chore: baseline usable version
This commit is contained in:
@@ -0,0 +1,372 @@
|
||||
#include "moderncombobox.h"
|
||||
#include <QAbstractItemView>
|
||||
#include <QApplication>
|
||||
#include <QEvent>
|
||||
#include <QFontMetrics>
|
||||
#include <QFrame>
|
||||
#include <QKeyEvent>
|
||||
#include <QListWidget>
|
||||
#include <QPainter>
|
||||
#include <QScrollBar>
|
||||
#include <QShortcut>
|
||||
#include <QStyleOption>
|
||||
#include <QStyleOptionComboBox>
|
||||
#include <QStyleOptionViewItem>
|
||||
#include <QStylePainter>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kArrowWidth = 32;
|
||||
|
||||
|
||||
|
||||
|
||||
constexpr int kModernComboMinItemHeight = 36;
|
||||
|
||||
constexpr auto kPopupEdgeFixMarker = "/* modern-combobox-popup-edge-fix */";
|
||||
constexpr auto kPopupEdgeFixQss = R"(
|
||||
/* modern-combobox-popup-edge-fix */
|
||||
QAbstractItemView QScrollBar:vertical {
|
||||
margin: 0px;
|
||||
}
|
||||
)";
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
void CenterAlignDelegate::paint(QPainter *painter,
|
||||
const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const {
|
||||
QStyleOptionViewItem opt = option;
|
||||
initStyleOption(&opt, index);
|
||||
|
||||
opt.displayAlignment = Qt::AlignCenter;
|
||||
QStyledItemDelegate::paint(painter, opt, index);
|
||||
}
|
||||
|
||||
QSize CenterAlignDelegate::sizeHint(const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const {
|
||||
QSize base = QStyledItemDelegate::sizeHint(option, index);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
base.setHeight(qMax(base.height(), kModernComboMinItemHeight));
|
||||
return base;
|
||||
}
|
||||
|
||||
|
||||
|
||||
ModernComboBox::ModernComboBox(QWidget *parent) : QComboBox(parent) {
|
||||
|
||||
setItemDelegate(new CenterAlignDelegate(this));
|
||||
|
||||
|
||||
setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
|
||||
|
||||
connect(this, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||
[this](int index) {
|
||||
if (m_maxTextWidth > 0 && index >= 0) {
|
||||
setToolTip(itemText(index));
|
||||
} else {
|
||||
setToolTip({});
|
||||
}
|
||||
});
|
||||
|
||||
polishPopupView();
|
||||
}
|
||||
|
||||
void ModernComboBox::setMaxTextWidth(int pixels) {
|
||||
m_maxTextWidth = pixels;
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
QSize ModernComboBox::sizeHint() const {
|
||||
|
||||
QFontMetrics fm(font());
|
||||
int maxW = 60;
|
||||
for (int i = 0; i < count(); ++i) {
|
||||
maxW = qMax(maxW, fm.horizontalAdvance(itemText(i)));
|
||||
}
|
||||
|
||||
if (m_maxTextWidth > 0) {
|
||||
maxW = qMin(maxW, m_maxTextWidth);
|
||||
}
|
||||
|
||||
|
||||
int w = maxW + 16 + 16 + 32;
|
||||
int h = QComboBox::sizeHint().height();
|
||||
return QSize(w, h);
|
||||
}
|
||||
|
||||
QSize ModernComboBox::minimumSizeHint() const { return sizeHint(); }
|
||||
|
||||
void ModernComboBox::showPopup() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if (window() && window()->isFullScreen()) {
|
||||
showEmbeddedPopup();
|
||||
return;
|
||||
}
|
||||
|
||||
polishPopupView();
|
||||
QComboBox::showPopup();
|
||||
|
||||
auto *popupView = view();
|
||||
QWidget *popupContainer = popupView ? popupView->window() : nullptr;
|
||||
if (!popupView || !popupContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
auto *vScrollBar = popupView->verticalScrollBar();
|
||||
if (vScrollBar && vScrollBar->isVisible()) {
|
||||
const int targetWidth = qMax(popupContainer->width(),
|
||||
width() + vScrollBar->sizeHint().width());
|
||||
if (targetWidth > popupContainer->width()) {
|
||||
popupContainer->resize(targetWidth, popupContainer->height());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModernComboBox::hidePopup() {
|
||||
closeEmbeddedPopup();
|
||||
QComboBox::hidePopup();
|
||||
}
|
||||
|
||||
bool ModernComboBox::eventFilter(QObject *watched, QEvent *event) {
|
||||
if (!event) {
|
||||
return QComboBox::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
|
||||
if (m_embeddedPopup && watched == m_embeddedPopup.data()) {
|
||||
|
||||
if (event->type() == QEvent::MouseButtonPress ||
|
||||
event->type() == QEvent::MouseButtonDblClick) {
|
||||
closeEmbeddedPopup();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return QComboBox::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ModernComboBox::showEmbeddedPopup() {
|
||||
closeEmbeddedPopup();
|
||||
|
||||
QWidget *host = window();
|
||||
if (!host || count() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool flushRightScrollBar =
|
||||
property("flush-right-scrollbar").toBool();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
auto *overlay = new QWidget(host);
|
||||
overlay->setAttribute(Qt::WA_DeleteOnClose);
|
||||
overlay->setObjectName(QStringLiteral("modernComboEmbeddedOverlay"));
|
||||
overlay->setGeometry(host->rect());
|
||||
overlay->installEventFilter(this);
|
||||
|
||||
|
||||
auto *listFrame = new QFrame(overlay);
|
||||
listFrame->setObjectName(QStringLiteral("modernComboEmbeddedFrame"));
|
||||
listFrame->setFrameStyle(QFrame::NoFrame);
|
||||
listFrame->setProperty("flush-right-scrollbar", flushRightScrollBar);
|
||||
|
||||
auto *frameLayout = new QVBoxLayout(listFrame);
|
||||
frameLayout->setContentsMargins(0, 0, 0, 0);
|
||||
frameLayout->setSpacing(0);
|
||||
|
||||
auto *listWidget = new QListWidget(listFrame);
|
||||
listWidget->setObjectName(QStringLiteral("modernComboEmbeddedList"));
|
||||
listWidget->setProperty("flush-right-scrollbar", flushRightScrollBar);
|
||||
listWidget->setItemDelegate(new CenterAlignDelegate(listWidget));
|
||||
listWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||
listWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
listWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
listWidget->setFocusPolicy(Qt::StrongFocus);
|
||||
listWidget->setFrameStyle(QFrame::NoFrame);
|
||||
listWidget->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
|
||||
for (int i = 0; i < count(); ++i) {
|
||||
auto *item = new QListWidgetItem(itemText(i));
|
||||
item->setData(Qt::UserRole, itemData(i));
|
||||
item->setTextAlignment(Qt::AlignCenter);
|
||||
listWidget->addItem(item);
|
||||
}
|
||||
|
||||
|
||||
if (currentIndex() >= 0 && currentIndex() < listWidget->count()) {
|
||||
listWidget->setCurrentRow(currentIndex());
|
||||
}
|
||||
|
||||
frameLayout->addWidget(listWidget);
|
||||
|
||||
|
||||
constexpr int kMaxVisibleItems = 12;
|
||||
constexpr int kItemPadding = 12;
|
||||
const QFontMetrics fm(listWidget->font());
|
||||
|
||||
|
||||
|
||||
const int itemH = qMax(fm.height() + kItemPadding, kModernComboMinItemHeight);
|
||||
const int visibleItems = qMin(count(), kMaxVisibleItems);
|
||||
int popupH = itemH * visibleItems + 6;
|
||||
int popupW = qMax(width(), 240);
|
||||
|
||||
|
||||
popupH = qMin(popupH, host->height() / 2);
|
||||
|
||||
|
||||
const QPoint comboBottomLeft = mapTo(host, QPoint(0, height()));
|
||||
const QPoint comboTopLeft = mapTo(host, QPoint(0, 0));
|
||||
|
||||
int popupX = comboBottomLeft.x();
|
||||
int popupY = comboBottomLeft.y() + 4;
|
||||
|
||||
|
||||
if (popupY + popupH > host->height() - 10) {
|
||||
popupY = comboTopLeft.y() - popupH - 4;
|
||||
}
|
||||
|
||||
|
||||
if (popupX + popupW > host->width() - 10) {
|
||||
popupX = host->width() - popupW - 10;
|
||||
}
|
||||
|
||||
popupX = qMax(10, popupX);
|
||||
popupY = qMax(10, popupY);
|
||||
|
||||
listFrame->setGeometry(popupX, popupY, popupW, popupH);
|
||||
|
||||
|
||||
overlay->show();
|
||||
overlay->raise();
|
||||
listFrame->show();
|
||||
listWidget->setFocus();
|
||||
|
||||
|
||||
if (currentIndex() >= 0 && currentIndex() < listWidget->count()) {
|
||||
listWidget->scrollToItem(listWidget->item(currentIndex()),
|
||||
QAbstractItemView::PositionAtCenter);
|
||||
}
|
||||
|
||||
m_embeddedPopup = overlay;
|
||||
|
||||
|
||||
|
||||
connect(listWidget, &QListWidget::itemClicked, this,
|
||||
[this, listWidget](QListWidgetItem *) {
|
||||
const int row = listWidget->currentRow();
|
||||
if (row >= 0 && row < count()) {
|
||||
setCurrentIndex(row);
|
||||
}
|
||||
closeEmbeddedPopup();
|
||||
});
|
||||
|
||||
|
||||
auto *escShortcut =
|
||||
new QShortcut(QKeySequence(Qt::Key_Escape), overlay,
|
||||
nullptr, nullptr, Qt::WidgetWithChildrenShortcut);
|
||||
connect(escShortcut, &QShortcut::activated, this,
|
||||
[this]() { closeEmbeddedPopup(); });
|
||||
|
||||
|
||||
auto *enterShortcut =
|
||||
new QShortcut(QKeySequence(Qt::Key_Return), listWidget,
|
||||
nullptr, nullptr, Qt::WidgetWithChildrenShortcut);
|
||||
connect(enterShortcut, &QShortcut::activated, this,
|
||||
[this, listWidget]() {
|
||||
const int row = listWidget->currentRow();
|
||||
if (row >= 0 && row < count()) {
|
||||
setCurrentIndex(row);
|
||||
}
|
||||
closeEmbeddedPopup();
|
||||
});
|
||||
}
|
||||
|
||||
void ModernComboBox::closeEmbeddedPopup() {
|
||||
if (m_embeddedPopup) {
|
||||
m_embeddedPopup->removeEventFilter(this);
|
||||
m_embeddedPopup->close();
|
||||
m_embeddedPopup = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ModernComboBox::polishPopupView() {
|
||||
auto *popupView = view();
|
||||
if (!popupView) {
|
||||
return;
|
||||
}
|
||||
|
||||
popupView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||
popupView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
|
||||
QString popupStyleSheet = popupView->styleSheet();
|
||||
if (!popupStyleSheet.contains(QLatin1String(kPopupEdgeFixMarker))) {
|
||||
if (!popupStyleSheet.isEmpty() && !popupStyleSheet.endsWith(QLatin1Char('\n'))) {
|
||||
popupStyleSheet += QLatin1Char('\n');
|
||||
}
|
||||
popupStyleSheet += QLatin1String(kPopupEdgeFixQss);
|
||||
popupView->setStyleSheet(popupStyleSheet);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ModernComboBox::paintEvent(QPaintEvent *) {
|
||||
QStylePainter painter(this);
|
||||
|
||||
QStyleOptionComboBox opt;
|
||||
initStyleOption(&opt);
|
||||
|
||||
|
||||
QString text = opt.currentText;
|
||||
opt.currentText = "";
|
||||
|
||||
|
||||
painter.drawComplexControl(QStyle::CC_ComboBox, opt);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
QRect textRect = rect().adjusted(0, 0, -kArrowWidth, 0);
|
||||
|
||||
|
||||
if (m_maxTextWidth > 0) {
|
||||
QFontMetrics fm(this->font());
|
||||
text = fm.elidedText(text, Qt::ElideRight, textRect.width() - 8);
|
||||
}
|
||||
|
||||
|
||||
painter.setPen(opt.palette.color(QPalette::ButtonText));
|
||||
painter.setFont(this->font());
|
||||
painter.drawText(textRect, Qt::AlignCenter | Qt::TextSingleLine, text);
|
||||
}
|
||||
Reference in New Issue
Block a user