|
1 /**************************************************************************** |
|
2 ** |
|
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). |
|
4 ** All rights reserved. |
|
5 ** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 ** |
|
7 ** This file is part of the QtGui module of the Qt Toolkit. |
|
8 ** |
|
9 ** $QT_BEGIN_LICENSE:LGPL$ |
|
10 ** No Commercial Usage |
|
11 ** This file contains pre-release code and may not be distributed. |
|
12 ** You may use this file in accordance with the terms and conditions |
|
13 ** contained in the Technology Preview License Agreement accompanying |
|
14 ** this package. |
|
15 ** |
|
16 ** GNU Lesser General Public License Usage |
|
17 ** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 ** General Public License version 2.1 as published by the Free Software |
|
19 ** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 ** packaging of this file. Please review the following information to |
|
21 ** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 ** |
|
24 ** In addition, as a special exception, Nokia gives you certain additional |
|
25 ** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 ** |
|
28 ** If you have questions regarding the use of this file, please contact |
|
29 ** Nokia at qt-info@nokia.com. |
|
30 ** |
|
31 ** |
|
32 ** |
|
33 ** |
|
34 ** |
|
35 ** |
|
36 ** |
|
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 <private/qcore_symbian_p.h> |
|
48 |
|
49 #include <fepitfr.h> |
|
50 #include <hal.h> |
|
51 |
|
52 #include <limits.h> |
|
53 // You only find these enumerations on SDK 5 onwards, so we need to provide our own |
|
54 // to remain compatible with older releases. They won't be called by pre-5.0 SDKs. |
|
55 |
|
56 // MAknEdStateObserver::EAknCursorPositionChanged |
|
57 #define QT_EAknCursorPositionChanged MAknEdStateObserver::EAknEdwinStateEvent(6) |
|
58 // MAknEdStateObserver::EAknActivatePenInputRequest |
|
59 #define QT_EAknActivatePenInputRequest MAknEdStateObserver::EAknEdwinStateEvent(7) |
|
60 |
|
61 QT_BEGIN_NAMESPACE |
|
62 |
|
63 QCoeFepInputContext::QCoeFepInputContext(QObject *parent) |
|
64 : QInputContext(parent), |
|
65 m_fepState(q_check_ptr(new CAknEdwinState)), // CBase derived object needs check on new |
|
66 m_lastImHints(Qt::ImhNone), |
|
67 m_textCapabilities(TCoeInputCapabilities::EAllText), |
|
68 m_inDestruction(false), |
|
69 m_pendingInputCapabilitiesChanged(false), |
|
70 m_cursorVisibility(1), |
|
71 m_inlinePosition(0), |
|
72 m_formatRetriever(0), |
|
73 m_pointerHandler(0), |
|
74 m_cursorPos(0), |
|
75 m_hasTempPreeditString(false) |
|
76 { |
|
77 m_fepState->SetObjectProvider(this); |
|
78 m_fepState->SetFlags(EAknEditorFlagDefault); |
|
79 m_fepState->SetDefaultInputMode( EAknEditorTextInputMode ); |
|
80 m_fepState->SetPermittedInputModes( EAknEditorAllInputModes ); |
|
81 m_fepState->SetDefaultCase( EAknEditorLowerCase ); |
|
82 m_fepState->SetPermittedCases( EAknEditorLowerCase|EAknEditorUpperCase ); |
|
83 m_fepState->SetSpecialCharacterTableResourceId(R_AVKON_SPECIAL_CHARACTER_TABLE_DIALOG); |
|
84 m_fepState->SetNumericKeymap( EAknEditorStandardNumberModeKeymap ); |
|
85 } |
|
86 |
|
87 QCoeFepInputContext::~QCoeFepInputContext() |
|
88 { |
|
89 m_inDestruction = true; |
|
90 |
|
91 // This is to make sure that the FEP manager "forgets" about us, |
|
92 // otherwise we may get callbacks even after we're destroyed. |
|
93 // The call below is essentially equivalent to InputCapabilitiesChanged(), |
|
94 // but is synchronous, rather than asynchronous. |
|
95 CCoeEnv::Static()->SyncNotifyFocusObserversOfChangeInFocus(); |
|
96 |
|
97 if (m_fepState) |
|
98 delete m_fepState; |
|
99 } |
|
100 |
|
101 void QCoeFepInputContext::reset() |
|
102 { |
|
103 commitCurrentString(true); |
|
104 } |
|
105 |
|
106 void QCoeFepInputContext::ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateEvent aEventType) |
|
107 { |
|
108 QT_TRAP_THROWING(m_fepState->ReportAknEdStateEventL(aEventType)); |
|
109 } |
|
110 |
|
111 void QCoeFepInputContext::update() |
|
112 { |
|
113 updateHints(false); |
|
114 |
|
115 // For pre-5.0 SDKs, we don't do text updates on S60 side. |
|
116 if (QSysInfo::s60Version() < QSysInfo::SV_S60_5_0) { |
|
117 return; |
|
118 } |
|
119 |
|
120 // Don't be fooled (as I was) by the name of this enumeration. |
|
121 // What it really does is tell the virtual keyboard UI that the text has been |
|
122 // updated and it should be reflected in the internal display of the VK. |
|
123 ReportAknEdStateEvent(QT_EAknCursorPositionChanged); |
|
124 } |
|
125 |
|
126 void QCoeFepInputContext::setFocusWidget(QWidget *w) |
|
127 { |
|
128 commitCurrentString(true); |
|
129 |
|
130 QInputContext::setFocusWidget(w); |
|
131 |
|
132 updateHints(true); |
|
133 } |
|
134 |
|
135 void QCoeFepInputContext::widgetDestroyed(QWidget *w) |
|
136 { |
|
137 // Make sure that the input capabilities of whatever new widget got focused are queried. |
|
138 CCoeControl *ctrl = w->effectiveWinId(); |
|
139 if (ctrl->IsFocused()) { |
|
140 queueInputCapabilitiesChanged(); |
|
141 } |
|
142 } |
|
143 |
|
144 QString QCoeFepInputContext::language() |
|
145 { |
|
146 TLanguage lang = m_fepState->LocalLanguage(); |
|
147 const QByteArray localeName = qt_symbianLocaleName(lang); |
|
148 if (!localeName.isEmpty()) { |
|
149 return QString::fromLatin1(localeName); |
|
150 } else { |
|
151 return QString::fromLatin1("C"); |
|
152 } |
|
153 } |
|
154 |
|
155 bool QCoeFepInputContext::needsInputPanel() |
|
156 { |
|
157 switch (QSysInfo::s60Version()) { |
|
158 case QSysInfo::SV_S60_3_1: |
|
159 case QSysInfo::SV_S60_3_2: |
|
160 // There are no touch phones for pre-5.0 SDKs. |
|
161 return false; |
|
162 #ifdef Q_CC_NOKIAX86 |
|
163 default: |
|
164 // For emulator we assume that we need an input panel, since we can't |
|
165 // separate between phone types. |
|
166 return true; |
|
167 #else |
|
168 case QSysInfo::SV_S60_5_0: { |
|
169 // For SDK == 5.0, we need phone specific detection, since the HAL API |
|
170 // is no good on most phones. However, all phones at the time of writing use the |
|
171 // input panel, except N97 in landscape mode, but in this mode it refuses to bring |
|
172 // up the panel anyway, so we don't have to care. |
|
173 return true; |
|
174 } |
|
175 default: |
|
176 // For unknown/newer types, we try to use the HAL API. |
|
177 int keyboardEnabled; |
|
178 int keyboardType; |
|
179 int err[2]; |
|
180 err[0] = HAL::Get(HAL::EKeyboard, keyboardType); |
|
181 err[1] = HAL::Get(HAL::EKeyboardState, keyboardEnabled); |
|
182 if (err[0] == KErrNone && err[1] == KErrNone |
|
183 && keyboardType != 0 && keyboardEnabled) |
|
184 // Means that we have some sort of keyboard. |
|
185 return false; |
|
186 |
|
187 // Fall back to using the input panel. |
|
188 return true; |
|
189 #endif // !Q_CC_NOKIAX86 |
|
190 } |
|
191 } |
|
192 |
|
193 bool QCoeFepInputContext::filterEvent(const QEvent *event) |
|
194 { |
|
195 // The CloseSoftwareInputPanel event is not handled here, because the VK will automatically |
|
196 // close when it discovers that the underlying widget does not have input capabilities. |
|
197 |
|
198 if (!focusWidget()) |
|
199 return false; |
|
200 |
|
201 switch (event->type()) { |
|
202 case QEvent::KeyPress: |
|
203 commitTemporaryPreeditString(); |
|
204 // fall through intended |
|
205 case QEvent::KeyRelease: |
|
206 const QKeyEvent *keyEvent = static_cast<const QKeyEvent *>(event); |
|
207 switch (keyEvent->key()) { |
|
208 case Qt::Key_F20: |
|
209 Q_ASSERT(m_lastImHints == focusWidget()->inputMethodHints()); |
|
210 if (m_lastImHints & Qt::ImhHiddenText) { |
|
211 // Special case in Symbian. On editors with secret text, F20 is for some reason |
|
212 // considered to be a backspace. |
|
213 QKeyEvent modifiedEvent(keyEvent->type(), Qt::Key_Backspace, keyEvent->modifiers(), |
|
214 keyEvent->text(), keyEvent->isAutoRepeat(), keyEvent->count()); |
|
215 QApplication::sendEvent(focusWidget(), &modifiedEvent); |
|
216 return true; |
|
217 } |
|
218 break; |
|
219 case Qt::Key_Select: |
|
220 if (!m_preeditString.isEmpty()) { |
|
221 commitCurrentString(true); |
|
222 return true; |
|
223 } |
|
224 break; |
|
225 default: |
|
226 break; |
|
227 } |
|
228 |
|
229 if (keyEvent->type() == QEvent::KeyPress |
|
230 && focusWidget()->inputMethodHints() & Qt::ImhHiddenText |
|
231 && !keyEvent->text().isEmpty()) { |
|
232 // Send some temporary preedit text in order to make text visible for a moment. |
|
233 m_cursorPos = focusWidget()->inputMethodQuery(Qt::ImCursorPosition).toInt(); |
|
234 m_preeditString = keyEvent->text(); |
|
235 QList<QInputMethodEvent::Attribute> attributes; |
|
236 QInputMethodEvent imEvent(m_preeditString, attributes); |
|
237 sendEvent(imEvent); |
|
238 m_tempPreeditStringTimeout.start(1000, this); |
|
239 m_hasTempPreeditString = true; |
|
240 update(); |
|
241 return true; |
|
242 } |
|
243 break; |
|
244 } |
|
245 |
|
246 if (!needsInputPanel()) |
|
247 return false; |
|
248 |
|
249 if (event->type() == QEvent::RequestSoftwareInputPanel) { |
|
250 // Notify S60 that we want the virtual keyboard to show up. |
|
251 QSymbianControl *sControl; |
|
252 sControl = focusWidget()->effectiveWinId()->MopGetObject(sControl); |
|
253 Q_ASSERT(sControl); |
|
254 |
|
255 // The FEP UI temporarily steals focus when it shows up the first time, causing |
|
256 // all sorts of weird effects on the focused widgets. Since it will immediately give |
|
257 // back focus to us, we temporarily disable focus handling until the job's done. |
|
258 if (sControl) { |
|
259 sControl->setIgnoreFocusChanged(true); |
|
260 } |
|
261 |
|
262 ensureInputCapabilitiesChanged(); |
|
263 m_fepState->ReportAknEdStateEventL(MAknEdStateObserver::QT_EAknActivatePenInputRequest); |
|
264 |
|
265 if (sControl) { |
|
266 sControl->setIgnoreFocusChanged(false); |
|
267 } |
|
268 return true; |
|
269 } |
|
270 |
|
271 return false; |
|
272 } |
|
273 |
|
274 void QCoeFepInputContext::timerEvent(QTimerEvent *timerEvent) |
|
275 { |
|
276 if (timerEvent->timerId() == m_tempPreeditStringTimeout.timerId()) |
|
277 commitTemporaryPreeditString(); |
|
278 } |
|
279 |
|
280 void QCoeFepInputContext::commitTemporaryPreeditString() |
|
281 { |
|
282 if (m_tempPreeditStringTimeout.isActive()) |
|
283 m_tempPreeditStringTimeout.stop(); |
|
284 |
|
285 if (!m_hasTempPreeditString) |
|
286 return; |
|
287 |
|
288 commitCurrentString(false); |
|
289 } |
|
290 |
|
291 void QCoeFepInputContext::mouseHandler( int x, QMouseEvent *event) |
|
292 { |
|
293 Q_ASSERT(focusWidget()); |
|
294 |
|
295 if (event->type() == QEvent::MouseButtonPress && event->button() == Qt::LeftButton) { |
|
296 commitCurrentString(true); |
|
297 int pos = focusWidget()->inputMethodQuery(Qt::ImCursorPosition).toInt(); |
|
298 |
|
299 QList<QInputMethodEvent::Attribute> attributes; |
|
300 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, pos + x, 0, QVariant()); |
|
301 QInputMethodEvent event(QLatin1String(""), attributes); |
|
302 sendEvent(event); |
|
303 } |
|
304 } |
|
305 |
|
306 TCoeInputCapabilities QCoeFepInputContext::inputCapabilities() |
|
307 { |
|
308 if (m_inDestruction || !focusWidget()) { |
|
309 return TCoeInputCapabilities(TCoeInputCapabilities::ENone, 0, 0); |
|
310 } |
|
311 |
|
312 return TCoeInputCapabilities(m_textCapabilities, this, 0); |
|
313 } |
|
314 |
|
315 static QTextCharFormat qt_TCharFormat2QTextCharFormat(const TCharFormat &cFormat) |
|
316 { |
|
317 QTextCharFormat qFormat; |
|
318 |
|
319 QBrush foreground(QColor(cFormat.iFontPresentation.iTextColor.Internal())); |
|
320 qFormat.setForeground(foreground); |
|
321 |
|
322 qFormat.setFontStrikeOut(cFormat.iFontPresentation.iStrikethrough == EStrikethroughOn); |
|
323 qFormat.setFontUnderline(cFormat.iFontPresentation.iUnderline == EUnderlineOn); |
|
324 |
|
325 return qFormat; |
|
326 } |
|
327 |
|
328 void QCoeFepInputContext::updateHints(bool mustUpdateInputCapabilities) |
|
329 { |
|
330 QWidget *w = focusWidget(); |
|
331 if (w) { |
|
332 Qt::InputMethodHints hints = w->inputMethodHints(); |
|
333 if (hints != m_lastImHints) { |
|
334 m_lastImHints = hints; |
|
335 applyHints(hints); |
|
336 } else if (!mustUpdateInputCapabilities) { |
|
337 // Optimization. Return immediately if there was no change. |
|
338 return; |
|
339 } |
|
340 } |
|
341 queueInputCapabilitiesChanged(); |
|
342 } |
|
343 |
|
344 void QCoeFepInputContext::applyHints(Qt::InputMethodHints hints) |
|
345 { |
|
346 using namespace Qt; |
|
347 |
|
348 commitTemporaryPreeditString(); |
|
349 |
|
350 bool numbersOnly = hints & ImhDigitsOnly || hints & ImhFormattedNumbersOnly |
|
351 || hints & ImhDialableCharactersOnly; |
|
352 bool noOnlys = !(numbersOnly || hints & ImhUppercaseOnly |
|
353 || hints & ImhLowercaseOnly); |
|
354 TInt flags; |
|
355 Qt::InputMethodHints oldHints = hints; |
|
356 |
|
357 // Some sanity checking. Make sure that only one preference is set. |
|
358 InputMethodHints prefs = ImhPreferNumbers | ImhPreferUppercase | ImhPreferLowercase; |
|
359 prefs &= hints; |
|
360 if (prefs != ImhPreferNumbers && prefs != ImhPreferUppercase && prefs != ImhPreferLowercase) { |
|
361 hints &= ~prefs; |
|
362 } |
|
363 if (!noOnlys) { |
|
364 // Make sure that the preference is within the permitted set. |
|
365 if (hints & ImhPreferNumbers && !(hints & ImhDigitsOnly || hints & ImhFormattedNumbersOnly |
|
366 || hints & ImhDialableCharactersOnly)) { |
|
367 hints &= ~ImhPreferNumbers; |
|
368 } else if (hints & ImhPreferUppercase && !(hints & ImhUppercaseOnly)) { |
|
369 hints &= ~ImhPreferUppercase; |
|
370 } else if (hints & ImhPreferLowercase && !(hints & ImhLowercaseOnly)) { |
|
371 hints &= ~ImhPreferLowercase; |
|
372 } |
|
373 // If there is no preference, set it to something within the permitted set. |
|
374 if (!(hints & ImhPreferNumbers || hints & ImhPreferUppercase || hints & ImhPreferLowercase)) { |
|
375 if (hints & ImhLowercaseOnly) { |
|
376 hints |= ImhPreferLowercase; |
|
377 } else if (hints & ImhUppercaseOnly) { |
|
378 hints |= ImhPreferUppercase; |
|
379 } else if (hints & ImhDigitsOnly || hints & ImhFormattedNumbersOnly |
|
380 || hints & ImhDialableCharactersOnly) { |
|
381 hints |= ImhPreferNumbers; |
|
382 } |
|
383 } |
|
384 } |
|
385 |
|
386 if (hints & ImhPreferNumbers) { |
|
387 m_fepState->SetDefaultInputMode(EAknEditorNumericInputMode); |
|
388 m_fepState->SetCurrentInputMode(EAknEditorNumericInputMode); |
|
389 } else { |
|
390 m_fepState->SetDefaultInputMode(EAknEditorTextInputMode); |
|
391 m_fepState->SetCurrentInputMode(EAknEditorTextInputMode); |
|
392 } |
|
393 flags = 0; |
|
394 if (numbersOnly) { |
|
395 flags |= EAknEditorNumericInputMode; |
|
396 } |
|
397 if (hints & ImhUppercaseOnly || hints & ImhLowercaseOnly) { |
|
398 flags |= EAknEditorTextInputMode; |
|
399 } |
|
400 if (flags == 0) { |
|
401 flags = EAknEditorAllInputModes; |
|
402 } |
|
403 m_fepState->SetPermittedInputModes(flags); |
|
404 ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateInputModeUpdate); |
|
405 |
|
406 if (hints & ImhPreferLowercase) { |
|
407 m_fepState->SetDefaultCase(EAknEditorLowerCase); |
|
408 m_fepState->SetCurrentCase(EAknEditorLowerCase); |
|
409 } else if (hints & ImhPreferUppercase) { |
|
410 m_fepState->SetDefaultCase(EAknEditorUpperCase); |
|
411 m_fepState->SetCurrentCase(EAknEditorUpperCase); |
|
412 } else if (hints & ImhNoAutoUppercase) { |
|
413 m_fepState->SetDefaultCase(EAknEditorLowerCase); |
|
414 m_fepState->SetCurrentCase(EAknEditorLowerCase); |
|
415 } else { |
|
416 m_fepState->SetDefaultCase(EAknEditorTextCase); |
|
417 m_fepState->SetCurrentCase(EAknEditorTextCase); |
|
418 } |
|
419 flags = 0; |
|
420 if (hints & ImhUppercaseOnly) { |
|
421 flags |= EAknEditorUpperCase; |
|
422 } |
|
423 if (hints & ImhLowercaseOnly) { |
|
424 flags |= EAknEditorLowerCase; |
|
425 } |
|
426 if (flags == 0) { |
|
427 flags = EAknEditorAllCaseModes; |
|
428 if (hints & ImhNoAutoUppercase) { |
|
429 flags &= ~EAknEditorTextCase; |
|
430 } |
|
431 } |
|
432 m_fepState->SetPermittedCases(flags); |
|
433 ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateCaseModeUpdate); |
|
434 |
|
435 flags = 0; |
|
436 if (hints & ImhUppercaseOnly && !(hints & ImhLowercaseOnly) |
|
437 || hints & ImhLowercaseOnly && !(hints & ImhUppercaseOnly)) { |
|
438 flags |= EAknEditorFlagFixedCase; |
|
439 } |
|
440 // Using T9 and hidden text together may actually crash the FEP, so check for hidden text too. |
|
441 if (hints & ImhNoPredictiveText || hints & ImhHiddenText) { |
|
442 flags |= EAknEditorFlagNoT9; |
|
443 } |
|
444 m_fepState->SetFlags(flags); |
|
445 ReportAknEdStateEvent(MAknEdStateObserver::EAknEdwinStateFlagsUpdate); |
|
446 |
|
447 if (hints & ImhFormattedNumbersOnly) { |
|
448 flags = EAknEditorCalculatorNumberModeKeymap; |
|
449 } else if (hints & ImhDigitsOnly) { |
|
450 flags = EAknEditorPlainNumberModeKeymap; |
|
451 } else { |
|
452 // ImhDialableCharactersOnly is the fallback as well, so we don't need to check for |
|
453 // that flag. |
|
454 flags = EAknEditorStandardNumberModeKeymap; |
|
455 } |
|
456 m_fepState->SetNumericKeymap(static_cast<TAknEditorNumericKeymap>(flags)); |
|
457 |
|
458 if (hints & ImhEmailCharactersOnly) { |
|
459 m_fepState->SetSpecialCharacterTableResourceId(R_AVKON_EMAIL_ADDR_SPECIAL_CHARACTER_TABLE_DIALOG); |
|
460 } else if (hints & ImhUrlCharactersOnly) { |
|
461 m_fepState->SetSpecialCharacterTableResourceId(R_AVKON_URL_SPECIAL_CHARACTER_TABLE_DIALOG); |
|
462 } else { |
|
463 m_fepState->SetSpecialCharacterTableResourceId(R_AVKON_SPECIAL_CHARACTER_TABLE_DIALOG); |
|
464 } |
|
465 |
|
466 if (hints & ImhHiddenText) { |
|
467 m_textCapabilities = TCoeInputCapabilities::EAllText | TCoeInputCapabilities::ESecretText; |
|
468 } else { |
|
469 m_textCapabilities = TCoeInputCapabilities::EAllText; |
|
470 } |
|
471 } |
|
472 |
|
473 void QCoeFepInputContext::applyFormat(QList<QInputMethodEvent::Attribute> *attributes) |
|
474 { |
|
475 TCharFormat cFormat; |
|
476 QColor styleTextColor = QApplication::palette("QLineEdit").text().color(); |
|
477 TLogicalRgb tontColor(TRgb(styleTextColor.red(), styleTextColor.green(), styleTextColor.blue(), styleTextColor.alpha())); |
|
478 cFormat.iFontPresentation.iTextColor = tontColor; |
|
479 |
|
480 TInt numChars = 0; |
|
481 TInt charPos = 0; |
|
482 int oldSize = attributes->size(); |
|
483 while (m_formatRetriever) { |
|
484 m_formatRetriever->GetFormatOfFepInlineText(cFormat, numChars, charPos); |
|
485 if (numChars <= 0) { |
|
486 // This shouldn't happen according to S60 docs, but apparently does sometimes. |
|
487 break; |
|
488 } |
|
489 attributes->append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, |
|
490 charPos, |
|
491 numChars, |
|
492 QVariant(qt_TCharFormat2QTextCharFormat(cFormat)))); |
|
493 charPos += numChars; |
|
494 if (charPos >= m_preeditString.size()) { |
|
495 break; |
|
496 } |
|
497 } |
|
498 |
|
499 if (attributes->size() == oldSize) { |
|
500 // S60 didn't provide any format, so let's give our own instead. |
|
501 attributes->append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, |
|
502 0, |
|
503 m_preeditString.size(), |
|
504 standardFormat(PreeditFormat))); |
|
505 } |
|
506 } |
|
507 |
|
508 void QCoeFepInputContext::queueInputCapabilitiesChanged() |
|
509 { |
|
510 if (m_pendingInputCapabilitiesChanged) |
|
511 return; |
|
512 |
|
513 // Call ensureInputCapabilitiesChanged asynchronously. This is done to improve performance |
|
514 // by not updating input capabilities too often. The reason we don't call the Symbian |
|
515 // asynchronous version of InputCapabilitiesChanged is because we need to ensure that it |
|
516 // is synchronous in some specific cases. Those will call ensureInputCapabilitesChanged. |
|
517 QMetaObject::invokeMethod(this, "ensureInputCapabilitiesChanged", Qt::QueuedConnection); |
|
518 m_pendingInputCapabilitiesChanged = true; |
|
519 } |
|
520 |
|
521 void QCoeFepInputContext::ensureInputCapabilitiesChanged() |
|
522 { |
|
523 if (!m_pendingInputCapabilitiesChanged) |
|
524 return; |
|
525 |
|
526 // The call below is essentially equivalent to InputCapabilitiesChanged(), |
|
527 // but is synchronous, rather than asynchronous. |
|
528 CCoeEnv::Static()->SyncNotifyFocusObserversOfChangeInFocus(); |
|
529 m_pendingInputCapabilitiesChanged = false; |
|
530 } |
|
531 |
|
532 void QCoeFepInputContext::StartFepInlineEditL(const TDesC& aInitialInlineText, |
|
533 TInt aPositionOfInsertionPointInInlineText, TBool aCursorVisibility, const MFormCustomDraw* /*aCustomDraw*/, |
|
534 MFepInlineTextFormatRetriever& aInlineTextFormatRetriever, |
|
535 MFepPointerEventHandlerDuringInlineEdit& aPointerEventHandlerDuringInlineEdit) |
|
536 { |
|
537 QWidget *w = focusWidget(); |
|
538 if (!w) |
|
539 return; |
|
540 |
|
541 commitTemporaryPreeditString(); |
|
542 |
|
543 m_cursorPos = w->inputMethodQuery(Qt::ImCursorPosition).toInt(); |
|
544 |
|
545 QList<QInputMethodEvent::Attribute> attributes; |
|
546 |
|
547 m_cursorVisibility = aCursorVisibility ? 1 : 0; |
|
548 m_inlinePosition = aPositionOfInsertionPointInInlineText; |
|
549 m_preeditString = qt_TDesC2QString(aInitialInlineText); |
|
550 |
|
551 m_formatRetriever = &aInlineTextFormatRetriever; |
|
552 m_pointerHandler = &aPointerEventHandlerDuringInlineEdit; |
|
553 |
|
554 // With T9 aInitialInlineText is typically empty when StartFepInlineEditL is called, |
|
555 // but FEP requires that selected text is always removed at StartFepInlineEditL. |
|
556 // Let's remove the selected text if aInitialInlineText is empty and there is selected text |
|
557 if (m_preeditString.isEmpty()) { |
|
558 int anchor = w->inputMethodQuery(Qt::ImAnchorPosition).toInt(); |
|
559 int replacementLength = qAbs(m_cursorPos-anchor); |
|
560 if (replacementLength > 0) { |
|
561 int replacementStart = m_cursorPos < anchor ? 0 : -replacementLength; |
|
562 QList<QInputMethodEvent::Attribute> clearSelectionAttributes; |
|
563 QInputMethodEvent clearSelectionEvent(QLatin1String(""), clearSelectionAttributes); |
|
564 clearSelectionEvent.setCommitString(QLatin1String(""), replacementStart, replacementLength); |
|
565 sendEvent(clearSelectionEvent); |
|
566 } |
|
567 } |
|
568 |
|
569 applyFormat(&attributes); |
|
570 |
|
571 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, |
|
572 m_inlinePosition, |
|
573 m_cursorVisibility, |
|
574 QVariant())); |
|
575 QInputMethodEvent event(m_preeditString, attributes); |
|
576 sendEvent(event); |
|
577 } |
|
578 |
|
579 void QCoeFepInputContext::UpdateFepInlineTextL(const TDesC& aNewInlineText, |
|
580 TInt aPositionOfInsertionPointInInlineText) |
|
581 { |
|
582 QWidget *w = focusWidget(); |
|
583 if (!w) |
|
584 return; |
|
585 |
|
586 m_inlinePosition = aPositionOfInsertionPointInInlineText; |
|
587 |
|
588 QList<QInputMethodEvent::Attribute> attributes; |
|
589 applyFormat(&attributes); |
|
590 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, |
|
591 m_inlinePosition, |
|
592 m_cursorVisibility, |
|
593 QVariant())); |
|
594 m_preeditString = qt_TDesC2QString(aNewInlineText); |
|
595 QInputMethodEvent event(m_preeditString, attributes); |
|
596 sendEvent(event); |
|
597 } |
|
598 |
|
599 void QCoeFepInputContext::SetInlineEditingCursorVisibilityL(TBool aCursorVisibility) |
|
600 { |
|
601 QWidget *w = focusWidget(); |
|
602 if (!w) |
|
603 return; |
|
604 |
|
605 m_cursorVisibility = aCursorVisibility ? 1 : 0; |
|
606 |
|
607 QList<QInputMethodEvent::Attribute> attributes; |
|
608 attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, |
|
609 m_inlinePosition, |
|
610 m_cursorVisibility, |
|
611 QVariant())); |
|
612 QInputMethodEvent event(m_preeditString, attributes); |
|
613 sendEvent(event); |
|
614 } |
|
615 |
|
616 void QCoeFepInputContext::CancelFepInlineEdit() |
|
617 { |
|
618 QList<QInputMethodEvent::Attribute> attributes; |
|
619 QInputMethodEvent event(QLatin1String(""), attributes); |
|
620 event.setCommitString(QLatin1String(""), 0, 0); |
|
621 m_preeditString.clear(); |
|
622 sendEvent(event); |
|
623 } |
|
624 |
|
625 TInt QCoeFepInputContext::DocumentLengthForFep() const |
|
626 { |
|
627 QWidget *w = focusWidget(); |
|
628 if (!w) |
|
629 return 0; |
|
630 |
|
631 QVariant variant = w->inputMethodQuery(Qt::ImSurroundingText); |
|
632 return variant.value<QString>().size() + m_preeditString.size(); |
|
633 } |
|
634 |
|
635 TInt QCoeFepInputContext::DocumentMaximumLengthForFep() const |
|
636 { |
|
637 QWidget *w = focusWidget(); |
|
638 if (!w) |
|
639 return 0; |
|
640 |
|
641 QVariant variant = w->inputMethodQuery(Qt::ImMaximumTextLength); |
|
642 int size; |
|
643 if (variant.isValid()) { |
|
644 size = variant.toInt(); |
|
645 } else { |
|
646 size = INT_MAX; // Sensible default for S60. |
|
647 } |
|
648 return size; |
|
649 } |
|
650 |
|
651 void QCoeFepInputContext::SetCursorSelectionForFepL(const TCursorSelection& aCursorSelection) |
|
652 { |
|
653 QWidget *w = focusWidget(); |
|
654 if (!w) |
|
655 return; |
|
656 |
|
657 commitTemporaryPreeditString(); |
|
658 |
|
659 int pos = aCursorSelection.iAnchorPos; |
|
660 int length = aCursorSelection.iCursorPos - pos; |
|
661 |
|
662 QList<QInputMethodEvent::Attribute> attributes; |
|
663 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, pos, length, QVariant()); |
|
664 QInputMethodEvent event(m_preeditString, attributes); |
|
665 sendEvent(event); |
|
666 } |
|
667 |
|
668 void QCoeFepInputContext::GetCursorSelectionForFep(TCursorSelection& aCursorSelection) const |
|
669 { |
|
670 QWidget *w = focusWidget(); |
|
671 if (!w) { |
|
672 aCursorSelection.SetSelection(0,0); |
|
673 return; |
|
674 } |
|
675 |
|
676 int cursor = w->inputMethodQuery(Qt::ImCursorPosition).toInt() + m_preeditString.size(); |
|
677 int anchor = w->inputMethodQuery(Qt::ImAnchorPosition).toInt() + m_preeditString.size(); |
|
678 QString text = w->inputMethodQuery(Qt::ImSurroundingText).value<QString>(); |
|
679 int combinedSize = text.size() + m_preeditString.size(); |
|
680 if (combinedSize < anchor || combinedSize < cursor) { |
|
681 // ### TODO! FIXME! QTBUG-5050 |
|
682 // This is a hack to prevent crashing in 4.6 with QLineEdits that use input masks. |
|
683 // The root problem is that cursor position is relative to displayed text instead of the |
|
684 // actual text we get. |
|
685 // |
|
686 // To properly fix this we would need to know the displayText of QLineEdits instead |
|
687 // of just the text, which on itself should be a trivial change. The difficulties start |
|
688 // when we need to commit the changes back to the QLineEdit, which would have to be somehow |
|
689 // able to handle displayText, too. |
|
690 // |
|
691 // Until properly fixed, the cursor and anchor positions will not reflect correct positions |
|
692 // for masked QLineEdits, unless all the masked positions are filled in order so that |
|
693 // cursor position relative to the displayed text matches position relative to actual text. |
|
694 aCursorSelection.iAnchorPos = combinedSize; |
|
695 aCursorSelection.iCursorPos = combinedSize; |
|
696 } else { |
|
697 aCursorSelection.iAnchorPos = anchor; |
|
698 aCursorSelection.iCursorPos = cursor; |
|
699 } |
|
700 } |
|
701 |
|
702 void QCoeFepInputContext::GetEditorContentForFep(TDes& aEditorContent, TInt aDocumentPosition, |
|
703 TInt aLengthToRetrieve) const |
|
704 { |
|
705 QWidget *w = focusWidget(); |
|
706 if (!w) { |
|
707 aEditorContent.FillZ(aLengthToRetrieve); |
|
708 return; |
|
709 } |
|
710 |
|
711 QString text = w->inputMethodQuery(Qt::ImSurroundingText).value<QString>(); |
|
712 // FEP expects the preedit string to be part of the editor content, so let's mix it in. |
|
713 int cursor = w->inputMethodQuery(Qt::ImCursorPosition).toInt(); |
|
714 text.insert(cursor, m_preeditString); |
|
715 aEditorContent.Copy(qt_QString2TPtrC(text.mid(aDocumentPosition, aLengthToRetrieve))); |
|
716 } |
|
717 |
|
718 void QCoeFepInputContext::GetFormatForFep(TCharFormat& aFormat, TInt /* aDocumentPosition */) const |
|
719 { |
|
720 QWidget *w = focusWidget(); |
|
721 if (!w) { |
|
722 aFormat = TCharFormat(); |
|
723 return; |
|
724 } |
|
725 |
|
726 QFont font = w->inputMethodQuery(Qt::ImFont).value<QFont>(); |
|
727 QFontMetrics metrics(font); |
|
728 //QString name = font.rawName(); |
|
729 QString name = font.defaultFamily(); // TODO! FIXME! Should be the above. |
|
730 QHBufC hBufC(name); |
|
731 aFormat = TCharFormat(hBufC->Des(), metrics.height()); |
|
732 } |
|
733 |
|
734 void QCoeFepInputContext::GetScreenCoordinatesForFepL(TPoint& aLeftSideOfBaseLine, TInt& aHeight, |
|
735 TInt& aAscent, TInt /* aDocumentPosition */) const |
|
736 { |
|
737 QWidget *w = focusWidget(); |
|
738 if (!w) { |
|
739 aLeftSideOfBaseLine = TPoint(0,0); |
|
740 aHeight = 0; |
|
741 aAscent = 0; |
|
742 return; |
|
743 } |
|
744 |
|
745 QRect rect = w->inputMethodQuery(Qt::ImMicroFocus).value<QRect>(); |
|
746 aLeftSideOfBaseLine.iX = rect.left(); |
|
747 aLeftSideOfBaseLine.iY = rect.bottom(); |
|
748 |
|
749 QFont font = w->inputMethodQuery(Qt::ImFont).value<QFont>(); |
|
750 QFontMetrics metrics(font); |
|
751 aHeight = metrics.height(); |
|
752 aAscent = metrics.ascent(); |
|
753 } |
|
754 |
|
755 void QCoeFepInputContext::DoCommitFepInlineEditL() |
|
756 { |
|
757 commitCurrentString(false); |
|
758 if (QSysInfo::s60Version() > QSysInfo::SV_S60_5_0) |
|
759 ReportAknEdStateEvent(QT_EAknCursorPositionChanged); |
|
760 |
|
761 } |
|
762 |
|
763 void QCoeFepInputContext::commitCurrentString(bool cancelFepTransaction) |
|
764 { |
|
765 int longPress = 0; |
|
766 |
|
767 if (m_preeditString.size() == 0) { |
|
768 QWidget *w = focusWidget(); |
|
769 if (!cancelFepTransaction && w) { |
|
770 // We must replace the last character only if the input box has already accepted one |
|
771 if (w->inputMethodQuery(Qt::ImCursorPosition).toInt() != m_cursorPos) |
|
772 longPress = 1; |
|
773 } |
|
774 return; |
|
775 } |
|
776 |
|
777 QList<QInputMethodEvent::Attribute> attributes; |
|
778 QInputMethodEvent event(QLatin1String(""), attributes); |
|
779 event.setCommitString(m_preeditString, 0-longPress, longPress); |
|
780 m_preeditString.clear(); |
|
781 sendEvent(event); |
|
782 |
|
783 m_hasTempPreeditString = false; |
|
784 longPress = 0; |
|
785 |
|
786 if (cancelFepTransaction) { |
|
787 CCoeFep* fep = CCoeEnv::Static()->Fep(); |
|
788 if (fep) |
|
789 fep->CancelTransaction(); |
|
790 } |
|
791 } |
|
792 |
|
793 MCoeFepAwareTextEditor_Extension1* QCoeFepInputContext::Extension1(TBool& aSetToTrue) |
|
794 { |
|
795 aSetToTrue = ETrue; |
|
796 return this; |
|
797 } |
|
798 |
|
799 void QCoeFepInputContext::SetStateTransferingOwnershipL(MCoeFepAwareTextEditor_Extension1::CState* aState, |
|
800 TUid /*aTypeSafetyUid*/) |
|
801 { |
|
802 // Note: The S60 docs are wrong! See the State() function. |
|
803 if (m_fepState) |
|
804 delete m_fepState; |
|
805 m_fepState = static_cast<CAknEdwinState *>(aState); |
|
806 } |
|
807 |
|
808 MCoeFepAwareTextEditor_Extension1::CState* QCoeFepInputContext::State(TUid /*aTypeSafetyUid*/) |
|
809 { |
|
810 // Note: The S60 docs are horribly wrong when describing the |
|
811 // SetStateTransferingOwnershipL function and this function. They say that the former |
|
812 // sets a CState object identified by the TUid, and the latter retrieves it. |
|
813 // In reality, the CState is expected to always be a CAknEdwinState (even if it was not |
|
814 // previously set), and the TUid is ignored. All in all, there is a single CAknEdwinState |
|
815 // per QCoeFepInputContext, which should be deleted if the SetStateTransferingOwnershipL |
|
816 // function is used to set a new one. |
|
817 return m_fepState; |
|
818 } |
|
819 |
|
820 TTypeUid::Ptr QCoeFepInputContext::MopSupplyObject(TTypeUid /*id*/) |
|
821 { |
|
822 return TTypeUid::Null(); |
|
823 } |
|
824 |
|
825 MObjectProvider *QCoeFepInputContext::MopNext() |
|
826 { |
|
827 QWidget *w = focusWidget(); |
|
828 if (w) |
|
829 return w->effectiveWinId(); |
|
830 return 0; |
|
831 } |
|
832 |
|
833 QT_END_NAMESPACE |
|
834 |
|
835 #endif // QT_NO_IM |