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}