]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.scenegraph.swing/src/org/simantics/scenegraph/swing/ComponentNode.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.scenegraph.swing / src / org / simantics / scenegraph / swing / ComponentNode.java
diff --git a/bundles/org.simantics.scenegraph.swing/src/org/simantics/scenegraph/swing/ComponentNode.java b/bundles/org.simantics.scenegraph.swing/src/org/simantics/scenegraph/swing/ComponentNode.java
new file mode 100644 (file)
index 0000000..c9cb8bc
--- /dev/null
@@ -0,0 +1,538 @@
+/*******************************************************************************\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
+       }
+}