Compare commits

..

4 Commits

Author SHA1 Message Date
Brübach, Lukas
a2af648e00 [VDE] Be saner about proxy indices 2025-12-05 04:14:53 +01:00
BruebachL
0a239712dd [VDD] Add search bar for filters. (#6389)
* [VDD] Add search bar for filters.

* Update cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_save_load_widget.cpp

Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>

---------

Co-authored-by: RickyRister <42636155+RickyRister@users.noreply.github.com>
2025-12-04 23:14:19 +01:00
RickyRister
95c3434205 [TagDisplayWidget] Refactor to just store tags and use signals (#6395) 2025-12-04 10:26:39 -08:00
RickyRister
f0be6972cc [TagsDisplayWidget] cleanup refactor (#6394)
* Make fields private

* Move method to static

* clean up code

* move code
2025-12-04 09:40:24 -08:00
16 changed files with 213 additions and 310 deletions

View File

@@ -154,8 +154,10 @@ void DeckEditorDeckDockWidget::createDeckDock()
&DeckEditorDeckDockWidget::setBannerCard);
bannerCardComboBox->setHidden(!SettingsCache::instance().getDeckEditorBannerCardComboBoxVisible());
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList());
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckModel->getDeckList()->getTags());
deckTagsDisplayWidget->setHidden(!SettingsCache::instance().getDeckEditorTagsWidgetVisible());
connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this,
&DeckEditorDeckDockWidget::setTags);
activeGroupCriteriaLabel = new QLabel(this);
@@ -383,6 +385,13 @@ void DeckEditorDeckDockWidget::setBannerCard(int /* changedIndex */)
emit deckModified();
}
void DeckEditorDeckDockWidget::setTags(const QStringList &tags)
{
deckModel->getDeckList()->setTags(tags);
deckEditor->setModified(true);
emit deckModified();
}
void DeckEditorDeckDockWidget::syncDeckListBannerCardWithComboBox()
{
auto [name, id] = bannerCardComboBox->currentData().value<QPair<QString, QString>>();
@@ -451,7 +460,7 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
sortDeckModelToDeckView();
expandAll();
deckTagsDisplayWidget->setDeckList(deckModel->getDeckList());
deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags());
}
void DeckEditorDeckDockWidget::sortDeckModelToDeckView()
@@ -484,7 +493,7 @@ void DeckEditorDeckDockWidget::cleanDeck()
emit deckModified();
emit deckChanged();
updateBannerCardComboBox();
deckTagsDisplayWidget->setDeckList(deckModel->getDeckList());
deckTagsDisplayWidget->setTags(deckModel->getDeckList()->getTags());
}
void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index)
@@ -579,7 +588,7 @@ bool DeckEditorDeckDockWidget::swapCard(const QModelIndex &currentIndex)
QModelIndex newCardIndex = card ? deckModel->addCard(card, otherZoneName)
// Third argument (true) says create the card no matter what, even if not in DB
: deckModel->addPreferredPrintingCard(cardName, otherZoneName, true);
recursiveExpand(proxy->mapToSource(newCardIndex));
recursiveExpand(proxy->mapFromSource(newCardIndex));
return true;
}
@@ -600,8 +609,18 @@ void DeckEditorDeckDockWidget::actDecrementCard(const ExactCard &card, QString z
}
deckView->clearSelection();
deckView->setCurrentIndex(proxy->mapToSource(idx));
offsetCountAtIndex(idx, -1);
setCurrentProxyIndex(idx);
offsetCountAtIndex(proxy->mapFromSource(idx), -1);
}
void DeckEditorDeckDockWidget::setCurrentProxyIndex(const QModelIndex &index)
{
deckView->setCurrentIndex(proxy->mapFromSource(index));
}
void DeckEditorDeckDockWidget::scrollToProxyIndex(const QModelIndex &index)
{
deckView->setCurrentIndex(proxy->mapFromSource(index));
}
void DeckEditorDeckDockWidget::actDecrementSelection()

View File

@@ -70,6 +70,8 @@ public slots:
void actRemoveCard();
void offsetCountAtIndex(const QModelIndex &idx, int offset);
void expandAll();
void setCurrentProxyIndex(const QModelIndex &index);
void scrollToProxyIndex(const QModelIndex &index);
signals:
void nameChanged();
@@ -112,6 +114,7 @@ private slots:
void updateName(const QString &name);
void updateComments();
void setBannerCard(int);
void setTags(const QStringList &tags);
void syncDeckListBannerCardWithComboBox();
void updateHash();
void refreshShortcuts();

View File

@@ -190,7 +190,7 @@ void CardAmountWidget::addPrinting(const QString &zone)
newCardIndex = deckModel->findCard(rootCard.getName(), zone, rootCard.getPrinting().getUuid(),
rootCard.getPrinting().getProperty("num"));
deckView->setCurrentIndex(newCardIndex);
deckEditor->deckDockWidget->setCurrentProxyIndex(newCardIndex);
deckView->setFocus(Qt::FocusReason::MouseFocusReason);
deckEditor->setModified(true);
}
@@ -256,7 +256,7 @@ void CardAmountWidget::offsetCountAtIndex(const QModelIndex &idx, int offset)
const int count = deckModel->data(numberIndex, Qt::EditRole).toInt();
const int new_count = count + offset;
deckView->setCurrentIndex(numberIndex);
deckEditor->deckDockWidget->setCurrentProxyIndex(numberIndex);
if (new_count <= 0) {
deckModel->removeRow(idx.row(), idx.parent());

View File

@@ -185,7 +185,7 @@ void PrintingSelector::selectCard(const int changeBy)
}
if (nextIndex.isValid()) {
deckView->setCurrentIndex(nextIndex);
deckEditor->deckDockWidget->setCurrentProxyIndex(nextIndex);
deckView->setFocus(Qt::FocusReason::MouseFocusReason);
}
}

View File

@@ -155,7 +155,7 @@ void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, QString zoneNam
QModelIndex newCardIndex = deckDockWidget->deckModel->addCard(card, zoneName);
deckDockWidget->expandAll();
deckDockWidget->deckView->clearSelection();
deckDockWidget->deckView->setCurrentIndex(newCardIndex);
deckDockWidget->setCurrentProxyIndex(newCardIndex);
setModified(true);
databaseDisplayDockWidget->searchEdit->setSelection(0, databaseDisplayDockWidget->searchEdit->text().length());

View File

@@ -232,8 +232,8 @@ void TabDeckEditorVisual::processMainboardCardClick(QMouseEvent *event,
return;
} else {
// Normal click = clear selection, select this, set current
deckDockWidget->deckView->setCurrentIndex(idx);
deckDockWidget->deckView->scrollTo(idx);
deckDockWidget->setCurrentProxyIndex(idx);
deckDockWidget->scrollToProxyIndex(idx);
return;
}

View File

@@ -18,6 +18,16 @@ VisualDatabaseDisplayFilterSaveLoadWidget::VisualDatabaseDisplayFilterSaveLoadWi
layout = new QVBoxLayout(this);
setLayout(layout);
// Filter search input
searchInput = new QLineEdit(this);
layout->addWidget(searchInput);
connect(searchInput, &QLineEdit::textChanged, this, &VisualDatabaseDisplayFilterSaveLoadWidget::applySearchFilter);
// File list container
fileListWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(fileListWidget);
// Input for filter filename
filenameInput = new QLineEdit(this);
layout->addWidget(filenameInput);
@@ -25,11 +35,12 @@ VisualDatabaseDisplayFilterSaveLoadWidget::VisualDatabaseDisplayFilterSaveLoadWi
// Save button
saveButton = new QPushButton(this);
layout->addWidget(saveButton);
connect(saveButton, &QPushButton::clicked, this, &VisualDatabaseDisplayFilterSaveLoadWidget::saveFilter);
// Disable save if empty
saveButton->setEnabled(false);
connect(filenameInput, &QLineEdit::textChanged, this,
[this](const QString &text) { saveButton->setEnabled(!text.trimmed().isEmpty()); });
// File list container
fileListWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
layout->addWidget(fileListWidget);
connect(saveButton, &QPushButton::clicked, this, &VisualDatabaseDisplayFilterSaveLoadWidget::saveFilter);
refreshFilterList(); // Populate the file list on startup
retranslateUi();
@@ -37,6 +48,7 @@ VisualDatabaseDisplayFilterSaveLoadWidget::VisualDatabaseDisplayFilterSaveLoadWi
void VisualDatabaseDisplayFilterSaveLoadWidget::retranslateUi()
{
searchInput->setPlaceholderText(tr("Search filter..."));
saveButton->setText(tr("Save Filter"));
saveButton->setToolTip(tr("Save all currently applied filters to a file"));
filenameInput->setPlaceholderText(tr("Enter filename..."));
@@ -112,42 +124,36 @@ void VisualDatabaseDisplayFilterSaveLoadWidget::loadFilter(const QString &filena
emit filterModel->layoutChanged();
}
void VisualDatabaseDisplayFilterSaveLoadWidget::applySearchFilter(const QString &text)
{
fileListWidget->clearLayout();
QString filter = text.trimmed();
QStringList filtered = allFilterFiles;
if (!filter.isEmpty()) {
filtered = filtered.filter(QRegularExpression(filter, QRegularExpression::CaseInsensitiveOption));
}
for (const QString &filename : filtered) {
FilterDisplayWidget *filterWidget = new FilterDisplayWidget(this, filename, filterModel);
fileListWidget->addWidget(filterWidget);
connect(filterWidget, &FilterDisplayWidget::filterLoadRequested, this,
&VisualDatabaseDisplayFilterSaveLoadWidget::loadFilter);
connect(filterWidget, &FilterDisplayWidget::filterDeleted, this,
&VisualDatabaseDisplayFilterSaveLoadWidget::refreshFilterList);
}
}
void VisualDatabaseDisplayFilterSaveLoadWidget::refreshFilterList()
{
fileListWidget->clearLayout();
// Clear existing widgets
for (auto buttonPair : fileButtons) {
buttonPair.first->deleteLater();
buttonPair.second->deleteLater();
}
fileButtons.clear(); // Clear the list of buttons
fileButtons.clear();
// Refresh the filter file list
QDir dir(SettingsCache::instance().getFiltersPath());
QStringList filterFiles = dir.entryList(QStringList() << "*.json", QDir::Files, QDir::Name);
allFilterFiles = dir.entryList({"*.json"}, QDir::Files, QDir::Name);
// Loop through the filter files and create widgets for them
for (const QString &filename : filterFiles) {
bool alreadyAdded = false;
// Check if the widget for this filter file already exists to avoid duplicates
for (const auto &pair : fileButtons) {
if (pair.first->text() == filename) {
alreadyAdded = true;
break;
}
}
if (!alreadyAdded) {
// Create a new custom widget for the filter
FilterDisplayWidget *filterWidget = new FilterDisplayWidget(this, filename, filterModel);
fileListWidget->addWidget(filterWidget);
// Connect signals to handle loading and deletion
connect(filterWidget, &FilterDisplayWidget::filterLoadRequested, this,
&VisualDatabaseDisplayFilterSaveLoadWidget::loadFilter);
connect(filterWidget, &FilterDisplayWidget::filterDeleted, this,
&VisualDatabaseDisplayFilterSaveLoadWidget::refreshFilterList);
}
}
applySearchFilter(searchInput->text());
}

View File

@@ -27,6 +27,7 @@ public:
void saveFilter();
void loadFilter(const QString &filename);
void applySearchFilter(const QString &text);
void refreshFilterList();
void deleteFilter(const QString &filename, QPushButton *deleteButton);
@@ -37,9 +38,11 @@ private:
FilterTreeModel *filterModel;
QVBoxLayout *layout;
QLineEdit *searchInput;
FlowWidget *fileListWidget;
QLineEdit *filenameInput;
QPushButton *saveButton;
FlowWidget *fileListWidget;
QStringList allFilterFiles;
QMap<QString, QPair<QPushButton *, QPushButton *>> fileButtons;
};

View File

@@ -13,8 +13,8 @@
#include <QHBoxLayout>
#include <QMessageBox>
DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList)
: QWidget(_parent), deckList(nullptr)
DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, const QStringList &_tags)
: QWidget(_parent), currentTags(_tags)
{
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
@@ -27,16 +27,14 @@ DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_par
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
if (_deckList) {
setDeckList(_deckList);
}
layout->addWidget(flowWidget);
refreshTags();
}
void DeckPreviewDeckTagsDisplayWidget::setDeckList(DeckList *_deckList)
void DeckPreviewDeckTagsDisplayWidget::setTags(const QStringList &_tags)
{
deckList = _deckList;
currentTags = _tags;
refreshTags();
}
@@ -44,7 +42,7 @@ void DeckPreviewDeckTagsDisplayWidget::refreshTags()
{
flowWidget->clearLayout();
for (const QString &tag : deckList->getTags()) {
for (const QString &tag : currentTags) {
flowWidget->addWidget(new DeckPreviewTagDisplayWidget(this, tag));
}
@@ -71,7 +69,45 @@ static QStringList getAllFiles(const QString &filePath)
return allFiles;
}
bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath)
/**
* Gets all tags that appear in the deck folder
*/
static QStringList findAllKnownTags()
{
QStringList allFiles = getAllFiles(SettingsCache::instance().getDeckPath());
QStringList knownTags;
auto loader = DeckLoader(nullptr);
for (const QString &file : allFiles) {
loader.loadFromFile(file, DeckLoader::getFormatFromName(file), false);
QStringList tags = loader.getDeckList()->getTags();
knownTags.append(tags);
knownTags.removeDuplicates();
}
return knownTags;
}
void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg()
{
if (qobject_cast<DeckPreviewWidget *>(parentWidget())) {
// If we're the child of a DeckPreviewWidget, then we need to handle conversion
auto *deckPreviewWidget = qobject_cast<DeckPreviewWidget *>(parentWidget());
bool canAddTags = promptFileConversionIfRequired(deckPreviewWidget);
if (canAddTags) {
QStringList knownTags = deckPreviewWidget->visualDeckStorageWidget->tagFilterWidget->getAllKnownTags();
execTagDialog(knownTags);
}
} else {
// If we're the child of an AbstractTabDeckEditor, then we don't bother with conversion
QStringList knownTags = findAllKnownTags();
execTagDialog(knownTags);
}
}
static bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath)
{
QFileInfo fileInfo(filePath);
QString newFileName = QDir::toNativeSeparators(fileInfo.path() + "/" + fileInfo.completeBaseName() + ".cod");
@@ -86,98 +122,70 @@ bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath)
return true; // Safe to proceed
}
void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg()
static void convertFileToCockatriceFormat(DeckPreviewWidget *deckPreviewWidget)
{
if (qobject_cast<DeckPreviewWidget *>(parentWidget())) {
auto *deckPreviewWidget = qobject_cast<DeckPreviewWidget *>(parentWidget());
QStringList knownTags = deckPreviewWidget->visualDeckStorageWidget->tagFilterWidget->getAllKnownTags();
QStringList activeTags = deckList->getTags();
deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath);
deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastLoadInfo().fileName;
deckPreviewWidget->refreshBannerCardText();
}
bool canAddTags = true;
/**
* Checks if the deck's file format supports tags.
* If not, then prompt the user for file conversion.
* @return whether the resulting file can support adding tags
*/
bool DeckPreviewDeckTagsDisplayWidget::promptFileConversionIfRequired(DeckPreviewWidget *deckPreviewWidget)
{
if (DeckLoader::getFormatFromName(deckPreviewWidget->filePath) == DeckLoader::CockatriceFormat) {
return true;
}
if (DeckLoader::getFormatFromName(deckPreviewWidget->filePath) != DeckLoader::CockatriceFormat) {
canAddTags = false;
// Retrieve saved preference if the prompt is disabled
if (!SettingsCache::instance().getVisualDeckStoragePromptForConversion()) {
if (SettingsCache::instance().getVisualDeckStorageAlwaysConvert()) {
if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath))
return;
deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath);
deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastLoadInfo().fileName;
deckPreviewWidget->refreshBannerCardText();
canAddTags = true;
}
} else {
// Show the dialog to the user
DialogConvertDeckToCodFormat conversionDialog(parentWidget());
if (conversionDialog.exec() == QDialog::Accepted) {
if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath))
return;
deckPreviewWidget->deckLoader->convertToCockatriceFormat(deckPreviewWidget->filePath);
deckPreviewWidget->filePath = deckPreviewWidget->deckLoader->getLastLoadInfo().fileName;
deckPreviewWidget->refreshBannerCardText();
canAddTags = true;
if (conversionDialog.dontAskAgain()) {
SettingsCache::instance().setVisualDeckStoragePromptForConversion(false);
SettingsCache::instance().setVisualDeckStorageAlwaysConvert(true);
}
} else {
SettingsCache::instance().setVisualDeckStorageAlwaysConvert(false);
if (conversionDialog.dontAskAgain()) {
SettingsCache::instance().setVisualDeckStoragePromptForConversion(false);
} else {
SettingsCache::instance().setVisualDeckStoragePromptForConversion(true);
}
}
}
// Retrieve saved preference if the prompt is disabled
if (!SettingsCache::instance().getVisualDeckStoragePromptForConversion()) {
if (!SettingsCache::instance().getVisualDeckStorageAlwaysConvert()) {
return false;
}
if (canAddTags) {
DeckPreviewTagDialog dialog(knownTags, activeTags);
if (dialog.exec() == QDialog::Accepted) {
QStringList updatedTags = dialog.getActiveTags();
deckList->setTags(updatedTags);
deckPreviewWidget->deckLoader->saveToFile(deckPreviewWidget->filePath, DeckLoader::CockatriceFormat);
refreshTags();
}
if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath)) {
return false;
}
} else if (parentWidget()) {
// If we're the child of an AbstractTabDeckEditor, we are buried under a ton of childWidgets in the
// DeckInfoDock.
QWidget *currentParent = parentWidget();
while (currentParent) {
if (qobject_cast<AbstractTabDeckEditor *>(currentParent)) {
break;
}
currentParent = currentParent->parentWidget();
}
if (qobject_cast<AbstractTabDeckEditor *>(currentParent)) {
auto *deckEditor = qobject_cast<AbstractTabDeckEditor *>(currentParent);
QStringList knownTags;
QStringList allFiles = getAllFiles(SettingsCache::instance().getDeckPath());
DeckLoader loader(this);
for (const QString &file : allFiles) {
loader.loadFromFile(file, DeckLoader::getFormatFromName(file), false);
QStringList tags = loader.getDeckList()->getTags();
knownTags.append(tags);
knownTags.removeDuplicates();
}
QStringList activeTags = deckList->getTags();
convertFileToCockatriceFormat(deckPreviewWidget);
return true;
}
DeckPreviewTagDialog dialog(knownTags, activeTags);
if (dialog.exec() == QDialog::Accepted) {
QStringList updatedTags = dialog.getActiveTags();
deckList->setTags(updatedTags);
deckEditor->setModified(true);
refreshTags();
}
// Show the dialog to the user
DialogConvertDeckToCodFormat conversionDialog(parentWidget());
if (conversionDialog.exec() != QDialog::Accepted) {
SettingsCache::instance().setVisualDeckStoragePromptForConversion(!conversionDialog.dontAskAgain());
SettingsCache::instance().setVisualDeckStorageAlwaysConvert(false);
return false;
}
// Try to convert file
if (!confirmOverwriteIfExists(this, deckPreviewWidget->filePath)) {
return false;
}
convertFileToCockatriceFormat(deckPreviewWidget);
if (conversionDialog.dontAskAgain()) {
SettingsCache::instance().setVisualDeckStoragePromptForConversion(false);
SettingsCache::instance().setVisualDeckStorageAlwaysConvert(true);
}
return true;
}
void DeckPreviewDeckTagsDisplayWidget::execTagDialog(const QStringList &knownTags)
{
DeckPreviewTagDialog dialog(knownTags, currentTags);
if (dialog.exec() == QDialog::Accepted) {
QStringList updatedTags = dialog.getActiveTags();
if (updatedTags != currentTags) {
setTags(updatedTags);
emit tagsChanged(updatedTags);
}
}
}
}

View File

@@ -12,21 +12,31 @@
#include <QWidget>
inline bool confirmOverwriteIfExists(QWidget *parent, const QString &filePath);
class DeckPreviewWidget;
class DeckPreviewDeckTagsDisplayWidget : public QWidget
{
Q_OBJECT
public:
explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList);
void setDeckList(DeckList *_deckList);
void refreshTags();
DeckList *deckList;
QStringList currentTags;
FlowWidget *flowWidget;
public:
explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, const QStringList &_tags);
void setTags(const QStringList &_tags);
void refreshTags();
public slots:
void openTagEditDlg();
private:
bool promptFileConversionIfRequired(DeckPreviewWidget *deckPreviewWidget);
void execTagDialog(const QStringList &knownTags);
signals:
/**
* Emitted when the tags have changed due to user interaction.
* @param tags The new list of tags.
*/
void tagsChanged(const QStringList &tags);
};
#endif // DECK_PREVIEW_DECK_TAGS_DISPLAY_WIDGET_H

View File

@@ -83,7 +83,8 @@ void DeckPreviewWidget::initializeUi(const bool deckLoadSuccess)
setFilePath(deckLoader->getLastLoadInfo().fileName);
colorIdentityWidget = new ColorIdentityWidget(this, getColorIdentity());
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeckList());
deckTagsDisplayWidget = new DeckPreviewDeckTagsDisplayWidget(this, deckLoader->getDeckList()->getTags());
connect(deckTagsDisplayWidget, &DeckPreviewDeckTagsDisplayWidget::tagsChanged, this, &DeckPreviewWidget::setTags);
bannerCardLabel = new QLabel(this);
bannerCardLabel->setObjectName("bannerCardLabel");
@@ -307,6 +308,12 @@ void DeckPreviewWidget::imageDoubleClickedEvent(QMouseEvent *event, DeckPreviewC
emit deckLoadRequested(filePath);
}
void DeckPreviewWidget::setTags(const QStringList &tags)
{
deckLoader->getDeckList()->setTags(tags);
deckLoader->saveToFile(filePath, DeckLoader::CockatriceFormat);
}
QMenu *DeckPreviewWidget::createRightClickMenu()
{
auto *menu = new QMenu(this);

View File

@@ -72,6 +72,8 @@ private:
void addSetBannerCardMenu(QMenu *menu);
private slots:
void setTags(const QStringList &tags);
void actRenameDeck();
void actRenameFile();
void actDeleteFile();

View File

@@ -6,17 +6,12 @@ set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
set(UTILITY_SOURCES libcockatrice/utility/expression.cpp libcockatrice/utility/levenshtein.cpp
libcockatrice/utility/passwordhasher.cpp libcockatrice/utility/system_memory_querier.cpp
libcockatrice/utility/passwordhasher.cpp
)
set(UTILITY_HEADERS
libcockatrice/utility/color.h
libcockatrice/utility/expression.h
libcockatrice/utility/levenshtein.h
libcockatrice/utility/macros.h
libcockatrice/utility/passwordhasher.h
libcockatrice/utility/system_memory_querier.h
libcockatrice/utility/trice_limits.h
libcockatrice/utility/color.h libcockatrice/utility/expression.h libcockatrice/utility/levenshtein.h
libcockatrice/utility/macros.h libcockatrice/utility/passwordhasher.h libcockatrice/utility/trice_limits.h
)
add_library(libcockatrice_utility STATIC ${UTILITY_SOURCES} ${UTILITY_HEADERS})

View File

@@ -1,112 +0,0 @@
#include "system_memory_querier.h"
#ifdef Q_OS_WIN
#include <windows.h>
#endif
#ifdef Q_OS_LINUX
#include <QFile>
#include <QRegularExpression>
#include <QTextStream>
#endif
#ifdef Q_OS_MACOS
#include <mach/mach.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <unistd.h>
#endif
qulonglong SystemMemoryQuerier::totalMemoryBytes()
{
#if defined(Q_OS_WIN)
MEMORYSTATUSEX statex;
statex.dwLength = sizeof(statex);
if (GlobalMemoryStatusEx(&statex))
return statex.ullTotalPhys;
return 0;
#elif defined(Q_OS_LINUX)
QFile file("/proc/meminfo");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return 0;
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine();
if (line.startsWith("MemTotal:")) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QStringList parts = line.split(QRegExp("\\s+"), QString::SkipEmptyParts);
#else
QStringList parts = line.split(QRegularExpression("\\s+"), Qt::SkipEmptyParts);
#endif
if (parts.size() >= 2)
return parts[1].toULongLong() * 1024; // kB → bytes
}
}
return 0;
#elif defined(Q_OS_MACOS)
int mib[2] = {CTL_HW, HW_MEMSIZE};
qulonglong memsize = 0;
size_t len = sizeof(memsize);
if (sysctl(mib, 2, &memsize, &len, nullptr, 0) == 0)
return memsize;
return 0;
#else
return 0;
#endif
}
qulonglong SystemMemoryQuerier::availableMemoryBytes()
{
#if defined(Q_OS_WIN)
MEMORYSTATUSEX statex;
statex.dwLength = sizeof(statex);
if (GlobalMemoryStatusEx(&statex))
return statex.ullAvailPhys;
return 0;
#elif defined(Q_OS_LINUX)
QFile file("/proc/meminfo");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return 0;
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine();
if (line.startsWith("MemAvailable:")) {
QStringList parts = line.split(QRegExp("\\s+"), Qt::SkipEmptyParts);
if (parts.size() >= 2)
return parts[1].toULongLong() * 1024;
}
}
return 0;
#elif defined(Q_OS_MACOS)
vm_size_t pageSize;
host_page_size(mach_host_self(), &pageSize);
mach_msg_type_number_t count = HOST_VM_INFO_COUNT;
vm_statistics64_data_t vmstat;
if (host_statistics64(mach_host_self(), HOST_VM_INFO, (host_info64_t)&vmstat, &count) != KERN_SUCCESS)
return 0;
qulonglong freeBytes = (qulonglong)vmstat.free_count * (qulonglong)pageSize;
return freeBytes;
#else
return 0;
#endif
}

View File

@@ -1,19 +0,0 @@
#ifndef COCKATRICE_SYSTEM_MEMORY_QUERIER_H
#define COCKATRICE_SYSTEM_MEMORY_QUERIER_H
#include <QtGlobal>
class SystemMemoryQuerier
{
public:
static qulonglong totalMemoryBytes();
static qulonglong availableMemoryBytes();
static bool hasAtLeastGiB(int gib)
{
const qulonglong GiB = 1024ull * 1024ull * 1024ull;
return totalMemoryBytes() >= (qulonglong)gib * GiB;
}
};
#endif // COCKATRICE_SYSTEM_MEMORY_QUERIER_H

View File

@@ -1,7 +1,6 @@
#include "pages.h"
#include "client/settings/cache_settings.h"
#include "libcockatrice/utility/system_memory_querier.h"
#include "main.h"
#include "oracleimporter.h"
#include "oraclewizard.h"
@@ -44,7 +43,6 @@
#define MTGJSON_V4_URL_COMPONENT "mtgjson.com/files/"
#define ALLSETS_URL_FALLBACK "https://www.mtgjson.com/api/v5/AllPrintings.json"
#define MTGJSON_VERSION_URL "https://www.mtgjson.com/api/v5/Meta.json"
#define MTGXML_URL "https://github.com/ebbit1q/mtgxml/releases/latest/download/mtg.xml.xz"
#ifdef HAS_LZMA
#define ALLSETS_URL "https://www.mtgjson.com/api/v5/AllPrintings.json.xz"
@@ -187,23 +185,6 @@ void LoadSetsPage::initializePage()
{
urlLineEdit->setText(wizard()->settings->value("allsetsurl", ALLSETS_URL).toString());
// Memory check because Oracle parsing fails on systems with less than 4GiB for MTGJsons allPrintings.json
if (!SystemMemoryQuerier::hasAtLeastGiB(4) && urlLineEdit->text() == ALLSETS_URL) {
// Ask user whether to switch URL
QMessageBox msgBox(
QMessageBox::Question, tr("Low Memory Detected"),
tr("Your system has less than 4 GiB of memory.\n"
"Using the default AllPrintings URL may cause high memory usage and is known to fail as a result.\n\n"
"Would you like to switch to the direct-download pre-parsed URL instead? (Updated daily)"),
QMessageBox::Yes | QMessageBox::No, this);
msgBox.setDefaultButton(QMessageBox::Yes);
if (msgBox.exec() == QMessageBox::Yes) {
urlLineEdit->setText(MTGXML_URL);
}
}
progressLabel->hide();
progressBar->hide();