CCIMEDispatcher.js 16 KB

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