Source for javax.swing.plaf.basic.BasicOptionPaneUI

   1: /* BasicOptionPaneUI.java --
   2:    Copyright (C) 2004, 2005 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing.plaf.basic;
  40: 
  41: import java.awt.BorderLayout;
  42: import java.awt.Color;
  43: import java.awt.Component;
  44: import java.awt.Container;
  45: import java.awt.Dimension;
  46: import java.awt.Font;
  47: import java.awt.Graphics;
  48: import java.awt.GridBagConstraints;
  49: import java.awt.GridBagLayout;
  50: import java.awt.Insets;
  51: import java.awt.LayoutManager;
  52: import java.awt.Polygon;
  53: import java.awt.Window;
  54: import java.awt.event.ActionEvent;
  55: import java.awt.event.ActionListener;
  56: import java.beans.PropertyChangeEvent;
  57: import java.beans.PropertyChangeListener;
  58: import java.beans.PropertyVetoException;
  59: 
  60: import javax.swing.AbstractAction;
  61: import javax.swing.Action;
  62: import javax.swing.ActionMap;
  63: import javax.swing.BorderFactory;
  64: import javax.swing.Box;
  65: import javax.swing.BoxLayout;
  66: import javax.swing.Icon;
  67: import javax.swing.InputMap;
  68: import javax.swing.JButton;
  69: import javax.swing.JComboBox;
  70: import javax.swing.JComponent;
  71: import javax.swing.JDialog;
  72: import javax.swing.JInternalFrame;
  73: import javax.swing.JLabel;
  74: import javax.swing.JList;
  75: import javax.swing.JOptionPane;
  76: import javax.swing.JPanel;
  77: import javax.swing.JTextField;
  78: import javax.swing.LookAndFeel;
  79: import javax.swing.SwingUtilities;
  80: import javax.swing.UIManager;
  81: import javax.swing.border.Border;
  82: import javax.swing.plaf.ActionMapUIResource;
  83: import javax.swing.plaf.ComponentUI;
  84: import javax.swing.plaf.OptionPaneUI;
  85: 
  86: /**
  87:  * This class is the UI delegate for JOptionPane in the Basic Look and Feel.
  88:  */
  89: public class BasicOptionPaneUI extends OptionPaneUI
  90: {
  91:   /**
  92:    * Implements the "close" keyboard action.
  93:    */
  94:   static class OptionPaneCloseAction
  95:     extends AbstractAction
  96:   {
  97: 
  98:     public void actionPerformed(ActionEvent event)
  99:     {
 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: }