diff options
17 files changed, 357 insertions, 109 deletions
diff --git a/src/corelib/kernel/qassociativeiterable.h b/src/corelib/kernel/qassociativeiterable.h index 4f9bbf67bfb..c042fe4a0ec 100644 --- a/src/corelib/kernel/qassociativeiterable.h +++ b/src/corelib/kernel/qassociativeiterable.h @@ -21,9 +21,8 @@ QT_WARNING_DISABLE_DEPRECATED Q_CORE_EXPORT QT_DEPRECATED_VERSION_X_6_15(text) #endif -class -QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaAssociation::Iterable::Iterator instead.") -QAssociativeIterator : public QIterator<QMetaAssociation> +// Keep this a single long line, otherwise syncqt doesn't create a class forwarding header +class QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaAssociation's iterables and iterators instead.") QAssociativeIterator : public QIterator<QMetaAssociation> { public: using key_type = QVariant; @@ -42,9 +41,8 @@ public: QVariantPointer<QAssociativeIterator> operator->() const; }; -class -QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaAssociation::Iterable::ConstIterator instead.") -QAssociativeConstIterator : public QConstIterator<QMetaAssociation> +// Keep this a single long line, otherwise syncqt doesn't create a class forwarding header +class QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaAssociation's iterables and iterators instead.") QAssociativeConstIterator : public QConstIterator<QMetaAssociation> { public: using key_type = QVariant; @@ -63,9 +61,8 @@ public: QVariantConstPointer operator->() const; }; -class -QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaAssociation::Iterable instead.") -QAssociativeIterable : public QIterable<QMetaAssociation> +// Keep this a single long line, otherwise syncqt doesn't create a class forwarding header +class QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaAssociation's iterables and iterators instead.") QAssociativeIterable : public QIterable<QMetaAssociation> { public: using iterator = QTaggedIterator<QAssociativeIterator, void>; diff --git a/src/corelib/kernel/qsequentialiterable.h b/src/corelib/kernel/qsequentialiterable.h index 76908bdae4b..b2e690ea9cf 100644 --- a/src/corelib/kernel/qsequentialiterable.h +++ b/src/corelib/kernel/qsequentialiterable.h @@ -21,9 +21,8 @@ QT_WARNING_DISABLE_DEPRECATED Q_CORE_EXPORT QT_DEPRECATED_VERSION_X_6_15(text) #endif -class -QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaSequence::Iterable::Iterator instead.") -QSequentialIterator : public QIterator<QMetaSequence> +// Keep this a single long line, otherwise syncqt doesn't create a class forwarding header +class QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaSequence's iterables and iterators instead.") QSequentialIterator : public QIterator<QMetaSequence> { public: using value_type = QVariant; @@ -38,9 +37,8 @@ public: QVariantPointer<QSequentialIterator> operator->() const; }; -class -QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaSequence::Iterable::ConstIterator instead.") -QSequentialConstIterator : public QConstIterator<QMetaSequence> +// Keep this a single long line, otherwise syncqt doesn't create a class forwarding header +class QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaSequence's iterables and iterators instead.") QSequentialConstIterator : public QConstIterator<QMetaSequence> { public: using value_type = QVariant; @@ -55,9 +53,8 @@ public: QVariantConstPointer operator->() const; }; -class -QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaSequence::Iterable instead.") -QSequentialIterable : public QIterable<QMetaSequence> +// Keep this a single long line, otherwise syncqt doesn't create a class forwarding header +class QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaSequence's iterables and iterators instead.") QSequentialIterable : public QIterable<QMetaSequence> { public: using iterator = QTaggedIterator<QSequentialIterator, void>; diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index 9459e8bdfd3..f96f154f5b1 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -4035,10 +4035,13 @@ void QWindowsWindow::requestUpdate() // the Posted event on the GUI thread. if (m_vsyncUpdatePending.testAndSetAcquire(UpdateState::Requested, UpdateState::Posted)) { QWindowsWindow *oldSelf = this; - QMetaObject::invokeMethod(w, [w, oldSelf] { + qsizetype oldCallbackId = m_vsyncServiceCallbackId; + QMetaObject::invokeMethod(w, [w, oldSelf, oldCallbackId] { // 'oldSelf' is only used for comparison, don't access it directly! auto *self = static_cast<QWindowsWindow *>(w->handle()); - if (self && self == oldSelf) { + // NOTE: In the off chance that the window got destroyed and recreated with the + // same address, we also check that the callback id is the same. + if (self && self == oldSelf && self->m_vsyncServiceCallbackId == oldCallbackId) { // The platform window is still alive self->m_vsyncUpdatePending.storeRelease(UpdateState::Ready); self->deliverUpdateRequest(); diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp index e2f181aa628..2ea3fe38e1c 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp @@ -596,6 +596,16 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR *pRetVal = QComVariant{ accessible->text(QAccessible::Help) }.release(); break; case UIA_HasKeyboardFocusPropertyId: + // If the top-level window has no focused child, report the top-level + // widget (window). If it already has a focused widget, it will be + // reported automatically. + if (topLevelWindow) { + QAccessibleInterface *focusacc = accessible->focusChild(); + if (!focusacc) { + *pRetVal = QComVariant{ accessible->state().active ? true : false }.release(); + break; + } + } *pRetVal = QComVariant{ accessible->state().focused ? true : false }.release(); break; case UIA_IsKeyboardFocusablePropertyId: diff --git a/src/testlib/qtest.h b/src/testlib/qtest.h index 431f91d5474..10bb63040c8 100644 --- a/src/testlib/qtest.h +++ b/src/testlib/qtest.h @@ -181,16 +181,16 @@ inline bool qCompare(quint32 const &t1, quint64 const &t2, const char *actual, } namespace Internal { -template <typename T, typename = void> -struct HasInitMain : std::false_type{}; +template <typename T> +using InitMainTest = decltype(&T::initMain); template <typename T> -struct HasInitMain<T, std::void_t<decltype(&T::initMain)>> : std::true_type {}; +constexpr inline bool hasInitMain = qxp::is_detected_v<InitMainTest, T>; template<typename T> void callInitMain() { - if constexpr (HasInitMain<T>::value) + if constexpr (hasInitMain<T>) T::initMain(); } diff --git a/src/widgets/itemviews/qtreeview.cpp b/src/widgets/itemviews/qtreeview.cpp index 570566793dc..0f0496ad04d 100644 --- a/src/widgets/itemviews/qtreeview.cpp +++ b/src/widgets/itemviews/qtreeview.cpp @@ -1622,12 +1622,6 @@ void QTreeViewPrivate::calcLogicalIndices( const auto indicesCount = logicalIndices->size(); itemPositions->resize(indicesCount); for (qsizetype currentLogicalSection = 0; currentLogicalSection < indicesCount; ++currentLogicalSection) { - // shortcut, no need to calc anything more - if (indicesCount == 1 || spanning) { - (*itemPositions)[currentLogicalSection] = QStyleOptionViewItem::OnlyOne; - continue; - } - const int headerSection = logicalIndices->at(currentLogicalSection); // determine the viewItemPosition depending on the position of column 0 int nextLogicalSection = currentLogicalSection + 1 >= indicesCount ? logicalIndexAfterRight @@ -1635,8 +1629,11 @@ void QTreeViewPrivate::calcLogicalIndices( int prevLogicalSection = currentLogicalSection - 1 < 0 ? logicalIndexBeforeLeft : logicalIndices->at(currentLogicalSection - 1); + const int headerSection = logicalIndices->at(currentLogicalSection); QStyleOptionViewItem::ViewItemPosition pos; - if ((nextLogicalSection != 0 && prevLogicalSection == -1) || isTreePosition(headerSection)) + if ((nextLogicalSection == -1 && prevLogicalSection == -1) || spanning) { + pos = QStyleOptionViewItem::OnlyOne; + } else if ((nextLogicalSection != 0 && prevLogicalSection == -1) || isTreePosition(headerSection)) pos = QStyleOptionViewItem::Beginning; else if (nextLogicalSection == 0 || nextLogicalSection == -1) pos = QStyleOptionViewItem::End; diff --git a/src/widgets/kernel/qstandardgestures.cpp b/src/widgets/kernel/qstandardgestures.cpp index e9de82d2357..314d82fd64f 100644 --- a/src/widgets/kernel/qstandardgestures.cpp +++ b/src/widgets/kernel/qstandardgestures.cpp @@ -348,8 +348,14 @@ QGestureRecognizer::Result QSwipeGestureRecognizer::recognize(QGesture *state, result = QGestureRecognizer::Ignore; break; case QSwipeGesturePrivate::ThreePointsReached: - result = (ev->touchPointStates() & QEventPoint::State::Pressed) - ? QGestureRecognizer::CancelGesture : QGestureRecognizer::Ignore; + if (ev->touchPointStates() & QEventPoint::State::Pressed) { + result = QGestureRecognizer::CancelGesture; + } else if (d->verticalDirection != QSwipeGesture::NoDirection || + d->horizontalDirection != QSwipeGesture::NoDirection) { + result = QGestureRecognizer::TriggerGesture; + } else { + result = QGestureRecognizer::Ignore; + } break; } } diff --git a/src/widgets/styles/qcommonstyle.cpp b/src/widgets/styles/qcommonstyle.cpp index 592b70ef8ba..1f026775435 100644 --- a/src/widgets/styles/qcommonstyle.cpp +++ b/src/widgets/styles/qcommonstyle.cpp @@ -1200,7 +1200,7 @@ void QCommonStylePrivate::tabLayout(const QStyleOptionTab *opt, const QWidget *w int vpadding = proxyStyle->pixelMetric(QStyle::PM_TabBarTabVSpace, opt, widget) / 2; if (opt->shape == QTabBar::RoundedSouth || opt->shape == QTabBar::TriangularSouth) verticalShift = -verticalShift; - tr.adjust(hpadding, verticalShift - vpadding, horizontalShift - hpadding, vpadding); + tr.adjust(hpadding, verticalShift + vpadding, horizontalShift - hpadding, -vpadding); bool selected = opt->state & QStyle::State_Selected; if (selected) { tr.setTop(tr.top() - verticalShift); @@ -1625,23 +1625,18 @@ void QCommonStyle::drawControl(ControlElement element, const QStyleOption *opt, if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { QRect rect = header->rect; if (!header->icon.isNull()) { - int iconExtent = proxy()->pixelMetric(PM_SmallIconSize, opt, widget); - QPixmap pixmap - = header->icon.pixmap(QSize(iconExtent, iconExtent), QStyleHelper::getDpr(p), (header->state & State_Enabled) ? QIcon::Normal : QIcon::Disabled); - int pixw = pixmap.width() / pixmap.devicePixelRatio(); - - QRect aligned = alignedRect(header->direction, header->iconAlignment, pixmap.size() / pixmap.devicePixelRatio(), rect); - QRect inter = aligned.intersected(rect); - p->drawPixmap(inter.x(), inter.y(), pixmap, - inter.x() - aligned.x(), inter.y() - aligned.y(), - aligned.width() * pixmap.devicePixelRatio(), - pixmap.height() * pixmap.devicePixelRatio()); + const int iconExtent = proxy()->pixelMetric(PM_SmallIconSize, opt, widget); + const QRect aligned = alignedRect(header->direction, header->iconAlignment, + QSize(iconExtent, iconExtent), rect); + header->icon.paint(p, aligned, Qt::AlignCenter, + header->state.testFlag(State_Enabled) ? QIcon::Normal + : QIcon::Disabled); const int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, opt, widget); if (header->direction == Qt::LeftToRight) - rect.setLeft(rect.left() + pixw + margin); + rect.setLeft(rect.left() + iconExtent + margin); else - rect.setRight(rect.right() - pixw - margin); + rect.setRight(rect.right() - iconExtent - margin); } QFontMetrics fm(header->fontMetrics); if (header->state & QStyle::State_On) { @@ -2838,7 +2833,6 @@ QRect QCommonStyle::subElementRect(SubElement sr, const QStyleOption *opt, int verticalShift = proxy()->pixelMetric(QStyle::PM_TabBarTabShiftVertical, tab, widget); int horizontalShift = proxy()->pixelMetric(QStyle::PM_TabBarTabShiftHorizontal, tab, widget); int hpadding = proxy()->pixelMetric(QStyle::PM_TabBarTabHSpace, opt, widget) / 2; - hpadding = qMax(hpadding, 4); //workaround KStyle returning 0 because they workaround an old bug in Qt bool verticalTabs = tab->shape == QTabBar::RoundedEast || tab->shape == QTabBar::RoundedWest @@ -2866,8 +2860,6 @@ QRect QCommonStyle::subElementRect(SubElement sr, const QStyleOption *opt, QSize size = (sr == SE_TabBarTabLeftButton) ? tab->leftButtonSize : tab->rightButtonSize; int w = size.width(); int h = size.height(); - int midHeight = static_cast<int>(qCeil(float(tr.height() - h) / 2)); - int midWidth = ((tr.width() - w) / 2); bool atTheTop = true; switch (tab->shape) { @@ -2879,14 +2871,19 @@ QRect QCommonStyle::subElementRect(SubElement sr, const QStyleOption *opt, case QTabBar::TriangularEast: atTheTop = (sr == SE_TabBarTabRightButton); break; - default: + default: { + const int midHeight = + tr.y() + static_cast<int>(qCeil(float(tr.height() - h) / 2)); if (sr == SE_TabBarTabLeftButton) r = QRect(tab->rect.x() + hpadding, midHeight, w, h); else r = QRect(tab->rect.right() - w - hpadding, midHeight, w, h); r = visualRect(tab->direction, tab->rect, r); + break; + } } if (verticalTabs) { + const int midWidth = tr.x() + ((tr.width() - w) / 2); if (atTheTop) r = QRect(midWidth, tr.y() + tab->rect.height() - hpadding - h, w, h); else @@ -4209,20 +4206,19 @@ QRect QCommonStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex #if QT_CONFIG(toolbutton) case CC_ToolButton: if (const QStyleOptionToolButton *tb = qstyleoption_cast<const QStyleOptionToolButton *>(opt)) { - int mbi = proxy()->pixelMetric(PM_MenuButtonIndicator, tb, widget); ret = tb->rect; switch (sc) { case SC_ToolButton: - if ((tb->features - & (QStyleOptionToolButton::MenuButtonPopup | QStyleOptionToolButton::PopupDelay)) - == QStyleOptionToolButton::MenuButtonPopup) + if (tb->features.testFlag(QStyleOptionToolButton::MenuButtonPopup)) { + const int mbi = proxy()->pixelMetric(PM_MenuButtonIndicator, tb, widget); ret.adjust(0, 0, -mbi, 0); + } break; case SC_ToolButtonMenu: - if ((tb->features - & (QStyleOptionToolButton::MenuButtonPopup | QStyleOptionToolButton::PopupDelay)) - == QStyleOptionToolButton::MenuButtonPopup) + if (tb->features.testFlag(QStyleOptionToolButton::MenuButtonPopup)) { + const int mbi = proxy()->pixelMetric(PM_MenuButtonIndicator, tb, widget); ret.adjust(ret.width() - mbi, 0, 0, 0); + } break; default: break; diff --git a/src/widgets/styles/qstylesheetstyle.cpp b/src/widgets/styles/qstylesheetstyle.cpp index e0fdd56e6d8..f36bcf458d9 100644 --- a/src/widgets/styles/qstylesheetstyle.cpp +++ b/src/widgets/styles/qstylesheetstyle.cpp @@ -94,6 +94,7 @@ #endif #include <QtGui/qpainterpath.h> +#include <QtGui/qpainterstateguard.h> #include <QtGui/qscreen.h> #include <QtCore/private/qduplicatetracker_p.h> @@ -4085,29 +4086,56 @@ void QStyleSheetStyle::drawControl(ControlElement ce, const QStyleOption *opt, Q case CE_HeaderLabel: if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { - QStyleOptionHeaderV2 hdr; - QStyleOptionHeader &v1Copy = hdr; - if (auto v2 = qstyleoption_cast<const QStyleOptionHeaderV2 *>(opt)) - hdr = *v2; - else - v1Copy = *header; + // Save painter state + QPainterStateGuard psGuard(p); + + QStyleOptionHeaderV2 hdr; + QStyleOptionHeader &v1Copy = hdr; + if (auto v2 = qstyleoption_cast<const QStyleOptionHeaderV2 *>(header)) + hdr = *v2; + else + v1Copy = *header; + QRenderRule subRule = renderRule(w, opt, PseudoElement_HeaderViewSection); if (hasStyleRule(w, PseudoElement_HeaderViewUpArrow) || hasStyleRule(w, PseudoElement_HeaderViewDownArrow)) { if (hdr.sortIndicator != QStyleOptionHeader::None) { + QRegion newClipRegion = QRegion(hdr.rect); const QRect arrowRect = subElementRect(SE_HeaderArrow, opt, w); - if (hdr.orientation == Qt::Horizontal) - hdr.rect.setWidth(hdr.rect.width() - arrowRect.width()); - else - hdr.rect.setHeight(hdr.rect.height() - arrowRect.height()); + const QRenderRule hrRule = renderRule(w, opt, PseudoElement_HeaderViewUpArrow); + // Clip the text to avoid overlapping with the sort arrow if + // the sort arrow is aligned to the right or left if horizontal + // or top or bottom if vertical. + if (hrRule.hasPosition()) { + const auto position = hrRule.position()->position; + if (hdr.orientation == Qt::Horizontal) { + if (position & Qt::AlignLeft) { + newClipRegion -= QRegion(arrowRect.x(), hdr.rect.y(), + arrowRect.width(), hdr.rect.height()); + } else if (position & Qt::AlignRight) { + newClipRegion -= QRegion(arrowRect.x(), hdr.rect.y(), + arrowRect.width(), hdr.rect.height()); + } + } else if (hdr.orientation == Qt::Vertical) { + if (position & Qt::AlignTop) { + newClipRegion -= QRegion(arrowRect.x(), hdr.rect.y(), + hdr.rect.width(), arrowRect.height()); + } else if (position & Qt::AlignBottom) { + newClipRegion -= QRegion(arrowRect.x(), arrowRect.y(), + hdr.rect.width(), arrowRect.height()); + } + } + } + p->setClipping(true); + p->setClipRegion(newClipRegion); } } + subRule.configurePalette(&hdr.palette, QPalette::ButtonText, QPalette::Button); + if (subRule.hasFont) { - QFont oldFont = p->font(); p->setFont(subRule.font.resolve(p->font())); ParentStyle::drawControl(ce, &hdr, p, w); - p->setFont(oldFont); } else { baseStyle()->drawControl(ce, &hdr, p, w); } @@ -5346,17 +5374,8 @@ QSize QStyleSheetStyle::sizeFromContents(ContentsType ct, const QStyleOption *op } return subRule.size(sz); } - sz = subRule.baseStyleCanDraw() ? baseStyle()->sizeFromContents(ct, opt, sz, w) - : QWindowsStyle::sizeFromContents(ct, opt, sz, w); - if (hasStyleRule(w, PseudoElement_HeaderViewDownArrow) - || hasStyleRule(w, PseudoElement_HeaderViewUpArrow)) { - const QRect arrowRect = subElementRect(SE_HeaderArrow, opt, w); - if (hdr->orientation == Qt::Horizontal) - sz.rwidth() += arrowRect.width(); - else - sz.rheight() += arrowRect.height(); - } - return sz; + return subRule.baseStyleCanDraw() ? baseStyle()->sizeFromContents(ct, opt, sz, w) + : QWindowsStyle::sizeFromContents(ct, opt, sz, w); } } break; @@ -6240,18 +6259,6 @@ QRect QStyleSheetStyle::subElementRect(SubElement se, const QStyleOption *opt, c QRenderRule subRule = renderRule(w, opt, PseudoElement_HeaderViewSection); if (subRule.hasBox() || !subRule.hasNativeBorder()) { auto r = subRule.contentsRect(opt->rect); - if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(opt)) { - // Subtract width needed for arrow, if there is one - if (header->sortIndicator != QStyleOptionHeader::None) { - const auto arrowRect = subElementRect(SE_HeaderArrow, opt, w); - if (arrowRect.isValid()) { - if (opt->state & State_Horizontal) - r.setWidth(r.width() - arrowRect.width()); - else - r.setHeight(r.height() - arrowRect.height()); - } - } - } return r; } } diff --git a/tests/auto/testlib/CMakeLists.txt b/tests/auto/testlib/CMakeLists.txt index 35a26a8e345..0829d71f187 100644 --- a/tests/auto/testlib/CMakeLists.txt +++ b/tests/auto/testlib/CMakeLists.txt @@ -1,6 +1,7 @@ # Copyright (C) 2022 The Qt Company Ltd. # SPDX-License-Identifier: BSD-3-Clause +add_subdirectory(initmain) add_subdirectory(qsignalspy) add_subdirectory(tostring) diff --git a/tests/auto/testlib/initmain/CMakeLists.txt b/tests/auto/testlib/initmain/CMakeLists.txt new file mode 100644 index 00000000000..6dfdc8965f0 --- /dev/null +++ b/tests/auto/testlib/initmain/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qsignalspy LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_initmain + SOURCES + tst_initmain.cpp +) diff --git a/tests/auto/testlib/initmain/tst_initmain.cpp b/tests/auto/testlib/initmain/tst_initmain.cpp index cdaac0c14f4..cf1a0880035 100644 --- a/tests/auto/testlib/initmain/tst_initmain.cpp +++ b/tests/auto/testlib/initmain/tst_initmain.cpp @@ -3,6 +3,7 @@ #include <QtCore/QCoreApplication> +#include <QtCore/qxptype_traits.h> #include <QTest> class tst_InitMain : public QObject @@ -19,8 +20,8 @@ private: static bool m_initMainCalled; }; -static_assert(QTest::Internals::HasInitMain<tst_InitMain>::value); -static_assert(!QTest::Internals::HasInitMain<QObject>::value); +static_assert(QTest::Internal::hasInitMain<tst_InitMain>); +static_assert(!QTest::Internal::hasInitMain<QObject>); bool tst_InitMain::m_initMainCalled = false; diff --git a/tests/auto/widgets/itemviews/qtreeview/tst_qtreeview.cpp b/tests/auto/widgets/itemviews/qtreeview/tst_qtreeview.cpp index ac50a5cdae7..e42fb4419a8 100644 --- a/tests/auto/widgets/itemviews/qtreeview/tst_qtreeview.cpp +++ b/tests/auto/widgets/itemviews/qtreeview/tst_qtreeview.cpp @@ -3518,12 +3518,33 @@ void tst_QTreeView::styleOptionViewItem() model.clear(); model.appendRow({ new QStandardItem("Hidden"), new QStandardItem("OnlyOne Last") }); + view.setModel(&model); view.setColumnHidden(0, true); view.setColumnHidden(1, false); - view.setModel(&model); delegate.count = 0; QTRY_COMPARE_GE(delegate.count, 1); + + // special case, four columns, only one is updated but + // calcLogicalIndices() returns the correct value + { + model.clear(); + model.appendRow({ new QStandardItem("Hidden"), + new QStandardItem("Beginning Last"), + new QStandardItem("Middle Last"), + new QStandardItem("End Last") }); + view.setColumnHidden(0, true); + delegate.count = 0; + QTRY_COMPARE_GE(delegate.count, 4); + + // do not rely on paintEvent() as this might redraw a bigger + // rect than we expect + QList<int> logicalIndices; + QList<QStyleOptionViewItem::ViewItemPosition> itemPositions; + view.d_func()->calcLogicalIndices(&logicalIndices, &itemPositions, 3, 3); + QCOMPARE(itemPositions.size(), 1); + QCOMPARE(itemPositions.at(0), QStyleOptionViewItem::End); + } } class task174627_TreeView : public QTreeView diff --git a/tests/auto/widgets/kernel/qgesturerecognizer/tst_qgesturerecognizer.cpp b/tests/auto/widgets/kernel/qgesturerecognizer/tst_qgesturerecognizer.cpp index febb9e2c9dc..255bcaa5bec 100644 --- a/tests/auto/widgets/kernel/qgesturerecognizer/tst_qgesturerecognizer.cpp +++ b/tests/auto/widgets/kernel/qgesturerecognizer/tst_qgesturerecognizer.cpp @@ -9,11 +9,14 @@ #include <QtGui/QScreen> #include <QtGui/QPointingDevice> #include <QtCore/QList> +#include <QtCore/QLoggingCategory> #include <QtCore/QString> #include <QtCore/QHash> #include <QtCore/QDebug> #include <memory> +Q_LOGGING_CATEGORY(lcTests, "qt.widgets.tests") + class tst_QGestureRecognizer : public QObject { Q_OBJECT @@ -60,14 +63,30 @@ public: bool gestureReceived(Qt::GestureType gestureType) const { return m_receivedGestures.value(gestureType); } -protected: - bool event(QEvent * event) override; + void clearReceivedGestures(); -private: - typedef QHash<Qt::GestureType, bool> GestureTypeHash; - GestureTypeHash m_receivedGestures; + qreal lastSwipeAngle = 0; + QSwipeGesture::SwipeDirection lastHorizontalDirection = QSwipeGesture::NoDirection; + QSwipeGesture::SwipeDirection lastVerticalDirection = QSwipeGesture::NoDirection; + Qt::GestureState lastSwipeState = Qt::NoGesture; + + protected: + bool event(QEvent *event) override; + + private: + typedef QHash<Qt::GestureType, bool> GestureTypeHash; + GestureTypeHash m_receivedGestures; }; +void TestWidget::clearReceivedGestures() +{ + m_receivedGestures.clear(); + lastSwipeAngle = {}; + lastHorizontalDirection = QSwipeGesture::NoDirection; + lastVerticalDirection = QSwipeGesture::NoDirection; + lastSwipeState = Qt::NoGesture; +} + TestWidget::TestWidget(const GestureTypeVector &gestureTypes) { setAttribute(Qt::WA_AcceptTouchEvents); @@ -95,8 +114,22 @@ bool TestWidget::event(QEvent * event) it.value() = true; } } - } + for (const QGesture *gesture : gestureEvent->activeGestures()) { + switch (gesture->gestureType()) { + case Qt::SwipeGesture: { + const auto *swipe = static_cast<const QSwipeGesture *>(gesture); + lastSwipeAngle = swipe->swipeAngle(); + lastHorizontalDirection = swipe->horizontalDirection(); + lastVerticalDirection = swipe->verticalDirection(); + lastSwipeState = gesture->state(); + break; + } + default: + break; + } + } break; + } default: break; } @@ -241,10 +274,26 @@ enum SwipeSubTest { void tst_QGestureRecognizer::swipeGesture_data() { QTest::addColumn<int>("swipeSubTest"); + QTest::addColumn<QPoint>("moveDelta"); QTest::addColumn<bool>("gestureExpected"); - QTest::newRow("Line") << int(SwipeLineSubTest) << true; - QTest::newRow("DirectionChange") << int(SwipeDirectionChangeSubTest) << false; - QTest::newRow("SmallDirectionChange") << int(SwipeSmallDirectionChangeSubTest) << true; + QTest::addColumn<int>("expectedAngle"); + QTest::addColumn<QSwipeGesture::SwipeDirection>("expectedHorizontalDirection"); + QTest::addColumn<QSwipeGesture::SwipeDirection>("expectedVerticalDirection"); + + QTest::newRow("UpRight Line") << int(SwipeLineSubTest) << QPoint(42, -25) + << true << 30 << QSwipeGesture::Right << QSwipeGesture::Up; + QTest::newRow("DownRight Line") << int(SwipeLineSubTest) << QPoint(42, 25) + << true << 329 << QSwipeGesture::Right << QSwipeGesture::Down; + QTest::newRow("OutRight Line") << int(SwipeLineSubTest) << QPoint(42, 0) + << true << 360 << QSwipeGesture::Right << QSwipeGesture::NoDirection; + QTest::newRow("DownLeft Line") << int(SwipeLineSubTest) << QPoint(-42, 25) + << true << 211 << QSwipeGesture::Left << QSwipeGesture::Down; + QTest::newRow("Up Line") << int(SwipeLineSubTest) << QPoint(0, -25) + << true << 90 << QSwipeGesture::NoDirection << QSwipeGesture::Up; + QTest::newRow("DirectionChange") << int(SwipeDirectionChangeSubTest) << QPoint(42, 25) + << false << 0 << QSwipeGesture::NoDirection << QSwipeGesture::NoDirection; + QTest::newRow("SmallDirectionChange") << int(SwipeSmallDirectionChangeSubTest) << QPoint(42, -25) + << true << 359 << QSwipeGesture::Right << QSwipeGesture::Down; } void tst_QGestureRecognizer::swipeGesture() @@ -252,7 +301,11 @@ void tst_QGestureRecognizer::swipeGesture() enum { swipePoints = 3 }; QFETCH(int, swipeSubTest); + QFETCH(QPoint, moveDelta); QFETCH(bool, gestureExpected); + QFETCH(int, expectedAngle); + QFETCH(QSwipeGesture::SwipeDirection, expectedHorizontalDirection); + QFETCH(QSwipeGesture::SwipeDirection, expectedVerticalDirection); const Qt::GestureType gestureType = Qt::SwipeGesture; TestWidget widget(GestureTypeVector(1, gestureType)); @@ -264,20 +317,27 @@ void tst_QGestureRecognizer::swipeGesture() // Start a swipe sequence with 2 points (QTBUG-15768) const QPoint fingerDistance(m_fingerDistance, m_fingerDistance); QList<QPoint> points; - for (int i = 0; i < swipePoints - 1; ++i) + for (int i = 1; i < swipePoints; ++i) points.append(fingerDistance + i * fingerDistance); QTest::QTouchEventWidgetSequence swipeSequence = QTest::touchEvent(&widget, m_touchDevice.get()); pressSequence(swipeSequence, points, &widget); + // Move a little: nothing happens + points[0] += {1, 1}; + points[1] += {1, 1}; + swipeSequence.move(0, points[0], &widget).move(1, points[1], &widget).commit(); + QCoreApplication::processEvents(); + QVERIFY(!widget.gestureReceived(gestureType)); + // Press point #3 points.append(points.last() + fingerDistance); swipeSequence.stationary(0).stationary(1).press(points.size() - 1, points.last(), &widget); swipeSequence.commit(); Q_ASSERT(points.size() == swipePoints); + QCOMPARE(widget.lastSwipeState, Qt::NoGesture); // Move. - const QPoint moveDelta(60, 20); switch (swipeSubTest) { case SwipeLineSubTest: linearSequence(5, moveDelta, swipeSequence, points, &widget); @@ -294,15 +354,37 @@ void tst_QGestureRecognizer::swipeGesture() } break; } + QCOMPARE(widget.lastSwipeState, Qt::GestureUpdated); - releaseSequence(swipeSequence, points, &widget); - + // release any point: the gesture ends + swipeSequence.release(0, points[0], &widget).commit(); if (gestureExpected) { QTRY_VERIFY(widget.gestureReceived(gestureType)); + qCDebug(lcTests) << "started @" << fingerDistance + << "; ended with angle" << widget.lastSwipeAngle + << "expected" << expectedAngle + << "dirns" << widget.lastHorizontalDirection << widget.lastVerticalDirection; + QCOMPARE(qRound(widget.lastSwipeAngle), expectedAngle); + QEXPECT_FAIL("Up Line", "90 degrees (up) should be NoDirection on horizontal axis", Continue); + QCOMPARE(widget.lastHorizontalDirection, expectedHorizontalDirection); + QEXPECT_FAIL("OutRight Line", "0 degrees (to the right) should be NoDirection on the vertical axis", Continue); + QCOMPARE(widget.lastVerticalDirection, expectedVerticalDirection); + QCOMPARE(widget.lastSwipeState, Qt::GestureFinished); } else { QCoreApplication::processEvents(); QVERIFY(!widget.gestureReceived(gestureType)); + QCOMPARE(widget.lastSwipeState, Qt::GestureUpdated); } + + // move the others a little, then release: no further swipe (it needs 3 fingers) + widget.clearReceivedGestures(); + points[1] += {1, 1}; + points[2] += {1, 1}; + swipeSequence.move(1, points[1], &widget).move(2, points[2], &widget).commit(); + swipeSequence.release(1, points[1], &widget).release(2, points[2], &widget).commit(); + QCoreApplication::processEvents(); + QVERIFY(!widget.gestureReceived(gestureType)); + QCOMPARE(widget.lastSwipeState, Qt::NoGesture); } void tst_QGestureRecognizer::touchReplay() diff --git a/tests/manual/widgets/itemviews/CMakeLists.txt b/tests/manual/widgets/itemviews/CMakeLists.txt index 4a67078103c..bae613b4fb9 100644 --- a/tests/manual/widgets/itemviews/CMakeLists.txt +++ b/tests/manual/widgets/itemviews/CMakeLists.txt @@ -5,3 +5,4 @@ add_subdirectory(qheaderview) add_subdirectory(qtreeview) add_subdirectory(qtreewidget) add_subdirectory(tableview-span-navigation) +add_subdirectory(qtablewidget-sort-indicator) diff --git a/tests/manual/widgets/itemviews/qtablewidget-sort-indicator/CMakeLists.txt b/tests/manual/widgets/itemviews/qtablewidget-sort-indicator/CMakeLists.txt new file mode 100644 index 00000000000..9d1ef6e7931 --- /dev/null +++ b/tests/manual/widgets/itemviews/qtablewidget-sort-indicator/CMakeLists.txt @@ -0,0 +1,16 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## qtablewidget-sort-indicator Binary: +##################################################################### + +qt_internal_add_manual_test(qtablewidget-sort-indicator + GUI + SOURCES + main.cpp + LIBRARIES + Qt::CorePrivate + Qt::Gui + Qt::Widgets +) diff --git a/tests/manual/widgets/itemviews/qtablewidget-sort-indicator/main.cpp b/tests/manual/widgets/itemviews/qtablewidget-sort-indicator/main.cpp new file mode 100644 index 00000000000..b3464c67c89 --- /dev/null +++ b/tests/manual/widgets/itemviews/qtablewidget-sort-indicator/main.cpp @@ -0,0 +1,100 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QApplication> +#include <QTableWidget> +#include <QHeaderView> +#include <QVBoxLayout> +#include <QGroupBox> +#include <QRadioButton> +#include <QLabel> + +QString createStyleSheet(const char *sortArrowPos) +{ + QString styleSheet {R"( + QHeaderView::section { + background-color: #f0f0f0; + padding: 5px; + border: 1px solid #ffffff; + font-weight: bold; + } + + QHeaderView::up-arrow, QHeaderView::down-arrow { + width: 24px; + height: 24px; + subcontrol-position: %1; + subcontrol-origin: padding; + } + )"}; + + return styleSheet.arg(sortArrowPos); +} + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + QWidget mainWidget; + mainWidget.setWindowTitle("QTableWidget sort indicator overlap with clipping"); + + const QStringList headerLabels { "Header", "LongHeaderText"}; + const QStringList column1 { "Alpha", "Beta", "Gamma" }; + const QStringList column2 { "1", "2", "3" }; + + QTableWidget tableWidget(3, 2, &mainWidget); + tableWidget.setHorizontalHeaderLabels(headerLabels); + + for (int i {0} ; i < column1.size() ; ++i) { + tableWidget.setItem(i, 0, new QTableWidgetItem(column1[i])); + tableWidget.setItem(i, 1, new QTableWidgetItem(column2[i])); + } + + tableWidget.setSortingEnabled(true); + tableWidget.setStyleSheet(createStyleSheet("center right")); + tableWidget.adjustSize(); + + QVBoxLayout mainLayout {&mainWidget}; + QGroupBox buttonBox; + QVBoxLayout buttonLayout {&buttonBox}; + QRadioButton leftButton {"Left-aligned sort indicator"}; + QRadioButton centerButton {"Center-aligned sort indicator"}; + QRadioButton rightButton {"Right-aligned sort indicator"}; + + buttonLayout.addWidget(&leftButton); + buttonLayout.addWidget(¢erButton); + buttonLayout.addWidget(&rightButton); + + mainLayout.addWidget(&tableWidget); + mainLayout.addWidget(&buttonBox); + + QLabel instructions { QT_TR_NOOP(R"(<html>Instructions: +<ol> +<li>There are 3 options for alignment of the column header sort arrow: left, center, and right. Click one of the 3 radio buttons to select the sort arrow alignment. +</li> +<li>Click the left column header to sort the table. The sort arrow should appear at its correct alignment without overlapping the text. +</li> +<li>Click the right column header. The sort arrow should appear at its correct alignment. The left and right alignment should clip the text without changing its position. The center alignment should not clip the text at all. +</ol> + </html>)")}; + instructions.setTextFormat(Qt::AutoText); + instructions.setWordWrap(true); + mainLayout.addWidget(&instructions); + + QObject::connect(&leftButton, &QRadioButton::clicked, &app, [&](bool checked) { + if (checked) + tableWidget.setStyleSheet(createStyleSheet("center left")); + }); + QObject::connect(¢erButton, &QRadioButton::clicked, &app, [&](bool checked) { + if (checked) + tableWidget.setStyleSheet(createStyleSheet("top center")); + }); + QObject::connect(&rightButton, &QRadioButton::clicked, &app, [&](bool checked) { + if (checked) + tableWidget.setStyleSheet(createStyleSheet("center right")); + }); + + leftButton.click(); + mainWidget.show(); + + return app.exec(); +} |
