, which supplies the fixed face. does both but adds a blank line above it.
+class CodeExcerptTagHandler : public wxHtmlWinTagHandler
+{
+public:
+ wxString GetSupportedTags() override { return wxT("EXCERPT"); }
+ bool HandleTag(const wxHtmlTag &tag) override
+ {
+ const wxHtmlWinParser::WhitespaceMode ws = m_WParser->GetWhitespaceMode();
+ m_WParser->SetWhitespaceMode(wxHtmlWinParser::Whitespace_Pre);
+ ParseInner(tag);
+ m_WParser->SetWhitespaceMode(ws);
+ return true;
+ }
+};
+
+// Render the message as HTML, monospacing only the code excerpts.
+static std::string format_parser_error_html(const std::string &msg)
+{
+ std::string out;
+ for (const auto &[text, is_code] : classify_code_lines(msg)) {
+ if (!out.empty()) out += "
"; // join, not trail; a trailing
forces a scrollbar
+ std::string escaped = xml_escape(text);
+ if (is_code)
+ out += "" + escaped + "";
+ else
+ out += escaped;
+ }
+ return out;
+}
+
+// Measure each line in the font it will render in, so the dialog fits the longest line without slack.
+static wxSize measure_mixed_text(wxWindow *parent, const std::string &msg, const wxFont &prose_font, const wxFont &code_font)
+{
+ wxClientDC dc(parent);
+ int width = 0, height = 0;
+ for (const auto &[text, is_code] : classify_code_lines(msg)) {
+ dc.SetFont(is_code ? code_font : prose_font);
+ width = std::max(width, dc.GetTextExtent(wxString::FromUTF8(text.c_str())).GetWidth());
+ height += dc.GetCharHeight();
+ }
+ return wxSize(width, height);
+}
+
// 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,
+ bool has_code_excerpts = false,
+ bool is_marked_msg = false,
const wxString &link_text = "",
std::function link_callback = nullptr)
{
@@ -243,7 +318,7 @@ static void add_msg_content(wxWindow *parent,
// count lines in the message
int msg_lines = 0;
- if (!monospaced_font) {
+ if (!has_code_excerpts) {
int line_len = 55;// count of symbols in one line
int start_line = 0;
for (auto i = msg.begin(); i != msg.end(); ++i) {
@@ -300,13 +375,23 @@ static void add_msg_content(wxWindow *parent,
page_size = wxSize(info_width, page_height);
}
else {
- wxClientDC dc(parent);
- dc.SetFont(font); // ORCA without this it calculates bigger size
- wxSize msg_sz = dc.GetMultiLineTextExtent(msg) + parent->FromDIP(wxSize(10,5)); // added extra spacing to prevent wrapping
+ wxSize msg_sz;
+ if (has_code_excerpts) {
+ msg_sz = measure_mixed_text(parent, msg.ToUTF8().data(), font, monospace);
+ } else {
+ wxClientDC dc(parent);
+ dc.SetFont(font); // ORCA without this it calculates bigger size
+ msg_sz = dc.GetMultiLineTextExtent(msg);
+ }
+ msg_sz += parent->FromDIP(wxSize(10,5)); // added extra spacing to prevent wrapping
- page_size = wxSize(std::min(msg_sz.GetX(), info_width), std::min(msg_sz.GetY(), info_width));
+ int page_height = msg_sz.GetY();
+ // Reserve the horizontal scrollbar's height, or it clips the last line.
+ if (msg_sz.GetX() > info_width)
+ page_height += wxSystemSettings::GetMetric(wxSYS_HSCROLL_Y, parent);
+ page_size = wxSize(std::min(msg_sz.GetX(), info_width), std::min(page_height, info_width));
// Extra line breaks in message dialog
- if (link_text.IsEmpty() && !link_callback && is_marked_msg == false) {//for common text
+ if (link_text.IsEmpty() && !link_callback && is_marked_msg == false && !has_code_excerpts) {//for common text
html->Destroy();
if (msg_sz.GetX() < info_width) {//No need for line breaks
info_width = msg_sz.GetX();
@@ -337,12 +422,15 @@ static void add_msg_content(wxWindow *parent,
}
html->SetMinSize(page_size);
- std::string msg_escaped = xml_escape(msg.ToUTF8().data(), is_marked_msg);
- boost::replace_all(msg_escaped, "\r\n", "
");
- boost::replace_all(msg_escaped, "\n", "
");
- if (monospaced_font)
- // Code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
- msg_escaped = std::string("") + msg_escaped + "
";
+ std::string msg_escaped;
+ if (has_code_excerpts) {
+ html->GetParser()->AddTagHandler(new CodeExcerptTagHandler());
+ msg_escaped = format_parser_error_html(msg.ToUTF8().data());
+ } else {
+ msg_escaped = xml_escape(msg.ToUTF8().data(), is_marked_msg);
+ boost::replace_all(msg_escaped, "\r\n", "
");
+ boost::replace_all(msg_escaped, "\n", "
");
+ }
if (!link_text.IsEmpty() && link_callback) {
msg_escaped += "" + std::string(link_text.ToUTF8().data()) + "";
@@ -360,15 +448,15 @@ static void add_msg_content(wxWindow *parent,
// ErrorDialog
-ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool monospaced_font)
+ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool has_code_excerpts)
: MsgDialog(parent, wxString::Format(_(L("%s error")), SLIC3R_APP_FULL_NAME),
wxString::Format(_(L("%s has encountered an error")), SLIC3R_APP_FULL_NAME), wxOK)
, msg(temp_msg)
{
- add_msg_content(this, content_sizer, msg, monospaced_font);
+ add_msg_content(this, content_sizer, msg, has_code_excerpts);
- // Use a small bitmap with monospaced font, as the error text will not be wrapped.
- logo->SetBitmap(create_scaled_bitmap("OrcaSlicer_192px_grayscale.png", this, monospaced_font ? 48 : /*1*/64));
+ // Use a small bitmap for code excerpts, which cannot wrap and so need the width.
+ logo->SetBitmap(create_scaled_bitmap("OrcaSlicer_192px_grayscale.png", this, has_code_excerpts ? 48 : /*1*/64));
SetMaxSize(MSG_DLG_MAX_SIZE);
diff --git a/src/slic3r/GUI/MsgDialog.hpp b/src/slic3r/GUI/MsgDialog.hpp
index 174d734336..90fd160310 100644
--- a/src/slic3r/GUI/MsgDialog.hpp
+++ b/src/slic3r/GUI/MsgDialog.hpp
@@ -106,9 +106,9 @@ protected:
class ErrorDialog : public MsgDialog
{
public:
- // If monospaced_font is true, the error message is displayed using html tags,
- // so that the code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
- ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool courier_font);
+ // If has_code_excerpts is true, code excerpts (a source line and the caret line below it) render
+ // monospaced so the caret aligns. Used for placeholder-parser errors.
+ ErrorDialog(wxWindow *parent, const wxString &temp_msg, bool has_code_excerpts);
ErrorDialog(ErrorDialog &&) = delete;
ErrorDialog(const ErrorDialog &) = delete;
ErrorDialog &operator=(ErrorDialog &&) = delete;