//+----------------------------------------------------------------------------+ //| Description: Magic Set Editor - Program to make card games | //| Copyright: (C) Twan van Laarhoven and the other MSE developers | //| License: GNU General Public License 2 or later (see file COPYING) | //+----------------------------------------------------------------------------+ // ----------------------------------------------------------------------------- : Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ----------------------------------------------------------------------------- : Extra types IMPLEMENT_REFLECTION_ENUM(CheckUpdates) { VALUE_N("always", CHECK_ALWAYS); VALUE_N("every 5", CHECK_5); //default VALUE_N("every 10", CHECK_10); VALUE_N("never", CHECK_NEVER); } IMPLEMENT_REFLECTION_ENUM(CheckUpdatesTargets) { VALUE_N("update app", CHECK_APP); VALUE_N("update games", CHECK_GAMES); VALUE_N("update everything", CHECK_EVERYTHING); //default } IMPLEMENT_REFLECTION_ENUM(InstallType) { VALUE_N("default", INSTALL_DEFAULT); //default VALUE_N("local", INSTALL_LOCAL); VALUE_N("global", INSTALL_GLOBAL); } bool is_install_local(InstallType type) { #ifdef __WXMSW__ #define DEFAULT_INSTALL_LOCAL false #else #define DEFAULT_INSTALL_LOCAL true #endif return type == INSTALL_DEFAULT ? DEFAULT_INSTALL_LOCAL : type == INSTALL_LOCAL; } IMPLEMENT_REFLECTION_ENUM(FilenameConflicts) { VALUE_N("keep old", CONFLICT_KEEP_OLD); VALUE_N("overwrite", CONFLICT_OVERWRITE); VALUE_N("number", CONFLICT_NUMBER); VALUE_N("number overwrite", CONFLICT_NUMBER_OVERWRITE); } const vector Settings::scale_choices = { 50,66,75,80,100,120,125,150,175,200 }; const int COLUMN_NOT_INITIALIZED = -100000; ColumnSettings::ColumnSettings() : width(100), position(COLUMN_NOT_INITIALIZED), visible(false) {} // dummy for ColumnSettings reflection ScriptValueP to_script(const ColumnSettings&) { return script_nil; } IMPLEMENT_REFLECTION_NO_SCRIPT(ColumnSettings) { REFLECT(width); REFLECT(position); REFLECT(visible); } GameSettings::GameSettings() : sort_cards_ascending (true) , images_export_filename (_("{card.name}.png")) , images_export_conflicts (CONFLICT_NUMBER_OVERWRITE) , use_auto_replace (true) , pack_seed_random (true) , pack_seed (123456) , initialized (false) {} void GameSettings::initDefaults(const Game& game) { // Defer initialization until the game is fully loaded. // This prevents data that needs to be initialized from // being accessed from the new set window, but removes // the need to load the entire file, which takes too long. if (initialized || !game.isFullyLoaded()) return; initialized = true; // init auto_replaces, copy from game file FOR_EACH_CONST(ar, game.auto_replaces) { // do we have this one? bool already_have = false; FOR_EACH(ar2, auto_replaces) { if (ar->match == ar2->match) { ar2->custom = false; already_have = true; break; } } if (!already_have) { // TODO: when we start saving games, clone here ar->custom = false; auto_replaces.push_back(ar); } } // make sure things aren't in a problematic state for (auto it = cardlist_columns.begin(); it != cardlist_columns.end(); ++it) { if (it->second.width < 20) it->second.width = 20; } if (images_export_filename.Trim().empty()) images_export_filename = _("{card.name}.png"); } IMPLEMENT_REFLECTION_NO_SCRIPT(GameSettings) { REFLECT(default_stylesheet); REFLECT(default_export); REFLECT(cardlist_columns); REFLECT(sort_cards_by); REFLECT(sort_cards_ascending); REFLECT(images_export_filename); REFLECT(images_export_conflicts); REFLECT(use_auto_replace); REFLECT(auto_replaces); REFLECT(pack_amounts); REFLECT(pack_seed_random); REFLECT(pack_seed); REFLECT(custom_colors); } StyleSheetSettings::StyleSheetSettings() : card_zoom (1.0, true) , export_scale_selection (0, true) , card_angle (0, true) , card_anti_alias (true, true) , card_borders (true, true) , card_draw_editing (true, true) , card_normal_export (true, true) , card_bleed_export (false, true) , card_notes_export (false, true) , card_spellcheck_enabled (true, true) {} void StyleSheetSettings::useDefault(const StyleSheetSettings& ss) { if (card_zoom .isDefault()) card_zoom .assignDefault(ss.card_zoom); if (export_scale_selection .isDefault()) export_scale_selection .assignDefault(ss.export_scale_selection); if (card_angle .isDefault()) card_angle .assignDefault(ss.card_angle); if (card_anti_alias .isDefault()) card_anti_alias .assignDefault(ss.card_anti_alias); if (card_borders .isDefault()) card_borders .assignDefault(ss.card_borders); if (card_draw_editing .isDefault()) card_draw_editing .assignDefault(ss.card_draw_editing); if (card_normal_export .isDefault()) card_normal_export .assignDefault(ss.card_normal_export); if (card_bleed_export .isDefault()) card_bleed_export .assignDefault(ss.card_bleed_export); if (card_notes_export .isDefault()) card_notes_export .assignDefault(ss.card_notes_export); if (card_spellcheck_enabled.isDefault()) card_spellcheck_enabled.assignDefault(ss.card_spellcheck_enabled); } IMPLEMENT_REFLECTION_NO_SCRIPT(StyleSheetSettings) { REFLECT(card_zoom); REFLECT(export_scale_selection); REFLECT(card_angle); REFLECT(card_anti_alias); REFLECT(card_borders); REFLECT(card_draw_editing); REFLECT(card_normal_export); REFLECT(card_bleed_export); REFLECT(card_notes_export); REFLECT(card_spellcheck_enabled); } // ----------------------------------------------------------------------------- : Printing settings IMPLEMENT_REFLECTION_ENUM(CutterLinesType) { VALUE_N("all", CUTTER_ALL); VALUE_N("no intersect", CUTTER_NO_INTERSECTION); VALUE_N("none", CUTTER_NONE); } // ----------------------------------------------------------------------------- : Dark mode settings IMPLEMENT_REFLECTION_ENUM(DarkModeType) { VALUE_N("yes", DARKMODE_YES); VALUE_N("system", DARKMODE_SYSTEM); VALUE_N("no", DARKMODE_NO); } // ----------------------------------------------------------------------------- : Settings Settings settings; Settings::Settings() : locale (_("en")) , set_window_maximized (false) , set_window_width (790) , set_window_height (300) , card_notes_height (40) , open_sets_in_new_window (true) , symbol_grid_size (30) , symbol_grid (true) , symbol_grid_snap (false) , print_spacing (0.33) , print_bleed (0.0) , print_cutter_lines (CUTTER_ALL) , dark_mode_type (DARKMODE_SYSTEM) , import_scale_selection (0) , allow_image_download (true) , installer_list_url (_("https://raw.githubusercontent.com/MagicSetEditorPacks/Installer-Pack/refs/heads/main/packages.txt")) , check_updates_what (CHECK_EVERYTHING) , check_updates_when (CHECK_5) , check_updates_counter (0) , website_url (_("https://magicseteditor.boards.net/")) , documentation_url (_("https://mseverse.miraheze.org/wiki/Dev:Documentation#Topics")) , install_type (INSTALL_DEFAULT) {} void Settings::addRecentFile(const String& filename) { // get absolute path wxFileName fn(filename); fn.Normalize(); String filenameAbs = fn.GetFullPath(); // remove duplicates recent_sets.erase( remove(recent_sets.begin(), recent_sets.end(), filenameAbs), recent_sets.end() ); // add to front of list recent_sets.insert(recent_sets.begin(), filenameAbs); // enforce size limit if (recent_sets.size() > max_recent_sets) recent_sets.resize(max_recent_sets); } GameSettings& Settings::gameSettingsFor(const Game& game) { GameSettingsP& gs = game_settings[game.name()]; if (!gs) gs = make_intrusive(); gs->initDefaults(game); return *gs; } ColumnSettings& Settings::columnSettingsFor(const Game& game, const Field& field) { // Get game info GameSettings& gs = gameSettingsFor(game); // Get column info ColumnSettings& cs = gs.cardlist_columns[field.name]; if (cs.position == COLUMN_NOT_INITIALIZED) { // column info not set, initialize based on the game cs.visible = field.card_list_visible; cs.position = field.card_list_column; cs.width = field.card_list_width; } return cs; } StyleSheetSettings& Settings::stylesheetSettingsFor(const StyleSheet& stylesheet) { // Use the canonical form here since the stylesheet name will be used as a stored key. // This does introduce the possibility of collision if two stylesheets return the same value canonically, but I think that's just a necessary risk. StyleSheetSettingsP& ss = stylesheet_settings[canonical_name_form(stylesheet.name())]; if (!ss) ss = make_intrusive(); ss->useDefault(default_stylesheet_settings); // update default settings return *ss; } double Settings::exportScaleSettingsFor(const StyleSheet& stylesheet) { StyleSheetSettings& ss = stylesheetSettingsFor(stylesheet); int export_scale = ss.export_scale_selection(); if (export_scale == 0) return adaptiveScaleSettingsFor(stylesheet, 300.0, 50.0); if (export_scale == 1) return adaptiveScaleSettingsFor(stylesheet, 300.0, 1.0); if (export_scale == 2) return adaptiveScaleSettingsFor(stylesheet, 150.0, 1.0); return (double)scale_choices[export_scale - 3] / 100.0; } double Settings::importScaleSettingsFor(const StyleSheet& stylesheet) { if (import_scale_selection == 0) return exportScaleSettingsFor(stylesheet); if (import_scale_selection == 1) return adaptiveScaleSettingsFor(stylesheet, 300.0, 50.0); if (import_scale_selection == 2) return adaptiveScaleSettingsFor(stylesheet, 300.0, 1.0); if (import_scale_selection == 3) return adaptiveScaleSettingsFor(stylesheet, 150.0, 1.0); return (double)scale_choices[import_scale_selection - 4] / 100.0; } double Settings::adaptiveScaleSettingsFor(const StyleSheet& stylesheet, double dpi_target, double dpi_leeway) { if (abs(stylesheet.card_dpi - dpi_target) <= dpi_leeway) return 1.0; return dpi_target / max(10.0, stylesheet.card_dpi); } Settings::ExportSettings Settings::exportSettingsFor(const StyleSheet& stylesheet) { StyleSheetSettings& ss = stylesheetSettingsFor(stylesheet); double zoom = settings.exportScaleSettingsFor(stylesheet); double angle = ss.card_normal_export() ? 0.0 : deg_to_rad(ss.card_angle()); double bleed = ss.card_bleed_export() ? (stylesheet.card_dpi / 300.0) * 36.0 * zoom : 0.0; // 36 pixels of bleed on a 300 DPI print return ExportSettings{zoom, angle, bleed}; } IndexMap& Settings::exportOptionsFor(const ExportTemplate& export_template) { return export_options.get(export_template.name(), export_template.option_fields); } /// Retrieve the directory to use for settings and other data files String user_settings_dir() { String dir = wxStandardPaths::Get().GetUserDataDir(); if (!wxDirExists(dir)) wxMkdir(dir); return dir + _("/"); } String Settings::settingsFile() { return user_settings_dir() + _("mse.config"); } bool Settings::darkMode() { return wxSystemSettings::GetAppearance().IsDark(); } String Settings::darkModePrefix() { if (darkMode()) return _("dark_"); return _(""); } Color Settings::darkModeColor() { if (darkMode()) return wxColor(15,8,0); return wxColor(240,247,255); } IMPLEMENT_REFLECTION_NO_SCRIPT(Settings) { REFLECT(locale); REFLECT(recent_sets); REFLECT(default_set_dir); REFLECT(default_image_dir); REFLECT(default_symbol_dir); REFLECT(default_export_dir); REFLECT(default_import_dir); REFLECT(set_window_maximized); REFLECT(set_window_width); REFLECT(set_window_height); REFLECT(card_notes_height); REFLECT(open_sets_in_new_window); REFLECT(symbol_grid_size); REFLECT(symbol_grid); REFLECT(symbol_grid_snap); REFLECT(default_game); REFLECT(print_spacing); REFLECT(print_bleed); REFLECT(print_cutter_lines); REFLECT(dark_mode_type); REFLECT(apprentice_location); REFLECT(import_scale_selection); REFLECT(allow_image_download); REFLECT(check_updates_what); REFLECT(check_updates_when); REFLECT(check_updates_counter); REFLECT(install_type); REFLECT(website_url); REFLECT(documentation_url); REFLECT(game_settings); REFLECT(stylesheet_settings); REFLECT(default_stylesheet_settings); REFLECT(export_options); } void Settings::clear() { recent_sets.clear(); game_settings.clear(); stylesheet_settings.clear(); default_stylesheet_settings = StyleSheetSettings(); export_options.clear(); } void Settings::read() { // clear current settings, otherwise we duplicate vector elements clear(); // (re)load settings String filename = settingsFile(); if (wxFileExists(filename)) { // settings file not existing is not an error wxFileInputStream file(filename); if (!file.Ok()) return; // failure is not an error Reader reader(file, nullptr, filename); reader.handle_greedy(*this); // make sure things aren't in a problematic state if (locale.Trim().empty()) locale = _("en"); if (symbol_grid_size < 30) symbol_grid_size = 30; if (default_stylesheet_settings.card_zoom < 0.5) default_stylesheet_settings.card_zoom = 1.0; } } void Settings::write() { wxFileOutputStream file(settingsFile()); Writer writer(file, app_version); writer.handle(*this); }