Fix object search bar on macOS and Linux (#9473)

* Remove unused search code

* Reimplement the object search bar (SoftFever/OrcaSlicer#7438)

* Fix result list when search text is empty

* Prevent infinite focus loop

* Update layout on Linux

* Fix focus on macOS

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
This commit is contained in:
Noisyfox 2025-05-01 15:21:11 +08:00 committed by GitHub
parent 940800b059
commit 66830d2344
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 111 additions and 183 deletions

View file

@ -380,7 +380,6 @@ struct Sidebar::priv
~priv();
void show_preset_comboboxes();
void on_search_update();
void jump_to_object(ObjectDataViewModelNode* item);
void can_search();
@ -423,15 +422,6 @@ void Sidebar::priv::show_preset_comboboxes()
scrolled->Refresh();
}
void Sidebar::priv::on_search_update()
{
m_object_list->assembly_plate_object_name();
wxString search_text = m_search_item->GetTextCtrl()->GetValue();
m_object_list->GetModel()->search_object(search_text);
dia->update_list();
}
void Sidebar::priv::jump_to_object(ObjectDataViewModelNode* item)
{
m_object_list->selected_object(item);
@ -1108,23 +1098,21 @@ Sidebar::Sidebar(Plater *parent)
text_ctrl->SetSize(wxSize(-1, FromDIP(16))); // Centers text vertically
text_ctrl->Bind(wxEVT_SET_FOCUS, [this](wxFocusEvent& e) {
this->p->on_search_update();
if (p->dia->IsShown()) {
e.Skip();
return;
}
p->m_search_bar->SetBorderColor(wxColour("#009688"));
wxPoint pos = this->p->m_search_bar->ClientToScreen(wxPoint(0, 0));
#ifndef __WXGTK__
pos.y += this->p->m_search_bar->GetRect().height;
#else
this->p->m_search_item->Enable(false);
#endif
p->dia->SetPosition(pos);
p->dia->Popup();
e.Skip(); // required to show caret
});
text_ctrl->Bind(wxEVT_COMMAND_TEXT_UPDATED, [this](wxCommandEvent&) {
this->p->on_search_update();
});
text_ctrl->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) {
p->dia->Dismiss();
p->m_search_bar->SetBorderColor(wxColour("#CECECE"));
p->m_search_item->GetTextCtrl()->SetValue(""); // reset value when loose focus
e.Skip();
});
auto search_sizer = new wxBoxSizer(wxHORIZONTAL);
search_sizer->Add(new wxWindow(p->m_search_bar, wxID_ANY, wxDefaultPosition, wxSize(0, 0)), 0, wxEXPAND|wxLEFT|wxRIGHT, FromDIP(1));
@ -1134,6 +1122,13 @@ Sidebar::Sidebar(Plater *parent)
search_sizer->Fit(p->m_search_bar);
p->m_object_list = new ObjectList(p->scrolled);
p->m_object_list->Bind(wxCUSTOMEVT_EXIT_SEARCH, [this](wxCommandEvent&) {
#ifdef __WXGTK__
this->p->m_search_item->Enable(true);
#endif
this->p->m_search_bar->SetBorderColor(wxColour("#CECECE"));
this->p->m_search_item->GetTextCtrl()->SetValue(""); // reset value when close
});
p->sizer_params->Add(p->m_search_bar, 0, wxALL | wxEXPAND, 0);
p->sizer_params->Add(p->m_object_list, 1, wxEXPAND | wxTOP, 0);
@ -1143,7 +1138,7 @@ Sidebar::Sidebar(Plater *parent)
// Frequently Object Settings
p->object_settings = new ObjectSettings(p->scrolled);
p->dia = new Search::SearchObjectDialog(p->m_object_list, text_ctrl);
p->dia = new Search::SearchObjectDialog(p->m_object_list, p->scrolled->GetParent(), p->m_search_item);
#if !NEW_OBJECT_SETTING
p->object_settings->Hide();
p->sizer_params->Add(p->object_settings->get_sizer(), 0, wxEXPAND | wxTOP, 5 * em / 10);

View file

@ -523,7 +523,7 @@ void SearchItem::on_mouse_left_up(wxMouseEvent &evt)
}
if (m_search_object_dialog) {
m_search_object_dialog->Dismiss();
m_search_object_dialog->Die();
wxCommandEvent event(wxCUSTOMEVT_JUMP_TO_OBJECT);
event.SetClientData(m_item);
wxPostEvent(GUI::wxGetApp().plater(), event);
@ -553,9 +553,7 @@ SearchDialog::SearchDialog(OptionsSearcher *searcher, Preset::Type type, wxWindo
em = GUI::wxGetApp().em_unit();
m_text_color = wxColour(38, 46, 48);
m_bg_colour = wxColour(255, 255, 255);
m_hover_colour = wxColour(248, 248, 248);
m_thumb_color = wxColour(196, 196, 196);
SetFont(GUI::wxGetApp().normal_font());
@ -582,10 +580,8 @@ SearchDialog::SearchDialog(OptionsSearcher *searcher, Preset::Type type, wxWindo
search_line->SetFont(GUI::wxGetApp().bold_font());
#endif
// default_string = _L("Enter a search term");
search_line->Bind(wxEVT_TEXT, &SearchDialog::OnInputText, this);
search_line->Bind(wxEVT_LEFT_UP, &SearchDialog::OnLeftUpInTextCtrl, this);
search_line->Bind(wxEVT_KEY_DOWN, &SearchDialog::OnKeyDown, this);
search_line2 = search_line->GetTextCtrl();
// scroll window
@ -681,76 +677,21 @@ void SearchDialog::Die()
wxPostEvent(search_line, event);
}
void SearchDialog::ProcessSelection(wxDataViewItem selection)
{
if (!selection.IsOk()) return;
// this->EndModal(wxID_CLOSE);
// If call GUI::wxGetApp().sidebar.jump_to_option() directly from here,
// then mainframe will not have focus and found option will not be "active" (have cursor) as a result
// SearchDialog have to be closed and have to lose a focus
// and only after that jump_to_option() function can be called
// So, post event to plater:
wxCommandEvent event(wxCUSTOMEVT_JUMP_TO_OPTION);
event.SetInt(search_list_model->GetRow(selection));
wxPostEvent(GUI::wxGetApp().plater(), event);
}
void SearchDialog::OnInputText(wxCommandEvent &)
{
search_line2->SetInsertionPointEnd();
wxString input_string = search_line2->GetValue();
if (input_string == default_string) input_string.Clear();
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() == default_string) search_line2->SetValue("");
if (search_line2->GetValue() == wxEmptyString) search_line2->SetValue("");
event.Skip();
}
void SearchDialog::OnKeyDown(wxKeyEvent &event)
{
event.Skip();
/* int key = event.GetKeyCode();
if (key == WXK_UP || key == WXK_DOWN)
{
search_list->SetFocus();
auto item = search_list->GetSelection();
if (item.IsOk()) {
unsigned selection = search_list_model->GetRow(item);
if (key == WXK_UP && selection > 0)
selection--;
if (key == WXK_DOWN && selection < unsigned(search_list_model->GetCount() - 1))
selection++;
prevent_list_events = true;
search_list->Select(search_list_model->GetItem(selection));
prevent_list_events = false;
}
}
else if (key == WXK_NUMPAD_ENTER || key == WXK_RETURN)
ProcessSelection(search_list->GetSelection());
else
event.Skip();*/
}
void SearchDialog::OnActivate(wxDataViewEvent &event) { ProcessSelection(event.GetItem()); }
void SearchDialog::OnSelect(wxDataViewEvent &event)
{
if (prevent_list_events) return;
// if (wxGetMouseState().LeftIsDown())
// ProcessSelection(search_list->GetSelection());
}
void SearchDialog::update_list()
{
#ifndef __WXGTK__
@ -787,77 +728,12 @@ void SearchDialog::update_list()
#ifndef __WXGTK__
Thaw();
#endif
// Under OSX model->Clear invoke wxEVT_DATAVIEW_SELECTION_CHANGED, so
// set prevent_list_events to true already here
// prevent_list_events = true;
// search_list_model->Clear();
/* const std::vector<FoundOption> &filters = searcher->found_options();
for (const FoundOption &item : filters)
search_list_model->Prepend(item.label);*/
// select first item, if search_list
/*if (search_list_model->GetCount() > 0)
search_list->Select(search_list_model->GetItem(0));
prevent_list_events = false;*/
// Refresh();
}
void SearchDialog::OnCheck(wxCommandEvent &event)
{
OptionViewParameters &params = searcher->view_params;
params.category = check_category->GetValue();
searcher->search();
update_list();
}
void SearchDialog::OnMotion(wxMouseEvent &event)
{
wxDataViewItem item;
wxWindow * win = this;
// search_list->HitTest(wxGetMousePosition() - win->GetScreenPosition(), item, col);
// search_list->Select(item);
event.Skip();
}
void SearchDialog::OnLeftDown(wxMouseEvent &event) { ProcessSelection(search_list->GetSelection()); }
void SearchDialog::msw_rescale()
{
/* const int &em = GUI::wxGetApp().em_unit();
search_list_model->msw_rescale();
search_list->GetColumn(SearchListModel::colIcon )->SetWidth(3 * em);
search_list->GetColumn(SearchListModel::colMarkedText)->SetWidth(45 * em);
msw_buttons_rescale(this, em, { wxID_CANCEL });
const wxSize& size = wxSize(40 * em, 30 * em);
SetMinSize(size);
Fit();
Refresh();*/
}
// void SearchDialog::on_sys_color_changed()
//{
//#ifdef _WIN32
// GUI::wxGetApp().UpdateAllStaticTextDarkUI(this);
// GUI::wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_CANCEL, this)), true);
// for (wxWindow* win : std::vector<wxWindow*> {search_line, search_list, check_category, check_english})
// if (win) GUI::wxGetApp().UpdateDarkUI(win);
//#endif
//
// // msw_rescale updates just icons, so use it
// search_list_model->msw_rescale();
//
// Refresh();
//}
// ----------------------------------------------------------------------------
// SearchListModel
// ----------------------------------------------------------------------------
@ -906,15 +782,16 @@ void SearchListModel::GetValueByRow(wxVariant &variant, unsigned int row, unsign
}
}
SearchObjectDialog::SearchObjectDialog(GUI::ObjectList* object_list, wxWindow* parent)
: PopupWindow(parent, wxBORDER_NONE), m_object_list(object_list)
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_text_color = wxColour(38, 46, 48);
m_bg_color = wxColour(255, 255, 255);
m_thumb_color = wxColour(196, 196, 196);
@ -933,6 +810,19 @@ SearchObjectDialog::SearchObjectDialog(GUI::ObjectList* object_list, wxWindow* p
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);
@ -947,6 +837,10 @@ SearchObjectDialog::SearchObjectDialog(GUI::ObjectList* object_list, wxWindow* p
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);
@ -970,15 +864,70 @@ SearchObjectDialog::~SearchObjectDialog() {}
void SearchObjectDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
{
update_list();
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();
search_line2->SetFocus();
m_object_list->assembly_plate_object_name();
m_object_list->GetModel()->search_object(wxEmptyString);
update_list();
}
void SearchObjectDialog::MSWDismissUnfocusedPopup()
{
Dismiss();
OnDismiss();
}
void SearchObjectDialog::OnDismiss() {}
void SearchObjectDialog::Dismiss()
{
auto focus_window = this->GetParent()->HasFocus();
auto pos = wxGetMousePosition();
auto focus_window = wxWindow::FindFocus();
if (!focus_window)
PopupWindow::Dismiss();
Die();
else 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()

View file

@ -184,25 +184,15 @@ class SearchListModel;
class SearchDialog : public PopupWindow
{
public:
wxString search_str;
wxString default_string;
bool prevent_list_events{false};
wxColour m_text_color;
wxColour m_bg_colour;
wxColour m_hover_colour;
wxColour m_bold_colour;
wxColour m_thumb_color;
wxBoxSizer *m_sizer_body{nullptr};
wxBoxSizer *m_sizer_main{nullptr};
wxBoxSizer *m_sizer_border{nullptr};
wxBoxSizer *m_listsizer{nullptr};
wxWindow *m_border_panel{nullptr};
wxWindow *m_client_panel{nullptr};
wxWindow *m_listPanel{nullptr};
wxWindow *m_event_tag{nullptr};
wxWindow *m_search_item_tag{nullptr};
@ -215,23 +205,12 @@ public:
wxTextCtrl * search_line2{nullptr};
Preset::Type search_type = Preset::TYPE_INVALID;
wxDataViewCtrl * search_list{nullptr};
ScrolledWindow * m_scrolledWindow{nullptr};
SearchListModel *search_list_model{nullptr};
wxCheckBox * check_category{nullptr};
OptionsSearcher *searcher{nullptr};
void OnInputText(wxCommandEvent &event);
void OnLeftUpInTextCtrl(wxEvent &event);
void OnKeyDown(wxKeyEvent &event);
void OnActivate(wxDataViewEvent &event);
void OnSelect(wxDataViewEvent &event);
void OnCheck(wxCommandEvent &event);
void OnMotion(wxMouseEvent &event);
void OnLeftDown(wxMouseEvent &event);
void update_list();
@ -244,12 +223,8 @@ public:
void OnDismiss();
void Dismiss();
void Die();
void ProcessSelection(wxDataViewItem selection);
void msw_rescale();
// void on_sys_color_changed() override;
protected:
// void on_dpi_changed(const wxRect& suggested_rect) override { msw_rescale(); }
};
// ----------------------------------------------------------------------------
@ -284,11 +259,17 @@ public:
class SearchObjectDialog : public PopupWindow
{
public:
SearchObjectDialog(GUI::ObjectList* object_list, wxWindow* parent);
SearchObjectDialog(GUI::ObjectList* object_list, wxWindow* parent, TextInput* input);
~SearchObjectDialog();
void MSWDismissUnfocusedPopup();
void Popup(wxPoint position = wxDefaultPosition);
void OnDismiss();
void Dismiss();
void Die();
void OnInputText(wxCommandEvent& event);
void OnLeftUpInTextCtrl(wxEvent& event);
void update_list();
@ -299,12 +280,13 @@ public:
const int POPUP_WIDTH = 41;
const int POPUP_HEIGHT = 45;
TextInput* search_line{nullptr};
wxTextCtrl* search_line2{nullptr};
ScrolledWindow* m_scrolledWindow{ nullptr };
wxColour m_text_color;
wxColour m_bg_color;
wxColour m_thumb_color;
wxColour m_bold_color;
wxBoxSizer* m_sizer_body{ nullptr };
wxBoxSizer* m_sizer_main{ nullptr };
@ -312,7 +294,9 @@ public:
wxWindow* m_border_panel{ nullptr };
wxWindow* m_client_panel{ nullptr };
wxWindow* m_listPanel{ nullptr };
private:
bool m_is_dismissing{ false };
};
} // namespace Search