113 lines
3.5 KiB
C++
113 lines
3.5 KiB
C++
|
|
#include "CompletionPopup.h"
|
||
|
|
#include <QApplication>
|
||
|
|
#include <QPainter>
|
||
|
|
#include <QScreen>
|
||
|
|
#include <QStyledItemDelegate>
|
||
|
|
|
||
|
|
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<EditorCompletionItem> &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<int>(std::min<size_t>(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();
|
||
|
|
}
|