1. /*
  2. * @(#)BasicOptionPaneUI.java 1.55 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.plaf.basic;
  8. import javax.swing.border.Border;
  9. import javax.swing.border.EmptyBorder;
  10. import javax.swing.*;
  11. import javax.swing.event.*;
  12. import javax.swing.plaf.ActionMapUIResource;
  13. import javax.swing.plaf.ComponentUI;
  14. import javax.swing.plaf.OptionPaneUI;
  15. import java.awt.*;
  16. import java.awt.event.*;
  17. import java.beans.PropertyChangeEvent;
  18. import java.beans.PropertyChangeListener;
  19. import java.util.Locale;
  20. import java.security.AccessController;
  21. /**
  22. * Provides the basic look and feel for a <code>JOptionPane</code>.
  23. * <code>BasicMessagePaneUI</code> provides a means to place an icon,
  24. * message and buttons into a <code>Container</code>.
  25. * Generally, the layout will look like:<p>
  26. * <pre>
  27. * ------------------
  28. * | i | message |
  29. * | c | message |
  30. * | o | message |
  31. * | n | message |
  32. * ------------------
  33. * | buttons |
  34. * |________________|
  35. * </pre>
  36. * icon is an instance of <code>Icon</code> that is wrapped inside a
  37. * <code>JLabel</code>. The message is an opaque object and is tested
  38. * for the following: if the message is a <code>Component</code> it is
  39. * added to the <code>Container</code>, if it is an <code>Icon</code>
  40. * it is wrapped inside a <code>JLabel</code> and added to the
  41. * <code>Container</code> otherwise it is wrapped inside a <code>JLabel</code>.
  42. * <p>
  43. * The above layout is used when the option pane's
  44. * <code>ComponentOrientation</code> property is horizontal, left-to-right.
  45. * The layout will be adjusted appropriately for other orientations.
  46. * <p>
  47. * The <code>Container</code>, message, icon, and buttons are all
  48. * determined from abstract methods.
  49. *
  50. * @version 1.55 01/23/03
  51. * @author James Gosling
  52. * @author Scott Violet
  53. * @author Amy Fowler
  54. */
  55. public class BasicOptionPaneUI extends OptionPaneUI {
  56. public static final int MinimumWidth = 262;
  57. public static final int MinimumHeight = 90;
  58. private static String newline;
  59. /**
  60. * The mnemonics for the keys, these are set in <code>getButtons</code>.
  61. */
  62. private int[] mnemonics;
  63. /**
  64. * <code>JOptionPane</code> that the receiver is providing the
  65. * look and feel for.
  66. */
  67. protected JOptionPane optionPane;
  68. protected Dimension minimumSize;
  69. /** JComponent provide for input if optionPane.getWantsInput() returns
  70. * true. */
  71. protected JComponent inputComponent;
  72. /** Component to receive focus when messaged with selectInitialValue. */
  73. protected Component initialFocusComponent;
  74. /** This is set to true in validateComponent if a Component is contained
  75. * in either the message or the buttons. */
  76. protected boolean hasCustomComponents;
  77. protected PropertyChangeListener propertyChangeListener;
  78. static {
  79. java.security.AccessController.doPrivileged(
  80. new java.security.PrivilegedAction() {
  81. public Object run() {
  82. newline = System.getProperty("line.separator");
  83. if (newline == null) {
  84. newline = "\n";
  85. }
  86. return null;
  87. }
  88. }
  89. );
  90. }
  91. /**
  92. * Creates a new BasicOptionPaneUI instance.
  93. */
  94. public static ComponentUI createUI(JComponent x) {
  95. return new BasicOptionPaneUI();
  96. }
  97. /**
  98. * Installs the receiver as the L&F for the passed in
  99. * <code>JOptionPane</code>.
  100. */
  101. public void installUI(JComponent c) {
  102. optionPane = (JOptionPane)c;
  103. installDefaults();
  104. optionPane.setLayout(createLayoutManager());
  105. installComponents();
  106. installListeners();
  107. installKeyboardActions();
  108. }
  109. /**
  110. * Removes the receiver from the L&F controller of the passed in split
  111. * pane.
  112. */
  113. public void uninstallUI(JComponent c) {
  114. uninstallComponents();
  115. optionPane.setLayout(null);
  116. uninstallKeyboardActions();
  117. uninstallListeners();
  118. uninstallDefaults();
  119. optionPane = null;
  120. }
  121. protected void installDefaults() {
  122. LookAndFeel.installColorsAndFont(optionPane, "OptionPane.background",
  123. "OptionPane.foreground", "OptionPane.font");
  124. LookAndFeel.installBorder(optionPane, "OptionPane.border");
  125. minimumSize = UIManager.getDimension("OptionPane.minimumSize");
  126. optionPane.setOpaque(true);
  127. }
  128. protected void uninstallDefaults() {
  129. LookAndFeel.uninstallBorder(optionPane);
  130. }
  131. protected void installComponents() {
  132. optionPane.add(createMessageArea());
  133. Container separator = createSeparator();
  134. if (separator != null) {
  135. optionPane.add(separator);
  136. }
  137. optionPane.add(createButtonArea());
  138. optionPane.applyComponentOrientation(optionPane.getComponentOrientation());
  139. }
  140. protected void uninstallComponents() {
  141. hasCustomComponents = false;
  142. inputComponent = null;
  143. initialFocusComponent = null;
  144. optionPane.removeAll();
  145. }
  146. protected LayoutManager createLayoutManager() {
  147. return new BoxLayout(optionPane, BoxLayout.Y_AXIS);
  148. }
  149. protected void installListeners() {
  150. if ((propertyChangeListener = createPropertyChangeListener()) != null) {
  151. optionPane.addPropertyChangeListener(propertyChangeListener);
  152. }
  153. }
  154. protected void uninstallListeners() {
  155. if (propertyChangeListener != null) {
  156. optionPane.removePropertyChangeListener(propertyChangeListener);
  157. propertyChangeListener = null;
  158. }
  159. }
  160. protected PropertyChangeListener createPropertyChangeListener() {
  161. return new PropertyChangeHandler();
  162. }
  163. protected void installKeyboardActions() {
  164. InputMap map = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
  165. SwingUtilities.replaceUIInputMap(optionPane, JComponent.
  166. WHEN_IN_FOCUSED_WINDOW, map);
  167. ActionMap actionMap = getActionMap();
  168. SwingUtilities.replaceUIActionMap(optionPane, actionMap);
  169. }
  170. protected void uninstallKeyboardActions() {
  171. SwingUtilities.replaceUIInputMap(optionPane, JComponent.
  172. WHEN_IN_FOCUSED_WINDOW, null);
  173. SwingUtilities.replaceUIActionMap(optionPane, null);
  174. }
  175. InputMap getInputMap(int condition) {
  176. if (condition == JComponent.WHEN_IN_FOCUSED_WINDOW) {
  177. Object[] bindings = (Object[])UIManager.get
  178. ("OptionPane.windowBindings");
  179. if (bindings != null) {
  180. return LookAndFeel.makeComponentInputMap(optionPane, bindings);
  181. }
  182. }
  183. return null;
  184. }
  185. ActionMap getActionMap() {
  186. ActionMap map = (ActionMap)UIManager.get("OptionPane.actionMap");
  187. if (map == null) {
  188. map = createActionMap();
  189. if (map != null) {
  190. UIManager.getLookAndFeelDefaults().put("OptionPane.actionMap",
  191. map);
  192. }
  193. }
  194. return map;
  195. }
  196. ActionMap createActionMap() {
  197. ActionMap map = new ActionMapUIResource();
  198. map.put("close", new CloseAction());
  199. // Set the ActionMap's parent to the Auditory Feedback Action Map
  200. BasicLookAndFeel lf = (BasicLookAndFeel)UIManager.getLookAndFeel();
  201. ActionMap audioMap = lf.getAudioActionMap();
  202. map.setParent(audioMap);
  203. return map;
  204. }
  205. /**
  206. * Returns the minimum size the option pane should be. Primarily
  207. * provided for subclassers wishing to offer a different minimum size.
  208. */
  209. public Dimension getMinimumOptionPaneSize() {
  210. if (minimumSize == null) {
  211. //minimumSize = UIManager.getDimension("OptionPane.minimumSize");
  212. // this is called before defaults initialized?!!!
  213. return new Dimension(MinimumWidth, MinimumHeight);
  214. }
  215. return new Dimension(minimumSize.width,
  216. minimumSize.height);
  217. }
  218. /**
  219. * If <code>c</code> is the <code>JOptionPane</code> the receiver
  220. * is contained in, the preferred
  221. * size that is returned is the maximum of the preferred size of
  222. * the <code>LayoutManager</code> for the <code>JOptionPane</code>, and
  223. * <code>getMinimumOptionPaneSize</code>.
  224. */
  225. public Dimension getPreferredSize(JComponent c) {
  226. if ((JOptionPane)c == optionPane) {
  227. Dimension ourMin = getMinimumOptionPaneSize();
  228. LayoutManager lm = c.getLayout();
  229. if (lm != null) {
  230. Dimension lmSize = lm.preferredLayoutSize(c);
  231. if (ourMin != null)
  232. return new Dimension
  233. (Math.max(lmSize.width, ourMin.width),
  234. Math.max(lmSize.height, ourMin.height));
  235. return lmSize;
  236. }
  237. return ourMin;
  238. }
  239. return null;
  240. }
  241. /**
  242. * Messages getPreferredSize.
  243. */
  244. public Dimension getMinimumSize(JComponent c) {
  245. return getPreferredSize(c);
  246. }
  247. /**
  248. * Messages getPreferredSize.
  249. */
  250. public Dimension getMaximumSize(JComponent c) {
  251. return getPreferredSize(c);
  252. }
  253. /**
  254. * Messaged from installComponents to create a Container containing the
  255. * body of the message. The icon is the created by calling
  256. * <code>addIcon</code>.
  257. */
  258. protected Container createMessageArea() {
  259. JPanel top = new JPanel();
  260. top.setBorder(UIManager.getBorder("OptionPane.messageAreaBorder"));
  261. top.setLayout(new BorderLayout());
  262. /* Fill the body. */
  263. Container body = new JPanel() {};
  264. Container realBody = new JPanel() {};
  265. realBody.setLayout(new BorderLayout());
  266. if (getIcon() != null) {
  267. Container sep = new JPanel() {
  268. public Dimension getPreferredSize() {
  269. return new Dimension(15, 1);
  270. }
  271. };
  272. realBody.add(sep, BorderLayout.BEFORE_LINE_BEGINS);
  273. }
  274. realBody.add(body, BorderLayout.CENTER);
  275. body.setLayout(new GridBagLayout());
  276. GridBagConstraints cons = new GridBagConstraints();
  277. cons.gridx = cons.gridy = 0;
  278. cons.gridwidth = GridBagConstraints.REMAINDER;
  279. cons.gridheight = 1;
  280. cons.anchor = GridBagConstraints.LINE_START;
  281. cons.insets = new Insets(0,0,3,0);
  282. addMessageComponents(body, cons, getMessage(),
  283. getMaxCharactersPerLineCount(), false);
  284. top.add(realBody, BorderLayout.CENTER);
  285. addIcon(top);
  286. return top;
  287. }
  288. /**
  289. * Creates the appropriate object to represent <code>msg</code> and
  290. * places it into <code>container</code>. If <code>msg</code> is an
  291. * instance of Component, it is added directly, if it is an Icon,
  292. * a JLabel is created to represent it, otherwise a JLabel is
  293. * created for the string, if <code>d</code> is an Object[], this
  294. * method will be recursively invoked for the children.
  295. * <code>internallyCreated</code> is true if Objc is an instance
  296. * of Component and was created internally by this method (this is
  297. * used to correctly set hasCustomComponents only if !internallyCreated).
  298. */
  299. protected void addMessageComponents(Container container,
  300. GridBagConstraints cons,
  301. Object msg, int maxll,
  302. boolean internallyCreated) {
  303. if (msg == null) {
  304. return;
  305. }
  306. if (msg instanceof Component) {
  307. // To workaround problem where Gridbad will set child
  308. // to its minimum size if its preferred size will not fit
  309. // within allocated cells
  310. if (msg instanceof JScrollPane || msg instanceof JPanel) {
  311. cons.fill = GridBagConstraints.BOTH;
  312. cons.weighty = 1;
  313. } else {
  314. cons.fill = GridBagConstraints.HORIZONTAL;
  315. }
  316. cons.weightx = 1;
  317. container.add((Component) msg, cons);
  318. cons.weightx = 0;
  319. cons.weighty = 0;
  320. cons.fill = GridBagConstraints.NONE;
  321. cons.gridy++;
  322. if (!internallyCreated) {
  323. hasCustomComponents = true;
  324. }
  325. } else if (msg instanceof Object[]) {
  326. Object [] msgs = (Object[]) msg;
  327. for (int i = 0; i < msgs.length; i++) {
  328. addMessageComponents(container, cons, msgs[i], maxll, false);
  329. }
  330. } else if (msg instanceof Icon) {
  331. JLabel label = new JLabel( (Icon)msg, SwingConstants.CENTER );
  332. configureMessageLabel(label);
  333. addMessageComponents(container, cons, label, maxll, true);
  334. } else {
  335. String s = msg.toString();
  336. int len = s.length();
  337. if (len <= 0) {
  338. return;
  339. }
  340. int nl = -1;
  341. int nll = 0;
  342. if ((nl = s.indexOf(newline)) >= 0) {
  343. nll = newline.length();
  344. } else if ((nl = s.indexOf("\r\n")) >= 0) {
  345. nll = 2;
  346. } else if ((nl = s.indexOf('\n')) >= 0) {
  347. nll = 1;
  348. }
  349. if (nl >= 0) {
  350. // break up newlines
  351. if (nl == 0) {
  352. addMessageComponents(container, cons, new Component() {
  353. public Dimension getPreferredSize() {
  354. Font f = getFont();
  355. if (f != null) {
  356. return new Dimension(1, f.getSize() + 2);
  357. }
  358. return new Dimension(0, 0);
  359. }
  360. }, maxll, true);
  361. } else {
  362. addMessageComponents(container, cons, s.substring(0, nl),
  363. maxll, false);
  364. }
  365. addMessageComponents(container, cons, s.substring(nl + nll), maxll,
  366. false);
  367. } else if (len > maxll) {
  368. Container c = Box.createVerticalBox();
  369. burstStringInto(c, s, maxll);
  370. addMessageComponents(container, cons, c, maxll, true );
  371. } else {
  372. JLabel label;
  373. label = new JLabel( s, JLabel.LEADING );
  374. configureMessageLabel(label);
  375. addMessageComponents(container, cons, label, maxll, true);
  376. }
  377. }
  378. }
  379. /**
  380. * Returns the message to display from the JOptionPane the receiver is
  381. * providing the look and feel for.
  382. */
  383. protected Object getMessage() {
  384. inputComponent = null;
  385. if (optionPane != null) {
  386. if (optionPane.getWantsInput()) {
  387. /* Create a user component to capture the input. If the
  388. selectionValues are non null the component and there
  389. are < 20 values it'll be a combobox, if non null and
  390. >= 20, it'll be a list, otherwise it'll be a textfield. */
  391. Object message = optionPane.getMessage();
  392. Object[] sValues = optionPane.getSelectionValues();
  393. Object inputValue = optionPane
  394. .getInitialSelectionValue();
  395. JComponent toAdd;
  396. if (sValues != null) {
  397. if (sValues.length < 20) {
  398. JComboBox cBox = new JComboBox();
  399. for(int counter = 0, maxCounter = sValues.length;
  400. counter < maxCounter; counter++) {
  401. cBox.addItem(sValues[counter]);
  402. }
  403. if (inputValue != null) {
  404. cBox.setSelectedItem(inputValue);
  405. }
  406. inputComponent = cBox;
  407. toAdd = cBox;
  408. } else {
  409. JList list = new JList(sValues);
  410. JScrollPane sp = new JScrollPane(list);
  411. list.setVisibleRowCount(10);
  412. list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
  413. if(inputValue != null)
  414. list.setSelectedValue(inputValue, true);
  415. list.addMouseListener(new ListSelectionListener());
  416. toAdd = sp;
  417. inputComponent = list;
  418. }
  419. } else {
  420. MultiplexingTextField tf = new MultiplexingTextField(20);
  421. tf.setKeyStrokes(new KeyStroke[] {
  422. KeyStroke.getKeyStroke("ENTER") } );
  423. if (inputValue != null) {
  424. String inputString = inputValue.toString();
  425. tf.setText(inputString);
  426. tf.setSelectionStart(0);
  427. tf.setSelectionEnd(inputString.length());
  428. }
  429. tf.addActionListener(new TextFieldActionListener());
  430. toAdd = inputComponent = tf;
  431. }
  432. Object[] newMessage;
  433. if (message == null) {
  434. newMessage = new Object[1];
  435. newMessage[0] = toAdd;
  436. } else {
  437. newMessage = new Object[2];
  438. newMessage[0] = message;
  439. newMessage[1] = toAdd;
  440. }
  441. return newMessage;
  442. }
  443. return optionPane.getMessage();
  444. }
  445. return null;
  446. }
  447. /**
  448. * Creates and adds a JLabel representing the icon returned from
  449. * <code>getIcon</code> to <code>top</code>. This is messaged from
  450. * <code>createMessageArea</code>
  451. */
  452. protected void addIcon(Container top) {
  453. /* Create the icon. */
  454. Icon sideIcon = getIcon();
  455. if (sideIcon != null) {
  456. JLabel iconLabel = new JLabel(sideIcon);
  457. iconLabel.setVerticalAlignment(SwingConstants.TOP);
  458. top.add(iconLabel, BorderLayout.BEFORE_LINE_BEGINS);
  459. }
  460. }
  461. /**
  462. * Returns the icon from the JOptionPane the receiver is providing
  463. * the look and feel for, or the default icon as returned from
  464. * <code>getDefaultIcon</code>.
  465. */
  466. protected Icon getIcon() {
  467. Icon mIcon = (optionPane == null ? null : optionPane.getIcon());
  468. if(mIcon == null && optionPane != null)
  469. mIcon = getIconForType(optionPane.getMessageType());
  470. return mIcon;
  471. }
  472. /**
  473. * Returns the icon to use for the passed in type.
  474. */
  475. protected Icon getIconForType(int messageType) {
  476. if(messageType < 0 || messageType > 3)
  477. return null;
  478. switch(messageType) {
  479. case 0:
  480. return UIManager.getIcon("OptionPane.errorIcon");
  481. case 1:
  482. return UIManager.getIcon("OptionPane.informationIcon");
  483. case 2:
  484. return UIManager.getIcon("OptionPane.warningIcon");
  485. case 3:
  486. return UIManager.getIcon("OptionPane.questionIcon");
  487. }
  488. return null;
  489. }
  490. /**
  491. * Returns the maximum number of characters to place on a line.
  492. */
  493. protected int getMaxCharactersPerLineCount() {
  494. return optionPane.getMaxCharactersPerLineCount();
  495. }
  496. /**
  497. * Recursively creates new JLabel instances to represent <code>d</code>.
  498. * Each JLabel instance is added to <code>c</code>.
  499. */
  500. protected void burstStringInto(Container c, String d, int maxll) {
  501. // Primitive line wrapping
  502. int len = d.length();
  503. if (len <= 0)
  504. return;
  505. if (len > maxll) {
  506. int p = d.lastIndexOf(' ', maxll);
  507. if (p <= 0)
  508. p = d.indexOf(' ', maxll);
  509. if (p > 0 && p < len) {
  510. burstStringInto(c, d.substring(0, p), maxll);
  511. burstStringInto(c, d.substring(p + 1), maxll);
  512. return;
  513. }
  514. }
  515. JLabel label = new JLabel(d, JLabel.LEFT);
  516. configureMessageLabel(label);
  517. c.add(label);
  518. }
  519. protected Container createSeparator() {
  520. return null;
  521. }
  522. /**
  523. * Creates and returns a Container containing the buttons. The buttons
  524. * are created by calling <code>getButtons</code>.
  525. */
  526. protected Container createButtonArea() {
  527. JPanel bottom = new JPanel();
  528. bottom.setBorder(UIManager.getBorder("OptionPane.buttonAreaBorder"));
  529. bottom.setLayout(new ButtonAreaLayout(true, 6));
  530. addButtonComponents(bottom, getButtons(), getInitialValueIndex());
  531. mnemonics = null;
  532. return bottom;
  533. }
  534. /**
  535. * Creates the appropriate object to represent each of the objects in
  536. * <code>buttons</code> and adds it to <code>container</code>. This
  537. * differs from addMessageComponents in that it will recurse on
  538. * <code>buttons</code> and that if button is not a Component
  539. * it will create an instance of JButton.
  540. */
  541. protected void addButtonComponents(Container container, Object[] buttons,
  542. int initialIndex) {
  543. if (buttons != null && buttons.length > 0) {
  544. boolean sizeButtonsToSame = getSizeButtonsToSameWidth();
  545. boolean createdAll = true;
  546. int numButtons = buttons.length;
  547. JButton[] createdButtons = null;
  548. int maxWidth = 0;
  549. int[] mnemonics = this.mnemonics;
  550. if (mnemonics != null && mnemonics.length != buttons.length) {
  551. mnemonics = null;
  552. }
  553. if (sizeButtonsToSame) {
  554. createdButtons = new JButton[numButtons];
  555. }
  556. for(int counter = 0; counter < numButtons; counter++) {
  557. Object button = buttons[counter];
  558. Component newComponent;
  559. if (button instanceof Component) {
  560. createdAll = false;
  561. newComponent = (Component)button;
  562. container.add(newComponent);
  563. hasCustomComponents = true;
  564. } else {
  565. JButton aButton;
  566. if (button instanceof Icon)
  567. aButton = new JButton((Icon)button);
  568. else
  569. aButton = new JButton(button.toString());
  570. aButton.setMultiClickThreshhold(UIManager.getInt("OptionPane.buttonClickThreshhold"));
  571. configureButton(aButton);
  572. container.add(aButton);
  573. ActionListener buttonListener = createButtonActionListener(counter);
  574. if (buttonListener != null) {
  575. aButton.addActionListener(buttonListener);
  576. }
  577. newComponent = aButton;
  578. if (mnemonics != null) {
  579. aButton.setMnemonic(mnemonics[counter]);
  580. }
  581. }
  582. if (sizeButtonsToSame && createdAll &&
  583. (newComponent instanceof JButton)) {
  584. createdButtons[counter] = (JButton)newComponent;
  585. maxWidth = Math.max(maxWidth,
  586. newComponent.getMinimumSize().width);
  587. }
  588. if (counter == initialIndex) {
  589. initialFocusComponent = newComponent;
  590. if (initialFocusComponent instanceof JButton) {
  591. JButton defaultB = (JButton)initialFocusComponent;
  592. defaultB.addAncestorListener(new AncestorListener() {
  593. public void ancestorAdded(AncestorEvent e) {
  594. JButton defaultButton = (JButton)e.getComponent();
  595. JRootPane root = SwingUtilities.getRootPane(defaultButton);
  596. if (root != null) {
  597. root.setDefaultButton(defaultButton);
  598. }
  599. }
  600. public void ancestorRemoved(AncestorEvent event) {}
  601. public void ancestorMoved(AncestorEvent event) {}
  602. });
  603. }
  604. }
  605. }
  606. ((ButtonAreaLayout)container.getLayout()).
  607. setSyncAllWidths((sizeButtonsToSame && createdAll));
  608. /* Set the padding, windows seems to use 8 if <= 2 components,
  609. otherwise 4 is used. It may actually just be the size of the
  610. buttons is always the same, not sure. */
  611. if (sizeButtonsToSame && createdAll) {
  612. JButton aButton;
  613. int padSize;
  614. padSize = (numButtons <= 2? 8 : 4);
  615. for(int counter = 0; counter < numButtons; counter++) {
  616. aButton = createdButtons[counter];
  617. aButton.setMargin(new Insets(2, padSize, 2, padSize));
  618. }
  619. }
  620. }
  621. }
  622. protected ActionListener createButtonActionListener(int buttonIndex) {
  623. return new ButtonActionListener(buttonIndex);
  624. }
  625. /**
  626. * Returns the buttons to display from the JOptionPane the receiver is
  627. * providing the look and feel for. If the JOptionPane has options
  628. * set, they will be provided, otherwise if the optionType is
  629. * YES_NO_OPTION, yesNoOptions is returned, if the type is
  630. * YES_NO_CANCEL_OPTION yesNoCancelOptions is returned, otherwise
  631. * defaultButtons are returned.
  632. */
  633. protected Object[] getButtons() {
  634. if (optionPane != null) {
  635. Object[] suppliedOptions = optionPane.getOptions();
  636. if (suppliedOptions == null) {
  637. Object[] defaultOptions;
  638. int type = optionPane.getOptionType();
  639. Locale l = optionPane.getLocale();
  640. if (type == JOptionPane.YES_NO_OPTION) {
  641. defaultOptions = new String[2];
  642. defaultOptions[0] = UIManager.get("OptionPane.yesButtonText",l);
  643. defaultOptions[1] = UIManager.get("OptionPane.noButtonText",l);
  644. mnemonics = new int[2];
  645. mnemonics[0] = getMnemonic("OptionPane.yesButtonMnemonic", l);
  646. mnemonics[1] = getMnemonic("OptionPane.noButtonMnemonic", l);
  647. } else if (type == JOptionPane.YES_NO_CANCEL_OPTION) {
  648. defaultOptions = new String[3];
  649. defaultOptions[0] = UIManager.get("OptionPane.yesButtonText",l);
  650. defaultOptions[1] = UIManager.get("OptionPane.noButtonText",l);
  651. defaultOptions[2] = UIManager.get("OptionPane.cancelButtonText",l);
  652. mnemonics = new int[3];
  653. mnemonics[0] = getMnemonic("OptionPane.yesButtonMnemonic", l);
  654. mnemonics[1] = getMnemonic("OptionPane.noButtonMnemonic", l);
  655. mnemonics[2] = getMnemonic("OptionPane.cancelButtonMnemonic", l);
  656. } else if (type == JOptionPane.OK_CANCEL_OPTION) {
  657. defaultOptions = new String[2];
  658. defaultOptions[0] = UIManager.get("OptionPane.okButtonText",l);
  659. defaultOptions[1] = UIManager.get("OptionPane.cancelButtonText",l);
  660. mnemonics = new int[2];
  661. mnemonics[0] = getMnemonic("OptionPane.okButtonMnemonic", l);
  662. mnemonics[1] = getMnemonic("OptionPane.cancelButtonMnemonic", l);
  663. } else {
  664. defaultOptions = new String[1];
  665. defaultOptions[0] = UIManager.get("OptionPane.okButtonText",l);
  666. mnemonics = new int[1];
  667. mnemonics[0] = getMnemonic("OptionPane.okButtonMnemonic", l);
  668. }
  669. return defaultOptions;
  670. }
  671. return suppliedOptions;
  672. }
  673. return null;
  674. }
  675. /**
  676. * Returns the mnemonic for the passed in key.
  677. */
  678. private int getMnemonic(String key, Locale l) {
  679. String value = (String)UIManager.get(key, l);
  680. if (value == null) {
  681. return 0;
  682. }
  683. try {
  684. return Integer.parseInt(value);
  685. }
  686. catch (NumberFormatException nfe) { }
  687. return 0;
  688. }
  689. /**
  690. * Returns true, basic L&F wants all the buttons to have the same
  691. * width.
  692. */
  693. protected boolean getSizeButtonsToSameWidth() {
  694. return true;
  695. }
  696. /**
  697. * Returns the initial index into the buttons to select. The index
  698. * is calculated from the initial value from the JOptionPane and
  699. * options of the JOptionPane or 0.
  700. */
  701. protected int getInitialValueIndex() {
  702. if (optionPane != null) {
  703. Object iv = optionPane.getInitialValue();
  704. Object[] options = optionPane.getOptions();
  705. if(options == null) {
  706. return 0;
  707. }
  708. else if(iv != null) {
  709. for(int counter = options.length - 1; counter >= 0; counter--){
  710. if(options[counter].equals(iv))
  711. return counter;
  712. }
  713. }
  714. }
  715. return -1;
  716. }
  717. /**
  718. * Sets the input value in the option pane the receiver is providing
  719. * the look and feel for based on the value in the inputComponent.
  720. */
  721. protected void resetInputValue() {
  722. if(inputComponent != null && (inputComponent instanceof JTextField)) {
  723. optionPane.setInputValue(((JTextField)inputComponent).getText());
  724. } else if(inputComponent != null &&
  725. (inputComponent instanceof JComboBox)) {
  726. optionPane.setInputValue(((JComboBox)inputComponent)
  727. .getSelectedItem());
  728. } else if(inputComponent != null) {
  729. optionPane.setInputValue(((JList)inputComponent)
  730. .getSelectedValue());
  731. }
  732. }
  733. /**
  734. * If inputComponent is non-null, the focus is requested on that,
  735. * otherwise request focus on the default value
  736. */
  737. public void selectInitialValue(JOptionPane op) {
  738. if (inputComponent != null)
  739. inputComponent.requestFocus();
  740. else {
  741. if (initialFocusComponent != null)
  742. initialFocusComponent.requestFocus();
  743. if (initialFocusComponent instanceof JButton) {
  744. JRootPane root = SwingUtilities.getRootPane(initialFocusComponent);
  745. if (root != null) {
  746. root.setDefaultButton((JButton)initialFocusComponent);
  747. }
  748. }
  749. }
  750. }
  751. /**
  752. * Returns true if in the last call to validateComponent the message
  753. * or buttons contained a subclass of Component.
  754. */
  755. public boolean containsCustomComponents(JOptionPane op) {
  756. return hasCustomComponents;
  757. }
  758. /**
  759. * <code>ButtonAreaLayout</code> behaves in a similar manner to
  760. * <code>FlowLayout</code>. It lays out all components from left to
  761. * right. If <code>syncAllWidths</code> is true, the widths of each
  762. * component will be set to the largest preferred size width.
  763. *
  764. * This inner class is marked "public" due to a compiler bug.
  765. * This class should be treated as a "protected" inner class.
  766. * Instantiate it only within subclasses of BasicOptionPaneUI.
  767. */
  768. public static class ButtonAreaLayout implements LayoutManager {
  769. protected boolean syncAllWidths;
  770. protected int padding;
  771. /** If true, children are lumped together in parent. */
  772. protected boolean centersChildren;
  773. public ButtonAreaLayout(boolean syncAllWidths, int padding) {
  774. this.syncAllWidths = syncAllWidths;
  775. this.padding = padding;
  776. centersChildren = true;
  777. }
  778. public void setSyncAllWidths(boolean newValue) {
  779. syncAllWidths = newValue;
  780. }
  781. public boolean getSyncAllWidths() {
  782. return syncAllWidths;
  783. }
  784. public void setPadding(int newPadding) {
  785. this.padding = newPadding;
  786. }
  787. public int getPadding() {
  788. return padding;
  789. }
  790. public void setCentersChildren(boolean newValue) {
  791. centersChildren = newValue;
  792. }
  793. public boolean getCentersChildren() {
  794. return centersChildren;
  795. }
  796. public void addLayoutComponent(String string, Component comp) {
  797. }
  798. public void layoutContainer(Container container) {
  799. Component[] children = container.getComponents();
  800. if(children != null && children.length > 0) {
  801. int numChildren = children.length;
  802. Dimension[] sizes = new Dimension[numChildren];
  803. Insets insets = container.getInsets();
  804. int counter;
  805. int yLocation = insets.top;
  806. boolean ltr = container.getComponentOrientation().isLeftToRight();
  807. if(syncAllWidths) {
  808. int maxWidth = 0;
  809. for(counter = 0; counter < numChildren; counter++) {
  810. sizes[counter] = children[counter].getPreferredSize();
  811. maxWidth = Math.max(maxWidth, sizes[counter].width);
  812. }
  813. int xLocation;
  814. int xOffset;
  815. if(getCentersChildren()) {
  816. xLocation = (container.getSize().width - insets.left - insets.right -
  817. (maxWidth * numChildren +
  818. (numChildren - 1) * padding)) / 2;
  819. xOffset = padding + maxWidth;
  820. }
  821. else {
  822. if(numChildren > 1) {
  823. xLocation = insets.left;
  824. xOffset = (container.getSize().width - insets.left - insets.right -
  825. (maxWidth * numChildren)) /
  826. (numChildren - 1) + maxWidth;
  827. }
  828. else {
  829. xLocation = insets.left +
  830. (container.getSize().width -
  831. insets.left - insets.right -
  832. maxWidth) / 2;
  833. xOffset = 0;
  834. }
  835. }
  836. // If right to left layout then adjust xLocation and
  837. // xOffset to start at the right side of the container
  838. // and move left.
  839. if( !ltr ) {
  840. xLocation = container.getSize().width - insets.right
  841. - (xLocation - insets.left) - maxWidth;
  842. xOffset = -xOffset;
  843. }
  844. for(counter = 0; counter < numChildren; counter++) {
  845. children[counter].setBounds(xLocation, yLocation,
  846. maxWidth,
  847. sizes[counter].height);
  848. xLocation += xOffset;
  849. }
  850. }
  851. else {
  852. int totalWidth = 0;
  853. for(counter = 0; counter < numChildren; counter++) {
  854. sizes[counter] = children[counter].getPreferredSize();
  855. totalWidth += sizes[counter].width;
  856. }
  857. totalWidth += ((numChildren - 1) * padding);
  858. boolean cc = getCentersChildren();
  859. int xOffset;
  860. int xLocation;
  861. if(cc) {
  862. xLocation = insets.left +
  863. (container.getSize().width - insets.left - insets.right -
  864. totalWidth) / 2;
  865. xOffset = padding;
  866. }
  867. else {
  868. if(numChildren > 1) {
  869. xOffset = (container.getSize().width - insets.left - insets.right -
  870. totalWidth) / (numChildren - 1);
  871. xLocation = insets.left;
  872. }
  873. else {
  874. xLocation = insets.left +
  875. (container.getSize().width - insets.left - insets.right -
  876. totalWidth) / 2;
  877. xOffset = 0;
  878. }
  879. }
  880. if( ltr ) {
  881. for(counter = 0; counter < numChildren; counter++) {
  882. children[counter].setBounds(xLocation, yLocation,
  883. sizes[counter].width, sizes[counter].height);
  884. xLocation += xOffset + sizes[counter].width;
  885. }
  886. } else {
  887. // If right to left layout then adjust xLocation to
  888. // start at the right side of the container.
  889. xLocation = container.getSize().width - insets.right
  890. - (xLocation - insets.left);
  891. for(counter = 0; counter < numChildren; counter++) {
  892. xLocation -= xOffset + sizes[counter].width;
  893. children[counter].setBounds(xLocation, yLocation,
  894. sizes[counter].width, sizes[counter].height);
  895. }
  896. }
  897. }
  898. }
  899. }
  900. public Dimension minimumLayoutSize(Container c) {
  901. if(c != null) {
  902. Component[] children = c.getComponents();
  903. if(children != null && children.length > 0) {
  904. Dimension aSize;
  905. int numChildren = children.length;
  906. int height = 0;
  907. Insets cInsets = c.getInsets();
  908. int extraHeight = cInsets.top + cInsets.bottom;
  909. int extraWidth = cInsets.left + cInsets.right;
  910. if (syncAllWidths) {
  911. int maxWidth = 0;
  912. for(int counter = 0; counter < numChildren; counter++){
  913. aSize = children[counter].getPreferredSize();
  914. height = Math.max(height, aSize.height);
  915. maxWidth = Math.max(maxWidth, aSize.width);
  916. }
  917. return new Dimension(extraWidth + (maxWidth * numChildren) +
  918. (numChildren - 1) * padding,
  919. extraHeight + height);
  920. }
  921. else {
  922. int totalWidth = 0;
  923. for(int counter = 0; counter < numChildren; counter++){
  924. aSize = children[counter].getPreferredSize();
  925. height = Math.max(height, aSize.height);
  926. totalWidth += aSize.width;
  927. }
  928. totalWidth += ((numChildren - 1) * padding);
  929. return new Dimension(extraWidth + totalWidth, extraHeight + height);
  930. }
  931. }
  932. }
  933. return new Dimension(0, 0);
  934. }
  935. public Dimension preferredLayoutSize(Container c) {
  936. return minimumLayoutSize(c);
  937. }
  938. public void removeLayoutComponent(Component c) { }
  939. }
  940. /**
  941. * This inner class is marked "public" due to a compiler bug.
  942. * This class should be treated as a "protected" inner class.
  943. * Instantiate it only within subclasses of BasicOptionPaneUI.
  944. */
  945. public class PropertyChangeHandler implements PropertyChangeListener {
  946. /**
  947. * If the source of the PropertyChangeEvent <code>e</code> equals the
  948. * optionPane and is one of the ICON_PROPERTY, MESSAGE_PROPERTY,
  949. * OPTIONS_PROPERTY or INITIAL_VALUE_PROPERTY,
  950. * validateComponent is invoked.
  951. */
  952. public void propertyChange(PropertyChangeEvent e) {
  953. if(e.getSource() == optionPane) {
  954. // Option Pane Auditory Cue Activation
  955. // only respond to "ancestor" changes
  956. // the idea being that a JOptionPane gets a JDialog when it is
  957. // set to appear and loses it's JDialog when it is dismissed.
  958. if ("ancestor" == e.getPropertyName()) {
  959. JOptionPane op = (JOptionPane)e.getSource();
  960. boolean isComingUp;
  961. // if the old value is null, then the JOptionPane is being
  962. // created since it didn't previously have an ancestor.
  963. if (e.getOldValue() == null) {
  964. isComingUp = true;
  965. } else {
  966. isComingUp = false;
  967. }
  968. // figure out what to do based on the message type
  969. switch (op.getMessageType()) {
  970. case JOptionPane.PLAIN_MESSAGE:
  971. if (isComingUp) {
  972. fireAudioAction("OptionPane.informationSound");
  973. }
  974. break;
  975. case JOptionPane.QUESTION_MESSAGE:
  976. if (isComingUp) {
  977. fireAudioAction("OptionPane.questionSound");
  978. }
  979. break;
  980. case JOptionPane.INFORMATION_MESSAGE:
  981. if (isComingUp) {
  982. fireAudioAction("OptionPane.informationSound");
  983. }
  984. break;
  985. case JOptionPane.WARNING_MESSAGE:
  986. if (isComingUp) {
  987. fireAudioAction("OptionPane.warningSound");
  988. }
  989. break;
  990. case JOptionPane.ERROR_MESSAGE:
  991. if (isComingUp) {
  992. fireAudioAction("OptionPane.errorSound");
  993. }
  994. break;
  995. default:
  996. System.err.println("Undefined JOptionPane type: " +
  997. op.getMessageType());
  998. break;
  999. }
  1000. }
  1001. // Visual activity
  1002. String changeName = e.getPropertyName();
  1003. if(changeName.equals(JOptionPane.OPTIONS_PROPERTY) ||
  1004. changeName.equals(JOptionPane.INITIAL_VALUE_PROPERTY) ||
  1005. changeName.equals(JOptionPane.ICON_PROPERTY) ||
  1006. changeName.equals(JOptionPane.MESSAGE_TYPE_PROPERTY) ||
  1007. changeName.equals(JOptionPane.OPTION_TYPE_PROPERTY) ||
  1008. changeName.equals(JOptionPane.MESSAGE_PROPERTY) ||
  1009. changeName.equals(JOptionPane.SELECTION_VALUES_PROPERTY) ||
  1010. changeName.equals(JOptionPane.INITIAL_SELECTION_VALUE_PROPERTY) ||
  1011. changeName.equals(JOptionPane.WANTS_INPUT_PROPERTY)) {
  1012. uninstallComponents();
  1013. installComponents();
  1014. optionPane.validate();
  1015. }
  1016. else if (changeName.equals("componentOrientation")) {
  1017. ComponentOrientation o = (ComponentOrientation)e.getNewValue();
  1018. JOptionPane op = (JOptionPane)e.getSource();
  1019. if (o != (ComponentOrientation)e.getOldValue()) {
  1020. op.applyComponentOrientation(o);
  1021. }
  1022. }
  1023. }
  1024. }
  1025. }
  1026. /**
  1027. * Utility method which contains code to fire the auditory feedback
  1028. * Actions.
  1029. *
  1030. * @since 1.4
  1031. */
  1032. private void fireAudioAction (String actionName) {
  1033. ActionMap map = optionPane.getActionMap();
  1034. if (map != null) {
  1035. Action audioAction = map.get(actionName);
  1036. if (audioAction != null) {
  1037. // pass off firing the Action to a utility method
  1038. BasicLookAndFeel lf = (BasicLookAndFeel)
  1039. UIManager.getLookAndFeel();
  1040. lf.playSound(audioAction);
  1041. }
  1042. }
  1043. }
  1044. /**
  1045. * Configures any necessary colors/fonts for the specified label
  1046. * used representing the message.
  1047. */
  1048. private void configureMessageLabel(JLabel label) {
  1049. label.setForeground(UIManager.getColor(
  1050. "OptionPane.messageForeground"));
  1051. Font messageFont = UIManager.getFont("OptionPane.messageFont");
  1052. if (messageFont != null) {
  1053. label.setFont(messageFont);
  1054. }
  1055. }
  1056. /**
  1057. * Configures any necessary colors/fonts for the specified button
  1058. * used representing the button portion of the optionpane.
  1059. */
  1060. private void configureButton(JButton button) {
  1061. Font buttonFont = UIManager.getFont("OptionPane.buttonFont");
  1062. if (buttonFont != null) {
  1063. button.setFont(buttonFont);
  1064. }
  1065. }
  1066. /**
  1067. * This inner class is marked "public" due to a compiler bug.
  1068. * This class should be treated as a "protected" inner class.
  1069. * Instantiate it only within subclasses of BasicOptionPaneUI.
  1070. */
  1071. public class ButtonActionListener implements ActionListener {
  1072. protected int buttonIndex;
  1073. public ButtonActionListener(int buttonIndex) {
  1074. this.buttonIndex = buttonIndex;
  1075. }
  1076. public void actionPerformed(ActionEvent e) {
  1077. if (optionPane != null) {
  1078. int optionType = optionPane.getOptionType();
  1079. Object[] options = optionPane.getOptions();
  1080. /* If the option pane takes input, then store the input value
  1081. * if custom options were specified, if the option type is
  1082. * DEFAULT_OPTION, OR if option type is set to a predefined
  1083. * one and the user chose the affirmative answer.
  1084. */
  1085. if (inputComponent != null) {
  1086. if (options != null ||
  1087. optionType == JOptionPane.DEFAULT_OPTION ||
  1088. ((optionType == JOptionPane.YES_NO_OPTION ||
  1089. optionType == JOptionPane.YES_NO_CANCEL_OPTION ||
  1090. optionType == JOptionPane.OK_CANCEL_OPTION) &&
  1091. buttonIndex == 0)) {
  1092. resetInputValue();
  1093. }
  1094. }
  1095. if (options == null) {
  1096. if (optionType == JOptionPane.OK_CANCEL_OPTION &&
  1097. buttonIndex == 1) {
  1098. optionPane.setValue(new Integer(2));
  1099. } else {
  1100. optionPane.setValue(new Integer(buttonIndex));
  1101. }
  1102. } else {
  1103. optionPane.setValue(options[buttonIndex]);
  1104. }
  1105. }
  1106. }
  1107. }
  1108. //
  1109. // Classed used when optionPane.getWantsInput returns true.
  1110. //
  1111. /**
  1112. * Listener when a JList is created to handle input from the user.
  1113. */
  1114. private class ListSelectionListener extends MouseAdapter
  1115. {
  1116. public void mousePressed(MouseEvent e) {
  1117. if (e.getClickCount() == 2) {
  1118. JList list = (JList)e.getSource();
  1119. int index = list.locationToIndex(e.getPoint());
  1120. optionPane.setInputValue(list.getModel().getElementAt(index));
  1121. }
  1122. }
  1123. }
  1124. /**
  1125. * Listener when a JTextField is created to handle input from the user.
  1126. */
  1127. private class TextFieldActionListener implements ActionListener
  1128. {
  1129. public void actionPerformed(ActionEvent e) {
  1130. optionPane.setInputValue(((JTextField)e.getSource()).getText());
  1131. }
  1132. }
  1133. /**
  1134. * A JTextField that allows you to specify an array of KeyStrokes that
  1135. * that will have their bindings processed regardless of whether or
  1136. * not they are registered on the JTextField. This is used as we really
  1137. * want the ActionListener to be notified so that we can push the
  1138. * change to the JOptionPane, but we also want additional bindings
  1139. * (those of the JRootPane) to be processed as well.
  1140. */
  1141. private static class MultiplexingTextField extends JTextField {
  1142. private KeyStroke[] strokes;
  1143. MultiplexingTextField(int cols) {
  1144. super(cols);
  1145. }
  1146. /**
  1147. * Sets the KeyStrokes that will be additional processed for
  1148. * ancestor bindings.
  1149. */
  1150. void setKeyStrokes(KeyStroke[] strokes) {
  1151. this.strokes = strokes;
  1152. }
  1153. protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
  1154. int condition, boolean pressed) {
  1155. boolean processed = super.processKeyBinding(ks, e, condition,
  1156. pressed);
  1157. if (processed && condition != JComponent.WHEN_IN_FOCUSED_WINDOW) {
  1158. for (int counter = strokes.length - 1; counter >= 0;
  1159. counter--) {
  1160. if (strokes[counter].equals(ks)) {
  1161. // Returning false will allow further processing
  1162. // of the bindings, eg our parent Containers will get a
  1163. // crack at them.
  1164. return false;
  1165. }
  1166. }
  1167. }
  1168. return processed;
  1169. }
  1170. }
  1171. // REMIND(aim,7/29/98): These actions should be broken
  1172. // out into protected inner classes in the next release where
  1173. // API changes are allowed
  1174. /**
  1175. * Registered in the ActionMap. Sets the value of the option pane
  1176. * to <code>JOptionPane.CLOSED_OPTION</code>.
  1177. */
  1178. private static class CloseAction extends AbstractAction {
  1179. public void actionPerformed(ActionEvent e) {
  1180. JOptionPane optionPane = (JOptionPane)e.getSource();
  1181. optionPane.setValue(new Integer(JOptionPane.CLOSED_OPTION));
  1182. }
  1183. }
  1184. }