commit 4f3745e69661cbad3d2ea7c41c36c78009845e71
parent 333683dce2678f8ded9ed3dce87bc411a81f8d79
Author: Dan Callaghan <djc@djc.id.au>
Date: Sat, 21 Aug 2010 14:15:13 +1000
beginnings of UI for editing properties
Diffstat:
5 files changed, 170 insertions(+), 17 deletions(-)
diff --git a/src/ImageMetadata.vala b/src/ImageMetadata.vala
@@ -1,21 +1,97 @@
namespace XmpEdit {
-public class PropertyEditor : Object {
+public interface PropertyEditor : Gtk.Widget {
- public string prop { get; construct; }
- public string value { get; construct; }
-
- public PropertyEditor(string prop, string value) {
- Object(prop: prop, value: value);
+ public static Type[] all_types() {
+ return { typeof(Description) };
}
+
+ public abstract string prop_name { get; }
+ public abstract RDF.Graph graph { get; set; }
+ public abstract RDF.URIRef subject { get; set; }
- public string get_list_markup() {
- return @"<b>Unknown property ($prop)</b>\n$value";
+ public abstract bool exists_in_graph();
+ public abstract string value_summary();
+ public abstract void refresh();
+
+ public string list_markup() {
+ var display_name = prop_name.substring(0, 1).up() + prop_name.substring(1);
+ return @"<b>$(display_name)</b>\n$(value_summary())";
}
}
+private class Description : Gtk.Table, PropertyEditor {
+
+ private static RDF.URIRef DC_DESCRIPTION = new RDF.URIRef("http://purl.org/dc/elements/1.1/description");
+
+ public string prop_name { get { return "description"; } }
+ public RDF.Graph graph { get; set; }
+ public RDF.URIRef subject { get; set; }
+ private Gtk.TextView text_view = new Gtk.TextView();
+
+ construct {
+ n_rows = 1;
+ n_columns = 1;
+ homogeneous = false;
+ attach(text_view,
+ 0, 1, 0, 1,
+ Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND, Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
+ 10, 10);
+ }
+
+ public bool exists_in_graph() {
+ return graph.find_objects(subject, DC_DESCRIPTION).size > 0;
+ }
+
+ public string value_summary() {
+ var description = graph.find_object(subject, DC_DESCRIPTION);
+ if (description == null)
+ return "<i>(not set)</i>";
+ if (description is RDF.SubjectNode) {
+ var node = (RDF.SubjectNode) description;
+ if (graph.has_matching_statement(node,
+ new RDF.URIRef(RDF.RDF_NS + "type"),
+ new RDF.URIRef(RDF.RDF_NS + "Alt"))) {
+ description = select_alternative(node, graph);
+ }
+ }
+ if (!(description is RDF.PlainLiteral)) {
+ return "<i>(non-literal node)</i>";
+ }
+ var literal = (RDF.PlainLiteral) description;
+ return literal.lexical_value;
+ }
+
+ public void refresh() {
+ text_view.buffer.text = value_summary();
+ }
+
+}
+
+/** Given an rdf:Alt node, returns the "best" alternative */
+private RDF.Node? select_alternative(RDF.SubjectNode alt_node, RDF.Graph graph) {
+ var preferred_lang = get_preferred_lang();
+ var alternatives = graph.find_objects(alt_node, new RDF.URIRef(RDF.RDF_NS + "li"));
+ if (alternatives.size == 0)
+ return null;
+ foreach (var alternative in alternatives) {
+ if (alternative is RDF.PlainLiteral) {
+ var literal = (RDF.PlainLiteral) alternative;
+ if (literal.lang == preferred_lang)
+ return literal;
+ }
+ }
+ return alternatives[0];
+}
+
+// XXX make this a user pref somehow?
+private string get_preferred_lang() {
+ var lang = Environment.get_variable("LANG");
+ return (lang != null ? lang.substring(0, 2) : "en");
+}
+
public class ImageMetadata : Object {
public string path { get; construct; }
@@ -37,12 +113,21 @@ public class ImageMetadata : Object {
exiv_metadata.open_path(path);
string xmp = exiv_metadata.get_xmp_packet();
stdout.puts(xmp);
- var g = new RDF.Graph.from_xml(xmp, File.new_for_path(path).get_uri());
+ var base_uri = File.new_for_path(path).get_uri();
+ var g = new RDF.Graph.from_xml(xmp, base_uri);
foreach (var s in g.get_statements())
stdout.puts(@"$s\n");
- foreach (var tag in exiv_metadata.get_xmp_tags()) {
- properties.add(new PropertyEditor(tag, exiv_metadata.get_xmp_tag_string(tag)));
+ var subject = new RDF.URIRef(base_uri);
+ foreach (var type in PropertyEditor.all_types()) {
+ var pe = (PropertyEditor) Object.new(type);
+ pe.graph = g;
+ pe.subject = subject;
+ if (pe.exists_in_graph())
+ properties.add(pe);
}
+ //foreach (var tag in exiv_metadata.get_xmp_tags()) {
+ // properties.add(new PropertyEditor(tag, exiv_metadata.get_xmp_tag_string(tag)));
+ //}
updated();
}
diff --git a/src/MainWindow.vala b/src/MainWindow.vala
@@ -8,7 +8,7 @@ public class MainWindow : Gtk.Window {
private Gtk.Image image_preview;
private Gtk.ScrolledWindow tree_view_scrolled;
private MetadataTreeView tree_view;
- private Gtk.ScrolledWindow detail_scrolled;
+ private PropertyDetailView detail_view;
public MainWindow(string path) throws GLib.Error {
Object(type: Gtk.WindowType.TOPLEVEL);
@@ -18,6 +18,7 @@ public class MainWindow : Gtk.Window {
image_preview = new Gtk.Image.from_pixbuf(new Gdk.Pixbuf.from_file_at_scale(path, 320, 320, /* preserve aspect */ true));
tree_view_scrolled = new Gtk.ScrolledWindow(null, null);
tree_view = new MetadataTreeView.connected_to(image_metadata);
+ detail_view = new PropertyDetailView.connected_to(image_metadata, tree_view);
title = File.new_for_path(path).get_basename();
default_width = 640;
@@ -35,6 +36,11 @@ public class MainWindow : Gtk.Window {
0, 1, 0, 2,
Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND,
0, 0);
+
+ table.attach(detail_view,
+ 1, 2, 1, 2,
+ Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND, Gtk.AttachOptions.FILL,
+ 0, 0);
add(table);
show_all();
diff --git a/src/MetadataTreeView.vala b/src/MetadataTreeView.vala
@@ -4,7 +4,7 @@ namespace XmpEdit {
public class MetadataTreeModel : Gtk.ListStore {
public MetadataTreeModel() {
- set_column_types({ typeof(string) });
+ set_column_types({ typeof(string), typeof(PropertyEditor) });
}
public void populate(ImageMetadata metadata) {
@@ -12,7 +12,8 @@ public class MetadataTreeModel : Gtk.ListStore {
foreach (var property_editor in metadata.properties) {
Gtk.TreeIter row;
append(out row);
- set_value(row, 0, property_editor.get_list_markup());
+ set_value(row, 0, property_editor.list_markup());
+ set_value(row, 1, property_editor);
}
}
@@ -37,6 +38,7 @@ public class MetadataTreeView : Gtk.TreeView {
column.pack_start(cell_renderer, /* expand */ true);
column.add_attribute(cell_renderer, "markup", 0);
append_column(column);
+ get_selection().set_mode(Gtk.SelectionMode.BROWSE);
}
}
diff --git a/src/PropertyDetailView.vala b/src/PropertyDetailView.vala
@@ -0,0 +1,28 @@
+
+namespace XmpEdit {
+
+public class PropertyDetailView : Gtk.ScrolledWindow {
+
+ public MetadataTreeView tree_view { get; construct; }
+
+ public PropertyDetailView.connected_to(ImageMetadata image_metadata, MetadataTreeView tree_view) {
+ Object(tree_view: tree_view);
+ }
+
+ construct {
+ set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
+ tree_view.cursor_changed.connect(() => {
+ Gtk.TreeIter iter;
+ tree_view.get_selection().get_selected(null, out iter);
+ Value value;
+ tree_view.model.get_value(iter, 1, out value);
+ PropertyEditor pe = (PropertyEditor) value.get_object();
+ pe.refresh();
+ //remove(child);
+ add_with_viewport(pe);
+ });
+ }
+
+}
+
+}
diff --git a/src/RDF.vala b/src/RDF.vala
@@ -249,11 +249,11 @@ public class Graph : Object {
}
}
- public Gee.Collection<Statement> get_statements() {
- return statements;
+ public Gee.List<Statement> get_statements() {
+ return statements.read_only_view;
}
- public Gee.Collection<Statement> find_matching_statements(
+ public Gee.List<Statement> find_matching_statements(
SubjectNode? subject, URIRef? predicate, Node? object) {
// XXX naive
var result = new Gee.ArrayList<Statement>((EqualFunc) Statement.equal);
@@ -265,6 +265,38 @@ public class Graph : Object {
}
return result;
}
+
+ public bool has_matching_statement(
+ SubjectNode? subject, URIRef? predicate, Node? object) {
+ // XXX naive
+ foreach (var s in statements) {
+ if ((subject == null || s.subject.equals(subject)) &&
+ (predicate == null || s.predicate.equals(predicate)) &&
+ (object == null || s.object.equals(object)))
+ return true;
+ }
+ return false;
+ }
+
+ public Gee.List<Node> find_objects(SubjectNode subject, URIRef predicate) {
+ // XXX naive
+ var result = new Gee.ArrayList<Node>();
+ foreach (var s in statements) {
+ if ((subject == null || s.subject.equals(subject)) &&
+ (predicate == null || s.predicate.equals(predicate)))
+ result.add(s.object);
+ }
+ return result;
+ }
+
+ public Node? find_object(SubjectNode subject, URIRef predicate) {
+ foreach (var s in statements) {
+ if ((subject == null || s.subject.equals(subject)) &&
+ (predicate == null || s.predicate.equals(predicate)))
+ return s.object;
+ }
+ return null;
+ }
}