/*FreeMind - A Program for creating and viewing Mindmaps *Copyright (C) 2000-2001 Joerg Mueller <joergmueller@bigfoot.com> *See COPYING for Details * *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, write to the Free Software *Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package freemind.view.mindmapview; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.FocusTraversalPolicy; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.KeyboardFocusManager; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.dnd.Autoscroll; import java.awt.dnd.DragGestureListener; import java.awt.dnd.DropTargetListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.KeyEvent; import java.awt.geom.CubicCurve2D; import java.awt.print.PageFormat; import java.awt.print.Printable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.ListIterator; import java.util.Timer; import java.util.TimerTask; import java.util.Vector; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JViewport; import javax.swing.KeyStroke; import freemind.controller.Controller; import freemind.controller.NodeKeyListener; import freemind.controller.NodeMotionListener; import freemind.controller.NodeMouseMotionListener; import freemind.main.FreeMind; import freemind.main.Resources; import freemind.main.Tools; import freemind.main.Tools.Pair; import freemind.modes.MindMap; import freemind.modes.MindMapArrowLink; import freemind.modes.MindMapLink; import freemind.modes.MindMapNode; import freemind.preferences.FreemindPropertyListener; /** * This class represents the view of a whole MindMap (in analogy to class * JTree). */ public class MapView extends JPanel implements Printable, Autoscroll { /** * Currently, this listener does nothing. But it should move the map * according to the resize event, such that the current map's center stays * at the same location (seen relative). */ private final class ResizeListener extends ComponentAdapter { Dimension mSize; ResizeListener() { mSize = getSize(); } public void componentResized(ComponentEvent pE) { logger.fine("Component resized " + pE + " old size " + mSize + " new size " + getSize()); // int deltaWidth = mSize.width - getWidth(); // int deltaHeight = mSize.height - getHeight(); // Point viewPosition = getViewPosition(); // viewPosition.x += deltaWidth/2; // viewPosition.y += deltaHeight/2; // mapViewport.setViewPosition(viewPosition); mSize = getSize(); } } static public class ScrollPane extends JScrollPane { public ScrollPane() { // /* // * Diagnosis for the input map, but I haven't // * managed to remove the ctrl pageup/down keys // * from it. // */ // InputMap inputMap = // getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); // KeyStroke[] keys = inputMap.allKeys(); // if (keys != null) { // for (int i = 0; i < keys.length; i++) { // KeyStroke stroke = keys[i]; // logger.fine("Stroke: " + stroke); // } // } else { // logger.fine("No keys in input map"); // } } protected boolean processKeyBinding(KeyStroke pKs, KeyEvent pE, int pCondition, boolean pPressed) { /* * the scroll pane eats control page up and down. Moreover, the page * up and down itself is not very useful, as the map hops away too * far. */ if (pE.getKeyCode() == KeyEvent.VK_PAGE_DOWN || pE.getKeyCode() == KeyEvent.VK_PAGE_UP) return false; return super.processKeyBinding(pKs, pE, pCondition, pPressed); } protected void validateTree() { final Component view = getViewport().getView(); if (view != null) { view.validate(); } super.validateTree(); } } int mPaintingTime; int mPaintingAmount; static boolean printOnWhiteBackground; static Color standardMapBackgroundColor; static Color standardSelectColor; static Color standardSelectRectangleColor; public static Color standardNodeTextColor; static boolean standardDrawRectangleForSelection; private static Stroke standardSelectionStroke; static private FreemindPropertyListener propertyChangeListener; private class Selected { private Vector mySelected = new Vector(); public Selected() { }; public void clear() { if (size() > 0) { removeFocusForHooks(get(0)); } for (Iterator it = mySelected.iterator(); it.hasNext();) { NodeView view = (NodeView) it.next(); changeSelection(view, false); } mySelected.clear(); logger.finest("Cleared selected."); } /** * @param pNode */ private void changeSelection(NodeView pNode, boolean pIsSelected) { if (pNode.getModel() == null) return; getModel().getModeController().changeSelection(pNode, pIsSelected); } public int size() { return mySelected.size(); } public void remove(NodeView node) { if (mySelected.indexOf(node) == 0) { removeFocusForHooks(node); } changeSelection(node, false); mySelected.remove(node); logger.finest("Removed focused " + node); } public void add(NodeView node) { if (size() > 0) { removeFocusForHooks(get(0)); } mySelected.add(0, node); addFocusForHooks(node); changeSelection(node, true); logger.finest("Added focused " + node + "\nAll=" + mySelected); } private void removeFocusForHooks(NodeView node) { if (node.getModel() == null) return; getModel().getModeController().onLostFocusNode(node); } private void addFocusForHooks(NodeView node) { getModel().getModeController().onFocusNode(node); } public NodeView get(int i) { return (NodeView) mySelected.get(i); } public boolean contains(NodeView node) { return mySelected.contains(node); } /** */ public void moveToFirst(NodeView newSelected) { if (contains(newSelected)) { int pos = mySelected.indexOf(newSelected); if (pos > 0) { // move if (size() > 0) { removeFocusForHooks(get(0)); } mySelected.remove(newSelected); mySelected.add(0, newSelected); } } else { add(newSelected); } addFocusForHooks(newSelected); logger.finest("MovedToFront selected " + newSelected + "\nAll=" + mySelected); } } // Logging: private static java.util.logging.Logger logger; private MindMap model; private NodeView rootView = null; private Selected selected = new Selected(); private Controller controller = null; private float zoom = 1F; private boolean disableMoveCursor = true; private int siblingMaxLevel; private boolean isPrinting = false; // use for remove selection from print private NodeView shiftSelectionOrigin = null; private int maxNodeWidth = 0; private Color background = null; private Rectangle boundingRectangle = null; private boolean fitToPage = true; /** Used to identify a right click onto a link curve. */ private Vector/* of ArrowLinkViews */mArrowLinkViews = new Vector(); private Point rootContentLocation; private NodeView nodeToBeVisible = null; private int extraWidth; private boolean selectedsValid = true; // // Constructors // static boolean NEED_PREF_SIZE_BUG_FIX = Controller.JAVA_VERSION .compareTo("1.5.0") < 0; public MapView(MindMap model, Controller controller) { super(); this.model = model; this.controller = controller; if (logger == null) logger = controller.getFrame().getLogger(this.getClass().getName()); mCenterNodeTimer = new Timer(); // initialize the standard colors. if (standardNodeTextColor == null) { try { String stdcolor = getController().getFrame().getProperty( FreeMind.RESOURCES_BACKGROUND_COLOR); standardMapBackgroundColor = Tools.xmlToColor(stdcolor); } catch (Exception ex) { freemind.main.Resources.getInstance().logException(ex); standardMapBackgroundColor = Color.WHITE; } try { String stdcolor = getController().getFrame().getProperty( FreeMind.RESOURCES_NODE_TEXT_COLOR); standardNodeTextColor = Tools.xmlToColor(stdcolor); } catch (Exception ex) { freemind.main.Resources.getInstance().logException(ex); standardSelectColor = Color.WHITE; } // initialize the selectedColor: try { String stdcolor = getController().getFrame().getProperty( FreeMind.RESOURCES_SELECTED_NODE_COLOR); standardSelectColor = Tools.xmlToColor(stdcolor); } catch (Exception ex) { freemind.main.Resources.getInstance().logException(ex); standardSelectColor = Color.BLUE.darker(); } // initialize the selectedTextColor: try { String stdtextcolor = getController().getFrame().getProperty( FreeMind.RESOURCES_SELECTED_NODE_RECTANGLE_COLOR); standardSelectRectangleColor = Tools.xmlToColor(stdtextcolor); } catch (Exception ex) { freemind.main.Resources.getInstance().logException(ex); standardSelectRectangleColor = Color.WHITE; } try { String drawCircle = getController().getFrame().getProperty( FreeMind.RESOURCE_DRAW_RECTANGLE_FOR_SELECTION); standardDrawRectangleForSelection = Tools .xmlToBoolean(drawCircle); } catch (Exception ex) { freemind.main.Resources.getInstance().logException(ex); standardDrawRectangleForSelection = false; } try { String printOnWhite = getController().getFrame().getProperty( FreeMind.RESOURCE_PRINT_ON_WHITE_BACKGROUND); printOnWhiteBackground = Tools.xmlToBoolean(printOnWhite); } catch (Exception ex) { freemind.main.Resources.getInstance().logException(ex); printOnWhiteBackground = true; } createPropertyChangeListener(); } this.setAutoscrolls(true); this.setLayout(new MindMapLayout()); initRoot(); setBackground(standardMapBackgroundColor); addMouseListener(controller.getMapMouseMotionListener()); addMouseMotionListener(controller.getMapMouseMotionListener()); addMouseWheelListener(controller.getMapMouseWheelListener()); addKeyListener(getNodeKeyListener()); // fc, 20.6.2004: to enable tab for insert. setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, Collections.EMPTY_SET); setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, Collections.EMPTY_SET); setFocusTraversalKeys(KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS, Collections.EMPTY_SET); // end change. // fc, 31.3.2013: set policy to achive that after note window close, the // current node is selected. setFocusTraversalPolicy(new FocusTraversalPolicy() { public Component getLastComponent(Container pAContainer) { return getDefaultComponent(pAContainer); } public Component getFirstComponent(Container pAContainer) { return getDefaultComponent(pAContainer); } public Component getDefaultComponent(Container pAContainer) { Component defaultComponent = getSelected(); logger.fine("Focus traversal to: " + defaultComponent); return defaultComponent; } public Component getComponentBefore(Container pAContainer, Component pAComponent) { return getDefaultComponent(pAContainer); } public Component getComponentAfter(Container pAContainer, Component pAComponent) { return getDefaultComponent(pAContainer); } }); this.setFocusTraversalPolicyProvider(true); // like in excel - write a letter means edit (PN) // on the other hand it doesn't allow key navigation (sdfe) disableMoveCursor = Resources.getInstance().getBoolProperty( "disable_cursor_move_paper"); addComponentListener(new ResizeListener()); } private void createPropertyChangeListener() { propertyChangeListener = new FreemindPropertyListener() { public void propertyChanged(String propertyName, String newValue, String oldValue) { if (propertyName.equals(FreeMind.RESOURCES_NODE_TEXT_COLOR)) { standardNodeTextColor = Tools.xmlToColor(newValue); controller.getMapModule().getView().getRoot().updateAll(); } else if (propertyName .equals(FreeMind.RESOURCES_BACKGROUND_COLOR)) { standardMapBackgroundColor = Tools.xmlToColor(newValue); controller.getMapModule().getView() .setBackground(standardMapBackgroundColor); } else if (propertyName .equals(FreeMind.RESOURCES_SELECTED_NODE_COLOR)) { standardSelectColor = Tools.xmlToColor(newValue); controller.getMapModule().getView().repaintSelecteds(); } else if (propertyName .equals(FreeMind.RESOURCES_SELECTED_NODE_RECTANGLE_COLOR)) { standardSelectRectangleColor = Tools.xmlToColor(newValue); controller.getMapModule().getView().repaintSelecteds(); } else if (propertyName .equals(FreeMind.RESOURCE_DRAW_RECTANGLE_FOR_SELECTION)) { standardDrawRectangleForSelection = Tools .xmlToBoolean(newValue); controller.getMapModule().getView().repaintSelecteds(); } else if (propertyName .equals(FreeMind.RESOURCE_PRINT_ON_WHITE_BACKGROUND)) { printOnWhiteBackground = Tools.xmlToBoolean(newValue); } } }; Controller.addPropertyChangeListener(propertyChangeListener); } public void initRoot() { rootContentLocation = new Point(); rootView = NodeViewFactory.getInstance().newNodeView( getModel().getRootNode(), 0, this, this); rootView.insert(); revalidate(); } public int getMaxNodeWidth() { if (maxNodeWidth == 0) { try { maxNodeWidth = Integer.parseInt(controller .getProperty("max_node_width")); } catch (NumberFormatException e) { freemind.main.Resources.getInstance().logException(e); maxNodeWidth = Integer.parseInt(controller .getProperty("el__max_default_window_width")); } } return maxNodeWidth; } // // Navigation // class CheckLaterForCenterNodeTask extends TimerTask { NodeView mNode; public CheckLaterForCenterNodeTask(NodeView pNode) { super(); mNode = pNode; } public void run() { centerNode(mNode); } } /** * Problem: Before scrollRectToVisible is called, the node has the location * (0,0), ie. the location first gets calculated after the scrollpane is * actually scrolled. Thus, as a workaround, I simply call * scrollRectToVisible twice, the first time the location of the node is * calculated, the second time the scrollPane is actually scrolled. */ public void centerNode(final NodeView node) { // FIXME: Correct the resize map behaviour. Tools.waitForEventQueue(); if (!isValid()) { mCenterNodeTimer.schedule(new CheckLaterForCenterNodeTask(node), 100); return; } Dimension d = getViewportSize(); JComponent content = node.getContent(); Rectangle rect = new Rectangle(content.getWidth() / 2 - d.width / 2, content.getHeight() / 2 - d.height / 2, d.width, d.height); logger.fine("Scroll to " + rect + ", " + this.getPreferredSize()); // One call of scrollRectToVisible suffices // after patching the FreeMind.java // and the FreeMindApplet content.scrollRectToVisible(rect); } // scroll with extension (PN 6.2) public void scrollNodeToVisible(NodeView node) { scrollNodeToVisible(node, 0); } // scroll with extension (PN) // e.g. the input field is bigger than the node view => scroll in order to // fit the input field into the screen public void scrollNodeToVisible(NodeView node, int extraWidth) { // see centerNode() if (!isValid()) { nodeToBeVisible = node; this.extraWidth = extraWidth; return; } final int HORIZ_SPACE = 10; final int HORIZ_SPACE2 = 20; final int VERT_SPACE = 5; final int VERT_SPACE2 = 10; // get left/right dimension final JComponent nodeContent = node.getContent(); int width = nodeContent.getWidth(); if (extraWidth < 0) { // extra left width width -= extraWidth; nodeContent.scrollRectToVisible(new Rectangle(-HORIZ_SPACE + extraWidth, -VERT_SPACE, width + HORIZ_SPACE2, nodeContent.getHeight() + VERT_SPACE2)); } else { // extra right width width += extraWidth; nodeContent.scrollRectToVisible(new Rectangle(-HORIZ_SPACE, -VERT_SPACE, width + HORIZ_SPACE2, nodeContent.getHeight() + VERT_SPACE2)); } } /** * Scroll the viewport of the map to the south-west, i.e. scroll the map * itself to the north-east. */ public void scrollBy(int x, int y) { Point currentPoint = getViewPosition(); currentPoint.translate(x, y); // Add the difference to it setViewLocation(currentPoint.x, currentPoint.y); } public void setViewLocation(int x, int y) { Point currentPoint = new Point(x, y); // Watch for the boundaries // Low boundaries if (currentPoint.getX() < 0) { currentPoint.setLocation(0, currentPoint.getY()); } if (currentPoint.getY() < 0) { currentPoint.setLocation(currentPoint.getX(), 0); } // High boundaries Dimension viewportSize = getViewportSize(); if (viewportSize == null) { return; } Dimension size = getSize(); // getView() gets viewed area - JPanel double maxX = size.getWidth() - viewportSize.getWidth(); double maxY = size.getHeight() - viewportSize.getHeight(); if (currentPoint.getX() > maxX) { currentPoint.setLocation(maxX, currentPoint.getY()); } if (currentPoint.getY() > maxY) { currentPoint.setLocation(currentPoint.getX(), maxY); } setViewPosition(currentPoint); } protected void setViewPosition(Point currentPoint) { if (getParent() instanceof JViewport) { JViewport mapViewport = (JViewport) getParent(); mapViewport.setViewPosition(currentPoint); } } // // Node Navigation // private NodeView getVisibleLeft(NodeView oldSelected) { NodeView newSelected = oldSelected; if (oldSelected.getModel().isRoot()) { newSelected = oldSelected.getPreferredVisibleChild(true); } else if (!oldSelected.isLeft()) { newSelected = oldSelected.getVisibleParentView(); } else { // If folded in the direction, unfold if (oldSelected.getModel().isFolded()) { model.getModeController().setFolded(oldSelected.getModel(), false); return oldSelected; } newSelected = oldSelected.getPreferredVisibleChild(true); while (newSelected != null && !newSelected.isContentVisible()) { newSelected = newSelected.getPreferredVisibleChild(true); } } return newSelected; } private NodeView getVisibleRight(NodeView oldSelected) { NodeView newSelected = oldSelected; if (oldSelected.getModel().isRoot()) { newSelected = oldSelected.getPreferredVisibleChild(false); } else if (oldSelected.isLeft()) { newSelected = oldSelected.getVisibleParentView(); } else { // If folded in the direction, unfold if (oldSelected.getModel().isFolded()) { model.getModeController().setFolded(oldSelected.getModel(), false); return oldSelected; } newSelected = oldSelected.getPreferredVisibleChild(false); while (newSelected != null && !newSelected.isContentVisible()) { newSelected = newSelected.getPreferredVisibleChild(false); } } return newSelected; } private NodeView getVisibleNeighbour(int directionCode) { NodeView oldSelected = getSelected(); logger.fine("Old selected: " + oldSelected); NodeView newSelected = null; switch (directionCode) { case KeyEvent.VK_LEFT: newSelected = getVisibleLeft(oldSelected); if (newSelected != null) { setSiblingMaxLevel(newSelected.getModel().getNodeLevel()); } return newSelected; case KeyEvent.VK_RIGHT: newSelected = getVisibleRight(oldSelected); if (newSelected != null) { setSiblingMaxLevel(newSelected.getModel().getNodeLevel()); } return newSelected; case KeyEvent.VK_UP: newSelected = oldSelected.getPreviousVisibleSibling(); break; case KeyEvent.VK_DOWN: newSelected = oldSelected.getNextVisibleSibling(); break; case KeyEvent.VK_PAGE_UP: newSelected = oldSelected.getPreviousPage(); break; case KeyEvent.VK_PAGE_DOWN: newSelected = oldSelected.getNextPage(); break; } return newSelected != oldSelected ? newSelected : null; } public void move(KeyEvent e) { NodeView newSelected = getVisibleNeighbour(e.getKeyCode()); logger.fine("New selected: " + newSelected); if (newSelected != null) { if (!(newSelected == getSelected())) { extendSelectionWithKeyMove(newSelected, e); scrollNodeToVisible(newSelected); } e.consume(); } } public void resetShiftSelectionOrigin() { shiftSelectionOrigin = null; } private void extendSelectionWithKeyMove(NodeView newlySelectedNodeView, KeyEvent e) { if (e.isShiftDown()) { // left or right if (e.getKeyCode() == KeyEvent.VK_LEFT || e.getKeyCode() == KeyEvent.VK_RIGHT) { shiftSelectionOrigin = null; NodeView toBeNewSelected = newlySelectedNodeView .isParentOf(getSelected()) ? newlySelectedNodeView : getSelected(); selectBranch(toBeNewSelected, false); makeTheSelected(toBeNewSelected); return; } if (shiftSelectionOrigin == null) { shiftSelectionOrigin = getSelected(); } final int newY = getMainViewY(newlySelectedNodeView); final int selectionOriginY = getMainViewY(shiftSelectionOrigin); int deltaY = newY - selectionOriginY; NodeView currentSelected = getSelected(); // page up and page down if (e.getKeyCode() == KeyEvent.VK_PAGE_UP) { for (;;) { final int currentSelectedY = getMainViewY(currentSelected); if (currentSelectedY > selectionOriginY) deselect(currentSelected); else makeTheSelected(currentSelected); if (currentSelectedY <= newY) break; currentSelected = currentSelected .getPreviousVisibleSibling(); } return; } if (e.getKeyCode() == KeyEvent.VK_PAGE_DOWN) { for (;;) { final int currentSelectedY = getMainViewY(currentSelected); if (currentSelectedY < selectionOriginY) deselect(currentSelected); else makeTheSelected(currentSelected); if (currentSelectedY >= newY) break; currentSelected = currentSelected.getNextVisibleSibling(); } return; } boolean enlargingMove = (deltaY > 0) && (e.getKeyCode() == KeyEvent.VK_DOWN) || (deltaY < 0) && (e.getKeyCode() == KeyEvent.VK_UP); if (enlargingMove) { toggleSelected(newlySelectedNodeView); } else { toggleSelected(getSelected()); makeTheSelected(newlySelectedNodeView); } } else { shiftSelectionOrigin = null; selectAsTheOnlyOneSelected(newlySelectedNodeView); } } private int getMainViewY(NodeView node) { Point newSelectedLocation = new Point(); Tools.convertPointToAncestor(node.getMainView(), newSelectedLocation, this); final int newY = newSelectedLocation.y; return newY; } public void moveToRoot() { selectAsTheOnlyOneSelected(getRoot()); centerNode(getRoot()); } /** * Select the node, resulting in only that one being selected. */ public void selectAsTheOnlyOneSelected(NodeView newSelected) { logger.finest("selectAsTheOnlyOneSelected"); LinkedList oldSelecteds = getSelecteds(); // select new node this.selected.clear(); this.selected.add(newSelected); // getController().getMode().getDefaultModeController().onSelectHook(newSelected.getModel()); // set last focused as preferred (PN) if (newSelected.getModel().getParentNode() != null) { ((NodeView) newSelected.getParent()).setPreferredChild(newSelected); } scrollNodeToVisible(newSelected); newSelected.repaintSelected(); for (ListIterator e = oldSelecteds.listIterator(); e.hasNext();) { NodeView oldSelected = (NodeView) e.next(); if (oldSelected != null) { oldSelected.repaintSelected(); } } } /** * Add the node to the selection if it is not yet there, remove it * otherwise. */ public void toggleSelected(NodeView newSelected) { logger.finest("toggleSelected"); NodeView oldSelected = getSelected(); if (isSelected(newSelected)) { if (selected.size() > 1) { selected.remove(newSelected); oldSelected = newSelected; } } else { selected.add(newSelected); } getSelected().repaintSelected(); if (oldSelected != null) oldSelected.repaintSelected(); } /** * Add the node to the selection if it is not yet there, making it the * focused selected node. */ public void makeTheSelected(NodeView newSelected) { logger.finest("makeTheSelected"); if (isSelected(newSelected)) { selected.moveToFirst(newSelected); } else { selected.add(newSelected); } getSelected().repaintSelected(); } public void deselect(NodeView newSelected) { if (isSelected(newSelected)) { selected.remove(newSelected); newSelected.repaintSelected(); } } /** * Select the node and his descendants. On extend = false clear up the * previous selection. if extend is false, the past selection will be empty. * if yes, the selection will extended with this node and its children */ public void selectBranch(NodeView newlySelectedNodeView, boolean extend) { // if (!extend || !isSelected(newlySelectedNodeView)) // toggleSelected(newlySelectedNodeView); if (!extend) { selectAsTheOnlyOneSelected(newlySelectedNodeView); } else if (!isSelected(newlySelectedNodeView) && newlySelectedNodeView.isContentVisible()) { toggleSelected(newlySelectedNodeView); } // select(newSelected,extend); for (ListIterator e = newlySelectedNodeView.getChildrenViews() .listIterator(); e.hasNext();) { NodeView target = (NodeView) e.next(); selectBranch(target, true); } } public boolean selectContinuous(NodeView newSelected) { /* fc, 25.1.2004: corrected due to completely inconsistent behaviour. */ NodeView oldSelected = null; // search for the last already selected item among the siblings: LinkedList selList = getSelecteds(); ListIterator j = selList.listIterator(/* selList.size() */); while (j.hasNext()) { NodeView selectedNode = (NodeView) j.next(); if (selectedNode != newSelected && newSelected.isSiblingOf(selectedNode)) { oldSelected = selectedNode; break; } } // no such sibling found. select the new one, and good bye. if (oldSelected == null) { if (!isSelected(newSelected) && newSelected.isContentVisible()) { toggleSelected(newSelected); return true; } return false; } // fc, bug fix: only select the nodes on the same side: boolean oldPositionLeft = oldSelected.isLeft(); boolean newPositionLeft = newSelected.isLeft(); /* find old starting point. */ ListIterator i = newSelected.getSiblingViews().listIterator(); while (i.hasNext()) { NodeView nodeView = (NodeView) i.next(); if (nodeView == oldSelected) { break; } } /* * Remove all selections for the siblings in the connected component * between old and new. */ ListIterator i_backup = i; while (i.hasNext()) { NodeView nodeView = (NodeView) i.next(); if ((nodeView.isLeft() == oldPositionLeft || nodeView.isLeft() == newPositionLeft)) { if (isSelected(nodeView)) deselect(nodeView); else break; } } /* other direction. */ i = i_backup; if (i.hasPrevious()) { i.previous(); /* this is old selected! */ while (i.hasPrevious()) { NodeView nodeView = (NodeView) i.previous(); if (nodeView.isLeft() == oldPositionLeft || nodeView.isLeft() == newPositionLeft) { if (isSelected(nodeView)) deselect(nodeView); else break; } } } /* reset iterator */ i = newSelected.getSiblingViews().listIterator(); /* find starting point. */ i = newSelected.getSiblingViews().listIterator(); while (i.hasNext()) { NodeView nodeView = (NodeView) i.next(); if (nodeView == newSelected || nodeView == oldSelected) { if (!isSelected(nodeView) && nodeView.isContentVisible()) toggleSelected(nodeView); break; } } /* select all up to the end point. */ while (i.hasNext()) { NodeView nodeView = (NodeView) i.next(); if ((nodeView.isLeft() == oldPositionLeft || nodeView.isLeft() == newPositionLeft) && !isSelected(nodeView) && nodeView.isContentVisible()) toggleSelected(nodeView); if (nodeView == newSelected || nodeView == oldSelected) { break; } } // now, make oldSelected the last of the list in order to make this // repeatable: toggleSelected(oldSelected); toggleSelected(oldSelected); return true; } // // get/set methods // public MindMap getModel() { return model; } // e.g. for dragging cursor (PN) public void setMoveCursor(boolean isHand) { int requiredCursor = (isHand && !disableMoveCursor) ? Cursor.MOVE_CURSOR : Cursor.DEFAULT_CURSOR; if (getCursor().getType() != requiredCursor) { setCursor(requiredCursor != Cursor.DEFAULT_CURSOR ? new Cursor( requiredCursor) : null); } } NodeMouseMotionListener getNodeMouseMotionListener() { return getController().getNodeMouseMotionListener(); } NodeMotionListener getNodeMotionListener() { return getController().getNodeMotionListener(); } NodeKeyListener getNodeKeyListener() { return getController().getNodeKeyListener(); } DragGestureListener getNodeDragListener() { return getController().getNodeDragListener(); } DropTargetListener getNodeDropListener() { return getController().getNodeDropListener(); } public NodeView getSelected() { if (selected.size() > 0) return selected.get(0); else return null; } private NodeView getSelected(int i) { return selected.get(i); } public LinkedList getSelecteds() { // return an ArrayList of NodeViews. LinkedList result = new LinkedList(); for (int i = 0; i < selected.size(); i++) { result.add(getSelected(i)); } return result; } /** * @return an ArrayList of MindMapNode objects. If both ancestor and * descendant node are selected, only the ancestor is returned */ public ArrayList /* of MindMapNodes */getSelectedNodesSortedByY() { final HashSet selectedNodesSet = new HashSet(); for (int i = 0; i < selected.size(); i++) { selectedNodesSet.add(getSelected(i).getModel()); } LinkedList pointNodePairs = new LinkedList(); Point point = new Point(); iteration: for (int i = 0; i < selected.size(); i++) { final NodeView view = getSelected(i); final MindMapNode node = view.getModel(); for (MindMapNode parent = node.getParentNode(); parent != null; parent = parent .getParentNode()) { if (selectedNodesSet.contains(parent)) { continue iteration; } } view.getContent().getLocation(point); Tools.convertPointToAncestor(view, point, this); pointNodePairs.add(new Pair(new Integer(point.y), node)); } // do the sorting: Collections.sort(pointNodePairs, new Comparator() { public int compare(Object arg0, Object arg1) { if (arg0 instanceof Pair) { Pair pair0 = (Pair) arg0; if (arg1 instanceof Pair) { Pair pair1 = (Pair) arg1; Integer int0 = (Integer) pair0.getFirst(); Integer int1 = (Integer) pair1.getFirst(); return int0.compareTo(int1); } } throw new IllegalArgumentException("Wrong compare arguments " + arg0 + ", " + arg1); } }); ArrayList selectedNodes = new ArrayList(); for (Iterator it = pointNodePairs.iterator(); it.hasNext();) { selectedNodes.add(((Pair) it.next()).getSecond()); } // logger.fine("Cutting #" + selectedNodes.size()); // for (Iterator it = selectedNodes.iterator(); it.hasNext();) { // MindMapNode node = (MindMapNode) it.next(); // logger.fine("Cutting " + node); // } return selectedNodes; } /** * @return an ArrayList of MindMapNode objects. If both ancestor and * descandant node are selected, only the ancestor ist returned */ public ArrayList /* of MindMapNodes */getSingleSelectedNodes() { ArrayList selectedNodes = new ArrayList(selected.size()); for (int i = selected.size() - 1; i >= 0; i--) { selectedNodes.add(getSelected(i).getModel().shallowCopy()); } return selectedNodes; } public boolean isSelected(NodeView n) { if (isPrinting) return false; return selected.contains(n); } public float getZoom() { return zoom; } public int getZoomed(int number) { return (int) (number * zoom); } public void setZoom(float zoom) { this.zoom = zoom; getRoot().updateAll(); revalidate(); nodeToBeVisible = getSelected(); } /* * (non-Javadoc) * * @see java.awt.Container#validateTree() */ protected void validateTree() { validateSelecteds(); super.validateTree(); setViewPositionAfterValidate(); } private void setViewPositionAfterValidate() { Point viewPosition = getViewPosition(); Point oldRootContentLocation = rootContentLocation; final NodeView root = getRoot(); Point newRootContentLocation = root.getContent().getLocation(); Tools.convertPointToAncestor(getRoot(), newRootContentLocation, getParent()); final int deltaX = newRootContentLocation.x - oldRootContentLocation.x; final int deltaY = newRootContentLocation.y - oldRootContentLocation.y; if (deltaX != 0 || deltaY != 0) { viewPosition.x += deltaX; viewPosition.y += deltaY; final int scrollMode = getScrollMode(); // avoid immediate scrolling here: setScrollMode(JViewport.SIMPLE_SCROLL_MODE); setViewPosition(viewPosition); setScrollMode(scrollMode); } else { // FIXME: fc, 7.9.2011: Here, a viewport->repaint was previously. // Test if really needed. repaint(); } if (nodeToBeVisible != null) { final int scrollMode = getScrollMode(); setScrollMode(JViewport.SIMPLE_SCROLL_MODE); scrollNodeToVisible(nodeToBeVisible, extraWidth); setScrollMode(scrollMode); nodeToBeVisible = null; } } /***************************************************************** ** P A I N T I N G ** *****************************************************************/ // private static Image image = null; /* * (non-Javadoc) * * @see javax.swing.JComponent#paint(java.awt.Graphics) */ public void paint(Graphics g) { long startMilli = System.currentTimeMillis(); if (isValid()) { getRoot().getContent().getLocation(rootContentLocation); Tools.convertPointToAncestor(getRoot(), rootContentLocation, getParent()); } final Graphics2D g2 = (Graphics2D) g; final Object renderingHint = g2 .getRenderingHint(RenderingHints.KEY_ANTIALIASING); final Object renderingTextHint = g2 .getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING); getController().setTextRenderingHint(g2); final Object oldRenderingHintFM = g2 .getRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS); final Object newRenderingHintFM = getZoom() != 1F ? RenderingHints.VALUE_FRACTIONALMETRICS_ON : RenderingHints.VALUE_FRACTIONALMETRICS_OFF; if (oldRenderingHintFM != newRenderingHintFM) { g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, newRenderingHintFM); } super.paint(g); if (oldRenderingHintFM != newRenderingHintFM && RenderingHints.KEY_FRACTIONALMETRICS .isCompatibleValue(oldRenderingHintFM)) { g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, oldRenderingHintFM); } if (RenderingHints.KEY_ANTIALIASING.isCompatibleValue(renderingHint)) { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, renderingHint); } if (RenderingHints.KEY_TEXT_ANTIALIASING .isCompatibleValue(renderingTextHint)) { g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, renderingTextHint); } // final Rectangle rect = getInnerBounds(); // g2.drawRect(rect.x, rect.y, rect.width, rect.height); long localTime = System.currentTimeMillis() - startMilli; mPaintingAmount++; mPaintingTime += localTime; logger.fine("End paint of " + getModel().getRestorable() + " in " + localTime + ". Mean time:" + (mPaintingTime / mPaintingAmount)); } public void paintChildren(Graphics graphics) { // first tries for background images. // if(image == null) { // image = // MindIcon.factory("ksmiletris").getIcon(controller.getFrame()).getImage(); // } // graphics.drawImage(image, 0, 0, getHeight(), getWidth(), null); HashMap labels = new HashMap(); mArrowLinkViews = new Vector(); collectLabels(rootView, labels); super.paintChildren(graphics); Graphics2D graphics2d = (Graphics2D) graphics; Object renderingHint = getController() .setEdgesRenderingHint(graphics2d); paintLinks(rootView, graphics2d, labels, null); Tools.restoreAntialiasing(graphics2d, renderingHint); paintSelecteds(graphics2d); } private void paintSelecteds(Graphics2D g) { if (!standardDrawRectangleForSelection || isCurrentlyPrinting()) { return; } final Color c = g.getColor(); final Stroke s = g.getStroke(); g.setColor(MapView.standardSelectRectangleColor); if (standardSelectionStroke == null) { standardSelectionStroke = new BasicStroke(2.0f); } g.setStroke(standardSelectionStroke); Object renderingHint = getController().setEdgesRenderingHint(g); final Iterator i = getSelecteds().iterator(); while (i.hasNext()) { NodeView selected = (NodeView) i.next(); paintSelected(g, selected); } Tools.restoreAntialiasing(g, renderingHint); g.setColor(c); g.setStroke(s); } private void paintSelected(Graphics2D g, NodeView selected) { final int arcWidth = 4; final JComponent content = selected.getContent(); Point contentLocation = new Point(); Tools.convertPointToAncestor(content, contentLocation, this); g.drawRoundRect(contentLocation.x - arcWidth, contentLocation.y - arcWidth, content.getWidth() + 2 * arcWidth, content.getHeight() + 2 * arcWidth, 15, 15); } /** collect all existing labels in the current map. */ protected void collectLabels(NodeView source, HashMap labels) { // check for existing registry: if (getModel().getLinkRegistry() == null) return; // apply own label: String label = getModel().getLinkRegistry().getLabel(source.getModel()); if (label != null) labels.put(label, source); for (ListIterator e = source.getChildrenViews().listIterator(); e .hasNext();) { NodeView target = (NodeView) e.next(); collectLabels(target, labels); } } protected void paintLinks(NodeView source, Graphics2D graphics, HashMap labels, HashSet /* MindMapLink s */LinkAlreadyVisited) { // check for existing registry: if (getModel().getLinkRegistry() == null) return; if (LinkAlreadyVisited == null) LinkAlreadyVisited = new HashSet(); // references first // logger.fine("Searching for links of " + // source.getModel().toString()); // paint own labels: Vector vec = getModel().getLinkRegistry() .getAllLinks(source.getModel()); for (int i = 0; i < vec.size(); ++i) { MindMapLink ref = (MindMapLink) vec.get(i); if (LinkAlreadyVisited.add(ref)) { // determine type of link if (ref instanceof MindMapArrowLink) { ArrowLinkView arrowLink = new ArrowLinkView( (MindMapArrowLink) ref, getNodeView(ref.getSource()), getNodeView(ref.getTarget())); arrowLink.paint(graphics); mArrowLinkViews.add(arrowLink); // resize map? // adjust container size // Rectangle rec = arrowLink.getBounds(); // the following does not work correctly. fc, 23.10.2003: // if (rec.x < 0) { // getMindMapLayout().resizeMap(rec.x); // } else if (rec.x+rec.width > getSize().width) { // getMindMapLayout().resizeMap(rec.x+rec.width); // } } } } for (ListIterator e = source.getChildrenViews().listIterator(); e .hasNext();) { NodeView target = (NodeView) e.next(); paintLinks(target, graphics, labels, LinkAlreadyVisited); } } public MindMapArrowLink detectCollision(Point p) { if (mArrowLinkViews == null) return null; for (int i = 0; i < mArrowLinkViews.size(); ++i) { ArrowLinkView arrowView = (ArrowLinkView) mArrowLinkViews.get(i); if (arrowView.detectCollision(p)) return arrowView.getModel(); } return null; } /** * Call preparePrinting() before printing and endPrinting() after printing * to minimize calculation efforts */ public void preparePrinting() { if (!isPrinting) { isPrinting = true; /* repaint for printing: */ if (NEED_PREF_SIZE_BUG_FIX) { getRoot().updateAll(); validate(); } else { repaintSelecteds(); } if (printOnWhiteBackground) { background = getBackground(); setBackground(Color.WHITE); } boundingRectangle = getInnerBounds(); fitToPage = Resources.getInstance().getBoolProperty("fit_to_page"); } else { logger.warning("Called preparePrinting although isPrinting is true."); } } private void repaintSelecteds() { final Iterator iterator = getSelecteds().iterator(); while (iterator.hasNext()) { NodeView next = (NodeView) iterator.next(); next.repaintSelected(); } // repaint(); } /** * Call preparePrinting() before printing and endPrinting() after printing * to minimize calculation efforts */ public void endPrinting() { if (isPrinting) { isPrinting = false; if (printOnWhiteBackground) { setBackground(background); } /* repaint for end printing: */ if (NEED_PREF_SIZE_BUG_FIX) { getRoot().updateAll(); validate(); } else { repaintSelecteds(); } } else { logger.warning("Called endPrinting although isPrinting is false."); } } public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) { // TODO: // ask user for : // - center in page (in page format ?) // - print zoom or maximize (in page format ?) // - print selection only // remember those parameters from one session to another // (as orientation & margin from pf) // User parameters double userZoomFactor = 1; try { userZoomFactor = Double.parseDouble(controller .getProperty("user_zoom")); } catch (Exception e) { // freemind.main.Resources.getInstance().logException(e); } userZoomFactor = Math.max(0, userZoomFactor); userZoomFactor = Math.min(2, userZoomFactor); // TODO: read user parameters from properties, make sure the multiple // page // printing really works, have look at Book class. if (fitToPage && pageIndex > 0) { return Printable.NO_SUCH_PAGE; } Graphics2D graphics2D = (Graphics2D) graphics; try { preparePrinting(); double zoomFactor = 1; if (fitToPage) { double zoomFactorX = pageFormat.getImageableWidth() / boundingRectangle.getWidth(); double zoomFactorY = pageFormat.getImageableHeight() / boundingRectangle.getHeight(); zoomFactor = Math.min(zoomFactorX, zoomFactorY); } else { zoomFactor = userZoomFactor; int nrPagesInWidth = (int) Math.ceil(zoomFactor * boundingRectangle.getWidth() / pageFormat.getImageableWidth()); int nrPagesInHeight = (int) Math.ceil(zoomFactor * boundingRectangle.getHeight() / pageFormat.getImageableHeight()); if (pageIndex >= nrPagesInWidth * nrPagesInHeight) { return Printable.NO_SUCH_PAGE; } int yPageCoord = (int) Math.floor(pageIndex / nrPagesInWidth); int xPageCoord = pageIndex - yPageCoord * nrPagesInWidth; graphics2D.translate(-pageFormat.getImageableWidth() * xPageCoord, -pageFormat.getImageableHeight() * yPageCoord); } graphics2D.translate(pageFormat.getImageableX(), pageFormat.getImageableY()); graphics2D.scale(zoomFactor, zoomFactor); graphics2D.translate(-boundingRectangle.getX(), -boundingRectangle.getY()); print(graphics2D); } finally { endPrinting(); } return Printable.PAGE_EXISTS; } // public void print(Graphics g) { // try{ // preparePrinting(); // super.print(g); // } // finally{ // endPrinting(); // } // } /** * For nodes, they can ask, whether or not the width must be bigger to * prevent the "..." at the output. (Bug of java). */ public boolean isCurrentlyPrinting() { return isPrinting; }; /** * Return the bounding box of all the descendants of the source view, that * without BORDER. Should that be implemented in LayoutManager as minimum * size? */ public Rectangle getInnerBounds() { final Rectangle innerBounds = getRoot().getInnerBounds(); innerBounds.x += getRoot().getX(); innerBounds.y += getRoot().getY(); final Rectangle maxBounds = new Rectangle(0, 0, getWidth(), getHeight()); for (int i = 0; i < mArrowLinkViews.size(); ++i) { ArrowLinkView arrowView = (ArrowLinkView) mArrowLinkViews.get(i); final CubicCurve2D arrowLinkCurve = arrowView.arrowLinkCurve; if (arrowLinkCurve == null) { continue; } Rectangle arrowViewBigBounds = arrowLinkCurve.getBounds(); if (!innerBounds.contains(arrowViewBigBounds)) { Rectangle arrowViewBounds = PathBBox.getBBox(arrowLinkCurve) .getBounds(); innerBounds.add(arrowViewBounds); } } return innerBounds.intersection(maxBounds); } public NodeView getRoot() { return rootView; } private MindMapLayout getMindMapLayout() { return (MindMapLayout) getLayout(); } /** * This method is a workaround to allow the inner class access to "this". * Change it as soon the correct syntax is known. */ private MapView getMap() { return this; } public Controller getController() { return controller; } // this property is used when the user navigates up/down using cursor keys // (PN) // it will keep the level of nodes that are understand as "siblings" public int getSiblingMaxLevel() { return this.siblingMaxLevel; } public void setSiblingMaxLevel(int level) { this.siblingMaxLevel = level; } private static final int margin = 20; private Timer mCenterNodeTimer; /* * (non-Javadoc) * * @see java.awt.dnd.Autoscroll#getAutoscrollInsets() */ public Insets getAutoscrollInsets() { Rectangle outer = getBounds(); Rectangle inner = getParent().getBounds(); return new Insets(inner.y - outer.y + margin, inner.x - outer.x + margin, outer.height - inner.height - inner.y + outer.y + margin, outer.width - inner.width - inner.x + outer.x + margin); } /* * (non-Javadoc) * * @see java.awt.dnd.Autoscroll#autoscroll(java.awt.Point) */ public void autoscroll(Point cursorLocn) { Rectangle r = new Rectangle((int) cursorLocn.getX() - margin, (int) cursorLocn.getY() - margin, 1 + 2 * margin, 1 + 2 * margin); scrollRectToVisible(r); } public NodeView getNodeView(MindMapNode node) { if (node == null) { return null; } Collection viewers = node.getViewers(); final Iterator iterator = viewers.iterator(); while (iterator.hasNext()) { NodeView candidateView = (NodeView) iterator.next(); if (candidateView.getMap() == this) { return candidateView; } } return null; } /* * (non-Javadoc) * * @see javax.swing.JComponent#getPreferredSize() */ public Dimension getPreferredSize() { if (!getParent().isValid()) { final Dimension preferredLayoutSize = getLayout() .preferredLayoutSize(this); return preferredLayoutSize; } return super.getPreferredSize(); } void revalidateSelecteds() { selectedsValid = false; } private void validateSelecteds() { if (selectedsValid) { return; } selectedsValid = true; // Keep selected nodes logger.finest("validateSelecteds"); ArrayList selectedNodes = new ArrayList(); for (ListIterator it = getSelecteds().listIterator(); it.hasNext();) { NodeView nodeView = (NodeView) it.next(); if (nodeView != null) { selectedNodes.add(nodeView); } } // Warning, the old views still exist, because JVM has not deleted them. // But don't use them! selected.clear(); for (ListIterator it = selectedNodes.listIterator(); it.hasNext();) { NodeView oldNodeView = ((NodeView) it.next()); if (oldNodeView.isContentVisible()) { NodeView newNodeView = getNodeView(oldNodeView.getModel()); // test, whether or not the node is still visible: if (newNodeView != null) { selected.add(newNodeView); } } } } public Point getNodeContentLocation(NodeView nodeView) { Point contentXY = new Point(0, 0); Tools.convertPointToAncestor(nodeView.getContent(), contentXY, this); return contentXY; } /** * Returns the size of the visible part of the view in view coordinates. */ public Dimension getViewportSize() { if (getParent() instanceof JViewport) { JViewport mapViewport = (JViewport) getParent(); return mapViewport == null ? null : mapViewport.getSize(); } return null; } /** * @return the position of the view or null, if not present. */ public Point getViewPosition() { Point viewPosition = new Point(0, 0); if (getParent() instanceof JViewport) { JViewport mapViewport = (JViewport) getParent(); viewPosition = mapViewport.getViewPosition(); } return viewPosition; } /** * @param pSimpleScrollMode */ private void setScrollMode(int pSimpleScrollMode) { if (getParent() instanceof JViewport) { JViewport mapViewport = (JViewport) getParent(); mapViewport.setScrollMode(pSimpleScrollMode); } } /** * @return */ private int getScrollMode() { if (getParent() instanceof JViewport) { JViewport mapViewport = (JViewport) getParent(); return mapViewport.getScrollMode(); } return 0; } }