From: Antti Villberg Date: Fri, 1 Sep 2017 10:57:05 +0000 (+0300) Subject: Editing of texts inside SVG elements X-Git-Tag: v1.31.0~203^2 X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=commitdiff_plain;h=4f38bc070c3e1e40d02bd8da64cc93a798c9aace Editing of texts inside SVG elements refs #7458 Change-Id: Ied21d5c7706f2fa7c060088b5b57c46ee17977b2 --- diff --git a/bundles/org.simantics.diagram.ontology/graph/Diagram.pgraph b/bundles/org.simantics.diagram.ontology/graph/Diagram.pgraph index bd99e8f98..42aa3b0ef 100644 --- a/bundles/org.simantics.diagram.ontology/graph/Diagram.pgraph +++ b/bundles/org.simantics.diagram.ontology/graph/Diagram.pgraph @@ -114,11 +114,17 @@ DIA.Functions.activeProfileModifier : L0.Function L0.HasValueType "String -> String" DIA.Functions.diagramElementIssuePath : L0.Function +DIA.DefinedElement.Parameter "[String]" but DIA.symbolCode now returns either "[String]" or "[G2DNodeModification]" directly >-- DIA.symbolCode -- DIA.symbolDropHandler ==> "[WorkbenchSelectionElement] -> ()" -- DIA.DefinedElement.textEditor ==> "String -> String -> ()" -- DIA.DefinedElement.textEditorFullText ==> "String -> String" DIA.DiagramActivityCondition.test ==> "Resource -> String" -- DIA.SVGElement.enableEditing ==> "Boolean" - org.simantics.scenegraph.g2d.nodes.SVGNode + org.simantics.diagram.elements.SVGNode diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/AnimatedSVGElementClassFactory.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/AnimatedSVGElementClassFactory.java index 76159f6ad..cbeed5b2a 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/AnimatedSVGElementClassFactory.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/AnimatedSVGElementClassFactory.java @@ -21,6 +21,7 @@ import org.simantics.db.common.procedure.adapter.TransientCacheAsyncListener; import org.simantics.db.common.procedure.guarded.GuardedAsyncProcedureWrapper; import org.simantics.db.common.request.BinaryAsyncRead; import org.simantics.db.procedure.AsyncProcedure; +import org.simantics.diagram.elements.AnimatedSVGImage; import org.simantics.diagram.stubs.G2DResource; import org.simantics.diagram.synchronization.SynchronizationHints; import org.simantics.diagram.synchronization.graph.TransformSynchronizer; @@ -40,7 +41,6 @@ import org.simantics.g2d.elementclass.ImageClass.ImageElementHandler; import org.simantics.g2d.image.DefaultImages; import org.simantics.g2d.image.Image; import org.simantics.g2d.image.ProviderUtils; -import org.simantics.g2d.svg.AnimatedSVGImage; import org.simantics.layer0.Layer0; import org.simantics.utils.datastructures.cache.IFactory; import org.simantics.utils.datastructures.cache.ProvisionException; diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/DefinedElementFactory.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/DefinedElementFactory.java index e7bb472e4..9f02393c7 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/DefinedElementFactory.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/DefinedElementFactory.java @@ -37,6 +37,7 @@ import org.simantics.g2d.diagram.IDiagram; import org.simantics.g2d.element.ElementClass; import org.simantics.g2d.element.ElementUtils; import org.simantics.g2d.element.IElement; +import org.simantics.g2d.element.handler.impl.DefaultParameters; import org.simantics.g2d.element.handler.impl.DefaultTransform; import org.simantics.g2d.element.handler.impl.ObjectTerminal; import org.simantics.g2d.element.handler.impl.SimpleElementLayers; @@ -228,6 +229,7 @@ public class DefinedElementFactory extends ElementFactoryAdapter { TextImpl.INSTANCE, new StaticObjectAdapter(elementType), DefaultTransform.INSTANCE, + DefaultParameters.INSTANCE, StaticSymbolImageInitializer.INSTANCE, new StaticSymbolImpl(img), DefinedElementHandler.INSTANCE, @@ -257,6 +259,8 @@ public class DefinedElementFactory extends ElementFactoryAdapter { // This is needed for terminal tooltips. e.setHint(TooltipParticipant.TOOLTIP_KEY, TerminalTooltipProvider.INSTANCE); + ElementFactoryUtil.readParameters(graph, element, e); + GuardedAsyncProcedureWrapper guard = new GuardedAsyncProcedureWrapper(procedure, 1); ElementFactoryUtil.readTransform(graph, element, e, guard); diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/DefinedElementHandler.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/DefinedElementHandler.java index 17f22cfcf..e3e1269f3 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/DefinedElementHandler.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/DefinedElementHandler.java @@ -18,6 +18,7 @@ import org.simantics.g2d.image.Image; import org.simantics.scenegraph.Node; import org.simantics.scenegraph.g2d.G2DParentNode; import org.simantics.scenegraph.g2d.IG2DNode; +import org.simantics.scenegraph.g2d.nodes.SingleElementNode; import org.simantics.utils.datastructures.hints.IHintContext.Key; public class DefinedElementHandler implements SceneGraph, InternalSize, Resize, Outline { @@ -52,6 +53,9 @@ public class DefinedElementHandler implements SceneGraph, InternalSize, Resize, n.setTransform(at); // FIXME: not tested.. } } + if(parent instanceof SingleElementNode) { + ((SingleElementNode)parent).setParameters(ElementUtils.getParameters(e)); + } } public void cleanup(final IElement e) { diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/ElementFactoryUtil.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/ElementFactoryUtil.java index 89ee666bb..f05667cb6 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/ElementFactoryUtil.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/ElementFactoryUtil.java @@ -12,11 +12,13 @@ package org.simantics.diagram.adapter; import java.awt.geom.AffineTransform; +import java.util.Map; import org.simantics.db.AsyncReadGraph; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.common.procedure.adapter.AsyncProcedureAdapter; +import org.simantics.db.common.procedure.adapter.ProcedureAdapter; import org.simantics.db.exception.DatabaseException; import org.simantics.db.procedure.AsyncProcedure; import org.simantics.diagram.stubs.DiagramResource; @@ -59,6 +61,15 @@ public class ElementFactoryUtil { procedure.execute(graph, e); } + public static void readParameters(AsyncReadGraph graph, final Resource resource, final IElement e) { + graph.asyncRequest(new ElementParameterRequest(resource), new ProcedureAdapter>() { + @Override + public void execute(Map result) { + ElementUtils.setParameters(e, result); + } + }); + } + public static ISynchronizationContext getContext(IDiagram diagram) { return diagram.getHint(SynchronizationHints.CONTEXT); } diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/ElementParameterRequest.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/ElementParameterRequest.java new file mode 100644 index 000000000..6592ec200 --- /dev/null +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/ElementParameterRequest.java @@ -0,0 +1,35 @@ +package org.simantics.diagram.adapter; + +import java.util.HashMap; +import java.util.Map; + +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.request.ResourceRead; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.variable.Variable; +import org.simantics.db.layer0.variable.Variables; +import org.simantics.diagram.stubs.DiagramResource; + +class ElementParameterRequest extends ResourceRead> { + + protected ElementParameterRequest(Resource resource) { + super(resource); + } + + @Override + public Map perform(ReadGraph graph) throws DatabaseException { + Variable var = Variables.getPossibleVariable(graph, resource); + if(var == null) return null; + Map result = null; + for(Variable property : var.getProperties(graph, DiagramResource.URIs.DefinedElement_Parameter)) { + Object value = property.getPossibleValue(graph); + if(value != null) { + if(result == null) result = new HashMap<>(); + result.put(property.getName(graph), value); + } + } + return result; + } + +} \ No newline at end of file diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/SVGElementClassFactory.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/SVGElementClassFactory.java index 94d783f2d..4f4c7f6ba 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/SVGElementClassFactory.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/adapter/SVGElementClassFactory.java @@ -26,6 +26,7 @@ import org.simantics.db.common.request.UnaryAsyncRead; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.procedure.AsyncProcedure; +import org.simantics.diagram.elements.SVGImage; import org.simantics.diagram.stubs.G2DResource; import org.simantics.diagram.synchronization.SynchronizationHints; import org.simantics.diagram.synchronization.graph.TransformSynchronizer; @@ -45,7 +46,6 @@ import org.simantics.g2d.elementclass.ImageClass.ImageElementHandler; import org.simantics.g2d.image.DefaultImages; import org.simantics.g2d.image.Image; import org.simantics.g2d.image.ProviderUtils; -import org.simantics.g2d.svg.SVGImage; import org.simantics.scenegraph.g2d.nodes.SVGNode; import org.simantics.utils.Development; import org.simantics.utils.datastructures.cache.IFactory; diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/svg/AnimatedSVGImage.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/AnimatedSVGImage.java similarity index 97% rename from bundles/org.simantics.g2d/src/org/simantics/g2d/svg/AnimatedSVGImage.java rename to bundles/org.simantics.diagram/src/org/simantics/diagram/elements/AnimatedSVGImage.java index afbc58686..2f9490b6e 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/svg/AnimatedSVGImage.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/AnimatedSVGImage.java @@ -9,7 +9,7 @@ * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ -package org.simantics.g2d.svg; +package org.simantics.diagram.elements; import java.awt.Point; import java.awt.Shape; @@ -19,8 +19,6 @@ import java.util.EnumSet; import org.simantics.g2d.image.Image; import org.simantics.scenegraph.Node; import org.simantics.scenegraph.g2d.G2DParentNode; -import org.simantics.scenegraph.g2d.nodes.AnimatedSVGNode; -import org.simantics.scenegraph.g2d.nodes.SVGNode; import org.simantics.utils.datastructures.cache.IFactory; import org.simantics.utils.datastructures.cache.ProvisionException; diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/AnimatedSVGNode.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/AnimatedSVGNode.java similarity index 97% rename from bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/AnimatedSVGNode.java rename to bundles/org.simantics.diagram/src/org/simantics/diagram/elements/AnimatedSVGNode.java index 1d0c9f795..e4924d1b1 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/AnimatedSVGNode.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/AnimatedSVGNode.java @@ -9,7 +9,7 @@ * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ -package org.simantics.scenegraph.g2d.nodes; +package org.simantics.diagram.elements; import java.awt.Graphics2D; import java.awt.geom.Rectangle2D; @@ -24,6 +24,7 @@ import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; +import org.simantics.g2d.elementclass.AnimatedNode; import org.simantics.scenegraph.utils.BufferedImage; import org.simantics.scenegraph.utils.G2DUtils; import org.simantics.scenegraph.utils.MipMapBufferedImage; @@ -36,7 +37,7 @@ import com.kitfox.svg.SVGException; import com.kitfox.svg.SVGUniverse; import com.kitfox.svg.xml.StyleAttribute; -public class AnimatedSVGNode extends SVGNode { +public class AnimatedSVGNode extends SVGNode implements AnimatedNode { /** * */ diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/DecorationSVGNode.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/DecorationSVGNode.java similarity index 60% rename from bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/DecorationSVGNode.java rename to bundles/org.simantics.diagram/src/org/simantics/diagram/elements/DecorationSVGNode.java index 7aac233de..4e3b7fe00 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/DecorationSVGNode.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/DecorationSVGNode.java @@ -1,5 +1,6 @@ -package org.simantics.scenegraph.g2d.nodes; +package org.simantics.diagram.elements; +import org.simantics.scenegraph.g2d.nodes.Decoration; public class DecorationSVGNode extends SVGNode implements Decoration { diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/EditorState.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/EditorState.java new file mode 100644 index 000000000..0c1375011 --- /dev/null +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/EditorState.java @@ -0,0 +1,358 @@ +package org.simantics.diagram.elements; + +import java.awt.Toolkit; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.geom.Rectangle2D; +import java.io.IOException; + +import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent; + +import com.kitfox.svg.Group; +import com.kitfox.svg.Line; +import com.kitfox.svg.Rect; +import com.kitfox.svg.SVGDiagram; +import com.kitfox.svg.SVGException; +import com.kitfox.svg.Text; +import com.kitfox.svg.Tspan; +import com.kitfox.svg.animation.AnimationElement; + +/** + * @author Antti Villberg + * @since 1.31.0 + */ +class EditorState { + + enum ModificationClass { + SINGLE_INSERT, AREA_INSERT,SINGLE_DELETE,AREA_DELETE,NO_EDIT + } + + EditorStateStatic base; + ModificationClass modificationClass = ModificationClass.NO_EDIT; + int caretPosition = -1; + int selectionOtherPosition = -1; + String currentText = null; + + private String selectedText() { + if(editModeHasSelection()) { + int min = Math.min(caretPosition, selectionOtherPosition); + int max = Math.max(caretPosition, selectionOtherPosition); + return currentText.substring(min, max); + } + return null; + } + + private boolean editModeHasSelection() { + return selectionOtherPosition != -1; + } + + private void editModeClearSelection() { + selectionOtherPosition = -1; + } + + private void deleteCurrentSelection() { + int min = Math.min(caretPosition, selectionOtherPosition); + int max = Math.max(caretPosition, selectionOtherPosition); + currentText = currentText.substring(0, min) + currentText.substring(max, currentText.length()); + caretPosition = min; + editModeClearSelection(); + } + + public void applyEditMode(SVGDiagram diagram) throws SVGException { + + Text text = (Text)diagram.getElement(base.textElementId); + Tspan span = (Tspan)text.getContent().get(0); + + // Measure the X-dimensions of the font - append TERM_STRING to account for trailing whitespace + span.setText(currentText + EditorStateManager.TERM_STRING); + text.rebuild(); + diagram.updateTime(0); + double textWidth = text.getBoundingBox().getWidth() - base.termStringWidth; + + // Measure the caret position + span.setText(currentText.substring(0, caretPosition) + EditorStateManager.TERM_STRING); + text.rebuild(); + diagram.updateTime(0); + double caretRectWidth = text.getBoundingBox().getWidth() - base.termStringWidth; + + double selectionOtherWidth = 0; + if(selectionOtherPosition != -1) { + span.setText(currentText.substring(0, selectionOtherPosition) + EditorStateManager.TERM_STRING); + text.rebuild(); + diagram.updateTime(0); + selectionOtherWidth = text.getBoundingBox().getWidth() - base.termStringWidth; + } + + + // Finally the actual text + span.setText(currentText); + text.rebuild(); + diagram.updateTime(0); + Rectangle2D finalBB = text.getBoundingBox(); + + Group group = (Group)text.getParent(); + Line line = new Line(); + try { + + group.removeChild(text); + + double xPadding = 0; + + double minY = (base.verticalDimensions.getMinY()-1); + double height = (base.verticalDimensions.getHeight()+2); + + Rect rect = new Rect(); + rect.addAttribute("x", AnimationElement.AT_XML, "" + (finalBB.getMinX()-xPadding)); + rect.addAttribute("y", AnimationElement.AT_XML, "" + minY); + rect.addAttribute("width", AnimationElement.AT_XML, "" + (textWidth+xPadding)); + rect.addAttribute("height", AnimationElement.AT_XML, "" + height); + rect.addAttribute("fill", AnimationElement.AT_XML, "#ccc"); + group.loaderAddChild(null, rect); + + double caretX = finalBB.getMinX() + caretRectWidth; + + if(selectionOtherPosition != -1) { + double selectionX = finalBB.getMinX() + selectionOtherWidth; + Rect selection = new Rect(); + if(selectionOtherPosition < caretPosition) { + selection.addAttribute("x", AnimationElement.AT_XML, "" + selectionX); + selection.addAttribute("y", AnimationElement.AT_XML, "" + minY); + selection.addAttribute("width", AnimationElement.AT_XML, "" + (caretX-selectionX)); + selection.addAttribute("height", AnimationElement.AT_XML, "" + height); + selection.addAttribute("fill", AnimationElement.AT_XML, "#888"); + } else { + selection.addAttribute("x", AnimationElement.AT_XML, "" + caretX); + selection.addAttribute("y", AnimationElement.AT_XML, "" + minY); + selection.addAttribute("width", AnimationElement.AT_XML, "" + (selectionX-caretX)); + selection.addAttribute("height", AnimationElement.AT_XML, "" + height); + selection.addAttribute("fill", AnimationElement.AT_XML, "#888"); + } + group.loaderAddChild(null, selection); + } + + line.addAttribute("x1", AnimationElement.AT_XML, "" + caretX); + line.addAttribute("x2", AnimationElement.AT_XML, "" + caretX); + line.addAttribute("y1", AnimationElement.AT_XML, "" + (base.verticalDimensions.getMinY()-1)); + line.addAttribute("y2", AnimationElement.AT_XML, "" + (base.verticalDimensions.getMaxY()+1)); + line.addAttribute("stroke", AnimationElement.AT_XML, "black"); + line.addAttribute("stroke-width", AnimationElement.AT_XML, "0.5"); + group.loaderAddChild(null, line); + + group.loaderAddChild(null, text); + + } finally { + + } + + diagram.updateTime(0); + + } + + boolean keyPressed(EditorStateManager esm, KeyPressedEvent e) { + boolean result = keyPressedInternal(esm, e); + if(selectionOtherPosition == caretPosition) + editModeClearSelection(); + return result; + } + + private void performDelete() { + if(editModeHasSelection()) { + deleteCurrentSelection(); + modificationClass = ModificationClass.AREA_DELETE; + } else { + if(caretPosition < currentText.length()) { + currentText = currentText.substring(0, caretPosition) + currentText.substring(caretPosition+1, currentText.length()); + } + modificationClass = ModificationClass.SINGLE_DELETE; + } + } + + private void performCopy() { + String selection = selectedText(); + if(selection == null) return; + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + clipboard.setContents(new StringSelection(selection), null); + } + + boolean keyPressedInternal(EditorStateManager esm, KeyPressedEvent e) { + + if(e.keyCode == java.awt.event.KeyEvent.VK_BACK_SPACE) { + if(editModeHasSelection()) { + deleteCurrentSelection(); + modificationClass = ModificationClass.AREA_DELETE; + } else { + if(caretPosition > 0) { + currentText = currentText.substring(0, caretPosition-1) + currentText.substring(caretPosition, currentText.length()); + caretPosition--; + } + modificationClass = ModificationClass.SINGLE_DELETE; + } + } else if (java.awt.event.KeyEvent.VK_DELETE == e.keyCode) { + performDelete(); + } else if (java.awt.event.KeyEvent.VK_C == e.keyCode && e.isControlDown()) { + performCopy(); + return false; + } else if (java.awt.event.KeyEvent.VK_X == e.keyCode && e.isControlDown()) { + performCopy(); + performDelete(); + } else if (java.awt.event.KeyEvent.VK_V == e.keyCode && e.isControlDown()) { + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + DataFlavor dataFlavor = DataFlavor.stringFlavor; + if (clipboard.isDataFlavorAvailable(dataFlavor)) { + try { + String text = clipboard.getData(dataFlavor).toString(); + if(editModeHasSelection()) + deleteCurrentSelection(); + + currentText = currentText.substring(0, caretPosition) + text + currentText.substring(caretPosition, currentText.length()); + caretPosition += text.length(); + modificationClass = ModificationClass.AREA_INSERT; + } catch (UnsupportedFlavorException | IOException e1) { + } + } else { + return false; + } + } else if (java.awt.event.KeyEvent.VK_A == e.keyCode && e.isControlDown()) { + caretPosition = 0; + selectionOtherPosition = currentText.length(); + } else if (java.awt.event.KeyEvent.VK_Z == e.keyCode && e.isControlDown()) { + esm.undo(); + return false; + } else if (java.awt.event.KeyEvent.VK_Y == e.keyCode && e.isControlDown()) { + esm.redo(); + return false; + } else if (java.awt.event.KeyEvent.VK_ESCAPE == e.keyCode) { + esm.deactivateEdit(); + } else if (java.awt.event.KeyEvent.VK_LEFT == e.keyCode) { + if(!e.isShiftDown() && editModeHasSelection()) { + if(selectionOtherPosition < caretPosition) { + caretPosition = selectionOtherPosition; + } + editModeClearSelection(); + } else { + if(e.isShiftDown() && !editModeHasSelection()) { + selectionOtherPosition = caretPosition; + } + if(caretPosition > 0) { + caretPosition--; + } + } + } else if (java.awt.event.KeyEvent.VK_RIGHT == e.keyCode) { + if(!e.isShiftDown() && editModeHasSelection()) { + if(selectionOtherPosition > caretPosition) { + caretPosition = selectionOtherPosition; + } + editModeClearSelection(); + } else { + if(e.isShiftDown() && !editModeHasSelection()) { + selectionOtherPosition = caretPosition; + } + if(caretPosition < currentText.length()) { + caretPosition++; + } + } + } else if (java.awt.event.KeyEvent.VK_END == e.keyCode) { + if(e.isShiftDown()) { + if(!editModeHasSelection()) { + selectionOtherPosition = caretPosition; + } + } else { + editModeClearSelection(); + } + caretPosition = currentText.length(); + } else if (java.awt.event.KeyEvent.VK_HOME == e.keyCode) { + if(e.isShiftDown()) { + if(!editModeHasSelection()) { + selectionOtherPosition = caretPosition; + } + } else { + editModeClearSelection(); + } + caretPosition = 0; + } else if (java.awt.event.KeyEvent.VK_ENTER == e.keyCode) { + esm.applyEdit(); + esm.deactivateEdit(); + } else if(isAllowedCharacter(e)) { + if(editModeHasSelection()) + deleteCurrentSelection(); + currentText = currentText.substring(0, caretPosition) + e.character + currentText.substring(caretPosition, currentText.length()); + caretPosition++; + modificationClass = ModificationClass.SINGLE_INSERT; + } else { + return false; + } + + esm.paint(); + + return true; + + } + + void replace(EditorState other) { + base = other.base; + caretPosition = other.caretPosition; + currentText = other.currentText; + selectionOtherPosition = other.selectionOtherPosition; + } + + boolean shouldReplace(EditorState comparedTo) { + return modificationClass.equals(comparedTo.modificationClass); + } + + EditorState copy() { + EditorState result = new EditorState(); + result.replace(this); + return result; + } + + private boolean isAllowedCharacter(KeyPressedEvent e) { + char c = e.character; + if (c == 65535 || Character.getType(c) == Character.CONTROL) { + return false; + } + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((base == null) ? 0 : base.hashCode()); + result = prime * result + caretPosition; + result = prime * result + ((currentText == null) ? 0 : currentText.hashCode()); + result = prime * result + ((modificationClass == null) ? 0 : modificationClass.hashCode()); + result = prime * result + selectionOtherPosition; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + EditorState other = (EditorState) obj; + if (base == null) { + if (other.base != null) + return false; + } else if (!base.equals(other.base)) + return false; + if (caretPosition != other.caretPosition) + return false; + if (currentText == null) { + if (other.currentText != null) + return false; + } else if (!currentText.equals(other.currentText)) + return false; + if (modificationClass != other.modificationClass) + return false; + if (selectionOtherPosition != other.selectionOtherPosition) + return false; + return true; + } + +} \ No newline at end of file diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/EditorStateManager.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/EditorStateManager.java new file mode 100644 index 000000000..8ddb16ac7 --- /dev/null +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/EditorStateManager.java @@ -0,0 +1,258 @@ +package org.simantics.diagram.elements; + +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.LinkedList; +import java.util.List; + +import org.simantics.db.common.utils.Logger; +import org.simantics.diagram.elements.EditorState.ModificationClass; +import org.simantics.g2d.canvas.ICanvasContext; +import org.simantics.g2d.element.IElement; +import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent; +import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent; +import org.simantics.scenegraph.g2d.nodes.SingleElementNode; +import org.simantics.scl.runtime.function.Function1; +import org.simantics.scl.runtime.function.Function2; + +import com.kitfox.svg.SVGDiagram; +import com.kitfox.svg.SVGElement; +import com.kitfox.svg.SVGException; +import com.kitfox.svg.Text; +import com.kitfox.svg.Tspan; +import com.kitfox.svg.animation.AnimationElement; + +/** + * @author Antti Villberg + * @since 1.31.0 + */ +class EditorStateManager { + + static String TERM_STRING = "-----"; + static String EDITOR_CLASS = "edit"; + static String EDITOR_ID = "edit"; + private SVGNode node; + + private LinkedList editorState = null; + private int editorStateIndex = 0; + + EditorStateManager(SVGNode node) { + this.node = node; + } + + public boolean isEditMode() { + return editorState != null; + } + + public EditorState currentState() { + return editorState.get(editorStateIndex); + } + + public int currentHash() { + if(!isEditMode()) return 0; + return currentState().hashCode(); + } + + public void activateEditMode(SVGDiagram diagram, Text text) { + + if(isEditMode()) return; + + if(text.getId().length() == 0) return; + + EditorState es = new EditorState(); + es.base = new EditorStateStatic(); + es.base.textElementId = text.getId(); + + Tspan span = (Tspan)text.getContent().get(0); + String currentText = span.getText(); + + SingleElementNode sne = node.getSingleElementNode(); + Function1 fullTextFunction = sne.getParameter("textEditorFullText"); + if(fullTextFunction != null) + es.currentText = fullTextFunction.apply(es.base.textElementId); + if(es.currentText == null) { + es.currentText = currentText; + } + + es.caretPosition = es.currentText.length(); + es.selectionOtherPosition = 0; + + // Measure the Y-dimensions of the font + try { + span.setText("Ig"); + text.rebuild(); + diagram.updateTime(0); + es.base.verticalDimensions = text.getBoundingBox(); + span.setText(TERM_STRING); + text.rebuild(); + diagram.updateTime(0); + es.base.termStringWidth = text.getBoundingBox().getWidth(); + span.setText(currentText); + text.rebuild(); + diagram.updateTime(0); + } catch (SVGException e) { + e.printStackTrace(); + } + + ICanvasContext ctx = DiagramNodeUtil.getCanvasContext(node); + IElement ie = DiagramNodeUtil.getElement(ctx, sne); + + EditDataNode data = EditDataNode.getNode(node); + deactivateEdit(data, null); + TextEditActivation result = new TextEditActivation(0, ie, ctx); + data.setTextEditActivation(result); + + editorState = new LinkedList<>(); + editorState.push(es); + editorStateIndex = 0; + + paint(); + + } + + private TextEditActivation editActivation; + + void applyEdit() { + SingleElementNode sne = node.getSingleElementNode(); + EditorState es = currentState(); + Function2 editor = sne.getParameter("textEditor"); + if(editor != null) { + editor.apply(es.base.textElementId, es.currentText); + } + } + + protected boolean deactivateEdit() { + boolean result = deactivateEdit( editActivation ); + result |= editActivation != null; + editActivation = null; + editorState = null; + paint(); + return result; + } + + protected boolean deactivateEdit(TextEditActivation activation) { + return deactivateEdit( EditDataNode.getNode(node), activation ); + } + + protected boolean deactivateEdit(EditDataNode data, TextEditActivation activation) { + TextEditActivation previous = data.getTextEditActivation(); + if (previous != null && (previous == activation || activation == null)) { + previous.release(); + data.setTextEditActivation(null); + return true; + } + return false; + } + + protected boolean keyPressed(KeyPressedEvent e) { + if(isEditMode()) { + EditorState es = currentState(); + EditorState nes = es.copy(); + if(nes.keyPressed(this, e)) { + if(!isEditMode()) { + // This key actually terminated editing + return true; + } + if(nes.shouldReplace(es)) { + es.replace(nes); + } else { + while(editorState.size() > (editorStateIndex + 1)) + editorState.removeLast(); + editorState.add(nes); + editorStateIndex = editorState.size() - 1; + } + return true; + } + } + return false; + } + + + public boolean tryToStartEditMode(SVGDiagram diagram) { + SVGElement element = diagram.getElement(EDITOR_ID); + if(element != null && element instanceof Text) { + activateEditMode(diagram, (Text)element); + return true; + } + return false; + } + + public boolean tryToStartEditMode(SVGDiagram diagram, MouseClickEvent e) { + + if(diagram != null) { + + Point2D local = node.controlToLocal( e.controlPosition ); + // FIXME: once the event coordinate systems are cleared up, remove this workaround + local = node.parentToLocal(local); + + double tolerance = 2.0; + Rectangle2D pickRect = new Rectangle2D.Double(local.getX()-tolerance, local.getY()-tolerance, 2*tolerance, 2*tolerance); + + try { + List retVec = diagram.pick(pickRect, null); + for(int i=0;i l = (List)retVec.get(i); + for(int j=0;j 0 && currentState().modificationClass.equals(ModificationClass.NO_EDIT)) { + editorStateIndex--; + } + if(editorStateIndex > 0) + editorStateIndex--; + paint(); + } + + void redo() { + while(editorStateIndex < editorState.size() - 1 && currentState().modificationClass.equals(ModificationClass.NO_EDIT)) { + editorStateIndex++; + } + if(editorStateIndex < editorState.size() - 1) { + editorStateIndex++; + } + paint(); + } + +} \ No newline at end of file diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/EditorStateStatic.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/EditorStateStatic.java new file mode 100644 index 000000000..f7e579184 --- /dev/null +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/EditorStateStatic.java @@ -0,0 +1,51 @@ +package org.simantics.diagram.elements; + +import java.awt.geom.Rectangle2D; + +/** + * @author Antti Villberg + * @since 1.31.0 + */ +class EditorStateStatic { + + Rectangle2D verticalDimensions = null; + double termStringWidth = 0; + String textElementId = null; + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((textElementId == null) ? 0 : textElementId.hashCode()); + result = prime * result + ((verticalDimensions == null) ? 0 : verticalDimensions.hashCode()); + long temp; + temp = Double.doubleToLongBits(termStringWidth); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + EditorStateStatic other = (EditorStateStatic) obj; + if (textElementId == null) { + if (other.textElementId != null) + return false; + } else if (!textElementId.equals(other.textElementId)) + return false; + if (verticalDimensions == null) { + if (other.verticalDimensions != null) + return false; + } else if (!verticalDimensions.equals(other.verticalDimensions)) + return false; + if (Double.doubleToLongBits(termStringWidth) != Double.doubleToLongBits(other.termStringWidth)) + return false; + return true; + } + +} \ No newline at end of file diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SVGHolderNode.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/SVGHolderNode.java similarity index 99% rename from bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SVGHolderNode.java rename to bundles/org.simantics.diagram/src/org/simantics/diagram/elements/SVGHolderNode.java index dbf6b7559..ec26e3ab9 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SVGHolderNode.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/SVGHolderNode.java @@ -9,7 +9,7 @@ * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ -package org.simantics.scenegraph.g2d.nodes; +package org.simantics.diagram.elements; import java.awt.BasicStroke; import java.awt.Color; @@ -17,6 +17,7 @@ import java.awt.Graphics2D; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; + import org.simantics.scenegraph.g2d.G2DParentNode; import org.simantics.scenegraph.g2d.IG2DNode; diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/SVGImage.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/SVGImage.java new file mode 100644 index 000000000..839642dc8 --- /dev/null +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/SVGImage.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * 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.elements; + +import java.awt.Point; + +import org.simantics.g2d.image.Image; +import org.simantics.scenegraph.Node; +import org.simantics.scenegraph.g2d.G2DParentNode; +import org.simantics.utils.datastructures.cache.IFactory; +import org.simantics.utils.datastructures.cache.ProvisionException; + +/** + * This is a SVG implementation to the PaintableSymbol interface. + */ +public class SVGImage extends org.simantics.g2d.svg.SVGImage { + + public SVGImage(String nodeIdentifier, String document, Point targetSize) { + super(nodeIdentifier, document, targetSize); + } + + public SVGImage(String nodeIdentifier, String document) { + super(nodeIdentifier, document); + } + + public static IFactory createFactoryFromString(String nodeIdentifier, String svgDocument, Point targetSize) { + return new SVGFactory(nodeIdentifier, svgDocument, targetSize); + } + + static class SVGFactory implements IFactory { + String nodeIdentifier; + String document; + Point targetSize; + public SVGFactory(String nodeIdentifier, String document) { + this(nodeIdentifier, document, null); + } + public SVGFactory(String nodeIdentifier, String document, Point referenceSize) { + if (nodeIdentifier == null) + throw new NullPointerException("nodeIdentifier is null"); + if (document == null) + throw new NullPointerException("document is null"); + + this.nodeIdentifier = nodeIdentifier; + this.document = document; + this.targetSize = referenceSize; + } + @Override + public Image get() throws ProvisionException { + return new SVGImage(nodeIdentifier, document, targetSize); + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!obj.getClass().equals(getClass())) + return false; + + SVGFactory other = (SVGFactory)obj; + + if (!nodeIdentifier.equals(other.nodeIdentifier)) + return false; + if (targetSize != null) { + if (!targetSize.equals(other.targetSize)) + return false; + } else { + if (other.targetSize != null) + return false; + } + + return document.equals(other.document); + } + @Override + public int hashCode() { + return nodeIdentifier.hashCode() * 31 + document.hashCode() + 123; + } + } + + @Override + public Node init(G2DParentNode parent) { + // FIXME: mipmaps enabled here by default, since some apps just don't work without them. + // Figure out a way to pass the mipmap argument from above + + SVGNode node = parent.getOrCreateNode(nodeIdentifier, SVGNode.class); + node.setData(svgDocument); + node.setTargetSize(targetSize); + node.useMipMap(true); + + return node; + } + +} diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/SVGNode.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/SVGNode.java new file mode 100644 index 000000000..8084c2a41 --- /dev/null +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/SVGNode.java @@ -0,0 +1,153 @@ +/******************************************************************************* + * 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.elements; + +import java.awt.Graphics2D; +import java.util.List; +import java.util.Set; + +import org.simantics.g2d.canvas.ICanvasContext; +import org.simantics.g2d.diagram.participant.Selection; +import org.simantics.g2d.element.IElement; +import org.simantics.scenegraph.ExportableWidget.RasterOutputWidget; +import org.simantics.scenegraph.INode; +import org.simantics.scenegraph.g2d.IG2DNode; +import org.simantics.scenegraph.g2d.events.EventTypes; +import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent; +import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent; +import org.simantics.scenegraph.g2d.events.command.CommandEvent; +import org.simantics.scenegraph.g2d.nodes.SVGNodeAssignment; +import org.simantics.scenegraph.g2d.nodes.SingleElementNode; +import org.simantics.scenegraph.utils.NodeUtil; + +import com.kitfox.svg.SVGDiagram; +import com.kitfox.svg.SVGException; + +@RasterOutputWidget +public class SVGNode extends org.simantics.scenegraph.g2d.nodes.SVGNode { + + private static final long serialVersionUID = 4735066193941274186L; + + transient EditorStateManager esm = null; + transient boolean parametersProcessed = false; + + @Override + public void cleanup() { + if (esm != null) + removeEventHandler(this); + super.cleanup(); + } + + @Override + public void render(Graphics2D g2d) { + if (!parametersProcessed) { + SingleElementNode sne = getSingleElementNode(); + Boolean enableEditing = sne.getParameter("enableEditing"); + if (enableEditing != null && enableEditing) { + esm = new EditorStateManager(this); + addEventHandler(this); + } + parametersProcessed = true; + } + + super.render(g2d); + } + + @Override + protected int dynamicHash() { + return esm != null ? esm.currentHash() : 0; + } + + @Override + protected boolean applyAssignments(SVGDiagram diagram, List assignments) throws SVGException { + boolean changed = super.applyAssignments(diagram, assignments); + if (esm != null && !esm.applyEditMode(diagram)) + diagram.updateTime(0); + return changed; + } + + private boolean isSelected(IG2DNode node) { + ICanvasContext ctx = DiagramNodeUtil.getCanvasContext(this); + IElement ie = DiagramNodeUtil.getElement(ctx, node); + if (ie == null) + return false; + Selection sel = ctx.getAtMostOneItemOfClass(Selection.class); + if (sel != null) { + Set elems = sel.getSelection(0); + if (elems.size() == 1 && elems.contains(ie)) + return true; + } + return false; + } + + private boolean isSelected() { + SingleElementNode sne = getSingleElementNode(); + if (isSelected(sne)) + return true; + INode n = sne.getParent(); + while (n != null) { + if (isSelected((IG2DNode)n)) + return true; + n = n.getParent(); + } + return false; + } + + @Override + protected boolean mouseClicked(MouseClickEvent e) { + if(esm != null) { + if(esm.isEditMode()) { + esm.applyEdit(); + esm.deactivateEdit(); + return true; + } + if(isSelected()) { + if(esm.tryToStartEditMode(diagramCache, e)) + return true; + } + } + return super.mouseClicked(e); + } + + SingleElementNode getSingleElementNode() { + ICanvasContext ctx = DiagramNodeUtil.getCanvasContext(this); + // FIXME: needed only because eventdelegator registrations are done before adding node to scene graph. + if (ctx == null) + return null; + return (SingleElementNode)NodeUtil.getNearestParentOfType(this, SingleElementNode.class); + } + + @Override + protected boolean keyPressed(KeyPressedEvent e) { + if (esm != null && esm.keyPressed(e)) + return true; + return super.keyPressed(e); + } + + @Override + protected boolean handleCommand(CommandEvent e) { + if (esm != null + && !esm.isEditMode() + && "org.eclipse.ui.edit.rename".equals(e.command.id) + && isSelected() + && esm.tryToStartEditMode(diagramCache)) { + return true; + } + return super.handleCommand(e); + } + + @Override + public int getEventMask() { + return EventTypes.KeyPressedMask | EventTypes.MouseClickMask | EventTypes.CommandMask; + } + +} diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/flag/FlagSceneGraph.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/flag/FlagSceneGraph.java index 9895dd39f..3f0587f1d 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/flag/FlagSceneGraph.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/flag/FlagSceneGraph.java @@ -9,6 +9,7 @@ import java.awt.geom.Rectangle2D; import org.simantics.databoard.util.Bean; import org.simantics.diagram.adapter.FlagTextInfo; import org.simantics.diagram.adapter.SVGImageInfo; +import org.simantics.diagram.elements.SVGNode; import org.simantics.diagram.elements.TextNode; import org.simantics.g2d.element.ElementUtils; import org.simantics.g2d.element.IElement; @@ -22,7 +23,6 @@ import org.simantics.g2d.utils.Alignment; import org.simantics.scenegraph.Node; import org.simantics.scenegraph.g2d.G2DParentNode; import org.simantics.scenegraph.g2d.nodes.FlagNode; -import org.simantics.scenegraph.g2d.nodes.SVGNode; import org.simantics.ui.colors.Colors; import org.simantics.ui.fonts.Fonts; import org.simantics.utils.datastructures.hints.IHintContext.Key; diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/profile/ButtonNode.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/profile/ButtonNode.java index 2ad90acbe..96d42474b 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/profile/ButtonNode.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/profile/ButtonNode.java @@ -8,6 +8,7 @@ import java.awt.geom.Rectangle2D; import org.simantics.datatypes.literal.RGB; import org.simantics.datatypes.literal.Vec2d; import org.simantics.diagram.elements.DiagramNodeUtil; +import org.simantics.diagram.elements.SVGNode; import org.simantics.diagram.internal.Activator; import org.simantics.diagram.profile.ButtonResult.A; import org.simantics.diagram.profile.ButtonResult.B; @@ -16,7 +17,6 @@ import org.simantics.scenegraph.g2d.events.EventTypes; import org.simantics.scenegraph.g2d.events.MouseEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent; -import org.simantics.scenegraph.g2d.nodes.SVGNode; import org.simantics.scenegraph.utils.NodeUtil; import org.simantics.scl.runtime.function.Function1; diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/profile/IconNode.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/profile/IconNode.java index 94c99741d..9521cec75 100644 --- a/bundles/org.simantics.diagram/src/org/simantics/diagram/profile/IconNode.java +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/profile/IconNode.java @@ -6,10 +6,10 @@ import java.awt.geom.Rectangle2D; import org.simantics.datatypes.literal.RGB; import org.simantics.datatypes.literal.Vec2d; +import org.simantics.diagram.elements.SVGNode; import org.simantics.diagram.internal.Activator; import org.simantics.diagram.profile.IconResult.A; import org.simantics.diagram.profile.IconResult.B; -import org.simantics.scenegraph.g2d.nodes.SVGNode; public class IconNode extends IconButtonNode { diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/element/ElementHints.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/element/ElementHints.java index 9710fab68..dc8f91474 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/element/ElementHints.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/element/ElementHints.java @@ -20,14 +20,15 @@ import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.util.Collection; import java.util.HashMap; +import java.util.Map; import java.util.Set; import org.simantics.g2d.canvas.Hints; import org.simantics.g2d.connection.ConnectionEntity; import org.simantics.g2d.connection.IConnectionAdvisor; -import org.simantics.g2d.element.handler.SceneGraph; import org.simantics.g2d.element.handler.EdgeVisuals.ArrowType; import org.simantics.g2d.element.handler.EdgeVisuals.StrokeType; +import org.simantics.g2d.element.handler.SceneGraph; import org.simantics.g2d.image.Image; import org.simantics.g2d.utils.Alignment; import org.simantics.scenegraph.Node; @@ -56,6 +57,11 @@ public class ElementHints { */ public static final Key KEY_TRANSFORM = new KeyOf(AffineTransform.class, "TRANSFORM"); + /** + * For describing representation parameters of an element. + */ + public static final Key KEY_PARAMETERS = new KeyOf(Map.class, "PARAMETERS"); + /** * For defining the rectangular boundaries of an element. */ diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/element/ElementUtils.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/element/ElementUtils.java index 749343217..300e74d31 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/element/ElementUtils.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/element/ElementUtils.java @@ -49,6 +49,7 @@ import org.simantics.g2d.element.handler.Hover; import org.simantics.g2d.element.handler.InternalSize; import org.simantics.g2d.element.handler.Move; import org.simantics.g2d.element.handler.Outline; +import org.simantics.g2d.element.handler.Parameters; import org.simantics.g2d.element.handler.Parent; import org.simantics.g2d.element.handler.Pick; import org.simantics.g2d.element.handler.Resize; @@ -350,6 +351,18 @@ public class ElementUtils { e.getElementClass().getSingleItem(Transform.class).setTransform(e, at); } + public static void setParameters(IElement e, Map parameters) + { + Parameters ps = e.getElementClass().getSingleItem(Parameters.class); + if(ps != null) ps.setParameters(e, parameters); + } + + public static Map getParameters(IElement e) + { + Parameters ps = e.getElementClass().getAtMostOneItemOfClass(Parameters.class); + return ps != null ? ps.getParameters(e) : null; + } + public static AffineTransform getInvTransform(IElement e) { try { diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/element/handler/Parameters.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/element/handler/Parameters.java new file mode 100644 index 000000000..c903e432d --- /dev/null +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/element/handler/Parameters.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2017 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: + * Semantum Oy - initial API and implementation + *******************************************************************************/ +package org.simantics.g2d.element.handler; + +import java.util.Map; + +import org.simantics.g2d.element.IElement; + +/** + * @author Antti Villberg + * @since 1.31.0 + */ +public interface Parameters extends ElementHandler { + + /** + * @return A map of element parameters. A null value is allowed + * which indicates no parameters. + */ + Map getParameters(IElement e); + + /** + * @param p + * the parameter map for an element. A null value is + * allowed which indicates no parameters. + */ + void setParameters(IElement e, Map p); + +} diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/element/handler/impl/DefaultParameters.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/element/handler/impl/DefaultParameters.java new file mode 100644 index 000000000..1b7b45303 --- /dev/null +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/element/handler/impl/DefaultParameters.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2017 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: + * Semantum Oy - initial API and implementation + *******************************************************************************/ +package org.simantics.g2d.element.handler.impl; + +import java.util.Map; + +import org.simantics.g2d.element.ElementHints; +import org.simantics.g2d.element.IElement; +import org.simantics.g2d.element.handler.Parameters; + +/** + * @author Antti Villberg + * @since 1.31.0 + */ +public class DefaultParameters implements Parameters { + + private static final long serialVersionUID = -9138310692519364097L; + + public static final DefaultParameters INSTANCE = new DefaultParameters(); + + @Override + public Map getParameters(IElement e) { + return e.getHint(ElementHints.KEY_PARAMETERS); + } + + @Override + public void setParameters(IElement e, Map p) { + if (p != null) { + e.setHint(ElementHints.KEY_PARAMETERS, p); + } else { + e.removeHint(ElementHints.KEY_PARAMETERS); + } + } + +} diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/AnimatedNode.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/AnimatedNode.java new file mode 100644 index 000000000..4bdd53424 --- /dev/null +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/AnimatedNode.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2017 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: + * Semantum Oy - initial API and implementation + *******************************************************************************/ +package org.simantics.g2d.elementclass; + +/** + * @author Antti Villberg + * @since 1.31.0 + */ +public interface AnimatedNode { +} diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/PlainElementPropertySetter.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/PlainElementPropertySetter.java index 250078736..f009dcfac 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/PlainElementPropertySetter.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/elementclass/PlainElementPropertySetter.java @@ -19,7 +19,6 @@ import org.simantics.scenegraph.INode; import org.simantics.scenegraph.Node; import org.simantics.scenegraph.g2d.G2DParentNode; import org.simantics.scenegraph.g2d.IG2DNode; -import org.simantics.scenegraph.g2d.nodes.AnimatedSVGNode; import org.simantics.scenegraph.utils.NodeUtil; import org.simantics.utils.datastructures.hints.IHintContext.Key; import org.simantics.utils.datastructures.hints.IHintContext.KeyOf; @@ -84,7 +83,7 @@ public class PlainElementPropertySetter implements PropertySetter { } private Node findAnimatedNode(Node node) { - Node animated = findSingle(node, AnimatedSVGNode.class); + Node animated = findSingle(node, AnimatedNode.class); if(animated != null) return animated; else return node; } diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/image/impl/ImageURLFactory.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/image/impl/ImageURLFactory.java index bd3d3e569..3cea842cd 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/image/impl/ImageURLFactory.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/image/impl/ImageURLFactory.java @@ -13,6 +13,7 @@ package org.simantics.g2d.image.impl; import java.awt.image.BufferedImage; import java.io.IOException; +import java.io.InputStream; import java.net.URL; import javax.imageio.ImageIO; @@ -40,7 +41,14 @@ public class ImageURLFactory implements IFactory { if (url==null) throw new IllegalArgumentException("null arg"); this.url = url; } + + public static Image loadFromURL(String nodeIdentifier, URL url) throws IOException { + try (InputStream in = url.openStream()) { + return new SVGImage(nodeIdentifier, in); + } + } + @Override public Image get() throws ProvisionException { boolean svg = url.getFile().toLowerCase().endsWith(".svg"); diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/svg/SVGImage.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/svg/SVGImage.java index 54ceeec41..e8d2456c1 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/svg/SVGImage.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/svg/SVGImage.java @@ -36,16 +36,13 @@ public class SVGImage implements Image { static EnumSet caps = EnumSet.of(Feature.Vector); private Rectangle2D bounds; - private final String nodeIdentifier; - private final String svgDocument; - private Point targetSize; + protected final String nodeIdentifier; + protected final String svgDocument; + protected Point targetSize; public static SVGImage loadFromURL(String nodeIdentifier, URL url) throws IOException { - InputStream in = url.openStream(); - try { + try (InputStream in = url.openStream()) { return new SVGImage(nodeIdentifier, in); - } finally { - in.close(); } } diff --git a/bundles/org.simantics.modeling.ui/plugin.xml b/bundles/org.simantics.modeling.ui/plugin.xml index 363cb9894..578c752ba 100644 --- a/bundles/org.simantics.modeling.ui/plugin.xml +++ b/bundles/org.simantics.modeling.ui/plugin.xml @@ -750,9 +750,20 @@ + + + + + + + + @@ -762,9 +773,20 @@ + + + + + + + + diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagram/style/DocumentDecorationStyle.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagram/style/DocumentDecorationStyle.java index e2d0a2b13..bb626c864 100644 --- a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagram/style/DocumentDecorationStyle.java +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagram/style/DocumentDecorationStyle.java @@ -20,6 +20,8 @@ import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.variable.Variable; +import org.simantics.diagram.elements.DecorationSVGNode; +import org.simantics.diagram.elements.SVGNode; import org.simantics.diagram.profile.StyleBase; import org.simantics.diagram.synchronization.graph.DiagramGraphUtil; import org.simantics.document.DocumentResource; @@ -27,8 +29,6 @@ import org.simantics.modeling.ModelingResources; import org.simantics.modeling.ui.Activator; import org.simantics.scenegraph.INode; import org.simantics.scenegraph.g2d.nodes.Decoration; -import org.simantics.scenegraph.g2d.nodes.DecorationSVGNode; -import org.simantics.scenegraph.g2d.nodes.SVGNode; import org.simantics.scenegraph.profile.EvaluationContext; import org.simantics.scenegraph.profile.common.ProfileVariables; import org.simantics.scenegraph.utils.NodeUtil; diff --git a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagram/style/IssueDecorationStyle.java b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagram/style/IssueDecorationStyle.java index c1f7cc648..0200af96e 100644 --- a/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagram/style/IssueDecorationStyle.java +++ b/bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/diagram/style/IssueDecorationStyle.java @@ -25,6 +25,8 @@ import org.simantics.db.common.procedure.adapter.TransientCacheListener; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.variable.Variable; import org.simantics.db.layer0.variable.Variables; +import org.simantics.diagram.elements.DecorationSVGNode; +import org.simantics.diagram.elements.SVGNode; import org.simantics.diagram.profile.StyleBase; import org.simantics.diagram.synchronization.graph.DiagramGraphUtil; import org.simantics.issues.Severity; @@ -35,8 +37,6 @@ import org.simantics.modeling.ui.Activator; import org.simantics.modeling.ui.diagram.style.IssueDecorationStyle.IssueResult; import org.simantics.scenegraph.INode; import org.simantics.scenegraph.g2d.nodes.Decoration; -import org.simantics.scenegraph.g2d.nodes.DecorationSVGNode; -import org.simantics.scenegraph.g2d.nodes.SVGNode; import org.simantics.scenegraph.profile.EvaluationContext; import org.simantics.scenegraph.profile.common.ProfileVariables; import org.simantics.scenegraph.utils.NodeUtil; diff --git a/bundles/org.simantics.modeling/src/org/simantics/modeling/SCLScenegraph.java b/bundles/org.simantics.modeling/src/org/simantics/modeling/SCLScenegraph.java index dd09f9ebf..09999bdd2 100644 --- a/bundles/org.simantics.modeling/src/org/simantics/modeling/SCLScenegraph.java +++ b/bundles/org.simantics.modeling/src/org/simantics/modeling/SCLScenegraph.java @@ -36,6 +36,7 @@ import org.simantics.db.Resource; import org.simantics.db.common.request.UnaryRead; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.RuntimeDatabaseException; +import org.simantics.diagram.elements.DecorationSVGNode; import org.simantics.diagram.elements.DiagramNodeUtil; import org.simantics.diagram.elements.TextGridNode; import org.simantics.diagram.elements.TextNode; @@ -61,7 +62,6 @@ import org.simantics.scenegraph.g2d.nodes.BackgroundNode; import org.simantics.scenegraph.g2d.nodes.BoundsNode; import org.simantics.scenegraph.g2d.nodes.ConnectionNode; import org.simantics.scenegraph.g2d.nodes.DataNode; -import org.simantics.scenegraph.g2d.nodes.DecorationSVGNode; import org.simantics.scenegraph.g2d.nodes.NavigationNode; import org.simantics.scenegraph.g2d.nodes.SVGNode; import org.simantics.scenegraph.g2d.nodes.SelectionNode; diff --git a/bundles/org.simantics.scenegraph.swing/src/org/simantics/scenegraph/example/SampleThread.java b/bundles/org.simantics.scenegraph.swing/src/org/simantics/scenegraph/example/SampleThread.java index 67b785f25..46494549b 100644 --- a/bundles/org.simantics.scenegraph.swing/src/org/simantics/scenegraph/example/SampleThread.java +++ b/bundles/org.simantics.scenegraph.swing/src/org/simantics/scenegraph/example/SampleThread.java @@ -23,8 +23,8 @@ import java.util.List; import javax.swing.BorderFactory; import javax.swing.JSlider; + import org.simantics.scenegraph.g2d.G2DSceneGraph; -import org.simantics.scenegraph.g2d.nodes.AnimatedSVGNode; import org.simantics.scenegraph.g2d.nodes.GridNode; import org.simantics.scenegraph.g2d.nodes.NavigationNode; import org.simantics.scenegraph.g2d.nodes.RulerNode; @@ -85,38 +85,38 @@ public class SampleThread implements Runnable { grid.setZIndex(1); // AnimatedSVGNode bmw = null; - AnimatedSVGNode svg1 = null; - AnimatedSVGNode svg2 = null; - AnimatedSVGNode svg3 = null; - AnimatedSVGNode svg4 = null; - try { - String data = loadSVG("tynnyri.svg"); - svg1 = nav.addNode(AnimatedSVGNode.class); - svg1.setZIndex(20); - svg1.setData(data); - svg1.setTransform(AffineTransform.getTranslateInstance(0, 150)); - svg1.useMipMap(false); - - svg2 = nav.addNode(AnimatedSVGNode.class); - svg2.setZIndex(20); - svg2.setData(data); - svg2.setTransform(AffineTransform.getTranslateInstance(250, 150)); - svg2.useMipMap(false); - - svg3 = nav.addNode(AnimatedSVGNode.class); - svg3.setZIndex(20); - svg3.setData(data); - svg3.setTransform(AffineTransform.getTranslateInstance(500, 150)); - svg3.useMipMap(false); - - svg4 = nav.addNode(AnimatedSVGNode.class); - svg4.setZIndex(20); - svg4.setData(data); - svg4.setTransform(AffineTransform.getTranslateInstance(750, 150)); - svg4.useMipMap(false); - } catch(IOException e) { - e.printStackTrace(); - } +// AnimatedSVGNode svg1 = null; +// AnimatedSVGNode svg2 = null; +// AnimatedSVGNode svg3 = null; +// AnimatedSVGNode svg4 = null; +// try { +// String data = loadSVG("tynnyri.svg"); +// svg1 = nav.addNode(AnimatedSVGNode.class); +// svg1.setZIndex(20); +// svg1.setData(data); +// svg1.setTransform(AffineTransform.getTranslateInstance(0, 150)); +// svg1.useMipMap(false); +// +// svg2 = nav.addNode(AnimatedSVGNode.class); +// svg2.setZIndex(20); +// svg2.setData(data); +// svg2.setTransform(AffineTransform.getTranslateInstance(250, 150)); +// svg2.useMipMap(false); +// +// svg3 = nav.addNode(AnimatedSVGNode.class); +// svg3.setZIndex(20); +// svg3.setData(data); +// svg3.setTransform(AffineTransform.getTranslateInstance(500, 150)); +// svg3.useMipMap(false); +// +// svg4 = nav.addNode(AnimatedSVGNode.class); +// svg4.setZIndex(20); +// svg4.setData(data); +// svg4.setTransform(AffineTransform.getTranslateInstance(750, 150)); +// svg4.useMipMap(false); +// } catch(IOException e) { +// e.printStackTrace(); +// } // try { // bmw = nav.addNode(AnimatedSVGNode.class); @@ -296,6 +296,7 @@ public class SampleThread implements Runnable { t2 = (float)v/1000; }}); + /* svg1.setScript(script1); svg2.setScript(script1); @@ -321,7 +322,8 @@ public class SampleThread implements Runnable { e.printStackTrace(); } } - + */ + close(); } diff --git a/bundles/org.simantics.scenegraph/META-INF/MANIFEST.MF b/bundles/org.simantics.scenegraph/META-INF/MANIFEST.MF index 610990a02..e6218a136 100644 --- a/bundles/org.simantics.scenegraph/META-INF/MANIFEST.MF +++ b/bundles/org.simantics.scenegraph/META-INF/MANIFEST.MF @@ -22,6 +22,7 @@ Bundle-ClassPath: ., lib/batik-awt-util-1.8.jar, lib/batik-util-1.8.jar Export-Package: com.kitfox.svg, + com.kitfox.svg.animation, com.kitfox.svg.xml, org.simantics.scenegraph, org.simantics.scenegraph.adapters, diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SVGNode.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SVGNode.java index ec85fd75c..db491a3fd 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SVGNode.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SVGNode.java @@ -24,6 +24,7 @@ import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -64,16 +65,21 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { protected Point targetSize = null; protected Boolean useMipMap = true; protected Rectangle2D bounds = null; - + protected List assignments = new ArrayList(); - transient BufferedImage buffer = null; - transient String documentCache = null; - transient SVGDiagram diagramCache = null; - transient String dataHash = null; + protected transient BufferedImage buffer = null; + protected transient String documentCache = null; + protected transient SVGDiagram diagramCache = null; + protected transient String dataHash = null; static transient Map> bufferCache = new HashMap>(); + @Override + public void init() { + super.init(); + } + @Override public void cleanup() { cleanDiagramCache(); @@ -159,6 +165,10 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { g2d.setTransform(ot); } + protected int dynamicHash() { + return 0; + } + protected String parseSVG() { if (data == null) return null; @@ -173,7 +183,6 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { diagramCache = null; } - // Lets check for rootAssignment that contributes the whole SVG SVGNodeAssignment rootAssignment = null; if (!assignments.isEmpty()) { @@ -191,7 +200,7 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { // NOTE: hard-coded to assume all SVG data is encoded in UTF-8 dataBytes = data.getBytes("UTF-8"); } - dataHash = digest(dataBytes, assignments); + dataHash = digest(dataBytes, assignments, dynamicHash()); URI uri = univ.loadSVG(new ByteArrayInputStream(dataBytes), dataHash); diagramCache = univ.getDiagram(uri, false); @@ -208,7 +217,7 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { bbox = root.getBoundingBox(); if (bbox.isEmpty()) { // Lets check if this should be visible or not - Set presentationAttributes = root.getPresentationAttributes(); + Set presentationAttributes = root.getPresentationAttributes(); if (!presentationAttributes.contains("display")) { // TODO: fix this - How can one read values of attributes in SVG salamander??? univ.decRefCount(diagramCache.getXMLBase()); @@ -246,14 +255,13 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { return dataHash; } - private static boolean applyAssignments(SVGDiagram diagram, List assignments) throws SVGException { + protected boolean applyAssignments(SVGDiagram diagram, List assignments) throws SVGException { if (assignments.isEmpty()) return false; + boolean changed = false; + for (SVGNodeAssignment ass : assignments) { -// System.err.println("assign: " + ass.elementId + " " + ass.attributeNameOrId + " " + ass.value); -// if("opacity".equals(ass.attributeNameOrId)) -// System.err.println("faaf"); SVGElement e = diagram.getElement(ass.elementId); if (e != null) { if ("$text".equals(ass.attributeNameOrId)) { @@ -269,21 +277,28 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { ((Text) parent).rebuild(); changed = true; } + } else if (ass.attributeNameOrId.startsWith("#")) { + e.setAttribute(ass.attributeNameOrId.substring(1), AnimationElement.AT_CSS, ass.value); + changed = true; } else { e.setAttribute(ass.attributeNameOrId, AnimationElement.AT_AUTO, ass.value); changed = true; } } } - diagram.updateTime(0); + return changed; } public static Rectangle2D getBounds(String data) { - return getBounds(data, null); + return getBounds(data, 0); + } + + public static Rectangle2D getBounds(String data, int dynamicHash) { + return getBounds(data, Collections.emptyList(), dynamicHash); } - public static Rectangle2D getBounds(String data, List assignments) { + public static Rectangle2D getBounds(String data, List assignments, int dynamicHash) { if (data == null) { new Exception("null SVG data").printStackTrace(); return null; @@ -293,7 +308,7 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { try { // NOTE: hard-coded to assume all SVG data is encoded in UTF-8 byte[] dataBytes = data.getBytes("UTF-8"); - String digest = digest(dataBytes, assignments); + String digest = digest(dataBytes, assignments, dynamicHash); SVGUniverse univ = SVGCache.getSVGUniverse(); // TODO: this completely removes any parallel processing from the SVG loading which would be nice to have. @@ -327,10 +342,10 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { } public static Rectangle2D getRealBounds(String data) { - return getRealBounds(data, null); + return getRealBounds(data, Collections.emptyList(), 0); } - public static Rectangle2D getRealBounds(String data, List assignments) { + public static Rectangle2D getRealBounds(String data, List assignments, int dynamicHash) { if (data == null) { new Exception("null SVG data").printStackTrace(); return null; @@ -340,7 +355,7 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { try { // NOTE: hard-coded to assume all SVG data is encoded in UTF-8 byte[] dataBytes = data.getBytes("UTF-8"); - String digest = digest(dataBytes, assignments); + String digest = digest(dataBytes, assignments, dynamicHash); SVGUniverse univ = SVGCache.getSVGUniverse(); // TODO: this completely removes any parallel processing from the SVG loading which would be nice to have. @@ -362,13 +377,12 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { } protected void initBuffer(Graphics2D g2d) { - if (!data.equals(documentCache) || diagramCache == null) { - dataHash = parseSVG(); - if (diagramCache == null) { - System.err.println("UNABLE TO PARSE SVG:\n" + data); - return; - } + dataHash = parseSVG(); + if (diagramCache == null) { + System.err.println("UNABLE TO PARSE SVG:\n" + data); + return; + } } if (buffer != null) { @@ -380,14 +394,14 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { } else if(diagramCache.getViewRect().getWidth()==0 || diagramCache.getViewRect().getHeight()==0) { buffer = null; } else if(useMipMap) { - if(G2DUtils.isAccelerated(g2d)) { + if(G2DUtils.isAccelerated(g2d)) { buffer = new MipMapVRamBufferedImage(diagramCache, bounds, targetSize); } else { buffer = new MipMapBufferedImage(diagramCache, bounds, targetSize); } bufferCache.put(dataHash, new WeakReference(buffer)); } else { - if(G2DUtils.isAccelerated(g2d)) { + if(G2DUtils.isAccelerated(g2d)) { buffer = new VRamBufferedImage(diagramCache, bounds, targetSize); } else { buffer = new BufferedImage(diagramCache, bounds, targetSize); @@ -419,13 +433,13 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { } static WeakHashMap digestCache = new WeakHashMap(); - - static String digest(byte[] dataBytes, List assignments) { + + static String digest(byte[] dataBytes, List assignments, int dynamicHash) { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] messageDigest = md.digest(dataBytes); BigInteger number = new BigInteger(1, messageDigest); - String dataHash = number.toString(16) + (assignments != null ? assignments.hashCode() : 0); + String dataHash = number.toString(16) + (assignments != null ? assignments.hashCode() : 0) + 31 * dynamicHash; String result = digestCache.get(dataHash); if(result == null) { result = dataHash; @@ -462,12 +476,12 @@ public class SVGNode extends G2DNode implements InitValueSupport, LoaderNode { public void synchronizeTransform(double[] data) { this.setTransform(new AffineTransform(data)); } - + public String getSVGText() { String ret = data.replace("", "g>"); //return diagramCache.toString(); //return data.replace("", "/g>"); return ret; } - + } diff --git a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SingleElementNode.java b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SingleElementNode.java index 61353557d..29ef9954e 100644 --- a/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SingleElementNode.java +++ b/bundles/org.simantics.scenegraph/src/org/simantics/scenegraph/g2d/nodes/SingleElementNode.java @@ -16,7 +16,9 @@ import java.awt.Composite; import java.awt.Graphics2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +import java.util.Map; +import org.simantics.scenegraph.INode; import org.simantics.scenegraph.g2d.G2DRenderingHints; import org.simantics.scenegraph.g2d.IG2DNode; import org.simantics.scenegraph.g2d.events.EventTypes; @@ -35,6 +37,7 @@ public class SingleElementNode extends TransformNode implements InitValueSupport protected Boolean hidden = Boolean.FALSE; private transient Object key; private transient String typeClass; + private transient Map parameters; public void setKey(Object key) { this.key = key; @@ -52,6 +55,23 @@ public class SingleElementNode extends TransformNode implements InitValueSupport return typeClass; } + public void setParameters(Map parameters) { + this.parameters = parameters; + } + + public T getParameter(String key) { + if (parameters != null) { + @SuppressWarnings("unchecked") + T t = (T) parameters.get(key); + if(t != null) return t; + } + INode parent = NodeUtil.getNearestParentOfType(this, SingleElementNode.class); + if (parent instanceof SingleElementNode) { + return ((SingleElementNode)parent).getParameter(key); + } + return null; + } + public void setTransferableProvider(TransferableProvider transferableProvider) { if (transferableProvider != this.transferableProvider) { if (this.transferableProvider != null) diff --git a/bundles/org.simantics.scl.osgi/src/org/simantics/scl/osgi/internal/BundleModuleSource.java b/bundles/org.simantics.scl.osgi/src/org/simantics/scl/osgi/internal/BundleModuleSource.java index 3213cf9b5..92385a1ed 100644 --- a/bundles/org.simantics.scl.osgi/src/org/simantics/scl/osgi/internal/BundleModuleSource.java +++ b/bundles/org.simantics.scl.osgi/src/org/simantics/scl/osgi/internal/BundleModuleSource.java @@ -1,13 +1,12 @@ package org.simantics.scl.osgi.internal; +import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; @@ -126,15 +125,24 @@ public class BundleModuleSource extends EncodedTextualModuleSource implements Up } } } - - private Path getPath() throws IOException { - try { - return Paths.get(FileLocator.toFileURL(url).toURI()); - } catch (URISyntaxException e) { - throw new IOException(e); + + /* + * This code is a copy from org.simantics.utils.ui.BundleUtils + */ + public static File resolveWritableBundleFile(URL url) throws IOException { + // This returns file, jar, http etc. - essentially resolves the bundle protocol + URL resolved = FileLocator.resolve(url); + if (resolved.getProtocol().equals("file")) { + return new File(resolved.getPath()); } + return null; } - + + private Path getPath() throws IOException { + File file = resolveWritableBundleFile(url); + return file != null ? file.toPath() : null; + } + @Override public boolean isUpdateable() { try { diff --git a/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/BundleUtils.java b/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/BundleUtils.java index f3e3383f6..62fd287c1 100644 --- a/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/BundleUtils.java +++ b/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/BundleUtils.java @@ -90,7 +90,7 @@ public final class BundleUtils { public static URL find(String bundleId, String path) { return find(Platform.getBundle(bundleId), path); } - + public static File findFile(String bundleId, String path) throws IOException { URL url = find(bundleId, path); if (url == null) @@ -98,5 +98,17 @@ public final class BundleUtils { url = FileLocator.toFileURL(url); return new File(url.getPath()); } - + + /** + * @since 1.31.0 + */ + public static File resolveWritableBundleFile(URL url) throws IOException { + // This returns file, jar, http etc. - essentially resolves the bundle protocol + URL resolved = FileLocator.resolve(url); + if (resolved.getProtocol().equals("file")) { + return new File(resolved.getPath()); + } + return null; + } + }