001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.openide;
042:
043: import org.openide.util.Lookup;
044: import org.openide.util.Utilities;
045:
046: import java.awt.*;
047: import java.awt.event.ActionEvent;
048: import java.awt.event.ActionListener;
049: import java.awt.event.KeyEvent;
050: import java.awt.event.WindowAdapter;
051: import java.awt.event.WindowEvent;
052:
053: import java.beans.PropertyChangeEvent;
054: import java.beans.PropertyChangeListener;
055:
056: import java.util.Arrays;
057: import java.util.HashSet;
058: import java.util.Set;
059:
060: import javax.swing.*;
061:
062: /** Permits dialogs to be displayed.
063: * @author Jesse Glick
064: * @since 3.14
065: */
066: public abstract class DialogDisplayer {
067: /** Subclass constructor. */
068: protected DialogDisplayer() {
069: }
070:
071: /** Get the default dialog displayer.
072: * @return the default instance from lookup
073: */
074: public static DialogDisplayer getDefault() {
075: DialogDisplayer dd = (DialogDisplayer) Lookup.getDefault()
076: .lookup(DialogDisplayer.class);
077:
078: if (dd == null) {
079: dd = new Trivial();
080: }
081:
082: return dd;
083: }
084:
085: /** Notify the user of something in a message box, possibly with feedback.
086: * <p>To support both GUI and non-GUI use, this method may be called
087: * from any thread (providing you are not holding any locks), and
088: * will block the caller's thread. In GUI mode, it will be run in the AWT
089: * event thread automatically. If you wish to hold locks, or do not
090: * need the result object immediately or at all, please make this call
091: * asynchronously (e.g. from the request processor).
092: * @param descriptor description of the notification
093: * @return the option that caused the message box to be closed
094: */
095: public abstract Object notify(NotifyDescriptor descriptor);
096:
097: /** Notify the user of something in a message box, possibly with feedback,
098: * this method method may be called
099: * from any thread. The thread will return immediately and
100: * the dialog will be shown <q>later</q>, usually when AWT thread
101: * is empty and can handle the request.
102: *
103: * <p class="non-normative">
104: * Implementation note: Since version 7.3, implementation improved to work
105: * also before main window is opened. For example: When method is called
106: * from ModuleInstall.restored, then modal dialog is opened and blocks main
107: * window until dialog is closed. Typical use case is login dialog.
108: * </p>
109: *
110: * @param descriptor description of the notification
111: * @since 7.0
112: */
113: public void notifyLater(final NotifyDescriptor descriptor) {
114: SwingUtilities.invokeLater(new Runnable() {
115: public void run() {
116: DialogDisplayer.this .notify(descriptor);
117: }
118: });
119: }
120:
121: /** Get a new standard dialog.
122: * The dialog is designed and created as specified in the parameter.
123: * Anyone who wants a dialog with standard buttons and
124: * standard behavior should use this method.
125: * <p><strong>Do not cache</strong> the resulting dialog if it
126: * is modal and try to reuse it! Always create a new dialog
127: * using this method if you need to show a dialog again.
128: * Otherwise previously closed windows can reappear.
129: * @param descriptor general description of the dialog
130: * @return the new dialog
131: */
132: public abstract Dialog createDialog(DialogDescriptor descriptor);
133:
134: /**
135: * Minimal implementation suited for standalone usage.
136: * @see "#30031"
137: */
138: private static final class Trivial extends DialogDisplayer {
139: public Object notify(NotifyDescriptor nd) {
140: JDialog dialog = new StandardDialog(nd.getTitle(), true,
141: nd, null, null);
142: dialog.setVisible(true);
143:
144: return (nd.getValue() != null) ? nd.getValue()
145: : NotifyDescriptor.CLOSED_OPTION;
146: }
147:
148: public Dialog createDialog(final DialogDescriptor dd) {
149: final StandardDialog dialog = new StandardDialog(dd
150: .getTitle(), dd.isModal(), dd, dd
151: .getClosingOptions(), dd.getButtonListener());
152: dd.addPropertyChangeListener(new DialogUpdater(dialog, dd));
153:
154: return dialog;
155: }
156:
157: /**
158: * Given a message object, create a displayable component from it.
159: */
160: private static Component message2Component(Object message) {
161: if (message instanceof Component) {
162: return (Component) message;
163: } else if (message instanceof Object[]) {
164: Object[] sub = (Object[]) message;
165: JPanel panel = new JPanel();
166: panel.setLayout(new FlowLayout());
167:
168: for (int i = 0; i < sub.length; i++) {
169: panel.add(message2Component(sub[i]));
170: }
171:
172: return panel;
173: } else if (message instanceof Icon) {
174: return new JLabel((Icon) message);
175: } else {
176: // bugfix #35742, used JTextArea to correctly word-wrapping
177: String text = message.toString();
178: JTextArea area = new JTextArea(text);
179: Color c = UIManager.getColor("Label.background"); // NOI18N
180:
181: if (c != null) {
182: area.setBackground(c);
183: }
184:
185: area.setLineWrap(true);
186: area.setWrapStyleWord(true);
187: area.setEditable(false);
188: area.setTabSize(4); // looks better for module sys messages than 8
189:
190: area.setColumns(40);
191:
192: if (text.indexOf('\n') != -1) {
193: // Complex multiline message.
194: return new JScrollPane(area);
195: } else {
196: // Simple message.
197: return area;
198: }
199: }
200: }
201:
202: private static Component option2Button(Object option,
203: NotifyDescriptor nd, ActionListener l, JRootPane rp) {
204: if (option instanceof AbstractButton) {
205: AbstractButton b = (AbstractButton) option;
206: b.addActionListener(l);
207:
208: return b;
209: } else if (option instanceof Component) {
210: return (Component) option;
211: } else if (option instanceof Icon) {
212: return new JLabel((Icon) option);
213: } else {
214: String text;
215: boolean defcap;
216:
217: if (option == NotifyDescriptor.OK_OPTION) {
218: text = "OK"; // XXX I18N
219: defcap = true;
220: } else if (option == NotifyDescriptor.CANCEL_OPTION) {
221: text = "Cancel"; // XXX I18N
222: defcap = false;
223: } else if (option == NotifyDescriptor.YES_OPTION) {
224: text = "Yes"; // XXX I18N
225: defcap = true;
226: } else if (option == NotifyDescriptor.NO_OPTION) {
227: text = "No"; // XXX I18N
228: defcap = false;
229: } else if (option == NotifyDescriptor.CLOSED_OPTION) {
230: throw new IllegalArgumentException();
231: } else {
232: text = option.toString();
233: defcap = false;
234: }
235:
236: JButton b = new JButton(text);
237:
238: if (defcap && (rp.getDefaultButton() == null)) {
239: rp.setDefaultButton(b);
240: }
241:
242: // added a simple accessible name to buttons
243: b.getAccessibleContext().setAccessibleName(text);
244: b.addActionListener(l);
245:
246: return b;
247: }
248: }
249:
250: private static final class StandardDialog extends JDialog {
251: final NotifyDescriptor nd;
252: private Component messageComponent;
253: private final JPanel buttonPanel;
254: private final Object[] closingOptions;
255: private final ActionListener buttonListener;
256: private boolean haveFinalValue = false;
257:
258: public StandardDialog(String title, boolean modal,
259: NotifyDescriptor nd, Object[] closingOptions,
260: ActionListener buttonListener) {
261: super ((Frame) null, title, modal);
262: this .nd = nd;
263: this .closingOptions = closingOptions;
264: this .buttonListener = buttonListener;
265: getContentPane().setLayout(new BorderLayout());
266: setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
267: updateMessage();
268: buttonPanel = new JPanel();
269: buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));
270: updateOptions();
271: getContentPane()
272: .add(buttonPanel, BorderLayout.SOUTH, 1);
273:
274: KeyStroke k = KeyStroke.getKeyStroke(
275: KeyEvent.VK_ESCAPE, 0);
276: Object actionKey = "cancel"; // NOI18N
277: getRootPane().getInputMap(
278: JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
279: .put(k, actionKey);
280:
281: Action cancelAction = new AbstractAction() {
282: public void actionPerformed(ActionEvent ev) {
283: cancel();
284: }
285: };
286:
287: getRootPane().getActionMap().put(actionKey,
288: cancelAction);
289: addWindowListener(new WindowAdapter() {
290: public void windowClosing(WindowEvent ev) {
291: if (!haveFinalValue) {
292: StandardDialog.this .nd
293: .setValue(NotifyDescriptor.CLOSED_OPTION);
294: }
295: }
296: });
297: pack();
298:
299: Rectangle r = Utilities.getUsableScreenBounds();
300: int maxW = (r.width * 9) / 10;
301: int maxH = (r.height * 9) / 10;
302: Dimension d = getPreferredSize();
303: d.width = Math.min(d.width, maxW);
304: d.height = Math.min(d.height, maxH);
305: setBounds(Utilities.findCenterBounds(d));
306: }
307:
308: private void cancel() {
309: nd.setValue(NotifyDescriptor.CANCEL_OPTION);
310: haveFinalValue = true;
311: dispose();
312: }
313:
314: public void updateMessage() {
315: if (messageComponent != null) {
316: getContentPane().remove(messageComponent);
317: }
318:
319: //System.err.println("updateMessage: " + nd.getMessage());
320: messageComponent = message2Component(nd.getMessage());
321: getContentPane().add(messageComponent,
322: BorderLayout.CENTER);
323: }
324:
325: public void updateOptions() {
326: Set<Object> addedOptions = new HashSet<Object>(5);
327: Object[] options = nd.getOptions();
328:
329: if (options == null) {
330: switch (nd.getOptionType()) {
331: case NotifyDescriptor.DEFAULT_OPTION:
332: case NotifyDescriptor.OK_CANCEL_OPTION:
333: options = new Object[] {
334: NotifyDescriptor.OK_OPTION,
335: NotifyDescriptor.CANCEL_OPTION, };
336:
337: break;
338:
339: case NotifyDescriptor.YES_NO_OPTION:
340: options = new Object[] {
341: NotifyDescriptor.YES_OPTION,
342: NotifyDescriptor.NO_OPTION, };
343:
344: break;
345:
346: case NotifyDescriptor.YES_NO_CANCEL_OPTION:
347: options = new Object[] {
348: NotifyDescriptor.YES_OPTION,
349: NotifyDescriptor.NO_OPTION,
350: NotifyDescriptor.CANCEL_OPTION, };
351:
352: break;
353:
354: default:
355: throw new IllegalArgumentException();
356: }
357: }
358:
359: //System.err.println("prep: " + Arrays.asList(options) + " " + Arrays.asList(closingOptions) + " " + buttonListener);
360: buttonPanel.removeAll();
361:
362: JRootPane rp = getRootPane();
363:
364: for (int i = 0; i < options.length; i++) {
365: addedOptions.add(options[i]);
366: buttonPanel.add(option2Button(options[i], nd,
367: makeListener(options[i]), rp));
368: }
369:
370: options = nd.getAdditionalOptions();
371:
372: if (options != null) {
373: for (int i = 0; i < options.length; i++) {
374: addedOptions.add(options[i]);
375: buttonPanel.add(option2Button(options[i], nd,
376: makeListener(options[i]), rp));
377: }
378: }
379:
380: if (closingOptions != null) {
381: for (int i = 0; i < closingOptions.length; i++) {
382: if (addedOptions.add(closingOptions[i])) {
383: ActionListener l = makeListener(closingOptions[i]);
384: attachActionListener(closingOptions[i], l);
385: }
386: }
387: }
388: }
389:
390: private void attachActionListener(Object comp,
391: ActionListener l) {
392: // on JButtons attach simply by method call
393: if (comp instanceof JButton) {
394: JButton b = (JButton) comp;
395: b.addActionListener(l);
396:
397: return;
398: } else {
399: // we will have to use dynamic method invocation to add the action listener
400: // to generic component (and we succeed only if it has the addActionListener method)
401: java.lang.reflect.Method m;
402:
403: try {
404: m = comp.getClass().getMethod(
405: "addActionListener",
406: new Class[] { ActionListener.class }); // NOI18N
407:
408: try {
409: m.setAccessible(true);
410: } catch (SecurityException se) {
411: m = null; // no jo, we cannot make accessible
412: }
413: } catch (NoSuchMethodException e) {
414: m = null; // no jo, we cannot attach ActionListener to this Component
415: } catch (SecurityException e2) {
416: m = null; // no jo, we cannot attach ActionListener to this Component
417: }
418:
419: if (m != null) {
420: try {
421: m.invoke(comp, new Object[] { l });
422: } catch (Exception e) {
423: // not succeeded, so give up
424: }
425: }
426: }
427: }
428:
429: private ActionListener makeListener(final Object option) {
430: return new ActionListener() {
431: public void actionPerformed(ActionEvent e) {
432: //System.err.println("actionPerformed: " + option);
433: nd.setValue(option);
434:
435: if (buttonListener != null) {
436: // #34485: some listeners expect that the action source is the option, not the button
437: ActionEvent e2 = new ActionEvent(option, e
438: .getID(), e.getActionCommand(), e
439: .getWhen(), e.getModifiers());
440: buttonListener.actionPerformed(e2);
441: }
442:
443: if ((closingOptions == null)
444: || Arrays.asList(closingOptions)
445: .contains(option)) {
446: haveFinalValue = true;
447: setVisible(false);
448: }
449: }
450: };
451: }
452: }
453:
454: private static class DialogUpdater implements
455: PropertyChangeListener {
456:
457: private StandardDialog dialog;
458:
459: private DialogDescriptor dd;
460:
461: public DialogUpdater(StandardDialog dialog,
462: DialogDescriptor dd) {
463: super ();
464: this .dialog = dialog;
465: this .dd = dd;
466: }
467:
468: public void propertyChange(PropertyChangeEvent ev) {
469: String pname = ev.getPropertyName();
470: if (NotifyDescriptor.PROP_TITLE.equals(pname)) {
471: dialog.setTitle(dd.getTitle());
472: } else if (NotifyDescriptor.PROP_MESSAGE.equals(pname)) {
473: dialog.updateMessage();
474: dialog.validate();
475: dialog.repaint();
476: } else if (NotifyDescriptor.PROP_OPTIONS.equals(pname)
477: || NotifyDescriptor.PROP_OPTION_TYPE
478: .equals(pname)) {
479: dialog.updateOptions();
480: dialog.validate();
481: dialog.repaint();
482: } else {
483: }
484: }
485: }
486:
487: }
488: }
|