mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2025-12-05 20:39:59 -08:00
* Make forEachCard const * Make some DeckLoader methods static * Update usages * Update method param documentation in deck_loader.cpp --------- Co-authored-by: BruebachL <44814898+BruebachL@users.noreply.github.com>
719 lines
23 KiB
C++
719 lines
23 KiB
C++
#include "deck_loader.h"
|
|
|
|
#include <QApplication>
|
|
#include <QClipboard>
|
|
#include <QDebug>
|
|
#include <QDir>
|
|
#include <QFile>
|
|
#include <QFileInfo>
|
|
#include <QFutureWatcher>
|
|
#include <QPrinter>
|
|
#include <QRegularExpression>
|
|
#include <QStringList>
|
|
#include <QTextCursor>
|
|
#include <QTextDocument>
|
|
#include <QTextStream>
|
|
#include <QTextTable>
|
|
#include <QtConcurrentRun>
|
|
#include <libcockatrice/card/database/card_database.h>
|
|
#include <libcockatrice/card/database/card_database_manager.h>
|
|
#include <libcockatrice/deck_list/deck_list.h>
|
|
#include <libcockatrice/deck_list/deck_list_card_node.h>
|
|
|
|
const QStringList DeckLoader::ACCEPTED_FILE_EXTENSIONS = {"*.cod", "*.dec", "*.dek", "*.txt", "*.mwDeck"};
|
|
|
|
const QStringList DeckLoader::FILE_NAME_FILTERS = {
|
|
tr("Common deck formats (%1)").arg(ACCEPTED_FILE_EXTENSIONS.join(" ")), tr("All files (*.*)")};
|
|
|
|
DeckLoader::DeckLoader(QObject *parent)
|
|
: QObject(parent), deckList(new DeckList()), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1)
|
|
{
|
|
}
|
|
|
|
DeckLoader::DeckLoader(QObject *parent, DeckList *_deckList)
|
|
: QObject(parent), deckList(_deckList), lastFileFormat(CockatriceFormat), lastRemoteDeckId(-1)
|
|
{
|
|
deckList->setParent(this);
|
|
}
|
|
|
|
DeckLoader::DeckLoader(const DeckLoader &other)
|
|
: QObject(), deckList(other.deckList), lastFileName(other.lastFileName), lastFileFormat(other.lastFileFormat),
|
|
lastRemoteDeckId(other.lastRemoteDeckId)
|
|
{
|
|
}
|
|
|
|
void DeckLoader::setDeckList(DeckList *_deckList)
|
|
{
|
|
deckList = _deckList;
|
|
deckList->setParent(this);
|
|
}
|
|
|
|
bool DeckLoader::loadFromFile(const QString &fileName, FileFormat fmt, bool userRequest)
|
|
{
|
|
QFile file(fileName);
|
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
return false;
|
|
}
|
|
|
|
bool result = false;
|
|
switch (fmt) {
|
|
case PlainTextFormat:
|
|
result = deckList->loadFromFile_Plain(&file);
|
|
break;
|
|
case CockatriceFormat: {
|
|
result = deckList->loadFromFile_Native(&file);
|
|
qCInfo(DeckLoaderLog) << "Loaded from" << fileName << "-" << result;
|
|
if (!result) {
|
|
qCInfo(DeckLoaderLog) << "Retrying as plain format";
|
|
file.seek(0);
|
|
result = deckList->loadFromFile_Plain(&file);
|
|
fmt = PlainTextFormat;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (result) {
|
|
lastFileName = fileName;
|
|
lastFileFormat = fmt;
|
|
if (userRequest) {
|
|
updateLastLoadedTimestamp(fileName, fmt);
|
|
}
|
|
|
|
emit deckLoaded();
|
|
}
|
|
|
|
qCInfo(DeckLoaderLog) << "Deck was loaded -" << result;
|
|
return result;
|
|
}
|
|
|
|
bool DeckLoader::loadFromFileAsync(const QString &fileName, FileFormat fmt, bool userRequest)
|
|
{
|
|
auto *watcher = new QFutureWatcher<bool>(this);
|
|
|
|
connect(watcher, &QFutureWatcher<bool>::finished, this, [this, watcher, fileName, fmt, userRequest]() {
|
|
const bool result = watcher->result();
|
|
watcher->deleteLater();
|
|
|
|
if (result) {
|
|
lastFileName = fileName;
|
|
lastFileFormat = fmt;
|
|
if (userRequest) {
|
|
updateLastLoadedTimestamp(fileName, fmt);
|
|
}
|
|
emit deckLoaded();
|
|
}
|
|
|
|
emit loadFinished(result);
|
|
});
|
|
|
|
QFuture<bool> future = QtConcurrent::run([=, this]() {
|
|
QFile file(fileName);
|
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
return false;
|
|
}
|
|
|
|
switch (fmt) {
|
|
case PlainTextFormat:
|
|
return deckList->loadFromFile_Plain(&file);
|
|
case CockatriceFormat: {
|
|
bool result = false;
|
|
result = deckList->loadFromFile_Native(&file);
|
|
if (!result) {
|
|
file.seek(0);
|
|
return deckList->loadFromFile_Plain(&file);
|
|
}
|
|
return result;
|
|
}
|
|
default:
|
|
return false;
|
|
break;
|
|
}
|
|
});
|
|
|
|
watcher->setFuture(future);
|
|
return true; // Return immediately to indicate the async task was started
|
|
}
|
|
|
|
bool DeckLoader::loadFromRemote(const QString &nativeString, int remoteDeckId)
|
|
{
|
|
bool result = deckList->loadFromString_Native(nativeString);
|
|
if (result) {
|
|
lastFileName = QString();
|
|
lastFileFormat = CockatriceFormat;
|
|
lastRemoteDeckId = remoteDeckId;
|
|
|
|
emit deckLoaded();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool DeckLoader::saveToFile(const QString &fileName, FileFormat fmt)
|
|
{
|
|
QFile file(fileName);
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
return false;
|
|
}
|
|
|
|
bool result = false;
|
|
switch (fmt) {
|
|
case PlainTextFormat:
|
|
result = deckList->saveToFile_Plain(&file);
|
|
break;
|
|
case CockatriceFormat:
|
|
result = deckList->saveToFile_Native(&file);
|
|
break;
|
|
}
|
|
|
|
if (result) {
|
|
lastFileName = fileName;
|
|
lastFileFormat = fmt;
|
|
}
|
|
|
|
file.flush();
|
|
file.close();
|
|
|
|
return result;
|
|
}
|
|
|
|
bool DeckLoader::updateLastLoadedTimestamp(const QString &fileName, FileFormat fmt)
|
|
{
|
|
QFileInfo fileInfo(fileName);
|
|
if (!fileInfo.exists()) {
|
|
qCWarning(DeckLoaderLog) << "File does not exist:" << fileName;
|
|
return false;
|
|
}
|
|
|
|
QDateTime originalTimestamp = fileInfo.lastModified();
|
|
|
|
// Open the file for writing
|
|
QFile file(fileName);
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
qCWarning(DeckLoaderLog) << "Failed to open file for writing:" << fileName;
|
|
return false;
|
|
}
|
|
|
|
bool result = false;
|
|
|
|
// Perform file modifications
|
|
switch (fmt) {
|
|
case PlainTextFormat:
|
|
result = deckList->saveToFile_Plain(&file);
|
|
break;
|
|
case CockatriceFormat:
|
|
deckList->setLastLoadedTimestamp(QDateTime::currentDateTime().toString());
|
|
result = deckList->saveToFile_Native(&file);
|
|
break;
|
|
}
|
|
|
|
file.close(); // Close the file to ensure changes are flushed
|
|
|
|
if (result) {
|
|
lastFileName = fileName;
|
|
lastFileFormat = fmt;
|
|
|
|
// Re-open the file and set the original timestamp
|
|
if (!file.open(QIODevice::ReadWrite)) {
|
|
qCWarning(DeckLoaderLog) << "Failed to re-open file to set timestamp:" << fileName;
|
|
return false;
|
|
}
|
|
|
|
if (!file.setFileTime(originalTimestamp, QFileDevice::FileModificationTime)) {
|
|
qCWarning(DeckLoaderLog) << "Failed to set modification time for file:" << fileName;
|
|
file.close();
|
|
return false;
|
|
}
|
|
|
|
file.close();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static QString getDomainForWebsite(DeckLoader::DecklistWebsite website)
|
|
{
|
|
switch (website) {
|
|
case DeckLoader::DecklistOrg:
|
|
return "www.decklist.org";
|
|
case DeckLoader::DecklistXyz:
|
|
return "www.decklist.xyz";
|
|
default:
|
|
qCWarning(DeckLoaderLog) << "Invalid decklist website enum:" << website;
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Converts the card to the String that represents it in the decklist export
|
|
*/
|
|
static QString toDecklistExportString(const DecklistCardNode *card)
|
|
{
|
|
QString cardString;
|
|
// Get the number of cards and add the card name
|
|
cardString += QString::number(card->getNumber());
|
|
// Add a space between card num and name
|
|
cardString += "%20";
|
|
// Add card name
|
|
cardString += card->getName();
|
|
|
|
if (!card->getCardSetShortName().isNull()) {
|
|
cardString += "%20";
|
|
cardString += "(" + card->getCardSetShortName() + ")";
|
|
}
|
|
if (!card->getCardCollectorNumber().isNull()) {
|
|
cardString += "%20";
|
|
cardString += card->getCardCollectorNumber();
|
|
}
|
|
|
|
// Add a return at the end of the card
|
|
cardString += "%0A";
|
|
|
|
return cardString;
|
|
}
|
|
|
|
/**
|
|
* Export deck to decklist function, called to format the deck in a way to be sent to a server
|
|
*
|
|
* @param deckList The decklist to export
|
|
* @param website The website we're sending the deck to
|
|
*/
|
|
QString DeckLoader::exportDeckToDecklist(const DeckList *deckList, DecklistWebsite website)
|
|
{
|
|
// Add the base url
|
|
QString deckString = "https://" + getDomainForWebsite(website) + "/?";
|
|
// Create two strings to pass to function
|
|
QString mainBoardCards, sideBoardCards;
|
|
|
|
// Set up the function to call
|
|
auto formatDeckListForExport = [&mainBoardCards, &sideBoardCards](const auto *node, const auto *card) {
|
|
// Get the card name
|
|
CardInfoPtr dbCard = CardDatabaseManager::query()->getCardInfo(card->getName());
|
|
if (!dbCard || dbCard->getIsToken()) {
|
|
// If it's a token, we don't care about the card.
|
|
return;
|
|
}
|
|
|
|
// Check if it's a sideboard card.
|
|
if (node->getName() == DECK_ZONE_SIDE) {
|
|
sideBoardCards += toDecklistExportString(card);
|
|
} else {
|
|
// If it's a mainboard card, do the same thing, but for the mainboard card string
|
|
mainBoardCards += toDecklistExportString(card);
|
|
}
|
|
};
|
|
|
|
// call our struct function for each card in the deck
|
|
deckList->forEachCard(formatDeckListForExport);
|
|
// Remove the extra return at the end of the last cards
|
|
mainBoardCards.chop(3);
|
|
sideBoardCards.chop(3);
|
|
// if after we've called it for each card, and the strings are empty, we know that
|
|
// there were no non-token cards in the deck, so show an error message.
|
|
if ((QString::compare(mainBoardCards, "", Qt::CaseInsensitive) == 0) &&
|
|
(QString::compare(sideBoardCards, "", Qt::CaseInsensitive) == 0)) {
|
|
return "";
|
|
}
|
|
// return a string with the url for decklist export
|
|
deckString += "deckmain=" + mainBoardCards + "&deckside=" + sideBoardCards;
|
|
return deckString;
|
|
}
|
|
|
|
// This struct is here to support the forEachCard function call, defined in decklist.
|
|
// It requires a function to be called for each card, and it will set the providerId to the preferred printing.
|
|
struct SetProviderIdToPreferred
|
|
{
|
|
// Main operator for struct, allowing the foreachcard to work.
|
|
SetProviderIdToPreferred()
|
|
{
|
|
}
|
|
|
|
void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const
|
|
{
|
|
Q_UNUSED(node);
|
|
PrintingInfo preferredPrinting = CardDatabaseManager::query()->getPreferredPrinting(card->getName());
|
|
QString providerId = preferredPrinting.getUuid();
|
|
QString setShortName = preferredPrinting.getSet()->getShortName();
|
|
QString collectorNumber = preferredPrinting.getProperty("num");
|
|
|
|
card->setCardProviderId(providerId);
|
|
card->setCardCollectorNumber(collectorNumber);
|
|
card->setCardSetShortName(setShortName);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* This function iterates through each card in the decklist and sets the providerId
|
|
* on each card based on its set name and collector number.
|
|
*
|
|
* @param deckList The decklist to modify
|
|
*/
|
|
void DeckLoader::setProviderIdToPreferredPrinting(const DeckList *deckList)
|
|
{
|
|
// Set up the struct to call.
|
|
SetProviderIdToPreferred setProviderIdToPreferred;
|
|
|
|
// Call the forEachCard method for each card in the deck
|
|
deckList->forEachCard(setProviderIdToPreferred);
|
|
}
|
|
|
|
/**
|
|
* Sets the providerId on each card in the decklist based on its set name and collector number.
|
|
*
|
|
* @param deckList The decklist to modify
|
|
*/
|
|
void DeckLoader::resolveSetNameAndNumberToProviderID(const DeckList *deckList)
|
|
{
|
|
auto setProviderId = [](const auto node, const auto card) {
|
|
Q_UNUSED(node);
|
|
// Retrieve the providerId based on setName and collectorNumber
|
|
QString providerId =
|
|
CardDatabaseManager::getInstance()
|
|
->query()
|
|
->getSpecificPrinting(card->getName(), card->getCardSetShortName(), card->getCardCollectorNumber())
|
|
.getUuid();
|
|
|
|
// Set the providerId on the card
|
|
card->setCardProviderId(providerId);
|
|
};
|
|
|
|
deckList->forEachCard(setProviderId);
|
|
}
|
|
|
|
// This struct is here to support the forEachCard function call, defined in decklist.
|
|
// It requires a function to be called for each card, and it will set the providerId.
|
|
struct ClearSetNameNumberAndProviderId
|
|
{
|
|
// Main operator for struct, allowing the foreachcard to work.
|
|
ClearSetNameNumberAndProviderId()
|
|
{
|
|
}
|
|
|
|
void operator()(const InnerDecklistNode *node, DecklistCardNode *card) const
|
|
{
|
|
Q_UNUSED(node);
|
|
// Set the providerId on the card
|
|
card->setCardSetShortName(nullptr);
|
|
card->setCardCollectorNumber(nullptr);
|
|
card->setCardProviderId(nullptr);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Clears the set name and numbers on each card in the decklist.
|
|
*
|
|
* @param deckList The decklist to modify
|
|
*/
|
|
void DeckLoader::clearSetNamesAndNumbers(const DeckList *deckList)
|
|
{
|
|
auto clearSetNameAndNumber = [](const auto node, auto card) {
|
|
Q_UNUSED(node)
|
|
// Set the providerId on the card
|
|
card->setCardSetShortName(nullptr);
|
|
card->setCardCollectorNumber(nullptr);
|
|
card->setCardProviderId(nullptr);
|
|
};
|
|
|
|
deckList->forEachCard(clearSetNameAndNumber);
|
|
}
|
|
|
|
DeckLoader::FileFormat DeckLoader::getFormatFromName(const QString &fileName)
|
|
{
|
|
if (fileName.endsWith(".cod", Qt::CaseInsensitive)) {
|
|
return CockatriceFormat;
|
|
}
|
|
return PlainTextFormat;
|
|
}
|
|
|
|
void DeckLoader::saveToClipboard(const DeckList *deckList, bool addComments, bool addSetNameAndNumber)
|
|
{
|
|
QString buffer;
|
|
QTextStream stream(&buffer);
|
|
saveToStream_Plain(stream, deckList, addComments, addSetNameAndNumber);
|
|
QApplication::clipboard()->setText(buffer, QClipboard::Clipboard);
|
|
QApplication::clipboard()->setText(buffer, QClipboard::Selection);
|
|
}
|
|
|
|
bool DeckLoader::saveToStream_Plain(QTextStream &out,
|
|
const DeckList *deckList,
|
|
bool addComments,
|
|
bool addSetNameAndNumber)
|
|
{
|
|
if (addComments) {
|
|
saveToStream_DeckHeader(out, deckList);
|
|
}
|
|
|
|
// loop zones
|
|
for (int i = 0; i < deckList->getRoot()->size(); i++) {
|
|
const auto *zoneNode = dynamic_cast<InnerDecklistNode *>(deckList->getRoot()->at(i));
|
|
|
|
saveToStream_DeckZone(out, zoneNode, addComments, addSetNameAndNumber);
|
|
|
|
// end of zone
|
|
out << "\n";
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void DeckLoader::saveToStream_DeckHeader(QTextStream &out, const DeckList *deckList)
|
|
{
|
|
if (!deckList->getName().isEmpty()) {
|
|
out << "// " << deckList->getName() << "\n\n";
|
|
}
|
|
|
|
if (!deckList->getComments().isEmpty()) {
|
|
QStringList commentRows = deckList->getComments().split(QRegularExpression("\n|\r\n|\r"));
|
|
for (const QString &row : commentRows) {
|
|
out << "// " << row << "\n";
|
|
}
|
|
out << "\n";
|
|
}
|
|
}
|
|
|
|
void DeckLoader::saveToStream_DeckZone(QTextStream &out,
|
|
const InnerDecklistNode *zoneNode,
|
|
bool addComments,
|
|
bool addSetNameAndNumber)
|
|
{
|
|
// group cards by card type and count the subtotals
|
|
QMultiMap<QString, DecklistCardNode *> cardsByType;
|
|
QMap<QString, int> cardTotalByType;
|
|
int cardTotal = 0;
|
|
|
|
for (int j = 0; j < zoneNode->size(); j++) {
|
|
auto *card = dynamic_cast<DecklistCardNode *>(zoneNode->at(j));
|
|
|
|
CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(card->getName());
|
|
QString cardType = info ? info->getMainCardType() : "unknown";
|
|
|
|
cardsByType.insert(cardType, card);
|
|
|
|
if (cardTotalByType.contains(cardType)) {
|
|
cardTotalByType[cardType] += card->getNumber();
|
|
} else {
|
|
cardTotalByType[cardType] = card->getNumber();
|
|
}
|
|
|
|
cardTotal += card->getNumber();
|
|
}
|
|
|
|
if (addComments) {
|
|
out << "// " << cardTotal << " " << zoneNode->getVisibleName() << "\n";
|
|
}
|
|
|
|
// print cards to stream
|
|
for (const QString &cardType : cardsByType.uniqueKeys()) {
|
|
if (addComments) {
|
|
out << "// " << cardTotalByType[cardType] << " " << cardType << "\n";
|
|
}
|
|
|
|
QList<DecklistCardNode *> cards = cardsByType.values(cardType);
|
|
|
|
saveToStream_DeckZoneCards(out, zoneNode, cards, addComments, addSetNameAndNumber);
|
|
|
|
if (addComments) {
|
|
out << "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
void DeckLoader::saveToStream_DeckZoneCards(QTextStream &out,
|
|
const InnerDecklistNode *zoneNode,
|
|
QList<DecklistCardNode *> cards,
|
|
bool addComments,
|
|
bool addSetNameAndNumber)
|
|
{
|
|
// QMultiMap sorts values in reverse order
|
|
for (int i = cards.size() - 1; i >= 0; --i) {
|
|
DecklistCardNode *card = cards[i];
|
|
|
|
if (zoneNode->getName() == DECK_ZONE_SIDE && addComments) {
|
|
out << "SB: ";
|
|
}
|
|
|
|
if (card->getNumber()) {
|
|
out << card->getNumber();
|
|
}
|
|
if (!card->getName().isNull() && !card->getName().isEmpty()) {
|
|
out << " " << card->getName();
|
|
}
|
|
if (addSetNameAndNumber) {
|
|
if (!card->getCardSetShortName().isNull() && !card->getCardSetShortName().isEmpty()) {
|
|
out << " "
|
|
<< "(" << card->getCardSetShortName() << ")";
|
|
}
|
|
if (!card->getCardCollectorNumber().isNull()) {
|
|
out << " " << card->getCardCollectorNumber();
|
|
}
|
|
}
|
|
out << "\n";
|
|
}
|
|
}
|
|
|
|
bool DeckLoader::convertToCockatriceFormat(QString fileName)
|
|
{
|
|
// Change the file extension to .cod
|
|
QFileInfo fileInfo(fileName);
|
|
QString newFileName = QDir::toNativeSeparators(fileInfo.path() + "/" + fileInfo.completeBaseName() + ".cod");
|
|
|
|
// Open the new file for writing
|
|
QFile file(newFileName);
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
qCWarning(DeckLoaderLog) << "Failed to open file for writing:" << newFileName;
|
|
return false;
|
|
}
|
|
|
|
bool result = false;
|
|
|
|
// Perform file modifications based on the detected format
|
|
switch (getFormatFromName(fileName)) {
|
|
case PlainTextFormat:
|
|
// Save in Cockatrice's native format
|
|
result = deckList->saveToFile_Native(&file);
|
|
break;
|
|
case CockatriceFormat:
|
|
qCInfo(DeckLoaderLog) << "File is already in Cockatrice format. No conversion needed.";
|
|
result = true;
|
|
break;
|
|
default:
|
|
qCWarning(DeckLoaderLog) << "Unsupported file format for conversion:" << fileName;
|
|
result = false;
|
|
break;
|
|
}
|
|
|
|
file.close();
|
|
|
|
// Delete the old file if conversion was successful
|
|
if (result) {
|
|
if (!QFile::remove(fileName)) {
|
|
qCWarning(DeckLoaderLog) << "Failed to delete original file:" << fileName;
|
|
} else {
|
|
qCInfo(DeckLoaderLog) << "Original file deleted successfully:" << fileName;
|
|
}
|
|
lastFileName = newFileName;
|
|
lastFileFormat = CockatriceFormat;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
QString DeckLoader::getCardZoneFromName(const QString &cardName, QString currentZoneName)
|
|
{
|
|
CardInfoPtr card = CardDatabaseManager::query()->getCardInfo(cardName);
|
|
|
|
if (card && card->getIsToken()) {
|
|
return DECK_ZONE_TOKENS;
|
|
}
|
|
|
|
return currentZoneName;
|
|
}
|
|
|
|
QString DeckLoader::getCompleteCardName(const QString &cardName)
|
|
{
|
|
if (CardDatabaseManager::getInstance()) {
|
|
ExactCard temp = CardDatabaseManager::query()->guessCard({cardName});
|
|
if (temp) {
|
|
return temp.getName();
|
|
}
|
|
}
|
|
|
|
return cardName;
|
|
}
|
|
|
|
void DeckLoader::printDeckListNode(QTextCursor *cursor, InnerDecklistNode *node)
|
|
{
|
|
const int totalColumns = 2;
|
|
|
|
if (node->height() == 1) {
|
|
QTextBlockFormat blockFormat;
|
|
QTextCharFormat charFormat;
|
|
charFormat.setFontPointSize(11);
|
|
charFormat.setFontWeight(QFont::Bold);
|
|
cursor->insertBlock(blockFormat, charFormat);
|
|
|
|
QTextTableFormat tableFormat;
|
|
tableFormat.setCellPadding(0);
|
|
tableFormat.setCellSpacing(0);
|
|
tableFormat.setBorder(0);
|
|
QTextTable *table = cursor->insertTable(node->size() + 1, totalColumns, tableFormat);
|
|
for (int i = 0; i < node->size(); i++) {
|
|
auto *card = dynamic_cast<AbstractDecklistCardNode *>(node->at(i));
|
|
|
|
QTextCharFormat cellCharFormat;
|
|
cellCharFormat.setFontPointSize(9);
|
|
|
|
QTextTableCell cell = table->cellAt(i, 0);
|
|
cell.setFormat(cellCharFormat);
|
|
QTextCursor cellCursor = cell.firstCursorPosition();
|
|
cellCursor.insertText(QString("%1 ").arg(card->getNumber()));
|
|
|
|
cell = table->cellAt(i, 1);
|
|
cell.setFormat(cellCharFormat);
|
|
cellCursor = cell.firstCursorPosition();
|
|
cellCursor.insertText(card->getName());
|
|
}
|
|
} else if (node->height() == 2) {
|
|
QTextBlockFormat blockFormat;
|
|
QTextCharFormat charFormat;
|
|
charFormat.setFontPointSize(14);
|
|
charFormat.setFontWeight(QFont::Bold);
|
|
|
|
cursor->insertBlock(blockFormat, charFormat);
|
|
|
|
QTextTableFormat tableFormat;
|
|
tableFormat.setCellPadding(10);
|
|
tableFormat.setCellSpacing(0);
|
|
tableFormat.setBorder(0);
|
|
QVector<QTextLength> constraints;
|
|
for (int i = 0; i < totalColumns; i++) {
|
|
constraints << QTextLength(QTextLength::PercentageLength, 100.0 / totalColumns);
|
|
}
|
|
tableFormat.setColumnWidthConstraints(constraints);
|
|
|
|
QTextTable *table = cursor->insertTable(1, totalColumns, tableFormat);
|
|
for (int i = 0; i < node->size(); i++) {
|
|
QTextCursor cellCursor = table->cellAt(0, (i * totalColumns) / node->size()).lastCursorPosition();
|
|
printDeckListNode(&cellCursor, dynamic_cast<InnerDecklistNode *>(node->at(i)));
|
|
}
|
|
}
|
|
|
|
cursor->movePosition(QTextCursor::End);
|
|
}
|
|
|
|
void DeckLoader::printDeckList(QPrinter *printer, const DeckList *deckList)
|
|
{
|
|
QTextDocument doc;
|
|
|
|
QFont font("Serif");
|
|
font.setStyleHint(QFont::Serif);
|
|
doc.setDefaultFont(font);
|
|
|
|
QTextCursor cursor(&doc);
|
|
|
|
QTextBlockFormat headerBlockFormat;
|
|
QTextCharFormat headerCharFormat;
|
|
headerCharFormat.setFontPointSize(16);
|
|
headerCharFormat.setFontWeight(QFont::Bold);
|
|
|
|
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
|
cursor.insertText(deckList->getName());
|
|
|
|
headerCharFormat.setFontPointSize(12);
|
|
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
|
cursor.insertText(deckList->getComments());
|
|
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
|
|
|
for (int i = 0; i < deckList->getRoot()->size(); i++) {
|
|
cursor.insertHtml("<br><img src=theme:hr.jpg>");
|
|
// cursor.insertHtml("<hr>");
|
|
cursor.insertBlock(headerBlockFormat, headerCharFormat);
|
|
|
|
printDeckListNode(&cursor, dynamic_cast<InnerDecklistNode *>(deckList->getRoot()->at(i)));
|
|
}
|
|
|
|
doc.print(printer);
|
|
}
|