/*
* Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package javax.swing;
import java.awt.*;
import java.util.*;
import java.awt.event.*;
import javax.swing.event.*;
import sun.awt.
AppContext;
import sun.awt.
AWTAccessor;
import sun.awt.
AWTAccessor.
MouseEventAccessor;
import sun.swing.
SwingUtilities2;
/**
* A MenuSelectionManager owns the selection in menu hierarchy.
*
* @author Arnaud Weber
*/
public class
MenuSelectionManager {
private
Vector<
MenuElement>
selection = new
Vector<
MenuElement>();
/* diagnostic aids -- should be false for production builds. */
private static final boolean
TRACE = false; // trace creates and disposes
private static final boolean
VERBOSE = false; // show reuse hits/misses
private static final boolean
DEBUG = false; // show bad params, misc.
private static final
StringBuilder MENU_SELECTION_MANAGER_KEY =
new
StringBuilder("javax.swing.MenuSelectionManager");
/**
* Returns the default menu selection manager.
*
* @return a MenuSelectionManager object
*/
public static
MenuSelectionManager defaultManager() {
synchronized (
MENU_SELECTION_MANAGER_KEY) {
AppContext context =
AppContext.
getAppContext();
MenuSelectionManager msm = (
MenuSelectionManager)
context.
get(
MENU_SELECTION_MANAGER_KEY);
if (
msm == null) {
msm = new
MenuSelectionManager();
context.
put(
MENU_SELECTION_MANAGER_KEY,
msm);
// installing additional listener if found in the AppContext
Object o =
context.
get(
SwingUtilities2.
MENU_SELECTION_MANAGER_LISTENER_KEY);
if (
o != null &&
o instanceof
ChangeListener) {
msm.
addChangeListener((
ChangeListener)
o);
}
}
return
msm;
}
}
/**
* Only one ChangeEvent is needed per button model instance since the
* event's only state is the source property. The source of events
* generated is always "this".
*/
protected transient
ChangeEvent changeEvent = null;
protected
EventListenerList listenerList = new
EventListenerList();
/**
* Changes the selection in the menu hierarchy. The elements
* in the array are sorted in order from the root menu
* element to the currently selected menu element.
* <p>
* Note that this method is public but is used by the look and
* feel engine and should not be called by client applications.
*
* @param path an array of <code>MenuElement</code> objects specifying
* the selected path
*/
public void
setSelectedPath(
MenuElement[]
path) {
int
i,
c;
int
currentSelectionCount =
selection.
size();
int
firstDifference = 0;
if(
path == null) {
path = new
MenuElement[0];
}
if (
DEBUG) {
System.
out.
print("Previous: ");
printMenuElementArray(
getSelectedPath());
System.
out.
print("New: ");
printMenuElementArray(
path);
}
for(
i=0,
c=
path.length;
i<
c;
i++) {
if (
i <
currentSelectionCount &&
selection.
elementAt(
i) ==
path[
i])
firstDifference++;
else
break;
}
for(
i=
currentSelectionCount - 1 ;
i >=
firstDifference ;
i--) {
MenuElement me =
selection.
elementAt(
i);
selection.
removeElementAt(
i);
me.
menuSelectionChanged(false);
}
for(
i =
firstDifference,
c =
path.length ;
i <
c ;
i++) {
if (
path[
i] != null) {
selection.
addElement(
path[
i]);
path[
i].
menuSelectionChanged(true);
}
}
fireStateChanged();
}
/**
* Returns the path to the currently selected menu item
*
* @return an array of MenuElement objects representing the selected path
*/
public
MenuElement[]
getSelectedPath() {
MenuElement res[] = new
MenuElement[
selection.
size()];
int
i,
c;
for(
i=0,
c=
selection.
size();
i<
c;
i++)
res[
i] =
selection.
elementAt(
i);
return
res;
}
/**
* Tell the menu selection to close and unselect all the menu components. Call this method
* when a choice has been made
*/
public void
clearSelectedPath() {
if (
selection.
size() > 0) {
setSelectedPath(null);
}
}
/**
* Adds a ChangeListener to the button.
*
* @param l the listener to add
*/
public void
addChangeListener(
ChangeListener l) {
listenerList.
add(
ChangeListener.class,
l);
}
/**
* Removes a ChangeListener from the button.
*
* @param l the listener to remove
*/
public void
removeChangeListener(
ChangeListener l) {
listenerList.
remove(
ChangeListener.class,
l);
}
/**
* Returns an array of all the <code>ChangeListener</code>s added
* to this MenuSelectionManager with addChangeListener().
*
* @return all of the <code>ChangeListener</code>s added or an empty
* array if no listeners have been added
* @since 1.4
*/
public
ChangeListener[]
getChangeListeners() {
return
listenerList.
getListeners(
ChangeListener.class);
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type. The event instance
* is created lazily.
*
* @see EventListenerList
*/
protected void
fireStateChanged() {
// Guaranteed to return a non-null array
Object[]
listeners =
listenerList.
getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int
i =
listeners.length-2;
i>=0;
i-=2) {
if (
listeners[
i]==
ChangeListener.class) {
// Lazily create the event:
if (
changeEvent == null)
changeEvent = new
ChangeEvent(this);
((
ChangeListener)
listeners[
i+1]).
stateChanged(
changeEvent);
}
}
}
/**
* When a MenuElement receives an event from a MouseListener, it should never process the event
* directly. Instead all MenuElements should call this method with the event.
*
* @param event a MouseEvent object
*/
public void
processMouseEvent(
MouseEvent event) {
int
screenX,
screenY;
Point p;
int
i,
c,
j,
d;
Component mc;
Rectangle r2;
int
cWidth,
cHeight;
MenuElement menuElement;
MenuElement subElements[];
MenuElement path[];
Vector<
MenuElement>
tmp;
int
selectionSize;
p =
event.
getPoint();
Component source =
event.
getComponent();
if ((
source != null) && !
source.
isShowing()) {
// This can happen if a mouseReleased removes the
// containing component -- bug 4146684
return;
}
int
type =
event.
getID();
int
modifiers =
event.
getModifiers();
// 4188027: drag enter/exit added in JDK 1.1.7A, JDK1.2
if ((
type==
MouseEvent.
MOUSE_ENTERED||
type==
MouseEvent.
MOUSE_EXITED)
&& ((
modifiers & (
InputEvent.
BUTTON1_MASK |
InputEvent.
BUTTON2_MASK |
InputEvent.
BUTTON3_MASK)) !=0 )) {
return;
}
if (
source != null) {
SwingUtilities.
convertPointToScreen(
p,
source);
}
screenX =
p.
x;
screenY =
p.
y;
tmp = (
Vector<
MenuElement>)
selection.
clone();
selectionSize =
tmp.
size();
boolean
success = false;
for (
i=
selectionSize - 1;
i >= 0 &&
success == false;
i--) {
menuElement = (
MenuElement)
tmp.
elementAt(
i);
subElements =
menuElement.
getSubElements();
path = null;
for (
j = 0,
d =
subElements.length;
j <
d &&
success == false;
j++) {
if (
subElements[
j] == null)
continue;
mc =
subElements[
j].
getComponent();
if(!
mc.
isShowing())
continue;
if(
mc instanceof
JComponent) {
cWidth =
mc.
getWidth();
cHeight =
mc.
getHeight();
} else {
r2 =
mc.
getBounds();
cWidth =
r2.
width;
cHeight =
r2.
height;
}
p.
x =
screenX;
p.
y =
screenY;
SwingUtilities.
convertPointFromScreen(
p,
mc);
/** Send the event to visible menu element if menu element currently in
* the selected path or contains the event location
*/
if(
(
p.
x >= 0 &&
p.
x <
cWidth &&
p.
y >= 0 &&
p.
y <
cHeight)) {
int
k;
if(
path == null) {
path = new
MenuElement[
i+2];
for(
k=0;
k<=
i;
k++)
path[
k] = (
MenuElement)
tmp.
elementAt(
k);
}
path[
i+1] =
subElements[
j];
MenuElement currentSelection[] =
getSelectedPath();
// Enter/exit detection -- needs tuning...
if (
currentSelection[
currentSelection.length-1] !=
path[
i+1] &&
(
currentSelection.length < 2 ||
currentSelection[
currentSelection.length-2] !=
path[
i+1])) {
Component oldMC =
currentSelection[
currentSelection.length-1].
getComponent();
MouseEvent exitEvent = new
MouseEvent(
oldMC,
MouseEvent.
MOUSE_EXITED,
event.
getWhen(),
event.
getModifiers(),
p.
x,
p.
y,
event.
getXOnScreen(),
event.
getYOnScreen(),
event.
getClickCount(),
event.
isPopupTrigger(),
MouseEvent.
NOBUTTON);
MouseEventAccessor meAccessor =
AWTAccessor.
getMouseEventAccessor();
meAccessor.
setCausedByTouchEvent(
exitEvent,
meAccessor.
isCausedByTouchEvent(
event));
currentSelection[
currentSelection.length-1].
processMouseEvent(
exitEvent,
path, this);
MouseEvent enterEvent = new
MouseEvent(
mc,
MouseEvent.
MOUSE_ENTERED,
event.
getWhen(),
event.
getModifiers(),
p.
x,
p.
y,
event.
getXOnScreen(),
event.
getYOnScreen(),
event.
getClickCount(),
event.
isPopupTrigger(),
MouseEvent.
NOBUTTON);
meAccessor.
setCausedByTouchEvent(
enterEvent,
meAccessor.
isCausedByTouchEvent(
event));
subElements[
j].
processMouseEvent(
enterEvent,
path, this);
}
MouseEvent mouseEvent = new
MouseEvent(
mc,
event.
getID(),
event.
getWhen(),
event.
getModifiers(),
p.
x,
p.
y,
event.
getXOnScreen(),
event.
getYOnScreen(),
event.
getClickCount(),
event.
isPopupTrigger(),
MouseEvent.
NOBUTTON);
MouseEventAccessor meAccessor =
AWTAccessor.
getMouseEventAccessor();
meAccessor.
setCausedByTouchEvent(
mouseEvent,
meAccessor.
isCausedByTouchEvent(
event));
subElements[
j].
processMouseEvent(
mouseEvent,
path, this);
success = true;
event.
consume();
}
}
}
}
private void
printMenuElementArray(
MenuElement path[]) {
printMenuElementArray(
path, false);
}
private void
printMenuElementArray(
MenuElement path[], boolean
dumpStack) {
System.
out.
println("Path is(");
int
i,
j;
for(
i=0,
j=
path.length;
i<
j ;
i++){
for (int
k=0;
k<=
i;
k++)
System.
out.
print(" ");
MenuElement me =
path[
i];
if(
me instanceof
JMenuItem) {
System.
out.
println(((
JMenuItem)
me).
getText() + ", ");
} else if (
me instanceof
JMenuBar) {
System.
out.
println("JMenuBar, ");
} else if(
me instanceof
JPopupMenu) {
System.
out.
println("JPopupMenu, ");
} else if (
me == null) {
System.
out.
println("NULL , ");
} else {
System.
out.
println("" +
me + ", ");
}
}
System.
out.
println(")");
if (
dumpStack == true)
Thread.
dumpStack();
}
/**
* Returns the component in the currently selected path
* which contains sourcePoint.
*
* @param source The component in whose coordinate space sourcePoint
* is given
* @param sourcePoint The point which is being tested
* @return The component in the currently selected path which
* contains sourcePoint (relative to the source component's
* coordinate space. If sourcePoint is not inside a component
* on the currently selected path, null is returned.
*/
public
Component componentForPoint(
Component source,
Point sourcePoint) {
int
screenX,
screenY;
Point p =
sourcePoint;
int
i,
c,
j,
d;
Component mc;
Rectangle r2;
int
cWidth,
cHeight;
MenuElement menuElement;
MenuElement subElements[];
Vector<
MenuElement>
tmp;
int
selectionSize;
SwingUtilities.
convertPointToScreen(
p,
source);
screenX =
p.
x;
screenY =
p.
y;
tmp = (
Vector<
MenuElement>)
selection.
clone();
selectionSize =
tmp.
size();
for(
i=
selectionSize - 1 ;
i >= 0 ;
i--) {
menuElement = (
MenuElement)
tmp.
elementAt(
i);
subElements =
menuElement.
getSubElements();
for(
j = 0,
d =
subElements.length ;
j <
d ;
j++) {
if (
subElements[
j] == null)
continue;
mc =
subElements[
j].
getComponent();
if(!
mc.
isShowing())
continue;
if(
mc instanceof
JComponent) {
cWidth =
mc.
getWidth();
cHeight =
mc.
getHeight();
} else {
r2 =
mc.
getBounds();
cWidth =
r2.
width;
cHeight =
r2.
height;
}
p.
x =
screenX;
p.
y =
screenY;
SwingUtilities.
convertPointFromScreen(
p,
mc);
/** Return the deepest component on the selection
* path in whose bounds the event's point occurs
*/
if (
p.
x >= 0 &&
p.
x <
cWidth &&
p.
y >= 0 &&
p.
y <
cHeight) {
return
mc;
}
}
}
return null;
}
/**
* When a MenuElement receives an event from a KeyListener, it should never process the event
* directly. Instead all MenuElements should call this method with the event.
*
* @param e a KeyEvent object
*/
public void
processKeyEvent(
KeyEvent e) {
MenuElement[]
sel2 = new
MenuElement[0];
sel2 =
selection.
toArray(
sel2);
int
selSize =
sel2.length;
MenuElement[]
path;
if (
selSize < 1) {
return;
}
for (int
i=
selSize-1;
i>=0;
i--) {
MenuElement elem =
sel2[
i];
MenuElement[]
subs =
elem.
getSubElements();
path = null;
for (int
j=0;
j<
subs.length;
j++) {
if (
subs[
j] == null || !
subs[
j].
getComponent().
isShowing()
|| !
subs[
j].
getComponent().
isEnabled()) {
continue;
}
if(
path == null) {
path = new
MenuElement[
i+2];
System.
arraycopy(
sel2, 0,
path, 0,
i+1);
}
path[
i+1] =
subs[
j];
subs[
j].
processKeyEvent(
e,
path, this);
if (
e.
isConsumed()) {
return;
}
}
}
// finally dispatch event to the first component in path
path = new
MenuElement[1];
path[0] =
sel2[0];
path[0].
processKeyEvent(
e,
path, this);
if (
e.
isConsumed()) {
return;
}
}
/**
* Return true if c is part of the currently used menu
*/
public boolean
isComponentPartOfCurrentMenu(
Component c) {
if(
selection.
size() > 0) {
MenuElement me =
selection.
elementAt(0);
return
isComponentPartOfCurrentMenu(
me,
c);
} else
return false;
}
private boolean
isComponentPartOfCurrentMenu(
MenuElement root,
Component c) {
MenuElement children[];
int
i,
d;
if (
root == null)
return false;
if(
root.
getComponent() ==
c)
return true;
else {
children =
root.
getSubElements();
for(
i=0,
d=
children.length;
i<
d;
i++) {
if(
isComponentPartOfCurrentMenu(
children[
i],
c))
return true;
}
}
return false;
}
}