Skip to content

Commit

Permalink
fix #30: QComboBox popup frame/shadow/border
Browse files Browse the repository at this point in the history
  • Loading branch information
oclero committed Nov 3, 2024
1 parent 3225462 commit e7956e4
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 68 deletions.
6 changes: 0 additions & 6 deletions lib/include/oclero/qlementine/style/QlementineStyle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
14 changes: 9 additions & 5 deletions lib/src/style/Delegates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand All @@ -142,7 +146,7 @@ QSize ComboBoxDelegate::sizeHint(const QStyleOptionViewItem& opt, const QModelIn
iconVariant.isValid() && iconVariant.userType() == QMetaType::QIcon ? iconVariant.value<QIcon>() : 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 };
}
Expand Down
42 changes: 35 additions & 7 deletions lib/src/style/EventFilters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <oclero/qlementine/utils/ImageUtils.hpp>
#include <oclero/qlementine/utils/PrimitiveUtils.hpp>
#include <oclero/qlementine/utils/MenuUtils.hpp>
#include <oclero/qlementine/utils/WidgetUtils.hpp>

#include <oclero/qlementine/icons/Icons16.hpp>

Expand All @@ -20,6 +21,9 @@
#include <QStylePainter>
#include <QPointer>
#include <QSpinBox>
#include <QComboBox>
#include <QListView>
#include <QLayout>

namespace oclero::qlementine {
LineEditButtonEventFilter::LineEditButtonEventFilter(
Expand Down Expand Up @@ -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<QComboBox>(_view);
const auto* qlementineStyle = qobject_cast<QlementineStyle*>(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) {}
Expand Down Expand Up @@ -528,5 +557,4 @@ bool LineEditMenuEventFilter::eventFilter(QObject*, QEvent* evt) {

return false;
}

} // namespace oclero::qlementine
9 changes: 6 additions & 3 deletions lib/src/style/EventFilters.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

#include <QTabBar>
#include <QToolButton>
#include <QAbstractItemView>
#include <QListView>
#include <QComboBox>
#include <QCommandLinkButton>

namespace oclero::qlementine {
Expand Down Expand Up @@ -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
Expand Down
82 changes: 38 additions & 44 deletions lib/src/style/QlementineStyle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,6 @@ struct QlementineStyleImpl {
WidgetAnimationManager animations;
std::unordered_map<QStyle::StandardPixmap, QIcon> standardIconCache;
std::unordered_map<QlementineStyle::StandardPixmapExt, QIcon> standardIconExtCache;
bool useMenuForComboBoxPopup{ false };
AutoIconColor autoIconColor{ AutoIconColor::None };
};

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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() };
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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<const QMenu*>(w) ? 1 : 0;
return _impl->theme.spacing / 2 + borderW;
}
case PM_MenuPanelWidth:
// Keep some space for drop shadow.
return _impl->theme.spacing;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -4840,25 +4833,26 @@ void QlementineStyle::polish(QWidget* w) {
}

// Try to remove the background...
if (auto* itemView = qobject_cast<QAbstractItemView*>(w)) {
auto* parent = itemView->parentWidget();
auto isComboBoxPopupContainer = parent && parent->inherits("QComboBoxPrivateContainer");
if (auto* itemView = qobject_cast<QListView*>(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<QAbstractScrollArea*>()) {
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<QComboBox>(itemView);
itemView->installEventFilter(new ComboboxItemViewFilter(comboBox, itemView));
}
}

Expand Down
5 changes: 2 additions & 3 deletions sandbox/src/SandboxWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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));
Expand Down

0 comments on commit e7956e4

Please sign in to comment.