diff options
Diffstat (limited to 'libjava/classpath/javax/swing/plaf/basic/BasicPopupMenuUI.java')
-rw-r--r-- | libjava/classpath/javax/swing/plaf/basic/BasicPopupMenuUI.java | 674 |
1 files changed, 674 insertions, 0 deletions
diff --git a/libjava/classpath/javax/swing/plaf/basic/BasicPopupMenuUI.java b/libjava/classpath/javax/swing/plaf/basic/BasicPopupMenuUI.java new file mode 100644 index 00000000000..247117bc983 --- /dev/null +++ b/libjava/classpath/javax/swing/plaf/basic/BasicPopupMenuUI.java @@ -0,0 +1,674 @@ +/* BasicPopupMenuUI.java + Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + +package javax.swing.plaf.basic; + +import java.awt.AWTEvent; +import java.awt.Component; +import java.awt.Container; +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Point; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.MouseEvent; + +import javax.swing.BoxLayout; +import javax.swing.JComponent; +import javax.swing.JLayeredPane; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; +import javax.swing.MenuElement; +import javax.swing.MenuSelectionManager; +import javax.swing.RootPaneContainer; +import javax.swing.SwingUtilities; +import javax.swing.UIDefaults; +import javax.swing.UIManager; +import javax.swing.event.MouseInputListener; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; +import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.PopupMenuUI; + + +/** + * UI Delegate for JPopupMenu + */ +public class BasicPopupMenuUI extends PopupMenuUI +{ + /* popupMenu for which this UI delegate is for*/ + protected JPopupMenu popupMenu; + + /* MouseInputListener listens to mouse events. Package private for inner classes. */ + static transient MouseInputListener mouseInputListener; + + /* PopupMenuListener listens to popup menu events fired by JPopupMenu*/ + private transient PopupMenuListener popupMenuListener; + + /* ComponentListener listening to popupMenu's invoker. + * This is package-private to avoid an accessor method. */ + TopWindowListener topWindowListener; + + /** + * Creates a new BasicPopupMenuUI object. + */ + public BasicPopupMenuUI() + { + popupMenuListener = new PopupMenuHandler(); + topWindowListener = new TopWindowListener(); + } + + /** + * Factory method to create a BasicPopupMenuUI for the given {@link + * JComponent}, which should be a {@link JMenuItem}. + * + * @param x The {@link JComponent} a UI is being created for. + * + * @return A BasicPopupMenuUI for the {@link JComponent}. + */ + public static ComponentUI createUI(JComponent x) + { + return new BasicPopupMenuUI(); + } + + /** + * Installs and initializes all fields for this UI delegate. Any properties + * of the UI that need to be initialized and/or set to defaults will be + * done now. It will also install any listeners necessary. + * + * @param c The {@link JComponent} that is having this UI installed. + */ + public void installUI(JComponent c) + { + super.installUI(c); + popupMenu = (JPopupMenu) c; + popupMenu.setLayout(new DefaultMenuLayout(popupMenu, BoxLayout.Y_AXIS)); + popupMenu.setBorderPainted(true); + JPopupMenu.setDefaultLightWeightPopupEnabled(true); + + installDefaults(); + installListeners(); + } + + /** + * This method installs the defaults that are defined in the Basic look + * and feel for this {@link JPopupMenu}. + */ + public void installDefaults() + { + UIDefaults defaults = UIManager.getLookAndFeelDefaults(); + + popupMenu.setBackground(defaults.getColor("PopupMenu.background")); + popupMenu.setBorder(defaults.getBorder("PopupMenu.border")); + popupMenu.setFont(defaults.getFont("PopupMenu.font")); + popupMenu.setForeground(defaults.getColor("PopupMenu.foreground")); + popupMenu.setOpaque(true); + } + + /** + * This method installs the listeners for the {@link JMenuItem}. + */ + protected void installListeners() + { + popupMenu.addPopupMenuListener(popupMenuListener); + } + + /** + * This method installs the keyboard actions for this {@link JPopupMenu}. + */ + protected void installKeyboardActions() + { + // FIXME: Need to implement + } + + /** + * Performs the opposite of installUI. Any properties or resources that need + * to be cleaned up will be done now. It will also uninstall any listeners + * it has. In addition, any properties of this UI will be nulled. + * + * @param c The {@link JComponent} that is having this UI uninstalled. + */ + public void uninstallUI(JComponent c) + { + uninstallListeners(); + uninstallDefaults(); + popupMenu = null; + } + + /** + * This method uninstalls the defaults and sets any objects created during + * install to null + */ + protected void uninstallDefaults() + { + popupMenu.setBackground(null); + popupMenu.setBorder(null); + popupMenu.setFont(null); + popupMenu.setForeground(null); + } + + /** + * Unregisters all the listeners that this UI delegate was using. + */ + protected void uninstallListeners() + { + popupMenu.removePopupMenuListener(popupMenuListener); + } + + /** + * Uninstalls any keyboard actions. + */ + protected void uninstallKeyboardActions() + { + // FIXME: Need to implement + } + + /** + * This method returns the minimum size of the JPopupMenu. + * + * @param c The JComponent to find a size for. + * + * @return The minimum size. + */ + public Dimension getMinimumSize(JComponent c) + { + return null; + } + + /** + * This method returns the preferred size of the JPopupMenu. + * + * @param c The JComponent to find a size for. + * + * @return The preferred size. + */ + public Dimension getPreferredSize(JComponent c) + { + return null; + } + + /** + * This method returns the minimum size of the JPopupMenu. + * + * @param c The JComponent to find a size for. + * + * @return The minimum size. + */ + public Dimension getMaximumSize(JComponent c) + { + return null; + } + + /** + * Return true if given mouse event is a platform popup trigger, and false + * otherwise + * + * @param e MouseEvent that is to be checked for popup trigger event + * + * @return true if given mouse event is a platform popup trigger, and false + * otherwise + */ + public boolean isPopupTrigger(MouseEvent e) + { + return false; + } + + /** + * This listener handles PopupMenuEvents fired by JPopupMenu + */ + private class PopupMenuHandler implements PopupMenuListener + { + /** + * This method is invoked when JPopupMenu is cancelled. + * + * @param event the PopupMenuEvent + */ + public void popupMenuCanceled(PopupMenuEvent event) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.clearSelectedPath(); + } + + /** + * This method is invoked when JPopupMenu becomes invisible + * + * @param event the PopupMenuEvent + */ + public void popupMenuWillBecomeInvisible(PopupMenuEvent event) + { + // remove listener that listens to component events fired + // by the top - level window that this popup belongs to. + Component invoker = popupMenu.getInvoker(); + + RootPaneContainer rootContainer = (RootPaneContainer) SwingUtilities + .getRoot(invoker); + ((Container) rootContainer).removeComponentListener(topWindowListener); + + // If this popup menu is the last popup menu visible on the screen, then + // stop interrupting mouse events in the glass pane before hiding this + // last popup menu. + boolean topLevelMenu = (popupMenu.getInvoker() instanceof JMenu) + && ((JMenu) popupMenu.getInvoker()) + .isTopLevelMenu(); + + if (topLevelMenu || ! (popupMenu.getInvoker() instanceof MenuElement)) + { + // set glass pane not to interrupt mouse events and remove + // mouseInputListener + Container glassPane = (Container) rootContainer.getGlassPane(); + glassPane.setVisible(false); + glassPane.removeMouseListener(mouseInputListener); + mouseInputListener = null; + } + } + + /** + * This method is invoked when JPopupMenu becomes visible + * + * @param event the PopupMenuEvent + */ + public void popupMenuWillBecomeVisible(PopupMenuEvent event) + { + // Adds topWindowListener to top-level window to listener to + // ComponentEvents fired by it. We need to cancel this popup menu + // if topWindow to which this popup belongs was resized or moved. + Component invoker = popupMenu.getInvoker(); + RootPaneContainer rootContainer = (RootPaneContainer) SwingUtilities + .getRoot(invoker); + ((Container) rootContainer).addComponentListener(topWindowListener); + + // Set the glass pane to interrupt all mouse events originating in root + // container + if (mouseInputListener == null) + { + Container glassPane = (Container) rootContainer.getGlassPane(); + glassPane.setVisible(true); + mouseInputListener = new MouseInputHandler(rootContainer); + glassPane.addMouseListener(mouseInputListener); + glassPane.addMouseMotionListener(mouseInputListener); + } + + // if this popup menu is a free floating popup menu, + // then by default its first element should be always selected when + // this popup menu becomes visible. + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + + if (manager.getSelectedPath().length == 0) + { + // Set selected path to point to the first item in the popup menu + MenuElement[] path = new MenuElement[2]; + path[0] = popupMenu; + Component[] comps = popupMenu.getComponents(); + if (comps.length != 0 && comps[0] instanceof MenuElement) + { + path[1] = (MenuElement) comps[0]; + manager.setSelectedPath(path); + } + } + } + } + + /** + * ComponentListener that listens to Component Events fired by the top - + * level window to which popup menu belongs. If top-level window was + * resized, moved or hidded then popup menu will be hidded and selected + * path of current menu hierarchy will be set to null. + */ + private class TopWindowListener implements ComponentListener + { + /** + * This method is invoked when top-level window is resized. This method + * closes current menu hierarchy. + * + * @param e The ComponentEvent + */ + public void componentResized(ComponentEvent e) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.clearSelectedPath(); + } + + /** + * This method is invoked when top-level window is moved. This method + * closes current menu hierarchy. + * + * @param e The ComponentEvent + */ + public void componentMoved(ComponentEvent e) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.clearSelectedPath(); + } + + /** + * This method is invoked when top-level window is shown This method + * does nothing by default. + * + * @param e The ComponentEvent + */ + public void componentShown(ComponentEvent e) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.clearSelectedPath(); + } + + /** + * This method is invoked when top-level window is hidden This method + * closes current menu hierarchy. + * + * @param e The ComponentEvent + */ + public void componentHidden(ComponentEvent e) + { + MenuSelectionManager manager = MenuSelectionManager.defaultManager(); + manager.clearSelectedPath(); + } + } + + /** + * MouseInputHandler listens to all mouse events originated in the root + * container. This class is responsible for closing menu hierarchy when the + * user presses mouse over any component that do not belong to the current + * menu hierarchy. This is acomplished by interrupting all mouse event in + * the glass pane and checking if other component was pressed while menu + * was open, before redestributing events further to intended components + */ + private class MouseInputHandler implements MouseInputListener + { + private JLayeredPane layeredPane; + private Container glassPane; + private Cursor nativeCursor; + private transient Component mouseEventTarget; + private transient Component pressedComponent; + private transient Component lastComponentEntered; + private transient Component tempComponent; + private transient int pressCount; + + /** + * Creates a new MouseInputHandler object. + * + * @param c the top most root container + */ + public MouseInputHandler(RootPaneContainer c) + { + layeredPane = c.getLayeredPane(); + glassPane = (Container) c.getGlassPane(); + } + + /** + * Handles mouse clicked event + * + * @param e Mouse event + */ + public void mouseClicked(MouseEvent e) + { + handleEvent(e); + } + + /** + * Handles mouseDragged event + * + * @param e MouseEvent + */ + public void mouseDragged(MouseEvent e) + { + handleEvent(e); + } + + /** + * Handles mouseEntered event + * + * @param e MouseEvent + */ + public void mouseEntered(MouseEvent e) + { + handleEvent(e); + } + + /** + * Handles mouseExited event + * + * @param e MouseEvent + */ + public void mouseExited(MouseEvent e) + { + handleEvent(e); + } + + /** + * Handles mouse moved event + * + * @param e MouseEvent + */ + public void mouseMoved(MouseEvent e) + { + handleEvent(e); + } + + /** + * Handles mouse pressed event + * + * @param e MouseEvent + */ + public void mousePressed(MouseEvent e) + { + handleEvent(e); + } + + /** + * Handles mouse released event + * + * @param e MouseEvent + */ + public void mouseReleased(MouseEvent e) + { + handleEvent(e); + } + + /* + * This method determines component that was intended to received mouse + * event, before it was interrupted within the glass pane. This method + * also redispatches mouse entered and mouse exited events to the + * appropriate components. This code is slightly modified code from + * Container.LightweightDispatcher class, which is private inside + * Container class and cannot be used here. + */ + public void acquireComponentForMouseEvent(MouseEvent me) + { + int x = me.getX(); + int y = me.getY(); + + // Find the candidate which should receive this event. + Component parent = layeredPane; + Component candidate = null; + Point p = me.getPoint(); + while ((candidate == null) && (parent != null)) + { + p = SwingUtilities.convertPoint(glassPane, p.x, p.y, parent); + candidate = SwingUtilities.getDeepestComponentAt(parent, p.x, p.y); + + if (candidate == null) + { + p = SwingUtilities.convertPoint(parent, p.x, p.y, + parent.getParent()); + parent = parent.getParent(); + } + } + + // If the only candidate we found was the native container itself, + // don't dispatch any event at all. We only care about the lightweight + // children here. + if (candidate == layeredPane) + candidate = null; + + // If our candidate is new, inform the old target we're leaving. + if ((lastComponentEntered != null) && lastComponentEntered.isShowing() + && (lastComponentEntered != candidate)) + { + // Old candidate could have been removed from + // the layeredPane so we check first. + if (SwingUtilities.isDescendingFrom(lastComponentEntered, layeredPane)) + { + Point tp = SwingUtilities.convertPoint(layeredPane, x, y, + lastComponentEntered); + MouseEvent exited = new MouseEvent(lastComponentEntered, + MouseEvent.MOUSE_EXITED, + me.getWhen(), + me.getModifiersEx(), tp.x, + tp.y, me.getClickCount(), + me.isPopupTrigger(), + me.getButton()); + + tempComponent = lastComponentEntered; + lastComponentEntered = null; + tempComponent.dispatchEvent(exited); + } + + lastComponentEntered = null; + } + + // If we have a candidate, maybe enter it. + if (candidate != null) + { + mouseEventTarget = candidate; + + if (candidate.isLightweight() && candidate.isShowing() + && (candidate != layeredPane) + && (candidate != lastComponentEntered)) + { + lastComponentEntered = mouseEventTarget; + + Point cp = SwingUtilities.convertPoint(layeredPane, x, y, + lastComponentEntered); + MouseEvent entered = new MouseEvent(lastComponentEntered, + MouseEvent.MOUSE_ENTERED, + me.getWhen(), + me.getModifiersEx(), cp.x, + cp.y, me.getClickCount(), + me.isPopupTrigger(), + me.getButton()); + lastComponentEntered.dispatchEvent(entered); + } + } + + if ((me.getID() == MouseEvent.MOUSE_RELEASED) + || ((me.getID() == MouseEvent.MOUSE_PRESSED) && (pressCount > 0)) + || (me.getID() == MouseEvent.MOUSE_DRAGGED)) + { + // If any of the following events occur while a button is held down, + // they should be dispatched to the same component to which the + // original MOUSE_PRESSED event was dispatched: + // - MOUSE_RELEASED + // - MOUSE_PRESSED: another button pressed while the first is held down + // - MOUSE_DRAGGED + if (SwingUtilities.isDescendingFrom(pressedComponent, layeredPane)) + mouseEventTarget = pressedComponent; + else if (me.getID() == MouseEvent.MOUSE_CLICKED) + { + // Don't dispatch CLICKED events whose target is not the same as the + // target for the original PRESSED event. + if (candidate != pressedComponent) + mouseEventTarget = null; + else if (pressCount == 0) + pressedComponent = null; + } + } + } + + /* + * This method handles mouse events interrupted by glassPane. It + * redispatches the mouse events appropriately to the intended components. + * The code in this method is also taken from + * Container.LightweightDispatcher class. The code is slightly modified + * to handle the case when mouse is released over non-menu component. In + * this case this method closes current menu hierarchy before + * redispatching the event further. + */ + public void handleEvent(AWTEvent e) + { + if (e instanceof MouseEvent) + { + MouseEvent me = (MouseEvent) e; + + acquireComponentForMouseEvent(me); + + // Avoid dispatching ENTERED and EXITED events twice. + if (mouseEventTarget != null && mouseEventTarget.isShowing() + && (e.getID() != MouseEvent.MOUSE_ENTERED) + && (e.getID() != MouseEvent.MOUSE_EXITED)) + { + MouseEvent newEvt = SwingUtilities.convertMouseEvent(glassPane, + me, + mouseEventTarget); + + mouseEventTarget.dispatchEvent(newEvt); + + // If mouse was clicked over the component that is not part + // of menu hierarchy,then must close the menu hierarchy */ + if (e.getID() == MouseEvent.MOUSE_RELEASED) + { + boolean partOfMenuHierarchy = false; + MenuSelectionManager manager = MenuSelectionManager + .defaultManager(); + + partOfMenuHierarchy = manager.isComponentPartOfCurrentMenu(mouseEventTarget); + + if (! partOfMenuHierarchy) + manager.clearSelectedPath(); + } + + switch (e.getID()) + { + case MouseEvent.MOUSE_PRESSED: + if (pressCount++ == 0) + pressedComponent = mouseEventTarget; + break; + case MouseEvent.MOUSE_RELEASED: + // Clear our memory of the original PRESSED event, only if + // we're not expecting a CLICKED event after this. If + // there is a CLICKED event after this, it will do clean up. + if ((--pressCount == 0) + && (mouseEventTarget != pressedComponent)) + pressedComponent = null; + break; + } + } + } + } + } +} |