Sync git svn branch with SVN repository r33189.
[simantics/platform.git] / bundles / org.simantics.diagram / src / org / simantics / diagram / elements / TextGridNode.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.diagram.elements;\r
13 \r
14 import gnu.trove.map.TIntObjectMap;\r
15 import gnu.trove.map.TMap;\r
16 import gnu.trove.map.hash.THashMap;\r
17 import gnu.trove.map.hash.TIntObjectHashMap;\r
18 import gnu.trove.procedure.TObjectProcedure;\r
19 \r
20 import java.awt.Color;\r
21 import java.awt.Font;\r
22 import java.awt.Graphics2D;\r
23 import java.awt.geom.AffineTransform;\r
24 import java.awt.geom.Point2D;\r
25 import java.awt.geom.Rectangle2D;\r
26 import java.util.ArrayList;\r
27 import java.util.List;\r
28 \r
29 import org.simantics.datatypes.literal.Vec2d;\r
30 import org.simantics.db.layer0.variable.RVI;\r
31 import org.simantics.diagram.profile.MonitorTextGridResult;\r
32 import org.simantics.scenegraph.ExportableWidget.OutputWidget;\r
33 import org.simantics.scenegraph.g2d.G2DParentNode;\r
34 import org.simantics.scenegraph.g2d.G2DSceneGraph;\r
35 import org.simantics.scenegraph.g2d.events.EventTypes;\r
36 import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;\r
37 import org.simantics.scenegraph.g2d.events.MouseEvent;\r
38 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;\r
39 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;\r
40 import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
41 import org.simantics.scenegraph.g2d.nodes.Decoration;\r
42 import org.simantics.scenegraph.utils.NodeUtil;\r
43 import org.simantics.scl.runtime.function.Function1;\r
44 \r
45 /**\r
46  * TODO: does not work remotely, e.g. rowIds \r
47  */\r
48 @OutputWidget("textGrid")\r
49 public class TextGridNode extends G2DParentNode implements Decoration {\r
50 \r
51     public static class A extends TextNode {\r
52 \r
53         MonitorTextGridResult cache = null;\r
54         \r
55         private static final long serialVersionUID = -4519849713591842241L;\r
56 \r
57         public MonitorTextGridResult getCache() {\r
58                 return cache;\r
59         }\r
60         \r
61         public void setCache(MonitorTextGridResult cache) {\r
62                 this.cache = cache;\r
63         }\r
64 \r
65     }\r
66 \r
67     private static final int CACHED_COLUMNS = 4;\r
68     private static final int CACHED_ROWS = 10;\r
69 \r
70     private static Cell[][] cache;\r
71 \r
72     static {\r
73         cache = new Cell[CACHED_ROWS][];\r
74         for (int row = 0; row < CACHED_ROWS; ++row) {\r
75             cache[row] = new Cell[CACHED_COLUMNS];\r
76             for (int col = 0; col < CACHED_COLUMNS; ++col) {\r
77                 cache[row][col] = new Cell(col+1, row+1);\r
78             }\r
79         }\r
80     }\r
81 \r
82     private static String makeId(int x, int y) {\r
83         return x + "#" + y;\r
84     }\r
85 \r
86     static class Cell {\r
87 \r
88         public final int x;\r
89         public final int y;\r
90 \r
91         public Cell(int x, int y) {\r
92             this.x = x;\r
93             this.y = y;\r
94         }\r
95 \r
96         @Override\r
97         public boolean equals(Object object) {\r
98             if (this == object)\r
99                 return true;\r
100             else if (object == null)\r
101                 return false;\r
102             else if (Cell.class != object.getClass())\r
103                 return false;\r
104             Cell p = (Cell)object;\r
105             return x == p.x && y == p.y;\r
106         }\r
107 \r
108         @Override\r
109         public int hashCode() {\r
110             return x *31 + y;\r
111         }\r
112 \r
113         @Override\r
114         public String toString() {\r
115             return makeId(x, y);\r
116         }\r
117 \r
118         public static Cell make(int x, int y) {\r
119             if (x >= 1 && x <= CACHED_COLUMNS && y >= 1 && y <= CACHED_ROWS)\r
120                 return cache[y-1][x-1];\r
121             return new Cell(x,y);\r
122         }\r
123     }\r
124 \r
125     private static final long serialVersionUID = 7015425802228571055L;\r
126 \r
127     private TMap<Cell, A> nodes = new THashMap<Cell, A>();\r
128     private TIntObjectMap<String> rowIds = new TIntObjectHashMap<>();\r
129     private boolean up = true;\r
130 \r
131     static class MaxY implements TObjectProcedure<Cell> {\r
132         int max = 0;\r
133         @Override\r
134         public boolean execute(Cell key) {\r
135             if (key.y > max)\r
136                 max = key.y;\r
137             return true;\r
138         }\r
139     }\r
140 \r
141     private int computeRows() {\r
142         MaxY maxy = new MaxY();\r
143         nodes.forEachKey(maxy);\r
144         return maxy.max;\r
145     }\r
146 \r
147     private List<Cell> peekRowCells(int y) {\r
148         ArrayList<Cell> row = new ArrayList<Cell>(4);\r
149         for (Cell key : nodes.keySet())\r
150             if (key.y == y)\r
151                 row.add(key);\r
152         return row;\r
153     }\r
154     \r
155     public void setUp(boolean up) {\r
156         this.up = up;\r
157     }\r
158 \r
159     public A get(int x, int y) {\r
160         Cell p = Cell.make(x, y);\r
161         A node = nodes.get(p);\r
162         if(node == null) {\r
163             //System.out.println(" create(" + x + "," + y + ")");\r
164             node = getOrCreateNode(p.toString(), A.class);\r
165             node.setZIndex(x + (y-1)*100);\r
166             nodes.put(p, node);\r
167         } else {\r
168             //System.out.println(" get(" + x + "," + y + ")");\r
169         }\r
170         return node;\r
171     }\r
172 \r
173     public MonitorTextGridResult getCache(int x, int y) {\r
174         return get(x,y).getCache();\r
175     }\r
176     \r
177     public void setCache(int x, int y, MonitorTextGridResult cache) {\r
178         get(x,y).setCache(cache);\r
179     }\r
180 \r
181     public void setTransform(int x, int y, AffineTransform transform) {\r
182         get(x,y).setTransform(transform);\r
183         dragBegin = null;\r
184         currentDrag = null;\r
185     }\r
186 \r
187     public void setHorizontalAlignment(int x, int y, byte horizontalAlignment) {\r
188         get(x,y).setHorizontalAlignment(horizontalAlignment);\r
189     }\r
190 \r
191     public void setVerticalAlignment(int x, int y, byte verticalAlignment) {\r
192         get(x,y).setVerticalAlignment(verticalAlignment);\r
193     }\r
194 \r
195     public void setForceEventListening(int x, int y, boolean force) {\r
196         get(x,y).setForceEventListening(force);\r
197     }\r
198     \r
199     public void setEditable(int x, int y, boolean editable) {\r
200         get(x,y).setEditable(editable);\r
201     }\r
202     \r
203     public void setTextListener(int x, int y, ITextListener listener) {\r
204         get(x,y).setTextListener(listener);\r
205     }\r
206 \r
207     public void setInputValidator(int x, int y, Function1<String, String> validator) {\r
208         get(x,y).setValidator(validator);\r
209     }\r
210     \r
211     public void setTranslator(Function1<Vec2d, Boolean> translator) {\r
212         this.translator = translator;\r
213     }\r
214 \r
215     public void setContentFilter(int x, int y, ITextContentFilter filter) {\r
216         get(x,y).setContentFilter(filter);\r
217     }\r
218 \r
219     public void setRVI(int x, int y, RVI rvi) {\r
220         get(x,y).setRVI(rvi);\r
221     }\r
222 \r
223     public void setBackgroundColor(int x, int y, Color color) {\r
224         get(x,y).setBackgroundColor(color);\r
225     }\r
226 \r
227     @SyncField("font")\r
228     public void setFont(int x, int y, Font font) {\r
229         get(x,y).setFont(font);\r
230     }\r
231 \r
232     @SyncField("text")\r
233     public void setText(int x, int y, String text) {\r
234         get(x,y).setText(text);\r
235     }\r
236 \r
237     @SyncField("text")\r
238     public void setPending(int x, int y, boolean pending) {\r
239         get(x,y).setPending(pending);\r
240     }\r
241 \r
242     @SyncField("color")\r
243     public void setColor(int x, int y, Color color) {\r
244         get(x,y).setColor(color);\r
245     }\r
246 \r
247     public void removeRow(int y) {\r
248         List<Cell> row = peekRowCells(y);\r
249         if (row.isEmpty())\r
250             return;\r
251         //System.out.println("removeRow(" + y + "): removing " + row.size() + " cells");\r
252         for (Cell cell : row) {\r
253             nodes.remove(cell);\r
254             removeNode(cell.toString());\r
255         }\r
256     }\r
257 \r
258     public void setRowId(int y, String id) {\r
259         rowIds.put(y, id);\r
260     }\r
261 \r
262     public String getRowId(int y) {\r
263         return rowIds.get(y);\r
264     }\r
265     \r
266     @Override\r
267     public void render(Graphics2D g2d) {\r
268         Vec2d delta = getDelta(FACTOR);\r
269         if(delta != null)\r
270                 setTransform(AffineTransform.getTranslateInstance(delta.x, delta.y-2.1*computeRows()*(up ? 1.0 : 0.0)));\r
271         else\r
272                 setTransform(AffineTransform.getTranslateInstance(0, -2.1*computeRows()*(up ? 1.0 : 0.0)));\r
273         super.render(g2d);\r
274     }\r
275     \r
276     @Override\r
277     public Rectangle2D getBoundsInLocal(boolean b) {\r
278         return null;\r
279     }\r
280     \r
281     @Override\r
282     public Rectangle2D getBoundsInLocal() {\r
283         return null;\r
284     }\r
285     \r
286     @Override\r
287     public Rectangle2D getBounds() {\r
288         return null;\r
289     }\r
290     \r
291     @Override\r
292     public int getEventMask() {\r
293         return EventTypes.MouseDragBeginMask | EventTypes.KeyPressedMask;\r
294     }\r
295     \r
296     private static boolean isEventDummy(MouseDragBegin e) {\r
297         if (e.controlPosition.distance(0, 0) == 0 \r
298                         && e.screenPosition.distance(0, 0) == 0\r
299                         && e.buttons == 0) {\r
300                 return true;\r
301         } else {\r
302                 return false;\r
303         }\r
304     }\r
305     \r
306     private boolean dragging = false;\r
307     private Point2D dragBegin = null;\r
308     private Point2D currentDrag = null;\r
309     private Function1<Vec2d, Boolean> translator = null;\r
310     \r
311     private static double FACTOR = 1.0; \r
312     private static double FACTOR2 = 7.0;\r
313     \r
314     private Vec2d getDelta(double factor) {\r
315             if(dragBegin != null && currentDrag != null) {\r
316                 double dx =  factor * (currentDrag.getX() - dragBegin.getX());\r
317                 double dy = factor * (currentDrag.getY() - dragBegin.getY());\r
318                 return new Vec2d(dx, dy);\r
319             } else {\r
320                 return null;\r
321             }\r
322     }\r
323     \r
324     @Override\r
325     protected boolean keyPressed(KeyPressedEvent e) {\r
326         if (dragging && e.keyCode == java.awt.event.KeyEvent.VK_ESCAPE) {\r
327                 dragBegin = null;\r
328                 currentDrag = null;\r
329                 dragging = false;\r
330                 repaint();\r
331                 return true;\r
332         }\r
333         return false;\r
334     }\r
335 \r
336     protected boolean mouseMoved(MouseMovedEvent event) {\r
337         \r
338         if (dragging) {\r
339                 currentDrag = NodeUtil.worldToLocal(this, event.controlPosition, new Point2D.Double());\r
340                 repaint();\r
341         }\r
342         \r
343         return false;\r
344         \r
345     }\r
346     \r
347     protected boolean hitTest(MouseEvent event, double tolerance) {\r
348         \r
349         Rectangle2D bounds = super.getBoundsInLocal(false);\r
350         if(bounds == null) return false;\r
351         Point2D localPos = NodeUtil.worldToLocal(this, event.controlPosition, new Point2D.Double());\r
352         double x = localPos.getX();\r
353         double y = localPos.getY()+2.1*computeRows()*(up ? 1.0 : 0.0);\r
354         boolean hit = bounds.contains(x, y);\r
355         return hit;\r
356         \r
357     }\r
358     \r
359     @Override\r
360     protected boolean mouseDragged(MouseDragBegin e) {\r
361         \r
362         // Get rid of dummy events from dragGestureRecognized\r
363         if (isEventDummy(e)) {\r
364                 return false;\r
365         }\r
366         \r
367         G2DSceneGraph sg = NodeUtil.getRootNode(this);\r
368         Boolean b = sg.getGlobalProperty(G2DSceneGraph.IGNORE_FOCUS, false);\r
369         if(!b) return false;\r
370         \r
371         if(!e.hasAnyButton(MouseEvent.LEFT_MASK) || e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK) || !hitTest(e, 0.0)) return false;\r
372         \r
373         dragBegin = NodeUtil.worldToLocal(this, e.controlPosition, new Point2D.Double());\r
374         dragging = true;\r
375         return true;\r
376         \r
377     }\r
378     \r
379     protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {\r
380         \r
381         if(!dragging) return false;\r
382         \r
383         Vec2d delta = getDelta(FACTOR2);\r
384         if(delta != null && translator != null) {\r
385                 translator.apply(delta); \r
386         } else {\r
387                 dragBegin = null;\r
388                 currentDrag = null;\r
389         }\r
390         dragging = false;\r
391         return false;\r
392     }\r
393     \r
394     @Override\r
395     public void init() {\r
396         super.init();\r
397         addEventHandler(this);\r
398     }\r
399 \r
400     @Override\r
401     public void cleanup() {\r
402         removeEventHandler(this);\r
403         super.cleanup();\r
404     }\r
405     \r
406 }\r