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