diff options
author | David Robillard <d@drobilla.net> | 2012-07-29 03:21:46 +0000 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2012-07-29 03:21:46 +0000 |
commit | 67531458e321e130b46a60557536eebe8ba91eea (patch) | |
tree | 086648d30e98a617826dc78a37149a7594c5bc36 /src/gui | |
parent | 445604bff1dafe415014035ffa8f704c9538b4ed (diff) | |
download | ingen-67531458e321e130b46a60557536eebe8ba91eea.tar.gz ingen-67531458e321e130b46a60557536eebe8ba91eea.tar.bz2 ingen-67531458e321e130b46a60557536eebe8ba91eea.zip |
Add UI for adding arbitrary object properties to properties dialog.
Property information is loaded from installed LV2 data, so any appropriate
properties will automatically be shown. Currently only implemented for object
properties where the LV2 world contains appropriate values, needs extending for
datatype properties.
At the moment, only "unit" and "port property" show up for ports.
git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@4566 a436a847-0d15-0410-975c-d299462d15a1
Diffstat (limited to 'src/gui')
-rw-r--r-- | src/gui/PropertiesWindow.cpp | 294 | ||||
-rw-r--r-- | src/gui/PropertiesWindow.hpp | 24 | ||||
-rw-r--r-- | src/gui/ingen_gui.ui | 51 |
3 files changed, 342 insertions, 27 deletions
diff --git a/src/gui/PropertiesWindow.cpp b/src/gui/PropertiesWindow.cpp index 4c451978..6f92d0d3 100644 --- a/src/gui/PropertiesWindow.cpp +++ b/src/gui/PropertiesWindow.cpp @@ -16,6 +16,7 @@ #include <algorithm> #include <cassert> +#include <set> #include <string> #include <gtkmm/label.h> @@ -40,6 +41,8 @@ using namespace Shared; namespace GUI { +typedef std::set<Raul::URI> URISet; + PropertiesWindow::PropertiesWindow(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& xml) : Window(cobject) @@ -47,18 +50,35 @@ PropertiesWindow::PropertiesWindow(BaseObjectType* cobject, 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_combo", _value_combo); + xml->get_widget("properties_add_button", _add_button); xml->get_widget("properties_cancel_button", _cancel_button); xml->get_widget("properties_apply_button", _apply_button); xml->get_widget("properties_ok_button", _ok_button); + _key_store = Gtk::ListStore::create(_combo_columns); + _key_combo->set_model(_key_store); + _key_combo->pack_start(_combo_columns.label_col); + + _value_store = Gtk::ListStore::create(_combo_columns); + _value_combo->set_model(_value_store); + _value_combo->pack_start(_combo_columns.label_col); + + _key_combo->signal_changed().connect( + sigc::mem_fun(this, &PropertiesWindow::key_changed)); + + _add_button->signal_clicked().connect( + sigc::mem_fun(this, &PropertiesWindow::add_clicked)); + _cancel_button->signal_clicked().connect( - sigc::mem_fun(this, &PropertiesWindow::cancel_clicked)); + sigc::mem_fun(this, &PropertiesWindow::cancel_clicked)); _apply_button->signal_clicked().connect( - sigc::mem_fun(this, &PropertiesWindow::apply_clicked)); + sigc::mem_fun(this, &PropertiesWindow::apply_clicked)); _ok_button->signal_clicked().connect( - sigc::mem_fun(this, &PropertiesWindow::ok_clicked)); + sigc::mem_fun(this, &PropertiesWindow::ok_clicked)); } void @@ -69,6 +89,8 @@ PropertiesWindow::reset() _table->property_n_rows() = 1; _records.clear(); + _key_store->clear(); + _value_store->clear(); _property_connection.disconnect(); _model.reset(); @@ -81,6 +103,148 @@ PropertiesWindow::present(SharedPtr<const ObjectModel> model) Gtk::Window::present(); } +/** Get all the types which this model is an instance of */ +static URISet +get_types(Shared::World* world, SharedPtr<const ObjectModel> model) +{ + typedef Resource::Properties::const_iterator PropIter; + typedef std::pair<PropIter, PropIter> PropRange; + + // Start with every rdf:type + URISet types; + PropRange range = model->properties().equal_range(world->uris().rdf_type); + for (PropIter t = range.first; t != range.second; ++t) { + types.insert(t->second.get_uri()); + } + + LilvNode* rdfs_subClassOf = lilv_new_uri( + world->lilv_world(), LILV_NS_RDFS "subClassOf"); + + /* Add super-classes until no new super-classes are encountered. Really + slow, but won't loop forever even if subClassOf loops exist. */ + unsigned added = 0; + do { + added = 0; + URISet supers; + for (URISet::iterator t = types.begin(); t != types.end(); ++t) { + LilvNode* type = lilv_new_uri(world->lilv_world(), t->c_str()); + LilvNodes* sups = lilv_world_find_nodes( + world->lilv_world(), type, rdfs_subClassOf, NULL); + LILV_FOREACH(nodes, s, sups) { + const LilvNode* super_node = lilv_nodes_get(sups, s); + if (lilv_node_is_uri(super_node)) { + Raul::URI super = lilv_node_as_uri(super_node); + if (!types.count(super)) { + ++added; + supers.insert(super); + } + } + } + lilv_nodes_free(sups); + lilv_node_free(type); + } + types.insert(supers.begin(), supers.end()); + } while (added > 0); + + lilv_node_free(rdfs_subClassOf); + return types; +} + +/** Get all the properties with domains appropriate to this model */ +static URISet +get_properties(Shared::World* world, SharedPtr<const ObjectModel> model) +{ + URISet properties; + URISet types = get_types(world, model); + + LilvNode* rdf_type = lilv_new_uri(world->lilv_world(), + LILV_NS_RDF "type"); + LilvNode* rdf_Property = lilv_new_uri(world->lilv_world(), + LILV_NS_RDF "Property"); + LilvNode* rdfs_domain = lilv_new_uri(world->lilv_world(), + LILV_NS_RDFS "domain"); + + LilvNodes* props = lilv_world_find_nodes( + world->lilv_world(), NULL, rdf_type, rdf_Property); + LILV_FOREACH(nodes, p, props) { + const LilvNode* prop = lilv_nodes_get(props, p); + if (lilv_node_is_uri(prop)) { + LilvNodes* domains = lilv_world_find_nodes( + world->lilv_world(), prop, rdfs_domain, NULL); + unsigned n_matching_domains = 0; + LILV_FOREACH(nodes, d, domains) { + const LilvNode* domain_node = lilv_nodes_get(domains, d); + const Raul::URI domain(lilv_node_as_uri(domain_node)); + if (types.count(domain)) { + ++n_matching_domains; + } + } + + if (n_matching_domains > 0 && + n_matching_domains == lilv_nodes_size(domains)) { + properties.insert(lilv_node_as_uri(prop)); + } + + lilv_nodes_free(domains); + } + } + + lilv_node_free(rdfs_domain); + lilv_node_free(rdf_Property); + lilv_node_free(rdf_type); + + return properties; +} + +static Glib::ustring +get_label(Shared::World* world, const LilvNode* node) +{ + LilvNode* rdfs_label = lilv_new_uri( + world->lilv_world(), LILV_NS_RDFS "label"); + LilvNodes* labels = lilv_world_find_nodes( + world->lilv_world(), node, rdfs_label, NULL); + + const LilvNode* first = lilv_nodes_get_first(labels); + Glib::ustring label = first ? lilv_node_as_string(first) : ""; + + lilv_nodes_free(labels); + lilv_node_free(rdfs_label); + return label; +} + +void +PropertiesWindow::add_property(const Raul::URI& uri, const Raul::Atom& value) +{ + Shared::World* world = _app->world(); + + const unsigned n_rows = _table->property_n_rows() + 1; + _table->property_n_rows() = n_rows; + + // Column 0: Property + LilvNode* prop = lilv_new_uri(world->lilv_world(), uri.c_str()); + Glib::ustring lab_text = get_label(world, prop); + if (lab_text.empty()) { + lab_text = world->rdf_world()->prefixes().qualify(uri.str()); + } + lab_text = Glib::ustring("<a href=\"") + uri.str() + "\">" + + lab_text + "</a>"; + Gtk::Label* lab = manage(new Gtk::Label(lab_text, 1.0, 0.5)); + lab->set_use_markup(true); + _table->attach(*lab, 0, 1, n_rows, n_rows + 1, + Gtk::FILL|Gtk::SHRINK, Gtk::SHRINK); + lilv_node_free(prop); + + // Column 1: Value + Gtk::Alignment* align = manage(new Gtk::Alignment(0.0, 0.5, 1.0, 0.0)); + Gtk::Widget* val_widget = create_value_widget(uri, value); + if (val_widget) { + align->add(*val_widget); + } + _table->attach(*align, 1, 2, n_rows, n_rows + 1, + Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK); + _records.insert(make_pair(uri, Record(value, align, n_rows))); +} + /** Set the node this window is associated with. * This function MUST be called before using this object in any way. */ @@ -94,29 +258,45 @@ PropertiesWindow::set_object(SharedPtr<const ObjectModel> model) Shared::World* world = _app->world(); - ostringstream ss; - unsigned n_rows = 0; + 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 = get_properties(world, model); + for (URISet::const_iterator p = props.begin(); p != props.end(); ++p) { + LilvNode* prop = lilv_new_uri(world->lilv_world(), p->c_str()); + const Glib::ustring label = get_label(world, prop); + if (label.empty()) { + continue; + } + + LilvNodes* ranges = lilv_world_find_nodes( + world->lilv_world(), prop, rdfs_range, NULL); + LILV_FOREACH(nodes, r, ranges) { + const LilvNode* range = lilv_nodes_get(ranges, r); + LilvNodes* objects = lilv_world_find_nodes( + world->lilv_world(), NULL, rdf_type, range); + if (lilv_nodes_get_first(objects)) { + // At least one applicable object, add property to key combo + Gtk::ListStore::iterator ki = _key_store->append(); + Gtk::ListStore::Row row = *ki; + row[_combo_columns.uri_col] = p->str(); + row[_combo_columns.label_col] = label; + break; + } + lilv_nodes_free(objects); + } + lilv_node_free(prop); + } + + lilv_node_free(rdfs_range); + lilv_node_free(rdf_type); + for (ObjectModel::Properties::const_iterator i = model->properties().begin(); i != model->properties().end(); ++i) { - _table->property_n_rows() = ++n_rows; - - const Raul::Atom& value = i->second; - - // Column 0: Property - Gtk::Label* lab = manage( - new Gtk::Label(world->rdf_world()->prefixes().qualify(i->first.str()), - 0.0, 0.5)); - _table->attach(*lab, 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::Widget* value_widget = create_value_widget(i->first, value); - if (value_widget) - align->add(*value_widget); - _table->attach(*align, 1, 2, n_rows, n_rows + 1, - Gtk::FILL|Gtk::EXPAND, Gtk::SHRINK); - _records.insert(make_pair(i->first, Record(value, align, n_rows))); + add_property(i->first, i->second); } _table->show_all(); @@ -209,7 +389,8 @@ PropertiesWindow::property_changed(const Raul::URI& predicate, const Raul::Atom& { Records::iterator r = _records.find(predicate); if (r == _records.end()) { - LOG(error) << "Unknown property `" << predicate << "' changed" << endl; + add_property(predicate, value); + _table->show_all(); return; } @@ -266,6 +447,69 @@ bad_type: } void +PropertiesWindow::key_changed() +{ + Shared::World* world = _app->world(); + + const Gtk::ListStore::Row row = *(_key_combo->get_active()); + Glib::ustring prop_uri = row[_combo_columns.uri_col]; + + if (prop_uri.empty()) { + return; + } + + _value_store->clear(); + + 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"); + 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); + LILV_FOREACH(nodes, r, ranges) { + const LilvNode* range = lilv_nodes_get(ranges, r); + LilvNodes* objects = lilv_world_find_nodes( + world->lilv_world(), NULL, rdf_type, range); + LILV_FOREACH(nodes, o, objects) { + const LilvNode* object = lilv_nodes_get(objects, o); + const Glib::ustring label = get_label(world, object); + if (!label.empty()) { + Gtk::ListStore::iterator vi = _value_store->append(); + Gtk::ListStore::Row vrow = *vi; + vrow[_combo_columns.uri_col] = lilv_node_as_string(object); + vrow[_combo_columns.label_col] = label; + } + } + } + + lilv_node_free(prop); + lilv_node_free(rdf_type); + lilv_node_free(rdfs_range); +} + +void +PropertiesWindow::add_clicked() +{ + if (!_key_combo->get_active() || !_value_combo->get_active()) { + return; + } + + const Gtk::ListStore::Row krow = *(_key_combo->get_active()); + const Gtk::ListStore::Row vrow = *(_value_combo->get_active()); + Glib::ustring key_uri = krow[_combo_columns.uri_col]; + Glib::ustring value_uri = vrow[_combo_columns.uri_col]; + + Raul::Atom value = _app->forge().alloc_uri(value_uri); + + Resource::Properties properties; + properties.insert(make_pair(key_uri.c_str(), value)); + _app->interface()->put(_model->path(), properties); +} + +void PropertiesWindow::cancel_clicked() { reset(); diff --git a/src/gui/PropertiesWindow.hpp b/src/gui/PropertiesWindow.hpp index 31fd0388..e62fb07f 100644 --- a/src/gui/PropertiesWindow.hpp +++ b/src/gui/PropertiesWindow.hpp @@ -23,6 +23,8 @@ #include <gtkmm/box.h> #include <gtkmm/builder.h> #include <gtkmm/button.h> +#include <gtkmm/liststore.h> +#include <gtkmm/combobox.h> #include <gtkmm/scrolledwindow.h> #include <gtkmm/table.h> @@ -64,6 +66,18 @@ private: int row; }; + struct ComboColumns : public Gtk::TreeModel::ColumnRecord { + ComboColumns() { + add(label_col); + add(uri_col); + } + Gtk::TreeModelColumn<Glib::ustring> label_col; + Gtk::TreeModelColumn<Glib::ustring> uri_col; + }; + + void add_property(const Raul::URI& uri, + const Raul::Atom& value); + Gtk::Widget* create_value_widget(const Raul::URI& uri, const Raul::Atom& value); @@ -72,9 +86,9 @@ private: void on_show(); void property_changed(const Raul::URI& predicate, const Raul::Atom& value); - void value_edited(const Raul::URI& predicate); - + void key_changed(); + void add_clicked(); void cancel_clicked(); void apply_clicked(); void ok_clicked(); @@ -83,10 +97,16 @@ private: Records _records; SharedPtr<const Client::ObjectModel> _model; + ComboColumns _combo_columns; + Glib::RefPtr<Gtk::ListStore> _key_store; + Glib::RefPtr<Gtk::ListStore> _value_store; sigc::connection _property_connection; Gtk::VBox* _vbox; Gtk::ScrolledWindow* _scrolledwindow; Gtk::Table* _table; + Gtk::ComboBox* _key_combo; + Gtk::ComboBox* _value_combo; + Gtk::Button* _add_button; Gtk::Button* _cancel_button; Gtk::Button* _apply_button; Gtk::Button* _ok_button; diff --git a/src/gui/ingen_gui.ui b/src/gui/ingen_gui.ui index 40db0323..ed393082 100644 --- a/src/gui/ingen_gui.ui +++ b/src/gui/ingen_gui.ui @@ -2195,6 +2195,8 @@ Contributors: <object class="GtkScrolledWindow" id="properties_scrolledwindow"> <property name="visible">True</property> <property name="can_focus">True</property> + <property name="hscrollbar_policy">automatic</property> + <property name="vscrollbar_policy">automatic</property> <child> <object class="GtkViewport" id="viewport2"> <property name="visible">True</property> @@ -2229,6 +2231,55 @@ Contributors: </packing> </child> <child> + <object class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkComboBox" id="properties_key_combo"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="properties_value_combo"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="properties_add_button"> + <property name="label">gtk-add</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> <object class="GtkHButtonBox" id="properties_buttonbox"> <property name="visible">True</property> <property name="can_focus">False</property> |