/*
* 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 java.io.File;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.prefs.Preferences;
import com.scratchdisk.script.Callable;
import com.scratchdisk.util.IntegerEnumUtils;
import com.scratchdisk.util.StringUtils;
import com.scriptographer.ScriptographerEngine;
import com.scriptographer.ScriptographerException;
import com.scriptographer.ai.Color;
import com.scriptographer.sg.Script;
/**
* @author lehni
*/
public abstract class Dialog extends Component {
// Dialog styles (for Create() call).
protected final static int
STYLE_MODAL = 0, // wrapped
STYLE_ALERT = 1, // wrapped
STYLE_FLOATING = 2, // wrapped
STYLE_TABBED_FLOATING = 3, // wrapped
STYLE_RESIZING_FLOATING = 4, // wrapped
STYLE_TABBED_RESIZING_FLOATING = 5, // wrapped
STYLE_POPUP = 6, // wrapped
STYLE_NOCLOSE_FLOATING = 7, // wrapped
STYLE_SYSTEM_ALERT = 8, // wrapped
STYLE_POPUP_CONTROL = 9, // wrapped
STYLE_RESIZING_MODAL = 10, // wrapped
STYLE_LEFTSIDED_FLOATING = 11, // wrapped
STYLE_LEFTSIDED_NOCLOSE_FLOATING = 12, // wrapped
STYLE_NOTITLE_DOCK_FLOATING = 13, // TODO: wrap this?
STYLE_TABBED_HIERARCHY_FLOATING = 14,
STYLE_TABBED_RESIZING_HIERARCHY_FLOATING = 15,
STYLE_RESIZING_POPUP_PALETTE = 16,
STYLE_POPUP_PALETTE = 17,
STYLE_HIERARCHY_POPUP_PALETTE = 18,
STYLE_RESIZING_HIERARCH_POPUP_PALETTE = 19,
STYLE_MODAL_NO_ACTIVATE = 20,
STYLE_HOST_DEFINED = 65536;
//
protected final static int
ITEM_UNIQUE = 0,
ITEM_FIRST = -1,
ITEM_LAST = -2,
ITEM_DEFAULT = -3,
ITEM_CANCEL = -4,
ITEM_MENU = -5,
ITEM_RESIZE = -6,
ITEM_PRIVATE_UNIQUE = -7,
ITEM_FIRST_UNUSED_PRIVATE = -8;
protected ArrayList<Item> items;
private EnumSet<DialogOption> options;
// The inside dimensions of the dialog, as used by layout managers and such
private Size size;
private Size minSize = null;
private Size maxSize = null;
private boolean isResizing = false;
private boolean isNotifying = false;
// Required in Item to filter out notifications that are triggered wrongly
// during Item.nativeCreate() calls.
protected boolean ignoreNotifications = false;
private String title = "";
private String name = "";
private boolean visible = false;
private boolean active = false;
protected static Dialog activeDialog = null;
protected static Dialog previousActiveDialog = null;
protected AWTDialogContainer container = null;
/**
* ignoreSizeChange tells onSizeChanged to ignore the event. this is set and
* unset around calls to setBounds and setGroupInfo.
*
* As of CS3, setting size and or bounds is not immediately effective. When
* natively retrieving size through GetLocalRect / GetBoundsRect, the old
* size is returned for a while. So do not rely on them being set here. This
* was the reason for introducing ignoreSizeChange, as even in the bounds
* change event in this case, still the old dimensions are returned (!)
*/
private boolean ignoreSizeChange = false;
// Use two boolean values to monitor the initialized state, to make the
// distinction between completely initialized (initialized == true) and
// somewhere during the call of initialize() (uninitialized == false)
private boolean unitialized = true;
private boolean initialized = false;
// Used to see whether the size where specified before the dialog is
// initialized
private boolean sizeSet = false;
// Used to check if the boundaries (min / max size) are to bet set after
// initialization
private boolean boundsInitialized = false;
// For scripts, we cannot always access ScriptRuntime.getTopCallScope(cx)
// store a reference to the script's preferences object so we can always
// use it this happens completely transparently, the dialog class does not
// need to know anything about the fact if it's a script or a java class.
private Preferences preferences;
private Script script = null;
private static ArrayList<Dialog> dialogs = new ArrayList<Dialog>();
private static HashMap<String, Dialog> dialogsByName =
new HashMap<String, Dialog>();
protected Dialog(int style, EnumSet<DialogOption> options) {
script = ScriptographerEngine.getCurrentScript();
preferences = ScriptographerEngine.getPreferences(script);
items = new ArrayList<Item>();
handle = nativeCreate(name, style, IntegerEnumUtils.getFlags(options));
// Always set dialogs hidden first.
// if the OPTION_HIDDEN pseudo flag is not set, the dialog is then
// displayed in initialize
setVisible(false);
size = nativeGetSize();
isResizing = style == STYLE_RESIZING_FLOATING ||
style == STYLE_TABBED_RESIZING_FLOATING ||
style == STYLE_TABBED_RESIZING_HIERARCHY_FLOATING;
this.options = options != null ? options.clone()
: EnumSet.noneOf(DialogOption.class);
if (handle != 0)
dialogs.add(this);
}
public void setFont(DialogFont font) {
super.setFont(font);
for (Item item : items)
item.setFont(font);
}
/**
* This is called when the dialog is displayed the first time.
* It's usually fired a bit after the constructor exits, or
* when setVisible / doModal / setGroupInfo is called.
* We fake this through onActivate and a native dialog timer.
* Whatever fires first, triggers initialize
*/
protected void initialize(boolean setBoundaries, boolean initBounds) {
// initialize can also be triggered e.g. by setGroupInfo, which needs to
// be ignored
if (!ignoreSizeChange) {
if (unitialized) {
unitialized = false;
// if setVisible was called before proper initialization, visible
// is set but it was not natively executed yet. handle this here
boolean show = !options.contains(DialogOption.HIDDEN) || visible;
boolean prefsLoaded = false;
if (options.contains(DialogOption.REMEMBER_PLACING)) {
prefsLoaded = loadPreferences(title);
// Only explicitly show dialog if prefs could not be loaded.
show = !prefsLoaded;
}
if (container != null) {
if (minSize == null)
setMinimumSize(new Size(container.getMinimumSize()));
if (maxSize == null)
setMaximumSize(new Size(container.getMaximumSize()));
// If no bounds where specified yet, set the preferred size
// as defined by the layout
if (!sizeSet) {
setSize(getPreferredSize());
} else {
// If a container was created, the layout needs to be
// recalculated now, even if the size has not changed.
// This is needed as updateSize might not have fired
// correctly before initialization...
// This solves the display of uninitialized dialogs
// when first running Scriptographer.
container.updateSize(size);
}
}
// Center it on screen now if prefs were not loaded above
if (!prefsLoaded)
centerOnScreen();
initialized = true;
// Execute callback handler
onInitialize();
if (show)
setVisible(true);
}
// setBoundaries is set to false when calling from initializeAll,
// because it would be too early to set it there. At least on Mac
// CS3 this causes problems
if (setBoundaries && isResizing) {
if (minSize != null)
nativeSetMinimumSize(minSize.width, minSize.height);
if (maxSize != null)
nativeSetMaximumSize(maxSize.width, maxSize.height);
}
// Call initBounds on all items at the first time the dialog is
// shown. This fixes issues on CS4 and above with wrong item bounds.
if (initBounds && !boundsInitialized) {
for (Item item : items)
item.initBounds();
boundsInitialized = true;
}
}
}
public boolean isInitialized() {
return initialized;
}
public void destroy() {
if (isNotifying) {
// If we're in a notification, invoke destroy later to fix a bug
// on Windows PC and possible future bugs on Mac.
invokeLater(new Runnable() {
public void run() {
Dialog.this.destroy();
}
});
} else {
nativeDestroy(handle);
dialogs.remove(this);
dialogsByName.remove(name);
handle = 0;
}
}
/**
* @jshide
*/
public void finalize() {
if (handle != 0)
this.destroy();
}
protected boolean canRemove(boolean ignoreKeepAlive) {
return script == null || script.canRemove(ignoreKeepAlive);
}
/**
* @jshide
*/
public static void destroyAll(boolean ignoreKeepAlive, boolean force) {
// Loop backwards since destroy removes from the list
for (int i = dialogs.size() - 1; i >= 0; i--) {
Dialog dialog = dialogs.get(i);
if (force || dialog.canRemove(ignoreKeepAlive))
dialog.destroy();
}
}
/**
* Initalize all is needed on startup, as in that particular case, the
* initalize event would not be fired fast enough, resulting in conflicts
* with positioning of floating palettes. initalizeAll prevents that
* problem. It is fired from {@link ScriptographerEngine.init}
*
* @jshide
*/
public static void initializeAll() {
for (Dialog dialog : dialogs) {
dialog.initialize(false, false);
// Work around weird issue of items sometimes not appearing properly
// in layouts after reloading the plug-in by just slightly reshaping
// the Window, causing the layout to be regenerated again. Simply
// calling doLayout() does not seem to be enough.
// This affects CS4 and above on Mac.
if (dialog.isVisible()) {
Size size = dialog.getSize();
size.height--;
dialog.setSize(size);
}
}
}
public boolean removeItem(Item item) {
if (items.remove(item)) {
item.destroy();
return true;
}
return false;
}
public void savePreferences(String name) {
Preferences prefs = preferences.node(name);
// Saving the palette position, tab/dock preference.
DialogGroupInfo groupInfo = getGroupInfo();
Rectangle bounds = getBounds();
prefs.put("group", groupInfo.group != null ? groupInfo.group : "");
prefs.putInt("positionCode", groupInfo.positionCode);
prefs.put("bounds", bounds.x + " " + bounds.y + " " +
bounds.width + " " + bounds.height);
}
public boolean loadPreferences(String name) {
try {
if (preferences.nodeExists(name)) {
Preferences prefs = preferences.node(name);
// Restore the size and location of the dialog
String[] parts = prefs.get("bounds", "").split("\\s");
Rectangle bounds;
if (parts.length == 4) {
bounds = new Rectangle(Integer.parseInt(parts[0]),
Integer.parseInt(parts[1]),
Integer.parseInt(parts[2]),
Integer.parseInt(parts[3]));
} else {
// Pick a default location in case it has never come up
// before on this machine
Rectangle defaultBounds = Dialog.getPaletteLayoutBounds();
bounds = getBounds();
bounds.setPoint(defaultBounds.x, defaultBounds.y);
}
String group = prefs.get("group", "");
int positionCode = prefs.getInt("positionCode",
DialogGroupInfo.POSITION_DEFAULT);
// Restore the position code of the dialog
setGroupInfo(group, positionCode);
// Now set the bounds
BoundsSetter setter = new BoundsSetter(bounds);
setter.run();
// Sometimes we need to set bounds again afterwards, as OWL
// seems to interfere here...
// This leads to annoying jumping around of the dialog.
// TODO: See if this can be fixed somehow?
invokeLater(setter);
return true;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return false;
}
private class BoundsSetter implements Runnable {
Rectangle bounds;
BoundsSetter(Rectangle bounds) {
this.bounds = bounds;
}
public void run() {
if (isResizing) {
setBounds(bounds);
} else {
setPosition(bounds.getPoint());
}
}
}
/*
* Callback functions
*/
private Callable onDestroy = null;
public Callable getOnDestroy() {
return onDestroy;
}
public void setOnDestroy(Callable onDestroy) {
this.onDestroy = onDestroy;
}
protected void onDestroy() {
if (onDestroy != null)
ScriptographerEngine.invoke(onDestroy, this);
}
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 onActivate = null;
public Callable getOnActivate() {
return onActivate;
}
public void setOnActivate(Callable onActivate) {
this.onActivate = onActivate;
}
protected void onActivate() {
if (onActivate != null)
ScriptographerEngine.invoke(onActivate, this);
}
private Callable onDeactivate = null;
public Callable getOnDeactivate() {
return onDeactivate;
}
public void setOnDeactivate(Callable onDeactivate) {
this.onDeactivate = onDeactivate;
}
protected void onDeactivate() {
if (onDeactivate != null)
ScriptographerEngine.invoke(onDeactivate, this);
}
private Callable onShow = null;
public Callable getOnShow() {
return onShow;
}
public void setOnShow(Callable onShow) {
this.onShow = onShow;
}
protected void onShow() {
if (onShow != null)
ScriptographerEngine.invoke(onShow, this);
}
private Callable onHide = null;
public Callable getOnHide() {
return onHide;
}
public void setOnHide(Callable onHide) {
this.onHide = onHide;
}
protected void onHide() {
if (onHide != null)
ScriptographerEngine.invoke(onHide, this);
}
private Callable onMove = null;
public Callable getOnMove() {
return onMove;
}
public void setOnMove(Callable onMove) {
this.onMove = onMove;
}
protected void onMove() {
if (onMove != null)
ScriptographerEngine.invoke(onMove, this);
}
private Callable onClose = null;
public Callable getOnClose() {
return onClose;
}
public void setOnClose(Callable onClose) {
this.onClose = onClose;
}
protected void onClose() {
if (onClose != null)
ScriptographerEngine.invoke(onClose, this);
}
private Callable onZoom = null;
public Callable getOnZoom() {
return onZoom;
}
public void setOnZoom(Callable onZoom) {
this.onZoom = onZoom;
}
protected void onZoom() {
if (onZoom != null)
ScriptographerEngine.invoke(onZoom, this);
}
private Callable onCycle = null;
public Callable getOnCycle() {
return onCycle;
}
public void setOnCycle(Callable onCycle) {
this.onCycle = onCycle;
}
protected void onCycle() {
if (onCycle != null)
ScriptographerEngine.invoke(onCycle, this);
}
private Callable onCollapse = null;
public Callable getOnCollapse() {
return onCollapse;
}
public void setOnCollapse(Callable onCollapse) {
this.onCollapse = onCollapse;
}
protected void onCollapse() {
if (onCollapse != null)
ScriptographerEngine.invoke(onCollapse, this);
}
private Callable onExpand = null;
public Callable getOnExpand() {
return onExpand;
}
public void setOnExpand(Callable onExpand) {
this.onExpand = onExpand;
}
protected void onExpand() {
if (onExpand != null)
ScriptographerEngine.invoke(onExpand, this);
}
// TODO: consider better name!
private Callable onContextMenuChange = null;
private boolean fireOnClose = true;
public Callable getOnContextMenuChange() {
return onContextMenuChange;
}
public void setOnContextMenuChange(Callable onContextMenuChange) {
this.onContextMenuChange = onContextMenuChange;
}
protected void onContextMenuChange() {
if (onContextMenuChange != null)
ScriptographerEngine.invoke(onContextMenuChange, this);
}
protected void onNotify(Notifier notifier) {
// Do not process this notification if we're told to ignore it. See
// Item constructor for explanations.
if (ignoreNotifications)
return;
isNotifying = true;
try {
switch (notifier) {
case INITIALIZE:
initialize(true, false);
break;
case DESTROY:
if (options.contains(DialogOption.REMEMBER_PLACING))
savePreferences(title);
onDestroy();
break;
case WINDOW_ACTIVATE:
// See comment for initialize to understand why this is fired
// here too
initialize(true, false);
activeDialog = this;
active = true;
onActivate();
break;
case WINDOW_DEACTIVATE:
if (activeDialog == this) {
// Keep track of the previously active dialog, as it is
// needed in a workaround for falsly activate modal dialogs.
previousActiveDialog = activeDialog;
activeDialog = null;
}
active = false;
onDeactivate();
break;
case WINDOW_SHOW:
// See comment for initialize to understand why this is fired
// here too
initialize(false, true);
visible = true;
fireOnClose = true;
onShow();
break;
case WINDOW_HIDE:
if (fireOnClose) {
// Workaround for missing onClose on CS3. This bug was
// reported to Adobe too late, hopefully it will be back
// again in CS4...
// (NOT. But in CS4, MASK_DOCK_CLOSED is now set, not the
// other two).
long code = this.getGroupInfo().positionCode;
if ((code & DialogGroupInfo.MASK_DOCK_VISIBLE) == 0 ||
(code & DialogGroupInfo.MASK_TAB_HIDDEN) != 0 ||
(code & DialogGroupInfo.MASK_DOCK_CLOSED) != 0) {
fireOnClose = false;
onClose();
}
}
visible = false;
onHide();
break;
case WINDOW_DRAG_MOVED:
onMove();
break;
case CLOSE_HIT:
// prevent onClose from being called twice...
if (fireOnClose)
onClose();
break;
case ZOOM_HIT:
onZoom();
break;
case CYCLE:
onCycle();
break;
case COLLAPSE:
onCollapse();
break;
case EXPAND:
onExpand();
break;
case CONTEXT_MENU_CHANGED:
onContextMenuChange();
break;
}
} finally {
isNotifying = false;
}
}
/**
* private callback method, to be called from the native environment
* It calls onResize
*/
private void onSizeChanged(int width, int height, boolean invoke) {
if (!ignoreSizeChange && size != null) {
int deltaX = width - size.width;
int deltaY = height - size.height;
// On some dialog types on OSX (non tabbed resizing),
// the new size is not ready in the nSizeChanged handler.
// Detect this here and use invokeLater to fix it by calling again.
if (deltaX != 0 || deltaY != 0) {
updateSize(deltaX, deltaY);
} else if (invoke) {
invokeLater(new Runnable() {
public void run() {
Size size = nativeGetSize();
onSizeChanged(size.width, size.height, false);
// These dialogs also seem to need a repaint
update();
}
});
}
}
}
/*
* Wrapper stuff:
*/
/* TODO: Check these:
* - timer stuff
* - createNestedItem(...);
* - beginAdjustingFocusOrder, doneAdjustingFocusOrder
*/
/**
* Returns the native window handle. This is a WindowPtr on the mac and a
* HWND on Windows.
*
* @jshide
*/
public native int getWindowHandle();
public native void makeOverlay(int handle);
/**
* Dumps the Mac control hierarchy to the given file. For debug purposes
* only.
*
* @jshide
*/
public native void dumpControlHierarchy(File file);
/*
* Dialog creation/destruction
*
*/
/**
* sets size and bounds
*/
private native int nativeCreate(String name, int dialogStyle, int options);
private native void nativeDestroy(int dialogRef);
/*
* 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);
/*
* Dialog timer
*
*/
/*
public native ADMTimerRef createTimer(ADMUInt32 inMilliseconds,
ADMActionMask inAbortMask, ADMDialogTimerProc inTimerProc,
ADMDialogTimerAbortProc inAbortProc, ADMInt32 inOptions);
public native void abortTimer(ADMTimerRef inTimerID);
*/
/*
* Dialog state accessors
*
*/
private native boolean nativeIsVisible();
public boolean isVisible() {
// There are rare occasions where the visible property is still out
// of sync with the native visibility, especially when launching
// Scriptographer or Illustrator for the first time. So keep it synced
// to make sure we're fine.
if (initialized)
visible = nativeIsVisible();
return visible;
}
private native void nativeSetVisible(boolean visible);
public void setVisible(boolean visible) {
// Do not set visibility natively before the dialog was properly
// initialized. otherwise we get a crash on certain systems (not sure
// which ones anymore, but this works fine).
if (initialized) {
fireOnClose = false;
nativeSetVisible(visible);
fireOnClose = true;
}
this.visible = visible;
}
public native boolean isEnabled();
public native void setEnabled(boolean enabled);
public boolean isActive() {
return active;
}
public native void nativeSetActive(boolean active);
public void setActive(boolean active) {
this.active = active;
nativeSetActive(active);
}
public static Dialog getActiveDialog() {
return activeDialog;
}
protected static void deactivateActiveDialog() {
if (activeDialog != null) {
activeDialog.setActive(false);
activeDialog = null;
}
}
/*
* Dialog bounds accessors
*
*/
private native Size nativeGetSize();
private native void nativeSetSize(int width, int height);
private native Rectangle nativeGetBounds();
private native void nativeSetBounds(int x, int y, int width, int height);
public Rectangle getBounds() {
// As kADMWindowDragMovedNotifier does not seem to work, fetch bounds
// natively.
return nativeGetBounds();
}
public void setBounds(int x, int y, int width, int height) {
ignoreSizeChange = true;
Rectangle bounds = nativeGetBounds();
nativeSetBounds(x, y, width, height);
updateSize(width - bounds.width, height - bounds.height);
ignoreSizeChange = false;
sizeSet = true;
}
public void setBounds(Rectangle bounds) {
setBounds(bounds.x, bounds.y, bounds.width, bounds.height);
}
public Size getSize() {
return (Size) size.clone();
}
public void setSize(int width, int height) {
ignoreSizeChange = true;
nativeSetSize(width, height);
updateSize(width - size.width, height - size.height);
ignoreSizeChange = false;
sizeSet = true;
}
public void setSize(Size size) {
if (size != null)
setSize(size.width, size.height);
}
/**
* Changes the internal size fields (size / bounds) relatively to their
* previous values. As bounds and size do not represent the same Dimensions
* (outer / inner), it has to be done relatively. Change of layout and
* calling of onResize is handled here too
*
* @param deltaX
* @param deltaY
*/
protected void updateSize(int deltaX, int deltaY) {
if (deltaX != 0 || deltaY != 0) {
size.set(size.width + deltaX, size.height + deltaY);
// If a container was created, the layout needs to be recalculated
// now:
if (container != null)
container.updateSize(size);
// Call onResize
try {
onResize(deltaX, deltaY);
} catch (Exception e) {
// TODO: deal with Exception...
throw new ScriptographerException(e);
}
}
}
public native Point getPosition();
public native void setPosition(int x, int y);
public final void setPosition(Point point) {
setPosition(point.x, point.y);
}
/*
* Coordinate system transformations
*
*/
public native Point localToScreen(int x, int y);
public native Point screenToLocal(int x, int y);
public native Rectangle localToScreen(int x, int y, int width, int height);
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);
}
/*
* Dialog redraw requests
*
*/
public native void invalidate();
public native void invalidate(int x, int y, int width, int height);
public native void update();
public void invalidate(Rectangle rt) {
invalidate(rt.x, rt.y, rt.width, rt.height);
}
public void redraw() {
invalidate();
update();
}
/*
* Cursor ID 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);
}
/*
* Dialog text accessors
*
*/
protected native int nativeGetFont();
protected native void nativeSetFont(int font);
public String getTitle() {
return title;
}
private native void nativeSetTitle(String title);
public void setTitle(String title) {
this.title = title != null ? title : "";
nativeSetTitle(title);
// If the dialog name is not set yet, use the title
if (name.equals("")) {
// Append script path to name
setName(script != null
? StringUtils.join(ScriptographerEngine.getScriptPath(
script.getFile(), false), "_") + "_" + title
: title);
}
}
public String getName() {
return name;
}
private native void nativeSetName(String name);
public void setName(String name) {
// If we are changing the name, remove the previous lookup
if (!this.name.equals(""))
dialogsByName.remove(this.name);
this.name = name != null ? name : "";
// See if there was a dialog with this name already, and if so
// destroy it first. This allows script to easily replace their
// own dialogs.
if (!this.name.equals("")) {
Dialog other = dialogsByName.get(this.name);
if (other != null) {
if (other.canRemove(false)) {
other.setVisible(false);
other.destroy();
} else {
// Renaming the other dialog, so they don't collide by name
// natively. This even works when there are more than two
// dialogs, as other.setName keeps calling setName
// recursively with appended names too.
other.setName(this.name + "_");
}
}
dialogsByName.put(this.name, this);
}
nativeSetName(name);
}
/*
* dialog length constraints
*
*/
/*
* There seems to be a problem on CS3 with setting min / max size
* before all layout is initialized. The workaround is to reflect these
* properties in the wrapper and then only set them natively when the dialog
* is activate.
*/
private native void nativeSetMinimumSize(int width, int height);
private native void nativeSetMaximumSize(int width, int height);
public Size getMinimumSize() {
return minSize != null ? minSize : getSize();
}
public void setMinimumSize(int width, int height) {
minSize = new Size(width, height);
if (initialized && isResizing)
nativeSetMinimumSize(width, height);
}
public void setMinimumSize(Size size) {
if (size != null)
setMinimumSize(size.width, size.height);
}
public Size getMaximumSize() {
return maxSize != null ? maxSize : getSize();
}
public void setMaximumSize(int width, int height) {
if (width > Short.MAX_VALUE)
width = Short.MAX_VALUE;
if (height > Short.MAX_VALUE)
height = Short.MAX_VALUE;
maxSize = new Size(width, height);
if (initialized && isResizing)
nativeSetMaximumSize(width, height);
}
public void setMaximumSize(Size size) {
if (size != null)
setMaximumSize(size.width, size.height);
}
public native Size getIncrement();
public native void setIncrement(int hor, int ver);
public void setIncrement(Size increment) {
if (increment != null)
setIncrement(increment.width, increment.height);
}
public void setIncrement(Point increment) {
if (increment != null)
setIncrement(increment.x, increment.y);
}
public Size getPreferredSize() {
if (container != null) {
Dimension preferred = container.getPreferredSize();
// Make sure preferred size does not go bellow minSize
if (minSize != null) {
if (preferred.width < minSize.width)
preferred.width = minSize.width;
if (preferred.height < minSize.height)
preferred.height = minSize.height;
}
return new Size(preferred);
}
return null;
}
/*
* item accessors
*
*/
protected native int getItemHandle(int itemID);
private PopupMenu popupMenu = null;
public PopupMenu getPopupMenu() {
if (popupMenu == null) {
int handle = getItemHandle(ITEM_MENU);
// We need to pass false for isChild as we want notifiers installed
popupMenu = handle != 0 ? new PopupMenu(this, handle, false) : null;
}
return popupMenu;
}
private Button resizeButton = null;
public Button getResizeButton() {
if (resizeButton == null) {
int handle = getItemHandle(ITEM_RESIZE);
resizeButton = handle != 0 ? new Button(this, handle, false) : null;
}
return resizeButton;
}
/*
* default/cancel items
*
*/
public native Item getDefaultItem();
public native void setDefaultItem(Item item);
public native Item getCancelItem();
public native void setCancelItem(Item item);
/*
* dialog state accessors
*
*/
public native boolean isCollapsed();
public native boolean isUpdateEnabled();
public native void setUpdateEnabled(boolean updateEnabled);
public native boolean isForcedOnScreen();
public native void setForcedOnScreen(boolean forcedOnScreen);
/*
* dialog group functions
*
*/
public native DialogGroupInfo getGroupInfo();
private native void nativeSetGroupInfo(String group, int positionCode);
public void setGroupInfo(String group, int positionCode) {
// ignore size changes since it would happen to early and the
// new size would not be returned by native ADM from within setGroupInfo.
// So get the new size after and call onSizeChanged manually.
ignoreSizeChange = true;
nativeSetGroupInfo(group, positionCode);
ignoreSizeChange = false;
Size size = nativeGetSize();
onSizeChanged(size.width, size.height, false);
}
public void setGroupInfo(DialogGroupInfo info) {
setGroupInfo(info.group, info.positionCode);
}
/*
* Support for various standard dialogs:
*/
private static native File nativeFileDialog(String message, String filter,
File directory, String filename, boolean open);
private static File fileDialog(String message, String[] filters,
File selectedFile, boolean open) {
String filter = null;
// Converts the filters to one long string, separated by \0
// as needed by the native function.
if (filters != null) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < filters.length; i++) {
buf.append(filters[i]);
buf.append('\0');
}
buf.append('\0');
filter = buf.toString();
}
File directory;
String filename;
if (selectedFile == null) {
directory = null;
filename = null;
} else if (selectedFile.isDirectory()) {
directory = selectedFile;
filename = "";
} else {
directory = selectedFile.getParentFile();
filename = selectedFile.getName();
}
return nativeFileDialog(message, filter, directory, filename, open);
}
public static File fileOpen(String message, String[] filters,
File selectedFile) {
return fileDialog(message, filters, selectedFile, true);
}
public static File fileSave(String message, String[] filters,
File selectedFile) {
return fileDialog(message, filters, selectedFile, false);
}
public static native File chooseDirectory(String message, File selectedDir);
public static native Color chooseColor(Color color);
/**
* @jshide
*/
public static native Rectangle getPaletteLayoutBounds();
/**
* Returns the screen size for centering of dialogs. Ideally
* this should be public and somewhere where it makes sense.
*/
protected static native Size getScreenSize();
public void centerOnScreen() {
// Visually center dialog on Screen,
// bit higher up than mathematically centered
Size screen = Dialog.getScreenSize(), size = this.getSize();
this.setPosition(
(screen.width - size.width) / 2,
(8 * screen.height / 10 - size.height) / 2
);
}
/*
* AWT LayoutManager integration:
*/
protected java.awt.Component getAWTComponent(boolean create) {
if (container == null && create)
container = new AWTDialogContainer();
return container;
}
/**
* doLayout recalculates the layout, but does not change the dialog's size
*
*/
public void doLayout() {
if (container != null)
container.doLayout();
}
/**
* AWTContainer wraps an UI Dialog and pretends it is an AWT Container,
* in order to take advantage of all the nice LayoutManagers in AWT.
*
* This goes hand in hand with the AWTComponent that wraps an IDM Item in a
* component.
*
* Unfortunately, some LayoutManagers access fields in Container not
* visible from the outside, so size information has to be passed up by
* super calls.
*
* Attention: the UI bounds are the outside of the window, while here we
* use the size of the AWT bounds for the inside! Also, for layout the
* location of the dialog doesn't matter, so let's only work with size for
* simplicity
*
* @author lehni
*/
class AWTDialogContainer extends AWTContainer {
public AWTDialogContainer() {
updateSize(Dialog.this.getSize());
setInsets(0, 0, 0, 0);
}
public Component getComponent() {
return Dialog.this;
}
public void updateSize(Size size) {
// Call setBounds instead of setSize, otherwise the call would loop
// back to the overridden setBounds here, as internally, setSize
// calls setBounds anyway
super.setBounds(0, 0, size.width, size.height);
doLayout();
}
public void setSize(int width, int height) {
super.setSize(width, height);
Dialog.this.setSize(width, height);
}
public void setSize(Dimension d) {
setSize(d.width, d.height);
}
public void setBounds(int x, int y, int width, int height) {
setSize(width, height);
}
public void setBounds(java.awt.Rectangle r) {
setSize(r.width, r.height);
}
public boolean isVisible() {
return true;
}
}
}