diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 477e8cced..a4bc922dc 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -256,6 +256,7 @@ set(cockatrice_SOURCES src/settings/shortcut_treeview.cpp src/settings/shortcuts_settings.cpp src/utility/card_info_comparator.cpp + src/utility/deck_list_sort_filter_proxy_model.h src/utility/key_signals.cpp src/utility/levenshtein.cpp src/utility/logger.cpp diff --git a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp index 5a1b6dda3..8dfa095ef 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.cpp @@ -3,6 +3,7 @@ #include "../../../../../deck/deck_list_model.h" #include "../../../../../game/cards/card_database_manager.h" #include "../../../../../utility/card_info_comparator.h" +#include "../../../../../utility/deck_list_sort_filter_proxy_model.h" #include "../card_info_picture_with_text_overlay_widget.h" #include @@ -34,10 +35,18 @@ CardGroupDisplayWidget::CardGroupDisplayWidget(QWidget *parent, connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &CardGroupDisplayWidget::onCardRemoval); } -QWidget *CardGroupDisplayWidget::constructWidgetForIndex(int rowIndex) +void CardGroupDisplayWidget::clearAllDisplayWidgets() { - QPersistentModelIndex index = QPersistentModelIndex(deckListModel->index(rowIndex, 0, trackedIndex)); + for (auto idx : indexToWidgetMap.keys()) { + auto displayWidget = indexToWidgetMap.value(idx); + removeFromLayout(displayWidget); + indexToWidgetMap.remove(idx); + delete displayWidget; + } +} +QWidget *CardGroupDisplayWidget::constructWidgetForIndex(QPersistentModelIndex index) +{ if (indexToWidgetMap.contains(index)) { return indexToWidgetMap[index]; } @@ -58,8 +67,28 @@ QWidget *CardGroupDisplayWidget::constructWidgetForIndex(int rowIndex) void CardGroupDisplayWidget::updateCardDisplays() { - for (int i = 0; i < deckListModel->rowCount(trackedIndex); ++i) { - addToLayout(constructWidgetForIndex(i)); + DeckListSortFilterProxyModel proxy; + proxy.setSourceModel(deckListModel); + proxy.setSortCriteria(activeSortCriteria); + + // This doesn't really matter since overwrite the whole lessThan function to just compare dynamically anyway. + proxy.setSortRole(Qt::EditRole); + proxy.sort(1, Qt::AscendingOrder); + + // 1. trackedIndex is a source index → map it to proxy space + QModelIndex proxyParent = proxy.mapFromSource(trackedIndex); + + // 2. iterate children under the proxy parent + for (int i = 0; i < proxy.rowCount(proxyParent); ++i) { + QModelIndex proxyIndex = proxy.index(i, 0, proxyParent); + + // 3. map back to source + QModelIndex sourceIndex = proxy.mapToSource(proxyIndex); + + // 4. persist the source index + QPersistentModelIndex persistent(sourceIndex); + + addToLayout(constructWidgetForIndex(persistent)); } } @@ -69,9 +98,15 @@ void CardGroupDisplayWidget::onCardAddition(const QModelIndex &parent, int first emit cleanupRequested(this); return; } + if (parent == trackedIndex) { - for (int i = first; i <= last; i++) { - insertIntoLayout(constructWidgetForIndex(i), i); + for (int row = first; row <= last; ++row) { + QModelIndex child = deckListModel->index(row, 0, parent); + + // Persist the index + QPersistentModelIndex persistent(child); + + insertIntoLayout(constructWidgetForIndex(persistent), row); } } } @@ -88,12 +123,21 @@ void CardGroupDisplayWidget::onCardRemoval(const QModelIndex &parent, int first, indexToWidgetMap.remove(idx); } } + if (!trackedIndex.isValid()) { emit cleanupRequested(this); } } } +void CardGroupDisplayWidget::onActiveSortCriteriaChanged(QStringList _activeSortCriteria) +{ + activeSortCriteria = std::move(_activeSortCriteria); + + clearAllDisplayWidgets(); + updateCardDisplays(); +} + void CardGroupDisplayWidget::onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card) { emit cardClicked(event, card); diff --git a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.h b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.h index d9e851016..ae5ca9eac 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/card_group_display_widget.h @@ -25,6 +25,7 @@ public: QStringList activeSortCriteria, int bannerOpacity, CardSizeWidget *cardSizeWidget); + void clearAllDisplayWidgets(); DeckListModel *deckListModel; QPersistentModelIndex trackedIndex; @@ -38,10 +39,11 @@ public: public slots: void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card); void onHover(const ExactCard &card); - virtual QWidget *constructWidgetForIndex(int rowIndex); + virtual QWidget *constructWidgetForIndex(QPersistentModelIndex index); virtual void updateCardDisplays(); virtual void onCardAddition(const QModelIndex &parent, int first, int last); virtual void onCardRemoval(const QModelIndex &parent, int first, int last); + void onActiveSortCriteriaChanged(QStringList activeSortCriteria); void resizeEvent(QResizeEvent *event) override; signals: diff --git a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp index 615e0fcbf..583b383de 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.cpp @@ -46,59 +46,6 @@ FlatCardGroupDisplayWidget::FlatCardGroupDisplayWidget(QWidget *parent, connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &FlatCardGroupDisplayWidget::onCardRemoval); } -void FlatCardGroupDisplayWidget::onCardAddition(const QModelIndex &parent, int first, int last) -{ - if (!trackedIndex.isValid()) { - emit cleanupRequested(this); - return; - } - if (parent == trackedIndex) { - for (int i = first; i <= last; i++) { - insertIntoLayout(constructWidgetForIndex(i), i); - } - } -} - -void FlatCardGroupDisplayWidget::onCardRemoval(const QModelIndex &parent, int first, int last) -{ - Q_UNUSED(first); - Q_UNUSED(last); - if (parent == trackedIndex) { - for (const QPersistentModelIndex &idx : indexToWidgetMap.keys()) { - if (!idx.isValid()) { - removeFromLayout(indexToWidgetMap.value(idx)); - indexToWidgetMap.value(idx)->deleteLater(); - indexToWidgetMap.remove(idx); - } - } - if (!trackedIndex.isValid()) { - emit cleanupRequested(this); - } - } -} - -QWidget *FlatCardGroupDisplayWidget::constructWidgetForIndex(int row) -{ - QPersistentModelIndex index = QPersistentModelIndex(deckListModel->index(row, 0, trackedIndex)); - - if (indexToWidgetMap.contains(index)) { - return indexToWidgetMap[index]; - } - auto cardName = deckListModel->data(index.sibling(index.row(), 1), Qt::EditRole).toString(); - auto cardProviderId = deckListModel->data(index.sibling(index.row(), 4), Qt::EditRole).toString(); - - auto widget = new CardInfoPictureWithTextOverlayWidget(flowWidget, true); - widget->setScaleFactor(cardSizeWidget->getSlider()->value()); - widget->setCard(CardDatabaseManager::getInstance()->getCard({cardName, cardProviderId})); - - connect(widget, &CardInfoPictureWithTextOverlayWidget::imageClicked, this, &FlatCardGroupDisplayWidget::onClick); - connect(widget, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this, &FlatCardGroupDisplayWidget::onHover); - connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, widget, &CardInfoPictureWidget::setScaleFactor); - - indexToWidgetMap.insert(index, widget); - return widget; -} - void FlatCardGroupDisplayWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); diff --git a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.h b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.h index fd2c01ebb..0a0fe85ed 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/flat_card_group_display_widget.h @@ -20,10 +20,7 @@ public: CardSizeWidget *cardSizeWidget); public slots: - QWidget *constructWidgetForIndex(int row) override; void resizeEvent(QResizeEvent *event) override; - void onCardAddition(const QModelIndex &parent, int first, int last) override; - void onCardRemoval(const QModelIndex &parent, int first, int last) override; private: FlowWidget *flowWidget; diff --git a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp index fc94e2af0..658d3eccf 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.cpp @@ -49,37 +49,6 @@ OverlappedCardGroupDisplayWidget::OverlappedCardGroupDisplayWidget(QWidget *pare connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &OverlappedCardGroupDisplayWidget::onCardRemoval); } -void OverlappedCardGroupDisplayWidget::onCardAddition(const QModelIndex &parent, int first, int last) -{ - if (!trackedIndex.isValid()) { - emit cleanupRequested(this); - return; - } - if (parent == trackedIndex) { - for (int i = first; i <= last; i++) { - insertIntoLayout(constructWidgetForIndex(i), i); - } - } -} - -void OverlappedCardGroupDisplayWidget::onCardRemoval(const QModelIndex &parent, int first, int last) -{ - Q_UNUSED(first); - Q_UNUSED(last); - if (parent == trackedIndex) { - for (const QPersistentModelIndex &idx : indexToWidgetMap.keys()) { - if (!idx.isValid()) { - removeFromLayout(indexToWidgetMap.value(idx)); - indexToWidgetMap.value(idx)->deleteLater(); - indexToWidgetMap.remove(idx); - } - } - if (!trackedIndex.isValid()) { - emit cleanupRequested(this); - } - } -} - void OverlappedCardGroupDisplayWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); diff --git a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.h b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.h index 1d5b2be5f..0cba41172 100644 --- a/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/card_group_display_widgets/overlapped_card_group_display_widget.h @@ -20,8 +20,6 @@ public: CardSizeWidget *cardSizeWidget); public slots: - void onCardAddition(const QModelIndex &parent, int first, int last) override; - void onCardRemoval(const QModelIndex &parent, int first, int last) override; void resizeEvent(QResizeEvent *event) override; private: diff --git a/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp b/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp index 99adf762a..4f599c4e8 100644 --- a/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp +++ b/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.cpp @@ -62,22 +62,26 @@ void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex auto *displayWidget = new OverlappedCardGroupDisplayWidget( cardGroupContainer, deckListModel, index, zoneName, categoryName, activeGroupCriteria, activeSortCriteria, subBannerOpacity, cardSizeWidget); - connect(displayWidget, SIGNAL(cardClicked(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *)), this, - SLOT(onClick(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *))); - connect(displayWidget, SIGNAL(cardHovered(ExactCard)), this, SLOT(onHover(ExactCard))); + connect(displayWidget, &OverlappedCardGroupDisplayWidget::cardClicked, this, + &DeckCardZoneDisplayWidget::onClick); + connect(displayWidget, &OverlappedCardGroupDisplayWidget::cardHovered, this, + &DeckCardZoneDisplayWidget::onHover); connect(displayWidget, &CardGroupDisplayWidget::cleanupRequested, this, &DeckCardZoneDisplayWidget::cleanupInvalidCardGroup); + connect(this, &DeckCardZoneDisplayWidget::activeSortCriteriaChanged, displayWidget, + &CardGroupDisplayWidget::onActiveSortCriteriaChanged); cardGroupLayout->addWidget(displayWidget); indexToWidgetMap.insert(index, displayWidget); } else if (displayType == DisplayType::Flat) { auto *displayWidget = new FlatCardGroupDisplayWidget(cardGroupContainer, deckListModel, index, zoneName, categoryName, activeGroupCriteria, activeSortCriteria, subBannerOpacity, cardSizeWidget); - connect(displayWidget, SIGNAL(cardClicked(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *)), this, - SLOT(onClick(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *))); - connect(displayWidget, SIGNAL(cardHovered(ExactCard)), this, SLOT(onHover(ExactCard))); + connect(displayWidget, &FlatCardGroupDisplayWidget::cardClicked, this, &DeckCardZoneDisplayWidget::onClick); + connect(displayWidget, &FlatCardGroupDisplayWidget::cardHovered, this, &DeckCardZoneDisplayWidget::onHover); connect(displayWidget, &CardGroupDisplayWidget::cleanupRequested, this, &DeckCardZoneDisplayWidget::cleanupInvalidCardGroup); + connect(this, &DeckCardZoneDisplayWidget::activeSortCriteriaChanged, displayWidget, + &CardGroupDisplayWidget::onActiveSortCriteriaChanged); cardGroupLayout->addWidget(displayWidget); indexToWidgetMap.insert(index, displayWidget); } @@ -85,9 +89,25 @@ void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex void DeckCardZoneDisplayWidget::displayCards() { - for (int i = 0; i < deckListModel->rowCount(trackedIndex); ++i) { - QPersistentModelIndex index = QPersistentModelIndex(deckListModel->index(i, 0, trackedIndex)); - constructAppropriateWidget(index); + QSortFilterProxyModel proxy; + proxy.setSourceModel(deckListModel); + proxy.setSortRole(Qt::EditRole); + proxy.sort(1, Qt::AscendingOrder); + + // 1. trackedIndex is a source index → map it to proxy space + QModelIndex proxyParent = proxy.mapFromSource(trackedIndex); + + // 2. iterate children under the proxy parent + for (int i = 0; i < proxy.rowCount(proxyParent); ++i) { + QModelIndex proxyIndex = proxy.index(i, 0, proxyParent); + + // 3. map back to source + QModelIndex sourceIndex = proxy.mapToSource(proxyIndex); + + // 4. persist the source index + QPersistentModelIndex persistent(sourceIndex); + + constructAppropriateWidget(persistent); } } diff --git a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.cpp b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.cpp index 74ae820ec..a8704f65b 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.cpp +++ b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.cpp @@ -270,16 +270,25 @@ void VisualDeckEditorWidget::onCardRemoval(const QModelIndex &parent, int first, void VisualDeckEditorWidget::constructZoneWidgetsFromDeckListModel() { - for (int i = 0; i < deckListModel->rowCount(deckListModel->parent(QModelIndex())); i++) { - QPersistentModelIndex index = QPersistentModelIndex(deckListModel->index(i, 0, deckListModel->getRoot())); + QSortFilterProxyModel proxy; + proxy.setSourceModel(deckListModel); + proxy.setSortRole(Qt::EditRole); + proxy.sort(1, Qt::AscendingOrder); - if (indexToWidgetMap.contains(index)) { + for (int i = 0; i < proxy.rowCount(); ++i) { + QModelIndex proxyIndex = proxy.index(i, 0); + QModelIndex sourceIndex = proxy.mapToSource(proxyIndex); + + // Make a persistent index from the *source* model + QPersistentModelIndex persistent(sourceIndex); + + if (indexToWidgetMap.contains(persistent)) { continue; } DeckCardZoneDisplayWidget *zoneDisplayWidget = new DeckCardZoneDisplayWidget( - zoneContainer, deckListModel, index, - deckListModel->data(index.sibling(index.row(), 1), Qt::EditRole).toString(), activeGroupCriteria, + zoneContainer, deckListModel, persistent, + deckListModel->data(persistent.sibling(persistent.row(), 1), Qt::EditRole).toString(), activeGroupCriteria, activeSortCriteria, currentDisplayType, 20, 10, cardSizeWidget); connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardHovered, this, &VisualDeckEditorWidget::onHover); connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardClicked, this, &VisualDeckEditorWidget::onCardClick); @@ -293,7 +302,7 @@ void VisualDeckEditorWidget::constructZoneWidgetsFromDeckListModel() &DeckCardZoneDisplayWidget::refreshDisplayType); zoneContainerLayout->addWidget(zoneDisplayWidget); - indexToWidgetMap.insert(index, zoneDisplayWidget); + indexToWidgetMap.insert(persistent, zoneDisplayWidget); } } diff --git a/cockatrice/src/deck/deck_list_model.cpp b/cockatrice/src/deck/deck_list_model.cpp index 4ae041c5d..b7be03a06 100644 --- a/cockatrice/src/deck/deck_list_model.cpp +++ b/cockatrice/src/deck/deck_list_model.cpp @@ -142,6 +142,8 @@ QVariant DeckListModel::data(const QModelIndex &index, int role) const return {}; } } + case Qt::UserRole + 1: + return false; case Qt::BackgroundRole: { int color = 90 + 60 * node->depth(); return QBrush(QColor(color, 255, color)); @@ -171,6 +173,8 @@ QVariant DeckListModel::data(const QModelIndex &index, int role) const return {}; } } + case Qt::UserRole + 1: + return true; case Qt::BackgroundRole: { int color = 255 - (index.row() % 2) * 30; return QBrush(QColor(color, color, color)); diff --git a/cockatrice/src/utility/deck_list_sort_filter_proxy_model.h b/cockatrice/src/utility/deck_list_sort_filter_proxy_model.h new file mode 100644 index 000000000..5b88c211d --- /dev/null +++ b/cockatrice/src/utility/deck_list_sort_filter_proxy_model.h @@ -0,0 +1,79 @@ +#ifndef COCKATRICE_DECK_LIST_SORT_FILTER_PROXY_MODEL_H +#define COCKATRICE_DECK_LIST_SORT_FILTER_PROXY_MODEL_H + +#include "../game/cards/card_database_manager.h" + +#include + +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: + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override + { + 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(1), Qt::EditRole).toString(); + QString rName = src->data(right.siblingAtColumn(1), Qt::EditRole).toString(); + return lName.localeAwareCompare(rName) < 0; + } + + // Both are cards -> apply sort criteria + auto *lNode = static_cast(left.internalPointer()); + auto *rNode = static_cast(right.internalPointer()); + + CardInfoPtr lInfo = CardDatabaseManager::getInstance()->guessCard({lNode->getName()}).getCardPtr(); + CardInfoPtr rInfo = CardDatabaseManager::getInstance()->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; + } + +private: + QStringList sortCriteria; +}; + +#endif // COCKATRICE_DECK_LIST_SORT_FILTER_PROXY_MODEL_H