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 }