|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (developer.feedback@nokia.com) |
|
6 ** |
|
7 ** This file is part of the HbPlugins module of the UI Extensions for Mobile. |
|
8 ** |
|
9 ** GNU Lesser General Public License Usage |
|
10 ** This file may be used under the terms of the GNU Lesser General Public |
|
11 ** License version 2.1 as published by the Free Software Foundation and |
|
12 ** appearing in the file LICENSE.LGPL included in the packaging of this file. |
|
13 ** Please review the following information to ensure the GNU Lesser General |
|
14 ** Public License version 2.1 requirements will be met: |
|
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
16 ** |
|
17 ** In addition, as a special exception, Nokia gives you certain additional |
|
18 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
20 ** |
|
21 ** If you have questions regarding the use of this file, please contact |
|
22 ** Nokia at developer.feedback@nokia.com. |
|
23 ** |
|
24 ****************************************************************************/ |
|
25 #include <QTimer> |
|
26 #include <QGraphicsScene> |
|
27 |
|
28 #include <hbinputmethod.h> |
|
29 #include <hbinputkeymapfactory.h> |
|
30 #include <hbinputpredictionengine.h> |
|
31 #include <hbinputsettingproxy.h> |
|
32 #include <hbinputvkbhost.h> |
|
33 #include <hbinputdialog.h> |
|
34 #include <hbaction.h> |
|
35 #include "virtual12key.h" |
|
36 |
|
37 #include "hbinputprediction12keyhandler.h" |
|
38 #include "hbinputpredictionhandler_p.h" |
|
39 #include "hbinputabstractbase.h" |
|
40 |
|
41 #define HbDeltaHeight 3.0 |
|
42 #define MAXUDBWORDSIZE 64 |
|
43 |
|
44 class HbInputPrediction12KeyHandlerPrivate: public HbInputPredictionHandlerPrivate |
|
45 { |
|
46 Q_DECLARE_PUBLIC(HbInputPrediction12KeyHandler) |
|
47 |
|
48 public: |
|
49 HbInputPrediction12KeyHandlerPrivate(); |
|
50 ~HbInputPrediction12KeyHandlerPrivate(); |
|
51 |
|
52 bool buttonReleased(const QKeyEvent *keyEvent); |
|
53 bool buttonPressed(const QKeyEvent *keyEvent); |
|
54 void _q_timeout(); |
|
55 void launchSpellDialog(QString customWord); |
|
56 void getSpellDialogPositionAndSize(QPointF & pos,QSizeF & size,QRectF & geom); |
|
57 void cancelButtonPress(); |
|
58 public: |
|
59 int mLastKey; |
|
60 bool mButtonDown; |
|
61 QChar mCurrentChar; |
|
62 bool mLongPressHappened; |
|
63 bool mShiftKeyDoubleTap; |
|
64 }; |
|
65 |
|
66 HbInputPrediction12KeyHandlerPrivate::HbInputPrediction12KeyHandlerPrivate() |
|
67 :mLastKey(0), |
|
68 mButtonDown(false), |
|
69 mCurrentChar(0), |
|
70 mLongPressHappened(false), |
|
71 mShiftKeyDoubleTap(false) |
|
72 { |
|
73 } |
|
74 |
|
75 HbInputPrediction12KeyHandlerPrivate::~HbInputPrediction12KeyHandlerPrivate() |
|
76 { |
|
77 } |
|
78 |
|
79 void HbInputPrediction12KeyHandlerPrivate::_q_timeout() |
|
80 { |
|
81 qDebug("HbInputPrediction12KeyHandlerPrivate::_q_timeout()"); |
|
82 Q_Q(HbInputPrediction12KeyHandler); |
|
83 |
|
84 // let's stop the timer first. |
|
85 mTimer->stop(); |
|
86 |
|
87 //Long key press number key is applicable to all keys |
|
88 if (mButtonDown) { |
|
89 if (mLastKey == Qt::Key_Asterisk) { |
|
90 //Remove the "?" mark if present |
|
91 if(!mCanContinuePrediction && (*mCandidates)[mBestGuessLocation].endsWith('?')) { |
|
92 (*mCandidates)[mBestGuessLocation].chop(1); |
|
93 updateEditor(); |
|
94 mCanContinuePrediction = true; |
|
95 } |
|
96 mInputMethod->switchMode(mLastKey); |
|
97 } else if (mLastKey == Qt::Key_Shift) { |
|
98 mInputMethod->switchMode(Qt::Key_Shift); |
|
99 mLongPressHappened = true; |
|
100 } else if (mLastKey == Qt::Key_Control) { |
|
101 mInputMethod->selectSpecialCharacterTableMode(); |
|
102 } else { |
|
103 //With a long key press of a key, numbers are supposed to be entered. |
|
104 //When the existing input (along with the short key press input of the |
|
105 //existing key) results in tail (i.e. autocompletion), we need to accept |
|
106 //the short key press as well as the tail. In case of ? delete the questionmark and add the number value. |
|
107 //mTailShowing = false; |
|
108 // Delete "?" entered |
|
109 if (!mCanContinuePrediction) { |
|
110 deleteOneCharacter(); |
|
111 } |
|
112 if (mLastKey != Qt::Key_Delete) { |
|
113 q->commitFirstMappedNumber(mLastKey); |
|
114 } |
|
115 mLongPressHappened = true; |
|
116 } |
|
117 } |
|
118 } |
|
119 |
|
120 bool HbInputPrediction12KeyHandlerPrivate::buttonPressed(const QKeyEvent *keyEvent) |
|
121 { |
|
122 mLongPressHappened = false; |
|
123 HbInputFocusObject *focusObject = 0; |
|
124 focusObject = mInputMethod->focusObject(); |
|
125 if (!focusObject) { |
|
126 return false; |
|
127 } |
|
128 |
|
129 int buttonId = keyEvent->key(); |
|
130 |
|
131 |
|
132 if (buttonId == Qt::Key_Control) { |
|
133 mLastKey = buttonId; |
|
134 mButtonDown = true; |
|
135 mTimer->start(HbLongPressTimerTimeout); |
|
136 return true; |
|
137 } else if (buttonId == Qt::Key_Shift) { |
|
138 // if we get a second consequtive shift key press, |
|
139 // we want to handle it in buttonRelease |
|
140 if (mTimer->isActive() && (mLastKey == buttonId)){ |
|
141 mShiftKeyDoubleTap = true; |
|
142 } |
|
143 if (!mTimer->isActive()) { |
|
144 mTimer->start(HbLongPressTimerTimeout); |
|
145 } |
|
146 mLastKey = buttonId; |
|
147 mButtonDown = true; |
|
148 return true; |
|
149 } |
|
150 |
|
151 mLastKey = buttonId; |
|
152 mButtonDown = true; |
|
153 |
|
154 // custom button should not start timer. |
|
155 if ((buttonId & CUSTOM_INPUT_MASK) != CUSTOM_INPUT_MASK) { |
|
156 mTimer->start(HbLongPressTimerTimeout); |
|
157 } |
|
158 return false; |
|
159 } |
|
160 |
|
161 /*! |
|
162 Handles the key release events from the VKB. Launches the SCT with key release event of |
|
163 asterisk. |
|
164 */ |
|
165 bool HbInputPrediction12KeyHandlerPrivate::buttonReleased(const QKeyEvent *keyEvent) |
|
166 { |
|
167 Q_Q(HbInputPrediction12KeyHandler); |
|
168 |
|
169 if(!mButtonDown || mLongPressHappened){ |
|
170 return false; |
|
171 } |
|
172 |
|
173 // since button is released we can set buttonDown back to false. |
|
174 mButtonDown = false; |
|
175 int buttonId = keyEvent->key(); |
|
176 // it was a long press on sct swith button. so just return form here. |
|
177 if (!mTimer->isActive() && buttonId == Qt::Key_Control) { |
|
178 return true; |
|
179 } |
|
180 |
|
181 // Sym key is handled in this class it self, so not passing it to |
|
182 // the base mode handlers. |
|
183 if ( buttonId == Qt::Key_Control) { |
|
184 //Same SYM key is used for launching candidate list (long key press) |
|
185 //and also for SCT. So, do not launch SCT if candidate list is already launched. |
|
186 mInputMethod->switchMode(buttonId); |
|
187 return true; |
|
188 } |
|
189 /* Behavior of Short Press of Asterisk Key when in inline editing state |
|
190 - Should launch Candidate List if we can continue with prediction i.e. "?" is not displayed |
|
191 - Should launch Spell Query Dialog if we cannot continue with prediction |
|
192 - Behavior of Short Press of Asterisk Key when not in inline editing state |
|
193 - Should launch SCT |
|
194 */ |
|
195 else if (buttonId == Qt::Key_Asterisk ) { |
|
196 if(!mCanContinuePrediction && (*mCandidates)[mBestGuessLocation].endsWith('?')) { |
|
197 //Remove the "?" mark |
|
198 (*mCandidates)[mBestGuessLocation].chop(1); |
|
199 updateEditor(); |
|
200 q->processCustomWord((*mCandidates)[mBestGuessLocation]); |
|
201 mCanContinuePrediction = true; |
|
202 } |
|
203 else |
|
204 mInputMethod->starKeySelected(); |
|
205 return true; |
|
206 } |
|
207 else if (buttonId == Qt::Key_Return) { |
|
208 mInputMethod->closeKeypad(); |
|
209 return true; |
|
210 } |
|
211 if (buttonId == Qt::Key_Shift) { |
|
212 // single tap of shift key toggles prediction status in case insensitive languages |
|
213 if (!HbInputSettingProxy::instance()->globalInputLanguage().isCaseSensitiveLanguage()) { |
|
214 HbInputSettingProxy::instance()->togglePrediction(); |
|
215 } else { |
|
216 if (mShiftKeyDoubleTap) { |
|
217 mTimer->stop(); |
|
218 mShiftKeyDoubleTap = false; |
|
219 //mShowTail = false; |
|
220 if (HbInputSettingProxy::instance()->globalInputLanguage()== mInputMethod->inputState().language()) { |
|
221 // in latin variants , double tap of shift key toggles the prediction status |
|
222 // revert back to the old case as this is a double tap |
|
223 // (the case was changed on the single tap) |
|
224 updateTextCase(); |
|
225 HbInputSettingProxy::instance()->togglePrediction(); |
|
226 } else { |
|
227 // if the global language is different from the input mode language, we should |
|
228 // go back to the root state |
|
229 // e.g. double tap of hash/shift key is used to change |
|
230 // to chinese input mode from latin input mode |
|
231 HbInputState rootState; |
|
232 mInputMethod->editorRootState(rootState); |
|
233 mInputMethod->activateState(rootState); |
|
234 } |
|
235 } else { |
|
236 updateTextCase(); |
|
237 if( !mTimer->isActive()){ |
|
238 mTimer->start(); |
|
239 } |
|
240 } |
|
241 } |
|
242 return true; |
|
243 } |
|
244 |
|
245 // text input happens on button release |
|
246 if (q->HbInputPredictionHandler::filterEvent(keyEvent)) { |
|
247 return true; |
|
248 } |
|
249 return false; |
|
250 } |
|
251 |
|
252 void HbInputPrediction12KeyHandlerPrivate::launchSpellDialog(QString editorText) |
|
253 { |
|
254 HbInputFocusObject *focusObject = mInputMethod->focusObject(); |
|
255 if (!focusObject) { |
|
256 return; |
|
257 } |
|
258 QPointer<QObject> focusedQObject = focusObject->object(); |
|
259 // store the current focused editor |
|
260 |
|
261 HbTextCase currentTextCase = focusObject->editorInterface().textCase(); |
|
262 mEngine->clear(); |
|
263 mCanContinuePrediction = true; |
|
264 // close the keypad before showing the spell dialog |
|
265 HbVkbHost *vkbHost = focusObject->editorInterface().vkbHost(); |
|
266 if (vkbHost && vkbHost->keypadStatus() != HbVkbHost::HbVkbStatusClosed) { |
|
267 vkbHost->closeKeypad(true); |
|
268 } |
|
269 // create the spell dialog |
|
270 HbInputDialog *spellDialog = new HbInputDialog(); |
|
271 spellDialog->setInputMode(HbInputDialog::TextInput); |
|
272 spellDialog->setPromptText(""); |
|
273 spellDialog->setValue(QVariant(editorText)); |
|
274 QSizeF dialogSize = spellDialog->size(); |
|
275 QPointF dialogPos = spellDialog->pos(); |
|
276 QRectF geom = spellDialog->geometry(); |
|
277 |
|
278 //set the spell dialog position |
|
279 getSpellDialogPositionAndSize(dialogPos,dialogSize, geom); |
|
280 geom.setHeight(dialogSize.height()); |
|
281 geom.setWidth(dialogSize.width()); |
|
282 spellDialog->setGeometry(geom); |
|
283 spellDialog->setPos(dialogPos); |
|
284 |
|
285 // change the focus to spell dialog editor |
|
286 HbLineEdit *spellEdit = spellDialog->lineEdit(); |
|
287 |
|
288 if (spellEdit) { |
|
289 spellEdit->setFocus(); |
|
290 spellEdit->clearFocus(); |
|
291 spellEdit->setFocus(); |
|
292 spellEdit->setMaxLength(MAXUDBWORDSIZE); |
|
293 spellEdit->setSmileysEnabled(false); |
|
294 HbEditorInterface eInt(spellEdit); |
|
295 spellEdit->setInputMethodHints(spellEdit->inputMethodHints() | Qt::ImhNoPredictiveText); |
|
296 eInt.setTextCase(currentTextCase); |
|
297 } |
|
298 // execute the spell dialog |
|
299 HbAction *act = spellDialog->exec(); |
|
300 |
|
301 //create new focus object and set the focus back to main editor |
|
302 HbInputFocusObject *newFocusObject = new HbInputFocusObject(focusedQObject); |
|
303 |
|
304 |
|
305 newFocusObject->releaseFocus(); |
|
306 newFocusObject->setFocus(); |
|
307 |
|
308 HbAbstractEdit *abstractEdit = qobject_cast<HbAbstractEdit*>(focusedQObject); |
|
309 if(abstractEdit) { |
|
310 abstractEdit->setCursorPosition(abstractEdit->cursorPosition()); |
|
311 } |
|
312 |
|
313 mInputMethod->setFocusObject(newFocusObject); |
|
314 mInputMethod->focusObject()->editorInterface().setTextCase(currentTextCase); |
|
315 |
|
316 if (act->text() == spellDialog->primaryAction()->text()) { |
|
317 commit(spellDialog->value().toString() , true, true); |
|
318 } else if (act->text() == spellDialog->secondaryAction()->text()) { |
|
319 //update the editor with pre-edit text |
|
320 mEngine->setWord(editorText); |
|
321 bool used = false; |
|
322 mEngine->updateCandidates(mBestGuessLocation, used); |
|
323 mShowTail = false; |
|
324 updateEditor(); |
|
325 } |
|
326 delete spellDialog; |
|
327 } |
|
328 |
|
329 void HbInputPrediction12KeyHandlerPrivate::getSpellDialogPositionAndSize(QPointF & pos,QSizeF & size, QRectF &geom) |
|
330 { |
|
331 QRectF cursorRect = mInputMethod->focusObject()->microFocus(); // from the top of the screen |
|
332 pos = QPointF(cursorRect.bottomLeft().x(),cursorRect.bottomLeft().y()); |
|
333 qreal heightOfTitlebar = 80.0; // Using magic number for now... |
|
334 qreal screenHeight = (qreal)HbDeviceProfile::current().logicalSize().height(); |
|
335 if( ((screenHeight - cursorRect.bottomLeft().y()) > (cursorRect.y() - heightOfTitlebar)) |
|
336 || ((screenHeight - cursorRect.bottomLeft().y() + HbDeltaHeight ) > geom.height()) ) { |
|
337 // this means there is amore space below inline text than at the top or we can fit spell Dialog |
|
338 // below inline text |
|
339 pos.setY(cursorRect.bottomLeft().y() + HbDeltaHeight); |
|
340 size.setHeight(screenHeight - pos.y()); |
|
341 } else { |
|
342 // this means there is amore space above inline text than below it |
|
343 pos.setY(cursorRect.y() - geom.height() - HbDeltaHeight); |
|
344 if (pos.y() < heightOfTitlebar) { |
|
345 // this means that spell dialog can not be fit in from top of inline text, we need to trim it |
|
346 pos.setY(heightOfTitlebar); |
|
347 } |
|
348 size.setHeight(cursorRect.y() - heightOfTitlebar - HbDeltaHeight); |
|
349 } |
|
350 if ( size.height() > geom.height()) { |
|
351 size.setHeight(geom.height()); |
|
352 } |
|
353 if ((pos.x() + size.width()) > (qreal)HbDeviceProfile::current().logicalSize().width()) { |
|
354 // can not fit spell dialog to the right side of inline edit text. |
|
355 pos.setX((qreal)HbDeviceProfile::current().logicalSize().width()- size.width()); |
|
356 } |
|
357 } |
|
358 |
|
359 void HbInputPrediction12KeyHandlerPrivate::cancelButtonPress() |
|
360 { |
|
361 mTimer->stop(); |
|
362 mButtonDown = false; |
|
363 mLastKey = 0; |
|
364 mShiftKeyDoubleTap = false; |
|
365 } |
|
366 |
|
367 HbInputPrediction12KeyHandler::HbInputPrediction12KeyHandler(HbInputAbstractMethod *inputMethod) |
|
368 :HbInputPredictionHandler(* new HbInputPrediction12KeyHandlerPrivate, inputMethod) |
|
369 { |
|
370 Q_D(HbInputPrediction12KeyHandler); |
|
371 d->q_ptr = this; |
|
372 } |
|
373 |
|
374 HbInputPrediction12KeyHandler::~HbInputPrediction12KeyHandler() |
|
375 { |
|
376 } |
|
377 |
|
378 /*! |
|
379 this function lists different modes. |
|
380 */ |
|
381 void HbInputPrediction12KeyHandler::listInputModes(QVector<HbInputModeProperties>& modes) const |
|
382 { |
|
383 Q_UNUSED(modes); |
|
384 } |
|
385 |
|
386 /*! |
|
387 filterEvent to handler keypress/release events. |
|
388 */ |
|
389 bool HbInputPrediction12KeyHandler::filterEvent(const QKeyEvent * event) |
|
390 { |
|
391 Q_D(HbInputPrediction12KeyHandler); |
|
392 HbInputFocusObject *focusObject = 0; |
|
393 focusObject = d->mInputMethod->focusObject(); |
|
394 |
|
395 //If there was a handling for empty candidate-list, i.e. the engine did not predict |
|
396 //any meaningful word for the input sequence. |
|
397 if(!d->mCanContinuePrediction) { |
|
398 int eventKey = event->key(); |
|
399 switch(eventKey) { |
|
400 case Qt::Key_0: |
|
401 case Qt::Key_Space: { |
|
402 if(d->mCandidates->size() && focusObject) { |
|
403 //Remove the "?" mark |
|
404 (*d->mCandidates)[d->mBestGuessLocation].chop(1); |
|
405 d->updateEditor(); |
|
406 d->mCanContinuePrediction = true; |
|
407 } |
|
408 } |
|
409 break; |
|
410 case Qt::Key_Shift: { |
|
411 if(event->type() == QEvent::KeyRelease && d->mShiftKeyDoubleTap) { |
|
412 //Remove the "?" mark |
|
413 deleteOneCharacter(); |
|
414 } |
|
415 } |
|
416 //For the following set of keys, it does not matter. |
|
417 case Qt::Key_Backspace: |
|
418 case Qt::Key_Delete: |
|
419 case Qt::Key_Return: |
|
420 case Qt::Key_Enter: |
|
421 case Qt::Key_Asterisk: |
|
422 break; |
|
423 /* Behavior for other keys i.e. from key1 to key9 - |
|
424 To start the long press timer as we need to handle long press functionality i.e Enter corresponding number mapped to a key */ |
|
425 case Qt::Key_1: |
|
426 case Qt::Key_2: |
|
427 case Qt::Key_3: |
|
428 case Qt::Key_4: |
|
429 case Qt::Key_5: |
|
430 case Qt::Key_6: |
|
431 case Qt::Key_7: |
|
432 case Qt::Key_8: |
|
433 case Qt::Key_9: { |
|
434 if (event->type() == QEvent::KeyRelease) { |
|
435 d->mButtonDown = false; |
|
436 } else { |
|
437 d->mButtonDown = true; |
|
438 d->mLastKey = event->key(); |
|
439 // start Long Press timer as corresponding number mapped to a key should be allowed to enter |
|
440 d->mTimer->start(HbLongPressTimerTimeout); |
|
441 } |
|
442 return true; |
|
443 } |
|
444 //The default behavior for any other key press is just to consume the key event and |
|
445 //not to do anything. |
|
446 default: { |
|
447 return true; |
|
448 } |
|
449 } |
|
450 } |
|
451 |
|
452 // prediction mode can't handle Qt::Key_0, so we will emit a passFilterEvent |
|
453 // this signal must be connected to by the plugin to a modehandler. |
|
454 // which can handle it. |
|
455 if (event->key() == Qt::Key_0) { |
|
456 if (d->mLastKey != Qt::Key_0) { |
|
457 actionHandler(HbInputModeHandler::HbInputModeActionCommit); |
|
458 } |
|
459 emit passFilterEvent(event); |
|
460 d->mLastKey = Qt::Key_0; |
|
461 return true; |
|
462 } else { |
|
463 if (d->mLastKey == Qt::Key_0) { |
|
464 emit passActionHandler(HbInputModeActionCommit); |
|
465 } |
|
466 if (event->type() == QEvent::KeyRelease) { |
|
467 return d->buttonReleased(event); |
|
468 } else { |
|
469 return d->buttonPressed(event); |
|
470 } |
|
471 } |
|
472 } |
|
473 |
|
474 /*! |
|
475 Action handler |
|
476 */ |
|
477 bool HbInputPrediction12KeyHandler::actionHandler(HbInputModeAction action) |
|
478 { |
|
479 Q_D(HbInputPrediction12KeyHandler); |
|
480 bool ret = true; |
|
481 switch (action) { |
|
482 case HbInputModeActionReset: |
|
483 HbInputPredictionHandler::actionHandler(HbInputModeActionReset); |
|
484 d->mTimer->stop(); |
|
485 break; |
|
486 case HbInputModeActionCancelButtonPress: |
|
487 d->cancelButtonPress(); |
|
488 break; |
|
489 case HbInputModeActionFocusRecieved: |
|
490 HbInputPredictionHandler::actionHandler(HbInputModeActionSetCandidateList); |
|
491 HbInputPredictionHandler::actionHandler(HbInputModeActionSetKeypad); |
|
492 d->mTimer->stop(); |
|
493 break; |
|
494 default: |
|
495 ret = HbInputPredictionHandler::actionHandler(action); |
|
496 break; |
|
497 } |
|
498 |
|
499 return ret; |
|
500 } |
|
501 |
|
502 void HbInputPrediction12KeyHandler::mouseHandler(int cursorPosition, QMouseEvent* mouseEvent) |
|
503 { |
|
504 Q_D(HbInputPrediction12KeyHandler); |
|
505 if(d->mLastKey == Qt::Key_0) { |
|
506 // zero key handling is done in the basic handler |
|
507 emit passActionHandler(HbInputModeActionCommit); |
|
508 } else { |
|
509 HbInputPredictionHandler::mouseHandler(cursorPosition,mouseEvent); |
|
510 } |
|
511 } |
|
512 |
|
513 |
|
514 /*! |
|
515 Returns true if preidciton engine is available and initialized. |
|
516 */ |
|
517 bool HbInputPrediction12KeyHandler::isActive() const |
|
518 { |
|
519 Q_D(const HbInputPrediction12KeyHandler); |
|
520 return d->mEngine != 0; |
|
521 } |
|
522 |
|
523 void HbInputPrediction12KeyHandler::processCustomWord(QString customWord) |
|
524 { |
|
525 Q_D(HbInputPrediction12KeyHandler); |
|
526 if (customWord.size()) { |
|
527 d->launchSpellDialog(customWord); |
|
528 } |
|
529 return; |
|
530 } |
|
531 //EOF |