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