diff --git a/src/slic3r/Config/Snapshot.cpp b/src/slic3r/Config/Snapshot.cpp index 23713dd119..77a74bffeb 100644 --- a/src/slic3r/Config/Snapshot.cpp +++ b/src/slic3r/Config/Snapshot.cpp @@ -22,6 +22,7 @@ #include "../GUI/GUI_App.hpp" #include "../GUI/I18N.hpp" #include "../GUI/MainFrame.hpp" +#include "../GUI/MsgDialog.hpp" #include @@ -591,7 +592,7 @@ bool take_config_snapshot_cancel_on_error(const AppConfig &app_config, Snapshot: SnapshotDB::singleton().take_snapshot(app_config, reason, comment); return true; } catch (std::exception &err) { - wxRichMessageDialog dlg(static_cast(wxGetApp().mainframe), + RichMessageDialog dlg(static_cast(wxGetApp().mainframe), _L("PrusaSlicer has encountered an error while taking a configuration snapshot.") + "\n\n" + from_u8(err.what()) + "\n\n" + from_u8(message), _L("PrusaSlicer error"), wxYES_NO); diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index b3b00f60c8..762de2cf53 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -555,7 +555,7 @@ PagePrinters::PagePrinters(ConfigWizard *parent, wizard_p()->on_printer_pick(this, evt); }); - append(new wxStaticLine(this)); + append(new StaticLine(this)); append(picker); printer_pickers.push_back(picker); @@ -2800,11 +2800,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent) auto *vsizer = new wxBoxSizer(wxVERTICAL); auto *topsizer = new wxBoxSizer(wxHORIZONTAL); - wxStaticLine* hline = nullptr; -#ifdef _MSW_DARK_MODE - if (!NppDarkMode::IsEnabled()) -#endif //_MSW_DARK_MODE - hline = new wxStaticLine(this); + auto* hline = new StaticLine(this); p->btnsizer = new wxBoxSizer(wxHORIZONTAL); // Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard without scrolling. @@ -2880,8 +2876,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent) p->index->go_to(size_t{0}); vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN); - if (hline) - vsizer->Add(hline, 0, wxEXPAND); + vsizer->Add(hline, 0, wxEXPAND | wxLEFT | wxRIGHT, VERTICAL_SPACING); vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN); SetSizer(vsizer); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index bab07b4664..025614d4d2 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -411,7 +411,7 @@ bool static check_old_linux_datadir(const wxString& app_name) { "location again.\n\n" "What do you want to do now?")) % SLIC3R_APP_NAME % new_path % old_path).str()); wxString caption = from_u8((boost::format(_u8L("%s - BREAKING CHANGE")) % SLIC3R_APP_NAME).str()); - wxRichMessageDialog dlg(nullptr, msg, caption, wxYES_NO); + RichMessageDialog dlg(nullptr, msg, caption, wxYES_NO); dlg.SetYesNoLabels(_L("Quit, I will move my data now"), _L("Start the application")); if (dlg.ShowModal() != wxID_NO) return false; @@ -846,7 +846,7 @@ bool GUI_App::check_older_app_config(Semver current_version, bool backup) return false; BOOST_LOG_TRIVIAL(info) << "last app config file used: " << m_older_data_dir_path; // ask about using older data folder - wxRichMessageDialog msg(nullptr, backup ? + RichMessageDialog msg(nullptr, backup ? wxString::Format(_L("PrusaSlicer detected another configuration folder at %s." "\nIts version is %s." "\nLast version you used in current configuration folder is %s." @@ -936,7 +936,7 @@ bool GUI_App::on_init_inner() // win32 build on win64 and viceversa #ifdef _WIN64 if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "") { - wxRichMessageDialog dlg(nullptr, + RichMessageDialog dlg(nullptr, _L("You have started PrusaSlicer for 64-bit architecture on 32-bit system." "\nPlease download and install correct version at https://www.prusa3d.cz/prusaslicer/." "\nDo you wish to continue?"), @@ -946,7 +946,7 @@ bool GUI_App::on_init_inner() } #elif _WIN32 if (wxPlatformInfo::Get().GetArchName().substr(0, 2) == "64") { - wxRichMessageDialog dlg(nullptr, + RichMessageDialog dlg(nullptr, _L("You have started PrusaSlicer for 32-bit architecture on 64-bit system." "\nPlease download and install correct version at https://www.prusa3d.cz/prusaslicer/." "\nDo you wish to continue?"), @@ -991,7 +991,7 @@ bool GUI_App::on_init_inner() bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Http::tls_system_cert_store(); if (!msg.empty() && !ssl_accept) { - wxRichMessageDialog + RichMessageDialog dlg(nullptr, wxString::Format(_L("%s\nDo you want to continue?"), msg), "PrusaSlicer", wxICON_QUESTION | wxYES_NO); @@ -2855,7 +2855,7 @@ bool GUI_App::open_browser_with_warning_dialog(const wxString& url, int flags/* bool launch = true; if (get_app_config()->get("suppress_hyperlinks").empty()) { - wxRichMessageDialog dialog(nullptr, _L("Should we open this hyperlink in your default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); + RichMessageDialog dialog(nullptr, _L("Should we open this hyperlink in your default browser?"), _L("PrusaSlicer: Open hyperlink"), wxICON_QUESTION | wxYES_NO); dialog.ShowCheckBox(_L("Remember my choice")); int answer = dialog.ShowModal(); launch = answer == wxID_YES; diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index c4cdde3d90..bc17dd9cec 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -27,7 +27,7 @@ namespace GUI { MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id, wxBitmap bitmap) : wxDialog(parent ? parent : dynamic_cast(wxGetApp().mainframe), wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) - , boldfont(wxGetApp().normal_font()/*wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)*/) + , boldfont(wxGetApp().normal_font()) , content_sizer(new wxBoxSizer(wxVERTICAL)) , btn_sizer(new wxBoxSizer(wxHORIZONTAL)) { @@ -36,6 +36,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he this->SetFont(wxGetApp().normal_font()); this->CenterOnParent(); + auto *main_sizer = new wxBoxSizer(wxVERTICAL); auto *topsizer = new wxBoxSizer(wxHORIZONTAL); auto *rightsizer = new wxBoxSizer(wxVERTICAL); @@ -46,6 +47,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he rightsizer->AddSpacer(VERT_SPACING); rightsizer->Add(content_sizer, 1, wxEXPAND); + btn_sizer->AddStretchSpacer(); if (button_id != wxID_NONE) { auto *button = new wxButton(this, button_id); @@ -53,8 +55,6 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he btn_sizer->Add(button); } - rightsizer->Add(btn_sizer, 0, wxALIGN_RIGHT); - if (! bitmap.IsOk()) { bitmap = create_scaled_bitmap("PrusaSlicer_192px.png", this, 192); } @@ -64,7 +64,11 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he topsizer->Add(logo, 0, wxALL, BORDER); topsizer->Add(rightsizer, 1, wxTOP | wxBOTTOM | wxRIGHT | wxEXPAND, BORDER); - SetSizerAndFit(topsizer); + main_sizer->Add(topsizer, 1, wxEXPAND); + main_sizer->Add(new StaticLine(this), 0, wxEXPAND | wxLEFT | wxRIGHT, HORIZ_SPACING); + main_sizer->Add(btn_sizer, 0, wxALL | wxEXPAND, VERT_SPACING); + + SetSizerAndFit(main_sizer); } void MsgDialog::add_btn(wxWindowID btn_id, bool set_focus /*= false*/) @@ -72,7 +76,7 @@ void MsgDialog::add_btn(wxWindowID btn_id, bool set_focus /*= false*/) wxButton* btn = new wxButton(this, btn_id); if (set_focus) btn->SetFocus(); - btn_sizer->Add(btn, 0, wxRIGHT, HORIZ_SPACING); + btn_sizer->Add(btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, HORIZ_SPACING); btn->Bind(wxEVT_BUTTON, [this, btn_id](wxCommandEvent&) { this->EndModal(btn_id); }); }; @@ -209,33 +213,38 @@ MessageDialog::MessageDialog(wxWindow* parent, apply_style(style); finalize(); } -#endif -// MessageWithCheckDialog +// RichMessageDialog -MessageWithCheckDialog::MessageWithCheckDialog( wxWindow* parent, - const wxString& message, - const wxString& checkbox_label, - const wxString& caption/* = wxEmptyString*/, - long style/* = wxOK*/) +RichMessageDialog::RichMessageDialog(wxWindow* parent, + const wxString& message, + const wxString& caption/* = wxEmptyString*/, + long style/* = wxOK*/) : MsgDialog(parent, caption.IsEmpty() ? wxString::Format(_L("%s info"), SLIC3R_APP_NAME) : caption, wxEmptyString, wxID_NONE) { add_msg_content(this, content_sizer, message); - m_check = new wxCheckBox(this, wxID_ANY, checkbox_label); - content_sizer->Add(m_check, 0, wxTOP, 10); + m_checkBox = new wxCheckBox(this, wxID_ANY, m_checkBoxText); + m_checkBox->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { m_checkBoxValue = m_checkBox->GetValue(); }); + + btn_sizer->Insert(0, m_checkBox, wxALIGN_CENTER_VERTICAL); apply_style(style); finalize(); } -bool MessageWithCheckDialog::GetCheckVal() +int RichMessageDialog::ShowModal() { - if (m_check) - return m_check->GetValue(); - return false; + if (m_checkBoxText.IsEmpty()) + m_checkBox->Hide(); + else + m_checkBox->SetLabelText(m_checkBoxText); + Layout(); + + return wxDialog::ShowModal(); } +#endif // InfoDialog diff --git a/src/slic3r/GUI/MsgDialog.hpp b/src/slic3r/GUI/MsgDialog.hpp index d3263f970e..22a81579a6 100644 --- a/src/slic3r/GUI/MsgDialog.hpp +++ b/src/slic3r/GUI/MsgDialog.hpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include class wxBoxSizer; class wxCheckBox; @@ -17,7 +19,6 @@ namespace Slic3r { namespace GUI { - // A message / query dialog with a bitmap on the left and any content on the right // with buttons underneath. struct MsgDialog : wxDialog @@ -87,6 +88,23 @@ public: }; #ifdef _WIN32 +// Generic static line, used intead of wxStaticLine +class StaticLine: public wxTextCtrl +{ +public: + StaticLine( wxWindow* parent, + wxWindowID id = wxID_ANY, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxLI_HORIZONTAL, + const wxString& name = wxASCII_STR(wxTextCtrlNameStr)) + : wxTextCtrl(parent, id, wxEmptyString, pos, size!=wxDefaultSize ? size : (style == wxLI_HORIZONTAL ? wxSize(10, 1) : wxSize(1, 10)), wxSIMPLE_BORDER, wxDefaultValidator, name) + { + this->Enable(false); + } + ~StaticLine() {} +}; + // Generic message dialog, used intead of wxMessageDialog class MessageDialog : public MsgDialog { @@ -101,7 +119,158 @@ public: MessageDialog &operator=(const MessageDialog&) = delete; virtual ~MessageDialog() = default; }; + +// Generic rich message dialog, used intead of wxRichMessageDialog +class RichMessageDialog : public MsgDialog +{ + wxCheckBox* m_checkBox{ nullptr }; + wxString m_checkBoxText; + bool m_checkBoxValue{ false }; + +public: + RichMessageDialog( wxWindow *parent, + const wxString& message, + const wxString& caption = wxEmptyString, + long style = wxOK); + RichMessageDialog(RichMessageDialog&&) = delete; + RichMessageDialog(const RichMessageDialog&) = delete; + RichMessageDialog &operator=(RichMessageDialog&&) = delete; + RichMessageDialog &operator=(const RichMessageDialog&) = delete; + virtual ~RichMessageDialog() = default; + + int ShowModal() override; + + void ShowCheckBox(const wxString& checkBoxText, bool checked = false) + { + m_checkBoxText = checkBoxText; + m_checkBoxValue = checked; + } + + wxString GetCheckBoxText() const { return m_checkBoxText; } + bool IsCheckBoxChecked() const { return m_checkBoxValue; } + +// This part o fcode isported from the "wx\msgdlg.h" + using wxMD = wxMessageDialogBase; + // customization of the message box buttons + virtual bool SetYesNoLabels(const wxMD::ButtonLabel& yes, const wxMD::ButtonLabel& no) + { + DoSetCustomLabel(m_yes, yes); + DoSetCustomLabel(m_no, no); + return true; + } + + virtual bool SetYesNoCancelLabels(const wxMD::ButtonLabel& yes, + const wxMD::ButtonLabel& no, + const wxMD::ButtonLabel& cancel) + { + DoSetCustomLabel(m_yes, yes); + DoSetCustomLabel(m_no, no); + DoSetCustomLabel(m_cancel, cancel); + return true; + } + + virtual bool SetOKLabel(const wxMD::ButtonLabel& ok) + { + DoSetCustomLabel(m_ok, ok); + return true; +} + + virtual bool SetOKCancelLabels(const wxMD::ButtonLabel& ok, + const wxMD::ButtonLabel& cancel) + { + DoSetCustomLabel(m_ok, ok); + DoSetCustomLabel(m_cancel, cancel); + return true; + } + + virtual bool SetHelpLabel(const wxMD::ButtonLabel& help) + { + DoSetCustomLabel(m_help, help); + return true; + } + // test if any custom labels were set + bool HasCustomLabels() const + { + return !(m_ok.empty() && m_cancel.empty() && m_help.empty() && + m_yes.empty() && m_no.empty()); + } + + // these functions return the label to be used for the button which is + // either a custom label explicitly set by the user or the default label, + // i.e. they always return a valid string + wxString GetYesLabel() const + { + return m_yes.empty() ? GetDefaultYesLabel() : m_yes; + } + wxString GetNoLabel() const + { + return m_no.empty() ? GetDefaultNoLabel() : m_no; + } + wxString GetOKLabel() const + { + return m_ok.empty() ? GetDefaultOKLabel() : m_ok; + } + wxString GetCancelLabel() const + { + return m_cancel.empty() ? GetDefaultCancelLabel() : m_cancel; + } + wxString GetHelpLabel() const + { + return m_help.empty() ? GetDefaultHelpLabel() : m_help; + } + +protected: + // this function is called by our public SetXXXLabels() and should assign + // the value to var with possibly some transformation (e.g. Cocoa version + // currently uses this to remove any accelerators from the button strings + // while GTK+ one handles stock items specifically here) + void DoSetCustomLabel(wxString& var, const wxMD::ButtonLabel& label) + { + var = label.GetAsString(); + } + + // these functions return the custom label or empty string and should be + // used only in specific circumstances such as creating the buttons with + // these labels (in which case it makes sense to only use a custom label if + // it was really given and fall back on stock label otherwise), use the + // Get{Yes,No,OK,Cancel}Label() methods above otherwise + const wxString& GetCustomYesLabel() const { return m_yes; } + const wxString& GetCustomNoLabel() const { return m_no; } + const wxString& GetCustomOKLabel() const { return m_ok; } + const wxString& GetCustomHelpLabel() const { return m_help; } + const wxString& GetCustomCancelLabel() const { return m_cancel; } + +private: + // these functions may be overridden to provide different defaults for the + // default button labels (this is used by wxGTK) + virtual wxString GetDefaultYesLabel() const { return wxGetTranslation("Yes"); } + virtual wxString GetDefaultNoLabel() const { return wxGetTranslation("No"); } + virtual wxString GetDefaultOKLabel() const { return wxGetTranslation("OK"); } + virtual wxString GetDefaultCancelLabel() const { return wxGetTranslation("Cancel"); } + virtual wxString GetDefaultHelpLabel() const { return wxGetTranslation("Help"); } + + // labels for the buttons, initially empty meaning that the defaults should + // be used, use GetYes/No/OK/CancelLabel() to access them + wxString m_yes, + m_no, + m_ok, + m_cancel, + m_help; +}; #else +// just a wrapper for wxStaticLine to use the same code on all platforms +class StaticLine : public wxStaticLine +{ +public: + StaticLine(wxWindow* parent, + wxWindowID id = wxID_ANY, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxLI_HORIZONTAL, + const wxString& name = wxASCII_STR(wxStaticLineNameStr)) + : wxStaticLine(parent, id, pos, size, style, name) {} + ~StaticLine() {} +}; // just a wrapper to wxMessageBox to use the same code on all platforms class MessageDialog : public wxMessageDialog { @@ -113,25 +282,19 @@ public: : wxMessageDialog(parent, message, caption, style) {} ~MessageDialog() {} }; -#endif -class MessageWithCheckDialog : public MsgDialog +// just a wrapper to wxRichMessageBox to use the same code on all platforms +class RichMessageDialog : public wxRichMessageDialog { - wxCheckBox* m_check{ nullptr }; public: - MessageWithCheckDialog(wxWindow* parent, + RichMessageDialog(wxWindow* parent, const wxString& message, - const wxString& checkbox_label, const wxString& caption = wxEmptyString, - long style = wxOK); - MessageWithCheckDialog(MessageWithCheckDialog&&) = delete; - MessageWithCheckDialog(const MessageWithCheckDialog&) = delete; - MessageWithCheckDialog& operator=(MessageWithCheckDialog&&) = delete; - MessageWithCheckDialog& operator=(const MessageWithCheckDialog&) = delete; - virtual ~MessageWithCheckDialog() = default; - - bool GetCheckVal(); + long style = wxOK) + : wxRichMessageDialog(parent, message, caption, style) {} + ~RichMessageDialog() {} }; +#endif // Generic info dialog, used for displaying exceptions class InfoDialog : public MsgDialog diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index eb64fb08ca..2567ca52f0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2482,15 +2482,15 @@ std::vector Plater::priv::load_files(const std::vector& input_ model.convert_from_meters(true); }; if (answer_convert_from_meters == wxOK_DEFAULT) { - MessageWithCheckDialog dlg(q, format_wxstr(_L_PLURAL( + RichMessageDialog dlg(q, format_wxstr(_L_PLURAL( "The dimensions of the object from file %s seem to be defined in meters.\n" "The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of the object?", "The dimensions of some objects from file %s seem to be defined in meters.\n" "The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n", - _L("Apply to all the remaining small objects being loaded."), _L("The object is too small"), wxICON_WARNING | wxYES | wxNO); + dlg.ShowCheckBox(_L("Apply to all the remaining small objects being loaded.")); int answer = dlg.ShowModal(); - if (dlg.GetCheckVal()) + if (dlg.IsCheckBoxChecked()) answer_convert_from_meters = answer; else convert_model_if(model, answer == wxID_YES); @@ -2504,15 +2504,15 @@ std::vector Plater::priv::load_files(const std::vector& input_ convert_from_imperial_units(model, true); }; if (answer_convert_from_imperial_units == wxOK_DEFAULT) { - MessageWithCheckDialog dlg(q, format_wxstr(_L_PLURAL( + RichMessageDialog dlg(q, format_wxstr(_L_PLURAL( "The dimensions of the object from file %s seem to be defined in inches.\n" "The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of the object?", "The dimensions of some objects from file %s seem to be defined in inches.\n" "The internal unit of PrusaSlicer are millimeters. Do you want to recalculate the dimensions of these objects?", model.objects.size()), from_path(filename)) + "\n", - _L("Apply to all the remaining small objects being loaded."), _L("The object is too small"), wxICON_WARNING | wxYES | wxNO); + dlg.ShowCheckBox(_L("Apply to all the remaining small objects being loaded.")); int answer = dlg.ShowModal(); - if (dlg.GetCheckVal()) + if (dlg.IsCheckBoxChecked()) answer_convert_from_imperial_units = answer; else convert_model_if(model, answer == wxID_YES);