37 |
|
|
38 |
package javax.swing.plaf.basic; |
package javax.swing.plaf.basic; |
39 |
|
|
40 |
|
import java.awt.AWTEvent; |
41 |
import java.awt.Component; |
import java.awt.Component; |
42 |
import java.awt.Container; |
import java.awt.Container; |
43 |
|
import java.awt.Cursor; |
44 |
import java.awt.Dimension; |
import java.awt.Dimension; |
45 |
|
import java.awt.Point; |
46 |
import java.awt.event.ComponentEvent; |
import java.awt.event.ComponentEvent; |
47 |
import java.awt.event.ComponentListener; |
import java.awt.event.ComponentListener; |
48 |
import java.awt.event.MouseEvent; |
import java.awt.event.MouseEvent; |
49 |
|
|
50 |
import javax.swing.BoxLayout; |
import javax.swing.BoxLayout; |
51 |
import javax.swing.JComponent; |
import javax.swing.JComponent; |
52 |
|
import javax.swing.JLayeredPane; |
53 |
|
import javax.swing.JMenu; |
54 |
import javax.swing.JPopupMenu; |
import javax.swing.JPopupMenu; |
55 |
import javax.swing.MenuElement; |
import javax.swing.MenuElement; |
56 |
import javax.swing.MenuSelectionManager; |
import javax.swing.MenuSelectionManager; |
57 |
|
import javax.swing.RootPaneContainer; |
58 |
import javax.swing.SwingUtilities; |
import javax.swing.SwingUtilities; |
59 |
import javax.swing.UIDefaults; |
import javax.swing.UIDefaults; |
60 |
import javax.swing.UIManager; |
import javax.swing.UIManager; |
89 |
public BasicPopupMenuUI() |
public BasicPopupMenuUI() |
90 |
{ |
{ |
91 |
popupMenuListener = new PopupMenuHandler(); |
popupMenuListener = new PopupMenuHandler(); |
|
mouseInputListener = new MouseInputHandler(); |
|
92 |
topWindowListener = new TopWindowListener(); |
topWindowListener = new TopWindowListener(); |
93 |
} |
} |
94 |
|
|
125 |
} |
} |
126 |
|
|
127 |
/** |
/** |
128 |
* This method installs the defaults that are defined in the Basic look and |
* This method installs the defaults that are defined in the Basic look |
129 |
* feel for this {@link JPopupMenu}. |
* and feel for this {@link JPopupMenu}. |
130 |
*/ |
*/ |
131 |
public void installDefaults() |
public void installDefaults() |
132 |
{ |
{ |
144 |
*/ |
*/ |
145 |
protected void installListeners() |
protected void installListeners() |
146 |
{ |
{ |
|
popupMenu.addMouseListener(mouseInputListener); |
|
|
popupMenu.addMouseMotionListener(mouseInputListener); |
|
147 |
popupMenu.addPopupMenuListener(popupMenuListener); |
popupMenu.addPopupMenuListener(popupMenuListener); |
148 |
} |
} |
149 |
|
|
186 |
*/ |
*/ |
187 |
protected void uninstallListeners() |
protected void uninstallListeners() |
188 |
{ |
{ |
|
popupMenu.removeMouseListener(mouseInputListener); |
|
|
popupMenu.removeMouseMotionListener(mouseInputListener); |
|
189 |
popupMenu.removePopupMenuListener(popupMenuListener); |
popupMenu.removePopupMenuListener(popupMenuListener); |
190 |
} |
} |
191 |
|
|
274 |
// by the top - level window that this popup belongs to. |
// by the top - level window that this popup belongs to. |
275 |
Component invoker = popupMenu.getInvoker(); |
Component invoker = popupMenu.getInvoker(); |
276 |
|
|
277 |
Container rootContainer = (Container) SwingUtilities.getRoot(invoker); |
RootPaneContainer rootContainer = (RootPaneContainer) SwingUtilities |
278 |
rootContainer.removeComponentListener(topWindowListener); |
.getRoot(invoker); |
279 |
|
((Container) rootContainer).removeComponentListener(topWindowListener); |
280 |
|
|
281 |
|
// If this popup menu is the last popup menu visible on the screen, then |
282 |
|
// stop interrupting mouse events in the glass pane before hiding this |
283 |
|
// last popup menu. |
284 |
|
boolean topLevelMenu = (popupMenu.getInvoker() instanceof JMenu) |
285 |
|
&& ((JMenu) popupMenu.getInvoker()) |
286 |
|
.isTopLevelMenu(); |
287 |
|
|
288 |
|
if (topLevelMenu || ! (popupMenu.getInvoker() instanceof MenuElement)) |
289 |
|
{ |
290 |
|
// set glass pane not to interrupt mouse events and remove |
291 |
|
// mouseInputListener |
292 |
|
Container glassPane = (Container) rootContainer.getGlassPane(); |
293 |
|
glassPane.setVisible(false); |
294 |
|
glassPane.removeMouseListener(mouseInputListener); |
295 |
|
mouseInputListener = null; |
296 |
|
} |
297 |
} |
} |
298 |
|
|
299 |
/** |
/** |
307 |
// ComponentEvents fired by it. We need to cancel this popup menu |
// ComponentEvents fired by it. We need to cancel this popup menu |
308 |
// if topWindow to which this popup belongs was resized or moved. |
// if topWindow to which this popup belongs was resized or moved. |
309 |
Component invoker = popupMenu.getInvoker(); |
Component invoker = popupMenu.getInvoker(); |
310 |
Container rootContainer = (Container) SwingUtilities.getRoot(invoker); |
RootPaneContainer rootContainer = (RootPaneContainer) SwingUtilities |
311 |
rootContainer.addComponentListener(topWindowListener); |
.getRoot(invoker); |
312 |
|
((Container) rootContainer).addComponentListener(topWindowListener); |
313 |
|
|
314 |
|
// Set the glass pane to interrupt all mouse events originating in root |
315 |
|
// container |
316 |
|
if (mouseInputListener == null) |
317 |
|
{ |
318 |
|
Container glassPane = (Container) rootContainer.getGlassPane(); |
319 |
|
glassPane.setVisible(true); |
320 |
|
mouseInputListener = new MouseInputHandler(rootContainer); |
321 |
|
glassPane.addMouseListener(mouseInputListener); |
322 |
|
glassPane.addMouseMotionListener(mouseInputListener); |
323 |
|
} |
324 |
|
|
325 |
// if this popup menu is a free floating popup menu, |
// if this popup menu is a free floating popup menu, |
326 |
// then by default its first element should be always selected when |
// then by default its first element should be always selected when |
375 |
} |
} |
376 |
|
|
377 |
/** |
/** |
378 |
* This method is invoked when top-level window is shown This method does |
* This method is invoked when top-level window is shown This method |
379 |
* nothing by default. |
* does nothing by default. |
380 |
* |
* |
381 |
* @param e The ComponentEvent |
* @param e The ComponentEvent |
382 |
*/ |
*/ |
399 |
} |
} |
400 |
} |
} |
401 |
|
|
402 |
|
/** |
403 |
|
* MouseInputHandler listens to all mouse events originated in the root |
404 |
|
* container. This class is responsible for closing menu hierarchy when the |
405 |
|
* user presses mouse over any component that do not belong to the current |
406 |
|
* menu hierarchy. This is acomplished by interrupting all mouse event in |
407 |
|
* the glass pane and checking if other component was pressed while menu |
408 |
|
* was open, before redestributing events further to intended components |
409 |
|
*/ |
410 |
private class MouseInputHandler implements MouseInputListener |
private class MouseInputHandler implements MouseInputListener |
411 |
{ |
{ |
412 |
|
private JLayeredPane layeredPane; |
413 |
|
private Container glassPane; |
414 |
|
private Cursor nativeCursor; |
415 |
|
private transient Component mouseEventTarget; |
416 |
|
private transient Component pressedComponent; |
417 |
|
private transient Component lastComponentEntered; |
418 |
|
private transient int pressCount; |
419 |
|
|
420 |
|
/** |
421 |
|
* Creates a new MouseInputHandler object. |
422 |
|
* |
423 |
|
* @param c the top most root container |
424 |
|
*/ |
425 |
|
public MouseInputHandler(RootPaneContainer c) |
426 |
|
{ |
427 |
|
layeredPane = c.getLayeredPane(); |
428 |
|
glassPane = (Container) c.getGlassPane(); |
429 |
|
} |
430 |
|
|
431 |
|
/** |
432 |
|
* Handles mouse clicked event |
433 |
|
* |
434 |
|
* @param e Mouse event |
435 |
|
*/ |
436 |
public void mouseClicked(MouseEvent e) |
public void mouseClicked(MouseEvent e) |
437 |
{ |
{ |
438 |
|
handleEvent(e); |
439 |
} |
} |
440 |
|
|
441 |
|
/** |
442 |
|
* Handles mouseDragged event |
443 |
|
* |
444 |
|
* @param e MouseEvent |
445 |
|
*/ |
446 |
public void mouseDragged(MouseEvent e) |
public void mouseDragged(MouseEvent e) |
447 |
{ |
{ |
448 |
|
handleEvent(e); |
449 |
} |
} |
450 |
|
|
451 |
|
/** |
452 |
|
* Handles mouseEntered event |
453 |
|
* |
454 |
|
* @param e MouseEvent |
455 |
|
*/ |
456 |
public void mouseEntered(MouseEvent e) |
public void mouseEntered(MouseEvent e) |
457 |
{ |
{ |
458 |
|
handleEvent(e); |
459 |
} |
} |
460 |
|
|
461 |
|
/** |
462 |
|
* Handles mouseExited event |
463 |
|
* |
464 |
|
* @param e MouseEvent |
465 |
|
*/ |
466 |
public void mouseExited(MouseEvent e) |
public void mouseExited(MouseEvent e) |
467 |
{ |
{ |
468 |
|
handleEvent(e); |
469 |
} |
} |
470 |
|
|
471 |
|
/** |
472 |
|
* Handles mouse moved event |
473 |
|
* |
474 |
|
* @param e MouseEvent |
475 |
|
*/ |
476 |
public void mouseMoved(MouseEvent e) |
public void mouseMoved(MouseEvent e) |
477 |
{ |
{ |
478 |
|
handleEvent(e); |
479 |
} |
} |
480 |
|
|
481 |
|
/** |
482 |
|
* Handles mouse pressed event |
483 |
|
* |
484 |
|
* @param e MouseEvent |
485 |
|
*/ |
486 |
public void mousePressed(MouseEvent e) |
public void mousePressed(MouseEvent e) |
487 |
{ |
{ |
488 |
|
handleEvent(e); |
489 |
} |
} |
490 |
|
|
491 |
|
/** |
492 |
|
* Handles mouse released event |
493 |
|
* |
494 |
|
* @param e MouseEvent |
495 |
|
*/ |
496 |
public void mouseReleased(MouseEvent e) |
public void mouseReleased(MouseEvent e) |
497 |
{ |
{ |
498 |
|
handleEvent(e); |
499 |
|
} |
500 |
|
|
501 |
|
/* |
502 |
|
* This method determines component that was intended to received mouse |
503 |
|
* event, before it was interrupted within the glass pane. This method |
504 |
|
* also redispatches mouse entered and mouse exited events to the |
505 |
|
* appropriate components. This code is slightly modified code from |
506 |
|
* Container.LightweightDispatcher class, which is private inside |
507 |
|
* Container class and cannot be used here. |
508 |
|
*/ |
509 |
|
public void acquireComponentForMouseEvent(MouseEvent me) |
510 |
|
{ |
511 |
|
int x = me.getX(); |
512 |
|
int y = me.getY(); |
513 |
|
|
514 |
|
// Find the candidate which should receive this event. |
515 |
|
Component parent = layeredPane; |
516 |
|
Component candidate = null; |
517 |
|
Point p = me.getPoint(); |
518 |
|
while ((candidate == null) && (parent != null)) |
519 |
|
{ |
520 |
|
p = SwingUtilities.convertPoint(glassPane, p.x, p.y, parent); |
521 |
|
candidate = SwingUtilities.getDeepestComponentAt(parent, p.x, p.y); |
522 |
|
|
523 |
|
if (candidate == null) |
524 |
|
{ |
525 |
|
p = SwingUtilities.convertPoint(parent, p.x, p.y, |
526 |
|
parent.getParent()); |
527 |
|
parent = parent.getParent(); |
528 |
|
} |
529 |
|
} |
530 |
|
|
531 |
|
// If the only candidate we found was the native container itself, |
532 |
|
// don't dispatch any event at all. We only care about the lightweight |
533 |
|
// children here. |
534 |
|
if (candidate == layeredPane) |
535 |
|
candidate = null; |
536 |
|
|
537 |
|
// If our candidate is new, inform the old target we're leaving. |
538 |
|
if ((lastComponentEntered != null) && lastComponentEntered.isShowing() |
539 |
|
&& (lastComponentEntered != candidate)) |
540 |
|
{ |
541 |
|
// Old candidate could have been removed from |
542 |
|
// the layeredPane so we check first. |
543 |
|
if (SwingUtilities.isDescendingFrom(lastComponentEntered, layeredPane)) |
544 |
|
{ |
545 |
|
Point tp = SwingUtilities.convertPoint(layeredPane, x, y, |
546 |
|
lastComponentEntered); |
547 |
|
MouseEvent exited = new MouseEvent(lastComponentEntered, |
548 |
|
MouseEvent.MOUSE_EXITED, |
549 |
|
me.getWhen(), |
550 |
|
me.getModifiersEx(), tp.x, |
551 |
|
tp.y, me.getClickCount(), |
552 |
|
me.isPopupTrigger(), |
553 |
|
me.getButton()); |
554 |
|
|
555 |
|
lastComponentEntered.dispatchEvent(exited); |
556 |
|
} |
557 |
|
|
558 |
|
lastComponentEntered = null; |
559 |
|
} |
560 |
|
|
561 |
|
// If we have a candidate, maybe enter it. |
562 |
|
if (candidate != null) |
563 |
|
{ |
564 |
|
mouseEventTarget = candidate; |
565 |
|
|
566 |
|
if (candidate.isLightweight() && candidate.isShowing() |
567 |
|
&& (candidate != layeredPane) |
568 |
|
&& (candidate != lastComponentEntered)) |
569 |
|
{ |
570 |
|
lastComponentEntered = mouseEventTarget; |
571 |
|
|
572 |
|
Point cp = SwingUtilities.convertPoint(layeredPane, x, y, |
573 |
|
lastComponentEntered); |
574 |
|
MouseEvent entered = new MouseEvent(lastComponentEntered, |
575 |
|
MouseEvent.MOUSE_ENTERED, |
576 |
|
me.getWhen(), |
577 |
|
me.getModifiersEx(), cp.x, |
578 |
|
cp.y, me.getClickCount(), |
579 |
|
me.isPopupTrigger(), |
580 |
|
me.getButton()); |
581 |
|
lastComponentEntered.dispatchEvent(entered); |
582 |
|
} |
583 |
|
} |
584 |
|
|
585 |
|
if ((me.getID() == MouseEvent.MOUSE_RELEASED) |
586 |
|
|| ((me.getID() == MouseEvent.MOUSE_PRESSED) && (pressCount > 0)) |
587 |
|
|| (me.getID() == MouseEvent.MOUSE_DRAGGED)) |
588 |
|
{ |
589 |
|
// If any of the following events occur while a button is held down, |
590 |
|
// they should be dispatched to the same component to which the |
591 |
|
// original MOUSE_PRESSED event was dispatched: |
592 |
|
// - MOUSE_RELEASED |
593 |
|
// - MOUSE_PRESSED: another button pressed while the first is held down |
594 |
|
// - MOUSE_DRAGGED |
595 |
|
if (SwingUtilities.isDescendingFrom(pressedComponent, layeredPane)) |
596 |
|
mouseEventTarget = pressedComponent; |
597 |
|
else if (me.getID() == MouseEvent.MOUSE_CLICKED) |
598 |
|
{ |
599 |
|
// Don't dispatch CLICKED events whose target is not the same as the |
600 |
|
// target for the original PRESSED event. |
601 |
|
if (candidate != pressedComponent) |
602 |
|
mouseEventTarget = null; |
603 |
|
else if (pressCount == 0) |
604 |
|
pressedComponent = null; |
605 |
|
} |
606 |
|
} |
607 |
|
} |
608 |
|
|
609 |
|
/* |
610 |
|
* This method handles mouse events interrupted by glassPane. It |
611 |
|
* redispatches the mouse events appropriately to the intended components. |
612 |
|
* The code in this method is also taken from |
613 |
|
* Container.LightweightDispatcher class. The code is slightly modified |
614 |
|
* to handle the case when mouse is released over non-menu component. In |
615 |
|
* this case this method closes current menu hierarchy before |
616 |
|
* redispatching the event further. |
617 |
|
*/ |
618 |
|
public void handleEvent(AWTEvent e) |
619 |
|
{ |
620 |
|
if (e instanceof MouseEvent) |
621 |
|
{ |
622 |
|
MouseEvent me = (MouseEvent) e; |
623 |
|
|
624 |
|
acquireComponentForMouseEvent(me); |
625 |
|
|
626 |
|
// Avoid dispatching ENTERED and EXITED events twice. |
627 |
|
if (mouseEventTarget != null && mouseEventTarget.isShowing() |
628 |
|
&& (e.getID() != MouseEvent.MOUSE_ENTERED) |
629 |
|
&& (e.getID() != MouseEvent.MOUSE_EXITED)) |
630 |
|
{ |
631 |
|
MouseEvent newEvt = SwingUtilities.convertMouseEvent(glassPane, |
632 |
|
me, |
633 |
|
mouseEventTarget); |
634 |
|
|
635 |
|
mouseEventTarget.dispatchEvent(newEvt); |
636 |
|
|
637 |
|
// If mouse was clicked over the component that is not part |
638 |
|
// of menu hierarchy,then must close the menu hierarchy */ |
639 |
|
if (e.getID() == MouseEvent.MOUSE_RELEASED) |
640 |
|
{ |
641 |
|
boolean partOfMenuHierarchy = false; |
642 |
|
MenuSelectionManager manager = MenuSelectionManager |
643 |
|
.defaultManager(); |
644 |
|
|
645 |
|
partOfMenuHierarchy = manager.isComponentPartOfCurrentMenu(mouseEventTarget); |
646 |
|
|
647 |
|
if (! partOfMenuHierarchy) |
648 |
|
manager.clearSelectedPath(); |
649 |
|
} |
650 |
|
|
651 |
|
switch (e.getID()) |
652 |
|
{ |
653 |
|
case MouseEvent.MOUSE_PRESSED: |
654 |
|
if (pressCount++ == 0) |
655 |
|
pressedComponent = mouseEventTarget; |
656 |
|
break; |
657 |
|
case MouseEvent.MOUSE_RELEASED: |
658 |
|
// Clear our memory of the original PRESSED event, only if |
659 |
|
// we're not expecting a CLICKED event after this. If |
660 |
|
// there is a CLICKED event after this, it will do clean up. |
661 |
|
if ((--pressCount == 0) |
662 |
|
&& (mouseEventTarget != pressedComponent)) |
663 |
|
pressedComponent = null; |
664 |
|
break; |
665 |
|
} |
666 |
|
} |
667 |
|
} |
668 |
} |
} |
669 |
} |
} |
670 |
} |
} |