/* * Scriptographer * * This file is part of Scriptographer, a Scripting Plugin for Adobe Illustrator * http://scriptographer.org/ * * Copyright (c) 2002-2010, Juerg Lehni * http://scratchdisk.com/ * * All rights reserved. See LICENSE file for details. * * File created on 22.12.2004. */ package com.scriptographer.adm; import java.awt.Dimension; import com.scratchdisk.script.Callable; import com.scratchdisk.util.IntegerEnumUtils; import com.scriptographer.ScriptographerEngine; import com.scriptographer.ScriptographerException; /** * @author lehni */ public abstract class Item extends Component { protected ItemType type; protected Dialog dialog; // The native bounds of the native item, including margin fixes for faulty // size handling by ADM (e.g. TextEditItem) protected Rectangle nativeBounds = null; // The bounds of the items, excluding any margins. protected Rectangle bounds; // The visual margins, containing both user set margins and the values // returned by getNativeMargin() which was introduced to easily compensate // for any native margins required by controls. protected Border margin; private String toolTip; protected java.awt.Component component = null; private Size minSize = null; private Size maxSize = null; private Size prefSize = null; private boolean sizeSet = false; private boolean isChild = false; protected Item() { // Call function as it is overridden by Button, where it sets // margin according to platform setMargin(0, 0, 0, 0); } /** * Constructor for newly created Items * * @param dialog * @param type * @param options */ protected Item(Dialog dialog, ItemType type, int options) { // Tell the dialog to ignore notifications during item creation, as on // some versions of Illustrator (CS2 and below), window activation // notifications seem to be triggered by item creation, confusing // dialog.initialize() calls. dialog.ignoreNotifications = true; int handle = nativeCreate(dialog, type.name, options); dialog.ignoreNotifications = false; init(dialog, handle, type); setFont(dialog.getFont()); } protected Item(Dialog dialog, ItemType type) { this(dialog, type, 0); } /** * Constructor for already existing Items that get wrapped, * e.g. PopupMenu * * @param dialog * @param handle * @param isChild */ protected Item(Dialog dialog, int handle, boolean isChild) { this.isChild = isChild; init(dialog, handle, ItemType.get(nativeInit(handle, isChild))); } protected void init(Dialog dialog, int handle, ItemType type) { this.dialog = dialog; this.handle = handle; this.type = type; // Set margin only after type was set, as getNativeMargin() sometimes // depends on it to be set. setMargin(0, 0, 0, 0); dialog.items.add(this); nativeBounds = nativeGetBounds(); // nativeSize and nativeBounds are set by the native environment // size and bounds need to be updated depending on margins and // internalInsets bounds = new Rectangle(nativeBounds).add(margin); } protected void initBounds() { if (!sizeSet) setSize(getBestSize()); // This is used to fix ADM bugs on CS4 where an item does not update its // native bounds in certain situations (hidden window?) even if it was // asked to do so. Rectangle bounds = nativeGetBounds(); if (!bounds.equals(nativeBounds)) setNativeBounds(nativeBounds.x, nativeBounds.y, nativeBounds.width, nativeBounds.height); } public void destroy() { if (handle != 0) { nativeDestroy(handle); handle = 0; dialog.removeItem(this); dialog = null; } } public Dialog getDialog() { return dialog; } /* * Callback functions: */ private Callable onInitialize = null; public Callable getOnInitialize() { return onInitialize; } public void setOnInitialize(Callable onInitialize) { this.onInitialize = onInitialize; } protected void onInitialize() { if (onInitialize != null) ScriptographerEngine.invoke(onInitialize, this); } private Callable onDestroy = null; public Callable getOnDestroy() { return onDestroy; } public void setOnDestroy(Callable onDestroy) { this.onDestroy = onDestroy; } protected void onDestroy() { // retrieve through getter so it can be overridden by subclasses, // e.g. HierarchyListBox Callable onDestroy = this.getOnDestroy(); if (onDestroy != null) ScriptographerEngine.invoke(onDestroy, this); } protected void onNotify(Notifier notifier) { switch (notifier) { case INITIALIZE: onInitialize(); break; case DESTROY: onDestroy(); break; } } /* * ADM stuff: */ /* * item creation/destruction * */ /** * sets size and bounds to valid values */ private native int nativeCreate(Dialog dialog, String type, int options); /** * sets size and bounds to valid values */ private native String nativeInit(int handle, boolean isChild); private native void nativeDestroy(int handle); /* * Handler activation / deactivation */ protected native void nativeSetTrackCallback(boolean enabled); protected native void nativeSetDrawCallback(boolean enabled); public native boolean defaultTrack(Tracker tracker); public native void defaultDraw(Drawer drawer); public native int getTrackMask(); public native void setTrackMask(int mask); /* * item timer * */ /* public native ADMTimerRef createTimer(ADMUInt32 inMilliseconds, ADMActionMask inAbortMask, ADMItemTimerProc inTimerProc, ADMItemTimerAbortProc inTimerAbortProc, ADMInt32 inOptions); public native void abortTimer(ADMTimerRef inTimer); */ /* * item information accessors * */ protected native void nativeSetStyle(int style); protected native int nativeGetStyle(); protected native int getChildItemHandle(int itemID); /* * item state accessors * */ public native boolean isVisible(); public native void setVisible(boolean visible); public native boolean isEnabled(); public native void setEnabled(boolean enabled); public native boolean isActive(); public native void setActive(boolean active); public native boolean isKnown(); public native void setKnown(boolean known); /* * others... * */ public native boolean wantsFocus(); public native void setWantsFocus(boolean wantsFocus); /* * item bounds accessors * */ protected native Rectangle nativeGetBounds(); protected native void nativeSetBounds(int x, int y, int width, int height); protected native void nativeSetSize(int width, int height); protected final void setNativeBounds(int x, int y, int width, int height) { nativeBounds.set(x, y, width, height); // Do not change location for children as this messes things up e.g. // for SpinEdit. if (isChild) nativeSetSize(width, height); else nativeSetBounds(x, y, width, height); } public Rectangle getBounds() { return new Rectangle(bounds); } /** * @jshide */ public void setBounds(int x, int y, int width, int height) { updateBounds(x, y, width, height, true); } public final void setBounds(Rectangle bounds) { setBounds(bounds.x, bounds.y, bounds.width, bounds.height); } protected void updateBounds(int x, int y, int width, int height, boolean sizeChanged) { if (sizeChanged) { // Set prefSize so getPreferredSize does not return results from // getBestSize() prefSize = new Size(width, height); // Set minSize if it is not set yet, so getBestSize() is not used // anymore. if (minSize == null) minSize = prefSize; sizeSet = true; } // Update bounds bounds.set(x, y, width, height); // Update bounds in AWT proxy: updateAWTBounds(bounds); // updateNativeBounds does all the work with margins. updateNativeBounds(x, y, width, height); } protected Size getSizeCorrection() { // Allow subclasses to correct the native size, to fix ADM issues with // layouts. Used in getSizeCorrection. return null; } protected void updateNativeBounds(int x, int y, int width, int height) { // Calculate native values int nativeX = x + margin.left; int nativeY = y + margin.top; int nativeWidth = width - margin.left - margin.right; int nativeHeight = height - margin.top - margin.bottom; Size fix = getSizeCorrection(); if (fix != null) { nativeWidth += fix.width; nativeHeight += fix.height; } int deltaX = nativeWidth - nativeBounds.width; int deltaY = nativeHeight - nativeBounds.height; boolean sizeChanged = deltaX != 0 || deltaY != 0; if (sizeChanged || nativeBounds.x != nativeX || nativeBounds.y != nativeY) { // Same as setNativeBounds here, only TextEditItem overrides this. setNativeBounds(nativeX, nativeY, nativeWidth, nativeHeight); } if (sizeChanged) { try { onResize(deltaX, deltaY); } catch (Exception e) { throw new ScriptographerException(e); } } } protected void updateAWTBounds(Rectangle bounds) { if (component != null) { if (component instanceof AWTItemComponent) ((AWTItemComponent) component).updateBounds(bounds); else if (component instanceof AWTComponentGroupContainer) ((AWTComponentGroupContainer) component).updateBounds(bounds); } } protected void updateAWTMargin(Border margin) { if (component != null && this instanceof ComponentGroup) this.getAWTContainer(true).setInsets(margin.top, margin.left, margin.bottom, margin.right); } /** * @jshide */ public void setPosition(int x, int y) { updateBounds(x, y, bounds.width, bounds.height, false); } public final void setPosition(Point loc) { setPosition(loc.x, loc.y); } public Point getPosition() { return new Point(bounds.x, bounds.y); } public Size getSize() { return bounds.getSize(); } /** * @jshide */ public void setSize(int width, int height) { updateBounds(bounds.x, bounds.y, width, height, true); } public final void setSize(Size size) { setSize(size.width, size.height); } public int getWidth() { return getSize().width; } public void setWidth(int width) { Size size = sizeSet ? getSize() : getBestSize(); size.width = width; setSize(size); } public int getHeight() { return getSize().height; } public void setHeight(int height) { Size size = sizeSet ? getSize() : getBestSize(); size.height = height; setSize(size); } public Size getBestSize() { int maxWidth = maxSize != null ? maxSize.width - margin.left - margin.right : -1; // TODO: verify for which items nativeGetBestSize really works! Size size = null; double factor = isSmall() ? 0.75 : 1; switch (type) { case PICTURE_STATIC: case PICTURE_CHECKBOX: case PICTURE_PUSHBUTTON: case PICTURE_RADIOBUTTON: Image image = null; if (this instanceof ImagePane) image = ((ImagePane) this).getImage(); else if (this instanceof Button) image = ((Button) this).getImage(); if (image != null) size = image.getSize(); else // Default size for image buttons size = new Size(32, 24).multiply(factor); break; case SLIDER: // Default size for slider size = new Size(isSmall() ? 120 : 160, 8); break; case POPUP_LIST: case SCROLLING_POPUP_LIST: Size textSize = getTextSize(" ", -1, true); // Calculate the required size of the popup list based on the // space required by the native UI element around the selected text. Size addSize = ScriptographerEngine.isMacintosh() ? new Size(isSmall() ? 32 : 38, 8) : new Size(26, 6); PopupList list = (PopupList) this; if (list.size() > 0) { size = new Size(0, 0); for (int i = 0, l = list.size(); i < l; i++) { ListEntry entry = list.get(i); String text = entry.getText(); Size entrySize = getTextSize(text, maxWidth != -1 ? maxWidth - addSize.width : -1, true); size.width = Math.max(size.width, entrySize.width); size.height = Math.max(size.height, entrySize.height); } } else { // Empty list, make sure height is at least set for text // and it has some width for future content (32 * space) size = textSize.multiply(32, 1); } size = size.add(addSize); break; default: String text = null; boolean multiline = false; if (this instanceof TextValueItem) { text = ((TextValueItem) this).getText(); multiline = ((TextValueItem) this).isMultiline(); } else if (this instanceof TextItem) { text = ((TextItem) this).getText(); } if (text != null) { if (text.equals("")) text = " "; size = getTextSize(text, maxWidth, !multiline); if (size != null) { if (this instanceof Button) { size.width += size.height * 2; size.height += 6; } else if (this instanceof TextEditItem) { // Ignore the text width for a TextEdit, just use the // text height and use a default width across // Scriptographer for text edits, based on the height. size.width = size.height * 5; size.height += 6; // Spin Edits seem to need 1px more on Windows... if (ScriptographerEngine.isWindows() && this instanceof SpinEdit) size.height += 1; } else if (this instanceof TextPane) { size.width += 4; size.height += 4; } } } } if (size == null) { // If it's not a button, use the current size of the object. // This is needed e.g. for Spacers, where its current size // is the preferred size too. size = this instanceof Button ? new Size(120, 20).multiply(factor) : getSize(); } // Add margins size.width += margin.left + margin.right; size.height += margin.top + margin.bottom; if (minSize != null) { if (size.width < minSize.width) size.width = minSize.width; if (size.height < minSize.height) size.height = minSize.height; } if (maxSize != null) { if (maxSize.width >= 0 && size.width > maxSize.width) size.width = maxSize.width; if (maxSize.height >= 0 && size.height > maxSize.height) size.height = maxSize.height; } return size; } /** * @jshide */ public void setPreferredSize(int width, int height) { prefSize = new Size(width, height); } public void setPreferredSize(Size size) { setPreferredSize(size.width, size.height); } public Size getPreferredSize() { return prefSize != null ? prefSize : getBestSize(); } /** * @jshide */ public void setMinimumSize(int width, int height) { minSize = new Size(width, height); } public void setMinimumSize(Size size) { setMinimumSize(size.width, size.height); } public Size getMinimumSize() { return minSize != null ? minSize : getBestSize(); } /** * @jshide */ public void setMaximumSize(int width, int height) { maxSize = new Size(width, height); } public void setMaximumSize(Size size) { setMaximumSize(size.width, size.height); } public Size getMaximumSize() { return maxSize != null ? maxSize : getSize(); } // top, right, bottom, left protected static final Border MARGIN_NONE = new Border(0, 0, 0, 0); protected Border getNativeMargin() { return MARGIN_NONE; } public Border getVisualMargin() { return (Border) margin.clone(); } public Border getMargin() { return margin.subtract(getNativeMargin()); } public void setMargin(int top, int right, int bottom, int left) { margin = new Border(top, right, bottom, left).add(getNativeMargin()); if (nativeBounds != null) updateBounds(bounds.x, bounds.y, bounds.width, bounds.height, false); // Update the margins in the AWT proxy as well updateAWTMargin(margin); } /* * Coordinate system transformations * */ /** * @jshide */ public native Point localToScreen(int x, int y); /** * @jshide */ public native Point screenToLocal(int x, int y); /** * @jshide */ public native Rectangle localToScreen(int x, int y, int width, int height); /** * @jshide */ public native Rectangle screenToLocal(int x, int y, int width, int height); public Point localToScreen(Point pt) { return localToScreen(pt.x, pt.y); } public Point screenToLocal(Point pt) { return screenToLocal(pt.x, pt.y); } public Rectangle localToScreen(Rectangle rt) { return localToScreen(rt.x, rt.y, rt.width, rt.height); } public Rectangle screenToLocal(Rectangle rt) { return screenToLocal(rt.x, rt.y, rt.width, rt.height); } /* * Item display * */ public native void invalidate(); /** * @jshide */ public native void invalidate(int x, int y, int width, int height); public final void invalidate(Rectangle rt) { invalidate(rt.x, rt.y, rt.width, rt.height); } public native void update(); protected native int nativeGetFont(); protected native void nativeSetFont(int font); private native void nativeSetBackgroundColor(int color); private native int nativeGetBackgroundColor(); public void setBackgroundColor(DialogColor color) { if (color != null) nativeSetBackgroundColor(color.value); } public DialogColor getBackgroundColor() { return IntegerEnumUtils.get(DialogColor.class, nativeGetBackgroundColor()); } /* * Cursor accessors * */ private native int nativeGetCursor(); private native void nativeSetCursor(int cursor); public Cursor getCursor() { return IntegerEnumUtils.get(Cursor.class, nativeGetCursor()); } public void setCursor(Cursor cursor) { if (cursor != null) nativeSetCursor(cursor.value); } /* * Tooltips * */ private native void nativeSetTooltip(String tooltip); public native boolean isToolTipEnabled(); public native void setToolTipEnabled(boolean enabled); /** * @jshide */ public native void showToolTip(int x, int y); public final void showToolTip(Point pt) { showToolTip(pt.x, pt.y); } public native void hideToolTip(); public String getToolTip() { return toolTip; } public void setToolTip(String tooltip) { this.toolTip = tooltip; nativeSetTooltip(tooltip); } /* * AWT LayoutManager integration: */ protected java.awt.Component getAWTComponent(boolean create) { if (component == null && create) { if (this instanceof ComponentGroup) { component = new AWTComponentGroupContainer(); } else { component = new AWTItemComponent(); } // Take over margin and bounds from the item. updateAWTMargin(margin); updateAWTBounds(bounds); } return component; } /* * Calculates the absolute origin of the AWT component. */ protected Point getOrigin(java.awt.Component component) { Point delta = new Point(); java.awt.Container parent = component.getParent(); while (true) { java.awt.Container next = parent.getParent(); if (next == null) break; java.awt.Point loc = parent.getLocation(); delta.x += loc.x; delta.y += loc.y; parent = next; } return delta; } /** * AWTComponent wraps an ADM Item and pretends it is a AWT Component, in * order to take advantage of all the nice LayoutManagers in AWT. * * @author lehni */ class AWTItemComponent extends java.awt.Component implements ComponentWrapper { public Component getComponent() { return Item.this; } public void doLayout() { // Do nothing here... } public void updateBounds(Rectangle bounds) { // Call the setBounds version in super that directly sets the // internal values. setBounds(Rectangle) would call the // overridden setBounds(int, int, int, int) which would change the // underlying Item. super.setBounds(bounds.x, bounds.y, bounds.width, bounds.height); } public Dimension getMaximumSize() { Size size = Item.this.getMaximumSize(); return new Dimension(size.width, size.height); } public Dimension getMinimumSize() { Size size = Item.this.getMinimumSize(); return new Dimension(size.width, size.height); } public Dimension getPreferredSize() { Size size = Item.this.getPreferredSize(); return new Dimension(size.width, size.height); } public void setBounds(int x, int y, int width, int height) { super.setBounds(x, y, width, height); Point origin = Item.this.getOrigin(this); Item.this.setBounds(x + origin.x, y + origin.y, width, height); } public void setBounds(java.awt.Rectangle r) { setBounds(r.x, r.y, r.width, r.height); } public void setSize(int width, int height) { super.setSize(width, height); Item.this.setSize(width, height); } public void setSize(Dimension d) { setSize(d.width, d.height); } public void setLocation(int x, int y) { super.setLocation(x, y); Point origin = Item.this.getOrigin(this); Item.this.setPosition(x + origin.x, y + origin.y); } public void setLocation(java.awt.Point p) { setLocation(p.x, p.y); } public boolean isVisible() { return Item.this.isVisible(); } } /** * The actual AWT class for ComponentGroup sthat does the work of collecting * wrap items or other ComponentGroups and redirecting doLayout calls to its * children. * * @author lehni */ class AWTComponentGroupContainer extends AWTContainer { public Component getComponent() { return Item.this; } public void updateBounds(Rectangle bounds) { // Call the setBounds version in super that directly sets the // internal values. setBounds(Rectangle) would call the // overridden setBounds(int, int, int, int) which would change the // underlying Item. super.setBounds(bounds.x, bounds.y, bounds.width, bounds.height); } public Dimension getMinimumSize() { // If this is a group item such as Frame or ItemGroup, do not // use the native items's min size if (Item.this instanceof ComponentGroup) return super.getMinimumSize(); Size size = Item.this.getMinimumSize(); return new Dimension(size.width, size.height); } public Dimension getMaximumSize() { // If this is a group item such as Frame or ItemGroup, do not // use the native items's max size if (Item.this instanceof ComponentGroup) return super.getMaximumSize(); Size size = Item.this.getMaximumSize(); return new Dimension(size.width, size.height); } public Dimension getPreferredSize() { // If this is a group item such as Frame or ItemGroup, do not // use the native items's preferred size if (Item.this instanceof ComponentGroup) return super.getPreferredSize(); Size size = Item.this.getPreferredSize(); return new Dimension(size.width, size.height); } public void setBounds(int x, int y, int width, int height) { super.setBounds(x, y, width, height); Point origin = Item.this.getOrigin(this); Item.this.setBounds(x + origin.x, y + origin.y, width, height); } public void setBounds(java.awt.Rectangle r) { setBounds(r.x, r.y, r.width, r.height); } public void setSize(int width, int height) { super.setSize(width, height); Item.this.setSize(width, height); } public void setSize(Dimension d) { setSize(d.width, d.height); } public void setLocation(int x, int y) { super.setLocation(x, y); Point origin = Item.this.getOrigin(this); Item.this.setPosition(x + origin.x, y + origin.y); } public void setLocation(java.awt.Point p) { setLocation(p.x, p.y); } public boolean isVisible() { return Item.this.isVisible(); } } }