/* * #! * Ontopia Vizigator * #- * Copyright (C) 2001 - 2013 The Ontopia Project * #- * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * !# */ package net.ontopia.topicmaps.viz; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.ClipboardOwner; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.JCheckBoxMenuItem; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import net.ontopia.topicmaps.core.TopicIF; import com.touchgraph.graphlayout.Node; import com.touchgraph.graphlayout.TGPanel; import com.touchgraph.graphlayout.interaction.DragNodeUI; import com.touchgraph.graphlayout.interaction.TGAbstractDragUI; import com.touchgraph.graphlayout.interaction.TGUserInterface; /** * INTERNAL. */ public class NavigateUI extends TGUserInterface { private VizPanel glPanel; private VizController controller; private TGPanel tgPanel; private GLNavigateMouseListener ml; private TGAbstractDragUI hvDragUI; private DragNodeUI dragNodeUI; private JPopupMenu nodePopup; private JPopupMenu edgePopup; private TMAbstractNode popupNode; private TMAbstractEdge popupEdge; private JMenuItem propertiesMenuItem; private JMenuItem setStartNodeMenuItem; private JMenuItem copyNameMenuItem; private JMenuItem gotoTopicMenuItem; private TextTransfer textTransfer = new TextTransfer(); public static final String ITEM_ID_EXPAND_NODE = "expand.node"; public static final String ITEM_ID_COLLAPSE_NODE = "collapse.node"; public static final String ITEM_ID_HIDE_NODE = "hide.node"; public static final String ITEM_ID_STICKY = "sticky"; public static final String ITEM_ID_PROPERTIES = "properties"; public static final String ITEM_ID_SET_START_TOPIC = "set.start.topic"; public static final String ITEM_ID_COPY_NAME = "copy.name"; public static final String ITEM_ID_GO_TO_TOPIC_PAGE = "go.to.topic.page"; public static final String ITEM_ID_HIDE_EDGE = "hide.edge"; private ParsedMenuFile enabledItemIds; public NavigateUI(VizPanel glp, VizController controller) { this.glPanel = glp; this.controller = controller; tgPanel = glPanel.getTGPanel(); hvDragUI = glPanel.getHVScroll().getHVDragUI(); dragNodeUI = new DragNodeUI(tgPanel); ml = new GLNavigateMouseListener(); enabledItemIds = controller.getEnabledItemIds(); nodePopup = setUpNodePopup(); edgePopup = setUpEdgePopup(); } private JPopupMenu setUpEdgePopup() { JPopupMenu edgePopup = new JPopupMenu(); JMenuItem menuItem = new JMenuItem(Messages.getString("Viz.PopupHideEdge")); ActionListener hideAction = new ActionListener() { public void actionPerformed(ActionEvent e) { if (popupEdge != null) { controller.hideEdge(popupEdge); } } }; menuItem.addActionListener(hideAction); addMenuItem(edgePopup, menuItem, ITEM_ID_HIDE_EDGE); edgePopup.addPopupMenuListener( new PopupMenuListener() { public void popupMenuCanceled(PopupMenuEvent e) { // Do nothing } public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { tgPanel.setMaintainMouseOver(false); tgPanel.setMouseOverE(null); tgPanel.repaint(); } public void popupMenuWillBecomeVisible(PopupMenuEvent e) { // Do nothing } } ); return edgePopup; } private JPopupMenu setUpNodePopup() { JPopupMenu nodePopup = new JPopupMenu(); nodePopup.addPopupMenuListener(new PopupMenuListener() { public void popupMenuCanceled(PopupMenuEvent e) { // Do nothing } public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { tgPanel.setMaintainMouseOver(false); tgPanel.setMouseOverN(null); tgPanel.repaint(); } public void popupMenuWillBecomeVisible(PopupMenuEvent e) { // This is handled in GLNavigateMouseListener>>#processPopupRequest } }); boolean group1 = isEnabled(ITEM_ID_EXPAND_NODE) || isEnabled(ITEM_ID_COLLAPSE_NODE) || isEnabled(ITEM_ID_HIDE_NODE); boolean group2 = isEnabled(ITEM_ID_STICKY); boolean group3 = isEnabled(ITEM_ID_PROPERTIES); boolean group4 = isEnabled(ITEM_ID_SET_START_TOPIC) || isEnabled(ITEM_ID_COPY_NAME) || isEnabled(ITEM_ID_GO_TO_TOPIC_PAGE); makeMenuItem(nodePopup, Messages.getString("Viz.MenuExpandNode"), OP_EXPAND_NODE, ITEM_ID_EXPAND_NODE); makeMenuItem(nodePopup, Messages.getString("Viz.MenuCollapseNode"), OP_COLLAPSE_NODE, ITEM_ID_COLLAPSE_NODE); makeMenuItem(nodePopup, Messages.getString("Viz.MenuHideNode"), OP_HIDE_NODE, ITEM_ID_HIDE_NODE); if (group1 && (group2 || group3 || group4)) nodePopup.addSeparator(); stickyMenu = new JCheckBoxMenuItem( Messages.getString("Viz.PopupSticky"), false); stickyMenu.addActionListener(new NodeMenuListener(OP_STICKY)); addMenuItem(nodePopup, stickyMenu, ITEM_ID_STICKY); if (group2 && (group3 || group4)) nodePopup.addSeparator(); propertiesMenuItem = makeMenuItem(nodePopup, Messages.getString("Viz.PopupProperties"), OP_OPEN_PROPERTIES, ITEM_ID_PROPERTIES); if (group3 && group4) nodePopup.addSeparator(); setStartNodeMenuItem = makeMenuItem(nodePopup, Messages.getString("Viz.PopupSetStartNode"), OP_SET_AS_START_NODE, ITEM_ID_SET_START_TOPIC); copyNameMenuItem = makeMenuItem(nodePopup, Messages.getString("Viz.CopyName"), OP_COPY_NAME, ITEM_ID_COPY_NAME); // addMenuItem(nodePopup, "Debug", OP_DEBUG); gotoTopicMenuItem = makeMenuItem(nodePopup, Messages.getString("Viz.PopupGoToTopic"), OP_GO_TO_TOPIC, ITEM_ID_GO_TO_TOPIC_PAGE); return nodePopup; } public void activate() { tgPanel.addMouseListener(ml); } public void deactivate() { tgPanel.removeMouseListener(ml); } /** * Create and add menu item with a given label to a given menu. * @param menu The menu to holde the menu item. * @param label The label of the menu item. * @param opcode Identifies the operation performed upon selecting this item. * @param itemId ID for testing if the item is enabled. * @return The JMenuItem created adn added to the menu. */ private JMenuItem makeMenuItem(JPopupMenu menu, String label, int opcode, String itemId) { JMenuItem item = new JMenuItem(label); item.addActionListener(new NodeMenuListener(opcode)); addMenuItem(menu, item, itemId); return item; } /** * Add a given menu item to a given menu. * @param menu The menu to holde the menu item. * @param item The menu item. * @param itemId ID for testing if the item is enabled. */ public void addMenuItem(JPopupMenu menu, JMenuItem item, String itemId) { if (isEnabled(itemId)) menu.add(item); } /** * Test if the menu item with the given ID is enabled. * @param itemId The ID of the menu item. * @return true iff the item is enabled. */ protected boolean isEnabled(String itemId) { return enabledItemIds.enabled(itemId); } class GLNavigateMouseListener extends MouseAdapter { private Integer doubleClickInterval = ((Integer) Toolkit .getDefaultToolkit().getDesktopProperty("awt.multiClickInterval")); private ClickThread clickThread; public void mousePressed(MouseEvent e) { // MacOS & Linux (Unix) popup handling if (e.isPopupTrigger()) { processPopupRequest(e); return; } if (e.getModifiers() == MouseEvent.BUTTON1_MASK) { Node mouseOverN = tgPanel.getMouseOverN(); if (mouseOverN == null) { hvDragUI.activate(e); } else { dragNodeUI.activate(e); } } } public void mouseClicked(MouseEvent e) { TMAbstractNode mouseOverN = (TMAbstractNode)tgPanel.getMouseOverN(); if (e.getModifiers() == MouseEvent.BUTTON1_MASK) { if (mouseOverN != null) { if (e.getClickCount() == 2) { this.killSingleClick(); this.performOperation(controller.getConfigurationManager() .getGeneralDoubleClick(), mouseOverN); } else if (e.getClickCount() == 1) { this.spawnSingleClick(mouseOverN); } } } } private void spawnSingleClick(TMAbstractNode node) { if (clickThread != null) clickThread.stopExecution(); int interval; if (doubleClickInterval == null) interval = 500; else interval = doubleClickInterval.intValue(); clickThread = new ClickThread(interval, this, node); clickThread.start(); } public void processSingleClick(TMAbstractNode node) { this.performOperation(controller.getConfigurationManager() .getGeneralSingleClick(), node); } private void killSingleClick() { if (clickThread != null) clickThread.stopExecution(); clickThread = null; } private void performOperation(int anAction, TMAbstractNode node) { switch (anAction) { case VizTopicMapConfigurationManager.EXPAND_NODE: controller.expandNode(node); break; case VizTopicMapConfigurationManager.SET_FOCUS_NODE: controller.focusNode((TMAbstractNode) node); break; case VizTopicMapConfigurationManager.GO_TO_TOPIC: controller.goToTopic(((TMTopicNode) node).getTopic()); } } public void mouseReleased(MouseEvent e) { // Windows Popup handling if (e.isPopupTrigger()) { processPopupRequest(e); } } private void processPopupRequest(MouseEvent e) { popupNode = (TMAbstractNode) tgPanel.getMouseOverN(); popupEdge = (TMAbstractEdge) tgPanel.getMouseOverE(); if (popupNode != null) { stickyMenu.setSelected(popupNode.getFixed()); tgPanel.setMaintainMouseOver(true); if (popupNode instanceof TMAssociationNode) { propertiesMenuItem.setEnabled(false); setStartNodeMenuItem.setEnabled(false); gotoTopicMenuItem.setEnabled(false); } else { propertiesMenuItem.setEnabled(true); setStartNodeMenuItem.setEnabled(!controller.isApplet() && !((TMTopicNode) popupNode).getTopic().equals( controller.getStartTopic())); gotoTopicMenuItem.setEnabled(controller.isApplet()); } if (popupNode instanceof TMTopicNode) copyNameMenuItem.setEnabled(true); else copyNameMenuItem.setEnabled(false); if (isEnabled(ITEM_ID_EXPAND_NODE) || isEnabled(ITEM_ID_COLLAPSE_NODE) || isEnabled(ITEM_ID_COPY_NAME) || isEnabled(ITEM_ID_GO_TO_TOPIC_PAGE) || isEnabled(ITEM_ID_HIDE_NODE) || isEnabled(ITEM_ID_PROPERTIES) || isEnabled(ITEM_ID_SET_START_TOPIC) || isEnabled(ITEM_ID_STICKY)) nodePopup.show(e.getComponent(), e.getX(), e.getY()); } else if (popupEdge != null) { if (isEnabled(ITEM_ID_HIDE_EDGE)) { tgPanel.setMaintainMouseOver(true); edgePopup.show(e.getComponent(), e.getX(), e.getY()); } } else { glPanel.glPopup.show(e.getComponent(), e.getX(), e.getY()); } } } // --- Node menu class private static final int OP_EXPAND_NODE = 0; private static final int OP_COLLAPSE_NODE = 1; private static final int OP_HIDE_NODE = 2; private static final int OP_SET_AS_START_NODE = 3; private static final int OP_GO_TO_TOPIC = 4; private static final int OP_DEBUG = 5; private static final int OP_OPEN_PROPERTIES = 6; private static final int OP_STICKY = 7; private static final int OP_COPY_NAME = 8; protected JCheckBoxMenuItem stickyMenu; class NodeMenuListener implements ActionListener { private int opcode; public NodeMenuListener(int opcode) { this.opcode = opcode; } public void actionPerformed(ActionEvent e) { if (popupNode == null) return; switch (opcode) { case OP_EXPAND_NODE: controller.expandNode(popupNode); break; case OP_COLLAPSE_NODE: controller.collapseNode(popupNode); break; case OP_HIDE_NODE: controller.hideNode(popupNode); break; case OP_SET_AS_START_NODE: TMTopicNode target = (TMTopicNode) popupNode; controller.focusNode(target); controller.setStartTopic(target.getTopic()); break; case OP_DEBUG: System.out.println("----------------------------------------"); VizUtils.debug(popupNode); VizUtils.debug(((TMTopicNode) popupNode).getTopic()); break; case OP_GO_TO_TOPIC: TopicIF topic = ((TMTopicNode) popupNode).getTopic(); controller.goToTopic(topic); break; case OP_OPEN_PROPERTIES: controller.openProperties((TMTopicNode) popupNode); break; case OP_STICKY: // FIXME: All-sticky implementation <<< TMAbstractNode node = popupNode; node.setFixed(!node.getFixed()); break; case OP_COPY_NAME: textTransfer.setClipboardContents(TopicMapView.fullName(popupNode)); } } } public final class TextTransfer implements ClipboardOwner { /** * Place a String on the clipboard, and make this class the * owner of the Clipboard's contents. */ public void setClipboardContents( String aString ){ StringSelection stringSelection = new StringSelection( aString ); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); clipboard.setContents( stringSelection, this ); } public void lostOwnership(Clipboard clipboard, Transferable contents) { // Do nothing } } /** * INTERNAL: Implementation to get around Swing's stupid double * click handling. */ private class ClickThread extends Thread { private int delta; private boolean execute = true; private TMAbstractNode target; private GLNavigateMouseListener parent; public ClickThread(int threashhold, GLNavigateMouseListener p, TMAbstractNode node) { super("ClickThread"); delta = threashhold; target = node; parent = p; } public void run() { try { ClickThread.sleep(delta); } catch (InterruptedException e) { // Do nothing } if (execute) parent.processSingleClick(target); } public void stopExecution() { execute = false; } } }