org.openide.awt.JPopupMenuUtils Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.openide.awt;
import org.openide.util.RequestProcessor;
import org.openide.util.Utilities;
import java.awt.*;
import java.util.StringTokenizer;
import javax.swing.JMenu;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
/** A class that contains a set of utility classes and methods
* around displaying and positioning popup menus.
*
* Popup menus sometimes need to be repositioned so that they
* don't "fall off" the edges of the screen.
*
* Some of the menus have items that are added dynamically, that is,
* after the menu is displayed. These menus are often placed correctly
* for their initial size, but will need to be repositioned as they
* grow.
*
* @author Evan Adams
*/
public class JPopupMenuUtils {
private static boolean problemTested = false;
private static boolean problem = false;
private static RequestProcessor reqProc;
private static RequestProcessor.Task task;
// private static final boolean NO_POPUP_PLACEMENT_HACK = Boolean.getBoolean("netbeans.popup.no_hack"); // NOI18N
/*
* Called when a visible menu has dynamically changed. Ensure that
* it stays on the screen. Compute its new location and,
* if it differs from the current one, move the popup.
*
* @param popup the popup menu
*/
public static void dynamicChange(final JPopupMenu popup, boolean usedToBeContained) {
if (!popup.isShowing()) {
return;
}
if (isProblemConfig()) {
callRefreshLater(popup);
return;
}
refreshPopup(popup);
Point p = popup.getLocationOnScreen();
Point newPt = getPopupMenuOrigin(popup, p);
boolean willBeContained = willPopupBeContained(popup, newPt);
if (usedToBeContained != willBeContained) {
popup.setVisible(false);
}
if (!newPt.equals(p)) {
// if (!NO_POPUP_PLACEMENT_HACK) {
// popup.setLocation(newPt.x, newPt.y);
// }
}
if (usedToBeContained != willBeContained) {
popup.setVisible(true);
}
}
/** Mysterious calls to pack(), invalidate() and validate() ;-) */
private static void refreshPopup(JPopupMenu popup) {
popup.pack();
popup.invalidate();
Component c = popup.getParent();
if (c != null) {
c.validate();
}
}
/** Called from dynamicChange. Performs refresh of the popup
* in task in reqProc.
*/
private static void callRefreshLater(final JPopupMenu popup) {
// this may cause the popup to flicker
if (reqProc == null) {
reqProc = new RequestProcessor();
}
if (task == null) {
task = reqProc.create(
new Runnable() {
public void run() {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
task = null;
// after the action is performed new task should be created
// (probably for another instance of a popup)
if (!popup.isShowing()) {
return;
}
Point p = popup.getLocationOnScreen();
Point newPt = getPopupMenuOrigin(popup, p);
popup.setVisible(false);
refreshPopup(popup);
if (!newPt.equals(p)) {
// if (!NO_POPUP_PLACEMENT_HACK) {
// popup.setLocation(newPt.x, newPt.y);
// }
}
popup.setVisible(true);
}
}
);
}
}
);
}
task.schedule(100);
}
/** Returns true when the popup has to be unconditioanlly
* redisplayed when adding new items. Currently
* this is true with JDK1.3 on Linux/Gnome. This
* method checks for presence of system property
* "netbeans.popup.linuxhack".
*/
private static boolean isProblemConfig() {
// we have already tested the need for the hack
if (problemTested) {
return problem;
}
problem = false;
String needHack = System.getProperty("netbeans.popup.linuxhack");
if (needHack != null) {
problem = true;
}
return problem;
}
/*
* Called when a visible submenu (pullright) has dynamically changed.
* Ensure that it stays on the screen. If it doesn't fit, then hide
* the popup and redisplay it. This causes JMenu's placement code
* to get executed again which may change the submens to go up rather
* than down.
*
* @param popup the popup menu
*/
public static void dynamicChangeToSubmenu(JPopupMenu popup, boolean usedToBeContained) {
Object invoker = popup.getInvoker();
if (!(invoker instanceof JMenu)) {
return;
}
JMenu menu = (JMenu) invoker;
if (!popup.isShowing()) {
return;
}
if (isProblemConfig()) {
callRefreshLater2(popup, menu);
return;
}
refreshPopup(popup);
Point p = popup.getLocationOnScreen();
Dimension popupSize = popup.getPreferredSize();
Rectangle popupRect = new Rectangle(p, popupSize);
Rectangle screenRect = getScreenRect();
boolean willBeContained = isPopupContained(popup);
if (!screenRect.contains(popupRect)) {
/*
* The menu grew off the edge of the screen.
*/
menu.setPopupMenuVisible(false);
menu.setPopupMenuVisible(true);
} else if (usedToBeContained != willBeContained) {
/*
* The menu grew off the edge of the containing window.
* Use the setVisible() hack to change the menu from
* lightweight to heavyweight.
*/
popup.setVisible(false);
popup.setVisible(true);
}
}
/** Called from dynamicChangeToSubmenu. Calls the popup refresh
* in a task in the reqProc.
*/
private static void callRefreshLater2(final JPopupMenu popup, final JMenu menu) {
// this may cause the popup to flicker
if (reqProc == null) {
reqProc = new RequestProcessor();
}
if (task == null) {
task = reqProc.create(
new Runnable() {
public void run() {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
task = null;
// after the action is performed new task should be created
// (probably for another instance of a popup)
if (!popup.isShowing()) {
return;
}
popup.setVisible(false);
refreshPopup(popup);
popup.setVisible(true);
Point p = popup.getLocationOnScreen();
Dimension popupSize = popup.getPreferredSize();
Rectangle popupRect = new Rectangle(p, popupSize);
Rectangle screenRect = getScreenRect();
if (!screenRect.contains(popupRect)) {
menu.setPopupMenuVisible(false);
menu.setPopupMenuVisible(true);
}
}
}
);
}
}
);
}
task.schedule(100);
}
/*
* Return the point for the origin of this popup.
* This is where the adjustments are made to ensure the
* popup stays on the screen.
*
* @param popup the popup menu
* @param p the popup menu's origin
* @return the popup menu's new origin
*/
static Point getPopupMenuOrigin(JPopupMenu popup, Point p) {
Point newPt = new Point(p);
Dimension popupSize = popup.getPreferredSize();
Rectangle screenRect = getScreenRect();
int popupRight = newPt.x + popupSize.width;
int popupBottom = newPt.y + popupSize.height;
int screenRight = screenRect.x + screenRect.width;
int screenBottom = screenRect.y + screenRect.height;
if (popupRight > screenRight) { // Are we off the right edge?
newPt.x = screenRight - popupSize.width;
}
if (newPt.x < screenRect.x) { // Are we off the left edge?
newPt.x = screenRect.x;
}
if (popupBottom > screenBottom) { // Are we off the bottom edge?
newPt.y = screenBottom - popupSize.height;
}
if (newPt.y < screenRect.y) { // Are we off the top edge?
newPt.y = screenRect.y;
}
return newPt;
}
/*
* Return whether or not the given popup is contained by its
* parent window. Uses the current location and size of the popup.
*
* @return boolean indicating if the popup is contained
*/
public static boolean isPopupContained(JPopupMenu popup) {
if (!popup.isShowing()) {
return false;
}
return willPopupBeContained(popup, popup.getLocationOnScreen());
}
/*
* Return whether or not the given popup will be contained by
* its parent window if it is moved to origin.
* Use its current size.
*
* @param popup
the popup to be tested
* @param origin
location of the popup to be tested
* @return boolean indicating if the popup will be contained
*/
private static boolean willPopupBeContained(JPopupMenu popup, Point origin) {
if (!popup.isShowing()) {
return false;
}
Window w = SwingUtilities.windowForComponent(popup.getInvoker());
Rectangle r = new Rectangle(origin, popup.getSize());
return (w != null) && w.getBounds().contains(r);
}
/*
* Return a rectange defining the usable portion of the screen. Originally
* designed to provide a way to account for the taskbar in Windows. Didn't
* work with multiple monitor configuration. The new implementation
* detects the current monitor and returns its bounds. Never cache the
* result of this method.
*
* @return a rectangle defining the usable area.
*/
public static Rectangle getScreenRect() {
return Utilities.getUsableScreenBounds();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy