JMenuButton.java
001 /*
002  *  Copyright (c) 1995-2010, The University of Sheffield. See the file
003  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
004  *
005  *  This file is part of GATE (see http://gate.ac.uk/), and is free
006  *  software, licenced under the GNU Library General Public License,
007  *  Version 2, June 1991 (in the distribution as file licence.html,
008  *  and also available at http://gate.ac.uk/gate/licence.html).
009  *
010  *  JMenuButton.java
011  *
012  *  Valentin Tablan, 21 Aug 2008
013  *
014  *  $Id: JMenuButton.java 12305 2010-02-25 13:54:10Z ian_roberts $
015  */
016 
017 package gate.swing;
018 
019 import java.awt.*;
020 import java.awt.event.ActionEvent;
021 import java.awt.event.ActionListener;
022 
023 import javax.swing.*;
024 import javax.swing.event.PopupMenuEvent;
025 import javax.swing.event.PopupMenuListener;
026 
027 /**
028  * A toggle button that shows a pop-up menu.
029  */
030 public class JMenuButton extends JToggleButton {
031   public JMenuButton(JMenu menu) {
032     this(menu.getPopupMenu());
033     this.menu = menu;
034   }
035   
036   public JMenuButton(JPopupMenu popup) {
037     this.popup = popup;
038     popup.setInvoker(this);
039     initListeners();
040   }
041 
042   protected void initListeners() {
043     
044     addActionListener(new ActionListener() {
045 
046       public void actionPerformed(ActionEvent e) {
047         if(menu != nullmenu.setSelected(isSelected());
048         if(isSelected()) {
049           // show the popup
050           Point p = getPopupMenuOrigin();
051           popup.show(JMenuButton.this, p.x, p.y);
052         }
053         else {
054           // hide the popup
055           popup.setVisible(false);
056         }
057       }
058     });
059     
060     popup.addPopupMenuListener(new PopupMenuListener(){
061 
062       public void popupMenuCanceled(PopupMenuEvent e) {
063         setSelected(false);
064         if(menu != nullmenu.setSelected(false);
065       }
066 
067       public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
068         setSelected(false);
069         if(menu != nullmenu.setSelected(false);
070       }
071 
072       public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
073       }
074     });
075   }
076 
077   /**
078    * Method largely borrowed from Swing's JMenu.
079    
080    * Computes the origin for the <code>JMenu</code>'s popup menu. This
081    * method uses Look and Feel properties named
082    <code>Menu.menuPopupOffsetX</code>,
083    <code>Menu.menuPopupOffsetY</code>,
084    <code>Menu.submenuPopupOffsetX</code>, and
085    <code>Menu.submenuPopupOffsetY</code> to adjust the exact location
086    * of popup.
087    
088    @return <code>Point</code> in the coordinate space of the menu
089    *         which should be used as the origin of the
090    *         <code>JMenu</code>'s popup menu
091    */
092   protected Point getPopupMenuOrigin() {
093     int x = 0;
094     int y = 0;
095     // Figure out the sizes needed to caclulate the menu position
096     Dimension s = getSize();
097     Dimension pmSize = popup.getSize();
098     // For the first time the menu is popped up,
099     // the size has not yet been initiated
100     if(pmSize.width == 0) {
101       pmSize = popup.getPreferredSize();
102     }
103     Point position = getLocationOnScreen();
104     Toolkit toolkit = Toolkit.getDefaultToolkit();
105     GraphicsConfiguration gc = getGraphicsConfiguration();
106     Rectangle screenBounds = new Rectangle(toolkit.getScreenSize());
107     GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
108     GraphicsDevice[] gd = ge.getScreenDevices();
109     for(int i = 0; i < gd.length; i++) {
110       if(gd[i].getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
111         GraphicsConfiguration dgc = gd[i].getDefaultConfiguration();
112         if(dgc.getBounds().contains(position)) {
113           gc = dgc;
114           break;
115         }
116       }
117     }
118 
119     if(gc != null) {
120       screenBounds = gc.getBounds();
121       // take screen insets (e.g. taskbar) into account
122       Insets screenInsets = toolkit.getScreenInsets(gc);
123 
124       screenBounds.width -= Math.abs(screenInsets.left + screenInsets.right);
125       screenBounds.height -= Math.abs(screenInsets.top + screenInsets.bottom);
126       position.x -= Math.abs(screenInsets.left);
127       position.y -= Math.abs(screenInsets.top);
128     }
129 
130     // We are a toplevel menu (pull-down)
131     int xOffset = UIManager.getInt("Menu.menuPopupOffsetX");
132     int yOffset = UIManager.getInt("Menu.menuPopupOffsetY");
133 
134     if(getComponentOrientation().isLeftToRight()) {
135       // First determine the x:
136       x = xOffset; // Extend to the right
137       if(position.x + x + pmSize.width >= screenBounds.width + screenBounds.x &&
138       // popup doesn't fit - place it wherever there's more
139               // room
140               screenBounds.width - s.width < (position.x - screenBounds.x)) {
141 
142         x = s.width - xOffset - pmSize.width;
143       }
144     }
145     else {
146       // First determine the x:
147       x = s.width - xOffset - pmSize.width; // Extend to the left
148       if(position.x + x < screenBounds.x &&
149       // popup doesn't fit - place it wherever there's more room
150               screenBounds.width - s.width > (position.x - screenBounds.x)) {
151 
152         x = xOffset;
153       }
154     }
155     // Then the y:
156     y = s.height + yOffset; // Prefer dropping down
157     if(position.y + y + pmSize.height >= screenBounds.height &&
158     // popup doesn't fit - place it wherever there's more room
159             screenBounds.height - s.height < (position.y - screenBounds.y)) {
160 
161       y = - yOffset - pmSize.height; // Otherwise drop 'up'
162     }
163     return new Point(x, y);
164   }
165 
166   protected JPopupMenu popup;
167   protected JMenu menu;
168 }