/* This file is part of Ingen. Copyright 2007-2012 David Robillard 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 . */ #include #include #include #include "ingen/Interface.hpp" #include "ingen/client/ClientStore.hpp" #include "ingen/client/PatchModel.hpp" #include "ingen/shared/LV2URIMap.hpp" #include "App.hpp" #include "LoadPluginWindow.hpp" #include "PatchCanvas.hpp" #include "PatchView.hpp" #include "PatchWindow.hpp" #include "ingen_config.h" using namespace std; using namespace Raul; namespace Ingen { using namespace Client; using namespace Shared; namespace GUI { LoadPluginWindow::LoadPluginWindow(BaseObjectType* cobject, const Glib::RefPtr& 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", _node_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("", _plugins_columns._col_icon); _plugins_treeview->append_column("_Name", _plugins_columns._col_name); _plugins_treeview->append_column("_Type", _plugins_columns._col_type); _plugins_treeview->append_column("_URI", _plugins_columns._col_uri); // This could be nicer.. store the TreeViewColumns locally maybe? _plugins_treeview->get_column(1)->set_sort_column(_plugins_columns._col_name); _plugins_treeview->get_column(2)->set_sort_column(_plugins_columns._col_type); _plugins_treeview->get_column(3)->set_sort_column(_plugins_columns._col_uri); for (int i=0; i < 3; ++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::NAME; _filter_combo->set_active(iter); row = *(iter = _criteria_liststore->append()); row[_criteria_columns._col_label] = "Type contains"; row[_criteria_columns._col_criteria] = CriteriaColumns::TYPE; row = *(iter = _criteria_liststore->append()); row[_criteria_columns._col_label] = "URI contains"; row[_criteria_columns._col_criteria] = CriteriaColumns::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)); _node_name_entry->signal_changed().connect( sigc::mem_fun(this, &LoadPluginWindow::name_changed)); #ifdef HAVE_NEW_GTKMM _search_entry->signal_icon_release().connect( sigc::mem_fun(this, &LoadPluginWindow::name_cleared)); #endif _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(SharedPtr patch, GraphObject::Properties data) { set_patch(patch); _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) { string name = _node_name_entry->get_text(); if (!Path::is_valid_name(name)) { _add_button->property_sensitive() = false; } else if (_app->store()->find_child(_patch, name)) { _add_button->property_sensitive() = false; } else if (name.length() == 0) { _add_button->property_sensitive() = false; } else { _add_button->property_sensitive() = true; } } } #ifdef HAVE_NEW_GTKMM void LoadPluginWindow::name_cleared(Gtk::EntryIconPosition pos, const GdkEventButton* event) { _search_entry->set_text(""); } #endif // HAVE_NEW_GTKMM /** Sets the patch controller for this window and initializes everything. * * This function MUST be called before using the window in any way! */ void LoadPluginWindow::set_patch(SharedPtr patch) { if (_patch) { _patch = patch; plugin_selection_changed(); } else { _patch = patch; } /*if (patch->poly() <= 1) _polyphonic_checkbutton->property_sensitive() = false; else _polyphonic_checkbutton->property_sensitive() = true;*/ } /** 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 patch. This is especially * important when many patch 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(SharedPtr m) { _rows.clear(); _plugins_liststore->clear(); for (ClientStore::Plugins::const_iterator i = m->begin(); i != m->end(); ++i) { add_plugin(i->second); } _plugins_liststore->set_sort_column(1, //Gtk::TreeSortable::DEFAULT_SORT_COLUMN_ID, Gtk::SORT_ASCENDING); _plugins_treeview->columns_autosize(); } void LoadPluginWindow::new_plugin(SharedPtr pm) { if (is_visible()) add_plugin(pm); else _refresh_list = true; } void LoadPluginWindow::set_row(Gtk::TreeModel::Row& row, SharedPtr 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.get_string(); switch (plugin->type()) { case Plugin::LV2: row[_plugins_columns._col_type] = "LV2"; break; case Plugin::Internal: row[_plugins_columns._col_type] = "Internal"; break; case Plugin::Patch: row[_plugins_columns._col_type] = "Patch"; break; case Plugin::NIL: row[_plugins_columns._col_type] = "?"; break; } row[_plugins_columns._col_uri] = plugin->uri().str(); row[_plugins_columns._col_plugin] = plugin; } void LoadPluginWindow::add_plugin(SharedPtr 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.insert(make_pair(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; _node_name_entry->set_text(""); _node_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; boost::shared_ptr p = row.get_value( _plugins_columns._col_plugin); _name_offset = _app->store()->child_name_offset( _patch->path(), p->default_node_symbol()); _node_name_entry->set_text(generate_module_name(p, _name_offset)); _node_name_entry->set_sensitive(true); } else { _name_offset = 0; _node_name_entry->set_text(""); _node_name_entry->set_sensitive(false); } } else { _node_name_entry->set_text(""); _node_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(SharedPtr plugin, int offset) { std::stringstream ss; ss << plugin->default_node_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; SharedPtr plugin = row.get_value(_plugins_columns._col_plugin); //bool polyphonic = _polyphonic_checkbutton->get_active(); string name = _node_name_entry->get_text(); if (name.empty()) name = generate_module_name(plugin, _name_offset); if (name.empty() || !Symbol::is_valid(name)) { Gtk::MessageDialog dialog(*this, "Unable to chose a default name for this node. Please enter a name.", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); dialog.run(); } else { Path path = _patch->path().base() + Path::nameify(name); Resource::Properties props = _initial_data; props.insert(make_pair(uris.rdf_type, uris.ingen_Node)); props.insert(make_pair(uris.rdf_instanceOf, _app->forge().alloc_uri(plugin->uri().str()))); Raul::warn << "FIXME: polyphonic" << std::endl; //props.insert(make_pair(uris.ingen_polyphonic, // _app->forge().make(polyphonic))); _app->engine()->put(path, props); if (_selection->get_selected_rows().size() == 1) { _name_offset = (_name_offset == 0) ? 2 : _name_offset + 1; _node_name_entry->set_text(generate_module_name(plugin, _name_offset)); } // Cascade next node 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 (ClientStore::Plugins::const_iterator i = _app->store()->plugins()->begin(); i != _app->store()->plugins()->end(); ++i) { const SharedPtr plugin = (*i).second; const Atom& name = plugin->get_property(uris.doap_name); switch (criteria) { case CriteriaColumns::NAME: if (name.is_valid() && name.type() == uris.forge.String) field = name.get_string(); break; case CriteriaColumns::TYPE: field = plugin->type_uri().str(); break; case CriteriaColumns::URI: field = plugin->uri().str(); break; default: throw; } 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.get_string(); } } } // namespace GUI } // namespace Ingen