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
+1 -1
View File
@@ -33,7 +33,7 @@ void AboutWindow::onPaint(wxPaintEvent& ev) {
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) {
wxSize ws = GetClientSize();
+78 -61
View File
@@ -10,13 +10,14 @@
#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 <gui/add_csv_window.hpp>
#include <util/window_id.hpp>
#include <data/action/set.hpp>
#include <gui/add_csv_window.hpp>
#include <script/functions/construction_helper.hpp>
#include <wx/statline.h>
// ----------------------------------------------------------------------------- : AddCSV
@@ -32,6 +33,7 @@ AddCSVWindow::AddCSVWindow(Window* parent, const SetP& set, bool sizer)
separator_type->Clear();
separator_type->Append(_LABEL_("add card csv tab"));
separator_type->Append(_LABEL_("add card csv comma"));
separator_type->Append(_LABEL_("add card csv semicolon"));
separator_type->SetSelection(0);
setSeparatorType();
// init sizers
@@ -39,7 +41,7 @@ AddCSVWindow::AddCSVWindow(Window* parent, const SetP& set, bool sizer)
wxSizer* s = new wxBoxSizer(wxVERTICAL);
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(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);
wxSizer* s2 = new wxBoxSizer(wxHORIZONTAL);
s2->Add(file_browse, 0, wxEXPAND | wxRIGHT, 8);
@@ -55,7 +57,8 @@ AddCSVWindow::AddCSVWindow(Window* parent, const SetP& set, bool sizer)
void AddCSVWindow::setSeparatorType() {
int sel = separator_type->GetSelection();
if (sel == 0) separator = ' ';
else separator = ',';
else if (sel == 1) separator = ',';
else separator = ';';
}
void AddCSVWindow::onSeparatorTypeChange(wxCommandEvent&) {
@@ -109,73 +112,87 @@ std::vector<std::string> AddCSVWindow::readCSVRow(const std::string& row) {
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;
}
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&) {
/// Perform the import
// Read the file, put it into a table
auto in = std::ifstream(file_path->GetValue().ToStdString());
if (in.fail()) {
wxBusyCursor wait;
// Read the file
auto file = std::ifstream(file_path->GetValue().ToStdString());
if (file.fail()) {
queue_message(MESSAGE_ERROR, _ERROR_("add card csv file not found"));
EndModal(wxID_ABORT);
return;
}
std::vector<std::vector<std::string>> table;
std::string row;
while (!in.eof()) {
std::getline(in, row);
if (in.bad() || in.fail()) {
break;
}
auto fields = readCSVRow(row);
table.push_back(fields);
// Put values into a table
std::vector<String> headers;
std::vector<std::vector<ScriptValueP>> table;
if (!readCSV(file, headers, table)) {
EndModal(wxID_ABORT);
return;
}
// ensure table is square
int count = table[0].size();
for (int y = 1; y < table.size(); ++y) {
if (table[y].size() != count) {
queue_message(MESSAGE_ERROR, _ERROR_1_("add card csv file malformed", wxString::Format(wxT("%i"), y+1)));
EndModal(wxID_ABORT);
return;
}
// Check for missing fields
String missing_fields;
check_table_headers(set->game, headers, _("CSV / TSV"), missing_fields);
if (missing_fields.size() > 0) {
queue_message(MESSAGE_WARNING, _ERROR_2_("import missing fields", _("CSV / TSV"), missing_fields));
}
// produce script from 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
// Produce cards from the table
vector<CardP> cards;
ScriptValueP it = result->makeIterator();
while (ScriptValueP item = it->next()) {
CardP card = from_script<CardP>(item);
// is this a new card?
if (contains(set->cards, card) || contains(cards, card)) {
// make copy
card = make_intrusive<Card>(*card);
}
cards.push_back(card);
if (!cards_from_table(set, headers, table, true, _("CSV / TSV"), cards)) {
EndModal(wxID_ABORT);
return;
}
// add to set
// Add cards to set
if (!cards.empty()) {
// TODO: change the name of the action somehow
set->actions.addAction(make_unique<AddCardAction>(ADD, *set, cards));
@@ -185,7 +202,7 @@ void AddCSVWindow::onOk(wxCommandEvent&) {
}
BEGIN_EVENT_TABLE(AddCSVWindow, wxDialog)
EVT_BUTTON(wxID_OK, AddCSVWindow::onOk)
EVT_BUTTON(ID_CARD_ADD_CSV_BROWSE, AddCSVWindow::onBrowseFiles)
EVT_CHOICE(ID_CARD_ADD_CSV_SEP, AddCSVWindow::onSeparatorTypeChange)
EVT_BUTTON(wxID_OK, AddCSVWindow::onOk)
EVT_BUTTON(ID_CARD_ADD_CSV_BROWSE, AddCSVWindow::onBrowseFiles)
EVT_CHOICE(ID_CARD_ADD_CSV_SEP, AddCSVWindow::onSeparatorTypeChange)
END_EVENT_TABLE()
+2 -2
View File
@@ -28,11 +28,11 @@ protected:
SetP set;
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);
void setSeparatorType();
void onSeparatorTypeChange(wxCommandEvent&);
void setSeparatorType();
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/set/window.hpp> // for sorting all cardlists in a window
#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/field.hpp>
#include <data/field/choice.hpp>
@@ -190,6 +191,15 @@ bool CardListBase::doAddCSV() {
}
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
+2 -1
View File
@@ -77,7 +77,8 @@ public:
bool doCopy() override;
bool doPaste() override;
bool doDelete() override;
bool doAddCSV();
bool doAddCSV();
bool doAddJSON();
// --------------------------------------------------- : Set actions
+39 -6
View File
@@ -39,7 +39,7 @@ CardsPanel::CardsPanel(Window* parent, int id)
notes = new TextCtrl(nodes_panel, ID_NOTES, true);
collapse_notes = new HoverButton(nodes_panel, ID_COLLAPSE_NOTES, _("btn_collapse"), Color(), false);
collapse_notes->SetExtraStyle(wxWS_EX_PROCESS_UI_UPDATES);
filter = nullptr;
filter = nullptr;
editor->next_in_tab_order = card_list;
// init sizer for notes panel
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_SEARCH, nullptr, "search cards");
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");
// NOTE: space after "Del" prevents wx from making del an accellerator
// otherwise we delete a card when delete is pressed inside the editor
// 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_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"));
menuCard->AppendSeparator();
auto menuRotate = new wxMenu();
@@ -98,7 +99,31 @@ CardsPanel::CardsPanel(Window* parent, int id)
menuFormat->Append(insertSymbolMenu);
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() {
wxSize editor_size = editor->GetBestSize();
@@ -205,9 +230,12 @@ void CardsPanel::initUI(wxToolBar* tb, wxMenuBar* mb) {
// Filter/search textbox
tb->AddSeparator();
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);
tb->AddControl(filter);
tb->AddControl(filter);
counts = new wxStaticText(tb, ID_CARD_COUNTER, _(""));
updateCardCounts();
tb->AddControl(counts);
tb->Realize();
// Menus
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_REMOVE);
tb->DeleteTool(ID_CARD_ROTATE);
tb->DeleteTool(ID_CARD_COUNTER);
// remember the value in the filter control, because the card list remains filtered
// the control is destroyed by DeleteTool
filter_value = filter->getFilterString();
@@ -282,7 +311,8 @@ void CardsPanel::onUpdateUI(wxUpdateUIEvent& ev) {
break;
}
#endif
}
}
updateCardCounts();
}
void CardsPanel::onMenuOpen(wxMenuEvent& ev) {
@@ -315,6 +345,9 @@ void CardsPanel::onCommand(int id) {
case ID_CARD_ADD_CSV:
card_list->doAddCSV();
break;
case ID_CARD_ADD_JSON:
card_list->doAddJSON();
break;
case ID_CARD_REMOVE:
card_list->doDelete();
break;
+5 -2
View File
@@ -87,9 +87,12 @@ private:
TextCtrl* notes;
HoverButton* collapse_notes;
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;
/// Update card counts
void updateCardCounts();
/// Move the notes panel below the editor or below the card list
void updateNotesPosition();
// before Layout, call updateNotesPosition.