diff options
author | BogDan Vatra <bogdan@kdab.com> | 2017-12-15 14:56:32 +0200 |
---|---|---|
committer | BogDan Vatra <bogdan@kdab.com> | 2018-03-14 18:24:18 +0000 |
commit | 288a2ada5bbbbb6ea1b5dcacd2d1678896957dba (patch) | |
tree | a8c1791d9692b4c1ca531cbea394fd9831fe8f22 /src | |
parent | 8899074d725da843ea5f7202e8c45a4029f1d3b2 (diff) |
Refactor cursor & selection handles
- turn CursorHandleShowMode in a QFlag for better control
- add ScopedValueChangeBack which is useful to control m_blockUpdateSelection
- implement longPress word selection
Change-Id: Ieed709644db991e10077d5be5d5a59f16f4fc3a8
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
Diffstat (limited to 'src')
3 files changed, 155 insertions, 93 deletions
diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java b/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java index fa7508921d..517b79f01e 100644 --- a/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java +++ b/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java @@ -484,10 +484,11 @@ public class QtActivityDelegate } // Values coming from QAndroidInputContext::CursorHandleShowMode - private static final int CursorHandleNotShown = 0; - private static final int CursorHandleShowNormal = 1; - private static final int CursorHandleShowSelection = 2; - private static final int CursorHandleShowPopup = 3; + private static final int CursorHandleNotShown = 0; + private static final int CursorHandleShowNormal = 1; + private static final int CursorHandleShowSelection = 2; + private static final int CursorHandleShowEdit = 0x100; + private static final int CursorHandleShowPopup = 0x200; /* called from the C++ code when the position of the cursor or selection handles needs to be adjusted. @@ -495,55 +496,70 @@ public class QtActivityDelegate */ public void updateHandles(int mode, int x1, int y1, int x2, int y2, boolean rtl) { - if (mode == CursorHandleNotShown) { - if (m_cursorHandle != null) - m_cursorHandle.hide(); - if (m_rightSelectionHandle != null) { - m_rightSelectionHandle.hide(); - m_leftSelectionHandle.hide(); - m_rightSelectionHandle = null; - m_leftSelectionHandle = null; - } - if (m_editMenu != null) - m_editMenu.hide(); - if (m_editPopupMenu != null) - m_editPopupMenu.hide(); - } else if (mode == CursorHandleShowNormal || mode == CursorHandleShowPopup) { - if (m_cursorHandle == null) { - m_cursorHandle = new CursorHandle(m_activity, m_layout, QtNative.IdCursorHandle, - android.R.attr.textSelectHandle, false); - } - m_cursorHandle.setPosition(x1, y1); - if (m_rightSelectionHandle != null) { - m_rightSelectionHandle.hide(); - m_leftSelectionHandle.hide(); - m_rightSelectionHandle = null; - m_leftSelectionHandle = null; - } - } else if (mode == CursorHandleShowSelection) { - if (m_rightSelectionHandle == null) { - m_leftSelectionHandle = new CursorHandle(m_activity, m_layout, QtNative.IdLeftHandle, - !rtl ? android.R.attr.textSelectHandleLeft : - android.R.attr.textSelectHandleRight, - rtl); - m_rightSelectionHandle = new CursorHandle(m_activity, m_layout, QtNative.IdRightHandle, - !rtl ? android.R.attr.textSelectHandleRight : - android.R.attr.textSelectHandleLeft, - rtl); - } - m_leftSelectionHandle.setPosition(x1,y1); - m_rightSelectionHandle.setPosition(x2,y2); - if (m_cursorHandle != null) - m_cursorHandle.hide(); - - if (m_editMenu == null) - m_editMenu = new EditMenu(m_activity); - m_editMenu.show(); - } + switch (mode & 0xff) + { + case CursorHandleNotShown: + if (m_cursorHandle != null) { + m_cursorHandle.hide(); + m_cursorHandle = null; + } + if (m_rightSelectionHandle != null) { + m_rightSelectionHandle.hide(); + m_leftSelectionHandle.hide(); + m_rightSelectionHandle = null; + m_leftSelectionHandle = null; + } + if (m_editMenu != null) { + m_editMenu.hide(); + m_editMenu = null; + } + if (m_editPopupMenu != null) { + m_editPopupMenu.hide(); + m_editPopupMenu = null; + } + break; - // show the edit popup menu - if (mode == CursorHandleShowPopup && (m_editMenu == null || !m_editMenu.isShown()) - && QtNative.hasClipboardText()) { + case CursorHandleShowNormal: + if (m_editMenu != null) { + m_editMenu.hide(); + m_editMenu = null; + } + if (m_cursorHandle == null) { + m_cursorHandle = new CursorHandle(m_activity, m_layout, QtNative.IdCursorHandle, + android.R.attr.textSelectHandle, false); + } + m_cursorHandle.setPosition(x1, y1); + if (m_rightSelectionHandle != null) { + m_rightSelectionHandle.hide(); + m_leftSelectionHandle.hide(); + m_rightSelectionHandle = null; + m_leftSelectionHandle = null; + } + break; + + case CursorHandleShowSelection: + if (m_rightSelectionHandle == null) { + m_leftSelectionHandle = new CursorHandle(m_activity, m_layout, QtNative.IdLeftHandle, + !rtl ? android.R.attr.textSelectHandleLeft : + android.R.attr.textSelectHandleRight, + rtl); + m_rightSelectionHandle = new CursorHandle(m_activity, m_layout, QtNative.IdRightHandle, + !rtl ? android.R.attr.textSelectHandleRight : + android.R.attr.textSelectHandleLeft, + rtl); + } + m_leftSelectionHandle.setPosition(x1,y1); + m_rightSelectionHandle.setPosition(x2,y2); + if (m_cursorHandle != null) { + m_cursorHandle.hide(); + m_cursorHandle = null; + } + if (m_editMenu == null) + m_editMenu = new EditMenu(m_activity); + m_editMenu.show(); + break; + } + if ((mode & CursorHandleShowPopup) == CursorHandleShowPopup && QtNative.hasClipboardText()) { if (m_editPopupMenu == null) m_editPopupMenu = new EditPopupMenu(m_activity, m_layout); if (y2 < m_editPopupMenu.getHeight()) { @@ -553,8 +569,8 @@ public class QtActivityDelegate m_editPopupMenu.setPosition(x2, y2); } else if (m_editPopupMenu != null) { m_editPopupMenu.hide(); + m_editPopupMenu = null; } - } public boolean loadApplication(Activity activity, ClassLoader classLoader, Bundle loaderParams) diff --git a/src/plugins/platforms/android/qandroidinputcontext.cpp b/src/plugins/platforms/android/qandroidinputcontext.cpp index f548a1fa96..fe35c13e26 100644 --- a/src/plugins/platforms/android/qandroidinputcontext.cpp +++ b/src/plugins/platforms/android/qandroidinputcontext.cpp @@ -63,6 +63,29 @@ QT_BEGIN_NAMESPACE +template <typename T> +class ScopedValueChangeBack +{ +public: + ScopedValueChangeBack(T &variable, T newValue) + : m_oldValue(variable), + m_variable(variable) + { + m_variable = newValue; + } + inline void setOldValue() + { + m_variable = m_oldValue; + } + ~ScopedValueChangeBack() + { + setOldValue(); + } +private: + T m_oldValue; + T &m_variable; +}; + static QAndroidInputContext *m_androidInputContext = 0; static char const *const QtNativeInputConnectionClassName = "org/qtproject/qt5/android/QtNativeInputConnection"; static char const *const QtExtractedTextClassName = "org/qtproject/qt5/android/QtExtractedText"; @@ -364,7 +387,7 @@ static QRect inputItemRectangle() QAndroidInputContext::QAndroidInputContext() : QPlatformInputContext(), m_composingTextStart(-1), m_blockUpdateSelection(false), - m_cursorHandleShown(CursorHandleNotShown), m_batchEditNestingLevel(0), m_focusObject(0) + m_handleMode(Hidden), m_batchEditNestingLevel(0), m_focusObject(0) { jclass clazz = QJNIEnvironmentPrivate::findClass(QtNativeInputConnectionClassName); if (Q_UNLIKELY(!clazz)) { @@ -444,7 +467,7 @@ QAndroidInputContext::QAndroidInputContext() auto im = qGuiApp->inputMethod(); if (!im->inputItemClipRectangle().contains(im->anchorRectangle()) || !im->inputItemClipRectangle().contains(im->cursorRectangle())) { - m_cursorHandleShown = CursorHandleNotShown; + m_handleMode = Hidden; updateSelectionHandles(); } }); @@ -485,7 +508,7 @@ void QAndroidInputContext::reset() { clear(); m_batchEditNestingLevel = 0; - m_cursorHandleShown = QAndroidInputContext::CursorHandleNotShown; + m_handleMode = Hidden; if (qGuiApp->focusObject()) { QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQueryThreadSafe(Qt::ImEnabled); if (!query.isNull() && query->value(Qt::ImEnabled).toBool()) { @@ -541,9 +564,9 @@ void QAndroidInputContext::updateSelectionHandles() return; auto im = qGuiApp->inputMethod(); - if (!m_focusObject || (m_cursorHandleShown == CursorHandleNotShown)) { + if (!m_focusObject || ((m_handleMode & 0xff) == Hidden)) { // Hide the handles - QtAndroidInput::updateHandles(0); + QtAndroidInput::updateHandles(Hidden); return; } QWindow *window = qGuiApp->focusWindow(); @@ -558,14 +581,14 @@ void QAndroidInputContext::updateSelectionHandles() if (cpos == anchor || im->anchorRectangle().isNull()) { if (!query.value(Qt::ImEnabled).toBool()) { - QtAndroidInput::updateHandles(0); + QtAndroidInput::updateHandles(Hidden); return; } auto curRect = im->cursorRectangle(); QPoint cursorPoint(curRect.center().x(), curRect.bottom()); QPoint editMenuPoint(curRect.center().x(), curRect.top()); - QtAndroidInput::updateHandles(m_cursorHandleShown, cursorPoint * pixelDensity, + QtAndroidInput::updateHandles(m_handleMode, cursorPoint * pixelDensity, editMenuPoint * pixelDensity); return; } @@ -577,13 +600,8 @@ void QAndroidInputContext::updateSelectionHandles() QPoint leftPoint(leftRect.bottomLeft().toPoint() * pixelDensity); QPoint righPoint(rightRect.bottomRight().toPoint() * pixelDensity); - QtAndroidInput::updateHandles(CursorHandleShowSelection, leftPoint, righPoint, + QtAndroidInput::updateHandles(ShowSelection, leftPoint, righPoint, query.value(Qt::ImCurrentSelection).toString().isRightToLeft()); - - if (m_cursorHandleShown == CursorHandleShowPopup) { - // make sure the popup does not reappear when the selection menu closes - m_cursorHandleShown = QAndroidInputContext::CursorHandleNotShown; - } } /* @@ -668,27 +686,57 @@ void QAndroidInputContext::handleLocationChanged(int handleId, int x, int y) void QAndroidInputContext::touchDown(int x, int y) { - if (m_focusObject && inputItemRectangle().contains(x, y) && !m_cursorHandleShown) { + if (m_focusObject && inputItemRectangle().contains(x, y)) { // If the user touch the input rectangle, we can show the cursor handle - m_cursorHandleShown = QAndroidInputContext::CursorHandleShowNormal; - updateSelectionHandles(); + m_handleMode = ShowCursor; + QMetaObject::invokeMethod(this, "updateSelectionHandles", Qt::QueuedConnection); } } void QAndroidInputContext::longPress(int x, int y) { if (m_focusObject && inputItemRectangle().contains(x, y)) { - // Show the paste menu if there is something to paste. - m_cursorHandleShown = QAndroidInputContext::CursorHandleShowPopup; - updateSelectionHandles(); + handleLocationChanged(1, x, y); + ScopedValueChangeBack<bool> svcb(m_blockUpdateSelection, true); + + QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition | Qt::ImTextBeforeCursor | Qt::ImTextAfterCursor); + QCoreApplication::sendEvent(m_focusObject, &query); + int cursor = query.value(Qt::ImCursorPosition).toInt(); + int anchor = cursor; + QString before = query.value(Qt::ImTextBeforeCursor).toString(); + QString after = query.value(Qt::ImTextAfterCursor).toString(); + for (const auto &ch : after) { + if (!ch.isLetterOrNumber()) + break; + ++anchor; + } + + for (auto itch = before.rbegin(); itch != after.rend(); ++itch) { + if (!itch->isLetterOrNumber()) + break; + --cursor; + } + if (cursor == anchor || cursor < 0 || cursor - anchor > 500) { + m_handleMode = ShowCursor | ShowEditPopup; + QMetaObject::invokeMethod(this, "updateSelectionHandles", Qt::QueuedConnection); + return; + } + QList<QInputMethodEvent::Attribute> imAttributes; + imAttributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, cursor, 0, QVariant())); + imAttributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection, anchor, cursor - anchor, QVariant())); + QInputMethodEvent event(QString(), imAttributes); + QGuiApplication::sendEvent(m_focusObject, &event); + + m_handleMode = ShowSelection | ShowEditPopup; + QMetaObject::invokeMethod(this, "updateSelectionHandles", Qt::QueuedConnection); } } void QAndroidInputContext::keyDown() { - if (m_cursorHandleShown) { + if (m_handleMode) { // When the user enter text on the keyboard, we hide the cursor handle - m_cursorHandleShown = QAndroidInputContext::CursorHandleNotShown; + m_handleMode = Hidden; updateSelectionHandles(); } } @@ -809,9 +857,7 @@ jboolean QAndroidInputContext::endBatchEdit() */ jboolean QAndroidInputContext::commitText(const QString &text, jint newCursorPosition) { - bool updateSelectionWasBlocked = m_blockUpdateSelection; - m_blockUpdateSelection = true; - + ScopedValueChangeBack<bool> svcb(m_blockUpdateSelection, true); QInputMethodEvent event; event.setCommitString(text); sendInputMethodEventThreadSafe(&event); @@ -831,8 +877,7 @@ jboolean QAndroidInputContext::commitText(const QString &text, jint newCursorPos newLocalPos, 0)); } } - m_blockUpdateSelection = updateSelectionWasBlocked; - + svcb.setOldValue(); updateCursorPosition(); return JNI_TRUE; } @@ -1083,8 +1128,7 @@ jboolean QAndroidInputContext::setComposingRegion(jint start, jint end) int localStart = start - blockPosition; // Qt uses position inside block int currentCursor = wasComposing ? m_composingCursor : blockPosition + localPos; - bool updateSelectionWasBlocked = m_blockUpdateSelection; - m_blockUpdateSelection = true; + ScopedValueChangeBack<bool> svcb(m_blockUpdateSelection, true); QString text = query->value(Qt::ImSurroundingText).toString(); @@ -1110,8 +1154,6 @@ jboolean QAndroidInputContext::setComposingRegion(jint start, jint end) event.setCommitString(QString(), relativeStart, length); sendInputMethodEventThreadSafe(&event); - m_blockUpdateSelection = updateSelectionWasBlocked; - #ifdef QT_DEBUG_ANDROID_IM_PROTOCOL QSharedPointer<QInputMethodQueryEvent> query2 = focusObjectInputMethodQueryThreadSafe(); if (!query2.isNull()) { @@ -1163,19 +1205,21 @@ jboolean QAndroidInputContext::setSelection(jint start, jint end) jboolean QAndroidInputContext::selectAll() { + m_handleMode &= ~ShowEditPopup; sendShortcut(QKeySequence::SelectAll); return JNI_TRUE; } jboolean QAndroidInputContext::cut() { - m_cursorHandleShown = CursorHandleNotShown; + m_handleMode &= ~ShowEditPopup; sendShortcut(QKeySequence::Cut); return JNI_TRUE; } jboolean QAndroidInputContext::copy() { + m_handleMode &= ~ShowEditPopup; sendShortcut(QKeySequence::Copy); return JNI_TRUE; } @@ -1189,7 +1233,7 @@ jboolean QAndroidInputContext::copyURL() jboolean QAndroidInputContext::paste() { finishComposingText(); - m_cursorHandleShown = CursorHandleNotShown; + m_handleMode &= ~ShowEditPopup; sendShortcut(QKeySequence::Paste); return JNI_TRUE; } diff --git a/src/plugins/platforms/android/qandroidinputcontext.h b/src/plugins/platforms/android/qandroidinputcontext.h index e7692bf720..65822b0d60 100644 --- a/src/plugins/platforms/android/qandroidinputcontext.h +++ b/src/plugins/platforms/android/qandroidinputcontext.h @@ -58,6 +58,14 @@ class QAndroidInputContext: public QPlatformInputContext }; public: + enum HandleMode { + Hidden = 0, + ShowCursor = 1, + ShowSelection = 2, + ShowEditPopup = 0x100 + }; + Q_DECLARE_FLAGS(HandleModes, HandleMode) + struct ExtractedText { ExtractedText() { clear(); } @@ -145,17 +153,11 @@ private: int m_composingCursor; QMetaObject::Connection m_updateCursorPosConnection; bool m_blockUpdateSelection; - enum CursorHandleShowMode { - CursorHandleNotShown, - CursorHandleShowNormal = 1, - CursorHandleShowSelection = 2, - CursorHandleShowPopup = 3 - }; - CursorHandleShowMode m_cursorHandleShown; + HandleModes m_handleMode; QAtomicInt m_batchEditNestingLevel; QObject *m_focusObject; }; - +Q_DECLARE_OPERATORS_FOR_FLAGS(QAndroidInputContext::HandleModes) QT_END_NAMESPACE #endif // ANDROIDINPUTCONTEXT_H |