add note onset detection to the ferret, c/o the aubio-based Onset VAMP plugin (REQUIR...
[ardour.git] / gtk2_ardour / plugin_selector.cc
1 /*
2     Copyright (C) 2000-2006 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 #include <cstdio>
21 #include <lrdf.h>
22 #include <map>
23
24 #include <algorithm>
25
26 #include <gtkmm/table.h>
27 #include <gtkmm/stock.h>
28 #include <gtkmm/button.h>
29 #include <gtkmm/notebook.h>
30
31 #include <gtkmm2ext/utils.h>
32
33 #include <pbd/convert.h>
34
35 #include <ardour/plugin_manager.h>
36 #include <ardour/plugin.h>
37 #include <ardour/configuration.h>
38
39 #include "ardour_ui.h"
40 #include "plugin_selector.h"
41 #include "gui_thread.h"
42
43 #include "i18n.h"
44
45 using namespace ARDOUR;
46 using namespace PBD;
47 using namespace Gtk;
48 using namespace std;
49
50 static const char* _filter_mode_strings[] = {
51         N_("Name contains"),
52         N_("Type contains"),
53         N_("Author contains"),
54         N_("Library contains"),
55         N_("Favorites only"),
56         0
57 };
58
59 PluginSelector::PluginSelector (PluginManager *mgr)
60         : ArdourDialog (_("ardour: plugins"), true, false),
61           filter_button (Stock::CLEAR)
62 {
63         set_position (Gtk::WIN_POS_MOUSE);
64         set_name ("PluginSelectorWindow");
65         add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK);
66
67         manager = mgr;
68         session = 0;
69         _menu = 0;
70         in_row_change = false;
71
72         plugin_model = Gtk::ListStore::create (plugin_columns);
73         plugin_display.set_model (plugin_model);
74         /* XXX translators: try to convert "Fav" into a short term
75            related to "favorite"
76         */
77         plugin_display.append_column (_("Fav"), plugin_columns.favorite);
78         plugin_display.append_column (_("Available Plugins"), plugin_columns.name);
79         plugin_display.append_column (_("Type"), plugin_columns.type_name);
80         plugin_display.append_column (_("Category"), plugin_columns.category);
81         plugin_display.append_column (_("Creator"), plugin_columns.creator);
82         plugin_display.append_column (_("# Inputs"),plugin_columns.ins);
83         plugin_display.append_column (_("# Outputs"), plugin_columns.outs);
84         plugin_display.set_headers_visible (true);
85         plugin_display.set_headers_clickable (true);
86         plugin_display.set_reorderable (false);
87         plugin_display.set_rules_hint (true);
88
89         CellRendererToggle* fav_cell = dynamic_cast<CellRendererToggle*>(plugin_display.get_column_cell_renderer (0));
90         fav_cell->property_activatable() = true;
91         fav_cell->property_radio() = false;
92         fav_cell->signal_toggled().connect (mem_fun (*this, &PluginSelector::favorite_changed));
93
94         scroller.set_border_width(10);
95         scroller.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
96         scroller.add(plugin_display);
97
98         amodel = Gtk::ListStore::create(acols);
99         added_list.set_model (amodel);
100         added_list.append_column (_("Plugins to be connected"), acols.text);
101         added_list.set_headers_visible (true);
102         added_list.set_reorderable (false);
103
104         for (int i = 0; i <=3; i++) {
105                 Gtk::TreeView::Column* column = plugin_display.get_column(i);
106                 column->set_sort_column(i);
107         }
108
109         ascroller.set_border_width(10);
110         ascroller.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
111         ascroller.add(added_list);
112         btn_add = manage(new Gtk::Button(Stock::ADD));
113         ARDOUR_UI::instance()->tooltips().set_tip(*btn_add, _("Add a plugin to the effect list"));
114         btn_add->set_sensitive (false);
115         btn_remove = manage(new Gtk::Button(Stock::REMOVE));
116         btn_remove->set_sensitive (false);
117         ARDOUR_UI::instance()->tooltips().set_tip(*btn_remove, _("Remove a plugin from the effect list"));
118         Gtk::Button *btn_update = manage(new Gtk::Button(Stock::REFRESH));
119         ARDOUR_UI::instance()->tooltips().set_tip(*btn_update, _("Update available plugins"));
120
121         btn_add->set_name("PluginSelectorButton");
122         btn_remove->set_name("PluginSelectorButton");
123
124         Gtk::Table* table = manage(new Gtk::Table(7, 11));
125         table->set_size_request(750, 500);
126         table->attach(scroller, 0, 7, 0, 5);
127
128         HBox* filter_box = manage (new HBox);
129
130         vector<string> filter_strings = I18N (_filter_mode_strings);
131         Gtkmm2ext::set_popdown_strings (filter_mode, filter_strings);
132         filter_mode.set_active_text (filter_strings.front());
133
134         filter_box->pack_start (filter_mode, false, false);
135         filter_box->pack_start (filter_entry, true, true);
136         filter_box->pack_start (filter_button, false, false);
137
138         filter_entry.signal_changed().connect (mem_fun (*this, &PluginSelector::filter_entry_changed));
139         filter_button.signal_clicked().connect (mem_fun (*this, &PluginSelector::filter_button_clicked));
140         filter_mode.signal_changed().connect (mem_fun (*this, &PluginSelector::filter_mode_changed));
141
142         filter_box->show ();
143         filter_mode.show ();
144         filter_entry.show ();
145         filter_button.show ();
146
147         table->attach (*filter_box, 0, 7, 5, 6, FILL|EXPAND, FILL, 5, 5);
148
149         table->attach(*btn_add, 1, 2, 6, 7, FILL, FILL, 5, 5);
150         table->attach(*btn_remove, 3, 4, 6, 7, FILL, FILL, 5, 5);
151         table->attach(*btn_update, 5, 6, 6, 7, FILL, FILL, 5, 5);
152
153         table->attach(ascroller, 0, 7, 8, 10);
154
155         add_button (Stock::CANCEL, RESPONSE_CANCEL);
156         add_button (_("Insert Plugin(s)"), RESPONSE_APPLY);
157         set_default_response (RESPONSE_APPLY);
158         set_response_sensitive (RESPONSE_APPLY, false);
159         get_vbox()->pack_start (*table);
160
161         table->set_name("PluginSelectorTable");
162         plugin_display.set_name("PluginSelectorDisplay");
163         //plugin_display.set_name("PluginSelectorList");
164         added_list.set_name("PluginSelectorList");
165
166         plugin_display.signal_button_press_event().connect_notify (mem_fun(*this, &PluginSelector::row_clicked));
167         plugin_display.get_selection()->signal_changed().connect (mem_fun(*this, &PluginSelector::display_selection_changed));
168         plugin_display.grab_focus();
169         
170         btn_update->signal_clicked().connect (mem_fun(*this, &PluginSelector::btn_update_clicked));
171         btn_add->signal_clicked().connect(mem_fun(*this, &PluginSelector::btn_add_clicked));
172         btn_remove->signal_clicked().connect(mem_fun(*this, &PluginSelector::btn_remove_clicked));
173         added_list.get_selection()->signal_changed().connect (mem_fun(*this, &PluginSelector::added_list_selection_changed));
174
175         refill ();
176 }
177
178 void
179 PluginSelector::row_clicked(GdkEventButton* event)
180 {
181         if (event->type == GDK_2BUTTON_PRESS)
182                 btn_add_clicked();
183 }
184
185 void
186 PluginSelector::set_session (Session* s)
187 {
188         ENSURE_GUI_THREAD(bind (mem_fun(*this, &PluginSelector::set_session), s));
189         
190         session = s;
191
192         if (session) {
193                 session->GoingAway.connect (bind (mem_fun(*this, &PluginSelector::set_session), static_cast<Session*> (0)));
194         }
195 }
196
197 bool
198 PluginSelector::show_this_plugin (const PluginInfoPtr& info, const std::string& filterstr)
199 {
200         std::string compstr;
201         std::string mode = filter_mode.get_active_text ();
202
203         if (mode == _("Favorites only")) {
204                 return manager->is_a_favorite_plugin (info);
205         }
206
207         if (!filterstr.empty()) {
208                 
209                 if (mode == _("Name contains")) {
210                         compstr = info->name;
211                 } else if (mode == _("Type contains")) {
212                         compstr = info->category;
213                 } else if (mode == _("Author contains")) {
214                         compstr = info->creator;
215                 } else if (mode == _("Library contains")) {
216                         compstr = info->path;
217                 } 
218
219                 transform (compstr.begin(), compstr.end(), compstr.begin(), ::toupper);
220
221                 if (compstr.find (filterstr) != string::npos) {
222                         return true;
223                 } else {
224                         return false;
225                 }
226         }
227
228         return true;
229 }
230
231 void
232 PluginSelector::setup_filter_string (string& filterstr)
233 {
234         filterstr = filter_entry.get_text ();
235         transform (filterstr.begin(), filterstr.end(), filterstr.begin(), ::toupper);
236 }       
237
238 void
239 PluginSelector::refill ()
240 {
241         std::string filterstr;
242
243         in_row_change = true;
244
245         plugin_model->clear ();
246
247         setup_filter_string (filterstr);
248
249         ladspa_refiller (filterstr);
250         lv2_refiller (filterstr);
251         vst_refiller (filterstr);
252         au_refiller (filterstr);
253
254         in_row_change = false;
255 }
256
257 void
258 PluginSelector::refiller (const PluginInfoList& plugs, const::std::string& filterstr, const char* type)
259 {
260         char buf[16];
261
262         for (PluginInfoList::const_iterator i = plugs.begin(); i != plugs.end(); ++i) {
263
264                 if (show_this_plugin (*i, filterstr)) {
265
266                         TreeModel::Row newrow = *(plugin_model->append());
267                         newrow[plugin_columns.favorite] = manager->is_a_favorite_plugin (*i);
268                         newrow[plugin_columns.name] = (*i)->name;
269                         newrow[plugin_columns.type_name] = type;
270                         newrow[plugin_columns.category] = (*i)->category;
271
272                         string creator = (*i)->creator;
273                         string::size_type pos = 0;
274
275                         /* stupid LADSPA creator strings */
276
277                         while (pos < creator.length() && (isalnum (creator[pos]) || isspace (creator[pos]))) ++pos;
278                         creator = creator.substr (0, pos);
279
280                         newrow[plugin_columns.creator] = creator;
281
282                         if ((*i)->n_inputs < 0) {
283                                 newrow[plugin_columns.ins] = "various";
284                         } else {
285                                 snprintf (buf, sizeof(buf), "%d", (*i)->n_inputs);
286                                 newrow[plugin_columns.ins] = buf;
287                         }
288                         if ((*i)->n_outputs < 0) {
289                                 newrow[plugin_columns.outs] = "various";
290                         } else {
291                                 snprintf (buf, sizeof(buf), "%d", (*i)->n_outputs);             
292                                 newrow[plugin_columns.outs] = buf;
293                         }
294
295                         newrow[plugin_columns.plugin] = *i;
296                 }
297         }       
298 }
299
300 void
301 PluginSelector::ladspa_refiller (const std::string& filterstr)
302 {
303         refiller (manager->ladspa_plugin_info(), filterstr, "LADSPA");
304 }
305
306 void
307 PluginSelector::lv2_refiller (const std::string& filterstr)
308 {
309 #ifdef HAVE_SLV2
310         refiller (manager->lv2_plugin_info(), filterstr, "LV2");
311 #endif
312 }
313
314 void
315 PluginSelector::vst_refiller (const std::string& filterstr)
316 {
317 #ifdef VST_SUPPORT
318         refiller (manager->vst_plugin_info(), filterstr, "VST");
319 #endif
320 }
321
322 void
323 PluginSelector::au_refiller (const std::string& filterstr)
324 {
325 #ifdef HAVE_AUDIOUNITS
326         refiller (manager->au_plugin_info(), filterstr, "AU");
327 #endif
328 }
329
330 void
331 PluginSelector::use_plugin (PluginInfoPtr pi)
332 {
333         if (session == 0) {
334                 return;
335         }
336
337         PluginPtr plugin = pi->load (*session);
338
339         if (plugin) {
340                 PluginCreated (plugin);
341         }
342 }
343
344 void
345 PluginSelector::btn_add_clicked()
346 {
347         std::string name;
348         PluginInfoPtr pi;
349         TreeModel::Row newrow = *(amodel->append());
350         TreeModel::Row row;
351
352         row = *(plugin_display.get_selection()->get_selected());
353         name = row[plugin_columns.name];
354         pi = row[plugin_columns.plugin];
355
356         newrow[acols.text] = name;
357         newrow[acols.plugin] = pi;
358
359         if (!amodel->children().empty()) {
360                 set_response_sensitive (RESPONSE_APPLY, true);
361         }
362 }
363
364 void
365 PluginSelector::btn_remove_clicked()
366 {
367         TreeModel::iterator iter = added_list.get_selection()->get_selected();
368         
369         amodel->erase(iter);
370         if (amodel->children().empty()) {
371                 set_response_sensitive (RESPONSE_APPLY, false);
372         }
373 }
374
375 void
376 PluginSelector::btn_update_clicked()
377 {
378         manager->refresh ();
379         refill();
380 }
381
382 void
383 PluginSelector::display_selection_changed()
384 {
385         if (plugin_display.get_selection()->count_selected_rows() != 0) {
386                 btn_add->set_sensitive (true);
387         } else {
388                 btn_add->set_sensitive (false);
389         }
390 }
391
392 void
393 PluginSelector::added_list_selection_changed()
394 {
395         if (added_list.get_selection()->count_selected_rows() != 0) {
396                 btn_remove->set_sensitive (true);
397         } else {
398                 btn_remove->set_sensitive (false);
399         }
400 }
401
402 int
403 PluginSelector::run ()
404 {
405         ResponseType r;
406         TreeModel::Children::iterator i;
407
408         r = (ResponseType) Dialog::run ();
409
410         switch (r) {
411         case RESPONSE_APPLY:
412                 for (i = amodel->children().begin(); i != amodel->children().end(); ++i) {
413                         PluginInfoPtr pp = (*i)[acols.plugin];
414                         use_plugin (pp);
415                 }
416                 break;
417
418         default:
419                 break;
420         }
421
422         cleanup ();
423
424         return (int) r;
425 }
426
427 void
428 PluginSelector::cleanup ()
429 {
430         hide();
431         amodel->clear();
432 }
433
434 void
435 PluginSelector::filter_button_clicked ()
436 {
437         filter_entry.set_text ("");
438 }
439
440 void
441 PluginSelector::filter_entry_changed ()
442 {
443         refill ();
444 }
445
446 void 
447 PluginSelector::filter_mode_changed ()
448 {
449         std::string mode = filter_mode.get_active_text ();
450
451         if (mode == _("Favorites only")) {
452                 filter_entry.set_sensitive (false);
453         } else {
454                 filter_entry.set_sensitive (true);
455         }
456
457         refill ();
458 }
459
460 void
461 PluginSelector::on_show ()
462 {
463         ArdourDialog::on_show ();
464         filter_entry.grab_focus ();
465 }
466
467 struct PluginMenuCompare {
468     bool operator() (PluginInfoPtr a, PluginInfoPtr b) const {
469             int cmp;
470
471             cmp = strcasecmp (a->creator.c_str(), b->creator.c_str());
472
473             if (cmp < 0) {
474                     return true;
475             } else if (cmp == 0) {
476                     /* same creator ... compare names */
477                     if (strcasecmp (a->name.c_str(), b->name.c_str()) < 0) {
478                             return true;
479                     } 
480             }
481             return false;
482     }
483 };
484
485 Gtk::Menu&
486 PluginSelector::plugin_menu()
487 {
488         using namespace Menu_Helpers;
489
490         typedef std::map<Glib::ustring,Gtk::Menu*> SubmenuMap;
491         SubmenuMap submenu_map;
492
493         if (!_menu) {
494                 _menu = new Menu();
495         } 
496
497         MenuList& items = _menu->items();
498         Menu* favs = new Menu();
499
500         items.clear ();
501         items.push_back (MenuElem (_("Favorites"), *favs));
502         items.push_back (MenuElem (_("Plugin Manager"), mem_fun (*this, &PluginSelector::show_manager)));
503         items.push_back (SeparatorElem ());
504
505         PluginInfoList all_plugs;
506
507         all_plugs.insert (all_plugs.end(), manager->ladspa_plugin_info().begin(), manager->ladspa_plugin_info().end());
508 #ifdef VST_SUPPORT
509         all_plugs.insert (all_plugs.end(), manager->vst_plugin_info().begin(), manager->vst_plugin_info().end());
510 #endif
511 #ifdef HAVE_AUDIOUNITS
512         all_plugs.insert (all_plugs.end(), manager->au_plugin_info().begin(), manager->au_plugin_info().end());
513 #endif
514 #ifdef HAVE_SLV2
515         all_plugs.insert (all_plugs.end(), manager->lv2_plugin_info().begin(), manager->lv2_plugin_info().end());
516 #endif
517
518         PluginMenuCompare cmp;
519         all_plugs.sort (cmp);
520
521         for (PluginInfoList::const_iterator i = all_plugs.begin(); i != all_plugs.end(); ++i) {
522                 SubmenuMap::iterator x;
523                 Gtk::Menu* submenu;
524
525                 string creator = (*i)->creator;
526                 string::size_type pos = 0;
527
528                 if (manager->is_a_favorite_plugin (*i)) {
529                         favs->items().push_back (MenuElem ((*i)->name, (bind (mem_fun (*this, &PluginSelector::plugin_chosen_from_menu), *i))));
530                 }
531                 
532                 /* stupid LADSPA creator strings */
533                 
534                 while (pos < creator.length() && (isalnum (creator[pos]) || isspace (creator[pos]))) ++pos;
535                 creator = creator.substr (0, pos);
536
537                 if ((x = submenu_map.find (creator)) != submenu_map.end()) {
538                         submenu = x->second;
539                 } else {
540                         submenu = new Gtk::Menu;
541                         items.push_back (MenuElem (creator, *submenu));
542                         submenu_map.insert (pair<Glib::ustring,Menu*> (creator, submenu));
543                 }
544                 
545                 submenu->items().push_back (MenuElem ((*i)->name, (bind (mem_fun (*this, &PluginSelector::plugin_chosen_from_menu), *i))));
546         }
547         
548         return *_menu;
549 }
550
551 void
552 PluginSelector::plugin_chosen_from_menu (const PluginInfoPtr& pi)
553 {
554         use_plugin (pi);
555 }
556
557 void 
558 PluginSelector::favorite_changed (const Glib::ustring& path)
559 {
560         PluginInfoPtr pi;
561
562         if (in_row_change) {
563                 return;
564         }
565
566         in_row_change = true;
567         
568         TreeModel::iterator iter = plugin_model->get_iter (path);
569         
570         if (iter) {
571
572                 bool favorite = !(*iter)[plugin_columns.favorite];
573
574                 /* change state */
575
576                 (*iter)[plugin_columns.favorite] = favorite;
577
578                 /* save new favorites list */
579
580                 pi = (*iter)[plugin_columns.plugin];
581                 
582                 if (favorite) {
583                         manager->add_favorite (pi->type, pi->unique_id);
584                 } else {
585                         manager->remove_favorite (pi->type, pi->unique_id);
586                 }
587                 
588                 manager->save_favorites ();
589         }
590         in_row_change = false;
591 }
592
593 void
594 PluginSelector::show_manager ()
595 {
596         show_all();
597         run ();
598 }