mirror of
https://github.com/sebastiandine/Card-Collection-Manager-3.git
synced 2026-08-28 17:01:02 +00:00
Minor: Add Asian Pokemon Card Support (#18)
This commit is contained in:
@@ -27,6 +27,8 @@ struct AppContext {
|
||||
IGameModule& pokemonModule;
|
||||
IGameModule& yuGiOhModule;
|
||||
IGameModule& digiBattle99Module;
|
||||
// Asia Pokemon sets/preview backend (not a separate Game menu entry).
|
||||
IGameModule& japanesePokemonModule;
|
||||
// Active per-game UI bundles. The order is the order shown in the
|
||||
// Game menu; the composition root constructs them and hands raw
|
||||
// pointers in. `MainFrame` does not own these — `app/main.cpp` does.
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
// BaseCardEditDialog<TCard>
|
||||
//
|
||||
// Header-only template for the modal create/edit form. Owns the parts every
|
||||
// game shares — Name, Set picker (read-only combo with prefix typeahead),
|
||||
// game shares — Name, Set picker (read-only combo with typeahead: prefix first,
|
||||
// then substring, with ASCII-fold so "Pokemon"/"Jungle" match "Pokémon Jungle"),
|
||||
// Amount spin, Language and Condition choices, Note, Image list with
|
||||
// Add/Remove/double-click-to-view, OK/Cancel — and exposes hooks the
|
||||
// subclass uses to:
|
||||
@@ -48,6 +49,7 @@
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -102,6 +104,9 @@ protected:
|
||||
// wants; the base only owns the surrounding label.
|
||||
virtual void buildFlagsRow(wxBoxSizer* flagsBox) = 0;
|
||||
|
||||
// Subclass adds any labelled rows between Name and Set. Default does nothing.
|
||||
virtual void appendPreSetRows(wxFlexGridSizer* /*grid*/) {}
|
||||
|
||||
// Subclass adds any extra game-specific labelled rows just below the
|
||||
// standard rows but above the Note row, by calling `appendRow(label, ctrl)`
|
||||
// (provided as a parameter). Default does nothing.
|
||||
@@ -114,6 +119,11 @@ protected:
|
||||
// Subclass copies the extra fields it owns from its widgets back into `card_`.
|
||||
virtual void writeExtraToCard() {}
|
||||
|
||||
// Languages offered in the Language choice. Default: allLanguages().
|
||||
[[nodiscard]] virtual std::span<const Language> languagesForChoice() const {
|
||||
return allLanguages();
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual std::string updateMenuName() const { return "Update Sets"; }
|
||||
|
||||
// Display name passed into errors and the dialog title hints.
|
||||
@@ -151,6 +161,12 @@ protected:
|
||||
return preloadedSets_ != nullptr ? *preloadedSets_ : sets_;
|
||||
}
|
||||
|
||||
void setPreloadedSetsPointer(const std::vector<Set>* sets) noexcept {
|
||||
preloadedSets_ = sets;
|
||||
}
|
||||
|
||||
void refreshSetAndLanguageChoices() { populateChoices(); }
|
||||
|
||||
// Default: combo only. Yu-Gi-Oh! overrides to add set-code entry + toggle.
|
||||
virtual void customizeSetPickerRow(wxBoxSizer& row, wxComboBox* combo) {
|
||||
row.Add(combo, 1, wxEXPAND);
|
||||
@@ -193,6 +209,9 @@ private:
|
||||
});
|
||||
appendRow(grid, "Name", nameCtrl_);
|
||||
|
||||
// Optional rows between Name and Set (e.g. Pokemon West/Asia region).
|
||||
appendPreSetRows(grid);
|
||||
|
||||
// `setCombo_` must be parented to `setHost` so every control in the Set row
|
||||
// shares the same `wxPanel`; otherwise the combo stays a direct child of the
|
||||
// dialog while the sizer lives on `setHost`, which corrupts layout on MSW.
|
||||
@@ -311,9 +330,10 @@ private:
|
||||
languageChoice_->Clear();
|
||||
int langIdx = 0;
|
||||
int i = 0;
|
||||
const auto langsForChoice = languagesForChoice();
|
||||
wxArrayString langs;
|
||||
langs.Alloc(allLanguages().size());
|
||||
for (auto l : allLanguages()) {
|
||||
langs.Alloc(langsForChoice.size());
|
||||
for (auto l : langsForChoice) {
|
||||
const std::string lang = std::string(to_string(l));
|
||||
langs.Add(wxString::FromUTF8(lang.c_str()));
|
||||
if (l == card_.language) langIdx = i;
|
||||
@@ -486,20 +506,43 @@ private:
|
||||
#endif
|
||||
}
|
||||
|
||||
// Fold a few Latin-1 diacritics so typing ASCII "Pokemon" matches "Pokémon".
|
||||
[[nodiscard]] static wxString foldAsciiForTypeahead(wxString s) {
|
||||
s.MakeLower();
|
||||
s.Replace(wxString::FromUTF8("\xc3\xa9"), wxT("e")); // é
|
||||
s.Replace(wxString::FromUTF8("\xc3\x89"), wxT("e")); // É (after lower: é)
|
||||
s.Replace(wxString::FromUTF8("\xc3\xa8"), wxT("e")); // è
|
||||
s.Replace(wxString::FromUTF8("\xc3\xaa"), wxT("e")); // ê
|
||||
s.Replace(wxString::FromUTF8("\xc3\xa0"), wxT("a")); // à
|
||||
s.Replace(wxString::FromUTF8("\xc3\xa1"), wxT("a")); // á
|
||||
s.Replace(wxString::FromUTF8("\xc3\xb1"), wxT("n")); // ñ
|
||||
s.Replace(wxString::FromUTF8("\xc3\xbc"), wxT("u")); // ü
|
||||
s.Replace(wxString::FromUTF8("\xc3\xb6"), wxT("o")); // ö
|
||||
return s;
|
||||
}
|
||||
|
||||
void applySetTypeaheadSelection() {
|
||||
const auto& available = availableSets();
|
||||
if (!setCombo_ || available.empty()) return;
|
||||
wxString pref = setTypeaheadPrefix_;
|
||||
pref.MakeLower();
|
||||
const wxString pref = foldAsciiForTypeahead(setTypeaheadPrefix_);
|
||||
if (pref.empty()) return;
|
||||
// Prefer prefix matches, then substring (so "Jungle" finds "Pokémon Jungle").
|
||||
for (std::size_t i = 0; i < available.size(); ++i) {
|
||||
wxString name(wxString::FromUTF8(available[i].name));
|
||||
name.MakeLower();
|
||||
const wxString name =
|
||||
foldAsciiForTypeahead(wxString::FromUTF8(available[i].name));
|
||||
if (name.StartsWith(pref)) {
|
||||
setCombo_->SetSelection(static_cast<int>(i));
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (std::size_t i = 0; i < available.size(); ++i) {
|
||||
const wxString name =
|
||||
foldAsciiForTypeahead(wxString::FromUTF8(available[i].name));
|
||||
if (name.Contains(pref)) {
|
||||
setCombo_->SetSelection(static_cast<int>(i));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onSetComboChar(wxKeyEvent& ev) {
|
||||
|
||||
@@ -217,6 +217,12 @@ protected:
|
||||
|
||||
[[nodiscard]] virtual Game gameId() const noexcept = 0;
|
||||
|
||||
// Preview / card-back routing. Defaults to `gameId()`; Pokemon overrides
|
||||
// so Asia cards use the JapanesePokemon preview source + card back.
|
||||
[[nodiscard]] virtual Game previewGameFor(const TCard& /*card*/) const noexcept {
|
||||
return gameId();
|
||||
}
|
||||
|
||||
// Construction ------------------------------------------------------------
|
||||
|
||||
BaseSelectedCardPanel(wxWindow* parent,
|
||||
@@ -292,6 +298,9 @@ private:
|
||||
case Game::Pokemon:
|
||||
// Mirrors CCM2's unresolved-preview fallback image.
|
||||
return "https://archives.bulbagarden.net/media/upload/1/17/Cardback.jpg";
|
||||
case Game::JapanesePokemon:
|
||||
// Japanese TCG back (distinct from the Western Cardback.jpg).
|
||||
return "https://archives.bulbagarden.net/media/upload/2/2a/TCG_Card_Back_Japanese.jpg";
|
||||
case Game::YuGiOh:
|
||||
// Yugipedia English TCG backing (thumbnail — smaller than full scan).
|
||||
return "https://ms.yugipedia.com/thumb/e/e5/Back-EN.png/250px-Back-EN.png";
|
||||
@@ -379,7 +388,7 @@ private:
|
||||
auto state = state_;
|
||||
CardPreviewService* svcPtr = &cardPreview_;
|
||||
auto [name, setId, setNo] = previewKey(card);
|
||||
const Game game = gameId();
|
||||
const Game game = previewGameFor(card);
|
||||
const std::string exeDirCopy = exeDirForBundledAssets_;
|
||||
|
||||
std::thread([state, gen, svcPtr, name = std::move(name),
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
// PokemonCardEditDialog: typed Add/Edit form for a `PokemonCard`. Inherits
|
||||
// the shared layout, set picker, and image management from
|
||||
// `BaseCardEditDialog<PokemonCard>` and adds:
|
||||
// - a `Set #` text input (between the Set picker and the Amount spin)
|
||||
// - `Holo`, `1. Edition`, `Signed`, `Altered` check boxes in the flags row
|
||||
// PokemonCardEditDialog: Add/Edit form for a unified West/Asia PokemonCard.
|
||||
// West/Asia switch drives set lists, language choices, preview APIs, and the
|
||||
// Asia-only UnnumberedPromo print UX (from the former Japanese dialog).
|
||||
|
||||
#include "ccm/domain/PokemonCard.hpp"
|
||||
#include "ccm/ports/ICardPreviewSource.hpp"
|
||||
#include "ccm/services/CardPreviewService.hpp"
|
||||
#include "ccm/ui/BaseCardEditDialog.hpp"
|
||||
#include "ccm/ui/SwitchCtrl.hpp"
|
||||
|
||||
#include <wx/button.h>
|
||||
#include <wx/stattext.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ccm::ui {
|
||||
|
||||
class VariantImagePreviewDialog;
|
||||
|
||||
class PokemonCardEditDialog final : public BaseCardEditDialog<PokemonCard> {
|
||||
public:
|
||||
PokemonCardEditDialog(wxWindow* parent,
|
||||
@@ -27,22 +31,27 @@ public:
|
||||
CardPreviewService& cardPreview,
|
||||
EditMode mode,
|
||||
PokemonCard initial,
|
||||
const std::vector<Set>* preloadedSets = nullptr);
|
||||
const std::vector<Set>* westSets = nullptr,
|
||||
const std::vector<Set>* asiaSets = nullptr);
|
||||
~PokemonCardEditDialog() override;
|
||||
|
||||
protected:
|
||||
void appendPreSetRows(wxFlexGridSizer* grid) override;
|
||||
void buildFlagsRow(wxBoxSizer* flagsBox) override;
|
||||
void appendExtraRows(wxFlexGridSizer* grid) override;
|
||||
void readExtraFromCard() override;
|
||||
void writeExtraToCard() override;
|
||||
[[nodiscard]] std::string updateMenuName() const override { return "Update Pokemon"; }
|
||||
void onCardLookupContextChanged() override;
|
||||
[[nodiscard]] std::span<const Language> languagesForChoice() const override;
|
||||
|
||||
private:
|
||||
struct VariantFetchState {
|
||||
std::atomic<bool> alive{true};
|
||||
};
|
||||
|
||||
void onRegionSwitch(wxCommandEvent&);
|
||||
void applyRegion(PokemonRegion region, bool clearSetIfMissing);
|
||||
void onAutoDetectSetNo(wxCommandEvent&);
|
||||
void onNextSetNo(wxCommandEvent&);
|
||||
void onSetSelectionChanged(wxCommandEvent&);
|
||||
@@ -60,15 +69,37 @@ private:
|
||||
void rebuildVariantRingFromCache();
|
||||
void syncRingPositionToControls();
|
||||
void refreshVariantNextControls();
|
||||
void refreshSetNoRowMode();
|
||||
void applySelectedSetNo(std::string setNo);
|
||||
void stepVariantRing(int delta);
|
||||
void scheduleDeferredVariantPrefetch();
|
||||
void prefetchVariantsForCurrentCardSilent(unsigned capturedEpoch);
|
||||
void closeUnnumberedPreview();
|
||||
void ensureUnnumberedPreviewOpen();
|
||||
void refreshUnnumberedPreview();
|
||||
void requestUnnumberedPreviewAsync(unsigned capturedEpoch,
|
||||
std::string name,
|
||||
std::string setId,
|
||||
std::string setNo,
|
||||
std::size_t ringIndex,
|
||||
std::size_t ringCount);
|
||||
[[nodiscard]] bool isUnnumberedPromoSelected() const;
|
||||
[[nodiscard]] std::string currentRingSetNo() const;
|
||||
[[nodiscard]] Game backendGame() const noexcept;
|
||||
[[nodiscard]] PokemonRegion currentRegion() const noexcept;
|
||||
[[nodiscard]] static std::string storedSetNoFromControls(const wxTextCtrl* ctrl);
|
||||
[[nodiscard]] static std::string normalizedStoredSetNo(std::string_view setNo);
|
||||
|
||||
EditMode dialogMode_;
|
||||
unsigned variantFetchEpoch_{0};
|
||||
unsigned previewFetchEpoch_{0};
|
||||
CardPreviewService& cardPreview_;
|
||||
const std::vector<Set>* westSets_{nullptr};
|
||||
const std::vector<Set>* asiaSets_{nullptr};
|
||||
std::shared_ptr<VariantFetchState> variantFetchState_;
|
||||
|
||||
SwitchCtrl* regionSwitch_{nullptr};
|
||||
wxStaticText* setNoLabel_{nullptr};
|
||||
wxTextCtrl* setNoCtrl_{nullptr};
|
||||
wxButton* autoSetNoBtn_{nullptr};
|
||||
wxButton* nextSetNoBtn_{nullptr};
|
||||
@@ -76,7 +107,9 @@ private:
|
||||
wxCheckBox* firstEditionCheck_{nullptr};
|
||||
wxCheckBox* signedCheck_{nullptr};
|
||||
wxCheckBox* alteredCheck_{nullptr};
|
||||
VariantImagePreviewDialog* unnumberedPreview_{nullptr};
|
||||
|
||||
std::string selectedSetNo_;
|
||||
std::vector<AutoDetectedPrint> cachedVariants_;
|
||||
std::vector<std::string> uniqueSetNos_;
|
||||
std::size_t setNoRingPos_{0};
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
// PokemonGameView: IGameView for the Pokemon TCG. Mirrors `MagicGameView` —
|
||||
// owns the Pokemon-typed list, selected, and edit-dialog widgets and
|
||||
// delegates persistence to a `CollectionService<PokemonCard>` reference
|
||||
// supplied by the composition root.
|
||||
// PokemonGameView: unified West + Asia Pokemon UI. One collection file;
|
||||
// separate West/Asia set caches; Sets > Update Pokemon refreshes both.
|
||||
|
||||
#include "ccm/domain/PokemonCard.hpp"
|
||||
#include "ccm/games/IGameModule.hpp"
|
||||
@@ -49,7 +47,7 @@ public:
|
||||
|
||||
private:
|
||||
void ensureSetsLoaded();
|
||||
const std::vector<Set>& setsForDialog();
|
||||
const std::vector<Set>& setsForDialog(PokemonRegion region);
|
||||
|
||||
ConfigService& config_;
|
||||
CollectionService<PokemonCard>& collection_;
|
||||
@@ -60,7 +58,8 @@ private:
|
||||
|
||||
PokemonCardListPanel* listPanel_{nullptr};
|
||||
PokemonSelectedCardPanel* selectedPanel_{nullptr};
|
||||
std::vector<Set> setsCache_;
|
||||
std::vector<Set> setsCacheWest_;
|
||||
std::vector<Set> setsCacheAsia_;
|
||||
bool attemptedInitialSetLoad_{false};
|
||||
};
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ protected:
|
||||
[[nodiscard]] std::tuple<std::string, std::string, std::string>
|
||||
previewKey(const PokemonCard& card) const override;
|
||||
[[nodiscard]] Game gameId() const noexcept override { return Game::Pokemon; }
|
||||
[[nodiscard]] Game previewGameFor(const PokemonCard& card) const noexcept override {
|
||||
return pokemonBackendGame(card.region);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace ccm::ui
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
// VariantImagePreviewDialog: small modeless popup that shows a single card
|
||||
// preview image (bytes decoded as wxImage). Used by Japanese Pokémon Add/Edit
|
||||
// when cycling UnnumberedPromo prints. Prev/Next fire custom events so the
|
||||
// edit dialog owns the variant ring.
|
||||
|
||||
#include <wx/button.h>
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/image.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/string.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace ccm::ui {
|
||||
|
||||
wxDECLARE_EVENT(EVT_VARIANT_PREVIEW_PREV, wxCommandEvent);
|
||||
wxDECLARE_EVENT(EVT_VARIANT_PREVIEW_NEXT, wxCommandEvent);
|
||||
|
||||
class VariantImagePreviewDialog : public wxDialog {
|
||||
public:
|
||||
explicit VariantImagePreviewDialog(wxWindow* parent);
|
||||
|
||||
void setCaption(const wxString& caption);
|
||||
void setImageBytes(std::string_view bytes);
|
||||
void clearImage();
|
||||
void setNavigationEnabled(bool enabled);
|
||||
void repositionBesideParent();
|
||||
|
||||
private:
|
||||
class ImageCanvas;
|
||||
|
||||
void onPrev(wxCommandEvent&);
|
||||
void onNext(wxCommandEvent&);
|
||||
|
||||
ImageCanvas* imageHost_{nullptr};
|
||||
wxStaticText* caption_{nullptr};
|
||||
wxButton* prevButton_{nullptr};
|
||||
wxButton* nextButton_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace ccm::ui
|
||||
Reference in New Issue
Block a user