/*
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
#include "ingen/Interface.hpp"
#include "ingen/client/ClientStore.hpp"
#include "ingen/client/PatchModel.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.ingen_prototype,
_app->forge().alloc_uri(plugin->uri().str())));
props.insert(make_pair(uris.ingen_polyphonic,
_app->forge().make(polyphonic)));
_app->interface()->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