From c0da1cf368b7d43c9c886b81768b4a62a07fae3f Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 28 Mar 2015 09:47:19 +0000 Subject: Unify value widgets in properties dialog. This shows the fancy URI selector for URI properties, and can show numeric controls for the property-to-add. The ontologies need some work, along with smarter widget creation, before the latter will actually be useful. git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@5648 a436a847-0d15-0410-975c-d299462d15a1 --- src/gui/App.cpp | 10 + src/gui/App.hpp | 15 +- src/gui/Port.cpp | 8 +- src/gui/PropertiesWindow.cpp | 485 ++++++++++++++++++------------------------- src/gui/PropertiesWindow.hpp | 29 ++- src/gui/RDFS.cpp | 52 ++++- src/gui/RDFS.hpp | 25 ++- src/gui/URIEntry.cpp | 172 +++++++++++++++ src/gui/URIEntry.hpp | 70 +++++++ src/gui/ingen_gui.ui | 30 +-- src/gui/wscript | 1 + 11 files changed, 548 insertions(+), 349 deletions(-) create mode 100644 src/gui/URIEntry.cpp create mode 100644 src/gui/URIEntry.hpp (limited to 'src/gui') diff --git a/src/gui/App.cpp b/src/gui/App.cpp index 1bb9ba4d..65f5c90a 100644 --- a/src/gui/App.cpp +++ b/src/gui/App.cpp @@ -44,6 +44,7 @@ #include "MessagesWindow.hpp" #include "NodeModule.hpp" #include "Port.hpp" +#include "RDFS.hpp" #include "Style.hpp" #include "SubgraphModule.hpp" #include "ThreadedLoader.hpp" @@ -235,6 +236,15 @@ App::set_property(const Raul::URI& subject, _client->signal_property_change().emit(subject, key, value); } +void +App::set_tooltip(Gtk::Widget* widget, const LilvNode* node) +{ + const std::string comment = RDFS::comment(_world, node); + if (!comment.empty()) { + widget->set_tooltip_text(comment); + } +} + void App::property_change(const Raul::URI& subject, const Raul::URI& key, diff --git a/src/gui/App.hpp b/src/gui/App.hpp index 0e3a4079..db7af77b 100644 --- a/src/gui/App.hpp +++ b/src/gui/App.hpp @@ -1,6 +1,6 @@ /* This file is part of Ingen. - Copyright 2007-2012 David Robillard + Copyright 2007-2015 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 @@ -17,10 +17,8 @@ #ifndef INGEN_GUI_APP_HPP #define INGEN_GUI_APP_HPP -#include #include #include -#include #include #include @@ -31,25 +29,27 @@ #include "ingen/World.hpp" #include "ingen/ingen.h" #include "ingen/types.hpp" +#include "lilv/lilv.h" #include "raul/Deletable.hpp" #include "raul/URI.hpp" namespace Ingen { + class Interface; class Log; class Port; class Serialiser; class World; + namespace Client { + class ClientStore; class GraphModel; class PluginModel; class PortModel; class SigClientInterface; -} -} -namespace Ingen { +} namespace GUI { @@ -101,6 +101,9 @@ public: const Raul::URI& key, const Atom& value); + /** Set the tooltip for a widget from its RDF documentation. */ + void set_tooltip(Gtk::Widget* widget, const LilvNode* node); + uint32_t sample_rate() const; ConnectWindow* connect_window() const { return _connect_window; } diff --git a/src/gui/Port.cpp b/src/gui/Port.cpp index 8076eb60..e7f32fbe 100644 --- a/src/gui/Port.cpp +++ b/src/gui/Port.cpp @@ -284,14 +284,14 @@ Port::build_uri_menu() // Add a menu item for each such class for (const auto& v : values) { - if (!v.second.empty()) { - Glib::ustring label = world->rdf_world()->prefixes().qualify(v.first) - + " - " + v.second; + if (!v.first.empty()) { + const std::string qname = world->rdf_world()->prefixes().qualify(v.second); + const std::string label = qname + " - " + v.first; menu->items().push_back(Gtk::Menu_Helpers::MenuElem(label)); Gtk::MenuItem* menu_item = &(menu->items().back()); menu_item->signal_activate().connect( sigc::bind(sigc::mem_fun(this, &Port::on_uri_activated), - v.first)); + v.second)); } } diff --git a/src/gui/PropertiesWindow.cpp b/src/gui/PropertiesWindow.cpp index 1eb92b10..6db19e65 100644 --- a/src/gui/PropertiesWindow.cpp +++ b/src/gui/PropertiesWindow.cpp @@ -31,6 +31,7 @@ #include "App.hpp" #include "PropertiesWindow.hpp" #include "RDFS.hpp" +#include "URIEntry.hpp" using namespace std; @@ -45,13 +46,13 @@ typedef std::set URISet; PropertiesWindow::PropertiesWindow(BaseObjectType* cobject, const Glib::RefPtr& xml) : Window(cobject) + , _value_type(0) { xml->get_widget("properties_vbox", _vbox); xml->get_widget("properties_scrolledwindow", _scrolledwindow); xml->get_widget("properties_table", _table); xml->get_widget("properties_key_combo", _key_combo); - xml->get_widget("properties_value_entry", _value_entry); - xml->get_widget("properties_value_button", _value_button); + xml->get_widget("properties_value_bin", _value_bin); xml->get_widget("properties_add_button", _add_button); xml->get_widget("properties_cancel_button", _cancel_button); xml->get_widget("properties_apply_button", _apply_button); @@ -64,9 +65,6 @@ PropertiesWindow::PropertiesWindow(BaseObjectType* cobject, _key_combo->signal_changed().connect( sigc::mem_fun(this, &PropertiesWindow::key_changed)); - _value_button->signal_event().connect( - sigc::mem_fun(this, &PropertiesWindow::value_clicked)); - _add_button->signal_clicked().connect( sigc::mem_fun(this, &PropertiesWindow::add_clicked)); @@ -87,7 +85,6 @@ PropertiesWindow::reset() _property_removed_connection.disconnect(); _key_store->clear(); - _value_entry->set_text(""); _records.clear(); _model.reset(); @@ -105,7 +102,7 @@ PropertiesWindow::present(SPtr model) } void -PropertiesWindow::add_property(const Raul::URI& uri, const Atom& value) +PropertiesWindow::add_property(const Raul::URI& key, const Atom& value) { World* world = _app->world(); @@ -113,33 +110,36 @@ PropertiesWindow::add_property(const Raul::URI& uri, const Atom& value) _table->property_n_rows() = n_rows; // Column 0: Property - LilvNode* prop = lilv_new_uri(world->lilv_world(), uri.c_str()); - Glib::ustring lab_text = RDFS::label(world, prop); - if (lab_text.empty()) { - lab_text = world->rdf_world()->prefixes().qualify(uri); + LilvNode* prop = lilv_new_uri(world->lilv_world(), key.c_str()); + std::string name = RDFS::label(world, prop); + if (name.empty()) { + name = world->rdf_world()->prefixes().qualify(key); } - lab_text = Glib::ustring("" - + lab_text + ""; - Gtk::Label* lab = manage(new Gtk::Label(lab_text, 1.0, 0.5)); - lab->set_use_markup(true); - set_tooltip(lab, prop); - _table->attach(*lab, 0, 1, n_rows, n_rows + 1, + Gtk::Label* label = new Gtk::Label( + std::string("" + name + "", 1.0, 0.5); + label->set_use_markup(true); + _app->set_tooltip(label, prop); + _table->attach(*Gtk::manage(label), 0, 1, n_rows, n_rows + 1, Gtk::FILL|Gtk::SHRINK, Gtk::SHRINK); // Column 1: Value - Gtk::Alignment* align = manage(new Gtk::Alignment(0.0, 0.5, 1.0, 0.0)); + Gtk::Alignment* align = manage(new Gtk::Alignment(0.0, 0.5, 1.0, 1.0)); Gtk::CheckButton* present = manage(new Gtk::CheckButton()); - Gtk::Widget* val_widget = create_value_widget(uri, value); - present->set_active(true); + const char* type = _app->world()->uri_map().unmap_uri(value.type()); + Gtk::Widget* val_widget = create_value_widget(key, type, value); + + present->set_active(); if (val_widget) { - align->add(*val_widget); - set_tooltip(val_widget, prop); + align->add(*Gtk::manage(val_widget)); + _app->set_tooltip(val_widget, prop); } + _table->attach(*align, 1, 2, n_rows, n_rows + 1, Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK); _table->attach(*present, 2, 3, n_rows, n_rows + 1, Gtk::FILL, Gtk::SHRINK); - _records.insert(make_pair(uri, Record(value, align, n_rows, present))); + _records.insert(make_pair(key, Record(value, align, n_rows, present))); + _table->show_all(); lilv_node_free(prop); } @@ -157,30 +157,22 @@ PropertiesWindow::set_object(SPtr model) World* world = _app->world(); - LilvNode* rdfs_range = lilv_new_uri( - world->lilv_world(), LILV_NS_RDFS "range"); LilvNode* rdf_type = lilv_new_uri( world->lilv_world(), LILV_NS_RDF "type"); // Populate key combo - const URISet props = RDFS::properties(world, model); + const URISet props = RDFS::properties(world, model); + std::map entries; for (const auto& p : props) { - LilvNode* prop = lilv_new_uri(world->lilv_world(), p.c_str()); - const Glib::ustring label = RDFS::label(world, prop); + LilvNode* prop = lilv_new_uri(world->lilv_world(), p.c_str()); + const std::string label = RDFS::label(world, prop); if (label.empty()) { continue; } - // Get all classes in the range of this property (including sub-classes) - LilvNodes* range = lilv_world_find_nodes( - world->lilv_world(), prop, rdfs_range, NULL); - URISet ranges; - LILV_FOREACH(nodes, r, range) { - ranges.insert(Raul::URI(lilv_node_as_string(lilv_nodes_get(range, r)))); - } - RDFS::classes(world, ranges, false); - - bool show = false; + // Get all classes in the range of this property (including subclasses) + URISet ranges = RDFS::range(world, prop, true); + bool show = false; for (const auto& r : ranges) { LilvNode* range = lilv_new_uri(world->lilv_world(), r.c_str()); LilvNodes* objects = lilv_world_find_nodes( @@ -191,21 +183,24 @@ PropertiesWindow::set_object(SPtr model) lilv_nodes_free(objects); lilv_node_free(range); if (show) { - break; // At least one appliable object + break; // At least one applicable object } } if (show || ranges.empty()) { - Gtk::ListStore::iterator ki = _key_store->append(); - Gtk::ListStore::Row row = *ki; - row[_combo_columns.uri_col] = p; - row[_combo_columns.label_col] = label; + entries.insert(std::make_pair(label, p)); } lilv_node_free(prop); } - lilv_node_free(rdfs_range); + for (const auto& e : entries) { + Gtk::ListStore::iterator ki = _key_store->append(); + Gtk::ListStore::Row row = *ki; + row[_combo_columns.uri_col] = e.second; + row[_combo_columns.label_col] = e.first; + } + lilv_node_free(rdf_type); for (const auto& p : model->properties()) { @@ -215,71 +210,98 @@ PropertiesWindow::set_object(SPtr model) _table->show_all(); _property_connection = model->signal_property().connect( - sigc::mem_fun(this, &PropertiesWindow::property_changed)); + sigc::mem_fun(this, &PropertiesWindow::add_property)); _property_removed_connection = model->signal_property_removed().connect( - sigc::mem_fun(this, &PropertiesWindow::property_removed)); + sigc::mem_fun(this, &PropertiesWindow::remove_property)); } Gtk::Widget* -PropertiesWindow::create_value_widget(const Raul::URI& uri, const Atom& value) +PropertiesWindow::create_value_widget(const Raul::URI& key, + const char* type_uri, + const Atom& value) { - Ingen::Forge& forge = _app->forge(); - if (value.type() == forge.Int) { + if (!type_uri || !Raul::URI::is_valid(type_uri)) { + return NULL; + } + + const Raul::URI type(type_uri); + Ingen::World* world = _app->world(); + LilvWorld* lworld = world->lilv_world(); + + if (type == _app->uris().atom_Int) { Gtk::SpinButton* widget = manage(new Gtk::SpinButton(0.0, 0)); widget->property_numeric() = true; widget->set_range(INT_MIN, INT_MAX); widget->set_increments(1, 10); - widget->set_value(value.get()); + if (value.is_valid()) { + widget->set_value(value.get()); + } widget->signal_value_changed().connect( - sigc::bind(sigc::mem_fun(this, &PropertiesWindow::value_edited), - uri)); + sigc::bind(sigc::mem_fun(this, &PropertiesWindow::on_change), key)); return widget; - } else if (value.type() == forge.Float) { + } else if (type == _app->uris().atom_Float) { Gtk::SpinButton* widget = manage(new Gtk::SpinButton(0.0, 4)); widget->property_numeric() = true; widget->set_snap_to_ticks(false); widget->set_range(-FLT_MAX, FLT_MAX); - widget->set_value(value.get()); widget->set_increments(0.1, 1.0); + if (value.is_valid()) { + widget->set_value(value.get()); + } widget->signal_value_changed().connect( - sigc::bind(sigc::mem_fun(this, &PropertiesWindow::value_edited), - uri)); + sigc::bind(sigc::mem_fun(this, &PropertiesWindow::on_change), key)); return widget; - } else if (value.type() == forge.Bool) { + } else if (type == _app->uris().atom_Bool) { Gtk::CheckButton* widget = manage(new Gtk::CheckButton()); - widget->set_active(value.get()); + if (value.is_valid()) { + widget->set_active(value.get()); + } widget->signal_toggled().connect( - sigc::bind(sigc::mem_fun(this, &PropertiesWindow::value_edited), - uri)); + sigc::bind(sigc::mem_fun(this, &PropertiesWindow::on_change), key)); return widget; - } else if (value.type() == forge.URI) { + } else if (type == _app->uris().atom_String) { Gtk::Entry* widget = manage(new Gtk::Entry()); - widget->set_text(value.ptr()); + if (value.is_valid()) { + widget->set_text(value.ptr()); + } widget->signal_changed().connect( - sigc::bind(sigc::mem_fun(this, &PropertiesWindow::value_edited), - uri)); + sigc::bind(sigc::mem_fun(this, &PropertiesWindow::on_change), key)); return widget; - } else if (value.type() == forge.URID) { - const char* val_uri = _app->world()->uri_map().unmap_uri(value.get()); - Gtk::Entry* widget = manage(new Gtk::Entry()); - if (val_uri) { - widget->set_text(val_uri); - } + } else if (type == _app->uris().atom_URID) { + const char* str = (value.is_valid() + ? world->uri_map().unmap_uri(value.get()) + : ""); + + LilvNode* pred = lilv_new_uri(lworld, key.c_str()); + URISet ranges = RDFS::range(world, pred, true); + URIEntry* widget = manage(new URIEntry(_app, ranges, str ? str : "")); widget->signal_changed().connect( - sigc::bind(sigc::mem_fun(this, &PropertiesWindow::value_edited), - uri)); + sigc::bind(sigc::mem_fun(this, &PropertiesWindow::on_change), key)); + lilv_node_free(pred); return widget; - } else if (value.type() == forge.String) { - Gtk::Entry* widget = manage(new Gtk::Entry()); - widget->set_text(value.ptr()); + } + + LilvNode* type_node = lilv_new_uri(lworld, type.c_str()); + LilvNode* rdfs_Class = lilv_new_uri(lworld, LILV_NS_RDFS "Class"); + const bool is_class = RDFS::is_a(world, type_node, rdfs_Class); + lilv_node_free(rdfs_Class); + lilv_node_free(type_node); + + if (type == _app->uris().atom_URI || + type == _app->uris().rdfs_Class || + is_class) { + LilvNode* pred = lilv_new_uri(lworld, key.c_str()); + URISet ranges = RDFS::range(world, pred, true); + const char* str = value.is_valid() ? value.ptr() : ""; + URIEntry* widget = manage(new URIEntry(_app, ranges, str)); widget->signal_changed().connect( - sigc::bind(sigc::mem_fun(this, &PropertiesWindow::value_edited), - uri)); + sigc::bind(sigc::mem_fun(this, &PropertiesWindow::on_change), key)); + lilv_node_free(pred); return widget; } - _app->log().error(fmt("Unable to create widget for value %1% type %2%\n") - % forge.str(value) % value.type()); + _app->log().error(fmt("No widget for value type %1%\n") % type); + return NULL; } @@ -291,16 +313,17 @@ PropertiesWindow::on_show() int width = 0; int height = 0; - Gtk::Requisition req; for (const auto& c : _vbox->children()) { - req = c.get_widget()->size_request(); + const Gtk::Requisition& req = c.get_widget()->size_request(); + width = std::max(width, req.width); height += req.height + VBOX_PAD; } - req = _table->size_request(); - width = 1.6 * std::max(width, req.width + 128); + const Gtk::Requisition& req = _table->size_request(); + + width = 1.2 * std::max(width, req.width + 128); height += req.height; set_default_size(width + WIN_PAD, height + WIN_PAD); @@ -309,30 +332,30 @@ PropertiesWindow::on_show() } void -PropertiesWindow::property_changed(const Raul::URI& predicate, - const Atom& value) +PropertiesWindow::change_property(const Raul::URI& key, const Atom& value) { - Records::iterator r = _records.find(predicate); + Records::iterator r = _records.find(key); if (r == _records.end()) { - add_property(predicate, value); + add_property(key, value); _table->show_all(); return; } - Record& record = r->second; - Gtk::Widget* value_widget = create_value_widget(predicate, value); + Record& record = r->second; + const char* type = _app->world()->uri_map().unmap_uri(value.type()); + Gtk::Widget* val_widget = create_value_widget(key, type, value); - record.value_widget->remove(); - if (value_widget) { - record.value_widget->add(*value_widget); - value_widget->show(); + if (val_widget) { + record.value_widget->remove(); + record.value_widget->add(*Gtk::manage(val_widget)); + val_widget->show_all(); } + record.value = value; } void -PropertiesWindow::property_removed(const Raul::URI& predicate, - const Atom& value) +PropertiesWindow::remove_property(const Raul::URI& key, const Atom& value) { // Bleh, there doesn't seem to be an easy way to remove a Gtk::Table row... _records.clear(); @@ -346,229 +369,135 @@ PropertiesWindow::property_removed(const Raul::URI& predicate, _table->show_all(); } -void -PropertiesWindow::value_edited(const Raul::URI& predicate) +Atom +PropertiesWindow::get_value(LV2_URID type, Gtk::Widget* value_widget) { - Records::iterator r = _records.find(predicate); - if (r == _records.end()) { - _app->log().error(fmt("Unknown property `%1%' edited\n") - % predicate); - return; - } + Forge& forge = _app->forge(); - Forge& forge = _app->forge(); - Record& record = r->second; - LV2_URID type = record.value.type(); if (type == forge.Int) { - Gtk::SpinButton* widget = dynamic_cast(record.value_widget->get_child()); - if (!widget) goto bad_type; - record.value = _app->forge().make(widget->get_value_as_int()); + Gtk::SpinButton* spin = dynamic_cast(value_widget); + if (spin) { + return _app->forge().make(spin->get_value_as_int()); + } } else if (type == forge.Float) { - Gtk::SpinButton* widget = dynamic_cast(record.value_widget->get_child()); - if (!widget) goto bad_type; - record.value = _app->forge().make(static_cast(widget->get_value())); + Gtk::SpinButton* spin = dynamic_cast(value_widget); + if (spin) { + return _app->forge().make(static_cast(spin->get_value())); + } } else if (type == forge.Bool) { - Gtk::CheckButton* widget = dynamic_cast(record.value_widget->get_child()); - if (!widget) goto bad_type; - record.value = _app->forge().make(widget->get_active()); + Gtk::CheckButton* check = dynamic_cast(value_widget); + if (check) { + return _app->forge().make(check->get_active()); + } } else if (type == forge.URI) { - Gtk::Entry* widget = dynamic_cast(record.value_widget->get_child()); - if (!widget) goto bad_type; - record.value = _app->forge().alloc_uri(widget->get_text()); + URIEntry* uri_entry = dynamic_cast(value_widget); + if (uri_entry) { + return _app->forge().alloc_uri(uri_entry->get_text()); + } + } else if (type == forge.URID) { + URIEntry* uri_entry = dynamic_cast(value_widget); + if (uri_entry) { + return _app->forge().make_urid( + _app->world()->uri_map().map_uri(uri_entry->get_text())); + } } else if (type == forge.String) { - Gtk::Entry* widget = dynamic_cast(record.value_widget->get_child()); - if (!widget) goto bad_type; - record.value = _app->forge().alloc(widget->get_text()); + Gtk::Entry* entry = dynamic_cast(value_widget); + if (entry) { + return _app->forge().alloc(entry->get_text()); + } } - return; - -bad_type: - _app->log().error(fmt("Property `%1%' value widget has wrong type\n") - % predicate); - return; -} - -std::string -PropertiesWindow::active_property() const -{ - const Gtk::ListStore::iterator iter = _key_combo->get_active(); - if (!iter) { - return ""; + URIEntry* uri_entry = dynamic_cast(value_widget); + if (uri_entry) { + return _app->forge().alloc_uri(uri_entry->get_text()); } - Glib::ustring prop_uri = (*iter)[_combo_columns.uri_col]; - return prop_uri; + return Atom(); } void -PropertiesWindow::key_changed() +PropertiesWindow::on_change(const Raul::URI& key) { - /* TODO: Clear value? Build new selector widget, once one for things other than - URIs actually exists. At the moment, clicking the menu button will - generate the appropriate menu anyway. */ -} - -void -PropertiesWindow::set_tooltip(Gtk::Widget* widget, const LilvNode* node) -{ - const Glib::ustring comment = RDFS::comment(_app->world(), node); - if (!comment.empty()) { - widget->set_tooltip_text(comment); + Records::iterator r = _records.find(key); + if (r == _records.end()) { + return; } -} -void -PropertiesWindow::add_class_menu_item(Gtk::Menu* menu, const LilvNode* klass) -{ - const Glib::ustring label = RDFS::label(_app->world(), klass); - Gtk::Menu* submenu = build_subclass_menu(klass); - - if (submenu) { - menu->items().push_back(Gtk::Menu_Helpers::MenuElem(label)); - Gtk::MenuItem* menu_item = &(menu->items().back()); - set_tooltip(menu_item, klass); - menu_item->set_submenu(*submenu); + Record& record = r->second; + const Atom value = get_value(record.value.type(), + record.value_widget->get_child()); + + if (value.is_valid()) { + record.value = value; } else { - menu->items().push_back( - Gtk::Menu_Helpers::MenuElem( - label, - sigc::bind(sigc::mem_fun(this, &PropertiesWindow::uri_chosen), - std::string(lilv_node_as_uri(klass))))); + _app->log().error(fmt("Failed to get `%1%' value from widget\n") % key); } - set_tooltip(&(menu->items().back()), klass); } -Gtk::Menu* -PropertiesWindow::build_subclass_menu(const LilvNode* klass) +std::string +PropertiesWindow::active_key() const { - World* world = _app->world(); - - LilvNode* rdfs_subClassOf = lilv_new_uri( - world->lilv_world(), LILV_NS_RDFS "subClassOf"); - LilvNodes* subclasses = lilv_world_find_nodes( - world->lilv_world(), NULL, rdfs_subClassOf, klass); - - if (lilv_nodes_size(subclasses) == 0) { - return NULL; - } - - const Glib::ustring label = RDFS::label(world, klass); - Gtk::Menu* menu = new Gtk::Menu(); - - // Add "header" item for choosing this class itself - menu->items().push_back( - Gtk::Menu_Helpers::MenuElem( - label, - sigc::bind(sigc::mem_fun(this, &PropertiesWindow::uri_chosen), - std::string(lilv_node_as_uri(klass))))); - menu->items().push_back(Gtk::Menu_Helpers::SeparatorElem()); - set_tooltip(&(menu->items().back()), klass); - - // Add an item (and maybe submenu) for each subclass - LILV_FOREACH(nodes, s, subclasses) { - add_class_menu_item(menu, lilv_nodes_get(subclasses, s)); + const Gtk::ListStore::iterator iter = _key_combo->get_active(); + if (!iter) { + return ""; } - lilv_nodes_free(subclasses); - return menu; -} - -void -PropertiesWindow::build_value_menu(Gtk::Menu* menu, const LilvNodes* ranges) -{ - World* world = _app->world(); - LilvWorld* lworld = world->lilv_world(); - - LilvNode* rdf_type = lilv_new_uri(lworld, LILV_NS_RDF "type"); - LilvNode* rdfs_Class = lilv_new_uri(lworld, LILV_NS_RDFS "Class"); - LilvNode* rdfs_subClassOf = lilv_new_uri(lworld, LILV_NS_RDFS "subClassOf"); - LILV_FOREACH(nodes, r, ranges) { - const LilvNode* klass = lilv_nodes_get(ranges, r); - if (!lilv_node_is_uri(klass)) { - continue; - } - const char* uri = lilv_node_as_string(klass); - - // Add items for instances of this class - RDFS::URISet ranges_uris; - ranges_uris.insert(Raul::URI(uri)); - RDFS::Objects values = RDFS::instances(world, ranges_uris); - for (const auto& v : values) { - const LilvNode* inst = lilv_new_uri(lworld, v.first.c_str()); - Glib::ustring label = RDFS::label(world, inst); - if (label.empty()) { - label = lilv_node_as_string(inst); - } - - if (lilv_world_ask(world->lilv_world(), inst, rdf_type, rdfs_Class)) { - if (!lilv_world_ask(lworld, inst, rdfs_subClassOf, NULL) || - lilv_world_ask(lworld, inst, rdfs_subClassOf, inst)) { - add_class_menu_item(menu, inst); - } - } else { - menu->items().push_back( - Gtk::Menu_Helpers::MenuElem( - label, - sigc::bind(sigc::mem_fun(this, &PropertiesWindow::uri_chosen), - std::string(lilv_node_as_uri(inst))))); - set_tooltip(&(menu->items().back()), inst); - } - } - } + Glib::ustring prop_uri = (*iter)[_combo_columns.uri_col]; + return prop_uri; } void -PropertiesWindow::uri_chosen(const std::string& uri) -{ - _value_entry->set_text(uri); -} - -bool -PropertiesWindow::value_clicked(GdkEvent* ev) +PropertiesWindow::key_changed() { - if (ev->type != GDK_BUTTON_PRESS) { - return false; + if (!_key_combo->get_active()) { + _value_bin->remove(); + return; } - // Get currently selected property (key) to add - const std::string prop_uri = active_property(); - if (prop_uri.empty()) { - return false; + LilvWorld* lworld = _app->world()->lilv_world(); + const Gtk::ListStore::Row key_row = *(_key_combo->get_active()); + const Glib::ustring key_uri = key_row[_combo_columns.uri_col]; + LilvNode* prop = lilv_new_uri(lworld, key_uri.c_str()); + + // Try to create a value widget in the range of this property + const URISet ranges = RDFS::range(_app->world(), prop, true); + for (const auto& r : ranges) { + Gtk::Widget* value_widget = create_value_widget( + Raul::URI(key_uri), r.c_str(), Atom()); + + if (value_widget) { + _add_button->set_sensitive(true); + _value_type = _app->world()->uri_map().map_uri(r); + _value_bin->remove(); + _value_bin->add(*Gtk::manage(value_widget)); + _value_bin->show_all(); + break; + } } - World* world = _app->world(); - - LilvNode* rdfs_range = lilv_new_uri( - world->lilv_world(), LILV_NS_RDFS "range"); - - LilvNode* prop = lilv_new_uri(world->lilv_world(), prop_uri.c_str()); - LilvNodes* ranges = lilv_world_find_nodes( - world->lilv_world(), prop, rdfs_range, NULL); - - Gtk::Menu* menu = new Gtk::Menu(); - build_value_menu(menu, ranges); - menu->popup(ev->button.button, ev->button.time); - return true; + lilv_node_free(prop); } void PropertiesWindow::add_clicked() { - if (!_key_combo->get_active() || _value_entry->get_text().empty()) { + if (!_key_combo->get_active() || !_value_type || !_value_bin->get_child()) { return; } - const Gtk::ListStore::Row krow = *(_key_combo->get_active()); - const Glib::ustring key_uri = krow[_combo_columns.uri_col]; - const Glib::ustring value_uri = _value_entry->get_text(); - - Atom value = _app->forge().alloc_uri(value_uri); - - Resource::Properties properties; - properties.insert(make_pair(Raul::URI(key_uri.c_str()), - Resource::Property(value))); - _app->interface()->put(_model->uri(), properties); + // Get selected key URI + const Gtk::ListStore::Row key_row = *(_key_combo->get_active()); + const Glib::ustring key_uri = key_row[_combo_columns.uri_col]; + + // Try to get value from value widget + const Atom& value = get_value(_value_type, _value_bin->get_child()); + if (value.is_valid()) { + // Send property to engine + Resource::Properties properties; + properties.insert(make_pair(Raul::URI(key_uri.c_str()), + Resource::Property(value))); + _app->interface()->put(_model->uri(), properties); + } } void diff --git a/src/gui/PropertiesWindow.hpp b/src/gui/PropertiesWindow.hpp index 7111269a..15842c48 100644 --- a/src/gui/PropertiesWindow.hpp +++ b/src/gui/PropertiesWindow.hpp @@ -76,28 +76,23 @@ private: Gtk::TreeModelColumn uri_col; }; - std::string active_property() const; + void add_property(const Raul::URI& key, const Atom& value); + void change_property(const Raul::URI& key, const Atom& value); + void remove_property(const Raul::URI& key, const Atom& value); + void on_change(const Raul::URI& key); - void add_property(const Raul::URI& uri, - const Atom& value); + Gtk::Widget* create_value_widget(const Raul::URI& key, + const char* type_uri, + const Atom& value = Atom()); - Gtk::Widget* create_value_widget(const Raul::URI& uri, - const Atom& value); - - Gtk::Menu* build_subclass_menu(const LilvNode* klass); - void add_class_menu_item(Gtk::Menu* menu, const LilvNode* klass); - void build_value_menu(Gtk::Menu* menu, const LilvNodes* ranges); - void set_tooltip(Gtk::Widget* widget, const LilvNode* node); + Atom get_value(LV2_URID type, Gtk::Widget* value_widget); void reset(); void on_show(); - void property_changed(const Raul::URI& predicate, const Atom& value); - void property_removed(const Raul::URI& predicate, const Atom& value); - void value_edited(const Raul::URI& predicate); + std::string active_key() const; + void key_changed(); - void uri_chosen(const std::string& uri); - bool value_clicked(GdkEvent* ev); void add_clicked(); void cancel_clicked(); void apply_clicked(); @@ -115,8 +110,8 @@ private: Gtk::ScrolledWindow* _scrolledwindow; Gtk::Table* _table; Gtk::ComboBox* _key_combo; - Gtk::Entry* _value_entry; - Gtk::Button* _value_button; + LV2_URID _value_type; + Gtk::Bin* _value_bin; Gtk::Button* _add_button; Gtk::Button* _cancel_button; Gtk::Button* _apply_button; diff --git a/src/gui/RDFS.cpp b/src/gui/RDFS.cpp index a24ae002..458c0bc5 100644 --- a/src/gui/RDFS.cpp +++ b/src/gui/RDFS.cpp @@ -25,7 +25,7 @@ namespace Ingen { namespace GUI { namespace RDFS { -Glib::ustring +std::string label(World* world, const LilvNode* node) { LilvNode* rdfs_label = lilv_new_uri( @@ -34,14 +34,14 @@ label(World* world, const LilvNode* node) world->lilv_world(), node, rdfs_label, NULL); const LilvNode* first = lilv_nodes_get_first(labels); - Glib::ustring label = first ? lilv_node_as_string(first) : ""; + std::string label = first ? lilv_node_as_string(first) : ""; lilv_nodes_free(labels); lilv_node_free(rdfs_label); return label; } -Glib::ustring +std::string comment(World* world, const LilvNode* node) { LilvNode* rdfs_comment = lilv_new_uri( @@ -49,8 +49,8 @@ comment(World* world, const LilvNode* node) LilvNodes* comments = lilv_world_find_nodes( world->lilv_world(), node, rdfs_comment, NULL); - const LilvNode* first = lilv_nodes_get_first(comments); - Glib::ustring comment = first ? lilv_node_as_string(first) : ""; + const LilvNode* first = lilv_nodes_get_first(comments); + std::string comment = first ? lilv_node_as_string(first) : ""; lilv_nodes_free(comments); lilv_node_free(rdfs_comment); @@ -178,11 +178,10 @@ instances(World* world, const URISet& types) if (!lilv_node_is_uri(object)) { continue; } - const Glib::ustring label = RDFS::label(world, object); + const std::string label = RDFS::label(world, object); result.insert( - std::make_pair( - Raul::URI(lilv_node_as_string(object)), - label)); + std::make_pair(label, + Raul::URI(lilv_node_as_string(object)))); } lilv_node_free(type); } @@ -191,6 +190,41 @@ instances(World* world, const URISet& types) return result; } +URISet +range(World* world, const LilvNode* prop, bool recursive) +{ + LilvNode* rdfs_range = lilv_new_uri( + world->lilv_world(), LILV_NS_RDFS "range"); + + LilvNodes* nodes = lilv_world_find_nodes( + world->lilv_world(), prop, rdfs_range, NULL); + + URISet ranges; + LILV_FOREACH(nodes, n, nodes) { + ranges.insert(Raul::URI(lilv_node_as_string(lilv_nodes_get(nodes, n)))); + } + + if (recursive) { + RDFS::classes(world, ranges, false); + } + + lilv_nodes_free(nodes); + lilv_node_free(rdfs_range); + return ranges; +} + +bool +is_a(World* world, const LilvNode* inst, const LilvNode* klass) +{ + LilvNode* rdf_type = lilv_new_uri(world->lilv_world(), LILV_NS_RDF "type"); + + const bool is_instance = lilv_world_ask( + world->lilv_world(), inst, rdf_type, klass); + + lilv_node_free(rdf_type); + return is_instance; +} + } // namespace RDFS } // namespace GUI } // namespace Ingen diff --git a/src/gui/RDFS.hpp b/src/gui/RDFS.hpp index d9506dbc..52931aaf 100644 --- a/src/gui/RDFS.hpp +++ b/src/gui/RDFS.hpp @@ -19,10 +19,10 @@ #include #include - -#include +#include #include "ingen/types.hpp" +#include "lilv/lilv.h" #include "raul/URI.hpp" namespace Ingen { @@ -38,16 +38,14 @@ namespace RDFS { /** Set of URIs. */ typedef std::set URISet; -/** Map of object labels, keyed by object URI. */ -typedef std::map Objects; +/** Label => Resource map. */ +typedef std::map Objects; /** Return the label of `node`. */ -Glib::ustring -label(World* world, const LilvNode* node); +std::string label(World* world, const LilvNode* node); /** Return the comment of `node`. */ -Glib::ustring -comment(World* world, const LilvNode* node); +std::string comment(World* world, const LilvNode* node); /** Set `types` to its super/sub class closure. * @param super If true, find all superclasses, otherwise all subclasses @@ -55,8 +53,7 @@ comment(World* world, const LilvNode* node); void classes(World* world, URISet& types, bool super); /** Get all instances of any class in `types`. */ -Objects -instances(World* world, const URISet& types); +Objects instances(World* world, const URISet& types); /** Get all the types which `model` is an instance of. */ URISet types(World* world, SPtr model); @@ -64,6 +61,14 @@ URISet types(World* world, SPtr model); /** Get all the properties with domains appropriate for `model`. */ URISet properties(World* world, SPtr model); +/** Return the range (value types) of `prop`. + * @param recursive If true, include all subclasses. + */ +URISet range(World* world, const LilvNode* prop, bool recursive); + +/** Return true iff `inst` is-a `klass`. */ +bool is_a(World* world, const LilvNode* inst, const LilvNode* klass); + } // namespace RDFS } // namespace GUI } // namespace Ingen diff --git a/src/gui/URIEntry.cpp b/src/gui/URIEntry.cpp new file mode 100644 index 00000000..7cd15cd8 --- /dev/null +++ b/src/gui/URIEntry.cpp @@ -0,0 +1,172 @@ +/* + This file is part of Ingen. + Copyright 2015 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 "App.hpp" +#include "RDFS.hpp" +#include "URIEntry.hpp" + +namespace Ingen { +namespace GUI { + +URIEntry::URIEntry(App* app, + const std::set& types, + const std::string& value) + : Gtk::HBox(false, 4) + , _app(app) + , _types(types) + , _menu_button(Gtk::manage(new Gtk::Button("≡"))) + , _entry(Gtk::manage(new Gtk::Entry())) +{ + pack_start(*_entry, true, true); + pack_start(*_menu_button, false, true); + + _entry->set_text(value); + + _menu_button->signal_event().connect( + sigc::mem_fun(this, &URIEntry::menu_button_event)); +} + +Gtk::Menu* +URIEntry::build_value_menu() +{ + World* world = _app->world(); + LilvWorld* lworld = world->lilv_world(); + Gtk::Menu* menu = new Gtk::Menu(); + + LilvNode* rdf_type = lilv_new_uri(lworld, LILV_NS_RDF "type"); + LilvNode* rdfs_Class = lilv_new_uri(lworld, LILV_NS_RDFS "Class"); + LilvNode* rdfs_subClassOf = lilv_new_uri(lworld, LILV_NS_RDFS "subClassOf"); + + RDFS::Objects values = RDFS::instances(world, _types); + + for (const auto& v : values) { + const LilvNode* inst = lilv_new_uri(lworld, v.second.c_str()); + std::string label = v.first; + if (label.empty()) { + // No label, show raw URI + label = lilv_node_as_string(inst); + } + + if (lilv_world_ask(world->lilv_world(), inst, rdf_type, rdfs_Class)) { + // This value is a class... + if (!lilv_world_ask(lworld, inst, rdfs_subClassOf, NULL)) { + // ... which is not a subclass, add menu + add_class_menu_item(menu, inst, label); + } + } else { + // Value is not a class, add item + menu->items().push_back( + Gtk::Menu_Helpers::MenuElem( + label, + sigc::bind(sigc::mem_fun(this, &URIEntry::uri_chosen), + std::string(lilv_node_as_uri(inst))))); + _app->set_tooltip(&menu->items().back(), inst); + } + } + + return menu; +} + +Gtk::Menu* +URIEntry::build_subclass_menu(const LilvNode* klass) +{ + World* world = _app->world(); + + LilvNode* rdfs_subClassOf = lilv_new_uri( + world->lilv_world(), LILV_NS_RDFS "subClassOf"); + LilvNodes* subclasses = lilv_world_find_nodes( + world->lilv_world(), NULL, rdfs_subClassOf, klass); + + if (lilv_nodes_size(subclasses) == 0) { + return NULL; + } + + Gtk::Menu* menu = new Gtk::Menu(); + + // Add "header" item for choosing this class itself + add_leaf_menu_item(menu, klass, RDFS::label(world, klass)); + menu->items().push_back(Gtk::Menu_Helpers::SeparatorElem()); + + // Put subclasses in a map keyed by label (to sort menu) + std::map entries; + LILV_FOREACH(nodes, s, subclasses) { + const LilvNode* node = lilv_nodes_get(subclasses, s); + entries.insert(std::make_pair(RDFS::label(world, node), node)); + } + + // Add an item (possibly with a submenu) for each subclass + for (const auto& e : entries) { + add_class_menu_item(menu, e.second, e.first); + } + lilv_nodes_free(subclasses); + + return menu; +} + +void +URIEntry::add_leaf_menu_item(Gtk::Menu* menu, + const LilvNode* node, + const std::string& label) +{ + menu->items().push_back( + Gtk::Menu_Helpers::MenuElem( + label, + sigc::bind(sigc::mem_fun(this, &URIEntry::uri_chosen), + std::string(lilv_node_as_uri(node))))); + + _app->set_tooltip(&menu->items().back(), node); +} + +void +URIEntry::add_class_menu_item(Gtk::Menu* menu, + const LilvNode* klass, + const std::string& label) +{ + Gtk::Menu* submenu = build_subclass_menu(klass); + + if (submenu) { + menu->items().push_back(Gtk::Menu_Helpers::MenuElem(label)); + menu->items().back().set_submenu(*Gtk::manage(submenu)); + } else { + add_leaf_menu_item(menu, klass, label); + } + + _app->set_tooltip(&menu->items().back(), klass); +} + +void +URIEntry::uri_chosen(const std::string& uri) +{ + _entry->set_text(uri); +} + +bool +URIEntry::menu_button_event(GdkEvent* ev) +{ + if (ev->type != GDK_BUTTON_PRESS) { + return false; + } + + Gtk::Menu* menu = Gtk::manage(build_value_menu()); + menu->popup(ev->button.button, ev->button.time); + + return true; +} + +} // namespace GUI +} // namespace Ingen diff --git a/src/gui/URIEntry.hpp b/src/gui/URIEntry.hpp new file mode 100644 index 00000000..d8f830d0 --- /dev/null +++ b/src/gui/URIEntry.hpp @@ -0,0 +1,70 @@ +/* + This file is part of Ingen. + Copyright 2015 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 . +*/ + +#ifndef INGEN_GUI_URI_ENTRY_HPP +#define INGEN_GUI_URI_ENTRY_HPP + +#include +#include +#include +#include + +#include "lilv/lilv.h" + +namespace Ingen { +namespace GUI { + +class App; + +class URIEntry : public Gtk::HBox { +public: + /** Create a widget for entering URIs. + * + * If `types` is given, then a menu button will be shown which pops up a + * enu for easily choosing known values with valid types. + */ + URIEntry(App* app, + const std::set& types, + const std::string& value); + + std::string get_text() { return _entry->get_text(); } + Glib::SignalProxy0 signal_changed() { return _entry->signal_changed(); } + +private: + Gtk::Menu* build_value_menu(); + Gtk::Menu* build_subclass_menu(const LilvNode* klass); + + void add_leaf_menu_item(Gtk::Menu* menu, + const LilvNode* klass, + const std::string& label); + + void add_class_menu_item(Gtk::Menu* menu, + const LilvNode* klass, + const std::string& label); + + void uri_chosen(const std::string& uri); + bool menu_button_event(GdkEvent* ev); + + App* _app; + const std::set _types; + Gtk::Button* _menu_button; + Gtk::Entry* _entry; +}; + +} // namespace GUI +} // namespace Ingen + +#endif // INGEN_GUI_URI_ENTRY_HPP diff --git a/src/gui/ingen_gui.ui b/src/gui/ingen_gui.ui index 8fa4f35e..9399300b 100644 --- a/src/gui/ingen_gui.ui +++ b/src/gui/ingen_gui.ui @@ -2430,35 +2430,14 @@ See COPYING file included with this distribution, or http://www.gnu.org/licenses - - - False - True - True - True - True - - - False - True - 1 - - - - + True - True - - True - False - False - True - True + False True True - 2 + 1 @@ -2466,6 +2445,7 @@ See COPYING file included with this distribution, or http://www.gnu.org/licenses gtk-add False True + False True True True @@ -2473,7 +2453,7 @@ See COPYING file included with this distribution, or http://www.gnu.org/licenses False True - 3 + 2 diff --git a/src/gui/wscript b/src/gui/wscript index 203d7570..1beba44b 100644 --- a/src/gui/wscript +++ b/src/gui/wscript @@ -74,6 +74,7 @@ def build(bld): Style.cpp SubgraphModule.cpp ThreadedLoader.cpp + URIEntry.cpp WidgetFactory.cpp WindowFactory.cpp ingen_gui.cpp -- cgit v1.2.1