]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.diagram/src/org/simantics/diagram/elements/EditorStateManager.java
Supply SVG text editor with element measurement context
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / elements / EditorStateManager.java
1 /*******************************************************************************
2  * Copyright (c) 2017 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     Semantum Oy - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.diagram.elements;
13
14 import java.awt.geom.Point2D;
15 import java.awt.geom.Rectangle2D;
16 import java.util.LinkedList;
17 import java.util.List;
18
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;
29
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;
36
37 /**
38  * @author Antti Villberg
39  * @since 1.31.0
40  */
41 class EditorStateManager {
42
43         static String TERM_STRING = "-----";
44         static String EDITOR_CLASS = "edit";
45         static String EDITOR_ID = "edit";
46         private SVGNode node;
47
48         private LinkedList<EditorState> editorState = null;
49         private int editorStateIndex = 0;
50
51         static class SVGMeasurementContextImpl implements SVGMeasurementContext {
52                 private SVGNode node;
53                 public SVGMeasurementContextImpl(SVGNode node) {
54                         this.node = node;
55                 }
56
57                 @Override
58                 public Tuple4 getBoundingBox(String id) {
59                         try {
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) {
64                                 return null;
65                         }
66                 }
67
68                 @Override
69                 public void modifyText(String id, String newText) {
70                         node.modifyTextElement(id, newText);
71                 }
72         }
73
74         EditorStateManager(SVGNode node) {
75                 this.node = node;
76         }
77
78         public boolean isEditMode() {
79                 return editorState != null;
80         }
81
82         public EditorState currentState() {
83                 return editorState.get(editorStateIndex);
84         }
85
86         public int currentHash() {
87                 if(!isEditMode()) return 0;
88                 return currentState().hashCode();
89         }
90
91         public void activateEditMode(SVGDiagram diagram, Text text) {
92
93                 if(isEditMode()) return;
94
95                 if(text.getId().length() == 0) return;
96
97                 EditorState es = new EditorState();
98                 es.base = new EditorStateStatic();
99                 es.base.textElementId = text.getId();
100
101                 Tspan span = (Tspan)text.getContent().get(0);
102                 String currentText = span.getText();
103
104                 SingleElementNode sne = node.getSingleElementNode();
105                 if (sne == null)
106                         return;
107
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;
113                 }
114
115                 es.caretPosition = es.currentText.length();
116                 es.selectionOtherPosition = 0;
117
118                 // Measure the Y-dimensions of the font
119                 try {
120                         span.setText("Ig");
121                         text.rebuild();
122                         diagram.updateTime(0);
123                         es.base.verticalDimensions = text.getBoundingBox(); 
124                         span.setText(TERM_STRING);
125                         text.rebuild();
126                         diagram.updateTime(0);
127                         es.base.termStringWidth = text.getBoundingBox().getWidth(); 
128                         span.setText(currentText);
129                         text.rebuild();
130                         diagram.updateTime(0);
131                 } catch (SVGException e) {
132                         e.printStackTrace();
133                 }
134
135                 ICanvasContext ctx = DiagramNodeUtil.getCanvasContext(node);
136                 IElement ie = DiagramNodeUtil.getElement(ctx, sne);
137
138                 EditDataNode data = EditDataNode.getNode(node);
139                 deactivateEdit(data, null);
140                 TextEditActivation result = new TextEditActivation(0, ie, ctx);
141                 data.setTextEditActivation(result);
142
143                 editorState = new LinkedList<>();
144                 editorState.push(es);
145                 editorStateIndex = 0;
146
147                 paint();
148
149         }
150
151         private TextEditActivation editActivation;
152
153         void applyEdit() {
154                 SingleElementNode sne = node.getSingleElementNode();
155                 if (sne != null) {
156                         EditorState es = currentState();
157                         Function3<SVGMeasurementContext,String,String,Object> editor = sne.getParameter("textEditor");
158                         if(editor != null) {
159                                 editor.apply(new SVGMeasurementContextImpl(node), es.base.textElementId, es.currentText);
160                         }
161                 }
162         }
163
164         protected boolean deactivateEdit() {
165                 boolean result = deactivateEdit( editActivation );
166                 result |= editActivation != null;
167                 editActivation = null;
168                 editorState = null;
169                 paint();
170                 return result;
171         }
172
173         protected boolean deactivateEdit(TextEditActivation activation) {
174                 return deactivateEdit( EditDataNode.getNode(node), activation );
175         }
176
177         protected boolean deactivateEdit(EditDataNode data, TextEditActivation activation) {
178                 TextEditActivation previous = data.getTextEditActivation();
179                 if (previous != null && (previous == activation || activation == null)) {
180                         previous.release();
181                         data.setTextEditActivation(null);
182                         return true;
183                 }
184                 return false;
185         }
186
187         protected boolean keyPressed(KeyPressedEvent e) {
188                 if(isEditMode()) {
189                         EditorState es = currentState();
190                         EditorState nes = es.copy();
191                         if(nes.keyPressed(this, e)) {
192                                 if(!isEditMode()) {
193                                         // This key actually terminated editing
194                                         return true;
195                                 }
196                                 if(nes.shouldReplace(es)) {
197                                         es.replace(nes);
198                                 } else {
199                                         while(editorState.size() > (editorStateIndex + 1))
200                                                 editorState.removeLast();
201                                         editorState.add(nes);
202                                         editorStateIndex = editorState.size() - 1;
203                                 }
204                                 return true; 
205                         }
206                 }
207                 return false;
208         }
209
210
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);
215                         return true;
216                 }
217                 return false;
218         }
219
220         public boolean tryToStartEditMode(SVGDiagram diagram, MouseClickEvent e) {
221
222                 if(diagram != null) {
223
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);
227
228                         double tolerance = 2.0;
229                         Rectangle2D pickRect = new Rectangle2D.Double(local.getX()-tolerance, local.getY()-tolerance, 2*tolerance, 2*tolerance); 
230
231                         try {
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) {
238                                                         return true;
239                                                 }
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);
246                                                                         return true;
247                                                                 }
248                                                         }
249                                                 }
250                                         }
251                                 }
252
253                         } catch (SVGException e1) {
254                                 Logger.defaultLogError(e1);
255                         }
256                 }
257
258                 return false;
259
260         }
261
262         boolean applyEditMode(SVGDiagram diagram) throws SVGException {
263
264                 if(isEditMode()) {
265                         EditorState es = currentState();
266                         es.applyEditMode(diagram);
267                         return true;
268                 }
269
270                 return false;
271
272         }
273
274         void paint() {
275                 node.cleanDiagramCache();
276                 node.repaint();
277         }
278
279         void undo() {
280                 while(editorStateIndex > 0 && currentState().modificationClass.equals(ModificationClass.NO_EDIT)) {
281                         editorStateIndex--;
282                 }
283                 if(editorStateIndex > 0)
284                         editorStateIndex--;
285                 paint();
286         }
287
288         void redo() {
289                 while(editorStateIndex < editorState.size() - 1 && currentState().modificationClass.equals(ModificationClass.NO_EDIT)) {
290                         editorStateIndex++;
291                 }
292                 if(editorStateIndex < editorState.size() - 1) {
293                         editorStateIndex++;
294                 }
295                 paint();
296         }
297
298 }