#include "dlg_create_token.h" #include "../../client/settings/cache_settings.h" #include "../../interface/widgets/printing_selector/printing_selector.h" #include "libcockatrice/card/card_info.h" #include "libcockatrice/card/database/card_database_querier.h" #include "libcockatrice/card/printing/exact_card.h" #include "token_preview_widget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class QModelIndex; class QWidget; DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *parent) : QDialog(parent), predefinedTokens(_predefinedTokens) { pic = new TokenPreviewWidget(); pic->setObjectName("pic"); nameLabel = new QLabel(tr("&Name:")); nameEdit = new QLineEdit(tr("Token")); nameEdit->setMaxLength(MAX_NAME_LENGTH); QTimer::singleShot(100, this, [=, this]() { nameEdit->selectAll(); nameEdit->setFocus(); }); connect(nameEdit, &QLineEdit::textChanged, this, &DlgCreateToken::updateSearch); nameLabel->setBuddy(nameEdit); colorLabel = new QLabel(tr("C&olor:")); colorEdit = new QComboBox; colorEdit->addItem(tr("white"), QChar('w')); colorEdit->addItem(tr("blue"), QChar('u')); colorEdit->addItem(tr("black"), QChar('b')); colorEdit->addItem(tr("red"), QChar('r')); colorEdit->addItem(tr("green"), QChar('g')); colorEdit->addItem(tr("multicolor"), QChar('m')); colorEdit->addItem(tr("colorless"), QChar()); colorLabel->setBuddy(colorEdit); ptLabel = new QLabel(tr("&P/T:")); ptEdit = new QLineEdit; ptEdit->setMaxLength(MAX_NAME_LENGTH); ptLabel->setBuddy(ptEdit); annotationLabel = new QLabel(tr("&Annotation:")); annotationEdit = new QLineEdit; annotationEdit->setMaxLength(MAX_NAME_LENGTH); annotationLabel->setBuddy(annotationEdit); destroyCheckBox = new QCheckBox(tr("&Destroy token when it leaves the table")); destroyCheckBox->setChecked(true); faceDownCheckBox = new QCheckBox(tr("Create face-down (Only hides name)")); connect(faceDownCheckBox, &QCheckBox::toggled, this, &DlgCreateToken::faceDownCheckBoxToggled); auto *grid = new QGridLayout; grid->addWidget(nameLabel, 0, 0); grid->addWidget(nameEdit, 0, 1); grid->addWidget(colorLabel, 1, 0); grid->addWidget(colorEdit, 1, 1); grid->addWidget(ptLabel, 2, 0); grid->addWidget(ptEdit, 2, 1); grid->addWidget(annotationLabel, 3, 0); grid->addWidget(annotationEdit, 3, 1); grid->addWidget(destroyCheckBox, 4, 0, 1, 2); grid->addWidget(faceDownCheckBox, 5, 0, 1, 2); auto *tokenDataGroupBox = new QGroupBox(tr("Token data")); tokenDataGroupBox->setLayout(grid); cardDatabaseModel = new CardDatabaseModel(CardDatabaseManager::getInstance(), false, this); cardDatabaseDisplayModel = new TokenDisplayModel(this); cardDatabaseDisplayModel->setSourceModel(cardDatabaseModel); const auto applyCardSearchLanguage = [this]() { const CardsDisplaySettings &cardsDisplay = SettingsCache::instance().cardsDisplay(); cardDatabaseDisplayModel->setSearchLanguage(CardSearchLanguage{ cardsDisplay.getCardLang(), static_cast(cardsDisplay.getCardSearchLanguage())}); }; applyCardSearchLanguage(); connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardLangChanged, this, applyCardSearchLanguage); connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardSearchLanguageChanged, this, applyCardSearchLanguage); chooseTokenFromAllRadioButton = new QRadioButton(tr("Show &all tokens")); connect(chooseTokenFromAllRadioButton, &QRadioButton::toggled, this, &DlgCreateToken::actChooseTokenFromAll); chooseTokenFromDeckRadioButton = new QRadioButton(tr("Show tokens from this &deck")); connect(chooseTokenFromDeckRadioButton, &QRadioButton::toggled, this, &DlgCreateToken::actChooseTokenFromDeck); QByteArray deckHeaderState = SettingsCache::instance().layouts().getDeckEditorDbHeaderState(); chooseTokenView = new QTreeView; chooseTokenView->setModel(cardDatabaseDisplayModel); chooseTokenView->setUniformRowHeights(true); chooseTokenView->setRootIsDecorated(false); chooseTokenView->setAlternatingRowColors(true); chooseTokenView->setSortingEnabled(true); chooseTokenView->sortByColumn(0, Qt::AscendingOrder); chooseTokenView->resizeColumnToContents(0); chooseTokenView->setWordWrap(true); if (!deckHeaderState.isNull()) { chooseTokenView->header()->restoreState(deckHeaderState); } chooseTokenView->header()->setStretchLastSection(false); chooseTokenView->header()->hideSection(1); // Sets chooseTokenView->header()->hideSection(2); // Mana Cost chooseTokenView->header()->setSectionResizeMode(5, QHeaderView::ResizeToContents); // Color(s) connect(chooseTokenView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &DlgCreateToken::tokenSelectionChanged); connect(chooseTokenView, &QTreeView::doubleClicked, this, &DlgCreateToken::actOk); if (predefinedTokens.isEmpty()) { chooseTokenFromAllRadioButton->setChecked(true); chooseTokenFromDeckRadioButton->setDisabled(true); // No tokens in deck = no need for option } else { chooseTokenFromDeckRadioButton->setChecked(true); cardDatabaseDisplayModel->setCardNameSet(QSet(predefinedTokens.begin(), predefinedTokens.end())); } auto *tokenChoosePage = new QWidget; tokenChoosePage->setObjectName("tokenChoosePage"); auto *tokenChooseLayout = QtUtils::makeLayout(tokenChoosePage); tokenChooseLayout->addWidget(chooseTokenFromAllRadioButton); tokenChooseLayout->addWidget(chooseTokenFromDeckRadioButton); tokenChooseLayout->addWidget(chooseTokenView); auto *printingsPage = new QWidget; printingsPage->setObjectName("tokenPrintingsPage"); auto *printingsLayout = QtUtils::makeLayout(printingsPage); printingSelector = new PrintingSelector(printingsPage, &printingSelectorHost); printingSelector->setObjectName("tokenPrintingSelector"); printingsLayout->addWidget(printingSelector); connect(printingSelector, &PrintingSelector::printingSelected, this, [this](const ExactCard &card) { explicitProviderId = card.getPrinting().getUuid(); explicitProviderName = card.getName(); updatePreview(); }); tokenSourceTabs = new QTabWidget; tokenSourceTabs->setObjectName("tokenSourceTabs"); tokenSourceTabs->addTab(tokenChoosePage, tr("&Tokens")); tokenSourceTabs->addTab(printingsPage, tr("&Printings")); auto *hbox = new QGridLayout; hbox->addWidget(pic, 0, 0, 1, 1); hbox->addWidget(tokenDataGroupBox, 1, 0, 1, 1); hbox->addWidget(tokenSourceTabs, 0, 1, 2, 1); hbox->setColumnStretch(1, 1); auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgCreateToken::actOk); connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgCreateToken::actReject); auto *mainLayout = new QVBoxLayout; mainLayout->addLayout(hbox); mainLayout->addWidget(buttonBox); setLayout(mainLayout); setWindowTitle(tr("Create token")); connect(nameEdit, &QLineEdit::textChanged, this, &DlgCreateToken::syncPrintingsToToken); connect(nameEdit, &QLineEdit::textChanged, this, &DlgCreateToken::updatePreview); connect(colorEdit, qOverload(&QComboBox::currentIndexChanged), this, &DlgCreateToken::updatePreview); connect(ptEdit, &QLineEdit::textChanged, this, &DlgCreateToken::updatePreview); connect(annotationEdit, &QLineEdit::textChanged, this, &DlgCreateToken::updatePreview); syncPrintingsToToken(); updatePreview(); resize(900, 650); restoreGeometry(SettingsCache::instance().layouts().getTokenDialogGeometry()); } void DlgCreateToken::closeEvent(QCloseEvent *event) { event->accept(); SettingsCache::instance().layouts().setTokenDialogGeometry(saveGeometry()); } void DlgCreateToken::faceDownCheckBoxToggled(bool checked) { // Keep the face-up draft visible while choosing what to create. Only the // outgoing command applies face-down color/P/T restrictions. colorEdit->setEnabled(!checked); ptEdit->setEnabled(!checked); if (checked) { ptEdit->clearFocus(); } updatePreview(); } void DlgCreateToken::tokenSelectionChanged(const QModelIndex ¤t, const QModelIndex & /*previous*/) { const QModelIndex realIndex = cardDatabaseDisplayModel->mapToSource(current); CardInfoPtr cardInfo; if (realIndex.isValid()) { cardInfo = cardDatabaseModel->getCard(realIndex.row()); } if (cardInfo) { const QSignalBlocker colorBlocker(colorEdit); const QSignalBlocker ptBlocker(ptEdit); const QSignalBlocker annotationBlocker(annotationEdit); updateSearchFieldWithoutUpdatingFilter(cardInfo->getName()); const QChar cardColor = cardInfo->getColorChar(); colorEdit->setCurrentIndex(colorEdit->findData(cardColor, Qt::UserRole, Qt::MatchFixedString)); ptEdit->setText(cardInfo->getPowTough()); if (SettingsCache::instance().userInterface().getAnnotateTokens()) { annotationEdit->setText(cardInfo->getText()); } } // Filtering, changing the deck/all scope, or reloading the database can // invalidate the current row. None of these actions discard the token draft. syncPrintingsToToken(); updatePreview(); } void DlgCreateToken::syncPrintingsToToken() { const CardInfoPtr cardInfo = CardDatabaseManager::query()->lookupCardByName(nameEdit->text()); if (!cardInfo) { explicitProviderId.clear(); explicitProviderName.clear(); } else if (!explicitProviderId.isEmpty() && explicitProviderName != cardInfo->getName()) { // A pick belongs to the token name it was made for. explicitProviderId.clear(); explicitProviderName.clear(); } if (!cardInfo || cardInfo->getSets().empty()) { printingSelector->setCard(nullptr); printingSelector->setSelectedProviderId(QString()); printingSelector->setPlaceholderText(tr("This token has no available printings.")); return; } printingSelector->setPlaceholderText(QString()); printingSelector->setCard(cardInfo); const QString providerId = explicitProviderId.isEmpty() ? SettingsCache::instance().cardOverrides().getCardPreferenceOverride(cardInfo->getName()) : explicitProviderId; printingSelector->setSelectedProviderId(providerId); } void DlgCreateToken::updateSearchFieldWithoutUpdatingFilter(const QString &newValue) const { const QSignalBlocker blocker(nameEdit); nameEdit->setText(newValue); } void DlgCreateToken::updatePreview() { pic->setToken(tokenDraft()); } void DlgCreateToken::updateSearch(const QString &search) { cardDatabaseDisplayModel->setCardName(search); } void DlgCreateToken::actChooseTokenFromAll(bool checked) { if (checked) { cardDatabaseDisplayModel->setCardNameSet(QSet()); } } void DlgCreateToken::actChooseTokenFromDeck(bool checked) { if (checked) { cardDatabaseDisplayModel->setCardNameSet(QSet(predefinedTokens.begin(), predefinedTokens.end())); } } void DlgCreateToken::actOk() { SettingsCache::instance().layouts().setTokenDialogGeometry(saveGeometry()); accept(); } void DlgCreateToken::actReject() { SettingsCache::instance().layouts().setTokenDialogGeometry(saveGeometry()); reject(); } TokenInfo DlgCreateToken::tokenDraft() const { // Keep an explicit pick only while it still belongs to the current token. QString providerId = explicitProviderId; if (!providerId.isEmpty()) { const CardInfoPtr pickedCard = CardDatabaseManager::query()->lookupCardByName(nameEdit->text()); if (!pickedCard || pickedCard->getName() != explicitProviderName) { providerId.clear(); } } if (providerId.isEmpty()) { providerId = SettingsCache::instance().cardOverrides().getCardPreferenceOverride(nameEdit->text()); } TokenInfo token = {.name = nameEdit->text(), .color = colorEdit->itemData(colorEdit->currentIndex()).toString(), .pt = ptEdit->text(), .annotation = annotationEdit->text(), .destroy = destroyCheckBox->isChecked(), .faceDown = faceDownCheckBox->isChecked(), .providerId = providerId}; // Match the normalization applied when the player creates the token, so // manually typed aliases and the default P/T preview the actual result. const ExactCard correctedCard = CardDatabaseManager::query()->guessCard(CardRef{.name = token.name, .providerId = token.providerId}); if (correctedCard) { token.name = correctedCard.getName(); if (token.pt.isEmpty()) { token.pt = correctedCard.getInfo().getPowTough(); } } return token; } TokenInfo DlgCreateToken::getTokenInfo() const { TokenInfo token = tokenDraft(); if (token.faceDown) { token.color = QString(QChar()); token.pt.clear(); } return token; }