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