]> gerrit.simantics Code Review - simantics/sysdyn.git/commitdiff
git-svn-id: https://www.simantics.org/svn/simantics/sysdyn/trunk@13136 ac1ea38d-2e2b...
authorniemisto <niemisto@ac1ea38d-2e2b-0410-8846-a27921b304fc>
Wed, 18 Nov 2009 13:41:49 +0000 (13:41 +0000)
committerniemisto <niemisto@ac1ea38d-2e2b-0410-8846-a27921b304fc>
Wed, 18 Nov 2009 13:41:49 +0000 (13:41 +0000)
58 files changed:
org.simantics.h2d/.classpath [new file with mode: 0644]
org.simantics.h2d/.hgignore [new file with mode: 0644]
org.simantics.h2d/.project [new file with mode: 0644]
org.simantics.h2d/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
org.simantics.h2d/META-INF/MANIFEST.MF [new file with mode: 0644]
org.simantics.h2d/build.properties [new file with mode: 0644]
org.simantics.h2d/doc/manual.mediawiki [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/action/IAction.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/canvas/EditorCanvas.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/diagram/Diagram.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/diagram/IDiagram.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/diagram/IDiagramListener.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/editor/IDiagramEditor.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/editor/ISelection.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/editor/ISelectionListener.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/editor/impl/DiagramEditor.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/editor/impl/EventHandlerManager.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/editor/impl/SceneGraphManager.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/editor/impl/Selection.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/element/ChainingElementListener.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/element/Element.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/element/IElement.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/element/IElementListener.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/element/handler/Connectable.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/element/handler/IElementHandler.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/element/handler/Movable.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/element/handler/Rotatable.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/element/handler/ShadowDrawable.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/ClickEvent.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/DragEvent.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/DragEventPhase.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/IDragHandler.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/IEvent.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/ILocatableEvent.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/KeyboardEvent.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/Modifiers.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/WheelEvent.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/handler/BoxSelection.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/handler/DefaultEventHandlers.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/handler/Delete.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/handler/DragEventHandler.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/handler/ElementEventDelegator.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/handler/EventPrinter.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/handler/IEventHandler.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/handler/MoveSelected.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/handler/Pan.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/handler/PickSelection.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/handler/RotateClockwise.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/handler/RotateCounterclockwise.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/handler/ToggleSelection.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/handler/Zoom.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/event/handler/ZoomToFit.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/node/FilledShapeNode.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/node/ITextListener.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/node/LineNode.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/node/RectangleNode.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/node/ShapeNode.java [new file with mode: 0644]
org.simantics.h2d/src/org/simantics/h2d/node/TextNode.java [new file with mode: 0644]

diff --git a/org.simantics.h2d/.classpath b/org.simantics.h2d/.classpath
new file mode 100644 (file)
index 0000000..8a8f166
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<classpath>\r
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>\r
+       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>\r
+       <classpathentry kind="src" path="src"/>\r
+       <classpathentry kind="output" path="bin"/>\r
+</classpath>\r
diff --git a/org.simantics.h2d/.hgignore b/org.simantics.h2d/.hgignore
new file mode 100644 (file)
index 0000000..73df90f
--- /dev/null
@@ -0,0 +1,5 @@
+syntax: regexp\r
+^bin/\r
+\r
+syntax: glob\r
+*.svn/*
\ No newline at end of file
diff --git a/org.simantics.h2d/.project b/org.simantics.h2d/.project
new file mode 100644 (file)
index 0000000..e91c226
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<projectDescription>\r
+       <name>org.simantics.h2d</name>\r
+       <comment></comment>\r
+       <projects>\r
+       </projects>\r
+       <buildSpec>\r
+               <buildCommand>\r
+                       <name>org.eclipse.jdt.core.javabuilder</name>\r
+                       <arguments>\r
+                       </arguments>\r
+               </buildCommand>\r
+               <buildCommand>\r
+                       <name>org.eclipse.pde.ManifestBuilder</name>\r
+                       <arguments>\r
+                       </arguments>\r
+               </buildCommand>\r
+               <buildCommand>\r
+                       <name>org.eclipse.pde.SchemaBuilder</name>\r
+                       <arguments>\r
+                       </arguments>\r
+               </buildCommand>\r
+       </buildSpec>\r
+       <natures>\r
+               <nature>org.eclipse.pde.PluginNature</nature>\r
+               <nature>org.eclipse.jdt.core.javanature</nature>\r
+       </natures>\r
+</projectDescription>\r
diff --git a/org.simantics.h2d/.settings/org.eclipse.jdt.core.prefs b/org.simantics.h2d/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..aa42399
--- /dev/null
@@ -0,0 +1,8 @@
+#Sun Nov 08 17:02:25 EET 2009\r
+eclipse.preferences.version=1\r
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled\r
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6\r
+org.eclipse.jdt.core.compiler.compliance=1.6\r
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error\r
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error\r
+org.eclipse.jdt.core.compiler.source=1.6\r
diff --git a/org.simantics.h2d/META-INF/MANIFEST.MF b/org.simantics.h2d/META-INF/MANIFEST.MF
new file mode 100644 (file)
index 0000000..54dd88c
--- /dev/null
@@ -0,0 +1,19 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: H2d
+Bundle-SymbolicName: org.simantics.h2d
+Bundle-Version: 1.0.0.qualifier
+Bundle-RequiredExecutionEnvironment: JavaSE-1.6
+Require-Bundle: org.simantics.scenegraph;bundle-version="1.0.0",
+ gnu.trove2;bundle-version="2.0.4",
+ org.simantics.objmap;bundle-version="0.1.0"
+Export-Package: org.simantics.h2d.action,
+ org.simantics.h2d.canvas,
+ org.simantics.h2d.diagram,
+ org.simantics.h2d.editor,
+ org.simantics.h2d.editor.impl,
+ org.simantics.h2d.element,
+ org.simantics.h2d.element.handler,
+ org.simantics.h2d.event,
+ org.simantics.h2d.event.handler,
+ org.simantics.h2d.node
diff --git a/org.simantics.h2d/build.properties b/org.simantics.h2d/build.properties
new file mode 100644 (file)
index 0000000..41eb6ad
--- /dev/null
@@ -0,0 +1,4 @@
+source.. = src/\r
+output.. = bin/\r
+bin.includes = META-INF/,\\r
+               .\r
diff --git a/org.simantics.h2d/doc/manual.mediawiki b/org.simantics.h2d/doc/manual.mediawiki
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/org.simantics.h2d/src/org/simantics/h2d/action/IAction.java b/org.simantics.h2d/src/org/simantics/h2d/action/IAction.java
new file mode 100644 (file)
index 0000000..73f00db
--- /dev/null
@@ -0,0 +1,14 @@
+package org.simantics.h2d.action;\r
+\r
+import org.simantics.h2d.event.handler.IEventHandler;\r
+import org.simantics.scenegraph.g2d.G2DParentNode;\r
+\r
+/**\r
+ * Action is a non-instantenous user operation on diagram. \r
+ * @see org.simantics.h2d.editor.IDiagramEditor#addAction\r
+ * @author Hannu Niemistö\r
+ */\r
+public interface IAction extends IEventHandler {\r
+       void init(G2DParentNode parent);\r
+       void remove();\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/canvas/EditorCanvas.java b/org.simantics.h2d/src/org/simantics/h2d/canvas/EditorCanvas.java
new file mode 100644 (file)
index 0000000..6c1aea1
--- /dev/null
@@ -0,0 +1,217 @@
+package org.simantics.h2d.canvas;\r
+\r
+import java.awt.Canvas;\r
+import java.awt.Color;\r
+import java.awt.Dimension;\r
+import java.awt.GradientPaint;\r
+import java.awt.Graphics;\r
+import java.awt.Graphics2D;\r
+import java.awt.RenderingHints;\r
+import java.awt.TexturePaint;\r
+import java.awt.event.ComponentAdapter;\r
+import java.awt.event.ComponentEvent;\r
+import java.awt.event.KeyEvent;\r
+import java.awt.event.KeyListener;\r
+import java.awt.event.MouseEvent;\r
+import java.awt.event.MouseListener;\r
+import java.awt.event.MouseMotionListener;\r
+import java.awt.event.MouseWheelEvent;\r
+import java.awt.event.MouseWheelListener;\r
+import java.awt.geom.Point2D;\r
+import java.awt.image.BufferedImage;\r
+import java.awt.image.VolatileImage;\r
+import java.io.File;\r
+import java.io.IOException;\r
+\r
+import javax.imageio.ImageIO;\r
+\r
+import org.simantics.h2d.editor.IDiagramEditor;\r
+import org.simantics.h2d.event.ClickEvent;\r
+import org.simantics.h2d.event.DragEvent;\r
+import org.simantics.h2d.event.DragEventPhase;\r
+import org.simantics.h2d.event.KeyboardEvent;\r
+import org.simantics.h2d.event.Modifiers;\r
+import org.simantics.h2d.event.WheelEvent;\r
+import org.simantics.scenegraph.g2d.G2DRenderingHints;\r
+\r
+\r
+public class EditorCanvas extends Canvas {\r
+\r
+       IDiagramEditor editor;\r
+       EventHandler eventHandler = new EventHandler();\r
+       //BufferedImage background;\r
+\r
+       public EditorCanvas(IDiagramEditor editor) {\r
+               this.editor = editor;\r
+               editor.setCanvas(this);\r
+               \r
+               /*G2DSceneGraph sceneGraph = editor.getSceneGraph();\r
+               addMouseListener(sceneGraph);\r
+        addMouseMotionListener(sceneGraph);\r
+        addMouseWheelListener(sceneGraph);\r
+        addKeyListener(sceneGraph);\r
+        */\r
+               addMouseListener(eventHandler);\r
+               addMouseMotionListener(eventHandler);\r
+               addMouseWheelListener(eventHandler);\r
+               addKeyListener(eventHandler);\r
+               \r
+               addComponentListener(new ComponentAdapter() {\r
+            @Override\r
+            public void componentResized(ComponentEvent e) {\r
+                repaint();\r
+            }\r
+        });\r
+               \r
+               /*try {\r
+                       background = ImageIO.read(new File("c:/paper.png"));\r
+               } catch (IOException e1) {\r
+                       // TODO Auto-generated catch block\r
+                       e1.printStackTrace();\r
+               }*/\r
+       }\r
+       \r
+    @Override\r
+    public void update(Graphics g) {\r
+        paint(g);\r
+    }\r
+       \r
+    private VolatileImage doubleBuffer;\r
+       @Override\r
+       public void paint(Graphics _g) {        \r
+        if(doubleBuffer == null ||\r
+                       doubleBuffer.getWidth() != getWidth() || doubleBuffer.getHeight() != getHeight()) {\r
+            doubleBuffer = createVolatileImage(getWidth(), getHeight());\r
+            editor.setViewDimensions(new Dimension(getWidth(), getHeight()));\r
+        }\r
+        Graphics2D g = (Graphics2D)doubleBuffer.getGraphics();\r
+       \r
+        g.setBackground(Color.WHITE);\r
+        g.setColor(Color.WHITE);\r
+        //g.setPaint(new GradientPaint(0.f, 0.f, Color.white, 2000.f, 1600.f, Color.BLUE, false));\r
+        //g.setPaint(new TexturePaint(background, getBounds()));\r
+        g.fillRect(0, 0, doubleBuffer.getWidth(), doubleBuffer.getHeight());\r
+        \r
+               g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);\r
+               g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);\r
+               g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);               \r
+               g.setRenderingHint(G2DRenderingHints.KEY_CONTROL_BOUNDS, getBounds());\r
+               \r
+               editor.getSceneGraph().render(g);\r
+               \r
+               _g.drawImage(doubleBuffer, 0, 0, this);\r
+       }\r
+       \r
+       class EventHandler implements MouseListener, MouseMotionListener, MouseWheelListener, KeyListener {\r
+\r
+               DragEvent dragEvent;\r
+               \r
+               @Override\r
+               public void mouseClicked(MouseEvent e) {\r
+                       ClickEvent event = new ClickEvent(\r
+                                       Modifiers.modifierString(e.getButton(), e.isControlDown(), e.isAltDown(), e.isShiftDown()),\r
+                                       editor.screenToDiagram(e.getPoint())\r
+                                       );\r
+                       event.pickedElements = dragEvent.pickedElements;\r
+                       editor.handleEvent(event);\r
+               }\r
+\r
+               @Override\r
+               public void mouseEntered(MouseEvent e) {\r
+                       // TODO Auto-generated method stub\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public void mouseExited(MouseEvent e) {\r
+                       // TODO Auto-generated method stub\r
+                       \r
+               }\r
+\r
+               @Override\r
+               public void mousePressed(MouseEvent e) {\r
+                       editor.getSceneGraph().mousePressed(e);\r
+                       if(e.isConsumed())\r
+                               return;\r
+                       dragEvent = new DragEvent(\r
+                                       Modifiers.modifierString(e.getButton(), e.isControlDown(), e.isAltDown(), e.isShiftDown()),\r
+                                       editor.screenToDiagram(e.getPoint())\r
+                                       );\r
+                       dragEvent.pickedElements = editor.pickElements(dragEvent.start);\r
+               }\r
+\r
+               @Override\r
+               public void mouseReleased(MouseEvent e) {\r
+                       editor.getSceneGraph().mouseReleased(e);\r
+                       if(e.isConsumed())\r
+                               return;\r
+                       if(dragEvent.phase == DragEventPhase.dragUpdate) {\r
+                               dragEvent.phase = DragEventPhase.dragEnd;\r
+                               editor.handleEvent(dragEvent);\r
+                       }\r
+               }\r
+\r
+               @Override\r
+               public void mouseDragged(MouseEvent e) {\r
+                       editor.getSceneGraph().mouseDragged(e);\r
+                       if(e.isConsumed())\r
+                               return;\r
+                       currentPosition = e.getPoint();\r
+                       dragEvent.currentModifiers = \r
+                               Modifiers.modifierString(e.getButton(), e.isControlDown(), e.isAltDown(), e.isShiftDown());\r
+                       dragEvent.current = editor.screenToDiagram(e.getPoint());\r
+                       editor.handleEvent(dragEvent);\r
+\r
+                       if(dragEvent.phase == DragEventPhase.dragBegin) {\r
+                               dragEvent.phase = DragEventPhase.dragUpdate;\r
+                               editor.handleEvent(dragEvent);\r
+                       }\r
+               }\r
+               \r
+               Point2D currentPosition = new Point2D.Double(0.0, 0.0);\r
+\r
+               @Override\r
+               public void mouseMoved(MouseEvent e) {\r
+                       currentPosition = e.getPoint();\r
+               }\r
+\r
+               @Override\r
+               public void mouseWheelMoved(MouseWheelEvent e) {\r
+                       editor.handleEvent(new WheelEvent(\r
+                                       Modifiers.modifierString(e.getButton(), e.isControlDown(), e.isAltDown(), e.isShiftDown()),\r
+                                       editor.screenToDiagram(e.getPoint()),\r
+                                       e.getWheelRotation()\r
+                                       ));\r
+               }\r
+\r
+               @Override\r
+               public void keyPressed(KeyEvent e) {\r
+                       editor.getSceneGraph().keyPressed(e);\r
+                       if(e.isConsumed())\r
+                               return;\r
+                       \r
+                       String keyText = KeyEvent.getKeyText(e.getKeyCode());\r
+                       if(e.getModifiers() != 0)\r
+                               keyText = KeyEvent.getKeyModifiersText(e.getModifiers()) \r
+                                       + "+" + keyText;\r
+                       KeyboardEvent event = new KeyboardEvent(\r
+                                       keyText,\r
+                                       editor.screenToDiagram(currentPosition)\r
+                                       );\r
+                       event.pickedElements = editor.pickElements(event.point);\r
+                       if(editor.handleEvent(event))\r
+                               e.consume();\r
+               }\r
+\r
+               @Override\r
+               public void keyReleased(KeyEvent e) {\r
+                       editor.getSceneGraph().keyReleased(e);\r
+               }\r
+\r
+               @Override\r
+               public void keyTyped(KeyEvent e) {\r
+                       editor.getSceneGraph().keyTyped(e);\r
+               } \r
+       }\r
+       \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/diagram/Diagram.java b/org.simantics.h2d/src/org/simantics/h2d/diagram/Diagram.java
new file mode 100644 (file)
index 0000000..afd9d8d
--- /dev/null
@@ -0,0 +1,41 @@
+package org.simantics.h2d.diagram;\r
+\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import org.simantics.h2d.element.IElement;\r
+import org.simantics.objmap.annotations.RelatedValue;\r
+import org.simantics.objmap.annotations.GraphType;\r
+\r
+@GraphType("http://www.simantics.org/Sysdyn#Configuration")\r
+public class Diagram implements IDiagram {\r
+\r
+    @RelatedValue("http://www.vtt.fi/Simantics/Layer0/1.0/Relations#ConsistsOf")\r
+       public ArrayList<IElement> elements = new ArrayList<IElement>();\r
+       ArrayList<IDiagramListener> listeners = new ArrayList<IDiagramListener>();\r
+       \r
+       @Override\r
+       public void addElement(IElement element) {\r
+               elements.add(element);\r
+               for(IDiagramListener listener : listeners)\r
+                       listener.elementAdded(element);\r
+       }\r
+\r
+       @Override\r
+       public List<IElement> getElements() {\r
+               return elements;\r
+       }\r
+\r
+       @Override\r
+       public void addDiagramListener(IDiagramListener listener) {\r
+               listeners.add(listener);\r
+       }\r
+\r
+       @Override\r
+       public void removeElement(IElement element) {\r
+               elements.remove(element);\r
+               for(IDiagramListener listener : listeners)\r
+                       listener.elementRemoved(element);\r
+       }\r
+\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/diagram/IDiagram.java b/org.simantics.h2d/src/org/simantics/h2d/diagram/IDiagram.java
new file mode 100644 (file)
index 0000000..48a2855
--- /dev/null
@@ -0,0 +1,16 @@
+package org.simantics.h2d.diagram;\r
+\r
+import java.util.List;\r
+\r
+import org.simantics.h2d.element.IElement;\r
+\r
+/**\r
+ * Diagram is the whole that is edited in a diagram editor.\r
+ * @author Hannu Niemistö\r
+ */\r
+public interface IDiagram {\r
+       List<IElement> getElements();\r
+       void addElement(IElement element);\r
+       void removeElement(IElement element);\r
+       void addDiagramListener(IDiagramListener listener);     \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/diagram/IDiagramListener.java b/org.simantics.h2d/src/org/simantics/h2d/diagram/IDiagramListener.java
new file mode 100644 (file)
index 0000000..e3724c6
--- /dev/null
@@ -0,0 +1,10 @@
+package org.simantics.h2d.diagram;\r
+\r
+import org.simantics.h2d.element.IElement;\r
+\r
+public interface IDiagramListener {\r
+\r
+       void elementAdded(IElement element);\r
+       void elementRemoved(IElement element);\r
+       \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/editor/IDiagramEditor.java b/org.simantics.h2d/src/org/simantics/h2d/editor/IDiagramEditor.java
new file mode 100644 (file)
index 0000000..fc96185
--- /dev/null
@@ -0,0 +1,86 @@
+package org.simantics.h2d.editor;\r
+\r
+import java.awt.Canvas;\r
+import java.awt.Dimension;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Dimension2D;\r
+import java.awt.geom.Point2D;\r
+import java.util.List;\r
+\r
+import org.simantics.h2d.action.IAction;\r
+import org.simantics.h2d.diagram.IDiagram;\r
+import org.simantics.h2d.element.IElement;\r
+import org.simantics.h2d.event.IEvent;\r
+import org.simantics.h2d.event.handler.IEventHandler;\r
+import org.simantics.scenegraph.g2d.G2DSceneGraph;\r
+\r
+public interface IDiagramEditor {\r
+\r
+       IDiagram getDiagram();\r
+       \r
+       /**\r
+        * Returns the root of the scenegraph that renders the diagram.\r
+        */\r
+       G2DSceneGraph getSceneGraph();\r
+       \r
+       /**\r
+        * Handles an external event. \r
+        * @return True if the event was consumed.\r
+        */\r
+       boolean handleEvent(IEvent event);\r
+       \r
+       /** \r
+        * Returns the current view transform (from diagram coordinates to screen coordinates) defined as:\r
+        * <pre>diagramToScreen(p) = (p - offset) / scale</pre>\r
+        */\r
+       AffineTransform getViewTransform();\r
+       \r
+       /**\r
+        * Returns the current view offset. That is the diagram coordinates of the top left point of the canvas.\r
+        */\r
+       Point2D getOffset();\r
+       \r
+       /**\r
+        * Returns the current view scale. That is <pre>lengthInDiagramCoordinates / lengthInScreenCoordinates</pre>.\r
+        */\r
+       double getScale();\r
+       \r
+       /**\r
+        * Maps a point from screen coordinates to diagram coordinates.\r
+        */\r
+       Point2D screenToDiagram(Point2D point);\r
+       \r
+       /**\r
+        * Sets a new view transform. \r
+        * @param offset New offset\r
+        * @param scale New scale\r
+        *\r
+        * @see #getOffset\r
+        * @see #getScale\r
+        */\r
+       void setViewTransform(Point2D offset, double scale);\r
+       \r
+       void setViewDimensions(Dimension2D dimension);\r
+       Dimension getViewDimension();\r
+       \r
+       // Events\r
+       void addEventHandler(int priority, String eventType, IEventHandler handler);\r
+       void addEventHandler(int priority, IEventHandler handler);\r
+       \r
+       void addAction(IAction action);\r
+       void removeAction(IAction action);\r
+       \r
+       void requestRepaint();\r
+       void setCanvas(Canvas canvas);\r
+       \r
+       /**\r
+        * Returns current selection\r
+        */\r
+       ISelection getSelection();\r
+\r
+       /**\r
+        * Returns all elements at the point. Pick uses a hard coded tolerance that is calculated in screen coordinates.\r
+        */\r
+       List<IElement> pickElements(Point2D point);             \r
+       \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/editor/ISelection.java b/org.simantics.h2d/src/org/simantics/h2d/editor/ISelection.java
new file mode 100644 (file)
index 0000000..511d7bb
--- /dev/null
@@ -0,0 +1,25 @@
+package org.simantics.h2d.editor;\r
+\r
+import java.util.Collection;\r
+\r
+import org.simantics.h2d.element.IElement;\r
+\r
+public interface ISelection extends Iterable<IElement> {\r
+\r
+        boolean contains(IElement el);\r
+        boolean containsOneOf(Collection<IElement> els);\r
+        void clear();\r
+        void set(Collection<IElement> els);\r
+        void set(IElement el);\r
+        boolean add(IElement el);\r
+        boolean addAll(Collection<IElement> els);\r
+        boolean toggle(IElement el);\r
+        boolean remove(IElement el);\r
+        boolean isEmpty();\r
+        int size();\r
+        IElement getSingleElement();\r
+\r
+        void addSelectionListener(ISelectionListener listener);\r
+        void removeSelectionListener(ISelectionListener listener);\r
+        \r
+}
\ No newline at end of file
diff --git a/org.simantics.h2d/src/org/simantics/h2d/editor/ISelectionListener.java b/org.simantics.h2d/src/org/simantics/h2d/editor/ISelectionListener.java
new file mode 100644 (file)
index 0000000..6d9558d
--- /dev/null
@@ -0,0 +1,7 @@
+package org.simantics.h2d.editor;\r
+\r
+public interface ISelectionListener {\r
+\r
+    void selectionChanged(ISelection selection);\r
+    \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/editor/impl/DiagramEditor.java b/org.simantics.h2d/src/org/simantics/h2d/editor/impl/DiagramEditor.java
new file mode 100644 (file)
index 0000000..75d7e89
--- /dev/null
@@ -0,0 +1,175 @@
+package org.simantics.h2d.editor.impl;\r
+\r
+import java.awt.Canvas;\r
+import java.awt.Dimension;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Dimension2D;\r
+import java.awt.geom.Point2D;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import javax.swing.JComponent;\r
+\r
+import org.simantics.h2d.action.IAction;\r
+import org.simantics.h2d.diagram.IDiagram;\r
+import org.simantics.h2d.diagram.IDiagramListener;\r
+import org.simantics.h2d.editor.IDiagramEditor;\r
+import org.simantics.h2d.editor.ISelection;\r
+import org.simantics.h2d.element.IElement;\r
+import org.simantics.h2d.event.IEvent;\r
+import org.simantics.h2d.event.handler.IEventHandler;\r
+import org.simantics.scenegraph.g2d.G2DSceneGraph;\r
+\r
+public class DiagramEditor implements IDiagramEditor {\r
+\r
+       public static final double PICK_TOLERANCE = 5.0;\r
+\r
+       IDiagram diagram;\r
+       \r
+       // Viewpoint\r
+       Point2D offset;\r
+       double scale;\r
+       AffineTransform viewTransform = new AffineTransform();\r
+       Dimension dimension = new Dimension(800, 600);\r
+               \r
+       ISelection selection;\r
+       \r
+       ArrayList<IAction> actionStack = new ArrayList<IAction>(); \r
+       \r
+       SceneGraphManager sgManager;\r
+       EventHandlerManager eventHandlerManager = new EventHandlerManager();\r
+       \r
+       Canvas canvas;\r
+       \r
+       public void setCanvas(Canvas canvas) {\r
+               this.canvas = canvas;\r
+       }\r
+       \r
+       public DiagramEditor(JComponent rootPane, IDiagram diagram) {\r
+               this.diagram = diagram;\r
+               sgManager = new SceneGraphManager(rootPane);            \r
+               \r
+               setViewTransform(new Point2D.Double(), 13.0 / 48.0);\r
+               sgManager.setViewTransform(viewTransform);\r
+               \r
+               selection = new Selection(sgManager.selectionNode);\r
+               \r
+               for(IElement element : diagram.getElements())\r
+                       element.init(sgManager.elementsNode);\r
+               diagram.addDiagramListener(new IDiagramListener() {\r
+                       \r
+                       @Override\r
+                       public void elementAdded(IElement element) {\r
+                               element.init(sgManager.elementsNode);\r
+                       }\r
+\r
+                       @Override\r
+                       public void elementRemoved(IElement element) {\r
+                               element.remove();\r
+                       }\r
+                       \r
+               });\r
+       }       \r
+       \r
+       @Override\r
+       public G2DSceneGraph getSceneGraph() {\r
+               return sgManager.sceneGraph;\r
+       }\r
+\r
+       @Override\r
+       public boolean handleEvent(IEvent event) {\r
+               for(int i=actionStack.size()-1;i>=0;--i)\r
+                       if(actionStack.get(i).handle(this, event))\r
+                               return true;\r
+               return eventHandlerManager.handle(this, event); \r
+       }\r
+\r
+       @Override\r
+       public void addEventHandler(int priority, String eventType, IEventHandler handler) {\r
+               eventHandlerManager.addEventHandler(priority, eventType, handler);\r
+       }\r
+\r
+       @Override\r
+       public void addEventHandler(int priority, IEventHandler handler) {\r
+               eventHandlerManager.addEventHandler(priority, handler);\r
+       }\r
+\r
+       @Override\r
+       public ISelection getSelection() {\r
+               return selection;\r
+       }\r
+\r
+       @Override\r
+       public Point2D getOffset() {\r
+               return offset;\r
+       }\r
+\r
+       @Override\r
+       public double getScale() {\r
+               return scale;\r
+       }\r
+\r
+       @Override\r
+       public AffineTransform getViewTransform() {\r
+               return viewTransform;\r
+       }\r
+       \r
+       @Override\r
+       public void setViewTransform(Point2D offset, double scale) {\r
+               this.offset = offset;\r
+               this.scale = scale;\r
+               viewTransform.setTransform(1.0/scale, 0.0, 0.0, 1.0/scale, \r
+                               -offset.getX()/scale, -offset.getY()/scale);\r
+       }\r
+\r
+       @Override\r
+       public Point2D screenToDiagram(Point2D point) {\r
+               return new Point2D.Double(point.getX()*scale + offset.getX(), point.getY()*scale + offset.getY());\r
+       }\r
+\r
+       @Override\r
+       public IDiagram getDiagram() {\r
+               return diagram;\r
+       }\r
+\r
+       @Override\r
+       public List<IElement> pickElements(Point2D point) {\r
+               double tolerance = PICK_TOLERANCE*scale;\r
+               ArrayList<IElement> result = new ArrayList<IElement>();\r
+               for(IElement element : getDiagram().getElements()) {                                    \r
+                       if(element.hitTest(point.getX(), point.getY(), tolerance))\r
+                               result.add(element);\r
+               }\r
+               return result;\r
+       }\r
+\r
+       @Override\r
+       public void addAction(IAction action) {\r
+               actionStack.add(action);\r
+               action.init(sgManager.actionNode);\r
+               requestRepaint();\r
+       }\r
+       \r
+       @Override\r
+       public void removeAction(IAction action) {\r
+               actionStack.remove(action);\r
+               action.remove();\r
+               requestRepaint();\r
+       }\r
+\r
+       @Override\r
+       public Dimension getViewDimension() {\r
+               return dimension;\r
+       }\r
+\r
+       @Override\r
+       public void setViewDimensions(Dimension2D dimension) {\r
+               this.dimension.setSize(dimension);\r
+       }\r
+\r
+       @Override\r
+       public void requestRepaint() {\r
+               canvas.repaint();\r
+       }\r
+       \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/editor/impl/EventHandlerManager.java b/org.simantics.h2d/src/org/simantics/h2d/editor/impl/EventHandlerManager.java
new file mode 100644 (file)
index 0000000..5618986
--- /dev/null
@@ -0,0 +1,116 @@
+package org.simantics.h2d.editor.impl;\r
+\r
+import gnu.trove.THashMap;\r
+\r
+import java.util.LinkedList;\r
+import java.util.ListIterator;\r
+\r
+import org.simantics.h2d.editor.IDiagramEditor;\r
+import org.simantics.h2d.event.IEvent;\r
+import org.simantics.h2d.event.handler.IEventHandler;\r
+\r
+class EventHandlerManager implements IEventHandler {\r
+       LinkedList<PrioritizedEventHandler> handlers = new LinkedList<PrioritizedEventHandler>();\r
+       \r
+       public void addEventHandler(int priority, String eventType, IEventHandler handler) {\r
+               ListIterator<PrioritizedEventHandler> it = handlers.listIterator();\r
+               while(it.hasNext()) {\r
+                       PrioritizedEventHandler group = it.next();\r
+                       if(group.priority == priority)\r
+                               group.put(eventType, handler);\r
+                       else if(group.priority < priority) {\r
+                               it.previous();\r
+                               break;\r
+                       }\r
+               }\r
+               \r
+               // Add a new level\r
+               MapEventHandler map = new MapEventHandler(priority);\r
+               map.put(eventType, handler);\r
+               it.add(map);\r
+       }\r
+\r
+       public void addEventHandler(int priority, IEventHandler handler) {\r
+               ListIterator<PrioritizedEventHandler> it = handlers.listIterator();\r
+               while(it.hasNext()) {\r
+                       PrioritizedEventHandler group = it.next();\r
+                       if(group.priority == priority)\r
+                               throw new IllegalArgumentException("Tried add an event handler of type of priority " + priority + \r
+                                       ", but this conflicts with an event handler(s) with the same priority.");\r
+                       else if(group.priority < priority) {\r
+                               it.previous();\r
+                               break;\r
+                       }\r
+               }\r
+               \r
+               // Add a new level\r
+               it.add(new SingletonEventHandler(priority, handler));\r
+       }\r
+       \r
+       static abstract class PrioritizedEventHandler {\r
+               public final int priority;\r
+\r
+               public PrioritizedEventHandler(int priority) {  \r
+                       this.priority = priority;\r
+               }\r
+\r
+               public abstract void put(String type, IEventHandler handler);\r
+               public abstract boolean handle(String type, IDiagramEditor editor, IEvent event);\r
+               \r
+       }\r
+       \r
+       static class SingletonEventHandler extends PrioritizedEventHandler {\r
+\r
+               IEventHandler handler;\r
+               \r
+               public SingletonEventHandler(int priority, IEventHandler handler) {\r
+                       super(priority);\r
+                       this.handler = handler;\r
+               }\r
+\r
+               @Override\r
+               public boolean handle(String type, IDiagramEditor editor, IEvent event) {\r
+                       return handler.handle(editor, event);\r
+               }\r
+\r
+               @Override\r
+               public void put(String type, IEventHandler handler) {\r
+                       throw new IllegalArgumentException("Tried add an event handler of type " + type + " and priority " + priority + \r
+                               ", but this conflicts with an event handler with the same priority.");  \r
+               }\r
+\r
+       }\r
+       \r
+       static class MapEventHandler extends PrioritizedEventHandler {\r
+\r
+               THashMap<String, IEventHandler> handlers = new THashMap<String, IEventHandler>();\r
+               public MapEventHandler(int priority) {\r
+                       super(priority);\r
+               }       \r
+               \r
+               @Override\r
+               public void put(String type, IEventHandler handler) {\r
+                       if(handlers.contains(type))\r
+                               throw new IllegalArgumentException("Tried add an event handler of type " + type + " and priority " + priority + \r
+                                               ", but this conflicts with an event handler with the same priority and type.");\r
+                       handlers.put(type, handler);\r
+               }\r
+               \r
+               @Override\r
+               public boolean handle(String type, IDiagramEditor editor, IEvent event) {\r
+                       IEventHandler handler = handlers.get(type);\r
+                       return handler != null && handler.handle(editor, event);\r
+               }\r
+\r
+       }\r
+\r
+       @Override\r
+       public boolean handle(IDiagramEditor editor, IEvent event) {\r
+               String type = event.getType();\r
+               for(PrioritizedEventHandler level : handlers) {\r
+                       if(level.handle(type, editor, event))\r
+                               return true;                    \r
+               }               \r
+               return false;\r
+       }\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/editor/impl/SceneGraphManager.java b/org.simantics.h2d/src/org/simantics/h2d/editor/impl/SceneGraphManager.java
new file mode 100644 (file)
index 0000000..1a67333
--- /dev/null
@@ -0,0 +1,41 @@
+package org.simantics.h2d.editor.impl;\r
+\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Rectangle2D;\r
+\r
+import javax.swing.JComponent;\r
+\r
+import org.simantics.scenegraph.g2d.G2DParentNode;\r
+import org.simantics.scenegraph.g2d.G2DSceneGraph;\r
+import org.simantics.scenegraph.g2d.nodes.PageBorderNode;\r
+import org.simantics.scenegraph.g2d.nodes.TransformNode;\r
+\r
+class SceneGraphManager {\r
+       G2DSceneGraph sceneGraph;\r
+       TransformNode diagramCoordinatesNode;\r
+       G2DParentNode elementsNode;\r
+       G2DParentNode selectionNode;\r
+       G2DParentNode actionNode;\r
+       \r
+       public SceneGraphManager(JComponent rootPane) {\r
+               sceneGraph = new G2DSceneGraph();\r
+               //sceneGraph.setRootPane(rootPane);\r
+               diagramCoordinatesNode = sceneGraph.addNode(TransformNode.class);\r
+               \r
+               PageBorderNode border = diagramCoordinatesNode.addNode(PageBorderNode.class);\r
+               border.init(new Rectangle2D.Double(0.0, 0.0, 297.0, 210.0), Boolean.TRUE);\r
+               \r
+               elementsNode = diagramCoordinatesNode.addNode(G2DParentNode.class);\r
+               elementsNode.setZIndex(0);\r
+               \r
+               selectionNode = diagramCoordinatesNode.addNode(G2DParentNode.class);\r
+               selectionNode.setZIndex(1);\r
+               \r
+               actionNode = diagramCoordinatesNode.addNode(G2DParentNode.class);\r
+               actionNode.setZIndex(2);\r
+       }\r
+       \r
+       void setViewTransform(AffineTransform viewTransform) {\r
+               diagramCoordinatesNode.setTransform(viewTransform);\r
+       }\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/editor/impl/Selection.java b/org.simantics.h2d/src/org/simantics/h2d/editor/impl/Selection.java
new file mode 100644 (file)
index 0000000..ee5028e
--- /dev/null
@@ -0,0 +1,175 @@
+package org.simantics.h2d.editor.impl;\r
+\r
+import gnu.trove.THashSet;\r
+\r
+import java.awt.Color;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Rectangle2D;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Iterator;\r
+import java.util.concurrent.CopyOnWriteArrayList;\r
+\r
+import org.simantics.h2d.editor.ISelection;\r
+import org.simantics.h2d.editor.ISelectionListener;\r
+import org.simantics.h2d.element.IElement;\r
+import org.simantics.h2d.element.IElementListener;\r
+import org.simantics.scenegraph.g2d.G2DParentNode;\r
+import org.simantics.scenegraph.g2d.nodes.SelectionNode;\r
+\r
+class Selection implements ISelection {\r
+       static final AffineTransform IDENTITY = new AffineTransform();\r
+       \r
+       THashSet<IElement> elements = new THashSet<IElement>();\r
+       G2DParentNode selectionParentNode;      \r
+       \r
+       CopyOnWriteArrayList<ISelectionListener> listeners = \r
+           new CopyOnWriteArrayList<ISelectionListener>();\r
+       \r
+       static class SelectionUpdater implements IElementListener {\r
+               SelectionNode node;\r
+               IElement element;\r
+               \r
+               public SelectionUpdater(SelectionNode node, IElement element) {\r
+                       this.node = node;\r
+                       this.element = element;\r
+                       \r
+                       elementUpdated(element);                        \r
+                       element.addListener(this);\r
+               }\r
+\r
+               @Override\r
+               public void elementUpdated(IElement element) {\r
+                       Rectangle2D bounds = new Rectangle2D.Double();\r
+                       element.getBounds(bounds);\r
+                       node.init(IDENTITY, bounds, Color.GRAY);\r
+               }               \r
+               \r
+               public void remove() {\r
+                       element.removeListener(this);\r
+               }\r
+\r
+        @Override\r
+        public void elementRemoved(IElement element) {\r
+            // TODO ?\r
+        }\r
+               \r
+       }\r
+       \r
+       ArrayList<SelectionUpdater> updaters = new ArrayList<SelectionUpdater>();\r
+       \r
+       public Selection(G2DParentNode selectionParentNode) {\r
+               this.selectionParentNode = selectionParentNode;\r
+       }       \r
+       \r
+       private void updateSceneGraph() {               \r
+               // Clear old selection\r
+               selectionParentNode.removeNodes();\r
+               for(SelectionUpdater updater : updaters)\r
+                       updater.remove();\r
+               updaters.clear();\r
+               \r
+               // Create new selection\r
+               //System.out.println("selection: " + elements.size());\r
+               for(IElement element : elements)\r
+                       updaters.add(new SelectionUpdater(selectionParentNode.addNode(SelectionNode.class), element));\r
+               \r
+               // Notify listeners\r
+               // TODO this is in wrong place\r
+               for(ISelectionListener listener : listeners)\r
+                   listener.selectionChanged(this);\r
+       }       \r
+\r
+       public boolean contains(IElement el) {\r
+               return elements.contains(el);\r
+       }\r
+       \r
+       public boolean containsOneOf(Collection<IElement> els) {\r
+               for(IElement el : els)\r
+                       if(elements.contains(el))\r
+                               return true;\r
+               return false;\r
+       }\r
+       \r
+       public void clear() {\r
+               if(!elements.isEmpty()) {\r
+                       elements.clear();\r
+                       updateSceneGraph();\r
+               }\r
+       }\r
+       \r
+       public void set(Collection<IElement> els) {\r
+               elements.clear();\r
+               elements.addAll(els);\r
+               updateSceneGraph();\r
+       }\r
+       \r
+       public void set(IElement el) {\r
+               elements.clear();\r
+               elements.add(el);\r
+               updateSceneGraph();\r
+       }\r
+       \r
+       public boolean add(IElement el) {\r
+               boolean result = elements.add(el);\r
+               updateSceneGraph();\r
+               return result;\r
+       }\r
+       \r
+       public boolean addAll(Collection<IElement> els) {\r
+               boolean result = elements.addAll(els);\r
+               updateSceneGraph();\r
+               return result;\r
+       }\r
+       \r
+       public boolean toggle(IElement el) {\r
+               if(elements.contains(el)) {\r
+                       elements.remove(el);\r
+                       updateSceneGraph();\r
+                       return false;\r
+               }\r
+               else {\r
+                       elements.add(el);\r
+                       updateSceneGraph();\r
+                       return true;\r
+               }\r
+       }\r
+       \r
+       public boolean remove(IElement el) {\r
+               boolean result = elements.remove(el);\r
+               updateSceneGraph();\r
+               return result;\r
+       }\r
+\r
+       @Override\r
+       public Iterator<IElement> iterator() {\r
+               return elements.iterator();\r
+       }\r
+\r
+       @Override\r
+       public boolean isEmpty() {\r
+               return elements.isEmpty();\r
+       }\r
+\r
+       @Override\r
+       public int size() {\r
+               return elements.size();\r
+       }\r
+\r
+       @Override\r
+       public IElement getSingleElement() {\r
+               for(IElement element : elements)\r
+                       return element;\r
+               return null;\r
+       }\r
+\r
+    @Override\r
+    public void addSelectionListener(ISelectionListener listener) {\r
+        listeners.add(listener);\r
+    }\r
+\r
+    @Override\r
+    public void removeSelectionListener(ISelectionListener listener) {\r
+        listeners.remove(listener);        \r
+    }\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/element/ChainingElementListener.java b/org.simantics.h2d/src/org/simantics/h2d/element/ChainingElementListener.java
new file mode 100644 (file)
index 0000000..be5edee
--- /dev/null
@@ -0,0 +1,50 @@
+package org.simantics.h2d.element;\r
+\r
+\r
+class ChainingElementListener implements IElementListener {\r
+       IElementListener listener1;\r
+       IElementListener listener2;\r
+       \r
+       ChainingElementListener(IElementListener listener1,\r
+                       IElementListener listener2) {\r
+               this.listener1 = listener1;\r
+               this.listener2 = listener2;\r
+       }\r
+\r
+       @Override\r
+       public void elementUpdated(IElement element) {\r
+               listener1.elementUpdated(element);\r
+               listener2.elementUpdated(element);              \r
+       }\r
+\r
+    @Override\r
+    public void elementRemoved(IElement element) {\r
+        listener1.elementRemoved(element);\r
+        listener2.elementRemoved(element);      \r
+    }\r
+    \r
+       static IElementListener addListener(IElementListener currentListener, IElementListener newListener) {\r
+               if(currentListener == null)\r
+                       return newListener;\r
+               else \r
+                       return new ChainingElementListener(currentListener, newListener);\r
+       }\r
+       \r
+       static IElementListener removeListener(IElementListener currentListener, IElementListener listenerToRemove) {\r
+               if(currentListener == null || currentListener == listenerToRemove)\r
+                       return null;\r
+               else if(currentListener instanceof ChainingElementListener) {\r
+                       ChainingElementListener chain = (ChainingElementListener)currentListener;\r
+                       if(chain.listener2 == listenerToRemove)\r
+                               return chain.listener1;\r
+                       else {\r
+                               IElementListener l = removeListener(chain.listener1, listenerToRemove);\r
+                               if(l == null)\r
+                                       return chain.listener2;\r
+                               chain.listener1 = l;\r
+                       }\r
+               }\r
+               return currentListener;\r
+       }\r
+       \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/element/Element.java b/org.simantics.h2d/src/org/simantics/h2d/element/Element.java
new file mode 100644 (file)
index 0000000..e51faa6
--- /dev/null
@@ -0,0 +1,37 @@
+package org.simantics.h2d.element;\r
+\r
+\r
+public abstract class Element implements IElement {\r
+\r
+       IElementListener listener;\r
+       \r
+       @Override\r
+       public <T> T getInterface(Class<T> clazz) {\r
+               if(clazz.isInstance(this))\r
+                       return (T) this;\r
+               return null;\r
+       }\r
+       \r
+       protected void update() {\r
+               if(listener != null)\r
+                       listener.elementUpdated(this);\r
+       }\r
+\r
+       @Override\r
+       public void addListener(IElementListener listener) {\r
+               this.listener = ChainingElementListener.addListener(this.listener, listener);\r
+       }\r
+       \r
+       @Override\r
+       public void removeListener(IElementListener listener) {\r
+               this.listener = ChainingElementListener.removeListener(this.listener, listener);\r
+       }\r
+       \r
+       @Override\r
+       public void remove() {\r
+               if(listener != null)\r
+                       listener.elementRemoved(this);\r
+       }\r
+       \r
+\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/element/IElement.java b/org.simantics.h2d/src/org/simantics/h2d/element/IElement.java
new file mode 100644 (file)
index 0000000..495e298
--- /dev/null
@@ -0,0 +1,35 @@
+package org.simantics.h2d.element;\r
+\r
+import java.awt.geom.Rectangle2D;\r
+\r
+import org.simantics.scenegraph.g2d.G2DParentNode;\r
+\r
+/**\r
+ * Element is a part of a document that has its own type and properties.\r
+ * @author Hannu Niemistö\r
+ */\r
+public interface IElement {\r
+       <T> T getInterface(Class<T> clazz);\r
+       \r
+    void init(G2DParentNode parent);\r
+    void remove();\r
+       \r
+       /**\r
+        * Updates the parameter <code>bounds</code> so that it contains \r
+        * the bounding box of the element. \r
+        */\r
+       void getBounds(Rectangle2D bounds);\r
+       \r
+       /**\r
+        * Returns true, if the interior of the element intersects\r
+        * a circle at <code>(x,y)</code> with radius <code>tolerance</code>.\r
+        * Returns false, if the element and a circle at <code>(x,y)</code> with \r
+        * radius <code>sqrt(2)*tolerance</code> are disjoint. Otherwise may return true\r
+        * or false depending on the implementation.\r
+        */\r
+       boolean hitTest(double x, double y, double tolerance);\r
+       \r
+       void addListener(IElementListener listener);\r
+       void removeListener(IElementListener listener);\r
+       \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/element/IElementListener.java b/org.simantics.h2d/src/org/simantics/h2d/element/IElementListener.java
new file mode 100644 (file)
index 0000000..cfc27d4
--- /dev/null
@@ -0,0 +1,13 @@
+package org.simantics.h2d.element;\r
+\r
+\r
+public interface IElementListener {\r
+       /**\r
+        * Called when the publicy available properties of the elements are changed.\r
+        * @param element\r
+        */\r
+       public void elementUpdated(IElement element);\r
+       \r
+       public void elementRemoved(IElement element);\r
+       \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/element/handler/Connectable.java b/org.simantics.h2d/src/org/simantics/h2d/element/handler/Connectable.java
new file mode 100644 (file)
index 0000000..8287ddf
--- /dev/null
@@ -0,0 +1,15 @@
+package org.simantics.h2d.element.handler;\r
+\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+\r
+import org.simantics.h2d.element.IElement;\r
+\r
+\r
+\r
+public interface Connectable extends IElementHandler, IElement {\r
+\r
+       void getBounds(Rectangle2D bounds);\r
+       Point2D getOrigo();\r
+       \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/element/handler/IElementHandler.java b/org.simantics.h2d/src/org/simantics/h2d/element/handler/IElementHandler.java
new file mode 100644 (file)
index 0000000..aaebe43
--- /dev/null
@@ -0,0 +1,9 @@
+package org.simantics.h2d.element.handler;\r
+\r
+/**\r
+ * The base interface of all element handler interfaces. Needed only for\r
+ * documenting purposes.\r
+ * @author Hannu Niemistö\r
+ */\r
+public interface IElementHandler {\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/element/handler/Movable.java b/org.simantics.h2d/src/org/simantics/h2d/element/handler/Movable.java
new file mode 100644 (file)
index 0000000..0097542
--- /dev/null
@@ -0,0 +1,12 @@
+package org.simantics.h2d.element.handler;\r
+\r
+\r
+\r
+public interface Movable extends IElementHandler {\r
+\r
+       /**\r
+        * Moves the element by the given delta.\r
+        */\r
+       void move(double deltaX, double deltaY);\r
+       \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/element/handler/Rotatable.java b/org.simantics.h2d/src/org/simantics/h2d/element/handler/Rotatable.java
new file mode 100644 (file)
index 0000000..48c089f
--- /dev/null
@@ -0,0 +1,12 @@
+package org.simantics.h2d.element.handler;\r
+\r
+\r
+\r
+public interface Rotatable extends IElementHandler {\r
+\r
+       /**\r
+        * Rotates the element <code>amount</code> times 90 degrees clockwise.\r
+        */\r
+       void rotate(int amount);\r
+       \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/element/handler/ShadowDrawable.java b/org.simantics.h2d/src/org/simantics/h2d/element/handler/ShadowDrawable.java
new file mode 100644 (file)
index 0000000..c0c105a
--- /dev/null
@@ -0,0 +1,13 @@
+package org.simantics.h2d.element.handler;\r
+\r
+import java.awt.Graphics2D;\r
+import java.awt.geom.AffineTransform;\r
+\r
+public interface ShadowDrawable {\r
+       \r
+       /**\r
+        * Draws the shadow of the element.\r
+        */\r
+       void draw(Graphics2D g, AffineTransform transform);\r
+       \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/ClickEvent.java b/org.simantics.h2d/src/org/simantics/h2d/event/ClickEvent.java
new file mode 100644 (file)
index 0000000..89c6978
--- /dev/null
@@ -0,0 +1,40 @@
+package org.simantics.h2d.event;\r
+\r
+import java.awt.geom.Point2D;\r
+import java.util.List;\r
+\r
+import org.simantics.h2d.element.IElement;\r
+\r
+\r
+public class ClickEvent implements ILocatableEvent {   \r
+       final public String modifiers;\r
+\r
+       // Click location in diagram coordinates\r
+       final public Point2D point;\r
+\r
+       public List<IElement> pickedElements;\r
+       \r
+       public ClickEvent(String modifiers, Point2D point) {\r
+               this.modifiers = modifiers;\r
+               this.point = point;\r
+       }\r
+\r
+       @Override\r
+       public String getType() {\r
+               return getType(modifiers);\r
+       }\r
+       \r
+       public static String getType(String modifiers) {\r
+               return "click(" + modifiers + ")";\r
+       }\r
+       \r
+       @Override\r
+       public Point2D getLocation() {\r
+               return point;\r
+       }\r
+\r
+       @Override\r
+       public List<IElement> getPickedElements() {\r
+               return pickedElements;\r
+       }\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/DragEvent.java b/org.simantics.h2d/src/org/simantics/h2d/event/DragEvent.java
new file mode 100644 (file)
index 0000000..9256752
--- /dev/null
@@ -0,0 +1,47 @@
+package org.simantics.h2d.event;\r
+\r
+import java.awt.geom.Point2D;\r
+import java.util.List;\r
+\r
+import org.simantics.h2d.element.IElement;\r
+\r
+\r
+public class DragEvent implements ILocatableEvent {\r
+       public DragEventPhase phase;\r
+       \r
+       public String startModifiers;\r
+       public String currentModifiers;\r
+       \r
+       public Point2D start;\r
+       public Point2D current;\r
+       \r
+       public List<IElement> pickedElements;\r
+\r
+       public DragEvent(String startModifiers, Point2D start) {\r
+               this.phase = DragEventPhase.dragBegin;\r
+               this.startModifiers = startModifiers;\r
+               this.currentModifiers = startModifiers;\r
+               this.start = start;\r
+               this.current = start;\r
+       }\r
+\r
+       @Override\r
+       public String getType() {\r
+               return getType(startModifiers);\r
+       }\r
+       \r
+       public static String getType(String modifiers) {\r
+               return "drag(" + modifiers + ")";\r
+       }\r
+\r
+       @Override\r
+       public Point2D getLocation() {\r
+               return start;\r
+       }\r
+\r
+       @Override\r
+       public List<IElement> getPickedElements() {\r
+               return pickedElements;\r
+       }\r
+       \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/DragEventPhase.java b/org.simantics.h2d/src/org/simantics/h2d/event/DragEventPhase.java
new file mode 100644 (file)
index 0000000..46d093d
--- /dev/null
@@ -0,0 +1,5 @@
+package org.simantics.h2d.event;\r
+\r
+public enum DragEventPhase {\r
+       dragBegin, dragUpdate, dragEnd \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/IDragHandler.java b/org.simantics.h2d/src/org/simantics/h2d/event/IDragHandler.java
new file mode 100644 (file)
index 0000000..e06e9fc
--- /dev/null
@@ -0,0 +1,6 @@
+package org.simantics.h2d.event;\r
+\r
+public interface IDragHandler {\r
+       public void handleMove();\r
+       public void handleRelease();\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/IEvent.java b/org.simantics.h2d/src/org/simantics/h2d/event/IEvent.java
new file mode 100644 (file)
index 0000000..0962420
--- /dev/null
@@ -0,0 +1,5 @@
+package org.simantics.h2d.event;\r
+\r
+public interface IEvent {\r
+       String getType();\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/ILocatableEvent.java b/org.simantics.h2d/src/org/simantics/h2d/event/ILocatableEvent.java
new file mode 100644 (file)
index 0000000..67eb22f
--- /dev/null
@@ -0,0 +1,11 @@
+package org.simantics.h2d.event;\r
+\r
+import java.awt.geom.Point2D;\r
+import java.util.List;\r
+\r
+import org.simantics.h2d.element.IElement;\r
+\r
+public interface ILocatableEvent extends IEvent {\r
+       Point2D getLocation();\r
+       List<IElement> getPickedElements();\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/KeyboardEvent.java b/org.simantics.h2d/src/org/simantics/h2d/event/KeyboardEvent.java
new file mode 100644 (file)
index 0000000..a93d4f5
--- /dev/null
@@ -0,0 +1,42 @@
+package org.simantics.h2d.event;\r
+\r
+import java.awt.geom.Point2D;\r
+import java.util.List;\r
+\r
+import org.simantics.h2d.element.IElement;\r
+\r
+\r
+public class KeyboardEvent implements ILocatableEvent {        \r
+       final public String key;\r
+\r
+       // Click location in diagram coordinates\r
+       final public Point2D point;\r
+\r
+       public List<IElement> pickedElements;\r
+       \r
+\r
+       public KeyboardEvent(String key, Point2D point) {\r
+               this.key = key;\r
+               this.point = point;\r
+       }\r
+\r
+\r
+       @Override\r
+       public String getType() {\r
+               return getType(key);\r
+       }\r
+       \r
+       public static String getType(String key) {\r
+               return "key(" + key + ")";\r
+       }\r
+       \r
+       @Override\r
+       public Point2D getLocation() {\r
+               return point;\r
+       }\r
+\r
+       @Override\r
+       public List<IElement> getPickedElements() {\r
+               return pickedElements;\r
+       }\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/Modifiers.java b/org.simantics.h2d/src/org/simantics/h2d/event/Modifiers.java
new file mode 100644 (file)
index 0000000..f891f3b
--- /dev/null
@@ -0,0 +1,29 @@
+package org.simantics.h2d.event;\r
+\r
+import java.awt.event.MouseEvent;\r
+\r
+public class Modifiers {\r
+\r
+       public static String modifierString(\r
+                       int button,\r
+                       boolean ctrl,\r
+                       boolean alt,\r
+                       boolean shift\r
+                       ) {\r
+               StringBuilder b = new StringBuilder();\r
+               if(ctrl)\r
+                       b.append("ctrl+");\r
+               if(alt)\r
+                       b.append("alt+");\r
+               if(shift)\r
+                       b.append("shift+");\r
+               if(button == MouseEvent.BUTTON1)\r
+                       b.append("left");\r
+               else if(button == MouseEvent.BUTTON2)\r
+                       b.append("middle");\r
+               else if(button == MouseEvent.BUTTON3)\r
+                       b.append("right");              \r
+               return b.toString();\r
+       }\r
+       \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/WheelEvent.java b/org.simantics.h2d/src/org/simantics/h2d/event/WheelEvent.java
new file mode 100644 (file)
index 0000000..3e7b419
--- /dev/null
@@ -0,0 +1,28 @@
+package org.simantics.h2d.event;\r
+\r
+import java.awt.geom.Point2D;\r
+\r
+\r
+public class WheelEvent implements IEvent {    \r
+       final public String modifiers;\r
+\r
+       // Click location in diagram coordinates\r
+       final public Point2D point;\r
+       \r
+       final public int amount;\r
+\r
+       public WheelEvent(String modifiers, Point2D point, int amount) {\r
+               this.modifiers = modifiers;\r
+               this.point = point;\r
+               this.amount = amount;\r
+       }\r
+\r
+       @Override\r
+       public String getType() {\r
+               return getType(modifiers);\r
+       }\r
+       \r
+       public static String getType(String modifiers) {\r
+               return "wheel(" + modifiers + ")";\r
+       }\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/handler/BoxSelection.java b/org.simantics.h2d/src/org/simantics/h2d/event/handler/BoxSelection.java
new file mode 100644 (file)
index 0000000..ce61a34
--- /dev/null
@@ -0,0 +1,57 @@
+package org.simantics.h2d.event.handler;\r
+\r
+import java.awt.geom.Rectangle2D;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+\r
+import org.simantics.h2d.editor.IDiagramEditor;\r
+import org.simantics.h2d.editor.ISelection;\r
+import org.simantics.h2d.element.IElement;\r
+import org.simantics.h2d.event.DragEvent;\r
+import org.simantics.h2d.node.RectangleNode;\r
+import org.simantics.scenegraph.g2d.G2DParentNode;\r
+\r
+public class BoxSelection extends DragEventHandler {\r
+\r
+       Rectangle2D rectangle = new Rectangle2D.Double();\r
+       RectangleNode selectionNode;\r
+       \r
+       @Override\r
+       protected void update(IDiagramEditor editor, DragEvent event) {\r
+               rectangle.setFrameFromDiagonal(event.start, event.current);\r
+               editor.requestRepaint();\r
+       }\r
+\r
+       @Override\r
+       protected void end(IDiagramEditor editor, DragEvent event) {\r
+               Rectangle2D.Double elementBounds = new Rectangle2D.Double();\r
+               ISelection selection = editor.getSelection();\r
+               System.out.println(event.currentModifiers);\r
+               \r
+               Collection<IElement> toBeSelected = new ArrayList<IElement>();\r
+               for(IElement element : editor.getDiagram().getElements()) {\r
+            element.getBounds(elementBounds);\r
+            if(rectangle.contains(elementBounds))\r
+                toBeSelected.add(element);\r
+        }\r
+               \r
+               if(event.currentModifiers.equals("ctrl+"))\r
+                   selection.addAll(toBeSelected);\r
+               else\r
+                   selection.set(toBeSelected);\r
+               editor.requestRepaint();                \r
+       }\r
+       \r
+       @Override\r
+       public void init(G2DParentNode parent) {\r
+               selectionNode = parent.addNode(RectangleNode.class);\r
+               selectionNode.init(rectangle);\r
+       }\r
+       \r
+       @Override\r
+       public void remove() {          \r
+               selectionNode.remove();\r
+               selectionNode = null;\r
+       }\r
+\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/handler/DefaultEventHandlers.java b/org.simantics.h2d/src/org/simantics/h2d/event/handler/DefaultEventHandlers.java
new file mode 100644 (file)
index 0000000..8a6be67
--- /dev/null
@@ -0,0 +1,29 @@
+package org.simantics.h2d.event.handler;\r
+\r
+import org.simantics.h2d.editor.IDiagramEditor;\r
+\r
+public class DefaultEventHandlers {\r
+\r
+       private DefaultEventHandlers() {}\r
+       \r
+       public static void configure(IDiagramEditor editor) {\r
+               editor.addEventHandler(1, "click(left)", new PickSelection());\r
+               editor.addEventHandler(1, "click(ctrl+left)", new ToggleSelection());           \r
+               editor.addEventHandler(1, "drag(alt+middle)", new Pan());\r
+               editor.addEventHandler(1, "wheel()", new Zoom());\r
+               editor.addEventHandler(1, "key(1)", new ZoomToFit());\r
+               editor.addEventHandler(1, "key(Period)", new RotateCounterclockwise());\r
+               editor.addEventHandler(1, "key(Comma)", new RotateClockwise());\r
+               editor.addEventHandler(1, "key(Ctrl+D)", new Delete());\r
+                               \r
+               editor.addEventHandler(0, new ElementEventDelegator());\r
+               \r
+               editor.addEventHandler(-1, "drag(left)", new MoveSelected());\r
+               editor.addEventHandler(-2, "drag(left)", new BoxSelection());\r
+               editor.addEventHandler(-2, "drag(ctrl+left)", new BoxSelection());\r
+               \r
+               // Prints all unhandled events\r
+               //editor.addEventHandler(-1000, new EventPrinter());\r
+       }\r
+       \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/handler/Delete.java b/org.simantics.h2d/src/org/simantics/h2d/event/handler/Delete.java
new file mode 100644 (file)
index 0000000..bcbf6dd
--- /dev/null
@@ -0,0 +1,21 @@
+package org.simantics.h2d.event.handler;\r
+\r
+import org.simantics.h2d.diagram.IDiagram;\r
+import org.simantics.h2d.editor.IDiagramEditor;\r
+import org.simantics.h2d.element.IElement;\r
+import org.simantics.h2d.event.IEvent;\r
+\r
+public class Delete implements IEventHandler {\r
+\r
+       @Override\r
+       public boolean handle(IDiagramEditor editor, IEvent event) {\r
+               IDiagram diagram = editor.getDiagram();\r
+               for(IElement element : editor.getSelection()) {\r
+                       diagram.removeElement(element);\r
+               }\r
+               editor.getSelection().clear();\r
+               editor.requestRepaint();\r
+               return true;\r
+       }\r
+\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/handler/DragEventHandler.java b/org.simantics.h2d/src/org/simantics/h2d/event/handler/DragEventHandler.java
new file mode 100644 (file)
index 0000000..f4c7ebd
--- /dev/null
@@ -0,0 +1,67 @@
+package org.simantics.h2d.event.handler;\r
+\r
+import org.simantics.h2d.action.IAction;\r
+import org.simantics.h2d.editor.IDiagramEditor;\r
+import org.simantics.h2d.event.DragEvent;\r
+import org.simantics.h2d.event.IEvent;\r
+import org.simantics.scenegraph.g2d.G2DParentNode;\r
+\r
+public abstract class DragEventHandler implements IAction {\r
+\r
+       boolean isActive = false;\r
+       \r
+       protected boolean begin(IDiagramEditor editor, DragEvent event) {\r
+               return true;\r
+       }\r
+       \r
+       protected void update(IDiagramEditor editor, DragEvent event) {\r
+       }\r
+       \r
+       protected void end(IDiagramEditor editor, DragEvent event) {\r
+       }\r
+       \r
+       @Override\r
+       public boolean handle(IDiagramEditor editor, IEvent _event) {\r
+               if(_event instanceof DragEvent) {\r
+                       DragEvent event = (DragEvent)_event;\r
+                       \r
+                       switch(event.phase) {\r
+                       case dragBegin:\r
+                               if(isActive)\r
+                                       return true;                            \r
+                               if(begin(editor, event)) {\r
+                                       isActive = true;\r
+                                       editor.addAction(this);\r
+                                       return true;\r
+                               }\r
+                               else\r
+                                       return false;\r
+                               \r
+                       case dragUpdate:\r
+                               if(!isActive)\r
+                                       return false;\r
+                               update(editor, event);\r
+                               return true;\r
+                               \r
+                       case dragEnd:\r
+                               if(!isActive)\r
+                                       return false;\r
+                               isActive = false;\r
+                               editor.removeAction(this);\r
+                               end(editor, event);\r
+                               return true;\r
+                       }       \r
+                       \r
+               }\r
+               return false;\r
+       }\r
+\r
+       @Override\r
+       public void init(G2DParentNode parent) {\r
+       }\r
+\r
+       @Override\r
+       public void remove() {\r
+       }\r
+\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/handler/ElementEventDelegator.java b/org.simantics.h2d/src/org/simantics/h2d/event/handler/ElementEventDelegator.java
new file mode 100644 (file)
index 0000000..66a6e21
--- /dev/null
@@ -0,0 +1,37 @@
+package org.simantics.h2d.event.handler;\r
+\r
+import org.simantics.h2d.editor.IDiagramEditor;\r
+import org.simantics.h2d.element.IElement;\r
+import org.simantics.h2d.event.IEvent;\r
+import org.simantics.h2d.event.ILocatableEvent;\r
+import org.simantics.h2d.event.KeyboardEvent;\r
+\r
+public class ElementEventDelegator implements IEventHandler {\r
+       \r
+       @Override\r
+       public boolean handle(IDiagramEditor editor, IEvent _event) {\r
+               if(_event instanceof KeyboardEvent) {\r
+                       IEventHandler uniqueHandler = null;\r
+                       for(IElement element : editor.getSelection()) {\r
+                               IEventHandler handler = element.getInterface(IEventHandler.class);\r
+                               if(handler != null) {\r
+                                       if(uniqueHandler != null)\r
+                                               return true; // nobody cannot consume the event\r
+                                       uniqueHandler = handler;\r
+                               }\r
+                       }\r
+                       if(uniqueHandler != null)\r
+                               return uniqueHandler.handle(editor, _event);\r
+               }\r
+               else if(_event instanceof ILocatableEvent) {\r
+                       ILocatableEvent event = (ILocatableEvent)_event;\r
+                       for(IElement element : event.getPickedElements()) {\r
+                               IEventHandler handler = element.getInterface(IEventHandler.class);\r
+                               if(handler != null && handler.handle(editor, event))\r
+                                       return true;\r
+                       }\r
+               }\r
+               return false;\r
+       }\r
+\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/handler/EventPrinter.java b/org.simantics.h2d/src/org/simantics/h2d/event/handler/EventPrinter.java
new file mode 100644 (file)
index 0000000..3d84895
--- /dev/null
@@ -0,0 +1,14 @@
+package org.simantics.h2d.event.handler;\r
+\r
+import org.simantics.h2d.editor.IDiagramEditor;\r
+import org.simantics.h2d.event.IEvent;\r
+\r
+public class EventPrinter implements IEventHandler {\r
+\r
+       @Override\r
+       public boolean handle(IDiagramEditor editor, IEvent event) {\r
+               System.out.println(event.getType());\r
+               return false;\r
+       }\r
+\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/handler/IEventHandler.java b/org.simantics.h2d/src/org/simantics/h2d/event/handler/IEventHandler.java
new file mode 100644 (file)
index 0000000..9e9f54f
--- /dev/null
@@ -0,0 +1,10 @@
+package org.simantics.h2d.event.handler;\r
+\r
+import org.simantics.h2d.editor.IDiagramEditor;\r
+import org.simantics.h2d.event.IEvent;\r
+\r
+public interface IEventHandler {\r
+\r
+       boolean handle(IDiagramEditor editor, IEvent event);\r
+       \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/handler/MoveSelected.java b/org.simantics.h2d/src/org/simantics/h2d/event/handler/MoveSelected.java
new file mode 100644 (file)
index 0000000..d55befc
--- /dev/null
@@ -0,0 +1,76 @@
+package org.simantics.h2d.event.handler;\r
+\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Rectangle2D;\r
+import java.util.List;\r
+\r
+import org.simantics.h2d.editor.IDiagramEditor;\r
+import org.simantics.h2d.editor.ISelection;\r
+import org.simantics.h2d.element.IElement;\r
+import org.simantics.h2d.element.handler.Movable;\r
+import org.simantics.h2d.event.DragEvent;\r
+import org.simantics.h2d.node.RectangleNode;\r
+import org.simantics.scenegraph.g2d.G2DParentNode;\r
+import org.simantics.scenegraph.g2d.nodes.TransformNode;\r
+\r
+public class MoveSelected extends DragEventHandler {\r
+\r
+       double deltaX, deltaY;  \r
+       TransformNode shadowNode;\r
+       ISelection selection;\r
+       \r
+       @Override\r
+       protected boolean begin(IDiagramEditor editor, DragEvent event) {\r
+               List<IElement> pick = event.pickedElements;\r
+               if(pick.isEmpty())\r
+                       return false;\r
+               selection = editor.getSelection();\r
+               boolean pickContainsSelected = false;\r
+               for(IElement element : pick)\r
+                       if(selection.contains(element)) {\r
+                               pickContainsSelected = true;\r
+                               break;\r
+                       }               \r
+               if(!pickContainsSelected) {\r
+                       selection.clear();\r
+                       selection.add(pick.get(0));\r
+               }\r
+               return true;\r
+       }\r
+       \r
+       @Override\r
+       protected void update(IDiagramEditor editor, DragEvent event) {\r
+               deltaX = event.current.getX() - event.start.getX();\r
+               deltaY = event.current.getY() - event.start.getY();\r
+               shadowNode.setTransform(new AffineTransform(1.0, 0.0, 0.0, 1.0, deltaX, deltaY));\r
+               editor.requestRepaint();\r
+       }\r
+       \r
+       @Override\r
+       protected void end(IDiagramEditor editor, DragEvent event) {\r
+               for(IElement element : selection)\r
+                       if(element instanceof Movable)\r
+                               ((Movable)element).move(deltaX, deltaY);\r
+               selection = null;\r
+               editor.requestRepaint();\r
+       }\r
+       \r
+       @Override\r
+       public void init(G2DParentNode parent) {\r
+               shadowNode = parent.addNode(TransformNode.class);\r
+               \r
+               for(IElement element : selection) {\r
+                       Rectangle2D elementBounds = new Rectangle2D.Double();\r
+                       element.getBounds(elementBounds);\r
+                       RectangleNode shadow = shadowNode.addNode(RectangleNode.class);\r
+                       shadow.init(elementBounds);\r
+               }\r
+       }\r
+       \r
+       @Override\r
+       public void remove() {\r
+               shadowNode.remove();\r
+               shadowNode = null;\r
+       }\r
+\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/handler/Pan.java b/org.simantics.h2d/src/org/simantics/h2d/event/handler/Pan.java
new file mode 100644 (file)
index 0000000..7455322
--- /dev/null
@@ -0,0 +1,23 @@
+package org.simantics.h2d.event.handler;\r
+\r
+import java.awt.geom.Point2D;\r
+\r
+import org.simantics.h2d.editor.IDiagramEditor;\r
+import org.simantics.h2d.event.DragEvent;\r
+\r
+public class Pan extends DragEventHandler {\r
+\r
+       @Override\r
+       protected void update(IDiagramEditor editor, DragEvent event) {\r
+               Point2D offset = editor.getOffset();\r
+               double scale = editor.getScale();\r
+               editor.setViewTransform(\r
+                               new Point2D.Double(\r
+                                               offset.getX() + event.start.getX() - event.current.getX(),\r
+                                               offset.getY() + event.start.getY() - event.current.getY()\r
+                                       ), \r
+                                       scale);\r
+               editor.requestRepaint();\r
+       }\r
+       \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/handler/PickSelection.java b/org.simantics.h2d/src/org/simantics/h2d/event/handler/PickSelection.java
new file mode 100644 (file)
index 0000000..a0cc6e4
--- /dev/null
@@ -0,0 +1,51 @@
+package org.simantics.h2d.event.handler;\r
+\r
+import java.util.List;\r
+\r
+import org.simantics.h2d.editor.IDiagramEditor;\r
+import org.simantics.h2d.editor.ISelection;\r
+import org.simantics.h2d.element.IElement;\r
+import org.simantics.h2d.event.ClickEvent;\r
+import org.simantics.h2d.event.IEvent;\r
+\r
+public class PickSelection implements IEventHandler {\r
+\r
+       @Override\r
+       public boolean handle(IDiagramEditor editor, IEvent _event) {\r
+               ClickEvent event = (ClickEvent)_event;                          \r
+               List<IElement> pick = event.pickedElements;\r
+               ISelection selection = editor.getSelection();\r
+               if(pick.isEmpty()) {\r
+                       selection.clear();\r
+                       editor.requestRepaint();\r
+               }\r
+               else {\r
+                       if(selection.containsOneOf(pick)) {\r
+                               if(pick.size() > 1 && selection.size()==1) {\r
+                                       /*\r
+                                        * If there are multiple elements under mouse and the current\r
+                                        * selection is exactly one of them then rotate thru all one\r
+                                        * element selection possibilities.\r
+                                        */\r
+                                       IElement s = selection.getSingleElement();\r
+                                       for(int i=0;i<pick.size();++i) {\r
+                                               if(s.equals(pick.get(i))) {\r
+                                                       if(i < pick.size()-1) {\r
+                                                               selection.set(pick.get(i+1));\r
+                                                               return true;\r
+                                                       }\r
+                                               }\r
+                                       }\r
+                                       selection.set(pick.get(0));\r
+                                       editor.requestRepaint();\r
+                               }\r
+                       }\r
+                       else {\r
+                               selection.set(pick.get(0));\r
+                               editor.requestRepaint();\r
+                       }\r
+               }               \r
+               return true;\r
+       }\r
+\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/handler/RotateClockwise.java b/org.simantics.h2d/src/org/simantics/h2d/event/handler/RotateClockwise.java
new file mode 100644 (file)
index 0000000..5bd3671
--- /dev/null
@@ -0,0 +1,20 @@
+package org.simantics.h2d.event.handler;\r
+\r
+import org.simantics.h2d.editor.IDiagramEditor;\r
+import org.simantics.h2d.element.IElement;\r
+import org.simantics.h2d.element.handler.Rotatable;\r
+import org.simantics.h2d.event.IEvent;\r
+\r
+public class RotateClockwise implements IEventHandler {\r
+\r
+       @Override\r
+       public boolean handle(IDiagramEditor editor, IEvent event) {\r
+               for(IElement element : editor.getSelection()) {\r
+                       Rotatable rotatable = element.getInterface(Rotatable.class);\r
+                       rotatable.rotate(1);\r
+               }\r
+               editor.requestRepaint();\r
+               return true;\r
+       }\r
+\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/handler/RotateCounterclockwise.java b/org.simantics.h2d/src/org/simantics/h2d/event/handler/RotateCounterclockwise.java
new file mode 100644 (file)
index 0000000..3fe04eb
--- /dev/null
@@ -0,0 +1,20 @@
+package org.simantics.h2d.event.handler;\r
+\r
+import org.simantics.h2d.editor.IDiagramEditor;\r
+import org.simantics.h2d.element.IElement;\r
+import org.simantics.h2d.element.handler.Rotatable;\r
+import org.simantics.h2d.event.IEvent;\r
+\r
+public class RotateCounterclockwise implements IEventHandler {\r
+\r
+       @Override\r
+       public boolean handle(IDiagramEditor editor, IEvent event) {\r
+               for(IElement element : editor.getSelection()) {\r
+                       Rotatable rotatable = element.getInterface(Rotatable.class);\r
+                       rotatable.rotate(-1);\r
+               }\r
+               editor.requestRepaint();\r
+               return true;\r
+       }\r
+\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/handler/ToggleSelection.java b/org.simantics.h2d/src/org/simantics/h2d/event/handler/ToggleSelection.java
new file mode 100644 (file)
index 0000000..f8761a8
--- /dev/null
@@ -0,0 +1,23 @@
+package org.simantics.h2d.event.handler;\r
+\r
+import java.util.List;\r
+\r
+import org.simantics.h2d.editor.IDiagramEditor;\r
+import org.simantics.h2d.element.IElement;\r
+import org.simantics.h2d.event.ClickEvent;\r
+import org.simantics.h2d.event.IEvent;\r
+\r
+public class ToggleSelection implements IEventHandler {\r
+\r
+       @Override\r
+       public boolean handle(IDiagramEditor editor, IEvent _event) {\r
+               ClickEvent event = (ClickEvent)_event;                          \r
+               List<IElement> pick = event.pickedElements;             \r
+               if(!pick.isEmpty()) {\r
+                       editor.getSelection().toggle(pick.get(0));\r
+                       editor.requestRepaint();\r
+               }\r
+               return true;\r
+       }\r
+\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/handler/Zoom.java b/org.simantics.h2d/src/org/simantics/h2d/event/handler/Zoom.java
new file mode 100644 (file)
index 0000000..d02e74a
--- /dev/null
@@ -0,0 +1,30 @@
+package org.simantics.h2d.event.handler;\r
+\r
+import java.awt.geom.Point2D;\r
+\r
+import org.simantics.h2d.editor.IDiagramEditor;\r
+import org.simantics.h2d.event.IEvent;\r
+import org.simantics.h2d.event.WheelEvent;\r
+\r
+public class Zoom implements IEventHandler {\r
+\r
+       public final static double ZOOM_PER_CLICK = 1.2;\r
+\r
+       @Override\r
+       public boolean handle(IDiagramEditor editor, IEvent _event) {\r
+               final WheelEvent event = (WheelEvent)_event;\r
+               Point2D offset = editor.getOffset();\r
+               double scale = editor.getScale();\r
+               double scaleRatio = Math.pow(ZOOM_PER_CLICK, event.amount);\r
+               \r
+               editor.setViewTransform(\r
+                               new Point2D.Double(\r
+                                       offset.getX() * scaleRatio + event.point.getX() * (1.0 - scaleRatio),\r
+                                       offset.getY() * scaleRatio + event.point.getY() * (1.0 - scaleRatio)\r
+                               ), \r
+                               scale * scaleRatio);\r
+               editor.requestRepaint();\r
+               return true;\r
+       }\r
+\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/event/handler/ZoomToFit.java b/org.simantics.h2d/src/org/simantics/h2d/event/handler/ZoomToFit.java
new file mode 100644 (file)
index 0000000..1f2d0bf
--- /dev/null
@@ -0,0 +1,45 @@
+package org.simantics.h2d.event.handler;\r
+\r
+import java.awt.Dimension;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+\r
+import org.simantics.h2d.editor.IDiagramEditor;\r
+import org.simantics.h2d.element.IElement;\r
+import org.simantics.h2d.event.IEvent;\r
+\r
+public class ZoomToFit implements IEventHandler {\r
+\r
+       @Override\r
+       public boolean handle(IDiagramEditor editor, IEvent event) {\r
+               Rectangle2D diagramBounds = null;\r
+               Rectangle2D elementBounds = new Rectangle2D.Double();\r
+               for(IElement element : editor.getDiagram().getElements()) {\r
+                       element.getBounds(elementBounds);\r
+                       if(diagramBounds == null) {\r
+                               diagramBounds = new Rectangle2D.Double();\r
+                               diagramBounds.setFrame(elementBounds);\r
+                       }\r
+                       else {\r
+                               Rectangle2D.union(diagramBounds, elementBounds, diagramBounds);\r
+                       }\r
+               }\r
+               if(diagramBounds != null) {\r
+                       Dimension dimension = editor.getViewDimension();\r
+                       \r
+                       double scale = Math.max(\r
+                                       diagramBounds.getWidth() / dimension.getWidth(),\r
+                                       diagramBounds.getHeight() / dimension.getHeight()\r
+                                       );                      \r
+                       \r
+                       Point2D offset = new Point2D.Double(\r
+                                       diagramBounds.getCenterX() - dimension.getWidth() * scale * 0.5,\r
+                                       diagramBounds.getCenterY() - dimension.getHeight() * scale * 0.5\r
+                               );\r
+                       editor.setViewTransform(offset, scale);\r
+               }\r
+               editor.requestRepaint();\r
+               return true;\r
+       }\r
+\r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/node/FilledShapeNode.java b/org.simantics.h2d/src/org/simantics/h2d/node/FilledShapeNode.java
new file mode 100644 (file)
index 0000000..687c847
--- /dev/null
@@ -0,0 +1,41 @@
+package org.simantics.h2d.node;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.geom.Rectangle2D;
+
+import org.simantics.scenegraph.g2d.G2DNode;
+
+public class FilledShapeNode extends G2DNode {
+
+       private static final long serialVersionUID = -7540487222025677413L;
+       
+       protected Shape shape = null;
+    protected Color color = Color.BLACK;
+
+    @SyncField("shape")
+    public void setShape(Shape shape) {
+        this.shape = shape;
+    }
+    
+    @SyncField("color")
+    public void setColor(Color color) {
+        this.color = color;
+    }
+
+    @Override
+    public void render(Graphics2D g2d) {
+        if(shape == null) return;
+        if(color != null) g2d.setColor(color);
+        
+        g2d.fill(shape);
+    }
+
+    @Override
+    public Rectangle2D getBounds() {
+        return shape.getBounds2D();
+    }
+    
+}
diff --git a/org.simantics.h2d/src/org/simantics/h2d/node/ITextListener.java b/org.simantics.h2d/src/org/simantics/h2d/node/ITextListener.java
new file mode 100644 (file)
index 0000000..e0d31d2
--- /dev/null
@@ -0,0 +1,7 @@
+package org.simantics.h2d.node;\r
+\r
+public interface ITextListener {\r
+\r
+       void textChanged();\r
+       \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/node/LineNode.java b/org.simantics.h2d/src/org/simantics/h2d/node/LineNode.java
new file mode 100644 (file)
index 0000000..3f608f5
--- /dev/null
@@ -0,0 +1,49 @@
+package org.simantics.h2d.node;\r
+\r
+import java.awt.BasicStroke;\r
+import java.awt.Color;\r
+import java.awt.Graphics2D;\r
+import java.awt.geom.Path2D;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+\r
+import org.simantics.scenegraph.g2d.G2DNode;\r
+\r
+public class LineNode extends G2DNode {\r
+       \r
+       private static final long serialVersionUID = 654692698101485672L;\r
+\r
+       Point2D begin;\r
+       Point2D end;\r
+    protected Path2D path;\r
+\r
+    @SyncField({"begin","end"})\r
+    public void init(Point2D begin, Point2D end) {\r
+        this.begin = begin;\r
+        this.end = end;\r
+        update();\r
+    }\r
+    \r
+    protected void update() {\r
+       path = new Path2D.Double();\r
+       path.moveTo(begin.getX(), begin.getY());\r
+       path.lineTo(end.getX(), end.getY());\r
+    }\r
+\r
+    @Override\r
+    public void render(Graphics2D g) {\r
+        if(path == null) return;\r
+        g.setColor(Color.BLACK);\r
+        double scale = g.getTransform().getScaleX();\r
+        g.setStroke(new BasicStroke( (float)(1.0 / scale) ));\r
+        g.draw(path);\r
+    }\r
+\r
+       @Override\r
+       public Rectangle2D getBounds() {\r
+               Rectangle2D bounds = new Rectangle2D.Double();\r
+               bounds.setFrameFromDiagonal(begin, end);\r
+               return bounds;\r
+       }\r
+    \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/node/RectangleNode.java b/org.simantics.h2d/src/org/simantics/h2d/node/RectangleNode.java
new file mode 100644 (file)
index 0000000..5f91df7
--- /dev/null
@@ -0,0 +1,36 @@
+package org.simantics.h2d.node;\r
+\r
+import java.awt.BasicStroke;\r
+import java.awt.Color;\r
+import java.awt.Graphics2D;\r
+import java.awt.geom.Rectangle2D;\r
+\r
+import org.simantics.scenegraph.g2d.G2DNode;\r
+\r
+public class RectangleNode extends G2DNode {\r
+       \r
+       private static final long serialVersionUID = 654692698101485672L;\r
+\r
+    protected Rectangle2D bounds = null;\r
+\r
+    @SyncField("bounds")\r
+    public void init(Rectangle2D bounds) {\r
+        this.bounds = bounds;\r
+    }\r
+\r
+    @Override\r
+    public void render(Graphics2D g) {\r
+        if(bounds == null) return;\r
+        g.setColor(Color.BLACK);\r
+        double scale = g.getTransform().getScaleX();\r
+        g.setStroke(new BasicStroke( (float)(1.0 / scale) ));\r
+\r
+        g.draw(bounds);\r
+    }\r
+\r
+       @Override\r
+       public Rectangle2D getBounds() {\r
+               return bounds;\r
+       }\r
+    \r
+}\r
diff --git a/org.simantics.h2d/src/org/simantics/h2d/node/ShapeNode.java b/org.simantics.h2d/src/org/simantics/h2d/node/ShapeNode.java
new file mode 100644 (file)
index 0000000..ba8f13b
--- /dev/null
@@ -0,0 +1,92 @@
+package org.simantics.h2d.node;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+
+import org.simantics.scenegraph.g2d.G2DNode;
+import org.simantics.scenegraph.utils.GeometryUtils;
+
+public class ShapeNode extends G2DNode {
+
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 8508750881358776559L;
+
+    protected Shape shape = null;
+    protected Stroke stroke = new BasicStroke(1);
+    protected Color color = Color.BLACK;
+    protected boolean fill = false;
+    protected boolean scaleStroke = false;
+    protected boolean scaleShape = false;
+
+    @SyncField("shape")
+    public void setShape(Shape shape) {
+        this.shape = shape;
+        repaint();
+    }
+
+    @SyncField("stroke")
+    public void setStroke(Stroke stroke) {
+        this.stroke = stroke;
+    }
+
+    @SyncField("color")
+    public void setColor(Color color) {
+        this.color = color;
+    }
+
+    @SyncField("fill")
+    public void setFill(boolean fill) {
+        this.fill = fill;
+    }
+
+    @SyncField("scaleStroke")
+    public void setScaleStroke(boolean scaleStroke) {
+        this.scaleStroke = scaleStroke;
+    }
+
+    @SyncField("scaleShape")
+    public void setScaleShape(boolean scaleShape) {
+        this.scaleShape = scaleShape;
+    }
+
+    @Override
+    public void render(Graphics2D g2d) {
+        if(shape == null) return;
+
+        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // FIXME
+        if(color != null) g2d.setColor(color);
+        if(stroke != null) {
+            if(scaleStroke && stroke instanceof BasicStroke) {
+                BasicStroke bs = GeometryUtils.scaleStroke(stroke, (float) (1.0 / GeometryUtils.getScale(g2d.getTransform())));
+                g2d.setStroke(bs);
+            } else {
+                g2d.setStroke(stroke);
+            }
+        }
+        if(scaleShape) {
+            double xs = g2d.getTransform().getScaleX();
+            double ys = g2d.getTransform().getScaleY();
+            g2d.scale(1/xs, 1/ys);
+        }
+
+        if(fill) {
+            g2d.fill(shape);
+        } else {
+            g2d.draw(shape);
+        }
+
+    }
+
+    @Override
+    public Rectangle2D getBounds() {
+        return shape.getBounds2D();
+    }
+}
diff --git a/org.simantics.h2d/src/org/simantics/h2d/node/TextNode.java b/org.simantics.h2d/src/org/simantics/h2d/node/TextNode.java
new file mode 100644 (file)
index 0000000..8c3ed90
--- /dev/null
@@ -0,0 +1,259 @@
+package org.simantics.h2d.node;\r
+\r
+import java.awt.AWTEvent;\r
+import java.awt.Color;\r
+import java.awt.Font;\r
+import java.awt.Graphics2D;\r
+import java.awt.Toolkit;\r
+import java.awt.datatransfer.Clipboard;\r
+import java.awt.datatransfer.DataFlavor;\r
+import java.awt.datatransfer.StringSelection;\r
+import java.awt.datatransfer.Transferable;\r
+import java.awt.event.KeyEvent;\r
+import java.awt.event.MouseEvent;\r
+import java.awt.font.FontRenderContext;\r
+import java.awt.geom.AffineTransform;\r
+import java.awt.geom.Line2D;\r
+import java.awt.geom.Rectangle2D;\r
+\r
+import org.simantics.scenegraph.g2d.G2DNode;\r
+\r
+public class TextNode extends G2DNode {\r
+       \r
+       //static final FontRenderContext FRC = new FontRenderContext(\r
+       //              new AffineTransform(1.0,0.0,0.0,1.0,0.0,0.0), true, true);\r
+       \r
+       private static final long serialVersionUID = 654692698101485672L;\r
+\r
+    protected String text = null;\r
+    protected Font font = null;\r
+    protected Color color = null;\r
+    protected double x;\r
+    protected double y;\r
+    protected double scale;\r
+        \r
+    boolean editAllowed;\r
+    int caret = 0;\r
+    int selectionTail = 0;\r
+    \r
+    ITextListener textListener;\r
+    \r
+    /**\r
+     * Enables or disables edit mode. It also sets\r
+     * the caret at the end of text all selects the\r
+     * whole text (this is the usual convention when \r
+     * beginning to edit one line texts).\r
+     * @param editAllowed\r
+     */\r
+       public void setEditMode(boolean editAllowed) {\r
+               this.editAllowed = editAllowed;\r
+               caret = text.length();\r
+               selectionTail = 0;\r
+       }\r
+\r
+    @SyncField({"text", "font", "color", "x", "y", "scale"})\r
+    public void init(String text, Font font, Color color, double x, double y, double scale) {\r
+        this.text = text;\r
+        this.font = font;\r
+        this.color = color;\r
+        this.x = x;\r
+        this.y = y;\r
+        this.scale = scale;\r
+    }\r
+    \r
+    public String getText() {\r
+       return text;\r
+    }\r
+    \r
+    private double getLength(FontRenderContext frc, String str) {\r
+       Rectangle2D bounds = font.getStringBounds(str, frc);\r
+       return bounds.getWidth();\r
+    }\r
+\r
+    @Override\r
+    public void render(Graphics2D g) {\r
+       if(text == null || font == null || color == null) return;\r
+       g.setFont(font);\r
+       g.translate(x, y);\r
+        g.scale(scale, scale);\r
+        \r
+       if(editAllowed) {\r
+               FontRenderContext frc = g.getFontRenderContext();\r
+               \r
+               int selectionMin = Math.min(caret, selectionTail);\r
+               int selectionMax = Math.max(caret, selectionTail);   \r
+               double selectionMinPos = getLength(frc, text.substring(0, selectionMin));\r
+               double selectionMaxPos = getLength(frc, text.substring(0, selectionMax));\r
+\r
+               // Selection background\r
+               g.setColor(new Color(0x316ac5));\r
+               g.fill(new Rectangle2D.Double(selectionMinPos, -12.0, \r
+                               selectionMaxPos-selectionMinPos, 12.0));\r
+               \r
+               // Text\r
+               g.setColor(color);    \r
+               g.drawString(text.substring(0, selectionMin), 0f, 0f);\r
+               g.drawString(text.substring(selectionMax), (float)selectionMaxPos, 0f);\r
+               \r
+               g.setColor(Color.WHITE);\r
+               g.drawString(text.substring(selectionMin, selectionMax), (float)selectionMinPos, 0f);\r
+               \r
+               // Caret\r
+               double caretPos = getLength(frc, text.substring(0, caret));\r
+               //g.setXORMode(Color.BLACK);\r
+               g.setColor(Color.BLACK);\r
+               g.draw(new Line2D.Double(caretPos, 0, caretPos, -12.0));                \r
+       }\r
+       else {\r
+                g.setColor(color);    \r
+                g.drawString(text, 0f, 0f);\r
+       }       \r
+    }\r
+\r
+    /**\r
+     * Replaces the current selection with the content or inserts\r
+     * the content at caret. After the insertion the caret\r
+     * will be at the end of inserted text and selection will\r
+     * be empty.\r
+     * @param content\r
+     */\r
+    @SyncField("text")\r
+    protected void insert(String content) {\r
+       int selectionMin = Math.min(caret, selectionTail);\r
+               int selectionMax = Math.max(caret, selectionTail);    \r
+               \r
+               String begin = text.substring(0, selectionMin);\r
+               String end = text.substring(selectionMax);\r
+               text = begin + content + end;\r
+               caret = selectionMin + content.length();\r
+               selectionTail = caret;\r
+    }\r
+    \r
+    \r
+    @ServerSide\r
+    protected void fireTextChanged() {\r
+       if(textListener != null)\r
+               textListener.textChanged();\r
+    }\r
+    \r
+    public void setTextListener(ITextListener listener) {\r
+       this.textListener = listener;\r
+    }\r
+    \r
+    private void handleKeyPressed(KeyEvent event) {\r
+       char c = event.getKeyChar();\r
+               //System.out.println("Key pressed " + c + " " + event.getKeyCode());\r
+               if(event.isControlDown())\r
+                       switch(event.getKeyCode()) {\r
+                       case KeyEvent.VK_C:\r
+                               if(caret != selectionTail) {\r
+                                       int selectionMin = Math.min(caret, selectionTail);\r
+                                       int selectionMax = Math.max(caret, selectionTail);\r
+                                       setCliboardContent(text.substring(selectionMin, selectionMax));\r
+                               }\r
+                               break;\r
+\r
+                       case KeyEvent.VK_V:                     \r
+                       {\r
+                               String content = getCliboardContent();\r
+                               if(content != null)\r
+                                       insert(content);        \r
+                       }\r
+                       break;\r
+                       default:\r
+                               return;\r
+                       }                       \r
+               else if(event.isAltDown())\r
+                       return;\r
+               else\r
+                       switch(event.getKeyCode()) {\r
+                       case KeyEvent.VK_LEFT:\r
+                               if(caret > 0) {\r
+                                       --caret;\r
+                                       if(!event.isShiftDown())\r
+                                               selectionTail = caret;\r
+                               }\r
+                               break;\r
+                       case KeyEvent.VK_RIGHT:\r
+                               if(caret < text.length()) {\r
+                                       ++caret;\r
+                                       if(!event.isShiftDown())\r
+                                               selectionTail = caret;\r
+                               }\r
+                               break;\r
+                       case KeyEvent.VK_HOME:\r
+                               caret = 0;\r
+                               if(!event.isShiftDown())\r
+                                       selectionTail = caret;\r
+                               break;\r
+                       case KeyEvent.VK_END:\r
+                               caret = text.length();\r
+                               if(!event.isShiftDown())\r
+                                       selectionTail = caret;\r
+                               break;\r
+\r
+                       case KeyEvent.VK_BACK_SPACE:\r
+                               if(caret == selectionTail && caret > 0)\r
+                                       --caret;\r
+                               insert("");     \r
+                               break;\r
+\r
+                       case KeyEvent.VK_DELETE:\r
+                               if(caret == selectionTail && caret < text.length())\r
+                                       ++caret;\r
+                               insert("");     \r
+                               break;                                  \r
+\r
+                       default:\r
+                               if(c == 65535 || Character.getType(c) == Character.CONTROL)\r
+                                       return;\r
+                               //System.out.println("Char " + (int)c + " " + Character.getType(c));\r
+                               insert(new String(new char[] {c}));                                                                                                     \r
+                       }\r
+               // FIXME This is called even if just caret was moved.\r
+               // This is currently necessary for repaints.\r
+               fireTextChanged();\r
+               event.consume();\r
+    }\r
+    \r
+       private void handleMousePressed(MouseEvent event) {\r
+               // TODO         \r
+       }\r
+    \r
+    @Override\r
+    public void handleEvent(AWTEvent event) {\r
+       if(editAllowed) {\r
+               if(caret > text.length())\r
+                       caret = text.length();\r
+               switch(event.getID()) {                 \r
+               case KeyEvent.KEY_PRESSED: \r
+                       handleKeyPressed((KeyEvent)event);\r
+                       break;\r
+               case MouseEvent.MOUSE_PRESSED: \r
+                       handleMousePressed((MouseEvent)event);\r
+                       break;\r
+               }\r
+       }\r
+    }\r
+\r
+       @Override\r
+       public Rectangle2D getBounds() {\r
+               return null;\r
+       }\r
+\r
+       public String getCliboardContent() {\r
+               Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();\r
+               Transferable clipData = clipboard.getContents(this);\r
+               try {\r
+                       return (String) (clipData.getTransferData(DataFlavor.stringFlavor));\r
+               } catch (Exception ee) {\r
+                       return null;\r
+               }\r
+       }\r
+       \r
+       public void setCliboardContent(String content) {\r
+               Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();\r
+               StringSelection data = new StringSelection(content);\r
+           clipboard.setContents(data, data);\r
+       }\r
+}\r