1 /*******************************************************************************
2 * Copyright (c) 2017 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * Semantum Oy - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.diagram.elements;
14 import java.awt.geom.Point2D;
15 import java.awt.geom.Rectangle2D;
16 import java.util.LinkedList;
17 import java.util.List;
19 import org.simantics.db.common.utils.Logger;
20 import org.simantics.diagram.elements.EditorState.ModificationClass;
21 import org.simantics.g2d.canvas.ICanvasContext;
22 import org.simantics.g2d.element.IElement;
23 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
24 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;
25 import org.simantics.scenegraph.g2d.nodes.SingleElementNode;
26 import org.simantics.scl.runtime.function.Function1;
27 import org.simantics.scl.runtime.function.Function3;
28 import org.simantics.scl.runtime.tuple.Tuple4;
30 import com.kitfox.svg.SVGDiagram;
31 import com.kitfox.svg.SVGElement;
32 import com.kitfox.svg.SVGException;
33 import com.kitfox.svg.Text;
34 import com.kitfox.svg.Tspan;
35 import com.kitfox.svg.animation.AnimationElement;
38 * @author Antti Villberg
41 class EditorStateManager {
43 static String TERM_STRING = "-----";
44 static String EDITOR_CLASS = "edit";
45 static String EDITOR_ID = "edit";
48 private LinkedList<EditorState> editorState = null;
49 private int editorStateIndex = 0;
51 static class SVGMeasurementContextImpl implements SVGMeasurementContext {
53 public SVGMeasurementContextImpl(SVGNode node) {
58 public Tuple4 getBoundingBox(String id) {
60 Rectangle2D rect = node.getElementBounds(id);
61 if(rect == null) return null;
62 return new Tuple4(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
63 } catch (SVGException e) {
69 public void modifyText(String id, String newText) {
70 node.modifyTextElement(id, newText);
74 EditorStateManager(SVGNode node) {
78 public boolean isEditMode() {
79 return editorState != null;
82 public EditorState currentState() {
83 return editorState.get(editorStateIndex);
86 public int currentHash() {
87 if(!isEditMode()) return 0;
88 return currentState().hashCode();
91 public void activateEditMode(SVGDiagram diagram, Text text) {
93 if(isEditMode()) return;
95 if(text.getId().length() == 0) return;
97 EditorState es = new EditorState();
98 es.base = new EditorStateStatic();
99 es.base.textElementId = text.getId();
101 Tspan span = (Tspan)text.getContent().get(0);
102 String currentText = span.getText();
104 SingleElementNode sne = node.getSingleElementNode();
108 Function1<String,String> fullTextFunction = sne.getParameter("textEditorFullText");
109 if(fullTextFunction != null)
110 es.currentText = fullTextFunction.apply(es.base.textElementId);
111 if(es.currentText == null) {
112 es.currentText = currentText;
115 es.caretPosition = es.currentText.length();
116 es.selectionOtherPosition = 0;
118 // Measure the Y-dimensions of the font
122 diagram.updateTime(0);
123 es.base.verticalDimensions = text.getBoundingBox();
124 span.setText(TERM_STRING);
126 diagram.updateTime(0);
127 es.base.termStringWidth = text.getBoundingBox().getWidth();
128 span.setText(currentText);
130 diagram.updateTime(0);
131 } catch (SVGException e) {
135 ICanvasContext ctx = DiagramNodeUtil.getCanvasContext(node);
136 IElement ie = DiagramNodeUtil.getElement(ctx, sne);
138 EditDataNode data = EditDataNode.getNode(node);
139 deactivateEdit(data, null);
140 TextEditActivation result = new TextEditActivation(0, ie, ctx);
141 data.setTextEditActivation(result);
143 editorState = new LinkedList<>();
144 editorState.push(es);
145 editorStateIndex = 0;
151 private TextEditActivation editActivation;
154 SingleElementNode sne = node.getSingleElementNode();
156 EditorState es = currentState();
157 Function3<SVGMeasurementContext,String,String,Object> editor = sne.getParameter("textEditor");
159 editor.apply(new SVGMeasurementContextImpl(node), es.base.textElementId, es.currentText);
164 protected boolean deactivateEdit() {
165 boolean result = deactivateEdit( editActivation );
166 result |= editActivation != null;
167 editActivation = null;
173 protected boolean deactivateEdit(TextEditActivation activation) {
174 return deactivateEdit( EditDataNode.getNode(node), activation );
177 protected boolean deactivateEdit(EditDataNode data, TextEditActivation activation) {
178 TextEditActivation previous = data.getTextEditActivation();
179 if (previous != null && (previous == activation || activation == null)) {
181 data.setTextEditActivation(null);
187 protected boolean keyPressed(KeyPressedEvent e) {
189 EditorState es = currentState();
190 EditorState nes = es.copy();
191 if(nes.keyPressed(this, e)) {
193 // This key actually terminated editing
196 if(nes.shouldReplace(es)) {
199 while(editorState.size() > (editorStateIndex + 1))
200 editorState.removeLast();
201 editorState.add(nes);
202 editorStateIndex = editorState.size() - 1;
211 public boolean tryToStartEditMode(SVGDiagram diagram) {
212 SVGElement element = diagram.getElement(EDITOR_ID);
213 if(element != null && element instanceof Text) {
214 activateEditMode(diagram, (Text)element);
220 public boolean tryToStartEditMode(SVGDiagram diagram, MouseClickEvent e) {
222 if(diagram != null) {
224 Point2D local = node.controlToLocal( e.controlPosition );
225 // FIXME: once the event coordinate systems are cleared up, remove this workaround
226 local = node.parentToLocal(local);
228 double tolerance = 2.0;
229 Rectangle2D pickRect = new Rectangle2D.Double(local.getX()-tolerance, local.getY()-tolerance, 2*tolerance, 2*tolerance);
232 List<?> retVec = diagram.pick(pickRect, null);
233 for(int i=0;i<retVec.size();i++) {
234 List<?> l = (List<?>)retVec.get(i);
235 for(int j=0;j<l.size();j++) {
236 SVGElement element = (SVGElement)l.get(j);
237 if(element instanceof Tspan) {
240 if(element instanceof Text) {
241 Text text = (Text)element;
242 if(text.hasAttribute("class", AnimationElement.AT_XML)) {
243 String clazz = text.getPresAbsolute("class").getStringValue();
244 if(clazz.contains(EDITOR_CLASS)) {
245 activateEditMode(diagram, text);
253 } catch (SVGException e1) {
254 Logger.defaultLogError(e1);
262 boolean applyEditMode(SVGDiagram diagram) throws SVGException {
265 EditorState es = currentState();
266 es.applyEditMode(diagram);
275 node.cleanDiagramCache();
280 while(editorStateIndex > 0 && currentState().modificationClass.equals(ModificationClass.NO_EDIT)) {
283 if(editorStateIndex > 0)
289 while(editorStateIndex < editorState.size() - 1 && currentState().modificationClass.equals(ModificationClass.NO_EDIT)) {
292 if(editorStateIndex < editorState.size() - 1) {