/* * $Id$ * * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, * Santa Clara, California 95054, U.S.A. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jdesktop.swingx; import java.awt.Dialog; import java.awt.Dimension; import java.awt.Frame; import java.awt.Window; import java.util.Locale; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JPanel; import javax.swing.JToolBar; import javax.swing.plaf.basic.BasicOptionPaneUI; import org.jdesktop.beans.JavaBean; import org.jdesktop.swingx.action.BoundAction; import org.jdesktop.swingx.plaf.LookAndFeelAddons; import org.jdesktop.swingx.plaf.UIManagerExt; /** * First cut for enhanced Dialog. The idea is to have a pluggable content * from which the dialog auto-configures all its "dialogueness". * * <ul> * <li> accepts a content and configures itself from content's properties - * replaces the execute action from the appropriate action in content's action map (if any) * and set's its title from the content's name. * <li> registers stand-in actions for close/execute with the dialog's RootPane * <li> registers keyStrokes for esc/enter to trigger the close/execute actions * <li> takes care of building the button panel using the close/execute actions. * </ul> * * <ul> * <li>TODO: add link to forum discussion, wiki summary? * <li>PENDING: add support for vetoing the close. * <li>PENDING: add complete set of constructors * <li>PENDING: add windowListener to delegate to close action * </ul> * * @author Jeanette Winzenburg * @author Karl Schaefer */ @JavaBean public class JXDialog extends JDialog { static { // Hack to enforce loading of SwingX framework ResourceBundle LookAndFeelAddons.getAddon(); } public static final String EXECUTE_ACTION_COMMAND = "execute"; public static final String CLOSE_ACTION_COMMAND = "close"; public static final String UIPREFIX = "XDialog."; protected JComponent content; /** * Creates a non-modal dialog with the given component as * content and without specified owner. A shared, hidden frame will be * set as the owner of the dialog. * <p> * @param content the component to show and to auto-configure from. */ public JXDialog(JComponent content) { super(); setContent(content); } /** * Creates a non-modal dialog with the given component as content and the * specified <code>Frame</code> as owner. * <p> * @param frame the owner * @param content the component to show and to auto-configure from. */ public JXDialog(Frame frame, JComponent content) { super(frame); setContent(content); } /** * Creates a non-modal dialog with the given component as content and the * specified <code>Dialog</code> as owner. * <p> * @param dialog the owner * @param content the component to show and to auto-configure from. */ public JXDialog(Dialog dialog, JComponent content) { super(dialog); setContent(content); } /** * Creates a non-modal dialog with the given component as content and the * specified <code>Window</code> as owner. * <p> * @param window the owner * @param content the component to show and to auto-configure from. */ public JXDialog(Window window, JComponent content) { super(window); setContentPane(content); } /** * {@inheritDoc} */ @Override protected JXRootPane createRootPane() { return new JXRootPane(); } /** * {@inheritDoc} */ @Override public JXRootPane getRootPane() { return (JXRootPane) super.getRootPane(); } /** * Sets the status bar property on the underlying {@code JXRootPane}. * * @param statusBar * the {@code JXStatusBar} which is to be the status bar * @see #getStatusBar() * @see JXRootPane#setStatusBar(JXStatusBar) */ public void setStatusBar(JXStatusBar statusBar) { getRootPane().setStatusBar(statusBar); } /** * Returns the value of the status bar property from the underlying * {@code JXRootPane}. * * @return the {@code JXStatusBar} which is the current status bar * @see #setStatusBar(JXStatusBar) * @see JXRootPane#getStatusBar() */ public JXStatusBar getStatusBar() { return getRootPane().getStatusBar(); } /** * Sets the tool bar property on the underlying {@code JXRootPane}. * * @param toolBar * the {@code JToolBar} which is to be the tool bar * @see #getToolBar() * @see JXRootPane#setToolBar(JToolBar) */ public void setToolBar(JToolBar toolBar) { getRootPane().setToolBar(toolBar); } /** * Returns the value of the tool bar property from the underlying * {@code JXRootPane}. * * @return the {@code JToolBar} which is the current tool bar * @see #setToolBar(JToolBar) * @see JXRootPane#getToolBar() */ public JToolBar getToolBar() { return getRootPane().getToolBar(); } /** * PENDING: widen access - this could be public to make the content really * pluggable? * * @param content */ private void setContent(JComponent content) { if (this.content != null) { throw new IllegalStateException("content must not be set more than once"); } initActions(); Action contentCloseAction = content.getActionMap().get(CLOSE_ACTION_COMMAND); if (contentCloseAction != null) { putAction(CLOSE_ACTION_COMMAND, contentCloseAction); } Action contentExecuteAction = content.getActionMap().get(EXECUTE_ACTION_COMMAND); if (contentExecuteAction != null) { putAction(EXECUTE_ACTION_COMMAND, contentExecuteAction); } this.content = content; build(); setTitleFromContent(); } /** * Infers and sets this dialog's title from the the content. * Does nothing if content is null. * * Here: uses the content's name as title. */ protected void setTitleFromContent() { if (content == null) return; setTitle(content.getName()); } /** * pre: content != null. * */ private void build() { JComponent contentBox = new Box(BoxLayout.PAGE_AXIS); contentBox.add(content); JComponent buttonPanel = createButtonPanel(); contentBox.add(buttonPanel); contentBox.setBorder(BorderFactory.createEmptyBorder(14, 14, 14, 14)); // content.applyComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT); // fieldPanel.setAlignmentX(); // buttonPanel.setAlignmentX(Component.RIGHT_ALIGNMENT); add(contentBox); } /** * {@inheritDoc} * * Overridden to check if content is available. <p> * PENDING: doesn't make sense - the content is immutable and guaranteed * to be not null. */ @Override public void setVisible(boolean visible) { if (content == null) throw new IllegalStateException("content must be built before showing the dialog"); super.setVisible(visible); } //------------------------ dynamic locale support /** * {@inheritDoc} <p> * * Overridden to set the content's Locale and then updated * this dialog's internal state. <p> * * */ @Override public void setLocale(Locale l) { /* * NOTE: this is called from super's constructor as one of the * first methods (prior to setting the rootPane!). So back out * */ if (content != null) { content.setLocale(l); updateLocaleState(l); } super.setLocale(l); } /** * Updates this dialog's locale-dependent state. * * Here: updates title and actions. * <p> * * * @see #setLocale(Locale) */ protected void updateLocaleState(Locale locale) { setTitleFromContent(); for (Object key : getRootPane().getActionMap().allKeys()) { if (key instanceof String) { Action contentAction = content.getActionMap().get(key); Action rootPaneAction = getAction(key); if ((!rootPaneAction.equals(contentAction))) { String keyString = getUIString((String) key, locale); if (!key.equals(keyString)) { rootPaneAction.putValue(Action.NAME, keyString); } } } } } /** * The callback method executed when closing the dialog. <p> * Here: calls dispose. * */ public void doClose() { dispose(); } private void initActions() { Action defaultAction = createCloseAction(); putAction(CLOSE_ACTION_COMMAND, defaultAction); putAction(EXECUTE_ACTION_COMMAND, defaultAction); } private Action createCloseAction() { String actionName = getUIString(CLOSE_ACTION_COMMAND); BoundAction action = new BoundAction(actionName, CLOSE_ACTION_COMMAND); action.registerCallback(this, "doClose"); return action; } /** * create the dialog button controls. * * * @return panel containing button controls */ protected JComponent createButtonPanel() { // PENDING: this is a hack until we have a dedicated ButtonPanel! JPanel panel = new JPanel(new BasicOptionPaneUI.ButtonAreaLayout(true, 6)) { @Override public Dimension getMaximumSize() { return getPreferredSize(); } }; panel.setBorder(BorderFactory.createEmptyBorder(9, 0, 0, 0)); Action executeAction = getAction(EXECUTE_ACTION_COMMAND); Action closeAction = getAction(CLOSE_ACTION_COMMAND); JButton defaultButton = new JButton(executeAction); panel.add(defaultButton); getRootPane().setDefaultButton(defaultButton); if (executeAction != closeAction) { JButton b = new JButton(closeAction); panel.add(b); getRootPane().setCancelButton(b); } return panel; } /** * convenience wrapper to access rootPane's actionMap. * @param key * @param action */ private void putAction(Object key, Action action) { getRootPane().getActionMap().put(key, action); } /** * convenience wrapper to access rootPane's actionMap. * * @param key * @return root pane's <code>ActionMap</code> */ private Action getAction(Object key) { return getRootPane().getActionMap().get(key); } /** * Returns a potentially localized value from the UIManager. The given key * is prefixed by this component|s <code>UIPREFIX</code> before doing the * lookup. The lookup respects this table's current <code>locale</code> * property. Returns the key, if no value is found. * * @param key the bare key to look up in the UIManager. * @return the value mapped to UIPREFIX + key or key if no value is found. */ protected String getUIString(String key) { return getUIString(key, getLocale()); } /** * Returns a potentially localized value from the UIManager for the * given locale. The given key * is prefixed by this component's <code>UIPREFIX</code> before doing the * lookup. Returns the key, if no value is found. * * @param key the bare key to look up in the UIManager. * @param locale the locale use for lookup * @return the value mapped to UIPREFIX + key in the given locale, * or key if no value is found. */ protected String getUIString(String key, Locale locale) { String text = UIManagerExt.getString(UIPREFIX + key, locale); return text != null ? text : key; } }