From 1375c454fbf40a5c698f8e95d9dc4f85d0835ebd Mon Sep 17 00:00:00 2001 From: Mathias Buhr Date: Mon, 7 Mar 2016 23:56:03 +0100 Subject: [PATCH] Implements filtering in bindings editor --- gtk2_ardour/icons/search.png | Bin 0 -> 1026 bytes gtk2_ardour/icons/search.svg | 94 +++++++++++++++ gtk2_ardour/keyeditor.cc | 164 +++++++++++++++++++-------- gtk2_ardour/keyeditor.h | 24 ++-- libs/gtkmm2ext/gtkmm2ext/searchbar.h | 32 ++++++ libs/gtkmm2ext/searchbar.cc | 93 +++++++++++++++ libs/gtkmm2ext/wscript | 1 + 7 files changed, 351 insertions(+), 57 deletions(-) create mode 100644 gtk2_ardour/icons/search.png create mode 100644 gtk2_ardour/icons/search.svg create mode 100644 libs/gtkmm2ext/gtkmm2ext/searchbar.h create mode 100644 libs/gtkmm2ext/searchbar.cc diff --git a/gtk2_ardour/icons/search.png b/gtk2_ardour/icons/search.png new file mode 100644 index 0000000000000000000000000000000000000000..0a0db844859d0587f5281b126176f5d952773a60 GIT binary patch literal 1026 zcmV+d1pWJoP)MIDmB4X3oc5#(mx=7MGM{3jZL7G4qa6$)kW3LqO&nr7#BfnqNS0jiJ14}o;!=- zJ)W94Lpuj9kNbG2$ESw}FZY%lc(# zXehC`xXAH%9LyXnXpDh~KtzH$0E~`~(!s%jv2FXqbUOXb?(S~ap76lHz$aSk(OfR4 zm>F7Y5D}OeN~tS@nS-H}!qn6hi^$zbBvR;=0UKjxEX&ekV`Fe#H>kP)#Y7Z<;Q;`{ z!^4QjU?>)g1&fET zuIoZ;eWU(91^||2K|}_N#UghD-XJ0)B5)ihXlS?$BGMBHVRYlB4*>Z0?Ck7azwTOV zD5ZjO_^9W30pw!<&~CQ@piwLqr7IvJe;ytlzSHS+R3s7!06zoZ#Tn3AcLk1)jx>O$ zxAoxh#l?lHR;yQ<96rH*KRnM1G68_&<71qkpDO@A^#r2P=nq8n_tw^y?sPg`0oQed z;dx$=(dOnRh^WEL-}eNTmzO&LzBoBK;kC6j<1Z54P2mju#n;!@(P%UP;4Rm6v%LTX z06RN7&n71)8_j0(QMFo?{{DVWBoaaW!#k%|t6_C@)zs^Cf9FJqXf~6{tXC?P+I8T{ z_R8n;A2aiJ#+bX7W$9!xsp9cC0HD=spJr~%+jV@xdc`k9D4D3{AmdIA8j zu&@wmwOXGN(F`-+H^vMC01=&Nt$!z?$FA#^`}+E_BJwMMx5Ee#JzQB?`J*S$CH!Y@ zoUr-%`S)Iiyb5q@ybSr@007L*&Ardes{rnV5fS<5wE(>kBKoWAAMn3Kxm + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/gtk2_ardour/keyeditor.cc b/gtk2_ardour/keyeditor.cc index 28005a0401..d7a92bd630 100644 --- a/gtk2_ardour/keyeditor.cc +++ b/gtk2_ardour/keyeditor.cc @@ -23,6 +23,8 @@ #include +#include + #include #include #include @@ -66,15 +68,21 @@ KeyEditor::KeyEditor () : ArdourWindow (_("Key Bindings")) , unbind_button (_("Remove shortcut")) , unbind_box (BUTTONBOX_END) + , filter_entry (_("Search...")) + , filter_string("") , sort_column(0) , sort_type(Gtk::SORT_ASCENDING) { - last_keyval = 0; notebook.signal_switch_page ().connect (sigc::mem_fun (*this, &KeyEditor::page_change)); vpacker.pack_start (notebook, true, true); + Glib::RefPtr icon = ARDOUR_UI_UTILS::get_icon ("search"); + filter_entry.set_icon_from_pixbuf (icon); + filter_entry.signal_search_string_updated ().connect (sigc::mem_fun (*this, &KeyEditor::search_string_updated)); + vpacker.pack_start (filter_entry, false, false); + Label* hint = manage (new Label (_("Select an action, then press the key(s) to (re)set its shortcut"))); hint->show (); unbind_box.set_spacing (6); @@ -124,7 +132,7 @@ KeyEditor::page_change (GtkNotebookPage*, guint) } bool -KeyEditor::on_key_press_event (GdkEventKey* ev) +KeyEditor::Tab::on_key_press_event (GdkEventKey* ev) { if (!ev->is_modifier) { last_keyval = ev->keyval; @@ -139,13 +147,13 @@ KeyEditor::on_key_press_event (GdkEventKey* ev) } bool -KeyEditor::on_key_release_event (GdkEventKey* ev) +KeyEditor::Tab::on_key_release_event (GdkEventKey* ev) { if (last_keyval == 0) { return false; } - current_tab()->bind (ev, last_keyval); + owner.current_tab()->bind (ev, last_keyval); last_keyval = 0; return true; @@ -155,10 +163,17 @@ KeyEditor::Tab::Tab (KeyEditor& ke, string const & str, Bindings* b) : owner (ke) , name (str) , bindings (b) + , last_keyval (0) { - model = TreeStore::create(columns); + data_model = TreeStore::create(columns); + populate (); + + filter = TreeModelFilter::create(data_model); + filter->set_visible_func (sigc::mem_fun (*this, &Tab::visible_func)); - view.set_model (model); + sorted_filter = TreeModelSort::create(filter); + + view.set_model (sorted_filter); view.append_column (_("Action"), columns.name); view.append_column (_("Shortcut"), columns.binding); view.set_headers_visible (true); @@ -170,12 +185,12 @@ KeyEditor::Tab::Tab (KeyEditor& ke, string const & str, Bindings* b) view.set_rules_hint (true); view.set_name (X_("KeyEditorTree")); - view.get_selection()->signal_changed().connect (sigc::mem_fun (*this, &Tab::action_selected)); + view.signal_cursor_changed().connect (sigc::mem_fun (*this, &Tab::action_selected)); view.get_column(0)->set_sort_column (columns.name); view.get_column(1)->set_sort_column (columns.binding); - model->set_sort_column (owner.sort_column, owner.sort_type); - model->signal_sort_column_changed().connect (sigc::mem_fun (*this, &Tab::sort_column_changed)); + data_model->set_sort_column (owner.sort_column, owner.sort_type); + data_model->signal_sort_column_changed().connect (sigc::mem_fun (*this, &Tab::sort_column_changed)); signal_map().connect (sigc::mem_fun (*this, &Tab::tab_mapped)); @@ -194,49 +209,46 @@ KeyEditor::Tab::action_selected () return; } - TreeModel::iterator i = view.get_selection()->get_selected(); + TreeModel::const_iterator it = view.get_selection()->get_selected(); - owner.unbind_button.set_sensitive (false); + if (!it) { + return; + } - if (i != model->children().end()) { + if (!(*it)[columns.bindable]) { + owner.unbind_button.set_sensitive (false); + return; + } - string path = (*i)[columns.path]; + const string& binding = (*it)[columns.binding]; - if (!(*i)[columns.bindable]) { - return; - } - - string binding = (*i)[columns.binding]; - - if (!binding.empty()) { - owner.unbind_button.set_sensitive (true); - } + if (!binding.empty()) { + owner.unbind_button.set_sensitive (true); } } void KeyEditor::Tab::unbind () { - TreeModel::iterator i = view.get_selection()->get_selected(); + const std::string& action_path = (*view.get_selection()->get_selected())[columns.path]; + + TreeModel::iterator it = find_action_path (data_model->children().begin(), data_model->children().end(), action_path); + + if (!it || !(*it)[columns.bindable]) { + return; + } + + bindings->remove (Gtkmm2ext::Bindings::Press, action_path , true); + (*it)[columns.binding] = string (); owner.unbind_button.set_sensitive (false); - - if (i != model->children().end()) { - if (!(*i)[columns.bindable]) { - return; - } - - const std::string& action_path = (*i)[columns.path]; - - bindings->remove (Gtkmm2ext::Bindings::Press, action_path , true); - (*i)[columns.binding] = string (); - } } void KeyEditor::Tab::bind (GdkEventKey* release_event, guint pressed_key) { - TreeModel::iterator i = view.get_selection()->get_selected(); + const std::string& action_path = (*view.get_selection()->get_selected())[columns.path]; + TreeModel::iterator it = find_action_path (data_model->children().begin(), data_model->children().end(), action_path); /* pressed key could be upper case if Shift was used. We want all single keys stored as their lower-case version, so ensure this @@ -244,13 +256,7 @@ KeyEditor::Tab::bind (GdkEventKey* release_event, guint pressed_key) pressed_key = gdk_keyval_to_lower (pressed_key); - if (i == model->children().end()) { - return; - } - - string action_path = (*i)[columns.path]; - - if (!(*i)[columns.bindable]) { + if (!it || !(*it)[columns.bindable]) { return; } @@ -265,7 +271,7 @@ KeyEditor::Tab::bind (GdkEventKey* release_event, guint pressed_key) bool result = bindings->replace (new_binding, Gtkmm2ext::Bindings::Press, action_path); if (result) { - (*i)[columns.binding] = gtk_accelerator_get_label (new_binding.key(), (GdkModifierType) new_binding.state()); + (*it)[columns.binding] = gtk_accelerator_get_label (new_binding.key(), (GdkModifierType) new_binding.state()); owner.unbind_button.set_sensitive (true); } } @@ -290,7 +296,7 @@ KeyEditor::Tab::populate () vector::iterator l; vector >::iterator a; - model->clear (); + data_model->clear (); for (a = actions.begin(), l = labels.begin(), k = keys.begin(), p = paths.begin(), t = tooltips.begin(); l != labels.end(); ++k, ++p, ++t, ++l, ++a) { @@ -319,7 +325,7 @@ KeyEditor::Tab::populate () TreeIter rowp; TreeModel::Row parent; - rowp = model->append(); + rowp = data_model->append(); nodes[category] = rowp; parent = *(rowp); parent[columns.name] = category; @@ -330,13 +336,13 @@ KeyEditor::Tab::populate () * out with information */ - row = *(model->append (parent.children())); + row = *(data_model->append (parent.children())); } else { /* category/group is present, so just add the child row */ - row = *(model->append ((*r->second)->children())); + row = *(data_model->append ((*r->second)->children())); } @@ -367,7 +373,7 @@ KeyEditor::Tab::sort_column_changed () { int column; SortType type; - if (model->get_sort_column_id (column, type)) { + if (data_model->get_sort_column_id (column, type)) { owner.sort_column = column; owner.sort_type = type; } @@ -376,7 +382,59 @@ KeyEditor::Tab::sort_column_changed () void KeyEditor::Tab::tab_mapped () { - model->set_sort_column (owner.sort_column, owner.sort_type); + data_model->set_sort_column (owner.sort_column, owner.sort_type); + filter->refilter (); +} + +bool +KeyEditor::Tab::visible_func(const Gtk::TreeModel::const_iterator& iter) const +{ + if (!iter) { + return false; + } + + // never filter when search string is empty or item is a category + if (owner.filter_string.empty () || !(*iter)[columns.bindable]) { + return true; + } + + // search name + std::string name = (*iter)[columns.name]; + boost::to_lower (name); + if (name.find (owner.filter_string) != std::string::npos) { + return true; + } + + // search binding + std::string binding = (*iter)[columns.binding]; + boost::to_lower (binding); + if (binding.find (owner.filter_string) != std::string::npos) { + return true; + } + + return false; +} + +TreeModel::iterator +KeyEditor::Tab::find_action_path (TreeModel::const_iterator begin, TreeModel::const_iterator end, const std::string& action_path) const +{ + if (!begin) { + return end; + } + + for (TreeModel::iterator it = begin; it != end; ++it) { + if (it->children()) { + TreeModel::iterator jt = find_action_path (it->children().begin(), it->children().end(), action_path); + if (jt != it->children().end()) { + return jt; + } + } + const std::string& path = (*it)[columns.path]; + if (action_path.compare(path) == 0) { + return it; + } + } + return end; } void @@ -395,3 +453,11 @@ KeyEditor::current_tab () { return dynamic_cast (notebook.get_nth_page (notebook.get_current_page())); } + +void +KeyEditor::search_string_updated (const std::string& filter) +{ + filter_string = boost::to_lower_copy(filter); + current_tab ()->filter->refilter (); +} + diff --git a/gtk2_ardour/keyeditor.h b/gtk2_ardour/keyeditor.h index b04b973009..1c90269875 100644 --- a/gtk2_ardour/keyeditor.h +++ b/gtk2_ardour/keyeditor.h @@ -27,6 +27,7 @@ #include #include #include +#include "gtkmm2ext/searchbar.h" #include "ardour_window.h" @@ -41,14 +42,10 @@ class KeyEditor : public ArdourWindow void add_tab (std::string const &name, Gtkmm2ext::Bindings&); - protected: - bool on_key_press_event (GdkEventKey*); - bool on_key_release_event (GdkEventKey*); - - private: + private: class Tab : public Gtk::VBox { - public: + public: Tab (KeyEditor&, std::string const &name, Gtkmm2ext::Bindings*); void populate (); @@ -57,6 +54,7 @@ class KeyEditor : public ArdourWindow void action_selected (); void sort_column_changed (); void tab_mapped (); + bool visible_func(const Gtk::TreeModel::const_iterator& iter) const; struct KeyEditorColumns : public Gtk::TreeModel::ColumnRecord { KeyEditorColumns () { @@ -83,8 +81,16 @@ class KeyEditor : public ArdourWindow Gtkmm2ext::Bindings* bindings; Gtk::ScrolledWindow scroller; Gtk::TreeView view; - Glib::RefPtr model; + Glib::RefPtr data_model; + Glib::RefPtr filter; + Glib::RefPtr sorted_filter; KeyEditorColumns columns; + guint last_keyval; + + protected: + bool on_key_press_event (GdkEventKey*); + bool on_key_release_event (GdkEventKey*); + Gtk::TreeModel::iterator find_action_path (Gtk::TreeModel::const_iterator begin, Gtk::TreeModel::const_iterator end, const std::string& path) const; }; friend class Tab; @@ -96,7 +102,8 @@ class KeyEditor : public ArdourWindow Gtk::HBox reset_box; Gtk::Button reset_button; Gtk::Label reset_label; - guint last_keyval; + Gtkmm2ext::SearchBar filter_entry; + std::string filter_string; typedef std::vector Tabs; @@ -110,6 +117,7 @@ class KeyEditor : public ArdourWindow unsigned int sort_column; Gtk::SortType sort_type; void toggle_sort_type (); + void search_string_updated (const std::string&); }; #endif /* __ardour_gtk_key_editor_h__ */ diff --git a/libs/gtkmm2ext/gtkmm2ext/searchbar.h b/libs/gtkmm2ext/gtkmm2ext/searchbar.h new file mode 100644 index 0000000000..5096ed53f9 --- /dev/null +++ b/libs/gtkmm2ext/gtkmm2ext/searchbar.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +namespace Gtkmm2ext { + +class SearchBar : public Gtk::Entry +{ +public: + SearchBar(const std::string& placeholder_text = "Search...", bool icon_click_resets = true); + + void reset (); + // emitted when the filter has been updated + sigc::signal signal_search_string_updated () { return sig_search_string_updated; } +protected: + bool focus_in_event (GdkEventFocus*); + bool focus_out_event (GdkEventFocus*); + + bool key_press_event (GdkEventKey*); + void icon_clicked_event (Gtk::EntryIconPosition, const GdkEventButton*); + + const std::string placeholder_text; + sigc::signal sig_search_string_updated; +private: + void search_string_changed () const; + + Glib::RefPtr icon; + bool icon_click_resets; +}; + +} diff --git a/libs/gtkmm2ext/searchbar.cc b/libs/gtkmm2ext/searchbar.cc new file mode 100644 index 0000000000..75915c1f2e --- /dev/null +++ b/libs/gtkmm2ext/searchbar.cc @@ -0,0 +1,93 @@ +#include "gtkmm2ext/searchbar.h" +#include "gtkmm2ext/keyboard.h" +#include + +namespace Gtkmm2ext { + +SearchBar::SearchBar (const std::string& label, bool icon_resets) + : placeholder_text (label) + , icon_click_resets (icon_resets) +{ + set_text (placeholder_text); + set_alignment (Gtk::ALIGN_CENTER); + signal_key_press_event().connect (sigc::mem_fun (*this, &SearchBar::key_press_event)); + signal_focus_in_event().connect (sigc::mem_fun (*this, &SearchBar::focus_in_event)); + signal_focus_out_event().connect (sigc::mem_fun (*this, &SearchBar::focus_out_event)); + signal_changed().connect (sigc::mem_fun (*this, &SearchBar::search_string_changed)); + signal_icon_release().connect (sigc::mem_fun (*this, &SearchBar::icon_clicked_event)); +} + +bool +SearchBar::focus_in_event (GdkEventFocus*) +{ + if (get_text ().compare (placeholder_text) == 0) { + set_text (""); + } + + icon = get_icon_pixbuf (); + if (icon) { + set_icon_from_pixbuf (Glib::RefPtr ()); + } + return true; +} + +bool +SearchBar::focus_out_event (GdkEventFocus*) +{ + if (get_text ().empty ()) { + set_text (placeholder_text); + } + + if (icon) { + set_icon_from_pixbuf (icon); + icon.reset (); + } + + search_string_changed (); + return false; +} + +bool +SearchBar::key_press_event (GdkEventKey* ev) +{ + switch (ev->keyval) { + case GDK_Escape: + set_text (placeholder_text); + return true; + default: + break; + } + + return false; +} + +void +SearchBar::icon_clicked_event (Gtk::EntryIconPosition, const GdkEventButton*) +{ + if (icon_click_resets) { + reset (); + } + else { + search_string_changed (); + } +} + +void +SearchBar::search_string_changed () const +{ + const std::string& text = get_text (); + if (text.empty() || text.compare (placeholder_text) == 0) { + sig_search_string_updated (""); + return; + } + sig_search_string_updated (text); +} + +void +SearchBar::reset () +{ + set_text (placeholder_text); + search_string_changed (); +} + +} \ No newline at end of file diff --git a/libs/gtkmm2ext/wscript b/libs/gtkmm2ext/wscript index 0c8f454128..b5676d1423 100644 --- a/libs/gtkmm2ext/wscript +++ b/libs/gtkmm2ext/wscript @@ -57,6 +57,7 @@ gtkmm2ext_sources = [ 'popup.cc', 'prompter.cc', 'scroomer.cc', + 'searchbar.cc', 'selector.cc', 'slider_controller.cc', 'stateful_button.cc',