summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2015-03-28 01:35:27 +0000
committerDavid Robillard <d@drobilla.net>2015-03-28 01:35:27 +0000
commitf4d8b438532b3c090094b4060d6e0a2b2fba9d4d (patch)
tree40339fff10921ad264fff3ace8a04c4b52d7c505
parentac60550c32598ad91f941722913f1629d5c9a35f (diff)
downloadingen-f4d8b438532b3c090094b4060d6e0a2b2fba9d4d.tar.gz
ingen-f4d8b438532b3c090094b4060d6e0a2b2fba9d4d.tar.bz2
ingen-f4d8b438532b3c090094b4060d6e0a2b2fba9d4d.zip
Improve properties window.
git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@5646 a436a847-0d15-0410-975c-d299462d15a1
-rw-r--r--src/gui/PropertiesWindow.cpp202
-rw-r--r--src/gui/PropertiesWindow.hpp13
-rw-r--r--src/gui/RDFS.cpp23
-rw-r--r--src/gui/RDFS.hpp4
-rw-r--r--src/gui/ingen_gui.ui31
5 files changed, 220 insertions, 53 deletions
diff --git a/src/gui/PropertiesWindow.cpp b/src/gui/PropertiesWindow.cpp
index 64dea4d1..1eb92b10 100644
--- a/src/gui/PropertiesWindow.cpp
+++ b/src/gui/PropertiesWindow.cpp
@@ -50,7 +50,8 @@ PropertiesWindow::PropertiesWindow(BaseObjectType* cobject,
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_value_entry", _value_entry);
+ xml->get_widget("properties_value_button", _value_button);
xml->get_widget("properties_add_button", _add_button);
xml->get_widget("properties_cancel_button", _cancel_button);
xml->get_widget("properties_apply_button", _apply_button);
@@ -60,13 +61,12 @@ PropertiesWindow::PropertiesWindow(BaseObjectType* cobject,
_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));
+ _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 +87,7 @@ PropertiesWindow::reset()
_property_removed_connection.disconnect();
_key_store->clear();
- _value_store->clear();
+ _value_entry->set_text("");
_records.clear();
_model.reset();
@@ -113,7 +113,7 @@ 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());
+ 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);
@@ -122,9 +122,9 @@ PropertiesWindow::add_property(const Raul::URI& uri, const Atom& value)
+ lab_text + "</a>";
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::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));
@@ -133,12 +133,15 @@ PropertiesWindow::add_property(const Raul::URI& uri, const Atom& value)
present->set_active(true);
if (val_widget) {
align->add(*val_widget);
+ 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)));
+
+ lilv_node_free(prop);
}
/** Set the node this window is associated with.
@@ -297,7 +300,7 @@ PropertiesWindow::on_show()
}
req = _table->size_request();
- width = std::max(width, req.width + 128);
+ width = 1.6 * std::max(width, req.width + 128);
height += req.height;
set_default_size(width + WIN_PAD, height + WIN_PAD);
@@ -386,68 +389,179 @@ bad_type:
return;
}
+std::string
+PropertiesWindow::active_property() const
+{
+ const Gtk::ListStore::iterator iter = _key_combo->get_active();
+ if (!iter) {
+ return "";
+ }
+
+ Glib::ustring prop_uri = (*iter)[_combo_columns.uri_col];
+ return prop_uri;
+}
+
void
PropertiesWindow::key_changed()
{
+ /* 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);
+ }
+}
+
+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);
+ } 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)))));
+ }
+ set_tooltip(&(menu->items().back()), klass);
+}
+
+Gtk::Menu*
+PropertiesWindow::build_subclass_menu(const LilvNode* klass)
+{
World* world = _app->world();
- _value_store->clear();
+ 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);
- const Gtk::ListStore::iterator iter = _key_combo->get_active();
- if (!iter) {
- return;
+ if (lilv_nodes_size(subclasses) == 0) {
+ return NULL;
}
- const Gtk::ListStore::Row row = *iter;
- Glib::ustring prop_uri = row[_combo_columns.uri_col];
- if (prop_uri.empty()) {
- return;
+ 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));
}
+ lilv_nodes_free(subclasses);
+ return menu;
+}
- 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());
+void
+PropertiesWindow::build_value_menu(Gtk::Menu* menu, const LilvNodes* ranges)
+{
+ World* world = _app->world();
+ LilvWorld* lworld = world->lilv_world();
- LilvNodes* range = lilv_world_find_nodes(
- world->lilv_world(), prop, rdfs_range, NULL);
+ 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");
- // Get all classes in the range of this property (including sub-classes)
- URISet ranges;
- LILV_FOREACH(nodes, r, range) {
- ranges.insert(Raul::URI(lilv_node_as_string(lilv_nodes_get(range, r))));
+ 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);
+ }
+ }
}
- RDFS::classes(world, ranges, false);
+}
- // Get all objects in range
- RDFS::Objects values = RDFS::instances(world, ranges);
+void
+PropertiesWindow::uri_chosen(const std::string& uri)
+{
+ _value_entry->set_text(uri);
+}
- // Fill value selector with suitable objects
- for (const auto& v : values) {
- if (!v.second.empty()) {
- Gtk::ListStore::iterator vi = _value_store->append();
- Gtk::ListStore::Row vrow = *vi;
+bool
+PropertiesWindow::value_clicked(GdkEvent* ev)
+{
+ if (ev->type != GDK_BUTTON_PRESS) {
+ return false;
+ }
- vrow[_combo_columns.uri_col] = v.first;
- vrow[_combo_columns.label_col] = v.second;
- }
+ // Get currently selected property (key) to add
+ const std::string prop_uri = active_property();
+ if (prop_uri.empty()) {
+ return false;
}
- lilv_node_free(prop);
- lilv_node_free(rdfs_range);
+ 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;
}
void
PropertiesWindow::add_clicked()
{
- if (!_key_combo->get_active() || !_value_combo->get_active()) {
+ if (!_key_combo->get_active() || _value_entry->get_text().empty()) {
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];
+ 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);
diff --git a/src/gui/PropertiesWindow.hpp b/src/gui/PropertiesWindow.hpp
index 025c98ae..7111269a 100644
--- a/src/gui/PropertiesWindow.hpp
+++ b/src/gui/PropertiesWindow.hpp
@@ -76,12 +76,19 @@ private:
Gtk::TreeModelColumn<Glib::ustring> uri_col;
};
+ std::string active_property() const;
+
void add_property(const Raul::URI& uri,
const Atom& value);
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);
+
void reset();
void on_show();
@@ -89,6 +96,8 @@ private:
void property_removed(const Raul::URI& predicate, const Atom& value);
void value_edited(const Raul::URI& predicate);
void key_changed();
+ void uri_chosen(const std::string& uri);
+ bool value_clicked(GdkEvent* ev);
void add_clicked();
void cancel_clicked();
void apply_clicked();
@@ -100,14 +109,14 @@ private:
SPtr<const Client::ObjectModel> _model;
ComboColumns _combo_columns;
Glib::RefPtr<Gtk::ListStore> _key_store;
- Glib::RefPtr<Gtk::ListStore> _value_store;
sigc::connection _property_connection;
sigc::connection _property_removed_connection;
Gtk::VBox* _vbox;
Gtk::ScrolledWindow* _scrolledwindow;
Gtk::Table* _table;
Gtk::ComboBox* _key_combo;
- Gtk::ComboBox* _value_combo;
+ Gtk::Entry* _value_entry;
+ Gtk::Button* _value_button;
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 671ef69e..a24ae002 100644
--- a/src/gui/RDFS.cpp
+++ b/src/gui/RDFS.cpp
@@ -41,6 +41,22 @@ label(World* world, const LilvNode* node)
return label;
}
+Glib::ustring
+comment(World* world, const LilvNode* node)
+{
+ LilvNode* rdfs_comment = lilv_new_uri(
+ world->lilv_world(), LILV_NS_RDFS "comment");
+ 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) : "";
+
+ lilv_nodes_free(comments);
+ lilv_node_free(rdfs_comment);
+ return comment;
+}
+
void
classes(World* world, URISet& types, bool super)
{
@@ -158,8 +174,11 @@ instances(World* world, const URISet& types)
LilvNodes* objects = lilv_world_find_nodes(
world->lilv_world(), NULL, rdf_type, type);
LILV_FOREACH(nodes, o, objects) {
- const LilvNode* object = lilv_nodes_get(objects, o);
- const Glib::ustring label = RDFS::label(world, object);
+ const LilvNode* object = lilv_nodes_get(objects, o);
+ if (!lilv_node_is_uri(object)) {
+ continue;
+ }
+ const Glib::ustring label = RDFS::label(world, object);
result.insert(
std::make_pair(
Raul::URI(lilv_node_as_string(object)),
diff --git a/src/gui/RDFS.hpp b/src/gui/RDFS.hpp
index 163b0bb7..d9506dbc 100644
--- a/src/gui/RDFS.hpp
+++ b/src/gui/RDFS.hpp
@@ -45,6 +45,10 @@ typedef std::map<Raul::URI, Glib::ustring> Objects;
Glib::ustring
label(World* world, const LilvNode* node);
+/** Return the comment of `node`. */
+Glib::ustring
+comment(World* world, const LilvNode* node);
+
/** Set `types` to its super/sub class closure.
* @param super If true, find all superclasses, otherwise all subclasses
*/
diff --git a/src/gui/ingen_gui.ui b/src/gui/ingen_gui.ui
index 3b1ebad5..8fa4f35e 100644
--- a/src/gui/ingen_gui.ui
+++ b/src/gui/ingen_gui.ui
@@ -2424,23 +2424,44 @@ See COPYING file included with this distribution, or http://www.gnu.org/licenses
<property name="can_focus">False</property>
</object>
<packing>
- <property name="expand">True</property>
+ <property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
- <object class="GtkComboBox" id="properties_value_combo">
+ <object class="GtkButton" id="properties_value_button">
+ <property name="label">☰</property>
+ <property name="use_action_appearance">False</property>
<property name="visible">True</property>
- <property name="can_focus">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_stock">True</property>
</object>
<packing>
- <property name="expand">True</property>
+ <property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
+ <object class="GtkEntry" id="properties_value_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="activates_default">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">True</property>
+ <property name="secondary_icon_sensitive">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
<object class="GtkButton" id="properties_add_button">
<property name="label">gtk-add</property>
<property name="use_action_appearance">False</property>
@@ -2452,7 +2473,7 @@ See COPYING file included with this distribution, or http://www.gnu.org/licenses
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="position">2</property>
+ <property name="position">3</property>
</packing>
</child>
</object>