pgLab/pglab/codeeditor/CompletionPopup.cpp

112 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();
}