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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.diagram.elements;
\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
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
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
46 * TODO: does not work remotely, e.g. rowIds
\r
48 @OutputWidget("textGrid")
\r
49 public class TextGridNode extends G2DParentNode implements Decoration {
\r
51 public static class A extends TextNode {
\r
53 MonitorTextGridResult cache = null;
\r
55 private static final long serialVersionUID = -4519849713591842241L;
\r
57 public MonitorTextGridResult getCache() {
\r
61 public void setCache(MonitorTextGridResult cache) {
\r
67 private static final int CACHED_COLUMNS = 4;
\r
68 private static final int CACHED_ROWS = 10;
\r
70 private static Cell[][] cache;
\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
82 private static String makeId(int x, int y) {
\r
91 public Cell(int x, int y) {
\r
97 public boolean equals(Object object) {
\r
100 else if (object == null)
\r
102 else if (Cell.class != object.getClass())
\r
104 Cell p = (Cell)object;
\r
105 return x == p.x && y == p.y;
\r
109 public int hashCode() {
\r
114 public String toString() {
\r
115 return makeId(x, y);
\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
125 private static final long serialVersionUID = 7015425802228571055L;
\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
131 static class MaxY implements TObjectProcedure<Cell> {
\r
134 public boolean execute(Cell key) {
\r
141 private int computeRows() {
\r
142 MaxY maxy = new MaxY();
\r
143 nodes.forEachKey(maxy);
\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
155 public void setUp(boolean up) {
\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
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
168 //System.out.println(" get(" + x + "," + y + ")");
\r
173 public MonitorTextGridResult getCache(int x, int y) {
\r
174 return get(x,y).getCache();
\r
177 public void setCache(int x, int y, MonitorTextGridResult cache) {
\r
178 get(x,y).setCache(cache);
\r
181 public void setTransform(int x, int y, AffineTransform transform) {
\r
182 get(x,y).setTransform(transform);
\r
184 currentDrag = null;
\r
187 public void setHorizontalAlignment(int x, int y, byte horizontalAlignment) {
\r
188 get(x,y).setHorizontalAlignment(horizontalAlignment);
\r
191 public void setVerticalAlignment(int x, int y, byte verticalAlignment) {
\r
192 get(x,y).setVerticalAlignment(verticalAlignment);
\r
195 public void setForceEventListening(int x, int y, boolean force) {
\r
196 get(x,y).setForceEventListening(force);
\r
199 public void setEditable(int x, int y, boolean editable) {
\r
200 get(x,y).setEditable(editable);
\r
203 public void setTextListener(int x, int y, ITextListener listener) {
\r
204 get(x,y).setTextListener(listener);
\r
207 public void setInputValidator(int x, int y, Function1<String, String> validator) {
\r
208 get(x,y).setValidator(validator);
\r
211 public void setTranslator(Function1<Vec2d, Boolean> translator) {
\r
212 this.translator = translator;
\r
215 public void setContentFilter(int x, int y, ITextContentFilter filter) {
\r
216 get(x,y).setContentFilter(filter);
\r
219 public void setRVI(int x, int y, RVI rvi) {
\r
220 get(x,y).setRVI(rvi);
\r
223 public void setBackgroundColor(int x, int y, Color color) {
\r
224 get(x,y).setBackgroundColor(color);
\r
228 public void setFont(int x, int y, Font font) {
\r
229 get(x,y).setFont(font);
\r
233 public void setText(int x, int y, String text) {
\r
234 get(x,y).setText(text);
\r
238 public void setPending(int x, int y, boolean pending) {
\r
239 get(x,y).setPending(pending);
\r
242 @SyncField("color")
\r
243 public void setColor(int x, int y, Color color) {
\r
244 get(x,y).setColor(color);
\r
247 public void removeRow(int y) {
\r
248 List<Cell> row = peekRowCells(y);
\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
258 public void setRowId(int y, String id) {
\r
262 public String getRowId(int y) {
\r
263 return rowIds.get(y);
\r
267 public void render(Graphics2D g2d) {
\r
268 Vec2d delta = getDelta(FACTOR);
\r
270 setTransform(AffineTransform.getTranslateInstance(delta.x, delta.y-2.1*computeRows()*(up ? 1.0 : 0.0)));
\r
272 setTransform(AffineTransform.getTranslateInstance(0, -2.1*computeRows()*(up ? 1.0 : 0.0)));
\r
277 public Rectangle2D getBoundsInLocal(boolean b) {
\r
282 public Rectangle2D getBoundsInLocal() {
\r
287 public Rectangle2D getBounds() {
\r
292 public int getEventMask() {
\r
293 return EventTypes.MouseDragBeginMask | EventTypes.KeyPressedMask;
\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
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
311 private static double FACTOR = 1.0;
\r
312 private static double FACTOR2 = 7.0;
\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
325 protected boolean keyPressed(KeyPressedEvent e) {
\r
326 if (dragging && e.keyCode == java.awt.event.KeyEvent.VK_ESCAPE) {
\r
328 currentDrag = null;
\r
336 protected boolean mouseMoved(MouseMovedEvent event) {
\r
339 currentDrag = NodeUtil.worldToLocal(this, event.controlPosition, new Point2D.Double());
\r
347 protected boolean hitTest(MouseEvent event, double tolerance) {
\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
360 protected boolean mouseDragged(MouseDragBegin e) {
\r
362 // Get rid of dummy events from dragGestureRecognized
\r
363 if (isEventDummy(e)) {
\r
367 G2DSceneGraph sg = NodeUtil.getRootNode(this);
\r
368 Boolean b = sg.getGlobalProperty(G2DSceneGraph.IGNORE_FOCUS, false);
\r
369 if(!b) return false;
\r
371 if(!e.hasAnyButton(MouseEvent.LEFT_MASK) || e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK) || !hitTest(e, 0.0)) return false;
\r
373 dragBegin = NodeUtil.worldToLocal(this, e.controlPosition, new Point2D.Double());
\r
379 protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {
\r
381 if(!dragging) return false;
\r
383 Vec2d delta = getDelta(FACTOR2);
\r
384 if(delta != null && translator != null) {
\r
385 translator.apply(delta);
\r
388 currentDrag = null;
\r
395 public void init() {
\r
397 addEventHandler(this);
\r
401 public void cleanup() {
\r
402 removeEventHandler(this);
\r