--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.diagram.elements;\r
+\r
+import gnu.trove.map.TIntObjectMap;\r
+import gnu.trove.map.TMap;\r
+import gnu.trove.map.hash.THashMap;\r
+import gnu.trove.map.hash.TIntObjectHashMap;\r
+import gnu.trove.procedure.TObjectProcedure;\r
+\r
+import java.awt.Color;\r
+import java.awt.Font;\r
+import java.awt.Graphics2D;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import org.simantics.datatypes.literal.Vec2d;\r
+import org.simantics.db.layer0.variable.RVI;\r
+import org.simantics.diagram.profile.MonitorTextGridResult;\r
+import org.simantics.scenegraph.ExportableWidget.OutputWidget;\r
+import org.simantics.scenegraph.g2d.G2DParentNode;\r
+import org.simantics.scenegraph.g2d.G2DSceneGraph;\r
+import org.simantics.scenegraph.g2d.events.EventTypes;\r
+import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;\r
+import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;\r
+import org.simantics.scenegraph.g2d.nodes.Decoration;\r
+import org.simantics.scenegraph.utils.NodeUtil;\r
+import org.simantics.scl.runtime.function.Function1;\r
+\r
+/**\r
+ * TODO: does not work remotely, e.g. rowIds \r
+ */\r
+@OutputWidget("textGrid")\r
+public class TextGridNode extends G2DParentNode implements Decoration {\r
+\r
+ public static class A extends TextNode {\r
+\r
+ MonitorTextGridResult cache = null;\r
+ \r
+ private static final long serialVersionUID = -4519849713591842241L;\r
+\r
+ public MonitorTextGridResult getCache() {\r
+ return cache;\r
+ }\r
+ \r
+ public void setCache(MonitorTextGridResult cache) {\r
+ this.cache = cache;\r
+ }\r
+\r
+ }\r
+\r
+ private static final int CACHED_COLUMNS = 4;\r
+ private static final int CACHED_ROWS = 10;\r
+\r
+ private static Cell[][] cache;\r
+\r
+ static {\r
+ cache = new Cell[CACHED_ROWS][];\r
+ for (int row = 0; row < CACHED_ROWS; ++row) {\r
+ cache[row] = new Cell[CACHED_COLUMNS];\r
+ for (int col = 0; col < CACHED_COLUMNS; ++col) {\r
+ cache[row][col] = new Cell(col+1, row+1);\r
+ }\r
+ }\r
+ }\r
+\r
+ private static String makeId(int x, int y) {\r
+ return x + "#" + y;\r
+ }\r
+\r
+ static class Cell {\r
+\r
+ public final int x;\r
+ public final int y;\r
+\r
+ public Cell(int x, int y) {\r
+ this.x = x;\r
+ this.y = y;\r
+ }\r
+\r
+ @Override\r
+ public boolean equals(Object object) {\r
+ if (this == object)\r
+ return true;\r
+ else if (object == null)\r
+ return false;\r
+ else if (Cell.class != object.getClass())\r
+ return false;\r
+ Cell p = (Cell)object;\r
+ return x == p.x && y == p.y;\r
+ }\r
+\r
+ @Override\r
+ public int hashCode() {\r
+ return x *31 + y;\r
+ }\r
+\r
+ @Override\r
+ public String toString() {\r
+ return makeId(x, y);\r
+ }\r
+\r
+ public static Cell make(int x, int y) {\r
+ if (x >= 1 && x <= CACHED_COLUMNS && y >= 1 && y <= CACHED_ROWS)\r
+ return cache[y-1][x-1];\r
+ return new Cell(x,y);\r
+ }\r
+ }\r
+\r
+ private static final long serialVersionUID = 7015425802228571055L;\r
+\r
+ private TMap<Cell, A> nodes = new THashMap<Cell, A>();\r
+ private TIntObjectMap<String> rowIds = new TIntObjectHashMap<>();\r
+ private boolean up = true;\r
+\r
+ static class MaxY implements TObjectProcedure<Cell> {\r
+ int max = 0;\r
+ @Override\r
+ public boolean execute(Cell key) {\r
+ if (key.y > max)\r
+ max = key.y;\r
+ return true;\r
+ }\r
+ }\r
+\r
+ private int computeRows() {\r
+ MaxY maxy = new MaxY();\r
+ nodes.forEachKey(maxy);\r
+ return maxy.max;\r
+ }\r
+\r
+ private List<Cell> peekRowCells(int y) {\r
+ ArrayList<Cell> row = new ArrayList<Cell>(4);\r
+ for (Cell key : nodes.keySet())\r
+ if (key.y == y)\r
+ row.add(key);\r
+ return row;\r
+ }\r
+ \r
+ public void setUp(boolean up) {\r
+ this.up = up;\r
+ }\r
+\r
+ public A get(int x, int y) {\r
+ Cell p = Cell.make(x, y);\r
+ A node = nodes.get(p);\r
+ if(node == null) {\r
+ //System.out.println(" create(" + x + "," + y + ")");\r
+ node = getOrCreateNode(p.toString(), A.class);\r
+ node.setZIndex(x + (y-1)*100);\r
+ nodes.put(p, node);\r
+ } else {\r
+ //System.out.println(" get(" + x + "," + y + ")");\r
+ }\r
+ return node;\r
+ }\r
+\r
+ public MonitorTextGridResult getCache(int x, int y) {\r
+ return get(x,y).getCache();\r
+ }\r
+ \r
+ public void setCache(int x, int y, MonitorTextGridResult cache) {\r
+ get(x,y).setCache(cache);\r
+ }\r
+\r
+ public void setTransform(int x, int y, AffineTransform transform) {\r
+ get(x,y).setTransform(transform);\r
+ dragBegin = null;\r
+ currentDrag = null;\r
+ }\r
+\r
+ public void setHorizontalAlignment(int x, int y, byte horizontalAlignment) {\r
+ get(x,y).setHorizontalAlignment(horizontalAlignment);\r
+ }\r
+\r
+ public void setVerticalAlignment(int x, int y, byte verticalAlignment) {\r
+ get(x,y).setVerticalAlignment(verticalAlignment);\r
+ }\r
+\r
+ public void setEditable(int x, int y, boolean editable) {\r
+ get(x,y).setEditable(editable);\r
+ }\r
+ \r
+ public void setTextListener(int x, int y, ITextListener listener) {\r
+ get(x,y).setTextListener(listener);\r
+ }\r
+\r
+ public void setInputValidator(int x, int y, Function1<String, String> validator) {\r
+ get(x,y).setValidator(validator);\r
+ }\r
+ \r
+ public void setTranslator(Function1<Vec2d, Boolean> translator) {\r
+ this.translator = translator;\r
+ }\r
+\r
+ public void setContentFilter(int x, int y, ITextContentFilter filter) {\r
+ get(x,y).setContentFilter(filter);\r
+ }\r
+\r
+ public void setRVI(int x, int y, RVI rvi) {\r
+ get(x,y).setRVI(rvi);\r
+ }\r
+\r
+ public void setBackgroundColor(int x, int y, Color color) {\r
+ get(x,y).setBackgroundColor(color);\r
+ }\r
+\r
+ @SyncField("font")\r
+ public void setFont(int x, int y, Font font) {\r
+ get(x,y).setFont(font);\r
+ }\r
+\r
+ @SyncField("text")\r
+ public void setText(int x, int y, String text) {\r
+ get(x,y).setText(text);\r
+ }\r
+\r
+ @SyncField("text")\r
+ public void setPending(int x, int y, boolean pending) {\r
+ get(x,y).setPending(pending);\r
+ }\r
+\r
+ @SyncField("color")\r
+ public void setColor(int x, int y, Color color) {\r
+ get(x,y).setColor(color);\r
+ }\r
+\r
+ public void removeRow(int y) {\r
+ List<Cell> row = peekRowCells(y);\r
+ if (row.isEmpty())\r
+ return;\r
+ //System.out.println("removeRow(" + y + "): removing " + row.size() + " cells");\r
+ for (Cell cell : row) {\r
+ nodes.remove(cell);\r
+ removeNode(cell.toString());\r
+ }\r
+ }\r
+\r
+ public void setRowId(int y, String id) {\r
+ rowIds.put(y, id);\r
+ }\r
+\r
+ public String getRowId(int y) {\r
+ return rowIds.get(y);\r
+ }\r
+ \r
+ @Override\r
+ public void render(Graphics2D g2d) {\r
+ Vec2d delta = getDelta(FACTOR);\r
+ if(delta != null)\r
+ setTransform(AffineTransform.getTranslateInstance(delta.x, delta.y-2.1*computeRows()*(up ? 1.0 : 0.0)));\r
+ else\r
+ setTransform(AffineTransform.getTranslateInstance(0, -2.1*computeRows()*(up ? 1.0 : 0.0)));\r
+ super.render(g2d);\r
+ }\r
+ \r
+ @Override\r
+ public Rectangle2D getBoundsInLocal(boolean b) {\r
+ return null;\r
+ }\r
+ \r
+ @Override\r
+ public Rectangle2D getBoundsInLocal() {\r
+ return null;\r
+ }\r
+ \r
+ @Override\r
+ public Rectangle2D getBounds() {\r
+ return null;\r
+ }\r
+ \r
+ @Override\r
+ public int getEventMask() {\r
+ return EventTypes.MouseDragBeginMask | EventTypes.KeyPressedMask;\r
+ }\r
+ \r
+ private static boolean isEventDummy(MouseDragBegin e) {\r
+ if (e.controlPosition.distance(0, 0) == 0 \r
+ && e.screenPosition.distance(0, 0) == 0\r
+ && e.buttons == 0) {\r
+ return true;\r
+ } else {\r
+ return false;\r
+ }\r
+ }\r
+ \r
+ private boolean dragging = false;\r
+ private Point2D dragBegin = null;\r
+ private Point2D currentDrag = null;\r
+ private Function1<Vec2d, Boolean> translator = null;\r
+ \r
+ private static double FACTOR = 1.0; \r
+ private static double FACTOR2 = 7.0;\r
+ \r
+ private Vec2d getDelta(double factor) {\r
+ if(dragBegin != null && currentDrag != null) {\r
+ double dx = factor * (currentDrag.getX() - dragBegin.getX());\r
+ double dy = factor * (currentDrag.getY() - dragBegin.getY());\r
+ return new Vec2d(dx, dy);\r
+ } else {\r
+ return null;\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ protected boolean keyPressed(KeyPressedEvent e) {\r
+ if (dragging && e.keyCode == java.awt.event.KeyEvent.VK_ESCAPE) {\r
+ dragBegin = null;\r
+ currentDrag = null;\r
+ dragging = false;\r
+ repaint();\r
+ return true;\r
+ }\r
+ return false;\r
+ }\r
+\r
+ protected boolean mouseMoved(MouseMovedEvent event) {\r
+ \r
+ if (dragging) {\r
+ currentDrag = NodeUtil.worldToLocal(this, event.controlPosition, new Point2D.Double());\r
+ repaint();\r
+ }\r
+ \r
+ return false;\r
+ \r
+ }\r
+ \r
+ protected boolean hitTest(MouseEvent event, double tolerance) {\r
+ \r
+ Rectangle2D bounds = super.getBoundsInLocal(false);\r
+ if(bounds == null) return false;\r
+ Point2D localPos = NodeUtil.worldToLocal(this, event.controlPosition, new Point2D.Double());\r
+ double x = localPos.getX();\r
+ double y = localPos.getY()+2.1*computeRows()*(up ? 1.0 : 0.0);\r
+ boolean hit = bounds.contains(x, y);\r
+ return hit;\r
+ \r
+ }\r
+ \r
+ @Override\r
+ protected boolean mouseDragged(MouseDragBegin e) {\r
+ \r
+ // Get rid of dummy events from dragGestureRecognized\r
+ if (isEventDummy(e)) {\r
+ return false;\r
+ }\r
+ \r
+ G2DSceneGraph sg = NodeUtil.getRootNode(this);\r
+ Boolean b = sg.getGlobalProperty(G2DSceneGraph.IGNORE_FOCUS, false);\r
+ if(!b) return false;\r
+ \r
+ if(!e.hasAnyButton(MouseEvent.LEFT_MASK) || e.hasAnyModifier(MouseEvent.ALL_MODIFIERS_MASK) || !hitTest(e, 0.0)) return false;\r
+ \r
+ dragBegin = NodeUtil.worldToLocal(this, e.controlPosition, new Point2D.Double());\r
+ dragging = true;\r
+ return true;\r
+ \r
+ }\r
+ \r
+ protected boolean mouseButtonReleased(MouseButtonReleasedEvent e) {\r
+ \r
+ if(!dragging) return false;\r
+ \r
+ Vec2d delta = getDelta(FACTOR2);\r
+ if(delta != null && translator != null) {\r
+ translator.apply(delta); \r
+ } else {\r
+ dragBegin = null;\r
+ currentDrag = null;\r
+ }\r
+ dragging = false;\r
+ return false;\r
+ }\r
+ \r
+ @Override\r
+ public void init() {\r
+ super.init();\r
+ addEventHandler(this);\r
+ }\r
+\r
+ @Override\r
+ public void cleanup() {\r
+ removeEventHandler(this);\r
+ super.cleanup();\r
+ }\r
+ \r
+}\r