Initial commit of mtgonline project
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(HEADERS
|
||||
card_database_model.h
|
||||
card_database_display_model.h
|
||||
card/card_completer_proxy_model.h
|
||||
card/card_search_model.h
|
||||
card_set/card_sets_model.h
|
||||
token/token_display_model.h
|
||||
token/token_edit_model.h
|
||||
)
|
||||
|
||||
if(Qt6_FOUND)
|
||||
qt6_wrap_cpp(MOC_SOURCES ${HEADERS})
|
||||
elseif(Qt5_FOUND)
|
||||
qt5_wrap_cpp(MOC_SOURCES ${HEADERS})
|
||||
endif()
|
||||
|
||||
add_library(
|
||||
libcockatrice_models_database STATIC
|
||||
${MOC_SOURCES}
|
||||
card_database_model.cpp
|
||||
card_database_display_model.cpp
|
||||
card/card_completer_proxy_model.cpp
|
||||
card/card_search_model.cpp
|
||||
card_set/card_sets_model.cpp
|
||||
token/token_display_model.cpp
|
||||
token/token_edit_model.cpp
|
||||
)
|
||||
|
||||
target_include_directories(libcockatrice_models_database PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
target_link_libraries(libcockatrice_models_database PUBLIC libcockatrice_card libcockatrice_filters ${QT_CORE_MODULE})
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
#include "card_completer_proxy_model.h"
|
||||
|
||||
CardCompleterProxyModel::CardCompleterProxyModel(QObject *parent) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool CardCompleterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
if (filterRegularExpression().pattern().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
QString data = index.data(Qt::DisplayRole).toString();
|
||||
|
||||
// Ensure substring matching
|
||||
return data.contains(filterRegularExpression());
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* @file card_completer_proxy_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef CARD_COMPLETER_PROXY_MODEL_H
|
||||
#define CARD_COMPLETER_PROXY_MODEL_H
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class CardCompleterProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CardCompleterProxyModel(QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
[[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
};
|
||||
|
||||
#endif // CARD_COMPLETER_PROXY_MODEL_H
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "card_search_model.h"
|
||||
|
||||
#include "../card_database_display_model.h"
|
||||
#include "../card_database_model.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <libcockatrice/utility/levenshtein.h>
|
||||
|
||||
CardSearchModel::CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent)
|
||||
: QAbstractListModel(parent), sourceModel(sourceModel)
|
||||
{
|
||||
}
|
||||
|
||||
int CardSearchModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return searchResults.size();
|
||||
}
|
||||
|
||||
QVariant CardSearchModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() >= searchResults.size()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
return searchResults.at(index.row()).card->getName();
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void CardSearchModel::updateSearchResults(const QString &query)
|
||||
{
|
||||
beginResetModel();
|
||||
searchResults.clear();
|
||||
|
||||
if (query.isEmpty() || !sourceModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the filter for the display model
|
||||
sourceModel->setCardName(query);
|
||||
|
||||
// Collect matching cards and compute Levenshtein distance
|
||||
for (int i = 0; i < sourceModel->rowCount(); ++i) {
|
||||
QModelIndex modelIndex = sourceModel->index(i, 0);
|
||||
QModelIndex sourceIndex = sourceModel->mapToSource(modelIndex);
|
||||
CardDatabaseModel *sourceDbModel = qobject_cast<CardDatabaseModel *>(sourceModel->sourceModel());
|
||||
|
||||
if (!sourceDbModel || !sourceIndex.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
CardInfoPtr card = sourceDbModel->getCard(sourceIndex.row());
|
||||
|
||||
if (!card) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int distance = levenshteinDistance(query.toLower(), card->getName().toLower());
|
||||
searchResults.append({card, distance});
|
||||
}
|
||||
|
||||
// Sort by Levenshtein distance (lower distance = better match)
|
||||
std::sort(searchResults.begin(), searchResults.end(),
|
||||
[](const SearchResult &a, const SearchResult &b) { return a.distance < b.distance; });
|
||||
|
||||
// Keep only the top 5 results
|
||||
if (searchResults.size() > 10) {
|
||||
searchResults = searchResults.mid(0, 10);
|
||||
}
|
||||
|
||||
emit dataChanged(index(0, 0), index(rowCount() - 1, 0));
|
||||
emit layoutChanged();
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @file card_search_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef CARD_SEARCH_MODEL_H
|
||||
#define CARD_SEARCH_MODEL_H
|
||||
|
||||
#include "../card_database_display_model.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
class CardSearchModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
void updateSearchResults(const QString &query); // Update results based on input
|
||||
|
||||
private:
|
||||
struct SearchResult
|
||||
{
|
||||
CardInfoPtr card;
|
||||
int distance;
|
||||
};
|
||||
|
||||
CardDatabaseDisplayModel *sourceModel;
|
||||
QList<SearchResult> searchResults;
|
||||
};
|
||||
|
||||
#endif // CARD_SEARCH_MODEL_H
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
#include "card_database_display_model.h"
|
||||
|
||||
#include "card_database_model.h"
|
||||
|
||||
CardDatabaseDisplayModel::CardDatabaseDisplayModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent), isToken(ShowAll), filterString(nullptr)
|
||||
{
|
||||
filterTree = nullptr;
|
||||
setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
|
||||
dirtyTimer.setSingleShot(true);
|
||||
connect(&dirtyTimer, &QTimer::timeout, this, [this]() {
|
||||
invalidate();
|
||||
emit modelDirty();
|
||||
});
|
||||
|
||||
loadedRowCount = 0;
|
||||
}
|
||||
|
||||
void CardDatabaseDisplayModel::setSourceModel(QAbstractItemModel *model)
|
||||
{
|
||||
QSortFilterProxyModel::setSourceModel(model);
|
||||
|
||||
connect(model, &QAbstractItemModel::rowsInserted, this, [this]() { dirty(); });
|
||||
|
||||
connect(model, &QAbstractItemModel::rowsRemoved, this, [this]() { dirty(); });
|
||||
|
||||
connect(model, &QAbstractItemModel::modelReset, this, [this]() {
|
||||
loadedRowCount = 0;
|
||||
dirty();
|
||||
});
|
||||
}
|
||||
|
||||
QMap<wchar_t, wchar_t> CardDatabaseDisplayModel::characterTranslation = {{L'“', L'\"'},
|
||||
{L'”', L'\"'},
|
||||
{L'‘', L'\''},
|
||||
{L'’', L'\''}};
|
||||
|
||||
bool CardDatabaseDisplayModel::canFetchMore(const QModelIndex &index) const
|
||||
{
|
||||
return loadedRowCount < sourceModel()->rowCount(index);
|
||||
}
|
||||
|
||||
void CardDatabaseDisplayModel::fetchMore(const QModelIndex &index)
|
||||
{
|
||||
int remainder = sourceModel()->rowCount(index) - loadedRowCount;
|
||||
int itemsToFetch = qMin(100, remainder);
|
||||
|
||||
if (itemsToFetch == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto startIndex = qMin(rowCount(QModelIndex()), loadedRowCount);
|
||||
beginInsertRows(QModelIndex(), startIndex, startIndex + itemsToFetch - 1);
|
||||
|
||||
loadedRowCount += itemsToFetch;
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
int CardDatabaseDisplayModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return QSortFilterProxyModel::rowCount(parent);
|
||||
}
|
||||
|
||||
bool CardDatabaseDisplayModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||
{
|
||||
|
||||
QString leftString = sourceModel()->data(left, CardDatabaseModel::SortRole).toString();
|
||||
QString rightString = sourceModel()->data(right, CardDatabaseModel::SortRole).toString();
|
||||
|
||||
if (!cardName.isEmpty() && left.column() == CardDatabaseModel::NameColumn) {
|
||||
bool isLeftType = leftString.startsWith(cardName, Qt::CaseInsensitive);
|
||||
bool isRightType = rightString.startsWith(cardName, Qt::CaseInsensitive);
|
||||
|
||||
// test for an exact match: isLeftType && leftString.size() == cardName.size()
|
||||
// or an exclusive start match: isLeftType && !isRightType
|
||||
if (isLeftType && (!isRightType || leftString.size() == cardName.size())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// same checks for the right string
|
||||
if (isRightType && (!isLeftType || rightString.size() == cardName.size())) {
|
||||
return false;
|
||||
}
|
||||
} else if (right.column() == CardDatabaseModel::PTColumn && left.column() == CardDatabaseModel::PTColumn) {
|
||||
QStringList leftList = leftString.split("/");
|
||||
QStringList rightList = rightString.split("/");
|
||||
|
||||
if (leftList.size() == 2 && rightList.size() == 2) {
|
||||
|
||||
// cool, have both P/T in list now
|
||||
int lessThanNum = lessThanNumerically(leftList.at(0), rightList.at(0));
|
||||
if (lessThanNum != 0) {
|
||||
return lessThanNum < 0;
|
||||
} else {
|
||||
// power equal, check toughness
|
||||
return lessThanNumerically(leftList.at(1), rightList.at(1)) < 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return QString::localeAwareCompare(leftString, rightString) < 0;
|
||||
}
|
||||
|
||||
int CardDatabaseDisplayModel::lessThanNumerically(const QString &left, const QString &right)
|
||||
{
|
||||
if (left == right) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool okLeft, okRight;
|
||||
float leftNum = left.toFloat(&okLeft);
|
||||
float rightNum = right.toFloat(&okRight);
|
||||
|
||||
if (okLeft && okRight) {
|
||||
if (leftNum < rightNum) {
|
||||
return -1;
|
||||
} else if (leftNum > rightNum) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// try and parsing again, for weird ones like "1+*"
|
||||
QString leftAfterNum = "";
|
||||
QString rightAfterNum = "";
|
||||
if (!okLeft) {
|
||||
int leftNumIndex = 0;
|
||||
for (; leftNumIndex < left.length(); leftNumIndex++) {
|
||||
if (!left.at(leftNumIndex).isDigit()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (leftNumIndex != 0) {
|
||||
leftNum = left.left(leftNumIndex).toFloat(&okLeft);
|
||||
leftAfterNum = left.right(leftNumIndex);
|
||||
}
|
||||
}
|
||||
if (!okRight) {
|
||||
int rightNumIndex = 0;
|
||||
for (; rightNumIndex < right.length(); rightNumIndex++) {
|
||||
if (!right.at(rightNumIndex).isDigit()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rightNumIndex != 0) {
|
||||
rightNum = right.left(rightNumIndex).toFloat(&okRight);
|
||||
rightAfterNum = right.right(rightNumIndex);
|
||||
}
|
||||
}
|
||||
if (okLeft && okRight) {
|
||||
|
||||
if (leftNum != rightNum) {
|
||||
// both parsed as numbers, but different number
|
||||
if (leftNum < rightNum) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
// both parsed, same number, but at least one has something else
|
||||
// so compare the part after the number - prefer nothing
|
||||
return QString::localeAwareCompare(leftAfterNum, rightAfterNum);
|
||||
}
|
||||
} else if (okLeft) {
|
||||
return -1;
|
||||
} else if (okRight) {
|
||||
return 1;
|
||||
}
|
||||
// couldn't parse it, just return String comparison
|
||||
return QString::localeAwareCompare(left, right);
|
||||
}
|
||||
bool CardDatabaseDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
|
||||
{
|
||||
CardInfoPtr info = static_cast<CardDatabaseModel *>(sourceModel())->getCard(sourceRow);
|
||||
|
||||
if (((isToken == ShowTrue) && !info->getIsToken()) || ((isToken == ShowFalse) && info->getIsToken())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filterString != nullptr) {
|
||||
if (filterTree != nullptr && !filterTree->acceptsCard(info)) {
|
||||
return false;
|
||||
}
|
||||
return filterString->check(info);
|
||||
}
|
||||
|
||||
return rowMatchesCardName(info);
|
||||
}
|
||||
|
||||
bool CardDatabaseDisplayModel::rowMatchesCardName(CardInfoPtr info) const
|
||||
{
|
||||
if (!cardName.isEmpty() && !info->getName().contains(cardName, Qt::CaseInsensitive)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!cardNameSet.isEmpty() && !cardNameSet.contains(info->getName())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filterTree != nullptr) {
|
||||
return filterTree->acceptsCard(info);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CardDatabaseDisplayModel::clearFilterAll()
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0))
|
||||
beginFilterChange();
|
||||
#endif
|
||||
cardName.clear();
|
||||
cardText.clear();
|
||||
cardTypes.clear();
|
||||
cardColors.clear();
|
||||
if (filterTree != nullptr) {
|
||||
filterTree->clear();
|
||||
}
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 10, 0))
|
||||
endFilterChange(QSortFilterProxyModel::Direction::Rows);
|
||||
#else
|
||||
invalidateFilter();
|
||||
#endif
|
||||
}
|
||||
|
||||
void CardDatabaseDisplayModel::setFilterTree(FilterTree *_filterTree)
|
||||
{
|
||||
if (this->filterTree != nullptr) {
|
||||
disconnect(this->filterTree, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
this->filterTree = _filterTree;
|
||||
connect(this->filterTree, &FilterTree::changed, this, &CardDatabaseDisplayModel::filterTreeChanged);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void CardDatabaseDisplayModel::filterTreeChanged()
|
||||
{
|
||||
invalidate();
|
||||
}
|
||||
|
||||
const QString CardDatabaseDisplayModel::sanitizeCardName(const QString &dirtyName, const QMap<wchar_t, wchar_t> &table)
|
||||
{
|
||||
std::wstring toReturn = dirtyName.toStdWString();
|
||||
for (wchar_t &ch : toReturn) {
|
||||
if (table.contains(ch)) {
|
||||
ch = table.value(ch);
|
||||
}
|
||||
}
|
||||
return QString::fromStdWString(toReturn);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* @file card_database_display_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
* @brief The CardDatabaseDisplayModel is a QSortFilterProxyModel that allows applying filters and sorting to a
|
||||
* CardDatabaseModel.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_CARD_DATABASE_DISPLAY_MODEL_H
|
||||
#define COCKATRICE_CARD_DATABASE_DISPLAY_MODEL_H
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QTimer>
|
||||
#include <libcockatrice/filters/filter_string.h>
|
||||
|
||||
class FilterTree;
|
||||
class CardDatabaseDisplayModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum FilterBool
|
||||
{
|
||||
ShowTrue,
|
||||
ShowFalse,
|
||||
ShowAll
|
||||
};
|
||||
|
||||
private:
|
||||
FilterBool isToken;
|
||||
QString cardName, cardText;
|
||||
QSet<QString> cardNameSet, cardTypes, cardColors;
|
||||
FilterTree *filterTree;
|
||||
FilterString *filterString;
|
||||
int loadedRowCount;
|
||||
QTimer dirtyTimer;
|
||||
|
||||
/** The translation table that will be used for sanitizeCardName. */
|
||||
static QMap<wchar_t, wchar_t> characterTranslation;
|
||||
|
||||
public:
|
||||
explicit CardDatabaseDisplayModel(QObject *parent = nullptr);
|
||||
void setSourceModel(QAbstractItemModel *model) override;
|
||||
void setFilterTree(FilterTree *_filterTree);
|
||||
void setIsToken(FilterBool _isToken)
|
||||
{
|
||||
isToken = _isToken;
|
||||
dirty();
|
||||
}
|
||||
|
||||
void setCardName(const QString &_cardName)
|
||||
{
|
||||
if (filterString != nullptr) {
|
||||
delete filterString;
|
||||
filterString = nullptr;
|
||||
}
|
||||
cardName = sanitizeCardName(_cardName, characterTranslation);
|
||||
dirty();
|
||||
}
|
||||
void setStringFilter(const QString &_src)
|
||||
{
|
||||
delete filterString;
|
||||
filterString = new FilterString(_src);
|
||||
dirty();
|
||||
}
|
||||
void setCardNameSet(const QSet<QString> &_cardNameSet)
|
||||
{
|
||||
cardNameSet = _cardNameSet;
|
||||
dirty();
|
||||
}
|
||||
|
||||
void dirty()
|
||||
{
|
||||
dirtyTimer.start(20);
|
||||
}
|
||||
void clearFilterAll();
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
[[nodiscard]] bool canFetchMore(const QModelIndex &parent) const override;
|
||||
void fetchMore(const QModelIndex &parent) override;
|
||||
signals:
|
||||
void modelDirty();
|
||||
|
||||
protected:
|
||||
[[nodiscard]] bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||
static int lessThanNumerically(const QString &left, const QString &right);
|
||||
[[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
[[nodiscard]] bool rowMatchesCardName(CardInfoPtr info) const;
|
||||
|
||||
private slots:
|
||||
void filterTreeChanged();
|
||||
/** Will translate all undesirable characters in DIRTYNAME according to the TABLE. */
|
||||
const QString sanitizeCardName(const QString &dirtyName, const QMap<wchar_t, wchar_t> &table);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_CARD_DATABASE_DISPLAY_MODEL_H
|
||||
@@ -0,0 +1,153 @@
|
||||
#include "card_database_model.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <libcockatrice/card/database/card_database.h>
|
||||
|
||||
#define CARDDBMODEL_COLUMNS 6
|
||||
|
||||
CardDatabaseModel::CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromEnabledSets, QObject *parent)
|
||||
: QAbstractListModel(parent), db(_db), showOnlyCardsFromEnabledSets(_showOnlyCardsFromEnabledSets)
|
||||
{
|
||||
connect(db, &CardDatabase::cardAdded, this, &CardDatabaseModel::cardAdded);
|
||||
connect(db, &CardDatabase::cardRemoved, this, &CardDatabaseModel::cardRemoved);
|
||||
connect(db, &CardDatabase::cardDatabaseEnabledSetsChanged, this,
|
||||
&CardDatabaseModel::cardDatabaseEnabledSetsChanged);
|
||||
|
||||
cardDatabaseEnabledSetsChanged();
|
||||
}
|
||||
|
||||
CardDatabaseModel::~CardDatabaseModel() = default;
|
||||
|
||||
int CardDatabaseModel::rowCount(const QModelIndex & /*parent*/) const
|
||||
{
|
||||
return cardList.size();
|
||||
}
|
||||
|
||||
int CardDatabaseModel::columnCount(const QModelIndex & /*parent*/) const
|
||||
{
|
||||
return CARDDBMODEL_COLUMNS;
|
||||
}
|
||||
|
||||
QVariant CardDatabaseModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() >= cardList.size() || index.column() >= CARDDBMODEL_COLUMNS ||
|
||||
(role != Qt::DisplayRole && role != SortRole)) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
CardInfoPtr card = cardList.at(index.row());
|
||||
switch (index.column()) {
|
||||
case NameColumn:
|
||||
return card->getName();
|
||||
case SetListColumn:
|
||||
return card->getSetsNames();
|
||||
case ManaCostColumn:
|
||||
return role == SortRole ? QString("%1%2").arg(card->getCmc(), 4, QChar('0')).arg(card->getManaCost())
|
||||
: card->getManaCost();
|
||||
case CardTypeColumn:
|
||||
return card->getCardType();
|
||||
case PTColumn:
|
||||
return card->getPowTough();
|
||||
case ColorColumn:
|
||||
return card->getColors();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant CardDatabaseModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (role != Qt::DisplayRole) {
|
||||
return QVariant();
|
||||
}
|
||||
if (orientation != Qt::Horizontal) {
|
||||
return QVariant();
|
||||
}
|
||||
switch (section) {
|
||||
case NameColumn:
|
||||
return QString(tr("Name"));
|
||||
case SetListColumn:
|
||||
return QString(tr("Sets"));
|
||||
case ManaCostColumn:
|
||||
return QString(tr("Mana cost"));
|
||||
case CardTypeColumn:
|
||||
return QString(tr("Card type"));
|
||||
case PTColumn:
|
||||
return QString(tr("P/T"));
|
||||
case ColorColumn:
|
||||
return QString(tr("Color(s)"));
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabaseModel::cardInfoChanged(const CardInfoPtr &card)
|
||||
{
|
||||
const int row = cardList.indexOf(card);
|
||||
if (row == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit dataChanged(index(row, 0), index(row, CARDDBMODEL_COLUMNS - 1));
|
||||
}
|
||||
|
||||
bool CardDatabaseModel::checkCardHasAtLeastOneEnabledSet(const CardInfoPtr &card) const
|
||||
{
|
||||
if (!showOnlyCardsFromEnabledSets) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const auto &printings : card->getSets()) {
|
||||
for (const auto &printing : printings) {
|
||||
if (printing.getSet()->getEnabled()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CardDatabaseModel::cardDatabaseEnabledSetsChanged()
|
||||
{
|
||||
// remove all the cards no more present in at least one enabled set
|
||||
for (const CardInfoPtr &card : cardList) {
|
||||
if (!checkCardHasAtLeastOneEnabledSet(card)) {
|
||||
cardRemoved(card);
|
||||
}
|
||||
}
|
||||
|
||||
// re-check all the card currently not shown, maybe their part of a newly-enabled set
|
||||
for (const CardInfoPtr &card : db->getCardList()) {
|
||||
if (!cardListSet.contains(card)) {
|
||||
cardAdded(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabaseModel::cardAdded(const CardInfoPtr &card)
|
||||
{
|
||||
if (checkCardHasAtLeastOneEnabledSet(card)) {
|
||||
// add the card if it's present in at least one enabled set
|
||||
beginInsertRows(QModelIndex(), cardList.size(), cardList.size());
|
||||
cardList.append(card);
|
||||
cardListSet.insert(card);
|
||||
connect(card.data(), &CardInfo::cardInfoChanged, this, &CardDatabaseModel::cardInfoChanged);
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
|
||||
void CardDatabaseModel::cardRemoved(CardInfoPtr card)
|
||||
{
|
||||
const int row = cardList.indexOf(card);
|
||||
if (row == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
disconnect(card.data(), nullptr, this, nullptr);
|
||||
cardListSet.remove(card);
|
||||
card.clear();
|
||||
cardList.removeAt(row);
|
||||
endRemoveRows();
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @file card_database_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
* @brief The CardDatabaseModel maps the cardList contained in the CardDatabase as a QAbstractListModel.
|
||||
*/
|
||||
|
||||
#ifndef CARDDATABASEMODEL_H
|
||||
#define CARDDATABASEMODEL_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <libcockatrice/card/database/card_database.h>
|
||||
|
||||
class CardDatabaseModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Columns
|
||||
{
|
||||
NameColumn,
|
||||
SetListColumn,
|
||||
ManaCostColumn,
|
||||
PTColumn,
|
||||
CardTypeColumn,
|
||||
ColorColumn
|
||||
};
|
||||
enum Role
|
||||
{
|
||||
SortRole = Qt::UserRole
|
||||
};
|
||||
CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromEnabledSets, QObject *parent = nullptr);
|
||||
~CardDatabaseModel() override;
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
[[nodiscard]] int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
|
||||
[[nodiscard]] QVariant
|
||||
headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
[[nodiscard]] CardDatabase *getDatabase() const
|
||||
{
|
||||
return db;
|
||||
}
|
||||
[[nodiscard]] CardInfoPtr getCard(int index) const
|
||||
{
|
||||
return cardList[index];
|
||||
}
|
||||
|
||||
private:
|
||||
QList<CardInfoPtr> cardList;
|
||||
QSet<CardInfoPtr> cardListSet; // Supports faster lookups in cardDatabaseEnabledSetsChanged()
|
||||
CardDatabase *db;
|
||||
bool showOnlyCardsFromEnabledSets;
|
||||
|
||||
inline bool checkCardHasAtLeastOneEnabledSet(const CardInfoPtr &card) const;
|
||||
private slots:
|
||||
void cardAdded(const CardInfoPtr &card);
|
||||
void cardRemoved(CardInfoPtr card);
|
||||
void cardInfoChanged(const CardInfoPtr &card);
|
||||
void cardDatabaseEnabledSetsChanged();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,322 @@
|
||||
#include "card_sets_model.h"
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
SetsModel::SetsModel(CardDatabase *_db, QObject *parent) : QAbstractTableModel(parent), sets(_db->getSetList())
|
||||
{
|
||||
sets.sortByKey();
|
||||
for (const CardSetPtr &set : sets) {
|
||||
if (set->getEnabled()) {
|
||||
enabledSets.insert(set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetsModel::~SetsModel() = default;
|
||||
|
||||
int SetsModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
} else {
|
||||
return sets.size();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant SetsModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || (index.column() >= NUM_COLS) || (index.row() >= rowCount())) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
CardSetPtr set = sets[index.row()];
|
||||
|
||||
if (index.column() == EnabledCol) {
|
||||
switch (role) {
|
||||
case SortRole:
|
||||
return enabledSets.contains(set) ? "1" : "0";
|
||||
case Qt::CheckStateRole:
|
||||
return static_cast<int>(enabledSets.contains(set) ? Qt::Checked : Qt::Unchecked);
|
||||
case Qt::DisplayRole:
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
if (role != Qt::DisplayRole && role != SortRole) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
switch (index.column()) {
|
||||
case SortKeyCol:
|
||||
return QString("%1").arg(index.row(), 8, 10, QChar('0'));
|
||||
case IsKnownCol:
|
||||
return set->getIsKnown();
|
||||
case SetTypeCol:
|
||||
return set->getSetType();
|
||||
case ShortNameCol:
|
||||
return set->getShortName();
|
||||
case LongNameCol:
|
||||
return set->getLongName();
|
||||
case ReleaseDateCol:
|
||||
return set->getReleaseDate().toString(Qt::ISODate);
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
bool SetsModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
if (role == Qt::CheckStateRole && index.column() == EnabledCol) {
|
||||
toggleRow(index.row(), value == Qt::Checked);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant SetsModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if ((role != Qt::DisplayRole) || (orientation != Qt::Horizontal)) {
|
||||
return QVariant();
|
||||
}
|
||||
switch (section) {
|
||||
case SortKeyCol:
|
||||
return QString("Key"); /* no tr() for translations needed, column just used for sorting --> hidden */
|
||||
case IsKnownCol:
|
||||
return QString(
|
||||
"Is known"); /* no tr() for translations needed, column is just used for sorting --> hidden */
|
||||
case EnabledCol:
|
||||
return tr("Enabled");
|
||||
case SetTypeCol:
|
||||
return tr("Set type");
|
||||
case ShortNameCol:
|
||||
return tr("Set code");
|
||||
case LongNameCol:
|
||||
return tr("Long name");
|
||||
case ReleaseDateCol:
|
||||
return tr("Release date");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
Qt::ItemFlags SetsModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return Qt::NoItemFlags;
|
||||
}
|
||||
|
||||
Qt::ItemFlags flags = QAbstractTableModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
|
||||
|
||||
if (index.column() == EnabledCol) {
|
||||
flags |= Qt::ItemIsUserCheckable;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
Qt::DropActions SetsModel::supportedDropActions() const
|
||||
{
|
||||
return Qt::MoveAction;
|
||||
}
|
||||
|
||||
QMimeData *SetsModel::mimeData(const QModelIndexList &indexes) const
|
||||
{
|
||||
if (indexes.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
SetsMimeData *result = new SetsMimeData(indexes[0].row());
|
||||
return qobject_cast<QMimeData *>(result);
|
||||
}
|
||||
|
||||
bool SetsModel::dropMimeData(const QMimeData *data,
|
||||
Qt::DropAction action,
|
||||
int row,
|
||||
int /*column*/,
|
||||
const QModelIndex &parent)
|
||||
{
|
||||
if (action != Qt::MoveAction) {
|
||||
return false;
|
||||
}
|
||||
if (row == -1) {
|
||||
if (!parent.isValid()) {
|
||||
return false;
|
||||
}
|
||||
row = parent.row();
|
||||
}
|
||||
int oldRow = qobject_cast<const SetsMimeData *>(data)->getOldRow();
|
||||
if (oldRow < row) {
|
||||
row--;
|
||||
}
|
||||
|
||||
swapRows(oldRow, row);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetsModel::toggleRow(int row, bool enable)
|
||||
{
|
||||
CardSetPtr temp = sets.at(row);
|
||||
|
||||
if (enable) {
|
||||
enabledSets.insert(temp);
|
||||
} else {
|
||||
enabledSets.remove(temp);
|
||||
}
|
||||
|
||||
emit dataChanged(index(row, EnabledCol), index(row, EnabledCol));
|
||||
}
|
||||
|
||||
void SetsModel::toggleRow(int row)
|
||||
{
|
||||
CardSetPtr tmp = sets.at(row);
|
||||
|
||||
if (tmp == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (enabledSets.contains(tmp)) {
|
||||
enabledSets.remove(tmp);
|
||||
} else {
|
||||
enabledSets.insert(tmp);
|
||||
}
|
||||
|
||||
emit dataChanged(index(row, EnabledCol), index(row, EnabledCol));
|
||||
}
|
||||
|
||||
void SetsModel::toggleAll(bool enabled)
|
||||
{
|
||||
enabledSets.clear();
|
||||
|
||||
if (enabled) {
|
||||
for (CardSetPtr set : sets) {
|
||||
enabledSets.insert(set);
|
||||
}
|
||||
}
|
||||
|
||||
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
|
||||
}
|
||||
|
||||
void SetsModel::swapRows(int oldRow, int newRow)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), oldRow, oldRow);
|
||||
CardSetPtr temp = sets.takeAt(oldRow);
|
||||
endRemoveRows();
|
||||
|
||||
beginInsertRows(QModelIndex(), newRow, newRow);
|
||||
sets.insert(newRow, temp);
|
||||
endInsertRows();
|
||||
|
||||
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
|
||||
}
|
||||
|
||||
void SetsModel::restoreOriginalOrder()
|
||||
{
|
||||
int numRows = rowCount();
|
||||
sets.defaultSort();
|
||||
emit dataChanged(index(0, 0), index(numRows - 1, columnCount() - 1));
|
||||
}
|
||||
|
||||
void SetsModel::sort(int column, Qt::SortOrder order)
|
||||
{
|
||||
QMultiMap<QString, CardSetPtr> setMap;
|
||||
int numRows = rowCount();
|
||||
int row;
|
||||
|
||||
for (row = 0; row < numRows; ++row) {
|
||||
setMap.insert(index(row, column).data(SetsModel::SortRole).toString(), sets.at(row));
|
||||
}
|
||||
|
||||
QList<CardSetPtr> tmp = setMap.values();
|
||||
sets.clear();
|
||||
if (order == Qt::AscendingOrder) {
|
||||
for (row = 0; row < tmp.size(); row++) {
|
||||
sets.append(tmp.at(row));
|
||||
}
|
||||
} else {
|
||||
for (row = tmp.size() - 1; row >= 0; row--) {
|
||||
sets.append(tmp.at(row));
|
||||
}
|
||||
}
|
||||
|
||||
emit dataChanged(index(0, 0), index(numRows - 1, columnCount() - 1));
|
||||
}
|
||||
|
||||
void SetsModel::save(CardDatabase *db)
|
||||
{
|
||||
QVector<ICardSetPriorityController::SetSaveData> saveData;
|
||||
saveData.reserve(sets.size());
|
||||
|
||||
for (int i = 0; i < sets.size(); ++i) {
|
||||
const unsigned int sortKey = static_cast<unsigned int>(i + 1);
|
||||
const bool enabled = enabledSets.contains(sets[i]);
|
||||
sets[i]->setSortKeyInMemory(sortKey);
|
||||
sets[i]->setEnabledInMemory(enabled);
|
||||
saveData.append({sets[i]->getShortName(), sortKey, enabled});
|
||||
}
|
||||
|
||||
db->getPriorityController()->saveSets(saveData);
|
||||
sets.sortByKey();
|
||||
db->notifyEnabledSetsChanged();
|
||||
}
|
||||
|
||||
void SetsModel::restore(CardDatabase *db)
|
||||
{
|
||||
// order
|
||||
sets = db->getSetList();
|
||||
sets.sortByKey();
|
||||
|
||||
// enabled sets
|
||||
enabledSets.clear();
|
||||
for (const CardSetPtr &set : sets) {
|
||||
if (set->getEnabled()) {
|
||||
enabledSets.insert(set);
|
||||
}
|
||||
}
|
||||
|
||||
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
|
||||
}
|
||||
|
||||
QStringList SetsModel::mimeTypes() const
|
||||
{
|
||||
return QStringList() << "application/x-cockatricecardset";
|
||||
}
|
||||
|
||||
SetsDisplayModel::SetsDisplayModel(QObject *parent) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
void SetsDisplayModel::fetchMore(const QModelIndex &index)
|
||||
{
|
||||
int itemsToFetch = sourceModel()->rowCount(index);
|
||||
|
||||
beginInsertRows(QModelIndex(), 0, itemsToFetch - 1);
|
||||
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
bool SetsDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
auto typeIndex = sourceModel()->index(sourceRow, SetsModel::SetTypeCol, sourceParent);
|
||||
auto nameIndex = sourceModel()->index(sourceRow, SetsModel::LongNameCol, sourceParent);
|
||||
auto shortNameIndex = sourceModel()->index(sourceRow, SetsModel::ShortNameCol, sourceParent);
|
||||
auto dateIndex = sourceModel()->index(sourceRow, SetsModel::ReleaseDateCol, sourceParent);
|
||||
|
||||
const auto filter = filterRegularExpression();
|
||||
|
||||
return (sourceModel()->data(typeIndex).toString().contains(filter) ||
|
||||
sourceModel()->data(nameIndex).toString().contains(filter) ||
|
||||
sourceModel()->data(shortNameIndex).toString().contains(filter) ||
|
||||
sourceModel()->data(dateIndex).toString().contains(filter));
|
||||
}
|
||||
|
||||
bool SetsDisplayModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||
{
|
||||
QString leftString = sourceModel()->data(left, SetsModel::SortRole).toString();
|
||||
QString rightString = sourceModel()->data(right, SetsModel::SortRole).toString();
|
||||
|
||||
return QString::localeAwareCompare(leftString, rightString) < 0;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* @file sets_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef SETSMODEL_H
|
||||
#define SETSMODEL_H
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QMimeData>
|
||||
#include <QSet>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <libcockatrice/card/database/card_database.h>
|
||||
|
||||
class SetsProxyModel;
|
||||
|
||||
class SetsMimeData : public QMimeData
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
int oldRow;
|
||||
|
||||
public:
|
||||
SetsMimeData(int _oldRow) : oldRow(_oldRow)
|
||||
{
|
||||
}
|
||||
[[nodiscard]] int getOldRow() const
|
||||
{
|
||||
return oldRow;
|
||||
}
|
||||
[[nodiscard]] QStringList formats() const
|
||||
{
|
||||
return QStringList() << "application/x-cockatricecardset";
|
||||
}
|
||||
};
|
||||
|
||||
class SetsModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class SetsProxyModel;
|
||||
|
||||
private:
|
||||
static const int NUM_COLS = 7;
|
||||
CardSetList sets;
|
||||
QSet<CardSetPtr> enabledSets;
|
||||
|
||||
public:
|
||||
enum SetsColumns
|
||||
{
|
||||
SortKeyCol,
|
||||
IsKnownCol,
|
||||
EnabledCol,
|
||||
LongNameCol,
|
||||
ShortNameCol,
|
||||
SetTypeCol,
|
||||
ReleaseDateCol,
|
||||
PriorityCol
|
||||
};
|
||||
enum Role
|
||||
{
|
||||
SortRole = Qt::UserRole
|
||||
};
|
||||
|
||||
explicit SetsModel(CardDatabase *_db, QObject *parent = nullptr);
|
||||
~SetsModel() override;
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
[[nodiscard]] int columnCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return NUM_COLS;
|
||||
}
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
||||
[[nodiscard]] QVariant
|
||||
headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
[[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
[[nodiscard]] Qt::DropActions supportedDropActions() const override;
|
||||
|
||||
[[nodiscard]] QMimeData *mimeData(const QModelIndexList &indexes) const override;
|
||||
bool
|
||||
dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
|
||||
[[nodiscard]] QStringList mimeTypes() const override;
|
||||
void swapRows(int oldRow, int newRow);
|
||||
void toggleRow(int row, bool enable);
|
||||
void toggleRow(int row);
|
||||
void toggleAll(bool);
|
||||
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
|
||||
void save(CardDatabase *db);
|
||||
void restore(CardDatabase *db);
|
||||
void restoreOriginalOrder();
|
||||
};
|
||||
|
||||
class SetsDisplayModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SetsDisplayModel(QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
[[nodiscard]] bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||
[[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
void fetchMore(const QModelIndex &index) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "token_display_model.h"
|
||||
|
||||
#include "../card_database_model.h"
|
||||
|
||||
TokenDisplayModel::TokenDisplayModel(QObject *parent) : CardDatabaseDisplayModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool TokenDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
|
||||
{
|
||||
CardInfoPtr info = static_cast<CardDatabaseModel *>(sourceModel())->getCard(sourceRow);
|
||||
return info->getIsToken() && rowMatchesCardName(info);
|
||||
}
|
||||
|
||||
int TokenDisplayModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// always load all tokens at start
|
||||
return QSortFilterProxyModel::rowCount(parent);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @file token_display_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef COCKATRICE_TOKEN_DISPLAY_MODEL_H
|
||||
#define COCKATRICE_TOKEN_DISPLAY_MODEL_H
|
||||
|
||||
#include "../card_database_display_model.h"
|
||||
|
||||
class TokenDisplayModel : public CardDatabaseDisplayModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TokenDisplayModel(QObject *parent = nullptr);
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
protected:
|
||||
[[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_TOKEN_DISPLAY_MODEL_H
|
||||
@@ -0,0 +1,22 @@
|
||||
#include "token_edit_model.h"
|
||||
|
||||
#include "../card_database_display_model.h"
|
||||
#include "../card_database_model.h"
|
||||
|
||||
#include <libcockatrice/card/card_info.h>
|
||||
|
||||
TokenEditModel::TokenEditModel(QObject *parent) : CardDatabaseDisplayModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool TokenEditModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
|
||||
{
|
||||
CardInfoPtr info = static_cast<CardDatabaseModel *>(sourceModel())->getCard(sourceRow);
|
||||
return info->getIsToken() && info->getSets().contains(CardSet::TOKENS_SETNAME) && rowMatchesCardName(info);
|
||||
}
|
||||
|
||||
int TokenEditModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// always load all tokens at start
|
||||
return QSortFilterProxyModel::rowCount(parent);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @file token_edit_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef COCKATRICE_TOKEN_EDIT_MODEL_H
|
||||
#define COCKATRICE_TOKEN_EDIT_MODEL_H
|
||||
|
||||
#include "../card_database_display_model.h"
|
||||
|
||||
class TokenEditModel : public CardDatabaseDisplayModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TokenEditModel(QObject *parent = nullptr);
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
protected:
|
||||
[[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_TOKEN_EDIT_MODEL_H
|
||||
@@ -0,0 +1,21 @@
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(HEADERS deck_list_model.h deck_list_sort_filter_proxy_model.h)
|
||||
|
||||
if(Qt6_FOUND)
|
||||
qt6_wrap_cpp(MOC_SOURCES ${HEADERS})
|
||||
elseif(Qt5_FOUND)
|
||||
qt5_wrap_cpp(MOC_SOURCES ${HEADERS})
|
||||
endif()
|
||||
|
||||
add_library(
|
||||
libcockatrice_models_deck_list STATIC ${MOC_SOURCES} deck_list_model.cpp deck_list_sort_filter_proxy_model.cpp
|
||||
)
|
||||
|
||||
target_include_directories(libcockatrice_models_deck_list PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
target_link_libraries(
|
||||
libcockatrice_models_deck_list PUBLIC libcockatrice_card libcockatrice_deck_list ${QT_CORE_MODULE}
|
||||
)
|
||||
@@ -0,0 +1,779 @@
|
||||
#include "deck_list_model.h"
|
||||
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
|
||||
DeckListModel::DeckListModel(QObject *parent)
|
||||
: QAbstractItemModel(parent), lastKnownColumn(1), lastKnownOrder(Qt::AscendingOrder)
|
||||
{
|
||||
deckList = QSharedPointer<DeckList>(new DeckList());
|
||||
root = new InnerDecklistNode;
|
||||
}
|
||||
|
||||
DeckListModel::DeckListModel(QObject *parent, const QSharedPointer<DeckList> &deckList) : DeckListModel(parent)
|
||||
{
|
||||
setDeckList(deckList);
|
||||
|
||||
// forward change signals
|
||||
connect(this, &DeckListModel::cardAddedAt, this, &DeckListModel::cardsChanged);
|
||||
connect(this, &DeckListModel::cardRemoved, this, &DeckListModel::cardsChanged);
|
||||
connect(this, &DeckListModel::deckReplaced, this, &DeckListModel::cardsChanged);
|
||||
|
||||
connect(this, &DeckListModel::cardNodeAddedAt, this, &DeckListModel::cardNodesChanged);
|
||||
connect(this, &DeckListModel::cardNodeRemoved, this, &DeckListModel::cardNodesChanged);
|
||||
connect(this, &DeckListModel::deckReplaced, this, &DeckListModel::cardNodesChanged);
|
||||
}
|
||||
|
||||
DeckListModel::~DeckListModel()
|
||||
{
|
||||
delete root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Extract the value from the card that is used for the group criteria.
|
||||
* @param info Pointer to card information.
|
||||
* @param criteria The group criteria
|
||||
* @return String representing the value of the criteria.
|
||||
*/
|
||||
static QString extractGroupCriteriaValue(const CardInfoPtr &info, DeckListModelGroupCriteria::Type criteria)
|
||||
{
|
||||
if (!info) {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
switch (criteria) {
|
||||
case DeckListModelGroupCriteria::MAIN_TYPE:
|
||||
return info->getMainCardType();
|
||||
case DeckListModelGroupCriteria::MANA_COST:
|
||||
return info->getCmc();
|
||||
case DeckListModelGroupCriteria::COLOR:
|
||||
return info->getColors() == "" ? "Colorless" : info->getColors();
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void DeckListModel::rebuildTree()
|
||||
{
|
||||
beginResetModel();
|
||||
root->clearTree();
|
||||
|
||||
InnerDecklistNode *listRoot = deckList->getTree()->getRoot();
|
||||
|
||||
for (int i = 0; i < listRoot->size(); i++) {
|
||||
auto *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
|
||||
auto *node = new InnerDecklistNode(currentZone->getName(), root);
|
||||
|
||||
for (int j = 0; j < currentZone->size(); j++) {
|
||||
auto *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
|
||||
|
||||
//! \todo Better sanity checking.
|
||||
if (currentCard == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(currentCard->getName());
|
||||
QString groupCriteria = extractGroupCriteriaValue(info, activeGroupCriteria);
|
||||
|
||||
auto *groupNode = dynamic_cast<InnerDecklistNode *>(node->findChild(groupCriteria));
|
||||
|
||||
if (!groupNode) {
|
||||
groupNode = new InnerDecklistNode(groupCriteria, node);
|
||||
}
|
||||
|
||||
new DecklistModelCardNode(currentCard, groupNode);
|
||||
}
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
|
||||
refreshCardFormatLegalities();
|
||||
}
|
||||
|
||||
int DeckListModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
// debugIndexInfo("rowCount", parent);
|
||||
auto *node = getNode<InnerDecklistNode *>(parent);
|
||||
if (node) {
|
||||
return node->size();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int DeckListModel::columnCount(const QModelIndex & /*parent*/) const
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
|
||||
QVariant DeckListModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
// debugIndexInfo("data", index);
|
||||
if (!index.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (index.column() >= columnCount()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto *node = static_cast<AbstractDecklistNode *>(index.internalPointer());
|
||||
auto *card = dynamic_cast<DecklistModelCardNode *>(node);
|
||||
|
||||
// Group node
|
||||
if (!card) {
|
||||
const auto *group = dynamic_cast<InnerDecklistNode *>(node);
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case Qt::EditRole: {
|
||||
switch (index.column()) {
|
||||
case DeckListModelColumns::CARD_AMOUNT:
|
||||
return group->recursiveCount(true);
|
||||
case DeckListModelColumns::CARD_NAME:
|
||||
if (role == Qt::DisplayRole) {
|
||||
return group->getVisibleName();
|
||||
}
|
||||
return group->getName();
|
||||
case DeckListModelColumns::CARD_SET:
|
||||
return group->getCardSetShortName();
|
||||
case DeckListModelColumns::CARD_COLLECTOR_NUMBER:
|
||||
return group->getCardCollectorNumber();
|
||||
case DeckListModelColumns::CARD_PROVIDER_ID:
|
||||
return group->getCardProviderId();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
case DeckRoles::IsCardRole:
|
||||
return false;
|
||||
|
||||
case DeckRoles::DepthRole:
|
||||
return group->depth();
|
||||
|
||||
// legality does not apply to group nodes
|
||||
case DeckRoles::IsLegalRole:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// Card node
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case Qt::EditRole:
|
||||
switch (index.column()) {
|
||||
case DeckListModelColumns::CARD_AMOUNT:
|
||||
return card->getNumber();
|
||||
case DeckListModelColumns::CARD_NAME:
|
||||
return card->getName();
|
||||
case DeckListModelColumns::CARD_SET:
|
||||
return card->getCardSetShortName();
|
||||
case DeckListModelColumns::CARD_COLLECTOR_NUMBER:
|
||||
return card->getCardCollectorNumber();
|
||||
case DeckListModelColumns::CARD_PROVIDER_ID:
|
||||
return card->getCardProviderId();
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
case DeckRoles::IsCardRole: {
|
||||
return true;
|
||||
}
|
||||
|
||||
case DeckRoles::DepthRole: {
|
||||
return card->depth();
|
||||
}
|
||||
|
||||
case DeckRoles::IsLegalRole: {
|
||||
return card->getFormatLegality();
|
||||
}
|
||||
|
||||
default: {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariant DeckListModel::headerData(const int section, const Qt::Orientation orientation, const int role) const
|
||||
{
|
||||
if ((role != Qt::DisplayRole) || (orientation != Qt::Horizontal)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (section >= columnCount()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (section) {
|
||||
case DeckListModelColumns::CARD_AMOUNT:
|
||||
return tr("Count");
|
||||
case DeckListModelColumns::CARD_NAME:
|
||||
return tr("Card");
|
||||
case DeckListModelColumns::CARD_SET:
|
||||
return tr("Set");
|
||||
case DeckListModelColumns::CARD_COLLECTOR_NUMBER:
|
||||
return tr("Number");
|
||||
case DeckListModelColumns::CARD_PROVIDER_ID:
|
||||
return tr("Provider ID");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndex DeckListModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
// debugIndexInfo("index", parent);
|
||||
if (!hasIndex(row, column, parent)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto *parentNode = getNode<InnerDecklistNode *>(parent);
|
||||
return row >= parentNode->size() ? QModelIndex() : createIndex(row, column, parentNode->at(row));
|
||||
}
|
||||
|
||||
QModelIndex DeckListModel::parent(const QModelIndex &ind) const
|
||||
{
|
||||
if (!ind.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return nodeToIndex(static_cast<AbstractDecklistNode *>(ind.internalPointer())->getParent());
|
||||
}
|
||||
|
||||
Qt::ItemFlags DeckListModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return Qt::NoItemFlags;
|
||||
}
|
||||
|
||||
Qt::ItemFlags result = Qt::ItemIsEnabled;
|
||||
result |= Qt::ItemIsSelectable;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void DeckListModel::emitBackgroundUpdates(const QModelIndex &parent)
|
||||
{
|
||||
int rows = rowCount(parent);
|
||||
if (rows == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
QModelIndex topLeft = index(0, 0, parent);
|
||||
QModelIndex bottomRight = index(rows - 1, columnCount() - 1, parent);
|
||||
emit dataChanged(topLeft, bottomRight, {Qt::BackgroundRole});
|
||||
|
||||
for (int r = 0; r < rows; ++r) {
|
||||
QModelIndex child = index(r, 0, parent);
|
||||
emitBackgroundUpdates(child);
|
||||
}
|
||||
}
|
||||
|
||||
void DeckListModel::emitRecursiveUpdates(const QModelIndex &index)
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit dataChanged(index, index);
|
||||
emitRecursiveUpdates(index.parent());
|
||||
}
|
||||
|
||||
bool DeckListModel::setData(const QModelIndex &index, const QVariant &value, const int role)
|
||||
{
|
||||
auto *node = getNode<DecklistModelCardNode *>(index);
|
||||
if (!node || (role != Qt::EditRole)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (index.column()) {
|
||||
case DeckListModelColumns::CARD_AMOUNT:
|
||||
node->setNumber(value.toInt());
|
||||
refreshCardFormatLegalities();
|
||||
break;
|
||||
case DeckListModelColumns::CARD_NAME:
|
||||
node->setName(value.toString());
|
||||
break;
|
||||
case DeckListModelColumns::CARD_SET:
|
||||
node->setCardSetShortName(value.toString());
|
||||
break;
|
||||
case DeckListModelColumns::CARD_COLLECTOR_NUMBER:
|
||||
node->setCardCollectorNumber(value.toString());
|
||||
break;
|
||||
case DeckListModelColumns::CARD_PROVIDER_ID:
|
||||
node->setCardProviderId(value.toString());
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
emitRecursiveUpdates(index);
|
||||
deckList->refreshDeckHash();
|
||||
emit deckHashChanged();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeckListModel::removeRows(int row, int count, const QModelIndex &parent)
|
||||
{
|
||||
auto *node = getNode<InnerDecklistNode *>(parent);
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (row + count > node->size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
beginRemoveRows(parent, row, row + count - 1);
|
||||
for (int i = 0; i < count; i++) {
|
||||
AbstractDecklistNode *toDelete = node->takeAt(row);
|
||||
if (auto *temp = dynamic_cast<DecklistModelCardNode *>(toDelete)) {
|
||||
deckList->getTree()->deleteNode(temp->getDataNode());
|
||||
}
|
||||
delete toDelete;
|
||||
}
|
||||
endRemoveRows();
|
||||
|
||||
if (node->empty() && (node != root)) {
|
||||
removeRows(parent.row(), 1, parent.parent());
|
||||
} else {
|
||||
emitRecursiveUpdates(parent);
|
||||
}
|
||||
|
||||
deckList->refreshDeckHash();
|
||||
emit deckHashChanged();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
InnerDecklistNode *DeckListModel::createNodeIfNeeded(const QString &name, InnerDecklistNode *parent)
|
||||
{
|
||||
auto *newNode = dynamic_cast<InnerDecklistNode *>(parent->findChild(name));
|
||||
if (!newNode) {
|
||||
beginInsertRows(nodeToIndex(parent), parent->size(), parent->size());
|
||||
newNode = new InnerDecklistNode(name, parent);
|
||||
endInsertRows();
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
|
||||
DecklistModelCardNode *DeckListModel::findCardNode(const QString &cardName,
|
||||
const QString &zoneName,
|
||||
const QString &providerId,
|
||||
const QString &cardNumber) const
|
||||
{
|
||||
InnerDecklistNode *zoneNode = dynamic_cast<InnerDecklistNode *>(root->findChild(zoneName));
|
||||
if (!zoneNode) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(cardName);
|
||||
if (!info) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString groupCriteria = extractGroupCriteriaValue(info, activeGroupCriteria);
|
||||
InnerDecklistNode *groupNode = dynamic_cast<InnerDecklistNode *>(zoneNode->findChild(groupCriteria));
|
||||
if (!groupNode) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return dynamic_cast<DecklistModelCardNode *>(
|
||||
groupNode->findCardChildByNameProviderIdAndNumber(cardName, providerId, cardNumber));
|
||||
}
|
||||
|
||||
QModelIndex DeckListModel::findCard(const QString &cardName,
|
||||
const QString &zoneName,
|
||||
const QString &providerId,
|
||||
const QString &cardNumber) const
|
||||
{
|
||||
DecklistModelCardNode *cardNode = findCardNode(cardName, zoneName, providerId, cardNumber);
|
||||
if (!cardNode) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return nodeToIndex(cardNode);
|
||||
}
|
||||
|
||||
QModelIndex DeckListModel::addPreferredPrintingCard(const QString &cardName, const QString &zoneName, bool abAddAnyway)
|
||||
{
|
||||
ExactCard card = CardDatabaseManager::query()->getCard({cardName});
|
||||
|
||||
if (!card) {
|
||||
if (abAddAnyway) {
|
||||
// We need to keep this card added no matter what
|
||||
// This is usually called from tab_deck_editor
|
||||
// So we'll create a new CardInfo with the name
|
||||
// and default values for all fields
|
||||
card = ExactCard(CardInfo::newInstance(cardName));
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return addCard(card, zoneName);
|
||||
}
|
||||
|
||||
QModelIndex DeckListModel::addCard(const ExactCard &card, const QString &zoneName)
|
||||
{
|
||||
if (!card) {
|
||||
return {};
|
||||
}
|
||||
|
||||
InnerDecklistNode *zoneNode = createNodeIfNeeded(zoneName, root);
|
||||
|
||||
CardInfoPtr cardInfo = card.getCardPtr();
|
||||
PrintingInfo printingInfo = card.getPrinting();
|
||||
|
||||
QString groupCriteria = extractGroupCriteriaValue(cardInfo, activeGroupCriteria);
|
||||
InnerDecklistNode *groupNode = createNodeIfNeeded(groupCriteria, zoneNode);
|
||||
|
||||
const QModelIndex parentIndex = nodeToIndex(groupNode);
|
||||
auto *cardNode = dynamic_cast<DecklistModelCardNode *>(groupNode->findCardChildByNameProviderIdAndNumber(
|
||||
card.getName(), printingInfo.getUuid(), printingInfo.getProperty("num")));
|
||||
const auto cardSetName = printingInfo.getSet().isNull() ? "" : printingInfo.getSet()->getCorrectedShortName();
|
||||
|
||||
bool cardNodeAdded = false;
|
||||
if (!cardNode) {
|
||||
// Determine the correct index
|
||||
int insertRow = findSortedInsertRow(groupNode, cardInfo);
|
||||
|
||||
auto *decklistCard = deckList->addCard(cardInfo->getName(), zoneName, insertRow, cardSetName,
|
||||
printingInfo.getProperty("num"), printingInfo.getProperty("uuid"));
|
||||
|
||||
beginInsertRows(parentIndex, insertRow, insertRow);
|
||||
cardNode = new DecklistModelCardNode(decklistCard, groupNode, insertRow);
|
||||
endInsertRows();
|
||||
|
||||
cardNodeAdded = true;
|
||||
} else {
|
||||
cardNode->setNumber(cardNode->getNumber() + 1);
|
||||
cardNode->setCardSetShortName(cardSetName);
|
||||
cardNode->setCardCollectorNumber(printingInfo.getProperty("num"));
|
||||
cardNode->setCardProviderId(printingInfo.getProperty("uuid"));
|
||||
// Emit dataChanged for the amount column since we modified it
|
||||
QModelIndex cardIndex = nodeToIndex(cardNode);
|
||||
QModelIndex amountIndex = cardIndex.sibling(cardIndex.row(), DeckListModelColumns::CARD_AMOUNT);
|
||||
emit dataChanged(amountIndex, amountIndex, {Qt::EditRole});
|
||||
}
|
||||
sort(lastKnownColumn, lastKnownOrder);
|
||||
refreshCardFormatLegalities();
|
||||
emitRecursiveUpdates(parentIndex);
|
||||
|
||||
deckList->refreshDeckHash();
|
||||
emit deckHashChanged();
|
||||
|
||||
auto index = nodeToIndex(cardNode);
|
||||
|
||||
if (cardNodeAdded) {
|
||||
emit cardNodeAddedAt(index);
|
||||
}
|
||||
|
||||
emit cardAddedAt(index);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
bool DeckListModel::offsetCountAtIndex(const QModelIndex &idx, int offset)
|
||||
{
|
||||
if (!idx.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto *node = static_cast<AbstractDecklistNode *>(idx.internalPointer());
|
||||
auto *card = dynamic_cast<DecklistModelCardNode *>(node);
|
||||
|
||||
if (!card) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QModelIndex numberIndex = idx.siblingAtColumn(DeckListModelColumns::CARD_AMOUNT);
|
||||
const int count = numberIndex.data(Qt::EditRole).toInt();
|
||||
const int newCount = count + offset;
|
||||
|
||||
if (newCount <= 0) {
|
||||
removeRow(idx.row(), idx.parent());
|
||||
emit cardNodeRemoved();
|
||||
} else {
|
||||
setData(numberIndex, newCount, Qt::EditRole);
|
||||
}
|
||||
|
||||
if (offset > 0) {
|
||||
emit cardAddedAt(idx);
|
||||
} else if (offset < 0) {
|
||||
emit cardRemoved();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeckListModel::removeCardAtIndex(const QModelIndex &idx)
|
||||
{
|
||||
if (!idx.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto *node = static_cast<AbstractDecklistNode *>(idx.internalPointer());
|
||||
auto *card = dynamic_cast<DecklistModelCardNode *>(node);
|
||||
|
||||
if (!card) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool success = removeRow(idx.row(), idx.parent());
|
||||
|
||||
if (success) {
|
||||
emit cardRemoved();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
int DeckListModel::findSortedInsertRow(const InnerDecklistNode *parent, const CardInfoPtr &cardInfo) const
|
||||
{
|
||||
if (!cardInfo) {
|
||||
return parent->size(); // fallback: append at end
|
||||
}
|
||||
|
||||
for (int i = 0; i < parent->size(); ++i) {
|
||||
auto *existingCard = dynamic_cast<DecklistModelCardNode *>(parent->at(i));
|
||||
if (!existingCard) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool lessThan = false;
|
||||
switch (lastKnownColumn) {
|
||||
case 0: // ByNumber
|
||||
lessThan = lastKnownOrder == Qt::AscendingOrder
|
||||
? cardInfo->getProperty("collectorNumber") < existingCard->getCardCollectorNumber()
|
||||
: cardInfo->getProperty("collectorNumber") > existingCard->getCardCollectorNumber();
|
||||
break;
|
||||
case 1: // ByName
|
||||
default:
|
||||
lessThan = lastKnownOrder == Qt::AscendingOrder
|
||||
? cardInfo->getName().localeAwareCompare(existingCard->getName()) < 0
|
||||
: cardInfo->getName().localeAwareCompare(existingCard->getName()) > 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (lessThan) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return parent->size(); // insert at end if no earlier match
|
||||
}
|
||||
|
||||
QModelIndex DeckListModel::nodeToIndex(AbstractDecklistNode *node) const
|
||||
{
|
||||
if (node == nullptr || node == root) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return createIndex(node->getParent()->indexOf(node), 0, node);
|
||||
}
|
||||
|
||||
void DeckListModel::sortHelper(InnerDecklistNode *node, Qt::SortOrder order)
|
||||
{
|
||||
// Sort children of node and save the information needed to
|
||||
// update the list of persistent indexes.
|
||||
QVector<QPair<int, int>> sortResult = node->sort(order);
|
||||
|
||||
QModelIndexList from, to;
|
||||
int columns = columnCount();
|
||||
for (int i = sortResult.size() - 1; i >= 0; --i) {
|
||||
const int fromRow = sortResult[i].first;
|
||||
const int toRow = sortResult[i].second;
|
||||
AbstractDecklistNode *temp = node->at(toRow);
|
||||
for (int j = 0; j < columns; ++j) {
|
||||
from << createIndex(fromRow, j, temp);
|
||||
to << createIndex(toRow, j, temp);
|
||||
}
|
||||
}
|
||||
changePersistentIndexList(from, to);
|
||||
|
||||
// Recursion
|
||||
for (int i = node->size() - 1; i >= 0; --i) {
|
||||
auto *subNode = dynamic_cast<InnerDecklistNode *>(node->at(i));
|
||||
if (subNode) {
|
||||
sortHelper(subNode, order);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeckListModel::sort(int column, Qt::SortOrder order)
|
||||
{
|
||||
lastKnownColumn = column;
|
||||
lastKnownOrder = order;
|
||||
|
||||
emit layoutAboutToBeChanged();
|
||||
DeckSortMethod sortMethod;
|
||||
switch (column) {
|
||||
case 0:
|
||||
sortMethod = ByNumber;
|
||||
break;
|
||||
case 1:
|
||||
sortMethod = ByName;
|
||||
break;
|
||||
default:
|
||||
sortMethod = ByName;
|
||||
}
|
||||
|
||||
root->setSortMethod(sortMethod);
|
||||
sortHelper(root, order);
|
||||
emit layoutChanged();
|
||||
}
|
||||
|
||||
void DeckListModel::setActiveGroupCriteria(DeckListModelGroupCriteria::Type newCriteria)
|
||||
{
|
||||
activeGroupCriteria = newCriteria;
|
||||
rebuildTree();
|
||||
}
|
||||
|
||||
void DeckListModel::setActiveFormat(const QString &_format)
|
||||
{
|
||||
deckList->setGameFormat(_format);
|
||||
refreshCardFormatLegalities();
|
||||
emitBackgroundUpdates(QModelIndex()); // start from root
|
||||
}
|
||||
|
||||
void DeckListModel::cleanList()
|
||||
{
|
||||
setDeckList(QSharedPointer<DeckList>(new DeckList()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param _deck The deck.
|
||||
*/
|
||||
void DeckListModel::setDeckList(const QSharedPointer<DeckList> &_deck)
|
||||
{
|
||||
if (deckList != _deck) {
|
||||
deckList = _deck;
|
||||
}
|
||||
rebuildTree();
|
||||
emit deckReplaced();
|
||||
}
|
||||
|
||||
void DeckListModel::forEachCard(const std::function<void(InnerDecklistNode *, DecklistCardNode *)> &func)
|
||||
{
|
||||
deckList->forEachCard(func);
|
||||
}
|
||||
|
||||
QList<const DecklistCardNode *> DeckListModel::getCardNodes() const
|
||||
{
|
||||
return deckList->getCardNodes();
|
||||
}
|
||||
|
||||
QList<const DecklistCardNode *> DeckListModel::getCardNodesForZone(const QString &zoneName) const
|
||||
{
|
||||
return deckList->getCardNodes({zoneName});
|
||||
}
|
||||
|
||||
QList<QString> DeckListModel::getCardNames() const
|
||||
{
|
||||
auto nodes = deckList->getCardNodes();
|
||||
|
||||
QList<QString> names;
|
||||
std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(names), [](auto node) { return node->getName(); });
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
QList<CardRef> DeckListModel::getCardRefs() const
|
||||
{
|
||||
auto nodes = deckList->getCardNodes();
|
||||
|
||||
QList<CardRef> cardRefs;
|
||||
std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(cardRefs),
|
||||
[](auto node) { return node->toCardRef(); });
|
||||
|
||||
return cardRefs;
|
||||
}
|
||||
|
||||
QList<QString> DeckListModel::getZones() const
|
||||
{
|
||||
auto zoneNodes = deckList->getZoneNodes();
|
||||
|
||||
QList<QString> zones;
|
||||
std::transform(zoneNodes.cbegin(), zoneNodes.cend(), std::back_inserter(zones),
|
||||
[](auto zoneNode) { return zoneNode->getName(); });
|
||||
|
||||
return zones;
|
||||
}
|
||||
|
||||
static int maxAllowedForLegality(const FormatRules &format, const QString &legality)
|
||||
{
|
||||
for (const AllowedCount &c : format.allowedCounts) {
|
||||
if (c.label == legality) {
|
||||
return c.max;
|
||||
}
|
||||
}
|
||||
return -1; // unknown legality → treat as illegal
|
||||
}
|
||||
|
||||
static bool isCardQuantityLegalForFormat(const QString &format, const CardInfo &cardInfo, int quantity)
|
||||
{
|
||||
if (format.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto formatRules = CardDatabaseManager::query()->getFormat(format);
|
||||
|
||||
// if format has no custom rules, then just do the default check
|
||||
if (!formatRules) {
|
||||
return cardInfo.isLegalInFormat(format);
|
||||
}
|
||||
|
||||
// Exceptions always win
|
||||
if (cardHasAnyException(cardInfo, *formatRules)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// check legality prop
|
||||
const QString legality = cardInfo.getLegalityProp(format);
|
||||
if (legality.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int maxAllowed = maxAllowedForLegality(*formatRules, legality);
|
||||
|
||||
if (maxAllowed == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (maxAllowed < 0) { // unlimited
|
||||
return true;
|
||||
}
|
||||
|
||||
return quantity <= maxAllowed;
|
||||
}
|
||||
|
||||
static bool isCardNodeLegalForFormat(const QString &format, const InnerDecklistNode *zone, const DecklistCardNode *card)
|
||||
{
|
||||
// Don't check legality for tokens
|
||||
if (zone->getName() == DECK_ZONE_TOKENS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// unknown cards are not legal
|
||||
ExactCard exactCard = CardDatabaseManager::query()->getCard(card->toCardRef());
|
||||
if (!exactCard) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// actual check
|
||||
return isCardQuantityLegalForFormat(format, exactCard.getInfo(), card->getNumber());
|
||||
}
|
||||
|
||||
void DeckListModel::refreshCardFormatLegalities()
|
||||
{
|
||||
QString format = deckList->getGameFormat();
|
||||
|
||||
deckList->forEachCard([&format](const InnerDecklistNode *zone, DecklistCardNode *card) {
|
||||
bool legal = isCardNodeLegalForFormat(format, zone, card);
|
||||
card->setFormatLegality(legal);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,442 @@
|
||||
#ifndef DECKLISTMODEL_H
|
||||
#define DECKLISTMODEL_H
|
||||
|
||||
#include <../../../../libcockatrice_deck_list/libcockatrice/deck_list/tree/abstract_deck_list_card_node.h>
|
||||
#include <../../../../libcockatrice_deck_list/libcockatrice/deck_list/tree/deck_list_card_node.h>
|
||||
#include <QAbstractItemModel>
|
||||
#include <QList>
|
||||
#include <libcockatrice/card/printing/exact_card.h>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
|
||||
class CardDatabase;
|
||||
class QPrinter;
|
||||
class QTextCursor;
|
||||
|
||||
/**
|
||||
* @namespace DeckRoles
|
||||
* @brief Custom model roles used by the DeckListModel for data retrieval.
|
||||
*
|
||||
* These roles extend Qt's item data roles starting at Qt::UserRole.
|
||||
*/
|
||||
namespace DeckRoles
|
||||
{
|
||||
/**
|
||||
* @enum DeckRoles
|
||||
* @brief Custom data roles for deck-related model items.
|
||||
*
|
||||
* These roles are used to retrieve specialized data from the DeckListModel.
|
||||
*/
|
||||
enum
|
||||
{
|
||||
IsCardRole = Qt::UserRole + 1, /**< Indicates whether the item represents a card. */
|
||||
DepthRole, /**< Depth level within the deck's grouping hierarchy. */
|
||||
IsLegalRole /**< Whether the card is legal in the current deck format. */
|
||||
};
|
||||
} // namespace DeckRoles
|
||||
|
||||
/**
|
||||
* @namespace DeckListModelColumns
|
||||
* @brief Column indices for the DeckListModel.
|
||||
*
|
||||
* These values map to the columns in the deck list table representation.
|
||||
*/
|
||||
namespace DeckListModelColumns
|
||||
{
|
||||
/**
|
||||
* @enum DeckListModelColumns
|
||||
* @brief Column identifiers for displaying card information in the deck list.
|
||||
*/
|
||||
enum
|
||||
{
|
||||
CARD_AMOUNT = 0, /**< The number of copies of the card. */
|
||||
CARD_NAME = 1, /**< The card's name. */
|
||||
CARD_SET = 2, /**< The set or expansion the card belongs to. */
|
||||
CARD_COLLECTOR_NUMBER = 3, /**< Collector number of the card within the set. */
|
||||
CARD_PROVIDER_ID = 4 /**< ID used by the external data provider (e.g., Scryfall). */
|
||||
};
|
||||
} // namespace DeckListModelColumns
|
||||
|
||||
/**
|
||||
* @namespace DeckListModelGroupCriteria
|
||||
* @brief Specifies criteria used to group cards in the DeckListModel.
|
||||
*
|
||||
* These values determine how cards are grouped in UI views such as the deck editor.
|
||||
*/
|
||||
namespace DeckListModelGroupCriteria
|
||||
{
|
||||
/**
|
||||
* @enum DeckListModelGroupCriteria
|
||||
* @brief Available grouping strategies for deck visualization.
|
||||
*/
|
||||
enum Type
|
||||
{
|
||||
MAIN_TYPE, /**< Group cards by their main type (e.g., creature, instant). */
|
||||
MANA_COST, /**< Group cards by their total mana cost. */
|
||||
COLOR /**< Group cards by their color identity. */
|
||||
};
|
||||
static inline QString toString(Type t)
|
||||
{
|
||||
switch (t) {
|
||||
case MAIN_TYPE:
|
||||
return "Main Type";
|
||||
case MANA_COST:
|
||||
return "Mana Cost";
|
||||
case COLOR:
|
||||
return "Colors";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static inline Type fromString(const QString &s)
|
||||
{
|
||||
if (s == "Main Type") {
|
||||
return MAIN_TYPE;
|
||||
}
|
||||
if (s == "Mana Cost") {
|
||||
return MANA_COST;
|
||||
}
|
||||
if (s == "Colors") {
|
||||
return COLOR;
|
||||
}
|
||||
return MAIN_TYPE; // default
|
||||
}
|
||||
} // namespace DeckListModelGroupCriteria
|
||||
|
||||
/**
|
||||
* @class DecklistModelCardNode
|
||||
* @ingroup DeckModels
|
||||
* @brief Adapter node that wraps a DecklistCardNode for use in the DeckListModel tree.
|
||||
*
|
||||
* This class forwards all property accessors (name, number, provider ID, set info, etc.)
|
||||
* to the underlying DecklistCardNode. It exists so the model can represent cards
|
||||
* in the same hierarchy as InnerDecklistNode containers.
|
||||
*/
|
||||
class DecklistModelCardNode : public AbstractDecklistCardNode
|
||||
{
|
||||
private:
|
||||
DecklistCardNode *dataNode; /**< Pointer to the underlying data node. */
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs a model node wrapping a DecklistCardNode.
|
||||
* @param _dataNode The underlying DecklistCardNode to wrap.
|
||||
* @param _parent The parent InnerDecklistNode in the model tree.
|
||||
* @param position Optional position to insert in parent (-1 appends at end).
|
||||
*/
|
||||
DecklistModelCardNode(DecklistCardNode *_dataNode, InnerDecklistNode *_parent, int position = -1)
|
||||
: AbstractDecklistCardNode(_parent, position), dataNode(_dataNode)
|
||||
{
|
||||
}
|
||||
[[nodiscard]] int getNumber() const override
|
||||
{
|
||||
return dataNode->getNumber();
|
||||
}
|
||||
void setNumber(int _number) override
|
||||
{
|
||||
dataNode->setNumber(_number);
|
||||
}
|
||||
[[nodiscard]] QString getName() const override
|
||||
{
|
||||
return dataNode->getName();
|
||||
}
|
||||
void setName(const QString &_name) override
|
||||
{
|
||||
dataNode->setName(_name);
|
||||
}
|
||||
[[nodiscard]] QString getCardProviderId() const override
|
||||
{
|
||||
return dataNode->getCardProviderId();
|
||||
}
|
||||
void setCardProviderId(const QString &_cardProviderId) override
|
||||
{
|
||||
dataNode->setCardProviderId(_cardProviderId);
|
||||
}
|
||||
[[nodiscard]] QString getCardSetShortName() const override
|
||||
{
|
||||
return dataNode->getCardSetShortName();
|
||||
}
|
||||
void setCardSetShortName(const QString &_cardSetShortName) override
|
||||
{
|
||||
dataNode->setCardSetShortName(_cardSetShortName);
|
||||
}
|
||||
[[nodiscard]] QString getCardCollectorNumber() const override
|
||||
{
|
||||
return dataNode->getCardCollectorNumber();
|
||||
}
|
||||
void setCardCollectorNumber(const QString &_cardSetNumber) override
|
||||
{
|
||||
dataNode->setCardCollectorNumber(_cardSetNumber);
|
||||
}
|
||||
bool getFormatLegality() const override
|
||||
{
|
||||
return dataNode->getFormatLegality();
|
||||
}
|
||||
void setFormatLegality(const bool _formatLegal) override
|
||||
{
|
||||
dataNode->setFormatLegality(_formatLegal);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the underlying data node.
|
||||
* @return Pointer to the DecklistCardNode wrapped by this node.
|
||||
*/
|
||||
[[nodiscard]] DecklistCardNode *getDataNode() const
|
||||
{
|
||||
return dataNode;
|
||||
}
|
||||
[[nodiscard]] bool isDeckHeader() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @class DeckListModel
|
||||
* @ingroup DeckModels
|
||||
* @brief Qt model representing a decklist for use in views (tree/table).
|
||||
*
|
||||
* DeckListModel is a QAbstractItemModel that exposes the structure of a deck
|
||||
* (zones and cards) to Qt views. It organizes cards hierarchically under
|
||||
* InnerDecklistNode containers and supports grouping, sorting, adding/removing
|
||||
* cards, and printing decklists.
|
||||
*
|
||||
* Outside code should refrain from modifying the model with methods inherited from QAbstractItemModel, such as with
|
||||
* `setData` or `removeRow`.
|
||||
* Instead, use the custom methods on this class to modify the model, such as `addCard`, `offsetCountAtIndex`, or
|
||||
* `removeCardAtIndex`.
|
||||
* This ensures the custom signals for this class are correctly emitted.
|
||||
*
|
||||
* Signals:
|
||||
* - deckHashChanged(): emitted when the deck contents change in a way that
|
||||
* affects its hash.
|
||||
*
|
||||
* Slots:
|
||||
* - rebuildTree(): rebuilds the model structure from the underlying node tree.
|
||||
*/
|
||||
class DeckListModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Rebuilds the model tree from the underlying node tree.
|
||||
*
|
||||
* This updates all indices and ensures the model reflects the current
|
||||
* state of the deck.
|
||||
*/
|
||||
void rebuildTree();
|
||||
|
||||
/**
|
||||
* @brief Sets the criteria used to group cards in the model.
|
||||
* @param newCriteria The new grouping criteria.
|
||||
*/
|
||||
void setActiveGroupCriteria(DeckListModelGroupCriteria::Type newCriteria);
|
||||
|
||||
void setActiveFormat(const QString &_format);
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Emitted whenever the deck hash changes due to modifications in the model.
|
||||
*/
|
||||
void deckHashChanged();
|
||||
|
||||
/**
|
||||
* @brief Emitted whenever the cards in the deck changes. This includes when the deck is replaced.
|
||||
*/
|
||||
void cardsChanged();
|
||||
|
||||
/**
|
||||
* @brief Emitted whenever a card is added to the deck, regardless of whether it's an entirely new card or an
|
||||
* existing card that got incremented.
|
||||
* @param index The index of the card that got added.
|
||||
*/
|
||||
void cardAddedAt(const QModelIndex &index);
|
||||
|
||||
/**
|
||||
* @brief Emitted whenever a card is removed from the deck, regardless of whether a card node was removed or an
|
||||
* existing card got decremented.
|
||||
*/
|
||||
void cardRemoved();
|
||||
|
||||
/**
|
||||
* @brief Emitted whenever a card node is added or removed. This includes when the deck is replaced.
|
||||
*/
|
||||
void cardNodesChanged();
|
||||
|
||||
/**
|
||||
* @brief Emitted whenever a new card node is added.
|
||||
* @param index The index of the card node that got added.
|
||||
*/
|
||||
void cardNodeAddedAt(const QModelIndex &index);
|
||||
|
||||
/**
|
||||
* @brief Emitted whenever a card node is removed.
|
||||
*/
|
||||
void cardNodeRemoved();
|
||||
|
||||
/**
|
||||
* @brief Emitted whenever the deck in the model has been replaced with a new one
|
||||
*/
|
||||
void deckReplaced();
|
||||
|
||||
public:
|
||||
explicit DeckListModel(QObject *parent = nullptr);
|
||||
explicit DeckListModel(QObject *parent, const QSharedPointer<DeckList> &deckList);
|
||||
~DeckListModel() override;
|
||||
|
||||
/**
|
||||
* @brief Returns the root index of the model.
|
||||
* @return QModelIndex representing the root node.
|
||||
*/
|
||||
[[nodiscard]] QModelIndex getRoot() const
|
||||
{
|
||||
return nodeToIndex(root);
|
||||
}
|
||||
|
||||
/// @name Qt model overrides
|
||||
///@{
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent) const override;
|
||||
[[nodiscard]] int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override;
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
|
||||
[[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
[[nodiscard]] QModelIndex index(int row, int column, const QModelIndex &parent) const override;
|
||||
[[nodiscard]] QModelIndex parent(const QModelIndex &index) const override;
|
||||
[[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
||||
bool removeRows(int row, int count, const QModelIndex &parent) override;
|
||||
void sort(int column, Qt::SortOrder order) override;
|
||||
///@}
|
||||
|
||||
/**
|
||||
* @brief Finds a card by name, zone, and optional identifiers.
|
||||
* @param cardName The card's name.
|
||||
* @param zoneName The zone to search in (main/side/etc.).
|
||||
* @param providerId Optional provider-specific ID.
|
||||
* @param cardNumber Optional collector number.
|
||||
* @return QModelIndex of the card, or invalid index if not found.
|
||||
*/
|
||||
[[nodiscard]] QModelIndex findCard(const QString &cardName,
|
||||
const QString &zoneName,
|
||||
const QString &providerId = "",
|
||||
const QString &cardNumber = "") const;
|
||||
|
||||
/**
|
||||
* @brief Adds a card using the preferred printing if available.
|
||||
*
|
||||
* @param cardName Name of the card to add.
|
||||
* @param zoneName Zone to insert the card into.
|
||||
* @param abAddAnyway Whether to add the card even if resolution fails.
|
||||
* @return QModelIndex pointing to the newly inserted card node.
|
||||
*/
|
||||
QModelIndex addPreferredPrintingCard(const QString &cardName, const QString &zoneName, bool abAddAnyway);
|
||||
|
||||
/**
|
||||
* @brief Adds an ExactCard to the specified zone.
|
||||
* @param card The card to add.
|
||||
* @param zoneName The zone to insert the card into.
|
||||
* @return QModelIndex pointing to the newly inserted card node.
|
||||
*/
|
||||
QModelIndex addCard(const ExactCard &card, const QString &zoneName);
|
||||
|
||||
/**
|
||||
* @brief Changes the `amount` field in the card node at the index by the amount.
|
||||
* Removes the node if it causes the amount to fall to 0 or below.
|
||||
* @param idx The index of a card node. No-ops if the index is invalid or not a card node.
|
||||
* @param offset The amount to change the amount field by.
|
||||
* @return Whether the operation was successful
|
||||
*/
|
||||
bool offsetCountAtIndex(const QModelIndex &idx, int offset);
|
||||
|
||||
/**
|
||||
* @brief Removes the card node at the index
|
||||
* @param idx The index of a card node. No-ops if the index is invalid or not a card node.
|
||||
* @return Whether the node was removed.
|
||||
*/
|
||||
bool removeCardAtIndex(const QModelIndex &idx);
|
||||
|
||||
/**
|
||||
* @brief Removes all cards and resets the model.
|
||||
*/
|
||||
void cleanList();
|
||||
|
||||
[[nodiscard]] QSharedPointer<DeckList> getDeckList() const
|
||||
{
|
||||
return deckList;
|
||||
}
|
||||
void setDeckList(const QSharedPointer<DeckList> &_deck);
|
||||
|
||||
/**
|
||||
* @brief Apply a function to every card in the deck tree.
|
||||
*
|
||||
* @param func Function taking (zone node, card node).
|
||||
*/
|
||||
void forEachCard(const std::function<void(InnerDecklistNode *, DecklistCardNode *)> &func);
|
||||
|
||||
/**
|
||||
* @brief Gets a list of all card nodes in the deck.
|
||||
*/
|
||||
[[nodiscard]] QList<const DecklistCardNode *> getCardNodes() const;
|
||||
[[nodiscard]] QList<const DecklistCardNode *> getCardNodesForZone(const QString &zoneName) const;
|
||||
|
||||
/**
|
||||
* @brief Gets a deduplicated list of all card names that appear in the model
|
||||
*/
|
||||
[[nodiscard]] QList<QString> getCardNames() const;
|
||||
/**
|
||||
* @brief Gets a deduplicated list of all CardRefs that appear in the model
|
||||
*/
|
||||
[[nodiscard]] QList<CardRef> getCardRefs() const;
|
||||
/**
|
||||
* @brief Gets a list of all zone names that appear in the model
|
||||
*/
|
||||
[[nodiscard]] QList<QString> getZones() const;
|
||||
|
||||
private:
|
||||
QSharedPointer<DeckList> deckList; /**< Pointer to the decklist providing the underlying data. */
|
||||
InnerDecklistNode *root; /**< Root node of the model tree. */
|
||||
DeckListModelGroupCriteria::Type activeGroupCriteria = DeckListModelGroupCriteria::MAIN_TYPE;
|
||||
int lastKnownColumn; /**< Last column used for sorting. */
|
||||
Qt::SortOrder lastKnownOrder; /**< Last known sort order. */
|
||||
|
||||
InnerDecklistNode *createNodeIfNeeded(const QString &name, InnerDecklistNode *parent);
|
||||
QModelIndex nodeToIndex(AbstractDecklistNode *node) const;
|
||||
[[nodiscard]] DecklistModelCardNode *findCardNode(const QString &cardName,
|
||||
const QString &zoneName,
|
||||
const QString &providerId = "",
|
||||
const QString &cardNumber = "") const;
|
||||
|
||||
/**
|
||||
* @brief Determines the sorted insertion row for a card.
|
||||
* @param parent The parent node where the card will be inserted.
|
||||
* @param cardInfo The card info to insert.
|
||||
* @return Row index where the card should be inserted to maintain sort order.
|
||||
*/
|
||||
int findSortedInsertRow(const InnerDecklistNode *parent, const CardInfoPtr &cardInfo) const;
|
||||
|
||||
/**
|
||||
* @brief Recursively emits the dataChanged signal with role as Qt::BackgroundRole for all indices that are children
|
||||
* of the given node. This is used to update the background color when changing formats.
|
||||
* @param parent The parent node
|
||||
*/
|
||||
void emitBackgroundUpdates(const QModelIndex &parent);
|
||||
|
||||
/**
|
||||
* @brief Recursively emits the dataChanged signal for the given node and all parent nodes.
|
||||
* @param index The parent node
|
||||
*/
|
||||
void emitRecursiveUpdates(const QModelIndex &index);
|
||||
|
||||
void sortHelper(InnerDecklistNode *node, Qt::SortOrder order);
|
||||
|
||||
template <typename T> T getNode(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return dynamic_cast<T>(root);
|
||||
}
|
||||
return dynamic_cast<T>(static_cast<AbstractDecklistNode *>(index.internalPointer()));
|
||||
}
|
||||
|
||||
void refreshCardFormatLegalities();
|
||||
};
|
||||
|
||||
#endif
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
#include "deck_list_sort_filter_proxy_model.h"
|
||||
|
||||
#include "deck_list_model.h"
|
||||
|
||||
bool DeckListSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||
{
|
||||
auto *src = sourceModel();
|
||||
|
||||
// Inner nodes? -> sort alphabetically by column 1
|
||||
bool leftIsCard = src->data(left, Qt::UserRole + 1).toBool();
|
||||
bool rightIsCard = src->data(right, Qt::UserRole + 1).toBool();
|
||||
|
||||
if (!leftIsCard || !rightIsCard) {
|
||||
QString lName = src->data(left.siblingAtColumn(DeckListModelColumns::CARD_NAME), Qt::EditRole).toString();
|
||||
QString rName = src->data(right.siblingAtColumn(DeckListModelColumns::CARD_NAME), Qt::EditRole).toString();
|
||||
return lName.localeAwareCompare(rName) < 0;
|
||||
}
|
||||
|
||||
// Both are cards -> apply sort criteria
|
||||
auto *lNode = static_cast<DecklistModelCardNode *>(left.internalPointer());
|
||||
auto *rNode = static_cast<DecklistModelCardNode *>(right.internalPointer());
|
||||
|
||||
CardInfoPtr lInfo = CardDatabaseManager::query()->guessCard({lNode->getName()}).getCardPtr();
|
||||
CardInfoPtr rInfo = CardDatabaseManager::query()->guessCard({rNode->getName()}).getCardPtr();
|
||||
|
||||
// Example: multiple tie-break criteria (colors > cmc > name)
|
||||
for (const QString &crit : sortCriteria) {
|
||||
if (crit == "name") {
|
||||
QString ln = lNode->getName();
|
||||
QString rn = rNode->getName();
|
||||
int cmp = ln.localeAwareCompare(rn);
|
||||
if (cmp != 0) {
|
||||
return cmp < 0;
|
||||
}
|
||||
} else if (crit == "cmc") {
|
||||
int lc = lInfo ? lInfo->getCmc().toInt() : 0;
|
||||
int rc = rInfo ? rInfo->getCmc().toInt() : 0;
|
||||
if (lc != rc) {
|
||||
return lc < rc;
|
||||
}
|
||||
} else if (crit == "colors") {
|
||||
QString lr = lInfo ? lInfo->getColors() : QString();
|
||||
QString rr = rInfo ? rInfo->getColors() : QString();
|
||||
int cmp = lr.localeAwareCompare(rr);
|
||||
if (cmp != 0) {
|
||||
return cmp < 0;
|
||||
}
|
||||
} else if (crit == "maintype") {
|
||||
QString lr = lInfo ? lInfo->getMainCardType() : QString();
|
||||
QString rr = rInfo ? rInfo->getMainCardType() : QString();
|
||||
int cmp = lr.localeAwareCompare(rr);
|
||||
if (cmp != 0) {
|
||||
return cmp < 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* @file deck_list_sort_filter_proxy_model.h
|
||||
* @ingroup DeckEditorCardGroupWidgets
|
||||
*/
|
||||
//! \todo Document this file.
|
||||
|
||||
#ifndef COCKATRICE_DECK_LIST_SORT_FILTER_PROXY_MODEL_H
|
||||
#define COCKATRICE_DECK_LIST_SORT_FILTER_PROXY_MODEL_H
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <libcockatrice/card/database/card_database_manager.h>
|
||||
|
||||
class DeckListSortFilterProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeckListSortFilterProxyModel(QObject *parent = nullptr) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void setSortCriteria(const QStringList &criteria)
|
||||
{
|
||||
sortCriteria = criteria;
|
||||
invalidate(); // re-sort
|
||||
}
|
||||
|
||||
protected:
|
||||
[[nodiscard]] bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||
|
||||
private:
|
||||
QStringList sortCriteria;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_DECK_LIST_SORT_FILTER_PROXY_MODEL_H
|
||||
Reference in New Issue
Block a user