1 #include "GUI.hpp"
2 #include "GUI_App.hpp"
3 #include "I18N.hpp"
4 #include "Field.hpp"
5 #include "wxExtensions.hpp"
6 #include "Plater.hpp"
7 #include "MainFrame.hpp"
8 #include "format.hpp"
9
10 #include "libslic3r/PrintConfig.hpp"
11
12 #include <regex>
13 #include <wx/numformatter.h>
14 #include <wx/tooltip.h>
15 #include <wx/notebook.h>
16 #include <wx/tokenzr.h>
17 #include <boost/algorithm/string/predicate.hpp>
18 #include "OG_CustomCtrl.hpp"
19
20 #ifdef __WXOSX__
21 #define wxOSX true
22 #else
23 #define wxOSX false
24 #endif
25
26 namespace Slic3r { namespace GUI {
27
double_to_string(double const value,const int max_precision)28 wxString double_to_string(double const value, const int max_precision /*= 4*/)
29 {
30 // Style_NoTrailingZeroes does not work on OSX. It also does not work correctly with some locales on Windows.
31 // return wxNumberFormatter::ToString(value, max_precision, wxNumberFormatter::Style_NoTrailingZeroes);
32
33 wxString s = wxNumberFormatter::ToString(value, max_precision, wxNumberFormatter::Style_None);
34
35 // The following code comes from wxNumberFormatter::RemoveTrailingZeroes(wxString& s)
36 // with the exception that here one sets the decimal separator explicitely to dot.
37 // If number is in scientific format, trailing zeroes belong to the exponent and cannot be removed.
38 if (s.find_first_of("eE") == wxString::npos) {
39 const size_t posDecSep = s.find(".");
40 // No decimal point => removing trailing zeroes irrelevant for integer number.
41 if (posDecSep != wxString::npos) {
42 // Find the last character to keep.
43 size_t posLastNonZero = s.find_last_not_of("0");
44 // If it's the decimal separator itself, don't keep it neither.
45 if (posLastNonZero == posDecSep)
46 -- posLastNonZero;
47 s.erase(posLastNonZero + 1);
48 // Remove sign from orphaned zero.
49 if (s.compare("-0") == 0)
50 s = "0";
51 }
52 }
53
54 return s;
55 }
56
get_thumbnails_string(const std::vector<Vec2d> & values)57 wxString get_thumbnails_string(const std::vector<Vec2d>& values)
58 {
59 wxString ret_str;
60 for (size_t i = 0; i < values.size(); ++ i) {
61 const Vec2d& el = values[i];
62 ret_str += wxString::Format((i == 0) ? "%ix%i" : ", %ix%i", int(el[0]), int(el[1]));
63 }
64 return ret_str;
65 }
66
67
~Field()68 Field::~Field()
69 {
70 if (m_on_kill_focus)
71 m_on_kill_focus = nullptr;
72 if (m_on_set_focus)
73 m_on_set_focus = nullptr;
74 if (m_on_change)
75 m_on_change = nullptr;
76 if (m_back_to_initial_value)
77 m_back_to_initial_value = nullptr;
78 if (m_back_to_sys_value)
79 m_back_to_sys_value = nullptr;
80 if (getWindow()) {
81 wxWindow* win = getWindow();
82 win->Destroy();
83 win = nullptr;
84 }
85 }
86
PostInitialize()87 void Field::PostInitialize()
88 {
89 auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
90
91 switch (m_opt.type)
92 {
93 case coPercents:
94 case coFloats:
95 case coStrings:
96 case coBools:
97 case coInts: {
98 auto tag_pos = m_opt_id.find("#");
99 if (tag_pos != std::string::npos)
100 m_opt_idx = stoi(m_opt_id.substr(tag_pos + 1, m_opt_id.size()));
101 break;
102 }
103 default:
104 break;
105 }
106
107 // initialize m_unit_value
108 m_em_unit = em_unit(m_parent);
109 parent_is_custom_ctrl = dynamic_cast<OG_CustomCtrl*>(m_parent) != nullptr;
110
111 BUILD();
112
113 // For the mode, when settings are in non-modal dialog, neither dialog nor tabpanel doesn't receive wxEVT_KEY_UP event, when some field is selected.
114 // So, like a workaround check wxEVT_KEY_UP event for the Filed and switch between tabs if Ctrl+(1-4) was pressed
115 if (getWindow())
116 getWindow()->Bind(wxEVT_KEY_UP, [](wxKeyEvent& evt) {
117 if ((evt.GetModifiers() & wxMOD_CONTROL) != 0) {
118 int tab_id = -1;
119 switch (evt.GetKeyCode()) {
120 case '1': { tab_id = 0; break; }
121 case '2': { tab_id = 1; break; }
122 case '3': { tab_id = 2; break; }
123 case '4': { tab_id = 3; break; }
124 #ifdef __APPLE__
125 case 'f':
126 #else /* __APPLE__ */
127 case WXK_CONTROL_F:
128 #endif /* __APPLE__ */
129 case 'F': { wxGetApp().plater()->search(false); break; }
130 default: break;
131 }
132 if (tab_id >= 0)
133 wxGetApp().mainframe->select_tab(tab_id);
134 if (tab_id > 0)
135 // tab panel should be focused for correct navigation between tabs
136 wxGetApp().tab_panel()->SetFocus();
137 }
138
139 evt.Skip();
140 });
141 }
142
143 // Values of width to alignments of fields
def_width()144 int Field::def_width() { return 8; }
def_width_wider()145 int Field::def_width_wider() { return 16; }
def_width_thinner()146 int Field::def_width_thinner() { return 4; }
147
on_kill_focus()148 void Field::on_kill_focus()
149 {
150 // call the registered function if it is available
151 if (m_on_kill_focus!=nullptr)
152 m_on_kill_focus(m_opt_id);
153 }
154
on_set_focus(wxEvent & event)155 void Field::on_set_focus(wxEvent& event)
156 {
157 // to allow the default behavior
158 event.Skip();
159 // call the registered function if it is available
160 if (m_on_set_focus!=nullptr)
161 m_on_set_focus(m_opt_id);
162 }
163
on_change_field()164 void Field::on_change_field()
165 {
166 // std::cerr << "calling Field::_on_change \n";
167 if (m_on_change != nullptr && !m_disable_change_event)
168 m_on_change(m_opt_id, get_value());
169 }
170
on_back_to_initial_value()171 void Field::on_back_to_initial_value()
172 {
173 if (m_back_to_initial_value != nullptr && m_is_modified_value)
174 m_back_to_initial_value(m_opt_id);
175 }
176
on_back_to_sys_value()177 void Field::on_back_to_sys_value()
178 {
179 if (m_back_to_sys_value != nullptr && m_is_nonsys_value)
180 m_back_to_sys_value(m_opt_id);
181 }
182
get_tooltip_text(const wxString & default_string)183 wxString Field::get_tooltip_text(const wxString& default_string)
184 {
185 wxString tooltip_text("");
186 wxString tooltip = _(m_opt.tooltip);
187 edit_tooltip(tooltip);
188
189 std::string opt_id = m_opt_id;
190 auto hash_pos = opt_id.find("#");
191 if (hash_pos != std::string::npos) {
192 opt_id.replace(hash_pos, 1,"[");
193 opt_id += "]";
194 }
195
196 if (tooltip.length() > 0)
197 tooltip_text = tooltip + "\n" + _(L("default value")) + "\t: " +
198 (boost::iends_with(opt_id, "_gcode") ? "\n" : "") + default_string +
199 (boost::iends_with(opt_id, "_gcode") ? "" : "\n") +
200 _(L("parameter name")) + "\t: " + opt_id;
201
202 return tooltip_text;
203 }
204
is_matched(const std::string & string,const std::string & pattern)205 bool Field::is_matched(const std::string& string, const std::string& pattern)
206 {
207 std::regex regex_pattern(pattern, std::regex_constants::icase); // use ::icase to make the matching case insensitive like /i in perl
208 return std::regex_match(string, regex_pattern);
209 }
210
na_value()211 static wxString na_value() { return _(L("N/A")); }
212
get_value_by_opt_type(wxString & str,const bool check_value)213 void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true*/)
214 {
215 switch (m_opt.type) {
216 case coInt:
217 m_value = wxAtoi(str);
218 break;
219 case coPercent:
220 case coPercents:
221 case coFloats:
222 case coFloat:{
223 if (m_opt.type == coPercent && !str.IsEmpty() && str.Last() == '%')
224 str.RemoveLast();
225 else if (!str.IsEmpty() && str.Last() == '%')
226 {
227 if (!check_value) {
228 m_value.clear();
229 break;
230 }
231
232 wxString label = m_opt.full_label.empty() ? _(m_opt.label) : _(m_opt.full_label);
233 show_error(m_parent, from_u8((boost::format(_utf8(L("%s doesn't support percentage"))) % label).str()));
234 set_value(double_to_string(m_opt.min), true);
235 m_value = double(m_opt.min);
236 break;
237 }
238 double val;
239 // Replace the first occurence of comma in decimal number.
240 str.Replace(",", ".", false);
241 if (str == ".")
242 val = 0.0;
243 else
244 {
245 if (m_opt.nullable && str == na_value())
246 val = ConfigOptionFloatsNullable::nil_value();
247 else if (!str.ToCDouble(&val))
248 {
249 if (!check_value) {
250 m_value.clear();
251 break;
252 }
253 show_error(m_parent, _(L("Invalid numeric input.")));
254 set_value(double_to_string(val), true);
255 }
256 if (m_opt.min > val || val > m_opt.max)
257 {
258 if (!check_value) {
259 m_value.clear();
260 break;
261 }
262 if (m_opt_id == "extrusion_multiplier") {
263 if (m_value.empty() || boost::any_cast<double>(m_value) != val) {
264 wxString msg_text = format_wxstr(_L("Input value is out of range\n"
265 "Are you sure that %s is a correct value and that you want to continue?"), str);
266 wxMessageDialog dialog(m_parent, msg_text, _L("Parameter validation") + ": " + m_opt_id, wxICON_WARNING | wxYES | wxNO);
267 if (dialog.ShowModal() == wxID_NO) {
268 if (m_value.empty()) {
269 if (m_opt.min > val) val = m_opt.min;
270 if (val > m_opt.max) val = m_opt.max;
271 }
272 else
273 val = boost::any_cast<double>(m_value);
274 set_value(double_to_string(val), true);
275 }
276 }
277 }
278 else {
279 show_error(m_parent, _L("Input value is out of range"));
280 if (m_opt.min > val) val = m_opt.min;
281 if (val > m_opt.max) val = m_opt.max;
282 set_value(double_to_string(val), true);
283 }
284 }
285 }
286 m_value = val;
287 break; }
288 case coString:
289 case coStrings:
290 case coFloatOrPercent: {
291 if (m_opt.type == coFloatOrPercent && !str.IsEmpty() && str.Last() != '%')
292 {
293 double val = 0.;
294 // Replace the first occurence of comma in decimal number.
295 str.Replace(",", ".", false);
296
297 // remove space and "mm" substring, if any exists
298 str.Replace(" ", "", true);
299 str.Replace("m", "", true);
300
301 if (!str.ToCDouble(&val))
302 {
303 if (!check_value) {
304 m_value.clear();
305 break;
306 }
307 show_error(m_parent, _(L("Invalid numeric input.")));
308 set_value(double_to_string(val), true);
309 }
310 else if (((m_opt.sidetext.rfind("mm/s") != std::string::npos && val > m_opt.max) ||
311 (m_opt.sidetext.rfind("mm ") != std::string::npos && val > 1)) &&
312 (m_value.empty() || std::string(str.ToUTF8().data()) != boost::any_cast<std::string>(m_value)))
313 {
314 if (!check_value) {
315 m_value.clear();
316 break;
317 }
318
319 bool infill_anchors = m_opt.opt_key == "infill_anchor" || m_opt.opt_key == "infill_anchor_max";
320
321 const std::string sidetext = m_opt.sidetext.rfind("mm/s") != std::string::npos ? "mm/s" : "mm";
322 const wxString stVal = double_to_string(val, 2);
323 const wxString msg_text = from_u8((boost::format(_utf8(L("Do you mean %s%% instead of %s %s?\n"
324 "Select YES if you want to change this value to %s%%, \n"
325 "or NO if you are sure that %s %s is a correct value."))) % stVal % stVal % sidetext % stVal % stVal % sidetext).str());
326 wxMessageDialog dialog(m_parent, msg_text, _(L("Parameter validation")) + ": " + m_opt_id , wxICON_WARNING | wxYES | wxNO);
327 if ((!infill_anchors || val > 100) && dialog.ShowModal() == wxID_YES) {
328 set_value(from_u8((boost::format("%s%%") % stVal).str()), false/*true*/);
329 str += "%%";
330 }
331 else
332 set_value(stVal, false); // it's no needed but can be helpful, when inputted value contained "," instead of "."
333 }
334 }
335
336 m_value = std::string(str.ToUTF8().data());
337 break; }
338
339 case coPoints: {
340 std::vector<Vec2d> out_values;
341 str.Replace(" ", wxEmptyString, true);
342 if (!str.IsEmpty()) {
343 bool invalid_val = false;
344 bool out_of_range_val = false;
345 wxStringTokenizer thumbnails(str, ",");
346 while (thumbnails.HasMoreTokens()) {
347 wxString token = thumbnails.GetNextToken();
348 double x, y;
349 wxStringTokenizer thumbnail(token, "x");
350 if (thumbnail.HasMoreTokens()) {
351 wxString x_str = thumbnail.GetNextToken();
352 if (x_str.ToDouble(&x) && thumbnail.HasMoreTokens()) {
353 wxString y_str = thumbnail.GetNextToken();
354 if (y_str.ToDouble(&y) && !thumbnail.HasMoreTokens()) {
355 if (0 < x && x < 1000 && 0 < y && y < 1000) {
356 out_values.push_back(Vec2d(x, y));
357 continue;
358 }
359 out_of_range_val = true;
360 break;
361 }
362 }
363 }
364 invalid_val = true;
365 break;
366 }
367
368 if (out_of_range_val) {
369 wxString text_value;
370 if (!m_value.empty())
371 text_value = get_thumbnails_string(boost::any_cast<std::vector<Vec2d>>(m_value));
372 set_value(text_value, true);
373 show_error(m_parent, _L("Input value is out of range")
374 );
375 }
376 else if (invalid_val) {
377 wxString text_value;
378 if (!m_value.empty())
379 text_value = get_thumbnails_string(boost::any_cast<std::vector<Vec2d>>(m_value));
380 set_value(text_value, true);
381 show_error(m_parent, format_wxstr(_L("Invalid input format. Expected vector of dimensions in the following format: \"%1%\""),"XxY, XxY, ..." ));
382 }
383 }
384
385 m_value = out_values;
386 break; }
387
388 default:
389 break;
390 }
391 }
392
msw_rescale()393 void Field::msw_rescale()
394 {
395 // update em_unit value
396 m_em_unit = em_unit(m_parent);
397 }
398
sys_color_changed()399 void Field::sys_color_changed()
400 {
401 }
402
403 template<class T>
is_defined_input_value(wxWindow * win,const ConfigOptionType & type)404 bool is_defined_input_value(wxWindow* win, const ConfigOptionType& type)
405 {
406 if (!win || (static_cast<T*>(win)->GetValue().empty() && type != coString && type != coStrings))
407 return false;
408 return true;
409 }
410
BUILD()411 void TextCtrl::BUILD() {
412 auto size = wxSize(def_width()*m_em_unit, wxDefaultCoord);
413 if (m_opt.height >= 0) size.SetHeight(m_opt.height*m_em_unit);
414 if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
415
416 wxString text_value = wxString("");
417
418 switch (m_opt.type) {
419 case coFloatOrPercent:
420 {
421 text_value = double_to_string(m_opt.default_value->getFloat());
422 if (m_opt.get_default_value<ConfigOptionFloatOrPercent>()->percent)
423 text_value += "%";
424 break;
425 }
426 case coPercent:
427 {
428 text_value = wxString::Format(_T("%i"), int(m_opt.default_value->getFloat()));
429 text_value += "%";
430 break;
431 }
432 case coPercents:
433 case coFloats:
434 case coFloat:
435 {
436 double val = m_opt.type == coFloats ?
437 m_opt.get_default_value<ConfigOptionFloats>()->get_at(m_opt_idx) :
438 m_opt.type == coFloat ?
439 m_opt.default_value->getFloat() :
440 m_opt.get_default_value<ConfigOptionPercents>()->get_at(m_opt_idx);
441 text_value = double_to_string(val);
442 m_last_meaningful_value = text_value;
443 break;
444 }
445 case coString:
446 text_value = m_opt.get_default_value<ConfigOptionString>()->value;
447 break;
448 case coStrings:
449 {
450 const ConfigOptionStrings *vec = m_opt.get_default_value<ConfigOptionStrings>();
451 if (vec == nullptr || vec->empty()) break; //for the case of empty default value
452 text_value = vec->get_at(m_opt_idx);
453 break;
454 }
455 case coPoints:
456 text_value = get_thumbnails_string(m_opt.get_default_value<ConfigOptionPoints>()->values);
457 break;
458 default:
459 break;
460 }
461
462 const long style = m_opt.multiline ? wxTE_MULTILINE : wxTE_PROCESS_ENTER/*0*/;
463 auto temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, style);
464 if (parent_is_custom_ctrl && m_opt.height < 0)
465 opt_height = (double)temp->GetSize().GetHeight()/m_em_unit;
466 temp->SetFont(m_opt.is_code ?
467 Slic3r::GUI::wxGetApp().code_font():
468 Slic3r::GUI::wxGetApp().normal_font());
469
470 if (! m_opt.multiline && !wxOSX)
471 // Only disable background refresh for single line input fields, as they are completely painted over by the edit control.
472 // This does not apply to the multi-line edit field, where the last line and a narrow frame around the text is not cleared.
473 temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
474 #ifdef __WXOSX__
475 temp->OSXDisableAllSmartSubstitutions();
476 #endif // __WXOSX__
477
478 temp->SetToolTip(get_tooltip_text(text_value));
479
480 if (style == wxTE_PROCESS_ENTER) {
481 temp->Bind(wxEVT_TEXT_ENTER, ([this, temp](wxEvent& e)
482 {
483 #if !defined(__WXGTK__)
484 e.Skip();
485 temp->GetToolTip()->Enable(true);
486 #endif // __WXGTK__
487 bEnterPressed = true;
488 propagate_value();
489 }), temp->GetId());
490 }
491
492 temp->Bind(wxEVT_SET_FOCUS, ([this](wxEvent& e) { on_set_focus(e); }), temp->GetId());
493
494 temp->Bind(wxEVT_LEFT_DOWN, ([temp](wxEvent& event)
495 {
496 //! to allow the default handling
497 event.Skip();
498 //! eliminating the g-code pop up text description
499 bool flag = false;
500 #ifdef __WXGTK__
501 // I have no idea why, but on GTK flag works in other way
502 flag = true;
503 #endif // __WXGTK__
504 temp->GetToolTip()->Enable(flag);
505 }), temp->GetId());
506
507 temp->Bind(wxEVT_KILL_FOCUS, ([this, temp](wxEvent& e)
508 {
509 e.Skip();
510 #ifdef __WXOSX__
511 // OSX issue: For some unknown reason wxEVT_KILL_FOCUS is emitted twice in a row in some cases
512 // (like when information dialog is shown during an update of the option value)
513 // Thus, suppress its second call
514 if (bKilledFocus)
515 return;
516 bKilledFocus = true;
517 #endif // __WXOSX__
518
519 #if !defined(__WXGTK__)
520 temp->GetToolTip()->Enable(true);
521 #endif // __WXGTK__
522 if (bEnterPressed)
523 bEnterPressed = false;
524 else
525 propagate_value();
526 #ifdef __WXOSX__
527 // After processing of KILL_FOCUS event we should to invalidate a bKilledFocus flag
528 bKilledFocus = false;
529 #endif // __WXOSX__
530 }), temp->GetId());
531 /*
532 // select all text using Ctrl+A
533 temp->Bind(wxEVT_CHAR, ([temp](wxKeyEvent& event)
534 {
535 if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL))
536 temp->SetSelection(-1, -1); //select all
537 event.Skip();
538 }));
539 */
540 // recast as a wxWindow to fit the calling convention
541 window = dynamic_cast<wxWindow*>(temp);
542 }
543
value_was_changed()544 bool TextCtrl::value_was_changed()
545 {
546 if (m_value.empty())
547 return true;
548
549 boost::any val = m_value;
550 wxString ret_str = static_cast<wxTextCtrl*>(window)->GetValue();
551 // update m_value!
552 // ret_str might be changed inside get_value_by_opt_type
553 get_value_by_opt_type(ret_str);
554
555 switch (m_opt.type) {
556 case coInt:
557 return boost::any_cast<int>(m_value) != boost::any_cast<int>(val);
558 case coPercent:
559 case coPercents:
560 case coFloats:
561 case coFloat: {
562 if (m_opt.nullable && std::isnan(boost::any_cast<double>(m_value)) &&
563 std::isnan(boost::any_cast<double>(val)))
564 return false;
565 return boost::any_cast<double>(m_value) != boost::any_cast<double>(val);
566 }
567 case coString:
568 case coStrings:
569 case coFloatOrPercent:
570 return boost::any_cast<std::string>(m_value) != boost::any_cast<std::string>(val);
571 default:
572 return true;
573 }
574 }
575
propagate_value()576 void TextCtrl::propagate_value()
577 {
578 if (!is_defined_input_value<wxTextCtrl>(window, m_opt.type) )
579 // on_kill_focus() cause a call of OptionsGroup::reload_config(),
580 // Thus, do it only when it's really needed (when undefined value was input)
581 on_kill_focus();
582 else if (value_was_changed())
583 on_change_field();
584 }
585
set_value(const boost::any & value,bool change_event)586 void TextCtrl::set_value(const boost::any& value, bool change_event/* = false*/) {
587 m_disable_change_event = !change_event;
588 if (m_opt.nullable) {
589 const bool m_is_na_val = boost::any_cast<wxString>(value) == na_value();
590 if (!m_is_na_val)
591 m_last_meaningful_value = value;
592 dynamic_cast<wxTextCtrl*>(window)->SetValue(m_is_na_val ? na_value() : boost::any_cast<wxString>(value));
593 }
594 else
595 dynamic_cast<wxTextCtrl*>(window)->SetValue(boost::any_cast<wxString>(value));
596 m_disable_change_event = false;
597
598 if (!change_event) {
599 wxString ret_str = static_cast<wxTextCtrl*>(window)->GetValue();
600 /* Update m_value to correct work of next value_was_changed().
601 * But after checking of entered value, don't fix the "incorrect" value and don't show a warning message,
602 * just clear m_value in this case.
603 */
604 get_value_by_opt_type(ret_str, false);
605 }
606 }
607
set_last_meaningful_value()608 void TextCtrl::set_last_meaningful_value()
609 {
610 dynamic_cast<wxTextCtrl*>(window)->SetValue(boost::any_cast<wxString>(m_last_meaningful_value));
611 propagate_value();
612 }
613
set_na_value()614 void TextCtrl::set_na_value()
615 {
616 dynamic_cast<wxTextCtrl*>(window)->SetValue(na_value());
617 propagate_value();
618 }
619
get_value()620 boost::any& TextCtrl::get_value()
621 {
622 wxString ret_str = static_cast<wxTextCtrl*>(window)->GetValue();
623 // update m_value
624 get_value_by_opt_type(ret_str);
625
626 return m_value;
627 }
628
msw_rescale()629 void TextCtrl::msw_rescale()
630 {
631 Field::msw_rescale();
632 auto size = wxSize(def_width() * m_em_unit, wxDefaultCoord);
633
634 if (m_opt.height >= 0)
635 size.SetHeight(m_opt.height*m_em_unit);
636 else if (parent_is_custom_ctrl && opt_height > 0)
637 size.SetHeight(lround(opt_height*m_em_unit));
638 if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
639
640 if (size != wxDefaultSize)
641 {
642 wxTextCtrl* field = dynamic_cast<wxTextCtrl*>(window);
643 if (parent_is_custom_ctrl)
644 field->SetSize(size);
645 else
646 field->SetMinSize(size);
647 }
648
649 }
650
enable()651 void TextCtrl::enable() { dynamic_cast<wxTextCtrl*>(window)->Enable(); dynamic_cast<wxTextCtrl*>(window)->SetEditable(true); }
disable()652 void TextCtrl::disable() { dynamic_cast<wxTextCtrl*>(window)->Disable(); dynamic_cast<wxTextCtrl*>(window)->SetEditable(false); }
653
654 #ifdef __WXGTK__
change_field_value(wxEvent & event)655 void TextCtrl::change_field_value(wxEvent& event)
656 {
657 if ((bChangedValueEvent = (event.GetEventType()==wxEVT_KEY_UP)))
658 on_change_field();
659 event.Skip();
660 };
661 #endif //__WXGTK__
662
BUILD()663 void CheckBox::BUILD() {
664 auto size = wxSize(wxDefaultSize);
665 if (m_opt.height >= 0) size.SetHeight(m_opt.height*m_em_unit);
666 if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
667
668 bool check_value = m_opt.type == coBool ?
669 m_opt.default_value->getBool() : m_opt.type == coBools ?
670 m_opt.get_default_value<ConfigOptionBools>()->get_at(m_opt_idx) :
671 false;
672
673 m_last_meaningful_value = static_cast<unsigned char>(check_value);
674
675 // Set Label as a string of at least one space simbol to correct system scaling of a CheckBox
676 auto temp = new wxCheckBox(m_parent, wxID_ANY, wxString(" "), wxDefaultPosition, size);
677 temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
678 if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
679 temp->SetValue(check_value);
680 if (m_opt.readonly) temp->Disable();
681
682 temp->Bind(wxEVT_CHECKBOX, ([this](wxCommandEvent e) {
683 m_is_na_val = false;
684 on_change_field();
685 }), temp->GetId());
686
687 temp->SetToolTip(get_tooltip_text(check_value ? "true" : "false"));
688
689 // recast as a wxWindow to fit the calling convention
690 window = dynamic_cast<wxWindow*>(temp);
691 }
692
set_value(const boost::any & value,bool change_event)693 void CheckBox::set_value(const boost::any& value, bool change_event)
694 {
695 m_disable_change_event = !change_event;
696 if (m_opt.nullable) {
697 m_is_na_val = boost::any_cast<unsigned char>(value) == ConfigOptionBoolsNullable::nil_value();
698 if (!m_is_na_val)
699 m_last_meaningful_value = value;
700 dynamic_cast<wxCheckBox*>(window)->SetValue(m_is_na_val ? false : boost::any_cast<unsigned char>(value) != 0);
701 }
702 else
703 dynamic_cast<wxCheckBox*>(window)->SetValue(boost::any_cast<bool>(value));
704 m_disable_change_event = false;
705 }
706
set_last_meaningful_value()707 void CheckBox::set_last_meaningful_value()
708 {
709 if (m_opt.nullable) {
710 m_is_na_val = false;
711 dynamic_cast<wxCheckBox*>(window)->SetValue(boost::any_cast<unsigned char>(m_last_meaningful_value) != 0);
712 on_change_field();
713 }
714 }
715
set_na_value()716 void CheckBox::set_na_value()
717 {
718 if (m_opt.nullable) {
719 m_is_na_val = true;
720 dynamic_cast<wxCheckBox*>(window)->SetValue(false);
721 on_change_field();
722 }
723 }
724
get_value()725 boost::any& CheckBox::get_value()
726 {
727 // boost::any m_value;
728 bool value = dynamic_cast<wxCheckBox*>(window)->GetValue();
729 if (m_opt.type == coBool)
730 m_value = static_cast<bool>(value);
731 else
732 m_value = m_is_na_val ? ConfigOptionBoolsNullable::nil_value() : static_cast<unsigned char>(value);
733 return m_value;
734 }
735
msw_rescale()736 void CheckBox::msw_rescale()
737 {
738 Field::msw_rescale();
739
740 wxCheckBox* field = dynamic_cast<wxCheckBox*>(window);
741 field->SetMinSize(wxSize(-1, int(1.5f*field->GetFont().GetPixelSize().y +0.5f)));
742 }
743
744
BUILD()745 void SpinCtrl::BUILD() {
746 auto size = wxSize(def_width() * m_em_unit, wxDefaultCoord);
747 if (m_opt.height >= 0) size.SetHeight(m_opt.height*m_em_unit);
748 if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
749
750 wxString text_value = wxString("");
751 int default_value = 0;
752
753 switch (m_opt.type) {
754 case coInt:
755 default_value = m_opt.default_value->getInt();
756 text_value = wxString::Format(_T("%i"), default_value);
757 break;
758 case coInts:
759 {
760 const ConfigOptionInts *vec = m_opt.get_default_value<ConfigOptionInts>();
761 if (vec == nullptr || vec->empty()) break;
762 for (size_t id = 0; id < vec->size(); ++id)
763 {
764 default_value = vec->get_at(id);
765 text_value += wxString::Format(_T("%i"), default_value);
766 }
767 break;
768 }
769 default:
770 break;
771 }
772
773 const int min_val = m_opt.min == INT_MIN
774 #ifdef __WXOSX__
775 // We will forcibly set the input value for SpinControl, since the value
776 // inserted from the keyboard is not updated under OSX.
777 // So, we can't set min control value bigger then 0.
778 // Otherwise, it couldn't be possible to input from keyboard value
779 // less then min_val.
780 || m_opt.min > 0
781 #endif
782 ? 0 : m_opt.min;
783 const int max_val = m_opt.max < 2147483647 ? m_opt.max : 2147483647;
784
785 auto temp = new wxSpinCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size,
786 0|wxTE_PROCESS_ENTER, min_val, max_val, default_value);
787 #ifdef __WXGTK3__
788 wxSize best_sz = temp->GetBestSize();
789 if (best_sz.x > size.x)
790 temp->SetSize(wxSize(size.x + 2 * best_sz.y, best_sz.y));
791 #endif //__WXGTK3__
792 temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
793 if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
794
795 if (m_opt.height < 0 && parent_is_custom_ctrl)
796 opt_height = (double)temp->GetSize().GetHeight() / m_em_unit;
797
798 // XXX: On OS X the wxSpinCtrl widget is made up of two subwidgets, unfortunatelly
799 // the kill focus event is not propagated to the encompassing widget,
800 // so we need to bind it on the inner text widget instead. (Ugh.)
801 #ifdef __WXOSX__
802 temp->GetText()->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e)
803 #else
804 temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e)
805 #endif
806 {
807 e.Skip();
808 if (bEnterPressed) {
809 bEnterPressed = false;
810 return;
811 }
812
813 propagate_value();
814 }));
815
816 temp->Bind(wxEVT_SPINCTRL, ([this](wxCommandEvent e) { propagate_value(); }), temp->GetId());
817
818 temp->Bind(wxEVT_TEXT_ENTER, ([this](wxCommandEvent e)
819 {
820 e.Skip();
821 propagate_value();
822 bEnterPressed = true;
823 }), temp->GetId());
824
825 temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e)
826 {
827 // # On OSX / Cocoa, wxSpinCtrl::GetValue() doesn't return the new value
828 // # when it was changed from the text control, so the on_change callback
829 // # gets the old one, and on_kill_focus resets the control to the old value.
830 // # As a workaround, we get the new value from $event->GetString and store
831 // # here temporarily so that we can return it from get_value()
832
833 long value;
834 const bool parsed = e.GetString().ToLong(&value);
835 tmp_value = parsed && value >= INT_MIN && value <= INT_MAX ? (int)value : UNDEF_VALUE;
836
837 #ifdef __WXOSX__
838 // Forcibly set the input value for SpinControl, since the value
839 // inserted from the keyboard or clipboard is not updated under OSX
840 if (tmp_value != UNDEF_VALUE) {
841 wxSpinCtrl* spin = static_cast<wxSpinCtrl*>(window);
842 spin->SetValue(tmp_value);
843
844 // But in SetValue() is executed m_text_ctrl->SelectAll(), so
845 // discard this selection and set insertion point to the end of string
846 spin->GetText()->SetInsertionPointEnd();
847 }
848 #endif
849 }), temp->GetId());
850
851 temp->SetToolTip(get_tooltip_text(text_value));
852
853 // recast as a wxWindow to fit the calling convention
854 window = dynamic_cast<wxWindow*>(temp);
855 }
856
propagate_value()857 void SpinCtrl::propagate_value()
858 {
859 if (suppress_propagation)
860 return;
861
862 suppress_propagation = true;
863 if (tmp_value == UNDEF_VALUE) {
864 on_kill_focus();
865 } else {
866 #ifdef __WXOSX__
867 // check input value for minimum
868 if (m_opt.min > 0 && tmp_value < m_opt.min) {
869 wxSpinCtrl* spin = static_cast<wxSpinCtrl*>(window);
870 spin->SetValue(m_opt.min);
871 spin->GetText()->SetInsertionPointEnd();
872 }
873 #endif
874 on_change_field();
875 }
876 suppress_propagation = false;
877 }
878
msw_rescale()879 void SpinCtrl::msw_rescale()
880 {
881 Field::msw_rescale();
882
883 wxSpinCtrl* field = dynamic_cast<wxSpinCtrl*>(window);
884 if (parent_is_custom_ctrl)
885 field->SetSize(wxSize(def_width() * m_em_unit, lround(opt_height * m_em_unit)));
886 else
887 field->SetMinSize(wxSize(def_width() * m_em_unit, int(1.9f*field->GetFont().GetPixelSize().y)));
888 }
889
890 #ifdef __WXOSX__
891 using choice_ctrl = wxBitmapComboBox;
892 #else
893 using choice_ctrl = wxComboBox;
894 #endif // __WXOSX__
895
BUILD()896 void Choice::BUILD() {
897 wxSize size(def_width_wider() * m_em_unit, wxDefaultCoord);
898 if (m_opt.height >= 0) size.SetHeight(m_opt.height*m_em_unit);
899 if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
900
901 choice_ctrl* temp;
902 if (!m_opt.gui_type.empty() && m_opt.gui_type.compare("select_open") != 0) {
903 m_is_editable = true;
904 temp = new choice_ctrl(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size);
905 }
906 else {
907 #ifdef __WXOSX__
908 /* wxBitmapComboBox with wxCB_READONLY style return NULL for GetTextCtrl(),
909 * so ToolTip doesn't shown.
910 * Next workaround helps to solve this problem
911 */
912 temp = new choice_ctrl();
913 temp->SetTextCtrlStyle(wxTE_READONLY);
914 temp->Create(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, nullptr);
915 #else
916 temp = new choice_ctrl(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, nullptr, wxCB_READONLY);
917 #endif //__WXOSX__
918 }
919
920 #ifdef __WXGTK3__
921 wxSize best_sz = temp->GetBestSize();
922 if (best_sz.x > size.x)
923 temp->SetSize(best_sz);
924 #endif //__WXGTK3__
925
926 temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
927 if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
928
929 // recast as a wxWindow to fit the calling convention
930 window = dynamic_cast<wxWindow*>(temp);
931
932 if (! m_opt.enum_labels.empty() || ! m_opt.enum_values.empty()) {
933 if (m_opt.enum_labels.empty()) {
934 // Append non-localized enum_values
935 for (auto el : m_opt.enum_values)
936 temp->Append(el);
937 } else {
938 // Append localized enum_labels
939 for (auto el : m_opt.enum_labels)
940 temp->Append(_(el));
941 }
942 set_selection();
943 }
944
945 #ifdef __WXOSX__
946 //#ifndef __WXGTK__
947 /* Workaround for a correct rendering of the control without Bitmap (under MSW and OSX):
948 *
949 * 1. We should create small Bitmap to fill Bitmaps RefData,
950 * ! in this case wxBitmap.IsOK() return true.
951 * 2. But then set width to 0 value for no using of bitmap left and right spacing
952 * 3. Set this empty bitmap to the at list one item and BitmapCombobox will be recreated correct
953 *
954 * Note: Set bitmap height to the Font size because of OSX rendering.
955 */
956 wxBitmap empty_bmp(1, temp->GetFont().GetPixelSize().y + 2);
957 empty_bmp.SetWidth(0);
958 temp->SetItemBitmap(0, empty_bmp);
959 #endif
960
961 temp->Bind(wxEVT_MOUSEWHEEL, [this](wxMouseEvent& e) {
962 if (m_suppress_scroll && !m_is_dropped)
963 e.StopPropagation();
964 else
965 e.Skip();
966 });
967 temp->Bind(wxEVT_COMBOBOX_DROPDOWN, [this](wxCommandEvent&) { m_is_dropped = true; });
968 temp->Bind(wxEVT_COMBOBOX_CLOSEUP, [this](wxCommandEvent&) { m_is_dropped = false; });
969
970 temp->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent&) { on_change_field(); }, temp->GetId());
971
972 if (m_is_editable) {
973 temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) {
974 e.Skip();
975 if (m_opt.type == coStrings) {
976 on_change_field();
977 return;
978 }
979
980 if (is_defined_input_value<choice_ctrl>(window, m_opt.type)) {
981 if (m_opt.type == coFloatOrPercent) {
982 std::string old_val = !m_value.empty() ? boost::any_cast<std::string>(m_value) : "";
983 if (old_val == boost::any_cast<std::string>(get_value()))
984 return;
985 }
986 else {
987 double old_val = !m_value.empty() ? boost::any_cast<double>(m_value) : -99999;
988 if (fabs(old_val - boost::any_cast<double>(get_value())) <= 0.0001)
989 return;
990 }
991 on_change_field();
992 }
993 else
994 on_kill_focus();
995 }), temp->GetId());
996 }
997
998 temp->SetToolTip(get_tooltip_text(temp->GetValue()));
999 }
1000
suppress_scroll()1001 void Choice::suppress_scroll()
1002 {
1003 m_suppress_scroll = true;
1004 }
1005
set_selection()1006 void Choice::set_selection()
1007 {
1008 /* To prevent earlier control updating under OSX set m_disable_change_event to true
1009 * (under OSX wxBitmapComboBox send wxEVT_COMBOBOX even after SetSelection())
1010 */
1011 m_disable_change_event = true;
1012
1013 wxString text_value = wxString("");
1014
1015 choice_ctrl* field = dynamic_cast<choice_ctrl*>(window);
1016 switch (m_opt.type) {
1017 case coEnum:{
1018 int id_value = m_opt.get_default_value<ConfigOptionEnum<SeamPosition>>()->value; //!!
1019 field->SetSelection(id_value);
1020 break;
1021 }
1022 case coFloat:
1023 case coPercent: {
1024 double val = m_opt.default_value->getFloat();
1025 text_value = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 1);
1026 break;
1027 }
1028 case coInt:{
1029 text_value = wxString::Format(_T("%i"), int(m_opt.default_value->getInt()));
1030 break;
1031 }
1032 case coStrings:{
1033 text_value = m_opt.get_default_value<ConfigOptionStrings>()->get_at(m_opt_idx);
1034 break;
1035 }
1036 case coFloatOrPercent: {
1037 text_value = double_to_string(m_opt.default_value->getFloat());
1038 if (m_opt.get_default_value<ConfigOptionFloatOrPercent>()->percent)
1039 text_value += "%";
1040 break;
1041 }
1042 default: break;
1043 }
1044
1045 if (!text_value.IsEmpty()) {
1046 int idx = 0;
1047 for (auto el : m_opt.enum_values) {
1048 if (el == text_value)
1049 break;
1050 ++idx;
1051 }
1052 idx == m_opt.enum_values.size() ? field->SetValue(text_value) : field->SetSelection(idx);
1053 }
1054 }
1055
set_value(const std::string & value,bool change_event)1056 void Choice::set_value(const std::string& value, bool change_event) //! Redundant?
1057 {
1058 m_disable_change_event = !change_event;
1059
1060 size_t idx=0;
1061 for (auto el : m_opt.enum_values)
1062 {
1063 if (el == value)
1064 break;
1065 ++idx;
1066 }
1067
1068 choice_ctrl* field = dynamic_cast<choice_ctrl*>(window);
1069 idx == m_opt.enum_values.size() ?
1070 field->SetValue(value) :
1071 field->SetSelection(idx);
1072
1073 m_disable_change_event = false;
1074 }
1075
set_value(const boost::any & value,bool change_event)1076 void Choice::set_value(const boost::any& value, bool change_event)
1077 {
1078 m_disable_change_event = !change_event;
1079
1080 choice_ctrl* field = dynamic_cast<choice_ctrl*>(window);
1081
1082 switch (m_opt.type) {
1083 case coInt:
1084 case coFloat:
1085 case coPercent:
1086 case coFloatOrPercent:
1087 case coString:
1088 case coStrings: {
1089 wxString text_value;
1090 if (m_opt.type == coInt)
1091 text_value = wxString::Format(_T("%i"), int(boost::any_cast<int>(value)));
1092 else
1093 text_value = boost::any_cast<wxString>(value);
1094 size_t idx = 0;
1095 const std::vector<std::string>& enums = m_opt.enum_values.empty() ? m_opt.enum_labels : m_opt.enum_values;
1096 for (auto el : enums)
1097 {
1098 if (el == text_value)
1099 break;
1100 ++idx;
1101 }
1102 if (idx == enums.size()) {
1103 // For editable Combobox under OSX is needed to set selection to -1 explicitly,
1104 // otherwise selection doesn't be changed
1105 field->SetSelection(-1);
1106 field->SetValue(text_value);
1107 }
1108 else
1109 field->SetSelection(idx);
1110 break;
1111 }
1112 case coEnum: {
1113 int val = boost::any_cast<int>(value);
1114 if (m_opt_id.compare("host_type") == 0 && val != 0 &&
1115 m_opt.enum_values.size() > field->GetCount()) // for case, when PrusaLink isn't used as a HostType
1116 val--;
1117
1118 if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern")
1119 {
1120 if (!m_opt.enum_values.empty()) {
1121 std::string key;
1122 t_config_enum_values map_names = ConfigOptionEnum<InfillPattern>::get_enum_values();
1123 for (auto it : map_names) {
1124 if (val == it.second) {
1125 key = it.first;
1126 break;
1127 }
1128 }
1129
1130 size_t idx = 0;
1131 for (auto el : m_opt.enum_values)
1132 {
1133 if (el == key)
1134 break;
1135 ++idx;
1136 }
1137
1138 val = idx == m_opt.enum_values.size() ? 0 : idx;
1139 }
1140 else
1141 val = 0;
1142 }
1143 field->SetSelection(val);
1144 break;
1145 }
1146 default:
1147 break;
1148 }
1149
1150 m_disable_change_event = false;
1151 }
1152
1153 //! it's needed for _update_serial_ports()
set_values(const std::vector<std::string> & values)1154 void Choice::set_values(const std::vector<std::string>& values)
1155 {
1156 if (values.empty())
1157 return;
1158 m_disable_change_event = true;
1159
1160 // # it looks that Clear() also clears the text field in recent wxWidgets versions,
1161 // # but we want to preserve it
1162 auto ww = dynamic_cast<choice_ctrl*>(window);
1163 auto value = ww->GetValue();
1164 ww->Clear();
1165 ww->Append("");
1166 for (const auto &el : values)
1167 ww->Append(wxString(el));
1168 ww->SetValue(value);
1169
1170 m_disable_change_event = false;
1171 }
1172
set_values(const wxArrayString & values)1173 void Choice::set_values(const wxArrayString &values)
1174 {
1175 if (values.empty())
1176 return;
1177
1178 m_disable_change_event = true;
1179
1180 // # it looks that Clear() also clears the text field in recent wxWidgets versions,
1181 // # but we want to preserve it
1182 auto ww = dynamic_cast<choice_ctrl*>(window);
1183 auto value = ww->GetValue();
1184 ww->Clear();
1185 // ww->Append("");
1186 for (const auto &el : values)
1187 ww->Append(el);
1188 ww->SetValue(value);
1189
1190 m_disable_change_event = false;
1191 }
1192
get_value()1193 boost::any& Choice::get_value()
1194 {
1195 choice_ctrl* field = dynamic_cast<choice_ctrl*>(window);
1196
1197 wxString ret_str = field->GetValue();
1198
1199 // options from right panel
1200 std::vector <std::string> right_panel_options{ "support", "pad", "scale_unit" };
1201 for (auto rp_option: right_panel_options)
1202 if (m_opt_id == rp_option)
1203 return m_value = boost::any(ret_str);
1204
1205 if (m_opt.type == coEnum)
1206 {
1207 int ret_enum = field->GetSelection();
1208 if (m_opt_id.compare("host_type") == 0 &&
1209 m_opt.enum_values.size() > field->GetCount()) // for case, when PrusaLink isn't used as a HostType
1210 ret_enum++;
1211
1212 if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern")
1213 {
1214 if (!m_opt.enum_values.empty()) {
1215 std::string key = m_opt.enum_values[ret_enum];
1216 t_config_enum_values map_names = ConfigOptionEnum<InfillPattern>::get_enum_values();
1217 int value = map_names.at(key);
1218
1219 m_value = static_cast<InfillPattern>(value);
1220 }
1221 else
1222 m_value = static_cast<InfillPattern>(0);
1223 }
1224 else if (m_opt_id.compare("ironing_type") == 0)
1225 m_value = static_cast<IroningType>(ret_enum);
1226 else if (m_opt_id.compare("gcode_flavor") == 0)
1227 m_value = static_cast<GCodeFlavor>(ret_enum);
1228 else if (m_opt_id.compare("machine_limits_usage") == 0)
1229 m_value = static_cast<MachineLimitsUsage>(ret_enum);
1230 else if (m_opt_id.compare("support_material_pattern") == 0)
1231 m_value = static_cast<SupportMaterialPattern>(ret_enum);
1232 else if (m_opt_id.compare("seam_position") == 0)
1233 m_value = static_cast<SeamPosition>(ret_enum);
1234 else if (m_opt_id.compare("host_type") == 0)
1235 m_value = static_cast<PrintHostType>(ret_enum);
1236 else if (m_opt_id.compare("display_orientation") == 0)
1237 m_value = static_cast<SLADisplayOrientation>(ret_enum);
1238 else if (m_opt_id.compare("support_pillar_connection_mode") == 0)
1239 m_value = static_cast<SLAPillarConnectionMode>(ret_enum);
1240 else if (m_opt_id == "printhost_authorization_type")
1241 m_value = static_cast<AuthorizationType>(ret_enum);
1242 }
1243 else if (m_opt.gui_type == "f_enum_open") {
1244 const int ret_enum = field->GetSelection();
1245 if (ret_enum < 0 || m_opt.enum_values.empty() || m_opt.type == coStrings ||
1246 (ret_str != m_opt.enum_values[ret_enum] && ret_str != _(m_opt.enum_labels[ret_enum])))
1247 // modifies ret_string!
1248 get_value_by_opt_type(ret_str);
1249 else if (m_opt.type == coFloatOrPercent)
1250 m_value = m_opt.enum_values[ret_enum];
1251 else
1252 m_value = atof(m_opt.enum_values[ret_enum].c_str());
1253 }
1254 else
1255 // modifies ret_string!
1256 get_value_by_opt_type(ret_str);
1257
1258 return m_value;
1259 }
1260
enable()1261 void Choice::enable() { dynamic_cast<choice_ctrl*>(window)->Enable(); };
disable()1262 void Choice::disable() { dynamic_cast<choice_ctrl*>(window)->Disable(); };
1263
msw_rescale()1264 void Choice::msw_rescale()
1265 {
1266 Field::msw_rescale();
1267
1268 choice_ctrl* field = dynamic_cast<choice_ctrl*>(window);
1269 #ifdef __WXOSX__
1270 const wxString selection = field->GetValue();// field->GetString(index);
1271
1272 /* To correct scaling (set new controll size) of a wxBitmapCombobox
1273 * we need to refill control with new bitmaps. So, in our case :
1274 * 1. clear control
1275 * 2. add content
1276 * 3. add scaled "empty" bitmap to the at least one item
1277 */
1278 field->Clear();
1279 wxSize size(wxDefaultSize);
1280 size.SetWidth((m_opt.width > 0 ? m_opt.width : def_width_wider()) * m_em_unit);
1281
1282 // Set rescaled min height to correct layout
1283 field->SetMinSize(wxSize(-1, int(1.5f*field->GetFont().GetPixelSize().y + 0.5f)));
1284 // Set rescaled size
1285 field->SetSize(size);
1286
1287 size_t idx = 0;
1288 if (! m_opt.enum_labels.empty() || ! m_opt.enum_values.empty()) {
1289 size_t counter = 0;
1290 bool labels = ! m_opt.enum_labels.empty();
1291 for (const std::string &el : labels ? m_opt.enum_labels : m_opt.enum_values) {
1292 wxString text = labels ? _(el) : wxString::FromUTF8(el.c_str());
1293 field->Append(text);
1294 if (text == selection)
1295 idx = counter;
1296 ++ counter;
1297 }
1298 }
1299
1300 wxBitmap empty_bmp(1, field->GetFont().GetPixelSize().y + 2);
1301 empty_bmp.SetWidth(0);
1302 field->SetItemBitmap(0, empty_bmp);
1303
1304 idx == m_opt.enum_values.size() ?
1305 field->SetValue(selection) :
1306 field->SetSelection(idx);
1307 #else
1308 auto size = wxSize(def_width_wider() * m_em_unit, wxDefaultCoord);
1309 if (m_opt.height >= 0) size.SetHeight(m_opt.height * m_em_unit);
1310 if (m_opt.width >= 0) size.SetWidth(m_opt.width * m_em_unit);
1311
1312 if (parent_is_custom_ctrl)
1313 field->SetSize(size);
1314 else
1315 field->SetMinSize(size);
1316 #endif
1317 }
1318
BUILD()1319 void ColourPicker::BUILD()
1320 {
1321 auto size = wxSize(def_width() * m_em_unit, wxDefaultCoord);
1322 if (m_opt.height >= 0) size.SetHeight(m_opt.height*m_em_unit);
1323 if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
1324
1325 // Validate the color
1326 wxString clr_str(m_opt.get_default_value<ConfigOptionStrings>()->get_at(m_opt_idx));
1327 wxColour clr(clr_str);
1328 if (clr_str.IsEmpty() || !clr.IsOk()) {
1329 clr = wxTransparentColour;
1330 }
1331
1332 auto temp = new wxColourPickerCtrl(m_parent, wxID_ANY, clr, wxDefaultPosition, size);
1333 if (parent_is_custom_ctrl && m_opt.height < 0)
1334 opt_height = (double)temp->GetSize().GetHeight() / m_em_unit;
1335 temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
1336 if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
1337
1338 // // recast as a wxWindow to fit the calling convention
1339 window = dynamic_cast<wxWindow*>(temp);
1340
1341 temp->Bind(wxEVT_COLOURPICKER_CHANGED, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId());
1342
1343 temp->SetToolTip(get_tooltip_text(clr_str));
1344 }
1345
set_undef_value(wxColourPickerCtrl * field)1346 void ColourPicker::set_undef_value(wxColourPickerCtrl* field)
1347 {
1348 field->SetColour(wxTransparentColour);
1349
1350 wxButton* btn = dynamic_cast<wxButton*>(field->GetPickerCtrl());
1351 wxBitmap bmp = btn->GetBitmap();
1352 wxMemoryDC dc(bmp);
1353 if (!dc.IsOk()) return;
1354 dc.SetTextForeground(*wxWHITE);
1355 dc.SetFont(wxGetApp().normal_font());
1356
1357 const wxRect rect = wxRect(0, 0, bmp.GetWidth(), bmp.GetHeight());
1358 dc.DrawLabel("undef", rect, wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL);
1359
1360 dc.SelectObject(wxNullBitmap);
1361 btn->SetBitmapLabel(bmp);
1362 }
1363
set_value(const boost::any & value,bool change_event)1364 void ColourPicker::set_value(const boost::any& value, bool change_event)
1365 {
1366 m_disable_change_event = !change_event;
1367 const wxString clr_str(boost::any_cast<wxString>(value));
1368 auto field = dynamic_cast<wxColourPickerCtrl*>(window);
1369
1370 wxColour clr(clr_str);
1371 if (clr_str.IsEmpty() || !clr.IsOk())
1372 set_undef_value(field);
1373 else
1374 field->SetColour(clr);
1375
1376 m_disable_change_event = false;
1377 }
1378
get_value()1379 boost::any& ColourPicker::get_value()
1380 {
1381 auto colour = static_cast<wxColourPickerCtrl*>(window)->GetColour();
1382 if (colour == wxTransparentColour)
1383 m_value = std::string("");
1384 else {
1385 auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), colour.Red(), colour.Green(), colour.Blue());
1386 m_value = clr_str.ToStdString();
1387 }
1388 return m_value;
1389 }
1390
msw_rescale()1391 void ColourPicker::msw_rescale()
1392 {
1393 Field::msw_rescale();
1394
1395 wxColourPickerCtrl* field = dynamic_cast<wxColourPickerCtrl*>(window);
1396 auto size = wxSize(def_width() * m_em_unit, wxDefaultCoord);
1397 if (m_opt.height >= 0)
1398 size.SetHeight(m_opt.height * m_em_unit);
1399 else if (parent_is_custom_ctrl && opt_height > 0)
1400 size.SetHeight(lround(opt_height * m_em_unit));
1401 if (m_opt.width >= 0) size.SetWidth(m_opt.width * m_em_unit);
1402 if (parent_is_custom_ctrl)
1403 field->SetSize(size);
1404 else
1405 field->SetMinSize(size);
1406
1407 if (field->GetColour() == wxTransparentColour)
1408 set_undef_value(field);
1409 }
1410
BUILD()1411 void PointCtrl::BUILD()
1412 {
1413 auto temp = new wxBoxSizer(wxHORIZONTAL);
1414
1415 const wxSize field_size(4 * m_em_unit, -1);
1416
1417 auto default_pt = m_opt.get_default_value<ConfigOptionPoints>()->values.at(0);
1418 double val = default_pt(0);
1419 wxString X = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None);
1420 val = default_pt(1);
1421 wxString Y = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None);
1422
1423 x_textctrl = new wxTextCtrl(m_parent, wxID_ANY, X, wxDefaultPosition, field_size, wxTE_PROCESS_ENTER);
1424 y_textctrl = new wxTextCtrl(m_parent, wxID_ANY, Y, wxDefaultPosition, field_size, wxTE_PROCESS_ENTER);
1425 if (parent_is_custom_ctrl && m_opt.height < 0)
1426 opt_height = (double)x_textctrl->GetSize().GetHeight() / m_em_unit;
1427
1428 x_textctrl->SetFont(Slic3r::GUI::wxGetApp().normal_font());
1429 x_textctrl->SetBackgroundStyle(wxBG_STYLE_PAINT);
1430 y_textctrl->SetFont(Slic3r::GUI::wxGetApp().normal_font());
1431 y_textctrl->SetBackgroundStyle(wxBG_STYLE_PAINT);
1432
1433 auto static_text_x = new wxStaticText(m_parent, wxID_ANY, "x : ");
1434 auto static_text_y = new wxStaticText(m_parent, wxID_ANY, " y : ");
1435 static_text_x->SetFont(Slic3r::GUI::wxGetApp().normal_font());
1436 static_text_x->SetBackgroundStyle(wxBG_STYLE_PAINT);
1437 static_text_y->SetFont(Slic3r::GUI::wxGetApp().normal_font());
1438 static_text_y->SetBackgroundStyle(wxBG_STYLE_PAINT);
1439
1440 temp->Add(static_text_x, 0, wxALIGN_CENTER_VERTICAL, 0);
1441 temp->Add(x_textctrl);
1442 temp->Add(static_text_y, 0, wxALIGN_CENTER_VERTICAL, 0);
1443 temp->Add(y_textctrl);
1444
1445 x_textctrl->Bind(wxEVT_TEXT_ENTER, ([this](wxCommandEvent e) { propagate_value(x_textctrl); }), x_textctrl->GetId());
1446 y_textctrl->Bind(wxEVT_TEXT_ENTER, ([this](wxCommandEvent e) { propagate_value(y_textctrl); }), y_textctrl->GetId());
1447
1448 x_textctrl->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { e.Skip(); propagate_value(x_textctrl); }), x_textctrl->GetId());
1449 y_textctrl->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { e.Skip(); propagate_value(y_textctrl); }), y_textctrl->GetId());
1450
1451 // // recast as a wxWindow to fit the calling convention
1452 sizer = dynamic_cast<wxSizer*>(temp);
1453
1454 x_textctrl->SetToolTip(get_tooltip_text(X+", "+Y));
1455 y_textctrl->SetToolTip(get_tooltip_text(X+", "+Y));
1456 }
1457
msw_rescale()1458 void PointCtrl::msw_rescale()
1459 {
1460 Field::msw_rescale();
1461
1462 wxSize field_size(4 * m_em_unit, -1);
1463
1464 if (parent_is_custom_ctrl) {
1465 field_size.SetHeight(lround(opt_height * m_em_unit));
1466 x_textctrl->SetSize(field_size);
1467 y_textctrl->SetSize(field_size);
1468 }
1469 else {
1470 x_textctrl->SetMinSize(field_size);
1471 y_textctrl->SetMinSize(field_size);
1472 }
1473 }
1474
value_was_changed(wxTextCtrl * win)1475 bool PointCtrl::value_was_changed(wxTextCtrl* win)
1476 {
1477 if (m_value.empty())
1478 return true;
1479
1480 boost::any val = m_value;
1481 // update m_value!
1482 get_value();
1483
1484 return boost::any_cast<Vec2d>(m_value) != boost::any_cast<Vec2d>(val);
1485 }
1486
propagate_value(wxTextCtrl * win)1487 void PointCtrl::propagate_value(wxTextCtrl* win)
1488 {
1489 if (win->GetValue().empty())
1490 on_kill_focus();
1491 else if (value_was_changed(win))
1492 on_change_field();
1493 }
1494
set_value(const Vec2d & value,bool change_event)1495 void PointCtrl::set_value(const Vec2d& value, bool change_event)
1496 {
1497 m_disable_change_event = !change_event;
1498
1499 double val = value(0);
1500 x_textctrl->SetValue(val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None));
1501 val = value(1);
1502 y_textctrl->SetValue(val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None));
1503
1504 m_disable_change_event = false;
1505 }
1506
set_value(const boost::any & value,bool change_event)1507 void PointCtrl::set_value(const boost::any& value, bool change_event)
1508 {
1509 Vec2d pt(Vec2d::Zero());
1510 const Vec2d *ptf = boost::any_cast<Vec2d>(&value);
1511 if (!ptf)
1512 {
1513 ConfigOptionPoints* pts = boost::any_cast<ConfigOptionPoints*>(value);
1514 pt = pts->values.at(0);
1515 }
1516 else
1517 pt = *ptf;
1518 set_value(pt, change_event);
1519 }
1520
get_value()1521 boost::any& PointCtrl::get_value()
1522 {
1523 double x, y;
1524 if (!x_textctrl->GetValue().ToDouble(&x) ||
1525 !y_textctrl->GetValue().ToDouble(&y))
1526 {
1527 set_value(m_value.empty() ? Vec2d(0.0, 0.0) : m_value, true);
1528 show_error(m_parent, _L("Invalid numeric input."));
1529 }
1530 else
1531 if (m_opt.min > x || x > m_opt.max ||
1532 m_opt.min > y || y > m_opt.max)
1533 {
1534 if (m_opt.min > x) x = m_opt.min;
1535 if (x > m_opt.max) x = m_opt.max;
1536 if (m_opt.min > y) y = m_opt.min;
1537 if (y > m_opt.max) y = m_opt.max;
1538 set_value(Vec2d(x, y), true);
1539
1540 show_error(m_parent, _L("Input value is out of range"));
1541 }
1542
1543 return m_value = Vec2d(x, y);
1544 }
1545
BUILD()1546 void StaticText::BUILD()
1547 {
1548 auto size = wxSize(wxDefaultSize);
1549 if (m_opt.height >= 0) size.SetHeight(m_opt.height*m_em_unit);
1550 if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
1551
1552 const wxString legend = wxString::FromUTF8(m_opt.get_default_value<ConfigOptionString>()->value.c_str());
1553 auto temp = new wxStaticText(m_parent, wxID_ANY, legend, wxDefaultPosition, size, wxST_ELLIPSIZE_MIDDLE);
1554 temp->SetFont(Slic3r::GUI::wxGetApp().normal_font());
1555 temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
1556 temp->SetFont(wxGetApp().bold_font());
1557
1558 // // recast as a wxWindow to fit the calling convention
1559 window = dynamic_cast<wxWindow*>(temp);
1560
1561 temp->SetToolTip(get_tooltip_text(legend));
1562 }
1563
msw_rescale()1564 void StaticText::msw_rescale()
1565 {
1566 Field::msw_rescale();
1567
1568 auto size = wxSize(wxDefaultSize);
1569 if (m_opt.height >= 0) size.SetHeight(m_opt.height*m_em_unit);
1570 if (m_opt.width >= 0) size.SetWidth(m_opt.width*m_em_unit);
1571
1572 if (size != wxDefaultSize)
1573 {
1574 wxStaticText* field = dynamic_cast<wxStaticText*>(window);
1575 field->SetSize(size);
1576 field->SetMinSize(size);
1577 }
1578 }
1579
BUILD()1580 void SliderCtrl::BUILD()
1581 {
1582 auto size = wxSize(wxDefaultSize);
1583 if (m_opt.height >= 0) size.SetHeight(m_opt.height);
1584 if (m_opt.width >= 0) size.SetWidth(m_opt.width);
1585
1586 auto temp = new wxBoxSizer(wxHORIZONTAL);
1587
1588 auto def_val = m_opt.get_default_value<ConfigOptionInt>()->value;
1589 auto min = m_opt.min == INT_MIN ? 0 : m_opt.min;
1590 auto max = m_opt.max == INT_MAX ? 100 : m_opt.max;
1591
1592 m_slider = new wxSlider(m_parent, wxID_ANY, def_val * m_scale,
1593 min * m_scale, max * m_scale,
1594 wxDefaultPosition, size);
1595 m_slider->SetFont(Slic3r::GUI::wxGetApp().normal_font());
1596 m_slider->SetBackgroundStyle(wxBG_STYLE_PAINT);
1597 wxSize field_size(40, -1);
1598
1599 m_textctrl = new wxTextCtrl(m_parent, wxID_ANY, wxString::Format("%d", m_slider->GetValue()/m_scale),
1600 wxDefaultPosition, field_size);
1601 m_textctrl->SetFont(Slic3r::GUI::wxGetApp().normal_font());
1602 m_textctrl->SetBackgroundStyle(wxBG_STYLE_PAINT);
1603
1604 temp->Add(m_slider, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL, 0);
1605 temp->Add(m_textctrl, 0, wxALIGN_CENTER_VERTICAL, 0);
1606
1607 m_slider->Bind(wxEVT_SLIDER, ([this](wxCommandEvent e) {
1608 if (!m_disable_change_event) {
1609 int val = boost::any_cast<int>(get_value());
1610 m_textctrl->SetLabel(wxString::Format("%d", val));
1611 on_change_field();
1612 }
1613 }), m_slider->GetId());
1614
1615 m_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) {
1616 std::string value = e.GetString().utf8_str().data();
1617 if (is_matched(value, "^-?\\d+(\\.\\d*)?$")) {
1618 m_disable_change_event = true;
1619 m_slider->SetValue(stoi(value)*m_scale);
1620 m_disable_change_event = false;
1621 on_change_field();
1622 }
1623 }), m_textctrl->GetId());
1624
1625 m_sizer = dynamic_cast<wxSizer*>(temp);
1626 }
1627
set_value(const boost::any & value,bool change_event)1628 void SliderCtrl::set_value(const boost::any& value, bool change_event)
1629 {
1630 m_disable_change_event = !change_event;
1631
1632 m_slider->SetValue(boost::any_cast<int>(value)*m_scale);
1633 int val = boost::any_cast<int>(get_value());
1634 m_textctrl->SetLabel(wxString::Format("%d", val));
1635
1636 m_disable_change_event = false;
1637 }
1638
get_value()1639 boost::any& SliderCtrl::get_value()
1640 {
1641 // int ret_val;
1642 // x_textctrl->GetValue().ToDouble(&val);
1643 return m_value = int(m_slider->GetValue()/m_scale);
1644 }
1645
1646
1647 } // GUI
1648 } // Slic3r
1649
1650
1651