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