diff options
Diffstat (limited to 'src/gui/LoadPluginWindow.cpp')
-rw-r--r-- | src/gui/LoadPluginWindow.cpp | 508 |
1 files changed, 508 insertions, 0 deletions
diff --git a/src/gui/LoadPluginWindow.cpp b/src/gui/LoadPluginWindow.cpp new file mode 100644 index 00000000..bb84f96f --- /dev/null +++ b/src/gui/LoadPluginWindow.cpp @@ -0,0 +1,508 @@ +/* + This file is part of Ingen. + Copyright 2007-2017 David Robillard <http://drobilla.net/> + + Ingen is free software: you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free + Software Foundation, either version 3 of the License, or any later version. + + Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for details. + + You should have received a copy of the GNU Affero General Public License + along with Ingen. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "App.hpp" +#include "GraphCanvas.hpp" +#include "GraphView.hpp" +#include "GraphWindow.hpp" +#include "LoadPluginWindow.hpp" +#include "ingen_config.h" + +#include "ingen/Interface.hpp" +#include "ingen/client/ClientStore.hpp" +#include "ingen/client/GraphModel.hpp" + +#include <string> +#include <cstddef> +#include <cassert> +#include <algorithm> + +using std::string; + +namespace ingen { + +using namespace client; + +namespace gui { + +LoadPluginWindow::LoadPluginWindow(BaseObjectType* cobject, + const Glib::RefPtr<Gtk::Builder>& xml) + : Window(cobject) + , _name_offset(0) + , _has_shown(false) + , _refresh_list(true) +{ + xml->get_widget("load_plugin_plugins_treeview", _plugins_treeview); + xml->get_widget("load_plugin_polyphonic_checkbutton", _polyphonic_checkbutton); + xml->get_widget("load_plugin_name_entry", _name_entry); + xml->get_widget("load_plugin_add_button", _add_button); + xml->get_widget("load_plugin_close_button", _close_button); + + xml->get_widget("load_plugin_filter_combo", _filter_combo); + xml->get_widget("load_plugin_search_entry", _search_entry); + + // Set up the plugins list + _plugins_liststore = Gtk::ListStore::create(_plugins_columns); + _plugins_treeview->set_model(_plugins_liststore); + _plugins_treeview->append_column("_Name", _plugins_columns._col_name); + _plugins_treeview->append_column("_Type", _plugins_columns._col_type); + _plugins_treeview->append_column("_Project", _plugins_columns._col_project); + _plugins_treeview->append_column("_Author", _plugins_columns._col_author); + _plugins_treeview->append_column("_URI", _plugins_columns._col_uri); + + // This could be nicer.. store the TreeViewColumns locally maybe? + _plugins_treeview->get_column(0)->set_sort_column(_plugins_columns._col_name); + _plugins_treeview->get_column(1)->set_sort_column(_plugins_columns._col_type); + _plugins_treeview->get_column(2)->set_sort_column(_plugins_columns._col_project); + _plugins_treeview->get_column(2)->set_sort_column(_plugins_columns._col_author); + _plugins_treeview->get_column(3)->set_sort_column(_plugins_columns._col_uri); + for (int i = 0; i < 5; ++i) { + _plugins_treeview->get_column(i)->set_resizable(true); + } + + // Set up the search criteria combobox + _criteria_liststore = Gtk::ListStore::create(_criteria_columns); + _filter_combo->set_model(_criteria_liststore); + + Gtk::TreeModel::iterator iter = _criteria_liststore->append(); + Gtk::TreeModel::Row row = *iter; + row[_criteria_columns._col_label] = "Name contains"; + row[_criteria_columns._col_criteria] = CriteriaColumns::Criteria::NAME; + _filter_combo->set_active(iter); + + row = *(iter = _criteria_liststore->append()); + row[_criteria_columns._col_label] = "Type contains"; + row[_criteria_columns._col_criteria] = CriteriaColumns::Criteria::TYPE; + + row = *(iter = _criteria_liststore->append()); + row[_criteria_columns._col_label] = "Project contains"; + row[_criteria_columns._col_criteria] = CriteriaColumns::Criteria::PROJECT; + + row = *(iter = _criteria_liststore->append()); + row[_criteria_columns._col_label] = "Author contains"; + row[_criteria_columns._col_criteria] = CriteriaColumns::Criteria::AUTHOR; + + row = *(iter = _criteria_liststore->append()); + row[_criteria_columns._col_label] = "URI contains"; + row[_criteria_columns._col_criteria] = CriteriaColumns::Criteria::URI; + _filter_combo->pack_start(_criteria_columns._col_label); + + _add_button->signal_clicked().connect( + sigc::mem_fun(this, &LoadPluginWindow::add_clicked)); + _close_button->signal_clicked().connect( + sigc::mem_fun(this, &Window::hide)); + _plugins_treeview->signal_row_activated().connect( + sigc::mem_fun(this, &LoadPluginWindow::plugin_activated)); + _search_entry->signal_activate().connect( + sigc::mem_fun(this, &LoadPluginWindow::add_clicked)); + _search_entry->signal_changed().connect( + sigc::mem_fun(this, &LoadPluginWindow::filter_changed)); + _name_entry->signal_changed().connect( + sigc::mem_fun(this, &LoadPluginWindow::name_changed)); + + _search_entry->signal_icon_release().connect( + sigc::mem_fun(this, &LoadPluginWindow::name_cleared)); + + _selection = _plugins_treeview->get_selection(); + _selection->set_mode(Gtk::SELECTION_MULTIPLE); + _selection->signal_changed().connect( + sigc::mem_fun(this, &LoadPluginWindow::plugin_selection_changed)); + + //m_add_button->grab_default(); +} + +void +LoadPluginWindow::present(SPtr<const GraphModel> graph, + Properties data) +{ + set_graph(graph); + _initial_data = data; + Gtk::Window::present(); +} + +/** Called every time the user types into the name input box. + * Used to display warning messages, and enable/disable the OK button. + */ +void +LoadPluginWindow::name_changed() +{ + // Toggle add button sensitivity according name legality + if (_selection->get_selected_rows().size() == 1) { + const string sym = _name_entry->get_text(); + if (!Raul::Symbol::is_valid(sym)) { + _add_button->property_sensitive() = false; + } else if (_app->store()->find(_graph->path().child(Raul::Symbol(sym))) + != _app->store()->end()) { + _add_button->property_sensitive() = false; + } else { + _add_button->property_sensitive() = true; + } + } +} + +void +LoadPluginWindow::name_cleared(Gtk::EntryIconPosition pos, const GdkEventButton* event) +{ + _search_entry->set_text(""); +} + +/** Sets the graph controller for this window and initializes everything. + * + * This function MUST be called before using the window in any way! + */ +void +LoadPluginWindow::set_graph(SPtr<const GraphModel> graph) +{ + if (_graph) { + _graph = graph; + plugin_selection_changed(); + } else { + _graph = graph; + } +} + +/** Populates the plugin list on the first show. + * + * This is done here instead of construction time as the list population is + * really expensive and bogs down creation of a graph. This is especially + * important when many graph notifications are sent at one time from the + * engine. + */ +void +LoadPluginWindow::on_show() +{ + if (!_has_shown) { + _app->store()->signal_new_plugin().connect( + sigc::mem_fun(this, &LoadPluginWindow::add_plugin)); + _has_shown = true; + } + + if (_refresh_list) { + set_plugins(_app->store()->plugins()); + _refresh_list = false; + } + + Gtk::Window::on_show(); +} + +void +LoadPluginWindow::set_plugins(SPtr<const ClientStore::Plugins> plugins) +{ + _rows.clear(); + _plugins_liststore->clear(); + + for (const auto& p : *plugins.get()) { + add_plugin(p.second); + } + + _plugins_liststore->set_sort_column(1, Gtk::SORT_ASCENDING); + _plugins_treeview->columns_autosize(); +} + +void +LoadPluginWindow::new_plugin(SPtr<const PluginModel> pm) +{ + if (is_visible()) { + add_plugin(pm); + } else { + _refresh_list = true; + } +} + +static std::string +get_project_name(SPtr<const PluginModel> plugin) +{ + std::string name; + if (plugin->lilv_plugin()) { + LilvNode* project = lilv_plugin_get_project(plugin->lilv_plugin()); + if (!project) { + return ""; + } + + LilvNode* doap_name = lilv_new_uri( + plugin->lilv_world(), "http://usefulinc.com/ns/doap#name"); + LilvNodes* names = lilv_world_find_nodes( + plugin->lilv_world(), project, doap_name, nullptr); + + if (names) { + name = lilv_node_as_string(lilv_nodes_get_first(names)); + } + + lilv_nodes_free(names); + lilv_node_free(doap_name); + lilv_node_free(project); + } + return name; +} + +static std::string +get_author_name(SPtr<const PluginModel> plugin) +{ + std::string name; + if (plugin->lilv_plugin()) { + LilvNode* author = lilv_plugin_get_author_name(plugin->lilv_plugin()); + if (author) { + name = lilv_node_as_string(author); + } + lilv_node_free(author); + } + return name; +} + +void +LoadPluginWindow::set_row(Gtk::TreeModel::Row& row, + SPtr<const PluginModel> plugin) +{ + const URIs& uris = _app->uris(); + const Atom& name = plugin->get_property(uris.doap_name); + if (name.is_valid() && name.type() == uris.forge.String) { + row[_plugins_columns._col_name] = name.ptr<char>(); + } + + if (uris.lv2_Plugin == plugin->type()) { + row[_plugins_columns._col_type] = lilv_node_as_string( + lilv_plugin_class_get_label( + lilv_plugin_get_class(plugin->lilv_plugin()))); + + row[_plugins_columns._col_project] = get_project_name(plugin); + row[_plugins_columns._col_author] = get_author_name(plugin); + } else if (uris.ingen_Internal == plugin->type()) { + row[_plugins_columns._col_type] = "Internal"; + row[_plugins_columns._col_project] = "Ingen"; + row[_plugins_columns._col_author] = "David Robillard"; + } else if (uris.ingen_Graph == plugin->type()) { + row[_plugins_columns._col_type] = "Graph"; + } else { + row[_plugins_columns._col_type] = ""; + } + + row[_plugins_columns._col_uri] = plugin->uri().string(); + row[_plugins_columns._col_plugin] = plugin; +} + +void +LoadPluginWindow::add_plugin(SPtr<const PluginModel> plugin) +{ + if (plugin->lilv_plugin() && lilv_plugin_is_replaced(plugin->lilv_plugin())) { + return; + } + + Gtk::TreeModel::iterator iter = _plugins_liststore->append(); + Gtk::TreeModel::Row row = *iter; + _rows.emplace(plugin->uri(), iter); + + set_row(row, plugin); + + plugin->signal_property().connect( + sigc::bind<0>(sigc::mem_fun(this, &LoadPluginWindow::plugin_property_changed), + plugin->uri())); +} + +///// Event Handlers ////// + +void +LoadPluginWindow::plugin_activated(const Gtk::TreeModel::Path& path, + Gtk::TreeViewColumn* col) +{ + add_clicked(); +} + +void +LoadPluginWindow::plugin_selection_changed() +{ + size_t n_selected = _selection->get_selected_rows().size(); + if (n_selected == 0) { + _name_offset = 0; + _name_entry->set_text(""); + _name_entry->set_sensitive(false); + } else if (n_selected == 1) { + Gtk::TreeModel::iterator iter = _plugins_liststore->get_iter( + *_selection->get_selected_rows().begin()); + if (iter) { + Gtk::TreeModel::Row row = *iter; + SPtr<const PluginModel> p = row.get_value( + _plugins_columns._col_plugin); + _name_offset = _app->store()->child_name_offset( + _graph->path(), p->default_block_symbol()); + _name_entry->set_text(generate_module_name(p, _name_offset)); + _name_entry->set_sensitive(true); + } else { + _name_offset = 0; + _name_entry->set_text(""); + _name_entry->set_sensitive(false); + } + } else { + _name_entry->set_text(""); + _name_entry->set_sensitive(false); + } +} + +/** Generate an automatic name for this Node. + * + * Offset is an offset of the number that will be appended to the plugin's + * label, needed if the user adds multiple plugins faster than the engine + * sends the notification back. + */ +string +LoadPluginWindow::generate_module_name(SPtr<const PluginModel> plugin, + int offset) +{ + std::stringstream ss; + ss << plugin->default_block_symbol(); + if (offset != 0) { + ss << "_" << offset; + } + return ss.str(); +} + +void +LoadPluginWindow::load_plugin(const Gtk::TreeModel::iterator& iter) +{ + const URIs& uris = _app->uris(); + Gtk::TreeModel::Row row = *iter; + SPtr<const PluginModel> plugin = row.get_value(_plugins_columns._col_plugin); + bool polyphonic = _polyphonic_checkbutton->get_active(); + string name = _name_entry->get_text(); + + if (name.empty()) { + name = generate_module_name(plugin, _name_offset); + } + + if (name.empty() || !Raul::Symbol::is_valid(name)) { + Gtk::MessageDialog dialog( + *this, + "Unable to choose a default name, please provide one", + false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); + + dialog.run(); + } else { + Raul::Path path = _graph->path().child(Raul::Symbol::symbolify(name)); + Properties props = _initial_data; + props.emplace(uris.rdf_type, Property(uris.ingen_Block)); + props.emplace(uris.lv2_prototype, _app->forge().make_urid(plugin->uri())); + props.emplace(uris.ingen_polyphonic, _app->forge().make(polyphonic)); + _app->interface()->put(path_to_uri(path), props); + + if (_selection->get_selected_rows().size() == 1) { + _name_offset = (_name_offset == 0) ? 2 : _name_offset + 1; + _name_entry->set_text(generate_module_name(plugin, _name_offset)); + } + + // Cascade next block + Atom& x = _initial_data.find(uris.ingen_canvasX)->second; + x = _app->forge().make(x.get<float>() + 20.0f); + Atom& y = _initial_data.find(uris.ingen_canvasY)->second; + y = _app->forge().make(y.get<float>() + 20.0f); + } +} + +void +LoadPluginWindow::add_clicked() +{ + _selection->selected_foreach_iter( + sigc::mem_fun(*this, &LoadPluginWindow::load_plugin)); +} + +void +LoadPluginWindow::filter_changed() +{ + _rows.clear(); + _plugins_liststore->clear(); + string search = _search_entry->get_text(); + transform(search.begin(), search.end(), search.begin(), ::toupper); + + // Get selected criteria + const Gtk::TreeModel::Row row = *(_filter_combo->get_active()); + CriteriaColumns::Criteria criteria = row[_criteria_columns._col_criteria]; + + string field; + + Gtk::TreeModel::Row model_row; + Gtk::TreeModel::iterator model_iter; + size_t num_visible = 0; + const URIs& uris = _app->uris(); + + for (const auto& p : *_app->store()->plugins().get()) { + const SPtr<PluginModel> plugin = p.second; + const Atom& name = plugin->get_property(uris.doap_name); + + switch (criteria) { + case CriteriaColumns::Criteria::NAME: + if (name.is_valid() && name.type() == uris.forge.String) { + field = name.ptr<char>(); + } + break; + case CriteriaColumns::Criteria::TYPE: + if (plugin->lilv_plugin()) { + field = lilv_node_as_string( + lilv_plugin_class_get_label( + lilv_plugin_get_class(plugin->lilv_plugin()))); + } + break; + case CriteriaColumns::Criteria::PROJECT: + field = get_project_name(plugin); + break; + case CriteriaColumns::Criteria::AUTHOR: + field = get_author_name(plugin); + break; + case CriteriaColumns::Criteria::URI: + field = plugin->uri(); + break; + } + + transform(field.begin(), field.end(), field.begin(), ::toupper); + + if (field.find(search) != string::npos) { + model_iter = _plugins_liststore->append(); + model_row = *model_iter; + set_row(model_row, plugin); + ++num_visible; + } + } + + if (num_visible == 1) { + _selection->unselect_all(); + _selection->select(model_iter); + } +} + +bool +LoadPluginWindow::on_key_press_event(GdkEventKey* event) +{ + if (event->keyval == GDK_w && event->state & GDK_CONTROL_MASK) { + hide(); + return true; + } else { + return Gtk::Window::on_key_press_event(event); + } +} + +void +LoadPluginWindow::plugin_property_changed(const URI& plugin, + const URI& predicate, + const Atom& value) +{ + const URIs& uris = _app->uris(); + if (predicate == uris.doap_name) { + Rows::const_iterator i = _rows.find(plugin); + if (i != _rows.end() && value.type() == uris.forge.String) { + (*i->second)[_plugins_columns._col_name] = value.ptr<char>(); + } + } +} + +} // namespace gui +} // namespace ingen |