diff options
author | Liang Qi <liang.qi@qt.io> | 2019-06-20 07:50:44 +0200 |
---|---|---|
committer | Liang Qi <liang.qi@qt.io> | 2019-06-20 07:50:44 +0200 |
commit | bd9959bde290168b0635cb878d7d189e1f9c4b4f (patch) | |
tree | 1a4d5a0eff8d6acaff26ae4ff4ded8ca71b76a06 /src/plugins | |
parent | b877285694501d16b2bb0dc8d1a92e185b079b87 (diff) | |
parent | 6398588338dfea4161704e01216ba70b216a7a0b (diff) |
Merge remote-tracking branch 'origin/5.12' into 5.13
Conflicts:
.qmake.conf
src/gui/painting/qdrawhelper.cpp
src/network/ssl/qsslsocket_openssl.cpp
src/widgets/styles/qstylesheetstyle.cpp
Change-Id: Ibe1cd40f46a823c9e5edbe0a3cd16be1e1686b17
Diffstat (limited to 'src/plugins')
14 files changed, 587 insertions, 283 deletions
diff --git a/src/plugins/bearer/qnetworksession_impl.cpp b/src/plugins/bearer/qnetworksession_impl.cpp index a09ae72cb5..c6b678ab20 100644 --- a/src/plugins/bearer/qnetworksession_impl.cpp +++ b/src/plugins/bearer/qnetworksession_impl.cpp @@ -56,12 +56,13 @@ QT_BEGIN_NAMESPACE static QBearerEngineImpl *getEngineFromId(const QString &id) { QNetworkConfigurationManagerPrivate *priv = qNetworkConfigurationManagerPrivate(); - - const auto engines = priv->engines(); - for (QBearerEngine *engine : engines) { - QBearerEngineImpl *engineImpl = qobject_cast<QBearerEngineImpl *>(engine); - if (engineImpl && engineImpl->hasIdentifier(id)) - return engineImpl; + if (priv) { + const auto engines = priv->engines(); + for (QBearerEngine *engine : engines) { + QBearerEngineImpl *engineImpl = qobject_cast<QBearerEngineImpl *>(engine); + if (engineImpl && engineImpl->hasIdentifier(id)) + return engineImpl; + } } return 0; diff --git a/src/plugins/platforms/android/qandroidinputcontext.cpp b/src/plugins/platforms/android/qandroidinputcontext.cpp index db40c30d7d..fa07af8c46 100644 --- a/src/plugins/platforms/android/qandroidinputcontext.cpp +++ b/src/plugins/platforms/android/qandroidinputcontext.cpp @@ -64,29 +64,33 @@ QT_BEGIN_NAMESPACE -template <typename T> -class ScopedValueChangeBack +namespace { + +class BatchEditLock { public: - ScopedValueChangeBack(T &variable, T newValue) - : m_oldValue(variable), - m_variable(variable) - { - m_variable = newValue; - } - inline void setOldValue() + + explicit BatchEditLock(QAndroidInputContext *context) + : m_context(context) { - m_variable = m_oldValue; + m_context->beginBatchEdit(); } - ~ScopedValueChangeBack() + + ~BatchEditLock() { - setOldValue(); + m_context->endBatchEdit(); } + + BatchEditLock(const BatchEditLock &) = delete; + BatchEditLock &operator=(const BatchEditLock &) = delete; + private: - T m_oldValue; - T &m_variable; + + QAndroidInputContext *m_context; }; +} // namespace anonymous + 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"; @@ -423,8 +427,12 @@ static QRect inputItemRectangle() } QAndroidInputContext::QAndroidInputContext() - : QPlatformInputContext(), m_composingTextStart(-1), m_blockUpdateSelection(false), - m_handleMode(Hidden), m_batchEditNestingLevel(0), m_focusObject(0) + : QPlatformInputContext() + , m_composingTextStart(-1) + , m_composingCursor(-1) + , m_handleMode(Hidden) + , m_batchEditNestingLevel(0) + , m_focusObject(0) { jclass clazz = QJNIEnvironmentPrivate::findClass(QtNativeInputConnectionClassName); if (Q_UNLIKELY(!clazz)) { @@ -565,13 +573,13 @@ void QAndroidInputContext::reset() void QAndroidInputContext::commit() { - finishComposingText(); + focusObjectStopComposing(); } void QAndroidInputContext::updateCursorPosition() { QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(); - if (!query.isNull() && !m_blockUpdateSelection && !m_batchEditNestingLevel) { + if (!query.isNull() && m_batchEditNestingLevel == 0) { const int cursorPos = getAbsoluteCursorPosition(query); const int composeLength = m_composingText.length(); @@ -579,24 +587,29 @@ void QAndroidInputContext::updateCursorPosition() if (m_composingText.isEmpty() != (m_composingTextStart == -1)) qWarning() << "Input method out of sync" << m_composingText << m_composingTextStart; - int realCursorPosition = cursorPos; - int realAnchorPosition = cursorPos; + int realSelectionStart = cursorPos; + int realSelectionEnd = cursorPos; int cpos = query->value(Qt::ImCursorPosition).toInt(); int anchor = query->value(Qt::ImAnchorPosition).toInt(); if (cpos != anchor) { if (!m_composingText.isEmpty()) { qWarning("Selecting text while preediting may give unpredictable results."); - finishComposingText(); + focusObjectStopComposing(); } int blockPos = getBlockPosition(query); - realCursorPosition = blockPos + cpos; - realAnchorPosition = blockPos + anchor; + realSelectionStart = blockPos + cpos; + realSelectionEnd = blockPos + anchor; } // Qt's idea of the cursor position is the start of the preedit area, so we maintain our own preedit cursor pos - if (!m_composingText.isEmpty()) - realCursorPosition = realAnchorPosition = m_composingCursor; - QtAndroidInput::updateSelection(realCursorPosition, realAnchorPosition, + if (focusObjectIsComposing()) + realSelectionStart = realSelectionEnd = m_composingCursor; + + // Some keyboards misbahave when selStart > selEnd + if (realSelectionStart > realSelectionEnd) + std::swap(realSelectionStart, realSelectionEnd); + + QtAndroidInput::updateSelection(realSelectionStart, realSelectionEnd, m_composingTextStart, m_composingTextStart + composeLength); // pre-edit text } } @@ -666,13 +679,11 @@ void QAndroidInputContext::updateSelectionHandles() */ void QAndroidInputContext::handleLocationChanged(int handleId, int x, int y) { - if (m_batchEditNestingLevel.load() || m_blockUpdateSelection) { + if (m_batchEditNestingLevel != 0) { qWarning() << "QAndroidInputContext::handleLocationChanged returned"; return; } - finishComposingText(); - auto im = qGuiApp->inputMethod(); auto leftRect = im->cursorRectangle(); // The handle is down of the cursor, but we want the position in the middle. @@ -682,63 +693,96 @@ void QAndroidInputContext::handleLocationChanged(int handleId, int x, int y) : QHighDpiScaling::factor(QtAndroid::androidPlatformIntegration()->screen()); QPointF point(x / pixelDensity, y / pixelDensity); point.setY(point.y() - leftRect.width() / 2); - if (handleId == 1) { - setSelectionOnFocusObject(point, point); - return; - } - QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition | Qt::ImCurrentSelection); + QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition + | Qt::ImAbsolutePosition | Qt::ImCurrentSelection); QCoreApplication::sendEvent(m_focusObject, &query); int cpos = query.value(Qt::ImCursorPosition).toInt(); int anchor = query.value(Qt::ImAnchorPosition).toInt(); - bool rtl = query.value(Qt::ImCurrentSelection).toString().isRightToLeft(); auto rightRect = im->anchorRectangle(); if (cpos > anchor) std::swap(leftRect, rightRect); - auto checkLeftHandle = [&rightRect](QPointF &handlePos) { - if (handlePos.y() > rightRect.center().y()) - handlePos.setY(rightRect.center().y()); // adjust Y handle pos - if (handlePos.y() >= rightRect.y() && handlePos.y() <= rightRect.bottom() && handlePos.x() >= rightRect.x()) - return false; // same line and wrong X pos ? - return true; - }; - - auto checkRtlRightHandle = [&rightRect](QPointF &handlePos) { - if (handlePos.y() > rightRect.center().y()) - handlePos.setY(rightRect.center().y()); // adjust Y handle pos - if (handlePos.y() >= rightRect.y() && handlePos.y() <= rightRect.bottom() && rightRect.x() >= handlePos.x()) - return false; // same line and wrong X pos ? - return true; - }; - - auto checkRightHandle = [&leftRect](QPointF &handlePos) { - if (handlePos.y() < leftRect.center().y()) - handlePos.setY(leftRect.center().y()); // adjust Y handle pos - if (handlePos.y() >= leftRect.y() && handlePos.y() <= leftRect.bottom() && leftRect.x() >= handlePos.x()) - return false; // same line and wrong X pos ? - return true; - }; - - auto checkRtlLeftHandle = [&leftRect](QPointF &handlePos) { - if (handlePos.y() < leftRect.center().y()) - handlePos.setY(leftRect.center().y()); // adjust Y handle pos - if (handlePos.y() >= leftRect.y() && handlePos.y() <= leftRect.bottom() && handlePos.x() >= leftRect.x()) - return false; // same line and wrong X pos ? - return true; - }; - - if (handleId == 2) { - QPointF rightPoint(rightRect.center()); - if ((!rtl && !checkLeftHandle(point)) || (rtl && !checkRtlRightHandle(point))) - return; - setSelectionOnFocusObject(point, rightPoint); + // Do not allow dragging left handle below right handle, or right handle above left handle + if (handleId == 2 && point.y() > rightRect.center().y()) { + point.setY(rightRect.center().y()); + } else if (handleId == 3 && point.y() < leftRect.center().y()) { + point.setY(leftRect.center().y()); + } + + const QPointF pointLocal = im->inputItemTransform().inverted().map(point); + bool ok; + const int handlePos = + QInputMethod::queryFocusObject(Qt::ImCursorPosition, pointLocal).toInt(&ok); + if (!ok) + return; + + int newCpos = cpos; + int newAnchor = anchor; + if (newAnchor > newCpos) + std::swap(newAnchor, newCpos); + + if (handleId == 1) { + newCpos = handlePos; + newAnchor = handlePos; + } else if (handleId == 2) { + newAnchor = handlePos; } else if (handleId == 3) { - QPointF leftPoint(leftRect.center()); - if ((!rtl && !checkRightHandle(point)) || (rtl && !checkRtlLeftHandle(point))) + newCpos = handlePos; + } + + /* + Do not allow clearing selection by dragging selection handles and do not allow swapping + selection handles for consistency with Android's native text editing controls. Ensure that at + least one symbol remains selected. + */ + if ((handleId == 2 || handleId == 3) && newCpos <= newAnchor) { + QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, + query.value(Qt::ImCurrentSelection).toString()); + + const int oldSelectionStartPos = qMin(cpos, anchor); + + if (handleId == 2) { + finder.toEnd(); + finder.toPreviousBoundary(); + newAnchor = finder.position() + oldSelectionStartPos; + } else { + finder.toStart(); + finder.toNextBoundary(); + newCpos = finder.position() + oldSelectionStartPos; + } + } + + // Check if handle has been dragged far enough + if (!focusObjectIsComposing() && newCpos == cpos && newAnchor == anchor) + return; + + /* + If the editor is currently in composing state, we have to compare newCpos with + m_composingCursor instead of cpos. And since there is nothing to compare with newAnchor, we + perform the check only when user drags the cursor handle. + */ + if (focusObjectIsComposing() && handleId == 1) { + int absoluteCpos = query.value(Qt::ImAbsolutePosition).toInt(&ok); + if (!ok) + absoluteCpos = cpos; + const int blockPos = absoluteCpos - cpos; + + if (blockPos + newCpos == m_composingCursor) return; - setSelectionOnFocusObject(leftPoint, point); } + + BatchEditLock batchEditLock(this); + + focusObjectStopComposing(); + + QList<QInputMethodEvent::Attribute> attributes; + attributes.append({ QInputMethodEvent::Selection, newAnchor, newCpos - newAnchor }); + if (newCpos != newAnchor) + attributes.append({ QInputMethodEvent::Cursor, 0, 0 }); + + QInputMethodEvent event(QString(), attributes); + QGuiApplication::sendEvent(m_focusObject, &event); } void QAndroidInputContext::touchDown(int x, int y) @@ -748,7 +792,7 @@ void QAndroidInputContext::touchDown(int x, int y) m_handleMode = ShowCursor; // The VK will appear in a moment, stop the timer m_hideCursorHandleTimer.stop(); - finishComposingText(); + focusObjectStopComposing(); updateSelectionHandles(); } } @@ -760,13 +804,19 @@ void QAndroidInputContext::longPress(int x, int y) return; if (m_focusObject && inputItemRectangle().contains(x, y)) { - finishComposingText(); + BatchEditLock batchEditLock(this); + + focusObjectStopComposing(); // Release left button, otherwise the following events will cancel the menu popup QtAndroidInput::releaseMouse(x, y); - handleLocationChanged(1, x, y); - ScopedValueChangeBack<bool> svcb(m_blockUpdateSelection, true); + const double pixelDensity = + QGuiApplication::focusWindow() + ? QHighDpiScaling::factor(QGuiApplication::focusWindow()) + : QHighDpiScaling::factor(QtAndroid::androidPlatformIntegration()->screen()); + const QPointF touchPoint(x / pixelDensity, y / pixelDensity); + setSelectionOnFocusObject(touchPoint, touchPoint); QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition | Qt::ImTextBeforeCursor | Qt::ImTextAfterCursor); QCoreApplication::sendEvent(m_focusObject, &query); @@ -905,6 +955,7 @@ void QAndroidInputContext::clear() { m_composingText.clear(); m_composingTextStart = -1; + m_composingCursor = -1; m_extractedText.clear(); } @@ -912,9 +963,8 @@ void QAndroidInputContext::clear() void QAndroidInputContext::setFocusObject(QObject *object) { if (object != m_focusObject) { + focusObjectStopComposing(); m_focusObject = object; - if (!m_composingText.isEmpty()) - finishComposingText(); reset(); } QPlatformInputContext::setFocusObject(object); @@ -929,78 +979,135 @@ jboolean QAndroidInputContext::beginBatchEdit() jboolean QAndroidInputContext::endBatchEdit() { - if (--m_batchEditNestingLevel == 0 && !m_blockUpdateSelection) //ending batch edit mode + if (--m_batchEditNestingLevel == 0) { //ending batch edit mode + focusObjectStartComposing(); updateCursorPosition(); + } return JNI_TRUE; } /* - Android docs say: If composing, replace compose text with \a text. - Otherwise insert \a text at current cursor position. - - The cursor should then be moved to newCursorPosition. If > 0, this is - relative to the end of the text - 1; if <= 0, this is relative to the start - of the text. updateSelection() needs to be called. + Android docs say: This behaves like calling setComposingText(text, newCursorPosition) then + finishComposingText(). */ jboolean QAndroidInputContext::commitText(const QString &text, jint newCursorPosition) { - ScopedValueChangeBack<bool> svcb(m_blockUpdateSelection, true); - QInputMethodEvent event; - event.setCommitString(text); - sendInputMethodEvent(&event); - clear(); - - // Qt has now put the cursor at the end of the text, corresponding to newCursorPosition == 1 - if (newCursorPosition != 1) { - QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(); - if (!query.isNull()) { - QList<QInputMethodEvent::Attribute> attributes; - const int localPos = query->value(Qt::ImCursorPosition).toInt(); - const int newLocalPos = newCursorPosition > 0 - ? localPos + newCursorPosition - 1 - : localPos - text.length() + newCursorPosition; - //move the cursor - attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection, - newLocalPos, 0)); - } - } - svcb.setOldValue(); - updateCursorPosition(); - return JNI_TRUE; + BatchEditLock batchEditLock(this); + return setComposingText(text, newCursorPosition) && finishComposingText(); } jboolean QAndroidInputContext::deleteSurroundingText(jint leftLength, jint rightLength) { + BatchEditLock batchEditLock(this); + + focusObjectStopComposing(); + QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(); if (query.isNull()) return JNI_TRUE; - m_composingText.clear(); - m_composingTextStart = -1; - if (leftLength < 0) { rightLength += -leftLength; leftLength = 0; } + const int initialBlockPos = getBlockPosition(query); + const int initialCursorPos = getAbsoluteCursorPosition(query); + const int initialAnchorPos = initialBlockPos + query->value(Qt::ImAnchorPosition).toInt(); + + /* + According to documentation, we should delete leftLength characters before current selection + and rightLength characters after current selection (without affecting selection). But that is + absolutely not what Android's native EditText does. It deletes leftLength characters before + min(selection start, composing region start) and rightLength characters after max(selection + end, composing region end). There are no known keyboards that depend on this behavior, but + it is better to be consistent with EditText behavior, because there definetly should be no + keyboards that depend on documented behavior. + */ + const int leftEnd = + m_composingText.isEmpty() + ? qMin(initialCursorPos, initialAnchorPos) + : qMin(qMin(initialCursorPos, initialAnchorPos), m_composingTextStart); + + const int rightBegin = + m_composingText.isEmpty() + ? qMax(initialCursorPos, initialAnchorPos) + : qMax(qMax(initialCursorPos, initialAnchorPos), + m_composingTextStart + m_composingText.length()); + + int textBeforeCursorLen; + int textAfterCursorLen; + QVariant textBeforeCursor = query->value(Qt::ImTextBeforeCursor); QVariant textAfterCursor = query->value(Qt::ImTextAfterCursor); if (textBeforeCursor.isValid() && textAfterCursor.isValid()) { - leftLength = qMin(leftLength, textBeforeCursor.toString().length()); - rightLength = qMin(rightLength, textAfterCursor.toString().length()); + textBeforeCursorLen = textBeforeCursor.toString().length(); + textAfterCursorLen = textAfterCursor.toString().length(); } else { - int cursorPos = query->value(Qt::ImCursorPosition).toInt(); - leftLength = qMin(leftLength, cursorPos); - rightLength = qMin(rightLength, query->value(Qt::ImSurroundingText).toString().length() - cursorPos); + textBeforeCursorLen = initialCursorPos - initialBlockPos; + textAfterCursorLen = + query->value(Qt::ImSurroundingText).toString().length() - textBeforeCursorLen; } + leftLength = qMin(qMax(0, textBeforeCursorLen - (initialCursorPos - leftEnd)), leftLength); + rightLength = qMin(qMax(0, textAfterCursorLen - (rightBegin - initialCursorPos)), rightLength); + if (leftLength == 0 && rightLength == 0) return JNI_TRUE; - QInputMethodEvent event; - event.setCommitString(QString(), -leftLength, leftLength+rightLength); - sendInputMethodEvent(&event); - clear(); + if (leftEnd == rightBegin) { + // We have no selection and no composing region; we can do everything using one event + QInputMethodEvent event; + event.setCommitString({}, -leftLength, leftLength + rightLength); + QGuiApplication::sendEvent(m_focusObject, &event); + } else { + if (initialCursorPos != initialAnchorPos) { + QInputMethodEvent event({}, { + { QInputMethodEvent::Selection, initialCursorPos - initialBlockPos, 0 } + }); + + QGuiApplication::sendEvent(m_focusObject, &event); + } + + int currentCursorPos = initialCursorPos; + + if (rightLength > 0) { + QInputMethodEvent event; + event.setCommitString({}, rightBegin - currentCursorPos, rightLength); + QGuiApplication::sendEvent(m_focusObject, &event); + + currentCursorPos = rightBegin; + } + + if (leftLength > 0) { + const int leftBegin = leftEnd - leftLength; + + QInputMethodEvent event; + event.setCommitString({}, leftBegin - currentCursorPos, leftLength); + QGuiApplication::sendEvent(m_focusObject, &event); + + currentCursorPos = leftBegin; + + if (!m_composingText.isEmpty()) + m_composingTextStart -= leftLength; + } + + // Restore cursor position or selection + if (currentCursorPos != initialCursorPos - leftLength + || initialCursorPos != initialAnchorPos) { + // If we have deleted a newline character, we are now in a new block + const int currentBlockPos = getBlockPosition( + focusObjectInputMethodQuery(Qt::ImAbsolutePosition | Qt::ImCursorPosition)); + + QInputMethodEvent event({}, { + { QInputMethodEvent::Selection, initialCursorPos - leftLength - currentBlockPos, + initialAnchorPos - initialCursorPos }, + { QInputMethodEvent::Cursor, 0, 0 } + }); + + QGuiApplication::sendEvent(m_focusObject, &event); + } + } return JNI_TRUE; } @@ -1008,16 +1115,70 @@ jboolean QAndroidInputContext::deleteSurroundingText(jint leftLength, jint right // Android docs say the cursor must not move jboolean QAndroidInputContext::finishComposingText() { - if (m_composingText.isEmpty()) - return JNI_TRUE; // not composing + BatchEditLock batchEditLock(this); + + if (!focusObjectStopComposing()) + return JNI_FALSE; + + clear(); + return JNI_TRUE; +} + +bool QAndroidInputContext::focusObjectIsComposing() const +{ + return m_composingCursor != -1; +} + +void QAndroidInputContext::focusObjectStartComposing() +{ + if (focusObjectIsComposing() || m_composingText.isEmpty()) + return; + + // Composing strings containing newline characters are rare and may cause problems + if (m_composingText.contains(QLatin1Char('\n'))) + return; + + QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(); + if (!query) + return; + + if (query->value(Qt::ImCursorPosition).toInt() != query->value(Qt::ImAnchorPosition).toInt()) + return; + + const int absoluteCursorPos = getAbsoluteCursorPosition(query); + if (absoluteCursorPos < m_composingTextStart + || absoluteCursorPos > m_composingTextStart + m_composingText.length()) + return; + + m_composingCursor = absoluteCursorPos; + + QTextCharFormat underlined; + underlined.setFontUnderline(true); + + QInputMethodEvent event(m_composingText, { + { QInputMethodEvent::Cursor, absoluteCursorPos - m_composingTextStart, 1 }, + { QInputMethodEvent::TextFormat, 0, m_composingText.length(), underlined } + }); + + event.setCommitString({}, m_composingTextStart - absoluteCursorPos, m_composingText.length()); + + QGuiApplication::sendEvent(m_focusObject, &event); +} + +bool QAndroidInputContext::focusObjectStopComposing() +{ + if (!focusObjectIsComposing()) + return true; // not composing QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(); if (query.isNull()) - return JNI_FALSE; + return false; const int blockPos = getBlockPosition(query); const int localCursorPos = m_composingCursor - blockPos; + m_composingCursor = -1; + // Moving Qt's cursor to where the preedit cursor used to be QList<QInputMethodEvent::Attribute> attributes; attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection, localCursorPos, 0)); @@ -1025,9 +1186,8 @@ jboolean QAndroidInputContext::finishComposingText() QInputMethodEvent event(QString(), attributes); event.setCommitString(m_composingText); sendInputMethodEvent(&event); - clear(); - return JNI_TRUE; + return true; } jint QAndroidInputContext::getCursorCapsMode(jint /*reqModes*/) @@ -1067,52 +1227,51 @@ const QAndroidInputContext::ExtractedText &QAndroidInputContext::getExtractedTex // updateExtractedText(View, int, ExtractedText) whenever you call // updateSelection(View, int, int, int, int)." QTBUG-37980 - QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(); + QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery( + Qt::ImCursorPosition | Qt::ImAbsolutePosition | Qt::ImAnchorPosition); if (query.isNull()) return m_extractedText; - int localPos = query->value(Qt::ImCursorPosition).toInt(); //position before pre-edit text relative to the current block - int blockPos = getBlockPosition(query); - QString blockText = query->value(Qt::ImSurroundingText).toString(); - int composeLength = m_composingText.length(); - - if (composeLength > 0) { - //Qt doesn't give us the preedit text, so we have to insert it at the correct position - int localComposePos = m_composingTextStart - blockPos; - blockText = blockText.leftRef(localComposePos) + m_composingText + blockText.midRef(localComposePos); - } - - int cpos = localPos + composeLength; //actual cursor pos relative to the current block - - int localOffset = 0; // start of extracted text relative to the current block - if (blockPos > 0) { - QString prevBlockEnding = query->value(Qt::ImTextBeforeCursor).toString(); - prevBlockEnding.chop(localPos); - if (prevBlockEnding.endsWith(QLatin1Char('\n'))) { - localOffset = -qMin(20, prevBlockEnding.length()); - blockText = prevBlockEnding.right(-localOffset) + blockText; - } - } + const int cursorPos = getAbsoluteCursorPosition(query); + const int blockPos = getBlockPosition(query); // It is documented that we should try to return hintMaxChars - // characters, but that's not what the standard Android controls do, and + // characters, but standard Android controls always return all text, and // there are input methods out there that (surprise) seem to depend on // what happens in reality rather than what's documented. - m_extractedText.text = blockText; - m_extractedText.startOffset = blockPos + localOffset; + QVariant textBeforeCursor = QInputMethod::queryFocusObject(Qt::ImTextBeforeCursor, INT_MAX); + QVariant textAfterCursor = QInputMethod::queryFocusObject(Qt::ImTextAfterCursor, INT_MAX); + if (textBeforeCursor.isValid() && textAfterCursor.isValid()) { + if (focusObjectIsComposing()) { + m_extractedText.text = + textBeforeCursor.toString() + m_composingText + textAfterCursor.toString(); + } else { + m_extractedText.text = textBeforeCursor.toString() + textAfterCursor.toString(); + } + + m_extractedText.startOffset = qMax(0, cursorPos - textBeforeCursor.toString().length()); + } else { + m_extractedText.text = focusObjectInputMethodQuery(Qt::ImSurroundingText) + ->value(Qt::ImSurroundingText).toString(); + + if (focusObjectIsComposing()) + m_extractedText.text.insert(cursorPos - blockPos, m_composingText); - const QString &selection = query->value(Qt::ImCurrentSelection).toString(); - const int selLen = selection.length(); - if (selLen) { - m_extractedText.selectionStart = query->value(Qt::ImAnchorPosition).toInt() - localOffset; - m_extractedText.selectionEnd = m_extractedText.selectionStart + selLen; - } else if (composeLength > 0) { + m_extractedText.startOffset = blockPos; + } + + if (focusObjectIsComposing()) { m_extractedText.selectionStart = m_composingCursor - m_extractedText.startOffset; - m_extractedText.selectionEnd = m_composingCursor - m_extractedText.startOffset; - } else { - m_extractedText.selectionStart = cpos - localOffset; - m_extractedText.selectionEnd = cpos - localOffset; + m_extractedText.selectionEnd = m_extractedText.selectionStart; + } else { + m_extractedText.selectionStart = cursorPos - m_extractedText.startOffset; + m_extractedText.selectionEnd = + blockPos + query->value(Qt::ImAnchorPosition).toInt() - m_extractedText.startOffset; + + // Some keyboards misbehave when selectionStart > selectionEnd + if (m_extractedText.selectionStart > m_extractedText.selectionEnd) + std::swap(m_extractedText.selectionStart, m_extractedText.selectionEnd); } return m_extractedText; @@ -1147,10 +1306,20 @@ QString QAndroidInputContext::getTextAfterCursor(jint length, jint /*flags*/) } } - // Controls do not report preedit text, so we have to add it - if (!m_composingText.isEmpty()) { + if (focusObjectIsComposing()) { + // Controls do not report preedit text, so we have to add it const int cursorPosInsidePreedit = m_composingCursor - m_composingTextStart; text = m_composingText.midRef(cursorPosInsidePreedit) + text; + } else { + // We must not return selected text if there is any + QSharedPointer<QInputMethodQueryEvent> query = + focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAnchorPosition); + if (query) { + const int cursorPos = query->value(Qt::ImCursorPosition).toInt(); + const int anchorPos = query->value(Qt::ImAnchorPosition).toInt(); + if (anchorPos > cursorPos) + text.remove(0, anchorPos - cursorPos); + } } text.truncate(length); @@ -1177,10 +1346,20 @@ QString QAndroidInputContext::getTextBeforeCursor(jint length, jint /*flags*/) } } - // Controls do not report preedit text, so we have to add it - if (!m_composingText.isEmpty()) { + if (focusObjectIsComposing()) { + // Controls do not report preedit text, so we have to add it const int cursorPosInsidePreedit = m_composingCursor - m_composingTextStart; text += m_composingText.leftRef(cursorPosInsidePreedit); + } else { + // We must not return selected text if there is any + QSharedPointer<QInputMethodQueryEvent> query = + focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAnchorPosition); + if (query) { + const int cursorPos = query->value(Qt::ImCursorPosition).toInt(); + const int anchorPos = query->value(Qt::ImAnchorPosition).toInt(); + if (anchorPos < cursorPos) + text.chop(cursorPos - anchorPos); + } } if (text.length() > length) @@ -1189,11 +1368,13 @@ QString QAndroidInputContext::getTextBeforeCursor(jint length, jint /*flags*/) } /* - Android docs say that this function should remove the current preedit text - if any, and replace it with the given text. Any selected text should be - removed. The cursor is then moved to newCursorPosition. If > 0, this is - relative to the end of the text - 1; if <= 0, this is relative to the start - of the text. + Android docs say that this function should: + - remove the current composing text, if there is any + - otherwise remove currently selected text, if there is any + - insert new text in place of old composing text or, if there was none, at current cursor position + - mark the inserted text as composing + - move cursor as specified by newCursorPosition: if > 0, it is relative to the end of inserted + text - 1; if <= 0, it is relative to the start of inserted text */ jboolean QAndroidInputContext::setComposingText(const QString &text, jint newCursorPosition) @@ -1202,47 +1383,110 @@ jboolean QAndroidInputContext::setComposingText(const QString &text, jint newCur if (query.isNull()) return JNI_FALSE; - const int cursorPos = getAbsoluteCursorPosition(query); - if (newCursorPosition > 0) - newCursorPosition += text.length() - 1; + BatchEditLock batchEditLock(this); + + const int absoluteCursorPos = getAbsoluteCursorPosition(query); + int absoluteAnchorPos = getBlockPosition(query) + query->value(Qt::ImAnchorPosition).toInt(); + + // If we have composing region and selection (and therefore focusObjectIsComposing() == false), + // we must clear selection so that we won't delete it when we will be replacing composing text + if (!m_composingText.isEmpty() && absoluteCursorPos != absoluteAnchorPos) { + const int cursorPos = query->value(Qt::ImCursorPosition).toInt(); + QInputMethodEvent event({}, { { QInputMethodEvent::Selection, cursorPos, 0 } }); + QGuiApplication::sendEvent(m_focusObject, &event); + + absoluteAnchorPos = absoluteCursorPos; + } + + // If we had no composing region, pretend that we had a zero-length composing region at current + // cursor position to simplify code. Also account for that we must delete selected text if there + // (still) is any. + const int effectiveAbsoluteCursorPos = qMin(absoluteCursorPos, absoluteAnchorPos); + if (m_composingTextStart == -1) + m_composingTextStart = effectiveAbsoluteCursorPos; + const int oldComposingTextLen = m_composingText.length(); m_composingText = text; - m_composingTextStart = text.isEmpty() ? -1 : cursorPos; - m_composingCursor = cursorPos + newCursorPosition; - QList<QInputMethodEvent::Attribute> attributes; - attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, - newCursorPosition, - 1)); - // Show compose text underlined - QTextCharFormat underlined; - underlined.setFontUnderline(true); - attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat,0, text.length(), - QVariant(underlined))); - QInputMethodEvent event(m_composingText, attributes); - sendInputMethodEvent(&event); + const int newAbsoluteCursorPos = + newCursorPosition <= 0 + ? m_composingTextStart + newCursorPosition + : m_composingTextStart + m_composingText.length() + newCursorPosition - 1; - QMetaObject::invokeMethod(this, "keyDown"); + const bool focusObjectWasComposing = focusObjectIsComposing(); + + // Same checks as in focusObjectStartComposing() + if (!m_composingText.isEmpty() && !m_composingText.contains(QLatin1Char('\n')) + && newAbsoluteCursorPos >= m_composingTextStart + && newAbsoluteCursorPos <= m_composingTextStart + m_composingText.length()) + m_composingCursor = newAbsoluteCursorPos; + else + m_composingCursor = -1; - updateCursorPosition(); + QInputMethodEvent event; + if (focusObjectIsComposing()) { + QTextCharFormat underlined; + underlined.setFontUnderline(true); + + event = QInputMethodEvent(m_composingText, { + { QInputMethodEvent::TextFormat, 0, m_composingText.length(), underlined }, + { QInputMethodEvent::Cursor, m_composingCursor - m_composingTextStart, 1 } + }); + + if (oldComposingTextLen > 0 && !focusObjectWasComposing) { + event.setCommitString({}, m_composingTextStart - effectiveAbsoluteCursorPos, + oldComposingTextLen); + } + } else { + event = QInputMethodEvent({}, {}); + + if (focusObjectWasComposing) { + event.setCommitString(m_composingText); + } else { + event.setCommitString(m_composingText, + m_composingTextStart - effectiveAbsoluteCursorPos, + oldComposingTextLen); + } + } + + if (m_composingText.isEmpty()) + clear(); + + QGuiApplication::sendEvent(m_focusObject, &event); + + if (!focusObjectIsComposing() && newCursorPosition != 1) { + // Move cursor using a separate event because if we have inserted or deleted a newline + // character, then we are now inside an another block + + const int newBlockPos = getBlockPosition( + focusObjectInputMethodQuery(Qt::ImCursorPosition | Qt::ImAbsolutePosition)); + + event = QInputMethodEvent({}, { + { QInputMethodEvent::Selection, newAbsoluteCursorPos - newBlockPos, 0 } + }); + + QGuiApplication::sendEvent(m_focusObject, &event); + } + + keyDown(); return JNI_TRUE; } // Android docs say: // * start may be after end, same meaning as if swapped -// * this function should not trigger updateSelection +// * this function should not trigger updateSelection, but Android's native EditText does trigger it // * if start == end then we should stop composing jboolean QAndroidInputContext::setComposingRegion(jint start, jint end) { + BatchEditLock batchEditLock(this); + // Qt will not include the current preedit text in the query results, and interprets all // parameters relative to the text excluding the preedit. The simplest solution is therefore to // tell Qt that we commit the text before we set the new region. This may cause a little flicker, but is // much more robust than trying to keep the two different world views in sync - bool wasComposing = !m_composingText.isEmpty(); - if (wasComposing) - finishComposingText(); + finishComposingText(); QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery(); if (query.isNull()) @@ -1253,54 +1497,42 @@ jboolean QAndroidInputContext::setComposingRegion(jint start, jint end) if (start > end) qSwap(start, end); - /* - start and end are cursor positions, not character positions, - i.e. selecting the first character is done by start == 0 and end == 1, - and start == end means no character selected - - Therefore, the length of the region is end - start - */ - - int length = end - start; - int localPos = query->value(Qt::ImCursorPosition).toInt(); - int blockPosition = getBlockPosition(query); - int localStart = start - blockPosition; // Qt uses position inside block - int currentCursor = wasComposing ? m_composingCursor : blockPosition + localPos; - - ScopedValueChangeBack<bool> svcb(m_blockUpdateSelection, true); - QString text = query->value(Qt::ImSurroundingText).toString(); + int textOffset = getBlockPosition(query); - m_composingText = text.mid(localStart, length); - m_composingTextStart = start; - m_composingCursor = currentCursor; - - //in the Qt text controls, the preedit is defined relative to the cursor position - int relativeStart = localStart - localPos; + if (start < textOffset || end > textOffset + text.length()) { + const int cursorPos = query->value(Qt::ImCursorPosition).toInt(); - QList<QInputMethodEvent::Attribute> attributes; + if (end - textOffset > text.length()) { + const QString after = query->value(Qt::ImTextAfterCursor).toString(); + const int additionalSuffixLen = after.length() - (text.length() - cursorPos); - // Show compose text underlined - QTextCharFormat underlined; - underlined.setFontUnderline(true); - attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat,0, length, - QVariant(underlined))); + if (additionalSuffixLen > 0) + text += after.rightRef(additionalSuffixLen); + } - // Keep the cursor position unchanged (don't move to end of preedit) - attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, currentCursor - start, 1)); + if (start < textOffset) { + QString before = query->value(Qt::ImTextBeforeCursor).toString(); + before.chop(cursorPos); - QInputMethodEvent event(m_composingText, attributes); - event.setCommitString(QString(), relativeStart, length); - sendInputMethodEvent(&event); + if (!before.isEmpty()) { + text = before + text; + textOffset -= before.length(); + } + } + if (start < textOffset || end - textOffset > text.length()) { #ifdef QT_DEBUG_ANDROID_IM_PROTOCOL - QSharedPointer<QInputMethodQueryEvent> query2 = focusObjectInputMethodQuery(); - if (!query2.isNull()) { - qDebug() << "Setting. Prev local cpos:" << localPos << "block pos:" <<blockPosition << "comp.start:" << m_composingTextStart << "rel.start:" << relativeStart << "len:" << length << "cpos attr:" << localPos - localStart; - qDebug() << "New cursor pos" << getAbsoluteCursorPosition(query2); - } + qWarning("setComposingRegion: failed to retrieve text from composing region"); #endif + return JNI_TRUE; + } + } + + m_composingText = text.mid(start - textOffset, end - start); + m_composingTextStart = start; + return JNI_TRUE; } @@ -1310,15 +1542,18 @@ jboolean QAndroidInputContext::setSelection(jint start, jint end) if (query.isNull()) return JNI_FALSE; + BatchEditLock batchEditLock(this); + int blockPosition = getBlockPosition(query); int localCursorPos = start - blockPosition; - QList<QInputMethodEvent::Attribute> attributes; - if (!m_composingText.isEmpty() && start == end) { + if (focusObjectIsComposing() && start == end && start >= m_composingTextStart + && start <= m_composingTextStart + m_composingText.length()) { // not actually changing the selection; just moving the // preedit cursor int localOldPos = query->value(Qt::ImCursorPosition).toInt(); int pos = localCursorPos - localOldPos; + QList<QInputMethodEvent::Attribute> attributes; attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, pos, 1)); //but we have to tell Qt about the compose text all over again @@ -1330,21 +1565,26 @@ jboolean QAndroidInputContext::setSelection(jint start, jint end) QVariant(underlined))); m_composingCursor = start; + QInputMethodEvent event(m_composingText, attributes); + QGuiApplication::sendEvent(m_focusObject, &event); } else { // actually changing the selection + focusObjectStopComposing(); + QList<QInputMethodEvent::Attribute> attributes; attributes.append(QInputMethodEvent::Attribute(QInputMethodEvent::Selection, localCursorPos, end - start)); + QInputMethodEvent event({}, attributes); + QGuiApplication::sendEvent(m_focusObject, &event); } - QInputMethodEvent event(m_composingText, attributes); - sendInputMethodEvent(&event); - updateCursorPosition(); return JNI_TRUE; } jboolean QAndroidInputContext::selectAll() { - finishComposingText(); + BatchEditLock batchEditLock(this); + + focusObjectStopComposing(); m_handleMode = ShowCursor; sendShortcut(QKeySequence::SelectAll); return JNI_TRUE; @@ -1352,7 +1592,12 @@ jboolean QAndroidInputContext::selectAll() jboolean QAndroidInputContext::cut() { + BatchEditLock batchEditLock(this); + + // This is probably not what native EditText would do, but normally if there is selection, then + // there will be no composing region finishComposingText(); + m_handleMode = ShowCursor; sendShortcut(QKeySequence::Cut); return JNI_TRUE; @@ -1360,7 +1605,9 @@ jboolean QAndroidInputContext::cut() jboolean QAndroidInputContext::copy() { - finishComposingText(); + BatchEditLock batchEditLock(this); + + focusObjectStopComposing(); m_handleMode = ShowCursor; sendShortcut(QKeySequence::Copy); return JNI_TRUE; @@ -1374,7 +1621,11 @@ jboolean QAndroidInputContext::copyURL() jboolean QAndroidInputContext::paste() { + BatchEditLock batchEditLock(this); + + // TODO: This is not what native EditText does finishComposingText(); + m_handleMode = ShowCursor; sendShortcut(QKeySequence::Paste); return JNI_TRUE; @@ -1386,8 +1637,12 @@ void QAndroidInputContext::sendShortcut(const QKeySequence &sequence) const int keys = sequence[i]; Qt::Key key = Qt::Key(keys & ~Qt::KeyboardModifierMask); Qt::KeyboardModifiers mod = Qt::KeyboardModifiers(keys & Qt::KeyboardModifierMask); - QGuiApplication::postEvent(m_focusObject, new QKeyEvent(QEvent::KeyPress, key, mod)); - QGuiApplication::postEvent(m_focusObject, new QKeyEvent(QEvent::KeyRelease, key, mod)); + + QKeyEvent pressEvent(QEvent::KeyPress, key, mod); + QKeyEvent releaseEvent(QEvent::KeyRelease, key, mod); + + QGuiApplication::sendEvent(m_focusObject, &pressEvent); + QGuiApplication::sendEvent(m_focusObject, &releaseEvent); } } diff --git a/src/plugins/platforms/android/qandroidinputcontext.h b/src/plugins/platforms/android/qandroidinputcontext.h index bd3edb30f0..e9bfb98e66 100644 --- a/src/plugins/platforms/android/qandroidinputcontext.h +++ b/src/plugins/platforms/android/qandroidinputcontext.h @@ -151,6 +151,9 @@ private slots: private: void sendInputMethodEvent(QInputMethodEvent *event); QSharedPointer<QInputMethodQueryEvent> focusObjectInputMethodQuery(Qt::InputMethodQueries queries = Qt::ImQueryAll); + bool focusObjectIsComposing() const; + void focusObjectStartComposing(); + bool focusObjectStopComposing(); private: ExtractedText m_extractedText; @@ -158,9 +161,8 @@ private: int m_composingTextStart; int m_composingCursor; QMetaObject::Connection m_updateCursorPosConnection; - bool m_blockUpdateSelection; HandleModes m_handleMode; - QAtomicInt m_batchEditNestingLevel; + int m_batchEditNestingLevel; QObject *m_focusObject; QTimer m_hideCursorHandleTimer; }; diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.h b/src/plugins/platforms/cocoa/qcocoabackingstore.h index acddc3ecc8..2398e6351e 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.h +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.h @@ -55,6 +55,7 @@ public: QNSWindowBackingStore(QWindow *window); ~QNSWindowBackingStore(); + void resize(const QSize &size, const QRegion &staticContents) override; void flush(QWindow *, const QRegion &, const QPoint &) override; private: diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm index c381f87844..01b4894324 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.mm +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm @@ -71,6 +71,24 @@ QImage::Format QNSWindowBackingStore::format() const return QRasterBackingStore::format(); } +void QNSWindowBackingStore::resize(const QSize &size, const QRegion &staticContents) +{ + qCDebug(lcQpaBackingStore) << "Resize requested to" << size; + QRasterBackingStore::resize(size, staticContents); + + // The window shadow rendered by AppKit is based on the shape/content of the + // NSWindow surface. Technically any flush of the backingstore can result in + // a potentially new shape of the window, and would need a shadow invalidation, + // but this is likely too expensive to do at every flush for the few cases where + // clients change the shape dynamically. One case where we do know that the shadow + // likely needs invalidation, if the window has partially transparent content, + // is after a resize, where AppKit's default shadow may be based on the previous + // window content. + QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window()->handle()); + if (cocoaWindow->isContentView() && !cocoaWindow->isOpaque()) + cocoaWindow->m_needsInvalidateShadow = true; +} + /*! Flushes the given \a region from the specified \a window onto the screen. @@ -217,6 +235,7 @@ void QNSWindowBackingStore::flush(QWindow *window, const QRegion ®ion, const QCocoaWindow *topLevelCocoaWindow = static_cast<QCocoaWindow *>(topLevelWindow->handle()); if (Q_UNLIKELY(topLevelCocoaWindow->m_needsInvalidateShadow)) { + qCDebug(lcQpaBackingStore) << "Invalidating window shadow for" << topLevelCocoaWindow; [topLevelView.window invalidateShadow]; topLevelCocoaWindow->m_needsInvalidateShadow = false; } @@ -382,10 +401,11 @@ void QCALayerBackingStore::ensureBackBuffer() bool QCALayerBackingStore::recreateBackBufferIfNeeded() { - const qreal devicePixelRatio = window()->devicePixelRatio(); + const QCocoaWindow *platformWindow = static_cast<QCocoaWindow *>(window()->handle()); + const qreal devicePixelRatio = platformWindow->devicePixelRatio(); QSize requestedBufferSize = m_requestedSize * devicePixelRatio; - const NSView *backingStoreView = static_cast<QCocoaWindow *>(window()->handle())->view(); + const NSView *backingStoreView = platformWindow->view(); Q_UNUSED(backingStoreView); auto bufferSizeMismatch = [&](const QSize requested, const QSize actual) { diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h index 9771cd0289..69587a24be 100644 --- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h @@ -191,6 +191,7 @@ public: static void waitingObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info); static void firstLoopEntry(CFRunLoopObserverRef ref, CFRunLoopActivity activity, void *info); + bool sendQueuedUserInputEvents(); void processPostedEvents(); }; diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm index 84ffadea83..d3bb0711f0 100644 --- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm @@ -377,16 +377,9 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) NSEvent* event = nil; // First, send all previously excluded input events, if any: - if (!excludeUserEvents) { - while (!d->queuedUserInputEvents.isEmpty()) { - event = static_cast<NSEvent *>(d->queuedUserInputEvents.takeFirst()); - if (!filterNativeEvent("NSEvent", event, nullptr)) { - [NSApp sendEvent:event]; - retVal = true; - } - [event release]; - } - } + if (d->sendQueuedUserInputEvents()) + retVal = true; + // If Qt is used as a plugin, or as an extension in a native cocoa // application, we should not run or stop NSApplication; This will be @@ -843,6 +836,23 @@ void QCocoaEventDispatcherPrivate::waitingObserverCallback(CFRunLoopObserverRef, emit static_cast<QCocoaEventDispatcher*>(info)->awake(); } +bool QCocoaEventDispatcherPrivate::sendQueuedUserInputEvents() +{ + Q_Q(QCocoaEventDispatcher); + if (processEventsFlags & QEventLoop::ExcludeUserInputEvents) + return false; + bool didSendEvent = false; + while (!queuedUserInputEvents.isEmpty()) { + NSEvent *event = static_cast<NSEvent *>(queuedUserInputEvents.takeFirst()); + if (!q->filterNativeEvent("NSEvent", event, nullptr)) { + [NSApp sendEvent:event]; + didSendEvent = true; + } + [event release]; + } + return didSendEvent; +} + void QCocoaEventDispatcherPrivate::processPostedEvents() { if (blockSendPostedEvents) { @@ -896,6 +906,7 @@ void QCocoaEventDispatcherPrivate::postedEventsSourceCallback(void *info) d->maybeCancelWaitForMoreEvents(); return; } + d->sendQueuedUserInputEvents(); d->processPostedEvents(); d->maybeCancelWaitForMoreEvents(); } diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 67b0cbdfe9..363a026e71 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -1011,16 +1011,16 @@ void QCocoaWindow::setMask(const QRegion ®ion) } else { m_view.layer.mask = nil; } - } - - if (isContentView()) { - // Setting the mask requires invalidating the NSWindow shadow, but that needs - // to happen after the backingstore has been redrawn, so that AppKit can pick - // up the new window shape based on the backingstore content. Doing a display - // directly here is not an option, as the window might not be exposed at this - // time, and so would not result in an updated backingstore. - m_needsInvalidateShadow = true; - [m_view setNeedsDisplay:YES]; + } else { + if (isContentView()) { + // Setting the mask requires invalidating the NSWindow shadow, but that needs + // to happen after the backingstore has been redrawn, so that AppKit can pick + // up the new window shape based on the backingstore content. Doing a display + // directly here is not an option, as the window might not be exposed at this + // time, and so would not result in an updated backingstore. + m_needsInvalidateShadow = true; + [m_view setNeedsDisplay:YES]; + } } } diff --git a/src/plugins/platforms/ios/quiview_accessibility.mm b/src/plugins/platforms/ios/quiview_accessibility.mm index a3f4156a59..458ddcc9b8 100644 --- a/src/plugins/platforms/ios/quiview_accessibility.mm +++ b/src/plugins/platforms/ios/quiview_accessibility.mm @@ -101,6 +101,8 @@ - (id)accessibilityElementAtIndex:(NSInteger)index { [self initAccessibility]; + if (index >= [m_accessibleElements count]) + return nil; return m_accessibleElements[index]; } @@ -110,4 +112,10 @@ return [m_accessibleElements indexOfObject:element]; } +- (NSArray *)accessibilityElements +{ + [self initAccessibility]; + return m_accessibleElements; +} + @end diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index de533cab08..38b9823d6b 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -77,6 +77,7 @@ #include <QtCore/qoperatingsystemversion.h> #include <QtCore/qsysinfo.h> #include <QtCore/qscopedpointer.h> +#include <QtCore/quuid.h> #include <QtCore/private/qsystemlibrary_p.h> #include <QtEventDispatcherSupport/private/qwindowsguieventdispatcher_p.h> @@ -544,7 +545,7 @@ QString QWindowsContext::registerWindowClass(QString cname, // each one has to have window class names with a unique name // The first instance gets the unmodified name; if the class // has already been registered by another instance of Qt then - // add an instance-specific ID, the address of the window proc. + // add a UUID. static int classExists = -1; const HINSTANCE appInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr)); @@ -555,7 +556,7 @@ QString QWindowsContext::registerWindowClass(QString cname, } if (classExists) - cname += QString::number(reinterpret_cast<quintptr>(proc)); + cname += QUuid::createUuid().toString(); if (d->m_registeredWindowClassNames.contains(cname)) // already registered in our list return cname; diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp index 4adf662152..f26f698e76 100644 --- a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp +++ b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp @@ -204,7 +204,6 @@ QGLXContext::QGLXContext(QXcbScreen *screen, const QSurfaceFormat &format, QPlat , m_shareContext(0) , m_format(format) , m_isPBufferCurrent(false) - , m_swapInterval(-1) , m_ownsContext(nativeHandle.isNull()) , m_getGraphicsResetStatus(0) , m_lost(false) @@ -567,9 +566,9 @@ bool QGLXContext::makeCurrent(QPlatformSurface *surface) if (success && surfaceClass == QSurface::Window) { int interval = surface->format().swapInterval(); + QXcbWindow *window = static_cast<QXcbWindow *>(surface); QXcbScreen *screen = screenForPlatformSurface(surface); - if (interval >= 0 && m_swapInterval != interval && screen) { - m_swapInterval = interval; + if (interval >= 0 && interval != window->swapInterval() && screen) { typedef void (*qt_glXSwapIntervalEXT)(Display *, GLXDrawable, int); typedef void (*qt_glXSwapIntervalMESA)(unsigned int); static qt_glXSwapIntervalEXT glXSwapIntervalEXT = 0; @@ -588,6 +587,7 @@ bool QGLXContext::makeCurrent(QPlatformSurface *surface) glXSwapIntervalEXT(m_display, glxDrawable, interval); else if (glXSwapIntervalMESA) glXSwapIntervalMESA(interval); + window->setSwapInterval(interval); } } diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.h b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.h index be9d3f5dcb..2a88fd6e59 100644 --- a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.h +++ b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.h @@ -87,7 +87,6 @@ private: GLXContext m_shareContext; QSurfaceFormat m_format; bool m_isPBufferCurrent; - int m_swapInterval; bool m_ownsContext; GLenum (APIENTRY * m_getGraphicsResetStatus)(); bool m_lost; diff --git a/src/plugins/platforms/xcb/qxcbwindow.h b/src/plugins/platforms/xcb/qxcbwindow.h index f98cd8a74d..8258cc2dfa 100644 --- a/src/plugins/platforms/xcb/qxcbwindow.h +++ b/src/plugins/platforms/xcb/qxcbwindow.h @@ -184,6 +184,9 @@ public: static void setWindowTitle(const QXcbConnection *conn, xcb_window_t window, const QString &title); static QString windowTitle(const QXcbConnection *conn, xcb_window_t window); + int swapInterval() const { return m_swapInterval; } + void setSwapInterval(int swapInterval) { m_swapInterval = swapInterval; } + public Q_SLOTS: void updateSyncRequestCounter(); @@ -276,6 +279,7 @@ protected: SyncState m_syncState = NoSyncNeeded; QXcbSyncWindowRequest *m_pendingSyncRequest = nullptr; + int m_swapInterval = -1; }; class QXcbForeignWindow : public QXcbWindow diff --git a/src/plugins/styles/mac/qmacstyle_mac.mm b/src/plugins/styles/mac/qmacstyle_mac.mm index 01eaa24e80..3542606a00 100644 --- a/src/plugins/styles/mac/qmacstyle_mac.mm +++ b/src/plugins/styles/mac/qmacstyle_mac.mm @@ -2555,11 +2555,12 @@ int QMacStyle::pixelMetric(PixelMetric metric, const QStyleOption *opt, const QW QPalette QMacStyle::standardPalette() const { - QPalette pal = QCommonStyle::standardPalette(); - pal.setColor(QPalette::Disabled, QPalette::Dark, QColor(191, 191, 191)); - pal.setColor(QPalette::Active, QPalette::Dark, QColor(191, 191, 191)); - pal.setColor(QPalette::Inactive, QPalette::Dark, QColor(191, 191, 191)); - return pal; + auto platformTheme = QGuiApplicationPrivate::platformTheme(); + auto styleNames = platformTheme->themeHint(QPlatformTheme::StyleNames); + if (styleNames.toStringList().contains("macintosh")) + return *platformTheme->palette(); + else + return QStyle::standardPalette(); } int QMacStyle::styleHint(StyleHint sh, const QStyleOption *opt, const QWidget *w, |