1 /* GtkComponentPeer.java -- Implements ComponentPeer with GTK
2 Copyright (C) 1998, 1999, 2002, 2004, 2005, 2006
3 Free Software Foundation, Inc.
5 This file is part of GNU Classpath.
7 GNU Classpath is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
12 GNU Classpath is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GNU Classpath; see the file COPYING. If not, write to the
19 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 Linking this library statically or dynamically with other modules is
23 making a combined work based on this library. Thus, the terms and
24 conditions of the GNU General Public License cover the whole
27 As a special exception, the copyright holders of this library give you
28 permission to link this library with independent modules to produce an
29 executable, regardless of the license terms of these independent
30 modules, and to copy and distribute the resulting executable under
31 terms of your choice, provided that you also meet, for each linked
32 independent module, the terms and conditions of the license of that
33 module. An independent module is a module which is not derived from
34 or based on this library. If you modify this library, you may extend
35 this exception to your version of the library, but you are not
36 obligated to do so. If you do not wish to do so, delete this
37 exception statement from your version. */
40 package gnu.java.awt.peer.gtk;
42 import java.awt.AWTEvent;
43 import java.awt.AWTException;
44 import java.awt.BufferCapabilities;
45 import java.awt.Color;
46 import java.awt.Component;
47 import java.awt.Container;
48 import java.awt.Cursor;
49 import java.awt.Dimension;
50 import java.awt.EventQueue;
52 import java.awt.FontMetrics;
53 import java.awt.Graphics;
54 import java.awt.GraphicsConfiguration;
55 import java.awt.GraphicsDevice;
56 import java.awt.GraphicsEnvironment;
57 import java.awt.Image;
58 import java.awt.Insets;
59 import java.awt.ItemSelectable;
60 import java.awt.KeyboardFocusManager;
61 import java.awt.Point;
62 import java.awt.Rectangle;
63 import java.awt.Toolkit;
64 import java.awt.Window;
65 import java.awt.event.FocusEvent;
66 import java.awt.event.ItemEvent;
67 import java.awt.event.KeyEvent;
68 import java.awt.event.MouseEvent;
69 import java.awt.event.MouseWheelEvent;
70 import java.awt.event.PaintEvent;
71 import java.awt.event.TextEvent;
72 import java.awt.image.ColorModel;
73 import java.awt.image.ImageObserver;
74 import java.awt.image.ImageProducer;
75 import java.awt.image.VolatileImage;
76 import java.awt.peer.ComponentPeer;
77 import java.awt.peer.ContainerPeer;
78 import java.awt.peer.LightweightPeer;
79 import java.awt.peer.WindowPeer;
80 import java.util.Timer;
81 import java.util.TimerTask;
83 public class GtkComponentPeer extends GtkGenericPeer
84 implements ComponentPeer
86 VolatileImage backBuffer;
87 BufferCapabilities caps;
89 Component awtComponent;
94 * The current repaint area. Use should be guarded by synchronizing on this.
96 private Rectangle currentPaintArea;
98 /* this isEnabled differs from Component.isEnabled, in that it
99 knows if a parent is disabled. In that case Component.isEnabled
100 may return true, but our isEnabled will always return false */
101 native boolean isEnabled ();
102 static native boolean modalHasGrab();
104 native int[] gtkWidgetGetForeground ();
105 native int[] gtkWidgetGetBackground ();
106 native void gtkWidgetGetDimensions (int[] dim);
107 native void gtkWidgetGetPreferredDimensions (int[] dim);
108 native void gtkWindowGetLocationOnScreen (int[] point);
109 native void gtkWidgetGetLocationOnScreen (int[] point);
110 native void gtkWidgetSetCursor (int type, GtkImage image, int x, int y);
111 native void gtkWidgetSetCursorUnlocked (int type, GtkImage image,
113 native void gtkWidgetSetBackground (int red, int green, int blue);
114 native void gtkWidgetSetForeground (int red, int green, int blue);
115 native void gtkWidgetSetSensitive (boolean sensitive);
116 native void gtkWidgetSetParent (ComponentPeer parent);
117 native void gtkWidgetRequestFocus ();
118 native void gtkWidgetDispatchKeyEvent (int id, long when, int mods,
119 int keyCode, int keyLocation);
120 native boolean gtkWidgetHasFocus();
121 native boolean gtkWidgetCanFocus();
123 native void realize();
124 native void setNativeEventMask ();
128 throw new RuntimeException ();
131 native void connectSignals ();
133 protected GtkComponentPeer (Component awtComponent)
135 super (awtComponent);
136 this.awtComponent = awtComponent;
137 insets = new Insets (0, 0, 0, 0);
143 if (awtComponent.getForeground () != null)
144 setForeground (awtComponent.getForeground ());
145 if (awtComponent.getBackground () != null)
146 setBackground (awtComponent.getBackground ());
147 if (awtComponent.getFont() != null)
148 setFont(awtComponent.getFont());
150 Component parent = awtComponent.getParent ();
152 setParentAndBounds ();
154 setNativeEventMask ();
156 // This peer is guaranteed to have an X window upon construction.
157 // That is, native methods such as those in GdkGraphics can rely
158 // on this component's widget->window field being non-null.
161 if (awtComponent.isCursorSet())
165 void setParentAndBounds ()
169 setComponentBounds ();
171 setVisibleAndEnabled ();
177 Component component = awtComponent;
180 component = component.getParent ();
181 p = component.getPeer ();
183 while (p instanceof java.awt.peer.LightweightPeer);
186 gtkWidgetSetParent (p);
190 * Set the bounds of this peer's AWT Component based on dimensions
191 * returned by the native windowing system. Most Components impose
192 * their dimensions on the peers which is what the default
193 * implementation does. However some peers, like GtkFileDialogPeer,
194 * need to pass their size back to the AWT Component.
196 void setComponentBounds ()
198 Rectangle bounds = awtComponent.getBounds ();
199 setBounds (bounds.x, bounds.y, bounds.width, bounds.height);
202 void setVisibleAndEnabled ()
204 setVisible (awtComponent.isVisible ());
205 setEnabled (awtComponent.isEnabled ());
208 public int checkImage (Image image, int width, int height,
209 ImageObserver observer)
211 return getToolkit().checkImage(image, width, height, observer);
214 public Image createImage (ImageProducer producer)
216 return new GtkImage (producer);
219 public Image createImage (int width, int height)
221 return CairoSurface.getBufferedImage(width, height);
224 public void disable ()
229 public void enable ()
234 public ColorModel getColorModel ()
236 return ColorModel.getRGBdefault ();
239 public FontMetrics getFontMetrics (Font font)
241 return getToolkit().getFontMetrics(font);
244 // getGraphics may be overridden by derived classes but it should
245 // never return null.
246 public Graphics getGraphics ()
248 return ComponentGraphics.getComponentGraphics(this);
251 public Point getLocationOnScreen ()
253 int point[] = new int[2];
254 if( this instanceof WindowPeer )
255 gtkWindowGetLocationOnScreen (point);
257 gtkWidgetGetLocationOnScreen (point);
258 return new Point (point[0], point[1]);
261 public Dimension getMinimumSize ()
263 return minimumSize ();
266 public Dimension getPreferredSize ()
268 return preferredSize ();
271 public Toolkit getToolkit ()
273 return Toolkit.getDefaultToolkit();
276 public void handleEvent (AWTEvent event)
278 int id = event.getID();
283 case PaintEvent.PAINT:
284 paintComponent((PaintEvent) event);
286 case PaintEvent.UPDATE:
287 updateComponent((PaintEvent) event);
289 case KeyEvent.KEY_PRESSED:
290 ke = (KeyEvent) event;
291 gtkWidgetDispatchKeyEvent (ke.getID (), ke.getWhen (), ke.getModifiersEx (),
292 ke.getKeyCode (), ke.getKeyLocation ());
294 case KeyEvent.KEY_RELEASED:
295 ke = (KeyEvent) event;
296 gtkWidgetDispatchKeyEvent (ke.getID (), ke.getWhen (), ke.getModifiersEx (),
297 ke.getKeyCode (), ke.getKeyLocation ());
302 // This method and its overrides are the only methods in the peers
303 // that should call awtComponent.paint.
304 protected void paintComponent (PaintEvent event)
306 // Do not call Component.paint if the component is not showing or
307 // if its bounds form a degenerate rectangle.
308 if (!awtComponent.isShowing()
309 || (awtComponent.getWidth() < 1 || awtComponent.getHeight() < 1))
312 // Creating and disposing a GdkGraphics every time paint is called
313 // seems expensive. However, the graphics state does not carry
314 // over between calls to paint, and resetting the graphics object
315 // may even be more costly than simply creating a new one.
317 // Make sure that the paintArea includes the area from the event
318 // in the case when an application sends PaintEvents directly.
319 coalescePaintEvent(event);
323 paintArea = currentPaintArea;
324 currentPaintArea = null;
327 if (paintArea != null)
329 Graphics g = getGraphics();
332 g.setClip(paintArea);
333 awtComponent.paint(g);
342 // This method and its overrides are the only methods in the peers
343 // that should call awtComponent.update.
344 protected void updateComponent (PaintEvent event)
346 // Do not call Component.update if the component is not showing or
347 // if its bounds form a degenerate rectangle.
348 if (!awtComponent.isShowing()
349 || (awtComponent.getWidth() < 1 || awtComponent.getHeight() < 1))
352 // Make sure that the paintArea includes the area from the event
353 // in the case when an application sends PaintEvents directly.
354 coalescePaintEvent(event);
358 paintArea = currentPaintArea;
359 currentPaintArea = null;
362 if (paintArea != null)
364 Graphics g = getGraphics();
367 g.setClip(paintArea);
368 awtComponent.update(g);
377 public boolean isFocusTraversable ()
382 public Dimension minimumSize ()
384 int dim[] = new int[2];
386 gtkWidgetGetPreferredDimensions (dim);
388 return new Dimension (dim[0], dim[1]);
391 public void paint (Graphics g)
395 public Dimension preferredSize ()
397 int dim[] = new int[2];
399 gtkWidgetGetPreferredDimensions (dim);
401 return new Dimension (dim[0], dim[1]);
404 public boolean prepareImage (Image image, int width, int height,
405 ImageObserver observer)
407 return getToolkit().prepareImage(image, width, height, observer);
410 public void print (Graphics g)
412 g.drawImage( ComponentGraphics.grab( this ), 0, 0, null );
415 public void repaint (long tm, int x, int y, int width, int height)
417 if (width < 1 || height < 1)
421 q().postEvent(new PaintEvent(awtComponent, PaintEvent.UPDATE,
422 new Rectangle(x, y, width, height)));
424 RepaintTimerTask.schedule(tm, x, y, width, height, awtComponent);
428 * Used for scheduling delayed paint updates on the event queue.
430 private static class RepaintTimerTask extends TimerTask
432 private static final Timer repaintTimer = new Timer(true);
434 private int x, y, width, height;
435 private Component awtComponent;
437 RepaintTimerTask(Component c, int x, int y, int width, int height)
442 this.height = height;
443 this.awtComponent = c;
448 q().postEvent (new PaintEvent (awtComponent, PaintEvent.UPDATE,
449 new Rectangle (x, y, width, height)));
452 static void schedule(long tm, int x, int y, int width, int height,
455 repaintTimer.schedule(new RepaintTimerTask(c, x, y, width, height), tm);
459 public void requestFocus ()
461 assert false: "Call new requestFocus() method instead";
464 public void reshape (int x, int y, int width, int height)
466 setBounds (x, y, width, height);
469 public void setBackground (Color c)
471 gtkWidgetSetBackground (c.getRed(), c.getGreen(), c.getBlue());
474 native void setNativeBounds (int x, int y, int width, int height);
476 public void setBounds (int x, int y, int width, int height)
481 Component parent = awtComponent.getParent ();
483 // Heavyweight components that are children of one or more
484 // lightweight containers have to be handled specially. Because
485 // calls to GLightweightPeer.setBounds do nothing, GTK has no
486 // knowledge of the lightweight containers' positions. So we have
487 // to add the offsets manually when placing a heavyweight
488 // component within a lightweight container. The lightweight
489 // container may itself be in a lightweight container and so on,
490 // so we need to continue adding offsets until we reach a
491 // container whose position GTK knows -- that is, the first
494 while (parent.isLightweight())
496 i = ((Container) parent).getInsets();
498 new_x += parent.getX() + i.left;
499 new_y += parent.getY() + i.top;
501 parent = parent.getParent();
503 // We only need to convert from Java to GTK coordinates if we're
504 // placing a heavyweight component in a Window.
505 if (parent instanceof Window)
507 GtkWindowPeer peer = (GtkWindowPeer) parent.getPeer ();
508 // important: we want the window peer's insets here, not the
509 // window's, since user sub-classes of Window can override
510 // getInset and we only want to correct for the frame borders,
511 // not for any user-defined inset values
512 Insets insets = peer.getInsets ();
514 int menuBarHeight = 0;
515 if (peer instanceof GtkFramePeer)
516 menuBarHeight = ((GtkFramePeer) peer).getMenuBarHeight ();
518 new_x -= insets.left;
520 new_y += menuBarHeight;
523 setNativeBounds (new_x, new_y, width, height);
525 // If the height or width were (or are now) smaller than zero
526 // then we want to adjust the visibility.
527 setVisible(awtComponent.isVisible());
532 setCursor (awtComponent.getCursor ());
535 public void setCursor (Cursor cursor)
539 int type = cursor.getType();
540 if (cursor instanceof GtkCursor)
542 GtkCursor gtkCursor = (GtkCursor) cursor;
543 image = gtkCursor.getGtkImage();
544 Point hotspot = gtkCursor.getHotspot();
555 if (Thread.currentThread() == GtkMainThread.mainThread)
556 gtkWidgetSetCursorUnlocked(cursor.getType(), image, x, y);
558 gtkWidgetSetCursor(cursor.getType(), image, x, y);
561 public void setEnabled (boolean b)
563 gtkWidgetSetSensitive (b);
566 public void setFont (Font f)
568 // FIXME: This should really affect the widget tree below me.
569 // Currently this is only handled if the call is made directly on
570 // a text widget, which implements setFont() itself.
571 gtkWidgetModifyFont(f.getName(), f.getStyle(), f.getSize());
574 public void setForeground (Color c)
576 gtkWidgetSetForeground (c.getRed(), c.getGreen(), c.getBlue());
579 public Color getForeground ()
581 int rgb[] = gtkWidgetGetForeground ();
582 return new Color (rgb[0], rgb[1], rgb[2]);
585 public Color getBackground ()
587 int rgb[] = gtkWidgetGetBackground ();
588 return new Color (rgb[0], rgb[1], rgb[2]);
591 public native void setVisibleNative (boolean b);
592 public native void setVisibleNativeUnlocked (boolean b);
594 public void setVisible (boolean b)
596 // Only really set visible when component is bigger than zero pixels.
597 if (b && ! (awtComponent instanceof Window))
599 Rectangle bounds = awtComponent.getBounds();
600 b = (bounds.width > 0) && (bounds.height > 0);
603 if (Thread.currentThread() == GtkMainThread.mainThread)
604 setVisibleNativeUnlocked (b);
606 setVisibleNative (b);
619 protected void postMouseEvent(int id, long when, int mods, int x, int y,
620 int clickCount, boolean popupTrigger)
622 q().postEvent(new MouseEvent(awtComponent, id, when, mods, x, y,
623 clickCount, popupTrigger));
627 * Callback for component_scroll_cb.
629 protected void postMouseWheelEvent(int id, long when, int mods,
630 int x, int y, int clickCount,
631 boolean popupTrigger,
632 int type, int amount, int rotation)
634 q().postEvent(new MouseWheelEvent(awtComponent, id, when, mods,
635 x, y, clickCount, popupTrigger,
636 type, amount, rotation));
639 protected void postExposeEvent (int x, int y, int width, int height)
641 q().postEvent (new PaintEvent (awtComponent, PaintEvent.PAINT,
642 new Rectangle (x, y, width, height)));
645 protected void postKeyEvent (int id, long when, int mods,
646 int keyCode, char keyChar, int keyLocation)
648 KeyEvent keyEvent = new KeyEvent (awtComponent, id, when, mods,
649 keyCode, keyChar, keyLocation);
653 // Also post a KEY_TYPED event if keyEvent is a key press that
654 // doesn't represent an action or modifier key.
655 if (keyEvent.getID () == KeyEvent.KEY_PRESSED
656 && (!keyEvent.isActionKey ()
657 && keyCode != KeyEvent.VK_SHIFT
658 && keyCode != KeyEvent.VK_CONTROL
659 && keyCode != KeyEvent.VK_ALT))
663 q.postEvent(keyEvent);
664 keyEvent = new KeyEvent(awtComponent, KeyEvent.KEY_TYPED, when,
665 mods, KeyEvent.VK_UNDEFINED, keyChar,
667 q.postEvent(keyEvent);
671 q.postEvent(keyEvent);
675 * Referenced from native code.
680 protected void postFocusEvent (int id, boolean temporary)
682 q().postEvent (new FocusEvent (awtComponent, id, temporary));
685 protected void postItemEvent (Object item, int stateChange)
687 q().postEvent (new ItemEvent ((ItemSelectable)awtComponent,
688 ItemEvent.ITEM_STATE_CHANGED,
692 protected void postTextEvent ()
694 q().postEvent (new TextEvent (awtComponent, TextEvent.TEXT_VALUE_CHANGED));
697 public GraphicsConfiguration getGraphicsConfiguration ()
699 // FIXME: The component might be showing on a non-default screen.
700 GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
701 GraphicsDevice dev = env.getDefaultScreenDevice();
702 return dev.getDefaultConfiguration();
705 public void setEventMask (long mask)
707 // FIXME: just a stub for now.
710 public boolean isFocusable ()
715 public boolean requestFocus (Component request, boolean temporary,
716 boolean allowWindowFocus, long time)
718 assert request == awtComponent || isLightweightDescendant(request);
719 boolean retval = false;
720 if (gtkWidgetHasFocus())
722 KeyboardFocusManager kfm =
723 KeyboardFocusManager.getCurrentKeyboardFocusManager();
724 Component currentFocus = kfm.getFocusOwner();
725 if (currentFocus == request)
726 // Nothing to do in this trivial case.
730 // Requested component is a lightweight descendant of this one
731 // or the actual heavyweight.
732 // Since this (native) component is already focused, we simply
733 // change the actual focus and be done.
734 postFocusEvent(FocusEvent.FOCUS_GAINED, temporary);
740 if (gtkWidgetCanFocus())
742 if (allowWindowFocus)
744 Window window = getWindowFor(request);
745 GtkWindowPeer wPeer = (GtkWindowPeer) window.getPeer();
746 if (! wPeer.gtkWindowHasFocus())
747 wPeer.requestWindowFocus();
749 // Store requested focus component so that the corresponding
750 // event is dispatched correctly.
751 gtkWidgetRequestFocus();
758 private Window getWindowFor(Component c)
761 while (! (comp instanceof Window))
762 comp = comp.getParent();
763 return (Window) comp;
767 * Returns <code>true</code> if the component is a direct (== no intermediate
768 * heavyweights) lightweight descendant of this peer's component.
770 * @param c the component to check
772 * @return <code>true</code> if the component is a direct (== no intermediate
773 * heavyweights) lightweight descendant of this peer's component
775 protected boolean isLightweightDescendant(Component c)
778 while (comp.getPeer() instanceof LightweightPeer)
779 comp = comp.getParent();
780 return comp == awtComponent;
783 public boolean isObscured ()
788 public boolean canDetermineObscurity ()
793 public void coalescePaintEvent (PaintEvent e)
797 Rectangle newRect = e.getUpdateRect();
798 if (currentPaintArea == null)
799 currentPaintArea = newRect;
801 Rectangle.union(currentPaintArea, newRect, currentPaintArea);
805 public void updateCursorImmediately ()
807 if (awtComponent.getCursor() != null)
808 setCursor(awtComponent.getCursor());
811 public boolean handlesWheelScrolling ()
816 // Convenience method to create a new volatile image on the screen
817 // on which this component is displayed.
818 public VolatileImage createVolatileImage (int width, int height)
820 return new GtkVolatileImage (this, width, height, null);
823 // Creates buffers used in a buffering strategy.
824 public void createBuffers (int numBuffers, BufferCapabilities caps)
827 // numBuffers == 2 implies double-buffering, meaning one back
828 // buffer and one front buffer.
830 backBuffer = new GtkVolatileImage(this, awtComponent.getWidth(),
831 awtComponent.getHeight(),
832 caps.getBackBufferCapabilities());
834 throw new AWTException("GtkComponentPeer.createBuffers:"
835 + " multi-buffering not supported");
839 // Return the back buffer.
840 public Image getBackBuffer ()
845 // FIXME: flip should be implemented as a fast native operation
846 public void flip (BufferCapabilities.FlipContents contents)
848 getGraphics().drawImage(backBuffer,
849 awtComponent.getWidth(),
850 awtComponent.getHeight(),
853 // create new back buffer and clear it to the background color.
854 if (contents == BufferCapabilities.FlipContents.BACKGROUND)
856 backBuffer = createVolatileImage(awtComponent.getWidth(),
857 awtComponent.getHeight());
858 backBuffer.getGraphics().clearRect(0, 0,
859 awtComponent.getWidth(),
860 awtComponent.getHeight());
862 // FIXME: support BufferCapabilities.FlipContents.PRIOR
865 // Release the resources allocated to back buffers.
866 public void destroyBuffers ()
871 public String toString ()
873 return "peer of " + awtComponent.toString();
875 public Rectangle getBounds()
880 public void reparent(ContainerPeer parent)
885 public void setBounds(int x, int y, int width, int height, int z)
888 setBounds (x, y, width, height);
891 public boolean isReparentSupported()