Add Json importer (#46)

add boost-json to dependencies
This commit is contained in:
GenevensiS
2025-06-21 07:53:35 +02:00
committed by GitHub
parent 128bb3867d
commit e45353af9b
27 changed files with 685 additions and 185 deletions
+2 -2
View File
@@ -21,11 +21,11 @@ On windows, the program can be compiled with Visual Studio (recommended) or with
======= =======
```` ````
.\vcpkg install pkgconf wxwidgets[fonts] boost-smart-ptr boost-regex boost-logic boost-pool boost-iterator hunspell --triplet=x64-windows-static .\vcpkg install pkgconf wxwidgets[fonts] boost-smart-ptr boost-regex boost-logic boost-pool boost-iterator boost-json hunspell --triplet=x64-windows-static
```` ````
and/or and/or
```` ````
.\vcpkg install pkgconf wxwidgets boost-smart-ptr boost-regex boost-logic boost-pool boost-iterator hunspell --triplet=x86-windows-static .\vcpkg install pkgconf wxwidgets boost-smart-ptr boost-regex boost-logic boost-pool boost-iterator boost-json hunspell --triplet=x86-windows-static
```` ````
then, regardless of your choice then, regardless of your choice
```` ````
+3
View File
@@ -841,6 +841,9 @@ error:
can't create file stream: Failed to create file stream: '%s' can't create file stream: Failed to create file stream: '%s'
can't write image to set: Failed to write image to set: '%s' can't write image to set: Failed to write image to set: '%s'
can't import image: Failed to import image: '%s' can't import image: Failed to import image: '%s'
import missing fields:
The %s file contains the following entries that could not
be imported, because no corresponding card field was found: %s
# Card creation # Card creation
no field with name: Card doesn't have a field named '%s' no field with name: Card doesn't have a field named '%s'
+1
View File
@@ -12,6 +12,7 @@ NOTE: you should use underscores instead of spaces in field names.
--Parameters-- --Parameters--
! Parameter Type Description ! Parameter Type Description
| @input@ [[type:map]] of field names to field values Field values to set | @input@ [[type:map]] of field names to field values Field values to set
| @ignore_field_not_found@ [[type:boolean]] Optional. If set to true, key/value pairs that don't correspond to a card field will be ignored instead of throwing.
--Examples-- --Examples--
> # Create a new card > # Create a new card
+2 -2
View File
@@ -43,10 +43,10 @@ Fields are part of the [[file:style triangle]]:
| @card list name@ [[type:localized string]] field name Alternate name to use for the card list, for example an abbreviation. | @card list name@ [[type:localized string]] field name Alternate name to use for the card list, for example an abbreviation.
| @card list alignment@ [[type:alignment]] @left@ Alignment of the card list column. | @card list alignment@ [[type:alignment]] @left@ Alignment of the card list column.
| @sort script@ [[type:script]] Alternate way to sort the card list when using this column to sort the list. | @sort script@ [[type:script]] Alternate way to sort the card list when using this column to sort the list.
| @construction script@ [[type:script]] Script applied to the value given when creating a card with the new_card function. | @import script@ [[type:script]] Script applied to the value given when creating a card with the new_card function. The script may return a map from field names to values.
For example, the pt field should not be initialized directly, since it is a combination of the power field and toughness field. For example, the pt field should not be initialized directly, since it is a combination of the power field and toughness field.
So if a value is given for pt, it must be redirected to power and toughness like so: {split := split_text(value, match:"/"); [power:split[0], toughness:split[1]]}. So if a value is given for pt, it must be redirected to power and toughness like so: {split := split_text(value, match:"/"); [power:split[0], toughness:split[1]]}.
The script must return a map from field names to values. Use the make_map function to dynamically create maps. Use the make_map function to dynamically create maps.
The @type@ determines what values of this field contain: The @type@ determines what values of this field contain:
! Type Values contain Displayed as ! Type Values contain Displayed as
+1 -1
View File
@@ -33,7 +33,7 @@ Such a package contains a [[file:format|data file]] called <tt>game</tt> that ha
| @default set style@ [[type:indexmap]] of [[type:style]]s Default style for the set fields, can be overridden by the stylesheet. | @default set style@ [[type:indexmap]] of [[type:style]]s Default style for the set fields, can be overridden by the stylesheet.
| @card fields@ [[type:list]] of [[type:field]]s Fields for each card. | @card fields@ [[type:list]] of [[type:field]]s Fields for each card.
| @card list color script@ [[type:script]] from fields Script that determines the color of an item in the card list. <br/>If not set uses the @card list colors@ property of the first card field that has it. | @card list color script@ [[type:script]] from fields Script that determines the color of an item in the card list. <br/>If not set uses the @card list colors@ property of the first card field that has it.
| @construction script@ [[type:script]] Script that is applied as the last step in the creation of a card in the new_card function. Must return a map from field names to values. Use the make_map function to dynamically create maps. | @import script@ [[type:script]] Script that is applied as the last step in the creation of a card in the new_card function. Must return a map from field names to values. Use the make_map function to dynamically create maps.
| @statistics dimensions@ [[type:list]] of [[type:statistics dimension]]s from fields Dimensions for statistics, a dimension is roughly the same as an axis. <br/>By default all card fields with 'show statistics' set to true are used. | @statistics dimensions@ [[type:list]] of [[type:statistics dimension]]s from fields Dimensions for statistics, a dimension is roughly the same as an axis. <br/>By default all card fields with 'show statistics' set to true are used.
| @statistics categories@ [[type:list]] of [[type:statistics category]]s from dimensions DOC_MSE_VERSION: not used since 0.3.6 | @statistics categories@ [[type:list]] of [[type:statistics category]]s from dimensions DOC_MSE_VERSION: not used since 0.3.6
Choices shown on the statistics panel. <br/>By default all statistics dimensions are used. Choices shown on the statistics panel. <br/>By default all statistics dimensions are used.
+1 -1
View File
@@ -61,5 +61,5 @@ To translate the MSE user interface:
* Add new keys for game, stylesheet or symbol font specific keys as described above. * Add new keys for game, stylesheet or symbol font specific keys as described above.
* Save the file, select the new locale from Edit->Preferences. * Save the file, select the new locale from Edit->Preferences.
* Restart MSE, and make sure everything looks right. * Restart MSE, and make sure everything looks right.
* Submit the new locale to the [[http://magicseteditor.sourceforge.net/forum/7|MSE forum]. * Submit the new locale as a pull request to the [[https://github.com/MagicSetEditorPacks/Full-Magic-Pack|Full-Magic-Pack repository].
* Maintain the locale when new versions of MSE come out. A new version may have new user interface items and therefore new keys. * Maintain the locale when new versions of MSE come out. A new version may have new user interface items and therefore new keys.
+2 -2
View File
@@ -40,7 +40,7 @@ Field::~Field() {}
void Field::initDependencies(Context& ctx, const Dependency& dep) const { void Field::initDependencies(Context& ctx, const Dependency& dep) const {
sort_script.initDependencies(ctx, dep); sort_script.initDependencies(ctx, dep);
construction_script.initDependencies(ctx, dep); import_script.initDependencies(ctx, dep);
} }
IMPLEMENT_REFLECTION(Field) { IMPLEMENT_REFLECTION(Field) {
@@ -64,7 +64,7 @@ IMPLEMENT_REFLECTION(Field) {
REFLECT(card_list_allow); REFLECT(card_list_allow);
REFLECT_LOCALIZED(card_list_name); REFLECT_LOCALIZED(card_list_name);
REFLECT(sort_script); REFLECT(sort_script);
REFLECT(construction_script); REFLECT(import_script);
REFLECT_N("card_list_alignment", card_list_align); REFLECT_N("card_list_alignment", card_list_align);
} }
+1 -1
View File
@@ -60,7 +60,7 @@ public:
LocalizedString card_list_name; ///< Name to use in card list. LocalizedString card_list_name; ///< Name to use in card list.
Alignment card_list_align; ///< Alignment of the card list colummn. Alignment card_list_align; ///< Alignment of the card list colummn.
OptionalScript sort_script; ///< The script to use when sorting this, if not the value. OptionalScript sort_script; ///< The script to use when sorting this, if not the value.
OptionalScript construction_script; ///< The script to apply to the supplied value, when creating a new card. OptionalScript import_script; ///< The script to apply to the supplied value, when creating a new card.
Dependencies dependent_scripts; ///< Scripts that depend on values of this field Dependencies dependent_scripts; ///< Scripts that depend on values of this field
String package_relative_filename; String package_relative_filename;
+1 -1
View File
@@ -51,7 +51,7 @@ SetP MSE1FileFormat::importSet(const String& filename) {
// file version check // file version check
String format = file.ReadLine(); String format = file.ReadLine();
if (format.substr(0,8) != _("MTG Set8")) { if (format.substr(0,8) != _("MTG Set8")) {
throw ParseError(_("Expected MSE format version 8\nTo convert files made with older versions of Magic Set Editor:\n 1. Download the latest version 1 from http:://magicsetedtitor.sourceforge.net\n 2. Open the set, then save the set\n 3. Try to open them again in this program.")); throw ParseError(_("Expected MSE format version 8\nTo convert files made with older versions of Magic Set Editor:\n 1. Download the latest version 1 from https://magicseteditor.boards.net/page/downloads\n 2. Open the set, then save the set\n 3. Try to open them again in this program."));
} }
// read general info // read general info
set->value<TextValue>(_("title")) .value = file.ReadLine(); set->value<TextValue>(_("title")) .value = file.ReadLine();
+2 -1
View File
@@ -49,7 +49,8 @@ IMPLEMENT_REFLECTION(Game) {
REFLECT_NO_SCRIPT(default_set_style); REFLECT_NO_SCRIPT(default_set_style);
REFLECT_NO_SCRIPT(card_fields); REFLECT_NO_SCRIPT(card_fields);
REFLECT_NO_SCRIPT(card_list_color_script); REFLECT_NO_SCRIPT(card_list_color_script);
REFLECT_NO_SCRIPT(construction_script); REFLECT_NO_SCRIPT(import_script);
REFLECT_NO_SCRIPT(json_paths);
REFLECT_NO_SCRIPT(statistics_dimensions); REFLECT_NO_SCRIPT(statistics_dimensions);
REFLECT_NO_SCRIPT(statistics_categories); REFLECT_NO_SCRIPT(statistics_categories);
REFLECT_COMPAT(<308, "pack_item", pack_types); REFLECT_COMPAT(<308, "pack_item", pack_types);
+2 -1
View File
@@ -42,7 +42,8 @@ public:
IndexMap<FieldP,StyleP> default_set_style; ///< Default style for the set fields, because it is often the same IndexMap<FieldP,StyleP> default_set_style; ///< Default style for the set fields, because it is often the same
vector<FieldP> card_fields; ///< Fields on each card vector<FieldP> card_fields; ///< Fields on each card
OptionalScript card_list_color_script; ///< Script that determines the color of items in the card list OptionalScript card_list_color_script; ///< Script that determines the color of items in the card list
OptionalScript construction_script; ///< Script applied as the last step of the new_card function OptionalScript import_script; ///< Script applied as the last step of the new_card function
vector<String> json_paths; ///< Paths inside JSON files to find the card array
vector<StatsDimensionP> statistics_dimensions; ///< (Additional) statistics dimensions vector<StatsDimensionP> statistics_dimensions; ///< (Additional) statistics dimensions
vector<StatsCategoryP> statistics_categories; ///< (Additional) statistics categories vector<StatsCategoryP> statistics_categories; ///< (Additional) statistics categories
vector<PackTypeP> pack_types; ///< Types of random card packs to generate vector<PackTypeP> pack_types; ///< Types of random card packs to generate
+4 -4
View File
@@ -178,13 +178,13 @@ Settings::Settings()
, internal_scale (1.0) , internal_scale (1.0)
, internal_image_extension(true) , internal_image_extension(true)
#if USE_OLD_STYLE_UPDATE_CHECKER #if USE_OLD_STYLE_UPDATE_CHECKER
, updates_url (_("http://magicseteditor.sourceforge.net/updates")) , updates_url (_("https://magicseteditor.boards.net/page/downloads"))
#endif #endif
, package_versions_url (_("http://magicseteditor.sourceforge.net/packages")) , package_versions_url (_("https://magicseteditor.boards.net/page/downloads"))
, installer_list_url (_("http://magicseteditor.sourceforge.net/installers")) , installer_list_url (_("https://magicseteditor.boards.net/page/downloads"))
, check_updates (CHECK_IF_CONNECTED) , check_updates (CHECK_IF_CONNECTED)
, check_updates_all (true) , check_updates_all (true)
, website_url (_("http://magicseteditor.sourceforge.net/")) , website_url (_("https://magicseteditor.boards.net/"))
, install_type (INSTALL_DEFAULT) , install_type (INSTALL_DEFAULT)
{} {}
+1 -1
View File
@@ -33,7 +33,7 @@ void AboutWindow::onPaint(wxPaintEvent& ev) {
draw(dc); draw(dc);
} }
const char* MSE_AUTHORS[] = {"Twan van Laarhoven (twanvl)", "Sean Hunt (coppro)", "Alissa Rao (Lymia)", "haganbmj", "CaiCai (247321453)"}; const char* MSE_AUTHORS[] = {"Twan van Laarhoven (twanvl)", "Sean Hunt (coppro)", "Alissa Rao (Lymia)", "haganbmj", "CaiCai (247321453)", "Olivier Bocksberger (GenevensiS)" };
void AboutWindow::draw(DC& dc) { void AboutWindow::draw(DC& dc) {
wxSize ws = GetClientSize(); wxSize ws = GetClientSize();
+78 -61
View File
@@ -10,13 +10,14 @@
#include <sstream> #include <sstream>
#include <regex> #include <regex>
#include <util/prec.hpp> #include <util/prec.hpp>
#include <util/window_id.hpp>
#include <data/game.hpp> #include <data/game.hpp>
#include <data/set.hpp> #include <data/set.hpp>
#include <data/card.hpp> #include <data/card.hpp>
#include <data/stylesheet.hpp> #include <data/stylesheet.hpp>
#include <gui/add_csv_window.hpp>
#include <util/window_id.hpp>
#include <data/action/set.hpp> #include <data/action/set.hpp>
#include <gui/add_csv_window.hpp>
#include <script/functions/construction_helper.hpp>
#include <wx/statline.h> #include <wx/statline.h>
// ----------------------------------------------------------------------------- : AddCSV // ----------------------------------------------------------------------------- : AddCSV
@@ -32,6 +33,7 @@ AddCSVWindow::AddCSVWindow(Window* parent, const SetP& set, bool sizer)
separator_type->Clear(); separator_type->Clear();
separator_type->Append(_LABEL_("add card csv tab")); separator_type->Append(_LABEL_("add card csv tab"));
separator_type->Append(_LABEL_("add card csv comma")); separator_type->Append(_LABEL_("add card csv comma"));
separator_type->Append(_LABEL_("add card csv semicolon"));
separator_type->SetSelection(0); separator_type->SetSelection(0);
setSeparatorType(); setSeparatorType();
// init sizers // init sizers
@@ -39,7 +41,7 @@ AddCSVWindow::AddCSVWindow(Window* parent, const SetP& set, bool sizer)
wxSizer* s = new wxBoxSizer(wxVERTICAL); wxSizer* s = new wxBoxSizer(wxVERTICAL);
s->Add(new wxStaticText(this, -1, _LABEL_("add card csv sep")), 0, wxALL, 8); s->Add(new wxStaticText(this, -1, _LABEL_("add card csv sep")), 0, wxALL, 8);
s->Add(separator_type, 0, wxEXPAND | (wxALL & ~wxTOP), 8); s->Add(separator_type, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
s->Add(new wxStaticText(this, -1, _(" ") + _LABEL_("add card csv file")), 0, wxALL | (wxALL & ~wxTOP), 8); s->Add(new wxStaticText(this, -1, _(" ") + _LABEL_("add card csv file")), 0, wxALL, 8);
s->Add(file_path, 0, wxEXPAND | (wxALL & ~wxTOP), 8); s->Add(file_path, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
wxSizer* s2 = new wxBoxSizer(wxHORIZONTAL); wxSizer* s2 = new wxBoxSizer(wxHORIZONTAL);
s2->Add(file_browse, 0, wxEXPAND | wxRIGHT, 8); s2->Add(file_browse, 0, wxEXPAND | wxRIGHT, 8);
@@ -55,7 +57,8 @@ AddCSVWindow::AddCSVWindow(Window* parent, const SetP& set, bool sizer)
void AddCSVWindow::setSeparatorType() { void AddCSVWindow::setSeparatorType() {
int sel = separator_type->GetSelection(); int sel = separator_type->GetSelection();
if (sel == 0) separator = ' '; if (sel == 0) separator = ' ';
else separator = ','; else if (sel == 1) separator = ',';
else separator = ';';
} }
void AddCSVWindow::onSeparatorTypeChange(wxCommandEvent&) { void AddCSVWindow::onSeparatorTypeChange(wxCommandEvent&) {
@@ -109,73 +112,87 @@ std::vector<std::string> AddCSVWindow::readCSVRow(const std::string& row) {
break; break;
} }
} }
// escape " { }
for (f = 0; f < fields.size(); ++f) {
fields[f] = std::regex_replace(fields[f], std::regex("\""), "\\\"");
fields[f] = std::regex_replace(fields[f], std::regex("\\{"), "\\{");
fields[f] = std::regex_replace(fields[f], std::regex("\\}"), "\\}");
}
return fields; return fields;
} }
bool AddCSVWindow::readCSV(std::ifstream& in, std::vector<String> headers_out, std::vector<std::vector<ScriptValueP>>& table_out) {
// Get the rows
vector<std::string> raw_rows;
std::string raw_row;
while (std::getline(in, raw_row)) {
raw_rows.push_back(raw_row);
}
// Check for quoted new line characters
vector<std::string> rows;
std::string row = "";
for (int y = 0; y < raw_rows.size(); ++y) {
row = row + raw_rows[y];
int quote_count = 0;
std::string::size_type pos = 0;
while ((pos = row.find("\"", pos)) != std::string::npos) {
++quote_count;
++pos;
}
if (quote_count % 2 == 0) {
if (row.find_first_not_of(' ') != std::string::npos) rows.push_back(row);
row = "";
} else {
row = row + "\n";
}
}
if (rows.size() == 0) {
queue_message(MESSAGE_ERROR, _ERROR_1_("import empty file", _("CSV / TSV")));
return false;
}
// Parse headers
std::vector<std::string> headers = readCSVRow(rows[0]);
for (int x = 0; x < headers.size(); ++x) {
String wxstring(headers[x].c_str(), wxConvUTF8);
headers_out.push_back(wxstring);
}
// Parse rows, add to table
for (int y = 0; y < rows.size(); ++y) {
auto fields = readCSVRow(rows[y]);
std::vector<ScriptValueP> values;
for (int x = 0; x < fields.size(); ++x) {
String wxstring(fields[x].c_str(), wxConvUTF8);
values.push_back(to_script(wxstring));
}
table_out.push_back(values);
}
return true;
}
void AddCSVWindow::onOk(wxCommandEvent&) { void AddCSVWindow::onOk(wxCommandEvent&) {
/// Perform the import /// Perform the import
// Read the file, put it into a table wxBusyCursor wait;
auto in = std::ifstream(file_path->GetValue().ToStdString()); // Read the file
if (in.fail()) { auto file = std::ifstream(file_path->GetValue().ToStdString());
if (file.fail()) {
queue_message(MESSAGE_ERROR, _ERROR_("add card csv file not found")); queue_message(MESSAGE_ERROR, _ERROR_("add card csv file not found"));
EndModal(wxID_ABORT); EndModal(wxID_ABORT);
return; return;
} }
std::vector<std::vector<std::string>> table; // Put values into a table
std::string row; std::vector<String> headers;
while (!in.eof()) { std::vector<std::vector<ScriptValueP>> table;
std::getline(in, row); if (!readCSV(file, headers, table)) {
if (in.bad() || in.fail()) { EndModal(wxID_ABORT);
break; return;
}
auto fields = readCSVRow(row);
table.push_back(fields);
} }
// ensure table is square // Check for missing fields
int count = table[0].size(); String missing_fields;
for (int y = 1; y < table.size(); ++y) { check_table_headers(set->game, headers, _("CSV / TSV"), missing_fields);
if (table[y].size() != count) { if (missing_fields.size() > 0) {
queue_message(MESSAGE_ERROR, _ERROR_1_("add card csv file malformed", wxString::Format(wxT("%i"), y+1))); queue_message(MESSAGE_WARNING, _ERROR_2_("import missing fields", _("CSV / TSV"), missing_fields));
EndModal(wxID_ABORT);
return;
}
} }
// produce script from table // Produce cards from the table
std::ostringstream stream;
stream << "[";
for (int y = 1; y < table.size(); ++y) {
stream << "new_card([";
for (int x = 0; x < count; ++x) {
stream << "\"" << table[0][x] << "\": \"" << table[y][x] << "\"";
if (x < count - 1) stream << ",";
}
stream << "])";
if (y < table.size() - 1) stream << ",";
}
stream << "]";
String string(stream.str().c_str(), wxConvUTF8);
ScriptP script = parse(string, nullptr, false);
Context& ctx = set->getContext();
ScriptValueP result = script->eval(ctx, false);
// create cards
vector<CardP> cards; vector<CardP> cards;
ScriptValueP it = result->makeIterator(); if (!cards_from_table(set, headers, table, true, _("CSV / TSV"), cards)) {
while (ScriptValueP item = it->next()) { EndModal(wxID_ABORT);
CardP card = from_script<CardP>(item); return;
// is this a new card?
if (contains(set->cards, card) || contains(cards, card)) {
// make copy
card = make_intrusive<Card>(*card);
}
cards.push_back(card);
} }
// add to set // Add cards to set
if (!cards.empty()) { if (!cards.empty()) {
// TODO: change the name of the action somehow // TODO: change the name of the action somehow
set->actions.addAction(make_unique<AddCardAction>(ADD, *set, cards)); set->actions.addAction(make_unique<AddCardAction>(ADD, *set, cards));
@@ -185,7 +202,7 @@ void AddCSVWindow::onOk(wxCommandEvent&) {
} }
BEGIN_EVENT_TABLE(AddCSVWindow, wxDialog) BEGIN_EVENT_TABLE(AddCSVWindow, wxDialog)
EVT_BUTTON(wxID_OK, AddCSVWindow::onOk) EVT_BUTTON(wxID_OK, AddCSVWindow::onOk)
EVT_BUTTON(ID_CARD_ADD_CSV_BROWSE, AddCSVWindow::onBrowseFiles) EVT_BUTTON(ID_CARD_ADD_CSV_BROWSE, AddCSVWindow::onBrowseFiles)
EVT_CHOICE(ID_CARD_ADD_CSV_SEP, AddCSVWindow::onSeparatorTypeChange) EVT_CHOICE(ID_CARD_ADD_CSV_SEP, AddCSVWindow::onSeparatorTypeChange)
END_EVENT_TABLE() END_EVENT_TABLE()
+2 -2
View File
@@ -28,11 +28,11 @@ protected:
SetP set; SetP set;
char separator; char separator;
bool readCSV(std::ifstream& in, std::vector<String> headers_out, std::vector<std::vector<ScriptValueP>>& table_out);
std::vector<std::string> readCSVRow(const std::string& row); std::vector<std::string> readCSVRow(const std::string& row);
void setSeparatorType();
void onSeparatorTypeChange(wxCommandEvent&); void onSeparatorTypeChange(wxCommandEvent&);
void setSeparatorType();
void onBrowseFiles(wxCommandEvent&); void onBrowseFiles(wxCommandEvent&);
+262
View File
@@ -0,0 +1,262 @@
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
// ----------------------------------------------------------------------------- : Includes
#include <iomanip>
#include <iostream>
#include <fstream>
#include <sstream>
#include <regex>
#include <util/prec.hpp>
#include <util/window_id.hpp>
#include <data/game.hpp>
#include <data/set.hpp>
#include <data/card.hpp>
#include <data/stylesheet.hpp>
#include <data/action/set.hpp>
#include <gui/add_json_window.hpp>
#include <script/functions/construction_helper.hpp>
#include <wx/statline.h>
#include <boost/json/src.hpp>
#include <boost/json.hpp>
// ----------------------------------------------------------------------------- : AddJSON
AddJSONWindow::AddJSONWindow(Window* parent, const SetP& set, bool sizer)
: wxDialog(parent, wxID_ANY, _TITLE_("add card json"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
, set(set)
{
// init controls
file_path = new wxTextCtrl(this, wxID_ANY, wxEmptyString);
file_browse = new wxButton(this, ID_CARD_ADD_JSON_BROWSE, _BUTTON_("browse"));
json_type = new wxChoice(this, ID_CARD_ADD_JSON_ARRAY, wxDefaultPosition, wxDefaultSize, 0, nullptr);
json_type->Clear();
FOR_EACH(type, set->game->json_paths) {
int delimiter_pos = type.find("//");
json_type->Append(type.substr(0, delimiter_pos).Trim().Trim(false));
}
json_type->Append(_LABEL_("add card json custom"));
json_type->SetSelection(0);
card_array_path = new wxTextCtrl(this, wxID_ANY, wxEmptyString);
setJSONType();
// init sizers
if (sizer) {
wxSizer* s = new wxBoxSizer(wxVERTICAL);
s->Add(new wxStaticText(this, -1, _LABEL_("add card json type")), 0, wxALL, 8);
s->Add(json_type, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
s->Add(new wxStaticText(this, -1, _LABEL_("add card json path")), 0, wxALL & ~wxTOP, 8);
s->Add(card_array_path, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
s->Add(new wxStaticText(this, -1, _LABEL_("add card json file")), 0, wxALL, 8);
s->Add(file_path, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
wxSizer* s2 = new wxBoxSizer(wxHORIZONTAL);
s2->Add(file_browse, 0, wxEXPAND | wxRIGHT, 8);
s2->Add(CreateButtonSizer(wxOK | wxCANCEL), 1, wxEXPAND, 8);
s->Add(s2, 0, wxEXPAND | wxALL, 12);
file_browse->SetFocus();
s->SetSizeHints(this);
SetSizer(s);
SetSize(500, 110);
}
}
static ScriptValueP json_to_script(boost::json::value jv) {
if (jv == nullptr) return script_nil;
else if (jv.is_null()) return script_nil;
else if (jv.is_bool()) return to_script(jv.get_bool());
else if (jv.is_double()) return to_script(jv.get_double());
else if (jv.is_int64()) {
int integer = jv.get_int64();
return to_script(integer);
}
else if (jv.is_uint64()) {
int integer = jv.get_uint64();
return to_script(integer);
}
else if (jv.is_string()) {
std::string stdstring = boost::json::value_to<std::string>(jv);
String wxstring(stdstring.c_str(), wxConvUTF8);
return to_script(wxstring);
}
else if (jv.is_array()) {
boost::json::array array = jv.get_array();
ScriptCustomCollectionP result = make_intrusive<ScriptCustomCollection>();
for (int i = 0; i < array.size(); ++i) {
boost::json::value jvalue = array[i];
ScriptValueP value = json_to_script(jvalue);
result->value.push_back(value);
}
return result;
}
else if (jv.is_object()) {
boost::json::object object = jv.get_object();
ScriptCustomCollectionP result = make_intrusive<ScriptCustomCollection>();
for (auto it = object.begin(); it != object.end(); ++it) {
boost::json::string_view jview = it->key();
std::string_view stdview = std::string_view(jview.data(), jview.size());
std::string stdstring = { stdview.begin(), stdview.end() };
String key(stdstring.c_str(), wxConvUTF8);
boost::json::value jvalue = it->value();
ScriptValueP value = json_to_script(jvalue);
result->key_value[key] = value;
}
return result;
}
else {
queue_message(MESSAGE_ERROR, _ERROR_("add card json unknown type"));
return script_nil;
}
}
void AddJSONWindow::setJSONType() {
int sel = json_type->GetSelection();
if (sel == json_type->GetCount() - 1) { // Custom type
card_array_path->ChangeValue(wxEmptyString);
card_array_path->Enable();
}
else {
String selection = json_type->GetString(sel);
FOR_EACH(type, set->game->json_paths) {
if (type.starts_with(selection)) {
int delimiter_pos = type.find("//");
card_array_path->ChangeValue(delimiter_pos + 2 < type.Length() ? type.substr(delimiter_pos + 2).Trim().Trim(false) : wxEmptyString);
card_array_path->Enable(false);
break;
}
}
}
}
void AddJSONWindow::onJSONTypeChange(wxCommandEvent&) {
setJSONType();
}
void AddJSONWindow::onBrowseFiles(wxCommandEvent&) {
wxFileDialog* dlg = new wxFileDialog(this, _TITLE_("add card json file"), settings.default_set_dir, wxEmptyString, _("JSON files|*.json|All files (*.*)|*"), wxFD_OPEN);
if (dlg->ShowModal() == wxID_OK) {
file_path->SetValue(dlg->GetPath());
}
}
bool AddJSONWindow::readJSON(std::ifstream& in, std::vector<String>& headers_out, std::vector<std::vector<ScriptValueP>>& table_out) {
// Read file
std::string input(std::istreambuf_iterator<char>(in), {});
boost::json::value jv;
try {
jv = boost::json::parse(input);
} catch (...) {
queue_message(MESSAGE_ERROR, _ERROR_("add card json failed to parse"));
return false;
}
// Split path into tokens
std::string path = card_array_path->GetValue().ToStdString();
std::vector<std::string> tokens;
size_t pos = 0;
std::string token;
while ((pos = path.find(":")) != std::string::npos) {
token = path.substr(0, pos);
tokens.push_back(token);
path.erase(0, pos + 1);
}
tokens.push_back(path);
// Navigate path to card array
for (int t = 0; t < tokens.size(); ++t) {
if (tokens[t] == "") break;
if (!jv.is_object()) {
queue_message(MESSAGE_ERROR, _ERROR_("add card json path not valid"));
return false;
}
auto& ov = jv.as_object();
jv = ov[tokens[t]];
if (jv == nullptr) {
queue_message(MESSAGE_ERROR, _ERROR_("add card json path not valid"));
return false;
}
}
if (!jv.is_array()) {
queue_message(MESSAGE_ERROR, _ERROR_("add card json path not valid"));
return false;
}
auto& card_array = jv.as_array();
size_t count = card_array.size();
if (count == 0) {
queue_message(MESSAGE_ERROR, _ERROR_("add card json empty array"));
return false;
}
auto& card = card_array[0];
if (!card.is_object()) {
queue_message(MESSAGE_ERROR, _ERROR_("add card json path not valid"));
return false;
}
// Get headers
for (int i = 0; i < count; ++i) {
auto& card = card_array[i].as_object();
for (auto it = card.begin(); it != card.end(); ++it) {
boost::json::string_view jview = it->key();
std::string_view stdview = std::string_view(jview.data(), jview.size());
std::string stdstring = { stdview.begin(), stdview.end() };
String key(stdstring.c_str(), wxConvUTF8);
if (std::find(headers_out.begin(), headers_out.end(), key) == headers_out.end()) {
headers_out.push_back(key);
}
}
}
// Parse cards, add to table
for (int i = 0; i < count; ++i) {
std::vector<ScriptValueP> row;
auto& card = card_array[i].as_object();
for (int h = 0; h < headers_out.size(); ++h) {
auto& value = card[headers_out[h].ToStdString()];
row.push_back(json_to_script(value));
}
table_out.push_back(row);
}
return true;
}
void AddJSONWindow::onOk(wxCommandEvent&) {
/// Perform the import
wxBusyCursor wait;
// Read the file
auto file = std::ifstream(file_path->GetValue().ToStdString());
if (file.fail()) {
queue_message(MESSAGE_ERROR, _ERROR_("add card json file not found"));
EndModal(wxID_ABORT);
return;
}
// Put values into a table
std::vector<String> headers;
std::vector<std::vector<ScriptValueP>> table;
if (!readJSON(file, headers, table)) {
EndModal(wxID_ABORT);
return;
}
// Check for missing fields
String missing_fields;
check_table_headers(set->game, headers, _("JSON"), missing_fields);
if (missing_fields.size() > 0) {
queue_message(MESSAGE_WARNING, _ERROR_2_("import missing fields", _("JSON"), missing_fields));
}
// Produce cards from the table
vector<CardP> cards;
if (!cards_from_table(set, headers, table, true, _("JSON"), cards)) {
EndModal(wxID_ABORT);
return;
}
// Add cards to set
if (!cards.empty()) {
// TODO: change the name of the action somehow
set->actions.addAction(make_unique<AddCardAction>(ADD, *set, cards));
}
// Done
EndModal(wxID_OK);
}
BEGIN_EVENT_TABLE(AddJSONWindow, wxDialog)
EVT_BUTTON(wxID_OK, AddJSONWindow::onOk)
EVT_BUTTON(ID_CARD_ADD_JSON_BROWSE, AddJSONWindow::onBrowseFiles)
EVT_CHOICE(ID_CARD_ADD_JSON_ARRAY, AddJSONWindow::onJSONTypeChange)
END_EVENT_TABLE()
+39
View File
@@ -0,0 +1,39 @@
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
#pragma once
// ----------------------------------------------------------------------------- : Includes
#include <util/prec.hpp>
DECLARE_POINTER_TYPE(Set);
// ----------------------------------------------------------------------------- : AddJSONWindow
/// A window for selecting a JSON file, and importing cards from it.
class AddJSONWindow : public wxDialog {
public:
AddJSONWindow(Window* parent, const SetP& set, bool sizer);
protected:
DECLARE_EVENT_TABLE();
wxChoice* json_type;
wxTextCtrl* card_array_path, *file_path;
wxButton* file_browse;
SetP set;
bool readJSON(std::ifstream& in, std::vector<String>& headers_out, std::vector<std::vector<ScriptValueP>>& table_out);
void onJSONTypeChange(wxCommandEvent&);
void setJSONType();
void onBrowseFiles(wxCommandEvent&);
void onOk(wxCommandEvent&);
};
+11 -1
View File
@@ -11,7 +11,8 @@
#include <gui/control/card_list_column_select.hpp> #include <gui/control/card_list_column_select.hpp>
#include <gui/set/window.hpp> // for sorting all cardlists in a window #include <gui/set/window.hpp> // for sorting all cardlists in a window
#include <gui/util.hpp> #include <gui/util.hpp>
#include <gui/add_csv_window.hpp> #include <gui/add_csv_window.hpp>
#include <gui/add_json_window.hpp>
#include <data/game.hpp> #include <data/game.hpp>
#include <data/field.hpp> #include <data/field.hpp>
#include <data/field/choice.hpp> #include <data/field/choice.hpp>
@@ -190,6 +191,15 @@ bool CardListBase::doAddCSV() {
} }
return false; return false;
} }
bool CardListBase::doAddJSON() {
AddJSONWindow wnd(this, set, true);
if (wnd.ShowModal() == wxID_OK) {
// The actual adding is done in this window's onOk function
return true;
}
return false;
}
// ----------------------------------------------------------------------------- : CardListBase : Building the list // ----------------------------------------------------------------------------- : CardListBase : Building the list
+2 -1
View File
@@ -77,7 +77,8 @@ public:
bool doCopy() override; bool doCopy() override;
bool doPaste() override; bool doPaste() override;
bool doDelete() override; bool doDelete() override;
bool doAddCSV(); bool doAddCSV();
bool doAddJSON();
// --------------------------------------------------- : Set actions // --------------------------------------------------- : Set actions
+39 -6
View File
@@ -39,7 +39,7 @@ CardsPanel::CardsPanel(Window* parent, int id)
notes = new TextCtrl(nodes_panel, ID_NOTES, true); notes = new TextCtrl(nodes_panel, ID_NOTES, true);
collapse_notes = new HoverButton(nodes_panel, ID_COLLAPSE_NOTES, _("btn_collapse"), Color(), false); collapse_notes = new HoverButton(nodes_panel, ID_COLLAPSE_NOTES, _("btn_collapse"), Color(), false);
collapse_notes->SetExtraStyle(wxWS_EX_PROCESS_UI_UPDATES); collapse_notes->SetExtraStyle(wxWS_EX_PROCESS_UI_UPDATES);
filter = nullptr; filter = nullptr;
editor->next_in_tab_order = card_list; editor->next_in_tab_order = card_list;
// init sizer for notes panel // init sizer for notes panel
wxSizer* sn = new wxBoxSizer(wxVERTICAL); wxSizer* sn = new wxBoxSizer(wxVERTICAL);
@@ -69,12 +69,13 @@ CardsPanel::CardsPanel(Window* parent, int id)
add_menu_item_tr(menuCard, ID_CARD_NEXT, nullptr, "next card"); add_menu_item_tr(menuCard, ID_CARD_NEXT, nullptr, "next card");
add_menu_item_tr(menuCard, ID_CARD_SEARCH, nullptr, "search cards"); add_menu_item_tr(menuCard, ID_CARD_SEARCH, nullptr, "search cards");
menuCard->AppendSeparator(); menuCard->AppendSeparator();
add_menu_item_tr(menuCard, ID_CARD_ADD, "card_add", "add_card");
insertManyCardsMenu = add_menu_item_tr(menuCard, ID_CARD_ADD_MULT, "card_add_multiple", "add cards"); insertManyCardsMenu = add_menu_item_tr(menuCard, ID_CARD_ADD_MULT, "card_add_multiple", "add cards");
// NOTE: space after "Del" prevents wx from making del an accellerator // NOTE: space after "Del" prevents wx from making del an accellerator
// otherwise we delete a card when delete is pressed inside the editor // otherwise we delete a card when delete is pressed inside the editor
// Adding a space never hurts, please keep it just to be safe. // Adding a space never hurts, please keep it just to be safe.
add_menu_item(menuCard, ID_CARD_ADD_CSV, "card_add_multiple", _MENU_("add card csv") + _(" "), _HELP_("add card csv")); add_menu_item(menuCard, ID_CARD_ADD_CSV, "card_add_multiple", _MENU_("add card csv") + _(" "), _HELP_("add card csv"));
add_menu_item(menuCard, ID_CARD_ADD_JSON, "card_add_multiple", _MENU_("add card json") + _(" "), _HELP_("add card json"));
add_menu_item_tr(menuCard, ID_CARD_ADD, "card_add", "add_card");
add_menu_item(menuCard, ID_CARD_REMOVE, "card_del", _MENU_("remove card")+_(" "), _HELP_("remove card")); add_menu_item(menuCard, ID_CARD_REMOVE, "card_del", _MENU_("remove card")+_(" "), _HELP_("remove card"));
menuCard->AppendSeparator(); menuCard->AppendSeparator();
auto menuRotate = new wxMenu(); auto menuRotate = new wxMenu();
@@ -98,7 +99,31 @@ CardsPanel::CardsPanel(Window* parent, int id)
menuFormat->Append(insertSymbolMenu); menuFormat->Append(insertSymbolMenu);
toolAddCard = nullptr; toolAddCard = nullptr;
} }
void CardsPanel::updateCardCounts() {
if (counts) {
if (card_list && set) {
int selected = card_list->GetSelectedItemCount();
int filtered = card_list->GetItemCount();
int total = set->cards.size();
if (filtered == total) {
counts->SetLabel(_TOOL_2_("card counts 2",
wxString::Format(wxT("%i"), selected),
wxString::Format(wxT("%i"), total)));
}
else {
counts->SetLabel(_TOOL_3_("card counts 3",
wxString::Format(wxT("%i"), selected),
wxString::Format(wxT("%i"), filtered),
wxString::Format(wxT("%i"), total)));
}
} else {
counts->SetLabel(_(""));
}
}
}
void CardsPanel::updateNotesPosition() { void CardsPanel::updateNotesPosition() {
wxSize editor_size = editor->GetBestSize(); wxSize editor_size = editor->GetBestSize();
@@ -205,9 +230,12 @@ void CardsPanel::initUI(wxToolBar* tb, wxMenuBar* mb) {
// Filter/search textbox // Filter/search textbox
tb->AddSeparator(); tb->AddSeparator();
assert(!filter); assert(!filter);
filter = new FilterCtrl(tb, ID_CARD_FILTER, _LABEL_("search cards"), _HELP_("search_cards_control")); filter = new FilterCtrl(tb, ID_CARD_FILTER, _TOOL_("search cards"), _HELP_("search cards control"));
filter->setFilter(filter_value); filter->setFilter(filter_value);
tb->AddControl(filter); tb->AddControl(filter);
counts = new wxStaticText(tb, ID_CARD_COUNTER, _(""));
updateCardCounts();
tb->AddControl(counts);
tb->Realize(); tb->Realize();
// Menus // Menus
mb->Insert(2, menuCard, _MENU_("cards")); mb->Insert(2, menuCard, _MENU_("cards"));
@@ -224,6 +252,7 @@ void CardsPanel::destroyUI(wxToolBar* tb, wxMenuBar* mb) {
tb->DeleteTool(ID_CARD_ADD); tb->DeleteTool(ID_CARD_ADD);
tb->DeleteTool(ID_CARD_REMOVE); tb->DeleteTool(ID_CARD_REMOVE);
tb->DeleteTool(ID_CARD_ROTATE); tb->DeleteTool(ID_CARD_ROTATE);
tb->DeleteTool(ID_CARD_COUNTER);
// remember the value in the filter control, because the card list remains filtered // remember the value in the filter control, because the card list remains filtered
// the control is destroyed by DeleteTool // the control is destroyed by DeleteTool
filter_value = filter->getFilterString(); filter_value = filter->getFilterString();
@@ -282,7 +311,8 @@ void CardsPanel::onUpdateUI(wxUpdateUIEvent& ev) {
break; break;
} }
#endif #endif
} }
updateCardCounts();
} }
void CardsPanel::onMenuOpen(wxMenuEvent& ev) { void CardsPanel::onMenuOpen(wxMenuEvent& ev) {
@@ -315,6 +345,9 @@ void CardsPanel::onCommand(int id) {
case ID_CARD_ADD_CSV: case ID_CARD_ADD_CSV:
card_list->doAddCSV(); card_list->doAddCSV();
break; break;
case ID_CARD_ADD_JSON:
card_list->doAddJSON();
break;
case ID_CARD_REMOVE: case ID_CARD_REMOVE:
card_list->doDelete(); card_list->doDelete();
break; break;
+5 -2
View File
@@ -87,9 +87,12 @@ private:
TextCtrl* notes; TextCtrl* notes;
HoverButton* collapse_notes; HoverButton* collapse_notes;
FilterCtrl* filter; FilterCtrl* filter;
String filter_value; // value of filter, need separate variable because the control is destroyed String filter_value; // value of filter, need separate variable because the control is destroyed
wxStaticText* counts;
bool notes_below_editor; bool notes_below_editor;
/// Update card counts
void updateCardCounts();
/// Move the notes panel below the editor or below the card list /// Move the notes panel below the editor or below the card list
void updateNotesPosition(); void updateNotesPosition();
// before Layout, call updateNotesPosition. // before Layout, call updateNotesPosition.
+50 -92
View File
@@ -9,6 +9,7 @@
#include <util/prec.hpp> #include <util/prec.hpp>
#include <script/functions/functions.hpp> #include <script/functions/functions.hpp>
#include <script/functions/util.hpp> #include <script/functions/util.hpp>
#include <script/functions/construction_helper.hpp>
#include <data/field.hpp> #include <data/field.hpp>
#include <data/field/text.hpp> #include <data/field/text.hpp>
#include <data/field/choice.hpp> #include <data/field/choice.hpp>
@@ -20,72 +21,11 @@
#include <data/card.hpp> #include <data/card.hpp>
#include <util/error.hpp> #include <util/error.hpp>
// ----------------------------------------------------------------------------- : Helper functions
static Value* get_container(GameP& game, CardP& card, String key_name) {
// find value container to update
IndexMap<FieldP, ValueP>::const_iterator value_it = card->data.find(key_name);
if (value_it == card->data.end()) {
// look among alternate names
map<String, String>::iterator alt_name_it = game->card_fields_alt_names.find(unified_form(key_name));
if (alt_name_it != game->card_fields_alt_names.end()) {
value_it = card->data.find(alt_name_it->second);
}
}
if (value_it == card->data.end()) {
throw ScriptError(_ERROR_1_("no field with name", key_name));
}
return value_it->get();
}
static void set_container(Value* container, ScriptValueP& value, String key_name) {
// set the given value into the container
if (TextValue* tvalue = dynamic_cast<TextValue*>(container)) {
tvalue->value = value->toString();
}
else if (ChoiceValue* cvalue = dynamic_cast<ChoiceValue*>(container)) {
cvalue->value = value->toString();
}
else if (PackageChoiceValue* pvalue = dynamic_cast<PackageChoiceValue*>(container)) {
pvalue->package_name = value->toString();
}
else if (ColorValue* cvalue = dynamic_cast<ColorValue*>(container)) {
cvalue->value = value->toColor();
}
else if (ImageValue* ivalue = dynamic_cast<ImageValue*>(container)) {
wxFileName fname(static_cast<ExternalImage*>(value.get())->toString());
ivalue->filename = LocalFileName::fromReadString(fname.GetName(), "");
}
else {
throw ScriptError(_ERROR_1_("can't set value", key_name));
}
}
static bool set_builtin_container(GameP& game, CardP& card, ScriptValueP& value, String key_name) {
// check if the given value is for a built-in field, if found set it and return true
key_name = unified_form(key_name);
if (key_name == _("notes") || key_name == _("note")) {
card->notes = value->toString();
return true;
} else if (key_name == _("style") || key_name == _("stylesheet") || key_name == _("template")) {
if (trim(value->toString()) != wxEmptyString) card->stylesheet = StyleSheet::byGameAndName(*game, value->toString());
return true;
}
// else if (key_name == _("id") || key_name == _("multiverse_id")) {
// card->id = value->toString();
// return true;
//}
//styling_data;
//linked_card;
//linked_relation_1;
return false;
}
// ----------------------------------------------------------------------------- : new_card // ----------------------------------------------------------------------------- : new_card
SCRIPT_FUNCTION(new_card) { SCRIPT_FUNCTION(new_card) {
SCRIPT_PARAM(GameP, game); SCRIPT_PARAM(GameP, game);
SCRIPT_OPTIONAL_PARAM_(bool, ignore_field_not_found);
// create a new card object // create a new card object
CardP new_card = make_intrusive<Card>(*game); CardP new_card = make_intrusive<Card>(*game);
// iterate on the given key/value pairs // iterate on the given key/value pairs
@@ -94,31 +34,42 @@ SCRIPT_FUNCTION(new_card) {
ScriptValueP key; ScriptValueP key;
while (ScriptValueP value = it->next(&key)) { while (ScriptValueP value = it->next(&key)) {
assert(key); assert(key);
if (key == script_nil) continue;
String key_name = key->toString(); String key_name = key->toString();
// check if the given value is for a built-in field // check if the given value is for a built-in field
if (set_builtin_container(game, new_card, value, key_name)) continue; if (set_builtin_container(game, new_card, value, key_name)) continue;
// find the field value (container) that corresponds to the given value // find the field value (container) that corresponds to the given value
Value* container = get_container(game, new_card, key_name); Value* container = get_container(game, new_card, key_name, ignore_field_not_found);
if (container == nullptr) continue;
FieldP field = container->fieldP; FieldP field = container->fieldP;
// if the field has a construction script, set the value and card context variables to be the given value and this card, run script // if the field has a construction script, set the value and card context variables to be the given value and this card, run script
if (field->construction_script) { if (field->import_script) {
ScriptValueP ctx_value = ctx.getVariableOpt(SCRIPT_VAR_value); ScriptValueP ctx_value = ctx.getVariableOpt(SCRIPT_VAR_value);
ScriptValueP ctx_card = ctx.getVariableOpt(SCRIPT_VAR_card); ScriptValueP ctx_card = ctx.getVariableOpt(SCRIPT_VAR_card);
ctx.setVariable(SCRIPT_VAR_value, value); ctx.setVariable(SCRIPT_VAR_value, value);
ctx.setVariable(SCRIPT_VAR_card, to_script(new_card)); ctx.setVariable(SCRIPT_VAR_card, to_script(new_card));
ScriptValueP script_input = field->construction_script.invoke(ctx, true); ScriptValueP script_input = field->import_script.invoke(ctx, true);
// iterate on the key/value pairs given by the script // if the script result is a collection, iterate on the key/value pairs
ScriptValueP script_it = script_input->makeIterator(); // treat the keys as field names and the values as what to populate those fields with
ScriptValueP script_key; if (script_input->type() == SCRIPT_COLLECTION) {
while (ScriptValueP script_value = script_it->next(&script_key)) { ScriptValueP script_it = script_input->makeIterator();
assert(script_key); ScriptValueP script_key;
String script_key_name = script_key->toString(); while (ScriptValueP script_value = script_it->next(&script_key)) {
// check if the script value is for a built-in field assert(script_key);
if (set_builtin_container(game, new_card, script_value, script_key_name)) continue; if (script_key == script_nil) continue;
// find the field value that corresponds to the script value String script_key_name = script_key->toString();
Value* script_container = get_container(game, new_card, script_key_name); // check if the script value is for a built-in field
// set the field value to the script value if (set_builtin_container(game, new_card, script_value, script_key_name)) continue;
set_container(script_container, script_value, script_key_name); // find the field value that corresponds to the script value
Value* script_container = get_container(game, new_card, script_key_name, ignore_field_not_found);
if (script_container == nullptr) continue;
// set the field value to the script value
set_container(script_container, script_value, script_key_name);
}
}
// if the script result is not a collection, simply set the field value to the script value
else {
set_container(container, script_input, key_name);
} }
// restore old value and card context variables // restore old value and card context variables
if (ctx_value) ctx.setVariable(SCRIPT_VAR_value, ctx_value); if (ctx_value) ctx.setVariable(SCRIPT_VAR_value, ctx_value);
@@ -130,22 +81,29 @@ SCRIPT_FUNCTION(new_card) {
} }
} }
// if the game has a construction script, set the card context variable to be this card, run script // if the game has a construction script, set the card context variable to be this card, run script
if (game->construction_script) { if (game->import_script) {
ScriptValueP ctx_card = ctx.getVariableOpt(SCRIPT_VAR_card); ScriptValueP ctx_card = ctx.getVariableOpt(SCRIPT_VAR_card);
ctx.setVariable(SCRIPT_VAR_card, to_script(new_card)); ctx.setVariable(SCRIPT_VAR_card, to_script(new_card));
ScriptValueP script_input = game->construction_script.invoke(ctx, true); ScriptValueP script_input = game->import_script.invoke(ctx, true);
// iterate on the key/value pairs given by the script if (script_input->type() == SCRIPT_COLLECTION) {
ScriptValueP script_it = script_input->makeIterator(); // iterate on the key/value pairs given by the script
ScriptValueP script_key; ScriptValueP script_it = script_input->makeIterator();
while (ScriptValueP script_value = script_it->next(&script_key)) { ScriptValueP script_key;
assert(script_key); while (ScriptValueP script_value = script_it->next(&script_key)) {
String script_key_name = script_key->toString(); assert(script_key);
// check if the script value is for a built-in field if (script_key == script_nil) continue;
if (set_builtin_container(game, new_card, script_value, script_key_name)) continue; String script_key_name = script_key->toString();
// find the field value that corresponds to the script value // check if the script value is for a built-in field
Value* script_container = get_container(game, new_card, script_key_name); if (set_builtin_container(game, new_card, script_value, script_key_name)) continue;
// set the field value to the script value // find the field value that corresponds to the script value
set_container(script_container, script_value, script_key_name); Value* script_container = get_container(game, new_card, script_key_name, ignore_field_not_found);
if (script_container == nullptr) continue;
// set the field value to the script value
set_container(script_container, script_value, script_key_name);
}
}
else {
queue_message(MESSAGE_ERROR, _ERROR_("game import script not map"));
} }
// restore old context card // restore old context card
if (ctx_card) ctx.setVariable(SCRIPT_VAR_card, ctx_card); if (ctx_card) ctx.setVariable(SCRIPT_VAR_card, ctx_card);
@@ -0,0 +1,160 @@
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
// ----------------------------------------------------------------------------- : Includes
#include <util/prec.hpp>
#include <data/field/text.hpp>
#include <data/field/choice.hpp>
#include <data/field/package_choice.hpp>
#include <data/field/color.hpp>
#include <data/field/image.hpp>
#include <data/action/set.hpp>
#include <data/game.hpp>
#include <data/set.hpp>
#include <data/stylesheet.hpp>
#include <data/card.hpp>
#include <util/error.hpp>
// ----------------------------------------------------------------------------- : Helper functions
static Value* get_container(GameP& game, CardP& card, String key_name, bool ignore_field_not_found) {
// find value container to update
IndexMap<FieldP, ValueP>::const_iterator value_it = card->data.find(key_name);
if (value_it == card->data.end()) {
// look among alternate names
map<String, String>::iterator alt_name_it = game->card_fields_alt_names.find(unified_form(key_name));
if (alt_name_it != game->card_fields_alt_names.end()) {
value_it = card->data.find(alt_name_it->second);
}
}
if (value_it == card->data.end()) {
if (ignore_field_not_found) return nullptr;
throw ScriptError(_ERROR_1_("no field with name", key_name));
}
return value_it->get();
}
static void set_container(Value* container, ScriptValueP& value, String key_name) {
// set the given value into the container
if (TextValue* tvalue = dynamic_cast<TextValue*>(container)) {
tvalue->value = value->toString();
}
else if (ChoiceValue* cvalue = dynamic_cast<ChoiceValue*>(container)) {
cvalue->value = value->toString();
}
else if (PackageChoiceValue* pvalue = dynamic_cast<PackageChoiceValue*>(container)) {
pvalue->package_name = value->toString();
}
else if (ColorValue* cvalue = dynamic_cast<ColorValue*>(container)) {
cvalue->value = value->toColor();
}
else if (ImageValue* ivalue = dynamic_cast<ImageValue*>(container)) {
wxFileName fname(static_cast<ExternalImage*>(value.get())->toString());
ivalue->filename = LocalFileName::fromReadString(fname.GetName(), "");
}
else {
throw ScriptError(_ERROR_1_("can't set value", key_name));
}
}
static bool set_builtin_container(GameP& game, CardP& card, ScriptValueP& value, String key_name) {
// check if the given value is for a built-in field, if found set it and return true
key_name = unified_form(key_name);
if (key_name == _("notes") || key_name == _("note")) {
card->notes = value->toString();
return true;
} else if (key_name == _("style") || key_name == _("stylesheet") || key_name == _("template")) {
if (trim(value->toString()) != wxEmptyString) card->stylesheet = StyleSheet::byGameAndName(*game, value->toString());
return true;
}
// else if (key_name == _("id") || key_name == _("multiverse_id")) {
// card->id = value->toString();
// return true;
//}
//styling_data;
//linked_card;
//linked_relation_1;
return false;
}
static bool check_table_headers(GameP& game, std::vector<String>& headers, const String& file_extension, String& missing_fields_out) {
if (headers.size() == 0) {
queue_message(MESSAGE_ERROR, _("Empty headers given"));
return false;
}
for (int x = 0; x < headers.size(); ++x) {
String key_name = headers[x];
if ( game->card_fields_alt_names.find(unified_form(key_name)) == game->card_fields_alt_names.end()
|| key_name == _("notes")
|| key_name == _("note")
|| key_name == _("style")
|| key_name == _("stylesheet")
|| key_name == _("template")
|| key_name == _("id")
|| key_name == _("uid")
|| key_name == _("multiverse_id")
|| key_name == _("linked_card")
|| key_name == _("linked_card_1")
|| key_name == _("linked_card_2")
|| key_name == _("linked_card_3")
|| key_name == _("linked_card_4")
|| key_name == _("linked_relation")
|| key_name == _("linked_relation_1")
|| key_name == _("linked_relation_2")
|| key_name == _("linked_relation_3")
|| key_name == _("linked_relation_4")
|| key_name == _("link_relation")
|| key_name == _("link_relation_1")
|| key_name == _("link_relation_2")
|| key_name == _("link_relation_3")
|| key_name == _("link_relation_4")
) {
missing_fields_out += _("\n ") + key_name;
}
}
return true;
}
static bool cards_from_table(SetP& set, vector<String>& headers, std::vector<std::vector<ScriptValueP>>& table, bool ignore_field_not_found, const String& file_extension, vector<CardP>& cards_out) {
// ensure table is square
int count = headers.size();
for (int y = 0; y < table.size(); ++y) {
if (table[y].size() != count) {
queue_message(MESSAGE_ERROR, _ERROR_2_("import file malformed", file_extension, wxString::Format(wxT("%i"), y+1)));
return false;
}
}
// produce cards from table
Context& ctx = set->getContext();
ScriptValueP new_card_function = ctx.getVariable("new_card");
ScriptValueP ctx_input = ctx.getVariableOpt(SCRIPT_VAR_input);
ScriptValueP ctx_ignore = ctx.getVariableOpt("ignore_field_not_found");
ctx.setVariable("ignore_field_not_found", to_script(ignore_field_not_found));
for (int y = 0; y < table.size(); ++y) {
ScriptCustomCollectionP field_map = make_intrusive<ScriptCustomCollection>();
for (int x = 0; x < count; ++x) {
// check if value is worth writing
if (table[y][x] != script_nil) {
field_map->key_value[headers[x]] = table[y][x];
}
}
ctx.setVariable(SCRIPT_VAR_input, field_map);
CardP card = from_script<CardP>(new_card_function->eval(ctx));
// is this a new card?
if (contains(set->cards, card) || contains(cards_out, card)) {
// make copy
card = make_intrusive<Card>(*card);
}
cards_out.push_back(card);
}
if (ctx_input) ctx.setVariable(SCRIPT_VAR_input, ctx_input);
if (ctx_ignore) ctx.setVariable("ignore_field_not_found", ctx_ignore);
return true;
}
+7
View File
@@ -87,6 +87,13 @@ String tr(const String&, const String& subcat, const String& key, DefaultLocaleF
/// A localized string for tooltip text, with 1 argument (printf style) /// A localized string for tooltip text, with 1 argument (printf style)
#define _HELP_1_(s,a) format_string(_HELP_(s), a) #define _HELP_1_(s,a) format_string(_HELP_(s), a)
/// A localized string for tooltip labels, with 1 argument (printf style)
#define _TOOL_1_(s,a) format_string(_TOOL_(s), a)
/// A localized string for tooltip labels, with 2 argument (printf style)
#define _TOOL_2_(s,a,b) format_string(_TOOL_(s), a, b)
/// A localized string for tooltip labels, with 3 argument (printf style)
#define _TOOL_3_(s,a,b,c) format_string(_TOOL_(s), a, b, c)
/// A localized string for tooltip text, with 1 argument (printf style) /// A localized string for tooltip text, with 1 argument (printf style)
#define _TOOLTIP_1_(s,a) format_string(_TOOLTIP_(s), a) #define _TOOLTIP_1_(s,a) format_string(_TOOLTIP_(s), a)
+4
View File
@@ -111,6 +111,9 @@ enum ChildMenuID {
ID_CARD_ADD_CSV, ID_CARD_ADD_CSV,
ID_CARD_ADD_CSV_SEP, ID_CARD_ADD_CSV_SEP,
ID_CARD_ADD_CSV_BROWSE, ID_CARD_ADD_CSV_BROWSE,
ID_CARD_ADD_JSON,
ID_CARD_ADD_JSON_ARRAY,
ID_CARD_ADD_JSON_BROWSE,
// Keyword menu // Keyword menu
ID_KEYWORD_ADD = 6101, ID_KEYWORD_ADD = 6101,
@@ -188,6 +191,7 @@ enum ChildMenuID {
// On cards panel // On cards panel
ID_COLLAPSE_NOTES = 8001, ID_COLLAPSE_NOTES = 8001,
ID_CARD_FILTER, ID_CARD_FILTER,
ID_CARD_COUNTER,
// Style panel // Style panel
ID_STYLE_USE_FOR_ALL = 8011, ID_STYLE_USE_FOR_ALL = 8011,
+2 -2
View File
@@ -17,7 +17,7 @@ The MSE source code is subdivided into several directories, with the following m
- <tt>gfx</tt>: Graphics related functions, again mostly independent of MSE. - <tt>gfx</tt>: Graphics related functions, again mostly independent of MSE.
This directory contains algorithms for image blending, scaling, and bezier curve functions. This directory contains algorithms for image blending, scaling, and bezier curve functions.
- <tt>data</tt>: Data structures, like sets, cards, symbols, etc. - <tt>data</tt>: Data structures, like sets, cards, symbols, etc.
These data structures are documented in the <a href="http://magicseteditor.sourceforge.net/extending">'Extending MSE'</a> section of the official documentation. These data structures are documented in the <a href="https://mseverse.miraheze.org/wiki/Dev:Data_types">'Data types'</a> section of the documentation.
- <tt>data/action</tt>: Actions that can be applied to those data structures. - <tt>data/action</tt>: Actions that can be applied to those data structures.
- <tt>data/field</tt>: Data types for fields, values and styles. One source file per type. - <tt>data/field</tt>: Data types for fields, values and styles. One source file per type.
- <tt>data/format</tt>: File formats and import/export stuff. - <tt>data/format</tt>: File formats and import/export stuff.
@@ -39,4 +39,4 @@ Additional tools (not needed for building MSE) also depend on:
- <a href="http://doxygen.org">doxygen</a> for generating the documentation. - <a href="http://doxygen.org">doxygen</a> for generating the documentation.
- Perl for small utility scripts - Perl for small utility scripts
*/ */
+1 -1
View File
@@ -10,4 +10,4 @@ description:
<li>Added a "Console" panel for error messages and code evaluation. <li>Added a "Console" panel for error messages and code evaluation.
<li>Fixed several bugs <li>Fixed several bugs
</ul> </ul>
<a href="http://magicseteditor.sourceforge.net/download">Get version 2.0.0 from the MSE download page</a>. <a href="https://magicseteditor.boards.net/page/downloads">Get version 2.5.8 from the MSE download page</a>.