--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.scenegraph.swing;
+
+import java.awt.AWTEvent;\r
+import java.awt.Color;\r
+import java.awt.Component;\r
+import java.awt.Container;\r
+import java.awt.Cursor;\r
+import java.awt.Font;\r
+import java.awt.Graphics;\r
+import java.awt.Graphics2D;\r
+import java.awt.event.FocusEvent;\r
+import java.awt.event.FocusListener;\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.geom.AffineTransform;\r
+import java.awt.geom.Point2D;\r
+import java.awt.geom.Rectangle2D;\r
+\r
+import javax.swing.JComponent;\r
+import javax.swing.UIDefaults;\r
+\r
+import org.simantics.scenegraph.g2d.G2DFocusManager;\r
+import org.simantics.scenegraph.g2d.G2DNode;\r
+import org.simantics.scenegraph.g2d.G2DParentNode;\r
+import org.simantics.scenegraph.g2d.IG2DNode;\r
+import org.simantics.scenegraph.g2d.events.ISGMouseEvent;\r
+import org.simantics.scenegraph.g2d.events.SGMouseEvent;\r
+import org.simantics.scenegraph.g2d.events.SGMouseWheelEvent;\r
+import org.simantics.scenegraph.utils.DummyComponent;\r
+import org.simantics.scenegraph.utils.NodeUtil;\r
+
+public class ComponentNode<T extends JComponent> extends G2DNode implements MouseListener, MouseMotionListener, KeyListener, FocusListener {
+
+ private static final long serialVersionUID = 3161843367263793336L;
+ protected ComponentContainer container = new ComponentContainer();\r
+
+ protected Rectangle2D bounds = null;\r
+ protected boolean focusable = true;\r
+
+ public class ComponentContainer extends Container {
+ /**
+ *
+ */
+ private static final long serialVersionUID = 7233440430091475715L;
+
+ public ComponentContainer() {
+ super();
+ }
+ protected boolean contains = false;
+\r
+// @Override\r
+// public void setSize(int width, int height) {\r
+// System.err.println("ss " + width + " " + height);\r
+// super.setSize(width, height);\r
+// }\r
+
+ public void setContains(boolean contains) {
+ this.contains = contains;\r
+ }\r
+// @Override\r
+// public void setBounds(int x, int y, int width, int height) {\r
+// super.setBounds(x, y, width, height);\r
+// }
+ @Override
+ public void update(Graphics g) {\r
+ component.update(g);
+ }
+ @Override
+ public void paint(Graphics g) {
+ }
+ @Override
+ public void paintAll(Graphics g) {
+ }
+ @Override
+ public void paintComponents(Graphics g) {\r
+ }
+ @Override\r
+ public void repaint() {\r
+ }
+ @Override\r
+ public void repaint(long tm) {\r
+ }\r
+ @Override\r
+ public void repaint(int x, int y, int width, int height) {\r
+ }\r
+ @Override\r
+ public void repaint(long tm, int x, int y, int width, int height) {\r
+ }\r
+\r
+ \r
+ @Override
+ public boolean contains(int eventX, int eventY) {\r
+ \r
+// G2DSceneGraph sg = (G2DSceneGraph)ComponentNode.this.getRootNode();\r
+ //AWTChassis chassis = (AWTChassis)(sg.getRootPane().getParent());\r
+// AffineTransform ct = chassis.getCanvasContext().getDefaultHintContext().getHint(Hints.KEY_CANVAS_TRANSFORM);\r
+// if(ct == null) return false;\r
+// Point2D canvasPosition = new Point2D.Double(); \r
+// try {\r
+// ct.inverseTransform(new Point2D.Double(x,y), canvasPosition);\r
+// } catch (NoninvertibleTransformException e) {\r
+// e.printStackTrace();\r
+// }\r
+//// System.err.println("pane tr=" + canvasPosition + " " + ct + " (" + x + "," + y + ")");\r
+// return super.contains((int)canvasPosition.getX() , (int)canvasPosition.getY());\r
+ \r
+ \r
+// System.err.println("pane tr=" + canvasPosition);\r
+// //ElementUtils.controlToElementCoordinate(element, element.get, controlPoint, elementPoint);\r
+// boolean contains = super.contains(x, y);\r
+// if(contains) {\r
+// System.err.println("pane contains!" + x + " " + y);\r
+// } else {\r
+// System.err.println("pane does not contain!" + x + " " + y);\r
+// }\r
+// return contains;\r
+ \r
+ \r
+// int trX = 0;//getAbsoluteX(this, 0);\r
+// int trY = 0;//getAbsoluteY(this, 0);\r
+// \r
+//// System.err.println("trX=" + trX + " trY = " + trY);\r
+// \r
+//// AWTChassis chassis = (AWTChassis)base.getParent().getParent();\r
+// AffineTransform ct = chassis.getCanvasContext().getDefaultHintContext().getHint(Hints.KEY_CANVAS_TRANSFORM);\r
+// if(ct == null) return false;\r
+//\r
+// int origX = eventX;\r
+// int origY = eventY;\r
+// \r
+// eventX += trX;\r
+// eventX -= ct.getTranslateX();\r
+// eventX /= ct.getScaleX();\r
+// eventX -= trX;\r
+// eventY += trY;\r
+// eventY -= ct.getTranslateY();\r
+// eventY /= ct.getScaleY();\r
+// eventY -= trY;\r
+//\r
+// System.err.println("ComponentNode contains? (" + origX + "," + origY + ")=>(" + eventX + "," + eventY + ")");\r
+\r
+// return super.contains(eventX, eventY);\r
+ \r
+ return true;
+//// return contains;
+ }
+
+ @Override
+ public Cursor getCursor() {
+ if(contains == false || component == null || component.isCursorSet() == false) {\r
+ return Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);\r
+ }\r
+ return component.getCursor();
+ }
+ };
+
+ protected T component = null;
+ protected boolean visible = true;
+ protected boolean scale = false; // If true, component is scaled. If false, component is resized\r
+\r
+ private transient Font componentFont;\r
+ private transient double lastScale = 0;\r
+ \r
+ @PropertySetter("Bounds")\r
+ @SyncField("bounds")\r
+ public void setBounds(Rectangle2D bounds) {\r
+ assert(bounds != null);\r
+ this.bounds = bounds;\r
+ setScaleDirty();\r
+ }\r
+ \r
+ @PropertySetter("Background Color")\r
+ @ClientSide\r
+ public void setBackgroundColor(Color color) {\r
+ if(component != null) {\r
+ component.setBackground(color);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * If the size, font or any other scalable visual property of the component\r
+ * changes, this method should be invoked to make sure that\r
+ * {@link #render(Graphics2D)} will update the visual properties for the\r
+ * component with proper scaling.\r
+ */\r
+ protected void setScaleDirty() {\r
+ // Triggers resetting of component font and size if necessary before\r
+ // rendering.\r
+ lastScale = 0;\r
+ //System.out.println("scale dirty");\r
+ }\r
+\r
+ protected Font getComponentFont() {\r
+ if (component != null) {\r
+ if (componentFont == null) {\r
+ componentFont = component.getFont();\r
+ }\r
+ return componentFont;\r
+ }\r
+ return null;\r
+ }\r
+\r
+ protected void setComponentFont(Font font) {\r
+ this.componentFont = font;\r
+ setScaleDirty();\r
+ component.setFont(font);\r
+ }\r
+\r
+ @Override
+ public void render(Graphics2D g2d) {
+ if(!visible || bounds == null) return;
+
+ Graphics2D g = (Graphics2D)g2d.create();
+ if (component != null) {\r
+ // see http://java.sun.com/docs/books/tutorial/uiswing/lookandfeel/size.html\r
+ component.putClientProperty("JComponent.sizeVariant", "large");
+
+ double sx = 1;\r
+ double sy = 1;\r
+ if (transform != null) {\r
+ g.transform(transform);\r
+// sx = g.getTransform().getScaleX();\r
+// sy = g.getTransform().getScaleY();\r
+// if(!scale) {\r
+// g.scale(1/sx, 1/sy);\r
+// }\r
+ }\r
+\r
+ // Assumes proportional scaling always.\r
+ if (!scale && lastScale != sx) {\r
+ //System.out.println("NEW SCALE: " + sx + " vs. " + lastScale);\r
+ lastScale = sx;\r
+\r
+ // Handle font scaling\r
+ if (componentFont == null)\r
+ componentFont = component.getFont();\r
+ if (componentFont != null) {\r
+ Font newFont = component.getFont().deriveFont(componentFont.getSize2D()*(float)sx);\r
+ component.setFont(newFont);\r
+ }\r
+\r
+ // Handle component size scaling.\r
+ component.setSize((int)(bounds.getWidth()*sx), (int)(bounds.getHeight()*sy));\r
+ }\r
+ component.setSize((int)(bounds.getWidth()*sx), (int)(bounds.getHeight()*sy));\r
+ g.translate(bounds.getMinX()*sx, bounds.getMinY()*sy);\r
+ component.paint(g);\r
+ }
+ g.dispose();
+ }
+
+ @SyncField({"visible"})
+ public void setVisible(boolean value) {
+ this.visible = value;
+ }
+\r
+ public Component getHeavyweightParent(Component component) {\r
+ if(component.isLightweight()) return getHeavyweightParent(component.getParent());\r
+ else return component;\r
+ }\r
+ \r
+ // FIXME
+ public void handleEvent(AWTEvent event) {\r
+ if(focusable == false) return; // No event handling if not focusable\r
+ if(bounds == null) return; // AAARGH..\r
+
+ AWTEvent cevent = event;\r
+ double sx = 1.0;\r
+ double sy = 1.0;\r
+ if(event instanceof MouseEvent) {\r
+ if(!scale) {\r
+ IG2DNode node = (IG2DNode) this.getParent();\r
+ while(node != null) {\r
+ sx *= node.getTransform().getScaleX();\r
+ sy *= node.getTransform().getScaleY();\r
+ node = (G2DParentNode)node.getParent(); // FIXME: it should be G2DParentNode but you can never be sure\r
+ }\r
+ }\r
+\r
+ MouseEvent me = (MouseEvent)event;\r
+ // Use double coordinates if available \r
+ double mx = me.getX();\r
+ double my = me.getY();\r
+ if(event instanceof ISGMouseEvent) {\r
+ mx = ((ISGMouseEvent)event).getDoubleX();\r
+ my = ((ISGMouseEvent)event).getDoubleY();\r
+ }\r
+ AffineTransform i = AffineTransform.getTranslateInstance(-transform.getTranslateX()*sx-getBoundsInLocal().getMinX()*sx, -transform.getTranslateY()*sy-getBoundsInLocal().getMinY()*sy);\r
+ Point2D p = i.transform(new Point2D.Double(sx*mx, sy*my), null);\r
+ cevent = relocateEvent(me, p);\r
+ Rectangle2D tb = new Rectangle2D.Double(transform.getTranslateX()+getBoundsInLocal().getMinX(), transform.getTranslateY()+getBoundsInLocal().getMinY(), getBoundsInLocal().getWidth(), getBoundsInLocal().getHeight());\r
+ \r
+ if(cevent.getID() == MouseEvent.MOUSE_PRESSED || cevent.getID() == MouseWheelEvent.MOUSE_WHEEL) {\r
+ if(tb.contains(me.getPoint())) {\r
+ G2DFocusManager.INSTANCE.markFocus(component);\r
+ if(component != null && component.hasFocus() == false) {\r
+ component.requestFocusInWindow();\r
+ }\r
+ }\r
+ }\r
+\r
+ if(tb.contains(me.getPoint())) {\r
+ container.setContains(true);\r
+ } else {\r
+ container.setContains(false);\r
+ }\r
+\r
+ if(cevent.getID() == MouseEvent.MOUSE_DRAGGED && component != null && component.hasFocus()) {\r
+ cevent.setSource(component);\r
+ }\r
+ }
+ if (component != null && container.contains) { //cevent.getSource().equals(component)) {\r
+// new Lightwe\r
+ cevent.setSource(component);\r
+ \r
+// container.dispatchEvent(cevent);\r
+ \r
+// Component hw = getHeavyweightParent(component);\r
+// hw.dispatchEvent(cevent);\r
+ \r
+// container.getParent().dispatchEvent(cevent);\r
+// System.err.println("cevent=" + cevent);\r
+// // FIXME: terrible kludge to dispatch event correctly to every child\r
+// for(Component c : component.getComponents()) {\r
+// AWTEvent xe = translateEvent(cevent, new Point2D.Double(-c.getLocation().getX(), -c.getLocation().getY()));\r
+// xe.setSource(c);\r
+// c.dispatchEvent(xe);\r
+// if(c instanceof JComponent) {\r
+// for(Component c2 : ((JComponent)c).getComponents()) {\r
+// AWTEvent qe = translateEvent(xe, new Point2D.Double(-c.getLocation().getX(), -c.getLocation().getY()));\r
+// qe.setSource(c);\r
+// qe.setSource(c2);\r
+// c2.dispatchEvent(qe);\r
+// System.err.println("qe=" + qe);\r
+// System.err.println("xe=" + xe);\r
+// System.err.println("cevent=" + cevent);\r
+// }\r
+// }\r
+// }
+ }
+ }
+\r
+ protected AWTEvent relocateEvent(AWTEvent e, Point2D p) {\r
+ if(!(e instanceof MouseEvent)) return e; // Only for mouse events\r
+ MouseEvent me = (MouseEvent)e;\r
+ MouseEvent cevent = null;\r
+ if(me.getID() == MouseWheelEvent.MOUSE_WHEEL) {\r
+ cevent = new SGMouseWheelEvent(new DummyComponent(), me.getID(), me.getWhen(), me.getModifiers(), p.getX(), p.getY(), me.getClickCount(), me.isPopupTrigger(), ((MouseWheelEvent)me).getScrollType(), ((MouseWheelEvent)me).getScrollAmount(), ((MouseWheelEvent)me).getWheelRotation(), me);\r
+ } else {\r
+ cevent = new SGMouseEvent(new DummyComponent(), me.getID(), me.getWhen(), me.getModifiers(), p.getX(), p.getY(), me.getClickCount(), me.isPopupTrigger(), me.getButton(), me);\r
+ }\r
+ return cevent;\r
+ }\r
+ \r
+ protected AWTEvent translateEvent(AWTEvent e, Point2D p) {\r
+ if(!(e instanceof MouseEvent)) return e; // Only for mouse events\r
+ MouseEvent me = (MouseEvent)e;\r
+ MouseEvent cevent = null;\r
+ if(me.getID() == MouseWheelEvent.MOUSE_WHEEL) {\r
+ cevent = new SGMouseWheelEvent(new DummyComponent(), me.getID(), me.getWhen(), me.getModifiers(), me.getX()+p.getX(), me.getY()+p.getY(), me.getClickCount(), me.isPopupTrigger(), ((MouseWheelEvent)me).getScrollType(), ((MouseWheelEvent)me).getScrollAmount(), ((MouseWheelEvent)me).getWheelRotation(), me);\r
+ } else {\r
+ cevent = new SGMouseEvent(new DummyComponent(), me.getID(), me.getWhen(), me.getModifiers(), me.getX()+p.getX(), me.getY()+p.getY(), me.getClickCount(), me.isPopupTrigger(), me.getButton(), me);\r
+ }\r
+ return cevent;\r
+ }\r
+ public void setFocusable(boolean focusable) {\r
+ this.focusable = focusable;\r
+ container.setContains(false); // Always false when focusable property is changed\r
+ component.setFocusable(focusable);\r
+ }\r
+ \r
+ @Override\r
+ public void init() {\r
+ if(component == null) return; // FIXME: assert maybe?\r
+\r
+ Container rootPane = NodeUtil.findRootPane(this);\r
+ if(rootPane != null) {\r
+ rootPane.add(container);\r
+ } else {\r
+ throw new AssertionError("The canvas has no rootPane!");\r
+ }\r
+ container.add(component);\r
+ component.setFocusable(true);\r
+ component.setIgnoreRepaint(true);\r
+\r
+ UIDefaults cDefaults = new UIDefaults();\r
+\r
+ component.putClientProperty("Nimbus.Overrides",cDefaults);\r
+ component.putClientProperty("Nimbus.Overrides.InheritDefaults",false);\r
+\r
+// component.addMouseListener(new MouseListener() {\r
+//\r
+// @Override\r
+// public void mouseClicked(MouseEvent e) {\r
+// System.err.println("aff");\r
+// }\r
+//\r
+// @Override\r
+// public void mousePressed(MouseEvent e) {\r
+// System.err.println("aff2");\r
+// }\r
+//\r
+// @Override\r
+// public void mouseReleased(MouseEvent e) {\r
+// System.err.println("aff3");\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
+// });\r
+ \r
+ NodeUtil.getEventDelegator(this).addMouseListener(this);\r
+ NodeUtil.getEventDelegator(this).addMouseMotionListener(this);\r
+ NodeUtil.getEventDelegator(this).addKeyListener(this);\r
+ NodeUtil.getEventDelegator(this).addFocusListener(this);\r
+ \r
+ }
+
+ @Override
+ public Rectangle2D getBoundsInLocal() {\r
+ return bounds;\r
+ }\r
+
+ @Override
+ public void finalize() throws Throwable {
+ cleanup();\r
+ super.finalize();
+ }
+
+ @Override
+ public void cleanup() {\r
+ retractMapping();\r
+\r
+ Container rootPane = NodeUtil.findRootPane(this);\r
+
+ if(container != null) {\r
+ rootPane.remove(container);\r
+ container.setContains(false);
+ if(container.getParent() != null) container.getParent().remove(container); // eh...\r
+ if (component != null)
+ container.remove(component);\r
+ }
+ container = null;
+ component = null;\r
+ \r
+ NodeUtil.getEventDelegator(this).removeMouseListener(this);\r
+ NodeUtil.getEventDelegator(this).removeMouseMotionListener(this);\r
+ NodeUtil.getEventDelegator(this).removeKeyListener(this);\r
+ NodeUtil.getEventDelegator(this).removeFocusListener(this);\r
+\r
+ super.cleanup();
+ }\r
+\r
+ @Override\r
+ public void keyTyped(KeyEvent e) {\r
+ handleEvent(e);\r
+ }\r
+\r
+ @Override\r
+ public void keyPressed(KeyEvent e) {\r
+ handleEvent(e);\r
+ }\r
+\r
+ @Override\r
+ public void keyReleased(KeyEvent e) {\r
+ handleEvent(e);\r
+ }\r
+\r
+ @Override\r
+ public void mouseDragged(MouseEvent e) {\r
+ handleEvent(e);\r
+ }\r
+\r
+ @Override\r
+ public void mouseMoved(MouseEvent e) {\r
+ handleEvent(e);\r
+ }\r
+\r
+ @Override\r
+ public void mouseClicked(MouseEvent e) {\r
+ handleEvent(e);\r
+ }\r
+\r
+ @Override\r
+ public void mousePressed(MouseEvent e) {\r
+ handleEvent(e);\r
+ }\r
+\r
+ @Override\r
+ public void mouseReleased(MouseEvent e) {\r
+ handleEvent(e);\r
+ }\r
+\r
+ @Override\r
+ public void mouseEntered(MouseEvent e) {\r
+ handleEvent(e);\r
+ }\r
+\r
+ @Override\r
+ public void mouseExited(MouseEvent e) {\r
+ handleEvent(e);\r
+ }\r
+\r
+ @Override\r
+ public void focusGained(FocusEvent e) {\r
+ handleEvent(e);\r
+ }\r
+\r
+ @Override\r
+ public void focusLost(FocusEvent e) {\r
+ handleEvent(e);\r
+ }
+}