001    /*
002     * Copyright 2002-2004 the original author or authors.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.richclient.dialog;
017    
018    import org.apache.commons.logging.Log;
019    import org.apache.commons.logging.LogFactory;
020    import org.springframework.richclient.application.support.ApplicationServicesAccessor;
021    import org.springframework.richclient.command.AbstractCommand;
022    import org.springframework.richclient.command.ActionCommand;
023    import org.springframework.richclient.command.CommandGroup;
024    import org.springframework.richclient.core.Guarded;
025    import org.springframework.richclient.core.TitleConfigurable;
026    import org.springframework.richclient.util.GuiStandardUtils;
027    import org.springframework.richclient.util.WindowUtils;
028    import org.springframework.util.Assert;
029    import org.springframework.util.StringUtils;
030    
031    import javax.swing.*;
032    import java.awt.*;
033    import java.awt.event.KeyEvent;
034    import java.awt.event.WindowAdapter;
035    import java.awt.event.WindowEvent;
036    import java.awt.event.WindowFocusListener;
037    
038    /**
039     * <p>
040     * Abstract Base Class for a dialog with standard layout, buttons, and behavior.
041     * </p>
042     * <p>
043     * Use of this class will apply a standard appearance to dialogs in the
044     * application.
045     * </p>
046     * <p>
047     * Subclasses implement the body of the dialog (wherein business objects are
048     * manipulated), and the action taken by the <code>OK</code> button. Aside
049     * from creating the dialog's contentj with {@link #createDialogContentPane()},
050     * a proper disposing should be implemented in
051     * {@link #disposeDialogContentPane()}.
052     * </p>
053     * <p>
054     * Services of a <code>ApplicationDialog</code> include:
055     * <ul>
056     * <li>centering on the parent frame</li>
057     * <li>reusing the parent's icon</li>
058     * <li>standard layout and border spacing, based on Java Look and Feel
059     * guidelines.</li>
060     * <li>uniform naming style for dialog title</li>
061     * <li><code>OK</code> and <code>Cancel</code> buttons at the bottom of the
062     * dialog -<code>OK</code> is the default, and the <code>Escape</code> key
063     * activates <code>Cancel</code> (the latter works only if the dialog receives
064     * the escape keystroke, and not one of its components)</li>
065     * <li>by default, modal</li>
066     * <li>enabling & disabling of resizing</li>
067     * <li>will be shown in taskbar if no parent window has been set, and no
068     * applicationwindow is open</li>
069     * </ul>
070     * </p>
071     * <em>Note: Default close behaviour is to dispose the graphical dialog when it closes, you can set the CloseAction to hide if needed.</em>
072     *
073     * @author Keith Donald
074     * @author Jan Hoskens
075     */
076    public abstract class ApplicationDialog extends ApplicationServicesAccessor implements TitleConfigurable, Guarded {
077    
078            private static final String DEFAULT_DIALOG_TITLE = "Application Dialog";
079    
080            protected static final String DEFAULT_FINISH_COMMAND_ID = "okCommand";
081    
082            protected static final String DEFAULT_CANCEL_COMMAND_ID = "cancelCommand";
083    
084            protected static final String DEFAULT_FINISH_SUCCESS_MESSAGE_KEY = "defaultFinishSuccessMessage";
085    
086            protected static final String DEFAULT_FINISH_SUCCESS_TITLE_KEY = "defaultFinishSuccessTitle";
087    
088            protected static final String SUCCESS_FINISH_MESSAGE_KEY = "finishSuccessMessage";
089    
090            protected static final String SUCCESS_FINISH_TITLE_KEY = "finishSuccessTitle";
091    
092            protected final Log logger = LogFactory.getLog(getClass());
093    
094            private final DialogEventHandler dialogEventHandler = new DialogEventHandler();
095    
096            private String title;
097    
098            private JDialog dialog;
099    
100            private Component parentComponent;
101    
102            private Window parentWindow;
103    
104            private CloseAction closeAction = CloseAction.DISPOSE;
105    
106            private boolean defaultEnabled = true;
107    
108            private boolean modal = true;
109    
110            private boolean resizable = true;
111    
112            private Dimension preferredSize;
113    
114            private Point location;
115    
116            private Component locationRelativeTo;
117    
118            private ActionCommand finishCommand;
119    
120            private ActionCommand cancelCommand;
121    
122            private CommandGroup dialogCommandGroup;
123    
124            private boolean displayFinishSuccessMessage;
125    
126            private ActionCommand callingCommand;
127    
128            /**
129             * Create dialog with default closeAction {@link CloseAction#DISPOSE}. No
130             * parent or title set.
131             *
132             * @see #init()
133             */
134            public ApplicationDialog() {
135                    init();
136            }
137    
138            /**
139             * Create dialog with default closeAction {@link CloseAction#DISPOSE}.
140             *
141             * @param title text that will appear on dialog's titlebar.
142             * @param parent component serving as parent in it's hierarchy.
143             *
144             * @see #init()
145             */
146            public ApplicationDialog(String title, Component parent) {
147                    setTitle(title);
148                    setParentComponent(parent);
149                    init();
150            }
151    
152            /**
153             * Creates a new application dialog. The actual UI is not initialized until
154             * showDialog() is called.
155             *
156             * @param title text which appears in the title bar after the name of the
157             * application.
158             * @param parent frame to which this dialog is attached.
159             * @param closeAction sets the behaviour of the dialog upon close. Default
160             * closeAction is {@link CloseAction#DISPOSE}.
161             *
162             * @see #init()
163             */
164            public ApplicationDialog(String title, Component parent, CloseAction closeAction) {
165                    setTitle(title);
166                    setParentComponent(parent);
167                    setCloseAction(closeAction);
168                    init();
169            }
170    
171            /**
172             * Hook called in constructor. Add specific initialization code here.
173             */
174            protected void init() {
175            }
176    
177            /**
178             * {@inheritDoc}
179             */
180            public void setTitle(String title) {
181                    this.title = title;
182                    if (dialog != null) {
183                            dialog.setTitle(getTitle());
184                    }
185            }
186    
187            /**
188             * Returns the title of this dialog. If no specific title has been set, the
189             * calling command's text will be used. If that doesn't yield a result, the
190             * default title is returned.
191             *
192             * @see #getCallingCommandText()
193             * @see #DEFAULT_DIALOG_TITLE
194             */
195            protected String getTitle() {
196                    if (!StringUtils.hasText(this.title)) {
197                            if (StringUtils.hasText(getCallingCommandText()))
198                                    return getCallingCommandText();
199    
200                            return DEFAULT_DIALOG_TITLE;
201                    }
202                    return this.title;
203            }
204    
205            /**
206             * The parent Component that will be used to extract the Frame/Dialog owner
207             * for the JDialog at creation. You may pass a Window/Frame that will be
208             * used directly as parent for the JDialog, or you can pass the component
209             * which has one of both in it's parent hierarchy. The latter option can be
210             * handy when you're locally implementing Components without a direct
211             * -connection to/notion of- a Window/Frame.
212             *
213             * @param parentComponent Component that is a Frame/Window or has one in its
214             * parent hierarchy.
215             */
216            public void setParentComponent(Component parentComponent) {
217                    this.parentComponent = parentComponent;
218            }
219    
220            /**
221             * Returns the parent Component.
222             *
223             * @return
224             * @see #setParentComponent(Component)
225             */
226            public Component getParentComponent() {
227                    return this.parentComponent;
228            }
229    
230            /**
231             * Set the {@link CloseAction} of this dialog. Default action is
232             * {@link CloseAction#DISPOSE} which disposes the visual dialog upon
233             * closing. When using {@link CloseAction#HIDE} the visual components are
234             * cached and reused.
235             *
236             * @param action the {@link CloseAction} to use when closing the dialog.
237             */
238            public void setCloseAction(CloseAction action) {
239                    this.closeAction = action;
240            }
241    
242            /**
243             * When opening the dialog, the finish button can be enabled by default.
244             *
245             * @param enabled <code>true</code> when the finish button should be
246             * enabled by default, <code>false</code> otherwise.
247             */
248            public void setDefaultEnabled(boolean enabled) {
249                    this.defaultEnabled = enabled;
250            }
251    
252            /**
253             * Set the modal property of the dialog.
254             *
255             * @see JDialog#setModal(boolean)
256             */
257            public void setModal(boolean modal) {
258                    this.modal = modal;
259            }
260    
261            /**
262             * Set the resizable property of the dialog.
263             *
264             * @see JDialog#setResizable(boolean)
265             */
266            public void setResizable(boolean resizable) {
267                    this.resizable = resizable;
268            }
269    
270            /**
271             * Set a specific location for the JDialog to popup.
272             *
273             * @param location point on screen where to place the JDialog.
274             */
275            public void setLocation(Point location) {
276                    this.location = location;
277            }
278    
279            /**
280             * Set a relative location for the JDialog to popup.
281             *
282             * @see Window#setLocationRelativeTo(Component)
283             */
284            public void setLocationRelativeTo(Component locationRelativeTo) {
285                    this.locationRelativeTo = locationRelativeTo;
286            }
287    
288            /**
289             * Set the preferrred size for the JDialog.
290             *
291             * @see JComponent#setPreferredSize(Dimension)
292             */
293            public void setPreferredSize(Dimension preferredSize) {
294                    this.preferredSize = preferredSize;
295            }
296    
297            /**
298             * Enable/disable the finish command of the dialog.
299             */
300            public void setEnabled(boolean enabled) {
301                    setFinishEnabled(enabled);
302            }
303    
304            /**
305             * Message to show upon succesful completion.
306             */
307            public void setDisplayFinishSuccessMessage(boolean displayFinishSuccessMessage) {
308                    this.displayFinishSuccessMessage = displayFinishSuccessMessage;
309            }
310    
311            /**
312             * Set the command that opened this dialog.
313             *
314             * @see #getFinishSuccessMessage()
315             * @see #getFinishSuccessTitle()
316             */
317            public void setCallingCommand(ActionCommand callingCommand) {
318                    this.callingCommand = callingCommand;
319            }
320    
321            /**
322             * Enable/disable the finish command.
323             */
324            protected void setFinishEnabled(boolean enabled) {
325                    if (isControlCreated()) {
326                            finishCommand.setEnabled(enabled);
327                    }
328            }
329    
330            /**
331             * Returns whether this Dialog is enabled.
332             */
333            public boolean isEnabled() {
334                    if (isControlCreated())
335                            return finishCommand.isEnabled();
336    
337                    return false;
338            }
339    
340            /**
341             * Returns <code>true</code> if the JDialog is showing.
342             *
343             * @see JDialog#isShowing()
344             */
345            public boolean isShowing() {
346                    if (!isControlCreated()) {
347                            return false;
348                    }
349                    return dialog.isShowing();
350            }
351    
352            /**
353             * Returns <code>true</code> if the JDialog is constructed.
354             */
355            public boolean isControlCreated() {
356                    return dialog != null;
357            }
358    
359            /**
360             * Return the JDialog, create it if needed (lazy).
361             */
362            public JDialog getDialog() {
363                    if (!isControlCreated()) {
364                            createDialog();
365                    }
366                    return dialog;
367            }
368    
369            /**
370             * Return the contentPane of the dialog.
371             *
372             * @see JDialog#getContentPane()
373             */
374            protected Container getDialogContentPane() {
375                    Assert.state(isControlCreated(), "The wrapped JDialog control has not yet been created.");
376                    return dialog.getContentPane();
377            }
378    
379            /**
380             * <p>
381             * Show the dialog. The dialog will be created if it doesn't exist yet.
382             * Before setting the dialog visible, a hook method onAboutToShow is called
383             * and the location will be set.
384             * </p>
385             * <p>
386             * When showing the dialog several times, it will always be opened on the
387             * location that has been set, or relative to the parent. (former location
388             * will not persist)
389             * </p>
390             */
391            public void showDialog() {
392                    if (!isControlCreated()) {
393                            createDialog();
394                    }
395                    if (!isShowing()) {
396                            onAboutToShow();
397                            if (getLocation() != null) {
398                                    dialog.setLocation(getLocation());
399                    dialog.setPreferredSize(getPreferredSize());
400                            }
401                            else {
402                                    WindowUtils.centerOnParent(dialog, getLocationRelativeTo());
403                            }
404    
405                            dialog.setVisible(true);
406                    }
407            }
408    
409            /**
410             * Subclasses should call if layout of the dialog components changes.
411             */
412            protected void componentsChanged() {
413                    if (isControlCreated()) {
414                            dialog.pack();
415                    }
416            }
417    
418            /**
419             * Builds/initializes the dialog and all of its components.
420             * <p>
421             * Follows the Java Look and Feel guidelines for spacing elements.
422             */
423            protected final void createDialog() {
424                    constructDialog();
425                    addDialogComponents();
426                    attachListeners();
427                    registerDefaultCommand();
428                    onInitialized();
429    
430                    dialog.pack();
431            }
432    
433            /**
434             * Construct the visual dialog frame on which the content needs to be added.
435             */
436            private void constructDialog() {
437                    if (getParentWindow() instanceof Frame) {
438                            dialog = new JDialog((Frame) getParentWindow(), getTitle(), modal);
439                    }
440                    else {
441                            dialog = new JDialog((Dialog) getParentWindow(), getTitle(), modal);
442                    }
443    
444                    dialog.getContentPane().setLayout(new BorderLayout());
445                    dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
446                    dialog.setResizable(resizable);
447    
448                    initStandardCommands();
449                    addCancelByEscapeKey();
450            }
451    
452            /**
453             * <p>
454             * --jh-- This method is copied from JOptionPane. I'm still trying to figure
455             * out why they chose to have a static method with package visibility for
456             * this one instead of just making it public.
457             * </p>
458             *
459             * Returns the specified component's toplevel <code>Frame</code> or
460             * <code>Dialog</code>.
461             *
462             * @param parentComponent the <code>Component</code> to check for a
463             * <code>Frame</code> or <code>Dialog</code>
464             * @return the <code>Frame</code> or <code>Dialog</code> that contains
465             * the component, or the default frame if the component is <code>null</code>,
466             * or does not have a valid <code>Frame</code> or <code>Dialog</code>
467             * parent
468             * @exception HeadlessException if
469             * <code>GraphicsEnvironment.isHeadless</code> returns <code>true</code>
470             * @see java.awt.GraphicsEnvironment#isHeadless
471             */
472            public static Window getWindowForComponent(Component parentComponent) throws HeadlessException {
473                    if (parentComponent == null)
474                            return JOptionPane.getRootFrame();
475                    if (parentComponent instanceof Frame || parentComponent instanceof Dialog)
476                            return (Window) parentComponent;
477                    return getWindowForComponent(parentComponent.getParent());
478            }
479    
480            /**
481             * Initialize the standard commands needed on a Dialog: Ok/Cancel.
482             */
483            private void initStandardCommands() {
484                    finishCommand = new ActionCommand(getFinishCommandId()) {
485                            public void doExecuteCommand() {
486                                    boolean result = onFinish();
487                                    if (result) {
488                                            if (getDisplayFinishSuccessMessage()) {
489                                                    showFinishSuccessMessageDialog();
490                                            }
491                                            executeCloseAction();
492                                    }
493                            }
494                    };
495                    finishCommand.setSecurityControllerId(getFinishSecurityControllerId());
496                    finishCommand.setEnabled(defaultEnabled);
497    
498                    cancelCommand = new ActionCommand(getCancelCommandId()) {
499    
500                            public void doExecuteCommand() {
501                                    onCancel();
502                            }
503                    };
504            }
505    
506            /**
507             * Subclasses may override to return a custom message key, default is
508             * "okCommand", corresponding to the "&OK" label.
509             *
510             * @return The message key to use for the finish ("ok") button
511             */
512            protected String getFinishCommandId() {
513                    return DEFAULT_FINISH_COMMAND_ID;
514            }
515    
516            /**
517             * Subclasses may override to return a security controller id to be attached
518             * to the finish command. The default is null, no controller.
519             *
520             * @return security controller id, or null if none
521             */
522            protected String getFinishSecurityControllerId() {
523                    return null;
524            }
525    
526            /**
527             * Request invocation of the action taken when the user hits the
528             * <code>OK</code> (finish) button.
529             *
530             * @return true if action completed successfully; false otherwise.
531             */
532            protected abstract boolean onFinish();
533    
534            /**
535             * Return the message that needs to be set on a succesful finish.
536             */
537            protected boolean getDisplayFinishSuccessMessage() {
538                    return displayFinishSuccessMessage;
539            }
540    
541            /**
542             * Opens a dialog which contains the sussesful finish message.
543             *
544             * @see #getFinishSuccessTitle()
545             * @see #getFinishSuccessMessage()
546             */
547            protected void showFinishSuccessMessageDialog() {
548                    MessageDialog messageDialog = new MessageDialog(getFinishSuccessTitle(), getDialog(), getFinishSuccessMessage());
549                    messageDialog.showDialog();
550            }
551    
552            /**
553             * Returns the message to use upon succesful finish.
554             */
555            protected String getFinishSuccessMessage() {
556                    ActionCommand callingCommand = getCallingCommand();
557                    if (callingCommand != null) {
558                            String[] successMessageKeys = new String[] { callingCommand.getId() + "." + SUCCESS_FINISH_MESSAGE_KEY,
559                                            DEFAULT_FINISH_SUCCESS_MESSAGE_KEY };
560                            return getMessage(successMessageKeys, getFinishSuccessMessageArguments());
561                    }
562                    return getMessage(DEFAULT_FINISH_SUCCESS_MESSAGE_KEY);
563            }
564    
565            /**
566             * Returns the command that opened this dialog.
567             */
568            protected ActionCommand getCallingCommand() {
569                    return callingCommand;
570            }
571    
572            /**
573             * Returns the arguments to use in the succesful finish message.
574             */
575            protected Object[] getFinishSuccessMessageArguments() {
576                    return new Object[0];
577            }
578    
579            /**
580             * Returns the title to use upon succesful finish.
581             */
582            protected String getFinishSuccessTitle() {
583                    ActionCommand callingCommand = getCallingCommand();
584                    if (callingCommand != null) {
585                            String[] successTitleKeys = new String[] { callingCommand.getId() + "." + SUCCESS_FINISH_TITLE_KEY,
586                                            DEFAULT_FINISH_SUCCESS_TITLE_KEY };
587                            return getMessage(successTitleKeys, getFinishSuccessTitleArguments());
588                    }
589                    return getMessage(DEFAULT_FINISH_SUCCESS_TITLE_KEY);
590            }
591    
592            /**
593             * Returns the arguments to use in the finish succesful title.
594             */
595            protected Object[] getFinishSuccessTitleArguments() {
596                    if (StringUtils.hasText(getCallingCommandText()))
597                            return new Object[] { getCallingCommandText() };
598    
599                    return new Object[0];
600            }
601    
602            /**
603             * Return the text of the command that opened this dialog.
604             */
605            private String getCallingCommandText() {
606                    return getCallingCommand() != null ? getCallingCommand().getText() : null;
607            }
608    
609            /**
610             * Returns the id for the cancel command.
611             */
612            protected String getCancelCommandId() {
613                    return DEFAULT_CANCEL_COMMAND_ID;
614            }
615    
616            /**
617             * Returns the finish command.
618             */
619            protected ActionCommand getFinishCommand() {
620                    return finishCommand;
621            }
622    
623            /**
624             * Returns the cancel command.
625             */
626            protected ActionCommand getCancelCommand() {
627                    return cancelCommand;
628            }
629    
630            /**
631             * Force the escape key to call the same action as pressing the Cancel
632             * button. This does not always work. See class comment.
633             */
634            private void addCancelByEscapeKey() {
635                    int noModifiers = 0;
636                    KeyStroke escapeKey = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, noModifiers, false);
637                    addActionKeyBinding(escapeKey, cancelCommand.getId());
638            }
639    
640            /**
641             * Add an action key binding to this dialog.
642             *
643             * @param key the {@link KeyStroke} that triggers the command/action.
644             * @param actionKey id of command that will be triggered by the {@link KeyStroke}.
645             *
646             * @see #addActionKeyBinding(KeyStroke, String, Action)
647             */
648            protected void addActionKeyBinding(KeyStroke key, String actionKey) {
649                    if (actionKey == finishCommand.getId()) {
650                            addActionKeyBinding(key, actionKey, finishCommand.getActionAdapter());
651                    }
652                    else if (actionKey == cancelCommand.getId()) {
653                            addActionKeyBinding(key, actionKey, cancelCommand.getActionAdapter());
654                    }
655                    else {
656                            throw new IllegalArgumentException("Unknown action key " + actionKey);
657                    }
658            }
659    
660            /**
661             * Add an action key binding to this dialog.
662             *
663             * @param key the {@link KeyStroke} that triggers the command/action.
664             * @param actionKey id of the action.
665             * @param action {@link Action} that will be triggered by the {@link KeyStroke}.
666             *
667             * @see #getActionMap()
668             * @see #getInputMap()
669             * @see ActionMap#put(Object, Action)
670             * @see InputMap#put(KeyStroke, Object)
671             */
672            protected void addActionKeyBinding(KeyStroke key, String actionKey, Action action) {
673                    getInputMap().put(key, actionKey);
674                    getActionMap().put(actionKey, action);
675            }
676    
677            /**
678             * Return the {@link ActionMap} of the dialog.
679             *
680             * @see JLayeredPane#getActionMap()
681             */
682            protected ActionMap getActionMap() {
683                    return getDialog().getLayeredPane().getActionMap();
684            }
685    
686            /**
687             * Return the {@link InputMap} of the dialog.
688             *
689             * @see JLayeredPane#getInputMap(int)
690             */
691            protected InputMap getInputMap() {
692                    return getDialog().getLayeredPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
693            }
694    
695            /**
696             * Subclasses may override to customize how this dialog is built.
697             */
698            protected void addDialogComponents() {
699                    JComponent dialogContentPane = createDialogContentPane();
700                    GuiStandardUtils.attachDialogBorder(dialogContentPane);
701                    if (getPreferredSize() != null) {
702                            dialogContentPane.setPreferredSize(getPreferredSize());
703                    }
704                    getDialogContentPane().add(dialogContentPane);
705                    getDialogContentPane().add(createButtonBar(), BorderLayout.SOUTH);
706            }
707    
708            /**
709             * Return the location of the dialog.
710             */
711            protected Point getLocation() {
712                    return location;
713            }
714    
715            /**
716             * Return the relative location of the dialog.
717             */
718            protected Component getLocationRelativeTo() {
719                    return locationRelativeTo;
720            }
721    
722            /**
723             * Return the preferred size for the dialog.
724             */
725            protected Dimension getPreferredSize() {
726                    return preferredSize;
727            }
728    
729            /**
730             * Return the GUI which allows the user to manipulate the business objects
731             * related to this dialog. This GUI will be placed above the <code>OK</code>
732             * and <code>Cancel</code> buttons, in a standard manner.
733             *
734             * <p>
735             * Any components/objects created at this point need to be disposed in
736             * {@link #disposeDialogContentPane()}.
737             * </p>
738             *
739             * @see #disposeDialogContentPane()
740             */
741            protected abstract JComponent createDialogContentPane();
742    
743            /**
744             * Attach the handler that invokes the lifecycle methods on the
745             * <code>ApplicationDialog</code>.
746             *
747             * @see DialogEventHandler
748             */
749            protected final void attachListeners() {
750                    dialog.addWindowFocusListener(dialogEventHandler);
751                    dialog.addWindowListener(dialogEventHandler);
752            }
753    
754            /**
755             * Return a standardized row of command buttons, right-justified and all of
756             * the same size, with OK as the default button, and no mnemonics used, as
757             * per the Java Look and Feel guidelines.
758             */
759            protected JComponent createButtonBar() {
760                    this.dialogCommandGroup = CommandGroup.createCommandGroup(null, getCommandGroupMembers());
761                    JComponent buttonBar = this.dialogCommandGroup.createButtonBar();
762                    GuiStandardUtils.attachDialogBorder(buttonBar);
763                    return buttonBar;
764            }
765    
766            /**
767             * Template getter method to return the commands to populate the dialog
768             * button bar.
769             *
770             * @return The array of commands (may also be a separator or glue
771             * identifier)
772             */
773            protected Object[] getCommandGroupMembers() {
774                    return new AbstractCommand[] { getFinishCommand(), getCancelCommand() };
775            }
776    
777            /**
778             * Register the finish button as the default dialog button.
779             */
780            protected void registerDefaultCommand() {
781                    if (isControlCreated()) {
782                            finishCommand.setDefaultButtonIn(getDialog());
783                    }
784            }
785    
786            /**
787             * Register the cancel button as the default dialog button.
788             */
789            protected final void registerCancelCommandAsDefault() {
790                    if (isControlCreated()) {
791                            cancelCommand.setDefaultButtonIn(getDialog());
792                    }
793            }
794    
795            /**
796             * Register the provided button as the default dialog button. The button
797             * must be present on the dialog.
798             *
799             * @param command The button to become the default.
800             */
801            protected final void registerDefaultCommand(ActionCommand command) {
802                    if (isControlCreated()) {
803                            command.setDefaultButtonIn(getDialog());
804                    }
805            }
806    
807            /**
808             * Template lifecycle method invoked after the dialog control is
809             * initialized.
810             */
811            protected void onInitialized() {
812            }
813    
814            /**
815             * Template lifecycle method invoked right before the dialog is to become
816             * visible.
817             */
818            protected void onAboutToShow() {
819            }
820    
821            /**
822             * Template lifecycle method invoked when the dialog gains focus.
823             */
824            protected void onWindowGainedFocus() {
825            }
826    
827            /**
828             * Template lifecycle method invoked when the dialog is activated.
829             */
830            protected void onWindowActivated() {
831            }
832    
833            /**
834             * Template lifecycle method invoked when the dialog loses focus.
835             */
836            protected void onWindowLostFocus() {
837            }
838    
839            /**
840             * Template lifecycle method invoked when the dialog's window is closing.
841             */
842            protected void onWindowClosing() {
843            }
844    
845            /**
846             * Handle a dialog cancellation request.
847             */
848            protected void onCancel() {
849                    executeCloseAction();
850            }
851    
852            /**
853             * Select the appropriate close logic.
854             */
855            private void executeCloseAction() {
856                    if (closeAction == CloseAction.HIDE) {
857                            hide();
858                    }
859                    else {
860                            dispose();
861                    }
862            }
863    
864            /**
865             * Close and dispose of the visual dialog. This forces the dialog to be
866             * re-built on the next show. Any subclasses that are creating visual
867             * components and holding references to them should dispose them when the
868             * surrounding dialog is disposed by implementing
869             * {@link #disposeDialogContentPane()}. Any other objects that are created
870             * in {@link #createDialogContentPane()} can be handled here as well.
871             *
872             * @see #disposeDialogContentPane()
873             */
874            protected final void dispose() {
875                    if (dialog != null) {
876                            onWindowClosing();
877                            disposeDialogContentPane();
878                            dialog.dispose();
879                            dialog = null;
880                    }
881            }
882    
883            /**
884             * Cleanup any components/objects that are created during
885             * {@link #createDialogContentPane()}. This method is called if the
886             * {@link CloseAction} is set to {@link CloseAction#DISPOSE} and the dialog
887             * is being closed. This ensures that when disposing the surrounding dialog,
888             * the content pane can be disposed as well.
889             *
890             * @see #createDialogContentPane()
891             * @see #dispose()
892             */
893            protected void disposeDialogContentPane() {
894            }
895    
896            /**
897             * Hide the dialog. This differs from dispose in that the dialog control
898             * stays cached in memory.
899             */
900            protected final void hide() {
901                    if (dialog != null) {
902                            onWindowClosing();
903                            this.dialog.setVisible(false);
904                    }
905            }
906    
907            /**
908             * Returns the parent window based on the internal parent Component. Will
909             * search for a Window in the parent hierarchy if needed (when parent
910             * Component isn't a Window).
911             *
912             * @return the parent window
913             */
914            public Window getParentWindow() {
915                    if (parentWindow == null) {
916                            if ((parentComponent == null) && (getActiveWindow() != null)) {
917                                    parentWindow = getActiveWindow().getControl();
918                            }
919                            else {
920                                    parentWindow = getWindowForComponent(parentComponent);
921                            }
922                    }
923                    return parentWindow;
924            }
925    
926            /**
927             * Handler that will be registered as listener on the dialog.
928             */
929            private class DialogEventHandler extends WindowAdapter implements WindowFocusListener {
930                    public void windowActivated(WindowEvent e) {
931                            onWindowActivated();
932                    }
933    
934                    public void windowClosing(WindowEvent e) {
935                            getCancelCommand().execute();
936                    }
937    
938                    public void windowGainedFocus(WindowEvent e) {
939                            onWindowGainedFocus();
940                    }
941    
942                    public void windowLostFocus(WindowEvent e) {
943                            onWindowLostFocus();
944                    }
945            }
946    }