[f-spot/metadata-editor] Introduce MetadataEditor



commit fa3b69bec8f7049a3286e2474064c441c498cd79
Author: Mike Gemünde <mike gemuende de>
Date:   Mon Oct 4 13:49:31 2010 +0200

    Introduce MetadataEditor

 .../FSpot.Gui.MetadataEditor/BasicEditorPage.cs    |   67 +++++
 .../FSpot.Gui.MetadataEditor/EditorController.cs   |  143 ++++++++++
 .../FSpot.Gui.MetadataEditor/EditorDialog.cs       |  283 ++++++++++++++++++++
 .../MainApp/FSpot.Gui.MetadataEditor/EditorPage.cs |   36 +++
 .../FSpot.Gui.MetadataEditor/EditorPhoto.cs        |   98 +++++++
 .../FieldEditorSyncButton.cs                       |   64 +++++
 .../FSpot.Gui.MetadataEditor/IFieldEditor.cs       |   11 +
 .../InfoEditorPresetWidget.cs                      |  105 ++++++++
 .../LabelledFieldEditorWidget.cs                   |   92 +++++++
 .../LabelledSyncEditorWidget.cs                    |   44 +++
 .../ui/MetadataEditorDialog.ui                     |  136 ++++++++++
 src/Clients/MainApp/FSpot/MainWindow.cs            |    9 +
 src/Clients/MainApp/MainApp.csproj                 |   13 +
 src/Clients/MainApp/Makefile.am                    |   11 +
 src/Clients/MainApp/ui/main_window.ui              |    8 +
 15 files changed, 1120 insertions(+), 0 deletions(-)
---
diff --git a/src/Clients/MainApp/FSpot.Gui.MetadataEditor/BasicEditorPage.cs b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/BasicEditorPage.cs
new file mode 100644
index 0000000..cd95f73
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/BasicEditorPage.cs
@@ -0,0 +1,67 @@
+/*
+ * BasicEditorPage.cs
+ *
+ * Author(s):
+ *  Mike Gemuende <mike gemuende de>
+ *
+ * This is frees software. See COPYING for details.
+ */
+
+using System;
+
+using Mono.Unix;
+
+using Gtk;
+
+using FSpot.Widgets;
+
+
+namespace FSpot.Gui.MetadataEditor
+{
+    public class BasicEditorPage : EditorPage
+    {
+        public BasicEditorPage (EditorController controller)
+            : base (Catalog.GetString ("Basic Photo Info"))
+        {
+            PackStart (new LabelledSyncEditorWidget (Catalog.GetString ("Creator:"),
+                            Catalog.GetString ("Set this Creator to all Photos"),
+                            controller, new Entry (), true,
+                            (widget, info) => { (widget as Entry).Text = info.Creator ?? String.Empty; },
+                            (widget, info) => { info.Creator = (widget as Entry).Text; }),
+                       false, false, 3);
+
+            PackStart (new LabelledSyncEditorWidget (Catalog.GetString ("Copyright:"),
+                            Catalog.GetString ("Set this Copyright to all Photos"),
+                            controller, new Entry (), true,
+                            (widget, info) => { (widget as Entry).Text = info.Copyright ?? String.Empty; },
+                            (widget, info) => { info.Copyright = (widget as Entry).Text; }),
+                       false, false, 3);
+
+            PackStart (new HSeparator (), false, false, 6);
+
+            PackStart (new LabelledSyncEditorWidget (Catalog.GetString ("Title:"),
+                            Catalog.GetString ("Set this Title to all Photos"),
+                            controller, new Entry (), true,
+                            (widget, info) => { (widget as Entry).Text = info.Title ?? String.Empty; },
+                            (widget, info) => { info.Title = (widget as Entry).Text; }),
+                       false, false, 3);
+
+            PackStart (new LabelledSyncEditorWidget (Catalog.GetString ("Comment:"),
+                            Catalog.GetString ("Set this Comment to all Photos"),
+                            controller, new Entry (), false,
+                            (widget, info) => { (widget as Entry).Text = info.Comment ?? String.Empty; },
+                            (widget, info) => { info.Comment = (widget as Entry).Text; }),
+                       false, false, 3);
+
+            PackStart (new LabelledFieldEditorWidget (Catalog.GetString ("Rating:"),
+                            controller, new RatingEntry (), false,
+                            (widget, info) => { (widget as RatingEntry).Value = (int) info.Rating; },
+                            (widget, info) => { info.Rating = (uint) (widget as RatingEntry).Value; },
+                            false),
+                       false, false, 0);
+
+            PackStart (new VBox (), true, true, 0);
+        }
+    }
+}
+
diff --git a/src/Clients/MainApp/FSpot.Gui.MetadataEditor/EditorController.cs b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/EditorController.cs
new file mode 100644
index 0000000..580dd93
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/EditorController.cs
@@ -0,0 +1,143 @@
+/*
+ * EditorController.cs
+ *
+ * Author(s):
+ *  Mike Gemuende <mike gemuende de>
+ *
+ * This is frees software. See COPYING for details.
+ */
+
+using System;
+using System.Collections.Generic;
+
+using Hyena;
+
+using FSpot.Core;
+using FSpot.Utils;
+
+
+namespace FSpot.Gui.MetadataEditor
+{
+    public class EditorController
+    {
+
+#region Private Fields
+
+        private Dictionary<IPhoto, EditorPhoto> properties = new Dictionary<IPhoto, EditorPhoto> ();
+        private List<IFieldEditor> editors = new List<IFieldEditor> ();
+
+#endregion
+
+#region Constructors
+
+        public EditorController (IBrowsableCollection collection)
+        {
+            Collection = collection;
+            Pointer = new BrowsablePointer (Collection, -1);
+
+            Pointer.Changed += (o, e) => {
+                SelectPhoto (Pointer.Current);
+            };
+        }
+
+#endregion
+
+#region Public Interface
+
+        public IBrowsableCollection Collection {
+            get; private set;
+        }
+
+        public BrowsablePointer Pointer {
+            get; private set;
+        }
+
+        public void AddEditor (IFieldEditor editor)
+        {
+            editors.Add (editor);
+        }
+
+        public IEnumerable<EditorPhoto> PhotoInfos {
+            get {
+                foreach (var photo in Collection.Items) {
+                    yield return properties [photo];
+                }
+            }
+        }
+
+        public bool Load (Func<int, int, bool> loaded)
+        {
+            int i = 0;
+            foreach (var photo in Collection.Items) {
+
+                properties.Add (photo, EditorPhoto.CreateFromPhoto (photo));
+
+                if ( ! loaded (i, Collection.Count))
+                    return false;
+
+                i++;
+            }
+
+            return true;
+        }
+
+        public bool Store (Func<int, int, bool> stored)
+        {
+            UpdateInfo ();
+
+            int i = 0;
+            foreach (var photo in Collection.Items) {
+
+                EditorPhoto.SaveToPhoto (properties [photo], photo);
+
+                if ( ! stored (i, Collection.Count)) {
+                    Log.DebugFormat ("Callback aborted.");
+                    return false;
+                }
+
+                i++;
+            }
+
+            return true;
+        }
+
+#endregion
+
+#region Controller Logic
+
+        private EditorPhoto SelectedPhotoInfo {
+            get; set;
+        }
+
+        private void SelectPhoto (IPhoto photo)
+        {
+            UpdateInfo ();
+
+            SelectedPhotoInfo = properties [photo];
+
+            UpdateEditors ();
+        }
+
+        private void UpdateInfo ()
+        {
+            if (SelectedPhotoInfo == null)
+                return;
+
+            foreach (var editor in editors)
+                editor.UpdateInfo (SelectedPhotoInfo);
+        }
+
+        private void UpdateEditors ()
+        {
+            if (SelectedPhotoInfo == null)
+                return;
+
+            foreach (var editor in editors)
+                editor.UpdateEditor (SelectedPhotoInfo);
+        }
+
+#endregion
+
+    }
+}
+
diff --git a/src/Clients/MainApp/FSpot.Gui.MetadataEditor/EditorDialog.cs b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/EditorDialog.cs
new file mode 100644
index 0000000..ef1afc1
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/EditorDialog.cs
@@ -0,0 +1,283 @@
+/*
+ * EditorDialog.cs
+ *
+ * Author(s):
+ *  Mike Gemuende <mike gemuende de>
+ *
+ * This is frees software. See COPYING for details.
+ */
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+using Mono.Unix;
+
+using Gtk;
+
+using Hyena;
+using Hyena.Widgets;
+
+using FSpot.Widgets;
+using FSpot.Core;
+using FSpot.UI.Dialog;
+
+
+namespace FSpot.Gui.MetadataEditor
+{
+    public class EditorDialog : BuilderDialog
+    {
+
+#region Private Fields
+
+        [GtkBeans.Builder.Object] private HBox dialog_content;
+        [GtkBeans.Builder.Object] private Alignment photo_view_alignment;
+        [GtkBeans.Builder.Object] private VBox content_box;
+        [GtkBeans.Builder.Object] private VBox progressbar_placeholder;
+
+        [GtkBeans.Builder.Object] private VBox dialog_vbox;
+        [GtkBeans.Builder.Object] private Button save_button;
+        [GtkBeans.Builder.Object] private Button cancel_button;
+
+        private AnimatedVBox progressbar_box;
+        private ProgressBar progressbar;
+        private Notebook editor_notebook;
+
+        private Button move_next_button;
+        private Button move_prev_button;
+
+        private BrowseablePointerGridView photo_view;
+        private EditorController controller;
+        private bool loaded = false;
+        private IPhoto selected_photo;
+
+#endregion
+
+#region Constructors
+
+        public EditorDialog (IBrowsableCollection collection)
+            : base ("MetadataEditorDialog.ui", "metadata_editor_dialog")
+        {
+            save_button.Clicked += (o, e) => { SaveContent (); };
+            cancel_button.Clicked += (o, e) => { Destroy (); };
+
+            // setup editor controller
+            controller = new EditorController (collection);
+            controller.Pointer.Changed += (o, e) => { UpdateMoveButtons (); };
+
+            // setup photo view
+            photo_view = new BrowseablePointerGridView (controller.Pointer) { MaxColumns = 1 };
+
+            var scrolled_window = new Hyena.Widgets.ScrolledWindow ();
+            photo_view_alignment.Add (scrolled_window);
+            scrolled_window.AddWithFrame (photo_view);
+            scrolled_window.WidthRequest = 200;
+
+            photo_view_alignment.ShowAll ();
+
+            // setup progressbar
+            progressbar = new ProgressBar ();
+            progressbar_box = new AnimatedVBox ();
+            progressbar_placeholder.Add (progressbar_box);
+
+            // setup editor notebook
+            editor_notebook = new Notebook ();
+            content_box.Add (editor_notebook);
+            editor_notebook.Show ();
+
+            AddEditorPage (new BasicEditorPage (controller));
+
+            // setup navigation buttons
+            move_prev_button = new Button (Stock.GoBack);
+            move_next_button = new Button (Stock.GoForward);
+
+            move_prev_button.Clicked += (o, e) => {
+                controller.Pointer.MovePrevious ();
+                photo_view.ScrollTo (controller.Pointer.Index);
+            };
+            move_next_button.Clicked += (o, e) => {
+                controller.Pointer.MoveNext ();
+                photo_view.ScrollTo (controller.Pointer.Index);
+            };
+
+            ActionArea.PackStart (move_prev_button, false, false, 0);
+            ActionArea.PackStart (move_next_button, false, false, 0);
+            ActionArea.SetChildSecondary (move_prev_button, true);
+            ActionArea.SetChildSecondary (move_next_button, true);
+
+            move_prev_button.Show ();
+            move_next_button.Show ();
+
+            progressbar_box.ShowAll ();
+            content_box.ShowAll ();
+            dialog_content.ShowAll ();
+        }
+
+#endregion
+
+#region GUI Utility Methods
+
+        private void EnableProgress ()
+        {
+            if ( ! progressbar_box.Contains (progressbar)) {
+
+                if (progressbar.Parent != null)
+                    (progressbar.Parent as Container).Remove (progressbar);
+
+                progressbar.Show ();
+                progressbar_box.Add (progressbar);
+            }
+
+            dialog_content.Sensitive = false;
+            save_button.Sensitive = false;
+            move_next_button.Sensitive = false;
+            move_prev_button.Sensitive = false;
+        }
+
+        private void DisableProgress ()
+        {
+            dialog_content.Sensitive = true;
+            save_button.Sensitive = true;
+
+            // here we set the sensitivity of the move buttons according to
+            // the selected photo instead of making them just sensitive
+            UpdateMoveButtons ();
+
+            if (progressbar_box.Contains (progressbar))
+                progressbar_box.Remove (progressbar);
+        }
+
+        private void UpdateProgress (string text, double fraction)
+        {
+            progressbar.Text = text;
+            progressbar.Fraction = fraction;
+        }
+
+        private void UpdateMoveButtons ()
+        {
+            move_prev_button.Sensitive = controller.Pointer.Index > 0;
+            move_next_button.Sensitive = controller.Pointer.Index < controller.Pointer.Collection.Count - 1;
+        }
+
+#endregion
+
+#region Public Methods
+
+        public new void Run ()
+        {
+            LoadContent ();
+            base.Run ();
+        }
+
+        public void AddEditorPage (EditorPage page)
+        {
+            var alignment = new Alignment (0.5f, 0.5f, 1.0f, 1.0f) {
+                LeftPadding = 8,
+                RightPadding = 8,
+                TopPadding = 8,
+                BottomPadding = 8};
+            alignment.Add (page);
+
+            editor_notebook.AppendPage (alignment, new Label (page.Title));
+
+            editor_notebook.ShowTabs = (editor_notebook.NPages > 1);
+        }
+
+#endregion
+
+#region Override Base Class Behavior
+
+        protected override void OnDestroyed ()
+        {
+            base.OnDestroyed ();
+
+            // ensure that running background threads are stopped
+            stop_requested = true;
+        }
+
+#endregion
+
+#region Background Working Stuff
+
+        // is set to request a stop of background threads
+        private bool stop_requested;
+
+        private void LoadContent ()
+        {
+            UpdateProgress (String.Format (Catalog.GetString ("Photo {0} of {1} Loaded"), 0, controller.Collection.Count), 0);
+            EnableProgress ();
+
+            stop_requested = false;
+
+            // start thread to load photos
+            ThreadAssist.Spawn (() => {
+
+                try {
+                    controller.Load ((loaded_index, count) => {
+
+                        ThreadAssist.ProxyToMain (() => UpdateProgress (String.Format (Catalog.GetString ("Photo {0} of {1} Loaded"),
+                                                       loaded_index + 1, count),
+                                        loaded_index / (double) count));
+
+                        // request controller to stop, if neccesary
+                        return ! stop_requested;
+
+                    });
+                } catch (Exception e) {
+                    Log.DebugException (e);
+
+                    if ( ! stop_requested)
+                        ThreadAssist.ProxyToMain (Destroy);
+                }
+
+                if (stop_requested)
+                    return;
+
+                ThreadAssist.ProxyToMain (() => {
+                    DisableProgress ();
+                    controller.Pointer.MoveFirst ();
+                });
+
+            });
+        }
+
+        private void SaveContent ()
+        {
+            UpdateProgress (String.Format (Catalog.GetString ("Photo {0} of {1} Saved"), 0, controller.Collection.Count), 0);
+            EnableProgress ();
+
+            stop_requested = false;
+
+            // start thread to save photos
+            ThreadAssist.Spawn (() => {
+
+                try {
+                    controller.Store ((stored_index, count) => {
+
+                        ThreadAssist.ProxyToMain (() => UpdateProgress (String.Format (Catalog.GetString ("Photo {0} of {1} Saved"),
+                                                       stored_index + 1, count),
+                                        stored_index / (double) count));
+
+                        // request controller to stop, if neccesary
+                        return ! stop_requested;
+
+                    });
+                } catch (Exception e) {
+                    Log.DebugException (e);
+
+                    if ( ! stop_requested)
+                        ThreadAssist.ProxyToMain (Destroy);
+                }
+
+                if (stop_requested)
+                    return;
+
+                ThreadAssist.ProxyToMain (Destroy);
+            });
+        }
+
+#endregion
+
+    }
+}
+
diff --git a/src/Clients/MainApp/FSpot.Gui.MetadataEditor/EditorPage.cs b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/EditorPage.cs
new file mode 100644
index 0000000..b3f7a9a
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/EditorPage.cs
@@ -0,0 +1,36 @@
+/*
+ * EditorPage.cs
+ *
+ * Author(s):
+ *  Mike Gemuende <mike gemuende de>
+ *
+ * This is frees software. See COPYING for details.
+ */
+
+using System;
+using System.Collections.Generic;
+
+using Gtk;
+
+
+namespace FSpot.Gui.MetadataEditor
+{
+    public class EditorPage : VBox
+    {
+
+#region Constructors
+
+        public EditorPage (string title)
+            : base (false, 6)
+        {
+            Title = title;
+        }
+
+#endregion
+
+        public string Title {
+            get; private set;
+        }
+    }
+}
+
diff --git a/src/Clients/MainApp/FSpot.Gui.MetadataEditor/EditorPhoto.cs b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/EditorPhoto.cs
new file mode 100644
index 0000000..31262e6
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/EditorPhoto.cs
@@ -0,0 +1,98 @@
+/*
+ * EditorPhoto.cs
+ *
+ * Author(s):
+ *  Mike Gemuende <mike gemuende de>
+ *
+ * This is frees software. See COPYING for details.
+ */
+
+using System;
+
+using FSpot.Core;
+using FSpot.Utils;
+
+
+namespace FSpot.Gui.MetadataEditor
+{
+    public class EditorPhoto
+    {
+
+#region Constructors
+
+        public EditorPhoto ()
+        {
+        }
+
+#endregion
+
+#region Photo Information
+
+        public string Creator { get; set; }
+        public string Copyright { get; set; }
+        public string Title { get; set; }
+        public string Comment { get; set; }
+        public uint Rating { get; set; }
+        public bool WriteableMetadata { get; private set; }
+
+#endregion
+
+#region Read/Write Photo Information
+
+        public static void SaveToPhoto (EditorPhoto info, IPhoto photo)
+        {
+            using (var metadata = Metadata.Parse (photo.DefaultVersion.Uri)) {
+                if (metadata == null)
+                    return;
+
+                metadata.EnsureAvailableTags ();
+                var tag = metadata.ImageTag;
+
+                // set information to photo object
+                var db_photo = photo as Photo;
+                if (db_photo != null) {
+                    db_photo.Description = info.Comment;
+                    db_photo.Rating = info.Rating;
+
+                    App.Instance.Database.Photos.Commit (db_photo);
+                }
+
+                // write information to file metadata
+                tag.Title = info.Title;
+                tag.Creator = info.Creator;
+                tag.Copyright = info.Copyright;
+                tag.Comment = info.Comment;
+                tag.Rating = info.Rating;
+
+                metadata.Save ();
+            }
+        }
+
+        public static EditorPhoto CreateFromPhoto (IPhoto photo)
+        {
+            using (var metadata = Metadata.Parse (photo.DefaultVersion.Uri)) {
+                if (metadata == null)
+                    return null;
+
+                var tag = metadata.ImageTag;
+                var info = new EditorPhoto ();
+
+                // get information from photo object
+                info.Comment = photo.Description;
+                info.Rating = photo.Rating;
+                // determine information from file metadata
+                info.Title = tag.Title;
+                info.Creator = tag.Creator;
+                info.Copyright = tag.Copyright;
+
+                info.WriteableMetadata = metadata.Writeable;
+
+                return info;
+            }
+        }
+
+#endregion
+
+    }
+}
+
diff --git a/src/Clients/MainApp/FSpot.Gui.MetadataEditor/FieldEditorSyncButton.cs b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/FieldEditorSyncButton.cs
new file mode 100644
index 0000000..753a772
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/FieldEditorSyncButton.cs
@@ -0,0 +1,64 @@
+//
+// FieldEditorSyncButton.cs : original file is SyncButton.cs from banshee
+//
+// Author:
+//   Aaron Bockover <abockover novell com>
+//
+// Copyright (C) 2008 Novell, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+using Gtk;
+
+namespace FSpot.Gui.MetadataEditor
+{
+    public class FieldEditorSyncButton : Button
+    {
+        protected FieldEditorSyncButton ()
+        {
+            Image image = new Image (Stock.Copy, IconSize.Menu);
+            Add (image);
+            Relief = ReliefStyle.None;
+            image.Show ();
+        }
+
+        public FieldEditorSyncButton (EditorController controller, IFieldEditor editor)
+            : this ()
+        {
+            Clicked += (o, a) => {
+                foreach (var info in controller.PhotoInfos)
+                    editor.UpdateInfo (info);
+            };
+        }
+
+        public FieldEditorSyncButton (EditorController controller, Action<EditorPhoto> sync_to)
+            : this ()
+        {
+            Clicked += (o, a) => {
+                foreach (var info in controller.PhotoInfos)
+                    sync_to (info);
+            };
+        }
+    }
+}
+
diff --git a/src/Clients/MainApp/FSpot.Gui.MetadataEditor/IFieldEditor.cs b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/IFieldEditor.cs
new file mode 100644
index 0000000..270a23b
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/IFieldEditor.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace FSpot.Gui.MetadataEditor
+{
+    public interface IFieldEditor
+    {
+        void UpdateEditor (EditorPhoto info);
+        void UpdateInfo (EditorPhoto info);
+    }
+}
+
diff --git a/src/Clients/MainApp/FSpot.Gui.MetadataEditor/InfoEditorPresetWidget.cs b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/InfoEditorPresetWidget.cs
new file mode 100644
index 0000000..3be53fa
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/InfoEditorPresetWidget.cs
@@ -0,0 +1,105 @@
+/*
+ * InfoEditorPresetWidget.cs
+ *
+ * Author(s):
+ *  Mike Gemuende <mike gemuende de>
+ *
+ * This is frees software. See COPYING for details.
+ */
+
+using System;
+using System.Collections.Generic;
+
+using Mono.Unix;
+
+using Gtk;
+
+
+namespace FSpot.Gui.MetadataEditor
+{
+    public class InfoEditorPresetWidget : HBox
+    {
+        private ComboBox preset_combo;
+
+        List<IFieldEditor> editors = new List<IFieldEditor> ();
+
+        public InfoEditorPresetWidget (string label, EditorController controller)
+            : base (false, 8)
+        {
+            PackStart (new Label (label), false, false, 0);
+
+            preset_combo = new ComboBox ();
+            PackStart (preset_combo, true, true, 0);
+
+            var save_button = new Button ();
+
+            Image image = new Image (Stock.Save, IconSize.Menu);
+            save_button.Add (image);
+            save_button.Relief = ReliefStyle.None;
+            save_button.TooltipText = Catalog.GetString ("Save Current Values as Preset");
+            save_button.Clicked += (o, a) => { SavePresetDialog (); };
+
+            PackStart (save_button, false, false, 0);
+        }
+
+
+        public void AddEditor (IFieldEditor editor)
+        {
+            editors.Add (editor);
+        }
+
+        private EditorPhoto ReadPreset ()
+        {
+            var info = new EditorPhoto ();
+
+            foreach (var editor in editors)
+                editor.UpdateInfo (info);
+
+            return info;
+        }
+
+        private void SavePreset (string preset_name)
+        {
+
+        }
+
+        private void SavePresetDialog ()
+        {
+            var dialog = new EnterPresetNameDialog ();
+
+            var result = dialog.Run ();
+
+            if (result == (int) ResponseType.Ok)
+                SavePreset (dialog.PresetName);
+
+            dialog.Destroy ();
+        }
+
+        private class EnterPresetNameDialog : Gtk.Dialog
+        {
+            private Entry name_entry;
+
+            public string PresetName {
+                get { return name_entry.Text; }
+            }
+
+            public EnterPresetNameDialog () : base ()
+            {
+                Title = Catalog.GetString ("Enter Preset Name");
+                SetSizeRequest (300, 100);
+
+                var inner_vbox = new VBox (false, 3);
+                VBox.Add (inner_vbox);
+
+                inner_vbox.PackStart (new Label (Catalog.GetString ("Preset Name:")) { Xalign = 0.0f }, false, false, 3);
+                inner_vbox.PackStart (name_entry = new Entry () {}, false, false, 3);
+
+                VBox.ShowAll ();
+
+                AddActionWidget (new Button ("gtk-cancel") { Visible = true }, ResponseType.Cancel);
+                AddActionWidget (new Button ("gtk-save") { Visible = true }, ResponseType.Ok);
+            }
+        }
+    }
+}
+
diff --git a/src/Clients/MainApp/FSpot.Gui.MetadataEditor/LabelledFieldEditorWidget.cs b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/LabelledFieldEditorWidget.cs
new file mode 100644
index 0000000..954d539
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/LabelledFieldEditorWidget.cs
@@ -0,0 +1,92 @@
+/*
+ * LabelledFieldEditorWidget.cs
+ *
+ * Author(s):
+ *  Mike Gemuende <mike gemuende de>
+ *
+ * This is frees software. See COPYING for details.
+ */
+
+using System;
+
+using Gtk;
+
+
+namespace FSpot.Gui.MetadataEditor
+{
+    public class LabelledFieldEditorWidget : VBox, IFieldEditor
+    {
+		private bool only_writeable_files;
+        private Action<Widget, EditorPhoto> update_widget;
+        private Action<Widget, EditorPhoto> update_info;
+
+        protected EditorController Controller {
+            get; private set;
+        }
+
+        protected Widget Widget {
+            get; private set;
+        }
+
+#region Constructors
+
+        public LabelledFieldEditorWidget (string label, EditorController controller, Widget widget,
+                                          Action<Widget, EditorPhoto> update_widget,
+                                          Action<Widget, EditorPhoto> update_info)
+            : this (label, controller, widget, update_widget, update_info, true)
+        {
+        }
+
+        public LabelledFieldEditorWidget (string label, EditorController controller, Widget widget,
+                                          Action<Widget, EditorPhoto> update_widget,
+                                          Action<Widget, EditorPhoto> update_info, bool expand)
+            : this (label, controller, widget, false, update_widget, update_info, expand)
+        {
+        }
+
+        public LabelledFieldEditorWidget (string label, EditorController controller, Widget widget,
+                                          bool only_writeable_files,
+                                          Action<Widget, EditorPhoto> update_widget,
+                                          Action<Widget, EditorPhoto> update_info, bool expand)
+            : base (false, 3)
+        {
+            this.Widget = widget;
+            this.Controller = controller;
+            this.update_widget = update_widget;
+            this.update_info = update_info;
+			this.only_writeable_files = only_writeable_files;
+
+            PackStart (new Label (label) { Xalign = 0 }, false, false, 0);
+
+            if (expand) {
+                PackStart (Widget, true, true, 0);
+            } else {
+                var hbox = new HBox (false, 0) {};
+                hbox.PackStart (Widget, false, false, 0);
+                PackStart (hbox, false, false, 0);
+            }
+
+            controller.AddEditor (this);
+        }
+
+#endregion
+
+#region IPhotoInfoEditor Implementation
+
+        public void UpdateEditor (EditorPhoto info)
+        {
+            update_widget (Widget, info);
+
+            Widget.Sensitive = info.WriteableMetadata || ! only_writeable_files;
+        }
+
+        public void UpdateInfo (EditorPhoto info)
+        {
+            update_info (Widget, info);
+        }
+
+#endregion
+
+    }
+}
+
diff --git a/src/Clients/MainApp/FSpot.Gui.MetadataEditor/LabelledSyncEditorWidget.cs b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/LabelledSyncEditorWidget.cs
new file mode 100644
index 0000000..33268c0
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/LabelledSyncEditorWidget.cs
@@ -0,0 +1,44 @@
+/*
+ * LabelledSyncEditorWidget.cs
+ *
+ * Author(s):
+ *  Mike Gemuende <mike gemuende de>
+ *
+ * This is frees software. See COPYING for details.
+ */
+
+using System;
+
+using Gtk;
+
+
+namespace FSpot.Gui.MetadataEditor
+{
+    public class LabelledSyncEditorWidget : LabelledFieldEditorWidget
+    {
+        public LabelledSyncEditorWidget (string label, string sync_button_tooltip, EditorController controller,
+                                         Widget widget,
+                                         Action<Widget, EditorPhoto> update_widget,
+                                         Action<Widget, EditorPhoto> update_info)
+            : this (label, sync_button_tooltip, controller, widget, false, update_widget, update_info)
+        {
+        }
+
+        public LabelledSyncEditorWidget (string label, string sync_button_tooltip, EditorController controller,
+                                         Widget widget, bool only_writeable_files,
+                                         Action<Widget, EditorPhoto> update_widget,
+                                         Action<Widget, EditorPhoto> update_info)
+            : base (label, controller, new HBox (false, 8),
+                    (w, info) => { update_widget (widget, info); },
+                    (w, info) => { update_info (widget, info); })
+        {
+            (Widget as HBox).PackStart (widget, true, true, 0);
+
+            var sync_button = new FieldEditorSyncButton (controller, this);
+            sync_button.TooltipText = sync_button_tooltip;
+
+            (Widget as HBox).PackStart (sync_button, false, false, 0);
+        }
+    }
+}
+
diff --git a/src/Clients/MainApp/FSpot.Gui.MetadataEditor/ui/MetadataEditorDialog.ui b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/ui/MetadataEditorDialog.ui
new file mode 100644
index 0000000..1585dff
--- /dev/null
+++ b/src/Clients/MainApp/FSpot.Gui.MetadataEditor/ui/MetadataEditorDialog.ui
@@ -0,0 +1,136 @@
+<?xml version="1.0"?>
+<interface>
+  <!-- interface-requires gtk+ 2.12 -->
+  <!-- interface-naming-policy toplevel-contextual -->
+  <object class="GtkAdjustment" id="adjustment1">
+    <property name="upper">100</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+    <property name="page_size">10</property>
+  </object>
+  <object class="GtkDialog" id="metadata_editor_dialog">
+    <property name="visible">True</property>
+    <property name="title" translatable="yes">Edit Photo Properties</property>
+    <property name="type_hint">dialog</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="dialog_vbox">
+        <property name="visible">True</property>
+        <child>
+          <object class="GtkHBox" id="dialog_content">
+            <property name="visible">True</property>
+            <property name="border_width">6</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkHBox" id="hbox81">
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkVBox" id="content_box">
+                    <property name="visible">True</property>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkFrame" id="tray_frame">
+                    <property name="visible">True</property>
+                    <property name="label_xalign">0</property>
+                    <property name="shadow_type">none</property>
+                    <child>
+                      <object class="GtkAlignment" id="photo_view_alignment">
+                        <property name="visible">True</property>
+                        <property name="left_padding">12</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                    </child>
+                    <child type="label">
+                      <object class="GtkLabel" id="label203">
+                        <property name="visible">True</property>
+                        <property name="label" translatable="yes">&lt;b&gt;Photos&lt;/b&gt;</property>
+                        <property name="use_markup">True</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkVBox" id="progressbar_placeholder">
+            <property name="visible">True</property>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <object class="GtkHButtonBox" id="dialog-action_area15">
+            <property name="visible">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="cancel_button">
+                <property name="label">gtk-cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="save_button">
+                <property name="label">gtk-save</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-6">cancel_button</action-widget>
+      <action-widget response="-5">save_button</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/src/Clients/MainApp/FSpot/MainWindow.cs b/src/Clients/MainApp/FSpot/MainWindow.cs
index 62dbb0d..5ea37d4 100644
--- a/src/Clients/MainApp/FSpot/MainWindow.cs
+++ b/src/Clients/MainApp/FSpot/MainWindow.cs
@@ -80,6 +80,7 @@ namespace FSpot
 
 		[GtkBeans.Builder.Object] Gtk.Action sharpen;
 		[GtkBeans.Builder.Object] Gtk.Action adjust_time;
+        [GtkBeans.Builder.Object] Gtk.Action edit_metadata;
 
 		[GtkBeans.Builder.Object] Gtk.Action update_thumbnail;
 		[GtkBeans.Builder.Object] Gtk.Action delete_from_drive;
@@ -1788,6 +1789,13 @@ namespace FSpot
 			(new AdjustTimeDialog (Database, list)).Run ();
 		}
 
+        void HandleEditMetadata (object sender, EventArgs args)
+        {
+            PhotoList list = new PhotoList (Selection.Items);
+            list.Sort (new IPhotoComparer.CompareDateName ());
+            new FSpot.Gui.MetadataEditor.EditorDialog (list).Run ();
+        }
+
 		public void HideLoupe ()
 		{
 			loupe_menu_item.Active = false;
@@ -2641,6 +2649,7 @@ namespace FSpot
 
 			set_as_background.Sensitive = single_active;
 			adjust_time.Sensitive = active_selection;
+            edit_metadata.Sensitive = active_selection;
 
 			attach_tag.Sensitive = active_selection;
 			remove_tag.Sensitive = active_selection;
diff --git a/src/Clients/MainApp/MainApp.csproj b/src/Clients/MainApp/MainApp.csproj
index 58a3fa4..d225d0d 100644
--- a/src/Clients/MainApp/MainApp.csproj
+++ b/src/Clients/MainApp/MainApp.csproj
@@ -205,6 +205,16 @@
     <Compile Include="FSpot.Widgets\SelectionCollectionGridView.cs" />
     <Compile Include="FSpot.Widgets\CollectionCellGridView.cs" />
     <Compile Include="FSpot.Widgets\BrowseablePointerGridView.cs" />
+    <Compile Include="FSpot.Gui.MetadataEditor\BasicEditorPage.cs" />
+    <Compile Include="FSpot.Gui.MetadataEditor\EditorController.cs" />
+    <Compile Include="FSpot.Gui.MetadataEditor\EditorDialog.cs" />
+    <Compile Include="FSpot.Gui.MetadataEditor\EditorPage.cs" />
+    <Compile Include="FSpot.Gui.MetadataEditor\EditorPhoto.cs" />
+    <Compile Include="FSpot.Gui.MetadataEditor\FieldEditorSyncButton.cs" />
+    <Compile Include="FSpot.Gui.MetadataEditor\IFieldEditor.cs" />
+    <Compile Include="FSpot.Gui.MetadataEditor\InfoEditorPresetWidget.cs" />
+    <Compile Include="FSpot.Gui.MetadataEditor\LabelledFieldEditorWidget.cs" />
+    <Compile Include="FSpot.Gui.MetadataEditor\LabelledSyncEditorWidget.cs" />
   </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="..\..\..\COPYING">
@@ -276,6 +286,9 @@
     <EmbeddedResource Include="FSpot.UI.Dialog\ui\SelectionRatioDialog.ui">
       <LogicalName>SelectionRatioDialog.ui</LogicalName>
     </EmbeddedResource>
+    <EmbeddedResource Include="FSpot.Gui.MetadataEditor\ui\MetadataEditorDialog.ui">
+      <LogicalName>MetadataEditorDialog.ui</LogicalName>
+    </EmbeddedResource>
   </ItemGroup>
   <ProjectExtensions>
     <MonoDevelop>
diff --git a/src/Clients/MainApp/Makefile.am b/src/Clients/MainApp/Makefile.am
index 4962f67..367316a 100644
--- a/src/Clients/MainApp/Makefile.am
+++ b/src/Clients/MainApp/Makefile.am
@@ -49,6 +49,16 @@ SOURCES =  \
 	FSpot.Filters/SharpFilter.cs \
 	FSpot.Filters/UniqueNameFilter.cs \
 	FSpot.Filters/WhiteListFilter.cs \
+	FSpot.Gui.MetadataEditor/BasicEditorPage.cs \
+	FSpot.Gui.MetadataEditor/EditorController.cs \
+	FSpot.Gui.MetadataEditor/EditorDialog.cs \
+	FSpot.Gui.MetadataEditor/EditorPage.cs \
+	FSpot.Gui.MetadataEditor/EditorPhoto.cs \
+	FSpot.Gui.MetadataEditor/FieldEditorSyncButton.cs \
+	FSpot.Gui.MetadataEditor/IFieldEditor.cs \
+	FSpot.Gui.MetadataEditor/InfoEditorPresetWidget.cs \
+	FSpot.Gui.MetadataEditor/LabelledFieldEditorWidget.cs \
+	FSpot.Gui.MetadataEditor/LabelledSyncEditorWidget.cs \
 	FSpot.Imaging/Ciff.cs \
 	FSpot.Imaging/DCRawFile.cs \
 	FSpot.Imaging/ImageFile.cs \
@@ -182,6 +192,7 @@ RESOURCES =  \
 	../../../icons/f-spot-128.png \
 	../../../icons/f-spot-not.png \
 	FSpot.addin.xml \
+	FSpot.Gui.MetadataEditor/ui/MetadataEditorDialog.ui \
 	FSpot.UI.Dialog/ui/AdjustTimeDialog.ui \
 	FSpot.UI.Dialog/ui/CreateTagDialog.ui \
 	FSpot.UI.Dialog/ui/DateRangeDialog.ui \
diff --git a/src/Clients/MainApp/ui/main_window.ui b/src/Clients/MainApp/ui/main_window.ui
index 5eabdd6..a3f058e 100644
--- a/src/Clients/MainApp/ui/main_window.ui
+++ b/src/Clients/MainApp/ui/main_window.ui
@@ -176,6 +176,13 @@
           </object>
         </child>
         <child>
+          <object class="GtkAction" id="edit_metadata">
+            <property name="name">edit_metadata</property>
+            <property name="label" translatable="yes">Edit Metadata</property>
+            <signal handler="HandleEditMetadata" name="activate"/>
+          </object>
+        </child>
+        <child>
           <object class="GtkAction" id="update_thumbnail">
             <property name="name">update_thumbnail</property>
             <property name="label" translatable="yes">Re_fresh Thumbnail</property>
@@ -609,6 +616,7 @@
           <separator/>
           <menuitem action="sharpen"/>
           <menuitem action="adjust_time"/>
+          <menuitem action="edit_metadata"/>
           <separator/>
           <menuitem action="update_thumbnail"/>
           <menuitem action="delete_from_drive"/>



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]