001/* BasicOptionPaneUI.java --
002   Copyright (C) 2004, 2005 Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing.plaf.basic;
040
041import java.awt.BorderLayout;
042import java.awt.Color;
043import java.awt.Component;
044import java.awt.Container;
045import java.awt.Dimension;
046import java.awt.Font;
047import java.awt.Graphics;
048import java.awt.GridBagConstraints;
049import java.awt.GridBagLayout;
050import java.awt.Insets;
051import java.awt.LayoutManager;
052import java.awt.Polygon;
053import java.awt.Window;
054import java.awt.event.ActionEvent;
055import java.awt.event.ActionListener;
056import java.beans.PropertyChangeEvent;
057import java.beans.PropertyChangeListener;
058import java.beans.PropertyVetoException;
059
060import javax.swing.AbstractAction;
061import javax.swing.Action;
062import javax.swing.ActionMap;
063import javax.swing.BorderFactory;
064import javax.swing.Box;
065import javax.swing.BoxLayout;
066import javax.swing.Icon;
067import javax.swing.InputMap;
068import javax.swing.JButton;
069import javax.swing.JComboBox;
070import javax.swing.JComponent;
071import javax.swing.JDialog;
072import javax.swing.JInternalFrame;
073import javax.swing.JLabel;
074import javax.swing.JList;
075import javax.swing.JOptionPane;
076import javax.swing.JPanel;
077import javax.swing.JTextField;
078import javax.swing.LookAndFeel;
079import javax.swing.SwingUtilities;
080import javax.swing.UIManager;
081import javax.swing.border.Border;
082import javax.swing.plaf.ActionMapUIResource;
083import javax.swing.plaf.ComponentUI;
084import javax.swing.plaf.OptionPaneUI;
085
086/**
087 * This class is the UI delegate for JOptionPane in the Basic Look and Feel.
088 */
089public 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}