Source code: edu/emory/mathcs/util/swing/JDetailedMessageBox.java
1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
8 *
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
13 *
14 * The Original Code is the Emory Utilities.
15 *
16 * The Initial Developer of the Original Code is
17 * The Distributed Computing Laboratory, Emory University.
18 * Portions created by the Initial Developer are Copyright (C) 2002
19 * the Initial Developer. All Rights Reserved.
20 *
21 * Alternatively, the contents of this file may be used under the terms of
22 * either the GNU General Public License Version 2 or later (the "GPL"), or
23 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
24 * in which case the provisions of the GPL or the LGPL are applicable instead
25 * of those above. If you wish to allow use of your version of this file only
26 * under the terms of either the GPL or the LGPL, and not to allow others to
27 * use your version of this file under the terms of the MPL, indicate your
28 * decision by deleting the provisions above and replace them with the notice
29 * and other provisions required by the GPL or the LGPL. If you do not delete
30 * the provisions above, a recipient may use your version of this file under
31 * the terms of any one of the MPL, the GPL or the LGPL.
32 *
33 * ***** END LICENSE BLOCK ***** */
34
35 package edu.emory.mathcs.util.swing;
36
37 import java.util.*;
38
39 import java.awt.*;
40 import java.awt.event.*;
41 import javax.swing.*;
42
43 /**
44 * Message box with the expandable detail pane at the bottom of the window.
45 * Allows users to show/hide message details that may be represented
46 * by any Swing component.
47 * Construction and usage patterns are similar to that of {@link JOptionPane}
48 * except that the "details" object is additionally required. If this
49 * object is a JComponent, it is used as is;
50 * otherwise, it is converted to string and displayed in a text box.
51 *
52 * @see JOptionPane for detailed documentation
53 *
54 * @author Dawid Kurzyniec
55 * @version 1.0
56 */
57
58 public class JDetailedMessageBox extends JDialog {
59
60 static Color BACKGROUND_COLOR = UIManager.getColor("OptionPane.background");
61 static Font MESSAGE_FONT = UIManager.getFont("OptionPane.messageFont");
62 static Font BUTTON_FONT = UIManager.getFont("OptionPane.font");
63 static Font LABEL_FONT = UIManager.getFont("Label.font");
64
65 JPanel detailPane;
66 JButton detailBtn;
67 JButton initialFocus;
68 int selectedBtn = -1;
69 int defaultBtn;
70 DetailButtonListener dblistener = new DetailButtonListener();
71
72 JDetailedMessageBox(Dialog owner, String title) {
73 super(owner, title, true);
74 }
75
76 JDetailedMessageBox(Frame owner, String title) {
77 super(owner, title, true);
78 }
79
80 public static JDetailedMessageBox newInstance(
81 Component parentComponent, String title,
82 Object message, int messageType, int optionType,
83 Object details)
84 {
85 return newInstance(parentComponent, title, message, messageType,
86 optionType, iconForMessageType(messageType), details);
87 }
88
89 public static JDetailedMessageBox newInstance(
90 Component parentComponent, String title,
91 Object message, int messageType, int optionType,
92 Icon icon, Object details)
93 {
94 return newInstance(parentComponent, title, message, messageType,
95 optionType, icon, null, details);
96 }
97
98 public static JDetailedMessageBox newInstance(
99 Component parentComponent, String title,
100 Object message, int messageType, int optionType,
101 Icon icon, Object[] options, Object details)
102 {
103 return newInstance(parentComponent, title, message, messageType,
104 optionType, icon, options, 0, details);
105 }
106
107 public static JDetailedMessageBox newInstance(
108 Component parentComponent, String title,
109 Object message, int messageType, int optionType,
110 Icon icon, Object[] options, int initialIndex, Object details)
111 {
112 return newInstance(parentComponent, title, message, messageType,
113 optionType, (Object)icon, options, initialIndex,
114 details);
115 }
116
117 public static JDetailedMessageBox newInstance(
118 Component parentComponent, String title,
119 Object message, int messageType, int optionType,
120 Object icon, Object[] options, int initialIndex, Object details)
121 {
122 final JDetailedMessageBox dialog;
123
124 Window window = getWindowForComponent(parentComponent);
125 if (window instanceof Frame) {
126 dialog = new JDetailedMessageBox((Frame)window, title);
127 } else {
128 dialog = new JDetailedMessageBox((Dialog)window, title);
129 }
130
131 Container contentPane = dialog.getContentPane();
132 contentPane.setLayout(new GridBagLayout());
133 //JPanel iconPane = new JPanel();
134
135 Component iconComponent;
136 if (icon == null) {
137 iconComponent = null;
138 }
139 else if (icon instanceof Component) {
140 iconComponent = (Component)icon;
141 }
142 else {
143 JLabel iconLabel;
144 if (icon instanceof Icon) {
145 iconLabel = new JLabel((Icon)icon);
146 } else {
147 iconLabel = new JLabel(icon.toString());
148 }
149 iconLabel.setBackground(BACKGROUND_COLOR);
150 iconComponent = iconLabel;
151 }
152
153 if (iconComponent != null) {
154 contentPane.add(iconComponent, new GridBagConstraints(0, 0, 1, 1, 0.0, 0.0
155 ,GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(6, 6, 5, 5), 0, 0));
156 }
157
158 //JPanel auxMsgPane = new JPanel();
159 JPanel msgPane = new JPanel();
160 contentPane.add(msgPane, new GridBagConstraints(1, 0, 1, 1, 1.0, 0.001
161 ,GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(6, 6, 5, 5), 0, 0));
162 //auxMsgPane.setLayout(new BorderLayout());
163 //auxMsgPane.add(msgPane, BorderLayout.NORTH);
164 JPanel auxBtnPane = new JPanel();
165 JPanel btnPane = new JPanel();
166 contentPane.add(auxBtnPane, new GridBagConstraints(2, 0, 1, 1, 0.0, 0.0
167 ,GridBagConstraints.NORTHEAST, GridBagConstraints.VERTICAL, new Insets(6, 6, 5, 5), 0, 0));
168 auxBtnPane.setLayout(new BorderLayout());
169 auxBtnPane.add(btnPane, BorderLayout.NORTH);
170
171 // buttons
172
173 btnPane.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0));
174 GridLayout btnLayout = new GridLayout(0, 1, 5, 5);
175 btnPane.setLayout(btnLayout);
176 if (options == null) {
177 options = buttonsFromOptionType(optionType, dialog.getLocale());
178 }
179 for (int i = 0; i < options.length; i++) {
180 Object btnObj = options[i];
181 JButton jbutton;
182 if (btnObj instanceof JButton) {
183 jbutton = (JButton)btnObj;
184 } else {
185 jbutton = new JButton(btnObj.toString());
186 }
187 if (i == initialIndex) {
188 dialog.getRootPane().setDefaultButton(jbutton);
189 dialog.initialFocus = jbutton;
190 }
191 configureButton(jbutton);
192 jbutton.addActionListener(dialog.new ButtonActionListener(i));
193 btnPane.add(jbutton);
194 }
195
196 // message
197
198 msgPane.setLayout(new BorderLayout());
199 if (message instanceof Component) {
200 msgPane.add((Component)message);
201 } else {
202 String msg = message.toString();
203 JTextArea ta = new JTextArea(msg);
204 Font font = getMessageFont(dialog);
205 if (font != null) ta.setFont(font);
206 ta.setEditable(false);
207 ta.setBackground(BACKGROUND_COLOR);
208 // check preferred witdh before word wrapping
209 // try to determine best dimensions for message pane, considering
210 // number of buttons and the amount of text to display
211 Dimension tapref = ta.getPreferredSize();
212 int width = (int)tapref.getWidth();
213 int height = (int)tapref.getHeight();
214 int preflines = (int)(btnPane.getPreferredSize().getHeight()/height);
215 if (preflines == 0) preflines = 1;
216 if (width > 250) {
217 width = width / preflines;
218 if (width < 250) {
219 width = 250;
220 }
221 else if (width > 500) {
222 width = 500;
223 }
224 }
225
226 // hint for the view to correctly compute preferred height
227 // (otherwise, height does not reflect wrapped lines)
228 ta.setSize(width, Integer.MAX_VALUE);
229 ta.setLineWrap(true);
230 ta.setWrapStyleWord(true);
231 msgPane.add(ta, BorderLayout.CENTER);
232 ta.invalidate();
233 }
234
235
236 // "details" button
237
238 JButton dbtn = new JButton();
239 configureButton(dbtn);
240 auxBtnPane.add(dbtn, BorderLayout.SOUTH);
241 dialog.detailBtn = dbtn;
242 dbtn.addActionListener(dialog.dblistener);
243
244 // detail pane
245
246 JPanel detailPane = new JPanel();
247
248 //detailPane.setPreferredSize(new Dimension(0, 150));
249 detailPane.setLayout(new BorderLayout());
250 if (details instanceof Component) {
251 detailPane.add((Component)details, BorderLayout.CENTER);
252 } else {
253 JScrollPane scrollPane = new JScrollPane();
254 detailPane.add(scrollPane, BorderLayout.CENTER);
255 JTextArea dtt = new JTextArea(details.toString());
256 dtt.setEditable(false);
257 dtt.setTabSize(4);
258 scrollPane.setViewport(new DetailViewport());
259 scrollPane.setViewportView(dtt);
260 }
261
262 Dimension dsize = detailPane.getSize();
263 dsize.height = 150;
264 detailPane.setSize(dsize);
265 detailPane.invalidate();
266
267 dialog.detailPane = detailPane;
268 contentPane.add(detailPane, new GridBagConstraints(0, 1, 3, 1, 1.0, 1.0
269 ,GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(6, 6, 5, 5), 0, 0));
270 dialog.dblistener.update(false);
271
272 if (JDialog.isDefaultLookAndFeelDecorated()) {
273 int style = styleFromMessageType(messageType);
274 boolean supportsWindowDecorations =
275 UIManager.getLookAndFeel().getSupportsWindowDecorations();
276 if (supportsWindowDecorations) {
277 dialog.setUndecorated(true);
278 dialog.getRootPane().setWindowDecorationStyle(style);
279 }
280 }
281
282 //dialog.setResizable(false);
283 dialog.pack();
284
285 dialog.setLocationRelativeTo(parentComponent);
286 dialog.addWindowListener(new WindowAdapter() {
287 private boolean gotFocus = false;
288 public void windowClosing(WindowEvent we) {
289 dialog.selectedBtn = -1;
290 }
291 public void windowGainedFocus(WindowEvent we) {
292 // Once window gets focus, set initial focus
293 if (!gotFocus) {
294 dialog.selectedBtn = dialog.defaultBtn;
295 gotFocus = true;
296 }
297 }
298 });
299 dialog.addComponentListener(new ComponentAdapter() {
300 public void componentShown(ComponentEvent ce) {
301 // reset value to ensure closing works properly
302 dialog.selectedBtn = -1;
303 }
304 });
305 return dialog;
306 }
307
308 static Window getWindowForComponent(Component parentComponent)
309 throws HeadlessException {
310 if (parentComponent == null)
311 return JOptionPane.getRootFrame();
312 if (parentComponent instanceof Frame || parentComponent instanceof Dialog)
313 return (Window)parentComponent;
314 return JDetailedMessageBox.getWindowForComponent(parentComponent.getParent());
315 }
316
317 public void setInitialFocus() {
318 if (initialFocus != null) {
319 initialFocus.requestFocus();
320 }
321 }
322
323 public int getValue() {
324 return selectedBtn;
325 }
326
327 private static int styleFromMessageType(int messageType) {
328 switch (messageType) {
329 case JOptionPane.ERROR_MESSAGE:
330 return JRootPane.ERROR_DIALOG;
331 case JOptionPane.QUESTION_MESSAGE:
332 return JRootPane.QUESTION_DIALOG;
333 case JOptionPane.WARNING_MESSAGE:
334 return JRootPane.WARNING_DIALOG;
335 case JOptionPane.INFORMATION_MESSAGE:
336 return JRootPane.INFORMATION_DIALOG;
337 case JOptionPane.PLAIN_MESSAGE:
338 default:
339 return JRootPane.PLAIN_DIALOG;
340 }
341 }
342
343 private static JButton[] buttonsFromOptionType(int optionType, Locale l) {
344 JButton[] options;
345 if (optionType == JOptionPane.YES_NO_OPTION) {
346 options = new JButton[2];
347 options[0] = new JButton(UIManager.get("OptionPane.yesButtonText",l).toString());
348 options[0].setMnemonic(getMnemonic("OptionPane.yesButtonMnemonic",l));
349 options[1] = new JButton(UIManager.get("OptionPane.noButtonText",l).toString());
350 options[1].setMnemonic(getMnemonic("OptionPane.noButtonMnemonic", l));
351 }
352 else if (optionType == JOptionPane.YES_NO_CANCEL_OPTION) {
353 options = new JButton[3];
354 options[0] = new JButton(UIManager.get("OptionPane.yesButtonText",l).toString());
355 options[0].setMnemonic(getMnemonic("OptionPane.yesButtonMnemonic",l));
356 options[1] = new JButton(UIManager.get("OptionPane.noButtonText",l).toString());
357 options[1].setMnemonic(getMnemonic("OptionPane.noButtonMnemonic", l));
358 options[2] = new JButton(UIManager.get("OptionPane.cancelButtonText",l).toString());
359 options[2].setMnemonic(getMnemonic("OptionPane.cancelButtonMnemonic", l));
360 }
361 else if (optionType == JOptionPane.OK_CANCEL_OPTION) {
362 options = new JButton[2];
363 options[0] = new JButton(UIManager.get("OptionPane.okButtonText",l).toString());
364 options[0].setMnemonic(getMnemonic("OptionPane.okButtonMnemonic",l));
365 options[1] = new JButton(UIManager.get("OptionPane.cancelButtonText",l).toString());
366 options[1].setMnemonic(getMnemonic("OptionPane.cancelButtonMnemonic", l));
367 }
368 else {
369 options = new JButton[1];
370 options[0] = new JButton(UIManager.get("OptionPane.okButtonText",l).toString());
371 options[0].setMnemonic(getMnemonic("OptionPane.okButtonMnemonic",l));
372 }
373 return options;
374 }
375
376 private static Icon iconForMessageType(int messageType) {
377 if(messageType < 0 || messageType > 3)
378 return null;
379 switch(messageType) {
380 case 0:
381 return UIManager.getIcon("OptionPane.errorIcon");
382 case 1:
383 return UIManager.getIcon("OptionPane.informationIcon");
384 case 2:
385 return UIManager.getIcon("OptionPane.warningIcon");
386 case 3:
387 return UIManager.getIcon("OptionPane.questionIcon");
388 }
389 return null;
390 }
391
392 private static int getMnemonic(String key, Locale l) {
393 String value = (String)UIManager.get(key, l);
394
395 if (value == null) {
396 return 0;
397 }
398 try {
399 return Integer.parseInt(value);
400 }
401 catch (NumberFormatException nfe) { }
402 return 0;
403 }
404
405 private class DetailButtonListener implements ActionListener {
406 boolean shown = false;
407
408 public void actionPerformed(ActionEvent evt) {
409 update(!shown);
410 }
411
412 void update(boolean show) {
413 String label = show ? ">> Details" : "Details >>";
414 detailBtn.setText(label);
415 detailPane.setVisible(show);
416 if (show == shown) return;
417 this.shown = show;
418 pack();
419 }
420 }
421
422 protected class ButtonActionListener implements ActionListener {
423 int buttonIndex;
424 public ButtonActionListener(int buttonIndex) {
425 this.buttonIndex = buttonIndex;
426 }
427 public void actionPerformed(ActionEvent e) {
428 selectedBtn = buttonIndex;
429 setVisible(false);
430 }
431 }
432
433 private static void configureButton(JButton btn) {
434 if (BUTTON_FONT != null && btn.getFont() == null) {
435 btn.setFont(BUTTON_FONT);
436 }
437 }
438
439 private static Font getMessageFont(Component owner) {
440 Font font = MESSAGE_FONT;
441 if (font == null) font = LABEL_FONT;
442 if (font == null) font = owner.getFont();
443 return font;
444 }
445
446 private static Locale getLocaleForComponent(Component c) {
447 return (c == null) ? Locale.getDefault() : c.getLocale();
448 }
449
450 public static void showMessageDialog(Component parentComponent,
451 Object message, Object details)
452 {
453 showMessageDialog(parentComponent, message, UIManager.getString(
454 "OptionPane.messageDialogTitle",
455 getLocaleForComponent(parentComponent)),
456 JOptionPane.INFORMATION_MESSAGE, details);
457 }
458
459 public static void showMessageDialog(Component parentComponent,
460 Object message, String title, int messageType, Object details)
461 {
462 showMessageDialog(parentComponent, message, title, messageType,
463 iconForMessageType(messageType), details);
464 }
465
466 public static void showMessageDialog(Component parentComponent,
467 Object message, String title, int messageType, Icon icon, Object details)
468 {
469 showOptionDialog(parentComponent, message, title, JOptionPane.DEFAULT_OPTION,
470 messageType, icon, null, details);
471 }
472
473 public static int showConfirmDialog(Component parentComponent,
474 Object message, Object details)
475 {
476 return showConfirmDialog(parentComponent, message,
477 UIManager.getString("OptionPane.titleText"),
478 JOptionPane.YES_NO_CANCEL_OPTION, details);
479 }
480
481 public static int showConfirmDialog(Component parentComponent,
482 Object message, String title, int optionType, Object details)
483 {
484 return showConfirmDialog(parentComponent, message, title, optionType,
485 JOptionPane.QUESTION_MESSAGE, details);
486 }
487
488 public static int showConfirmDialog(Component parentComponent,
489 Object message, String title, int optionType, int messageType,
490 Object details)
491 {
492 return showConfirmDialog(parentComponent, message, title, optionType,
493 messageType, iconForMessageType(messageType),
494 details);
495 }
496
497 public static int showConfirmDialog(Component parentComponent,
498 Object message, String title, int optionType,
499 int messageType, Icon icon, Object details)
500 {
501 return showOptionDialog(parentComponent, message, title, optionType,
502 messageType, icon, null, details);
503 }
504
505 public static int showOptionDialog(Component parentComponent,
506 Object message, String title, int optionType, int messageType,
507 Object[] options, Object details)
508 {
509 return showOptionDialog(parentComponent, message, title, optionType,
510 messageType, iconForMessageType(messageType),
511 options, details);
512 }
513
514 public static int showOptionDialog(Component parentComponent,
515 Object message, String title, int optionType, int messageType,
516 Icon icon, Object[] options, Object details)
517 {
518 JDetailedMessageBox mbox = newInstance(parentComponent, title, message,
519 messageType, optionType, icon,
520 options, details);
521
522 mbox.setInitialFocus();
523 mbox.show();
524 mbox.dispose();
525
526 int selectedValue = mbox.getValue();
527 return selectedValue;
528 }
529
530 private static class DetailViewport extends JViewport {
531 public Dimension getPreferredSize() {
532 Dimension d = super.getPreferredSize();
533 d.height = 150;
534 return d;
535 }
536 }
537 }