forked from qt-creator/qt-creator
Core: Base GridView on QListView (instead of on QTableView)
Use QListView with its very capable icon view mode instead of QTableView in order to layout items in a grid. This removes the need for the GridProxyModel. It also leaves the previously self-implemented calculation of columns count to QListView. Only the ProductGridView still needs to calculate that. Change-Id: If6e7f033fc78883930794c1e261aea396ae25190 Reviewed-by: Christian Stenger <christian.stenger@qt.io> Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
@@ -78,18 +78,15 @@ SearchBox::SearchBox(QWidget *parent)
|
||||
}
|
||||
|
||||
GridView::GridView(QWidget *parent)
|
||||
: QTableView(parent)
|
||||
: QListView(parent)
|
||||
{
|
||||
setVerticalScrollMode(ScrollPerPixel);
|
||||
horizontalHeader()->hide();
|
||||
horizontalHeader()->setDefaultSectionSize(GridProxyModel::GridItemWidth);
|
||||
verticalHeader()->hide();
|
||||
verticalHeader()->setDefaultSectionSize(GridProxyModel::GridItemHeight);
|
||||
setResizeMode(QListView::Adjust);
|
||||
setMouseTracking(true); // To enable hover.
|
||||
setSelectionMode(QAbstractItemView::NoSelection);
|
||||
setFrameShape(QFrame::NoFrame);
|
||||
setGridStyle(Qt::NoPen);
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setViewMode(IconMode);
|
||||
setUniformItemSizes(true);
|
||||
|
||||
QPalette pal;
|
||||
pal.setColor(QPalette::Base, themeColor(Theme::Welcome_BackgroundColor));
|
||||
@@ -102,129 +99,6 @@ void GridView::leaveEvent(QEvent *)
|
||||
viewportEvent(&hev); // Seemingly needed to kill the hover paint.
|
||||
}
|
||||
|
||||
void GridProxyModel::setSourceModel(QAbstractItemModel *newModel)
|
||||
{
|
||||
if (m_sourceModel == newModel)
|
||||
return;
|
||||
if (m_sourceModel)
|
||||
disconnect(m_sourceModel, nullptr, this, nullptr);
|
||||
m_sourceModel = newModel;
|
||||
if (newModel) {
|
||||
connect(newModel, &QAbstractItemModel::layoutAboutToBeChanged, this, [this] {
|
||||
emit layoutAboutToBeChanged();
|
||||
});
|
||||
connect(newModel, &QAbstractItemModel::layoutChanged, this, [this] {
|
||||
emit layoutChanged();
|
||||
});
|
||||
connect(newModel, &QAbstractItemModel::modelAboutToBeReset, this, [this] {
|
||||
beginResetModel();
|
||||
});
|
||||
connect(newModel, &QAbstractItemModel::modelReset, this, [this] { endResetModel(); });
|
||||
connect(newModel, &QAbstractItemModel::rowsAboutToBeInserted, this, [this] {
|
||||
beginResetModel();
|
||||
});
|
||||
connect(newModel, &QAbstractItemModel::rowsInserted, this, [this] { endResetModel(); });
|
||||
connect(newModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, [this] {
|
||||
beginResetModel();
|
||||
});
|
||||
connect(newModel, &QAbstractItemModel::rowsRemoved, this, [this] { endResetModel(); });
|
||||
connect(newModel,
|
||||
&QAbstractItemModel::dataChanged,
|
||||
this,
|
||||
[this] (const QModelIndex &topLeft,
|
||||
const QModelIndex &bottomRight,
|
||||
const QVector<int> &roles) {
|
||||
emit QAbstractItemModel::dataChanged(mapFromSource(topLeft),
|
||||
mapFromSource(bottomRight), roles);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QAbstractItemModel *GridProxyModel::sourceModel() const
|
||||
{
|
||||
return m_sourceModel;
|
||||
}
|
||||
|
||||
QVariant GridProxyModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
const OptModelIndex sourceIndex = mapToSource(index);
|
||||
if (sourceIndex)
|
||||
return sourceModel()->data(*sourceIndex, role);
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
Qt::ItemFlags GridProxyModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
const OptModelIndex sourceIndex = mapToSource(index);
|
||||
if (sourceIndex)
|
||||
return sourceModel()->flags(*sourceIndex);
|
||||
return Qt::ItemFlags();
|
||||
}
|
||||
|
||||
bool GridProxyModel::hasChildren(const QModelIndex &parent) const
|
||||
{
|
||||
const OptModelIndex sourceParent = mapToSource(parent);
|
||||
if (sourceParent)
|
||||
return sourceModel()->hasChildren(*sourceParent);
|
||||
return false;
|
||||
}
|
||||
|
||||
void GridProxyModel::setColumnCount(int columnCount)
|
||||
{
|
||||
if (columnCount == m_columnCount)
|
||||
return;
|
||||
QTC_ASSERT(columnCount >= 1, columnCount = 1);
|
||||
m_columnCount = columnCount;
|
||||
emit layoutChanged();
|
||||
}
|
||||
|
||||
int GridProxyModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
int rows = sourceModel()->rowCount(QModelIndex());
|
||||
return (rows + m_columnCount - 1) / m_columnCount;
|
||||
}
|
||||
|
||||
int GridProxyModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
return m_columnCount;
|
||||
}
|
||||
|
||||
QModelIndex GridProxyModel::index(int row, int column, const QModelIndex &) const
|
||||
{
|
||||
return createIndex(row, column, nullptr);
|
||||
}
|
||||
|
||||
QModelIndex GridProxyModel::parent(const QModelIndex &) const
|
||||
{
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
// The items at the lower right of the grid might not correspond to source items, if
|
||||
// source's row count is not N*columnCount
|
||||
OptModelIndex GridProxyModel::mapToSource(const QModelIndex &proxyIndex) const
|
||||
{
|
||||
if (!proxyIndex.isValid())
|
||||
return QModelIndex();
|
||||
int sourceRow = proxyIndex.row() * m_columnCount + proxyIndex.column();
|
||||
if (sourceRow < sourceModel()->rowCount())
|
||||
return sourceModel()->index(sourceRow, 0);
|
||||
return OptModelIndex();
|
||||
}
|
||||
|
||||
QModelIndex GridProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
|
||||
{
|
||||
if (!sourceIndex.isValid())
|
||||
return QModelIndex();
|
||||
QTC_CHECK(sourceIndex.column() == 0);
|
||||
int proxyRow = sourceIndex.row() / m_columnCount;
|
||||
int proxyColumn = sourceIndex.row() % m_columnCount;
|
||||
return index(proxyRow, proxyColumn, QModelIndex());
|
||||
}
|
||||
|
||||
const QSize ListModel::defaultImageSize(188, 145);
|
||||
|
||||
ListModel::ListModel(QObject *parent)
|
||||
@@ -474,13 +348,13 @@ void ListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
|
||||
const int d = 10;
|
||||
const int x = rc.x() + d;
|
||||
const int y = rc.y() + d;
|
||||
const int w = rc.width() - 2 * d - GridProxyModel::GridItemGap;
|
||||
const int w = rc.width() - 2 * d;
|
||||
const int h = rc.height() - 2 * d;
|
||||
const bool hovered = option.state & QStyle::State_MouseOver;
|
||||
|
||||
const int tagsBase = GridProxyModel::TagsSeparatorY + 10;
|
||||
const int shiftY = GridProxyModel::TagsSeparatorY - 20;
|
||||
const int nameY = GridProxyModel::TagsSeparatorY - 20;
|
||||
const int tagsBase = TagsSeparatorY + 10;
|
||||
const int shiftY = TagsSeparatorY - 20;
|
||||
const int nameY = TagsSeparatorY - 20;
|
||||
|
||||
const QRect textRect = QRect(x, y + nameY, w, h);
|
||||
|
||||
@@ -576,8 +450,7 @@ void ListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opti
|
||||
|
||||
// Separator line between text and 'Tags:' section
|
||||
painter->setPen(lightColor);
|
||||
painter->drawLine(x, y + GridProxyModel::TagsSeparatorY,
|
||||
x + w, y + GridProxyModel::TagsSeparatorY);
|
||||
painter->drawLine(x, y + TagsSeparatorY, x + w, y + TagsSeparatorY);
|
||||
|
||||
// The 'Tags:' section
|
||||
const int tagsHeight = h - tagsBase;
|
||||
@@ -623,7 +496,7 @@ bool ListItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
||||
|
||||
if (index.isValid()) {
|
||||
const QPoint pos = mev->pos();
|
||||
if (pos.y() > option.rect.y() + GridProxyModel::TagsSeparatorY) {
|
||||
if (pos.y() > option.rect.y() + TagsSeparatorY) {
|
||||
//const QStringList tags = idx.data(Tags).toStringList();
|
||||
for (const auto &it : qAsConst(m_currentTagRects)) {
|
||||
if (it.second.contains(pos))
|
||||
@@ -637,6 +510,11 @@ bool ListItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
||||
return QStyledItemDelegate::editorEvent(event, model, option, index);
|
||||
}
|
||||
|
||||
QSize ListItemDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const
|
||||
{
|
||||
return {GridItemWidth, GridItemHeight};
|
||||
}
|
||||
|
||||
void ListItemDelegate::drawPixmapOverlay(const ListItem *, QPainter *,
|
||||
const QStyleOptionViewItem &, const QRect &) const
|
||||
{
|
||||
|
@@ -34,7 +34,7 @@
|
||||
#include <QPointer>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QTableView>
|
||||
#include <QListView>
|
||||
|
||||
namespace Utils { class FancyLineEdit; }
|
||||
|
||||
@@ -48,7 +48,7 @@ public:
|
||||
Utils::FancyLineEdit *m_lineEdit = nullptr;
|
||||
};
|
||||
|
||||
class CORE_EXPORT GridView : public QTableView
|
||||
class CORE_EXPORT GridView : public QListView
|
||||
{
|
||||
public:
|
||||
explicit GridView(QWidget *parent);
|
||||
@@ -58,33 +58,6 @@ protected:
|
||||
|
||||
using OptModelIndex = Utils::optional<QModelIndex>;
|
||||
|
||||
class CORE_EXPORT GridProxyModel : public QAbstractItemModel
|
||||
{
|
||||
public:
|
||||
void setSourceModel(QAbstractItemModel *newModel);
|
||||
QAbstractItemModel *sourceModel() const;
|
||||
QVariant data(const QModelIndex &index, int role) const final;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const final;
|
||||
bool hasChildren(const QModelIndex &parent) const final;
|
||||
void setColumnCount(int columnCount);
|
||||
int rowCount(const QModelIndex &parent) const final;
|
||||
int columnCount(const QModelIndex &parent) const final;
|
||||
QModelIndex index(int row, int column, const QModelIndex &) const final;
|
||||
QModelIndex parent(const QModelIndex &) const final;
|
||||
|
||||
OptModelIndex mapToSource(const QModelIndex &proxyIndex) const;
|
||||
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
|
||||
|
||||
static constexpr int GridItemWidth = 230;
|
||||
static constexpr int GridItemHeight = 230;
|
||||
static constexpr int GridItemGap = 10;
|
||||
static constexpr int TagsSeparatorY = GridItemHeight - 60;
|
||||
|
||||
private:
|
||||
QAbstractItemModel *m_sourceModel = nullptr;
|
||||
int m_columnCount = 1;
|
||||
};
|
||||
|
||||
class CORE_EXPORT ListItem
|
||||
{
|
||||
public:
|
||||
@@ -148,12 +121,18 @@ public:
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
static constexpr int GridItemWidth = 230;
|
||||
static constexpr int GridItemHeight = 230;
|
||||
static constexpr int GridItemGap = 10;
|
||||
static constexpr int TagsSeparatorY = GridItemHeight - 60;
|
||||
|
||||
signals:
|
||||
void tagClicked(const QString &tag);
|
||||
|
||||
protected:
|
||||
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) override;
|
||||
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
|
||||
|
||||
virtual void drawPixmapOverlay(const ListItem *item, QPainter *painter,
|
||||
const QStyleOptionViewItem &option,
|
||||
|
@@ -66,41 +66,23 @@ class ProductGridView : public Core::GridView
|
||||
{
|
||||
public:
|
||||
ProductGridView(QWidget *parent) : Core::GridView(parent) {}
|
||||
QSize viewportSizeHint() const override
|
||||
{
|
||||
if (!model())
|
||||
return Core::GridView::viewportSizeHint();
|
||||
|
||||
static int gridW = Core::GridProxyModel::GridItemWidth + Core::GridProxyModel::GridItemGap;
|
||||
static int gridH = Core::GridProxyModel::GridItemHeight + Core::GridProxyModel::GridItemGap;
|
||||
return QSize(model()->columnCount() * gridW, model()->rowCount() * gridH);
|
||||
bool hasHeightForWidth() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void setColumnCount(int columnCount)
|
||||
int heightForWidth(int width) const override
|
||||
{
|
||||
if (columnCount < 1)
|
||||
columnCount = 1;
|
||||
|
||||
auto gridProxyModel = static_cast<Core::GridProxyModel *>(model());
|
||||
gridProxyModel->setColumnCount(columnCount);
|
||||
const int columnCount = width / Core::ListItemDelegate::GridItemWidth;
|
||||
const int rowCount = (model()->rowCount() + columnCount - 1) / columnCount;
|
||||
return rowCount * Core::ListItemDelegate::GridItemHeight;
|
||||
}
|
||||
};
|
||||
|
||||
class ProductItemDelegate : public Core::ListItemDelegate
|
||||
{
|
||||
public:
|
||||
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
|
||||
{
|
||||
const Core::ListItem *item = index.data(Core::ListModel::ItemRole).value<Core::ListItem *>();
|
||||
|
||||
// "empty" items (last row of a section)
|
||||
if (!item)
|
||||
return Core::ListItemDelegate::sizeHint(option, index);
|
||||
|
||||
return QSize(Core::GridProxyModel::GridItemWidth + Core::GridProxyModel::GridItemGap,
|
||||
Core::GridProxyModel::GridItemHeight + Core::GridProxyModel::GridItemGap);
|
||||
}
|
||||
|
||||
void clickAction(const Core::ListItem *item) const override
|
||||
{
|
||||
QTC_ASSERT(item, return);
|
||||
@@ -163,9 +145,8 @@ static int priority(const QString &collection)
|
||||
|
||||
SectionedProducts::SectionedProducts(QWidget *parent)
|
||||
: QStackedWidget(parent)
|
||||
, m_allProductsView(new ProductGridView(this))
|
||||
, m_allProductsView(new Core::GridView(this))
|
||||
, m_filteredAllProductsModel(new Core::ListModelFilter(new AllProductsModel(this), this))
|
||||
, m_gridModel(new Core::GridProxyModel)
|
||||
, m_productDelegate(new ProductItemDelegate)
|
||||
{
|
||||
auto area = new QScrollArea(this);
|
||||
@@ -182,9 +163,8 @@ SectionedProducts::SectionedProducts(QWidget *parent)
|
||||
|
||||
addWidget(area);
|
||||
|
||||
m_gridModel->setSourceModel(m_filteredAllProductsModel);
|
||||
m_allProductsView->setItemDelegate(m_productDelegate);
|
||||
m_allProductsView->setModel(m_gridModel);
|
||||
m_allProductsView->setModel(m_filteredAllProductsModel);
|
||||
addWidget(m_allProductsView);
|
||||
|
||||
connect(m_productDelegate, &ProductItemDelegate::tagClicked,
|
||||
@@ -195,7 +175,6 @@ SectionedProducts::~SectionedProducts()
|
||||
{
|
||||
qDeleteAll(m_gridViews);
|
||||
delete m_productDelegate;
|
||||
delete m_gridModel;
|
||||
}
|
||||
|
||||
void SectionedProducts::updateCollections()
|
||||
@@ -328,18 +307,6 @@ void SectionedProducts::queueImageForDownload(const QString &url)
|
||||
fetchNextImage();
|
||||
}
|
||||
|
||||
void SectionedProducts::setColumnCount(int columns)
|
||||
{
|
||||
if (columns < 1)
|
||||
columns = 1;
|
||||
m_columnCount = columns;
|
||||
for (ProductGridView *view : qAsConst(m_gridViews)) {
|
||||
view->setColumnCount(columns);
|
||||
view->setFixedSize(view->viewportSizeHint());
|
||||
}
|
||||
m_allProductsView->setColumnCount(columns);
|
||||
}
|
||||
|
||||
void SectionedProducts::setSearchString(const QString &searchString)
|
||||
{
|
||||
int view = searchString.isEmpty() ? 0 // sectioned view
|
||||
@@ -401,14 +368,11 @@ void SectionedProducts::addNewSection(const Section §ion, const QList<Core::
|
||||
ProductListModel *productModel = new ProductListModel(this);
|
||||
productModel->appendItems(items);
|
||||
auto filteredModel = new Core::ListModelFilter(productModel, this);
|
||||
Core::GridProxyModel *gridModel = new Core::GridProxyModel;
|
||||
gridModel->setSourceModel(filteredModel);
|
||||
auto gridView = new ProductGridView(this);
|
||||
gridView->setItemDelegate(m_productDelegate);
|
||||
gridView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
gridView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
gridView->setModel(gridModel);
|
||||
gridModel->setColumnCount(m_columnCount);
|
||||
gridView->setModel(filteredModel);
|
||||
|
||||
m_productModels.insert(section, productModel);
|
||||
const auto it = m_gridViews.insert(section, gridView);
|
||||
@@ -425,12 +389,10 @@ void SectionedProducts::addNewSection(const Section §ion, const QList<Core::
|
||||
QTC_ASSERT(position <= vbox->count() - 1, position = vbox->count() - 1);
|
||||
vbox->insertWidget(position, sectionLabel);
|
||||
vbox->insertWidget(position + 1, gridView);
|
||||
gridView->setFixedSize(gridView->viewportSizeHint());
|
||||
|
||||
// add the items also to the all products model to be able to search correctly
|
||||
auto allProducts = static_cast<ProductListModel *>(m_filteredAllProductsModel->sourceModel());
|
||||
allProducts->appendItems(items);
|
||||
m_allProductsView->setColumnCount(m_columnCount);
|
||||
}
|
||||
|
||||
void SectionedProducts::onTagClicked(const QString &tag)
|
||||
|
@@ -108,10 +108,9 @@ private:
|
||||
QSet<QString> m_pendingImages;
|
||||
QMap<QString, QString> m_collectionTitles;
|
||||
QMap<Section, ProductListModel *> m_productModels;
|
||||
QMap<Section, ProductGridView *> m_gridViews;
|
||||
ProductGridView *m_allProductsView = nullptr;
|
||||
QMap<Section, Core::GridView *> m_gridViews;
|
||||
Core::GridView *m_allProductsView = nullptr;
|
||||
Core::ListModelFilter *m_filteredAllProductsModel = nullptr;
|
||||
Core::GridProxyModel * const m_gridModel;
|
||||
ProductItemDelegate *m_productDelegate = nullptr;
|
||||
bool m_isDownloadingImage = false;
|
||||
int m_columnCount = 1;
|
||||
|
@@ -124,18 +124,6 @@ public:
|
||||
QWidget::showEvent(event);
|
||||
}
|
||||
|
||||
void resizeEvent(QResizeEvent *ev) final
|
||||
{
|
||||
QWidget::resizeEvent(ev);
|
||||
m_sectionedProducts->setColumnCount(bestColumnCount());
|
||||
}
|
||||
|
||||
int bestColumnCount() const
|
||||
{
|
||||
return qMax(1, width() / (Core::GridProxyModel::GridItemWidth
|
||||
+ Core::GridProxyModel::GridItemGap));
|
||||
}
|
||||
|
||||
void onTagClicked(const QString &tag)
|
||||
{
|
||||
QString text = m_searcher->text();
|
||||
|
@@ -293,8 +293,8 @@ public:
|
||||
// for macOS dark mode
|
||||
pal.setColor(QPalette::Text, Utils::creatorTheme()->color(Theme::Welcome_TextColor));
|
||||
exampleSetSelector->setPalette(pal);
|
||||
exampleSetSelector->setMinimumWidth(GridProxyModel::GridItemWidth);
|
||||
exampleSetSelector->setMaximumWidth(GridProxyModel::GridItemWidth);
|
||||
exampleSetSelector->setMinimumWidth(Core::ListItemDelegate::GridItemWidth);
|
||||
exampleSetSelector->setMaximumWidth(Core::ListItemDelegate::GridItemWidth);
|
||||
ExampleSetModel *exampleSetModel = m_examplesModel->exampleSetModel();
|
||||
exampleSetSelector->setModel(exampleSetModel);
|
||||
exampleSetSelector->setCurrentIndex(exampleSetModel->selectedExampleSet());
|
||||
@@ -312,10 +312,8 @@ public:
|
||||
hbox->addSpacing(sideMargin);
|
||||
vbox->addItem(hbox);
|
||||
|
||||
m_gridModel.setSourceModel(filteredModel);
|
||||
|
||||
auto gridView = new GridView(this);
|
||||
gridView->setModel(&m_gridModel);
|
||||
gridView->setModel(filteredModel);
|
||||
gridView->setItemDelegate(&m_exampleDelegate);
|
||||
if (auto sb = gridView->verticalScrollBar())
|
||||
sb->setSingleStep(25);
|
||||
@@ -327,17 +325,6 @@ public:
|
||||
filteredModel, &ExamplesListModelFilter::setSearchString);
|
||||
}
|
||||
|
||||
int bestColumnCount() const
|
||||
{
|
||||
return qMax(1, width() / (GridProxyModel::GridItemWidth + GridProxyModel::GridItemGap));
|
||||
}
|
||||
|
||||
void resizeEvent(QResizeEvent *ev) final
|
||||
{
|
||||
QWidget::resizeEvent(ev);
|
||||
m_gridModel.setColumnCount(bestColumnCount());
|
||||
}
|
||||
|
||||
void onTagClicked(const QString &tag)
|
||||
{
|
||||
QString text = m_searcher->text();
|
||||
@@ -348,7 +335,6 @@ public:
|
||||
ExampleDelegate m_exampleDelegate;
|
||||
QPointer<ExamplesListModel> m_examplesModel;
|
||||
QLineEdit *m_searcher;
|
||||
GridProxyModel m_gridModel;
|
||||
};
|
||||
|
||||
QWidget *ExamplesWelcomePage::createWidget() const
|
||||
|
Reference in New Issue
Block a user