]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.spreadsheet.graph/src/org/simantics/spreadsheet/graph/GraphUI.java
Some fixes for resource cleaning spreadsheets in simupedia
[simantics/platform.git] / bundles / org.simantics.spreadsheet.graph / src / org / simantics / spreadsheet / graph / GraphUI.java
index 9a3e8732679d284b4a30abfdbcfc274da9a708e7..5850eb84750d7303e64a33abac540fc850c7e4ab 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in 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.spreadsheet.graph;\r
-\r
-import java.util.ArrayList;\r
-import java.util.Collection;\r
-import java.util.Collections;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.function.Consumer;\r
-\r
-import org.eclipse.e4.core.contexts.IEclipseContext;\r
-import org.eclipse.e4.ui.di.UISynchronize;\r
-import org.eclipse.jface.dialogs.Dialog;\r
-import org.eclipse.ui.PlatformUI;\r
-import org.simantics.Simantics;\r
-import org.simantics.databoard.Bindings;\r
-import org.simantics.databoard.binding.Binding;\r
-import org.simantics.databoard.binding.mutable.MutableVariant;\r
-import org.simantics.databoard.binding.mutable.Variant;\r
-import org.simantics.db.AsyncReadGraph;\r
-import org.simantics.db.ReadGraph;\r
-import org.simantics.db.RequestProcessor;\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.WriteGraph;\r
-import org.simantics.db.common.procedure.adapter.AsyncListenerSupport;\r
-import org.simantics.db.common.procedure.adapter.ListenerSupport;\r
-import org.simantics.db.common.procedure.adapter.SyncListenerSupport;\r
-import org.simantics.db.common.procedure.adapter.TransientCacheAsyncListener;\r
-import org.simantics.db.common.procedure.single.SingleSetSyncListenerDelegate;\r
-import org.simantics.db.common.request.ReadRequest;\r
-import org.simantics.db.common.request.ResourceRead;\r
-import org.simantics.db.common.request.UnaryRead;\r
-import org.simantics.db.common.request.UniqueRead;\r
-import org.simantics.db.common.request.WriteRequest;\r
-import org.simantics.db.common.request.WriteResultRequest;\r
-import org.simantics.db.common.session.SessionEventListenerAdapter;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.layer0.StandardRealm;\r
-import org.simantics.db.layer0.request.PossibleURIVariable;\r
-import org.simantics.db.layer0.request.VariableName;\r
-import org.simantics.db.layer0.request.VariableRead;\r
-import org.simantics.db.layer0.variable.ProxyVariables;\r
-import org.simantics.db.layer0.variable.Variable;\r
-import org.simantics.db.layer0.variable.Variables;\r
-import org.simantics.db.procedure.SyncListener;\r
-import org.simantics.db.request.Write;\r
-import org.simantics.db.service.SessionEventSupport;\r
-import org.simantics.layer0.Layer0;\r
-import org.simantics.spreadsheet.Adaptable;\r
-import org.simantics.spreadsheet.CellEditor;\r
-import org.simantics.spreadsheet.ClientModel;\r
-import org.simantics.spreadsheet.ClientModel.OperationMode;\r
-import org.simantics.spreadsheet.SheetCommands;\r
-import org.simantics.spreadsheet.common.logging.Logger;\r
-import org.simantics.spreadsheet.event.model.RemoveCellHandler;\r
-import org.simantics.spreadsheet.resource.SpreadsheetResource;\r
-import org.simantics.ui.selection.WorkbenchSelectionUtils;\r
-import org.simantics.utils.datastructures.Pair;\r
-import org.simantics.utils.strings.AlphanumComparator;\r
-import org.simantics.utils.threads.logger.ITask;\r
-import org.simantics.utils.threads.logger.ThreadLogger;\r
-\r
-import gnu.trove.map.hash.THashMap;\r
-import gnu.trove.map.hash.TObjectIntHashMap;\r
-\r
-class FilteredVariableProperties extends UnaryRead<Variable, Collection<Pair<String,Variable>>> {\r
-\r
-       final static String CLASSIFICATION = SpreadsheetResource.URIs.Attribute;\r
-       \r
-    public FilteredVariableProperties(Variable variable) {\r
-        super(variable);\r
-    }\r
-\r
-    @Override\r
-    public Collection<Pair<String,Variable>> perform(ReadGraph graph) throws DatabaseException {\r
-       ArrayList<Pair<String,Variable>> result = new ArrayList<Pair<String,Variable>>();\r
-       for(Variable var : parameter.getProperties(graph, CLASSIFICATION)) {\r
-               String name = var.getName(graph);\r
-               String uri = var.getURI(graph);\r
-               result.add(Pair.make(name, var));\r
-               Variable expression = var.getPossibleProperty(graph, "expression");\r
-               if(expression != null)\r
-                   result.add(Pair.make(name + "#expression", expression));\r
-               Variable editable = var.getPossibleProperty(graph, "editable");\r
-            if(editable != null)\r
-                result.add(Pair.make(name + "#editable", editable));\r
-       }\r
-       return result;\r
-    }\r
-\r
-}\r
-\r
-public class GraphUI implements Adaptable, ListenerSupport, AsyncListenerSupport, SyncListenerSupport {\r
-\r
-       final public static boolean DEBUG = false;\r
-       \r
-    final private RequestProcessor processor;\r
-    \r
-    private CellEditor<Write> cellEditor;\r
-    \r
-    private Variable run;\r
-    private ClientModel client;\r
-\r
-    private Map<String, PropertyListener> listenerCache = new THashMap<>();\r
-\r
-    public GraphUI(RequestProcessor processor) {\r
-        this.processor = processor;\r
-    }\r
-    \r
-    public void addCell(ReadGraph graph, Pair<String, Variable> child, final ClientModel client) throws DatabaseException {\r
-\r
-               if(DEBUG) System.out.println("GraphUI adds cell  " + child.second.getURI(graph));\r
-\r
-       final String childName = child.second.getName(graph);\r
-       Boolean immutable = child.second.getPossiblePropertyValue(graph, "immutable", Bindings.BOOLEAN);\r
-       if(immutable != null && immutable) {\r
-               Collection<Variable> properties = child.second.getProperties(graph, FilteredVariableProperties.CLASSIFICATION);\r
-               addProperties(graph, properties, client, childName);\r
-       } else {\r
-           PropertyListener listener = listenerCache.get(child.first); \r
-           if (listener == null) {\r
-                   listener = propertyListener(client, childName);\r
-                   listenerCache.put(child.first, listener);\r
-           }\r
-               graph.asyncRequest(new FilteredVariableProperties(child.second), listener);\r
-       }\r
-\r
-    }\r
-\r
-    public void removeCell(ReadGraph graph, Pair<String, Variable> child, final ClientModel client) throws DatabaseException {\r
-\r
-               if(DEBUG) System.out.println("GraphUI removed cell " + child.first);\r
-               \r
-               client.clear(child.first);\r
-               PropertyListener listener = listenerCache.remove(child.first);\r
-               if (listener != null)\r
-                   listener.dispose();\r
-\r
-    }\r
-\r
-    public void loadCells(ReadGraph graph, Variable container, boolean immutable, final ClientModel client) throws DatabaseException {\r
-       \r
-               if(DEBUG) System.out.println("GraphUI loads cells from " + container.getURI(graph));\r
-               \r
-               if(immutable) {\r
-                       for(Pair<String, Variable> cell : graph.syncRequest(new Cells(container), TransientCacheAsyncListener.<Collection<Pair<String, Variable>>>instance())) { \r
-                               addCell(graph, cell, client);\r
-                       }\r
-               } else {\r
-               graph.syncRequest(new Cells(container), new SingleSetSyncListenerDelegate<Pair<String, Variable>>(GraphUI.this) {\r
-       \r
-                   @Override\r
-                   public void add(ReadGraph graph, final Pair<String, Variable> child) throws DatabaseException {\r
-                       addCell(graph, child, client);\r
-                   }\r
-       \r
-                   @Override\r
-                   public void remove(ReadGraph graph, final Pair<String, Variable> child) throws DatabaseException {\r
-                       removeCell(graph, child, client);\r
-                   }\r
-               });\r
-               }\r
-       \r
-    }\r
-    \r
-    private SessionEventListenerAdapter listener;\r
-    \r
-    private String currentSource;\r
-\r
-    private boolean disposed;\r
-    \r
-    public Resource load(final Variable variable, final ClientModel client) throws DatabaseException {\r
-       \r
-//        for (PropertyListener listener : listenerCache.values())\r
-//            listener.dispose();\r
-//        \r
-//        listenerCache.clear();\r
-        \r
-        \r
-        \r
-       assert(variable != null);\r
-       \r
-       this.run = variable;\r
-       this.client = client;\r
-       \r
-       SessionEventSupport support = processor.getService(SessionEventSupport.class);\r
-       \r
-       for (PropertyListener listener : listenerCache.values()) {\r
-           listener.dispose();\r
-       }\r
-       listenerCache.clear();\r
-       \r
-       if(listener != null)\r
-               support.removeListener(listener);\r
-       \r
-       listener = new SessionEventListenerAdapter() {\r
-               \r
-               @Override\r
-               public void writeTransactionFinished() {\r
-                       client.flush();\r
-               }\r
-               \r
-       }; \r
-       \r
-       support.addListener(listener);\r
-\r
-        this.cellEditor = processor.sync(new VariableRead<CellEditor>(variable) {\r
-\r
-                       @Override\r
-                       public CellEditor perform(ReadGraph graph) throws DatabaseException {\r
-                               SpreadsheetResource SHEET = SpreadsheetResource.getInstance(graph);\r
-                               return variable.getPropertyValue(graph, SHEET.cellEditor);\r
-                       }\r
-               \r
-        });\r
-       \r
-               final ITask task = ThreadLogger.getInstance().begin("GraphUI.init");\r
-\r
-               client.clearAll();\r
-\r
-               Map<String,Variable> sources = processor.syncRequest(new Sources(variable));\r
-\r
-               List<String> sheetList = processor.syncRequest(new Sheets(variable));\r
-               String currentSheet = processor.syncRequest(new VariableName(variable));\r
-               \r
-               Map<String, Resource> stateList = processor.syncRequest(new SpreadsheetStates(variable));\r
-\r
-               if(currentSource == null) currentSource = "Sheet";\r
-               \r
-               ArrayList<String> sourceList = new ArrayList<String>(sources.keySet());\r
-               \r
-               Collections.sort(sourceList, AlphanumComparator.CASE_INSENSITIVE_COMPARATOR);\r
-               if(!sourceList.contains(currentSource)) sourceList.add(currentSource);\r
-               \r
-               client.setProperty(ClientModel.SOURCES, ClientModel.SOURCES_AVAILABLE, sourceList.toArray(new String[sourceList.size()]));\r
-               client.setProperty(ClientModel.SOURCES, ClientModel.SOURCES_CURRENT, currentSource);\r
-\r
-               client.setProperty(ClientModel.SHEETS, ClientModel.SHEETS_AVAILABLE, sheetList.toArray(new String[sheetList.size()]));\r
-               client.setProperty(ClientModel.SHEETS, ClientModel.SHEETS_CURRENT, currentSheet);\r
-               \r
-               client.setProperty(ClientModel.STATES, ClientModel.STATES_AVAILABLE, stateList.keySet().toArray(new String[stateList.size()]));\r
-               \r
-               client.setProperty(ClientModel.CONTEXT, ClientModel.CONTEXT_CURRENT, variable);\r
-               \r
-               client.setProperty(ClientModel.MODE, ClientModel.MODE_CURRENT, OperationMode.OPERATION);\r
-               \r
-               String currentState = processor.syncRequest(new UniqueRead<String>() {\r
-\r
-            @Override\r
-            public String perform(ReadGraph graph) throws DatabaseException {\r
-                Resource book = variable.getParent(graph).getRepresents(graph);\r
-                Resource ic = graph.getPossibleObject(book, SpreadsheetResource.getInstance(graph).Book_HasDefaultInitialCondition);\r
-                if (ic == null)\r
-                       return "";\r
-                return graph.getRelatedValue2(ic, Layer0.getInstance(graph).HasName, Bindings.STRING);\r
-            }\r
-        });\r
-               \r
-               client.setProperty(ClientModel.STATES, ClientModel.STATES_CURRENT, currentState);\r
-\r
-        processor.syncRequest(new ReadRequest() {\r
-\r
-            @Override\r
-            public void run(ReadGraph graph) throws DatabaseException {\r
-\r
-               loadCells(graph, variable, false, client);\r
-\r
-               graph.syncRequest(new Ranges(variable), new SingleSetSyncListenerDelegate<Variable>(GraphUI.this) {\r
-\r
-                    @Override\r
-                    public void add(ReadGraph graph, final Variable range) throws DatabaseException {\r
-\r
-                               if(DEBUG) System.out.println("GraphUI adds range  " + range.getURI(graph));\r
-\r
-                       Boolean immutable = range.getPossiblePropertyValue(graph, "immutable", Bindings.BOOLEAN);\r
-                       loadCells(graph, range, immutable != null && immutable, client);\r
-\r
-                    }\r
-\r
-                    @Override\r
-                    public void remove(ReadGraph graph, final Variable range) throws DatabaseException {\r
-\r
-                    }\r
-                    \r
-               });\r
-               \r
-               \r
-               graph.syncRequest(new SheetLines(variable), new SingleSetSyncListenerDelegate<Variable>(GraphUI.this) {\r
-\r
-                    @Override\r
-                    public void add(ReadGraph graph, final Variable range) throws DatabaseException {\r
-\r
-                               if(DEBUG) System.out.println("GraphUI adds line  " + range.getURI(graph));\r
-\r
-                       Boolean immutable = range.getPossiblePropertyValue(graph, "immutable", Bindings.BOOLEAN);\r
-                       loadCells(graph, range, immutable != null && immutable, client);\r
-\r
-                    }\r
-\r
-                    @Override\r
-                    public void remove(ReadGraph graph, final Variable range) throws DatabaseException {\r
-\r
-                    }\r
-               });\r
-\r
-            }\r
-\r
-//            @Override\r
-//            public void remove(ReadGraph graph, Variable child) throws DatabaseException {\r
-//\r
-//             String location = locations.get(cellResource);\r
-//             assert(location != null);\r
-//\r
-//             client.setProperty(location, "Label", null);\r
-//             client.setProperty(location, "Expression", null);\r
-//\r
-//            }\r
-\r
-        });\r
-        \r
-\r
-               task.finish();\r
-               client.flush();\r
-               \r
-               return null;\r
-\r
-    }\r
-    \r
-    private static class PropertyListener extends SingleSetSyncListenerDelegate<Pair<String,Variable>> {\r
-\r
-        private ClientModel client;\r
-        private String childName;\r
-        private boolean listenerDisposed;\r
-\r
-        public PropertyListener(AsyncListenerSupport support, ClientModel client, String childName) {\r
-            super(support);\r
-            this.client = client;\r
-            this.childName = childName;\r
-        }\r
-        \r
-        @Override\r
-        public void add(ReadGraph graph, final Pair<String,Variable> property) throws DatabaseException {\r
-\r
-            if(DEBUG)\r
-                System.out.println("GraphUI adds property  " + property.second.getURI(graph));\r
-\r
-            graph.asyncRequest(new CellValue(property.second), new SyncListener<Object>() {\r
-\r
-                @Override\r
-                public void execute(ReadGraph graph, final Object value) throws DatabaseException {\r
-\r
-                    String propertyName = property.first;\r
-\r
-                    if(DEBUG)\r
-                        System.out.println("GraphUI detected content change(1) at  " + childName + " - " + propertyName + " -> " + value);\r
-                    client.setProperty(childName, propertyName, value);\r
-                    \r
-                }\r
-\r
-                @Override\r
-                public void exception(ReadGraph graph, Throwable throwable) throws DatabaseException {\r
-                    \r
-                    Logger.defaultLogError(throwable);\r
-                    \r
-                    String propertyName = property.first;\r
-                    if("content".equals(propertyName)) {\r
-                        if(throwable == null) throwable = new Exception();\r
-                        String message = throwable.getMessage();\r
-                        if(message == null) message = throwable.toString();\r
-                        client.setProperty(childName, propertyName, Variant.ofInstance(message));\r
-                    } else {\r
-                        client.setProperty(childName, propertyName, null);\r
-                    }\r
-                    \r
-                }\r
-\r
-                @Override\r
-                public boolean isDisposed() {\r
-                    return listenerDisposed;\r
-                }\r
-\r
-            });\r
-        }\r
-\r
-        public void dispose() {\r
-            listenerDisposed = true;\r
-        }\r
-        \r
-        @Override\r
-        public String toString() {\r
-            return super.toString() + ":" + childName;\r
-        }\r
-        \r
-    }\r
-    \r
-    private PropertyListener propertyListener(final ClientModel client, final String childName) {\r
-       return new PropertyListener(this, client, childName);\r
-    }\r
-    \r
-    private void addProperties(ReadGraph graph, final Collection<Variable> properties, final ClientModel client, final String childName) throws DatabaseException {\r
-\r
-       for(Variable property : properties) {\r
-               \r
-               if(DEBUG) System.out.println("GraphUI adds immutable property  " + property.getURI(graph));\r
-\r
-               final String propertyName = property.getName(graph);\r
-               \r
-               Object value = property.getValue(graph);\r
-\r
-               if(DEBUG) System.out.println("GraphUI detected change at  " + childName + " - " + propertyName + " -> " + value);\r
-               client.setProperty(childName, propertyName, value);\r
-               \r
-               String expression = property.getPossiblePropertyValue(graph, "expression", Bindings.STRING);\r
-               if(expression != null) {\r
-                       if(DEBUG) System.out.println("GraphUI detected change at  " + childName + " - " + (propertyName + "#expression") + " -> " + value);\r
-                       client.setProperty(childName, propertyName + "#expression", expression);\r
-               }\r
-               \r
-            Boolean editable = property.getPossiblePropertyValue(graph, "editable", Bindings.STRING);\r
-            if(editable != null) {\r
-                if(DEBUG) System.out.println("GraphUI detected change at  " + childName + " - " + (propertyName + "#editable") + " -> " + value);\r
-                client.setProperty(childName, propertyName + "#editable", editable);\r
-            }\r
-       \r
-       }\r
-\r
-    }\r
-    \r
-    @SuppressWarnings("unchecked")\r
-    @Override\r
-    public <T> T getAdapter(Class<T> clazz) {\r
-\r
-       if(Variable.class == clazz) {\r
-\r
-               return (T)run;\r
-               \r
-       } else if(RemoveCellHandler.class == clazz) {\r
-               \r
-            return (T) new RemoveCellHandler() {\r
-\r
-                               @Override\r
-                               public void handle(final String location) {\r
-                                       \r
-                               processor.asyncRequest(new ReadRequest() {\r
-\r
-                                   @Override\r
-                                   public void run(ReadGraph graph) throws DatabaseException {\r
-                                       \r
-                                                       Variable cellVariable = run.getPossibleChild(graph, location);\r
-                                                       if(cellVariable != null) {\r
-                                                               final Resource config = cellVariable.getPossiblePropertyValue(graph, "Represents");\r
-                                                               if(config != null) {\r
-                                                                       \r
-                                                                       graph.asyncRequest(new WriteRequest() {\r
-\r
-                                                                               @Override\r
-                                                                               public void perform(WriteGraph graph) throws DatabaseException {\r
-                                                                                       \r
-                                                                                       Layer0 l0 = Layer0.getInstance(graph);\r
-//                                                                                     SpreadsheetResource sr = SpreadsheetResource.getInstance(graph);\r
-                                                                                       graph.deny(config, l0.PartOf);\r
-//                                                                                     graph.deny(config, sr.RowOf);\r
-//                                                                                     graph.deny(config, sr.ColumnOf);\r
-                                                                                       \r
-                                                                               }\r
-                                                                               \r
-                                                                       });\r
-                                                                       \r
-                                                               }\r
-                                                       }\r
-                                                       \r
-                                   }\r
-                                   \r
-                               });\r
-                                       \r
-                               }\r
-               \r
-            };\r
-\r
-       } else if(CellEditor.class == clazz) {\r
-       \r
-               return (T)new CellEditor<Write>() {\r
-\r
-                               @Override\r
-                               public <T> void edit(Transaction<Write> transaction, String location, String property, T value, Binding binding, Consumer<?> callback) {\r
-                                       \r
-                                   if (ClientModel.ITERATION_ENABLED.equals(location)) {\r
-                                       Simantics.getSession().asyncRequest(new ReadRequest() {\r
-                                                       @Override\r
-                                                       public void run(ReadGraph graph) throws DatabaseException {\r
-                                                               getBook(graph).setIterationEnabled((boolean)value);\r
-                                                       }\r
-                                               });\r
-                                       return;\r
-                                   }\r
-                                   \r
-                                   if (ClientModel.MODE.equals(location)) {\r
-                                       if (ClientModel.MODE_CURRENT.equals(property)) {\r
-                                           client.setProperty(location, property, value);\r
-                                           client.flush();\r
-                                           return;\r
-                                       }\r
-                                   }\r
-                                   \r
-                                   if (ClientModel.CONTEXT.equals(location)) {\r
-                                       if(ClientModel.CONTEXT_CURRENT.equals(property)) {\r
-                            if(value instanceof String) {\r
-                                try {\r
-                                    Variable newContext = processor.syncRequest(new UnaryRead<String, Variable>((String)value) {\r
-\r
-                                        @Override\r
-                                        public Variable perform(ReadGraph graph) throws DatabaseException {\r
-                                      \r
-                                            String sheetName = run.getName(graph);\r
-                                            \r
-                                            Variable book = Variables.getContext(graph, run);\r
-                                            Resource bookResource = book.getRepresents(graph);\r
-                                            \r
-                                            Variable input = Variables.getVariable(graph, parameter);\r
-                                            Variable proxy = ProxyVariables.makeProxyVariable(graph, Variables.getVariable(graph, bookResource), input);\r
-                                            \r
-                                            return proxy.getChild(graph, sheetName);\r
-                                            \r
-//                                            return variable.getParent(graph).getChild(graph, parameter);\r
-                                        }\r
-\r
-                                    });\r
-                                    \r
-                                    load(newContext, client);\r
-                                    return;\r
-                                } catch (DatabaseException e) {\r
-                                    Logger.defaultLogError(e);\r
-                                }\r
-                            }\r
-                                       }\r
-                                   }\r
-                                   \r
-                                       if(ClientModel.SHEETS.equals(location)) {\r
-                                               if(ClientModel.SHEETS_CURRENT.equals(property)) {\r
-                                                       \r
-                                                       if(value instanceof String) {\r
-\r
-                                                               try {\r
-\r
-                                                                       Variable newInput = processor.syncRequest(new UnaryRead<String, Variable>((String)value) {\r
-\r
-                                                                               @Override\r
-                                                                               public Variable perform(ReadGraph graph) throws DatabaseException {\r
-                                                                                       return run.getParent(graph).getChild(graph, parameter);\r
-                                                                               }\r
-\r
-                                                                       });\r
-\r
-                                                                       load(newInput, client);\r
-                                                                       return;\r
-                                                               } catch (DatabaseException e) {\r
-                                                                       Logger.defaultLogError(e);\r
-                                                               }\r
-                                                       }\r
-                                               }\r
-                                       }\r
-                                       \r
-                   if(ClientModel.STATES.equals(location)) {\r
-                        if(ClientModel.STATES_CURRENT.equals(property)) {\r
-                            if(value instanceof String) {\r
-                                final String parameter = (String) value;\r
-                                try {\r
-                                    \r
-                                    String uri = processor.syncRequest(new WriteResultRequest<String>() {\r
-                \r
-                                        @Override\r
-                                        public String perform(WriteGraph graph) throws DatabaseException {\r
-                                            \r
-                                            Map<String, Resource> states = graph.syncRequest(new SpreadsheetStates(run));\r
-                                            \r
-                                            Resource state = null;\r
-                                            for (Map.Entry<String, Resource> entry : states.entrySet()) {\r
-                                                if (entry.getKey().equals(parameter)) {\r
-                                                    state = entry.getValue();\r
-                                                    break;\r
-                                                }\r
-                                            }\r
-                                            if (state != null) {\r
-                                                Variable context = Variables.getContext(graph, run);\r
-                                                Resource bookResource = context.getRepresents(graph);\r
-                                                SpreadsheetGraphUtils.setDefaultInitialConditionForBook(graph, bookResource, state);\r
-                                                \r
-                                                String contextURI = context.getURI(graph);\r
-                                                \r
-                                                String sessionName = context.getParent(graph).getURI(graph);\r
-                                                SpreadsheetSessionManager.getInstance().removeRealm(sessionName);\r
-                                                SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);\r
-                                            }\r
-                                            \r
-                                            return run.getURI(graph);\r
-                                        }\r
-                                    });\r
-                                    Variable newInput = processor.syncRequest(new PossibleURIVariable(uri));\r
-                                    load(newInput, client);\r
-//                                    fullSynchronize();\r
-                                    return;\r
-                                } catch (DatabaseException e) {\r
-                                    Logger.defaultLogError(e);\r
-                                }\r
-                            }\r
-                        }\r
-                    }\r
-\r
-                                       if(ClientModel.SOURCES.equals(location)) {\r
-                                               if(ClientModel.SOURCES_CURRENT.equals(property)) {\r
-                                                       try {\r
-                                                               Resource res = WorkbenchSelectionUtils.getPossibleResource(value);\r
-                                                               if(res != null) {\r
-                                                                       \r
-                                                                       Variable newInput = processor.syncRequest(new ResourceRead<Variable>(res) {\r
-\r
-                                                                               @Override\r
-                                                                               public Variable perform(ReadGraph graph) throws DatabaseException {\r
-                                                                                       Variable base = ProxyVariables.proxyVariableBase(graph, run);\r
-                                                                                       Variable in = Variables.getVariable(graph, resource);\r
-                                                                                       currentSource = in.getURI(graph);\r
-                                                                                       return ProxyVariables.makeProxyVariable(graph, base, in);\r
-                                                                               }\r
-                                               \r
-                                                                       });\r
-                                                                       \r
-                                                                       load(newInput, client);\r
-                                                                       \r
-                                                                       return;\r
-\r
-                                                               } else if(value instanceof String) {\r
-                                                                       \r
-                                                                       Variable newInput = processor.syncRequest(new UnaryRead<String, Variable>((String)value) {\r
-\r
-                                                                               @Override\r
-                                                                               public Variable perform(ReadGraph graph) throws DatabaseException {\r
-                                                                                       \r
-                                                                                       Variable base = ProxyVariables.proxyVariableBase(graph, run);\r
-                                                                                       Map<String,Variable> sources = graph.syncRequest(new Sources(base));\r
-\r
-                                                                                       Variable found = sources.get(parameter);\r
-                                                                                       if(found == null) return null;\r
-                                                                                       \r
-                                                                                       currentSource = parameter;\r
-\r
-                                                                                       return ProxyVariables.makeProxyVariable(graph, base, found);\r
-                                                                                       \r
-                                                                               }\r
-                                               \r
-                                                                       });\r
-                                                                       \r
-                                                                       load(newInput, client);\r
-                                                                       return;\r
-                                                               }\r
-                                                               \r
-                                                       } catch (DatabaseException e) {\r
-                                                               Logger.defaultLogError(e);\r
-                                                       }\r
-                                               }\r
-                                               return;\r
-                                       }\r
-                                       boolean needsCommit = false;\r
-                                       if (transaction == null) {\r
-                                           OperationMode mode = client.getPropertyAt(ClientModel.MODE, ClientModel.MODE_CURRENT);\r
-                                           transaction = startTransaction(mode);\r
-//                                         if (mode.equals(OperationMode.OPERATION))\r
-                                       transaction.setContext(run);\r
-                                           needsCommit = true;\r
-                                       }\r
-                                       final Transaction<Write> finalTransaction = transaction;\r
-                                       cellEditor.edit(transaction, location, property, value, binding, new Consumer<Object>() {\r
-                        \r
-                        @Override\r
-                        public void accept(Object param) {\r
-                            if (finalTransaction.needSynchronization() != null)\r
-                                synchronize(finalTransaction.needSynchronization());\r
-                        }\r
-                                       });\r
-                                       if (needsCommit)\r
-                                           transaction.commit();\r
-                               }\r
-\r
-                               @Override\r
-                               public void edit(Transaction<Write> transaction, String location, Variant variant, Consumer<?> callback) {\r
-                                   boolean needsCommit = false;\r
-                                   if (transaction == null) {\r
-                                       OperationMode mode = client.getPropertyAt(ClientModel.MODE, ClientModel.MODE_CURRENT);\r
-                                       transaction = startTransaction(mode);\r
-//                                     if (mode.equals(OperationMode.OPERATION))\r
-                                   transaction.setContext(run);\r
-                                       needsCommit = true;\r
-                                   }\r
-                                   final Transaction<Write> finalTransaction = transaction;\r
-                                       cellEditor.edit(transaction, location, variant, new Consumer<Object>() {\r
-                        \r
-                        @Override\r
-                        public void accept(Object param) {\r
-                            if (finalTransaction.needSynchronization() != null)\r
-                                synchronize(finalTransaction.needSynchronization());\r
-                        }\r
-                    });\r
-                                       if (needsCommit)\r
-                                           transaction.commit();\r
-                               }\r
-\r
-                               @Override\r
-                               public void copy(final Transaction<Write> transaction, String location, MutableVariant variant, Consumer<?> callback) {\r
-                                       cellEditor.edit(transaction, location, variant, new Consumer<Object>() {\r
-                        \r
-                        @Override\r
-                        public void accept(Object param) {\r
-                            if (transaction.needSynchronization() != null)\r
-                                synchronize(transaction.needSynchronization());\r
-                        }\r
-                                       });\r
-                               }\r
-\r
-                               @Override\r
-                               public Transaction<Write> startTransaction(OperationMode mode) {\r
-                                       return cellEditor.startTransaction(mode);\r
-                               }\r
-                               \r
-               };\r
-               \r
-        } else if (SheetCommands.class == clazz ) {\r
-            \r
-            return (T) new SheetCommands() {\r
-                \r
-                @Override\r
-                public void saveState() {\r
-                    \r
-                    Simantics.getSession().asyncRequest(new ReadRequest() {\r
-                        \r
-                        @Override\r
-                        public void run(ReadGraph graph) throws DatabaseException {\r
-                            IEclipseContext context = PlatformUI.getWorkbench().getService(IEclipseContext.class);\r
-                            \r
-                            Resource uiContextResource = run.getRepresents(graph);\r
-                            Resource bookResource = Variables.getContext(graph, run).getRepresents(graph);\r
-                            Layer0 L0 = Layer0.getInstance(graph);\r
-                            String uiContextName = graph.getRelatedValue2(uiContextResource, L0.HasName, Bindings.STRING);\r
-                            String bookName = graph.getRelatedValue2(bookResource, L0.HasName, Bindings.STRING);\r
-                            \r
-                            UISynchronize synchronizer = context.get(UISynchronize.class);\r
-                            synchronizer.asyncExec(() -> {\r
-                                Pair<String, Resource>[] pairs = new Pair[] {Pair.make(uiContextName, uiContextResource), Pair.make(bookName, bookResource) };\r
-                                SaveSpreadsheetStateDialog dialog = new SaveSpreadsheetStateDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor().getSite(), "Save Spreadsheet state", pairs);\r
-                                if (dialog.open() == Dialog.OK) {\r
-                                    Object[] result = dialog.getSelection();\r
-                                    if (result != null) {\r
-                                        Pair<Resource, String> p = (Pair<Resource, String>) result[0];\r
-                                        Simantics.getSession().asyncRequest(new WriteRequest() {\r
-                                            \r
-                                            @Override\r
-                                            public void perform(WriteGraph graph) throws DatabaseException {\r
-                                                \r
-                                                Variable parent = run.getParent(graph);\r
-                                                Variable base = ProxyVariables.proxyVariableBase(graph, parent);\r
-                                                SpreadsheetGraphUtils.saveInitialCondition(graph, parent, p.first, p.second);\r
-                                            }\r
-                                        });\r
-                                    }\r
-                                } else {\r
-                                    return;\r
-                                }\r
-                            });\r
-                        }\r
-                    });\r
-                }\r
-            };\r
-        }\r
-\r
-        return null;\r
-\r
-    }\r
-\r
-    @Override\r
-    public void exception(Throwable t) {\r
-        t.printStackTrace();\r
-    }\r
-\r
-    @Override\r
-    public boolean isDisposed() {\r
-        return disposed;\r
-    }\r
-\r
-    @Override\r
-    public void exception(AsyncReadGraph graph, Throwable t) {\r
-       Logger.defaultLogError("Failed to read properties.", t);\r
-    }\r
-\r
-    @Override\r
-    public void exception(ReadGraph graph, Throwable t) {\r
-       Logger.defaultLogError("Failed to read properties.", t);\r
-    }\r
-\r
-    public void dispose() {\r
-        for (PropertyListener listener : listenerCache.values())\r
-            listener.dispose();\r
-        \r
-        listenerCache.clear();\r
-        SessionEventSupport support = processor.getService(SessionEventSupport.class);\r
-        support.removeListener(listener);\r
-        disposed = true;\r
-    }\r
-    \r
-    private void synchronize(List<Object> list) {\r
-        Simantics.getSession().asyncRequest(new FullSynchronizeBook(run, list));\r
-    }\r
-    \r
-    public static class FullSynchronizeBook extends ReadRequest {\r
-\r
-        private final Variable run;\r
-        private final List<Object> location;\r
-        \r
-        public FullSynchronizeBook(Variable run, List<Object> cellLocation) {\r
-            this.run = run;\r
-            this.location = cellLocation;\r
-        }\r
-        \r
-        @Override\r
-        public void run(ReadGraph graph) throws DatabaseException {\r
-            String uri = run.getURI(graph);\r
-            String parentUri = run.getParent(graph).getURI(graph);\r
-            System.err.println("Full sync for book " + parentUri);\r
-            \r
-            Resource sheetResource = run.getRepresents(graph);\r
-            Variable sheetVariable = Variables.getVariable(graph, sheetResource);\r
-            \r
-            TObjectIntHashMap<Variable> changes = null;\r
-            if (location != null) {\r
-                changes = new TObjectIntHashMap<>(location.size());\r
-                for (Object loc : location) {\r
-                    Variable var = (Variable) loc;\r
-                    changes.put(var, 1);\r
-                };\r
-            }\r
-            SpreadsheetGraphUtils.partialSynchronization(graph, run.getParent(graph), changes);\r
-        }\r
-        \r
-    }\r
-    \r
-    private SpreadsheetBook getBook(ReadGraph graph) throws DatabaseException {\r
-        String sessionName = run.getParent(graph).getParent(graph).getURI(graph);\r
-        StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);\r
-        SpreadsheetBook book = realm.getEngine();\r
-        return book;\r
-    }\r
-    \r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 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.spreadsheet.graph;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import org.eclipse.e4.core.contexts.IEclipseContext;
+import org.eclipse.e4.ui.di.UISynchronize;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.ui.PlatformUI;
+import org.simantics.Simantics;
+import org.simantics.databoard.Bindings;
+import org.simantics.databoard.binding.Binding;
+import org.simantics.databoard.binding.mutable.MutableVariant;
+import org.simantics.databoard.binding.mutable.Variant;
+import org.simantics.db.AsyncReadGraph;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.RequestProcessor;
+import org.simantics.db.Resource;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.common.procedure.adapter.AsyncListenerSupport;
+import org.simantics.db.common.procedure.adapter.ListenerSupport;
+import org.simantics.db.common.procedure.adapter.SyncListenerSupport;
+import org.simantics.db.common.procedure.adapter.TransientCacheAsyncListener;
+import org.simantics.db.common.procedure.single.SingleSetSyncListenerDelegate;
+import org.simantics.db.common.request.ReadRequest;
+import org.simantics.db.common.request.ResourceRead;
+import org.simantics.db.common.request.UnaryRead;
+import org.simantics.db.common.request.UniqueRead;
+import org.simantics.db.common.request.WriteRequest;
+import org.simantics.db.common.request.WriteResultRequest;
+import org.simantics.db.common.session.SessionEventListenerAdapter;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.StandardRealm;
+import org.simantics.db.layer0.request.PossibleURIVariable;
+import org.simantics.db.layer0.request.VariableName;
+import org.simantics.db.layer0.request.VariableRead;
+import org.simantics.db.layer0.variable.ProxyVariables;
+import org.simantics.db.layer0.variable.Variable;
+import org.simantics.db.layer0.variable.Variables;
+import org.simantics.db.procedure.SyncListener;
+import org.simantics.db.request.Write;
+import org.simantics.db.service.SessionEventSupport;
+import org.simantics.layer0.Layer0;
+import org.simantics.spreadsheet.Adaptable;
+import org.simantics.spreadsheet.CellEditor;
+import org.simantics.spreadsheet.ClientModel;
+import org.simantics.spreadsheet.ClientModel.OperationMode;
+import org.simantics.spreadsheet.SheetCommands;
+import org.simantics.spreadsheet.event.model.RemoveCellHandler;
+import org.simantics.spreadsheet.resource.SpreadsheetResource;
+import org.simantics.ui.selection.WorkbenchSelectionUtils;
+import org.simantics.utils.datastructures.Pair;
+import org.simantics.utils.strings.AlphanumComparator;
+import org.simantics.utils.threads.logger.ITask;
+import org.simantics.utils.threads.logger.ThreadLogger;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import gnu.trove.map.hash.THashMap;
+import gnu.trove.map.hash.TObjectIntHashMap;
+
+class FilteredVariableProperties extends UnaryRead<Variable, Collection<Pair<String,Variable>>> {
+
+       final static String CLASSIFICATION = SpreadsheetResource.URIs.Attribute;
+       
+    public FilteredVariableProperties(Variable variable) {
+        super(variable);
+    }
+
+    @Override
+    public Collection<Pair<String,Variable>> perform(ReadGraph graph) throws DatabaseException {
+       ArrayList<Pair<String,Variable>> result = new ArrayList<Pair<String,Variable>>();
+       for(Variable var : parameter.getProperties(graph, CLASSIFICATION)) {
+               String name = var.getName(graph);
+               String uri = var.getURI(graph);
+               result.add(Pair.make(name, var));
+               Variable expression = var.getPossibleProperty(graph, "expression");
+               if(expression != null)
+                   result.add(Pair.make(name + "#expression", expression));
+               Variable editable = var.getPossibleProperty(graph, "editable");
+            if(editable != null)
+                result.add(Pair.make(name + "#editable", editable));
+       }
+       return result;
+    }
+
+}
+
+public class GraphUI implements Adaptable, ListenerSupport, AsyncListenerSupport, SyncListenerSupport {
+
+       private static final Logger LOGGER = LoggerFactory.getLogger(GraphUI.class);
+
+       final public static boolean DEBUG = false;
+       
+    final private RequestProcessor processor;
+    
+    private CellEditor<Write> cellEditor;
+    
+    private Variable run;
+    private ClientModel client;
+
+    private Map<String, PropertyListener> listenerCache = new THashMap<>();
+
+    public GraphUI(RequestProcessor processor) {
+        this.processor = processor;
+    }
+    
+    public void addCell(ReadGraph graph, Pair<String, Variable> child, final ClientModel client) throws DatabaseException {
+
+               if(DEBUG) System.out.println("GraphUI adds cell  " + child.second.getURI(graph));
+
+       final String childName = child.second.getName(graph);
+       Boolean immutable = child.second.getPossiblePropertyValue(graph, "immutable", Bindings.BOOLEAN);
+       if(immutable != null && immutable) {
+               Collection<Variable> properties = child.second.getProperties(graph, FilteredVariableProperties.CLASSIFICATION);
+               addProperties(graph, properties, client, childName);
+       } else {
+           PropertyListener listener = listenerCache.get(child.first); 
+           if (listener == null) {
+                   listener = propertyListener(client, childName);
+                   listenerCache.put(child.first, listener);
+           }
+               graph.asyncRequest(new FilteredVariableProperties(child.second), listener);
+       }
+
+    }
+
+    public void removeCell(ReadGraph graph, Pair<String, Variable> child, final ClientModel client) throws DatabaseException {
+
+               if(DEBUG) System.out.println("GraphUI removed cell " + child.first);
+               
+               client.clear(child.first);
+               PropertyListener listener = listenerCache.remove(child.first);
+               if (listener != null)
+                   listener.dispose();
+
+    }
+
+    public void loadCells(ReadGraph graph, Variable container, boolean immutable, final ClientModel client) throws DatabaseException {
+       
+               if(DEBUG) System.out.println("GraphUI loads cells from " + container.getURI(graph));
+               
+               if(immutable) {
+                       for(Pair<String, Variable> cell : graph.syncRequest(new Cells(container), TransientCacheAsyncListener.<Collection<Pair<String, Variable>>>instance())) { 
+                               addCell(graph, cell, client);
+                       }
+               } else {
+               graph.syncRequest(new Cells(container), new SingleSetSyncListenerDelegate<Pair<String, Variable>>(GraphUI.this) {
+       
+                   @Override
+                   public void add(ReadGraph graph, final Pair<String, Variable> child) throws DatabaseException {
+                       addCell(graph, child, client);
+                   }
+       
+                   @Override
+                   public void remove(ReadGraph graph, final Pair<String, Variable> child) throws DatabaseException {
+                       removeCell(graph, child, client);
+                   }
+               });
+               }
+       
+    }
+    
+    private SessionEventListenerAdapter listener;
+    
+    private String currentSource;
+
+    private boolean disposed;
+    
+    public Resource load(final Variable variable, final ClientModel client) throws DatabaseException {
+       
+//        for (PropertyListener listener : listenerCache.values())
+//            listener.dispose();
+//        
+//        listenerCache.clear();
+        
+        
+        
+       assert(variable != null);
+       
+       this.run = variable;
+       this.client = client;
+       
+       SessionEventSupport support = processor.getService(SessionEventSupport.class);
+       
+       for (PropertyListener listener : listenerCache.values()) {
+           listener.dispose();
+       }
+       listenerCache.clear();
+       
+       if(listener != null)
+               support.removeListener(listener);
+       
+       listener = new SessionEventListenerAdapter() {
+               
+               @Override
+               public void writeTransactionFinished() {
+                       client.flush();
+               }
+               
+       }; 
+       
+       support.addListener(listener);
+
+        this.cellEditor = processor.sync(new VariableRead<CellEditor<Write>>(variable) {
+
+                       @Override
+                       public CellEditor<Write> perform(ReadGraph graph) throws DatabaseException {
+                               SpreadsheetResource SHEET = SpreadsheetResource.getInstance(graph);
+                               return variable.getPropertyValue(graph, SHEET.cellEditor);
+                       }
+               
+        });
+       
+               final ITask task = ThreadLogger.getInstance().begin("GraphUI.init");
+
+               client.clearAll();
+
+               Map<String,Variable> sources = processor.syncRequest(new Sources(variable));
+
+               List<String> sheetList = processor.syncRequest(new Sheets(variable));
+               String currentSheet = processor.syncRequest(new VariableName(variable));
+               
+               Map<String, Resource> stateList = processor.syncRequest(new SpreadsheetStates(variable));
+
+               if(currentSource == null) currentSource = "Sheet";
+               
+               ArrayList<String> sourceList = new ArrayList<String>(sources.keySet());
+               
+               Collections.sort(sourceList, AlphanumComparator.CASE_INSENSITIVE_COMPARATOR);
+               if(!sourceList.contains(currentSource)) sourceList.add(currentSource);
+               
+               client.setProperty(ClientModel.SOURCES, ClientModel.SOURCES_AVAILABLE, sourceList.toArray(new String[sourceList.size()]));
+               client.setProperty(ClientModel.SOURCES, ClientModel.SOURCES_CURRENT, currentSource);
+
+               client.setProperty(ClientModel.SHEETS, ClientModel.SHEETS_AVAILABLE, sheetList.toArray(new String[sheetList.size()]));
+               client.setProperty(ClientModel.SHEETS, ClientModel.SHEETS_CURRENT, currentSheet);
+               
+               client.setProperty(ClientModel.STATES, ClientModel.STATES_AVAILABLE, stateList.keySet().toArray(new String[stateList.size()]));
+               
+               client.setProperty(ClientModel.CONTEXT, ClientModel.CONTEXT_CURRENT, variable);
+               
+               client.setProperty(ClientModel.MODE, ClientModel.MODE_CURRENT, OperationMode.OPERATION);
+               
+               String currentState = processor.syncRequest(new UniqueRead<String>() {
+
+            @Override
+            public String perform(ReadGraph graph) throws DatabaseException {
+                Resource book = variable.getParent(graph).getRepresents(graph);
+                Resource ic = graph.getPossibleObject(book, SpreadsheetResource.getInstance(graph).Book_HasDefaultInitialCondition);
+                if (ic == null)
+                       return "";
+                return graph.getRelatedValue2(ic, Layer0.getInstance(graph).HasName, Bindings.STRING);
+            }
+        });
+               
+               client.setProperty(ClientModel.STATES, ClientModel.STATES_CURRENT, currentState);
+
+        processor.syncRequest(new ReadRequest() {
+
+            @Override
+            public void run(ReadGraph graph) throws DatabaseException {
+
+               loadCells(graph, variable, false, client);
+
+               graph.syncRequest(new Ranges(variable), new SingleSetSyncListenerDelegate<Variable>(GraphUI.this) {
+
+                    @Override
+                    public void add(ReadGraph graph, final Variable range) throws DatabaseException {
+
+                               if(DEBUG) System.out.println("GraphUI adds range  " + range.getURI(graph));
+
+                       Boolean immutable = range.getPossiblePropertyValue(graph, "immutable", Bindings.BOOLEAN);
+                       loadCells(graph, range, immutable != null && immutable, client);
+
+                    }
+
+                    @Override
+                    public void remove(ReadGraph graph, final Variable range) throws DatabaseException {
+
+                    }
+                    
+               });
+               
+               
+               graph.syncRequest(new SheetLines(variable), new SingleSetSyncListenerDelegate<Variable>(GraphUI.this) {
+
+                    @Override
+                    public void add(ReadGraph graph, final Variable range) throws DatabaseException {
+
+                               if(DEBUG) System.out.println("GraphUI adds line  " + range.getURI(graph));
+
+                       Boolean immutable = range.getPossiblePropertyValue(graph, "immutable", Bindings.BOOLEAN);
+                       loadCells(graph, range, immutable != null && immutable, client);
+
+                    }
+
+                    @Override
+                    public void remove(ReadGraph graph, final Variable range) throws DatabaseException {
+
+                    }
+               });
+
+            }
+
+//            @Override
+//            public void remove(ReadGraph graph, Variable child) throws DatabaseException {
+//
+//             String location = locations.get(cellResource);
+//             assert(location != null);
+//
+//             client.setProperty(location, "Label", null);
+//             client.setProperty(location, "Expression", null);
+//
+//            }
+
+        });
+        
+
+               task.finish();
+               client.flush();
+               
+               return null;
+
+    }
+    
+    private static class PropertyListener extends SingleSetSyncListenerDelegate<Pair<String,Variable>> {
+
+        private static final Logger LOGGER = LoggerFactory.getLogger(PropertyListener.class);
+
+        private ClientModel client;
+        private String childName;
+        private boolean listenerDisposed;
+
+        public PropertyListener(AsyncListenerSupport support, ClientModel client, String childName) {
+            super(support);
+            this.client = client;
+            this.childName = childName;
+        }
+        
+        @Override
+        public void add(ReadGraph graph, final Pair<String,Variable> property) throws DatabaseException {
+
+            if(DEBUG)
+                System.out.println("GraphUI adds property  " + property.second.getURI(graph));
+
+            graph.asyncRequest(new CellValue(property.second), new SyncListener<Object>() {
+
+                @Override
+                public void execute(ReadGraph graph, final Object value) throws DatabaseException {
+
+                    String propertyName = property.first;
+
+                    if(DEBUG)
+                        System.out.println("GraphUI detected content change(1) at  " + childName + " - " + propertyName + " -> " + value);
+                    client.setProperty(childName, propertyName, value);
+                    
+                }
+
+                @Override
+                public void exception(ReadGraph graph, Throwable throwable) throws DatabaseException {
+                    
+                    LOGGER.error("PropertyListener.exception", throwable);
+                    
+                    String propertyName = property.first;
+                    if("content".equals(propertyName)) {
+                        if(throwable == null) throwable = new Exception();
+                        String message = throwable.getMessage();
+                        if(message == null) message = throwable.toString();
+                        client.setProperty(childName, propertyName, Variant.ofInstance(message));
+                    } else {
+                        client.setProperty(childName, propertyName, null);
+                    }
+                    
+                }
+
+                @Override
+                public boolean isDisposed() {
+                    return listenerDisposed;
+                }
+
+            });
+        }
+
+        public void dispose() {
+            listenerDisposed = true;
+        }
+        
+        @Override
+        public String toString() {
+            return super.toString() + ":" + childName;
+        }
+        
+    }
+    
+    private PropertyListener propertyListener(final ClientModel client, final String childName) {
+       return new PropertyListener(this, client, childName);
+    }
+    
+    private void addProperties(ReadGraph graph, final Collection<Variable> properties, final ClientModel client, final String childName) throws DatabaseException {
+
+       for(Variable property : properties) {
+               
+               if(DEBUG) System.out.println("GraphUI adds immutable property  " + property.getURI(graph));
+
+               final String propertyName = property.getName(graph);
+               
+               Object value = property.getValue(graph);
+
+               if(DEBUG) System.out.println("GraphUI detected change at  " + childName + " - " + propertyName + " -> " + value);
+               client.setProperty(childName, propertyName, value);
+               
+               String expression = property.getPossiblePropertyValue(graph, "expression", Bindings.STRING);
+               if(expression != null) {
+                       if(DEBUG) System.out.println("GraphUI detected change at  " + childName + " - " + (propertyName + "#expression") + " -> " + value);
+                       client.setProperty(childName, propertyName + "#expression", expression);
+               }
+               
+            Boolean editable = property.getPossiblePropertyValue(graph, "editable", Bindings.STRING);
+            if(editable != null) {
+                if(DEBUG) System.out.println("GraphUI detected change at  " + childName + " - " + (propertyName + "#editable") + " -> " + value);
+                client.setProperty(childName, propertyName + "#editable", editable);
+            }
+       
+       }
+
+    }
+    
+    @SuppressWarnings("unchecked")
+    @Override
+    public <T> T getAdapter(Class<T> clazz) {
+
+       if(Variable.class == clazz) {
+
+               return (T)run;
+               
+       } else if(RemoveCellHandler.class == clazz) {
+               
+            return (T) new RemoveCellHandler() {
+
+                               @Override
+                               public void handle(final String location) {
+                                       
+                               processor.asyncRequest(new ReadRequest() {
+
+                                   @Override
+                                   public void run(ReadGraph graph) throws DatabaseException {
+                                       
+                                                       Variable cellVariable = run.getPossibleChild(graph, location);
+                                                       if(cellVariable != null) {
+                                                               final Resource config = cellVariable.getPossiblePropertyValue(graph, "Represents");
+                                                               if(config != null) {
+                                                                       
+                                                                       graph.asyncRequest(new WriteRequest() {
+
+                                                                               @Override
+                                                                               public void perform(WriteGraph graph) throws DatabaseException {
+                                                                                       
+                                                                                       Layer0 l0 = Layer0.getInstance(graph);
+//                                                                                     SpreadsheetResource sr = SpreadsheetResource.getInstance(graph);
+                                                                                       graph.deny(config, l0.PartOf);
+//                                                                                     graph.deny(config, sr.RowOf);
+//                                                                                     graph.deny(config, sr.ColumnOf);
+                                                                                       
+                                                                               }
+                                                                               
+                                                                       });
+                                                                       
+                                                               }
+                                                       }
+                                                       
+                                   }
+                                   
+                               });
+                                       
+                               }
+               
+            };
+
+       } else if(CellEditor.class == clazz) {
+       
+               return (T)new CellEditor<Write>() {
+
+                               @Override
+                               public <E> void edit(Transaction<Write> transaction, String location, String property, E value, Binding binding, Consumer<?> callback) {
+                                       
+                                   if (ClientModel.ITERATION_ENABLED.equals(location)) {
+                                       Simantics.getSession().asyncRequest(new ReadRequest() {
+                                                       @Override
+                                                       public void run(ReadGraph graph) throws DatabaseException {
+                                                               getBook(graph).setIterationEnabled((boolean)value);
+                                                       }
+                                               });
+                                       return;
+                                   }
+                                   
+                                   if (ClientModel.MODE.equals(location)) {
+                                       if (ClientModel.MODE_CURRENT.equals(property)) {
+                                           client.setProperty(location, property, value);
+                                           client.flush();
+                                           return;
+                                       }
+                                   }
+                                   
+                                   if (ClientModel.CONTEXT.equals(location)) {
+                                       if(ClientModel.CONTEXT_CURRENT.equals(property)) {
+                            if(value instanceof String) {
+                                try {
+                                    Variable newContext = processor.syncRequest(new UnaryRead<String, Variable>((String)value) {
+
+                                        @Override
+                                        public Variable perform(ReadGraph graph) throws DatabaseException {
+                                      
+                                            String sheetName = run.getName(graph);
+                                            
+                                            Variable book = Variables.getContext(graph, run);
+                                            Resource bookResource = book.getRepresents(graph);
+                                            
+                                            Variable input = Variables.getVariable(graph, parameter);
+                                            Variable proxy = ProxyVariables.makeProxyVariable(graph, Variables.getVariable(graph, bookResource), input);
+                                            
+                                            return proxy.getChild(graph, sheetName);
+                                            
+//                                            return variable.getParent(graph).getChild(graph, parameter);
+                                        }
+
+                                    });
+                                    
+                                    load(newContext, client);
+                                    return;
+                                } catch (DatabaseException e) {
+                                    LOGGER.error("edit failed for model key '" + ClientModel.CONTEXT_CURRENT + "'", e);
+                                }
+                            }
+                                       }
+                                   }
+                                   
+                                       if(ClientModel.SHEETS.equals(location)) {
+                                               if(ClientModel.SHEETS_CURRENT.equals(property)) {
+                                                       
+                                                       if(value instanceof String) {
+
+                                                               try {
+
+                                                                       Variable newInput = processor.syncRequest(new UnaryRead<String, Variable>((String)value) {
+
+                                                                               @Override
+                                                                               public Variable perform(ReadGraph graph) throws DatabaseException {
+                                                                                       return run.getParent(graph).getChild(graph, parameter);
+                                                                               }
+
+                                                                       });
+
+                                                                       load(newInput, client);
+                                                                       return;
+                                                               } catch (DatabaseException e) {
+                                                                       LOGGER.error("edit failed for model key '" + ClientModel.SHEETS_CURRENT + "'", e);
+                                                               }
+                                                       }
+                                               }
+                                       }
+                                       
+                   if(ClientModel.STATES.equals(location)) {
+                        if(ClientModel.STATES_CURRENT.equals(property)) {
+                            if(value instanceof String) {
+                                final String parameter = (String) value;
+                                try {
+                                    
+                                    String uri = processor.syncRequest(new WriteResultRequest<String>() {
+                
+                                        @Override
+                                        public String perform(WriteGraph graph) throws DatabaseException {
+                                            
+                                            Map<String, Resource> states = graph.syncRequest(new SpreadsheetStates(run));
+                                            
+                                            Resource state = null;
+                                            for (Map.Entry<String, Resource> entry : states.entrySet()) {
+                                                if (entry.getKey().equals(parameter)) {
+                                                    state = entry.getValue();
+                                                    break;
+                                                }
+                                            }
+                                            if (state != null) {
+                                                Variable context = Variables.getContext(graph, run);
+                                                Resource bookResource = context.getRepresents(graph);
+                                                SpreadsheetGraphUtils.setDefaultInitialConditionForBook(graph, bookResource, state);
+                                                
+                                                String contextURI = context.getURI(graph);
+                                                
+                                                String sessionName = context.getParent(graph).getURI(graph);
+                                                SpreadsheetSessionManager.getInstance().removeRealm(graph, sessionName);
+                                                SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
+                                            }
+                                            
+                                            return run.getURI(graph);
+                                        }
+                                    });
+                                    Variable newInput = processor.syncRequest(new PossibleURIVariable(uri));
+                                    load(newInput, client);
+//                                    fullSynchronize();
+                                    return;
+                                } catch (DatabaseException e) {
+                                    LOGGER.error("edit failed for model key '" + ClientModel.STATES_CURRENT + "'", e);
+                                }
+                            }
+                        }
+                    }
+
+                                       if(ClientModel.SOURCES.equals(location)) {
+                                               if(ClientModel.SOURCES_CURRENT.equals(property)) {
+                                                       try {
+                                                               Resource res = WorkbenchSelectionUtils.getPossibleResource(value);
+                                                               if(res != null) {
+                                                                       
+                                                                       Variable newInput = processor.syncRequest(new ResourceRead<Variable>(res) {
+
+                                                                               @Override
+                                                                               public Variable perform(ReadGraph graph) throws DatabaseException {
+                                                                                       Variable base = ProxyVariables.proxyVariableBase(graph, run);
+                                                                                       Variable in = Variables.getVariable(graph, resource);
+                                                                                       currentSource = in.getURI(graph);
+                                                                                       return ProxyVariables.makeProxyVariable(graph, base, in);
+                                                                               }
+                                               
+                                                                       });
+                                                                       
+                                                                       load(newInput, client);
+                                                                       
+                                                                       return;
+
+                                                               } else if(value instanceof String) {
+                                                                       
+                                                                       Variable newInput = processor.syncRequest(new UnaryRead<String, Variable>((String)value) {
+
+                                                                               @Override
+                                                                               public Variable perform(ReadGraph graph) throws DatabaseException {
+                                                                                       
+                                                                                       Variable base = ProxyVariables.proxyVariableBase(graph, run);
+                                                                                       Map<String,Variable> sources = graph.syncRequest(new Sources(base));
+
+                                                                                       Variable found = sources.get(parameter);
+                                                                                       if(found == null) return null;
+                                                                                       
+                                                                                       currentSource = parameter;
+
+                                                                                       return ProxyVariables.makeProxyVariable(graph, base, found);
+                                                                                       
+                                                                               }
+                                               
+                                                                       });
+                                                                       
+                                                                       load(newInput, client);
+                                                                       return;
+                                                               }
+                                                               
+                                                       } catch (DatabaseException e) {
+                                                               LOGGER.error("edit failed for model key '" + ClientModel.SOURCES_CURRENT + "'", e);
+                                                       }
+                                               }
+                                               return;
+                                       }
+                                       boolean needsCommit = false;
+                                       if (transaction == null) {
+                                           OperationMode mode = client.getPropertyAt(ClientModel.MODE, ClientModel.MODE_CURRENT);
+                                           transaction = startTransaction(mode);
+//                                         if (mode.equals(OperationMode.OPERATION))
+                                       transaction.setContext(run);
+                                           needsCommit = true;
+                                       }
+                                       final Transaction<Write> finalTransaction = transaction;
+                                       cellEditor.edit(transaction, location, property, value, binding, new Consumer<Object>() {
+                        
+                        @Override
+                        public void accept(Object param) {
+                            if (finalTransaction.needSynchronization() != null)
+                                synchronize(finalTransaction.needSynchronization());
+                        }
+                                       });
+                                       if (needsCommit)
+                                           transaction.commit();
+                               }
+
+                               @Override
+                               public void edit(Transaction<Write> transaction, String location, Variant variant, Consumer<?> callback) {
+                                   boolean needsCommit = false;
+                                   if (transaction == null) {
+                                       OperationMode mode = client.getPropertyAt(ClientModel.MODE, ClientModel.MODE_CURRENT);
+                                       transaction = startTransaction(mode);
+//                                     if (mode.equals(OperationMode.OPERATION))
+                                   transaction.setContext(run);
+                                       needsCommit = true;
+                                   }
+                                   final Transaction<Write> finalTransaction = transaction;
+                                       cellEditor.edit(transaction, location, variant, new Consumer<Object>() {
+                        
+                        @Override
+                        public void accept(Object param) {
+                            if (finalTransaction.needSynchronization() != null)
+                                synchronize(finalTransaction.needSynchronization());
+                        }
+                    });
+                                       if (needsCommit)
+                                           transaction.commit();
+                               }
+
+                               @Override
+                               public void copy(final Transaction<Write> transaction, String location, MutableVariant variant, Consumer<?> callback) {
+                                       cellEditor.edit(transaction, location, variant, new Consumer<Object>() {
+                        
+                        @Override
+                        public void accept(Object param) {
+                            if (transaction.needSynchronization() != null)
+                                synchronize(transaction.needSynchronization());
+                        }
+                                       });
+                               }
+
+                               @Override
+                               public Transaction<Write> startTransaction(OperationMode mode) {
+                                       return cellEditor.startTransaction(mode);
+                               }
+                               
+               };
+               
+        } else if (SheetCommands.class == clazz ) {
+            
+            return (T) new SheetCommands() {
+                
+                @Override
+                public void saveState() {
+                    
+                    Simantics.getSession().asyncRequest(new ReadRequest() {
+                        
+                        @Override
+                        public void run(ReadGraph graph) throws DatabaseException {
+                            IEclipseContext context = PlatformUI.getWorkbench().getService(IEclipseContext.class);
+                            
+                            Resource uiContextResource = run.getRepresents(graph);
+                            Resource bookResource = Variables.getContext(graph, run).getRepresents(graph);
+                            Layer0 L0 = Layer0.getInstance(graph);
+                            String uiContextName = graph.getRelatedValue2(uiContextResource, L0.HasName, Bindings.STRING);
+                            String bookName = graph.getRelatedValue2(bookResource, L0.HasName, Bindings.STRING);
+                            
+                            UISynchronize synchronizer = context.get(UISynchronize.class);
+                            synchronizer.asyncExec(() -> {
+                                Pair<String, Resource>[] pairs = new Pair[] {Pair.make(uiContextName, uiContextResource), Pair.make(bookName, bookResource) };
+                                SaveSpreadsheetStateDialog dialog = new SaveSpreadsheetStateDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor().getSite(), "Save Spreadsheet state", pairs);
+                                if (dialog.open() == Dialog.OK) {
+                                    Object[] result = dialog.getSelection();
+                                    if (result != null) {
+                                        Pair<Resource, String> p = (Pair<Resource, String>) result[0];
+                                        Simantics.getSession().asyncRequest(new WriteRequest() {
+                                            
+                                            @Override
+                                            public void perform(WriteGraph graph) throws DatabaseException {
+                                                
+                                                Variable parent = run.getParent(graph);
+                                                Variable base = ProxyVariables.proxyVariableBase(graph, parent);
+                                                SpreadsheetGraphUtils.saveInitialCondition(graph, parent, p.first, p.second);
+                                            }
+                                        });
+                                    }
+                                } else {
+                                    return;
+                                }
+                            });
+                        }
+                    });
+                }
+            };
+        }
+
+        return null;
+
+    }
+
+    @Override
+    public void exception(Throwable t) {
+        t.printStackTrace();
+    }
+
+    @Override
+    public boolean isDisposed() {
+        return disposed;
+    }
+
+    @Override
+    public void exception(AsyncReadGraph graph, Throwable t) {
+        LOGGER.error("Failed to read properties.", t);
+    }
+
+    @Override
+    public void exception(ReadGraph graph, Throwable t) {
+        LOGGER.error("Failed to read properties.", t);
+    }
+
+    public void dispose() {
+        for (PropertyListener listener : listenerCache.values())
+            listener.dispose();
+        
+        listenerCache.clear();
+        SessionEventSupport support = processor.getService(SessionEventSupport.class);
+        support.removeListener(listener);
+        disposed = true;
+    }
+    
+    private void synchronize(List<Object> list) {
+        Simantics.getSession().asyncRequest(new FullSynchronizeBook(run, list));
+    }
+    
+    public static class FullSynchronizeBook extends ReadRequest {
+
+        private final Variable run;
+        private final List<Object> location;
+        
+        public FullSynchronizeBook(Variable run, List<Object> cellLocation) {
+            this.run = run;
+            this.location = cellLocation;
+        }
+        
+        @Override
+        public void run(ReadGraph graph) throws DatabaseException {
+            String uri = run.getURI(graph);
+            String parentUri = run.getParent(graph).getURI(graph);
+            System.err.println("Full sync for book " + parentUri);
+            
+            Resource sheetResource = run.getRepresents(graph);
+            Variable sheetVariable = Variables.getVariable(graph, sheetResource);
+            
+            TObjectIntHashMap<Variable> changes = null;
+            if (location != null) {
+                changes = new TObjectIntHashMap<>(location.size());
+                for (Object loc : location) {
+                    Variable var = (Variable) loc;
+                    changes.put(var, 1);
+                };
+            }
+            SpreadsheetGraphUtils.partialSynchronization(graph, run.getParent(graph), changes);
+        }
+        
+    }
+    
+    private SpreadsheetBook getBook(ReadGraph graph) throws DatabaseException {
+        String sessionName = run.getParent(graph).getParent(graph).getURI(graph);
+        StandardRealm<SheetNode, SpreadsheetBook> realm = SpreadsheetSessionManager.getInstance().getOrCreateRealm(graph, sessionName);
+        SpreadsheetBook book = realm.getEngine();
+        return book;
+    }
+    
+}