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