summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/gui/PropertiesWindow.cpp294
-rw-r--r--src/gui/PropertiesWindow.hpp24
-rw-r--r--src/gui/ingen_gui.ui51
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>