From d5dc70ccee71bc38af7b6e0514b5931b34ee7f23 Mon Sep 17 00:00:00 2001 From: BruebachL <44814898+BruebachL@users.noreply.github.com> Date: Fri, 13 Jun 2025 19:21:34 +0200 Subject: [PATCH] More granular decklist signals (#5981) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Performance stuffs. * Actually make widgets track their indices. * Functional stuff. * More display stuff. * Determine where we will insert the card before actually inserting it in the model. * Allow overlap layouts to insert widgets at specific positions. * Modified signals. * Raise trailing widgets on overlap layout widget insertion. * Nix the logging config changes. * Lint. * Address comments. * Address comments. --------- Co-authored-by: Lukas BrĂ¼bach --- .../tab_deck_editor_visual.cpp | 2 +- .../src/client/tapped_out_interface.cpp | 4 +- .../src/client/ui/layouts/flow_layout.cpp | 11 ++ .../src/client/ui/layouts/flow_layout.h | 1 + .../src/client/ui/layouts/overlap_layout.cpp | 13 ++ .../src/client/ui/layouts/overlap_layout.h | 1 + .../card_group_display_widget.cpp | 81 ++++++--- .../card_group_display_widget.h | 33 +++- .../flat_card_group_display_widget.cpp | 120 +++++++------ .../flat_card_group_display_widget.h | 28 ++- .../overlapped_card_group_display_widget.cpp | 111 +++++------- .../overlapped_card_group_display_widget.h | 27 ++- .../cards/deck_card_zone_display_widget.cpp | 163 ++++++++++-------- .../cards/deck_card_zone_display_widget.h | 11 +- .../deck_editor_deck_dock_widget.cpp | 3 + .../general/layout_containers/flow_widget.cpp | 6 + .../general/layout_containers/flow_widget.h | 1 + .../layout_containers/overlap_widget.cpp | 6 + .../layout_containers/overlap_widget.h | 1 + .../visual_deck_editor_widget.cpp | 149 ++++++++++------ .../visual_deck_editor_widget.h | 8 +- cockatrice/src/deck/deck_list_model.cpp | 47 ++++- cockatrice/src/deck/deck_list_model.h | 9 +- cockatrice/src/deck/deck_stats_interface.cpp | 2 +- common/decklist.cpp | 17 +- common/decklist.h | 13 +- 26 files changed, 546 insertions(+), 322 deletions(-) diff --git a/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index 68f1eee17..7e3ce3c5b 100644 --- a/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/client/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -81,7 +81,7 @@ void TabDeckEditorVisual::createCentralFrame() void TabDeckEditorVisual::onDeckChanged() { AbstractTabDeckEditor::onDeckModified(); - tabContainer->visualDeckView->decklistDataChanged(QModelIndex(), QModelIndex()); + tabContainer->visualDeckView->constructZoneWidgetsFromDeckListModel(); tabContainer->deckAnalytics->refreshDisplays(deckDockWidget->deckModel); tabContainer->sampleHandWidget->setDeckModel(deckDockWidget->deckModel); } diff --git a/cockatrice/src/client/tapped_out_interface.cpp b/cockatrice/src/client/tapped_out_interface.cpp index 4fd103c8c..05d163970 100644 --- a/cockatrice/src/client/tapped_out_interface.cpp +++ b/cockatrice/src/client/tapped_out_interface.cpp @@ -101,9 +101,9 @@ void TappedOutInterface::copyDeckSplitMainAndSide(DeckList &source, DeckList &ma DecklistCardNode *addedCard; if (node->getName() == DECK_ZONE_SIDE) - addedCard = sideboard.addCard(card->getName(), node->getName()); + addedCard = sideboard.addCard(card->getName(), node->getName(), -1); else - addedCard = mainboard.addCard(card->getName(), node->getName()); + addedCard = mainboard.addCard(card->getName(), node->getName(), -1); addedCard->setNumber(card->getNumber()); }; diff --git a/cockatrice/src/client/ui/layouts/flow_layout.cpp b/cockatrice/src/client/ui/layouts/flow_layout.cpp index 6e00fdb68..f7ebbfb79 100644 --- a/cockatrice/src/client/ui/layouts/flow_layout.cpp +++ b/cockatrice/src/client/ui/layouts/flow_layout.cpp @@ -548,6 +548,17 @@ void FlowLayout::addItem(QLayoutItem *item) } } +void FlowLayout::insertWidgetAtIndex(QWidget *toInsert, int index) +{ + addChildWidget(toInsert); + + // We don't want to fail on an index that violates the bounds, so we just clamp it. + int boundedIndex = qBound(0, index, qMax(0, static_cast(items.size()))); + items.insert(boundedIndex, new QWidgetItem(toInsert)); + + invalidate(); +} + /** * @brief Retrieves the count of items in the layout. * @return The number of layout items. diff --git a/cockatrice/src/client/ui/layouts/flow_layout.h b/cockatrice/src/client/ui/layouts/flow_layout.h index d8c6d15c8..19f696d1c 100644 --- a/cockatrice/src/client/ui/layouts/flow_layout.h +++ b/cockatrice/src/client/ui/layouts/flow_layout.h @@ -15,6 +15,7 @@ public: explicit FlowLayout(QWidget *parent = nullptr); FlowLayout(QWidget *parent, Qt::Orientation _flowDirection, int margin = 0, int hSpacing = 0, int vSpacing = 0); ~FlowLayout() override; + void insertWidgetAtIndex(QWidget *toInsert, int index); QSize calculateMinimumSizeHorizontal() const; QSize calculateSizeHintVertical() const; diff --git a/cockatrice/src/client/ui/layouts/overlap_layout.cpp b/cockatrice/src/client/ui/layouts/overlap_layout.cpp index 234bc41f4..9bf5e8468 100644 --- a/cockatrice/src/client/ui/layouts/overlap_layout.cpp +++ b/cockatrice/src/client/ui/layouts/overlap_layout.cpp @@ -54,6 +54,19 @@ OverlapLayout::~OverlapLayout() } } +void OverlapLayout::insertWidgetAtIndex(QWidget *toInsert, int index) +{ + addChildWidget(toInsert); + int clampedIndex = qBound(0, index, qMax(0, static_cast(itemList.size()))); + itemList.insert(clampedIndex, new QWidgetItem(toInsert)); + + for (int i = clampedIndex; i < itemList.size(); ++i) { + dynamic_cast(itemList.at(i))->widget()->raise(); + } + + invalidate(); +} + /** * @brief Adds a new item to the layout. * diff --git a/cockatrice/src/client/ui/layouts/overlap_layout.h b/cockatrice/src/client/ui/layouts/overlap_layout.h index d2cfca32a..ab823222a 100644 --- a/cockatrice/src/client/ui/layouts/overlap_layout.h +++ b/cockatrice/src/client/ui/layouts/overlap_layout.h @@ -18,6 +18,7 @@ public: Qt::Orientation overlapDirection = Qt::Vertical, Qt::Orientation flowDirection = Qt::Horizontal); ~OverlapLayout(); + void insertWidgetAtIndex(QWidget *toInsert, int index); void addItem(QLayoutItem *item) override; int count() const override; 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 7e2caaccc..c4ccfdcb3 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 @@ -9,15 +9,16 @@ CardGroupDisplayWidget::CardGroupDisplayWidget(QWidget *parent, DeckListModel *_deckListModel, + QPersistentModelIndex _trackedIndex, QString _zoneName, QString _cardGroupCategory, QString _activeGroupCriteria, QStringList _activeSortCriteria, int bannerOpacity, CardSizeWidget *_cardSizeWidget) - : QWidget(parent), deckListModel(_deckListModel), zoneName(_zoneName), cardGroupCategory(_cardGroupCategory), - activeGroupCriteria(_activeGroupCriteria), activeSortCriteria(_activeSortCriteria), - cardSizeWidget(_cardSizeWidget) + : QWidget(parent), deckListModel(_deckListModel), trackedIndex(_trackedIndex), zoneName(_zoneName), + cardGroupCategory(_cardGroupCategory), activeGroupCriteria(_activeGroupCriteria), + activeSortCriteria(_activeSortCriteria), cardSizeWidget(_cardSizeWidget) { layout = new QVBoxLayout(this); setLayout(layout); @@ -26,42 +27,70 @@ CardGroupDisplayWidget::CardGroupDisplayWidget(QWidget *parent, banner = new BannerWidget(this, cardGroupCategory, Qt::Orientation::Vertical, bannerOpacity); layout->addWidget(banner); + CardGroupDisplayWidget::updateCardDisplays(); + + connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &CardGroupDisplayWidget::onCardAddition); + connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &CardGroupDisplayWidget::onCardRemoval); +} + +QWidget *CardGroupDisplayWidget::constructWidgetForIndex(int rowIndex) +{ + QPersistentModelIndex index = QPersistentModelIndex(deckListModel->index(rowIndex, 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(getLayoutParent(), true); + widget->setScaleFactor(cardSizeWidget->getSlider()->value()); + widget->setCard(CardDatabaseManager::getInstance()->getCardByNameAndProviderId(cardName, cardProviderId)); + + connect(widget, &CardInfoPictureWithTextOverlayWidget::imageClicked, this, &CardGroupDisplayWidget::onClick); + connect(widget, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this, &CardGroupDisplayWidget::onHover); + connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, widget, &CardInfoPictureWidget::setScaleFactor); + + indexToWidgetMap.insert(index, widget); + return widget; } void CardGroupDisplayWidget::updateCardDisplays() { + for (int i = 0; i < deckListModel->rowCount(trackedIndex); ++i) { + addToLayout(constructWidgetForIndex(i)); + } } -QList CardGroupDisplayWidget::getCardsMatchingGroup(QList cardsToSort) +void CardGroupDisplayWidget::onCardAddition(const QModelIndex &parent, int first, int last) { - cardsToSort = sortCardList(cardsToSort, activeSortCriteria, Qt::SortOrder::AscendingOrder); - - QList activeList; - for (const CardInfoPtr &info : cardsToSort) { - if (info && info->getProperty(activeGroupCriteria) == cardGroupCategory) { - activeList.append(info); + if (!trackedIndex.isValid()) { + emit cleanupRequested(this); + return; + } + if (parent == trackedIndex) { + for (int i = first; i <= last; i++) { + insertIntoLayout(constructWidgetForIndex(i), i); } } - - return activeList; } -QList CardGroupDisplayWidget::sortCardList(QList cardsToSort, - const QStringList properties, - Qt::SortOrder order = Qt::AscendingOrder) +void CardGroupDisplayWidget::onCardRemoval(const QModelIndex &parent, int first, int last) { - CardInfoComparator comparator(properties, order); - std::sort(cardsToSort.begin(), cardsToSort.end(), comparator); - - return cardsToSort; -} - -void CardGroupDisplayWidget::onActiveSortCriteriaChanged(QStringList _activeSortCriteria) -{ - if (activeSortCriteria != _activeSortCriteria) { - activeSortCriteria = _activeSortCriteria; - updateCardDisplays(); // Refresh display with new sorting + 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); + } } } 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 079c5e3ea..e24b2b254 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 @@ -18,6 +18,7 @@ class CardGroupDisplayWidget : public QWidget public: CardGroupDisplayWidget(QWidget *parent, DeckListModel *deckListModel, + QPersistentModelIndex trackedIndex, QString zoneName, QString cardGroupCategory, QString activeGroupCriteria, @@ -25,10 +26,9 @@ public: int bannerOpacity, CardSizeWidget *cardSizeWidget); - QList getCardsMatchingGroup(QList cardsToSort); - void resizeEvent(QResizeEvent *event) override; - DeckListModel *deckListModel; + QPersistentModelIndex trackedIndex; + QHash indexToWidgetMap; QString zoneName; QString cardGroupCategory; QString activeGroupCriteria; @@ -36,18 +36,41 @@ public: CardSizeWidget *cardSizeWidget; public slots: - QList sortCardList(QList cardsToSort, QStringList properties, Qt::SortOrder order); void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card); void onHover(CardInfoPtr card); + virtual QWidget *constructWidgetForIndex(int rowIndex); virtual void updateCardDisplays(); - void onActiveSortCriteriaChanged(QStringList activeSortCriteria); + virtual void onCardAddition(const QModelIndex &parent, int first, int last); + virtual void onCardRemoval(const QModelIndex &parent, int first, int last); + void resizeEvent(QResizeEvent *event) override; signals: void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card); void cardHovered(CardInfoPtr card); + void cleanupRequested(CardGroupDisplayWidget *cardGroupDisplayWidget); protected: QVBoxLayout *layout; BannerWidget *banner; + + virtual QWidget *getLayoutParent() + { + return this; + } + + virtual void addToLayout(QWidget *toAdd) + { + layout->addWidget(toAdd); + } + + virtual void insertIntoLayout(QWidget *toInsert, int insertAt) + { + layout->insertWidget(insertAt, toInsert); + } + + virtual void removeFromLayout(QWidget *toRemove) + { + layout->removeWidget(toRemove); + } }; #endif // CARD_GROUP_DISPLAY_WIDGET_H 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 62eeb0134..3fdfed9f1 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 @@ -6,9 +6,11 @@ #include "../card_info_picture_with_text_overlay_widget.h" #include +#include FlatCardGroupDisplayWidget::FlatCardGroupDisplayWidget(QWidget *parent, DeckListModel *_deckListModel, + QPersistentModelIndex _trackedIndex, QString _zoneName, QString _cardGroupCategory, QString _activeGroupCriteria, @@ -17,6 +19,7 @@ FlatCardGroupDisplayWidget::FlatCardGroupDisplayWidget(QWidget *parent, CardSizeWidget *_cardSizeWidget) : CardGroupDisplayWidget(parent, _deckListModel, + std::move(_trackedIndex), _zoneName, _cardGroupCategory, _activeGroupCriteria, @@ -28,77 +31,72 @@ FlatCardGroupDisplayWidget::FlatCardGroupDisplayWidget(QWidget *parent, banner->setBuddy(flowWidget); layout->addWidget(flowWidget); + + for (const QPersistentModelIndex &idx : indexToWidgetMap.keys()) { + FlatCardGroupDisplayWidget::removeFromLayout(indexToWidgetMap.value(idx)); + indexToWidgetMap.value(idx)->deleteLater(); + indexToWidgetMap.remove(idx); + } + FlatCardGroupDisplayWidget::updateCardDisplays(); - connect(deckListModel, &DeckListModel::dataChanged, this, &FlatCardGroupDisplayWidget::updateCardDisplays); + disconnect(deckListModel, &QAbstractItemModel::rowsInserted, this, &CardGroupDisplayWidget::onCardAddition); + disconnect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &CardGroupDisplayWidget::onCardRemoval); + + connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &FlatCardGroupDisplayWidget::onCardAddition); + connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &FlatCardGroupDisplayWidget::onCardRemoval); } -void FlatCardGroupDisplayWidget::updateCardDisplays() +void FlatCardGroupDisplayWidget::onCardAddition(const QModelIndex &parent, int first, int last) { - // Retrieve and sort cards - QList cardsInZone = getCardsMatchingGroup(deckListModel->getCardsAsCardInfoPtrsForZone(zoneName)); - - // Show or hide widget - bool shouldBeVisible = !cardsInZone.isEmpty(); - if (shouldBeVisible != isVisible()) { - setVisible(shouldBeVisible); + if (!trackedIndex.isValid()) { + emit cleanupRequested(this); + return; } - - // Retrieve existing widgets - QList existingWidgets = - flowWidget->findChildren(); - - QHash> widgetMap; - for (CardInfoPictureWithTextOverlayWidget *widget : existingWidgets) { - widgetMap[widget->getInfo()->getName()].append(widget); - } - - QList sortedWidgets; - QSet usedWidgets; - - // Ensure widgets are ordered to match the sorted cards - for (const CardInfoPtr &card : cardsInZone) { - QString name = card->getName(); - CardInfoPictureWithTextOverlayWidget *widget = nullptr; - - if (!widgetMap[name].isEmpty()) { - // Reuse an existing widget - widget = widgetMap[name].takeFirst(); - } else { - // Create a new widget if needed - widget = new CardInfoPictureWithTextOverlayWidget(flowWidget, true); - widget->setScaleFactor(cardSizeWidget->getSlider()->value()); - widget->setCard(card); - - connect(widget, &CardInfoPictureWithTextOverlayWidget::imageClicked, this, - &FlatCardGroupDisplayWidget::onClick); - connect(widget, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this, - &FlatCardGroupDisplayWidget::onHover); - connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, widget, - &CardInfoPictureWidget::setScaleFactor); - - flowWidget->addWidget(widget); - } - - // Store in sorted order - sortedWidgets.append(widget); - usedWidgets.insert(widget); - } - - // Remove extra widgets - for (CardInfoPictureWithTextOverlayWidget *widget : existingWidgets) { - if (!usedWidgets.contains(widget)) { - flowWidget->layout()->removeWidget(widget); - widget->deleteLater(); + if (parent == trackedIndex) { + for (int i = first; i <= last; i++) { + insertIntoLayout(constructWidgetForIndex(i), i); } } +} - // **Reorder widgets in place** - for (int i = 0; i < sortedWidgets.size(); ++i) { - sortedWidgets[i]->setParent(nullptr); // Temporarily detach +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); + } } - for (int i = 0; i < sortedWidgets.size(); ++i) { - flowWidget->addWidget(sortedWidgets[i]); // Reattach in correct order +} + +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()->getCardByNameAndProviderId(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) 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 45a75907e..fd2c01ebb 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 @@ -11,6 +11,7 @@ class FlatCardGroupDisplayWidget : public CardGroupDisplayWidget public: FlatCardGroupDisplayWidget(QWidget *parent, DeckListModel *deckListModel, + QPersistentModelIndex trackedIndex, QString zoneName, QString cardGroupCategory, QString activeGroupCriteria, @@ -18,13 +19,34 @@ public: int bannerOpacity, CardSizeWidget *cardSizeWidget); - void resizeEvent(QResizeEvent *event) override; - public slots: - void updateCardDisplays() override; + 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; + + QWidget *getLayoutParent() override + { + return flowWidget; + } + + void addToLayout(QWidget *toAdd) override + { + flowWidget->addWidget(toAdd); + } + + void insertIntoLayout(QWidget *toInsert, int insertAt) override + { + flowWidget->insertWidgetAtIndex(toInsert, insertAt); + } + + void removeFromLayout(QWidget *toRemove) override + { + flowWidget->removeWidget(toRemove); + } }; #endif // FLAT_CARD_GROUP_DISPLAY_WIDGET_H 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 0e4a88cf3..fc94e2af0 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 @@ -9,6 +9,7 @@ OverlappedCardGroupDisplayWidget::OverlappedCardGroupDisplayWidget(QWidget *parent, DeckListModel *_deckListModel, + QPersistentModelIndex _trackedIndex, QString _zoneName, QString _cardGroupCategory, QString _activeGroupCriteria, @@ -17,6 +18,7 @@ OverlappedCardGroupDisplayWidget::OverlappedCardGroupDisplayWidget(QWidget *pare CardSizeWidget *_cardSizeWidget) : CardGroupDisplayWidget(parent, _deckListModel, + _trackedIndex, _zoneName, _cardGroupCategory, _activeGroupCriteria, @@ -28,89 +30,54 @@ OverlappedCardGroupDisplayWidget::OverlappedCardGroupDisplayWidget(QWidget *pare banner->setBuddy(overlapWidget); layout->addWidget(overlapWidget); + + for (const QPersistentModelIndex &idx : indexToWidgetMap.keys()) { + OverlappedCardGroupDisplayWidget::removeFromLayout(indexToWidgetMap.value(idx)); + indexToWidgetMap.value(idx)->deleteLater(); + indexToWidgetMap.remove(idx); + } + OverlappedCardGroupDisplayWidget::updateCardDisplays(); - connect(deckListModel, &DeckListModel::dataChanged, this, &OverlappedCardGroupDisplayWidget::updateCardDisplays); + connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, this, [this]() { overlapWidget->adjustMaxColumnsAndRows(); }); + + disconnect(deckListModel, &QAbstractItemModel::rowsInserted, this, &CardGroupDisplayWidget::onCardAddition); + disconnect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &CardGroupDisplayWidget::onCardRemoval); + + connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &OverlappedCardGroupDisplayWidget::onCardAddition); + connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &OverlappedCardGroupDisplayWidget::onCardRemoval); } -void OverlappedCardGroupDisplayWidget::updateCardDisplays() +void OverlappedCardGroupDisplayWidget::onCardAddition(const QModelIndex &parent, int first, int last) { - overlapWidget->setUpdatesEnabled(false); - // Retrieve and sort cards - QList cardsInZone = getCardsMatchingGroup(deckListModel->getCardsAsCardInfoPtrsForZone(zoneName)); - - // Show or hide widget - bool shouldBeVisible = !cardsInZone.isEmpty(); - if (shouldBeVisible != isVisible()) { - setVisible(shouldBeVisible); + if (!trackedIndex.isValid()) { + emit cleanupRequested(this); + return; } - - // Retrieve existing widgets - QList existingWidgets = - overlapWidget->findChildren(); - - QHash> widgetMap; - for (CardInfoPictureWithTextOverlayWidget *widget : existingWidgets) { - widgetMap[widget->getInfo()->getName()].append(widget); - } - - QList sortedWidgets; - QSet usedWidgets; - - // Ensure widgets are ordered to match the sorted cards - for (const CardInfoPtr &card : cardsInZone) { - QString name = card->getName(); - CardInfoPictureWithTextOverlayWidget *widget = nullptr; - - if (!widgetMap[name].isEmpty()) { - // Reuse an existing widget - widget = widgetMap[name].takeFirst(); - } else { - // Create a new widget if needed - widget = new CardInfoPictureWithTextOverlayWidget(overlapWidget, true); - widget->setScaleFactor(cardSizeWidget->getSlider()->value()); - widget->setCard(card); - - connect(widget, &CardInfoPictureWithTextOverlayWidget::imageClicked, this, - &OverlappedCardGroupDisplayWidget::onClick); - connect(widget, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this, - &OverlappedCardGroupDisplayWidget::onHover); - connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, widget, - &CardInfoPictureWidget::setScaleFactor); - - overlapWidget->addWidget(widget); - } - - // Store in sorted order - sortedWidgets.append(widget); - usedWidgets.insert(widget); - } - - // Remove extra widgets - for (CardInfoPictureWithTextOverlayWidget *widget : existingWidgets) { - if (!usedWidgets.contains(widget)) { - overlapWidget->layout()->removeWidget(widget); - widget->deleteLater(); + if (parent == trackedIndex) { + for (int i = first; i <= last; i++) { + insertIntoLayout(constructWidgetForIndex(i), i); } } +} - // **Reorder widgets in place** - for (int i = 0; i < sortedWidgets.size(); ++i) { - sortedWidgets[i]->setParent(nullptr); // Temporarily detach +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); + } } - for (int i = 0; i < sortedWidgets.size(); ++i) { - overlapWidget->addWidget(sortedWidgets[i]); // Reattach in correct order - } - - // Ensure proper layering - for (CardInfoPictureWithTextOverlayWidget *widget : sortedWidgets) { - widget->raise(); - } - - overlapWidget->adjustMaxColumnsAndRows(); - overlapWidget->setUpdatesEnabled(true); - overlapWidget->update(); } void OverlappedCardGroupDisplayWidget::resizeEvent(QResizeEvent *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 e492316cd..1d5b2be5f 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 @@ -11,6 +11,7 @@ class OverlappedCardGroupDisplayWidget : public CardGroupDisplayWidget public: OverlappedCardGroupDisplayWidget(QWidget *parent, DeckListModel *deckListModel, + QPersistentModelIndex trackedIndex, QString zoneName, QString cardGroupCategory, QString activeGroupCriteria, @@ -18,13 +19,33 @@ public: int bannerOpacity, CardSizeWidget *cardSizeWidget); - void resizeEvent(QResizeEvent *event) override; - public slots: - void updateCardDisplays() override; + 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: OverlapWidget *overlapWidget; + + QWidget *getLayoutParent() override + { + return overlapWidget; + } + + void addToLayout(QWidget *toAdd) override + { + overlapWidget->addWidget(toAdd); + } + + void insertIntoLayout(QWidget *toInsert, int insertAt) override + { + overlapWidget->insertWidgetAtIndex(toInsert, insertAt); + } + + void removeFromLayout(QWidget *toRemove) override + { + overlapWidget->removeWidget(toRemove); + } }; #endif // OVERLAPPED_CARD_GROUP_DISPLAY_WIDGET_H 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 f1885e11f..d6383f5e9 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 @@ -9,6 +9,7 @@ DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent, DeckListModel *_deckListModel, + QPersistentModelIndex _trackedIndex, QString _zoneName, QString _activeGroupCriteria, QStringList _activeSortCriteria, @@ -16,9 +17,9 @@ DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent, int bannerOpacity, int subBannerOpacity, CardSizeWidget *_cardSizeWidget) - : QWidget(parent), deckListModel(_deckListModel), zoneName(_zoneName), activeGroupCriteria(_activeGroupCriteria), - activeSortCriteria(_activeSortCriteria), displayType(_displayType), bannerOpacity(bannerOpacity), - subBannerOpacity(subBannerOpacity), cardSizeWidget(_cardSizeWidget) + : QWidget(parent), deckListModel(_deckListModel), trackedIndex(_trackedIndex), zoneName(_zoneName), + activeGroupCriteria(_activeGroupCriteria), activeSortCriteria(_activeSortCriteria), displayType(_displayType), + bannerOpacity(bannerOpacity), subBannerOpacity(subBannerOpacity), cardSizeWidget(_cardSizeWidget) { layout = new QVBoxLayout(this); setLayout(layout); @@ -34,8 +35,94 @@ DeckCardZoneDisplayWidget::DeckCardZoneDisplayWidget(QWidget *parent, banner->setBuddy(cardGroupContainer); displayCards(); - connect(deckListModel, &DeckListModel::dataChanged, this, &DeckCardZoneDisplayWidget::displayCards); + + connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &DeckCardZoneDisplayWidget::onCategoryAddition); + connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &DeckCardZoneDisplayWidget::onCategoryRemoval); } + +void DeckCardZoneDisplayWidget::cleanupInvalidCardGroup(CardGroupDisplayWidget *displayWidget) +{ + cardGroupLayout->removeWidget(displayWidget); + displayWidget->setParent(nullptr); + for (auto idx : indexToWidgetMap.keys()) { + if (!idx.isValid()) { + indexToWidgetMap.remove(idx); + } + } + delete displayWidget; +} + +void DeckCardZoneDisplayWidget::constructAppropriateWidget(QPersistentModelIndex index) +{ + auto categoryName = deckListModel->data(index.sibling(index.row(), 1), Qt::EditRole).toString(); + if (indexToWidgetMap.contains(index)) { + return; + } + if (displayType == DisplayType::Overlap) { + 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(CardInfoPtr)), this, SLOT(onHover(CardInfoPtr))); + connect(displayWidget, &CardGroupDisplayWidget::cleanupRequested, this, + &DeckCardZoneDisplayWidget::cleanupInvalidCardGroup); + 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(CardInfoPtr)), this, SLOT(onHover(CardInfoPtr))); + connect(displayWidget, &CardGroupDisplayWidget::cleanupRequested, this, + &DeckCardZoneDisplayWidget::cleanupInvalidCardGroup); + cardGroupLayout->addWidget(displayWidget); + indexToWidgetMap.insert(index, displayWidget); + } +} + +void DeckCardZoneDisplayWidget::displayCards() +{ + for (int i = 0; i < deckListModel->rowCount(trackedIndex); ++i) { + QPersistentModelIndex index = QPersistentModelIndex(deckListModel->index(i, 0, trackedIndex)); + constructAppropriateWidget(index); + } +} + +void DeckCardZoneDisplayWidget::onCategoryAddition(const QModelIndex &parent, int first, int last) +{ + if (!trackedIndex.isValid()) { + emit requestCleanup(this); + return; + } + if (parent == trackedIndex) { + for (int i = first; i <= last; i++) { + QPersistentModelIndex index = QPersistentModelIndex(deckListModel->index(i, 0, trackedIndex)); + + constructAppropriateWidget(index); + } + } +} + +void DeckCardZoneDisplayWidget::onCategoryRemoval(const QModelIndex &parent, int first, int last) +{ + Q_UNUSED(parent); + Q_UNUSED(first); + Q_UNUSED(last); + for (const QPersistentModelIndex &idx : indexToWidgetMap.keys()) { + if (!idx.isValid()) { + cardGroupLayout->removeWidget(indexToWidgetMap.value(idx)); + indexToWidgetMap.value(idx)->deleteLater(); + indexToWidgetMap.remove(idx); + } + } + if (!trackedIndex.isValid()) { + emit requestCleanup(this); + } +} + void DeckCardZoneDisplayWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); @@ -55,12 +142,6 @@ void DeckCardZoneDisplayWidget::onHover(CardInfoPtr card) emit cardHovered(card); } -void DeckCardZoneDisplayWidget::displayCards() -{ - addCardGroupIfItDoesNotExist(); - deleteCardGroupIfItDoesNotExist(); -} - void DeckCardZoneDisplayWidget::refreshDisplayType(const DisplayType &_displayType) { displayType = _displayType; @@ -74,6 +155,8 @@ void DeckCardZoneDisplayWidget::refreshDisplayType(const DisplayType &_displayTy delete item; } + indexToWidgetMap.clear(); + // We gotta wait for all the deleteLater's to finish so we fire after the next event cycle auto timer = new QTimer(this); @@ -82,66 +165,6 @@ void DeckCardZoneDisplayWidget::refreshDisplayType(const DisplayType &_displayTy timer->start(); } -void DeckCardZoneDisplayWidget::addCardGroupIfItDoesNotExist() -{ - QList cardGroupsDisplayWidgets = - cardGroupContainer->findChildren(); - - QList cardGroups = getGroupCriteriaValueList(); - - for (QString cardGroup : cardGroups) { - bool found = false; - for (CardGroupDisplayWidget *cardGroupDisplayWidget : cardGroupsDisplayWidgets) { - if (cardGroupDisplayWidget->cardGroupCategory == cardGroup) { - found = true; - cardGroupDisplayWidget->updateCardDisplays(); - break; - } - } - - if (found) { - continue; - } - - if (displayType == DisplayType::Overlap) { - auto *display_widget = new OverlappedCardGroupDisplayWidget( - cardGroupContainer, deckListModel, zoneName, cardGroup, activeGroupCriteria, activeSortCriteria, - subBannerOpacity, cardSizeWidget); - connect(display_widget, SIGNAL(cardClicked(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *)), this, - SLOT(onClick(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *))); - connect(display_widget, SIGNAL(cardHovered(CardInfoPtr)), this, SLOT(onHover(CardInfoPtr))); - connect(this, &DeckCardZoneDisplayWidget::activeSortCriteriaChanged, display_widget, - &CardGroupDisplayWidget::onActiveSortCriteriaChanged); - cardGroupLayout->addWidget(display_widget); - } else if (displayType == DisplayType::Flat) { - auto *display_widget = new FlatCardGroupDisplayWidget(cardGroupContainer, deckListModel, zoneName, - cardGroup, activeGroupCriteria, activeSortCriteria, - subBannerOpacity, cardSizeWidget); - connect(display_widget, SIGNAL(cardClicked(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *)), this, - SLOT(onClick(QMouseEvent *, CardInfoPictureWithTextOverlayWidget *))); - connect(display_widget, SIGNAL(cardHovered(CardInfoPtr)), this, SLOT(onHover(CardInfoPtr))); - connect(this, &DeckCardZoneDisplayWidget::activeSortCriteriaChanged, display_widget, - &CardGroupDisplayWidget::onActiveSortCriteriaChanged); - cardGroupLayout->addWidget(display_widget); - } - } -} - -void DeckCardZoneDisplayWidget::deleteCardGroupIfItDoesNotExist() -{ - QList cardGroupsDisplayWidgets = - cardGroupContainer->findChildren(); - - QList validGroups = getGroupCriteriaValueList(); - - for (CardGroupDisplayWidget *cardGroupDisplayWidget : cardGroupsDisplayWidgets) { - if (!validGroups.contains(cardGroupDisplayWidget->cardGroupCategory)) { - cardGroupLayout->removeWidget(cardGroupDisplayWidget); - cardGroupDisplayWidget->deleteLater(); // Properly delete the widget after the event loop cycles - } - } -} - void DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged(QString _activeGroupCriteria) { activeGroupCriteria = _activeGroupCriteria; diff --git a/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.h b/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.h index 861896abf..8a21ec3d3 100644 --- a/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.h +++ b/cockatrice/src/client/ui/widgets/cards/deck_card_zone_display_widget.h @@ -6,6 +6,7 @@ #include "../general/display/banner_widget.h" #include "../general/layout_containers/overlap_widget.h" #include "../visual_deck_editor/visual_deck_editor_widget.h" +#include "card_group_display_widgets/card_group_display_widget.h" #include "card_info_picture_with_text_overlay_widget.h" #include "card_size_widget.h" @@ -19,6 +20,7 @@ class DeckCardZoneDisplayWidget : public QWidget public: DeckCardZoneDisplayWidget(QWidget *parent, DeckListModel *deckListModel, + QPersistentModelIndex trackedIndex, QString zoneName, QString activeGroupCriteria, QStringList activeSortCriteria, @@ -27,6 +29,7 @@ public: int subBannerOpacity, CardSizeWidget *_cardSizeWidget); DeckListModel *deckListModel; + QPersistentModelIndex trackedIndex; QString zoneName; void addCardsToOverlapWidget(); void resizeEvent(QResizeEvent *event) override; @@ -34,18 +37,21 @@ public: public slots: void onClick(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card); void onHover(CardInfoPtr card); + void cleanupInvalidCardGroup(CardGroupDisplayWidget *displayWidget); + void constructAppropriateWidget(QPersistentModelIndex index); void displayCards(); void refreshDisplayType(const DisplayType &displayType); - void addCardGroupIfItDoesNotExist(); - void deleteCardGroupIfItDoesNotExist(); void onActiveGroupCriteriaChanged(QString activeGroupCriteria); void onActiveSortCriteriaChanged(QStringList activeSortCriteria); QList getGroupCriteriaValueList(); + void onCategoryAddition(const QModelIndex &parent, int first, int last); + void onCategoryRemoval(const QModelIndex &parent, int first, int last); signals: void cardClicked(QMouseEvent *event, CardInfoPictureWithTextOverlayWidget *card, QString zoneName); void cardHovered(CardInfoPtr card); void activeSortCriteriaChanged(QStringList activeSortCriteria); + void requestCleanup(DeckCardZoneDisplayWidget *displayWidget); private: QString activeGroupCriteria; @@ -59,6 +65,7 @@ private: QWidget *cardGroupContainer; QVBoxLayout *cardGroupLayout; OverlapWidget *overlapWidget; + QHash indexToWidgetMap; }; #endif // DECK_CARD_ZONE_DISPLAY_WIDGET_H diff --git a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp index 0cbeb2b50..03ad65e4f 100644 --- a/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp +++ b/cockatrice/src/client/ui/widgets/deck_editor/deck_editor_deck_dock_widget.cpp @@ -245,6 +245,7 @@ void DeckEditorDeckDockWidget::updateCard(const QModelIndex /*¤t*/, const void DeckEditorDeckDockWidget::updateName(const QString &name) { deckModel->getDeckList()->setName(name); + deckEditor->setModified(name.isEmpty()); emit nameChanged(); emit deckModified(); } @@ -252,6 +253,7 @@ void DeckEditorDeckDockWidget::updateName(const QString &name) void DeckEditorDeckDockWidget::updateComments() { deckModel->getDeckList()->setComments(commentsEdit->toPlainText()); + deckEditor->setModified(commentsEdit->toPlainText().isEmpty()); emit commentsChanged(); emit deckModified(); } @@ -333,6 +335,7 @@ void DeckEditorDeckDockWidget::setBannerCard(int /* changedIndex */) { auto cardAndId = bannerCardComboBox->currentData().value>(); deckModel->getDeckList()->setBannerCard(cardAndId); + deckEditor->setModified(true); emit deckModified(); } diff --git a/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp b/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp index 73b86a573..75ab56b34 100644 --- a/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp +++ b/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.cpp @@ -88,6 +88,12 @@ void FlowWidget::addWidget(QWidget *widget_to_add) const flowLayout->addWidget(widget_to_add); } +void FlowWidget::insertWidgetAtIndex(QWidget *toInsert, int index) +{ + flowLayout->insertWidgetAtIndex(toInsert, index); + update(); +} + void FlowWidget::removeWidget(QWidget *widgetToRemove) const { flowLayout->removeWidget(widgetToRemove); diff --git a/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.h b/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.h index 75ccbf345..246b5a274 100644 --- a/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.h +++ b/cockatrice/src/client/ui/widgets/general/layout_containers/flow_widget.h @@ -20,6 +20,7 @@ public: Qt::ScrollBarPolicy horizontalPolicy, Qt::ScrollBarPolicy verticalPolicy); void addWidget(QWidget *widget_to_add) const; + void insertWidgetAtIndex(QWidget *toInsert, int index); void removeWidget(QWidget *widgetToRemove) const; void clearLayout(); [[nodiscard]] int count() const; diff --git a/cockatrice/src/client/ui/widgets/general/layout_containers/overlap_widget.cpp b/cockatrice/src/client/ui/widgets/general/layout_containers/overlap_widget.cpp index 33f369ef6..8aa90dfc9 100644 --- a/cockatrice/src/client/ui/widgets/general/layout_containers/overlap_widget.cpp +++ b/cockatrice/src/client/ui/widgets/general/layout_containers/overlap_widget.cpp @@ -59,6 +59,12 @@ void OverlapWidget::addWidget(QWidget *widgetToAdd) const overlapLayout->addWidget(widgetToAdd); } +void OverlapWidget::insertWidgetAtIndex(QWidget *toInsert, int index) +{ + overlapLayout->insertWidgetAtIndex(toInsert, index); + update(); +} + void OverlapWidget::removeWidget(QWidget *widgetToRemove) const { overlapLayout->removeWidget(widgetToRemove); diff --git a/cockatrice/src/client/ui/widgets/general/layout_containers/overlap_widget.h b/cockatrice/src/client/ui/widgets/general/layout_containers/overlap_widget.h index 910362ebd..2d2a94132 100644 --- a/cockatrice/src/client/ui/widgets/general/layout_containers/overlap_widget.h +++ b/cockatrice/src/client/ui/widgets/general/layout_containers/overlap_widget.h @@ -17,6 +17,7 @@ public: Qt::Orientation direction, bool adjustOnResize = false); void addWidget(QWidget *widgetToAdd) const; + void insertWidgetAtIndex(QWidget *toInsert, int index); void removeWidget(QWidget *widgetToRemove) const; void clearLayout(); void adjustMaxColumnsAndRows(); 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 f659fac39..d58baacad 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 @@ -26,8 +26,6 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, DeckListModel *_deckListModel) : QWidget(parent), deckListModel(_deckListModel) { - connect(deckListModel, &DeckListModel::dataChanged, this, &VisualDeckEditorWidget::decklistDataChanged); - // The Main Widget and Main Layout, which contain a single Widget: The Scroll Area setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); mainLayout = new QVBoxLayout(this); @@ -175,14 +173,17 @@ VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, DeckListModel *_ scrollArea->addScrollBarWidget(zoneContainer, Qt::AlignHCenter); scrollArea->setWidget(zoneContainer); - updateZoneWidgets(); - cardSizeWidget = new CardSizeWidget(this); mainLayout->addWidget(groupAndSortContainer); mainLayout->addWidget(scrollArea); mainLayout->addWidget(cardSizeWidget); + connect(deckListModel, &DeckListModel::dataChanged, this, &VisualDeckEditorWidget::decklistDataChanged); + connect(deckListModel, &QAbstractItemModel::rowsInserted, this, &VisualDeckEditorWidget::onCardAddition); + connect(deckListModel, &QAbstractItemModel::rowsRemoved, this, &VisualDeckEditorWidget::onCardRemoval); + constructZoneWidgetsFromDeckListModel(); + retranslateUi(); } @@ -198,10 +199,95 @@ void VisualDeckEditorWidget::retranslateUi() tr("Change how cards are displayed within zones (i.e. overlapped or fully visible.)")); } +void VisualDeckEditorWidget::cleanupInvalidZones(DeckCardZoneDisplayWidget *displayWidget) +{ + zoneContainerLayout->removeWidget(displayWidget); + for (auto idx : indexToWidgetMap.keys()) { + if (!idx.isValid()) { + indexToWidgetMap.remove(idx); + } + } + delete displayWidget; +} + +void VisualDeckEditorWidget::onCardAddition(const QModelIndex &parent, int first, int last) +{ + if (parent == deckListModel->getRoot()) { + for (int i = first; i <= last; i++) { + QPersistentModelIndex index = QPersistentModelIndex(deckListModel->index(i, 0, deckListModel->getRoot())); + + if (indexToWidgetMap.contains(index)) { + continue; + } + + DeckCardZoneDisplayWidget *zoneDisplayWidget = new DeckCardZoneDisplayWidget( + zoneContainer, deckListModel, index, + deckListModel->data(index.sibling(index.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); + connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::requestCleanup, this, + &VisualDeckEditorWidget::cleanupInvalidZones); + connect(this, &VisualDeckEditorWidget::activeSortCriteriaChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged); + connect(this, &VisualDeckEditorWidget::activeGroupCriteriaChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged); + connect(this, &VisualDeckEditorWidget::displayTypeChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::refreshDisplayType); + zoneDisplayWidget->refreshDisplayType(currentDisplayType); + zoneContainerLayout->addWidget(zoneDisplayWidget); + + indexToWidgetMap.insert(index, zoneDisplayWidget); + } + } +} + +void VisualDeckEditorWidget::onCardRemoval(const QModelIndex &parent, int first, int last) +{ + Q_UNUSED(parent); + Q_UNUSED(first); + Q_UNUSED(last); + for (const QPersistentModelIndex &idx : indexToWidgetMap.keys()) { + if (!idx.isValid()) { + zoneContainerLayout->removeWidget(indexToWidgetMap.value(idx)); + indexToWidgetMap.value(idx)->deleteLater(); + indexToWidgetMap.remove(idx); + } + } +} + +void VisualDeckEditorWidget::constructZoneWidgetsFromDeckListModel() +{ + for (int i = 0; i < deckListModel->rowCount(deckListModel->parent(QModelIndex())); i++) { + QPersistentModelIndex index = QPersistentModelIndex(deckListModel->index(i, 0, deckListModel->getRoot())); + + if (indexToWidgetMap.contains(index)) { + continue; + } + + DeckCardZoneDisplayWidget *zoneDisplayWidget = new DeckCardZoneDisplayWidget( + zoneContainer, deckListModel, index, + deckListModel->data(index.sibling(index.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); + connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::requestCleanup, this, + &VisualDeckEditorWidget::cleanupInvalidZones); + connect(this, &VisualDeckEditorWidget::activeSortCriteriaChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged); + connect(this, &VisualDeckEditorWidget::activeGroupCriteriaChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged); + connect(this, &VisualDeckEditorWidget::displayTypeChanged, zoneDisplayWidget, + &DeckCardZoneDisplayWidget::refreshDisplayType); + zoneContainerLayout->addWidget(zoneDisplayWidget); + + indexToWidgetMap.insert(index, zoneDisplayWidget); + } +} + void VisualDeckEditorWidget::updateZoneWidgets() { - addZoneIfDoesNotExist(); - deleteZoneIfDoesNotExist(); } void VisualDeckEditorWidget::updateDisplayType() @@ -221,57 +307,6 @@ void VisualDeckEditorWidget::updateDisplayType() emit displayTypeChanged(currentDisplayType); } -void VisualDeckEditorWidget::addZoneIfDoesNotExist() -{ - QList cardZoneDisplayWidgets = - zoneContainer->findChildren(); - for (const QString &zone : *deckListModel->getZones()) { - bool found = false; - for (DeckCardZoneDisplayWidget *displayWidget : cardZoneDisplayWidgets) { - if (displayWidget->zoneName == zone) { - found = true; - displayWidget->displayCards(); - break; - } - } - - if (found) { - continue; - } - DeckCardZoneDisplayWidget *zoneDisplayWidget = - new DeckCardZoneDisplayWidget(zoneContainer, deckListModel, zone, activeGroupCriteria, activeSortCriteria, - currentDisplayType, 20, 10, cardSizeWidget); - connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardHovered, this, &VisualDeckEditorWidget::onHover); - connect(zoneDisplayWidget, &DeckCardZoneDisplayWidget::cardClicked, this, &VisualDeckEditorWidget::onCardClick); - connect(this, &VisualDeckEditorWidget::activeSortCriteriaChanged, zoneDisplayWidget, - &DeckCardZoneDisplayWidget::onActiveSortCriteriaChanged); - connect(this, &VisualDeckEditorWidget::activeGroupCriteriaChanged, zoneDisplayWidget, - &DeckCardZoneDisplayWidget::onActiveGroupCriteriaChanged); - connect(this, &VisualDeckEditorWidget::displayTypeChanged, zoneDisplayWidget, - &DeckCardZoneDisplayWidget::refreshDisplayType); - zoneContainerLayout->addWidget(zoneDisplayWidget); - } -} - -void VisualDeckEditorWidget::deleteZoneIfDoesNotExist() -{ - QList cardZoneDisplayWidgets = - zoneContainer->findChildren(); - for (DeckCardZoneDisplayWidget *displayWidget : cardZoneDisplayWidgets) { - bool found = false; - for (const QString &zone : *deckListModel->getZones()) { - if (displayWidget->zoneName == zone) { - found = true; - break; - } - } - - if (!found) { - zoneContainerLayout->removeWidget(displayWidget); - } - } -} - void VisualDeckEditorWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); diff --git a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.h b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.h index 41f8bbbbe..6f43d624c 100644 --- a/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.h +++ b/cockatrice/src/client/ui/widgets/visual_deck_editor/visual_deck_editor_widget.h @@ -17,6 +17,7 @@ #include #include +class DeckCardZoneDisplayWidget; enum class DisplayType { Flat, @@ -41,8 +42,10 @@ public slots: void decklistDataChanged(QModelIndex topLeft, QModelIndex bottomRight); void updateZoneWidgets(); void updateDisplayType(); - void addZoneIfDoesNotExist(); - void deleteZoneIfDoesNotExist(); + void cleanupInvalidZones(DeckCardZoneDisplayWidget *displayWidget); + void onCardAddition(const QModelIndex &parent, int first, int last); + void onCardRemoval(const QModelIndex &parent, int first, int last); + void constructZoneWidgetsFromDeckListModel(); signals: void activeCardChanged(CardInfoPtr activeCard); @@ -83,6 +86,7 @@ private: QVBoxLayout *zoneContainerLayout; // OverlapControlWidget *overlapControlWidget; QWidget *container; + QHash indexToWidgetMap; }; #endif // VISUAL_DECK_EDITOR_H diff --git a/cockatrice/src/deck/deck_list_model.cpp b/cockatrice/src/deck/deck_list_model.cpp index 746a1d9e9..52f0e316c 100644 --- a/cockatrice/src/deck/deck_list_model.cpp +++ b/cockatrice/src/deck/deck_list_model.cpp @@ -312,7 +312,7 @@ bool DeckListModel::removeRows(int row, int count, const QModelIndex &parent) } else { emitRecursiveUpdates(parent); } - emit dataChanged(parent, parent); + return true; } @@ -408,10 +408,14 @@ QModelIndex DeckListModel::addCard(const QString &cardName, const auto cardSetName = cardInfoSet.getPtr().isNull() ? "" : cardInfoSet.getPtr()->getCorrectedShortName(); if (!cardNode) { - auto *decklistCard = deckList->addCard(cardInfo->getName(), zoneName, cardSetName, + // Determine the correct index + int insertRow = findSortedInsertRow(cardTypeNode, cardInfo); + + auto *decklistCard = deckList->addCard(cardInfo->getName(), zoneName, insertRow, cardSetName, cardInfoSet.getProperty("num"), cardInfoSet.getProperty("uuid")); - beginInsertRows(parentIndex, static_cast(cardTypeNode->size()), static_cast(cardTypeNode->size())); - cardNode = new DecklistModelCardNode(decklistCard, cardTypeNode); + + beginInsertRows(parentIndex, insertRow, insertRow); + cardNode = new DecklistModelCardNode(decklistCard, cardTypeNode, insertRow); endInsertRows(); } else { cardNode->setNumber(cardNode->getNumber() + 1); @@ -420,11 +424,44 @@ QModelIndex DeckListModel::addCard(const QString &cardName, cardNode->setCardProviderId(cardInfoSet.getProperty("uuid")); deckList->refreshDeckHash(); } - sort(lastKnownColumn, lastKnownOrder); + // sort(lastKnownColumn, lastKnownOrder); emitRecursiveUpdates(parentIndex); return nodeToIndex(cardNode); } +int DeckListModel::findSortedInsertRow(InnerDecklistNode *parent, CardInfoPtr cardInfo) const +{ + if (!cardInfo) { + return parent->size(); // fallback: append at end + } + + for (int i = 0; i < parent->size(); ++i) { + auto *existingCard = dynamic_cast(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) { diff --git a/cockatrice/src/deck/deck_list_model.h b/cockatrice/src/deck/deck_list_model.h index 81ae06508..370fc2378 100644 --- a/cockatrice/src/deck/deck_list_model.h +++ b/cockatrice/src/deck/deck_list_model.h @@ -25,8 +25,8 @@ private: DecklistCardNode *dataNode; public: - DecklistModelCardNode(DecklistCardNode *_dataNode, InnerDecklistNode *_parent) - : AbstractDecklistCardNode(_parent), dataNode(_dataNode) + DecklistModelCardNode(DecklistCardNode *_dataNode, InnerDecklistNode *_parent, int position = -1) + : AbstractDecklistCardNode(_parent, position), dataNode(_dataNode) { } int getNumber() const override @@ -92,6 +92,10 @@ signals: public: explicit DeckListModel(QObject *parent = nullptr); ~DeckListModel() override; + QModelIndex getRoot() const + { + return nodeToIndex(root); + }; QString getSortCriteriaForCard(CardInfoPtr info); int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override; @@ -111,6 +115,7 @@ public: const CardInfoPerSet &cardInfoSet, const QString &zoneName, bool abAddAnyway = false); + int findSortedInsertRow(InnerDecklistNode *parent, CardInfoPtr cardInfo) const; void sort(int column, Qt::SortOrder order) override; void cleanList(); DeckLoader *getDeckList() const diff --git a/cockatrice/src/deck/deck_stats_interface.cpp b/cockatrice/src/deck/deck_stats_interface.cpp index 7cfd69d40..19b8f4447 100644 --- a/cockatrice/src/deck/deck_stats_interface.cpp +++ b/cockatrice/src/deck/deck_stats_interface.cpp @@ -72,7 +72,7 @@ void DeckStatsInterface::copyDeckWithoutTokens(DeckList &source, DeckList &desti auto copyIfNotAToken = [this, &destination](const auto node, const auto card) { CardInfoPtr dbCard = cardDatabase.getCard(card->getName()); if (dbCard && !dbCard->getIsToken()) { - DecklistCardNode *addedCard = destination.addCard(card->getName(), node->getName()); + DecklistCardNode *addedCard = destination.addCard(card->getName(), node->getName(), -1); addedCard->setNumber(card->getNumber()); } }; diff --git a/common/decklist.cpp b/common/decklist.cpp index a7da934f4..e72e343b5 100644 --- a/common/decklist.cpp +++ b/common/decklist.cpp @@ -71,10 +71,15 @@ void SideboardPlan::write(QXmlStreamWriter *xml) xml->writeEndElement(); } -AbstractDecklistNode::AbstractDecklistNode(InnerDecklistNode *_parent) : parent(_parent), sortMethod(Default) +AbstractDecklistNode::AbstractDecklistNode(InnerDecklistNode *_parent, int position) + : parent(_parent), sortMethod(Default) { if (parent) { - parent->append(this); + if (position == -1) { + parent->append(this); + } else { + parent->insert(position, this); + } } } @@ -274,7 +279,7 @@ bool InnerDecklistNode::readElement(QXmlStreamReader *xml) } else if (childName == "card") { DecklistCardNode *newCard = new DecklistCardNode( xml->attributes().value("name").toString(), xml->attributes().value("number").toString().toInt(), - this, xml->attributes().value("setShortName").toString(), + this, -1, xml->attributes().value("setShortName").toString(), xml->attributes().value("collectorNumber").toString(), xml->attributes().value("uuid").toString()); newCard->readElement(xml); } @@ -725,7 +730,7 @@ bool DeckList::loadFromStream_Plain(QTextStream &in, bool preserveMetadata) QString zoneName = getCardZoneFromName(cardName, sideboard ? DECK_ZONE_SIDE : DECK_ZONE_MAIN); // make new entry in decklist - new DecklistCardNode(cardName, amount, getZoneObjFromName(zoneName), setCode, collectorNumber); + new DecklistCardNode(cardName, amount, getZoneObjFromName(zoneName), -1, setCode, collectorNumber); } refreshDeckHash(); @@ -856,6 +861,7 @@ int DeckList::getSideboardSize() const DecklistCardNode *DeckList::addCard(const QString &cardName, const QString &zoneName, + const int position, const QString &cardSetName, const QString &cardSetCollectorNumber, const QString &cardProviderId) @@ -865,7 +871,8 @@ DecklistCardNode *DeckList::addCard(const QString &cardName, zoneNode = new InnerDecklistNode(zoneName, root); } - auto *node = new DecklistCardNode(cardName, 1, zoneNode, cardSetName, cardSetCollectorNumber, cardProviderId); + auto *node = + new DecklistCardNode(cardName, 1, zoneNode, position, cardSetName, cardSetCollectorNumber, cardProviderId); refreshDeckHash(); return node; diff --git a/common/decklist.h b/common/decklist.h index a2d58de47..1db3b3991 100644 --- a/common/decklist.h +++ b/common/decklist.h @@ -56,7 +56,7 @@ protected: DeckSortMethod sortMethod; public: - explicit AbstractDecklistNode(InnerDecklistNode *_parent = nullptr); + explicit AbstractDecklistNode(InnerDecklistNode *_parent = nullptr, int position = -1); virtual ~AbstractDecklistNode() = default; virtual void setSortMethod(DeckSortMethod method) { @@ -88,8 +88,8 @@ class InnerDecklistNode : public AbstractDecklistNode, public QList