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>bb10
parent
8899074d72
commit
288a2ada5b
|
|
@ -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();
|
||||
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;
|
||||
|
||||
if (m_editMenu == null)
|
||||
m_editMenu = new EditMenu(m_activity);
|
||||
m_editMenu.show();
|
||||
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;
|
||||
}
|
||||
|
||||
// show the edit popup menu
|
||||
if (mode == CursorHandleShowPopup && (m_editMenu == null || !m_editMenu.isShown())
|
||||
&& QtNative.hasClipboardText()) {
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue