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