package abbot.script;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import org.jdom.*;
import org.jdom.Element;
import abbot.*;
import abbot.finder.*;
import abbot.i18n.Strings;
import abbot.util.Properties;
/** Provides a method for communicating a message on the display. May display
for a reasonable delay or require user input to continue.<p>
Usage:<br>
<blockquote><code>
<annotation [userDismiss="true"] >Text or HTML message</annotation><br>
</code></blockquote>
<p>
Properties:<br>
abbot.annotation.min_delay: minimum time to display an annotation<br>
abbot.annotation.delay: per-word time to display an annotation<br>
*/
public class Annotation extends Step {
public static final String TAG_ANNOTATION = "annotation";
public static final String TAG_USER_DISMISS = "userDismiss";
private static final String USAGE =
"<annotation [title=\"...\"] [component=\"<component ID>\"] [x=XX y=YY] [width=WWW height=HHH] [userDismiss=\"true\"]>Text or HTML message</annotation>";
private static final int WORD_SIZE = 6;
private static final Color BACKGROUND =
new Color((Color.yellow.getRed() + Color.white.getRed()*3)/4,
(Color.yellow.getGreen() + Color.white.getGreen()*3)/4,
(Color.yellow.getBlue() + Color.white.getBlue()*3)/4);
private String title;
private String componentID;
private boolean userDismiss;
private static int minDelay = 5000;
private static int delayUnit = 250;
private String text = "";
private int x = -1;
private int y = -1;
private int width = -1;
private int height = -1;
class WindowLock {}
private transient Object WINDOW_LOCK = new WindowLock();
private transient volatile Frame frame;
private transient volatile AnnotationWindow window;
private transient Point anchorPoint;
private transient boolean ignoreChanges;
static {
minDelay = Properties.getProperty("abbot.annotation.min_delay",
minDelay, 0, 10000);
delayUnit = Properties.getProperty("abbot.annotation.delay_unit",
delayUnit, 1, 5000);
}
public Annotation(Resolver resolver, Element el, Map attributes) {
super(resolver, attributes);
componentID = (String)attributes.get(TAG_COMPONENT);
userDismiss = attributes.get(TAG_USER_DISMISS) != null;
setTitle((String)attributes.get(TAG_TITLE));
String xs = (String)attributes.get(TAG_X);
String ys = (String)attributes.get(TAG_Y);
if (xs != null && ys != null) {
try { x = Integer.parseInt(xs); y = Integer.parseInt(ys); }
catch(NumberFormatException nfe) { x = y = -1; }
}
String ws = (String)attributes.get(TAG_WIDTH);
String hs = (String)attributes.get(TAG_HEIGHT);
if (ws != null & hs != null) {
try {
width = Integer.parseInt(ws);
height = Integer.parseInt(hs);
}
catch(NumberFormatException nfe) { width = height = -1; }
}
String text = null;
Iterator iter = el.getContent().iterator();
while(iter.hasNext()) {
Object obj = iter.next();
if (obj instanceof CDATA) {
text = ((CDATA)obj).getText();
break;
}
}
if (text == null) {
text = el.getText();
}
setText(text);
}
public Annotation(Resolver resolver, String description) {
super(resolver, description);
}
public boolean isShowing() {
synchronized(WINDOW_LOCK) {
return window != null;
}
}
private void showAnnotationWindow() {
Window win = getWindow();
win.pack();
Point where = null;
if (anchorPoint != null) {
where = new Point(anchorPoint);
}
if (x != -1 && y != -1) {
if (where != null) {
where.x += x; where.y += y;
}
else {
where = new Point(x, y);
}
}
if (where != null) {
win.setLocation(where);
}
if (width != -1 && height != -1) {
win.setSize(new Dimension(width, height));
}
win.show();
}
public void showAnnotation() {
dispose();
if (SwingUtilities.isEventDispatchThread()) {
showAnnotationWindow();
}
else try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
showAnnotationWindow();
}
});
SwingUtilities.invokeAndWait(new Runnable() {
public void run() { }
});
}
catch(Exception e) {
Log.warn(e);
}
}
public long getDelayTime() {
long time = (getText().length() / WORD_SIZE) * delayUnit;
return Math.max(time, minDelay);
}
/** Display a non-modal window. */
protected void runStep() throws Throwable {
ignoreChanges = true;
showAnnotation();
ignoreChanges = false;
long start = System.currentTimeMillis();
while ((userDismiss && window != null && window.isShowing())
|| (!userDismiss
&& System.currentTimeMillis() - start < getDelayTime())) {
try { Thread.sleep(200); }
catch(InterruptedException e) { }
Thread.yield();
}
if (!userDismiss) {
dispose();
}
}
public Window getWindow() {
synchronized(WINDOW_LOCK) {
if (window == null) {
window = createWindow();
}
return window;
}
}
// expects to have the WINDOW_LOCK
private AnnotationWindow createWindow() {
Component parent = null;
AnnotationWindow w = null;
Frame f = null;
anchorPoint = null;
if (componentID != null) {
try {
parent = (Component)ArgumentParser.eval(getResolver(),
componentID,
Component.class);
Point loc = parent.getLocationOnScreen();
anchorPoint = new Point(loc.x, loc.y);
while (!(parent instanceof Dialog)
&& !(parent instanceof Frame)) {
parent = parent.getParent();
}
w = (parent instanceof Dialog)
? (title != null
? new AnnotationWindow((Dialog)parent, title)
: new AnnotationWindow((Dialog)parent))
: (title != null
? new AnnotationWindow((Frame)parent, title)
: new AnnotationWindow((Frame)parent));
}
catch(ComponentSearchException e) {
// Ignore the exception and display it in global coords
Log.warn(e);
}
catch(NoSuchReferenceException nsr) {
// Ignore the exception and display it in global coords
Log.warn(nsr);
}
}
if (w == null) {
f = new Frame();
w = (title != null)
? new AnnotationWindow(f, title)
: new AnnotationWindow(f);
}
JPanel pane = (JPanel)w.getContentPane();
pane.setBackground(BACKGROUND);
pane.setLayout(new BorderLayout());
pane.setBorder(new EmptyBorder(4,4,4,4));
JLabel label = new JLabel(replaceNewlines(text));
pane.add(label, BorderLayout.CENTER);
if (userDismiss) {
JPanel bottom = new JPanel(new BorderLayout());
bottom.setBackground(BACKGROUND);
JButton close = new JButton(Strings.get("annotation.continue"));
bottom.add(close, BorderLayout.EAST);
close.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
dispose();
}
});
pane.add(bottom, BorderLayout.SOUTH);
}
// If the user closes the window, make sure we continue execution
w.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent we) {
dispose();
}
public void windowClosed(WindowEvent we) {
dispose();
}
});
frame = f;
return w;
}
private void dispose() {
Window w;
Frame f;
synchronized(WINDOW_LOCK) {
w = window;
f = frame;
window = null;
frame = null;
}
if (w != null) {
if (f != null) {
getResolver().getHierarchy().dispose(f);
}
getResolver().getHierarchy().dispose(w);
}
}
private String replaceNewlines(String text) {
boolean needsHTML = false;
String[] breaks = { "\r\n", "\n" };
for (int i=0;i < breaks.length;i++) {
int index = text.indexOf(breaks[i]);
while (index != -1) {
needsHTML = true;
text = text.substring(0, index) + "<br>"
+ text.substring(index + breaks[i].length());
index = text.indexOf(breaks[i]);
}
}
if (needsHTML && !text.startsWith("<html>")) {
text = "<html>" + text + "</html>";
}
return text;
}
private void updateWindowSize(Window win) {
if (window != win)
return;
Dimension size = win.getSize();
width = size.width;
height = size.height;
}
private void updateWindowPosition(Window win) {
if (window != win)
return;
Point where = win.getLocation();
x = where.x;
y = where.y;
if (anchorPoint != null) {
// Update the window location
x -= anchorPoint.x;
y -= anchorPoint.y;
}
}
public String getDefaultDescription() {
String desc = "Annotation";
if (!"".equals(getText())) {
desc += ": " + getText();
}
return desc;
}
public String getUsage() { return USAGE; }
public String getXMLTag() { return TAG_ANNOTATION; }
protected Element addContent(Element el) {
return el.addContent(new CDATA(getText()));
}
public Map getAttributes() {
Map map = super.getAttributes();
if (componentID != null) {
map.put(TAG_COMPONENT, componentID);
}
if (userDismiss) {
map.put(TAG_USER_DISMISS, "true");
}
if (title != null) {
map.put(TAG_TITLE, title);
}
if (x != -1 || y != -1) {
map.put(TAG_X, String.valueOf(x));
map.put(TAG_Y, String.valueOf(y));
}
if (width != -1 || height != -1) {
map.put(TAG_WIDTH, String.valueOf(width));
map.put(TAG_HEIGHT, String.valueOf(height));
}
return map;
}
public boolean getUserDismiss() { return userDismiss; }
public void setUserDismiss(boolean state) { userDismiss = state; }
public String getRelativeTo() { return componentID; }
public void setRelativeTo(String id) { componentID = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getText() { return text; }
public void setText(String text) { this.text = text; }
public void setDisplayLocation(Point pt) {
if (pt != null) {
x = pt.x; y = pt.y;
}
else {
x = y = -1;
}
}
public Point getDisplayLocation() {
if (x != -1 || y != -1)
return new Point(x, y);
return null;
}
class AnnotationWindow extends JDialog {
public AnnotationWindow(Dialog parent, String title) {
super(parent, title);
addListener();
}
public AnnotationWindow(Dialog parent) {
super(parent);
addListener();
}
public AnnotationWindow(Frame parent, String title) {
super(parent, title);
addListener();
}
public AnnotationWindow(Frame parent) {
super(parent);
addListener();
}
private void addListener() {
addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent ce) {
if (!ignoreChanges && AnnotationWindow.this.isShowing())
updateWindowSize(AnnotationWindow.this);
}
public void componentMoved(ComponentEvent ce) {
if (!ignoreChanges && AnnotationWindow.this.isShowing())
updateWindowPosition(AnnotationWindow.this);
}
});
}
}
}