1. /*
  2. * @(#)JPopupMenu.java 1.181 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;
  8. import java.awt.*;
  9. import java.awt.event.*;
  10. import java.io.IOException;
  11. import java.io.ObjectInputStream;
  12. import java.io.ObjectOutputStream;
  13. import java.io.Serializable;
  14. import java.beans.*;
  15. import java.util.Locale;
  16. import java.util.Vector;
  17. import java.util.Hashtable;
  18. import javax.accessibility.*;
  19. import javax.swing.plaf.PopupMenuUI;
  20. import javax.swing.plaf.ComponentUI;
  21. import javax.swing.event.*;
  22. import java.applet.Applet;
  23. /**
  24. * An implementation of a popup menu -- a small window that pops up
  25. * and displays a series of choices. A <code>JPopupMenu</code> is used for the
  26. * menu that appears when the user selects an item on the menu bar.
  27. * It is also used for "pull-right" menu that appears when the
  28. * selects a menu item that activates it. Finally, a <code>JPopupMenu</code>
  29. * can also be used anywhere else you want a menu to appear. For
  30. * example, when the user right-clicks in a specified area.
  31. * <p>
  32. * For information and examples of using popup menus, see
  33. * <a
  34. href="http://java.sun.com/docs/books/tutorial/uiswing/components/menu.html">How to Use Menus</a>
  35. * in <em>The Java Tutorial.</em>
  36. * For the keyboard keys used by this component in the standard Look and
  37. * Feel (L&F) renditions, see the
  38. * <a href="doc-files/Key-Index.html#JPopupMenu"><code>JPopupMenu</code> key assignments</a>.
  39. * <p>
  40. * <strong>Warning:</strong>
  41. * Serialized objects of this class will not be compatible with
  42. * future Swing releases. The current serialization support is
  43. * appropriate for short term storage or RMI between applications running
  44. * the same version of Swing. As of 1.4, support for long term storage
  45. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  46. * has been added to the <code>java.beans</code> package.
  47. * Please see {@link java.beans.XMLEncoder}.
  48. *
  49. * @beaninfo
  50. * attribute: isContainer false
  51. * description: A small window that pops up and displays a series of choices.
  52. *
  53. * @version 1.181 01/23/03
  54. * @author Georges Saab
  55. * @author David Karlton
  56. * @author Arnaud Weber
  57. */
  58. public class JPopupMenu extends JComponent implements Accessible,MenuElement {
  59. /**
  60. * @see #getUIClassID
  61. * @see #readObject
  62. */
  63. private static final String uiClassID = "PopupMenuUI";
  64. /**
  65. * Key used in AppContext to determine if light way popups are the default.
  66. */
  67. private static final Object defaultLWPopupEnabledKey =
  68. new StringBuffer("JPopupMenu.defaultLWPopupEnabledKey");
  69. /** Bug#4425878-Property javax.swing.adjustPopupLocationToFit introduced */
  70. static boolean popupPostionFixDisabled = false;
  71. static {
  72. popupPostionFixDisabled = java.security.AccessController.doPrivileged(
  73. new sun.security.action.GetPropertyAction(
  74. "javax.swing.adjustPopupLocationToFit","")).equals("false");
  75. }
  76. transient Component invoker;
  77. transient Popup popup;
  78. transient Frame frame;
  79. private int desiredLocationX,desiredLocationY;
  80. private String label = null;
  81. private boolean paintBorder = true;
  82. private Insets margin = null;
  83. /**
  84. * Used to indicate if lightweight popups should be used.
  85. */
  86. private boolean lightWeightPopup = true;
  87. /*
  88. * Model for the selected subcontrol.
  89. */
  90. private SingleSelectionModel selectionModel;
  91. /* Lock object used in place of class object for synchronization.
  92. * (4187686)
  93. */
  94. private static final Object classLock = new Object();
  95. /* diagnostic aids -- should be false for production builds. */
  96. private static final boolean TRACE = false; // trace creates and disposes
  97. private static final boolean VERBOSE = false; // show reuse hits/misses
  98. private static final boolean DEBUG = false; // show bad params, misc.
  99. /**
  100. * Sets the default value of the <code>lightWeightPopupEnabled</code>
  101. * property.
  102. *
  103. * @param aFlag <code>true</code> if popups can be lightweight,
  104. * otherwise <code>false</code>
  105. * @see #getDefaultLightWeightPopupEnabled
  106. * @see #setLightWeightPopupEnabled
  107. */
  108. public static void setDefaultLightWeightPopupEnabled(boolean aFlag) {
  109. SwingUtilities.appContextPut(defaultLWPopupEnabledKey,
  110. Boolean.valueOf(aFlag));
  111. }
  112. /**
  113. * Gets the <code>defaultLightWeightPopupEnabled</code> property,
  114. * which by default is <code>true</code>.
  115. *
  116. * @return the value of the <code>defaultLightWeightPopupEnabled</code>
  117. * property
  118. *
  119. * @see #setDefaultLightWeightPopupEnabled
  120. */
  121. public static boolean getDefaultLightWeightPopupEnabled() {
  122. Boolean b = (Boolean)
  123. SwingUtilities.appContextGet(defaultLWPopupEnabledKey);
  124. if (b == null) {
  125. SwingUtilities.appContextPut(defaultLWPopupEnabledKey,
  126. Boolean.TRUE);
  127. return true;
  128. }
  129. return b.booleanValue();
  130. }
  131. /**
  132. * Constructs a <code>JPopupMenu</code> without an "invoker".
  133. */
  134. public JPopupMenu() {
  135. this(null);
  136. }
  137. /**
  138. * Constructs a <code>JPopupMenu</code> with the specified title.
  139. *
  140. * @param label the string that a UI may use to display as a title
  141. * for the popup menu.
  142. */
  143. public JPopupMenu(String label) {
  144. this.label = label;
  145. lightWeightPopup = getDefaultLightWeightPopupEnabled();
  146. setSelectionModel(new DefaultSingleSelectionModel());
  147. addMouseListener(new MouseAdapter() {});
  148. setFocusTraversalKeysEnabled(false);
  149. updateUI();
  150. }
  151. /**
  152. * Returns the look and feel (L&F) object that renders this component.
  153. *
  154. * @return the <code>PopupMenuUI</code> object that renders this component
  155. */
  156. public PopupMenuUI getUI() {
  157. return (PopupMenuUI)ui;
  158. }
  159. /**
  160. * Sets the L&F object that renders this component.
  161. *
  162. * @param ui the new <code>PopupMenuUI</code> L&F object
  163. * @see UIDefaults#getUI
  164. * @beaninfo
  165. * bound: true
  166. * hidden: true
  167. * attribute: visualUpdate true
  168. * description: The UI object that implements the Component's LookAndFeel.
  169. */
  170. public void setUI(PopupMenuUI ui) {
  171. super.setUI(ui);
  172. }
  173. /**
  174. * Resets the UI property to a value from the current look and feel.
  175. *
  176. * @see JComponent#updateUI
  177. */
  178. public void updateUI() {
  179. setUI((PopupMenuUI)UIManager.getUI(this));
  180. }
  181. /**
  182. * Returns the name of the L&F class that renders this component.
  183. *
  184. * @return the string "PopupMenuUI"
  185. * @see JComponent#getUIClassID
  186. * @see UIDefaults#getUI
  187. */
  188. public String getUIClassID() {
  189. return uiClassID;
  190. }
  191. protected void processFocusEvent(FocusEvent evt) {
  192. super.processFocusEvent(evt);
  193. }
  194. /**
  195. * Processes key stroke events such as mnemonics and accelerators.
  196. *
  197. * @param evt the key event to be processed
  198. */
  199. protected void processKeyEvent(KeyEvent evt) {
  200. MenuSelectionManager.defaultManager().processKeyEvent(evt);
  201. if (evt.isConsumed()) {
  202. return;
  203. }
  204. super.processKeyEvent(evt);
  205. }
  206. /**
  207. * Returns the model object that handles single selections.
  208. *
  209. * @return the <code>selectionModel</code> property
  210. * @see SingleSelectionModel
  211. */
  212. public SingleSelectionModel getSelectionModel() {
  213. return selectionModel;
  214. }
  215. /**
  216. * Sets the model object to handle single selections.
  217. *
  218. * @param model the new <code>SingleSelectionModel</code>
  219. * @see SingleSelectionModel
  220. * @beaninfo
  221. * description: The selection model for the popup menu
  222. * expert: true
  223. */
  224. public void setSelectionModel(SingleSelectionModel model) {
  225. selectionModel = model;
  226. }
  227. /**
  228. * Appends the specified menu item to the end of this menu.
  229. *
  230. * @param menuItem the <code>JMenuItem</code> to add
  231. * @return the <code>JMenuItem</code> added
  232. */
  233. public JMenuItem add(JMenuItem menuItem) {
  234. super.add(menuItem);
  235. return menuItem;
  236. }
  237. /**
  238. * Creates a new menu item with the specified text and appends
  239. * it to the end of this menu.
  240. *
  241. * @param s the string for the menu item to be added
  242. */
  243. public JMenuItem add(String s) {
  244. return add(new JMenuItem(s));
  245. }
  246. /**
  247. * Appends a new menu item to the end of the menu which
  248. * dispatches the specified <code>Action</code> object.
  249. *
  250. * As of JDK 1.3, this is no longer the preferred method for adding
  251. * <code>Actions</code> to
  252. * a container. Instead it is recommended to configure a control with
  253. * an action using <code>setAction</code>, and then add that control
  254. * directly to the <code>Container</code>.
  255. *
  256. * @param a the <code>Action</code> to add to the menu
  257. * @return the new menu item
  258. * @see Action
  259. */
  260. public JMenuItem add(Action a) {
  261. JMenuItem mi = createActionComponent(a);
  262. mi.setAction(a);
  263. add(mi);
  264. return mi;
  265. }
  266. /**
  267. * Returns an point which has been adjusted to take into account of the
  268. * desktop bounds, taskbar and multi-monitor configuration.
  269. * <p>
  270. * This adustment may be cancelled by invoking the application with
  271. * -Djavax.swing.adjustPopupLocationToFit=false
  272. */
  273. Point adjustPopupLocationToFitScreen(int xposition, int yposition) {
  274. Point p = new Point(xposition, yposition);
  275. if(popupPostionFixDisabled == true || GraphicsEnvironment.isHeadless())
  276. return p;
  277. Toolkit toolkit = Toolkit.getDefaultToolkit();
  278. Rectangle screenBounds;
  279. Insets screenInsets;
  280. GraphicsConfiguration gc = null;
  281. // Try to find GraphicsConfiguration, that includes mouse
  282. // pointer position
  283. GraphicsEnvironment ge =
  284. GraphicsEnvironment.getLocalGraphicsEnvironment();
  285. GraphicsDevice[] gd = ge.getScreenDevices();
  286. for(int i = 0; i < gd.length; i++) {
  287. if(gd[i].getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
  288. GraphicsConfiguration dgc =
  289. gd[i].getDefaultConfiguration();
  290. if(dgc.getBounds().contains(p)) {
  291. gc = dgc;
  292. break;
  293. }
  294. }
  295. }
  296. // If not found and we have invoker, ask invoker about his gc
  297. if(gc == null && getInvoker() != null) {
  298. gc = getInvoker().getGraphicsConfiguration();
  299. }
  300. if(gc != null) {
  301. // If we have GraphicsConfiguration use it to get
  302. // screen bounds and insets
  303. screenInsets = toolkit.getScreenInsets(gc);
  304. screenBounds = gc.getBounds();
  305. } else {
  306. // If we don't have GraphicsConfiguration use primary screen
  307. // and empty insets
  308. screenInsets = new Insets(0, 0, 0, 0);
  309. screenBounds = new Rectangle(toolkit.getScreenSize());
  310. }
  311. int scrWidth = screenBounds.width -
  312. Math.abs(screenInsets.left+screenInsets.right);
  313. int scrHeight = screenBounds.height -
  314. Math.abs(screenInsets.top+screenInsets.bottom);
  315. Dimension size;
  316. size = JPopupMenu.this.getPreferredSize();
  317. if( (p.x + size.width) > screenBounds.x + scrWidth )
  318. p.x = screenBounds.x + scrWidth - size.width;
  319. if( (p.y + size.height) > screenBounds.y + scrHeight)
  320. p.y = screenBounds.y + scrHeight - size.height;
  321. /* Change is made to the desired (X,Y) values, when the
  322. PopupMenu is too tall OR too wide for the screen
  323. */
  324. if( p.x < screenBounds.x )
  325. p.x = screenBounds.x ;
  326. if( p.y < screenBounds.y )
  327. p.y = screenBounds.y;
  328. return p;
  329. }
  330. /**
  331. * Factory method which creates the <code>JMenuItem</code> for
  332. * <code>Actions</code> added to the <code>JPopupMenu</code>.
  333. * As of JDK 1.3, this is no
  334. * longer the preferred method, instead it is recommended to configure
  335. * a control with an action using <code>setAction</code>,
  336. * and then adding that
  337. * control directly to the <code>Container</code>.
  338. *
  339. * @param a the <code>Action</code> for the menu item to be added
  340. * @return the new menu item
  341. * @see Action
  342. *
  343. * @since 1.3
  344. */
  345. protected JMenuItem createActionComponent(Action a) {
  346. JMenuItem mi = new JMenuItem((String)a.getValue(Action.NAME),
  347. (Icon)a.getValue(Action.SMALL_ICON)){
  348. protected PropertyChangeListener createActionPropertyChangeListener(Action a) {
  349. PropertyChangeListener pcl = createActionChangeListener(this);
  350. if (pcl == null) {
  351. pcl = super.createActionPropertyChangeListener(a);
  352. }
  353. return pcl;
  354. }
  355. };
  356. mi.setHorizontalTextPosition(JButton.TRAILING);
  357. mi.setVerticalTextPosition(JButton.CENTER);
  358. mi.setEnabled(a.isEnabled());
  359. return mi;
  360. }
  361. /**
  362. * Returns a properly configured <code>PropertyChangeListener</code>
  363. * which updates the control as changes to the <code>Action</code> occur.
  364. * As of JDK 1.3, this is no longer the preferred method for adding
  365. * <code>Actions</code> to
  366. * a container. Instead it is recommended to configure a control with
  367. * an action using <code>setAction</code>, and then add that control
  368. * directly to the <code>Container</code>.
  369. */
  370. protected PropertyChangeListener createActionChangeListener(JMenuItem b) {
  371. return new ActionChangedListener(b);
  372. }
  373. private class ActionChangedListener implements PropertyChangeListener, Serializable {
  374. private JMenuItem menuItem;
  375. public ActionChangedListener(JMenuItem mi) {
  376. super();
  377. setTarget(mi);
  378. }
  379. public void propertyChange(PropertyChangeEvent e) {
  380. String propertyName = e.getPropertyName();
  381. if (e.getPropertyName().equals(Action.NAME)) {
  382. String text = (String) e.getNewValue();
  383. menuItem.setText(text);
  384. } else if (propertyName.equals("enabled")) {
  385. Boolean enabledState = (Boolean) e.getNewValue();
  386. menuItem.setEnabled(enabledState.booleanValue());
  387. } else if (e.getPropertyName().equals(Action.SMALL_ICON)) {
  388. Icon icon = (Icon) e.getNewValue();
  389. menuItem.setIcon(icon);
  390. menuItem.invalidate();
  391. menuItem.repaint();
  392. }
  393. }
  394. public void setTarget(JMenuItem b) {
  395. this.menuItem = b;
  396. }
  397. }
  398. /**
  399. * Removes the component at the specified index from this popup menu.
  400. *
  401. * @param pos the position of the item to be removed
  402. * @exception IllegalArgumentException if the value of
  403. * <code>pos</code> < 0, or if the value of
  404. * <code>pos</code> is greater than the
  405. * number of items
  406. */
  407. public void remove(int pos) {
  408. if (pos < 0) {
  409. throw new IllegalArgumentException("index less than zero.");
  410. }
  411. if (pos > getComponentCount() -1) {
  412. throw new IllegalArgumentException("index greater than the number of items.");
  413. }
  414. super.remove(pos);
  415. }
  416. /**
  417. * Sets the value of the <code>lightWeightPopupEnabled</code> property,
  418. * which by default is <code>true</code>.
  419. * By default, when a look and feel displays a popup,
  420. * it can choose to
  421. * use a lightweight (all-Java) popup.
  422. * Lightweight popup windows are more efficient than heavyweight
  423. * (native peer) windows,
  424. * but lightweight and heavyweight components do not mix well in a GUI.
  425. * If your application mixes lightweight and heavyweight components,
  426. * you should disable lightweight popups.
  427. * Some look and feels might always use heavyweight popups,
  428. * no matter what the value of this property.
  429. *
  430. * @param aFlag <code>false</code> to disable lightweight popups
  431. * @beaninfo
  432. * description: Determines whether lightweight popups are used when possible
  433. * expert: true
  434. *
  435. * @see #isLightWeightPopupEnabled
  436. */
  437. public void setLightWeightPopupEnabled(boolean aFlag) {
  438. // NOTE: this use to set the flag on a shared JPopupMenu, which meant
  439. // this effected ALL JPopupMenus.
  440. lightWeightPopup = aFlag;
  441. }
  442. /**
  443. * Gets the <code>lightWeightPopupEnabled</code> property.
  444. *
  445. * @return the value of the <code>lightWeightPopupEnabled</code> property
  446. * @see #setLightWeightPopupEnabled
  447. */
  448. public boolean isLightWeightPopupEnabled() {
  449. return lightWeightPopup;
  450. }
  451. /**
  452. * Returns the popup menu's label
  453. *
  454. * @return a string containing the popup menu's label
  455. * @see #setLabel
  456. */
  457. public String getLabel() {
  458. return label;
  459. }
  460. /**
  461. * Sets the popup menu's label. Different look and feels may choose
  462. * to display or not display this.
  463. *
  464. * @param label a string specifying the label for the popup menu
  465. *
  466. * @see #setLabel
  467. * @beaninfo
  468. * description: The label for the popup menu.
  469. * bound: true
  470. */
  471. public void setLabel(String label) {
  472. String oldValue = this.label;
  473. this.label = label;
  474. firePropertyChange("label", oldValue, label);
  475. if (accessibleContext != null) {
  476. accessibleContext.firePropertyChange(
  477. AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY,
  478. oldValue, label);
  479. }
  480. invalidate();
  481. repaint();
  482. }
  483. /**
  484. * Appends a new separator at the end of the menu.
  485. */
  486. public void addSeparator() {
  487. add( new JPopupMenu.Separator() );
  488. }
  489. /**
  490. * Inserts a menu item for the specified <code>Action</code> object at
  491. * a given position.
  492. *
  493. * @param a the <code>Action</code> object to insert
  494. * @param index specifies the position at which to insert the
  495. * <code>Action</code>, where 0 is the first
  496. * @exception IllegalArgumentException if <code>index</code> < 0
  497. * @see Action
  498. */
  499. public void insert(Action a, int index) {
  500. JMenuItem mi = createActionComponent(a);
  501. mi.setAction(a);
  502. insert(mi, index);
  503. }
  504. /**
  505. * Inserts the specified component into the menu at a given
  506. * position.
  507. *
  508. * @param component the <code>Component</code> to insert
  509. * @param index specifies the position at which
  510. * to insert the component, where 0 is the first
  511. * @exception IllegalArgumentException if <code>index</code> < 0
  512. */
  513. public void insert(Component component, int index) {
  514. if (index < 0) {
  515. throw new IllegalArgumentException("index less than zero.");
  516. }
  517. int nitems = getComponentCount();
  518. // PENDING(ges): Why not use an array?
  519. Vector tempItems = new Vector();
  520. /* Remove the item at index, nitems-index times
  521. storing them in a temporary vector in the
  522. order they appear on the menu.
  523. */
  524. for (int i = index ; i < nitems; i++) {
  525. tempItems.addElement(getComponent(index));
  526. remove(index);
  527. }
  528. add(component);
  529. /* Add the removed items back to the menu, they are
  530. already in the correct order in the temp vector.
  531. */
  532. for (int i = 0; i < tempItems.size() ; i++) {
  533. add((Component)tempItems.elementAt(i));
  534. }
  535. }
  536. /**
  537. * Adds a <code>PopupMenu</code> listener.
  538. *
  539. * @param l the <code>PopupMenuListener</code> to add
  540. */
  541. public void addPopupMenuListener(PopupMenuListener l) {
  542. listenerList.add(PopupMenuListener.class,l);
  543. }
  544. /**
  545. * Removes a <code>PopupMenu</code> listener.
  546. *
  547. * @param l the <code>PopupMenuListener</code> to remove
  548. */
  549. public void removePopupMenuListener(PopupMenuListener l) {
  550. listenerList.remove(PopupMenuListener.class,l);
  551. }
  552. /**
  553. * Returns an array of all the <code>PopupMenuListener</code>s added
  554. * to this JMenuItem with addPopupMenuListener().
  555. *
  556. * @return all of the <code>PopupMenuListener</code>s added or an empty
  557. * array if no listeners have been added
  558. * @since 1.4
  559. */
  560. public PopupMenuListener[] getPopupMenuListeners() {
  561. return (PopupMenuListener[])listenerList.getListeners(
  562. PopupMenuListener.class);
  563. }
  564. /**
  565. * Notifies <code>PopupMenuListener</code>s that this popup menu will
  566. * become visible.
  567. */
  568. protected void firePopupMenuWillBecomeVisible() {
  569. Object[] listeners = listenerList.getListenerList();
  570. PopupMenuEvent e=null;
  571. for (int i = listeners.length-2; i>=0; i-=2) {
  572. if (listeners[i]==PopupMenuListener.class) {
  573. if (e == null)
  574. e = new PopupMenuEvent(this);
  575. ((PopupMenuListener)listeners[i+1]).popupMenuWillBecomeVisible(e);
  576. }
  577. }
  578. }
  579. /**
  580. * Notifies <code>PopupMenuListener</code>s that this popup menu will
  581. * become invisible.
  582. */
  583. protected void firePopupMenuWillBecomeInvisible() {
  584. Object[] listeners = listenerList.getListenerList();
  585. PopupMenuEvent e=null;
  586. for (int i = listeners.length-2; i>=0; i-=2) {
  587. if (listeners[i]==PopupMenuListener.class) {
  588. if (e == null)
  589. e = new PopupMenuEvent(this);
  590. ((PopupMenuListener)listeners[i+1]).popupMenuWillBecomeInvisible(e);
  591. }
  592. }
  593. }
  594. /**
  595. * Notifies <code>PopupMenuListeners</code> that this popup menu is
  596. * cancelled.
  597. */
  598. protected void firePopupMenuCanceled() {
  599. Object[] listeners = listenerList.getListenerList();
  600. PopupMenuEvent e=null;
  601. for (int i = listeners.length-2; i>=0; i-=2) {
  602. if (listeners[i]==PopupMenuListener.class) {
  603. if (e == null)
  604. e = new PopupMenuEvent(this);
  605. ((PopupMenuListener)listeners[i+1]).popupMenuCanceled(e);
  606. }
  607. }
  608. }
  609. /**
  610. * Always returns true since popups, by definition, should always
  611. * be on top of all other windows.
  612. * @return true
  613. */
  614. // package private
  615. boolean alwaysOnTop() {
  616. return true;
  617. }
  618. /**
  619. * Lays out the container so that it uses the minimum space
  620. * needed to display its contents.
  621. */
  622. public void pack() {
  623. if(popup != null) {
  624. Dimension pref = getPreferredSize();
  625. if (pref == null || pref.width != getWidth() ||
  626. pref.height != getHeight()) {
  627. popup = getPopup();
  628. } else {
  629. validate();
  630. }
  631. }
  632. }
  633. /**
  634. * Sets the visibility of the popup menu.
  635. *
  636. * @param b true to make the popup visible, or false to
  637. * hide it
  638. * @beaninfo
  639. * bound: true
  640. * description: Makes the popup visible
  641. */
  642. public void setVisible(boolean b) {
  643. if (DEBUG) {
  644. System.out.println("JPopupMenu.setVisible " + b);
  645. }
  646. // Is it a no-op?
  647. if (b == isVisible())
  648. return;
  649. // if closing, first close all Submenus
  650. if (b == false) {
  651. // 4234793: This is a workaround because JPopupMenu.firePopupMenuCanceled is
  652. // a protected method and cannot be called from BasicPopupMenuUI directly
  653. // The real solution could be to make
  654. // firePopupMenuCanceled public and call it directly.
  655. Boolean doCanceled = (Boolean)getClientProperty("JPopupMenu.firePopupMenuCanceled");
  656. if (doCanceled != null && doCanceled == Boolean.TRUE) {
  657. putClientProperty("JPopupMenu.firePopupMenuCanceled", Boolean.FALSE);
  658. firePopupMenuCanceled();
  659. }
  660. getSelectionModel().clearSelection();
  661. } else {
  662. // This is a popup menu with MenuElement children,
  663. // set selection path before popping up!
  664. if (isPopupMenu()) {
  665. if (getSubElements().length > 0) {
  666. MenuElement me[] = new MenuElement[2];
  667. me[0]=(MenuElement)this;
  668. me[1]=getSubElements()[0];
  669. MenuSelectionManager.defaultManager().setSelectedPath(me);
  670. } else {
  671. MenuElement me[] = new MenuElement[1];
  672. me[0]=(MenuElement)this;
  673. MenuSelectionManager.defaultManager().setSelectedPath(me);
  674. }
  675. }
  676. }
  677. if(b) {
  678. firePopupMenuWillBecomeVisible();
  679. popup = getPopup();
  680. firePropertyChange("visible", Boolean.FALSE, Boolean.TRUE);
  681. } else if(popup != null) {
  682. firePopupMenuWillBecomeInvisible();
  683. popup.hide();
  684. popup = null;
  685. firePropertyChange("visible", Boolean.TRUE, Boolean.FALSE);
  686. // 4694797: When popup menu is made invisible, selected path
  687. // should be cleared
  688. if (isPopupMenu()) {
  689. MenuSelectionManager.defaultManager().clearSelectedPath();
  690. }
  691. }
  692. if (accessibleContext != null) {
  693. if (b) {
  694. accessibleContext.firePropertyChange(
  695. AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
  696. null, AccessibleState.VISIBLE);
  697. } else {
  698. accessibleContext.firePropertyChange(
  699. AccessibleContext.ACCESSIBLE_STATE_PROPERTY,
  700. AccessibleState.VISIBLE, null);
  701. }
  702. }
  703. }
  704. /**
  705. * Returns a <code>Popup</code> instance from the
  706. * <code>PopupMenuUI</code> that has had <code>show</code> invoked on
  707. * it. If the current <code>popup</code> is non-null,
  708. * this will invoke <code>dispose</code> of it, and then
  709. * <code>show</code> the new one.
  710. * <p>
  711. * This does NOT fire any events, it is up the caller to dispatch
  712. * the necessary events.
  713. */
  714. private Popup getPopup() {
  715. Popup oldPopup = popup;
  716. if (oldPopup != null) {
  717. oldPopup.hide();
  718. }
  719. PopupFactory popupFactory = PopupFactory.getSharedInstance();
  720. if (isLightWeightPopupEnabled()) {
  721. popupFactory.setPopupType(PopupFactory.LIGHT_WEIGHT_POPUP);
  722. }
  723. else {
  724. popupFactory.setPopupType(PopupFactory.MEDIUM_WEIGHT_POPUP);
  725. }
  726. // adjust the location of the popup
  727. Point p = adjustPopupLocationToFitScreen(desiredLocationX,desiredLocationY);
  728. desiredLocationX = p.x;
  729. desiredLocationY = p.y;
  730. Popup newPopup = getUI().getPopup(this, desiredLocationX,
  731. desiredLocationY);
  732. popupFactory.setPopupType(PopupFactory.LIGHT_WEIGHT_POPUP);
  733. newPopup.show();
  734. return newPopup;
  735. }
  736. /**
  737. * Returns true if the popup menu is visible (currently
  738. * being displayed).
  739. */
  740. public boolean isVisible() {
  741. if(popup != null)
  742. return true;
  743. else
  744. return false;
  745. }
  746. /**
  747. * Sets the location of the upper left corner of the
  748. * popup menu using x, y coordinates.
  749. *
  750. * @param x the x coordinate of the popup's new position
  751. * @param y the y coordinate of the popup's new position
  752. * @beaninfo
  753. * description: The location of the popup menu.
  754. */
  755. public void setLocation(int x, int y) {
  756. int oldX = desiredLocationX;
  757. int oldY = desiredLocationY;
  758. desiredLocationX = x;
  759. desiredLocationY = y;
  760. if(popup != null && (x != oldX || y != oldY)) {
  761. popup = getPopup();
  762. }
  763. }
  764. /**
  765. * Returns true if the popup menu is a standalone popup menu
  766. * rather than the submenu of a <code>JMenu</code>.
  767. *
  768. * @return true if this menu is a standalone popup menu, otherwise false
  769. */
  770. private boolean isPopupMenu() {
  771. return ((invoker != null) && !(invoker instanceof JMenu));
  772. }
  773. /**
  774. * Returns the component which is the 'invoker' of this
  775. * popup menu.
  776. *
  777. * @return the <code>Component</code> in which the popup menu is displayed
  778. */
  779. public Component getInvoker() {
  780. return this.invoker;
  781. }
  782. /**
  783. * Sets the invoker of this popup menu -- the component in which
  784. * the popup menu menu is to be displayed.
  785. *
  786. * @param invoker the <code>Component</code> in which the popup
  787. * menu is displayed
  788. * @beaninfo
  789. * description: The invoking component for the popup menu
  790. * expert: true
  791. */
  792. public void setInvoker(Component invoker) {
  793. Component oldInvoker = this.invoker;
  794. this.invoker = invoker;
  795. if ((oldInvoker != this.invoker) && (ui != null)) {
  796. ui.uninstallUI(this);
  797. ui.installUI(this);
  798. }
  799. invalidate();
  800. }
  801. /**
  802. * Displays the popup menu at the position x,y in the coordinate
  803. * space of the component invoker.
  804. *
  805. * @param invoker the component in whose space the popup menu is to appear
  806. * @param x the x coordinate in invoker's coordinate space at which
  807. * the popup menu is to be displayed
  808. * @param y the y coordinate in invoker's coordinate space at which
  809. * the popup menu is to be displayed
  810. */
  811. public void show(Component invoker, int x, int y) {
  812. if (DEBUG) {
  813. System.out.println("in JPopupMenu.show " );
  814. }
  815. setInvoker(invoker);
  816. Frame newFrame = getFrame(invoker);
  817. if (newFrame != frame) {
  818. // Use the invoker's frame so that events
  819. // are propagated properly
  820. if (newFrame!=null) {
  821. this.frame = newFrame;
  822. if(popup != null) {
  823. setVisible(false);
  824. }
  825. }
  826. }
  827. Point invokerOrigin;
  828. if (invoker != null) {
  829. invokerOrigin = invoker.getLocationOnScreen();
  830. setLocation(invokerOrigin.x + x,
  831. invokerOrigin.y + y);
  832. } else {
  833. setLocation(x, y);
  834. }
  835. setVisible(true);
  836. }
  837. /**
  838. * Returns the popup menu which is at the root of the menu system
  839. * for this popup menu.
  840. *
  841. * @return the topmost grandparent <code>JPopupMenu</code>
  842. */
  843. JPopupMenu getRootPopupMenu() {
  844. JPopupMenu mp = this;
  845. while((mp!=null) && (mp.isPopupMenu()!=true) &&
  846. (mp.getInvoker() != null) &&
  847. (mp.getInvoker().getParent() != null) &&
  848. (mp.getInvoker().getParent() instanceof JPopupMenu)
  849. ) {
  850. mp = (JPopupMenu) mp.getInvoker().getParent();
  851. }
  852. return mp;
  853. }
  854. /**
  855. * Returns the component at the specified index.
  856. *
  857. * @param i the index of the component, where 0 is the first
  858. * @return the <code>Component</code> at that index
  859. * @deprecated replaced by <code>getComponent(int i)</code>
  860. */
  861. public Component getComponentAtIndex(int i) {
  862. return getComponent(i);
  863. }
  864. /**
  865. * Returns the index of the specified component.
  866. *
  867. * @param c the <code>Component</code> to find
  868. * @return the index of the component, where 0 is the first;
  869. * or -1 if the component is not found
  870. */
  871. public int getComponentIndex(Component c) {
  872. int ncomponents = this.getComponentCount();
  873. Component[] component = this.getComponents();
  874. for (int i = 0 ; i < ncomponents ; i++) {
  875. Component comp = component[i];
  876. if (comp == c)
  877. return i;
  878. }
  879. return -1;
  880. }
  881. /**
  882. * Sets the size of the Popup window using a <code>Dimension</code> object.
  883. * This is equivalent to <code>setPreferredSize(d)</code>.
  884. *
  885. * @param d the <code>Dimension</code> specifying the new size
  886. * of this component.
  887. * @beaninfo
  888. * description: The size of the popup menu
  889. */
  890. public void setPopupSize(Dimension d) {
  891. Dimension oldSize = getPreferredSize();
  892. setPreferredSize(d);
  893. if (popup != null) {
  894. Dimension newSize = getPreferredSize();
  895. if (!oldSize.equals(newSize)) {
  896. popup = getPopup();
  897. }
  898. }
  899. }
  900. /**
  901. * Sets the size of the Popup window to the specified width and
  902. * height. This is equivalent to
  903. * <code>setPreferredSize(new Dimension(width, height))</code>.
  904. *
  905. * @param width the new width of the Popup in pixels
  906. * @param height the new height of the Popup in pixels
  907. * @beaninfo
  908. * description: The size of the popup menu
  909. */
  910. public void setPopupSize(int width, int height) {
  911. setPopupSize(new Dimension(width, height));
  912. }
  913. /**
  914. * Sets the currently selected component, This will result
  915. * in a change to the selection model.
  916. *
  917. * @param sel the <code>Component</code> to select
  918. * @beaninfo
  919. * description: The selected component on the popup menu
  920. * expert: true
  921. * hidden: true
  922. */
  923. public void setSelected(Component sel) {
  924. SingleSelectionModel model = getSelectionModel();
  925. int index = getComponentIndex(sel);
  926. model.setSelectedIndex(index);
  927. }
  928. /**
  929. * Checks whether the border should be painted.
  930. *
  931. * @return true if the border is painted, false otherwise
  932. * @see #setBorderPainted
  933. */
  934. public boolean isBorderPainted() {
  935. return paintBorder;
  936. }
  937. /**
  938. * Sets whether the border should be painted.
  939. *
  940. * @param b if true, the border is painted.
  941. * @see #isBorderPainted
  942. * @beaninfo
  943. * description: Is the border of the popup menu painted
  944. */
  945. public void setBorderPainted(boolean b) {
  946. paintBorder = b;
  947. repaint();
  948. }
  949. /**
  950. * Paints the popup menu's border if the <code>borderPainted</code>
  951. * property is <code>true</code>.
  952. * @param g the <code>Graphics</code> object
  953. *
  954. * @see JComponent#paint
  955. * @see JComponent#setBorder
  956. */
  957. protected void paintBorder(Graphics g) {
  958. if (isBorderPainted()) {
  959. super.paintBorder(g);
  960. }
  961. }
  962. /**
  963. * Returns the margin, in pixels, between the popup menu's border and
  964. * its containees.
  965. *
  966. * @return an <code>Insets</code> object containing the margin values.
  967. */
  968. public Insets getMargin() {
  969. if(margin == null) {
  970. return new Insets(0,0,0,0);
  971. } else {
  972. return margin;
  973. }
  974. }
  975. /**
  976. * Examines the list of menu items to determine whether
  977. * <code>popup</code> is a popup menu.
  978. *
  979. * @param popup a <code>JPopupMenu</code>
  980. * @return true if <code>popup</code>
  981. */
  982. boolean isSubPopupMenu(JPopupMenu popup) {
  983. int ncomponents = this.getComponentCount();
  984. Component[] component = this.getComponents();
  985. for (int i = 0 ; i < ncomponents ; i++) {
  986. Component comp = component[i];
  987. if (comp instanceof JMenu) {
  988. JMenu menu = (JMenu)comp;
  989. JPopupMenu subPopup = menu.getPopupMenu();
  990. if (subPopup == popup)
  991. return true;
  992. if (subPopup.isSubPopupMenu(popup))
  993. return true;
  994. }
  995. }
  996. return false;
  997. }
  998. private static Frame getFrame(Component c) {
  999. Component w = c;
  1000. while(!(w instanceof Frame) && (w!=null)) {
  1001. w = w.getParent();
  1002. }
  1003. return (Frame)w;
  1004. }
  1005. /**
  1006. * Returns a string representation of this <code>JPopupMenu</code>.
  1007. * This method
  1008. * is intended to be used only for debugging purposes, and the
  1009. * content and format of the returned string may vary between
  1010. * implementations. The returned string may be empty but may not
  1011. * be <code>null</code>.
  1012. *
  1013. * @return a string representation of this <code>JPopupMenu</code>.
  1014. */
  1015. protected String paramString() {
  1016. String labelString = (label != null ?
  1017. label : "");
  1018. String paintBorderString = (paintBorder ?
  1019. "true" : "false");
  1020. String marginString = (margin != null ?
  1021. margin.toString() : "");
  1022. String lightWeightPopupEnabledString = (isLightWeightPopupEnabled() ?
  1023. "true" : "false");
  1024. return super.paramString() +
  1025. ",desiredLocationX=" + desiredLocationX +
  1026. ",desiredLocationY=" + desiredLocationY +
  1027. ",label=" + labelString +
  1028. ",lightWeightPopupEnabled=" + lightWeightPopupEnabledString +
  1029. ",margin=" + marginString +
  1030. ",paintBorder=" + paintBorderString;
  1031. }
  1032. /////////////////
  1033. // Accessibility support
  1034. ////////////////
  1035. /**
  1036. * Gets the AccessibleContext associated with this JPopupMenu.
  1037. * For JPopupMenus, the AccessibleContext takes the form of an
  1038. * AccessibleJPopupMenu.
  1039. * A new AccessibleJPopupMenu instance is created if necessary.
  1040. *
  1041. * @return an AccessibleJPopupMenu that serves as the
  1042. * AccessibleContext of this JPopupMenu
  1043. */
  1044. public AccessibleContext getAccessibleContext() {
  1045. if (accessibleContext == null) {
  1046. accessibleContext = new AccessibleJPopupMenu();
  1047. }
  1048. return accessibleContext;
  1049. }
  1050. /**
  1051. * This class implements accessibility support for the
  1052. * <code>JPopupMenu</code> class. It provides an implementation of the
  1053. * Java Accessibility API appropriate to popup menu user-interface
  1054. * elements.
  1055. */
  1056. protected class AccessibleJPopupMenu extends AccessibleJComponent {
  1057. /**
  1058. * Get the role of this object.
  1059. *
  1060. * @return an instance of AccessibleRole describing the role of
  1061. * the object
  1062. */
  1063. public AccessibleRole getAccessibleRole() {
  1064. return AccessibleRole.POPUP_MENU;
  1065. }
  1066. } // inner class AccessibleJPopupMenu
  1067. ////////////
  1068. // Serialization support.
  1069. ////////////
  1070. private void writeObject(ObjectOutputStream s) throws IOException {
  1071. Vector values = new Vector();
  1072. s.defaultWriteObject();
  1073. // Save the invoker, if its Serializable.
  1074. if(invoker != null && invoker instanceof Serializable) {
  1075. values.addElement("invoker");
  1076. values.addElement(invoker);
  1077. }
  1078. // Save the popup, if its Serializable.
  1079. if(popup != null && popup instanceof Serializable) {
  1080. values.addElement("popup");
  1081. values.addElement(popup);
  1082. }
  1083. s.writeObject(values);
  1084. if (getUIClassID().equals(uiClassID)) {
  1085. byte count = JComponent.getWriteObjCounter(this);
  1086. JComponent.setWriteObjCounter(this, --count);
  1087. if (count == 0 && ui != null) {
  1088. ui.installUI(this);
  1089. }
  1090. }
  1091. }
  1092. // implements javax.swing.MenuElement
  1093. private void readObject(ObjectInputStream s)
  1094. throws IOException, ClassNotFoundException {
  1095. s.defaultReadObject();
  1096. Vector values = (Vector)s.readObject();
  1097. int indexCounter = 0;
  1098. int maxCounter = values.size();
  1099. if(indexCounter < maxCounter && values.elementAt(indexCounter).
  1100. equals("invoker")) {
  1101. invoker = (Component)values.elementAt(++indexCounter);
  1102. indexCounter++;
  1103. }
  1104. if(indexCounter < maxCounter && values.elementAt(indexCounter).
  1105. equals("popup")) {
  1106. popup = (Popup)values.elementAt(++indexCounter);
  1107. indexCounter++;
  1108. }
  1109. }
  1110. /**
  1111. * This method is required to conform to the
  1112. * <code>MenuElement</code> interface, but it not implemented.
  1113. * @see MenuElement#processMouseEvent(MouseEvent, MenuElement[], MenuSelectionManager)
  1114. */
  1115. public void processMouseEvent(MouseEvent event,MenuElement path[],MenuSelectionManager manager) {}
  1116. /**
  1117. * This method is required to conform to the
  1118. * <code>MenuElement</code> interface, but it not implemented.
  1119. * @see MenuElement#processKeyEvent(KeyEvent, MenuElement[], MenuSelectionManager)
  1120. */
  1121. public void processKeyEvent(KeyEvent e,MenuElement path[],MenuSelectionManager manager) {
  1122. }
  1123. /**
  1124. * Messaged when the menubar selection changes to activate or
  1125. * deactivate this menu. This implements the
  1126. * <code>javax.swing.MenuElement</code> interface.
  1127. * Overrides <code>MenuElement.menuSelectionChanged</code>.
  1128. *
  1129. * @param isIncluded true if this menu is active, false if
  1130. * it is not
  1131. * @see MenuElement#menuSelectionChanged(boolean)
  1132. */
  1133. public void menuSelectionChanged(boolean isIncluded) {
  1134. if (DEBUG) {
  1135. System.out.println("In JPopupMenu.menuSelectionChanged " + isIncluded);
  1136. }
  1137. if(invoker instanceof JMenu) {
  1138. JMenu m = (JMenu) invoker;
  1139. if(isIncluded)
  1140. m.setPopupMenuVisible(true);
  1141. else
  1142. m.setPopupMenuVisible(false);
  1143. }
  1144. if (isPopupMenu() && !isIncluded)
  1145. setVisible(false);
  1146. }
  1147. /**
  1148. * Returns an array of <code>MenuElement</code>s containing the submenu
  1149. * for this menu component. It will only return items conforming to
  1150. * the <code>JMenuElement</code> interface.
  1151. * If popup menu is <code>null</code> returns
  1152. * an empty array. This method is required to conform to the
  1153. * <code>MenuElement</code> interface.
  1154. *
  1155. * @return an array of <code>MenuElement</code> objects
  1156. * @see MenuElement#getSubElements
  1157. */
  1158. public MenuElement[] getSubElements() {
  1159. MenuElement result[];
  1160. Vector tmp = new Vector();
  1161. int c = getComponentCount();
  1162. int i;
  1163. Component m;
  1164. for(i=0 ; i < c ; i++) {
  1165. m = getComponent(i);
  1166. if(m instanceof MenuElement)
  1167. tmp.addElement(m);
  1168. }
  1169. result = new MenuElement[tmp.size()];
  1170. for(i=0,c=tmp.size() ; i < c ; i++)
  1171. result[i] = (MenuElement) tmp.elementAt(i);
  1172. return result;
  1173. }
  1174. /**
  1175. * Returns this <code>JPopupMenu</code> component.
  1176. * @return this <code>JPopupMenu</code> object
  1177. * @see MenuElement#getComponent
  1178. */
  1179. public Component getComponent() {
  1180. return this;
  1181. }
  1182. /**
  1183. * A popup menu-specific separator.
  1184. */
  1185. static public class Separator extends JSeparator
  1186. {
  1187. public Separator( )
  1188. {
  1189. super( JSeparator.HORIZONTAL );
  1190. }
  1191. /**
  1192. * Returns the name of the L&F class that renders this component.
  1193. *
  1194. * @return the string "PopupMenuSeparatorUI"
  1195. * @see JComponent#getUIClassID
  1196. * @see UIDefaults#getUI
  1197. */
  1198. public String getUIClassID()
  1199. {
  1200. return "PopupMenuSeparatorUI";
  1201. }
  1202. }
  1203. /**
  1204. * Returns true if the <code>MouseEvent</code> is considered a popup trigger
  1205. * by the <code>JPopupMenu</code>'s currently installed UI.
  1206. *
  1207. * @return true if the mouse event is a popup trigger
  1208. * @since 1.3
  1209. */
  1210. public boolean isPopupTrigger(MouseEvent e) {
  1211. return getUI().isPopupTrigger(e);
  1212. }
  1213. }