/* * Freeplane - mind map editor * Copyright (C) 2010 Volker Boerchers * * This file's author is Volker Boerchers * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.freeplane.core.util; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.swing.Action; import javax.swing.Icon; import javax.swing.JMenuItem; import javax.swing.KeyStroke; import javax.swing.tree.DefaultMutableTreeNode; import org.freeplane.core.resources.ResourceController; import org.freeplane.core.ui.IFreeplaneAction; import org.freeplane.core.ui.IndexedTree; import org.freeplane.core.ui.IndexedTree.Node; import org.freeplane.core.ui.MenuBuilder; import org.freeplane.core.ui.components.UITools; import org.freeplane.features.icon.MindIcon; import org.freeplane.features.map.MapController; import org.freeplane.features.map.NodeModel; import org.freeplane.features.mode.Controller; import org.freeplane.features.mode.ModeController; public class MenuUtils { /** The userObject type for createMenuEntryTree(). */ public static class MenuEntry { private final String key; private final String label; private final String iconKey; private final KeyStroke keyStroke; private final String toolTipText; public MenuEntry(final String key, final String label, final String iconKey, final KeyStroke keyStroke, final String toolTipText) { this.key = key; this.label = label; this.iconKey = iconKey; this.keyStroke = keyStroke; this.toolTipText = toolTipText; } public MenuEntry(String key, String label) { this(key, label, null, null, null); } public String getKey() { return key; } public String getLabel() { return label; } public String getIconKey() { return iconKey; } public KeyStroke getKeyStroke() { return keyStroke; } public String getToolTipText() { return toolTipText; } public MindIcon createMindIcon() { String resource = ResourceController.getResourceController().getProperty(iconKey, null); if (resource == null) { // this is the regular case: most MenuEntries (i.e. actions) will have the iconKey set // but only for a few of these Icons are available return null; } return new MindIcon(resource.replaceAll("/images/(.*).png", "../$1")); } @Override public String toString() { return label; } } /** * returns a tree of all <code>JMenuItem</code> nodes the menu contains (omitting Strings and Separators). * The tree is build from <code>DefaultMutableTreeNode</code> nodes having <code>MenuEntry</code> objects as * their userObjects. * * Note that the root node may have null as userObject if the menu item at <code>menuRootKey</code> doesn't * contain a <code>JMenuItem</code>! * * @param menuRootKey the key of the node that should form the root of the output. * @param menuBuilder access point for the menu(s). */ public static DefaultMutableTreeNode createMenuEntryTree(final String menuRootKey, final MenuBuilder menuBuilder) { final HashMap<String, KeyStroke> menuKeyToKeyStrokeMap = MenuUtils.invertAcceleratorMap(menuBuilder .getAcceleratorMap()); final DefaultMutableTreeNode menuRoot = menuBuilder.get(menuRootKey); final DefaultMutableTreeNode treeRoot = new DefaultMutableTreeNode(MenuUtils.menuNode2menuEntryNode(menuRoot, menuKeyToKeyStrokeMap)); MenuUtils.addChildrenRecursively(treeRoot, menuRoot.children(), menuKeyToKeyStrokeMap); return treeRoot; } @SuppressWarnings("rawtypes") private static void addChildrenRecursively(final DefaultMutableTreeNode treeNode, final Enumeration menuChildren, final HashMap<String, KeyStroke> menuKeyToKeyStrokeMap) { while (menuChildren.hasMoreElements()) { final DefaultMutableTreeNode childMenu = (DefaultMutableTreeNode) menuChildren.nextElement(); final DefaultMutableTreeNode treeChild = MenuUtils.menuNode2menuEntryNode(childMenu, menuKeyToKeyStrokeMap); if (treeChild != null) { treeNode.add(treeChild); MenuUtils.addChildrenRecursively(treeChild, childMenu.children(), menuKeyToKeyStrokeMap); } else { MenuUtils.addChildrenRecursively(treeNode, childMenu.children(), menuKeyToKeyStrokeMap); } } } // in: node for JMenu, out: node for MenuEntry private static DefaultMutableTreeNode menuNode2menuEntryNode(final DefaultMutableTreeNode menuNode, final HashMap<String, KeyStroke> menuKeyToKeyStrokeMap) { final IndexedTree.Node node = (Node) menuNode; final Object userObject = menuNode.getUserObject(); if (userObject instanceof JMenuItem) { final JMenuItem jMenuItem = (JMenuItem) userObject; final IFreeplaneAction action = (IFreeplaneAction) jMenuItem.getAction(); final String key = String.valueOf(node.getKey()); final String iconKey = action == null ? null : action.getIconKey(); return new DefaultMutableTreeNode(new MenuEntry(key, jMenuItem.getText(), iconKey, menuKeyToKeyStrokeMap .get(key), jMenuItem.getToolTipText())); } // the other expected types are String and javax.swing.JPopupMenu.Separator // - just omit them return null; } /** * Same as {@link #createMenuEntryTree(String, Controller)} but all MenuEntries without associated accelerator * and (then) empty submenus are removed from the result. */ public static DefaultMutableTreeNode createAcceleratebleMenuEntryTree(final String menuRootKey, final MenuBuilder menuBuilder) { final DefaultMutableTreeNode menuEntryTreeNode = MenuUtils.createMenuEntryTree(menuRootKey, menuBuilder); final DefaultMutableTreeNode result = new DefaultMutableTreeNode(menuEntryTreeNode.getUserObject()); MenuUtils.addAcceleratableChildrenRecursively(result, menuEntryTreeNode.children()); return result; } // filters out non-acceleratable menu entries @SuppressWarnings("rawtypes") private static void addAcceleratableChildrenRecursively(final DefaultMutableTreeNode target, final Enumeration sourceChildren) { while (sourceChildren.hasMoreElements()) { final DefaultMutableTreeNode sourceChild = (DefaultMutableTreeNode) sourceChildren.nextElement(); final MenuEntry menuEntry = (MenuEntry) sourceChild.getUserObject(); if (sourceChild.isLeaf()) { if (menuEntry.getKeyStroke() != null) { target.add(new DefaultMutableTreeNode(menuEntry)); } } else { final DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(menuEntry); target.add(newNode); MenuUtils.addAcceleratableChildrenRecursively(newNode, sourceChild.children()); if (newNode.isLeaf()) { target.remove(newNode); } } } } private static HashMap<String, KeyStroke> invertAcceleratorMap(final Map<KeyStroke, Node> acceleratorMap) { final HashMap<String, KeyStroke> result = new HashMap<String, KeyStroke>(); for (final Entry<KeyStroke, Node> entry : acceleratorMap.entrySet()) { result.put(String.valueOf(entry.getValue().getKey()), entry.getKey()); } return result; } @SuppressWarnings("rawtypes") public static void insertAsNodeModelRecursively(final NodeModel nodeModel, final Enumeration children, final MapController mapController) { while (children.hasMoreElements()) { final DefaultMutableTreeNode child = (DefaultMutableTreeNode) children.nextElement(); final NodeModel newNodeModel = MenuUtils.insertAsNodeModel(nodeModel, child, mapController); if (!child.isLeaf()) { MenuUtils.insertAsNodeModelRecursively(newNodeModel, child.children(), mapController); } } } private static NodeModel insertAsNodeModel(final NodeModel nodeModel, final DefaultMutableTreeNode treeNode, final MapController mapController) { final MenuEntry menuEntry = (MenuEntry) treeNode.getUserObject(); final String text = menuEntry.getKeyStroke() == null ? menuEntry.getLabel() : menuEntry.getLabel() + ": " + MenuUtils.formatKeyStroke(menuEntry.getKeyStroke()); final NodeModel newNodeModel = mapController.newNode(text, nodeModel.getMap()); if (!treeNode.isLeaf()) { newNodeModel.setFolded(true); } if (menuEntry.getIconKey() != null) { final MindIcon mindIcon = menuEntry.createMindIcon(); if (mindIcon != null) newNodeModel.addIcon(mindIcon); } nodeModel.insert(newNodeModel); return newNodeModel; } public static String formatKeyStroke(final KeyStroke keyStroke) { final String keyModifiersText = KeyEvent.getKeyModifiersText(keyStroke.getModifiers()); final String keyText = KeyEvent.getKeyText(keyStroke.getKeyCode()); return keyModifiersText.length() == 0 ? keyText : keyModifiersText + "+" + keyText; } public static Node findAssignedMenuItemNodeRecursively(final DefaultMutableTreeNode menubarNode, final KeyStroke keystroke) { final Enumeration<?> children = menubarNode.children(); while (children.hasMoreElements()) { final Node child = (Node) children.nextElement(); final Object childUserObject = child.getUserObject(); if (childUserObject instanceof JMenuItem) { final JMenuItem childMenuItem = (JMenuItem) childUserObject; if (keystroke.equals(childMenuItem.getAccelerator())) { return child; } } // recurse final Node assignedMenuItemNode = findAssignedMenuItemNodeRecursively(child, keystroke); if (assignedMenuItemNode != null) return assignedMenuItemNode; } return null; } public static String makeAcceleratorKey(String menuItemKey) { return "acceleratorForMindMap/$" + menuItemKey + "$0"; } public static void executeMenuItems(final List<String> menuItemKeys) { LogUtils.info("menu items to execute: " + menuItemKeys); final MenuBuilder menuBuilder = getMenuBuilder(); for (String menuItemKey : menuItemKeys) { final DefaultMutableTreeNode treeNode = menuBuilder.get(menuItemKey); if (treeNode == null || !treeNode.isLeaf() || !(treeNode.getUserObject() instanceof JMenuItem)) { UITools.errorMessage(TextUtils.format("MenuUtils.invalid_menuitem", menuItemKey)); return; } final JMenuItem menuItem = (JMenuItem) treeNode.getUserObject(); final Action action = menuItem.getAction(); LogUtils.info("executing " + menuItem.getText() + "(" + menuItemKey + ")"); ActionEvent e = new ActionEvent(menuItem, 0, null); action.actionPerformed(e); } } private static MenuBuilder getMenuBuilder() { final ModeController modeController = Controller.getCurrentModeController(); final MenuBuilder menuBuilder = modeController.getUserInputListenerFactory().getMenuBuilder(); return menuBuilder; } public static Icon getMenuItemIcon(String menuItemKey) { final DefaultMutableTreeNode treeNode = getMenuBuilder().get(menuItemKey); if (treeNode == null || !treeNode.isLeaf() || !(treeNode.getUserObject() instanceof JMenuItem)) { return null; } final JMenuItem menuItem = (JMenuItem) treeNode.getUserObject(); return menuItem.getIcon(); } }