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_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
// 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.

View File

@ -20,24 +20,19 @@
#include "format.hpp"
#include "Tab.hpp"
using Slic3r::GUI::format_wxstr;
namespace fs = boost::filesystem;
namespace Slic3r {
namespace GUI {
constexpr auto BORDER_W = 10;
//-----------------------------------------------
// BulkExportDialog::Item
//-----------------------------------------------
void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* input_path_sizer, const std::string path)
void BulkExportDialog::Item::init_input_name_ctrl(wxBoxSizer* input_path_sizer, const std::string &path)
{
#ifdef _WIN32
long style = wxBORDER_SIMPLE;
const long style = wxBORDER_SIMPLE;
#else
long style = 0L;
const long style = 0L;
#endif
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);
@ -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);
}
BulkExportDialog::Item::Item(wxWindow* parent, wxBoxSizer* sizer, PrintToExport& pte):
BulkExportDialog::Item::Item(
wxWindow *parent,
wxBoxSizer *sizer,
const boost::filesystem::path &path,
Validator validator
):
path(path),
m_parent(parent),
m_print_to_export(&pte),
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());
wxBoxSizer* input_path_sizer = new wxBoxSizer(wxHORIZONTAL);
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());
input_path_sizer->Add(m_valid_bmp, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, BORDER_W);
init_input_name_ctrl(input_path_sizer, path.filename().string());
sizer->Add(input_path_sizer,0, wxEXPAND | wxTOP, 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();
}
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__
const int max_path_length = MAX_PATH;
constexpr int max_path_length = MAX_PATH;
#else
const int max_path_length = 255;
constexpr int max_path_length = 255;
#endif
if (m_valid_type == ValidationType::Valid && m_path.length() >= max_path_length) {
info_line = _L("The name is too long.");
m_valid_type = ValidationType::NoValid;
struct PathValidator {
std::reference_wrapper<std::vector<std::unique_ptr<BulkExportDialog::Item>>> items;
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) {
info_line = _L("The name cannot start with space character.");
m_valid_type = ValidationType::NoValid;
}
std::pair<BulkExportDialog::ItemStatus, wxString> operator()(
const fs::path &path,
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) {
info_line = _L("The name cannot end with space character.");
m_valid_type = ValidationType::NoValid;
if (filename.empty()) {
return {
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->Show(!info_line.IsEmpty());
m_status = status;
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();
}
void BulkExportDialog::Item::update_valid_bmp()
{
std::string bmp_name = m_valid_type == ValidationType::Warning ? "exclamation_manifold" :
m_valid_type == ValidationType::NoValid ? "exclamation" : "tick_mark" ;
m_valid_bmp->SetBitmap(*get_bmp_bundle(bmp_name));
std::string get_bmp_name(const BulkExportDialog::ItemStatus status) {
using ItemStatus = BulkExportDialog::ItemStatus;
switch(status) {
case ItemStatus::Warning: return "exclamation_manifold";
case ItemStatus::NoValid: return "exclamation";
case ItemStatus::Valid: return "tick_mark";
}
return ""; // unreachable
}
//-----------------------------------------------
// BulkExportDialog
//-----------------------------------------------
void BulkExportDialog::Item::update_valid_bmp()
{
m_valid_bmp->SetBitmap(*get_bmp_bundle(get_bmp_name(m_status)));
}
BulkExportDialog::BulkExportDialog(wxWindow* parent, std::vector<PrintToExport>& exports)
: DPIDialog(parent, wxID_ANY, exports.size() == 1 ? _L("Save bed") : _L("Save beds"),
wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING)
BulkExportDialog::BulkExportDialog(const std::vector<fs::path> &paths):
DPIDialog(
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());
@ -151,8 +201,9 @@ BulkExportDialog::BulkExportDialog(wxWindow* parent, std::vector<PrintToExport>&
m_sizer = new wxBoxSizer(wxVERTICAL);
for (PrintToExport& exp : exports)
AddItem(exp);
for (const fs::path& path : paths) {
AddItem(path);
}
// Add dialog's buttons
wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
@ -173,22 +224,17 @@ BulkExportDialog::BulkExportDialog(wxWindow* parent, std::vector<PrintToExport>&
#endif
}
BulkExportDialog::~BulkExportDialog()
void BulkExportDialog::AddItem(const fs::path& path)
{
for (auto item : m_items)
delete item;
}
void BulkExportDialog::AddItem(PrintToExport& pte)
{
m_items.emplace_back(new Item{ this, m_sizer, pte });
m_items.push_back(std::make_unique<Item>(this, m_sizer, path, PathValidator{m_items}));
}
bool BulkExportDialog::enable_ok_btn() const
{
for (const Item* item : m_items)
if (!item->is_valid())
for (const auto &item : m_items)
if (!item->is_valid()) {
return false;
}
return true;
}
@ -200,13 +246,26 @@ bool BulkExportDialog::Layout()
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();
msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL });
for (Item* item : m_items)
for (auto &item : m_items)
item->update_valid_bmp();
const wxSize& size = wxSize(65 * em, 35 * em);

View File

@ -16,79 +16,74 @@ class wxTextCtrl;
class wxStaticBitmap;
namespace Slic3r {
class Print;
struct GCodeProcessorResult;
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
{
public:
enum class ItemStatus { Valid, NoValid, Warning };
struct Item
{
enum class ValidationType
{
Valid,
NoValid,
Warning
};
using Validator = std::function<
std::pair<BulkExportDialog::ItemStatus, wxString>(
boost::filesystem::path,
std::string
)
>;
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(wxWindow* parent, wxBoxSizer* sizer, PrintToExport& path);
// Item cannot have copy or move constructors, because a wx event binds
// directly to its address.
void update_valid_bmp();
bool is_valid() const { return m_valid_type != ValidationType::NoValid; }
void update_valid_bmp();
bool is_valid() const { return m_status != ItemStatus::NoValid; }
boost::filesystem::path path;
private:
std::string m_path;
PrintToExport* m_print_to_export { nullptr };
ItemStatus m_status{ItemStatus::NoValid};
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};
wxWindow* m_parent {nullptr};
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();
void init_input_name_ctrl(wxBoxSizer *input_name_sizer, const std::string &path);
void update();
};
private:
std::vector<Item*> m_items;
std::vector<PrintToExport>* m_exports {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};
// This must be a unique ptr, because Item does not have copy nor move constructors.
std::vector<std::unique_ptr<Item>> m_items;
wxBoxSizer *m_sizer{nullptr};
public:
BulkExportDialog(wxWindow* parent, std::vector<PrintToExport>& exports);
~BulkExportDialog() override;
BulkExportDialog(const std::vector<boost::filesystem::path> &paths);
bool Layout() override;
void AddItem(PrintToExport& pte);
std::vector<boost::filesystem::path> get_paths() const;
protected:
void on_dpi_changed(const wxRect& suggested_rect) override;
void on_dpi_changed(const wxRect &) override;
void on_sys_color_changed() override {}
private:
void AddItem(const boost::filesystem::path &path);
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);
}
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) {
const auto optional_default_output_file{this->get_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};
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) {
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};
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) {
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};
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) {
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());

View File

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