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