package hirondelle.movies.util.ui; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; /** <b>Standard dialog</b>, centralizing various display policies. <P>Using a standard class for all dialogs increases the uniformity of the application's appearance, and eliminates code repetition. <P>This standard dialog has the following characteristics : <ul> <li>it's centered on its owner <li>it can't be resized <li>it has a border of standard dimensions <li>it inherits the parent frame's icon <li>the title has standardized content <li>a row of buttons are placed at the bottom of the dialog, centered, and with fixed spacing <li>the escape key performs the same operation as the {@link OnClose} value passed to the constructor <li>preserves the usual ALT+TAB behavior when switching between applications </ul> <P> Taken individually, such policies are relatively minor. Taken as a group, they form an effective way of establish the overall feel of your application. <P><b>Login dialogs</b><br> Login dialogs represent a special case, since they have no parent JFrame. Thus, they cannot inherit an icon. In JDK 6, this can be fixed, by adding a method to this class to specify the icon. <P>In addition, closing a login dialog should cause the application to exit. However, {@link JDialog#setDefaultCloseOperation(int)} does not allow for that behavior, while this class does. <P><em>This class does not extend {@link JDialog}, since it doesn't need to</em>. As a pleasant side-effect of this choice, the javadoc for this class is <em>greatly</em> simplified. */ public final class StandardDialog { /** Construct a standard dialog. @param aOwner the frame which is the owner/caller/parent of this dialog. This dialog gets its icon and its position from the owner. Possibly null. It's strongly recommened to use a non-null owner. @param aTitle the text to appear on the title bar of this dialog @param aIsModal controls whether this dialog is modal: if <tt>true</tt>, then this dialog must be dismissed before you are allowed to return to the main window. @param aOnClose specifies desired behavior when this dialog closes @param aBody the body of the dialog, where the user enters information @param aButtons a row of buttons appearing at the bottom of this dialog */ public StandardDialog ( JFrame aOwner, String aTitle, boolean aIsModal, OnClose aOnClose, JPanel aBody, java.util.List<JButton> aButtons ) { String title = UiUtil.getDialogTitle(aTitle); fDialog = new JDialog(aOwner, title, aIsModal); JPanel content = new JPanel(); content.setLayout(new BoxLayout(content, BoxLayout.PAGE_AXIS)); content.setBorder(UiUtil.getStandardBorder()); aBody.setAlignmentX(Component.CENTER_ALIGNMENT); content.add(aBody); content.add(Box.createVerticalStrut(10)); content.add(buildButtonPanel(aButtons)); fDialog.add(content); fDialog.setResizable(false); fDialog.setDefaultCloseOperation(aOnClose.getIntValue()); addCancelByEscapeKey(aOnClose); } /** Display the dialog. <P> The dialog is not automatically displayed in the constructor. This is because some callers may want to build a dialog upon startup, but only display it later. (Such a style might be chosen in order to slightly improve the apparent responsiveness of the application.) */ public void display() { UiUtil.centerAndShow(fDialog); } /** Assign a default button for this dialog. */ public void setDefaultButton(JButton aButton) { fDialog.getRootPane().setDefaultButton(aButton); } /** Call <tt>dispose</tt> on the underlying dialog object. */ public void dispose() { fDialog.dispose(); } /** Return the underlying dialog object. */ public JDialog getDialog() { return fDialog; } // PRIVATE private JDialog fDialog; /** Force the escape key to call the same action as the default {@link OnClose} operation passed to the constructor. In some special cases, this does not always work. */ private void addCancelByEscapeKey(final OnClose aOnClose) { String CANCEL_ACTION_KEY = "CANCEL_ACTION_KEY"; int noModifiers = 0; KeyStroke escapeKey = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, noModifiers, false); InputMap inputMap = fDialog.getRootPane().getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); inputMap.put(escapeKey, CANCEL_ACTION_KEY); AbstractAction cancelAction = new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (OnClose.DO_NOTHING == aOnClose) { // do nothing } else if (OnClose.DISPOSE == aOnClose) { fDialog.dispose(); } else if (OnClose.HIDE == aOnClose) { fDialog.setVisible(false); } else if (OnClose.EXIT == aOnClose) { fDialog.dispose(); System.exit(0); } else { throw new AssertionError("Unexpected branch for this value of OnClose: " + aOnClose); } } }; fDialog.getRootPane().getActionMap().put(CANCEL_ACTION_KEY, cancelAction); } private JPanel buildButtonPanel(java.util.List<JButton> aButtons) { JPanel result = new JPanel(); result.setLayout(new BoxLayout(result, BoxLayout.LINE_AXIS)); result.add(Box.createHorizontalGlue()); int count = 0; for (JButton button : aButtons) { count++; result.add(button); if (count < aButtons.size()) { result.add(Box.createHorizontalStrut(6)); } } result.add(Box.createHorizontalGlue()); return result; } }