diff --git a/CMakeLists.txt b/CMakeLists.txt index a37e7a1b92..8557ab0d7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -496,7 +496,7 @@ foreach(po_file ${L10N_PO_FILES}) add_custom_command( TARGET gettext_merge_po_with_pot PRE_BUILD COMMAND msgmerge -N -o ${po_file} ${po_file} "${L10N_DIR}/PrusaSlicer.pot" - # delete obsolit lines from resulting PO to avoid conflicts after a merging of it with wxWidgets.po + # delete obsolete lines from resulting PO to avoid conflicts after a merging of it with wxWidgets.po COMMAND msgattrib --no-obsolete -o ${po_file} ${po_file} DEPENDS ${po_file} ) @@ -516,7 +516,7 @@ foreach(po_file ${L10N_PO_FILES}) add_custom_command( TARGET gettext_concat_wx_po_with_po PRE_BUILD COMMAND msgcat --use-first -o ${po_file} ${po_file} ${wx_po_file} - # delete obsolit lines from resulting PO + # delete obsolete lines from resulting PO COMMAND msgattrib --no-obsolete -o ${po_file} ${po_file} DEPENDS ${po_file} ) diff --git a/build_win.bat b/build_win.bat index 1a735d7cdd..de5030ee50 100644 --- a/build_win.bat +++ b/build_win.bat @@ -10,6 +10,7 @@ @ECHO [-PRODUCT ^] [-DESTDIR ^] @ECHO [-STEPS ^] @ECHO [-RUN ^] +@ECHO [-PRIORITY ^] @ECHO. @ECHO -a -ARCH Target processor architecture @ECHO Default: %PS_ARCH_HOST% @@ -38,6 +39,8 @@ @ECHO -d -DESTDIR Deps destination directory @ECHO Warning: Changing destdir path will not delete the old destdir. @ECHO Default: %PS_DESTDIR_DEFAULT_MSG% +@ECHO -p -PRIORITY Build CPU priority +@ECHO Default: normal @ECHO. @ECHO Examples: @ECHO. @@ -86,6 +89,7 @@ SET PS_RUN=none SET PS_DESTDIR= SET PS_VERSION= SET PS_PRODUCT=%PS_PRODUCT_DEFAULT% +SET PS_PRIORITY=normal CALL :RESOLVE_DESTDIR_CACHE REM Set up parameters used by help menu @@ -99,7 +103,7 @@ SET EXIT_STATUS=1 SET PS_CURRENT_STEP=arguments SET PARSER_STATE= SET PARSER_FAIL= -FOR %%I in (%*) DO CALL :PARSE_OPTION "ARCH CONFIG DESTDIR STEPS RUN VERSION PRODUCT" PARSER_STATE "%%~I" +FOR %%I in (%*) DO CALL :PARSE_OPTION "ARCH CONFIG DESTDIR STEPS RUN VERSION PRODUCT PRIORITY" PARSER_STATE "%%~I" IF "%PARSER_FAIL%" NEQ "" ( @ECHO ERROR: Invalid switch: %PARSER_FAIL% 1>&2 GOTO :HELP @@ -114,6 +118,9 @@ CALL :TOLOWER PS_ARCH SET PS_ARCH=%PS_ARCH:amd64=x64% CALL :PARSE_OPTION_VALUE %PS_CONFIG_LIST:;= % PS_CONFIG IF "%PS_CONFIG%" EQU "" GOTO :HELP +CALL :PARSE_OPTION_VALUE "normal low" PS_PRIORITY +SET PS_PRIORITY=%PS_PRIORITY:normal= % +SET PS_PRIORITY=%PS_PRIORITY:low=-low% REM RESOLVE_DESTDIR_CACHE must go after PS_ARCH and PS_CONFIG, but before PS STEPS CALL :RESOLVE_DESTDIR_CACHE IF "%PS_STEPS%" EQU "" SET PS_STEPS=%PS_STEPS_DEFAULT% @@ -200,7 +207,7 @@ IF %ERRORLEVEL% NEQ 0 IF "%PS_STEPS_DIRTY%" NEQ "" ( (del CMakeCache.txt && cmake.exe .. -DDESTDIR="%PS_DESTDIR%") || GOTO :END ) ELSE GOTO :END (echo %PS_DESTDIR%)> "%PS_DEPS_PATH_FILE%" -msbuild /m ALL_BUILD.vcxproj /p:Configuration=%PS_CONFIG% /v:quiet || GOTO :END +msbuild /m ALL_BUILD.vcxproj /p:Configuration=%PS_CONFIG% /v:quiet %PS_PRIORITY% || GOTO :END cd ..\.. IF /I "%PS_STEPS:~0,4%" EQU "deps" GOTO :RUN_APP @@ -223,7 +230,7 @@ IF %ERRORLEVEL% NEQ 0 IF "%PS_STEPS_DIRTY%" NEQ "" ( (del CMakeCache.txt && cmake.exe .. -DCMAKE_PREFIX_PATH="%PS_DESTDIR%\usr\local" -DCMAKE_CONFIGURATION_TYPES=%PS_CONFIG_LIST%) || GOTO :END ) ELSE GOTO :END REM Skip the build step if we're using the undocumented app-cmake to regenerate the full config from inside devenv -IF "%PS_STEPS%" NEQ "app-cmake" msbuild /m ALL_BUILD.vcxproj /p:Configuration=%PS_CONFIG% /v:quiet || GOTO :END +IF "%PS_STEPS%" NEQ "app-cmake" msbuild /m ALL_BUILD.vcxproj /p:Configuration=%PS_CONFIG% /v:quiet %PS_PRIORITY% || GOTO :END (echo %PS_DESTDIR%)> "%PS_DEPS_PATH_FILE_FOR_CONFIG%" REM Run app diff --git a/src/libslic3r/GCode/FindReplace.cpp b/src/libslic3r/GCode/FindReplace.cpp index 9f9852f048..85b027b795 100644 --- a/src/libslic3r/GCode/FindReplace.cpp +++ b/src/libslic3r/GCode/FindReplace.cpp @@ -24,11 +24,11 @@ const void unescape_extended_search_mode(std::string &s) GCodeFindReplace::GCodeFindReplace(const std::vector &gcode_substitutions) { - if ((gcode_substitutions.size() % 3) != 0) + if ((gcode_substitutions.size() % 4) != 0) throw RuntimeError("Invalid length of gcode_substitutions parameter"); - m_substitutions.reserve(gcode_substitutions.size() / 3); - for (size_t i = 0; i < gcode_substitutions.size(); i += 3) { + m_substitutions.reserve(gcode_substitutions.size() / 4); + for (size_t i = 0; i < gcode_substitutions.size(); i += 4) { Substitution out; try { out.plain_pattern = gcode_substitutions[i]; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index d632db268f..5eea58a0fe 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1709,7 +1709,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) }; auto extract_move_id = [&biased_seams_ids](size_t id) { - size_t new_id = -1; + size_t new_id = size_t(-1); auto it = std::lower_bound(biased_seams_ids.begin(), biased_seams_ids.end(), id); if (it == biased_seams_ids.end()) new_id = id + biased_seams_ids.size(); @@ -1719,7 +1719,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result) else if (it != biased_seams_ids.begin()) new_id = id + std::distance(biased_seams_ids.begin(), it); } - return (new_id == -1) ? id : new_id; + return (new_id == size_t(-1)) ? id : new_id; }; const size_t vertex_size_floats = t_buffer.vertices.vertex_size_floats(); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 4dde41d906..b55ba0d75e 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -770,7 +770,9 @@ void GUI_App::post_init() // This is ugly but I honestly found no better way to do it. // Neither wxShowEvent nor wxWindowCreateEvent work reliably. if (this->preset_updater) { // G-Code Viewer does not initialize preset_updater. - this->check_updates(false); + if (! this->check_updates(false)) + // Configuration is not compatible and reconfigure was refused by the user. Application is closing. + return; CallAfter([this] { bool cw_showed = this->config_wizard_startup(); this->preset_updater->sync(preset_bundle); @@ -789,6 +791,10 @@ void GUI_App::post_init() }); } + // Set PrusaSlicer version and save to PrusaSlicer.ini or PrusaSlicerGcodeViewer.ini. + app_config->set("version", SLIC3R_VERSION); + app_config->save(); + #ifdef _WIN32 // Sets window property to mainframe so other instances can indentify it. OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); @@ -1033,6 +1039,8 @@ bool GUI_App::OnInit() } } +static bool update_gui_after_init = true; + bool GUI_App::on_init_inner() { // Set initialization of image handlers before any UI actions - See GH issue #7469 @@ -1178,12 +1186,10 @@ bool GUI_App::on_init_inner() // supplied as argument to --datadir; in that case we should still run the wizard preset_bundle->setup_directories(); - if (! older_data_dir_path.empty()) + if (! older_data_dir_path.empty()) { preset_bundle->import_newer_configs(older_data_dir_path); - - // Save PrusaSlicer.ini after possibly copying the config from the alternate location and after all the configs from the alternate location were copied. - app_config->set("version", SLIC3R_VERSION); - app_config->save(); + app_config->save(); + } if (is_editor()) { #ifdef __WXMSW__ @@ -1299,13 +1305,8 @@ bool GUI_App::on_init_inner() if (! plater_) return; - if (app_config->dirty() && app_config->get("autosave") == "1") - app_config->save(); - this->obj_manipul()->update_if_dirty(); - static bool update_gui_after_init = true; - // An ugly solution to GH #5537 in which GUI_App::init_opengl (normally called from events wxEVT_PAINT // and wxEVT_SET_FOCUS before GUI_App::post_init is called) wasn't called before GUI_App::post_init and OpenGL wasn't initialized. #ifdef __linux__ @@ -1319,6 +1320,9 @@ bool GUI_App::on_init_inner() #endif this->post_init(); } + + if (! update_gui_after_init && app_config->dirty() && app_config->get("autosave") == "1") + app_config->save(); }); m_initialized = true; @@ -1404,6 +1408,7 @@ void GUI_App::init_label_colours() m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT); m_color_highlight_default = is_dark_mode ? wxColour(78, 78, 78) : wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); m_color_hovered_btn_label = is_dark_mode ? wxColour(253, 111, 40) : wxColour(252, 77, 1); + m_color_default_btn_label = is_dark_mode ? wxColour(255, 181, 100): wxColour(203, 61, 0); m_color_selected_btn_bg = is_dark_mode ? wxColour(95, 73, 62) : wxColour(228, 220, 216); #else m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); @@ -1438,12 +1443,22 @@ static bool is_focused(HWND hWnd) HWND hFocusedWnd = ::GetFocus(); return hFocusedWnd && hWnd == hFocusedWnd; } + +static bool is_default(wxWindow* win) +{ + wxTopLevelWindow* tlw = find_toplevel_parent(win); + if (!tlw) + return false; + + return win == tlw->GetDefaultItem(); +} #endif void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool just_font/* = false*/) { #ifdef _WIN32 bool is_focused_button = false; + bool is_default_button = false; if (wxButton* btn = dynamic_cast(window)) { if (!(btn->GetWindowStyle() & wxNO_BORDER)) { btn->SetWindowStyle(btn->GetWindowStyle() | wxNO_BORDER); @@ -1455,7 +1470,7 @@ void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool ju if (btn->GetLabel().IsEmpty()) btn->SetBackgroundColour(mark ? m_color_selected_btn_bg : highlited ? m_color_highlight_default : m_color_window_default); else - btn->SetForegroundColour(mark ? m_color_hovered_btn_label : m_color_label_default); + btn->SetForegroundColour(mark ? m_color_hovered_btn_label : (is_default(btn) ? m_color_default_btn_label : m_color_label_default)); btn->Refresh(); btn->Update(); }; @@ -1467,8 +1482,10 @@ void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool ju btn->Bind(wxEVT_SET_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(true); event.Skip(); }); btn->Bind(wxEVT_KILL_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(false); event.Skip(); }); - if (is_focused_button = is_focused(btn->GetHWND())) - mark_button(true); + is_focused_button = is_focused(btn->GetHWND()); + is_default_button = is_default(btn); + if (is_focused_button || is_default_button) + mark_button(is_focused_button); } } else if (wxTextCtrl* text = dynamic_cast(window)) { @@ -1490,7 +1507,7 @@ void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool ju if (!just_font) window->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); - if (!is_focused_button) + if (!is_focused_button && !is_default_button) window->SetForegroundColour(m_color_label_default); #endif } @@ -3003,13 +3020,15 @@ bool GUI_App::config_wizard_startup() return false; } -void GUI_App::check_updates(const bool verbose) +bool GUI_App::check_updates(const bool verbose) { PresetUpdater::UpdateResult updater_result; try { updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION); if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) { mainframe->Close(); + // Applicaiton is closing. + return false; } else if (updater_result == PresetUpdater::R_INCOMPAT_CONFIGURED) { m_app_conf_exists = true; @@ -3022,6 +3041,8 @@ void GUI_App::check_updates(const bool verbose) catch (const std::exception & ex) { show_error(nullptr, ex.what()); } + // Applicaiton will continue. + return true; } bool GUI_App::open_browser_with_warning_dialog(const wxString& url, wxWindow* parent/* = nullptr*/, bool force_remember_choice /*= true*/, int flags/* = 0*/) diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index bdbe420704..ac80fc8b2d 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -124,6 +124,7 @@ private: #ifdef _WIN32 wxColour m_color_highlight_label_default; wxColour m_color_hovered_btn_label; + wxColour m_color_default_btn_label; wxColour m_color_highlight_default; wxColour m_color_selected_btn_bg; bool m_force_colors_update { false }; @@ -352,7 +353,9 @@ private: bool select_language(); bool config_wizard_startup(); - void check_updates(const bool verbose); + // Returns true if the configuration is fine. + // Returns true if the configuration is not compatible and the user decided to rather close the slicer instead of reconfiguring. + bool check_updates(const bool verbose); bool m_datadir_redefined { false }; }; diff --git a/src/slic3r/GUI/HintNotification.cpp b/src/slic3r/GUI/HintNotification.cpp index 3e98374af9..93e0fb3259 100644 --- a/src/slic3r/GUI/HintNotification.cpp +++ b/src/slic3r/GUI/HintNotification.cpp @@ -14,6 +14,7 @@ #include "libslic3r/Config.hpp" #include "libslic3r/PrintConfig.hpp" +#include #include #include #include @@ -342,6 +343,9 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path) //unescape text1 unescape_string_cstyle(dict["text"], fulltext); fulltext = _utf8(fulltext); +#ifdef __APPLE__ + boost::replace_all(fulltext, "Ctrl+", "⌘"); +#endif //__APPLE__ // replace and for imgui markers std::string marker_s(1, ImGui::ColorMarkerStart); std::string marker_e(1, ImGui::ColorMarkerEnd); diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index 9d80f9dd41..b53fe5c474 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -37,7 +37,8 @@ static const char *CONFIG_KEY_PATH = "printhost_path"; static const char *CONFIG_KEY_GROUP = "printhost_group"; PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUploadActions post_actions, const wxArrayString &groups) - : MsgDialog(static_cast(wxGetApp().mainframe), _L("Send G-Code to printer host"), _L("Upload to Printer Host with the following filename:")) + : MsgDialog(static_cast(wxGetApp().mainframe), _L("Send G-Code to printer host"), _L("Upload to Printer Host with the following filename:"), 0) // Set style = 0 to avoid default creation of the "OK" button. + // All buttons will be added later in this constructor , txt_filename(new wxTextCtrl(this, wxID_ANY)) , combo_groups(!groups.IsEmpty() ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, groups, wxCB_READONLY) : nullptr) , post_upload_action(PrintHostPostUploadAction::None) @@ -74,7 +75,6 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo const auto stem_len = stem.Length(); txt_filename->SetValue(recent_path); - txt_filename->SetFocus(); if (size_t extension_start = recent_path.find_last_of('.'); extension_start != std::string::npos) m_valid_suffix = recent_path.substr(extension_start); @@ -88,6 +88,15 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo return true; }; + auto* btn_ok = add_button(wxID_OK, true, _L("Upload")); + btn_ok->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) { + if (validate_path(txt_filename->GetValue())) { + post_upload_action = PrintHostPostUploadAction::None; + EndDialog(wxID_OK); + } + }); + txt_filename->SetFocus(); + if (post_actions.has(PrintHostPostUploadAction::StartPrint)) { auto* btn_print = add_button(wxID_YES, false, _L("Upload and Print")); btn_print->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) { @@ -110,16 +119,6 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo } add_button(wxID_CANCEL); - - if (auto* btn_ok = get_button(wxID_OK); btn_ok != NULL) { - btn_ok->SetLabel(_L("Upload")); - btn_ok->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) { - if (validate_path(txt_filename->GetValue())) { - post_upload_action = PrintHostPostUploadAction::None; - EndDialog(wxID_OK); - } - }); - } finalize(); #ifdef __linux__ @@ -137,6 +136,7 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo // Another similar case where the function only works with EVT_SHOW + CallAfter, // this time on Mac. CallAfter([=]() { + txt_filename->SetInsertionPoint(0); txt_filename->SetSelection(recent_path_len, recent_path_len + stem_len); }); }); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index babcec0715..d1b7c6769b 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1661,7 +1661,7 @@ void TabPrint::build() optgroup = page->new_optgroup(L("Other")); - create_line_with_widget(optgroup.get(), "gcode_substitutions", "", [this](wxWindow* parent) { + create_line_with_widget(optgroup.get(), "gcode_substitutions", "g-code-substitutions_301694", [this](wxWindow* parent) { return create_manage_substitution_widget(parent); }); line = { "", "" }; @@ -3866,17 +3866,17 @@ void SubstitutionManager::init(DynamicPrintConfig* config, wxWindow* parent, wxF void SubstitutionManager::validate_lenth() { std::vector& substitutions = m_config->option("gcode_substitutions")->values; - if ((substitutions.size() % 3) != 0) { + if ((substitutions.size() % 4) != 0) { WarningDialog(m_parent, "Value of gcode_substitutions parameter will be cut to valid length", "Invalid length of gcode_substitutions parameter").ShowModal(); - substitutions.resize(substitutions.size() - (substitutions.size() % 3)); + substitutions.resize(substitutions.size() - (substitutions.size() % 4)); } } bool SubstitutionManager::is_compatibile_with_ui() { const std::vector& substitutions = m_config->option("gcode_substitutions")->values; - if (int(substitutions.size() / 3) != m_grid_sizer->GetEffectiveRowsCount() - 1) { + if (int(substitutions.size() / 4) != m_grid_sizer->GetEffectiveRowsCount() - 1) { ErrorDialog(m_parent, "Invalid compatibility between UI and BE", false).ShowModal(); return false; } @@ -3886,7 +3886,7 @@ bool SubstitutionManager::is_compatibile_with_ui() bool SubstitutionManager::is_valid_id(int substitution_id, const wxString& message) { const std::vector& substitutions = m_config->option("gcode_substitutions")->values; - if (int(substitutions.size() / 3) < substitution_id) { + if (int(substitutions.size() / 4) < substitution_id) { ErrorDialog(m_parent, message, false).ShowModal(); return false; } @@ -3899,11 +3899,14 @@ void SubstitutionManager::create_legend() return; // name of the first column is empty m_grid_sizer->Add(new wxStaticText(m_parent, wxID_ANY, wxEmptyString)); + // Legend for another columns - for (const std::string col : { L("Find"), L("Replace with"), L("Options") }) { - auto temp = new wxStaticText(m_parent, wxID_ANY, _(col), wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_MIDDLE); - m_grid_sizer->Add(temp); - } + auto legend_sizer = new wxBoxSizer(wxHORIZONTAL); // "Find", "Replace", "Notes" + legend_sizer->Add(new wxStaticText(m_parent, wxID_ANY, _L("Find")), 3, wxEXPAND); + legend_sizer->Add(new wxStaticText(m_parent, wxID_ANY, _L("Replace with")), 3, wxEXPAND); + legend_sizer->Add(new wxStaticText(m_parent, wxID_ANY, _L("Notes")), 2, wxEXPAND); + + m_grid_sizer->Add(legend_sizer, 1, wxEXPAND); } // delete substitution_id from substitutions @@ -3915,7 +3918,7 @@ void SubstitutionManager::delete_substitution(int substitution_id) // delete substitution std::vector& substitutions = m_config->option("gcode_substitutions")->values; - substitutions.erase(std::next(substitutions.begin(), substitution_id * 3), std::next(substitutions.begin(), substitution_id * 3 + 3)); + substitutions.erase(std::next(substitutions.begin(), substitution_id * 4), std::next(substitutions.begin(), substitution_id * 4 + 4)); call_ui_update(); // update grid_sizer @@ -3923,7 +3926,11 @@ void SubstitutionManager::delete_substitution(int substitution_id) } // Add substitution line -void SubstitutionManager::add_substitution(int substitution_id, const std::string& plain_pattern, const std::string& format, const std::string& params) +void SubstitutionManager::add_substitution( int substitution_id, + const std::string& plain_pattern, + const std::string& format, + const std::string& params, + const std::string& notes) { bool call_after_layout = false; @@ -3937,7 +3944,7 @@ void SubstitutionManager::add_substitution(int substitution_id, const std::strin // create new substitution // it have to be added to config too std::vector& substitutions = m_config->option("gcode_substitutions")->values; - for (size_t i = 0; i < 3; i ++) + for (size_t i = 0; i < 4; i ++) substitutions.push_back(std::string()); call_after_layout = true; @@ -3948,9 +3955,10 @@ void SubstitutionManager::add_substitution(int substitution_id, const std::strin delete_substitution(substitution_id); }); - m_grid_sizer->Add(del_btn, 0, wxRIGHT | wxLEFT, m_em); + m_grid_sizer->Add(del_btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxLEFT, int(0.5*m_em)); - auto add_text_editor = [substitution_id, this](const wxString& value, int opt_pos) { + auto top_sizer = new wxBoxSizer(wxHORIZONTAL); + auto add_text_editor = [substitution_id, top_sizer, this](const wxString& value, int opt_pos, int proportion) { auto editor = new wxTextCtrl(m_parent, wxID_ANY, value, wxDefaultPosition, wxSize(15 * m_em, wxDefaultCoord), wxTE_PROCESS_ENTER #ifdef _WIN32 | wxBORDER_SIMPLE @@ -3959,7 +3967,7 @@ void SubstitutionManager::add_substitution(int substitution_id, const std::strin editor->SetFont(wxGetApp().normal_font()); wxGetApp().UpdateDarkUI(editor); - m_grid_sizer->Add(editor, 0, wxALIGN_CENTER_VERTICAL); + top_sizer->Add(editor, proportion, wxALIGN_CENTER_VERTICAL | wxEXPAND| wxRIGHT, m_em); editor->Bind(wxEVT_TEXT_ENTER, [this, editor, substitution_id, opt_pos](wxEvent& e) { #if !defined(__WXGTK__) @@ -3974,8 +3982,9 @@ void SubstitutionManager::add_substitution(int substitution_id, const std::strin }); }; - add_text_editor(from_u8(plain_pattern), 0); - add_text_editor(from_u8(format), 1); + add_text_editor(from_u8(plain_pattern), 0, 3); + add_text_editor(from_u8(format), 1, 3); + add_text_editor(from_u8(notes), 3, 2); auto params_sizer = new wxBoxSizer(wxHORIZONTAL); bool regexp = strchr(params.c_str(), 'r') != nullptr || strchr(params.c_str(), 'R') != nullptr; @@ -4020,7 +4029,10 @@ void SubstitutionManager::add_substitution(int substitution_id, const std::strin }); } - m_grid_sizer->Add(params_sizer); + auto v_sizer = new wxBoxSizer(wxVERTICAL); + v_sizer->Add(top_sizer, 1, wxEXPAND); + v_sizer->Add(params_sizer, 1, wxEXPAND|wxTOP|wxBOTTOM, int(0.5* m_em)); + m_grid_sizer->Add(v_sizer, 1, wxALIGN_CENTER_VERTICAL | wxEXPAND); if (call_after_layout) { m_parent->GetParent()->Layout(); @@ -4034,14 +4046,16 @@ void SubstitutionManager::update_from_config() m_grid_sizer->Clear(true); std::vector& subst = m_config->option("gcode_substitutions")->values; - if (!subst.empty()) + if (subst.empty()) + hide_delete_all_btn(); + else create_legend(); validate_lenth(); int subst_id = 0; - for (size_t i = 0; i < subst.size(); i += 3) - add_substitution(subst_id++, subst[i], subst[i + 1], subst[i + 2]); + for (size_t i = 0; i < subst.size(); i += 4) + add_substitution(subst_id++, subst[i], subst[i + 1], subst[i + 2], subst[i + 3]); m_parent->GetParent()->Layout(); } @@ -4065,7 +4079,7 @@ void SubstitutionManager::edit_substitution(int substitution_id, int opt_pos, co if(!is_compatibile_with_ui() || !is_valid_id(substitution_id, "Invalid substitution_id to edit")) return; - substitutions[substitution_id * 3 + opt_pos] = value; + substitutions[substitution_id * 4 + opt_pos] = value; call_ui_update(); } @@ -4108,20 +4122,21 @@ wxSizer* TabPrint::create_manage_substitution_widget(wxWindow* parent) // Return a callback to create a TabPrint widget to edit G-code substitutions wxSizer* TabPrint::create_substitutions_widget(wxWindow* parent) { - wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(4, 5, wxGetApp().em_unit()); // delete_button, "Old val", "New val", "Params" - grid_sizer->SetFlexibleDirection(wxHORIZONTAL); + wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(2, 5, wxGetApp().em_unit()); // delete_button, edit column contains "Find", "Replace", "Notes" + grid_sizer->SetFlexibleDirection(wxBOTH); + grid_sizer->AddGrowableCol(1); m_subst_manager.init(m_config, parent, grid_sizer); m_subst_manager.set_cb_edited_substitution([this]() { update_dirty(); wxGetApp().mainframe->on_config_changed(m_config); // invalidate print }); - - auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(grid_sizer, 0, wxALIGN_CENTER_VERTICAL); + m_subst_manager.set_cb_hide_delete_all_btn([this]() { + m_del_all_substitutions_btn->Hide(); + }); parent->GetParent()->Layout(); - return sizer; + return grid_sizer; } // Return a callback to create a TabPrinter widget to edit bed shape diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 1f0137090d..7bdbcdb825 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -54,14 +54,15 @@ class SubstitutionManager int m_em{10}; std::function m_cb_edited_substitution{ nullptr }; + std::function m_cb_hide_delete_all_btn{ nullptr }; void validate_lenth(); bool is_compatibile_with_ui(); bool is_valid_id(int substitution_id, const wxString& message); public: - SubstitutionManager() {}; - ~SubstitutionManager() {}; + SubstitutionManager() = default; + ~SubstitutionManager() = default; void init(DynamicPrintConfig* config, wxWindow* parent, wxFlexGridSizer* grid_sizer); void create_legend(); @@ -69,7 +70,8 @@ public: void add_substitution( int substitution_id = -1, const std::string& plain_pattern = std::string(), const std::string& format = std::string(), - const std::string& params = std::string()); + const std::string& params = std::string(), + const std::string& notes = std::string()); void update_from_config(); void delete_all(); void edit_substitution(int substitution_id, @@ -82,6 +84,13 @@ public: if (m_cb_edited_substitution) m_cb_edited_substitution(); } + void set_cb_hide_delete_all_btn(std::function cb_hide_delete_all_btn) { + m_cb_hide_delete_all_btn = cb_hide_delete_all_btn; + } + void hide_delete_all_btn() { + if (m_cb_hide_delete_all_btn) + m_cb_hide_delete_all_btn(); + } bool is_empty_substitutions(); }; diff --git a/tests/fff_print/test_gcodefindreplace.cpp b/tests/fff_print/test_gcodefindreplace.cpp index e8a69bee21..1d714d1c33 100644 --- a/tests/fff_print/test_gcodefindreplace.cpp +++ b/tests/fff_print/test_gcodefindreplace.cpp @@ -15,7 +15,7 @@ SCENARIO("Find/Replace with plain text", "[GCodeFindReplace]") { "G1 X13 Y32 Z1; infill\n" "G1 X13 Y32 Z1; wipe\n"; WHEN("Replace \"move up\" with \"move down\", case sensitive") { - GCodeFindReplace find_replace({ "move up", "move down", "" }); + GCodeFindReplace find_replace({ "move up", "move down", "", "" }); REQUIRE(find_replace.process_layer(gcode) == "G1 Z0; home\n" // substituted @@ -25,7 +25,7 @@ SCENARIO("Find/Replace with plain text", "[GCodeFindReplace]") { "G1 X13 Y32 Z1; wipe\n"); } WHEN("Replace \"move up\" with \"move down\", case insensitive") { - GCodeFindReplace find_replace({ "move up", "move down", "i" }); + GCodeFindReplace find_replace({ "move up", "move down", "i", "" }); REQUIRE(find_replace.process_layer(gcode) == "G1 Z0; home\n" // substituted @@ -35,7 +35,7 @@ SCENARIO("Find/Replace with plain text", "[GCodeFindReplace]") { "G1 X13 Y32 Z1; wipe\n"); } WHEN("Replace \"move UP\" with \"move down\", case insensitive") { - GCodeFindReplace find_replace({ "move UP", "move down", "i" }); + GCodeFindReplace find_replace({ "move UP", "move down", "i", "" }); REQUIRE(find_replace.process_layer(gcode) == "G1 Z0; home\n" // substituted @@ -45,13 +45,13 @@ SCENARIO("Find/Replace with plain text", "[GCodeFindReplace]") { "G1 X13 Y32 Z1; wipe\n"); } WHEN("Replace \"move up\" with \"move down\", case sensitive") { - GCodeFindReplace find_replace({ "move UP", "move down", "" }); + GCodeFindReplace find_replace({ "move UP", "move down", "", "" }); REQUIRE(find_replace.process_layer(gcode) == gcode); } // Whole word WHEN("Replace \"move up\" with \"move down\", whole word") { - GCodeFindReplace find_replace({ "move up", "move down", "w" }); + GCodeFindReplace find_replace({ "move up", "move down", "w", "" }); REQUIRE(find_replace.process_layer(gcode) == "G1 Z0; home\n" // substituted @@ -61,17 +61,17 @@ SCENARIO("Find/Replace with plain text", "[GCodeFindReplace]") { "G1 X13 Y32 Z1; wipe\n"); } WHEN("Replace \"move u\" with \"move down\", whole word") { - GCodeFindReplace find_replace({ "move u", "move down", "w" }); + GCodeFindReplace find_replace({ "move u", "move down", "w", "" }); REQUIRE(find_replace.process_layer(gcode) == gcode); } WHEN("Replace \"ove up\" with \"move down\", whole word") { - GCodeFindReplace find_replace({ "move u", "move down", "w" }); + GCodeFindReplace find_replace({ "move u", "move down", "w", "" }); REQUIRE(find_replace.process_layer(gcode) == gcode); } // Multi-line replace WHEN("Replace \"move up\\nG1 X0 \" with \"move down\\nG0 X1 \"") { - GCodeFindReplace find_replace({ "move up\\nG1 X0 ", "move down\\nG0 X1 ", "" }); + GCodeFindReplace find_replace({ "move up\\nG1 X0 ", "move down\\nG0 X1 ", "", "" }); REQUIRE(find_replace.process_layer(gcode) == "G1 Z0; home\n" // substituted @@ -82,7 +82,7 @@ SCENARIO("Find/Replace with plain text", "[GCodeFindReplace]") { } // Multi-line replace, whole word. WHEN("Replace \"move up\\nG1 X0\" with \"move down\\nG0 X1\", whole word") { - GCodeFindReplace find_replace({ "move up\\nG1 X0", "move down\\nG0 X1", "w" }); + GCodeFindReplace find_replace({ "move up\\nG1 X0", "move down\\nG0 X1", "w", "" }); REQUIRE(find_replace.process_layer(gcode) == "G1 Z0; home\n" // substituted @@ -93,7 +93,7 @@ SCENARIO("Find/Replace with plain text", "[GCodeFindReplace]") { } // Multi-line replace, whole word, fails. WHEN("Replace \"move up\\nG1 X\" with \"move down\\nG0 X\", whole word") { - GCodeFindReplace find_replace({ "move up\\nG1 X", "move down\\nG0 X", "w" }); + GCodeFindReplace find_replace({ "move up\\nG1 X", "move down\\nG0 X", "w", "" }); REQUIRE(find_replace.process_layer(gcode) == gcode); } } @@ -104,7 +104,7 @@ SCENARIO("Find/Replace with plain text", "[GCodeFindReplace]") { "G1 Z1.21; move up\n" "G1 X0 Y.33 Z.431 E1.2; perimeter\n"; WHEN("Regular expression NOT processed in non-regex mode") { - GCodeFindReplace find_replace({ "( [XYZEF]-?)\\.([0-9]+)", "\\10.\\2", "" }); + GCodeFindReplace find_replace({ "( [XYZEF]-?)\\.([0-9]+)", "\\10.\\2", "", "" }); REQUIRE(find_replace.process_layer(gcode) == gcode); } } @@ -119,7 +119,7 @@ SCENARIO("Find/Replace with regexp", "[GCodeFindReplace]") { "G1 X13 Y32 Z1; infill\n" "G1 X13 Y32 Z1; wipe\n"; WHEN("Replace \"move up\" with \"move down\", case sensitive") { - GCodeFindReplace find_replace({ "move up", "move down", "r" }); + GCodeFindReplace find_replace({ "move up", "move down", "r", "" }); REQUIRE(find_replace.process_layer(gcode) == "G1 Z0; home\n" // substituted @@ -129,7 +129,7 @@ SCENARIO("Find/Replace with regexp", "[GCodeFindReplace]") { "G1 X13 Y32 Z1; wipe\n"); } WHEN("Replace \"move up\" with \"move down\", case insensitive") { - GCodeFindReplace find_replace({ "move up", "move down", "ri" }); + GCodeFindReplace find_replace({ "move up", "move down", "ri", "" }); REQUIRE(find_replace.process_layer(gcode) == "G1 Z0; home\n" // substituted @@ -139,7 +139,7 @@ SCENARIO("Find/Replace with regexp", "[GCodeFindReplace]") { "G1 X13 Y32 Z1; wipe\n"); } WHEN("Replace \"move UP\" with \"move down\", case insensitive") { - GCodeFindReplace find_replace({ "move UP", "move down", "ri" }); + GCodeFindReplace find_replace({ "move UP", "move down", "ri", "" }); REQUIRE(find_replace.process_layer(gcode) == "G1 Z0; home\n" // substituted @@ -149,13 +149,13 @@ SCENARIO("Find/Replace with regexp", "[GCodeFindReplace]") { "G1 X13 Y32 Z1; wipe\n"); } WHEN("Replace \"move up\" with \"move down\", case sensitive") { - GCodeFindReplace find_replace({ "move UP", "move down", "r" }); + GCodeFindReplace find_replace({ "move UP", "move down", "r", "" }); REQUIRE(find_replace.process_layer(gcode) == gcode); } // Whole word WHEN("Replace \"move up\" with \"move down\", whole word") { - GCodeFindReplace find_replace({ "move up", "move down", "rw" }); + GCodeFindReplace find_replace({ "move up", "move down", "rw", "" }); REQUIRE(find_replace.process_layer(gcode) == "G1 Z0; home\n" // substituted @@ -165,17 +165,17 @@ SCENARIO("Find/Replace with regexp", "[GCodeFindReplace]") { "G1 X13 Y32 Z1; wipe\n"); } WHEN("Replace \"move u\" with \"move down\", whole word") { - GCodeFindReplace find_replace({ "move u", "move down", "rw" }); + GCodeFindReplace find_replace({ "move u", "move down", "rw", "" }); REQUIRE(find_replace.process_layer(gcode) == gcode); } WHEN("Replace \"ove up\" with \"move down\", whole word") { - GCodeFindReplace find_replace({ "move u", "move down", "rw" }); + GCodeFindReplace find_replace({ "move u", "move down", "rw", "" }); REQUIRE(find_replace.process_layer(gcode) == gcode); } // Multi-line replace WHEN("Replace \"move up\\nG1 X0 \" with \"move down\\nG0 X1 \"") { - GCodeFindReplace find_replace({ "move up\\nG1 X0 ", "move down\\nG0 X1 ", "r" }); + GCodeFindReplace find_replace({ "move up\\nG1 X0 ", "move down\\nG0 X1 ", "r", "" }); REQUIRE(find_replace.process_layer(gcode) == "G1 Z0; home\n" // substituted @@ -186,7 +186,7 @@ SCENARIO("Find/Replace with regexp", "[GCodeFindReplace]") { } // Multi-line replace, whole word. WHEN("Replace \"move up\\nG1 X0\" with \"move down\\nG0 X1\", whole word") { - GCodeFindReplace find_replace({ "move up\\nG1 X0", "move down\\nG0 X1", "rw" }); + GCodeFindReplace find_replace({ "move up\\nG1 X0", "move down\\nG0 X1", "rw", "" }); REQUIRE(find_replace.process_layer(gcode) == "G1 Z0; home\n" // substituted @@ -197,7 +197,7 @@ SCENARIO("Find/Replace with regexp", "[GCodeFindReplace]") { } // Multi-line replace, whole word, fails. WHEN("Replace \"move up\\nG1 X\" with \"move down\\nG0 X\", whole word") { - GCodeFindReplace find_replace({ "move up\\nG1 X", "move down\\nG0 X", "rw" }); + GCodeFindReplace find_replace({ "move up\\nG1 X", "move down\\nG0 X", "rw", "" }); REQUIRE(find_replace.process_layer(gcode) == gcode); } } @@ -208,7 +208,7 @@ SCENARIO("Find/Replace with regexp", "[GCodeFindReplace]") { "G1 Z1.21; move up\n" "G1 X0 Y.33 Z.431 E1.2; perimeter\n"; WHEN("Missing zeros before dot filled in") { - GCodeFindReplace find_replace({ "( [XYZEF]-?)\\.([0-9]+)", "\\10.\\2", "r" }); + GCodeFindReplace find_replace({ "( [XYZEF]-?)\\.([0-9]+)", "\\10.\\2", "r", "" }); REQUIRE(find_replace.process_layer(gcode) == "G1 Z0.123; home\n" "G1 Z1.21; move up\n" @@ -237,7 +237,7 @@ SCENARIO("Find/Replace with regexp", "[GCodeFindReplace]") { ";TYPE:External perimeter\n" "G1 X1 Y.3 Z.431 E0.1\n"; WHEN("Change extrusion rate of top solid infill, single line modifier") { - GCodeFindReplace find_replace({ "(;TYPE:Top solid infill\\n)(.*?)(;TYPE:[^T][^o][^p][^ ][^s]|$)", "${1}M221 S98\\n${2}M221 S95\\n${3}", "rs" }); + GCodeFindReplace find_replace({ "(;TYPE:Top solid infill\\n)(.*?)(;TYPE:[^T][^o][^p][^ ][^s]|$)", "${1}M221 S98\\n${2}M221 S95\\n${3}", "rs", "" }); REQUIRE(find_replace.process_layer(gcode) == "G1 Z1.21; move up\n" ";TYPE:Infill\n" @@ -262,7 +262,7 @@ SCENARIO("Find/Replace with regexp", "[GCodeFindReplace]") { "G1 X1 Y.3 Z.431 E0.1\n"); } WHEN("Change extrusion rate of top solid infill, no single line modifier (incorrect)") { - GCodeFindReplace find_replace({ "(;TYPE:Top solid infill\\n)(.*?)(;TYPE:[^T][^o][^p][^ ][^s]|$)", "${1}M221 S98\\n${2}\\nM221 S95${3}", "r" }); + GCodeFindReplace find_replace({ "(;TYPE:Top solid infill\\n)(.*?)(;TYPE:[^T][^o][^p][^ ][^s]|$)", "${1}M221 S98\\n${2}\\nM221 S95${3}", "r", "" }); REQUIRE(find_replace.process_layer(gcode) == "G1 Z1.21; move up\n" ";TYPE:Infill\n"