/******************************************************************************* * 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.diagram.synchronization.graph.layer; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import org.simantics.databoard.Bindings; import org.simantics.db.AsyncReadGraph; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.WriteGraph; import org.simantics.db.common.procedure.adapter.AsyncProcedureAdapter; import org.simantics.db.exception.CancelTransactionException; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.ServiceException; import org.simantics.db.procedure.AsyncProcedure; import org.simantics.diagram.stubs.DiagramResource; import org.simantics.diagram.synchronization.IModificationQueue; import org.simantics.diagram.synchronization.ModificationAdapter; import org.simantics.diagram.synchronization.graph.DiagramGraphUtil; import org.simantics.g2d.diagram.DiagramHints; import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.element.ElementHints; import org.simantics.g2d.element.IElement; import org.simantics.g2d.element.handler.ElementLayers; import org.simantics.g2d.layers.IEditableLayer; import org.simantics.g2d.layers.IEditableLayer.ILayerListener; import org.simantics.g2d.layers.IEditableLayer.LayerChangeEvent; import org.simantics.g2d.layers.ILayer; import org.simantics.g2d.layers.ILayers; import org.simantics.g2d.layers.ILayersEditor; import org.simantics.g2d.layers.ILayersEditor.ILayersEditorListener; import org.simantics.g2d.layers.SimpleLayer; import org.simantics.g2d.layers.SimpleLayers; import org.simantics.layer0.Layer0; /** * @author Tuukka Lehtonen */ public class GraphLayerManager { class LayerListener implements ILayersEditorListener, ILayerListener { @Override public void layerAdded(final ILayer layer) { modificationQueue.offer(new CreateLayer(layer, getDiagramResource()), null); modificationQueue.flush(); } @Override public void layerRemoved(final ILayer layer) { modificationQueue.offer(new RemoveLayer(layer, getDiagramResource()), null); modificationQueue.flush(); } @Override public void layerActivated(ILayer layer) { postActivation(layer, true); } @Override public void layerDeactivated(ILayer layer) { postActivation(layer, false); } @Override public void ignoreFocusChanged(boolean value) { // Ignore, not written to graph. } @Override public void ignoreVisibilityChanged(boolean value) { // Ignore, not written to graph. } void postActivation(ILayer layer, boolean activated) { modificationQueue.offer(new LayerActivation(layer, activated), null); modificationQueue.flush(); } @Override public void layerChanged(LayerChangeEvent event) { LayerChange change = null; if (IEditableLayer.PROP_NAME.equals(event.getProperty())) { String oldName = (String) event.getOldValue(); String newName = (String) event.getNewValue(); synchronized (layers) { GraphLayer gl = layers.remove(oldName); if (gl == null) return; layers.put(newName, gl.withName(newName)); change = new LayerChange(event, gl); } } if (change != null) { modificationQueue.offer(change, null); modificationQueue.flush(); } } } public static final boolean DEBUG_LAYERS = false; IModificationQueue modificationQueue; Resource diagram; Layer0 l0; DiagramResource dia; /** * All the layers currently loaded from the diagram. */ ConcurrentMap layers = new ConcurrentHashMap(); SimpleLayers layerEditor; /** * The listener for ILayersEditor and all IEditableLayer. */ LayerListener layerListener = new LayerListener(); public GraphLayerManager(ReadGraph graph, IModificationQueue modificationQueue, Resource diagram) { this.modificationQueue = modificationQueue; this.diagram = diagram; this.l0 = Layer0.getInstance(graph); this.dia = DiagramResource.getInstance(graph); } public void dispose() { for (ILayer layer : layerEditor.getLayers()) { if (layer instanceof IEditableLayer) { ((IEditableLayer) layer).removeLayerListener(layerListener); } } layers.clear(); } Resource getDiagramResource() { return diagram; } // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // LAYERS BEGIN // ------------------------------------------------------------------------ class CreateLayer extends ModificationAdapter { final ILayer layer; final Resource diagram; public CreateLayer(ILayer layer, Resource diagram) { super(LOW_PRIORITY); assert layer != null; this.layer = layer; this.diagram = diagram; if (layer instanceof IEditableLayer) { ((IEditableLayer) layer).addLayerListener(layerListener); } } @Override public void perform(WriteGraph g) throws Exception { String newName = layer.getName(); for (Resource l : g.getObjects(diagram, dia.HasLayer)) { String name = g.getRelatedValue(l, l0.HasName); if (newName.equals(name)) { return; } } GraphLayer l = new GraphLayerUtil(g).createLayer(newName, false); g.claim(diagram, dia.HasLayer, l.getLayer()); layers.put(newName, l); } } class RemoveLayer extends ModificationAdapter { final ILayer layer; final Resource diagram; public RemoveLayer(ILayer layer, Resource diagram) { super(LOW_PRIORITY); assert layer != null; this.layer = layer; this.diagram = diagram; if (layer instanceof IEditableLayer) { ((IEditableLayer) layer).removeLayerListener(layerListener); } } @Override public void perform(WriteGraph g) throws Exception { String removedName = layer.getName(); for (Resource l : g.getObjects(diagram, dia.HasLayer)) { String name = g.getRelatedValue(l, l0.HasName); if (removedName.equals(name)) { g.denyStatement(diagram, dia.HasLayer, l); deleteLayer(g, l); // NOTE: leave the layer tags intact, remove them gradually // by checking the validity of all layer tags during element // writeback. layers.remove(name); return; } } } void deleteLayer(WriteGraph g, Resource layer) throws DatabaseException { g.deny(layer); } } class LayerActivation extends ModificationAdapter { final ILayer layer; final boolean activated; public LayerActivation(ILayer layer, boolean activated) { super(LOW_PRIORITY); assert layer != null; this.layer = layer; this.activated = activated; } @Override public void perform(WriteGraph g) throws Exception { GraphLayer gl = layers.get(layer.getName()); if (gl == null) throw new CancelTransactionException("Diagram has no matching layer description: " + layer.getName()); g.claimLiteral(gl.getLayer(), dia.IsActive, Boolean.valueOf(activated)); } } class LayerChange extends ModificationAdapter { final LayerChangeEvent event; final GraphLayer gl; public LayerChange(LayerChangeEvent event, GraphLayer gl) { super(LOW_PRIORITY); assert event != null; assert gl != null; this.event = event; this.gl = gl; } @Override public void perform(WriteGraph g) throws Exception { // Resource name = g.getSingleObject(gl.getLayer(), b.HasName); // g.claimValue(name, event.getSource().getName()); g.claimLiteral(gl.getLayer(), l0.HasName, event.getSource().getName(), Bindings.STRING); } } void deleteTag(WriteGraph g, Resource tag) throws DatabaseException { g.deny(tag); } Collection getActiveLayers(ReadGraph g) throws DatabaseException { Collection result = new ArrayList(); for (GraphLayer gl : layers.values()) { Boolean active = g.getPossibleRelatedValue(gl.getLayer(), dia.IsActive); if (Boolean.TRUE.equals(active)) { result.add(gl); } } return result; } void tagElementWithActiveLayers(WriteGraph g, Resource element) throws DatabaseException { Collection activeLayers = getActiveLayers(g); for (GraphLayer activeLayer : activeLayers) { DiagramGraphUtil.tag(g, element, activeLayer.getVisible(), true); DiagramGraphUtil.tag(g, element, activeLayer.getFocusable(), true); } } GraphLayer loadLayer(ReadGraph g, Resource layer) throws DatabaseException { String name = g.getRelatedValue(layer, l0.HasName); Resource visible = g.getSingleObject(layer, dia.HasVisibleTag); Resource focusable = g.getSingleObject(layer, dia.HasFocusableTag); return new GraphLayer(name, layer, visible, focusable); } public ILayersEditor loadLayers(IDiagram diagram, ReadGraph g, Resource diagramResource) throws DatabaseException { SimpleLayers result = new SimpleLayers(); ConcurrentMap newLayers = new ConcurrentHashMap(); String[] fixed = diagram.getHint(DiagramHints.KEY_FIXED_LAYERS); if (fixed != null) { // for (String name : fixed) { // SimpleLayer l = new SimpleLayer(name); // result.addLayer(l); // result.activate(l); // } // We need to put GraphLayer to newLayers so... for (Resource layer : g.getObjects(diagramResource, dia.HasLayer)) { GraphLayer gl = loadLayer(g, layer); for (String name : fixed) { if (name.equals(gl.getName())) { SimpleLayer l = new SimpleLayer(gl.getName()); newLayers.put(gl.getName(), gl); result.addLayer(l); result.activate(l); } } } } else { if (DEBUG_LAYERS) System.out.println("Loading layers"); for (Resource layer : g.getObjects(diagramResource, dia.HasLayer)) { GraphLayer gl = loadLayer(g, layer); SimpleLayer l = new SimpleLayer(gl.getName()); newLayers.put(gl.getName(), gl); result.addLayer(l); Boolean active = g.getPossibleRelatedValue(layer, dia.IsActive); if (active == null) active = Boolean.FALSE; if (DEBUG_LAYERS) System.out.println(" Loaded " + (active ? "active" : "inactive") + " layer '" + gl.getName() + "'"); l.addLayerListener(layerListener); if (active) result.activate(l); } if (DEBUG_LAYERS) System.out.println("Loaded " + newLayers.size() + " layers"); } this.layers = newLayers; this.layerEditor = result; this.layerEditor.addListener(layerListener); return result; } public void loadLayersForElement(ReadGraph graph, ILayersEditor layersEditor, IElement e, Resource element) throws DatabaseException { if (DEBUG_LAYERS) System.out.println("Loading layers for element " + element + " - " + e); Set visible = null; Set focusable = null; for (ILayer l : layersEditor.getLayers()) { GraphLayer gl = layers.get(l.getName()); if (gl != null) { if (graph.hasStatement(element, gl.getVisible(), element)) { if (visible == null) visible = new HashSet(4); visible.add(l); if (DEBUG_LAYERS) System.out.println(" Visible on layer '" + gl.getName() + "'"); } if (graph.hasStatement(element, gl.getFocusable(), element)) { if (focusable == null) focusable = new HashSet(4); focusable.add(l); if (DEBUG_LAYERS) System.out.println(" Focusable on layer '" + gl.getName() + "'"); } } } if (visible == null) visible = new HashSet(1); if (focusable == null) focusable = new HashSet(1); e.setHint(ElementHints.KEY_VISIBLE_LAYERS, visible); e.setHint(ElementHints.KEY_FOCUS_LAYERS, focusable); } /** * @param graph * @param layersEditor * @param e * @param element * @param procedure a procedure whose exception method may be called 0 to * many times depending on how many errors occur during layer loading * @throws DatabaseException */ public void loadLayersForElement(AsyncReadGraph graph, ILayersEditor layersEditor, final IElement e, Resource element, final AsyncProcedure callback) { if (DEBUG_LAYERS) System.out.println("Loading layers for element " + element + " - " + e); final Set visible = new HashSet(2); final Set focusable = new HashSet(2); // NOTE: must not set layer hints into element until the layer sets have // been properly loaded. Set allLayers = layersEditor.getLayers(); if (allLayers.isEmpty()) { e.setHint(ElementHints.KEY_VISIBLE_LAYERS, visible); e.setHint(ElementHints.KEY_FOCUS_LAYERS, focusable); callback.execute(graph, e); return; } final AtomicInteger ready = new AtomicInteger(allLayers.size() * 2); for (final ILayer l : allLayers) { final GraphLayer gl = layers.get(l.getName()); if (gl != null) { graph.forHasStatement(element, gl.getVisible(), element, new AsyncProcedureAdapter() { @Override public void execute(AsyncReadGraph graph, Boolean result) { synchronized (visible) { visible.add(l); } if (DEBUG_LAYERS) System.out.println(" Visible on layer '" + gl.getName() + "'"); if (ready.decrementAndGet() == 0) { e.setHint(ElementHints.KEY_VISIBLE_LAYERS, visible); e.setHint(ElementHints.KEY_FOCUS_LAYERS, focusable); callback.execute(graph, e); } } @Override public void exception(AsyncReadGraph graph, Throwable t) { callback.exception(graph, t); } }); graph.forHasStatement(element, gl.getFocusable(), element, new AsyncProcedureAdapter() { @Override public void execute(AsyncReadGraph graph, Boolean result) { synchronized (focusable) { focusable.add(l); } if (DEBUG_LAYERS) System.out.println(" Focusable on layer '" + gl.getName() + "'"); if (ready.decrementAndGet() == 0) { e.setHint(ElementHints.KEY_VISIBLE_LAYERS, visible); e.setHint(ElementHints.KEY_FOCUS_LAYERS, focusable); callback.execute(graph, e); } } @Override public void exception(AsyncReadGraph graph, Throwable t) { callback.exception(graph, t); } }); } else { if (ready.addAndGet(-2) == 0) { e.setHint(ElementHints.KEY_VISIBLE_LAYERS, visible); e.setHint(ElementHints.KEY_FOCUS_LAYERS, focusable); callback.execute(graph, e); } } } } void putElementOnVisibleLayers(IDiagram diagram, IElement element) { // Make the new element visible and focusable on all currently // active layers. ILayers diagramLayers = diagram.getHint(DiagramHints.KEY_LAYERS); if (diagramLayers != null) { element.setHint(ElementHints.KEY_VISIBLE_LAYERS, new HashSet()); element.setHint(ElementHints.KEY_FOCUS_LAYERS, new HashSet()); Set visibleLayers = diagramLayers.getVisibleLayers(); if (DEBUG_LAYERS) System.out.println("Marking element visible and focusable only on visible layers: " + visibleLayers); for (ElementLayers elementLayers : element.getElementClass().getItemsByClass(ElementLayers.class)) { for (ILayer layer : visibleLayers) { elementLayers.setVisibility(element, layer, true); elementLayers.setFocusability(element, layer, true); } } } } public void putElementOnVisibleLayers(IDiagram diagram, WriteGraph g, Resource element) throws DatabaseException { // Make the new element visible and focusable on all currently // active layers. ILayers diagramLayers = diagram.getHint(DiagramHints.KEY_LAYERS); if (diagramLayers != null) { Set visibleLayers = diagramLayers.getVisibleLayers(); if (DEBUG_LAYERS) System.out.println("Marking element visible and focusable in the graph on visible layers: " + visibleLayers); for (ILayer layer : visibleLayers) { GraphLayer gl = layers.get(layer.getName()); if (gl != null) { DiagramGraphUtil.tag(g, element, gl.getVisible(), true); DiagramGraphUtil.tag(g, element, gl.getFocusable(), true); } } } } public void removeFromAllLayers(WriteGraph graph, Resource element) throws ServiceException { // Remove element from all layers. graph.deny(element, dia.IsVisible); graph.deny(element, dia.IsFocusable); } public GraphLayer getGraphLayer(String name) { return layers.get(name); } }