1 /**************************************************************************** 2 Copyright (c) 2008-2010 Ricardo Quesada 3 Copyright (c) 2011-2012 cocos2d-x.org 4 Copyright (c) 2013-2014 Chukong Technologies 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.rect(0, 0, 0, 0); 35 this.end = end || cc.rect(0, 0, 0, 0); 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 function, override it to extend the construction behavior, remember to call "this._super()" in the extended "ctor" function. 47 */ 48 ctor:function () { 49 cc.imeDispatcher.addDelegate(this); 50 }, 51 /** 52 * Remove delegate 53 */ 54 removeDelegate:function () { 55 cc.imeDispatcher.removeDelegate(this); 56 }, 57 /** 58 * Remove delegate 59 * @return {Boolean} 60 */ 61 attachWithIME:function () { 62 return cc.imeDispatcher.attachDelegateWithIME(this); 63 }, 64 /** 65 * Detach with IME 66 * @return {Boolean} 67 */ 68 detachWithIME:function () { 69 return cc.imeDispatcher.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 * cc.imeDispatcher is a singleton object which manage input message dispatching. 136 * @class 137 * @name cc.imeDispatcher 138 */ 139 cc.IMEDispatcher = cc.Class.extend(/** @lends cc.imeDispatcher# */{ 140 _domInputControl:null, 141 impl:null, 142 _currentInputString:"", 143 _lastClickPosition:null, 144 /** 145 * Constructor function, override it to extend the construction behavior, remember to call "this._super()" in the extended "ctor" function. 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.sys.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 cc._addEventListener(this._domInputControl, "input", function () { 174 selfPointer._processDomInputString(selfPointer._domInputControl.value); 175 }, false); 176 cc._addEventListener(this._domInputControl, "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 cc._addEventListener(this._domInputControl, "keyup", function (e) { 190 if (e.keyCode === cc.KEY.backspace) { 191 selfPointer._processDomInputString(selfPointer._domInputControl.value); 192 } 193 }, false); 194 } 195 196 cc._addEventListener(window, 'mousedown', function (event) { 197 var tx = event.pageX || 0; 198 var ty = event.pageY || 0; 199 200 selfPointer._lastClickPosition.x = tx; 201 selfPointer._lastClickPosition.y = ty; 202 }, false); 203 }, 204 205 _processDomInputString:function (text) { 206 var i, startPos; 207 var len = this._currentInputString.length < text.length ? this._currentInputString.length : text.length; 208 for (startPos = 0; startPos < len; startPos++) { 209 if (text[startPos] !== this._currentInputString[startPos]) 210 break; 211 } 212 var delTimes = this._currentInputString.length - startPos; 213 var insTimes = text.length - startPos; 214 for (i = 0; i < delTimes; i++) 215 this.dispatchDeleteBackward(); 216 217 for (i = 0; i < insTimes; i++) 218 this.dispatchInsertText(text[startPos + i], 1); 219 220 this._currentInputString = text; 221 }, 222 223 /** 224 * Dispatch the input text from ime 225 * @param {String} text 226 * @param {Number} len 227 */ 228 dispatchInsertText:function (text, len) { 229 if (!this.impl || !text || len <= 0) 230 return; 231 232 // there is no delegate attach with ime 233 if (!this.impl._delegateWithIme) 234 return; 235 236 this.impl._delegateWithIme.insertText(text, len); 237 }, 238 239 /** 240 * Dispatch the delete backward operation 241 */ 242 dispatchDeleteBackward:function () { 243 if (!this.impl) { 244 return; 245 } 246 247 // there is no delegate attach with ime 248 if (!this.impl._delegateWithIme) 249 return; 250 251 this.impl._delegateWithIme.deleteBackward(); 252 }, 253 254 /** 255 * Get the content text, which current CCIMEDelegate which attached with IME has. 256 * @return {String} 257 */ 258 getContentText:function () { 259 if (this.impl && this.impl._delegateWithIme) { 260 var pszContentText = this.impl._delegateWithIme.getContentText(); 261 return (pszContentText) ? pszContentText : ""; 262 } 263 return ""; 264 }, 265 266 /** 267 * Dispatch keyboard notification 268 * @param {cc.IMEKeyboardNotificationInfo} info 269 */ 270 dispatchKeyboardWillShow:function (info) { 271 if (this.impl) { 272 for (var i = 0; i < this.impl._delegateList.length; i++) { 273 var delegate = this.impl._delegateList[i]; 274 if (delegate) { 275 delegate.keyboardWillShow(info); 276 } 277 } 278 } 279 }, 280 281 /** 282 * Dispatch keyboard notification 283 * @param {cc.IMEKeyboardNotificationInfo} info 284 */ 285 dispatchKeyboardDidShow:function (info) { 286 if (this.impl) { 287 for (var i = 0; i < this.impl._delegateList.length; i++) { 288 var delegate = this.impl._delegateList[i]; 289 if (delegate) 290 delegate.keyboardDidShow(info); 291 } 292 } 293 }, 294 295 /** 296 * Dispatch keyboard notification 297 * @param {cc.IMEKeyboardNotificationInfo} info 298 */ 299 dispatchKeyboardWillHide:function (info) { 300 if (this.impl) { 301 for (var i = 0; i < this.impl._delegateList.length; i++) { 302 var delegate = this.impl._delegateList[i]; 303 if (delegate) { 304 delegate.keyboardWillHide(info); 305 } 306 } 307 } 308 }, 309 310 /** 311 * Dispatch keyboard notification 312 * @param {cc.IMEKeyboardNotificationInfo} info 313 */ 314 dispatchKeyboardDidHide:function (info) { 315 if (this.impl) { 316 for (var i = 0; i < this.impl._delegateList.length; i++) { 317 var delegate = this.impl._delegateList[i]; 318 if (delegate) { 319 delegate.keyboardDidHide(info); 320 } 321 } 322 } 323 }, 324 325 /** 326 * Add delegate to concern ime msg 327 * @param {cc.IMEDelegate} delegate 328 * @example 329 * //example 330 * cc.imeDispatcher.addDelegate(this); 331 */ 332 addDelegate:function (delegate) { 333 if (!delegate || !this.impl) 334 return; 335 336 if (this.impl._delegateList.indexOf(delegate) > -1) { 337 // delegate already in list 338 return; 339 } 340 this.impl._delegateList.splice(0, 0, delegate); 341 }, 342 343 /** 344 * Attach the pDeleate with ime. 345 * @param {cc.IMEDelegate} delegate 346 * @return {Boolean} If the old delegate can detattach with ime and the new delegate can attach with ime, return true, otherwise return false. 347 * @example 348 * //example 349 * var ret = cc.imeDispatcher.attachDelegateWithIME(this); 350 */ 351 attachDelegateWithIME:function (delegate) { 352 if (!this.impl || !delegate) 353 return false; 354 355 // if delegate is not in delegate list, return 356 if (this.impl._delegateList.indexOf(delegate) === -1) 357 return false; 358 359 if (this.impl._delegateWithIme) { 360 // if old delegate canDetachWithIME return false 361 // or delegate canAttachWithIME return false, 362 // do nothing. 363 if (!this.impl._delegateWithIme.canDetachWithIME() 364 || !delegate.canAttachWithIME()) 365 return false; 366 367 // detach first 368 var pOldDelegate = this.impl._delegateWithIme; 369 this.impl._delegateWithIme = null; 370 pOldDelegate.didDetachWithIME(); 371 372 this._focusDomInput(delegate); 373 return true; 374 } 375 376 // havn't delegate attached with IME yet 377 if (!delegate.canAttachWithIME()) 378 return false; 379 380 this._focusDomInput(delegate); 381 return true; 382 }, 383 384 _focusDomInput:function (delegate) { 385 if(cc.sys.isMobile){ 386 this.impl._delegateWithIme = delegate; 387 delegate.didAttachWithIME(); 388 //prompt 389 this._currentInputString = delegate.string || ""; 390 391 var tipMessage = delegate.getTipMessage ? delegate.getTipMessage() : "please enter your word:"; 392 // wechat cover the prompt funciton .So need use the Window.prototype.prompt 393 var userInput; 394 if(window.Window && Window.prototype.prompt != prompt){ 395 userInput = Window.prototype.prompt.call(window, tipMessage, this._currentInputString); 396 }else{ 397 userInput = prompt(tipMessage, this._currentInputString); 398 } 399 if(userInput != null) 400 this._processDomInputString(userInput); 401 this.dispatchInsertText("\n", 1); 402 }else{ 403 this.impl._delegateWithIme = delegate; 404 this._currentInputString = delegate.string || ""; 405 delegate.didAttachWithIME(); 406 this._domInputControl.focus(); 407 this._domInputControl.value = this._currentInputString; 408 this._domInputControlTranslate(); 409 } 410 }, 411 412 _domInputControlTranslate:function () { 413 if (/msie/i.test(navigator.userAgent)) { 414 this._domInputControl.style.left = this._lastClickPosition.x + "px"; 415 this._domInputControl.style.top = this._lastClickPosition.y + "px"; 416 } else { 417 this._domInputControl.translates(this._lastClickPosition.x, this._lastClickPosition.y); 418 } 419 }, 420 421 /** 422 * Detach the pDeleate with ime. 423 * @param {cc.IMEDelegate} delegate 424 * @return {Boolean} If the old delegate can detattach with ime and the new delegate can attach with ime, return true, otherwise return false. 425 * @example 426 * //example 427 * var ret = cc.imeDispatcher.detachDelegateWithIME(this); 428 */ 429 detachDelegateWithIME:function (delegate) { 430 if (!this.impl || !delegate) 431 return false; 432 433 // if delegate is not the current delegate attached with ime, return 434 if (this.impl._delegateWithIme !== delegate) 435 return false; 436 437 if (!delegate.canDetachWithIME()) 438 return false; 439 440 this.impl._delegateWithIme = null; 441 delegate.didDetachWithIME(); 442 cc._canvas.focus(); 443 return true; 444 }, 445 446 /** 447 * Remove the delegate from the delegates who concern ime msg 448 * @param {cc.IMEDelegate} delegate 449 * @example 450 * //example 451 * cc.imeDispatcher.removeDelegate(this); 452 */ 453 removeDelegate:function (delegate) { 454 if (!this.impl || !delegate) 455 return; 456 457 // if delegate is not in delegate list, return 458 if (this.impl._delegateList.indexOf(delegate) === -1) 459 return; 460 461 if (this.impl._delegateWithIme) { 462 if (delegate === this.impl._delegateWithIme) { 463 this.impl._delegateWithIme = null; 464 } 465 } 466 cc.arrayRemoveObject(this.impl._delegateList, delegate); 467 }, 468 469 /** 470 * Process keydown's keycode 471 * @param {Number} keyCode 472 * @example 473 * //example 474 * document.addEventListener("keydown", function (e) { 475 * cc.imeDispatcher.processKeycode(e.keyCode); 476 * }); 477 */ 478 processKeycode:function (keyCode) { 479 if (keyCode < 32) { 480 if (keyCode === cc.KEY.backspace) { 481 this.dispatchDeleteBackward(); 482 } else if (keyCode === cc.KEY.enter) { 483 this.dispatchInsertText("\n", 1); 484 } else if (keyCode === cc.KEY.tab) { 485 //tab input 486 } else if (keyCode === cc.KEY.escape) { 487 //ESC input 488 } 489 } else if (keyCode < 255) { 490 this.dispatchInsertText(String.fromCharCode(keyCode), 1); 491 } else { 492 // 493 } 494 } 495 }); 496 497 /** 498 * Create the cc.IMEDispatcher.Imp Object. <br /> 499 * This is the inner class... 500 * @class 501 * @extends cc.Class 502 * @name cc.IMEDispatcher.Impl 503 */ 504 cc.IMEDispatcher.Impl = cc.Class.extend(/** @lends cc.IMEDispatcher.Impl# */{ 505 _delegateWithIme:null, 506 _delegateList:null, 507 /** 508 * Constructor function, override it to extend the construction behavior, remember to call "this._super()" in the extended "ctor" function. 509 */ 510 ctor:function () { 511 this._delegateList = []; 512 }, 513 /** 514 * Find delegate 515 * @param {cc.IMEDelegate} delegate 516 * @return {Number|Null} 517 */ 518 findDelegate:function (delegate) { 519 for (var i = 0; i < this._delegateList.length; i++) { 520 if (this._delegateList[i] === delegate) 521 return i; 522 } 523 return null; 524 } 525 }); 526 527 // Initialize imeDispatcher singleton 528 cc.imeDispatcher = new cc.IMEDispatcher(); 529 530 document.body ? 531 cc.imeDispatcher.init() : 532 cc._addEventListener(window, 'load', function () { 533 cc.imeDispatcher.init(); 534 }, false);