JMenuButton.java
001 /*
002  *  Copyright (c) 1995-2012, 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 17608 2014-03-09 12:38:50Z markagreenwood $
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 @SuppressWarnings("serial")
031 public class JMenuButton extends JToggleButton {
032   public JMenuButton(JMenu menu) {
033     this(menu.getPopupMenu());
034     this.menu = menu;
035   }
036   
037   public JMenuButton(JPopupMenu popup) {
038     this.popup = popup;
039     popup.setInvoker(this);
040     initListeners();
041   }
042 
043   protected void initListeners() {
044     
045     addActionListener(new ActionListener() {
046 
047       @Override
048       public void actionPerformed(ActionEvent e) {
049         if(menu != nullmenu.setSelected(isSelected());
050         if(isSelected()) {
051           // show the popup
052           Point p = getPopupMenuOrigin();
053           popup.show(JMenuButton.this, p.x, p.y);
054         }
055         else {
056           // hide the popup
057           popup.setVisible(false);
058         }
059       }
060     });
061     
062     popup.addPopupMenuListener(new PopupMenuListener(){
063 
064       @Override
065       public void popupMenuCanceled(PopupMenuEvent e) {
066         setSelected(false);
067         if(menu != nullmenu.setSelected(false);
068       }
069 
070       @Override
071       public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
072         setSelected(false);
073         if(menu != nullmenu.setSelected(false);
074       }
075 
076       @Override
077       public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
078       }
079     });
080   }
081 
082   /**
083    * Method largely borrowed from Swing's JMenu.
084    
085    * Computes the origin for the <code>JMenu</code>'s popup menu. This
086    * method uses Look and Feel properties named
087    <code>Menu.menuPopupOffsetX</code>,
088    <code>Menu.menuPopupOffsetY</code>,
089    <code>Menu.submenuPopupOffsetX</code>, and
090    <code>Menu.submenuPopupOffsetY</code> to adjust the exact location
091    * of popup.
092    
093    @return <code>Point</code> in the coordinate space of the menu
094    *         which should be used as the origin of the
095    *         <code>JMenu</code>'s popup menu
096    */
097   protected Point getPopupMenuOrigin() {
098     int x = 0;
099     int y = 0;
100     // Figure out the sizes needed to caclulate the menu position
101     Dimension s = getSize();
102     Dimension pmSize = popup.getSize();
103     // For the first time the menu is popped up,
104     // the size has not yet been initiated
105     if(pmSize.width == 0) {
106       pmSize = popup.getPreferredSize();
107     }
108     Point position = getLocationOnScreen();
109     Toolkit toolkit = Toolkit.getDefaultToolkit();
110     GraphicsConfiguration gc = getGraphicsConfiguration();
111     Rectangle screenBounds = new Rectangle(toolkit.getScreenSize());
112     GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
113     GraphicsDevice[] gd = ge.getScreenDevices();
114     for(int i = 0; i < gd.length; i++) {
115       if(gd[i].getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
116         GraphicsConfiguration dgc = gd[i].getDefaultConfiguration();
117         if(dgc.getBounds().contains(position)) {
118           gc = dgc;
119           break;
120         }
121       }
122     }
123 
124     if(gc != null) {
125       screenBounds = gc.getBounds();
126       // take screen insets (e.g. taskbar) into account
127       Insets screenInsets = toolkit.getScreenInsets(gc);
128 
129       screenBounds.width -= Math.abs(screenInsets.left + screenInsets.right);
130       screenBounds.height -= Math.abs(screenInsets.top + screenInsets.bottom);
131       position.x -= Math.abs(screenInsets.left);
132       position.y -= Math.abs(screenInsets.top);
133     }
134 
135     // We are a toplevel menu (pull-down)
136     int xOffset = UIManager.getInt("Menu.menuPopupOffsetX");
137     int yOffset = UIManager.getInt("Menu.menuPopupOffsetY");
138 
139     if(getComponentOrientation().isLeftToRight()) {
140       // First determine the x:
141       x = xOffset; // Extend to the right
142       if(position.x + x + pmSize.width >= screenBounds.width + screenBounds.x &&
143       // popup doesn't fit - place it wherever there's more
144               // room
145               screenBounds.width - s.width < (position.x - screenBounds.x)) {
146 
147         x = s.width - xOffset - pmSize.width;
148       }
149     }
150     else {
151       // First determine the x:
152       x = s.width - xOffset - pmSize.width; // Extend to the left
153       if(position.x + x < screenBounds.x &&
154       // popup doesn't fit - place it wherever there's more room
155               screenBounds.width - s.width > (position.x - screenBounds.x)) {
156 
157         x = xOffset;
158       }
159     }
160     // Then the y:
161     y = s.height + yOffset; // Prefer dropping down
162     if(position.y + y + pmSize.height >= screenBounds.height &&
163     // popup doesn't fit - place it wherever there's more room
164             screenBounds.height - s.height < (position.y - screenBounds.y)) {
165 
166       y = - yOffset - pmSize.height; // Otherwise drop 'up'
167     }
168     return new Point(x, y);
169   }
170 
171   protected JPopupMenu popup;
172   protected JMenu menu;
173 }