Package org.freeplane.view.swing.map

Source Code of org.freeplane.view.swing.map.MapView$Selection

/*
*  Freeplane - mind map editor
*  Copyright (C) 2008 Joerg Mueller, Daniel Polansky, Christian Foltin, Dimitry Polivaev
*
*  This file is modified by Dimitry Polivaev in 2008.
*
*  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.view.swing.map;

import java.awt.AWTKeyStroke;
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.EventQueue;
import java.awt.Font;
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.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.RoundRectangle2D;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.AbstractList;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import java.util.Vector;

import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;

import org.freeplane.core.io.xml.TreeXmlReader;
import org.freeplane.core.resources.IFreeplanePropertyListener;
import org.freeplane.core.resources.ResourceController;
import org.freeplane.core.ui.IUserInputListenerFactory;
import org.freeplane.core.ui.components.UITools;
import org.freeplane.core.util.ColorUtils;
import org.freeplane.core.util.LogUtils;
import org.freeplane.features.attribute.AttributeController;
import org.freeplane.features.attribute.ModelessAttributeController;
import org.freeplane.features.filter.Filter;
import org.freeplane.features.link.ConnectorModel;
import org.freeplane.features.link.ConnectorModel.Shape;
import org.freeplane.features.link.LinkController;
import org.freeplane.features.link.NodeLinkModel;
import org.freeplane.features.link.NodeLinks;
import org.freeplane.features.map.IMapChangeListener;
import org.freeplane.features.map.IMapSelection;
import org.freeplane.features.map.INodeChangeListener;
import org.freeplane.features.map.INodeView;
import org.freeplane.features.map.MapChangeEvent;
import org.freeplane.features.map.MapController;
import org.freeplane.features.map.MapModel;
import org.freeplane.features.map.NodeChangeEvent;
import org.freeplane.features.map.NodeModel;
import org.freeplane.features.map.SummaryNode;
import org.freeplane.features.mode.Controller;
import org.freeplane.features.mode.ModeController;
import org.freeplane.features.nodestyle.NodeStyleController;
import org.freeplane.features.note.NoteController;
import org.freeplane.features.print.FitMap;
import org.freeplane.features.styles.MapStyle;
import org.freeplane.features.styles.MapStyleModel;
import org.freeplane.features.styles.MapViewLayout;
import org.freeplane.features.text.TextController;
import org.freeplane.features.ui.ViewController;
import org.freeplane.features.url.UrlManager;
import org.freeplane.view.swing.features.filepreview.IViewerFactory;
import org.freeplane.view.swing.features.filepreview.ImageLoadingListener;
import org.freeplane.view.swing.features.filepreview.ScalableComponent;
import org.freeplane.view.swing.features.filepreview.ViewerController;
import org.freeplane.view.swing.map.link.ConnectorView;
import org.freeplane.view.swing.map.link.EdgeLinkView;
import org.freeplane.view.swing.map.link.ILinkView;

/**
* This class represents the view of a whole MindMap (in analogy to class
* JTree).
*/
public class MapView extends JPanel implements Printable, Autoscroll, IMapChangeListener, IFreeplanePropertyListener {

  private MapViewLayout layoutType;

  public MapViewLayout getLayoutType() {
    return layoutType;
  }

  protected void setLayoutType(final MapViewLayout layoutType) {
    this.layoutType = layoutType;
  }

  private boolean showNotes;

  boolean showNotes() {
    return showNotes;
  }

  private void setShowNotes() {
    final boolean showNotes= NoteController.getController(getModeController()).showNotesInMap(getModel());
    if(this.showNotes == showNotes){
      return;
    }
    this.showNotes = showNotes;
    getRoot().updateAll();
  }

  private PaintingMode paintingMode = null;

  private class MapSelection implements IMapSelection {
    public void centerNode(final NodeModel node) {
      final NodeView nodeView = getNodeView(node);
      if (nodeView != null) {
        MapView.this.centerNode(nodeView, false);
      }
    }

    public NodeModel getSelected() {
      final NodeView selected = MapView.this.getSelected();
      return selected.getModel();
    }

    public Set<NodeModel> getSelection() {
      return MapView.this.getSelectedNodes();
    }


    public List<NodeModel> getOrderedSelection() {
      return MapView.this.getOrderedSelectedNodes();
        }
    public List<NodeModel> getSortedSelection(final boolean differentSubtrees) {
      return MapView.this.getSelectedNodesSortedByY(differentSubtrees);
    }

    public boolean isSelected(final NodeModel node) {
      final NodeView nodeView = getNodeView(node);
      return nodeView != null && MapView.this.isSelected(nodeView);
    }

    public void keepNodePosition(final NodeModel node, final float horizontalPoint, final float verticalPoint) {
      anchorToSelected(node, horizontalPoint, verticalPoint);
    }

    public void makeTheSelected(final NodeModel node) {
      final NodeView nodeView = getNodeView(node);
      if (nodeView != null) {
        MapView.this.addSelected(nodeView, false);
      }
    }

    public void scrollNodeToVisible(final NodeModel node) {
      MapView.this.scrollNodeToVisible(getNodeView(node));
    }

    public void selectAsTheOnlyOneSelected(final NodeModel node) {
      final NodeView nodeView = getNodeView(node);
      if (nodeView != null) {
        MapView.this.selectAsTheOnlyOneSelected(nodeView);
      }
    }

    public void selectBranch(final NodeModel node, final boolean extend) {
      if(! extend)
        selectAsTheOnlyOneSelected(node);
      MapView.this.addBranchToSelection(getNodeView(node));
    }

    public void selectContinuous(final NodeModel node) {
      MapView.this.selectContinuous(getNodeView(node));
    }

    public void selectRoot() {
      final NodeModel rootNode = getModel().getRootNode();
      selectAsTheOnlyOneSelected(rootNode);
      centerNode(rootNode);
    }

    public void setSiblingMaxLevel(final int nodeLevel) {
      MapView.this.setSiblingMaxLevel(nodeLevel);
    }

    public int size() {
      return getSelection().size();
    }

    public void toggleSelected(final NodeModel node) {
      MapView.this.toggleSelected(getNodeView(node));
    }

        public void replaceSelection(NodeModel[] nodes) {
            if(nodes.length == 0)
                return;
            ArrayList<NodeView> views = new ArrayList<NodeView>(nodes.length);
            for(NodeModel node : nodes) {
              final NodeView nodeView = getNodeView(node);
              if(nodeView != null)
                views.add(nodeView);
            }
            MapView.this.replaceSelection(views.toArray(new NodeView[]{}));
        }

  }

  private class Selection {
    final private Set<NodeView> selectedSet = new LinkedHashSet<NodeView>();
    final private List<NodeView> selectedList = new ArrayList<NodeView>();
    private NodeView selectedNode = null;
    private NodeView selectionStart = null;
    private NodeView selectionEnd = null;

    public Selection() {
    };

    private void select(final NodeView node) {
      clear();
      selectedSet.add(node);
      selectedList.add(node);
      selectedNode = node;
      selectionEnd = selectionStart = node;
      addSelectionForHooks(node);
      node.repaintSelected();
    }

    private boolean add(final NodeView node) {
      if(selectedNode == null){
        select(node);
        return true;
      }
      else{
        if(selectedSet.add(node)){
          selectedList.add(node);
          node.repaintSelected();
          return true;
        }
        return false;
      }
    }

    private void addSelectionForHooks(final NodeView node) {
      if(! isSelected())
        return;
      final ModeController modeController = getModeController();
      final MapController mapController = modeController.getMapController();
      final NodeModel model = node.getModel();
      mapController.onSelect(model);
    }

    private void clear() {
      if (selectedNode != null) {
        removeSelectionForHooks(selectedNode);
        selectedNode = null;
        selectedSet.clear();
        selectedList.clear();
        selectionEnd = selectionStart = null;
      }
    }

    private boolean contains(final NodeView node) {
      return selectedSet.contains(node);
    }

    public Set<NodeView> getSelection() {
      return Collections.unmodifiableSet(selectedSet);
    }

    private boolean deselect(final NodeView node) {
      if(selectionStart == node)
        selectionEnd = selectionStart = null;
      final boolean selectedChanged = selectedNode != null && selectedNode.equals(node);
      if (selectedChanged) {
        removeSelectionForHooks(node);
      }
      if (selectedSet.remove(node)){
        final int last = selectedList.size() - 1;
        if(selectedList.get(last) .equals(node))
          selectedList.remove(last);
        else
          selectedList.remove(node);
        node.repaintSelected();
        if(selectedChanged) {
                  if (size() > 0) {
                    selectedNode = selectedSet.iterator().next();
                    addSelectionForHooks(selectedNode);
                  }
                  else{
                    selectedNode = null;
                  }
                }
        return true;
      }
      return false;
    }

    private void removeSelectionForHooks(final NodeView node) {
      if (node.getModel() == null || ! isSelected()) {
        return;
      }
      getModeController().getMapController().onDeselect(node.getModel());
    }

    private int size() {
      return selectedSet.size();
    }

    private void replace(NodeView[] newSelection) {
            if(newSelection.length == 0)
                return;
            final boolean selectedChanges = ! newSelection[0].equals(selectedNode);
            if (selectedChanges) {
              if(selectedNode != null)
                removeSelectionForHooks(selectedNode);
              selectedNode = newSelection[0];
            }
            for(NodeView view : newSelection)
                if (!selectedSet.contains(view))
                  view.repaintSelected();
            final NodeView[] oldSelection = selectedSet.toArray(new NodeView[selectedSet.size()]);
            selectedSet.clear();
            selectedList.clear();
            for(NodeView view : newSelection)
                if (selectedSet.add(view))
                  selectedList.add(view);
            if (selectedChanges) {
                addSelectionForHooks(selectedNode);
            }
            for(NodeView view : oldSelection)
                if (!selectedSet.contains(view))
                  view.repaintSelected();
        }

    public NodeView[] toArray() {
          return selectedList.toArray(new NodeView[selectedList.size()]);
        }

    private List<NodeView> getSelectedList() {
          return selectedList;
        }

    private Set<NodeView> getSelectedSet() {
          return selectedSet;
        }

    public NodeView getSelectionStart() {
      return selectionStart;
    }

    public void setSelectionStart(NodeView node) {
      selectionEnd = selectionStart = node;
    }
    public NodeView getSelectionEnd() {
      return selectionEnd;
    }

    public void setSelectionEnd(NodeView selectionEnd) {
      this.selectionEnd = selectionEnd;
    }

  }

  private static final int margin = 20;
  static boolean printOnWhiteBackground;
  static private IFreeplanePropertyListener propertyChangeListener;
  public static final String RESOURCES_SELECTED_NODE_COLOR = "standardselectednodecolor";
  public static final String RESOURCES_SELECTED_NODE_RECTANGLE_COLOR = "standardselectednoderectanglecolor";
  private static final String PRESENTATION_DIMMER_TRANSPARENCY = "presentation_dimmer_transparency";
  private static final String PRESENTATION_MODE_ENABLED = "presentation_mode";

  private static final long serialVersionUID = 1L;
  static boolean standardDrawRectangleForSelection;
  static Color standardSelectColor;
  private static Stroke standardSelectionStroke;
  static Color standardSelectRectangleColor;
  private NodeView anchor;
  private Point anchorContentLocation;
  /** Used to identify a right click onto a link curve. */
  private Vector<ILinkView> arrowLinkViews;
  private Color background = null;
  private JComponent backgroundComponent;
  private Rectangle boundingRectangle = null;
  private boolean disableMoveCursor = true;
  private int extraWidth;
  private FitMap fitMap = FitMap.USER_DEFINED;
  private boolean isPreparedForPrinting = false;
  private boolean isPrinting = false;
  private final ModeController modeController;
  final private MapModel model;
  private NodeView nodeToBeVisible = null;
  private NodeView rootView = null;
  private boolean selectedsValid = true;
  final private Selection selection = new Selection();
  private int siblingMaxLevel;
  private float zoom = 1F;
  private float anchorHorizontalPoint;
  private float anchorVerticalPoint;
  private NodeView nodeToBeCentered;
    private Font noteFont;
    private Font detailFont;
    private Color detailForeground;
    private Color detailBackground;
  private boolean slowScroll;
  private static boolean presentationModeEnabled;
  private boolean fitToViewport;
  private static int transparency;
  final private ComponentAdapter backgroundImageResizer;
  private INodeChangeListener connectorChangeListener;

  public MapView(final MapModel model, final ModeController modeController) {
    super();
    this.model = model;
    this.modeController = modeController;
    final String name = model.getTitle();
    setName(name);
    if (MapView.standardSelectColor == null) {
      final String stdcolor = ResourceController.getResourceController().getProperty(
          MapView.RESOURCES_SELECTED_NODE_COLOR);
      MapView.standardSelectColor = ColorUtils.stringToColor(stdcolor);
      final String stdtextcolor = ResourceController.getResourceController().getProperty(
          MapView.RESOURCES_SELECTED_NODE_RECTANGLE_COLOR);
      MapView.standardSelectRectangleColor = ColorUtils.stringToColor(stdtextcolor);
      final String drawCircle = ResourceController.getResourceController().getProperty(
          ResourceController.RESOURCE_DRAW_RECTANGLE_FOR_SELECTION);
      MapView.standardDrawRectangleForSelection = TreeXmlReader.xmlToBoolean(drawCircle);
      final String printOnWhite = ResourceController.getResourceController()
          .getProperty("printonwhitebackground");
      MapView.printOnWhiteBackground = TreeXmlReader.xmlToBoolean(printOnWhite);
      MapView.transparency = 255 - ResourceController.getResourceController().getIntProperty(PRESENTATION_DIMMER_TRANSPARENCY, 0x70);
      MapView.presentationModeEnabled = ResourceController.getResourceController().getBooleanProperty(PRESENTATION_MODE_ENABLED);

      createPropertyChangeListener();
    }
    this.setAutoscrolls(true);
    this.setLayout(new MindMapLayout());
    final NoteController noteController = NoteController.getController(getModeController());
    showNotes= noteController != null && noteController.showNotesInMap(getModel());
        updateContentStyle();
        initRoot();
    setBackground(requiredBackground());
    final MapStyleModel mapStyleModel = MapStyleModel.getExtension(model);
    zoom = mapStyleModel.getZoom();
    layoutType = mapStyleModel.getMapViewLayout();
    final IUserInputListenerFactory userInputListenerFactory = getModeController().getUserInputListenerFactory();
    addMouseListener(userInputListenerFactory.getMapMouseListener());
    addMouseMotionListener(userInputListenerFactory.getMapMouseListener());
    addMouseWheelListener(userInputListenerFactory.getMapMouseWheelListener());
    setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, emptyNodeViewSet());
    setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, emptyNodeViewSet());
    setFocusTraversalKeys(KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS, emptyNodeViewSet());
    disableMoveCursor = ResourceController.getResourceController().getBooleanProperty("disable_cursor_move_paper");
    backgroundImageResizer = new ComponentAdapter() {
      public void componentResized(ComponentEvent e) {
        if (fitToViewport) {
          adjustBackgroundComponentScale();
          repaint();
        }
      }
    };
    addComponentListener(backgroundImageResizer);
    final String fitToViewportAsString = MapStyle.getController(modeController).getPropertySetDefault(model,
        MapStyle.FIT_TO_VIEWPORT);
    fitToViewport = Boolean.parseBoolean(fitToViewportAsString);
    loadBackgroundImage();
    connectorChangeListener = new INodeChangeListener() {
      public void nodeChanged(NodeChangeEvent event) {
        if(NodeLinks.CONNECTOR.equals(event.getProperty()) &&
            event.getNode().getMap().equals(getModel()))
          repaint();
      }
    };
  }

  public void replaceSelection(NodeView[] views) {
        selection.replace(views);
        if(views.length > 0)
          views[0].requestFocusInWindow();
    }

    // generics trickery
  private Set<AWTKeyStroke> emptyNodeViewSet() {
      return Collections.emptySet();
    }

  private void anchorToSelected(final NodeModel node, final float horizontalPoint, final float verticalPoint) {
    final NodeView view = getNodeView(node);
    anchorToSelected(view, horizontalPoint, verticalPoint);
  }

  void anchorToSelected(final NodeView view, final float horizontalPoint, final float verticalPoint) {
    if (view != null && view.getMainView() != null) {
      anchor = view;
      anchorHorizontalPoint = horizontalPoint;
      anchorVerticalPoint = verticalPoint;
      this.anchorContentLocation = getAnchorCenterPoint();
      if (nodeToBeVisible == null) {
        nodeToBeVisible = anchor;
        extraWidth = 0;
      }
    }
  }

  /*
   * (non-Javadoc)
   * @see java.awt.dnd.Autoscroll#autoscroll(java.awt.Point)
   */
  public void autoscroll(final Point cursorLocn) {
    final Rectangle r = new Rectangle((int) cursorLocn.getX() - MapView.margin, (int) cursorLocn.getY()
            - MapView.margin, 1 + 2 * MapView.margin, 1 + 2 * MapView.margin);
    scrollRectToVisible(r);
  }

  public void centerNode(final NodeView node, boolean slowScroll) {
    if (node != null) {
      this.slowScroll = slowScroll;
      nodeToBeVisible = null;
      nodeToBeCentered = node;
      if (isDisplayable())
        centerNodeNow(slowScroll);
    }
  }

  private void centerNodeNow(boolean slowScroll) {
      final JViewport viewPort = (JViewport) getParent();
    if(slowScroll)
      viewPort.putClientProperty(ViewController.SLOW_SCROLLING, Boolean.TRUE);
    final Dimension d = viewPort.getExtentSize();
    final JComponent content = nodeToBeCentered.getContent();
    final Rectangle rect = new Rectangle(content.getWidth() / 2 - d.width / 2, content.getHeight() / 2 - d.height
            / 2, d.width, d.height);
    content.scrollRectToVisible(rect);
    nodeToBeCentered = null;
    this.slowScroll = false;
    this.anchorContentLocation = getAnchorCenterPoint();
    }
 
 

  @Override
    public void addNotify() {
      super.addNotify();
      modeController.getMapController().addNodeChangeListener(connectorChangeListener);
      getParent().addComponentListener(backgroundImageResizer);
    adjustViewportScrollMode();
    }

  private void adjustViewportScrollMode() {
      if(fitToViewport && backgroundComponent != null)
        ((JViewport) getParent()).setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
      else
        ((JViewport) getParent()).setScrollMode(JViewport.BLIT_SCROLL_MODE);
    }


  @Override
    public void removeNotify() {
    modeController.getMapController().removeNodeChangeListener(connectorChangeListener);
    getParent().removeComponentListener(backgroundImageResizer);
      super.removeNotify();
    }

  boolean isLayoutCompleted() {
      final JViewport viewPort = (JViewport) getParent();
    final Dimension visibleDimension = viewPort.getExtentSize();
    return visibleDimension.width > 0;
    }

  static private void createPropertyChangeListener() {
    MapView.propertyChangeListener = new IFreeplanePropertyListener() {
      public void propertyChanged(final String propertyName, final String newValue, final String oldValue) {
        final Component mapView = Controller.getCurrentController().getMapViewManager().getMapViewComponent();
        if (!(mapView instanceof MapView)) {
          return;
        }
        if (propertyName.equals(RESOURCES_SELECTED_NODE_COLOR)) {
          MapView.standardSelectColor = ColorUtils.stringToColor(newValue);
          ((MapView) mapView).repaintSelecteds();
          return;
        }
        if (propertyName.equals(RESOURCES_SELECTED_NODE_RECTANGLE_COLOR)) {
          MapView.standardSelectRectangleColor = ColorUtils.stringToColor(newValue);
          ((MapView) mapView).repaintSelecteds();
          return;
        }
        if (propertyName.equals(ResourceController.RESOURCE_DRAW_RECTANGLE_FOR_SELECTION)) {
          MapView.standardDrawRectangleForSelection = TreeXmlReader.xmlToBoolean(newValue);
          ((MapView) mapView).repaintSelecteds();
          return;
        }
        if (propertyName.equals("printonwhitebackground")) {
          MapView.printOnWhiteBackground = TreeXmlReader.xmlToBoolean(newValue);
          return;
        }
        if (propertyName.equals(PRESENTATION_DIMMER_TRANSPARENCY)) {
          MapView.transparency = 255 - ResourceController.getResourceController().getIntProperty(PRESENTATION_DIMMER_TRANSPARENCY, 0x70);
          ((MapView) mapView).repaint();
          return;
        }
        if (propertyName.equals(PRESENTATION_MODE_ENABLED)) {
          MapView.presentationModeEnabled = ResourceController.getResourceController().getBooleanProperty(PRESENTATION_MODE_ENABLED);
          ((MapView) mapView).repaint();
          return;
        }
      }
    };
    ResourceController.getResourceController().addPropertyChangeListener(MapView.propertyChangeListener);
  }

  public void deselect(final NodeView newSelected) {
    if (selection.contains(newSelected) && selection.deselect(newSelected)) {
      newSelected.repaintSelected();
    }
  }

  public Object detectCollision(final Point p) {
    if (arrowLinkViews == null) {
      return null;
    }
    for (int i = 0; i < arrowLinkViews.size(); ++i) {
      final ILinkView arrowView = arrowLinkViews.get(i);
      if (arrowView.detectCollision(p, true)) {
        return arrowView.getModel();
      }
    }
    for (int i = 0; i < arrowLinkViews.size(); ++i) {
      final ILinkView arrowView = arrowLinkViews.get(i);
      if (arrowView.detectCollision(p, false)) {
        return arrowView.getModel();
      }
    }
    return null;
  }

  /**
   * Call preparePrinting() before printing and endPrinting() after printing
   * to minimize calculation efforts
   */
  public void endPrinting() {
    if (!isPreparedForPrinting)
      return;
    isPreparedForPrinting = false;
    isPrinting = false;
    if (zoom == 1f) {
      getRoot().updateAll();
      synchronized (getTreeLock()) {
        validateTree();
      }
    }
    if (MapView.printOnWhiteBackground) {
      setBackground(background);
    }
  }

  private Point getAnchorCenterPoint() {
    if (! isDisplayable()) {
      return new Point();
    }
    final MainView mainView = anchor.getMainView();
    final int mainViewWidth = mainView.getWidth();
    final int mainViewHeight = mainView.getHeight();
    final Point anchorCenterPoint = new Point((int) (mainViewWidth * anchorHorizontalPoint), (int) (mainViewHeight * anchorVerticalPoint));
    final JViewport parent = (JViewport) getParent();
    UITools.convertPointToAncestor(mainView, anchorCenterPoint, this);
    anchorCenterPoint.x += - parent.getWidth() / 2;
    anchorCenterPoint.y += - parent.getHeight() / 2;
    return anchorCenterPoint;
  }

  /*
   * (non-Javadoc)
   * @see java.awt.dnd.Autoscroll#getAutoscrollInsets()
   */
  public Insets getAutoscrollInsets() {
    final Container parent = getParent();
    if (parent == null) {
      return new Insets(0, 0, 0, 0);
    }
    final Rectangle outer = getBounds();
    final Rectangle inner = parent.getBounds();
    return new Insets(inner.y - outer.y + MapView.margin, inner.x - outer.x + MapView.margin, outer.height
            - inner.height - inner.y + outer.y + MapView.margin, outer.width - inner.width - inner.x + outer.x
            + MapView.margin);
  }

  public Rectangle getInnerBounds() {
    final Rectangle innerBounds = rootView.getBounds();
    final Rectangle maxBounds = new Rectangle(0, 0, getWidth(), getHeight());
    for (int i = 0; i < arrowLinkViews.size(); ++i) {
      final ILinkView arrowView = arrowLinkViews.get(i);
      arrowView.increaseBounds(innerBounds);
    }
    return innerBounds.intersection(maxBounds);
  }

  public IMapSelection getMapSelection() {
    return new MapSelection();
  }

  public ModeController getModeController() {
    return modeController;
  }

  public MapModel getModel() {
    return model;
  }

  public Point getNodeContentLocation(final NodeView nodeView) {
    final Point contentXY = new Point(0, 0);
    UITools.convertPointToAncestor(nodeView.getContent(), contentXY, this);
    return contentXY;
  }

  private NodeView getNodeView(Object o) {
        if(! (o instanceof NodeModel))
      return null;
    final NodeView nodeView = getNodeView((NodeModel)o);
    return nodeView;
    }

  public NodeView getNodeView(final NodeModel node) {
    if (node == null) {
      return null;
    }
    for (INodeView iNodeView : node.getViewers()) {
      if(! (iNodeView instanceof NodeView)){
        continue;
      }
      final NodeView candidateView = (NodeView) iNodeView;
      if (candidateView.getMap() == this) {
        return candidateView;
      }
    }
    NodeView root = getRoot();
    if(root.getModel().equals(node))
      return root;
    else
      return null;
  }

  /*
   * (non-Javadoc)
   * @see javax.swing.JComponent#getPreferredSize()
   */
  @Override
  public Dimension getPreferredSize() {
    return getLayout().preferredLayoutSize(this);
  }

  public NodeView getRoot() {
    return rootView;
  }

  public NodeView getSelected() {
    if(! selectedsValid) {
      NodeView node = selection.selectedNode;
          if (node == null || ! SwingUtilities.isDescendingFrom(node, this))
            validateSelecteds();
            else {
              final JComponent content = node.getContent();
              if (content == null || ! content.isVisible())
                  validateSelecteds();
            }
        }
    return selection.selectedNode;
  }

  public Set<NodeModel> getSelectedNodes() {
    validateSelecteds();
    return new AbstractSet<NodeModel>() {

      @Override
      public int size() {
        return selection.size();
      }

      @Override
            public boolean contains(Object o) {
                final NodeView nodeView = getNodeView(o);
                if(nodeView == null)
                  return false;
                return selection.contains(nodeView);
            }

      @Override
            public boolean add(NodeModel o) {
        final NodeView nodeView = getNodeView(o);
        if(nodeView == null)
          return false;
        return selection.add(nodeView);
            }

      @Override
            public boolean remove(Object o) {
        final NodeView nodeView = getNodeView(o);
        if(nodeView == null)
          return false;
        return selection.deselect(nodeView);
            }

      @Override
            public Iterator<NodeModel> iterator() {
        return new Iterator<NodeModel>() {
          final Iterator<NodeView> i = selection.getSelectedSet().iterator();

          public boolean hasNext() {
                      return i.hasNext();
                    }

          public NodeModel next() {
                      return i.next().getModel();
                    }

          public void remove() {
                      i.remove();
                    }

        };
            }
    };
  }

  public List<NodeModel> getOrderedSelectedNodes() {
    validateSelecteds();
    return new AbstractList<NodeModel>(){

      @Override
            public boolean add(NodeModel o) {
        final NodeView nodeView = getNodeView(o);
        if(nodeView == null)
          return false;
        return selection.add(nodeView);
            }



      @Override
            public boolean contains(Object o) {
                final NodeView nodeView = getNodeView(o);
                if(nodeView == null)
                  return false;
                return selection.contains(nodeView);
            }



      @Override
            public boolean remove(Object o) {
        final NodeView nodeView = getNodeView(o);
        if(nodeView == null)
          return false;
        return selection.deselect(nodeView);
            }

      @Override
            public NodeModel get(int index) {
              return selection.getSelectedList().get(index).getModel();
            }

      @Override
            public int size() {
              return selection.size();
            }
    };
    }

  /**
   * @param differentSubtrees
   * @return an ArrayList of MindMapNode objects. If both ancestor and
   *         descandant node are selected, only the ancestor ist returned
   */
  ArrayList<NodeModel> getSelectedNodesSortedByY(final boolean differentSubtrees) {
    validateSelecteds();
    final TreeMap<Integer, LinkedList<NodeModel>> sortedNodes = new TreeMap<Integer, LinkedList<NodeModel>>();
    for (final NodeView view : selection.getSelectedSet()) {
      if (differentSubtrees) {
        if(viewBelongsToSelectedSubtreeOrItsClone(view))
          continue;
      }
      final Point point = new Point();
      UITools.convertPointToAncestor(view.getParent(), point, this);
      final NodeModel node = view.getModel();
      if(node.getParentNode() != null){
          point.y += node.getParentNode().getIndex(node);
      }
      LinkedList<NodeModel> nodeList = sortedNodes.get(point.y);
      if (nodeList == null) {
        nodeList = new LinkedList<NodeModel>();
        sortedNodes.put(point.y, nodeList);
      }
      nodeList.add(node);
    }
    final ArrayList<NodeModel> selectedNodes = new ArrayList<NodeModel>();
    for (final LinkedList<NodeModel> nodeList : sortedNodes.values()) {
      for (final NodeModel nodeModel : nodeList) {
        selectedNodes.add(nodeModel);
      }
    }
    return selectedNodes;
  }

  private boolean viewBelongsToSelectedSubtreeOrItsClone(final NodeView view) {
    HashSet<NodeModel> selectedNodesWithClones = new HashSet<NodeModel>();
    for (NodeView selectedView : selection.getSelectedList())
      for(NodeModel clone : selectedView.getModel().clones())
        selectedNodesWithClones.add(clone);
     
      for (Component parent = view.getParent(); parent instanceof NodeView; parent = parent.getParent()) {
        if (selectedNodesWithClones.contains(((NodeView)parent).getModel())) {
          return true;
        }
      }
      return false;
    }

  /**
   * @return
   */
  public Collection<NodeView> getSelection() {
    validateSelecteds();
    return selection.getSelection();
  }

  public int getSiblingMaxLevel() {
    return siblingMaxLevel;
  }

  /**
   * Returns the size of the visible part of the view in view coordinates.
   */
  public Dimension getViewportSize() {
    final JViewport mapViewport = (JViewport) getParent();
    return mapViewport == null ? null : mapViewport.getSize();
  }

  private NodeView getVisibleLeft(final NodeView oldSelected) {
    NodeView newSelected = oldSelected;
    final NodeModel oldModel = oldSelected.getModel();
    if (oldModel.isRoot()) {
      newSelected = oldSelected.getPreferredVisibleChild(layoutType.equals(MapViewLayout.OUTLINE), true);
    }
    else if (!oldSelected.isLeft()) {
      newSelected = oldSelected.getVisibleParentView();
    }
    else {
      if (getModeController().getMapController().isFolded(oldModel)) {
        getModeController().getMapController().setFolded(oldModel, false);
        return oldSelected;
      }
      newSelected = oldSelected.getPreferredVisibleChild(layoutType.equals(MapViewLayout.OUTLINE), true);
      while (newSelected != null && !newSelected.isContentVisible()) {
        newSelected = newSelected.getPreferredVisibleChild(layoutType.equals(MapViewLayout.OUTLINE), true);
      }
      if(newSelected == null)
        newSelected = getVisibleSummaryView(oldSelected);
    }
    return newSelected;
  }

  private NodeView getVisibleSummaryView(NodeView node) {
      if(node.isRoot())
        return null;
      final int currentSummaryLevel = SummaryNode.getSummaryLevel(node.getModel());
    int level = currentSummaryLevel;
    final int requiredSummaryLevel = level + 1;
      final NodeView parent = node.getParentView();
      for (int i = 1 + getIndex(node);i < parent.getComponentCount();i++){
        final Component component = parent.getComponent(i);
        if(! (component instanceof NodeView))
          break;
        NodeView next = (NodeView) component;
        if(next.isLeft() != node.isLeft())
          continue;
        if(next.isSummary())
          level++;
        else
          level = 0;
        if(level == requiredSummaryLevel){
          if(next.getModel().isVisible())
            return next;
          break;
        }
        if(level == currentSummaryLevel && SummaryNode.isFirstGroupNode(next.getModel()))
          break;
      }
      return getVisibleSummaryView(parent);
    }

  int getIndex(NodeView node) {
      final NodeView parent = node.getParentView();
      for(int i = 0; i < parent.getComponentCount(); i++){
        if(parent.getComponent(i).equals(node))
          return i;
      }
      return -1;
    }

  private NodeView getVisibleRight(final NodeView oldSelected) {
    NodeView newSelected = oldSelected;
    final NodeModel oldModel = oldSelected.getModel();
    if (oldModel.isRoot()) {
      newSelected = oldSelected.getPreferredVisibleChild(layoutType.equals(MapViewLayout.OUTLINE), false);
    }
    else if (oldSelected.isLeft()) {
      newSelected = oldSelected.getVisibleParentView();
    }
    else {
      if (getModeController().getMapController().isFolded(oldModel)) {
        getModeController().getMapController().setFolded(oldModel, false);
        return oldSelected;
      }
      newSelected = oldSelected.getPreferredVisibleChild(layoutType.equals(MapViewLayout.OUTLINE), false);
      while (newSelected != null && !newSelected.isContentVisible()) {
        newSelected = newSelected.getPreferredVisibleChild(layoutType.equals(MapViewLayout.OUTLINE), false);
      }
      if(newSelected == null)
        newSelected = getVisibleSummaryView(oldSelected);
    }
    return newSelected;
  }

  public float getZoom() {
    return zoom;
  }

  public int getZoomed(final int number) {
    return (int) Math.ceil(number * zoom);
  }

  private void initRoot() {
    this.anchorContentLocation = new Point();
    rootView = NodeViewFactory.getInstance().newNodeView(getModel().getRootNode(), this, this, 0);
    anchor = rootView;
  }
 
  public boolean isPrinting() {
    return isPrinting;
  }

  public boolean isSelected(final NodeView n) {
    if(isPrinting || (! selectedsValid &&
        (selection.selectedNode == null || ! SwingUtilities.isDescendingFrom(selection.selectedNode, this|| ! selection.selectedNode.getContent().isVisible())))
      return false;
    return selection.contains(n);
  }

  /**
   * Add the node to the selection if it is not yet there, making it the
   * focused selected node.
   */
  void addSelected(final NodeView newSelected, boolean scroll) {
    selection.add(newSelected);
    if(scroll)
      scrollNodeToVisible(newSelected);
  }

  public void mapChanged(final MapChangeEvent event) {
    final Object property = event.getProperty();
    if (property.equals(MapStyle.RESOURCES_BACKGROUND_COLOR)) {
      setBackground(requiredBackground());
      return;
    }
    if (property.equals(MapStyle.MAP_STYLES)){
          // set default font for notes:
          updateContentStyle();
    }
    if (property.equals(MapStyle.MAP_STYLES) && event.getMap().equals(model)
            || property.equals(ModelessAttributeController.ATTRIBUTE_VIEW_TYPE)
            || property.equals(Filter.class)
            || property.equals(UrlManager.MAP_URL)) {
      setBackground(requiredBackground());
      getRoot().updateAll();
      return;
    }
    if(property.equals(AttributeController.SHOW_ICON_FOR_ATTRIBUTES)
        ||property.equals(NoteController.SHOW_NOTE_ICONS))
      updateStateIconsRecursively(getRoot());
    if(property.equals(NoteController.SHOW_NOTES_IN_MAP))
      setShowNotes();
    if (property.equals(MapStyle.RESOURCES_BACKGROUND_IMAGE)) {
      final String fitToViewportAsString = MapStyle.getController(modeController).getPropertySetDefault(model,
          MapStyle.FIT_TO_VIEWPORT);
      fitToViewport = Boolean.parseBoolean(fitToViewportAsString);
      loadBackgroundImage();
      adjustViewportScrollMode();
    }
    if (property.equals(MapStyle.FIT_TO_VIEWPORT)) {
      final String fitToViewportAsString = MapStyle.getController(modeController).getPropertySetDefault(model,
          MapStyle.FIT_TO_VIEWPORT);
      fitToViewport = Boolean.parseBoolean(fitToViewportAsString);
      adjustViewportScrollMode();
      adjustBackgroundComponentScale();
      repaint();
    }
  }

  private void loadBackgroundImage() {
    final MapStyle mapStyle = getModeController().getExtension(MapStyle.class);
    final String uriString = mapStyle.getProperty(model, MapStyle.RESOURCES_BACKGROUND_IMAGE);
    backgroundComponent = null;
    if (uriString != null) {
      URI uri = assignAbsoluteURI(uriString);
      final ViewerController vc = getModeController().getExtension(ViewerController.class);
      final IViewerFactory factory = vc.getCombiFactory();
      if (uri != null) {
        assignViewerToBackgroundComponent(factory, uri);
      }
    }
    repaint();
  }

  private URI assignAbsoluteURI(final String uriString) {
    final UrlManager urlManager = getModeController().getExtension(UrlManager.class);
    URI uri = null;
    try {
      uri = urlManager.getAbsoluteUri(model, new URI(uriString));
    }
    catch (final URISyntaxException e) {
      LogUtils.severe(e);
    }
        catch (MalformedURLException e) {
      LogUtils.severe(e);
        }
    return uri;
  }

  private void assignViewerToBackgroundComponent(final IViewerFactory factory, final URI uri) {
    try {
      if (fitToViewport) {
        final JViewport vp = (JViewport) getParent();
        if(vp != null){
          final Dimension viewPortSize = vp.getVisibleRect().getSize();
          backgroundComponent = (JComponent) factory.createViewer(uri, viewPortSize);
        }
        else
          backgroundComponent = (JComponent) factory.createViewer(uri, new Dimension(1,1));
      }
            else
              backgroundComponent = (JComponent) factory.createViewer(uri, zoom);
      ((ScalableComponent) backgroundComponent).setCenter(true);
      ((ScalableComponent)backgroundComponent).setImageLoadingListener(new ImageLoadingListener() {
        public void imageLoaded() {
          repaint();
        }
      });
    }
    catch (final MalformedURLException e1) {
      LogUtils.severe(e1);
    }
    catch (final IOException e1) {
      LogUtils.severe(e1);
    }
  }

  private void updateStateIconsRecursively(NodeView node) {
      final MainView mainView = node.getMainView();
      if(mainView == null)
        return;
    mainView.updateIcons(node);
      for(int i = 0; i < node.getComponentCount(); i++){
        final Component component = node.getComponent(i);
        if(component instanceof NodeView)
        updateStateIconsRecursively((NodeView) component);
      }
    }

  private void updateContentStyle() {
        final NodeStyleController style = Controller.getCurrentModeController().getExtension(NodeStyleController.class);
        MapModel map = getModel();
        noteFont = UITools.scale(style.getDefaultFont(map, MapStyleModel.NOTE_STYLE));
        final MapStyleModel model = MapStyleModel.getExtension(map);
        final NodeModel detailStyleNode = model.getStyleNodeSafe(MapStyleModel.DETAILS_STYLE);
        detailFont = UITools.scale(style.getFont(detailStyleNode));
        detailBackground = style.getBackgroundColor(detailStyleNode);
        detailForeground = style.getColor(detailStyleNode);
    }

  public boolean selectLeft(boolean continious) {
    NodeView selected = getSelected();
    NodeView newSelected = getVisibleLeft(selected);
    return selectRightOrLeft(newSelected, continious);
    }

  private boolean selectRightOrLeft(NodeView newSelected, boolean continious) {
      if (newSelected == null) {
        return false;
    }
    if(continious){
      if(newSelected.isParentOf(getSelected())){
        selectAsTheOnlyOneSelected(newSelected);
        addBranchToSelection(newSelected);
      }
      else{
        addBranchToSelection(getSelected());
      }
    }
    else
      selectAsTheOnlyOneSelected(newSelected);
    return true;
    }

  public boolean selectRight(boolean continious) {
    NodeView selected = getSelected();
    NodeView newSelected = getVisibleRight(selected);
    return selectRightOrLeft(newSelected, continious);
    }


  public boolean selectUp(boolean continious) {
    return selectSibling(continious, false, false);
  }

  private boolean selectSibling(final boolean continious, final boolean page, final boolean down) {
    final NodeView oldSelectionEnd = selection.getSelectionEnd();
    if(oldSelectionEnd == null)
      return false;
    NodeView nextSelected = oldSelectionEnd;
    {
      final NodeView nextVisibleSibling = getNextVisibleSibling(nextSelected, down);
      if (nextSelected == nextVisibleSibling)
        return false;
      nextSelected = nextVisibleSibling;
    }
    if(page){
      NodeView sibling = nextSelected;
      for(;;)  {
        sibling = getNextVisibleSibling(sibling, down);
        final boolean noNextNodeFound = sibling == nextSelected;
        if(noNextNodeFound
            || sibling.getParentView() != nextSelected.getParentView()
            || sibling.isSelected() && sibling.getParentView() != oldSelectionEnd.getParentView()
            )
          break;
        nextSelected = sibling;
      }
      if(nextSelected.isSelected() && nextSelected.getParentView() == oldSelectionEnd.getParentView())
        nextSelected = getNextVisibleSibling(nextSelected, down);
    }
    if(continious){
      final NodeView selectionStart = selection.getSelectionStart();
      selectAsTheOnlyOneSelected(selectionStart);
      Boolean selectsDown = selectsDown(selectionStart, nextSelected);
      if(selectsDown != null){
        NodeView node = selectionStart;
        do{
          node = getNextVisibleSibling(node, selectsDown);
          addSelected(node, false);
        }while(node != nextSelected);
        selection.setSelectionEnd(nextSelected);
        scrollNodeToVisible(nextSelected);
      }
    }
    else
      selectAsTheOnlyOneSelected(nextSelected);
    return true;
    }

  private Boolean selectsDown(NodeView first, NodeView second) {
    if(first == second)
      return null;
    NodeView node = first;
    for(boolean down : new boolean[]{true, false}){
      for(;;){
        final NodeView nextVisibleSibling = getNextVisibleSibling(node, down);
        if(node == nextVisibleSibling)
          break;
        node = nextVisibleSibling;
        if(node == second)
          return down;
      };
    }
    return null;
    }

  public NodeView getNextVisibleSibling(NodeView node, boolean down) {
      return down ? node.getNextVisibleSibling() : node.getPreviousVisibleSibling();
    }

  public boolean selectDown(boolean continious) {
    return selectSibling(continious, false, true);
  }

  public boolean selectPageDown(boolean continious) {
    return selectSibling(continious, true, true);
    }

  public boolean selectPageUp(boolean continious) {
    return selectSibling(continious, true, false);
    }

  public void onNodeDeleted(final NodeModel parent, final NodeModel child, final int index) {
  }

  public void onNodeInserted(final NodeModel parent, final NodeModel child, final int newIndex) {
  }

  public void onNodeMoved(final NodeModel oldParent, final int oldIndex, final NodeModel newParent,
                          final NodeModel child, final int newIndex) {
  }

  public void onPreNodeDelete(final NodeModel oldParent, final NodeModel selectedNode, final int index) {
  }

  /*****************************************************************
   ** P A I N T I N G **
   *****************************************************************/
  /*
   * (non-Javadoc)
   * @see javax.swing.JComponent#paint(java.awt.Graphics)
   */
  @Override
  public void paint(final Graphics g) {
    if (!isPrinting && isPreparedForPrinting){
      isPreparedForPrinting = false;
      EventQueue.invokeLater(new Runnable() {
        public void run() {
          endPrinting();
          repaint();
        }
      });
      return;
    }

    final Graphics2D g2 = (Graphics2D) g.create();
    try {
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
      Controller.getCurrentController().getMapViewManager().setTextRenderingHint(g2);
      super.paint(g2);
    }
    finally {
      this.paintingMode = null;
      g2.dispose();
    }
  }

  @Override
  protected void paintComponent(final Graphics g) {
    super.paintComponent(g);
    if (backgroundComponent != null) {
      paintBackgroundComponent(g);
    }
  }

  private void paintBackgroundComponent(final Graphics g) {
      Graphics backgroundGraphics = g.create();
      try {
        setBackgroundComponentLocation(backgroundGraphics);
        backgroundComponent.paint(backgroundGraphics);
      }
      finally {
        backgroundGraphics.dispose();
      }
    }

  private void setBackgroundComponentLocation(Graphics g) {
    if (fitToViewport) {
      final JViewport vp = (JViewport) getParent();
      final Point viewPosition = vp.getViewPosition();
      g.translate(viewPosition.x, viewPosition.y);
    }
    else {
      final Point centerPoint = getRootCenterPoint();
      final Point backgroundImageTopLeft = getBackgroundImageTopLeft(centerPoint);
      g.translate(backgroundImageTopLeft.x, backgroundImageTopLeft.y);
    }
  }

  private Point getRootCenterPoint() {
    final Point centerPoint = new Point(getRoot().getMainView().getWidth() / 2,
        getRoot().getMainView().getHeight() / 2);
    UITools.convertPointToAncestor(getRoot().getMainView(), centerPoint, this);
    return centerPoint;
  }

  private Point getBackgroundImageTopLeft(Point centerPoint) {
    int x = centerPoint.x - (backgroundComponent.getWidth() / 2);
    int y = centerPoint.y - (backgroundComponent.getHeight() / 2);
    return new Point(x, y);
  }

  @Override
  protected void paintChildren(final Graphics g) {
      final boolean paintLinksBehind = ResourceController.getResourceController().getBooleanProperty(
            "paint_connectors_behind");
      final PaintingMode paintModes[];
      if(paintLinksBehind)
        paintModes = new PaintingMode[]{
          PaintingMode.CLOUDS,
          PaintingMode.LINKS, PaintingMode.NODES, PaintingMode.SELECTED_NODES
          };
      else
        paintModes = new PaintingMode[]{
          PaintingMode.CLOUDS,
          PaintingMode.NODES, PaintingMode.SELECTED_NODES, PaintingMode.LINKS
          };
      Graphics2D g2 = (Graphics2D) g;
      paintChildren(g2, paintModes);
      if(presentationModeEnabled)
        paintDimmer(g2, paintModes);
    paintSelecteds(g2);
    highlightEditor(g2);
    }

  private void paintChildren(Graphics2D g2, final PaintingMode[] paintModes) {
      for(PaintingMode paintingMode : paintModes){
        this.paintingMode = paintingMode;
      switch(paintingMode){
          case LINKS:
            paintLinks(g2);
            break;
        default:
          super.paintChildren(g2);
      }
      }
    };


  private void paintDimmer(Graphics2D g2, PaintingMode[] paintModes) {
    final Color color = g2.getColor();
    try{
      Color dimmer = new Color(0, 0, 0, transparency);
      g2.setColor(dimmer);
      g2.fillRect(0, 0, getWidth(), getHeight());
    }
    finally{
      g2.setColor(color);
    }
    for (final NodeView selected : getSelection()) {
      highlightSelected(g2, selected, paintModes);
    }
    }

  private void highlightEditor(Graphics2D g2) {
      final Component editor = getComponent(0);
    if(editor instanceof NodeView)
        return;
      final java.awt.Shape oldClip = g2.getClip();
      try{
        g2.setClip(editor.getX(), editor.getY(), editor.getWidth(), editor.getHeight());
        super.paintChildren(g2);
      }
      finally{
        g2.setClip(oldClip);
      }

    }

  protected PaintingMode getPaintingMode() {
    return paintingMode;
  }

  private void paintLinks(final Collection<NodeLinkModel> links, final Graphics2D graphics,
                          final HashSet<ConnectorModel> alreadyPaintedLinks) {
    final Font font = graphics.getFont();
    try {
      final Iterator<NodeLinkModel> linkIterator = links.iterator();
      while (linkIterator.hasNext()) {
        final NodeLinkModel next = linkIterator.next();
        if (!(next instanceof ConnectorModel)) {
          continue;
        }
        final ConnectorModel ref = (ConnectorModel) next;
        if (alreadyPaintedLinks.add(ref)) {
          final NodeModel target = ref.getTarget();
          if (target == null) {
            continue;
          }
          final NodeModel source = ref.getSource();
          final NodeView sourceView = getNodeView(source);
          final NodeView targetView = getNodeView(target);
          final ILinkView arrowLink;
          if (sourceView != null && targetView != null
                  && (Shape.EDGE_LIKE.equals(ref.getShape()) || sourceView.getMap().getLayoutType() == MapViewLayout.OUTLINE)
                  && source.isVisible() && target.isVisible()) {
            arrowLink = new EdgeLinkView(ref, getModeController(), sourceView, targetView);
          }
          else {
            arrowLink = new ConnectorView(ref, sourceView, targetView, getBackground());
          }
          arrowLink.paint(graphics);
          arrowLinkViews.add(arrowLink);
        }
      }
    }
    finally {
      graphics.setFont(font);
    }
  }

  private void paintLinks(final Graphics2D graphics) {
    arrowLinkViews = new Vector<ILinkView>();
    final Object renderingHint = getModeController().getController().getMapViewManager().setEdgesRenderingHint(
        graphics);
    paintLinks(rootView, graphics, new HashSet<ConnectorModel>());
    graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, renderingHint);
  }

  private void paintLinks(final NodeView source, final Graphics2D graphics,
                          final HashSet<ConnectorModel> alreadyPaintedLinks) {
    final NodeModel node = source.getModel();
    final Collection<NodeLinkModel> outLinks = NodeLinks.getLinks(node);
    paintLinks(outLinks, graphics, alreadyPaintedLinks);
    final Collection<NodeLinkModel> inLinks = LinkController.getController(getModeController()).getLinksTo(node);
    paintLinks(inLinks, graphics, alreadyPaintedLinks);
    final int nodeViewCount = source.getComponentCount();
    for (int i = 0; i < nodeViewCount; i++) {
      final Component component = source.getComponent(i);
      if (!(component instanceof NodeView)) {
        continue;
      }
      final NodeView child = (NodeView) component;
      if (!isPrinting) {
        final Rectangle bounds = SwingUtilities.convertRectangle(source, child.getBounds(), this);
        final JViewport vp = (JViewport) getParent();
        final Rectangle viewRect = vp.getViewRect();
        viewRect.x -= viewRect.width;
        viewRect.y -= viewRect.height;
        viewRect.width *= 3;
        viewRect.height *= 3;
        if (!viewRect.intersects(bounds)) {
          continue;
        }
      }
      paintLinks(child, graphics, alreadyPaintedLinks);
    }
  }

  private void paintSelecteds(final Graphics2D g) {
    if (!MapView.standardDrawRectangleForSelection || isPrinting()) {
      return;
    }
    final Color c = g.getColor();
    final Stroke s = g.getStroke();
    g.setColor(MapView.standardSelectRectangleColor);
    final Stroke standardSelectionStroke = getStandardSelectionStroke();
    g.setStroke(standardSelectionStroke);
    final Object renderingHint = getModeController().getController().getMapViewManager().setEdgesRenderingHint(g);
    for (final NodeView selected : getSelection()) {
      paintSelectionRectangle(g, selected);
    }
    g.setColor(c);
    g.setStroke(s);
    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, renderingHint);
  }

  private RoundRectangle2D.Float getRoundRectangleAround(NodeView selected, int gap, int arcw) {
    final JComponent content = selected.getContent();
    final Point contentLocation = new Point();
    UITools.convertPointToAncestor(content, contentLocation, this);
    gap -= 1;
    final RoundRectangle2D.Float roundRectClip = new RoundRectangle2D.Float(
      contentLocation.x - gap, contentLocation.y - gap,
      content.getWidth() + 2 * gap, content.getHeight() + 2 * gap, arcw, arcw);
    return roundRectClip;
  }

  private void paintSelectionRectangle(final Graphics2D g, final NodeView selected) {
    if (selected.getMainView().isEdited()) {
      return;
    }
    final RoundRectangle2D.Float roundRectClip = getRoundRectangleAround(selected, 4, 15);
    g.draw(roundRectClip);
  }

  private void highlightSelected(Graphics2D g, NodeView selected, PaintingMode[] paintedModes) {
    final java.awt.Shape highlightClip;
    if (MapView.standardDrawRectangleForSelection)
      highlightClip = getRoundRectangleAround(selected, 4, 15);
    else
      highlightClip = getRoundRectangleAround(selected, 4, 2);
    final java.awt.Shape oldClip = g.getClip();
    final Rectangle oldClipBounds = g.getClipBounds();
    try{
      g.setClip(highlightClip);
      if(oldClipBounds != null)
        g.clipRect(oldClipBounds.x, oldClipBounds.y, oldClipBounds.width, oldClipBounds.height);
      final Rectangle clipBounds = highlightClip.getBounds();
      final Color color = g.getColor();
      g.setColor(getBackground());
      g.fillRect(clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height);
      g.setColor(color);
      paintChildren(g, paintedModes);
    }
    finally{
      g.setClip(oldClip);
    }
    }

  Stroke getStandardSelectionStroke() {
      if (MapView.standardSelectionStroke == null) {
      MapView.standardSelectionStroke = new BasicStroke(2.0f);
    }
    final Stroke standardSelectionStroke = MapView.standardSelectionStroke;
      return standardSelectionStroke;
    }

  /**
   * Call preparePrinting() before printing and endPrinting() after printing
   * to minimize calculation efforts
   */
  public void preparePrinting() {
    isPrinting = true;
    if (!isPreparedForPrinting) {
      if (zoom == 1f) {
        getRoot().updateAll();
        synchronized (getTreeLock()) {
          validateTree();
        }
      }
      if (MapView.printOnWhiteBackground) {
        background = getBackground();
        setBackground(Color.WHITE);
      }
      fitMap = FitMap.valueOf();
      if (backgroundComponent != null && fitMap == FitMap.BACKGROUND) {
        boundingRectangle = getBackgroundImageInnerBounds();
      }
      else {
        boundingRectangle = getInnerBounds();
      }
      isPreparedForPrinting = true;
    }
  }

  private Rectangle getBackgroundImageInnerBounds() {
    final Point centerPoint = getRootCenterPoint();
    final Point backgroundImageTopLeft = getBackgroundImageTopLeft(centerPoint);
    return new Rectangle(backgroundImageTopLeft.x, backgroundImageTopLeft.y, backgroundComponent.getWidth(), backgroundComponent.getHeight());
  }

  @Override
  public void print(final Graphics g) {
    try {
      preparePrinting();
      super.print(g);
    }
    finally {
      isPrinting = false;
    }
  }

  public void render(Graphics g1, final Rectangle source, final Rectangle target) {
    Graphics2D g = (Graphics2D) g1;
    AffineTransform old = g.getTransform();
    final double scaleX = (0.0 + target.width) / source.width;
    final double scaleY = (0.0 + target.height) / source.height;
    final double zoom;
    if(scaleX < scaleY){
      zoom = scaleX;
    }
    else{
      zoom = scaleY;
    }
    AffineTransform tr2 = new AffineTransform(old);
    tr2.translate(target.getWidth() / 2, target.getHeight() / 2);
    tr2.scale(zoom, zoom);
    tr2.translate(-source.getX()- (source.getWidth() ) / 2, -source.getY()- (source.getHeight()) / 2);
    g.setTransform(tr2);
    final Rectangle clipBounds = g1.getClipBounds();
    g1.clipRect(source.x, source.y, source.width, source.height);
    print(g1);
    g.setTransform(old);
    g1.setClip(clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height);
  }

  public int print(final Graphics graphics, final PageFormat pageFormat, final int pageIndex) {
    double userZoomFactor = ResourceController.getResourceController().getDoubleProperty("user_zoom", 1);
    userZoomFactor = Math.max(0, userZoomFactor);
    userZoomFactor = Math.min(2, userZoomFactor);
    if ((fitMap == FitMap.PAGE || fitMap == FitMap.BACKGROUND) && pageIndex > 0) {
      return Printable.NO_SUCH_PAGE;
    }
    final Graphics2D g2 = (Graphics2D) graphics.create();
    preparePrinting();
    final double zoomFactor;
    final double imageableX = pageFormat.getImageableX();
    final double imageableY = pageFormat.getImageableY();
    final double imageableWidth = pageFormat.getImageableWidth();
    final double imageableHeight = pageFormat.getImageableHeight();
    g2.clipRect((int)imageableX, (int)imageableY, (int)imageableWidth, (int)imageableHeight);
    final double mapWidth = boundingRectangle.getWidth();
    final double mapHeight = boundingRectangle.getHeight();
    if (fitMap == FitMap.PAGE || fitMap == FitMap.BACKGROUND) {
      final double zoomFactorX = imageableWidth / mapWidth;
      final double zoomFactorY = imageableHeight / mapHeight;
      zoomFactor = Math.min(zoomFactorX, zoomFactorY) * 0.99;
    }
    else {
      if (fitMap == FitMap.WIDTH) {
        zoomFactor = imageableWidth / mapWidth * 0.99;
      }
      else if (fitMap == FitMap.HEIGHT) {
        zoomFactor = imageableHeight / mapHeight * 0.99;
      }
      else {
        zoomFactor = userZoomFactor / UITools.FONT_SCALE_FACTOR;
      }
      final int nrPagesInWidth = (int) Math.ceil(zoomFactor * mapWidth
        / imageableWidth);
      final int nrPagesInHeight = (int) Math.ceil(zoomFactor * mapHeight
        / imageableHeight);
      if (pageIndex >= nrPagesInWidth * nrPagesInHeight) {
        return Printable.NO_SUCH_PAGE;
      }
      final int yPageCoord = (int) Math.floor(pageIndex / nrPagesInWidth);
      final int xPageCoord = pageIndex - yPageCoord * nrPagesInWidth;
      g2.translate(-imageableWidth * xPageCoord, -imageableHeight * yPageCoord);
    }
    g2.translate(imageableX, imageableY);
    g2.scale(zoomFactor, zoomFactor);
    final double mapX = boundingRectangle.getX();
    final double mapY = boundingRectangle.getY();
    g2.translate(-mapX, -mapY);
    print(g2);
    g2.dispose();
    return Printable.PAGE_EXISTS;
  }

  private void repaintSelecteds() {
    for (final NodeView selected : getSelection()) {
      selected.repaintSelected();
    }
  }

  private Color requiredBackground() {
    final MapStyle mapStyle = getModeController().getExtension(MapStyle.class);
    final Color mapBackground = mapStyle.getBackground(model);
    return mapBackground;
  }

  void revalidateSelecteds() {
    selectedsValid = false;
  }

  /**
   * Scroll the viewport of the map to the south-west, i.e. scroll the map
   * itself to the north-east.
   */
  public void scrollBy(final int x, final int y) {
    final JViewport mapViewport = (JViewport) getParent();
    if (mapViewport != null) {
      final Point currentPoint = mapViewport.getViewPosition();
      currentPoint.translate(x, y);
      if (currentPoint.getX() < 0) {
        currentPoint.setLocation(0, currentPoint.getY());
      }
      if (currentPoint.getY() < 0) {
        currentPoint.setLocation(currentPoint.getX(), 0);
      }
      final double maxX = getSize().getWidth() - mapViewport.getExtentSize().getWidth();
      final double maxY = getSize().getHeight() - mapViewport.getExtentSize().getHeight();
      if (currentPoint.getX() > maxX) {
        currentPoint.setLocation(maxX, currentPoint.getY());
      }
      if (currentPoint.getY() > maxY) {
        currentPoint.setLocation(currentPoint.getX(), maxY);
      }
      mapViewport.setViewPosition(currentPoint);
    }
  }

  public void scrollNodeToVisible(final NodeView node) {
    scrollNodeToVisible(node, 0);
  }

  public void scrollNodeToVisible(final NodeView node, final int extraWidth) {
    if (nodeToBeCentered != null) {
      if (node != nodeToBeCentered) {
        centerNode(node, false);
      }
      return;
    }
    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;
    final JComponent nodeContent = node.getContent();
    int width = nodeContent.getWidth();
    if (extraWidth < 0) {
      width -= extraWidth;
      nodeContent.scrollRectToVisible(new Rectangle(-HORIZ_SPACE + extraWidth, -VERT_SPACE, width + HORIZ_SPACE2,
          nodeContent.getHeight() + VERT_SPACE2));
    }
    else {
      width += extraWidth;
      nodeContent.scrollRectToVisible(new Rectangle(-HORIZ_SPACE, -VERT_SPACE, width + HORIZ_SPACE2, nodeContent
          .getHeight()
              + VERT_SPACE2));
    }
  }

  /**
   * Select the node, resulting in only that one being selected.
   */
  public void selectAsTheOnlyOneSelected(final NodeView newSelected) {
    if(! newSelected.getModel().isVisible())
      throw new AssertionError("select invisible node");
    if (ResourceController.getResourceController().getBooleanProperty("center_selected_node")) {
      centerNode(newSelected, ResourceController.getResourceController().getBooleanProperty("slow_scroll_selected_node"));
    }
    else {
      scrollNodeToVisible(newSelected);
    }
    selectAsTheOnlyOneSelected(newSelected, true);
    setSiblingMaxLevel(newSelected.getModel().getNodeLevel(false));
  }

  public void selectAsTheOnlyOneSelected(final NodeView newSelected, final boolean requestFocus) {
    if (requestFocus) {
      newSelected.requestFocusInWindow();
    }
    scrollNodeToVisible(newSelected);
    if(selection.size() == 1 && getSelected().equals(newSelected)){
      return;
    }
    final NodeView[] oldSelecteds = selection.toArray();
    selection.select(newSelected);
    if (newSelected.getModel().getParentNode() != null) {
      ((NodeView) newSelected.getParent()).setPreferredChild(newSelected);
    }
    newSelected.repaintSelected();
    for (final NodeView oldSelected : oldSelecteds) {
      if (oldSelected != null) {
        oldSelected.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
   */
  private void addBranchToSelection(final NodeView newlySelectedNodeView) {
    if (newlySelectedNodeView.isContentVisible()) {
      addSelected(newlySelectedNodeView, false);
    }
    for (final NodeView target : newlySelectedNodeView.getChildrenViews()) {
      addBranchToSelection(target);
    }
  }

  void selectContinuous(final NodeView newSelected) {
    final NodeView selectionStart = selection.getSelectionStart();
    final NodeView selectionEnd = selection.getSelectionEnd();
    final NodeView parentView = newSelected.getParentView();
    final boolean left = newSelected.isLeft();
    if(newSelected.isRoot()
        || selectionStart == null || selectionEnd == null
        || parentView != selectionStart.getParentView() || parentView != selectionEnd.getParentView()
        || left != selectionStart.isLeft() || newSelected.isLeft() != selectionEnd.isLeft()){
      selection.setSelectionStart(newSelected);
      if(!newSelected.isSelected())
        selection.add(newSelected);
      scrollNodeToVisible(newSelected);
      return;
    }

    boolean selectionFound = false;
    boolean selectionRequired = false;
    for (NodeView child : parentView.getChildrenViews()){
      if(child.isLeft() == left){
        final boolean onOldSelectionMargin = child == selectionStart || child == selectionEnd;
        boolean selectionFoundNow = ! selectionFound && onOldSelectionMargin;
        selectionFound = selectionFound || selectionFoundNow;
       
        final boolean onNewSelectionMargin = child == selectionStart || child == newSelected;
        boolean selectionRequiredNow = ! selectionRequired && onNewSelectionMargin;
        selectionRequired = selectionRequired || selectionRequiredNow;
       
        if(selectionRequired && ! selectionFound)
          selection.add(child);
        else if(! selectionRequired && selectionFound)
          selection.deselect(child);
       
        if(selectionFound && (selectionStart == selectionEnd || ! selectionFoundNow && onOldSelectionMargin))
          selectionFound = false;
        if(selectionRequired && (selectionStart == newSelected ||  ! selectionRequiredNow && onNewSelectionMargin))
          selectionRequired = false;
      }
    }
    selection.setSelectionEnd(newSelected);
    scrollNodeToVisible(newSelected);
   
  }

  public void setMoveCursor(final boolean isHand) {
    final int requiredCursor = (isHand && !disableMoveCursor) ? Cursor.MOVE_CURSOR : Cursor.DEFAULT_CURSOR;
    if (getCursor().getType() != requiredCursor) {
      setCursor(requiredCursor != Cursor.DEFAULT_CURSOR ? new Cursor(requiredCursor) : null);
    }
  }

  void setSiblingMaxLevel(final int level) {
    siblingMaxLevel = level;
  }

  private void scrollView() {
    if(nodeToBeCentered != null){
      centerNode(nodeToBeCentered, slowScroll);
      return;
    }
    if (anchorContentLocation.getX() == 0 && anchorContentLocation.getY() == 0) {
      return;
    }
    final JViewport vp = (JViewport) getParent();
    final Point viewPosition = vp.getViewPosition();
    final Point oldAnchorContentLocation = anchorContentLocation;
    final Point newAnchorContentLocation = getAnchorCenterPoint();
    final int deltaX = newAnchorContentLocation.x - oldAnchorContentLocation.x;
    final int deltaY = newAnchorContentLocation.y - oldAnchorContentLocation.y;
    if (deltaX != 0 || deltaY != 0) {
      viewPosition.x += deltaX;
      viewPosition.y += deltaY;
      final int scrollMode = vp.getScrollMode();
      vp.setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
      vp.setViewPosition(viewPosition);
      vp.setScrollMode(scrollMode);
    }
    else {
      repaintVisible();
    }
    if (nodeToBeVisible != null) {
      final int scrollMode = vp.getScrollMode();
      vp.setScrollMode(JViewport.SIMPLE_SCROLL_MODE);
      scrollNodeToVisible(nodeToBeVisible, extraWidth);
      vp.setScrollMode(scrollMode);
      nodeToBeVisible = null;
    }
    anchor = getRoot();
    anchorHorizontalPoint = anchorVerticalPoint = 0;
    this.anchorContentLocation = getAnchorCenterPoint();
  }

  public void setZoom(final float zoom) {
    this.zoom = zoom;
    anchorToSelected(getSelected(), CENTER_ALIGNMENT, CENTER_ALIGNMENT);
    getRoot().updateAll();
    adjustBackgroundComponentScale();
  }

  private void adjustBackgroundComponentScale() {
    if (backgroundComponent != null) {
      if (fitToViewport) {
        final JViewport vp = (JViewport) getParent();
        final Dimension viewPortSize = vp.getVisibleRect().getSize();
        ((ScalableComponent) backgroundComponent).setFinalViewerSize(viewPortSize);
      }
      else {
        ((ScalableComponent) backgroundComponent).setMaximumComponentSize(getPreferredSize());
        ((ScalableComponent) backgroundComponent).setFinalViewerSize(zoom);
      }
    }
  }

  /**
   * Add the node to the selection if it is not yet there, remove it
   * otherwise.
   * @param requestFocus
   */
  private void toggleSelected(final NodeView nodeView) {
    if (isSelected(nodeView)) {
      if(selection.size() > 1)
        selection.deselect(nodeView);
    }
    else {
      selection.setSelectionStart(nodeView);
      selection.add(nodeView);
      scrollNodeToVisible(nodeView);
    }
  }

  private void validateSelecteds() {
    if (selectedsValid) {
      return;
    }
    selectedsValid = true;
    final NodeView selectedView = getSelected();
    if(selectedView == null){
      final NodeView root = getRoot();
      selectAsTheOnlyOneSelected(root);
      centerNode(root, false);
      return;
    }
    final NodeModel selectedNode = selectedView.getModel();
    final ArrayList<NodeView> selectedNodes = new ArrayList<NodeView>(getSelection().size());
    for (final NodeView nodeView : getSelection()) {
      if (nodeView != null) {
        selectedNodes.add(nodeView);
      }
    }
    selection.clear();
    for (final NodeView nodeView : selectedNodes) {
      if (nodeView.isContentVisible()) {
        selection.add(nodeView);
      }
    }
    if (getSelected() != null) {
      return;
        }
    for(NodeModel node = selectedNode.getParentNode(); node != null; node = node.getParentNode()){
      final NodeView newNodeView = getNodeView(node);
      if(newNodeView != null && newNodeView.isContentVisible() ){
        selectAsTheOnlyOneSelected(newNodeView);
        return;
      }
    }
    selectAsTheOnlyOneSelected(getRoot());
  }

  /*
   * (non-Javadoc)
   * @see java.awt.Container#validateTree()
   */
  @Override
  protected void validateTree() {
    validateSelecteds();
    getRoot().validateTree();
    super.validateTree();
  }

  public void onPreNodeMoved(final NodeModel oldParent, final int oldIndex, final NodeModel newParent,
                             final NodeModel child, final int newIndex) {
  }

  public void repaintVisible() {
    final JViewport vp = (JViewport) getParent();
    repaint(vp.getViewRect());
  }

  public void propertyChanged(String propertyName, String newValue, String oldValue) {
    if(propertyName.equals(TextController.MARK_TRANSFORMED_TEXT))
      UITools.repaintAll(getRoot());
  }

  public void selectVisibleAncestorOrSelf(NodeView preferred) {
    while(! preferred.getModel().isVisible())
      preferred = preferred.getParentView();
    selectAsTheOnlyOneSelected(preferred);
    }

    public Font getDefaultNoteFont() {
        return noteFont;
    }

    public Font getDetailFont() {
        return detailFont;
    }

    public Color getDetailForeground() {
        return detailForeground;
    }

    public Color getDetailBackground() {
        return detailBackground;
    }

  private boolean isSelected() {
      return Controller.getCurrentController().getMapViewManager().getMapViewComponent() == MapView.this;
    }

  void selectIfSelectionIsEmpty(NodeView nodeView) {
    if(selection.selectedNode == null)
      selectAsTheOnlyOneSelected(nodeView);
    }

  public static MapView getMapView(final Component component) {
      if(component instanceof MapView)
        return (MapView) component;
      return (MapView) SwingUtilities.getAncestorOfClass(MapView.class, component);
    }

  public void select() {
    getModeController().getController().getMapViewManager().changeToMapView(this);
    }

  @Override
    public void setSize(int width, int height) {
    super.setSize(width, height);
      validate();
    if(isDisplayable())
        scrollView();
    }

  @Override
    public void setLocation(int x, int y) {
      if(isValid()){
        super.setLocation(x, y);
        this.anchorContentLocation = getAnchorCenterPoint();
      }
    }
}
TOP

Related Classes of org.freeplane.view.swing.map.MapView$Selection

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.