2 Copyright (C) 2000-2006 Paul Davis
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.
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.
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.
20 #include "gtk2ardour-config.h"
29 #include <gtkmm/table.h>
30 #include <gtkmm/stock.h>
31 #include <gtkmm/button.h>
32 #include <gtkmm/notebook.h>
34 #include <gtkmm2ext/utils.h>
36 #include "pbd/convert.h"
38 #include "ardour/plugin_manager.h"
39 #include "ardour/plugin.h"
40 #include "ardour/configuration.h"
41 #include "ardour/session.h"
43 #include "ardour_ui.h"
44 #include "plugin_selector.h"
45 #include "gui_thread.h"
49 using namespace ARDOUR
;
54 static const char* _filter_mode_strings
[] = {
57 N_("Category contains"),
58 N_("Author contains"),
59 N_("Library contains"),
65 PluginSelector::PluginSelector (PluginManager
*mgr
)
66 : ArdourDialog (_("Plugin Manager"), true, false),
67 filter_button (Stock::CLEAR
)
69 set_position (Gtk::WIN_POS_MOUSE
);
70 set_name ("PluginSelectorWindow");
71 add_events (Gdk::KEY_PRESS_MASK
|Gdk::KEY_RELEASE_MASK
);
75 in_row_change
= false;
77 manager
->PluginListChanged
.connect (plugin_list_changed_connection
, invalidator (*this), boost::bind (&PluginSelector::build_plugin_menu
, this), gui_context());
80 plugin_model
= Gtk::ListStore::create (plugin_columns
);
81 plugin_display
.set_model (plugin_model
);
82 /* XXX translators: try to convert "Fav" into a short term
83 related to "favorite" and "Hid" into a short term
86 plugin_display
.append_column (_("Fav"), plugin_columns
.favorite
);
87 plugin_display
.append_column (_("Hid"), plugin_columns
.hidden
);
88 plugin_display
.append_column (_("Available Plugins"), plugin_columns
.name
);
89 plugin_display
.append_column (_("Type"), plugin_columns
.type_name
);
90 plugin_display
.append_column (_("Category"), plugin_columns
.category
);
91 plugin_display
.append_column (_("Creator"), plugin_columns
.creator
);
92 plugin_display
.append_column (_("# Audio In"),plugin_columns
.audio_ins
);
93 plugin_display
.append_column (_("# Audio Out"), plugin_columns
.audio_outs
);
94 plugin_display
.append_column (_("# MIDI In"),plugin_columns
.midi_ins
);
95 plugin_display
.append_column (_("# MIDI Out"), plugin_columns
.midi_outs
);
96 plugin_display
.set_headers_visible (true);
97 plugin_display
.set_headers_clickable (true);
98 plugin_display
.set_reorderable (false);
99 plugin_display
.set_rules_hint (true);
101 CellRendererToggle
* fav_cell
= dynamic_cast<CellRendererToggle
*>(plugin_display
.get_column_cell_renderer (0));
102 fav_cell
->property_activatable() = true;
103 fav_cell
->property_radio() = true;
104 fav_cell
->signal_toggled().connect (sigc::mem_fun (*this, &PluginSelector::favorite_changed
));
106 CellRendererToggle
* hidden_cell
= dynamic_cast<CellRendererToggle
*>(plugin_display
.get_column_cell_renderer (1));
107 hidden_cell
->property_activatable() = true;
108 hidden_cell
->property_radio() = true;
109 hidden_cell
->signal_toggled().connect (sigc::mem_fun (*this, &PluginSelector::hidden_changed
));
111 scroller
.set_border_width(10);
112 scroller
.set_policy(Gtk::POLICY_AUTOMATIC
, Gtk::POLICY_AUTOMATIC
);
113 scroller
.add(plugin_display
);
115 amodel
= Gtk::ListStore::create(acols
);
116 added_list
.set_model (amodel
);
117 added_list
.append_column (_("Plugins to be connected"), acols
.text
);
118 added_list
.set_headers_visible (true);
119 added_list
.set_reorderable (false);
121 for (int i
= 0; i
<=8; i
++) {
122 Gtk::TreeView::Column
* column
= plugin_display
.get_column(i
);
123 column
->set_sort_column(i
);
126 ascroller
.set_border_width(10);
127 ascroller
.set_policy(Gtk::POLICY_AUTOMATIC
, Gtk::POLICY_AUTOMATIC
);
128 ascroller
.add(added_list
);
129 btn_add
= manage(new Gtk::Button(Stock::ADD
));
130 ARDOUR_UI::instance()->set_tip(*btn_add
, _("Add a plugin to the effect list"));
131 btn_add
->set_sensitive (false);
132 btn_remove
= manage(new Gtk::Button(Stock::REMOVE
));
133 btn_remove
->set_sensitive (false);
134 ARDOUR_UI::instance()->set_tip(*btn_remove
, _("Remove a plugin from the effect list"));
135 Gtk::Button
*btn_update
= manage(new Gtk::Button(Stock::REFRESH
));
136 ARDOUR_UI::instance()->set_tip(*btn_update
, _("Update available plugins"));
138 btn_add
->set_name("PluginSelectorButton");
139 btn_remove
->set_name("PluginSelectorButton");
141 Gtk::Table
* table
= manage(new Gtk::Table(7, 11));
142 table
->set_size_request(750, 500);
143 table
->attach(scroller
, 0, 7, 0, 5);
145 HBox
* filter_box
= manage (new HBox
);
147 vector
<string
> filter_strings
= I18N (_filter_mode_strings
);
148 Gtkmm2ext::set_popdown_strings (filter_mode
, filter_strings
);
149 filter_mode
.set_active_text (filter_strings
.front());
151 filter_box
->pack_start (filter_mode
, false, false);
152 filter_box
->pack_start (filter_entry
, true, true);
153 filter_box
->pack_start (filter_button
, false, false);
155 filter_entry
.signal_changed().connect (sigc::mem_fun (*this, &PluginSelector::filter_entry_changed
));
156 filter_button
.signal_clicked().connect (sigc::mem_fun (*this, &PluginSelector::filter_button_clicked
));
157 filter_mode
.signal_changed().connect (sigc::mem_fun (*this, &PluginSelector::filter_mode_changed
));
161 filter_entry
.show ();
162 filter_button
.show ();
164 table
->attach (*filter_box
, 0, 7, 5, 6, FILL
|EXPAND
, FILL
, 5, 5);
166 table
->attach(*btn_add
, 1, 2, 6, 7, FILL
, FILL
, 5, 5);
167 table
->attach(*btn_remove
, 3, 4, 6, 7, FILL
, FILL
, 5, 5);
168 table
->attach(*btn_update
, 5, 6, 6, 7, FILL
, FILL
, 5, 5);
170 table
->attach(ascroller
, 0, 7, 8, 10);
172 add_button (Stock::CLOSE
, RESPONSE_CLOSE
);
173 add_button (_("Insert Plugin(s)"), RESPONSE_APPLY
);
174 set_default_response (RESPONSE_APPLY
);
175 set_response_sensitive (RESPONSE_APPLY
, false);
176 get_vbox()->pack_start (*table
);
178 table
->set_name("PluginSelectorTable");
179 plugin_display
.set_name("PluginSelectorDisplay");
180 //plugin_display.set_name("PluginSelectorList");
181 added_list
.set_name("PluginSelectorList");
183 plugin_display
.signal_button_press_event().connect_notify (sigc::mem_fun(*this, &PluginSelector::row_clicked
));
184 plugin_display
.get_selection()->signal_changed().connect (sigc::mem_fun(*this, &PluginSelector::display_selection_changed
));
185 plugin_display
.grab_focus();
187 btn_update
->signal_clicked().connect (sigc::mem_fun(*this, &PluginSelector::btn_update_clicked
));
188 btn_add
->signal_clicked().connect(sigc::mem_fun(*this, &PluginSelector::btn_add_clicked
));
189 btn_remove
->signal_clicked().connect(sigc::mem_fun(*this, &PluginSelector::btn_remove_clicked
));
190 added_list
.get_selection()->signal_changed().connect (sigc::mem_fun(*this, &PluginSelector::added_list_selection_changed
));
195 PluginSelector::~PluginSelector ()
201 PluginSelector::row_clicked(GdkEventButton
* event
)
203 if (event
->type
== GDK_2BUTTON_PRESS
) {
209 PluginSelector::show_this_plugin (const PluginInfoPtr
& info
, const std::string
& filterstr
)
212 std::string mode
= filter_mode
.get_active_text ();
214 if (mode
== _("Favorites only")) {
215 return manager
->get_status (info
) == PluginManager::Favorite
;
218 if (mode
== _("Hidden only")) {
219 return manager
->get_status (info
) == PluginManager::Hidden
;
222 if (!filterstr
.empty()) {
224 if (mode
== _("Name contains")) {
225 compstr
= info
->name
;
226 } else if (mode
== _("Category contains")) {
227 compstr
= info
->category
;
228 } else if (mode
== _("Type contains")) {
230 switch (info
->type
) {
232 compstr
= X_("LADSPA");
235 compstr
= X_("AudioUnit");
245 } else if (mode
== _("Author contains")) {
246 compstr
= info
->creator
;
247 } else if (mode
== _("Library contains")) {
248 compstr
= info
->path
;
251 if (compstr
.empty()) {
255 transform (compstr
.begin(), compstr
.end(), compstr
.begin(), ::toupper
);
257 if (compstr
.find (filterstr
) != string::npos
) {
268 PluginSelector::setup_filter_string (string
& filterstr
)
270 filterstr
= filter_entry
.get_text ();
271 transform (filterstr
.begin(), filterstr
.end(), filterstr
.begin(), ::toupper
);
275 PluginSelector::refill ()
277 std::string filterstr
;
279 in_row_change
= true;
281 plugin_model
->clear ();
283 setup_filter_string (filterstr
);
285 ladspa_refiller (filterstr
);
286 lv2_refiller (filterstr
);
287 vst_refiller (filterstr
);
288 au_refiller (filterstr
);
290 in_row_change
= false;
294 PluginSelector::refiller (const PluginInfoList
& plugs
, const::std::string
& filterstr
, const char* type
)
298 for (PluginInfoList::const_iterator i
= plugs
.begin(); i
!= plugs
.end(); ++i
) {
300 if (show_this_plugin (*i
, filterstr
)) {
302 TreeModel::Row newrow
= *(plugin_model
->append());
303 newrow
[plugin_columns
.favorite
] = (manager
->get_status (*i
) == PluginManager::Favorite
);
304 newrow
[plugin_columns
.hidden
] = (manager
->get_status (*i
) == PluginManager::Hidden
);
305 newrow
[plugin_columns
.name
] = (*i
)->name
;
306 newrow
[plugin_columns
.type_name
] = type
;
307 newrow
[plugin_columns
.category
] = (*i
)->category
;
309 string creator
= (*i
)->creator
;
310 string::size_type pos
= 0;
312 /* stupid LADSPA creator strings */
314 while (pos
< creator
.length() && (isalnum (creator
[pos
]) || isspace (creator
[pos
]))) ++pos
;
315 creator
= creator
.substr (0, pos
);
317 newrow
[plugin_columns
.creator
] = creator
;
319 snprintf (buf
, sizeof(buf
), "%d", (*i
)->n_inputs
.n_audio());
320 newrow
[plugin_columns
.audio_ins
] = buf
;
321 snprintf (buf
, sizeof(buf
), "%d", (*i
)->n_inputs
.n_midi());
322 newrow
[plugin_columns
.midi_ins
] = buf
;
324 snprintf (buf
, sizeof(buf
), "%d", (*i
)->n_outputs
.n_audio());
325 newrow
[plugin_columns
.audio_outs
] = buf
;
326 snprintf (buf
, sizeof(buf
), "%d", (*i
)->n_outputs
.n_midi());
327 newrow
[plugin_columns
.midi_outs
] = buf
;
329 newrow
[plugin_columns
.plugin
] = *i
;
335 PluginSelector::ladspa_refiller (const std::string
& filterstr
)
337 refiller (manager
->ladspa_plugin_info(), filterstr
, "LADSPA");
341 PluginSelector::lv2_refiller (const std::string
& filterstr
)
344 refiller (manager
->lv2_plugin_info(), filterstr
, "LV2");
350 PluginSelector::vst_refiller (const std::string
& filterstr
)
352 PluginSelector::vst_refiller (const std::string
&)
356 refiller (manager
->vst_plugin_info(), filterstr
, "VST");
361 #ifdef HAVE_AUDIOUNITS
362 PluginSelector::au_refiller (const std::string
& filterstr
)
364 PluginSelector::au_refiller (const std::string
&)
367 #ifdef HAVE_AUDIOUNITS
368 refiller (manager
->au_plugin_info(), filterstr
, "AU");
373 PluginSelector::load_plugin (PluginInfoPtr pi
)
379 return pi
->load (*_session
);
383 PluginSelector::btn_add_clicked()
387 TreeModel::Row newrow
= *(amodel
->append());
390 row
= *(plugin_display
.get_selection()->get_selected());
391 name
= row
[plugin_columns
.name
];
392 pi
= row
[plugin_columns
.plugin
];
394 newrow
[acols
.text
] = name
;
395 newrow
[acols
.plugin
] = pi
;
397 if (!amodel
->children().empty()) {
398 set_response_sensitive (RESPONSE_APPLY
, true);
403 PluginSelector::btn_remove_clicked()
405 TreeModel::iterator iter
= added_list
.get_selection()->get_selected();
408 if (amodel
->children().empty()) {
409 set_response_sensitive (RESPONSE_APPLY
, false);
414 PluginSelector::btn_update_clicked()
421 PluginSelector::display_selection_changed()
423 if (plugin_display
.get_selection()->count_selected_rows() != 0) {
424 btn_add
->set_sensitive (true);
426 btn_add
->set_sensitive (false);
431 PluginSelector::added_list_selection_changed()
433 if (added_list
.get_selection()->count_selected_rows() != 0) {
434 btn_remove
->set_sensitive (true);
436 btn_remove
->set_sensitive (false);
441 PluginSelector::run ()
444 TreeModel::Children::iterator i
;
450 SelectedPlugins plugins
;
451 r
= (ResponseType
) Dialog::run ();
455 for (i
= amodel
->children().begin(); i
!= amodel
->children().end(); ++i
) {
456 PluginInfoPtr pp
= (*i
)[acols
.plugin
];
457 PluginPtr p
= load_plugin (pp
);
459 plugins
.push_back (p
);
461 MessageDialog
msg (string_compose (_("The plugin \"%1\" could not be loaded\n\nSee the Log window for more details (maybe)"), pp
->name
));
465 if (interested_object
&& !plugins
.empty()) {
466 finish
= !interested_object
->use_plugins (plugins
);
480 interested_object
= 0;
486 PluginSelector::filter_button_clicked ()
488 filter_entry
.set_text ("");
492 PluginSelector::filter_entry_changed ()
498 PluginSelector::filter_mode_changed ()
500 std::string mode
= filter_mode
.get_active_text ();
502 if (mode
== _("Favorites only") || mode
== _("Hidden only")) {
503 filter_entry
.set_sensitive (false);
505 filter_entry
.set_sensitive (true);
512 PluginSelector::on_show ()
514 ArdourDialog::on_show ();
515 filter_entry
.grab_focus ();
518 struct PluginMenuCompareByCreator
{
519 bool operator() (PluginInfoPtr a
, PluginInfoPtr b
) const {
522 cmp
= strcasecmp (a
->creator
.c_str(), b
->creator
.c_str());
526 } else if (cmp
== 0) {
527 /* same creator ... compare names */
528 if (strcasecmp (a
->name
.c_str(), b
->name
.c_str()) < 0) {
536 struct PluginMenuCompareByName
{
537 bool operator() (PluginInfoPtr a
, PluginInfoPtr b
) const {
540 cmp
= strcasecmp (a
->name
.c_str(), b
->name
.c_str());
544 } else if (cmp
== 0) {
545 /* same name ... compare type */
546 if (a
->type
< b
->type
) {
554 struct PluginMenuCompareByCategory
{
555 bool operator() (PluginInfoPtr a
, PluginInfoPtr b
) const {
558 cmp
= strcasecmp (a
->category
.c_str(), b
->category
.c_str());
562 } else if (cmp
== 0) {
563 /* same category ... compare names */
564 if (strcasecmp (a
->name
.c_str(), b
->name
.c_str()) < 0) {
572 /** @return Plugin menu. The caller should not delete it */
574 PluginSelector::plugin_menu()
580 PluginSelector::build_plugin_menu ()
582 PluginInfoList all_plugs
;
584 all_plugs
.insert (all_plugs
.end(), manager
->ladspa_plugin_info().begin(), manager
->ladspa_plugin_info().end());
586 all_plugs
.insert (all_plugs
.end(), manager
->vst_plugin_info().begin(), manager
->vst_plugin_info().end());
588 #ifdef HAVE_AUDIOUNITS
589 all_plugs
.insert (all_plugs
.end(), manager
->au_plugin_info().begin(), manager
->au_plugin_info().end());
592 all_plugs
.insert (all_plugs
.end(), manager
->lv2_plugin_info().begin(), manager
->lv2_plugin_info().end());
595 using namespace Menu_Helpers
;
599 _plugin_menu
= manage (new Menu
);
600 _plugin_menu
->set_name("ArdourContextMenu");
602 MenuList
& items
= _plugin_menu
->items();
605 Gtk::Menu
* favs
= create_favs_menu(all_plugs
);
606 items
.push_back (MenuElem (_("Favorites"), *manage (favs
)));
608 items
.push_back (MenuElem (_("Plugin Manager..."), sigc::mem_fun (*this, &PluginSelector::show_manager
)));
609 items
.push_back (SeparatorElem ());
611 Menu
* by_creator
= create_by_creator_menu(all_plugs
);
612 items
.push_back (MenuElem (_("By Creator"), *manage (by_creator
)));
614 Menu
* by_category
= create_by_category_menu(all_plugs
);
615 items
.push_back (MenuElem (_("By Category"), *manage (by_category
)));
619 PluginSelector::create_favs_menu (PluginInfoList
& all_plugs
)
621 using namespace Menu_Helpers
;
623 Menu
* favs
= new Menu();
624 favs
->set_name("ArdourContextMenu");
626 PluginMenuCompareByName cmp_by_name
;
627 all_plugs
.sort (cmp_by_name
);
629 for (PluginInfoList::const_iterator i
= all_plugs
.begin(); i
!= all_plugs
.end(); ++i
) {
630 if (manager
->get_status (*i
) == PluginManager::Favorite
) {
631 favs
->items().push_back (MenuElem ((*i
)->name
, (sigc::bind (sigc::mem_fun (*this, &PluginSelector::plugin_chosen_from_menu
), *i
))));
638 PluginSelector::create_by_creator_menu (ARDOUR::PluginInfoList
& all_plugs
)
640 using namespace Menu_Helpers
;
642 typedef std::map
<std::string
,Gtk::Menu
*> SubmenuMap
;
643 SubmenuMap creator_submenu_map
;
645 Menu
* by_creator
= new Menu();
646 by_creator
->set_name("ArdourContextMenu");
648 MenuList
& by_creator_items
= by_creator
->items();
649 PluginMenuCompareByCreator cmp_by_creator
;
650 all_plugs
.sort (cmp_by_creator
);
652 for (PluginInfoList::const_iterator i
= all_plugs
.begin(); i
!= all_plugs
.end(); ++i
) {
654 if (manager
->get_status (*i
) == PluginManager::Hidden
) continue;
656 string creator
= (*i
)->creator
;
658 /* stupid LADSPA creator strings */
659 string::size_type pos
= 0;
660 while (pos
< creator
.length() && (isalnum (creator
[pos
]) || isspace (creator
[pos
]))) ++pos
;
661 creator
= creator
.substr (0, pos
);
663 SubmenuMap::iterator x
;
665 if ((x
= creator_submenu_map
.find (creator
)) != creator_submenu_map
.end()) {
668 submenu
= new Gtk::Menu
;
669 by_creator_items
.push_back (MenuElem (creator
, *manage (submenu
)));
670 creator_submenu_map
.insert (pair
<std::string
,Menu
*> (creator
, submenu
));
671 submenu
->set_name("ArdourContextMenu");
673 submenu
->items().push_back (MenuElem ((*i
)->name
, (sigc::bind (sigc::mem_fun (*this, &PluginSelector::plugin_chosen_from_menu
), *i
))));
679 PluginSelector::create_by_category_menu (ARDOUR::PluginInfoList
& all_plugs
)
681 using namespace Menu_Helpers
;
683 typedef std::map
<std::string
,Gtk::Menu
*> SubmenuMap
;
684 SubmenuMap category_submenu_map
;
686 Menu
* by_category
= new Menu();
687 by_category
->set_name("ArdourContextMenu");
689 MenuList
& by_category_items
= by_category
->items();
690 PluginMenuCompareByCategory cmp_by_category
;
691 all_plugs
.sort (cmp_by_category
);
693 for (PluginInfoList::const_iterator i
= all_plugs
.begin(); i
!= all_plugs
.end(); ++i
) {
695 if (manager
->get_status (*i
) == PluginManager::Hidden
) continue;
697 string category
= (*i
)->category
;
699 SubmenuMap::iterator x
;
701 if ((x
= category_submenu_map
.find (category
)) != category_submenu_map
.end()) {
704 submenu
= new Gtk::Menu
;
705 by_category_items
.push_back (MenuElem (category
, *manage (submenu
)));
706 category_submenu_map
.insert (pair
<std::string
,Menu
*> (category
, submenu
));
707 submenu
->set_name("ArdourContextMenu");
709 submenu
->items().push_back (MenuElem ((*i
)->name
, (sigc::bind (sigc::mem_fun (*this, &PluginSelector::plugin_chosen_from_menu
), *i
))));
715 PluginSelector::plugin_chosen_from_menu (const PluginInfoPtr
& pi
)
717 PluginPtr p
= load_plugin (pi
);
719 if (p
&& interested_object
) {
720 SelectedPlugins plugins
;
721 plugins
.push_back (p
);
722 interested_object
->use_plugins (plugins
);
725 interested_object
= 0;
729 PluginSelector::favorite_changed (const std::string
& path
)
737 in_row_change
= true;
739 TreeModel::iterator iter
= plugin_model
->get_iter (path
);
743 bool favorite
= !(*iter
)[plugin_columns
.favorite
];
747 (*iter
)[plugin_columns
.favorite
] = favorite
;
748 (*iter
)[plugin_columns
.hidden
] = false;
749 PluginManager::PluginStatusType status
= (favorite
? PluginManager::Favorite
: PluginManager::Normal
);
751 /* save new statuses list */
753 pi
= (*iter
)[plugin_columns
.plugin
];
755 manager
->set_status (pi
->type
, pi
->unique_id
, status
);
757 manager
->save_statuses ();
759 build_plugin_menu ();
761 in_row_change
= false;
765 PluginSelector::hidden_changed (const std::string
& path
)
773 in_row_change
= true;
775 TreeModel::iterator iter
= plugin_model
->get_iter (path
);
779 bool hidden
= !(*iter
)[plugin_columns
.hidden
];
783 (*iter
)[plugin_columns
.favorite
] = false;
784 (*iter
)[plugin_columns
.hidden
] = hidden
;
785 PluginManager::PluginStatusType status
= (hidden
? PluginManager::Hidden
: PluginManager::Normal
);
787 /* save new statuses list */
789 pi
= (*iter
)[plugin_columns
.plugin
];
791 manager
->set_status (pi
->type
, pi
->unique_id
, status
);
793 manager
->save_statuses ();
795 in_row_change
= false;
799 PluginSelector::show_manager ()
806 PluginSelector::set_interested_object (PluginInterestedObject
& obj
)
808 interested_object
= &obj
;