/* * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package apple.laf; import java.nio.*; import java.util.*; import apple.laf.JRSUIConstants.*; public final class JRSUIControl { private static native int initNativeJRSUI(); private static native long getPtrOfBuffer(ByteBuffer byteBuffer); private static native long getCFDictionary(boolean flipped); private static native void disposeCFDictionary(long cfDictionaryPtr); private static native int syncChanges(long cfDictionaryPtr, long byteBufferPtr); // private static native int paint(long cfDictionaryPtr, long oldProperties, long newProperties, OSXSurfaceData osxsd, double x, double y, double w, double h); // private static native int paintChanges(long cfDictionaryPtr, long byteBufferPtr, long oldProperties, long newProperties, OSXSurfaceData osxsd, double x, double y, double w, double h); private static native int paintToCGContext (long cgContext, long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h); private static native int paintChangesToCGContext (long cgContext, long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, long byteBufferPtr); private static native int paintImage (int[] data, int imgW, int imgH, long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h); private static native int paintChangesImage (int[] data, int imgW, int imgH, long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, long byteBufferPtr); private static native int getNativeHitPart( long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, double hitX, double hitY); private static native void getNativePartBounds(final double[] rect, long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, int part); private static native double getNativeScrollBarOffsetChange( long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, int offset, int visibleAmount, int extent); private static final int INCOHERENT = 2; private static final int NOT_INIT = 1; private static final int SUCCESS = 0; private static final int NULL_PTR = -1; private static final int NULL_CG_REF = -2; private static int nativeJRSInitialized = NOT_INIT; public static void initJRSUI() { if (nativeJRSInitialized == SUCCESS) return; nativeJRSInitialized = initNativeJRSUI(); if (nativeJRSInitialized != SUCCESS) throw new RuntimeException("JRSUI could not be initialized (" + nativeJRSInitialized + ")."); } private static final int NIO_BUFFER_SIZE = 128; private static class ThreadLocalByteBuffer { final ByteBuffer buffer; final long ptr; public ThreadLocalByteBuffer() { buffer = ByteBuffer.allocateDirect(NIO_BUFFER_SIZE); buffer.order(ByteOrder.nativeOrder()); ptr = getPtrOfBuffer(buffer); } } private static final ThreadLocal<ThreadLocalByteBuffer> threadLocal = new ThreadLocal<ThreadLocalByteBuffer>(); private static ThreadLocalByteBuffer getThreadLocalBuffer() { ThreadLocalByteBuffer byteBuffer = threadLocal.get(); if (byteBuffer != null) return byteBuffer; byteBuffer = new ThreadLocalByteBuffer(); threadLocal.set(byteBuffer); return byteBuffer; } private final HashMap<Key, DoubleValue> nativeMap; private final HashMap<Key, DoubleValue> changes; private long cfDictionaryPtr; private long priorEncodedProperties; private long currentEncodedProperties; private final boolean flipped; public JRSUIControl(final boolean flipped){ this.flipped = flipped; cfDictionaryPtr = getCFDictionary(flipped); if (cfDictionaryPtr == 0) throw new RuntimeException("Unable to create native representation"); nativeMap = new HashMap<Key, DoubleValue>(); changes = new HashMap<Key, DoubleValue>(); } JRSUIControl(final JRSUIControl other) { flipped = other.flipped; cfDictionaryPtr = getCFDictionary(flipped); if (cfDictionaryPtr == 0) throw new RuntimeException("Unable to create native representation"); nativeMap = new HashMap<Key, DoubleValue>(); changes = new HashMap<Key, DoubleValue>(other.nativeMap); changes.putAll(other.changes); } protected synchronized void finalize() throws Throwable { if (cfDictionaryPtr == 0) return; disposeCFDictionary(cfDictionaryPtr); cfDictionaryPtr = 0; } enum BufferState { NO_CHANGE, ALL_CHANGES_IN_BUFFER, SOME_CHANGES_IN_BUFFER, CHANGE_WONT_FIT_IN_BUFFER; } private BufferState loadBufferWithChanges(final ThreadLocalByteBuffer localByteBuffer) { final ByteBuffer buffer = localByteBuffer.buffer; buffer.rewind(); for (final JRSUIConstants.Key key : new HashSet<JRSUIConstants.Key>(changes.keySet())) { final int changeIndex = buffer.position(); final JRSUIConstants.DoubleValue value = changes.get(key); try { buffer.putLong(key.getConstantPtr()); buffer.put(value.getTypeCode()); value.putValueInBuffer(buffer); } catch (final BufferOverflowException e) { return handleBufferOverflow(buffer, changeIndex); } catch (final RuntimeException e) { System.err.println(this); throw e; } if (buffer.position() >= NIO_BUFFER_SIZE - 8) { return handleBufferOverflow(buffer, changeIndex); } changes.remove(key); nativeMap.put(key, value); } buffer.putLong(0); return BufferState.ALL_CHANGES_IN_BUFFER; } private BufferState handleBufferOverflow(final ByteBuffer buffer, final int changeIndex) { if (changeIndex == 0) { buffer.putLong(0, 0); return BufferState.CHANGE_WONT_FIT_IN_BUFFER; } buffer.putLong(changeIndex, 0); return BufferState.SOME_CHANGES_IN_BUFFER; } private synchronized void set(final JRSUIConstants.Key key, final JRSUIConstants.DoubleValue value) { final JRSUIConstants.DoubleValue existingValue = nativeMap.get(key); if (existingValue != null && existingValue.equals(value)) { changes.remove(key); return; } changes.put(key, value); } public void set(final JRSUIState state) { state.apply(this); } void setEncodedState(final long state) { currentEncodedProperties = state; } void set(final JRSUIConstants.Key key, final double value) { set(key, new JRSUIConstants.DoubleValue(value)); } // private static final Color blue = new Color(0x00, 0x00, 0xFF, 0x40); // private static void paintDebug(Graphics2D g, double x, double y, double w, double h) { // final Color prev = g.getColor(); // g.setColor(blue); // g.drawRect((int)x, (int)y, (int)w, (int)h); // g.setColor(prev); // } // private static int paintsWithNoChange = 0; // private static int paintsWithChangesThatFit = 0; // private static int paintsWithChangesThatOverflowed = 0; public void paint(final int[] data, final int imgW, final int imgH, final double x, final double y, final double w, final double h) { paintImage(data, imgW, imgH, x, y, w, h); priorEncodedProperties = currentEncodedProperties; } private synchronized int paintImage(final int[] data, final int imgW, final int imgH, final double x, final double y, final double w, final double h) { if (changes.isEmpty()) { // paintsWithNoChange++; return paintImage(data, imgW, imgH, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h); } final ThreadLocalByteBuffer localByteBuffer = getThreadLocalBuffer(); BufferState bufferState = loadBufferWithChanges(localByteBuffer); // fast tracking this, since it's the likely scenario if (bufferState == BufferState.ALL_CHANGES_IN_BUFFER) { // paintsWithChangesThatFit++; return paintChangesImage(data, imgW, imgH, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, localByteBuffer.ptr); } while (bufferState == BufferState.SOME_CHANGES_IN_BUFFER) { final int status = syncChanges(cfDictionaryPtr, localByteBuffer.ptr); if (status != SUCCESS) throw new RuntimeException("JRSUI failed to sync changes into the native buffer: " + this); bufferState = loadBufferWithChanges(localByteBuffer); } if (bufferState == BufferState.CHANGE_WONT_FIT_IN_BUFFER) { throw new RuntimeException("JRSUI failed to sync changes to the native buffer, because some change was too big: " + this); } // implicitly ALL_CHANGES_IN_BUFFER, now that we sync'd the buffer down to native a few times // paintsWithChangesThatOverflowed++; return paintChangesImage(data, imgW, imgH, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, localByteBuffer.ptr); } public void paint(final long cgContext, final double x, final double y, final double w, final double h) { paintToCGContext(cgContext, x, y, w, h); priorEncodedProperties = currentEncodedProperties; } private synchronized int paintToCGContext(final long cgContext, final double x, final double y, final double w, final double h) { if (changes.isEmpty()) { // paintsWithNoChange++; return paintToCGContext(cgContext, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h); } final ThreadLocalByteBuffer localByteBuffer = getThreadLocalBuffer(); BufferState bufferState = loadBufferWithChanges(localByteBuffer); // fast tracking this, since it's the likely scenario if (bufferState == BufferState.ALL_CHANGES_IN_BUFFER) { // paintsWithChangesThatFit++; return paintChangesToCGContext(cgContext, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, localByteBuffer.ptr); } while (bufferState == BufferState.SOME_CHANGES_IN_BUFFER) { final int status = syncChanges(cfDictionaryPtr, localByteBuffer.ptr); if (status != SUCCESS) throw new RuntimeException("JRSUI failed to sync changes into the native buffer: " + this); bufferState = loadBufferWithChanges(localByteBuffer); } if (bufferState == BufferState.CHANGE_WONT_FIT_IN_BUFFER) { throw new RuntimeException("JRSUI failed to sync changes to the native buffer, because some change was too big: " + this); } // implicitly ALL_CHANGES_IN_BUFFER, now that we sync'd the buffer down to native a few times // paintsWithChangesThatOverflowed++; return paintChangesToCGContext(cgContext, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, localByteBuffer.ptr); } Hit getHitForPoint(final int x, final int y, final int w, final int h, final int hitX, final int hitY) { sync(); // reflect hitY about the midline of the control before sending to native final Hit hit = JRSUIConstants.getHit(getNativeHitPart(cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, hitX, 2 * y + h - hitY)); priorEncodedProperties = currentEncodedProperties; return hit; } void getPartBounds(final double[] rect, final int x, final int y, final int w, final int h, final int part) { if (rect == null) throw new NullPointerException("Cannot load null rect"); if (rect.length != 4) throw new IllegalArgumentException("Rect must have four elements"); sync(); getNativePartBounds(rect, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, part); priorEncodedProperties = currentEncodedProperties; } double getScrollBarOffsetChange(final int x, final int y, final int w, final int h, final int offset, final int visibleAmount, final int extent) { sync(); final double offsetChange = getNativeScrollBarOffsetChange(cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, offset, visibleAmount, extent); priorEncodedProperties = currentEncodedProperties; return offsetChange; } private void sync() { if (changes.isEmpty()) return; final ThreadLocalByteBuffer localByteBuffer = getThreadLocalBuffer(); BufferState bufferState = loadBufferWithChanges(localByteBuffer); if (bufferState == BufferState.ALL_CHANGES_IN_BUFFER) { final int status = syncChanges(cfDictionaryPtr, localByteBuffer.ptr); if (status != SUCCESS) throw new RuntimeException("JRSUI failed to sync changes into the native buffer: " + this); return; } while (bufferState == BufferState.SOME_CHANGES_IN_BUFFER) { final int status = syncChanges(cfDictionaryPtr, localByteBuffer.ptr); if (status != SUCCESS) throw new RuntimeException("JRSUI failed to sync changes into the native buffer: " + this); bufferState = loadBufferWithChanges(localByteBuffer); } if (bufferState == BufferState.CHANGE_WONT_FIT_IN_BUFFER) { throw new RuntimeException("JRSUI failed to sync changes to the native buffer, because some change was too big: " + this); } } @Override public int hashCode() { int bits = (int)(currentEncodedProperties ^ (currentEncodedProperties >>> 32)); bits ^= nativeMap.hashCode(); bits ^= changes.hashCode(); return bits; } @Override public boolean equals(final Object obj) { if (!(obj instanceof JRSUIControl)) return false; final JRSUIControl other = (JRSUIControl)obj; if (currentEncodedProperties != other.currentEncodedProperties) return false; if (!nativeMap.equals(other.nativeMap)) return false; if (!changes.equals(other.changes)) return false; return true; } @Override public String toString() { final StringBuilder builder = new StringBuilder("JRSUIControl[inNative:"); builder.append(Arrays.toString(nativeMap.entrySet().toArray())); builder.append(", changes:"); builder.append(Arrays.toString(changes.entrySet().toArray())); builder.append("]"); return builder.toString(); } }