blob: 98b7620f7ab71d99adc94903805ad85f1bc254c8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.pivot.scene;
import java.awt.AWTEvent;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Random;
import org.apache.pivot.scene.Stage;
/**
* AWT scene graph host component.
*/
public class AWTStageHost extends java.awt.Component {
private static final long serialVersionUID = 0;
private Stage stage = new Stage() {
@Override
public void invalidate() {
AWTStageHost.this.invalidate();
}
@Override
public void repaintHost(int x, int y, int width, int height) {
AWTStageHost.this.repaint(x, y, width, height);
}
@Override
public Graphics getHostGraphics() {
Graphics2D graphics2D = (Graphics2D)AWTStageHost.this.getGraphics();
Graphics graphics = null;
if (graphics2D != null) {
graphics2D.clipRect(0, 0, getWidth(), getHeight());
graphics = new AWTGraphics(graphics2D);
}
return graphics;
}
@Override
public void requestNativeFocus() {
requestFocusInWindow();
}
@Override
public Object getNativeHost() {
return AWTStageHost.this;
}
};
private Node mouseCapturer = null;
private Node focusOwner = null;
private boolean doubleBuffered = true;
private boolean disableVolatileBuffer = true;
private boolean debugPaint = false;
private boolean debugFocus = false;
private boolean paintPending = false;
public AWTStageHost() {
enableEvents(AWTEvent.COMPONENT_EVENT_MASK
| AWTEvent.FOCUS_EVENT_MASK
| AWTEvent.MOUSE_EVENT_MASK
| AWTEvent.MOUSE_MOTION_EVENT_MASK
| AWTEvent.MOUSE_WHEEL_EVENT_MASK
| AWTEvent.KEY_EVENT_MASK);
try {
System.setProperty("sun.awt.noerasebackground", "true");
System.setProperty("sun.awt.erasebackgroundonresize", "false");
} catch (SecurityException exception) {
// No-op
}
setFocusTraversalKeysEnabled(false);
// Listen for changes to the font desktop hints property
Toolkit toolkit = Toolkit.getDefaultToolkit();
toolkit.addPropertyChangeListener("awt.font.desktophints", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent event) {
invalidate();
}
});
}
public Stage getStage() {
return stage;
}
@Override
public boolean isDoubleBuffered() {
return doubleBuffered;
}
public void setDoubleBuffered(boolean doubleBuffered) {
this.doubleBuffered = doubleBuffered;
}
public boolean getDisableVolatileBuffer() {
return disableVolatileBuffer;
}
public void setDisableVolatileBuffer(boolean disableVolatileBuffer) {
this.disableVolatileBuffer = disableVolatileBuffer;
}
public boolean getDebugPaint() {
return debugPaint;
}
public void setDebugPaint(boolean debugPaint) {
this.debugPaint = debugPaint;
}
public boolean getDebugFocus() {
return debugFocus;
}
public void setDebugFocus(boolean debugFocus) {
this.debugFocus = debugFocus;
// TODO
repaint();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(stage.getPreferredWidth(), stage.getPreferredHeight());
}
@Override
public void repaint(int x, int y, int width, int height) {
// Ensure that the repaint call is properly bounded (some
// implementations of AWT do not properly clip the repaint call
// when x or y is negative: the negative value is converted to 0,
// but the width/height is not adjusted)
if (x < 0) {
width = Math.max(width + x, 0);
x = 0;
}
if (y < 0) {
height = Math.max(height + y, 0);
y = 0;
}
if (width > 0
&& height > 0) {
super.repaint(x, y, width, height);
paintPending = true;
}
}
@Override
public void paint(java.awt.Graphics graphics) {
// Intersect the clip region with the bounds of this component
// (for some reason, AWT does not do this automatically)
graphics.clipRect(0, 0, getWidth(), getHeight());
java.awt.Rectangle clipBounds = graphics.getClipBounds();
if (clipBounds != null
&& !clipBounds.isEmpty()) {
Graphics2D graphics2D = (Graphics2D)graphics;
try {
if (doubleBuffered) {
if (disableVolatileBuffer
|| !paintVolatileBuffered(graphics2D)) {
if (!paintBuffered(graphics2D)) {
paintUnbuffered(graphics2D);
}
}
} else {
paintUnbuffered(graphics2D);
}
if (debugPaint) {
Random random = new Random();
graphics.setColor(new java.awt.Color(random.nextInt(256),
random.nextInt(256), random.nextInt(256), 75));
graphics.fillRect(0, 0, getWidth(), getHeight());
}
} catch (RuntimeException exception) {
exception.printStackTrace();
throw exception;
}
}
paintPending = false;
}
@Override
public void update(java.awt.Graphics graphics) {
paint(graphics);
}
/**
* Attempts to paint the stage using an offscreen buffer.
*
* @param graphics
* The source graphics context.
*
* @return
* <tt>true</tt> if the stage was painted using the offscreen
* buffer; <tt>false</tt>, otherwise.
*/
private boolean paintBuffered(Graphics2D graphics) {
boolean painted = false;
// Paint the stage into an offscreen buffer
GraphicsConfiguration gc = graphics.getDeviceConfiguration();
java.awt.Rectangle clipBounds = graphics.getClipBounds();
java.awt.image.BufferedImage bufferedImage =
gc.createCompatibleImage(clipBounds.width, clipBounds.height, Transparency.OPAQUE);
if (bufferedImage != null) {
Graphics2D bufferedImageGraphics = (Graphics2D)bufferedImage.getGraphics();
bufferedImageGraphics.setClip(0, 0, clipBounds.width, clipBounds.height);
bufferedImageGraphics.translate(-clipBounds.x, -clipBounds.y);
try {
paintUnbuffered(bufferedImageGraphics);
graphics.drawImage(bufferedImage, clipBounds.x, clipBounds.y, this);
} finally {
bufferedImageGraphics.dispose();
}
painted = true;
}
return painted;
}
/**
* Attempts to paint the stage using a volatile offscreen buffer.
*
* @param graphics
* The source graphics context.
*
* @return
* <tt>true</tt> if the stage was painted using the offscreen
* buffer; <tt>false</tt>, otherwise.
*/
private boolean paintVolatileBuffered(Graphics2D graphics) {
boolean painted = false;
// Paint the stage into a volatile offscreen buffer
GraphicsConfiguration gc = graphics.getDeviceConfiguration();
java.awt.Rectangle clipBounds = graphics.getClipBounds();
java.awt.image.VolatileImage volatileImage =
gc.createCompatibleVolatileImage(clipBounds.width, clipBounds.height,
Transparency.OPAQUE);
if (volatileImage != null) {
int valid = volatileImage.validate(gc);
if (valid == java.awt.image.VolatileImage.IMAGE_OK
|| valid == java.awt.image.VolatileImage.IMAGE_RESTORED) {
Graphics2D volatileImageGraphics = volatileImage.createGraphics();
volatileImageGraphics.setClip(0, 0, clipBounds.width, clipBounds.height);
volatileImageGraphics.translate(-clipBounds.x, -clipBounds.y);
try {
paintUnbuffered(volatileImageGraphics);
graphics.drawImage(volatileImage, clipBounds.x, clipBounds.y, this);
} finally {
volatileImageGraphics.dispose();
}
painted = !volatileImage.contentsLost();
}
}
return painted;
}
/**
* Paints the stage without any buffering.
*
* @param graphics
*/
private void paintUnbuffered(Graphics2D graphics) {
graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
AWTGraphics awtGraphics = new AWTGraphics(graphics);
stage.paint(awtGraphics);
}
@Override
protected void processComponentEvent(ComponentEvent event) {
super.processComponentEvent(event);
switch (event.getID()) {
case ComponentEvent.COMPONENT_RESIZED: {
stage.setSize(Math.max(getWidth(), 0), Math.max(getHeight(), 0));
break;
}
}
}
@Override
protected void processFocusEvent(FocusEvent event) {
super.processFocusEvent(event);
switch(event.getID()) {
case FocusEvent.FOCUS_GAINED: {
if (focusOwner != null
&& focusOwner.isShowing()
&& !focusOwner.isBlocked()) {
focusOwner.requestFocus();
}
focusOwner = null;
break;
}
case FocusEvent.FOCUS_LOST: {
focusOwner = Node.getFocusedNode();
Node.clearFocus();
break;
}
}
}
@Override
protected void processMouseEvent(MouseEvent event) {
super.processMouseEvent(event);
int x = event.getX();
int y = event.getY();
// Set the mouse button state
int mouseButtons = 0x00;
int modifiersEx = event.getModifiersEx();
if ((modifiersEx & MouseEvent.BUTTON1_DOWN_MASK) > 0) {
mouseButtons |= Mouse.Button.LEFT.getMask();
}
if ((modifiersEx & MouseEvent.BUTTON2_DOWN_MASK) > 0) {
mouseButtons |= Mouse.Button.MIDDLE.getMask();
}
if ((modifiersEx & MouseEvent.BUTTON3_DOWN_MASK) > 0) {
mouseButtons |= Mouse.Button.RIGHT.getMask();
}
Mouse.setButtons(mouseButtons);
// Get the button associated with this event
Mouse.Button button = null;
switch (event.getButton()) {
case MouseEvent.BUTTON1: {
button = Mouse.Button.LEFT;
break;
}
case MouseEvent.BUTTON2: {
button = Mouse.Button.MIDDLE;
break;
}
case MouseEvent.BUTTON3: {
button = Mouse.Button.RIGHT;
break;
}
}
// Process the event
boolean consumed = false;
try {
switch(event.getID()) {
case MouseEvent.MOUSE_ENTERED: {
stage.mouseEntered();
break;
}
case MouseEvent.MOUSE_EXITED: {
stage.mouseExited();
break;
}
case MouseEvent.MOUSE_PRESSED: {
consumed = stage.mousePressed(button, x, y);
break;
}
case MouseEvent.MOUSE_RELEASED: {
consumed = stage.mouseReleased(button, x, y);
break;
}
}
} catch (Exception exception) {
exception.printStackTrace();
}
if (consumed) {
event.consume();
}
}
@Override
protected void processMouseMotionEvent(MouseEvent event) {
super.processMouseMotionEvent(event);
if (!paintPending) {
int x = event.getX();
int y = event.getY();
// Process the event
boolean consumed = false;
try {
switch (event.getID()) {
case MouseEvent.MOUSE_MOVED: {
mouseCapturer = null;
consumed = stage.mouseMoved(x, y, false);
break;
}
case MouseEvent.MOUSE_DRAGGED: {
if (mouseCapturer == null) {
mouseCapturer = stage.getDescendantAt(x, y);
}
Point location = mouseCapturer.mapPointFromAncestor(stage, x, y);
consumed = mouseCapturer.mouseMoved(location.x, location.y, true);
break;
}
}
} catch (Exception exception) {
exception.printStackTrace();
}
if (consumed) {
event.consume();
}
}
}
@Override
protected void processMouseWheelEvent(MouseWheelEvent event) {
super.processMouseWheelEvent(event);
// Get the event coordinates
int x = event.getX();
int y = event.getY();
// Get the scroll type
Mouse.ScrollType scrollType = null;
switch (event.getScrollType()) {
case MouseWheelEvent.WHEEL_BLOCK_SCROLL: {
scrollType = Mouse.ScrollType.BLOCK;
break;
}
case MouseWheelEvent.WHEEL_UNIT_SCROLL: {
scrollType = Mouse.ScrollType.UNIT;
break;
}
}
// Process the event
boolean consumed = false;
try {
switch (event.getID()) {
case MouseEvent.MOUSE_WHEEL: {
consumed = stage.mouseWheelScrolled(scrollType, event.getScrollAmount(),
event.getWheelRotation(), x, y);
break;
}
}
} catch (Exception exception) {
exception.printStackTrace();
}
if (consumed) {
event.consume();
}
}
@Override
protected void processKeyEvent(KeyEvent event) {
super.processKeyEvent(event);
if (focusOwner != null
&& !focusOwner.isBlocked()) {
int modifiersEx = event.getModifiersEx();
int awtKeyLocation = event.getKeyLocation();
// Set the keyboard modifier state
int keyboardModifiers = 0;
if ((modifiersEx & KeyEvent.SHIFT_DOWN_MASK) > 0) {
keyboardModifiers |= Keyboard.Modifier.SHIFT.getMask();
}
// Ignore Control when Alt-Graphics is pressed
if ((modifiersEx & KeyEvent.CTRL_DOWN_MASK) > 0
&& ((modifiersEx & KeyEvent.ALT_DOWN_MASK) == 0
|| awtKeyLocation == KeyEvent.KEY_LOCATION_RIGHT)) {
keyboardModifiers |= Keyboard.Modifier.CTRL.getMask();
}
if ((modifiersEx & KeyEvent.ALT_DOWN_MASK) > 0) {
keyboardModifiers |= Keyboard.Modifier.ALT.getMask();
}
if ((modifiersEx & KeyEvent.META_DOWN_MASK) > 0) {
keyboardModifiers |= Keyboard.Modifier.META.getMask();
}
Keyboard.setModifiers(keyboardModifiers);
// Get the key location
Keyboard.KeyLocation keyLocation = null;
switch (awtKeyLocation) {
case KeyEvent.KEY_LOCATION_STANDARD: {
keyLocation = Keyboard.KeyLocation.STANDARD;
break;
}
case KeyEvent.KEY_LOCATION_LEFT: {
keyLocation = Keyboard.KeyLocation.LEFT;
break;
}
case KeyEvent.KEY_LOCATION_RIGHT: {
keyLocation = Keyboard.KeyLocation.RIGHT;
break;
}
case KeyEvent.KEY_LOCATION_NUMPAD: {
keyLocation = Keyboard.KeyLocation.KEYPAD;
break;
}
}
// Process the event
boolean consumed = false;
try {
switch (event.getID()) {
case KeyEvent.KEY_PRESSED: {
int keyCode = event.getKeyCode();
consumed = focusOwner.keyPressed(keyCode, keyLocation);
break;
}
case KeyEvent.KEY_RELEASED: {
int keyCode = event.getKeyCode();
consumed = focusOwner.keyReleased(keyCode, keyLocation);
break;
}
case KeyEvent.KEY_TYPED: {
char keyChar = event.getKeyChar();
consumed = focusOwner.keyTyped(keyChar);
break;
}
}
} catch (Exception exception) {
exception.printStackTrace();
}
if (consumed) {
event.consume();
}
}
}
}