in key editor, attach key press/release handlers before other handlers so that normal...
[ardour.git] / gtk2_ardour / keyeditor.cc
1 /*
2     Copyright (C) 2002 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #ifdef WAF_BUILD
21 #include "gtk2ardour-config.h"
22 #endif
23
24 #include <map>
25
26 #include <boost/algorithm/string.hpp>
27
28 #include <gtkmm/stock.h>
29 #include <gtkmm/label.h>
30 #include <gtkmm/accelkey.h>
31 #include <gtkmm/accelmap.h>
32 #include <gtkmm/uimanager.h>
33
34 #include "gtkmm2ext/bindings.h"
35 #include "gtkmm2ext/utils.h"
36
37 #include "pbd/strsplit.h"
38
39 #include "ardour/filesystem_paths.h"
40 #include "ardour/profile.h"
41
42 #include "actions.h"
43 #include "keyboard.h"
44 #include "keyeditor.h"
45
46 #include "i18n.h"
47
48 using namespace std;
49 using namespace Gtk;
50 using namespace Gdk;
51 using namespace PBD;
52
53 using Gtkmm2ext::Keyboard;
54 using Gtkmm2ext::Bindings;
55
56 void bindings_collision_dialog (Gtk::Window& parent)
57 {
58         ArdourDialog dialog (parent, _("Colliding keybindings"), true);
59         Label label (_("The key sequence is already bound. Please remove the other binding first."));
60
61         dialog.get_vbox()->pack_start (label, true, true);
62         dialog.add_button (_("Ok"), Gtk::RESPONSE_ACCEPT);
63         dialog.show_all ();
64         dialog.run();
65 }
66
67 KeyEditor::KeyEditor ()
68         : ArdourWindow (_("Key Bindings"))
69         , unbind_button (_("Remove shortcut"))
70         , unbind_box (BUTTONBOX_END)
71         , filter_entry (_("Search..."), true)
72         , filter_string("")
73         , sort_column(0)
74         , sort_type(Gtk::SORT_ASCENDING)
75 {
76
77         notebook.signal_switch_page ().connect (sigc::mem_fun (*this, &KeyEditor::page_change));
78
79         vpacker.pack_start (notebook, true, true);
80
81         Glib::RefPtr<Gdk::Pixbuf> icon = ARDOUR_UI_UTILS::get_icon ("search");
82         filter_entry.set_icon_from_pixbuf (icon);
83         filter_entry.set_icon_tooltip_text (_("Click to reset search string"));
84         filter_entry.signal_search_string_updated ().connect (sigc::mem_fun (*this, &KeyEditor::search_string_updated));
85         vpacker.pack_start (filter_entry, false, false);
86
87         Label* hint = manage (new Label (_("Select an action, then press the key(s) to (re)set its shortcut")));
88         hint->show ();
89         unbind_box.set_spacing (6);
90         unbind_box.pack_start (*hint, false, true);
91         unbind_box.pack_start (unbind_button, false, false);
92         unbind_button.signal_clicked().connect (sigc::mem_fun (*this, &KeyEditor::unbind));
93
94         vpacker.pack_start (unbind_box, false, false);
95         unbind_box.show ();
96         unbind_button.show ();
97
98         reset_button.add (reset_label);
99         reset_label.set_markup (string_compose ("<span size=\"large\" weight=\"bold\">%1</span>", _("Reset Bindings to Defaults")));
100
101         reset_box.pack_start (reset_button, true, false);
102         reset_box.show ();
103         reset_button.show ();
104         reset_label.show ();
105         reset_button.signal_clicked().connect (sigc::mem_fun (*this, &KeyEditor::reset));
106         vpacker.pack_start (reset_box, false, false);
107
108         add (vpacker);
109
110         unbind_button.set_sensitive (false);
111 }
112
113 void
114 KeyEditor::add_tab (string const & name, Bindings& bindings)
115 {
116         Tab* t = new Tab (*this, name, &bindings);
117         t->populate ();
118         t->show_all ();
119         notebook.append_page (*t, name);
120 }
121
122
123 void
124 KeyEditor::remove_tab (string const &name)
125 {
126         guint npages = notebook.get_n_pages ();
127
128         for (guint n = 0; n < npages; ++n) {
129                 Widget* w = notebook.get_nth_page (n);
130                 Tab* tab = dynamic_cast<Tab*> (w);
131                 if (tab) {
132                         if (tab->name == name) {
133                                 notebook.remove_page (*w);
134                                 return;
135                         }
136                 }
137         }
138 }
139
140 void
141 KeyEditor::unbind ()
142 {
143         current_tab()->unbind ();
144 }
145
146 void
147 KeyEditor::page_change (GtkNotebookPage*, guint)
148 {
149         current_tab()->view.get_selection()->unselect_all ();
150         unbind_button.set_sensitive (false);
151 }
152
153 bool
154 KeyEditor::Tab::key_press_event (GdkEventKey* ev)
155 {
156         if (view.get_selection()->count_selected_rows() != 1) {
157                 return false;
158         }
159
160         if (!ev->is_modifier) {
161                 last_keyval = ev->keyval;
162         }
163
164         /* Don't let anything else handle the key press, because navigation
165          * keys will be used by GTK to change the selection/treeview cursor
166          * position
167          */
168
169         return true;
170 }
171
172 bool
173 KeyEditor::Tab::key_release_event (GdkEventKey* ev)
174 {
175         if (view.get_selection()->count_selected_rows() != 1) {
176                 return false;
177         }
178
179         if (last_keyval == 0) {
180                 return false;
181         }
182
183         owner.current_tab()->bind (ev, last_keyval);
184
185         last_keyval = 0;
186         return true;
187 }
188
189 KeyEditor::Tab::Tab (KeyEditor& ke, string const & str, Bindings* b)
190         : owner (ke)
191         , name (str)
192         , bindings (b)
193         , last_keyval (0)
194 {
195         data_model = TreeStore::create(columns);
196         populate ();
197
198         filter = TreeModelFilter::create(data_model);
199         filter->set_visible_func (sigc::mem_fun (*this, &Tab::visible_func));
200
201         sorted_filter = TreeModelSort::create(filter);
202
203         view.set_model (sorted_filter);
204         view.append_column (_("Action"), columns.name);
205         view.append_column (_("Shortcut"), columns.binding);
206         view.set_headers_visible (true);
207         view.set_headers_clickable (true);
208         view.get_selection()->set_mode (SELECTION_SINGLE);
209         view.set_reorderable (false);
210         view.set_size_request (500,300);
211         view.set_enable_search (false);
212         view.set_rules_hint (true);
213         view.set_name (X_("KeyEditorTree"));
214
215         view.signal_cursor_changed().connect (sigc::mem_fun (*this, &Tab::action_selected));
216         view.signal_key_press_event().connect (sigc::mem_fun (*this, &Tab::key_press_event), false);
217         view.signal_key_release_event().connect (sigc::mem_fun (*this, &Tab::key_release_event), false);
218
219         view.get_column(0)->set_sort_column (columns.name);
220         view.get_column(1)->set_sort_column (columns.binding);
221         data_model->set_sort_column (owner.sort_column,  owner.sort_type);
222         data_model->signal_sort_column_changed().connect (sigc::mem_fun (*this, &Tab::sort_column_changed));
223
224         signal_map().connect (sigc::mem_fun (*this, &Tab::tab_mapped));
225
226         scroller.add (view);
227         scroller.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
228
229         set_spacing (6);
230         set_border_width (12);
231         pack_start (scroller);
232 }
233
234 void
235 KeyEditor::Tab::action_selected ()
236 {
237         if (view.get_selection()->count_selected_rows() == 0) {
238                 return;
239         }
240
241         TreeModel::const_iterator it = view.get_selection()->get_selected();
242
243         if (!it) {
244                 return;
245         }
246
247         if (!(*it)[columns.bindable]) {
248                 owner.unbind_button.set_sensitive (false);
249                 return;
250         }
251
252         const string& binding = (*it)[columns.binding];
253
254         if (!binding.empty()) {
255                 owner.unbind_button.set_sensitive (true);
256         }
257 }
258
259 void
260 KeyEditor::Tab::unbind ()
261 {
262         const std::string& action_path = (*view.get_selection()->get_selected())[columns.path];
263
264         TreeModel::iterator it = find_action_path (data_model->children().begin(), data_model->children().end(),  action_path);
265
266         if (!it || !(*it)[columns.bindable]) {
267                 return;
268         }
269
270         bindings->remove (Gtkmm2ext::Bindings::Press,  action_path , true);
271         (*it)[columns.binding] = string ();
272
273         owner.unbind_button.set_sensitive (false);
274 }
275
276 void
277 KeyEditor::Tab::bind (GdkEventKey* release_event, guint pressed_key)
278 {
279         const std::string& action_path = (*view.get_selection()->get_selected())[columns.path];
280         TreeModel::iterator it = find_action_path (data_model->children().begin(), data_model->children().end(),  action_path);
281
282         /* pressed key could be upper case if Shift was used. We want all
283            single keys stored as their lower-case version, so ensure this
284         */
285
286         pressed_key = gdk_keyval_to_lower (pressed_key);
287
288         if (!it || !(*it)[columns.bindable]) {
289                 return;
290         }
291
292         GdkModifierType mod = (GdkModifierType)(Keyboard::RelevantModifierKeyMask & release_event->state);
293         Gtkmm2ext::KeyboardKey new_binding (mod, pressed_key);
294
295         if (bindings->is_bound (new_binding, Gtkmm2ext::Bindings::Press)) {
296                 bindings_collision_dialog (owner);
297                 return;
298         }
299
300         bool result = bindings->replace (new_binding, Gtkmm2ext::Bindings::Press, action_path);
301
302         if (result) {
303                 (*it)[columns.binding] = gtk_accelerator_get_label (new_binding.key(), (GdkModifierType) new_binding.state());
304                 owner.unbind_button.set_sensitive (true);
305         }
306 }
307
308 void
309 KeyEditor::Tab::populate ()
310 {
311         vector<string> paths;
312         vector<string> labels;
313         vector<string> tooltips;
314         vector<string> keys;
315         vector<Glib::RefPtr<Action> > actions;
316         typedef std::map<string,TreeIter> NodeMap;
317         NodeMap nodes;
318         NodeMap::iterator r;
319
320         bindings->get_all_actions (paths, labels, tooltips, keys, actions);
321
322         vector<string>::iterator k;
323         vector<string>::iterator p;
324         vector<string>::iterator t;
325         vector<string>::iterator l;
326         vector<Glib::RefPtr<Action> >::iterator a;
327
328         data_model->clear ();
329
330         for (a = actions.begin(), l = labels.begin(), k = keys.begin(), p = paths.begin(), t = tooltips.begin(); l != labels.end(); ++k, ++p, ++t, ++l, ++a) {
331
332                 TreeModel::Row row;
333                 vector<string> parts;
334
335                 split (*p, parts, '/');
336
337                 string category = parts[1];
338                 string action_name = parts[2];
339
340                 if (action_name.empty()) {
341                         continue;
342                 }
343
344                 //kinda kludgy way to avoid displaying menu items as mappable
345                 if ((action_name.find ("Menu") == action_name.length() - 4) ||
346                     (action_name.find ("menu") == action_name.length() - 4) ||
347                     (action_name == _("RegionList"))) {
348                         continue;
349                 }
350
351                 if ((r = nodes.find (category)) == nodes.end()) {
352
353                         /* category/group is missing, so add it first */
354
355                         TreeIter rowp;
356                         TreeModel::Row parent;
357                         rowp = data_model->append();
358                         nodes[category] = rowp;
359                         parent = *(rowp);
360                         parent[columns.name] = category;
361                         parent[columns.bindable] = false;
362                         parent[columns.action] = *a;
363
364                         /* now set up the child row that we're about to fill
365                          * out with information
366                          */
367
368                         row = *(data_model->append (parent.children()));
369
370                 } else {
371
372                         /* category/group is present, so just add the child row */
373
374                         row = *(data_model->append ((*r->second)->children()));
375
376                 }
377
378                 /* add this action */
379
380                 /* use the "visible label" as the action name */
381
382                 if (l->empty ()) {
383                         /* no label, try using the tooltip instead */
384                         row[columns.name] = *t;
385                 } else {
386                         row[columns.name] = *l;
387                 }
388                 row[columns.path] = string_compose ("%1/%2", category, action_name);
389                 row[columns.bindable] = true;
390
391                 if (*k == ActionManager::unbound_string) {
392                         row[columns.binding] = string();
393                 } else {
394                         row[columns.binding] = *k;
395                 }
396                 row[columns.action] = *a;
397         }
398 }
399
400 void
401 KeyEditor::Tab::sort_column_changed ()
402 {
403         int column;
404         SortType type;
405         if (data_model->get_sort_column_id (column, type)) {
406                 owner.sort_column = column;
407                 owner.sort_type = type;
408         }
409 }
410
411 void
412 KeyEditor::Tab::tab_mapped ()
413 {
414         data_model->set_sort_column (owner.sort_column,  owner.sort_type);
415         filter->refilter ();
416 }
417
418 bool
419 KeyEditor::Tab::visible_func(const Gtk::TreeModel::const_iterator& iter) const
420 {
421         if (!iter) {
422                 return false;
423         }
424
425         // never filter when search string is empty or item is a category
426         if (owner.filter_string.empty () || !(*iter)[columns.bindable]) {
427                 return true;
428         }
429
430         // search name
431         std::string name = (*iter)[columns.name];
432         boost::to_lower (name);
433         if (name.find (owner.filter_string) != std::string::npos) {
434                 return true;
435         }
436
437         // search binding
438         std::string binding = (*iter)[columns.binding];
439         boost::to_lower (binding);
440         if (binding.find (owner.filter_string) != std::string::npos) {
441                 return true;
442         }
443
444         return false;
445 }
446
447 TreeModel::iterator
448 KeyEditor::Tab::find_action_path (TreeModel::const_iterator begin, TreeModel::const_iterator end, const std::string& action_path) const
449 {
450         if (!begin) {
451                 return end;
452         }
453
454         for (TreeModel::iterator it = begin; it != end; ++it) {
455                 if (it->children()) {
456                         TreeModel::iterator jt = find_action_path (it->children().begin(), it->children().end(), action_path);
457                         if (jt != it->children().end()) {
458                                 return jt;
459                         }
460                 }
461                 const std::string& path = (*it)[columns.path];
462                 if (action_path.compare(path) == 0) {
463                         return it;
464                 }
465         }
466         return end;
467 }
468
469 void
470 KeyEditor::reset ()
471 {
472         Keyboard::the_keyboard().reset_bindings ();
473
474         for (Tabs::iterator t = tabs.begin(); t != tabs.end(); ++t) {
475                 (*t)->view.get_selection()->unselect_all ();
476                 (*t)->populate ();
477         }
478 }
479
480 KeyEditor::Tab*
481 KeyEditor::current_tab ()
482 {
483         return dynamic_cast<Tab*> (notebook.get_nth_page (notebook.get_current_page()));
484 }
485
486 void
487 KeyEditor::search_string_updated (const std::string& filter)
488 {
489         filter_string = boost::to_lower_copy(filter);
490         current_tab ()->filter->refilter ();
491 }