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