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