/* This file is part of Ingen. * Copyright (C) 2007-2009 Dave Robillard * * Ingen is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) 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 General Public License for details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include "interface/EngineInterface.hpp" #include "client/PatchModel.hpp" #include "client/ClientStore.hpp" #include "App.hpp" #include "LoadPluginWindow.hpp" #include "PatchWindow.hpp" #include "PatchView.hpp" #include "PatchCanvas.hpp" #define NAME_ENTRY_MULTI_STRING "(multiple values)" using namespace std; using namespace Raul; namespace Ingen { namespace GUI { LoadPluginWindow::LoadPluginWindow(BaseObjectType* cobject, const Glib::RefPtr& xml) : Window(cobject) , _plugin_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); iter = _criteria_liststore->append(); row = *iter; row[_criteria_columns._col_label] = "Type contains"; row[_criteria_columns._col_criteria] = CriteriaColumns::TYPE; iter = _criteria_liststore->append(); row = *iter; row[_criteria_columns._col_label] = "URI contains"; row[_criteria_columns._col_criteria] = CriteriaColumns::URI; _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)) { //m_message_label->set_text("Name contains invalid characters."); _add_button->property_sensitive() = false; } else if (App::instance().store()->find_child(_patch, name)) { //m_message_label->set_text("An object already exists with that name."); _add_button->property_sensitive() = false; } else if (name.length() == 0) { //m_message_label->set_text(""); _add_button->property_sensitive() = false; } else { //m_message_label->set_text(""); _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::instance().store()->signal_new_plugin.connect( sigc::mem_fun(this, &LoadPluginWindow::add_plugin)); _has_shown = true; } if (_refresh_list) { set_plugins(App::instance().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::add_plugin(SharedPtr plugin) { Gtk::TreeModel::iterator iter = _plugins_liststore->append(); Gtk::TreeModel::Row row = *iter; _rows.insert(make_pair(plugin->uri(), iter)); const Atom& name = plugin->get_property("doap:name"); if (name.is_valid()) { if (name.type() == Atom::STRING) row[_plugins_columns._col_name] = name.get_string(); } else if (plugin->type() == Plugin::LADSPA) { App::instance().engine()->request_property(plugin->uri(), "doap:name"); } switch (plugin->type()) { case Plugin::LV2: row[_plugins_columns._col_type] = "LV2"; break; case Plugin::LADSPA: row[_plugins_columns._col_type] = "LADSPA"; 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; 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) { _plugin_name_offset = 0; _node_name_entry->set_text(""); } 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); _plugin_name_offset = App::instance().store()->child_name_offset( _patch->path(), p->default_node_symbol()); _node_name_entry->set_text(generate_module_name(p, _plugin_name_offset)); } else { _plugin_name_offset = 0; _node_name_entry->set_text(""); } } else { _node_name_entry->set_text(NAME_ENTRY_MULTI_STRING); } } /** 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 + 1; return ss.str(); } void LoadPluginWindow::load_plugin(const Gtk::TreeModel::iterator& iter) { 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 == "" || name == NAME_ENTRY_MULTI_STRING) name = generate_module_name(plugin, _plugin_name_offset); if (name == "" || !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("rdf:type", Atom(Atom::URI, "ingen:Node"))); props.insert(make_pair("rdf:instanceOf", Atom(Atom::URI, plugin->uri().str()))); props.insert(make_pair("ingen:polyphonic", polyphonic)); App::instance().engine()->put(path, props); if (_selection->get_selected_rows().size() == 1) _node_name_entry->set_text(generate_module_name(plugin, _plugin_name_offset + 1)); // Cascade next node Atom& x = _initial_data.find("ingenuity:canvas-x")->second; x = Atom(x.get_float() + 20.0f); Atom& y = _initial_data.find("ingenuity:canvas-y")->second; y = Atom(y.get_float() + 20.0f); } } void LoadPluginWindow::add_clicked() { _selection->selected_foreach_iter( sigc::mem_fun(*this, &LoadPluginWindow::load_plugin)); ++_plugin_name_offset; } 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; for (ClientStore::Plugins::const_iterator i = App::instance().store()->plugins()->begin(); i != App::instance().store()->plugins()->end(); ++i) { const SharedPtr plugin = (*i).second; const Atom& name = plugin->get_property("doap:name"); switch (criteria) { case CriteriaColumns::NAME: if (name.is_valid() && name.type() == Atom::STRING) field = name.get_string(); break; case CriteriaColumns::TYPE: field = plugin->type_uri(); 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; model_row[_plugins_columns._col_name] = name.is_valid() ? name.get_string() : ""; model_row[_plugins_columns._col_type] = plugin->type_uri(); model_row[_plugins_columns._col_uri] = plugin->uri().str(); model_row[_plugins_columns._col_plugin] = 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) { if (predicate.str() == "doap:name") { Rows::const_iterator i = _rows.find(plugin); if (i != _rows.end() && value.type() == Atom::STRING) (*i->second)[_plugins_columns._col_name] = value.get_string(); } } } // namespace GUI } // namespace Ingen