--- /dev/null
+<?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
--- /dev/null
+syntax: regexp\r
+^bin/\r
+\r
+syntax: glob\r
+*.svn/*
\ No newline at end of file
--- /dev/null
+<?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
--- /dev/null
+#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
--- /dev/null
+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
--- /dev/null
+source.. = src/\r
+output.. = bin/\r
+bin.includes = META-INF/,\\r
+ .\r
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+package org.simantics.h2d.editor;\r
+\r
+public interface ISelectionListener {\r
+\r
+ void selectionChanged(ISelection selection);\r
+ \r
+}\r
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+package org.simantics.h2d.event;\r
+\r
+public enum DragEventPhase {\r
+ dragBegin, dragUpdate, dragEnd \r
+}\r
--- /dev/null
+package org.simantics.h2d.event;\r
+\r
+public interface IDragHandler {\r
+ public void handleMove();\r
+ public void handleRelease();\r
+}\r
--- /dev/null
+package org.simantics.h2d.event;\r
+\r
+public interface IEvent {\r
+ String getType();\r
+}\r
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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();
+ }
+
+}
--- /dev/null
+package org.simantics.h2d.node;\r
+\r
+public interface ITextListener {\r
+\r
+ void textChanged();\r
+ \r
+}\r
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+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();
+ }
+}
--- /dev/null
+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