diff --git a/lib/include/oclero/qlementine/style/QlementineStyle.hpp b/lib/include/oclero/qlementine/style/QlementineStyle.hpp index d039cb9..dbb3419 100644 --- a/lib/include/oclero/qlementine/style/QlementineStyle.hpp +++ b/lib/include/oclero/qlementine/style/QlementineStyle.hpp @@ -21,8 +21,6 @@ class QlementineStyle : public QCommonStyle { Q_OBJECT Q_PROPERTY(bool animationsEnabled READ animationsEnabled WRITE setAnimationsEnabled NOTIFY animationsEnabledChanged) - Q_PROPERTY(bool useMenuForComboBoxPopup READ useMenuForComboBoxPopup WRITE setUseMenuForComboBoxPopup NOTIFY - useMenuForComboBoxPopupChanged) public: enum class StandardPixmapExt { @@ -61,10 +59,6 @@ class QlementineStyle : public QCommonStyle { void setAnimationsEnabled(bool enabled); Q_SIGNAL void animationsEnabledChanged(); - bool useMenuForComboBoxPopup() const; - void setUseMenuForComboBoxPopup(bool useMenu); - Q_SIGNAL void useMenuForComboBoxPopupChanged(); - void triggerCompleteRepaint(); void setAutoIconColor(AutoIconColor autoIconColor); diff --git a/lib/src/style/Delegates.cpp b/lib/src/style/Delegates.cpp index 3c3aa3f..863976d 100644 --- a/lib/src/style/Delegates.cpp +++ b/lib/src/style/Delegates.cpp @@ -21,8 +21,11 @@ void ComboBoxDelegate::paint(QPainter* p, const QStyleOptionViewItem& opt, const const auto& theme = _qlementineStyle ? _qlementineStyle->theme() : Theme{}; const auto isSeparator = idx.data(Qt::AccessibleDescriptionRole).toString() == QLatin1String("separator"); + const auto contentMargin = _qlementineStyle->pixelMetric(QStyle::PM_MenuHMargin); + const auto contentRect = opt.rect.marginsRemoved({ contentMargin, 0, contentMargin, 0 }); + if (isSeparator) { - const auto& rect = opt.rect; + const auto& rect = contentRect; const auto& color = _qlementineStyle ? _qlementineStyle->toolBarSeparatorColor() : Theme().secondaryAlternativeColorDisabled; const auto lineW = theme.borderWidth; @@ -37,11 +40,11 @@ void ComboBoxDelegate::paint(QPainter* p, const QStyleOptionViewItem& opt, const const auto mouse = getMenuItemMouseState(opt.state); // Background. + const auto& bgRect = contentRect; const auto hPadding = theme.spacing; - const auto& bgRect = opt.rect; const auto& bgColor = _qlementineStyle ? _qlementineStyle->menuItemBackgroundColor(mouse) : Theme().primaryColorTransparent; - constexpr auto radius = 0; + const auto radius = _qlementineStyle->theme().borderRadius - contentMargin / 2; p->setRenderHint(QPainter::Antialiasing, true); p->setPen(Qt::NoPen); p->setBrush(bgColor); @@ -128,8 +131,9 @@ QSize ComboBoxDelegate::sizeHint(const QStyleOptionViewItem& opt, const QModelIn const auto h = theme.spacing + theme.borderWidth; return QSize{ h, h }; } else { + const auto contentMargin = _qlementineStyle->pixelMetric(QStyle::PM_MenuHMargin); const auto hPadding = theme.spacing; - const auto vPadding = theme.spacing / 2; + const auto vPadding = theme.spacing; const auto iconSize = theme.iconSize; const auto spacing = theme.spacing; const auto& fm = opt.fontMetrics; @@ -142,7 +146,7 @@ QSize ComboBoxDelegate::sizeHint(const QStyleOptionViewItem& opt, const QModelIn iconVariant.isValid() && iconVariant.userType() == QMetaType::QIcon ? iconVariant.value() : QIcon{}; const auto textW = qlementine::textWidth(fm, text); const auto iconW = !icon.isNull() ? iconSize.width() + spacing : 0; - const auto w = std::max(0, hPadding + iconW + textW + hPadding); + const auto w = std::max(0, contentMargin * 2 + hPadding + iconW + textW + hPadding); const auto h = std::max(theme.controlHeightMedium, std::max(iconSize.height(), vPadding) + vPadding); return QSize{ w, h }; } diff --git a/lib/src/style/EventFilters.cpp b/lib/src/style/EventFilters.cpp index b71eb36..fe8e63d 100644 --- a/lib/src/style/EventFilters.cpp +++ b/lib/src/style/EventFilters.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -20,6 +21,9 @@ #include #include #include +#include +#include +#include namespace oclero::qlementine { LineEditButtonEventFilter::LineEditButtonEventFilter( @@ -352,22 +356,47 @@ bool MenuEventFilter::eventFilter(QObject* watchedObject, QEvent* evt) { return QObject::eventFilter(watchedObject, evt); } -ComboboxItemViewFilter::ComboboxItemViewFilter(QAbstractItemView* view) +ComboboxItemViewFilter::ComboboxItemViewFilter(QComboBox* comboBox, QListView* view) : QObject(view) + , _comboBox(comboBox) , _view(view) { - view->installEventFilter(this); + _view->installEventFilter(this); + _view->parentWidget()->installEventFilter(this); + _comboBox->installEventFilter(this); } bool ComboboxItemViewFilter::eventFilter(QObject* watchedObject, QEvent* evt) { const auto type = evt->type(); - if (type == QEvent::Type::Show) { - // Fix Qt bug. - const auto width = _view->sizeHintForColumn(0); - _view->setMinimumWidth(width); + switch (type) { + case QEvent::Type::Show: + fixViewGeometry(); + break; + case QEvent::Type::Resize: + if (watchedObject == _comboBox) { + fixViewGeometry(); + } + break; + default: + break; } return QObject::eventFilter(watchedObject, evt); } +void ComboboxItemViewFilter::fixViewGeometry() { + const auto* comboBox = findFirstParentOfType(_view); + const auto* qlementineStyle = qobject_cast(comboBox->style()); + const auto contentMargin = qlementineStyle->pixelMetric(QStyle::PM_MenuHMargin); + const auto shadowWidth = qlementineStyle->theme().spacing; + const auto borderWidth = qlementineStyle->theme().borderWidth; + const auto width = + std::max(comboBox->width(), _view->sizeHintForColumn(0) + shadowWidth * 2) + contentMargin * 2 + borderWidth * 2; + const auto scrollButtonHeight = qlementineStyle->pixelMetric(QStyle::PM_MenuTearoffHeight); + const auto height = _view->minimumSizeHint().height() + scrollButtonHeight * 2; + _view->setFixedWidth(width); + _view->setFixedHeight(height); + _view->parentWidget()->adjustSize(); +} + TextEditEventFilter::TextEditEventFilter(QAbstractScrollArea* textEdit) : QObject(textEdit) , _textEdit(textEdit) {} @@ -528,5 +557,4 @@ bool LineEditMenuEventFilter::eventFilter(QObject*, QEvent* evt) { return false; } - } // namespace oclero::qlementine diff --git a/lib/src/style/EventFilters.hpp b/lib/src/style/EventFilters.hpp index 8d920db..5a3aca5 100644 --- a/lib/src/style/EventFilters.hpp +++ b/lib/src/style/EventFilters.hpp @@ -8,7 +8,8 @@ #include #include -#include +#include +#include #include namespace oclero::qlementine { @@ -71,12 +72,14 @@ class MenuEventFilter : public QObject { class ComboboxItemViewFilter : public QObject { public: - ComboboxItemViewFilter(QAbstractItemView* view); + ComboboxItemViewFilter(QComboBox* comboBox, QListView* view); bool eventFilter(QObject* watchedObject, QEvent* evt) override; private: - QAbstractItemView* _view{ nullptr }; + void fixViewGeometry(); + QComboBox* _comboBox{ nullptr }; + QListView* _view{ nullptr }; }; // Works for both QTextEdit and QPlainTextEdit diff --git a/lib/src/style/QlementineStyle.cpp b/lib/src/style/QlementineStyle.cpp index c05e415..7b5fd6a 100644 --- a/lib/src/style/QlementineStyle.cpp +++ b/lib/src/style/QlementineStyle.cpp @@ -255,7 +255,6 @@ struct QlementineStyleImpl { WidgetAnimationManager animations; std::unordered_map standardIconCache; std::unordered_map standardIconExtCache; - bool useMenuForComboBoxPopup{ false }; AutoIconColor autoIconColor{ AutoIconColor::None }; }; @@ -298,17 +297,6 @@ void QlementineStyle::setAnimationsEnabled(bool enabled) { } } -bool QlementineStyle::useMenuForComboBoxPopup() const { - return _impl->useMenuForComboBoxPopup; -} - -void QlementineStyle::setUseMenuForComboBoxPopup(bool useMenu) { - if (useMenu != _impl->useMenuForComboBoxPopup) { - _impl->useMenuForComboBoxPopup = useMenu; - emit useMenuForComboBoxPopupChanged(); - } -} - void QlementineStyle::triggerCompleteRepaint() { _impl->updateFonts(); _impl->updatePalette(); @@ -438,10 +426,8 @@ void QlementineStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption* opt case PE_FrameMenu: return; // Let PE_PanelMenu do the drawing. case PE_FrameStatusBarItem: { - // p->fillRect(opt->rect, Qt::red); - const auto rect = opt->rect; - const auto penColor = Qt::red; // _impl->theme.borderColor; + const auto penColor = _impl->theme.borderColor; const auto penWidth = _impl->theme.borderWidth; const auto p1 = QPoint{ rect.x() + 1 + penWidth, rect.y() + rect.x() }; const auto p2 = QPoint{ rect.x() + 1 + penWidth, rect.y() + rect.height() }; @@ -3329,8 +3315,17 @@ QRect QlementineStyle::subControlRect( const auto frameY = comboBoxOpt->rect.y() + (comboBoxOpt->rect.height() - frameH) / 2; return QRect{ frameX, frameY, frameW, frameH }; } break; - case SC_ComboBoxListBoxPopup: - return opt->rect; + case SC_ComboBoxListBoxPopup: { + const auto contentMarginH = pixelMetric(PM_MenuHMargin); + const auto contentMarginV = pixelMetric(PM_MenuVMargin); + const auto shadowWidth = _impl->theme.spacing; + const auto borderWidth = _impl->theme.borderWidth; + const auto width = std::max(opt->rect.width(), w->width()); + const auto height = opt->rect.height() + 12; // Not possible to change height here. + const auto x = opt->rect.x() - shadowWidth - borderWidth - contentMarginH; + const auto y = opt->rect.y() - shadowWidth - borderWidth - contentMarginV / 2; // TODO remove hardcoded + return { x, y, width, height }; + } break; default: break; } @@ -4101,9 +4096,11 @@ int QlementineStyle::pixelMetric(PixelMetric m, const QStyleOption* opt, const Q // Scroller is the part where the user can click to scroll the menu when it is too big. return _impl->theme.controlHeightSmall; case PM_MenuHMargin: - case PM_MenuVMargin: + case PM_MenuVMargin: { // Keep some space between the items and the frame. - return _impl->theme.spacing; + const auto borderW = qobject_cast(w) ? 1 : 0; + return _impl->theme.spacing / 2 + borderW; + } case PM_MenuPanelWidth: // Keep some space for drop shadow. return _impl->theme.spacing; @@ -4384,17 +4381,13 @@ int QlementineStyle::styleHint(StyleHint sh, const QStyleOption* opt, const QWid case SH_ComboBox_ListMouseTracking: return true; case SH_ComboBox_Popup: - // This changes the way the dropdown popup behaves. - // A different QItemDelegate will be used to size/draw the items. - // - true: not animated, uses QComboBoxMenuDelegate, that calls QStyle::drawControl(CE_MenuItem) - // - false: animated, uses QComboBoxDelegate, that just calls QItemDelegate::sizeHint()/paint() - return _impl->useMenuForComboBoxPopup; + return true; case SH_ComboBox_LayoutDirection: break; case SH_ComboBox_PopupFrameStyle: return QFrame::StyledPanel | QFrame::Plain; - case SH_ComboBox_UseNativePopup: // Only on MacOS. - return true; + case SH_ComboBox_UseNativePopup: + return false; case SH_ComboBox_AllowWheelScrolling: return false; @@ -4840,25 +4833,26 @@ void QlementineStyle::polish(QWidget* w) { } // Try to remove the background... - if (auto* itemView = qobject_cast(w)) { - auto* parent = itemView->parentWidget(); - auto isComboBoxPopupContainer = parent && parent->inherits("QComboBoxPrivateContainer"); + if (auto* itemView = qobject_cast(w)) { + auto* popup = itemView->parentWidget(); + auto isComboBoxPopupContainer = popup && popup->inherits("QComboBoxPrivateContainer"); if (isComboBoxPopupContainer) { - itemView->setBackgroundRole(QPalette::NoRole); - itemView->viewport()->setBackgroundRole(QPalette::NoRole); - parent->setBackgroundRole(QPalette::NoRole); - parent->setAutoFillBackground(false); - parent->setAttribute(Qt::WA_TranslucentBackground, true); - parent->setAttribute(Qt::WA_OpaquePaintEvent, false); - parent->setAttribute(Qt::WA_NoSystemBackground, true); - itemView->installEventFilter(new ComboboxItemViewFilter(itemView)); - if (auto* scrollArea = parent->findChild()) { - scrollArea->setBackgroundRole(QPalette::NoRole); - scrollArea->setAutoFillBackground(false); - scrollArea->setAttribute(Qt::WA_TranslucentBackground, true); - scrollArea->setAttribute(Qt::WA_OpaquePaintEvent, false); - scrollArea->setAttribute(Qt::WA_NoSystemBackground, true); - } + popup->setAttribute(Qt::WA_TranslucentBackground, true); + popup->setAttribute(Qt::WA_OpaquePaintEvent, false); + popup->setAttribute(Qt::WA_NoSystemBackground, true); + popup->setWindowFlag(Qt::FramelessWindowHint, true); + popup->setWindowFlag(Qt::NoDropShadowWindowHint, true); + popup->setProperty("_q_windowsDropShadow", false); + + // Same shadow as QMenu. + const auto shadowWidth = _impl->theme.spacing; + const auto borderWidth = _impl->theme.borderWidth; + const auto margin = shadowWidth + borderWidth; + popup->layout()->setContentsMargins(margin, margin, margin, margin); + + itemView->viewport()->setAutoFillBackground(false); + auto* comboBox = findFirstParentOfType(itemView); + itemView->installEventFilter(new ComboboxItemViewFilter(comboBox, itemView)); } } diff --git a/sandbox/src/SandboxWindow.cpp b/sandbox/src/SandboxWindow.cpp index b5c87a3..efad4a1 100644 --- a/sandbox/src/SandboxWindow.cpp +++ b/sandbox/src/SandboxWindow.cpp @@ -604,7 +604,7 @@ struct SandboxWindow::Impl { // Non-editable { auto* combobox = new QComboBox(windowContent); - combobox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + combobox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); combobox->setFocusPolicy(Qt::StrongFocus); for (auto i = 0; i < 4; ++i) { @@ -640,7 +640,6 @@ struct SandboxWindow::Impl { windowContentLayout->addWidget(listView); // Context menu. - qDebug() << listView->contextMenuPolicy(); listView->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); QObject::connect(listView, &QListView::customContextMenuRequested, listView, [listView](const QPoint& pos) { if (const auto item = listView->itemAt(pos)) { @@ -748,7 +747,7 @@ struct SandboxWindow::Impl { auto* menuBar = owner.menuBar(); // NB: it looks like MacOS' native menu bar has an issue with QIcon, so we have to force // it to generate icons for High-DPI screens. - const auto icon = makeIconFromSvg(":/refresh.svg", owner.iconSize()); + const auto icon = makeQIcon(Icons16::Action_Save); for (auto i = 0; i < 5; ++i) { auto* menu = menuBar->addMenu(QString("Menu &%1").arg(i));