1 /**
  2  * The Render Engine
  3  * InputControl
  4  *
  5  * @fileoverview A single line text input control.
  6  *
  7  * @author: Brett Fattori (brettf@renderengine.com)
  8  *
  9  * @author: $Author: bfattori $
 10  * @version: $Revision: 1555 $
 11  *
 12  * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com)
 13  *
 14  * Permission is hereby granted, free of charge, to any person obtaining a copy
 15  * of this software and associated documentation files (the "Software"), to deal
 16  * in the Software without restriction, including without limitation the rights
 17  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 18  * copies of the Software, and to permit persons to whom the Software is
 19  * furnished to do so, subject to the following conditions:
 20  *
 21  * The above copyright notice and this permission notice shall be included in
 22  * all copies or substantial portions of the Software.
 23  *
 24  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 25  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 26  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 27  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 28  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 29  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 30  * THE SOFTWARE.
 31  *
 32  */
 33 
 34 // The class this file defines and its required classes
 35 R.Engine.define({
 36     "class":"R.ui.TextInputControl",
 37     "requires":[
 38         "R.ui.AbstractUIControl",
 39         "R.components.input.Keyboard"
 40     ]
 41 });
 42 
 43 /**
 44  * @class UI input control for the input of a single line of text.
 45  *
 46  * @constructor
 47  * @param [size=10] {Number} The number of visible characters in the context.
 48  * @param [maxLength=0] {Number} The maximum number of allowed characters.  Zero for
 49  *    unlimited text length.
 50  * @extends R.ui.AbstractUIControl
 51  */
 52 R.ui.TextInputControl = function () {
 53     return R.ui.AbstractUIControl.extend(/** @scope R.ui.TextInputControl.prototype */{
 54 
 55         text:"",
 56         maxLength:0,
 57         size:0,
 58         styleClass:"",
 59         password:false,
 60         passwordChar:null,
 61         passwordText:null,
 62         blinkTime:0,
 63         blink:false,
 64 
 65         /** @private */
 66         constructor:function (size, maxLength, textRenderer) {
 67             this.base("TextInput", textRenderer);
 68             this.size = size || 10;
 69             this.maxLength = maxLength || 0;
 70             this.addClass("inputcontrol");
 71             this.password = false;
 72             this.passwordChar = "*";
 73             this.passwordText = "";
 74             this.blinkTime = 0;
 75             this.blink = false;
 76 
 77             // We won't get events for keyboard unless we have the component
 78             this.add(R.components.input.Keyboard.create("keyinput"));
 79 
 80             // We want to add events to capture key presses
 81             // when the control has focus
 82             this.addEvents({
 83                 "keydown":function (evt, which) {
 84                     if (this.hasFocus()) {
 85                         if (which == R.engine.Events.KEYCODE_BACKSPACE) {
 86                             if (this.text.length > 0) {
 87                                 this.text = this.text.substring(0, this.text.length - 1);
 88                                 if (this.password) {
 89                                     this.passwordText = this.passwordText.substring(0, this.text.length - 1);
 90                                 }
 91                             }
 92 
 93                             evt.stopPropagation();
 94                             evt.preventDefault();
 95                         }
 96 
 97                         this.getTextRenderer().setText(this.text);
 98                         this.triggerEvent("change");
 99                     }
100                 },
101                 "keypress":function (evt, which) {
102                     if (this.hasFocus()) {
103                         if (which != R.engine.Events.KEYCODE_ENTER &&
104                             which != R.engine.Events.KEYCODE_BACKSPACE) {
105                             if (this.maxLength == 0 || this.text.length < this.maxLength) {
106                                 if (this.password) {
107                                     this.text += this.passwordChar;
108                                     this.passwordText += String.fromCharCode(which);
109                                 } else {
110                                     this.text += String.fromCharCode(which);
111                                 }
112                             }
113                         }
114 
115                         this.getTextRenderer().setText(this.text);
116                         this.triggerEvent("change");
117                         evt.stopPropagation();
118                         evt.preventDefault();
119                     }
120                 }
121             });
122         },
123 
124         /**
125          * Destroy the text input control, releasing its event handlers.
126          */
127         destroy:function () {
128             this.removeEvent("keydown");
129             this.removeEvent("keypress");
130             this.base();
131         },
132 
133         /**
134          * Releases the object back into the object pool.  See {@link R.engine.PooledObject#release}
135          * for more information.
136          */
137         release:function () {
138             this.base();
139             this.text = "";
140             this.maxLength = 0;
141             this.size = 10;
142             this.styleClass = "";
143         },
144 
145         /**
146          * Set a flag indicating this is a password field.
147          * @param state {Boolean} <code>true</code> to mask the characters
148          */
149         setPassword:function (state) {
150             this.password = state;
151         },
152 
153         /**
154          * Set the number of characters to display in the input.
155          * @param size {Number} The number of characters to display
156          */
157         setSize:function (size) {
158             this.size = size;
159         },
160 
161         /**
162          * Set the maximum length of input allowed.
163          * @param maxLength {Number} Maximum allowed input length
164          */
165         setMaxLength:function (maxLength) {
166             this.maxLength = maxLength;
167         },
168 
169         /**
170          * @private
171          */
172         mask:function (str) {
173             var m = "";
174             for (var s = 0; s < str.length; s++) {
175                 m += this.passwordChar;
176             }
177             return m;
178         },
179 
180         /**
181          * Set the value of the input control.
182          * @param text {String} Text
183          */
184         setText:function (text) {
185             this.text = this.password ? this.mask(text) : text;
186             this.passwordText = this.password ? text : "";
187             this.getTextRenderer().setText(this.text);
188         },
189 
190         /**
191          * Get the value of the input control.
192          * @return {String}
193          */
194         getText:function () {
195             return this.password ? this.passwordText : this.text;
196         },
197 
198         /**
199          * Calculate and return the width of the control in pixels.
200          * @return {Number}
201          */
202         calcWidth:function (str) {
203             var old = this.getTextRenderer().getText();
204             if (str === undefined) {
205                 str = "";
206                 for (var s = 0; s < this.size; s++) {
207                     str += "W";
208                 }
209             }
210             this.getTextRenderer().setText(str);
211             var width = this.getTextRenderer().getBoundingBox().w;
212             this.getTextRenderer().setText(old);
213             return width;
214         },
215 
216         /**
217          * Calculate and return the height of the control in pixels.
218          * @return {Number}
219          */
220         calcHeight:function () {
221             var str = "W", old = this.getTextRenderer().getText();
222             this.getTextRenderer().setText(str);
223             var height = this.getTextRenderer().getBoundingBox().h;
224             this.getTextRenderer().setText(old);
225             return height;
226         },
227 
228         /**
229          * Draw the input caret.
230          * @param renderContext {R.rendercontexts.RenderContext2D} The render context where the control is
231          *    drawn.
232          * @param worldTime {Number} The current world time, in milliseconds
233          * @param dt {Number} The time since the last frame was drawn by the engine, in milliseconds
234          */
235         drawCaret:function (renderContext, worldTime, dt) {
236             if (this.hasFocus()) {
237                 if (worldTime > this.blinkTime) {
238                     this.blink = !this.blink;
239                     this.blinkTime = worldTime + 500;
240                 }
241                 if (this.blink) {
242                     var cPos = R.math.Point2D.create(this.calcWidth(this.text) + 4, 2),
243                         cEnd = R.clone(cPos);
244                     cEnd.y += this.calcHeight() - 4;
245                     renderContext.setLineWidth(2);
246                     renderContext.setLineStyle(this.getTextRenderer().getTextColor());
247                     renderContext.drawLine(cPos, cEnd);
248                     cPos.destroy();
249                     cEnd.destroy();
250                 }
251             }
252         },
253 
254         /**
255          * Draw the input component within the
256          * @param renderContext {R.rendercontexts.RenderContext2D} The render context where the control is
257          *    drawn.
258          * @param worldTime {Number} The current world time, in milliseconds
259          * @param dt {Number} The time since the last frame was drawn by the engine, in milliseconds
260          */
261         drawControl:function (renderContext, worldTime, dt) {
262             // Draw the current input text.  The text baseline is the bottom of the font,
263             // so we need to move that down by the height of the control (with some padding to look right)
264             renderContext.pushTransform();
265             var baseline = R.math.Point2D.create(2, this.calcHeight() - 3);
266             renderContext.setPosition(baseline);
267             this.getTextRenderer().update(renderContext, worldTime, dt);
268             baseline.destroy();
269             renderContext.popTransform();
270 
271             // Draw the caret
272             this.drawCaret(renderContext, worldTime, dt);
273         },
274 
275         /**
276          * Returns a bean which represents the read or read/write properties
277          * of the object.
278          *
279          * @return {Object} The properties object
280          */
281         getProperties:function () {
282             var self = this;
283             var prop = this.base(self);
284             return $.extend(prop, {
285                 "Size":[function () {
286                     return self.size;
287                 }, function (i) {
288                     self.setSize(parseInt(i));
289                 }, true],
290                 "MaxLength":[function () {
291                     return self.maxLength;
292                 }, function (i) {
293                     self.setMaxLength(parseInt(i));
294                 }, true],
295                 "IsPassword":[function () {
296                     return self.password;
297                 }, {
298                     "toggle":true,
299                     "fn":function (s) {
300                         self.setPassword(s);
301                     }
302                 }, true],
303                 "Text":[function () {
304                     return self.getText();
305                 }, function (i) {
306                     self.setText(i);
307                 }, true]
308             });
309         }
310 
311     }, /** @scope R.ui.TextInputControl.prototype */{
312 
313         /**
314          * Get the class name of this object
315          * @return {String} The string "R.ui.TextInputControl"
316          */
317         getClassName:function () {
318             return "R.ui.TextInputControl";
319         },
320 
321         /**
322          * Get a properties object with values for the given object.
323          * @param obj {R.ui.TextInputControl} The text input control to query
324          * @param [defaults] {Object} Default values that don't need to be serialized unless
325          *    they are different.
326          * @return {Object}
327          */
328         serialize:function (obj, defaults) {
329             // Defaults for object properties which can be skipped if no different
330             defaults = defaults || [];
331             $.extend(defaults, {
332                 "Text":"",
333                 "MaxLength":0,
334                 "IsPassword":false,
335                 "Size":10
336             });
337             return R.ui.AbstractUIControl.serialize(obj, defaults);
338         },
339 
340         /**
341          * Deserialize the object back into a text input control.
342          * @param obj {Object} The object to deserialize
343          * @param [clazz] {Class} The object class to populate
344          * @return {R.ui.TextInputControl} The object which was deserialized
345          */
346         deserialize:function (obj, clazz) {
347             clazz = clazz || R.ui.TextInputControl.create();
348             R.ui.AbstractUIControl.deserialize(obj, clazz);
349             return clazz;
350         }
351     });
352 
353 };