2020-01-09 15:52:47 +01:00
|
|
|
/****************************************************************************
|
|
|
|
|
**
|
|
|
|
|
** Copyright (C) 2019 The Qt Company Ltd.
|
|
|
|
|
** Contact: https://www.qt.io/licensing/
|
|
|
|
|
**
|
|
|
|
|
** This file is part of Qt Creator.
|
|
|
|
|
**
|
|
|
|
|
** Commercial License Usage
|
|
|
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
|
|
|
** accordance with the commercial license agreement provided with the
|
|
|
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
|
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
|
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
|
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
|
|
|
**
|
|
|
|
|
** GNU General Public License Usage
|
|
|
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
|
|
|
** General Public License version 3 as published by the Free Software
|
|
|
|
|
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
|
|
|
|
|
** included in the packaging of this file. Please review the following
|
|
|
|
|
** information to ensure the GNU General Public License requirements will
|
|
|
|
|
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
|
|
|
|
|
**
|
|
|
|
|
****************************************************************************/
|
|
|
|
|
|
|
|
|
|
#include "welcomepagehelper.h"
|
|
|
|
|
|
2020-01-16 15:48:32 +01:00
|
|
|
#include <utils/algorithm.h>
|
2020-01-09 15:52:47 +01:00
|
|
|
#include <utils/fancylineedit.h>
|
|
|
|
|
#include <utils/qtcassert.h>
|
2021-11-29 20:48:43 +01:00
|
|
|
#include <utils/stylehelper.h>
|
2020-01-09 15:52:47 +01:00
|
|
|
#include <utils/theme/theme.h>
|
|
|
|
|
|
2021-09-19 19:32:52 +02:00
|
|
|
#include <QEasingCurve>
|
2021-11-29 20:48:43 +01:00
|
|
|
#include <QFontDatabase>
|
2020-01-09 15:52:47 +01:00
|
|
|
#include <QHeaderView>
|
|
|
|
|
#include <QHoverEvent>
|
|
|
|
|
#include <QLayout>
|
2020-01-16 15:48:32 +01:00
|
|
|
#include <QMouseEvent>
|
|
|
|
|
#include <QPainter>
|
|
|
|
|
#include <QPixmapCache>
|
|
|
|
|
#include <QTimer>
|
2020-01-09 15:52:47 +01:00
|
|
|
|
2021-09-19 19:32:52 +02:00
|
|
|
#include <qdrawutil.h>
|
|
|
|
|
|
2020-01-09 15:52:47 +01:00
|
|
|
namespace Core {
|
|
|
|
|
|
|
|
|
|
using namespace Utils;
|
|
|
|
|
|
2020-01-16 15:48:32 +01:00
|
|
|
static QColor themeColor(Theme::Color role)
|
|
|
|
|
{
|
|
|
|
|
return creatorTheme()->color(role);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QFont sizedFont(int size, const QWidget *widget)
|
|
|
|
|
{
|
|
|
|
|
QFont f = widget->font();
|
|
|
|
|
f.setPixelSize(size);
|
|
|
|
|
return f;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-29 20:48:43 +01:00
|
|
|
namespace WelcomePageHelpers {
|
|
|
|
|
|
|
|
|
|
QFont brandFont()
|
|
|
|
|
{
|
|
|
|
|
const static QFont f = []{
|
|
|
|
|
const int id = QFontDatabase::addApplicationFont(":/studiofonts/TitilliumWeb-Regular.ttf");
|
|
|
|
|
QFont result;
|
|
|
|
|
result.setPixelSize(16);
|
|
|
|
|
if (id >= 0) {
|
|
|
|
|
const QStringList fontFamilies = QFontDatabase::applicationFontFamilies(id);
|
|
|
|
|
result.setFamilies(fontFamilies);
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}();
|
|
|
|
|
return f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QWidget *panelBar(QWidget *parent)
|
|
|
|
|
{
|
|
|
|
|
auto frame = new QWidget(parent);
|
|
|
|
|
frame->setAutoFillBackground(true);
|
|
|
|
|
frame->setMinimumWidth(WelcomePageHelpers::HSpacing);
|
2022-02-02 17:35:58 +01:00
|
|
|
QPalette pal;
|
|
|
|
|
pal.setBrush(QPalette::Window, {});
|
2021-11-29 20:48:43 +01:00
|
|
|
pal.setColor(QPalette::Window, themeColor(Theme::Welcome_BackgroundPrimaryColor));
|
|
|
|
|
frame->setPalette(pal);
|
|
|
|
|
return frame;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace WelcomePageHelpers
|
|
|
|
|
|
2020-01-09 15:52:47 +01:00
|
|
|
SearchBox::SearchBox(QWidget *parent)
|
|
|
|
|
: WelcomePageFrame(parent)
|
|
|
|
|
{
|
2021-11-29 20:48:43 +01:00
|
|
|
setAutoFillBackground(true);
|
2020-01-09 15:52:47 +01:00
|
|
|
|
|
|
|
|
m_lineEdit = new FancyLineEdit;
|
|
|
|
|
m_lineEdit->setFiltering(true);
|
|
|
|
|
m_lineEdit->setFrame(false);
|
2021-11-29 20:48:43 +01:00
|
|
|
m_lineEdit->setFont(WelcomePageHelpers::brandFont());
|
|
|
|
|
m_lineEdit->setMinimumHeight(33);
|
2020-01-09 15:52:47 +01:00
|
|
|
m_lineEdit->setAttribute(Qt::WA_MacShowFocusRect, false);
|
|
|
|
|
|
2021-11-29 20:48:43 +01:00
|
|
|
QPalette pal = buttonPalette(false, false, true);
|
|
|
|
|
// for the margins
|
|
|
|
|
pal.setColor(QPalette::Window, m_lineEdit->palette().color(QPalette::Base));
|
|
|
|
|
// for macOS dark mode
|
|
|
|
|
pal.setColor(QPalette::WindowText, themeColor(Theme::Welcome_ForegroundPrimaryColor));
|
|
|
|
|
pal.setColor(QPalette::Text, themeColor(Theme::Welcome_TextColor));
|
|
|
|
|
setPalette(pal);
|
|
|
|
|
|
2020-01-09 15:52:47 +01:00
|
|
|
auto box = new QHBoxLayout(this);
|
2021-11-29 20:48:43 +01:00
|
|
|
box->setContentsMargins(10, 0, 1, 0);
|
2020-01-09 15:52:47 +01:00
|
|
|
box->addWidget(m_lineEdit);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GridView::GridView(QWidget *parent)
|
2021-11-24 22:19:46 +01:00
|
|
|
: QListView(parent)
|
2020-01-09 15:52:47 +01:00
|
|
|
{
|
2021-11-24 22:19:46 +01:00
|
|
|
setResizeMode(QListView::Adjust);
|
2020-01-09 15:52:47 +01:00
|
|
|
setMouseTracking(true); // To enable hover.
|
|
|
|
|
setSelectionMode(QAbstractItemView::NoSelection);
|
|
|
|
|
setFrameShape(QFrame::NoFrame);
|
2021-03-04 18:00:02 +01:00
|
|
|
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
2021-11-24 22:19:46 +01:00
|
|
|
setViewMode(IconMode);
|
|
|
|
|
setUniformItemSizes(true);
|
2020-01-09 15:52:47 +01:00
|
|
|
|
|
|
|
|
QPalette pal;
|
2021-11-29 20:48:43 +01:00
|
|
|
pal.setColor(QPalette::Base, themeColor(Theme::Welcome_BackgroundSecondaryColor));
|
2020-01-09 15:52:47 +01:00
|
|
|
setPalette(pal); // Makes a difference on Mac.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GridView::leaveEvent(QEvent *)
|
|
|
|
|
{
|
|
|
|
|
QHoverEvent hev(QEvent::HoverLeave, QPointF(), QPointF());
|
|
|
|
|
viewportEvent(&hev); // Seemingly needed to kill the hover paint.
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-29 20:48:43 +01:00
|
|
|
const QSize ListModel::defaultImageSize(214, 160);
|
2020-01-16 15:48:32 +01:00
|
|
|
|
|
|
|
|
ListModel::ListModel(QObject *parent)
|
|
|
|
|
: QAbstractListModel(parent)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ListModel::~ListModel()
|
|
|
|
|
{
|
|
|
|
|
qDeleteAll(m_items);
|
|
|
|
|
m_items.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int ListModel::rowCount(const QModelIndex &) const
|
|
|
|
|
{
|
|
|
|
|
return m_items.size();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QVariant ListModel::data(const QModelIndex &index, int role) const
|
|
|
|
|
{
|
|
|
|
|
if (!index.isValid() || index.row() >= m_items.count())
|
|
|
|
|
return QVariant();
|
|
|
|
|
|
|
|
|
|
ListItem *item = m_items.at(index.row());
|
|
|
|
|
switch (role) {
|
|
|
|
|
case Qt::DisplayRole: // for search only
|
|
|
|
|
return QString(item->name + ' ' + item->tags.join(' '));
|
|
|
|
|
case ItemRole:
|
|
|
|
|
return QVariant::fromValue(item);
|
|
|
|
|
case ItemImageRole: {
|
|
|
|
|
QPixmap pixmap;
|
|
|
|
|
if (QPixmapCache::find(item->imageUrl, &pixmap))
|
|
|
|
|
return pixmap;
|
|
|
|
|
if (pixmap.isNull())
|
|
|
|
|
pixmap = fetchPixmapAndUpdatePixmapCache(item->imageUrl);
|
|
|
|
|
return pixmap;
|
|
|
|
|
}
|
|
|
|
|
case ItemTagsRole:
|
|
|
|
|
return item->tags;
|
|
|
|
|
default:
|
|
|
|
|
return QVariant();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ListModelFilter::ListModelFilter(ListModel *sourceModel, QObject *parent) :
|
|
|
|
|
QSortFilterProxyModel(parent)
|
|
|
|
|
{
|
|
|
|
|
setSourceModel(sourceModel);
|
|
|
|
|
setDynamicSortFilter(true);
|
|
|
|
|
setFilterCaseSensitivity(Qt::CaseInsensitive);
|
|
|
|
|
sort(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ListModelFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
|
|
|
|
{
|
|
|
|
|
const ListItem *item = sourceModel()->index(sourceRow, 0, sourceParent).data(
|
|
|
|
|
ListModel::ItemRole).value<Core::ListItem *>();
|
|
|
|
|
|
|
|
|
|
if (!item)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
bool earlyExitResult;
|
|
|
|
|
if (leaveFilterAcceptsRowBeforeFiltering(item, &earlyExitResult))
|
|
|
|
|
return earlyExitResult;
|
|
|
|
|
|
|
|
|
|
if (!m_filterTags.isEmpty()) {
|
|
|
|
|
return Utils::allOf(m_filterTags, [&item](const QString &filterTag) {
|
|
|
|
|
return item->tags.contains(filterTag);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!m_filterStrings.isEmpty()) {
|
|
|
|
|
for (const QString &subString : m_filterStrings) {
|
|
|
|
|
bool wordMatch = false;
|
|
|
|
|
wordMatch |= bool(item->name.contains(subString, Qt::CaseInsensitive));
|
|
|
|
|
if (wordMatch)
|
|
|
|
|
continue;
|
|
|
|
|
const auto subMatch = [&subString](const QString &elem) { return elem.contains(subString); };
|
|
|
|
|
wordMatch |= Utils::contains(item->tags, subMatch);
|
|
|
|
|
if (wordMatch)
|
|
|
|
|
continue;
|
|
|
|
|
wordMatch |= bool(item->description.contains(subString, Qt::CaseInsensitive));
|
|
|
|
|
if (!wordMatch)
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ListModelFilter::delayedUpdateFilter()
|
|
|
|
|
{
|
|
|
|
|
if (m_timerId != 0)
|
|
|
|
|
killTimer(m_timerId);
|
|
|
|
|
|
|
|
|
|
m_timerId = startTimer(320);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ListModelFilter::timerEvent(QTimerEvent *timerEvent)
|
|
|
|
|
{
|
|
|
|
|
if (m_timerId == timerEvent->timerId()) {
|
|
|
|
|
invalidateFilter();
|
|
|
|
|
emit layoutChanged();
|
|
|
|
|
killTimer(m_timerId);
|
|
|
|
|
m_timerId = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct SearchStringLexer
|
|
|
|
|
{
|
|
|
|
|
QString code;
|
|
|
|
|
const QChar *codePtr;
|
|
|
|
|
QChar yychar;
|
|
|
|
|
QString yytext;
|
|
|
|
|
|
|
|
|
|
enum TokenKind {
|
|
|
|
|
END_OF_STRING = 0,
|
|
|
|
|
TAG,
|
|
|
|
|
STRING_LITERAL,
|
|
|
|
|
UNKNOWN
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
inline void yyinp() { yychar = *codePtr++; }
|
|
|
|
|
|
2021-11-29 20:48:43 +01:00
|
|
|
explicit SearchStringLexer(const QString &code)
|
2020-01-16 15:48:32 +01:00
|
|
|
: code(code)
|
|
|
|
|
, codePtr(code.unicode())
|
|
|
|
|
, yychar(QLatin1Char(' ')) { }
|
|
|
|
|
|
|
|
|
|
int operator()() { return yylex(); }
|
|
|
|
|
|
|
|
|
|
int yylex() {
|
|
|
|
|
while (yychar.isSpace())
|
|
|
|
|
yyinp(); // skip all the spaces
|
|
|
|
|
|
|
|
|
|
yytext.clear();
|
|
|
|
|
|
|
|
|
|
if (yychar.isNull())
|
|
|
|
|
return END_OF_STRING;
|
|
|
|
|
|
|
|
|
|
QChar ch = yychar;
|
|
|
|
|
yyinp();
|
|
|
|
|
|
|
|
|
|
switch (ch.unicode()) {
|
|
|
|
|
case '"':
|
|
|
|
|
case '\'':
|
|
|
|
|
{
|
|
|
|
|
const QChar quote = ch;
|
|
|
|
|
yytext.clear();
|
|
|
|
|
while (!yychar.isNull()) {
|
|
|
|
|
if (yychar == quote) {
|
|
|
|
|
yyinp();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (yychar == QLatin1Char('\\')) {
|
|
|
|
|
yyinp();
|
|
|
|
|
switch (yychar.unicode()) {
|
|
|
|
|
case '"': yytext += QLatin1Char('"'); yyinp(); break;
|
|
|
|
|
case '\'': yytext += QLatin1Char('\''); yyinp(); break;
|
|
|
|
|
case '\\': yytext += QLatin1Char('\\'); yyinp(); break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
yytext += yychar;
|
|
|
|
|
yyinp();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return STRING_LITERAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
if (ch.isLetterOrNumber() || ch == QLatin1Char('_')) {
|
|
|
|
|
yytext.clear();
|
|
|
|
|
yytext += ch;
|
|
|
|
|
while (yychar.isLetterOrNumber() || yychar == QLatin1Char('_')) {
|
|
|
|
|
yytext += yychar;
|
|
|
|
|
yyinp();
|
|
|
|
|
}
|
|
|
|
|
if (yychar == QLatin1Char(':') && yytext == QLatin1String("tag")) {
|
|
|
|
|
yyinp();
|
|
|
|
|
return TAG;
|
|
|
|
|
}
|
|
|
|
|
return STRING_LITERAL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
yytext += ch;
|
|
|
|
|
return UNKNOWN;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void ListModelFilter::setSearchString(const QString &arg)
|
|
|
|
|
{
|
|
|
|
|
if (m_searchString == arg)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_searchString = arg;
|
|
|
|
|
m_filterTags.clear();
|
|
|
|
|
m_filterStrings.clear();
|
|
|
|
|
|
|
|
|
|
// parse and update
|
|
|
|
|
SearchStringLexer lex(arg);
|
|
|
|
|
bool isTag = false;
|
|
|
|
|
while (int tk = lex()) {
|
|
|
|
|
if (tk == SearchStringLexer::TAG) {
|
|
|
|
|
isTag = true;
|
|
|
|
|
m_filterStrings.append(lex.yytext);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tk == SearchStringLexer::STRING_LITERAL) {
|
|
|
|
|
if (isTag) {
|
|
|
|
|
m_filterStrings.pop_back();
|
|
|
|
|
m_filterTags.append(lex.yytext);
|
|
|
|
|
isTag = false;
|
|
|
|
|
} else {
|
|
|
|
|
m_filterStrings.append(lex.yytext);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
delayedUpdateFilter();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ListModelFilter::leaveFilterAcceptsRowBeforeFiltering(const ListItem *, bool *) const
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ListItemDelegate::ListItemDelegate()
|
2021-11-29 20:48:43 +01:00
|
|
|
: backgroundPrimaryColor(themeColor(Theme::Welcome_BackgroundPrimaryColor))
|
|
|
|
|
, backgroundSecondaryColor(themeColor(Theme::Welcome_BackgroundSecondaryColor))
|
|
|
|
|
, foregroundPrimaryColor(themeColor(Theme::Welcome_ForegroundPrimaryColor))
|
|
|
|
|
, hoverColor(themeColor(Theme::Welcome_HoverColor))
|
|
|
|
|
, textColor(themeColor(Theme::Welcome_TextColor))
|
2020-01-16 15:48:32 +01:00
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
|
|
|
|
const QModelIndex &index) const
|
|
|
|
|
{
|
|
|
|
|
const ListItem *item = index.data(ListModel::ItemRole).value<Core::ListItem *>();
|
|
|
|
|
|
|
|
|
|
const QRect rc = option.rect;
|
2021-11-29 20:48:43 +01:00
|
|
|
const QRect tileRect(0, 0, rc.width() - GridItemGap, rc.height() - GridItemGap);
|
|
|
|
|
const QSize thumbnailBgSize = ListModel::defaultImageSize.grownBy(QMargins(1, 1, 1, 1));
|
|
|
|
|
const QRect thumbnailBgRect((tileRect.width() - thumbnailBgSize.width()) / 2, GridItemGap,
|
|
|
|
|
thumbnailBgSize.width(), thumbnailBgSize.height());
|
|
|
|
|
const QRect textArea = tileRect.adjusted(GridItemGap, GridItemGap, -GridItemGap, -GridItemGap);
|
2020-01-16 15:48:32 +01:00
|
|
|
|
|
|
|
|
const bool hovered = option.state & QStyle::State_MouseOver;
|
|
|
|
|
|
2021-11-29 20:48:43 +01:00
|
|
|
constexpr int tagsBase = TagsSeparatorY + 17;
|
|
|
|
|
constexpr int shiftY = TagsSeparatorY - 16;
|
|
|
|
|
constexpr int nameY = TagsSeparatorY - 20;
|
|
|
|
|
|
|
|
|
|
const QRect textRect = textArea.translated(0, nameY);
|
2022-01-20 22:27:58 +01:00
|
|
|
const QFont descriptionFont = sizedFont(11, option.widget);
|
2020-01-16 15:48:32 +01:00
|
|
|
|
2021-11-29 20:48:43 +01:00
|
|
|
painter->save();
|
|
|
|
|
painter->translate(rc.topLeft());
|
|
|
|
|
|
|
|
|
|
painter->fillRect(tileRect, hovered ? hoverColor : backgroundPrimaryColor);
|
2020-01-16 15:48:32 +01:00
|
|
|
|
|
|
|
|
QTextOption wrapped;
|
|
|
|
|
wrapped.setWrapMode(QTextOption::WordWrap);
|
|
|
|
|
int offset = 0;
|
2021-09-19 19:32:52 +02:00
|
|
|
float animationProgress = 0; // Linear increase from 0.0 to 1.0 during hover animation
|
2020-01-16 15:48:32 +01:00
|
|
|
if (hovered) {
|
|
|
|
|
if (index != m_previousIndex) {
|
|
|
|
|
m_previousIndex = index;
|
2021-11-29 20:48:43 +01:00
|
|
|
m_blurredThumbnail = QPixmap();
|
2020-01-16 15:48:32 +01:00
|
|
|
m_startTime.start();
|
|
|
|
|
m_currentWidget = qobject_cast<QAbstractItemView *>(
|
|
|
|
|
const_cast<QWidget *>(option.widget));
|
|
|
|
|
}
|
2021-11-29 20:48:43 +01:00
|
|
|
constexpr float hoverAnimationDuration = 260;
|
|
|
|
|
animationProgress = m_startTime.elapsed() / hoverAnimationDuration;
|
|
|
|
|
static const QEasingCurve animationCurve(QEasingCurve::OutCubic);
|
2021-09-19 19:32:52 +02:00
|
|
|
offset = animationCurve.valueForProgress(animationProgress) * shiftY;
|
2020-01-16 15:48:32 +01:00
|
|
|
if (offset < shiftY)
|
2021-09-19 19:32:52 +02:00
|
|
|
QTimer::singleShot(10, this, &ListItemDelegate::goon);
|
2022-01-20 14:55:42 +01:00
|
|
|
} else if (index == m_previousIndex) {
|
2020-01-16 15:48:32 +01:00
|
|
|
m_previousIndex = QModelIndex();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QRect shiftedTextRect = textRect.adjusted(0, -offset, 0, -offset);
|
|
|
|
|
|
|
|
|
|
// The pixmap.
|
2021-11-29 20:48:43 +01:00
|
|
|
const QPixmap pm = index.data(ListModel::ItemImageRole).value<QPixmap>();
|
|
|
|
|
QPoint thumbnailPos = thumbnailBgRect.center();
|
|
|
|
|
if (!pm.isNull()) {
|
|
|
|
|
painter->fillRect(thumbnailBgRect, backgroundSecondaryColor);
|
|
|
|
|
|
|
|
|
|
thumbnailPos.rx() -= pm.width() / pm.devicePixelRatio() / 2 - 1;
|
|
|
|
|
thumbnailPos.ry() -= pm.height() / pm.devicePixelRatio() / 2 - 1;
|
|
|
|
|
painter->drawPixmap(thumbnailPos, pm);
|
|
|
|
|
|
|
|
|
|
painter->setPen(foregroundPrimaryColor);
|
|
|
|
|
drawPixmapOverlay(item, painter, option, thumbnailBgRect);
|
|
|
|
|
} else {
|
|
|
|
|
// The description text as fallback.
|
|
|
|
|
painter->setPen(textColor);
|
2022-01-20 22:27:58 +01:00
|
|
|
painter->setFont(descriptionFont);
|
|
|
|
|
painter->drawText(textArea, item->description, wrapped);
|
2021-09-19 19:32:52 +02:00
|
|
|
}
|
|
|
|
|
|
2021-11-29 20:48:43 +01:00
|
|
|
// The description background
|
2022-01-20 22:27:58 +01:00
|
|
|
if (offset) {
|
|
|
|
|
QRect backgroundPortionRect = tileRect;
|
|
|
|
|
backgroundPortionRect.setTop(shiftY - offset);
|
|
|
|
|
if (!pm.isNull()) {
|
|
|
|
|
if (m_blurredThumbnail.isNull()) {
|
|
|
|
|
constexpr int blurRadius = 50;
|
|
|
|
|
QImage thumbnail(tileRect.size() + QSize(blurRadius, blurRadius) * 2,
|
|
|
|
|
QImage::Format_ARGB32_Premultiplied);
|
|
|
|
|
thumbnail.fill(hoverColor);
|
|
|
|
|
QPainter thumbnailPainter(&thumbnail);
|
|
|
|
|
thumbnailPainter.translate(blurRadius, blurRadius);
|
|
|
|
|
thumbnailPainter.fillRect(thumbnailBgRect, backgroundSecondaryColor);
|
|
|
|
|
thumbnailPainter.drawPixmap(thumbnailPos, pm);
|
|
|
|
|
thumbnailPainter.setPen(foregroundPrimaryColor);
|
|
|
|
|
drawPixmapOverlay(item, &thumbnailPainter, option, thumbnailBgRect);
|
|
|
|
|
thumbnailPainter.end();
|
|
|
|
|
|
|
|
|
|
m_blurredThumbnail = QPixmap(tileRect.size());
|
|
|
|
|
QPainter blurredThumbnailPainter(&m_blurredThumbnail);
|
|
|
|
|
blurredThumbnailPainter.translate(-blurRadius, -blurRadius);
|
|
|
|
|
qt_blurImage(&blurredThumbnailPainter, thumbnail, blurRadius, false, false);
|
|
|
|
|
blurredThumbnailPainter.setOpacity(0.825);
|
|
|
|
|
blurredThumbnailPainter.fillRect(tileRect, hoverColor);
|
|
|
|
|
}
|
|
|
|
|
const QPixmap thumbnailPortionPM = m_blurredThumbnail.copy(backgroundPortionRect);
|
|
|
|
|
painter->drawPixmap(backgroundPortionRect.topLeft(), thumbnailPortionPM);
|
|
|
|
|
} else {
|
|
|
|
|
painter->fillRect(backgroundPortionRect, hoverColor);
|
2021-11-29 20:48:43 +01:00
|
|
|
}
|
2020-01-16 15:48:32 +01:00
|
|
|
}
|
|
|
|
|
|
2021-11-29 20:48:43 +01:00
|
|
|
// The description Text (unhovered or hovered)
|
|
|
|
|
painter->setPen(textColor);
|
|
|
|
|
painter->setFont(sizedFont(13, option.widget)); // Title font
|
2020-01-16 15:48:32 +01:00
|
|
|
if (offset) {
|
2021-11-29 20:48:43 +01:00
|
|
|
// The title of the example
|
|
|
|
|
const QRectF nameRect = painter->boundingRect(shiftedTextRect, item->name, wrapped);
|
2020-01-16 15:48:32 +01:00
|
|
|
painter->drawText(nameRect, item->name, wrapped);
|
|
|
|
|
|
2021-11-29 20:48:43 +01:00
|
|
|
// The separator line below the example title.
|
|
|
|
|
const int ll = nameRect.height() + 3;
|
|
|
|
|
const QLine line = QLine(0, ll, textArea.width(), ll).translated(shiftedTextRect.topLeft());
|
|
|
|
|
painter->setPen(foregroundPrimaryColor);
|
2021-09-19 19:32:52 +02:00
|
|
|
painter->setOpacity(animationProgress); // "fade in" separator line and description
|
2021-11-29 20:48:43 +01:00
|
|
|
painter->drawLine(line);
|
2020-01-16 15:48:32 +01:00
|
|
|
|
2021-11-29 20:48:43 +01:00
|
|
|
// The description text.
|
|
|
|
|
const int dd = ll + 5;
|
|
|
|
|
const QRect descRect = shiftedTextRect.adjusted(0, dd, 0, dd);
|
|
|
|
|
painter->setPen(textColor);
|
2022-01-20 22:27:58 +01:00
|
|
|
painter->setFont(descriptionFont);
|
2020-01-16 15:48:32 +01:00
|
|
|
painter->drawText(descRect, item->description, wrapped);
|
2021-09-19 19:32:52 +02:00
|
|
|
painter->setOpacity(1);
|
2021-11-29 20:48:43 +01:00
|
|
|
} else {
|
|
|
|
|
// The title of the example
|
|
|
|
|
const QString elidedName = painter->fontMetrics()
|
|
|
|
|
.elidedText(item->name, Qt::ElideRight, textRect.width());
|
|
|
|
|
painter->drawText(textRect, elidedName);
|
2020-01-16 15:48:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Separator line between text and 'Tags:' section
|
2021-11-29 20:48:43 +01:00
|
|
|
painter->setPen(foregroundPrimaryColor);
|
|
|
|
|
painter->drawLine(QLineF(textArea.topLeft(), textArea.topRight())
|
|
|
|
|
.translated(0, TagsSeparatorY));
|
2020-01-16 15:48:32 +01:00
|
|
|
|
|
|
|
|
// The 'Tags:' section
|
2021-11-29 20:48:43 +01:00
|
|
|
painter->setPen(foregroundPrimaryColor);
|
2020-01-16 15:48:32 +01:00
|
|
|
const QFont tagsFont = sizedFont(10, option.widget);
|
|
|
|
|
painter->setFont(tagsFont);
|
2021-11-29 20:48:43 +01:00
|
|
|
const QFontMetrics fm = painter->fontMetrics();
|
|
|
|
|
const QString tagsLabelText = tr("Tags:");
|
|
|
|
|
constexpr int tagsHorSpacing = 5;
|
|
|
|
|
const QRect tagsLabelRect =
|
|
|
|
|
QRect(0, 0, fm.horizontalAdvance(tagsLabelText) + tagsHorSpacing, fm.height())
|
|
|
|
|
.translated(textArea.x(), tagsBase);
|
|
|
|
|
painter->drawText(tagsLabelRect, tagsLabelText);
|
2020-01-16 15:48:32 +01:00
|
|
|
|
|
|
|
|
painter->setPen(themeColor(Theme::Welcome_LinkColor));
|
|
|
|
|
m_currentTagRects.clear();
|
2022-02-10 09:22:17 +01:00
|
|
|
int emptyTagRowsLeft = 2;
|
2020-01-16 15:48:32 +01:00
|
|
|
int xx = 0;
|
2021-11-29 20:48:43 +01:00
|
|
|
int yy = 0;
|
2020-01-16 15:48:32 +01:00
|
|
|
for (const QString &tag : item->tags) {
|
2021-11-29 20:48:43 +01:00
|
|
|
const int ww = fm.horizontalAdvance(tag) + tagsHorSpacing;
|
|
|
|
|
if (xx + ww > textArea.width() - tagsLabelRect.width()) {
|
2022-02-10 09:22:17 +01:00
|
|
|
if (--emptyTagRowsLeft == 0)
|
|
|
|
|
break;
|
2021-11-29 20:48:43 +01:00
|
|
|
yy += fm.lineSpacing();
|
2020-01-16 15:48:32 +01:00
|
|
|
xx = 0;
|
|
|
|
|
}
|
2021-11-29 20:48:43 +01:00
|
|
|
const QRect tagRect = QRect(xx, yy, ww, tagsLabelRect.height())
|
|
|
|
|
.translated(tagsLabelRect.topRight());
|
2020-01-16 15:48:32 +01:00
|
|
|
painter->drawText(tagRect, tag);
|
2021-11-29 20:48:43 +01:00
|
|
|
m_currentTagRects.append({ tag, tagRect.translated(rc.topLeft()) });
|
2020-01-16 15:48:32 +01:00
|
|
|
xx += ww;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-29 20:48:43 +01:00
|
|
|
painter->restore();
|
2020-01-16 15:48:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ListItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
|
|
|
|
const QStyleOptionViewItem &option, const QModelIndex &index)
|
|
|
|
|
{
|
|
|
|
|
if (event->type() == QEvent::MouseButtonRelease) {
|
|
|
|
|
const ListItem *item = index.data(ListModel::ItemRole).value<ListItem *>();
|
2020-01-22 06:58:21 +01:00
|
|
|
if (!item)
|
|
|
|
|
return false;
|
2020-01-16 15:48:32 +01:00
|
|
|
auto mev = static_cast<QMouseEvent *>(event);
|
2020-04-30 15:08:56 +02:00
|
|
|
|
|
|
|
|
if (mev->button() != Qt::LeftButton) // do not react on right click
|
|
|
|
|
return false;
|
|
|
|
|
|
2020-01-16 15:48:32 +01:00
|
|
|
if (index.isValid()) {
|
|
|
|
|
const QPoint pos = mev->pos();
|
2021-11-24 22:19:46 +01:00
|
|
|
if (pos.y() > option.rect.y() + TagsSeparatorY) {
|
2020-01-16 15:48:32 +01:00
|
|
|
//const QStringList tags = idx.data(Tags).toStringList();
|
2021-02-15 10:03:57 +01:00
|
|
|
for (const auto &it : qAsConst(m_currentTagRects)) {
|
2020-01-16 15:48:32 +01:00
|
|
|
if (it.second.contains(pos))
|
|
|
|
|
emit tagClicked(it.first);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
clickAction(item);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return QStyledItemDelegate::editorEvent(event, model, option, index);
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-24 22:19:46 +01:00
|
|
|
QSize ListItemDelegate::sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const
|
|
|
|
|
{
|
|
|
|
|
return {GridItemWidth, GridItemHeight};
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-16 15:48:32 +01:00
|
|
|
void ListItemDelegate::drawPixmapOverlay(const ListItem *, QPainter *,
|
|
|
|
|
const QStyleOptionViewItem &, const QRect &) const
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ListItemDelegate::clickAction(const ListItem *) const
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ListItemDelegate::goon()
|
|
|
|
|
{
|
|
|
|
|
if (m_currentWidget)
|
2022-01-20 16:37:28 +01:00
|
|
|
m_currentWidget->update(m_previousIndex);
|
2020-01-16 15:48:32 +01:00
|
|
|
}
|
|
|
|
|
|
2020-01-09 15:52:47 +01:00
|
|
|
} // namespace Core
|