Polish BulkExportDialog

- Add duplicate detection to validation
- Add file existance detection to validation
- Remove dependency on PrintToExport
- Remove destructor and use unique_ptr instead
This commit is contained in:
Martin Šach 2024-12-05 23:54:13 +01:00 committed by Lukas Matena
parent 8f7b2b2259
commit fd43fb6e9b
5 changed files with 211 additions and 132 deletions

View File

@ -92,6 +92,8 @@ public:
void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; } void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; }
void set_gcode_result(GCodeProcessorResult* result) { m_gcode_result = result; } void set_gcode_result(GCodeProcessorResult* result) { m_gcode_result = result; }
GCodeProcessorResult *get_gcode_result() { return m_gcode_result; }
// The following wxCommandEvent will be sent to the UI thread / Plater window, when the slicing is finished // The following wxCommandEvent will be sent to the UI thread / Plater window, when the slicing is finished
// and the background processing will transition into G-code export. // and the background processing will transition into G-code export.
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed. // The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.

View File

@ -20,24 +20,19 @@
#include "format.hpp" #include "format.hpp"
#include "Tab.hpp" #include "Tab.hpp"
using Slic3r::GUI::format_wxstr; namespace fs = boost::filesystem;
namespace Slic3r { namespace Slic3r {
namespace GUI { namespace GUI {
constexpr auto BORDER_W = 10; constexpr auto BORDER_W = 10;
//----------------------------------------------- void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* input_path_sizer, const std::string &path)
// BulkExportDialog::Item
//-----------------------------------------------
void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* input_path_sizer, const std::string path)
{ {
#ifdef _WIN32 #ifdef _WIN32
long style = wxBORDER_SIMPLE; const long style = wxBORDER_SIMPLE;
#else #else
long style = 0L; const long style = 0L;
#endif #endif
m_text_ctrl = new wxTextCtrl(m_parent, wxID_ANY, from_u8(path), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), style); m_text_ctrl = new wxTextCtrl(m_parent, wxID_ANY, from_u8(path), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), style);
wxGetApp().UpdateDarkUI(m_text_ctrl); wxGetApp().UpdateDarkUI(m_text_ctrl);
@ -45,18 +40,24 @@ void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* input_path_sizer,
input_path_sizer->Add(m_text_ctrl, 1, wxEXPAND, BORDER_W); input_path_sizer->Add(m_text_ctrl, 1, wxEXPAND, BORDER_W);
} }
BulkExportDialog::Item::Item(
BulkExportDialog::Item::Item(wxWindow* parent, wxBoxSizer* sizer, PrintToExport& pte): wxWindow *parent,
wxBoxSizer *sizer,
const boost::filesystem::path &path,
Validator validator
):
path(path),
m_parent(parent), m_parent(parent),
m_print_to_export(&pte),
m_valid_bmp(new wxStaticBitmap(m_parent, wxID_ANY, *get_bmp_bundle("tick_mark"))), m_valid_bmp(new wxStaticBitmap(m_parent, wxID_ANY, *get_bmp_bundle("tick_mark"))),
m_valid_label(new wxStaticText(m_parent, wxID_ANY, "")) m_valid_label(new wxStaticText(m_parent, wxID_ANY, "")),
m_validator(std::move(validator)),
m_directory(path.parent_path())
{ {
m_valid_label->SetFont(wxGetApp().bold_font()); m_valid_label->SetFont(wxGetApp().bold_font());
wxBoxSizer* input_path_sizer = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* input_path_sizer = new wxBoxSizer(wxHORIZONTAL);
input_path_sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, BORDER_W); input_path_sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, BORDER_W);
init_input_name_ctrl(input_path_sizer, pte.output_path.filename().string()); init_input_name_ctrl(input_path_sizer, path.filename().string());
sizer->Add(input_path_sizer,0, wxEXPAND | wxTOP, BORDER_W); sizer->Add(input_path_sizer,0, wxEXPAND | wxTOP, BORDER_W);
sizer->Add(m_valid_label, 0, wxEXPAND | wxLEFT, 3*BORDER_W); sizer->Add(m_valid_label, 0, wxEXPAND | wxLEFT, 3*BORDER_W);
@ -64,82 +65,131 @@ BulkExportDialog::Item::Item(wxWindow* parent, wxBoxSizer* sizer, PrintToExport&
update(); update();
} }
void BulkExportDialog::Item::update()
{
m_path = into_u8(m_text_ctrl->GetValue());
m_valid_type = ValidationType::Valid;
wxString info_line;
const char* unusable_symbols = "<>[]:/\\|?*\"";
for (size_t i = 0; i < std::strlen(unusable_symbols); i++) {
if (m_path.find_first_of(unusable_symbols[i]) != std::string::npos) {
info_line = _L("The following characters are not allowed in the name") + ": " + unusable_symbols;
m_valid_type = ValidationType::NoValid;
break;
}
}
bool existing = false;// is_existing(m_path);
if (m_valid_type == ValidationType::Valid && existing) {
info_line = _L("This name is already used, use another.");
m_valid_type = ValidationType::Warning;
}
if (m_valid_type == ValidationType::Valid && m_path.empty()) {
info_line = _L("The name cannot be empty.");
m_valid_type = ValidationType::NoValid;
}
#ifdef __WXMSW__ #ifdef __WXMSW__
const int max_path_length = MAX_PATH; constexpr int max_path_length = MAX_PATH;
#else #else
const int max_path_length = 255; constexpr int max_path_length = 255;
#endif #endif
if (m_valid_type == ValidationType::Valid && m_path.length() >= max_path_length) { struct PathValidator {
info_line = _L("The name is too long."); std::reference_wrapper<std::vector<std::unique_ptr<BulkExportDialog::Item>>> items;
m_valid_type = ValidationType::NoValid; using ItemStatus = BulkExportDialog::ItemStatus;
bool is_duplicate(const fs::path &path) {
const int64_t count{std::count_if(
items.get().begin(),
items.get().end(),
[&](const auto &item){
return item->path == path;
}
)};
return count >= 2;
} }
if (m_valid_type == ValidationType::Valid && m_path.find_first_of(' ') == 0) { std::pair<BulkExportDialog::ItemStatus, wxString> operator()(
info_line = _L("The name cannot start with space character."); const fs::path &path,
m_valid_type = ValidationType::NoValid; const std::string &filename
} ) {
const char* unusable_symbols = "<>[]:/\\|?*\"";
for (size_t i = 0; i < std::strlen(unusable_symbols); i++) {
if (filename.find_first_of(unusable_symbols[i]) != std::string::npos) {
return {
ItemStatus::NoValid,
_L("The following characters are not allowed in the name") + ": " + unusable_symbols
};
}
}
if (m_valid_type == ValidationType::Valid && m_path.find_last_of(' ') == m_path.length()-1) { if (filename.empty()) {
info_line = _L("The name cannot end with space character."); return {
m_valid_type = ValidationType::NoValid; ItemStatus::NoValid,
_L("The name cannot be empty.")
};
}
if (path.string().length() >= max_path_length) {
return {
ItemStatus::NoValid,
_L("The name is too long.")
};
}
if (filename.find_first_of(' ') == 0) {
return {
ItemStatus::NoValid,
_L("The name cannot start with space character.")
};
}
if (filename.find_last_of(' ') == filename.length()-1) {
return {
ItemStatus::NoValid,
_L("The name cannot end with space character.")
};
}
if (is_duplicate(path)) {
return {
ItemStatus::NoValid,
_L("This name is already used, use another.")
};
}
if (fs::exists(path)) {
return {
ItemStatus::Warning,
_L("The file already exists!")
};
}
return {ItemStatus::Valid, ""};
} }
};
void BulkExportDialog::Item::update()
{
std::string filename{into_u8(m_text_ctrl->GetValue())};
path = m_directory / filename;
// Validator needs to be called after path is set!
// It has has a reference to all items and searches
// for duplicates;
const auto [status, info_line]{m_validator(path, filename)};
m_valid_label->SetLabel(info_line); m_valid_label->SetLabel(info_line);
m_valid_label->Show(!info_line.IsEmpty()); m_valid_label->Show(!info_line.IsEmpty());
m_status = status;
update_valid_bmp(); update_valid_bmp();
if (is_valid()) {
boost::filesystem::path field = m_print_to_export->output_path.parent_path();
m_print_to_export->output_path = field / m_path;
}
m_parent->Layout(); m_parent->Layout();
} }
void BulkExportDialog::Item::update_valid_bmp() std::string get_bmp_name(const BulkExportDialog::ItemStatus status) {
{ using ItemStatus = BulkExportDialog::ItemStatus;
std::string bmp_name = m_valid_type == ValidationType::Warning ? "exclamation_manifold" : switch(status) {
m_valid_type == ValidationType::NoValid ? "exclamation" : "tick_mark" ; case ItemStatus::Warning: return "exclamation_manifold";
m_valid_bmp->SetBitmap(*get_bmp_bundle(bmp_name)); case ItemStatus::NoValid: return "exclamation";
case ItemStatus::Valid: return "tick_mark";
}
return ""; // unreachable
} }
//----------------------------------------------- void BulkExportDialog::Item::update_valid_bmp()
// BulkExportDialog {
//----------------------------------------------- m_valid_bmp->SetBitmap(*get_bmp_bundle(get_bmp_name(m_status)));
}
BulkExportDialog::BulkExportDialog(wxWindow* parent, std::vector<PrintToExport>& exports) BulkExportDialog::BulkExportDialog(const std::vector<fs::path> &paths):
: DPIDialog(parent, wxID_ANY, exports.size() == 1 ? _L("Save bed") : _L("Save beds"), DPIDialog(
wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING) nullptr,
wxID_ANY,
paths.size() == 1 ? _L("Save bed") : _L("Save beds"),
wxDefaultPosition,
wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()),
wxDEFAULT_DIALOG_STYLE | wxICON_WARNING
)
{ {
this->SetFont(wxGetApp().normal_font()); this->SetFont(wxGetApp().normal_font());
@ -151,8 +201,9 @@ BulkExportDialog::BulkExportDialog(wxWindow* parent, std::vector<PrintToExport>&
m_sizer = new wxBoxSizer(wxVERTICAL); m_sizer = new wxBoxSizer(wxVERTICAL);
for (PrintToExport& exp : exports) for (const fs::path& path : paths) {
AddItem(exp); AddItem(path);
}
// Add dialog's buttons // Add dialog's buttons
wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
@ -173,22 +224,17 @@ BulkExportDialog::BulkExportDialog(wxWindow* parent, std::vector<PrintToExport>&
#endif #endif
} }
BulkExportDialog::~BulkExportDialog() void BulkExportDialog::AddItem(const fs::path& path)
{ {
for (auto item : m_items) m_items.push_back(std::make_unique<Item>(this, m_sizer, path, PathValidator{m_items}));
delete item;
}
void BulkExportDialog::AddItem(PrintToExport& pte)
{
m_items.emplace_back(new Item{ this, m_sizer, pte });
} }
bool BulkExportDialog::enable_ok_btn() const bool BulkExportDialog::enable_ok_btn() const
{ {
for (const Item* item : m_items) for (const auto &item : m_items)
if (!item->is_valid()) if (!item->is_valid()) {
return false; return false;
}
return true; return true;
} }
@ -200,13 +246,26 @@ bool BulkExportDialog::Layout()
return ret; return ret;
} }
void BulkExportDialog::on_dpi_changed(const wxRect& suggested_rect) std::vector<boost::filesystem::path> BulkExportDialog::get_paths() const {
std::vector<boost::filesystem::path> result;
std::transform(
m_items.begin(),
m_items.end(),
std::back_inserter(result),
[](const auto &item){
return item->path;
}
);
return result;
}
void BulkExportDialog::on_dpi_changed(const wxRect&)
{ {
const int& em = em_unit(); const int& em = em_unit();
msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL });
for (Item* item : m_items) for (auto &item : m_items)
item->update_valid_bmp(); item->update_valid_bmp();
const wxSize& size = wxSize(65 * em, 35 * em); const wxSize& size = wxSize(65 * em, 35 * em);

View File

@ -16,79 +16,74 @@ class wxTextCtrl;
class wxStaticBitmap; class wxStaticBitmap;
namespace Slic3r { namespace Slic3r {
class Print; class Print;
struct GCodeProcessorResult; struct GCodeProcessorResult;
namespace GUI { namespace GUI {
struct PrintToExport {
std::reference_wrapper<Slic3r::Print> print;
std::reference_wrapper<Slic3r::GCodeProcessorResult> processor_result;
boost::filesystem::path output_path;
};
class BulkExportDialog : public DPIDialog class BulkExportDialog : public DPIDialog
{ {
public: public:
enum class ItemStatus { Valid, NoValid, Warning };
struct Item struct Item
{ {
enum class ValidationType using Validator = std::function<
{ std::pair<BulkExportDialog::ItemStatus, wxString>(
Valid, boost::filesystem::path,
NoValid, std::string
Warning )
}; >;
Item(
wxWindow *parent,
wxBoxSizer *sizer,
const boost::filesystem::path &path,
Validator validator
);
Item(const Item &) = delete;
Item& operator=(const Item &) = delete;
Item(Item &&) = delete;
Item& operator=(Item &&) = delete;
// Item as a separate control(f.e. as a part of ConfigWizard to check name of the new custom priter) // Item cannot have copy or move constructors, because a wx event binds
Item(wxWindow* parent, wxBoxSizer* sizer, PrintToExport& path); // directly to its address.
void update_valid_bmp(); void update_valid_bmp();
bool is_valid() const { return m_valid_type != ValidationType::NoValid; } bool is_valid() const { return m_status != ItemStatus::NoValid; }
boost::filesystem::path path;
private: private:
std::string m_path; ItemStatus m_status{ItemStatus::NoValid};
PrintToExport* m_print_to_export { nullptr }; wxWindow *m_parent{nullptr};
wxStaticBitmap *m_valid_bmp{nullptr};
wxTextCtrl *m_text_ctrl{nullptr};
wxStaticText *m_valid_label{nullptr};
Validator m_validator;
boost::filesystem::path m_directory{};
ValidationType m_valid_type {ValidationType::NoValid}; void init_input_name_ctrl(wxBoxSizer *input_name_sizer, const std::string &path);
wxWindow* m_parent {nullptr}; void update();
wxStaticBitmap* m_valid_bmp {nullptr};
wxTextCtrl* m_text_ctrl {nullptr};
wxStaticText* m_valid_label {nullptr};
void init_input_name_ctrl(wxBoxSizer *input_name_sizer, std::string path);
void update();
}; };
private: private:
std::vector<Item*> m_items; // This must be a unique ptr, because Item does not have copy nor move constructors.
std::vector<std::unique_ptr<Item>> m_items;
std::vector<PrintToExport>* m_exports {nullptr}; wxBoxSizer *m_sizer{nullptr};
wxBoxSizer* m_sizer {nullptr};
wxStaticText* m_label {nullptr};
std::string m_ph_printer_name;
std::string m_old_preset_name;
wxString m_info_line_extention{wxEmptyString};
public: public:
BulkExportDialog(const std::vector<boost::filesystem::path> &paths);
BulkExportDialog(wxWindow* parent, std::vector<PrintToExport>& exports);
~BulkExportDialog() override;
bool Layout() override; bool Layout() override;
std::vector<boost::filesystem::path> get_paths() const;
void AddItem(PrintToExport& pte);
protected: protected:
void on_dpi_changed(const wxRect& suggested_rect) override; void on_dpi_changed(const wxRect &) override;
void on_sys_color_changed() override {} void on_sys_color_changed() override {}
private: private:
void AddItem(const boost::filesystem::path &path);
bool enable_ok_btn() const; bool enable_ok_btn() const;
}; };
} } // namespace GUI
} }

View File

@ -5923,6 +5923,12 @@ void Plater::export_gcode_to_path(
appconfig.update_last_output_dir(output_path.parent_path().string(), path_on_removable_media); appconfig.update_last_output_dir(output_path.parent_path().string(), path_on_removable_media);
} }
struct PrintToExport {
std::reference_wrapper<Slic3r::Print> print;
std::reference_wrapper<Slic3r::GCodeProcessorResult> processor_result;
boost::filesystem::path output_path;
};
void Plater::export_all_gcodes(bool prefer_removable) { void Plater::export_all_gcodes(bool prefer_removable) {
const auto optional_default_output_file{this->get_default_output_file()}; const auto optional_default_output_file{this->get_default_output_file()};
if (!optional_default_output_file) { if (!optional_default_output_file) {
@ -5937,6 +5943,7 @@ void Plater::export_all_gcodes(bool prefer_removable) {
const fs_path &output_dir{*optional_output_dir}; const fs_path &output_dir{*optional_output_dir};
std::vector<PrintToExport> prints_to_export; std::vector<PrintToExport> prints_to_export;
std::vector<fs::path> paths;
for (std::size_t print_index{0}; print_index < this->get_fff_prints().size(); ++print_index) { for (std::size_t print_index{0}; print_index < this->get_fff_prints().size(); ++print_index) {
const std::unique_ptr<Print> &print{this->get_fff_prints()[print_index]}; const std::unique_ptr<Print> &print{this->get_fff_prints()[print_index]};
@ -5952,14 +5959,27 @@ void Plater::export_all_gcodes(bool prefer_removable) {
}; };
const fs::path output_file{output_dir / filename}; const fs::path output_file{output_dir / filename};
prints_to_export.push_back({*print, this->p->gcode_results[print_index], output_file}); prints_to_export.push_back({*print, this->p->gcode_results[print_index], output_file});
paths.push_back(output_file);
} }
BulkExportDialog dialog(nullptr, prints_to_export); BulkExportDialog dialog{paths};
if (dialog.ShowModal() != wxID_OK) { if (dialog.ShowModal() != wxID_OK) {
return; return;
} }
paths = dialog.get_paths();
for (std::size_t path_index{0}; path_index < paths.size(); ++path_index) {
prints_to_export[path_index].output_path = paths[path_index];
}
bool path_on_removable_media{false}; bool path_on_removable_media{false};
Print *original_print{&active_fff_print()};
GCodeProcessorResult *original_result{this->p->background_process.get_gcode_result()};
ScopeGuard guard{[&](){
this->p->background_process.set_fff_print(original_print);
this->p->background_process.set_gcode_result(original_result);
}};
for (const PrintToExport &print_to_export : prints_to_export) { for (const PrintToExport &print_to_export : prints_to_export) {
this->p->background_process.set_fff_print(&print_to_export.print.get()); this->p->background_process.set_fff_print(&print_to_export.print.get());
this->p->background_process.set_gcode_result(&print_to_export.processor_result.get()); this->p->background_process.set_gcode_result(&print_to_export.processor_result.get());

View File

@ -31,6 +31,7 @@
#include "Jobs/Worker.hpp" #include "Jobs/Worker.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp" #include "libslic3r/GCode/ThumbnailData.hpp"
#include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/Camera.hpp"
#include "slic3r/Utils/PrintHost.hpp"
class wxString; class wxString;
@ -240,6 +241,8 @@ public:
void send_gcode(); void send_gcode();
void send_gcode_inner(DynamicPrintConfig* physical_printer_config); void send_gcode_inner(DynamicPrintConfig* physical_printer_config);
void eject_drive(); void eject_drive();
std::optional<PrintHostJob> get_connect_print_host_job();
void connect_gcode(); void connect_gcode();
void printables_to_connect_gcode(const std::string& url); void printables_to_connect_gcode(const std::string& url);
std::string get_upload_filename(); std::string get_upload_filename();