Examples: Limit categories to single row and allow showing all

Limit the items shown for categories to a single row (two rows for first,
"featured" category), and add a "Show All" link/button in the heading,
that shows all items in that category only (with a "Back" button).

The "Search in Examples" input ignores categories.

Change-Id: I7c8561a306ad86c3b537587621d3fd030cd08af8
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Eike Ziller
2023-03-15 12:54:51 +01:00
parent 4028777743
commit 9650a2bded
3 changed files with 114 additions and 19 deletions

View File

@@ -7,6 +7,7 @@
#include <utils/algorithm.h> #include <utils/algorithm.h>
#include <utils/fancylineedit.h> #include <utils/fancylineedit.h>
#include <utils/layoutbuilder.h>
#include <utils/qtcassert.h> #include <utils/qtcassert.h>
#include <utils/stylehelper.h> #include <utils/stylehelper.h>
#include <utils/theme/theme.h> #include <utils/theme/theme.h>
@@ -25,9 +26,11 @@
#include <qdrawutil.h> #include <qdrawutil.h>
using namespace Utils;
namespace Core { namespace Core {
using namespace Utils; using namespace WelcomePageHelpers;
static QColor themeColor(Theme::Color role) static QColor themeColor(Theme::Color role)
{ {
@@ -123,6 +126,17 @@ SectionGridView::SectionGridView(QWidget *parent)
: GridView(parent) : GridView(parent)
{} {}
void SectionGridView::setMaxRows(std::optional<int> max)
{
m_maxRows = max;
updateGeometry();
}
std::optional<int> SectionGridView::maxRows() const
{
return m_maxRows;
}
bool SectionGridView::hasHeightForWidth() const bool SectionGridView::hasHeightForWidth() const
{ {
return true; return true;
@@ -132,7 +146,16 @@ int SectionGridView::heightForWidth(int width) const
{ {
const int columnCount = width / Core::ListItemDelegate::GridItemWidth; const int columnCount = width / Core::ListItemDelegate::GridItemWidth;
const int rowCount = (model()->rowCount() + columnCount - 1) / columnCount; const int rowCount = (model()->rowCount() + columnCount - 1) / columnCount;
return rowCount * Core::ListItemDelegate::GridItemHeight; const int maxRowCount = m_maxRows ? std::min(*m_maxRows, rowCount) : rowCount;
return maxRowCount * Core::ListItemDelegate::GridItemHeight;
}
void SectionGridView::wheelEvent(QWheelEvent *e)
{
if (m_maxRows) // circumvent scrolling of the list view
QWidget::wheelEvent(e);
else
GridView::wheelEvent(e);
} }
const QSize ListModel::defaultImageSize(214, 160); const QSize ListModel::defaultImageSize(214, 160);
@@ -646,7 +669,7 @@ SectionedGridView::SectionedGridView(QWidget *parent)
auto sectionedView = new QWidget; auto sectionedView = new QWidget;
auto layout = new QVBoxLayout; auto layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0); layout->setContentsMargins(0, 0, 0, 0);
layout->addStretch(); layout->addStretch(1);
sectionedView->setLayout(layout); sectionedView->setLayout(layout);
area->setWidget(sectionedView); area->setWidget(sectionedView);
@@ -678,7 +701,11 @@ void SectionedGridView::setPixmapFunction(const Core::ListModel::PixmapFunction
void SectionedGridView::setSearchString(const QString &searchString) void SectionedGridView::setSearchString(const QString &searchString)
{ {
if (searchString.isEmpty()) { if (searchString.isEmpty()) {
// back to sectioned view // back to previous view
m_allItemsView.reset();
if (m_zoomedInWidget)
setCurrentWidget(m_zoomedInWidget);
else
setCurrentIndex(0); setCurrentIndex(0);
return; return;
} }
@@ -711,13 +738,19 @@ ListModel *SectionedGridView::addSection(const Section &section, const QList<Lis
gridView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); gridView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
gridView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); gridView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
gridView->setModel(model); gridView->setModel(model);
gridView->setMaxRows(section.maxRows);
m_sectionModels.insert(section, model); m_sectionModels.insert(section, model);
const auto it = m_gridViews.insert(section, gridView); const auto it = m_gridViews.insert(section, gridView);
auto sectionLabel = new QLabel(section.name); using namespace Layouting;
auto seeAllLink = new QLabel("<a href=\"link\">" + Tr::tr("Show All") + " &gt;</a>", this);
seeAllLink->setVisible(gridView->maxRows().has_value());
connect(seeAllLink, &QLabel::linkActivated, this, [this, section] { zoomInSection(section); });
QWidget *sectionLabel = Column{hr, Row{section.name, st, seeAllLink, Space(HSpacing)}}.emerge(
Layouting::WithoutMargins);
m_sectionLabels.append(sectionLabel); m_sectionLabels.append(sectionLabel);
sectionLabel->setContentsMargins(0, Core::WelcomePageHelpers::ItemGap, 0, 0); sectionLabel->setContentsMargins(0, ItemGap, 0, 0);
sectionLabel->setFont(Core::WelcomePageHelpers::brandFont()); sectionLabel->setFont(Core::WelcomePageHelpers::brandFont());
auto scrollArea = qobject_cast<QScrollArea *>(widget(0)); auto scrollArea = qobject_cast<QScrollArea *>(widget(0));
auto vbox = qobject_cast<QVBoxLayout *>(scrollArea->widget()->layout()); auto vbox = qobject_cast<QVBoxLayout *>(scrollArea->widget()->layout());
@@ -753,4 +786,47 @@ void SectionedGridView::clear()
m_allItemsView.reset(); m_allItemsView.reset();
} }
void SectionedGridView::zoomInSection(const Section &section)
{
auto zoomedInWidget = new QWidget(this);
auto layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
zoomedInWidget->setLayout(layout);
using namespace Layouting;
auto backLink = new QLabel("<a href=\"link\">&lt; " + Tr::tr("Back") + "</a>", zoomedInWidget);
connect(backLink, &QLabel::linkActivated, this, [this, zoomedInWidget] {
removeWidget(zoomedInWidget);
delete zoomedInWidget;
setCurrentIndex(0);
});
QWidget *sectionLabel = Column{hr, Row{section.name, st, backLink, Space(HSpacing)}}.emerge(
Layouting::WithoutMargins);
sectionLabel->setContentsMargins(0, ItemGap, 0, 0);
sectionLabel->setFont(Core::WelcomePageHelpers::brandFont());
auto gridView = new GridView(zoomedInWidget);
gridView->setItemDelegate(m_itemDelegate);
gridView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
gridView->setModel(m_sectionModels.value(section));
layout->addWidget(sectionLabel);
layout->addWidget(gridView);
m_zoomedInWidget = zoomedInWidget;
addWidget(zoomedInWidget);
setCurrentWidget(zoomedInWidget);
}
Section::Section(const QString &name, int priority)
: name(name)
, priority(priority)
{}
Section::Section(const QString &name, int priority, std::optional<int> maxRows)
: name(name)
, priority(priority)
, maxRows(maxRows)
{}
} // namespace Core } // namespace Core

View File

@@ -51,8 +51,16 @@ class CORE_EXPORT SectionGridView : public GridView
public: public:
explicit SectionGridView(QWidget *parent); explicit SectionGridView(QWidget *parent);
bool hasHeightForWidth() const; void setMaxRows(std::optional<int> max);
int heightForWidth(int width) const; std::optional<int> maxRows() const;
bool hasHeightForWidth() const override;
int heightForWidth(int width) const override;
void wheelEvent(QWheelEvent *e) override;
private:
std::optional<int> m_maxRows;
}; };
using OptModelIndex = std::optional<QModelIndex>; using OptModelIndex = std::optional<QModelIndex>;
@@ -165,6 +173,9 @@ private:
class CORE_EXPORT Section class CORE_EXPORT Section
{ {
public: public:
Section(const QString &name, int priority);
Section(const QString &name, int priority, std::optional<int> maxRows);
friend bool operator<(const Section &lhs, const Section &rhs) friend bool operator<(const Section &lhs, const Section &rhs)
{ {
if (lhs.priority < rhs.priority) if (lhs.priority < rhs.priority)
@@ -179,6 +190,7 @@ public:
QString name; QString name;
int priority; int priority;
std::optional<int> maxRows;
}; };
class CORE_EXPORT SectionedGridView : public QStackedWidget class CORE_EXPORT SectionedGridView : public QStackedWidget
@@ -196,11 +208,14 @@ public:
void clear(); void clear();
private: private:
void zoomInSection(const Section &section);
QMap<Section, Core::ListModel *> m_sectionModels; QMap<Section, Core::ListModel *> m_sectionModels;
QList<QWidget *> m_sectionLabels; QList<QWidget *> m_sectionLabels;
QMap<Section, Core::GridView *> m_gridViews; QMap<Section, Core::GridView *> m_gridViews;
std::unique_ptr<Core::ListModel> m_allItemsModel; std::unique_ptr<Core::ListModel> m_allItemsModel;
std::unique_ptr<Core::GridView> m_allItemsView; std::unique_ptr<Core::GridView> m_allItemsView;
QPointer<QWidget> m_zoomedInWidget;
Core::ListModel::PixmapFunction m_pixmapFunction; Core::ListModel::PixmapFunction m_pixmapFunction;
QAbstractItemDelegate *m_itemDelegate = nullptr; QAbstractItemDelegate *m_itemDelegate = nullptr;
}; };

View File

@@ -329,7 +329,7 @@ static bool sortByHighlightedAndName(ExampleItem *first, ExampleItem *second)
return first->name.compare(second->name, Qt::CaseInsensitive) < 0; return first->name.compare(second->name, Qt::CaseInsensitive) < 0;
} }
static QList<std::pair<QString, QList<ExampleItem *>>> getCategories( static QList<std::pair<Section, QList<ExampleItem *>>> getCategories(
const QList<ExampleItem *> &items) const QList<ExampleItem *> &items)
{ {
static const QString otherDisplayName = Tr::tr("Other", "Category for all other examples"); static const QString otherDisplayName = Tr::tr("Other", "Category for all other examples");
@@ -345,7 +345,7 @@ static QList<std::pair<QString, QList<ExampleItem *>>> getCategories(
other.append(item); other.append(item);
} }
} }
QList<std::pair<QString, QList<ExampleItem *>>> categories; QList<std::pair<Section, QList<ExampleItem *>>> categories;
if (categoryMap.isEmpty()) { if (categoryMap.isEmpty()) {
// The example set doesn't define categories. Consider the "highlighted" ones as "featured" // The example set doesn't define categories. Consider the "highlighted" ones as "featured"
QList<ExampleItem *> featured; QList<ExampleItem *> featured;
@@ -354,15 +354,19 @@ static QList<std::pair<QString, QList<ExampleItem *>>> getCategories(
return i->isHighlighted; return i->isHighlighted;
}); });
if (!featured.isEmpty()) if (!featured.isEmpty())
categories.append({Tr::tr("Featured", "Category for highlighted examples"), featured}); categories.append(
{{Tr::tr("Featured", "Category for highlighted examples"), 0}, featured});
if (!allOther.isEmpty()) if (!allOther.isEmpty())
categories.append({otherDisplayName, allOther}); categories.append({{otherDisplayName, 1}, allOther});
} else { } else {
int index = 0;
const auto end = categoryMap.constKeyValueEnd(); const auto end = categoryMap.constKeyValueEnd();
for (auto it = categoryMap.constKeyValueBegin(); it != end; ++it) for (auto it = categoryMap.constKeyValueBegin(); it != end; ++it) {
categories.append(*it); categories.append({{it->first, index, /*maxRows=*/index == 0 ? 2 : 1}, it->second});
++index;
}
if (!other.isEmpty()) if (!other.isEmpty())
categories.append({otherDisplayName, other}); categories.append({{otherDisplayName, index, /*maxRows=*/1}, other});
} }
const auto end = categories.end(); const auto end = categories.end();
for (auto it = categories.begin(); it != end; ++it) for (auto it = categories.begin(); it != end; ++it)
@@ -414,9 +418,9 @@ void ExamplesViewController::updateExamples()
} }
} }
const QList<std::pair<QString, QList<ExampleItem *>>> sections = getCategories(items); const QList<std::pair<Section, QList<ExampleItem *>>> sections = getCategories(items);
for (int i = 0; i < sections.size(); ++i) { for (int i = 0; i < sections.size(); ++i) {
m_view->addSection({sections.at(i).first, i}, m_view->addSection(sections.at(i).first,
static_container_cast<ListItem *>(sections.at(i).second)); static_container_cast<ListItem *>(sections.at(i).second));
} }
} }
@@ -519,7 +523,7 @@ QStringList ExampleSetModel::exampleSources(QString *examplesInstallPath, QStrin
const QStringList demosPattern(QLatin1String("demos-manifest.xml")); const QStringList demosPattern(QLatin1String("demos-manifest.xml"));
QFileInfoList fis; QFileInfoList fis;
const QFileInfoList subDirs = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); const QFileInfoList subDirs = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
for (QFileInfo subDir : subDirs) { for (const QFileInfo &subDir : subDirs) {
fis << QDir(subDir.absoluteFilePath()).entryInfoList(examplesPattern); fis << QDir(subDir.absoluteFilePath()).entryInfoList(examplesPattern);
fis << QDir(subDir.absoluteFilePath()).entryInfoList(demosPattern); fis << QDir(subDir.absoluteFilePath()).entryInfoList(demosPattern);
} }