001    /*
002     * LAPIS lightweight structured text processing system
003     *
004     * Copyright (C) 1998-2002 Carnegie Mellon University,
005     * Copyright (C) 2003 Massachusetts Institute of Technology.
006     * All rights reserved.
007     *
008     * This library is free software; you can redistribute it
009     * and/or modify it under the terms of the GNU General
010     * Public License as published by the Free Software
011     * Foundation, version 2.
012     *
013     * LAPIS homepage: http://graphics.lcs.mit.edu/lapis/
014     */
015    
016    package lapisx.swing;
017    
018    import java.awt.*;
019    import java.awt.event.*;
020    import javax.swing.*;
021    
022    /**
023     * A popup menu that appears only after the mouse is held down for 
024     * a certain period of time.
025     */
026    public class DelayedPopupMenu {
027        Component frame;
028        Component trigger;
029        Timer timer;
030        JPopupMenu menu;
031        
032    
033        /**
034         * Make a delayed popup menu.
035         * @param frame Component in which the popup menu will appear (see
036         * {@link javax.swing.JPopupMenu#show}.  This is often a JFrame.
037         * @param trigger Component which should trigger the menu.
038         * @param delay Delay before showing menu, in milliseconds.
039         * @effects Creates a DelayedPopupMenu and attaches a mouse listener
040         * to <i>trigger</i> so that when the mouse is pressed down <i>trigger</i>
041         * and held for more than <i>delay</i> msec, the menu appears.
042         */
043        public DelayedPopupMenu (Component frame, Component trigger, int delay) {
044            this.frame = frame;
045            this.trigger = trigger;
046    
047            trigger.addMouseListener (new MouseAdapter () {
048                    public void mousePressed (MouseEvent event) {
049                        timer.restart ();
050                    }
051                    public void mouseReleased (MouseEvent event) {
052                        timer.stop ();
053    
054                        if (menu != null
055                            && menu.isVisible ()
056                            && !menu.contains 
057                            (SwingUtilities.convertPoint ((Component) event.getSource (),
058                                                          event.getPoint (),
059                                                          menu)))
060                            menu.setVisible (false);
061                    }
062                });
063    
064            timer = new Timer (delay, new ActionListener () {
065                    public void actionPerformed (ActionEvent event) {
066                        menu = makeMenu ();
067                        showMenu (menu);
068                    }
069                });
070            timer.setRepeats (false);
071        }
072    
073        /**
074         * Test whether menu is popped up.
075         * @return true iff menu is showing
076         */
077        public boolean isVisible () {
078            return menu != null && menu.isVisible ();
079        }
080    
081        /**
082         * Callback that constructs the menu just before showing it.
083         * Default implementation just creates an empty JPopupMenu.
084         * A subclass should override this to populate the menu.
085         * @return menu, or null to prevent popping up the menu
086         */
087        protected JPopupMenu makeMenu () {
088            return new JPopupMenu ();
089        }
090    
091        /**
092         * Callback that shows the menu.
093         * Default implementation positions the menu just below the trigger
094         * that triggered it in the frame passed to the constructor.
095         * A subclass can override this for different placement.
096         * @param menu Menu about to be shown
097         */
098        protected void showMenu (JPopupMenu menu) {
099            Point triggerLocation = 
100                SwingUtilities.convertPoint (trigger.getParent (),
101                                             trigger.getLocation (),
102                                             frame);
103            Dimension triggerSize = trigger.getSize ();
104            
105            int x = triggerLocation.x;
106            int y = triggerLocation.y + triggerSize.height;
107    
108            menu.show (frame, x, y);
109        }
110    
111        /**
112         * Get the component that triggers this menu.
113         * @return trigger component
114         */
115        public Component getTrigger () {
116            return trigger;            
117        }
118    
119        /**
120         * Get the frame in which this menu will popup.
121         * @return frame component
122         */
123        public Component getFrame () {
124            return frame;
125        }
126    }