blob: 4b7d7994b5e0d11f7549e07d4d8d9dad2ff9bc98 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2003-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25package javax.swing.plaf.synth;
26
27import java.awt.Color;
28import java.awt.Component;
29import java.awt.Font;
30import java.awt.Graphics;
31import java.awt.Image;
32import java.awt.Insets;
33import java.awt.Toolkit;
34import java.io.BufferedInputStream;
35import java.io.IOException;
36import java.io.InputStream;
37import java.net.MalformedURLException;
38import java.net.URL;
39import java.net.URLClassLoader;
40import java.text.ParseException;
41import java.util.ArrayList;
42import java.util.HashMap;
43import java.util.Locale;
44import java.util.Map;
45import java.util.StringTokenizer;
46import java.util.regex.PatternSyntaxException;
47
48import javax.swing.ImageIcon;
49import javax.swing.JSplitPane;
50import javax.swing.SwingConstants;
51import javax.swing.UIDefaults;
52import javax.swing.plaf.ColorUIResource;
53import javax.swing.plaf.DimensionUIResource;
54import javax.swing.plaf.FontUIResource;
55import javax.swing.plaf.InsetsUIResource;
56import javax.swing.plaf.UIResource;
57import javax.xml.parsers.ParserConfigurationException;
58import javax.xml.parsers.SAXParser;
59import javax.xml.parsers.SAXParserFactory;
60
61import org.xml.sax.AttributeList;
62import org.xml.sax.HandlerBase;
63import org.xml.sax.InputSource;
64import org.xml.sax.Locator;
65import org.xml.sax.SAXException;
66import org.xml.sax.SAXParseException;
67
68import com.sun.beans.ObjectHandler;
69
70class SynthParser extends HandlerBase {
71 //
72 // Known element names
73 //
74 private static final String ELEMENT_SYNTH = "synth";
75 private static final String ELEMENT_STYLE = "style";
76 private static final String ELEMENT_STATE = "state";
77 private static final String ELEMENT_FONT = "font";
78 private static final String ELEMENT_COLOR = "color";
79 private static final String ELEMENT_IMAGE_PAINTER = "imagePainter";
80 private static final String ELEMENT_PAINTER = "painter";
81 private static final String ELEMENT_PROPERTY = "property";
82 private static final String ELEMENT_SYNTH_GRAPHICS = "graphicsUtils";
83 private static final String ELEMENT_IMAGE_ICON = "imageIcon";
84 private static final String ELEMENT_BIND = "bind";
85 private static final String ELEMENT_BIND_KEY = "bindKey";
86 private static final String ELEMENT_INSETS = "insets";
87 private static final String ELEMENT_OPAQUE = "opaque";
88 private static final String ELEMENT_DEFAULTS_PROPERTY =
89 "defaultsProperty";
90 private static final String ELEMENT_INPUT_MAP = "inputMap";
91
92 //
93 // Known attribute names
94 //
95 private static final String ATTRIBUTE_ACTION = "action";
96 private static final String ATTRIBUTE_ID = "id";
97 private static final String ATTRIBUTE_IDREF = "idref";
98 private static final String ATTRIBUTE_CLONE = "clone";
99 private static final String ATTRIBUTE_VALUE = "value";
100 private static final String ATTRIBUTE_NAME = "name";
101 private static final String ATTRIBUTE_STYLE = "style";
102 private static final String ATTRIBUTE_SIZE = "size";
103 private static final String ATTRIBUTE_TYPE = "type";
104 private static final String ATTRIBUTE_TOP = "top";
105 private static final String ATTRIBUTE_LEFT = "left";
106 private static final String ATTRIBUTE_BOTTOM = "bottom";
107 private static final String ATTRIBUTE_RIGHT = "right";
108 private static final String ATTRIBUTE_KEY = "key";
109 private static final String ATTRIBUTE_SOURCE_INSETS = "sourceInsets";
110 private static final String ATTRIBUTE_DEST_INSETS = "destinationInsets";
111 private static final String ATTRIBUTE_PATH = "path";
112 private static final String ATTRIBUTE_STRETCH = "stretch";
113 private static final String ATTRIBUTE_PAINT_CENTER = "paintCenter";
114 private static final String ATTRIBUTE_METHOD = "method";
115 private static final String ATTRIBUTE_DIRECTION = "direction";
116 private static final String ATTRIBUTE_CENTER = "center";
117
118 /**
119 * Lazily created, used for anything we don't understand.
120 */
121 private ObjectHandler _handler;
122
123 /**
124 * Indicates the depth of how many elements we've encountered but don't
125 * understand. This is used when forwarding to beans persistance to know
126 * when we hsould stop forwarding.
127 */
128 private int _depth;
129
130 /**
131 * Factory that new styles are added to.
132 */
133 private DefaultSynthStyleFactory _factory;
134
135 /**
136 * Array of state infos for the current style. These are pushed to the
137 * style when </style> is received.
138 */
139 private java.util.List _stateInfos;
140
141 /**
142 * Current style.
143 */
144 private ParsedSynthStyle _style;
145
146 /**
147 * Current state info.
148 */
149 private ParsedSynthStyle.StateInfo _stateInfo;
150
151 /**
152 * Bindings for the current InputMap
153 */
154 private java.util.List _inputMapBindings;
155
156 /**
157 * ID for the input map. This is cached as
158 * the InputMap is created AFTER the inputMapProperty has ended.
159 */
160 private String _inputMapID;
161
162 /**
163 * Object references outside the scope of persistance.
164 */
165 private Map<String,Object> _mapping;
166
167 /**
168 * Based URL used to resolve paths.
169 */
170 private URL _urlResourceBase;
171
172 /**
173 * Based class used to resolve paths.
174 */
175 private Class<?> _classResourceBase;
176
177 /**
178 * List of ColorTypes. This is populated in startColorType.
179 */
180 private java.util.List _colorTypes;
181
182 /**
183 * defaultsPropertys are placed here.
184 */
185 private Map _defaultsMap;
186
187 /**
188 * List of SynthStyle.Painters that will be applied to the current style.
189 */
190 private java.util.List _stylePainters;
191
192 /**
193 * List of SynthStyle.Painters that will be applied to the current state.
194 */
195 private java.util.List _statePainters;
196
197 SynthParser() {
198 _mapping = new HashMap<String,Object>();
199 _stateInfos = new ArrayList();
200 _colorTypes = new ArrayList();
201 _inputMapBindings = new ArrayList();
202 _stylePainters = new ArrayList();
203 _statePainters = new ArrayList();
204 }
205
206 /**
207 * Parses a set of styles from <code>inputStream</code>, adding the
208 * resulting styles to the passed in DefaultSynthStyleFactory.
209 * Resources are resolved either from a URL or from a Class. When calling
210 * this method, one of the URL or the Class must be null but not both at
211 * the same time.
212 *
213 * @param inputStream XML document containing the styles to read
214 * @param factory DefaultSynthStyleFactory that new styles are added to
215 * @param urlResourceBase the URL used to resolve any resources, such as Images
216 * @param classResourceBase the Class used to resolve any resources, such as Images
217 * @param defaultsMap Map that UIDefaults properties are placed in
218 */
219 public void parse(InputStream inputStream,
220 DefaultSynthStyleFactory factory,
221 URL urlResourceBase, Class<?> classResourceBase,
222 Map defaultsMap)
223 throws ParseException, IllegalArgumentException {
224 if (inputStream == null || factory == null ||
225 (urlResourceBase == null && classResourceBase == null)) {
226 throw new IllegalArgumentException(
227 "You must supply an InputStream, StyleFactory and Class or URL");
228 }
229
230 assert(!(urlResourceBase != null && classResourceBase != null));
231
232 _factory = factory;
233 _classResourceBase = classResourceBase;
234 _urlResourceBase = urlResourceBase;
235 _defaultsMap = defaultsMap;
236 try {
237 try {
238 SAXParser saxParser = SAXParserFactory.newInstance().
239 newSAXParser();
240 saxParser.parse(new BufferedInputStream(inputStream), this);
241 } catch (ParserConfigurationException e) {
242 throw new ParseException("Error parsing: " + e, 0);
243 }
244 catch (SAXException se) {
245 throw new ParseException("Error parsing: " + se + " " +
246 se.getException(), 0);
247 }
248 catch (IOException ioe) {
249 throw new ParseException("Error parsing: " + ioe, 0);
250 }
251 } finally {
252 reset();
253 }
254 }
255
256 /**
257 * Returns the path to a resource.
258 */
259 private URL getResource(String path) {
260 if (_classResourceBase != null) {
261 return _classResourceBase.getResource(path);
262 } else {
263 try {
264 return new URL(_urlResourceBase, path);
265 } catch (MalformedURLException mue) {
266 return null;
267 }
268 }
269 }
270
271 /**
272 * Clears our internal state.
273 */
274 private void reset() {
275 _handler = null;
276 _depth = 0;
277 _mapping.clear();
278 _stateInfos.clear();
279 _colorTypes.clear();
280 _statePainters.clear();
281 _stylePainters.clear();
282 }
283
284 /**
285 * Returns true if we are forwarding to persistance.
286 */
287 private boolean isForwarding() {
288 return (_depth > 0);
289 }
290
291 /**
292 * Handles beans persistance.
293 */
294 private ObjectHandler getHandler() {
295 if (_handler == null) {
296 if (_urlResourceBase != null) {
297 // getHandler() is never called before parse() so it is safe
298 // to create a URLClassLoader with _resourceBase.
299 //
300 // getResource(".") is called to ensure we have the directory
301 // containing the resources in the case the resource base is a
302 // .class file.
303 URL[] urls = new URL[] { getResource(".") };
304 ClassLoader parent = Thread.currentThread().getContextClassLoader();
305 ClassLoader urlLoader = new URLClassLoader(urls, parent);
306 _handler = new ObjectHandler(null, urlLoader);
307 } else {
308 _handler = new ObjectHandler(null,
309 _classResourceBase.getClassLoader());
310 }
311
312 for (String key : _mapping.keySet()) {
313 _handler.register(key, _mapping.get(key));
314 }
315 }
316 return _handler;
317 }
318
319 /**
320 * If <code>value</code> is an instance of <code>type</code> it is
321 * returned, otherwise a SAXException is thrown.
322 */
323 private Object checkCast(Object value, Class type) throws SAXException {
324 if (!type.isInstance(value)) {
325 throw new SAXException("Expected type " + type + " got " +
326 value.getClass());
327 }
328 return value;
329 }
330
331 /**
332 * Returns an object created with id=key. If the object is not of
333 * type type, this will throw an exception.
334 */
335 private Object lookup(String key, Class type) throws SAXException {
336 Object value = null;
337 if (_handler != null) {
338 if ((value = _handler.lookup(key)) != null) {
339 return checkCast(value, type);
340 }
341 }
342 value = _mapping.get(key);
343 if (value == null) {
344 throw new SAXException("ID " + key + " has not been defined");
345 }
346 return checkCast(value, type);
347 }
348
349 /**
350 * Registers an object by name. This will throw an exception if an
351 * object has already been registered under the given name.
352 */
353 private void register(String key, Object value) throws SAXException {
354 if (key != null) {
355 if (_mapping.get(key) != null ||
356 (_handler != null && _handler.lookup(key) != null)) {
357 throw new SAXException("ID " + key + " is already defined");
358 }
359 if (_handler != null) {
360 _handler.register(key, value);
361 }
362 else {
363 _mapping.put(key, value);
364 }
365 }
366 }
367
368 /**
369 * Convenience method to return the next int, or throw if there are no
370 * more valid ints.
371 */
372 private int nextInt(StringTokenizer tok, String errorMsg) throws
373 SAXException {
374 if (!tok.hasMoreTokens()) {
375 throw new SAXException(errorMsg);
376 }
377 try {
378 return Integer.parseInt(tok.nextToken());
379 } catch (NumberFormatException nfe) {
380 throw new SAXException(errorMsg);
381 }
382 }
383
384 /**
385 * Convenience method to return an Insets object.
386 */
387 private Insets parseInsets(String insets, String errorMsg) throws
388 SAXException {
389 StringTokenizer tokenizer = new StringTokenizer(insets);
390 return new Insets(nextInt(tokenizer, errorMsg),
391 nextInt(tokenizer, errorMsg),
392 nextInt(tokenizer, errorMsg),
393 nextInt(tokenizer, errorMsg));
394 }
395
396
397
398 //
399 // The following methods are invoked from startElement/stopElement
400 //
401
402 private void startStyle(AttributeList attributes) throws SAXException {
403 String id = null;
404
405 _style = null;
406 for(int i = attributes.getLength() - 1; i >= 0; i--) {
407 String key = attributes.getName(i);
408 if (key.equals(ATTRIBUTE_CLONE)) {
409 _style = (ParsedSynthStyle)((ParsedSynthStyle)lookup(
410 attributes.getValue(i), ParsedSynthStyle.class)).
411 clone();
412 }
413 else if (key.equals(ATTRIBUTE_ID)) {
414 id = attributes.getValue(i);
415 }
416 }
417 if (_style == null) {
418 _style = new ParsedSynthStyle();
419 }
420 register(id, _style);
421 }
422
423 private void endStyle() throws SAXException {
424 int size = _stylePainters.size();
425 if (size > 0) {
426 _style.setPainters((ParsedSynthStyle.PainterInfo[])
427 _stylePainters.toArray(new ParsedSynthStyle.
428 PainterInfo[size]));
429 _stylePainters.clear();
430 }
431 size = _stateInfos.size();
432 if (size > 0) {
433 _style.setStateInfo((ParsedSynthStyle.StateInfo[])_stateInfos.
434 toArray(new ParsedSynthStyle.StateInfo[size]));
435 _stateInfos.clear();
436 }
437 _style = null;
438 }
439
440 private void startState(AttributeList attributes) throws SAXException {
441 ParsedSynthStyle.StateInfo stateInfo = null;
442 int state = 0;
443 String id = null;
444
445 _stateInfo = null;
446 for(int i = attributes.getLength() - 1; i >= 0; i--) {
447 String key = attributes.getName(i);
448 if (key.equals(ATTRIBUTE_ID)) {
449 id = attributes.getValue(i);
450 }
451 else if (key.equals(ATTRIBUTE_IDREF)) {
452 _stateInfo = (ParsedSynthStyle.StateInfo)lookup(
453 attributes.getValue(i), ParsedSynthStyle.StateInfo.class);
454 }
455 else if (key.equals(ATTRIBUTE_CLONE)) {
456 _stateInfo = (ParsedSynthStyle.StateInfo)((ParsedSynthStyle.
457 StateInfo)lookup(attributes.getValue(i),
458 ParsedSynthStyle.StateInfo.class)).clone();
459 }
460 else if (key.equals(ATTRIBUTE_VALUE)) {
461 StringTokenizer tokenizer = new StringTokenizer(
462 attributes.getValue(i));
463 while (tokenizer.hasMoreTokens()) {
464 String stateString = tokenizer.nextToken().toUpperCase().
465 intern();
466 if (stateString == "ENABLED") {
467 state |= SynthConstants.ENABLED;
468 }
469 else if (stateString == "MOUSE_OVER") {
470 state |= SynthConstants.MOUSE_OVER;
471 }
472 else if (stateString == "PRESSED") {
473 state |= SynthConstants.PRESSED;
474 }
475 else if (stateString == "DISABLED") {
476 state |= SynthConstants.DISABLED;
477 }
478 else if (stateString == "FOCUSED") {
479 state |= SynthConstants.FOCUSED;
480 }
481 else if (stateString == "SELECTED") {
482 state |= SynthConstants.SELECTED;
483 }
484 else if (stateString == "DEFAULT") {
485 state |= SynthConstants.DEFAULT;
486 }
487 else if (stateString != "AND") {
488 throw new SAXException("Unknown state: " + state);
489 }
490 }
491 }
492 }
493 if (_stateInfo == null) {
494 _stateInfo = new ParsedSynthStyle.StateInfo();
495 }
496 _stateInfo.setComponentState(state);
497 register(id, _stateInfo);
498 _stateInfos.add(_stateInfo);
499 }
500
501 private void endState() throws SAXException {
502 int size = _statePainters.size();
503 if (size > 0) {
504 _stateInfo.setPainters((ParsedSynthStyle.PainterInfo[])
505 _statePainters.toArray(new ParsedSynthStyle.
506 PainterInfo[size]));
507 _statePainters.clear();
508 }
509 _stateInfo = null;
510 }
511
512 private void startFont(AttributeList attributes) throws SAXException {
513 Font font = null;
514 int style = Font.PLAIN;
515 int size = 0;
516 String id = null;
517 String name = null;
518
519 for(int i = attributes.getLength() - 1; i >= 0; i--) {
520 String key = attributes.getName(i);
521 if (key.equals(ATTRIBUTE_ID)) {
522 id = attributes.getValue(i);
523 }
524 else if (key.equals(ATTRIBUTE_IDREF)) {
525 font = (Font)lookup(attributes.getValue(i), Font.class);
526 }
527 else if (key.equals(ATTRIBUTE_NAME)) {
528 name = attributes.getValue(i);
529 }
530 else if (key.equals(ATTRIBUTE_SIZE)) {
531 try {
532 size = Integer.parseInt(attributes.getValue(i));
533 } catch (NumberFormatException nfe) {
534 throw new SAXException("Invalid font size: " +
535 attributes.getValue(i));
536 }
537 }
538 else if (key.equals(ATTRIBUTE_STYLE)) {
539 StringTokenizer tok = new StringTokenizer(
540 attributes.getValue(i));
541 while (tok.hasMoreTokens()) {
542 String token = tok.nextToken().intern();
543 if (token == "BOLD") {
544 style = ((style | Font.PLAIN) ^ Font.PLAIN) |
545 Font.BOLD;
546 }
547 else if (token == "ITALIC") {
548 style |= Font.ITALIC;
549 }
550 }
551 }
552 }
553 if (font == null) {
554 if (name == null) {
555 throw new SAXException("You must define a name for the font");
556 }
557 if (size == 0) {
558 throw new SAXException("You must define a size for the font");
559 }
560 font = new FontUIResource(name, style, size);
561 }
562 else if (name != null || size != 0 || style != Font.PLAIN) {
563 throw new SAXException("Name, size and style are not for use " +
564 "with idref");
565 }
566 register(id, font);
567 if (_stateInfo != null) {
568 _stateInfo.setFont(font);
569 }
570 else if (_style != null) {
571 _style.setFont(font);
572 }
573 }
574
575 private void startColor(AttributeList attributes) throws SAXException {
576 Color color = null;
577 String id = null;
578
579 _colorTypes.clear();
580 for(int i = attributes.getLength() - 1; i >= 0; i--) {
581 String key = attributes.getName(i);
582 if (key.equals(ATTRIBUTE_ID)) {
583 id = attributes.getValue(i);
584 }
585 else if (key.equals(ATTRIBUTE_IDREF)) {
586 color = (Color)lookup(attributes.getValue(i), Color.class);
587 }
588 else if (key.equals(ATTRIBUTE_NAME)) {
589 }
590 else if (key.equals(ATTRIBUTE_VALUE)) {
591 String value = attributes.getValue(i);
592
593 if (value.startsWith("#")) {
594 try {
595 int argb;
596 boolean hasAlpha;
597
598 int length = value.length();
599 if (length < 8) {
600 // Just RGB, or some portion of it.
601 argb = Integer.decode(value);
602 hasAlpha = false;
603 } else if (length == 8) {
604 // Single character alpha: #ARRGGBB.
605 argb = Integer.decode(value);
606 hasAlpha = true;
607 } else if (length == 9) {
608 // Color has alpha and is of the form
609 // #AARRGGBB.
610 // The following split decoding is mandatory due to
611 // Integer.decode() behavior which won't decode
612 // hexadecimal values higher than #7FFFFFFF.
613 // Thus, when an alpha channel is detected, it is
614 // decoded separately from the RGB channels.
615 int rgb = Integer.decode('#' +
616 value.substring(3, 9));
617 int a = Integer.decode(value.substring(0, 3));
618 argb = (a << 24) | rgb;
619 hasAlpha = true;
620 } else {
621 throw new SAXException("Invalid Color value: "
622 + value);
623 }
624
625 color = new ColorUIResource(new Color(argb, hasAlpha));
626 } catch (NumberFormatException nfe) {
627 throw new SAXException("Invalid Color value: " +value);
628 }
629 }
630 else {
631 try {
632 color = new ColorUIResource((Color)Color.class.
633 getField(value.toUpperCase()).get(Color.class));
634 } catch (NoSuchFieldException nsfe) {
635 throw new SAXException("Invalid color name: " + value);
636 } catch (IllegalAccessException iae) {
637 throw new SAXException("Invalid color name: " + value);
638 }
639 }
640 }
641 else if (key.equals(ATTRIBUTE_TYPE)) {
642 StringTokenizer tokenizer = new StringTokenizer(
643 attributes.getValue(i));
644 while (tokenizer.hasMoreTokens()) {
645 String typeName = tokenizer.nextToken();
646 int classIndex = typeName.lastIndexOf('.');
647 Class typeClass;
648
649 if (classIndex == -1) {
650 typeClass = ColorType.class;
651 classIndex = 0;
652 }
653 else {
654 try {
655 typeClass = Class.forName(typeName.substring(
656 0, classIndex));
657 } catch (ClassNotFoundException cnfe) {
658 throw new SAXException("Unknown class: " +
659 typeName.substring(0, classIndex));
660 }
661 classIndex++;
662 }
663 try {
664 _colorTypes.add((ColorType)checkCast(typeClass.
665 getField(typeName.substring(classIndex,
666 typeName.length() - classIndex)).
667 get(typeClass), ColorType.class));
668 } catch (NoSuchFieldException nsfe) {
669 throw new SAXException("Unable to find color type: " +
670 typeName);
671 } catch (IllegalAccessException iae) {
672 throw new SAXException("Unable to find color type: " +
673 typeName);
674 }
675 }
676 }
677 }
678 if (color == null) {
679 throw new SAXException("color: you must specificy a value");
680 }
681 register(id, color);
682 if (_stateInfo != null && _colorTypes.size() > 0) {
683 Color[] colors = _stateInfo.getColors();
684 int max = 0;
685 for (int counter = _colorTypes.size() - 1; counter >= 0;
686 counter--) {
687 max = Math.max(max, ((ColorType)_colorTypes.get(counter)).
688 getID());
689 }
690 if (colors == null || colors.length <= max) {
691 Color[] newColors = new Color[max + 1];
692 if (colors != null) {
693 System.arraycopy(colors, 0, newColors, 0, colors.length);
694 }
695 colors = newColors;
696 }
697 for (int counter = _colorTypes.size() - 1; counter >= 0;
698 counter--) {
699 colors[((ColorType)_colorTypes.get(counter)).getID()] = color;
700 }
701 _stateInfo.setColors(colors);
702 }
703 }
704
705 private void startProperty(AttributeList attributes,
706 Object property) throws SAXException {
707 Object value = null;
708 Object key = null;
709 // Type of the value: 0=idref, 1=boolean, 2=dimension, 3=insets,
710 // 4=integer,5=string
711 int iType = 0;
712 String aValue = null;
713
714 for(int i = attributes.getLength() - 1; i >= 0; i--) {
715 String aName = attributes.getName(i);
716 if (aName.equals(ATTRIBUTE_TYPE)) {
717 String type = attributes.getValue(i).toUpperCase();
718 if (type.equals("IDREF")) {
719 iType = 0;
720 }
721 else if (type.equals("BOOLEAN")) {
722 iType = 1;
723 }
724 else if (type.equals("DIMENSION")) {
725 iType = 2;
726 }
727 else if (type.equals("INSETS")) {
728 iType = 3;
729 }
730 else if (type.equals("INTEGER")) {
731 iType = 4;
732 }
733 else if (type.equals("STRING")) {
734 iType = 5;
735 }
736 else {
737 throw new SAXException(property + " unknown type, use" +
738 "idref, boolean, dimension, insets or integer");
739 }
740 }
741 else if (aName.equals(ATTRIBUTE_VALUE)) {
742 aValue = attributes.getValue(i);
743 }
744 else if (aName.equals(ATTRIBUTE_KEY)) {
745 key = attributes.getValue(i);
746 }
747 }
748 if (aValue != null) {
749 switch (iType) {
750 case 0: // idref
751 value = lookup(aValue, Object.class);
752 break;
753 case 1: // boolean
754 if (aValue.toUpperCase().equals("TRUE")) {
755 value = Boolean.TRUE;
756 }
757 else {
758 value = Boolean.FALSE;
759 }
760 break;
761 case 2: // dimension
762 StringTokenizer tok = new StringTokenizer(aValue);
763 value = new DimensionUIResource(
764 nextInt(tok, "Invalid dimension"),
765 nextInt(tok, "Invalid dimension"));
766 break;
767 case 3: // insets
768 value = parseInsets(aValue, property + " invalid insets");
769 break;
770 case 4: // integer
771 try {
772 value = new Integer(Integer.parseInt(aValue));
773 } catch (NumberFormatException nfe) {
774 throw new SAXException(property + " invalid value");
775 }
776 break;
777 case 5: //string
778 value = aValue;
779 break;
780 }
781 }
782 if (value == null || key == null) {
783 throw new SAXException(property + ": you must supply a " +
784 "key and value");
785 }
786 if (property == ELEMENT_DEFAULTS_PROPERTY) {
787 _defaultsMap.put(key, value);
788 }
789 else if (_stateInfo != null) {
790 if (_stateInfo.getData() == null) {
791 _stateInfo.setData(new HashMap());
792 }
793 _stateInfo.getData().put(key, value);
794 }
795 else if (_style != null) {
796 if (_style.getData() == null) {
797 _style.setData(new HashMap());
798 }
799 _style.getData().put(key, value);
800 }
801 }
802
803 private void startGraphics(AttributeList attributes) throws SAXException {
804 SynthGraphicsUtils graphics = null;
805
806 for(int i = attributes.getLength() - 1; i >= 0; i--) {
807 String key = attributes.getName(i);
808 if (key.equals(ATTRIBUTE_IDREF)) {
809 graphics = (SynthGraphicsUtils)lookup(attributes.getValue(i),
810 SynthGraphicsUtils.class);
811 }
812 }
813 if (graphics == null) {
814 throw new SAXException("graphicsUtils: you must supply an idref");
815 }
816 if (_style != null) {
817 _style.setGraphicsUtils(graphics);
818 }
819 }
820
821 private void startInsets(AttributeList attributes) throws SAXException {
822 int top = 0;
823 int bottom = 0;
824 int left = 0;
825 int right = 0;
826 Insets insets = null;
827 String id = null;
828
829 for(int i = attributes.getLength() - 1; i >= 0; i--) {
830 String key = attributes.getName(i);
831
832 try {
833 if (key.equals(ATTRIBUTE_IDREF)) {
834 insets = (Insets)lookup(attributes.getValue(i),
835 Insets.class);
836 }
837 else if (key.equals(ATTRIBUTE_ID)) {
838 id = attributes.getValue(i);
839 }
840 else if (key.equals(ATTRIBUTE_TOP)) {
841 top = Integer.parseInt(attributes.getValue(i));
842 }
843 else if (key.equals(ATTRIBUTE_LEFT)) {
844 left = Integer.parseInt(attributes.getValue(i));
845 }
846 else if (key.equals(ATTRIBUTE_BOTTOM)) {
847 bottom = Integer.parseInt(attributes.getValue(i));
848 }
849 else if (key.equals(ATTRIBUTE_RIGHT)) {
850 right = Integer.parseInt(attributes.getValue(i));
851 }
852 } catch (NumberFormatException nfe) {
853 throw new SAXException("insets: bad integer value for " +
854 attributes.getValue(i));
855 }
856 }
857 if (insets == null) {
858 insets = new InsetsUIResource(top, left, bottom, right);
859 }
860 register(id, insets);
861 if (_style != null) {
862 _style.setInsets(insets);
863 }
864 }
865
866 private void startBind(AttributeList attributes) throws SAXException {
867 ParsedSynthStyle style = null;
868 String path = null;
869 int type = -1;
870
871 for(int i = attributes.getLength() - 1; i >= 0; i--) {
872 String key = attributes.getName(i);
873
874 if (key.equals(ATTRIBUTE_STYLE)) {
875 style = (ParsedSynthStyle)lookup(attributes.getValue(i),
876 ParsedSynthStyle.class);
877 }
878 else if (key.equals(ATTRIBUTE_TYPE)) {
879 String typeS = attributes.getValue(i).toUpperCase();
880
881 if (typeS.equals("NAME")) {
882 type = DefaultSynthStyleFactory.NAME;
883 }
884 else if (typeS.equals("REGION")) {
885 type = DefaultSynthStyleFactory.REGION;
886 }
887 else {
888 throw new SAXException("bind: unknown type " + typeS);
889 }
890 }
891 else if (key.equals(ATTRIBUTE_KEY)) {
892 path = attributes.getValue(i);
893 }
894 }
895 if (style == null || path == null || type == -1) {
896 throw new SAXException("bind: you must specify a style, type " +
897 "and key");
898 }
899 try {
900 _factory.addStyle(style, path, type);
901 } catch (PatternSyntaxException pse) {
902 throw new SAXException("bind: " + path + " is not a valid " +
903 "regular expression");
904 }
905 }
906
907 private void startPainter(AttributeList attributes, String type) throws SAXException {
908 Insets sourceInsets = null;
909 Insets destInsets = null;
910 String path = null;
911 boolean paintCenter = true;
912 boolean stretch = true;
913 SynthPainter painter = null;
914 String method = null;
915 String id = null;
916 int direction = -1;
917 boolean center = false;
918
919 boolean stretchSpecified = false;
920 boolean paintCenterSpecified = false;
921
922 for(int i = attributes.getLength() - 1; i >= 0; i--) {
923 String key = attributes.getName(i);
924 String value = attributes.getValue(i);
925
926 if (key.equals(ATTRIBUTE_ID)) {
927 id = value;
928 }
929 else if (key.equals(ATTRIBUTE_METHOD)) {
930 method = value.toLowerCase(Locale.ENGLISH);
931 }
932 else if (key.equals(ATTRIBUTE_IDREF)) {
933 painter = (SynthPainter)lookup(value, SynthPainter.class);
934 }
935 else if (key.equals(ATTRIBUTE_PATH)) {
936 path = value;
937 }
938 else if (key.equals(ATTRIBUTE_SOURCE_INSETS)) {
939 sourceInsets = parseInsets(value, type +
940 ": sourceInsets must be top left bottom right");
941 }
942 else if (key.equals(ATTRIBUTE_DEST_INSETS)) {
943 destInsets = parseInsets(value, type +
944 ": destinationInsets must be top left bottom right");
945 }
946 else if (key.equals(ATTRIBUTE_PAINT_CENTER)) {
947 paintCenter = value.toLowerCase().equals("true");
948 paintCenterSpecified = true;
949 }
950 else if (key.equals(ATTRIBUTE_STRETCH)) {
951 stretch = value.toLowerCase().equals("true");
952 stretchSpecified = true;
953 }
954 else if (key.equals(ATTRIBUTE_DIRECTION)) {
955 value = value.toUpperCase().intern();
956 if (value == "EAST") {
957 direction = SwingConstants.EAST;
958 }
959 else if (value == "NORTH") {
960 direction = SwingConstants.NORTH;
961 }
962 else if (value == "SOUTH") {
963 direction = SwingConstants.SOUTH;
964 }
965 else if (value == "WEST") {
966 direction = SwingConstants.WEST;
967 }
968 else if (value == "TOP") {
969 direction = SwingConstants.TOP;
970 }
971 else if (value == "LEFT") {
972 direction = SwingConstants.LEFT;
973 }
974 else if (value == "BOTTOM") {
975 direction = SwingConstants.BOTTOM;
976 }
977 else if (value == "RIGHT") {
978 direction = SwingConstants.RIGHT;
979 }
980 else if (value == "HORIZONTAL") {
981 direction = SwingConstants.HORIZONTAL;
982 }
983 else if (value == "VERTICAL") {
984 direction = SwingConstants.VERTICAL;
985 }
986 else if (value == "HORIZONTAL_SPLIT") {
987 direction = JSplitPane.HORIZONTAL_SPLIT;
988 }
989 else if (value == "VERTICAL_SPLIT") {
990 direction = JSplitPane.VERTICAL_SPLIT;
991 }
992 else {
993 throw new SAXException(type + ": unknown direction");
994 }
995 }
996 else if (key.equals(ATTRIBUTE_CENTER)) {
997 center = value.toLowerCase().equals("true");
998 }
999 }
1000 if (painter == null) {
1001 if (type == ELEMENT_PAINTER) {
1002 throw new SAXException(type +
1003 ": you must specify an idref");
1004 }
1005 if (sourceInsets == null && !center) {
1006 throw new SAXException(
1007 "property: you must specify sourceInsets");
1008 }
1009 if (path == null) {
1010 throw new SAXException("property: you must specify a path");
1011 }
1012 if (center && (sourceInsets != null || destInsets != null ||
1013 paintCenterSpecified || stretchSpecified)) {
1014 throw new SAXException("The attributes: sourceInsets, " +
1015 "destinationInsets, paintCenter and stretch " +
1016 " are not legal when center is true");
1017 }
1018 painter = new ImagePainter(!stretch, paintCenter,
1019 sourceInsets, destInsets, getResource(path), center);
1020 }
1021 register(id, painter);
1022 if (_stateInfo != null) {
1023 addPainterOrMerge(_statePainters, method, painter, direction);
1024 }
1025 else if (_style != null) {
1026 addPainterOrMerge(_stylePainters, method, painter, direction);
1027 }
1028 }
1029
1030 private void addPainterOrMerge(java.util.List painters, String method,
1031 SynthPainter painter, int direction) {
1032 ParsedSynthStyle.PainterInfo painterInfo;
1033 painterInfo = new ParsedSynthStyle.PainterInfo(method,
1034 painter,
1035 direction);
1036
1037 for (Object infoObject: painters) {
1038 ParsedSynthStyle.PainterInfo info;
1039 info = (ParsedSynthStyle.PainterInfo) infoObject;
1040
1041 if (painterInfo.equalsPainter(info)) {
1042 info.addPainter(painter);
1043 return;
1044 }
1045 }
1046
1047 painters.add(painterInfo);
1048 }
1049
1050 private void startImageIcon(AttributeList attributes) throws SAXException {
1051 String path = null;
1052 String id = null;
1053
1054 for(int i = attributes.getLength() - 1; i >= 0; i--) {
1055 String key = attributes.getName(i);
1056
1057 if (key.equals(ATTRIBUTE_ID)) {
1058 id = attributes.getValue(i);
1059 }
1060 else if (key.equals(ATTRIBUTE_PATH)) {
1061 path = attributes.getValue(i);
1062 }
1063 }
1064 if (path == null) {
1065 throw new SAXException("imageIcon: you must specify a path");
1066 }
1067 register(id, new LazyImageIcon(getResource(path)));
1068 }
1069
1070 private void startOpaque(AttributeList attributes) throws
1071 SAXException {
1072 if (_style != null) {
1073 _style.setOpaque(true);
1074 for(int i = attributes.getLength() - 1; i >= 0; i--) {
1075 String key = attributes.getName(i);
1076
1077 if (key.equals(ATTRIBUTE_VALUE)) {
1078 _style.setOpaque("true".equals(attributes.getValue(i).
1079 toLowerCase()));
1080 }
1081 }
1082 }
1083 }
1084
1085 private void startInputMap(AttributeList attributes) throws SAXException {
1086 _inputMapBindings.clear();
1087 _inputMapID = null;
1088 if (_style != null) {
1089 for(int i = attributes.getLength() - 1; i >= 0; i--) {
1090 String key = attributes.getName(i);
1091
1092 if (key.equals(ATTRIBUTE_ID)) {
1093 _inputMapID = attributes.getValue(i);
1094 }
1095 }
1096 }
1097 }
1098
1099 private void endInputMap() throws SAXException {
1100 if (_inputMapID != null) {
1101 register(_inputMapID, new UIDefaults.LazyInputMap(
1102 _inputMapBindings.toArray(new Object[_inputMapBindings.
1103 size()])));
1104 }
1105 _inputMapBindings.clear();
1106 _inputMapID = null;
1107 }
1108
1109 private void startBindKey(AttributeList attributes) throws SAXException {
1110 if (_inputMapID == null) {
1111 // Not in an inputmap, bail.
1112 return;
1113 }
1114 if (_style != null) {
1115 String key = null;
1116 String value = null;
1117 for(int i = attributes.getLength() - 1; i >= 0; i--) {
1118 String aKey = attributes.getName(i);
1119
1120 if (aKey.equals(ATTRIBUTE_KEY)) {
1121 key = attributes.getValue(i);
1122 }
1123 else if (aKey.equals(ATTRIBUTE_ACTION)) {
1124 value = attributes.getValue(i);
1125 }
1126 }
1127 if (key == null || value == null) {
1128 throw new SAXException(
1129 "bindKey: you must supply a key and action");
1130 }
1131 _inputMapBindings.add(key);
1132 _inputMapBindings.add(value);
1133 }
1134 }
1135
1136 //
1137 // SAX methods, these forward to the ObjectHandler if we don't know
1138 // the element name.
1139 //
1140
1141 public InputSource resolveEntity(String publicId, String systemId)
1142 throws SAXException {
1143 if (isForwarding()) {
1144 return getHandler().resolveEntity(publicId, systemId);
1145 }
1146 return null;
1147 }
1148
1149 public void notationDecl(String name, String publicId, String systemId) {
1150 if (isForwarding()) {
1151 getHandler().notationDecl(name, publicId, systemId);
1152 }
1153 }
1154
1155 public void unparsedEntityDecl(String name, String publicId,
1156 String systemId, String notationName) {
1157 if (isForwarding()) {
1158 getHandler().unparsedEntityDecl(name, publicId, systemId,
1159 notationName);
1160 }
1161 }
1162
1163 public void setDocumentLocator(Locator locator) {
1164 if (isForwarding()) {
1165 getHandler().setDocumentLocator(locator);
1166 }
1167 }
1168
1169 public void startDocument() throws SAXException {
1170 if (isForwarding()) {
1171 getHandler().startDocument();
1172 }
1173 }
1174
1175 public void endDocument() throws SAXException {
1176 if (isForwarding()) {
1177 getHandler().endDocument();
1178 }
1179 }
1180
1181 public void startElement(String name, AttributeList attributes)
1182 throws SAXException {
1183 name = name.intern();
1184 if (name == ELEMENT_STYLE) {
1185 startStyle(attributes);
1186 }
1187 else if (name == ELEMENT_STATE) {
1188 startState(attributes);
1189 }
1190 else if (name == ELEMENT_FONT) {
1191 startFont(attributes);
1192 }
1193 else if (name == ELEMENT_COLOR) {
1194 startColor(attributes);
1195 }
1196 else if (name == ELEMENT_PAINTER) {
1197 startPainter(attributes, name);
1198 }
1199 else if (name == ELEMENT_IMAGE_PAINTER) {
1200 startPainter(attributes, name);
1201 }
1202 else if (name == ELEMENT_PROPERTY) {
1203 startProperty(attributes, ELEMENT_PROPERTY);
1204 }
1205 else if (name == ELEMENT_DEFAULTS_PROPERTY) {
1206 startProperty(attributes, ELEMENT_DEFAULTS_PROPERTY);
1207 }
1208 else if (name == ELEMENT_SYNTH_GRAPHICS) {
1209 startGraphics(attributes);
1210 }
1211 else if (name == ELEMENT_INSETS) {
1212 startInsets(attributes);
1213 }
1214 else if (name == ELEMENT_BIND) {
1215 startBind(attributes);
1216 }
1217 else if (name == ELEMENT_BIND_KEY) {
1218 startBindKey(attributes);
1219 }
1220 else if (name == ELEMENT_IMAGE_ICON) {
1221 startImageIcon(attributes);
1222 }
1223 else if (name == ELEMENT_OPAQUE) {
1224 startOpaque(attributes);
1225 }
1226 else if (name == ELEMENT_INPUT_MAP) {
1227 startInputMap(attributes);
1228 }
1229 else if (name != ELEMENT_SYNTH) {
1230 if (_depth++ == 0) {
1231 getHandler().reset();
1232 }
1233 getHandler().startElement(name, attributes);
1234 }
1235 }
1236
1237 public void endElement(String name) throws SAXException {
1238 if (isForwarding()) {
1239 getHandler().endElement(name);
1240 _depth--;
1241 if (!isForwarding()) {
1242 getHandler().reset();
1243 }
1244 }
1245 else {
1246 name = name.intern();
1247 if (name == ELEMENT_STYLE) {
1248 endStyle();
1249 }
1250 else if (name == ELEMENT_STATE) {
1251 endState();
1252 }
1253 else if (name == ELEMENT_INPUT_MAP) {
1254 endInputMap();
1255 }
1256 }
1257 }
1258
1259 public void characters(char ch[], int start, int length)
1260 throws SAXException {
1261 if (isForwarding()) {
1262 getHandler().characters(ch, start, length);
1263 }
1264 }
1265
1266 public void ignorableWhitespace (char ch[], int start, int length)
1267 throws SAXException {
1268 if (isForwarding()) {
1269 getHandler().ignorableWhitespace(ch, start, length);
1270 }
1271 }
1272
1273 public void processingInstruction(String target, String data)
1274 throws SAXException {
1275 if (isForwarding()) {
1276 getHandler().processingInstruction(target, data);
1277 }
1278 }
1279
1280 public void warning(SAXParseException e) throws SAXException {
1281 if (isForwarding()) {
1282 getHandler().warning(e);
1283 }
1284 }
1285
1286 public void error(SAXParseException e) throws SAXException {
1287 if (isForwarding()) {
1288 getHandler().error(e);
1289 }
1290 }
1291
1292
1293 public void fatalError(SAXParseException e) throws SAXException {
1294 if (isForwarding()) {
1295 getHandler().fatalError(e);
1296 }
1297 throw e;
1298 }
1299
1300
1301 /**
1302 * ImageIcon that lazily loads the image until needed.
1303 */
1304 private static class LazyImageIcon extends ImageIcon implements UIResource {
1305 private URL location;
1306
1307 public LazyImageIcon(URL location) {
1308 super();
1309 this.location = location;
1310 }
1311
1312 public void paintIcon(Component c, Graphics g, int x, int y) {
1313 if (getImage() != null) {
1314 super.paintIcon(c, g, x, y);
1315 }
1316 }
1317
1318 public int getIconWidth() {
1319 if (getImage() != null) {
1320 return super.getIconWidth();
1321 }
1322 return 0;
1323 }
1324
1325 public int getIconHeight() {
1326 if (getImage() != null) {
1327 return super.getIconHeight();
1328 }
1329 return 0;
1330 }
1331
1332 public Image getImage() {
1333 if (location != null) {
1334 setImage(Toolkit.getDefaultToolkit().getImage(location));
1335 location = null;
1336 }
1337 return super.getImage();
1338 }
1339 }
1340}