From 5d4b7c03b603945cec03e270faca14f957f08cb0 Mon Sep 17 00:00:00 2001 From: enricoturri1966 Date: Tue, 6 Apr 2021 13:17:29 +0200 Subject: [PATCH] Extended interface of project dirty state manager --- src/slic3r/GUI/GUI_App.cpp | 8 ++ src/slic3r/GUI/GUI_ObjectList.cpp | 10 +- src/slic3r/GUI/MainFrame.cpp | 48 +++++++++ src/slic3r/GUI/MainFrame.hpp | 9 ++ src/slic3r/GUI/Plater.cpp | 103 +++++++++++++++++++- src/slic3r/GUI/Plater.hpp | 10 ++ src/slic3r/GUI/ProjectDirtyStateManager.cpp | 20 ++++ src/slic3r/GUI/ProjectDirtyStateManager.hpp | 10 ++ src/slic3r/GUI/Tab.cpp | 7 +- 9 files changed, 219 insertions(+), 6 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b22cd6009..793ef80b7 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -904,6 +904,14 @@ bool GUI_App::on_init_inner() } else load_current_presets(); + +#if ENABLE_PROJECT_DIRTY_STATE + if (plater_ != nullptr) { +// plater_->reset_project_initial_presets(); + plater_->update_project_dirty_from_presets(); + } +#endif // ENABLE_PROJECT_DIRTY_STATE + mainframe->Show(true); obj_list()->set_min_height(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 58560c8bd..bce64f9ea 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -7,6 +7,9 @@ #include "GUI_App.hpp" #include "I18N.hpp" #include "Plater.hpp" +#if ENABLE_PROJECT_DIRTY_STATE +#include "MainFrame.hpp" +#endif // ENABLE_PROJECT_DIRTY_STATE #include "OptionsGroup.hpp" #include "Tab.hpp" @@ -1457,12 +1460,15 @@ void ObjectList::load_shape_object(const std::string& type_name) if (obj_idx < 0) return; - take_snapshot(_(L("Add Shape"))); + take_snapshot(_L("Add Shape")); // Create mesh BoundingBoxf3 bb; TriangleMesh mesh = create_mesh(type_name, bb); - load_mesh_object(mesh, _(L("Shape")) + "-" + _(type_name)); + load_mesh_object(mesh, _L("Shape") + "-" + _(type_name)); +#if ENABLE_PROJECT_DIRTY_STATE + wxGetApp().mainframe->update_title(); +#endif // ENABLE_PROJECT_DIRTY_STATE } void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center) diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 35b1c16d8..832ebc257 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -206,6 +206,11 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S // declare events Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) { +#if ENABLE_PROJECT_DIRTY_STATE + if (m_plater != nullptr) + m_plater->save_project_if_dirty(); +#endif // ENABLE_PROJECT_DIRTY_STATE + if (event.CanVeto() && !wxGetApp().check_unsaved_changes()) { event.Veto(); return; @@ -487,8 +492,14 @@ void MainFrame::update_title() // m_plater->get_project_filename() produces file name including path, but excluding extension. // Don't try to remove the extension, it would remove part of the file name after the last dot! wxString project = from_path(into_path(m_plater->get_project_filename()).filename()); +#if ENABLE_PROJECT_DIRTY_STATE + wxString dirty_marker = (!m_plater->model().objects.empty() && m_plater->is_project_dirty()) ? "*" : ""; + if (!dirty_marker.empty() || !project.empty()) + title = dirty_marker + project + " - "; +#else if (!project.empty()) title += (project + " - "); +#endif // ENABLE_PROJECT_DIRTY_STATE } std::string build_id = wxGetApp().is_editor() ? SLIC3R_BUILD_ID : GCODEVIEWER_BUILD_ID; @@ -668,10 +679,36 @@ bool MainFrame::can_start_new_project() const return (m_plater != nullptr) && (!m_plater->get_project_filename(".3mf").IsEmpty() || !m_plater->model().objects.empty()); } +#if ENABLE_PROJECT_DIRTY_STATE +bool MainFrame::can_save() const +{ + return (m_plater != nullptr) && !m_plater->model().objects.empty() && !m_plater->get_project_filename().empty() && m_plater->is_project_dirty(); +} + +bool MainFrame::can_save_as() const +{ + return (m_plater != nullptr) && !m_plater->model().objects.empty(); +} + +void MainFrame::save_project() +{ + save_project_as(m_plater->get_project_filename(".3mf")); +} + +void 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(); + } +} +#else bool MainFrame::can_save() const { return (m_plater != nullptr) && !m_plater->model().objects.empty(); } +#endif // ENABLE_PROJECT_DIRTY_STATE bool MainFrame::can_export_model() const { @@ -977,16 +1014,27 @@ void MainFrame::init_menubar_as_editor() Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_recent_projects.GetCount() > 0); }, recent_projects_submenu->GetId()); +#if ENABLE_PROJECT_DIRTY_STATE + append_menu_item(fileMenu, wxID_ANY, _L("&Save Project") + "\tCtrl+S", _L("Save current project file"), + [this](wxCommandEvent&) { save_project(); }, "save", nullptr, + [this](){return m_plater != nullptr && can_save(); }, this); +#else append_menu_item(fileMenu, wxID_ANY, _L("&Save Project") + "\tCtrl+S", _L("Save current project file"), [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename(".3mf"))); }, "save", nullptr, [this](){return m_plater != nullptr && can_save(); }, this); +#endif // ENABLE_PROJECT_DIRTY_STATE #ifdef __APPLE__ append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Shift+S", _L("Save current project file as"), #else append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Alt+S", _L("Save current project file as"), #endif // __APPLE__ +#if ENABLE_PROJECT_DIRTY_STATE + [this](wxCommandEvent&) { save_project_as(); }, "save", nullptr, + [this](){return m_plater != nullptr && can_save_as(); }, this); +#else [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(); }, "save", nullptr, [this](){return m_plater != nullptr && can_save(); }, this); +#endif // ENABLE_PROJECT_DIRTY_STATE fileMenu->AppendSeparator(); diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 0971fdc77..307cdf1ae 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -91,7 +91,9 @@ class MainFrame : public DPIFrame void on_value_changed(wxCommandEvent&); bool can_start_new_project() const; +#if !ENABLE_PROJECT_DIRTY_STATE bool can_save() const; +#endif // !ENABLE_PROJECT_DIRTY_STATE bool can_export_model() const; bool can_export_toolpaths() const; bool can_export_supports() const; @@ -184,6 +186,13 @@ public: // Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig void on_config_changed(DynamicPrintConfig* cfg) const ; +#if ENABLE_PROJECT_DIRTY_STATE + bool can_save() const; + bool can_save_as() const; + void save_project(); + void save_project_as(const wxString& filename = wxString()); +#endif // ENABLE_PROJECT_DIRTY_STATE + void add_to_recent_projects(const wxString& filename); PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 0434d2555..9de4641c0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1393,7 +1393,13 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi this->MSWUpdateDragImageOnLeave(); #endif // WIN32 +#if ENABLE_PROJECT_DIRTY_STATE + bool res = (m_plater != nullptr) ? m_plater->load_files(filenames) : false; + wxGetApp().mainframe->update_title(); + return res; +#else return (m_plater != nullptr) ? m_plater->load_files(filenames) : false; +#endif // ENABLE_PROJECT_DIRTY_STATE } // State to manage showing after export notifications and device ejecting @@ -1511,9 +1517,26 @@ struct Plater::priv priv(Plater *q, MainFrame *main_frame); ~priv(); +#if ENABLE_PROJECT_DIRTY_STATE + bool is_project_dirty() const { return dirty_state.is_dirty(); } + void update_project_dirty_from_presets() { dirty_state.update_from_presets(); } + bool save_project_if_dirty() { + 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); + if (dlg.ShowModal() == wxID_CANCEL) + return false; + mainframe->save_project_as(wxGetApp().plater()->get_project_filename()); + } + } + return true; + } + void reset_project_dirty_after_save() { dirty_state.reset_after_save(); } #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void render_project_state_debug_window() const { dirty_state.render_debug_window(); } #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +#endif // ENABLE_PROJECT_DIRTY_STATE enum class UpdateParams { FORCE_FULL_SCREEN_REFRESH = 1, @@ -4216,6 +4239,11 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name) } this->undo_redo_stack().take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), snapshot_data); this->undo_redo_stack().release_least_recently_used(); + +#if ENABLE_PROJECT_DIRTY_STATE + dirty_state.update_from_undo_redo_stack(undo_redo_stack_main(), undo_redo_stack()); +#endif // ENABLE_PROJECT_DIRTY_STATE + // Save the last active preset name of a particular printer technology. ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info(); @@ -4346,6 +4374,10 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active) view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); } + +#if ENABLE_PROJECT_DIRTY_STATE + dirty_state.update_from_undo_redo_stack(undo_redo_stack_main(), undo_redo_stack()); +#endif // ENABLE_PROJECT_DIRTY_STATE } void Plater::priv::update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bool /* temp_snapshot_was_taken */) @@ -4429,9 +4461,15 @@ Plater::Plater(wxWindow *parent, MainFrame *main_frame) // Initialization performed in the private c-tor } +#if ENABLE_PROJECT_DIRTY_STATE +bool Plater::is_project_dirty() const { return p->is_project_dirty(); } +void Plater::update_project_dirty_from_presets() { p->update_project_dirty_from_presets(); } +bool Plater::save_project_if_dirty() { return p->save_project_if_dirty(); } +void Plater::reset_project_dirty_after_save() { p->reset_project_dirty_after_save(); } #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void Plater::render_project_state_debug_window() const { p->render_project_state_debug_window(); } #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +#endif // ENABLE_PROJECT_DIRTY_STATE Sidebar& Plater::sidebar() { return *p->sidebar; } Model& Plater::model() { return p->model; } @@ -4442,12 +4480,30 @@ SLAPrint& Plater::sla_print() { return p->sla_print; } void Plater::new_project() { +#if ENABLE_PROJECT_DIRTY_STATE + if (!p->save_project_if_dirty()) + return; +#endif // ENABLE_PROJECT_DIRTY_STATE + p->select_view_3D("3D"); +#if ENABLE_PROJECT_DIRTY_STATE + take_snapshot(_L("New Project")); + Plater::SuppressSnapshots suppress(this); + reset(); +// reset_project_initial_presets(); + update_project_dirty_from_presets(); +#else wxPostEvent(p->view3D->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); +#endif // ENABLE_PROJECT_DIRTY_STATE } void Plater::load_project() { +#if ENABLE_PROJECT_DIRTY_STATE + if (!p->save_project_if_dirty()) + return; +#endif // ENABLE_PROJECT_DIRTY_STATE + // Ask user for a project file name. wxString input_file; wxGetApp().load_project(this, input_file); @@ -4471,8 +4527,16 @@ void Plater::load_project(const wxString& filename) std::vector res = load_files(input_paths); // if res is empty no data has been loaded +#if ENABLE_PROJECT_DIRTY_STATE + if (!res.empty()) { + p->set_project_filename(filename); +// reset_project_initial_presets(); + update_project_dirty_from_presets(); + } +#else if (!res.empty()) p->set_project_filename(filename); +#endif // ENABLE_PROJECT_DIRTY_STATE } void Plater::add_model(bool imperial_units/* = false*/) @@ -4503,7 +4567,13 @@ void Plater::add_model(bool imperial_units/* = false*/) } Plater::TakeSnapshot snapshot(this, snapshot_label); +#if ENABLE_PROJECT_DIRTY_STATE + std::vector res = load_files(paths, true, false, imperial_units); + if (!res.empty()) + wxGetApp().mainframe->update_title(); +#else load_files(paths, true, false, imperial_units); +#endif // ENABLE_PROJECT_DIRTY_STATE } void Plater::import_sl1_archive() @@ -5187,24 +5257,39 @@ void Plater::export_amf() } } +#if ENABLE_PROJECT_DIRTY_STATE +bool Plater::export_3mf(const boost::filesystem::path& output_path) +#else void Plater::export_3mf(const boost::filesystem::path& output_path) +#endif // ENABLE_PROJECT_DIRTY_STATE { if (p->model.objects.empty() || canvas3D()->get_gizmos_manager().is_in_editing_mode(true)) +#if ENABLE_PROJECT_DIRTY_STATE + return false; +#else return; +#endif // ENABLE_PROJECT_DIRTY_STATE wxString path; bool export_config = true; - if (output_path.empty()) - { + if (output_path.empty()) { path = p->get_export_file(FT_3MF); +#if ENABLE_PROJECT_DIRTY_STATE + if (path.empty()) { return false; } +#else if (path.empty()) { return; } +#endif // ENABLE_PROJECT_DIRTY_STATE } else path = from_path(output_path); if (!path.Lower().EndsWith(".3mf")) +#if ENABLE_PROJECT_DIRTY_STATE + return false; +#else return; +#endif // ENABLE_PROJECT_DIRTY_STATE DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); const std::string path_u8 = into_u8(path); @@ -5212,6 +5297,19 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1"; ThumbnailData thumbnail_data; p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true); +#if ENABLE_PROJECT_DIRTY_STATE + bool ret = Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data); + if (ret) { + // Success + p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path)); + p->set_project_filename(path); + } + else { + // Failure + p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path)); + } + return ret; +#else if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data)) { // Success p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path)); @@ -5221,6 +5319,7 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) // Failure p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path)); } +#endif // ENABLE_PROJECT_DIRTY_STATE } void Plater::reload_from_disk() diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index f2d60d801..ead9679c7 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -130,9 +130,15 @@ public: Plater &operator=(const Plater &) = delete; ~Plater() = default; +#if ENABLE_PROJECT_DIRTY_STATE + bool is_project_dirty() const; + void update_project_dirty_from_presets(); + bool save_project_if_dirty(); + void reset_project_dirty_after_save(); #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void render_project_state_debug_window() const; #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +#endif // ENABLE_PROJECT_DIRTY_STATE Sidebar& sidebar(); Model& model(); @@ -201,7 +207,11 @@ public: void export_gcode(bool prefer_removable); void export_stl(bool extended = false, bool selection_only = false); void export_amf(); +#if ENABLE_PROJECT_DIRTY_STATE + bool export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); +#else void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); +#endif // ENABLE_PROJECT_DIRTY_STATE void reload_from_disk(); void reload_all_from_disk(); bool has_toolpaths_to_export() const; diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp index 9a19676b2..5cf7274bb 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.cpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -2,12 +2,32 @@ #include "ProjectDirtyStateManager.hpp" #include "ImGuiWrapper.hpp" #include "GUI_App.hpp" +#include "MainFrame.hpp" #if ENABLE_PROJECT_DIRTY_STATE namespace Slic3r { namespace GUI { +void ProjectDirtyStateManager::update_from_undo_redo_stack(const Slic3r::UndoRedo::Stack& main_stack, const Slic3r::UndoRedo::Stack& active_stack) +{ + if (!wxGetApp().initialized()) + return; + + wxGetApp().mainframe->update_title(); +} + +void ProjectDirtyStateManager::update_from_presets() +{ + wxGetApp().mainframe->update_title(); +} + +void ProjectDirtyStateManager::reset_after_save() +{ + m_state.reset(); + wxGetApp().mainframe->update_title(); +} + #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void ProjectDirtyStateManager::render_debug_window() const { diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.hpp b/src/slic3r/GUI/ProjectDirtyStateManager.hpp index 81ac28915..b488c00bb 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.hpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.hpp @@ -4,6 +4,9 @@ #if ENABLE_PROJECT_DIRTY_STATE namespace Slic3r { +namespace UndoRedo { +class Stack; +} // namespace UndoRedo namespace GUI { class ProjectDirtyStateManager @@ -14,12 +17,19 @@ class ProjectDirtyStateManager bool presets{ false }; bool is_dirty() const { return plater || presets; } + void reset() { + plater = false; + presets = false; + } }; DirtyState m_state; public: bool is_dirty() const { return m_state.is_dirty(); } + void update_from_undo_redo_stack(const Slic3r::UndoRedo::Stack& main_stack, const Slic3r::UndoRedo::Stack& active_stack); + void update_from_presets(); + void reset_after_save(); #if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW void render_debug_window() const; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 11c4875eb..97117f418 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1215,9 +1215,8 @@ void Tab::apply_config_from_cache() // to update number of "filament" selection boxes when the number of extruders change. void Tab::on_presets_changed() { - if (wxGetApp().plater() == nullptr) { + if (wxGetApp().plater() == nullptr) return; - } // Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets wxGetApp().plater()->sidebar().update_presets(m_type); @@ -1235,6 +1234,10 @@ void Tab::on_presets_changed() // clear m_dependent_tabs after first update from select_preset() // to avoid needless preset loading from update() function m_dependent_tabs.clear(); + +#if ENABLE_PROJECT_DIRTY_STATE + wxGetApp().plater()->update_project_dirty_from_presets(); +#endif // ENABLE_PROJECT_DIRTY_STATE } void Tab::build_preset_description_line(ConfigOptionsGroup* optgroup)