[f-spot/metadata-editor] Introduce MetadataEditor
- From: Mike Gemünde <mgemuende src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [f-spot/metadata-editor] Introduce MetadataEditor
- Date: Mon, 4 Oct 2010 12:08:34 +0000 (UTC)
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"><b>Photos</b></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]