org.pushingpixels.flamingo.internal.utils.KeyTipManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of flamengo Show documentation
Show all versions of flamengo Show documentation
Flamingo Swing component suite
The newest version!
/*
* Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of Flamingo Kirill Grouchnikov nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pushingpixels.flamingo.internal.utils;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.annotation.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import javax.swing.*;
import javax.swing.FocusManager;
import javax.swing.event.EventListenerList;
import org.pushingpixels.flamingo.api.common.*;
import org.pushingpixels.flamingo.api.common.popup.JPopupPanel;
import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager;
import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager.PopupInfo;
import org.pushingpixels.flamingo.api.ribbon.*;
import org.pushingpixels.flamingo.internal.ui.ribbon.*;
import org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.JRibbonApplicationMenuButton;
import org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.JRibbonApplicationMenuPopupPanel;
public class KeyTipManager {
boolean isShowingKeyTips;
List keyTipChains;
protected EventListenerList listenerList;
protected BlockingQueue processingQueue;
protected ProcessingThread processingThread;
private JRibbonFrame rootOwner;
private Component focusOwner;
private static final KeyTipManager instance = new KeyTipManager();
public interface KeyTipLinkTraversal {
public KeyTipChain getNextChain();
}
public static interface KeyTipListener extends EventListener {
public void keyTipsShown(KeyTipEvent event);
public void keyTipsHidden(KeyTipEvent event);
}
public static class KeyTipEvent extends AWTEvent {
public KeyTipEvent(Object source, int id) {
super(source, id);
}
}
/**
* Annotation to mark a command button that shows UI content with associated
* keytips on clicking its action area. Can be used to associate keytips
* with menu command buttons in the popup menu shown when the ribbon gallery
* is expanded.
*
* @author Kirill Grouchnikov
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public static @interface HasNextKeyTipChain {
}
public class KeyTipLink {
public String keyTipString;
public JComponent comp;
public Point prefAnchorPoint;
public ActionListener onActivated;
public KeyTipLinkTraversal traversal;
public boolean enabled;
}
public class KeyTipChain {
private List links;
public int keyTipLookupIndex;
public JComponent chainParentComponent;
private KeyTipLinkTraversal parent;
public KeyTipChain(JComponent chainParentComponent) {
this.chainParentComponent = chainParentComponent;
this.links = new ArrayList();
this.keyTipLookupIndex = 0;
}
public void addLink(KeyTipLink link) {
this.links.add(link);
}
}
public static KeyTipManager defaultManager() {
return instance;
}
private KeyTipManager() {
this.isShowingKeyTips = false;
this.keyTipChains = new ArrayList();
this.listenerList = new EventListenerList();
this.processingQueue = new LinkedBlockingQueue();
this.processingThread = new ProcessingThread();
this.processingThread.start();
}
public boolean isShowingKeyTips() {
return !this.keyTipChains.isEmpty();
}
public void hideAllKeyTips() {
if (this.keyTipChains.isEmpty())
return;
this.keyTipChains.clear();
this.fireKeyTipsHidden(rootOwner);
repaintWindows();
// try restoring the focus owner if still relevant
this.tryRestoringFocusOwner();
}
private void tryRestoringFocusOwner() {
if (focusOwner != null) {
if (focusOwner.isDisplayable() && focusOwner.isShowing()) {
focusOwner.requestFocus();
}
}
}
public void showRootKeyTipChain(JRibbonFrame ribbonFrame) {
if (!this.keyTipChains.isEmpty()) {
throw new IllegalStateException(
"Can't call this method when key tip chains are present");
}
// store the current focus owner
focusOwner = FocusManager.getCurrentManager().getFocusOwner();
// and transfer the focus to the ribbon frame itself. If the focus
// is cleared, no key events will be dispatched to our window.
ribbonFrame.requestFocus();
rootOwner = ribbonFrame;
final JRibbon ribbon = ribbonFrame.getRibbon();
// root chain - application menu button,
// taskbar panel components and task toggle buttons
KeyTipChain root = new KeyTipChain(ribbon);
// application menu button
final JRibbonApplicationMenuButton appMenuButton = FlamingoUtilities
.getApplicationMenuButton(ribbonFrame);
if ((appMenuButton != null)
&& (ribbon.getApplicationMenuKeyTip() != null)) {
final KeyTipLink appMenuButtonLink = new KeyTipLink();
appMenuButtonLink.comp = appMenuButton;
appMenuButtonLink.keyTipString = ribbon.getApplicationMenuKeyTip();
appMenuButtonLink.prefAnchorPoint = appMenuButton.getUI()
.getKeyTipAnchorCenterPoint();
appMenuButtonLink.onActivated = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
appMenuButton.doPopupClick();
}
};
appMenuButtonLink.enabled = true;
appMenuButtonLink.traversal = new KeyTipLinkTraversal() {
@Override
public KeyTipChain getNextChain() {
// System.out.println("Get next chain");
// collect key tips of all controls in the relevant popup
// panel
List popups = PopupPanelManager.defaultManager()
.getShownPath();
if (popups.size() > 0) {
PopupInfo last = popups.get(popups.size() - 1);
if (last.getPopupOriginator() == appMenuButton) {
JPopupPanel popupPanel = last.getPopupPanel();
KeyTipChain chain = new KeyTipChain(popupPanel);
chain.parent = appMenuButtonLink.traversal;
populateChain(last.getPopupPanel(), chain);
// popupPanel.putClientProperty(KEYTIP_MANAGER,
// KeyTipManager.this);
return chain;
}
}
return null;
}
};
root.addLink(appMenuButtonLink);
}
// taskbar panel components
for (Component taskbarComp : ribbon.getTaskbarComponents()) {
if (taskbarComp instanceof AbstractCommandButton) {
AbstractCommandButton cb = (AbstractCommandButton) taskbarComp;
KeyTipLink actionLink = getCommandButtonActionLink(cb);
if (actionLink != null) {
root.addLink(actionLink);
}
if (taskbarComp instanceof JCommandButton) {
JCommandButton jcb = (JCommandButton) taskbarComp;
KeyTipLink popupLink = getCommandButtonPopupLink(jcb);
if (popupLink != null) {
root.addLink(popupLink);
}
}
}
}
// task toggle buttons
RibbonUI ui = ribbon.getUI();
if (ui instanceof BasicRibbonUI) {
for (Map.Entry ttbEntry : ((BasicRibbonUI) ui)
.getTaskToggleButtons().entrySet()) {
final RibbonTask task = ttbEntry.getKey();
final JRibbonTaskToggleButton taskToggleButton = ttbEntry
.getValue();
String keyTip = task.getKeyTip();
if (keyTip != null) {
final KeyTipLink taskToggleButtonLink = new KeyTipLink();
taskToggleButtonLink.comp = taskToggleButton;
taskToggleButtonLink.keyTipString = keyTip;
taskToggleButtonLink.prefAnchorPoint = new Point(
taskToggleButton.getWidth() / 2, taskToggleButton
.getHeight());
taskToggleButtonLink.onActivated = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
taskToggleButton.doActionClick();
}
};
taskToggleButtonLink.enabled = true;
taskToggleButtonLink.traversal = new KeyTipLinkTraversal() {
@Override
public KeyTipChain getNextChain() {
KeyTipChain taskChain = new KeyTipChain(
taskToggleButton);
// collect key tips of all controls from all task
// bands
for (AbstractRibbonBand band : task.getBands())
populateChain(band, taskChain);
taskChain.parent = taskToggleButtonLink.traversal;
return taskChain;
}
};
root.addLink(taskToggleButtonLink);
}
}
}
this.keyTipChains.add(root);
this.fireKeyTipsShown(ribbonFrame);
ribbonFrame.repaint();
}
public Collection getCurrentlyShownKeyTips() {
if (this.keyTipChains.isEmpty())
return Collections.emptyList();
return Collections.unmodifiableCollection(this.keyTipChains
.get(this.keyTipChains.size() - 1).links);
}
public KeyTipChain getCurrentlyShownKeyTipChain() {
if (this.keyTipChains.isEmpty())
return null;
return this.keyTipChains.get(this.keyTipChains.size() - 1);
}
public void showPreviousChain() {
if (this.keyTipChains.isEmpty())
return;
this.keyTipChains.remove(this.keyTipChains.size() - 1);
// was last?
if (!this.isShowingKeyTips()) {
// try restoring focus owner
this.tryRestoringFocusOwner();
}
repaintWindows();
}
private void addCommandButtonLinks(Component c, KeyTipChain chain) {
AbstractCommandButton cb = (AbstractCommandButton) c;
KeyTipLink actionLink = getCommandButtonActionLink(cb);
if (actionLink != null) {
chain.addLink(actionLink);
}
if (c instanceof JCommandButton) {
JCommandButton jcb = (JCommandButton) c;
KeyTipLink popupLink = getCommandButtonPopupLink(jcb);
if (popupLink != null) {
chain.addLink(popupLink);
}
}
}
private void populateChain(final Component c, final KeyTipChain chain) {
if (c instanceof AbstractCommandButton) {
Rectangle compBounds = c.getBounds();
if (c.isVisible() && c.isShowing()) {
if ((compBounds.height > 0) && (compBounds.width > 0))
addCommandButtonLinks(c, chain);
else
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
Rectangle compBounds = c.getBounds();
if ((compBounds.height > 0)
&& (compBounds.width > 0))
addCommandButtonLinks(c, chain);
}
});
}
}
if (c instanceof JRibbonComponent) {
JRibbonComponent rc = (JRibbonComponent) c;
KeyTipLink link = getRibbonComponentLink(rc);
if (link != null) {
chain.addLink(link);
}
}
if (c instanceof Container) {
Container cont = (Container) c;
for (int i = 0; i < cont.getComponentCount(); i++) {
populateChain(cont.getComponent(i), chain);
}
}
}
private KeyTipLink getCommandButtonActionLink(final AbstractCommandButton cb) {
if (cb.getActionKeyTip() != null) {
final KeyTipLink link = new KeyTipLink();
link.comp = cb;
link.keyTipString = cb.getActionKeyTip();
link.prefAnchorPoint = cb.getUI().getKeyTipAnchorCenterPoint();
link.onActivated = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
cb.doActionClick();
}
};
link.enabled = cb.getActionModel().isEnabled();
if (cb.getClass().isAnnotationPresent(
KeyTipManager.HasNextKeyTipChain.class)) {
link.traversal = new KeyTipLinkTraversal() {
@Override
public KeyTipChain getNextChain() {
// collect key tips of all controls in the relevant
// popup panel
List popups = PopupPanelManager
.defaultManager().getShownPath();
if (popups.size() > 0) {
PopupInfo last = popups.get(popups.size() - 1);
JPopupPanel popupPanel = last.getPopupPanel();
KeyTipChain chain = new KeyTipChain(popupPanel);
populateChain(last.getPopupPanel(), chain);
chain.parent = link.traversal;
return chain;
}
return null;
}
};
} else {
link.traversal = null;
}
return link;
}
return null;
}
private KeyTipLink getRibbonComponentLink(final JRibbonComponent rc) {
if (rc.getKeyTip() != null) {
KeyTipLink link = new KeyTipLink();
link.comp = rc;
link.keyTipString = rc.getKeyTip();
link.prefAnchorPoint = rc.getUI().getKeyTipAnchorCenterPoint();
link.onActivated = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JComponent mainComponent = rc.getMainComponent();
if (mainComponent instanceof AbstractButton) {
((AbstractButton) mainComponent).doClick();
} else {
if (mainComponent instanceof JComboBox) {
((JComboBox) mainComponent).showPopup();
} else {
if (mainComponent instanceof JSpinner) {
JComponent editor = ((JSpinner) mainComponent)
.getEditor();
editor.requestFocusInWindow();
} else {
mainComponent.requestFocusInWindow();
}
}
}
}
};
link.enabled = rc.getMainComponent().isEnabled();
link.traversal = null;
return link;
}
return null;
}
private KeyTipLink getCommandButtonPopupLink(final JCommandButton cb) {
if (cb.getPopupKeyTip() != null) {
final KeyTipLink link = new KeyTipLink();
link.comp = cb;
link.keyTipString = cb.getPopupKeyTip();
link.prefAnchorPoint = cb.getUI().getKeyTipAnchorCenterPoint();
link.onActivated = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (cb instanceof JCommandMenuButton) {
((JCommandMenuButton) cb).doActionRollover();
}
cb.doPopupClick();
}
};
link.enabled = cb.getPopupModel().isEnabled();
link.traversal = new KeyTipLinkTraversal() {
@Override
public KeyTipChain getNextChain() {
// System.out.println("Get next chain");
// collect key tips of all controls in the relevant popup
// panel
List popups = PopupPanelManager.defaultManager()
.getShownPath();
if (popups.size() > 0) {
PopupInfo last = popups.get(popups.size() - 1);
// if (last.getPopupOriginator() == cb) {
JPopupPanel popupPanel = last.getPopupPanel();
// special case - application menu
if (popupPanel instanceof JRibbonApplicationMenuPopupPanel) {
JRibbonApplicationMenuPopupPanel appMenuPopupPanel = (JRibbonApplicationMenuPopupPanel) popupPanel;
// check whether there are entries at level 2
JPanel level1 = appMenuPopupPanel.getPanelLevel1();
JPanel level2 = appMenuPopupPanel.getPanelLevel2();
if (level2.getComponentCount() > 0) {
KeyTipChain chain = new KeyTipChain(level2);
populateChain(level2, chain);
chain.parent = link.traversal;
return chain;
} else {
KeyTipChain chain = new KeyTipChain(level1);
populateChain(level1, chain);
chain.parent = link.traversal;
return chain;
}
} else {
KeyTipChain chain = new KeyTipChain(popupPanel);
populateChain(last.getPopupPanel(), chain);
chain.parent = link.traversal;
return chain;
}
// popupPanel.putClientProperty(KEYTIP_MANAGER,
// KeyTipManager.this);
// }
}
return null;
}
};
return link;
}
return null;
}
public void handleKeyPress(char keyChar) {
this.processingQueue.add(keyChar);
}
private class ProcessingThread extends Thread {
public ProcessingThread() {
super();
this.setName("KeyTipManager processing thread");
this.setDaemon(true);
}
@Override
public void run() {
while (true) {
try {
final char keyChar = processingQueue.take();
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
processNextKeyPress(keyChar);
}
});
} catch (Throwable t) {
t.printStackTrace();
}
}
}
}
private void processNextKeyPress(char keyChar) {
if (this.keyTipChains.isEmpty())
return;
KeyTipChain currChain = this.keyTipChains
.get(this.keyTipChains.size() - 1);
// go over the key tip links and see if there is an exact match
for (final KeyTipLink link : currChain.links) {
String keyTipString = link.keyTipString;
if ((Character.toLowerCase(keyTipString
.charAt(currChain.keyTipLookupIndex)) == Character
.toLowerCase(keyChar))
&& (keyTipString.length() == (currChain.keyTipLookupIndex + 1))) {
// exact match
if (link.enabled) {
link.onActivated.actionPerformed(new ActionEvent(link.comp,
ActionEvent.ACTION_PERFORMED, "keyTipActivated"));
if (link.traversal != null) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
final KeyTipChain next = link.traversal
.getNextChain();
if (next != null) {
KeyTipChain prev = (keyTipChains.isEmpty() ? null
: keyTipChains.get(keyTipChains
.size() - 1));
keyTipChains.add(next);
repaintWindows();
if (prev != null) {
// force repaint of all menu buttons
for (KeyTipLink link : prev.links) {
if (link.comp instanceof JCommandMenuButton)
link.comp.repaint();
}
}
}
}
});
} else {
// match found and activated, and no further
// traversal - dismiss all key tip chains
hideAllKeyTips();
}
}
return;
}
}
// go over the key tip links and look for key tips that have
// the specified character as the prefix
if (currChain.keyTipLookupIndex == 0) {
KeyTipChain secondary = new KeyTipChain(
currChain.chainParentComponent);
secondary.keyTipLookupIndex = 1;
for (KeyTipLink link : currChain.links) {
String keyTipString = link.keyTipString;
if ((Character.toLowerCase(keyTipString
.charAt(currChain.keyTipLookupIndex)) == Character
.toLowerCase(keyChar))
&& (keyTipString.length() == 2)) {
KeyTipLink secondaryLink = new KeyTipLink();
secondaryLink.comp = link.comp;
secondaryLink.enabled = link.enabled;
secondaryLink.keyTipString = link.keyTipString;
secondaryLink.onActivated = link.onActivated;
secondaryLink.prefAnchorPoint = link.prefAnchorPoint;
secondaryLink.traversal = link.traversal;
secondary.addLink(secondaryLink);
}
}
if (secondary.links.size() > 0) {
this.keyTipChains.add(secondary);
}
repaintWindows();
return;
}
}
private void repaintWindows() {
for (Window window : Window.getWindows()) {
window.repaint();
}
List popups = PopupPanelManager.defaultManager()
.getShownPath();
for (PopupPanelManager.PopupInfo popup : popups) {
JPopupPanel popupPanel = popup.getPopupPanel();
popupPanel.paintImmediately(new Rectangle(0, 0, popupPanel
.getWidth(), popupPanel.getHeight()));
}
}
public void addKeyTipListener(KeyTipListener keyTipListener) {
this.listenerList.add(KeyTipListener.class, keyTipListener);
}
public void removeKeyTipListener(KeyTipListener keyTipListener) {
this.listenerList.remove(KeyTipListener.class, keyTipListener);
}
protected void fireKeyTipsShown(JRibbonFrame ribbonFrame) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
KeyTipEvent e = new KeyTipEvent(ribbonFrame, 0);
// 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] == KeyTipListener.class) {
((KeyTipListener) listeners[i + 1]).keyTipsShown(e);
}
}
}
protected void fireKeyTipsHidden(JRibbonFrame ribbonFrame) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
KeyTipEvent e = new KeyTipEvent(ribbonFrame, 0);
// 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] == KeyTipListener.class) {
((KeyTipListener) listeners[i + 1]).keyTipsHidden(e);
}
}
}
public void refreshCurrentChain() {
KeyTipChain curr = this.keyTipChains.get(this.keyTipChains.size() - 1);
if (curr.parent == null)
return;
KeyTipChain refreshed = curr.parent.getNextChain();
this.keyTipChains.remove(this.keyTipChains.size() - 1);
this.keyTipChains.add(refreshed);
repaintWindows();
}
}