KickJava   Java API By Example, From Geeks To Geeks.

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


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
17 package com.google.gwt.user.client.ui;
18
19 import com.google.gwt.user.client.DOM;
20 import com.google.gwt.user.client.Element;
21 import com.google.gwt.user.client.Event;
22
23 /**
24  * Custom Button is a base button class with built in support for a set number
25  * of button faces.
26  *
27  * Each face has its own style modifier. For example, the state for down and
28  * hovering is assigned the CSS modifier <i>down-hovering</i>. So, if the
29  * button's overall style name is <i>gwt-PushButton</i> then when showing the
30  * <code>down-hovering</code> face, the button's style is <i>
31  * gwt-PushButton-down-hovering</i>. The overall style name can be used to
32  * change the style of the button irrespective of the current face.
33  *
34  * <p>
35  * Each button face can be assigned is own image, text, or html contents. If no
36  * content is defined for a face, then the face will use the contents of another
37  * face. For example, if <code>down-hovering</code> does not have defined
38  * contents, it will use the contents defined by the <code>down</code> face.
39  * </p>
40  *
41  * <p>
42  * The supported faces are defined below:
43  * </p>
44  * <p>
45  * <table border="4">
46  * <tr>
47  *
48  * <td><b>CSS style name</b></td>
49  * <td><b>Getter method</b></td>
50  * <td><b>description of face</b></td>
51  * <td><b>defaults to contents of face</b></td>
52  * </tr>
53  *
54  * <tr>
55  * <td>up</td>
56  * <td> {@link #getUpFace()} </td>
57  * <td>face shown when button is up</td>
58  * <td> none</td>
59  * </tr>
60  *
61  * <tr>
62  * <td>down</td>
63  * <td> {@link #getDownFace()} </td>
64  * <td>face shown when button is down</td>
65  * <td> up </td>
66  * </tr>
67  *
68  * <tr>
69  * <td>up-hovering</td>
70  * <td> {@link #getUpHoveringFace()} </td>
71  * <td>face shown when button is up and hovering</td>
72  * <td> up </td>
73  * </tr>
74  *
75  * <tr>
76  * <td>up-disabled</td>
77  * <td> {@link #getUpDisabledFace()} </td>
78  * <td>face shown when button is up and disabled</td>
79  * <td> up</td>
80  * </tr>
81  *
82  * <tr>
83  * <td>down-hovering</td>
84  * <td> {@link #getDownHoveringFace()} </td>
85  * <td>face shown when button is down and hovering</td>
86  * <td> down</td>
87  * </tr>
88  *
89  * <tr>
90  * <td>down-disabled</td>
91  * <td> {@link #getDownDisabledFace()} </td>
92  * <td>face shown when button is down and disabled</td>
93  * <td>down</td>
94  * </tr>
95  * </table>
96  * </p>
97  *
98  */

99 public abstract class CustomButton extends ButtonBase implements
100     SourcesKeyboardEvents {
101   /**
102    * Represents a button's face. Each face is associated with its own style
103    * modifier and, optionally, its own contents html, text, or image.
104    */

105   public abstract class Face implements HasHTML, HasText {
106     private static final String JavaDoc STYLENAME_HTML_FACE = "html-face";
107     private final Face delegateTo;
108     private Element face;
109
110     /**
111      * Constructor for <code>Face</code>. Creates a new face that delegates
112      * to the supplied face.
113      *
114      * @param delegateTo default content provider
115      */

116     private Face(Face delegateTo) {
117       this.delegateTo = delegateTo;
118     }
119
120     /**
121      * Gets the face's contents as html.
122      *
123      * @return face's contents as html
124      *
125      */

126     public String JavaDoc getHTML() {
127       return DOM.getInnerHTML(getFace());
128     }
129
130     /**
131      * Gets the face's contents as text.
132      *
133      * @return face's contents as text
134      *
135      */

136     public String JavaDoc getText() {
137       return DOM.getInnerText(getFace());
138     }
139
140     /**
141      * Set the face's contents as html.
142      *
143      * @param html html to set as face's contents html
144      *
145      */

146     public void setHTML(String JavaDoc html) {
147       face = DOM.createDiv();
148       UIObject.setStyleName(face, STYLENAME_HTML_FACE, true);
149       DOM.setInnerHTML(face, html);
150       updateButtonFace();
151     }
152
153     /**
154      * Set the face's contents as an image.
155      *
156      * @param image image to set as face contents
157      */

158     public final void setImage(Image image) {
159       face = image.getElement();
160       updateButtonFace();
161     }
162
163     /**
164      * Sets the face's contents as text.
165      *
166      * @param text text to set as face's contents
167      */

168     public final void setText(String JavaDoc text) {
169       face = DOM.createDiv();
170       UIObject.setStyleName(face, STYLENAME_HTML_FACE, true);
171       DOM.setInnerText(face, text);
172       updateButtonFace();
173     }
174
175     public final String JavaDoc toString() {
176       return this.getName();
177     }
178
179     /**
180      * Gets the ID associated with this face. This will be a bitwise and of all
181      * of the attributes that comprise this face.
182      */

183     abstract int getFaceID();
184
185     /**
186      * Get the name of the face. This property is also used as a modifier on the
187      * <code>CustomButton</code> style. <p/> For instance, if the
188      * <code>CustomButton</code> style is "gwt-PushButton" and the face name
189      * is "up", then the CSS class name will be "gwt-PushButton-up".
190      *
191      * @return the face's name
192      */

193     abstract String JavaDoc getName();
194
195     /**
196      * Gets the contents associated with this face.
197      */

198     private Element getFace() {
199       if (face == null) {
200         if (delegateTo == null) {
201           // provide a default face as none was supplied.
202
face = DOM.createDiv();
203           return face;
204         } else {
205           return delegateTo.getFace();
206         }
207       } else {
208         return face;
209       }
210     }
211
212     private void updateButtonFace() {
213       if (curFace != null && curFace.getFace() == this.getFace()) {
214         setCurrentFaceElement(face);
215       }
216     }
217   }
218
219   private static final String JavaDoc STYLENAME_DEFAULT = "gwt-CustomButton";
220
221   /**
222    * Pressed Attribute bit.
223    */

224   private static final int DOWN_ATTRIBUTE = 1;
225
226   /**
227    * Hovering Attribute bit.
228    */

229   private static final int HOVERING_ATTRIBUTE = 2;
230
231   /**
232    * Disabled Attribute bit.
233    */

234   private static final int DISABLED_ATTRIBUTE = 4;
235
236   /**
237    * ID for up face.
238    */

239   private static final int UP = 0;
240
241   /**
242    * ID for down face.
243    */

244   private static final int DOWN = DOWN_ATTRIBUTE;
245
246   /**
247    * ID for upHovering face.
248    */

249   private static final int UP_HOVERING = HOVERING_ATTRIBUTE;
250
251   /**
252    * ID for downHovering face.
253    */

254   private static final int DOWN_HOVERING = DOWN_ATTRIBUTE | HOVERING_ATTRIBUTE;
255
256   /**
257    * ID for upDisabled face.
258    */

259   private static final int UP_DISABLED = DISABLED_ATTRIBUTE;
260
261   /**
262    * ID for downDisabled face.
263    */

264   private static final int DOWN_DISABLED = DOWN | DISABLED_ATTRIBUTE;
265
266   /**
267    * The button's current face element.
268    */

269   private Element curFaceElement;
270
271   /**
272    * The button's current face.
273    */

274   private Face curFace;
275
276   /**
277    * Face for up.
278    */

279   private Face up;
280
281   /**
282    * Face for down.
283    */

284   private Face down;
285
286   /**
287    * Face for downHover.
288    */

289   private Face downHovering;
290
291   /**
292    * Face for upHover.
293    */

294   private Face upHovering;
295
296   /**
297    * Face for upDisabled.
298    */

299   private Face upDisabled;
300
301   /**
302    * Face for downDisabled.
303    */

304   private Face downDisabled;
305
306   /**
307    * If <code>true</code>, this widget is capturing with the mouse held down.
308    */

309   private boolean isCapturing;
310
311   /**
312    * If <code>true</code>, this widget has focus with the space bar down.
313    */

314   private boolean isFocusing;
315
316   /**
317    * Constructor for <code>CustomButton</code>.
318    *
319    * @param upImage image for the default (up) face of the button
320    */

321   public CustomButton(Image upImage) {
322     this();
323     getUpFace().setImage(upImage);
324   }
325
326   /**
327    * Constructor for <code>CustomButton</code>.
328    *
329    * @param upImage image for the default (up) face of the button
330    * @param downImage image for the down face of the button
331    */

332   public CustomButton(Image upImage, Image downImage) {
333     this(upImage);
334     getDownFace().setImage(downImage);
335   }
336
337   /**
338    * Constructor for <code>CustomButton</code>.
339    *
340    * @param upImage image for the default (up) face of the button
341    * @param downImage image for the down face of the button
342    * @param listener clickListener
343    */

344   public CustomButton(Image upImage, Image downImage, ClickListener listener) {
345     this(upImage, listener);
346     getDownFace().setImage(downImage);
347   }
348
349   /**
350    * Constructor for <code>CustomButton</code>.
351    *
352    * @param upImage image for the default (up) face of the button
353    * @param listener the click listener
354    */

355   public CustomButton(Image upImage, ClickListener listener) {
356     this(upImage);
357     addClickListener(listener);
358   }
359
360   /**
361    * Constructor for <code>CustomButton</code>.
362    *
363    * @param upText the text for the default (up) face of the button.
364    */

365   public CustomButton(String JavaDoc upText) {
366     this();
367     getUpFace().setText(upText);
368   }
369
370   /**
371    * Constructor for <code>CustomButton</code>.
372    *
373    * @param upText the text for the default (up) face of the button
374    * @param listener the click listener
375    */

376   public CustomButton(String JavaDoc upText, ClickListener listener) {
377     this(upText);
378     addClickListener(listener);
379   }
380
381   /**
382    * Constructor for <code>CustomButton</code>.
383    *
384    * @param upText the text for the default (up) face of the button
385    * @param downText the text for the down face of the button
386    */

387   public CustomButton(String JavaDoc upText, String JavaDoc downText) {
388     this(upText);
389     getDownFace().setText(downText);
390   }
391
392   /**
393    * Constructor for <code>CustomButton</code>.
394    *
395    * @param upText the text for the default (up) face of the button
396    * @param downText the text for the down face of the button
397    * @param listener the click listener
398    */

399   public CustomButton(String JavaDoc upText, String JavaDoc downText, ClickListener listener) {
400     this(upText, downText);
401     addClickListener(listener);
402   }
403
404   /**
405    * Constructor for <code>CustomButton</code>.
406    */

407   protected CustomButton() {
408     // Use FocusPanel.impl rather than FocusWidget because only FocusPanel.impl
409
// works across browsers to create a focusable element.
410
super(FocusPanel.impl.createFocusable());
411     sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.FOCUSEVENTS);
412     setUpFace(createFace(null, "up", UP));
413     setStyleName(STYLENAME_DEFAULT);
414   }
415
416   /**
417    * Gets the downDisabled face of the button.
418    *
419    * @return the downDisabled face
420    */

421   public final Face getDownDisabledFace() {
422     if (downDisabled == null) {
423       setDownDisabledFace(createFace(getDownFace(), "down-disabled",
424           DOWN_DISABLED));
425     }
426     return downDisabled;
427   }
428
429   /**
430    * Gets the down face of the button.
431    *
432    * @return the down face
433    */

434   public final Face getDownFace() {
435     if (down == null) {
436       setDownFace(createFace(getUpFace(), "down", DOWN));
437     }
438     return down;
439   }
440
441   /**
442    * Gets the downHovering face of the button.
443    *
444    * @return the downHovering face
445    */

446   public final Face getDownHoveringFace() {
447     if (downHovering == null) {
448       setDownHoveringFace(createFace(getDownFace(), "down-hovering",
449           DOWN_HOVERING));
450     }
451     return downHovering;
452   }
453
454   /**
455    * Gets the current face's html.
456    *
457    * @return current face's html
458    */

459   public String JavaDoc getHTML() {
460     return getCurrentFace().getHTML();
461   }
462
463   public int getTabIndex() {
464     return FocusPanel.impl.getTabIndex(getElement());
465   }
466
467   /**
468    * Gets the current face's text.
469    *
470    * @return current face's text
471    */

472   public String JavaDoc getText() {
473     return getCurrentFace().getText();
474   }
475
476   /**
477    * Gets the upDisabled face of the button.
478    *
479    * @return the upDisabled face
480    */

481   public final Face getUpDisabledFace() {
482     if (upDisabled == null) {
483       setUpDisabledFace(createFace(getUpFace(), "up-disabled", UP_DISABLED));
484     }
485     return upDisabled;
486   }
487
488   /**
489    * Gets the up face of the button.
490    *
491    * @return the up face
492    */

493   public final Face getUpFace() {
494     return up;
495   }
496
497   /**
498    * Gets the upHovering face of the button.
499    *
500    * @return the upHovering face
501    */

502   public final Face getUpHoveringFace() {
503     if (upHovering == null) {
504       setUpHoveringFace(createFace(getUpFace(), "up-hovering", UP_HOVERING));
505     }
506     return upHovering;
507   }
508
509   public void onBrowserEvent(Event event) {
510     // Should not act on button if disabled.
511
if (isEnabled() == false) {
512       // This can happen when events are bubbled up from non-disabled children
513
return;
514     }
515
516     int type = DOM.eventGetType(event);
517     switch (type) {
518       case Event.ONMOUSEDOWN:
519         setFocus(true);
520         onClickStart();
521         DOM.setCapture(getElement());
522         isCapturing = true;
523         // Prevent dragging (on some browsers);
524
DOM.eventPreventDefault(event);
525         break;
526       case Event.ONMOUSEUP:
527         if (isCapturing) {
528           isCapturing = false;
529           DOM.releaseCapture(getElement());
530           if (isHovering()) {
531             onClick();
532           }
533         }
534         break;
535       case Event.ONMOUSEMOVE:
536         if (isCapturing) {
537           // Prevent dragging (on other browsers);
538
DOM.eventPreventDefault(event);
539         }
540         break;
541       case Event.ONMOUSEOUT:
542         if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
543           if (isCapturing) {
544             onClickCancel();
545           }
546           setHovering(false);
547         }
548         break;
549       case Event.ONMOUSEOVER:
550         if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
551           setHovering(true);
552           if (isCapturing) {
553             onClickStart();
554           }
555         }
556         break;
557       case Event.ONCLICK:
558         // we handle clicks ourselves
559
return;
560       case Event.ONBLUR:
561         if (isFocusing) {
562           isFocusing = false;
563           onClickCancel();
564         }
565         break;
566       case Event.ONLOSECAPTURE:
567         if (isCapturing) {
568           isCapturing = false;
569           onClickCancel();
570         }
571         break;
572     }
573
574     super.onBrowserEvent(event);
575
576     // Synthesize clicks based on keyboard events AFTER the normal key handling.
577
char keyCode = (char) DOM.eventGetKeyCode(event);
578     switch (type) {
579       case Event.ONKEYDOWN:
580         if (keyCode == ' ') {
581           isFocusing = true;
582           onClickStart();
583         }
584         break;
585       case Event.ONKEYUP:
586         if (isFocusing && keyCode == ' ') {
587           isFocusing = false;
588           onClick();
589         }
590         break;
591       case Event.ONKEYPRESS:
592         if (keyCode == '\n' || keyCode == '\r') {
593           onClickStart();
594           onClick();
595         }
596         break;
597     }
598   }
599
600   public void setAccessKey(char key) {
601     FocusPanel.impl.setAccessKey(getElement(), key);
602   }
603
604   /**
605    * Sets whether this button is enabled.
606    *
607    * @param enabled <code>true</code> to enable the button, <code>false</code>
608    * to disable it
609    */

610   public final void setEnabled(boolean enabled) {
611     if (isEnabled() != enabled) {
612       toggleDisabled();
613       super.setEnabled(enabled);
614       if (!enabled) {
615         cleanupCaptureState();
616       }
617     }
618   }
619
620   public void setFocus(boolean focused) {
621     if (focused) {
622       FocusPanel.impl.focus(getElement());
623     } else {
624       FocusPanel.impl.blur(getElement());
625     }
626   }
627
628   /**
629    * Sets the current face's html.
630    *
631    * @param html html to set
632    */

633   public void setHTML(String JavaDoc html) {
634     getCurrentFace().setHTML(html);
635   }
636
637   public void setTabIndex(int index) {
638     FocusPanel.impl.setTabIndex(getElement(), index);
639   }
640
641   /**
642    * Sets the current face's text.
643    *
644    * @param text text to set
645    */

646   public void setText(String JavaDoc text) {
647     getCurrentFace().setText(text);
648   }
649
650   /**
651    * Is this button down?
652    *
653    * @return <code>true</code> if the button is down
654    */

655   protected boolean isDown() {
656     return (DOWN_ATTRIBUTE & getCurrentFace().getFaceID()) > 0;
657   }
658
659   /**
660    * Overridden on attach to ensure that a button face has been chosen before
661    * the button is displayed.
662    */

663   protected void onAttach() {
664     finishSetup();
665     super.onAttach();
666   }
667
668   /**
669    * Called when the user finishes clicking on this button. The default behavior
670    * is to fire the click event to listeners. Subclasses that override
671    * {@link #onClickStart()} should override this method to restore the normal
672    * widget display.
673    */

674   protected void onClick() {
675     fireClickListeners();
676   }
677
678   /**
679    * Called when the user aborts a click in progress; for example, by dragging
680    * the mouse outside of the button before releasing the mouse button.
681    * Subclasses that override {@link #onClickStart()} should override this
682    * method to restore the normal widget display.
683    */

684   protected void onClickCancel() {
685   }
686
687   /**
688    * Called when the user begins to click on this button. Subclasses may
689    * override this method to display the start of the click visually; such
690    * subclasses should also override {@link #onClick()} and
691    * {@link #onClickCancel()} to restore normal visual state. Each
692    * <code>onClickStart</code> will eventually be followed by either
693    * <code>onClick</code> or <code>onClickCancel</code>, depending on
694    * whether the click is completed.
695    */

696   protected void onClickStart() {
697   }
698
699   protected void onDetach() {
700     super.onDetach();
701     cleanupCaptureState();
702   }
703
704   /**
705    * Sets whether this button is down.
706    *
707    * @param down <code>true</code> to press the button, <code>false</code>
708    * otherwise
709    */

710   protected void setDown(boolean down) {
711     if (down != isDown()) {
712       toggleDown();
713     }
714   }
715
716   /**
717    * Common setup between constructors.
718    */

719   void finishSetup() {
720     if (curFace == null) {
721       setCurrentFace(getUpFace());
722     }
723   }
724
725   /**
726    * Gets the current face of the button.
727    *
728    * @return the current face
729    */

730
731   Face getCurrentFace() {
732     /*
733      * Implementation note: Package protected so we can use it when testing the
734      * button.
735      */

736     finishSetup();
737     return curFace;
738   }
739
740   /**
741    * Is the mouse hovering over this button?
742    *
743    * @return <code>true</code> if the mouse is hovering
744    */

745   final boolean isHovering() {
746     return (HOVERING_ATTRIBUTE & getCurrentFace().getFaceID()) > 0;
747   }
748
749   void setCurrentFace(Face newFace) {
750     /*
751      * Implementation note: default access for testing.
752      */

753     if (curFace != newFace) {
754       if (curFace != null) {
755         super.removeStyleName(getCSSStyleName());
756       }
757       curFace = newFace;
758       setCurrentFaceElement(newFace.getFace());
759       super.addStyleName(getCSSStyleName());
760     }
761   }
762
763   /**
764    * Sets whether this button is hovering.
765    *
766    * @param hovering is this button hovering?
767    */

768   final void setHovering(boolean hovering) {
769     if (hovering != isHovering()) {
770       toggleHover();
771     }
772   }
773
774   /**
775    * Toggle the up/down attribute.
776    */

777   void toggleDown() {
778     int newFaceID = getCurrentFace().getFaceID() ^ DOWN_ATTRIBUTE;
779     setCurrentFace(newFaceID);
780   }
781
782   /**
783    * Resets internal state if this button can no longer service events. This can
784    * occur when the widget becomes detached or disabled.
785    */

786   private void cleanupCaptureState() {
787     if (isCapturing || isFocusing) {
788       DOM.releaseCapture(getElement());
789       isCapturing = false;
790       isFocusing = false;
791       onClickCancel();
792     }
793   }
794
795   private Face createFace(Face delegateTo, final String JavaDoc name, final int faceID) {
796     return new Face(delegateTo) {
797
798       public String JavaDoc getName() {
799         return name;
800       }
801
802       int getFaceID() {
803         return faceID;
804       }
805     };
806   }
807
808   /**
809    * Gets the modified style name.
810    *
811    * @return the modified style name
812    */

813   private String JavaDoc getCSSStyleName() {
814     return getStyleName() + "-" + curFace.getName();
815   }
816
817   private Face getFaceFromID(int id) {
818     switch (id) {
819       case DOWN:
820         return getDownFace();
821       case UP:
822         return getUpFace();
823       case DOWN_HOVERING:
824         return getDownHoveringFace();
825       case UP_HOVERING:
826         return getUpHoveringFace();
827       case UP_DISABLED:
828         return getUpDisabledFace();
829       case DOWN_DISABLED:
830         return getDownDisabledFace();
831       default:
832         throw new IllegalStateException JavaDoc(id + " is not a known face id.");
833     }
834   }
835
836   /**
837    * Sets the current face based on the faceID.
838    *
839    * @param faceID sets the new face of the button
840    */

841   private void setCurrentFace(int faceID) {
842     Face newFace = getFaceFromID(faceID);
843     setCurrentFace(newFace);
844   }
845
846   private void setCurrentFaceElement(Element newFaceElement) {
847     if (curFaceElement != newFaceElement) {
848       if (curFaceElement != null) {
849         DOM.removeChild(getElement(), curFaceElement);
850       }
851       curFaceElement = newFaceElement;
852       DOM.appendChild(getElement(), curFaceElement);
853     }
854   }
855
856   /**
857    * Sets the downDisabled face of the button.
858    *
859    * @param downDisabled downDisabled face
860    */

861   private void setDownDisabledFace(Face downDisabled) {
862     this.downDisabled = downDisabled;
863   }
864
865   /**
866    * Sets the down face of the button.
867    *
868    * @param down the down face
869    */

870   private void setDownFace(Face down) {
871     this.down = down;
872   }
873
874   /**
875    * Sets the downHovering face of the button.
876    *
877    * @param downHovering hoverDown face
878    */

879   private void setDownHoveringFace(Face downHovering) {
880     this.downHovering = downHovering;
881   }
882
883   /**
884    * Sets the upDisabled face of the button.
885    *
886    * @param upDisabled upDisabled face
887    */

888   private void setUpDisabledFace(Face upDisabled) {
889     this.upDisabled = upDisabled;
890   }
891
892   /**
893    * Sets the up face of the button.
894    *
895    * @param up up face
896    */

897   private void setUpFace(Face up) {
898     this.up = up;
899   }
900
901   /**
902    * Sets the upHovering face of the button.
903    *
904    * @param upHovering upHovering face
905    */

906   private void setUpHoveringFace(Face upHovering) {
907     this.upHovering = upHovering;
908   }
909
910   /**
911    * Toggle the disabled attribute.
912    */

913   private void toggleDisabled() {
914     // Toggle disabled.
915
int newFaceID = getCurrentFace().getFaceID() ^ DISABLED_ATTRIBUTE;
916
917     // Remove hovering.
918
newFaceID &= ~HOVERING_ATTRIBUTE;
919
920     // Sets the current face.
921
setCurrentFace(newFaceID);
922   }
923
924   /**
925    * Toggle the hovering attribute.
926    */

927   private void toggleHover() {
928     // Toggle hovering.
929
int newFaceID = getCurrentFace().getFaceID() ^ HOVERING_ATTRIBUTE;
930
931     // Remove disabled.
932
newFaceID &= ~DISABLED_ATTRIBUTE;
933     setCurrentFace(newFaceID);
934   }
935 }
936
Popular Tags