This improves the positioning of JMenu popups, so that the screen bounds are checked and the popup placed wherever it fits best, if the default location isn't suitable.

It also fixes some small bug with the change listener that was pointed out by Eclipse.

2006-08-29  Roman Kennke  <[EMAIL PROTECTED]>

        * javax/swing/JMenu.java
        (getMenu): Removed unneeded cast.
        (getPopupMenuOrigin): Made positioning algorithm better respect
        the screen bounds.
        (setMenuLocation): Also set the location on the popup if it's
        not null.
        (setModel): Use menuChangeListener so that we don't override
        the changeListener field from AbstractButton.
        (setPopupMenuVisible): Use custom location if set, otherwise
        fallback to getPopupMenuOrigin().

/Roman
Index: javax/swing/JMenu.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/JMenu.java,v
retrieving revision 1.32
diff -u -1 -2 -r1.32 JMenu.java
--- javax/swing/JMenu.java	13 Aug 2006 20:38:02 -0000	1.32
+++ javax/swing/JMenu.java	30 Aug 2006 10:40:41 -0000
@@ -30,25 +30,32 @@
 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;
 
 import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
+import java.awt.Insets;
 import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
 import java.awt.event.KeyEvent;
 import java.awt.event.WindowAdapter;
 import java.awt.event.WindowEvent;
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.EventListener;
 
 import javax.accessibility.Accessible;
 import javax.accessibility.AccessibleContext;
 import javax.accessibility.AccessibleRole;
@@ -110,26 +117,28 @@
 
   /** Whenever menu is selected or deselected the MenuEvent is fired to
      menu's registered listeners. */
   private MenuEvent menuEvent = new MenuEvent(this);
 
   /*Amount of time, in milliseconds, that should pass before popupMenu
     associated with this menu appears or disappers */
   private int delay;
 
   /* PopupListener */
   protected WinListener popupListener;
 
-  /** Location at which popup menu associated with this menu will be
-     displayed */
+  /**
+   * Location at which popup menu associated with this menu will be
+   * displayed
+   */
   private Point menuLocation;
 
   /**
    * The ChangeListener for the ButtonModel.
    *
    * @see MenuChangeListener
    */
   private ChangeListener menuChangeListener;
 
   /**
    * Creates a new JMenu object.
    */
@@ -357,34 +366,34 @@
   {
     return "MenuUI";
   }
 
   /**
    * Sets model for this menu.
    *
    * @param model model to set
    */
   public void setModel(ButtonModel model)
   {
     ButtonModel oldModel = getModel();
-    if (oldModel != null && changeListener != null)
-      oldModel.removeChangeListener(changeListener);
+    if (oldModel != null && menuChangeListener != null)
+      oldModel.removeChangeListener(menuChangeListener);
 
     super.setModel(model);
 
     if (model != null)
       {
-        if (changeListener == null)
-          changeListener = new MenuChangeListener();
-        model.addChangeListener(changeListener);
+        if (menuChangeListener == null)
+          menuChangeListener = new MenuChangeListener();
+        model.addChangeListener(menuChangeListener);
       }
   }
 
   /**
    * Returns true if the menu is selected and false otherwise
    *
    * @return true if the menu is selected and false otherwise
    */
   public boolean isSelected()
   {
     return super.isSelected();
   }
@@ -416,53 +425,137 @@
   /**
    * Sets popup menu visibility
    *
    * @param popup true if popup should be visible and false otherwise
    */
   public void setPopupMenuVisible(boolean popup)
   {
     if (popup != isPopupMenuVisible() && (isEnabled() || ! popup))
       {
         if (popup && isShowing())
           {
             // Set location as determined by getPopupLocation().
-            Point loc = getPopupMenuOrigin();
+            Point loc = menuLocation == null ? getPopupMenuOrigin()
+                                             : menuLocation;
             getPopupMenu().show(this, loc.x, loc.y);
           }
         else
           getPopupMenu().setVisible(false);
       }
   }
 
   /**
-   * Returns origin point of the popup menu
+   * Returns origin point of the popup menu. This takes the screen bounds
+   * into account and places the popup where it fits best. 
    *
-   * @return Point containing
+   * @return the origin of the popup menu
    */
   protected Point getPopupMenuOrigin()
   {
-    Point point;
+    // The menu's screen location and size.
+    Point screenLoc = getLocationOnScreen();
+    Dimension size = getSize();
+
+    // Determine the popup's size.
+    JPopupMenu popup = getPopupMenu();
+    Dimension popupSize = popup.getSize();
+    if (popupSize.width == 0 || popupSize.height == 0)
+      popupSize = popup.getPreferredSize(); 
+
+    // Determine screen bounds.
+    Toolkit tk = Toolkit.getDefaultToolkit();
+    Rectangle screenBounds = new Rectangle(tk.getScreenSize());
+    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
+    GraphicsDevice gd = ge.getDefaultScreenDevice();
+    GraphicsConfiguration gc = gd.getDefaultConfiguration();
+    Insets screenInsets = tk.getScreenInsets(gc);
+    screenBounds.x -= screenInsets.left;
+    screenBounds.width -= screenInsets.left + screenInsets.right;
+    screenBounds.y -= screenInsets.top;
+    screenBounds.height -= screenInsets.top + screenInsets.bottom;
+    screenLoc.x -= screenInsets.left;
+    screenLoc.y -= screenInsets.top;
 
-    // if menu in the menu bar
+    Point point = new Point();
     if (isTopLevelMenu())
-      point = new Point(0, this.getHeight());
-
-    // if submenu
+      {
+        // If menu in the menu bar.
+        int xOffset = UIManager.getInt("Menu.menuPopupOffsetX");
+        int yOffset = UIManager.getInt("Menu.menuPopupOffsetY");
+        // Determine X location.
+        if (getComponentOrientation().isLeftToRight())
+          {
+            // Prefer popup to the right.
+            point.x = xOffset;
+            // Check if it fits, otherwise place popup wherever it fits.
+            if (screenLoc.x + point.x + popupSize.width
+                > screenBounds.width + screenBounds.width
+                && screenBounds.width - size.width
+                   < 2 * (screenLoc.x - screenBounds.x))
+              // Popup to the right if there's not enough room.
+              point.x = size.width - xOffset - popupSize.width;
+          }
+        else
+          {
+            // Prefer popup to the left.
+            point.x = size.width - xOffset - popupSize.width;
+            if (screenLoc.x + point.x < screenBounds.x
+                && screenBounds.width - size.width
+                   > 2 * (screenLoc.x - screenBounds.x))
+              // Popup to the left if there's not enough room.
+              point.x = xOffset;
+          }
+        // Determine Y location. Prefer popping down.
+        point.y = size.height + yOffset;
+        if (screenLoc.y + point.y + popupSize.height >= screenBounds.height
+            && screenBounds.height - size.height
+               < 2 * (screenLoc.y - screenBounds.y))
+          // Position above if there's not enough room below.
+          point.y = - yOffset - popupSize.height;
+      }
     else
       {
+        // If submenu.
         int xOffset = UIManager.getInt("Menu.submenuPopupOffsetX");
         int yOffset = UIManager.getInt("Menu.submenuPopupOffsetY");
-        int x = getWidth() + xOffset;
-        int y = yOffset;
-        point = new Point(x, y);
+        // Determine X location.
+        if (getComponentOrientation().isLeftToRight())
+          {
+            // Prefer popup to the right.
+            point.x = size.width + xOffset;
+            if (screenLoc.x + point.x + popupSize.width
+                >= screenBounds.x + screenBounds.width
+                && screenBounds.width - size.width
+                   < 2 * (screenLoc.x - screenBounds.x))
+              // Position to the left if there's not enough room on the right.
+              point.x = - xOffset - popupSize.width;
+          }
+        else
+          {
+            // Prefer popup on the left side.
+            point.x = - xOffset - popupSize.width;
+            if (screenLoc.x + point.x < screenBounds.x
+                && screenBounds.width - size.width
+                > 2 * (screenLoc.x - screenBounds.x))
+              // Popup to the right if there's not enough room.
+              point.x = size.width + xOffset;
+          }
+        // Determine Y location. Prefer popping down.
+        point.y = yOffset;
+        if (screenLoc.y + point.y + popupSize.height
+            >= screenBounds.y + screenBounds.height
+            && screenBounds.height - size.height
+            < 2 * (screenLoc.y - screenBounds.y))
+          // Pop up if there's not enough room below.
+          point.y = size.height - yOffset - popupSize.height;
       }
     return point;
   }
 
   /**
    * Returns delay property.
    *
    * @return delay property, indicating number of milliseconds before
    * popup menu associated with the menu appears or disappears after
    * menu was selected or deselected respectively
    */
   public int getDelay()
@@ -486,24 +579,26 @@
   }
 
   /**
    * Sets location at which popup menu should be displayed
    * The location given is relative to this menu item
    *
    * @param x x-coordinate of the menu location
    * @param y y-coordinate of the menu location
    */
   public void setMenuLocation(int x, int y)
   {
     menuLocation = new Point(x, y);
+    if (popupMenu != null)
+      popupMenu.setLocation(x, y);
   }
 
   /**
    * Creates and returns JMenuItem associated with the given action
    *
    * @param action Action to use for creation of JMenuItem
    *
    * @return JMenuItem that was creted with given action
    */
   protected JMenuItem createActionComponent(Action action)
   {
     return new JMenuItem(action);
@@ -603,25 +698,25 @@
    * Returns menu component located at the givent index
    * in the menu
    *
    * @param index index at which to get the menu component in the menu
    *
    * @return Menu Component located in the menu at the specified index
    */
   public Component getMenuComponent(int index)
   {
     if (getPopupMenu() == null || getMenuComponentCount() == 0)
       return null;
     
-    return (Component) popupMenu.getComponentAtIndex(index);
+    return popupMenu.getComponentAtIndex(index);
   }
 
   /**
    * Return components belonging to this menu
    *
    * @return components belonging to this menu
    */
   public Component[] getMenuComponents()
   {
     return getPopupMenu().getComponents();
   }
 

Reply via email to