xmpedit

GTK+ editor for XMP metadata embedded in images
git clone https://code.djc.id.au/git/xmpedit/
commit 5c626fd080f8a3a0f02fc04ae30afbbe28838a33
parent 91cd4a9dd12000a65e71661f12c26d1769b981cf
Author: Dan Callaghan <djc@djc.id.au>
Date:   Sun, 22 Aug 2010 19:23:47 +1000

implement TreeModel directly on ImageMetadata, to avoid having to synchronise and separate ListStore; share graph on ImageMetadata

Diffstat:
Msrc/ImageMetadata.vala | 143++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/MetadataTreeView.vala | 24+-----------------------
Msrc/PropertyDetailView.vala | 5++++-
3 files changed, 122 insertions(+), 50 deletions(-)
diff --git a/src/ImageMetadata.vala b/src/ImageMetadata.vala
@@ -8,11 +8,12 @@ public interface PropertyEditor : Gtk.Widget {
     }
 
     public abstract string prop_name { get; }
+    public abstract RDF.Graph graph { get; set; }
     public abstract RDF.URIRef subject { get; set; }
     
     public abstract string value_summary();
-    public abstract bool populate(RDF.Graph graph);
-    public abstract Gee.Collection<RDF.Statement> as_rdf();
+    public abstract void load();
+    public abstract void commit();
     
     public string prop_display_name() {
         return prop_name.substring(0, 1).up() + prop_name.substring(1);
@@ -29,6 +30,7 @@ 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.ScrolledWindow text_scrolled = new Gtk.ScrolledWindow(null, null);
@@ -73,11 +75,11 @@ private class Description : Gtk.Table, PropertyEditor {
     }
 
     public string value_summary() {
-        string value = text_view.buffer.text;
-        return (value.size() > 0 ? value : "<i>not set</i>");
+        var literal = find_literal();
+        return (literal != null ? literal.lexical_value : "<i>not set</i>");
     }
 
-    private RDF.PlainLiteral? find_literal(RDF.Graph graph) {
+    private RDF.PlainLiteral? find_literal() {
         var description = graph.find_object(subject, DC_DESCRIPTION);
         if (description == null)
             return null;
@@ -100,21 +102,21 @@ private class Description : Gtk.Table, PropertyEditor {
         return (RDF.PlainLiteral) description;
     }
     
-    public bool populate(RDF.Graph graph) {
-        var literal = find_literal(graph);
+    // XXX use signals for these somehow???
+    
+    public void load() {
+        var literal = find_literal();
         if (literal == null) {
             text_view.buffer.text = "";
             lang_entry.text = "";
-            return false;
         } else {
             text_view.buffer.text = literal.lexical_value;
             lang_entry.text = (literal.lang != null ? literal.lang : "");
-            return true;
         }
     }
     
-    public Gee.Collection<RDF.Statement> as_rdf() {
-        var result = new Gee.ArrayList<RDF.Statement>();
+    public void commit() {
+        graph.remove_matching_statements(subject, DC_DESCRIPTION, null);
         string value = text_view.buffer.text;
         string lang = lang_entry.text;
         if (value.size() > 0) {
@@ -123,9 +125,8 @@ private class Description : Gtk.Table, PropertyEditor {
                 object = new RDF.PlainLiteral.with_lang(value, lang);
             else
                 object = new RDF.PlainLiteral(value);
-            result.add(new RDF.Statement(subject, DC_DESCRIPTION, object));
+            graph.insert(new RDF.Statement(subject, DC_DESCRIPTION, object));
         }
-        return result;
     }
 
 }
@@ -152,10 +153,12 @@ private string get_preferred_lang() {
     return (lang != null ? lang.substring(0, 2) : "en");
 }
 
-public class ImageMetadata : Object {
+public class ImageMetadata : Object, Gtk.TreeModel {
 
     public string path { get; construct; }
     public Gee.List<PropertyEditor> properties { get; construct; }
+    private RDF.Graph graph;
+    private RDF.URIRef subject;
     
     public signal void updated();
     
@@ -173,15 +176,15 @@ public class ImageMetadata : Object {
         string xmp = exiv_metadata.get_xmp_packet();
         stdout.puts(xmp);
         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())
+        graph = new RDF.Graph.from_xml(xmp, base_uri);
+        foreach (var s in graph.get_statements())
             stdout.puts(@"$s\n");
-        var subject = new RDF.URIRef(base_uri);
+        subject = new RDF.URIRef(base_uri);
         foreach (var type in PropertyEditor.all_types()) {
             var pe = (PropertyEditor) Object.new(type);
+            pe.graph = graph;
             pe.subject = subject;
-            if (pe.populate(g))
-                properties.add(pe);
+            properties.add(pe);
         }
         //foreach (var tag in exiv_metadata.get_xmp_tags()) {
         //    properties.add(new PropertyEditor(tag, exiv_metadata.get_xmp_tag_string(tag)));
@@ -190,15 +193,103 @@ public class ImageMetadata : Object {
     }
     
     public void save() {
-        var g = new RDF.Graph();
-        foreach (var pe in properties) {
-            foreach (var s in pe.as_rdf()) {
-                g.insert(s);
-            }
-        }
-        foreach (var s in g.get_statements())
+        foreach (var s in graph.get_statements())
             stdout.puts(@"$s\n");
         // XXX actually write it out
+        // XXX gc unreachable nodes in the graph
+    }
+    
+    /****** TREEMODEL IMPLEMENTATION STUFF **********/
+    
+    // XXX use custom cellrenderer instead of having a string column
+    
+    private int stamp() {
+        return (int) this;
+    }
+    
+    public Type get_column_type(int column) {
+        return_if_fail(column == 0 || column == 1);
+        return column == 0 ? typeof(string) : typeof(PropertyEditor);
+    }
+    
+    public int get_n_columns() {
+        return 2;
+    }
+    
+    public Gtk.TreeModelFlags get_flags() {
+        return Gtk.TreeModelFlags.LIST_ONLY;
+    }
+    
+    public bool get_iter(out Gtk.TreeIter iter, Gtk.TreePath path) {
+        if (path.get_depth() > 1) return false;
+        var index = path.get_indices()[0];
+        if (index > properties.size - 1) return false;
+        iter = { stamp(), (void*) properties[index], (void*) index, null };
+        return true;
+    }
+
+    public Gtk.TreePath get_path(Gtk.TreeIter iter) {
+        return_if_fail(iter.stamp == stamp());
+        return new Gtk.TreePath.from_indices((int) iter.user_data2);
+    }
+    
+    public void get_value(Gtk.TreeIter iter, int column, out Value value) {
+        return_if_fail(iter.stamp == stamp());
+        return_if_fail(column == 0 || column == 1);
+        var pe = (PropertyEditor) iter.user_data;
+        if (column == 0) {
+            value = Value(typeof(string));
+            value.take_string(pe.list_markup()); // XXX
+        } else {
+            value = Value(typeof(PropertyEditor));
+            value.set_object(pe);
+        }
+    }
+    
+    public bool iter_children(out Gtk.TreeIter iter, Gtk.TreeIter? parent) {
+        if (parent == null) {
+            iter = { stamp(), (void*) properties[0], (void*) 0, null };
+            return true;
+        }
+        return false;
+    }
+    
+    public bool iter_has_child(Gtk.TreeIter iter) {
+        return false;
+    }
+    
+    public int iter_n_children(Gtk.TreeIter? iter) {
+        if (iter == null)
+            return properties.size;
+        return 0;
+    }
+    
+    public bool iter_next(ref Gtk.TreeIter iter) {
+        return_if_fail(iter.stamp == stamp());
+        var index = (int) iter.user_data2;
+        if (index < properties.size - 1) {
+            iter.user_data2 = (void*) index + 1;
+            return true;
+        }
+        return false;
+    }
+    
+    public bool iter_nth_child(out Gtk.TreeIter iter, Gtk.TreeIter? parent, int n) {
+        if (parent == null && n < properties.size - 1) {
+            iter = { stamp(), (void*) properties[n], (void*) n, null };
+            return true;
+        }
+        return false;
+    }
+    
+    public bool iter_parent(out Gtk.TreeIter iter, Gtk.TreeIter child) {
+        return false;
+    }
+    
+    public void ref_node(Gtk.TreeIter iter) {
+    }
+    
+    public void unref_node(Gtk.TreeIter iter) {
     }
     
 }
diff --git a/src/MetadataTreeView.vala b/src/MetadataTreeView.vala
@@ -1,32 +1,10 @@
 
 namespace Xmpedit {
 
-public class MetadataTreeModel : Gtk.ListStore {
-
-    public MetadataTreeModel() {
-        set_column_types({ typeof(string), typeof(PropertyEditor) });
-    }
-    
-    public void populate(ImageMetadata metadata) {
-        clear(); // XXX probably inefficient
-        foreach (var property_editor in metadata.properties) {
-            Gtk.TreeIter row;
-            append(out row);
-            set_value(row, 0, property_editor.list_markup());
-            set_value(row, 1, property_editor);
-        }
-    }
-
-}
-
 public class MetadataTreeView : Gtk.TreeView {
 
-    private MetadataTreeModel custom_model = new MetadataTreeModel();
-
     public MetadataTreeView.connected_to(ImageMetadata metadata) {
-        model = custom_model;
-        custom_model.populate(metadata);
-        metadata.updated.connect((t) => { custom_model.populate(t); });
+        Object(model: metadata);
     }
     
     construct {
diff --git a/src/PropertyDetailView.vala b/src/PropertyDetailView.vala
@@ -17,8 +17,11 @@ public class PropertyDetailView : Gtk.Alignment {
             Value value;
             tree_view.model.get_value(iter, 1, out value);
             PropertyEditor pe = (PropertyEditor) value.get_object();
-            if (child != null)
+            if (child != null) {
+                ((PropertyEditor) child).commit();
                 remove(child);
+            }
+            pe.load();
             add(pe);
         });
     }