/*******************************************************************************
* Copyright (c) 2007, 2018 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.participant;
import java.awt.Color;
import java.awt.Font;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import org.simantics.Simantics;
import org.simantics.db.ReadGraph;
import org.simantics.db.Resource;
import org.simantics.db.common.request.PossibleIndexRoot;
import org.simantics.db.common.request.UnaryRead;
import org.simantics.db.exception.DatabaseException;
import org.simantics.db.layer0.adapter.Instances;
import org.simantics.db.layer0.request.IsActive;
import org.simantics.db.layer0.util.Layer0Utils;
import org.simantics.db.procedure.Listener;
import org.simantics.diagram.elements.TextNode;
import org.simantics.diagram.stubs.DiagramResource;
import org.simantics.g2d.canvas.Hints;
import org.simantics.g2d.canvas.ICanvasContext;
import org.simantics.g2d.canvas.SGDesignation;
import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;
import org.simantics.g2d.canvas.impl.HintReflection.HintListener;
import org.simantics.g2d.canvas.impl.SGNodeReflection.SGCleanup;
import org.simantics.g2d.canvas.impl.SGNodeReflection.SGInit;
import org.simantics.g2d.participant.RulerPainter;
import org.simantics.g2d.utils.Alignment;
import org.simantics.layer0.Layer0;
import org.simantics.modeling.ModelingResources;
import org.simantics.scenegraph.g2d.G2DParentNode;
import org.simantics.scenegraph.utils.DPIUtil;
import org.simantics.structural.stubs.StructuralResource2;
import org.simantics.utils.datastructures.hints.IHintContext;
import org.simantics.utils.datastructures.hints.IHintContext.Key;
import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;
import org.simantics.utils.datastructures.hints.IHintObservable;
import org.simantics.utils.ui.ErrorLogger;
/**
* DiagramModelActivityTracker tracks whether or not the specified input
* resource is part of the diagram model or not and modifies the canvas
* background color accordingly. If the diagram is not in an active model, an
* INACTIVE MODEL banner is also shown in the background using
* {@link TextNode}.
*
* @author Tuukka Lehtonen
*/
public class DiagramModelActivityTracker extends AbstractCanvasParticipant {
public static final Key KEY_IN_ACTIVE_MODEL = new KeyOf(String.class, "IN_ACTIVE_MODEL");
public static final Key KEY_ACTIVE_BACKGROUND = new KeyOf(Color.class, "ACTIVE_BACKGROUND");
public static final Key KEY_INACTIVE_BACKGROUND = new KeyOf(Color.class, "INACTIVE_BACKGROUND");
private static final Color DEFAULT_ACTIVE_BACKGROUND = Color.WHITE;
private static final Color DEFAULT_INACTIVE_BACKGROUND = new Color(242, 242, 242);
Resource input;
IsInActiveModelListener listener;
TextNode bannerNode;
Rectangle2D bounds;
boolean rulerVisible = false;
double rulerSize = 0;
public DiagramModelActivityTracker(Resource input) {
if (input == null)
throw new NullPointerException("null input");
this.input = input;
}
@HintListener(Class=Hints.class, Field="KEY_CONTROL_BOUNDS")
public void controlBoundsChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
bounds = (Rectangle2D) newValue;
resetBannerNode(bounds, rulerVisible, rulerSize);
}
@HintListener(Class=RulerPainter.class, Field="KEY_RULER_ENABLED")
public void rulerToggled(IHintObservable sender, Key key, Object oldValue, Object newValue) {
rulerVisible = Boolean.TRUE.equals(newValue);
resetBannerNode(bounds, rulerVisible, rulerSize);
}
@HintListener(Class=RulerPainter.class, Field="KEY_RULER_SIZE")
public void rulerSizeChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
rulerSize = newValue != null ? (Double) newValue : 0.0;
resetBannerNode(bounds, rulerVisible, rulerSize);
}
private void resetBannerNode(Rectangle2D bounds, boolean rulerVisible, double rulerSize) {
if (bannerNode == null)
return;
if (bounds != null) {
AffineTransform at = new AffineTransform();
at.translate(5,5);
if (rulerVisible) {
double s = DPIUtil.upscale(rulerSize);
at.translate(s, s);
}
bannerNode.setTransform(at);
bannerNode.setColor(Color.BLACK);
} else {
// Disable rendering
bannerNode.setColor(null);
}
}
@HintListener(Class=DiagramModelActivityTracker.class, Field="KEY_IN_ACTIVE_MODEL")
public void containingModelActivityChanged(IHintObservable sender, Key key, Object oldValue, Object _newValue) {
String newValue = (String)_newValue;
if (bannerNode == null)
return;
if (newValue == null) {
bannerNode.setText("");
} else {
bannerNode.setText(newValue);
//bannerNode.setColor(new Color(192, 32, 32, 128));
//bannerNode.setColor(new Color(192, 32, 32, 32));
}
}
@Override
public void addedToContext(ICanvasContext ctx) {
super.addedToContext(ctx);
rulerVisible = Boolean.TRUE.equals(getHint(RulerPainter.KEY_RULER_ENABLED));
Double rs = getHint(RulerPainter.KEY_RULER_SIZE);
rulerSize = rs != null ? rs : 0;
listener = new IsInActiveModelListener(ctx);
Simantics.getSession().async(new IsActiveDiagram(input), listener);
if (!ctx.getHintStack().containsHint(KEY_ACTIVE_BACKGROUND))
ctx.getDefaultHintContext().setHint(KEY_ACTIVE_BACKGROUND, DEFAULT_ACTIVE_BACKGROUND);
if (!ctx.getHintStack().containsHint(KEY_INACTIVE_BACKGROUND))
ctx.getDefaultHintContext().setHint(KEY_INACTIVE_BACKGROUND, DEFAULT_INACTIVE_BACKGROUND);
}
@Override
public void removedFromContext(ICanvasContext ctx) {
if (listener != null) {
listener.dispose();
listener = null;
}
super.removedFromContext(ctx);
}
@SGInit(designation = SGDesignation.CONTROL)
public void initSG(G2DParentNode parent) {
bannerNode = parent.addNode("inactivity-banner", TextNode.class);
bannerNode.setZIndex(Integer.MIN_VALUE / 4);
//bannerNode.setZIndex(Integer.MAX_VALUE / 4);
bannerNode.setHorizontalAlignment((byte) Alignment.LEADING.ordinal());
bannerNode.setVerticalAlignment((byte) Alignment.LEADING.ordinal());
//bannerNode.setText("Model is not active.");
bannerNode.setText("");
int fontSize = DPIUtil.upscale(16);
bannerNode.setFont(new Font("Tahoma", Font.PLAIN, fontSize));
//bannerNode.setBackgroundColor(new Color(192, 192, 192, 128));
bannerNode.setPadding(10, 10);
bannerNode.setBorderColor(Color.GRAY);
bannerNode.setBorderWidth(0);
bannerNode.setEditable(false);
bannerNode.setColor(Color.BLACK);
}
@SGCleanup
public void cleanupSG() {
if (bannerNode != null) {
bannerNode.remove();
bannerNode = null;
}
}
static class IsActiveDiagram extends UnaryRead {
public IsActiveDiagram(Resource parameter) {
super(parameter);
}
@Override
public String perform(ReadGraph graph) throws DatabaseException {
Resource indexRoot = graph.syncRequest( new PossibleIndexRoot(parameter) );
if (indexRoot == null) {
return "This diagram is not part of any model or shared library";
}
Layer0 L0 = Layer0.getInstance(graph);
StructuralResource2 STR = StructuralResource2.getInstance(graph);
ModelingResources MOD = ModelingResources.getInstance(graph);
Resource composite = graph.getPossibleObject(parameter, MOD.DiagramToComposite);
if(composite == null)
return "This diagram does not have a corresponding configuration";
Resource componentType = graph.getPossibleObject(composite, STR.Defines);
if(componentType != null) {
// This is a component type
boolean published = Layer0Utils.isPublished(graph, componentType);
if(published)
return "This diagram defines a published (read-only) user component";
}
if(graph.isInstanceOf(indexRoot, L0.SharedOntology)) {
return "";
}
boolean activeModel = graph.syncRequest( new IsActive(indexRoot) );
if(!activeModel)
return "The model of this diagram is not active";
DiagramResource DIA = DiagramResource.getInstance(graph);
Instances query = graph.adapt(DIA.DiagramActivityCondition, Instances.class);
for(Resource condition : query.find(graph, indexRoot)) {
String value = Simantics.tryInvokeSCL(graph, condition, DIA.DiagramActivityCondition_test, parameter);
if(value != null && !value.isEmpty()) return value;
}
return "";
}
}
static class IsInActiveModelListener implements Listener {
ICanvasContext context;
public IsInActiveModelListener(ICanvasContext context) {
this.context = context;
}
@Override
public void execute(final String result) {
final Color color = (result.isEmpty())
? context.getHintStack().getHint(KEY_ACTIVE_BACKGROUND)
: context.getHintStack().getHint(KEY_INACTIVE_BACKGROUND);
context.getThreadAccess().asyncExec(new Runnable() {
@Override
public void run() {
ICanvasContext ctx = context;
if (ctx == null)
return;
if (ctx.isDisposed())
return;
IHintContext hints = ctx.getDefaultHintContext();
if (color != null)
hints.setHint(Hints.KEY_BACKGROUND_COLOR, color);
hints.setHint(KEY_IN_ACTIVE_MODEL, result);
}
});
}
public void dispose() {
context = null;
}
@Override
public boolean isDisposed() {
ICanvasContext context = this.context;
return context == null || context.isDisposed();
}
@Override
public void exception(Throwable t) {
ErrorLogger.defaultLogError(t);
}
}
}