001package hirondelle.stocks.util.ui; 002 003import java.util.*; 004import javax.swing.*; 005import java.awt.event.*; 006 007import hirondelle.stocks.util.Args; 008 009/** 010* Abstract Base Class for a dialog with standard layout, buttons, and behavior. 011* (The name <tt>StandardEditor</tt> was chosen since almost all non-trivial 012* dialogs allow the user to edit data in some way.) 013* 014* <P>Use of this class will apply a standard appearance to 015* dialogs in the application. 016* 017* <P> Subclasses implement the body of the dialog (wherein business objects 018* are manipulated), and the action taken by the <tt>OK</tt> button. 019* 020* <P>Services of a <tt>StandardEditor</tt> include: 021*<ul> 022* <li>centering on the parent frame 023* <li>reusing the parent's icon 024* <li>standard layout and border spacing, based on Java Look and Feel guidelines. 025* <li>uniform naming style for dialog title, with the application name appearing first 026* <li><tt>OK</tt> and <tt>Cancel</tt> buttons at the bottom of the dialog - 027* <tt>OK</tt> is the default, and the <tt>Escape</tt> key activates 028* <tt>Cancel</tt> (the latter works only if the dialog receives the escape 029* keystroke, and not one of its components) 030* <li>disabling of resizing 031*</ul> 032* 033* <P>The <tt>Escape</tt> key does not always work (for example, when a 034* <tt>JTable</tt> row has the focus) 035*/ 036public abstract class StandardEditor { 037 038 /** 039 * Constructor. 040 * @param aTitle text which appears in the title bar after the name of 041 * the application; must have content. 042 * @param aParent window to which this dialog is attached. 043 * @param aCloseAction sets the behaviour of the dialog upon close. 044 */ 045 protected StandardEditor (String aTitle, JFrame aParent, CloseAction aCloseAction) { 046 Args.checkForContent(aTitle); 047 Args.checkForNull(aParent); 048 fTitle = aTitle; 049 fParent = aParent; 050 fCloseAction = aCloseAction.getValue(); 051 } 052 053 /** 054 * Forces calls to constructor to have greater clarity, by using an 055 * enumeration instead of integers. 056 */ 057 protected enum CloseAction { 058 DISPOSE(JDialog.DISPOSE_ON_CLOSE), 059 HIDE(JDialog.HIDE_ON_CLOSE); 060 int getValue(){ 061 return fAction; 062 } 063 private final int fAction; 064 private CloseAction(int aAction){ 065 fAction = aAction; 066 } 067 } 068 069 /** 070 * Display this <tt>StandardEditor</tt> to the user. 071 * 072 * <P>Follows the Java Look and Feel guidelines for spacing elements. 073 */ 074 public final void showDialog(){ 075 boolean isModal = true; 076 fDialog = new JDialog(fParent, UiUtil.getDialogTitle(fTitle), isModal); 077 fDialog.setDefaultCloseOperation(fCloseAction); 078 fDialog.setResizable(false); 079 addCancelByEscapeKey(); 080 081 JPanel standardLayout = new JPanel(); 082 standardLayout.setLayout(new BoxLayout(standardLayout, BoxLayout.Y_AXIS)); 083 standardLayout.setBorder(UiUtil.getStandardBorder()); 084 standardLayout.add(getEditorUI()); 085 standardLayout.add(getCommandRow()); 086 087 fDialog.getContentPane().add(standardLayout); 088 089 UiUtil.centerOnParentAndShow(fDialog); 090 } 091 092 /** Close the editor dialog. */ 093 public final void dispose(){ 094 fDialog.dispose(); 095 } 096 097 /** 098 * Return the GUI which allows the user to manipulate the business 099 * objects related to this dialog; this GUI will be placed above the 100 * <tt>OK</tt> and <tt>Cancel</tt> buttons, in a standard manner. 101 */ 102 protected abstract JComponent getEditorUI(); 103 104 /** 105 * The action taken when the user hits the <tt>OK</tt> button. 106 */ 107 protected abstract void okAction(); 108 109 // PRIVATE 110 private final String fTitle; 111 private final JFrame fParent; 112 private JDialog fDialog; 113 private final int fCloseAction; 114 115 /** 116 * Return a standardized row of command buttons, right-justified and 117 * all of the same size, with OK as the default button, and no mnemonics used, 118 * as per the Java Look and Feel guidelines. 119 */ 120 private JComponent getCommandRow() { 121 JButton ok = new JButton("OK"); 122 ok.addActionListener( new ActionListener() { 123 @Override public void actionPerformed(ActionEvent event) { 124 okAction(); 125 } 126 }); 127 fDialog.getRootPane().setDefaultButton( ok ); 128 JButton cancel = new JButton("Cancel"); 129 cancel.addActionListener( new ActionListener() { 130 public void actionPerformed(ActionEvent event) { 131 closeDialog(); 132 } 133 }); 134 List<JComponent> buttons = new ArrayList<>(); 135 buttons.add(ok); 136 buttons.add(cancel); 137 return UiUtil.getCommandRow(buttons); 138 } 139 140 /** 141 * Force the escape key to call the same action as pressing the Cancel button. 142 * 143 * <P>This does not always work. See class comment. 144 */ 145 private void addCancelByEscapeKey(){ 146 String CANCEL_ACTION_KEY = "CANCEL_ACTION_KEY"; 147 int noModifiers = 0; 148 KeyStroke escapeKey = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, noModifiers, false); 149 InputMap inputMap = 150 fDialog.getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) 151 ; 152 inputMap.put(escapeKey, CANCEL_ACTION_KEY); 153 AbstractAction cancelAction = new AbstractAction(){ 154 @Override public void actionPerformed(ActionEvent e){ 155 closeDialog(); 156 } 157 }; 158 fDialog.getRootPane().getActionMap().put(CANCEL_ACTION_KEY, cancelAction); 159 } 160 161 private void closeDialog(){ 162 fDialog.dispose(); 163 } 164}