1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.scenegraph.swing;
14 import java.awt.AWTEvent;
\r
15 import java.awt.Color;
\r
16 import java.awt.Component;
\r
17 import java.awt.Container;
\r
18 import java.awt.Cursor;
\r
19 import java.awt.Font;
\r
20 import java.awt.Graphics;
\r
21 import java.awt.Graphics2D;
\r
22 import java.awt.event.FocusEvent;
\r
23 import java.awt.event.FocusListener;
\r
24 import java.awt.event.KeyEvent;
\r
25 import java.awt.event.KeyListener;
\r
26 import java.awt.event.MouseEvent;
\r
27 import java.awt.event.MouseListener;
\r
28 import java.awt.event.MouseMotionListener;
\r
29 import java.awt.event.MouseWheelEvent;
\r
30 import java.awt.geom.AffineTransform;
\r
31 import java.awt.geom.Point2D;
\r
32 import java.awt.geom.Rectangle2D;
\r
34 import javax.swing.JComponent;
\r
35 import javax.swing.UIDefaults;
\r
37 import org.simantics.scenegraph.g2d.G2DFocusManager;
\r
38 import org.simantics.scenegraph.g2d.G2DNode;
\r
39 import org.simantics.scenegraph.g2d.G2DParentNode;
\r
40 import org.simantics.scenegraph.g2d.IG2DNode;
\r
41 import org.simantics.scenegraph.g2d.events.ISGMouseEvent;
\r
42 import org.simantics.scenegraph.g2d.events.SGMouseEvent;
\r
43 import org.simantics.scenegraph.g2d.events.SGMouseWheelEvent;
\r
44 import org.simantics.scenegraph.utils.DummyComponent;
\r
45 import org.simantics.scenegraph.utils.NodeUtil;
\r
47 public class ComponentNode<T extends JComponent> extends G2DNode implements MouseListener, MouseMotionListener, KeyListener, FocusListener {
49 private static final long serialVersionUID = 3161843367263793336L;
50 protected ComponentContainer container = new ComponentContainer();
\r
52 protected Rectangle2D bounds = null;
\r
53 protected boolean focusable = true;
\r
55 public class ComponentContainer extends Container {
59 private static final long serialVersionUID = 7233440430091475715L;
61 public ComponentContainer() {
64 protected boolean contains = false;
67 // public void setSize(int width, int height) {
\r
68 // System.err.println("ss " + width + " " + height);
\r
69 // super.setSize(width, height);
\r
72 public void setContains(boolean contains) {
73 this.contains = contains;
\r
76 // public void setBounds(int x, int y, int width, int height) {
\r
77 // super.setBounds(x, y, width, height);
\r
80 public void update(Graphics g) {
\r
84 public void paint(Graphics g) {
87 public void paintAll(Graphics g) {
90 public void paintComponents(Graphics g) {
\r
93 public void repaint() {
\r
96 public void repaint(long tm) {
\r
99 public void repaint(int x, int y, int width, int height) {
\r
102 public void repaint(long tm, int x, int y, int width, int height) {
\r
107 public boolean contains(int eventX, int eventY) {
\r
109 // G2DSceneGraph sg = (G2DSceneGraph)ComponentNode.this.getRootNode();
\r
110 //AWTChassis chassis = (AWTChassis)(sg.getRootPane().getParent());
\r
111 // AffineTransform ct = chassis.getCanvasContext().getDefaultHintContext().getHint(Hints.KEY_CANVAS_TRANSFORM);
\r
112 // if(ct == null) return false;
\r
113 // Point2D canvasPosition = new Point2D.Double();
\r
115 // ct.inverseTransform(new Point2D.Double(x,y), canvasPosition);
\r
116 // } catch (NoninvertibleTransformException e) {
\r
117 // e.printStackTrace();
\r
119 //// System.err.println("pane tr=" + canvasPosition + " " + ct + " (" + x + "," + y + ")");
\r
120 // return super.contains((int)canvasPosition.getX() , (int)canvasPosition.getY());
\r
123 // System.err.println("pane tr=" + canvasPosition);
\r
124 // //ElementUtils.controlToElementCoordinate(element, element.get, controlPoint, elementPoint);
\r
125 // boolean contains = super.contains(x, y);
\r
127 // System.err.println("pane contains!" + x + " " + y);
\r
129 // System.err.println("pane does not contain!" + x + " " + y);
\r
131 // return contains;
\r
134 // int trX = 0;//getAbsoluteX(this, 0);
\r
135 // int trY = 0;//getAbsoluteY(this, 0);
\r
137 //// System.err.println("trX=" + trX + " trY = " + trY);
\r
139 //// AWTChassis chassis = (AWTChassis)base.getParent().getParent();
\r
140 // AffineTransform ct = chassis.getCanvasContext().getDefaultHintContext().getHint(Hints.KEY_CANVAS_TRANSFORM);
\r
141 // if(ct == null) return false;
\r
143 // int origX = eventX;
\r
144 // int origY = eventY;
\r
147 // eventX -= ct.getTranslateX();
\r
148 // eventX /= ct.getScaleX();
\r
151 // eventY -= ct.getTranslateY();
\r
152 // eventY /= ct.getScaleY();
\r
155 // System.err.println("ComponentNode contains? (" + origX + "," + origY + ")=>(" + eventX + "," + eventY + ")");
\r
157 // return super.contains(eventX, eventY);
\r
160 //// return contains;
164 public Cursor getCursor() {
165 if(contains == false || component == null || component.isCursorSet() == false) {
\r
166 return Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
\r
168 return component.getCursor();
172 protected T component = null;
173 protected boolean visible = true;
174 protected boolean scale = false; // If true, component is scaled. If false, component is resized
\r
176 private transient Font componentFont;
\r
177 private transient double lastScale = 0;
\r
179 @PropertySetter("Bounds")
\r
180 @SyncField("bounds")
\r
181 public void setBounds(Rectangle2D bounds) {
\r
182 assert(bounds != null);
\r
183 this.bounds = bounds;
\r
187 @PropertySetter("Background Color")
\r
189 public void setBackgroundColor(Color color) {
\r
190 if(component != null) {
\r
191 component.setBackground(color);
\r
196 * If the size, font or any other scalable visual property of the component
\r
197 * changes, this method should be invoked to make sure that
\r
198 * {@link #render(Graphics2D)} will update the visual properties for the
\r
199 * component with proper scaling.
\r
201 protected void setScaleDirty() {
\r
202 // Triggers resetting of component font and size if necessary before
\r
205 //System.out.println("scale dirty");
\r
208 protected Font getComponentFont() {
\r
209 if (component != null) {
\r
210 if (componentFont == null) {
\r
211 componentFont = component.getFont();
\r
213 return componentFont;
\r
218 protected void setComponentFont(Font font) {
\r
219 this.componentFont = font;
\r
221 component.setFont(font);
\r
225 public void render(Graphics2D g2d) {
226 if(!visible || bounds == null) return;
228 Graphics2D g = (Graphics2D)g2d.create();
229 if (component != null) {
\r
230 // see http://java.sun.com/docs/books/tutorial/uiswing/lookandfeel/size.html
\r
231 component.putClientProperty("JComponent.sizeVariant", "large");
235 if (transform != null) {
\r
236 g.transform(transform);
\r
237 // sx = g.getTransform().getScaleX();
\r
238 // sy = g.getTransform().getScaleY();
\r
240 // g.scale(1/sx, 1/sy);
\r
244 // Assumes proportional scaling always.
\r
245 if (!scale && lastScale != sx) {
\r
246 //System.out.println("NEW SCALE: " + sx + " vs. " + lastScale);
\r
249 // Handle font scaling
\r
250 if (componentFont == null)
\r
251 componentFont = component.getFont();
\r
252 if (componentFont != null) {
\r
253 Font newFont = component.getFont().deriveFont(componentFont.getSize2D()*(float)sx);
\r
254 component.setFont(newFont);
\r
257 // Handle component size scaling.
\r
258 component.setSize((int)(bounds.getWidth()*sx), (int)(bounds.getHeight()*sy));
\r
260 component.setSize((int)(bounds.getWidth()*sx), (int)(bounds.getHeight()*sy));
\r
261 g.translate(bounds.getMinX()*sx, bounds.getMinY()*sy);
\r
262 component.paint(g);
\r
267 @SyncField({"visible"})
268 public void setVisible(boolean value) {
269 this.visible = value;
272 public Component getHeavyweightParent(Component component) {
\r
273 if(component.isLightweight()) return getHeavyweightParent(component.getParent());
\r
274 else return component;
\r
278 public void handleEvent(AWTEvent event) {
\r
279 if(focusable == false) return; // No event handling if not focusable
\r
280 if(bounds == null) return; // AAARGH..
\r
282 AWTEvent cevent = event;
\r
285 if(event instanceof MouseEvent) {
\r
287 IG2DNode node = (IG2DNode) this.getParent();
\r
288 while(node != null) {
\r
289 sx *= node.getTransform().getScaleX();
\r
290 sy *= node.getTransform().getScaleY();
\r
291 node = (G2DParentNode)node.getParent(); // FIXME: it should be G2DParentNode but you can never be sure
\r
295 MouseEvent me = (MouseEvent)event;
\r
296 // Use double coordinates if available
\r
297 double mx = me.getX();
\r
298 double my = me.getY();
\r
299 if(event instanceof ISGMouseEvent) {
\r
300 mx = ((ISGMouseEvent)event).getDoubleX();
\r
301 my = ((ISGMouseEvent)event).getDoubleY();
\r
303 AffineTransform i = AffineTransform.getTranslateInstance(-transform.getTranslateX()*sx-getBoundsInLocal().getMinX()*sx, -transform.getTranslateY()*sy-getBoundsInLocal().getMinY()*sy);
\r
304 Point2D p = i.transform(new Point2D.Double(sx*mx, sy*my), null);
\r
305 cevent = relocateEvent(me, p);
\r
306 Rectangle2D tb = new Rectangle2D.Double(transform.getTranslateX()+getBoundsInLocal().getMinX(), transform.getTranslateY()+getBoundsInLocal().getMinY(), getBoundsInLocal().getWidth(), getBoundsInLocal().getHeight());
\r
308 if(cevent.getID() == MouseEvent.MOUSE_PRESSED || cevent.getID() == MouseWheelEvent.MOUSE_WHEEL) {
\r
309 if(tb.contains(me.getPoint())) {
\r
310 G2DFocusManager.INSTANCE.markFocus(component);
\r
311 if(component != null && component.hasFocus() == false) {
\r
312 component.requestFocusInWindow();
\r
317 if(tb.contains(me.getPoint())) {
\r
318 container.setContains(true);
\r
320 container.setContains(false);
\r
323 if(cevent.getID() == MouseEvent.MOUSE_DRAGGED && component != null && component.hasFocus()) {
\r
324 cevent.setSource(component);
\r
327 if (component != null && container.contains) { //cevent.getSource().equals(component)) {
\r
329 cevent.setSource(component);
\r
331 // container.dispatchEvent(cevent);
\r
333 // Component hw = getHeavyweightParent(component);
\r
334 // hw.dispatchEvent(cevent);
\r
336 // container.getParent().dispatchEvent(cevent);
\r
337 // System.err.println("cevent=" + cevent);
\r
338 // // FIXME: terrible kludge to dispatch event correctly to every child
\r
339 // for(Component c : component.getComponents()) {
\r
340 // AWTEvent xe = translateEvent(cevent, new Point2D.Double(-c.getLocation().getX(), -c.getLocation().getY()));
\r
341 // xe.setSource(c);
\r
342 // c.dispatchEvent(xe);
\r
343 // if(c instanceof JComponent) {
\r
344 // for(Component c2 : ((JComponent)c).getComponents()) {
\r
345 // AWTEvent qe = translateEvent(xe, new Point2D.Double(-c.getLocation().getX(), -c.getLocation().getY()));
\r
346 // qe.setSource(c);
\r
347 // qe.setSource(c2);
\r
348 // c2.dispatchEvent(qe);
\r
349 // System.err.println("qe=" + qe);
\r
350 // System.err.println("xe=" + xe);
\r
351 // System.err.println("cevent=" + cevent);
\r
358 protected AWTEvent relocateEvent(AWTEvent e, Point2D p) {
\r
359 if(!(e instanceof MouseEvent)) return e; // Only for mouse events
\r
360 MouseEvent me = (MouseEvent)e;
\r
361 MouseEvent cevent = null;
\r
362 if(me.getID() == MouseWheelEvent.MOUSE_WHEEL) {
\r
363 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
365 cevent = new SGMouseEvent(new DummyComponent(), me.getID(), me.getWhen(), me.getModifiers(), p.getX(), p.getY(), me.getClickCount(), me.isPopupTrigger(), me.getButton(), me);
\r
370 protected AWTEvent translateEvent(AWTEvent e, Point2D p) {
\r
371 if(!(e instanceof MouseEvent)) return e; // Only for mouse events
\r
372 MouseEvent me = (MouseEvent)e;
\r
373 MouseEvent cevent = null;
\r
374 if(me.getID() == MouseWheelEvent.MOUSE_WHEEL) {
\r
375 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
377 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
381 public void setFocusable(boolean focusable) {
\r
382 this.focusable = focusable;
\r
383 container.setContains(false); // Always false when focusable property is changed
\r
384 component.setFocusable(focusable);
\r
388 public void init() {
\r
389 if(component == null) return; // FIXME: assert maybe?
\r
391 Container rootPane = NodeUtil.findRootPane(this);
\r
392 if(rootPane != null) {
\r
393 rootPane.add(container);
\r
395 throw new AssertionError("The canvas has no rootPane!");
\r
397 container.add(component);
\r
398 component.setFocusable(true);
\r
399 component.setIgnoreRepaint(true);
\r
401 UIDefaults cDefaults = new UIDefaults();
\r
403 component.putClientProperty("Nimbus.Overrides",cDefaults);
\r
404 component.putClientProperty("Nimbus.Overrides.InheritDefaults",false);
\r
406 // component.addMouseListener(new MouseListener() {
\r
409 // public void mouseClicked(MouseEvent e) {
\r
410 // System.err.println("aff");
\r
414 // public void mousePressed(MouseEvent e) {
\r
415 // System.err.println("aff2");
\r
419 // public void mouseReleased(MouseEvent e) {
\r
420 // System.err.println("aff3");
\r
424 // public void mouseEntered(MouseEvent e) {
\r
425 // // TODO Auto-generated method stub
\r
430 // public void mouseExited(MouseEvent e) {
\r
431 // // TODO Auto-generated method stub
\r
437 NodeUtil.getEventDelegator(this).addMouseListener(this);
\r
438 NodeUtil.getEventDelegator(this).addMouseMotionListener(this);
\r
439 NodeUtil.getEventDelegator(this).addKeyListener(this);
\r
440 NodeUtil.getEventDelegator(this).addFocusListener(this);
\r
445 public Rectangle2D getBoundsInLocal() {
\r
450 public void finalize() throws Throwable {
456 public void cleanup() {
\r
459 Container rootPane = NodeUtil.findRootPane(this);
\r
461 if(container != null) {
\r
462 rootPane.remove(container);
\r
463 container.setContains(false);
464 if(container.getParent() != null) container.getParent().remove(container); // eh...
\r
465 if (component != null)
466 container.remove(component);
\r
471 NodeUtil.getEventDelegator(this).removeMouseListener(this);
\r
472 NodeUtil.getEventDelegator(this).removeMouseMotionListener(this);
\r
473 NodeUtil.getEventDelegator(this).removeKeyListener(this);
\r
474 NodeUtil.getEventDelegator(this).removeFocusListener(this);
\r
480 public void keyTyped(KeyEvent e) {
\r
485 public void keyPressed(KeyEvent e) {
\r
490 public void keyReleased(KeyEvent e) {
\r
495 public void mouseDragged(MouseEvent e) {
\r
500 public void mouseMoved(MouseEvent e) {
\r
505 public void mouseClicked(MouseEvent e) {
\r
510 public void mousePressed(MouseEvent e) {
\r
515 public void mouseReleased(MouseEvent e) {
\r
520 public void mouseEntered(MouseEvent e) {
\r
525 public void mouseExited(MouseEvent e) {
\r
530 public void focusGained(FocusEvent e) {
\r
535 public void focusLost(FocusEvent e) {
\r