Package org.freeplane.view.swing.map

Source Code of org.freeplane.view.swing.map.NodeView

/*
*  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.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.Window;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.util.LinkedList;
import java.util.ListIterator;

import javax.swing.JComponent;
import javax.swing.SwingUtilities;

import org.freeplane.core.resources.ResourceController;
import org.freeplane.core.ui.IUserInputListenerFactory;
import org.freeplane.core.ui.components.UITools;
import org.freeplane.features.attribute.AttributeController;
import org.freeplane.features.attribute.NodeAttributeTableModel;
import org.freeplane.features.cloud.CloudController;
import org.freeplane.features.cloud.CloudModel;
import org.freeplane.features.edge.EdgeController;
import org.freeplane.features.edge.EdgeStyle;
import org.freeplane.features.filter.FilterController;
import org.freeplane.features.icon.HierarchicalIcons;
import org.freeplane.features.map.FreeNode;
import org.freeplane.features.map.HideChildSubtree;
import org.freeplane.features.map.HistoryInformationModel;
import org.freeplane.features.map.INodeView;
import org.freeplane.features.map.MapChangeEvent;
import org.freeplane.features.map.NodeChangeEvent;
import org.freeplane.features.map.NodeModel;
import org.freeplane.features.map.NodeModel.NodeChangeType;
import org.freeplane.features.map.SummaryNode;
import org.freeplane.features.mode.Controller;
import org.freeplane.features.mode.ModeController;
import org.freeplane.features.nodelocation.LocationModel;
import org.freeplane.features.nodestyle.NodeStyleController;
import org.freeplane.features.nodestyle.NodeStyleModel;
import org.freeplane.features.styles.MapViewLayout;
import org.freeplane.features.text.ShortenedTextModel;
import org.freeplane.features.text.TextController;
import org.freeplane.view.swing.map.attribute.AttributeView;
import org.freeplane.view.swing.map.cloud.CloudView;
import org.freeplane.view.swing.map.cloud.CloudViewFactory;
import org.freeplane.view.swing.map.edge.EdgeView;
import org.freeplane.view.swing.map.edge.EdgeViewFactory;

/**
* This class represents a single Node of a MindMap (in analogy to
* TreeCellRenderer).
*/
public class NodeView extends JComponent implements INodeView {
  final static int ALIGN_BOTTOM = -1;
  final static int ALIGN_CENTER = 0;
  final static int ALIGN_TOP = 1;
  protected final static Color dragColor = Color.lightGray;
  public final static int DRAGGED_OVER_NO = 0;
  public final static int DRAGGED_OVER_SIBLING = 2;
  public final static int DRAGGED_OVER_SON = 1;
  /** For RootNodeView. */
  public final static int DRAGGED_OVER_SON_LEFT = 3;
  static private int FOLDING_SYMBOL_WIDTH = -1;
  private static final long serialVersionUID = 1L;
  public final static int SHIFT = -2;
  static final int SPACE_AROUND = 50;
  public static final int MAIN_VIEWER_POSITION = 1;
  public static final int NOTE_VIEWER_POSITION = 10;
  final static boolean PAINT_DEBUG_BORDER;
  static {
    boolean paintDebugBorder = false;
    try{
      paintDebugBorder = Boolean.getBoolean("org.freeplane.view.swing.map.NodeView.PAINT_DEBUG_BORDER");
    }
    catch(Exception e){
    }
    PAINT_DEBUG_BORDER = paintDebugBorder;
  }
  static private int maxToolTipWidth;
  private AttributeView attributeView;
  private JComponent contentPane;
  private MainView mainView;
  private final MapView map;
  private NodeModel model;
  private NodeView preferredChild;
  private EdgeStyle edgeStyle = EdgeStyle.EDGESTYLE_HIDDEN;
  private Integer edgeWidth = 1;
  private Color edgeColor = Color.BLACK;
  private Color modelBackgroundColor;

  private int topOverlap;
  private int bottomOverlap;

  public static final int DETAIL_VIEWER_POSITION = 2;

  protected NodeView(final NodeModel model, final MapView map, final Container parent) {
    setFocusCycleRoot(true);
    this.model = model;
    this.map = map;
  }

  void addDragListener(final DragGestureListener dgl) {
    if (dgl == null) {
      return;
    }
    final DragSource dragSource = DragSource.getDefaultDragSource();
    dragSource.createDefaultDragGestureRecognizer(getMainView(), DnDConstants.ACTION_COPY
            | DnDConstants.ACTION_MOVE | DnDConstants.ACTION_LINK, dgl);
  }

  void addDropListener(final DropTargetListener dtl) {
    if (dtl == null) {
      return;
    }
    final DropTarget dropTarget = new DropTarget(getMainView(), dtl);
    dropTarget.setActive(true);
  }

  private int calcShiftY(final LocationModel locationModel) {
    try {
      final NodeModel parent = model.getParentNode();
      return locationModel.getShiftY() + (getMap().getModeController().hasOneVisibleChild(parent) ? SHIFT : 0);
    }
    catch (final NullPointerException e) {
      return 0;
    }
  }

  public static int ADDITIONAL_MOUSE_SENSITIVE_AREA = 50;
 
  @Override
  public boolean contains(final int x, final int y) {
    final int space = getMap().getZoomed(NodeView.SPACE_AROUND);
    final int reducedSpace = space - ADDITIONAL_MOUSE_SENSITIVE_AREA;
    if (x >= reducedSpace && x < getWidth() - reducedSpace && y >= reducedSpace && y < getHeight() - reducedSpace){
      for(int i = getComponentCount()-1; i >= 0; i--){
        final Component comp = getComponent(i);
        if(comp.isVisible() && comp.contains(x-comp.getX(), y-comp.getY()))
          return true;
      }
    }
    return false;
  }

  protected void convertPointToMap(final Point p) {
    UITools.convertPointToAncestor(this, p, getMap());
  }

  public void createAttributeView() {
    if (attributeView == null && NodeAttributeTableModel.getModel(model).getNode() != null) {
      attributeView = new AttributeView(this, true);
    }
    syncronizeAttributeView();
  }

  public boolean focused() {
    return mainView.hasFocus();
  }

  public AttributeView getAttributeView() {
    if (attributeView == null) {
      AttributeController.getController(getMap().getModeController()).createAttributeTableModel(model);
      attributeView = new AttributeView(this, true);
    }
    return attributeView;
  }

  public Color getBackgroundColor() {
    final Color cloudColor = getCloudColor();
    if (cloudColor != null) {
      return cloudColor;
    }
    if (isRoot()) {
      return getMap().getBackground();
    }
    return getParentView().getBackgroundColor();
  }

  public Color getCloudColor() {
      final CloudModel cloudModel = getCloudModel();
    if(cloudModel != null){
      final Color cloudColor = cloudModel.getColor();
      return cloudColor;
    }
    return null;
    }

  /**
   * This method returns the NodeViews that are children of this node.
   */
  public LinkedList<NodeView> getChildrenViews() {
    final LinkedList<NodeView> childrenViews = new LinkedList<NodeView>();
    final Component[] components = getComponents();
    for (int i = 0; i < components.length; i++) {
      if (!(components[i] instanceof NodeView)) {
        continue;
      }
      final NodeView view = (NodeView) components[i];
      childrenViews.add(view);
    }
    return childrenViews;
  }

  public JComponent getContent() {
    final JComponent c = contentPane == null ? mainView : contentPane;
    assert (c == null || c.getParent() == this);
    return c;
  }

  private Container getContentPane() {
    if (contentPane == null) {
      Window windowAncestor = SwingUtilities.getWindowAncestor(mainView);
      boolean hasFocus = windowAncestor != null && windowAncestor.getMostRecentFocusOwner() == mainView;
      contentPane = NodeViewFactory.getInstance().newContentPane(this);
      final int index = getComponentCount() - 1;
      remove(index);
      contentPane.add(mainView);
      mainView.putClientProperty("NODE_VIEW_CONTENT_POSITION", MAIN_VIEWER_POSITION);
      if(! mainView.isVisible())
        mainView.setVisible(true);
      add(contentPane, index);
      if(hasFocus)
        restoreFocusToMainView();
    }
    return contentPane;
  }

  private void restoreFocusToMainView() {
    final Window windowAncestor = SwingUtilities.getWindowAncestor(mainView);
    if(windowAncestor.isFocused())
      mainView.requestFocusInWindow();
    else
      windowAncestor.addWindowFocusListener(new WindowFocusListener() {
        public void windowLostFocus(WindowEvent e) {
        }

        public void windowGainedFocus(WindowEvent e) {
          mainView.requestFocusInWindow();
          windowAncestor.removeWindowFocusListener(this);
        }
      });
  }

  /**
   * Returns the coordinates occupied by the node and its children as a vector
   * of four point per node.
   */
  public void getCoordinates(final LinkedList<Point> inList) {
    getCoordinates(inList, 0, false, 0, 0);
  }

  private void getCoordinates(final LinkedList<Point> inList, int additionalDistanceForConvexHull,
                              final boolean byChildren, final int transX, final int transY) {
    if (!isVisible()) {
      return;
    }
    if (isContentVisible()) {
      if (byChildren) {
        final ModeController modeController = getMap().getModeController();
        final CloudController cloudController = CloudController.getController(modeController);
        final CloudModel cloud = cloudController.getCloud(getModel());
        if (cloud != null) {
          additionalDistanceForConvexHull += CloudView.getAdditionalHeigth(cloud, this) / 5;
        }
      }
      final int x = transX + getContent().getX() - getDeltaX();
      final int y = transY + getContent().getY() - getDeltaY();
      final int width = mainView.getMainViewWidthWithFoldingMark();
      final int heightWithFoldingMark = mainView.getMainViewHeightWithFoldingMark();
      final int height = Math.max(heightWithFoldingMark, getContent().getHeight());
      inList.addLast(new Point(-additionalDistanceForConvexHull + x, -additionalDistanceForConvexHull + y));
      inList
          .addLast(new Point(-additionalDistanceForConvexHull + x, additionalDistanceForConvexHull + y + height));
      inList.addLast(new Point(additionalDistanceForConvexHull + x + width, additionalDistanceForConvexHull + y
              + height));
      inList
          .addLast(new Point(additionalDistanceForConvexHull + x + width, -additionalDistanceForConvexHull + y));
    }
    for (final NodeView child : getChildrenViews()) {
      child.getCoordinates(inList, additionalDistanceForConvexHull, true, transX + child.getX(),
          transY + child.getY());
    }
  }

  /** get x coordinate including folding symbol */
  public int getDeltaX() {
    return mainView.getDeltaX();
  }

  /** get y coordinate including folding symbol */
  public int getDeltaY() {
    return mainView.getDeltaY();
  }

  NodeView getFirst(Component startAfter, final boolean leftOnly, final boolean rightOnly) {
    final Component[] components = getComponents();
    for (int i = 0; i < components.length; i++) {
      if (startAfter != null) {
        if (components[i] == startAfter) {
          startAfter = null;
        }
        continue;
      }
      if (!(components[i] instanceof NodeView)) {
        continue;
      }
      final NodeView view = (NodeView) components[i];
      if (leftOnly && !view.isLeft() || rightOnly && view.isLeft()) {
        continue;
      }
      if (view.isContentVisible()) {
        return view;
      }
      final NodeView child = view.getFirst(null, leftOnly, rightOnly);
      if (child != null) {
        return child;
      }
    }
    return null;
  }

  public int getHGap() {
    return map.getZoomed(LocationModel.getModel(model).getHGap());
  }

  private NodeView getLast(Component startBefore, final boolean leftOnly, final boolean rightOnly) {
    final Component[] components = getComponents();
    for (int i = components.length - 1; i >= 0; i--) {
      if (startBefore != null) {
        if (components[i] == startBefore) {
          startBefore = null;
        }
        continue;
      }
      if (!(components[i] instanceof NodeView)) {
        continue;
      }
      final NodeView view = (NodeView) components[i];
      if (leftOnly && !view.isLeft() || rightOnly && view.isLeft()) {
        continue;
      }
      if (view.isContentVisible()) {
        return view;
      }
      final NodeView child = view.getLast(null, leftOnly, rightOnly);
      if (child != null) {
        return child;
      }
    }
    return null;
  }

  LinkedList<NodeView> getLeft(final boolean onlyVisible) {
    final LinkedList<NodeView> left = new LinkedList<NodeView>();
    for (final NodeView node : getChildrenViews()) {
      if (node == null) {
        continue;
      }
      if (node.isLeft()) {
        left.add(node);
      }
    }
    return left;
  }

  /**
   * Returns the Point where the Links should arrive the Node.
   */
  public Point getLinkPoint(final Point declination) {
    int x, y;
    Point linkPoint;
    if (declination != null) {
      x = getMap().getZoomed(declination.x);
      y = getMap().getZoomed(declination.y);
    }
    else {
      x = 1;
      y = 0;
    }
    if (isLeft()) {
      x = -x;
    }
    if (y != 0) {
      final double ctgRect = Math.abs((double) getContent().getWidth() / getContent().getHeight());
      final double ctgLine = Math.abs((double) x / y);
      int absLinkX, absLinkY;
      if (ctgRect > ctgLine) {
        absLinkX = Math.abs(x * getContent().getHeight() / (2 * y));
        absLinkY = getContent().getHeight() / 2;
      }
      else {
        absLinkX = getContent().getWidth() / 2;
        absLinkY = Math.abs(y * getContent().getWidth() / (2 * x));
      }
      linkPoint = new Point(getContent().getWidth() / 2 + (x > 0 ? absLinkX : -absLinkX), getContent()
          .getHeight() / 2 + (y > 0 ? absLinkY : -absLinkY));
    }
    else {
      linkPoint = new Point((x > 0 ? getContent().getWidth() : 0), (getContent().getHeight() / 2));
    }
    linkPoint.translate(getContent().getX(), getContent().getY());
    convertPointToMap(linkPoint);
    return linkPoint;
  }

  public MainView getMainView() {
    return mainView;
  }

    public Point getMainViewConnectorPoint(NodeView target) {
        final Point relativeLocation = getRelativeLocation(target);
        relativeLocation.x += target.getMainView().getWidth()/2;
        relativeLocation.y += target.getMainView().getHeight()/2;
        return mainView.getConnectorPoint(relativeLocation);
    }

    public Point getRelativeLocation(NodeView target) {
        Component component;
        int targetX = 0;
        int targetY = 0;
        for(component = target.getMainView();
            !(this.equals(component) || component.getClass().equals(MapView.class));
            component = component.getParent()){
            targetX += component.getX();
            targetY += component.getY();
        }
        Point relativeLocation = new Point();
        UITools.convertPointToAncestor(mainView, relativeLocation, component);
        relativeLocation.x = targetX - relativeLocation.x;
        relativeLocation.y = targetY - relativeLocation.y;
        return relativeLocation;
    }

  public MapView getMap() {
    return map;
  }

  public int getMaxToolTipWidth() {
    if (maxToolTipWidth == 0) {
      try {
        maxToolTipWidth = ResourceController.getResourceController().getIntProperty(
            "toolTipManager.max_tooltip_width", 600);
      }
      catch (final NumberFormatException e) {
        maxToolTipWidth = 600;
      }
    }
    return maxToolTipWidth;
  }

  public NodeModel getModel() {
    return model;
  }

  protected NodeView getNextSiblingSingle() {
    LinkedList<NodeView> v = null;
    if (getParentView().getModel().isRoot()) {
      if (this.isLeft()) {
        v = (getParentView()).getLeft(true);
      }
      else {
        v = (getParentView()).getRight(true);
      }
    }
    else {
      v = getParentView().getChildrenViews();
    }
    final int index = v.indexOf(this);
    for (int i = index + 1; i < v.size(); i++) {
      final NodeView nextView = v.get(i);
      if (nextView.isContentVisible()) {
        return nextView;
      }
      else {
        final NodeView first = nextView.getFirst(null, false, false);
        if (first != null) {
          return first;
        }
      }
    }
    return this;
  }

  protected NodeView getNextVisibleSibling() {
    NodeView sibling;
    NodeView lastSibling = this;
    for (sibling = this; !sibling.getModel().isRoot(); sibling = sibling.getParentView()) {
      lastSibling = sibling;
      sibling = sibling.getNextSiblingSingle();
      if (sibling != lastSibling) {
        break;
      }
    }
    while (sibling.getModel().getNodeLevel(false) < getMap().getSiblingMaxLevel()) {
      final NodeView first = sibling.getFirst(sibling.isRoot() ? lastSibling : null, this.isLeft(),
          !this.isLeft());
      if (first == null) {
        break;
      }
      sibling = first;
    }
    if (sibling.isRoot()) {
      return this;
    }
    return sibling;
  }

  public NodeView getParentView() {
    final Container parent = getParent();
    if (parent instanceof NodeView) {
      return (NodeView) parent;
    }
    return null;
  }

  public NodeView getPreferredVisibleChild(final boolean getUpper, final boolean left) {
    if (getModel().isLeaf()) {
      return null;
    }
    if (getUpper) {
      preferredChild = null;
    }
    if (preferredChild != null && (left == preferredChild.isLeft()) && preferredChild.getParent() == this) {
      if (preferredChild.isContentVisible()) {
        return preferredChild;
      }
      else {
        final NodeView newSelected = preferredChild.getPreferredVisibleChild(getUpper, left);
        if (newSelected != null) {
          return newSelected;
        }
      }
    }
    int yGap = Integer.MAX_VALUE;
    final NodeView baseComponent;
    if (isContentVisible()) {
      baseComponent = this;
    }
    else {
      baseComponent = getVisibleParentView();
    }
    final int ownX = baseComponent.getContent().getX() + baseComponent.getContent().getWidth() / 2;
    final int ownY = baseComponent.getContent().getY() + baseComponent.getContent().getHeight() / 2;
    NodeView newSelected = null;
    for (int i = 0; i < getComponentCount(); i++) {
      final Component c = getComponent(i);
      if (!(c instanceof NodeView)) {
        continue;
      }
      NodeView childView = (NodeView) c;
      if (!(childView.isLeft() == left)) {
        continue;
      }
      if (!childView.isContentVisible()) {
        childView = childView.getPreferredVisibleChild(getUpper, left);
        if (childView == null) {
          continue;
        }
      }
      if (getUpper) {
        return childView;
      }
      final JComponent childContent = childView.getContent();
      if(childContent == null)
        continue;
      final Point childPoint = new Point(left ? childContent.getWidth() : 0, childContent.getHeight() / 2);
      UITools.convertPointToAncestor(childContent, childPoint, baseComponent);
      final int dy = childPoint.y - ownY;
      final int dx = childPoint.x - ownX;
      final int gapToChild = dy*dy + dx*dx;
      if (gapToChild < yGap) {
        newSelected = childView;
        preferredChild = (NodeView) c;
        yGap = gapToChild;
      }
      else {
        break;
      }
    }
    return newSelected;
  }

  protected NodeView getPreviousSiblingSingle() {
    LinkedList<NodeView> v = null;
    if (getParentView().getModel().isRoot()) {
      if (this.isLeft()) {
        v = (getParentView()).getLeft(true);
      }
      else {
        v = (getParentView()).getRight(true);
      }
    }
    else {
      v = getParentView().getChildrenViews();
    }
    final int index = v.indexOf(this);
    for (int i = index - 1; i >= 0; i--) {
      final NodeView nextView = v.get(i);
      if (nextView.isContentVisible()) {
        return nextView;
      }
      else {
        final NodeView last = nextView.getLast(null, false, false);
        if (last != null) {
          return last;
        }
      }
    }
    return this;
  }

  protected NodeView getPreviousVisibleSibling() {
    NodeView sibling;
    NodeView previousSibling = this;
    for (sibling = this; !sibling.getModel().isRoot(); sibling = sibling.getParentView()) {
      previousSibling = sibling;
      sibling = sibling.getPreviousSiblingSingle();
      if (sibling != previousSibling) {
        break;
      }
    }
    while (sibling.getModel().getNodeLevel(false) < getMap().getSiblingMaxLevel()) {
      final NodeView last = sibling.getLast(sibling.isRoot() ? previousSibling : null, this.isLeft(),
          !this.isLeft());
      if (last == null) {
        break;
      }
      sibling = last;
    }
    if (sibling.isRoot()) {
      return this;
    }
    return sibling;
  }

  LinkedList<NodeView> getRight(final boolean onlyVisible) {
    final LinkedList<NodeView> right = new LinkedList<NodeView>();
    for (final NodeView node : getChildrenViews()) {
      if (node == null) {
        continue;
      }
      if (!node.isLeft()) {
        right.add(node);
      }
    }
    return right;
  }

  /**
   * @return returns the color that should used to select the node.
   */
  public Color getSelectedColor() {
    return MapView.standardSelectColor;
  }

  /**
     * @return Returns the sHIFT.s
     */
  public int getShift() {
    final LocationModel locationModel = LocationModel.getModel(model);
    return map.getZoomed(calcShiftY(locationModel));
  }

  protected LinkedList<NodeView> getSiblingViews() {
    return getParentView().getChildrenViews();
  }

  public Color getTextBackground() {
    if (modelBackgroundColor != null) {
      return modelBackgroundColor;
    }
    return getBackgroundColor();
  }

  public Color getTextColor() {
    final Color color = NodeStyleController.getController(getMap().getModeController()).getColor(model);
    return color;
  }

  /**
   * @return Returns the VGAP.
   */
  public int getVGap() {
    return map.getZoomed(LocationModel.getModel(model).getVGap());
  }

  public NodeView getVisibleParentView() {
    final Container parent = getParent();
    if (!(parent instanceof NodeView)) {
      return null;
    }
    final NodeView parentView = (NodeView) parent;
    if (parentView.isContentVisible()) {
      return parentView;
    }
    return parentView.getVisibleParentView();
  }

  public int getZoomedFoldingSymbolHalfWidth() {
    if (NodeView.FOLDING_SYMBOL_WIDTH == -1) {
      NodeView.FOLDING_SYMBOL_WIDTH = ResourceController.getResourceController().getIntProperty(
          "foldingsymbolwidth", 8);
    }
    final int preferredFoldingSymbolHalfWidth = (int) ((NodeView.FOLDING_SYMBOL_WIDTH * map.getZoom()) / 2);
    return preferredFoldingSymbolHalfWidth;
  }

  void addChildViews() {
    int index = 0;
    for (NodeModel child : getMap().getModeController().getMapController().childrenFolded(getModel())) {
      if(child.containsExtension(HideChildSubtree.class))
        return;
      if(getComponentCount() <= index
          || ! (getComponent(index) instanceof NodeView))
        addChildView(child, index++);
    }
  }

  /**
   * Create views for the newNode and all his descendants, set their isLeft
   * attribute according to this view.
   * @param index2
   */
  void addChildView(final NodeModel newNode, int index) {
      NodeViewFactory.getInstance().newNodeView(newNode, getMap(), this, index);
  }

  /* fc, 25.1.2004: Refactoring necessary: should call the model. */
  public boolean isChildOf(final NodeView myNodeView) {
    return getParentView() == myNodeView;
  }

  /**
   */
  public boolean isContentVisible() {
    return getModel().isVisible();
  }

  public boolean isLeft() {
    if (getMap().getLayoutType() == MapViewLayout.OUTLINE) {
      return false;
    }
    return getModel().isLeft();
  }

  public boolean isParentHidden() {
    final Container parent = getParent();
    if (!(parent instanceof NodeView)) {
      return false;
    }
    final NodeView parentView = (NodeView) parent;
    return !parentView.isContentVisible();
  }

  /* fc, 25.1.2004: Refactoring necessary: should call the model. */
  public boolean isParentOf(final NodeView myNodeView) {
    return (this == myNodeView.getParentView());
  }

  public boolean isRoot() {
    return getModel().isRoot();
  }

  public boolean isSelected() {
    return (getMap().isSelected(this));
  }

  /* fc, 25.1.2004: Refactoring necessary: should call the model. */
  public boolean isSiblingOf(final NodeView myNodeView) {
    return getParentView() == myNodeView.getParentView();
  }

  public void mapChanged(final MapChangeEvent event) {
  }

  public void nodeChanged(final NodeChangeEvent event) {
    final NodeModel node = event.getNode();
    // is node is deleted, skip the rest.
    if (!node.isRoot() && node.getParentNode() == null) {
      return;
    }
    final Object property = event.getProperty();
    if (property == NodeChangeType.FOLDING) {
      treeStructureChanged();
      getMap().selectIfSelectionIsEmpty(this);
      String shape = NodeStyleController.getController(getMap().getModeController()).getShape(model);
      if (shape.equals(NodeStyleModel.SHAPE_COMBINED))
        update();
      return;
    }
    // is node is not fully initialized, skip the rest.
    if (mainView == null) {
      return;
    }
    if (property.equals(NodeModel.NODE_ICON) || property.equals(HierarchicalIcons.ICONS)) {
      mainView.updateIcons(this);
      revalidate();
      return;
    }
    if (property.equals(NodeModel.NOTE_TEXT)) {
      NodeViewFactory.getInstance().updateNoteViewer(this);
      mainView.updateIcons(this);
      return;
    }
    if (property.equals(ShortenedTextModel.SHORTENER)) {
      NodeViewFactory.getInstance().updateNoteViewer(this);
    }

    if (property.equals(HistoryInformationModel.class)) {
      return;
    }
    update();
    if (!isRoot())
      getParentView().numberingChanged(node.getParentNode().getIndex(node) + 1);
  }

  public void onNodeDeleted(final NodeModel parent, final NodeModel child, final int index) {
    if (getMap().getModeController().getMapController().isFolded(model)) {
      return;
    }
    final boolean preferredChildIsLeft = preferredChild != null && preferredChild.isLeft();
    final NodeView node = (NodeView) getComponent(index);
    if (node == preferredChild) {
      preferredChild = null;
      for (int j = index + 1; j < getComponentCount(); j++) {
        final Component c = getComponent(j);
        if (!(c instanceof NodeView)) {
          break;
        }
        final NodeView candidate = (NodeView) c;
        if (candidate.isVisible() && node.isLeft() == candidate.isLeft()) {
          preferredChild = candidate;
          break;
        }
      }
      if (preferredChild == null) {
        for (int j = index - 1; j >= 0; j--) {
          final Component c = getComponent(j);
          if (!(c instanceof NodeView)) {
            break;
          }
          final NodeView candidate = (NodeView) c;
          if (candidate.isVisible() && node.isLeft() == candidate.isLeft()) {
            preferredChild = candidate;
            break;
          }
        }
      }
    }
    numberingChanged(index+1);
    node.remove();
    NodeView preferred = getPreferredVisibleChild(false, preferredChildIsLeft);
    if (preferred == null) {
      preferred = this;
    }
    if(getMap().getSelected() ==  null)
      getMap().selectVisibleAncestorOrSelf(preferred);
    revalidate();
  }

  public void onNodeInserted(final NodeModel parent, final NodeModel child, final int index) {
    assert parent == model;
    if (getMap().getModeController().getMapController().isFolded(model)) {
      return;
    }
    addChildView(child, index);
    numberingChanged(index + 1);
    revalidate();
  }

  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 child, final int oldIndex) {
  }

  // updates children, starting from firstChangedIndex, if necessary.
  private void numberingChanged(int firstChangedIndex) {
    final TextController textController = TextController.getController(getMap().getModeController());
    if (firstChangedIndex > 0 || textController.getNodeNumbering(getModel())) {
      final Component[] components = getComponents();
      for (int i = firstChangedIndex; i < components.length; i++) {
        if (components[i] instanceof NodeView) {
          final NodeView view = (NodeView) components[i];
          final MainView childMainView = view.getMainView();
          if(childMainView != null){
            childMainView.updateText(view.getModel());
            view.numberingChanged(0);
          }
        }
      }
    }
  }

  /*
   * (non-Javadoc)
   * @see javax.swing.JComponent#paint(java.awt.Graphics)
   */
  @Override
  public void paintComponent(final Graphics g) {
    if(getMainView() == null)
      return;
    final PaintingMode paintingMode = map.getPaintingMode();
    if (isContentVisible()) {
      final Graphics2D g2 = (Graphics2D) g;
      final ModeController modeController = map.getModeController();
      final Object renderingHint = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
      switch (paintingMode) {
        case CLOUDS:
          modeController.getController().getMapViewManager().setEdgesRenderingHint(g2);
          final boolean isRoot = isRoot();
          if (isRoot) {
            paintCloud(g);
          }
                    paintClouds(g2);
          g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, renderingHint);
          break;
        case NODES:
          g2.setStroke(BubbleMainView.DEF_STROKE);
          modeController.getController().getMapViewManager().setEdgesRenderingHint(g2);
                    paintEdges(g2, this);
          g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, renderingHint);
          break;
      }
    }
    if (PAINT_DEBUG_BORDER && isSelected() && paintingMode.equals(PaintingMode.SELECTED_NODES)){
      final int spaceAround = getZoomed(SPACE_AROUND);
      g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
      g.drawRect(spaceAround - 1, spaceAround - 1, getWidth() - 2 * spaceAround, getHeight() - 2 * spaceAround);
    }
  }

  @Override
    public void paint(Graphics g) {
      super.paint(g);
    paintDecoration((Graphics2D) g);
    }

  private void paintCloud(final Graphics g) {
    if (!isContentVisible()) {
      return;
    }
    final CloudModel cloudModel = getCloudModel();
    if (cloudModel == null) {
      return;
    }
    final CloudView cloud = new CloudViewFactory().createCloudView(cloudModel, this);
    cloud.paint(g);
  }

    private void paintClouds(final Graphics2D g) {
        for (int i = getComponentCount() - 1; i >= 0; i--) {
            final Component component = getComponent(i);
            if (!(component instanceof NodeView)) {
                continue;
            }
            final NodeView nodeView = (NodeView) component;
            final Point p = new Point();
            UITools.convertPointToAncestor(nodeView, p, this);
            g.translate(p.x, p.y);
            if (nodeView.isContentVisible()) {
                nodeView.paintCloud(g);
             }
            else {
                nodeView.paintClouds(g);
            }
            g.translate(-p.x, -p.y);
        }
    }

    private void paintEdges(final Graphics2D g, NodeView source) {
      SummaryEdgePainter summaryEdgePainter = new SummaryEdgePainter(this, isRoot() ? true : isLeft());
      SummaryEdgePainter rightSummaryEdgePainter =  isRoot() ? new SummaryEdgePainter(this, false) : null;
        final int start;
        final int end;
        final int step;
        if (getMap().getLayoutType() == MapViewLayout.OUTLINE){
          start = getComponentCount() - 1;
          end = -1;
          step = -1;
        }
        else{
          start = 0;
          end = getComponentCount();
          step = 1;
        }
    for (int i = start; i != end; i+=step) {
            final Component component = getComponent(i);
            if (!(component instanceof NodeView)) {
                continue;
            }
            final NodeView nodeView = (NodeView) component;
          if (getMap().getLayoutType() != MapViewLayout.OUTLINE) {
            SummaryEdgePainter activePainter = nodeView.isLeft() || !isRoot() ? summaryEdgePainter : rightSummaryEdgePainter;
            activePainter.addChild(nodeView);
            if(activePainter.paintSummaryEdge(g, source, nodeView)){
              if(! nodeView.isContentVisible()){
                final Rectangle bounds =  SwingUtilities.convertRectangle(this, nodeView.getBounds(), source);
                final Graphics cg = g.create(bounds.x, bounds.y, bounds.width, bounds.height);
                try{
                  nodeView.paintEdges((Graphics2D) cg, nodeView);
                }
                finally{
                  cg.dispose();
                }

              }
              continue;
            }
            }
          if (nodeView.isContentVisible()) {
            final EdgeView edge = EdgeViewFactory.getInstance().getEdge(source, nodeView, source);
            edge.paint(g);
          }
          else {
            nodeView.paintEdges(g, source);
          }
        }
    }


  int getSpaceAround() {
    return getZoomed(NodeView.SPACE_AROUND);
  }

  public int getZoomed(int x) {
    return getMap().getZoomed(x);
  }

  private void paintDecoration(final Graphics2D g) {
    final PaintingMode paintingMode = map.getPaintingMode();
    if(! (getMainView() != null &&
        ( paintingMode.equals(PaintingMode.NODES) && !isSelected() || paintingMode.equals(PaintingMode.SELECTED_NODES) && isSelected())
        && isContentVisible()))
      return;
    final Graphics2D g2 = g;
    final ModeController modeController = map.getModeController();
    final Object renderingHint = g2.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
    g2.setStroke(BubbleMainView.DEF_STROKE);
    modeController.getController().getMapViewManager().setEdgesRenderingHint(g2);
    final Point origin = new Point();
    UITools.convertPointToAncestor(mainView, origin, this);
    g.translate(origin.x, origin.y);
    mainView.paintDecoration(this, g);
    g.translate(-origin.x, -origin.y);
    final FilterController filterController = FilterController.getController(getMap().getModeController().getController());
    if(filterController.isNodeHighlighted(getModel())){
      final Color oldColor = g.getColor();
      final Stroke oldStroke = g.getStroke();
      g.setColor(Color.MAGENTA);
      g.setStroke(getMap().getStandardSelectionStroke());
      final JComponent content = getContent();
      Point contentLocation = content.getLocation();
      final int arcWidth = 8;
      g.drawRoundRect(contentLocation.x - arcWidth, contentLocation.y - arcWidth, content.getWidth() + 2 * arcWidth,
          content.getHeight() + 2 * arcWidth, 15, 15);
      g.setColor(oldColor);
      g.setStroke(oldStroke);
    }
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, renderingHint);
  }

  /**
   * This is a bit problematic, because getChildrenViews() only works if model
   * is not yet removed. (So do not _really_ delete the model before the view
   * removed (it needs to stay in memory)
   */
  void remove() {
    for (final ListIterator<NodeView> e = getChildrenViews().listIterator(); e.hasNext();) {
      e.next().remove();
    }
    getMap().deselect(this);
    getMap().getModeController().onViewRemoved(this);
    removeFromMap();
    if (attributeView != null) {
      attributeView.viewRemoved();
    }
    getModel().removeViewer(this);
  }

  protected void removeFromMap() {
    setFocusCycleRoot(false);
    Container parent = getParent();
    parent.remove(this);
  }

  private void repaintEdge(final NodeView target) {
    if (target.getMap().getLayoutType() == MapViewLayout.OUTLINE){
      target.getVisibleParentView().repaint();
      return;
    }
    final Point relativeLocation = getRelativeLocation(target);
        final MainView targetMainView = target.getMainView();
        relativeLocation.x += targetMainView.getWidth()/2;
        relativeLocation.y += targetMainView.getHeight()/2;
        final Point inPoint = mainView.getConnectorPoint(relativeLocation);
        UITools.convertPointToAncestor(targetMainView, inPoint, this);

        relativeLocation.x -= targetMainView.getWidth()/2;
        relativeLocation.y -= targetMainView.getHeight()/2;
        relativeLocation.x = - relativeLocation.x + mainView.getWidth()/2;
        relativeLocation.y = - relativeLocation.y + mainView.getHeight()/2;
    final Point outPoint = targetMainView.getConnectorPoint(relativeLocation);
    UITools.convertPointToAncestor(getMainView(), outPoint, this);

    final int x = Math.min(inPoint.x, outPoint.x);
    final int y = Math.min(inPoint.y, outPoint.y);
    final int w = Math.abs(inPoint.x - outPoint.x);
    final int h = Math.abs(inPoint.y - outPoint.y);
    final int EXTRA = 50;
    repaint(x - EXTRA, y - EXTRA, w + EXTRA * 2, h + EXTRA * 2);
  }

  void repaintSelected() {
    // return if main view was not set
    if (mainView == null) {
      return;
    }
    // do not repaint removed nodes
    if (model.getParentNode() == null && !model.isRoot()) {
      return;
    }
    if (getEdgeStyle().equals(EdgeStyle.EDGESTYLE_HIDDEN)) {
      final NodeView visibleParentView = getVisibleParentView();
      if (visibleParentView != null) {
        visibleParentView.repaintEdge(this);
      }
    }
    final JComponent content = getContent();
    final int EXTRA = 20;
    final int x = content.getX() - EXTRA;
    final int y = content.getY() - EXTRA;
    repaint(x, y, content.getWidth() + EXTRA * 2, content.getHeight() + EXTRA * 2);
  }

  @Override
  public boolean requestFocusInWindow() {
    if (mainView == null) {
      return false;
    }
    if (mainView.requestFocusInWindow()) {
      getMap().scrollNodeToVisible(this);
      Controller.getCurrentController().getViewController().addObjectTypeInfo(getModel().getUserObject());
      return true;
    }
    return false;
  }

  @Override
  public void requestFocus() {
    if (mainView == null) {
      return;
    }
    getMap().scrollNodeToVisible(this);
    Controller.getCurrentController().getViewController().addObjectTypeInfo(getModel().getUserObject());
    mainView.requestFocus();
  }

  void setMainView(final MainView newMainView) {
    if (contentPane != null) {
      assert (contentPane.getParent() == this);
      if (mainView != null)
        removeContent(MAIN_VIEWER_POSITION);
      addContent(newMainView, MAIN_VIEWER_POSITION);
      assert (contentPane.getParent() == this);
    }
    else if (mainView != null) {
      final Container c = mainView.getParent();
      int i;
      for (i = c.getComponentCount() - 1; i >= 0 && mainView != c.getComponent(i); i--) {
        ;
      }
      c.remove(i);
      c.add(newMainView, i);
    }
    else {
      add(newMainView);
    }
    mainView = newMainView;
    final IUserInputListenerFactory userInputListenerFactory = getMap().getModeController()
        .getUserInputListenerFactory();
    mainView.addMouseListener(userInputListenerFactory.getNodeMouseMotionListener());
    mainView.addMouseMotionListener(userInputListenerFactory.getNodeMouseMotionListener());
    mainView.addKeyListener(userInputListenerFactory.getNodeKeyListener());
    addDragListener(userInputListenerFactory.getNodeDragListener());
    addDropListener(userInputListenerFactory.getNodeDropTargetListener());
  }

  protected void setModel(final NodeModel model) {
    this.model = model;
  }

  public void setPreferredChild(final NodeView view) {
    if(view != null && ! SummaryNode.isSummaryNode(view.getModel()))
      preferredChild = view;
    final Container parent = this.getParent();
    if (view == null) {
      return;
    }
    else if (parent instanceof NodeView) {
      ((NodeView) parent).setPreferredChild(this);
    }
  }

  /**
   */
  public void setText(final String string) {
    mainView.setText(string);
  }


  void syncronizeAttributeView() {
    if (attributeView != null) {
      attributeView.syncronizeAttributeView();
    }
  }

  /*
   * (non-Javadoc)
   * @see java.awt.Component#toString()
   */
  @Override
  public String toString() {
    return getModel().toString() + ", " + super.toString();
  }

  /*
   * (non-Javadoc)
   * @see
   * javax.swing.event.TreeModelListener#treeStructureChanged(javax.swing.
   * event.TreeModelEvent)
   */
  private void treeStructureChanged() {
    for (final ListIterator<NodeView> i = getChildrenViews().listIterator(); i.hasNext();) {
      i.next().remove();
    }
    addChildViews();
    map.revalidateSelecteds();
    revalidate();
  }

  public void update() {
    updateShape();
    updateEdge();
    if (!isContentVisible()) {
      mainView.setVisible(false);
      return;
    }
    mainView.setVisible(true);
    mainView.updateTextColor(this);
    mainView.updateFont(this);
    createAttributeView();
    if (attributeView != null) {
      attributeView.update();
    }
    final boolean textShortened = isShortened();

    if(! textShortened){
      NodeViewFactory.getInstance().updateDetails(this);
      if (contentPane != null) {
        final int componentCount = contentPane.getComponentCount();
        for (int i = 1; i < componentCount; i++) {
          final Component component = contentPane.getComponent(i);
          if (component instanceof JComponent) {
            ((JComponent) component).revalidate();
          }
        }
      }
    }
    updateShortener(getModel(), textShortened);
    mainView.updateIcons(this);
    mainView.updateText(getModel());
    updateCloud();
    modelBackgroundColor = NodeStyleController.getController(getMap().getModeController()).getBackgroundColor(model);
    revalidate();
  }

  public boolean isShortened() {
      final ModeController modeController = getMap().getModeController();
    final TextController textController = TextController.getController(modeController);
    final boolean textShortened = textController.isMinimized(getModel());
      return textShortened;
    }

  private void updateEdge() {
        final EdgeController edgeController = EdgeController.getController(getMap().getModeController());
    this.edgeStyle = edgeController.getStyle(model, false);
    this.edgeWidth = edgeController.getWidth(model, false);
    this.edgeColor = edgeController.getColor(model, false);
    }

  public EdgeStyle getEdgeStyle() {
    if(edgeStyle != null)
      return edgeStyle;
    final NodeView parentView = getParentView();
    if(parentView != null)
      return parentView.getEdgeStyle();
    return EdgeStyle.values()[0];
    }

  public int getEdgeWidth() {
    if(edgeWidth != null)
        return edgeWidth;
    final NodeView parentView = getParentView();
    if(parentView != null)
      return parentView.getEdgeWidth();
    return 1;
    }
  public Color getEdgeColor() {
    if(edgeColor != null)
      return edgeColor;
    final NodeView parentView = getParentView();
    if(parentView != null)
      return parentView.getEdgeColor();
    return Color.GRAY;
    }

  private void updateCloud() {
    final CloudModel cloudModel = CloudController.getController(getMap().getModeController()).getCloud(model);
    putClientProperty(CloudModel.class, cloudModel);
    }

  public CloudModel getCloudModel() {
    return (CloudModel) getClientProperty(CloudModel.class);
    }

  private void updateShortener(NodeModel nodeModel, boolean textShortened) {
    final boolean componentsVisible = !textShortened;
    setContentComponentVisible(componentsVisible);
  }

  private void setContentComponentVisible(final boolean componentsVisible) {
    if(contentPane == null)
      return;
    final Component[] components = getContentPane().getComponents();
    int index;
    for (index = 0; index < components.length; index++) {
      final Component component = components[index];
      if (component == getMainView()) {
        continue;
      }
      if (component.isVisible() != componentsVisible) {
        component.setVisible(componentsVisible);
      }
    }
  }

  public void updateAll() {
    NodeViewFactory.getInstance().updateNoteViewer(this);
    update();
    invalidate();
    for (final NodeView child : getChildrenViews()) {
      child.updateAll();
    }
  }

  private void updateShape() {
    final String newShape = NodeStyleController.getController(getMap().getModeController()).getShape(model);
    final String oldShape;
    if(mainView != null)
      oldShape = mainView.getShape();
    else
      oldShape = null;
    if (mainView != null){
      if(oldShape.equals(newShape))
        return;
      if(model.isRoot()) {
        if(newShape != null)
          ((RootMainView)mainView).setShape(newShape);
        return;
      }
    }
    final MainView newMainView = NodeViewFactory.getInstance().newMainView(this);
    if(newMainView.getShape().equals(oldShape))
      return;
    setMainView(newMainView);
    if (map.getSelected() == this) {
      requestFocusInWindow();
    }
  }

  boolean useSelectionColors() {
    return isSelected() && !MapView.standardDrawRectangleForSelection && !map.isPrinting();
  }

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

  @Override
  protected void validateTree() {
    super.validateTree();
  }

  public void addContent(JComponent component, int pos) {
    component.putClientProperty("NODE_VIEW_CONTENT_POSITION", pos);
    final Container contentPane = getContentPane();
    for (int i = 0; i < contentPane.getComponentCount(); i++) {
      JComponent content = (JComponent) contentPane.getComponent(i);
      if (content == null)
        throw new RuntimeException("component " + i + "is null");
      final Object clientProperty = content.getClientProperty("NODE_VIEW_CONTENT_POSITION");
      if (clientProperty == null)
        throw new RuntimeException("NODE_VIEW_CONTENT_POSITION not set on component " + content.toString() + i
                + "/" + contentPane.getComponentCount());
      if (pos < (Integer) clientProperty) {
        contentPane.add(component, i);
        return;
      }
    }
    contentPane.add(component);
  }

  public JComponent removeContent(int pos) {
    return removeContent(pos, true);
  }

  private JComponent removeContent(int pos, boolean remove) {
    if (contentPane == null)
      return null;
    for (int i = 0; i < contentPane.getComponentCount(); i++) {
      JComponent component = (JComponent) contentPane.getComponent(i);
      Integer contentPos = (Integer) component.getClientProperty("NODE_VIEW_CONTENT_POSITION");
      if (contentPos == null) {
        continue;
      }
      if (contentPos == pos) {
        if (remove) {
          component.putClientProperty("NODE_VIEW_CONTENT_POSITION", null);
          contentPane.remove(i);
        }
        return component;
      }
      if (contentPos > pos) {
        return null;
      }
    }
    return null;
  }

  public JComponent getContent(int pos) {
    return removeContent(pos, false);
  }

  public boolean isSummary() {
    return SummaryNode.isSummaryNode(getModel());
  }

  public boolean isFirstGroupNode() {
    return SummaryNode.isFirstGroupNode(getModel());
  }

  public boolean isFree() {
    return FreeNode.isFreeNode(getModel());
  }

    public Color getDetailBackground() {
        final Color detailBackground = getMap().getDetailBackground();
        return detailBackground;
     }

  int getTopOverlap() {
    return topOverlap;
  }

  void setTopOverlap(int topOverlap) {
    this.topOverlap = topOverlap;
  }

  int getBottomOverlap() {
    return bottomOverlap;
  }

  void setBottomOverlap(int bottomOverlap) {
    this.bottomOverlap = bottomOverlap;
  }
}
TOP

Related Classes of org.freeplane.view.swing.map.NodeView

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.