diff options
author | David Robillard <d@drobilla.net> | 2015-03-28 01:35:27 +0000 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2015-03-28 01:35:27 +0000 |
commit | f4d8b438532b3c090094b4060d6e0a2b2fba9d4d (patch) | |
tree | 40339fff10921ad264fff3ace8a04c4b52d7c505 | |
parent | ac60550c32598ad91f941722913f1629d5c9a35f (diff) | |
download | ingen-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.cpp | 202 | ||||
-rw-r--r-- | src/gui/PropertiesWindow.hpp | 13 | ||||
-rw-r--r-- | src/gui/RDFS.cpp | 23 | ||||
-rw-r--r-- | src/gui/RDFS.hpp | 4 | ||||
-rw-r--r-- | src/gui/ingen_gui.ui | 31 |
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> |