1 #include "OG_CustomCtrl.hpp"
2 #include "OptionsGroup.hpp"
3 #include "Plater.hpp"
4 #include "GUI_App.hpp"
5 #include "libslic3r/AppConfig.hpp"
6
7 #include <wx/utils.h>
8 #include <boost/algorithm/string/split.hpp>
9 #include "libslic3r/Utils.hpp"
10 #include "I18N.hpp"
11 #include "format.hpp"
12
13 namespace Slic3r { namespace GUI {
14
is_point_in_rect(const wxPoint & pt,const wxRect & rect)15 static bool is_point_in_rect(const wxPoint& pt, const wxRect& rect)
16 {
17 return rect.GetLeft() <= pt.x && pt.x <= rect.GetRight() &&
18 rect.GetTop() <= pt.y && pt.y <= rect.GetBottom();
19 }
20
get_bitmap_size(const wxBitmap & bmp)21 static wxSize get_bitmap_size(const wxBitmap& bmp)
22 {
23 #ifdef __APPLE__
24 return bmp.GetScaledSize();
25 #else
26 return bmp.GetSize();
27 #endif
28 }
29
get_url(const wxString & path_end,bool get_default=false)30 static wxString get_url(const wxString& path_end, bool get_default = false)
31 {
32 if (path_end.IsEmpty())
33 return wxEmptyString;
34
35 wxString language = wxGetApp().app_config->get("translation_language");
36 wxString lang_marker = language.IsEmpty() ? "en" : language.BeforeFirst('_');
37
38 return wxString("https://help.prusa3d.com/") + lang_marker + "/article/" + path_end;
39 }
40
OG_CustomCtrl(wxWindow * parent,OptionsGroup * og,const wxPoint & pos,const wxSize & size,const wxValidator & val,const wxString & name)41 OG_CustomCtrl::OG_CustomCtrl( wxWindow* parent,
42 OptionsGroup* og,
43 const wxPoint& pos /* = wxDefaultPosition*/,
44 const wxSize& size/* = wxDefaultSize*/,
45 const wxValidator& val /* = wxDefaultValidator*/,
46 const wxString& name/* = wxEmptyString*/) :
47 wxPanel(parent, wxID_ANY, pos, size, /*wxWANTS_CHARS |*/ wxBORDER_NONE | wxTAB_TRAVERSAL),
48 opt_group(og)
49 {
50 if (!wxOSX)
51 SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
52
53 m_font = wxGetApp().normal_font();
54 m_em_unit = em_unit(m_parent);
55 m_v_gap = lround(1.0 * m_em_unit);
56 m_h_gap = lround(0.2 * m_em_unit);
57
58 m_bmp_mode_sz = get_bitmap_size(create_scaled_bitmap("mode_simple", this, wxOSX ? 10 : 12));
59 m_bmp_blinking_sz = get_bitmap_size(create_scaled_bitmap("search_blink", this));
60
61 init_ctrl_lines();// from og.lines()
62
63 this->Bind(wxEVT_PAINT, &OG_CustomCtrl::OnPaint, this);
64 this->Bind(wxEVT_MOTION, &OG_CustomCtrl::OnMotion, this);
65 this->Bind(wxEVT_LEFT_DOWN, &OG_CustomCtrl::OnLeftDown, this);
66 this->Bind(wxEVT_LEAVE_WINDOW, &OG_CustomCtrl::OnLeaveWin, this);
67 }
68
init_ctrl_lines()69 void OG_CustomCtrl::init_ctrl_lines()
70 {
71 const std::vector<Line>& og_lines = opt_group->get_lines();
72 for (const Line& line : og_lines)
73 {
74 if (line.full_width && (
75 // description line
76 line.widget != nullptr ||
77 // description line with widget (button)
78 !line.get_extra_widgets().empty())
79 )
80 continue;
81
82 const std::vector<Option>& option_set = line.get_options();
83 wxCoord height;
84
85 // if we have a single option with no label, no sidetext just add it directly to sizer
86 if (option_set.size() == 1 && opt_group->label_width == 0 && option_set.front().opt.full_width &&
87 option_set.front().opt.label.empty() &&
88 option_set.front().opt.sidetext.size() == 0 && option_set.front().side_widget == nullptr &&
89 line.get_extra_widgets().size() == 0)
90 {
91 height = m_bmp_blinking_sz.GetHeight() + m_v_gap;
92 ctrl_lines.emplace_back(CtrlLine(height, this, line, true));
93 }
94 else if (opt_group->label_width != 0 && (!line.label.IsEmpty() || option_set.front().opt.gui_type == "legend") )
95 {
96 wxSize label_sz = GetTextExtent(line.label);
97 height = label_sz.y * (label_sz.GetWidth() > int(opt_group->label_width * m_em_unit) ? 2 : 1) + m_v_gap;
98 ctrl_lines.emplace_back(CtrlLine(height, this, line, false, opt_group->staticbox));
99 }
100 else
101 int i = 0;
102 }
103 }
104
get_height(const Line & line)105 int OG_CustomCtrl::get_height(const Line& line)
106 {
107 for (auto ctrl_line : ctrl_lines)
108 if (&ctrl_line.og_line == &line)
109 return ctrl_line.height;
110
111 return 0;
112 }
113
get_pos(const Line & line,Field * field_in)114 wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/)
115 {
116 wxCoord v_pos = 0;
117 wxCoord h_pos = 0;
118
119 auto correct_line_height = [](int& line_height, wxWindow* win)
120 {
121 int win_height = win->GetSize().GetHeight();
122 if (line_height < win_height)
123 line_height = win_height;
124 };
125
126 for (CtrlLine& ctrl_line : ctrl_lines) {
127 if (&ctrl_line.og_line == &line)
128 {
129 h_pos = m_bmp_mode_sz.GetWidth() + m_h_gap;
130 if (line.near_label_widget_win) {
131 wxSize near_label_widget_sz = line.near_label_widget_win->GetSize();
132 if (field_in)
133 h_pos += near_label_widget_sz.GetWidth() + m_h_gap;
134 else
135 break;
136 }
137
138 wxString label = line.label;
139 if (opt_group->label_width != 0)
140 h_pos += opt_group->label_width * m_em_unit + m_h_gap;
141
142 int blinking_button_width = m_bmp_blinking_sz.GetWidth() + m_h_gap;
143
144 if (line.widget) {
145 h_pos += blinking_button_width;
146
147 for (auto child : line.widget_sizer->GetChildren())
148 if (child->IsWindow())
149 correct_line_height(ctrl_line.height, child->GetWindow());
150 break;
151 }
152
153 // If we have a single option with no sidetext
154 const std::vector<Option>& option_set = line.get_options();
155 if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 &&
156 option_set.front().opt.label.empty() &&
157 option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0)
158 {
159 h_pos += 3 * blinking_button_width;
160 Field* field = opt_group->get_field(option_set.front().opt_id);
161 correct_line_height(ctrl_line.height, field->getWindow());
162 break;
163 }
164
165 for (auto opt : option_set) {
166 Field* field = opt_group->get_field(opt.opt_id);
167 correct_line_height(ctrl_line.height, field->getWindow());
168
169 ConfigOptionDef option = opt.opt;
170 // add label if any
171 if (!option.label.empty()) {
172 //! To correct translation by context have to use wxGETTEXT_IN_CONTEXT macro from wxWidget 3.1.1
173 label = (option.label == L_CONTEXT("Top", "Layers") || option.label == L_CONTEXT("Bottom", "Layers")) ?
174 _CTX(option.label, "Layers") : _(option.label);
175 label += ":";
176
177 wxCoord label_w, label_h;
178 #ifdef __WXMSW__
179 // when we use 2 monitors with different DPIs, GetTextExtent() return value for the primary display
180 // so, use dc.GetMultiLineTextExtent on Windows
181 wxClientDC dc(this);
182 dc.SetFont(m_font);
183 dc.GetMultiLineTextExtent(label, &label_w, &label_h);
184 #else
185 GetTextExtent(label, &label_w, &label_h, 0, 0, &m_font);
186 #endif //__WXMSW__
187 h_pos += label_w + 1 + m_h_gap;
188 }
189 h_pos += (opt.opt.gui_type == "legend" ? 1 : 3) * blinking_button_width;
190
191 if (field == field_in)
192 break;
193 if (opt.opt.gui_type == "legend")
194 h_pos += 2 * blinking_button_width;
195
196 h_pos += field->getWindow()->GetSize().x;
197
198 if (option_set.size() == 1 && option_set.front().opt.full_width)
199 break;
200
201 // add sidetext if any
202 if (!option.sidetext.empty() || opt_group->sidetext_width > 0)
203 h_pos += opt_group->sidetext_width * m_em_unit + m_h_gap;
204
205 if (opt.opt_id != option_set.back().opt_id) //! istead of (opt != option_set.back())
206 h_pos += lround(0.6 * m_em_unit);
207 }
208 break;
209 }
210 if (ctrl_line.is_visible)
211 v_pos += ctrl_line.height;
212 }
213
214 return wxPoint(h_pos, v_pos);
215 }
216
217
OnPaint(wxPaintEvent &)218 void OG_CustomCtrl::OnPaint(wxPaintEvent&)
219 {
220 // case, when custom controll is destroyed but doesn't deleted from the evet loop
221 if(!this->opt_group->custom_ctrl)
222 return;
223
224 wxPaintDC dc(this);
225 dc.SetFont(m_font);
226
227 wxCoord v_pos = 0;
228 for (CtrlLine& line : ctrl_lines) {
229 if (!line.is_visible)
230 continue;
231 line.render(dc, v_pos);
232 v_pos += line.height;
233 }
234 }
235
OnMotion(wxMouseEvent & event)236 void OG_CustomCtrl::OnMotion(wxMouseEvent& event)
237 {
238 const wxPoint pos = event.GetLogicalPosition(wxClientDC(this));
239 wxString tooltip;
240
241 wxString language = wxGetApp().app_config->get("translation_language");
242
243 bool suppress_hyperlinks = get_app_config()->get("suppress_hyperlinks") == "1";
244
245 for (CtrlLine& line : ctrl_lines) {
246 line.is_focused = is_point_in_rect(pos, line.rect_label);
247 if (line.is_focused) {
248 if (!suppress_hyperlinks && !line.og_line.label_path.empty())
249 tooltip = get_url(line.og_line.label_path) +"\n\n";
250 tooltip += line.og_line.label_tooltip;
251 break;
252 }
253
254 for (size_t opt_idx = 0; opt_idx < line.rects_undo_icon.size(); opt_idx++)
255 if (is_point_in_rect(pos, line.rects_undo_icon[opt_idx])) {
256 const std::vector<Option>& option_set = line.og_line.get_options();
257 Field* field = opt_group->get_field(option_set[opt_idx].opt_id);
258 if (field)
259 tooltip = *field->undo_tooltip();
260 break;
261 }
262 for (size_t opt_idx = 0; opt_idx < line.rects_undo_to_sys_icon.size(); opt_idx++)
263 if (is_point_in_rect(pos, line.rects_undo_to_sys_icon[opt_idx])) {
264 const std::vector<Option>& option_set = line.og_line.get_options();
265 Field* field = opt_group->get_field(option_set[opt_idx].opt_id);
266 if (field)
267 tooltip = *field->undo_to_sys_tooltip();
268 break;
269 }
270 if (!tooltip.IsEmpty())
271 break;
272 }
273
274 // Set tooltips with information for each icon
275 this->SetToolTip(tooltip);
276
277 Refresh();
278 Update();
279 event.Skip();
280 }
281
OnLeftDown(wxMouseEvent & event)282 void OG_CustomCtrl::OnLeftDown(wxMouseEvent& event)
283 {
284 const wxPoint pos = event.GetLogicalPosition(wxClientDC(this));
285
286 for (const CtrlLine& line : ctrl_lines) {
287 if (line.launch_browser())
288 return;
289 for (size_t opt_idx = 0; opt_idx < line.rects_undo_icon.size(); opt_idx++)
290 if (is_point_in_rect(pos, line.rects_undo_icon[opt_idx])) {
291 const std::vector<Option>& option_set = line.og_line.get_options();
292 Field* field = opt_group->get_field(option_set[opt_idx].opt_id);
293 if (field)
294 field->on_back_to_initial_value();
295 event.Skip();
296 return;
297 }
298 for (size_t opt_idx = 0; opt_idx < line.rects_undo_to_sys_icon.size(); opt_idx++)
299 if (is_point_in_rect(pos, line.rects_undo_to_sys_icon[opt_idx])) {
300 const std::vector<Option>& option_set = line.og_line.get_options();
301 Field* field = opt_group->get_field(option_set[opt_idx].opt_id);
302 if (field)
303 field->on_back_to_sys_value();
304 event.Skip();
305 return;
306 }
307 }
308
309 }
310
OnLeaveWin(wxMouseEvent & event)311 void OG_CustomCtrl::OnLeaveWin(wxMouseEvent& event)
312 {
313 for (CtrlLine& line : ctrl_lines)
314 line.is_focused = false;
315
316 Refresh();
317 Update();
318 event.Skip();
319 }
320
update_visibility(ConfigOptionMode mode)321 bool OG_CustomCtrl::update_visibility(ConfigOptionMode mode)
322 {
323 wxCoord v_pos = 0;
324
325 size_t invisible_lines = 0;
326 for (CtrlLine& line : ctrl_lines) {
327 line.update_visibility(mode);
328 if (line.is_visible)
329 v_pos += (wxCoord)line.height;
330 else
331 invisible_lines++;
332 }
333
334 this->SetMinSize(wxSize(wxDefaultCoord, v_pos));
335
336 return invisible_lines != ctrl_lines.size();
337 }
338
correct_window_position(wxWindow * win,const Line & line,Field * field)339 void OG_CustomCtrl::correct_window_position(wxWindow* win, const Line& line, Field* field/* = nullptr*/)
340 {
341 wxPoint pos = get_pos(line, field);
342 int line_height = get_height(line);
343 pos.y += std::max(0, int(0.5 * (line_height - win->GetSize().y)));
344 win->SetPosition(pos);
345 };
346
correct_widgets_position(wxSizer * widget,const Line & line,Field * field)347 void OG_CustomCtrl::correct_widgets_position(wxSizer* widget, const Line& line, Field* field/* = nullptr*/) {
348 auto children = widget->GetChildren();
349 wxPoint line_pos = get_pos(line, field);
350 int line_height = get_height(line);
351 for (auto child : children)
352 if (child->IsWindow()) {
353 wxPoint pos = line_pos;
354 wxSize sz = child->GetWindow()->GetSize();
355 pos.y += std::max(0, int(0.5 * (line_height - sz.y)));
356 child->GetWindow()->SetPosition(pos);
357 line_pos.x += sz.x + m_h_gap;
358 }
359 };
360
msw_rescale()361 void OG_CustomCtrl::msw_rescale()
362 {
363 #ifdef __WXOSX__
364 return;
365 #endif
366 m_font = wxGetApp().normal_font();
367 m_em_unit = em_unit(m_parent);
368 m_v_gap = lround(1.0 * m_em_unit);
369 m_h_gap = lround(0.2 * m_em_unit);
370
371 m_bmp_mode_sz = create_scaled_bitmap("mode_simple", this, wxOSX ? 10 : 12).GetSize();
372 m_bmp_blinking_sz = create_scaled_bitmap("search_blink", this).GetSize();
373
374 wxCoord v_pos = 0;
375 for (CtrlLine& line : ctrl_lines) {
376 line.msw_rescale();
377 if (line.is_visible)
378 v_pos += (wxCoord)line.height;
379 }
380 this->SetMinSize(wxSize(wxDefaultCoord, v_pos));
381
382 GetParent()->Layout();
383 }
384
sys_color_changed()385 void OG_CustomCtrl::sys_color_changed()
386 {
387 }
388
CtrlLine(wxCoord height,OG_CustomCtrl * ctrl,const Line & og_line,bool draw_just_act_buttons,bool draw_mode_bitmap)389 OG_CustomCtrl::CtrlLine::CtrlLine( wxCoord height,
390 OG_CustomCtrl* ctrl,
391 const Line& og_line,
392 bool draw_just_act_buttons /* = false*/,
393 bool draw_mode_bitmap/* = true*/):
394 height(height),
395 ctrl(ctrl),
396 og_line(og_line),
397 draw_just_act_buttons(draw_just_act_buttons),
398 draw_mode_bitmap(draw_mode_bitmap)
399 {
400
401 for (size_t i = 0; i < og_line.get_options().size(); i++) {
402 rects_undo_icon.emplace_back(wxRect());
403 rects_undo_to_sys_icon.emplace_back(wxRect());
404 }
405 }
406
correct_items_positions()407 void OG_CustomCtrl::CtrlLine::correct_items_positions()
408 {
409 if (draw_just_act_buttons || !is_visible)
410 return;
411
412 if (og_line.near_label_widget_win)
413 ctrl->correct_window_position(og_line.near_label_widget_win, og_line);
414 if (og_line.widget_sizer)
415 ctrl->correct_widgets_position(og_line.widget_sizer, og_line);
416 if (og_line.extra_widget_sizer)
417 ctrl->correct_widgets_position(og_line.extra_widget_sizer, og_line);
418
419 const std::vector<Option>& option_set = og_line.get_options();
420 for (auto opt : option_set) {
421 Field* field = ctrl->opt_group->get_field(opt.opt_id);
422 if (!field)
423 continue;
424 if (field->getSizer())
425 ctrl->correct_widgets_position(field->getSizer(), og_line, field);
426 else if (field->getWindow())
427 ctrl->correct_window_position(field->getWindow(), og_line, field);
428 }
429 }
430
msw_rescale()431 void OG_CustomCtrl::CtrlLine::msw_rescale()
432 {
433 // if we have a single option with no label, no sidetext
434 if (draw_just_act_buttons)
435 height = get_bitmap_size(create_scaled_bitmap("empty")).GetHeight();
436
437 if (ctrl->opt_group->label_width != 0 && !og_line.label.IsEmpty()) {
438 wxSize label_sz = ctrl->GetTextExtent(og_line.label);
439 height = label_sz.y * (label_sz.GetWidth() > int(ctrl->opt_group->label_width * ctrl->m_em_unit) ? 2 : 1) + ctrl->m_v_gap;
440 }
441
442 correct_items_positions();
443 }
444
update_visibility(ConfigOptionMode mode)445 void OG_CustomCtrl::CtrlLine::update_visibility(ConfigOptionMode mode)
446 {
447 const std::vector<Option>& option_set = og_line.get_options();
448
449 const ConfigOptionMode& line_mode = option_set.front().opt.mode;
450 is_visible = line_mode <= mode;
451
452 if (draw_just_act_buttons)
453 return;
454
455 if (og_line.near_label_widget_win)
456 og_line.near_label_widget_win->Show(is_visible);
457 if (og_line.widget_sizer)
458 og_line.widget_sizer->ShowItems(is_visible);
459 if (og_line.extra_widget_sizer)
460 og_line.extra_widget_sizer->ShowItems(is_visible);
461
462 for (auto opt : option_set) {
463 Field* field = ctrl->opt_group->get_field(opt.opt_id);
464 if (!field)
465 continue;
466
467 if (field->getSizer()) {
468 auto children = field->getSizer()->GetChildren();
469 for (auto child : children)
470 if (child->IsWindow())
471 child->GetWindow()->Show(is_visible);
472 }
473 else if (field->getWindow())
474 field->getWindow()->Show(is_visible);
475 }
476
477 correct_items_positions();
478 }
479
render(wxDC & dc,wxCoord v_pos)480 void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos)
481 {
482 Field* field = ctrl->opt_group->get_field(og_line.get_options().front().opt_id);
483
484 bool suppress_hyperlinks = get_app_config()->get("suppress_hyperlinks") == "1";
485 if (draw_just_act_buttons) {
486 if (field)
487 draw_act_bmps(dc, wxPoint(0, v_pos), field->undo_to_sys_bitmap()->bmp(), field->undo_bitmap()->bmp(), field->blink());
488 return;
489 }
490
491 wxCoord h_pos = draw_mode_bmp(dc, v_pos);
492
493 if (og_line.near_label_widget_win)
494 h_pos += og_line.near_label_widget_win->GetSize().x + ctrl->m_h_gap;
495
496 const std::vector<Option>& option_set = og_line.get_options();
497
498 wxString label = og_line.label;
499 bool is_url_string = false;
500 if (ctrl->opt_group->label_width != 0 && !label.IsEmpty()) {
501 const wxColour* text_clr = (option_set.size() == 1 && field ? field->label_color() : og_line.full_Label_color);
502 is_url_string = !suppress_hyperlinks && !og_line.label_path.IsEmpty();
503 h_pos = draw_text(dc, wxPoint(h_pos, v_pos), label + ":", text_clr, ctrl->opt_group->label_width * ctrl->m_em_unit, is_url_string);
504 }
505
506 // If there's a widget, build it and set result to the correct position.
507 if (og_line.widget != nullptr) {
508 draw_blinking_bmp(dc, wxPoint(h_pos, v_pos), og_line.blink);
509 return;
510 }
511
512 // If we're here, we have more than one option or a single option with sidetext
513 // so we need a horizontal sizer to arrange these things
514
515 // If we have a single option with no sidetext just add it directly to the grid sizer
516 if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 &&
517 option_set.front().opt.label.empty() &&
518 option_set.front().side_widget == nullptr && og_line.get_extra_widgets().size() == 0)
519 {
520 if (field && field->undo_to_sys_bitmap())
521 h_pos = draw_act_bmps(dc, wxPoint(h_pos, v_pos), field->undo_to_sys_bitmap()->bmp(), field->undo_bitmap()->bmp(), field->blink()) + ctrl->m_h_gap;
522 // update width for full_width fields
523 if (option_set.front().opt.full_width && field->getWindow())
524 field->getWindow()->SetSize(ctrl->GetSize().x - h_pos, -1);
525 return;
526 }
527
528 size_t bmp_rect_id = 0;
529 for (const Option& opt : option_set) {
530 field = ctrl->opt_group->get_field(opt.opt_id);
531 ConfigOptionDef option = opt.opt;
532 // add label if any
533 if (!option.label.empty()) {
534 //! To correct translation by context have to use wxGETTEXT_IN_CONTEXT macro from wxWidget 3.1.1
535 label = (option.label == L_CONTEXT("Top", "Layers") || option.label == L_CONTEXT("Bottom", "Layers")) ?
536 _CTX(option.label, "Layers") : _(option.label);
537 label += ":";
538
539 if (is_url_string)
540 is_url_string = false;
541 else if(opt == option_set.front())
542 is_url_string = !suppress_hyperlinks && !og_line.label_path.IsEmpty();
543 h_pos = draw_text(dc, wxPoint(h_pos, v_pos), label, field ? field->label_color() : nullptr, ctrl->opt_group->sublabel_width * ctrl->m_em_unit, is_url_string);
544 }
545
546 if (field && field->undo_to_sys_bitmap()) {
547 h_pos = draw_act_bmps(dc, wxPoint(h_pos, v_pos), field->undo_to_sys_bitmap()->bmp(), field->undo_bitmap()->bmp(), field->blink(), bmp_rect_id++);
548 if (field->getSizer())
549 {
550 auto children = field->getSizer()->GetChildren();
551 for (auto child : children)
552 if (child->IsWindow())
553 h_pos += child->GetWindow()->GetSize().x + ctrl->m_h_gap;
554 }
555 else if (field->getWindow())
556 h_pos += field->getWindow()->GetSize().x + ctrl->m_h_gap;
557 }
558
559 // add field
560 if (option_set.size() == 1 && option_set.front().opt.full_width)
561 break;
562
563 // add sidetext if any
564 if (!option.sidetext.empty() || ctrl->opt_group->sidetext_width > 0)
565 h_pos = draw_text(dc, wxPoint(h_pos, v_pos), _(option.sidetext), nullptr, ctrl->opt_group->sidetext_width * ctrl->m_em_unit);
566
567 if (opt.opt_id != option_set.back().opt_id) //! istead of (opt != option_set.back())
568 h_pos += lround(0.6 * ctrl->m_em_unit);
569 }
570 }
571
draw_mode_bmp(wxDC & dc,wxCoord v_pos)572 wxCoord OG_CustomCtrl::CtrlLine::draw_mode_bmp(wxDC& dc, wxCoord v_pos)
573 {
574 if (!draw_mode_bitmap)
575 return ctrl->m_h_gap;
576
577 ConfigOptionMode mode = og_line.get_options()[0].opt.mode;
578 const std::string& bmp_name = mode == ConfigOptionMode::comSimple ? "mode_simple" :
579 mode == ConfigOptionMode::comAdvanced ? "mode_advanced" : "mode_expert";
580 wxBitmap bmp = create_scaled_bitmap(bmp_name, ctrl, wxOSX ? 10 : 12);
581 wxCoord y_draw = v_pos + lround((height - get_bitmap_size(bmp).GetHeight()) / 2);
582
583 if (og_line.get_options().front().opt.gui_type != "legend")
584 dc.DrawBitmap(bmp, 0, y_draw);
585
586 return get_bitmap_size(bmp).GetWidth() + ctrl->m_h_gap;
587 }
588
draw_text(wxDC & dc,wxPoint pos,const wxString & text,const wxColour * color,int width,bool is_url)589 wxCoord OG_CustomCtrl::CtrlLine::draw_text(wxDC& dc, wxPoint pos, const wxString& text, const wxColour* color, int width, bool is_url/* = false*/)
590 {
591 wxString multiline_text;
592 if (width > 0 && dc.GetTextExtent(text).x > width) {
593 multiline_text = text;
594
595 size_t idx = size_t(-1);
596 for (size_t i = 0; i < multiline_text.Len(); i++)
597 {
598 if (multiline_text[i] == ' ')
599 {
600 if (dc.GetTextExtent(multiline_text.SubString(0, i)).x < width)
601 idx = i;
602 else {
603 if (idx != size_t(-1))
604 multiline_text[idx] = '\n';
605 else
606 multiline_text[i] = '\n';
607 break;
608 }
609 }
610 }
611
612 if (idx != size_t(-1))
613 multiline_text[idx] = '\n';
614 }
615
616 if (!text.IsEmpty()) {
617 const wxString& out_text = multiline_text.IsEmpty() ? text : multiline_text;
618 wxCoord text_width, text_height;
619 dc.GetMultiLineTextExtent(out_text, &text_width, &text_height);
620
621 pos.y = pos.y + lround((height - text_height) / 2);
622 if (width > 0)
623 rect_label = wxRect(pos, wxSize(text_width, text_height));
624
625 wxColour old_clr = dc.GetTextForeground();
626 wxFont old_font = dc.GetFont();
627 if (is_focused && is_url)
628 // temporary workaround for the OSX because of strange Bold font behavior on BigSerf
629 #ifdef __APPLE__
630 dc.SetFont(old_font.Underlined());
631 #else
632 dc.SetFont(old_font.Bold().Underlined());
633 #endif
634 dc.SetTextForeground(color ? *color : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
635 dc.DrawText(out_text, pos);
636 dc.SetTextForeground(old_clr);
637 dc.SetFont(old_font);
638
639 if (width < 1)
640 width = text_width;
641 }
642
643 return pos.x + width + ctrl->m_h_gap;
644 }
645
draw_blinking_bmp(wxDC & dc,wxPoint pos,bool is_blinking)646 wxPoint OG_CustomCtrl::CtrlLine::draw_blinking_bmp(wxDC& dc, wxPoint pos, bool is_blinking)
647 {
648 wxBitmap bmp_blinking = create_scaled_bitmap(is_blinking ? "search_blink" : "empty", ctrl);
649 wxCoord h_pos = pos.x;
650 wxCoord v_pos = pos.y + lround((height - get_bitmap_size(bmp_blinking).GetHeight()) / 2);
651
652 dc.DrawBitmap(bmp_blinking, h_pos, v_pos);
653
654 int bmp_dim = get_bitmap_size(bmp_blinking).GetWidth();
655
656 h_pos += bmp_dim + ctrl->m_h_gap;
657 return wxPoint(h_pos, v_pos);
658 }
659
draw_act_bmps(wxDC & dc,wxPoint pos,const wxBitmap & bmp_undo_to_sys,const wxBitmap & bmp_undo,bool is_blinking,size_t rect_id)660 wxCoord OG_CustomCtrl::CtrlLine::draw_act_bmps(wxDC& dc, wxPoint pos, const wxBitmap& bmp_undo_to_sys, const wxBitmap& bmp_undo, bool is_blinking, size_t rect_id)
661 {
662 pos = draw_blinking_bmp(dc, pos, is_blinking);
663 wxCoord h_pos = pos.x;
664 wxCoord v_pos = pos.y;
665
666 dc.DrawBitmap(bmp_undo_to_sys, h_pos, v_pos);
667
668 int bmp_dim = get_bitmap_size(bmp_undo_to_sys).GetWidth();
669 rects_undo_to_sys_icon[rect_id] = wxRect(h_pos, v_pos, bmp_dim, bmp_dim);
670
671 h_pos += bmp_dim + ctrl->m_h_gap;
672 dc.DrawBitmap(bmp_undo, h_pos, v_pos);
673
674 bmp_dim = get_bitmap_size(bmp_undo).GetWidth();
675 rects_undo_icon[rect_id] = wxRect(h_pos, v_pos, bmp_dim, bmp_dim);
676
677 h_pos += bmp_dim + ctrl->m_h_gap;
678
679 return h_pos;
680 }
681
launch_browser() const682 bool OG_CustomCtrl::CtrlLine::launch_browser() const
683 {
684 if (!is_focused || og_line.label_path.IsEmpty())
685 return false;
686
687 bool launch = true;
688
689 if (get_app_config()->get("suppress_hyperlinks").empty()) {
690 RememberChoiceDialog dialog(nullptr, _L("Should we open this hyperlink in your default browser?"), _L("PrusaSlicer: Open hyperlink"));
691 int answer = dialog.ShowModal();
692 launch = answer == wxID_YES;
693
694 get_app_config()->set("suppress_hyperlinks", dialog.remember_choice() ? (answer == wxID_NO ? "1" : "0") : "");
695 }
696 if (launch)
697 launch = get_app_config()->get("suppress_hyperlinks") != "1";
698
699 return launch && wxLaunchDefaultBrowser(get_url(og_line.label_path));
700 }
701
702
RememberChoiceDialog(wxWindow * parent,const wxString & msg_text,const wxString & caption)703 RememberChoiceDialog::RememberChoiceDialog(wxWindow* parent, const wxString& msg_text, const wxString& caption)
704 : wxDialog(parent, wxID_ANY, caption, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxICON_INFORMATION)
705 {
706 this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
707 this->SetEscapeId(wxID_CLOSE);
708
709 wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
710
711 m_remember_choice = new wxCheckBox(this, wxID_ANY, _L("Remember my choice"));
712 m_remember_choice->SetValue(false);
713 m_remember_choice->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& evt)
714 {
715 if (!evt.IsChecked())
716 return;
717 wxString preferences_item = _L("Suppress to open hyperlink in browser");
718 wxString msg =
719 _L("PrusaSlicer will remember your choice.") + "\n\n" +
720 _L("You will not be asked about it again on label hovering.") + "\n\n" +
721 format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item);
722
723 wxMessageDialog dialog(nullptr, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
724 if (dialog.ShowModal() == wxID_CANCEL)
725 m_remember_choice->SetValue(false);
726 });
727
728
729 // Add dialog's buttons
730 wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxYES | wxNO);
731 wxButton* btnYES = static_cast<wxButton*>(this->FindWindowById(wxID_YES, this));
732 wxButton* btnNO = static_cast<wxButton*>(this->FindWindowById(wxID_NO, this));
733 btnYES->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { this->EndModal(wxID_YES); });
734 btnNO->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { this->EndModal(wxID_NO); });
735
736 topSizer->Add(new wxStaticText(this, wxID_ANY, msg_text), 0, wxEXPAND | wxALL, 10);
737 topSizer->Add(m_remember_choice, 0, wxEXPAND | wxALL, 10);
738 topSizer->Add(btns, 0, wxEXPAND | wxALL, 10);
739
740 this->SetSizer(topSizer);
741 topSizer->SetSizeHints(this);
742
743 this->CenterOnScreen();
744 }
745
746 } // GUI
747 } // Slic3r
748