Cockatrice/cockatrice/src/interface/widgets/dialogs/dlg_edit_tokens.cpp
BruebachL 627c7a83aa
[Icons] Migrate UI glyphs to palette-token SVGs (#7595)
* [Icons] Migrate UI glyphs to palette-token SVGs

Runtime inversion is not a theming system: it rotates hue, breaks duotone art
and mishandles alpha, and a QPixmap snapshot cannot follow a palette change.
Redraw the top-level UI glyph set on a 24x24 grid with `currentColor` as the
paint token, drop Inkscape/Sodipodi cruft, and delete the icons whose metaphor
was duplicated (cogwheel/gear -> settings, scale_balanced -> scales,
update.png -> reload).

Load the glyphs through `loadThemedIcon` so the engine resolves the token from
the palette on every request, and let `themePixmap` use the same resolution for
one-shot pixmaps. `loadColorAdjustedPixmap` is gone; every caller now keeps a
QIcon, which `SettingsButtonWidget::setButtonIcon` accepts instead of a QPixmap.

Content art (mana, counters, countries) keeps its authored colors.

Add a render test covering aspect ratio, palette flips, icon modes and
high-DPI pixmaps.

* [Tests] Keep the icon render test QApplication alive until exit

Creating it as a function-local static tears it down before Qt's library statics
at exit, and libQt6Network then runs QObject destructors against freed memory —
a SEGV the sanitizer leg reports after the tests have already passed. Leak the
application instead, the usual pattern for a test binary.

* [Icons] Render lock and dropdown glyphs through the icon engine

Both generators rendered the sentinel SVG to a fixed height x height pixmap
at DPR 1. The pre-migration path rendered larger and let QIcon scale it, so
on a 2x display the games-list lock (13px) and the banner dropdown arrow
(24px) came back crisp; the new path hands the widget a DPR-1 bitmap that
gets upscaled and blurred.

- The games list asks the themed engine for `icons/lock` directly, so the
  item view paints it at the device pixel ratio and the lock also follows
  HighlightedText on a selected row.
- The dropdown goes through `loadThemedIcon(...).pixmap(size, dpr)`, which
  keeps the glyph's aspect ratio and renders at the requested ratio.
- LockPixmapGenerator has no consumer left, so it and its cache are gone.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-11-04 18:39:41 +01:00

220 lines
7.9 KiB
C++

#include "dlg_edit_tokens.h"
#include "../../theme_icon.h"
#include "../interface/widgets/utility/get_text_with_max.h"
#include "card_database_display_model.h"
#include "libcockatrice/card/card_info.h"
#include "libcockatrice/card/database/card_database_querier.h"
#include "libcockatrice/card/printing/printing_info.h"
#include "libcockatrice/card/set/card_set.h"
#include <QAction>
#include <QChar>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QHash>
#include <QHeaderView>
#include <QItemSelectionModel>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <QModelIndex>
#include <QSharedPointer>
#include <QStringLiteral>
#include <QToolBar>
#include <QTreeView>
#include <QVBoxLayout>
#include <QVariant>
#include <QtGlobal>
#include <libcockatrice/card/database/card_database.h>
#include <libcockatrice/card/database/card_database_manager.h>
#include <libcockatrice/models/database/card_database_model.h>
#include <libcockatrice/models/database/token/token_edit_model.h>
#include <libcockatrice/utility/string_limits.h>
#include <qnamespace.h>
class QWidget;
DlgEditTokens::DlgEditTokens(QWidget *parent) : QDialog(parent), currentCard(nullptr)
{
setObjectName("editTokensDialog");
nameLabel = new QLabel(tr("&Name:"));
nameEdit = new QLineEdit;
nameEdit->setMaxLength(MAX_NAME_LENGTH);
nameEdit->setEnabled(false);
nameLabel->setBuddy(nameEdit);
colorLabel = new QLabel(tr("C&olor:"));
colorEdit = new QComboBox;
colorEdit->addItem(tr("white"), QChar('w'));
colorEdit->addItem(tr("blue"), QChar('u'));
colorEdit->addItem(tr("black"), QChar('b'));
colorEdit->addItem(tr("red"), QChar('r'));
colorEdit->addItem(tr("green"), QChar('g'));
colorEdit->addItem(tr("multicolor"), QChar('m'));
colorEdit->addItem(tr("colorless"), QChar());
colorLabel->setBuddy(colorEdit);
connect(colorEdit, qOverload<int>(&QComboBox::currentIndexChanged), this, &DlgEditTokens::colorChanged);
ptLabel = new QLabel(tr("&P/T:"));
ptEdit = new QLineEdit;
ptEdit->setMaxLength(MAX_NAME_LENGTH);
ptLabel->setBuddy(ptEdit);
connect(ptEdit, &QLineEdit::textChanged, this, &DlgEditTokens::ptChanged);
annotationLabel = new QLabel(tr("&Annotation:"));
annotationEdit = new QLineEdit;
annotationEdit->setMaxLength(MAX_NAME_LENGTH);
annotationLabel->setBuddy(annotationEdit);
connect(annotationEdit, &QLineEdit::textChanged, this, &DlgEditTokens::annotationChanged);
auto *grid = new QGridLayout;
grid->addWidget(nameLabel, 0, 0);
grid->addWidget(nameEdit, 0, 1);
grid->addWidget(colorLabel, 1, 0);
grid->addWidget(colorEdit, 1, 1);
grid->addWidget(ptLabel, 2, 0);
grid->addWidget(ptEdit, 2, 1);
grid->addWidget(annotationLabel, 3, 0);
grid->addWidget(annotationEdit, 3, 1);
QGroupBox *tokenDataGroupBox = new QGroupBox(tr("Token data"));
tokenDataGroupBox->setLayout(grid);
databaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this);
databaseModel->setObjectName("databaseModel");
cardDatabaseDisplayModel = new TokenEditModel(this);
cardDatabaseDisplayModel->setSourceModel(databaseModel);
cardDatabaseDisplayModel->setIsToken(CardDatabaseDisplayModel::ShowTrue);
chooseTokenView = new QTreeView;
chooseTokenView->setModel(cardDatabaseDisplayModel);
chooseTokenView->setUniformRowHeights(true);
chooseTokenView->setRootIsDecorated(false);
chooseTokenView->setAlternatingRowColors(true);
chooseTokenView->setSortingEnabled(true);
chooseTokenView->sortByColumn(0, Qt::AscendingOrder);
chooseTokenView->resizeColumnToContents(0);
chooseTokenView->header()->setStretchLastSection(false);
chooseTokenView->header()->hideSection(1);
chooseTokenView->header()->hideSection(2);
chooseTokenView->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
chooseTokenView->header()->setSectionResizeMode(4, QHeaderView::ResizeToContents);
connect(chooseTokenView->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
&DlgEditTokens::tokenSelectionChanged);
QAction *aAddToken = new QAction(tr("Add token"), this);
aAddToken->setIcon(loadThemedIcon(QStringLiteral("icons/increment")));
connect(aAddToken, &QAction::triggered, this, &DlgEditTokens::actAddToken);
QAction *aRemoveToken = new QAction(tr("Remove token"), this);
aRemoveToken->setIcon(loadThemedIcon(QStringLiteral("icons/decrement")));
connect(aRemoveToken, &QAction::triggered, this, &DlgEditTokens::actRemoveToken);
auto *databaseToolBar = new QToolBar;
databaseToolBar->addAction(aAddToken);
databaseToolBar->addAction(aRemoveToken);
auto *leftVBox = new QVBoxLayout;
leftVBox->addWidget(chooseTokenView);
leftVBox->addWidget(databaseToolBar);
auto *hbox = new QHBoxLayout;
hbox->addLayout(leftVBox);
hbox->addWidget(tokenDataGroupBox);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgEditTokens::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgEditTokens::reject);
auto *mainLayout = new QVBoxLayout;
mainLayout->addLayout(hbox);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
setWindowTitle(tr("Edit custom tokens"));
}
void DlgEditTokens::tokenSelectionChanged(const QModelIndex &current, const QModelIndex & /* previous */)
{
const QModelIndex realIndex = cardDatabaseDisplayModel->mapToSource(current);
if (current.row() >= 0) {
currentCard = databaseModel->getCard(realIndex.row());
} else {
currentCard.clear();
}
if (currentCard) {
nameEdit->setText(currentCard->getName());
const QChar cardColor = currentCard->getColorChar();
colorEdit->setCurrentIndex(colorEdit->findData(cardColor, Qt::UserRole, Qt::MatchFixedString));
ptEdit->setText(currentCard->getPowTough());
annotationEdit->setText(currentCard->getText());
} else {
nameEdit->setText("");
colorEdit->setCurrentIndex(colorEdit->findData(QChar(), Qt::UserRole, Qt::MatchFixedString));
ptEdit->setText("");
annotationEdit->setText("");
}
}
void DlgEditTokens::actAddToken()
{
QString name;
for (;;) {
name = getTextWithMax(this, tr("Add token"), tr("Please enter the name of the token:"));
if (name.isEmpty()) {
return;
}
if (databaseModel->getDatabase()->query()->getCardInfo(name)) {
QMessageBox::critical(this, tr("Error"),
tr("The chosen name conflicts with an existing card or token.\nMake sure to enable "
"the 'Token' set in the \"Manage sets\" dialog to display them correctly."));
} else {
break;
}
}
QString setName = CardSet::TOKENS_SETNAME;
SetToPrintingsMap sets;
sets[setName].append(PrintingInfo(databaseModel->getDatabase()->getSet(setName)));
CardInfo::UiAttributes attributes = {.tableRow = -1};
CardInfoPtr card = CardInfo::newInstance(name, "", true, {}, {}, {}, sets, attributes);
card->setCardType("Token");
databaseModel->getDatabase()->addCard(card);
}
void DlgEditTokens::actRemoveToken()
{
if (currentCard) {
CardInfoPtr cardToRemove = currentCard; // the currentCard property gets modified during db->removeCard()
currentCard.clear();
databaseModel->getDatabase()->removeCard(cardToRemove);
}
}
void DlgEditTokens::colorChanged(int colorIndex)
{
if (currentCard) {
currentCard->setColors(QString(colorEdit->itemData(colorIndex).toChar()));
}
}
void DlgEditTokens::ptChanged(const QString &_pt)
{
if (currentCard) {
currentCard->setPowTough(_pt);
}
}
void DlgEditTokens::annotationChanged(const QString &_annotation)
{
if (currentCard) {
currentCard->setText(_annotation);
}
}