001: package abbot.script;
002:
003: import java.awt.*;
004: import java.awt.event.*;
005: import java.util.*;
006:
007: import javax.swing.*;
008: import javax.swing.border.EmptyBorder;
009:
010: import org.jdom.*;
011: import org.jdom.Element;
012:
013: import abbot.*;
014: import abbot.finder.*;
015: import abbot.i18n.Strings;
016: import abbot.util.Properties;
017:
018: /** Provides a method for communicating a message on the display. May display
019: for a reasonable delay or require user input to continue.<p>
020: Usage:<br>
021: <blockquote><code>
022: <annotation [userDismiss="true"] >Text or HTML message</annotation><br>
023: </code></blockquote>
024: <p>
025: Properties:<br>
026: abbot.annotation.min_delay: minimum time to display an annotation<br>
027: abbot.annotation.delay: per-word time to display an annotation<br>
028: */
029:
030: public class Annotation extends Step {
031:
032: public static final String TAG_ANNOTATION = "annotation";
033: public static final String TAG_USER_DISMISS = "userDismiss";
034: 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>";
035:
036: private static final int WORD_SIZE = 6;
037: private static final Color BACKGROUND = new Color((Color.yellow
038: .getRed() + Color.white.getRed() * 3) / 4, (Color.yellow
039: .getGreen() + Color.white.getGreen() * 3) / 4,
040: (Color.yellow.getBlue() + Color.white.getBlue() * 3) / 4);
041:
042: private String title;
043: private String componentID;
044: private boolean userDismiss;
045: private static int minDelay = 5000;
046: private static int delayUnit = 250;
047: private String text = "";
048: private int x = -1;
049: private int y = -1;
050: private int width = -1;
051: private int height = -1;
052:
053: class WindowLock {
054: }
055:
056: private transient Object WINDOW_LOCK = new WindowLock();
057: private transient volatile Frame frame;
058: private transient volatile AnnotationWindow window;
059: private transient Point anchorPoint;
060: private transient boolean ignoreChanges;
061:
062: static {
063: minDelay = Properties.getProperty("abbot.annotation.min_delay",
064: minDelay, 0, 10000);
065: delayUnit = Properties.getProperty(
066: "abbot.annotation.delay_unit", delayUnit, 1, 5000);
067: }
068:
069: public Annotation(Resolver resolver, Element el, Map attributes) {
070: super (resolver, attributes);
071: componentID = (String) attributes.get(TAG_COMPONENT);
072: userDismiss = attributes.get(TAG_USER_DISMISS) != null;
073: setTitle((String) attributes.get(TAG_TITLE));
074: String xs = (String) attributes.get(TAG_X);
075: String ys = (String) attributes.get(TAG_Y);
076: if (xs != null && ys != null) {
077: try {
078: x = Integer.parseInt(xs);
079: y = Integer.parseInt(ys);
080: } catch (NumberFormatException nfe) {
081: x = y = -1;
082: }
083: }
084: String ws = (String) attributes.get(TAG_WIDTH);
085: String hs = (String) attributes.get(TAG_HEIGHT);
086: if (ws != null & hs != null) {
087: try {
088: width = Integer.parseInt(ws);
089: height = Integer.parseInt(hs);
090: } catch (NumberFormatException nfe) {
091: width = height = -1;
092: }
093: }
094:
095: String text = null;
096: Iterator iter = el.getContent().iterator();
097: while (iter.hasNext()) {
098: Object obj = iter.next();
099: if (obj instanceof CDATA) {
100: text = ((CDATA) obj).getText();
101: break;
102: }
103: }
104: if (text == null) {
105: text = el.getText();
106: }
107: setText(text);
108: }
109:
110: public Annotation(Resolver resolver, String description) {
111: super (resolver, description);
112: }
113:
114: public boolean isShowing() {
115: synchronized (WINDOW_LOCK) {
116: return window != null;
117: }
118: }
119:
120: private void showAnnotationWindow() {
121: Window win = getWindow();
122: win.pack();
123: Point where = null;
124: if (anchorPoint != null) {
125: where = new Point(anchorPoint);
126: }
127: if (x != -1 && y != -1) {
128: if (where != null) {
129: where.x += x;
130: where.y += y;
131: } else {
132: where = new Point(x, y);
133: }
134: }
135: if (where != null) {
136: win.setLocation(where);
137: }
138: if (width != -1 && height != -1) {
139: win.setSize(new Dimension(width, height));
140: }
141: win.setVisible(true);
142: }
143:
144: public void showAnnotation() {
145: dispose();
146: if (SwingUtilities.isEventDispatchThread()) {
147: showAnnotationWindow();
148: } else
149: try {
150: SwingUtilities.invokeAndWait(new Runnable() {
151: public void run() {
152: showAnnotationWindow();
153: }
154: });
155: SwingUtilities.invokeAndWait(new Runnable() {
156: public void run() {
157: }
158: });
159: } catch (Exception e) {
160: Log.warn(e);
161: }
162: }
163:
164: public long getDelayTime() {
165: long time = (getText().length() / WORD_SIZE) * delayUnit;
166: return Math.max(time, minDelay);
167: }
168:
169: /** Display a non-modal window. */
170: protected void runStep() throws Throwable {
171: ignoreChanges = true;
172: showAnnotation();
173: ignoreChanges = false;
174: long start = System.currentTimeMillis();
175: while ((userDismiss && window != null && window.isShowing())
176: || (!userDismiss && System.currentTimeMillis() - start < getDelayTime())) {
177: try {
178: Thread.sleep(200);
179: } catch (InterruptedException e) {
180: }
181: Thread.yield();
182: }
183: if (!userDismiss) {
184: dispose();
185: }
186: }
187:
188: public Window getWindow() {
189: synchronized (WINDOW_LOCK) {
190: if (window == null) {
191: window = createWindow();
192: }
193: return window;
194: }
195: }
196:
197: // expects to have the WINDOW_LOCK
198: private AnnotationWindow createWindow() {
199: Component parent = null;
200: AnnotationWindow w = null;
201: Frame f = null;
202: anchorPoint = null;
203: if (componentID != null) {
204: try {
205: parent = (Component) ArgumentParser.eval(getResolver(),
206: componentID, Component.class);
207: Point loc = parent.getLocationOnScreen();
208: anchorPoint = new Point(loc.x, loc.y);
209: while (!(parent instanceof Dialog)
210: && !(parent instanceof Frame)) {
211: parent = parent.getParent();
212: }
213: w = (parent instanceof Dialog) ? (title != null ? new AnnotationWindow(
214: (Dialog) parent, title)
215: : new AnnotationWindow((Dialog) parent))
216: : (title != null ? new AnnotationWindow(
217: (Frame) parent, title)
218: : new AnnotationWindow((Frame) parent));
219: } catch (ComponentSearchException e) {
220: // Ignore the exception and display it in global coords
221: Log.warn(e);
222: } catch (NoSuchReferenceException nsr) {
223: // Ignore the exception and display it in global coords
224: Log.warn(nsr);
225: }
226: }
227: if (w == null) {
228: f = new Frame();
229: w = (title != null) ? new AnnotationWindow(f, title)
230: : new AnnotationWindow(f);
231: }
232: JPanel pane = (JPanel) w.getContentPane();
233: pane.setBackground(BACKGROUND);
234: pane.setLayout(new BorderLayout());
235: pane.setBorder(new EmptyBorder(4, 4, 4, 4));
236: JLabel label = new JLabel(replaceNewlines(text));
237: pane.add(label, BorderLayout.CENTER);
238: if (userDismiss) {
239: JPanel bottom = new JPanel(new BorderLayout());
240: bottom.setBackground(BACKGROUND);
241: JButton close = new JButton(Strings
242: .get("annotation.continue"));
243: bottom.add(close, BorderLayout.EAST);
244: close.addActionListener(new ActionListener() {
245: public void actionPerformed(ActionEvent ev) {
246: dispose();
247: }
248: });
249: pane.add(bottom, BorderLayout.SOUTH);
250: }
251: // If the user closes the window, make sure we continue execution
252: w.addWindowListener(new WindowAdapter() {
253: public void windowClosing(WindowEvent we) {
254: dispose();
255: }
256:
257: public void windowClosed(WindowEvent we) {
258: dispose();
259: }
260: });
261: frame = f;
262: return w;
263: }
264:
265: private void dispose() {
266: Window w;
267: Frame f;
268: synchronized (WINDOW_LOCK) {
269: w = window;
270: f = frame;
271: window = null;
272: frame = null;
273: }
274: if (w != null) {
275: if (f != null) {
276: getResolver().getHierarchy().dispose(f);
277: }
278: getResolver().getHierarchy().dispose(w);
279: }
280: }
281:
282: private String replaceNewlines(String text) {
283: boolean needsHTML = false;
284: String[] breaks = { "\r\n", "\n" };
285: for (int i = 0; i < breaks.length; i++) {
286: int index = text.indexOf(breaks[i]);
287: while (index != -1) {
288: needsHTML = true;
289: text = text.substring(0, index) + "<br>"
290: + text.substring(index + breaks[i].length());
291: index = text.indexOf(breaks[i]);
292: }
293: }
294: if (needsHTML && !text.startsWith("<html>")) {
295: text = "<html>" + text + "</html>";
296: }
297: return text;
298: }
299:
300: private void updateWindowSize(Window win) {
301: if (window != win)
302: return;
303: Dimension size = win.getSize();
304: width = size.width;
305: height = size.height;
306: }
307:
308: private void updateWindowPosition(Window win) {
309: if (window != win)
310: return;
311:
312: Point where = win.getLocation();
313: x = where.x;
314: y = where.y;
315: if (anchorPoint != null) {
316: // Update the window location
317: x -= anchorPoint.x;
318: y -= anchorPoint.y;
319: }
320: }
321:
322: public String getDefaultDescription() {
323: String desc = "Annotation";
324: if (!"".equals(getText())) {
325: desc += ": " + getText();
326: }
327: return desc;
328: }
329:
330: public String getUsage() {
331: return USAGE;
332: }
333:
334: public String getXMLTag() {
335: return TAG_ANNOTATION;
336: }
337:
338: protected Element addContent(Element el) {
339: return el.addContent(new CDATA(getText()));
340: }
341:
342: public Map getAttributes() {
343: Map map = super .getAttributes();
344: if (componentID != null) {
345: map.put(TAG_COMPONENT, componentID);
346: }
347: if (userDismiss) {
348: map.put(TAG_USER_DISMISS, "true");
349: }
350: if (title != null) {
351: map.put(TAG_TITLE, title);
352: }
353: if (x != -1 || y != -1) {
354: map.put(TAG_X, String.valueOf(x));
355: map.put(TAG_Y, String.valueOf(y));
356: }
357: if (width != -1 || height != -1) {
358: map.put(TAG_WIDTH, String.valueOf(width));
359: map.put(TAG_HEIGHT, String.valueOf(height));
360: }
361: return map;
362: }
363:
364: public boolean getUserDismiss() {
365: return userDismiss;
366: }
367:
368: public void setUserDismiss(boolean state) {
369: userDismiss = state;
370: }
371:
372: public String getRelativeTo() {
373: return componentID;
374: }
375:
376: public void setRelativeTo(String id) {
377: componentID = id;
378: }
379:
380: public String getTitle() {
381: return title;
382: }
383:
384: public void setTitle(String title) {
385: this .title = title;
386: }
387:
388: public String getText() {
389: return text;
390: }
391:
392: public void setText(String text) {
393: this .text = text;
394: }
395:
396: public void setDisplayLocation(Point pt) {
397: if (pt != null) {
398: x = pt.x;
399: y = pt.y;
400: } else {
401: x = y = -1;
402: }
403: }
404:
405: public Point getDisplayLocation() {
406: if (x != -1 || y != -1)
407: return new Point(x, y);
408: return null;
409: }
410:
411: class AnnotationWindow extends JDialog {
412: public AnnotationWindow(Dialog parent, String title) {
413: super (parent, title);
414: addListener();
415: }
416:
417: public AnnotationWindow(Dialog parent) {
418: super (parent);
419: addListener();
420: }
421:
422: public AnnotationWindow(Frame parent, String title) {
423: super (parent, title);
424: addListener();
425: }
426:
427: public AnnotationWindow(Frame parent) {
428: super (parent);
429: addListener();
430: }
431:
432: private void addListener() {
433: addComponentListener(new ComponentAdapter() {
434: public void componentResized(ComponentEvent ce) {
435: if (!ignoreChanges
436: && AnnotationWindow.this .isShowing())
437: updateWindowSize(AnnotationWindow.this );
438: }
439:
440: public void componentMoved(ComponentEvent ce) {
441: if (!ignoreChanges
442: && AnnotationWindow.this.isShowing())
443: updateWindowPosition(AnnotationWindow.this);
444: }
445: });
446: }
447: }
448: }
|