1 /* 2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.swing.plaf.basic; 27 28 import sun.swing.DefaultLookup; 29 import sun.swing.UIAction; 30 import javax.swing.border.Border; 31 import javax.swing.border.EmptyBorder; 32 import javax.swing.*; 33 import javax.swing.event.*; 34 import javax.swing.plaf.ActionMapUIResource; 35 import javax.swing.plaf.ComponentUI; 36 import javax.swing.plaf.OptionPaneUI; 37 import java.awt.*; 38 import java.awt.event.*; 39 import java.beans.PropertyChangeEvent; 40 import java.beans.PropertyChangeListener; 41 import java.util.Locale; 42 import java.security.AccessController; 43 44 import sun.security.action.GetPropertyAction; 45 46 47 /** 48 * Provides the basic look and feel for a <code>JOptionPane</code>. 49 * <code>BasicMessagePaneUI</code> provides a means to place an icon, 50 * message and buttons into a <code>Container</code>. 51 * Generally, the layout will look like: 52 * <pre> 53 * ------------------ 54 * | i | message | 55 * | c | message | 56 * | o | message | 57 * | n | message | 58 * ------------------ 59 * | buttons | 60 * |________________| 61 * </pre> 62 * icon is an instance of <code>Icon</code> that is wrapped inside a 63 * <code>JLabel</code>. The message is an opaque object and is tested 64 * for the following: if the message is a <code>Component</code> it is 65 * added to the <code>Container</code>, if it is an <code>Icon</code> 66 * it is wrapped inside a <code>JLabel</code> and added to the 67 * <code>Container</code> otherwise it is wrapped inside a <code>JLabel</code>. 68 * <p> 69 * The above layout is used when the option pane's 70 * <code>ComponentOrientation</code> property is horizontal, left-to-right. 71 * The layout will be adjusted appropriately for other orientations. 72 * <p> 73 * The <code>Container</code>, message, icon, and buttons are all 74 * determined from abstract methods. 75 * 76 * @author James Gosling 77 * @author Scott Violet 78 * @author Amy Fowler 79 */ 80 public class BasicOptionPaneUI extends OptionPaneUI { 81 82 public static final int MinimumWidth = 262; 83 public static final int MinimumHeight = 90; 84 85 private static String newline; 86 87 /** 88 * <code>JOptionPane</code> that the receiver is providing the 89 * look and feel for. 90 */ 91 protected JOptionPane optionPane; 92 93 protected Dimension minimumSize; 94 95 /** JComponent provide for input if optionPane.getWantsInput() returns 96 * true. */ 97 protected JComponent inputComponent; 98 99 /** Component to receive focus when messaged with selectInitialValue. */ 100 protected Component initialFocusComponent; 101 102 /** This is set to true in validateComponent if a Component is contained 103 * in either the message or the buttons. */ 104 protected boolean hasCustomComponents; 105 106 protected PropertyChangeListener propertyChangeListener; 107 108 private Handler handler; 109 110 111 static { 112 newline = System.lineSeparator(); 113 if (newline == null) { 114 newline = "\n"; 115 } 116 } 117 118 static void loadActionMap(LazyActionMap map) { 119 map.put(new Actions(Actions.CLOSE)); 120 BasicLookAndFeel.installAudioActionMap(map); 121 } 122 123 124 125 /** 126 * Creates a new BasicOptionPaneUI instance. 127 */ 128 public static ComponentUI createUI(JComponent x) { 129 return new BasicOptionPaneUI(); 130 } 131 132 /** 133 * Installs the receiver as the L&F for the passed in 134 * <code>JOptionPane</code>. 135 */ 136 public void installUI(JComponent c) { 137 optionPane = (JOptionPane)c; 138 installDefaults(); 139 optionPane.setLayout(createLayoutManager()); 140 installComponents(); 141 installListeners(); 142 installKeyboardActions(); 143 } 144 145 /** 146 * Removes the receiver from the L&F controller of the passed in split 147 * pane. 148 */ 149 public void uninstallUI(JComponent c) { 150 uninstallComponents(); 151 optionPane.setLayout(null); 152 uninstallKeyboardActions(); 153 uninstallListeners(); 154 uninstallDefaults(); 155 optionPane = null; 156 } 157 158 protected void installDefaults() { 159 LookAndFeel.installColorsAndFont(optionPane, "OptionPane.background", 160 "OptionPane.foreground", "OptionPane.font"); 161 LookAndFeel.installBorder(optionPane, "OptionPane.border"); 162 minimumSize = UIManager.getDimension("OptionPane.minimumSize"); 163 LookAndFeel.installProperty(optionPane, "opaque", Boolean.TRUE); 164 } 165 166 protected void uninstallDefaults() { 167 LookAndFeel.uninstallBorder(optionPane); 168 } 169 170 protected void installComponents() { 171 optionPane.add(createMessageArea()); 172 173 Container separator = createSeparator(); 174 if (separator != null) { 175 optionPane.add(separator); 176 } 177 optionPane.add(createButtonArea()); 178 optionPane.applyComponentOrientation(optionPane.getComponentOrientation()); 179 } 180 181 protected void uninstallComponents() { 182 hasCustomComponents = false; 183 inputComponent = null; 184 initialFocusComponent = null; 185 optionPane.removeAll(); 186 } 187 188 protected LayoutManager createLayoutManager() { 189 return new BoxLayout(optionPane, BoxLayout.Y_AXIS); 190 } 191 192 protected void installListeners() { 193 if ((propertyChangeListener = createPropertyChangeListener()) != null) { 194 optionPane.addPropertyChangeListener(propertyChangeListener); 195 } 196 } 197 198 protected void uninstallListeners() { 199 if (propertyChangeListener != null) { 200 optionPane.removePropertyChangeListener(propertyChangeListener); 201 propertyChangeListener = null; 202 } 203 handler = null; 204 } 205 206 protected PropertyChangeListener createPropertyChangeListener() { 207 return getHandler(); 208 } 209 210 private Handler getHandler() { 211 if (handler == null) { 212 handler = new Handler(); 213 } 214 return handler; 215 } 216 217 protected void installKeyboardActions() { 218 InputMap map = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); 219 220 SwingUtilities.replaceUIInputMap(optionPane, JComponent. 221 WHEN_IN_FOCUSED_WINDOW, map); 222 223 LazyActionMap.installLazyActionMap(optionPane, BasicOptionPaneUI.class, 224 "OptionPane.actionMap"); 225 } 226 227 protected void uninstallKeyboardActions() { 228 SwingUtilities.replaceUIInputMap(optionPane, JComponent. 229 WHEN_IN_FOCUSED_WINDOW, null); 230 SwingUtilities.replaceUIActionMap(optionPane, null); 231 } 232 233 InputMap getInputMap(int condition) { 234 if (condition == JComponent.WHEN_IN_FOCUSED_WINDOW) { 235 Object[] bindings = (Object[])DefaultLookup.get( 236 optionPane, this, "OptionPane.windowBindings"); 237 if (bindings != null) { 238 return LookAndFeel.makeComponentInputMap(optionPane, bindings); 239 } 240 } 241 return null; 242 } 243 244 /** 245 * Returns the minimum size the option pane should be. Primarily 246 * provided for subclassers wishing to offer a different minimum size. 247 */ 248 public Dimension getMinimumOptionPaneSize() { 249 if (minimumSize == null) { 250 return new Dimension(MinimumWidth, MinimumHeight); 251 } 252 return new Dimension(minimumSize.width, 253 minimumSize.height); 254 } 255 256 /** 257 * If <code>c</code> is the <code>JOptionPane</code> the receiver 258 * is contained in, the preferred 259 * size that is returned is the maximum of the preferred size of 260 * the <code>LayoutManager</code> for the <code>JOptionPane</code>, and 261 * <code>getMinimumOptionPaneSize</code>. 262 */ 263 public Dimension getPreferredSize(JComponent c) { 264 if (c == optionPane) { 265 Dimension ourMin = getMinimumOptionPaneSize(); 266 LayoutManager lm = c.getLayout(); 267 268 if (lm != null) { 269 Dimension lmSize = lm.preferredLayoutSize(c); 270 271 if (ourMin != null) 272 return new Dimension 273 (Math.max(lmSize.width, ourMin.width), 274 Math.max(lmSize.height, ourMin.height)); 275 return lmSize; 276 } 277 return ourMin; 278 } 279 return null; 280 } 281 282 /** 283 * Messaged from installComponents to create a Container containing the 284 * body of the message. The icon is the created by calling 285 * <code>addIcon</code>. 286 */ 287 protected Container createMessageArea() { 288 JPanel top = new JPanel(); 289 Border topBorder = (Border)DefaultLookup.get(optionPane, this, 290 "OptionPane.messageAreaBorder"); 291 if (topBorder != null) { 292 top.setBorder(topBorder); 293 } 294 top.setLayout(new BorderLayout()); 295 296 /* Fill the body. */ 297 Container body = new JPanel(new GridBagLayout()); 298 Container realBody = new JPanel(new BorderLayout()); 299 300 body.setName("OptionPane.body"); 301 realBody.setName("OptionPane.realBody"); 302 303 if (getIcon() != null) { 304 JPanel sep = new JPanel(); 305 sep.setName("OptionPane.separator"); 306 sep.setPreferredSize(new Dimension(15, 1)); 307 realBody.add(sep, BorderLayout.BEFORE_LINE_BEGINS); 308 } 309 realBody.add(body, BorderLayout.CENTER); 310 311 GridBagConstraints cons = new GridBagConstraints(); 312 cons.gridx = cons.gridy = 0; 313 cons.gridwidth = GridBagConstraints.REMAINDER; 314 cons.gridheight = 1; 315 cons.anchor = DefaultLookup.getInt(optionPane, this, 316 "OptionPane.messageAnchor", GridBagConstraints.CENTER); 317 cons.insets = new Insets(0,0,3,0); 318 319 addMessageComponents(body, cons, getMessage(), 320 getMaxCharactersPerLineCount(), false); 321 top.add(realBody, BorderLayout.CENTER); 322 323 addIcon(top); 324 return top; 325 } 326 327 /** 328 * Creates the appropriate object to represent <code>msg</code> and 329 * places it into <code>container</code>. If <code>msg</code> is an 330 * instance of Component, it is added directly, if it is an Icon, 331 * a JLabel is created to represent it, otherwise a JLabel is 332 * created for the string, if <code>d</code> is an Object[], this 333 * method will be recursively invoked for the children. 334 * <code>internallyCreated</code> is true if Objc is an instance 335 * of Component and was created internally by this method (this is 336 * used to correctly set hasCustomComponents only if !internallyCreated). 337 */ 338 protected void addMessageComponents(Container container, 339 GridBagConstraints cons, 340 Object msg, int maxll, 341 boolean internallyCreated) { 342 if (msg == null) { 343 return; 344 } 345 if (msg instanceof Component) { 346 // To workaround problem where Gridbad will set child 347 // to its minimum size if its preferred size will not fit 348 // within allocated cells 349 if (msg instanceof JScrollPane || msg instanceof JPanel) { 350 cons.fill = GridBagConstraints.BOTH; 351 cons.weighty = 1; 352 } else { 353 cons.fill = GridBagConstraints.HORIZONTAL; 354 } 355 cons.weightx = 1; 356 357 container.add((Component) msg, cons); 358 cons.weightx = 0; 359 cons.weighty = 0; 360 cons.fill = GridBagConstraints.NONE; 361 cons.gridy++; 362 if (!internallyCreated) { 363 hasCustomComponents = true; 364 } 365 366 } else if (msg instanceof Object[]) { 367 Object [] msgs = (Object[]) msg; 368 for (Object o : msgs) { 369 addMessageComponents(container, cons, o, maxll, false); 370 } 371 372 } else if (msg instanceof Icon) { 373 JLabel label = new JLabel( (Icon)msg, SwingConstants.CENTER ); 374 configureMessageLabel(label); 375 addMessageComponents(container, cons, label, maxll, true); 376 377 } else { 378 String s = msg.toString(); 379 int len = s.length(); 380 if (len <= 0) { 381 return; 382 } 383 int nl; 384 int nll = 0; 385 386 if ((nl = s.indexOf(newline)) >= 0) { 387 nll = newline.length(); 388 } else if ((nl = s.indexOf("\r\n")) >= 0) { 389 nll = 2; 390 } else if ((nl = s.indexOf('\n')) >= 0) { 391 nll = 1; 392 } 393 if (nl >= 0) { 394 // break up newlines 395 if (nl == 0) { 396 @SuppressWarnings("serial") // anonymous class 397 JPanel breakPanel = new JPanel() { 398 public Dimension getPreferredSize() { 399 Font f = getFont(); 400 401 if (f != null) { 402 return new Dimension(1, f.getSize() + 2); 403 } 404 return new Dimension(0, 0); 405 } 406 }; 407 breakPanel.setName("OptionPane.break"); 408 addMessageComponents(container, cons, breakPanel, maxll, 409 true); 410 } else { 411 addMessageComponents(container, cons, s.substring(0, nl), 412 maxll, false); 413 } 414 addMessageComponents(container, cons, s.substring(nl + nll), maxll, 415 false); 416 417 } else if (len > maxll) { 418 Container c = Box.createVerticalBox(); 419 c.setName("OptionPane.verticalBox"); 420 burstStringInto(c, s, maxll); 421 addMessageComponents(container, cons, c, maxll, true ); 422 423 } else { 424 JLabel label; 425 label = new JLabel( s, JLabel.LEADING ); 426 label.setName("OptionPane.label"); 427 configureMessageLabel(label); 428 addMessageComponents(container, cons, label, maxll, true); 429 } 430 } 431 } 432 433 /** 434 * Returns the message to display from the JOptionPane the receiver is 435 * providing the look and feel for. 436 */ 437 protected Object getMessage() { 438 inputComponent = null; 439 if (optionPane != null) { 440 if (optionPane.getWantsInput()) { 441 /* Create a user component to capture the input. If the 442 selectionValues are non null the component and there 443 are < 20 values it'll be a combobox, if non null and 444 >= 20, it'll be a list, otherwise it'll be a textfield. */ 445 Object message = optionPane.getMessage(); 446 Object[] sValues = optionPane.getSelectionValues(); 447 Object inputValue = optionPane 448 .getInitialSelectionValue(); 449 JComponent toAdd; 450 451 if (sValues != null) { 452 if (sValues.length < 20) { 453 JComboBox cBox = new JComboBox(); 454 455 cBox.setName("OptionPane.comboBox"); 456 for(int counter = 0, maxCounter = sValues.length; 457 counter < maxCounter; counter++) { 458 cBox.addItem(sValues[counter]); 459 } 460 if (inputValue != null) { 461 cBox.setSelectedItem(inputValue); 462 } 463 inputComponent = cBox; 464 toAdd = cBox; 465 466 } else { 467 JList list = new JList(sValues); 468 JScrollPane sp = new JScrollPane(list); 469 470 sp.setName("OptionPane.scrollPane"); 471 list.setName("OptionPane.list"); 472 list.setVisibleRowCount(10); 473 list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 474 if(inputValue != null) 475 list.setSelectedValue(inputValue, true); 476 list.addMouseListener(getHandler()); 477 toAdd = sp; 478 inputComponent = list; 479 } 480 481 } else { 482 MultiplexingTextField tf = new MultiplexingTextField(20); 483 484 tf.setName("OptionPane.textField"); 485 tf.setKeyStrokes(new KeyStroke[] { 486 KeyStroke.getKeyStroke("ENTER") } ); 487 if (inputValue != null) { 488 String inputString = inputValue.toString(); 489 tf.setText(inputString); 490 tf.setSelectionStart(0); 491 tf.setSelectionEnd(inputString.length()); 492 } 493 tf.addActionListener(getHandler()); 494 toAdd = inputComponent = tf; 495 } 496 497 Object[] newMessage; 498 499 if (message == null) { 500 newMessage = new Object[1]; 501 newMessage[0] = toAdd; 502 503 } else { 504 newMessage = new Object[2]; 505 newMessage[0] = message; 506 newMessage[1] = toAdd; 507 } 508 return newMessage; 509 } 510 return optionPane.getMessage(); 511 } 512 return null; 513 } 514 515 /** 516 * Creates and adds a JLabel representing the icon returned from 517 * <code>getIcon</code> to <code>top</code>. This is messaged from 518 * <code>createMessageArea</code> 519 */ 520 protected void addIcon(Container top) { 521 /* Create the icon. */ 522 Icon sideIcon = getIcon(); 523 524 if (sideIcon != null) { 525 JLabel iconLabel = new JLabel(sideIcon); 526 527 iconLabel.setName("OptionPane.iconLabel"); 528 iconLabel.setVerticalAlignment(SwingConstants.TOP); 529 top.add(iconLabel, BorderLayout.BEFORE_LINE_BEGINS); 530 } 531 } 532 533 /** 534 * Returns the icon from the JOptionPane the receiver is providing 535 * the look and feel for, or the default icon as returned from 536 * <code>getDefaultIcon</code>. 537 */ 538 protected Icon getIcon() { 539 Icon mIcon = (optionPane == null ? null : optionPane.getIcon()); 540 541 if(mIcon == null && optionPane != null) 542 mIcon = getIconForType(optionPane.getMessageType()); 543 return mIcon; 544 } 545 546 /** 547 * Returns the icon to use for the passed in type. 548 */ 549 protected Icon getIconForType(int messageType) { 550 if(messageType < 0 || messageType > 3) 551 return null; 552 String propertyName = null; 553 switch(messageType) { 554 case 0: 555 propertyName = "OptionPane.errorIcon"; 556 break; 557 case 1: 558 propertyName = "OptionPane.informationIcon"; 559 break; 560 case 2: 561 propertyName = "OptionPane.warningIcon"; 562 break; 563 case 3: 564 propertyName = "OptionPane.questionIcon"; 565 break; 566 } 567 if (propertyName != null) { 568 return (Icon)DefaultLookup.get(optionPane, this, propertyName); 569 } 570 return null; 571 } 572 573 /** 574 * Returns the maximum number of characters to place on a line. 575 */ 576 protected int getMaxCharactersPerLineCount() { 577 return optionPane.getMaxCharactersPerLineCount(); 578 } 579 580 /** 581 * Recursively creates new JLabel instances to represent <code>d</code>. 582 * Each JLabel instance is added to <code>c</code>. 583 */ 584 protected void burstStringInto(Container c, String d, int maxll) { 585 // Primitive line wrapping 586 int len = d.length(); 587 if (len <= 0) 588 return; 589 if (len > maxll) { 590 int p = d.lastIndexOf(' ', maxll); 591 if (p <= 0) 592 p = d.indexOf(' ', maxll); 593 if (p > 0 && p < len) { 594 burstStringInto(c, d.substring(0, p), maxll); 595 burstStringInto(c, d.substring(p + 1), maxll); 596 return; 597 } 598 } 599 JLabel label = new JLabel(d, JLabel.LEFT); 600 label.setName("OptionPane.label"); 601 configureMessageLabel(label); 602 c.add(label); 603 } 604 605 protected Container createSeparator() { 606 return null; 607 } 608 609 /** 610 * Creates and returns a Container containing the buttons. The buttons 611 * are created by calling <code>getButtons</code>. 612 */ 613 protected Container createButtonArea() { 614 JPanel bottom = new JPanel(); 615 Border border = (Border)DefaultLookup.get(optionPane, this, 616 "OptionPane.buttonAreaBorder"); 617 bottom.setName("OptionPane.buttonArea"); 618 if (border != null) { 619 bottom.setBorder(border); 620 } 621 bottom.setLayout(new ButtonAreaLayout( 622 DefaultLookup.getBoolean(optionPane, this, 623 "OptionPane.sameSizeButtons", true), 624 DefaultLookup.getInt(optionPane, this, "OptionPane.buttonPadding", 625 6), 626 DefaultLookup.getInt(optionPane, this, 627 "OptionPane.buttonOrientation", SwingConstants.CENTER), 628 DefaultLookup.getBoolean(optionPane, this, "OptionPane.isYesLast", 629 false))); 630 addButtonComponents(bottom, getButtons(), getInitialValueIndex()); 631 return bottom; 632 } 633 634 /** 635 * Creates the appropriate object to represent each of the objects in 636 * <code>buttons</code> and adds it to <code>container</code>. This 637 * differs from addMessageComponents in that it will recurse on 638 * <code>buttons</code> and that if button is not a Component 639 * it will create an instance of JButton. 640 */ 641 protected void addButtonComponents(Container container, Object[] buttons, 642 int initialIndex) { 643 if (buttons != null && buttons.length > 0) { 644 boolean sizeButtonsToSame = getSizeButtonsToSameWidth(); 645 boolean createdAll = true; 646 int numButtons = buttons.length; 647 JButton[] createdButtons = null; 648 int maxWidth = 0; 649 650 if (sizeButtonsToSame) { 651 createdButtons = new JButton[numButtons]; 652 } 653 654 for(int counter = 0; counter < numButtons; counter++) { 655 Object button = buttons[counter]; 656 Component newComponent; 657 658 if (button instanceof Component) { 659 createdAll = false; 660 newComponent = (Component)button; 661 container.add(newComponent); 662 hasCustomComponents = true; 663 664 } else { 665 JButton aButton; 666 667 if (button instanceof ButtonFactory) { 668 aButton = ((ButtonFactory)button).createButton(); 669 } 670 else if (button instanceof Icon) 671 aButton = new JButton((Icon)button); 672 else 673 aButton = new JButton(button.toString()); 674 675 aButton.setName("OptionPane.button"); 676 aButton.setMultiClickThreshhold(DefaultLookup.getInt( 677 optionPane, this, "OptionPane.buttonClickThreshhold", 678 0)); 679 configureButton(aButton); 680 681 container.add(aButton); 682 683 ActionListener buttonListener = createButtonActionListener(counter); 684 if (buttonListener != null) { 685 aButton.addActionListener(buttonListener); 686 } 687 newComponent = aButton; 688 } 689 if (sizeButtonsToSame && createdAll && 690 (newComponent instanceof JButton)) { 691 createdButtons[counter] = (JButton)newComponent; 692 maxWidth = Math.max(maxWidth, 693 newComponent.getMinimumSize().width); 694 } 695 if (counter == initialIndex) { 696 initialFocusComponent = newComponent; 697 if (initialFocusComponent instanceof JButton) { 698 JButton defaultB = (JButton)initialFocusComponent; 699 defaultB.addHierarchyListener(new HierarchyListener() { 700 public void hierarchyChanged(HierarchyEvent e) { 701 if ((e.getChangeFlags() & 702 HierarchyEvent.PARENT_CHANGED) != 0) { 703 JButton defaultButton = (JButton) e.getComponent(); 704 JRootPane root = 705 SwingUtilities.getRootPane(defaultButton); 706 if (root != null) { 707 root.setDefaultButton(defaultButton); 708 } 709 } 710 } 711 }); 712 } 713 } 714 } 715 ((ButtonAreaLayout)container.getLayout()). 716 setSyncAllWidths((sizeButtonsToSame && createdAll)); 717 /* Set the padding, windows seems to use 8 if <= 2 components, 718 otherwise 4 is used. It may actually just be the size of the 719 buttons is always the same, not sure. */ 720 if (DefaultLookup.getBoolean(optionPane, this, 721 "OptionPane.setButtonMargin", true) && sizeButtonsToSame && 722 createdAll) { 723 JButton aButton; 724 int padSize; 725 726 padSize = (numButtons <= 2? 8 : 4); 727 728 for(int counter = 0; counter < numButtons; counter++) { 729 aButton = createdButtons[counter]; 730 aButton.setMargin(new Insets(2, padSize, 2, padSize)); 731 } 732 } 733 } 734 } 735 736 protected ActionListener createButtonActionListener(int buttonIndex) { 737 return new ButtonActionListener(buttonIndex); 738 } 739 740 /** 741 * Returns the buttons to display from the JOptionPane the receiver is 742 * providing the look and feel for. If the JOptionPane has options 743 * set, they will be provided, otherwise if the optionType is 744 * YES_NO_OPTION, yesNoOptions is returned, if the type is 745 * YES_NO_CANCEL_OPTION yesNoCancelOptions is returned, otherwise 746 * defaultButtons are returned. 747 */ 748 protected Object[] getButtons() { 749 if (optionPane != null) { 750 Object[] suppliedOptions = optionPane.getOptions(); 751 752 if (suppliedOptions == null) { 753 Object[] defaultOptions; 754 int type = optionPane.getOptionType(); 755 Locale l = optionPane.getLocale(); 756 int minimumWidth = 757 DefaultLookup.getInt(optionPane, this, 758 "OptionPane.buttonMinimumWidth",-1); 759 if (type == JOptionPane.YES_NO_OPTION) { 760 defaultOptions = new ButtonFactory[2]; 761 defaultOptions[0] = new ButtonFactory( 762 UIManager.getString("OptionPane.yesButtonText", l), 763 getMnemonic("OptionPane.yesButtonMnemonic", l), 764 (Icon)DefaultLookup.get(optionPane, this, 765 "OptionPane.yesIcon"), minimumWidth); 766 defaultOptions[1] = new ButtonFactory( 767 UIManager.getString("OptionPane.noButtonText", l), 768 getMnemonic("OptionPane.noButtonMnemonic", l), 769 (Icon)DefaultLookup.get(optionPane, this, 770 "OptionPane.noIcon"), minimumWidth); 771 } else if (type == JOptionPane.YES_NO_CANCEL_OPTION) { 772 defaultOptions = new ButtonFactory[3]; 773 defaultOptions[0] = new ButtonFactory( 774 UIManager.getString("OptionPane.yesButtonText", l), 775 getMnemonic("OptionPane.yesButtonMnemonic", l), 776 (Icon)DefaultLookup.get(optionPane, this, 777 "OptionPane.yesIcon"), minimumWidth); 778 defaultOptions[1] = new ButtonFactory( 779 UIManager.getString("OptionPane.noButtonText",l), 780 getMnemonic("OptionPane.noButtonMnemonic", l), 781 (Icon)DefaultLookup.get(optionPane, this, 782 "OptionPane.noIcon"), minimumWidth); 783 defaultOptions[2] = new ButtonFactory( 784 UIManager.getString("OptionPane.cancelButtonText",l), 785 getMnemonic("OptionPane.cancelButtonMnemonic", l), 786 (Icon)DefaultLookup.get(optionPane, this, 787 "OptionPane.cancelIcon"), minimumWidth); 788 } else if (type == JOptionPane.OK_CANCEL_OPTION) { 789 defaultOptions = new ButtonFactory[2]; 790 defaultOptions[0] = new ButtonFactory( 791 UIManager.getString("OptionPane.okButtonText",l), 792 getMnemonic("OptionPane.okButtonMnemonic", l), 793 (Icon)DefaultLookup.get(optionPane, this, 794 "OptionPane.okIcon"), minimumWidth); 795 defaultOptions[1] = new ButtonFactory( 796 UIManager.getString("OptionPane.cancelButtonText",l), 797 getMnemonic("OptionPane.cancelButtonMnemonic", l), 798 (Icon)DefaultLookup.get(optionPane, this, 799 "OptionPane.cancelIcon"), minimumWidth); 800 } else { 801 defaultOptions = new ButtonFactory[1]; 802 defaultOptions[0] = new ButtonFactory( 803 UIManager.getString("OptionPane.okButtonText",l), 804 getMnemonic("OptionPane.okButtonMnemonic", l), 805 (Icon)DefaultLookup.get(optionPane, this, 806 "OptionPane.okIcon"), minimumWidth); 807 } 808 return defaultOptions; 809 810 } 811 return suppliedOptions; 812 } 813 return null; 814 } 815 816 private int getMnemonic(String key, Locale l) { 817 String value = (String)UIManager.get(key, l); 818 819 if (value == null) { 820 return 0; 821 } 822 try { 823 return Integer.parseInt(value); 824 } 825 catch (NumberFormatException nfe) { } 826 return 0; 827 } 828 829 /** 830 * Returns true, basic L&F wants all the buttons to have the same 831 * width. 832 */ 833 protected boolean getSizeButtonsToSameWidth() { 834 return true; 835 } 836 837 /** 838 * Returns the initial index into the buttons to select. The index 839 * is calculated from the initial value from the JOptionPane and 840 * options of the JOptionPane or 0. 841 */ 842 protected int getInitialValueIndex() { 843 if (optionPane != null) { 844 Object iv = optionPane.getInitialValue(); 845 Object[] options = optionPane.getOptions(); 846 847 if(options == null) { 848 return 0; 849 } 850 else if(iv != null) { 851 for(int counter = options.length - 1; counter >= 0; counter--){ 852 if(options[counter].equals(iv)) 853 return counter; 854 } 855 } 856 } 857 return -1; 858 } 859 860 /** 861 * Sets the input value in the option pane the receiver is providing 862 * the look and feel for based on the value in the inputComponent. 863 */ 864 protected void resetInputValue() { 865 if(inputComponent != null && (inputComponent instanceof JTextField)) { 866 optionPane.setInputValue(((JTextField)inputComponent).getText()); 867 868 } else if(inputComponent != null && 869 (inputComponent instanceof JComboBox)) { 870 optionPane.setInputValue(((JComboBox)inputComponent) 871 .getSelectedItem()); 872 } else if(inputComponent != null) { 873 optionPane.setInputValue(((JList)inputComponent) 874 .getSelectedValue()); 875 } 876 } 877 878 879 /** 880 * If inputComponent is non-null, the focus is requested on that, 881 * otherwise request focus on the default value 882 */ 883 public void selectInitialValue(JOptionPane op) { 884 if (inputComponent != null) 885 inputComponent.requestFocus(); 886 else { 887 if (initialFocusComponent != null) 888 initialFocusComponent.requestFocus(); 889 890 if (initialFocusComponent instanceof JButton) { 891 JRootPane root = SwingUtilities.getRootPane(initialFocusComponent); 892 if (root != null) { 893 root.setDefaultButton((JButton)initialFocusComponent); 894 } 895 } 896 } 897 } 898 899 /** 900 * Returns true if in the last call to validateComponent the message 901 * or buttons contained a subclass of Component. 902 */ 903 public boolean containsCustomComponents(JOptionPane op) { 904 return hasCustomComponents; 905 } 906 907 908 /** 909 * <code>ButtonAreaLayout</code> behaves in a similar manner to 910 * <code>FlowLayout</code>. It lays out all components from left to 911 * right. If <code>syncAllWidths</code> is true, the widths of each 912 * component will be set to the largest preferred size width. 913 * 914 * This class should be treated as a "protected" inner class. 915 * Instantiate it only within subclasses of {@code BasicOptionPaneUI}. 916 */ 917 public static class ButtonAreaLayout implements LayoutManager { 918 protected boolean syncAllWidths; 919 protected int padding; 920 /** If true, children are lumped together in parent. */ 921 protected boolean centersChildren; 922 private int orientation; 923 private boolean reverseButtons; 924 /** 925 * Indicates whether or not centersChildren should be used vs 926 * the orientation. This is done for backward compatibility 927 * for subclassers. 928 */ 929 private boolean useOrientation; 930 931 public ButtonAreaLayout(boolean syncAllWidths, int padding) { 932 this.syncAllWidths = syncAllWidths; 933 this.padding = padding; 934 centersChildren = true; 935 useOrientation = false; 936 } 937 938 ButtonAreaLayout(boolean syncAllSizes, int padding, int orientation, 939 boolean reverseButtons) { 940 this(syncAllSizes, padding); 941 useOrientation = true; 942 this.orientation = orientation; 943 this.reverseButtons = reverseButtons; 944 } 945 946 public void setSyncAllWidths(boolean newValue) { 947 syncAllWidths = newValue; 948 } 949 950 public boolean getSyncAllWidths() { 951 return syncAllWidths; 952 } 953 954 public void setPadding(int newPadding) { 955 this.padding = newPadding; 956 } 957 958 public int getPadding() { 959 return padding; 960 } 961 962 public void setCentersChildren(boolean newValue) { 963 centersChildren = newValue; 964 useOrientation = false; 965 } 966 967 public boolean getCentersChildren() { 968 return centersChildren; 969 } 970 971 private int getOrientation(Container container) { 972 if (!useOrientation) { 973 return SwingConstants.CENTER; 974 } 975 if (container.getComponentOrientation().isLeftToRight()) { 976 return orientation; 977 } 978 switch (orientation) { 979 case SwingConstants.LEFT: 980 return SwingConstants.RIGHT; 981 case SwingConstants.RIGHT: 982 return SwingConstants.LEFT; 983 case SwingConstants.CENTER: 984 return SwingConstants.CENTER; 985 } 986 return SwingConstants.LEFT; 987 } 988 989 public void addLayoutComponent(String string, Component comp) { 990 } 991 992 public void layoutContainer(Container container) { 993 Component[] children = container.getComponents(); 994 995 if(children != null && children.length > 0) { 996 int numChildren = children.length; 997 Insets insets = container.getInsets(); 998 int maxWidth = 0; 999 int maxHeight = 0; 1000 int totalButtonWidth = 0; 1001 int x = 0; 1002 int xOffset = 0; 1003 boolean ltr = container.getComponentOrientation(). 1004 isLeftToRight(); 1005 boolean reverse = (ltr) ? reverseButtons : !reverseButtons; 1006 1007 for(int counter = 0; counter < numChildren; counter++) { 1008 Dimension pref = children[counter].getPreferredSize(); 1009 maxWidth = Math.max(maxWidth, pref.width); 1010 maxHeight = Math.max(maxHeight, pref.height); 1011 totalButtonWidth += pref.width; 1012 } 1013 if (getSyncAllWidths()) { 1014 totalButtonWidth = maxWidth * numChildren; 1015 } 1016 totalButtonWidth += (numChildren - 1) * padding; 1017 1018 switch (getOrientation(container)) { 1019 case SwingConstants.LEFT: 1020 x = insets.left; 1021 break; 1022 case SwingConstants.RIGHT: 1023 x = container.getWidth() - insets.right - totalButtonWidth; 1024 break; 1025 case SwingConstants.CENTER: 1026 if (getCentersChildren() || numChildren < 2) { 1027 x = (container.getWidth() - totalButtonWidth) / 2; 1028 } 1029 else { 1030 x = insets.left; 1031 if (getSyncAllWidths()) { 1032 xOffset = (container.getWidth() - insets.left - 1033 insets.right - totalButtonWidth) / 1034 (numChildren - 1) + maxWidth; 1035 } 1036 else { 1037 xOffset = (container.getWidth() - insets.left - 1038 insets.right - totalButtonWidth) / 1039 (numChildren - 1); 1040 } 1041 } 1042 break; 1043 } 1044 1045 for (int counter = 0; counter < numChildren; counter++) { 1046 int index = (reverse) ? numChildren - counter - 1 : 1047 counter; 1048 Dimension pref = children[index].getPreferredSize(); 1049 1050 if (getSyncAllWidths()) { 1051 children[index].setBounds(x, insets.top, 1052 maxWidth, maxHeight); 1053 } 1054 else { 1055 children[index].setBounds(x, insets.top, pref.width, 1056 pref.height); 1057 } 1058 if (xOffset != 0) { 1059 x += xOffset; 1060 } 1061 else { 1062 x += children[index].getWidth() + padding; 1063 } 1064 } 1065 } 1066 } 1067 1068 public Dimension minimumLayoutSize(Container c) { 1069 if(c != null) { 1070 Component[] children = c.getComponents(); 1071 1072 if(children != null && children.length > 0) { 1073 Dimension aSize; 1074 int numChildren = children.length; 1075 int height = 0; 1076 Insets cInsets = c.getInsets(); 1077 int extraHeight = cInsets.top + cInsets.bottom; 1078 int extraWidth = cInsets.left + cInsets.right; 1079 1080 if (syncAllWidths) { 1081 int maxWidth = 0; 1082 1083 for(int counter = 0; counter < numChildren; counter++){ 1084 aSize = children[counter].getPreferredSize(); 1085 height = Math.max(height, aSize.height); 1086 maxWidth = Math.max(maxWidth, aSize.width); 1087 } 1088 return new Dimension(extraWidth + (maxWidth * numChildren) + 1089 (numChildren - 1) * padding, 1090 extraHeight + height); 1091 } 1092 else { 1093 int totalWidth = 0; 1094 1095 for(int counter = 0; counter < numChildren; counter++){ 1096 aSize = children[counter].getPreferredSize(); 1097 height = Math.max(height, aSize.height); 1098 totalWidth += aSize.width; 1099 } 1100 totalWidth += ((numChildren - 1) * padding); 1101 return new Dimension(extraWidth + totalWidth, extraHeight + height); 1102 } 1103 } 1104 } 1105 return new Dimension(0, 0); 1106 } 1107 1108 public Dimension preferredLayoutSize(Container c) { 1109 return minimumLayoutSize(c); 1110 } 1111 1112 public void removeLayoutComponent(Component c) { } 1113 } 1114 1115 1116 /** 1117 * This class should be treated as a "protected" inner class. 1118 * Instantiate it only within subclasses of {@code BasicOptionPaneUI}. 1119 */ 1120 public class PropertyChangeHandler implements PropertyChangeListener { 1121 /** 1122 * If the source of the PropertyChangeEvent <code>e</code> equals the 1123 * optionPane and is one of the ICON_PROPERTY, MESSAGE_PROPERTY, 1124 * OPTIONS_PROPERTY or INITIAL_VALUE_PROPERTY, 1125 * validateComponent is invoked. 1126 */ 1127 public void propertyChange(PropertyChangeEvent e) { 1128 getHandler().propertyChange(e); 1129 } 1130 } 1131 1132 /** 1133 * Configures any necessary colors/fonts for the specified label 1134 * used representing the message. 1135 */ 1136 private void configureMessageLabel(JLabel label) { 1137 Color color = (Color)DefaultLookup.get(optionPane, this, 1138 "OptionPane.messageForeground"); 1139 if (color != null) { 1140 label.setForeground(color); 1141 } 1142 Font messageFont = (Font)DefaultLookup.get(optionPane, this, 1143 "OptionPane.messageFont"); 1144 if (messageFont != null) { 1145 label.setFont(messageFont); 1146 } 1147 } 1148 1149 /** 1150 * Configures any necessary colors/fonts for the specified button 1151 * used representing the button portion of the optionpane. 1152 */ 1153 private void configureButton(JButton button) { 1154 Font buttonFont = (Font)DefaultLookup.get(optionPane, this, 1155 "OptionPane.buttonFont"); 1156 if (buttonFont != null) { 1157 button.setFont(buttonFont); 1158 } 1159 } 1160 1161 /** 1162 * This class should be treated as a "protected" inner class. 1163 * Instantiate it only within subclasses of {@code BasicOptionPaneUI}. 1164 */ 1165 public class ButtonActionListener implements ActionListener { 1166 protected int buttonIndex; 1167 1168 public ButtonActionListener(int buttonIndex) { 1169 this.buttonIndex = buttonIndex; 1170 } 1171 1172 public void actionPerformed(ActionEvent e) { 1173 if (optionPane != null) { 1174 int optionType = optionPane.getOptionType(); 1175 Object[] options = optionPane.getOptions(); 1176 1177 /* If the option pane takes input, then store the input value 1178 * if custom options were specified, if the option type is 1179 * DEFAULT_OPTION, OR if option type is set to a predefined 1180 * one and the user chose the affirmative answer. 1181 */ 1182 if (inputComponent != null) { 1183 if (options != null || 1184 optionType == JOptionPane.DEFAULT_OPTION || 1185 ((optionType == JOptionPane.YES_NO_OPTION || 1186 optionType == JOptionPane.YES_NO_CANCEL_OPTION || 1187 optionType == JOptionPane.OK_CANCEL_OPTION) && 1188 buttonIndex == 0)) { 1189 resetInputValue(); 1190 } 1191 } 1192 if (options == null) { 1193 if (optionType == JOptionPane.OK_CANCEL_OPTION && 1194 buttonIndex == 1) { 1195 optionPane.setValue(Integer.valueOf(2)); 1196 1197 } else { 1198 optionPane.setValue(Integer.valueOf(buttonIndex)); 1199 } 1200 } else { 1201 optionPane.setValue(options[buttonIndex]); 1202 } 1203 } 1204 } 1205 } 1206 1207 1208 private class Handler implements ActionListener, MouseListener, 1209 PropertyChangeListener { 1210 // 1211 // ActionListener 1212 // 1213 public void actionPerformed(ActionEvent e) { 1214 optionPane.setInputValue(((JTextField)e.getSource()).getText()); 1215 } 1216 1217 1218 // 1219 // MouseListener 1220 // 1221 public void mouseClicked(MouseEvent e) { 1222 } 1223 1224 public void mouseReleased(MouseEvent e) { 1225 } 1226 1227 public void mouseEntered(MouseEvent e) { 1228 } 1229 1230 public void mouseExited(MouseEvent e) { 1231 } 1232 1233 public void mousePressed(MouseEvent e) { 1234 if (e.getClickCount() == 2) { 1235 JList list = (JList)e.getSource(); 1236 int index = list.locationToIndex(e.getPoint()); 1237 1238 optionPane.setInputValue(list.getModel().getElementAt(index)); 1239 optionPane.setValue(JOptionPane.OK_OPTION); 1240 } 1241 } 1242 1243 // 1244 // PropertyChangeListener 1245 // 1246 public void propertyChange(PropertyChangeEvent e) { 1247 if(e.getSource() == optionPane) { 1248 // Option Pane Auditory Cue Activation 1249 // only respond to "ancestor" changes 1250 // the idea being that a JOptionPane gets a JDialog when it is 1251 // set to appear and loses it's JDialog when it is dismissed. 1252 if ("ancestor" == e.getPropertyName()) { 1253 JOptionPane op = (JOptionPane)e.getSource(); 1254 boolean isComingUp; 1255 1256 // if the old value is null, then the JOptionPane is being 1257 // created since it didn't previously have an ancestor. 1258 if (e.getOldValue() == null) { 1259 isComingUp = true; 1260 } else { 1261 isComingUp = false; 1262 } 1263 1264 // figure out what to do based on the message type 1265 switch (op.getMessageType()) { 1266 case JOptionPane.PLAIN_MESSAGE: 1267 if (isComingUp) { 1268 BasicLookAndFeel.playSound(optionPane, 1269 "OptionPane.informationSound"); 1270 } 1271 break; 1272 case JOptionPane.QUESTION_MESSAGE: 1273 if (isComingUp) { 1274 BasicLookAndFeel.playSound(optionPane, 1275 "OptionPane.questionSound"); 1276 } 1277 break; 1278 case JOptionPane.INFORMATION_MESSAGE: 1279 if (isComingUp) { 1280 BasicLookAndFeel.playSound(optionPane, 1281 "OptionPane.informationSound"); 1282 } 1283 break; 1284 case JOptionPane.WARNING_MESSAGE: 1285 if (isComingUp) { 1286 BasicLookAndFeel.playSound(optionPane, 1287 "OptionPane.warningSound"); 1288 } 1289 break; 1290 case JOptionPane.ERROR_MESSAGE: 1291 if (isComingUp) { 1292 BasicLookAndFeel.playSound(optionPane, 1293 "OptionPane.errorSound"); 1294 } 1295 break; 1296 default: 1297 System.err.println("Undefined JOptionPane type: " + 1298 op.getMessageType()); 1299 break; 1300 } 1301 } 1302 // Visual activity 1303 String changeName = e.getPropertyName(); 1304 1305 if(changeName == JOptionPane.OPTIONS_PROPERTY || 1306 changeName == JOptionPane.INITIAL_VALUE_PROPERTY || 1307 changeName == JOptionPane.ICON_PROPERTY || 1308 changeName == JOptionPane.MESSAGE_TYPE_PROPERTY || 1309 changeName == JOptionPane.OPTION_TYPE_PROPERTY || 1310 changeName == JOptionPane.MESSAGE_PROPERTY || 1311 changeName == JOptionPane.SELECTION_VALUES_PROPERTY || 1312 changeName == JOptionPane.INITIAL_SELECTION_VALUE_PROPERTY || 1313 changeName == JOptionPane.WANTS_INPUT_PROPERTY) { 1314 uninstallComponents(); 1315 installComponents(); 1316 optionPane.validate(); 1317 } 1318 else if (changeName == "componentOrientation") { 1319 ComponentOrientation o = (ComponentOrientation)e.getNewValue(); 1320 JOptionPane op = (JOptionPane)e.getSource(); 1321 if (o != e.getOldValue()) { 1322 op.applyComponentOrientation(o); 1323 } 1324 } 1325 } 1326 } 1327 } 1328 1329 1330 // 1331 // Classes used when optionPane.getWantsInput returns true. 1332 // 1333 1334 /** 1335 * A JTextField that allows you to specify an array of KeyStrokes that 1336 * that will have their bindings processed regardless of whether or 1337 * not they are registered on the JTextField. This is used as we really 1338 * want the ActionListener to be notified so that we can push the 1339 * change to the JOptionPane, but we also want additional bindings 1340 * (those of the JRootPane) to be processed as well. 1341 */ 1342 @SuppressWarnings("serial") // Superclass is not serializable across versions 1343 private static class MultiplexingTextField extends JTextField { 1344 private KeyStroke[] strokes; 1345 1346 MultiplexingTextField(int cols) { 1347 super(cols); 1348 } 1349 1350 /** 1351 * Sets the KeyStrokes that will be additional processed for 1352 * ancestor bindings. 1353 */ 1354 void setKeyStrokes(KeyStroke[] strokes) { 1355 this.strokes = strokes; 1356 } 1357 1358 protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, 1359 int condition, boolean pressed) { 1360 boolean processed = super.processKeyBinding(ks, e, condition, 1361 pressed); 1362 1363 if (processed && condition != JComponent.WHEN_IN_FOCUSED_WINDOW) { 1364 for (int counter = strokes.length - 1; counter >= 0; 1365 counter--) { 1366 if (strokes[counter].equals(ks)) { 1367 // Returning false will allow further processing 1368 // of the bindings, eg our parent Containers will get a 1369 // crack at them. 1370 return false; 1371 } 1372 } 1373 } 1374 return processed; 1375 } 1376 } 1377 1378 1379 1380 /** 1381 * Registered in the ActionMap. Sets the value of the option pane 1382 * to <code>JOptionPane.CLOSED_OPTION</code>. 1383 */ 1384 private static class Actions extends UIAction { 1385 private static final String CLOSE = "close"; 1386 1387 Actions(String key) { 1388 super(key); 1389 } 1390 1391 public void actionPerformed(ActionEvent e) { 1392 if (getName() == CLOSE) { 1393 JOptionPane optionPane = (JOptionPane)e.getSource(); 1394 1395 optionPane.setValue(Integer.valueOf(JOptionPane.CLOSED_OPTION)); 1396 } 1397 } 1398 } 1399 1400 1401 /** 1402 * This class is used to create the default buttons. This indirection is 1403 * used so that addButtonComponents can tell which Buttons were created 1404 * by us vs subclassers or from the JOptionPane itself. 1405 */ 1406 private static class ButtonFactory { 1407 private String text; 1408 private int mnemonic; 1409 private Icon icon; 1410 private int minimumWidth = -1; 1411 1412 ButtonFactory(String text, int mnemonic, Icon icon, int minimumWidth) { 1413 this.text = text; 1414 this.mnemonic = mnemonic; 1415 this.icon = icon; 1416 this.minimumWidth = minimumWidth; 1417 } 1418 1419 JButton createButton() { 1420 JButton button; 1421 1422 if (minimumWidth > 0) { 1423 button = new ConstrainedButton(text, minimumWidth); 1424 } else { 1425 button = new JButton(text); 1426 } 1427 if (icon != null) { 1428 button.setIcon(icon); 1429 } 1430 if (mnemonic != 0) { 1431 button.setMnemonic(mnemonic); 1432 } 1433 return button; 1434 } 1435 1436 @SuppressWarnings("serial") // Superclass is not serializable across versions 1437 private static class ConstrainedButton extends JButton { 1438 int minimumWidth; 1439 1440 ConstrainedButton(String text, int minimumWidth) { 1441 super(text); 1442 this.minimumWidth = minimumWidth; 1443 } 1444 1445 public Dimension getMinimumSize() { 1446 Dimension min = super.getMinimumSize(); 1447 min.width = Math.max(min.width, minimumWidth); 1448 return min; 1449 } 1450 1451 public Dimension getPreferredSize() { 1452 Dimension pref = super.getPreferredSize(); 1453 pref.width = Math.max(pref.width, minimumWidth); 1454 return pref; 1455 } 1456 } 1457 } 1458 }