/*
* 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.features.map;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import org.freeplane.core.extension.ExtensionContainer;
import org.freeplane.core.extension.IExtension;
import org.freeplane.core.extension.SmallExtensionMap;
import org.freeplane.core.util.HtmlUtils;
import org.freeplane.core.util.LogUtils;
import org.freeplane.core.util.XmlUtils;
import org.freeplane.features.filter.Filter;
import org.freeplane.features.filter.FilterInfo;
import org.freeplane.features.icon.MindIcon;
import org.freeplane.features.ui.INodeViewVisitor;
/**
* This class represents a single Node of a Tree. It contains direct handles to
* its parent and children and to its view.
*
* Note that this class does not and must not know anything about its extensions,
* otherwise this class would become too big.
* Extension methods that add functionality to nodes are in the extension packages
* and get NodeModel as an argument.
*/
public class NodeModel implements MutableTreeNode {
public enum NodeChangeType {
FOLDING, REFRESH
}
private static final boolean ALLOWSCHILDREN = true;
public final static int LEFT_POSITION = -1;
public static final String NODE_TEXT = "node_text";
public static final String NOTE_TEXT = "note_text";
public final static int RIGHT_POSITION = 1;
public final static int UNKNOWN_POSITION = 0;
static public final Object UNKNOWN_PROPERTY = new Object();
public static final String NODE_ICON = "icon";
//DOCEAR - fixed: new property type for node link changes
static public final Object HYPERLINK_CHANGED = "hyperlink_changed";
private final List<NodeModel> children = new ArrayList<NodeModel>();
private final ExtensionContainer extensionContainer;
final private FilterInfo filterInfo = new FilterInfo();
private boolean folded;
private HistoryInformationModel historyInformation = null;
final private NodeIconSetModel icons;
private String id;
private MapModel map = null;
private NodeModel parent;
private int position = NodeModel.UNKNOWN_POSITION;
private NodeModel preferredChild;
private Object userObject = null;
public Object getUserObject() {
return userObject;
}
private Collection<INodeView> views = null;
private String xmlText = null;
public NodeModel(final MapModel map) {
this("", map);
}
public NodeModel(final Object userObject, final MapModel map) {
extensionContainer = new ExtensionContainer(new SmallExtensionMap());
init(userObject);
this.map = map;
icons = new NodeIconSetModel();
}
protected void init(final Object userObject) {
setUserObject(userObject);
setHistoryInformation(new HistoryInformationModel());
}
public void acceptViewVisitor(final INodeViewVisitor visitor) {
if (views == null) {
return;
}
for (final INodeView view : views) {
visitor.visit(view);
}
}
public void addExtension(final IExtension extension) {
extensionContainer.addExtension(extension);
}
public IExtension putExtension(final IExtension extension) {
return extensionContainer.putExtension(extension);
}
public void addIcon(final MindIcon icon) {
icons.addIcon(icon);
if (map != null) {
map.getIconRegistry().addIcon(icon);
}
}
public void addIcon(final MindIcon icon, final int position) {
icons.addIcon(icon, position);
getMap().getIconRegistry().addIcon(icon);
}
public void addViewer(final INodeView viewer) {
getViewers().add(viewer);
}
public boolean areViewsEmpty() {
return views == null || views.isEmpty();
}
protected List<NodeModel> getChildrenInternal() {
return children;
}
public Enumeration<NodeModel> children() {
final Iterator<NodeModel> i = getChildrenInternal().iterator();
return new Enumeration<NodeModel>() {
public boolean hasMoreElements() {
return i.hasNext();
}
public NodeModel nextElement() {
return i.next();
}
};
}
public boolean containsExtension(final Class<? extends IExtension> clazz) {
return extensionContainer.containsExtension(clazz);
}
public String createID() {
if (id == null) {
id = getMap().registryNode(this);
}
return id;
}
public void fireNodeChanged(final NodeChangeEvent nodeChangeEvent) {
if (views == null) {
return;
}
final Iterator<INodeView> iterator = views.iterator();
while (iterator.hasNext()) {
try {
iterator.next().nodeChanged(nodeChangeEvent);
}
catch (Exception e) {
LogUtils.warn(e);
}
}
}
private void fireNodeInserted(final NodeModel child, final int index) {
if (views == null) {
return;
}
final Iterator<INodeView> iterator = views.iterator();
while (iterator.hasNext()) {
iterator.next().onNodeInserted(this, child, index);
}
}
private void fireNodeRemoved(final NodeModel child, final int index) {
if (views == null) {
return;
}
final Iterator<INodeView> iterator = views.iterator();
while (iterator.hasNext()) {
iterator.next().onNodeDeleted(this, child, index);
}
}
public boolean getAllowsChildren() {
return NodeModel.ALLOWSCHILDREN;
};
public TreeNode getChildAt(final int childIndex) {
return getChildrenInternal().get(childIndex);
}
public int getChildCount() {
if (getChildrenInternal() == null) {
return 0;
}
final EncryptionModel encryptionModel = EncryptionModel.getModel(this);
return encryptionModel == null || encryptionModel.isAccessible() ? getChildrenInternal().size() : 0;
}
public int getChildPosition(final NodeModel childNode) {
int position = 0;
for (final ListIterator<NodeModel> i = getChildrenInternal().listIterator(); i.hasNext(); ++position) {
if ((i.next()) == childNode) {
return position;
}
}
return -1;
}
public List<NodeModel> getChildren() {
List<NodeModel> childrenList;
if (getChildrenInternal() != null) {
childrenList = getChildrenInternal();
}
else {
childrenList = Collections.emptyList();
}
return Collections.unmodifiableList(childrenList);
}
public <T extends IExtension> T getExtension(final Class<T> clazz) {
return (T) extensionContainer.getExtension(clazz);
}
public Map<Class<? extends IExtension>, IExtension> getExtensions() {
return extensionContainer.getExtensions();
};
public FilterInfo getFilterInfo() {
return filterInfo;
}
public HistoryInformationModel getHistoryInformation() {
return historyInformation;
}
public MindIcon getIcon(final int position) {
return icons.getIcon(position);
}
public List<MindIcon> getIcons() {
return icons.getIcons();
}
public String getID() {
return id;
}
public int getIndex(final TreeNode node) {
return getChildrenInternal().indexOf(node);
}
public MapModel getMap() {
return map;
}
public int getNodeLevel(final boolean countHidden) {
int level = 0;
NodeModel parent;
for (parent = getParentNode(); parent != null; parent = parent.getParentNode()) {
if (countHidden || parent.isVisible()) {
level++;
}
}
return level;
}
public TreeNode getParent() {
return parent;
}
public NodeModel getParentNode() {
return parent;
}
public NodeModel[] getPathToRoot() {
int i = getNodeLevel(true);
final NodeModel[] path = new NodeModel[i + 1];
NodeModel node = this;
while (i >= 0) {
path[i--] = node;
node = node.getParentNode();
}
return path;
}
public String getText() {
String string = "";
if (userObject != null) {
string = userObject.toString();
}
return string;
}
public Collection<INodeView> getViewers() {
if (views == null) {
views = new LinkedList<INodeView>();
}
return views;
}
public final String getXmlText() {
return xmlText;
}
public boolean hasChildren() {
return getChildCount() != 0;
}
public boolean hasID() {
return id != null;
}
public void insert(final MutableTreeNode child, int index) {
if (!isAccessible()) {
throw new IllegalArgumentException("Trying to insert nodes into a ciphered node.");
}
final NodeModel childNode = (NodeModel) child;
if (index < 0) {
index = getChildCount();
getChildrenInternal().add(index, (NodeModel) child);
}
else {
getChildrenInternal().add(index, (NodeModel) child);
preferredChild = childNode;
}
child.setParent(this);
fireNodeInserted(childNode, getIndex(child));
}
private boolean isAccessible() {
final EncryptionModel encryptionModel = EncryptionModel.getModel(this);
return encryptionModel == null || encryptionModel.isAccessible();
}
/**
* Returns whether the argument is parent or parent of one of the grandpa's
* of this node. (transitive)
*/
public boolean isDescendantOf(final NodeModel node) {
if (parent == null) {
return false;
}
else if (node == parent) {
return true;
}
else {
return parent.isDescendantOf(node);
}
}
public boolean isFolded() {
return folded;
}
/*
* Notes
*/
public boolean isLeaf() {
return getChildCount() == 0;
}
public boolean isLeft() {
if (position == NodeModel.UNKNOWN_POSITION && getParentNode() != null) {
setLeft(getParentNode().isLeft());
}
return position == NodeModel.LEFT_POSITION;
}
public boolean isNewChildLeft() {
if (!isRoot()) {
return isLeft();
}
int rightChildrenCount = 0;
for (int i = 0; i < getChildCount(); i++) {
if (!((NodeModel) getChildAt(i)).isLeft()) {
rightChildrenCount++;
}
if (rightChildrenCount > getChildCount() / 2) {
return true;
}
}
return false;
}
public boolean isRoot() {
return getMap().getRootNode() == this;
}
public boolean isVisible() {
final Filter filter = getMap().getFilter();
return filter == null || filter.isVisible(this);
}
public void remove(final int index) {
final MutableTreeNode node = getChildrenInternal().get(index);
remove(node);
}
public void remove(final MutableTreeNode node) {
if (node == preferredChild) {
final int index = getChildrenInternal().indexOf(node);
if (getChildrenInternal().size() > index + 1) {
preferredChild = (getChildrenInternal().get(index + 1));
}
else {
preferredChild = (index > 0) ? (NodeModel) (getChildrenInternal().get(index - 1)) : null;
}
}
final int index = getIndex(node);
node.setParent(null);
getChildrenInternal().remove(node);
fireNodeRemoved((NodeModel) node, index);
}
public <T extends IExtension> T removeExtension(final Class<T> clazz){
return extensionContainer.removeExtension(clazz);
}
public boolean removeExtension(final IExtension extension) {
return extensionContainer.removeExtension(extension);
}
public void removeFromParent() {
parent.remove(this);
}
/**
* remove last icon
*
* @return the number of remaining icons.
*/
public int removeIcon() {
return icons.removeIcon();
}
/**
* @param remove icons with given position
*
* @return the number of remaining icons
*/
public int removeIcon(final int position) {
return icons.removeIcon(position);
}
public void removeViewer(final INodeView viewer) {
getViewers().remove(viewer);
}
public void setFolded(boolean folded) {
if (this.folded == folded) {
return;
}
final EncryptionModel encryptionModel = EncryptionModel.getModel(this);
if (encryptionModel != null && !encryptionModel.isAccessible() && folded == false) {
folded = true;
}
else if (AlwaysUnfoldedNode.isConnectorNode(this)){
folded = false;
}
if (this.folded == folded) {
return;
}
this.folded = folded;
fireNodeChanged(new NodeChangeEvent(this, NodeChangeType.FOLDING, Boolean.valueOf(!folded),
Boolean.valueOf(folded)));
}
public void setHistoryInformation(final HistoryInformationModel historyInformation) {
this.historyInformation = historyInformation;
}
public void setID(final String value) {
id = value;
getMap().registryID(value, this);
}
public void setLeft(final boolean isLeft) {
position = isLeft ? NodeModel.LEFT_POSITION : NodeModel.RIGHT_POSITION;
if (!isRoot()) {
for (int i = 0; i < getChildCount(); i++) {
final NodeModel child = (NodeModel) getChildAt(i);
if (child.position != position) {
child.setLeft(isLeft);
}
}
}
}
/**
*/
public void setMap(final MapModel map) {
this.map = map;
for (final NodeModel child : getChildrenInternal()) {
child.setMap(map);
}
}
public void setParent(final MutableTreeNode newParent) {
parent = (NodeModel) newParent;
}
public void setParent(final NodeModel newParent) {
parent = newParent;
}
public final void setText(final String text) {
userObject = XmlUtils.makeValidXml(text);
xmlText = HtmlUtils.toXhtml(text);
if (xmlText != null && !xmlText.startsWith("<")) {
userObject = " " + text;
xmlText = null;
}
}
public final void setUserObject(final Object data) {
if (data instanceof String) {
setText(data.toString());
return;
}
userObject = data;
xmlText = null;
}
public final void setXmlText(final String pXmlText) {
xmlText = XmlUtils.makeValidXml(pXmlText);
userObject = HtmlUtils.toHtml(xmlText);
}
@Override
public String toString() {
return HtmlUtils.htmlToPlain(getText());
}
public int depth() {
final NodeModel parentNode = getParentNode();
if (parentNode == null) {
return 0;
}
return parentNode.depth() + 1;
}
public void insert(final NodeModel newNodeModel) {
insert(newNodeModel, getChildCount());
}
public NodeModel getVisibleAncestorOrSelf() {
NodeModel node = this;
while (!node.isVisible()) {
node = node.getParentNode();
}
return node;
}
}