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 }