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