001: /*
002: * Javu WingS - Lightweight Java Component Set
003: * Copyright (c) 2005-2007 Krzysztof A. Sadlocha
004: * e-mail: ksadlocha@programics.com
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020:
021: package com.javujavu.javux.wings;
022:
023: import java.awt.Color;
024: import java.awt.Font;
025: import java.awt.Image;
026: import java.awt.Insets;
027: import java.awt.Toolkit;
028: import java.awt.image.MemoryImageSource;
029: import java.io.File;
030: import java.io.FileInputStream;
031: import java.io.FileNotFoundException;
032: import java.io.IOException;
033: import java.net.MalformedURLException;
034: import java.net.URL;
035: import java.util.Vector;
036: import com.javujavu.javux.proptree.PropTree;
037: import com.javujavu.javux.proptree.PropTreeDocReader;
038: import com.javujavu.javux.util.SortMap;
039:
040: /**
041: * This class loads and manages hierarchical skin style sheets and associated resources
042: * <br>
043: * <b>This is one of the core WingS classes required by all the components</b><br>
044: * <br>
045: * <b>This class is thread safe.</b>
046: **/
047: public class WingSkin {
048: public static final Insets DEF_MARGIN = new Insets(0, 0, 0, 0);
049:
050: private static final Vector skins = new Vector();
051: private static final Vector styleSheets = new Vector();
052: private static final SortMap cache = new SortMap();
053: private static final SortMap styles = new SortMap();
054:
055: private PropTree n = new PropTree();
056: private String path;
057: private boolean pathUrl;
058: private Class pathRef;
059:
060: /**
061: * Removes all previously loaded skins
062: */
063: public static synchronized void removeAllSkins() {
064: skins.removeAllElements();
065: cache.removeAllElements();
066: styles.removeAllElements();
067: }
068:
069: /**
070: * Tries to load all registered style sheets from the specified location.
071: * It overrides but not removes properties from previously loaded style sheets.
072: * <br>
073: * There are 3 possible variants:
074: * <ul>
075: * <li><code>loadSkin("local file path", false, null);</code>
076: * loads the styles from local files
077: * <li><code>loadSkin("net URL", true, null);</code>
078: * loads the styles from the net
079: * <li><code>loadSkin("relative path", true, Reference_class);</code>
080: * loads the styles from the location relative to <code>Reference_class</code>,
081: * usually from the jar file
082: * <ul>
083: * @param path path of the skin folder
084: * @param pathUrl determines wheter the path is an URL or local file path
085: * @param pathRef optional class to location of which the path is relative
086: * @return returns true if the style sheet wings.ini was found in this location
087: */
088: public static synchronized boolean loadSkin(String path,
089: boolean pathUrl, Class pathRef) {
090: if (pathRef != null)
091: pathUrl = true;
092: String separator = (pathUrl) ? "/" : File.separator;
093: if (!path.endsWith(separator))
094: path += separator;
095:
096: WingSkin s = new WingSkin();
097: s.path = path;
098: s.pathUrl = pathUrl;
099: s.pathRef = pathRef;
100:
101: if (loadPropDoc(s, "wings")) {
102: cache.removeAllElements();
103: styles.removeAllElements();
104: skins.addElement(s);
105: for (int i = 0; i < styleSheets.size(); i++) {
106: loadPropDoc(s, (String) styleSheets.elementAt(i));
107: }
108: // System.out.println("load skin succeed "+path+" "+pathUrl+" "+pathRef);
109: return true;
110: }
111: // System.out.println("load skin failed "+path+" "+pathUrl+" "+pathRef);
112:
113: return false;
114: }
115:
116: private static boolean loadPropDoc(WingSkin skin, String file) {
117: file = skin.path + file + ".ini";
118: PropTree n = new PropTree();
119: PropTreeDocReader tr = new PropTreeDocReader();
120: if (skin.pathUrl) {
121: try {
122: URL u = (skin.pathRef != null) ? skin.pathRef
123: .getResource(file) : new URL(file);
124: // if(u==null && skin.pathRef!=null)
125: // {
126: // ClassLoader cl= skin.pathRef.getClassLoader();
127: // if(cl!=null)
128: // {
129: // InputStream is= cl.getResourceAsStream(file);
130: // if(is!=null) tr.load(is, n);
131: // }
132: // }
133: if (u != null) {
134: tr.load(u.openStream(), n);
135: }
136: } catch (SecurityException e) {
137: } catch (IOException e) {
138: }
139: } else {
140: try {
141: tr.load(new FileInputStream(file), n);
142: } catch (FileNotFoundException e) {
143: }
144: }
145: if (n.getInteger("wingskin.version", 0) == WingComponent.WINGSKIN_VERSION) {
146: skin.n.appendTree(n);
147: return true;
148: } else {
149: return false;
150: }
151: }
152:
153: /**
154: * Registers specified style sheet and loads it from all previously
155: * processed skin locations.<br>
156: * By default WingS looks only for wings.ini stylesheet
157: * @param styleSheet style sheet name without the .ini exension
158: * @see #loadSkin(String, boolean, Class)
159: */
160: public static synchronized void registerStyleSheet(String styleSheet) {
161: if (styleSheets.contains(styleSheet))
162: return;
163: styleSheets.addElement(styleSheet);
164: WingSkin s;
165: for (int i = 0; i < skins.size(); i++) {
166: s = (WingSkin) skins.elementAt(i);
167: loadPropDoc(s, styleSheet);
168: }
169: }
170:
171: /**
172: * Returns a string that represents the concatenation of the keys
173: * where dot is a node separator.
174: * If one of the keys is null returns the other one
175: * in other case returns <code>key1.key2</code>
176: * @param key1 key 1
177: * @param key2 key 2
178: * @return a string that represents the concatenation of the keys
179: */
180: public static String catKeys(String key1, String key2) {
181: if (key1 == null)
182: return key2;
183: if (key2 == null)
184: return key1;
185: return key1 + "." + key2;
186: }
187:
188: /**
189: * Returns the color specified by the key being concatenation
190: * of <code>key1</code> and <code>key2</code>
191: * or <code>null</code> if not found
192: * @param key1 first part of the key
193: * @param key2 second part of the key
194: * @return the color specified by the key or null if not found
195: */
196: public static Color getColor(String key1, String key2) {
197: return getColor(key1, key2, null);
198: }
199:
200: /**
201: * Returns the color specified by the key being concatenation
202: * of <code>key1</code> and <code>key2</code>
203: * or <code>def</code> if not found
204: * @param key1 first part of the key
205: * @param key2 second part of the key
206: * @param def default value returned if no color is found
207: * @return the color specified by the key or def if not found
208: */
209: public static Color getColor(String key1, String key2, Color def) {
210: return (Color) getObject(catKeys(key1, key2), "color", def);
211: }
212:
213: /**
214: * Returns the font specified by the key being concatenation
215: * of <code>key1</code> and <code>key2</code>
216: * or <code>null</code> if not found
217: * @param key1 first part of the key
218: * @param key2 second part of the key
219: * @return the font specified by the key or null if not found
220: */
221: public static WingFont getFont(String key1, String key2) {
222: return getFont(key1, key2, null);
223: }
224:
225: /**
226: * Returns the font specified by the key being concatenation
227: * of <code>key1</code> and <code>key2</code>
228: * or <code>def</code> if not found
229: * @param key1 first part of the key
230: * @param key2 second part of the key
231: * @param def default value returned if no font is found
232: * @return the font specified by the key or def if not found
233: */
234: public static WingFont getFont(String key1, String key2,
235: WingFont def) {
236: return (WingFont) getObject(catKeys(key1, key2), "font", def);
237: }
238:
239: /**
240: * Returns the image specified by the key being concatenation
241: * of <code>key1</code> and <code>key2</code>
242: * or a special image produced by <code>getNotFoundImage()</code> if not found
243: * @param key1 first part of the key
244: * @param key2 second part of the key
245: * @return the image specified by the key or getNotFoundImage() if not found
246: */
247: public static WingImage getImage(String key1, String key2) {
248: WingImage r = getImage(key1, key2, null);
249: if (r == null)
250: r = getNotFoundImage();
251: return r;
252: }
253:
254: /**
255: * Returns the image specified by the key being concatenation
256: * of <code>key1</code> and <code>key2</code>
257: * or <code>def</code> if not found
258: * @param key1 first part of the key
259: * @param key2 second part of the key
260: * @param def default value returned if no image is found
261: * @return the image specified by the key or def if not found
262: */
263: public static WingImage getImage(String key1, String key2,
264: WingImage def) {
265: return (WingImage) getObject(catKeys(key1, key2), "image", def);
266: }
267:
268: /**
269: * Returns the margin specified by the key being concatenation
270: * of <code>key1</code> and <code>key2</code>
271: * or <code>DEF_MARGIN</code> if not found
272: * @param key1 first part of the key
273: * @param key2 second part of the key
274: * @return the margin specified by the key or DEF_MARGIN if not found
275: */
276: public static Insets getMargin(String key1, String key2) {
277: Insets r = getMargin(key1, key2, DEF_MARGIN);
278: if (r == null)
279: r = DEF_MARGIN;
280: return r;
281: }
282:
283: /**
284: * Returns the margin specified by the key being concatenation
285: * of <code>key1</code> and <code>key2</code>
286: * or <code>def</code> if not found
287: * @param key1 first part of the key
288: * @param key2 second part of the key
289: * @param def default value returned if no margin is found
290: * @return the margin specified by the key or def if not found
291: */
292: public static Insets getMargin(String key1, String key2, Insets def) {
293: return (Insets) getObject(catKeys(key1, key2), "margin", def);
294: }
295:
296: /**
297: * Returns the integer value specified by the key being concatenation
298: * of <code>key1</code> and <code>key2</code>
299: * or <code>def</code> if not found
300: * @param key1 first part of the key
301: * @param key2 second part of the key
302: * @param def default value returned if no property is found
303: * @return the property specified by the key or def if not found
304: */
305: public static int getInteger(String key1, String key2, int def) {
306: String s = (String) getObject(catKeys(key1, key2), null, null);
307: if (s == null)
308: return def;
309: try {
310: return Integer.parseInt(s);
311: } catch (NumberFormatException e) {
312: }
313: return def;
314: }
315:
316: // public static InputStream getFile(String key)
317: // {
318: // return (InputStream)getObject(key, "file", null);
319: // }
320: // public static String getString(String key1, String key2, String def)
321: // {
322: // return (String)getObject(catKeys(key1,key2), "string", def);
323: // }
324: // public static PropTree getPropTree(String key1, String key2)
325: // {
326: // return (PropTree)the.getObject(catKeys(key1,key2), "proptree", null);
327: // }
328:
329: private static synchronized Object getObject(String key,
330: String type, Object def) {
331: Object r = null;
332: PropTree n = null;
333: int i, j;
334: WingSkin s = null;
335: String keyt = catKeys(key, type);
336:
337: while (true) {
338: if ((i = cache.find(keyt)) != -1)
339: return cache.value(i);
340:
341: for (j = skins.size() - 1; j >= 0; j--) {
342: s = (WingSkin) skins.elementAt(j);
343: n = s.n.getLastNode(keyt);
344: if (n != null)
345: break;
346: }
347: if (n != null)
348: break;
349: if ((i = keyt.indexOf('.')) == -1)
350: break;
351: keyt = keyt.substring(i + 1);
352: }
353: if (n != null) {
354: if (type == null)
355: r = n.getString();
356: else if (type.equals("image"))
357: r = getImage(n, s);
358: else if (type.equals("color"))
359: r = getColor(n);
360: else if (type.equals("font"))
361: r = getFont(n);
362: else if (type.equals("margin"))
363: r = getMargin(n);
364: // else if(type.equals("file")) return getFile(n, s);
365: // else if(type.equals("string")) return n.getString();
366: // else if(type.equals("proptree")) r= n;
367:
368: cache.set(keyt, r);
369: } else
370: r = def;
371:
372: return r;
373: }
374:
375: private static Object getFont(PropTree n) {
376: String face = n.getString();
377: int style = Font.PLAIN;
378: int size = 0;
379: boolean underline = false;
380: boolean antialias = false;
381:
382: if (face == null)
383: return null;
384: if (face.startsWith("[")) {
385: WingFont f = getFont(face.substring(1, face.length() - 1),
386: null, null);
387: if (f == null)
388: return null;
389: face = f.font.getFamily();
390: style = f.font.getStyle();
391: size = f.font.getSize();
392: underline = f.underline;
393: antialias = f.antialias;
394: }
395: if (n.getChild("italic") != null)
396: style &= Font.BOLD; //clear ITALIC
397: if (n.getBoolean("italic"))
398: style |= Font.ITALIC;
399:
400: if (n.getChild("bold") != null)
401: style &= Font.ITALIC; //clear BOLD
402: if (n.getBoolean("bold"))
403: style |= Font.BOLD;
404:
405: if (n.getChild("underline") != null)
406: underline = n.getBoolean("underline");
407:
408: if (n.getChild("antialias") != null)
409: antialias = n.getBoolean("antialias");
410:
411: int s = n.getInteger("size");
412: if (s > 0)
413: size = s;
414: if (size > 0) {
415: return new WingFont(face, style, size, underline, antialias);
416: }
417: return null;
418: }
419:
420: private static Object getColor(PropTree n) {
421: String s = n.getString();
422: if (s != null) {
423: if (s.startsWith("[")) {
424: return getColor(s.substring(1, s.length() - 1), null,
425: null);
426: }
427: if (s.startsWith("#")) {
428: try {
429: return WingToolkit.the().createColor(
430: Integer.parseInt(s.substring(1), 16));
431: } catch (NumberFormatException e) {
432: }
433: }
434: }
435: return null;
436: }
437:
438: private static Insets getMargin(PropTree n) {
439: String key = n.getString();
440: if (key != null && key.startsWith("[")) {
441: return getMargin(key.substring(1, key.length() - 1), null,
442: null);
443: }
444: int[] ii = new int[4];
445: n.getIntegers(null, ii);
446: return new Insets(ii[1], ii[0], ii[3], ii[2]);
447: }
448:
449: private static Object getImage(PropTree n, WingSkin s) {
450: String key = n.getString();
451: if (key == null)
452: return null;
453:
454: WingImage img = null;
455:
456: if (key.startsWith("[")) {
457: img = getImage(key.substring(1, key.length() - 1), null,
458: null);
459: if (img == null)
460: return null;
461: }
462:
463: String file;
464: if (img == null) {
465: Image iimg = null;
466: file = s.path + key;
467: if (s.pathRef != null)
468: iimg = WingImage.loadImage(file, s.pathRef);
469: else if (s.pathUrl) {
470: try {
471: iimg = WingImage.loadImage(new URL(file));
472: } catch (MalformedURLException e) {
473: }
474: } else
475: iimg = WingImage.loadImage(file.replace('/',
476: File.separatorChar));
477: if (iimg != null)
478: img = new WingImage(iimg);
479: }
480: if (img == null)
481: return null;
482:
483: WingImage r;
484: PropTree n2;
485: if ((n2 = n.getChild("rect")) != null) {
486: int[] ii = new int[4];
487: n2.getIntegers(null, ii);
488: r = new WingImage(img, ii[0], ii[1], ii[2], ii[3]);
489: } else if ((n2 = n.getChild("part")) != null) {
490: int[] ii = new int[3];
491: n2.getIntegers(null, ii);
492: r = new WingImage(img, ii[0], ii[1], ii[2]);
493: } else {
494: r = new WingImage(img);
495: }
496: n2 = n.getChild("stretch");
497: if (n2 != null)
498: r.setStretch((Insets) getMargin(n2));
499: String t = n.getString("tile");
500: if (t != null) {
501: t = t.toUpperCase();
502: if (t.equals("NW"))
503: r.setTile(WingImage.TILE_NW);
504: else if (t.equals("N"))
505: r.setTile(WingImage.TILE_N);
506: else if (t.equals("NE"))
507: r.setTile(WingImage.TILE_NE);
508: else if (t.equals("E"))
509: r.setTile(WingImage.TILE_E);
510: else if (t.equals("SE"))
511: r.setTile(WingImage.TILE_SE);
512: else if (t.equals("S"))
513: r.setTile(WingImage.TILE_S);
514: else if (t.equals("SW"))
515: r.setTile(WingImage.TILE_SW);
516: else if (t.equals("W"))
517: r.setTile(WingImage.TILE_W);
518: }
519: String a = n.getString("alpha");
520: if (a != null) {
521: try {
522: r.setAlpha(Integer.parseInt(a.substring(1), 16));
523: } catch (NumberFormatException e) {
524: }
525: }
526: return r;
527: }
528:
529: private static WingImage notFoundImage = null;
530:
531: private static WingImage getNotFoundImage() {
532: if (notFoundImage == null) {
533: int w = 14, h = 14;
534: int pix[] = new int[w * h];
535: int i = 0;
536: for (int y = 0; y < h; y++) {
537: for (int x = 0; x < w; x++) {
538: pix[i++] = (((x < w / 2 && y < h / 2) || (x >= w / 2 && y >= h / 2)) ^ (x == y || x == h
539: - 1 - y)) ? 0xffff0000 : 0xffffffff;
540: }
541: }
542: Image img1 = Toolkit.getDefaultToolkit().createImage(
543: new MemoryImageSource(w, h, pix, 0, w));
544: Image img2 = WingImage.loadImage(img1);
545: if (img2 != null)
546: img1 = img2;
547: notFoundImage = new WingImage(img1);
548: notFoundImage.tile = WingImage.TILE_NW;
549: }
550: return notFoundImage;
551: }
552:
553: /////////////////////////////////////////////////////
554: // style
555:
556: /**
557: * Returns the style specified by the key being concatenation
558: * of <code>id1</code> and <code>id2</code>
559: * using settings from <code>def</code> style as defaults
560: * @return the style specified by the key and def
561: * @param id1 first part of the key
562: * @param id2 second part of the key
563: * @param def style specifying default style values
564: */
565: public static Style getStyle(String id1, String id2, Style def) {
566: return getStyle(id1, id2, 0, def);
567: }
568:
569: /**
570: * Returns the style specified by the key being concatenation
571: * of <code>id1</code> and <code>id2</code>
572: * and keys specified by the <code>state</code> value
573: * using settings from <code>def</code> style as defaults
574: * @return the style specified by the key and def
575: * @param id1 first part of the key
576: * @param id2 second part of the key
577: * @param state state representing by this style
578: * @param def style specifying default style values
579: */
580: public synchronized static Style getStyle(String id1, String id2,
581: int state, Style def) {
582: if (state != 0 || id2 != null) {
583: StringBuffer b = new StringBuffer();
584: if (id1 != null) {
585: b.append(".");
586: b.append(id1);
587: }
588: if (id2 != null) {
589: b.append(".");
590: b.append(id2);
591: }
592: if ((state & WingConst.DOC) != 0)
593: b.append(".doc");
594: if ((state & WingConst.ITEM) != 0)
595: b.append(".item");
596: if ((state & WingConst.ON) != 0)
597: b.append(".on");
598: if ((state & WingConst.DISABLED) != 0)
599: b.append(".disabled");
600: if ((state & WingConst.SELECTED) != 0)
601: b.append(".selected");
602: if ((state & WingConst.FOCUSED) != 0)
603: b.append(".focused");
604: if ((state & WingConst.HOVER) != 0)
605: b.append(".hover");
606: if ((state & WingConst.PRESSED) != 0)
607: b.append(".pressed");
608: if ((state & WingConst.READONLY) != 0)
609: b.append(".readonly");
610: if ((state & WingConst.DARK) != 0)
611: b.append(".dark");
612: if ((state & WingConst.NORMAL) != 0)
613: b.append(".normal");
614: id1 = b.toString().substring(1);
615: }
616: if (id1 == null || id1.length() == 0)
617: return null;
618:
619: Style s = (Style) styles.get(id1);
620: if (s == null) {
621: s = new Style();
622: if (def == null)
623: def = s;
624: s.state = state;
625:
626: s.background = getColor(id1, null, def.background);
627: s.image = getImage(id1, null, def.image);
628: s.foreground = getColor(id1, "foreground", def.foreground);
629: s.border = getColor(id1, "border", def.border);
630: s.focus = getColor(id1, "focus", def.focus);
631: s.icon = getImage(id1, "icon", def.icon);
632: s.font = getFont(id1, null, def.font);
633: // s.underline= getInteger(id1, "underline", 0)>0;
634: // s.antialias= getInteger(id1, "antialias", 0)>0;
635: s.margin = getMargin(id1, null, def.margin);
636: s.gap = getInteger(id1, "gap", def.gap);
637:
638: styles.set(id1, s);
639: }
640: return s;
641: }
642:
643: /**
644: * Returns an array containing keys of all images defined by style sheets
645: * @return array containing keys of all images defined by style sheets
646: */
647: public static String[] listImages() {
648: return listImages(null, null, null);
649: }
650:
651: private static synchronized String[] listImages(SortMap preload,
652: String key, PropTree p) {
653: if (preload == null) {
654: preload = new SortMap();
655: WingSkin s;
656: for (int i = 0; i < skins.size(); i++) {
657: s = (WingSkin) skins.elementAt(i);
658: listImages(preload, "", s.n);
659: }
660: SortMap p2 = new SortMap();
661: for (int i = 0; i < preload.size(); i++) {
662: p2.set((String) preload.value(i), preload.key(i));
663: }
664: String[] r = new String[p2.size()];
665: p2.getValues().copyInto(r);
666: for (int i = 0; i < r.length; i++) {
667: r[i] = r[i].substring(0, r[i].length() - 6);
668: // cut ".image"
669: }
670: return r;
671: } else {
672: if ("image".equals(p.getKey())) {
673: String s = p.getString();
674: if (s == null || s.startsWith("["))
675: preload.remove(key);
676: else
677: preload.set(key, s);
678: } else {
679: if (key.length() > 0)
680: key += ".";
681: PropTree[] nn = p.getChilds();
682: for (int i = 0; nn != null && i < nn.length; i++) {
683: listImages(preload, key + nn[i].getKey(), nn[i]);
684: }
685: }
686: return null;
687: }
688: }
689: }
|