mirror of
https://github.com/sebastiandine/Card-Collection-Manager-3.git
synced 2026-08-29 01:08:49 +00:00
minor: New Game Digimon Digi-Battle (#17)
* digimon digi battle added to supported games * sonarqube update * readme update --------- Co-authored-by: sdine <sdine@sdine.com>
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
#include "ccm/ui/DigiBattle99CardEditDialog.hpp"
|
||||
|
||||
#include "ccm/domain/Enums.hpp"
|
||||
#include "ccm/games/digibattle99/DigiBattle99CardPreviewSource.hpp"
|
||||
#include <wx/app.h>
|
||||
#include <wx/panel.h>
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace ccm::ui {
|
||||
|
||||
DigiBattle99CardEditDialog::DigiBattle99CardEditDialog(wxWindow* parent,
|
||||
ImageService& imageService,
|
||||
SetService& setService,
|
||||
CardPreviewService& cardPreview,
|
||||
EditMode mode,
|
||||
DigiBattle99Card initial,
|
||||
const std::vector<Set>* preloadedSets)
|
||||
: BaseCardEditDialog<DigiBattle99Card>(
|
||||
parent,
|
||||
mode == EditMode::Create ? "Add Digimon (Digi-Battle) Card"
|
||||
: "Edit Digimon (Digi-Battle) Card",
|
||||
imageService, setService, mode, std::move(initial), Game::DigiBattle99,
|
||||
preloadedSets),
|
||||
dialogMode_(mode),
|
||||
cardPreview_(cardPreview),
|
||||
variantFetchState_(std::make_shared<VariantFetchState>()) {
|
||||
buildAndPopulate();
|
||||
if (dialogMode_ == EditMode::Edit) {
|
||||
scheduleDeferredVariantPrefetch();
|
||||
}
|
||||
}
|
||||
|
||||
DigiBattle99CardEditDialog::~DigiBattle99CardEditDialog() {
|
||||
if (variantFetchState_) {
|
||||
variantFetchState_->alive.store(false);
|
||||
}
|
||||
}
|
||||
|
||||
void DigiBattle99CardEditDialog::onCardLookupContextChanged() {
|
||||
clearCachedPrintVariants();
|
||||
}
|
||||
|
||||
void DigiBattle99CardEditDialog::buildFlagsRow(wxBoxSizer* flagsBox) {
|
||||
holoCheck_ = new wxCheckBox(this, wxID_ANY, "Holo");
|
||||
firstEditionCheck_ = new wxCheckBox(this, wxID_ANY, "1. Edition");
|
||||
signedCheck_ = new wxCheckBox(this, wxID_ANY, "Signed");
|
||||
alteredCheck_ = new wxCheckBox(this, wxID_ANY, "Altered");
|
||||
flagsBox->Add(holoCheck_, 0, wxRIGHT, 12);
|
||||
flagsBox->Add(firstEditionCheck_, 0, wxRIGHT, 12);
|
||||
flagsBox->Add(signedCheck_, 0, wxRIGHT, 12);
|
||||
flagsBox->Add(alteredCheck_, 0, wxRIGHT, 12);
|
||||
}
|
||||
|
||||
void DigiBattle99CardEditDialog::appendExtraRows(wxFlexGridSizer* grid) {
|
||||
auto* setNoPanel = new wxPanel(this, wxID_ANY);
|
||||
setNoCtrl_ = new wxTextCtrl(setNoPanel, wxID_ANY);
|
||||
autoSetNoBtn_ = new wxButton(setNoPanel, wxID_ANY, "Auto detect");
|
||||
autoSetNoBtn_->Bind(wxEVT_BUTTON, &DigiBattle99CardEditDialog::onAutoDetectSetNo, this);
|
||||
nextSetNoBtn_ = new wxButton(setNoPanel, wxID_ANY, "Next");
|
||||
nextSetNoBtn_->Bind(wxEVT_BUTTON, &DigiBattle99CardEditDialog::onNextSetNo, this);
|
||||
nextSetNoBtn_->Show(false);
|
||||
auto* setNoRow = new wxBoxSizer(wxHORIZONTAL);
|
||||
setNoRow->Add(setNoCtrl_, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, 6);
|
||||
setNoRow->Add(autoSetNoBtn_, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 6);
|
||||
setNoRow->Add(nextSetNoBtn_, 0, wxALIGN_CENTER_VERTICAL);
|
||||
setNoPanel->SetSizer(setNoRow);
|
||||
|
||||
appendRow(grid, "Set #", setNoPanel);
|
||||
|
||||
if (auto* setCombo = setComboControl()) {
|
||||
setCombo->Bind(wxEVT_COMBOBOX, &DigiBattle99CardEditDialog::onSetSelectionChanged, this);
|
||||
}
|
||||
}
|
||||
|
||||
std::string DigiBattle99CardEditDialog::normalizedStoredSetNo(std::string_view setNo) {
|
||||
return DigiBattle99CardPreviewSource::normalizeCardNumber(setNo);
|
||||
}
|
||||
|
||||
std::string DigiBattle99CardEditDialog::storedSetNoFromControls(const wxTextCtrl* ctrl) {
|
||||
if (ctrl == nullptr) return {};
|
||||
return normalizedStoredSetNo(ctrl->GetValue().ToStdString(wxConvUTF8));
|
||||
}
|
||||
|
||||
void DigiBattle99CardEditDialog::readExtraFromCard() {
|
||||
clearCachedPrintVariants();
|
||||
if (setNoCtrl_) {
|
||||
setNoCtrl_->ChangeValue(
|
||||
wxString::FromUTF8(normalizedStoredSetNo(constCard().setNo).c_str()));
|
||||
}
|
||||
if (holoCheck_) holoCheck_->SetValue(constCard().holo);
|
||||
if (firstEditionCheck_) firstEditionCheck_->SetValue(constCard().firstEdition);
|
||||
if (signedCheck_) signedCheck_->SetValue(constCard().signed_);
|
||||
if (alteredCheck_) alteredCheck_->SetValue(constCard().altered);
|
||||
}
|
||||
|
||||
void DigiBattle99CardEditDialog::writeExtraToCard() {
|
||||
if (setNoCtrl_) mutableCard().setNo = storedSetNoFromControls(setNoCtrl_);
|
||||
if (holoCheck_) mutableCard().holo = holoCheck_->IsChecked();
|
||||
if (firstEditionCheck_) mutableCard().firstEdition = firstEditionCheck_->IsChecked();
|
||||
if (signedCheck_) mutableCard().signed_ = signedCheck_->IsChecked();
|
||||
if (alteredCheck_) mutableCard().altered = alteredCheck_->IsChecked();
|
||||
}
|
||||
|
||||
void DigiBattle99CardEditDialog::clearCachedPrintVariants() {
|
||||
++variantFetchEpoch_;
|
||||
cachedVariants_.clear();
|
||||
uniqueSetNos_.clear();
|
||||
setNoRingPos_ = 0;
|
||||
refreshVariantNextControls();
|
||||
}
|
||||
|
||||
void DigiBattle99CardEditDialog::scheduleDeferredVariantPrefetch() {
|
||||
const unsigned epoch = variantFetchEpoch_;
|
||||
wxTheApp->CallAfter([this, epoch]() {
|
||||
prefetchVariantsForCurrentCardSilent(epoch);
|
||||
});
|
||||
}
|
||||
|
||||
void DigiBattle99CardEditDialog::prefetchVariantsForCurrentCardSilent(unsigned capturedEpoch) {
|
||||
if (capturedEpoch != variantFetchEpoch_) return;
|
||||
if (!cachedVariants_.empty()) return;
|
||||
const auto& card = constCard();
|
||||
// digimoncard.io pack= uses the display set name, not the slug id.
|
||||
if (card.name.empty() || card.set.name.empty()) return;
|
||||
|
||||
requestVariantsAsync(capturedEpoch, card.name, card.set.name, false, false);
|
||||
}
|
||||
|
||||
void DigiBattle99CardEditDialog::requestVariantsAsync(unsigned capturedEpoch,
|
||||
std::string name,
|
||||
std::string setName,
|
||||
bool fillSetNoOnSuccess,
|
||||
bool showFailureDialog) {
|
||||
if (capturedEpoch != variantFetchEpoch_) return;
|
||||
|
||||
if (fillSetNoOnSuccess && autoSetNoBtn_) {
|
||||
autoSetNoBtn_->Disable();
|
||||
}
|
||||
|
||||
auto state = variantFetchState_;
|
||||
CardPreviewService* svc = &cardPreview_;
|
||||
DigiBattle99CardEditDialog* self = this;
|
||||
std::thread([state, svc, self, capturedEpoch, name = std::move(name),
|
||||
setName = std::move(setName), fillSetNoOnSuccess, showFailureDialog]() {
|
||||
auto detected = svc->detectPrintVariants(Game::DigiBattle99, name, setName);
|
||||
wxTheApp->CallAfter([state, self, capturedEpoch, detected = std::move(detected),
|
||||
fillSetNoOnSuccess, showFailureDialog]() mutable {
|
||||
if (!state->alive.load()) return;
|
||||
self->applyDetectedVariants(capturedEpoch, std::move(detected),
|
||||
fillSetNoOnSuccess, showFailureDialog);
|
||||
});
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void DigiBattle99CardEditDialog::applyDetectedVariants(
|
||||
unsigned capturedEpoch,
|
||||
Result<std::vector<AutoDetectedPrint>> detected,
|
||||
bool fillSetNoOnSuccess,
|
||||
bool showFailureDialog) {
|
||||
if (capturedEpoch != variantFetchEpoch_) return;
|
||||
|
||||
if (fillSetNoOnSuccess && autoSetNoBtn_) {
|
||||
autoSetNoBtn_->Enable();
|
||||
}
|
||||
|
||||
if (!detected) {
|
||||
if (showFailureDialog) {
|
||||
showThemedMessageDialog(this, "Auto detect failed: " + detected.error(), "Auto detect",
|
||||
wxOK | wxICON_WARNING);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
cachedVariants_ = std::move(detected).value();
|
||||
if (fillSetNoOnSuccess && setNoCtrl_ && !cachedVariants_.empty()) {
|
||||
setNoCtrl_->ChangeValue(
|
||||
wxString::FromUTF8(cachedVariants_.front().setNo.c_str()));
|
||||
}
|
||||
|
||||
rebuildVariantRingFromCache();
|
||||
syncRingPositionToControls();
|
||||
refreshVariantNextControls();
|
||||
}
|
||||
|
||||
void DigiBattle99CardEditDialog::rebuildVariantRingFromCache() {
|
||||
uniqueSetNos_.clear();
|
||||
if (cachedVariants_.empty()) return;
|
||||
|
||||
std::unordered_set<std::string> seen;
|
||||
seen.reserve(cachedVariants_.size());
|
||||
for (const auto& p : cachedVariants_) {
|
||||
if (p.setNo.empty()) continue;
|
||||
if (!seen.insert(p.setNo).second) continue;
|
||||
uniqueSetNos_.push_back(p.setNo);
|
||||
}
|
||||
}
|
||||
|
||||
void DigiBattle99CardEditDialog::syncRingPositionToControls() {
|
||||
if (!setNoCtrl_) return;
|
||||
const std::string current = storedSetNoFromControls(setNoCtrl_);
|
||||
setNoRingPos_ = 0;
|
||||
for (std::size_t i = 0; i < uniqueSetNos_.size(); ++i) {
|
||||
if (uniqueSetNos_[i] == current) {
|
||||
setNoRingPos_ = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DigiBattle99CardEditDialog::refreshVariantNextControls() {
|
||||
if (!nextSetNoBtn_) return;
|
||||
nextSetNoBtn_->Show(uniqueSetNos_.size() > 1);
|
||||
Layout();
|
||||
if (GetSizer()) Fit();
|
||||
}
|
||||
|
||||
void DigiBattle99CardEditDialog::onAutoDetectSetNo(wxCommandEvent&) {
|
||||
autoDetectFromApi();
|
||||
}
|
||||
|
||||
void DigiBattle99CardEditDialog::onNextSetNo(wxCommandEvent&) {
|
||||
if (uniqueSetNos_.size() <= 1) return;
|
||||
setNoRingPos_ = (setNoRingPos_ + 1) % uniqueSetNos_.size();
|
||||
if (setNoCtrl_) {
|
||||
setNoCtrl_->ChangeValue(wxString::FromUTF8(uniqueSetNos_[setNoRingPos_].c_str()));
|
||||
}
|
||||
refreshVariantNextControls();
|
||||
}
|
||||
|
||||
void DigiBattle99CardEditDialog::autoDetectFromApi() {
|
||||
syncCardFromControls();
|
||||
const auto& card = constCard();
|
||||
if (card.name.empty()) {
|
||||
showThemedMessageDialog(this, "Enter a card name first.", "Auto detect",
|
||||
wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
if (card.set.name.empty()) {
|
||||
showThemedMessageDialog(this, "Select a set first.", "Auto detect",
|
||||
wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
|
||||
const unsigned epoch = variantFetchEpoch_;
|
||||
requestVariantsAsync(epoch, card.name, card.set.name, true, true);
|
||||
}
|
||||
|
||||
void DigiBattle99CardEditDialog::onSetSelectionChanged(wxCommandEvent& ev) {
|
||||
clearCachedPrintVariants();
|
||||
scheduleDeferredVariantPrefetch();
|
||||
ev.Skip();
|
||||
}
|
||||
|
||||
} // namespace ccm::ui
|
||||
@@ -0,0 +1,71 @@
|
||||
#include "ccm/ui/DigiBattle99CardListPanel.hpp"
|
||||
|
||||
#include "ccm/services/CardFilter.hpp"
|
||||
#include "ccm/ui/SvgIcons.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ccm::ui {
|
||||
|
||||
DigiBattle99CardListPanel::DigiBattle99CardListPanel(wxWindow* parent)
|
||||
: BaseCardListPanel<DigiBattle99Card, DigiBattle99SortColumn>(parent) {
|
||||
buildLayout();
|
||||
}
|
||||
|
||||
std::vector<DigiBattle99CardListPanel::TextColumnSpec>
|
||||
DigiBattle99CardListPanel::declareTextColumns() const {
|
||||
return {
|
||||
{"Name", 220, wxLIST_FORMAT_LEFT, DigiBattle99SortColumn::Name},
|
||||
{"Set", 180, wxLIST_FORMAT_LEFT, DigiBattle99SortColumn::SetReleaseDate},
|
||||
{"Amount", 70, wxLIST_FORMAT_RIGHT, DigiBattle99SortColumn::Amount},
|
||||
{"Condition", 100, wxLIST_FORMAT_LEFT, DigiBattle99SortColumn::Condition},
|
||||
{"Language", 100, wxLIST_FORMAT_LEFT, DigiBattle99SortColumn::Language},
|
||||
{"Note", 220, wxLIST_FORMAT_LEFT, DigiBattle99SortColumn::Note},
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<DigiBattle99CardListPanel::IconColumnSpec>
|
||||
DigiBattle99CardListPanel::declareIconColumns() const {
|
||||
constexpr int kFlagColWidth = 36;
|
||||
return {
|
||||
{kSvgHolo, kFlagColWidth, DigiBattle99SortColumn::Holo},
|
||||
{kSvgFirstEdition, kFlagColWidth, DigiBattle99SortColumn::FirstEdition},
|
||||
{kSvgSigned, kFlagColWidth, DigiBattle99SortColumn::Signed},
|
||||
{kSvgAltered, kFlagColWidth, DigiBattle99SortColumn::Altered},
|
||||
};
|
||||
}
|
||||
|
||||
std::string DigiBattle99CardListPanel::renderTextCell(const DigiBattle99Card& card,
|
||||
std::size_t idx) const {
|
||||
switch (idx) {
|
||||
case 0: return card.name;
|
||||
case 1: return card.set.name;
|
||||
case 2: return std::to_string(card.amount);
|
||||
case 3: return std::string(to_string(card.condition));
|
||||
case 4: return std::string(to_string(card.language));
|
||||
case 5: return card.note;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool DigiBattle99CardListPanel::isIconColumnSet(const DigiBattle99Card& card,
|
||||
std::size_t idx) const {
|
||||
switch (idx) {
|
||||
case 0: return card.holo;
|
||||
case 1: return card.firstEdition;
|
||||
case 2: return card.signed_;
|
||||
case 3: return card.altered;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DigiBattle99CardListPanel::sortBy(DigiBattle99SortColumn column, bool ascending) {
|
||||
sortDigiBattle99Cards(mutableCards(), column, ascending);
|
||||
}
|
||||
|
||||
bool DigiBattle99CardListPanel::matchesFilter(const DigiBattle99Card& card,
|
||||
std::string_view filter) const {
|
||||
return matchesDigiBattle99Filter(card, filter);
|
||||
}
|
||||
|
||||
} // namespace ccm::ui
|
||||
@@ -0,0 +1,216 @@
|
||||
#include "ccm/ui/DigiBattle99GameView.hpp"
|
||||
|
||||
#include "ccm/ui/CardEditModalGuard.hpp"
|
||||
#include "ccm/ui/DigiBattle99CardEditDialog.hpp"
|
||||
#include "ccm/ui/DigiBattle99CardListPanel.hpp"
|
||||
#include "ccm/ui/DigiBattle99SelectedCardPanel.hpp"
|
||||
#include "ccm/ui/Theme.hpp"
|
||||
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/window.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace ccm::ui {
|
||||
|
||||
DigiBattle99GameView::DigiBattle99GameView(ConfigService& config,
|
||||
CollectionService<DigiBattle99Card>& collection,
|
||||
SetService& sets,
|
||||
ImageService& images,
|
||||
CardPreviewService& cardPreview,
|
||||
IGameModule& module)
|
||||
: config_(config),
|
||||
collection_(collection),
|
||||
sets_(sets),
|
||||
images_(images),
|
||||
cardPreview_(cardPreview),
|
||||
module_(module) {}
|
||||
|
||||
void DigiBattle99GameView::ensureSetsLoaded() {
|
||||
if (attemptedInitialSetLoad_) return;
|
||||
attemptedInitialSetLoad_ = true;
|
||||
|
||||
auto cached = sets_.getSets(Game::DigiBattle99);
|
||||
if (cached) {
|
||||
setsCache_ = std::move(cached).value();
|
||||
if (!setsCache_.empty()) return;
|
||||
} else {
|
||||
setsCache_.clear();
|
||||
}
|
||||
|
||||
auto refreshed = sets_.updateSets(Game::DigiBattle99);
|
||||
if (refreshed) {
|
||||
setsCache_ = std::move(refreshed).value();
|
||||
}
|
||||
}
|
||||
|
||||
wxPanel* DigiBattle99GameView::listPanel(wxWindow* parent) {
|
||||
if (listPanel_ == nullptr) {
|
||||
listPanel_ = new DigiBattle99CardListPanel(parent);
|
||||
listPanel_->Bind(EVT_CARD_SELECTED, [this](wxCommandEvent&) {
|
||||
if (selectedPanel_ != nullptr && listPanel_ != nullptr) {
|
||||
selectedPanel_->setCard(listPanel_->selected());
|
||||
}
|
||||
});
|
||||
listPanel_->Bind(EVT_CARD_ACTIVATED, [this](wxCommandEvent&) {
|
||||
wxWindow* owner = wxGetTopLevelParent(listPanel_);
|
||||
onEditCard(owner != nullptr ? owner : static_cast<wxWindow*>(listPanel_));
|
||||
});
|
||||
}
|
||||
return listPanel_;
|
||||
}
|
||||
|
||||
wxPanel* DigiBattle99GameView::selectedPanel(wxWindow* parent) {
|
||||
if (selectedPanel_ == nullptr) {
|
||||
selectedPanel_ = new DigiBattle99SelectedCardPanel(parent, images_, cardPreview_);
|
||||
}
|
||||
return selectedPanel_;
|
||||
}
|
||||
|
||||
void DigiBattle99GameView::refreshCollection() {
|
||||
if (listPanel_ == nullptr) return;
|
||||
auto loaded = collection_.list(Game::DigiBattle99);
|
||||
if (!loaded) {
|
||||
showThemedMessageDialog(
|
||||
nullptr,
|
||||
"Failed to load Digimon (Digi-Battle) collection: " + loaded.error(),
|
||||
"Error", wxOK | wxICON_ERROR);
|
||||
return;
|
||||
}
|
||||
listPanel_->setCards(std::move(loaded).value());
|
||||
listPanel_->activateSelection();
|
||||
if (selectedPanel_) selectedPanel_->setCard(listPanel_->selected());
|
||||
}
|
||||
|
||||
const std::vector<Set>& DigiBattle99GameView::setsForDialog() {
|
||||
ensureSetsLoaded();
|
||||
if (!setsCache_.empty()) return setsCache_;
|
||||
auto loaded = sets_.getSets(Game::DigiBattle99);
|
||||
if (loaded) setsCache_ = std::move(loaded).value();
|
||||
else setsCache_.clear();
|
||||
return setsCache_;
|
||||
}
|
||||
|
||||
void DigiBattle99GameView::onAddCard(wxWindow* parentWindow) {
|
||||
if (cardEditModalIsActive()) {
|
||||
showThemedMessageDialog(parentWindow, wxString::FromUTF8(kCardEditModalBlockedUtf8),
|
||||
wxString::FromUTF8("Add card"), wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
DigiBattle99Card fresh;
|
||||
fresh.amount = 1;
|
||||
fresh.language = Language::English;
|
||||
fresh.condition = Condition::NearMint;
|
||||
|
||||
DigiBattle99CardEditDialog dlg(parentWindow, images_, sets_, cardPreview_, EditMode::Create,
|
||||
fresh, &setsForDialog());
|
||||
themeModalDialog(&dlg, config_.current().theme);
|
||||
CardEditModalGuard modalGuard;
|
||||
if (dlg.ShowModal() != wxID_OK) return;
|
||||
|
||||
auto added = collection_.add(Game::DigiBattle99, dlg.card());
|
||||
if (!added) {
|
||||
showThemedMessageDialog(parentWindow, "Failed to add card: " + added.error(),
|
||||
"Error", wxOK | wxICON_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
DigiBattle99Card persisted = dlg.card();
|
||||
persisted.id = added.value();
|
||||
auto normalized = images_.normalizeNamesForPersistedCard(
|
||||
Game::DigiBattle99, persisted.id, persisted.set.name, persisted.name, persisted.images);
|
||||
if (normalized) {
|
||||
if (normalized.value() != persisted.images) {
|
||||
persisted.images = std::move(normalized).value();
|
||||
auto updated = collection_.update(Game::DigiBattle99, persisted);
|
||||
if (!updated) {
|
||||
showThemedMessageDialog(
|
||||
parentWindow,
|
||||
"Card added, but image name normalization failed to persist: " +
|
||||
updated.error(),
|
||||
"Warning", wxOK | wxICON_WARNING);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showThemedMessageDialog(
|
||||
parentWindow,
|
||||
"Card added, but image rename to ID-prefixed format failed: " + normalized.error(),
|
||||
"Warning", wxOK | wxICON_WARNING);
|
||||
}
|
||||
refreshCollection();
|
||||
}
|
||||
|
||||
void DigiBattle99GameView::onEditCard(wxWindow* parentWindow) {
|
||||
if (listPanel_ == nullptr) return;
|
||||
auto sel = listPanel_->selected();
|
||||
if (!sel) {
|
||||
showThemedMessageDialog(parentWindow, "Select a card first.", "Edit",
|
||||
wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
if (cardEditModalIsActive()) {
|
||||
showThemedMessageDialog(parentWindow, wxString::FromUTF8(kCardEditModalBlockedUtf8),
|
||||
wxString::FromUTF8("Edit"), wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
DigiBattle99CardEditDialog dlg(parentWindow, images_, sets_, cardPreview_, EditMode::Edit,
|
||||
*sel, &setsForDialog());
|
||||
themeModalDialog(&dlg, config_.current().theme);
|
||||
CardEditModalGuard modalGuard;
|
||||
if (dlg.ShowModal() != wxID_OK) return;
|
||||
auto updated = collection_.update(Game::DigiBattle99, dlg.card());
|
||||
if (!updated) {
|
||||
showThemedMessageDialog(parentWindow, "Failed to update card: " + updated.error(),
|
||||
"Error", wxOK | wxICON_ERROR);
|
||||
return;
|
||||
}
|
||||
refreshCollection();
|
||||
}
|
||||
|
||||
void DigiBattle99GameView::onDeleteCard(wxWindow* parentWindow) {
|
||||
if (listPanel_ == nullptr) return;
|
||||
auto sel = listPanel_->selected();
|
||||
if (!sel) {
|
||||
showThemedMessageDialog(parentWindow, "Select a card first.", "Delete",
|
||||
wxOK | wxICON_INFORMATION);
|
||||
return;
|
||||
}
|
||||
if (showThemedConfirmDialog(parentWindow, "Delete \"" + sel->name + "\"?",
|
||||
"Confirm") != wxID_YES) {
|
||||
return;
|
||||
}
|
||||
auto removed = collection_.remove(Game::DigiBattle99, sel->id);
|
||||
if (!removed) {
|
||||
showThemedMessageDialog(parentWindow, "Failed to delete card: " + removed.error(),
|
||||
"Error", wxOK | wxICON_ERROR);
|
||||
return;
|
||||
}
|
||||
refreshCollection();
|
||||
}
|
||||
|
||||
std::string DigiBattle99GameView::onUpdateSets(wxWindow* parentWindow) {
|
||||
auto out = sets_.updateSets(Game::DigiBattle99);
|
||||
if (!out) {
|
||||
showThemedMessageDialog(parentWindow, "Failed to update sets: " + out.error(),
|
||||
"Error", wxOK | wxICON_ERROR);
|
||||
return "Update failed";
|
||||
}
|
||||
setsCache_ = out.value();
|
||||
showThemedMessageDialog(
|
||||
parentWindow,
|
||||
"Updated " + std::to_string(out.value().size()) + " Digimon (Digi-Battle) sets.",
|
||||
"Sets updated", wxOK | wxICON_INFORMATION);
|
||||
return "Digimon (Digi-Battle) sets updated.";
|
||||
}
|
||||
|
||||
void DigiBattle99GameView::setFilter(std::string_view filter) {
|
||||
if (listPanel_) listPanel_->setFilter(filter);
|
||||
}
|
||||
|
||||
void DigiBattle99GameView::applyTheme(const ThemePalette& palette) {
|
||||
if (listPanel_) listPanel_->applyTheme(palette);
|
||||
if (selectedPanel_) selectedPanel_->applyTheme(palette);
|
||||
}
|
||||
|
||||
} // namespace ccm::ui
|
||||
@@ -0,0 +1,84 @@
|
||||
#include "ccm/ui/DigiBattle99SelectedCardPanel.hpp"
|
||||
|
||||
#include "ccm/ui/SvgIcons.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ccm::ui {
|
||||
|
||||
namespace {
|
||||
enum DigiBattle99DetailKey : int {
|
||||
kName = 0,
|
||||
kSet,
|
||||
kSetNo,
|
||||
kLanguage,
|
||||
kCondition,
|
||||
kAmount,
|
||||
kHolo,
|
||||
kFirstEdition,
|
||||
kSigned,
|
||||
kAltered,
|
||||
};
|
||||
} // namespace
|
||||
|
||||
DigiBattle99SelectedCardPanel::DigiBattle99SelectedCardPanel(wxWindow* parent,
|
||||
ImageService& imageService,
|
||||
CardPreviewService& cardPreview)
|
||||
: BaseSelectedCardPanel<DigiBattle99Card>(parent, imageService, cardPreview) {
|
||||
buildLayout();
|
||||
}
|
||||
|
||||
std::vector<DigiBattle99SelectedCardPanel::DetailRowSpec>
|
||||
DigiBattle99SelectedCardPanel::declareDetailRows() const {
|
||||
return {
|
||||
{"Name", kName, "(no card selected)"},
|
||||
{"Set", kSet, ""},
|
||||
{"Set #", kSetNo, ""},
|
||||
{"Language", kLanguage, ""},
|
||||
{"Condition", kCondition, ""},
|
||||
{"Amount", kAmount, ""},
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<DigiBattle99SelectedCardPanel::FlagIconSpec>
|
||||
DigiBattle99SelectedCardPanel::declareFlagIcons() const {
|
||||
return {
|
||||
{kSvgHolo, "Holo", kHolo},
|
||||
{kSvgFirstEdition, "1. Edition", kFirstEdition},
|
||||
{kSvgSigned, "Signed", kSigned},
|
||||
{kSvgAltered, "Altered", kAltered},
|
||||
};
|
||||
}
|
||||
|
||||
std::string DigiBattle99SelectedCardPanel::detailValueFor(const DigiBattle99Card& card,
|
||||
DetailKey key) const {
|
||||
switch (key) {
|
||||
case kName: return card.name;
|
||||
case kSet: return card.set.name;
|
||||
case kSetNo: return card.setNo;
|
||||
case kLanguage: return std::string(to_string(card.language));
|
||||
case kCondition: return std::string(to_string(card.condition));
|
||||
case kAmount: return std::to_string(card.amount);
|
||||
case kNoteKey: return card.note;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool DigiBattle99SelectedCardPanel::isFlagSet(const DigiBattle99Card& card,
|
||||
DetailKey key) const {
|
||||
switch (key) {
|
||||
case kHolo: return card.holo;
|
||||
case kFirstEdition: return card.firstEdition;
|
||||
case kSigned: return card.signed_;
|
||||
case kAltered: return card.altered;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string, std::string>
|
||||
DigiBattle99SelectedCardPanel::previewKey(const DigiBattle99Card& card) const {
|
||||
// Middle slot is Set.name (pack display name) for digimoncard.io pack=.
|
||||
return {card.name, card.set.name, card.setNo};
|
||||
}
|
||||
|
||||
} // namespace ccm::ui
|
||||
@@ -41,8 +41,10 @@ constexpr const char kFilterInputHint[] = "Filter";
|
||||
|
||||
std::string dirNameForGame(Game g) {
|
||||
switch (g) {
|
||||
case Game::Magic: return "magic";
|
||||
case Game::Pokemon: return "pokemon";
|
||||
case Game::Magic: return "magic";
|
||||
case Game::Pokemon: return "pokemon";
|
||||
case Game::YuGiOh: return "yugioh";
|
||||
case Game::DigiBattle99: return "digibattle99";
|
||||
}
|
||||
return "magic";
|
||||
}
|
||||
@@ -67,7 +69,7 @@ void ensureDataStorageScaffold(const Configuration& cfg) {
|
||||
}
|
||||
}
|
||||
|
||||
for (Game game : {Game::Magic, Game::Pokemon}) {
|
||||
for (Game game : allGames()) {
|
||||
const fs::path gameRoot = root / dirNameForGame(game);
|
||||
fs::create_directories(gameRoot / "images", ec);
|
||||
if (ec) continue;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ccm/ui/SettingsDialog.hpp"
|
||||
#include "ccm/ui/Theme.hpp"
|
||||
#include "ccm/domain/Enums.hpp"
|
||||
|
||||
#include <wx/button.h>
|
||||
#include <wx/dirdlg.h>
|
||||
@@ -9,6 +10,20 @@
|
||||
|
||||
namespace ccm::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
wxString displayLabelForGame(Game g) {
|
||||
switch (g) {
|
||||
case Game::Magic: return "Magic";
|
||||
case Game::Pokemon: return "Pokemon";
|
||||
case Game::YuGiOh: return "Yu-Gi-Oh!";
|
||||
case Game::DigiBattle99: return "Digimon (Digi-Battle)";
|
||||
}
|
||||
return wxString::FromUTF8(to_string(g).data());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SettingsDialog::SettingsDialog(wxWindow* parent, ConfigService& config)
|
||||
: wxDialog(parent, wxID_ANY, "Settings",
|
||||
wxDefaultPosition, wxSize(560, 200),
|
||||
@@ -30,9 +45,14 @@ SettingsDialog::SettingsDialog(wxWindow* parent, ConfigService& config)
|
||||
gameRow->Add(new wxStaticText(this, wxID_ANY, "Default game:"),
|
||||
0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 6);
|
||||
defaultGameChoice_ = new wxChoice(this, wxID_ANY);
|
||||
defaultGameChoice_->Append("Magic");
|
||||
defaultGameChoice_->Append("Pokemon");
|
||||
defaultGameChoice_->SetSelection(config_.current().defaultGame == Game::Magic ? 0 : 1);
|
||||
int selected = 0;
|
||||
int idx = 0;
|
||||
for (const Game g : allGames()) {
|
||||
defaultGameChoice_->Append(displayLabelForGame(g));
|
||||
if (g == config_.current().defaultGame) selected = idx;
|
||||
++idx;
|
||||
}
|
||||
defaultGameChoice_->SetSelection(selected);
|
||||
gameRow->Add(defaultGameChoice_, 0);
|
||||
root->Add(gameRow, 0, wxEXPAND | wxLEFT | wxRIGHT, 10);
|
||||
|
||||
@@ -77,7 +97,11 @@ void SettingsDialog::onBrowse(wxCommandEvent&) {
|
||||
void SettingsDialog::onOk(wxCommandEvent& ev) {
|
||||
Configuration next = config_.current();
|
||||
next.dataStorage = dataDirCtrl_->GetValue().ToStdString();
|
||||
next.defaultGame = defaultGameChoice_->GetSelection() == 0 ? Game::Magic : Game::Pokemon;
|
||||
const int gameSel = defaultGameChoice_->GetSelection();
|
||||
const auto& games = allGames();
|
||||
if (gameSel >= 0 && static_cast<std::size_t>(gameSel) < games.size()) {
|
||||
next.defaultGame = games[static_cast<std::size_t>(gameSel)];
|
||||
}
|
||||
switch (themeChoice_->GetSelection()) {
|
||||
case 1: next.theme = Theme::Dark; break;
|
||||
case 0:
|
||||
|
||||
Reference in New Issue
Block a user