NO-OP whitespace (updated GH PR #357)
[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/table.h>
29 #include <gtkmm/stock.h>
30 #include <gtkmm/button.h>
31 #include <gtkmm/notebook.h>
32
33 #include <gtkmm2ext/utils.h>
34
35 #include "pbd/convert.h"
36
37 #include "ardour/plugin_manager.h"
38 #include "ardour/plugin.h"
39 #include "ardour/utils.h"
40
41 #include "plugin_selector.h"
42 #include "gui_thread.h"
43 #include "tooltips.h"
44
45 #include "pbd/i18n.h"
46
47 using namespace ARDOUR;
48 using namespace PBD;
49 using namespace Gtk;
50 using namespace std;
51 using namespace ARDOUR_UI_UTILS;
52
53 static const char* _filter_mode_strings[] = {
54         N_("Name contains"),
55         N_("Type contains"),
56         N_("Category contains"),
57         N_("Author contains"),
58         N_("Library contains"),
59         N_("Favorites only"),
60         N_("Hidden only"),
61         0
62 };
63
64 PluginSelector::PluginSelector (PluginManager& mgr)
65         : ArdourDialog (_("Plugin Manager"), true, false)
66         , filter_button (Stock::CLEAR)
67         , fil_hidden_button (ArdourButton::led_default_elements)
68         , fil_instruments_button (ArdourButton::default_elements)
69         , fil_analysis_button (ArdourButton::default_elements)
70         , fil_utils_button (ArdourButton::default_elements)
71         , manager (mgr)
72         , _show_hidden (false)
73         , _show_instruments (Gtkmm2ext::ImplicitActive)
74         , _show_analysers (Gtkmm2ext::Off)
75         , _show_utils (Gtkmm2ext::Off)
76
77 {
78         set_name ("PluginSelectorWindow");
79         add_events (Gdk::KEY_PRESS_MASK|Gdk::KEY_RELEASE_MASK);
80
81         _plugin_menu = 0;
82         in_row_change = false;
83
84         manager.PluginListChanged.connect (plugin_list_changed_connection, invalidator (*this), boost::bind (&PluginSelector::build_plugin_menu, this), gui_context());
85         manager.PluginListChanged.connect (plugin_list_changed_connection, invalidator (*this), boost::bind (&PluginSelector::refill, this), gui_context());
86         manager.PluginStatusesChanged.connect (plugin_list_changed_connection, invalidator (*this), boost::bind (&PluginSelector::build_plugin_menu, this), gui_context());
87         manager.PluginStatusesChanged.connect (plugin_list_changed_connection, invalidator (*this), boost::bind (&PluginSelector::refill, this), gui_context());
88         build_plugin_menu ();
89
90         plugin_model = Gtk::ListStore::create (plugin_columns);
91         plugin_display.set_model (plugin_model);
92         /* XXX translators: try to convert "Fav" into a short term
93            related to "favorite" and "Hid" into a short term
94            related to "hidden"
95         */
96         plugin_display.append_column (_("Fav"), plugin_columns.favorite);
97         plugin_display.append_column (_("Hide"), plugin_columns.hidden);
98         plugin_display.append_column (_("Available Plugins"), plugin_columns.name);
99         plugin_display.append_column (_("Type"), plugin_columns.type_name);
100         plugin_display.append_column (_("Category"), plugin_columns.category);
101         plugin_display.append_column (_("Creator"), plugin_columns.creator);
102         plugin_display.append_column (_("# Audio In"),plugin_columns.audio_ins);
103         plugin_display.append_column (_("# Audio Out"), plugin_columns.audio_outs);
104         plugin_display.append_column (_("# MIDI In"),plugin_columns.midi_ins);
105         plugin_display.append_column (_("# MIDI Out"), plugin_columns.midi_outs);
106         plugin_display.set_headers_visible (true);
107         plugin_display.set_headers_clickable (true);
108         plugin_display.set_reorderable (false);
109         plugin_display.set_rules_hint (true);
110         plugin_display.add_object_drag (plugin_columns.plugin.index(), "PluginInfoPtr");
111         plugin_display.set_drag_column (plugin_columns.name.index());
112
113         // setting a sort-column prevents re-ordering via Drag/Drop
114         plugin_model->set_sort_column (plugin_columns.name.index(), Gtk::SORT_ASCENDING);
115
116         CellRendererToggle* fav_cell = dynamic_cast<CellRendererToggle*>(plugin_display.get_column_cell_renderer (0));
117         fav_cell->property_activatable() = true;
118         fav_cell->property_radio() = true;
119         fav_cell->signal_toggled().connect (sigc::mem_fun (*this, &PluginSelector::favorite_changed));
120
121         CellRendererToggle* hidden_cell = dynamic_cast<CellRendererToggle*>(plugin_display.get_column_cell_renderer (1));
122         hidden_cell->property_activatable() = true;
123         hidden_cell->property_radio() = true;
124         hidden_cell->signal_toggled().connect (sigc::mem_fun (*this, &PluginSelector::hidden_changed));
125
126         scroller.set_border_width(10);
127         scroller.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
128         scroller.add(plugin_display);
129
130         amodel = Gtk::ListStore::create(acols);
131         added_list.set_model (amodel);
132         added_list.append_column (_("Plugins to be connected"), acols.text);
133         added_list.set_headers_visible (true);
134         added_list.set_reorderable (false);
135
136         for (int i = 0; i <=8; i++) {
137                 Gtk::TreeView::Column* column = plugin_display.get_column(i);
138                 column->set_sort_column(i);
139         }
140
141         ascroller.set_border_width(10);
142         ascroller.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
143         ascroller.add(added_list);
144         btn_add = manage(new Gtk::Button(Stock::ADD));
145         set_tooltip(*btn_add, _("Add a plugin to the effect list"));
146         btn_add->set_sensitive (false);
147         btn_remove = manage(new Gtk::Button(Stock::REMOVE));
148         btn_remove->set_sensitive (false);
149         set_tooltip(*btn_remove, _("Remove a plugin from the effect list"));
150
151         btn_add->set_name("PluginSelectorButton");
152         btn_remove->set_name("PluginSelectorButton");
153
154
155         Gtk::Table* table = manage(new Gtk::Table(7, 11));
156         table->set_size_request(750, 500);
157
158         Gtk::Table* filter_table = manage(new Gtk::Table(2, 5));
159
160         fil_hidden_button.set_name ("pluginlist hide button");
161         fil_hidden_button.set_text (_("Show Hidden"));
162         fil_hidden_button.set_active (_show_hidden);
163         set_tooltip (fil_hidden_button, _("Include hidden plugins in list."));
164
165         fil_instruments_button.set_name ("pluginlist filter button");
166         fil_instruments_button.set_text (_("Instruments"));
167         fil_instruments_button.set_active_state (_show_instruments);
168         set_tooltip (fil_instruments_button, _("Cycle display of instrument plugins (if any)."));
169
170         fil_analysis_button.set_name ("pluginlist filter button");
171         fil_analysis_button.set_text (_("Analyzers"));
172         fil_analysis_button.set_active_state (_show_analysers);
173         set_tooltip (fil_analysis_button, _("Cycle display of analysis plugins (if any)."));
174
175         fil_utils_button.set_name ("pluginlist filter button");
176         fil_utils_button.set_text (_("Utils"));
177         fil_utils_button.set_active_state (_show_utils);
178         set_tooltip (fil_utils_button, _("Cycle display of utility plugins (if any)."));
179
180         vector<string> filter_strings = I18N (_filter_mode_strings);
181         Gtkmm2ext::set_popdown_strings (filter_mode, filter_strings);
182         filter_mode.set_active_text (filter_strings.front());
183
184         fil_hidden_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PluginSelector::fil_hidden_button_release), false);
185         fil_instruments_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PluginSelector::fil_instruments_button_release), false);
186         fil_analysis_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PluginSelector::fil_analysis_button_release), false);
187         fil_utils_button.signal_button_release_event().connect (sigc::mem_fun(*this, &PluginSelector::fil_utils_button_release), false);
188
189         filter_entry.signal_changed().connect (sigc::mem_fun (*this, &PluginSelector::filter_entry_changed));
190         filter_button.signal_clicked().connect (sigc::mem_fun (*this, &PluginSelector::filter_button_clicked));
191         filter_mode.signal_changed().connect (sigc::mem_fun (*this, &PluginSelector::filter_mode_changed));
192
193         filter_table->attach (filter_mode,            0, 1, 0, 1, FILL, FILL);
194         filter_table->attach (filter_entry,           1, 4, 0, 1, FILL|EXPAND, FILL);
195         filter_table->attach (filter_button,          4, 5, 0, 1, FILL, FILL);
196
197         filter_table->attach (fil_hidden_button,      1, 2, 1, 2, FILL, FILL);
198         filter_table->attach (fil_instruments_button, 2, 3, 1, 2, FILL, FILL);
199         filter_table->attach (fil_analysis_button,    3, 4, 1, 2, FILL, FILL);
200         filter_table->attach (fil_utils_button,       4, 5, 1, 2, FILL, FILL);
201
202         filter_table->set_border_width (4);
203         filter_table->set_col_spacings (2);
204         filter_table->set_row_spacings (4);
205
206         Frame* filter_frame = manage (new Frame);
207         filter_frame->set_name ("BaseFrame");
208         filter_frame->set_label (_("Filter"));
209         filter_frame->add (*filter_table);
210
211         filter_frame->show_all ();
212
213         HBox* side_by_side = manage (new HBox);
214         VBox* right_side = manage (new VBox);
215
216         table->attach (scroller, 0, 7, 0, 5);
217         table->attach (*filter_frame, 0, 7, 6, 7, FILL|EXPAND, FILL, 5, 5);
218
219         right_side->pack_start (ascroller);
220
221         HBox* add_remove = manage (new HBox);
222         add_remove->pack_start (*btn_add, true, true);
223         add_remove->pack_start (*btn_remove, true, true);
224
225         right_side->pack_start (*add_remove, false, false);
226         right_side->set_size_request (200, -1);
227
228         side_by_side->pack_start (*table);
229         side_by_side->pack_start (*right_side);
230
231         add_button (Stock::CLOSE, RESPONSE_CLOSE);
232         add_button (_("Insert Plugin(s)"), RESPONSE_APPLY);
233         set_default_response (RESPONSE_APPLY);
234         set_response_sensitive (RESPONSE_APPLY, false);
235         get_vbox()->pack_start (*side_by_side);
236
237         table->set_name("PluginSelectorTable");
238         plugin_display.set_name("PluginSelectorDisplay");
239         //plugin_display.set_name("PluginSelectorList");
240         added_list.set_name("PluginSelectorList");
241
242         plugin_display.signal_row_activated().connect_notify (sigc::mem_fun(*this, &PluginSelector::row_activated));
243         plugin_display.get_selection()->signal_changed().connect (sigc::mem_fun(*this, &PluginSelector::display_selection_changed));
244         plugin_display.grab_focus();
245
246         btn_add->signal_clicked().connect(sigc::mem_fun(*this, &PluginSelector::btn_add_clicked));
247         btn_remove->signal_clicked().connect(sigc::mem_fun(*this, &PluginSelector::btn_remove_clicked));
248         added_list.get_selection()->signal_changed().connect (sigc::mem_fun(*this, &PluginSelector::added_list_selection_changed));
249         added_list.signal_button_press_event().connect_notify (mem_fun(*this, &PluginSelector::added_row_clicked));
250
251         refill ();
252 }
253
254 PluginSelector::~PluginSelector ()
255 {
256         delete _plugin_menu;
257 }
258
259 void
260 PluginSelector::row_activated(Gtk::TreeModel::Path, Gtk::TreeViewColumn*)
261 {
262         btn_add_clicked();
263 }
264
265 void
266 PluginSelector::added_row_clicked(GdkEventButton* event)
267 {
268         if (event->type == GDK_2BUTTON_PRESS)
269                 btn_remove_clicked();
270 }
271
272 static bool is_analyzer (const PluginInfoPtr& info) {
273         // Anaylsis, Analyzer are for backwards compatibility (vst cache)
274         return info->in_category ("Analyser") || info->in_category ("Anaylsis") ||  info->in_category ("Analyzer");
275 }
276
277 static bool is_util (const PluginInfoPtr& info) {
278         // all MIDI plugins which are not Instruments are Utils.
279         return info->in_category ("Utility") || info->in_category ("MIDI") || info->in_category ("Generator");
280 }
281
282 bool
283 PluginSelector::show_this_plugin (const PluginInfoPtr& info, const std::string& filterstr)
284 {
285         std::string compstr;
286         std::string mode = filter_mode.get_active_text ();
287
288         if (mode == _("Favorites only")) {
289                 return manager.get_status (info) == PluginManager::Favorite;
290         }
291
292         if (mode == _("Hidden only")) {
293                 return manager.get_status (info) == PluginManager::Hidden;
294         }
295
296         if (!_show_hidden && manager.get_status (info) == PluginManager::Hidden) {
297                 return false;
298         }
299
300         if (_show_instruments == Gtkmm2ext::Off && info->is_instrument()) {
301                 return false;
302         }
303         if (_show_analysers == Gtkmm2ext::Off && is_analyzer (info)) {
304                 return false;
305         }
306         if (_show_utils == Gtkmm2ext::Off && is_util (info)) {
307                 return false;
308         }
309
310         /* NB once lilv_node_as_string() does honor translation AND
311          * the lv2 onthology provides localized class name,
312          * PluginInfo will need methods for Util & Analysis.
313          */
314         bool exp_ok = false;
315         if (_show_instruments == Gtkmm2ext::ExplicitActive && info->is_instrument()) {
316                 exp_ok = true;
317         }
318         if (_show_analysers == Gtkmm2ext::ExplicitActive && is_analyzer(info)) {
319                 exp_ok = true;
320         }
321         if (_show_utils == Gtkmm2ext::ExplicitActive && is_util (info)) {
322                 exp_ok = true;
323         }
324         if (_show_instruments == Gtkmm2ext::ExplicitActive  || _show_analysers == Gtkmm2ext::ExplicitActive || _show_utils == Gtkmm2ext::ExplicitActive) {
325                 if (!exp_ok) {
326                         return false;
327                 }
328         }
329
330         if (!filterstr.empty()) {
331
332                 if (mode == _("Name contains")) {
333                         compstr = info->name;
334                 } else if (mode == _("Category contains")) {
335                         compstr = info->category;
336                 } else if (mode == _("Type contains")) {
337
338                         switch (info->type) {
339                         case LADSPA:
340                                 compstr = X_("LADSPA");
341                                 break;
342                         case AudioUnit:
343                                 compstr = X_("AudioUnit");
344                                 break;
345                         case LV2:
346                                 compstr = X_("LV2");
347                                 break;
348                         case Windows_VST:
349                                 compstr = X_("VST");
350                                 break;
351                         case LXVST:
352                                 compstr = X_("LXVST");
353                                 break;
354                         case MacVST:
355                                 compstr = X_("MacVST");
356                                 break;
357                         case Lua:
358                                 compstr = X_("Lua");
359                                 break;
360                         }
361
362                 } else if (mode == _("Author contains")) {
363                         compstr = info->creator;
364                 } else if (mode == _("Library contains")) {
365                         compstr = info->path;
366                 }
367
368                 if (compstr.empty()) {
369                         return false;
370                 }
371
372                 transform (compstr.begin(), compstr.end(), compstr.begin(), ::toupper);
373
374                 if (compstr.find (filterstr) != string::npos) {
375                         return true;
376                 } else {
377                         return false;
378                 }
379         }
380
381         return true;
382 }
383
384 void
385 PluginSelector::setup_filter_string (string& filterstr)
386 {
387         filterstr = filter_entry.get_text ();
388         transform (filterstr.begin(), filterstr.end(), filterstr.begin(), ::toupper);
389 }
390
391 void
392 PluginSelector::refill ()
393 {
394         std::string filterstr;
395
396         in_row_change = true;
397
398         plugin_model->clear ();
399
400         setup_filter_string (filterstr);
401
402         ladspa_refiller (filterstr);
403         lv2_refiller (filterstr);
404         vst_refiller (filterstr);
405         lxvst_refiller (filterstr);
406         mac_vst_refiller (filterstr);
407         au_refiller (filterstr);
408         lua_refiller (filterstr);
409
410         in_row_change = false;
411 }
412
413 void
414 PluginSelector::refiller (const PluginInfoList& plugs, const::std::string& filterstr, const char* type)
415 {
416         char buf[16];
417
418         for (PluginInfoList::const_iterator i = plugs.begin(); i != plugs.end(); ++i) {
419
420                 if (show_this_plugin (*i, filterstr)) {
421
422                         TreeModel::Row newrow = *(plugin_model->append());
423                         newrow[plugin_columns.favorite] = (manager.get_status (*i) == PluginManager::Favorite);
424                         newrow[plugin_columns.hidden] = (manager.get_status (*i) == PluginManager::Hidden);
425                         newrow[plugin_columns.name] = (*i)->name;
426                         newrow[plugin_columns.type_name] = type;
427                         newrow[plugin_columns.category] = (*i)->category;
428
429                         string creator = (*i)->creator;
430                         string::size_type pos = 0;
431
432                         if ((*i)->type == ARDOUR::LADSPA) {
433                                 /* stupid LADSPA creator strings */
434 #ifdef PLATFORM_WINDOWS
435                                 while (pos < creator.length() && creator[pos] > -2 && creator[pos] < 256 && (isalnum (creator[pos]) || isspace (creator[pos]))) ++pos;
436 #else
437                                 while (pos < creator.length() && (isalnum (creator[pos]) || isspace (creator[pos]))) ++pos;
438 #endif
439                         } else {
440                                 pos = creator.length ();
441                         }
442                         // If there were too few characters to create a
443                         // meaningful name, mark this creator as 'Unknown'
444                         if (creator.length() < 2 || pos < 3) {
445                                 creator = "Unknown";
446                         } else{
447                                 creator = creator.substr (0, pos);
448                         }
449
450                         newrow[plugin_columns.creator] = creator;
451
452                         if ((*i)->reconfigurable_io ()) {
453                                 newrow[plugin_columns.audio_ins] = _("variable");
454                                 newrow[plugin_columns.midi_ins] = _("variable");
455                                 newrow[plugin_columns.audio_outs] = _("variable");
456                                 newrow[plugin_columns.midi_outs] = _("variable");
457                         } else {
458                                 snprintf (buf, sizeof(buf), "%d", (*i)->n_inputs.n_audio());
459                                 newrow[plugin_columns.audio_ins] = buf;
460                                 snprintf (buf, sizeof(buf), "%d", (*i)->n_inputs.n_midi());
461                                 newrow[plugin_columns.midi_ins] = buf;
462
463                                 snprintf (buf, sizeof(buf), "%d", (*i)->n_outputs.n_audio());
464                                 newrow[plugin_columns.audio_outs] = buf;
465                                 snprintf (buf, sizeof(buf), "%d", (*i)->n_outputs.n_midi());
466                                 newrow[plugin_columns.midi_outs] = buf;
467                         }
468
469                         newrow[plugin_columns.plugin] = *i;
470                 }
471         }
472 }
473
474 void
475 PluginSelector::ladspa_refiller (const std::string& filterstr)
476 {
477         refiller (manager.ladspa_plugin_info(), filterstr, "LADSPA");
478 }
479
480 void
481 PluginSelector::lua_refiller (const std::string& filterstr)
482 {
483         refiller (manager.lua_plugin_info(), filterstr, "Lua");
484 }
485
486 void
487 PluginSelector::lv2_refiller (const std::string& filterstr)
488 {
489 #ifdef LV2_SUPPORT
490         refiller (manager.lv2_plugin_info(), filterstr, "LV2");
491 #endif
492 }
493
494 void
495 #ifdef WINDOWS_VST_SUPPORT
496 PluginSelector::vst_refiller (const std::string& filterstr)
497 #else
498 PluginSelector::vst_refiller (const std::string&)
499 #endif
500 {
501 #ifdef WINDOWS_VST_SUPPORT
502         refiller (manager.windows_vst_plugin_info(), filterstr, "VST");
503 #endif
504 }
505
506 void
507 #ifdef LXVST_SUPPORT
508 PluginSelector::lxvst_refiller (const std::string& filterstr)
509 #else
510 PluginSelector::lxvst_refiller (const std::string&)
511 #endif
512 {
513 #ifdef LXVST_SUPPORT
514         refiller (manager.lxvst_plugin_info(), filterstr, "LXVST");
515 #endif
516 }
517
518 void
519 #ifdef MACVST_SUPPORT
520 PluginSelector::mac_vst_refiller (const std::string& filterstr)
521 #else
522 PluginSelector::mac_vst_refiller (const std::string&)
523 #endif
524 {
525 #ifdef MACVST_SUPPORT
526         refiller (manager.mac_vst_plugin_info(), filterstr, "MacVST");
527 #endif
528 }
529
530 void
531 #ifdef AUDIOUNIT_SUPPORT
532 PluginSelector::au_refiller (const std::string& filterstr)
533 #else
534 PluginSelector::au_refiller (const std::string&)
535 #endif
536 {
537 #ifdef AUDIOUNIT_SUPPORT
538         refiller (manager.au_plugin_info(), filterstr, "AU");
539 #endif
540 }
541
542 PluginPtr
543 PluginSelector::load_plugin (PluginInfoPtr pi)
544 {
545         if (_session == 0) {
546                 return PluginPtr();
547         }
548
549         return pi->load (*_session);
550 }
551
552 void
553 PluginSelector::btn_add_clicked()
554 {
555         std::string name;
556         PluginInfoPtr pi;
557         TreeModel::Row newrow = *(amodel->append());
558         TreeModel::Row row;
559
560         row = *(plugin_display.get_selection()->get_selected());
561         name = row[plugin_columns.name];
562         pi = row[plugin_columns.plugin];
563
564         newrow[acols.text] = name;
565         newrow[acols.plugin] = pi;
566
567         if (!amodel->children().empty()) {
568                 set_response_sensitive (RESPONSE_APPLY, true);
569         }
570 }
571
572 void
573 PluginSelector::btn_remove_clicked()
574 {
575         TreeModel::iterator iter = added_list.get_selection()->get_selected();
576
577         amodel->erase(iter);
578         if (amodel->children().empty()) {
579                 set_response_sensitive (RESPONSE_APPLY, false);
580         }
581 }
582
583 void
584 PluginSelector::display_selection_changed()
585 {
586         if (plugin_display.get_selection()->count_selected_rows() != 0) {
587                 btn_add->set_sensitive (true);
588         } else {
589                 btn_add->set_sensitive (false);
590         }
591 }
592
593 void
594 PluginSelector::added_list_selection_changed()
595 {
596         if (added_list.get_selection()->count_selected_rows() != 0) {
597                 btn_remove->set_sensitive (true);
598         } else {
599                 btn_remove->set_sensitive (false);
600         }
601 }
602
603 int
604 PluginSelector::run ()
605 {
606         ResponseType r;
607         TreeModel::Children::iterator i;
608
609         bool finish = false;
610
611         while (!finish) {
612
613                 SelectedPlugins plugins;
614                 r = (ResponseType) Dialog::run ();
615
616                 switch (r) {
617                 case RESPONSE_APPLY:
618                         for (i = amodel->children().begin(); i != amodel->children().end(); ++i) {
619                                 PluginInfoPtr pp = (*i)[acols.plugin];
620                                 PluginPtr p = load_plugin (pp);
621                                 if (p) {
622                                         plugins.push_back (p);
623                                 } else {
624                                         MessageDialog msg (string_compose (_("The plugin \"%1\" could not be loaded\n\nSee the Log window for more details (maybe)"), pp->name));
625                                         msg.run ();
626                                 }
627                         }
628                         if (interested_object && !plugins.empty()) {
629                                 finish = !interested_object->use_plugins (plugins);
630                         }
631
632                         break;
633
634                 default:
635                         finish = true;
636                         break;
637                 }
638         }
639
640
641         hide();
642         amodel->clear();
643         interested_object = 0;
644
645         return (int) r;
646 }
647
648 void
649 PluginSelector::filter_button_clicked ()
650 {
651         filter_entry.set_text ("");
652 }
653
654 void
655 PluginSelector::filter_entry_changed ()
656 {
657         refill ();
658 }
659
660 void
661 PluginSelector::filter_mode_changed ()
662 {
663         std::string mode = filter_mode.get_active_text ();
664
665         if (mode == _("Favorites only") || mode == _("Hidden only")) {
666                 filter_entry.set_sensitive (false);
667                 filter_button.set_sensitive (false);
668                 fil_hidden_button.set_sensitive (false);
669                 fil_instruments_button.set_sensitive (false);
670                 fil_analysis_button.set_sensitive (false);
671                 fil_utils_button.set_sensitive (false);
672         } else {
673                 filter_entry.set_sensitive (true);
674                 filter_button.set_sensitive (true);
675                 fil_hidden_button.set_sensitive (true);
676                 fil_instruments_button.set_sensitive (true);
677                 fil_analysis_button.set_sensitive (true);
678                 fil_utils_button.set_sensitive (true);
679         }
680
681         refill ();
682 }
683
684 void
685 PluginSelector::on_show ()
686 {
687         ArdourDialog::on_show ();
688         filter_entry.grab_focus ();
689 }
690
691 struct PluginMenuCompareByCreator {
692         bool operator() (PluginInfoPtr a, PluginInfoPtr b) const {
693                 int cmp;
694
695                 cmp = cmp_nocase_utf8 (a->creator, b->creator);
696
697                 if (cmp < 0) {
698                         return true;
699                 } else if (cmp == 0) {
700                         /* same creator ... compare names */
701                         if (cmp_nocase_utf8 (a->name, b->name) < 0) {
702                                 return true;
703                         }
704                 }
705                 return false;
706         }
707 };
708
709 struct PluginMenuCompareByName {
710         bool operator() (PluginInfoPtr a, PluginInfoPtr b) const {
711                 int cmp;
712
713                 cmp = cmp_nocase_utf8 (a->name, b->name);
714
715                 if (cmp < 0) {
716                         return true;
717                 } else if (cmp == 0) {
718                         /* same name ... compare type */
719                         if (a->type < b->type) {
720                                 return true;
721                         }
722                 }
723                 return false;
724         }
725 };
726
727 struct PluginMenuCompareByCategory {
728         bool operator() (PluginInfoPtr a, PluginInfoPtr b) const {
729                 int cmp;
730
731                 cmp = cmp_nocase_utf8 (a->category, b->category);
732
733                 if (cmp < 0) {
734                         return true;
735                 } else if (cmp == 0) {
736                         /* same category ... compare names */
737                         if (cmp_nocase_utf8 (a->name, b->name) < 0) {
738                                 return true;
739                         }
740                 }
741                 return false;
742         }
743 };
744
745 /** @return Plugin menu. The caller should not delete it */
746 Gtk::Menu*
747 PluginSelector::plugin_menu()
748 {
749         return _plugin_menu;
750 }
751
752 void
753 PluginSelector::build_plugin_menu ()
754 {
755         PluginInfoList all_plugs;
756
757         all_plugs.insert (all_plugs.end(), manager.ladspa_plugin_info().begin(), manager.ladspa_plugin_info().end());
758         all_plugs.insert (all_plugs.end(), manager.lua_plugin_info().begin(), manager.lua_plugin_info().end());
759 #ifdef WINDOWS_VST_SUPPORT
760         all_plugs.insert (all_plugs.end(), manager.windows_vst_plugin_info().begin(), manager.windows_vst_plugin_info().end());
761 #endif
762 #ifdef LXVST_SUPPORT
763         all_plugs.insert (all_plugs.end(), manager.lxvst_plugin_info().begin(), manager.lxvst_plugin_info().end());
764 #endif
765 #ifdef MACVST_SUPPORT
766         all_plugs.insert (all_plugs.end(), manager.mac_vst_plugin_info().begin(), manager.mac_vst_plugin_info().end());
767 #endif
768 #ifdef AUDIOUNIT_SUPPORT
769         all_plugs.insert (all_plugs.end(), manager.au_plugin_info().begin(), manager.au_plugin_info().end());
770 #endif
771 #ifdef LV2_SUPPORT
772         all_plugs.insert (all_plugs.end(), manager.lv2_plugin_info().begin(), manager.lv2_plugin_info().end());
773 #endif
774
775         using namespace Menu_Helpers;
776
777         delete _plugin_menu;
778
779         _plugin_menu = manage (new Menu);
780         _plugin_menu->set_name("ArdourContextMenu");
781
782         MenuList& items = _plugin_menu->items();
783         items.clear ();
784
785         Gtk::Menu* favs = create_favs_menu(all_plugs);
786         items.push_back (MenuElem (_("Favorites"), *manage (favs)));
787
788         items.push_back (MenuElem (_("Plugin Manager..."), sigc::mem_fun (*this, &PluginSelector::show_manager)));
789         items.push_back (SeparatorElem ());
790
791         Menu* by_creator = create_by_creator_menu(all_plugs);
792         items.push_back (MenuElem (_("By Creator"), *manage (by_creator)));
793
794         Menu* by_category = create_by_category_menu(all_plugs);
795         items.push_back (MenuElem (_("By Category"), *manage (by_category)));
796 }
797
798 string
799 GetPluginTypeStr(PluginInfoPtr info)
800 {
801         string type;
802         
803         switch (info->type) {
804         case LADSPA:
805                 type = X_(" (LADSPA)");
806                 break;
807         case AudioUnit:
808                 type = X_(" (AU)");
809                 break;
810         case LV2:
811                 type = X_(" (LV2)");
812                 break;
813         case Windows_VST:
814         case LXVST:
815         case MacVST:
816                 type = X_(" (VST)");
817                 break;
818         case Lua:
819                 type = X_(" (Lua)");
820                 break;
821         }
822         
823         return type;
824 }
825
826 Gtk::Menu*
827 PluginSelector::create_favs_menu (PluginInfoList& all_plugs)
828 {
829         using namespace Menu_Helpers;
830
831         Menu* favs = new Menu();
832         favs->set_name("ArdourContextMenu");
833
834         PluginMenuCompareByName cmp_by_name;
835         all_plugs.sort (cmp_by_name);
836
837         for (PluginInfoList::const_iterator i = all_plugs.begin(); i != all_plugs.end(); ++i) {
838                 if (manager.get_status (*i) == PluginManager::Favorite) {
839                         string typ = GetPluginTypeStr(*i);
840                         MenuElem elem ((*i)->name + typ, (sigc::bind (sigc::mem_fun (*this, &PluginSelector::plugin_chosen_from_menu), *i)));
841                         elem.get_child()->set_use_underline (false);
842                         favs->items().push_back (elem);
843                 }
844         }
845         return favs;
846 }
847
848 Gtk::Menu*
849 PluginSelector::create_by_creator_menu (ARDOUR::PluginInfoList& all_plugs)
850 {
851         using namespace Menu_Helpers;
852
853         typedef std::map<std::string,Gtk::Menu*> SubmenuMap;
854         SubmenuMap creator_submenu_map;
855
856         Menu* by_creator = new Menu();
857         by_creator->set_name("ArdourContextMenu");
858
859         MenuList& by_creator_items = by_creator->items();
860         PluginMenuCompareByCreator cmp_by_creator;
861         all_plugs.sort (cmp_by_creator);
862
863         for (PluginInfoList::const_iterator i = all_plugs.begin(); i != all_plugs.end(); ++i) {
864
865                 if (manager.get_status (*i) == PluginManager::Hidden) continue;
866
867                 string creator = (*i)->creator;
868                 string::size_type pos = 0;
869
870                 if ((*i)->type == ARDOUR::LADSPA) {
871                         /* stupid LADSPA creator strings */
872 #ifdef PLATFORM_WINDOWS
873                         while (pos < creator.length() && creator[pos] > -2 && creator[pos] < 256 && (isalnum (creator[pos]) || isspace (creator[pos]))) ++pos;
874 #else
875                         while (pos < creator.length() && (isalnum (creator[pos]) || isspace (creator[pos]))) ++pos;
876 #endif
877                 } else {
878                         pos = creator.length ();
879                 }
880
881                 // If there were too few characters to create a
882                 // meaningful name, mark this creator as 'Unknown'
883                 if (creator.length() < 2 || pos < 3) {
884                         creator = "Unknown";
885                 } else{
886                         creator = creator.substr (0, pos);
887                 }
888
889                 SubmenuMap::iterator x;
890                 Gtk::Menu* submenu;
891                 if ((x = creator_submenu_map.find (creator)) != creator_submenu_map.end()) {
892                         submenu = x->second;
893                 } else {
894                         submenu = new Gtk::Menu;
895                         by_creator_items.push_back (MenuElem (creator, *manage (submenu)));
896                         creator_submenu_map.insert (pair<std::string,Menu*> (creator, submenu));
897                         submenu->set_name("ArdourContextMenu");
898                 }
899                 string typ = GetPluginTypeStr(*i);
900                 MenuElem elem ((*i)->name+typ, (sigc::bind (sigc::mem_fun (*this, &PluginSelector::plugin_chosen_from_menu), *i)));
901                 elem.get_child()->set_use_underline (false);
902                 submenu->items().push_back (elem);
903         }
904         return by_creator;
905 }
906
907 Gtk::Menu*
908 PluginSelector::create_by_category_menu (ARDOUR::PluginInfoList& all_plugs)
909 {
910         using namespace Menu_Helpers;
911
912         typedef std::map<std::string,Gtk::Menu*> SubmenuMap;
913         SubmenuMap category_submenu_map;
914
915         Menu* by_category = new Menu();
916         by_category->set_name("ArdourContextMenu");
917
918         MenuList& by_category_items = by_category->items();
919         PluginMenuCompareByCategory cmp_by_category;
920         all_plugs.sort (cmp_by_category);
921
922         for (PluginInfoList::const_iterator i = all_plugs.begin(); i != all_plugs.end(); ++i) {
923
924                 if (manager.get_status (*i) == PluginManager::Hidden) continue;
925
926                 string category = (*i)->category;
927
928                 SubmenuMap::iterator x;
929                 Gtk::Menu* submenu;
930                 if ((x = category_submenu_map.find (category)) != category_submenu_map.end()) {
931                         submenu = x->second;
932                 } else {
933                         submenu = new Gtk::Menu;
934                         by_category_items.push_back (MenuElem (category, *manage (submenu)));
935                         category_submenu_map.insert (pair<std::string,Menu*> (category, submenu));
936                         submenu->set_name("ArdourContextMenu");
937                 }
938                 string typ = GetPluginTypeStr(*i);
939                 MenuElem elem ((*i)->name + typ, (sigc::bind (sigc::mem_fun (*this, &PluginSelector::plugin_chosen_from_menu), *i)));
940                 elem.get_child()->set_use_underline (false);
941                 submenu->items().push_back (elem);
942         }
943         return by_category;
944 }
945
946 void
947 PluginSelector::plugin_chosen_from_menu (const PluginInfoPtr& pi)
948 {
949         PluginPtr p = load_plugin (pi);
950
951         if (p && interested_object) {
952                 SelectedPlugins plugins;
953                 plugins.push_back (p);
954                 interested_object->use_plugins (plugins);
955         }
956
957         interested_object = 0;
958 }
959
960 void
961 PluginSelector::favorite_changed (const std::string& path)
962 {
963         PluginInfoPtr pi;
964
965         if (in_row_change) {
966                 return;
967         }
968
969         in_row_change = true;
970
971         TreeModel::iterator iter = plugin_model->get_iter (path);
972
973         if (iter) {
974
975                 bool favorite = !(*iter)[plugin_columns.favorite];
976
977                 /* change state */
978
979                 (*iter)[plugin_columns.favorite] = favorite;
980                 (*iter)[plugin_columns.hidden] = false;
981                 PluginManager::PluginStatusType status = (favorite ? PluginManager::Favorite : PluginManager::Normal);
982
983                 /* save new statuses list */
984
985                 pi = (*iter)[plugin_columns.plugin];
986
987                 manager.set_status (pi->type, pi->unique_id, status);
988
989                 manager.save_statuses ();
990
991                 build_plugin_menu ();
992         }
993         in_row_change = false;
994 }
995
996 void
997 PluginSelector::hidden_changed (const std::string& path)
998 {
999         PluginInfoPtr pi;
1000
1001         if (in_row_change) {
1002                 return;
1003         }
1004
1005         in_row_change = true;
1006
1007         TreeModel::iterator iter = plugin_model->get_iter (path);
1008
1009         if (iter) {
1010
1011                 bool hidden = !(*iter)[plugin_columns.hidden];
1012
1013                 /* change state */
1014
1015                 (*iter)[plugin_columns.favorite] = false;
1016                 (*iter)[plugin_columns.hidden] = hidden;
1017                 PluginManager::PluginStatusType status = (hidden ? PluginManager::Hidden : PluginManager::Normal);
1018
1019                 /* save new statuses list */
1020
1021                 pi = (*iter)[plugin_columns.plugin];
1022
1023                 manager.set_status (pi->type, pi->unique_id, status);
1024
1025                 manager.save_statuses ();
1026
1027                 build_plugin_menu ();
1028         }
1029         in_row_change = false;
1030 }
1031
1032 bool
1033 PluginSelector::fil_hidden_button_release (GdkEventButton*)
1034 {
1035         _show_hidden = (fil_hidden_button.active_state() == 0);
1036         fil_hidden_button.set_active (_show_hidden);
1037         refill ();
1038         return false;
1039 }
1040
1041 static Gtkmm2ext::ActiveState next_state (Gtkmm2ext::ActiveState s){
1042         switch (s) {
1043                 case Gtkmm2ext::Off:
1044                         return Gtkmm2ext::ImplicitActive;
1045                         break;
1046                 case Gtkmm2ext::ImplicitActive:
1047                         return Gtkmm2ext::ExplicitActive;
1048                         break;
1049                 case Gtkmm2ext::ExplicitActive:
1050                         return Gtkmm2ext::Off;
1051                         break;
1052                 default: assert(0); break; // not reached
1053         }
1054         /* impossible, but keep some compiles happy */
1055         fatal << string_compose (_("programming error: %1"),
1056                         X_("Illegal Active State."))
1057                 << endmsg;
1058         abort(); /*NOTREACHED*/
1059         return Gtkmm2ext::Off;
1060 }
1061
1062 static Gtkmm2ext::ActiveState prev_state (Gtkmm2ext::ActiveState s){
1063         switch (s) {
1064                 case Gtkmm2ext::Off:
1065                         return Gtkmm2ext::ExplicitActive;
1066                         break;
1067                 case Gtkmm2ext::ImplicitActive:
1068                         return Gtkmm2ext::Off;
1069                         break;
1070                 case Gtkmm2ext::ExplicitActive:
1071                         return Gtkmm2ext::ImplicitActive;
1072                         break;
1073                 default: assert(0); break; // not reached
1074         }
1075         /* impossible, but keep some compiles happy */
1076         fatal << string_compose (_("programming error: %1"),
1077                         X_("Illegal Active State."))
1078                 << endmsg;
1079         abort(); /*NOTREACHED*/
1080         return Gtkmm2ext::Off;
1081 }
1082
1083 bool
1084 PluginSelector::fil_instruments_button_release (GdkEventButton* ev)
1085 {
1086         if (ev->button == 3) {
1087                 _show_instruments = prev_state (fil_instruments_button.active_state());
1088         } else {
1089                 _show_instruments = next_state (fil_instruments_button.active_state());
1090         }
1091         fil_instruments_button.set_active_state (_show_instruments);
1092         refill ();
1093         return false;
1094 }
1095
1096 bool
1097 PluginSelector::fil_analysis_button_release (GdkEventButton* ev)
1098 {
1099         if (ev->button == 3) {
1100                 _show_analysers = prev_state (fil_analysis_button.active_state());
1101         } else {
1102                 _show_analysers = next_state (fil_analysis_button.active_state());
1103         }
1104         fil_analysis_button.set_active_state (_show_analysers);
1105         refill ();
1106         return false;
1107 }
1108
1109 bool
1110 PluginSelector::fil_utils_button_release (GdkEventButton* ev)
1111 {
1112         if (ev->button == 3) {
1113                 _show_utils = prev_state (fil_utils_button.active_state());
1114         } else {
1115                 _show_utils = next_state (fil_utils_button.active_state());
1116         }
1117         fil_utils_button.set_active_state (_show_utils);
1118         refill ();
1119         return false;
1120 }
1121
1122 void
1123 PluginSelector::show_manager ()
1124 {
1125         show_all();
1126         run ();
1127 }
1128
1129 void
1130 PluginSelector::set_interested_object (PluginInterestedObject& obj)
1131 {
1132         interested_object = &obj;
1133 }