/*
* 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.core.util.LogUtils;
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 {
public static boolean modifyModelWithoutRepaint = false;
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();
}
/**
* @param startAfter
*/
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 = (NodeView) 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 = (NodeView) 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.getParent() == 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.getParent().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){
//DOCEAR - fix: catch occasionally occurring exceptions
try {
childMainView.updateText(view.getModel());
view.numberingChanged(0);
}
catch (Exception e) {
LogUtils.info(e.getMessage());
}
}
}
}
}
}
/*
* (non-Javadoc)
* @see javax.swing.JComponent#paint(java.awt.Graphics)
*/
@Override
public void paintComponent(final Graphics g) {
//DOCEAR
if (modifyModelWithoutRepaint) {
return;
}
if (getMainView() == null)
return;
final PaintingMode paintingMode = map.getPaintingMode();
//DOCEAR -- if no paintingMode is set
if(paintingMode == null) {
return;
}
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);
}
switch (paintingMode) {
case NODES:
g2.setStroke(BubbleMainView.DEF_STROKE);
modeController.getController().getMapViewManager().setEdgesRenderingHint(g2);
paintEdges(g2, this);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, renderingHint);
}
}
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 = (Graphics2D) 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);
getParent().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() {
//DOCEAR: update the nodemodel without repainting, to greatly enhance speed when inserting references
if (NodeView.modifyModelWithoutRepaint) {
return false;
}
if (mainView == null) {
return false;
}
getMap().scrollNodeToVisible(this);
Controller.getCurrentController().getViewController().addObjectTypeInfo(getModel().getUserObject());
return mainView.requestFocusInWindow();
}
@Override
public void requestFocus() {
//DOCEAR: update the nodemodel without repainting, to greatly enhance speed when inserting references
if (NodeView.modifyModelWithoutRepaint) {
return;
}
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 synchronized void update() {
//DOCEAR: update the nodemodel without repainting, to greatly enhance speed when inserting references
if (NodeView.modifyModelWithoutRepaint) {
return;
}
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() {
//DOCEAR
if (modifyModelWithoutRepaint) {
return;
}
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() {
//DOCEAR - (repaint errors) catch every exception and ignore it
try {
super.validateTree();
}
catch (Throwable e) {
LogUtils.info("Error in org.freeplane.view.swing.map.NodeView.validateTree(): "+ e.getMessage());
}
}
public synchronized 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;
}
public static void setModifyModelWithoutRepaint(boolean modifyModelWithoutRepaint) {
NodeView.modifyModelWithoutRepaint = modifyModelWithoutRepaint;
}
public static boolean isModifyModelWithoutRepaint() {
return modifyModelWithoutRepaint;
}
}