001: /*
002: #IFNDEF ALT_LICENSE
003: ThinWire(R) RIA Ajax Framework
004: Copyright (C) 2003-2007 Custom Credit Systems
005:
006: This library is free software; you can redistribute it and/or modify it under
007: the terms of the GNU Lesser General Public License as published by the Free
008: Software Foundation; either version 2.1 of the License, or (at your option) any
009: later version.
010:
011: This library is distributed in the hope that it will be useful, but WITHOUT ANY
012: WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
013: PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
014:
015: You should have received a copy of the GNU Lesser General Public License along
016: with this library; if not, write to the Free Software Foundation, Inc., 59
017: Temple Place, Suite 330, Boston, MA 02111-1307 USA
018:
019: Users who would rather have a commercial license, warranty or support should
020: contact the following company who invented, built and supports the technology:
021:
022: Custom Credit Systems, Richardson, TX 75081, USA.
023: email: info@thinwire.com ph: +1 (888) 644-6405
024: http://www.thinwire.com
025: #ENDIF
026: [ v1.2_RC2 ]
027: */
028: package thinwire.ui;
029:
030: import java.io.*;
031: import java.lang.ref.WeakReference;
032: import java.net.MalformedURLException;
033: import java.net.URL;
034: import java.net.URLConnection;
035: import java.util.ArrayList;
036: import java.util.Collections;
037: import java.util.HashMap;
038: import java.util.Map;
039: import java.util.List;
040: import java.util.Properties;
041: import java.util.WeakHashMap;
042: import java.util.EventListener;
043: import java.util.logging.Level;
044: import java.util.logging.Logger;
045: import java.util.zip.*;
046:
047: import thinwire.render.Renderer;
048: import thinwire.ui.event.ActionEvent;
049: import thinwire.ui.event.ActionListener;
050: import thinwire.ui.event.DropListener;
051: import thinwire.ui.event.ExceptionEvent;
052: import thinwire.ui.event.ExceptionListener;
053: import thinwire.ui.event.ItemChangeListener;
054: import thinwire.ui.event.KeyPressListener;
055: import thinwire.ui.event.PropertyChangeEvent;
056: import thinwire.ui.event.PropertyChangeListener;
057: import thinwire.ui.style.*;
058: import thinwire.util.XOD;
059:
060: /**
061: * The Application class represents an instance of a ThinWire application. Methods in this class are
062: * those that directly affect the system and it's environment.
063: * @author Joshua J. Gertzen
064: */
065: public abstract class Application {
066: private static final Logger log = Logger
067: .getLogger(Application.class.getName());
068: private static final String DEFAULT_STYLE_SHEET = "class:///"
069: + Application.class.getName()
070: + "/resources/CorporateStyle.zip";
071:
072: private static final Map<String, String> versionInfo;
073: private static final Map<Class<? extends Component>, Style> defaultStyleMap;
074:
075: static {
076: Properties props = new Properties();
077: Map<String, String> vi = new HashMap<String, String>();
078:
079: try {
080: props
081: .load(Application.class
082: .getResourceAsStream("resources/versionInfo.properties"));
083:
084: for (Map.Entry<Object, Object> e : props.entrySet()) {
085: String key = e.getKey().toString();
086: key = key.substring(key.indexOf('.') + 1);
087: vi.put(key, e.getValue().toString());
088: }
089:
090: versionInfo = Collections.unmodifiableMap(vi);
091: XOD xod = new XOD();
092: xod.execute(DEFAULT_STYLE_SHEET + "/Style.xml");
093: defaultStyleMap = buildStyleMap(xod);
094: } catch (IOException e) {
095: throw new RuntimeException(e);
096: }
097: }
098:
099: private Map<Local, Object> appLocal = new WeakHashMap<Local, Object>();
100:
101: public static class Local<T> {
102: public void set(T value) {
103: Map<Local, Object> map = Application.current().appLocal;
104:
105: synchronized (map) {
106: map.put(this , value);
107: }
108: }
109:
110: public T get() {
111: Map<Local, Object> map = Application.current().appLocal;
112:
113: synchronized (map) {
114: T value = (T) map.get(this );
115: if (value == null && !map.containsKey(this ))
116: map.put(this , value = initialValue());
117: return value;
118: }
119: }
120:
121: protected T initialValue() {
122: return null;
123: }
124: }
125:
126: /**
127: * Returns the current version info details for the platform.
128: * The returned Map is read-only and contains the following keys:
129: * companyName
130: * companyAddress1
131: * companyAddress2
132: * companyCity
133: * companyState
134: * companyZip
135: * companyPhone
136: * companyWebsite
137: * productDescription
138: * productVersion
139: * internalName
140: * legalCopyright
141: * originalFilename
142: * @return the current version info details for the platform.
143: */
144: public static Map<String, String> getPlatformVersionInfo() {
145: return versionInfo;
146: }
147:
148: /**
149: * Displays a version detail dialog.
150: * @param args
151: */
152: public static void main(String[] args) throws Exception {
153: StringBuilder sb = new StringBuilder();
154: BufferedReader r = new BufferedReader(
155: new InputStreamReader(
156: Application.class
157: .getResourceAsStream("resources/licenseHeader.txt")));
158: String line = r.readLine();
159:
160: while (line != null) {
161: sb.append(line).append('\n');
162: line = r.readLine();
163: }
164:
165: sb
166: .append("\nContents of the Application.getPlatformVersionInfo() map:\n\n");
167:
168: for (Map.Entry<String, String> e : getPlatformVersionInfo()
169: .entrySet()) {
170: sb.append(e.getKey()).append("=").append(e.getValue())
171: .append('\n');
172: }
173:
174: final java.awt.Frame frame = new java.awt.Frame(
175: "ThinWire(R) RIA Ajax Framework v"
176: + getPlatformVersionInfo()
177: .get("productVersion"));
178: final java.awt.TextArea ta = new java.awt.TextArea(sb
179: .toString());
180: ta.setEditable(false);
181: frame.add(ta);
182: frame.setSize(640, 480);
183: frame.setVisible(true);
184: frame.addWindowListener(new java.awt.event.WindowAdapter() {
185: public void windowClosing(java.awt.event.WindowEvent ev) {
186: frame.dispose();
187: }
188: });
189: }
190:
191: /**
192: * @return the current instance of the application, or null if called from a thread other than the UI thread.
193: */
194: public static Application current() {
195: return thinwire.render.web.WebApplication.current();
196: }
197:
198: static boolean validateURL(String url) {
199: if (url != null && url.trim().length() > 0) {
200: int index = url.indexOf(".zip");
201: if (index > 0 && index + 5 < url.length())
202: url = url.substring(0, index + 4);
203:
204: if (url.startsWith("class:///")) {
205: return true;
206: } else if (url.indexOf("://") >= 0
207: && !url.startsWith("file:///")) {
208: try {
209: new URL(url);
210: return true;
211: } catch (MalformedURLException e) {
212: return false;
213: }
214: } else {
215: Application app = Application.current();
216: if (url.startsWith("file:///"))
217: url = url.substring(7);
218: File file = app == null ? new File(url) : app
219: .getRelativeFile(url);
220: return file.exists();
221: }
222: } else {
223: return false;
224: }
225: }
226:
227: /**
228: * Returns an InputStream representing the specified resource.
229: * The following URL's are supported:
230: * <ul>
231: * <li>Any valid resource filename. If Application.current() returns non-null,
232: * which is the typical scenario, then relative paths are interpreted as
233: * relative to the application folder. {@link #getRelativeFile}</li>
234: * <li>class:///com.mypackage.MyClass/resource.ext</li>
235: * <li>http://www.mycompany.com/resource.ext</li>
236: * </ul>
237: *
238: * @return an InputStream representing the specified resource or null if the resource was not found.
239: */
240: public static InputStream getResourceAsStream(String uri) {
241: InputStream is = null;
242:
243: if (uri != null && uri.trim().length() > 0) {
244: try {
245: String innerFile = null;
246: int index = uri.indexOf(".zip");
247:
248: if (index > 0 && index + 5 < uri.length()) {
249: innerFile = uri.substring(index + 5);
250: uri = uri.substring(0, index + 4);
251: }
252:
253: //"class:///thinwire.ui.layout.SplitLayout/resources/Image.png"
254: if (uri.startsWith("class:///")) {
255: int endIndex = uri.indexOf('/', 9);
256: String className = uri.substring(9, endIndex);
257: String resource = uri.substring(endIndex + 1);
258: Class clazz = Class.forName(className);
259: is = clazz.getResourceAsStream(resource);
260: } else if (uri.indexOf("://") >= 0
261: && !uri.startsWith("file:///")) {
262: URL remoteImageURL = new URL(uri);
263: URLConnection remoteImageConnection = remoteImageURL
264: .openConnection();
265: is = remoteImageConnection.getInputStream();
266: } else {
267: Application app = Application.current();
268: if (uri.startsWith("file:///"))
269: uri = uri.substring(7);
270: File file = app == null ? new File(uri) : app
271: .getRelativeFile(uri);
272: if (file.exists())
273: is = new FileInputStream(file);
274: }
275:
276: if (is != null) {
277: is = new BufferedInputStream(is);
278:
279: if (innerFile != null) {
280: innerFile = innerFile.replace('\\', '/')
281: .toLowerCase();
282: ZipInputStream zip = new ZipInputStream(is);
283: is = null;
284: ZipEntry entry;
285:
286: while ((entry = zip.getNextEntry()) != null) {
287: if (!entry.isDirectory()
288: && entry.getName().toLowerCase()
289: .equals(innerFile)) {
290: byte[] bytes = new byte[(int) entry
291: .getSize()];
292: int pos = 0, cnt;
293:
294: while ((cnt = zip.read(bytes, pos,
295: bytes.length - pos)) > 0) {
296: pos += cnt;
297: }
298:
299: is = new ByteArrayInputStream(bytes);
300: break;
301: }
302:
303: zip.closeEntry();
304: }
305:
306: zip.close();
307: }
308: }
309: } catch (Exception e) {
310: if (e instanceof RuntimeException)
311: throw (RuntimeException) e;
312: throw new RuntimeException(e);
313: }
314: }
315:
316: return is;
317: }
318:
319: public static byte[] getResourceBytes(String uri) {
320: ByteArrayOutputStream baos = new ByteArrayOutputStream();
321: writeResourceToStream(uri, baos);
322: return baos.toByteArray();
323: }
324:
325: public static void writeResourceToStream(String uri, OutputStream os) {
326: if (os == null)
327: throw new IllegalArgumentException("os == null");
328: InputStream is = getResourceAsStream(uri);
329: if (is == null)
330: throw new IllegalArgumentException(
331: "Content for URI was not found:" + uri);
332: byte[] bytes = new byte[128];
333: int size;
334:
335: try {
336: while ((size = is.read(bytes)) != -1)
337: os.write(bytes, 0, size);
338:
339: is.close();
340: } catch (IOException e) {
341: throw new RuntimeException(e);
342: }
343: }
344:
345: public static Style getDefaultStyle(Class<? extends Component> clazz) {
346: if (clazz == null)
347: throw new IllegalArgumentException("clazz == null");
348: Map<Class<? extends Component>, Style> map = current() != null ? current().compTypeToStyle
349: : defaultStyleMap;
350:
351: Style style = map.get(clazz);
352:
353: if (style == null) {
354: List<Class<? extends Component>> lst = new ArrayList<Class<? extends Component>>();
355: lst.add(clazz);
356:
357: do {
358: Class<? extends Component> findClazz = lst.remove(0);
359: style = map.get(findClazz);
360:
361: if (style == null) {
362: Class sc = findClazz.getSuperclass();
363: if (Component.class.isAssignableFrom(sc))
364: lst.add(sc);
365:
366: for (Class i : findClazz.getInterfaces()) {
367: if (Component.class.isAssignableFrom(i))
368: lst.add(i);
369: }
370: } else {
371: break;
372: }
373: } while (lst.size() > 0);
374:
375: if (style == null)
376: style = map.get(null);
377: map.put(clazz, style);
378: }
379:
380: return style;
381: }
382:
383: private static Map<Class<? extends Component>, Style> buildStyleMap(
384: XOD props) {
385: Map<Class<? extends Component>, Style> styleMap = new HashMap<Class<? extends Component>, Style>();
386:
387: for (Map.Entry<String, Object> e : props.getObjectMap()
388: .entrySet()) {
389: String name = e.getKey();
390: Object value = e.getValue();
391:
392: if (value instanceof Style) {
393: if (name.startsWith("default")) {
394: if (name.equals("default"))
395: styleMap.put(null, (Style) value);
396: } else if (name.matches(".*?[A-Z].*?")) {
397: if (Character.isUpperCase(name.charAt(0))
398: && name.indexOf('.') == -1)
399: name = "thinwire.ui." + name;
400:
401: try {
402: Class clazz = Class.forName(name);
403: if (clazz.getMethod("getStyle") != null)
404: styleMap.put(
405: (Class<? extends Component>) clazz,
406: (Style) value);
407: } catch (Exception ex) { /*purposely fall through*/
408: }
409: }
410: } else if (!(value instanceof Color)) {
411: throw new UnsupportedOperationException(
412: "Unsupported object type in style sheet:"
413: + value.getClass());
414: }
415: }
416:
417: return styleMap;
418: }
419:
420: private List<ExceptionListener> exceptionListeners;
421: private Map<Class, EventListenerImpl> globalListeners;
422: private WeakReference<Component> priorFocus;
423: private Frame frame;
424:
425: private Map<String, Color> systemColors;
426: private Map<String, String> systemImages;
427: private Map<Class<? extends Component>, Style> compTypeToStyle;
428:
429: protected Application() {
430: exceptionListeners = new ArrayList<ExceptionListener>();
431: }
432:
433: <T extends EventListener> EventListenerImpl<T> getGlobalListenerSet(
434: Class<T> type, boolean createIfNull) {
435: if (globalListeners == null) {
436: if (createIfNull) {
437: globalListeners = new HashMap<Class, EventListenerImpl>();
438: } else {
439: return null;
440: }
441: }
442:
443: EventListenerImpl<T> set = globalListeners.get(type);
444: if (set == null && createIfNull)
445: globalListeners.put(type, set = new EventListenerImpl<T>(
446: null, type));
447: return set;
448: }
449:
450: /**
451: * Adds a <code>PropertyChangeListener</code> that will be notified when the specified property of any new component changes. To
452: * further clarify, a global property change listener will receive an event notification for any component that is created after
453: * this global property change listener is added, for which the specified property changes. Therefore, establishing a global
454: * property change listener is the same as invoking the {@link Component#addPropertyChangeListener(String, PropertyChangeListener)}
455: * method on every component after it's creation.
456: * <p>
457: * As a general rule, you should avoid using this feature because it can cause a large volume of events to be generated. This is
458: * especially true if you listen to a frequently updated property, such as <code>PROPERTY_TEXT</code>. However, there are
459: * cases where this can be quite useful. One such case is when you use the 'userObject' property to store a boolean state that
460: * indicates whether a value is required for the component. In such a case, you can establish a global property change listener
461: * for 'userObject' and then based on the property being set to <code>true</code> you could update the background color so it
462: * is apparent to the user that a value is required.
463: * </p>
464: * <b>Example:</b>
465: *
466: * <pre>
467: * Component.addGlobalPropertyChangeListener(Component.PROPERTY_USER_OBJECT, new PropertyChangeListener() {
468: * public void propertyChange(PropertyChangeEvent pce) {
469: * Component comp = (Component) pce.getSource();
470: *
471: * if (comp.getUserObject() == Boolean.TRUE) {
472: * comp.getStyle().getBackground().setColor(Color.PALEGOLDENROD);
473: * } else {
474: * comp.getStyle().getBackground().setColor(null); //restore the default background color
475: * }
476: * }
477: * });
478: *
479: * TextField tf = new TextField();
480: * tf.setUserObject(Boolean.TRUE); //Causes background color to be set to Color.PALEGOLDENROD
481: * tf.setUserObject(Boolean.FALSE); //Causes background color to be set to the default.
482: * </pre>
483: *
484: * @param propertyName the name of the property that the listener will receive change events for.
485: * @param listener the listener that will receive <code>PropertyChangeEvent</code> objects upon the property of any new component changing.
486: * @throws IllegalArgumentException if <code>listener</code> or <code>propertyName</code> is null or if
487: * <code>propertyName</code> is an empty string.
488: * @see Component#addPropertyChangeListener(String, PropertyChangeListener)
489: * @see thinwire.ui.event.PropertyChangeListener
490: * @see thinwire.ui.event.PropertyChangeEvent
491: */
492: public void addGlobalPropertyChangeListener(String propertyName,
493: PropertyChangeListener listener) {
494: EventListenerImpl<PropertyChangeListener> set = getGlobalListenerSet(
495: PropertyChangeListener.class, true);
496: set.addListener(propertyName, listener);
497: }
498:
499: /**
500: * Adds a <code>PropertyChangeListener</code> that will be notified when any of the specified properties of
501: * any new component changes. This method is equivalent to calling
502: * {@link #addGlobalPropertyChangeListener(String, PropertyChangeListener)} once for each property you want to listen to.
503: * @param propertyNames a string array of property names that the listener will receive change events for.
504: * @param listener the listerner that will receive <code>PropertyChangeEvent</code> objects anytime one of the specified
505: * propertyNames of any new component change.
506: * @throws IllegalArgumentException if <code>listener</code>, <code>propertyNames</code> or any property name is the array is null or if
507: * any property name is an empty string.
508: * @see #addGlobalPropertyChangeListener(String, PropertyChangeListener)
509: * @see Component#addPropertyChangeListener(String[], PropertyChangeListener)
510: * @see thinwire.ui.event.PropertyChangeListener
511: * @see thinwire.ui.event.PropertyChangeEvent
512: */
513: public void addGlobalPropertyChangeListener(String[] propertyNames,
514: PropertyChangeListener listener) {
515: EventListenerImpl<PropertyChangeListener> set = getGlobalListenerSet(
516: PropertyChangeListener.class, true);
517: set.addListener(propertyNames, listener);
518: }
519:
520: /**
521: * Removes the <code>PropertyChangeListener</code> from the list of global listeners that are added to all
522: * new <code>Component</code>'s. To further clarify, removing a global property change listener will NOT
523: * remove the <code>listener</code> from <code>Component</code>'s that have already been created.
524: * @param listener the listener to remove from the notification list.
525: * @throws IllegalArgumentException if <code>listener</code> is null.
526: * @see thinwire.ui.event.PropertyChangeListener
527: */
528: public void removeGlobalPropertyChangeListener(
529: PropertyChangeListener listener) {
530: EventListenerImpl<PropertyChangeListener> set = getGlobalListenerSet(
531: PropertyChangeListener.class, true);
532: set.removeListener(listener);
533: }
534:
535: /**
536: * Adds a <code>ActionListener</code> that will be notified when the specified action occurs on any new component. To
537: * further clarify, a global action listener will receive an event notification for any component that is created after
538: * this global action listener is added, on which the specified action occurs. Therefore, establishing a global
539: * action listener is the same as invoking the {@link Component#addActionListener(String, ActionListener)}
540: * method on every component after it's creation.
541: * @param action the action to specficially be notified of.
542: * @param listener the event listener that will receive notification.
543: */
544: public void addGlobalActionListener(String action,
545: ActionListener listener) {
546: EventListenerImpl<ActionListener> set = getGlobalListenerSet(
547: ActionListener.class, true);
548: set.addListener(action, listener);
549: }
550:
551: /**
552: * Adds a <code>ActionListener</code> that will be notified when any of the specified actions occur on
553: * any new component. This method is equivalent to calling
554: * {@link #addGlobalActionListener(String, ActionListener)} once for each action you want to receive notification for.
555: * @param actions the actions to specficially be notified of.
556: * @param listener the event listener that will receive notifications.
557: */
558: public void addGlobalActionListener(String[] actions,
559: ActionListener listener) {
560: EventListenerImpl<ActionListener> set = getGlobalListenerSet(
561: ActionListener.class, true);
562: set.addListener(actions, listener);
563: }
564:
565: /**
566: * Unregister an <code>ActionListener</code> from all action event notifications from any component.
567: * @param listener the listener that should no longer receive action event notifications.
568: */
569: public void removeGlobalActionListener(ActionListener listener) {
570: EventListenerImpl<ActionListener> set = getGlobalListenerSet(
571: ActionListener.class, true);
572: set.removeListener(listener);
573: }
574:
575: public void addGlobalDropListener(Component dragSource,
576: DropListener listener) {
577: EventListenerImpl<DropListener> set = getGlobalListenerSet(
578: DropListener.class, true);
579: set.addListener(dragSource, listener);
580: }
581:
582: public void addGlobalDropListener(Component[] dragSources,
583: DropListener listener) {
584: EventListenerImpl<DropListener> set = getGlobalListenerSet(
585: DropListener.class, true);
586: set.addListener(dragSources, listener);
587: }
588:
589: public void removeGlobalDropListener(DropListener listener) {
590: EventListenerImpl<DropListener> set = getGlobalListenerSet(
591: DropListener.class, true);
592: set.removeListener(listener);
593: }
594:
595: public void addGlobalKeyPressListener(String keyPressCombo,
596: KeyPressListener listener) {
597: EventListenerImpl<KeyPressListener> set = getGlobalListenerSet(
598: KeyPressListener.class, true);
599: set.addListener(keyPressCombo, listener);
600: }
601:
602: public void addGlobalKeyPressListener(String[] keyPressCombos,
603: KeyPressListener listener) {
604: EventListenerImpl<KeyPressListener> set = getGlobalListenerSet(
605: KeyPressListener.class, true);
606: set.addListener(keyPressCombos, listener);
607: }
608:
609: public void removeGlobalKeyPressListener(KeyPressListener listener) {
610: EventListenerImpl<KeyPressListener> set = getGlobalListenerSet(
611: KeyPressListener.class, true);
612: set.removeListener(listener);
613: }
614:
615: public void addGlobalItemChangeListener(ItemChangeListener listener) {
616: EventListenerImpl<ItemChangeListener> set = getGlobalListenerSet(
617: ItemChangeListener.class, true);
618: set.addListener(listener);
619: }
620:
621: public void removeGlobalItemChangeListener(
622: ItemChangeListener listener) {
623: EventListenerImpl<ItemChangeListener> set = getGlobalListenerSet(
624: ItemChangeListener.class, true);
625: set.removeListener(listener);
626: }
627:
628: /**
629: *
630: * @param listener
631: */
632: public void addExceptionListener(ExceptionListener listener) {
633: exceptionListeners.add(listener);
634: }
635:
636: /**
637: *
638: * @param listener
639: */
640: public void removeExceptionListener(ExceptionListener listener) {
641: exceptionListeners.remove(listener);
642: }
643:
644: /**
645: *
646: * @param source
647: * @param exception
648: */
649: public void reportException(Object source, Throwable exception) {
650: ExceptionEvent ee = new ExceptionEvent(source, exception);
651:
652: for (ExceptionListener el : exceptionListeners
653: .toArray(new ExceptionListener[exceptionListeners
654: .size()])) {
655: el.exceptionOccurred(ee);
656: if (ee.isStopPropagation())
657: break;
658: }
659:
660: if (!ee.isSuppressLogging())
661: log.log(Level.SEVERE, null, exception);
662:
663: if (!ee.isCanceled()) {
664: final int msgSize = 300;
665: final int gap = 5;
666: final Dialog d = new Dialog("System Exception");
667: String image = "WARNING";
668: Image img = null;
669:
670: if (getRelativeFile(image).exists()) {
671: img = new Image(image);
672: img.setBounds(gap, gap, 40, 40);
673: d.getChildren().add(img);
674: }
675:
676: final TextArea taUserMessage = new TextArea(ee
677: .getDefaultMessage());
678: taUserMessage.setBounds(img == null ? gap : img.getX()
679: + img.getWidth() + gap, gap, msgSize, 60);
680: taUserMessage.setEnabled(false);
681: d.getChildren().add(taUserMessage);
682:
683: final Button bDetail = new Button("Show Details >>");
684: bDetail.setSize(90, 25);
685: bDetail.setPosition(taUserMessage.getX()
686: + taUserMessage.getWidth() - bDetail.getWidth(),
687: taUserMessage.getY() + taUserMessage.getHeight()
688: + gap);
689: d.getChildren().add(bDetail);
690:
691: final TextArea taDetail = new TextArea(ee
692: .getStackTraceText());
693:
694: final Button bOk = new Button();
695: bOk.setBounds(bDetail.getX() - gap - bDetail.getWidth(),
696: bDetail.getY(), bDetail.getWidth(), bDetail
697: .getHeight());
698: bOk.setText("OK");
699: d.getChildren().add(bOk);
700: bOk.addActionListener(Button.ACTION_CLICK,
701: new ActionListener() {
702: public void actionPerformed(ActionEvent ev) {
703: d.setVisible(false);
704: }
705: });
706:
707: taDetail.setBounds(taUserMessage.getX(), bOk.getY()
708: + bOk.getHeight() + gap, msgSize * 2, msgSize);
709: taDetail.setVisible(false);
710: d.getChildren().add(taDetail);
711:
712: int x = (getFrame().getInnerWidth() / 2)
713: - ((d.getWidth() + msgSize) / 2);
714: int y = (getFrame().getInnerHeight() / 2)
715: - ((d.getHeight() + msgSize) / 2);
716: if (x < 0)
717: x = 0;
718: if (y < 0)
719: y = 0;
720: d.setPosition(x, y);
721:
722: final int normalWidth = 6 + taUserMessage.getX()
723: + taUserMessage.getWidth() + gap;
724: final int normalHeight = 25 + bDetail.getY()
725: + bDetail.getHeight() + gap;
726: d.setSize(normalWidth, normalHeight);
727: final int[] detailSize = new int[4];
728: detailSize[0] = normalWidth + msgSize;
729: detailSize[1] = normalHeight + msgSize + gap;
730: detailSize[2] = taDetail.getWidth();
731: detailSize[3] = taDetail.getHeight();
732:
733: final PropertyChangeListener dialogSizePCL = new PropertyChangeListener() {
734: public void propertyChange(PropertyChangeEvent pce) {
735: int diff = ((Integer) pce.getNewValue())
736: - ((Integer) pce.getOldValue());
737:
738: if (pce.getPropertyName().equals(
739: Dialog.PROPERTY_WIDTH)) {
740: diff = taDetail.getWidth() + diff;
741: if (diff < 10)
742: diff = 10;
743: taDetail.setWidth(diff);
744: } else {
745: diff = taDetail.getHeight() + diff;
746: if (diff < 10)
747: diff = 10;
748: taDetail.setHeight(diff);
749: }
750: }
751: };
752:
753: bDetail.addActionListener(Button.ACTION_CLICK,
754: new ActionListener() {
755: public void actionPerformed(ActionEvent ev) {
756: if (bDetail.getText().equals(
757: "Show Details >>")) {
758: bDetail.setText("<< Hide Details");
759: d.setResizeAllowed(true);
760: d.setSize(detailSize[0], detailSize[1]);
761: taDetail.setSize(detailSize[2],
762: detailSize[3]);
763: taDetail.setVisible(true);
764: d
765: .addPropertyChangeListener(
766: new String[] {
767: Dialog.PROPERTY_WIDTH,
768: Dialog.PROPERTY_HEIGHT },
769: dialogSizePCL);
770: } else if (bDetail.getText().equals(
771: "<< Hide Details")) {
772: d
773: .removePropertyChangeListener(dialogSizePCL);
774: bDetail.setText("Show Details >>");
775: d.setResizeAllowed(false);
776: d.setSize(normalWidth, normalHeight);
777: taDetail.setVisible(false);
778: }
779: }
780: });
781:
782: d.setVisible(true);
783: }
784: }
785:
786: /**
787: * Gets the <code>Frame</code> that represents the primary application window.
788: * @return the <code>Frame</code> that represents the primary application window.
789: */
790: public Frame getFrame() {
791: if (frame == null)
792: frame = new Frame();
793: return frame;
794: }
795:
796: /**
797: * Returns the <code>Component</code> that previously had focus in the Application. The <code>Component</code> that
798: * previously had focus is the last <code>Component</code> in ANY window that held the focus. This is different from the
799: * behavior of the focus property in which their is one <code>Component</code> with focus PER window.
800: * @return the <code>Component</code> that previously had focus in the Application, or null if no <code>Component</code> has
801: * had prior focus.
802: * @see Component#isFocus()
803: * @see Component#setFocus(boolean)
804: */
805: public Component getPriorFocus() {
806: return priorFocus.get();
807: }
808:
809: protected void loadStyleSheet(String styleSheet) {
810: XOD props = new XOD();
811: String sheet = styleSheet == null ? DEFAULT_STYLE_SHEET
812: : styleSheet;
813: sheet = getSystemFile(sheet);
814: if (!sheet.endsWith(".xml"))
815: sheet += "/Style.xml";
816: props.execute(sheet);
817: compTypeToStyle = buildStyleMap(props);
818: systemColors = new HashMap<String, Color>();
819: systemImages = new HashMap<String, String>();
820:
821: for (Map.Entry<String, Object> e : props.getObjectMap()
822: .entrySet()) {
823: String name = e.getKey();
824: Object value = e.getValue();
825:
826: if (value instanceof Color) {
827: Color color = Color.valueOf(name); //Just verify that the specified systemColor is valid;
828: if (!color.isSystemColor())
829: throw new UnsupportedOperationException(
830: "You can only override system colors in the style sheet: "
831: + name);
832: systemColors.put(color.toString(), (Color) value);
833: } else if (!(value instanceof Style)) {
834: throw new UnsupportedOperationException(
835: "Unsupported object type in style sheet:"
836: + value.getClass());
837: }
838: }
839:
840: for (Color c : Color.values()) {
841: if (c.isSystemColor()) {
842: String name = c.toString();
843: if (systemColors.get(name) == null)
844: systemColors.put(name, c);
845: }
846: }
847:
848: for (Map.Entry<String, String> e : props.getPropertyMap()
849: .entrySet()) {
850: String key = e.getKey();
851:
852: if (key.startsWith("image.")) {
853: key = key.substring(6).replace('.', '_').toUpperCase();
854: systemImages.put(key, e.getValue());
855: }
856: }
857: }
858:
859: protected String getSystemFile(String value) {
860: if (!value.matches("^\\w{3,}://.*")) {
861: if (value != null && !value.matches("^\\w?:?[\\\\|/].*"))
862: value = getBaseFolder() + File.separator + value;
863:
864: try {
865: value = new File(value).getCanonicalPath();
866: } catch (IOException e) {
867: return value;
868: }
869: }
870:
871: return value;
872: }
873:
874: protected Map<Class<? extends Component>, Style> getDefaultStyles() {
875: return compTypeToStyle;
876: }
877:
878: protected Map<String, Color> getSystemColors() {
879: return systemColors;
880: }
881:
882: protected Map<String, String> getSystemImages() {
883: return systemImages;
884: }
885:
886: void setPriorFocus(Component comp) {
887: priorFocus = new WeakReference<Component>(comp);
888: }
889:
890: /**
891: * @return the base folder of the system
892: */
893: public abstract String getBaseFolder();
894:
895: /**
896: *
897: * @param pathname
898: * @return
899: */
900: public File getRelativeFile(String pathname) {
901: return new File(appendBaseFolder(pathname));
902: }
903:
904: /**
905: *
906: * @param parent
907: * @param child
908: * @return
909: */
910: public File getRelativeFile(String parent, String child) {
911: return new File(appendBaseFolder(parent), child);
912: }
913:
914: private String appendBaseFolder(String s) {
915: String ret = null;
916:
917: if (s != null && !s.matches("^\\w?:?[\\\\|/].*")) {
918: if (ret == null)
919: ret = getBaseFolder() + File.separator + s;
920: }
921:
922: ret = (ret == null ? s : ret);
923: return ret;
924: }
925:
926: protected Object setPackagePrivateMember(String memberName,
927: Component comp, Object value) {
928: if (memberName.equals("renderer")) {
929: ((AbstractComponent) comp).setRenderer((Renderer) value);
930: } else if (memberName.equals("innerWidth")) {
931: ((Frame) comp).setInnerWidth((Integer) value);
932: } else if (memberName.equals("innerHeight")) {
933: ((Frame) comp).setInnerHeight((Integer) value);
934: } else if (memberName.equals("frameSize")) {
935: Integer[] size = (Integer[]) value;
936: ((Frame) comp).sizeChanged(size[0], size[1]);
937: } else if (memberName.equals("initDDGBView")) {
938: DropDownGridBox.DefaultView v = new DropDownGridBox.DefaultView();
939: v.init(null, (GridBox) value);
940: return v;
941: }
942: return null;
943: }
944:
945: protected abstract void captureThread();
946:
947: protected abstract void releaseThread();
948:
949: protected abstract void showWindow(Window w);
950:
951: protected abstract void hideWindow(Window w);
952:
953: protected abstract FileChooser.FileInfo getFileInfo();
954:
955: protected abstract String getQualifiedURL(String location);
956:
957: protected abstract void removeFileFromMap(String location);
958:
959: /**
960: *
961: * @param task
962: * @param milliseconds
963: */
964: public abstract void addTimerTask(Runnable task, long milliseconds);
965:
966: /**
967: *
968: * @param task
969: * @param milliseconds
970: * @param repeat
971: */
972: public abstract void addTimerTask(Runnable task, long milliseconds,
973: boolean repeat);
974:
975: /**
976: *
977: * @param task
978: */
979: public abstract void resetTimerTask(Runnable task);
980:
981: /**
982: *
983: * @param task
984: */
985: public abstract void removeTimerTask(Runnable task);
986: }
|