package org.freeplane.view.swing.ui; import java.awt.Cursor; import java.awt.KeyboardFocusManager; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.awt.geom.Point2D; import java.net.URI; import java.util.Timer; import java.util.TimerTask; import javax.swing.FocusManager; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import javax.swing.text.JTextComponent; import org.freeplane.core.resources.ResourceController; import org.freeplane.core.ui.ControllerPopupMenuListener; import org.freeplane.core.ui.DoubleClickTimer; import org.freeplane.core.ui.IMouseListener; import org.freeplane.core.ui.components.AutoHide; import org.freeplane.core.util.Compat; import org.freeplane.core.util.LogUtils; import org.freeplane.core.util.SysUtils; import org.freeplane.features.link.LinkController; import org.freeplane.features.map.FoldingController; import org.freeplane.features.map.IMapSelection; import org.freeplane.features.map.MapController; import org.freeplane.features.map.NodeModel; import org.freeplane.features.mode.Controller; import org.freeplane.features.mode.ModeController; import org.freeplane.features.url.UrlManager; import org.freeplane.view.swing.map.FoldingMark; import org.freeplane.view.swing.map.MainView; import org.freeplane.view.swing.map.MapView; import org.freeplane.view.swing.map.MouseArea; import org.freeplane.view.swing.map.NodeView; /** * The MouseMotionListener which belongs to every NodeView */ public class DefaultNodeMouseMotionListener implements IMouseListener { private static final String FOLD_ON_CLICK_INSIDE = "fold_on_click_inside"; private static final String SELECTION_METHOD_DIRECT = "selection_method_direct"; private static final String SELECTION_METHOD_BY_CLICK = "selection_method_by_click"; private static final String TIME_FOR_DELAYED_SELECTION = "time_for_delayed_selection"; private static final String SELECTION_METHOD = "selection_method"; protected class TimeDelayedSelection extends TimerTask { final private MouseEvent e; TimeDelayedSelection(final MouseEvent e) { this.e = e; } /** TimerTask method to enable the selection after a given time. */ @Override public void run() { /* * formerly in ControllerAdapter. To guarantee, that * point-to-select does not change selection if any meta key is * pressed. */ SwingUtilities.invokeLater(new Runnable() { public void run() { if (e.getModifiers() != 0){ return; } try { Controller controller = Controller.getCurrentController(); if (!controller.getModeController().isBlocked()&& controller.getSelection().size() <= 1) { final NodeView nodeV = ((MainView) e.getComponent()).getNodeView(); if(nodeV.isDisplayable() && nodeV.getModel().isVisible() && nodeV.getMap() == controller.getMapViewManager().getMapViewComponent()) controller.getSelection().selectAsTheOnlyOneSelected(nodeV.getModel()); } } catch (NullPointerException e) { } } }); } } /** * The mouse has to stay in this region to enable the selection after a * given time. */ private Rectangle controlRegionForDelayedSelection; // final private ModeController mc; final private ControllerPopupMenuListener popupListener; private Timer timerForDelayedSelection; protected final DoubleClickTimer doubleClickTimer; private boolean wasFocused; public DefaultNodeMouseMotionListener() { // mc = modeController; popupListener = new ControllerPopupMenuListener(); doubleClickTimer = new DoubleClickTimer(); } private void createTimer(final MouseEvent e) { if(! isInside(e)) return; stopTimerForDelayedSelection(); if (!JOptionPane.getFrameForComponent(e.getComponent()).isFocused()) { return; } if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() instanceof JTextComponent) { return; } /* Region to check for in the sequel. */ controlRegionForDelayedSelection = getControlRegion(e.getPoint()); final String selectionMethod = ResourceController.getResourceController().getProperty(SELECTION_METHOD); if (selectionMethod.equals(SELECTION_METHOD_BY_CLICK)) { return; } if (selectionMethod.equals(SELECTION_METHOD_DIRECT)) { new TimeDelayedSelection(e).run(); return; } final int timeForDelayedSelection = ResourceController.getResourceController().getIntProperty( TIME_FOR_DELAYED_SELECTION, 0); timerForDelayedSelection = SysUtils.createTimer(getClass().getSimpleName()); timerForDelayedSelection.schedule(new TimeDelayedSelection(e), timeForDelayedSelection); } protected boolean isInFoldingRegion(MouseEvent e) { return ((MainView)e.getComponent()).isInFoldingRegion(e.getPoint()); } protected boolean isInDragRegion(MouseEvent e) { return ((MainView)e.getComponent()).isInDragRegion(e.getPoint()); } protected Rectangle getControlRegion(final Point2D p) { final int side = 8; return new Rectangle((int) (p.getX() - side / 2), (int) (p.getY() - side / 2), side, side); } public void mouseClicked(final MouseEvent e) { final ModeController mc = Controller.getCurrentController().getModeController(); if(Compat.isMacOsX()){ final JPopupMenu popupmenu = mc.getUserInputListenerFactory().getNodePopupMenu(); if(popupmenu.isShowing()){ return; } } final MainView component = (MainView) e.getComponent(); NodeView nodeView = component.getNodeView(); if (nodeView == null) return; final NodeModel node = nodeView.getModel(); final boolean plainEvent = Compat.isPlainEvent(e); final boolean inside = isInside(e); final MapController mapController = mc.getMapController(); if(e.getButton() == 1){ if(plainEvent){ if (component.isInFollowLinkRegion(e.getX())) { LinkController.getController(mc).loadURL(node, e); e.consume(); return; } final String link = component.getLink(e.getPoint()); if (link != null) { doubleClickTimer.start(new Runnable() { public void run() { loadLink(link); } }); e.consume(); return; } if(inside && e.getClickCount() == 1 && ResourceController.getResourceController().getBooleanProperty(FOLD_ON_CLICK_INSIDE)){ final boolean fold = FoldingMark.UNFOLDED.equals(component.foldingMarkType(mapController, node)) && ! mapController.hasHiddenChildren(node); if(!shouldSelectOnClick(e)){ doubleClickTimer.start(new Runnable() { public void run() { mapController.setFolded(node, fold); } }); } } } else if(Compat.isShiftEvent(e)){ if (isInFoldingRegion(e)) { if (! mapController.showNextChild(node)) mapController.setFolded(node, true); e.consume(); } } } final boolean inFoldingRegion = isInFoldingRegion(e); if ((plainEvent && inFoldingRegion || (inFoldingRegion || inside) && Compat.isCtrlShiftEvent(e)) && !shouldSelectOnClick(e)) { boolean fold = FoldingMark.UNFOLDED.equals(component.foldingMarkType(mapController, node)) && ! mapController.hasHiddenChildren(node); doubleClickTimer.cancel(); mapController.setFolded(node, fold); e.consume(); return; } if(inside && e.getButton() == 1 && ! e.isAltDown()) extendSelection(e); } private boolean shouldSelectOnClick(MouseEvent e) { if(isInside(e)){ final MainView component = (MainView) e.getComponent(); NodeView nodeView = component.getNodeView(); return !nodeView.isSelected() || Controller.getCurrentController().getSelection().size() != 1; } return false; } protected void loadLink(final String link) { try { UrlManager.getController().loadURL(new URI(link)); } catch (Exception ex) { LogUtils.warn(ex); } } /** * Invoked when a mouse button is pressed on a component and then * dragged. */ public void mouseDragged(final MouseEvent e) { if(! isInside(e)) return; stopTimerForDelayedSelection(); final NodeView nodeV = ((MainView) e.getComponent()).getNodeView(); final Controller controller = Controller.getCurrentController(); if (!((MapView) controller.getViewController().getMapView()).isSelected(nodeV)) { controller.getSelection().selectAsTheOnlyOneSelected(nodeV.getModel()); } } public void mouseEntered(final MouseEvent e) { createTimer(e); mouseMoved(e); } public void mouseExited(final MouseEvent e) { stopTimerForDelayedSelection(); final MainView v = (MainView) e.getSource(); v.setMouseArea(MouseArea.OUT); } public void mouseMoved(final MouseEvent e) { final MainView node = ((MainView) e.getComponent()); String link = node.getLink(e.getPoint()); boolean followLink = link != null; Controller currentController = Controller.getCurrentController(); if(! followLink){ followLink = node.isInFollowLinkRegion(e.getX()); if(followLink){ link = LinkController.getController(currentController.getModeController()).getLinkShortText(node.getNodeView().getModel()); } } final Cursor requiredCursor; if(followLink){ currentController.getViewController().out(link); requiredCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); node.setMouseArea(MouseArea.LINK); } else if (isInFoldingRegion(e)){ requiredCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR); node.setMouseArea(MouseArea.FOLDING); } else{ requiredCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR); node.setMouseArea(MouseArea.DEFAULT); } if (node.getCursor().getType() != requiredCursor.getType() || requiredCursor.getType() == Cursor.CUSTOM_CURSOR && node.getCursor() != requiredCursor) { node.setCursor(requiredCursor); } if (controlRegionForDelayedSelection == null || !controlRegionForDelayedSelection.contains(e.getPoint())) { createTimer(e); } } public void mousePressed(final MouseEvent e) { doubleClickTimer.cancel(); final MainView component = (MainView) e.getComponent(); wasFocused = component.hasFocus(); showPopupMenu(e); } public boolean wasFocused() { return wasFocused; } public void mouseReleased(final MouseEvent e) { stopTimerForDelayedSelection(); showPopupMenu(e); } public void showPopupMenu(final MouseEvent e) { if (! e.isPopupTrigger()) return; final boolean inside = isInside(e); final boolean inFoldingRegion = ! inside && isInFoldingRegion(e); if (inside || inFoldingRegion) { if(inside){ stopTimerForDelayedSelection(); ModeController mc = Controller.getCurrentController().getModeController(); final MainView component = (MainView) e.getComponent(); final NodeView nodeView = component.getNodeView(); if(! nodeView.isSelected()){ Controller.getCurrentController().getSelection().selectAsTheOnlyOneSelected(nodeView.getModel()); } final JPopupMenu popupmenu = mc.getUserInputListenerFactory().getNodePopupMenu(); showMenuAndConsumeEvent(popupmenu, e); } else if(inFoldingRegion){ showFoldingPopup(e); } } } private void showFoldingPopup(MouseEvent e) { ModeController mc = Controller.getCurrentController().getModeController(); final FoldingController foldingController = mc.getExtension(FoldingController.class); if(foldingController == null) return; final MainView component = (MainView) e.getComponent(); final NodeView nodeView = component.getNodeView(); final JPopupMenu popupmenu = foldingController.createFoldingPopupMenu(nodeView.getModel()); AutoHide.start(popupmenu); showMenuAndConsumeEvent(popupmenu, e); } private void showMenuAndConsumeEvent(final JPopupMenu popupmenu, final MouseEvent e) { if (popupmenu != null) { popupmenu.addHierarchyListener(popupListener); popupmenu.show(e.getComponent(), e.getX(), e.getY()); e.consume(); } } protected boolean isInside(final MouseEvent e) { return new Rectangle(0, 0, e.getComponent().getWidth(), e.getComponent().getHeight()).contains(e.getPoint()); } protected void stopTimerForDelayedSelection() { if (timerForDelayedSelection != null) { timerForDelayedSelection.cancel(); } timerForDelayedSelection = null; controlRegionForDelayedSelection = null; } private void extendSelection(final MouseEvent e) { final Controller controller = Controller.getCurrentController(); final MainView mainView = (MainView) e.getComponent(); final NodeModel newlySelectedNode = mainView.getNodeView().getModel(); final boolean extend = Compat.isMacOsX() ? e.isMetaDown() : e.isControlDown(); final boolean range = e.isShiftDown(); final IMapSelection selection = controller.getSelection(); if (range && !extend) { selection.selectContinuous(newlySelectedNode); } else if (extend && !range) { selection.toggleSelected(newlySelectedNode); } if(extend == range){ if (selection.isSelected(newlySelectedNode) && selection.size() == 1 && FocusManager.getCurrentManager().getFocusOwner() instanceof MainView) return; else { selection.selectAsTheOnlyOneSelected(newlySelectedNode); } e.consume(); } } }