mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-28 00:42:19 -07:00
Hide widgets instead of destroying, go back to signals, rename for consistency.
Took 13 minutes Took 4 seconds Took 26 minutes Took 5 seconds # Commit time for manual adjustment: # Took 9 minutes
This commit is contained in:
parent
7bbee72ec9
commit
b6a7f6d52a
12 changed files with 305 additions and 135 deletions
|
|
@ -6,7 +6,7 @@
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
|
|
||||||
DeckPreviewColorIdentityFilterWidget::DeckPreviewColorIdentityFilterWidget(VisualDeckStorageWidget *parent)
|
DeckPreviewColorIdentityFilterWidget::DeckPreviewColorIdentityFilterWidget(VisualDeckStorageWidget *parent)
|
||||||
: QWidget(parent), layout(new QHBoxLayout(this)), visualDeckStorageWidget(parent)
|
: QWidget(parent), layout(new QHBoxLayout(this))
|
||||||
{
|
{
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
layout->setSpacing(5);
|
layout->setSpacing(5);
|
||||||
|
|
@ -55,9 +55,9 @@ void DeckPreviewColorIdentityFilterWidget::retranslateUi()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Pushes the current filter state to the proxy model.
|
* @brief The colors that are currently toggled on.
|
||||||
*/
|
*/
|
||||||
void DeckPreviewColorIdentityFilterWidget::applyFilter()
|
QSet<QChar> DeckPreviewColorIdentityFilterWidget::getActiveColors() const
|
||||||
{
|
{
|
||||||
QSet<QChar> activeColorSet;
|
QSet<QChar> activeColorSet;
|
||||||
for (auto it = activeColors.constBegin(); it != activeColors.constEnd(); ++it) {
|
for (auto it = activeColors.constBegin(); it != activeColors.constEnd(); ++it) {
|
||||||
|
|
@ -65,15 +65,13 @@ void DeckPreviewColorIdentityFilterWidget::applyFilter()
|
||||||
activeColorSet.insert(it.key());
|
activeColorSet.insert(it.key());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return activeColorSet;
|
||||||
// The VDS owns the proxy model, which exists for the widget's whole lifetime.
|
|
||||||
visualDeckStorageWidget->proxyModel()->setColorFilter(filterMode, activeColorSet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckPreviewColorIdentityFilterWidget::handleColorToggled(QChar color, bool active)
|
void DeckPreviewColorIdentityFilterWidget::handleColorToggled(QChar color, bool active)
|
||||||
{
|
{
|
||||||
activeColors[color] = active;
|
activeColors[color] = active;
|
||||||
applyFilter();
|
emit activeColorsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckPreviewColorIdentityFilterWidget::updateFilterMode()
|
void DeckPreviewColorIdentityFilterWidget::updateFilterMode()
|
||||||
|
|
@ -92,5 +90,5 @@ void DeckPreviewColorIdentityFilterWidget::updateFilterMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
retranslateUi(); // Update the button text
|
retranslateUi(); // Update the button text
|
||||||
applyFilter();
|
emit filterModeChanged(filterMode);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
#include <QSet>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
class VisualDeckStorageWidget;
|
class VisualDeckStorageWidget;
|
||||||
|
|
@ -23,18 +24,40 @@ public:
|
||||||
explicit DeckPreviewColorIdentityFilterWidget(VisualDeckStorageWidget *parent);
|
explicit DeckPreviewColorIdentityFilterWidget(VisualDeckStorageWidget *parent);
|
||||||
void retranslateUi();
|
void retranslateUi();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The currently active color identity filter mode.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] VisualDeckStorageSortFilterProxyModel::FilterMode getFilterMode() const
|
||||||
|
{
|
||||||
|
return filterMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The colors that are currently toggled on.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QSet<QChar> getActiveColors() const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
/**
|
||||||
|
* Emitted when the set of active colors changed due to user interaction.
|
||||||
|
*/
|
||||||
|
void activeColorsChanged();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when the user cycles the color identity filter mode.
|
||||||
|
* @param mode The new filter mode.
|
||||||
|
*/
|
||||||
|
void filterModeChanged(VisualDeckStorageSortFilterProxyModel::FilterMode mode);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void handleColorToggled(QChar color, bool active);
|
void handleColorToggled(QChar color, bool active);
|
||||||
void updateFilterMode();
|
void updateFilterMode();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void applyFilter();
|
|
||||||
|
|
||||||
QHBoxLayout *layout;
|
QHBoxLayout *layout;
|
||||||
QPushButton *toggleButton;
|
QPushButton *toggleButton;
|
||||||
QMap<QChar, bool> activeColors;
|
QMap<QChar, bool> activeColors;
|
||||||
VisualDeckStorageSortFilterProxyModel::FilterMode filterMode = VisualDeckStorageSortFilterProxyModel::Includes;
|
VisualDeckStorageSortFilterProxyModel::FilterMode filterMode = VisualDeckStorageSortFilterProxyModel::Includes;
|
||||||
VisualDeckStorageWidget *visualDeckStorageWidget; ///< The VDS owning the proxy model.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DECK_PREVIEW_COLOR_IDENTITY_FILTER_WIDGET_H
|
#endif // DECK_PREVIEW_COLOR_IDENTITY_FILTER_WIDGET_H
|
||||||
|
|
|
||||||
|
|
@ -52,12 +52,12 @@ void DeckPreviewDeckTagsDisplayWidget::refreshTags()
|
||||||
|
|
||||||
void DeckPreviewDeckTagsDisplayWidget::setKnownTagsProvider(const std::function<QStringList()> &provider)
|
void DeckPreviewDeckTagsDisplayWidget::setKnownTagsProvider(const std::function<QStringList()> &provider)
|
||||||
{
|
{
|
||||||
knownTagsProvider_ = provider;
|
knownTagsProvider = provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeckPreviewDeckTagsDisplayWidget::setConversionPromptHandler(const std::function<bool()> &handler)
|
void DeckPreviewDeckTagsDisplayWidget::setConversionPromptHandler(const std::function<bool()> &handler)
|
||||||
{
|
{
|
||||||
conversionPromptHandler_ = handler;
|
conversionPromptHandler = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -99,11 +99,11 @@ static QStringList findAllKnownTags()
|
||||||
void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg()
|
void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg()
|
||||||
{
|
{
|
||||||
// The deck editor path has no conversion prompt; the VDS path registers one.
|
// The deck editor path has no conversion prompt; the VDS path registers one.
|
||||||
if (conversionPromptHandler_ && !conversionPromptHandler_()) {
|
if (conversionPromptHandler && !conversionPromptHandler()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QStringList knownTags = knownTagsProvider_ ? knownTagsProvider_() : findAllKnownTags();
|
const QStringList knownTags = knownTagsProvider ? knownTagsProvider() : findAllKnownTags();
|
||||||
execTagDialog(knownTags);
|
execTagDialog(knownTags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ class DeckPreviewDeckTagsDisplayWidget : public QWidget
|
||||||
|
|
||||||
QStringList currentTags;
|
QStringList currentTags;
|
||||||
FlowWidget *flowWidget;
|
FlowWidget *flowWidget;
|
||||||
std::function<QStringList()> knownTagsProvider_;
|
std::function<QStringList()> knownTagsProvider;
|
||||||
std::function<bool()> conversionPromptHandler_;
|
std::function<bool()> conversionPromptHandler;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, const QStringList &_tags = {});
|
explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, const QStringList &_tags = {});
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,9 @@
|
||||||
|
|
||||||
DeckPreviewWidget::DeckPreviewWidget(QWidget *_parent,
|
DeckPreviewWidget::DeckPreviewWidget(QWidget *_parent,
|
||||||
VisualDeckStorageWidget *_visualDeckStorageWidget,
|
VisualDeckStorageWidget *_visualDeckStorageWidget,
|
||||||
VisualDeckStorageModel *model,
|
VisualDeckStorageModel *_model,
|
||||||
const QString &_filePath)
|
const QString &_filePath)
|
||||||
: QWidget(_parent), visualDeckStorageWidget(_visualDeckStorageWidget), model_(model), filePath(_filePath)
|
: QWidget(_parent), visualDeckStorageWidget(_visualDeckStorageWidget), model(_model), filePath(_filePath)
|
||||||
{
|
{
|
||||||
layout = new QVBoxLayout(this);
|
layout = new QVBoxLayout(this);
|
||||||
setLayout(layout);
|
setLayout(layout);
|
||||||
|
|
@ -83,7 +83,16 @@ DeckPreviewWidget::DeckPreviewWidget(QWidget *_parent,
|
||||||
layout->addWidget(bannerCardLabel);
|
layout->addWidget(bannerCardLabel);
|
||||||
layout->addWidget(bannerCardComboBox);
|
layout->addWidget(bannerCardComboBox);
|
||||||
|
|
||||||
connect(model_, &QAbstractItemModel::dataChanged, this, &DeckPreviewWidget::syncFromModel);
|
// Only re-sync when this widget's own row changed. Without the row check, every
|
||||||
|
// finished deck load would trigger a full resync (card db lookup + combo rebuild)
|
||||||
|
// in every preview widget.
|
||||||
|
connect(model, &QAbstractItemModel::dataChanged, this,
|
||||||
|
[this](const QModelIndex &topLeft, const QModelIndex &bottomRight) {
|
||||||
|
const int r = row();
|
||||||
|
if (r >= topLeft.row() && r <= bottomRight.row()) {
|
||||||
|
syncFromModel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
retranslateUi();
|
retranslateUi();
|
||||||
syncFromModel();
|
syncFromModel();
|
||||||
|
|
@ -119,7 +128,7 @@ void DeckPreviewWidget::enterEvent(QEnterEvent *event)
|
||||||
*/
|
*/
|
||||||
int DeckPreviewWidget::row() const
|
int DeckPreviewWidget::row() const
|
||||||
{
|
{
|
||||||
return model_->rowForFilePath(filePath);
|
return model->rowForFilePath(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -131,7 +140,7 @@ QString DeckPreviewWidget::getDisplayName() const
|
||||||
if (r == -1) {
|
if (r == -1) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return model_->dataForRow(r).displayName;
|
return model->dataForRow(r).displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -140,11 +149,11 @@ QString DeckPreviewWidget::getDisplayName() const
|
||||||
void DeckPreviewWidget::reloadIfModified()
|
void DeckPreviewWidget::reloadIfModified()
|
||||||
{
|
{
|
||||||
const int r = row();
|
const int r = row();
|
||||||
if (r == -1 || !model_->dataForRow(r).loadSucceeded) {
|
if (r == -1 || !model->dataForRow(r).loadSucceeded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
model_->reloadIfModified(r);
|
model->reloadIfModified(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -157,7 +166,7 @@ void DeckPreviewWidget::syncFromModel()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DeckPreviewData &data = model_->dataForRow(r);
|
const DeckPreviewData &data = model->dataForRow(r);
|
||||||
filePath = data.filePath;
|
filePath = data.filePath;
|
||||||
|
|
||||||
const CardRef bannerCardRef = data.deck.deckList.getBannerCard();
|
const CardRef bannerCardRef = data.deck.deckList.getBannerCard();
|
||||||
|
|
@ -246,7 +255,7 @@ void DeckPreviewWidget::updateBannerCardComboBox(const QString ¤tText)
|
||||||
|
|
||||||
const int r = row();
|
const int r = row();
|
||||||
if (r != -1) {
|
if (r != -1) {
|
||||||
const DeckList &deckList = model_->dataForRow(r).deck.deckList;
|
const DeckList &deckList = model->dataForRow(r).deck.deckList;
|
||||||
const QList<const DecklistCardNode *> cardsInDeck = deckList.getCardNodes();
|
const QList<const DecklistCardNode *> cardsInDeck = deckList.getCardNodes();
|
||||||
|
|
||||||
for (auto currentCard : cardsInDeck) {
|
for (auto currentCard : cardsInDeck) {
|
||||||
|
|
@ -265,16 +274,16 @@ void DeckPreviewWidget::updateBannerCardComboBox(const QString ¤tText)
|
||||||
|
|
||||||
// This is *slightly* more performant than using addItem in a loop.
|
// This is *slightly* more performant than using addItem in a loop.
|
||||||
|
|
||||||
QStandardItemModel *model = new QStandardItemModel(pairList.size(), 1, bannerCardComboBox);
|
QStandardItemModel *comboModel = new QStandardItemModel(pairList.size(), 1, bannerCardComboBox);
|
||||||
|
|
||||||
int row = 0;
|
int row = 0;
|
||||||
for (const auto &pair : pairList) {
|
for (const auto &pair : pairList) {
|
||||||
QStandardItem *item = new QStandardItem(pair.first);
|
QStandardItem *item = new QStandardItem(pair.first);
|
||||||
item->setData(QVariant::fromValue(pair), Qt::UserRole);
|
item->setData(QVariant::fromValue(pair), Qt::UserRole);
|
||||||
model->setItem(row++, 0, item);
|
comboModel->setItem(row++, 0, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
bannerCardComboBox->setModel(model);
|
bannerCardComboBox->setModel(comboModel);
|
||||||
|
|
||||||
// Try to restore the previous selection by finding the currentText
|
// Try to restore the previous selection by finding the currentText
|
||||||
int restoredIndex = bannerCardComboBox->findText(currentText);
|
int restoredIndex = bannerCardComboBox->findText(currentText);
|
||||||
|
|
@ -283,7 +292,7 @@ void DeckPreviewWidget::updateBannerCardComboBox(const QString ¤tText)
|
||||||
} else {
|
} else {
|
||||||
// Add a placeholder "-" and set it as the current selection
|
// Add a placeholder "-" and set it as the current selection
|
||||||
const QString currentBannerCardName =
|
const QString currentBannerCardName =
|
||||||
r == -1 ? QString() : model_->dataForRow(r).deck.deckList.getBannerCard().name;
|
r == -1 ? QString() : model->dataForRow(r).deck.deckList.getBannerCard().name;
|
||||||
int bannerIndex = bannerCardComboBox->findText(currentBannerCardName);
|
int bannerIndex = bannerCardComboBox->findText(currentBannerCardName);
|
||||||
if (bannerIndex != -1) {
|
if (bannerIndex != -1) {
|
||||||
bannerCardComboBox->setCurrentIndex(bannerIndex);
|
bannerCardComboBox->setCurrentIndex(bannerIndex);
|
||||||
|
|
@ -306,7 +315,7 @@ void DeckPreviewWidget::setBannerCard(int /* changedIndex */)
|
||||||
if (r == -1) {
|
if (r == -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
model_->setBannerCard(r, cardRef);
|
model->setBannerCard(r, cardRef);
|
||||||
bannerCardDisplayWidget->setCard(CardDatabaseManager::query()->getCard(cardRef));
|
bannerCardDisplayWidget->setCard(CardDatabaseManager::query()->getCard(cardRef));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -330,7 +339,7 @@ void DeckPreviewWidget::setTags(const QStringList &tags)
|
||||||
{
|
{
|
||||||
const int r = row();
|
const int r = row();
|
||||||
if (r != -1) {
|
if (r != -1) {
|
||||||
model_->setTags(r, tags);
|
model->setTags(r, tags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -343,7 +352,7 @@ QMenu *DeckPreviewWidget::createRightClickMenu()
|
||||||
|
|
||||||
connect(menu->addAction(tr("Open in deck editor")), &QAction::triggered, this, [this, r] {
|
connect(menu->addAction(tr("Open in deck editor")), &QAction::triggered, this, [this, r] {
|
||||||
if (r != -1) {
|
if (r != -1) {
|
||||||
emit openDeckEditor(model_->deckForRow(r));
|
emit openDeckEditor(model->deckForRow(r));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -360,22 +369,22 @@ QMenu *DeckPreviewWidget::createRightClickMenu()
|
||||||
|
|
||||||
connect(saveToClipboardMenu->addAction(tr("Annotated")), &QAction::triggered, this, [this, r] {
|
connect(saveToClipboardMenu->addAction(tr("Annotated")), &QAction::triggered, this, [this, r] {
|
||||||
if (r != -1) {
|
if (r != -1) {
|
||||||
DeckLoader::saveToClipboard(model_->dataForRow(r).deck.deckList, true, true);
|
DeckLoader::saveToClipboard(model->dataForRow(r).deck.deckList, true, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(saveToClipboardMenu->addAction(tr("Annotated (No set info)")), &QAction::triggered, this, [this, r] {
|
connect(saveToClipboardMenu->addAction(tr("Annotated (No set info)")), &QAction::triggered, this, [this, r] {
|
||||||
if (r != -1) {
|
if (r != -1) {
|
||||||
DeckLoader::saveToClipboard(model_->dataForRow(r).deck.deckList, true, false);
|
DeckLoader::saveToClipboard(model->dataForRow(r).deck.deckList, true, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(saveToClipboardMenu->addAction(tr("Not Annotated")), &QAction::triggered, this, [this, r] {
|
connect(saveToClipboardMenu->addAction(tr("Not Annotated")), &QAction::triggered, this, [this, r] {
|
||||||
if (r != -1) {
|
if (r != -1) {
|
||||||
DeckLoader::saveToClipboard(model_->dataForRow(r).deck.deckList, false, true);
|
DeckLoader::saveToClipboard(model->dataForRow(r).deck.deckList, false, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(saveToClipboardMenu->addAction(tr("Not Annotated (No set info)")), &QAction::triggered, this, [this, r] {
|
connect(saveToClipboardMenu->addAction(tr("Not Annotated (No set info)")), &QAction::triggered, this, [this, r] {
|
||||||
if (r != -1) {
|
if (r != -1) {
|
||||||
DeckLoader::saveToClipboard(model_->dataForRow(r).deck.deckList, false, false);
|
DeckLoader::saveToClipboard(model->dataForRow(r).deck.deckList, false, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -418,7 +427,7 @@ void DeckPreviewWidget::actRenameDeck()
|
||||||
}
|
}
|
||||||
|
|
||||||
// read input
|
// read input
|
||||||
const QString oldName = model_->dataForRow(r).deckName;
|
const QString oldName = model->dataForRow(r).deckName;
|
||||||
|
|
||||||
bool ok;
|
bool ok;
|
||||||
QString newName = QInputDialog::getText(this, tr("Rename deck"), tr("New name:"), QLineEdit::Normal, oldName, &ok);
|
QString newName = QInputDialog::getText(this, tr("Rename deck"), tr("New name:"), QLineEdit::Normal, oldName, &ok);
|
||||||
|
|
@ -427,7 +436,7 @@ void DeckPreviewWidget::actRenameDeck()
|
||||||
}
|
}
|
||||||
|
|
||||||
// write change
|
// write change
|
||||||
model_->renameDeck(r, newName);
|
model->renameDeck(r, newName);
|
||||||
|
|
||||||
// The banner card text updates via the model's dataChanged signal.
|
// The banner card text updates via the model's dataChanged signal.
|
||||||
}
|
}
|
||||||
|
|
@ -450,7 +459,7 @@ void DeckPreviewWidget::actRenameFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
// write change
|
// write change
|
||||||
if (!model_->renameFile(r, newName)) {
|
if (!model->renameFile(r, newName)) {
|
||||||
QMessageBox::critical(this, tr("Error"), tr("Rename failed"));
|
QMessageBox::critical(this, tr("Error"), tr("Rename failed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -472,7 +481,7 @@ void DeckPreviewWidget::actDeleteFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
// write change
|
// write change
|
||||||
if (!model_->deleteFile(r)) {
|
if (!model->deleteFile(r)) {
|
||||||
QMessageBox::critical(this, tr("Error"), tr("Delete failed"));
|
QMessageBox::critical(this, tr("Error"), tr("Delete failed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -515,7 +524,7 @@ bool DeckPreviewWidget::promptFileConversionIfRequired()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
model_->convertToCockatriceFormat(row());
|
model->convertToCockatriceFormat(row());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -534,7 +543,7 @@ bool DeckPreviewWidget::promptFileConversionIfRequired()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
model_->convertToCockatriceFormat(row());
|
model->convertToCockatriceFormat(row());
|
||||||
|
|
||||||
if (conversionDialog.dontAskAgain()) {
|
if (conversionDialog.dontAskAgain()) {
|
||||||
SettingsCache::instance().visualDeckStorage().setVisualDeckStoragePromptForConversion(false);
|
SettingsCache::instance().visualDeckStorage().setVisualDeckStoragePromptForConversion(false);
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ class DeckPreviewWidget final : public QWidget
|
||||||
public:
|
public:
|
||||||
explicit DeckPreviewWidget(QWidget *parent,
|
explicit DeckPreviewWidget(QWidget *parent,
|
||||||
VisualDeckStorageWidget *_visualDeckStorageWidget,
|
VisualDeckStorageWidget *_visualDeckStorageWidget,
|
||||||
VisualDeckStorageModel *model,
|
VisualDeckStorageModel *_model,
|
||||||
const QString &_filePath);
|
const QString &_filePath);
|
||||||
void retranslateUi();
|
void retranslateUi();
|
||||||
|
|
||||||
|
|
@ -83,7 +83,7 @@ private:
|
||||||
void actDeleteFile();
|
void actDeleteFile();
|
||||||
|
|
||||||
VisualDeckStorageWidget *visualDeckStorageWidget;
|
VisualDeckStorageWidget *visualDeckStorageWidget;
|
||||||
VisualDeckStorageModel *model_;
|
VisualDeckStorageModel *model;
|
||||||
QString filePath;
|
QString filePath;
|
||||||
QVBoxLayout *layout;
|
QVBoxLayout *layout;
|
||||||
ColorIdentityWidget *colorIdentityWidget;
|
ColorIdentityWidget *colorIdentityWidget;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
#include "visual_deck_storage_sort_filter_proxy_model.h"
|
#include "visual_deck_storage_sort_filter_proxy_model.h"
|
||||||
#include "visual_deck_storage_widget.h"
|
#include "visual_deck_storage_widget.h"
|
||||||
|
|
||||||
|
#include <QElapsedTimer>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
@ -59,55 +60,103 @@ VisualDeckStorageFolderDisplayWidget::VisualDeckStorageFolderDisplayWidget(
|
||||||
reconcileTimer->setInterval(150);
|
reconcileTimer->setInterval(150);
|
||||||
connect(reconcileTimer, &QTimer::timeout, this, &VisualDeckStorageFolderDisplayWidget::reconcile);
|
connect(reconcileTimer, &QTimer::timeout, this, &VisualDeckStorageFolderDisplayWidget::reconcile);
|
||||||
|
|
||||||
reconcile();
|
// Building the whole folder subtree synchronously here would stall the ui thread on large
|
||||||
|
// collections, so the first reconcile runs as a chunked pass on later event loop turns.
|
||||||
|
scheduleReconcile();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VisualDeckStorageFolderDisplayWidget::scheduleReconcile()
|
void VisualDeckStorageFolderDisplayWidget::scheduleReconcile()
|
||||||
{
|
{
|
||||||
|
if (deckPassActive) {
|
||||||
|
// The active pass may be scanning stale model state, so restart it from a clean
|
||||||
|
// slate once the current chunk yields.
|
||||||
|
deckPassRestartRequested = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
reconcileTimer->start();
|
reconcileTimer->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Re-reads the proxy and rebuilds the deck previews and subfolder widgets to match.
|
* @brief Starts a new chunked scan of the source model, yielding to the event loop between chunks.
|
||||||
*/
|
*/
|
||||||
void VisualDeckStorageFolderDisplayWidget::reconcile()
|
void VisualDeckStorageFolderDisplayWidget::reconcile()
|
||||||
{
|
{
|
||||||
reconcileDecks();
|
beginDeckPass();
|
||||||
createSubFolderWidgets();
|
|
||||||
refreshVisibility();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void VisualDeckStorageFolderDisplayWidget::beginDeckPass()
|
||||||
* @brief Reconciles the deck preview widgets in the flow layout with the accepted proxy rows.
|
{
|
||||||
*
|
deckPassActive = true;
|
||||||
* Decks are keyed by file path so that already-created preview widgets are reused
|
deckPassRestartRequested = false;
|
||||||
* (their banner combo box, tag editor state, etc. is preserved) whenever possible.
|
deckPassRow = 0;
|
||||||
*/
|
visibleDeckCount = 0;
|
||||||
void VisualDeckStorageFolderDisplayWidget::reconcileDecks()
|
deckPassPresentPaths.clear();
|
||||||
|
|
||||||
|
continueDeckPass();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualDeckStorageFolderDisplayWidget::continueDeckPass()
|
||||||
|
{
|
||||||
|
if (!deckPassActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QElapsedTimer passTimer;
|
||||||
|
passTimer.start();
|
||||||
|
|
||||||
|
auto *proxy = visualDeckStorageWidget->proxyModel();
|
||||||
|
auto *model = visualDeckStorageWidget->model();
|
||||||
|
const int sourceRowCount = model->rowCount();
|
||||||
|
|
||||||
|
// Scan rows of this folder, creating missing previews, until the time budget for this
|
||||||
|
// event loop turn runs out. The rest continues on the next turn.
|
||||||
|
while (deckPassRow < sourceRowCount) {
|
||||||
|
const int row = deckPassRow++;
|
||||||
|
const DeckPreviewData &data = model->dataForRow(row);
|
||||||
|
if (showFolders && data.folderPath != folderPath) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
deckPassPresentPaths.insert(data.filePath);
|
||||||
|
|
||||||
|
DeckPreviewWidget *deckPreviewWidget = deckWidgets.value(data.filePath, nullptr);
|
||||||
|
if (!deckPreviewWidget) {
|
||||||
|
deckPreviewWidget = createDeckPreviewWidget(data.filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool accepted = proxy->mapFromSource(model->index(row, 0)).isValid();
|
||||||
|
if (accepted == deckPreviewWidget->isHidden()) {
|
||||||
|
deckPreviewWidget->setVisible(accepted);
|
||||||
|
}
|
||||||
|
if (accepted) {
|
||||||
|
++visibleDeckCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (passTimer.elapsed() >= DECK_PASS_TIME_BUDGET_MS) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deckPassRestartRequested) {
|
||||||
|
beginDeckPass();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deckPassRow < sourceRowCount) {
|
||||||
|
QMetaObject::invokeMethod(this, &VisualDeckStorageFolderDisplayWidget::continueDeckPass, Qt::QueuedConnection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
finishDeckPass();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisualDeckStorageFolderDisplayWidget::finishDeckPass()
|
||||||
{
|
{
|
||||||
auto *proxy = visualDeckStorageWidget->proxyModel();
|
auto *proxy = visualDeckStorageWidget->proxyModel();
|
||||||
auto *model = visualDeckStorageWidget->model();
|
auto *model = visualDeckStorageWidget->model();
|
||||||
|
|
||||||
// Collect the file paths this folder should display, in proxy (sorted) order.
|
// Drop previews of decks that no longer exist in the source model.
|
||||||
QStringList desiredOrder;
|
|
||||||
for (int proxyRow = 0; proxyRow < proxy->rowCount(); ++proxyRow) {
|
|
||||||
const QModelIndex sourceIndex = proxy->mapToSource(proxy->index(proxyRow, 0));
|
|
||||||
if (!sourceIndex.isValid()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DeckPreviewData &data = model->dataForRow(sourceIndex.row());
|
|
||||||
if (showFolders && data.folderPath != folderPath) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
desiredOrder.append(data.filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
const QSet<QString> desiredSet(desiredOrder.cbegin(), desiredOrder.cend());
|
|
||||||
|
|
||||||
// Drop previews of decks that no longer match.
|
|
||||||
for (auto it = deckWidgets.begin(); it != deckWidgets.end();) {
|
for (auto it = deckWidgets.begin(); it != deckWidgets.end();) {
|
||||||
if (!desiredSet.contains(it.key())) {
|
if (!deckPassPresentPaths.contains(it.key())) {
|
||||||
flowWidget->removeWidget(it.value());
|
flowWidget->removeWidget(it.value());
|
||||||
it.value()->deleteLater();
|
it.value()->deleteLater();
|
||||||
it = deckWidgets.erase(it);
|
it = deckWidgets.erase(it);
|
||||||
|
|
@ -116,37 +165,69 @@ void VisualDeckStorageFolderDisplayWidget::reconcileDecks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create or reuse the preview widgets in the desired order.
|
// Order the flow layout like the proxy sorts its rows.
|
||||||
QList<DeckPreviewWidget *> orderedWidgets;
|
QStringList orderedFilePaths;
|
||||||
orderedWidgets.reserve(desiredOrder.size());
|
orderedFilePaths.reserve(proxy->rowCount());
|
||||||
for (const QString &filePath : desiredOrder) {
|
for (int proxyRow = 0; proxyRow < proxy->rowCount(); ++proxyRow) {
|
||||||
DeckPreviewWidget *deckPreviewWidget = deckWidgets.value(filePath, nullptr);
|
const QModelIndex sourceIndex = proxy->mapToSource(proxy->index(proxyRow, 0));
|
||||||
if (!deckPreviewWidget) {
|
if (!sourceIndex.isValid()) {
|
||||||
deckPreviewWidget = new DeckPreviewWidget(flowWidget, visualDeckStorageWidget, model, filePath);
|
continue;
|
||||||
|
}
|
||||||
|
const QString &filePath = model->dataForRow(sourceIndex.row()).filePath;
|
||||||
|
if (deckWidgets.contains(filePath)) {
|
||||||
|
orderedFilePaths.append(filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Hidden previews are appended so they remain part of the layout and take their
|
||||||
|
// sorted place once a filter lets them through again.
|
||||||
|
QSet<QString> orderedSet(orderedFilePaths.cbegin(), orderedFilePaths.cend());
|
||||||
|
for (auto it = deckWidgets.constBegin(); it != deckWidgets.constEnd(); ++it) {
|
||||||
|
if (!orderedSet.contains(it.key())) {
|
||||||
|
orderedSet.insert(it.key());
|
||||||
|
orderedFilePaths.append(it.key());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-add all widgets so the flow layout order matches the proxy order. Skipped when the
|
||||||
|
// order is unchanged so that data-only updates don't invalidate the flow layout.
|
||||||
|
if (orderedFilePaths != lastOrderedFilePaths) {
|
||||||
|
for (const QString &filePath : orderedFilePaths) {
|
||||||
|
flowWidget->removeWidget(deckWidgets.value(filePath));
|
||||||
|
}
|
||||||
|
for (const QString &filePath : orderedFilePaths) {
|
||||||
|
flowWidget->addWidget(deckWidgets.value(filePath));
|
||||||
|
}
|
||||||
|
lastOrderedFilePaths = orderedFilePaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
createSubFolderWidgets();
|
||||||
|
|
||||||
|
// Mark completion before evaluating visibility so this pass's own numbers decide whether
|
||||||
|
// the folder has content. The flag only guards evaluations made *during* a build.
|
||||||
|
deckPassActive = false;
|
||||||
|
initialPassCompleted = true;
|
||||||
|
|
||||||
|
refreshVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a deck preview widget and wires it up to the storage widget.
|
||||||
|
*
|
||||||
|
* @param filePath The absolute path of the deck file to preview.
|
||||||
|
*/
|
||||||
|
DeckPreviewWidget *VisualDeckStorageFolderDisplayWidget::createDeckPreviewWidget(const QString &filePath)
|
||||||
|
{
|
||||||
|
auto *deckPreviewWidget =
|
||||||
|
new DeckPreviewWidget(flowWidget, visualDeckStorageWidget, visualDeckStorageWidget->model(), filePath);
|
||||||
connect(deckPreviewWidget, &DeckPreviewWidget::deckLoadRequested, visualDeckStorageWidget,
|
connect(deckPreviewWidget, &DeckPreviewWidget::deckLoadRequested, visualDeckStorageWidget,
|
||||||
&VisualDeckStorageWidget::deckLoadRequested);
|
&VisualDeckStorageWidget::deckLoadRequested);
|
||||||
connect(deckPreviewWidget, &DeckPreviewWidget::openDeckEditor, visualDeckStorageWidget,
|
connect(deckPreviewWidget, &DeckPreviewWidget::openDeckEditor, visualDeckStorageWidget,
|
||||||
&VisualDeckStorageWidget::openDeckEditor);
|
&VisualDeckStorageWidget::openDeckEditor);
|
||||||
connect(visualDeckStorageWidget->settings(), &VisualDeckStorageQuickSettingsWidget::cardSizeChanged,
|
connect(visualDeckStorageWidget->settings(), &VisualDeckStorageQuickSettingsWidget::cardSizeChanged,
|
||||||
deckPreviewWidget->bannerCardDisplayWidget, &CardInfoPictureWidget::setScaleFactor);
|
deckPreviewWidget->bannerCardDisplayWidget, &CardInfoPictureWidget::setScaleFactor);
|
||||||
deckPreviewWidget->bannerCardDisplayWidget->setScaleFactor(
|
deckPreviewWidget->bannerCardDisplayWidget->setScaleFactor(visualDeckStorageWidget->settings()->getCardSize());
|
||||||
visualDeckStorageWidget->settings()->getCardSize());
|
|
||||||
deckWidgets.insert(filePath, deckPreviewWidget);
|
deckWidgets.insert(filePath, deckPreviewWidget);
|
||||||
}
|
return deckPreviewWidget;
|
||||||
orderedWidgets.append(deckPreviewWidget);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-add all widgets so the flow layout order matches the proxy order. Skipped when the
|
|
||||||
// order is unchanged so that data-only updates don't invalidate the flow layout.
|
|
||||||
if (desiredOrder != lastOrderedFilePaths) {
|
|
||||||
for (DeckPreviewWidget *deckPreviewWidget : orderedWidgets) {
|
|
||||||
flowWidget->removeWidget(deckPreviewWidget);
|
|
||||||
}
|
|
||||||
for (DeckPreviewWidget *deckPreviewWidget : orderedWidgets) {
|
|
||||||
flowWidget->addWidget(deckPreviewWidget);
|
|
||||||
}
|
|
||||||
lastOrderedFilePaths = desiredOrder;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -200,7 +281,7 @@ void VisualDeckStorageFolderDisplayWidget::updateShowFolders(bool enabled)
|
||||||
subFolderWidgets.clear();
|
subFolderWidgets.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
reconcile();
|
scheduleReconcile();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -218,10 +299,13 @@ void VisualDeckStorageFolderDisplayWidget::refreshVisibility()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether this folder shows any deck previews or has any visible subfolder.
|
* @brief Whether this folder shows any deck previews or has any visible subfolder.
|
||||||
|
*
|
||||||
|
* While the first pass is still building, the folder counts as having content so it
|
||||||
|
* doesn't flicker or hide prematurely before its previews have been created.
|
||||||
*/
|
*/
|
||||||
bool VisualDeckStorageFolderDisplayWidget::hasContent() const
|
bool VisualDeckStorageFolderDisplayWidget::hasContent() const
|
||||||
{
|
{
|
||||||
if (!deckWidgets.isEmpty()) {
|
if (!initialPassCompleted || visibleDeckCount > 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,20 @@
|
||||||
* @ingroup VisualDeckStorageWidgets
|
* @ingroup VisualDeckStorageWidgets
|
||||||
* @brief Renders the decks of one folder of the Visual Deck Storage.
|
* @brief Renders the decks of one folder of the Visual Deck Storage.
|
||||||
*
|
*
|
||||||
* This is a pure view: it reads the accepted rows of the
|
* This is a pure view: it keeps one persistent DeckPreviewWidget alive per deck
|
||||||
* VisualDeckStorageSortFilterProxyModel whose folder matches this widget's
|
* in its folder, and shows or hides those widgets according to the accepted rows
|
||||||
* folder path, and keeps a set of child DeckPreviewWidgets in sync with those
|
* of the VisualDeckStorageSortFilterProxyModel. Subfolders are shown as nested
|
||||||
* rows. Subfolders are shown as nested VisualDeckStorageFolderDisplayWidgets
|
* VisualDeckStorageFolderDisplayWidgets when the "show folders" setting is enabled.
|
||||||
* when the "show folders" setting is enabled.
|
*
|
||||||
|
* Reconciling runs as a time-budgeted chunked pass that yields to the event loop
|
||||||
|
* between chunks, so scanning a large collection never stalls the ui thread.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef VISUAL_DECK_STORAGE_FOLDER_DISPLAY_WIDGET_H
|
#ifndef VISUAL_DECK_STORAGE_FOLDER_DISPLAY_WIDGET_H
|
||||||
#define VISUAL_DECK_STORAGE_FOLDER_DISPLAY_WIDGET_H
|
#define VISUAL_DECK_STORAGE_FOLDER_DISPLAY_WIDGET_H
|
||||||
|
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
|
#include <QSet>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
|
@ -36,8 +39,8 @@ public:
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
/**
|
/**
|
||||||
* @brief Re-reads the proxy and rebuilds the deck previews and subfolder
|
* @brief Starts a new chunked reconcile pass that re-reads the proxy and rebuilds
|
||||||
* widgets to match.
|
* the deck previews and subfolder widgets to match.
|
||||||
*/
|
*/
|
||||||
void reconcile();
|
void reconcile();
|
||||||
|
|
||||||
|
|
@ -56,14 +59,26 @@ signals:
|
||||||
void contentVisibilityChanged();
|
void contentVisibilityChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void reconcileDecks();
|
void beginDeckPass();
|
||||||
|
void continueDeckPass();
|
||||||
|
void finishDeckPass();
|
||||||
|
[[nodiscard]] DeckPreviewWidget *createDeckPreviewWidget(const QString &filePath);
|
||||||
void createSubFolderWidgets();
|
void createSubFolderWidgets();
|
||||||
void refreshVisibility();
|
void refreshVisibility();
|
||||||
[[nodiscard]] bool hasContent() const;
|
[[nodiscard]] bool hasContent() const;
|
||||||
[[nodiscard]] QStringList childFolderPaths() const;
|
[[nodiscard]] QStringList childFolderPaths() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The maximum time in milliseconds spent creating deck previews per event loop turn.
|
||||||
|
*
|
||||||
|
* Creating all previews of a large folder at once blocks the ui thread for hundreds of
|
||||||
|
* milliseconds, so the pass is split into chunks that yield to the event loop instead.
|
||||||
|
*/
|
||||||
|
static constexpr int DECK_PASS_TIME_BUDGET_MS = 20;
|
||||||
|
|
||||||
bool showFolders;
|
bool showFolders;
|
||||||
QString folderPath; ///< Path relative to the deck folder; empty for the root folder.
|
QString folderPath; ///< Path relative to the deck folder, empty for the root folder.
|
||||||
|
int visibleDeckCount = 0; ///< The number of this folder's deck previews not filtered out.
|
||||||
QVBoxLayout *layout;
|
QVBoxLayout *layout;
|
||||||
QWidget *container;
|
QWidget *container;
|
||||||
QVBoxLayout *containerLayout;
|
QVBoxLayout *containerLayout;
|
||||||
|
|
@ -74,6 +89,16 @@ private:
|
||||||
QHash<QString, VisualDeckStorageFolderDisplayWidget *> subFolderWidgets; ///< Folder path -> subfolder widget.
|
QHash<QString, VisualDeckStorageFolderDisplayWidget *> subFolderWidgets; ///< Folder path -> subfolder widget.
|
||||||
QTimer *reconcileTimer = nullptr; ///< Coalesces proxy change bursts.
|
QTimer *reconcileTimer = nullptr; ///< Coalesces proxy change bursts.
|
||||||
QStringList lastOrderedFilePaths; ///< The deck order last applied to the flow layout.
|
QStringList lastOrderedFilePaths; ///< The deck order last applied to the flow layout.
|
||||||
|
|
||||||
|
/// Whether a chunked reconcile pass is currently running.
|
||||||
|
bool deckPassActive = false;
|
||||||
|
/// Set when the model changes mid-pass. Discards progress and restarts the scan once
|
||||||
|
/// the current chunk finishes so the pass always converges on the latest model state.
|
||||||
|
bool deckPassRestartRequested = false;
|
||||||
|
/// Whether the first reconcile pass has run to completion at least once.
|
||||||
|
bool initialPassCompleted = false;
|
||||||
|
int deckPassRow = 0; ///< Next source row to scan in the active pass.
|
||||||
|
QSet<QString> deckPassPresentPaths; ///< File paths seen so far in the active pass.
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // VISUAL_DECK_STORAGE_FOLDER_DISPLAY_WIDGET_H
|
#endif // VISUAL_DECK_STORAGE_FOLDER_DISPLAY_WIDGET_H
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
#include "../../../filters/syntax_help.h"
|
#include "../../../filters/syntax_help.h"
|
||||||
#include "../../pixel_map_generator.h"
|
#include "../../pixel_map_generator.h"
|
||||||
#include "visual_deck_storage_widget.h"
|
|
||||||
|
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
@ -13,9 +12,9 @@
|
||||||
* Provides a search bar that allows users to search decks by filename or search
|
* Provides a search bar that allows users to search decks by filename or search
|
||||||
* expression, with a debounced timer to trigger the search after the user stops typing.
|
* expression, with a debounced timer to trigger the search after the user stops typing.
|
||||||
*
|
*
|
||||||
* @param parent The VisualDeckStorageWidget owning the search.
|
* @param parent The parent widget.
|
||||||
*/
|
*/
|
||||||
VisualDeckStorageSearchWidget::VisualDeckStorageSearchWidget(VisualDeckStorageWidget *parent) : parent(parent)
|
VisualDeckStorageSearchWidget::VisualDeckStorageSearchWidget(QWidget *parent) : QWidget(parent)
|
||||||
{
|
{
|
||||||
layout = new QHBoxLayout(this);
|
layout = new QHBoxLayout(this);
|
||||||
layout->setContentsMargins(0, 0, 0, 0);
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
|
@ -38,6 +37,5 @@ VisualDeckStorageSearchWidget::VisualDeckStorageSearchWidget(VisualDeckStorageWi
|
||||||
searchDebounceTimer->start(300); // 300ms debounce
|
searchDebounceTimer->start(300); // 300ms debounce
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(searchDebounceTimer, &QTimer::timeout, this,
|
connect(searchDebounceTimer, &QTimer::timeout, this, [this] { emit searchTextChanged(searchBar->text()); });
|
||||||
[this] { this->parent->proxyModel()->setSearchText(searchBar->text()); });
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,17 +11,23 @@
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
class QTimer;
|
class QTimer;
|
||||||
class VisualDeckStorageWidget;
|
|
||||||
class VisualDeckStorageSearchWidget : public QWidget
|
class VisualDeckStorageSearchWidget : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit VisualDeckStorageSearchWidget(VisualDeckStorageWidget *parent);
|
explicit VisualDeckStorageSearchWidget(QWidget *parent);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
/**
|
||||||
|
* Emitted once the debounce timer fires after the user stopped typing.
|
||||||
|
* @param text The current contents of the search bar.
|
||||||
|
*/
|
||||||
|
void searchTextChanged(const QString &text);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QHBoxLayout *layout;
|
QHBoxLayout *layout;
|
||||||
VisualDeckStorageWidget *parent;
|
|
||||||
QLineEdit *searchBar;
|
QLineEdit *searchBar;
|
||||||
QTimer *searchDebounceTimer;
|
QTimer *searchDebounceTimer;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare
|
||||||
{
|
{
|
||||||
// The model and proxy own all deck data, sorting and filtering. The view widgets below only
|
// The model and proxy own all deck data, sorting and filtering. The view widgets below only
|
||||||
// display the proxy's accepted rows, so nothing touches the filesystem outside the model.
|
// display the proxy's accepted rows, so nothing touches the filesystem outside the model.
|
||||||
model_ = new VisualDeckStorageModel(this);
|
storageModel = new VisualDeckStorageModel(this);
|
||||||
proxyModel_ = new VisualDeckStorageSortFilterProxyModel(this);
|
storageProxyModel = new VisualDeckStorageSortFilterProxyModel(this);
|
||||||
proxyModel_->setSourceModel(model_);
|
storageProxyModel->setSourceModel(storageModel);
|
||||||
|
|
||||||
layout = new QVBoxLayout(this);
|
layout = new QVBoxLayout(this);
|
||||||
layout->setSpacing(0);
|
layout->setSpacing(0);
|
||||||
|
|
@ -86,17 +86,25 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare
|
||||||
refreshTimer->setSingleShot(true);
|
refreshTimer->setSingleShot(true);
|
||||||
refreshTimer->setInterval(150);
|
refreshTimer->setInterval(150);
|
||||||
connect(refreshTimer, &QTimer::timeout, this, [this] {
|
connect(refreshTimer, &QTimer::timeout, this, [this] {
|
||||||
proxyModel_->reapplyFilters();
|
storageProxyModel->reapplyFilters();
|
||||||
// A batch of decks finished loading: re-gather the tag chips from the visible decks once
|
// A batch of decks finished loading: re-gather the tag chips from the visible decks once
|
||||||
// the burst settles instead of on every individual load.
|
// the burst settles instead of on every individual load.
|
||||||
tagFilterWidget->refreshTags();
|
tagFilterWidget->refreshTags();
|
||||||
});
|
});
|
||||||
connect(model_, &QAbstractItemModel::dataChanged, this, [this] { refreshTimer->start(); });
|
connect(storageModel, &QAbstractItemModel::dataChanged, this, [this] { refreshTimer->start(); });
|
||||||
connect(model_, &VisualDeckStorageModel::deckLoaded, this, [this] { refreshTimer->start(); });
|
connect(storageModel, &VisualDeckStorageModel::deckLoaded, this, [this] { refreshTimer->start(); });
|
||||||
// A deck's file path changed: re-apply the sort, since orders like "filename" depend on it.
|
// A deck's file path changed: re-apply the sort, since orders like "filename" depend on it.
|
||||||
connect(model_, &VisualDeckStorageModel::deckFilePathChanged, this, [this] { proxyModel_->resort(); });
|
connect(storageModel, &VisualDeckStorageModel::deckFilePathChanged, this, [this] { storageProxyModel->resort(); });
|
||||||
connect(sortWidget, &VisualDeckStorageSortWidget::sortOrderChanged, this,
|
connect(sortWidget, &VisualDeckStorageSortWidget::sortOrderChanged, this,
|
||||||
&VisualDeckStorageWidget::updateSortOrder);
|
&VisualDeckStorageWidget::updateSortOrder);
|
||||||
|
// The filter widgets only own their ui state. Pushing it into the proxy model
|
||||||
|
// happens here, so the children stay decoupled from the model layer.
|
||||||
|
connect(deckPreviewColorIdentityFilterWidget, &DeckPreviewColorIdentityFilterWidget::activeColorsChanged, this,
|
||||||
|
&VisualDeckStorageWidget::updateColorFilter);
|
||||||
|
connect(deckPreviewColorIdentityFilterWidget, &DeckPreviewColorIdentityFilterWidget::filterModeChanged, this,
|
||||||
|
&VisualDeckStorageWidget::updateColorFilter);
|
||||||
|
connect(searchWidget, &VisualDeckStorageSearchWidget::searchTextChanged, this,
|
||||||
|
&VisualDeckStorageWidget::updateSearchFilter);
|
||||||
|
|
||||||
connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this,
|
connect(CardDatabaseManager::getInstance(), &CardDatabase::cardDatabaseLoadingFinished, this,
|
||||||
&VisualDeckStorageWidget::createRootFolderWidget);
|
&VisualDeckStorageWidget::createRootFolderWidget);
|
||||||
|
|
@ -163,8 +171,8 @@ const VisualDeckStorageQuickSettingsWidget *VisualDeckStorageWidget::settings()
|
||||||
*/
|
*/
|
||||||
void VisualDeckStorageWidget::reapplySortAndFilters()
|
void VisualDeckStorageWidget::reapplySortAndFilters()
|
||||||
{
|
{
|
||||||
proxyModel_->setSortOrder(sortWidget->currentSortOrder());
|
storageProxyModel->setSortOrder(sortWidget->currentSortOrder());
|
||||||
proxyModel_->reapplyFilters();
|
storageProxyModel->reapplyFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -172,7 +180,7 @@ void VisualDeckStorageWidget::reapplySortAndFilters()
|
||||||
*/
|
*/
|
||||||
void VisualDeckStorageWidget::createRootFolderWidget()
|
void VisualDeckStorageWidget::createRootFolderWidget()
|
||||||
{
|
{
|
||||||
model_->setDeckPath(SettingsCache::instance().paths().getDeckPath());
|
storageModel->setDeckPath(SettingsCache::instance().paths().getDeckPath());
|
||||||
|
|
||||||
folderWidget =
|
folderWidget =
|
||||||
new VisualDeckStorageFolderDisplayWidget(this, this, QString(), false, quickSettingsWidget->getShowFolders());
|
new VisualDeckStorageFolderDisplayWidget(this, this, QString(), false, quickSettingsWidget->getShowFolders());
|
||||||
|
|
@ -194,19 +202,36 @@ void VisualDeckStorageWidget::updateShowFolders(bool enabled)
|
||||||
|
|
||||||
void VisualDeckStorageWidget::updateSortOrder()
|
void VisualDeckStorageWidget::updateSortOrder()
|
||||||
{
|
{
|
||||||
proxyModel_->setSortOrder(sortWidget->currentSortOrder());
|
storageProxyModel->setSortOrder(sortWidget->currentSortOrder());
|
||||||
}
|
}
|
||||||
|
|
||||||
void VisualDeckStorageWidget::updateTagFilter()
|
void VisualDeckStorageWidget::updateTagFilter()
|
||||||
{
|
{
|
||||||
const QStringList selected = tagFilterWidget->selectedTags();
|
const QStringList selected = tagFilterWidget->selectedTags();
|
||||||
const QStringList excluded = tagFilterWidget->excludedTags();
|
const QStringList excluded = tagFilterWidget->excludedTags();
|
||||||
proxyModel_->setTagFilter(QSet<QString>(selected.cbegin(), selected.cend()),
|
storageProxyModel->setTagFilter(QSet<QString>(selected.cbegin(), selected.cend()),
|
||||||
QSet<QString>(excluded.cbegin(), excluded.cend()));
|
QSet<QString>(excluded.cbegin(), excluded.cend()));
|
||||||
// The visible deck set changed, so the chips are re-gathered from it.
|
// The visible deck set changed, so the chips are re-gathered from it.
|
||||||
tagFilterWidget->refreshTags();
|
tagFilterWidget->refreshTags();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes the color identity filter widget's state into the proxy model.
|
||||||
|
*/
|
||||||
|
void VisualDeckStorageWidget::updateColorFilter()
|
||||||
|
{
|
||||||
|
storageProxyModel->setColorFilter(deckPreviewColorIdentityFilterWidget->getFilterMode(),
|
||||||
|
deckPreviewColorIdentityFilterWidget->getActiveColors());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes the search bar's text into the proxy model.
|
||||||
|
*/
|
||||||
|
void VisualDeckStorageWidget::updateSearchFilter(const QString &text)
|
||||||
|
{
|
||||||
|
storageProxyModel->setSearchText(text);
|
||||||
|
}
|
||||||
|
|
||||||
void VisualDeckStorageWidget::updateTagsVisibility(const bool visible)
|
void VisualDeckStorageWidget::updateTagsVisibility(const bool visible)
|
||||||
{
|
{
|
||||||
if (visible) {
|
if (visible) {
|
||||||
|
|
|
||||||
|
|
@ -40,11 +40,11 @@ public:
|
||||||
[[nodiscard]] const VisualDeckStorageQuickSettingsWidget *settings() const;
|
[[nodiscard]] const VisualDeckStorageQuickSettingsWidget *settings() const;
|
||||||
[[nodiscard]] VisualDeckStorageModel *model() const
|
[[nodiscard]] VisualDeckStorageModel *model() const
|
||||||
{
|
{
|
||||||
return model_;
|
return storageModel;
|
||||||
}
|
}
|
||||||
[[nodiscard]] VisualDeckStorageSortFilterProxyModel *proxyModel() const
|
[[nodiscard]] VisualDeckStorageSortFilterProxyModel *proxyModel() const
|
||||||
{
|
{
|
||||||
return proxyModel_;
|
return storageProxyModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
@ -57,6 +57,8 @@ public slots:
|
||||||
void updateSelectionAnimationEnabled(bool enabled);
|
void updateSelectionAnimationEnabled(bool enabled);
|
||||||
void updateSortOrder();
|
void updateSortOrder();
|
||||||
void updateTagFilter();
|
void updateTagFilter();
|
||||||
|
void updateColorFilter();
|
||||||
|
void updateSearchFilter(const QString &text);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void deckLoadRequested(const QString &filePath);
|
void deckLoadRequested(const QString &filePath);
|
||||||
|
|
@ -81,8 +83,8 @@ private:
|
||||||
VisualDeckStorageQuickSettingsWidget *quickSettingsWidget;
|
VisualDeckStorageQuickSettingsWidget *quickSettingsWidget;
|
||||||
QScrollArea *scrollArea;
|
QScrollArea *scrollArea;
|
||||||
VisualDeckStorageFolderDisplayWidget *folderWidget = nullptr;
|
VisualDeckStorageFolderDisplayWidget *folderWidget = nullptr;
|
||||||
VisualDeckStorageModel *model_ = nullptr;
|
VisualDeckStorageModel *storageModel = nullptr;
|
||||||
VisualDeckStorageSortFilterProxyModel *proxyModel_ = nullptr;
|
VisualDeckStorageSortFilterProxyModel *storageProxyModel = nullptr;
|
||||||
QTimer *refreshTimer = nullptr; ///< Coalesces the re-apply/refresh burst following a batch of deck loads.
|
QTimer *refreshTimer = nullptr; ///< Coalesces the re-apply/refresh burst following a batch of deck loads.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue