Merge branch 'lm_extension_error'

This commit is contained in:
Lukas Matena 2023-11-03 21:20:46 +01:00
commit e7d6f63232
5 changed files with 143 additions and 33 deletions

View File

@ -122,21 +122,20 @@ void MsgDialog::finalize()
this->CenterOnParent(); this->CenterOnParent();
} }
// Text shown as HTML, so that mouse selection and Ctrl-V to copy will work. // Text shown as HTML, so that mouse selection and Ctrl-V to copy will work.
static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxString msg, bool monospaced_font = false, bool is_marked_msg = false) static void add_msg_content(MsgDialog* parent, wxBoxSizer* content_sizer, const HtmlContent& content)
{ {
wxHtmlWindow* html = new wxHtmlWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO); wxHtmlWindow* html = new wxHtmlWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO);
// count lines in the message // count lines in the message
int msg_lines = 0; int msg_lines = 0;
if (!monospaced_font) { if (!content.is_monospaced_font) {
int line_len = 55;// count of symbols in one line int line_len = 55;// count of symbols in one line
int start_line = 0; int start_line = 0;
for (auto i = msg.begin(); i != msg.end(); ++i) { for (auto i = content.msg.begin(); i != content.msg.end(); ++i) {
if (*i == '\n') { if (*i == '\n') {
int cur_line_len = i - msg.begin() - start_line; int cur_line_len = i - content.msg.begin() - start_line;
start_line = i - msg.begin(); start_line = i - content.msg.begin();
if (cur_line_len == 0 || line_len > cur_line_len) if (cur_line_len == 0 || line_len > cur_line_len)
msg_lines++; msg_lines++;
else else
@ -175,11 +174,11 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin
} }
// if message containes the table // if message containes the table
if (msg.Contains("<tr>")) { if (content.msg.Contains("<tr>")) {
int lines = msg.Freq('\n') + 1; int lines = content.msg.Freq('\n') + 1;
int pos = 0; int pos = 0;
while (pos < (int)msg.Len() && pos != wxNOT_FOUND) { while (pos < (int)content.msg.Len() && pos != wxNOT_FOUND) {
pos = msg.find("<tr>", pos + 1); pos = content.msg.find("<tr>", pos + 1);
lines += 2; lines += 2;
} }
int page_height = std::min(int(font.GetPixelSize().y+2) * lines, 68 * em); int page_height = std::min(int(font.GetPixelSize().y+2) * lines, 68 * em);
@ -187,16 +186,16 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin
} }
else { else {
wxClientDC dc(parent); wxClientDC dc(parent);
wxSize msg_sz = dc.GetMultiLineTextExtent(msg); wxSize msg_sz = dc.GetMultiLineTextExtent(content.msg);
page_size = wxSize(std::min(msg_sz.GetX() + 2 * em, 68 * em), page_size = wxSize(std::min(msg_sz.GetX() + 2 * em, 68 * em),
std::min(msg_sz.GetY() + 2 * em, 68 * em)); std::min(msg_sz.GetY() + 2 * em, 68 * em));
} }
html->SetMinSize(page_size); html->SetMinSize(page_size);
std::string msg_escaped = xml_escape(into_u8(msg), is_marked_msg); std::string msg_escaped = xml_escape(into_u8(content.msg), content.is_marked_msg || content.on_link_clicked);
boost::replace_all(msg_escaped, "\r\n", "<br>"); boost::replace_all(msg_escaped, "\r\n", "<br>");
boost::replace_all(msg_escaped, "\n", "<br>"); boost::replace_all(msg_escaped, "\n", "<br>");
if (monospaced_font) if (content.is_monospaced_font)
// Code formatting will be preserved. This is useful for reporting errors from the placeholder parser. // Code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
msg_escaped = std::string("<pre><code>") + msg_escaped + "</code></pre>"; msg_escaped = std::string("<pre><code>") + msg_escaped + "</code></pre>";
html->SetPage(format_wxstr("<html>" html->SetPage(format_wxstr("<html>"
@ -208,7 +207,12 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin
"</html>", "</html>",
bgr_clr_str, text_clr_str, from_u8(msg_escaped))); bgr_clr_str, text_clr_str, from_u8(msg_escaped)));
html->Bind(wxEVT_HTML_LINK_CLICKED, [parent](wxHtmlLinkEvent& event) { html->Bind(wxEVT_HTML_LINK_CLICKED, [parent, &content](wxHtmlLinkEvent& event) {
if (content.on_link_clicked) {
parent->EndModal(wxID_CLOSE);
content.on_link_clicked(into_u8(event.GetLinkInfo().GetHref()));
}
else
wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref(), parent, false); wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref(), parent, false);
event.Skip(false); event.Skip(false);
}); });
@ -219,21 +223,34 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin
// ErrorDialog // ErrorDialog
ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, bool monospaced_font) void ErrorDialog::create(const HtmlContent& content, int icon_width)
: MsgDialog(parent, wxString::Format(_(L("%s error")), SLIC3R_APP_NAME),
wxString::Format(_(L("%s has encountered an error")), SLIC3R_APP_NAME), wxOK)
, msg(msg)
{ {
add_msg_content(this, content_sizer, msg, monospaced_font); add_msg_content(this, content_sizer, content);
// Use a small bitmap with monospaced font, as the error text will not be wrapped. // Use a small bitmap with monospaced font, as the error text will not be wrapped.
logo->SetBitmap(*get_bmp_bundle("PrusaSlicer_192px_grayscale.png", monospaced_font ? 48 : /*1*/84)); logo->SetBitmap(*get_bmp_bundle("PrusaSlicer_192px_grayscale.png", icon_width));
SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT*wxGetApp().em_unit())); SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT*wxGetApp().em_unit()));
finalize(); finalize();
} }
ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, bool monospaced_font)
: MsgDialog(parent, wxString::Format(_L("%s error"), SLIC3R_APP_NAME),
wxString::Format(_L("%s has encountered an error"), SLIC3R_APP_NAME), wxOK)
, m_content(HtmlContent{ msg, monospaced_font, true })
{
create(m_content, monospaced_font ? 48 : 84);
}
ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, const std::function<void(const std::string&)> &on_link_clicked)
: MsgDialog(parent, wxString::Format(_L("%s error"), SLIC3R_APP_NAME),
wxString::Format(_L("%s has encountered an error"), SLIC3R_APP_NAME), wxOK)
, m_content(HtmlContent{ msg, false, true, on_link_clicked })
{
create(m_content, 84);
}
// WarningDialog // WarningDialog
WarningDialog::WarningDialog(wxWindow *parent, WarningDialog::WarningDialog(wxWindow *parent,
@ -243,7 +260,7 @@ WarningDialog::WarningDialog(wxWindow *parent,
: MsgDialog(parent, caption.IsEmpty() ? wxString::Format(_L("%s warning"), SLIC3R_APP_NAME) : caption, : MsgDialog(parent, caption.IsEmpty() ? wxString::Format(_L("%s warning"), SLIC3R_APP_NAME) : caption,
wxString::Format(_L("%s has a warning")+":", SLIC3R_APP_NAME), style) wxString::Format(_L("%s has a warning")+":", SLIC3R_APP_NAME), style)
{ {
add_msg_content(this, content_sizer, message); add_msg_content(this, content_sizer, HtmlContent{ message });
finalize(); finalize();
} }
@ -256,7 +273,7 @@ MessageDialog::MessageDialog(wxWindow* parent,
long style/* = wxOK*/) long style/* = wxOK*/)
: MsgDialog(parent, caption.IsEmpty() ? wxString::Format(_L("%s info"), SLIC3R_APP_NAME) : caption, wxEmptyString, style) : MsgDialog(parent, caption.IsEmpty() ? wxString::Format(_L("%s info"), SLIC3R_APP_NAME) : caption, wxEmptyString, style)
{ {
add_msg_content(this, content_sizer, get_wraped_wxString(message)); add_msg_content(this, content_sizer, HtmlContent{ get_wraped_wxString(message) });
finalize(); finalize();
} }
@ -269,7 +286,7 @@ RichMessageDialog::RichMessageDialog(wxWindow* parent,
long style/* = wxOK*/) long style/* = wxOK*/)
: MsgDialog(parent, caption.IsEmpty() ? wxString::Format(_L("%s info"), SLIC3R_APP_NAME) : caption, wxEmptyString, style) : MsgDialog(parent, caption.IsEmpty() ? wxString::Format(_L("%s info"), SLIC3R_APP_NAME) : caption, wxEmptyString, style)
{ {
add_msg_content(this, content_sizer, get_wraped_wxString(message)); add_msg_content(this, content_sizer, HtmlContent{ get_wraped_wxString(message) });
m_checkBox = new ::CheckBox(this, m_checkBoxText); m_checkBox = new ::CheckBox(this, m_checkBoxText);
wxGetApp().UpdateDarkUI(m_checkBox); wxGetApp().UpdateDarkUI(m_checkBox);
@ -298,7 +315,7 @@ InfoDialog::InfoDialog(wxWindow* parent, const wxString &title, const wxString&
: MsgDialog(parent, wxString::Format(_L("%s information"), SLIC3R_APP_NAME), title, style) : MsgDialog(parent, wxString::Format(_L("%s information"), SLIC3R_APP_NAME), title, style)
, msg(msg) , msg(msg)
{ {
add_msg_content(this, content_sizer, msg, false, is_marked_msg); add_msg_content(this, content_sizer, HtmlContent{ msg, false, is_marked_msg });
finalize(); finalize();
} }

View File

@ -24,6 +24,14 @@ namespace Slic3r {
namespace GUI { namespace GUI {
struct HtmlContent
{
wxString msg{ wxEmptyString };
bool is_monospaced_font{ false };
bool is_marked_msg{ false };
std::function<void(const std::string&)> on_link_clicked{ nullptr };
};
// A message / query dialog with a bitmap on the left and any content on the right // A message / query dialog with a bitmap on the left and any content on the right
// with buttons underneath. // with buttons underneath.
struct MsgDialog : wxDialog struct MsgDialog : wxDialog
@ -67,6 +75,7 @@ public:
// If monospaced_font is true, the error message is displayed using html <code><pre></pre></code> tags, // If monospaced_font is true, the error message is displayed using html <code><pre></pre></code> tags,
// so that the code formatting will be preserved. This is useful for reporting errors from the placeholder parser. // so that the code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
ErrorDialog(wxWindow *parent, const wxString &msg, bool courier_font); ErrorDialog(wxWindow *parent, const wxString &msg, bool courier_font);
ErrorDialog(wxWindow *parent, const wxString &msg, const std::function<void(const std::string&)>& on_link_clicked);
ErrorDialog(ErrorDialog &&) = delete; ErrorDialog(ErrorDialog &&) = delete;
ErrorDialog(const ErrorDialog &) = delete; ErrorDialog(const ErrorDialog &) = delete;
ErrorDialog &operator=(ErrorDialog &&) = delete; ErrorDialog &operator=(ErrorDialog &&) = delete;
@ -74,7 +83,9 @@ public:
virtual ~ErrorDialog() = default; virtual ~ErrorDialog() = default;
private: private:
wxString msg; void create(const HtmlContent& content, int icon_width);
HtmlContent m_content;
}; };

View File

@ -1243,6 +1243,29 @@ void Sidebar::search()
p->searcher.search(); p->searcher.search();
} }
void Sidebar::jump_to_option(const std::string& composite_key)
{
const auto separator_pos = composite_key.find(";");
const std::string opt_key = composite_key.substr(0, separator_pos);
const std::string tab_name = composite_key.substr(separator_pos + 1, composite_key.length());
for (Tab* tab : wxGetApp().tabs_list) {
if (tab->name() == tab_name) {
check_and_update_searcher(true);
// Regularly searcher is sorted in respect to the options labels,
// so resort searcher before get an option
p->searcher.sort_options_by_key();
const Search::Option& opt = p->searcher.get_option(opt_key, tab->type());
tab->activate_option(opt_key, boost::nowide::narrow(opt.category));
// Revert sort of searcher back
p->searcher.sort_options_by_label();
break;
}
}
}
void Sidebar::jump_to_option(const std::string& opt_key, Preset::Type type, const std::wstring& category) void Sidebar::jump_to_option(const std::string& opt_key, Preset::Type type, const std::wstring& category)
{ {
//const Search::Option& opt = p->searcher.get_option(opt_key, type); //const Search::Option& opt = p->searcher.get_option(opt_key, type);
@ -6688,6 +6711,32 @@ void Plater::apply_cut_object_to_model(size_t obj_idx, const ModelObjectPtrs& ne
w.wait_for_idle(); w.wait_for_idle();
} }
wxString check_binary_vs_ascii_gcode_extension(PrinterTechnology pt, const std::string& ext, bool binary_output)
{
wxString err_out;
if (pt == ptFFF) {
const bool binary_extension = (ext == ".bgcode" || ext == ".bgc");
const bool ascii_extension = (ext == ".gcode" || ext == ".g" || ext == ".gco");
if (binary_output && ascii_extension) {
// TRN The placeholder %1% is the file extension the user has selected.
err_out = format_wxstr(_L("Cannot save binary G-code with %1% extension.\n\n"
"Use <a href=%2%>a different extension</a> or disable <a href=%3%>binary G-code export</a> "
"in Print Settings."), ext, "output_filename_format;print", "gcode_binary;print");
}
if (!binary_output && binary_extension) {
// TRN The placeholder %1% is the file extension the user has selected.
err_out = format_wxstr(_L("Cannot save ASCII G-code with %1% extension.\n\n"
"Use <a href=%2%>a different extension</a> or enable <a href=%3%>binary G-code export</a> "
"in Print Settings."), ext, "output_filename_format;print", "gcode_binary;print");
}
}
return err_out;
}
void Plater::export_gcode(bool prefer_removable) void Plater::export_gcode(bool prefer_removable)
{ {
if (p->model.objects.empty()) if (p->model.objects.empty())
@ -6743,9 +6792,28 @@ void Plater::export_gcode(bool prefer_removable)
); );
if (dlg.ShowModal() == wxID_OK) { if (dlg.ShowModal() == wxID_OK) {
output_path = into_path(dlg.GetPath()); output_path = into_path(dlg.GetPath());
while (has_illegal_filename_characters(output_path.filename().string())) {
show_error(this, _L("The provided file name is not valid.") + "\n" + auto check_for_error = [this](const boost::filesystem::path& path, wxString& err_out) -> bool {
_L("The following characters are not allowed by a FAT file system:") + " <>:/\\|?*\""); const std::string filename = path.filename().string();
const std::string ext = boost::algorithm::to_lower_copy(path.extension().string());
if (has_illegal_filename_characters(filename)) {
err_out = _L("The provided file name is not valid.") + "\n" +
_L("The following characters are not allowed by a FAT file system:") + " <>:/\\|?*\"";
return true;
}
err_out = check_binary_vs_ascii_gcode_extension(printer_technology(), ext, wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("gcode_binary"));
return !err_out.IsEmpty();
};
wxString error_str;
#if 1 // #ysFIXME > clear code after testing
if (check_for_error(output_path, error_str)) {
ErrorDialog(this, error_str, [this](const std::string& key) -> void { sidebar().jump_to_option(key); }).ShowModal();
output_path.clear();
}
#else
while (check_for_error(output_path, error_str)) {
show_error(this, error_str);
dlg.SetFilename(from_path(output_path.filename())); dlg.SetFilename(from_path(output_path.filename()));
if (dlg.ShowModal() == wxID_OK) if (dlg.ShowModal() == wxID_OK)
output_path = into_path(dlg.GetPath()); output_path = into_path(dlg.GetPath());
@ -6754,6 +6822,7 @@ void Plater::export_gcode(bool prefer_removable)
break; break;
} }
} }
#endif
} }
} }
@ -7300,6 +7369,17 @@ void Plater::send_gcode()
PrintHostSendDialog dlg(default_output_file, upload_job.printhost->get_post_upload_actions(), groups, storage_paths, storage_names); PrintHostSendDialog dlg(default_output_file, upload_job.printhost->get_post_upload_actions(), groups, storage_paths, storage_names);
if (dlg.ShowModal() == wxID_OK) { if (dlg.ShowModal() == wxID_OK) {
{
const std::string ext = boost::algorithm::to_lower_copy(dlg.filename().extension().string());
const bool binary_output = wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("gcode_binary");
const wxString error_str = check_binary_vs_ascii_gcode_extension(printer_technology(), ext, binary_output);
if (! error_str.IsEmpty()) {
ErrorDialog(this, error_str, t_kill_focus([](const std::string& key) -> void { wxGetApp().sidebar().jump_to_option(key); })).ShowModal();
return;
}
}
upload_job.upload_data.upload_path = dlg.filename(); upload_job.upload_data.upload_path = dlg.filename();
upload_job.upload_data.post_action = dlg.post_action(); upload_job.upload_data.post_action = dlg.post_action();
upload_job.upload_data.group = dlg.group(); upload_job.upload_data.group = dlg.group();

View File

@ -102,6 +102,8 @@ public:
void search(); void search();
void jump_to_option(size_t selected); void jump_to_option(size_t selected);
void jump_to_option(const std::string& opt_key, Preset::Type type, const std::wstring& category); void jump_to_option(const std::string& opt_key, Preset::Type type, const std::wstring& category);
// jump to option which is represented by composite key : "opt_key;tab_name"
void jump_to_option(const std::string& composite_key);
ObjectManipulation* obj_manipul(); ObjectManipulation* obj_manipul();
ObjectList* obj_list(); ObjectList* obj_list();

View File

@ -25,6 +25,7 @@
#include "GUI.hpp" #include "GUI.hpp"
#include "GUI_App.hpp" #include "GUI_App.hpp"
#include "Plater.hpp"
#include "MsgDialog.hpp" #include "MsgDialog.hpp"
#include "I18N.hpp" #include "I18N.hpp"
#include "MainFrame.hpp" #include "MainFrame.hpp"
@ -107,8 +108,7 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo
auto validate_path = [this](const wxString &path) -> bool { auto validate_path = [this](const wxString &path) -> bool {
if (!path.Lower().EndsWith(m_valid_suffix.Lower())) { if (!path.Lower().EndsWith(m_valid_suffix.Lower())) {
MessageDialog msg_wingow(this, wxString::Format(_L("Upload filename doesn't end with \"%s\". Do you wish to continue?"), m_valid_suffix), wxString(SLIC3R_APP_NAME), wxYES | wxNO); MessageDialog msg_wingow(this, wxString::Format(_L("Upload filename doesn't end with \"%s\". Do you wish to continue?"), m_valid_suffix), wxString(SLIC3R_APP_NAME), wxYES | wxNO);
if (msg_wingow.ShowModal() == wxID_NO) return msg_wingow.ShowModal() == wxID_YES;
return false;
} }
return true; return true;
}; };