/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo 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 3 of the License, or * (at your option) any later version. * * OpenFlexo 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 OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.fge; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.Hashtable; import java.util.List; import java.util.Map.Entry; import java.util.Observable; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import org.openflexo.fge.notifications.GraphicalObjectsHierarchyRebuildEnded; import org.openflexo.fge.notifications.GraphicalObjectsHierarchyRebuildStarted; /** * This class is the default implementation for all objects representing a graphical drawing, that is a complex graphical representation * involving an object tree where all objects have their own graphical representation. * * To perform this, two major features are required here: * <ul> * <li>First, a * * <pre> * Drawing * </pre> * * must indicate how to map a given object (called drawable) to its graphical representation (@see * {@link #getGraphicalRepresentation(Object)})</li> * <li>Then, this * * <pre> * Drawing * </pre> * * must encode the objects hierarchy, by implementing following methods: (@see {@link #getContainer(Object)} and @see * {@link #getContainedObjects(Object)})</li> * </ul> * * Note that at top level, this drawing is associated with its own {@link GraphicalRepresentation} which is this case is a * {@link DrawingGraphicalRepresentation<M>} of * * <pre> * M * </pre> * * . * * * @author sylvain * * @param <M> * Type of object which is handled as root object */ public abstract class DefaultDrawing<M> extends Observable implements Drawing<M> { private static final Logger logger = Logger.getLogger(DefaultDrawing.class.getPackage().getName()); private Hashtable<Object, DrawingTreeNode<?>> _hashMap; private DrawingTreeNode<M> _root; private M model; private boolean editable = true; public DefaultDrawing(M aModel) { model = aModel; _hashMap = new Hashtable<Object, DrawingTreeNode<?>>(); _root = new DrawingTreeNode<M>(model, null); _hashMap.put(model, _root); } @Override public boolean isEditable() { return editable; } public void setEditable(boolean editable) { this.editable = editable; } public <O> void updateDrawable(O aDrawable) { DrawingTreeNode<?> alreadyExistingNode = _hashMap.get(aDrawable); if (alreadyExistingNode == null) { logger.warning("Cannot find DrawingTreeNode for " + aDrawable); return; } if (alreadyExistingNode.parentNode != null) { Object parentDrawable = alreadyExistingNode.parentNode.drawable; removeDrawable(aDrawable, parentDrawable); addDrawable(aDrawable, parentDrawable); } } public <O> void addDrawable(O aDrawable, Object aParentDrawable) { // logger.fine("> add drawable "+aDrawable+" under "+aParentDrawable); if (aParentDrawable == null) { logger.warning("Cannot register drawable above null parent"); return; } DrawingTreeNode<?> parentNode = _hashMap.get(aParentDrawable); if (parentNode == null) { logger.warning("Cannot find DrawingTreeNode for " + aParentDrawable); return; } if (parentNode.childs.contains(aDrawable)) { DrawingTreeNode<?> alreadyExistingNode = _hashMap.get(aDrawable); if (alreadyExistingNode == null) { logger.warning("Cannot find DrawingTreeNode for " + aDrawable); return; } if (parentNode.nodesToRemove == null) { if (logger.isLoggable(Level.FINE)) { logger.fine("parentNode.nodesToRemove == null"); logger.fine("Adding drawable " + aDrawable + " under " + aParentDrawable); } } parentNode.nodesToRemove.remove(alreadyExistingNode); if (logger.isLoggable(Level.FINE)) { logger.fine("No need to insert " + aDrawable + " under " + aParentDrawable + " as it is already done"); } return; } else { if (logger.isLoggable(Level.FINE)) { logger.fine("add drawable " + aDrawable + " under " + aParentDrawable); } new DrawingTreeNode<O>(aDrawable, aParentDrawable); if (getGraphicalRepresentation(aDrawable) == null) { logger.warning("Could not find graphical representation for " + aDrawable); } else { if (isUpdatingObjectHierarchy) { graphicalRepresentationToNotifyAdding.add(getGraphicalRepresentation(aDrawable)); } else { // Do it now getGraphicalRepresentation(aDrawable).setValidated(true); getGraphicalRepresentation(aParentDrawable).notifyDrawableAdded(getGraphicalRepresentation(aDrawable)); } } } } public <O> void removeDrawable(O aDrawable, Object aParentDrawable) { if (aParentDrawable == null) { logger.warning("Cannot unregister drawable above null parent"); return; } DrawingTreeNode<?> parentNode = _hashMap.get(aParentDrawable); if (parentNode == null) { logger.warning("Cannot find DrawingTreeNode for " + aParentDrawable); return; } if (!parentNode.childs.contains(aDrawable)) { logger.warning("Cannot remove " + aDrawable + " under " + aParentDrawable + " as it is not registered"); return; } else { DrawingTreeNode<?> nodeToRemove = _hashMap.get(aDrawable); if (nodeToRemove == null) { logger.warning("Cannot find DrawingTreeNode for " + nodeToRemove); return; } if (logger.isLoggable(Level.FINE)) { logger.fine("remove drawable " + aDrawable + " under " + aParentDrawable); } GraphicalRepresentation<?> parentGR = getGraphicalRepresentation(aParentDrawable); GraphicalRepresentation<?> removedGR = getGraphicalRepresentation(aDrawable); parentNode.removeChild(nodeToRemove); parentGR.notifyDrawableRemoved(removedGR); removedGR.delete(); } } public Enumeration<GraphicalRepresentation<?>> getAllGraphicalRepresentations() { Vector<GraphicalRepresentation<?>> returned = new Vector<GraphicalRepresentation<?>>(); for (Enumeration<DrawingTreeNode<?>> en = _hashMap.elements(); en.hasMoreElements();) { returned.add(en.nextElement().getGraphicalRepresentation()); } return returned.elements(); } public Enumeration<GraphicalRepresentation<?>> getAllSortedGraphicalRepresentations() { Vector<GraphicalRepresentation<?>> returned = new Vector<GraphicalRepresentation<?>>(); for (Enumeration<DrawingTreeNode<?>> en = getAllSortedNodes(); en.hasMoreElements();) { returned.add(en.nextElement().getGraphicalRepresentation()); } return returned.elements(); } private Enumeration<DrawingTreeNode<?>> getAllSortedNodes() { Vector<DrawingTreeNode<?>> dtnList = new Vector<DrawingTreeNode<?>>(); for (Enumeration<DrawingTreeNode<?>> en = _hashMap.elements(); en.hasMoreElements();) { dtnList.add(en.nextElement()); } Collections.sort(dtnList, new Comparator<DrawingTreeNode<?>>() { @Override public int compare(DrawingTreeNode<?> o1, DrawingTreeNode<?> o2) { int res = o2.getDepth() - o1.getDepth(); if (res != 0) { return res; } if (o2.parentNode == o1.parentNode) { return o2.parentNode.childNodes.indexOf(o1) - o2.parentNode.childNodes.indexOf(o2); } // 1. We first find a common ancestor DrawingTreeNode<?> ancestor = o1.getCommonAncestor(o2); if (ancestor != null) { DrawingTreeNode<?> p1 = null, p2 = null; // 2. We look for a direct child of the common ancestor which is an ancestor of the compared nodes DrawingTreeNode<?> c1 = o1; while (c1.parentNode != ancestor) { c1 = c1.parentNode; } p1 = c1; DrawingTreeNode<?> c2 = o2; while (c2.parentNode != ancestor) { c2 = c2.parentNode; } p2 = c2; // 3. We now return the difference of index between those 2. if (p1 != null && p2 != null) { return ancestor.childNodes.indexOf(p1) - ancestor.childNodes.indexOf(p2); } } if (o1.isAncestorOf(o2)) { return -1; } if (o2.isAncestorOf(o1)) { return 1; } logger.warning("Could not compare " + o1 + " and " + o2); return 0; } }); /*Vector<GraphicalRepresentation> returned = new Vector<GraphicalRepresentation>(); for (Enumeration<DrawingTreeNode> en = dtnList.elements(); en.hasMoreElements();) { returned.add(en.nextElement().graphicalRepresentation); }*/ return dtnList.elements(); } private boolean isUpdatingObjectHierarchy = false; private Vector<DrawingTreeNode<?>> nodesToUpdate; private Vector<GraphicalRepresentation<?>> graphicalRepresentationToNotifyAdding; private boolean isGraphicalHierarchyEnabled = true; private boolean isGraphicalHierarchyDirty = true; public final void enableGraphicalHierarchy() { isGraphicalHierarchyEnabled = true; if (isGraphicalHierarchyDirty) { updateGraphicalObjectsHierarchy(); } } public final void disableGraphicalHierarchy() { isGraphicalHierarchyEnabled = false; } public final void updateGraphicalObjectsHierarchy() { if (!isGraphicalHierarchyEnabled) { isGraphicalHierarchyDirty = true; return; } if (isUpdatingObjectHierarchy) { return; } // logger.info("******************** BEGIN updateGraphicalObjectsHierarchy() "); setChanged(); notifyObservers(new GraphicalObjectsHierarchyRebuildStarted(this)); beginUpdateObjectHierarchy(); buildGraphicalObjectsHierarchy(); endUpdateObjectHierarchy(); isGraphicalHierarchyDirty = false; setChanged(); notifyObservers(new GraphicalObjectsHierarchyRebuildEnded(this)); // logger.info("******************** END updateGraphicalObjectsHierarchy() "); } /** * Invalidate the whole hierarchy. All nodes of drawing tree are invalidated, which means that a complete recomputing of the whole tree * will be performed during next updateGraphicalHierarchy() call<br> * Existing graphical representation are kept. */ public void invalidateGraphicalObjectsHierarchy() { invalidateGraphicalObjectsHierarchy(getModel()); } /** * Invalidate the whole hierarchy under current node designated by supplied object All nodes of drawing tree under supplied node are * invalidated, which means that a recomputing of the whole tree under supplied node will be performed during next * updateGraphicalHierarchy() call.<br> * If flag deleteGraphicalRepresentation is set to true, associated graphical representation will be deleted and nullified, and then new * graphical representations will be instanciated during next update request. * * @param deleteGraphicalRepresentation */ public void invalidateGraphicalObjectsHierarchy(Object object) { DrawingTreeNode<?> dtn = _hashMap.get(object); // System.out.println("invalidateGraphicalObjectsHierarchy with " + object + " dtn=" + dtn); if (dtn != null) { dtn.invalidate(); } } public void printGraphicalObjectHierarchy() { logger.info("Graphical object hierarchy"); if (_root != null) { _printGraphicalObjectHierarchy(_root, 0); } else { logger.info(" > Root node is null !"); } } private void _printGraphicalObjectHierarchy(DrawingTreeNode<?> dtn, int level) { logger.info(buildWhiteSpaceIndentation(level * 5) + " > " + (dtn.getGraphicalRepresentation() != null ? dtn.getGraphicalRepresentation().getClass().getSimpleName() + " " + Integer.toHexString(dtn.getGraphicalRepresentation().hashCode()) : " null ") + " object=" + dtn.drawable); if (dtn.childNodes != null) { for (DrawingTreeNode<?> child : dtn.childNodes) { _printGraphicalObjectHierarchy(child, level + 1); } } } public static String buildString(char c, int length) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < length; i++) { sb.append(c); } return sb.toString(); } public static String buildWhiteSpaceIndentation(int length) { return buildString(' ', length); } protected abstract void buildGraphicalObjectsHierarchy(); protected void beginUpdateObjectHierarchy() { // System.out.println("*************** Hop, DEBUT pour " + this); nodesToUpdate = new Vector<DrawingTreeNode<?>>(); Enumeration<DrawingTreeNode<?>> allNodes = getAllSortedNodes(); while (allNodes.hasMoreElements()) { DrawingTreeNode<?> next = allNodes.nextElement(); if (next.getGraphicalRepresentation() != null) { // logger.info("What about "+next.getClass().getSimpleName()); next.getGraphicalRepresentation().notifyObjectHierarchyWillBeUpdated(); } nodesToUpdate.add(next); } isUpdatingObjectHierarchy = true; for (DrawingTreeNode<?> n : nodesToUpdate) { if (n.getGraphicalRepresentation() instanceof ConnectorGraphicalRepresentation) { ConnectorGraphicalRepresentation<?> connector = (ConnectorGraphicalRepresentation<?>) n.getGraphicalRepresentation(); if (!connector.isConnectorConsistent()) { n.invalidate(); continue; } DrawingTreeNode<?> startTreeNode = _hashMap.get(connector.getStartObject().getDrawable()); DrawingTreeNode<?> endTreeNode = _hashMap.get(connector.getEndObject().getDrawable()); if (startTreeNode != null && startTreeNode.isInvalidated) { // System.out.println("Invalidate "+n.graphicalRepresentation.getDrawable()+" because "+startTreeNode.graphicalRepresentation.getDrawable()+" is invalidated"); n.invalidate(); } if (endTreeNode != null && endTreeNode.isInvalidated) { // System.out.println("Invalidate "+n.graphicalRepresentation.getDrawable()+" because "+endTreeNode.graphicalRepresentation.getDrawable()+" is invalidated"); n.invalidate(); } } } for (DrawingTreeNode<?> n : nodesToUpdate) { n.beginUpdateObjectHierarchy(); } graphicalRepresentationToNotifyAdding = new Vector<GraphicalRepresentation<?>>(); } protected void endUpdateObjectHierarchy() { if (logger.isLoggable(Level.FINE)) { logger.fine("Called endUpdateObjectHierarchy()"); } // First validate all shape graphical representations Enumeration<GraphicalRepresentation<?>> allGr = getAllSortedGraphicalRepresentations(); while (allGr.hasMoreElements()) { GraphicalRepresentation<?> next = allGr.nextElement(); if (next instanceof ShapeGraphicalRepresentation) { next.setValidated(true); } } // First validate all connector graphical representations allGr = getAllSortedGraphicalRepresentations(); while (allGr.hasMoreElements()) { GraphicalRepresentation<?> next = allGr.nextElement(); if (next instanceof ConnectorGraphicalRepresentation) { next.setValidated(true); } } for (DrawingTreeNode<?> n : nodesToUpdate) { n.endUpdateObjectHierarchy(); } for (GraphicalRepresentation<?> gr : graphicalRepresentationToNotifyAdding) { if (gr.getContainerGraphicalRepresentation() != null) { gr.getContainerGraphicalRepresentation().notifyDrawableAdded(gr); } else { logger.warning("null ContainerGraphicalRepresentation for " + gr); } } graphicalRepresentationToNotifyAdding.clear(); isUpdatingObjectHierarchy = false; // logger.info("**************************** endUpdateObjectHierarchy()"); allGr = getAllSortedGraphicalRepresentations(); while (allGr.hasMoreElements()) { GraphicalRepresentation<?> next = allGr.nextElement(); if (next != null) { // logger.info("> notifyObjectHierarchyHasBeenUpdated() for "+next); next.notifyObjectHierarchyHasBeenUpdated(); } } // System.out.println("*************** Hop, FIN pour " + this); // printGraphicalObjectHierarchy(); } private class DrawingTreeNode<O> { private O drawable; DrawingTreeNode<?> parentNode; private Vector<DrawingTreeNode<?>> childNodes; Vector<Object> childs; GraphicalRepresentation<O> graphicalRepresentation; boolean isInvalidated = false; protected void invalidate() { // System.out.println("* Invalidate " + drawable.getClass().getSimpleName() + " : " + drawable); isInvalidated = true; for (DrawingTreeNode<?> dtn : childNodes) { dtn.invalidate(); } } private List<DrawingTreeNode<?>> getAncestors() { List<DrawingTreeNode<?>> ancestors = new ArrayList<DefaultDrawing<M>.DrawingTreeNode<?>>(); ancestors.add(this); if (parentNode != null) { ancestors.addAll(parentNode.getAncestors()); } return ancestors; } public DrawingTreeNode<?> getCommonAncestor(DrawingTreeNode<?> o) { List<DrawingTreeNode<?>> ancestors = o.getAncestors(); for (DrawingTreeNode<?> ancestor : getAncestors()) { if (ancestors.contains(ancestor)) { return ancestor; } } return null; } private boolean isAncestorOf(DrawingTreeNode<?> o2) { if (o2 == null) { return false; } DrawingTreeNode<?> current = o2; while (current != DrawingTreeNode.this && current != null) { current = current.parentNode; } if (current == null) { return false; } return true; } private int getDepth() { int returned = 0; DrawingTreeNode<?> current = this; while (current.parentNode != null) { returned++; current = current.parentNode; } return returned; } private DrawingTreeNode(O aDrawable, Object aParentDrawable) { // logger.info("New DrawingTreeNode for "+aDrawable+" under "+aParentDrawable+" (is "+this+")"); drawable = aDrawable; if (aParentDrawable != null) { parentNode = _hashMap.get(aParentDrawable); if (parentNode == null) { logger.warning("Cannot find GR for " + aParentDrawable); } else { parentNode.childNodes.add(this); parentNode.childs.add(aDrawable); } } childNodes = new Vector<DrawingTreeNode<?>>(); childs = new Vector<Object>(); _hashMap.put(aDrawable, this); /*if (aParentDrawable == null) { // This is the root node graphicalRepresentation = (GraphicalRepresentation<O>) getDrawingGraphicalRepresentation(); } else { graphicalRepresentation = retrieveGraphicalRepresentation(aDrawable); }*/ } private GraphicalRepresentation<O> getGraphicalRepresentation() { if (graphicalRepresentation == null) { if (parentNode == null) { // This is the root node graphicalRepresentation = (GraphicalRepresentation<O>) getDrawingGraphicalRepresentation(); } else { graphicalRepresentation = retrieveGraphicalRepresentation(drawable); } } return graphicalRepresentation; } /*private void update() { if (parentNode == null) { // This is the root node graphicalRepresentation = (GraphicalRepresentation<O>)getDrawingGraphicalRepresentation(); } else { GraphicalRe parentGR.notifyDrawableRemoved(removedGR); graphicalRepresentation = retrieveGraphicalRepresentation(drawable); System.out.println("Tiens maintenant la GR c'est "+graphicalRepresentation); } }*/ private void removeChild(DrawingTreeNode<?> aChildNode) { if (aChildNode == null) { logger.warning("Cannot remove null node"); return; } if (childNodes.contains(aChildNode)) { childNodes.remove(aChildNode); } else { logger.warning("Cannot remove node: not present"); return; } Object child = aChildNode.drawable; if (childs.contains(child)) { childs.remove(child); } else { logger.warning("Cannot remove child: not present"); return; } aChildNode.delete(); } protected void delete() { // Normally, it is already done, but check and do it when required... if (parentNode != null && parentNode.childNodes.contains(this)) { parentNode.removeChild(this); } if (childNodes != null) { for (DrawingTreeNode<?> n : new ArrayList<DrawingTreeNode<?>>(childNodes)) { removeChild(n); } childNodes.clear(); } childNodes = null; if (childs != null) { childs.clear(); } childs = null; if (_hashMap != null && drawable != null) { _hashMap.remove(drawable); } if (graphicalRepresentation != null) { graphicalRepresentation.delete(); } drawable = null; parentNode = null; graphicalRepresentation = null; } Vector<DrawingTreeNode<?>> nodesToRemove = new Vector<DrawingTreeNode<?>>(); protected void beginUpdateObjectHierarchy() { // Invalidated nodes are to be removed rigth now // (we are sure that we don't want to keep it) if (childNodes != null) { for (DrawingTreeNode<?> n : new ArrayList<DrawingTreeNode<?>>(childNodes)) { if (n.isInvalidated) { removeDrawable(n.drawable, drawable); } } } // Remaining nodes are marked to potential deletion if (childNodes != null) { for (DrawingTreeNode<?> n : childNodes) { nodesToRemove.add(n); } } } protected void endUpdateObjectHierarchy() { // Nodes that keep marked for deletion are deleted now for (DrawingTreeNode<?> n : nodesToRemove) { removeDrawable(n.drawable, drawable); } nodesToRemove.clear(); } } @Override public List<?> getContainedObjects(Object aDrawable) { DrawingTreeNode<?> treeNode = _hashMap.get(aDrawable); // logger.info("getContainedObjects() for "+aDrawable+" > "+ _hashMap.get(aDrawable)+" childs="+treeNode.childs); if (treeNode != null) { return new Vector<Object>(treeNode.childs); } return null; } @Override public Object getContainer(Object aDrawable) { /*if (_hashMap == null) { logger.info("Ici, on demande _hashMap pour le Drawing "+this+" "+Integer.toHexString(hashCode())); }*/ DrawingTreeNode<?> treeNode = _hashMap.get(aDrawable); /*logger.info("Pour "+aDrawable+" le DTN c'est "+treeNode); if (treeNode != null && treeNode.parentNode != null) { logger.info("Pour "+aDrawable+" le parent DTN c'est "+treeNode.parentNode); logger.info("Pour "+aDrawable+" et le drawable c'est "+treeNode.parentNode.drawable); }*/ if (treeNode != null && treeNode.parentNode != null) { return treeNode.parentNode.drawable; } return null; } @Override public M getModel() { return model; } @Override public <O> GraphicalRepresentation<O> getGraphicalRepresentation(O aDrawable) { if (aDrawable == getModel()) { return (GraphicalRepresentation<O>) getDrawingGraphicalRepresentation(); } DrawingTreeNode<O> treeNode = (DrawingTreeNode<O>) _hashMap.get(aDrawable); if (treeNode != null) { return treeNode.getGraphicalRepresentation(); } return null; } public abstract <O> GraphicalRepresentation<O> retrieveGraphicalRepresentation(O aDrawable); @Override public String toString() { return "Drawing of " + model; } public void delete() { if (logger.isLoggable(Level.INFO)) { logger.info("deleting " + this); } if (_hashMap != null) { for (Entry<Object, DrawingTreeNode<?>> e : new ArrayList<Entry<Object, DrawingTreeNode<?>>>(_hashMap.entrySet())) { DrawingTreeNode<?> dtn = e.getValue(); if (dtn != null) { if (dtn.graphicalRepresentation != null) { dtn.graphicalRepresentation.delete(); } else { if (logger.isLoggable(Level.WARNING)) { logger.warning("No GR for " + e.getKey()); } } dtn.delete(); } } if (getDrawingGraphicalRepresentation() != null) { getDrawingGraphicalRepresentation().delete(); } _hashMap.clear(); } model = null; // _hashMap = null; } /** * This hook is called whenever a drawing will be displayed (when a DrawingView will be build) */ public void prepareVisualization() { } }