#include "playercombobox.h" #include "../managers/thememanager.h" #include #include #include #include #include #include #include static constexpr int kItemHeight = 36; static constexpr int kIconSize = 20; static constexpr int kBadgeSize = 14; static constexpr int kDeleteSize = 14; static constexpr int kPadding = 10; static constexpr int kSpacing = 6; PlayerItemDelegate::PlayerItemDelegate(QObject *parent) : QStyledItemDelegate(parent) {} QSize PlayerItemDelegate::sizeHint(const QStyleOptionViewItem & , const QModelIndex & ) const { return QSize(200, kItemHeight); } QRect PlayerItemDelegate::deleteButtonRect(const QStyleOptionViewItem &option) const { int x = option.rect.right() - kPadding - kDeleteSize; int y = option.rect.top() + (option.rect.height() - kDeleteSize) / 2; return QRect(x, y, kDeleteSize, kDeleteSize); } int PlayerItemDelegate::hitDeleteButton(const QStyleOptionViewItem &option, const QPoint &pos) const { QRect btnRect = deleteButtonRect(option); btnRect.adjust(-4, -4, 4, 4); return btnRect.contains(pos) ? 0 : -1; } void PlayerItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { painter->save(); painter->setRenderHint(QPainter::Antialiasing, true); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); bool isDark = ThemeManager::instance()->isDarkMode(); QString source = index.data(PlayerComboBox::SourceRole).toString(); QString path = index.data(Qt::UserRole).toString(); QString text = index.data(Qt::DisplayRole).toString(); bool isCustomEntry = (source == PlayerComboBox::SourceCustom); if (option.state & QStyle::State_Selected) { QColor sel = isDark ? QColor(59, 130, 246, 50) : QColor(59, 130, 246, 25); painter->fillRect(option.rect, sel); } else if (option.state & QStyle::State_MouseOver) { QColor hov = isDark ? QColor(255, 255, 255, 18) : QColor(0, 0, 0, 12); painter->fillRect(option.rect, hov); } int x = option.rect.left() + kPadding; int centerY = option.rect.center().y(); if (!isCustomEntry && !path.isEmpty()) { QFileInfo fi(path); QIcon exeIcon = m_iconProvider.icon(fi); if (!exeIcon.isNull()) { QRect iconRect(x, centerY - kIconSize / 2, kIconSize, kIconSize); exeIcon.paint(painter, iconRect); } x += kIconSize + kSpacing; } int rightX = option.rect.right() - kPadding; if (!isCustomEntry) { QRect delRect(rightX - kDeleteSize, centerY - kDeleteSize / 2, kDeleteSize, kDeleteSize); QIcon closeIcon = ThemeManager::getAdaptiveIcon( ":/svg/dark/close.svg", (option.state & QStyle::State_MouseOver) ? (isDark ? QColor("#EF4444") : QColor("#DC2626")) : (isDark ? QColor("#94A3B8") : QColor("#64748B"))); closeIcon.paint(painter, delRect); rightX -= kDeleteSize + kSpacing; } if (!isCustomEntry) { QString badgeSvg = (source == PlayerComboBox::SourceAuto) ? ":/svg/dark/search.svg" : ":/svg/dark/external-player.svg"; QRect badgeRect(rightX - kBadgeSize, centerY - kBadgeSize / 2, kBadgeSize, kBadgeSize); QIcon badgeIcon = ThemeManager::getAdaptiveIcon( badgeSvg, isDark ? QColor("#64748B") : QColor("#94A3B8")); badgeIcon.paint(painter, badgeRect); rightX -= kBadgeSize + kSpacing; } QRect textRect(x, option.rect.top(), rightX - x - kSpacing, option.rect.height()); QColor textColor = isDark ? QColor("#E2E8F0") : QColor("#1E293B"); if (option.state & QStyle::State_Selected) { textColor = isDark ? QColor("#93C5FD") : QColor("#2563EB"); } painter->setPen(textColor); QFont f = option.font; f.setPointSizeF(9.5); painter->setFont(f); painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, painter->fontMetrics().elidedText(text, Qt::ElideMiddle, textRect.width())); painter->restore(); } PlayerComboBox::PlayerComboBox(QWidget *parent) : ModernComboBox(parent) { m_delegate = new PlayerItemDelegate(this); setItemDelegate(m_delegate); view()->installEventFilter(this); view()->viewport()->installEventFilter(this); } void PlayerComboBox::addPlayer(const QString &displayName, const QString &path, const QString &source) { int idx = count(); for (int i = 0; i < count(); ++i) { if (itemData(i, SourceRole).toString() == SourceCustom) { idx = i; break; } } insertItem(idx, displayName, path); setItemData(idx, source, SourceRole); updateGeometry(); } void PlayerComboBox::removePlayer(int index) { if (index < 0 || index >= count()) return; QString source = itemData(index, SourceRole).toString(); if (source == SourceCustom) return; QString path = itemData(index).toString(); bool wasCurrent = (currentIndex() == index); blockSignals(true); removeItem(index); blockSignals(false); updateGeometry(); if (wasCurrent && count() > 0) { int newIdx = qMin(index, count() - 1); if (itemData(newIdx, SourceRole).toString() == SourceCustom && newIdx > 0) { newIdx--; } setCurrentIndex(newIdx); } emit playerRemoved(index, path); } void PlayerComboBox::removeAutoDetected() { for (int i = count() - 1; i >= 0; --i) { if (itemData(i, SourceRole).toString() == SourceAuto) { removeItem(i); } } updateGeometry(); } QString PlayerComboBox::currentPlayerPath() const { return currentData(Qt::UserRole).toString(); } void PlayerComboBox::paintEvent(QPaintEvent *) { QStylePainter painter(this); QStyleOptionComboBox opt; initStyleOption(&opt); QString text = opt.currentText; opt.currentText = ""; painter.drawComplexControl(QStyle::CC_ComboBox, opt); const int arrowWidth = 32; const int iconSz = 18; const int iconPad = 6; QRect availRect = rect().adjusted(0, 0, -arrowWidth, 0); QString path = currentData(Qt::UserRole).toString(); QString source = currentData(SourceRole).toString(); int textLeft = availRect.left(); if (source != SourceCustom && !path.isEmpty()) { QFileInfo fi(path); QIcon exeIcon = m_iconProvider.icon(fi); if (!exeIcon.isNull()) { int iconY = availRect.center().y() - iconSz / 2; int iconX = availRect.left() + iconPad; QRect iconRect(iconX, iconY, iconSz, iconSz); exeIcon.paint(&painter, iconRect); textLeft = iconX + iconSz + iconPad; } } QRect textRect(textLeft, availRect.top(), availRect.right() - textLeft, availRect.height()); painter.setPen(opt.palette.color(QPalette::ButtonText)); painter.setFont(this->font()); painter.drawText(textRect, Qt::AlignCenter | Qt::TextSingleLine, text); } bool PlayerComboBox::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::MouseButtonRelease) { auto *me = static_cast(event); QAbstractItemView *v = view(); QModelIndex idx = v->indexAt(me->pos()); if (idx.isValid()) { QString source = idx.data(SourceRole).toString(); if (source != SourceCustom) { QStyleOptionViewItem opt; opt.rect = v->visualRect(idx); opt.state = QStyle::State_Enabled; if (m_delegate->hitDeleteButton(opt, me->pos()) >= 0) { removePlayer(idx.row()); event->accept(); return true; } } } } return ModernComboBox::eventFilter(obj, event); }