#include "CompletionPopup.h" #include #include #include #include namespace { constexpr int DetailRole = Qt::UserRole + 1; constexpr int AmbiguousRole = Qt::UserRole + 2; class CompletionDelegate : public QStyledItemDelegate { public: using QStyledItemDelegate::QStyledItemDelegate; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); const QString display = opt.text; opt.text.clear(); const QWidget *w = opt.widget; (w ? w->style() : QApplication::style())->drawControl(QStyle::CE_ItemViewItem, &opt, painter, w); const bool selected = opt.state & QStyle::State_Selected; const QPalette &pal = opt.palette; const QString detail = index.data(DetailRole).toString(); const bool ambiguous = index.data(AmbiguousRole).toBool(); painter->save(); painter->setPen(selected ? pal.highlightedText().color() : pal.text().color()); QRect r = opt.rect.adjusted(4, 0, -4, 0); painter->drawText(r, Qt::AlignVCenter | Qt::AlignLeft, display); if (!detail.isEmpty()) { const int x = painter->fontMetrics().horizontalAdvance(display) + 12; QColor dim = selected ? pal.highlightedText().color() : pal.color(QPalette::Disabled, QPalette::Text); painter->setPen(dim); if (ambiguous) { QFont f = painter->font(); f.setBold(true); painter->setFont(f); } painter->drawText(r.adjusted(x, 0, 0, 0), Qt::AlignVCenter | Qt::AlignLeft, QStringLiteral("\u2014 ") + detail); } painter->restore(); } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override { QSize s = QStyledItemDelegate::sizeHint(option, index); const QFontMetrics fm(option.font); s.setWidth(fm.horizontalAdvance(index.data(Qt::DisplayRole).toString()) + fm.horizontalAdvance(index.data(DetailRole).toString()) + 40); return s; } }; } // namespace CompletionPopup::CompletionPopup(QWidget *parent) : QListWidget(parent) { setWindowFlags(Qt::ToolTip); setAttribute(Qt::WA_ShowWithoutActivating); setFocusPolicy(Qt::NoFocus); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setUniformItemSizes(true); setItemDelegate(new CompletionDelegate(this)); connect(this, &QListWidget::itemClicked, this, [this](QListWidgetItem *item) { emit itemAccepted(row(item)); }); } void CompletionPopup::setItems(const std::vector &items) { clear(); int width = 150; const QFontMetrics fm(font()); for (const auto &i : items) { auto *item = new QListWidgetItem(i.display, this); item->setData(DetailRole, i.detail); item->setData(AmbiguousRole, i.ambiguous); width = std::max(width, fm.horizontalAdvance(i.display) + fm.horizontalAdvance(i.detail) + 50); } if (!items.empty()) setCurrentRow(0); const int rows = static_cast(std::min(items.size(), 10)); const int rowHeight = fm.height() + 6; resize(std::min(width, 600), rows * rowHeight + 2 * frameWidth() + 2); } void CompletionPopup::moveSelection(int delta) { const int n = count(); if (n == 0) return; int r = QListWidget::currentRow() + delta; r = std::clamp(r, 0, n - 1); setCurrentRow(r); } void CompletionPopup::showAt(const QPoint &globalPos) { QPoint p = globalPos; if (auto *screen = QApplication::screenAt(globalPos)) { const QRect avail = screen->availableGeometry(); if (p.x() + width() > avail.right()) p.setX(std::max(avail.left(), avail.right() - width())); } move(p); show(); }