1 /**************************************************************************** 2 Copyright (c) 2010-2012 cocos2d-x.org 3 Copyright (c) 2008-2010 Ricardo Quesada 4 Copyright (c) 2011 Zynga Inc. 5 6 http://www.cocos2d-x.org 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy 9 of this software and associated documentation files (the "Software"), to deal 10 in the Software without restriction, including without limitation the rights 11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 copies of the Software, and to permit persons to whom the Software is 13 furnished to do so, subject to the following conditions: 14 15 The above copyright notice and this permission notice shall be included in 16 all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 THE SOFTWARE. 25 ****************************************************************************/ 26 27 /** 28 * IME Keyboard Notification Info structure 29 * @param {cc.Rect} begin the soft keyboard rectangle when animatin begin 30 * @param {cc.Rect} end the soft keyboard rectangle when animatin end 31 * @param {Number} duration the soft keyboard animation duration 32 */ 33 cc.IMEKeyboardNotificationInfo = function (begin, end, duration) { 34 this.begin = begin || cc.RectZero(); 35 this.end = end || cc.RectZero(); 36 this.duration = duration || 0; 37 }; 38 39 /** 40 * Input method editor delegate. 41 * @class 42 * @extends cc.Class 43 */ 44 cc.IMEDelegate = cc.Class.extend(/** @lends cc.IMEDelegate# */{ 45 /** 46 * Constructor 47 */ 48 ctor:function () { 49 cc.IMEDispatcher.getInstance().addDelegate(this); 50 }, 51 /** 52 * Remove delegate 53 */ 54 removeDelegate:function () { 55 cc.IMEDispatcher.getInstance().removeDelegate(this); 56 }, 57 /** 58 * Remove delegate 59 * @return {Boolean} 60 */ 61 attachWithIME:function () { 62 return cc.IMEDispatcher.getInstance().attachDelegateWithIME(this); 63 }, 64 /** 65 * Detach with IME 66 * @return {Boolean} 67 */ 68 detachWithIME:function () { 69 return cc.IMEDispatcher.getInstance().detachDelegateWithIME(this); 70 }, 71 72 /** 73 * Decide the delegate instance is ready for receive ime message or not.<br /> 74 * Called by CCIMEDispatcher. 75 * @return {Boolean} 76 */ 77 canAttachWithIME:function () { 78 return false; 79 }, 80 81 /** 82 * When the delegate detach with IME, this method call by CCIMEDispatcher. 83 */ 84 didAttachWithIME:function () { 85 }, 86 87 /** 88 * Decide the delegate instance can stop receive ime message or not. 89 * @return {Boolean} 90 */ 91 canDetachWithIME:function () { 92 return false; 93 }, 94 95 /** 96 * When the delegate detach with IME, this method call by CCIMEDispatcher. 97 */ 98 didDetachWithIME:function () { 99 }, 100 101 /** 102 * Called by CCIMEDispatcher when some text input from IME. 103 */ 104 insertText:function (text, len) { 105 }, 106 107 /** 108 * Called by CCIMEDispatcher when user clicked the backward key. 109 */ 110 deleteBackward:function () { 111 }, 112 113 /** 114 * Called by CCIMEDispatcher for get text which delegate already has. 115 * @return {String} 116 */ 117 getContentText:function () { 118 return ""; 119 }, 120 121 ////////////////////////////////////////////////////////////////////////// 122 // keyboard show/hide notification 123 ////////////////////////////////////////////////////////////////////////// 124 keyboardWillShow:function (info) { 125 }, 126 keyboardDidShow:function (info) { 127 }, 128 keyboardWillHide:function (info) { 129 }, 130 keyboardDidHide:function (info) { 131 } 132 }); 133 134 /** 135 * Input Method Edit Message Dispatcher. 136 * @class 137 * @extends cc.Class 138 */ 139 cc.IMEDispatcher = cc.Class.extend(/** @lends cc.IMEDispatcher# */{ 140 _domInputControl:null, 141 impl:null, 142 _currentInputString:"", 143 _lastClickPosition:null, 144 /** 145 * Constructor 146 */ 147 ctor:function () { 148 this.impl = new cc.IMEDispatcher.Impl(); 149 this._lastClickPosition = cc.p(0, 0); 150 }, 151 152 init:function () { 153 if (cc.Browser.isMobile) 154 return; 155 this._domInputControl = cc.$("#imeDispatcherInput"); 156 if (!this._domInputControl) { 157 this._domInputControl = cc.$new("input"); 158 this._domInputControl.setAttribute("type", "text"); 159 this._domInputControl.setAttribute("id", "imeDispatcherInput"); 160 this._domInputControl.resize(0.0, 0.0); 161 this._domInputControl.translates(0, 0); 162 this._domInputControl.style.opacity = "0"; 163 //this._domInputControl.style.filter = "alpha(opacity = 0)"; 164 this._domInputControl.style.fontSize = "1px"; 165 this._domInputControl.setAttribute('tabindex', 2); 166 this._domInputControl.style.position = "absolute"; 167 this._domInputControl.style.top = 0; 168 this._domInputControl.style.left = 0; 169 document.body.appendChild(this._domInputControl); 170 } 171 var selfPointer = this; 172 //add event listener 173 this._domInputControl.addEventListener("input", function () { 174 selfPointer._processDomInputString(selfPointer._domInputControl.value); 175 }, false); 176 this._domInputControl.addEventListener("keydown", function (e) { 177 // ignore tab key 178 if (e.keyCode === cc.KEY.tab) { 179 e.stopPropagation(); 180 e.preventDefault(); 181 } else if (e.keyCode == cc.KEY.enter) { 182 selfPointer.dispatchInsertText("\n", 1); 183 e.stopPropagation(); 184 e.preventDefault(); 185 } 186 }, false); 187 188 if (/msie/i.test(navigator.userAgent)) { 189 this._domInputControl.addEventListener("keyup", function (e) { 190 if (e.keyCode == cc.KEY.backspace) { 191 selfPointer._processDomInputString(selfPointer._domInputControl.value); 192 } 193 }, false); 194 } 195 196 window.addEventListener('mousedown', function (event) { 197 var tx = event.pageX || 0; 198 var ty = event.pageY || 0; 199 200 selfPointer._lastClickPosition = cc.p(tx, ty); 201 }, false); 202 }, 203 204 _processDomInputString:function (text) { 205 var i, startPos; 206 var len = this._currentInputString.length < text.length ? this._currentInputString.length : text.length; 207 for (startPos = 0; startPos < len; startPos++) { 208 if (text[startPos] !== this._currentInputString[startPos]) 209 break; 210 } 211 var delTimes = this._currentInputString.length - startPos; 212 var insTimes = text.length - startPos; 213 for (i = 0; i < delTimes; i++) 214 this.dispatchDeleteBackward(); 215 216 for (i = 0; i < insTimes; i++) 217 this.dispatchInsertText(text[startPos + i], 1); 218 219 this._currentInputString = text; 220 }, 221 222 /** 223 * Dispatch the input text from ime 224 * @param {String} text 225 * @param {Number} len 226 */ 227 dispatchInsertText:function (text, len) { 228 if (!this.impl || !text || len <= 0) 229 return; 230 231 // there is no delegate attach with ime 232 if (!this.impl._delegateWithIme) 233 return; 234 235 this.impl._delegateWithIme.insertText(text, len); 236 }, 237 238 /** 239 * Dispatch the delete backward operation 240 */ 241 dispatchDeleteBackward:function () { 242 if (!this.impl) { 243 return; 244 } 245 246 // there is no delegate attach with ime 247 if (!this.impl._delegateWithIme) 248 return; 249 250 this.impl._delegateWithIme.deleteBackward(); 251 }, 252 253 /** 254 * Get the content text, which current CCIMEDelegate which attached with IME has. 255 * @return {String} 256 */ 257 getContentText:function () { 258 if (this.impl && this.impl._delegateWithIme) { 259 var pszContentText = this.impl._delegateWithIme.getContentText(); 260 return (pszContentText) ? pszContentText : ""; 261 } 262 return ""; 263 }, 264 265 /** 266 * Dispatch keyboard notification 267 * @param {cc.IMEKeyboardNotificationInfo} info 268 */ 269 dispatchKeyboardWillShow:function (info) { 270 if (this.impl) { 271 for (var i = 0; i < this.impl._delegateList.length; i++) { 272 var delegate = this.impl._delegateList[i]; 273 if (delegate) { 274 delegate.keyboardWillShow(info); 275 } 276 } 277 } 278 }, 279 280 /** 281 * Dispatch keyboard notification 282 * @param {cc.IMEKeyboardNotificationInfo} info 283 */ 284 dispatchKeyboardDidShow:function (info) { 285 if (this.impl) { 286 for (var i = 0; i < this.impl._delegateList.length; i++) { 287 var delegate = this.impl._delegateList[i]; 288 if (delegate) 289 delegate.keyboardDidShow(info); 290 } 291 } 292 }, 293 294 /** 295 * Dispatch keyboard notification 296 * @param {cc.IMEKeyboardNotificationInfo} info 297 */ 298 dispatchKeyboardWillHide:function (info) { 299 if (this.impl) { 300 for (var i = 0; i < this.impl._delegateList.length; i++) { 301 var delegate = this.impl._delegateList[i]; 302 if (delegate) { 303 delegate.keyboardWillHide(info); 304 } 305 } 306 } 307 }, 308 309 /** 310 * Dispatch keyboard notification 311 * @param {cc.IMEKeyboardNotificationInfo} info 312 */ 313 dispatchKeyboardDidHide:function (info) { 314 if (this.impl) { 315 for (var i = 0; i < this.impl._delegateList.length; i++) { 316 var delegate = this.impl._delegateList[i]; 317 if (delegate) { 318 delegate.keyboardDidHide(info); 319 } 320 } 321 } 322 }, 323 324 /** 325 * Add delegate to concern ime msg 326 * @param {cc.IMEDelegate} delegate 327 * @example 328 * //example 329 * cc.IMEDispatcher.getInstance().addDelegate(this); 330 */ 331 addDelegate:function (delegate) { 332 if (!delegate || !this.impl) 333 return; 334 335 if (this.impl._delegateList.indexOf(delegate) > -1) { 336 // delegate already in list 337 return; 338 } 339 this.impl._delegateList = cc.ArrayAppendObjectToIndex(this.impl._delegateList, delegate, 0); 340 }, 341 342 /** 343 * Attach the pDeleate with ime. 344 * @param {cc.IMEDelegate} delegate 345 * @return {Boolean} If the old delegate can detattach with ime and the new delegate can attach with ime, return true, otherwise return false. 346 * @example 347 * //example 348 * var ret = cc.IMEDispatcher.getInstance().attachDelegateWithIME(this); 349 */ 350 attachDelegateWithIME:function (delegate) { 351 if (!this.impl || !delegate) 352 return false; 353 354 // if delegate is not in delegate list, return 355 if (this.impl._delegateList.indexOf(delegate) == -1) 356 return false; 357 358 if (this.impl._delegateWithIme) { 359 // if old delegate canDetachWithIME return false 360 // or delegate canAttachWithIME return false, 361 // do nothing. 362 if (!this.impl._delegateWithIme.canDetachWithIME() 363 || !delegate.canAttachWithIME()) 364 return false; 365 366 // detach first 367 var pOldDelegate = this.impl._delegateWithIme; 368 this.impl._delegateWithIme = null; 369 pOldDelegate.didDetachWithIME(); 370 371 this._focusDomInput(delegate); 372 return true; 373 } 374 375 // havn't delegate attached with IME yet 376 if (!delegate.canAttachWithIME()) 377 return false; 378 379 this._focusDomInput(delegate); 380 return true; 381 }, 382 383 _focusDomInput:function (delegate) { 384 if(cc.Browser.isMobile){ 385 this.impl._delegateWithIme = delegate; 386 delegate.didAttachWithIME(); 387 //prompt 388 this._currentInputString = delegate.getString ? delegate.getString() : ""; 389 var userInput = prompt("please enter your word:", this._currentInputString); 390 if(userInput != null) 391 this._processDomInputString(userInput); 392 this.dispatchInsertText("\n", 1); 393 }else{ 394 this.impl._delegateWithIme = delegate; 395 this._currentInputString = delegate.getString ? delegate.getString() : ""; 396 delegate.didAttachWithIME(); 397 this._domInputControl.focus(); 398 this._domInputControl.value = this._currentInputString; 399 this._domInputControlTranslate(); 400 } 401 }, 402 403 _domInputControlTranslate:function () { 404 if (/msie/i.test(navigator.userAgent)) { 405 this._domInputControl.style.left = this._lastClickPosition.x + "px"; 406 this._domInputControl.style.top = this._lastClickPosition.y + "px"; 407 } else { 408 this._domInputControl.translates(this._lastClickPosition.x, this._lastClickPosition.y); 409 } 410 }, 411 412 /** 413 * Detach the pDeleate with ime. 414 * @param {cc.IMEDelegate} delegate 415 * @return {Boolean} If the old delegate can detattach with ime and the new delegate can attach with ime, return true, otherwise return false. 416 * @example 417 * //example 418 * var ret = cc.IMEDispatcher.getInstance().detachDelegateWithIME(this); 419 */ 420 detachDelegateWithIME:function (delegate) { 421 if (!this.impl || !delegate) 422 return false; 423 424 // if delegate is not the current delegate attached with ime, return 425 if (this.impl._delegateWithIme != delegate) 426 return false; 427 428 if (!delegate.canDetachWithIME()) 429 return false; 430 431 this.impl._delegateWithIme = null; 432 delegate.didDetachWithIME(); 433 cc.canvas.focus(); 434 return true; 435 }, 436 437 /** 438 * Remove the delegate from the delegates who concern ime msg 439 * @param {cc.IMEDelegate} delegate 440 * @example 441 * //example 442 * cc.IMEDispatcher.getInstance().removeDelegate(this); 443 */ 444 removeDelegate:function (delegate) { 445 if (!this.impl || !delegate) 446 return; 447 448 // if delegate is not in delegate list, return 449 if (this.impl._delegateList.indexOf(delegate) == -1) 450 return; 451 452 if (this.impl._delegateWithIme) { 453 if (delegate == this.impl._delegateWithIme) { 454 this.impl._delegateWithIme = null; 455 } 456 } 457 cc.ArrayRemoveObject(this.impl._delegateList, delegate); 458 }, 459 460 /** 461 * Process keydown's keycode 462 * @param {Number} keyCode 463 * @example 464 * //example 465 * document.addEventListener("keydown", function (e) { 466 * cc.IMEDispatcher.getInstance().processKeycode(e.keyCode); 467 * }); 468 */ 469 processKeycode:function (keyCode) { 470 if (keyCode < 32) { 471 if (keyCode == cc.KEY.backspace) { 472 this.dispatchDeleteBackward(); 473 } else if (keyCode == cc.KEY.enter) { 474 this.dispatchInsertText("\n", 1); 475 } else if (keyCode == cc.KEY.tab) { 476 //tab input 477 } else if (keyCode == cc.KEY.escape) { 478 //ESC input 479 } 480 } else if (keyCode < 255) { 481 this.dispatchInsertText(String.fromCharCode(keyCode), 1); 482 } else { 483 // 484 } 485 } 486 }); 487 488 /** 489 * @class 490 * @extends cc.Class 491 */ 492 cc.IMEDispatcher.Impl = cc.Class.extend(/** @lends cc.IMEDispatcher.Impl# */{ 493 _delegateWithIme:null, 494 _delegateList:null, 495 /** 496 * Constructor 497 */ 498 ctor:function () { 499 this._delegateList = []; 500 }, 501 /** 502 * Find delegate 503 * @param {cc.IMEDelegate} delegate 504 * @return {Number|Null} 505 */ 506 findDelegate:function (delegate) { 507 for (var i = 0; i < this._delegateList.length; i++) { 508 if (this._delegateList[i] == delegate) 509 return i; 510 } 511 return null; 512 } 513 }); 514 515 /** 516 * Returns the shared CCIMEDispatcher object for the system. 517 * @return {cc.IMEDispatcher} 518 */ 519 cc.IMEDispatcher.getInstance = function () { 520 if (!cc.IMEDispatcher.instance) { 521 cc.IMEDispatcher.instance = new cc.IMEDispatcher(); 522 cc.IMEDispatcher.instance.init(); 523 } 524 return cc.IMEDispatcher.instance; 525 }; 526 527 /** 528 * @type object 529 */ 530 cc.IMEDispatcher.instance = null; 531