1 #include "Search.hpp"
2 
3 #include <cstddef>
4 #include <string>
5 #include <boost/algorithm/string.hpp>
6 #include <boost/optional.hpp>
7 #include <boost/nowide/convert.hpp>
8 
9 #include "wx/dataview.h"
10 
11 #include "libslic3r/PrintConfig.hpp"
12 #include "libslic3r/PresetBundle.hpp"
13 #include "GUI_App.hpp"
14 #include "Plater.hpp"
15 #include "Tab.hpp"
16 
17 #define FTS_FUZZY_MATCH_IMPLEMENTATION
18 #include "fts_fuzzy_match.h"
19 
20 #include "imgui/imconfig.h"
21 
22 using boost::optional;
23 
24 namespace Slic3r {
25 
26 using GUI::from_u8;
27 using GUI::into_u8;
28 
29 namespace Search {
30 
marker_by_type(Preset::Type type,PrinterTechnology pt)31 static char marker_by_type(Preset::Type type, PrinterTechnology pt)
32 {
33     switch(type) {
34     case Preset::TYPE_PRINT:
35     case Preset::TYPE_SLA_PRINT:
36         return ImGui::PrintIconMarker;
37     case Preset::TYPE_FILAMENT:
38         return ImGui::FilamentIconMarker;
39     case Preset::TYPE_SLA_MATERIAL:
40         return ImGui::MaterialIconMarker;
41     case Preset::TYPE_PRINTER:
42         return pt == ptSLA ? ImGui::PrinterSlaIconMarker : ImGui::PrinterIconMarker;
43     default:
44         return ' ';
45 	}
46 }
47 
get_marked_label_and_tooltip(const char ** label_,const char ** tooltip_) const48 void FoundOption::get_marked_label_and_tooltip(const char** label_, const char** tooltip_) const
49 {
50     *label_   = marked_label.c_str();
51     *tooltip_ = tooltip.c_str();
52 }
53 
54 template<class T>
55 //void change_opt_key(std::string& opt_key, DynamicPrintConfig* config)
change_opt_key(std::string & opt_key,DynamicPrintConfig * config,int & cnt)56 void change_opt_key(std::string& opt_key, DynamicPrintConfig* config, int& cnt)
57 {
58     T* opt_cur = static_cast<T*>(config->option(opt_key));
59     cnt = opt_cur->values.size();
60     return;
61 
62     if (opt_cur->values.size() > 0)
63         opt_key += "#" + std::to_string(0);
64 }
65 
append_options(DynamicPrintConfig * config,Preset::Type type,ConfigOptionMode mode)66 void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type type, ConfigOptionMode mode)
67 {
68     auto emplace = [this, type](const std::string opt_key, const wxString& label)
69     {
70         const GroupAndCategory& gc = groups_and_categories[opt_key];
71         if (gc.group.IsEmpty() || gc.category.IsEmpty())
72             return;
73 
74         wxString suffix;
75         wxString suffix_local;
76         if (gc.category == "Machine limits") {
77             suffix = opt_key.back()=='1' ? L("Stealth") : L("Normal");
78             suffix_local = " " + _(suffix);
79             suffix = " " + suffix;
80         }
81 
82         if (!label.IsEmpty())
83             options.emplace_back(Option{ boost::nowide::widen(opt_key), type,
84                                         (label + suffix).ToStdWstring(), (_(label) + suffix_local).ToStdWstring(),
85                                         gc.group.ToStdWstring(), _(gc.group).ToStdWstring(),
86                                         gc.category.ToStdWstring(), GUI::Tab::translate_category(gc.category, type).ToStdWstring() });
87     };
88 
89     for (std::string opt_key : config->keys())
90     {
91         const ConfigOptionDef& opt = config->def()->options.at(opt_key);
92         if (opt.mode > mode)
93             continue;
94 
95         int cnt = 0;
96 
97         if ( (type == Preset::TYPE_SLA_MATERIAL || type == Preset::TYPE_PRINTER) && opt_key != "bed_shape" && opt_key != "thumbnails")
98             switch (config->option(opt_key)->type())
99             {
100             case coInts:	change_opt_key<ConfigOptionInts		>(opt_key, config, cnt);	break;
101             case coBools:	change_opt_key<ConfigOptionBools	>(opt_key, config, cnt);	break;
102             case coFloats:	change_opt_key<ConfigOptionFloats	>(opt_key, config, cnt);	break;
103             case coStrings:	change_opt_key<ConfigOptionStrings	>(opt_key, config, cnt);	break;
104             case coPercents:change_opt_key<ConfigOptionPercents	>(opt_key, config, cnt);	break;
105             case coPoints:	change_opt_key<ConfigOptionPoints	>(opt_key, config, cnt);	break;
106             default:		break;
107             }
108 
109         wxString label = opt.full_label.empty() ? opt.label : opt.full_label;
110 
111         if (cnt == 0)
112             emplace(opt_key, label);
113         else
114             for (int i = 0; i < cnt; ++i)
115                 // ! It's very important to use "#". opt_key#n is a real option key used in GroupAndCategory
116                 emplace(opt_key + "#" + std::to_string(i), label);
117     }
118 }
119 
120 // Mark a string using ColorMarkerStart and ColorMarkerEnd symbols
mark_string(const std::wstring & str,const std::vector<uint16_t> & matches,Preset::Type type,PrinterTechnology pt)121 static std::wstring mark_string(const std::wstring &str, const std::vector<uint16_t> &matches, Preset::Type type, PrinterTechnology pt)
122 {
123 	std::wstring out;
124     out += marker_by_type(type, pt);
125 	if (matches.empty())
126 		out += str;
127 	else {
128 		out.reserve(str.size() * 2);
129 		if (matches.front() > 0)
130 			out += str.substr(0, matches.front());
131 		for (size_t i = 0;;) {
132 			// Find the longest string of successive indices.
133 			size_t j = i + 1;
134             while (j < matches.size() && matches[j] == matches[j - 1] + 1)
135                 ++ j;
136             out += ImGui::ColorMarkerStart;
137             out += str.substr(matches[i], matches[j - 1] - matches[i] + 1);
138             out += ImGui::ColorMarkerEnd;
139             if (j == matches.size()) {
140 				out += str.substr(matches[j - 1] + 1);
141 				break;
142 			}
143             out += str.substr(matches[j - 1] + 1, matches[j] - matches[j - 1] - 1);
144             i = j;
145 		}
146 	}
147 	return out;
148 }
149 
search()150 bool OptionsSearcher::search()
151 {
152     return search(search_line, true);
153 }
154 
fuzzy_match(const std::wstring & search_pattern,const std::wstring & label,int & out_score,std::vector<uint16_t> & out_matches)155 static bool fuzzy_match(const std::wstring &search_pattern, const std::wstring &label, int& out_score, std::vector<uint16_t> &out_matches)
156 {
157     uint16_t matches[fts::max_matches + 1]; // +1 for the stopper
158     int score;
159     if (fts::fuzzy_match(search_pattern.c_str(), label.c_str(), score, matches)) {
160 	    size_t cnt = 0;
161 	    for (; matches[cnt] != fts::stopper; ++cnt);
162 	    out_matches.assign(matches, matches + cnt);
163 		out_score = score;
164 		return true;
165 	} else
166 		return false;
167 }
168 
search(const std::string & search,bool force)169 bool OptionsSearcher::search(const std::string& search, bool force/* = false*/)
170 {
171     if (search_line == search && !force)
172         return false;
173 
174     found.clear();
175 
176     bool full_list = search.empty();
177     std::wstring sep = L" : ";
178 
179     auto get_label = [this, &sep](const Option& opt, bool marked = true)
180     {
181         std::wstring out;
182         if (marked)
183             out += marker_by_type(opt.type, printer_technology);
184     	const std::wstring *prev = nullptr;
185     	for (const std::wstring * const s : {
186 	        view_params.category 	? &opt.category_local 		: nullptr,
187 	        &opt.group_local, &opt.label_local })
188     		if (s != nullptr && (prev == nullptr || *prev != *s)) {
189       			if (out.size()>2)
190     				out += sep;
191     			out += *s;
192     			prev = s;
193     		}
194         return out;
195     };
196 
197     auto get_label_english = [this, &sep](const Option& opt, bool marked = true)
198     {
199         std::wstring out;
200         if (marked)
201             out += marker_by_type(opt.type, printer_technology);
202     	const std::wstring*prev = nullptr;
203     	for (const std::wstring * const s : {
204 	        view_params.category 	? &opt.category 			: nullptr,
205 	        &opt.group, &opt.label })
206     		if (s != nullptr && (prev == nullptr || *prev != *s)) {
207       			if (out.size()>2)
208     				out += sep;
209     			out += *s;
210     			prev = s;
211     		}
212         return out;
213     };
214 
215     auto get_tooltip = [this, &sep](const Option& opt)
216     {
217         return  marker_by_type(opt.type, printer_technology) +
218                 opt.category_local + sep +
219                 opt.group_local + sep + opt.label_local;
220     };
221 
222     std::vector<uint16_t> matches, matches2;
223     for (size_t i=0; i < options.size(); i++)
224     {
225         const Option &opt = options[i];
226         if (full_list) {
227             std::string label = into_u8(get_label(opt));
228             found.emplace_back(FoundOption{ label, label, boost::nowide::narrow(get_tooltip(opt)), i, 0 });
229             continue;
230         }
231 
232         std::wstring wsearch       = boost::nowide::widen(search);
233         boost::trim_left(wsearch);
234         std::wstring label         = get_label(opt, false);
235         std::wstring label_english = get_label_english(opt, false);
236         int score = std::numeric_limits<int>::min();
237         int score2;
238         matches.clear();
239         fuzzy_match(wsearch, label, score, matches);
240         if (fuzzy_match(wsearch, opt.opt_key, score2, matches2) && score2 > score) {
241         	for (fts::pos_type &pos : matches2)
242         		pos += label.size() + 1;
243         	label += L"(" + opt.opt_key + L")";
244         	append(matches, matches2);
245         	score = score2;
246         }
247         if (view_params.english && fuzzy_match(wsearch, label_english, score2, matches2) && score2 > score) {
248         	label   = std::move(label_english);
249         	matches = std::move(matches2);
250         	score   = score2;
251         }
252         if (score > 90/*std::numeric_limits<int>::min()*/) {
253 		    label = mark_string(label, matches, opt.type, printer_technology);
254             label += L"  [" + std::to_wstring(score) + L"]";// add score value
255 	        std::string label_u8 = into_u8(label);
256 	        std::string label_plain = label_u8;
257 
258 #ifdef SUPPORTS_MARKUP
259             boost::replace_all(label_plain, std::string(1, char(ImGui::ColorMarkerStart)), "<b>");
260             boost::replace_all(label_plain, std::string(1, char(ImGui::ColorMarkerEnd)),   "</b>");
261 #else
262             boost::erase_all(label_plain, std::string(1, char(ImGui::ColorMarkerStart)));
263             boost::erase_all(label_plain, std::string(1, char(ImGui::ColorMarkerEnd)));
264 #endif
265 	        found.emplace_back(FoundOption{ label_plain, label_u8, boost::nowide::narrow(get_tooltip(opt)), i, score });
266         }
267     }
268 
269     if (!full_list)
270         sort_found();
271 
272     if (search_line != search)
273         search_line = search;
274 
275     return true;
276 }
277 
OptionsSearcher()278 OptionsSearcher::OptionsSearcher()
279 {
280     search_dialog = new SearchDialog(this);
281 }
282 
~OptionsSearcher()283 OptionsSearcher::~OptionsSearcher()
284 {
285     if (search_dialog)
286         search_dialog->Destroy();
287 }
288 
init(std::vector<InputInfo> input_values)289 void OptionsSearcher::init(std::vector<InputInfo> input_values)
290 {
291     options.clear();
292     for (auto i : input_values)
293         append_options(i.config, i.type, i.mode);
294     sort_options();
295 
296     search(search_line, true);
297 }
298 
apply(DynamicPrintConfig * config,Preset::Type type,ConfigOptionMode mode)299 void OptionsSearcher::apply(DynamicPrintConfig* config, Preset::Type type, ConfigOptionMode mode)
300 {
301     if (options.empty())
302         return;
303 
304     options.erase(std::remove_if(options.begin(), options.end(), [type](Option opt) {
305             return opt.type == type;
306         }), options.end());
307 
308     append_options(config, type, mode);
309 
310     sort_options();
311 
312     search(search_line, true);
313 }
314 
get_option(size_t pos_in_filter) const315 const Option& OptionsSearcher::get_option(size_t pos_in_filter) const
316 {
317     assert(pos_in_filter != size_t(-1) && found[pos_in_filter].option_idx != size_t(-1));
318     return options[found[pos_in_filter].option_idx];
319 }
320 
get_option(const std::string & opt_key) const321 const Option& OptionsSearcher::get_option(const std::string& opt_key) const
322 {
323     auto it = std::lower_bound(options.begin(), options.end(), Option({ boost::nowide::widen(opt_key) }));
324     assert(it != options.end());
325 
326     return options[it - options.begin()];
327 }
328 
add_key(const std::string & opt_key,const wxString & group,const wxString & category)329 void OptionsSearcher::add_key(const std::string& opt_key, const wxString& group, const wxString& category)
330 {
331     groups_and_categories[opt_key] = GroupAndCategory{group, category};
332 }
333 
334 
335 //------------------------------------------
336 //          SearchDialog
337 //------------------------------------------
338 
339 static const std::map<const char, int> icon_idxs = {
340     {ImGui::PrintIconMarker     , 0},
341     {ImGui::PrinterIconMarker   , 1},
342     {ImGui::PrinterSlaIconMarker, 2},
343     {ImGui::FilamentIconMarker  , 3},
344     {ImGui::MaterialIconMarker  , 4},
345 };
346 
SearchDialog(OptionsSearcher * searcher)347 SearchDialog::SearchDialog(OptionsSearcher* searcher)
348     : GUI::DPIDialog(NULL, wxID_ANY, _L("Search"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
349     searcher(searcher)
350 {
351     SetFont(GUI::wxGetApp().normal_font());
352     wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
353     SetBackgroundColour(bgr_clr);
354 
355     default_string = _L("Enter a search term");
356     int border = 10;
357     int em = em_unit();
358 
359     search_line = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
360 
361     search_list = new wxDataViewCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(em * 40, em * 30), wxDV_NO_HEADER | wxDV_SINGLE | wxBORDER_SIMPLE);
362     search_list_model = new SearchListModel(this);
363     search_list->AssociateModel(search_list_model);
364 
365     search_list->AppendBitmapColumn("", SearchListModel::colIcon);
366 
367     wxDataViewTextRenderer* const markupRenderer = new wxDataViewTextRenderer();
368 
369 #ifdef SUPPORTS_MARKUP
370     markupRenderer->EnableMarkup();
371 #endif
372 
373     search_list->AppendColumn(new wxDataViewColumn("", markupRenderer, SearchListModel::colMarkedText, wxCOL_WIDTH_AUTOSIZE, wxALIGN_LEFT));
374 
375     search_list->GetColumn(SearchListModel::colIcon      )->SetWidth(3  * em_unit());
376     search_list->GetColumn(SearchListModel::colMarkedText)->SetWidth(40 * em_unit());
377 
378     wxBoxSizer* check_sizer = new wxBoxSizer(wxHORIZONTAL);
379 
380     check_category  = new wxCheckBox(this, wxID_ANY, _L("Category"));
381     if (GUI::wxGetApp().is_localized())
382         check_english   = new wxCheckBox(this, wxID_ANY, _L("Search in English"));
383 
384     wxStdDialogButtonSizer* cancel_btn = this->CreateStdDialogButtonSizer(wxCANCEL);
385 
386     check_sizer->Add(new wxStaticText(this, wxID_ANY, _L("Use for search") + ":"), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border);
387     check_sizer->Add(check_category, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border);
388     if (check_english)
389         check_sizer->Add(check_english,  0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border);
390     check_sizer->AddStretchSpacer(border);
391     check_sizer->Add(cancel_btn,     0, wxALIGN_CENTER_VERTICAL);
392 
393     wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
394 
395     topSizer->Add(search_line, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);
396     topSizer->Add(search_list, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border);
397     topSizer->Add(check_sizer, 0, wxEXPAND | wxALL, border);
398 
399     search_line->Bind(wxEVT_TEXT,    &SearchDialog::OnInputText, this);
400     search_line->Bind(wxEVT_LEFT_UP, &SearchDialog::OnLeftUpInTextCtrl, this);
401     // process wxEVT_KEY_DOWN to navigate inside search_list, if ArrowUp/Down was pressed
402     search_line->Bind(wxEVT_KEY_DOWN,&SearchDialog::OnKeyDown, this);
403 
404     search_list->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, &SearchDialog::OnSelect,    this);
405     search_list->Bind(wxEVT_DATAVIEW_ITEM_ACTIVATED,    &SearchDialog::OnActivate,  this);
406 #ifdef __WXMSW__
407     search_list->GetMainWindow()->Bind(wxEVT_MOTION,    &SearchDialog::OnMotion,    this);
408     search_list->GetMainWindow()->Bind(wxEVT_LEFT_DOWN, &SearchDialog::OnLeftDown, this);
409 #endif //__WXMSW__
410 
411     // Under OSX mouse and key states didn't fill after wxEVT_DATAVIEW_SELECTION_CHANGED call
412     // As a result, we can't to identify what kind of actions was done
413     // So, under OSX is used OnKeyDown function to navigate inside the list
414 #ifdef __APPLE__
415     search_list->Bind(wxEVT_KEY_DOWN, &SearchDialog::OnKeyDown, this);
416 #endif
417 
418     check_category->Bind(wxEVT_CHECKBOX, &SearchDialog::OnCheck, this);
419     if (check_english)
420         check_english ->Bind(wxEVT_CHECKBOX, &SearchDialog::OnCheck, this);
421 
422 //    Bind(wxEVT_MOTION, &SearchDialog::OnMotion, this);
423     Bind(wxEVT_LEFT_DOWN, &SearchDialog::OnLeftDown, this);
424 
425     SetSizer(topSizer);
426     topSizer->SetSizeHints(this);
427 }
428 
Popup(wxPoint position)429 void SearchDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
430 {
431     const std::string& line = searcher->search_string();
432     search_line->SetValue(line.empty() ? default_string : from_u8(line));
433     search_line->SetFocus();
434     search_line->SelectAll();
435 
436     update_list();
437 
438     const OptionViewParameters& params = searcher->view_params;
439     check_category->SetValue(params.category);
440     if (check_english)
441         check_english->SetValue(params.english);
442 
443     this->SetPosition(position);
444     this->ShowModal();
445 }
446 
ProcessSelection(wxDataViewItem selection)447 void SearchDialog::ProcessSelection(wxDataViewItem selection)
448 {
449     if (!selection.IsOk())
450         return;
451 
452     GUI::wxGetApp().sidebar().jump_to_option(search_list_model->GetRow(selection));
453     this->EndModal(wxID_CLOSE);
454 }
455 
OnInputText(wxCommandEvent &)456 void SearchDialog::OnInputText(wxCommandEvent&)
457 {
458     search_line->SetInsertionPointEnd();
459 
460     wxString input_string = search_line->GetValue();
461     if (input_string == default_string)
462         input_string.Clear();
463 
464     searcher->search(into_u8(input_string));
465 
466     update_list();
467 }
468 
OnLeftUpInTextCtrl(wxEvent & event)469 void SearchDialog::OnLeftUpInTextCtrl(wxEvent& event)
470 {
471     if (search_line->GetValue() == default_string)
472         search_line->SetValue("");
473 
474     event.Skip();
475 }
476 
OnKeyDown(wxKeyEvent & event)477 void SearchDialog::OnKeyDown(wxKeyEvent& event)
478 {
479     int key = event.GetKeyCode();
480 
481     // change selected item in the list
482     if (key == WXK_UP || key == WXK_DOWN)
483     {
484         // So, for the next correct navigation, set focus on the search_list
485         search_list->SetFocus();
486 
487         auto item = search_list->GetSelection();
488 
489         if (item.IsOk()) {
490             unsigned selection = search_list_model->GetRow(item);
491 
492             if (key == WXK_UP && selection > 0)
493                 selection--;
494             if (key == WXK_DOWN && selection < unsigned(search_list_model->GetCount() - 1))
495                 selection++;
496 
497             prevent_list_events = true;
498             search_list->Select(search_list_model->GetItem(selection));
499             prevent_list_events = false;
500         }
501     }
502     // process "Enter" pressed
503     else if (key == WXK_NUMPAD_ENTER || key == WXK_RETURN)
504         ProcessSelection(search_list->GetSelection());
505     else
506         event.Skip(); // !Needed to have EVT_CHAR generated as well
507 }
508 
OnActivate(wxDataViewEvent & event)509 void SearchDialog::OnActivate(wxDataViewEvent& event)
510 {
511     ProcessSelection(event.GetItem());
512 }
513 
OnSelect(wxDataViewEvent & event)514 void SearchDialog::OnSelect(wxDataViewEvent& event)
515 {
516     // To avoid selection update from Select() under osx
517     if (prevent_list_events)
518         return;
519 
520     // Under OSX mouse and key states didn't fill after wxEVT_DATAVIEW_SELECTION_CHANGED call
521     // As a result, we can't to identify what kind of actions was done
522     // So, under OSX is used OnKeyDown function to navigate inside the list
523 #ifndef __APPLE__
524     // wxEVT_DATAVIEW_SELECTION_CHANGED is processed, when selection is changed after mouse click or press the Up/Down arrows
525     // But this two cases should be processed in different way:
526     // Up/Down arrows   -> leave it as it is (just a navigation)
527     // LeftMouseClick   -> call the ProcessSelection function
528     if (wxGetMouseState().LeftIsDown())
529 #endif //__APPLE__
530         ProcessSelection(search_list->GetSelection());
531 }
532 
update_list()533 void SearchDialog::update_list()
534 {
535     // Under OSX model->Clear invoke wxEVT_DATAVIEW_SELECTION_CHANGED, so
536     // set prevent_list_events to true already here
537     prevent_list_events = true;
538     search_list_model->Clear();
539 
540     const std::vector<FoundOption>& filters = searcher->found_options();
541     for (const FoundOption& item : filters)
542         search_list_model->Prepend(item.label);
543 
544     // select first item, if search_list
545     if (search_list_model->GetCount() > 0)
546         search_list->Select(search_list_model->GetItem(0));
547     prevent_list_events = false;
548 }
549 
OnCheck(wxCommandEvent & event)550 void SearchDialog::OnCheck(wxCommandEvent& event)
551 {
552     OptionViewParameters& params = searcher->view_params;
553     if (check_english)
554         params.english  = check_english->GetValue();
555     params.category = check_category->GetValue();
556 
557     searcher->search();
558     update_list();
559 }
560 
OnMotion(wxMouseEvent & event)561 void SearchDialog::OnMotion(wxMouseEvent& event)
562 {
563     wxDataViewItem    item;
564     wxDataViewColumn* col;
565     wxWindow* win = this;
566 #ifdef __WXMSW__
567     win = search_list;
568 #endif
569     search_list->HitTest(wxGetMousePosition() - win->GetScreenPosition(), item, col);
570     search_list->Select(item);
571 
572     event.Skip();
573 }
574 
OnLeftDown(wxMouseEvent & event)575 void SearchDialog::OnLeftDown(wxMouseEvent& event)
576 {
577     ProcessSelection(search_list->GetSelection());
578 }
579 
on_dpi_changed(const wxRect & suggested_rect)580 void SearchDialog::on_dpi_changed(const wxRect& suggested_rect)
581 {
582     const int& em = em_unit();
583 
584     search_list_model->msw_rescale();
585     search_list->GetColumn(SearchListModel::colIcon      )->SetWidth(3  * em);
586     search_list->GetColumn(SearchListModel::colMarkedText)->SetWidth(45 * em);
587 
588     msw_buttons_rescale(this, em, { wxID_CANCEL });
589 
590     const wxSize& size = wxSize(40 * em, 30 * em);
591     SetMinSize(size);
592 
593     Fit();
594     Refresh();
595 }
596 
on_sys_color_changed()597 void SearchDialog::on_sys_color_changed()
598 {
599     // msw_rescale updates just icons, so use it
600     search_list_model->msw_rescale();
601 
602     Refresh();
603 }
604 
605 // ----------------------------------------------------------------------------
606 // SearchListModel
607 // ----------------------------------------------------------------------------
608 
SearchListModel(wxWindow * parent)609 SearchListModel::SearchListModel(wxWindow* parent) : wxDataViewVirtualListModel(0)
610 {
611     int icon_id = 0;
612     for (const std::string& icon : { "cog", "printer", "sla_printer", "spool", "resin" })
613         m_icon[icon_id++] = ScalableBitmap(parent, icon);
614 }
615 
Clear()616 void SearchListModel::Clear()
617 {
618     m_values.clear();
619     Reset(0);
620 }
621 
Prepend(const std::string & label)622 void SearchListModel::Prepend(const std::string& label)
623 {
624     const char icon_c = label.at(0);
625     int icon_idx = icon_idxs.at(icon_c);
626     wxString str = from_u8(label).Remove(0, 1);
627 
628     m_values.emplace_back(str, icon_idx);
629 
630     RowPrepended();
631 }
632 
msw_rescale()633 void SearchListModel::msw_rescale()
634 {
635     for (ScalableBitmap& bmp : m_icon)
636         bmp.msw_rescale();
637 }
638 
GetColumnType(unsigned int col) const639 wxString SearchListModel::GetColumnType(unsigned int col) const
640 {
641     if (col == colIcon)
642         return "wxBitmap";
643     return "string";
644 }
645 
GetValueByRow(wxVariant & variant,unsigned int row,unsigned int col) const646 void SearchListModel::GetValueByRow(wxVariant& variant,
647     unsigned int row, unsigned int col) const
648 {
649     switch (col)
650     {
651     case colIcon:
652         variant << m_icon[m_values[row].second].bmp();
653         break;
654     case colMarkedText:
655         variant = m_values[row].first;
656         break;
657     case colMax:
658         wxFAIL_MSG("invalid column");
659     default:
660         break;
661     }
662 }
663 
664 
665 }
666 
667 }    // namespace Slic3r::GUI
668