001 /* BasicOptionPaneUI.java --
002 Copyright (C) 2004, 2005 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.plaf.basic;
040
041 import java.awt.BorderLayout;
042 import java.awt.Color;
043 import java.awt.Component;
044 import java.awt.Container;
045 import java.awt.Dimension;
046 import java.awt.Font;
047 import java.awt.Graphics;
048 import java.awt.GridBagConstraints;
049 import java.awt.GridBagLayout;
050 import java.awt.Insets;
051 import java.awt.LayoutManager;
052 import java.awt.Polygon;
053 import java.awt.Window;
054 import java.awt.event.ActionEvent;
055 import java.awt.event.ActionListener;
056 import java.beans.PropertyChangeEvent;
057 import java.beans.PropertyChangeListener;
058 import java.beans.PropertyVetoException;
059
060 import javax.swing.AbstractAction;
061 import javax.swing.Action;
062 import javax.swing.ActionMap;
063 import javax.swing.BorderFactory;
064 import javax.swing.Box;
065 import javax.swing.BoxLayout;
066 import javax.swing.Icon;
067 import javax.swing.InputMap;
068 import javax.swing.JButton;
069 import javax.swing.JComboBox;
070 import javax.swing.JComponent;
071 import javax.swing.JDialog;
072 import javax.swing.JInternalFrame;
073 import javax.swing.JLabel;
074 import javax.swing.JList;
075 import javax.swing.JOptionPane;
076 import javax.swing.JPanel;
077 import javax.swing.JTextField;
078 import javax.swing.LookAndFeel;
079 import javax.swing.SwingUtilities;
080 import javax.swing.UIManager;
081 import javax.swing.border.Border;
082 import javax.swing.plaf.ActionMapUIResource;
083 import javax.swing.plaf.ComponentUI;
084 import javax.swing.plaf.OptionPaneUI;
085
086 /**
087 * This class is the UI delegate for JOptionPane in the Basic Look and Feel.
088 */
089 public class BasicOptionPaneUI extends OptionPaneUI
090 {
091 /**
092 * Implements the "close" keyboard action.
093 */
094 static class OptionPaneCloseAction
095 extends AbstractAction
096 {
097
098 public void actionPerformed(ActionEvent event)
099 {
100 JOptionPane op = (JOptionPane) event.getSource();
101 op.setValue(new Integer(JOptionPane.CLOSED_OPTION));
102 }
103
104 }
105
106 /**
107 * This is a helper class that listens to the buttons located at the bottom
108 * of the JOptionPane.
109 *
110 * @specnote Apparently this class was intended to be protected,
111 * but was made public by a compiler bug and is now
112 * public for compatibility.
113 */
114 public class ButtonActionListener implements ActionListener
115 {
116 /** The index of the option this button represents. */
117 protected int buttonIndex;
118
119 /**
120 * Creates a new ButtonActionListener object with the given buttonIndex.
121 *
122 * @param buttonIndex The index of the option this button represents.
123 */
124 public ButtonActionListener(int buttonIndex)
125 {
126 this.buttonIndex = buttonIndex;
127 }
128
129 /**
130 * This method is called when one of the option buttons are pressed.
131 *
132 * @param e The ActionEvent.
133 */
134 public void actionPerformed(ActionEvent e)
135 {
136 Object value = new Integer(JOptionPane.CLOSED_OPTION);
137 Object[] options = optionPane.getOptions();
138 if (options != null)
139 value = new Integer(buttonIndex);
140 else
141 {
142 String text = ((JButton) e.getSource()).getText();
143 if (text.equals(OK_STRING))
144 value = new Integer(JOptionPane.OK_OPTION);
145 if (text.equals(CANCEL_STRING))
146 value = new Integer(JOptionPane.CANCEL_OPTION);
147 if (text.equals(YES_STRING))
148 value = new Integer(JOptionPane.YES_OPTION);
149 if (text.equals(NO_STRING))
150 value = new Integer(JOptionPane.NO_OPTION);
151 }
152 optionPane.setValue(value);
153 resetInputValue();
154
155 Window owner = SwingUtilities.windowForComponent(optionPane);
156
157 if (owner instanceof JDialog)
158 ((JDialog) owner).dispose();
159
160 //else we probably have some kind of internal frame.
161 JInternalFrame inf = (JInternalFrame) SwingUtilities.getAncestorOfClass(
162 JInternalFrame.class, optionPane);
163 if (inf != null)
164 {
165 try
166 {
167 inf.setClosed(true);
168 }
169 catch (PropertyVetoException pve)
170 {
171 // We do nothing if attempt has been vetoed.
172 }
173 }
174 }
175 }
176
177 /**
178 * This helper layout manager is responsible for the layout of the button
179 * area. The button area is the panel that holds the buttons which
180 * represent the options.
181 *
182 * @specnote Apparently this class was intended to be protected,
183 * but was made public by a compiler bug and is now
184 * public for compatibility.
185 */
186 public static class ButtonAreaLayout implements LayoutManager
187 {
188 /** Whether this layout will center the buttons. */
189 protected boolean centersChildren = true;
190
191 /** The space between the buttons. */
192 protected int padding;
193
194 /** Whether the buttons will share the same widths. */
195 protected boolean syncAllWidths;
196
197 /** The width of the widest button. */
198 private transient int widthOfWidestButton;
199
200 /** The height of the tallest button. */
201 private transient int tallestButton;
202
203 /**
204 * Creates a new ButtonAreaLayout object with the given sync widths
205 * property and padding.
206 *
207 * @param syncAllWidths Whether the buttons will share the same widths.
208 * @param padding The padding between the buttons.
209 */
210 public ButtonAreaLayout(boolean syncAllWidths, int padding)
211 {
212 this.syncAllWidths = syncAllWidths;
213 this.padding = padding;
214 }
215
216 /**
217 * This method is called when a component is added to the container.
218 *
219 * @param string The constraints string.
220 * @param comp The component added.
221 */
222 public void addLayoutComponent(String string, Component comp)
223 {
224 // Do nothing.
225 }
226
227 /**
228 * This method returns whether the children will be centered.
229 *
230 * @return Whether the children will be centered.
231 */
232 public boolean getCentersChildren()
233 {
234 return centersChildren;
235 }
236
237 /**
238 * This method returns the amount of space between components.
239 *
240 * @return The amount of space between components.
241 */
242 public int getPadding()
243 {
244 return padding;
245 }
246
247 /**
248 * This method returns whether all components will share widths (set to
249 * largest width).
250 *
251 * @return Whether all components will share widths.
252 */
253 public boolean getSyncAllWidths()
254 {
255 return syncAllWidths;
256 }
257
258 /**
259 * This method lays out the given container.
260 *
261 * @param container The container to lay out.
262 */
263 public void layoutContainer(Container container)
264 {
265 Component[] buttonList = container.getComponents();
266 int x = container.getInsets().left;
267 if (getCentersChildren())
268 x += (int) ((double) (container.getSize().width) / 2
269 - (double) (buttonRowLength(container)) / 2);
270 for (int i = 0; i < buttonList.length; i++)
271 {
272 Dimension dims = buttonList[i].getPreferredSize();
273 if (syncAllWidths)
274 {
275 buttonList[i].setBounds(x, 0, widthOfWidestButton, dims.height);
276 x += widthOfWidestButton + getPadding();
277 }
278 else
279 {
280 buttonList[i].setBounds(x, 0, dims.width, dims.height);
281 x += dims.width + getPadding();
282 }
283 }
284 }
285
286 /**
287 * This method returns the width of the given container taking into
288 * consideration the padding and syncAllWidths.
289 *
290 * @param c The container to calculate width for.
291 *
292 * @return The width of the given container.
293 */
294 private int buttonRowLength(Container c)
295 {
296 Component[] buttonList = c.getComponents();
297
298 int buttonLength = 0;
299 int widest = 0;
300 int tallest = 0;
301
302 for (int i = 0; i < buttonList.length; i++)
303 {
304 Dimension dims = buttonList[i].getPreferredSize();
305 buttonLength += dims.width + getPadding();
306 widest = Math.max(widest, dims.width);
307 tallest = Math.max(tallest, dims.height);
308 }
309
310 widthOfWidestButton = widest;
311 tallestButton = tallest;
312
313 int width;
314 if (getSyncAllWidths())
315 width = widest * buttonList.length
316 + getPadding() * (buttonList.length - 1);
317 else
318 width = buttonLength;
319
320 Insets insets = c.getInsets();
321 width += insets.left + insets.right;
322
323 return width;
324 }
325
326 /**
327 * This method returns the minimum layout size for the given container.
328 *
329 * @param c The container to measure.
330 *
331 * @return The minimum layout size.
332 */
333 public Dimension minimumLayoutSize(Container c)
334 {
335 return preferredLayoutSize(c);
336 }
337
338 /**
339 * This method returns the preferred size of the given container.
340 *
341 * @param c The container to measure.
342 *
343 * @return The preferred size.
344 */
345 public Dimension preferredLayoutSize(Container c)
346 {
347 int w = buttonRowLength(c);
348
349 return new Dimension(w, tallestButton);
350 }
351
352 /**
353 * This method removes the given component from the layout manager's
354 * knowledge.
355 *
356 * @param c The component to remove.
357 */
358 public void removeLayoutComponent(Component c)
359 {
360 // Do nothing.
361 }
362
363 /**
364 * This method sets whether the children will be centered.
365 *
366 * @param newValue Whether the children will be centered.
367 */
368 public void setCentersChildren(boolean newValue)
369 {
370 centersChildren = newValue;
371 }
372
373 /**
374 * This method sets the amount of space between each component.
375 *
376 * @param newPadding The padding between components.
377 */
378 public void setPadding(int newPadding)
379 {
380 padding = newPadding;
381 }
382
383 /**
384 * This method sets whether the widths will be synced.
385 *
386 * @param newValue Whether the widths will be synced.
387 */
388 public void setSyncAllWidths(boolean newValue)
389 {
390 syncAllWidths = newValue;
391 }
392 }
393
394 /**
395 * This helper class handles property change events from the JOptionPane.
396 *
397 * @specnote Apparently this class was intended to be protected,
398 * but was made public by a compiler bug and is now
399 * public for compatibility.
400 */
401 public class PropertyChangeHandler implements PropertyChangeListener
402 {
403 /**
404 * This method is called when one of the properties of the JOptionPane
405 * changes.
406 *
407 * @param e The PropertyChangeEvent.
408 */
409 public void propertyChange(PropertyChangeEvent e)
410 {
411 String property = e.getPropertyName();
412 if (property.equals(JOptionPane.ICON_PROPERTY)
413 || property.equals(JOptionPane.INITIAL_SELECTION_VALUE_PROPERTY)
414 || property.equals(JOptionPane.INITIAL_VALUE_PROPERTY)
415 || property.equals(JOptionPane.MESSAGE_PROPERTY)
416 || property.equals(JOptionPane.MESSAGE_TYPE_PROPERTY)
417 || property.equals(JOptionPane.OPTION_TYPE_PROPERTY)
418 || property.equals(JOptionPane.OPTIONS_PROPERTY)
419 || property.equals(JOptionPane.WANTS_INPUT_PROPERTY))
420 {
421 uninstallComponents();
422 installComponents();
423 optionPane.validate();
424 }
425 }
426 }
427
428 /**
429 * The minimum width for JOptionPanes.
430 */
431 public static final int MinimumWidth = 262;
432
433 /**
434 * The minimum height for JOptionPanes.
435 */
436 public static final int MinimumHeight = 90;
437
438 /** Whether the JOptionPane contains custom components. */
439 protected boolean hasCustomComponents;
440
441 // The initialFocusComponent seems to always be set to a button (even if
442 // I try to set initialSelectionValue). This is different from what the
443 // javadocs state (which should switch this reference to the input component
444 // if one is present since that is what's going to get focus).
445
446 /**
447 * The button that will receive focus based on initialValue when no input
448 * component is present. If an input component is present, then the input
449 * component will receive focus instead.
450 */
451 protected Component initialFocusComponent;
452
453 /** The component that receives input when the JOptionPane needs it. */
454 protected JComponent inputComponent;
455
456 /** The minimum dimensions of the JOptionPane. */
457 protected Dimension minimumSize;
458
459 /** The propertyChangeListener for the JOptionPane. */
460 protected PropertyChangeListener propertyChangeListener;
461
462 /** The JOptionPane this UI delegate is used for. */
463 protected JOptionPane optionPane;
464
465 /** The size of the icons. */
466 private static final int ICON_SIZE = 36;
467
468 /** The string used to describe OK buttons. */
469 private static final String OK_STRING = "OK";
470
471 /** The string used to describe Yes buttons. */
472 private static final String YES_STRING = "Yes";
473
474 /** The string used to describe No buttons. */
475 private static final String NO_STRING = "No";
476
477 /** The string used to describe Cancel buttons. */
478 private static final String CANCEL_STRING = "Cancel";
479
480 /** The container for the message area.
481 * This is package-private to avoid an accessor method. */
482 transient Container messageAreaContainer;
483
484 /** The container for the buttons.
485 * This is package-private to avoid an accessor method. */
486 transient Container buttonContainer;
487
488 /**
489 * A helper class that implements Icon. This is used temporarily until
490 * ImageIcons are fixed.
491 */
492 private static class MessageIcon implements Icon
493 {
494 /**
495 * This method returns the width of the icon.
496 *
497 * @return The width of the icon.
498 */
499 public int getIconWidth()
500 {
501 return ICON_SIZE;
502 }
503
504 /**
505 * This method returns the height of the icon.
506 *
507 * @return The height of the icon.
508 */
509 public int getIconHeight()
510 {
511 return ICON_SIZE;
512 }
513
514 /**
515 * This method paints the icon as a part of the given component using the
516 * given graphics and the given x and y position.
517 *
518 * @param c The component that owns this icon.
519 * @param g The Graphics object to paint with.
520 * @param x The x coordinate.
521 * @param y The y coordinate.
522 */
523 public void paintIcon(Component c, Graphics g, int x, int y)
524 {
525 // Nothing to do here.
526 }
527 }
528
529 /** The icon displayed for ERROR_MESSAGE. */
530 private static MessageIcon errorIcon = new MessageIcon()
531 {
532 public void paintIcon(Component c, Graphics g, int x, int y)
533 {
534 Polygon oct = new Polygon(new int[] { 0, 0, 9, 27, 36, 36, 27, 9 },
535 new int[] { 9, 27, 36, 36, 27, 9, 0, 0 }, 8);
536 g.translate(x, y);
537
538 Color saved = g.getColor();
539 g.setColor(Color.RED);
540
541 g.fillPolygon(oct);
542
543 g.setColor(Color.BLACK);
544 g.drawRect(13, 16, 10, 4);
545
546 g.setColor(saved);
547 g.translate(-x, -y);
548 }
549 };
550
551 /** The icon displayed for INFORMATION_MESSAGE. */
552 private static MessageIcon infoIcon = new MessageIcon()
553 {
554 public void paintIcon(Component c, Graphics g, int x, int y)
555 {
556 g.translate(x, y);
557 Color saved = g.getColor();
558
559 // Should be purple.
560 g.setColor(Color.RED);
561
562 g.fillOval(0, 0, ICON_SIZE, ICON_SIZE);
563
564 g.setColor(Color.BLACK);
565 g.drawOval(16, 6, 4, 4);
566
567 Polygon bottomI = new Polygon(new int[] { 15, 15, 13, 13, 23, 23, 21, 21 },
568 new int[] { 12, 28, 28, 30, 30, 28, 28, 12 },
569 8);
570 g.drawPolygon(bottomI);
571
572 g.setColor(saved);
573 g.translate(-x, -y);
574 }
575 };
576
577 /** The icon displayed for WARNING_MESSAGE. */
578 private static MessageIcon warningIcon = new MessageIcon()
579 {
580 public void paintIcon(Component c, Graphics g, int x, int y)
581 {
582 g.translate(x, y);
583 Color saved = g.getColor();
584 g.setColor(Color.YELLOW);
585
586 Polygon triangle = new Polygon(new int[] { 0, 18, 36 },
587 new int[] { 36, 0, 36 }, 3);
588 g.fillPolygon(triangle);
589
590 g.setColor(Color.BLACK);
591
592 Polygon excl = new Polygon(new int[] { 15, 16, 20, 21 },
593 new int[] { 8, 26, 26, 8 }, 4);
594 g.drawPolygon(excl);
595 g.drawOval(16, 30, 4, 4);
596
597 g.setColor(saved);
598 g.translate(-x, -y);
599 }
600 };
601
602 /** The icon displayed for MESSAGE_ICON. */
603 private static MessageIcon questionIcon = new MessageIcon()
604 {
605 public void paintIcon(Component c, Graphics g, int x, int y)
606 {
607 g.translate(x, y);
608 Color saved = g.getColor();
609 g.setColor(Color.GREEN);
610
611 g.fillRect(0, 0, ICON_SIZE, ICON_SIZE);
612
613 g.setColor(Color.BLACK);
614
615 g.drawOval(11, 2, 16, 16);
616 g.drawOval(14, 5, 10, 10);
617
618 g.setColor(Color.GREEN);
619 g.fillRect(0, 10, ICON_SIZE, ICON_SIZE - 10);
620
621 g.setColor(Color.BLACK);
622
623 g.drawLine(11, 10, 14, 10);
624
625 g.drawLine(24, 10, 17, 22);
626 g.drawLine(27, 10, 20, 22);
627 g.drawLine(17, 22, 20, 22);
628
629 g.drawOval(17, 25, 3, 3);
630
631 g.setColor(saved);
632 g.translate(-x, -y);
633 }
634 };
635
636 /**
637 * Creates a new BasicOptionPaneUI object.
638 */
639 public BasicOptionPaneUI()
640 {
641 // Nothing to do here.
642 }
643
644 /**
645 * This method is messaged to add the buttons to the given container.
646 *
647 * @param container The container to add components to.
648 * @param buttons The buttons to add. (If it is an instance of component,
649 * the Object is added directly. If it is an instance of Icon, it is
650 * packed into a label and added. For all other cases, the string
651 * representation of the Object is retreived and packed into a
652 * label.)
653 * @param initialIndex The index of the component that is the initialValue.
654 */
655 protected void addButtonComponents(Container container, Object[] buttons,
656 int initialIndex)
657 {
658 if (buttons == null)
659 return;
660 for (int i = 0; i < buttons.length; i++)
661 {
662 if (buttons[i] != null)
663 {
664 Component toAdd;
665 if (buttons[i] instanceof Component)
666 toAdd = (Component) buttons[i];
667 else
668 {
669 if (buttons[i] instanceof Icon)
670 toAdd = new JButton((Icon) buttons[i]);
671 else
672 toAdd = new JButton(buttons[i].toString());
673 hasCustomComponents = true;
674 }
675 if (toAdd instanceof JButton)
676 ((JButton) toAdd).addActionListener(createButtonActionListener(i));
677 if (i == initialIndex)
678 initialFocusComponent = toAdd;
679 container.add(toAdd);
680 }
681 }
682 selectInitialValue(optionPane);
683 }
684
685 /**
686 * This method adds the appropriate icon the given container.
687 *
688 * @param top The container to add an icon to.
689 */
690 protected void addIcon(Container top)
691 {
692 JLabel iconLabel = null;
693 Icon icon = getIcon();
694 if (icon != null)
695 {
696 iconLabel = new JLabel(icon);
697 configureLabel(iconLabel);
698 top.add(iconLabel, BorderLayout.WEST);
699 }
700 }
701
702 /**
703 * A helper method that returns an instance of GridBagConstraints to be used
704 * for creating the message area.
705 *
706 * @return An instance of GridBagConstraints.
707 */
708 private static GridBagConstraints createConstraints()
709 {
710 GridBagConstraints constraints = new GridBagConstraints();
711 constraints.gridx = GridBagConstraints.REMAINDER;
712 constraints.gridy = GridBagConstraints.REMAINDER;
713 constraints.gridwidth = 0;
714 constraints.anchor = GridBagConstraints.LINE_START;
715 constraints.fill = GridBagConstraints.NONE;
716 constraints.insets = new Insets(0, 0, 3, 0);
717
718 return constraints;
719 }
720
721 /**
722 * This method creates the proper object (if necessary) to represent msg.
723 * (If msg is an instance of Component, it will add it directly. If it is
724 * an icon, then it will pack it in a label and add it. Otherwise, it gets
725 * treated as a string. If the string is longer than maxll, a box is
726 * created and the burstStringInto is called with the box as the container.
727 * The box is then added to the given container. Otherwise, the string is
728 * packed in a label and placed in the given container.) This method is
729 * also used for adding the inputComponent to the container.
730 *
731 * @param container The container to add to.
732 * @param cons The constraints when adding.
733 * @param msg The message to add.
734 * @param maxll The max line length.
735 * @param internallyCreated Whether the msg is internally created.
736 */
737 protected void addMessageComponents(Container container,
738 GridBagConstraints cons, Object msg,
739 int maxll, boolean internallyCreated)
740 {
741 if (msg == null)
742 return;
743 hasCustomComponents = internallyCreated;
744 if (msg instanceof Object[])
745 {
746 Object[] arr = (Object[]) msg;
747 for (int i = 0; i < arr.length; i++)
748 addMessageComponents(container, cons, arr[i], maxll,
749 internallyCreated);
750 return;
751 }
752 else if (msg instanceof Component)
753 {
754 container.add((Component) msg, cons);
755 cons.gridy++;
756 }
757 else if (msg instanceof Icon)
758 {
759 JLabel label = new JLabel((Icon) msg);
760 configureLabel(label);
761 container.add(label, cons);
762 cons.gridy++;
763 }
764 else
765 {
766 // Undocumented behaviour.
767 // if msg.toString().length greater than maxll
768 // it will create a box and burst the string.
769 // otherwise, it will just create a label and re-call
770 // this method with the label o.O
771 if (msg.toString().length() > maxll || msg.toString().contains("\n"))
772 {
773 Box tmp = new Box(BoxLayout.Y_AXIS);
774 burstStringInto(tmp, msg.toString(), maxll);
775 addMessageComponents(container, cons, tmp, maxll, true);
776 }
777 else
778 {
779 JLabel label = new JLabel(msg.toString());
780 configureLabel(label);
781 addMessageComponents(container, cons, label, maxll, true);
782 }
783 }
784 }
785
786 /**
787 * This method creates instances of d (recursively if necessary based on
788 * maxll) and adds to c.
789 *
790 * @param c The container to add to.
791 * @param d The string to burst.
792 * @param maxll The max line length.
793 */
794 protected void burstStringInto(Container c, String d, int maxll)
795 {
796 if (d == null || c == null)
797 return;
798
799 int newlineIndex = d.indexOf('\n');
800 String line;
801 String remainder;
802 if (newlineIndex >= 0 && newlineIndex < maxll)
803 {
804 line = d.substring(0, newlineIndex);
805 remainder = d.substring(newlineIndex + 1);
806 }
807 else
808 {
809 line = d.substring(0, maxll);
810 remainder = d.substring(maxll);
811 }
812 JLabel label = new JLabel(line);
813 configureLabel(label);
814 c.add(label);
815
816 // If there is nothing left to burst, then we can stop.
817 if (remainder.length() == 0)
818 return;
819
820 // Recursively call ourselves to burst the remainder of the string,
821 if (remainder.length() > maxll || remainder.contains("\n"))
822 burstStringInto(c, remainder, maxll);
823 else
824 {
825 // Add the remainder to the container and be done.
826 JLabel l = new JLabel(remainder);
827 configureLabel(l);
828 c.add(l);
829 }
830 }
831
832 /**
833 * This method returns true if the given JOptionPane contains custom
834 * components.
835 *
836 * @param op The JOptionPane to check.
837 *
838 * @return True if the JOptionPane contains custom components.
839 */
840 public boolean containsCustomComponents(JOptionPane op)
841 {
842 return hasCustomComponents;
843 }
844
845 /**
846 * This method creates a button action listener for the given button index.
847 *
848 * @param buttonIndex The index of the button in components.
849 *
850 * @return A new ButtonActionListener.
851 */
852 protected ActionListener createButtonActionListener(int buttonIndex)
853 {
854 return new ButtonActionListener(buttonIndex);
855 }
856
857 /**
858 * This method creates the button area.
859 *
860 * @return A new Button Area.
861 */
862 protected Container createButtonArea()
863 {
864 JPanel buttonPanel = new JPanel();
865 Border b = UIManager.getBorder("OptionPane.buttonAreaBorder");
866 if (b != null)
867 buttonPanel.setBorder(b);
868
869 buttonPanel.setLayout(createLayoutManager());
870 addButtonComponents(buttonPanel, getButtons(), getInitialValueIndex());
871
872 return buttonPanel;
873 }
874
875 /**
876 * This method creates a new LayoutManager for the button area.
877 *
878 * @return A new LayoutManager for the button area.
879 */
880 protected LayoutManager createLayoutManager()
881 {
882 return new ButtonAreaLayout(getSizeButtonsToSameWidth(), 6);
883 }
884
885 /**
886 * This method creates the message area.
887 *
888 * @return A new message area.
889 */
890 protected Container createMessageArea()
891 {
892 JPanel messageArea = new JPanel();
893 Border messageBorder = UIManager.getBorder("OptionPane.messageAreaBorder");
894 if (messageBorder != null)
895 messageArea.setBorder(messageBorder);
896
897 messageArea.setLayout(new BorderLayout());
898 addIcon(messageArea);
899
900 JPanel rightSide = new JPanel();
901 rightSide.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
902 rightSide.setLayout(new GridBagLayout());
903 GridBagConstraints con = createConstraints();
904
905 addMessageComponents(rightSide, con, getMessage(),
906 getMaxCharactersPerLineCount(), false);
907
908 if (optionPane.getWantsInput())
909 {
910 Object[] selection = optionPane.getSelectionValues();
911
912 if (selection == null)
913 inputComponent = new JTextField(15);
914 else if (selection.length < 20)
915 inputComponent = new JComboBox(selection);
916 else
917 inputComponent = new JList(selection);
918 if (inputComponent != null)
919 {
920 addMessageComponents(rightSide, con, inputComponent,
921 getMaxCharactersPerLineCount(), false);
922 resetSelectedValue();
923 selectInitialValue(optionPane);
924 }
925 }
926
927 messageArea.add(rightSide, BorderLayout.CENTER);
928
929 return messageArea;
930 }
931
932 /**
933 * This method creates a new PropertyChangeListener for listening to the
934 * JOptionPane.
935 *
936 * @return A new PropertyChangeListener.
937 */
938 protected PropertyChangeListener createPropertyChangeListener()
939 {
940 return new PropertyChangeHandler();
941 }
942
943 /**
944 * This method creates a Container that will separate the message and button
945 * areas.
946 *
947 * @return A Container that will separate the message and button areas.
948 */
949 protected Container createSeparator()
950 {
951 // The reference implementation returns null here. When overriding
952 // to return something non-null, the component gets added between
953 // the message area and the button area. See installComponents().
954 return null;
955 }
956
957 /**
958 * This method creates a new BasicOptionPaneUI for the given component.
959 *
960 * @param x The component to create a UI for.
961 *
962 * @return A new BasicOptionPaneUI.
963 */
964 public static ComponentUI createUI(JComponent x)
965 {
966 return new BasicOptionPaneUI();
967 }
968
969 /**
970 * This method returns the buttons for the JOptionPane. If no options are
971 * set, a set of options will be created based upon the optionType.
972 *
973 * @return The buttons that will be added.
974 */
975 protected Object[] getButtons()
976 {
977 if (optionPane.getOptions() != null)
978 return optionPane.getOptions();
979 switch (optionPane.getOptionType())
980 {
981 case JOptionPane.YES_NO_OPTION:
982 return new Object[] { YES_STRING, NO_STRING };
983 case JOptionPane.YES_NO_CANCEL_OPTION:
984 return new Object[] { YES_STRING, NO_STRING, CANCEL_STRING };
985 case JOptionPane.OK_CANCEL_OPTION:
986 return new Object[] { OK_STRING, CANCEL_STRING };
987 case JOptionPane.DEFAULT_OPTION:
988 return (optionPane.getWantsInput()) ?
989 new Object[] { OK_STRING, CANCEL_STRING } :
990 (optionPane.getMessageType() == JOptionPane.QUESTION_MESSAGE) ?
991 new Object[] { YES_STRING, NO_STRING, CANCEL_STRING } :
992 // ERROR_MESSAGE, INFORMATION_MESSAGE, WARNING_MESSAGE, PLAIN_MESSAGE
993 new Object[] { OK_STRING };
994 }
995 return null;
996 }
997
998 /**
999 * This method will return the icon the user has set or the icon that will
1000 * be used based on message type.
1001 *
1002 * @return The icon to use in the JOptionPane.
1003 */
1004 protected Icon getIcon()
1005 {
1006 if (optionPane.getIcon() != null)
1007 return optionPane.getIcon();
1008 else
1009 return getIconForType(optionPane.getMessageType());
1010 }
1011
1012 /**
1013 * This method returns the icon for the given messageType.
1014 *
1015 * @param messageType The type of message.
1016 *
1017 * @return The icon for the given messageType.
1018 */
1019 protected Icon getIconForType(int messageType)
1020 {
1021 Icon tmp = null;
1022 switch (messageType)
1023 {
1024 case JOptionPane.ERROR_MESSAGE:
1025 tmp = errorIcon;
1026 break;
1027 case JOptionPane.INFORMATION_MESSAGE:
1028 tmp = infoIcon;
1029 break;
1030 case JOptionPane.WARNING_MESSAGE:
1031 tmp = warningIcon;
1032 break;
1033 case JOptionPane.QUESTION_MESSAGE:
1034 tmp = questionIcon;
1035 break;
1036 }
1037 return tmp;
1038 // FIXME: Don't cast till the default icons are in.
1039 // return new IconUIResource(tmp);
1040 }
1041
1042 /**
1043 * This method returns the index of the initialValue in the options array.
1044 *
1045 * @return The index of the initalValue.
1046 */
1047 protected int getInitialValueIndex()
1048 {
1049 Object[] buttons = getButtons();
1050
1051 if (buttons == null)
1052 return -1;
1053
1054 Object select = optionPane.getInitialValue();
1055
1056 for (int i = 0; i < buttons.length; i++)
1057 {
1058 if (select == buttons[i])
1059 return i;
1060 }
1061 return 0;
1062 }
1063
1064 /**
1065 * This method returns the maximum number of characters that should be
1066 * placed on a line.
1067 *
1068 * @return The maximum number of characteres that should be placed on a
1069 * line.
1070 */
1071 protected int getMaxCharactersPerLineCount()
1072 {
1073 return optionPane.getMaxCharactersPerLineCount();
1074 }
1075
1076 /**
1077 * This method returns the maximum size.
1078 *
1079 * @param c The JComponent to measure.
1080 *
1081 * @return The maximum size.
1082 */
1083 public Dimension getMaximumSize(JComponent c)
1084 {
1085 return getPreferredSize(c);
1086 }
1087
1088 /**
1089 * This method returns the message of the JOptionPane.
1090 *
1091 * @return The message.
1092 */
1093 protected Object getMessage()
1094 {
1095 return optionPane.getMessage();
1096 }
1097
1098 /**
1099 * This method returns the minimum size of the JOptionPane.
1100 *
1101 * @return The minimum size.
1102 */
1103 public Dimension getMinimumOptionPaneSize()
1104 {
1105 return minimumSize;
1106 }
1107
1108 /**
1109 * This method returns the minimum size.
1110 *
1111 * @param c The JComponent to measure.
1112 *
1113 * @return The minimum size.
1114 */
1115 public Dimension getMinimumSize(JComponent c)
1116 {
1117 return getPreferredSize(c);
1118 }
1119
1120 /**
1121 * This method returns the preferred size of the JOptionPane. The preferred
1122 * size is the maximum of the size desired by the layout and the minimum
1123 * size.
1124 *
1125 * @param c The JComponent to measure.
1126 *
1127 * @return The preferred size.
1128 */
1129 public Dimension getPreferredSize(JComponent c)
1130 {
1131 Dimension d = optionPane.getLayout().preferredLayoutSize(optionPane);
1132 Dimension d2 = getMinimumOptionPaneSize();
1133
1134 int w = Math.max(d.width, d2.width);
1135 int h = Math.max(d.height, d2.height);
1136 return new Dimension(w, h);
1137 }
1138
1139 /**
1140 * This method returns whether all buttons should have the same width.
1141 *
1142 * @return Whether all buttons should have the same width.
1143 */
1144 protected boolean getSizeButtonsToSameWidth()
1145 {
1146 return true;
1147 }
1148
1149 /**
1150 * This method installs components for the JOptionPane.
1151 */
1152 protected void installComponents()
1153 {
1154 // First thing is the message area.
1155 optionPane.add(createMessageArea());
1156
1157 // Add separator when createSeparator() is overridden to return
1158 // something other than null.
1159 Container sep = createSeparator();
1160 if (sep != null)
1161 optionPane.add(sep);
1162
1163 // Last thing is the button area.
1164 optionPane.add(createButtonArea());
1165 }
1166
1167 /**
1168 * This method installs defaults for the JOptionPane.
1169 */
1170 protected void installDefaults()
1171 {
1172 LookAndFeel.installColorsAndFont(optionPane, "OptionPane.background",
1173 "OptionPane.foreground",
1174 "OptionPane.font");
1175 LookAndFeel.installBorder(optionPane, "OptionPane.border");
1176 optionPane.setOpaque(true);
1177
1178 minimumSize = UIManager.getDimension("OptionPane.minimumSize");
1179
1180 // FIXME: Image icons don't seem to work properly right now.
1181 // Once they do, replace the synthetic icons with these ones.
1182
1183 /*
1184 warningIcon = (IconUIResource) defaults.getIcon("OptionPane.warningIcon");
1185 infoIcon = (IconUIResource) defaults.getIcon("OptionPane.informationIcon");
1186 errorIcon = (IconUIResource) defaults.getIcon("OptionPane.errorIcon");
1187 questionIcon = (IconUIResource) defaults.getIcon("OptionPane.questionIcon");
1188 */
1189 }
1190
1191 /**
1192 * This method installs keyboard actions for the JOptionpane.
1193 */
1194 protected void installKeyboardActions()
1195 {
1196 // Install the input map.
1197 Object[] bindings =
1198 (Object[]) SharedUIDefaults.get("OptionPane.windowBindings");
1199 InputMap inputMap = LookAndFeel.makeComponentInputMap(optionPane,
1200 bindings);
1201 SwingUtilities.replaceUIInputMap(optionPane,
1202 JComponent.WHEN_IN_FOCUSED_WINDOW,
1203 inputMap);
1204
1205 // FIXME: The JDK uses a LazyActionMap for parentActionMap
1206 SwingUtilities.replaceUIActionMap(optionPane, getActionMap());
1207 }
1208
1209 /**
1210 * Fetches the action map from the UI defaults, or create a new one
1211 * if the action map hasn't been initialized.
1212 *
1213 * @return the action map
1214 */
1215 private ActionMap getActionMap()
1216 {
1217 ActionMap am = (ActionMap) UIManager.get("OptionPane.actionMap");
1218 if (am == null)
1219 {
1220 am = createDefaultActions();
1221 UIManager.getLookAndFeelDefaults().put("OptionPane.actionMap", am);
1222 }
1223 return am;
1224 }
1225
1226 private ActionMap createDefaultActions()
1227 {
1228 ActionMapUIResource am = new ActionMapUIResource();
1229 Action action = new OptionPaneCloseAction();
1230
1231 am.put("close", action);
1232 return am;
1233 }
1234
1235 /**
1236 * This method installs listeners for the JOptionPane.
1237 */
1238 protected void installListeners()
1239 {
1240 propertyChangeListener = createPropertyChangeListener();
1241
1242 optionPane.addPropertyChangeListener(propertyChangeListener);
1243 }
1244
1245 /**
1246 * This method installs the UI for the JOptionPane.
1247 *
1248 * @param c The JComponent to install the UI for.
1249 */
1250 public void installUI(JComponent c)
1251 {
1252 if (c instanceof JOptionPane)
1253 {
1254 optionPane = (JOptionPane) c;
1255
1256 installDefaults();
1257 installComponents();
1258 installListeners();
1259 installKeyboardActions();
1260 }
1261 }
1262
1263 /**
1264 * Changes the inputValue property in the JOptionPane based on the current
1265 * value of the inputComponent.
1266 */
1267 protected void resetInputValue()
1268 {
1269 if (optionPane.getWantsInput() && inputComponent != null)
1270 {
1271 Object output = null;
1272 if (inputComponent instanceof JTextField)
1273 output = ((JTextField) inputComponent).getText();
1274 else if (inputComponent instanceof JComboBox)
1275 output = ((JComboBox) inputComponent).getSelectedItem();
1276 else if (inputComponent instanceof JList)
1277 output = ((JList) inputComponent).getSelectedValue();
1278
1279 if (output != null)
1280 optionPane.setInputValue(output);
1281 }
1282 }
1283
1284 /**
1285 * This method requests focus to the inputComponent (if one is present) and
1286 * the initialFocusComponent otherwise.
1287 *
1288 * @param op The JOptionPane.
1289 */
1290 public void selectInitialValue(JOptionPane op)
1291 {
1292 if (inputComponent != null)
1293 {
1294 inputComponent.requestFocus();
1295 return;
1296 }
1297 if (initialFocusComponent != null)
1298 initialFocusComponent.requestFocus();
1299 }
1300
1301 /**
1302 * This method resets the value in the inputComponent to the
1303 * initialSelectionValue property.
1304 * This is package-private to avoid an accessor method.
1305 */
1306 void resetSelectedValue()
1307 {
1308 if (inputComponent != null)
1309 {
1310 Object init = optionPane.getInitialSelectionValue();
1311 if (init == null)
1312 return;
1313 if (inputComponent instanceof JTextField)
1314 ((JTextField) inputComponent).setText((String) init);
1315 else if (inputComponent instanceof JComboBox)
1316 ((JComboBox) inputComponent).setSelectedItem(init);
1317 else if (inputComponent instanceof JList)
1318 {
1319 // ((JList) inputComponent).setSelectedValue(init, true);
1320 }
1321 }
1322 }
1323
1324 /**
1325 * This method uninstalls all the components in the JOptionPane.
1326 */
1327 protected void uninstallComponents()
1328 {
1329 optionPane.removeAll();
1330 buttonContainer = null;
1331 messageAreaContainer = null;
1332 }
1333
1334 /**
1335 * This method uninstalls the defaults for the JOptionPane.
1336 */
1337 protected void uninstallDefaults()
1338 {
1339 optionPane.setFont(null);
1340 optionPane.setForeground(null);
1341 optionPane.setBackground(null);
1342
1343 minimumSize = null;
1344
1345 // FIXME: ImageIcons don't seem to work properly
1346
1347 /*
1348 warningIcon = null;
1349 errorIcon = null;
1350 questionIcon = null;
1351 infoIcon = null;
1352 */
1353 }
1354
1355 /**
1356 * This method uninstalls keyboard actions for the JOptionPane.
1357 */
1358 protected void uninstallKeyboardActions()
1359 {
1360 SwingUtilities.replaceUIInputMap(optionPane, JComponent.
1361 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
1362 SwingUtilities.replaceUIActionMap(optionPane, null);
1363 }
1364
1365 /**
1366 * This method uninstalls listeners for the JOptionPane.
1367 */
1368 protected void uninstallListeners()
1369 {
1370 optionPane.removePropertyChangeListener(propertyChangeListener);
1371 propertyChangeListener = null;
1372 }
1373
1374 /**
1375 * This method uninstalls the UI for the given JComponent.
1376 *
1377 * @param c The JComponent to uninstall for.
1378 */
1379 public void uninstallUI(JComponent c)
1380 {
1381 uninstallKeyboardActions();
1382 uninstallListeners();
1383 uninstallComponents();
1384 uninstallDefaults();
1385
1386 optionPane = null;
1387 }
1388
1389 /**
1390 * Applies the proper UI configuration to labels that are added to
1391 * the OptionPane.
1392 *
1393 * @param l the label to configure
1394 */
1395 private void configureLabel(JLabel l)
1396 {
1397 Color c = UIManager.getColor("OptionPane.messageForeground");
1398 if (c != null)
1399 l.setForeground(c);
1400 Font f = UIManager.getFont("OptionPane.messageFont");
1401 if (f != null)
1402 l.setFont(f);
1403 }
1404 }