mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-01-26 03:14:44 -08:00
Compare commits
10 Commits
f0ebd28148
...
oracle-mem
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c0c8b416a | ||
|
|
65a3423009 | ||
|
|
4ddbc8d018 | ||
|
|
d4bf40694a | ||
|
|
ec98bcf95d | ||
|
|
a799cd097a | ||
|
|
b4e3f2cba9 | ||
|
|
658ae83157 | ||
|
|
d29e72ce72 | ||
|
|
30cc8ad6f9 |
@@ -49,7 +49,7 @@ void CardInfoDisplayWidget::setCard(const ExactCard &card)
|
||||
if (exactCard)
|
||||
connect(exactCard.getCardPtr().data(), &QObject::destroyed, this, &CardInfoDisplayWidget::clear);
|
||||
|
||||
text->setCard(exactCard.getCardPtr());
|
||||
text->setCard(exactCard);
|
||||
pic->setCard(exactCard);
|
||||
}
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ void CardInfoFrameWidget::setCard(const ExactCard &card)
|
||||
|
||||
setViewTransformationButtonVisibility(hasTransformation(exactCard.getInfo()));
|
||||
|
||||
text->setCard(exactCard.getCardPtr());
|
||||
text->setCard(exactCard);
|
||||
pic->setCard(exactCard);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,8 +50,9 @@ void CardInfoTextWidget::setTexts(const QString &propsText, const QString &textT
|
||||
textLabel->setText(textText);
|
||||
}
|
||||
|
||||
void CardInfoTextWidget::setCard(CardInfoPtr card)
|
||||
void CardInfoTextWidget::setCard(const ExactCard &exactCard)
|
||||
{
|
||||
auto card = exactCard.getCardPtr();
|
||||
if (card == nullptr) {
|
||||
setTexts("", "");
|
||||
return;
|
||||
@@ -61,6 +62,15 @@ void CardInfoTextWidget::setCard(CardInfoPtr card)
|
||||
text += QString("<tr><td>%1</td><td width=\"5\"></td><td>%2</td></tr>")
|
||||
.arg(tr("Name:"), card->getName().toHtmlEscaped());
|
||||
|
||||
if (exactCard.getPrinting() != PrintingInfo()) {
|
||||
QString setShort = exactCard.getPrinting().getSet()->getShortName().toHtmlEscaped();
|
||||
QString cardNum = exactCard.getPrinting().getProperty("num").toHtmlEscaped();
|
||||
|
||||
text += QString("<tr><td>%1</td><td></td><td>%2</td></tr>").arg(tr("Set:"), setShort);
|
||||
|
||||
text += QString("<tr><td>%1</td><td></td><td>%2</td></tr>").arg(tr("Collector Number:"), cardNum);
|
||||
}
|
||||
|
||||
QStringList cardProps = card->getProperties();
|
||||
for (const QString &key : cardProps) {
|
||||
if (key.contains("-"))
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#ifndef CARDINFOTEXT_H
|
||||
#define CARDINFOTEXT_H
|
||||
|
||||
#include "libcockatrice/card/printing/exact_card.h"
|
||||
|
||||
#include <QFrame>
|
||||
#include <libcockatrice/card/card_info.h>
|
||||
class QLabel;
|
||||
@@ -32,7 +34,7 @@ public:
|
||||
signals:
|
||||
void linkActivated(const QString &link);
|
||||
public slots:
|
||||
void setCard(CardInfoPtr card);
|
||||
void setCard(const ExactCard &card);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -424,7 +424,6 @@ void DeckEditorDeckDockWidget::setDeck(DeckLoader *_deck)
|
||||
deckLoader->setParent(this);
|
||||
deckModel->setDeckList(deckLoader->getDeckList());
|
||||
connect(deckLoader, &DeckLoader::deckLoaded, deckModel, &DeckListModel::rebuildTree);
|
||||
connect(deckLoader->getDeckList(), &DeckList::deckHashChanged, deckModel, &DeckListModel::deckHashChanged);
|
||||
|
||||
emit requestDeckHistoryClear();
|
||||
historyManagerWidget->setDeckListModel(deckModel);
|
||||
@@ -452,7 +451,7 @@ void DeckEditorDeckDockWidget::syncDisplayWidgetsToModel()
|
||||
sortDeckModelToDeckView();
|
||||
expandAll();
|
||||
|
||||
deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList());
|
||||
deckTagsDisplayWidget->setDeckList(deckModel->getDeckList());
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::sortDeckModelToDeckView()
|
||||
@@ -485,7 +484,7 @@ void DeckEditorDeckDockWidget::cleanDeck()
|
||||
emit deckModified();
|
||||
emit deckChanged();
|
||||
updateBannerCardComboBox();
|
||||
deckTagsDisplayWidget->connectDeckList(deckModel->getDeckList());
|
||||
deckTagsDisplayWidget->setDeckList(deckModel->getDeckList());
|
||||
}
|
||||
|
||||
void DeckEditorDeckDockWidget::recursiveExpand(const QModelIndex &index)
|
||||
|
||||
@@ -69,6 +69,7 @@ public slots:
|
||||
void actSwapCard();
|
||||
void actRemoveCard();
|
||||
void offsetCountAtIndex(const QModelIndex &idx, int offset);
|
||||
void expandAll();
|
||||
|
||||
signals:
|
||||
void nameChanged();
|
||||
@@ -117,7 +118,6 @@ private slots:
|
||||
void updateShowBannerCardComboBox(bool visible);
|
||||
void updateShowTagsWidget(bool visible);
|
||||
void syncBannerCardComboBoxSelectionWithDeck();
|
||||
void expandAll();
|
||||
};
|
||||
|
||||
#endif // DECK_EDITOR_DECK_DOCK_WIDGET_H
|
||||
|
||||
@@ -145,31 +145,49 @@ void DlgSelectSetForCards::retranslateUi()
|
||||
void DlgSelectSetForCards::actOK()
|
||||
{
|
||||
QMap<QString, QStringList> modifiedSetsAndCardsMap = getModifiedCards();
|
||||
|
||||
if (modifiedSetsAndCardsMap.isEmpty()) {
|
||||
accept(); // Nothing to do
|
||||
} else {
|
||||
emit deckAboutToBeModified(tr("Bulk modified printings."));
|
||||
}
|
||||
|
||||
for (QString modifiedSet : modifiedSetsAndCardsMap.keys()) {
|
||||
for (QString card : modifiedSetsAndCardsMap.value(modifiedSet)) {
|
||||
QModelIndex find_card = model->findCard(card, DECK_ZONE_MAIN);
|
||||
if (!find_card.isValid()) {
|
||||
continue;
|
||||
}
|
||||
int amount =
|
||||
model->data(find_card.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT), Qt::DisplayRole).toInt();
|
||||
model->removeRow(find_card.row(), find_card.parent());
|
||||
CardInfoPtr cardInfo = CardDatabaseManager::query()->getCardInfo(card);
|
||||
PrintingInfo printing = CardDatabaseManager::query()->getSpecificPrinting(card, modifiedSet, "");
|
||||
model->addCard(ExactCard(cardInfo, printing), DECK_ZONE_MAIN);
|
||||
for (int i = 0; i < amount; i++) {
|
||||
model->addCard(ExactCard(cardInfo, printing), DECK_ZONE_MAIN);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!modifiedSetsAndCardsMap.isEmpty()) {
|
||||
emit deckModified();
|
||||
}
|
||||
accept();
|
||||
}
|
||||
|
||||
void DlgSelectSetForCards::actClear()
|
||||
{
|
||||
emit deckAboutToBeModified(tr("Cleared all printing information."));
|
||||
DeckLoader::clearSetNamesAndNumbers(model->getDeckList());
|
||||
emit deckModified();
|
||||
accept();
|
||||
}
|
||||
|
||||
void DlgSelectSetForCards::actSetAllToPreferred()
|
||||
{
|
||||
emit deckAboutToBeModified(tr("Set all printings to preferred."));
|
||||
DeckLoader::clearSetNamesAndNumbers(model->getDeckList());
|
||||
DeckLoader::setProviderIdToPreferredPrinting(model->getDeckList());
|
||||
emit deckModified();
|
||||
accept();
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,8 @@ public:
|
||||
signals:
|
||||
void widgetOrderChanged();
|
||||
void orderChanged();
|
||||
void deckAboutToBeModified(const QString &reason);
|
||||
void deckModified();
|
||||
|
||||
public slots:
|
||||
void actOK();
|
||||
|
||||
@@ -40,6 +40,11 @@ public:
|
||||
return deckModel;
|
||||
}
|
||||
|
||||
[[nodiscard]] AbstractTabDeckEditor *getDeckEditor() const
|
||||
{
|
||||
return deckEditor;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void retranslateUi();
|
||||
void updateDisplay();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "printing_selector_card_selection_widget.h"
|
||||
|
||||
#include "../../../interface/widgets/dialogs/dlg_select_set_for_cards.h"
|
||||
#include "../tabs/abstract_tab_deck_editor.h"
|
||||
|
||||
/**
|
||||
* @brief Constructs a PrintingSelectorCardSelectionWidget for navigating through cards in the deck.
|
||||
@@ -48,6 +49,10 @@ void PrintingSelectorCardSelectionWidget::connectSignals()
|
||||
void PrintingSelectorCardSelectionWidget::selectSetForCards()
|
||||
{
|
||||
auto *setSelectionDialog = new DlgSelectSetForCards(nullptr, parent->getDeckModel());
|
||||
connect(setSelectionDialog, &DlgSelectSetForCards::deckAboutToBeModified, parent->getDeckEditor(),
|
||||
&AbstractTabDeckEditor::onDeckHistorySaveRequested);
|
||||
connect(setSelectionDialog, &DlgSelectSetForCards::deckModified, parent->getDeckEditor(),
|
||||
&AbstractTabDeckEditor::onDeckModified);
|
||||
if (!setSelectionDialog->exec()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -153,6 +153,7 @@ void AbstractTabDeckEditor::addCardHelper(const ExactCard &card, QString zoneNam
|
||||
card.getPrinting().getProperty("num")));
|
||||
|
||||
QModelIndex newCardIndex = deckDockWidget->deckModel->addCard(card, zoneName);
|
||||
deckDockWidget->expandAll();
|
||||
deckDockWidget->deckView->clearSelection();
|
||||
deckDockWidget->deckView->setCurrentIndex(newCardIndex);
|
||||
setModified(true);
|
||||
|
||||
@@ -28,21 +28,15 @@ DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_par
|
||||
flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded);
|
||||
|
||||
if (_deckList) {
|
||||
connectDeckList(_deckList);
|
||||
setDeckList(_deckList);
|
||||
}
|
||||
|
||||
layout->addWidget(flowWidget);
|
||||
}
|
||||
|
||||
void DeckPreviewDeckTagsDisplayWidget::connectDeckList(DeckList *_deckList)
|
||||
void DeckPreviewDeckTagsDisplayWidget::setDeckList(DeckList *_deckList)
|
||||
{
|
||||
if (deckList) {
|
||||
disconnect(deckList, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags);
|
||||
}
|
||||
|
||||
deckList = _deckList;
|
||||
connect(deckList, &DeckList::deckTagsChanged, this, &DeckPreviewDeckTagsDisplayWidget::refreshTags);
|
||||
|
||||
refreshTags();
|
||||
}
|
||||
|
||||
@@ -150,6 +144,7 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg()
|
||||
QStringList updatedTags = dialog.getActiveTags();
|
||||
deckList->setTags(updatedTags);
|
||||
deckPreviewWidget->deckLoader->saveToFile(deckPreviewWidget->filePath, DeckLoader::CockatriceFormat);
|
||||
refreshTags();
|
||||
}
|
||||
}
|
||||
} else if (parentWidget()) {
|
||||
@@ -181,6 +176,7 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg()
|
||||
QStringList updatedTags = dialog.getActiveTags();
|
||||
deckList->setTags(updatedTags);
|
||||
deckEditor->setModified(true);
|
||||
refreshTags();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class DeckPreviewDeckTagsDisplayWidget : public QWidget
|
||||
|
||||
public:
|
||||
explicit DeckPreviewDeckTagsDisplayWidget(QWidget *_parent, DeckList *_deckList);
|
||||
void connectDeckList(DeckList *_deckList);
|
||||
void setDeckList(DeckList *_deckList);
|
||||
void refreshTags();
|
||||
DeckList *deckList;
|
||||
FlowWidget *flowWidget;
|
||||
|
||||
@@ -87,6 +87,12 @@ DeckList::DeckList()
|
||||
root = new InnerDecklistNode;
|
||||
}
|
||||
|
||||
DeckList::DeckList(const DeckList &other)
|
||||
: metadata(other.metadata), sideboardPlans(other.sideboardPlans), root(new InnerDecklistNode(other.getRoot())),
|
||||
cachedDeckHash(other.cachedDeckHash)
|
||||
{
|
||||
}
|
||||
|
||||
DeckList::DeckList(const QString &nativeString)
|
||||
{
|
||||
root = new InnerDecklistNode;
|
||||
@@ -443,11 +449,8 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata)
|
||||
cardName.replace(diff.key(), diff.value());
|
||||
}
|
||||
|
||||
// Resolve complete card name, this function does nothing if the name is not found
|
||||
cardName = getCompleteCardName(cardName);
|
||||
|
||||
// Determine the zone (mainboard/sideboard)
|
||||
QString zoneName = getCardZoneFromName(cardName, sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN);
|
||||
QString zoneName = sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN;
|
||||
|
||||
// make new entry in decklist
|
||||
new DecklistCardNode(cardName, amount, getZoneObjFromName(zoneName), -1, setCode, collectorNumber);
|
||||
@@ -708,12 +711,11 @@ QString DeckList::getDeckHash() const
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the cached deckHash and emits the deckHashChanged signal.
|
||||
* Invalidates the cached deckHash.
|
||||
*/
|
||||
void DeckList::refreshDeckHash()
|
||||
{
|
||||
cachedDeckHash = QString();
|
||||
emit deckHashChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @file decklist.h
|
||||
* @file deck_list.h
|
||||
* @brief Defines the DeckList class and supporting types for managing a full
|
||||
* deck structure including cards, zones, sideboard plans, and
|
||||
* serialization to/from multiple formats. This is a logic class which
|
||||
@@ -93,7 +93,7 @@ public:
|
||||
* @brief Represents a complete deck, including metadata, zones, cards,
|
||||
* and sideboard plans.
|
||||
*
|
||||
* A DeckList is a QObject wrapper around an `InnerDecklistNode` tree,
|
||||
* A DeckList is a wrapper around an `InnerDecklistNode` tree,
|
||||
* enriched with metadata like deck name, comments, tags, banner card,
|
||||
* and multiple sideboard plans.
|
||||
*
|
||||
@@ -110,10 +110,6 @@ public:
|
||||
* - Owns the root `InnerDecklistNode` tree.
|
||||
* - Owns `SideboardPlan` instances stored in `sideboardPlans`.
|
||||
*
|
||||
* ### Signals:
|
||||
* - @c deckHashChanged() — emitted when the deck contents change.
|
||||
* - @c deckTagsChanged() — emitted when tags are added/removed.
|
||||
*
|
||||
* ### Example workflow:
|
||||
* ```
|
||||
* DeckList deck;
|
||||
@@ -123,10 +119,8 @@ public:
|
||||
* deck.saveToFile_Native(device);
|
||||
* ```
|
||||
*/
|
||||
class DeckList : public QObject
|
||||
class DeckList
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct Metadata
|
||||
{
|
||||
@@ -158,37 +152,7 @@ private:
|
||||
static void getCardRefListHelper(InnerDecklistNode *item, QList<CardRef> &result);
|
||||
InnerDecklistNode *getZoneObjFromName(const QString &zoneName);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Map a card name to its zone.
|
||||
* Override in subclasses for format-specific logic.
|
||||
* @param cardName Card being placed.
|
||||
* @param currentZoneName Zone candidate.
|
||||
* @return Zone name to use.
|
||||
*/
|
||||
virtual QString getCardZoneFromName(const QString /*cardName*/, QString currentZoneName)
|
||||
{
|
||||
return currentZoneName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Produce the complete display name of a card.
|
||||
* Override in subclasses to add set suffixes or annotations.
|
||||
* @param cardName Base name.
|
||||
* @return Full display name.
|
||||
*/
|
||||
virtual QString getCompleteCardName(const QString &cardName) const
|
||||
{
|
||||
return cardName;
|
||||
}
|
||||
|
||||
signals:
|
||||
/// Emitted when the deck hash changes.
|
||||
void deckHashChanged();
|
||||
/// Emitted when the deck tags are modified.
|
||||
void deckTagsChanged();
|
||||
|
||||
public slots:
|
||||
public:
|
||||
/// @name Metadata setters
|
||||
///@{
|
||||
void setName(const QString &_name = QString())
|
||||
@@ -202,17 +166,14 @@ public slots:
|
||||
void setTags(const QStringList &_tags = QStringList())
|
||||
{
|
||||
metadata.tags = _tags;
|
||||
emit deckTagsChanged();
|
||||
}
|
||||
void addTag(const QString &_tag)
|
||||
{
|
||||
metadata.tags.append(_tag);
|
||||
emit deckTagsChanged();
|
||||
}
|
||||
void clearTags()
|
||||
{
|
||||
metadata.tags.clear();
|
||||
emit deckTagsChanged();
|
||||
}
|
||||
void setBannerCard(const CardRef &_bannerCard = {})
|
||||
{
|
||||
@@ -224,15 +185,13 @@ public slots:
|
||||
}
|
||||
///@}
|
||||
|
||||
public:
|
||||
/// @brief Construct an empty deck.
|
||||
explicit DeckList();
|
||||
/// @brief Delete copy constructor.
|
||||
DeckList(const DeckList &) = delete;
|
||||
DeckList &operator=(const DeckList &) = delete;
|
||||
/// @brief Copy constructor (deep copies the node tree)
|
||||
DeckList(const DeckList &other);
|
||||
/// @brief Construct from a serialized native-format string.
|
||||
explicit DeckList(const QString &nativeString);
|
||||
~DeckList() override;
|
||||
virtual ~DeckList();
|
||||
|
||||
/// @name Metadata getters
|
||||
/// The individual metadata getters still exist for backwards compatibility.
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
DeckListModel::DeckListModel(QObject *parent)
|
||||
: QAbstractItemModel(parent), lastKnownColumn(1), lastKnownOrder(Qt::AscendingOrder)
|
||||
{
|
||||
// This class will leak the decklist object. We cannot safely delete it in the dtor because the deckList field is a
|
||||
// non-owning pointer and another deckList might have been assigned to it.
|
||||
// `DeckListModel::cleanList` also leaks for the same reason.
|
||||
// TODO: fix the leak
|
||||
deckList = new DeckList;
|
||||
deckList->setParent(this);
|
||||
root = new InnerDecklistNode;
|
||||
}
|
||||
|
||||
@@ -284,6 +287,7 @@ bool DeckListModel::setData(const QModelIndex &index, const QVariant &value, con
|
||||
|
||||
emitRecursiveUpdates(index);
|
||||
deckList->refreshDeckHash();
|
||||
emit deckHashChanged();
|
||||
|
||||
emit dataChanged(index, index);
|
||||
return true;
|
||||
@@ -422,6 +426,7 @@ QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneNam
|
||||
cardNode->setCardCollectorNumber(printingInfo.getProperty("num"));
|
||||
cardNode->setCardProviderId(printingInfo.getProperty("uuid"));
|
||||
deckList->refreshDeckHash();
|
||||
emit deckHashChanged();
|
||||
}
|
||||
sort(lastKnownColumn, lastKnownOrder);
|
||||
emitRecursiveUpdates(parentIndex);
|
||||
|
||||
@@ -6,12 +6,17 @@ 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/passwordhasher.cpp libcockatrice/utility/system_memory_querier.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/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/system_memory_querier.h
|
||||
libcockatrice/utility/trice_limits.h
|
||||
)
|
||||
|
||||
add_library(libcockatrice_utility STATIC ${UTILITY_SOURCES} ${UTILITY_HEADERS})
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
#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
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#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
|
||||
@@ -1,6 +1,7 @@
|
||||
#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"
|
||||
@@ -43,6 +44,7 @@
|
||||
#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"
|
||||
@@ -185,6 +187,23 @@ 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();
|
||||
|
||||
@@ -672,13 +691,21 @@ QString LoadTokensPage::getFileType()
|
||||
return tr("XML; token database (*.xml)");
|
||||
}
|
||||
|
||||
QString LoadTokensPage::getFilePromptName()
|
||||
{
|
||||
return tr("tokens");
|
||||
}
|
||||
|
||||
void LoadTokensPage::retranslateUi()
|
||||
{
|
||||
setTitle(tr("Tokens import"));
|
||||
setSubTitle(tr("Please specify a compatible source for token data."));
|
||||
|
||||
urlLabel->setText(tr("Download URL:"));
|
||||
urlRadioButton->setText(tr("Download URL:"));
|
||||
fileRadioButton->setText(tr("Local file:"));
|
||||
urlButton->setText(tr("Restore default URL"));
|
||||
fileButton->setText(tr("Choose file..."));
|
||||
|
||||
pathLabel->setText(tr("The token database will be saved at the following location:") + "<br>" +
|
||||
SettingsCache::instance().getTokenDatabasePath());
|
||||
defaultPathCheckBox->setText(tr("Save to a custom path (not recommended)"));
|
||||
@@ -709,13 +736,21 @@ QString LoadSpoilersPage::getFileType()
|
||||
return tr("XML; spoiler database (*.xml)");
|
||||
}
|
||||
|
||||
QString LoadSpoilersPage::getFilePromptName()
|
||||
{
|
||||
return tr("spoiler");
|
||||
}
|
||||
|
||||
void LoadSpoilersPage::retranslateUi()
|
||||
{
|
||||
setTitle(tr("Spoilers import"));
|
||||
setSubTitle(tr("Please specify a compatible source for spoiler data."));
|
||||
|
||||
urlLabel->setText(tr("Download URL:"));
|
||||
urlRadioButton->setText(tr("Download URL:"));
|
||||
fileRadioButton->setText(tr("Local file:"));
|
||||
urlButton->setText(tr("Restore default URL"));
|
||||
fileButton->setText(tr("Choose file..."));
|
||||
|
||||
pathLabel->setText(tr("The spoiler database will be saved at the following location:") + "<br>" +
|
||||
SettingsCache::instance().getSpoilerCardDatabasePath());
|
||||
defaultPathCheckBox->setText(tr("Save to a custom path (not recommended)"));
|
||||
|
||||
@@ -131,6 +131,7 @@ protected:
|
||||
QString getDefaultSavePath() override;
|
||||
QString getWindowTitle() override;
|
||||
QString getFileType() override;
|
||||
QString getFilePromptName() override;
|
||||
};
|
||||
|
||||
class LoadTokensPage : public SimpleDownloadFilePage
|
||||
@@ -148,6 +149,7 @@ protected:
|
||||
QString getDefaultSavePath() override;
|
||||
QString getWindowTitle() override;
|
||||
QString getFileType() override;
|
||||
QString getFilePromptName() override;
|
||||
void initializePage() override;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,32 +12,43 @@
|
||||
#include <QNetworkReply>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QtGui>
|
||||
|
||||
SimpleDownloadFilePage::SimpleDownloadFilePage(QWidget *parent) : OracleWizardPage(parent)
|
||||
{
|
||||
urlLabel = new QLabel(this);
|
||||
urlRadioButton = new QRadioButton(this);
|
||||
fileRadioButton = new QRadioButton(this);
|
||||
|
||||
urlLineEdit = new QLineEdit(this);
|
||||
fileLineEdit = new QLineEdit(this);
|
||||
|
||||
progressLabel = new QLabel(this);
|
||||
progressBar = new QProgressBar(this);
|
||||
|
||||
urlRadioButton->setChecked(true);
|
||||
|
||||
urlButton = new QPushButton(this);
|
||||
connect(urlButton, &QPushButton::clicked, this, &SimpleDownloadFilePage::actRestoreDefaultUrl);
|
||||
|
||||
defaultPathCheckBox = new QCheckBox(this);
|
||||
fileButton = new QPushButton(this);
|
||||
connect(fileButton, &QPushButton::clicked, this, &SimpleDownloadFilePage::actLoadCardFile);
|
||||
|
||||
defaultPathCheckBox = new QCheckBox(this);
|
||||
pathLabel = new QLabel(this);
|
||||
pathLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
||||
|
||||
auto *layout = new QGridLayout(this);
|
||||
layout->addWidget(urlLabel, 0, 0);
|
||||
layout->addWidget(urlRadioButton, 0, 0);
|
||||
layout->addWidget(urlLineEdit, 0, 1);
|
||||
layout->addWidget(urlButton, 1, 1, Qt::AlignRight);
|
||||
layout->addWidget(pathLabel, 2, 0, 1, 2);
|
||||
layout->addWidget(defaultPathCheckBox, 3, 0, 1, 2);
|
||||
layout->addWidget(progressLabel, 4, 0);
|
||||
layout->addWidget(progressBar, 4, 1);
|
||||
layout->addWidget(fileRadioButton, 2, 0);
|
||||
layout->addWidget(fileLineEdit, 2, 1);
|
||||
layout->addWidget(fileButton, 3, 1, Qt::AlignRight);
|
||||
layout->addWidget(pathLabel, 4, 0, 1, 2);
|
||||
layout->addWidget(defaultPathCheckBox, 5, 0, 1, 2);
|
||||
layout->addWidget(progressLabel, 6, 0);
|
||||
layout->addWidget(progressBar, 6, 1);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
@@ -56,6 +67,31 @@ void SimpleDownloadFilePage::actRestoreDefaultUrl()
|
||||
urlLineEdit->setText(getDefaultUrl());
|
||||
}
|
||||
|
||||
void SimpleDownloadFilePage::actLoadCardFile()
|
||||
{
|
||||
QFileDialog dialog(this, tr("Load %1 file").arg(getFilePromptName()));
|
||||
dialog.setFileMode(QFileDialog::ExistingFile);
|
||||
|
||||
QString extensions = "*.json *.xml";
|
||||
#ifdef HAS_ZLIB
|
||||
extensions += " *.zip";
|
||||
#endif
|
||||
#ifdef HAS_LZMA
|
||||
extensions += " *.xz";
|
||||
#endif
|
||||
dialog.setNameFilter(tr("%1 file (%1)").arg(getFilePromptName(), extensions));
|
||||
|
||||
if (!fileLineEdit->text().isEmpty() && QFile::exists(fileLineEdit->text())) {
|
||||
dialog.selectFile(fileLineEdit->text());
|
||||
}
|
||||
|
||||
if (!dialog.exec()) {
|
||||
return;
|
||||
}
|
||||
|
||||
fileLineEdit->setText(dialog.selectedFiles().at(0));
|
||||
}
|
||||
|
||||
bool SimpleDownloadFilePage::validatePage()
|
||||
{
|
||||
// if data has already been downloaded, pass directly to the "save" step
|
||||
@@ -68,22 +104,41 @@ bool SimpleDownloadFilePage::validatePage()
|
||||
}
|
||||
}
|
||||
|
||||
QUrl url = QUrl::fromUserInput(urlLineEdit->text());
|
||||
if (!url.isValid()) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("The provided URL is not valid: ") + url.toString());
|
||||
return false;
|
||||
// else, try to import sets
|
||||
if (urlRadioButton->isChecked()) {
|
||||
QUrl url = QUrl::fromUserInput(urlLineEdit->text());
|
||||
if (!url.isValid()) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("The provided URL is not valid: ") + url.toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
progressLabel->setText(tr("Downloading (0MB)"));
|
||||
// show an infinite progressbar
|
||||
progressBar->setMaximum(0);
|
||||
progressBar->setMinimum(0);
|
||||
progressBar->setValue(0);
|
||||
progressLabel->show();
|
||||
progressBar->show();
|
||||
|
||||
wizard()->disableButtons();
|
||||
downloadFile(url);
|
||||
|
||||
} else if (fileRadioButton->isChecked()) {
|
||||
QFile cardFile(fileLineEdit->text());
|
||||
if (!cardFile.exists()) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Please choose a file."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!cardFile.open(QIODevice::ReadOnly)) {
|
||||
QMessageBox::critical(nullptr, tr("Error"), tr("Cannot open file '%1'.").arg(fileLineEdit->text()));
|
||||
return false;
|
||||
}
|
||||
|
||||
downloadData = cardFile.readAll();
|
||||
wizard()->next();
|
||||
}
|
||||
|
||||
progressLabel->setText(tr("Downloading (0MB)"));
|
||||
// show an infinite progressbar
|
||||
progressBar->setMaximum(0);
|
||||
progressBar->setMinimum(0);
|
||||
progressBar->setValue(0);
|
||||
progressLabel->show();
|
||||
progressBar->show();
|
||||
|
||||
wizard()->disableButtons();
|
||||
downloadFile(url);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <QWizardPage>
|
||||
|
||||
class QFile;
|
||||
class QRadioButton;
|
||||
class OracleWizard;
|
||||
class QCheckBox;
|
||||
class QLabel;
|
||||
@@ -43,15 +45,19 @@ protected:
|
||||
virtual QString getDefaultSavePath() = 0;
|
||||
virtual QString getWindowTitle() = 0;
|
||||
virtual QString getFileType() = 0;
|
||||
virtual QString getFilePromptName() = 0;
|
||||
bool saveToFile();
|
||||
bool internalSaveToFile(const QString &fileName);
|
||||
|
||||
protected:
|
||||
QByteArray downloadData;
|
||||
QLabel *urlLabel;
|
||||
QLabel *pathLabel;
|
||||
QRadioButton *urlRadioButton;
|
||||
QRadioButton *fileRadioButton;
|
||||
QLineEdit *urlLineEdit;
|
||||
QLineEdit *fileLineEdit;
|
||||
QPushButton *urlButton;
|
||||
QPushButton *fileButton;
|
||||
QLabel *pathLabel;
|
||||
QLabel *progressLabel;
|
||||
QProgressBar *progressBar;
|
||||
QCheckBox *defaultPathCheckBox;
|
||||
@@ -60,6 +66,7 @@ signals:
|
||||
void parsedDataReady();
|
||||
private slots:
|
||||
void actRestoreDefaultUrl();
|
||||
void actLoadCardFile();
|
||||
void actDownloadProgress(qint64 received, qint64 total);
|
||||
void actDownloadFinished();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user