/*
* @(#)OSXAquaPainter.java
*
* Copyright (c) 2011 Werner Randelshofer, Immensee, Switzerland.
* All rights reserved.
*
* You may not use, copy or modify this file, except in compliance with the
* license agreement you entered into with Werner Randelshofer.
* For details see accompanying license terms.
*/
package ch.randelshofer.quaqua.osx;
import ch.randelshofer.quaqua.QuaquaManager;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.Raster;
import java.security.AccessControlException;
/**
* Renders Aqua user interface controls using the JavaRuntimeSupport framework
* API which is present in OS X 10.6 and 10.7.
* <p>
* References:<br>
* <a
* href="http://hg.openjdk.java.net/macosx-port/macosx-port/jdk/file/tip/src/macosx/classes/com/apple/laf/"
* >OpenJDK LaF classes</a><br>
*
* <a href="http://hg.openjdk.java.net/macosx-port/macosx-port/jdk/file/tip/src/macosx/classes/apple/laf/"
* >OpenJDK JRSUIControl classes</a><br>
*
* <a href="http://hg.openjdk.java.net/macosx-port/macosx-port/jdk/file/tip/src/macosx/native/com/apple/laf/"
* >
* OpenJDK native code.
* </a>
*</p>
* @author Werner Randelshofer
* @version $Id: OSXAquaPainter.java 416 2011-07-30 14:12:52Z wrandelshofer $
*/
public class OSXAquaPainter {
/**
* This variable is set to true, if native code is available.
*/
private static volatile Boolean isNativeCodeAvailable;
/** The handle to the native control. */
private long handle;
private Widget widget;
/**
* Version of the native code library.
*/
private final static int EXPECTED_NATIVE_CODE_VERSION = 1;
/**
* Load the native code.
*/
public static boolean isNativeCodeAvailable() {
if (isNativeCodeAvailable == null) {
synchronized (OSXApplication.class) {
if (isNativeCodeAvailable == null) {
boolean success = false;
try {
// Note: The following line ensures that AWT is started,
// and has initialized NSApplication, before we attempt
// to access it.
Toolkit.getDefaultToolkit().getSystemEventQueue();
String value = QuaquaManager.getProperty("Quaqua.jniIsPreloaded");
if (value == null) {
value = QuaquaManager.getProperty("Quaqua.JNI.isPreloaded");
}
if (value != null && value.equals("true")) {
success = true;
} else {
// Try to load 64-bit libraries if possible
String[] libraryNames;
String osArch = System.getProperty("os.arch");
if (osArch.equals("x86_64")) {
libraryNames = new String[]{"quaqua64"};
} else {
libraryNames = new String[]{"quaqua64", "quaqua"};
}
for (String libraryName : libraryNames) {
try {
System.loadLibrary(libraryName);
success = true;
break;
} catch (UnsatisfiedLinkError e) {
System.err.println("Warning: " + OSXApplication.class + " couldn't load library \"" + System.mapLibraryName(libraryName) + "\". " + e);
success = false;
} catch (AccessControlException e) {
System.err.println("Warning: " + OSXApplication.class + " access controller denied loading library \"" + System.mapLibraryName(libraryName) + "\". " + e);
success = false;
} catch (Throwable e) {
e.printStackTrace();
System.err.println("Warning: " + OSXApplication.class + " couldn't load library \"" + System.mapLibraryName(libraryName) + "\". " + e);
success = false;
}
}
}
if (success) {
int nativeCodeVersion = nativeGetNativeCodeVersion();
if (nativeCodeVersion != EXPECTED_NATIVE_CODE_VERSION) {
System.err.println("Warning: " + OSXApplication.class + " can't use library libquaqua.jnilib. It has version " + nativeCodeVersion + " instead of " + EXPECTED_NATIVE_CODE_VERSION);
success = false;
}
}
} finally {
isNativeCodeAvailable = Boolean.valueOf(success);
}
}
}
}
return isNativeCodeAvailable == Boolean.TRUE;
}
public void dispose() {
releaseControl();
}
public Widget getWidget() {
return widget;
}
/** Property keys. */
public enum Key {
widget(1),
state(2),
size(3),
direction(4),
orientation(5),
verticalAlignment(6),
horizontalAlignment(7),
position(8),
pressedPart(9),
variant(10),
windowType(11),
focused(12),
indicatorOnly(13),
noIndicator(14),
nothingToScroll(15),
arrowsOnly(16),
frameOnly(17),
segmentTrailingSeparator(18),
maximumValue(19),
value(20),
animationStartTime(21),
animationTime(22),
animationFrame(23),
thumbProportion(24),
thumbStart(25),
windowFrameDrawClipped(26),
windowFrameDrawTitleSeparator(27),
windowTitleBarHeight(28);
//
private int id;
private Key(int id) {
this.id = id;
}
public int getId() {
return id;
}
};
public enum Widget {
background(1),
buttonBevel(2),
buttonBevelInset(3),
buttonBevelRound(4),
buttonCheckBox(5),
buttonComboBox(6),
buttonComboBoxInset(7),
buttonDisclosure(8),
buttonListHeader(9),
buttonLittleArrows(10),
buttonPopDown(11),
buttonPopDownInset(12),
buttonPopDownSquare(13),
buttonPopUp(14),
buttonPopUpInset(15),
buttonPopUpSquare(16),
buttonPush(17),
buttonPushScope(18),
buttonPushScope2(19),
buttonPushTextured(20),
buttonPushInset(21),
buttonPushInset2(22),
buttonRadio(23),
buttonRound(24),
buttonRoundHelp(25),
buttonRoundInset(26),
buttonRoundInset2(27),
buttonSearchFieldCancel(28),
buttonSearchFieldFind(29),
buttonSegmented(30),
buttonSegmentedInset(31),
buttonSegmentedInset2(32),
buttonSegmentedSCurve(33),
buttonSegmentedTextured(34),
buttonSegmentedToolbar(35),
dial(36),
disclosureTriangle(37),
dividerGrabber(38),
dividerSeparatorBar(39),
dividerSplitter(40),
focus(41),
frameGroupBox(42),
frameGroupBoxSecondary(43),
frameListBox(44),
framePlacard(45),
frameTextField(46),
frameTextFieldRound(47),
frameWell(48),
growBox(49),
growBoxTextured(50),
gradient(51),
menu(52),
menuItem(53),
menuBar(54),
menuTitle(55),
progressBar(56),
progressIndeterminateBar(57),
progressRelevance(58),
progressSpinner(59),
scrollBar(60),
scrollColumnSizer(61),
slider(62),
sliderThumb(63),
synchronization(64),
tab(65),
titleBarCloseBox(66),
titleBarCollapseBox(67),
titleBarZoomBox(68),
titleBarToolbarButton(69),
toolbarItemWell(70),
windowFrame(71);
//
private int id;
private Widget(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
public enum State {
active(1),
inactive(2),
disabled(3),
pressed(4),
pulsed(5),
rollover(6),
drag(7);
//
private int id;
private State(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
public enum Size {
mini(1),
small(2),
regular(3),
large(4);
//
private int id;
private Size(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
public enum Direction {
none(1),
up(2),
down(3),
left(4),
right(5),
north(6),
south(7),
east(8),
west(9);
//
private int id;
private Direction(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
public enum Orientation {
horizontal(1),
vertical(2);
//
private int id;
private Orientation(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
public enum HorizontalAlignment {
left(1),
center(2),
right(3);
//
private int id;
private HorizontalAlignment(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
public enum VerticalAlignment {
top(1),
center(2),
bottom(3);
//
private int id;
private VerticalAlignment(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
public enum SegmentPosition {
first(1),
middle(2),
last(3),
only(4);
//
private int id;
private SegmentPosition(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
public enum ScrollBarPart {
none(1),
thumb(2),
arrowMin(3),
arrowMax(4),
arrowMaxInside(5),
arrowMinInside(6),
trackMin(7),
trackMax(8);
//
private int id;
private ScrollBarPart(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
public enum Variant {
menuGlyph(1),
menuPopup(2),
menuPulldown(3),
menuHierarchical(4),
gradientListBackgroundEven(5),
gradientListBackgroundOdd(6),
gradientSideBar(7),
gradientSideBarSelection(8),
gradientSideBarFocusedSelection(9);
//
private int id;
private Variant(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
public enum WindowType {
document(1),
utility(2),
titlelessUtility(3);
//
private int id;
private WindowType(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
private boolean createControl() {
if (handle == 0 && isNativeCodeAvailable()) {
handle = nativeCreateControl(false);
}
return handle != 0;
}
private void releaseControl() {
if (handle != 0 && isNativeCodeAvailable()) {
nativeReleaseControl(handle);
handle = 0;
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
releaseControl();
}
/** Sets the widget type of the JRSUIControl. */
public void setWidget(Widget widget) {
this.widget=widget;
if (createControl()) {
nativeSetWidget(handle, widget.getId());
}
}
/** Sets the state of the JRSUIControl. */
public void setState(State state) {
if (createControl()) {
nativeSetState(handle, state.getId());
}
}
/** Sets a key value of the JRSUIControl. */
public void setValueByKey(Key key, double value) {
if (createControl()) {
nativeSetValueByKey(handle, key.getId(), value);
}
}
/** Sets the size variant of the JRSUIControl. */
public void setSize(Size size) {
if (createControl()) {
nativeSetSize(handle, size.getId());
}
}
/** Sets the direction of the JRSUIControl. */
public void setDirection(Direction direction) {
if (createControl()) {
nativeSetDirection(handle, direction.getId());
}
}
/** Sets the orientation of the JRSUIControl. */
public void setOrientation(Orientation orientation) {
if (createControl()) {
nativeSetOrientation(handle, orientation.getId());
}
}
/** Sets the horizontal alignment of the JRSUIControl. */
public void setHorizontalAlignment(HorizontalAlignment halignment) {
if (createControl()) {
nativeSetHorizontalAlignment(handle, halignment.getId());
}
}
/** Sets the vertical alignment of the JRSUIControl. */
public void setVerticalAlignment(VerticalAlignment valignment) {
if (createControl()) {
nativeSetVerticalAlignment(handle, valignment.getId());
}
}
/** Sets the segment position of the JRSUIControl. */
public void setSegmentPosition(SegmentPosition segpos) {
if (createControl()) {
nativeSetSegmentPosition(handle, segpos.getId());
}
}
/** Specifies the desired scroll bar part of the JRSUIControl. */
public void setScrollBarPart(ScrollBarPart sbpart) {
if (createControl()) {
nativeSetScrollBarPart(handle, sbpart.getId());
}
}
/** Specifies the desired variant of the JRSUIControl. */
public void setVariant(Variant variant) {
if (createControl()) {
nativeSetVariant(handle, variant.getId());
}
}
/** Specifies the desired window type of the JRSUIControl. */
public void setWindowType(WindowType wtype) {
if (createControl()) {
nativeSetWindowType(handle, wtype.getId());
}
}
/** Specifies whether to show arrows on a JRSUIControl. */
public void setShowArrows(boolean b) {
if (createControl()) {
nativeSetShowArrows(handle, b);
}
}
/** Specifies whether to animate a JRSUIControl. */
public void setAnimating(boolean b) {
if (createControl()) {
nativeSetAnimating(handle, b);
}
}
/** Paints the widget on the specified image.
* The image data must be of type {@code BufferedImage.TYPE_INT_ARGB_PRE}.
*/
public void paint(int[] imageData, int imgWidth, int imgHeight,//
double x, double y, double width, double height) {
if (createControl()) {
nativePaint(imageData, imgWidth, imgHeight, handle, x, imgHeight - y - height, width, height);
}
}
/** Paints the widget on the specified image.
* The image data must be of type {@code BufferedImage.TYPE_INT_ARGB_PRE}.
*
* @throws IllegalArgumentException if the image type is not {@code BufferedImage.TYPE_INT_ARGB_PRE}.
*/
public void paint(BufferedImage image,//
double x, double y, double width, double height) {
if (image.getType() != BufferedImage.TYPE_INT_ARGB_PRE) {
throw new IllegalArgumentException("Unsupported image type=" + image.getType());
}
if (createControl()) {
int[] imgData = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
int imgWidth = image.getWidth(), imgHeight = image.getHeight(), imgX = 0, imgY = 0;
Raster raster = image.getRaster();
while (raster.getParent() != null) {
imgX -= raster.getMinX();
imgY -= raster.getMinY();
imgWidth = raster.getWidth();
imgHeight = raster.getHeight();
}
nativePaint(imgData, imgWidth, imgHeight, handle, x, imgHeight - y - height, width, height);
}
}
/**
* Returns the version of the native code library. If the version
* does not match with the version that we expect, we can not use
* it.
* @return The version number of the native code.
*/
private static native int nativeGetNativeCodeVersion();
/** Creates a JRSUIControl and returns a handle to it. */
private static native long nativeCreateControl(boolean isFlipped);
/** Disposes of the JRSUIControl. */
private static native void nativeReleaseControl(long handle);
/** Sets a property value on a JRSUIControl. */
private static native void nativeSetValueByKey(long ctrlHandle, int key, double value);
/** Sets the widget type on a JRSUIControl. */
private static native void nativeSetWidget(long ctrlHandle, int widget);
/** Sets the state of the JRSUIControl. */
private static native void nativeSetState(long ctrlHandle, int state);
/** Sets the size variant of the JRSUIControl. */
private static native void nativeSetSize(long ctrlHandle, int size);
/** Sets the direction of the JRSUIControl. */
private static native void nativeSetDirection(long ctrlHandle, int direction);
/** Sets the orientation of the JRSUIControl. */
private static native void nativeSetOrientation(long ctrlHandle, int orientation);
/** Sets the horizontal alignment of the JRSUIControl. */
private static native void nativeSetHorizontalAlignment(long ctrlHandle, int halignment);
/** Sets the vertical alignment of the JRSUIControl. */
private static native void nativeSetVerticalAlignment(long ctrlHandle, int valignment);
/** Sets the segment position of the JRSUIControl. */
private static native void nativeSetSegmentPosition(long ctrlHandle, int segpos);
/** Specifies the desired scroll bar part of the JRSUIControl. */
private static native void nativeSetScrollBarPart(long ctrlHandle, int sbpart);
/** Specifies the desired variant of the JRSUIControl. */
private static native void nativeSetVariant(long ctrlHandle, int variant);
/** Specifies the desired window type of the JRSUIControl. */
private static native void nativeSetWindowType(long ctrlHandle, int wtype);
/** Specifies whether to show arrows on a JRSUIControl. */
private static native void nativeSetShowArrows(long ctrlHandle, boolean b);
/** Specifies whether to animate a JRSUIControl. */
private static native void nativeSetAnimating(long ctrlHandle, boolean b);
/** Paints the widget on the specified image.
* Note: The coordinate system of the native paint method has its origin
* at the lower left corner. (Java has the origin at the top left corner.
*/
private static native void nativePaint(int[] imgData, int imgWidth, int imgHeight,//
long ctrlHandle, double x, double y, double width, double height);
}