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