001: package net.xoetrope.swing;
002:
003: import java.io.BufferedWriter;
004: import java.io.File;
005: import java.io.FileOutputStream;
006: import java.io.OutputStreamWriter;
007: import java.net.URL;
008:
009: import java.awt.BorderLayout;
010: import java.awt.Component;
011: import java.awt.Container;
012: import java.awt.Dimension;
013: import java.awt.Toolkit;
014: import java.awt.Window;
015: import java.awt.event.WindowEvent;
016: import java.awt.event.WindowListener;
017: import javax.swing.JApplet;
018: import javax.swing.JFrame;
019: import javax.swing.JMenuBar;
020: import javax.swing.JWindow;
021:
022: import net.xoetrope.data.XDataSource;
023: import net.xoetrope.debug.DebugLogger;
024: import net.xoetrope.xui.XComponentConstructor;
025: import net.xoetrope.xui.XComponentFactory;
026: import net.xoetrope.xui.XPage;
027: import net.xoetrope.xui.XPageDisplay;
028: import net.xoetrope.xui.XPageLoader;
029: import net.xoetrope.xui.XPageManager;
030: import net.xoetrope.xui.XProject;
031: import net.xoetrope.xui.XProjectManager;
032: import net.xoetrope.xui.XResourceManager;
033: import net.xoetrope.xui.XTarget;
034: import net.xoetrope.xui.build.BuildProperties;
035: import net.xoetrope.xui.style.XStyleManager;
036: import net.xoetrope.xui.XLifeCycleListener;
037: import java.lang.reflect.Method;
038: import net.xoetrope.builder.XuiBuilder;
039: import javax.swing.SwingUtilities;
040:
041: /**
042: * <p>This class is constructed with a window or frame
043: * and can be part of an applet or an application. The class acts as the main
044: * entry point to an XUI application and provides some of the infrastructure
045: * needed to support the application.</p>
046: * <p>The applet can provide support for a frameset or a single page. Page display
047: * functions are also supported to allow the application to display more than a
048: * single page or change the page that is displayed.</p>
049: * <p>By choosing either the AWT or Swing version of the XApplet you choose to
050: * have either an AWT or a Swing application/applet. In general once this choice
051: * has been made you should not mix toolkits.</p>
052: * <p>Copyright: Copyright (c) Xoetrope Ltd., 1998-2003<br>
053: * License: see license.txt
054: * @version $Revision: 1.24 $
055: */
056: public class XApplet extends JApplet implements WindowListener,
057: XPageDisplay {
058: protected XProject project;
059: protected static Window appWindow;
060: protected static JFrame clientFrame;
061: protected XPageManager pageMgr;
062: protected boolean bUseWindow;
063: protected XDataSource modelDataSource;
064: protected static Class defaultSourceClass = net.xoetrope.data.XDataSource.class;
065: protected static XApplet this Applet;
066: protected int clientWidth = 800;
067: protected int clientHeight = 600;
068:
069: /**
070: * main method to be invoked as an application. This method is invoked as the
071: * entry point to the 'Application', it is not used if an Applet is being
072: * launched. This method establishes the frame within which the application
073: * runs. If overloading this method remeber to call the setup method.
074: */
075: public static void main(String args[]) {
076: final String[] myArgs = args;
077: SwingUtilities.invokeLater(new Runnable() {
078: public void run() {
079: // createSplashScreen();
080: loadUI(myArgs);
081: }
082: });
083: }
084:
085: /**
086: * Do the actual work of loading the UI
087: * @param args
088: */
089: private static void loadUI(String[] args) {
090: JFrame frame = new JFrame();
091: XApplet applet = new XApplet();
092: frame.getContentPane().add(applet);
093: applet.setup(frame, args);
094: }
095:
096: /**
097: * A default constructor. Most of the setup work is actually done by the initialize
098: * method and is called by the main method or the init method depending on
099: * whether or not an application of applet is being launched.
100: */
101: public XApplet() {
102: this Applet = this ;
103: }
104:
105: /**
106: * Setup the applet by setting paths and then initializing the applet. This
107: * method is a stand-in for the main method, processing the commandline
108: * parameters, so that its work is reusable and
109: * does not needed to be redone by derived classes. This method should not be
110: * called directly by user code.
111: * @param frame the owner frame
112: * @param args the command line arguments
113: */
114: protected void setup(JFrame frame, String[] args) {
115: setResourceFile(args.length > 0 ? (String) args[0]
116: : "startup.properties");
117:
118: String classPackageName = args.length > 1 ? args[1]
119: : XPage.XUI_SWING_PACKAGE;
120: XResourceManager.setPackageName(classPackageName);
121:
122: XPageLoader pageLoader = getSecondaryClassLoader(classPackageName);
123: if (pageLoader != null)
124: XProjectManager.getPageManager().setSecondaryLoader(
125: pageLoader);
126:
127: String icon = XProjectManager.getCurrentProject()
128: .getStartupParam("Icon");
129: if (icon != null)
130: frame.setIconImage(XProjectManager.getResourceManager()
131: .getImage(icon));
132:
133: initialise(frame);
134: }
135:
136: /**
137: * <p>Construct a new builder and set the default package. XUI sometimes uses
138: * additional class loaders to find the resources needed in a project. By default
139: * XUI uses the XuiBuilder class loader to convert XML files to Java classes.</p>
140: * <p>A custom class loader can be referenced in the startup properties file
141: * using the 'BuilderClass' property. Once this property has been determined
142: * this method will instantiate an instance of that class if necessary.
143: * @param packageName the name of the default widget package e.g. net.xoetrope.awt,
144: * this is normally defined as a result of choosing the appropriate version of
145: * the XApplet class
146: */
147: protected XPageLoader getSecondaryClassLoader(String packageName) {
148: String builderClass = XProjectManager.getCurrentProject()
149: .getStartupParam("BuilderClass");
150: if (builderClass != null) {
151: try {
152: XPageLoader pageLoader = (XPageLoader) Class.forName(
153: builderClass).newInstance();
154: pageLoader.setPackageName(packageName);
155: return pageLoader;
156: } catch (Exception ex) {
157: DebugLogger.logError("Unable to load builder class: "
158: + ex.getMessage());
159: }
160: }
161:
162: try {
163: XPageLoader pageLoader = (XPageLoader) Class.forName(
164: "net.xoetrope.builder.XuiBuilder").newInstance();
165: pageLoader.setPackageName(packageName);
166: return pageLoader;
167: } catch (Exception ex) {
168: DebugLogger.logError("Unable to load XuiBuilder class:"
169: + ex.getMessage());
170: }
171: return null;
172: }
173:
174: /**
175: * Sets the default datasource class. The default data source will be used to
176: * provide any initial data for the XModel. Normally this data is static data
177: * that will be used to populate things like lists and provide default values.
178: * @param className the name of the datasource class
179: * e.g. net.xoetrope.data.XDataSource.class, this class reads data from an XML file
180: */
181: public static void setDefaultDataSource(String className) {
182: try {
183: defaultSourceClass = Class.forName(className);
184: } catch (ClassNotFoundException ex) {
185: }
186: }
187:
188: /**
189: * Setup the default resource file for the application and load some of the
190: * information in it. The resource file is the startup properties file
191: * @param startFile The name of the file to be loaded, by default startup.properties
192: */
193: protected void setResourceFile(String startFile) {
194: project = XProjectManager.getCurrentProject();
195: project.initialise(startFile);
196:
197: try {
198: String sUseWindow = project.getStartupParam("UseWindow");
199: bUseWindow = sUseWindow.compareTo("true") == 0 ? true
200: : false;
201: String temp = project.getStartupParam("ClientWidth");
202: if (temp != null)
203: clientWidth = Integer.parseInt(temp);
204: temp = project.getStartupParam("ClientHeight");
205: if (temp != null)
206: clientHeight = Integer.parseInt(temp);
207:
208: // Check for subclassing of the datasource/model
209: temp = project.getStartupParam("XDataSourceClass");
210: if (temp != null) {
211: try {
212: defaultSourceClass = Class.forName(temp);
213: } catch (Exception ex) {
214: }
215: }
216: } catch (Exception ex) {
217: }
218: }
219:
220: /**
221: * Invoked when used as an applet. Sets up the startup file and initialises
222: * the application. Reads the applet parameters and calls initialize.
223: */
224: public void init() {
225: setResourceFile(getParameter("StartFile"));
226: String classPackageName = getParameter("StartPackage");
227: XResourceManager.setPackageName(classPackageName);
228: XProjectManager.getPageManager().setSecondaryLoader(
229: getSecondaryClassLoader(classPackageName));
230: initialise(getFrame());
231: }
232:
233: /**
234: * Gets the Frame containing the applet.
235: * @return Frame which is the applet or application's parent
236: */
237: public JFrame getFrame() {
238: Container parent;
239: for (parent = getParent(); parent != null; parent = parent
240: .getParent()) {
241: if ((parent != null) && (parent instanceof JFrame))
242: return (JFrame) parent;
243: }
244: return null;
245: }
246:
247: /**
248: * <p>Generic function which is called from the constructor if it's an application
249: * or from start if its an applet.</p>
250: * The initialization process proceeds in the following order<br>
251: * <ol>
252: * <li>Register the component factories</li>
253: * <li>Setup the project class</li>
254: * <li>Setup the page manager</li>
255: * <li>Setup the resource manager</li>
256: * <li>Setup the style manager</li>
257: * <li>Size the main window</li>
258: * <li>Set the layout</li>
259: * <li>Add a shutdown hook</li>
260: * <li>Display the main window</li>
261: * </ol>
262: * @param f The Frame which acts as the parent.
263: * the style manager
264: */
265: protected void initialise(JFrame f) {
266: register();
267:
268: project = XProjectManager.getCurrentProject();
269:
270: clientFrame = f;
271: XProjectManager.getPageManager();
272: XProjectManager.getResourceManager();
273: try {
274: URL db = getDocumentBase();
275: project.getResourceManager().setDocumentBase(db);
276: } catch (Exception e) {
277: }
278:
279: XStyleManager styleMgr = XProjectManager.getStyleManager();
280:
281: if (f != null)
282: f.setSize(clientWidth, clientHeight);
283: setSize(new Dimension(clientWidth, clientHeight));
284:
285: appWindow = null;
286: if (bUseWindow) {
287: appWindow = new JWindow(f);
288: appWindow.setSize(clientWidth, clientHeight);
289: appWindow.addWindowListener(this );
290: f.addWindowListener(this );
291: } else {
292: appWindow = f;
293: if (f != null)
294: f.addWindowListener(this );
295: }
296:
297: XResourceManager.setApplet(this );
298: XResourceManager.setAppFrame(clientFrame);
299: XResourceManager.setAppWindow(appWindow);
300:
301: // Try to create the shutdown hook, this is not supported on all VMs
302: try {
303: Object shutdownHook = Class.forName(
304: "net.xoetrope.xui.build.conditional.ShutdownHook")
305: .newInstance();
306:
307: // Try to register a listener for the startup and shutdown events.
308: String lifeCycleObjectName = project
309: .getStartupParam("LifeCycleListener");
310: if ((lifeCycleObjectName != null)
311: && (lifeCycleObjectName.length() > 0)) {
312: XLifeCycleListener lifeCycleObject = (XLifeCycleListener) Class
313: .forName(lifeCycleObjectName).newInstance();
314: lifeCycleObject.initialize();
315:
316: Class params[] = new Class[1];
317: Object args[] = new Object[1];
318: params[0] = XLifeCycleListener.class;
319: args[0] = lifeCycleObject;
320: Method method = shutdownHook.getClass().getMethod(
321: "addLifeCycleListener", params);
322: method.invoke(shutdownHook, args);
323: }
324: } catch (Exception ex) {
325: ex.printStackTrace();
326: }
327:
328: Dimension screenSize = Toolkit.getDefaultToolkit()
329: .getScreenSize();
330: if (bUseWindow) {
331: Dimension frameSize = appWindow.getSize();
332: appWindow.setLocation(
333: (screenSize.width - frameSize.width) / 2,
334: (screenSize.height - frameSize.height) / 2);
335: appWindow.setVisible(true);
336: f.setLocation(screenSize.width / 2, screenSize.height / 2);
337: f.setSize(0, 0);
338: f.setVisible(true);
339: } else if (f != null) {
340: Dimension frameSize = f.getSize();
341: String center = null;
342: try {
343: center = project.getStartupParam("CenterWin");
344: } catch (Exception ex) {
345: }
346: if ((center != null) && (center.compareTo("true") == 0))
347: f.setLocation((screenSize.width - frameSize.width) / 2,
348: (screenSize.height - frameSize.height) / 2);
349: f.setVisible(true);
350: }
351:
352: setContent(f);
353: if (appWindow != null) {
354: appWindow.invalidate();
355: appWindow.validate();
356: appWindow.repaint();
357: }
358: }
359:
360: /**
361: * Load the componentFactories by reading the factory names from the startup
362: * file. The value of the 'NumComponentFactories' is first read and then
363: * the value of ComponentFactory<X> (i.e. ComponentFactory0, ComponentFactory1)
364: * where <X> is the number, starting at zero, of the factory. Each factory is
365: * then instantiated and will be called upon to construct components in the
366: * order in which the factories were registered.
367: */
368: protected void register() {
369: int numFactories = 0;
370: String numFactoriesStr = XProjectManager.getCurrentProject()
371: .getStartupParam("NumComponentFactories");
372:
373: if (numFactoriesStr != null)
374: numFactories = new Integer(numFactoriesStr).intValue();
375:
376: for (int i = 0; i < numFactories; i++) {
377: String factoryName = XProjectManager.getCurrentProject()
378: .getStartupParam("ComponentFactory" + i);
379: try {
380: if (factoryName != null)
381: XComponentFactory
382: .registerComponentFactory((XComponentConstructor) Class
383: .forName(factoryName).newInstance());
384: } catch (Exception ex) {
385: if (BuildProperties.DEBUG)
386: DebugLogger.logError("Factory not loaded: "
387: + ex.getMessage());
388: }
389: }
390: }
391:
392: /**
393: * Load the content into the model. In the process an instance of the default
394: * model data source class is instantiated and reads data from a file pointed
395: * to by the 'ModelData' startup parameter.
396: * @param f the frame
397: */
398: protected void setContent(JFrame f) {
399: boolean sourceExists = false;
400: try {
401: modelDataSource = (XDataSource) defaultSourceClass
402: .newInstance();
403: } catch (Exception ex2) {
404: return;
405: }
406:
407: try {
408: String fileName = project.getStartupParam("ModelData");
409: if (fileName != null) {
410: try {
411: modelDataSource.read(project.getResourceManager()
412: .getBufferedReader(fileName, null));
413: } catch (Exception ex3) {
414: if (BuildProperties.DEBUG)
415: DebugLogger.logError("Could not access file:"
416: + fileName);
417: }
418: }
419: } catch (Exception ex) {
420: if (BuildProperties.DEBUG)
421: DebugLogger.logError("Exception in setContent");
422: }
423:
424: try {
425: if (f != null)
426: f.setTitle(project.getStartupParam("Title"));
427: } catch (Exception ex1) {
428: ex1.printStackTrace();
429: }
430: setHome();
431: }
432:
433: /**
434: * Set the home page using the startup properties. The home page is established
435: * by combining the two startup properties 'StartPackage' and 'StartClass'. By
436: * default 'StartClass' is set to a value of 'home. Both startup parameters
437: * are optional. Load the first page for the application.
438: */
439: public void setHome() {
440: try {
441: String packageName = project
442: .getStartupParam("StartPackage");
443: String homePage = project.getStartupParam("StartClass");
444: if ((packageName == null) && (homePage == null))
445: homePage = "home";
446: if (packageName != null)
447: packageName += ".";
448:
449: pageMgr = XProjectManager.getPageManager();
450: pageMgr.setPackageName(packageName);
451: pageMgr.setPageDisplay(this );
452:
453: if (homePage != null)
454: pageMgr.showPage(homePage);
455: } catch (Exception ex) {
456: ex.printStackTrace();
457: }
458: }
459:
460: /**
461: * Called when a page has been added or shown via the XPageManager. The page manager
462: * then requests that the applet/application then displays the page in the
463: * appropriate location.
464: * @param page The XPage which has been loaded.
465: * @return the page being displayed
466: */
467: public XPage displayPage(XPage page) {
468: return displayPage(page, null);
469: }
470:
471: /**
472: * <p>Called when a page has been added or shown via the XPageManager. The page manager
473: * then requests that the applet/application then displays the page in the
474: * appropriate location.</p>
475: * <p>
476: * The sequence with which the page transition occurs is as follows:
477: * </p>
478: * <li>Find the appropriate target area</li>
479: * <li>Ask the page to make its components non visible (to avoid flicker during the page update)</li>
480: * <li>Set the page size</li>
481: * <li>Save the current page's data by calling 'saveBoundComponentValues</li>
482: * <li>Mark the current page as deactivated, and call the page's deactivated() method</li>
483: * <li>Remove the old page from the container</li>
484: * <li>Add the new page</li>
485: * <li>Update the new page's bindings by calling updateBindings()</li>
486: * <li>Update the new page's data by calling updateBoundComponentValues()</li>
487: * <li>Layout the container and request it to repaint</li>
488: * <li>Show the new page</li>
489: * <li>Mark the new page as activated, and call its pageActivated method</li>
490: * <ol>
491: * </ol>
492: * @param page The XPage which has been loaded.
493: * @param target the area to update
494: * @return the page being displayed
495: */
496: public XPage displayPage(XPage page, String target) {
497: Container targetContainer = findTarget(target);
498:
499: page.setSize(new Dimension(clientWidth, clientHeight));
500:
501: page.setVisible(true);
502:
503: XPage lastPage = null;
504: try {
505: lastPage = (XPage) targetContainer.getComponent(0);
506: } catch (Exception e) {
507: }
508:
509: if (page != lastPage) {
510: targetContainer.add(page, BorderLayout.CENTER);
511:
512: if (lastPage != null) {
513: lastPage.saveBoundComponentValues();
514: targetContainer.remove(lastPage);
515: page.setStatus(XPage.DEACTIVATED);
516: try {
517: lastPage.pageDeactivated();
518: } catch (Exception ex1) {
519: if (BuildProperties.DEBUG)
520: DebugLogger
521: .logError("The pageDeactivated method could not be invoked due to an exception");
522: }
523: }
524: page.updateBindings();
525: page.updateBoundComponentValues();
526:
527: targetContainer.doLayout();
528: page.doLayout();
529: } else {
530: page.updateBindings();
531: page.updateBoundComponentValues();
532: }
533:
534: targetContainer.doLayout();
535: page.setVisible(true);
536: page.repaint();
537:
538: page.validate();
539: page.doLayout();
540: page.setStatus(XPage.ACTIVATED);
541: try {
542: page.pageActivated();
543: } catch (Exception ex) {
544: if (BuildProperties.DEBUG) {
545: DebugLogger
546: .logError("The pageActivated method could not be invoked due to an exception");
547: ex.printStackTrace();
548: }
549: }
550:
551: return page;
552: }
553:
554: /**
555: * Remove the page from container. The page is simply removed and the container validated.
556: * @param page
557: */
558: public void hidePage(XPage page) {
559: XPage lastPage = null;
560: Container container = bUseWindow ? (Container) ((JWindow) appWindow)
561: .getContentPane()
562: : (Container) getContentPane();
563:
564: try {
565: lastPage = (XPage) container.getComponent(0);
566:
567: if ((lastPage != null) && (page != lastPage)) {
568: container.remove(lastPage);
569:
570: page.setStatus(XPage.DEACTIVATED);
571: page.pageDeactivated();
572:
573: container.doLayout();
574: validate();
575: }
576: } catch (Exception e) {
577: if (BuildProperties.DEBUG)
578: DebugLogger
579: .logError("The page could not be hidden due to an exception: "
580: + e.getMessage());
581: }
582: }
583:
584: /**
585: * Finds the target area for a page display request, this only applies to a
586: * frameset. If there is no frameset the main area or default area is named
587: * 'content' and this area is used if null is used as the target name.
588: * @param target the target area name
589: * @return the container to be updated
590: */
591: public Container findTarget(String target) {
592: Container targetContainer = bUseWindow ? (Container) ((JWindow) appWindow)
593: .getContentPane()
594: : (Container) getContentPane();
595: if (target == null)
596: target = "content";
597:
598: Component[] comps = targetContainer.getComponents();
599: int numChildren = comps.length;
600: for (int i = 0; i < numChildren; i++) {
601: if (comps[i].getName().compareTo(target) == 0)
602: return (XTarget) comps[i];
603: }
604: return targetContainer;
605: }
606:
607: /**
608: * Get the target for a page display request. The target areas are stored in
609: * the order in which they were declared or added.
610: * @param idx the target area
611: * @return the container to be updated
612: */
613: public Container getTarget(int idx) {
614: Container targetContainer = bUseWindow ? (Container) ((JWindow) appWindow)
615: .getContentPane()
616: : (Container) getContentPane();
617: Component[] comps = targetContainer.getComponents();
618: int numChildren = comps.length;
619: return (XTarget) comps[idx];
620: }
621:
622: /**
623: * Get the number of targets in the container
624: */
625: public int getNumTargets() {
626: Container targetContainer = bUseWindow ? (Container) ((JWindow) appWindow)
627: .getContentPane()
628: : (Container) getContentPane();
629: return targetContainer.getComponentCount();
630: }
631:
632: /**
633: * Add a new frame or target area to a frameset
634: * @param name the frame name
635: * @param constraint the BorderlayoutConstraint
636: * @param preferredWidth the preferred width
637: * @param preferredHeight the preferred height
638: */
639: public void addTarget(String name, Object constraint,
640: int preferredWidth, int preferredHeight) {
641: Container root = bUseWindow ? (Container) ((JWindow) appWindow)
642: .getContentPane() : (Container) getContentPane();
643: XTarget container = new XTarget(name, preferredWidth,
644: preferredHeight);
645: root.add(container, constraint);
646: }
647:
648: /**
649: * Adds a target frame to the frameset
650: * @param frame the new traget frame
651: */
652: public void addTarget(XTarget frame, Object constraint) {
653: if (bUseWindow)
654: ((JWindow) appWindow).getContentPane().add(frame,
655: constraint);
656: else
657: getContentPane().add(frame, constraint);
658: }
659:
660: /**
661: * Exit the application
662: */
663: public void windowClosing(WindowEvent e) {
664: System.exit(0);
665: }
666:
667: /**
668: * Get the width of the main window. Retrieved from the startup file or
669: * defaulted
670: */
671: public int getClientWidth() {
672: return clientWidth;
673: }
674:
675: /**
676: * Get the height of the main window. Retrieved from the startup file or
677: * defaulted
678: */
679: public int getClientHeight() {
680: return clientHeight;
681: }
682:
683: /**
684: * Set the menubar.
685: * @param mb
686: */
687: public static void setMenuBar(JMenuBar mb) {
688: this Applet.setJMenuBar(mb);
689: }
690:
691: /**
692: * Get the menubar.
693: * @param mb
694: */
695: public static JMenuBar getMenuBar() {
696: return this Applet.getJMenuBar();
697: }
698:
699: /**
700: * Template method, unused at present
701: */
702: public void windowActivated(WindowEvent e) {
703: }
704:
705: /**
706: * Template method, unused at present
707: */
708: public void windowClosed(WindowEvent e) {
709: }
710:
711: /**
712: * Template method, unused at present
713: */
714: public void windowDeiconified(WindowEvent e) {
715: }
716:
717: /**
718: * Template method, unused at present
719: */
720: public void windowDeactivated(WindowEvent e) {
721: }
722:
723: /**
724: * Template method, unused at present
725: */
726: public void windowIconified(WindowEvent e) {
727: }
728:
729: /**
730: * Template method, unused at present
731: */
732: public void windowOpened(WindowEvent e) {
733: }
734: }
|