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
This commit is contained in:
Oleksandra Yushchenko 2021-09-22 12:44:13 +02:00 committed by GitHub
parent 846b868215
commit 8f064dd155
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 462 additions and 178 deletions

View file

@ -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");

View file

@ -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();

View file

@ -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__

View file

@ -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;
}

View file

@ -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();

View file

@ -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".

View file

@ -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";
}

View file

@ -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:"),

View file

@ -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();

View file

@ -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: {

View file

@ -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

View file

@ -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 {

View file

@ -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()

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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;
}
}