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 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.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 function .So need use the Window.prototype.prompt 393 var userInput; 394 var win = window.Window; 395 if(win && win.prototype.prompt && win.prototype.prompt != prompt){ 396 userInput = win.prototype.prompt.call(window, tipMessage, this._currentInputString); 397 }else{ 398 userInput = prompt(tipMessage, this._currentInputString); 399 } 400 if(userInput != null) 401 this._processDomInputString(userInput); 402 this.dispatchInsertText("\n", 1); 403 }else{ 404 this.impl._delegateWithIme = delegate; 405 this._currentInputString = delegate.string || ""; 406 delegate.didAttachWithIME(); 407 this._domInputControl.focus(); 408 this._domInputControl.value = this._currentInputString; 409 this._domInputControlTranslate(); 410 } 411 }, 412 413 _domInputControlTranslate:function () { 414 if (/msie/i.test(navigator.userAgent)) { 415 this._domInputControl.style.left = this._lastClickPosition.x + "px"; 416 this._domInputControl.style.top = this._lastClickPosition.y + "px"; 417 } else { 418 this._domInputControl.translates(this._lastClickPosition.x, this._lastClickPosition.y); 419 } 420 }, 421 422 /** 423 * Detach the pDeleate with ime. 424 * @param {cc.IMEDelegate} delegate 425 * @return {Boolean} If the old delegate can detattach with ime and the new delegate can attach with ime, return true, otherwise return false. 426 * @example 427 * //example 428 * var ret = cc.imeDispatcher.detachDelegateWithIME(this); 429 */ 430 detachDelegateWithIME:function (delegate) { 431 if (!this.impl || !delegate) 432 return false; 433 434 // if delegate is not the current delegate attached with ime, return 435 if (this.impl._delegateWithIme !== delegate) 436 return false; 437 438 if (!delegate.canDetachWithIME()) 439 return false; 440 441 this.impl._delegateWithIme = null; 442 delegate.didDetachWithIME(); 443 cc._canvas.focus(); 444 return true; 445 }, 446 447 /** 448 * Remove the delegate from the delegates who concern ime msg 449 * @param {cc.IMEDelegate} delegate 450 * @example 451 * //example 452 * cc.imeDispatcher.removeDelegate(this); 453 */ 454 removeDelegate:function (delegate) { 455 if (!this.impl || !delegate) 456 return; 457 458 // if delegate is not in delegate list, return 459 if (this.impl._delegateList.indexOf(delegate) === -1) 460 return; 461 462 if (this.impl._delegateWithIme) { 463 if (delegate === this.impl._delegateWithIme) { 464 this.impl._delegateWithIme = null; 465 } 466 } 467 cc.arrayRemoveObject(this.impl._delegateList, delegate); 468 }, 469 470 /** 471 * Process keydown's keycode 472 * @param {Number} keyCode 473 * @example 474 * //example 475 * document.addEventListener("keydown", function (e) { 476 * cc.imeDispatcher.processKeycode(e.keyCode); 477 * }); 478 */ 479 processKeycode:function (keyCode) { 480 if (keyCode < 32) { 481 if (keyCode === cc.KEY.backspace) { 482 this.dispatchDeleteBackward(); 483 } else if (keyCode === cc.KEY.enter) { 484 this.dispatchInsertText("\n", 1); 485 } else if (keyCode === cc.KEY.tab) { 486 //tab input 487 } else if (keyCode === cc.KEY.escape) { 488 //ESC input 489 } 490 } else if (keyCode < 255) { 491 this.dispatchInsertText(String.fromCharCode(keyCode), 1); 492 } else { 493 // 494 } 495 } 496 }); 497 498 /** 499 * Create the cc.IMEDispatcher.Imp Object. <br /> 500 * This is the inner class... 501 * @class 502 * @extends cc.Class 503 * @name cc.IMEDispatcher.Impl 504 */ 505 cc.IMEDispatcher.Impl = cc.Class.extend(/** @lends cc.IMEDispatcher.Impl# */{ 506 _delegateWithIme:null, 507 _delegateList:null, 508 /** 509 * Constructor function, override it to extend the construction behavior, remember to call "this._super()" in the extended "ctor" function. 510 */ 511 ctor:function () { 512 this._delegateList = []; 513 }, 514 /** 515 * Find delegate 516 * @param {cc.IMEDelegate} delegate 517 * @return {Number|Null} 518 */ 519 findDelegate:function (delegate) { 520 for (var i = 0; i < this._delegateList.length; i++) { 521 if (this._delegateList[i] === delegate) 522 return i; 523 } 524 return null; 525 } 526 }); 527 528 // Initialize imeDispatcher singleton 529 cc.imeDispatcher = new cc.IMEDispatcher(); 530 531 document.body ? 532 cc.imeDispatcher.init() : 533 window.addEventListener('load', function () { 534 cc.imeDispatcher.init(); 535 }, false);