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