1 /* 2 * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package javax.swing; 26 27 import java.awt.*; 28 import java.util.*; 29 import java.awt.event.*; 30 import javax.swing.event.*; 31 32 import sun.awt.AppContext; 33 34 /** 35 * A MenuSelectionManager owns the selection in menu hierarchy. 36 * 37 * @author Arnaud Weber 38 */ 39 public class MenuSelectionManager { 40 private Vector<MenuElement> selection = new Vector<MenuElement>(); 41 42 /* diagnostic aids -- should be false for production builds. */ 43 private static final boolean TRACE = false; // trace creates and disposes 44 private static final boolean VERBOSE = false; // show reuse hits/misses 45 private static final boolean DEBUG = false; // show bad params, misc. 46 47 private static final StringBuilder MENU_SELECTION_MANAGER_KEY = 48 new StringBuilder("javax.swing.MenuSelectionManager"); 49 50 /** 51 * Returns the default menu selection manager. 52 * 53 * @return a MenuSelectionManager object 54 */ 55 public static MenuSelectionManager defaultManager() { 56 synchronized (MENU_SELECTION_MANAGER_KEY) { 57 AppContext context = AppContext.getAppContext(); 58 MenuSelectionManager msm = (MenuSelectionManager)context.get( 59 MENU_SELECTION_MANAGER_KEY); 60 if (msm == null) { 61 msm = new MenuSelectionManager(); 62 context.put(MENU_SELECTION_MANAGER_KEY, msm); 63 } 64 65 return msm; 66 } 67 } 68 69 /** 70 * Only one ChangeEvent is needed per button model instance since the 71 * event's only state is the source property. The source of events 72 * generated is always "this". 73 */ 74 protected transient ChangeEvent changeEvent = null; 75 protected EventListenerList listenerList = new EventListenerList(); 76 77 /** 78 * Changes the selection in the menu hierarchy. The elements 79 * in the array are sorted in order from the root menu 80 * element to the currently selected menu element. 81 * <p> 82 * Note that this method is public but is used by the look and 83 * feel engine and should not be called by client applications. 84 * 85 * @param path an array of <code>MenuElement</code> objects specifying 86 * the selected path 87 */ 88 public void setSelectedPath(MenuElement[] path) { 89 int i,c; 90 int currentSelectionCount = selection.size(); 91 int firstDifference = 0; 92 93 if(path == null) { 94 path = new MenuElement[0]; 95 } 96 97 if (DEBUG) { 98 System.out.print("Previous: "); printMenuElementArray(getSelectedPath()); 99 System.out.print("New: "); printMenuElementArray(path); 100 } 101 102 for(i=0,c=path.length;i<c;i++) { 103 if (i < currentSelectionCount && selection.elementAt(i) == path[i]) 104 firstDifference++; 105 else 106 break; 107 } 108 109 for(i=currentSelectionCount - 1 ; i >= firstDifference ; i--) { 110 MenuElement me = selection.elementAt(i); 111 selection.removeElementAt(i); 112 me.menuSelectionChanged(false); 113 } 114 115 for(i = firstDifference, c = path.length ; i < c ; i++) { 116 if (path[i] != null) { 117 selection.addElement(path[i]); 118 path[i].menuSelectionChanged(true); 119 } 120 } 121 122 fireStateChanged(); 123 } 124 125 /** 126 * Returns the path to the currently selected menu item 127 * 128 * @return an array of MenuElement objects representing the selected path 129 */ 130 public MenuElement[] getSelectedPath() { 131 MenuElement res[] = new MenuElement[selection.size()]; 132 int i,c; 133 for(i=0,c=selection.size();i<c;i++) 134 res[i] = selection.elementAt(i); 135 return res; 136 } 137 138 /** 139 * Tell the menu selection to close and unselect all the menu components. Call this method 140 * when a choice has been made 141 */ 142 public void clearSelectedPath() { 143 if (selection.size() > 0) { 144 setSelectedPath(null); 145 } 146 } 147 148 /** 149 * Adds a ChangeListener to the button. 150 * 151 * @param l the listener to add 152 */ 153 public void addChangeListener(ChangeListener l) { 154 listenerList.add(ChangeListener.class, l); 155 } 156 157 /** 158 * Removes a ChangeListener from the button. 159 * 160 * @param l the listener to remove 161 */ 162 public void removeChangeListener(ChangeListener l) { 163 listenerList.remove(ChangeListener.class, l); 164 } 165 166 /** 167 * Returns an array of all the <code>ChangeListener</code>s added 168 * to this MenuSelectionManager with addChangeListener(). 169 * 170 * @return all of the <code>ChangeListener</code>s added or an empty 171 * array if no listeners have been added 172 * @since 1.4 173 */ 174 public ChangeListener[] getChangeListeners() { 175 return listenerList.getListeners(ChangeListener.class); 176 } 177 178 /** 179 * Notifies all listeners that have registered interest for 180 * notification on this event type. The event instance 181 * is created lazily. 182 * 183 * @see EventListenerList 184 */ 185 protected void fireStateChanged() { 186 // Guaranteed to return a non-null array 187 Object[] listeners = listenerList.getListenerList(); 188 // Process the listeners last to first, notifying 189 // those that are interested in this event 190 for (int i = listeners.length-2; i>=0; i-=2) { 191 if (listeners[i]==ChangeListener.class) { 192 // Lazily create the event: 193 if (changeEvent == null) 194 changeEvent = new ChangeEvent(this); 195 ((ChangeListener)listeners[i+1]).stateChanged(changeEvent); 196 } 197 } 198 } 199 200 /** 201 * When a MenuElement receives an event from a MouseListener, it should never process the event 202 * directly. Instead all MenuElements should call this method with the event. 203 * 204 * @param event a MouseEvent object 205 */ 206 public void processMouseEvent(MouseEvent event) { 207 int screenX,screenY; 208 Point p; 209 int i,c,j,d; 210 Component mc; 211 Rectangle r2; 212 int cWidth,cHeight; 213 MenuElement menuElement; 214 MenuElement subElements[]; 215 MenuElement path[]; 216 Vector<MenuElement> tmp; 217 int selectionSize; 218 p = event.getPoint(); 219 220 Component source = event.getComponent(); 221 222 if ((source != null) && !source.isShowing()) { 223 // This can happen if a mouseReleased removes the 224 // containing component -- bug 4146684 225 return; 226 } 227 228 int type = event.getID(); 229 int modifiers = event.getModifiers(); 230 // 4188027: drag enter/exit added in JDK 1.1.7A, JDK1.2 231 if ((type==MouseEvent.MOUSE_ENTERED|| 232 type==MouseEvent.MOUSE_EXITED) 233 && ((modifiers & (InputEvent.BUTTON1_MASK | 234 InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK)) !=0 )) { 235 return; 236 } 237 238 if (source != null) { 239 SwingUtilities.convertPointToScreen(p, source); 240 } 241 242 screenX = p.x; 243 screenY = p.y; 244 245 tmp = (Vector<MenuElement>)selection.clone(); 246 selectionSize = tmp.size(); 247 boolean success = false; 248 for (i=selectionSize - 1;i >= 0 && success == false; i--) { 249 menuElement = (MenuElement) tmp.elementAt(i); 250 subElements = menuElement.getSubElements(); 251 252 path = null; 253 for (j = 0, d = subElements.length;j < d && success == false; j++) { 254 if (subElements[j] == null) 255 continue; 256 mc = subElements[j].getComponent(); 257 if(!mc.isShowing()) 258 continue; 259 if(mc instanceof JComponent) { 260 cWidth = mc.getWidth(); 261 cHeight = mc.getHeight(); 262 } else { 263 r2 = mc.getBounds(); 264 cWidth = r2.width; 265 cHeight = r2.height; 266 } 267 p.x = screenX; 268 p.y = screenY; 269 SwingUtilities.convertPointFromScreen(p,mc); 270 271 /** Send the event to visible menu element if menu element currently in 272 * the selected path or contains the event location 273 */ 274 if( 275 (p.x >= 0 && p.x < cWidth && p.y >= 0 && p.y < cHeight)) { 276 int k; 277 if(path == null) { 278 path = new MenuElement[i+2]; 279 for(k=0;k<=i;k++) 280 path[k] = (MenuElement)tmp.elementAt(k); 281 } 282 path[i+1] = subElements[j]; 283 MenuElement currentSelection[] = getSelectedPath(); 284 285 // Enter/exit detection -- needs tuning... 286 if (currentSelection[currentSelection.length-1] != 287 path[i+1] && 288 (currentSelection.length < 2 || 289 currentSelection[currentSelection.length-2] != 290 path[i+1])) { 291 Component oldMC = currentSelection[currentSelection.length-1].getComponent(); 292 293 MouseEvent exitEvent = new MouseEvent(oldMC, MouseEvent.MOUSE_EXITED, 294 event.getWhen(), 295 event.getModifiers(), p.x, p.y, 296 event.getXOnScreen(), 297 event.getYOnScreen(), 298 event.getClickCount(), 299 event.isPopupTrigger(), 300 MouseEvent.NOBUTTON); 301 currentSelection[currentSelection.length-1]. 302 processMouseEvent(exitEvent, path, this); 303 304 MouseEvent enterEvent = new MouseEvent(mc, 305 MouseEvent.MOUSE_ENTERED, 306 event.getWhen(), 307 event.getModifiers(), p.x, p.y, 308 event.getXOnScreen(), 309 event.getYOnScreen(), 310 event.getClickCount(), 311 event.isPopupTrigger(), 312 MouseEvent.NOBUTTON); 313 subElements[j].processMouseEvent(enterEvent, path, this); 314 } 315 MouseEvent mouseEvent = new MouseEvent(mc, event.getID(),event. getWhen(), 316 event.getModifiers(), p.x, p.y, 317 event.getXOnScreen(), 318 event.getYOnScreen(), 319 event.getClickCount(), 320 event.isPopupTrigger(), 321 MouseEvent.NOBUTTON); 322 subElements[j].processMouseEvent(mouseEvent, path, this); 323 success = true; 324 event.consume(); 325 } 326 } 327 } 328 } 329 330 private void printMenuElementArray(MenuElement path[]) { 331 printMenuElementArray(path, false); 332 } 333 334 private void printMenuElementArray(MenuElement path[], boolean dumpStack) { 335 System.out.println("Path is("); 336 int i, j; 337 for(i=0,j=path.length; i<j ;i++){ 338 for (int k=0; k<=i; k++) 339 System.out.print(" "); 340 MenuElement me = path[i]; 341 if(me instanceof JMenuItem) { 342 System.out.println(((JMenuItem)me).getText() + ", "); 343 } else if (me instanceof JMenuBar) { 344 System.out.println("JMenuBar, "); 345 } else if(me instanceof JPopupMenu) { 346 System.out.println("JPopupMenu, "); 347 } else if (me == null) { 348 System.out.println("NULL , "); 349 } else { 350 System.out.println("" + me + ", "); 351 } 352 } 353 System.out.println(")"); 354 355 if (dumpStack == true) 356 Thread.dumpStack(); 357 } 358 359 /** 360 * Returns the component in the currently selected path 361 * which contains sourcePoint. 362 * 363 * @param source The component in whose coordinate space sourcePoint 364 * is given 365 * @param sourcePoint The point which is being tested 366 * @return The component in the currently selected path which 367 * contains sourcePoint (relative to the source component's 368 * coordinate space. If sourcePoint is not inside a component 369 * on the currently selected path, null is returned. 370 */ 371 public Component componentForPoint(Component source, Point sourcePoint) { 372 int screenX,screenY; 373 Point p = sourcePoint; 374 int i,c,j,d; 375 Component mc; 376 Rectangle r2; 377 int cWidth,cHeight; 378 MenuElement menuElement; 379 MenuElement subElements[]; 380 Vector<MenuElement> tmp; 381 int selectionSize; 382 383 SwingUtilities.convertPointToScreen(p,source); 384 385 screenX = p.x; 386 screenY = p.y; 387 388 tmp = (Vector<MenuElement>)selection.clone(); 389 selectionSize = tmp.size(); 390 for(i=selectionSize - 1 ; i >= 0 ; i--) { 391 menuElement = (MenuElement) tmp.elementAt(i); 392 subElements = menuElement.getSubElements(); 393 394 for(j = 0, d = subElements.length ; j < d ; j++) { 395 if (subElements[j] == null) 396 continue; 397 mc = subElements[j].getComponent(); 398 if(!mc.isShowing()) 399 continue; 400 if(mc instanceof JComponent) { 401 cWidth = mc.getWidth(); 402 cHeight = mc.getHeight(); 403 } else { 404 r2 = mc.getBounds(); 405 cWidth = r2.width; 406 cHeight = r2.height; 407 } 408 p.x = screenX; 409 p.y = screenY; 410 SwingUtilities.convertPointFromScreen(p,mc); 411 412 /** Return the deepest component on the selection 413 * path in whose bounds the event's point occurs 414 */ 415 if (p.x >= 0 && p.x < cWidth && p.y >= 0 && p.y < cHeight) { 416 return mc; 417 } 418 } 419 } 420 return null; 421 } 422 423 /** 424 * When a MenuElement receives an event from a KeyListener, it should never process the event 425 * directly. Instead all MenuElements should call this method with the event. 426 * 427 * @param e a KeyEvent object 428 */ 429 public void processKeyEvent(KeyEvent e) { 430 MenuElement[] sel2 = new MenuElement[0]; 431 sel2 = selection.toArray(sel2); 432 int selSize = sel2.length; 433 MenuElement[] path; 434 435 if (selSize < 1) { 436 return; 437 } 438 439 for (int i=selSize-1; i>=0; i--) { 440 MenuElement elem = sel2[i]; 441 MenuElement[] subs = elem.getSubElements(); 442 path = null; 443 444 for (int j=0; j<subs.length; j++) { 445 if (subs[j] == null || !subs[j].getComponent().isShowing() 446 || !subs[j].getComponent().isEnabled()) { 447 continue; 448 } 449 450 if(path == null) { 451 path = new MenuElement[i+2]; 452 System.arraycopy(sel2, 0, path, 0, i+1); 453 } 454 path[i+1] = subs[j]; 455 subs[j].processKeyEvent(e, path, this); 456 if (e.isConsumed()) { 457 return; 458 } 459 } 460 } 461 462 // finally dispatch event to the first component in path 463 path = new MenuElement[1]; 464 path[0] = sel2[0]; 465 path[0].processKeyEvent(e, path, this); 466 if (e.isConsumed()) { 467 return; 468 } 469 } 470 471 /** 472 * Return true if c is part of the currently used menu 473 */ 474 public boolean isComponentPartOfCurrentMenu(Component c) { 475 if(selection.size() > 0) { 476 MenuElement me = selection.elementAt(0); 477 return isComponentPartOfCurrentMenu(me,c); 478 } else 479 return false; 480 } 481 482 private boolean isComponentPartOfCurrentMenu(MenuElement root,Component c) { 483 MenuElement children[]; 484 int i,d; 485 486 if (root == null) 487 return false; 488 489 if(root.getComponent() == c) 490 return true; 491 else { 492 children = root.getSubElements(); 493 for(i=0,d=children.length;i<d;i++) { 494 if(isComponentPartOfCurrentMenu(children[i],c)) 495 return true; 496 } 497 } 498 return false; 499 } 500 }