2
* Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions are met:
7
* o Redistributions of source code must retain the above copyright notice,
8
* this list of conditions and the following disclaimer.
10
* o Redistributions in binary form must reproduce the above copyright notice,
11
* this list of conditions and the following disclaimer in the documentation
12
* and/or other materials provided with the distribution.
14
* o Neither the name of Flamingo Kirill Grouchnikov nor the names of
15
* its contributors may be used to endorse or promote products derived
16
* from this software without specific prior written permission.
18
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
20
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
package org.pushingpixels.flamingo.internal.ui.common.popup;
32
import java.applet.Applet;
34
import java.awt.event.*;
35
import java.util.List;
38
import javax.swing.border.*;
39
import javax.swing.plaf.*;
40
import javax.swing.plaf.basic.ComboPopup;
42
import org.pushingpixels.flamingo.api.common.JCommandButton;
43
import org.pushingpixels.flamingo.api.common.popup.JPopupPanel;
44
import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager;
45
import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager.PopupEvent;
46
import org.pushingpixels.flamingo.api.ribbon.JRibbon;
47
import org.pushingpixels.flamingo.internal.ui.ribbon.JRibbonTaskToggleButton;
48
import org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.JRibbonApplicationMenuPopupPanel;
49
import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
50
import org.pushingpixels.flamingo.internal.utils.KeyTipManager;
53
* Basic UI for popup panel {@link JPopupPanel}.
55
* @author Kirill Grouchnikov
57
public class BasicPopupPanelUI extends PopupPanelUI {
59
* The associated popup panel.
61
protected JPopupPanel popupPanel;
66
* @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
68
public static ComponentUI createUI(JComponent c) {
69
return new BasicPopupPanelUI();
75
* @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
78
public void installUI(JComponent c) {
79
this.popupPanel = (JPopupPanel) c;
80
super.installUI(this.popupPanel);
89
* @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
92
public void uninstallUI(JComponent c) {
94
uninstallComponents();
96
super.uninstallUI(this.popupPanel);
100
* Installs default settings for the associated command popup menu.
102
protected void installDefaults() {
103
Color bg = this.popupPanel.getBackground();
104
if (bg == null || bg instanceof UIResource) {
105
this.popupPanel.setBackground(FlamingoUtilities.getColor(
106
Color.lightGray, "PopupPanel.background",
107
"Panel.background"));
110
Border b = this.popupPanel.getBorder();
111
if (b == null || b instanceof UIResource) {
112
Border toSet = UIManager.getBorder("PopupPanel.border");
114
toSet = new BorderUIResource.CompoundBorderUIResource(
115
new LineBorder(FlamingoUtilities.getBorderColor()),
116
new EmptyBorder(1, 1, 1, 1));
117
this.popupPanel.setBorder(toSet);
119
LookAndFeel.installProperty(this.popupPanel, "opaque", Boolean.TRUE);
123
* Installs listeners on the associated command popup menu.
125
protected void installListeners() {
126
initiliazeGlobalListeners();
130
* Installs components on the associated command popup menu.
132
protected void installComponents() {
136
* Uninstalls default settings from the associated command popup menu.
138
protected void uninstallDefaults() {
139
LookAndFeel.uninstallBorder(this.popupPanel);
143
* Uninstalls listeners from the associated command popup menu.
145
protected void uninstallListeners() {
149
* Uninstalls subcomponents from the associated command popup menu.
151
protected void uninstallComponents() {
155
* The global listener that tracks the ESC key action on the root panes of
156
* windows that show popup panels.
158
static PopupPanelManager.PopupListener popupPanelManagerListener;
161
* Initializes the global listeners.
163
protected static synchronized void initiliazeGlobalListeners() {
164
if (popupPanelManagerListener != null) {
168
popupPanelManagerListener = new PopupPanelEscapeDismisser();
169
PopupPanelManager.defaultManager().addPopupListener(
170
popupPanelManagerListener);
176
* This class is used to trace the changes in the shown popup panels and
177
* install ESC key listener on the matching root pane so that the popup
178
* panels can be dismissed with the ESC key.
180
* @author Kirill Grouchnikov
182
protected static class PopupPanelEscapeDismisser implements
183
PopupPanelManager.PopupListener {
185
* The currently installed action map on the {@link #tracedRootPane}.
187
private ActionMap newActionMap;
190
* The currently installed input map on the {@link #tracedRootPane}.
192
private InputMap newInputMap;
195
* The last shown popup panel sequence.
197
List<PopupPanelManager.PopupInfo> lastPathSelected;
200
* Currently traced root pane. It is the root pane of the originating
201
* component of the first popup panel in the currently shown sequence of
202
* {@link PopupPanelManager}.
204
private JRootPane tracedRootPane;
207
* Creates a new tracer for popup panels to be dismissed with ESC key.
209
public PopupPanelEscapeDismisser() {
210
PopupPanelManager popupPanelManager = PopupPanelManager
212
this.lastPathSelected = popupPanelManager.getShownPath();
213
if (this.lastPathSelected.size() != 0) {
214
traceRootPane(this.lastPathSelected);
219
public void popupHidden(PopupEvent event) {
220
PopupPanelManager msm = PopupPanelManager.defaultManager();
221
List<PopupPanelManager.PopupInfo> p = msm.getShownPath();
223
if (lastPathSelected.size() != 0 && p.size() == 0) {
224
// if it is the last popup panel to be dismissed, untrace the
229
lastPathSelected = p;
233
* Removes the installed maps on the currently traced root pane.
235
private void untraceRootPane() {
236
if (this.tracedRootPane != null) {
237
removeUIActionMap(this.tracedRootPane, this.newActionMap);
238
removeUIInputMap(this.tracedRootPane, this.newInputMap);
243
public void popupShown(PopupEvent event) {
244
PopupPanelManager msm = PopupPanelManager.defaultManager();
245
List<PopupPanelManager.PopupInfo> p = msm.getShownPath();
247
if (lastPathSelected.size() == 0 && p.size() != 0) {
248
// if it is the first popup panel to be shown, trace the root
253
lastPathSelected = p;
257
* Installs the maps on the root pane of the originating component of
258
* the first popup panel of the specified sequence to trace the ESC key
259
* and dismiss the shown popup panels.
262
* Popup panel sequence.
264
private void traceRootPane(List<PopupPanelManager.PopupInfo> shownPath) {
265
JComponent originator = shownPath.get(0).getPopupOriginator();
266
this.tracedRootPane = SwingUtilities.getRootPane(originator);
268
if (this.tracedRootPane != null) {
269
newInputMap = new ComponentInputMapUIResource(tracedRootPane);
270
newInputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
273
newActionMap = new ActionMapUIResource();
274
newActionMap.put("hidePopupPanel", new AbstractAction() {
276
public void actionPerformed(ActionEvent e) {
277
// Hide the last sequence popup for every ESC keystroke.
278
// There is special case - if the keytips are shown
279
// for the *second* panel of the app menu popup panel,
280
// do not dismiss the popup
281
List<PopupPanelManager.PopupInfo> popups = PopupPanelManager
282
.defaultManager().getShownPath();
283
if (popups.size() > 0) {
284
PopupPanelManager.PopupInfo lastPopup = popups
285
.get(popups.size() - 1);
286
if (lastPopup.getPopupPanel() instanceof JRibbonApplicationMenuPopupPanel) {
287
JRibbonApplicationMenuPopupPanel appMenuPopupPanel = (JRibbonApplicationMenuPopupPanel) lastPopup
289
KeyTipManager.KeyTipChain currentlyShownKeyTipChain = KeyTipManager
291
.getCurrentlyShownKeyTipChain();
292
if ((currentlyShownKeyTipChain != null)
293
&& (currentlyShownKeyTipChain.chainParentComponent == appMenuPopupPanel
298
PopupPanelManager.defaultManager().hideLastPopup();
302
addUIInputMap(tracedRootPane, newInputMap);
303
addUIActionMap(tracedRootPane, newActionMap);
308
* Adds the specified input map to the specified component.
315
void addUIInputMap(JComponent c, InputMap map) {
316
InputMap lastNonUI = null;
317
InputMap parent = c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
319
while (parent != null && !(parent instanceof UIResource)) {
321
parent = parent.getParent();
324
if (lastNonUI == null) {
325
c.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, map);
327
lastNonUI.setParent(map);
329
map.setParent(parent);
333
* Adds the specified action map to the specified component.
340
void addUIActionMap(JComponent c, ActionMap map) {
341
ActionMap lastNonUI = null;
342
ActionMap parent = c.getActionMap();
344
while (parent != null && !(parent instanceof UIResource)) {
346
parent = parent.getParent();
349
if (lastNonUI == null) {
352
lastNonUI.setParent(map);
354
map.setParent(parent);
358
* Removes the specified input map from the specified component.
363
* Input map to remove.
365
void removeUIInputMap(JComponent c, InputMap map) {
367
InputMap parent = c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
369
while (parent != null) {
372
c.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, map
375
im.setParent(map.getParent());
380
parent = parent.getParent();
385
* Removes the specified action map from the specified component.
390
* Action map to remove.
392
void removeUIActionMap(JComponent c, ActionMap map) {
394
ActionMap parent = c.getActionMap();
396
while (parent != null) {
399
c.setActionMap(map.getParent());
401
im.setParent(map.getParent());
406
parent = parent.getParent();
412
* This class is used to dismiss popup panels on the following events:
415
* <li>Mouse click outside any shown popup panel.</li>
416
* <li>Closing, iconifying or deactivation of a top-level window.</li>
417
* <li>Any change in the component hierarchy of a top-level window.</li>
420
* Only one top-level window is tracked at any time. The assumption is that
421
* the {@link PopupPanelManager} only shows popup panels originating from
422
* one top-level window.
424
* @author Kirill Grouchnikov
426
protected static class WindowTracker implements
427
PopupPanelManager.PopupListener, AWTEventListener,
428
ComponentListener, WindowListener {
431
* The currently tracked window. It is the window of the originating
432
* component of the first popup panel in the currently shown sequence of
433
* {@link PopupPanelManager}.
435
Window grabbedWindow;
438
* Last selected path in the {@link PopupPanelManager}.
440
List<PopupPanelManager.PopupInfo> lastPathSelected;
443
* Creates the new window tracker.
445
public WindowTracker() {
446
PopupPanelManager popupPanelManager = PopupPanelManager
448
popupPanelManager.addPopupListener(this);
449
this.lastPathSelected = popupPanelManager.getShownPath();
450
if (this.lastPathSelected.size() != 0) {
451
grabWindow(this.lastPathSelected);
456
* Grabs the window of the first popup panel in the specified popup
460
* Sequence of the currently shown popup panels.
462
void grabWindow(List<PopupPanelManager.PopupInfo> shownPath) {
463
final Toolkit tk = Toolkit.getDefaultToolkit();
464
java.security.AccessController
465
.doPrivileged(new java.security.PrivilegedAction() {
467
public Object run() {
468
tk.addAWTEventListener(WindowTracker.this,
469
AWTEvent.MOUSE_EVENT_MASK
470
| AWTEvent.MOUSE_MOTION_EVENT_MASK
471
| AWTEvent.MOUSE_WHEEL_EVENT_MASK
472
| AWTEvent.WINDOW_EVENT_MASK);
477
Component invoker = shownPath.get(0).getPopupOriginator();
478
grabbedWindow = invoker instanceof Window ? (Window) invoker
479
: SwingUtilities.getWindowAncestor(invoker);
480
if (grabbedWindow != null) {
481
grabbedWindow.addComponentListener(this);
482
grabbedWindow.addWindowListener(this);
487
* Ungrabs the currently tracked window.
489
void ungrabWindow() {
490
final Toolkit tk = Toolkit.getDefaultToolkit();
491
// The grab should be removed
492
java.security.AccessController
493
.doPrivileged(new java.security.PrivilegedAction() {
495
public Object run() {
496
tk.removeAWTEventListener(WindowTracker.this);
500
if (grabbedWindow != null) {
501
grabbedWindow.removeComponentListener(this);
502
grabbedWindow.removeWindowListener(this);
503
grabbedWindow = null;
508
public void popupShown(PopupEvent event) {
509
PopupPanelManager msm = PopupPanelManager.defaultManager();
510
List<PopupPanelManager.PopupInfo> p = msm.getShownPath();
512
if (lastPathSelected.size() == 0 && p.size() != 0) {
513
// if it is the first popup panel to be shown, grab its window
517
lastPathSelected = p;
521
public void popupHidden(PopupEvent event) {
522
PopupPanelManager msm = PopupPanelManager.defaultManager();
523
List<PopupPanelManager.PopupInfo> p = msm.getShownPath();
525
if (lastPathSelected.size() != 0 && p.size() == 0) {
526
// if it is the last popup panel to be hidden, ungrab its window
530
lastPathSelected = p;
534
public void eventDispatched(AWTEvent ev) {
535
if (!(ev instanceof MouseEvent)) {
536
// We are interested in MouseEvents only
539
MouseEvent me = (MouseEvent) ev;
540
final Component src = me.getComponent();
541
JPopupPanel popupPanelParent = (JPopupPanel) SwingUtilities
542
.getAncestorOfClass(JPopupPanel.class, src);
543
switch (me.getID()) {
544
case MouseEvent.MOUSE_PRESSED:
545
boolean wasCommandButtonPopupShowing = false;
546
if (src instanceof JCommandButton) {
547
wasCommandButtonPopupShowing = ((JCommandButton) src)
548
.getPopupModel().isPopupShowing();
551
if (!wasCommandButtonPopupShowing && (popupPanelParent != null)) {
552
// close all popups until this parent and return
553
PopupPanelManager.defaultManager().hidePopups(
557
if (src instanceof JRibbonTaskToggleButton) {
558
JRibbon ribbon = (JRibbon) SwingUtilities
559
.getAncestorOfClass(JRibbon.class, src);
562
.isShowingMinimizedRibbonInPopup(ribbon)) {
563
// This will be handled in the action listener installed
564
// on ribbon task toggle buttons in BasicRibbonUI.
565
// There the ribbon popup will be hidden.
570
// if the popup of command button was showing, it will be hidden
571
// in BasicCommandButtonUI.processPopupAction() - via
572
// BasicCommandButtonUI.createPopupActionListener().
573
if (!wasCommandButtonPopupShowing) {
574
// special case - ignore mouse press on an item in a combo popup
576
.getAncestorOfClass(ComboPopup.class, src) == null) {
577
PopupPanelManager.defaultManager().hidePopups(src);
581
// pass the event so that it gets processed by the controls
584
case MouseEvent.MOUSE_RELEASED:
585
// special case - mouse release on an item in a combo popup
586
if (SwingUtilities.getAncestorOfClass(ComboPopup.class, src) != null) {
587
SwingUtilities.invokeLater(new Runnable() {
590
PopupPanelManager.defaultManager().hidePopups(src);
595
// pass the event so that it gets processed by the controls
598
case MouseEvent.MOUSE_WHEEL:
599
if (popupPanelParent != null) {
600
// close all popups until this parent and return
601
PopupPanelManager.defaultManager().hidePopups(
606
PopupPanelManager.defaultManager().hidePopups(src);
612
* Checks whether the specified component lies inside a
613
* {@link JPopupPanel}.
617
* @return <code>true</code> if the specified component lies inside a
618
* {@link JPopupPanel}.
620
boolean isInPopupPanel(Component src) {
621
for (Component c = src; c != null; c = c.getParent()) {
622
if (c instanceof Applet || c instanceof Window) {
624
} else if (c instanceof JPopupPanel) {
632
public void componentResized(ComponentEvent e) {
633
PopupPanelManager.defaultManager().hidePopups(null);
637
public void componentMoved(ComponentEvent e) {
638
PopupPanelManager.defaultManager().hidePopups(null);
642
public void componentShown(ComponentEvent e) {
643
PopupPanelManager.defaultManager().hidePopups(null);
647
public void componentHidden(ComponentEvent e) {
648
PopupPanelManager.defaultManager().hidePopups(null);
652
public void windowClosing(WindowEvent e) {
653
PopupPanelManager.defaultManager().hidePopups(null);
657
public void windowClosed(WindowEvent e) {
658
PopupPanelManager.defaultManager().hidePopups(null);
662
public void windowIconified(WindowEvent e) {
663
PopupPanelManager.defaultManager().hidePopups(null);
667
public void windowDeactivated(WindowEvent e) {
668
PopupPanelManager.defaultManager().hidePopups(null);
672
public void windowOpened(WindowEvent e) {
676
public void windowDeiconified(WindowEvent e) {
680
public void windowActivated(WindowEvent e) {