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