Files
OrcaSlicer/src/slic3r/GUI/Search.cpp
T
Kris AustinandRodrigo Faselli cbd1bf2c37 build: clear 295 more -Woverloaded-virtual warnings in GUI widgets (#15394)
build: clear 295 -Woverloaded-virtual warnings in GUI widgets

Turns three hidden base virtuals into real overrides, clearing 295 of
the 553 -Woverloaded-virtual warnings and taking a full clang-cl build
from 1,264 to 969. Part of #15374.

Search.hpp: SearchDialog::Popup and SearchObjectDialog::Popup took a
wxPoint that neither body ever read, hiding the virtual
wxPopupTransientWindow::Popup(wxWindow*). Both bodies clear the input,
call the base, set focus and refill the list, and SearchObjectDialog
also guards re-entry, so hiding meant none of that ran when the window
was popped through a base pointer. They now override and forward focus.

LabeledStaticBox::SetFont and ScrolledWindow::SetBackgroundColour hid
their base virtuals the same way, so the label metrics recompute and
the child colour propagation only ran for callers holding the concrete
type. Both now override.

Marking a member override makes clang flag every other unmarked
override in the same class, so seven sibling declarations needed the
keyword too. Left unmarked they were worth 481 warnings, which would
have made this a net loss.

MSWDismissUnfocusedPopup is declared only inside #ifdef __WXMSW__ in
wx/popupwin.h, so off Windows there is no base virtual to override and
the keyword would not compile. Both the declarations and the definitions
are guarded, which is how wxWidgets itself declares MSWWindowProc in
wx/nativewin.h and how this repo already handles it in BBLTopbar,
MainFrame, Button, ComboBox and TabCtrl.

ScrolledWindow's constructor left m_userPanel and m_scroll_win
uninitialised unless the style requested a vertical scrollbar, while
SetBackgroundColour dereferences both. No caller hits that today since
every instantiation passes wxVSCROLL, but the override widens who can
reach them, so they are now initialised alongside their siblings.

Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-08-27 08:06:50 -03:00

1055 lines
37 KiB
C++

#include "Search.hpp"
#include <vector>
#include <cstddef>
#include <string>
#include <boost/algorithm/string.hpp>
#include <boost/optional.hpp>
#include <boost/nowide/convert.hpp>
#include "wx/dataview.h"
#include "wx/numformatter.h"
#include "Widgets/Label.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "GUI_App.hpp"
#include "Plater.hpp"
#include "Tab.hpp"
#define FTS_FUZZY_MATCH_IMPLEMENTATION
#include "fts_fuzzy_match.h"
#include "imgui/imconfig.h"
#if defined(__WXGTK__)
#include "LinuxDisplayBackend.hpp"
#endif
using boost::optional;
namespace Slic3r {
wxDEFINE_EVENT(wxCUSTOMEVT_JUMP_TO_OPTION, wxCommandEvent);
wxDEFINE_EVENT(wxCUSTOMEVT_EXIT_SEARCH, wxCommandEvent);
wxDEFINE_EVENT(wxCUSTOMEVT_JUMP_TO_OBJECT, wxCommandEvent);
using GUI::from_u8;
using GUI::into_u8;
namespace {
bool focus_left_popup(wxWindow* popup, wxWindow* focus_window, wxWindow* related_window_1 = nullptr,
wxWindow* related_window_2 = nullptr, wxWindow* related_window_3 = nullptr)
{
return focus_window != popup && !popup->IsDescendant(focus_window) && focus_window != related_window_1 &&
focus_window != related_window_2 && focus_window != related_window_3;
}
} // namespace
namespace Search {
static char marker_by_type(Preset::Type type, PrinterTechnology pt)
{
switch (type) {
case Preset::TYPE_PRINT:
case Preset::TYPE_SLA_PRINT: return ImGui::PrintIconMarker;
case Preset::TYPE_FILAMENT: return ImGui::FilamentIconMarker;
case Preset::TYPE_SLA_MATERIAL: return ImGui::MaterialIconMarker;
case Preset::TYPE_PRINTER: return pt == ptSLA ? ImGui::PrinterSlaIconMarker : ImGui::PrinterIconMarker;
default: return ' ';
}
}
std::string Option::opt_key() const { return into_u8(key).substr(2); }
void FoundOption::get_marked_label_and_tooltip(const char **label_, const char **tooltip_) const
{
*label_ = marked_label.c_str();
*tooltip_ = tooltip.c_str();
}
template<class T>
// void change_opt_key(std::string& opt_key, DynamicPrintConfig* config)
void change_opt_key(std::string &opt_key, DynamicPrintConfig *config, int &cnt)
{
T *opt_cur = static_cast<T *>(config->option(opt_key));
cnt = opt_cur->values.size();
return;
if (opt_cur->values.size() > 0) opt_key += "#" + std::to_string(0);
}
static std::string get_key(const std::string &opt_key, Preset::Type type) { return std::to_string(int(type)) + ";" + opt_key; }
void OptionsSearcher::append_options(DynamicPrintConfig *config, Preset::Type type, ConfigOptionMode mode)
{
auto emplace = [this, type](const std::string key, const wxString &label) {
const GroupAndCategory &gc = groups_and_categories[key];
if (gc.group.IsEmpty() || gc.category.IsEmpty()) return;
wxString suffix;
wxString suffix_local;
if (gc.category == "Machine limits") {
//suffix = key.back() == '1' ? L("Stealth") : L("Normal");
suffix = key.back() == '1' ? wxEmptyString : wxEmptyString;
suffix_local = " " + _(suffix);
suffix = " " + suffix;
}
if (!label.IsEmpty())
options.emplace_back(Option{boost::nowide::widen(key), type, (label + suffix).ToStdWstring(), (_(label) + suffix_local).ToStdWstring(), gc.group.ToStdWstring(),
_(gc.group).ToStdWstring(), gc.category.ToStdWstring(), GUI::Tab::translate_category(gc.category, type).ToStdWstring()});
};
for (std::string opt_key : config->keys()) {
const ConfigOptionDef &opt = config->def()->options.at(opt_key);
if (opt.mode > mode) continue;
int cnt = 0;
if ((type == Preset::TYPE_SLA_MATERIAL || type == Preset::TYPE_PRINTER || type == Preset::TYPE_PRINT) && opt_key != "printable_area")
switch (config->option(opt_key)->type()) {
case coInts: change_opt_key<ConfigOptionInts>(opt_key, config, cnt); break;
case coBools: change_opt_key<ConfigOptionBools>(opt_key, config, cnt); break;
case coFloats: change_opt_key<ConfigOptionFloats>(opt_key, config, cnt); break;
case coStrings: change_opt_key<ConfigOptionStrings>(opt_key, config, cnt); break;
case coPercents: change_opt_key<ConfigOptionPercents>(opt_key, config, cnt); break;
case coPoints: change_opt_key<ConfigOptionPoints>(opt_key, config, cnt); break;
// BBS
case coEnums: change_opt_key<ConfigOptionInts>(opt_key, config, cnt); break;
default: break;
}
if (type == Preset::TYPE_FILAMENT && filament_options_with_variant.find(opt_key) != filament_options_with_variant.end())
opt_key += "#0";
wxString label = opt.full_label.empty() ? opt.label : opt.full_label;
std::string key = get_key(opt_key, type);
if (cnt == 0)
emplace(key, label);
else
for (int i = 0; i < cnt; ++i)
// ! It's very important to use "#". opt_key#n is a real option key used in GroupAndCategory
emplace(key + "#" + std::to_string(i), label);
}
}
inline void OptionsSearcher::sort_options()
{
std::sort(options.begin(), options.end(), [](const Option &o1, const Option &o2) { return o1.label < o2.label; });
Option * last = nullptr;
for (auto& opt : options) {
if (last && last->label == opt.label && last->group == opt.group && last->type == opt.type && last->category != opt.category) {
last->multi_category = true;
opt.multi_category = true;
}
last = &opt;
}
}
// Mark a string using ColorMarkerStart and ColorMarkerEnd symbols
static std::wstring mark_string(const std::wstring &str, const std::vector<uint16_t> &matches, Preset::Type type, PrinterTechnology pt)
{
std::wstring out;
out += marker_by_type(type, pt);
if (matches.empty())
out += str;
else {
out.reserve(str.size() * 2);
if (matches.front() > 0) out += str.substr(0, matches.front());
for (size_t i = 0;;) {
// Find the longest string of successive indices.
size_t j = i + 1;
while (j < matches.size() && matches[j] == matches[j - 1] + 1) ++j;
out += ImGui::ColorMarkerStart;
out += str.substr(matches[i], matches[j - 1] - matches[i] + 1);
out += ImGui::ColorMarkerEnd;
if (j == matches.size()) {
out += str.substr(matches[j - 1] + 1);
break;
}
out += str.substr(matches[j - 1] + 1, matches[j] - matches[j - 1] - 1);
i = j;
}
}
return out;
}
bool OptionsSearcher::search() { return search(search_line, true); }
static bool fuzzy_match(const std::wstring &search_pattern, const std::wstring &label, int &out_score, std::vector<uint16_t> &out_matches)
{
uint16_t matches[fts::max_matches + 1]; // +1 for the stopper
int score;
if (fts::fuzzy_match(search_pattern.c_str(), label.c_str(), score, matches)) {
size_t cnt = 0;
for (; matches[cnt] != fts::stopper; ++cnt)
;
out_matches.assign(matches, matches + cnt);
out_score = score;
return true;
} else
return false;
}
bool OptionsSearcher::search(const std::string &search, bool force /* = false*/, Preset::Type type/* = Preset::TYPE_INVALID*/)
{
if (search_line == search && search_type == type && !force) return false;
found.clear();
bool full_list = search.empty();
wxString sep = L" : ";
auto get_label = [this, &sep](const Option &opt, bool marked = true) {
std::wstring out;
if (marked) out += marker_by_type(opt.type, printer_technology);
const std::wstring *prev = nullptr;
for (const std::wstring *const s : {view_params.category || opt.multi_category ? &opt.category_local : nullptr, &opt.group_local, &opt.label_local})
if (s != nullptr && (prev == nullptr || *prev != *s)) {
if (out.size() > 2) out += sep;
out += *s;
prev = s;
}
return out;
};
auto get_label_english = [this, &sep](const Option &opt, bool marked = true) {
std::wstring out;
if (marked) out += marker_by_type(opt.type, printer_technology);
const std::wstring *prev = nullptr;
for (const std::wstring *const s : {view_params.category || opt.multi_category ? &opt.category : nullptr, &opt.group, &opt.label})
if (s != nullptr && (prev == nullptr || *prev != *s)) {
if (out.size() > 2) out += sep;
out += *s;
prev = s;
}
return out;
};
auto get_tooltip = [this, &sep](const Option &opt) {
return wxString(marker_by_type(opt.type, printer_technology)) + opt.category_local + sep + opt.group_local + sep + opt.label_local;
};
std::vector<uint16_t> matches, matches2;
for (size_t i = 0; i < options.size(); i++) {
const Option &opt = options[i];
if (full_list) {
std::string label = into_u8(get_label(opt));
//all
if (type == Preset::TYPE_INVALID) {
found.emplace_back(FoundOption{label, label, into_u8(get_tooltip(opt)), i, 0});
} else if (type == opt.type){
found.emplace_back(FoundOption{label, label, into_u8(get_tooltip(opt)), i, 0});
}
continue;
}
std::wstring wsearch = boost::nowide::widen(search);
boost::trim_left(wsearch);
std::wstring label = get_label(opt, false);
std::wstring label_english = get_label_english(opt, false);
int score = std::numeric_limits<int>::min();
int score2;
matches.clear();
fuzzy_match(wsearch, label, score, matches);
// bbs hide the contents in parentheses
/* if (fuzzy_match(wsearch, opt.key, score2, matches2) && score2 > score) {
for (fts::pos_type &pos : matches2) pos += label.size() + 1;
label += L"(" + opt.key + L")";
append(matches, matches2);
score = score2;
}*/
if (view_params.english && fuzzy_match(wsearch, label_english, score2, matches2) && score2 > score) {
label = std::move(label_english);
matches = std::move(matches2);
score = score2;
}
if (score > 90 /*std::numeric_limits<int>::min()*/) {
label = mark_string(label, matches, opt.type, printer_technology);
//label += L" [" + std::to_wstring(score) + L"]"; // add score value
std::string label_u8 = into_u8(label);
std::string label_plain = label_u8;
#ifdef SUPPORTS_MARKUP
boost::replace_all(label_plain, std::string(1, char(ImGui::ColorMarkerStart)), "<b>");
boost::replace_all(label_plain, std::string(1, char(ImGui::ColorMarkerEnd)), "</b>");
#else
boost::erase_all(label_plain, std::string(1, char(ImGui::ColorMarkerStart)));
boost::erase_all(label_plain, std::string(1, char(ImGui::ColorMarkerEnd)));
#endif
if (type == Preset::TYPE_INVALID) {
found.emplace_back(FoundOption{label_plain, label_u8, into_u8(get_tooltip(opt)), i, score});
} else if (type == opt.type) {
found.emplace_back(FoundOption{label_plain, label_u8, into_u8(get_tooltip(opt)), i, score});
}
}
}
if (!full_list) sort_found();
if (search_line != search) search_line = search;
if (search_type != type) search_type = type;
return true;
}
OptionsSearcher::OptionsSearcher() {}
OptionsSearcher::~OptionsSearcher() {}
void OptionsSearcher::init(std::vector<InputInfo> input_values)
{
options.clear();
for (auto i : input_values) append_options(i.config, i.type, i.mode);
sort_options();
search(search_line, true, search_type);
}
void OptionsSearcher::apply(DynamicPrintConfig *config, Preset::Type type, ConfigOptionMode mode)
{
if (options.empty()) return;
options.erase(std::remove_if(options.begin(), options.end(), [type](Option opt) { return opt.type == type; }), options.end());
append_options(config, type, mode);
sort_options();
search(search_line, true, search_type);
}
const Option &OptionsSearcher::get_option(size_t pos_in_filter) const
{
assert(pos_in_filter != size_t(-1) && found[pos_in_filter].option_idx != size_t(-1));
return options[found[pos_in_filter].option_idx];
}
const Option &OptionsSearcher::get_option(const std::string &opt_key, Preset::Type type, int &variant_index) const
{
std::string opt_key2 = opt_key;
if (auto n = opt_key.find('#'); n != std::string::npos) {
variant_index = std::atoi(opt_key.c_str() + n + 1);
opt_key2 = opt_key.substr(0, n);
}
auto it = std::lower_bound(options.begin(), options.end(), Option({boost::nowide::widen(get_key(opt_key2, type))}));
// BBS: return the 0th option when not found in searcher caused by mode difference
// assert(it != options.end());
if (it == options.end()) { variant_index = -2 ; return options[0]; }
if (it->opt_key() == opt_key2) {
variant_index = -1;
} else {
const std::string opt_key3 = opt_key2 + "#";
it = std::lower_bound(it, options.end(), Option({boost::nowide::widen(get_key(opt_key3, type))}));
if (it == options.end() || it->opt_key().compare(0, opt_key3.length(), opt_key3) != 0) {
variant_index = -2; // Not found
return options[0];
}
auto it2 = it;
++it2;
if (it2 != options.end() && it2->opt_key().compare(0, opt_key3.length(), opt_key3) == 0
&& printer_options_with_variant_1.find(opt_key2) == printer_options_with_variant_1.end())
variant_index = -2;
}
return options[it - options.begin()];
}
static Option create_option(const std::string &opt_key, const wxString &label, Preset::Type type, const GroupAndCategory &gc)
{
wxString suffix;
wxString suffix_local;
if (gc.category == "Machine limits") {
//suffix = opt_key.back() == '1' ? L("Stealth") : L("Normal");
suffix = opt_key.back() == '1' ? wxEmptyString : wxEmptyString;
suffix_local = " " + _(suffix);
suffix = " " + suffix;
}
wxString category = gc.category;
if (type == Preset::TYPE_PRINTER && category.Contains("Extruder ")) {
std::string opt_idx = opt_key.substr(opt_key.find("#") + 1);
category = wxString::Format("%s %d", "Extruder", atoi(opt_idx.c_str()) + 1);
}
return Option{boost::nowide::widen(get_key(opt_key, type)),
type,
(label + suffix).ToStdWstring(),
(_(label) + suffix_local).ToStdWstring(),
gc.group.ToStdWstring(),
_(gc.group).ToStdWstring(),
gc.category.ToStdWstring(),
GUI::Tab::translate_category(category, type).ToStdWstring()};
}
Option OptionsSearcher::get_option(const std::string &opt_key, const wxString &label, Preset::Type type) const
{
std::string key = get_key(opt_key, type);
auto it = std::lower_bound(options.begin(), options.end(), Option({boost::nowide::widen(key)}));
// BBS: return the 0th option when not found in searcher caused by mode difference
if (it == options.end()) return options[0];
if (it->key == boost::nowide::widen(key)) return options[it - options.begin()];
if (groups_and_categories.find(key) == groups_and_categories.end()) {
size_t pos = key.find('#');
if (pos == std::string::npos) return options[it - options.begin()];
std::string zero_opt_key = key.substr(0, pos + 1) + "0";
if (groups_and_categories.find(zero_opt_key) == groups_and_categories.end()) return options[it - options.begin()];
return create_option(opt_key, label, type, groups_and_categories.at(zero_opt_key));
}
const GroupAndCategory &gc = groups_and_categories.at(key);
if (gc.group.IsEmpty() || gc.category.IsEmpty()) return options[it - options.begin()];
return create_option(opt_key, label, type, gc);
}
void OptionsSearcher::show_dialog(Preset::Type type, wxWindow *parent, TextInput *input, wxWindow* ssearch_btn)
{
if (parent == nullptr || input == nullptr) return;
auto search_dialog = new SearchDialog(this, type, parent, input, ssearch_btn);
wxPoint pos = input->GetParent()->ClientToScreen(wxPoint(0, 0));
#ifndef __WXGTK__
pos.y += input->GetParent()->GetRect().height;
#else
input->GetParent()->Hide();
#endif
search_dialog->SetPosition(pos);
search_dialog->Popup();
}
void OptionsSearcher::dlg_sys_color_changed()
{
/*if (search_dialog)
search_dialog->on_sys_color_changed();*/
}
void OptionsSearcher::dlg_msw_rescale()
{
if (search_dialog) search_dialog->msw_rescale();
}
void OptionsSearcher::add_key(const std::string &opt_key, Preset::Type type, const wxString &group, const wxString &category)
{
groups_and_categories[get_key(opt_key, type)] = GroupAndCategory{group, category};
}
//------------------------------------------
// SearchItem
//------------------------------------------
SearchItem::SearchItem(wxWindow *parent, wxString text, int index, SearchDialog* sdialog, SearchObjectDialog* search_dialog, wxString tooltip)
: wxWindow(parent, wxID_ANY, wxDefaultPosition, wxSize(parent->GetSize().GetWidth(), 3 * GUI::wxGetApp().em_unit()))
{
m_sdialog = sdialog;
m_search_object_dialog = search_dialog;
m_text = text;
m_index = index;
this->SetToolTip(tooltip);
SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#FFFFFF")));
Bind(wxEVT_ENTER_WINDOW, &SearchItem::on_mouse_enter, this);
Bind(wxEVT_LEAVE_WINDOW, &SearchItem::on_mouse_leave, this);
Bind(wxEVT_LEFT_DOWN, &SearchItem::on_mouse_left_down, this);
Bind(wxEVT_LEFT_UP, &SearchItem::on_mouse_left_up, this);
Bind(wxEVT_PAINT, &SearchItem::OnPaint, this);
}
wxSize SearchItem::DrawTextString(wxDC &dc, const wxString &text, const wxPoint &pt, bool bold)
{
if (bold) {
dc.SetFont(Label::Head_14);
} else {
dc.SetFont(Label::Body_14);
}
dc.SetBackgroundMode(wxTRANSPARENT);
dc.SetTextForeground(StateColor::darkModeColorFor(wxColour("#323A3C")));
dc.DrawText(text, pt);
return dc.GetTextExtent(text);
}
void SearchItem::OnPaint(wxPaintEvent &event)
{
wxPaintDC dc(this);
auto top = 5;
int left = 20;
auto bold_pair = std::vector<std::pair<int, int>>();
auto index = 0;
auto b_first_list = std::vector<int>();
auto b_second_list = std::vector<int>();
auto position = 0;
while ((position = m_text.find("<b>", position)) != wxString::npos) {
b_first_list.push_back(position);
position++;
}
position = 0;
while ((position = m_text.find("</b>", position)) != wxString::npos) {
b_second_list.push_back(position + 3);
position++;
}
if (b_first_list.size() != b_second_list.size()) { return; }
for (auto i = 0; i < b_first_list.size(); i++) {
auto pair = std::make_pair(b_first_list[i], b_second_list[i]);
bold_pair.push_back(pair);
}
//DrawTextString(dc, m_text, wxPoint(left, top), false);
/*if (bold_pair.size() <= 0) {
DrawTextString(dc, m_text, wxPoint(left, top), false);
} else {
auto index = 0;
for (auto i = 0; i < bold_pair.size(); i++) { DrawTextString(dc, m_text.SubString(index, bold_pair[i].second), wxPoint(left, top), true); }
}*/
auto str = wxString("");
for (auto c = 0; c < m_text.length(); c++) {
str = m_text[c];
auto inset = false;
auto pair_index = 0;
for (auto o = 0; o < bold_pair.size(); o++) {
if (c >= bold_pair[o].first && c <= bold_pair[o].second) {
pair_index = o;
inset = true;
break;
}
}
if (!inset) {
left += DrawTextString(dc, str, wxPoint(left, top), false).GetWidth();
} else {
//str = str.erase(bold_pair[pair_index].first, 3);
//str = str.erase(bold_pair[pair_index].second, 4);
if (c - bold_pair[pair_index].first >= 3 && bold_pair[pair_index].second - c > 3) {
left += DrawTextString(dc, str, wxPoint(left, top), true).GetWidth();
}
}
}
}
void SearchItem::on_mouse_enter(wxMouseEvent &evt)
{
SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#BFE1DE"))); // ORCA color with %25 opacity
Refresh();
}
void SearchItem::on_mouse_leave(wxMouseEvent &evt)
{
SetBackgroundColour(StateColor::darkModeColorFor(wxColour(255, 255, 255)));
Refresh();
}
void SearchItem::on_mouse_left_down(wxMouseEvent &evt)
{
SetBackgroundColour(StateColor::darkModeColorFor(wxColour("#BFE1DE"))); // ORCA color with %25 opacity
Refresh();
}
void SearchItem::on_mouse_left_up(wxMouseEvent &evt)
{
//if (m_sdialog->prevent_list_events) return;
// if (wxGetMouseState().LeftIsDown())
if (m_sdialog) {
m_sdialog->Die();
wxCommandEvent event(wxCUSTOMEVT_JUMP_TO_OPTION);
event.SetInt(m_index);
wxPostEvent(GUI::wxGetApp().plater(), event);
}
if (m_search_object_dialog) {
m_search_object_dialog->Die();
wxCommandEvent event(wxCUSTOMEVT_JUMP_TO_OBJECT);
event.SetClientData(m_item);
wxPostEvent(GUI::wxGetApp().plater(), event);
}
}
//------------------------------------------
// SearchDialog
//------------------------------------------
static const std::map<const char, int> icon_idxs = {
{ImGui::PrintIconMarker, 0}, {ImGui::PrinterIconMarker, 1}, {ImGui::PrinterSlaIconMarker, 2}, {ImGui::FilamentIconMarker, 3}, {ImGui::MaterialIconMarker, 4},
};
SearchDialog::SearchDialog(OptionsSearcher *searcher, Preset::Type type, wxWindow *parent, TextInput *input, wxWindow *search_btn)
: PopupWindow(parent, wxBORDER_NONE | wxPU_CONTAINS_CONTROLS), searcher(searcher)
{
m_event_tag = parent;
search_line = input;
search_type = type;
m_search_item_tag = search_btn;
// set border color
Freeze();
SetBackgroundColour(wxColour(238, 238, 238));
em = GUI::wxGetApp().em_unit();
m_bg_colour = wxColour(255, 255, 255);
m_thumb_color = wxColour(196, 196, 196);
SetFont(GUI::wxGetApp().normal_font());
SetSizeHints(wxDefaultSize, wxDefaultSize);
m_sizer_border = new wxBoxSizer(wxVERTICAL);
m_sizer_main = new wxBoxSizer(wxVERTICAL);
m_sizer_body = new wxBoxSizer(wxVERTICAL);
// border
m_border_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(POPUP_WIDTH * em, POPUP_HEIGHT * em), wxTAB_TRAVERSAL);
m_border_panel->SetBackgroundColour(m_bg_colour);
// client
m_client_panel = new wxPanel(m_border_panel, wxID_ANY, wxDefaultPosition, wxSize(POPUP_WIDTH * em, POPUP_HEIGHT * em), wxTAB_TRAVERSAL);
m_client_panel->SetBackgroundColour(m_bg_colour);
// search line
//search_line = new wxTextCtrl(m_client_panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
#ifdef __WXGTK__
search_line = new TextInput(m_client_panel, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0);
search_line->SetBackgroundColour(wxColour(238, 238, 238));
search_line->SetForegroundColour(wxColour(43, 52, 54));
search_line->SetFont(GUI::wxGetApp().bold_font());
#endif
search_line->Bind(wxEVT_TEXT, &SearchDialog::OnInputText, this);
search_line->Bind(wxEVT_LEFT_UP, &SearchDialog::OnLeftUpInTextCtrl, this);
search_line2 = search_line->GetTextCtrl();
// scroll window
m_scrolledWindow = new ScrolledWindow(m_client_panel, wxID_ANY, wxDefaultPosition, wxSize(POPUP_WIDTH * em - (em + em /2), POPUP_HEIGHT * em), wxVSCROLL, 6, 6);
m_scrolledWindow->SetMarginColor(m_bg_colour);
m_scrolledWindow->SetScrollbarColor(m_thumb_color);
m_scrolledWindow->SetBackgroundColour(m_bg_colour);
auto m_listsizer = new wxBoxSizer(wxVERTICAL);
auto m_listPanel = new wxWindow(m_scrolledWindow->GetPanel(), -1);
m_listPanel->SetBackgroundColour(m_bg_colour);
m_listPanel->SetSize(wxSize(m_scrolledWindow->GetSize().GetWidth(), -1));
m_listPanel->SetSizer(m_listsizer);
m_listPanel->Fit();
m_scrolledWindow->SetScrollbars(1, 1, 0, m_listPanel->GetSize().GetHeight());
#ifdef __WXGTK__
m_sizer_body->Add(search_line, 0, wxEXPAND | wxALL, em / 2);
search_line = input;
#endif
m_sizer_body->Add(m_scrolledWindow, 0, wxEXPAND | wxALL, em);
m_client_panel->SetSizer(m_sizer_body);
m_client_panel->Layout();
m_sizer_body->Fit(m_client_panel);
m_sizer_main->Add(m_client_panel, 1, wxEXPAND, 0);
m_border_panel->SetSizer(m_sizer_main);
m_border_panel->Layout();
m_sizer_border->Add(m_border_panel, 1, wxEXPAND | wxALL, 1);
SetSizer(m_sizer_border);
Layout();
m_sizer_border->Fit(this);
Thaw();
// check_category->Bind(wxEVT_CHECKBOX, &SearchDialog::OnCheck, this);
// Bind(wxEVT_MOTION, &SearchDialog::OnMotion, this);
// Bind(wxEVT_LEFT_DOWN, &SearchDialog::OnLeftDown, this);
// SetSizer(topSizer);
// topSizer->SetSizeHints(this);
GUI::wxGetApp().UpdateDarkUIWin(this);
}
SearchDialog::~SearchDialog() {}
void SearchDialog::Popup(wxWindow *focus /*= nullptr*/)
{
/* const std::string& line = searcher->search_string();
search_line->SetValue(line.empty() ? default_string : from_u8(line));
update_list();
const OptionViewParameters& params = searcher->view_params;
check_category->SetValue(params.category);*/
//const std::string &line = searcher->search_string();
//search_line->SetValue(line.empty() ? default_string : from_u8(line));
search_line2->SetValue(wxString(""));
//const std::string &line = searcher->search_string();
//searcher->search(into_u8(line), true);
PopupWindow::Popup(focus);
search_line2->SetFocus();
update_list();
}
#ifdef __WXMSW__
void SearchDialog::MSWDismissUnfocusedPopup()
{
Dismiss();
OnDismiss();
}
#endif // __WXMSW__
void SearchDialog::OnDismiss() { }
void SearchDialog::Dismiss()
{
auto focus_window = wxWindow::FindFocus();
if (!focus_window) {
Die();
return;
}
#if defined(__WXGTK__)
// On Wayland, wxGetMousePosition() returns unreliable global coords.
// Rely on focus tracking instead: if focus moved to a window outside
// this dialog and its related controls, dismiss.
if (Slic3r::GUI::is_running_on_wayland()) {
if (focus_left_popup(this, focus_window, m_event_tag, m_search_item_tag, search_line)) {
Die();
}
return;
}
#endif
auto pos = wxGetMousePosition();
if (!m_event_tag->GetScreenRect().Contains(pos) && !this->GetScreenRect().Contains(pos) && !m_search_item_tag->GetScreenRect().Contains(pos)) {
Die();
}
}
void SearchDialog::Die()
{
PopupWindow::Dismiss();
wxCommandEvent event(wxCUSTOMEVT_EXIT_SEARCH);
wxPostEvent(search_line, event);
}
void SearchDialog::OnInputText(wxCommandEvent &)
{
search_line2->SetInsertionPointEnd();
wxString input_string = search_line2->GetValue();
if (input_string == wxEmptyString) input_string.Clear();
searcher->search(into_u8(input_string), true, search_type);
update_list();
}
void SearchDialog::OnLeftUpInTextCtrl(wxEvent &event)
{
if (search_line2->GetValue() == wxEmptyString) search_line2->SetValue("");
event.Skip();
}
void SearchDialog::update_list()
{
#ifndef __WXGTK__
Freeze();
#endif
m_scrolledWindow->Destroy();
m_scrolledWindow = new ScrolledWindow(m_client_panel, wxID_ANY, wxDefaultPosition, wxSize(POPUP_WIDTH * em - (em + em / 2), POPUP_HEIGHT * em - em), wxVSCROLL, 6, 6);
m_scrolledWindow->SetMarginColor(StateColor::darkModeColorFor(m_bg_colour));
m_scrolledWindow->SetScrollbarColor(StateColor::darkModeColorFor(m_thumb_color));
m_scrolledWindow->SetBackgroundColour(StateColor::darkModeColorFor(m_bg_colour));
auto m_listsizer = new wxBoxSizer(wxVERTICAL);
auto m_listPanel = new wxWindow(m_scrolledWindow->GetPanel(), -1);
m_listPanel->SetBackgroundColour(StateColor::darkModeColorFor(m_bg_colour));
m_listPanel->SetSize(wxSize(m_scrolledWindow->GetSize().GetWidth(), -1));
const std::vector<FoundOption> &filters = searcher->found_options();
auto index = 0;
for (const FoundOption &item : filters) {
wxString str = from_u8(item.label).Remove(0, 1);
auto tmp = new SearchItem(m_listPanel, str, index, this);
m_listsizer->Add(tmp, 0, wxEXPAND, 0);
index++;
}
m_listPanel->SetSizer(m_listsizer);
m_listPanel->Fit();
m_scrolledWindow->SetScrollbars(1, 1, 0, m_listPanel->GetSize().GetHeight());
m_sizer_body->Add(m_scrolledWindow, 0, wxEXPAND | wxALL, em);
m_sizer_body->Fit(m_client_panel);
m_sizer_body->Layout();
#ifndef __WXGTK__
Thaw();
#endif
}
void SearchDialog::msw_rescale()
{
}
// ----------------------------------------------------------------------------
// SearchListModel
// ----------------------------------------------------------------------------
SearchListModel::SearchListModel(wxWindow *parent) : wxDataViewVirtualListModel(0)
{
int icon_id = 0;
for (const std::string icon : {"cog", "printer", "printer", "spool", "blank_16"}) m_icon[icon_id++] = ScalableBitmap(parent, icon);
}
void SearchListModel::Clear()
{
m_values.clear();
Reset(0);
}
void SearchListModel::Prepend(const std::string &label)
{
const char icon_c = label.at(0);
int icon_idx = icon_idxs.at(icon_c);
wxString str = from_u8(label).Remove(0, 1);
m_values.emplace_back(str, icon_idx);
RowPrepended();
}
void SearchListModel::msw_rescale()
{
for (ScalableBitmap &bmp : m_icon) bmp.msw_rescale();
}
wxString SearchListModel::GetColumnType(unsigned int col) const
{
if (col == colIcon) return "wxBitmap";
return "string";
}
void SearchListModel::GetValueByRow(wxVariant &variant, unsigned int row, unsigned int col) const
{
switch (col) {
case colIcon: variant << m_icon[m_values[row].second].bmp(); break;
case colMarkedText: variant = m_values[row].first; break;
case colMax: wxFAIL_MSG("invalid column");
default: break;
}
}
SearchObjectDialog::SearchObjectDialog(GUI::ObjectList* object_list, wxWindow* parent, TextInput* input)
: PopupWindow(parent, wxBORDER_NONE | wxPU_CONTAINS_CONTROLS), m_object_list(object_list)
{
search_line = input;
Freeze();
SetBackgroundColour(wxColour(238, 238, 238));
em = GUI::wxGetApp().em_unit();
m_bg_color = wxColour(255, 255, 255);
m_thumb_color = wxColour(196, 196, 196);
SetFont(GUI::wxGetApp().normal_font());
SetSizeHints(wxDefaultSize, wxDefaultSize);
m_sizer_border = new wxBoxSizer(wxVERTICAL);
m_sizer_main = new wxBoxSizer(wxVERTICAL);
m_sizer_body = new wxBoxSizer(wxVERTICAL);
// border
m_border_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(POPUP_WIDTH * em, POPUP_HEIGHT * em), wxTAB_TRAVERSAL);
m_border_panel->SetBackgroundColour(m_bg_color);
// client
m_client_panel = new wxPanel(m_border_panel, wxID_ANY, wxDefaultPosition, wxSize(POPUP_WIDTH * em, POPUP_HEIGHT * em), wxTAB_TRAVERSAL);
m_client_panel->SetBackgroundColour(m_bg_color);
// search line
#ifdef __WXGTK__
search_line = new TextInput(m_client_panel, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0);
search_line->SetBackgroundColour(wxColour(238, 238, 238));
search_line->SetForegroundColour(wxColour(43, 52, 54));
search_line->SetFont(GUI::wxGetApp().bold_font());
#endif
search_line->Bind(wxEVT_TEXT, &SearchObjectDialog::OnInputText, this);
search_line->Bind(wxEVT_LEFT_UP, &SearchObjectDialog::OnLeftUpInTextCtrl, this);
search_line2 = search_line->GetTextCtrl();
// scroll window
m_scrolledWindow = new ScrolledWindow(m_client_panel, wxID_ANY, wxDefaultPosition, wxSize(POPUP_WIDTH * em - (em + em / 2), POPUP_HEIGHT * em), wxVSCROLL, 6, 6);
m_scrolledWindow->SetMarginColor(m_bg_color);
m_scrolledWindow->SetScrollbarColor(m_thumb_color);
m_scrolledWindow->SetBackgroundColour(m_bg_color);
auto m_listsizer = new wxBoxSizer(wxVERTICAL);
auto m_listPanel = new wxWindow(m_scrolledWindow->GetPanel(), -1);
m_listPanel->SetBackgroundColour(m_bg_color);
m_listPanel->SetSize(wxSize(m_scrolledWindow->GetSize().GetWidth(), -1));
m_listPanel->SetSizer(m_listsizer);
m_listPanel->Fit();
m_scrolledWindow->SetScrollbars(1, 1, 0, m_listPanel->GetSize().GetHeight());
#ifdef __WXGTK__
m_sizer_body->Add(search_line, 0, wxEXPAND | wxALL, em / 2);
search_line = input;
#endif
m_sizer_body->Add(m_scrolledWindow, 0, wxEXPAND | wxALL, em);
m_client_panel->SetSizer(m_sizer_body);
m_client_panel->Layout();
m_sizer_body->Fit(m_client_panel);
m_sizer_main->Add(m_client_panel, 1, wxEXPAND, 0);
m_border_panel->SetSizer(m_sizer_main);
m_border_panel->Layout();
m_sizer_border->Add(m_border_panel, 1, wxEXPAND | wxALL, 1);
SetSizer(m_sizer_border);
Layout();
m_sizer_border->Fit(this);
Thaw();
GUI::wxGetApp().UpdateDarkUIWin(this);
}
SearchObjectDialog::~SearchObjectDialog() {}
void SearchObjectDialog::Popup(wxWindow *focus /*= nullptr*/)
{
if (m_is_dismissing || this->IsShown()) {
return;
}
search_line2->SetValue(wxString(""));
#ifdef __WXOSX__
// On macOS we need to remove the focus from the text input before popping up the
// dropdown list, otherwise the text input won't be usable
m_object_list->SetFocus();
#endif
PopupWindow::Popup(focus);
search_line2->SetFocus();
m_object_list->assembly_plate_object_name();
m_object_list->GetModel()->search_object(wxEmptyString);
update_list();
}
#ifdef __WXMSW__
void SearchObjectDialog::MSWDismissUnfocusedPopup()
{
Dismiss();
OnDismiss();
}
#endif // __WXMSW__
void SearchObjectDialog::OnDismiss() {}
void SearchObjectDialog::Dismiss()
{
auto focus_window = wxWindow::FindFocus();
if (!focus_window) {
Die();
return;
}
#if defined(__WXGTK__)
// On Wayland, wxGetMousePosition() returns unreliable global coords.
// Rely on focus tracking instead: if focus moved to a window outside
// this dialog and its related controls, dismiss.
if (Slic3r::GUI::is_running_on_wayland()) {
if (focus_left_popup(this, focus_window, search_line)) {
Die();
}
return;
}
#endif
auto pos = wxGetMousePosition();
if (!search_line->GetScreenRect().Contains(pos) && !this->GetScreenRect().Contains(pos)) {
Die();
}
}
void SearchObjectDialog::Die()
{
m_is_dismissing = true;
m_object_list->SetFocus();
PopupWindow::Dismiss();
wxCommandEvent event(wxCUSTOMEVT_EXIT_SEARCH);
wxPostEvent(m_object_list, event);
m_is_dismissing = false;
}
void SearchObjectDialog::OnInputText(wxCommandEvent&)
{
search_line2->SetInsertionPointEnd();
wxString input_string = search_line2->GetValue();
if (input_string == wxEmptyString)
input_string.Clear();
m_object_list->assembly_plate_object_name();
m_object_list->GetModel()->search_object(input_string);
update_list();
}
void SearchObjectDialog::OnLeftUpInTextCtrl(wxEvent& event)
{
if (search_line2->GetValue() == wxEmptyString)
search_line2->SetValue("");
event.Skip();
}
void SearchObjectDialog::update_list()
{
#ifndef __WXGTK__
Freeze();
#endif
m_scrolledWindow->Destroy();
m_scrolledWindow = new ScrolledWindow(m_client_panel, wxID_ANY, wxDefaultPosition, wxSize(POPUP_WIDTH * em - (em + em / 2), POPUP_HEIGHT * em - em), wxVSCROLL, 6, 6);
m_scrolledWindow->SetMarginColor(StateColor::darkModeColorFor(m_bg_color));
m_scrolledWindow->SetScrollbarColor(StateColor::darkModeColorFor(m_thumb_color));
m_scrolledWindow->SetBackgroundColour(StateColor::darkModeColorFor(m_bg_color));
auto m_listsizer = new wxBoxSizer(wxVERTICAL);
auto m_listPanel = new wxWindow(m_scrolledWindow->GetPanel(), -1);
m_listPanel->SetBackgroundColour(StateColor::darkModeColorFor(m_bg_color));
m_listPanel->SetSize(wxSize(m_scrolledWindow->GetSize().GetWidth(), -1));
const std::vector<std::tuple<GUI::ObjectDataViewModelNode*, wxString, wxString>>& found = m_object_list->GetModel()->get_found_list();
auto index = 0;
for (const auto& [model_node, name, tip] : found) {
auto tmp = new SearchItem(m_listPanel, name, index, nullptr, this, tip);
tmp->m_item = model_node;
m_listsizer->Add(tmp, 0, wxEXPAND, 0);
index++;
}
m_listPanel->SetSizer(m_listsizer);
m_listPanel->Fit();
m_scrolledWindow->SetScrollbars(1, 1, 0, m_listPanel->GetSize().GetHeight());
m_sizer_body->Add(m_scrolledWindow, 0, wxEXPAND | wxALL, em);
m_sizer_body->Fit(m_client_panel);
m_sizer_body->Layout();
#ifndef __WXGTK__
Thaw();
#endif
}
} // namespace Search
} // namespace Slic3r