blob: 59fc0c3a8ac007e4b01d7f0885d4e5232baafe8a [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.wtk;
import java.awt.AWTEvent;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.PrintGraphics;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureRecognizer;
import java.awt.dnd.DragSourceContext;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusEvent;
import java.awt.event.InputEvent;
import java.awt.event.InputMethodEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.font.TextHitInfo;
import java.awt.im.InputMethodRequests;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.awt.print.PrinterGraphics;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.AttributedCharacterIterator;
import java.util.Iterator;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import org.apache.pivot.collections.ArrayList;
import org.apache.pivot.collections.Dictionary;
import org.apache.pivot.collections.HashMap;
import org.apache.pivot.collections.List;
import org.apache.pivot.collections.Map;
import org.apache.pivot.json.JSONSerializer;
import org.apache.pivot.serialization.SerializationException;
import org.apache.pivot.util.Utils;
import org.apache.pivot.util.Version;
import org.apache.pivot.wtk.Component.DecoratorSequence;
import org.apache.pivot.wtk.effects.Decorator;
import org.apache.pivot.wtk.effects.ShadeDecorator;
/**
* Base class for application contexts.
*/
public abstract class ApplicationContext implements Application.UncaughtExceptionHandler {
/**
* Native display host. This is the Pivot interface with AWT.
*/
public static class DisplayHost extends java.awt.Component {
private static final long serialVersionUID = -815713849595314026L;
private transient Display display = new Display(this);
private AWTEvent currentAWTEvent = null;
private Component focusedComponent = null;
private Point dragLocation = null;
private Component dragDescendant = null;
private transient Manifest dragManifest = null;
private DropAction userDropAction = null;
private Component dropDescendant = null;
private MenuPopup menuPopup = null;
private double scale = 1;
private boolean debugPaint = false;
private boolean bufferedImagePaintEnabled = true;
private BufferedImage bufferedImage = null;
private GraphicsConfiguration bufferedImageGC = null;
private boolean volatileImagePaintEnabled = true;
private VolatileImage volatileImage = null;
private GraphicsConfiguration volatileImageGC = null;
private Random random = null;
private transient DropTargetListener dropTargetListener = new DropTargetListener() {
@Override
public void dragEnter(DropTargetDragEvent event) {
if (dragDescendant != null) {
throw new IllegalStateException("Local drag already in progress.");
}
java.awt.Point location = event.getLocation();
dragLocation = new Point(location.x, location.y);
// Initialize drag state
dragManifest = new RemoteManifest(event.getTransferable());
// Initialize drop state
userDropAction = getDropAction(event.getDropAction());
// Notify drop target
dropDescendant = getDropDescendant(location.x, location.y);
DropAction dropAction = null;
if (dropDescendant != null) {
DropTarget dropTarget = dropDescendant.getDropTarget();
dropAction = dropTarget.dragEnter(dropDescendant, dragManifest,
getSupportedDropActions(event.getSourceActions()), userDropAction);
}
if (dropAction == null) {
event.rejectDrag();
} else {
event.acceptDrag(getNativeDropAction(dropAction));
}
display.validate();
}
@Override
public void dragExit(DropTargetEvent event) {
// Clear drag location and state
dragLocation = null;
dragManifest = null;
// Clear drop state
userDropAction = null;
if (dropDescendant != null) {
DropTarget dropTarget = dropDescendant.getDropTarget();
dropTarget.dragExit(dropDescendant);
}
dropDescendant = null;
display.validate();
}
@Override
public void dragOver(DropTargetDragEvent event) {
java.awt.Point location = event.getLocation();
// Get the previous and current drop descendant and call
// move or exit/enter as appropriate
Component previousDropDescendant = dropDescendant;
dropDescendant = getDropDescendant(location.x, location.y);
DropAction dropAction = null;
if (previousDropDescendant == dropDescendant) {
if (dropDescendant != null) {
DropTarget dropTarget = dropDescendant.getDropTarget();
Point dropLocation = dropDescendant.mapPointFromAncestor(display,
location.x, location.y);
if (dropLocation == null) {
dropLocation = display.getMouseLocation();
}
if (dropLocation != null) {
dropAction = dropTarget.dragMove(dropDescendant, dragManifest,
getSupportedDropActions(event.getSourceActions()),
dropLocation.x, dropLocation.y, userDropAction);
}
}
} else {
if (previousDropDescendant != null) {
DropTarget previousDropTarget = previousDropDescendant.getDropTarget();
previousDropTarget.dragExit(previousDropDescendant);
}
if (dropDescendant != null) {
DropTarget dropTarget = dropDescendant.getDropTarget();
dropAction = dropTarget.dragEnter(dropDescendant, dragManifest,
getSupportedDropActions(event.getSourceActions()), userDropAction);
}
}
// Update cursor
setCursor(getDropCursor(dropAction));
if (dropAction == null) {
event.rejectDrag();
} else {
event.acceptDrag(getNativeDropAction(dropAction));
}
display.validate();
}
@Override
public void dropActionChanged(DropTargetDragEvent event) {
userDropAction = getDropAction(event.getDropAction());
DropAction dropAction = null;
if (dropDescendant != null) {
java.awt.Point location = event.getLocation();
Point dropLocation = dropDescendant.mapPointFromAncestor(display, location.x,
location.y);
if (dropLocation == null) {
dropLocation = display.getMouseLocation();
}
if (dropLocation != null) {
DropTarget dropTarget = dropDescendant.getDropTarget();
dropAction = dropTarget.userDropActionChange(dropDescendant, dragManifest,
getSupportedDropActions(event.getSourceActions()),
dropLocation.x, dropLocation.y, userDropAction);
}
}
if (dropAction == null) {
event.rejectDrag();
} else {
event.acceptDrag(getNativeDropAction(dropAction));
}
display.validate();
}
@Override
public void drop(DropTargetDropEvent event) {
java.awt.Point location = event.getLocation();
dropDescendant = getDropDescendant(location.x, location.y);
DropAction dropAction = null;
if (dropDescendant != null) {
Point dropLocation = dropDescendant.mapPointFromAncestor(display, location.x,
location.y);
if (dropLocation == null) {
dropLocation = display.getMouseLocation();
}
DropTarget dropTarget = dropDescendant.getDropTarget();
// Simulate a user drop action change to get the current
// drop action
int supportedDropActions = getSupportedDropActions(event.getSourceActions());
if (dropLocation != null) {
dropAction = dropTarget.userDropActionChange(dropDescendant, dragManifest,
supportedDropActions, dropLocation.x, dropLocation.y, userDropAction);
if (dropAction != null) {
// Perform the drop
event.acceptDrop(getNativeDropAction(dropAction));
dropTarget.drop(dropDescendant, dragManifest, supportedDropActions,
dropLocation.x, dropLocation.y, userDropAction);
}
}
}
if (dropAction == null) {
event.rejectDrop();
}
event.dropComplete(true);
// Restore the cursor to the default
setCursor(java.awt.Cursor.getDefaultCursor());
// Clear drag state
dragManifest = null;
dragLocation = null;
// Clear drop state
dropDescendant = null;
display.validate();
}
};
private transient TextInputMethodListener textInputMethodListener = new TextInputMethodListener() {
private TextInputMethodListener getCurrentListener() {
Component focusedComponent = Component.getFocusedComponent();
if (focusedComponent != null) {
return focusedComponent.getTextInputMethodListener();
}
return null;
}
@Override
public AttributedCharacterIterator cancelLatestCommittedText(
AttributedCharacterIterator.Attribute[] attributes) {
TextInputMethodListener listener = getCurrentListener();
if (listener != null) {
return listener.cancelLatestCommittedText(attributes);
}
return null;
}
@Override
public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
AttributedCharacterIterator.Attribute[] attributes) {
TextInputMethodListener listener = getCurrentListener();
if (listener != null) {
return listener.getCommittedText(beginIndex, endIndex, attributes);
}
return null;
}
@Override
public int getCommittedTextLength() {
TextInputMethodListener listener = getCurrentListener();
if (listener != null) {
return listener.getCommittedTextLength();
}
return 0;
}
@Override
public int getInsertPositionOffset() {
TextInputMethodListener listener = getCurrentListener();
if (listener != null) {
return listener.getInsertPositionOffset();
}
return 0;
}
@Override
public TextHitInfo getLocationOffset(int x, int y) {
TextInputMethodListener listener = getCurrentListener();
if (listener != null) {
return listener.getLocationOffset(x, y);
}
return null;
}
@Override
public AttributedCharacterIterator getSelectedText(
AttributedCharacterIterator.Attribute[] attributes) {
TextInputMethodListener listener = getCurrentListener();
if (listener != null) {
return listener.getSelectedText(attributes);
}
return null;
}
@Override
public Rectangle getTextLocation(TextHitInfo offset) {
TextInputMethodListener listener = getCurrentListener();
if (listener != null) {
return listener.getTextLocation(offset);
}
return new Rectangle();
}
@Override
public void inputMethodTextChanged(InputMethodEvent event) {
TextInputMethodListener listener = getCurrentListener();
if (listener != null) {
listener.inputMethodTextChanged(event);
}
}
@Override
public void caretPositionChanged(InputMethodEvent event) {
TextInputMethodListener listener = getCurrentListener();
if (listener != null) {
listener.caretPositionChanged(event);
}
}
};
@Override
public InputMethodRequests getInputMethodRequests() {
return textInputMethodListener;
}
public DisplayHost() {
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);
enableInputMethods(true);
addInputMethodListener(textInputMethodListener);
try {
System.setProperty("sun.awt.noerasebackground", "true");
System.setProperty("sun.awt.erasebackgroundonresize", "false");
} catch (SecurityException exception) {
// No-op
}
try {
if (Boolean.getBoolean("org.apache.pivot.wtk.disablevolatilebuffer")) {
volatileImagePaintEnabled = false;
}
} catch (SecurityException ex) {
// No-op
}
try {
debugPaint = Boolean.getBoolean("org.apache.pivot.wtk.debugpaint");
if (debugPaint) {
random = new Random();
}
} catch (SecurityException ex) {
// No-op
}
try {
boolean debugFocus = Boolean.getBoolean("org.apache.pivot.wtk.debugfocus");
if (debugFocus) {
final Decorator focusDecorator = new ShadeDecorator(0.2f, Color.RED);
ComponentClassListener focusChangeListener = new ComponentClassListener() {
@Override
public void focusedComponentChanged(Component previousFocusedComponent) {
if (previousFocusedComponent != null
&& previousFocusedComponent.getDecorators().indexOf(focusDecorator) > -1) {
previousFocusedComponent.getDecorators().remove(focusDecorator);
}
Component focusedComponentLocal = Component.getFocusedComponent();
if (focusedComponentLocal != null
&& focusedComponentLocal.getDecorators().indexOf(focusDecorator) == -1) {
focusedComponentLocal.getDecorators().add(focusDecorator);
}
System.out.println("focusedComponentChanged():\n from = "
+ previousFocusedComponent + "\n to = " + focusedComponentLocal);
}
};
Component.getComponentClassListeners().add(focusChangeListener);
}
} catch (SecurityException ex) {
// No-op
}
// Add native drop support
@SuppressWarnings("unused")
java.awt.dnd.DropTarget dropTarget = new java.awt.dnd.DropTarget(this,
dropTargetListener);
setFocusTraversalKeysEnabled(false);
}
public Display getDisplay() {
return display;
}
public AWTEvent getCurrentAWTEvent() {
return currentAWTEvent;
}
/**
* @return The current scale (or zoom) factor for the entire application's
* display.
* @see #setScale
*/
public double getScale() {
return scale;
}
/**
* Use this method to scale up or down (that is zoom in or out)
* the entire application's display.
* <p> For the main application window, a {@link org.apache.pivot.wtk.effects.ScaleDecorator}
* cannot be used. This method (and related ones) must be used instead.
* @param scale The new scale (zoom) factor for the entire display.
* @see #scaleUp
* @see #scaleDown
* @see #getScale
*/
public void setScale(double scale) {
if (scale != this.scale) {
this.scale = scale;
display.setSize(Math.max((int) Math.ceil(getWidth() / scale), 0),
Math.max((int) Math.ceil(getHeight() / scale), 0));
display.repaint();
}
}
/**
* Use this method to zoom in to the application's main window
* (that is, make all the text and components look visually bigger).
* <p> The scale is increased in discrete steps for each call to
* this method: 1, 1.25, 1.5, 2.0, then whole integer values
* up to a maximum of 12.
* @see #setScale
* @see #scaleDown
* @see #getScale
*/
public void scaleUp() {
double newScale;
if (scale < 1) {
newScale = 1;
} else if (scale < 1.25) {
newScale = 1.25;
} else if (scale < 1.5) {
newScale = 1.5;
} else if (scale < 2) {
newScale = 2;
} else {
newScale = Math.min(Math.floor(scale) + 1, 12);
}
setScale(newScale);
}
/**
* Use this method to zoom out of the application's main window
* (that is, to make all the text and components visually smaller).
* <p> The scale is decreased in discrete steps for each call to
* this method: next whole integer down for values above 2.0, then
* 2.0, 1.5, 1.25, then finally 1.
* @see #setScale
* @see #scaleUp
* @see #getScale
*/
public void scaleDown() {
double newScale;
if (scale <= 1.25) {
newScale = 1;
} else if (scale <= 1.5) {
newScale = 1.25;
} else if (scale <= 2) {
newScale = 1.5;
} else {
newScale = Math.ceil(scale) - 1;
}
setScale(newScale);
}
/**
* Under some conditions, e.g. running under Linux in an applet,
* volatile buffering can reduce performance.
* @param enabled Whether or not to use volatile image painting.
*/
public void setVolatileImagePaintEnabled(boolean enabled) {
volatileImagePaintEnabled = enabled;
if (enabled) {
bufferedImage = null;
bufferedImageGC = null;
} else {
volatileImage = null;
volatileImageGC = null;
}
}
public void setBufferedImagePaintEnabled(boolean enabled) {
bufferedImagePaintEnabled = enabled;
if (!enabled) {
bufferedImage = null;
bufferedImageGC = null;
}
}
@Override
public void repaint(int x, int y, int width, int height) {
int xMutable = x;
int yMutable = y;
int widthMutable = width;
int heightMutable = 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 (xMutable < 0) {
widthMutable = Math.max(widthMutable + xMutable, 0);
xMutable = 0;
}
if (yMutable < 0) {
heightMutable = Math.max(heightMutable + yMutable, 0);
yMutable = 0;
}
if (widthMutable > 0 && heightMutable > 0) {
if (scale == 1) {
super.repaint(xMutable, yMutable, widthMutable, heightMutable);
} else {
super.repaint((int) Math.floor(xMutable * scale),
(int) Math.floor(yMutable * scale),
(int) Math.ceil(widthMutable * scale) + 1,
(int) Math.ceil(heightMutable * scale) + 1);
}
}
}
@Override
public void paint(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());
if (graphics instanceof PrintGraphics || graphics instanceof PrinterGraphics) {
print(graphics);
return;
}
java.awt.Rectangle clipBounds = graphics.getClipBounds();
if (clipBounds != null && !clipBounds.isEmpty()) {
try {
boolean bPaintSuccess = false;
if (volatileImagePaintEnabled) {
bPaintSuccess = paintVolatileBuffered((Graphics2D) graphics);
}
if (!bPaintSuccess && bufferedImagePaintEnabled) {
bPaintSuccess = paintBuffered((Graphics2D) graphics);
}
if (!bPaintSuccess) {
paintDisplay((Graphics2D) graphics);
}
if (debugPaint) {
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) {
System.err.println("Exception thrown during paint(): " + exception);
throw exception;
}
}
}
@Override
public void update(Graphics graphics) {
paint(graphics);
}
@Override
public void print(Graphics graphics) {
// TODO: verify if/how we have to re-scale output in this case ...
// 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()) {
try {
// When printing, there is no point in using offscreen
// buffers.
paintDisplay((Graphics2D) graphics);
} catch (RuntimeException exception) {
System.err.println("Exception thrown during print(): " + exception);
throw exception;
}
}
}
/**
* Attempts to paint the display using an offscreen buffer.
*
* @param graphics The source graphics context.
* @return <tt>true</tt> if the display was painted using the offscreen
* buffer; <tt>false</tt>, otherwise.
*/
private boolean paintBuffered(Graphics2D graphics) {
boolean painted = false;
// Paint the display into an offscreen buffer
GraphicsConfiguration gc = graphics.getDeviceConfiguration();
java.awt.Rectangle clipBounds = graphics.getClipBounds();
if (bufferedImage == null || bufferedImageGC != gc
|| bufferedImage.getWidth() < clipBounds.width
|| bufferedImage.getHeight() < clipBounds.height) {
bufferedImage = gc.createCompatibleImage(clipBounds.width, clipBounds.height,
Transparency.OPAQUE);
bufferedImageGC = gc;
}
if (bufferedImage != null) {
Graphics2D bufferedImageGraphics = (Graphics2D) bufferedImage.getGraphics();
bufferedImageGraphics.setClip(0, 0, clipBounds.width, clipBounds.height);
bufferedImageGraphics.translate(-clipBounds.x, -clipBounds.y);
try {
paintDisplay(bufferedImageGraphics);
graphics.drawImage(bufferedImage, clipBounds.x, clipBounds.y, this);
} finally {
bufferedImageGraphics.dispose();
}
painted = true;
}
return painted;
}
/**
* Attempts to paint the display using a volatile offscreen buffer.
*
* @param graphics The source graphics context.
* @return <tt>true</tt> if the display was painted using the offscreen
* buffer; <tt>false</tt>, otherwise.
*/
private boolean paintVolatileBuffered(Graphics2D graphics) {
boolean painted = false;
// Paint the display into a volatile offscreen buffer
GraphicsConfiguration gc = graphics.getDeviceConfiguration();
java.awt.Rectangle gcBounds = gc.getBounds();
if (volatileImage == null || volatileImageGC != gc) {
if (volatileImage != null) {
volatileImage.flush();
}
volatileImage = gc.createCompatibleVolatileImage(gcBounds.width, gcBounds.height,
Transparency.OPAQUE);
// we need to create a new volatile if the GC changes
volatileImageGC = gc;
}
// If we have a valid volatile image, attempt to paint the
// display to it
int valid = volatileImage.validate(gc);
if (valid == java.awt.image.VolatileImage.IMAGE_OK
|| valid == java.awt.image.VolatileImage.IMAGE_RESTORED) {
java.awt.Rectangle clipBounds = graphics.getClipBounds();
Graphics2D volatileImageGraphics = volatileImage.createGraphics();
volatileImageGraphics.setClip(clipBounds.x, clipBounds.y, clipBounds.width,
clipBounds.height);
try {
paintDisplay(volatileImageGraphics);
// this drawImage method doesn't use width and height
int x2 = clipBounds.x + clipBounds.width;
int y2 = clipBounds.y + clipBounds.height;
graphics.drawImage(volatileImage, clipBounds.x, clipBounds.y, x2, y2,
clipBounds.x, clipBounds.y, x2, y2, this);
} finally {
volatileImageGraphics.dispose();
}
painted = !volatileImage.contentsLost();
} else {
volatileImage.flush();
volatileImage = null;
}
return painted;
}
/**
* Paints the display including any decorators.
*
* @param graphics The graphics to paint into.
*/
private void paintDisplay(Graphics2D graphics) {
if (scale != 1) {
graphics.scale(scale, scale);
}
Graphics2D decoratedGraphics = graphics;
DecoratorSequence decorators = display.getDecorators();
int n = decorators.getLength();
for (int i = n - 1; i >= 0; i--) {
Decorator decorator = decorators.get(i);
decoratedGraphics = decorator.prepare(display, decoratedGraphics);
}
graphics.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_SPEED);
display.paint(graphics);
for (int i = 0; i < n; i++) {
Decorator decorator = decorators.get(i);
decorator.update();
}
// Paint the drag visual
if (dragDescendant != null) {
DragSource dragSource = dragDescendant.getDragSource();
Visual dragRepresentation = dragSource.getRepresentation();
if (dragRepresentation != null) {
Point dragOffset = dragSource.getOffset();
int tx = dragLocation.x - dragOffset.x;
int ty = dragLocation.y - dragOffset.y;
graphics.translate(tx, ty);
dragRepresentation.paint(graphics);
}
}
}
private void repaintDragRepresentation() {
DragSource dragSource = dragDescendant.getDragSource();
Visual dragRepresentation = dragSource.getRepresentation();
if (dragRepresentation != null) {
Point dragOffset = dragSource.getOffset();
repaint(dragLocation.x - dragOffset.x, dragLocation.y - dragOffset.y,
dragRepresentation.getWidth(), dragRepresentation.getHeight());
}
}
private Component getDropDescendant(int x, int y) {
Component dropDescendantLocal = display.getDescendantAt(x, y);
while (dropDescendantLocal != null && dropDescendantLocal.getDropTarget() == null) {
dropDescendantLocal = dropDescendantLocal.getParent();
}
if (dropDescendantLocal != null && dropDescendantLocal.isBlocked()) {
dropDescendantLocal = null;
}
return dropDescendantLocal;
}
private void startNativeDrag(final DragSource dragSource,
final Component dragDescendantArgument, final MouseEvent mouseEvent) {
java.awt.dnd.DragSource awtDragSource = java.awt.dnd.DragSource.getDefaultDragSource();
final int supportedDropActions = dragSource.getSupportedDropActions();
DragGestureRecognizer dragGestureRecognizer = new DragGestureRecognizer(
java.awt.dnd.DragSource.getDefaultDragSource(), DisplayHost.this) {
private static final long serialVersionUID = -3204487375572082596L;
{
appendEvent(mouseEvent);
}
@Override
public synchronized int getSourceActions() {
int awtSourceActions = 0;
if (DropAction.COPY.isSelected(supportedDropActions)) {
awtSourceActions |= DnDConstants.ACTION_COPY;
}
if (DropAction.MOVE.isSelected(supportedDropActions)) {
awtSourceActions |= DnDConstants.ACTION_MOVE;
}
if (DropAction.LINK.isSelected(supportedDropActions)) {
awtSourceActions |= DnDConstants.ACTION_LINK;
}
return awtSourceActions;
}
@Override
protected void registerListeners() {
// No-op
}
@Override
protected void unregisterListeners() {
// No-op
}
};
java.util.List<InputEvent> inputEvents = new java.util.ArrayList<>();
inputEvents.add(mouseEvent);
// TODO If current user drop action is supported by drag source, use
// it
// as initial action - otherwise, select MOVE, COPY, LINK in that
// order
java.awt.Point location = new java.awt.Point(mouseEvent.getX(), mouseEvent.getY());
DragGestureEvent trigger = new DragGestureEvent(dragGestureRecognizer,
DnDConstants.ACTION_MOVE, location, inputEvents);
LocalManifest dragContent = dragSource.getContent();
LocalManifestAdapter localManifestAdapter = new LocalManifestAdapter(dragContent);
awtDragSource.startDrag(trigger, java.awt.Cursor.getDefaultCursor(), null, null,
localManifestAdapter, new DragSourceListener() {
@Override
public void dragEnter(DragSourceDragEvent event) {
DragSourceContext context = event.getDragSourceContext();
context.setCursor(getDropCursor(getDropAction(event.getDropAction())));
}
@Override
public void dragExit(DragSourceEvent event) {
DragSourceContext context = event.getDragSourceContext();
context.setCursor(java.awt.Cursor.getDefaultCursor());
}
@Override
public void dragOver(DragSourceDragEvent event) {
DragSourceContext context = event.getDragSourceContext();
context.setCursor(getDropCursor(getDropAction(event.getDropAction())));
}
@Override
public void dropActionChanged(DragSourceDragEvent event) {
DragSourceContext context = event.getDragSourceContext();
context.setCursor(getDropCursor(getDropAction(event.getDropAction())));
}
@Override
public void dragDropEnd(DragSourceDropEvent event) {
DragSourceContext context = event.getDragSourceContext();
context.setCursor(java.awt.Cursor.getDefaultCursor());
dragSource.endDrag(dragDescendantArgument,
getDropAction(event.getDropAction()));
}
});
}
@Override
protected void processEvent(AWTEvent event) {
currentAWTEvent = event;
super.processEvent(event);
currentAWTEvent = null;
display.validate();
}
@Override
protected void processComponentEvent(ComponentEvent event) {
super.processComponentEvent(event);
switch (event.getID()) {
case ComponentEvent.COMPONENT_RESIZED:
if (scale == 1) {
display.setSize(Math.max(getWidth(), 0), Math.max(getHeight(), 0));
} else {
display.setSize(Math.max((int) Math.ceil(getWidth() / scale), 0),
Math.max((int) Math.ceil(getHeight() / scale), 0));
}
break;
default:
break;
}
}
@Override
protected void processFocusEvent(FocusEvent event) {
super.processFocusEvent(event);
switch (event.getID()) {
case FocusEvent.FOCUS_GAINED:
if (focusedComponent != null && focusedComponent.isShowing()
&& !focusedComponent.isBlocked()) {
focusedComponent.requestFocus();
}
break;
case FocusEvent.FOCUS_LOST:
focusedComponent = Component.getFocusedComponent();
Component.clearFocus();
break;
default:
break;
}
}
@Override
protected void processInputMethodEvent(InputMethodEvent event) {
super.processInputMethodEvent(event);
}
@Override
protected void processMouseEvent(MouseEvent event) {
super.processMouseEvent(event);
int x = (int) Math.round(event.getX() / scale);
int y = (int) Math.round(event.getY() / scale);
// Set the mouse button state
int mouseButtons = 0x00;
int modifiersEx = event.getModifiersEx();
if ((modifiersEx & InputEvent.BUTTON1_DOWN_MASK) > 0) {
mouseButtons |= Mouse.Button.LEFT.getMask();
}
if ((modifiersEx & InputEvent.BUTTON2_DOWN_MASK) > 0) {
mouseButtons |= Mouse.Button.MIDDLE.getMask();
}
if ((modifiersEx & InputEvent.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;
default:
break;
}
// Process the event
int eventID = event.getID();
if (eventID == MouseEvent.MOUSE_ENTERED || eventID == MouseEvent.MOUSE_EXITED) {
try {
switch (eventID) {
case MouseEvent.MOUSE_ENTERED:
display.mouseOver();
break;
case MouseEvent.MOUSE_EXITED:
display.mouseOut();
break;
default:
break;
}
} catch (Throwable exception) {
handleUncaughtException(exception);
}
} else {
// Determine the mouse owner
Component mouseOwner;
Component mouseCapturer = Mouse.getCapturer();
if (mouseCapturer == null) {
mouseOwner = display;
} else {
mouseOwner = mouseCapturer;
Point location = mouseOwner.mapPointFromAncestor(display, x, y);
if (location == null) {
location = display.getMouseLocation();
}
if (location != null) {
x = location.x;
y = location.y;
}
}
// Delegate the event to the owner
try {
boolean consumed;
switch (eventID) {
case MouseEvent.MOUSE_PRESSED:
requestFocus();
requestFocusInWindow();
consumed = mouseOwner.mouseDown(button, x, y);
if (button == Mouse.Button.LEFT) {
dragLocation = new Point(x, y);
} else if (menuPopup == null && button == Mouse.Button.RIGHT
&& !consumed) {
// Instantiate a context menu
Menu menu = new Menu();
// Allow menu handlers to configure the menu
Component component = mouseOwner;
int componentX = x;
int componentY = y;
do {
MenuHandler menuHandler = component.getMenuHandler();
if (menuHandler != null) {
if (menuHandler.configureContextMenu(component, menu,
componentX, componentY)) {
// Stop propagation
break;
}
}
if (component instanceof Container) {
Container container = (Container) component;
component = container.getComponentAt(componentX, componentY);
if (component != null) {
componentX -= component.getX();
componentY -= component.getY();
}
} else {
component = null;
}
} while (component != null && component.isEnabled());
// Show the context menu if it contains any sections
if (menu.getSections().getLength() > 0) {
menuPopup = new MenuPopup(menu);
menuPopup.getWindowStateListeners().add(
new WindowStateListener() {
@Override
public void windowClosed(Window window,
Display displayArgument, Window owner) {
menuPopup.getMenu().getSections().clear();
menuPopup = null;
window.getWindowStateListeners().remove(this);
}
});
Window window = null;
if (mouseOwner == display) {
window = (Window) display.getComponentAt(x, y);
} else {
window = mouseOwner.getWindow();
}
Display displayLocal = window.getDisplay();
Point location = mouseOwner.mapPointToAncestor(displayLocal, x,
y);
menuPopup.open(window, location);
}
}
if (consumed) {
event.consume();
}
break;
case MouseEvent.MOUSE_RELEASED:
if (dragDescendant == null) {
consumed = mouseOwner.mouseUp(button, x, y);
if (consumed) {
event.consume();
}
} else {
DragSource dragSource = dragDescendant.getDragSource();
repaintDragRepresentation();
if (dropDescendant == null) {
dragSource.endDrag(dragDescendant, null);
} else {
DropTarget dropTarget = dropDescendant.getDropTarget();
DropAction dropAction = dropTarget.drop(dropDescendant,
dragManifest, dragSource.getSupportedDropActions(), x, y,
getUserDropAction(event));
dragSource.endDrag(dragDescendant, dropAction);
}
setCursor(java.awt.Cursor.getDefaultCursor());
// Clear the drag state
dragDescendant = null;
dragManifest = null;
// Clear the drop state
userDropAction = null;
dropDescendant = null;
}
// Clear the drag location
dragLocation = null;
break;
default:
break;
}
} catch (Throwable exception) {
handleUncaughtException(exception);
}
}
}
@Override
protected void processMouseMotionEvent(MouseEvent event) {
super.processMouseMotionEvent(event);
int x = (int) Math.round(event.getX() / scale);
int y = (int) Math.round(event.getY() / scale);
// Process the event
try {
switch (event.getID()) {
case MouseEvent.MOUSE_MOVED:
case MouseEvent.MOUSE_DRAGGED:
if (dragDescendant == null) {
// A drag is not active
Component mouseCapturer = Mouse.getCapturer();
if (mouseCapturer == null) {
// The mouse is not captured, so propagate the
// event to the display
if (!display.isMouseOver()) {
display.mouseOver();
}
display.mouseMove(x, y);
int dragThreshold = Platform.getDragThreshold();
if (dragLocation != null
&& (Math.abs(x - dragLocation.x) > dragThreshold
|| Math.abs(y - dragLocation.y) > dragThreshold)) {
// The user has dragged the mouse past the drag threshold; try
// to find a drag source
dragDescendant = display.getDescendantAt(dragLocation.x,
dragLocation.y);
while (dragDescendant != null
&& dragDescendant.getDragSource() == null) {
dragDescendant = dragDescendant.getParent();
}
if (dragDescendant == null || dragDescendant.isBlocked()) {
// There was nothing to drag, so clear the drag location
dragDescendant = null;
dragLocation = null;
} else {
DragSource dragSource = dragDescendant.getDragSource();
dragLocation = dragDescendant.mapPointFromAncestor(display, x, y);
if (dragLocation == null) {
dragLocation = display.getMouseLocation();
}
if (dragLocation != null) {
if (dragSource.beginDrag(dragDescendant,
dragLocation.x, dragLocation.y)) {
// A drag has started
if (dragSource.isNative()) {
startNativeDrag(dragSource, dragDescendant, event);
// Clear the drag state since it is not used for
// native drags
dragDescendant = null;
dragLocation = null;
} else {
if (dragSource.getRepresentation() != null
&& dragSource.getOffset() == null) {
throw new IllegalStateException(
"Drag offset is required when a "
+ " representation is specified.");
}
if (display.isMouseOver()) {
display.mouseOut();
}
// Get the drag content
dragManifest = dragSource.getContent();
// Get the initial user drop action
userDropAction = getUserDropAction(event);
// Repaint the drag visual
dragLocation = new Point(x, y);
repaintDragRepresentation();
}
} else {
// Clear the drag state
dragDescendant = null;
dragLocation = null;
}
}
}
}
} else {
// Delegate the event to the capturer
Point location = mouseCapturer.mapPointFromAncestor(display, x, y);
if (location == null) {
location = display.getMouseLocation();
}
if (location != null) {
boolean consumed = mouseCapturer.mouseMove(location.x, location.y);
if (consumed) {
event.consume();
}
}
}
} else {
if (dragLocation != null) {
DragSource dragSource = dragDescendant.getDragSource();
// Get the previous and current drop descendant and call
// move or exit/enter as appropriate
Component previousDropDescendant = dropDescendant;
dropDescendant = getDropDescendant(x, y);
DropAction dropAction = null;
if (previousDropDescendant == dropDescendant) {
if (dropDescendant != null) {
DropTarget dropTarget = dropDescendant.getDropTarget();
Point dropLocation = dropDescendant.mapPointFromAncestor(
display, x, y);
if (dropLocation == null) {
dropLocation = display.getMouseLocation();
}
if (dropLocation != null) {
dropAction = dropTarget.dragMove(dropDescendant, dragManifest,
dragSource.getSupportedDropActions(),
dropLocation.x, dropLocation.y, userDropAction);
}
}
} else {
if (previousDropDescendant != null) {
DropTarget previousDropTarget = previousDropDescendant.getDropTarget();
previousDropTarget.dragExit(previousDropDescendant);
}
if (dropDescendant != null) {
DropTarget dropTarget = dropDescendant.getDropTarget();
dropAction = dropTarget.dragEnter(dropDescendant,
dragManifest, dragSource.getSupportedDropActions(),
userDropAction);
}
}
// Update cursor
setCursor(getDropCursor(dropAction));
// Repaint the drag visual
repaintDragRepresentation();
dragLocation = new Point(x, y);
repaintDragRepresentation();
}
}
break;
default:
break;
}
} catch (Throwable exception) {
handleUncaughtException(exception);
}
}
@Override
protected void processMouseWheelEvent(MouseWheelEvent event) {
super.processMouseWheelEvent(event);
// Get the event coordinates
int x = (int) Math.round(event.getX() / scale);
int y = (int) Math.round(event.getY() / scale);
// 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;
default:
break;
}
// Process the event
try {
switch (event.getID()) {
case MouseEvent.MOUSE_WHEEL:
if (Keyboard.isPressed(Keyboard.Modifier.CTRL)
&& Keyboard.isPressed(Keyboard.Modifier.SHIFT)) {
// Mouse wheel scaling
if (event.getWheelRotation() < 0) {
scaleUp();
} else {
scaleDown();
}
} else if (dragDescendant == null) {
// Determine the mouse owner
Component mouseOwner;
Component mouseCapturer = Mouse.getCapturer();
if (mouseCapturer == null) {
mouseOwner = display;
} else {
mouseOwner = mouseCapturer;
Point location = mouseOwner.mapPointFromAncestor(display, x, y);
if (location == null) {
location = display.getMouseLocation();
}
if (location != null) {
x = location.x;
y = location.y;
}
}
// Delegate the event to the owner
boolean consumed = mouseOwner.mouseWheel(scrollType,
event.getScrollAmount(), event.getWheelRotation(), x, y);
if (consumed) {
event.consume();
}
}
break;
default:
break;
}
} catch (Throwable exception) {
handleUncaughtException(exception);
}
}
@Override
protected void processKeyEvent(KeyEvent event) {
super.processKeyEvent(event);
int modifiersEx = event.getModifiersEx();
int awtKeyLocation = event.getKeyLocation();
// Set the keyboard modifier state
int keyboardModifiers = 0;
if ((modifiersEx & InputEvent.SHIFT_DOWN_MASK) > 0) {
keyboardModifiers |= Keyboard.Modifier.SHIFT.getMask();
}
// Ignore Control when Alt-Graphics is pressed
if ((modifiersEx & InputEvent.CTRL_DOWN_MASK) > 0
&& ((modifiersEx & InputEvent.ALT_DOWN_MASK) == 0
|| awtKeyLocation == KeyEvent.KEY_LOCATION_RIGHT)) {
keyboardModifiers |= Keyboard.Modifier.CTRL.getMask();
}
if ((modifiersEx & InputEvent.ALT_DOWN_MASK) > 0) {
keyboardModifiers |= Keyboard.Modifier.ALT.getMask();
}
if ((modifiersEx & InputEvent.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;
default:
break;
}
if (dragDescendant == null) {
// Process the event
Component focusedComponentLocal = Component.getFocusedComponent();
boolean consumed = false;
int keyCode;
switch (event.getID()) {
case KeyEvent.KEY_PRESSED:
keyCode = event.getKeyCode();
if (Keyboard.isPressed(Keyboard.Modifier.CTRL)
&& Keyboard.isPressed(Keyboard.Modifier.SHIFT)) {
if (keyCode == Keyboard.KeyCode.PLUS
|| keyCode == Keyboard.KeyCode.EQUALS
|| keyCode == Keyboard.KeyCode.ADD) {
scaleUp();
} else if (keyCode == Keyboard.KeyCode.MINUS
|| keyCode == Keyboard.KeyCode.SUBTRACT) {
scaleDown();
}
}
try {
if (focusedComponentLocal == null) {
for (Application application : applications) {
if (application instanceof Application.UnprocessedKeyHandler) {
Application.UnprocessedKeyHandler unprocessedKeyHandler =
(Application.UnprocessedKeyHandler) application;
unprocessedKeyHandler.keyPressed(keyCode, keyLocation);
}
}
} else {
if (!focusedComponentLocal.isBlocked()) {
consumed = focusedComponentLocal.keyPressed(keyCode,
keyLocation);
}
}
} catch (Throwable exception) {
handleUncaughtException(exception);
}
if (consumed) {
event.consume();
}
break;
case KeyEvent.KEY_RELEASED:
keyCode = event.getKeyCode();
try {
if (focusedComponentLocal == null) {
for (Application application : applications) {
if (application instanceof Application.UnprocessedKeyHandler) {
Application.UnprocessedKeyHandler unprocessedKeyHandler =
(Application.UnprocessedKeyHandler) application;
unprocessedKeyHandler.keyReleased(keyCode, keyLocation);
}
}
} else {
if (!focusedComponentLocal.isBlocked()) {
consumed = focusedComponentLocal.keyReleased(keyCode,
keyLocation);
}
}
} catch (Throwable exception) {
handleUncaughtException(exception);
}
if (consumed) {
event.consume();
}
break;
case KeyEvent.KEY_TYPED:
char keyChar = event.getKeyChar();
try {
if (focusedComponentLocal == null) {
for (Application application : applications) {
if (application instanceof Application.UnprocessedKeyHandler) {
Application.UnprocessedKeyHandler unprocessedKeyHandler =
(Application.UnprocessedKeyHandler) application;
unprocessedKeyHandler.keyTyped(keyChar);
}
}
} else {
if (!focusedComponentLocal.isBlocked()) {
consumed = focusedComponentLocal.keyTyped(keyChar);
}
}
} catch (Throwable exception) {
handleUncaughtException(exception);
}
if (consumed) {
event.consume();
}
break;
default:
break;
}
} else {
DragSource dragSource = dragDescendant.getDragSource();
// If the user drop action changed, notify the drop descendant
if (dropDescendant != null) {
DropAction previousUserDropAction = userDropAction;
userDropAction = getUserDropAction(event);
if (previousUserDropAction != userDropAction) {
DropTarget dropTarget = dropDescendant.getDropTarget();
Point dropLocation = dragLocation;
if (dropLocation != null) {
dropLocation = dropDescendant.mapPointFromAncestor(display,
dropLocation.x, dropLocation.y);
}
if (dropLocation == null) {
dropLocation = display.getMouseLocation();
}
if (dropLocation != null) {
dropTarget.userDropActionChange(dropDescendant, dragManifest,
dragSource.getSupportedDropActions(),
dropLocation.x, dropLocation.y, userDropAction);
}
}
}
}
}
}
/**
* Resource cache dictionary implementation. <p> Note that this
* implementation does not have a way to limiting the number of elements to
* contain, so the cache continues to grow; to keep it small you have to
* manually remove old elements from it when they are no more necessary.
*/
public static final class ResourceCacheDictionary implements Dictionary<URL, Object>,
Iterable<URL> {
private ResourceCacheDictionary() {
}
@Override
public synchronized Object get(URL key) {
try {
return resourceCache.get(key.toURI());
} catch (URISyntaxException exception) {
throw new RuntimeException(exception);
}
}
@Override
public synchronized Object put(URL key, Object value) {
try {
return resourceCache.put(key.toURI(), value);
} catch (URISyntaxException exception) {
throw new RuntimeException(exception);
}
}
@Override
public synchronized Object remove(URL key) {
try {
return resourceCache.remove(key.toURI());
} catch (URISyntaxException exception) {
throw new RuntimeException(exception);
}
}
@Override
public synchronized boolean containsKey(URL key) {
try {
return resourceCache.containsKey(key.toURI());
} catch (URISyntaxException exception) {
throw new RuntimeException(exception);
}
}
@Override
public Iterator<URL> iterator() {
return new Iterator<URL>() {
private Iterator<URI> iterator = resourceCache.iterator();
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public URL next() {
try {
return iterator.next().toURL();
} catch (MalformedURLException exception) {
throw new RuntimeException(exception);
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public synchronized int getCount() {
return resourceCache.getCount();
}
}
/**
* Class representing a scheduled callback.
*/
public static final class ScheduledCallback extends TimerTask {
private Runnable callback;
private QueuedCallback queuedCallback = null;
private ScheduledCallback(Runnable callback) {
this.callback = callback;
}
@Override
public void run() {
if (queuedCallback != null) {
queuedCallback.cancel();
}
queuedCallback = queueCallback(callback);
}
@Override
public boolean cancel() {
if (queuedCallback != null) {
queuedCallback.cancel();
}
return super.cancel();
}
}
/**
* Class representing a queued callback.
*/
public static final class QueuedCallback implements Runnable {
private Runnable callback;
private volatile boolean executed = false;
private volatile boolean cancelled = false;
private QueuedCallback(Runnable callback) {
this.callback = callback;
}
@Override
public void run() {
if (!cancelled) {
try {
callback.run();
} catch (Throwable exception) {
exception.printStackTrace();
handleUncaughtException(exception);
}
for (Display display : displays) {
display.validate();
}
executed = true;
}
}
public boolean cancel() {
cancelled = true;
return (!executed);
}
}
/**
* Added so that any unexpected version string formats that might cause an error
* will not also cause the application to fail to start.
*
* @param versionString A potential version string to parse/decode.
* @return The parsed version information (if possible), or an empty version
* (that will look like: "0.0.0_00") if there was a parsing problem of any kind.
*/
private static Version safelyDecodeVersion(String versionString) {
if (!Utils.isNullOrEmpty(versionString)) {
try {
return Version.decode(versionString);
} catch (Throwable ex) {
String exMsg = Utils.isNullOrEmpty(ex.getMessage())
? ex.getClass().getSimpleName()
: ex.getMessage();
System.err.println("Error decoding version string \"" + versionString + "\": " + exMsg);
}
}
return new Version(0, 0, 0, 0);
}
protected static URL origin = null;
protected static ArrayList<Display> displays = new ArrayList<>();
protected static ArrayList<Application> applications = new ArrayList<>();
private static Timer timer = null;
private static HashMap<URI, Object> resourceCache = new HashMap<>();
private static ResourceCacheDictionary resourceCacheDictionary = new ResourceCacheDictionary();
private static final Package CURRENT_PACKAGE = ApplicationContext.class.getPackage();
private static Version jvmVersion = null;
private static Version javaVersion = null;
private static Version pivotVersion = null;
static {
// Get the JVM & Java runtime versions
jvmVersion = safelyDecodeVersion(System.getProperty("java.vm.version"));
javaVersion = safelyDecodeVersion(System.getProperty("java.runtime.version"));
// Get the Pivot version
pivotVersion = safelyDecodeVersion(CURRENT_PACKAGE.getImplementationVersion());
}
/**
* Returns this application's origin (the URL of it's originating server).
*
* @return The application's origin, or <tt>null</tt> if the origin cannot
* be determined.
*/
public static URL getOrigin() {
return origin;
}
/**
* @return The dictionary of cached resources.
*/
public static ResourceCacheDictionary getResourceCache() {
return resourceCacheDictionary;
}
/**
* Adds the styles from a named stylesheet to the named or typed style
* collections.
* <p> Does not allow macros (standard behavior) which can also be 25x
* faster than allowing macros.
*
* @param resourceName The resource name of the stylesheet to apply.
*/
public static void applyStylesheet(String resourceName) {
applyStylesheet(resourceName, false);
}
/**
* Adds the styles from a named stylesheet to the named or typed style
* collections.
*
* @param resourceName The resource name of the stylesheet to apply.
* @param allowMacros Whether or not there will be macros in the stylesheet.
*/
@SuppressWarnings("unchecked")
public static void applyStylesheet(String resourceName, boolean allowMacros) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL stylesheetLocation = classLoader.getResource(resourceName.substring(1));
if (stylesheetLocation == null) {
throw new RuntimeException("Unable to locate style sheet resource \"" + resourceName + "\".");
}
try (InputStream inputStream = stylesheetLocation.openStream()) {
JSONSerializer serializer = new JSONSerializer();
serializer.setAllowMacros(allowMacros);
Map<String, ?> stylesheet = (Map<String, ?>) serializer.readObject(inputStream);
for (String name : stylesheet) {
Map<String, ?> styles = (Map<String, ?>) stylesheet.get(name);
int i = name.lastIndexOf('.') + 1;
if (Character.isUpperCase(name.charAt(i))) {
// Assume the current package if none specified
if (!name.contains(".")) {
name = CURRENT_PACKAGE.getName() + "." + name;
}
Class<?> type = null;
try {
type = Class.forName(name);
} catch (ClassNotFoundException exception) {
// No-op
}
if (type != null && Component.class.isAssignableFrom(type)) {
Component.getTypedStyles().put((Class<? extends Component>) type, styles);
}
} else {
Component.getNamedStyles().put(name, styles);
}
}
} catch (IOException exception) {
throw new RuntimeException(exception);
} catch (SerializationException exception) {
throw new RuntimeException(exception);
}
}
/**
* Returns the current JVM version, parsed from the "java.vm.version" system
* property.
*
* @return The current JVM version, or an "empty" version if it can't be
* determined (that is, "0.0.0_00").
*/
public static Version getJVMVersion() {
return jvmVersion;
}
/**
* Returns the current Java Runtime version, parsed from the "java.runtime.version"
* system property.
*
* @return The current Java version, or an "empty" version if it can't be
* determined (that is, "0.0.0_00").
*/
public static Version getJavaVersion() {
return javaVersion;
}
/**
* Returns the current Pivot version.
*
* @return The current Pivot version (determined at build time), or
* an "empty" version if it can't be determined (that is, "0.0.0_00").
*/
public static Version getPivotVersion() {
return pivotVersion;
}
/**
* Schedules a task for one-time execution. The task will be executed on the
* UI thread.
*
* @param callback The task to execute.
* @param delay The length of time to wait before executing the task (in
* milliseconds).
* @return The callback object.
*/
public static ScheduledCallback scheduleCallback(Runnable callback, long delay) {
ScheduledCallback scheduledCallback = new ScheduledCallback(callback);
// TODO This is a workaround for a potential OS X bug; revisit
try {
try {
timer.schedule(scheduledCallback, delay);
} catch (IllegalStateException exception) {
createTimer();
timer.schedule(scheduledCallback, delay);
}
} catch (Throwable throwable) {
System.err.println("Unable to schedule callback: " + throwable);
}
return scheduledCallback;
}
/**
* Schedules a task for repeated execution. The task will be executed on the
* UI thread and will begin executing immediately.
*
* @param callback The task to execute.
* @param period The interval at which the task will be repeated (in
* milliseconds).
* @return The callback object.
*/
public static ScheduledCallback scheduleRecurringCallback(Runnable callback, long period) {
return scheduleRecurringCallback(callback, 0, period);
}
/**
* Schedules a task for repeated execution. The task will be executed on the
* UI thread.
*
* @param callback The task to execute.
* @param delay The length of time to wait before the first execution of the
* task (milliseconds).
* @param period The interval at which the task will be repeated (also in
* milliseconds).
* @return The callback object.
*/
public static ScheduledCallback scheduleRecurringCallback(Runnable callback, long delay,
long period) {
ScheduledCallback scheduledCallback = new ScheduledCallback(callback);
// TODO This is a workaround for a potential OS X bug; revisit
try {
try {
timer.schedule(scheduledCallback, delay, period);
} catch (IllegalStateException exception) {
createTimer();
timer.schedule(scheduledCallback, delay, period);
}
} catch (Throwable throwable) {
System.err.println("Unable to schedule callback: " + throwable);
}
return scheduledCallback;
}
/**
* Runs a task and then schedules it for repeated execution.
* The task will be executed on the UI thread and will begin
* executing immediately.
*
* @param callback The task to execute.
* @param period The interval at which the task will be repeated (in
* milliseconds).
* @return The callback object.
*/
public static ScheduledCallback runAndScheduleRecurringCallback(Runnable callback, long period) {
return runAndScheduleRecurringCallback(callback, 0, period);
}
/**
* Runs a task once and then schedules it for repeated execution. The task will be executed on the
* UI thread. This is a common pattern for caret blink, scrolling, etc. to have an immediate
* effect, with recurring execution after that.
*
* @param callback The task to execute.
* @param delay The length of time to wait before the next execution of the
* task (milliseconds).
* @param period The interval at which the task will be repeated (also in
* milliseconds).
* @return The callback object.
*/
public static ScheduledCallback runAndScheduleRecurringCallback(Runnable callback, long delay,
long period) {
ScheduledCallback scheduledCallback = new ScheduledCallback(callback);
// TODO This is a workaround for a potential OS X bug; revisit
try {
try {
timer.schedule(scheduledCallback, delay, period);
} catch (IllegalStateException exception) {
createTimer();
timer.schedule(scheduledCallback, delay, period);
}
} catch (Throwable throwable) {
System.err.println("Unable to schedule callback: " + throwable);
}
// Before returning, run the task once to start things off
callback.run();
return scheduledCallback;
}
/**
* Queues a task to execute after all pending events have been processed and
* returns without waiting for the task to complete.
*
* @param callback The task to execute.
* @return The callback object (used to manipulate or wait for the task).
*/
public static QueuedCallback queueCallback(Runnable callback) {
return queueCallback(callback, false);
}
/**
* Queues a task to execute after all pending events have been processed and
* optionally waits for the task to complete.
*
* @param callback The task to execute.
* @param wait If <tt>true</tt>, does not return until the task has
* executed. Otherwise, returns immediately.
* @return The callback object (used to manipulate or wait for the task).
*/
public static QueuedCallback queueCallback(Runnable callback, boolean wait) {
QueuedCallback queuedCallback = new QueuedCallback(callback);
// TODO This is a workaround for a potential OS X bug; revisit
try {
if (wait) {
try {
java.awt.EventQueue.invokeAndWait(queuedCallback);
} catch (InvocationTargetException exception) {
throw new RuntimeException(exception.getCause());
} catch (InterruptedException exception) {
throw new RuntimeException(exception);
}
} else {
java.awt.EventQueue.invokeLater(queuedCallback);
}
} catch (Throwable throwable) {
System.err.println("Unable to queue callback: " + throwable);
}
return queuedCallback;
}
protected static void createTimer() {
timer = new Timer();
}
protected static void destroyTimer() {
timer.cancel();
timer = null;
}
public static List<Display> getDisplays() {
return displays;
}
protected static void invalidateDisplays() {
for (Display display : displays) {
display.invalidate();
}
}
private static DropAction getUserDropAction(InputEvent event) {
DropAction userDropAction;
if ((event.isControlDown() && event.isShiftDown())
|| (event.isAltDown() && event.isMetaDown())) {
userDropAction = DropAction.LINK;
} else if (event.isControlDown() || (event.isAltDown())) {
userDropAction = DropAction.COPY;
} else if (event.isShiftDown()) {
userDropAction = DropAction.MOVE;
} else {
userDropAction = null;
}
return userDropAction;
}
private static DropAction getDropAction(int nativeDropAction) {
DropAction dropAction = null;
switch (nativeDropAction) {
case DnDConstants.ACTION_COPY:
dropAction = DropAction.COPY;
break;
case DnDConstants.ACTION_MOVE:
dropAction = DropAction.MOVE;
break;
case DnDConstants.ACTION_LINK:
dropAction = DropAction.LINK;
break;
default:
break;
}
return dropAction;
}
private static int getSupportedDropActions(int sourceActions) {
int dropActions = 0;
if ((sourceActions & DnDConstants.ACTION_COPY) > 0) {
dropActions |= DropAction.COPY.getMask();
}
if ((sourceActions & DnDConstants.ACTION_MOVE) > 0) {
dropActions |= DropAction.MOVE.getMask();
}
if ((sourceActions & DnDConstants.ACTION_LINK) > 0) {
dropActions |= DropAction.LINK.getMask();
}
return dropActions;
}
private static int getNativeDropAction(DropAction dropAction) {
int nativeDropAction = 0;
if (dropAction != null) {
switch (dropAction) {
case COPY:
nativeDropAction = DnDConstants.ACTION_COPY;
break;
case MOVE:
nativeDropAction = DnDConstants.ACTION_MOVE;
break;
case LINK:
nativeDropAction = DnDConstants.ACTION_LINK;
break;
default:
break;
}
}
return nativeDropAction;
}
private static java.awt.Cursor getDropCursor(DropAction dropAction) {
// Update the drop cursor
java.awt.Cursor cursor = java.awt.Cursor.getDefaultCursor();
if (dropAction != null) {
// Show the cursor for the drop action returned by the
// drop target
switch (dropAction) {
case COPY:
cursor = java.awt.dnd.DragSource.DefaultCopyDrop;
break;
case MOVE:
cursor = java.awt.dnd.DragSource.DefaultMoveDrop;
break;
case LINK:
cursor = java.awt.dnd.DragSource.DefaultLinkDrop;
break;
default:
break;
}
}
return cursor;
}
@Override
public void uncaughtException(Thread thread, Throwable exception) {
handleUncaughtException(thread, exception);
}
public static void defaultUncaughtExceptionHandler(Thread thread, Throwable exception) {
exception.printStackTrace();
Display display = (displays.getLength() > 0) ? displays.get(0) : null;
if (display == null) {
return;
}
String message = String.format("%1$s on Thread %2$s:",
exception.getClass().getName(), thread.getName());
TextArea body = null;
String bodyText = exception.getMessage();
if (bodyText != null && bodyText.length() > 0) {
body = new TextArea();
body.setText(bodyText);
body.setEditable(false);
}
Alert alert = new Alert(MessageType.ERROR, message, null, body, false);
alert.open(display);
}
public static void handleUncaughtException(Throwable exception) {
handleUncaughtException(Thread.currentThread(), exception);
}
public static void handleUncaughtException(Thread thread, Throwable exception) {
int n = 0;
for (Application application : applications) {
if (application instanceof Application.UncaughtExceptionHandler) {
Application.UncaughtExceptionHandler uncaughtExceptionHandler =
(Application.UncaughtExceptionHandler) application;
uncaughtExceptionHandler.uncaughtException(thread, exception);
n++;
}
}
if (n == 0) {
defaultUncaughtExceptionHandler(thread, exception);
}
}
}