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    }