Android: Add select and copy capability to read-only text widgets

In case of a read-only text editing widget it was imposibble to copy text
from it. In QtWidgets you could not even see the selection handless.
Text selection in QtWidgets module was filtered depending on readOnly
property of the widget. Additionally, in InputMethod the read-only state
was translated into disabled.

Patch also makes the edit menu to be aware of the read-only status of
the control - the menu items are different for rw and ro controls.

Task-number: QTBUG-91417
Change-Id: Ic7b27f78678eeaa87a38607af787f254db1383b8
Reviewed-by: Rami Potinkara <rami.potinkara@qt.io>
Reviewed-by: Ville Voutilainen <ville.voutilainen@qt.io>
bb10
Piotr Mikolajczyk 2021-02-25 15:36:29 +01:00
parent 9275edbc31
commit c80f262258
12 changed files with 99 additions and 22 deletions

View File

@ -566,9 +566,7 @@ public class QtActivityDelegate
break;
}
if (QtNative.hasClipboardText())
editButtons |= EditContextView.PASTE_BUTTON;
else
if (!QtNative.hasClipboardText())
editButtons &= ~EditContextView.PASTE_BUTTON;
if ((mode & CursorHandleShowEdit) == CursorHandleShowEdit && editButtons != 0) {

View File

@ -1394,6 +1394,7 @@ namespace Qt {
ImAnchorRectangle = 0x4000,
ImInputItemClipRectangle = 0x8000,
ImReadOnly = 0x10000,
ImPlatformData = 0x80000000,
ImQueryInput = ImCursorRectangle | ImCursorPosition | ImSurroundingText |
ImCurrentSelection | ImAnchorRectangle | ImAnchorPosition,

View File

@ -604,34 +604,34 @@ void QAndroidInputContext::updateCursorPosition()
void QAndroidInputContext::updateSelectionHandles()
{
static bool noHandles = qEnvironmentVariableIntValue("QT_QPA_NO_TEXT_HANDLES");
if (noHandles)
if (noHandles || !m_focusObject)
return;
auto im = qGuiApp->inputMethod();
if (!m_focusObject || ((m_handleMode & 0xff) == Hidden)) {
// Hide the handles
QtAndroidInput::updateHandles(Hidden);
return;
}
QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition | Qt::ImEnabled | Qt::ImCurrentSelection | Qt::ImHints | Qt::ImSurroundingText);
QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition | Qt::ImEnabled
| Qt::ImCurrentSelection | Qt::ImHints | Qt::ImSurroundingText
| Qt::ImReadOnly);
QCoreApplication::sendEvent(m_focusObject, &query);
int cpos = query.value(Qt::ImCursorPosition).toInt();
int anchor = query.value(Qt::ImAnchorPosition).toInt();
const QVariant readOnlyVariant = query.value(Qt::ImReadOnly);
bool readOnly = readOnlyVariant.toBool();
if ( cpos == anchor && (!readOnlyVariant.isValid() || readOnly)) {
QtAndroidInput::updateHandles(Hidden);
return;
}
if (cpos == anchor || im->anchorRectangle().isNull()) {
if (!query.value(Qt::ImEnabled).toBool()) {
QtAndroidInput::updateHandles(Hidden);
return;
}
auto curRect = cursorRectangle();
QPoint cursorPoint = qGuiApp->focusWindow()->handle()->mapToGlobal(QPoint(curRect.x() + (curRect.width() / 2), curRect.y() + curRect.height()));
QPoint editMenuPoint(cursorPoint.x(), cursorPoint.y());
m_handleMode &= ShowEditPopup;
m_handleMode |= ShowCursor;
uint32_t buttons = EditContext::PasteButton;
uint32_t buttons = readOnly ? 0 : EditContext::PasteButton;
if (!query.value(Qt::ImSurroundingText).toString().isEmpty())
buttons |= EditContext::SelectAllButton;
QtAndroidInput::updateHandles(m_handleMode, editMenuPoint, buttons, cursorPoint);
@ -650,7 +650,10 @@ void QAndroidInputContext::updateSelectionHandles()
QPoint leftPoint(leftRect.bottomLeft().toPoint());
QPoint righPoint(rightRect.bottomRight().toPoint());
QPoint editPoint(leftRect.united(rightRect).topLeft().toPoint());
QtAndroidInput::updateHandles(m_handleMode, editPoint, EditContext::AllButtons, leftPoint, righPoint,
uint32_t buttons = readOnly ? EditContext::CopyButton | EditContext::SelectAllButton
: EditContext::AllButtons;
QtAndroidInput::updateHandles(m_handleMode, editPoint, buttons, leftPoint, righPoint,
query.value(Qt::ImCurrentSelection).toString().isRightToLeft());
m_hideCursorHandleTimer.stop();
}

View File

@ -1806,7 +1806,7 @@ QRect QLineEdit::cursorRect() const
void QLineEdit::inputMethodEvent(QInputMethodEvent *e)
{
Q_D(QLineEdit);
if (d->control->isReadOnly()) {
if (!d->shouldEnableInputMethod()) {
e->ignore();
return;
}
@ -1874,6 +1874,20 @@ QVariant QLineEdit::inputMethodQuery(Qt::InputMethodQuery property, QVariant arg
return QVariant(d->control->selectionEnd());
else
return QVariant(d->control->selectionStart());
case Qt::ImReadOnly:
return isReadOnly();
case Qt::ImTextBeforeCursor: {
const QPointF pt = argument.toPointF();
if (!pt.isNull())
return d->textBeforeCursor(d->xToPos(pt.x(), QTextLine::CursorBetweenCharacters));
else
return d->textBeforeCursor(d->control->cursor()); }
case Qt::ImTextAfterCursor: {
const QPointF pt = argument.toPointF();
if (!pt.isNull())
return d->textAfterCursor(d->xToPos(pt.x(), QTextLine::CursorBetweenCharacters));
else
return d->textAfterCursor(d->control->cursor()); }
default:
return QWidget::inputMethodQuery(property);
}

View File

@ -86,6 +86,18 @@ int QLineEditPrivate::xToPos(int x, QTextLine::CursorPosition betweenOrOn) const
return control->xToPos(x, betweenOrOn);
}
QString QLineEditPrivate::textBeforeCursor(int curPos) const
{
const QString &text = control->text();
return text.mid(0, curPos);
}
QString QLineEditPrivate::textAfterCursor(int curPos) const
{
const QString &text = control->text();
return text.mid(curPos);
}
bool QLineEditPrivate::inSelection(int x) const
{
x -= adjustedContentsRect().x() - hscroll + horizontalMargin;

View File

@ -179,13 +179,19 @@ public:
void setCursorVisible(bool visible);
void setText(const QString& text);
QString textBeforeCursor(int curPos) const;
QString textAfterCursor(int curPos) const;
void updatePasswordEchoEditing(bool);
void resetInputMethod();
inline bool shouldEnableInputMethod() const
{
#if defined (Q_OS_ANDROID)
return !control->isReadOnly() || control->isSelectableByMouse();
#else
return !control->isReadOnly();
#endif
}
inline bool shouldShowPlaceholderText() const
{

View File

@ -71,9 +71,14 @@
QT_BEGIN_NAMESPACE
static inline bool shouldEnableInputMethod(QPlainTextEdit *plaintextedit)
static inline bool shouldEnableInputMethod(QPlainTextEdit *control)
{
return !plaintextedit->isReadOnly();
#if defined(Q_OS_ANDROID)
Q_UNUSED(control);
return !control->isReadOnly() || (control->textInteractionFlags() & Qt::TextSelectableByMouse);
#else
return !control->isReadOnly();
#endif
}
class QPlainTextDocumentLayoutPrivate : public QAbstractTextDocumentLayoutPrivate
@ -2238,6 +2243,8 @@ QVariant QPlainTextEdit::inputMethodQuery(Qt::InputMethodQuery query, QVariant a
case Qt::ImHints:
case Qt::ImInputItemClipRectangle:
return QWidget::inputMethodQuery(query);
case Qt::ImReadOnly:
return isReadOnly();
default:
break;
}

View File

@ -57,6 +57,15 @@
QT_BEGIN_NAMESPACE
static inline bool shouldEnableInputMethod(QTextBrowser *texbrowser)
{
#if defined (Q_OS_ANDROID)
return !texbrowser->isReadOnly() || (texbrowser->textInteractionFlags() & Qt::TextSelectableByMouse);
#else
return !texbrowser->isReadOnly();
#endif
}
Q_LOGGING_CATEGORY(lcBrowser, "qt.text.browser")
class QTextBrowserPrivate : public QTextEditPrivate
@ -692,7 +701,7 @@ void QTextBrowserPrivate::init()
#ifndef QT_NO_CURSOR
viewport->setCursor(oldCursor);
#endif
q->setAttribute(Qt::WA_InputMethodEnabled, !q->isReadOnly());
q->setAttribute(Qt::WA_InputMethodEnabled, shouldEnableInputMethod(q));
q->setUndoRedoEnabled(false);
viewport->setMouseTracking(true);
QObject::connect(q->document(), SIGNAL(contentsChanged()), q, SLOT(_q_documentModified()));

View File

@ -79,7 +79,11 @@ QT_BEGIN_NAMESPACE
static inline bool shouldEnableInputMethod(QTextEdit *textedit)
{
#if defined (Q_OS_ANDROID)
return !textedit->isReadOnly() || (textedit->textInteractionFlags() & Qt::TextSelectableByMouse);
#else
return !textedit->isReadOnly();
#endif
}
class QTextEditControl : public QWidgetTextControl
@ -1832,6 +1836,8 @@ QVariant QTextEdit::inputMethodQuery(Qt::InputMethodQuery query, QVariant argume
case Qt::ImHints:
case Qt::ImInputItemClipRectangle:
return QWidget::inputMethodQuery(query);
case Qt::ImReadOnly:
return isReadOnly();
default:
break;
}

View File

@ -164,6 +164,9 @@ public:
int selectionStart() const { return hasSelectedText() ? m_selstart : -1; }
int selectionEnd() const { return hasSelectedText() ? m_selend : -1; }
#if defined (Q_OS_ANDROID)
bool isSelectableByMouse() const { return true; }
#endif
bool inSelection(int x) const
{
if (m_selstart >= m_selend)

View File

@ -2039,7 +2039,7 @@ bool QWidgetTextControlPrivate::dropEvent(const QMimeData *mimeData, const QPoin
void QWidgetTextControlPrivate::inputMethodEvent(QInputMethodEvent *e)
{
Q_Q(QWidgetTextControl);
if (!(interactionFlags & Qt::TextEditable) || cursor.isNull()) {
if (!(interactionFlags & (Qt::TextEditable | Qt::TextSelectableByMouse)) || cursor.isNull()) {
e->ignore();
return;
}

View File

@ -466,11 +466,29 @@ void tst_QTextBrowser::textInteractionFlags_vs_readOnly()
void tst_QTextBrowser::inputMethodAttribute_vs_readOnly()
{
QVERIFY(browser->isReadOnly());
#if defined(Q_OS_ANDROID)
QInputMethodQueryEvent query(Qt::ImReadOnly);
QCoreApplication::sendEvent(browser, &query);
QVERIFY(query.value(Qt::ImReadOnly).toBool());
#else
QVERIFY(!browser->testAttribute(Qt::WA_InputMethodEnabled));
#endif
browser->setReadOnly(false);
#if defined(Q_OS_ANDROID)
QCoreApplication::sendEvent(browser, &query);
QVERIFY(!query.value(Qt::ImReadOnly).toBool());
#else
QVERIFY(browser->testAttribute(Qt::WA_InputMethodEnabled));
#endif
browser->setReadOnly(true);
#if defined(Q_OS_ANDROID)
QCoreApplication::sendEvent(browser, &query);
QVERIFY(query.value(Qt::ImReadOnly).toBool());
#else
QVERIFY(!browser->testAttribute(Qt::WA_InputMethodEnabled));
#endif
}
void tst_QTextBrowser::anchorsWithSelfBuiltHtml()