From 8f064dd15511710641d08d0f3303494e90f0b352 Mon Sep 17 00:00:00 2001 From: Oleksandra Yushchenko <yusanka@gmail.com> Date: Wed, 22 Sep 2021 12:44:13 +0200 Subject: [PATCH] Check unsaved changes (#6991) * Check Unsaved changes (partially related to #5903) + Allow create new project when Plater is empty, but some of presets are modified (related to #5903) + When creating new project allow Keep or Discard modification from previous project + Added check of changes: * before any load project (including DnD and "Load From Recent Projects") * before preset updater * when configuration is changing from the ConfigWizard + Dialog caption is added for each check + Create/Destroy ConfigWizard every time when it's called * Check Unsaved changes: Next Improvements + For dialog "Save project changes" added a reason of saving and name of the current project (or "Untitled") + UnsavedChangesDialog: Headers are extended to better explain the reason + Preferences: Fixed tooltiops for "Always ask for unsaved changes when..." + Suppress "Remember my choice" checkbox for actions which are not frequently used * Fixed behavior of the application when try to save changed project but "Cancel" button is selected in "Save file as..." dialog * Check unsaved changes: Improvements for Config Wizard - Check all cases when presets should be updated + Fixed info line for Materials pages. Text of the info relates to the printer technology now * Improved suggested name for a project when Application is closing * Fixed Linux/OSX build warnings --- src/libslic3r/AppConfig.cpp | 3 + src/slic3r/GUI/ConfigWizard.cpp | 84 ++++++++++-- src/slic3r/GUI/ConfigWizard_private.hpp | 2 +- src/slic3r/GUI/ExtraRenderers.cpp | 18 ++- src/slic3r/GUI/GUI_App.cpp | 174 ++++++++++++++++++------ src/slic3r/GUI/GUI_App.hpp | 7 +- src/slic3r/GUI/GUI_ObjectList.cpp | 2 +- src/slic3r/GUI/MainFrame.cpp | 26 ++-- src/slic3r/GUI/MainFrame.hpp | 2 +- src/slic3r/GUI/Plater.cpp | 84 ++++++++---- src/slic3r/GUI/Plater.hpp | 2 +- src/slic3r/GUI/Preferences.cpp | 17 ++- src/slic3r/GUI/Tab.cpp | 20 ++- src/slic3r/GUI/Tab.hpp | 2 +- src/slic3r/GUI/UnsavedChangesDialog.cpp | 151 ++++++++++++-------- src/slic3r/GUI/UnsavedChangesDialog.hpp | 24 +++- src/slic3r/Utils/PresetUpdater.cpp | 22 +-- 17 files changed, 462 insertions(+), 178 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 177d8d708..f3a1d5988 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -139,6 +139,9 @@ void AppConfig::set_defaults() if (get("default_action_on_select_preset").empty()) set("default_action_on_select_preset", "none"); // , "transfer", "discard" or "save" + if (get("default_action_on_new_project").empty()) + set("default_action_on_new_project", "none"); // , "keep(transfer)", "discard" or "save" + if (get("color_mapinulation_panel").empty()) set("color_mapinulation_panel", "0"); diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 3f388f485..ee6e3d5ab 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -41,6 +41,7 @@ #include "format.hpp" #include "MsgDialog.hpp" #include "libslic3r/libslic3r.h" +#include "UnsavedChangesDialog.hpp" #if defined(__linux__) && defined(__WXGTK3__) #define wxLinux_gtk3 true @@ -741,10 +742,10 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector<std::s const auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); const auto text_clr = wxGetApp().get_label_clr_default();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); const auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); - wxString first_line = _L("Filaments marked with <b>*</b> are <b>not</b> compatible with some installed printers."); + wxString first_line = format_wxstr(_L("%1% marked with <b>*</b> are <b>not</b> compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); wxString text; if (all_printers) { - wxString second_line = _L("All installed printers are compatible with the selected filament."); + wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material")); text = wxString::Format( "<html>" "<style>" @@ -764,7 +765,7 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector<std::s , second_line ); } else { - wxString second_line = _L("Only the following installed printers are compatible with the selected filament:"); + wxString second_line = printer_names.empty() ? "" : format_wxstr(_L("Only the following installed printers are compatible with the selected %1%:"), materials->technology == T_FFF ? _L("filament") : _L("SLA material")); text = wxString::Format( "<html>" "<style>" @@ -2473,8 +2474,16 @@ static std::string get_first_added_preset(const std::map<std::string, std::strin return *diff.begin(); } -bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater) +bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes) { + wxString header, caption = _L("Configuration is editing from ConfigWizard"); + bool check_unsaved_preset_changes = page_welcome->reset_user_profile(); + if (check_unsaved_preset_changes) + header = _L("All user presets will be deleted."); + int act_btns = UnsavedChangesDialog::ActionButtons::KEEP; + if (!check_unsaved_preset_changes) + act_btns |= UnsavedChangesDialog::ActionButtons::SAVE; + const auto enabled_vendors = appconfig_new.vendors(); // Install bundles from resources if needed: @@ -2500,6 +2509,9 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese install_bundles.emplace_back(pair.first); } } + if (!check_unsaved_preset_changes) + if ((check_unsaved_preset_changes = install_bundles.size() > 0)) + header = _L_PLURAL("New vendor was installed and one of its printer will be activated", "New vendors were installed and one of theirs printer will be activated", install_bundles.size()); #ifdef __linux__ // Desktop integration on Linux @@ -2531,6 +2543,10 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Continue with applying configuration changes?"))) return false; + if (check_unsaved_preset_changes && + !wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + if (install_bundles.size() > 0) { // Install bundles from resources. // Don't create snapshot - we've already done that above if applicable. @@ -2586,17 +2602,52 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese } } + // if unsaved changes was not cheched till this moment + if (!check_unsaved_preset_changes) { + if ((check_unsaved_preset_changes = !preferred_model.empty())) { + header = _L("A new Printer was installed and it will be activated."); + if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + } + else if ((check_unsaved_preset_changes = enabled_vendors_old != enabled_vendors)) { + header = _L("Some Printers were uninstalled."); + if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + } + } + std::string first_added_filament, first_added_sla_material; - auto apply_section = [this, app_config](const std::string& section_name, std::string& first_added_preset) { + auto get_first_added_material_preset = [this, app_config](const std::string& section_name, std::string& first_added_preset) { if (appconfig_new.has_section(section_name)) { // get first of new added preset names const std::map<std::string, std::string>& old_presets = app_config->has_section(section_name) ? app_config->get_section(section_name) : std::map<std::string, std::string>(); first_added_preset = get_first_added_preset(old_presets, appconfig_new.get_section(section_name)); - app_config->set_section(section_name, appconfig_new.get_section(section_name)); } }; - apply_section(AppConfig::SECTION_FILAMENTS, first_added_filament); - apply_section(AppConfig::SECTION_MATERIALS, first_added_sla_material); + get_first_added_material_preset(AppConfig::SECTION_FILAMENTS, first_added_filament); + get_first_added_material_preset(AppConfig::SECTION_MATERIALS, first_added_sla_material); + + // if unsaved changes was not cheched till this moment + if (!check_unsaved_preset_changes) { + if ((check_unsaved_preset_changes = !first_added_filament.empty() || !first_added_sla_material.empty())) { + header = format_wxstr(_L("A new %1% was installed and it will be activated."), !first_added_filament.empty() ? _L("Filament") : _L("SLA material")); + if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + } + else { + bool is_filaments_changed = app_config->get_section(AppConfig::SECTION_FILAMENTS) != appconfig_new.get_section(AppConfig::SECTION_FILAMENTS); + bool is_sla_materials_changed = app_config->get_section(AppConfig::SECTION_MATERIALS) != appconfig_new.get_section(AppConfig::SECTION_MATERIALS); + if ((check_unsaved_preset_changes = is_filaments_changed || is_sla_materials_changed)) { + header = format_wxstr(_L("Some %1% were uninstalled."), is_filaments_changed ? _L("Filaments") : _L("SLA materials")); + if (!wxGetApp().check_and_keep_current_preset_changes(caption, header, act_btns, &apply_keeped_changes)) + return false; + } + } + } + + // apply materials in app_config + for (const std::string& section_name : {AppConfig::SECTION_FILAMENTS, AppConfig::SECTION_MATERIALS}) + app_config->set_section(section_name, appconfig_new.get_section(section_name)); app_config->set_vendors(appconfig_new); @@ -2624,10 +2675,16 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese page_mode->serialize_mode(app_config); - preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem, - {preferred_model, preferred_variant, first_added_filament, first_added_sla_material}); + if (check_unsaved_preset_changes) + preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem, + {preferred_model, preferred_variant, first_added_filament, first_added_sla_material}); if (page_custom->custom_wanted()) { + // if unsaved changes was not cheched till this moment + if (!check_unsaved_preset_changes && + !wxGetApp().check_and_keep_current_preset_changes(caption, _L("Custom printer was installed and it will be activated."), act_btns, &apply_keeped_changes)) + return false; + page_firmware->apply_custom_config(*custom_config); page_bed->apply_custom_config(*custom_config); page_diams->apply_custom_config(*custom_config); @@ -2851,8 +2908,13 @@ bool ConfigWizard::run(RunReason reason, StartPage start_page) p->set_start_page(start_page); if (ShowModal() == wxID_OK) { - if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater)) + bool apply_keeped_changes = false; + if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater, apply_keeped_changes)) return false; + + if (apply_keeped_changes) + app.apply_keeped_preset_modifications(); + app.app_config->set_legacy_datadir(false); app.update_mode(); app.obj_manipul()->update_ui_from_settings(); diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index 84def4202..d4b1fac04 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -611,7 +611,7 @@ struct ConfigWizard::priv bool on_bnt_finish(); bool check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id = std::string()); - bool apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater); + bool apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes); // #ys_FIXME_alise void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add); #ifdef __linux__ diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp index 5fe86db27..533b59398 100644 --- a/src/slic3r/GUI/ExtraRenderers.cpp +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -323,15 +323,19 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR c_editor->SetSelection(atoi(data.GetText().c_str())); - // to avoid event propagation to other sidebar items - c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { - evt.StopPropagation(); + #ifdef __linux__ - // FinishEditing grabs new selection and triggers config update. We better call - // it explicitly, automatic update on KILL_FOCUS didn't work on Linux. - this->FinishEditing(); -#endif + c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { + // to avoid event propagation to other sidebar items + evt.StopPropagation(); + // FinishEditing grabs new selection and triggers config update. We better call + // it explicitly, automatic update on KILL_FOCUS didn't work on Linux. + this->FinishEditing(); }); +#else + // to avoid event propagation to other sidebar items + c_editor->Bind(wxEVT_COMBOBOX, [](wxCommandEvent& evt) { evt.StopPropagation(); }); +#endif return c_editor; } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 8689f43cd..9c08159fc 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -694,7 +694,6 @@ GUI_App::GUI_App(EAppMode mode) , m_app_mode(mode) , m_em_unit(10) , m_imgui(new ImGuiWrapper()) - , m_wizard(nullptr) , m_removable_drive_manager(std::make_unique<RemovableDriveManager>()) , m_other_instance_message_handler(std::make_unique<OtherInstanceMessageHandler>()) { @@ -1333,8 +1332,6 @@ void GUI_App::recreate_GUI(const wxString& msg_name) dlg.Update(30, _L("Recreating") + dots); old_main_frame->Destroy(); - // For this moment ConfigWizard is deleted, invalidate it. - m_wizard = nullptr; dlg.Update(80, _L("Loading of current presets") + dots); m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); @@ -1409,8 +1406,6 @@ void GUI_App::update_ui_from_settings() m_force_colors_update = false; mainframe->force_color_changed(); mainframe->diff_dialog.force_color_changed(); - if (m_wizard) - m_wizard->force_color_changed(); } #endif mainframe->update_ui_from_settings(); @@ -1871,8 +1866,9 @@ void GUI_App::add_config_menu(wxMenuBar *menu) #endif case ConfigMenuTakeSnapshot: // Take a configuration snapshot. - if (check_and_save_current_preset_changes()) { - wxTextEntryDialog dlg(nullptr, _L("Taking configuration snapshot"), _L("Snapshot name")); + if (wxString action_name = _L("Taking a configuration snapshot"); + check_and_save_current_preset_changes(action_name, _L("Some presets are modified and the unsaved changes will not be captured by the configuration snapshot."), false, true)) { + wxTextEntryDialog dlg(nullptr, action_name, _L("Snapshot name")); UpdateDlgDarkUI(&dlg); // set current normal font for dialog children, @@ -1888,7 +1884,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu) } break; case ConfigMenuSnapshots: - if (check_and_save_current_preset_changes()) { + if (check_and_save_current_preset_changes(_L("Loading a configuration snapshot"), "", false)) { std::string on_snapshot; if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config)) on_snapshot = app_config->get("on_snapshot"); @@ -1910,9 +1906,6 @@ void GUI_App::add_config_menu(wxMenuBar *menu) // Load the currently selected preset into the GUI, update the preset selection box. load_current_presets(); - - // update config wizard in respect to the new config - update_wizard_from_config(); } catch (std::exception &ex) { GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what())); } @@ -2081,13 +2074,28 @@ std::vector<std::pair<unsigned int, std::string>> GUI_App::get_selected_presets( return ret; } -// This is called when closing the application, when loading a config file or when starting the config wizard -// to notify the user whether he is aware that some preset changes will be lost. -bool GUI_App::check_and_save_current_preset_changes(const wxString& header, const wxString& caption) +// To notify the user whether he is aware that some preset changes will be lost, +// UnsavedChangesDialog: "Discard / Save / Cancel" +// This is called when: +// - Close Application & Current project isn't saved +// - Load Project & Current project isn't saved +// - Undo / Redo with change of print technologie +// - Loading snapshot +// - Loading config_file/bundle +// UnsavedChangesDialog: "Don't save / Save / Cancel" +// This is called when: +// - Exporting config_bundle +// - Taking snapshot +bool GUI_App::check_and_save_current_preset_changes(const wxString& caption, const wxString& header, bool remember_choice/* = true*/, bool dont_save_insted_of_discard/* = false*/) { - if (/*this->plater()->model().objects.empty() && */has_current_preset_changes()) { - UnsavedChangesDialog dlg(header, caption); - if (wxGetApp().app_config->get("default_action_on_close_application") == "none" && dlg.ShowModal() == wxID_CANCEL) + if (has_current_preset_changes()) { + const std::string app_config_key = remember_choice ? "default_action_on_close_application" : ""; + int act_buttons = UnsavedChangesDialog::ActionButtons::SAVE; + if (dont_save_insted_of_discard) + act_buttons |= UnsavedChangesDialog::ActionButtons::DONT_SAVE; + UnsavedChangesDialog dlg(caption, header, app_config_key, act_buttons); + std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key); + if (act == "none" && dlg.ShowModal() == wxID_CANCEL) return false; if (dlg.save_preset()) // save selected changes @@ -2095,18 +2103,122 @@ bool GUI_App::check_and_save_current_preset_changes(const wxString& header, cons for (const std::pair<std::string, Preset::Type>& nt : dlg.get_names_and_types()) preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second)); + load_current_presets(false); + // if we saved changes to the new presets, we should to // synchronize config.ini with the current selections. preset_bundle->export_selections(*app_config); - wxMessageBox(_L_PLURAL("The preset modifications are successfully saved", - "The presets modifications are successfully saved", dlg.get_names_and_types().size())); + MessageDialog(nullptr, _L_PLURAL("The preset modifications are successfully saved", + "The presets modifications are successfully saved", dlg.get_names_and_types().size())).ShowModal(); } } return true; } +void GUI_App::apply_keeped_preset_modifications() +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (Tab* tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology)) + tab->apply_config_from_cache(); + } + load_current_presets(false); +} + +// This is called when creating new project or load another project +// OR close ConfigWizard +// to ask the user what should we do with unsaved changes for presets. +// New Project => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Cancel" +// => Current project isn't saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel" +// Close ConfigWizard => Current project is saved => UnsavedChangesDialog: "Keep / Discard / Save / Cancel" +// Note: no_nullptr postponed_apply_of_keeped_changes indicates that thie function is called after ConfigWizard is closed +bool GUI_App::check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes/* = nullptr*/) +{ + if (has_current_preset_changes()) { + bool is_called_from_configwizard = postponed_apply_of_keeped_changes != nullptr; + + const std::string app_config_key = is_called_from_configwizard ? "" : "default_action_on_new_project"; + UnsavedChangesDialog dlg(caption, header, app_config_key, action_buttons); + std::string act = app_config_key.empty() ? "none" : wxGetApp().app_config->get(app_config_key); + if (act == "none" && dlg.ShowModal() == wxID_CANCEL) + return false; + + auto reset_modifications = [this, is_called_from_configwizard]() { + if (is_called_from_configwizard) + return; // no need to discared changes. It will be done fromConfigWizard closing + + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (const Tab* const tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) + tab->m_presets->discard_current_changes(); + } + load_current_presets(false); + }; + + if (dlg.discard()) + reset_modifications(); + else // save selected changes + { + const auto& preset_names_and_types = dlg.get_names_and_types(); + if (dlg.save_preset()) { + for (const std::pair<std::string, Preset::Type>& nt : preset_names_and_types) + preset_bundle->save_changes_for_preset(nt.first, nt.second, dlg.get_unselected_options(nt.second)); + + // if we saved changes to the new presets, we should to + // synchronize config.ini with the current selections. + preset_bundle->export_selections(*app_config); + + wxString text = _L_PLURAL("The preset modifications are successfully saved", + "The presets modifications are successfully saved", preset_names_and_types.size()); + if (!is_called_from_configwizard) + text += "\n\n" + _L("For new project all modifications will be reseted"); + + MessageDialog(nullptr, text).ShowModal(); + reset_modifications(); + } + else if (dlg.transfer_changes() && (dlg.has_unselected_options() || is_called_from_configwizard)) { + // execute this part of code only if not all modifications are keeping to the new project + // OR this function is called when ConfigWizard is closed and "Keep modifications" is selected + for (const std::pair<std::string, Preset::Type>& nt : preset_names_and_types) { + Preset::Type type = nt.second; + Tab* tab = get_tab(type); + std::vector<std::string> selected_options = dlg.get_selected_options(type); + if (type == Preset::TYPE_PRINTER) { + auto it = std::find(selected_options.begin(), selected_options.end(), "extruders_count"); + if (it != selected_options.end()) { + // erase "extruders_count" option from the list + selected_options.erase(it); + // cache the extruders count + static_cast<TabPrinter*>(tab)->cache_extruder_cnt(); + } + } + tab->cache_config_diff(selected_options); + if (!is_called_from_configwizard) + tab->m_presets->discard_current_changes(); + } + if (is_called_from_configwizard) + *postponed_apply_of_keeped_changes = true; + else + apply_keeped_preset_modifications(); + } + } + } + + return true; +} + +bool GUI_App::can_load_project() +{ + int saved_project = plater()->save_project_if_dirty(_L("Loading a new project while the current project is modified.")); + if (saved_project == wxID_CANCEL || + (plater()->is_project_dirty() && saved_project == wxID_NO && + !check_and_save_current_preset_changes(_L("Project is loading"), _L("Loading a new project while some presets are modified.")))) + return false; + return true; +} + bool GUI_App::check_print_host_queue() { wxString dirty; @@ -2164,17 +2276,6 @@ void GUI_App::load_current_presets(bool check_printer_presets_ /*= true*/) } } -void GUI_App::update_wizard_from_config() -{ - if (!m_wizard) - return; - // If ConfigWizard was created before changing of the configuration, - // we have to destroy it to have possibility to create it again in respect to the new config's parameters - m_wizard->Reparent(nullptr); - m_wizard->Destroy(); - m_wizard = nullptr; -} - bool GUI_App::OnExceptionInMainLoop() { generic_exception_handle(); @@ -2336,19 +2437,12 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null"); if (reason == ConfigWizard::RR_USER) { - wxString header = _L("Updates to Configuration Wizard may cause an another preset selection and lost of preset modification as a result.\n" - "So, check unsaved changes and save them if necessary.") + "\n"; - if (!check_and_save_current_preset_changes(header, _L("ConfigWizard is opening")) || - preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED) + if (preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD) == PresetUpdater::R_ALL_CANCELED) return false; } - if (! m_wizard) { - wxBusyCursor wait; - m_wizard = new ConfigWizard(mainframe); - } - - const bool res = m_wizard->run(reason, start_page); + auto wizard = new ConfigWizard(mainframe); + const bool res = wizard->run(reason, start_page); if (res) { load_current_presets(); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 3171f3b03..60a143144 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -150,7 +150,6 @@ private: std::unique_ptr<ImGuiWrapper> m_imgui; std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue; - ConfigWizard* m_wizard; // Managed by wxWindow tree std::unique_ptr <OtherInstanceMessageHandler> m_other_instance_message_handler; std::unique_ptr <wxSingleInstanceChecker> m_single_instance_checker; std::string m_instance_hash_string; @@ -246,11 +245,13 @@ public: bool has_current_preset_changes() const; void update_saved_preset_from_current_preset(); std::vector<std::pair<unsigned int, std::string>> get_selected_presets() const; - bool check_and_save_current_preset_changes(const wxString& header = wxString(), const wxString& caption = wxString()); + bool check_and_save_current_preset_changes(const wxString& caption, const wxString& header, bool remember_choice = true, bool use_dont_save_insted_of_discard = false); + void apply_keeped_preset_modifications(); + bool check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes = nullptr); + bool can_load_project(); bool check_print_host_queue(); bool checked_tab(Tab* tab); void load_current_presets(bool check_printer_presets = true); - void update_wizard_from_config(); wxString current_language_code() const { return m_wxLocale->GetCanonicalName(); } // Translate the language code to a code, for which Prusa Research maintains translations. Defaults to "en_US". diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 7c019337e..44f04ef5f 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -4071,7 +4071,7 @@ void ObjectList::fix_through_netfabb() msg += ": " + from_u8(model_name) + "\n"; else { msg += ":\n"; - for (size_t i = 0; i < model_names.size(); ++i) + for (int i = 0; i < int(model_names.size()); ++i) msg += (i == model_idx ? " > " : " ") + from_u8(model_names[i]) + "\n"; msg += "\n"; } diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 89b6a6bea..027e9ce97 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -222,13 +222,14 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S } if (m_plater != nullptr) { - int saved_project = m_plater->save_project_if_dirty(); + int saved_project = m_plater->save_project_if_dirty(_L("Closing PrusaSlicer. Current project is modified.")); if (saved_project == wxID_CANCEL) { event.Veto(); return; } // check unsaved changes only if project wasn't saved - else if (saved_project == wxID_NO && event.CanVeto() && !wxGetApp().check_and_save_current_preset_changes()) { + else if (saved_project == wxID_NO && event.CanVeto() && + !wxGetApp().check_and_save_current_preset_changes(_L("PrusaSlicer is closing"), _L("Closing PrusaSlicer while some presets are modified."))) { event.Veto(); return; } @@ -820,7 +821,10 @@ bool MainFrame::is_active_and_shown_tab(Tab* tab) bool MainFrame::can_start_new_project() const { - return (m_plater != nullptr) && (!m_plater->get_project_filename(".3mf").IsEmpty() || GetTitle().StartsWith('*') || !m_plater->model().objects.empty()); + return m_plater && (!m_plater->get_project_filename(".3mf").IsEmpty() || + GetTitle().StartsWith('*')|| + wxGetApp().has_current_preset_changes() || + !m_plater->model().objects.empty() ); } bool MainFrame::can_save() const @@ -852,13 +856,14 @@ void MainFrame::save_project() save_project_as(m_plater->get_project_filename(".3mf")); } -void MainFrame::save_project_as(const wxString& filename) +bool MainFrame::save_project_as(const wxString& filename) { bool ret = (m_plater != nullptr) ? m_plater->export_3mf(into_path(filename)) : false; if (ret) { // wxGetApp().update_saved_preset_from_current_preset(); m_plater->reset_project_dirty_after_save(); } + return ret; } bool MainFrame::can_export_model() const @@ -1151,8 +1156,10 @@ void MainFrame::init_menubar_as_editor() Bind(wxEVT_MENU, [this](wxCommandEvent& evt) { size_t file_id = evt.GetId() - wxID_FILE1; wxString filename = m_recent_projects.GetHistoryFile(file_id); - if (wxFileExists(filename)) - m_plater->load_project(filename); + if (wxFileExists(filename)) { + if (wxGetApp().can_load_project()) + m_plater->load_project(filename); + } else { //wxMessageDialog msg(this, _L("The selected project is no longer available.\nDo you want to remove it from the recent projects list?"), _L("Error"), wxYES_NO | wxYES_DEFAULT); @@ -1772,7 +1779,7 @@ void MainFrame::export_config() // Load a config file containing a Print, Filament & Printer preset. void MainFrame::load_config_file() { - if (!wxGetApp().check_and_save_current_preset_changes()) + if (!wxGetApp().check_and_save_current_preset_changes(_L("Loading of a configuration file"), "", false)) return; wxFileDialog dlg(this, _L("Select configuration to load:"), !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), @@ -1803,7 +1810,8 @@ bool MainFrame::load_config_file(const std::string &path) void MainFrame::export_configbundle(bool export_physical_printers /*= false*/) { - if (!wxGetApp().check_and_save_current_preset_changes()) + if (!wxGetApp().check_and_save_current_preset_changes(_L("Exporting configuration bundle"), + _L("Some presets are modified and the unsaved changes will not be exported into configuration bundle."), false, true)) return; // validate current configuration in case it's dirty auto err = wxGetApp().preset_bundle->full_config().validate(); @@ -1835,7 +1843,7 @@ void MainFrame::export_configbundle(bool export_physical_printers /*= false*/) // but that behavior was not documented and likely buggy. void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool reset_user_profile*/) { - if (!wxGetApp().check_and_save_current_preset_changes()) + if (!wxGetApp().check_and_save_current_preset_changes(_L("Loading of a configuration bundle"), "", false)) return; if (file.IsEmpty()) { wxFileDialog dlg(this, _L("Select configuration to load:"), diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 487d002af..1b66a6052 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -190,7 +190,7 @@ public: bool can_save() const; bool can_save_as() const; void save_project(); - void save_project_as(const wxString& filename = wxString()); + bool save_project_as(const wxString& filename = wxString()); void add_to_recent_projects(const wxString& filename); void technology_changed(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 8ec91662f..f4b7e758a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1575,16 +1575,22 @@ struct Plater::priv bool is_project_dirty() const { return dirty_state.is_dirty(); } void update_project_dirty_from_presets() { dirty_state.update_from_presets(); } - int save_project_if_dirty() { + int save_project_if_dirty(const wxString& reason) { int res = wxID_NO; if (dirty_state.is_dirty()) { MainFrame* mainframe = wxGetApp().mainframe; if (mainframe->can_save_as()) { - //wxMessageDialog dlg(mainframe, _L("Do you want to save the changes to the current project ?"), wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL); - MessageDialog dlg(mainframe, _L("Do you want to save the changes to the current project ?"), wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL); - res = dlg.ShowModal(); + wxString suggested_project_name; + wxString project_name = suggested_project_name = get_project_filename(".3mf"); + if (suggested_project_name.IsEmpty()) { + fs::path output_file = get_export_file_path(FT_3MF); + suggested_project_name = output_file.empty() ? _L("Untitled") : from_u8(output_file.stem().string()); + } + res = MessageDialog(mainframe, reason + "\n" + format_wxstr(_L("Do you want to save the changes to \"%1%\"?"), suggested_project_name), + wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL).ShowModal(); if (res == wxID_YES) - mainframe->save_project_as(wxGetApp().plater()->get_project_filename()); + if (!mainframe->save_project_as(project_name)) + res = wxID_CANCEL; } } return res; @@ -1644,6 +1650,7 @@ struct Plater::priv std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool used_inches = false); std::vector<size_t> load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false); + fs::path get_export_file_path(GUI::FileType file_type); wxString get_export_file(GUI::FileType file_type); const Selection& get_selection() const; @@ -2599,22 +2606,8 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& mode return obj_idxs; } -wxString Plater::priv::get_export_file(GUI::FileType file_type) +fs::path Plater::priv::get_export_file_path(GUI::FileType file_type) { - wxString wildcard; - switch (file_type) { - case FT_STL: - case FT_AMF: - case FT_3MF: - case FT_GCODE: - case FT_OBJ: - wildcard = file_wildcards(file_type); - break; - default: - wildcard = file_wildcards(FT_MODEL); - break; - } - // Update printbility state of each of the ModelInstances. this->update_print_volume_state(); @@ -2639,7 +2632,31 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type) if (output_file.empty() && !model.objects.empty()) // Find the file name of the first object. output_file = this->model.objects[0]->get_export_filename(); + + if (output_file.empty()) + // Use _L("Untitled") name + output_file = into_path(_L("Untitled")); } + return output_file; +} + +wxString Plater::priv::get_export_file(GUI::FileType file_type) +{ + wxString wildcard; + switch (file_type) { + case FT_STL: + case FT_AMF: + case FT_3MF: + case FT_GCODE: + case FT_OBJ: + wildcard = file_wildcards(file_type); + break; + default: + wildcard = file_wildcards(FT_MODEL); + break; + } + + fs::path output_file = get_export_file_path(file_type); wxString dlg_title; switch (file_type) { @@ -4714,8 +4731,10 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator if (printer_technology_changed) { // Switching the printer technology when jumping forwards / backwards in time. Switch to the last active printer profile of the other type. std::string s_pt = (it_snapshot->snapshot_data.printer_technology == ptFFF) ? "FFF" : "SLA"; - if (!wxGetApp().check_and_save_current_preset_changes(format_wxstr(_L( - "%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."), s_pt))) + if (!wxGetApp().check_and_save_current_preset_changes(_L("Undo / Redo is processing"), +// format_wxstr(_L("%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."), s_pt))) + format_wxstr(_L("Switching the printer technology from %1% to %2%.\n" + "Some %1% presets were modified, which will be lost after switching the printer technology."), s_pt =="FFF" ? "SLA" : "FFF", s_pt), false)) // Don't switch the profiles. return; } @@ -4893,7 +4912,7 @@ Plater::Plater(wxWindow *parent, MainFrame *main_frame) bool Plater::is_project_dirty() const { return p->is_project_dirty(); } void Plater::update_project_dirty_from_presets() { p->update_project_dirty_from_presets(); } -int Plater::save_project_if_dirty() { return p->save_project_if_dirty(); } +int Plater::save_project_if_dirty(const wxString& reason) { return p->save_project_if_dirty(reason); } void Plater::reset_project_dirty_after_save() { p->reset_project_dirty_after_save(); } void Plater::reset_project_dirty_initial_presets() { p->reset_project_dirty_initial_presets(); } #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW @@ -4910,8 +4929,20 @@ SLAPrint& Plater::sla_print() { return p->sla_print; } void Plater::new_project() { - if (p->save_project_if_dirty() == wxID_CANCEL) + if (int saved_project = p->save_project_if_dirty(_L("Creating a new project while the current project is modified.")); saved_project == wxID_CANCEL) return; + else { + wxString header = _L("Creating a new project while some presets are modified.") + "\n" + + (saved_project == wxID_YES ? _L("You can keep presets modifications to the new project or discard them") : + _L("You can keep presets modifications to the new project, discard them or save changes as new presets.\n" + "Note, if changes will be saved than new project wouldn't keep them")); + using ab = UnsavedChangesDialog::ActionButtons; + int act_buttons = ab::KEEP; + if (saved_project == wxID_NO) + act_buttons |= ab::SAVE; + if (!wxGetApp().check_and_keep_current_preset_changes(_L("New Project is creating"), header, act_buttons)) + return; + } p->select_view_3D("3D"); take_snapshot(_L("New Project")); @@ -4923,7 +4954,7 @@ void Plater::new_project() void Plater::load_project() { - if (p->save_project_if_dirty() == wxID_CANCEL) + if (!wxGetApp().can_load_project()) return; // Ask user for a project file name. @@ -5219,7 +5250,8 @@ bool Plater::load_files(const wxArrayString& filenames) switch (load_type) { case LoadType::OpenProject: { - load_project(from_path(*it)); + if (wxGetApp().can_load_project()) + load_project(from_path(*it)); break; } case LoadType::LoadGeometry: { diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 5406a8a6e..c03681a34 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -140,7 +140,7 @@ public: bool is_project_dirty() const; void update_project_dirty_from_presets(); - int save_project_if_dirty(); + int save_project_if_dirty(const wxString& reason); void reset_project_dirty_after_save(); void reset_project_dirty_initial_presets(); #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index d8af0e887..0687399b2 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -73,7 +73,7 @@ void PreferencesDialog::build(size_t selected_tab) // Add "General" tab m_optgroup_general = create_options_tab(_L("General"), tabs); m_optgroup_general->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (opt_key == "default_action_on_close_application" || opt_key == "default_action_on_select_preset") + if (opt_key == "default_action_on_close_application" || opt_key == "default_action_on_select_preset" || opt_key == "default_action_on_new_project") m_values[opt_key] = boost::any_cast<bool>(value) ? "none" : "discard"; else m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0"; @@ -186,19 +186,28 @@ void PreferencesDialog::build(size_t selected_tab) option = Option(def, "single_instance"); m_optgroup_general->append_single_option_line(option); - def.label = L("Ask for unsaved changes when closing application"); + def.label = L("Ask for unsaved changes when closing application or loading new project"); def.type = coBool; - def.tooltip = L("When closing the application, always ask for unsaved changes"); + def.tooltip = L("Always ask for unsaved changes, when: \n" + "- Closing PrusaSlicer while some presets are modified,\n" + "- Loading a new project while some presets are modified"); def.set_default_value(new ConfigOptionBool{ app_config->get("default_action_on_close_application") == "none" }); option = Option(def, "default_action_on_close_application"); m_optgroup_general->append_single_option_line(option); def.label = L("Ask for unsaved changes when selecting new preset"); def.type = coBool; - def.tooltip = L("Always ask for unsaved changes when selecting new preset"); + def.tooltip = L("Always ask for unsaved changes when selecting new preset or resetting a preset"); def.set_default_value(new ConfigOptionBool{ app_config->get("default_action_on_select_preset") == "none" }); option = Option(def, "default_action_on_select_preset"); m_optgroup_general->append_single_option_line(option); + + def.label = L("Ask for unsaved changes when creating new project"); + def.type = coBool; + def.tooltip = L("Always ask for unsaved changes when creating new project"); + def.set_default_value(new ConfigOptionBool{ app_config->get("default_action_on_new_project") == "none" }); + option = Option(def, "default_action_on_new_project"); + m_optgroup_general->append_single_option_line(option); } #ifdef _WIN32 else { diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 5effa3599..4ee733942 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1216,12 +1216,20 @@ void Tab::cache_config_diff(const std::vector<std::string>& selected_options) void Tab::apply_config_from_cache() { + bool was_applied = false; + // check and apply extruders count for printer preset + if (m_type == Preset::TYPE_PRINTER) + was_applied = static_cast<TabPrinter*>(this)->apply_extruder_cnt_from_cache(); + if (!m_cache_config.empty()) { m_presets->get_edited_preset().config.apply(m_cache_config); m_cache_config.clear(); - update_dirty(); + was_applied = true; } + + if (was_applied) + update_dirty(); } @@ -3322,10 +3330,6 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, m_dependent_tabs = { Preset::Type::TYPE_SLA_PRINT, Preset::Type::TYPE_SLA_MATERIAL }; } - // check and apply extruders count for printer preset - if (m_type == Preset::TYPE_PRINTER) - static_cast<TabPrinter*>(this)->apply_extruder_cnt_from_cache(); - // check if there is something in the cache to move to the new selected preset apply_config_from_cache(); @@ -3862,15 +3866,17 @@ void TabPrinter::cache_extruder_cnt() m_cache_extruder_count = m_extruders_count; } -void TabPrinter::apply_extruder_cnt_from_cache() +bool TabPrinter::apply_extruder_cnt_from_cache() { if (m_presets->get_edited_preset().printer_technology() == ptSLA) - return; + return false; if (m_cache_extruder_count > 0) { m_presets->get_edited_preset().set_num_extruders(m_cache_extruder_count); m_cache_extruder_count = 0; + return true; } + return false; } bool Tab::validate_custom_gcodes() diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 74585f2aa..5b7983711 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -469,7 +469,7 @@ public: wxSizer* create_bed_shape_widget(wxWindow* parent); void cache_extruder_cnt(); - void apply_extruder_cnt_from_cache(); + bool apply_extruder_cnt_from_cache(); }; class TabSLAMaterial : public Tab diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index ef480f60f..40cfa548a 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -727,14 +727,21 @@ void DiffViewCtrl::item_value_changed(wxDataViewEvent& event) m_empty_selection = selected_options().empty(); } -std::vector<std::string> DiffViewCtrl::unselected_options(Preset::Type type) +bool DiffViewCtrl::has_unselected_options() +{ + for (auto item : m_items_map) + if (!model->IsEnabledItem(item.first)) + return true; + + return false; +} + +std::vector<std::string> DiffViewCtrl::options(Preset::Type type, bool selected) { std::vector<std::string> ret; for (auto item : m_items_map) { - if (item.second.opt_key == "extruders_count") - continue; - if (item.second.type == type && !model->IsEnabledItem(item.first)) + if (item.second.type == type && model->IsEnabledItem(item.first) == selected) ret.emplace_back(get_pure_opt_key(item.second.opt_key)); } @@ -757,20 +764,24 @@ std::vector<std::string> DiffViewCtrl::selected_options() // UnsavedChangesDialog //------------------------------------------ -UnsavedChangesDialog::UnsavedChangesDialog(const wxString& header, const wxString& caption/* = wxString()*/) - : DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, (caption.IsEmpty() ? _L("PrusaSlicer is closing") : caption) + ": " + _L("Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) -{ - m_app_config_key = "default_action_on_close_application"; +static std::string none{"none"}; +UnsavedChangesDialog::UnsavedChangesDialog(const wxString& caption, const wxString& header, + const std::string& app_config_key, int act_buttons) + : DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, caption + ": " + _L("Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), + m_app_config_key(app_config_key), + m_buttons(act_buttons) +{ build(Preset::TYPE_INVALID, nullptr, "", header); - const std::string& def_action = wxGetApp().app_config->get(m_app_config_key); - if (def_action == "none") + const std::string& def_action = m_app_config_key.empty() ? none : wxGetApp().app_config->get(m_app_config_key); + if (def_action == none) this->CenterOnScreen(); else { - m_exit_action = def_action == ActSave ? Action::Save : Action::Discard; - if (m_exit_action == Action::Save) - save(nullptr); + m_exit_action = def_action == ActTransfer ? Action::Transfer : + def_action == ActSave ? Action::Save : Action::Discard; + if (m_exit_action != Action::Discard) + save(nullptr, m_exit_action == Action::Save); } } @@ -782,7 +793,7 @@ UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, PresetCollection* build(type, dependent_presets, new_selected_preset); const std::string& def_action = wxGetApp().app_config->get(m_app_config_key); - if (def_action == "none") { + if (def_action == none) { if (wxGetApp().mainframe->is_dlg_layout() && wxGetApp().mainframe->m_settings_dialog.HasFocus()) this->SetPosition(wxGetApp().mainframe->m_settings_dialog.GetPosition()); this->CenterOnScreen(); @@ -833,7 +844,8 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ (*btn)->Bind(wxEVT_BUTTON, [this, close_act, dependent_presets](wxEvent&) { update_config(close_act); - if (close_act == Action::Save && !save(dependent_presets)) + bool save_names_and_types = close_act == Action::Save || (close_act == Action::Transfer && ActionButtons::KEEP & m_buttons); + if (save_names_and_types && !save(dependent_presets, close_act == Action::Save)) return; close(close_act); }); @@ -842,13 +854,26 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ (*btn)->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& e) { show_info_line(Action::Undef); e.Skip(); }); }; - const PresetCollection* switched_presets = type == Preset::TYPE_INVALID ? nullptr : wxGetApp().get_tab(type)->get_presets(); - if (dependent_presets && switched_presets && (type == dependent_presets->type() ? - dependent_presets->get_edited_preset().printer_technology() == dependent_presets->find_preset(new_selected_preset)->printer_technology() : - switched_presets->get_edited_preset().printer_technology() == switched_presets->find_preset(new_selected_preset)->printer_technology())) - add_btn(&m_transfer_btn, m_move_btn_id, "paste_menu", Action::Transfer, _L("Transfer")); - add_btn(&m_discard_btn, m_continue_btn_id, dependent_presets ? "switch_presets" : "exit", Action::Discard, _L("Discard"), false); - add_btn(&m_save_btn, m_save_btn_id, "save", Action::Save, _L("Save")); + // "Transfer" / "Keep" button + if (ActionButtons::TRANSFER & m_buttons) { + const PresetCollection* switched_presets = type == Preset::TYPE_INVALID ? nullptr : wxGetApp().get_tab(type)->get_presets(); + if (dependent_presets && switched_presets && (type == dependent_presets->type() ? + dependent_presets->get_edited_preset().printer_technology() == dependent_presets->find_preset(new_selected_preset)->printer_technology() : + switched_presets->get_edited_preset().printer_technology() == switched_presets->find_preset(new_selected_preset)->printer_technology())) + add_btn(&m_transfer_btn, m_move_btn_id, "paste_menu", Action::Transfer, switched_presets->get_edited_preset().name == new_selected_preset ? _L("Keep") : _L("Transfer")); + } + if (!m_transfer_btn && (ActionButtons::KEEP & m_buttons)) + add_btn(&m_transfer_btn, m_move_btn_id, "paste_menu", Action::Transfer, _L("Keep")); + + { // "Don't save" / "Discard" button + std::string btn_icon = (ActionButtons::DONT_SAVE & m_buttons) ? "" : (dependent_presets || (ActionButtons::KEEP & m_buttons)) ? "switch_presets" : "exit"; + wxString btn_label = (ActionButtons::DONT_SAVE & m_buttons) ? _L("Don't save") : _L("Discard"); + add_btn(&m_discard_btn, m_continue_btn_id, btn_icon, Action::Discard, btn_label, false); + } + + // "Save" button + if (ActionButtons::SAVE & m_buttons) + add_btn(&m_save_btn, m_save_btn_id, "save", Action::Save, _L("Save")); ScalableButton* cancel_btn = new ScalableButton(this, wxID_CANCEL, "cross", _L("Cancel"), wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true, 24); buttons->Add(cancel_btn, 1, wxLEFT|wxRIGHT, 5); @@ -859,34 +884,42 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ m_info_line->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold()); m_info_line->Hide(); - m_remember_choice = new wxCheckBox(this, wxID_ANY, _L("Remember my choice")); - m_remember_choice->SetValue(wxGetApp().app_config->get(m_app_config_key) != "none"); - m_remember_choice->Bind(wxEVT_CHECKBOX, [type, this](wxCommandEvent& evt) - { - if (!evt.IsChecked()) - return; - wxString preferences_item = type == Preset::TYPE_INVALID ? _L("Ask for unsaved changes when closing application") : - _L("Ask for unsaved changes when selecting new preset"); - wxString msg = - _L("PrusaSlicer will remember your action.") + "\n\n" + - (type == Preset::TYPE_INVALID ? - _L("You will not be asked about the unsaved changes the next time you close PrusaSlicer.") : - _L("You will not be asked about the unsaved changes the next time you switch a preset.")) + "\n\n" + - format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto be asked about unsaved changes again."), preferences_item); + if (!m_app_config_key.empty()) { + m_remember_choice = new wxCheckBox(this, wxID_ANY, _L("Remember my choice")); + m_remember_choice->SetValue(wxGetApp().app_config->get(m_app_config_key) != none); + m_remember_choice->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& evt) + { + if (!evt.IsChecked()) + return; + wxString preferences_item = m_app_config_key == "default_action_on_new_project" ? _L("Ask for unsaved changes when creating new project") : + m_app_config_key == "default_action_on_select_preset" ? _L("Ask for unsaved changes when selecting new preset") : + _L("Ask for unsaved changes when ??closing application??") ; + wxString action = m_app_config_key == "default_action_on_new_project" ? _L("You will not be asked about the unsaved changes the next time you create new project") : + m_app_config_key == "default_action_on_select_preset" ? _L("You will not be asked about the unsaved changes the next time you switch a preset") : + _L("You will not be asked about the unsaved changes the next time you: \n" + "- close the application,\n" + "- load project,\n" + "- process Undo / Redo with change of print technologie,\n" + "- take/load snapshot,\n" + "- load config file/bundle,\n" + "- export config_bundle") ; + wxString msg = _L("PrusaSlicer will remember your action.") + "\n\n" + action + "\n\n" + + format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto be asked about unsaved changes again."), preferences_item); - //wxMessageDialog dialog(nullptr, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION); - MessageDialog dialog(nullptr, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION); - if (dialog.ShowModal() == wxID_CANCEL) - m_remember_choice->SetValue(false); - }); + MessageDialog dialog(nullptr, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION); + if (dialog.ShowModal() == wxID_CANCEL) + m_remember_choice->SetValue(false); + }); + } wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); topSizer->Add(m_action_line,0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(m_tree, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); topSizer->Add(m_info_line, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 2*border); - topSizer->Add(buttons, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); - topSizer->Add(m_remember_choice, 0, wxEXPAND | wxALL, border); + topSizer->Add(buttons, 0, wxEXPAND | wxALL, border); + if (m_remember_choice) + topSizer->Add(m_remember_choice, 0, wxEXPAND | wxLEFT | wxBOTTOM | wxRIGHT, border); update(type, dependent_presets, new_selected_preset, header); @@ -905,10 +938,12 @@ void UnsavedChangesDialog::show_info_line(Action action, std::string preset_name if (action == Action::Undef) text = _L("Some fields are too long to fit. Right mouse click reveals the full text."); else if (action == Action::Discard) - text = _L("All settings changes will be discarded."); + text = ActionButtons::DONT_SAVE & m_buttons ? _L("All settings changes will not be saved") :_L("All settings changes will be discarded."); else { if (preset_name.empty()) - text = action == Action::Save ? _L("Save the selected options.") : _L("Transfer the selected settings to the newly selected preset."); + text = action == Action::Save ? _L("Save the selected options.") : + ActionButtons::KEEP & m_buttons ? _L("Keep the selected settings.") : + _L("Transfer the selected settings to the newly selected preset."); else text = format_wxstr( action == Action::Save ? @@ -927,7 +962,7 @@ void UnsavedChangesDialog::show_info_line(Action action, std::string preset_name void UnsavedChangesDialog::update_config(Action action) { - if (!m_remember_choice->GetValue()) + if (!m_remember_choice || !m_remember_choice->GetValue()) return; std::string act = action == Action::Transfer ? ActTransfer : @@ -941,7 +976,7 @@ void UnsavedChangesDialog::close(Action action) this->EndModal(wxID_CLOSE); } -bool UnsavedChangesDialog::save(PresetCollection* dependent_presets) +bool UnsavedChangesDialog::save(PresetCollection* dependent_presets, bool show_save_preset_dialog/* = true*/) { names_and_types.clear(); @@ -979,7 +1014,7 @@ bool UnsavedChangesDialog::save(PresetCollection* dependent_presets) } - if (!types_for_save.empty()) { + if (show_save_preset_dialog && !types_for_save.empty()) { SavePresetDialog save_dlg(this, types_for_save); if (save_dlg.ShowModal() != wxID_OK) { m_exit_action = Action::Discard; @@ -1164,16 +1199,24 @@ void UnsavedChangesDialog::update(Preset::Type type, PresetCollection* dependent PresetCollection* presets = dependent_presets; // activate buttons and labels - m_save_btn ->Bind(wxEVT_ENTER_WINDOW, [this, presets] (wxMouseEvent& e) { show_info_line(Action::Save, presets ? presets->get_selected_preset().name : ""); e.Skip(); }); + if (m_save_btn) + m_save_btn ->Bind(wxEVT_ENTER_WINDOW, [this, presets] (wxMouseEvent& e) { show_info_line(Action::Save, presets ? presets->get_selected_preset().name : ""); e.Skip(); }); if (m_transfer_btn) { - bool is_empty_name = type != dependent_presets->type(); - m_transfer_btn ->Bind(wxEVT_ENTER_WINDOW, [this, new_selected_preset, is_empty_name] (wxMouseEvent& e) { show_info_line(Action::Transfer, is_empty_name ? "" : new_selected_preset); e.Skip(); }); + bool is_empty_name = dependent_presets && type != dependent_presets->type(); + m_transfer_btn->Bind(wxEVT_ENTER_WINDOW, [this, new_selected_preset, is_empty_name](wxMouseEvent& e) { show_info_line(Action::Transfer, is_empty_name ? "" : new_selected_preset); e.Skip(); }); } - m_discard_btn ->Bind(wxEVT_ENTER_WINDOW, [this] (wxMouseEvent& e) { show_info_line(Action::Discard); e.Skip(); }); - + if (m_discard_btn) + m_discard_btn ->Bind(wxEVT_ENTER_WINDOW, [this] (wxMouseEvent& e) { show_info_line(Action::Discard); e.Skip(); }); if (type == Preset::TYPE_INVALID) { - m_action_line->SetLabel(header + "\n" + _L("The following presets were modified:")); + PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology(); + int presets_cnt = 0; + for (Tab* tab : wxGetApp().tabs_list) + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) + presets_cnt++; + m_action_line->SetLabel((header.IsEmpty() ? "" : header + "\n\n") + //_L("The following presets were modified:")); + + _L_PLURAL("The following preset was modified", + "The following presets were modified", presets_cnt)); } else { wxString action_msg; diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp index 9dbaf6e99..42f93f660 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.hpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -220,8 +220,9 @@ public: void context_menu(wxDataViewEvent& event); void item_value_changed(wxDataViewEvent& event); void set_em_unit(int em) { m_em_unit = em; } + bool has_unselected_options(); - std::vector<std::string> unselected_options(Preset::Type type); + std::vector<std::string> options(Preset::Type type, bool selected); std::vector<std::string> selected_options(); }; @@ -261,10 +262,22 @@ class UnsavedChangesDialog : public DPIDialog Action m_exit_action {Action::Undef}; // preset names which are modified in SavePresetDialog and related types std::vector<std::pair<std::string, Preset::Type>> names_and_types; + // additional action buttons used in dialog + int m_buttons { ActionButtons::TRANSFER | ActionButtons::SAVE }; public: - UnsavedChangesDialog(const wxString& header, const wxString& caption = wxString()); + // Discard and Cancel buttons are always but next buttons are optional + enum ActionButtons { + TRANSFER = 1, + KEEP = 2, + SAVE = 4, + DONT_SAVE = 8, + }; + + // show unsaved changes when preset is switching UnsavedChangesDialog(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset); + // show unsaved changes for all another cases + UnsavedChangesDialog(const wxString& caption, const wxString& header, const std::string& app_config_key, int act_buttons); ~UnsavedChangesDialog() {} void build(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header = ""); @@ -273,7 +286,8 @@ public: void show_info_line(Action action, std::string preset_name = ""); void update_config(Action action); void close(Action action); - bool save(PresetCollection* dependent_presets); + // save information about saved presets and their types to names_and_types and show SavePresetDialog to set the names for new presets + bool save(PresetCollection* dependent_presets, bool show_save_preset_dialog = true); bool save_preset() const { return m_exit_action == Action::Save; } bool transfer_changes() const { return m_exit_action == Action::Transfer; } @@ -284,8 +298,10 @@ public: // short version of the previous function, for the case, when just one preset is modified std::string get_preset_name() { return names_and_types[0].first; } - std::vector<std::string> get_unselected_options(Preset::Type type) { return m_tree->unselected_options(type); } + std::vector<std::string> get_unselected_options(Preset::Type type) { return m_tree->options(type, false); } + std::vector<std::string> get_selected_options (Preset::Type type) { return m_tree->options(type, true); } std::vector<std::string> get_selected_options() { return m_tree->selected_options(); } + bool has_unselected_options() { return m_tree->has_unselected_options(); } protected: void on_dpi_changed(const wxRect& suggested_rect) override; diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 29d474dca..97c6cb2a5 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -720,8 +720,13 @@ void PresetUpdater::slic3r_update_notify() } } -static void reload_configs_update_gui() +static bool reload_configs_update_gui() { + wxString header = _L("Configuration Updates causes a lost of preset modification.\n" + "So, check unsaved changes and save them if necessary."); + if (!GUI::wxGetApp().check_and_save_current_preset_changes(_L("Updater is processing"), header, false )) + return false; + // Reload global configuration auto* app_config = GUI::wxGetApp().app_config; // System profiles should not trigger any substitutions, user profiles may trigger substitutions, but these substitutions @@ -730,7 +735,8 @@ static void reload_configs_update_gui() GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem); GUI::wxGetApp().load_current_presets(); GUI::wxGetApp().plater()->set_bed_shape(); - GUI::wxGetApp().update_wizard_from_config(); + + return true; } PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, UpdateParams params) const @@ -803,9 +809,9 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3 const auto res = dlg.ShowModal(); if (res == wxID_OK) { BOOST_LOG_TRIVIAL(info) << "User wants to update..."; - if (! p->perform_updates(std::move(updates))) + if (! p->perform_updates(std::move(updates)) || + ! reload_configs_update_gui()) return R_INCOMPAT_EXIT; - reload_configs_update_gui(); return R_UPDATE_INSTALLED; } else { @@ -833,9 +839,9 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3 const auto res = dlg.ShowModal(); if (res == wxID_OK) { BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; - if (! p->perform_updates(std::move(updates))) + if (! p->perform_updates(std::move(updates)) || + ! reload_configs_update_gui()) return R_ALL_CANCELED; - reload_configs_update_gui(); return R_UPDATE_INSTALLED; } else { @@ -886,8 +892,8 @@ void PresetUpdater::on_update_notification_confirm() const auto res = dlg.ShowModal(); if (res == wxID_OK) { BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; - if (p->perform_updates(std::move(p->waiting_updates))) { - reload_configs_update_gui(); + if (p->perform_updates(std::move(p->waiting_updates)) && + reload_configs_update_gui()) { p->has_waiting_updates = false; } }