/** * $Id: mxSharedGraphModel.java,v 1.1 2012/11/15 13:26:50 gaudenz Exp $ * Copyright (c) 2007-2012, JGraph Ltd */ package com.mxgraph.sharing; import java.util.LinkedList; import org.w3c.dom.Node; import com.mxgraph.io.mxCodec; import com.mxgraph.model.mxGraphModel; import com.mxgraph.model.mxGraphModel.mxChildChange; import com.mxgraph.model.mxICell; import com.mxgraph.model.mxIGraphModel.mxAtomicGraphModelChange; import com.mxgraph.util.mxEvent; import com.mxgraph.util.mxEventObject; import com.mxgraph.util.mxUndoableEdit; import com.mxgraph.util.mxXmlUtils; /** * Implements a diagram that may be shared among multiple sessions. */ public class mxSharedGraphModel extends mxSharedState { /** * */ protected mxGraphModel model; /** * */ protected mxCodec codec = new mxCodec() { public Object lookup(String id) { return model.getCell(id); } }; /** * Whether remote changes should be significant in the * local command history. Default is true. */ protected boolean significantRemoteChanges = true; /** * Constructs a new diagram with the given model. * * @param model Initial model of the diagram. */ public mxSharedGraphModel(mxGraphModel model) { super(null); // Overrides getState this.model = model; } /** * @return the model */ public mxGraphModel getModel() { return model; } /** * @return the significantRemoteChanges */ public boolean isSignificantRemoteChanges() { return significantRemoteChanges; } /** * @param significantRemoteChanges the significantRemoteChanges to set */ public void setSignificantRemoteChanges(boolean significantRemoteChanges) { this.significantRemoteChanges = significantRemoteChanges; } /** * Returns the initial state of the diagram. */ public String getState() { return mxXmlUtils.getXml(codec.encode(model)); } /** * */ public synchronized void addDelta(String edits) { // Edits are not added to the history. They are sent straight out to // all sessions and the model is updated so the next session will get // these edits via the new state of the model in getState. } /** * */ protected String processEdit(Node node) { mxAtomicGraphModelChange[] changes = decodeChanges(node.getFirstChild()); if (changes.length > 0) { mxUndoableEdit edit = createUndoableEdit(changes); // No notify event here to avoid the edit from being encoded and transmitted // LATER: Remove changes property (deprecated) model.fireEvent(new mxEventObject(mxEvent.CHANGE, "edit", edit, "changes", changes)); model.fireEvent(new mxEventObject(mxEvent.UNDO, "edit", edit)); fireEvent(new mxEventObject(mxEvent.FIRED, "edit", edit)); } return super.processEdit(node); } /** * Creates a new mxUndoableEdit that implements the notify function to fire * a change and notify event via the model. */ protected mxUndoableEdit createUndoableEdit( mxAtomicGraphModelChange[] changes) { mxUndoableEdit edit = new mxUndoableEdit(this, significantRemoteChanges) { public void dispatch() { // LATER: Remove changes property (deprecated) ((mxGraphModel) source).fireEvent(new mxEventObject( mxEvent.CHANGE, "edit", this, "changes", changes)); ((mxGraphModel) source).fireEvent(new mxEventObject( mxEvent.NOTIFY, "edit", this, "changes", changes)); } }; for (int i = 0; i < changes.length; i++) { edit.add(changes[i]); } return edit; } /** * Adds removed cells to the codec object lookup for references to the removed * cells after this point in time. */ protected mxAtomicGraphModelChange[] decodeChanges(Node node) { // Updates the document in the existing codec codec.setDocument(node.getOwnerDocument()); LinkedList<mxAtomicGraphModelChange> changes = new LinkedList<mxAtomicGraphModelChange>(); while (node != null) { Object change; if (node.getNodeName().equals("mxRootChange")) { // Handles the special case were no ids should be // resolved in the existing model. This change will // replace all registered ids and cells from the // model and insert a new cell hierarchy instead. mxCodec tmp = new mxCodec(node.getOwnerDocument()); change = tmp.decode(node); } else { change = codec.decode(node); } if (change instanceof mxAtomicGraphModelChange) { mxAtomicGraphModelChange ac = (mxAtomicGraphModelChange) change; ac.setModel(model); ac.execute(); // Workaround for references not being resolved if cells have // been removed from the model prior to being referenced. This // adds removed cells in the codec object lookup table. if (ac instanceof mxChildChange && ((mxChildChange) ac).getParent() == null) { cellRemoved(((mxChildChange) ac).getChild()); } changes.add(ac); } node = node.getNextSibling(); } return changes.toArray(new mxAtomicGraphModelChange[changes.size()]); } /** * Adds removed cells to the codec object lookup for references to the removed * cells after this point in time. */ public void cellRemoved(Object cell) { codec.putObject(((mxICell) cell).getId(), cell); int childCount = model.getChildCount(cell); for (int i = 0; i < childCount; i++) { cellRemoved(model.getChildAt(cell, i)); } } }