ProjectExplorer: Use Widget as project delegate in ProjectWelcomePage

Less manual pixel calculation, more Layouting.

Change-Id: I33e0ce82a83d7561c73e9b6a92e349354d8f758b
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Alessandro Portale
2024-11-20 14:31:18 +01:00
parent c3af57a29b
commit 7d6c3beb50

View File

@@ -20,6 +20,7 @@
#include <coreplugin/welcomepagehelper.h> #include <coreplugin/welcomepagehelper.h>
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/elidinglabel.h>
#include <utils/fileutils.h> #include <utils/fileutils.h>
#include <utils/icon.h> #include <utils/icon.h>
#include <utils/layoutbuilder.h> #include <utils/layoutbuilder.h>
@@ -630,104 +631,128 @@ private:
mutable QRect m_activeActionRects[3]; mutable QRect m_activeActionRects[3];
}; };
class ProjectItemWidget final : public QWidget
{
public:
ProjectItemWidget(QWidget *parent = nullptr)
: QWidget(parent)
{
m_shortcut = new QLabel;
applyTf(m_shortcut, shortcutNumberTF, false);
const int shortcutWidth = HPaddingXs + shortcutNumberWidth + HGapXs;
m_shortcut->setMinimumWidth(shortcutWidth);
const QPixmap icon = pixmap("project", Theme::Token_Text_Muted);
const QSize iconS = icon.deviceIndependentSize().toSize();
auto iconLabel = new QLabel;
iconLabel->setFixedSize(iconS);
iconLabel->setPixmap(icon);
m_projectName = new ElidingLabel;
applyTf(m_projectName, projectNameTF);
m_projectName->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
m_projectPath = new ElidingLabel;
applyTf(m_projectPath, projectPathTF);
m_projectPath->setElideMode(Qt::ElideMiddle);
m_projectPath->setSizePolicy(m_projectName->sizePolicy());
using namespace Layouting;
Row {
m_shortcut,
iconLabel,
Space(HGapXs),
Column {
m_projectName,
m_projectPath,
customMargins(0, VPaddingXs, 0, VPaddingXs),
spacing(VGapXs),
},
customMargins(0, 0, HPaddingXs, 0),
spacing(0),
}.attachTo(this);
setAutoFillBackground(false);
}
void setData(const QModelIndex &index)
{
const int row = index.row() + 1;
m_shortcut->setText(row <= 9 ? QString::number(row) : QString());
const QString projectName = index.data(Qt::DisplayRole).toString();
m_projectName->setText(projectName);
const FilePath projectPath =
FilePath::fromVariant(index.data(ProjectModel::FilePathRole));
const QString displayPath =
projectPath.osType() == OsTypeWindows ? projectPath.displayName()
: projectPath.withTildeHomePath();
m_projectPath->setText(displayPath);
}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
{
const bool hovered = option.widget->isActiveWindow()
&& option.state & QStyle::State_MouseOver;
const QRect bgRGlobal = option.rect.adjusted(0, 0, -s(HPaddingXs), -itemSpacing());
const QRect bgR = bgRGlobal.translated(-option.rect.topLeft());
QFont projectNameFont = m_projectName->font();
projectNameFont.setUnderline(hovered);
m_projectName->setFont(projectNameFont);
setFixedWidth(bgR.width());
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
painter->translate(bgRGlobal.topLeft());
drawBackgroundRect(painter, bgR, hovered);
render(painter, bgR.topLeft(), {}, QWidget::DrawChildren);
painter->restore();
}
private:
QLabel *m_shortcut;
ElidingLabel *m_projectName;
ElidingLabel *m_projectPath;
};
class ProjectDelegate : public BaseDelegate class ProjectDelegate : public BaseDelegate
{ {
public:
explicit ProjectDelegate()
: BaseDelegate()
{}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)
const override
{
m_itemWidget.setData(index);
m_itemWidget.paint(painter, option, index);
}
QSize sizeHint([[maybe_unused]] const QStyleOptionViewItem &option,
const QModelIndex &index) const override
{
return {-1, m_itemWidget.minimumSizeHint().height() + itemSpacing()};
}
protected:
QString entryType() override QString entryType() override
{ {
return Tr::tr("project", "Appears in \"Open project <name>\""); return Tr::tr("project", "Appears in \"Open project <name>\"");
} }
int shortcutRole() const override { return ProjectModel::ShortcutRole; }
public: int shortcutRole() const override
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const final
{ {
// visible on with Icon() Extra margin right of project item return ProjectModel::ShortcutRole;
// | |
// +--------+-------+ +------+-----+
// | | | |
//
// +------------+--------+--------+------+---------+-------------+------------+------------+
// | | | | | | (VPaddingXs)| | |
// | | | | | +-------------+ | |
// | | | | | |<projectName>| | |
// | | | | | +-------------+ | |
// |(HPaddingXs)|<number>|(HGapXs)|<icon>|(HGapXxs)| (VGapXs) |(HPaddingXs)|(HPaddingXs)|
// | |(w:6) | | | +-------------+ | |
// | | | | | |<projectPath>| | |
// | | | | | +-------------+ | |
// | | | | | | (VPaddingXs)| | |
// +------------+--------+--------+------+---------+-------------+------------+------------+ --+
// | (VGapL) | +-- Gap between project items
// +---------------------------------------------------------------------------------------+ --+
const bool hovered = option.widget->isActiveWindow()
&& option.state & QStyle::State_MouseOver;
const QRect bgR = option.rect.adjusted(0, 0, -s(HPaddingXs), -itemSpacing());
static const QPixmap icon = pixmap("project", Theme::Token_Text_Muted);
const QSize iconS = icon.deviceIndependentSize().toSize();
const int x = bgR.x();
const int numberX = x + s(HPaddingXs);
const int iconX = numberX + shortcutNumberWidth + s(HGapXs);
const int iconWidth = iconS.width();
const int textX = withIcon() ? iconX + iconWidth + s(HGapXs) : iconX;
const int textWidth = bgR.width() - s(HPaddingXs) - textX;
const int y = bgR.y();
const int iconHeight = iconS.height();
const int iconY = y + (bgR.height() - iconHeight) / 2;
const int projectNameY = y + s(VPaddingXs);
const QRect projectNameR(textX, projectNameY, textWidth, projectNameTF.lineHeight());
const int projectPathY = projectNameY + projectNameR.height() + s(VGapXs);
const QRect projectPathR(textX, projectPathY, textWidth, projectPathTF.lineHeight());
QTC_CHECK(option.rect.bottom() == projectPathR.bottom() + s(VPaddingXs) + itemSpacing());
{
drawBackgroundRect(painter, bgR, hovered);
}
if (idx.row() < 9) {
painter->setPen(shortcutNumberTF.color());
painter->setFont(shortcutNumberTF.font());
const QRect numberR(numberX, y, shortcutNumberWidth, bgR.height());
const QString numberString = QString::number(idx.row() + 1);
painter->drawText(numberR, shortcutNumberTF.drawTextFlags, numberString);
}
if (withIcon()) {
painter->drawPixmap(iconX, iconY, icon);
}
{
painter->setPen(projectNameTF.color());
painter->setFont(projectNameTF.font(hovered));
const QString projectName = idx.data(Qt::DisplayRole).toString();
const QString projectNameElided =
painter->fontMetrics().elidedText(projectName, Qt::ElideRight, textWidth);
painter->drawText(projectNameR, projectNameTF.drawTextFlags, projectNameElided);
}
{
painter->setPen(projectPathTF.color());
painter->setFont(projectPathTF.font());
const FilePath projectPath =
FilePath::fromVariant(idx.data(ProjectModel::FilePathRole));
const QString displayPath =
projectPath.osType() == OsTypeWindows ? projectPath.displayName()
: projectPath.withTildeHomePath();
const QString displayPathElided =
painter->fontMetrics().elidedText(displayPath, Qt::ElideMiddle, textWidth);
painter->drawText(projectPathR, projectPathTF.drawTextFlags, displayPathElided);
}
}
QSize sizeHint([[maybe_unused]] const QStyleOptionViewItem &option,
[[maybe_unused]] const QModelIndex &idx) const override
{
return QSize(-1, itemHeight() + itemSpacing());
} }
bool editorEvent(QEvent *ev, QAbstractItemModel *model, bool editorEvent(QEvent *ev, QAbstractItemModel *model,
const QStyleOptionViewItem &, const QModelIndex &idx) final const QStyleOptionViewItem &, const QModelIndex &idx) final
{ {
if (ev->type() == QEvent::MouseButtonRelease) { if (ev->type() == QEvent::MouseButtonRelease) {
const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(ev); const QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(ev);
@@ -762,16 +787,7 @@ public:
} }
private: private:
static int itemHeight() mutable ProjectItemWidget m_itemWidget;
{
const int height =
s(VPaddingXs)
+ projectNameTF.lineHeight()
+ s(VGapXs)
+ projectPathTF.lineHeight()
+ s(VPaddingXs);
return height;
}
}; };
class TreeView : public QTreeView class TreeView : public QTreeView