-/*******************************************************************************\r
- * Copyright (c) 2012, 2013 Association for Decentralized Information Management in\r
- * Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- * VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.g3d.vtk.common;\r
-\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.HashMap;\r
-import java.util.HashSet;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Set;\r
-import java.util.Stack;\r
-\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.Session;\r
-import org.simantics.db.WriteGraph;\r
-import org.simantics.db.common.request.ReadRequest;\r
-import org.simantics.db.common.request.WriteRequest;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.g3d.ontology.G3D;\r
-import org.simantics.g3d.scenegraph.IG3DNode;\r
-import org.simantics.g3d.scenegraph.RenderListener;\r
-import org.simantics.g3d.scenegraph.base.INode;\r
-import org.simantics.g3d.scenegraph.base.NodeListener;\r
-import org.simantics.g3d.scenegraph.base.ParentNode;\r
-import org.simantics.objmap.graph.IMapping;\r
-import org.simantics.objmap.graph.IMappingListener;\r
-import org.simantics.utils.datastructures.Callback;\r
-import org.simantics.utils.datastructures.MapList;\r
-import org.simantics.utils.datastructures.MapSet;\r
-import org.simantics.utils.datastructures.Pair;\r
-import org.simantics.utils.ui.ExceptionUtils;\r
-\r
-import vtk.vtkProp;\r
-\r
-public abstract class AbstractVTKNodeMap<E extends INode> implements VTKNodeMap, IMappingListener, RenderListener, NodeListener{\r
-\r
- private static final boolean DEBUG = false;\r
- \r
- protected Session session;\r
- protected IMapping<Object,E> mapping;\r
- protected InteractiveVtkPanel panel;\r
- \r
- protected MapList<E, vtkProp> nodeToActor = new MapList<E, vtkProp>();\r
- protected Map<vtkProp,E> actorToNode = new HashMap<vtkProp, E>();\r
-\r
- protected ParentNode<E> rootNode;\r
- \r
- public AbstractVTKNodeMap(Session session, IMapping<Object,E> mapping, InteractiveVtkPanel panel, ParentNode<E> rootNode) {\r
- this.session = session;\r
- this.mapping = mapping;\r
- this.panel = panel;\r
- this.rootNode = rootNode;\r
- panel.addListener(this);\r
- mapping.addMappingListener(this);\r
- rootNode.addListener(this);\r
- }\r
- \r
- \r
- protected abstract void addActor(E node);\r
- protected abstract void removeActor(E node);\r
- protected abstract void updateActor(E node,Set<String> ids);\r
- \r
- public void repaint() {\r
- panel.repaint();\r
- }\r
- \r
- public void populate() {\r
- for (E node : rootNode.getNodes()) {\r
- receiveAdd(node, node.getParentRel(),true);\r
- }\r
- repaint();\r
- }\r
- \r
- @Override\r
- public INode getNode(vtkProp prop) {\r
- return actorToNode.get(prop);\r
- }\r
- \r
- @SuppressWarnings("unchecked")\r
- @Override\r
- public Collection<vtkProp> getRenderObjects(INode node) {\r
- return nodeToActor.getValues((E)node);\r
- }\r
- \r
- @SuppressWarnings("unchecked")\r
- @Override\r
- public ParentNode<IG3DNode> getRootNode() {\r
- return (ParentNode<IG3DNode>)rootNode;\r
- }\r
- \r
- \r
- \r
- @Override\r
- public boolean isChangeTracking() {\r
- return changeTracking;\r
- }\r
- \r
- @Override\r
- public void setChangeTracking(boolean enabled) {\r
- changeTracking = enabled;\r
- }\r
- \r
- private boolean changeTracking = true;\r
- \r
- protected Object syncMutex = new Object(); \r
- \r
-\r
- private List<Pair<E,String>> added = new ArrayList<Pair<E,String>>();\r
- private List<Pair<E,String>> removed = new ArrayList<Pair<E,String>>();\r
- //private List<Pair<E,String>> updated = new ArrayList<Pair<E,String>>();\r
- private MapSet<E, String> updated = new MapSet.Hash<E, String>();\r
-\r
- private boolean rangeModified = false;\r
- \r
- @SuppressWarnings("unchecked")\r
- @Override\r
- public void updateRenderObjectsFor(INode node) {\r
- List<vtkProp> toDelete = new ArrayList<vtkProp>();\r
- for (vtkProp prop : nodeToActor.getValues((E)node)) {\r
- if (prop.GetVTKId() != 0) {\r
- panel.GetRenderer().RemoveActor(prop);\r
- //prop.Delete();\r
- toDelete.add(prop);\r
- }\r
- actorToNode.remove(prop);\r
- }\r
- nodeToActor.remove((E)node);\r
- Collection<vtkProp> coll = getActors((E)node);\r
- if (coll == null)\r
- return;\r
- for (vtkProp prop : coll) {\r
- nodeToActor.add((E)node,prop);\r
- actorToNode.put(prop, (E)node);\r
- toDelete.remove(prop);\r
- }\r
- for (vtkProp p : toDelete)\r
- p.Delete();\r
- }\r
- \r
- protected abstract Collection<vtkProp> getActors(E node);\r
- \r
- @SuppressWarnings("unchecked")\r
- private void receiveAdd(E node, String id, boolean db) {\r
- if (DEBUG) System.out.println("receiveAdd " + node + " " + id + " " + db);\r
- synchronized (syncMutex) {\r
- for (Pair<E, String> n : added) {\r
- if (n.first.equals(node))\r
- return;\r
- }\r
- if (changeTracking) {\r
- mapping.rangeModified((E)node.getParent());\r
- }\r
- added.add(new Pair<E, String>(node, id));\r
- rangeModified = true;\r
- }\r
- panel.repaint();\r
- }\r
- \r
- @SuppressWarnings("unchecked")\r
- private void receiveRemove(E node, String id, boolean db) {\r
- if (DEBUG) System.out.println("receiveRemove " + node + " " + id + " " + db);\r
- synchronized (syncMutex) {\r
- for (Pair<E, String> n : removed) {\r
- if (n.first.equals(node))\r
- return;\r
- }\r
- if (changeTracking && !db)\r
- mapping.rangeModified((E)node.getParent());\r
- removed.add(new Pair<E, String>(node, id));\r
- rangeModified = true;\r
- }\r
- panel.repaint();\r
- }\r
- \r
- @SuppressWarnings("unchecked")\r
- private void receiveUpdate(E node, String id, boolean db) {\r
- if (DEBUG) System.out.println("receiveUpdate " + node + " " + id + " " + db);\r
- synchronized (syncMutex) {\r
-// for (Pair<E, String> n : updated) {\r
-// if (n.first.equals(node))\r
-// return;\r
-// }\r
- if (changeTracking && !db)\r
- mapping.rangeModified(node);\r
- //updated.add(new Pair<E, String>(node, id));\r
- updated.add(node, id);\r
- rangeModified = true;\r
- }\r
- panel.repaint();\r
- }\r
- \r
- private boolean graphUpdates = false;\r
- private Set<E> graphModified = new HashSet<E>();\r
- \r
- private boolean requestCommit = false;\r
- \r
- @Override\r
- public void commit() {\r
- requestCommit = true;\r
- }\r
- \r
- protected void doCommit() {\r
- session.asyncRequest(new WriteRequest() {\r
- \r
- @Override\r
- public void perform(WriteGraph graph) throws DatabaseException {\r
- commit(graph);\r
- }\r
- \r
- }, new Callback<DatabaseException>() {\r
- \r
- @Override\r
- public void run(DatabaseException parameter) {\r
- if (parameter != null)\r
- ExceptionUtils.logAndShowError("Cannot commit editor changes", parameter);\r
- }\r
- });\r
- }\r
- \r
- protected void commit(WriteGraph graph) throws DatabaseException {\r
- synchronized(syncMutex) {\r
- if (DEBUG) System.out.println("Commit");\r
- graphUpdates = true;\r
- mapping.updateDomain(graph);\r
- graphUpdates = false;\r
- }\r
- }\r
- \r
- @Override\r
- public void domainModified() {\r
- if (graphUpdates)\r
- return;\r
- if (DEBUG)System.out.println("domainModified");\r
- session.asyncRequest(new ReadRequest() {\r
- \r
- @SuppressWarnings("unchecked")\r
- @Override\r
- public void run(ReadGraph graph) throws DatabaseException {\r
- update(graph);\r
- }\r
- });\r
- \r
- }\r
- \r
- protected void update(ReadGraph graph) throws DatabaseException {\r
- synchronized (syncMutex) {\r
- graphUpdates = true;\r
- for (Object domainObject : mapping.getDomainModified()) {\r
- E rangeObject = mapping.get(domainObject);\r
- if (rangeObject != null)\r
- graphModified.add(rangeObject);\r
- }\r
- mapping.updateRange(graph);\r
- graphModified.clear();\r
- graphUpdates = false;\r
- }\r
- \r
- if (mapping.isRangeModified())\r
- commit();\r
- }\r
- \r
- @Override\r
- public void rangeModified() {\r
- //System.out.println("rangeModified");\r
-\r
- }\r
- \r
- @Override\r
- public void postRender() {\r
- // Commit changes if\r
- // 1. Commit has been requested\r
- // 2. There are no pending changes that should be processed in preRender() \r
- if (requestCommit && !rangeModified) { // FIXME : not thread safe.\r
- requestCommit = false;\r
- doCommit();\r
- }\r
- }\r
- \r
- List<Pair<E, String>> rem = new ArrayList<Pair<E,String>>();\r
- List<Pair<E, String>> add = new ArrayList<Pair<E,String>>();\r
- MapSet<E, String> mod = new MapSet.Hash<E, String>();\r
- Set<E> propagation = new HashSet<E>();\r
- Stack<E> stack = new Stack<E>();\r
- \r
- \r
- @Override\r
- public synchronized void preRender() {\r
- updateCycle();\r
- }\r
- \r
- @SuppressWarnings("unchecked")\r
- protected void updateCycle() {\r
- rem.clear();\r
- add.clear();\r
- mod.clear();\r
- propagation.clear();\r
- \r
- synchronized (syncMutex) {\r
- rem.addAll(removed);\r
- add.addAll(added);\r
- for (E e : updated.getKeys()) {\r
- for (String s : updated.getValues(e)) {\r
- mod.add(e, s);\r
- }\r
- }\r
- \r
- removed.clear();\r
- added.clear();\r
- updated.clear();\r
- }\r
- \r
- for (Pair<E, String> n : rem) {\r
- stopListening(n.first);\r
- removeActor(n.first);\r
- \r
- }\r
- \r
- for (Pair<E, String> n : add) {\r
- addActor(n.first);\r
- listen(n.first);\r
- }\r
- \r
- for (E e : mod.getKeys()) {\r
- Set<String> ids = mod.getValues(e);\r
- if (ids.contains(G3D.URIs.hasPosition) || ids.contains(G3D.URIs.hasOrientation)) {\r
- if (!propagation.contains(e))\r
- propagation.add(e);\r
- }\r
- }\r
-\r
- if (propagation.size() > 0) {\r
- stack.clear();\r
- stack.addAll(propagation);\r
- propagation.clear();\r
- while (!stack.isEmpty()) {\r
- E node = stack.pop();\r
- if (propagation.contains(node))\r
- continue;\r
- propagation.add(node);\r
- for (NodeListener l : node.getListeners()) {\r
- if (l == this) {\r
- //changeTracking = false;\r
- //l.propertyChanged(node, G3D.URIs.hasPosition);\r
- //changeTracking = true;\r
- } else {\r
- l.propertyChanged(node, G3D.URIs.hasWorldPosition);\r
- }\r
- }\r
- if (node instanceof ParentNode) {\r
- stack.addAll(((ParentNode<E>)node).getNodes());\r
- }\r
- }\r
- }\r
- \r
-// synchronized (syncMutex) {\r
-// rem.addAll(removed);\r
-// add.addAll(added);\r
-// //mod.addAll(updated);\r
-// for (E e : updated.getKeys()) {\r
-// for (String s : updated.getValues(e))\r
-// mod.add(e, s);\r
-// }\r
-// \r
-// removed.clear();\r
-// added.clear();\r
-// updated.clear();\r
-// }\r
- \r
- for (E e : mod.getKeys()) {\r
- Set<String> ids = mod.getValues(e);\r
- updateActor(e,ids);\r
- }\r
- \r
- \r
- for (Pair<E, String> n : rem) {\r
- for (NodeListener l : nodeListeners)\r
- l.nodeRemoved(null, n.first, n.second);\r
- }\r
- for (Pair<E, String> n : add) {\r
- for (NodeListener l : nodeListeners)\r
- l.nodeAdded(n.first.getParent(), n.first, n.second);\r
- }\r
-// for (Pair<E, String> n : mod) {\r
-// for (NodeListener l : nodeListeners)\r
-// l.propertyChanged(n.first, n.second);\r
-// }\r
- for (E e : mod.getKeys()) {\r
- for (NodeListener l : nodeListeners)\r
- for (String s : mod.getValues(e))\r
- l.propertyChanged(e, s);\r
- }\r
- synchronized (syncMutex) {\r
- if (added.isEmpty() && removed.isEmpty() && updated.getKeys().size() == 0)\r
- rangeModified = false;\r
- }\r
- }\r
- \r
- @SuppressWarnings("unchecked")\r
- private void listen(INode node) {\r
- node.addListener(this);\r
- if (node instanceof ParentNode<?>) {\r
- ParentNode<INode> parentNode = (ParentNode<INode>)node;\r
- for (INode n : parentNode.getNodes())\r
- listen(n);\r
- }\r
- }\r
- \r
- private void stopListening(INode node) {\r
- node.removeListener(this);\r
- if (node instanceof ParentNode<?>) {\r
- @SuppressWarnings("unchecked")\r
- ParentNode<INode> parentNode = (ParentNode<INode>)node;\r
- for (INode n : parentNode.getNodes())\r
- stopListening(n);\r
- }\r
- }\r
- \r
- @SuppressWarnings("unchecked")\r
- @Override\r
- public void propertyChanged(INode node, String id) {\r
- //receiveUpdate((E)node, id, graphUpdates);\r
- receiveUpdate((E)node, id, graphModified.contains(node));\r
- \r
- }\r
- \r
- @SuppressWarnings("unchecked")\r
- @Override\r
- public <T extends INode> void nodeAdded(ParentNode<T> node, INode child,\r
- String rel) {\r
- if (DEBUG) System.out.println("Node added " + child + " parent " + node);\r
- //receiveAdd((E)child, rel ,graphUpdates);\r
- receiveAdd((E)child, rel ,graphModified.contains(node));\r
- \r
- }\r
- \r
- @SuppressWarnings("unchecked")\r
- @Override\r
- public <T extends INode> void nodeRemoved(ParentNode<T> node, INode child,\r
- String rel) {\r
- if (DEBUG) System.out.println("Node removed " + child + " parent " + node);\r
- //receiveRemove((E)child, rel, graphUpdates);\r
- receiveRemove((E)child, rel, graphModified.contains(node));\r
- \r
- //FIXME : sometimes removed structural models cause ObjMap to add their children again.\r
- // removing the listener here prevents corruption of visual model, but better fix is needed.\r
- stopListening(child);\r
- }\r
- \r
- @Override\r
- public void delete() {\r
- changeTracking = false;\r
- panel.removeListener(this);\r
- mapping.removeMappingListener(this);\r
-\r
- List<E> nodes = new ArrayList<E>(nodeToActor.getKeySize());\r
- nodes.addAll(nodeToActor.getKeys());\r
- for (E node : nodes) {\r
- node.removeListener(this);\r
- removeActor(node);\r
- node.cleanup();\r
- }\r
- for (vtkProp prop : actorToNode.keySet()) {\r
- if (prop.GetVTKId() != 0) \r
- prop.Delete();\r
- }\r
- actorToNode.clear();\r
- nodeToActor.clear();\r
- \r
- }\r
- \r
- \r
- private List<NodeListener> nodeListeners = new ArrayList<NodeListener>();\r
- @Override\r
- public void addListener(NodeListener listener) {\r
- nodeListeners.add(listener);\r
- \r
- }\r
- \r
- @Override\r
- public void removeListener(NodeListener listener) {\r
- nodeListeners.remove(listener);\r
- \r
- }\r
- \r
- public IMapping<Object,E> getMapping() {\r
- return mapping;\r
- }\r
- \r
- \r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2012, 2013 Association for Decentralized Information Management in
+ * Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.g3d.vtk.common;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+import java.util.function.Consumer;
+
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Session;
+import org.simantics.db.UndoContext;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.common.request.ReadRequest;
+import org.simantics.db.common.request.WriteRequest;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.util.Layer0Utils;
+import org.simantics.db.service.UndoRedoSupport;
+import org.simantics.g3d.ontology.G3D;
+import org.simantics.g3d.scenegraph.RenderListener;
+import org.simantics.g3d.scenegraph.base.INode;
+import org.simantics.g3d.scenegraph.base.NodeListener;
+import org.simantics.g3d.scenegraph.base.ParentNode;
+import org.simantics.objmap.exceptions.MappingException;
+import org.simantics.objmap.graph.IMapping;
+import org.simantics.objmap.graph.IMappingListener;
+import org.simantics.utils.datastructures.MapList;
+import org.simantics.utils.datastructures.MapSet;
+import org.simantics.utils.datastructures.Pair;
+import org.simantics.utils.ui.ExceptionUtils;
+
+import vtk.vtkProp;
+
+public abstract class AbstractVTKNodeMap<DBObject,E extends INode> implements VTKNodeMap<DBObject,E>, IMappingListener, RenderListener, NodeListener, UndoRedoSupport.ChangeListener{
+
+ private static final boolean DEBUG = false;
+
+ protected Session session;
+ protected IMapping<DBObject, INode> mapping;
+ protected VtkView view;
+
+ private MapList<E, vtkProp> nodeToActor = new MapList<E, vtkProp>();
+ private Map<vtkProp,E> actorToNode = new HashMap<vtkProp, E>();
+
+ protected ParentNode<E> rootNode;
+
+ protected UndoRedoSupport undoRedoSupport;
+ protected int undoOpCount = 0;
+ protected int redoOpCount = 0;
+ protected boolean runUndo = false;
+ protected boolean runRedo = false;
+ public AbstractVTKNodeMap(Session session, IMapping<DBObject,INode> mapping, VtkView view, ParentNode<E> rootNode) {
+ this.session = session;
+ this.mapping = mapping;
+ this.view = view;
+ this.rootNode = rootNode;
+ view.addListener(this);
+ mapping.addMappingListener(this);
+ rootNode.addListener(this);
+
+ undoRedoSupport = session.getService(UndoRedoSupport.class);
+ undoRedoSupport.subscribe(this);
+ try {
+ UndoContext undoContext = undoRedoSupport.getUndoContext(session);
+ undoOpCount = undoContext.getAll().size();
+ redoOpCount = undoContext.getRedoList().size();
+ } catch(DatabaseException e) {
+ e.printStackTrace();
+ }
+ }
+
+ protected abstract void addActor(E node);
+ protected abstract void removeActor(E node);
+ protected abstract void updateActor(E node,Set<String> ids);
+
+ public void repaint() {
+ view.refresh();
+ }
+
+ public void populate() {
+ for (E node : rootNode.getNodes()) {
+ receiveAdd(node, node.getParentRel(),true);
+ }
+ repaint();
+ }
+
+ @Override
+ public E getNode(vtkProp prop) {
+ return actorToNode.get(prop);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Collection<vtkProp> getRenderObjects(INode node) {
+ return nodeToActor.getValues((E) node);
+ }
+
+ protected <T extends vtkProp> void map(E node, Collection<T> props) {
+ for (vtkProp p : props) {
+ nodeToActor.add(node, p);
+ actorToNode.put(p, node);
+ }
+ }
+
+ protected void removeMap(E node) {
+ Collection<vtkProp> coll = nodeToActor.getValuesUnsafe(node);
+ for (vtkProp p : coll) {
+ actorToNode.remove(p);
+ }
+ nodeToActor.remove(node);
+ }
+
+ @Override
+ public ParentNode<E> getRootNode() {
+ return (ParentNode<E>)rootNode;
+ }
+
+
+
+ @Override
+ public boolean isChangeTracking() {
+ return changeTracking;
+ }
+
+ @Override
+ public void setChangeTracking(boolean enabled) {
+ changeTracking = enabled;
+ }
+
+ private boolean changeTracking = true;
+
+ protected Object syncMutex = new Object();
+
+
+ private List<Pair<E,String>> added = new ArrayList<Pair<E,String>>();
+ private List<Pair<E,String>> removed = new ArrayList<Pair<E,String>>();
+ private MapSet<E, String> updated = new MapSet.Hash<E, String>();
+
+ private boolean rangeModified = false;
+
+ public boolean isRangeModified() {
+ return rangeModified;
+ }
+
+ @Override
+ public void onChanged() {
+ try {
+ UndoContext undoContext = undoRedoSupport.getUndoContext(session);
+ int ucount = undoContext.getAll().size();
+ int rcount = undoContext.getRedoList().size();
+ if (DEBUG) System.out.println("Previous U:" + undoOpCount +" R:" + redoOpCount +" Current U:"+ucount+" R:"+rcount);
+ if (ucount < undoOpCount) {
+ runUndo = true;
+ } else {
+ runUndo = false;
+ }
+ if (!runUndo && rcount > 0)
+ runRedo = true;
+ else
+ runRedo = false;
+ undoOpCount = ucount;
+ redoOpCount = rcount;
+
+ if (DEBUG) System.out.println("Undo " + runUndo + " Redo " + runRedo);
+ } catch (DatabaseException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+
+ }
+
+ @Override
+ public void updateRenderObjectsFor(E node) {
+ List<vtkProp> toDelete = new ArrayList<vtkProp>();
+ view.lock();
+ for (vtkProp prop : nodeToActor.getValues(node)) {
+ if (prop.GetVTKId() != 0) {
+ view.getRenderer().RemoveActor(prop);
+ //prop.Delete();
+ toDelete.add(prop);
+ }
+ actorToNode.remove(prop);
+ }
+ view.unlock();
+ nodeToActor.remove(node);
+ Collection<vtkProp> coll = getActors(node);
+ if (coll != null) {
+ for (vtkProp prop : coll) {
+ nodeToActor.add(node,prop);
+ actorToNode.put(prop, node);
+ toDelete.remove(prop);
+ }
+ }
+ for (vtkProp p : toDelete)
+ p.Delete();
+ }
+
+ protected abstract Collection<vtkProp> getActors(E node);
+
+ @SuppressWarnings("unchecked")
+ private void receiveAdd(E node, String id, boolean db) {
+ if (DEBUG) System.out.println("receiveAdd " + debugString(node) + " " + id + " " + db);
+ synchronized (syncMutex) {
+ for (Pair<E, String> n : added) {
+ if (n.first.equals(node))
+ return;
+ }
+ if (changeTracking) {
+ mapping.rangeModified((E)node.getParent());
+ mapping.rangeModified((E)node);
+ }
+ added.add(new Pair<E, String>(node, id));
+ rangeModified = true;
+ }
+ repaint();
+ }
+
+ @SuppressWarnings("unchecked")
+ private void receiveRemove(E node, String id, boolean db) {
+ if (DEBUG) System.out.println("receiveRemove " + debugString(node) + " " + id + " " + db);
+ synchronized (syncMutex) {
+ for (Pair<E, String> n : removed) {
+ if (n.first.equals(node))
+ return;
+ }
+ if (changeTracking && !db) {
+ mapping.rangeModified((E)node);
+ mapping.rangeModified((E)node.getParent());
+ }
+ removed.add(new Pair<E, String>(node, id));
+ rangeModified = true;
+ }
+ repaint();
+ }
+
+ private void receiveUpdate(E node, String id, boolean db) {
+ if (DEBUG) System.out.println("receiveUpdate " + debugString(node) + " " + id + " " + db);
+ synchronized (syncMutex) {
+// for (Pair<E, String> n : updated) {
+// if (n.first.equals(node))
+// return;
+// }
+ if (changeTracking && !db)
+ mapping.rangeModified(node);
+ //updated.add(new Pair<E, String>(node, id));
+ updated.add(node, id);
+ rangeModified = true;
+ }
+ repaint();
+ }
+
+ private boolean graphUpdates = false;
+ private Set<E> graphModified = new HashSet<E>();
+
+ private boolean requestCommit = false;
+ private String commitMessage = null;
+
+ @Override
+ public void commit(String message) {
+ requestCommit = true;
+ commitMessage = message;
+ }
+
+ protected void doCommit() {
+ session.asyncRequest(new WriteRequest() {
+
+ @Override
+ public void perform(WriteGraph graph) throws DatabaseException {
+ if (DEBUG) System.out.println("Commit " + commitMessage);
+ if (commitMessage != null) {
+ Layer0Utils.addCommentMetadata(graph, commitMessage);
+ graph.markUndoPoint();
+ commitMessage = null;
+ }
+ commit(graph);
+ }
+
+ }, new Consumer<DatabaseException>() {
+
+ @Override
+ public void accept(DatabaseException parameter) {
+ if (parameter != null)
+ ExceptionUtils.logAndShowError("Cannot commit editor changes", parameter);
+ }
+ });
+ }
+
+ protected void commit(WriteGraph graph) throws DatabaseException {
+ synchronized(syncMutex) {
+ if (DEBUG) System.out.println("Commit");
+ graphUpdates = true;
+ mapping.updateDomain(graph);
+ graphUpdates = false;
+ clearDeletes();
+ if (DEBUG) System.out.println("Commit done");
+ }
+ }
+
+
+
+ @Override
+ public void domainModified() {
+ if (graphUpdates)
+ return;
+ if (DEBUG)System.out.println("domainModified");
+ session.asyncRequest(new ReadRequest() {
+
+ @Override
+ public void run(ReadGraph graph) throws DatabaseException {
+ update(graph);
+ }
+ });
+
+ }
+
+ protected void reset(ReadGraph graph) throws MappingException {
+ if (DEBUG) System.out.println("Reset");
+
+ synchronized (syncMutex) {
+ graphUpdates = true;
+ mapping.getRangeModified().clear();
+ for (DBObject o : mapping.getDomain())
+ mapping.domainModified(o);
+ mapping.updateRange(graph);
+ graphModified.clear();
+ graphUpdates = false;
+ }
+ }
+
+ private boolean useFullSyncWithUndo = false;
+
+ protected void update(ReadGraph graph) throws DatabaseException {
+ if (DEBUG) System.out.println("Graph update start");
+ if (runUndo && useFullSyncWithUndo) {
+ reset(graph);
+ } else {
+ synchronized (syncMutex) {
+ graphUpdates = true;
+ for (DBObject domainObject : mapping.getDomainModified()) {
+ @SuppressWarnings("unchecked")
+ E rangeObject = (E) mapping.get(domainObject);
+ if (rangeObject != null)
+ graphModified.add(rangeObject);
+ }
+ mapping.updateRange(graph);
+ graphModified.clear();
+ syncDeletes();
+ clearDeletes();
+ graphUpdates = false;
+ }
+ }
+
+ if (mapping.isRangeModified() && !runUndo && !runRedo)
+ commit((String)null);
+ if (DEBUG) System.out.println("Graph update done");
+ }
+
+ @Override
+ public void rangeModified() {
+ //System.out.println("rangeModified");
+
+ }
+
+ @Override
+ public void postRender() {
+ // Commit changes if
+ // 1. Commit has been requested
+ // 2. There are no pending changes that should be processed in preRender()
+ if (requestCommit && !rangeModified) { // FIXME : not thread safe.
+ requestCommit = false;
+ doCommit();
+ }
+ }
+
+ // Reusable containers for data synchronisation
+ List<Pair<E, String>> rem = new ArrayList<Pair<E,String>>(); // Removed objects
+ List<Pair<E, String>> add = new ArrayList<Pair<E,String>>(); // Added objects
+ MapSet<E, String> mod = new MapSet.Hash<E, String>(); // Modified objects
+ Set<E> propagation = new HashSet<E>(); // Objects with propagated changes
+ Stack<E> stack = new Stack<E>(); // Stack for handling propagation
+ Set<E> delete = Collections.synchronizedSet(new HashSet<E>()); // Objects to be completely deleted
+ Set<E> deleteUC = new HashSet<E>();
+
+ @Override
+ public synchronized void preRender() {
+ updateCycle();
+ }
+
+
+ /**
+ * When objects are removed (either from Java or Graph), after remove processing the Java objects remain in mapping cache.
+ * This causes problems with Undo and Redo, which cause re-using the removed objects from mapping cache.
+ *
+ * This code here synchronizes removed and added objects to collect deletable objects. (a deletable object is one which is removed but not added).
+ *
+ */
+ @SuppressWarnings("unused")
+ protected void syncDeletes() {
+ deleteUC.clear();
+ for (Pair<E, String> n : removed) {
+ deleteUC.add(n.first);
+ }
+ for (Pair<E, String> n : added) {
+ deleteUC.remove(n.first);
+ }
+ if (DEBUG && deleteUC.size() > 0) {
+ System.out.println("Delete sync");
+ for (E n : delete) {
+ System.out.println(debugString(n));
+ }
+ }
+ delete.addAll(deleteUC);
+ deleteUC.clear();
+ }
+
+ /**
+ * Clears deletable objects from mapping cache.
+ */
+ @SuppressWarnings("unused")
+ protected void clearDeletes() {
+ if (DEBUG && delete.size() > 0) System.out.println("Delete");
+ for (E n : delete) {
+ if (DEBUG) System.out.println(debugString(n));
+ mapping.getRange().remove(n);
+ stopListening(n);
+ }
+ delete.clear();
+ }
+
+ protected String debugString(E n) {
+ return n + "@" + Integer.toHexString(n.hashCode());
+ }
+
+ protected boolean filterChange(List<Pair<E,String>> list,E n) {
+ for (int i = list.size()-1; i >= 0; i--) {
+ if (list.get(i).first == n) {
+ list.remove(i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @SuppressWarnings("unchecked")
+ protected void updateCycle() {
+ rem.clear();
+ add.clear();
+ mod.clear();
+ propagation.clear();
+
+
+ synchronized (syncMutex) {
+ // Check for overlapping additions and deletions, prevent deleting objects that are also added and vice versa.
+ Deque<E> stack = new ArrayDeque<E>();
+ for (Pair<E, String> n : added) {
+ stack.add(n.first);
+ }
+ while (!stack.isEmpty()) {
+ E n = stack.pop();
+ boolean conflict = filterChange(removed, n);
+ if (conflict) {
+ System.out.println("Prevent removing " + n);
+ //filterChange(added, n)
+ if (filterChange(added, n))
+ System.out.println("Prevent adding " + n);
+ }
+ if (n instanceof ParentNode) {
+ ParentNode<INode> pn = (ParentNode<INode>)n;
+ for (INode cn : pn.getNodes()) {
+ stack.push((E)cn);
+ }
+ }
+ }
+
+ rem.addAll(removed);
+ add.addAll(added);
+ for (E e : updated.getKeys()) {
+ for (String s : updated.getValues(e)) {
+ mod.add(e, s);
+ }
+ }
+ syncDeletes();
+ removed.clear();
+ added.clear();
+ updated.clear();
+ }
+
+
+
+ for (Pair<E, String> n : rem) {
+ stopListening(n.first);
+ removeActor(n.first);
+ }
+
+ for (Pair<E, String> n : add) {
+ addActor(n.first);
+ listen(n.first);
+ }
+
+ for (E e : mod.getKeys()) {
+ Set<String> ids = mod.getValues(e);
+ if (ids.contains(G3D.URIs.hasPosition) || ids.contains(G3D.URIs.hasOrientation)) {
+ if (!propagation.contains(e))
+ propagation.add(e);
+ }
+ }
+
+ if (propagation.size() > 0) {
+ stack.clear();
+ stack.addAll(propagation);
+ propagation.clear();
+ while (!stack.isEmpty()) {
+ E node = stack.pop();
+ if (propagation.contains(node))
+ continue;
+ propagation.add(node);
+ for (NodeListener l : node.getListeners()) {
+ if (l == this) {
+ //changeTracking = false;
+ //l.propertyChanged(node, G3D.URIs.hasPosition);
+ //changeTracking = true;
+ } else {
+ l.propertyChanged(node, G3D.URIs.hasWorldPosition);
+ }
+ }
+ if (node instanceof ParentNode) {
+ stack.addAll(((ParentNode<E>)node).getNodes());
+ }
+ }
+ }
+
+// synchronized (syncMutex) {
+// rem.addAll(removed);
+// add.addAll(added);
+// //mod.addAll(updated);
+// for (E e : updated.getKeys()) {
+// for (String s : updated.getValues(e))
+// mod.add(e, s);
+// }
+//
+// removed.clear();
+// added.clear();
+// updated.clear();
+// }
+
+ for (E e : mod.getKeys()) {
+ Set<String> ids = mod.getValues(e);
+ updateActor(e,ids);
+ }
+
+
+ for (Pair<E, String> n : rem) {
+ for (NodeListener l : nodeListeners)
+ l.nodeRemoved(null, n.first, n.second);
+ }
+ for (Pair<E, String> n : add) {
+ for (NodeListener l : nodeListeners)
+ l.nodeAdded(n.first.getParent(), n.first, n.second);
+ }
+// for (Pair<E, String> n : mod) {
+// for (NodeListener l : nodeListeners)
+// l.propertyChanged(n.first, n.second);
+// }
+ for (E e : mod.getKeys()) {
+ for (NodeListener l : nodeListeners)
+ for (String s : mod.getValues(e))
+ l.propertyChanged(e, s);
+ }
+
+ synchronized (syncMutex) {
+ if (added.isEmpty() && removed.isEmpty() && updated.getKeys().size() == 0)
+ rangeModified = false;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private void listen(INode node) {
+ node.addListener(this);
+ if (node instanceof ParentNode<?>) {
+ ParentNode<INode> parentNode = (ParentNode<INode>)node;
+ for (INode n : parentNode.getNodes())
+ listen(n);
+ }
+ }
+
+ private void stopListening(INode node) {
+ node.removeListener(this);
+ if (node instanceof ParentNode<?>) {
+ @SuppressWarnings("unchecked")
+ ParentNode<INode> parentNode = (ParentNode<INode>)node;
+ for (INode n : parentNode.getNodes())
+ stopListening(n);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void propertyChanged(INode node, String id) {
+ //receiveUpdate((E)node, id, graphUpdates);
+ receiveUpdate((E)node, id, graphModified.contains(node));
+
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T extends INode> void nodeAdded(ParentNode<T> node, INode child,
+ String rel) {
+ if (DEBUG) System.out.println("Node added " + child + " parent " + node);
+ //receiveAdd((E)child, rel ,graphUpdates);
+ receiveAdd((E)child, rel ,graphModified.contains(node));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T extends INode> void nodeRemoved(ParentNode<T> node, INode child,
+ String rel) {
+ if (DEBUG) System.out.println("Node removed " + child + " parent " + node);
+ //receiveRemove((E)child, rel, graphUpdates);
+ receiveRemove((E)child, rel, graphModified.contains(node));
+
+ //FIXME : 1. sometimes removed structural models cause ObjMap to add their children again.
+ // removing the listener here prevents corruption of visual model, but better fix is needed.
+ // 2. detach causes nodeRemoved event, which then causes other critical events to be missed. Took out th
+ //stopListening(child);
+ }
+
+ @Override
+ public void delete() {
+ if (undoRedoSupport != null)
+ undoRedoSupport.cancel(this);
+
+ changeTracking = false;
+ view.removeListener(this);
+ mapping.removeMappingListener(this);
+
+ List<E> nodes = new ArrayList<E>(nodeToActor.getKeySize());
+ nodes.addAll(nodeToActor.getKeys());
+ for (E node : nodes) {
+ node.removeListener(this);
+ removeActor(node);
+ node.cleanup();
+ }
+ for (vtkProp prop : actorToNode.keySet()) {
+ if (prop.GetVTKId() != 0)
+ prop.Delete();
+ }
+ actorToNode.clear();
+ nodeToActor.clear();
+
+ }
+
+
+ private List<NodeListener> nodeListeners = new ArrayList<NodeListener>();
+ @Override
+ public void addListener(NodeListener listener) {
+ nodeListeners.add(listener);
+
+ }
+
+ @Override
+ public void removeListener(NodeListener listener) {
+ nodeListeners.remove(listener);
+
+ }
+
+ public IMapping<DBObject,INode> getMapping() {
+ return mapping;
+ }
+
+
+}