001    /*
002     * $Id: JXDialog.java 3400 2009-07-22 17:27:45Z kschaefe $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    package org.jdesktop.swingx;
022    
023    import java.awt.Dialog;
024    import java.awt.Dimension;
025    import java.awt.Frame;
026    import java.util.Locale;
027    
028    import javax.swing.Action;
029    import javax.swing.BorderFactory;
030    import javax.swing.Box;
031    import javax.swing.BoxLayout;
032    import javax.swing.JButton;
033    import javax.swing.JComponent;
034    import javax.swing.JDialog;
035    import javax.swing.JPanel;
036    import javax.swing.JToolBar;
037    import javax.swing.plaf.basic.BasicOptionPaneUI;
038    
039    import org.jdesktop.swingx.action.BoundAction;
040    import org.jdesktop.swingx.plaf.LookAndFeelAddons;
041    import org.jdesktop.swingx.plaf.UIManagerExt;
042    
043    /**
044     * First cut for enhanced Dialog. The idea is to have a pluggable content
045     * from which the dialog auto-configures all its "dialogueness". 
046     * 
047     * <ul>
048     * <li> accepts a content and configures itself from content's properties - 
049     *  replaces the execute action from the appropriate action in content's action map (if any)
050     *  and set's its title from the content's name. 
051     * <li> registers stand-in actions for close/execute with the dialog's RootPane
052     * <li> registers keyStrokes for esc/enter to trigger the close/execute actions
053     * <li> takes care of building the button panel using the close/execute actions.
054     * </ul> 
055     * 
056     * <ul>
057     * <li>TODO: add link to forum discussion, wiki summary? 
058     * <li>PENDING: add support for vetoing the close.
059     * <li>PENDING: add complete set of constructors
060     * <li>PENDING: add windowListener to delegate to close action
061     * </ul>
062     * 
063     * @author Jeanette Winzenburg
064     * @author Karl Schaefer
065     */
066    public class JXDialog extends JDialog {
067    
068        static {
069            // Hack to enforce loading of SwingX framework ResourceBundle
070            LookAndFeelAddons.getAddon();
071        }
072        
073        public static final String EXECUTE_ACTION_COMMAND = "execute";
074        public static final String CLOSE_ACTION_COMMAND = "close";
075        public static final String UIPREFIX = "XDialog.";
076    
077        protected JComponent content;
078        
079        /**
080         * Creates a non-modal dialog with the given component as 
081         * content and without specified owner.  A shared, hidden frame will be
082         * set as the owner of the dialog.
083         * <p>
084         * @param content the component to show and to auto-configure from.
085         */
086        public JXDialog(JComponent content) {
087            super();
088            setContent(content);
089        }
090        
091        
092        /**
093         * Creates a non-modal dialog with the given component as content and the
094         * specified <code>Frame</code> as owner.
095         * <p>
096         * @param frame the owner
097         * @param content the component to show and to auto-configure from.
098         */
099        public JXDialog(Frame frame, JComponent content) {
100            super(frame);
101            setContent(content);
102        }
103        
104        /**
105         * Creates a non-modal dialog with the given component as content and the
106         * specified <code>Dialog</code> as owner.
107         * <p>
108         * @param dialog the owner
109         * @param content the component to show and to auto-configure from.
110         */
111        public JXDialog(Dialog dialog, JComponent content) {
112            super(dialog);
113            setContent(content);
114        }
115    
116        /**
117         * {@inheritDoc}
118         */
119        @Override
120        protected JXRootPane createRootPane() {
121            return new JXRootPane();
122        }
123    
124        /**
125         * {@inheritDoc}
126         */
127        public JXRootPane getRootPane() {
128            return (JXRootPane) super.getRootPane();
129        }
130        
131        /**
132         * Sets the status bar property on the underlying {@code JXRootPane}.
133         * 
134         * @param statusBar
135         *            the {@code JXStatusBar} which is to be the status bar
136         * @see #getStatusBar()
137         * @see JXRootPane#setStatusBar(JXStatusBar)
138         */
139        public void setStatusBar(JXStatusBar statusBar) {
140            getRootPane().setStatusBar(statusBar);
141        }
142        
143        /**
144         * Returns the value of the status bar property from the underlying
145         * {@code JXRootPane}.
146         * 
147         * @return the {@code JXStatusBar} which is the current status bar
148         * @see #setStatusBar(JXStatusBar)
149         * @see JXRootPane#getStatusBar()
150         */
151        public JXStatusBar getStatusBar() {
152            return getRootPane().getStatusBar();
153        }
154    
155        /**
156         * Sets the tool bar property on the underlying {@code JXRootPane}.
157         * 
158         * @param toolBar
159         *            the {@code JToolBar} which is to be the tool bar
160         * @see #getToolBar()
161         * @see JXRootPane#setToolBar(JToolBar)
162         */
163        public void setToolBar(JToolBar toolBar) {
164            getRootPane().setToolBar(toolBar);
165        }
166        
167        /**
168         * Returns the value of the tool bar property from the underlying
169         * {@code JXRootPane}.
170         * 
171         * @return the {@code JToolBar} which is the current tool bar
172         * @see #setToolBar(JToolBar)
173         * @see JXRootPane#getToolBar()
174         */
175        public JToolBar getToolBar() {
176            return getRootPane().getToolBar();
177        }
178        
179        /**
180         * PENDING: widen access - this could be public to make the content really 
181         * pluggable?
182         * 
183         * @param content
184         */
185        private void setContent(JComponent content) {
186            if (this.content != null) {
187                throw new IllegalStateException("content must not be set more than once");
188            }
189            initActions();
190            Action contentCloseAction = content.getActionMap().get(CLOSE_ACTION_COMMAND);
191            if (contentCloseAction != null) {
192                putAction(CLOSE_ACTION_COMMAND, contentCloseAction);
193            }
194            Action contentExecuteAction = content.getActionMap().get(EXECUTE_ACTION_COMMAND);
195            if (contentExecuteAction != null) {
196                putAction(EXECUTE_ACTION_COMMAND, contentExecuteAction);
197            }
198            this.content = content;
199            build();
200            setTitleFromContent();
201        }
202    
203        /**
204         * Infers and sets this dialog's title from the the content. 
205         * Does nothing if content is null. 
206         * 
207         * Here: uses the content's name as title. 
208         */
209        protected void setTitleFromContent() {
210            if (content == null) return;
211            setTitle(content.getName());
212        }
213    
214        /**
215         * pre: content != null.
216         *
217         */
218        private void build() {
219            JComponent contentBox = new Box(BoxLayout.PAGE_AXIS); 
220            contentBox.add(content);
221            JComponent buttonPanel = createButtonPanel();
222            contentBox.add(buttonPanel);
223            contentBox.setBorder(BorderFactory.createEmptyBorder(14, 14, 14, 14));
224    //        content.applyComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
225            
226    //        fieldPanel.setAlignmentX();
227    //      buttonPanel.setAlignmentX(Component.RIGHT_ALIGNMENT);
228            add(contentBox);
229            
230        }
231    
232        /**
233         * {@inheritDoc}
234         * 
235         * Overridden to check if content is available. <p>
236         * PENDING: doesn't make sense - the content is immutable and guaranteed
237         * to be not null.
238         */
239        @Override
240        public void setVisible(boolean visible) {
241            if (content == null) throw 
242                new IllegalStateException("content must be built before showing the dialog");
243            super.setVisible(visible);
244        }
245    
246    //------------------------ dynamic locale support
247        
248    
249        /**
250         * {@inheritDoc} <p>
251         * 
252         * Overridden to set the content's Locale and then updated
253         * this dialog's internal state. <p>
254         * 
255         * 
256         */
257        @Override
258        public void setLocale(Locale l) {
259            /*
260             * NOTE: this is called from super's constructor as one of the
261             * first methods (prior to setting the rootPane!). So back out
262             * 
263             */  
264            if (content != null) {
265                content.setLocale(l);
266                updateLocaleState(l);
267            }
268            super.setLocale(l);
269        }
270        
271        /**
272         * Updates this dialog's locale-dependent state.
273         * 
274         * Here: updates title and actions.
275         * <p>
276         * 
277         * 
278         * @see #setLocale(Locale)
279         */
280        protected void updateLocaleState(Locale locale) {
281            setTitleFromContent();
282            for (Object key : getRootPane().getActionMap().allKeys()) {
283                if (key instanceof String) {
284                    Action contentAction = content.getActionMap().get(key);
285                    Action rootPaneAction = getAction(key);
286                    if ((!rootPaneAction.equals(contentAction))) {
287                        String keyString = getUIString((String) key, locale);
288                        if (!key.equals(keyString)) {
289                            rootPaneAction.putValue(Action.NAME, keyString);
290                        }
291                    }
292                }
293            }
294        }
295        
296        /**
297         * The callback method executed when closing the dialog. <p>
298         * Here: calls dispose. 
299         *
300         */
301        public void doClose() {
302            dispose();
303        }
304        
305        private void initActions() {
306            Action defaultAction = createCloseAction();
307            putAction(CLOSE_ACTION_COMMAND, defaultAction);
308            putAction(EXECUTE_ACTION_COMMAND, defaultAction);
309        }
310    
311        private Action createCloseAction() {
312            String actionName = getUIString(CLOSE_ACTION_COMMAND);
313            BoundAction action = new BoundAction(actionName,
314                    CLOSE_ACTION_COMMAND);
315            action.registerCallback(this, "doClose");
316            return action;
317        }
318    
319        /**
320         * create the dialog button controls.
321         * 
322         * 
323         * @return panel containing button controls
324         */
325        protected JComponent createButtonPanel() {
326            // PENDING: this is a hack until we have a dedicated ButtonPanel!
327            JPanel panel = new JPanel(new BasicOptionPaneUI.ButtonAreaLayout(true, 6))
328            {
329                @Override
330                public Dimension getMaximumSize() {
331                    return getPreferredSize();
332                }
333            };
334    
335            panel.setBorder(BorderFactory.createEmptyBorder(9, 0, 0, 0));
336            Action executeAction = getAction(EXECUTE_ACTION_COMMAND);
337            Action closeAction = getAction(CLOSE_ACTION_COMMAND);
338    
339            JButton defaultButton = new JButton(executeAction);
340            panel.add(defaultButton);
341            getRootPane().setDefaultButton(defaultButton);
342            
343            if (executeAction != closeAction) {
344                JButton b = new JButton(closeAction);
345                panel.add(b);
346                getRootPane().setCancelButton(b);
347            }
348            
349            return panel;
350        }
351    
352        /**
353         * convenience wrapper to access rootPane's actionMap.
354         * @param key
355         * @param action
356         */
357        private void putAction(Object key, Action action) {
358            getRootPane().getActionMap().put(key, action);
359        }
360        
361        /**
362         * convenience wrapper to access rootPane's actionMap.
363         * 
364         * @param key
365         * @return root pane's <code>ActionMap</code>
366         */
367        private Action getAction(Object key) {
368            return getRootPane().getActionMap().get(key);
369        }
370    
371        /**
372         * Returns a potentially localized value from the UIManager. The given key
373         * is prefixed by this component|s <code>UIPREFIX</code> before doing the
374         * lookup. The lookup respects this table's current <code>locale</code>
375         * property. Returns the key, if no value is found.
376         * 
377         * @param key the bare key to look up in the UIManager.
378         * @return the value mapped to UIPREFIX + key or key if no value is found.
379         */
380        protected String getUIString(String key) {
381            return getUIString(key, getLocale());
382        }
383    
384        /**
385         * Returns a potentially localized value from the UIManager for the 
386         * given locale. The given key
387         * is prefixed by this component's <code>UIPREFIX</code> before doing the
388         * lookup. Returns the key, if no value is found.
389         * 
390         * @param key the bare key to look up in the UIManager.
391         * @param locale the locale use for lookup
392         * @return the value mapped to UIPREFIX + key in the given locale,
393         *    or key if no value is found.
394         */
395        protected String getUIString(String key, Locale locale) {
396            String text = UIManagerExt.getString(UIPREFIX + key, locale);
397            return text != null ? text : key;
398        }
399    }