1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file. Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #ifndef QT_NO_IM
43
44 #include "qcoefepinputcontext_p.h"
45 #include <qapplication.h>
46 #include <qtextformat.h>
47 #include <qgraphicsview.h>
48 #include <qgraphicsscene.h>
49 #include <qgraphicswidget.h>
50 #include <qsymbianevent.h>
51 #include <qlayout.h>
52 #include <qdesktopwidget.h>
53 #include <private/qcore_symbian_p.h>
54
55 #include <fepitfr.h>
56 #include <hal.h>
57 #include <e32property.h>
58
59 #include <limits.h>
60
61 #include <eikccpu.h>
62 #include <aknedsts.h>
63 #include <coeinput.h>
64 #include <w32std.h>
65 #include <akndiscreetpopup.h>
66 #include <aknextendedinputcapabilities.h>
67
68 #include <qtextedit.h>
69 #include <qplaintextedit.h>
70 #include <qlineedit.h>
71 #include <qclipboard.h>
72 #include <qvalidator.h>
73 #include <qgraphicsproxywidget.h>
74 #include <qgraphicsitem.h>
75
76 // You only find these enumerations on SDK 5 onwards, so we need to provide our own
77 // to remain compatible with older releases. They won't be called by pre-5.0 SDKs.
78
79 // MAknEdStateObserver::EAknCursorPositionChanged
80 #define QT_EAknCursorPositionChanged MAknEdStateObserver::EAknEdwinStateEvent(6)
81 // MAknEdStateObserver::EAknActivatePenInputRequest
82 #define QT_EAknActivatePenInputRequest MAknEdStateObserver::EAknEdwinStateEvent(7)
83 // MAknEdStateObserver::EAknClosePenInputRequest
84 #define QT_EAknClosePenInputRequest MAknEdStateObserver::EAknEdwinStateEvent(10)
85
86 // EAknEditorFlagSelectionVisible is only valid from 3.2 onwards.
87 // Sym^3 AVKON FEP manager expects that this flag is used for FEP-aware editors
88 // that support text selection.
89 #define QT_EAknEditorFlagSelectionVisible 0x100000
90
91 // EAknEditorFlagEnablePartialScreen is only valid from Sym^3 onwards.
92 #define QT_EAknEditorFlagEnablePartialScreen 0x200000
93
94 // Properties to detect VKB status from AknFepInternalPSKeys.h
95 #define QT_EPSUidAknFep 0x100056de
96 #define QT_EAknFepTouchInputActive 0x00000004
97
98 // For compatibility with older Symbian^3 environments, which do not have this define yet.
99 #ifndef R_AVKON_DISCREET_POPUP_TEXT_COPIED
100 #define R_AVKON_DISCREET_POPUP_TEXT_COPIED 0x8cc0227
101 #endif
102
103 _LIT(KAvkonResourceFile, "z:\\resource\\avkon.rsc" );
104
105 // Vietnamese tone tables
106 const int VietToneMarks = 5;
107
108 const QChar VietToneList[VietToneMarks] = {
109 0x0301, 0x0300, 0x0309, 0x0303, 0x0323
110 };
111
112 const QChar VietVowelList[] = {
113 0x0041, 0x0061, 0x0102, 0x0103, 0x00c2,
114 0x00e2, 0x0045, 0x0065, 0x00ca, 0x00ea,
115 0x0049, 0x0069, 0x004f, 0x006f, 0x00d4,
116 0x00f4, 0x01a0, 0x01a1, 0x0055, 0x0075,
117 0x01af, 0x01b0, 0x0059, 0x0079
118 };
119
120 const QChar VietToneMatrix[][VietToneMarks] = {
121 // Matrix for each vowel (row) after applying certain tone mark (column)
122 0x00c1, 0x00c0, 0x1ea2, 0x00c3, 0x1ea0,
123 0x00e1, 0x00e0, 0x1ea3, 0x00e3, 0x1ea1,
124 0x1eae, 0x1eb0, 0x1eb2, 0x1eb4, 0x1eb6,
125 0x1eaf, 0x1eb1, 0x1eb3, 0x1eb5, 0x1eb7,
126 0x1ea4, 0x1ea6, 0x1ea8, 0x1eaa, 0x1eac,
127 0x1ea5, 0x1ea7, 0x1ea9, 0x1eab, 0x1ead,
128 0x00c9, 0x00c8, 0x1eba, 0x1ebc, 0x1eb8,
129 0x00e9, 0x00e8, 0x1ebb, 0x1ebd, 0x1eb9,
130 0x1ebe, 0x1ec0, 0x1ec2, 0x1ec4, 0x1ec6,
131 0x1ebf, 0x1ec1, 0x1ec3, 0x1ec5, 0x1ec7,
132 0x00cd, 0x00cc, 0x1ec8, 0x0128, 0x1eca,
133 0x00ed, 0x00ec, 0x1ec9, 0x0129, 0x1ecb,
134 0x00d3, 0x00d2, 0x1ece, 0x00d5, 0x1ecc,
135 0x00f3, 0x00f2, 0x1ecf, 0x00f5, 0x1ecd,
136 0x1ed0, 0x1ed2, 0x1ed4, 0x1ed6, 0x1ed8,
137 0x1ed1, 0x1ed3, 0x1ed5, 0x1ed7, 0x1ed9,
138 0x1eda, 0x1edc, 0x1ede, 0x1ee0, 0x1ee2,
139 0x1edb, 0x1edd, 0x1edf, 0x1ee1, 0x1ee3,
140 0x00da, 0x00d9, 0x1ee6, 0x0168, 0x1ee4,
141 0x00fa, 0x00f9, 0x1ee7, 0x0169, 0x1ee5,
142 0x1ee8, 0x1eea, 0x1eec, 0x1eee, 0x1ef0,
143 0x1ee9, 0x1eeb, 0x1eed, 0x1eef, 0x1ef1,
144 0x00dd, 0x1ef2, 0x1ef6, 0x1ef8, 0x1ef4,
145 0x00fd, 0x1ef3, 0x1ef7, 0x1ef9, 0x1ef5
146 };
147
148 QT_BEGIN_NAMESPACE
149
getFocusedChild(const QList<QObject * > & objectList)150 static QWidget* getFocusedChild(const QList<QObject*>& objectList)
151 {
152 for (int j = 0; j < objectList.count(); j++) {
153 if (QWidget* ow = qobject_cast<QWidget *>(objectList[j])) {
154 if (ow->hasFocus()) {
155 return ow;
156 } else {
157 if (QWidget* rw = getFocusedChild(ow->children()))
158 return rw;
159 }
160 }
161 }
162 return 0;
163 }
164
165 // A generic method for invoking "cut", "copy", and "paste" slots on editor
166 // All supported editors are expected to have these.
ccpuInvokeSlot(QObject * obj,QObject * focusObject,const char * member)167 static bool ccpuInvokeSlot(QObject *obj, QObject *focusObject, const char *member)
168 {
169 QObject *invokeTarget = obj;
170 if (focusObject)
171 invokeTarget = focusObject;
172
173 return QMetaObject::invokeMethod(invokeTarget, member, Qt::DirectConnection);
174 }
175
176 // focusObject is used to return a pointer to focused graphics object, if any
getQWidgetFromQGraphicsView(QWidget * widget,QObject ** focusObject=0)177 static QWidget *getQWidgetFromQGraphicsView(QWidget *widget, QObject **focusObject = 0)
178 {
179 if (focusObject)
180 *focusObject = 0;
181
182 if (!widget)
183 return 0;
184
185 if (QGraphicsView* qgv = qobject_cast<QGraphicsView *>(widget)) {
186 QGraphicsItem *focusItem = 0;
187 if (qgv->scene())
188 focusItem = qgv->scene()->focusItem();
189 if (focusItem) {
190 if (focusObject)
191 *focusObject = focusItem->toGraphicsObject();
192 if (QGraphicsProxyWidget* const qgpw = qgraphicsitem_cast<QGraphicsProxyWidget* const>(focusItem)) {
193 if (QWidget* w = qgpw->widget()) {
194 if (w->layout()) {
195 if (QWidget* rw = getFocusedChild(w->children()))
196 return rw;
197 } else {
198 return w;
199 }
200 }
201 }
202 }
203 }
204 return widget;
205 }
206
QCoeFepInputMaskHandler(const QString & mask)207 QCoeFepInputMaskHandler::QCoeFepInputMaskHandler(const QString &mask)
208 {
209 QString inputMask;
210 int delimiter = mask.indexOf(QLatin1Char(';'));
211 if (mask.isEmpty() || delimiter == 0)
212 return;
213
214 if (delimiter == -1) {
215 m_blank = QLatin1Char(' ');
216 inputMask = mask;
217 } else {
218 inputMask = mask.left(delimiter);
219 m_blank = (delimiter + 1 < mask.length()) ? mask[delimiter + 1] : QLatin1Char(' ');
220 }
221
222 // Calculate m_maxLength / m_maskData length
223 m_maxLength = 0;
224 QChar c = 0;
225 for (int i = 0; i < inputMask.length(); i++) {
226 c = inputMask.at(i);
227 if (i > 0 && inputMask.at(i - 1) == QLatin1Char('\\')) {
228 m_maxLength++;
229 continue;
230 }
231 if (c != QLatin1Char('\\') && c != QLatin1Char('!')
232 && c != QLatin1Char('<') && c != QLatin1Char('>')
233 && c != QLatin1Char('{') && c != QLatin1Char('}')
234 && c != QLatin1Char('[') && c != QLatin1Char(']')) {
235 m_maxLength++;
236 }
237 }
238
239 m_maskData = new MaskInputData[m_maxLength];
240
241 MaskInputData::Casemode m = MaskInputData::NoCaseMode;
242 c = 0;
243 bool s = false;
244 bool escape = false;
245 int index = 0;
246 for (int i = 0; i < inputMask.length(); i++) {
247 c = inputMask.at(i);
248 if (escape) {
249 s = true;
250 m_maskData[index].maskChar = c;
251 m_maskData[index].separator = s;
252 m_maskData[index].caseMode = m;
253 index++;
254 escape = false;
255 } else if (c == QLatin1Char('<')) {
256 m = MaskInputData::Lower;
257 } else if (c == QLatin1Char('>')) {
258 m = MaskInputData::Upper;
259 } else if (c == QLatin1Char('!')) {
260 m = MaskInputData::NoCaseMode;
261 } else if (c != QLatin1Char('{') && c != QLatin1Char('}') && c != QLatin1Char('[') && c != QLatin1Char(']')) {
262 switch (c.unicode()) {
263 case 'A':
264 case 'a':
265 case 'N':
266 case 'n':
267 case 'X':
268 case 'x':
269 case '9':
270 case '0':
271 case 'D':
272 case 'd':
273 case '#':
274 case 'H':
275 case 'h':
276 case 'B':
277 case 'b':
278 s = false;
279 break;
280 case '\\':
281 escape = true;
282 break;
283 default:
284 s = true;
285 break;
286 }
287
288 if (!escape) {
289 m_maskData[index].maskChar = c;
290 m_maskData[index].separator = s;
291 m_maskData[index].caseMode = m;
292 index++;
293 }
294 }
295 }
296 }
297
~QCoeFepInputMaskHandler()298 QCoeFepInputMaskHandler::~QCoeFepInputMaskHandler()
299 {
300 if (m_maskData)
301 delete[] m_maskData;
302 }
303
canPasteClipboard(const QString & text)304 bool QCoeFepInputMaskHandler::canPasteClipboard(const QString &text)
305 {
306 if (!m_maskData)
307 return true;
308
309 if (text.length() > m_maxLength)
310 return false;
311 int limit = qMin(m_maxLength, text.length());
312 for (int i = 0; i < limit; ++i) {
313 if (m_maskData[i].separator) {
314 if (text.at(i) != m_maskData[i].maskChar)
315 return false;
316 } else {
317 if (!isValidInput(text.at(i), m_maskData[i].maskChar))
318 return false;
319 }
320 }
321 return true;
322 }
323
isValidInput(QChar key,QChar mask) const324 bool QCoeFepInputMaskHandler::isValidInput(QChar key, QChar mask) const
325 {
326 switch (mask.unicode()) {
327 case 'A':
328 if (key.isLetter())
329 return true;
330 break;
331 case 'a':
332 if (key.isLetter() || key == m_blank)
333 return true;
334 break;
335 case 'N':
336 if (key.isLetterOrNumber())
337 return true;
338 break;
339 case 'n':
340 if (key.isLetterOrNumber() || key == m_blank)
341 return true;
342 break;
343 case 'X':
344 if (key.isPrint())
345 return true;
346 break;
347 case 'x':
348 if (key.isPrint() || key == m_blank)
349 return true;
350 break;
351 case '9':
352 if (key.isNumber())
353 return true;
354 break;
355 case '0':
356 if (key.isNumber() || key == m_blank)
357 return true;
358 break;
359 case 'D':
360 if (key.isNumber() && key.digitValue() > 0)
361 return true;
362 break;
363 case 'd':
364 if ((key.isNumber() && key.digitValue() > 0) || key == m_blank)
365 return true;
366 break;
367 case '#':
368 if (key.isNumber() || key == QLatin1Char('+') || key == QLatin1Char('-') || key == m_blank)
369 return true;
370 break;
371 case 'B':
372 if (key == QLatin1Char('0') || key == QLatin1Char('1'))
373 return true;
374 break;
375 case 'b':
376 if (key == QLatin1Char('0') || key == QLatin1Char('1') || key == m_blank)
377 return true;
378 break;
379 case 'H':
380 if (key.isNumber() || (key >= QLatin1Char('a') && key <= QLatin1Char('f')) || (key >= QLatin1Char('A') && key <= QLatin1Char('F')))
381 return true;
382 break;
383 case 'h':
384 if (key.isNumber() || (key >= QLatin1Char('a') && key <= QLatin1Char('f')) || (key >= QLatin1Char('A') && key <= QLatin1Char('F')) || key == m_blank)
385 return true;
386 break;
387 default:
388 break;
389 }
390 return false;
391 }
392
qt_s60_setPartialScreenInputMode(bool enable)393 Q_GUI_EXPORT void qt_s60_setPartialScreenInputMode(bool enable)
394 {
395 S60->partial_keyboard = enable;
396
397 QApplication::setAttribute(Qt::AA_S60DisablePartialScreenInputMode, !S60->partial_keyboard);
398
399 QInputContext *ic = 0;
400 if (QApplication::focusWidget()) {
401 ic = QApplication::focusWidget()->inputContext();
402 } else if (qApp && qApp->inputContext()) {
403 ic = qApp->inputContext();
404 }
405 if (ic)
406 ic->update();
407 }
408
qt_s60_setPartialScreenAutomaticTranslation(bool enable)409 Q_GUI_EXPORT void qt_s60_setPartialScreenAutomaticTranslation(bool enable)
410 {
411 S60->partial_keyboardAutoTranslation = enable;
412 }
413
qt_s60_setEditorFlags(int flags)414 Q_GUI_EXPORT void qt_s60_setEditorFlags(int flags)
415 {
416 S60->editorFlags |= flags;
417 }
418
QCoeFepInputContext(QObject * parent)419 QCoeFepInputContext::QCoeFepInputContext(QObject *parent)
420 : QInputContext(parent),
421 m_fepState(q_check_ptr(new CAknEdwinState)), // CBase derived object needs check on new
422 m_lastImHints(Qt::ImhNone),
423 m_textCapabilities(TCoeInputCapabilities::EAllText),
424 m_inDestruction(false),
425 m_pendingInputCapabilitiesChanged(false),
426 m_pendingTransactionCancel(false),
427 m_cursorVisibility(1),
428 m_inlinePosition(0),
429 m_formatRetriever(0),
430 m_pointerHandler(0),
431 m_hasTempPreeditString(false),
432 m_cachedCursorAndAnchorPosition(-1),
433 m_splitViewResizeBy(0),
434 m_splitViewPreviousWindowStates(Qt::WindowNoState),
435 m_splitViewPreviousFocusItem(0),
436 m_ccpu(0),
437 m_extendedInputCapabilities(0),
438 m_formAccessor(0),
439 m_dummyEditor(0)
440 {
441 m_fepState->SetObjectProvider(this);
442 int defaultFlags = EAknEditorFlagDefault;
443 if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0) {
444 if (isPartialKeyboardSupported()) {
445 defaultFlags |= QT_EAknEditorFlagEnablePartialScreen;
446 }
447 defaultFlags |= QT_EAknEditorFlagSelectionVisible;
448 }
449 m_fepState->SetFlags(defaultFlags);
450 m_fepState->SetDefaultInputMode( EAknEditorTextInputMode );
451 m_fepState->SetPermittedInputModes( EAknEditorAllInputModes );
452 m_fepState->SetDefaultCase( EAknEditorTextCase );
453 m_fepState->SetPermittedCases( EAknEditorAllCaseModes );
454 m_fepState->SetSpecialCharacterTableResourceId(R_AVKON_SPECIAL_CHARACTER_TABLE_DIALOG);
455 m_fepState->SetNumericKeymap(EAknEditorAlphanumericNumberModeKeymap);
456 enableSymbianCcpuSupport();
457
458 //adding softkeys
459 QString copyLabel = QLatin1String("Copy");
460 QString pasteLabel = QLatin1String("Paste");
461 TRAP_IGNORE(
462 CEikonEnv* coe = CEikonEnv::Static();
463 if (coe) {
464 HBufC* copyBuf = coe->AllocReadResourceLC(R_TEXT_SOFTKEY_COPY);
465 copyLabel = qt_TDesC2QString(*copyBuf);
466 CleanupStack::PopAndDestroy(copyBuf);
467 HBufC* pasteBuf = coe->AllocReadResourceLC(R_TEXT_SOFTKEY_PASTE);
468 pasteLabel = qt_TDesC2QString(*pasteBuf);
469 CleanupStack::PopAndDestroy(pasteBuf);
470 }
471
472 m_extendedInputCapabilities = CAknExtendedInputCapabilities::NewL();
473 )
474
475 m_copyAction = new QAction(copyLabel, QApplication::desktop());
476 m_pasteAction = new QAction(pasteLabel, QApplication::desktop());
477 m_copyAction->setSoftKeyRole(QAction::PositiveSoftKey);
478 m_pasteAction->setSoftKeyRole(QAction::NegativeSoftKey);
479 connect(m_copyAction, SIGNAL(triggered()), this, SLOT(copy()));
480 connect(m_pasteAction, SIGNAL(triggered()), this, SLOT(paste()));
481
482 // Use dummy editor to enable smiley support by default
483 m_dummyEditor.reset(new CEikEdwin());
484 TRAPD(err, m_dummyEditor->ConstructL(CEikEdwin::EAvkonEnableSmileySupport));
485 if (!err) {
486 m_formAccessor.reset(new CAknEdwinFormAccessor(m_dummyEditor.data()));
487 m_fepState->SetFormAccessor(m_formAccessor.data());
488 }
489 }
490
~QCoeFepInputContext()491 QCoeFepInputContext::~QCoeFepInputContext()
492 {
493 m_inDestruction = true;
494
495 // This is to make sure that the FEP manager "forgets" about us,
496 // otherwise we may get callbacks even after we're destroyed.
497 // The call below is essentially equivalent to InputCapabilitiesChanged(),
498 // but is synchronous, rather than asynchronous.
499 CCoeEnv::Static()->SyncNotifyFocusObserversOfChangeInFocus();
500
501 delete m_fepState;
502 delete m_ccpu;
503 delete m_extendedInputCapabilities;
504 }
505
reset()506 void QCoeFepInputContext::reset()
507 {
508 Qt::InputMethodHints currentHints = Qt::ImhNone;
509 if (focusWidget()) {
510 QWidget *proxy = focusWidget()->focusProxy();
511 currentHints = proxy ? proxy->inputMethodHints() : focusWidget()->inputMethodHints();
512 }
513 // Store a copy of preedit text, if prediction is active and input context is reseted.
514 // This is to ensure that we can replace preedit string after losing focus to FEP manager's
515 // internal sub-windows. Additionally, store the cursor position if there is no selected text.
516 // This allows input context to replace preedit strings if they are not at the end of current
517 // text.
518 if (m_cachedPreeditString.isEmpty() && !(currentHints & Qt::ImhNoPredictiveText)) {
519 m_cachedPreeditString = m_preeditString;
520 if (focusWidget() && !m_cachedPreeditString.isEmpty()) {
521 int cursor = focusWidget()->inputMethodQuery(Qt::ImCursorPosition).toInt();
522 int anchor = focusWidget()->inputMethodQuery(Qt::ImAnchorPosition).toInt();
523 if (cursor == anchor)
524 m_cachedCursorAndAnchorPosition = cursor;
525 }
526 }
527 commitCurrentString(true);
528
529 // QGraphicsScene calls reset() when changing focus item. Unfortunately, the new focus item is
530 // set right after resetting the input context. Therefore, asynchronously call ensureWidgetVisibility().
531 if (S60->splitViewLastWidget)
532 QMetaObject::invokeMethod(this,"ensureWidgetVisibility", Qt::QueuedConnection);
533 }
534
ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateEvent aEventType)535 void QCoeFepInputContext::ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateEvent aEventType)
536 {
537 QT_TRAP_THROWING(m_fepState->ReportAknEdStateEventL(aEventType));
538 }
539
update()540 void QCoeFepInputContext::update()
541 {
542 updateHints(false);
543
544 // For pre-5.0 SDKs, we don't do text updates on S60 side.
545 if (QSysInfo::s60Version() < QSysInfo::SV_S60_5_0) {
546 return;
547 }
548
549 // Don't be fooled (as I was) by the name of this enumeration.
550 // What it really does is tell the virtual keyboard UI that the text has been
551 // updated and it should be reflected in the internal display of the VK.
552 ReportAknEdStateEvent(QT_EAknCursorPositionChanged);
553 }
554
setFocusWidget(QWidget * w)555 void QCoeFepInputContext::setFocusWidget(QWidget *w)
556 {
557 commitCurrentString(true);
558
559 QInputContext::setFocusWidget(w);
560
561 updateHints(true);
562 if (w) {
563 // Store last focused widget and object. Needed when Menu is Opened
564 QObject *focusObject = 0;
565 m_lastFocusedEditor = getQWidgetFromQGraphicsView(focusWidget(),
566 &focusObject);
567 m_lastFocusedObject = focusObject; // Can be null
568 Q_ASSERT(m_lastFocusedEditor);
569 }
570 }
571
widgetDestroyed(QWidget * w)572 void QCoeFepInputContext::widgetDestroyed(QWidget *w)
573 {
574 m_cachedPreeditString.clear();
575 m_cachedCursorAndAnchorPosition = -1;
576
577 // Make sure that the input capabilities of whatever new widget got focused are queried.
578 CCoeControl *ctrl = w->effectiveWinId();
579 if (ctrl->IsFocused()) {
580 queueInputCapabilitiesChanged();
581 }
582 }
583
language()584 QString QCoeFepInputContext::language()
585 {
586 TLanguage lang = m_fepState->LocalLanguage();
587 const QByteArray localeName = qt_symbianLocaleName(lang);
588 if (!localeName.isEmpty()) {
589 return QString::fromLatin1(localeName);
590 } else {
591 return QString::fromLatin1("C");
592 }
593 }
594
needsInputPanel()595 bool QCoeFepInputContext::needsInputPanel()
596 {
597 switch (QSysInfo::s60Version()) {
598 case QSysInfo::SV_S60_3_1:
599 case QSysInfo::SV_S60_3_2:
600 // There are no touch phones for pre-5.0 SDKs.
601 return false;
602 #ifdef Q_CC_NOKIAX86
603 default:
604 // For emulator we assume that we need an input panel, since we can't
605 // separate between phone types.
606 return true;
607 #else
608 case QSysInfo::SV_S60_5_0: {
609 // For SDK == 5.0, we need phone specific detection, since the HAL API
610 // is no good on most phones. However, all phones at the time of writing use the
611 // input panel, except N97 in landscape mode, but in this mode it refuses to bring
612 // up the panel anyway, so we don't have to care.
613 return true;
614 }
615 default:
616 // For unknown/newer types, we try to use the HAL API.
617 int keyboardEnabled;
618 int keyboardType;
619 int err[2];
620 err[0] = HAL::Get(HAL::EKeyboard, keyboardType);
621 err[1] = HAL::Get(HAL::EKeyboardState, keyboardEnabled);
622 if (err[0] == KErrNone && err[1] == KErrNone
623 && keyboardType != 0 && keyboardEnabled)
624 // Means that we have some sort of keyboard.
625 return false;
626
627 // Fall back to using the input panel.
628 return true;
629 #endif // !Q_CC_NOKIAX86
630 }
631 }
632
vietCharConversion(const QEvent * event)633 bool QCoeFepInputContext::vietCharConversion(const QEvent *event)
634 {
635 const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
636 const uint VietVowelListCount = sizeof(VietVowelList)/sizeof(VietVowelList[0]);
637 for (int tone = 0; tone < VietToneMarks; tone++) {
638 if (keyEvent->key() == VietToneList[tone]) {
639 // Vietnamese vowel tone mark pressed, check previous character
640 const int cursor = focusWidget()->inputMethodQuery(Qt::ImCursorPosition).toInt();
641 if (cursor > 0) {
642 QString widgetText = focusWidget()->inputMethodQuery(Qt::ImSurroundingText).toString();
643 for (int vowel = 0; vowel < VietVowelListCount; vowel++) {
644 if (widgetText[cursor-1].unicode() == VietVowelList[vowel]) {
645 // Previous character is Vietnamese vowel, replace it from matrix
646 QList<QInputMethodEvent::Attribute> attributes;
647 if (event->type() == QEvent::KeyPress) {
648 QInputMethodEvent imEvent(QString(VietToneMatrix[vowel][tone]), attributes);
649 sendEvent(imEvent);
650 } else {
651 // event->type() == QEvent::KeyRelease
652 QInputMethodEvent imEvent(QLatin1String(""), attributes);
653 imEvent.setCommitString(QString(VietToneMatrix[vowel][tone]), -1, 1);
654 sendEvent(imEvent);
655 }
656 return true;
657 }
658 }
659 }
660 return false;
661 }
662 }
663 if (keyEvent->key() == Qt::Key_Backspace) {
664 // Backspace pressed, check previous character
665 const int cursor = focusWidget()->inputMethodQuery(Qt::ImCursorPosition).toInt();
666 if (cursor > 0) {
667 QString widgetText = focusWidget()->inputMethodQuery(Qt::ImSurroundingText).toString();
668 for (int vowel = 0; vowel < VietVowelListCount; vowel++) {
669 for (int tone = 0; tone < VietToneMarks; tone++) {
670 if (widgetText[cursor-1].unicode() == VietToneMatrix[vowel][tone]) {
671 // Previous character is Vietnamese vowel with tone, replace it with plain vowel
672 QList<QInputMethodEvent::Attribute> attributes;
673 if (event->type() == QEvent::KeyPress) {
674 QInputMethodEvent imEvent(QString(VietVowelList[vowel]), attributes);
675 sendEvent(imEvent);
676 } else {
677 // event->type() == QEvent::KeyRelease
678 QInputMethodEvent imEvent(QLatin1String(""), attributes);
679 imEvent.setCommitString(QString(VietVowelList[vowel]), -1, 1);
680 sendEvent(imEvent);
681 }
682 return true;
683 }
684 }
685 }
686 }
687 }
688 return false;
689 }
690
filterEvent(const QEvent * event)691 bool QCoeFepInputContext::filterEvent(const QEvent *event)
692 {
693 if (!focusWidget())
694 return false;
695
696 if ((event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) &&
697 QApplication::keyboardInputLocale().language() == QLocale::Vietnamese) {
698 // Vietnamese character conversions
699 if (vietCharConversion(event))
700 return true;
701 }
702
703 switch (event->type()) {
704 case QEvent::KeyPress:
705 commitTemporaryPreeditString();
706 // fall through intended
707 case QEvent::KeyRelease:
708 const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event);
709 //If proxy exists, always use hints from proxy.
710 QWidget *proxy = focusWidget()->focusProxy();
711 Qt::InputMethodHints currentHints = proxy ? proxy->inputMethodHints() : focusWidget()->inputMethodHints();
712
713 switch (keyEvent->key()) {
714 case Qt::Key_F20:
715 Q_ASSERT(m_lastImHints == currentHints);
716 if (m_lastImHints & Qt::ImhHiddenText) {
717 // Special case in Symbian. On editors with secret text, F20 is for some reason
718 // considered to be a backspace.
719 QKeyEvent modifiedEvent(keyEvent->type(), Qt::Key_Backspace, keyEvent->modifiers(),
720 keyEvent->text(), keyEvent->isAutoRepeat(), keyEvent->count());
721 QApplication::sendEvent(focusWidget(), &modifiedEvent);
722 return true;
723 }
724 break;
725 case Qt::Key_Select:
726 if (!m_preeditString.isEmpty()) {
727 commitCurrentString(true);
728 return true;
729 }
730 break;
731 default:
732 break;
733 }
734
735 QString widgetText = focusWidget()->inputMethodQuery(Qt::ImSurroundingText).toString();
736 bool validLength;
737 int maxLength = focusWidget()->inputMethodQuery(Qt::ImMaximumTextLength).toInt(&validLength);
738 if (!keyEvent->text().isEmpty() && validLength
739 && widgetText.size() + m_preeditString.size() >= maxLength) {
740 // Don't send key events with string content if the widget is "full".
741 return true;
742 }
743
744 if (keyEvent->type() == QEvent::KeyPress
745 && currentHints & Qt::ImhHiddenText
746 && !keyEvent->text().isEmpty()
747 && keyEvent->key() != Qt::Key_Enter) {
748 // Send some temporary preedit text in order to make text visible for a moment.
749 m_preeditString = keyEvent->text();
750 QList<QInputMethodEvent::Attribute> attributes;
751 QInputMethodEvent imEvent(m_preeditString, attributes);
752 sendEvent(imEvent);
753 m_tempPreeditStringTimeout.start(1000, this);
754 m_hasTempPreeditString = true;
755 update();
756 return true;
757 }
758 break;
759 }
760
761 if (!needsInputPanel())
762 return false;
763
764 if ((event->type() == QEvent::CloseSoftwareInputPanel)
765 && (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0)) {
766 m_fepState->ReportAknEdStateEventL(QT_EAknClosePenInputRequest);
767 return false;
768 }
769
770 if (event->type() == QEvent::RequestSoftwareInputPanel) {
771 // Only request virtual keyboard if it is not yet active or if this is the first time
772 // panel is requested for this application.
773 static bool firstTime = true;
774 int vkbActive = 0;
775
776 if (firstTime) {
777 // Sometimes the global QT_EAknFepTouchInputActive value can be left incorrect at
778 // application exit if the application is exited when input panel is active.
779 // Therefore we always want to open the panel the first time application requests it.
780 firstTime = false;
781 } else {
782 const TUid KPSUidAknFep = {QT_EPSUidAknFep};
783 // No need to check for return value, as vkbActive stays zero in that case
784 RProperty::Get(KPSUidAknFep, QT_EAknFepTouchInputActive, vkbActive);
785 }
786
787 if (!vkbActive) {
788 // Notify S60 that we want the virtual keyboard to show up.
789 QSymbianControl *sControl;
790 sControl = focusWidget()->effectiveWinId()->MopGetObject(sControl);
791 Q_ASSERT(sControl);
792
793 // Store last focused widget and object in case of fullscreen VKB
794 QObject *focusObject = 0;
795 m_lastFocusedEditor = getQWidgetFromQGraphicsView(focusWidget(), &focusObject);
796 m_lastFocusedObject = focusObject; // Can be null
797 Q_ASSERT(m_lastFocusedEditor);
798
799 // The FEP UI temporarily steals focus when it shows up the first time, causing
800 // all sorts of weird effects on the focused widgets. Since it will immediately give
801 // back focus to us, we temporarily disable focus handling until the job's done.
802 if (sControl) {
803 sControl->setIgnoreFocusChanged(true);
804 }
805
806 ensureInputCapabilitiesChanged();
807 m_fepState->ReportAknEdStateEventL(MAknEdStateObserver::QT_EAknActivatePenInputRequest);
808
809 if (sControl) {
810 sControl->setIgnoreFocusChanged(false);
811 }
812 }
813 }
814
815 return false;
816 }
817
symbianFilterEvent(QWidget * keyWidget,const QSymbianEvent * event)818 bool QCoeFepInputContext::symbianFilterEvent(QWidget *keyWidget, const QSymbianEvent *event)
819 {
820 Q_UNUSED(keyWidget);
821 if (event->type() == QSymbianEvent::WindowServerEvent) {
822 const TWsEvent* wsEvent = event->windowServerEvent();
823 TInt eventType = 0;
824 if (wsEvent)
825 eventType = wsEvent->Type();
826
827 if (eventType == EEventKey) {
828 TKeyEvent* keyEvent = wsEvent->Key();
829 if (keyEvent) {
830 switch (keyEvent->iScanCode) {
831 case EEikCmdEditCopy:
832 CcpuCopyL();
833 break;
834 case EEikCmdEditCut:
835 CcpuCutL();
836 break;
837 case EEikCmdEditPaste:
838 CcpuPasteL();
839 break;
840 case EStdKeyF21:
841 changeCBA(true);
842 break;
843 default:
844 break;
845 }
846 switch (keyEvent->iCode) {
847 case EKeyLeftArrow:
848 case EKeyRightArrow:
849 case EKeyUpArrow:
850 case EKeyDownArrow:
851 if (CcpuCanCopy() && ((keyEvent->iModifiers & EModifierShift) == EModifierShift))
852 changeCBA(true);
853 break;
854 default:
855 break;
856 }
857 }
858 } else if (eventType == EEventKeyUp) {
859 if (wsEvent->Key() && wsEvent->Key()->iScanCode == EStdKeyLeftShift)
860 changeCBA(false);
861 } else if (eventType == EEventWindowVisibilityChanged && S60->splitViewLastWidget) {
862 QGraphicsView *gv = qobject_cast<QGraphicsView*>(S60->splitViewLastWidget);
863 const bool alwaysResize = (gv && gv->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff);
864
865 if (alwaysResize) {
866 TUint visibleFlags = event->windowServerEvent()->VisibilityChanged()->iFlags;
867 if (visibleFlags & TWsVisibilityChangedEvent::EPartiallyVisible)
868 ensureFocusWidgetVisible(S60->splitViewLastWidget);
869 if (visibleFlags & TWsVisibilityChangedEvent::ENotVisible)
870 resetSplitViewWidget(true);
871 }
872 }
873 }
874
875 if (event->type() == QSymbianEvent::CommandEvent)
876 // A command basically means the same as a button being pushed. With Qt buttons
877 // that would normally result in a reset of the input method due to the focus change.
878 // This should also happen for commands.
879 reset();
880
881
882 if (event->type() == QSymbianEvent::ResourceChangeEvent
883 && (event->resourceChangeType() == KEikMessageFadeAllWindows
884 || event->resourceChangeType() == KEikDynamicLayoutVariantSwitch)) {
885 reset();
886 }
887
888 return false;
889 }
890
timerEvent(QTimerEvent * timerEvent)891 void QCoeFepInputContext::timerEvent(QTimerEvent *timerEvent)
892 {
893 if (timerEvent->timerId() == m_tempPreeditStringTimeout.timerId())
894 commitTemporaryPreeditString();
895 }
896
commitTemporaryPreeditString()897 void QCoeFepInputContext::commitTemporaryPreeditString()
898 {
899 if (m_tempPreeditStringTimeout.isActive())
900 m_tempPreeditStringTimeout.stop();
901
902 if (!m_hasTempPreeditString)
903 return;
904
905 commitCurrentString(false);
906 }
907
mouseHandler(int x,QMouseEvent * event)908 void QCoeFepInputContext::mouseHandler(int x, QMouseEvent *event)
909 {
910 Q_ASSERT(focusWidget());
911
912 if (event->type() == QEvent::MouseButtonPress && event->button() == Qt::LeftButton) {
913 QWidget *proxy = focusWidget()->focusProxy();
914 Qt::InputMethodHints currentHints = proxy ? proxy->inputMethodHints() : focusWidget()->inputMethodHints();
915
916 //If splitview is open and T9 word is tapped, pass the pointer event to pointer handler.
917 //This will open the "suggested words" list. Pass pointer position always as zero, to make
918 //full word replacement in case user makes a selection.
919 if (isPartialKeyboardSupported()
920 && S60->partialKeyboardOpen
921 && m_pointerHandler
922 && !(currentHints & Qt::ImhNoPredictiveText)
923 && (x > 0 && x < m_preeditString.length())) {
924 m_pointerHandler->HandlePointerEventInInlineTextL(TPointerEvent::EButton1Up, 0, 0);
925 } else {
926 // Notify FEP about pointer event via CAknExtendedInputCapabilities::ReporEventL().
927 // FEP will then commit the string and cancel inline edit state properly.
928 // FEP does not really use the pointer event parameter, so it is ok to pass NULL.
929 if (m_extendedInputCapabilities) {
930 TRAP_IGNORE(
931 m_extendedInputCapabilities->ReportEventL(
932 CAknExtendedInputCapabilities::MAknEventObserver::EPointerEventReceived,
933 NULL));
934 } else {
935 // In practice m_extendedInputCapabilities should always exist.
936 // If it does not, commit current string directly here.
937 // This will cancel inline edit in FEP but VKB might still think that
938 // inline edit is ongoing.
939 commitCurrentString(true);
940 }
941
942 int pos = focusWidget()->inputMethodQuery(Qt::ImCursorPosition).toInt();
943
944 QList<QInputMethodEvent::Attribute> attributes;
945 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, pos + x, 0, QVariant());
946 QInputMethodEvent event(QLatin1String(""), attributes);
947 sendEvent(event);
948 }
949 }
950 }
951
inputCapabilities()952 TCoeInputCapabilities QCoeFepInputContext::inputCapabilities()
953 {
954 if (m_inDestruction || !focusWidget()) {
955 return TCoeInputCapabilities(TCoeInputCapabilities::ENone, 0, 0);
956 }
957
958 TCoeInputCapabilities inputCapabilities(m_textCapabilities, this, 0);
959 inputCapabilities.SetObjectProvider(this);
960 return inputCapabilities;
961 }
962
resetSplitViewWidget(bool keepInputWidget)963 void QCoeFepInputContext::resetSplitViewWidget(bool keepInputWidget)
964 {
965 QGraphicsView *gv = qobject_cast<QGraphicsView*>(S60->splitViewLastWidget);
966
967 if (!gv)
968 return;
969
970 QSymbianControl *symControl = static_cast<QSymbianControl*>(gv->effectiveWinId());
971 symControl->CancelLongTapTimer();
972
973 const bool alwaysResize = (gv->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff);
974 QWidget *windowToMove = gv->window();
975
976 bool userResize = gv->testAttribute(Qt::WA_Resized);
977
978 windowToMove->setUpdatesEnabled(false);
979
980 if (!alwaysResize) {
981 if (gv->scene() && S60->partial_keyboardAutoTranslation) {
982 if (gv->scene()->focusItem()) {
983 QGraphicsItem *focusItem =
984 m_splitViewPreviousFocusItem ? m_splitViewPreviousFocusItem : gv->scene()->focusItem();
985 // Check if the widget contains cursorPositionChanged signal and disconnect from it.
986 QByteArray signal = QMetaObject::normalizedSignature(SIGNAL(cursorPositionChanged()));
987 int index = focusItem->toGraphicsObject()->metaObject()->indexOfSignal(signal.right(signal.length() - 1));
988 if (index != -1)
989 disconnect(focusItem->toGraphicsObject(), SIGNAL(cursorPositionChanged()), this, SLOT(translateInputWidget()));
990 }
991
992 QGraphicsItem *rootItem = 0;
993 foreach (QGraphicsItem *item, gv->scene()->items()) {
994 if (!item->parentItem()) {
995 rootItem = item;
996 break;
997 }
998 }
999 if (rootItem)
1000 rootItem->resetTransform();
1001 }
1002 } else {
1003 if (m_splitViewResizeBy)
1004 if (m_splitViewPreviousWindowStates & Qt::WindowFullScreen)
1005 gv->resize(gv->rect().width(), qApp->desktop()->height());
1006 else
1007 gv->resize(gv->rect().width(), m_splitViewResizeBy);
1008 }
1009 // Resizing might have led to widget losing its original windowstate.
1010 // Restore previous window state.
1011
1012 if (m_splitViewPreviousWindowStates != windowToMove->windowState())
1013 windowToMove->setWindowState(m_splitViewPreviousWindowStates);
1014
1015 windowToMove->setUpdatesEnabled(true);
1016
1017 gv->setAttribute(Qt::WA_Resized, userResize); //not a user resize
1018
1019 m_splitViewResizeBy = 0;
1020 if (!keepInputWidget) {
1021 m_splitViewPreviousWindowStates = Qt::WindowNoState;
1022 S60->splitViewLastWidget = 0;
1023 }
1024 }
1025
1026 // Checks if a given widget is visible in the splitview rect. The offset
1027 // parameter can be used to validate if moving widget upwards or downwards
1028 // by the offset would make a difference for the visibility.
1029
isWidgetVisible(QWidget * widget,int offset)1030 bool QCoeFepInputContext::isWidgetVisible(QWidget *widget, int offset)
1031 {
1032 bool visible = false;
1033 if (widget) {
1034 QRect splitViewRect = qt_TRect2QRect(static_cast<CEikAppUi*>(S60->appUi())->ClientRect());
1035 QWidget *window = QApplication::activeWindow();
1036 QGraphicsView *gv = qobject_cast<QGraphicsView*>(widget);
1037 if (gv && window) {
1038 if (QGraphicsScene *scene = gv->scene()) {
1039 if (QGraphicsItem *focusItem = scene->focusItem()) {
1040 QPoint cursorPos = window->mapToGlobal(focusItem->cursor().pos());
1041 cursorPos.setY(cursorPos.y() + offset);
1042 if (splitViewRect.contains(cursorPos)) {
1043 visible = true;
1044 }
1045 }
1046 }
1047 }
1048 }
1049 return visible;
1050 }
1051
isPartialKeyboardSupported()1052 bool QCoeFepInputContext::isPartialKeyboardSupported()
1053 {
1054 return (S60->partial_keyboard || !QApplication::testAttribute(Qt::AA_S60DisablePartialScreenInputMode));
1055 }
1056
ensureWidgetVisibility()1057 void QCoeFepInputContext::ensureWidgetVisibility()
1058 {
1059 ensureFocusWidgetVisible(S60->splitViewLastWidget);
1060 }
1061
1062 // Ensure that the input widget is visible in the splitview rect.
1063
ensureFocusWidgetVisible(QWidget * widget)1064 void QCoeFepInputContext::ensureFocusWidgetVisible(QWidget *widget)
1065 {
1066 if (!widget)
1067 return;
1068
1069 // Native side opening and closing its virtual keyboard when it changes the keyboard layout,
1070 // has an adverse impact on long tap timer. Cancel the timer when splitview opens to avoid this.
1071 QSymbianControl *symControl = static_cast<QSymbianControl*>(widget->effectiveWinId());
1072 symControl->CancelLongTapTimer();
1073
1074 // Graphicsviews that have vertical scrollbars should always be resized to the splitview area.
1075 // Graphicsviews without scrollbars should be translated.
1076
1077 QGraphicsView *gv = qobject_cast<QGraphicsView*>(widget);
1078 if (!gv)
1079 return;
1080
1081 const bool alwaysResize = (gv && gv->verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff);
1082 const bool moveWithinVisibleArea = (S60->splitViewLastWidget != 0);
1083
1084 QWidget *windowToMove = gv ? gv : symControl->widget();
1085 if (!windowToMove->isWindow())
1086 windowToMove = windowToMove->window();
1087 if (!windowToMove) {
1088 return;
1089 }
1090
1091 // When opening the keyboard (not moving within the splitview area), save the original
1092 // window state. In some cases, ensuring input widget visibility might lead to window
1093 // states getting changed.
1094
1095 if (!moveWithinVisibleArea) {
1096 S60->splitViewLastWidget = widget;
1097 m_splitViewPreviousWindowStates = windowToMove->windowState();
1098 }
1099
1100 // Check if the widget contains cursorPositionChanged signal and connect to it.
1101 if (gv->scene() && gv->scene()->focusItem() && S60->partial_keyboardAutoTranslation) {
1102 QByteArray signal = QMetaObject::normalizedSignature(SIGNAL(cursorPositionChanged()));
1103 if (m_splitViewPreviousFocusItem && m_splitViewPreviousFocusItem != gv->scene()->focusItem())
1104 disconnect(m_splitViewPreviousFocusItem->toGraphicsObject(), SIGNAL(cursorPositionChanged()), this, SLOT(translateInputWidget()));
1105 int index = gv->scene()->focusItem()->toGraphicsObject()->metaObject()->indexOfSignal(signal.right(signal.length() - 1));
1106 if (index != -1) {
1107 connect(gv->scene()->focusItem()->toGraphicsObject(), SIGNAL(cursorPositionChanged()), this, SLOT(translateInputWidget()));
1108 m_splitViewPreviousFocusItem = gv->scene()->focusItem();
1109 }
1110 }
1111
1112 int windowTop = widget->window()->pos().y();
1113
1114 const bool userResize = widget->testAttribute(Qt::WA_Resized);
1115
1116 QRect splitViewRect = qt_TRect2QRect(static_cast<CEikAppUi*>(S60->appUi())->ClientRect());
1117
1118
1119 // When resizing a window widget, it will lose its maximized window state.
1120 // Native applications hide statuspane in splitview state, so lets move to
1121 // fullscreen mode. This makes available area slightly bigger, which helps usability
1122 // and greatly reduces event passing in orientation switch cases,
1123 // as the statuspane size is not changing.
1124
1125 if (alwaysResize)
1126 windowToMove->setUpdatesEnabled(false);
1127
1128 if (!(windowToMove->windowState() & Qt::WindowFullScreen)) {
1129 windowToMove->setWindowState(
1130 (windowToMove->windowState() & ~(Qt::WindowMinimized | Qt::WindowFullScreen)) | Qt::WindowFullScreen);
1131 }
1132
1133 if (alwaysResize) {
1134 if (!moveWithinVisibleArea) {
1135 m_splitViewResizeBy = widget->height();
1136 windowTop = widget->geometry().top();
1137 widget->resize(widget->width(), splitViewRect.height() - windowTop);
1138 }
1139
1140 if (gv->scene() && S60->partial_keyboardAutoTranslation) {
1141 const QRectF microFocusRect = gv->scene()->inputMethodQuery(Qt::ImMicroFocus).toRectF();
1142 gv->ensureVisible(microFocusRect);
1143 }
1144 } else {
1145 if (S60->partial_keyboardAutoTranslation)
1146 translateInputWidget();
1147 }
1148
1149 if (alwaysResize)
1150 windowToMove->setUpdatesEnabled(true);
1151
1152 widget->setAttribute(Qt::WA_Resized, userResize); //not a user resize
1153 }
1154
qt_TCharFormat2QTextCharFormat(const TCharFormat & cFormat,bool validStyleColor)1155 static QTextCharFormat qt_TCharFormat2QTextCharFormat(const TCharFormat &cFormat, bool validStyleColor)
1156 {
1157 QTextCharFormat qFormat;
1158
1159 if (validStyleColor) {
1160 QBrush foreground(QColor(cFormat.iFontPresentation.iTextColor.Internal()));
1161 qFormat.setForeground(foreground);
1162 }
1163
1164 qFormat.setFontStrikeOut(cFormat.iFontPresentation.iStrikethrough == EStrikethroughOn);
1165 qFormat.setFontUnderline(cFormat.iFontPresentation.iUnderline == EUnderlineOn);
1166
1167 return qFormat;
1168 }
1169
updateHints(bool mustUpdateInputCapabilities)1170 void QCoeFepInputContext::updateHints(bool mustUpdateInputCapabilities)
1171 {
1172 QWidget *w = focusWidget();
1173 if (w) {
1174 QWidget *proxy = w->focusProxy();
1175 Qt::InputMethodHints hints = proxy ? proxy->inputMethodHints() : w->inputMethodHints();
1176
1177 // Since splitview support works like an input method hint, yet it is private flag,
1178 // we need to update its state separately.
1179 if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0) {
1180 TInt currentFlags = m_fepState->Flags();
1181 if (isPartialKeyboardSupported())
1182 currentFlags |= QT_EAknEditorFlagEnablePartialScreen;
1183 else
1184 currentFlags &= ~QT_EAknEditorFlagEnablePartialScreen;
1185 if (currentFlags != m_fepState->Flags())
1186 m_fepState->SetFlags(currentFlags);
1187 }
1188
1189 if (hints != m_lastImHints) {
1190 m_lastImHints = hints;
1191 applyHints(hints);
1192 } else if (!mustUpdateInputCapabilities) {
1193 // Optimization. Return immediately if there was no change.
1194 return;
1195 }
1196 }
1197 queueInputCapabilitiesChanged();
1198 }
1199
applyHints(Qt::InputMethodHints hints)1200 void QCoeFepInputContext::applyHints(Qt::InputMethodHints hints)
1201 {
1202 using namespace Qt;
1203
1204 reset();
1205 commitTemporaryPreeditString();
1206
1207 const bool anynumbermodes = hints & (ImhDigitsOnly | ImhFormattedNumbersOnly | ImhDialableCharactersOnly);
1208 const bool anytextmodes = hints & (ImhUppercaseOnly | ImhLowercaseOnly | ImhEmailCharactersOnly | ImhUrlCharactersOnly);
1209 const bool numbersOnly = anynumbermodes && !anytextmodes;
1210 const bool noOnlys = !(hints & ImhExclusiveInputMask);
1211 // if alphanumeric input, or if multiple incompatible number modes are selected;
1212 // then make all symbols available in numeric mode too.
1213 const bool needsCharMap= !numbersOnly || ((hints & ImhFormattedNumbersOnly) && (hints & ImhDialableCharactersOnly));
1214 TInt flags;
1215 Qt::InputMethodHints oldHints = hints;
1216
1217 // Some sanity checking. Make sure that only one preference is set.
1218 InputMethodHints prefs = ImhPreferNumbers | ImhPreferUppercase | ImhPreferLowercase;
1219 prefs &= hints;
1220 if (prefs != ImhPreferNumbers && prefs != ImhPreferUppercase && prefs != ImhPreferLowercase) {
1221 hints &= ~prefs;
1222 }
1223 if (!noOnlys) {
1224 // Make sure that the preference is within the permitted set.
1225 if (hints & ImhPreferNumbers && !anynumbermodes) {
1226 hints &= ~ImhPreferNumbers;
1227 } else if (hints & ImhPreferUppercase && !(hints & ImhUppercaseOnly)) {
1228 hints &= ~ImhPreferUppercase;
1229 } else if (hints & ImhPreferLowercase && !(hints & ImhLowercaseOnly)) {
1230 hints &= ~ImhPreferLowercase;
1231 }
1232 // If there is no preference, set it to something within the permitted set.
1233 if (!(hints & ImhPreferNumbers || hints & ImhPreferUppercase || hints & ImhPreferLowercase)) {
1234 if (hints & ImhLowercaseOnly) {
1235 hints |= ImhPreferLowercase;
1236 } else if (hints & ImhUppercaseOnly) {
1237 hints |= ImhPreferUppercase;
1238 } else if (numbersOnly) {
1239 hints |= ImhPreferNumbers;
1240 }
1241 }
1242 }
1243
1244 if (hints & ImhPreferNumbers) {
1245 m_fepState->SetDefaultInputMode(EAknEditorNumericInputMode);
1246 m_fepState->SetCurrentInputMode(EAknEditorNumericInputMode);
1247 } else {
1248 m_fepState->SetDefaultInputMode(EAknEditorTextInputMode);
1249 m_fepState->SetCurrentInputMode(EAknEditorTextInputMode);
1250 }
1251 flags = 0;
1252 if (noOnlys || (anynumbermodes && anytextmodes)) {
1253 flags = EAknEditorAllInputModes;
1254 }
1255 else if (anynumbermodes) {
1256 flags |= EAknEditorNumericInputMode;
1257 }
1258 else if (anytextmodes) {
1259 flags |= EAknEditorTextInputMode;
1260 }
1261 else {
1262 flags = EAknEditorAllInputModes;
1263 }
1264 m_fepState->SetPermittedInputModes(flags);
1265 ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateInputModeUpdate);
1266
1267 if (hints & ImhPreferLowercase) {
1268 m_fepState->SetDefaultCase(EAknEditorLowerCase);
1269 m_fepState->SetCurrentCase(EAknEditorLowerCase);
1270 } else if (hints & ImhPreferUppercase) {
1271 m_fepState->SetDefaultCase(EAknEditorUpperCase);
1272 m_fepState->SetCurrentCase(EAknEditorUpperCase);
1273 } else if (hints & ImhNoAutoUppercase) {
1274 m_fepState->SetDefaultCase(EAknEditorLowerCase);
1275 m_fepState->SetCurrentCase(EAknEditorLowerCase);
1276 } else if (hints & ImhHiddenText) {
1277 m_fepState->SetDefaultCase(EAknEditorLowerCase);
1278 m_fepState->SetCurrentCase(EAknEditorLowerCase);
1279 } else {
1280 m_fepState->SetDefaultCase(EAknEditorTextCase);
1281 m_fepState->SetCurrentCase(EAknEditorTextCase);
1282 }
1283 flags = 0;
1284 if (hints & ImhUppercaseOnly) {
1285 flags |= EAknEditorUpperCase;
1286 }
1287 if (hints & ImhLowercaseOnly) {
1288 flags |= EAknEditorLowerCase;
1289 }
1290 if (hints & ImhHiddenText) {
1291 flags = EAknEditorAllCaseModes;
1292 flags &= ~EAknEditorTextCase;
1293 }
1294 if (flags == 0) {
1295 flags = EAknEditorAllCaseModes;
1296 if (hints & ImhNoAutoUppercase) {
1297 flags &= ~EAknEditorTextCase;
1298 }
1299 }
1300 m_fepState->SetPermittedCases(flags);
1301 ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateCaseModeUpdate);
1302
1303 flags = 0;
1304 if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0) {
1305 if (isPartialKeyboardSupported())
1306 flags |= QT_EAknEditorFlagEnablePartialScreen;
1307 flags |= QT_EAknEditorFlagSelectionVisible;
1308 }
1309 if (hints & ImhUppercaseOnly && !(hints & ImhLowercaseOnly)
1310 || hints & ImhLowercaseOnly && !(hints & ImhUppercaseOnly)) {
1311 flags |= EAknEditorFlagFixedCase;
1312 }
1313 // Using T9 and hidden text together may actually crash the FEP, so check for hidden text too.
1314 if (hints & ImhNoPredictiveText || hints & ImhHiddenText) {
1315 flags |= EAknEditorFlagNoT9;
1316 }
1317
1318 if (S60->editorFlags & EAknEditorFlagLatinInputModesOnly){
1319 flags |= EAknEditorFlagLatinInputModesOnly;
1320 }
1321
1322 if (needsCharMap)
1323 flags |= EAknEditorFlagUseSCTNumericCharmap;
1324 m_fepState->SetFlags(flags);
1325 ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateFlagsUpdate);
1326
1327 if (hints & ImhDialableCharactersOnly) {
1328 // This is first, because if (ImhDialableCharactersOnly | ImhFormattedNumbersOnly)
1329 // is specified, this one is more natural (# key enters a #)
1330 flags = EAknEditorStandardNumberModeKeymap;
1331 } else if (hints & ImhFormattedNumbersOnly) {
1332 // # key enters decimal point
1333 flags = EAknEditorCalculatorNumberModeKeymap;
1334 } else if (hints & ImhDigitsOnly) {
1335 // This is last, because it is most restrictive (# key is inactive)
1336 flags = EAknEditorPlainNumberModeKeymap;
1337 } else {
1338 flags = EAknEditorStandardNumberModeKeymap;
1339 }
1340 m_fepState->SetNumericKeymap(static_cast<TAknEditorNumericKeymap>(flags));
1341
1342 if (hints & ImhUrlCharactersOnly) {
1343 // URL characters is everything except space, so a superset of the other restrictions
1344 m_fepState->SetExtensionFlags(EAknEditorExtFlagKeyboardUrl);
1345 } else if (hints & ImhEmailCharactersOnly) {
1346 m_fepState->SetExtensionFlags(EAknEditorExtFlagKeyboardEmail);
1347 } else {
1348 m_fepState->SetExtensionFlags(0);
1349 }
1350
1351 bool enableSmileys = needsCharMap && !(hints & (ImhHiddenText | ImhUrlCharactersOnly | ImhEmailCharactersOnly));
1352 if (enableSmileys)
1353 m_dummyEditor->AddFlagToUserFlags(CEikEdwin::EAvkonEnableSmileySupport);
1354 else
1355 m_dummyEditor->RemoveFlagFromUserFlags(CEikEdwin::EAvkonEnableSmileySupport);
1356
1357 if (hints & ImhHiddenText) {
1358 m_textCapabilities = TCoeInputCapabilities::EAllText | TCoeInputCapabilities::ESecretText;
1359 } else {
1360 m_textCapabilities = TCoeInputCapabilities::EAllText;
1361 }
1362 }
1363
applyFormat(QList<QInputMethodEvent::Attribute> * attributes)1364 void QCoeFepInputContext::applyFormat(QList<QInputMethodEvent::Attribute> *attributes)
1365 {
1366 TCharFormat cFormat;
1367 QColor styleTextColor;
1368 if (QWidget *focused = focusWidget()) {
1369 QGraphicsView *gv = qobject_cast<QGraphicsView*>(focused);
1370 if (!gv) // could be either the QGV or its viewport that has focus
1371 gv = qobject_cast<QGraphicsView*>(focused->parentWidget());
1372 if (gv) {
1373 if (QGraphicsScene *scene = gv->scene()) {
1374 if (QGraphicsItem *focusItem = scene->focusItem()) {
1375 if (focusItem->isWidget()) {
1376 styleTextColor = static_cast<QGraphicsWidget*>(focusItem)->palette().text().color();
1377 }
1378 }
1379 }
1380 } else {
1381 styleTextColor = focused->palette().text().color();
1382 }
1383 } else {
1384 styleTextColor = QApplication::palette("QLineEdit").text().color();
1385 }
1386
1387 if (styleTextColor.isValid()) {
1388 const TLogicalRgb fontColor(TRgb(styleTextColor.red(), styleTextColor.green(), styleTextColor.blue(), styleTextColor.alpha()));
1389 cFormat.iFontPresentation.iTextColor = fontColor;
1390 }
1391
1392 TInt numChars = 0;
1393 TInt charPos = 0;
1394 int oldSize = attributes->size();
1395 while (m_formatRetriever) {
1396 m_formatRetriever->GetFormatOfFepInlineText(cFormat, numChars, charPos);
1397 if (numChars <= 0) {
1398 // This shouldn't happen according to S60 docs, but apparently does sometimes.
1399 break;
1400 }
1401 attributes->append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat,
1402 charPos,
1403 numChars,
1404 QVariant(qt_TCharFormat2QTextCharFormat(cFormat, styleTextColor.isValid()))));
1405 charPos += numChars;
1406 if (charPos >= m_preeditString.size()) {
1407 break;
1408 }
1409 }
1410
1411 }
1412
queueInputCapabilitiesChanged()1413 void QCoeFepInputContext::queueInputCapabilitiesChanged()
1414 {
1415 if (m_pendingInputCapabilitiesChanged)
1416 return;
1417
1418 // Call ensureInputCapabilitiesChanged asynchronously. This is done to improve performance
1419 // by not updating input capabilities too often. The reason we don't call the Symbian
1420 // asynchronous version of InputCapabilitiesChanged is because we need to ensure that it
1421 // is synchronous in some specific cases. Those will call ensureInputCapabilitesChanged.
1422 QMetaObject::invokeMethod(this, "ensureInputCapabilitiesChanged", Qt::QueuedConnection);
1423 m_pendingInputCapabilitiesChanged = true;
1424 }
1425
ensureInputCapabilitiesChanged()1426 void QCoeFepInputContext::ensureInputCapabilitiesChanged()
1427 {
1428 if (!m_pendingInputCapabilitiesChanged)
1429 return;
1430
1431 // The call below is essentially equivalent to InputCapabilitiesChanged(),
1432 // but is synchronous, rather than asynchronous.
1433 CCoeEnv::Static()->SyncNotifyFocusObserversOfChangeInFocus();
1434 m_pendingInputCapabilitiesChanged = false;
1435 }
1436
translateInputWidget()1437 void QCoeFepInputContext::translateInputWidget()
1438 {
1439 QGraphicsView *gv = qobject_cast<QGraphicsView *>(S60->splitViewLastWidget);
1440 if (!gv)
1441 return;
1442 QRect splitViewRect = qt_TRect2QRect(static_cast<CEikAppUi*>(S60->appUi())->ClientRect());
1443
1444 QRectF cursor = gv->scene()->inputMethodQuery(Qt::ImMicroFocus).toRectF();
1445 QPolygon cursorP = gv->mapFromScene(cursor);
1446 QRectF vkbRect = QRectF(splitViewRect.bottomLeft(), qApp->desktop()->rect().bottomRight());
1447 if (cursor.isEmpty() || vkbRect.isEmpty())
1448 return;
1449
1450 // Fetch root item (i.e. graphicsitem with no parent)
1451 QGraphicsItem *rootItem = 0;
1452 foreach (QGraphicsItem *item, gv->scene()->items()) {
1453 if (!item->parentItem()) {
1454 rootItem = item;
1455 break;
1456 }
1457 }
1458 if (!rootItem)
1459 return;
1460
1461 m_transformation = (rootItem->transform().isTranslating()) ? QRectF(0,0, gv->width(), rootItem->transform().dy()) : QRectF();
1462
1463 // Adjust cursor bounding rect towards navigation direction,
1464 // so that view translates if the cursor gets near the splitview border.
1465 QRect cursorRect = (cursorP.boundingRect().top() < 0) ?
1466 cursorP.boundingRect().adjusted(0, -cursor.height(), 0, -cursor.height()) :
1467 cursorP.boundingRect().adjusted(0, cursor.height(), 0, cursor.height());
1468
1469 // If the current cursor position and upcoming cursor positions are visible in the splitview
1470 // area, do not move the view.
1471 if (splitViewRect.contains(cursorRect) && splitViewRect.contains(cursorP.boundingRect()))
1472 return;
1473
1474 // New Y position should be ideally just above the keyboard.
1475 // If that would expose unpainted canvas, limit the tranformation to the visible scene rect or
1476 // to the focus item's shape/clip path.
1477
1478 const QPainterPath path = gv->scene()->focusItem()->isClipped() ?
1479 gv->scene()->focusItem()->clipPath() : gv->scene()->focusItem()->shape();
1480 const qreal itemHeight = path.boundingRect().height();
1481
1482 // Limit the maximum translation so that underlaying window content is not exposed.
1483 qreal availableSpace = gv->sceneRect().bottom() - splitViewRect.bottom();
1484 availableSpace = m_transformation.height() ?
1485 (qMin(itemHeight, availableSpace) + m_transformation.height()) :
1486 availableSpace;
1487
1488 // Translation should happen row-by-row, but initially it needs to ensure that cursor is visible.
1489 const qreal translation = m_transformation.height() ?
1490 cursor.height() : (cursorRect.bottom() - vkbRect.top());
1491 qreal dy = 0.0;
1492 if (availableSpace > 0)
1493 dy = -(qMin(availableSpace, translation));
1494 else
1495 dy = -(translation);
1496
1497 // Correct the translation direction, if the cursor rect would be moved above application area.
1498 if ((cursorP.boundingRect().bottom() + dy) < 0)
1499 dy *= -1;
1500
1501 // Do not allow transform above screen top, nor beyond scenerect. Also, if there is no available
1502 // space anymore, skip translation.
1503 if ((m_transformation.height() + dy) > 0
1504 || (gv->sceneRect().bottom() + m_transformation.height()) < 0
1505 || !availableSpace) {
1506 // If we already have some transformation, remove it.
1507 if (m_transformation.height() < 0 || gv->sceneRect().bottom() + m_transformation.height() < 0) {
1508 rootItem->resetTransform();
1509 translateInputWidget();
1510 }
1511 return;
1512 }
1513
1514 rootItem->setTransform(QTransform::fromTranslate(0, dy), true);
1515 }
1516
StartFepInlineEditL(const TDesC & aInitialInlineText,TInt aPositionOfInsertionPointInInlineText,TBool aCursorVisibility,const MFormCustomDraw *,MFepInlineTextFormatRetriever & aInlineTextFormatRetriever,MFepPointerEventHandlerDuringInlineEdit & aPointerEventHandlerDuringInlineEdit)1517 void QCoeFepInputContext::StartFepInlineEditL(const TDesC& aInitialInlineText,
1518 TInt aPositionOfInsertionPointInInlineText, TBool aCursorVisibility, const MFormCustomDraw* /*aCustomDraw*/,
1519 MFepInlineTextFormatRetriever& aInlineTextFormatRetriever,
1520 MFepPointerEventHandlerDuringInlineEdit& aPointerEventHandlerDuringInlineEdit)
1521 {
1522 QWidget *w = focusWidget();
1523 if (!w)
1524 return;
1525
1526 m_cachedPreeditString.clear();
1527 m_cachedCursorAndAnchorPosition = -1;
1528
1529 commitTemporaryPreeditString();
1530
1531 QList<QInputMethodEvent::Attribute> attributes;
1532
1533 m_cursorVisibility = aCursorVisibility ? 1 : 0;
1534 m_inlinePosition = aPositionOfInsertionPointInInlineText;
1535 m_preeditString = qt_TDesC2QString(aInitialInlineText);
1536
1537 m_formatRetriever = &aInlineTextFormatRetriever;
1538 m_pointerHandler = &aPointerEventHandlerDuringInlineEdit;
1539
1540 // With T9 aInitialInlineText is typically empty when StartFepInlineEditL is called,
1541 // but FEP requires that selected text is always removed at StartFepInlineEditL.
1542 // Let's remove the selected text if aInitialInlineText is empty and there is selected text
1543 if (m_preeditString.isEmpty()) {
1544 QString currentSelection = w->inputMethodQuery(Qt::ImCurrentSelection).toString();
1545 if (!currentSelection.isEmpty()) {
1546 // To correctly remove selection in cases where we have multiple lines selected,
1547 // we must rely on the control's own selection removal mechanism, as surrounding
1548 // text contains only one line. It's also impossible to accurately detect
1549 // these overselection cases as the anchor and cursor positions are limited to the
1550 // surrounding text.
1551 // Solution is to clear the selection by faking a preedit. Use a dummy character
1552 // from the current selection just to be safe.
1553 QString dummyText = currentSelection.left(1);
1554 QList<QInputMethodEvent::Attribute> attributes;
1555 QInputMethodEvent clearSelectionEvent(dummyText, attributes);
1556 clearSelectionEvent.setCommitString(QLatin1String(""), 0, 0);
1557 sendEvent(clearSelectionEvent);
1558
1559 // Now that selection is taken care of, clear the fake preedit.
1560 QInputMethodEvent clearPreeditEvent(QLatin1String(""), attributes);
1561 clearPreeditEvent.setCommitString(QLatin1String(""), 0, 0);
1562 sendEvent(clearPreeditEvent);
1563 }
1564 }
1565
1566 applyFormat(&attributes);
1567
1568 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor,
1569 m_inlinePosition,
1570 m_cursorVisibility,
1571 QVariant()));
1572 QInputMethodEvent event(m_preeditString, attributes);
1573 sendEvent(event);
1574 }
1575
UpdateFepInlineTextL(const TDesC & aNewInlineText,TInt aPositionOfInsertionPointInInlineText)1576 void QCoeFepInputContext::UpdateFepInlineTextL(const TDesC& aNewInlineText,
1577 TInt aPositionOfInsertionPointInInlineText)
1578 {
1579 QWidget *w = focusWidget();
1580 if (!w)
1581 return;
1582
1583 commitTemporaryPreeditString();
1584
1585 m_inlinePosition = aPositionOfInsertionPointInInlineText;
1586
1587 QList<QInputMethodEvent::Attribute> attributes;
1588 applyFormat(&attributes);
1589 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor,
1590 m_inlinePosition,
1591 m_cursorVisibility,
1592 QVariant()));
1593 QString newPreeditString = qt_TDesC2QString(aNewInlineText);
1594 QInputMethodEvent event(newPreeditString, attributes);
1595 if (!m_cachedPreeditString.isEmpty()) {
1596 int cursorPos = w->inputMethodQuery(Qt::ImCursorPosition).toInt();
1597 // Predicted word is either replaced from the end of the word (normal case),
1598 // or from stored location, if the predicted word is either in the beginning of,
1599 // or in the middle of already committed word.
1600 int diff = cursorPos - m_cachedCursorAndAnchorPosition;
1601 int replaceLocation = (diff != m_cachedPreeditString.length()) ? diff : m_cachedPreeditString.length();
1602
1603 event.setCommitString(QLatin1String(""), -replaceLocation, m_cachedPreeditString.length());
1604 m_cachedPreeditString.clear();
1605 m_cachedCursorAndAnchorPosition = -1;
1606 } else if (newPreeditString.isEmpty() && m_preeditString.isEmpty()) {
1607 // In Symbian world this means "erase last character".
1608 event.setCommitString(QLatin1String(""), -1, 1);
1609 }
1610 m_preeditString = newPreeditString;
1611 sendEvent(event);
1612 }
1613
SetInlineEditingCursorVisibilityL(TBool aCursorVisibility)1614 void QCoeFepInputContext::SetInlineEditingCursorVisibilityL(TBool aCursorVisibility)
1615 {
1616 QWidget *w = focusWidget();
1617 if (!w)
1618 return;
1619
1620 m_cursorVisibility = aCursorVisibility ? 1 : 0;
1621
1622 QList<QInputMethodEvent::Attribute> attributes;
1623 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor,
1624 m_inlinePosition,
1625 m_cursorVisibility,
1626 QVariant()));
1627 QInputMethodEvent event(m_preeditString, attributes);
1628 sendEvent(event);
1629 }
1630
CancelFepInlineEdit()1631 void QCoeFepInputContext::CancelFepInlineEdit()
1632 {
1633 // We are not supposed to ever have a tempPreeditString and a real preedit string
1634 // from S60 at the same time, so it should be safe to rely on this test to determine
1635 // whether we should honor S60's request to clear the text or not.
1636 if (m_hasTempPreeditString || m_pendingTransactionCancel)
1637 return;
1638
1639 m_pendingTransactionCancel = true;
1640
1641 QT_TRY {
1642 QList<QInputMethodEvent::Attribute> attributes;
1643 QInputMethodEvent event(QLatin1String(""), attributes);
1644 event.setCommitString(QLatin1String(""), 0, 0);
1645 m_preeditString.clear();
1646 m_inlinePosition = 0;
1647 sendEvent(event);
1648
1649 // Prior to S60 5.4 need to sync with native side editor state so that native side can then do
1650 // various operations based on editor state, such as removing 'exact word bubble'.
1651 // Starting with S60 5.4 this sync request is not needed and can actually lead to a crash.
1652 if (QSysInfo::s60Version() < QSysInfo::SV_S60_5_4 && !m_pendingInputCapabilitiesChanged)
1653 ReportAknEdStateEvent(MAknEdStateObserver::EAknSyncEdwinState);
1654 } QT_CATCH(const std::exception&) {
1655 m_preeditString.clear();
1656 m_inlinePosition = 0;
1657 }
1658
1659 m_pendingTransactionCancel = false;
1660 }
1661
DocumentLengthForFep() const1662 TInt QCoeFepInputContext::DocumentLengthForFep() const
1663 {
1664 QT_TRY {
1665 QWidget *w = focusWidget();
1666 QObject *focusObject = 0;
1667 if (!w) {
1668 //when Menu is opened editor lost the focus, but fep manager wants focused editor
1669 w = m_lastFocusedEditor;
1670 focusObject = m_lastFocusedObject;
1671 } else {
1672 w = getQWidgetFromQGraphicsView(w, &focusObject);
1673 }
1674 if (!w)
1675 return 0;
1676
1677 QVariant variant = w->inputMethodQuery(Qt::ImSurroundingText);
1678 int size = variant.value<QString>().size() + m_preeditString.size();
1679
1680 // To fix an issue with backspaces not being generated if document size is zero,
1681 // fake document length to be at least one always, except when dealing with
1682 // hidden text widgets, all singleline text widgets and
1683 // also multiline text widget with single line.
1684 if (size == 0 && !(m_textCapabilities & TCoeInputCapabilities::ESecretText)
1685 && !(qobject_cast< QLineEdit *> (w))) {
1686 int lineCount = 0;
1687 if (QTextEdit* tedit = qobject_cast<QTextEdit *>(w)) {
1688 lineCount = tedit->document()->lineCount();
1689 } else if (QPlainTextEdit* ptedit = qobject_cast<QPlainTextEdit *>(w)) {
1690 lineCount = ptedit->document()->lineCount();
1691 } else {
1692 // Unknown editor (probably a QML one); Request the "lineCount" property.
1693 QObject *invokeTarget = w;
1694 if (focusObject)
1695 invokeTarget = focusObject;
1696 QVariant lineVariant = invokeTarget->property("lineCount");
1697 if (lineVariant.isValid()) {
1698 lineCount = lineVariant.toInt();
1699 } else {
1700 // If we can't get linecount from a custom QML editor, assume that it
1701 // has multiple lines, so that it can receive backspaces also when
1702 // the current line is empty.
1703 lineCount = 2;
1704 }
1705 }
1706 // To fix an issue with backspaces not being generated if document size is zero,
1707 // return size to 1 only for multiline editors with
1708 // no text and multiple lines presented.
1709 if (lineCount > 1)
1710 size = 1;
1711 }
1712 return size;
1713 } QT_CATCH(const std::exception&) {
1714 return 0;
1715 }
1716 }
1717
DocumentMaximumLengthForFep() const1718 TInt QCoeFepInputContext::DocumentMaximumLengthForFep() const
1719 {
1720 QWidget *w = focusWidget();
1721 if (!w)
1722 return 0;
1723
1724 QVariant variant = w->inputMethodQuery(Qt::ImMaximumTextLength);
1725 int size;
1726 if (variant.isValid()) {
1727 size = variant.toInt();
1728 } else {
1729 size = INT_MAX; // Sensible default for S60.
1730 }
1731 return size;
1732 }
1733
SetCursorSelectionForFepL(const TCursorSelection & aCursorSelection)1734 void QCoeFepInputContext::SetCursorSelectionForFepL(const TCursorSelection& aCursorSelection)
1735 {
1736 QWidget *w = focusWidget();
1737 if (!w)
1738 return;
1739
1740 commitTemporaryPreeditString();
1741
1742 int pos = aCursorSelection.iAnchorPos;
1743 int length = aCursorSelection.iCursorPos - pos;
1744 if (m_cachedCursorAndAnchorPosition != -1) {
1745 pos = m_cachedCursorAndAnchorPosition;
1746 length = 0;
1747 }
1748
1749 QList<QInputMethodEvent::Attribute> attributes;
1750 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, pos, length, QVariant());
1751 QInputMethodEvent event(m_preeditString, attributes);
1752 sendEvent(event);
1753 }
1754
GetCursorSelectionForFep(TCursorSelection & aCursorSelection) const1755 void QCoeFepInputContext::GetCursorSelectionForFep(TCursorSelection& aCursorSelection) const
1756 {
1757 QT_TRY {
1758 QWidget *w = focusWidget();
1759 if (!w) {
1760 aCursorSelection.SetSelection(0,0);
1761 return;
1762 }
1763
1764 int cursor = w->inputMethodQuery(Qt::ImCursorPosition).toInt() + m_preeditString.size();
1765 int anchor = w->inputMethodQuery(Qt::ImAnchorPosition).toInt() + m_preeditString.size();
1766
1767 // If the position is stored, use that value, so that word replacement from proposed word
1768 // lists are added to the correct position.
1769 if (m_cachedCursorAndAnchorPosition != -1) {
1770 cursor = m_cachedCursorAndAnchorPosition;
1771 anchor = m_cachedCursorAndAnchorPosition;
1772 }
1773 QString text = w->inputMethodQuery(Qt::ImSurroundingText).value<QString>();
1774 int combinedSize = text.size() + m_preeditString.size();
1775 if (combinedSize < anchor || combinedSize < cursor) {
1776 // ### TODO! FIXME! QTBUG-5050
1777 // This is a hack to prevent crashing in 4.6 with QLineEdits that use input masks.
1778 // The root problem is that cursor position is relative to displayed text instead of the
1779 // actual text we get.
1780 //
1781 // To properly fix this we would need to know the displayText of QLineEdits instead
1782 // of just the text, which on itself should be a trivial change. The difficulties start
1783 // when we need to commit the changes back to the QLineEdit, which would have to be somehow
1784 // able to handle displayText, too.
1785 //
1786 // Until properly fixed, the cursor and anchor positions will not reflect correct positions
1787 // for masked QLineEdits, unless all the masked positions are filled in order so that
1788 // cursor position relative to the displayed text matches position relative to actual text.
1789 aCursorSelection.iAnchorPos = combinedSize;
1790 aCursorSelection.iCursorPos = combinedSize;
1791 } else {
1792 aCursorSelection.iAnchorPos = anchor;
1793 aCursorSelection.iCursorPos = cursor;
1794 }
1795 } QT_CATCH(const std::exception&) {
1796 aCursorSelection.SetSelection(0,0);
1797 }
1798 }
1799
GetEditorContentForFep(TDes & aEditorContent,TInt aDocumentPosition,TInt aLengthToRetrieve) const1800 void QCoeFepInputContext::GetEditorContentForFep(TDes& aEditorContent, TInt aDocumentPosition,
1801 TInt aLengthToRetrieve) const
1802 {
1803 QWidget *w = focusWidget();
1804 if (!w) {
1805 aEditorContent.FillZ(aLengthToRetrieve);
1806 return;
1807 }
1808
1809 QString text = w->inputMethodQuery(Qt::ImSurroundingText).value<QString>();
1810 // FEP expects the preedit string to be part of the editor content, so let's mix it in.
1811 int cursor = w->inputMethodQuery(Qt::ImCursorPosition).toInt();
1812 text.insert(cursor, m_preeditString);
1813
1814 // Add additional space to empty non-password text to compensate
1815 // for the fake length we specified in DocumentLengthForFep().
1816 if (text.size() == 0 && !(m_textCapabilities & TCoeInputCapabilities::ESecretText))
1817 text += QChar(0x20);
1818
1819 aEditorContent.Copy(qt_QString2TPtrC(text.mid(aDocumentPosition, aLengthToRetrieve)));
1820 }
1821
GetFormatForFep(TCharFormat & aFormat,TInt) const1822 void QCoeFepInputContext::GetFormatForFep(TCharFormat& aFormat, TInt /* aDocumentPosition */) const
1823 {
1824 QWidget *w = focusWidget();
1825 if (!w) {
1826 aFormat = TCharFormat();
1827 return;
1828 }
1829
1830 QFont font = w->inputMethodQuery(Qt::ImFont).value<QFont>();
1831 QFontMetrics metrics(font);
1832 //QString name = font.rawName();
1833 QString name = font.defaultFamily(); // TODO! FIXME! Should be the above.
1834 QHBufC hBufC(name);
1835 aFormat = TCharFormat(hBufC->Des(), metrics.height());
1836 }
1837
GetScreenCoordinatesForFepL(TPoint & aLeftSideOfBaseLine,TInt & aHeight,TInt & aAscent,TInt aDocumentPosition) const1838 void QCoeFepInputContext::GetScreenCoordinatesForFepL(TPoint& aLeftSideOfBaseLine, TInt& aHeight,
1839 TInt& aAscent, TInt aDocumentPosition) const
1840 {
1841 QT_TRYCATCH_LEAVING(getScreenCoordinatesForFepX(aLeftSideOfBaseLine, aHeight, aAscent, aDocumentPosition));
1842 }
1843
getScreenCoordinatesForFepX(TPoint & aLeftSideOfBaseLine,TInt & aHeight,TInt & aAscent,TInt) const1844 void QCoeFepInputContext::getScreenCoordinatesForFepX(TPoint& aLeftSideOfBaseLine, TInt& aHeight,
1845 TInt& aAscent, TInt /* aDocumentPosition */) const
1846 {
1847 QWidget *w = focusWidget();
1848 if (!w) {
1849 aLeftSideOfBaseLine = TPoint(0,0);
1850 aHeight = 0;
1851 aAscent = 0;
1852 return;
1853 }
1854
1855 QRect rect = w->inputMethodQuery(Qt::ImMicroFocus).value<QRect>();
1856 aLeftSideOfBaseLine.iX = rect.left();
1857 aLeftSideOfBaseLine.iY = rect.bottom();
1858
1859 QFont font = w->inputMethodQuery(Qt::ImFont).value<QFont>();
1860 QFontMetrics metrics(font);
1861 aHeight = metrics.height();
1862 aAscent = metrics.ascent();
1863 }
1864
enableSymbianCcpuSupport()1865 void QCoeFepInputContext::enableSymbianCcpuSupport()
1866 {
1867 if (!m_ccpu) {
1868 QT_TRAP_THROWING(
1869 m_ccpu = new (ELeave) CAknCcpuSupport(this);
1870 m_ccpu->SetMopParent(this);
1871 CleanupStack::PushL(m_ccpu);
1872 m_ccpu->ConstructL();
1873 CleanupStack::Pop(m_ccpu);
1874 );
1875 Q_ASSERT(m_fepState);
1876 if (m_fepState)
1877 m_fepState->SetCcpuState(this);
1878 }
1879 }
1880
changeCBA(bool showCopyAndOrPaste)1881 void QCoeFepInputContext::changeCBA(bool showCopyAndOrPaste)
1882 {
1883 QWidget *w = focusWidget();
1884 if (!w)
1885 w = m_lastFocusedEditor;
1886
1887 if (w) {
1888 if (showCopyAndOrPaste) {
1889 if (CcpuCanCopy())
1890 w->addAction(m_copyAction);
1891 if (CcpuCanPaste())
1892 w->addAction(m_pasteAction);
1893 } else {
1894 w->removeAction(m_copyAction);
1895 w->removeAction(m_pasteAction);
1896 }
1897 }
1898 }
1899
copyOrCutTextToClipboard(const char * operation)1900 void QCoeFepInputContext::copyOrCutTextToClipboard(const char *operation)
1901 {
1902 QWidget *w = focusWidget();
1903 QObject *focusObject = 0;
1904 if (!w) {
1905 w = m_lastFocusedEditor;
1906 focusObject = m_lastFocusedObject;
1907 } else {
1908 w = getQWidgetFromQGraphicsView(w, &focusObject);
1909 }
1910
1911 if (w) {
1912 int cursor = w->inputMethodQuery(Qt::ImCursorPosition).toInt();
1913 int anchor = w->inputMethodQuery(Qt::ImAnchorPosition).toInt();
1914
1915 if (cursor != anchor) {
1916 if (ccpuInvokeSlot(w, focusObject, operation)) {
1917 if (QSysInfo::symbianVersion() > QSysInfo::SV_SF_3) {
1918 TRAP_IGNORE(
1919 CAknDiscreetPopup::ShowGlobalPopupL(
1920 R_AVKON_DISCREET_POPUP_TEXT_COPIED,
1921 KAvkonResourceFile);
1922 )
1923 }
1924 }
1925 }
1926 }
1927 }
1928
1929
DoCommitFepInlineEditL()1930 void QCoeFepInputContext::DoCommitFepInlineEditL()
1931 {
1932 commitCurrentString(false);
1933 if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0)
1934 ReportAknEdStateEvent(QT_EAknCursorPositionChanged);
1935
1936 }
1937
commitCurrentString(bool cancelFepTransaction)1938 void QCoeFepInputContext::commitCurrentString(bool cancelFepTransaction)
1939 {
1940 QList<QInputMethodEvent::Attribute> attributes;
1941 QInputMethodEvent event(QLatin1String(""), attributes);
1942 event.setCommitString(m_preeditString, 0, 0);
1943 m_preeditString.clear();
1944 m_inlinePosition = 0;
1945 sendEvent(event);
1946
1947 m_hasTempPreeditString = false;
1948
1949 //Only cancel FEP transactions with prediction, when there is still active window.
1950 Qt::InputMethodHints currentHints = Qt::ImhNone;
1951 if (focusWidget()) {
1952 if (focusWidget()->focusProxy())
1953 currentHints = focusWidget()->focusProxy()->inputMethodHints();
1954 else
1955 currentHints = focusWidget()->inputMethodHints();
1956 }
1957 bool predictive = !(currentHints & Qt::ImhNoPredictiveText);
1958 bool widgetAndWindowAvailable = QApplication::activeWindow() && focusWidget();
1959
1960 if (cancelFepTransaction && ((predictive && widgetAndWindowAvailable) || !predictive)) {
1961 CCoeFep* fep = CCoeEnv::Static()->Fep();
1962 if (fep)
1963 fep->CancelTransaction();
1964 }
1965 }
1966
Extension1(TBool & aSetToTrue)1967 MCoeFepAwareTextEditor_Extension1* QCoeFepInputContext::Extension1(TBool& aSetToTrue)
1968 {
1969 aSetToTrue = ETrue;
1970 return this;
1971 }
1972
SetStateTransferingOwnershipL(MCoeFepAwareTextEditor_Extension1::CState * aState,TUid)1973 void QCoeFepInputContext::SetStateTransferingOwnershipL(MCoeFepAwareTextEditor_Extension1::CState* aState,
1974 TUid /*aTypeSafetyUid*/)
1975 {
1976 // Note: The S60 docs are wrong! See the State() function.
1977 if (m_fepState)
1978 delete m_fepState;
1979 m_fepState = static_cast<CAknEdwinState *>(aState);
1980 }
1981
State(TUid)1982 MCoeFepAwareTextEditor_Extension1::CState* QCoeFepInputContext::State(TUid /*aTypeSafetyUid*/)
1983 {
1984 // Note: The S60 docs are horribly wrong when describing the
1985 // SetStateTransferingOwnershipL function and this function. They say that the former
1986 // sets a CState object identified by the TUid, and the latter retrieves it.
1987 // In reality, the CState is expected to always be a CAknEdwinState (even if it was not
1988 // previously set), and the TUid is ignored. All in all, there is a single CAknEdwinState
1989 // per QCoeFepInputContext, which should be deleted if the SetStateTransferingOwnershipL
1990 // function is used to set a new one.
1991 return m_fepState;
1992 }
1993
CcpuIsFocused() const1994 TBool QCoeFepInputContext::CcpuIsFocused() const
1995 {
1996 return focusWidget() != 0;
1997 }
1998
CcpuCanCut() const1999 TBool QCoeFepInputContext::CcpuCanCut() const
2000 {
2001 QT_TRY {
2002 bool retval = false;
2003 if (m_inDestruction)
2004 return retval;
2005 QWidget *w = focusWidget();
2006 QObject *focusObject = 0;
2007 if (!w) {
2008 w = m_lastFocusedEditor;
2009 focusObject = m_lastFocusedObject;
2010 } else {
2011 w = getQWidgetFromQGraphicsView(w, &focusObject);
2012 }
2013 if (w) {
2014 QRect microFocus = w->inputMethodQuery(Qt::ImMicroFocus).toRect();
2015 if (microFocus.isNull()) {
2016 // For some reason, the editor does not have microfocus. Most probably,
2017 // it is due to using native fullscreen editing mode with QML apps.
2018 // Try accessing "selectedText" directly.
2019 QObject *invokeTarget = w;
2020 if (focusObject)
2021 invokeTarget = focusObject;
2022
2023 QString selectedText = invokeTarget->property("selectedText").toString();
2024 retval = !selectedText.isNull();
2025 } else {
2026 int cursor = w->inputMethodQuery(Qt::ImCursorPosition).toInt();
2027 int anchor = w->inputMethodQuery(Qt::ImAnchorPosition).toInt();
2028 retval = cursor != anchor;
2029 }
2030 }
2031 return retval;
2032 } QT_CATCH(const std::exception&) {
2033 return EFalse;
2034 }
2035 }
2036
CcpuCutL()2037 void QCoeFepInputContext::CcpuCutL()
2038 {
2039 copyOrCutTextToClipboard("cut");
2040 }
2041
CcpuCanCopy() const2042 TBool QCoeFepInputContext::CcpuCanCopy() const
2043 {
2044 return CcpuCanCut();
2045 }
2046
CcpuCopyL()2047 void QCoeFepInputContext::CcpuCopyL()
2048 {
2049 copyOrCutTextToClipboard("copy");
2050 }
2051
CcpuCanPaste() const2052 TBool QCoeFepInputContext::CcpuCanPaste() const
2053 {
2054 bool canPaste = false;
2055 if (m_inDestruction)
2056 return canPaste;
2057
2058 QString textToPaste = QApplication::clipboard()->text();
2059 if (!textToPaste.isEmpty()) {
2060 QWidget *w = focusWidget();
2061 QObject *focusObject = 0;
2062 if (!w) {
2063 w = m_lastFocusedEditor;
2064 focusObject = m_lastFocusedObject;
2065 } else {
2066 w = getQWidgetFromQGraphicsView(w, &focusObject);
2067 }
2068 if (w) {
2069 // First, check if we are dealing with standard Qt editors (QLineEdit, QTextEdit, or QPlainTextEdit),
2070 // as they do not have queryable property.
2071 if (QTextEdit* tedit = qobject_cast<QTextEdit *>(w)) {
2072 canPaste = tedit->canPaste();
2073 } else if (QPlainTextEdit* ptedit = qobject_cast<QPlainTextEdit *>(w)) {
2074 canPaste = ptedit->canPaste();
2075 } else if (QLineEdit* ledit = qobject_cast<QLineEdit *>(w)) {
2076 QString fullText = ledit->text();
2077 if (ledit->hasSelectedText()) {
2078 fullText.remove(ledit->selectionStart(), ledit->selectedText().length());
2079 fullText.insert(ledit->selectionStart(), textToPaste);
2080 } else {
2081 fullText.insert(ledit->cursorPosition(), textToPaste);
2082 }
2083
2084 if (fullText.length() > ledit->maxLength()) {
2085 canPaste = false;
2086 } else {
2087 const QValidator* validator = ledit->validator();
2088 if (validator) {
2089 int pos = 0;
2090 if (validator->validate(fullText, pos) == QValidator::Invalid)
2091 canPaste = false;
2092 else
2093 canPaste = true;
2094 } else {
2095 QString mask(ledit->inputMask());
2096 if (!mask.isEmpty()) {
2097 QCoeFepInputMaskHandler maskhandler(mask);
2098 if (maskhandler.canPasteClipboard(fullText))
2099 canPaste = true;
2100 else
2101 canPaste = false;
2102 } else {
2103 canPaste = true;
2104 }
2105 }
2106 }
2107 } else {
2108 // Unknown editor (probably a QML one); Request the "canPaste" property.
2109 QObject *invokeTarget = w;
2110 if (focusObject)
2111 invokeTarget = focusObject;
2112
2113 canPaste = invokeTarget->property("canPaste").toBool();
2114 }
2115 }
2116 }
2117 return canPaste;
2118 }
2119
CcpuPasteL()2120 void QCoeFepInputContext::CcpuPasteL()
2121 {
2122 QWidget *w = focusWidget();
2123 QObject *focusObject = 0;
2124 if (!w) {
2125 w = m_lastFocusedEditor;
2126 focusObject = m_lastFocusedObject;
2127 } else {
2128 w = getQWidgetFromQGraphicsView(w, &focusObject);
2129 }
2130 if (w)
2131 ccpuInvokeSlot(w, focusObject, "paste");
2132 }
2133
CcpuCanUndo() const2134 TBool QCoeFepInputContext::CcpuCanUndo() const
2135 {
2136 //not supported
2137 return EFalse;
2138 }
2139
CcpuUndoL()2140 void QCoeFepInputContext::CcpuUndoL()
2141 {
2142 //not supported
2143 }
2144
copy()2145 void QCoeFepInputContext::copy()
2146 {
2147 QT_TRAP_THROWING(CcpuCopyL());
2148 }
2149
paste()2150 void QCoeFepInputContext::paste()
2151 {
2152 QT_TRAP_THROWING(CcpuPasteL());
2153 }
2154
MopSupplyObject(TTypeUid id)2155 TTypeUid::Ptr QCoeFepInputContext::MopSupplyObject(TTypeUid id)
2156 {
2157 if (m_extendedInputCapabilities
2158 && id.iUid == CAknExtendedInputCapabilities::ETypeId)
2159 return id.MakePtr(m_extendedInputCapabilities);
2160
2161 return TTypeUid::Null();
2162 }
2163
MopNext()2164 MObjectProvider *QCoeFepInputContext::MopNext()
2165 {
2166 QWidget *w = focusWidget();
2167 if (w)
2168 return w->effectiveWinId();
2169 return 0;
2170 }
2171
2172 QT_END_NAMESPACE
2173
2174 #endif // QT_NO_IM
2175