Cockatrice/cockatrice/src/interface/deck_loader/deck_loader.h
BruebachL 0d09e633e3
[Client] Expose custom zone management in the deck editor (#7205)
* [Client] Expose custom zone management in the deck editor

Wires the state layer into every editor surface that shows deck zones.

- Deck dock: context menu on zones gains New/Rename/Delete/Change
  board actions, with per-zone submenus for adding cards.
- Card database dock and visual database display gain an add-to-zone
  submenu listing custom zones per board plus a create-zone entry.
- All prompt call sites pass validateNewZoneName so duplicates and
  reserved names are rejected inline before Ok unlocks.
- Rename reuses the same dialog in name-only mode, keeping one
  validation contract for every zone-name entry point.
- Change board marks the current board instead of offering a no-op,
  and the state layer refuses moves onto boards holding a same-named
  zone from imported decks.

* [DeckEditor] Address custom-zone menu and export review feedback

* [DeckLoader] Keep the sideboard marker and block ordering when exporting nested zones

- saveToStream_DeckZone threads the owning board zone name down to the card
  writer, so cards in a custom zone under the sideboard keep their SB:
  prefix instead of being re-imported into the maindeck
- nested sub-zones are collected during the loop and written after the
  parent zone's own header and cards, so they no longer read as part of the
  zone printed before them

* [DeckEditor] Fix move-to-zone menu use-after-free and per-zone enabled state

- resolve the card name/provider/collector number before createNewCustomZone
  rebuilds the model tree, then re-find the refreshed index via findCard and
  move it (mirrors the decrementCard re-find pattern)
- the enabled test now compares the card's own zone (nearest custom-zone
  ancestor, else its board), matching moveCardToZone's lookup, so moving a
  card out of a custom zone back to the board root is offered and the card's
  own zone is disabled

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-09-05 22:00:21 +02:00

171 lines
6 KiB
C++

/**
* @file deck_loader.h
* @ingroup ImportExport
*/
//! \todo Document this file.
#ifndef DECK_LOADER_H
#define DECK_LOADER_H
#include "loaded_deck.h"
#include <QLoggingCategory>
#include <QPrinter>
#include <QTextCursor>
#include <libcockatrice/deck_list/deck_list.h>
#include <optional>
inline Q_LOGGING_CATEGORY(DeckLoaderLog, "deck_loader");
class DeckLoader : public QObject
{
Q_OBJECT
signals:
void loadFinished(bool success);
public:
/**
* Supported file extensions for decklist files
*/
static const QStringList ACCEPTED_FILE_EXTENSIONS;
/**
* For use with `QFileDialog::setNameFilters`
*/
static const QStringList FILE_NAME_FILTERS;
enum DecklistWebsite
{
DecklistOrg,
DecklistXyz
};
private:
LoadedDeck loadedDeck;
public:
DeckLoader(QObject *parent);
DeckLoader(const DeckLoader &) = delete;
DeckLoader &operator=(const DeckLoader &) = delete;
[[nodiscard]] bool hasNotBeenLoaded() const
{
return loadedDeck.lastLoadInfo.isEmpty();
}
/**
* @brief Asynchronously loads a deck from a local file into this DeckLoader.
* The `loadFinished` signal will be emitted when the load finishes.
* Once the loading finishes, the deck can be accessed with `getDeck`
* @param fileName The file to load
* @param fmt The format of the file to load
* @param userRequest Whether the load was manually requested by the user, instead of being done in the background.
*/
void loadFromFileAsync(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest);
/**
* @brief Loads the file that the lastLoadInfo currently points to into this instance.
* No-ops if the lastLoadInfo is missing the required info or the load fails.
* @return Whether the loaded succeeded.
*/
bool reload();
/**
* @brief Loads a deck from a local file.
* @param fileName The file to load
* @param fmt The format of the file to load
* @param userRequest Whether the load was manually requested by the user, instead of being done in the background.
* @return An optional containing the LoadedDeck, or empty if the load failed.
*/
static std::optional<LoadedDeck>
loadFromFile(const QString &fileName, DeckFileFormat::Format fmt, bool userRequest = false);
/**
* @brief Loads a deck from the response of a remote deck request
* @param nativeString The deck string, in cod format
* @param remoteDeckId The remote deck id
* @return An optional containing the LoadedDeck, or empty if the load failed.
*/
static std::optional<LoadedDeck> loadFromRemote(const QString &nativeString, int remoteDeckId);
/**
* @brief Saves a DeckList to a local file.
* @param deck The DeckList
* @param fileName The file to write to
* @param fmt The deck file format to use
* @return An optional containing the LoadInfo for the new file, or empty if the save failed.
*/
static std::optional<LoadedDeck::LoadInfo>
saveToFile(const DeckList &deck, const QString &fileName, DeckFileFormat::Format fmt);
/**
* @brief Saves a LoadedDeck to a local file.
* Uses the lastLoadInfo in the LoadedDeck to determine where to save to.
* @param deck The LoadedDeck to save. Should have valid lastLoadInfo.
* @return Whether the save succeeded.
*/
static bool saveToFile(const LoadedDeck &deck);
/**
* @brief Saves a LoadedDeck to a new local file.
* @param deck The LoadedDeck to save. Will update the lastLoadInfo.
* @param fileName The file to write to
* @param fmt The deck file format to use
* @return Whether the save succeeded.
*/
static bool saveToNewFile(LoadedDeck &deck, const QString &fileName, DeckFileFormat::Format fmt);
static QString exportDeckToDecklist(const DeckList &deckList, DecklistWebsite website);
static void saveToClipboard(const DeckList &deckList, bool addComments = true, bool addSetNameAndNumber = true);
static bool saveToStream_Plain(QTextStream &out,
const DeckList &deckList,
bool addComments = true,
bool addSetNameAndNumber = true);
/**
* @brief Prints the decklist to the provided QPrinter.
* @param printer The printer to render the decklist to.
* @param deckList
*/
static void printDeckList(QPrinter *printer, const DeckList &deckList);
/**
* Converts the given deck's file to the cockatrice file format.
* Uses the lastLoadInfo in the LoadedDeck to determine the current name of the file and where to save to.
* @param deck The deck to convert. Should have valid lastLoadInfo. Will update the lastLoadInfo.
* @return Whether the conversion succeeded.
*/
static bool convertToCockatriceFormat(LoadedDeck &deck);
LoadedDeck &getDeck()
{
return loadedDeck;
}
const LoadedDeck &getDeck() const
{
return loadedDeck;
}
void setDeck(const LoadedDeck &deck)
{
loadedDeck = deck;
}
private:
static bool updateLastLoadedTimestamp(LoadedDeck &deck);
static void printDeckListNode(QTextCursor *cursor, const InnerDecklistNode *node);
static void saveToStream_DeckHeader(QTextStream &out, const DeckList &deckList);
static void saveToStream_DeckZone(QTextStream &out,
const InnerDecklistNode *zoneNode,
bool addComments = true,
bool addSetNameAndNumber = true,
const QString &boardZoneName = QString());
static void saveToStream_DeckZoneCards(QTextStream &out,
QList<DecklistCardNode *> cards,
bool addComments = true,
bool addSetNameAndNumber = true,
const QString &boardZoneName = QString());
};
#endif