/*******************************************************************************
* Copyright (c) 2012 Google, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Google, Inc. - initial API and implementation
*******************************************************************************/
package com.windowtester.swt.macosx.external;
import java.lang.reflect.Field;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.internal.Platform;
import org.eclipse.swt.internal.carbon.CGPoint;
import org.eclipse.swt.internal.carbon.OS;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import com.windowtester.swt.platform.ext.macosx.MacExtensions;
/**
* More stuff that ought to be in SWT's OS class
*
* @author Eric Herrmann, Adobe Systems
* @author Steve Messick
*/
@SuppressWarnings("restriction")
public class OSExt extends Platform implements MacExtensions {
static {
try {
//some background on this lib can be found here:
System.loadLibrary("swtext-carbon-3346");
} catch ( Throwable t ) {
t.printStackTrace();
}
}
/**
* Returns the value of an accessibility object's attribute.
*
* @param inUIElement The AXUIElementRef representing the accessibility object.
* @param attribute The attribute name.
* @param value On return, the value associated with the specified attribute.
* @return 0 if no error
*/
static final native int AXUIElementCopyAttributeValue(int inUIElement, int attribute, int [] value);
/**
* Convert an accessibility attribute value to a CGPoint.
*
* @param valueRef the AXValueRef
* @param point the CGPoint to hold the value
* @return true for success, false if the valueRef is not of the proper type
*/
/* $codepro.preprocessor.if version > 3.0 $ */
static final native boolean AXValueGetValueCGPoint(int valueRef, CGPoint point);
/* $codepro.preprocessor.endif $ */
/**
* Convert an accessibility attribute value to a CGSize.
*
* @param valueRef the AXValueRef
* @param point the CGSize to hold the value
* @return true for success, false if the valueRef is not of the proper type
*/
static final native boolean AXValueGetValueCGSize(int valueRef, CGSize size);
/**
* Return true if the accessibility API is enabled.
*
* To enable it: open System Preferences, select Universal Access, then
* select "Enable access for assistive devices".
* @return true if the accessibility API is enabled
*/
static final native boolean AXAPIEnabled();
/**
* Get the OS handle from a menu. This field is not public in SWT
* so we use reflection to access it.
*
* @param menu the menu
* @return the menu handle as an int or 0 if something went wrong
*/
static final int SWTGetMenuHandle(Menu menu) {
try {
Field field = Menu.class.getDeclaredField("handle");
field.setAccessible(true);
return field.getInt(menu);
} catch (Exception ex) {
return 0;
}
}
/**
* Utility method to convert a java String to a CFString.
* NOTE: Caller is responsible for releasing the returned value.
*
* @param string the string to convert
* @return a CFString reference
*/
static int stringToCFStringRef(String string) {
char [] buffer = new char [string.length ()];
string.getChars (0, buffer.length, buffer, 0);
return OS.CFStringCreateWithCharacters (OS.kCFAllocatorDefault, buffer, buffer.length);
}
/**
* Given a MenuItem, return its bounding box.
*
* @param item the menu item
* @return Rectangle of item (in global coordinates), or null if something didn't work
*/
public Rectangle getMenuItemBounds(MenuItem item) {
if (item == null)
return null;
Menu parent = item.getParent();
int index = parent.indexOf(item);
int axError;
boolean axReturnCode;
// Find the OS handle of the menu. It's private in Menu, so we need this workaround.
int menuRef = OSExt.SWTGetMenuHandle(parent);
// Get the AX element for the menu
int[] children = new int[1];
int axMenuRef = OS.AXUIElementCreateWithHIObjectAndIdentifier(menuRef, (long) 0);
int cfChildren = OSExt.stringToCFStringRef(OS.kAXChildrenAttribute);
axError = OSExt.AXUIElementCopyAttributeValue(axMenuRef, cfChildren, children);
OS.CFRelease(cfChildren);
OS.CFRelease(axMenuRef);
if (axError != 0 || children[0] == 0) {
System.out.println("Do you have 'System Preferences/Universal Access/Enable access for assistive devices' turned on?");
return null;
}
// The Mac menu bar includes the Apple menu and the application menu, which are
// not part of the SWT menu bar. If we're looking at the menu bar, increment
// the index to skip those two menus. (Does menu bar visibility matter?)
if ((parent.getStyle() & SWT.BAR) != 0)
index += 2;
int menuItem = OS.CFArrayGetValueAtIndex(children[0], index);
CGPoint position = new CGPoint();
CGSize size = new CGSize();
// Get the position
int cfPosition = OSExt.stringToCFStringRef(OS.kAXPositionAttribute);
int[] positionRef = new int[1];
axError = OSExt.AXUIElementCopyAttributeValue(menuItem, cfPosition, positionRef);
axReturnCode = OSExt.AXValueGetValueCGPoint(positionRef[0], position);
OS.CFRelease(positionRef[0]);
OS.CFRelease(cfPosition);
if (!axReturnCode) {
System.out.println("Internal error: type mismatch in native code");
return null;
}
// Get the size
int cfSize = OSExt.stringToCFStringRef(OS.kAXSizeAttribute);
int[] sizeRef = new int[1];
axError = OSExt.AXUIElementCopyAttributeValue(menuItem, cfSize, sizeRef);
axReturnCode = OSExt.AXValueGetValueCGSize(sizeRef[0], size);
OS.CFRelease(sizeRef[0]);
OS.CFRelease(cfSize);
if (!axReturnCode) {
System.out.println("Internal error: type mismatch in native code");
return null;
}
Rectangle result = new Rectangle((int)position.x, (int)position.y, (int)size.width, (int)size.height);
//System.out.println(result);
return result;
}
/**
* Given a TabItem, return its bounding box.
*
* @param item the tab item
* @return Rectangle of item (in global coordinates), or null if something didn't work
*/
public Rectangle getTabItemBounds(TabItem item) {
if (item == null)
return null;
TabFolder parent = item.getParent();
// Find the index of the tab
int index;
boolean found = false;
for (index = 0; index < parent.getItemCount(); index++) {
if (parent.getItem(index) == item) {
found = true;
break;
}
}
if (!found)
return null;
index += 1; // index=0 represents the page; tabs start at 1 (see Apple docs)
CGPoint position = new CGPoint();
CGSize size = new CGSize();
int axTabControlRef = 0;
try {
// Get the AX element for the tab item
axTabControlRef = OS.AXUIElementCreateWithHIObjectAndIdentifier(parent.handle, (long) index);
@SuppressWarnings("unused")
int axError;
boolean axReturnCode;
// Get the position
int cfPosition = OSExt.stringToCFStringRef(OS.kAXPositionAttribute);
int[] positionRef = new int[1];
axError = OSExt.AXUIElementCopyAttributeValue(axTabControlRef, cfPosition, positionRef);
axReturnCode = OSExt.AXValueGetValueCGPoint(positionRef[0], position);
OS.CFRelease(positionRef[0]);
OS.CFRelease(cfPosition);
if (!axReturnCode) {
System.out.println("Internal error: type mismatch in native code");
return null;
}
// Get the size
int cfSize = OSExt.stringToCFStringRef(OS.kAXSizeAttribute);
int[] sizeRef = new int[1];
axError = OSExt.AXUIElementCopyAttributeValue(axTabControlRef, cfSize, sizeRef);
axReturnCode = OSExt.AXValueGetValueCGSize(sizeRef[0], size);
OS.CFRelease(sizeRef[0]);
OS.CFRelease(cfSize);
if (!axReturnCode) {
System.out.println("Internal error: type mismatch in native code");
return null;
}
} finally {
if (axTabControlRef != 0)
OS.CFRelease(axTabControlRef);
}
Rectangle result = new Rectangle((int)position.x, (int)position.y, (int)size.width, (int)size.height);
return result;
}
/**
* Return true if the accessibility API is enabled.
*
* To enable it: open System Preferences, select Universal Access, then
* select "Enable access for assistive devices".
* @return true if the accessibility API is enabled
*/
public boolean isAXAPIEnabled() {
return AXAPIEnabled();
}
}