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