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