Transfer Fn: skip phase calculation for silence and small signal levels
[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 <map>
25
26 #include <algorithm>
27
28 #include <gtkmm/button.h>
29 #include <gtkmm/comboboxtext.h>
30 #include <gtkmm/frame.h>
31 #include <gtkmm/messagedialog.h>
32 #include <gtkmm/notebook.h>
33 #include <gtkmm/stock.h>
34 #include <gtkmm/table.h>
35 #include <gtkmm/treestore.h>
36
37 #include "gtkmm2ext/utils.h"
38
39 #include "widgets/tooltips.h"
40
41 #include "pbd/convert.h"
42 #include "pbd/tokenizer.h"
43
44 #include "ardour/utils.h"
45
46 #include "plugin_selector.h"
47 #include "gui_thread.h"
48 #include "ui_config.h"
49
50 #include "pbd/i18n.h"
51
52 using namespace ARDOUR;
53 using namespace PBD;
54 using namespace Gtk;
55 using namespace std;
56 using namespace ArdourWidgets;
57
58 static const uint32_t MAX_CREATOR_LEN = 24;
59
60 PluginSelector::PluginSelector (PluginManager& mgr)
61         : ArdourDialog (_("Plugin Manager"), true, false)
62         , search_clear_button (Stock::CLEAR)
63         , manager (mgr)
64         , _need_tag_save (false)
65         , _need_status_save (false)
66         , _need_menu_rebuild (false)
67         , _inhibit_refill (false)
68 {
69         set_name ("PluginSelectorWindow");
70         add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK);
71
72         _plugin_menu = 0;
73         in_row_change = false;
74
75         manager.PluginListChanged.connect (plugin_list_changed_connection, invalidator (*this), boost::bind (&PluginSelector::build_plugin_menu, this), gui_context());
76         manager.PluginStatusChanged.connect (plugin_list_changed_connection, invalidator (*this), boost::bind (&PluginSelector::build_plugin_menu, this), gui_context());
77
78         manager.PluginStatusChanged.connect (plugin_list_changed_connection, invalidator (*this), boost::bind (&PluginSelector::plugin_status_changed, this, _1, _2, _3), gui_context());
79         manager.PluginTagChanged.connect(plugin_list_changed_connection, invalidator (*this), boost::bind (&PluginSelector::tags_changed, this, _1, _2, _3), gui_context());
80
81         plugin_model = Gtk::ListStore::create (plugin_columns);
82         plugin_display.set_model (plugin_model);
83         /* XXX translators: try to convert "Fav" into a short term
84          * related to "favorite" and "Hid" into a short term
85          * related to "hidden"
86          */
87         plugin_display.append_column (_("Fav"), plugin_columns.favorite);
88         plugin_display.append_column (_("Hide"), plugin_columns.hidden);
89         plugin_display.append_column (_("Name"), plugin_columns.name);
90         plugin_display.append_column (_("Tags"), plugin_columns.tags);
91         plugin_display.append_column (_("Creator"), plugin_columns.creator);
92         plugin_display.append_column (_("Type"), plugin_columns.type_name);
93         plugin_display.append_column (_("Audio I/O"),plugin_columns.audio_io);
94         plugin_display.append_column (_("MIDI I/O"), plugin_columns.midi_io);
95         plugin_display.set_headers_visible (true);
96         plugin_display.set_headers_clickable (true);
97         plugin_display.set_reorderable (false);
98         plugin_display.set_rules_hint (true);
99         plugin_display.add_object_drag (plugin_columns.plugin.index(), "PluginInfoPtr");
100         plugin_display.set_drag_column (plugin_columns.name.index());
101
102         // setting a sort-column prevents re-ordering via Drag/Drop
103         plugin_model->set_sort_column (plugin_columns.name.index(), Gtk::SORT_ASCENDING);
104
105         plugin_display.set_name("PluginSelectorDisplay");
106         plugin_display.signal_row_activated().connect_notify (sigc::mem_fun(*this, &PluginSelector::row_activated));
107         plugin_display.get_selection()->signal_changed().connect (sigc::mem_fun(*this, &PluginSelector::display_selection_changed));
108
109         CellRendererToggle* fav_cell = dynamic_cast<CellRendererToggle*>(plugin_display.get_column_cell_renderer (0));
110         fav_cell->property_activatable() = true;
111         fav_cell->property_radio() = true;
112         fav_cell->signal_toggled().connect (sigc::mem_fun (*this, &PluginSelector::favorite_changed));
113
114         CellRendererToggle* hidden_cell = dynamic_cast<CellRendererToggle*>(plugin_display.get_column_cell_renderer (1));
115         hidden_cell->property_activatable() = true;
116         hidden_cell->property_radio() = true;
117         hidden_cell->signal_toggled().connect (sigc::mem_fun (*this, &PluginSelector::hidden_changed));
118
119         scroller.set_border_width(10);
120         scroller.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
121         scroller.add(plugin_display);
122
123         amodel = Gtk::ListStore::create(acols);
124         added_list.set_model (amodel);
125         added_list.append_column (_("Plugins to be connected"), acols.text);
126         added_list.set_headers_visible (true);
127         added_list.set_reorderable (false);
128
129         for (int i = 2; i <= 7; ++i) {
130                 Gtk::TreeView::Column* column = plugin_display.get_column(i);
131                 if (column) {
132                         column->set_sort_column(i);
133                 }
134         }
135
136         ascroller.set_border_width(10);
137         ascroller.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
138         ascroller.add(added_list);
139         btn_add = manage(new Gtk::Button(Stock::ADD));
140         set_tooltip(*btn_add, _("Add a plugin to the effect list"));
141         btn_add->set_sensitive (false);
142         btn_remove = manage(new Gtk::Button(Stock::REMOVE));
143         btn_remove->set_sensitive (false);
144         set_tooltip(*btn_remove, _("Remove a plugin from the effect list"));
145
146         btn_add->set_name("PluginSelectorButton");
147         btn_remove->set_name("PluginSelectorButton");
148
149         /* SEARCH */
150
151         Gtk::Table* search_table = manage(new Gtk::Table(2, 2));
152
153         search_entry.signal_changed().connect (sigc::mem_fun (*this, &PluginSelector::search_entry_changed));
154         search_clear_button.signal_clicked().connect (sigc::mem_fun (*this, &PluginSelector::search_clear_button_clicked));
155
156         _search_name_checkbox = manage (new ArdourButton (_("Name"), ArdourButton::led_default_elements, true));
157         _search_name_checkbox->set_active(true);
158         _search_name_checkbox->set_name ("pluginlist filter button");
159
160         _search_tags_checkbox = manage (new ArdourButton (_("Tags"), ArdourButton::led_default_elements, true));
161         _search_tags_checkbox->set_active(true);
162         _search_tags_checkbox->set_name ("pluginlist filter button");
163
164         _search_ignore_checkbox = manage (new ArdourButton(_("Ignore Filters when searching"), ArdourButton::led_default_elements, true));
165         _search_ignore_checkbox->set_active(true);
166         _search_ignore_checkbox->set_name ("pluginlist filter button");
167
168         Gtk::Label* search_help_label1 = manage (new Label(
169                 _("All search terms must be matched."), Gtk::ALIGN_LEFT));
170
171         Gtk::Label* search_help_label2 = manage (new Label(
172                 _("Ex: \"ess dyn\" will find \"dynamic de-esser\" but not \"de-esser\"."), Gtk::ALIGN_LEFT));
173
174         search_table->attach (search_entry,            0, 3, 0, 1, FILL|EXPAND, FILL);
175         search_table->attach (search_clear_button,     3, 4, 0, 1, FILL, FILL);
176         search_table->attach (*_search_name_checkbox,  0, 1, 1, 2, FILL, FILL);
177         search_table->attach (*_search_tags_checkbox,  1, 2, 1, 2, FILL, FILL);
178         search_table->attach (*_search_ignore_checkbox,2, 3, 1, 2, FILL, FILL);
179         search_table->attach (*search_help_label1,     0, 3, 2, 3, FILL, FILL);
180         search_table->attach (*search_help_label2,     0, 3, 3, 4, FILL, FILL);
181
182         search_table->set_border_width (4);
183         search_table->set_col_spacings (4);
184         search_table->set_row_spacings (4);
185
186         Frame* search_frame = manage (new Frame);
187         search_frame->set_name ("BaseFrame");
188         search_frame->set_label (_("Search"));
189         search_frame->add (*search_table);
190         search_frame->show_all ();
191
192         _search_name_checkbox->signal_clicked.connect (sigc::mem_fun (*this, &PluginSelector::refill));
193         _search_tags_checkbox->signal_clicked.connect (sigc::mem_fun (*this, &PluginSelector::refill));
194         _search_ignore_checkbox->signal_clicked.connect (sigc::mem_fun (*this, &PluginSelector::set_sensitive_widgets));
195
196         /* FILTER */
197
198         Gtk::RadioButtonGroup fil_radio_group;
199
200         _fil_effects_radio = manage (new RadioButton (fil_radio_group, _("Show Effects Only")));
201         _fil_instruments_radio = manage (new RadioButton (fil_radio_group, _("Show Instruments Only")));
202         _fil_utils_radio = manage (new RadioButton (fil_radio_group, _("Show Utilities Only")));
203         _fil_favorites_radio = manage (new RadioButton (fil_radio_group, _("Show Favorites Only")));
204         _fil_hidden_radio = manage (new RadioButton (fil_radio_group, _("Show Hidden Only")));
205         _fil_all_radio = manage (new RadioButton (fil_radio_group, _("Show All")));
206
207         //_fil_type_combo = manage (new ComboBoxText);
208         _fil_type_combo.append_text_item (_("Show All Formats"));
209         _fil_type_combo.append_text_item (X_("VST"));
210 #ifdef AUDIOUNIT_SUPPORT
211         _fil_type_combo.append_text_item (X_("AudioUnit"));
212 #endif
213 #ifdef LV2_SUPPORT
214         _fil_type_combo.append_text_item (X_("LV2"));
215 #endif
216         _fil_type_combo.append_text_item (X_("Lua"));
217         _fil_type_combo.append_text_item (X_("LADSPA"));
218         _fil_type_combo.set_text (_("Show All Formats"));
219
220         /* note: _fil_creator_combo menu gets filled in build_plugin_menu */
221         _fil_creator_combo.set_text_ellipsize (Pango::ELLIPSIZE_END);
222         _fil_creator_combo.set_layout_ellipsize_width (PANGO_SCALE * 160 * UIConfiguration::instance ().get_ui_scale ());
223
224         VBox* filter_vbox = manage (new VBox);
225         filter_vbox->pack_start (*_fil_effects_radio,     false, false);
226         filter_vbox->pack_start (*_fil_instruments_radio, false, false);
227         filter_vbox->pack_start (*_fil_utils_radio,       false, false);
228         filter_vbox->pack_start (*_fil_favorites_radio,   false, false);
229         filter_vbox->pack_start (*_fil_hidden_radio,      false, false);
230         filter_vbox->pack_start (*_fil_all_radio,         false, false);
231         filter_vbox->pack_start (_fil_type_combo,         false, false);
232         filter_vbox->pack_start (_fil_creator_combo,      false, false);
233
234         filter_vbox->set_border_width (4);
235         filter_vbox->set_spacing (4);
236
237         Frame* filter_frame = manage (new Frame);
238         filter_frame->set_name ("BaseFrame");
239         filter_frame->set_label (_("Filter"));
240         filter_frame->add (*filter_vbox);
241         filter_frame->show_all ();
242
243         _fil_effects_radio->signal_clicked().connect (sigc::mem_fun (*this, &PluginSelector::refill));
244         _fil_instruments_radio->signal_clicked().connect (sigc::mem_fun (*this, &PluginSelector::refill));
245         _fil_utils_radio->signal_clicked().connect (sigc::mem_fun (*this, &PluginSelector::refill));
246         _fil_favorites_radio->signal_clicked().connect (sigc::mem_fun (*this, &PluginSelector::refill));
247         _fil_hidden_radio->signal_clicked().connect (sigc::mem_fun (*this, &PluginSelector::refill));
248
249         _fil_type_combo.StateChanged.connect (sigc::mem_fun (*this, &PluginSelector::refill));
250         _fil_creator_combo.StateChanged.connect (sigc::mem_fun (*this, &PluginSelector::refill));
251
252         /* TAG entry */
253
254         Gtk::Table* tagging_table = manage(new Gtk::Table(1, 2));
255         tagging_table->set_border_width (4);
256         tagging_table->set_col_spacings (4);
257         tagging_table->set_row_spacings (4);
258
259         tag_entry = manage (new Gtk::Entry);
260         tag_entry_connection = tag_entry->signal_changed().connect (sigc::mem_fun (*this, &PluginSelector::tag_entry_changed));
261
262         tag_reset_button = manage (new Button (_("Reset")));
263         tag_reset_button->signal_clicked().connect (sigc::mem_fun (*this, &PluginSelector::tag_reset_button_clicked));
264
265         Gtk::Label* tagging_help_label1 = manage (new Label(
266                 _("Enter space-separated, one-word Tags for the selected plugin."), Gtk::ALIGN_LEFT));
267
268         Gtk::Label* tagging_help_label2 = manage (new Label(
269                 _("You can include dashes, colons or underscores in a Tag."), Gtk::ALIGN_LEFT));
270
271         Gtk::Label* tagging_help_label3 = manage (new Label(
272                 _("Ex: \"dynamic de-esser vocal\" applies 3 Tags."), Gtk::ALIGN_LEFT));
273
274         int p = 0;
275         tagging_table->attach (*tag_entry,           0, 1, p, p+1, FILL|EXPAND, FILL);
276         tagging_table->attach (*tag_reset_button,    1, 2, p, p+1, FILL, FILL); p++;
277         tagging_table->attach (*tagging_help_label1, 0, 2, p, p+1, FILL, FILL); p++;
278         tagging_table->attach (*tagging_help_label2, 0, 2, p, p+1, FILL, FILL); p++;
279         tagging_table->attach (*tagging_help_label3, 0, 2, p, p+1, FILL, FILL); p++;
280
281         Frame* tag_frame = manage (new Frame);
282         tag_frame->set_name ("BaseFrame");
283         tag_frame->set_label (_("Tags for Selected Plugin"));
284         tag_frame->add (*tagging_table);
285         tag_frame->show_all ();
286
287         /* Add & remove buttons */
288
289         HBox* add_remove = manage (new HBox);
290         add_remove->pack_start (*btn_add, true, true);
291         add_remove->pack_start (*btn_remove, true, true);
292
293         btn_add->signal_clicked().connect(sigc::mem_fun(*this, &PluginSelector::btn_add_clicked));
294         btn_remove->signal_clicked().connect(sigc::mem_fun(*this, &PluginSelector::btn_remove_clicked));
295         added_list.get_selection()->signal_changed().connect (sigc::mem_fun(*this, &PluginSelector::added_list_selection_changed));
296         added_list.signal_button_press_event().connect_notify (mem_fun(*this, &PluginSelector::added_row_clicked));
297
298         added_list.set_name("PluginSelectorList");
299
300         /* Top-level Layout */
301
302         VBox* to_be_inserted_vbox = manage (new VBox);
303         to_be_inserted_vbox->pack_start (ascroller);
304         to_be_inserted_vbox->pack_start (*add_remove, false, false);
305         to_be_inserted_vbox->set_size_request (200, -1);
306
307         Gtk::Table* table = manage(new Gtk::Table(3, 3));
308         table->set_size_request(-1, 600);
309         table->attach (scroller,               0, 3, 0, 5); /* this is the main plugin list */
310         table->attach (*search_frame,          0, 1, 6, 7, FILL, FILL, 5, 5);
311         table->attach (*tag_frame,             0, 1, 7, 8, FILL, FILL, 5, 5);
312         table->attach (*filter_frame,          1, 2, 6, 8, FILL, FILL, 5, 5);
313         table->attach (*to_be_inserted_vbox,   2, 3, 6, 8, FILL|EXPAND, FILL, 5, 5); /* to be inserted... */
314
315         add_button (Stock::CLOSE, RESPONSE_CLOSE);
316         add_button (_("Insert Plugin(s)"), RESPONSE_APPLY);
317         set_default_response (RESPONSE_APPLY);
318         set_response_sensitive (RESPONSE_APPLY, false);
319         get_vbox()->pack_start (*table);
320
321         table->set_name("PluginSelectorTable");
322
323         plugin_display.grab_focus();
324
325         build_plugin_menu ();
326         display_selection_changed ();
327 }
328
329 PluginSelector::~PluginSelector ()
330 {
331         delete _plugin_menu;
332 }
333
334 void
335 PluginSelector::row_activated(Gtk::TreeModel::Path, Gtk::TreeViewColumn*)
336 {
337         btn_add_clicked();
338 }
339
340 void
341 PluginSelector::added_row_clicked(GdkEventButton* event)
342 {
343         if (event->type == GDK_2BUTTON_PRESS)
344                 btn_remove_clicked();
345 }
346
347 bool
348 PluginSelector::show_this_plugin (const PluginInfoPtr& info, const std::string& searchstr)
349 {
350         string mode;
351         bool maybe_show = false;
352         PluginManager::PluginStatusType status = manager.get_status (info);
353
354         if (!searchstr.empty()) {
355
356                 std::string compstr;
357
358                 if (_search_name_checkbox->get_active()) { /* name contains */
359                         compstr = info->name;
360                         transform (compstr.begin(), compstr.end(), compstr.begin(), ::toupper);
361                         if (compstr.find (searchstr) != string::npos) {
362                                 maybe_show = true;
363                         }
364                 }
365
366                 if (_search_tags_checkbox->get_active()) { /* tag contains */
367                         compstr = manager.get_tags_as_string (info);
368                         transform (compstr.begin(), compstr.end(), compstr.begin(), ::toupper);
369                         if (compstr.find (searchstr) != string::npos) {
370                                 maybe_show = true;
371                         }
372                 }
373
374                 if (!maybe_show) {
375                         return false;
376                 }
377
378                 /* user asked to ignore filters */
379                 if (maybe_show && _search_ignore_checkbox->get_active()) {
380                         if (status == PluginManager::Hidden) {
381                                 return false;
382                         }
383                         if (status == PluginManager::Concealed) {
384                                 return false;
385                         }
386                         return true;
387                 }
388         }
389
390         if (_fil_effects_radio->get_active() && !info->is_effect()) {
391                 return false;
392         }
393
394         if (_fil_instruments_radio->get_active() && !info->is_instrument()) {
395                 return false;
396         }
397
398         if (_fil_utils_radio->get_active() && !(info->is_utility() || info->is_analyzer())) {
399                 return false;
400         }
401
402         if (_fil_favorites_radio->get_active() && status != PluginManager::Favorite) {
403                 return false;
404         }
405
406         if (_fil_hidden_radio->get_active() && (status != PluginManager::Hidden && status != PluginManager::Concealed)) {
407                 return false;
408         }
409
410         if (!_fil_hidden_radio->get_active() && status == PluginManager::Hidden) {
411                 return false;
412         }
413
414         if (!_fil_hidden_radio->get_active() && status == PluginManager::Concealed) {
415                 return false;
416         }
417
418         /* Filter "type" combobox */
419
420         if (_fil_type_combo.get_text() == X_("VST") && PluginManager::to_generic_vst(info->type) != LXVST) {
421                 return false;
422         }
423
424         if (_fil_type_combo.get_text() == X_("AudioUnit") && info->type != AudioUnit) {
425                 return false;
426         }
427
428 #ifdef LV2_SUPPORT
429         if (_fil_type_combo.get_text() == X_("LV2") && info->type != LV2) {
430                 return false;
431         }
432 #endif
433
434         if (_fil_type_combo.get_text() == X_("Lua") && info->type != Lua) {
435                 return false;
436         }
437
438         if (_fil_type_combo.get_text() == X_("LADSPA") && info->type != LADSPA) {
439                 return false;
440         }
441
442         /* Filter "creator" combobox */
443
444         if (_fil_creator_combo.get_text() != _("Show All Creators")) {
445                 if (_fil_creator_combo.get_text() != info->creator) {
446                         return false;
447                 }
448         }
449
450         return true;
451 }
452
453 void
454 PluginSelector::setup_search_string (string& searchstr)
455 {
456         searchstr = search_entry.get_text ();
457         transform (searchstr.begin(), searchstr.end(), searchstr.begin(), ::toupper);
458 }
459
460 void
461 PluginSelector::set_sensitive_widgets ()
462 {
463         if (_search_ignore_checkbox->get_active() && !search_entry.get_text().empty()) {
464                 _fil_effects_radio->set_sensitive(false);
465                 _fil_instruments_radio->set_sensitive(false);
466                 _fil_utils_radio->set_sensitive(false);
467                 _fil_favorites_radio->set_sensitive(false);
468                 _fil_hidden_radio->set_sensitive(false);
469                 _fil_all_radio->set_sensitive(false);
470                 _inhibit_refill = true;
471                 _fil_type_combo.set_sensitive(false);
472                 _fil_creator_combo.set_sensitive(false);
473                 _inhibit_refill = false;
474         } else {
475                 _fil_effects_radio->set_sensitive(true);
476                 _fil_instruments_radio->set_sensitive(true);
477                 _fil_utils_radio->set_sensitive(true);
478                 _fil_favorites_radio->set_sensitive(true);
479                 _fil_hidden_radio->set_sensitive(true);
480                 _fil_all_radio->set_sensitive(true);
481                 _inhibit_refill = true;
482                 _fil_type_combo.set_sensitive(true);
483                 _fil_creator_combo.set_sensitive(true);
484                 _inhibit_refill = false;
485         }
486         if (!search_entry.get_text().empty()) {
487                 refill ();
488         }
489 }
490
491 void
492 PluginSelector::refill ()
493 {
494         if (_inhibit_refill) {
495                 return;
496         }
497
498         std::string searchstr;
499
500         in_row_change = true;
501
502         plugin_display.set_model (Glib::RefPtr<Gtk::TreeStore>(0));
503
504         int sort_col;
505         SortType sort_type;
506         bool sorted = plugin_model->get_sort_column_id (sort_col, sort_type);
507
508         /* Disable sorting to gain performance */
509         plugin_model->set_sort_column (-2, SORT_ASCENDING);
510
511         plugin_model->clear ();
512
513         setup_search_string (searchstr);
514
515         ladspa_refiller (searchstr);
516         lv2_refiller (searchstr);
517         vst_refiller (searchstr);
518         lxvst_refiller (searchstr);
519         mac_vst_refiller (searchstr);
520         au_refiller (searchstr);
521         lua_refiller (searchstr);
522
523         in_row_change = false;
524
525         plugin_display.set_model (plugin_model);
526         if (sorted) {
527                 plugin_model->set_sort_column (sort_col, sort_type);
528         }
529 }
530
531 void
532 PluginSelector::refiller (const PluginInfoList& plugs, const::std::string& searchstr, const char* type)
533 {
534         char buf[16];
535
536         for (PluginInfoList::const_iterator i = plugs.begin(); i != plugs.end(); ++i) {
537
538                 if (show_this_plugin (*i, searchstr)) {
539
540                         TreeModel::Row newrow = *(plugin_model->append());
541
542                         PluginManager::PluginStatusType status = manager.get_status (*i);
543                         newrow[plugin_columns.favorite] = status == PluginManager::Favorite;
544                         newrow[plugin_columns.hidden] = status == PluginManager::Hidden;
545
546                         string name = (*i)->name;
547                         if (name.length() > 48) {
548                                 name = name.substr (0, 48);
549                                 name.append("...");
550                         }
551                         newrow[plugin_columns.name] = name;
552
553                         newrow[plugin_columns.type_name] = type;
554
555                         /* Creator */
556                         string creator = (*i)->creator;
557                         string::size_type pos = 0;
558                         if ((*i)->type == ARDOUR::LADSPA) {
559                                 /* stupid LADSPA creator strings */
560 #ifdef PLATFORM_WINDOWS
561                                 while (pos < creator.length() && creator[pos] > -2 && creator[pos] < 256 && (isalnum (creator[pos]) || isspace (creator[pos]))) ++pos;
562 #else
563                                 while (pos < creator.length() && (isalnum (creator[pos]) || isspace (creator[pos]))) ++pos;
564 #endif
565                         } else {
566                                 pos = creator.length ();
567                         }
568                         // If there were too few characters to create a
569                         // meaningful name, mark this creator as 'Unknown'
570                         if (creator.length() < 2 || pos < 3) {
571                                 creator = "Unknown";
572                         } else{
573                                 creator = creator.substr (0, pos);
574                         }
575
576                         if (creator.length() > MAX_CREATOR_LEN) {
577                                 creator = creator.substr (0, MAX_CREATOR_LEN);
578                                 creator.append("...");
579                         }
580                         newrow[plugin_columns.creator] = creator;
581
582                         /* Tags */
583                         string tags = manager.get_tags_as_string(*i);
584                         if (tags.length() > 32) {
585                                 tags = tags.substr (0, 32);
586                                 tags.append("...");
587                         }
588                         newrow[plugin_columns.tags] = tags;
589
590                         if ((*i)->reconfigurable_io ()) {
591                                 newrow[plugin_columns.audio_io] = "* / *";
592                                 newrow[plugin_columns.midi_io] = "* / *";
593                         } else {
594                                 snprintf (buf, sizeof(buf), "%d / %d", (*i)->n_inputs.n_audio(), (*i)->n_outputs.n_audio());
595                                 newrow[plugin_columns.audio_io] = buf;
596                                 snprintf (buf, sizeof(buf), "%d / %d", (*i)->n_inputs.n_midi(), (*i)->n_outputs.n_midi());
597                                 newrow[plugin_columns.midi_io] = buf;
598                         }
599
600                         newrow[plugin_columns.plugin] = *i;
601                 }
602         }
603 }
604
605 void
606 PluginSelector::ladspa_refiller (const std::string& searchstr)
607 {
608         refiller (manager.ladspa_plugin_info(), searchstr, "LADSPA");
609 }
610
611 void
612 PluginSelector::lua_refiller (const std::string& searchstr)
613 {
614         refiller (manager.lua_plugin_info(), searchstr, "Lua");
615 }
616
617 void
618 PluginSelector::lv2_refiller (const std::string& searchstr)
619 {
620 #ifdef LV2_SUPPORT
621         refiller (manager.lv2_plugin_info(), searchstr, "LV2");
622 #endif
623 }
624
625 void
626 #ifdef WINDOWS_VST_SUPPORT
627 PluginSelector::vst_refiller (const std::string& searchstr)
628 #else
629 PluginSelector::vst_refiller (const std::string&)
630 #endif
631 {
632 #ifdef WINDOWS_VST_SUPPORT
633         refiller (manager.windows_vst_plugin_info(), searchstr, "VST");
634 #endif
635 }
636
637 void
638 #ifdef LXVST_SUPPORT
639 PluginSelector::lxvst_refiller (const std::string& searchstr)
640 #else
641 PluginSelector::lxvst_refiller (const std::string&)
642 #endif
643 {
644 #ifdef LXVST_SUPPORT
645         refiller (manager.lxvst_plugin_info(), searchstr, "LXVST");
646 #endif
647 }
648
649 void
650 #ifdef MACVST_SUPPORT
651 PluginSelector::mac_vst_refiller (const std::string& searchstr)
652 #else
653 PluginSelector::mac_vst_refiller (const std::string&)
654 #endif
655 {
656 #ifdef MACVST_SUPPORT
657         refiller (manager.mac_vst_plugin_info(), searchstr, "MacVST");
658 #endif
659 }
660
661 void
662 #ifdef AUDIOUNIT_SUPPORT
663 PluginSelector::au_refiller (const std::string& searchstr)
664 #else
665 PluginSelector::au_refiller (const std::string&)
666 #endif
667 {
668 #ifdef AUDIOUNIT_SUPPORT
669         refiller (manager.au_plugin_info(), searchstr, "AU");
670 #endif
671 }
672
673 PluginPtr
674 PluginSelector::load_plugin (PluginInfoPtr pi)
675 {
676         if (_session == 0) {
677                 return PluginPtr();
678         }
679
680         return pi->load (*_session);
681 }
682
683 void
684 PluginSelector::btn_add_clicked()
685 {
686         if (plugin_display.get_selection()->count_selected_rows() == 0) {
687                 /* may happen with ctrl + double-click un-selecting but activating a row */
688                 return;
689         }
690         std::string name;
691         PluginInfoPtr pi;
692         TreeModel::Row newrow = *(amodel->append());
693         TreeModel::Row row;
694
695         row = *(plugin_display.get_selection()->get_selected());
696         name = row[plugin_columns.name];
697         pi = row[plugin_columns.plugin];
698
699         newrow[acols.text] = name;
700         newrow[acols.plugin] = pi;
701
702         if (!amodel->children().empty()) {
703                 set_response_sensitive (RESPONSE_APPLY, true);
704         }
705 }
706
707 void
708 PluginSelector::btn_remove_clicked()
709 {
710         TreeModel::iterator iter = added_list.get_selection()->get_selected();
711
712         amodel->erase(iter);
713         if (amodel->children().empty()) {
714                 set_response_sensitive (RESPONSE_APPLY, false);
715         }
716 }
717
718 void
719 PluginSelector::display_selection_changed()
720 {
721         tag_entry_connection.block ();
722         if (plugin_display.get_selection()->count_selected_rows() != 0) {
723
724                 /* a plugin row is selected; allow the user to edit the "tags" on it. */
725                 TreeModel::Row row = *(plugin_display.get_selection()->get_selected());
726                 string tags = manager.get_tags_as_string (row[plugin_columns.plugin]);
727                 tag_entry->set_text (tags);
728
729                 tag_entry->set_sensitive (true);
730                 tag_reset_button->set_sensitive (true);
731                 btn_add->set_sensitive (true);
732
733         } else {
734                 tag_entry->set_text ("");
735
736                 tag_entry->set_sensitive (false);
737                 tag_reset_button->set_sensitive (false);
738                 btn_add->set_sensitive (false);
739         }
740         tag_entry_connection.unblock ();
741 }
742
743 void
744 PluginSelector::added_list_selection_changed()
745 {
746         if (added_list.get_selection()->count_selected_rows() != 0) {
747                 btn_remove->set_sensitive (true);
748         } else {
749                 btn_remove->set_sensitive (false);
750         }
751 }
752
753 int
754 PluginSelector::run ()
755 {
756         ResponseType r;
757         TreeModel::Children::iterator i;
758
759         bool finish = false;
760
761         while (!finish) {
762
763                 SelectedPlugins plugins;
764                 r = (ResponseType) Dialog::run ();
765
766                 switch (r) {
767                 case RESPONSE_APPLY:
768                         for (i = amodel->children().begin(); i != amodel->children().end(); ++i) {
769                                 PluginInfoPtr pp = (*i)[acols.plugin];
770                                 PluginPtr p = load_plugin (pp);
771                                 if (p) {
772                                         plugins.push_back (p);
773                                 } else {
774                                         MessageDialog msg (string_compose (_("The plugin \"%1\" could not be loaded\n\nSee the Log window for more details (maybe)"), pp->name));
775                                         msg.run ();
776                                 }
777                         }
778                         if (interested_object && !plugins.empty()) {
779                                 finish = !interested_object->use_plugins (plugins);
780                         }
781
782                         break;
783
784                 default:
785                         finish = true;
786                         break;
787                 }
788         }
789
790
791         hide();
792         amodel->clear();
793         interested_object = 0;
794
795         if (_need_tag_save) {
796                 manager.save_tags();
797         }
798
799         if (_need_status_save) {
800                 manager.save_statuses();
801         }
802         if (_need_menu_rebuild) {
803                 build_plugin_menu ();
804         }
805
806         return (int) r;
807 }
808
809 void
810 PluginSelector::search_clear_button_clicked ()
811 {
812         search_entry.set_text ("");
813 }
814
815 void
816 PluginSelector::tag_reset_button_clicked ()
817 {
818         if (plugin_display.get_selection()->count_selected_rows() != 0) {
819                 TreeModel::Row row = *(plugin_display.get_selection()->get_selected());
820                 ARDOUR::PluginInfoPtr pi = row[plugin_columns.plugin];
821                 manager.reset_tags (pi);
822                 display_selection_changed ();
823                 _need_tag_save = true;
824         }
825 }
826
827 void
828 PluginSelector::search_entry_changed ()
829 {
830         set_sensitive_widgets();
831         if (search_entry.get_text().empty()) {
832                 refill ();
833         }
834 }
835
836 void
837 PluginSelector::tag_entry_changed ()
838 {
839         if (plugin_display.get_selection()->count_selected_rows() != 0) {
840                 TreeModel::Row row = *(plugin_display.get_selection()->get_selected());
841
842                 ARDOUR::PluginInfoPtr pi = row[plugin_columns.plugin];
843                 manager.set_tags (pi->type, pi->unique_id, tag_entry->get_text(), pi->name, PluginManager::FromGui);
844
845                 _need_tag_save = true;
846         }
847 }
848
849 void
850 PluginSelector::tags_changed (PluginType t, std::string unique_id, std::string tags)
851 {
852         if (plugin_display.get_selection()->count_selected_rows() != 0) {
853                 TreeModel::Row row = *(plugin_display.get_selection()->get_selected());
854                 if (tags.length() > 32) {
855                         tags = tags.substr (0, 32);
856                         tags.append ("...");
857                 }
858                 row[plugin_columns.tags] = tags;
859         }
860 }
861
862 void
863 PluginSelector::plugin_status_changed (PluginType t, std::string uid, PluginManager::PluginStatusType stat)
864 {
865         Gtk::TreeModel::iterator i;
866         for (i = plugin_model->children().begin(); i != plugin_model->children().end(); ++i) {
867                 PluginInfoPtr pp = (*i)[plugin_columns.plugin];
868                 if ((pp->type == t) && (pp->unique_id == uid)) {
869                         (*i)[plugin_columns.favorite] = (stat == PluginManager::Favorite) ? true : false;
870                         (*i)[plugin_columns.hidden] = (stat == PluginManager::Hidden) ? true : false;
871
872                         /* if plug was hidden, remove it from the view */
873                         if (stat == PluginManager::Hidden || stat == PluginManager::Concealed) {
874                                 if (!_fil_hidden_radio->get_active() && !_fil_all_radio->get_active()) {
875                                         plugin_model->erase(i);
876                                 }
877                         } else if (_fil_hidden_radio->get_active()) {
878                                 plugin_model->erase(i);
879                         }
880                         /* if no longer a favorite, remove it from the view */
881                         if (stat != PluginManager::Favorite && _fil_favorites_radio->get_active()) {
882                                         plugin_model->erase(i);
883                         }
884
885                         return;
886                 }
887         }
888 }
889
890 void
891 PluginSelector::on_show ()
892 {
893         ArdourDialog::on_show ();
894         search_entry.grab_focus ();
895
896         refill ();
897
898         _need_tag_save = false;
899         _need_status_save = false;
900 }
901
902 struct PluginMenuCompareByCreator {
903         bool operator() (PluginInfoPtr a, PluginInfoPtr b) const {
904                 int cmp;
905
906                 cmp = cmp_nocase_utf8 (a->creator, b->creator);
907
908                 if (cmp < 0) {
909                         return true;
910                 } else if (cmp == 0) {
911                         /* same creator ... compare names */
912                         if (cmp_nocase_utf8 (a->name, b->name) < 0) {
913                                 return true;
914                         }
915                 }
916                 return false;
917         }
918 };
919
920 struct PluginMenuCompareByName {
921         bool operator() (PluginInfoPtr a, PluginInfoPtr b) const {
922                 int cmp;
923
924                 cmp = cmp_nocase_utf8 (a->name, b->name);
925
926                 if (cmp < 0) {
927                         return true;
928                 } else if (cmp == 0) {
929                         /* same name ... compare type */
930                         if (a->type < b->type) {
931                                 return true;
932                         }
933                 }
934                 return false;
935         }
936 };
937
938 /** @return Plugin menu. The caller should not delete it */
939 Gtk::Menu*
940 PluginSelector::plugin_menu()
941 {
942         return _plugin_menu;
943 }
944
945 void
946 PluginSelector::build_plugin_menu ()
947 {
948         if (is_visible ()) {
949                 _need_menu_rebuild = true;
950                 return;
951         }
952         _need_menu_rebuild = false;
953         PluginInfoList all_plugs;
954
955         all_plugs.insert (all_plugs.end(), manager.ladspa_plugin_info().begin(), manager.ladspa_plugin_info().end());
956         all_plugs.insert (all_plugs.end(), manager.lua_plugin_info().begin(), manager.lua_plugin_info().end());
957 #ifdef WINDOWS_VST_SUPPORT
958         all_plugs.insert (all_plugs.end(), manager.windows_vst_plugin_info().begin(), manager.windows_vst_plugin_info().end());
959 #endif
960 #ifdef LXVST_SUPPORT
961         all_plugs.insert (all_plugs.end(), manager.lxvst_plugin_info().begin(), manager.lxvst_plugin_info().end());
962 #endif
963 #ifdef MACVST_SUPPORT
964         all_plugs.insert (all_plugs.end(), manager.mac_vst_plugin_info().begin(), manager.mac_vst_plugin_info().end());
965 #endif
966 #ifdef AUDIOUNIT_SUPPORT
967         all_plugs.insert (all_plugs.end(), manager.au_plugin_info().begin(), manager.au_plugin_info().end());
968 #endif
969 #ifdef LV2_SUPPORT
970         all_plugs.insert (all_plugs.end(), manager.lv2_plugin_info().begin(), manager.lv2_plugin_info().end());
971 #endif
972
973         using namespace Menu_Helpers;
974
975         delete _plugin_menu;
976
977         _plugin_menu = new Menu;
978         _plugin_menu->set_name("ArdourContextMenu");
979
980         MenuList& items = _plugin_menu->items();
981         items.clear ();
982
983         Gtk::Menu* favs = create_favs_menu(all_plugs);
984         items.push_back (MenuElem (_("Favorites"), *manage (favs)));
985
986         items.push_back (MenuElem (_("Plugin Manager..."), sigc::mem_fun (*this, &PluginSelector::show_manager)));
987         items.push_back (SeparatorElem ());
988
989         Menu* by_creator = create_by_creator_menu(all_plugs);
990         items.push_back (MenuElem (_("By Creator"), *manage (by_creator)));
991
992         Menu* by_tags = create_by_tags_menu(all_plugs);
993         items.push_back (MenuElem (_("By Tags"), *manage (by_tags)));
994 }
995
996 string
997 GetPluginTypeStr(PluginInfoPtr info)
998 {
999         string type;
1000
1001         switch (info->type) {
1002         case LADSPA:
1003                 type = X_(" (LADSPA)");
1004                 break;
1005         case AudioUnit:
1006                 type = X_(" (AU)");
1007                 break;
1008         case LV2:
1009                 type = X_(" (LV2)");
1010                 break;
1011         case Windows_VST:
1012         case LXVST:
1013         case MacVST:
1014                 type = X_(" (VST)");
1015                 break;
1016         case Lua:
1017                 type = X_(" (Lua)");
1018                 break;
1019         }
1020
1021         return type;
1022 }
1023
1024 Gtk::Menu*
1025 PluginSelector::create_favs_menu (PluginInfoList& all_plugs)
1026 {
1027         using namespace Menu_Helpers;
1028
1029         Menu* favs = new Menu();
1030         favs->set_name("ArdourContextMenu");
1031
1032         PluginMenuCompareByName cmp_by_name;
1033         all_plugs.sort (cmp_by_name);
1034
1035         for (PluginInfoList::const_iterator i = all_plugs.begin(); i != all_plugs.end(); ++i) {
1036                 if (manager.get_status (*i) == PluginManager::Favorite) {
1037                         string typ = GetPluginTypeStr(*i);
1038                         MenuElem elem ((*i)->name + typ, (sigc::bind (sigc::mem_fun (*this, &PluginSelector::plugin_chosen_from_menu), *i)));
1039                         elem.get_child()->set_use_underline (false);
1040                         favs->items().push_back (elem);
1041                 }
1042         }
1043         return favs;
1044 }
1045
1046 Gtk::Menu*
1047 PluginSelector::create_by_creator_menu (ARDOUR::PluginInfoList& all_plugs)
1048 {
1049         _inhibit_refill = true;
1050         _fil_creator_combo.clear_items ();
1051         _fil_creator_combo.append_text_item (_("Show All Creators"));
1052         _fil_creator_combo.set_text (_("Show All Creators"));
1053         _inhibit_refill = false;
1054
1055         using namespace Menu_Helpers;
1056
1057         typedef std::map<std::string,Gtk::Menu*> SubmenuMap;
1058         SubmenuMap creator_submenu_map;
1059
1060         Menu* by_creator = new Menu();
1061         by_creator->set_name("ArdourContextMenu");
1062
1063         MenuList& by_creator_items = by_creator->items();
1064         PluginMenuCompareByCreator cmp_by_creator;
1065         all_plugs.sort (cmp_by_creator);
1066
1067         for (PluginInfoList::const_iterator i = all_plugs.begin(); i != all_plugs.end(); ++i) {
1068
1069                 PluginManager::PluginStatusType status = manager.get_status (*i);
1070                 if (status == PluginManager::Hidden) continue;
1071                 if (status == PluginManager::Concealed) continue;
1072
1073                 string creator = (*i)->creator;
1074
1075                 /* If there were too few characters to create a
1076                  * meaningful name, mark this creator as 'Unknown'
1077                  */
1078                 if (creator.length() < 2) {
1079                         creator = "Unknown";
1080                 }
1081
1082                 SubmenuMap::iterator x;
1083                 Gtk::Menu* submenu;
1084                 if ((x = creator_submenu_map.find (creator)) != creator_submenu_map.end()) {
1085                         submenu = x->second;
1086                 } else {
1087
1088                         _fil_creator_combo.append_text_item (creator);
1089
1090                         submenu = new Gtk::Menu;
1091                         by_creator_items.push_back (MenuElem (creator, *manage (submenu)));
1092                         creator_submenu_map.insert (pair<std::string,Menu*> (creator, submenu));
1093                         submenu->set_name("ArdourContextMenu");
1094                 }
1095                 string typ = GetPluginTypeStr(*i);
1096                 MenuElem elem ((*i)->name+typ, (sigc::bind (sigc::mem_fun (*this, &PluginSelector::plugin_chosen_from_menu), *i)));
1097                 elem.get_child()->set_use_underline (false);
1098                 submenu->items().push_back (elem);
1099         }
1100
1101         return by_creator;
1102 }
1103
1104 Gtk::Menu*
1105 PluginSelector::create_by_tags_menu (ARDOUR::PluginInfoList& all_plugs)
1106 {
1107         using namespace Menu_Helpers;
1108
1109         typedef std::map<std::string,Gtk::Menu*> SubmenuMap;
1110         SubmenuMap tags_submenu_map;
1111
1112         Menu* by_tags = new Menu();
1113         by_tags->set_name("ArdourContextMenu");
1114         MenuList& by_tags_items = by_tags->items();
1115
1116         std::vector<std::string> all_tags = manager.get_all_tags (PluginManager::NoHidden);
1117         for (vector<string>::iterator t = all_tags.begin(); t != all_tags.end(); ++t) {
1118                 Gtk::Menu *submenu = new Gtk::Menu;
1119                 by_tags_items.push_back (MenuElem (*t, *manage (submenu)));
1120                 tags_submenu_map.insert (pair<std::string,Menu*> (*t, submenu));
1121                 submenu->set_name("ArdourContextMenu");
1122         }
1123
1124         PluginMenuCompareByName cmp_by_name;
1125         all_plugs.sort (cmp_by_name);
1126
1127         for (PluginInfoList::const_iterator i = all_plugs.begin(); i != all_plugs.end(); ++i) {
1128
1129                 PluginManager::PluginStatusType status = manager.get_status (*i);
1130                 if (status == PluginManager::Hidden) continue;
1131                 if (status == PluginManager::Concealed) continue;
1132
1133                 /* for each tag in the plugins tag list, add it to that submenu */
1134                 vector<string> tokens = manager.get_tags(*i);
1135                 for (vector<string>::iterator t = tokens.begin(); t != tokens.end(); ++t) {
1136                         SubmenuMap::iterator x;
1137                         Gtk::Menu* submenu;
1138                         if ((x = tags_submenu_map.find (*t)) != tags_submenu_map.end()) {
1139                                 submenu = x->second;
1140                                 string typ = GetPluginTypeStr(*i);
1141                                 MenuElem elem ((*i)->name + typ, (sigc::bind (sigc::mem_fun (*this, &PluginSelector::plugin_chosen_from_menu), *i)));
1142                                 elem.get_child()->set_use_underline (false);
1143                                 submenu->items().push_back (elem);
1144                         }
1145                 }
1146         }
1147         return by_tags;
1148 }
1149
1150 void
1151 PluginSelector::plugin_chosen_from_menu (const PluginInfoPtr& pi)
1152 {
1153         PluginPtr p = load_plugin (pi);
1154
1155         if (p && interested_object) {
1156                 SelectedPlugins plugins;
1157                 plugins.push_back (p);
1158                 interested_object->use_plugins (plugins);
1159         }
1160
1161         interested_object = 0;
1162 }
1163
1164 void
1165 PluginSelector::favorite_changed (const std::string& path)
1166 {
1167         PluginInfoPtr pi;
1168
1169         if (in_row_change) {
1170                 return;
1171         }
1172
1173         in_row_change = true;
1174
1175         TreeModel::iterator iter = plugin_model->get_iter (path);
1176
1177         if (iter) {
1178
1179                 bool favorite = !(*iter)[plugin_columns.favorite];
1180
1181                 /* change state */
1182
1183                 PluginManager::PluginStatusType status = (favorite ? PluginManager::Favorite : PluginManager::Normal);
1184
1185                 /* save new statuses list */
1186
1187                 pi = (*iter)[plugin_columns.plugin];
1188
1189                 manager.set_status (pi->type, pi->unique_id, status);
1190
1191                 _need_status_save = true;
1192         }
1193         in_row_change = false;
1194 }
1195
1196 void
1197 PluginSelector::hidden_changed (const std::string& path)
1198 {
1199         PluginInfoPtr pi;
1200
1201         if (in_row_change) {
1202                 return;
1203         }
1204
1205         in_row_change = true;
1206
1207         TreeModel::iterator iter = plugin_model->get_iter (path);
1208
1209         if (iter) {
1210
1211                 bool hidden = !(*iter)[plugin_columns.hidden];
1212
1213                 /* change state */
1214
1215                 PluginManager::PluginStatusType status = (hidden ? PluginManager::Hidden : PluginManager::Normal);
1216
1217                 /* save new statuses list */
1218
1219                 pi = (*iter)[plugin_columns.plugin];
1220
1221                 manager.set_status (pi->type, pi->unique_id, status);
1222
1223                 _need_status_save = true;
1224         }
1225         in_row_change = false;
1226 }
1227
1228 void
1229 PluginSelector::show_manager ()
1230 {
1231         show_all();
1232         run ();
1233 }
1234
1235 void
1236 PluginSelector::set_interested_object (PluginInterestedObject& obj)
1237 {
1238         interested_object = &obj;
1239 }