make context menu-driven stuff on ruler bars happen in the right place (e.g. new...
[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_LV2
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 PluginPtr
331 PluginSelector::load_plugin (PluginInfoPtr pi)
332 {
333         if (session == 0) {
334                 return PluginPtr();
335         }
336
337         return pi->load (*session);
338 }
339
340 void
341 PluginSelector::btn_add_clicked()
342 {
343         std::string name;
344         PluginInfoPtr pi;
345         TreeModel::Row newrow = *(amodel->append());
346         TreeModel::Row row;
347
348         row = *(plugin_display.get_selection()->get_selected());
349         name = row[plugin_columns.name];
350         pi = row[plugin_columns.plugin];
351
352         newrow[acols.text] = name;
353         newrow[acols.plugin] = pi;
354
355         if (!amodel->children().empty()) {
356                 set_response_sensitive (RESPONSE_APPLY, true);
357         }
358 }
359
360 void
361 PluginSelector::btn_remove_clicked()
362 {
363         TreeModel::iterator iter = added_list.get_selection()->get_selected();
364         
365         amodel->erase(iter);
366         if (amodel->children().empty()) {
367                 set_response_sensitive (RESPONSE_APPLY, false);
368         }
369 }
370
371 void
372 PluginSelector::btn_update_clicked()
373 {
374         manager->refresh ();
375         refill();
376 }
377
378 void
379 PluginSelector::display_selection_changed()
380 {
381         if (plugin_display.get_selection()->count_selected_rows() != 0) {
382                 btn_add->set_sensitive (true);
383         } else {
384                 btn_add->set_sensitive (false);
385         }
386 }
387
388 void
389 PluginSelector::added_list_selection_changed()
390 {
391         if (added_list.get_selection()->count_selected_rows() != 0) {
392                 btn_remove->set_sensitive (true);
393         } else {
394                 btn_remove->set_sensitive (false);
395         }
396 }
397
398 int
399 PluginSelector::run ()
400 {
401         ResponseType r;
402         TreeModel::Children::iterator i;
403         SelectedPlugins plugins;
404
405         r = (ResponseType) Dialog::run ();
406
407         switch (r) {
408         case RESPONSE_APPLY:
409                 for (i = amodel->children().begin(); i != amodel->children().end(); ++i) {
410                         PluginInfoPtr pp = (*i)[acols.plugin];
411                         PluginPtr p = load_plugin (pp);
412                         if (p) {
413                                 plugins.push_back (p);
414                         }
415                 }
416                 if (interested_object && !plugins.empty()) {
417                         interested_object->use_plugins (plugins);
418                 }
419                 
420                 break;
421
422         default:
423                 break;
424         }
425
426         hide();
427         amodel->clear();
428         interested_object = 0;
429
430         return (int) r;
431 }
432
433 void
434 PluginSelector::filter_button_clicked ()
435 {
436         filter_entry.set_text ("");
437 }
438
439 void
440 PluginSelector::filter_entry_changed ()
441 {
442         refill ();
443 }
444
445 void 
446 PluginSelector::filter_mode_changed ()
447 {
448         std::string mode = filter_mode.get_active_text ();
449
450         if (mode == _("Favorites only")) {
451                 filter_entry.set_sensitive (false);
452         } else {
453                 filter_entry.set_sensitive (true);
454         }
455
456         refill ();
457 }
458
459 void
460 PluginSelector::on_show ()
461 {
462         ArdourDialog::on_show ();
463         filter_entry.grab_focus ();
464 }
465
466 struct PluginMenuCompare {
467     bool operator() (PluginInfoPtr a, PluginInfoPtr b) const {
468             int cmp;
469
470             cmp = strcasecmp (a->creator.c_str(), b->creator.c_str());
471
472             if (cmp < 0) {
473                     return true;
474             } else if (cmp == 0) {
475                     /* same creator ... compare names */
476                     if (strcasecmp (a->name.c_str(), b->name.c_str()) < 0) {
477                             return true;
478                     } 
479             }
480             return false;
481     }
482 };
483
484 Gtk::Menu&
485 PluginSelector::plugin_menu()
486 {
487         using namespace Menu_Helpers;
488
489         typedef std::map<Glib::ustring,Gtk::Menu*> SubmenuMap;
490         SubmenuMap submenu_map;
491
492         if (!_menu) {
493                 _menu = new Menu();
494                 _menu->set_name("ArdourContextMenu");
495         } 
496
497         MenuList& items = _menu->items();
498         Menu* favs = new Menu();
499         favs->set_name("ArdourContextMenu");
500
501         items.clear ();
502         items.push_back (MenuElem (_("Favorites"), *favs));
503         items.push_back (MenuElem (_("Plugin Manager"), mem_fun (*this, &PluginSelector::show_manager)));
504         items.push_back (SeparatorElem ());
505
506         PluginInfoList all_plugs;
507
508         all_plugs.insert (all_plugs.end(), manager->ladspa_plugin_info().begin(), manager->ladspa_plugin_info().end());
509 #ifdef VST_SUPPORT
510         all_plugs.insert (all_plugs.end(), manager->vst_plugin_info().begin(), manager->vst_plugin_info().end());
511 #endif
512 #ifdef HAVE_AUDIOUNITS
513         all_plugs.insert (all_plugs.end(), manager->au_plugin_info().begin(), manager->au_plugin_info().end());
514 #endif
515 #ifdef HAVE_LV2
516         all_plugs.insert (all_plugs.end(), manager->lv2_plugin_info().begin(), manager->lv2_plugin_info().end());
517 #endif
518
519         PluginMenuCompare cmp;
520         all_plugs.sort (cmp);
521
522         for (PluginInfoList::const_iterator i = all_plugs.begin(); i != all_plugs.end(); ++i) {
523                 SubmenuMap::iterator x;
524                 Gtk::Menu* submenu;
525
526                 string creator = (*i)->creator;
527                 string::size_type pos = 0;
528
529                 if (manager->is_a_favorite_plugin (*i)) {
530                         favs->items().push_back (MenuElem ((*i)->name, (bind (mem_fun (*this, &PluginSelector::plugin_chosen_from_menu), *i))));
531                 }
532                 
533                 /* stupid LADSPA creator strings */
534                 
535                 while (pos < creator.length() && (isalnum (creator[pos]) || isspace (creator[pos]))) ++pos;
536                 creator = creator.substr (0, pos);
537
538                 if ((x = submenu_map.find (creator)) != submenu_map.end()) {
539                         submenu = x->second;
540                 } else {
541                         submenu = new Gtk::Menu;
542                         items.push_back (MenuElem (creator, *submenu));
543                         submenu_map.insert (pair<Glib::ustring,Menu*> (creator, submenu));
544                         submenu->set_name("ArdourContextMenu");
545                 }
546                 
547                 submenu->items().push_back (MenuElem ((*i)->name, (bind (mem_fun (*this, &PluginSelector::plugin_chosen_from_menu), *i))));
548         }
549         
550         return *_menu;
551 }
552
553 void
554 PluginSelector::plugin_chosen_from_menu (const PluginInfoPtr& pi)
555 {
556         PluginPtr p = load_plugin (pi);
557
558         if (p && interested_object) {
559                 SelectedPlugins plugins;
560                 plugins.push_back (p);
561                 interested_object->use_plugins (plugins);
562         }
563
564         interested_object = 0;
565 }
566
567 void 
568 PluginSelector::favorite_changed (const Glib::ustring& path)
569 {
570         PluginInfoPtr pi;
571
572         if (in_row_change) {
573                 return;
574         }
575
576         in_row_change = true;
577         
578         TreeModel::iterator iter = plugin_model->get_iter (path);
579         
580         if (iter) {
581
582                 bool favorite = !(*iter)[plugin_columns.favorite];
583
584                 /* change state */
585
586                 (*iter)[plugin_columns.favorite] = favorite;
587
588                 /* save new favorites list */
589
590                 pi = (*iter)[plugin_columns.plugin];
591                 
592                 if (favorite) {
593                         manager->add_favorite (pi->type, pi->unique_id);
594                 } else {
595                         manager->remove_favorite (pi->type, pi->unique_id);
596                 }
597                 
598                 manager->save_favorites ();
599         }
600         in_row_change = false;
601 }
602
603 void
604 PluginSelector::show_manager ()
605 {
606         show_all();
607         run ();
608 }
609
610 void
611 PluginSelector::set_interested_object (PluginInterestedObject& obj)
612 {
613         interested_object = &obj;
614 }