001: /* *************************************************************************
002:
003: Millstone(TM)
004: Open Sourced User Interface Library for
005: Internet Development with Java
006:
007: Millstone is a registered trademark of IT Mill Ltd
008: Copyright (C) 2000-2005 IT Mill Ltd
009:
010: *************************************************************************
011:
012: This library is free software; you can redistribute it and/or
013: modify it under the terms of the GNU Lesser General Public
014: license version 2.1 as published by the Free Software Foundation.
015:
016: This library is distributed in the hope that it will be useful,
017: but WITHOUT ANY WARRANTY; without even the implied warranty of
018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: Lesser General Public License for more details.
020:
021: You should have received a copy of the GNU Lesser General Public
022: License along with this library; if not, write to the Free Software
023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024:
025: *************************************************************************
026:
027: For more information, contact:
028:
029: IT Mill Ltd phone: +358 2 4802 7180
030: Ruukinkatu 2-4 fax: +358 2 4802 7181
031: 20540, Turku email: info@itmill.com
032: Finland company www: www.itmill.com
033:
034: Primary source for MillStone information and releases: www.millstone.org
035:
036: ********************************************************************** */
037:
038: package org.millstone.base.ui;
039:
040: import org.millstone.base.terminal.DownloadStream;
041: import org.millstone.base.terminal.ParameterHandler;
042: import org.millstone.base.terminal.Resource;
043: import org.millstone.base.terminal.Sizeable;
044: import org.millstone.base.terminal.URIHandler;
045: import org.millstone.base.terminal.Terminal;
046: import org.millstone.base.terminal.PaintTarget;
047: import org.millstone.base.terminal.PaintException;
048: import org.millstone.base.Application;
049:
050: import java.lang.ref.WeakReference;
051: import java.net.MalformedURLException;
052: import java.net.URL;
053: import java.util.HashMap;
054: import java.util.LinkedList;
055: import java.util.Map;
056: import java.util.Iterator;
057:
058: /**
059: * Application window component.
060: *
061: * @author IT Mill Ltd.
062: * @version
063: * 3.1.1
064: * @since 3.0
065: */
066: public class Window extends Panel implements URIHandler,
067: ParameterHandler {
068:
069: /** Window with no border */
070: public static final int BORDER_NONE = 0;
071:
072: /** Window with only minimal border */
073: public static final int BORDER_MINIMAL = 1;
074:
075: /** Window with default borders */
076: public static final int BORDER_DEFAULT = 2;
077:
078: /** The terminal this window is attached to */
079: private Terminal terminal = null;
080:
081: /** The applicaiton this window is attached to */
082: private Application application = null;
083:
084: /** List of URI handlers for this window */
085: private LinkedList uriHandlerList = null;
086:
087: /** List of parameter handlers for this window */
088: private LinkedList parameterHandlerList = null;
089:
090: /**
091: * Explicitly specified theme of this window. If null, application theme is
092: * used
093: */
094: private String theme = null;
095:
096: /** Resources to be opened automatically on next repaint */
097: private LinkedList openList = new LinkedList();
098:
099: /** The name of the window */
100: private String name = null;
101:
102: /** Window border mode */
103: private int border = BORDER_DEFAULT;
104:
105: /** Focused component */
106: private Focusable focusedComponent;
107:
108: /* ********************************************************************* */
109:
110: /**
111: * Create new empty unnamed window with default layout.
112: *
113: * <p>
114: * To show the window in application, it must be added to application with
115: * <code>Application.addWindow()</code> method.
116: * </p>
117: *
118: * <p>
119: * The windows are scrollable by default.
120: * </p>
121: *
122: * @param caption
123: * Title of the window
124: */
125: public Window() {
126: this ("", null);
127: }
128:
129: /**
130: * Create new empty window with default layout.
131: *
132: * <p>
133: * To show the window in application, it must be added to application with
134: * <code>Application.addWindow()</code> method.
135: * </p>
136: *
137: * <p>
138: * The windows are scrollable by default.
139: * </p>
140: *
141: * @param caption
142: * Title of the window
143: */
144: public Window(String caption) {
145: this (caption, null);
146: }
147:
148: /**
149: * Create new window.
150: *
151: * <p>
152: * To show the window in application, it must be added to application with
153: * <code>Application.addWindow()</code> method.
154: * </p>
155: *
156: * <p>
157: * The windows are scrollable by default.
158: * </p>
159: *
160: * @param caption
161: * Title of the window
162: * @param layout
163: * Layout of the window
164: */
165: public Window(String caption, Layout layout) {
166: super (caption, layout);
167: setScrollable(true);
168: }
169:
170: /**
171: * Get terminal type.
172: *
173: * @return Value of property terminal.
174: */
175: public Terminal getTerminal() {
176: return this .terminal;
177: }
178:
179: /* ********************************************************************* */
180:
181: /**
182: * Get window of the component. Returns the window where this component
183: * belongs to. If the component does not yet belong to a window the returns
184: * null.
185: *
186: * @return parent window of the component.
187: */
188: public final Window getWindow() {
189: return this ;
190: }
191:
192: /**
193: * Get application instance of the component. Returns the application where
194: * this component belongs to. If the component does not yet belong to a
195: * application the returns null.
196: *
197: * @return parent application of the component.
198: */
199: public final Application getApplication() {
200: return this .application;
201: }
202:
203: /**
204: * Getter for property parent. Parent is the visual parent of a component.
205: * Each component can belong to only one ComponentContainer at time.
206: *
207: * @return Value of property parent.
208: */
209: public final Component getParent() {
210: return null;
211: }
212:
213: /**
214: * Setter for property parent. Parent is the visual parent of a component.
215: * This is mostly called by containers add method. Setting parent is not
216: * allowed for the window, and thus this call should newer be called.
217: *
218: * @param parent
219: * New value of property parent.
220: */
221: public void setParent(Component parent) {
222: throw new RuntimeException(
223: "Setting parent for Window is not allowed");
224: }
225:
226: /**
227: * Get component UIDL tag.
228: *
229: * @return Component UIDL tag as string.
230: */
231: public String getTag() {
232: return "window";
233: }
234:
235: /* ********************************************************************* */
236:
237: /** Add new URI handler to this window */
238: public void addURIHandler(URIHandler handler) {
239: if (uriHandlerList == null)
240: uriHandlerList = new LinkedList();
241: synchronized (uriHandlerList) {
242: uriHandlerList.addLast(handler);
243: }
244: }
245:
246: /** Remove given URI handler from this window */
247: public void removeURIHandler(URIHandler handler) {
248: if (handler == null || uriHandlerList == null)
249: return;
250: synchronized (uriHandlerList) {
251: uriHandlerList.remove(handler);
252: if (uriHandlerList.isEmpty())
253: uriHandlerList = null;
254: }
255: }
256:
257: /**
258: * Handle uri recursively.
259: */
260: public DownloadStream handleURI(URL context, String relativeUri) {
261: DownloadStream result = null;
262: if (uriHandlerList != null) {
263: Object[] handlers;
264: synchronized (uriHandlerList) {
265: handlers = uriHandlerList.toArray();
266: }
267: for (int i = 0; i < handlers.length; i++) {
268: DownloadStream ds = ((URIHandler) handlers[i])
269: .handleURI(context, relativeUri);
270: if (ds != null) {
271: if (result != null)
272: throw new RuntimeException("handleURI for "
273: + context + " uri: '" + relativeUri
274: + "' returns ambigious result.");
275: result = ds;
276: }
277: }
278: }
279: return result;
280: }
281:
282: /* ********************************************************************* */
283:
284: /** Add new parameter handler to this window. */
285: public void addParameterHandler(ParameterHandler handler) {
286: if (parameterHandlerList == null)
287: parameterHandlerList = new LinkedList();
288: synchronized (parameterHandlerList) {
289: parameterHandlerList.addLast(handler);
290: }
291: }
292:
293: /** Remove given URI handler from this window. */
294: public void removeParameterHandler(ParameterHandler handler) {
295: if (handler == null || parameterHandlerList == null)
296: return;
297: synchronized (parameterHandlerList) {
298: parameterHandlerList.remove(handler);
299: if (parameterHandlerList.isEmpty())
300: parameterHandlerList = null;
301: }
302: }
303:
304: /* Documented by the interface */
305: public void handleParameters(Map parameters) {
306: if (parameterHandlerList != null) {
307: Object[] handlers;
308: synchronized (parameterHandlerList) {
309: handlers = parameterHandlerList.toArray();
310: }
311: for (int i = 0; i < handlers.length; i++)
312: ((ParameterHandler) handlers[i])
313: .handleParameters(parameters);
314: }
315: }
316:
317: /* ********************************************************************* */
318:
319: /**
320: * Get theme for this window.
321: *
322: * @return Name of the theme used in window. If the theme for this
323: * individual window is not explicitly set, the application theme is
324: * used instead. If application is not assigned the
325: * terminal.getDefaultTheme is used. If terminal is not set, null is
326: * returned
327: */
328: public String getTheme() {
329: if (theme != null)
330: return theme;
331: if ((application != null) && (application.getTheme() != null))
332: return application.getTheme();
333: if (terminal != null)
334: return terminal.getDefaultTheme();
335: return null;
336: }
337:
338: /**
339: * Set theme for this window.
340: *
341: * @param theme
342: * New theme for this window. Null implies the default theme.
343: */
344: public void setTheme(String theme) {
345: this .theme = theme;
346: requestRepaint();
347: }
348:
349: /**
350: * Paint the content of this component.
351: *
352: * @param event
353: * PaintEvent.
354: * @throws PaintException
355: * The paint operation failed.
356: */
357: public synchronized void paintContent(PaintTarget target)
358: throws PaintException {
359:
360: // Set the window name
361: target.addAttribute("name", getName());
362:
363: // Mark main window
364: if (getApplication() != null
365: && this == getApplication().getMainWindow())
366: target.addAttribute("main", true);
367:
368: // Open requested resource
369: synchronized (openList) {
370: if (!openList.isEmpty()) {
371: for (Iterator i = openList.iterator(); i.hasNext();)
372: ((OpenResource) i.next()).paintContent(target);
373: openList.clear();
374: }
375: }
376:
377: // Contents of the window panel is painted
378: super .paintContent(target);
379:
380: // Set focused component
381: if (this .focusedComponent != null)
382: target.addVariable(this , "focused", ""
383: + this .focusedComponent.getFocusableId());
384: else
385: target.addVariable(this , "focused", "");
386:
387: }
388:
389: /* ********************************************************************* */
390:
391: /**
392: * Open the given resource in this window.
393: */
394: public void open(Resource resource) {
395: synchronized (openList) {
396: openList.add(new OpenResource(resource, null, -1, -1,
397: BORDER_DEFAULT));
398: }
399: requestRepaint();
400: }
401:
402: /* ********************************************************************* */
403:
404: /**
405: * Open the given resource in named terminal window. Empty or
406: * <code>null</code> window name results the resource to be opened in this
407: * window.
408: */
409: public void open(Resource resource, String windowName) {
410: synchronized (openList) {
411: openList.add(new OpenResource(resource, windowName, -1, -1,
412: BORDER_DEFAULT));
413: }
414: requestRepaint();
415: }
416:
417: /* ********************************************************************* */
418:
419: /**
420: * Open the given resource in named terminal window with given size and
421: * border properties. Empty or <code>null</code> window name results the
422: * resource to be opened in this window.
423: */
424: public void open(Resource resource, String windowName, int width,
425: int height, int border) {
426: synchronized (openList) {
427: openList.add(new OpenResource(resource, windowName, width,
428: height, border));
429: }
430: requestRepaint();
431: }
432:
433: /* ********************************************************************* */
434:
435: /**
436: * Returns the full url of the window, this returns window specific url even
437: * for the main window.
438: *
439: * @return String
440: */
441: public URL getURL() {
442:
443: if (application == null)
444: return null;
445:
446: try {
447: return new URL(application.getURL(), getName() + "/");
448: } catch (MalformedURLException e) {
449: throw new RuntimeException(
450: "Internal problem, please report");
451: }
452: }
453:
454: /**
455: * Get the unique name of the window that indentifies it on the terminal.
456: *
457: * @return String
458: */
459: public String getName() {
460: return name;
461: }
462:
463: /**
464: * Returns the border.
465: *
466: * @return int
467: */
468: public int getBorder() {
469: return border;
470: }
471:
472: /**
473: * Sets the border.
474: *
475: * @param border
476: * The border to set
477: */
478: public void setBorder(int border) {
479: this .border = border;
480: }
481:
482: /**
483: * Sets the application this window is connected to.
484: *
485: * <p>
486: * This method should not be invoked directly. Instead the
487: * {@link org.millstone.base.Application#addWindow(Window)} method should be
488: * used to add the window to an application and
489: * {@link org.millstone.base.Application#removeWindow(Window)} method for
490: * removing the window from the applicion. These methods call this method
491: * implicitly.
492: * </p>
493: *
494: * <p>
495: * The method invokes {@link Component#attach()} and
496: * {@link Component#detach()} methods when necessary.
497: * <p>
498: *
499: * @param application
500: * The application to set
501: */
502: public void setApplication(Application application) {
503:
504: // If the application is not changed, dont do nothing
505: if (application == this .application)
506: return;
507:
508: // Send detach event if the window is connected to application
509: if (this .application != null) {
510: detach();
511: }
512:
513: // Connect to new parent
514: this .application = application;
515:
516: // Send attach event if connected to a window
517: if (application != null)
518: attach();
519: }
520:
521: /**
522: * Sets the name.
523: * <p>
524: * The name of the window must be unique inside the application. Also the
525: * name may only contain the following characters: a-z, A-Z and 0-9.
526: * </p>
527: *
528: * <p>
529: * If the name is null, the the window is given name automatically when it
530: * is added to an application.
531: * </p>
532: *
533: * @param name
534: * The name to set
535: */
536: public void setName(String name) {
537:
538: // The name can not be changed in application
539: if (getApplication() != null)
540: throw new IllegalStateException(
541: "Window name can not be changed while "
542: + "the window is in application");
543:
544: // Check the name format
545: if (name != null)
546: for (int i = 0; i < name.length(); i++) {
547: char c = name.charAt(i);
548: if (!(('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')))
549: throw new IllegalArgumentException(
550: "Window name can contain "
551: + "only a-z, A-Z and 0-9 characters: '"
552: + name + "' given.");
553: }
554:
555: this .name = name;
556: }
557:
558: /**
559: * Set terminal type. The terminal type is set by the the terminal adapter
560: * and may change from time to time.
561: *
562: * @param type
563: * terminal type to set
564: */
565: public void setTerminal(Terminal type) {
566: this .terminal = type;
567: }
568:
569: /**
570: * Window only supports pixels as unit.
571: *
572: * @see org.millstone.base.terminal.Sizeable#getHeightUnits()
573: */
574: public void setHeightUnits(int units) {
575: if (units != Sizeable.UNITS_PIXELS)
576: throw new IllegalArgumentException(
577: "Only pixels are supported");
578: }
579:
580: /**
581: * Window only supports pixels as unit.
582: *
583: * @see org.millstone.base.terminal.Sizeable#getWidthUnits()
584: */
585: public void setWidthUnits(int units) {
586: if (units != Sizeable.UNITS_PIXELS)
587: throw new IllegalArgumentException(
588: "Only pixels are supported");
589: }
590:
591: /** Private data structure for storing opening window properties */
592: private class OpenResource {
593:
594: private Resource resource;
595:
596: private String name;
597:
598: private int width;
599:
600: private int height;
601:
602: private int border;
603:
604: /** Create new open resource */
605: private OpenResource(Resource resource, String name, int width,
606: int height, int border) {
607: this .resource = resource;
608: this .name = name;
609: this .width = width;
610: this .height = height;
611: this .border = border;
612: }
613:
614: /** Paint the open-tag inside the window. */
615: private void paintContent(PaintTarget target)
616: throws PaintException {
617: target.startTag("open");
618: target.addAttribute("src", resource);
619: if (name != null && name.length() > 0)
620: target.addAttribute("name", name);
621: if (width >= 0)
622: target.addAttribute("width", width);
623: if (height >= 0)
624: target.addAttribute("height", height);
625: switch (border) {
626: case Window.BORDER_MINIMAL:
627: target.addAttribute("border", "minimal");
628: break;
629: case Window.BORDER_NONE:
630: target.addAttribute("border", "none");
631: break;
632: }
633:
634: target.endTag("open");
635: }
636: }
637:
638: /**
639: * @see org.millstone.base.terminal.VariableOwner#changeVariables(java.lang.Object,
640: * java.util.Map)
641: */
642: public void changeVariables(Object source, Map variables) {
643: super .changeVariables(source, variables);
644:
645: // Get focused component
646: String focusedId = (String) variables.get("focused");
647: if (focusedId != null) {
648: try {
649: long id = Long.parseLong(focusedId);
650: this .focusedComponent = Window.getFocusableById(id);
651: } catch (NumberFormatException ignored) {
652: // We ignore invalid focusable ids
653: }
654: }
655:
656: }
657:
658: /**
659: * Get currently focused component in this window.
660: *
661: * @return Focused component or null if none is focused.
662: */
663: public Component.Focusable getFocusedComponent() {
664: return this .focusedComponent;
665: }
666:
667: /**
668: * Set currently focused component in this window.
669: *
670: * @param focusable
671: * Focused component or null if none is focused.
672: */
673: public void setFocusedComponent(Component.Focusable focusable) {
674: this .focusedComponent = focusable;
675: }
676:
677: /* Focusable id generator ****************************************** */
678:
679: private static long lastUsedFocusableId = 0;
680:
681: private static Map focusableComponents = new HashMap();
682:
683: /** Get an id for focusable component. */
684: public static long getNewFocusableId(Component.Focusable focusable) {
685: long newId = ++lastUsedFocusableId;
686: WeakReference ref = new WeakReference(focusable);
687: focusableComponents.put(new Long(newId), ref);
688: return newId;
689: }
690:
691: /** Map focusable id back to focusable component. */
692: public static Component.Focusable getFocusableById(long focusableId) {
693: WeakReference ref = (WeakReference) focusableComponents
694: .get(new Long(focusableId));
695: if (ref != null) {
696: Object o = ref.get();
697: if (o != null) {
698: return (Component.Focusable) o;
699: }
700: }
701: return null;
702: }
703:
704: /** Release focusable component id when not used anymore. */
705: public static void removeFocusableId(long focusableId) {
706: Long id = new Long(focusableId);
707: WeakReference ref = (WeakReference) focusableComponents.get(id);
708: ref.clear();
709: focusableComponents.remove(id);
710: }
711: }
|