blob: bc5546d712de8bbc92e95457267cd77629685889 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2005 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.text.html;
26
27import sun.swing.SwingUtilities2;
28import java.util.*;
29import java.awt.*;
30import java.io.*;
31import java.net.*;
32import javax.swing.Icon;
33import javax.swing.ImageIcon;
34import javax.swing.border.*;
35import javax.swing.event.ChangeListener;
36import javax.swing.text.*;
37
38/**
39 * Support for defining the visual characteristics of
40 * HTML views being rendered. The StyleSheet is used to
41 * translate the HTML model into visual characteristics.
42 * This enables views to be customized by a look-and-feel,
43 * multiple views over the same model can be rendered
44 * differently, etc. This can be thought of as a CSS
45 * rule repository. The key for CSS attributes is an
46 * object of type CSS.Attribute. The type of the value
47 * is up to the StyleSheet implementation, but the
48 * <code>toString</code> method is required
49 * to return a string representation of CSS value.
50 * <p>
51 * The primary entry point for HTML View implementations
52 * to get their attributes is the
53 * <a href="#getViewAttributes">getViewAttributes</a>
54 * method. This should be implemented to establish the
55 * desired policy used to associate attributes with the view.
56 * Each HTMLEditorKit (i.e. and therefore each associated
57 * JEditorPane) can have its own StyleSheet, but by default one
58 * sheet will be shared by all of the HTMLEditorKit instances.
59 * HTMLDocument instance can also have a StyleSheet, which
60 * holds the document-specific CSS specifications.
61 * <p>
62 * In order for Views to store less state and therefore be
63 * more lightweight, the StyleSheet can act as a factory for
64 * painters that handle some of the rendering tasks. This allows
65 * implementations to determine what they want to cache
66 * and have the sharing potentially at the level that a
67 * selector is common to multiple views. Since the StyleSheet
68 * may be used by views over multiple documents and typically
69 * the HTML attributes don't effect the selector being used,
70 * the potential for sharing is significant.
71 * <p>
72 * The rules are stored as named styles, and other information
73 * is stored to translate the context of an element to a
74 * rule quickly. The following code fragment will display
75 * the named styles, and therefore the CSS rules contained.
76 * <code><pre>
77 * &nbsp;
78 * &nbsp; import java.util.*;
79 * &nbsp; import javax.swing.text.*;
80 * &nbsp; import javax.swing.text.html.*;
81 * &nbsp;
82 * &nbsp; public class ShowStyles {
83 * &nbsp;
84 * &nbsp; public static void main(String[] args) {
85 * &nbsp; HTMLEditorKit kit = new HTMLEditorKit();
86 * &nbsp; HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
87 * &nbsp; StyleSheet styles = doc.getStyleSheet();
88 * &nbsp;
89 * &nbsp; Enumeration rules = styles.getStyleNames();
90 * &nbsp; while (rules.hasMoreElements()) {
91 * &nbsp; String name = (String) rules.nextElement();
92 * &nbsp; Style rule = styles.getStyle(name);
93 * &nbsp; System.out.println(rule.toString());
94 * &nbsp; }
95 * &nbsp; System.exit(0);
96 * &nbsp; }
97 * &nbsp; }
98 * &nbsp;
99 * </pre></code>
100 * <p>
101 * The semantics for when a CSS style should overide visual attributes
102 * defined by an element are not well defined. For example, the html
103 * <code>&lt;body bgcolor=red&gt;</code> makes the body have a red
104 * background. But if the html file also contains the CSS rule
105 * <code>body { background: blue }</code> it becomes less clear as to
106 * what color the background of the body should be. The current
107 * implemention gives visual attributes defined in the element the
108 * highest precedence, that is they are always checked before any styles.
109 * Therefore, in the previous example the background would have a
110 * red color as the body element defines the background color to be red.
111 * <p>
112 * As already mentioned this supports CSS. We don't support the full CSS
113 * spec. Refer to the javadoc of the CSS class to see what properties
114 * we support. The two major CSS parsing related
115 * concepts we do not currently
116 * support are pseudo selectors, such as <code>A:link { color: red }</code>,
117 * and the <code>important</code> modifier.
118 * <p>
119 * <font color="red">Note: This implementation is currently
120 * incomplete. It can be replaced with alternative implementations
121 * that are complete. Future versions of this class will provide
122 * better CSS support.</font>
123 *
124 * @author Timothy Prinzing
125 * @author Sunita Mani
126 * @author Sara Swanson
127 * @author Jill Nakata
128 */
129public class StyleSheet extends StyleContext {
130 // As the javadoc states, this class maintains a mapping between
131 // a CSS selector (such as p.bar) and a Style.
132 // This consists of a number of parts:
133 // . Each selector is broken down into its constituent simple selectors,
134 // and stored in an inverted graph, for example:
135 // p { color: red } ol p { font-size: 10pt } ul p { font-size: 12pt }
136 // results in the graph:
137 // root
138 // |
139 // p
140 // / \
141 // ol ul
142 // each node (an instance of SelectorMapping) has an associated
143 // specificity and potentially a Style.
144 // . Every rule that is asked for (either by way of getRule(String) or
145 // getRule(HTML.Tag, Element)) results in a unique instance of
146 // ResolvedStyle. ResolvedStyles contain the AttributeSets from the
147 // SelectorMapping.
148 // . When a new rule is created it is inserted into the graph, and
149 // the AttributeSets of each ResolvedStyles are updated appropriately.
150 // . This class creates special AttributeSets, LargeConversionSet and
151 // SmallConversionSet, that maintain a mapping between StyleConstants
152 // and CSS so that developers that wish to use the StyleConstants
153 // methods can do so.
154 // . When one of the AttributeSets is mutated by way of a
155 // StyleConstants key, all the associated CSS keys are removed. This is
156 // done so that the two representations don't get out of sync. For
157 // example, if the developer adds StyleConsants.BOLD, FALSE to an
158 // AttributeSet that contains HTML.Tag.B, the HTML.Tag.B entry will
159 // be removed.
160
161 /**
162 * Construct a StyleSheet
163 */
164 public StyleSheet() {
165 super();
166 selectorMapping = new SelectorMapping(0);
167 resolvedStyles = new Hashtable();
168 if (css == null) {
169 css = new CSS();
170 }
171 }
172
173 /**
174 * Fetches the style to use to render the given type
175 * of HTML tag. The element given is representing
176 * the tag and can be used to determine the nesting
177 * for situations where the attributes will differ
178 * if nesting inside of elements.
179 *
180 * @param t the type to translate to visual attributes
181 * @param e the element representing the tag; the element
182 * can be used to determine the nesting for situations where
183 * the attributes will differ if nested inside of other
184 * elements
185 * @return the set of CSS attributes to use to render
186 * the tag
187 */
188 public Style getRule(HTML.Tag t, Element e) {
189 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
190
191 try {
192 // Build an array of all the parent elements.
193 Vector searchContext = sb.getVector();
194
195 for (Element p = e; p != null; p = p.getParentElement()) {
196 searchContext.addElement(p);
197 }
198
199 // Build a fully qualified selector.
200 int n = searchContext.size();
201 StringBuffer cacheLookup = sb.getStringBuffer();
202 AttributeSet attr;
203 String eName;
204 Object name;
205
206 // >= 1 as the HTML.Tag for the 0th element is passed in.
207 for (int counter = n - 1; counter >= 1; counter--) {
208 e = (Element)searchContext.elementAt(counter);
209 attr = e.getAttributes();
210 name = attr.getAttribute(StyleConstants.NameAttribute);
211 eName = name.toString();
212 cacheLookup.append(eName);
213 if (attr != null) {
214 if (attr.isDefined(HTML.Attribute.ID)) {
215 cacheLookup.append('#');
216 cacheLookup.append(attr.getAttribute
217 (HTML.Attribute.ID));
218 }
219 else if (attr.isDefined(HTML.Attribute.CLASS)) {
220 cacheLookup.append('.');
221 cacheLookup.append(attr.getAttribute
222 (HTML.Attribute.CLASS));
223 }
224 }
225 cacheLookup.append(' ');
226 }
227 cacheLookup.append(t.toString());
228 e = (Element)searchContext.elementAt(0);
229 attr = e.getAttributes();
230 if (e.isLeaf()) {
231 // For leafs, we use the second tier attributes.
232 Object testAttr = attr.getAttribute(t);
233 if (testAttr instanceof AttributeSet) {
234 attr = (AttributeSet)testAttr;
235 }
236 else {
237 attr = null;
238 }
239 }
240 if (attr != null) {
241 if (attr.isDefined(HTML.Attribute.ID)) {
242 cacheLookup.append('#');
243 cacheLookup.append(attr.getAttribute(HTML.Attribute.ID));
244 }
245 else if (attr.isDefined(HTML.Attribute.CLASS)) {
246 cacheLookup.append('.');
247 cacheLookup.append(attr.getAttribute
248 (HTML.Attribute.CLASS));
249 }
250 }
251
252 Style style = getResolvedStyle(cacheLookup.toString(),
253 searchContext, t);
254 return style;
255 }
256 finally {
257 SearchBuffer.releaseSearchBuffer(sb);
258 }
259 }
260
261 /**
262 * Fetches the rule that best matches the selector given
263 * in string form. Where <code>selector</code> is a space separated
264 * String of the element names. For example, <code>selector</code>
265 * might be 'html body tr td''<p>
266 * The attributes of the returned Style will change
267 * as rules are added and removed. That is if you to ask for a rule
268 * with a selector "table p" and a new rule was added with a selector
269 * of "p" the returned Style would include the new attributes from
270 * the rule "p".
271 */
272 public Style getRule(String selector) {
273 selector = cleanSelectorString(selector);
274 if (selector != null) {
275 Style style = getResolvedStyle(selector);
276 return style;
277 }
278 return null;
279 }
280
281 /**
282 * Adds a set of rules to the sheet. The rules are expected to
283 * be in valid CSS format. Typically this would be called as
284 * a result of parsing a &lt;style&gt; tag.
285 */
286 public void addRule(String rule) {
287 if (rule != null) {
288 //tweaks to control display properties
289 //see BasicEditorPaneUI
290 final String baseUnitsDisable = "BASE_SIZE_DISABLE";
291 final String baseUnits = "BASE_SIZE ";
292 final String w3cLengthUnitsEnable = "W3C_LENGTH_UNITS_ENABLE";
293 final String w3cLengthUnitsDisable = "W3C_LENGTH_UNITS_DISABLE";
294 if (rule == baseUnitsDisable) {
295 sizeMap = sizeMapDefault;
296 } else if (rule.startsWith(baseUnits)) {
297 rebaseSizeMap(Integer.
298 parseInt(rule.substring(baseUnits.length())));
299 } else if (rule == w3cLengthUnitsEnable) {
300 w3cLengthUnits = true;
301 } else if (rule == w3cLengthUnitsDisable) {
302 w3cLengthUnits = false;
303 } else {
304 CssParser parser = new CssParser();
305 try {
306 parser.parse(getBase(), new StringReader(rule), false, false);
307 } catch (IOException ioe) { }
308 }
309 }
310 }
311
312 /**
313 * Translates a CSS declaration to an AttributeSet that represents
314 * the CSS declaration. Typically this would be called as a
315 * result of encountering an HTML style attribute.
316 */
317 public AttributeSet getDeclaration(String decl) {
318 if (decl == null) {
319 return SimpleAttributeSet.EMPTY;
320 }
321 CssParser parser = new CssParser();
322 return parser.parseDeclaration(decl);
323 }
324
325 /**
326 * Loads a set of rules that have been specified in terms of
327 * CSS1 grammar. If there are collisions with existing rules,
328 * the newly specified rule will win.
329 *
330 * @param in the stream to read the CSS grammar from
331 * @param ref the reference URL. This value represents the
332 * location of the stream and may be null. All relative
333 * URLs specified in the stream will be based upon this
334 * parameter.
335 */
336 public void loadRules(Reader in, URL ref) throws IOException {
337 CssParser parser = new CssParser();
338 parser.parse(ref, in, false, false);
339 }
340
341 /**
342 * Fetches a set of attributes to use in the view for
343 * displaying. This is basically a set of attributes that
344 * can be used for View.getAttributes.
345 */
346 public AttributeSet getViewAttributes(View v) {
347 return new ViewAttributeSet(v);
348 }
349
350 /**
351 * Removes a named style previously added to the document.
352 *
353 * @param nm the name of the style to remove
354 */
355 public void removeStyle(String nm) {
356 Style aStyle = getStyle(nm);
357
358 if (aStyle != null) {
359 String selector = cleanSelectorString(nm);
360 String[] selectors = getSimpleSelectors(selector);
361 synchronized(this) {
362 SelectorMapping mapping = getRootSelectorMapping();
363 for (int i = selectors.length - 1; i >= 0; i--) {
364 mapping = mapping.getChildSelectorMapping(selectors[i],
365 true);
366 }
367 Style rule = mapping.getStyle();
368 if (rule != null) {
369 mapping.setStyle(null);
370 if (resolvedStyles.size() > 0) {
371 Enumeration values = resolvedStyles.elements();
372 while (values.hasMoreElements()) {
373 ResolvedStyle style = (ResolvedStyle)values.
374 nextElement();
375 style.removeStyle(rule);
376 }
377 }
378 }
379 }
380 }
381 super.removeStyle(nm);
382 }
383
384 /**
385 * Adds the rules from the StyleSheet <code>ss</code> to those of
386 * the receiver. <code>ss's</code> rules will override the rules of
387 * any previously added style sheets. An added StyleSheet will never
388 * override the rules of the receiving style sheet.
389 *
390 * @since 1.3
391 */
392 public void addStyleSheet(StyleSheet ss) {
393 synchronized(this) {
394 if (linkedStyleSheets == null) {
395 linkedStyleSheets = new Vector();
396 }
397 if (!linkedStyleSheets.contains(ss)) {
398 int index = 0;
399 if (ss instanceof javax.swing.plaf.UIResource
400 && linkedStyleSheets.size() > 1) {
401 index = linkedStyleSheets.size() - 1;
402 }
403 linkedStyleSheets.insertElementAt(ss, index);
404 linkStyleSheetAt(ss, index);
405 }
406 }
407 }
408
409 /**
410 * Removes the StyleSheet <code>ss</code> from those of the receiver.
411 *
412 * @since 1.3
413 */
414 public void removeStyleSheet(StyleSheet ss) {
415 synchronized(this) {
416 if (linkedStyleSheets != null) {
417 int index = linkedStyleSheets.indexOf(ss);
418 if (index != -1) {
419 linkedStyleSheets.removeElementAt(index);
420 unlinkStyleSheet(ss, index);
421 if (index == 0 && linkedStyleSheets.size() == 0) {
422 linkedStyleSheets = null;
423 }
424 }
425 }
426 }
427 }
428
429 //
430 // The following is used to import style sheets.
431 //
432
433 /**
434 * Returns an array of the linked StyleSheets. Will return null
435 * if there are no linked StyleSheets.
436 *
437 * @since 1.3
438 */
439 public StyleSheet[] getStyleSheets() {
440 StyleSheet[] retValue;
441
442 synchronized(this) {
443 if (linkedStyleSheets != null) {
444 retValue = new StyleSheet[linkedStyleSheets.size()];
445 linkedStyleSheets.copyInto(retValue);
446 }
447 else {
448 retValue = null;
449 }
450 }
451 return retValue;
452 }
453
454 /**
455 * Imports a style sheet from <code>url</code>. The resulting rules
456 * are directly added to the receiver. If you do not want the rules
457 * to become part of the receiver, create a new StyleSheet and use
458 * addStyleSheet to link it in.
459 *
460 * @since 1.3
461 */
462 public void importStyleSheet(URL url) {
463 try {
464 InputStream is;
465
466 is = url.openStream();
467 Reader r = new BufferedReader(new InputStreamReader(is));
468 CssParser parser = new CssParser();
469 parser.parse(url, r, false, true);
470 r.close();
471 is.close();
472 } catch (Throwable e) {
473 // on error we simply have no styles... the html
474 // will look mighty wrong but still function.
475 }
476 }
477
478 /**
479 * Sets the base. All import statements that are relative, will be
480 * relative to <code>base</code>.
481 *
482 * @since 1.3
483 */
484 public void setBase(URL base) {
485 this.base = base;
486 }
487
488 /**
489 * Returns the base.
490 *
491 * @since 1.3
492 */
493 public URL getBase() {
494 return base;
495 }
496
497 /**
498 * Adds a CSS attribute to the given set.
499 *
500 * @since 1.3
501 */
502 public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key,
503 String value) {
504 css.addInternalCSSValue(attr, key, value);
505 }
506
507 /**
508 * Adds a CSS attribute to the given set.
509 *
510 * @since 1.3
511 */
512 public boolean addCSSAttributeFromHTML(MutableAttributeSet attr,
513 CSS.Attribute key, String value) {
514 Object iValue = css.getCssValue(key, value);
515 if (iValue != null) {
516 attr.addAttribute(key, iValue);
517 return true;
518 }
519 return false;
520 }
521
522 // ---- Conversion functionality ---------------------------------
523
524 /**
525 * Converts a set of HTML attributes to an equivalent
526 * set of CSS attributes.
527 *
528 * @param htmlAttrSet AttributeSet containing the HTML attributes.
529 */
530 public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) {
531 AttributeSet cssAttrSet = css.translateHTMLToCSS(htmlAttrSet);
532
533 MutableAttributeSet cssStyleSet = addStyle(null, null);
534 cssStyleSet.addAttributes(cssAttrSet);
535
536 return cssStyleSet;
537 }
538
539 /**
540 * Adds an attribute to the given set, and returns
541 * the new representative set. This is reimplemented to
542 * convert StyleConstant attributes to CSS prior to forwarding
543 * to the superclass behavior. The StyleConstants attribute
544 * has no corresponding CSS entry, the StyleConstants attribute
545 * is stored (but will likely be unused).
546 *
547 * @param old the old attribute set
548 * @param key the non-null attribute key
549 * @param value the attribute value
550 * @return the updated attribute set
551 * @see MutableAttributeSet#addAttribute
552 */
553 public AttributeSet addAttribute(AttributeSet old, Object key,
554 Object value) {
555 if (css == null) {
556 // supers constructor will call this before returning,
557 // and we need to make sure CSS is non null.
558 css = new CSS();
559 }
560 if (key instanceof StyleConstants) {
561 HTML.Tag tag = HTML.getTagForStyleConstantsKey(
562 (StyleConstants)key);
563
564 if (tag != null && old.isDefined(tag)) {
565 old = removeAttribute(old, tag);
566 }
567
568 Object cssValue = css.styleConstantsValueToCSSValue
569 ((StyleConstants)key, value);
570 if (cssValue != null) {
571 Object cssKey = css.styleConstantsKeyToCSSKey
572 ((StyleConstants)key);
573 if (cssKey != null) {
574 return super.addAttribute(old, cssKey, cssValue);
575 }
576 }
577 }
578 return super.addAttribute(old, key, value);
579 }
580
581 /**
582 * Adds a set of attributes to the element. If any of these attributes
583 * are StyleConstants attributes, they will be converted to CSS prior
584 * to forwarding to the superclass behavior.
585 *
586 * @param old the old attribute set
587 * @param attr the attributes to add
588 * @return the updated attribute set
589 * @see MutableAttributeSet#addAttribute
590 */
591 public AttributeSet addAttributes(AttributeSet old, AttributeSet attr) {
592 if (!(attr instanceof HTMLDocument.TaggedAttributeSet)) {
593 old = removeHTMLTags(old, attr);
594 }
595 return super.addAttributes(old, convertAttributeSet(attr));
596 }
597
598 /**
599 * Removes an attribute from the set. If the attribute is a StyleConstants
600 * attribute, the request will be converted to a CSS attribute prior to
601 * forwarding to the superclass behavior.
602 *
603 * @param old the old set of attributes
604 * @param key the non-null attribute name
605 * @return the updated attribute set
606 * @see MutableAttributeSet#removeAttribute
607 */
608 public AttributeSet removeAttribute(AttributeSet old, Object key) {
609 if (key instanceof StyleConstants) {
610 HTML.Tag tag = HTML.getTagForStyleConstantsKey(
611 (StyleConstants)key);
612 if (tag != null) {
613 old = super.removeAttribute(old, tag);
614 }
615
616 Object cssKey = css.styleConstantsKeyToCSSKey((StyleConstants)key);
617 if (cssKey != null) {
618 return super.removeAttribute(old, cssKey);
619 }
620 }
621 return super.removeAttribute(old, key);
622 }
623
624 /**
625 * Removes a set of attributes for the element. If any of the attributes
626 * is a StyleConstants attribute, the request will be converted to a CSS
627 * attribute prior to forwarding to the superclass behavior.
628 *
629 * @param old the old attribute set
630 * @param names the attribute names
631 * @return the updated attribute set
632 * @see MutableAttributeSet#removeAttributes
633 */
634 public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names) {
635 // PENDING: Should really be doing something similar to
636 // removeHTMLTags here, but it is rather expensive to have to
637 // clone names
638 return super.removeAttributes(old, names);
639 }
640
641 /**
642 * Removes a set of attributes. If any of the attributes
643 * is a StyleConstants attribute, the request will be converted to a CSS
644 * attribute prior to forwarding to the superclass behavior.
645 *
646 * @param old the old attribute set
647 * @param attrs the attributes
648 * @return the updated attribute set
649 * @see MutableAttributeSet#removeAttributes
650 */
651 public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs) {
652 if (old != attrs) {
653 old = removeHTMLTags(old, attrs);
654 }
655 return super.removeAttributes(old, convertAttributeSet(attrs));
656 }
657
658 /**
659 * Creates a compact set of attributes that might be shared.
660 * This is a hook for subclasses that want to alter the
661 * behavior of SmallAttributeSet. This can be reimplemented
662 * to return an AttributeSet that provides some sort of
663 * attribute conversion.
664 *
665 * @param a The set of attributes to be represented in the
666 * the compact form.
667 */
668 protected SmallAttributeSet createSmallAttributeSet(AttributeSet a) {
669 return new SmallConversionSet(a);
670 }
671
672 /**
673 * Creates a large set of attributes that should trade off
674 * space for time. This set will not be shared. This is
675 * a hook for subclasses that want to alter the behavior
676 * of the larger attribute storage format (which is
677 * SimpleAttributeSet by default). This can be reimplemented
678 * to return a MutableAttributeSet that provides some sort of
679 * attribute conversion.
680 *
681 * @param a The set of attributes to be represented in the
682 * the larger form.
683 */
684 protected MutableAttributeSet createLargeAttributeSet(AttributeSet a) {
685 return new LargeConversionSet(a);
686 }
687
688 /**
689 * For any StyleConstants key in attr that has an associated HTML.Tag,
690 * it is removed from old. The resulting AttributeSet is then returned.
691 */
692 private AttributeSet removeHTMLTags(AttributeSet old, AttributeSet attr) {
693 if (!(attr instanceof LargeConversionSet) &&
694 !(attr instanceof SmallConversionSet)) {
695 Enumeration names = attr.getAttributeNames();
696
697 while (names.hasMoreElements()) {
698 Object key = names.nextElement();
699
700 if (key instanceof StyleConstants) {
701 HTML.Tag tag = HTML.getTagForStyleConstantsKey(
702 (StyleConstants)key);
703
704 if (tag != null && old.isDefined(tag)) {
705 old = super.removeAttribute(old, tag);
706 }
707 }
708 }
709 }
710 return old;
711 }
712
713 /**
714 * Converts a set of attributes (if necessary) so that
715 * any attributes that were specified as StyleConstants
716 * attributes and have a CSS mapping, will be converted
717 * to CSS attributes.
718 */
719 AttributeSet convertAttributeSet(AttributeSet a) {
720 if ((a instanceof LargeConversionSet) ||
721 (a instanceof SmallConversionSet)) {
722 // known to be converted.
723 return a;
724 }
725 // in most cases, there are no StyleConstants attributes
726 // so we iterate the collection of keys to avoid creating
727 // a new set.
728 Enumeration names = a.getAttributeNames();
729 while (names.hasMoreElements()) {
730 Object name = names.nextElement();
731 if (name instanceof StyleConstants) {
732 // we really need to do a conversion, iterate again
733 // building a new set.
734 MutableAttributeSet converted = new LargeConversionSet();
735 Enumeration keys = a.getAttributeNames();
736 while (keys.hasMoreElements()) {
737 Object key = keys.nextElement();
738 Object cssValue = null;
739 if (key instanceof StyleConstants) {
740 // convert the StyleConstants attribute if possible
741 Object cssKey = css.styleConstantsKeyToCSSKey
742 ((StyleConstants)key);
743 if (cssKey != null) {
744 Object value = a.getAttribute(key);
745 cssValue = css.styleConstantsValueToCSSValue
746 ((StyleConstants)key, value);
747 if (cssValue != null) {
748 converted.addAttribute(cssKey, cssValue);
749 }
750 }
751 }
752 if (cssValue == null) {
753 converted.addAttribute(key, a.getAttribute(key));
754 }
755 }
756 return converted;
757 }
758 }
759 return a;
760 }
761
762 /**
763 * Large set of attributes that does conversion of requests
764 * for attributes of type StyleConstants.
765 */
766 class LargeConversionSet extends SimpleAttributeSet {
767
768 /**
769 * Creates a new attribute set based on a supplied set of attributes.
770 *
771 * @param source the set of attributes
772 */
773 public LargeConversionSet(AttributeSet source) {
774 super(source);
775 }
776
777 public LargeConversionSet() {
778 super();
779 }
780
781 /**
782 * Checks whether a given attribute is defined.
783 *
784 * @param key the attribute key
785 * @return true if the attribute is defined
786 * @see AttributeSet#isDefined
787 */
788 public boolean isDefined(Object key) {
789 if (key instanceof StyleConstants) {
790 Object cssKey = css.styleConstantsKeyToCSSKey
791 ((StyleConstants)key);
792 if (cssKey != null) {
793 return super.isDefined(cssKey);
794 }
795 }
796 return super.isDefined(key);
797 }
798
799 /**
800 * Gets the value of an attribute.
801 *
802 * @param key the attribute name
803 * @return the attribute value
804 * @see AttributeSet#getAttribute
805 */
806 public Object getAttribute(Object key) {
807 if (key instanceof StyleConstants) {
808 Object cssKey = css.styleConstantsKeyToCSSKey
809 ((StyleConstants)key);
810 if (cssKey != null) {
811 Object value = super.getAttribute(cssKey);
812 if (value != null) {
813 return css.cssValueToStyleConstantsValue
814 ((StyleConstants)key, value);
815 }
816 }
817 }
818 return super.getAttribute(key);
819 }
820 }
821
822 /**
823 * Small set of attributes that does conversion of requests
824 * for attributes of type StyleConstants.
825 */
826 class SmallConversionSet extends SmallAttributeSet {
827
828 /**
829 * Creates a new attribute set based on a supplied set of attributes.
830 *
831 * @param source the set of attributes
832 */
833 public SmallConversionSet(AttributeSet attrs) {
834 super(attrs);
835 }
836
837 /**
838 * Checks whether a given attribute is defined.
839 *
840 * @param key the attribute key
841 * @return true if the attribute is defined
842 * @see AttributeSet#isDefined
843 */
844 public boolean isDefined(Object key) {
845 if (key instanceof StyleConstants) {
846 Object cssKey = css.styleConstantsKeyToCSSKey
847 ((StyleConstants)key);
848 if (cssKey != null) {
849 return super.isDefined(cssKey);
850 }
851 }
852 return super.isDefined(key);
853 }
854
855 /**
856 * Gets the value of an attribute.
857 *
858 * @param key the attribute name
859 * @return the attribute value
860 * @see AttributeSet#getAttribute
861 */
862 public Object getAttribute(Object key) {
863 if (key instanceof StyleConstants) {
864 Object cssKey = css.styleConstantsKeyToCSSKey
865 ((StyleConstants)key);
866 if (cssKey != null) {
867 Object value = super.getAttribute(cssKey);
868 if (value != null) {
869 return css.cssValueToStyleConstantsValue
870 ((StyleConstants)key, value);
871 }
872 }
873 }
874 return super.getAttribute(key);
875 }
876 }
877
878 // ---- Resource handling ----------------------------------------
879
880 /**
881 * Fetches the font to use for the given set of attributes.
882 */
883 public Font getFont(AttributeSet a) {
884 return css.getFont(this, a, 12, this);
885 }
886
887 /**
888 * Takes a set of attributes and turn it into a foreground color
889 * specification. This might be used to specify things
890 * like brighter, more hue, etc.
891 *
892 * @param a the set of attributes
893 * @return the color
894 */
895 public Color getForeground(AttributeSet a) {
896 Color c = css.getColor(a, CSS.Attribute.COLOR);
897 if (c == null) {
898 return Color.black;
899 }
900 return c;
901 }
902
903 /**
904 * Takes a set of attributes and turn it into a background color
905 * specification. This might be used to specify things
906 * like brighter, more hue, etc.
907 *
908 * @param a the set of attributes
909 * @return the color
910 */
911 public Color getBackground(AttributeSet a) {
912 return css.getColor(a, CSS.Attribute.BACKGROUND_COLOR);
913 }
914
915 /**
916 * Fetches the box formatter to use for the given set
917 * of CSS attributes.
918 */
919 public BoxPainter getBoxPainter(AttributeSet a) {
920 return new BoxPainter(a, css, this);
921 }
922
923 /**
924 * Fetches the list formatter to use for the given set
925 * of CSS attributes.
926 */
927 public ListPainter getListPainter(AttributeSet a) {
928 return new ListPainter(a, this);
929 }
930
931 /**
932 * Sets the base font size, with valid values between 1 and 7.
933 */
934 public void setBaseFontSize(int sz) {
935 css.setBaseFontSize(sz);
936 }
937
938 /**
939 * Sets the base font size from the passed in String. The string
940 * can either identify a specific font size, with legal values between
941 * 1 and 7, or identifiy a relative font size such as +1 or -2.
942 */
943 public void setBaseFontSize(String size) {
944 css.setBaseFontSize(size);
945 }
946
947 public static int getIndexOfSize(float pt) {
948 return CSS.getIndexOfSize(pt, sizeMapDefault);
949 }
950
951 /**
952 * Returns the point size, given a size index.
953 */
954 public float getPointSize(int index) {
955 return css.getPointSize(index, this);
956 }
957
958 /**
959 * Given a string such as "+2", "-2", or "2",
960 * returns a point size value.
961 */
962 public float getPointSize(String size) {
963 return css.getPointSize(size, this);
964 }
965
966 /**
967 * Converts a color string such as "RED" or "#NNNNNN" to a Color.
968 * Note: This will only convert the HTML3.2 color strings
969 * or a string of length 7;
970 * otherwise, it will return null.
971 */
972 public Color stringToColor(String string) {
973 return CSS.stringToColor(string);
974 }
975
976 /**
977 * Returns the ImageIcon to draw in the background for
978 * <code>attr</code>.
979 */
980 ImageIcon getBackgroundImage(AttributeSet attr) {
981 Object value = attr.getAttribute(CSS.Attribute.BACKGROUND_IMAGE);
982
983 if (value != null) {
984 return ((CSS.BackgroundImage)value).getImage(getBase());
985 }
986 return null;
987 }
988
989 /**
990 * Adds a rule into the StyleSheet.
991 *
992 * @param selector the selector to use for the rule.
993 * This will be a set of simple selectors, and must
994 * be a length of 1 or greater.
995 * @param declaration the set of CSS attributes that
996 * make up the rule.
997 */
998 void addRule(String[] selector, AttributeSet declaration,
999 boolean isLinked) {
1000 int n = selector.length;
1001 StringBuffer sb = new StringBuffer();
1002 sb.append(selector[0]);
1003 for (int counter = 1; counter < n; counter++) {
1004 sb.append(' ');
1005 sb.append(selector[counter]);
1006 }
1007 String selectorName = sb.toString();
1008 Style rule = getStyle(selectorName);
1009 if (rule == null) {
1010 // Notice how the rule is first created, and it not part of
1011 // the synchronized block. It is done like this as creating
1012 // a new rule will fire a ChangeEvent. We do not want to be
1013 // holding the lock when calling to other objects, it can
1014 // result in deadlock.
1015 Style altRule = addStyle(selectorName, null);
1016 synchronized(this) {
1017 SelectorMapping mapping = getRootSelectorMapping();
1018 for (int i = n - 1; i >= 0; i--) {
1019 mapping = mapping.getChildSelectorMapping
1020 (selector[i], true);
1021 }
1022 rule = mapping.getStyle();
1023 if (rule == null) {
1024 rule = altRule;
1025 mapping.setStyle(rule);
1026 refreshResolvedRules(selectorName, selector, rule,
1027 mapping.getSpecificity());
1028 }
1029 }
1030 }
1031 if (isLinked) {
1032 rule = getLinkedStyle(rule);
1033 }
1034 rule.addAttributes(declaration);
1035 }
1036
1037 //
1038 // The following gaggle of methods is used in maintaing the rules from
1039 // the sheet.
1040 //
1041
1042 /**
1043 * Updates the attributes of the rules to reference any related
1044 * rules in <code>ss</code>.
1045 */
1046 private synchronized void linkStyleSheetAt(StyleSheet ss, int index) {
1047 if (resolvedStyles.size() > 0) {
1048 Enumeration values = resolvedStyles.elements();
1049 while (values.hasMoreElements()) {
1050 ResolvedStyle rule = (ResolvedStyle)values.nextElement();
1051 rule.insertExtendedStyleAt(ss.getRule(rule.getName()),
1052 index);
1053 }
1054 }
1055 }
1056
1057 /**
1058 * Removes references to the rules in <code>ss</code>.
1059 * <code>index</code> gives the index the StyleSheet was at, that is
1060 * how many StyleSheets had been added before it.
1061 */
1062 private synchronized void unlinkStyleSheet(StyleSheet ss, int index) {
1063 if (resolvedStyles.size() > 0) {
1064 Enumeration values = resolvedStyles.elements();
1065 while (values.hasMoreElements()) {
1066 ResolvedStyle rule = (ResolvedStyle)values.nextElement();
1067 rule.removeExtendedStyleAt(index);
1068 }
1069 }
1070 }
1071
1072 /**
1073 * Returns the simple selectors that comprise selector.
1074 */
1075 /* protected */
1076 String[] getSimpleSelectors(String selector) {
1077 selector = cleanSelectorString(selector);
1078 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1079 Vector selectors = sb.getVector();
1080 int lastIndex = 0;
1081 int length = selector.length();
1082 while (lastIndex != -1) {
1083 int newIndex = selector.indexOf(' ', lastIndex);
1084 if (newIndex != -1) {
1085 selectors.addElement(selector.substring(lastIndex, newIndex));
1086 if (++newIndex == length) {
1087 lastIndex = -1;
1088 }
1089 else {
1090 lastIndex = newIndex;
1091 }
1092 }
1093 else {
1094 selectors.addElement(selector.substring(lastIndex));
1095 lastIndex = -1;
1096 }
1097 }
1098 String[] retValue = new String[selectors.size()];
1099 selectors.copyInto(retValue);
1100 SearchBuffer.releaseSearchBuffer(sb);
1101 return retValue;
1102 }
1103
1104 /**
1105 * Returns a string that only has one space between simple selectors,
1106 * which may be the passed in String.
1107 */
1108 /*protected*/ String cleanSelectorString(String selector) {
1109 boolean lastWasSpace = true;
1110 for (int counter = 0, maxCounter = selector.length();
1111 counter < maxCounter; counter++) {
1112 switch(selector.charAt(counter)) {
1113 case ' ':
1114 if (lastWasSpace) {
1115 return _cleanSelectorString(selector);
1116 }
1117 lastWasSpace = true;
1118 break;
1119 case '\n':
1120 case '\r':
1121 case '\t':
1122 return _cleanSelectorString(selector);
1123 default:
1124 lastWasSpace = false;
1125 }
1126 }
1127 if (lastWasSpace) {
1128 return _cleanSelectorString(selector);
1129 }
1130 // It was fine.
1131 return selector;
1132 }
1133
1134 /**
1135 * Returns a new String that contains only one space between non
1136 * white space characters.
1137 */
1138 private String _cleanSelectorString(String selector) {
1139 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1140 StringBuffer buff = sb.getStringBuffer();
1141 boolean lastWasSpace = true;
1142 int lastIndex = 0;
1143 char[] chars = selector.toCharArray();
1144 int numChars = chars.length;
1145 String retValue = null;
1146 try {
1147 for (int counter = 0; counter < numChars; counter++) {
1148 switch(chars[counter]) {
1149 case ' ':
1150 if (!lastWasSpace) {
1151 lastWasSpace = true;
1152 if (lastIndex < counter) {
1153 buff.append(chars, lastIndex,
1154 1 + counter - lastIndex);
1155 }
1156 }
1157 lastIndex = counter + 1;
1158 break;
1159 case '\n':
1160 case '\r':
1161 case '\t':
1162 if (!lastWasSpace) {
1163 lastWasSpace = true;
1164 if (lastIndex < counter) {
1165 buff.append(chars, lastIndex,
1166 counter - lastIndex);
1167 buff.append(' ');
1168 }
1169 }
1170 lastIndex = counter + 1;
1171 break;
1172 default:
1173 lastWasSpace = false;
1174 break;
1175 }
1176 }
1177 if (lastWasSpace && buff.length() > 0) {
1178 // Remove last space.
1179 buff.setLength(buff.length() - 1);
1180 }
1181 else if (lastIndex < numChars) {
1182 buff.append(chars, lastIndex, numChars - lastIndex);
1183 }
1184 retValue = buff.toString();
1185 }
1186 finally {
1187 SearchBuffer.releaseSearchBuffer(sb);
1188 }
1189 return retValue;
1190 }
1191
1192 /**
1193 * Returns the root selector mapping that all selectors are relative
1194 * to. This is an inverted graph of the selectors.
1195 */
1196 private SelectorMapping getRootSelectorMapping() {
1197 return selectorMapping;
1198 }
1199
1200 /**
1201 * Returns the specificity of the passed in String. It assumes the
1202 * passed in string doesn't contain junk, that is each selector is
1203 * separated by a space and each selector at most contains one . or one
1204 * #. A simple selector has a weight of 1, an id selector has a weight
1205 * of 100, and a class selector has a weight of 10000.
1206 */
1207 /*protected*/ static int getSpecificity(String selector) {
1208 int specificity = 0;
1209 boolean lastWasSpace = true;
1210
1211 for (int counter = 0, maxCounter = selector.length();
1212 counter < maxCounter; counter++) {
1213 switch(selector.charAt(counter)) {
1214 case '.':
1215 specificity += 100;
1216 break;
1217 case '#':
1218 specificity += 10000;
1219 break;
1220 case ' ':
1221 lastWasSpace = true;
1222 break;
1223 default:
1224 if (lastWasSpace) {
1225 lastWasSpace = false;
1226 specificity += 1;
1227 }
1228 }
1229 }
1230 return specificity;
1231 }
1232
1233 /**
1234 * Returns the style that linked attributes should be added to. This
1235 * will create the style if necessary.
1236 */
1237 private Style getLinkedStyle(Style localStyle) {
1238 // NOTE: This is not synchronized, and the caller of this does
1239 // not synchronize. There is the chance for one of the callers to
1240 // overwrite the existing resolved parent, but it is quite rare.
1241 // The reason this is left like this is because setResolveParent
1242 // will fire a ChangeEvent. It is really, REALLY bad for us to
1243 // hold a lock when calling outside of us, it may cause a deadlock.
1244 Style retStyle = (Style)localStyle.getResolveParent();
1245 if (retStyle == null) {
1246 retStyle = addStyle(null, null);
1247 localStyle.setResolveParent(retStyle);
1248 }
1249 return retStyle;
1250 }
1251
1252 /**
1253 * Returns the resolved style for <code>selector</code>. This will
1254 * create the resolved style, if necessary.
1255 */
1256 private synchronized Style getResolvedStyle(String selector,
1257 Vector elements,
1258 HTML.Tag t) {
1259 Style retStyle = (Style)resolvedStyles.get(selector);
1260 if (retStyle == null) {
1261 retStyle = createResolvedStyle(selector, elements, t);
1262 }
1263 return retStyle;
1264 }
1265
1266 /**
1267 * Returns the resolved style for <code>selector</code>. This will
1268 * create the resolved style, if necessary.
1269 */
1270 private synchronized Style getResolvedStyle(String selector) {
1271 Style retStyle = (Style)resolvedStyles.get(selector);
1272 if (retStyle == null) {
1273 retStyle = createResolvedStyle(selector);
1274 }
1275 return retStyle;
1276 }
1277
1278 /**
1279 * Adds <code>mapping</code> to <code>elements</code>. It is added
1280 * such that <code>elements</code> will remain ordered by
1281 * specificity.
1282 */
1283 private void addSortedStyle(SelectorMapping mapping, Vector elements) {
1284 int size = elements.size();
1285
1286 if (size > 0) {
1287 int specificity = mapping.getSpecificity();
1288
1289 for (int counter = 0; counter < size; counter++) {
1290 if (specificity >= ((SelectorMapping)elements.elementAt
1291 (counter)).getSpecificity()) {
1292 elements.insertElementAt(mapping, counter);
1293 return;
1294 }
1295 }
1296 }
1297 elements.addElement(mapping);
1298 }
1299
1300 /**
1301 * Adds <code>parentMapping</code> to <code>styles</code>, and
1302 * recursively calls this method if <code>parentMapping</code> has
1303 * any child mappings for any of the Elements in <code>elements</code>.
1304 */
1305 private synchronized void getStyles(SelectorMapping parentMapping,
1306 Vector styles,
1307 String[] tags, String[] ids, String[] classes,
1308 int index, int numElements,
1309 Hashtable alreadyChecked) {
1310 // Avoid desending the same mapping twice.
1311 if (alreadyChecked.contains(parentMapping)) {
1312 return;
1313 }
1314 alreadyChecked.put(parentMapping, parentMapping);
1315 Style style = parentMapping.getStyle();
1316 if (style != null) {
1317 addSortedStyle(parentMapping, styles);
1318 }
1319 for (int counter = index; counter < numElements; counter++) {
1320 String tagString = tags[counter];
1321 if (tagString != null) {
1322 SelectorMapping childMapping = parentMapping.
1323 getChildSelectorMapping(tagString, false);
1324 if (childMapping != null) {
1325 getStyles(childMapping, styles, tags, ids, classes,
1326 counter + 1, numElements, alreadyChecked);
1327 }
1328 if (classes[counter] != null) {
1329 String className = classes[counter];
1330 childMapping = parentMapping.getChildSelectorMapping(
1331 tagString + "." + className, false);
1332 if (childMapping != null) {
1333 getStyles(childMapping, styles, tags, ids, classes,
1334 counter + 1, numElements, alreadyChecked);
1335 }
1336 childMapping = parentMapping.getChildSelectorMapping(
1337 "." + className, false);
1338 if (childMapping != null) {
1339 getStyles(childMapping, styles, tags, ids, classes,
1340 counter + 1, numElements, alreadyChecked);
1341 }
1342 }
1343 if (ids[counter] != null) {
1344 String idName = ids[counter];
1345 childMapping = parentMapping.getChildSelectorMapping(
1346 tagString + "#" + idName, false);
1347 if (childMapping != null) {
1348 getStyles(childMapping, styles, tags, ids, classes,
1349 counter + 1, numElements, alreadyChecked);
1350 }
1351 childMapping = parentMapping.getChildSelectorMapping(
1352 "#" + idName, false);
1353 if (childMapping != null) {
1354 getStyles(childMapping, styles, tags, ids, classes,
1355 counter + 1, numElements, alreadyChecked);
1356 }
1357 }
1358 }
1359 }
1360 }
1361
1362 /**
1363 * Creates and returns a Style containing all the rules that match
1364 * <code>selector</code>.
1365 */
1366 private synchronized Style createResolvedStyle(String selector,
1367 String[] tags,
1368 String[] ids, String[] classes) {
1369 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1370 Vector tempVector = sb.getVector();
1371 Hashtable tempHashtable = sb.getHashtable();
1372 // Determine all the Styles that are appropriate, placing them
1373 // in tempVector
1374 try {
1375 SelectorMapping mapping = getRootSelectorMapping();
1376 int numElements = tags.length;
1377 String tagString = tags[0];
1378 SelectorMapping childMapping = mapping.getChildSelectorMapping(
1379 tagString, false);
1380 if (childMapping != null) {
1381 getStyles(childMapping, tempVector, tags, ids, classes, 1,
1382 numElements, tempHashtable);
1383 }
1384 if (classes[0] != null) {
1385 String className = classes[0];
1386 childMapping = mapping.getChildSelectorMapping(
1387 tagString + "." + className, false);
1388 if (childMapping != null) {
1389 getStyles(childMapping, tempVector, tags, ids, classes, 1,
1390 numElements, tempHashtable);
1391 }
1392 childMapping = mapping.getChildSelectorMapping(
1393 "." + className, false);
1394 if (childMapping != null) {
1395 getStyles(childMapping, tempVector, tags, ids, classes,
1396 1, numElements, tempHashtable);
1397 }
1398 }
1399 if (ids[0] != null) {
1400 String idName = ids[0];
1401 childMapping = mapping.getChildSelectorMapping(
1402 tagString + "#" + idName, false);
1403 if (childMapping != null) {
1404 getStyles(childMapping, tempVector, tags, ids, classes,
1405 1, numElements, tempHashtable);
1406 }
1407 childMapping = mapping.getChildSelectorMapping(
1408 "#" + idName, false);
1409 if (childMapping != null) {
1410 getStyles(childMapping, tempVector, tags, ids, classes,
1411 1, numElements, tempHashtable);
1412 }
1413 }
1414 // Create a new Style that will delegate to all the matching
1415 // Styles.
1416 int numLinkedSS = (linkedStyleSheets != null) ?
1417 linkedStyleSheets.size() : 0;
1418 int numStyles = tempVector.size();
1419 AttributeSet[] attrs = new AttributeSet[numStyles + numLinkedSS];
1420 for (int counter = 0; counter < numStyles; counter++) {
1421 attrs[counter] = ((SelectorMapping)tempVector.
1422 elementAt(counter)).getStyle();
1423 }
1424 // Get the AttributeSet from linked style sheets.
1425 for (int counter = 0; counter < numLinkedSS; counter++) {
1426 AttributeSet attr = ((StyleSheet)linkedStyleSheets.
1427 elementAt(counter)).getRule(selector);
1428 if (attr == null) {
1429 attrs[counter + numStyles] = SimpleAttributeSet.EMPTY;
1430 }
1431 else {
1432 attrs[counter + numStyles] = attr;
1433 }
1434 }
1435 ResolvedStyle retStyle = new ResolvedStyle(selector, attrs,
1436 numStyles);
1437 resolvedStyles.put(selector, retStyle);
1438 return retStyle;
1439 }
1440 finally {
1441 SearchBuffer.releaseSearchBuffer(sb);
1442 }
1443 }
1444
1445 /**
1446 * Creates and returns a Style containing all the rules that
1447 * matches <code>selector</code>.
1448 *
1449 * @param elements a Vector of all the Elements
1450 * the style is being asked for. The
1451 * first Element is the deepest Element, with the last Element
1452 * representing the root.
1453 * @param t the Tag to use for
1454 * the first Element in <code>elements</code>
1455 */
1456 private Style createResolvedStyle(String selector, Vector elements,
1457 HTML.Tag t) {
1458 int numElements = elements.size();
1459 // Build three arrays, one for tags, one for class's, and one for
1460 // id's
1461 String tags[] = new String[numElements];
1462 String ids[] = new String[numElements];
1463 String classes[] = new String[numElements];
1464 for (int counter = 0; counter < numElements; counter++) {
1465 Element e = (Element)elements.elementAt(counter);
1466 AttributeSet attr = e.getAttributes();
1467 if (counter == 0 && e.isLeaf()) {
1468 // For leafs, we use the second tier attributes.
1469 Object testAttr = attr.getAttribute(t);
1470 if (testAttr instanceof AttributeSet) {
1471 attr = (AttributeSet)testAttr;
1472 }
1473 else {
1474 attr = null;
1475 }
1476 }
1477 if (attr != null) {
1478 HTML.Tag tag = (HTML.Tag)attr.getAttribute(StyleConstants.
1479 NameAttribute);
1480 if (tag != null) {
1481 tags[counter] = tag.toString();
1482 }
1483 else {
1484 tags[counter] = null;
1485 }
1486 if (attr.isDefined(HTML.Attribute.CLASS)) {
1487 classes[counter] = attr.getAttribute
1488 (HTML.Attribute.CLASS).toString();
1489 }
1490 else {
1491 classes[counter] = null;
1492 }
1493 if (attr.isDefined(HTML.Attribute.ID)) {
1494 ids[counter] = attr.getAttribute(HTML.Attribute.ID).
1495 toString();
1496 }
1497 else {
1498 ids[counter] = null;
1499 }
1500 }
1501 else {
1502 tags[counter] = ids[counter] = classes[counter] = null;
1503 }
1504 }
1505 tags[0] = t.toString();
1506 return createResolvedStyle(selector, tags, ids, classes);
1507 }
1508
1509 /**
1510 * Creates and returns a Style containing all the rules that match
1511 * <code>selector</code>. It is assumed that each simple selector
1512 * in <code>selector</code> is separated by a space.
1513 */
1514 private Style createResolvedStyle(String selector) {
1515 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1516 // Will contain the tags, ids, and classes, in that order.
1517 Vector elements = sb.getVector();
1518 try {
1519 boolean done;
1520 int dotIndex = 0;
1521 int spaceIndex = 0;
1522 int poundIndex = 0;
1523 int lastIndex = 0;
1524 int length = selector.length();
1525 while (lastIndex < length) {
1526 if (dotIndex == lastIndex) {
1527 dotIndex = selector.indexOf('.', lastIndex);
1528 }
1529 if (poundIndex == lastIndex) {
1530 poundIndex = selector.indexOf('#', lastIndex);
1531 }
1532 spaceIndex = selector.indexOf(' ', lastIndex);
1533 if (spaceIndex == -1) {
1534 spaceIndex = length;
1535 }
1536 if (dotIndex != -1 && poundIndex != -1 &&
1537 dotIndex < spaceIndex && poundIndex < spaceIndex) {
1538 if (poundIndex < dotIndex) {
1539 // #.
1540 if (lastIndex == poundIndex) {
1541 elements.addElement("");
1542 }
1543 else {
1544 elements.addElement(selector.substring(lastIndex,
1545 poundIndex));
1546 }
1547 if ((dotIndex + 1) < spaceIndex) {
1548 elements.addElement(selector.substring
1549 (dotIndex + 1, spaceIndex));
1550 }
1551 else {
1552 elements.addElement(null);
1553 }
1554 if ((poundIndex + 1) == dotIndex) {
1555 elements.addElement(null);
1556 }
1557 else {
1558 elements.addElement(selector.substring
1559 (poundIndex + 1, dotIndex));
1560 }
1561 }
1562 else if(poundIndex < spaceIndex) {
1563 // .#
1564 if (lastIndex == dotIndex) {
1565 elements.addElement("");
1566 }
1567 else {
1568 elements.addElement(selector.substring(lastIndex,
1569 dotIndex));
1570 }
1571 if ((dotIndex + 1) < poundIndex) {
1572 elements.addElement(selector.substring
1573 (dotIndex + 1, poundIndex));
1574 }
1575 else {
1576 elements.addElement(null);
1577 }
1578 if ((poundIndex + 1) == spaceIndex) {
1579 elements.addElement(null);
1580 }
1581 else {
1582 elements.addElement(selector.substring
1583 (poundIndex + 1, spaceIndex));
1584 }
1585 }
1586 dotIndex = poundIndex = spaceIndex + 1;
1587 }
1588 else if (dotIndex != -1 && dotIndex < spaceIndex) {
1589 // .
1590 if (dotIndex == lastIndex) {
1591 elements.addElement("");
1592 }
1593 else {
1594 elements.addElement(selector.substring(lastIndex,
1595 dotIndex));
1596 }
1597 if ((dotIndex + 1) == spaceIndex) {
1598 elements.addElement(null);
1599 }
1600 else {
1601 elements.addElement(selector.substring(dotIndex + 1,
1602 spaceIndex));
1603 }
1604 elements.addElement(null);
1605 dotIndex = spaceIndex + 1;
1606 }
1607 else if (poundIndex != -1 && poundIndex < spaceIndex) {
1608 // #
1609 if (poundIndex == lastIndex) {
1610 elements.addElement("");
1611 }
1612 else {
1613 elements.addElement(selector.substring(lastIndex,
1614 poundIndex));
1615 }
1616 elements.addElement(null);
1617 if ((poundIndex + 1) == spaceIndex) {
1618 elements.addElement(null);
1619 }
1620 else {
1621 elements.addElement(selector.substring(poundIndex + 1,
1622 spaceIndex));
1623 }
1624 poundIndex = spaceIndex + 1;
1625 }
1626 else {
1627 // id
1628 elements.addElement(selector.substring(lastIndex,
1629 spaceIndex));
1630 elements.addElement(null);
1631 elements.addElement(null);
1632 }
1633 lastIndex = spaceIndex + 1;
1634 }
1635 // Create the tag, id, and class arrays.
1636 int total = elements.size();
1637 int numTags = total / 3;
1638 String[] tags = new String[numTags];
1639 String[] ids = new String[numTags];
1640 String[] classes = new String[numTags];
1641 for (int index = 0, eIndex = total - 3; index < numTags;
1642 index++, eIndex -= 3) {
1643 tags[index] = (String)elements.elementAt(eIndex);
1644 classes[index] = (String)elements.elementAt(eIndex + 1);
1645 ids[index] = (String)elements.elementAt(eIndex + 2);
1646 }
1647 return createResolvedStyle(selector, tags, ids, classes);
1648 }
1649 finally {
1650 SearchBuffer.releaseSearchBuffer(sb);
1651 }
1652 }
1653
1654 /**
1655 * Should be invoked when a new rule is added that did not previously
1656 * exist. Goes through and refreshes the necessary resolved
1657 * rules.
1658 */
1659 private synchronized void refreshResolvedRules(String selectorName,
1660 String[] selector,
1661 Style newStyle,
1662 int specificity) {
1663 if (resolvedStyles.size() > 0) {
1664 Enumeration values = resolvedStyles.elements();
1665 while (values.hasMoreElements()) {
1666 ResolvedStyle style = (ResolvedStyle)values.nextElement();
1667 if (style.matches(selectorName)) {
1668 style.insertStyle(newStyle, specificity);
1669 }
1670 }
1671 }
1672 }
1673
1674
1675 /**
1676 * A temporary class used to hold a Vector, a StringBuffer and a
1677 * Hashtable. This is used to avoid allocing a lot of garbage when
1678 * searching for rules. Use the static method obtainSearchBuffer and
1679 * releaseSearchBuffer to get a SearchBuffer, and release it when
1680 * done.
1681 */
1682 private static class SearchBuffer {
1683 /** A stack containing instances of SearchBuffer. Used in getting
1684 * rules. */
1685 static Stack searchBuffers = new Stack();
1686 // A set of temporary variables that can be used in whatever way.
1687 Vector vector = null;
1688 StringBuffer stringBuffer = null;
1689 Hashtable hashtable = null;
1690
1691 /**
1692 * Returns an instance of SearchBuffer. Be sure and issue
1693 * a releaseSearchBuffer when done with it.
1694 */
1695 static SearchBuffer obtainSearchBuffer() {
1696 SearchBuffer sb;
1697 try {
1698 if(!searchBuffers.empty()) {
1699 sb = (SearchBuffer)searchBuffers.pop();
1700 } else {
1701 sb = new SearchBuffer();
1702 }
1703 } catch (EmptyStackException ese) {
1704 sb = new SearchBuffer();
1705 }
1706 return sb;
1707 }
1708
1709 /**
1710 * Adds <code>sb</code> to the stack of SearchBuffers that can
1711 * be used.
1712 */
1713 static void releaseSearchBuffer(SearchBuffer sb) {
1714 sb.empty();
1715 searchBuffers.push(sb);
1716 }
1717
1718 StringBuffer getStringBuffer() {
1719 if (stringBuffer == null) {
1720 stringBuffer = new StringBuffer();
1721 }
1722 return stringBuffer;
1723 }
1724
1725 Vector getVector() {
1726 if (vector == null) {
1727 vector = new Vector();
1728 }
1729 return vector;
1730 }
1731
1732 Hashtable getHashtable() {
1733 if (hashtable == null) {
1734 hashtable = new Hashtable();
1735 }
1736 return hashtable;
1737 }
1738
1739 void empty() {
1740 if (stringBuffer != null) {
1741 stringBuffer.setLength(0);
1742 }
1743 if (vector != null) {
1744 vector.removeAllElements();
1745 }
1746 if (hashtable != null) {
1747 hashtable.clear();
1748 }
1749 }
1750 }
1751
1752
1753 static final Border noBorder = new EmptyBorder(0,0,0,0);
1754
1755 /**
1756 * Class to carry out some of the duties of
1757 * CSS formatting. Implementations of this
1758 * class enable views to present the CSS formatting
1759 * while not knowing anything about how the CSS values
1760 * are being cached.
1761 * <p>
1762 * As a delegate of Views, this object is responsible for
1763 * the insets of a View and making sure the background
1764 * is maintained according to the CSS attributes.
1765 */
1766 public static class BoxPainter implements Serializable {
1767
1768 BoxPainter(AttributeSet a, CSS css, StyleSheet ss) {
1769 this.ss = ss;
1770 this.css = css;
1771 border = getBorder(a);
1772 binsets = border.getBorderInsets(null);
1773 topMargin = getLength(CSS.Attribute.MARGIN_TOP, a);
1774 bottomMargin = getLength(CSS.Attribute.MARGIN_BOTTOM, a);
1775 leftMargin = getLength(CSS.Attribute.MARGIN_LEFT, a);
1776 rightMargin = getLength(CSS.Attribute.MARGIN_RIGHT, a);
1777 bg = ss.getBackground(a);
1778 if (ss.getBackgroundImage(a) != null) {
1779 bgPainter = new BackgroundImagePainter(a, css, ss);
1780 }
1781 }
1782
1783 /**
1784 * Fetches a border to render for the given attributes.
1785 * PENDING(prinz) This is pretty badly hacked at the
1786 * moment.
1787 */
1788 Border getBorder(AttributeSet a) {
1789 return new CSSBorder(a);
1790 }
1791
1792 /**
1793 * Fetches the color to use for borders. This will either be
1794 * the value specified by the border-color attribute (which
1795 * is not inherited), or it will default to the color attribute
1796 * (which is inherited).
1797 */
1798 Color getBorderColor(AttributeSet a) {
1799 Color color = css.getColor(a, CSS.Attribute.BORDER_COLOR);
1800 if (color == null) {
1801 color = css.getColor(a, CSS.Attribute.COLOR);
1802 if (color == null) {
1803 return Color.black;
1804 }
1805 }
1806 return color;
1807 }
1808
1809 /**
1810 * Fetches the inset needed on a given side to
1811 * account for the margin, border, and padding.
1812 *
1813 * @param side The size of the box to fetch the
1814 * inset for. This can be View.TOP,
1815 * View.LEFT, View.BOTTOM, or View.RIGHT.
1816 * @param v the view making the request. This is
1817 * used to get the AttributeSet, and may be used to
1818 * resolve percentage arguments.
1819 * @exception IllegalArgumentException for an invalid direction
1820 */
1821 public float getInset(int side, View v) {
1822 AttributeSet a = v.getAttributes();
1823 float inset = 0;
1824 switch(side) {
1825 case View.LEFT:
1826 inset += getOrientationMargin(HorizontalMargin.LEFT,
1827 leftMargin, a, isLeftToRight(v));
1828 inset += binsets.left;
1829 inset += getLength(CSS.Attribute.PADDING_LEFT, a);
1830 break;
1831 case View.RIGHT:
1832 inset += getOrientationMargin(HorizontalMargin.RIGHT,
1833 rightMargin, a, isLeftToRight(v));
1834 inset += binsets.right;
1835 inset += getLength(CSS.Attribute.PADDING_RIGHT, a);
1836 break;
1837 case View.TOP:
1838 inset += topMargin;
1839 inset += binsets.top;
1840 inset += getLength(CSS.Attribute.PADDING_TOP, a);
1841 break;
1842 case View.BOTTOM:
1843 inset += bottomMargin;
1844 inset += binsets.bottom;
1845 inset += getLength(CSS.Attribute.PADDING_BOTTOM, a);
1846 break;
1847 default:
1848 throw new IllegalArgumentException("Invalid side: " + side);
1849 }
1850 return inset;
1851 }
1852
1853 /**
1854 * Paints the CSS box according to the attributes
1855 * given. This should paint the border, padding,
1856 * and background.
1857 *
1858 * @param g the rendering surface.
1859 * @param x the x coordinate of the allocated area to
1860 * render into.
1861 * @param y the y coordinate of the allocated area to
1862 * render into.
1863 * @param w the width of the allocated area to render into.
1864 * @param h the height of the allocated area to render into.
1865 * @param v the view making the request. This is
1866 * used to get the AttributeSet, and may be used to
1867 * resolve percentage arguments.
1868 */
1869 public void paint(Graphics g, float x, float y, float w, float h, View v) {
1870 // PENDING(prinz) implement real rendering... which would
1871 // do full set of border and background capabilities.
1872 // remove margin
1873
1874 float dx = 0;
1875 float dy = 0;
1876 float dw = 0;
1877 float dh = 0;
1878 AttributeSet a = v.getAttributes();
1879 boolean isLeftToRight = isLeftToRight(v);
1880 float localLeftMargin = getOrientationMargin(HorizontalMargin.LEFT,
1881 leftMargin,
1882 a, isLeftToRight);
1883 float localRightMargin = getOrientationMargin(HorizontalMargin.RIGHT,
1884 rightMargin,
1885 a, isLeftToRight);
1886 if (!(v instanceof HTMLEditorKit.HTMLFactory.BodyBlockView)) {
1887 dx = localLeftMargin;
1888 dy = topMargin;
1889 dw = -(localLeftMargin + localRightMargin);
1890 dh = -(topMargin + bottomMargin);
1891 }
1892 if (bg != null) {
1893 g.setColor(bg);
1894 g.fillRect((int) (x + dx),
1895 (int) (y + dy),
1896 (int) (w + dw),
1897 (int) (h + dh));
1898 }
1899 if (bgPainter != null) {
1900 bgPainter.paint(g, x + dx, y + dy, w + dw, h + dh, v);
1901 }
1902 x += localLeftMargin;
1903 y += topMargin;
1904 w -= localLeftMargin + localRightMargin;
1905 h -= topMargin + bottomMargin;
1906 if (border instanceof BevelBorder) {
1907 //BevelBorder does not support border width
1908 int bw = (int) getLength(CSS.Attribute.BORDER_TOP_WIDTH, a);
1909 for (int i = bw - 1; i >= 0; i--) {
1910 border.paintBorder(null, g, (int) x + i, (int) y + i,
1911 (int) w - 2 * i, (int) h - 2 * i);
1912 }
1913 } else {
1914 border.paintBorder(null, g, (int) x, (int) y, (int) w, (int) h);
1915 }
1916 }
1917
1918 float getLength(CSS.Attribute key, AttributeSet a) {
1919 return css.getLength(a, key, ss);
1920 }
1921
1922 static boolean isLeftToRight(View v) {
1923 boolean ret = true;
1924 if (isOrientationAware(v)) {
1925 Container container = null;
1926 if (v != null && (container = v.getContainer()) != null) {
1927 ret = container.getComponentOrientation().isLeftToRight();
1928 }
1929 }
1930 return ret;
1931 }
1932
1933 /*
1934 * only certain tags are concerned about orientation
1935 * <dir>, <menu>, <ul>, <ol>
1936 * for all others we return true. It is implemented this way
1937 * for performance purposes
1938 */
1939 static boolean isOrientationAware(View v) {
1940 boolean ret = false;
1941 AttributeSet attr = null;
1942 Object obj = null;
1943 if (v != null
1944 && (attr = v.getElement().getAttributes()) != null
1945 && (obj = attr.getAttribute(StyleConstants.NameAttribute)) instanceof HTML.Tag
1946 && (obj == HTML.Tag.DIR
1947 || obj == HTML.Tag.MENU
1948 || obj == HTML.Tag.UL
1949 || obj == HTML.Tag.OL)) {
1950 ret = true;
1951 }
1952
1953 return ret;
1954 }
1955
1956 static enum HorizontalMargin { LEFT, RIGHT };
1957
1958 /**
1959 * for <dir>, <menu>, <ul> etc.
1960 * margins are Left-To-Right/Right-To-Left depended.
1961 * see 5088268 for more details
1962 * margin-(left|right)-(ltr|rtl) were introduced to describe it
1963 * if margin-(left|right) is present we are to use it.
1964 *
1965 * @param side The horizontal side to fetch margin for
1966 * This can be HorizontalMargin.LEFT or HorizontalMargin.RIGHT
1967 * @param cssMargin margin from css
1968 * @param a AttributeSet for the View we getting margin for
1969 * @param isLeftToRight
1970 * @return orientation depended margin
1971 */
1972 float getOrientationMargin(HorizontalMargin side, float cssMargin,
1973 AttributeSet a, boolean isLeftToRight) {
1974 float margin = cssMargin;
1975 float orientationMargin = cssMargin;
1976 Object cssMarginValue = null;
1977 switch (side) {
1978 case RIGHT:
1979 {
1980 orientationMargin = (isLeftToRight) ?
1981 getLength(CSS.Attribute.MARGIN_RIGHT_LTR, a) :
1982 getLength(CSS.Attribute.MARGIN_RIGHT_RTL, a);
1983 cssMarginValue = a.getAttribute(CSS.Attribute.MARGIN_RIGHT);
1984 }
1985 break;
1986 case LEFT :
1987 {
1988 orientationMargin = (isLeftToRight) ?
1989 getLength(CSS.Attribute.MARGIN_LEFT_LTR, a) :
1990 getLength(CSS.Attribute.MARGIN_LEFT_RTL, a);
1991 cssMarginValue = a.getAttribute(CSS.Attribute.MARGIN_LEFT);
1992 }
1993 break;
1994 }
1995
1996 if (cssMarginValue == null
1997 && orientationMargin != Integer.MIN_VALUE) {
1998 margin = orientationMargin;
1999 }
2000 return margin;
2001 }
2002
2003 float topMargin;
2004 float bottomMargin;
2005 float leftMargin;
2006 float rightMargin;
2007 // Bitmask, used to indicate what margins are relative:
2008 // bit 0 for top, 1 for bottom, 2 for left and 3 for right.
2009 short marginFlags;
2010 Border border;
2011 Insets binsets;
2012 CSS css;
2013 StyleSheet ss;
2014 Color bg;
2015 BackgroundImagePainter bgPainter;
2016 }
2017
2018 /**
2019 * Class to carry out some of the duties of CSS list
2020 * formatting. Implementations of this
2021 * class enable views to present the CSS formatting
2022 * while not knowing anything about how the CSS values
2023 * are being cached.
2024 */
2025 public static class ListPainter implements Serializable {
2026
2027 ListPainter(AttributeSet attr, StyleSheet ss) {
2028 this.ss = ss;
2029 /* Get the image to use as a list bullet */
2030 String imgstr = (String)attr.getAttribute(CSS.Attribute.
2031 LIST_STYLE_IMAGE);
2032 type = null;
2033 if (imgstr != null && !imgstr.equals("none")) {
2034 String tmpstr = null;
2035 try {
2036 StringTokenizer st = new StringTokenizer(imgstr, "()");
2037 if (st.hasMoreTokens())
2038 tmpstr = st.nextToken();
2039 if (st.hasMoreTokens())
2040 tmpstr = st.nextToken();
2041 URL u = new URL(tmpstr);
2042 img = new ImageIcon(u);
2043 } catch (MalformedURLException e) {
2044 if (tmpstr != null && ss != null && ss.getBase() != null) {
2045 try {
2046 URL u = new URL(ss.getBase(), tmpstr);
2047 img = new ImageIcon(u);
2048 } catch (MalformedURLException murle) {
2049 img = null;
2050 }
2051 }
2052 else {
2053 img = null;
2054 }
2055 }
2056 }
2057
2058 /* Get the type of bullet to use in the list */
2059 if (img == null) {
2060 type = (CSS.Value)attr.getAttribute(CSS.Attribute.
2061 LIST_STYLE_TYPE);
2062 }
2063 start = 1;
2064
2065 paintRect = new Rectangle();
2066 }
2067
2068 /**
2069 * Returns a string that represents the value
2070 * of the HTML.Attribute.TYPE attribute.
2071 * If this attributes is not defined, then
2072 * then the type defaults to "disc" unless
2073 * the tag is on Ordered list. In the case
2074 * of the latter, the default type is "decimal".
2075 */
2076 private CSS.Value getChildType(View childView) {
2077 CSS.Value childtype = (CSS.Value)childView.getAttributes().
2078 getAttribute(CSS.Attribute.LIST_STYLE_TYPE);
2079
2080 if (childtype == null) {
2081 if (type == null) {
2082 // Parent view.
2083 View v = childView.getParent();
2084 HTMLDocument doc = (HTMLDocument)v.getDocument();
2085 if (doc.matchNameAttribute(v.getElement().getAttributes(),
2086 HTML.Tag.OL)) {
2087 childtype = CSS.Value.DECIMAL;
2088 } else {
2089 childtype = CSS.Value.DISC;
2090 }
2091 } else {
2092 childtype = type;
2093 }
2094 }
2095 return childtype;
2096 }
2097
2098 /**
2099 * Obtains the starting index from <code>parent</code>.
2100 */
2101 private void getStart(View parent) {
2102 checkedForStart = true;
2103 Element element = parent.getElement();
2104 if (element != null) {
2105 AttributeSet attr = element.getAttributes();
2106 Object startValue;
2107 if (attr != null && attr.isDefined(HTML.Attribute.START) &&
2108 (startValue = attr.getAttribute
2109 (HTML.Attribute.START)) != null &&
2110 (startValue instanceof String)) {
2111
2112 try {
2113 start = Integer.parseInt((String)startValue);
2114 }
2115 catch (NumberFormatException nfe) {}
2116 }
2117 }
2118 }
2119
2120 /**
2121 * Returns an integer that should be used to render the child at
2122 * <code>childIndex</code> with. The retValue will usually be
2123 * <code>childIndex</code> + 1, unless <code>parentView</code>
2124 * has some Views that do not represent LI's, or one of the views
2125 * has a HTML.Attribute.START specified.
2126 */
2127 private int getRenderIndex(View parentView, int childIndex) {
2128 if (!checkedForStart) {
2129 getStart(parentView);
2130 }
2131 int retIndex = childIndex;
2132 for (int counter = childIndex; counter >= 0; counter--) {
2133 AttributeSet as = parentView.getElement().getElement(counter).
2134 getAttributes();
2135 if (as.getAttribute(StyleConstants.NameAttribute) !=
2136 HTML.Tag.LI) {
2137 retIndex--;
2138 } else if (as.isDefined(HTML.Attribute.VALUE)) {
2139 Object value = as.getAttribute(HTML.Attribute.VALUE);
2140 if (value != null &&
2141 (value instanceof String)) {
2142 try {
2143 int iValue = Integer.parseInt((String)value);
2144 return retIndex - counter + iValue;
2145 }
2146 catch (NumberFormatException nfe) {}
2147 }
2148 }
2149 }
2150 return retIndex + start;
2151 }
2152
2153 /**
2154 * Paints the CSS list decoration according to the
2155 * attributes given.
2156 *
2157 * @param g the rendering surface.
2158 * @param x the x coordinate of the list item allocation
2159 * @param y the y coordinate of the list item allocation
2160 * @param w the width of the list item allocation
2161 * @param h the height of the list item allocation
2162 * @param v the allocated area to paint into.
2163 * @param item which list item is being painted. This
2164 * is a number greater than or equal to 0.
2165 */
2166 public void paint(Graphics g, float x, float y, float w, float h, View v, int item) {
2167 View cv = v.getView(item);
2168 Object name = cv.getElement().getAttributes().getAttribute
2169 (StyleConstants.NameAttribute);
2170 // Only draw something if the View is a list item. This won't
2171 // be the case for comments.
2172 if (!(name instanceof HTML.Tag) ||
2173 name != HTML.Tag.LI) {
2174 return;
2175 }
2176 // deside on what side draw bullets, etc.
2177 isLeftToRight =
2178 cv.getContainer().getComponentOrientation().isLeftToRight();
2179
2180 // How the list indicator is aligned is not specified, it is
2181 // left up to the UA. IE and NS differ on this behavior.
2182 // This is closer to NS where we align to the first line of text.
2183 // If the child is not text we draw the indicator at the
2184 // origin (0).
2185 float align = 0;
2186 if (cv.getViewCount() > 0) {
2187 View pView = cv.getView(0);
2188 Object cName = pView.getElement().getAttributes().
2189 getAttribute(StyleConstants.NameAttribute);
2190 if ((cName == HTML.Tag.P || cName == HTML.Tag.IMPLIED) &&
2191 pView.getViewCount() > 0) {
2192 paintRect.setBounds((int)x, (int)y, (int)w, (int)h);
2193 Shape shape = cv.getChildAllocation(0, paintRect);
2194 if (shape != null && (shape = pView.getView(0).
2195 getChildAllocation(0, shape)) != null) {
2196 Rectangle rect = (shape instanceof Rectangle) ?
2197 (Rectangle)shape : shape.getBounds();
2198
2199 align = pView.getView(0).getAlignment(View.Y_AXIS);
2200 y = rect.y;
2201 h = rect.height;
2202 }
2203 }
2204 }
2205
2206 // set the color of a decoration
2207 if (ss != null) {
2208 g.setColor(ss.getForeground(cv.getAttributes()));
2209 } else {
2210 g.setColor(Color.black);
2211 }
2212
2213 if (img != null) {
2214 drawIcon(g, (int) x, (int) y, (int) w, (int) h, align,
2215 v.getContainer());
2216 return;
2217 }
2218 CSS.Value childtype = getChildType(cv);
2219 Font font = ((StyledDocument)cv.getDocument()).
2220 getFont(cv.getAttributes());
2221 if (font != null) {
2222 g.setFont(font);
2223 }
2224 if (childtype == CSS.Value.SQUARE || childtype == CSS.Value.CIRCLE
2225 || childtype == CSS.Value.DISC) {
2226 drawShape(g, childtype, (int) x, (int) y,
2227 (int) w, (int) h, align);
2228 } else if (childtype == CSS.Value.DECIMAL) {
2229 drawLetter(g, '1', (int) x, (int) y, (int) w, (int) h, align,
2230 getRenderIndex(v, item));
2231 } else if (childtype == CSS.Value.LOWER_ALPHA) {
2232 drawLetter(g, 'a', (int) x, (int) y, (int) w, (int) h, align,
2233 getRenderIndex(v, item));
2234 } else if (childtype == CSS.Value.UPPER_ALPHA) {
2235 drawLetter(g, 'A', (int) x, (int) y, (int) w, (int) h, align,
2236 getRenderIndex(v, item));
2237 } else if (childtype == CSS.Value.LOWER_ROMAN) {
2238 drawLetter(g, 'i', (int) x, (int) y, (int) w, (int) h, align,
2239 getRenderIndex(v, item));
2240 } else if (childtype == CSS.Value.UPPER_ROMAN) {
2241 drawLetter(g, 'I', (int) x, (int) y, (int) w, (int) h, align,
2242 getRenderIndex(v, item));
2243 }
2244 }
2245
2246 /**
2247 * Draws the bullet icon specified by the list-style-image argument.
2248 *
2249 * @param g the graphics context
2250 * @param ax x coordinate to place the bullet
2251 * @param ay y coordinate to place the bullet
2252 * @param aw width of the container the bullet is placed in
2253 * @param ah height of the container the bullet is placed in
2254 * @param align preferred alignment factor for the child view
2255 */
2256 void drawIcon(Graphics g, int ax, int ay, int aw, int ah,
2257 float align, Component c) {
2258 // Align to bottom of icon.
2259 int gap = isLeftToRight ? - (img.getIconWidth() + bulletgap) :
2260 (aw + bulletgap);
2261 int x = ax + gap;
2262 int y = Math.max(ay, ay + (int)(align * ah) -img.getIconHeight());
2263
2264 img.paintIcon(c, g, x, y);
2265 }
2266
2267 /**
2268 * Draws the graphical bullet item specified by the type argument.
2269 *
2270 * @param g the graphics context
2271 * @param type type of bullet to draw (circle, square, disc)
2272 * @param ax x coordinate to place the bullet
2273 * @param ay y coordinate to place the bullet
2274 * @param aw width of the container the bullet is placed in
2275 * @param ah height of the container the bullet is placed in
2276 * @param align preferred alignment factor for the child view
2277 */
2278 void drawShape(Graphics g, CSS.Value type, int ax, int ay, int aw,
2279 int ah, float align) {
2280 // Align to bottom of shape.
2281 int gap = isLeftToRight ? - (bulletgap + 8) : (aw + bulletgap);
2282 int x = ax + gap;
2283 int y = Math.max(ay, ay + (int)(align * ah) - 8);
2284
2285 if (type == CSS.Value.SQUARE) {
2286 g.drawRect(x, y, 8, 8);
2287 } else if (type == CSS.Value.CIRCLE) {
2288 g.drawOval(x, y, 8, 8);
2289 } else {
2290 g.fillOval(x, y, 8, 8);
2291 }
2292 }
2293
2294 /**
2295 * Draws the letter or number for an ordered list.
2296 *
2297 * @param g the graphics context
2298 * @param letter type of ordered list to draw
2299 * @param ax x coordinate to place the bullet
2300 * @param ay y coordinate to place the bullet
2301 * @param aw width of the container the bullet is placed in
2302 * @param ah height of the container the bullet is placed in
2303 * @param index position of the list item in the list
2304 */
2305 void drawLetter(Graphics g, char letter, int ax, int ay, int aw,
2306 int ah, float align, int index) {
2307 String str = formatItemNum(index, letter);
2308 str = isLeftToRight ? str + "." : "." + str;
2309 FontMetrics fm = SwingUtilities2.getFontMetrics(null, g);
2310 int stringwidth = SwingUtilities2.stringWidth(null, fm, str);
2311 int gap = isLeftToRight ? - (stringwidth + bulletgap) :
2312 (aw + bulletgap);
2313 int x = ax + gap;
2314 int y = Math.max(ay + fm.getAscent(), ay + (int)(ah * align));
2315 SwingUtilities2.drawString(null, g, str, x, y);
2316 }
2317
2318 /**
2319 * Converts the item number into the ordered list number
2320 * (i.e. 1 2 3, i ii iii, a b c, etc.
2321 *
2322 * @param itemNum number to format
2323 * @param type type of ordered list
2324 */
2325 String formatItemNum(int itemNum, char type) {
2326 String numStyle = "1";
2327
2328 boolean uppercase = false;
2329
2330 String formattedNum;
2331
2332 switch (type) {
2333 case '1':
2334 default:
2335 formattedNum = String.valueOf(itemNum);
2336 break;
2337
2338 case 'A':
2339 uppercase = true;
2340 // fall through
2341 case 'a':
2342 formattedNum = formatAlphaNumerals(itemNum);
2343 break;
2344
2345 case 'I':
2346 uppercase = true;
2347 // fall through
2348 case 'i':
2349 formattedNum = formatRomanNumerals(itemNum);
2350 }
2351
2352 if (uppercase) {
2353 formattedNum = formattedNum.toUpperCase();
2354 }
2355
2356 return formattedNum;
2357 }
2358
2359 /**
2360 * Converts the item number into an alphabetic character
2361 *
2362 * @param itemNum number to format
2363 */
2364 String formatAlphaNumerals(int itemNum) {
2365 String result = "";
2366
2367 if (itemNum > 26) {
2368 result = formatAlphaNumerals(itemNum / 26) +
2369 formatAlphaNumerals(itemNum % 26);
2370 } else {
2371 // -1 because item is 1 based.
2372 result = String.valueOf((char)('a' + itemNum - 1));
2373 }
2374
2375 return result;
2376 }
2377
2378 /* list of roman numerals */
2379 static final char romanChars[][] = {
2380 {'i', 'v'},
2381 {'x', 'l' },
2382 {'c', 'd' },
2383 {'m', '?' },
2384 };
2385
2386 /**
2387 * Converts the item number into a roman numeral
2388 *
2389 * @param num number to format
2390 */
2391 String formatRomanNumerals(int num) {
2392 return formatRomanNumerals(0, num);
2393 }
2394
2395 /**
2396 * Converts the item number into a roman numeral
2397 *
2398 * @param num number to format
2399 */
2400 String formatRomanNumerals(int level, int num) {
2401 if (num < 10) {
2402 return formatRomanDigit(level, num);
2403 } else {
2404 return formatRomanNumerals(level + 1, num / 10) +
2405 formatRomanDigit(level, num % 10);
2406 }
2407 }
2408
2409
2410 /**
2411 * Converts the item number into a roman numeral
2412 *
2413 * @param level position
2414 * @param num digit to format
2415 */
2416 String formatRomanDigit(int level, int digit) {
2417 String result = "";
2418 if (digit == 9) {
2419 result = result + romanChars[level][0];
2420 result = result + romanChars[level + 1][0];
2421 return result;
2422 } else if (digit == 4) {
2423 result = result + romanChars[level][0];
2424 result = result + romanChars[level][1];
2425 return result;
2426 } else if (digit >= 5) {
2427 result = result + romanChars[level][1];
2428 digit -= 5;
2429 }
2430
2431 for (int i = 0; i < digit; i++) {
2432 result = result + romanChars[level][0];
2433 }
2434
2435 return result;
2436 }
2437
2438 private Rectangle paintRect;
2439 private boolean checkedForStart;
2440 private int start;
2441 private CSS.Value type;
2442 URL imageurl;
2443 private StyleSheet ss = null;
2444 Icon img = null;
2445 private int bulletgap = 5;
2446 private boolean isLeftToRight;
2447 }
2448
2449
2450 /**
2451 * Paints the background image.
2452 */
2453 static class BackgroundImagePainter implements Serializable {
2454 ImageIcon backgroundImage;
2455 float hPosition;
2456 float vPosition;
2457 // bit mask: 0 for repeat x, 1 for repeat y, 2 for horiz relative,
2458 // 3 for vert relative
2459 short flags;
2460 // These are used when painting, updatePaintCoordinates updates them.
2461 private int paintX;
2462 private int paintY;
2463 private int paintMaxX;
2464 private int paintMaxY;
2465
2466 BackgroundImagePainter(AttributeSet a, CSS css, StyleSheet ss) {
2467 backgroundImage = ss.getBackgroundImage(a);
2468 // Determine the position.
2469 CSS.BackgroundPosition pos = (CSS.BackgroundPosition)a.getAttribute
2470 (CSS.Attribute.BACKGROUND_POSITION);
2471 if (pos != null) {
2472 hPosition = pos.getHorizontalPosition();
2473 vPosition = pos.getVerticalPosition();
2474 if (pos.isHorizontalPositionRelativeToSize()) {
2475 flags |= 4;
2476 }
2477 else if (pos.isHorizontalPositionRelativeToSize()) {
2478 hPosition *= css.getFontSize(a, 12, ss);
2479 }
2480 if (pos.isVerticalPositionRelativeToSize()) {
2481 flags |= 8;
2482 }
2483 else if (pos.isVerticalPositionRelativeToFontSize()) {
2484 vPosition *= css.getFontSize(a, 12, ss);
2485 }
2486 }
2487 // Determine any repeating values.
2488 CSS.Value repeats = (CSS.Value)a.getAttribute(CSS.Attribute.
2489 BACKGROUND_REPEAT);
2490 if (repeats == null || repeats == CSS.Value.BACKGROUND_REPEAT) {
2491 flags |= 3;
2492 }
2493 else if (repeats == CSS.Value.BACKGROUND_REPEAT_X) {
2494 flags |= 1;
2495 }
2496 else if (repeats == CSS.Value.BACKGROUND_REPEAT_Y) {
2497 flags |= 2;
2498 }
2499 }
2500
2501 void paint(Graphics g, float x, float y, float w, float h, View v) {
2502 Rectangle clip = g.getClipRect();
2503 if (clip != null) {
2504 // Constrain the clip so that images don't draw outside the
2505 // legal bounds.
2506 g.clipRect((int)x, (int)y, (int)w, (int)h);
2507 }
2508 if ((flags & 3) == 0) {
2509 // no repeating
2510 int width = backgroundImage.getIconWidth();
2511 int height = backgroundImage.getIconWidth();
2512 if ((flags & 4) == 4) {
2513 paintX = (int)(x + w * hPosition -
2514 (float)width * hPosition);
2515 }
2516 else {
2517 paintX = (int)x + (int)hPosition;
2518 }
2519 if ((flags & 8) == 8) {
2520 paintY = (int)(y + h * vPosition -
2521 (float)height * vPosition);
2522 }
2523 else {
2524 paintY = (int)y + (int)vPosition;
2525 }
2526 if (clip == null ||
2527 !((paintX + width <= clip.x) ||
2528 (paintY + height <= clip.y) ||
2529 (paintX >= clip.x + clip.width) ||
2530 (paintY >= clip.y + clip.height))) {
2531 backgroundImage.paintIcon(null, g, paintX, paintY);
2532 }
2533 }
2534 else {
2535 int width = backgroundImage.getIconWidth();
2536 int height = backgroundImage.getIconHeight();
2537 if (width > 0 && height > 0) {
2538 paintX = (int)x;
2539 paintY = (int)y;
2540 paintMaxX = (int)(x + w);
2541 paintMaxY = (int)(y + h);
2542 if (updatePaintCoordinates(clip, width, height)) {
2543 while (paintX < paintMaxX) {
2544 int ySpot = paintY;
2545 while (ySpot < paintMaxY) {
2546 backgroundImage.paintIcon(null, g, paintX,
2547 ySpot);
2548 ySpot += height;
2549 }
2550 paintX += width;
2551 }
2552 }
2553 }
2554 }
2555 if (clip != null) {
2556 // Reset clip.
2557 g.setClip(clip.x, clip.y, clip.width, clip.height);
2558 }
2559 }
2560
2561 private boolean updatePaintCoordinates
2562 (Rectangle clip, int width, int height){
2563 if ((flags & 3) == 1) {
2564 paintMaxY = paintY + 1;
2565 }
2566 else if ((flags & 3) == 2) {
2567 paintMaxX = paintX + 1;
2568 }
2569 if (clip != null) {
2570 if ((flags & 3) == 1 && ((paintY + height <= clip.y) ||
2571 (paintY > clip.y + clip.height))) {
2572 // not visible.
2573 return false;
2574 }
2575 if ((flags & 3) == 2 && ((paintX + width <= clip.x) ||
2576 (paintX > clip.x + clip.width))) {
2577 // not visible.
2578 return false;
2579 }
2580 if ((flags & 1) == 1) {
2581 if ((clip.x + clip.width) < paintMaxX) {
2582 if ((clip.x + clip.width - paintX) % width == 0) {
2583 paintMaxX = clip.x + clip.width;
2584 }
2585 else {
2586 paintMaxX = ((clip.x + clip.width - paintX) /
2587 width + 1) * width + paintX;
2588 }
2589 }
2590 if (clip.x > paintX) {
2591 paintX = (clip.x - paintX) / width * width + paintX;
2592 }
2593 }
2594 if ((flags & 2) == 2) {
2595 if ((clip.y + clip.height) < paintMaxY) {
2596 if ((clip.y + clip.height - paintY) % height == 0) {
2597 paintMaxY = clip.y + clip.height;
2598 }
2599 else {
2600 paintMaxY = ((clip.y + clip.height - paintY) /
2601 height + 1) * height + paintY;
2602 }
2603 }
2604 if (clip.y > paintY) {
2605 paintY = (clip.y - paintY) / height * height + paintY;
2606 }
2607 }
2608 }
2609 // Valid
2610 return true;
2611 }
2612 }
2613
2614
2615 /**
2616 * A subclass of MuxingAttributeSet that translates between
2617 * CSS and HTML and StyleConstants. The AttributeSets used are
2618 * the CSS rules that match the Views Elements.
2619 */
2620 class ViewAttributeSet extends MuxingAttributeSet {
2621 ViewAttributeSet(View v) {
2622 host = v;
2623
2624 // PENDING(prinz) fix this up to be a more realistic
2625 // implementation.
2626 Document doc = v.getDocument();
2627 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
2628 Vector muxList = sb.getVector();
2629 try {
2630 if (doc instanceof HTMLDocument) {
2631 StyleSheet styles = StyleSheet.this;
2632 Element elem = v.getElement();
2633 AttributeSet a = elem.getAttributes();
2634 AttributeSet htmlAttr = styles.translateHTMLToCSS(a);
2635
2636 if (htmlAttr.getAttributeCount() != 0) {
2637 muxList.addElement(htmlAttr);
2638 }
2639 if (elem.isLeaf()) {
2640 Enumeration keys = a.getAttributeNames();
2641 while (keys.hasMoreElements()) {
2642 Object key = keys.nextElement();
2643 if (key instanceof HTML.Tag) {
2644 if ((HTML.Tag)key == HTML.Tag.A) {
2645 Object o = a.getAttribute((HTML.Tag)key);
2646 /**
2647 In the case of an A tag, the css rules
2648 apply only for tags that have their
2649 href attribute defined and not for
2650 anchors that only have their name attributes
2651 defined, i.e anchors that function as
2652 destinations. Hence we do not add the
2653 attributes for that latter kind of
2654 anchors. When CSS2 support is added,
2655 it will be possible to specificity this
2656 kind of conditional behaviour in the
2657 stylesheet.
2658 **/
2659 if (o != null && o instanceof AttributeSet) {
2660 AttributeSet attr = (AttributeSet)o;
2661 if (attr.getAttribute(HTML.Attribute.HREF) == null) {
2662 continue;
2663 }
2664 }
2665 }
2666 AttributeSet cssRule = styles.getRule((HTML.Tag) key, elem);
2667 if (cssRule != null) {
2668 muxList.addElement(cssRule);
2669 }
2670 }
2671 }
2672 } else {
2673 HTML.Tag t = (HTML.Tag) a.getAttribute
2674 (StyleConstants.NameAttribute);
2675 AttributeSet cssRule = styles.getRule(t, elem);
2676 if (cssRule != null) {
2677 muxList.addElement(cssRule);
2678 }
2679 }
2680 }
2681 AttributeSet[] attrs = new AttributeSet[muxList.size()];
2682 muxList.copyInto(attrs);
2683 setAttributes(attrs);
2684 }
2685 finally {
2686 SearchBuffer.releaseSearchBuffer(sb);
2687 }
2688 }
2689
2690 // --- AttributeSet methods ----------------------------
2691
2692 /**
2693 * Checks whether a given attribute is defined.
2694 * This will convert the key over to CSS if the
2695 * key is a StyleConstants key that has a CSS
2696 * mapping.
2697 *
2698 * @param key the attribute key
2699 * @return true if the attribute is defined
2700 * @see AttributeSet#isDefined
2701 */
2702 public boolean isDefined(Object key) {
2703 if (key instanceof StyleConstants) {
2704 Object cssKey = css.styleConstantsKeyToCSSKey
2705 ((StyleConstants)key);
2706 if (cssKey != null) {
2707 key = cssKey;
2708 }
2709 }
2710 return super.isDefined(key);
2711 }
2712
2713 /**
2714 * Gets the value of an attribute. If the requested
2715 * attribute is a StyleConstants attribute that has
2716 * a CSS mapping, the request will be converted.
2717 *
2718 * @param key the attribute name
2719 * @return the attribute value
2720 * @see AttributeSet#getAttribute
2721 */
2722 public Object getAttribute(Object key) {
2723 if (key instanceof StyleConstants) {
2724 Object cssKey = css.styleConstantsKeyToCSSKey
2725 ((StyleConstants)key);
2726 if (cssKey != null) {
2727 Object value = doGetAttribute(cssKey);
2728 if (value instanceof CSS.CssValue) {
2729 return ((CSS.CssValue)value).toStyleConstants
2730 ((StyleConstants)key, host);
2731 }
2732 }
2733 }
2734 return doGetAttribute(key);
2735 }
2736
2737 Object doGetAttribute(Object key) {
2738 Object retValue = super.getAttribute(key);
2739 if (retValue != null) {
2740 return retValue;
2741 }
2742 // didn't find it... try parent if it's a css attribute
2743 // that is inherited.
2744 if (key instanceof CSS.Attribute) {
2745 CSS.Attribute css = (CSS.Attribute) key;
2746 if (css.isInherited()) {
2747 AttributeSet parent = getResolveParent();
2748 if (parent != null)
2749 return parent.getAttribute(key);
2750 }
2751 }
2752 return null;
2753 }
2754
2755 /**
2756 * If not overriden, the resolving parent defaults to
2757 * the parent element.
2758 *
2759 * @return the attributes from the parent
2760 * @see AttributeSet#getResolveParent
2761 */
2762 public AttributeSet getResolveParent() {
2763 if (host == null) {
2764 return null;
2765 }
2766 View parent = host.getParent();
2767 return (parent != null) ? parent.getAttributes() : null;
2768 }
2769
2770 /** View created for. */
2771 View host;
2772 }
2773
2774
2775 /**
2776 * A subclass of MuxingAttributeSet that implements Style. Currently
2777 * the MutableAttributeSet methods are unimplemented, that is they
2778 * do nothing.
2779 */
2780 // PENDING(sky): Decide what to do with this. Either make it
2781 // contain a SimpleAttributeSet that modify methods are delegated to,
2782 // or change getRule to return an AttributeSet and then don't make this
2783 // implement Style.
2784 static class ResolvedStyle extends MuxingAttributeSet implements
2785 Serializable, Style {
2786 ResolvedStyle(String name, AttributeSet[] attrs, int extendedIndex) {
2787 super(attrs);
2788 this.name = name;
2789 this.extendedIndex = extendedIndex;
2790 }
2791
2792 /**
2793 * Inserts a Style into the receiver so that the styles the
2794 * receiver represents are still ordered by specificity.
2795 * <code>style</code> will be added before any extended styles, that
2796 * is before extendedIndex.
2797 */
2798 synchronized void insertStyle(Style style, int specificity) {
2799 AttributeSet[] attrs = getAttributes();
2800 int maxCounter = attrs.length;
2801 int counter = 0;
2802 for (;counter < extendedIndex; counter++) {
2803 if (specificity > getSpecificity(((Style)attrs[counter]).
2804 getName())) {
2805 break;
2806 }
2807 }
2808 insertAttributeSetAt(style, counter);
2809 extendedIndex++;
2810 }
2811
2812 /**
2813 * Removes a previously added style. This will do nothing if
2814 * <code>style</code> is not referenced by the receiver.
2815 */
2816 synchronized void removeStyle(Style style) {
2817 AttributeSet[] attrs = getAttributes();
2818
2819 for (int counter = attrs.length - 1; counter >= 0; counter--) {
2820 if (attrs[counter] == style) {
2821 removeAttributeSetAt(counter);
2822 if (counter < extendedIndex) {
2823 extendedIndex--;
2824 }
2825 break;
2826 }
2827 }
2828 }
2829
2830 /**
2831 * Adds <code>s</code> as one of the Attributesets to look up
2832 * attributes in.
2833 */
2834 synchronized void insertExtendedStyleAt(Style attr, int index) {
2835 insertAttributeSetAt(attr, extendedIndex + index);
2836 }
2837
2838 /**
2839 * Adds <code>s</code> as one of the AttributeSets to look up
2840 * attributes in. It will be the AttributeSet last checked.
2841 */
2842 synchronized void addExtendedStyle(Style attr) {
2843 insertAttributeSetAt(attr, getAttributes().length);
2844 }
2845
2846 /**
2847 * Removes the style at <code>index</code> +
2848 * <code>extendedIndex</code>.
2849 */
2850 synchronized void removeExtendedStyleAt(int index) {
2851 removeAttributeSetAt(extendedIndex + index);
2852 }
2853
2854 /**
2855 * Returns true if the receiver matches <code>selector</code>, where
2856 * a match is defined by the CSS rule matching.
2857 * Each simple selector must be separated by a single space.
2858 */
2859 protected boolean matches(String selector) {
2860 int sLast = selector.length();
2861
2862 if (sLast == 0) {
2863 return false;
2864 }
2865 int thisLast = name.length();
2866 int sCurrent = selector.lastIndexOf(' ');
2867 int thisCurrent = name.lastIndexOf(' ');
2868 if (sCurrent >= 0) {
2869 sCurrent++;
2870 }
2871 if (thisCurrent >= 0) {
2872 thisCurrent++;
2873 }
2874 if (!matches(selector, sCurrent, sLast, thisCurrent, thisLast)) {
2875 return false;
2876 }
2877 while (sCurrent != -1) {
2878 sLast = sCurrent - 1;
2879 sCurrent = selector.lastIndexOf(' ', sLast - 1);
2880 if (sCurrent >= 0) {
2881 sCurrent++;
2882 }
2883 boolean match = false;
2884 while (!match && thisCurrent != -1) {
2885 thisLast = thisCurrent - 1;
2886 thisCurrent = name.lastIndexOf(' ', thisLast - 1);
2887 if (thisCurrent >= 0) {
2888 thisCurrent++;
2889 }
2890 match = matches(selector, sCurrent, sLast, thisCurrent,
2891 thisLast);
2892 }
2893 if (!match) {
2894 return false;
2895 }
2896 }
2897 return true;
2898 }
2899
2900 /**
2901 * Returns true if the substring of the receiver, in the range
2902 * thisCurrent, thisLast matches the substring of selector in
2903 * the ranme sCurrent to sLast based on CSS selector matching.
2904 */
2905 boolean matches(String selector, int sCurrent, int sLast,
2906 int thisCurrent, int thisLast) {
2907 sCurrent = Math.max(sCurrent, 0);
2908 thisCurrent = Math.max(thisCurrent, 0);
2909 int thisDotIndex = boundedIndexOf(name, '.', thisCurrent,
2910 thisLast);
2911 int thisPoundIndex = boundedIndexOf(name, '#', thisCurrent,
2912 thisLast);
2913 int sDotIndex = boundedIndexOf(selector, '.', sCurrent, sLast);
2914 int sPoundIndex = boundedIndexOf(selector, '#', sCurrent, sLast);
2915 if (sDotIndex != -1) {
2916 // Selector has a '.', which indicates name must match it,
2917 // or if the '.' starts the selector than name must have
2918 // the same class (doesn't matter what element name).
2919 if (thisDotIndex == -1) {
2920 return false;
2921 }
2922 if (sCurrent == sDotIndex) {
2923 if ((thisLast - thisDotIndex) != (sLast - sDotIndex) ||
2924 !selector.regionMatches(sCurrent, name, thisDotIndex,
2925 (thisLast - thisDotIndex))) {
2926 return false;
2927 }
2928 }
2929 else {
2930 // Has to fully match.
2931 if ((sLast - sCurrent) != (thisLast - thisCurrent) ||
2932 !selector.regionMatches(sCurrent, name, thisCurrent,
2933 (thisLast - thisCurrent))) {
2934 return false;
2935 }
2936 }
2937 return true;
2938 }
2939 if (sPoundIndex != -1) {
2940 // Selector has a '#', which indicates name must match it,
2941 // or if the '#' starts the selector than name must have
2942 // the same id (doesn't matter what element name).
2943 if (thisPoundIndex == -1) {
2944 return false;
2945 }
2946 if (sCurrent == sPoundIndex) {
2947 if ((thisLast - thisPoundIndex) !=(sLast - sPoundIndex) ||
2948 !selector.regionMatches(sCurrent, name, thisPoundIndex,
2949 (thisLast - thisPoundIndex))) {
2950 return false;
2951 }
2952 }
2953 else {
2954 // Has to fully match.
2955 if ((sLast - sCurrent) != (thisLast - thisCurrent) ||
2956 !selector.regionMatches(sCurrent, name, thisCurrent,
2957 (thisLast - thisCurrent))) {
2958 return false;
2959 }
2960 }
2961 return true;
2962 }
2963 if (thisDotIndex != -1) {
2964 // Reciever references a class, just check element name.
2965 return (((thisDotIndex - thisCurrent) == (sLast - sCurrent)) &&
2966 selector.regionMatches(sCurrent, name, thisCurrent,
2967 thisDotIndex - thisCurrent));
2968 }
2969 if (thisPoundIndex != -1) {
2970 // Reciever references an id, just check element name.
2971 return (((thisPoundIndex - thisCurrent) ==(sLast - sCurrent))&&
2972 selector.regionMatches(sCurrent, name, thisCurrent,
2973 thisPoundIndex - thisCurrent));
2974 }
2975 // Fail through, no classes or ides, just check string.
2976 return (((thisLast - thisCurrent) == (sLast - sCurrent)) &&
2977 selector.regionMatches(sCurrent, name, thisCurrent,
2978 thisLast - thisCurrent));
2979 }
2980
2981 /**
2982 * Similiar to String.indexOf, but allows an upper bound
2983 * (this is slower in that it will still check string starting at
2984 * start.
2985 */
2986 int boundedIndexOf(String string, char search, int start,
2987 int end) {
2988 int retValue = string.indexOf(search, start);
2989 if (retValue >= end) {
2990 return -1;
2991 }
2992 return retValue;
2993 }
2994
2995 public void addAttribute(Object name, Object value) {}
2996 public void addAttributes(AttributeSet attributes) {}
2997 public void removeAttribute(Object name) {}
2998 public void removeAttributes(Enumeration<?> names) {}
2999 public void removeAttributes(AttributeSet attributes) {}
3000 public void setResolveParent(AttributeSet parent) {}
3001 public String getName() {return name;}
3002 public void addChangeListener(ChangeListener l) {}
3003 public void removeChangeListener(ChangeListener l) {}
3004 public ChangeListener[] getChangeListeners() {
3005 return new ChangeListener[0];
3006 }
3007
3008 /** The name of the Style, which is the selector.
3009 * This will NEVER change!
3010 */
3011 String name;
3012 /** Start index of styles coming from other StyleSheets. */
3013 private int extendedIndex;
3014 }
3015
3016
3017 /**
3018 * SelectorMapping contains a specifitiy, as an integer, and an associated
3019 * Style. It can also reference children <code>SelectorMapping</code>s,
3020 * so that it behaves like a tree.
3021 * <p>
3022 * This is not thread safe, it is assumed the caller will take the
3023 * necessary precations if this is to be used in a threaded environment.
3024 */
3025 static class SelectorMapping implements Serializable {
3026 public SelectorMapping(int specificity) {
3027 this.specificity = specificity;
3028 }
3029
3030 /**
3031 * Returns the specificity this mapping represents.
3032 */
3033 public int getSpecificity() {
3034 return specificity;
3035 }
3036
3037 /**
3038 * Sets the Style associated with this mapping.
3039 */
3040 public void setStyle(Style style) {
3041 this.style = style;
3042 }
3043
3044 /**
3045 * Returns the Style associated with this mapping.
3046 */
3047 public Style getStyle() {
3048 return style;
3049 }
3050
3051 /**
3052 * Returns the child mapping identified by the simple selector
3053 * <code>selector</code>. If a child mapping does not exist for
3054 *<code>selector</code>, and <code>create</code> is true, a new
3055 * one will be created.
3056 */
3057 public SelectorMapping getChildSelectorMapping(String selector,
3058 boolean create) {
3059 SelectorMapping retValue = null;
3060
3061 if (children != null) {
3062 retValue = (SelectorMapping)children.get(selector);
3063 }
3064 else if (create) {
3065 children = new HashMap(7);
3066 }
3067 if (retValue == null && create) {
3068 int specificity = getChildSpecificity(selector);
3069
3070 retValue = createChildSelectorMapping(specificity);
3071 children.put(selector, retValue);
3072 }
3073 return retValue;
3074 }
3075
3076 /**
3077 * Creates a child <code>SelectorMapping</code> with the specified
3078 * <code>specificity</code>.
3079 */
3080 protected SelectorMapping createChildSelectorMapping(int specificity) {
3081 return new SelectorMapping(specificity);
3082 }
3083
3084 /**
3085 * Returns the specificity for the child selector
3086 * <code>selector</code>.
3087 */
3088 protected int getChildSpecificity(String selector) {
3089 // class (.) 100
3090 // id (#) 10000
3091 char firstChar = selector.charAt(0);
3092 int specificity = getSpecificity();
3093
3094 if (firstChar == '.') {
3095 specificity += 100;
3096 }
3097 else if (firstChar == '#') {
3098 specificity += 10000;
3099 }
3100 else {
3101 specificity += 1;
3102 if (selector.indexOf('.') != -1) {
3103 specificity += 100;
3104 }
3105 if (selector.indexOf('#') != -1) {
3106 specificity += 10000;
3107 }
3108 }
3109 return specificity;
3110 }
3111
3112 /**
3113 * The specificity for this selector.
3114 */
3115 private int specificity;
3116 /**
3117 * Style for this selector.
3118 */
3119 private Style style;
3120 /**
3121 * Any sub selectors. Key will be String, and value will be
3122 * another SelectorMapping.
3123 */
3124 private HashMap children;
3125 }
3126
3127
3128 // ---- Variables ---------------------------------------------
3129
3130 final static int DEFAULT_FONT_SIZE = 3;
3131
3132 private CSS css;
3133
3134 /**
3135 * An inverted graph of the selectors.
3136 */
3137 private SelectorMapping selectorMapping;
3138
3139 /** Maps from selector (as a string) to Style that includes all
3140 * relevant styles. */
3141 private Hashtable resolvedStyles;
3142
3143 /** Vector of StyleSheets that the rules are to reference.
3144 */
3145 private Vector linkedStyleSheets;
3146
3147 /** Where the style sheet was found. Used for relative imports. */
3148 private URL base;
3149
3150
3151 /**
3152 * Default parser for CSS specifications that get loaded into
3153 * the StyleSheet.<p>
3154 * This class is NOT thread safe, do not ask it to parse while it is
3155 * in the middle of parsing.
3156 */
3157 class CssParser implements CSSParser.CSSParserCallback {
3158
3159 /**
3160 * Parses the passed in CSS declaration into an AttributeSet.
3161 */
3162 public AttributeSet parseDeclaration(String string) {
3163 try {
3164 return parseDeclaration(new StringReader(string));
3165 } catch (IOException ioe) {}
3166 return null;
3167 }
3168
3169 /**
3170 * Parses the passed in CSS declaration into an AttributeSet.
3171 */
3172 public AttributeSet parseDeclaration(Reader r) throws IOException {
3173 parse(base, r, true, false);
3174 return declaration.copyAttributes();
3175 }
3176
3177 /**
3178 * Parse the given CSS stream
3179 */
3180 public void parse(URL base, Reader r, boolean parseDeclaration,
3181 boolean isLink) throws IOException {
3182 this.base = base;
3183 this.isLink = isLink;
3184 this.parsingDeclaration = parseDeclaration;
3185 declaration.removeAttributes(declaration);
3186 selectorTokens.removeAllElements();
3187 selectors.removeAllElements();
3188 propertyName = null;
3189 parser.parse(r, this, parseDeclaration);
3190 }
3191
3192 //
3193 // CSSParserCallback methods, public to implement the interface.
3194 //
3195
3196 /**
3197 * Invoked when a valid @import is encountered, will call
3198 * <code>importStyleSheet</code> if a
3199 * <code>MalformedURLException</code> is not thrown in creating
3200 * the URL.
3201 */
3202 public void handleImport(String importString) {
3203 URL url = CSS.getURL(base, importString);
3204 if (url != null) {
3205 importStyleSheet(url);
3206 }
3207 }
3208
3209 /**
3210 * A selector has been encountered.
3211 */
3212 public void handleSelector(String selector) {
3213 //class and index selectors are case sensitive
3214 if (!(selector.startsWith(".")
3215 || selector.startsWith("#"))) {
3216 selector = selector.toLowerCase();
3217 }
3218 int length = selector.length();
3219
3220 if (selector.endsWith(",")) {
3221 if (length > 1) {
3222 selector = selector.substring(0, length - 1);
3223 selectorTokens.addElement(selector);
3224 }
3225 addSelector();
3226 }
3227 else if (length > 0) {
3228 selectorTokens.addElement(selector);
3229 }
3230 }
3231
3232 /**
3233 * Invoked when the start of a rule is encountered.
3234 */
3235 public void startRule() {
3236 if (selectorTokens.size() > 0) {
3237 addSelector();
3238 }
3239 propertyName = null;
3240 }
3241
3242 /**
3243 * Invoked when a property name is encountered.
3244 */
3245 public void handleProperty(String property) {
3246 propertyName = property;
3247 }
3248
3249 /**
3250 * Invoked when a property value is encountered.
3251 */
3252 public void handleValue(String value) {
3253 if (propertyName != null && value != null && value.length() > 0) {
3254 CSS.Attribute cssKey = CSS.getAttribute(propertyName);
3255 if (cssKey != null) {
3256 // There is currently no mechanism to determine real
3257 // base that style sheet was loaded from. For the time
3258 // being, this maps for LIST_STYLE_IMAGE, which appear
3259 // to be the only one that currently matters. A more
3260 // general mechanism is definately needed.
3261 if (cssKey == CSS.Attribute.LIST_STYLE_IMAGE) {
3262 if (value != null && !value.equals("none")) {
3263 URL url = CSS.getURL(base, value);
3264
3265 if (url != null) {
3266 value = url.toString();
3267 }
3268 }
3269 }
3270 addCSSAttribute(declaration, cssKey, value);
3271 }
3272 propertyName = null;
3273 }
3274 }
3275
3276 /**
3277 * Invoked when the end of a rule is encountered.
3278 */
3279 public void endRule() {
3280 int n = selectors.size();
3281 for (int i = 0; i < n; i++) {
3282 String[] selector = (String[]) selectors.elementAt(i);
3283 if (selector.length > 0) {
3284 StyleSheet.this.addRule(selector, declaration, isLink);
3285 }
3286 }
3287 declaration.removeAttributes(declaration);
3288 selectors.removeAllElements();
3289 }
3290
3291 private void addSelector() {
3292 String[] selector = new String[selectorTokens.size()];
3293 selectorTokens.copyInto(selector);
3294 selectors.addElement(selector);
3295 selectorTokens.removeAllElements();
3296 }
3297
3298
3299 Vector selectors = new Vector();
3300 Vector selectorTokens = new Vector();
3301 /** Name of the current property. */
3302 String propertyName;
3303 MutableAttributeSet declaration = new SimpleAttributeSet();
3304 /** True if parsing a declaration, that is the Reader will not
3305 * contain a selector. */
3306 boolean parsingDeclaration;
3307 /** True if the attributes are coming from a linked/imported style. */
3308 boolean isLink;
3309 /** Where the CSS stylesheet lives. */
3310 URL base;
3311 CSSParser parser = new CSSParser();
3312 }
3313
3314 void rebaseSizeMap(int base) {
3315 final int minimalFontSize = 4;
3316 sizeMap = new int[sizeMapDefault.length];
3317 for (int i = 0; i < sizeMapDefault.length; i++) {
3318 sizeMap[i] = Math.max(base * sizeMapDefault[i] /
3319 sizeMapDefault[CSS.baseFontSizeIndex],
3320 minimalFontSize);
3321 }
3322
3323 }
3324
3325 int[] getSizeMap() {
3326 return sizeMap;
3327 }
3328 boolean isW3CLengthUnits() {
3329 return w3cLengthUnits;
3330 }
3331
3332 /**
3333 * The HTML/CSS size model has seven slots
3334 * that one can assign sizes to.
3335 */
3336 static final int sizeMapDefault[] = { 8, 10, 12, 14, 18, 24, 36 };
3337
3338 private int sizeMap[] = sizeMapDefault;
3339 private boolean w3cLengthUnits = false;
3340}