chore: savepoint

This commit is contained in:
Charles Kerr
2025-11-14 22:43:45 -06:00
parent 6a7ad3ff09
commit b58e95910b
4 changed files with 208 additions and 10 deletions

View File

@@ -61,6 +61,8 @@ target_sources(${TR_NAME}-qt
MainWindow.h
MakeDialog.cc
MakeDialog.h
NativeIcon.cc
NativeIcon.h
OptionsDialog.cc
OptionsDialog.h
PathButton.cc

View File

@@ -19,6 +19,7 @@
#include "FilterBarComboBoxDelegate.h"
#include "Filters.h"
#include "IconCache.h"
#include "NativeIcon.h"
#include "Prefs.h"
#include "Torrent.h"
#include "TorrentFilter.h"
@@ -37,6 +38,8 @@ enum
FilterBarComboBox* FilterBar::createActivityCombo()
{
auto* const style = QApplication::style();
auto* c = new FilterBarComboBox{ this };
auto* delegate = new FilterBarComboBoxDelegate{ this, c };
c->setItemDelegate(delegate);
@@ -51,32 +54,58 @@ FilterBarComboBox* FilterBar::createActivityCombo()
FilterBarComboBoxDelegate::setSeparator(model, model->index(1, 0));
auto const& icons = IconCache::get();
#if 0
struct NativeIcon
{
using
public:
struct Spec
{
// https://developer.apple.com/sf-symbols
// https://github.com/andrewtavis/sf-symbols-online
QString sfSymbolName;
row = new QStandardItem{ icons.getThemeIcon(QStringLiteral("system-run")), tr("Active") };
// https://learn.microsoft.com/en-us/windows/apps/design/style/segoe-fluent-icons-font
QString fluentCodepoint;
// https://specifications.freedesktop.org/icon-naming/latest/#names
QString fdoName;
// https://doc.qt.io/qt-6/qstyle.html#StandardPixmap-enum
std::optional<QStyle::StandardPixmap> fallback;
QFont::Weight weight = QFont::Normal;
};
static QIcon get(const Spec& spec, QStyle* style = QApplication::style());
};
#endif
row = new QStandardItem{ NativeIcon::get({ QStringLiteral("play.fill"), QStringLiteral("e768"), QStringLiteral("media-playback-start"), QStyle::SP_MediaPlay }), tr("Active") };
row->setData(FilterMode::SHOW_ACTIVE, ACTIVITY_ROLE);
model->appendRow(row);
row = new QStandardItem{ icons.getThemeIcon(QStringLiteral("go-down")), tr("Downloading") };
row->setData(FilterMode::SHOW_DOWNLOADING, ACTIVITY_ROLE);
model->appendRow(row);
row = new QStandardItem{ icons.getThemeIcon(QStringLiteral("go-up")), tr("Seeding") };
row = new QStandardItem{ NativeIcon::get({ QStringLiteral("chevron.up"), QStringLiteral("e70e"), QStringLiteral("go-up"), QStyle::SP_ArrowUp }), tr("Seeding") };
row->setData(FilterMode::SHOW_SEEDING, ACTIVITY_ROLE);
model->appendRow(row);
row = new QStandardItem{ icons.getThemeIcon(QStringLiteral("media-playback-pause")), tr("Paused") };
row = new QStandardItem{ NativeIcon::get({ QStringLiteral("chevron.down"), QStringLiteral("e70d"), QStringLiteral("go-down"), QStyle::SP_ArrowUp }), tr("Downloading") };
row->setData(FilterMode::SHOW_DOWNLOADING, ACTIVITY_ROLE);
model->appendRow(row);
row = new QStandardItem{ NativeIcon::get({ QStringLiteral("pause.fill"), QStringLiteral("e769"), QStringLiteral("media-playback-pause"), QStyle::SP_MediaPause }), tr("Paused") };
row->setData(FilterMode::SHOW_PAUSED, ACTIVITY_ROLE);
model->appendRow(row);
row = new QStandardItem{ icons.getThemeIcon(QStringLiteral("dialog-ok")), tr("Finished") };
row = new QStandardItem{ tr("Finished") };
row->setData(FilterMode::SHOW_FINISHED, ACTIVITY_ROLE);
model->appendRow(row);
row = new QStandardItem{ icons.getThemeIcon(QStringLiteral("view-refresh")), tr("Verifying") };
row = new QStandardItem{ NativeIcon::get({ QStringLiteral("arrow.clockwise"), QStringLiteral("e72c"), QStringLiteral("view-refresh"), QStyle::SP_BrowserReload }), tr("Verifying") };
row->setData(FilterMode::SHOW_VERIFYING, ACTIVITY_ROLE);
model->appendRow(row);
row = new QStandardItem{ icons.getThemeIcon(QStringLiteral("process-stop")), tr("Error") };
row = new QStandardItem{ NativeIcon::get({ tr("xmark.circle"), QStringLiteral("eb90"), QStringLiteral("dialog-error"), QStyle::SP_MessageBoxWarning }), tr("Error") };
row->setData(FilterMode::SHOW_ERROR, ACTIVITY_ROLE);
model->appendRow(row);

130
qt/NativeIcon.cc Normal file
View File

@@ -0,0 +1,130 @@
// This file Copyright © Mnemosyne LLC.
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
#include "NativeIcon.h"
#include <iostream>
#include <QtGui/QIcon>
#include <QtGui/QPixmap>
#include <QtGui/QPainter>
#include <QtGui/QFont>
#include <QtGui/QGuiApplication>
#include <QtGui/QPalette>
namespace
{
#if defined(Q_OS_WIN)
QChar parseCodepoint(QString const& code)
{
// Supports "E710" or "\uE710"
QString s = code.trimmed();
if (s.startsWith("\\u", Qt::CaseInsensitive))
s = s.mid(2);
bool ok = false;
uint u = s.toUInt(&ok, 16);
return ok ? QChar(u) : QChar();
}
QPixmap makeFluentPixmap(QString const& codepointHex, int const pointSize)
{
if (codepointHex.isEmpty())
return {};
const QChar glyph = parseCodepoint(codepointHex);
if (glyph.isNull())
return {};
// Prefer Segoe Fluent Icons (Win11), fall back to Segoe MDL2 Assets (Win10)
QFont font;
font.setPointSize(pointSize);
font.setStyleStrategy(QFont::PreferDefault);
// Try Fluent first
font.setFamily("Segoe Fluent Icons");
if (!QFontInfo(font).exactMatch()) {
font.setFamily("Segoe MDL2 Assets");
}
auto const dpr = qApp->primaryScreen()->devicePixelRatio();
auto const px = qRound(pointSize * dpr) + 8; // padding
QPixmap pm{px, px};
pm.setDevicePixelRatio(dpr);
pm.fill(Qt::transparent);
auto const fg = qApp->palette().color(QPalette::ButtonText);
QPainter p{&pm};
p.setRenderHint(QPainter::Antialiasing, true);
p.setPen(Qt::NoPen);
p.setBrush(fg);
p.setFont(font);
// Center the glyph
QRectF r(0, 0, pm.width() / dpr, pm.height() / dpr);
p.drawText(r, Qt::AlignCenter, QString(glyph));
p.end();
return QIcon{pm};
}
# endif // Q_OS_WIN
} // namespace
QIcon NativeIcon::get(Spec const& spec, QStyle* style)
{
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
auto constexpr auto IconMetrics = std::array<QStyle::PixelMetric, 7U>{{
QStyle::PM_ButtonIconSize
QStyle::PM_LargeIconSize,
QStyle::PM_ListViewIconSize,
QStyle::PM_MessageBoxIconSize,
QStyle::PM_SmallIconSize,
QStyle::PM_TabBarIconSize,
QStyle::PM_ToolBarIconSize,
}};
auto dipSizes = std::set<QSize>{};
for (auto const pm : IconMetrics) {
auto const dip = style->pixelMetric(pm);
dipSizes.emplace(dip, dip);
}
#endif
#if defined(Q_OS_MAC)
// TODO: try sfSymbolName if on macOS
// https://stackoverflow.com/questions/74747658/how-to-convert-a-cgimageref-to-a-qpixmap-in-qt-6
#endif
#if defined(Q_OS_WIN)
if (!spec.fluentCodepoint.isEmpty()) {
auto icon = QIcon{};
for (auto const dipSize : dipSizes)
if (auto pixmap = makeFluentIcon(spec.fluentCodepoint, dipSize); !pixmap.isNull())
icon.addPixmap(pixmap);
if (!icon.isNull())
return icon;
}
#endif
if (!spec.fdoName.isEmpty()) {
if (auto icon = QIcon::fromTheme(spec.fdoName + QStringLiteral("-symbolic")); !icon.isNull()) {
std::cerr << "using fdo" << std::endl;
return icon;
} else {
std::cerr << "QIcon::fromTheme(" << qPrintable(spec.fdoName) << ") returned empty" << std::endl;
}
}
if (spec.fallback) {
if (auto icon = style->standardIcon(*spec.fallback); !icon.isNull()) {
std::cerr << "Qt Standard Icon" << std::endl;
return icon;
}
}
std::cerr << "no match" << std::endl;
return {};
}

37
qt/NativeIcon.h Normal file
View File

@@ -0,0 +1,37 @@
// This file Copyright © Mnemosyne LLC.
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
// or any future license endorsed by Mnemosyne LLC.
// License text can be found in the licenses/ folder.
#pragma once
#include <optional>
#include <QApplication>
#include <QFont>
#include <QString>
#include <QStyle>
struct NativeIcon
{
public:
struct Spec
{
// https://developer.apple.com/sf-symbols
// https://github.com/andrewtavis/sf-symbols-online
QString sfSymbolName;
// https://learn.microsoft.com/en-us/windows/apps/design/style/segoe-fluent-icons-font
QString fluentCodepoint;
// https://specifications.freedesktop.org/icon-naming/latest/#names
QString fdoName;
// https://doc.qt.io/qt-6/qstyle.html#StandardPixmap-enum
std::optional<QStyle::StandardPixmap> fallback;
QFont::Weight weight = QFont::Normal;
};
static QIcon get(const Spec& spec, QStyle* style = QApplication::style());
};