KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > user > client > ui > UIObject


1 /*
2  * Copyright 2007 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */

16 package com.google.gwt.user.client.ui;
17
18 import com.google.gwt.user.client.DOM;
19 import com.google.gwt.user.client.Element;
20
21 /**
22  * The superclass for all user-interface objects. It simply wraps a DOM element,
23  * and cannot receive events. Most interesting user-interface classes derive
24  * from {@link com.google.gwt.user.client.ui.Widget}.
25  *
26  * <h3>Styling With CSS</h3>
27  * <p>
28  * All <code>UIObject</code> objects can be styled using CSS. Style names that
29  * are specified programmatically in Java source are implicitly associated with
30  * CSS style rules. In terms of HTML and CSS, a GWT style name is the element's
31  * CSS "class". By convention, GWT style names are of the form
32  * <code>[project]-[widget]</code>.
33  * </p>
34  *
35  * <p>
36  * For example, the {@link Button} widget has the style name
37  * <code>gwt-Button</code>, meaning that within the <code>Button</code>
38  * constructor, the following call occurs:
39  *
40  * <pre class="code">
41  * setStyleName("gwt-Button");</pre>
42  *
43  * A corresponding CSS style rule can then be written as follows:
44  *
45  * <pre class="code">
46  * // Example of how you might choose to style a Button widget
47  * .gwt-Button {
48  * background-color: yellow;
49  * color: black;
50  * font-size: 24pt;
51  * }</pre>
52  *
53  * Note the dot prefix in the CSS style rule. This syntax is called a <a
54  * HREF="http://www.w3.org/TR/REC-CSS2/selector.html#class-html">CSS class
55  * selector</a>.
56  * </p>
57  *
58  * <h3>Style Name Specifics</h3>
59  * <p>
60  * Every <code>UIObject</code> has a <i>primary style name</i> that
61  * identifies the key CSS style rule that should always be applied to it. Use
62  * {@link #setStyleName(String)} to specify an object's primary style name. In
63  * most cases, the primary style name is set in a widget's constructor and never
64  * changes again during execution. In the case that no primary style name is
65  * specified, it defaults to <code>gwt-nostyle</code>.
66  * </p>
67  *
68  * <p>
69  * More complex styling behavior can be achieved by manipulating an object's
70  * <i>secondary style names</i>. Secondary style names can be added and removed
71  * using {@link #addStyleName(String)} and {@link #removeStyleName(String)}.
72  * The purpose of secondary style names is to associate a variety of CSS style
73  * rules over time as an object progresses through different visual states.
74  * </p>
75  *
76  * <p>
77  * There is an important special formulation of secondary style names called
78  * <i>dependent style names</i>. A dependent style name is a secondary style
79  * name prefixed with the primary style name of the widget itself. See
80  * {@link #addStyleName(String)} for details.
81  * </p>
82  */

83 public abstract class UIObject {
84
85   private static final String JavaDoc EMPTY_STYLENAME_MSG = "Style names cannot be empty";
86
87   private static final String JavaDoc NULL_HANDLE_MSG = "Null widget handle. If you "
88       + "are creating a composite, ensure that initWidget() has been called.";
89
90   private static final String JavaDoc STYLE_EMPTY = "gwt-nostyle";
91
92   public static native boolean isVisible(Element elem) /*-{
93     return (elem.style.display != 'none');
94   }-*/
;
95
96   public static native void setVisible(Element elem, boolean visible) /*-{
97     elem.style.display = visible ? '' : 'none';
98   }-*/
;
99
100   /**
101    * Sets the object's primary style name and updates all dependent style names.
102    *
103    * @param elem the element whose style is to be reset
104    * @param style the new primary style name
105    * @see #setStyleName(Element, String, boolean)
106    */

107   protected static void resetStyleName(Element elem, String JavaDoc style) {
108     if (elem == null) {
109       throw new RuntimeException JavaDoc(NULL_HANDLE_MSG);
110     }
111
112     // Style names cannot contain leading or trailing whitespace, and cannot
113
// legally be empty.
114
style = style.trim();
115     if (style.length() == 0) {
116       throw new IllegalArgumentException JavaDoc(EMPTY_STYLENAME_MSG);
117     }
118
119     ensurePrimaryStyleName(elem);
120     updatePrimaryAndDependentStyleNames(elem, style);
121   }
122
123   /**
124    * This convenience method adds or removes a secondary style name to the
125    * primary style name for a given element. Set {@link #setStyleName(String)}
126    * for a description of how primary and secondary style names are used.
127    *
128    * @param elem the element whose style is to be modified
129    * @param style the secondary style name to be added or removed
130    * @param add <code>true</code> to add the given style, <code>false</code>
131    * to remove it
132    */

133   protected static void setStyleName(Element elem, String JavaDoc style, boolean add) {
134     if (elem == null) {
135       throw new RuntimeException JavaDoc(NULL_HANDLE_MSG);
136     }
137
138     style = style.trim();
139     if (style.length() == 0) {
140       throw new IllegalArgumentException JavaDoc(EMPTY_STYLENAME_MSG);
141     }
142
143     // Get the current style string.
144
String JavaDoc oldStyle = ensurePrimaryStyleName(elem);
145     int idx;
146     if (oldStyle == null) {
147       idx = -1;
148       oldStyle = "";
149     } else {
150       idx = oldStyle.indexOf(style);
151     }
152
153     // Calculate matching index.
154
while (idx != -1) {
155       if (idx == 0 || oldStyle.charAt(idx - 1) == ' ') {
156         int last = idx + style.length();
157         int lastPos = oldStyle.length();
158         if ((last == lastPos)
159             || ((last < lastPos) && (oldStyle.charAt(last) == ' '))) {
160           break;
161         }
162       }
163       idx = oldStyle.indexOf(style, idx + 1);
164     }
165
166     if (add) {
167       // Only add the style if it's not already present.
168
if (idx == -1) {
169         if (oldStyle.length() > 0) {
170           oldStyle += " ";
171         }
172         DOM.setElementProperty(elem, "className", oldStyle + style);
173       }
174     } else {
175       // Don't try to remove the style if it's not there.
176
if (idx != -1) {
177         if (idx == 0) {
178           // You can't remove the base (i.e. the first) style name.
179
throw new IllegalArgumentException JavaDoc("Cannot remove base style name");
180         }
181         String JavaDoc begin = oldStyle.substring(0, idx);
182         String JavaDoc end = oldStyle.substring(idx + style.length());
183         DOM.setElementProperty(elem, "className", begin + end);
184       }
185     }
186   }
187
188   /**
189    * Ensure that the root element has a primary style name. If one is not
190    * already present, then it is assigned the default style name.
191    *
192    * @return the primary style name
193    */

194   private static String JavaDoc ensurePrimaryStyleName(Element elem) {
195     String JavaDoc className = DOM.getElementProperty(elem, "className").trim();
196
197     if ("".equals(className)) {
198       className = STYLE_EMPTY;
199       DOM.setElementProperty(elem, "className", className);
200     }
201     
202     return className;
203   }
204
205   /**
206    * Replaces all instances of the primary style name with newPrimaryStyleName.
207    */

208   private static native void updatePrimaryAndDependentStyleNames(Element elem, String JavaDoc newStyle) /*-{
209     var className = elem.className;
210
211     var spaceIdx = className.indexOf(' ');
212     if (spaceIdx >= 0) {
213       // Get the old base style name from the beginning of the className.
214       var oldStyle = className.substring(0, spaceIdx);
215
216       // Replace oldStyle with newStyle. We have to do this by hand because
217       // there is no String.replaceAll() and String.replace() takes a regex,
218       // which we can't guarantee is safe on arbitrary class names.
219       var newClassName = '', curIdx = 0;
220       while (true) {
221         var idx = className.indexOf(oldStyle, curIdx);
222         if (idx == -1) {
223           newClassName += className.substring(curIdx);
224           break;
225         }
226
227         newClassName += className.substring(curIdx, idx);
228         newClassName += newStyle;
229         curIdx = idx + oldStyle.length;
230       }
231
232       elem.className = newClassName;
233     } else {
234       // There was no space, and therefore only one class name, which we can
235       // simply clobber.
236       elem.className = newStyle;
237     }
238   }-*/
;
239
240   private Element element;
241
242   /**
243    * Adds a secondary or dependent style name to this object. A secondary style
244    * name is an additional style name that is, in HTML/CSS terms, included as a
245    * space-separated token in the value of the CSS <code>class</code>
246    * attribute for this object's root element.
247    *
248    * <p>
249    * The most important use for this method is to add a special kind of
250    * secondary style name called a <i>dependent style name</i>. To add a
251    * dependent style name, prefix the 'style' argument with the result of
252    * {@link #getStyleName()}. For example, suppose the primary style name is
253    * <code>gwt-TextBox</code>. If the following method is called as
254    * <code>obj.setReadOnly(true)</code>:
255    * </p>
256    *
257    * <pre class="code">
258    * public void setReadOnly(boolean readOnly) {
259    * isReadOnlyMode = readOnly;
260    *
261    * // Create a dependent style name.
262    * String readOnlyStyle = getStyleName() + "-readonly";
263    *
264    * if (readOnly) {
265    * addStyleName(readOnlyStyle);
266    * } else {
267    * removeStyleName(readOnlyStyle);
268    * }
269    * }</pre>
270    *
271    * <p>
272    * then both of the CSS style rules below will be applied:
273    * </p>
274    *
275    * <pre class="code">
276    *
277    * // This rule is based on the primary style name and is always active.
278    * .gwt-TextBox {
279    * font-size: 12pt;
280    * }
281    *
282    * // This rule is based on a dependent style name that is only active
283    * // when the widget has called addStyleName(getStyleName() + "-readonly").
284    * .gwt-TextBox-readonly {
285    * background-color: lightgrey;
286    * border: none;
287    * }</pre>
288    *
289    * <p>
290    * Dependent style names are powerful because they are automatically updated
291    * whenever the primary style name changes. Continuing with the example above,
292    * if the primary style name changed due to the following call:
293    * </p>
294    *
295    * <pre class="code">setStyleName("my-TextThingy");</pre>
296    *
297    * <p>
298    * then the object would be re-associated with style rules below rather than
299    * those above:
300    * </p>
301    *
302    * <pre class="code">
303    * .my-TextThingy {
304    * font-size: 12pt;
305    * }
306    *
307    * .my-TextThingy-readonly {
308    * background-color: lightgrey;
309    * border: none;
310    * }</pre>
311    *
312    * <p>
313    * Secondary style names that are not dependent style names are not
314    * automatically updated when the primary style name changes.
315    * </p>
316    *
317    * @param style the secondary style name to be added
318    * @see UIObject
319    * @see #removeStyleName(String)
320    */

321   public void addStyleName(String JavaDoc style) {
322     setStyleName(getStyleElement(), style, true);
323   }
324
325   /**
326    * Gets the object's absolute left position in pixels, as measured from the
327    * browser window's client area.
328    *
329    * @return the object's absolute left position
330    */

331   public int getAbsoluteLeft() {
332     return DOM.getAbsoluteLeft(getElement());
333   }
334
335   /**
336    * Gets the object's absolute top position in pixels, as measured from the
337    * browser window's client area.
338    *
339    * @return the object's absolute top position
340    */

341   public int getAbsoluteTop() {
342     return DOM.getAbsoluteTop(getElement());
343   }
344
345   /**
346    * Gets a handle to the object's underlying DOM element.
347    *
348    * @return the object's browser element
349    */

350   public Element getElement() {
351     return element;
352   }
353
354   /**
355    * Gets the object's offset height in pixels. This is the total height of the
356    * object, including decorations such as border, margin, and padding.
357    *
358    * @return the object's offset height
359    */

360   public int getOffsetHeight() {
361     return DOM.getElementPropertyInt(element, "offsetHeight");
362   }
363
364   /**
365    * Gets the object's offset width in pixels. This is the total width of the
366    * object, including decorations such as border, margin, and padding.
367    *
368    * @return the object's offset width
369    */

370   public int getOffsetWidth() {
371     return DOM.getElementPropertyInt(element, "offsetWidth");
372   }
373
374   /**
375    * Gets the primary style name associated with the object.
376    *
377    * @return the object's primary style name
378    * @see #setStyleName(String)
379    * @see #addStyleName(String)
380    * @see #removeStyleName(String)
381    */

382   public String JavaDoc getStyleName() {
383     String JavaDoc fullClassName = ensurePrimaryStyleName(getStyleElement());
384
385     // The base style name is always the first token of the full CSS class
386
// name. There can be no leading whitespace in the class name, so it's not
387
// necessary to trim() it.
388
int spaceIdx = fullClassName.indexOf(' ');
389     if (spaceIdx >= 0) {
390       return fullClassName.substring(0, spaceIdx);
391     }
392     return fullClassName;
393   }
394
395   /**
396    * Gets the title associated with this object. The title is the 'tool-tip'
397    * displayed to users when they hover over the object.
398    *
399    * @return the object's title
400    */

401   public String JavaDoc getTitle() {
402     return DOM.getElementProperty(element, "title");
403   }
404
405   /**
406    * Determines whether or not this object is visible.
407    *
408    * @return <code>true</code> if the object is visible
409    */

410   public boolean isVisible() {
411     return isVisible(element);
412   }
413
414   /**
415    * Removes a secondary style name.
416    *
417    * @param style the secondary style name to be removed
418    * @see #addStyleName(String)
419    */

420   public void removeStyleName(String JavaDoc style) {
421     setStyleName(getStyleElement(), style, false);
422   }
423
424   /**
425    * Sets the object's height. This height does not include decorations such as
426    * border, margin, and padding.
427    *
428    * @param height the object's new height, in CSS units (e.g. "10px", "1em")
429    */

430   public void setHeight(String JavaDoc height) {
431     // This exists to deal with an inconsistency in IE's implementation where
432
// it won't accept negative numbers in length measurements
433
assert extractLengthValue(height.trim().toLowerCase()) >= 0 :
434       "CSS heights should not be negative";
435     DOM.setStyleAttribute(element, "height", height);
436   }
437
438   /**
439    * Sets the object's size, in pixels, not including decorations such as
440    * border, margin, and padding.
441    *
442    * @param width the object's new width, in pixels
443    * @param height the object's new height, in pixels
444    */

445   public void setPixelSize(int width, int height) {
446     if (width >= 0) {
447       setWidth(width + "px");
448     }
449     if (height >= 0) {
450       setHeight(height + "px");
451     }
452   }
453
454   /**
455    * Sets the object's size. This size does not include decorations such as
456    * border, margin, and padding.
457    *
458    * @param width the object's new width, in CSS units (e.g. "10px", "1em")
459    * @param height the object's new height, in CSS units (e.g. "10px", "1em")
460    */

461   public void setSize(String JavaDoc width, String JavaDoc height) {
462     setWidth(width);
463     setHeight(height);
464   }
465
466   /**
467    * Sets the object's primary style name and updates all dependent style names.
468    *
469    * @param style the new primary style name
470    * @see #addStyleName(String)
471    * @see #removeStyleName(String)
472    */

473   public void setStyleName(String JavaDoc style) {
474     resetStyleName(getStyleElement(), style);
475   }
476
477   /**
478    * Sets the title associated with this object. The title is the 'tool-tip'
479    * displayed to users when they hover over the object.
480    *
481    * @param title the object's new title
482    */

483   public void setTitle(String JavaDoc title) {
484     if (title == null || title.length() == 0) {
485       DOM.removeElementAttribute(element, "title");
486     } else {
487       DOM.setElementAttribute(element, "title", title);
488     }
489   }
490
491   /**
492    * Sets whether this object is visible.
493    *
494    * @param visible <code>true</code> to show the object, <code>false</code>
495    * to hide it
496    */

497   public void setVisible(boolean visible) {
498     setVisible(element, visible);
499   }
500
501   /**
502    * Sets the object's width. This width does not include decorations such as
503    * border, margin, and padding.
504    *
505    * @param width the object's new width, in CSS units (e.g. "10px", "1em")
506    */

507   public void setWidth(String JavaDoc width) {
508     // This exists to deal with an inconsistency in IE's implementation where
509
// it won't accept negative numbers in length measurements
510
assert extractLengthValue(width.trim().toLowerCase()) >= 0 :
511       "CSS widths should not be negative";
512     DOM.setStyleAttribute(element, "width", width);
513   }
514
515   /**
516    * Adds a set of events to be sunk by this object. Note that only
517    * {@link Widget widgets} may actually receive events, but can receive events
518    * from all objects contained within them.
519    *
520    * @param eventBitsToAdd a bitfield representing the set of events to be added
521    * to this element's event set
522    * @see com.google.gwt.user.client.Event
523    */

524   public void sinkEvents(int eventBitsToAdd) {
525     DOM.sinkEvents(getElement(), eventBitsToAdd
526         | DOM.getEventsSunk(getElement()));
527   }
528
529   /**
530    * This method is overridden so that any object can be viewed in the debugger
531    * as an HTML snippet.
532    *
533    * @return a string representation of the object
534    */

535   public String JavaDoc toString() {
536     if (element == null) {
537       return "(null handle)";
538     }
539     return DOM.toString(element);
540   }
541
542   /**
543    * Removes a set of events from this object's event list.
544    *
545    * @param eventBitsToRemove a bitfield representing the set of events to be
546    * removed from this element's event set
547    * @see #sinkEvents
548    * @see com.google.gwt.user.client.Event
549    */

550   public void unsinkEvents(int eventBitsToRemove) {
551     DOM.sinkEvents(getElement(), DOM.getEventsSunk(getElement())
552         & (~eventBitsToRemove));
553   }
554
555   /**
556    * Template method that returns the element to which style names will be
557    * applied. By default it returns the root element, but this method may be
558    * overridden to apply styles to a child element.
559    *
560    * @return the element to which style names will be applied
561    */

562   protected Element getStyleElement() {
563     return element;
564   }
565
566   /**
567    * Sets this object's browser element. UIObject subclasses must call this
568    * method before attempting to call any other methods.
569    *
570    * If the browser element has already been set, then the current element's
571    * position is located in the DOM and removed. The new element is added into
572    * the previous element's position.
573    *
574    * @param elem the object's new element
575    */

576   protected void setElement(Element elem) {
577     if (this.element != null) {
578       // replace this.element in its parent with elem.
579
replaceNode(this.element, elem);
580     }
581
582     this.element = elem;
583
584     // We do not actually force the creation of a primary style name here.
585
// Instead, we do it lazily -- when it is aboslutely required --
586
// in getStyleName(), addStyleName(), and removeStyleName().
587
}
588
589   /**
590    * Intended to be used to pull the value out of a CSS length. We rely on the
591    * behavior of parseFloat to ignore non-numeric chars in its input. If the
592    * value is "auto" or "inherit", 0 will be returned.
593    *
594    * @param s The CSS length string to extract
595    * @return The leading numeric portion of <code>s</code>, or 0 if "auto" or
596    * "inherit" are passed in.
597    */

598   private native double extractLengthValue(String JavaDoc s) /*-{
599     if (s == "auto" || s == "inherit" || s == "") {
600       return 0;
601     } else {
602       return parseFloat(s);
603     }
604   }-*/
;
605   
606   private native void replaceNode(Element node, Element newNode) /*-{
607     var p = node.parentNode;
608     if (!p) {
609       return;
610     }
611     p.insertBefore(newNode, node);
612     p.removeChild(node);
613   }-*/
;
614 }
615
Popular Tags