[Pkg-cli-apps-commits] [SCM] f-spot branch, master, updated. debian/0.6.1.5-3-14-g486a410
Iain Lane
laney at ubuntu.com
Wed May 19 15:42:21 UTC 2010
The following commit has been merged in the master branch:
commit 12c8ba201bb3a11cfca7e12912ffc4691373f53a
Author: Iain Lane <laney at ubuntu.com>
Date: Wed May 19 13:31:27 2010 +0100
Steal and rebase Ubuntu patches
* debian/patches/ubuntu*: Steal patches from Ubuntu package to improve
--view mode and add an undo/redo stack. Rebase on new upstream version.
Thanks to Chris Halse Rogers.
diff --git a/debian/changelog b/debian/changelog
index 290e027..1b09154 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -45,6 +45,9 @@ f-spot (0.6.2-1) UNRELEASED; urgency=low
* debian/patches/*: Refresh to apply to new upstream version
* debian/rules: Pass include directories to autoreconf to have the correct
macros in scope for the new build system
+ * debian/patches/ubuntu*: Steal patches from Ubuntu package to improve
+ --view mode and add an undo/redo stack. Rebase on new upstream version.
+ Thanks to Chris Halse Rogers.
* debian/patches/ubuntu_fname_quote_percent.patch: Drop, now upstream.
-- Iain Lane <laney at ubuntu.com> Mon, 17 May 2010 11:59:58 +0100
diff --git a/debian/patches/series b/debian/patches/series
index 610be21..87364b2 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -7,7 +7,20 @@ debian_link-system-gnome-keyring.patch
debian_disable-unit-tests
debian_fix_f-spot.exe.config.patch
+# Patches from Ubuntu
ubuntu_importer-targetdir-selector.patch
ubuntu_fix_export_crash_FlickrRemote.cs.patch
ubuntu_nofuse_fix_photo_import.patch
ubuntu_xdg-photo-dir.patch
+
+ubuntu_add_editing_to_view_mode.patch
+ubuntu_fix_disposed_pixbuf_errors_in_adjustment.patch
+ubuntu_add_save_and_undo.patch
+ubuntu_add_filmstrip_browsing_to_view_mode.patch
+ubuntu_add_other_files_in_directory_in_view_mode.patch
+ubuntu_clear_selection_on_crop.patch
+ubuntu_only_update_timestamp_of_unprotected_versions.patch
+ubuntu_handle_broken_uris_to_view_mode.patch
+
+ubuntu_default_view_size.patch
+ubuntu_fix_folder_export_hang.patch
diff --git a/debian/patches/ubuntu_add_editing_to_view_mode.patch b/debian/patches/ubuntu_add_editing_to_view_mode.patch
new file mode 100644
index 0000000..e17ef1c
--- /dev/null
+++ b/debian/patches/ubuntu_add_editing_to_view_mode.patch
@@ -0,0 +1,341 @@
+Description: Enable editing in View mode
+Author: Christopher Halse Rogers <christopher.halse.rogers at canonical.com>
+Author: Ken VanDine <ken.vandine at canonical.com>
+Bug-Ubuntu: https://bugs.edge.launchpad.net/ubuntu/+source/f-spot/+bug/484888
+Bug: https://bugzilla.gnome.org/show_bug.cgi?id=513561
+
+=== modified file 'src/Editors/Editor.cs'
+Index: f-spot.git/src/Editors/Editor.cs
+===================================================================
+--- f-spot.git.orig/src/Editors/Editor.cs 2010-05-19 13:51:16.967958461 +0100
++++ f-spot.git/src/Editors/Editor.cs 2010-05-19 13:51:50.877959347 +0100
+@@ -18,6 +18,7 @@
+ using Mono.Unix;
+
+ using System;
++using System.IO;
+
+ namespace FSpot.Editors {
+ [ExtensionNode ("Editor")]
+@@ -61,6 +62,9 @@
+ public event ProcessingStepHandler ProcessingStep;
+ public event ProcessingFinishedHandler ProcessingFinished;
+
++ public PhotoImageView View { get; set; }
++ public Gtk.Window ParentWindow {get; set; }
++
+ // Contains the current selection, the items being edited, ...
+ private EditorState state;
+ public EditorState State {
+@@ -96,7 +100,7 @@
+ }
+
+
+- protected void LoadPhoto (Photo photo, out Pixbuf photo_pixbuf, out Cms.Profile photo_profile) {
++ protected void LoadPhoto (IBrowsableItem photo, out Pixbuf photo_pixbuf, out Cms.Profile photo_profile) {
+ // FIXME: We might get this value from the PhotoImageView.
+ using (ImageFile img = ImageFile.Create (photo.DefaultVersionUri)) {
+ photo_pixbuf = img.Load ();
+@@ -143,18 +147,29 @@
+ }
+
+ int done = 0;
+- foreach (Photo photo in State.Items) {
++ foreach (IBrowsableItem item in State.Items) {
+ Pixbuf input;
+ Cms.Profile input_profile;
+- LoadPhoto (photo, out input, out input_profile);
++ LoadPhoto (item, out input, out input_profile);
+
+ Pixbuf edited = Process (input, input_profile);
+ input.Dispose ();
+
+- bool create_version = photo.DefaultVersion.IsProtected;
+- photo.SaveVersion (edited, create_version);
+- photo.Changes.DataChanged = true;
+- App.Instance.Database.Photos.Commit (photo);
++ if (item is Photo) {
++ var photo = item as Photo;
++ bool create_version = photo.DefaultVersion.IsProtected;
++ photo.SaveVersion (edited, create_version);
++ photo.Changes.DataChanged = true;
++ App.Instance.Database.Photos.Commit (photo);
++ } else {
++ var pb = edited.Copy ();
++ using (ImageFile img = ImageFile.Create (item.DefaultVersionUri)) {
++ using (Stream stream = System.IO.File.OpenWrite (item.DefaultVersionUri.LocalPath)) {
++ img.Save (edited, stream);
++ }
++ }
++ State.PhotoImageView.Pixbuf = pb;
++ }
+
+ done++;
+ if (ProcessingStep != null) {
+@@ -205,7 +220,11 @@
+ Pixbuf previewed = ProcessFast (preview, null);
+ State.PhotoImageView.Pixbuf = previewed;
+ State.PhotoImageView.ZoomFit (false);
+- MainWindow.Toplevel.InfoBox.UpdateHistogram (previewed);
++ if (MainWindow.Toplevel != null) {
++ //MainWindow.Toplevel is null if we're in View mode.
++ //If we're in View mode we don't have a histogram, so we don't need to update it.
++ MainWindow.Toplevel.InfoBox.UpdateHistogram (previewed);
++ }
+
+ if (old_preview != null) {
+ old_preview.Dispose ();
+@@ -238,7 +257,11 @@
+ State.PhotoImageView.Pixbuf = original;
+ State.PhotoImageView.ZoomFit (false);
+
+- MainWindow.Toplevel.InfoBox.UpdateHistogram (null);
++ if (MainWindow.Toplevel != null) {
++ //MainWindow.Toplevel is null if we're in View mode.
++ //If we're in View mode we don't have a histogram, so we dont' need to update it.
++ MainWindow.Toplevel.InfoBox.UpdateHistogram (null);
++ }
+ }
+
+ Reset ();
+Index: f-spot.git/src/FSpot.addin.xml
+===================================================================
+--- f-spot.git.orig/src/FSpot.addin.xml 2010-05-19 13:51:16.927958582 +0100
++++ f-spot.git/src/FSpot.addin.xml 2010-05-19 13:51:50.877959347 +0100
+@@ -61,8 +61,8 @@
+
+ <Extension path = "/FSpot/Sidebar">
+ <SidebarPage sidebar_page_type = "FSpot.Widgets.MetadataDisplayPage" />
++ <SidebarPage sidebar_page_type = "FSpot.Widgets.EditorPage" />
+ <Condition id="ViewMode" mode="library">
+- <SidebarPage sidebar_page_type = "FSpot.Widgets.EditorPage" />
+ <SidebarPage sidebar_page_type = "FSpot.Widgets.FolderTreePage" />
+ </Condition>
+ </Extension>
+Index: f-spot.git/src/MainWindow.cs
+===================================================================
+--- f-spot.git.orig/src/MainWindow.cs 2010-05-19 13:51:16.867959974 +0100
++++ f-spot.git/src/MainWindow.cs 2010-05-19 13:51:50.877959347 +0100
+@@ -375,10 +375,8 @@
+
+ Sidebar.AppendPage (tag_selection_scrolled, Catalog.GetString ("Tags"), "tag");
+
+- AddinManager.AddExtensionNodeHandler ("/FSpot/Sidebar", OnSidebarExtensionChanged);
+-
+ Sidebar.Context = ViewContext.Library;
+-
++
+ Sidebar.CloseRequested += HideSidebar;
+ Sidebar.Show ();
+
+@@ -454,7 +452,7 @@
+ new FSpot.PreviewPopup (icon_view);
+
+ Gtk.Drag.SourceSet (icon_view, Gdk.ModifierType.Button1Mask | Gdk.ModifierType.Button3Mask,
+- icon_source_target_table, DragAction.Copy | DragAction.Move);
++ icon_source_target_table, DragAction.Copy | DragAction.Move);
+
+ icon_view.DragBegin += HandleIconViewDragBegin;
+ icon_view.DragDataGet += HandleIconViewDragDataGet;
+@@ -487,6 +485,9 @@
+ photo_view.UpdateFinished += HandlePhotoViewUpdateFinished;
+
+ photo_view.View.ZoomChanged += HandleZoomChanged;
++
++ // Sidebar extensions need access to PhotoView, so this has to be delayed until after we've constructed photo_view
++ AddinManager.AddExtensionNodeHandler ("/FSpot/Sidebar", OnSidebarExtensionChanged);
+
+ // Tag typing: focus the tag entry if the user starts typing a tag
+ icon_view.KeyPressEvent += HandlePossibleTagTyping;
+@@ -567,8 +568,12 @@
+
+ private void OnSidebarExtensionChanged (object s, ExtensionNodeEventArgs args) {
+ // FIXME: No sidebar page removal yet!
+- if (args.Change == ExtensionChange.Add)
+- Sidebar.AppendPage ((args.ExtensionNode as SidebarPageNode).GetPage ());
++ if (args.Change == ExtensionChange.Add) {
++ var page = ((SidebarPageNode)args.ExtensionNode).GetPage ();
++ page.PhotoImageView = PhotoView.View;
++ page.ParentWindow = Window;
++ Sidebar.AppendPage (page);
++ }
+ }
+
+ private Photo CurrentPhoto {
+Index: f-spot.git/src/SingleView.cs
+===================================================================
+--- f-spot.git.orig/src/SingleView.cs 2010-05-19 13:51:16.887960131 +0100
++++ f-spot.git/src/SingleView.cs 2010-05-19 13:51:50.877959347 +0100
+@@ -114,9 +114,6 @@
+ info_vbox.Add (sidebar);
+ sidebar.AppendPage (directory_scrolled, Catalog.GetString ("Folder"), "gtk-directory");
+
+- AddinManager.AddExtensionNodeHandler ("/FSpot/Sidebar", OnSidebarExtensionChanged);
+-
+- sidebar.Context = ViewContext.Single;
+
+ sidebar.CloseRequested += HandleHideSidePane;
+ sidebar.Show ();
+@@ -136,6 +133,9 @@
+
+ Window.ShowAll ();
+
++ AddinManager.AddExtensionNodeHandler ("/FSpot/Sidebar", OnSidebarExtensionChanged);
++ sidebar.Context = ViewContext.Single;
++
+ zoom_scale.ValueChanged += HandleZoomScaleValueChanged;
+
+ LoadPreference (Preferences.VIEWER_SHOW_TOOLBAR);
+@@ -143,7 +143,9 @@
+ LoadPreference (Preferences.VIEWER_TRANSPARENCY);
+ LoadPreference (Preferences.VIEWER_TRANS_COLOR);
+
+- ShowSidebar = collection.Count > 1;
++ // We always want to start by showing the sidebar to make it
++ // more obvious how to edit.
++ ShowSidebar = true;
+
+ LoadPreference (Preferences.VIEWER_SHOW_FILENAMES);
+
+@@ -176,8 +178,13 @@
+
+ private void OnSidebarExtensionChanged (object s, ExtensionNodeEventArgs args) {
+ // FIXME: No sidebar page removal yet!
+- if (args.Change == ExtensionChange.Add)
+- sidebar.AppendPage ((args.ExtensionNode as SidebarPageNode).GetPage ());
++ if (args.Change == ExtensionChange.Add) {
++ var page = (args.ExtensionNode as SidebarPageNode).GetPage ();
++ Log.Debug ("OnSidebarExtensionAdded {0} / {1} / {2}", page, image_view, window);
++ page.PhotoImageView = image_view;
++ page.ParentWindow = window;
++ sidebar.AppendPage (page);
++ }
+ }
+
+ void HandleExportActivated (object o, EventArgs e)
+Index: f-spot.git/src/Widgets/EditorPage.cs
+===================================================================
+--- f-spot.git.orig/src/Widgets/EditorPage.cs 2010-05-19 13:51:16.947959526 +0100
++++ f-spot.git/src/Widgets/EditorPage.cs 2010-05-19 13:51:50.877959347 +0100
+@@ -24,7 +24,7 @@
+ namespace FSpot.Widgets {
+ public class EditorPage : SidebarPage {
+ internal bool InPhotoView;
+- private readonly EditorPageWidget EditorPageWidget;
++ readonly EditorPageWidget EditorPageWidget;
+
+ public EditorPage () : base (new EditorPageWidget (),
+ Catalog.GetString ("Edit"),
+@@ -35,6 +35,16 @@
+ EditorPageWidget.Page = this;
+ }
+
++ public override PhotoImageView PhotoImageView {
++ get { return EditorPageWidget.PhotoImageView; }
++ set { EditorPageWidget.PhotoImageView = value; }
++ }
++
++ public override Gtk.Window ParentWindow {
++ get { return EditorPageWidget.ParentWindow; }
++ set { EditorPageWidget.ParentWindow = value ;}
++ }
++
+ protected override void AddedToSidebar () {
+ (Sidebar as Sidebar).SelectionChanged += delegate (IBrowsableCollection collection) { EditorPageWidget.ShowTools (); };
+ (Sidebar as Sidebar).ContextChanged += HandleContextChanged;
+@@ -42,18 +52,21 @@
+
+ private void HandleContextChanged (object sender, EventArgs args)
+ {
+- InPhotoView = ((Sidebar as Sidebar).Context == ViewContext.Edit);
++ InPhotoView = (Sidebar.Context == ViewContext.Edit) || (Sidebar.Context == ViewContext.Single);
+ EditorPageWidget.ChangeButtonVisibility ();
+ }
+ }
+
+ public class EditorPageWidget : ScrolledWindow {
+- private VBox widgets;
+- private VButtonBox buttons;
+- private Widget active_editor;
++ VBox widgets;
++ VButtonBox buttons;
++ Widget active_editor;
++
++ List<Editor> editors;
++ Editor current_editor;
+
+- private List<Editor> editors;
+- private Editor current_editor;
++ public PhotoImageView PhotoImageView { get; set;}
++ public Gtk.Window ParentWindow { get; set; }
+
+ // Used to make buttons insensitive when selecting multiple images.
+ private Dictionary<Editor, Button> editor_buttons;
+@@ -79,6 +92,8 @@
+ editor.ProcessingStarted += OnProcessingStarted;
+ editor.ProcessingStep += OnProcessingStep;
+ editor.ProcessingFinished += OnProcessingFinished;
++ editor.View = PhotoImageView;
++ editor.ParentWindow = ParentWindow;
+ editors.Add (editor);
+ PackButton (editor);
+ }
+@@ -87,7 +102,7 @@
+ private ProgressDialog progress;
+
+ private void OnProcessingStarted (string name, int count) {
+- progress = new ProgressDialog (name, ProgressDialog.CancelButtonType.None, count, MainWindow.Toplevel.Window);
++ progress = new ProgressDialog (name, ProgressDialog.CancelButtonType.None, count, ParentWindow);
+ }
+
+ private void OnProcessingStep (int done) {
+@@ -172,8 +187,7 @@
+ private bool SetupEditor (Editor editor) {
+ EditorState state = editor.CreateState ();
+
+- PhotoImageView photo_view = MainWindow.Toplevel.PhotoView.View;
+-
++ PhotoImageView photo_view = PhotoImageView;
+ if (Page.InPhotoView && photo_view != null) {
+ state.Selection = photo_view.Selection;
+ state.PhotoImageView = photo_view;
+@@ -197,7 +211,7 @@
+ string msg = Catalog.GetString ("No selection available");
+ string desc = Catalog.GetString ("This tool requires an active selection. Please select a region of the photo and try the operation again");
+
+- HigMessageDialog md = new HigMessageDialog (MainWindow.Toplevel.Window,
++ HigMessageDialog md = new HigMessageDialog (ParentWindow,
+ DialogFlags.DestroyWithParent,
+ Gtk.MessageType.Error, ButtonsType.Ok,
+ msg,
+@@ -218,7 +232,7 @@
+ string desc = String.Format (Catalog.GetString ("Received exception \"{0}\". Note that you have to develop RAW files into JPEG before you can edit them."),
+ e.Message);
+
+- HigMessageDialog md = new HigMessageDialog (MainWindow.Toplevel.Window,
++ HigMessageDialog md = new HigMessageDialog (ParentWindow,
+ DialogFlags.DestroyWithParent,
+ Gtk.MessageType.Error, ButtonsType.Ok,
+ msg,
+Index: f-spot.git/src/Extensions/SidebarPage.cs
+===================================================================
+--- f-spot.git.orig/src/Extensions/SidebarPage.cs 2010-05-19 13:51:16.907958803 +0100
++++ f-spot.git/src/Extensions/SidebarPage.cs 2010-05-19 13:52:47.296709401 +0100
+@@ -9,6 +9,7 @@
+ * This is free software. See COPYING for details.
+ */
+
++using FSpot.Widgets;
+ using FSpot.Extensions;
+ using FSpot.Utils;
+ using Gtk;
+@@ -64,6 +65,9 @@
+ // Can be overriden to get notified as soon as we're added to a sidebar.
+ protected virtual void AddedToSidebar () { }
+
++ public virtual PhotoImageView PhotoImageView { get; set; }
++ public virtual Gtk.Window ParentWindow { get; set; }
++
+ // // Whether this page is currently visible
+ // public bool IsActive {
+ // get { return Sidebar.IsActive (this); }
diff --git a/debian/patches/ubuntu_add_filmstrip_browsing_to_view_mode.patch b/debian/patches/ubuntu_add_filmstrip_browsing_to_view_mode.patch
new file mode 100644
index 0000000..817a81a
--- /dev/null
+++ b/debian/patches/ubuntu_add_filmstrip_browsing_to_view_mode.patch
@@ -0,0 +1,274 @@
+Description: Add a filmstrip and previous/next buttons to View mode
+ Without this, it's frustrating to switch between images to edit.
+Author: Christopher Halse Rogers <christopher.halse.rogers at canonical.com>
+Forwarded: No
+
+=== modified file 'src/PhotoImageView.cs'
+Index: f-spot.git/src/PhotoImageView.cs
+===================================================================
+--- f-spot.git.orig/src/PhotoImageView.cs 2010-05-19 14:28:58.127960001 +0100
++++ f-spot.git/src/PhotoImageView.cs 2010-05-19 14:28:58.147959007 +0100
+@@ -24,7 +24,7 @@
+ {
+ }
+
+- public PhotoImageView (IBrowsableCollection query, UndoCache history) : this(query)
++ public PhotoImageView (BrowsablePointer query, UndoCache history) : this(query)
+ {
+ history_cache = history;
+ }
+Index: f-spot.git/src/SingleView.cs
+===================================================================
+--- f-spot.git.orig/src/SingleView.cs 2010-05-19 14:28:58.127960001 +0100
++++ f-spot.git/src/SingleView.cs 2010-05-19 14:30:47.816709694 +0100
+@@ -21,6 +21,7 @@
+ [Glade.Widget] Gtk.ScrolledWindow image_scrolled;
+ [Glade.Widget] Gtk.HPaned info_hpaned;
+
++ [Glade.Widget] Gtk.VBox display_vbox;
+ Gtk.ScrolledWindow directory_scrolled;
+
+ [Glade.Widget] Gtk.CheckMenuItem side_pane_item;
+@@ -41,12 +42,17 @@
+
+ ToolButton rr_button, rl_button;
+ ToolButton undo;
++
++ ToolButton prev_image;
++ ToolButton next_image;
++ Label count_label;
+
+ Sidebar sidebar;
+
+ protected Glade.XML xml;
+ private Gtk.Window window;
+ PhotoImageView image_view;
++ Filmstrip filmstrip;
+ FSpot.Widgets.IconView directory_view;
+ private Uri uri;
+
+@@ -93,7 +99,6 @@
+ toolbar.Insert (undo, -1);
+
+ toolbar.Insert (new SeparatorToolItem (), -1);
+-
+
+ ToolButton fs_button = GtkUtil.ToolButtonFromTheme ("view-fullscreen", Catalog.GetString ("Fullscreen"), true);
+ fs_button.Clicked += HandleViewFullscreen;
+@@ -104,12 +109,33 @@
+ ss_button.Clicked += HandleViewSlideshow;
+ ss_button.SetTooltip (toolTips, Catalog.GetString ("View photos in a slideshow"), null);
+ toolbar.Insert (ss_button, -1);
++
++ SeparatorToolItem white_space = new SeparatorToolItem ();
++ white_space.Draw = false;
++ white_space.Expand = true;
++ toolbar.Insert (white_space, -1);
++
++ ToolItem label_item = new ToolItem ();
++ count_label = new Label (String.Empty);
++ label_item.Child = count_label;
++ toolbar.Insert (label_item, -1);
++
++ prev_image = new ToolButton (Stock.GoBack);
++ toolbar.Insert (prev_image, -1);
++ prev_image.TooltipText = Catalog.GetString ("Previous photo");
++ prev_image.Clicked += (sender, args) => { image_view.Item.MovePrevious (); };
++
++ next_image = new ToolButton (Stock.GoForward);
++ toolbar.Insert (next_image, -1);
++ next_image.TooltipText = Catalog.GetString ("Next photo");
++ next_image.Clicked += (sender, args) => { image_view.Item.MoveNext (); };
+
++
+ collection = new UriCollection (uris);
+ undo_history = new UndoCache ();
+ undo_history.UndoStateChanged += HandleUndoStateChanged;
+
+- TargetEntry [] dest_table = {
++ TargetEntry[] dest_table = {
+ FSpot.DragDropTargets.UriListEntry,
+ FSpot.DragDropTargets.PlainTextEntry
+ };
+@@ -118,12 +144,12 @@
+ directory_view.Selection.Changed += HandleSelectionChanged;
+ directory_view.DragDataReceived += HandleDragDataReceived;
+ Gtk.Drag.DestSet (directory_view, DestDefaults.All, dest_table,
+- DragAction.Copy | DragAction.Move);
++ DragAction.Copy | DragAction.Move);
+ directory_view.DisplayTags = false;
+ directory_view.DisplayDates = false;
+ directory_view.DisplayRatings = false;
+
+- directory_scrolled = new ScrolledWindow();
++ directory_scrolled = new ScrolledWindow ();
+ directory_scrolled.Add (directory_view);
+
+ sidebar = new Sidebar ();
+@@ -137,7 +163,8 @@
+
+ ThumbnailGenerator.Default.OnPixbufLoaded += delegate { directory_view.QueueDraw (); };
+
+- image_view = new PhotoImageView (collection, undo_history);
++ BrowsablePointer bp = new BrowsablePointer (collection, -1);
++ image_view = new PhotoImageView (bp, undo_history);
+ GtkUtil.ModifyColors (image_view);
+ GtkUtil.ModifyColors (image_scrolled);
+ image_view.ZoomChanged += HandleZoomChanged;
+@@ -145,9 +172,19 @@
+ image_view.ButtonPressEvent += HandleImageViewButtonPressEvent;
+ image_view.DragDataReceived += HandleDragDataReceived;
+ Gtk.Drag.DestSet (image_view, DestDefaults.All, dest_table,
+- DragAction.Copy | DragAction.Move);
++ DragAction.Copy | DragAction.Move);
+ image_scrolled.Add (image_view);
+
++ filmstrip = new Filmstrip (bp);
++ Gdk.Pixbuf bg = new Gdk.Pixbuf (Gdk.Colorspace.Rgb, true, 8, 1, 69);
++ bg.Fill (0x00000000);
++ filmstrip.BackgroundTile = bg;
++ filmstrip.ThumbOffset = 1;
++ filmstrip.Spacing = 4;
++ ToggleFilmstrip (true);
++ filmstrip.Visible = true;
++ GtkUtil.ModifyColors (filmstrip);
++
+ Window.ShowAll ();
+
+ AddinManager.AddExtensionNodeHandler ("/FSpot/Sidebar", OnSidebarExtensionChanged);
+@@ -184,6 +221,7 @@
+ image_view.Item.Collection.ItemsChanged += sidebar.HandleSelectionItemsChanged;
+
+ UpdateStatusLabel ();
++ UpdateToolbar (image_view.Item.Collection);
+
+ if (collection.Count > 0)
+ directory_view.Selection.Add (0);
+@@ -193,6 +231,28 @@
+ export.Activated += HandleExportActivated ;
+ }
+
++ void ToggleFilmstrip (bool enabled)
++ {
++ Gtk.Widget packed_filmstrip = display_vbox.AllChildren.Cast<Gtk.Widget> ().FirstOrDefault ((widget) => {return widget == filmstrip;});
++ if (enabled && packed_filmstrip == null) {
++ // Filmstrip isn't in the vbox, add it
++ Log.Debug ("Packing filmstrip");
++ display_vbox.PackStart (filmstrip, false, false, 0);
++ } else if (!enabled && packed_filmstrip != null) {
++ // Filmstrip is in the vbox, remove it
++ display_vbox.Remove (packed_filmstrip);
++ }
++ }
++
++ void UpdateToolbar (IBrowsableCollection collection)
++ {
++ // Note for translators: This indicates the current photo is photo {0} of {1} out of photos
++ count_label.Text = String.Format (Catalog.GetString ("{0} of {1}"),
++ collection.Count == 0 ? 0 : image_view.Item.Index + 1, collection.Count);
++ prev_image.Sensitive = collection.Count > 0 && image_view.Item.Index != 0;
++ next_image.Sensitive = collection.Count > 0 && image_view.Item.Index != image_view.Item.Collection.Count - 1;
++ }
++
+ void HandleUndoStateChanged (object sender, UndoStateChangedArgs e)
+ {
+ if (e.ChangedFor == image_view.Item.Current.DefaultVersionUri) {
+@@ -244,6 +304,7 @@
+ rotate_left.Sensitive = rotate_right.Sensitive = rr_button.Sensitive = rl_button.Sensitive = collection.Count != 0;
+
+ UpdateStatusLabel ();
++ UpdateToolbar (collection);
+ }
+
+ public bool ShowSidebar {
+@@ -308,6 +369,7 @@
+ undo.Sensitive = false;
+ }
+ UpdateStatusLabel ();
++ UpdateToolbar (image_view.Item.Collection);
+ }
+
+ void UpdateTitle ()
+@@ -370,8 +432,9 @@
+
+ private void HandleViewFilenames (object sender, System.EventArgs args)
+ {
+- directory_view.DisplayFilenames = filenames_item.Active;
++ directory_view.DisplayFilenames = filenames_item.Active;
+ UpdateStatusLabel ();
++ UpdateToolbar (image_view.Item.Collection);
+ }
+
+ private void HandleAbout (object sender, System.EventArgs args)
+@@ -672,6 +735,7 @@
+ {
+ if (PresentUnsavedChangesDialog ()) {
+ SavePreferences ();
++ filmstrip.Dispose ();
+ undo_history.Dispose ();
+ this.Window.Destroy ();
+ }
+@@ -714,6 +778,7 @@
+ if (PresentUnsavedChangesDialog ()) {
+ SavePreferences ();
+ undo_history.Dispose ();
++ filmstrip.Dispose ();
+ this.Window.Destroy ();
+ }
+ }
+Index: f-spot.git/src/f-spot.glade
+===================================================================
+--- f-spot.git.orig/src/f-spot.glade 2010-05-19 14:28:58.127960001 +0100
++++ f-spot.git/src/f-spot.glade 2010-05-19 14:28:58.147959007 +0100
+@@ -2699,15 +2699,28 @@
+ </packing>
+ </child>
+ <child>
+- <widget class="GtkScrolledWindow" id="image_scrolled">
++ <widget class="GtkVBox" id="display_vbox">
+ <property name="visible">True</property>
+- <property name="can_focus">True</property>
+- <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+- <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+- <property name="shadow_type">GTK_SHADOW_IN</property>
++ <property name="orientation">vertical</property>
+ <child>
+ <placeholder/>
+ </child>
++ <child>
++ <widget class="GtkScrolledWindow" id="image_scrolled">
++ <property name="visible">True</property>
++ <property name="can_focus">True</property>
++ <property name="hscrollbar_policy">automatic</property>
++ <property name="vscrollbar_policy">automatic</property>
++ <property name="shadow_type">in</property>
++ <child>
++ <placeholder/>
++ </child>
++ </widget>
++ <packing>
++ <property name="pack_type">end</property>
++ <property name="position">0</property>
++ </packing>
++ </child>
+ </widget>
+ <packing>
+ <property name="resize">True</property>
+Index: f-spot.git/src/Widgets/Filmstrip.cs
+===================================================================
+--- f-spot.git.orig/src/Widgets/Filmstrip.cs 2010-05-19 13:49:44.436739359 +0100
++++ f-spot.git/src/Widgets/Filmstrip.cs 2010-05-19 14:28:58.147959007 +0100
+@@ -587,8 +587,11 @@
+
+ protected override bool OnButtonPressEvent (EventButton evnt)
+ {
+- if (evnt.Button == 3)
+- return DrawOrientationMenu (evnt);
++ // We don't handle changing the orientation in View mode at this point
++ if (MainWindow.Toplevel != null) {
++ if (evnt.Button == 3)
++ return DrawOrientationMenu (evnt);
++ }
+
+ if (evnt.Button != 1 || (
+ (Orientation == Orientation.Horizontal && (evnt.X > filmstrip_end_pos || evnt.X < filmstrip_start_pos)) ||
diff --git a/debian/patches/ubuntu_add_other_files_in_directory_in_view_mode.patch b/debian/patches/ubuntu_add_other_files_in_directory_in_view_mode.patch
new file mode 100644
index 0000000..565cd37
--- /dev/null
+++ b/debian/patches/ubuntu_add_other_files_in_directory_in_view_mode.patch
@@ -0,0 +1,382 @@
+Description: Display all images in directories passed to --view
+ When a file is passed to --view, also view all the other images in the same
+ directory.
+Author: Christopher James Halse Rogers <christopher.halse.rogers at canonical.com>
+Bug-LP: https://bugs.edge.launchpad.net/ubuntu/+source/f-spot/+bug/484887
+Bug: https://bugzilla.gnome.org/show_bug.cgi?id=333189
+
+Index: f-spot.git/src/SingleView.cs
+===================================================================
+--- f-spot.git.orig/src/SingleView.cs 2010-05-19 14:28:58.147959007 +0100
++++ f-spot.git/src/SingleView.cs 2010-05-19 14:28:58.167959093 +0100
+@@ -60,6 +60,8 @@
+ UndoCache undo_history;
+
+ FullScreenView fsview;
++
++ List<FileSystemWatcher> directory_watchers;
+
+ private static Gtk.Tooltips toolTips = new Gtk.Tooltips ();
+
+@@ -68,28 +70,53 @@
+ string glade_name = "single_view";
+ this.uri = uris[0];
+
++ directory_watchers = new List<FileSystemWatcher> ();
++ // If we are passed the URI to a file we want to
++ // (a) Display & select that file, and
++ // (b) Display the other files in the same directory.
++ List<Uri> uris_to_display = new List<Uri> ();
++ foreach (var uri in uris) {
++ Uri candidate_uri;
++ if (!Directory.Exists (uri.LocalPath)) {
++ candidate_uri = uri.GetDirectoryUri ();
++ } else {
++ candidate_uri = uri;
++ }
++ if (!uris_to_display.Contains (candidate_uri)) {
++ uris_to_display.Add (candidate_uri);
++ var directory_watcher = new FileSystemWatcher (candidate_uri.LocalPath);
++ Log.Debug ("Watching directory {0} for changes.", candidate_uri.LocalPath);
++ directory_watcher.IncludeSubdirectories = false;
++ directory_watcher.Changed += HandleFileChangedEvent;
++ directory_watcher.Created += HandleFileCreatedEvent;
++ directory_watcher.Deleted += HandleFileDeletedEvent;
++ directory_watcher.EnableRaisingEvents = true;
++ directory_watchers.Add (directory_watcher);
++ }
++ }
++
+ xml = new Glade.XML (null, "f-spot.glade", glade_name, "f-spot");
+ xml.Autoconnect (this);
+ window = (Gtk.Window)xml.GetWidget (glade_name);
+-
++
+ LoadPreference (Preferences.VIEWER_WIDTH);
+ LoadPreference (Preferences.VIEWER_MAXIMIZED);
+-
++
+ Gtk.Toolbar toolbar = new Gtk.Toolbar ();
+ toolbar_hbox.PackStart (toolbar);
+-
++
+ rl_button = GtkUtil.ToolButtonFromTheme ("object-rotate-left", Catalog.GetString ("Rotate Left"), true);
+ rl_button.Clicked += HandleRotate270Command;
+ rl_button.SetTooltip (toolTips, Catalog.GetString ("Rotate photo left"), null);
+ toolbar.Insert (rl_button, -1);
+-
++
+ rr_button = GtkUtil.ToolButtonFromTheme ("object-rotate-right", Catalog.GetString ("Rotate Right"), true);
+ rr_button.Clicked += HandleRotate90Command;
+ rr_button.SetTooltip (toolTips, Catalog.GetString ("Rotate photo right"), null);
+ toolbar.Insert (rr_button, -1);
+-
++
+ toolbar.Insert (new SeparatorToolItem (), -1);
+-
++
+ StockItem undo_info = Stock.Lookup (Stock.Undo);
+ undo = new ToolButton (undo_info.StockId);
+ undo.Label = undo_info.Label;
+@@ -104,7 +131,7 @@
+ fs_button.Clicked += HandleViewFullscreen;
+ fs_button.SetTooltip (toolTips, Catalog.GetString ("View photos fullscreen"), null);
+ toolbar.Insert (fs_button, -1);
+-
++
+ ToolButton ss_button = GtkUtil.ToolButtonFromTheme ("media-playback-start", Catalog.GetString ("Slideshow"), true);
+ ss_button.Clicked += HandleViewSlideshow;
+ ss_button.SetTooltip (toolTips, Catalog.GetString ("View photos in a slideshow"), null);
+@@ -129,9 +156,10 @@
+ toolbar.Insert (next_image, -1);
+ next_image.TooltipText = Catalog.GetString ("Next photo");
+ next_image.Clicked += (sender, args) => { image_view.Item.MoveNext (); };
+-
+
+- collection = new UriCollection (uris);
++ collection = new UriCollection (uris_to_display.ToArray ());
++ collection.LoadingComplete += HandleCollectionLoadingComplete;
++
+ undo_history = new UndoCache ();
+ undo_history.UndoStateChanged += HandleUndoStateChanged;
+
+@@ -187,7 +215,7 @@
+
+ Window.ShowAll ();
+
+- AddinManager.AddExtensionNodeHandler ("/FSpot/Sidebar", OnSidebarExtensionChanged);
++ AddinManager.AddExtensionNodeHandler ("/FSpot/Sidebar", OnSidebarExtensionChanged);
+ sidebar.Context = ViewContext.Single;
+
+ zoom_scale.ValueChanged += HandleZoomScaleValueChanged;
+@@ -223,14 +251,119 @@
+ UpdateStatusLabel ();
+ UpdateToolbar (image_view.Item.Collection);
+
+- if (collection.Count > 0)
+- directory_view.Selection.Add (0);
++ if (collection.Count > 0) {
++ if (File.Exists (uris[0].AbsolutePath)) {
++ Log.Debug ("File {0} exists, displaying", uris[0].AbsolutePath);
++ var selected_image = collection.Items.FirstOrDefault ((item) => item.DefaultVersionUri == uris[0]);
++ if (selected_image != null) {
++ directory_view.Selection.Add (selected_image);
++ } else {
++ Log.Warning ("Unable to open requested file {0}", uris[0].AbsolutePath);
++ }
++ } else {
++ directory_view.Selection.Add (0);
++ }
++ }
+
+ export.Submenu = (Mono.Addins.AddinManager.GetExtensionNode ("/FSpot/Menus/Exports") as FSpot.Extensions.SubmenuNode).GetMenuItem (this).Submenu;
+ export.Submenu.ShowAll ();
+ export.Activated += HandleExportActivated ;
+ }
+
++ void HandleFileCreatedEvent (object sender, FileSystemEventArgs e)
++ {
++ Log.Debug ("Trying to add new file {0} to viewer", e.FullPath);
++ GLib.Timeout.Add (500, () =>
++ {
++ if (!File.Exists (e.FullPath)) {
++ // The file may have been deleted after being created, particularly if it was a temporary file
++ // for write => rename over save methods. In this case, we've got nothing to do now.
++ return false;
++ }
++ if ((DateTime.Now - File.GetLastWriteTime (e.FullPath)).TotalMilliseconds < 400) {
++ Log.Debug ("A process is still writing to {0}. Waiting another 500ms then trying again", e.FullPath);
++ return true;
++ }
++ collection.Add (new Uri (e.FullPath));
++ return false;
++ });
++ }
++
++ void HandleFileDeletedEvent (object sender, FileSystemEventArgs e)
++ {
++ Log.Debug ("File {0} deleted, removing references from viewer", e.FullPath);
++ Uri deleted_uri = new Uri (e.FullPath);
++ // File monitor events will come from an arbitrary thread. Proxy them to the main thread via the main loop to make things easier.
++ GLib.Timeout.Add(0, () => {
++ foreach (var deleted_item in collection.Items.Where ((item) => item.DefaultVersionUri == deleted_uri)) {
++ if (deleted_item != null) {
++ var current_item = image_view.Item.Current;
++ if (current_item == deleted_item) {
++ if (collection.Count == 1) {
++ // Someone's deleted our only file! Waaaa!
++ // The user can still edit this copy, though.
++ return false;
++ }
++ if (image_view.Item.Index < collection.Count - 1) {
++ // We're not at the last item, so we can move to the next one
++ current_item = collection[image_view.Item.Index + 1];
++ } else {
++ // We are at the last item. Move to the previous item
++ current_item = collection[image_view.Item.Index - 1];
++ }
++ }
++ while (undo_history.ContainsUndoInformationFor (deleted_uri)) {
++ undo_history.PopEdit (deleted_uri);
++ }
++ collection.Items = collection.Items.Where ((item) => item != deleted_item).ToArray ();
++ image_view.Item.MoveFirst ();
++ image_view.Item.Index = collection.IndexOf (current_item);
++ }
++ }
++ return false;
++ });
++ }
++
++ HashSet<string> file_change_processors = new HashSet<string> ();
++ void HandleFileChangedEvent (object sender, FileSystemEventArgs e)
++ {
++ Uri changed_uri = new Uri (e.FullPath);
++ var changed_item = collection.Items.FirstOrDefault ((item) => item.DefaultVersionUri == changed_uri);
++ if (changed_item != null) {
++ if (!file_change_processors.Contains (e.FullPath)) {
++ Log.Debug ("File {0} changed, triggering update.", e.FullPath);
++ file_change_processors.Add (e.FullPath);
++ GLib.Timeout.Add (500, () => {
++ if ((DateTime.Now - File.GetLastWriteTime (e.FullPath)).TotalMilliseconds < 400) {
++ Log.Debug ("A process is still writing to {0}. Waiting 500ms for it to finish", e.FullPath);
++ return true;
++ }
++ collection.MarkChanged (new BrowsableEventArgs (collection.IndexOf (changed_item), new FullInvalidate ()));
++ file_change_processors.Remove (e.FullPath);
++ return false;
++ });
++ }
++ }
++ }
++
++ void HandleCollectionLoadingComplete (object sender, EventArgs e)
++ {
++ if (collection.Count > 0) {
++ if (File.Exists (Uri.UnescapeDataString (uri.AbsolutePath))) {
++ Log.Debug ("{0} exists, displaying", uri);
++ var selected_image = collection.Items.FirstOrDefault ((item) => item.DefaultVersionUri == uri);
++
++ if (selected_image != null && directory_view != null) {
++ directory_view.Selection.Clear ();
++ directory_view.Selection.Add (selected_image);
++ }
++ } else {
++ Log.Warning ("Unable to open requested file {0}", uri);
++ }
++ }
++ collection.LoadingComplete -= HandleCollectionLoadingComplete;
++ }
++
+ void ToggleFilmstrip (bool enabled)
+ {
+ Gtk.Widget packed_filmstrip = display_vbox.AllChildren.Cast<Gtk.Widget> ().FirstOrDefault ((widget) => {return widget == filmstrip;});
+Index: f-spot.git/src/UriCollection.cs
+===================================================================
+--- f-spot.git.orig/src/UriCollection.cs 2010-05-19 13:49:44.436739359 +0100
++++ f-spot.git/src/UriCollection.cs 2010-05-19 14:28:58.167959093 +0100
+@@ -21,25 +21,31 @@
+
+ namespace FSpot {
+ public class UriCollection : PhotoList {
+- public UriCollection () : base (new IBrowsableItem [0])
++ public UriCollection () : base(new IBrowsableItem[0])
+ {
++ loading_count = 0;
+ }
+
+- public UriCollection (System.IO.FileInfo [] files) : this ()
++ public UriCollection (System.IO.FileInfo[] files) : this()
+ {
++ loading_count = 0;
++ LoaderStarting ();
+ LoadItems (files);
+ }
+
+- public UriCollection (Uri [] uri) : this ()
++ public UriCollection (Uri[] uri) : this()
+ {
++ loading_count = 0;
+ LoadItems (uri);
+ }
+
+ public void Add (Uri uri)
+ {
++ LoaderStarting ();
+ if (FSpot.ImageFile.HasLoader (uri)) {
+ //Console.WriteLine ("using image loader {0}", uri.ToString ());
+ Add (new FileBrowsableItem (uri));
++ LoaderComplete ();
+ } else {
+ GLib.FileInfo info = FileFactory.NewForUri (uri).QueryInfo ("standard::type,standard::content-type", FileQueryInfoFlags.None, null);
+
+@@ -57,13 +63,37 @@
+ }
+ }
+
+- public void LoadItems (Uri [] uris)
++ public void LoadItems (Uri[] uris)
+ {
++ // Prime the count with 1, so that if everything is synchronous the LoadingCount-- at the end
++ // will trigger LoadingComplete.
++ LoaderStarting ();
+ foreach (Uri uri in uris) {
+ Add (uri);
+ }
++ LoaderComplete ();
+ }
+
++ public event EventHandler LoadingComplete;
++
++ int loading_count;
++ void LoaderStarting ()
++ {
++ System.Threading.Interlocked.Increment (ref loading_count);
++ }
++
++ void LoaderComplete ()
++ {
++ if (System.Threading.Interlocked.Decrement (ref loading_count) == 0) {
++ FSpot.Utils.Log.Debug ("Final loader finished, triggering LoadingComplete signal");
++ var handler = LoadingComplete;
++ if (handler != null) {
++ handler (this, EventArgs.Empty);
++ }
++ }
++ }
++
++
+ private class RssLoader
+ {
+ public RssLoader (UriCollection collection, System.Uri uri)
+@@ -74,15 +104,15 @@
+ ns.AddNamespace ("media", "http://search.yahoo.com/mrss/");
+ ns.AddNamespace ("pheed", "http://www.pheed.com/pheed/");
+ ns.AddNamespace ("apple", "http://www.apple.com/ilife/wallpapers");
+-
++
+ ArrayList items = new ArrayList ();
+ XmlNodeList list = doc.SelectNodes ("/rss/channel/item/media:content", ns);
+ foreach (XmlNode item in list) {
+- Uri image_uri = new Uri (item.Attributes ["url"].Value);
++ Uri image_uri = new Uri (item.Attributes["url"].Value);
+ System.Console.WriteLine ("flickr uri = {0}", image_uri.ToString ());
+ items.Add (new FileBrowsableItem (image_uri));
+ }
+-
++
+ if (list.Count < 1) {
+ list = doc.SelectNodes ("/rss/channel/item/pheed:imgsrc", ns);
+ foreach (XmlNode item in list) {
+@@ -91,7 +121,7 @@
+ items.Add (new FileBrowsableItem (image_uri));
+ }
+ }
+-
++
+ if (list.Count < 1) {
+ list = doc.SelectNodes ("/rss/channel/item/apple:image", ns);
+ foreach (XmlNode item in list) {
+@@ -100,7 +130,8 @@
+ items.Add (new FileBrowsableItem (image_uri));
+ }
+ }
+- collection.Add (items.ToArray (typeof (FileBrowsableItem)) as FileBrowsableItem []);
++ collection.Add (items.ToArray (typeof(FileBrowsableItem)) as FileBrowsableItem[]);
++ collection.LoaderComplete ();
+ }
+ }
+
+@@ -120,7 +151,6 @@
+ 500,
+ null,
+ InfoLoaded);
+-
+ }
+
+ void InfoLoaded (GLib.Object o, GLib.AsyncResult res)
+@@ -132,13 +162,14 @@
+ if (FSpot.ImageFile.HasLoader (i))
+ items.Add (new FileBrowsableItem (i));
+ }
+- Gtk.Application.Invoke (items, System.EventArgs.Empty, delegate (object sender, EventArgs args) {
++ Gtk.Application.Invoke (items, System.EventArgs.Empty, delegate(object sender, EventArgs args) {
+ collection.Add (items.ToArray ());
++ collection.LoaderComplete ();
+ });
+ }
+ }
+
+- protected void LoadItems (System.IO.FileInfo [] files)
++ protected void LoadItems (System.IO.FileInfo[] files)
+ {
+ List<IBrowsableItem> items = new List<IBrowsableItem> ();
+ foreach (var f in files) {
+@@ -149,6 +180,7 @@
+ }
+
+ list = items;
++ LoaderComplete ();
+ this.Reload ();
+ }
+ }
diff --git a/debian/patches/ubuntu_add_save_and_undo.patch b/debian/patches/ubuntu_add_save_and_undo.patch
new file mode 100644
index 0000000..9421c94
--- /dev/null
+++ b/debian/patches/ubuntu_add_save_and_undo.patch
@@ -0,0 +1,921 @@
+Description: Add Save & Undo to editing in View mode
+Author: Christopher Halse Rogers <christopher.halse.rogers at canonical.com>
+Forwarded: No
+
+=== modified file 'src/Editors/Editor.cs'
+Index: f-spot.git/src/Editors/Editor.cs
+===================================================================
+--- f-spot.git.orig/src/Editors/Editor.cs 2010-05-19 14:28:58.077959584 +0100
++++ f-spot.git/src/Editors/Editor.cs 2010-05-19 14:28:58.117959302 +0100
+@@ -18,7 +18,6 @@
+ using Mono.Unix;
+
+ using System;
+-using System.IO;
+
+ namespace FSpot.Editors {
+ [ExtensionNode ("Editor")]
+@@ -50,6 +49,9 @@
+ public bool InBrowseMode {
+ get { return PhotoImageView == null; }
+ }
++
++ // Undo history
++ public UndoCache History;
+ }
+
+ // This is the base class from which all editors inherit.
+@@ -100,11 +102,31 @@
+ }
+
+
+- protected void LoadPhoto (IBrowsableItem photo, out Pixbuf photo_pixbuf, out Cms.Profile photo_profile) {
+- // FIXME: We might get this value from the PhotoImageView.
+- using (ImageFile img = ImageFile.Create (photo.DefaultVersionUri)) {
+- photo_pixbuf = img.Load ();
+- photo_profile = img.GetProfile ();
++ protected void LoadPhoto (IBrowsableItem photo, out Pixbuf photo_pixbuf, out Cms.Profile photo_profile)
++ {
++ if (State.History != null) {
++ if (State.History.ContainsImageFor (photo.DefaultVersionUri)) {
++ photo_pixbuf = State.History.Pixbuf (photo.DefaultVersionUri);
++ photo_profile = State.History.Profile (photo.DefaultVersionUri);
++ return;
++ }
++ // FIXME: We might get this value from the PhotoImageView.
++ PixbufOrientation orientation;
++ using (ImageFile img = ImageFile.Create (photo.DefaultVersionUri)) {
++ photo_pixbuf = img.Load ();
++ orientation = img.Orientation;
++ var temp = FSpot.Utils.PixbufUtils.TransformOrientation (photo_pixbuf, FSpot.Utils.PixbufUtils.ReverseTransformation (orientation));
++ photo_pixbuf.Dispose ();
++ photo_pixbuf = temp;
++ photo_profile = img.GetProfile ();
++ }
++ State.History.InitialiseImage (photo.DefaultVersionUri, photo_pixbuf, orientation, photo_profile);
++ } else {
++ // FIXME: We might get this value from the PhotoImageView.
++ using (ImageFile img = ImageFile.Create (photo.DefaultVersionUri)) {
++ photo_pixbuf = img.Load ();
++ photo_profile = img.GetProfile ();
++ }
+ }
+ }
+
+@@ -141,7 +163,8 @@
+ }
+ }
+
+- private void TryApply () {
++ private void TryApply ()
++ {
+ if (NeedsSelection && !State.HasSelection) {
+ throw new Exception ("Cannot apply without selection!");
+ }
+@@ -152,9 +175,27 @@
+ Cms.Profile input_profile;
+ LoadPhoto (item, out input, out input_profile);
+
++ if (State.History != null) {
++ // If we're loading from history, then we've got the pixbuf in the original orientation
++ // and are transforming it on display. We need to transform it before editing.
++ var transformed_input = Utils.PixbufUtils.TransformOrientation (input, State.History.Orientation (item.DefaultVersionUri));
++ input.Dispose ();
++ input = transformed_input;
++ }
++
+ Pixbuf edited = Process (input, input_profile);
+ input.Dispose ();
+-
++
++
++ if (State.History != null) {
++ // And now we need to reverse the transformation, so that the pixbuf remains in the
++ // original orientation.
++ var transformed_edited = Utils.PixbufUtils.TransformOrientation (edited,
++ Utils.PixbufUtils.ReverseTransformation (State.History.Orientation (item.DefaultVersionUri)));
++ edited.Dispose ();
++ edited = transformed_edited;
++ }
++
+ if (item is Photo) {
+ var photo = item as Photo;
+ bool create_version = photo.DefaultVersion.IsProtected;
+@@ -162,13 +203,10 @@
+ photo.Changes.DataChanged = true;
+ App.Instance.Database.Photos.Commit (photo);
+ } else {
+- var pb = edited.Copy ();
+- using (ImageFile img = ImageFile.Create (item.DefaultVersionUri)) {
+- using (Stream stream = System.IO.File.OpenWrite (item.DefaultVersionUri.LocalPath)) {
+- img.Save (edited, stream);
+- }
+- }
+- State.PhotoImageView.Pixbuf = pb;
++ State.History.PushEdit (item.DefaultVersionUri, edited);
++ Pixbuf prev = State.PhotoImageView.Pixbuf;
++ State.PhotoImageView.Pixbuf = edited;
++ prev.Dispose ();
+ }
+
+ done++;
+Index: f-spot.git/src/Makefile.am
+===================================================================
+--- f-spot.git.orig/src/Makefile.am 2010-05-19 14:27:22.516709468 +0100
++++ f-spot.git/src/Makefile.am 2010-05-19 14:28:58.127960001 +0100
+@@ -59,6 +59,7 @@
+ $(srcdir)/Utils/Log.cs \
+ $(srcdir)/Utils/PixbufOrientation.cs \
+ $(srcdir)/Utils/PixbufUtils.cs \
++ $(srcdir)/Utils/UndoCache.cs \
+ $(srcdir)/Utils/Unix.cs \
+ $(srcdir)/Utils/UriExtensions.cs \
+ $(srcdir)/Utils/UriUtils.cs
+@@ -343,6 +344,7 @@
+ -pkg:gnome-sharp-2.0 \
+ -r:Mono.Posix \
+ -r:Mono.Cairo \
++ -r:Cms.dll \
+ $(GCONF_PKG)
+
+ JOBSCHEDULER_ASSEMBLIES = \
+@@ -502,7 +504,7 @@
+
+ FSpot.Utils.dll.mdb: FSpot.Utils.dll
+
+-FSpot.Utils.dll: $(UTILS_CSFILES)
++FSpot.Utils.dll: $(UTILS_CSFILES) Cms.dll
+ @echo -e "\n*** Compiling $@"
+ $(CSC_LIB) -out:$@ $(EXTRAFLAGS) $(UTILS_CSFILES) $(UTILS_ASSEMBLIES)
+
+Index: f-spot.git/src/PhotoImageView.cs
+===================================================================
+--- f-spot.git.orig/src/PhotoImageView.cs 2010-05-19 14:27:22.536710442 +0100
++++ f-spot.git/src/PhotoImageView.cs 2010-05-19 14:30:51.287958810 +0100
+@@ -20,9 +20,14 @@
+ namespace FSpot.Widgets {
+ public class PhotoImageView : ImageView {
+ #region public API
+- public PhotoImageView (IBrowsableCollection query) : this (new BrowsablePointer (query, -1))
++ public PhotoImageView (IBrowsableCollection query) : this(new BrowsablePointer (query, -1))
+ {
+ }
++
++ public PhotoImageView (IBrowsableCollection query, UndoCache history) : this(query)
++ {
++ history_cache = history;
++ }
+
+ public PhotoImageView (BrowsablePointer item) : base ()
+ {
+@@ -166,8 +171,25 @@
+ #region loader
+ uint timer;
+ IImageLoader loader;
++ UndoCache history_cache;
+ void Load (Uri uri)
+ {
++ if (history_cache != null && history_cache.ContainsImageFor (uri)) {
++ // We can get the pixmap faster out of the cache. Just load up the orientation from the file
++ using (ImageFile img = ImageFile.Create (uri)) {
++ PixbufOrientation = Accelerometer.GetViewOrientation (img.Orientation);
++ }
++ Pixbuf prev = Pixbuf;
++ Pixbuf = history_cache.Pixbuf (uri);
++ if (prev != null) {
++ prev.Dispose ();
++ }
++ ZoomFit ();
++ Gdk.Rectangle area = ImageCoordsToWindow (new Gdk.Rectangle (0, 0, Pixbuf.Width, Pixbuf.Height));
++ QueueDrawArea (area.X, area.Y, area.Width, area.Height);
++ return;
++ }
++
+ timer = Log.DebugTimerStart ();
+ if (loader != null)
+ loader.Dispose ();
+Index: f-spot.git/src/SingleView.cs
+===================================================================
+--- f-spot.git.orig/src/SingleView.cs 2010-05-19 14:28:58.077959584 +0100
++++ f-spot.git/src/SingleView.cs 2010-05-19 14:30:51.267959290 +0100
+@@ -1,6 +1,8 @@
+ using Gtk;
+ using Gdk;
+ using System;
++using System.IO;
++using System.Linq;
+ using System.Collections.Generic;
+
+ using Mono.Addins;
+@@ -38,6 +40,7 @@
+ [Glade.Widget] ImageMenuItem rotate_right;
+
+ ToolButton rr_button, rl_button;
++ ToolButton undo;
+
+ Sidebar sidebar;
+
+@@ -48,19 +51,20 @@
+ private Uri uri;
+
+ UriCollection collection;
++ UndoCache undo_history;
+
+ FullScreenView fsview;
+
+ private static Gtk.Tooltips toolTips = new Gtk.Tooltips ();
+
+- public SingleView (Uri [] uris)
++ public SingleView (Uri[] uris)
+ {
+ string glade_name = "single_view";
+- this.uri = uris [0];
++ this.uri = uris[0];
+
+ xml = new Glade.XML (null, "f-spot.glade", glade_name, "f-spot");
+ xml.Autoconnect (this);
+- window = (Gtk.Window) xml.GetWidget (glade_name);
++ window = (Gtk.Window)xml.GetWidget (glade_name);
+
+ LoadPreference (Preferences.VIEWER_WIDTH);
+ LoadPreference (Preferences.VIEWER_MAXIMIZED);
+@@ -80,6 +84,17 @@
+
+ toolbar.Insert (new SeparatorToolItem (), -1);
+
++ StockItem undo_info = Stock.Lookup (Stock.Undo);
++ undo = new ToolButton (undo_info.StockId);
++ undo.Label = undo_info.Label;
++ undo.IsImportant = true;
++ undo.UseUnderline = true;
++ undo.Clicked += HandleUndoClicked;
++ toolbar.Insert (undo, -1);
++
++ toolbar.Insert (new SeparatorToolItem (), -1);
++
++
+ ToolButton fs_button = GtkUtil.ToolButtonFromTheme ("view-fullscreen", Catalog.GetString ("Fullscreen"), true);
+ fs_button.Clicked += HandleViewFullscreen;
+ fs_button.SetTooltip (toolTips, Catalog.GetString ("View photos fullscreen"), null);
+@@ -91,6 +106,8 @@
+ toolbar.Insert (ss_button, -1);
+
+ collection = new UriCollection (uris);
++ undo_history = new UndoCache ();
++ undo_history.UndoStateChanged += HandleUndoStateChanged;
+
+ TargetEntry [] dest_table = {
+ FSpot.DragDropTargets.UriListEntry,
+@@ -120,7 +137,7 @@
+
+ ThumbnailGenerator.Default.OnPixbufLoaded += delegate { directory_view.QueueDraw (); };
+
+- image_view = new PhotoImageView (collection);
++ image_view = new PhotoImageView (collection, undo_history);
+ GtkUtil.ModifyColors (image_view);
+ GtkUtil.ModifyColors (image_scrolled);
+ image_view.ZoomChanged += HandleZoomChanged;
+@@ -176,6 +193,23 @@
+ export.Activated += HandleExportActivated ;
+ }
+
++ void HandleUndoStateChanged (object sender, UndoStateChangedArgs e)
++ {
++ if (e.ChangedFor == image_view.Item.Current.DefaultVersionUri) {
++ undo.Sensitive = undo_history.ContainsUndoInformationFor (e.ChangedFor);
++ UpdateTitle ();
++ }
++ }
++
++ void HandleUndoClicked (object sender, EventArgs e)
++ {
++ undo_history.PopEdit (image_view.Item.Current.DefaultVersionUri);
++ Pixbuf prev = image_view.Pixbuf;
++ image_view.Pixbuf = undo_history.Pixbuf (image_view.Item.Current.DefaultVersionUri);
++ prev.Dispose ();
++ undo.Sensitive = undo_history.ContainsUndoInformationFor (image_view.Item.Current.DefaultVersionUri);
++ }
++
+ private void OnSidebarExtensionChanged (object s, ExtensionNodeEventArgs args) {
+ // FIXME: No sidebar page removal yet!
+ if (args.Change == ExtensionChange.Add) {
+@@ -184,6 +218,11 @@
+ page.PhotoImageView = image_view;
+ page.ParentWindow = window;
+ sidebar.AppendPage (page);
++ if (page is EditorPage) {
++ // Pass undo history to Editors
++ ((EditorPage)page).History = undo_history;
++ sidebar.SwitchTo (page.Label);
++ }
+ }
+ }
+
+@@ -255,23 +294,41 @@
+ collection.MarkChanged (image_view.Item.Index, FullInvalidate.Instance);
+ }
+
+- private void HandleSelectionChanged (FSpot.IBrowsableCollection selection)
++ private void HandleSelectionChanged (FSpot.IBrowsableCollection selection)
+ {
+
+ if (selection.Count > 0) {
+ image_view.Item.Index = ((FSpot.Widgets.IconView.SelectionCollection)selection).Ids[0];
+-
++
+ zoom_scale.Value = image_view.NormalizedZoom;
+ }
++ if (selection.Count == 1) {
++ undo.Sensitive = undo_history.ContainsUndoInformationFor (image_view.Item.Current.DefaultVersionUri);
++ } else {
++ undo.Sensitive = false;
++ }
+ UpdateStatusLabel ();
+ }
+
++ void UpdateTitle ()
++ {
++ if (image_view.Item.IsValid) {
++ Window.Title = String.Format ("{0}{1} - F-Spot View",
++ image_view.Item.Current.Name,
++ undo_history.ContainsUndoInformationFor (image_view.Item.Current.DefaultVersionUri) ? "*" : "");
++ } else {
++ Window.Title = "F-Spot View";
++ }
++ }
++
+ private void HandleItemChanged (object sender, BrowsablePointerChangedEventArgs old)
+ {
+ BrowsablePointer pointer = sender as BrowsablePointer;
+ if (pointer == null)
+ return;
+
++ UpdateTitle ();
++
+ directory_view.FocusCell = pointer.Index;
+ directory_view.Selection.Clear ();
+ if (collection.Count > 0) {
+@@ -343,6 +400,115 @@
+ Open (FileChooserAction.Open);
+ }
+
++ void SafeSaveFile (Uri original, Uri saveTo, Pixbuf image, PixbufOrientation orientation)
++ {
++ //Get file permissions... (mkstemp does not keep permissions or ownership)
++ Mono.Unix.Native.Stat stat;
++ int stat_err = Mono.Unix.Native.Syscall.stat (original.LocalPath, out stat);
++
++ string temp_path = saveTo.LocalPath;
++ using (ImageFile img = ImageFile.Create (original)) {
++ using (Stream stream = FSpot.Utils.Unix.MakeSafeTemp (ref temp_path)) {
++ img.Save (image, stream);
++ }
++ if (img is JpegFile) {
++ ((JpegFile)img).SetOrientation (orientation);
++ Log.Debug ("Saving orientation {0}", orientation);
++ ((JpegFile)img).SaveMetaData (temp_path);
++ }
++ }
++ File.SetAttributes (temp_path, File.GetAttributes (original.LocalPath));
++
++ if (FSpot.Utils.Unix.Rename (temp_path, saveTo.LocalPath) < 0) {
++ System.IO.File.Delete (temp_path);
++ throw new System.Exception (System.String.Format ("Unable to rename {0} to {1}",
++ temp_path, saveTo.LocalPath));
++ }
++
++ //Set file permissions and gid...
++ if (stat_err == 0) {
++ try {
++ Mono.Unix.Native.Syscall.chmod (saveTo.LocalPath, stat.st_mode |
++ Mono.Unix.Native.FilePermissions.S_IRUSR |
++ Mono.Unix.Native.FilePermissions.S_IWUSR);
++ Mono.Unix.Native.Syscall.chown (saveTo.LocalPath, Mono.Unix.Native.Syscall.getuid (), stat.st_gid);
++ } catch (Exception) {
++ }
++ }
++ }
++
++ private void HandleSave (object sender, System.EventArgs args)
++ {
++ IBrowsableItem current_item = image_view.Item.Current;
++ if (!undo_history.ChangedUris ().Contains (current_item.DefaultVersionUri)) {
++ Log.Debug ("Not saving unchanged file: {0}", current_item.DefaultVersionUri.AbsoluteUri);
++ return;
++ }
++ Pixbuf current_image = undo_history.Pixbuf (current_item.DefaultVersionUri);
++ PixbufOrientation current_orientation = undo_history.Orientation (current_item.DefaultVersionUri);
++
++ SafeSaveFile (current_item.DefaultVersionUri, current_item.DefaultVersionUri, current_image, current_orientation);
++
++ Log.Debug ("Saved changes to {0}", current_item.DefaultVersionUri.LocalPath);
++ undo_history.MarkImageSaved (current_item.DefaultVersionUri);
++ directory_view.UpdateThumbnail (collection.IndexOf (current_item));
++ }
++
++ private void HandleSaveAs (object sender, System.EventArgs args)
++ {
++ IBrowsableItem current_item = image_view.Item.Current;
++ Pixbuf current_image;
++ PixbufOrientation current_orientation;
++ if (!undo_history.ContainsImageFor (current_item.DefaultVersionUri)) {
++ //TODO: This should probably load from the image file, instead.
++ //TODO: Or possibly just do a filesystem copy?
++ current_image = image_view.Pixbuf;
++ current_orientation = image_view.PixbufOrientation;
++ } else {
++ current_image = undo_history.Pixbuf (current_item.DefaultVersionUri);
++ current_orientation = undo_history.Orientation (current_item.DefaultVersionUri);
++ }
++ FileChooserDialog chooser = new FileChooserDialog (Catalog.GetString ("Save As..."),
++ window,
++ FileChooserAction.Save);
++
++ chooser.AddButton (Stock.Cancel, ResponseType.Cancel);
++ chooser.AddButton (Stock.Save, ResponseType.Ok);
++
++ // TODO: In an ideal world, this would be using GIO and we wouldn't have the "local only" constraint.
++ chooser.LocalOnly = true;
++ chooser.DoOverwriteConfirmation = true;
++
++ chooser.SelectUri (current_item.DefaultVersionUri.AbsoluteUri);
++ int response = chooser.Run ();
++
++ if ((ResponseType)response == ResponseType.Ok) {
++ Uri saveTo = new Uri (chooser.Uri);
++ SafeSaveFile (current_item.DefaultVersionUri, saveTo, current_image, current_orientation);
++
++ Log.Debug ("Saved changes to {0} to {1}", current_item.DefaultVersionUri.AbsoluteUri, chooser.Filename);
++
++ if (current_item.DefaultVersionUri != saveTo) {
++ // We're saving to a new file name
++ if (undo_history.ContainsUndoInformationFor (current_item.DefaultVersionUri)) {
++ undo_history.Clear (current_item.DefaultVersionUri);
++ }
++
++ Uri new_item_uri = new Uri (chooser.Uri);
++ collection.Add (new_item_uri);
++ // We know that chooser.Uri points to a local file, so collection.Add will be synchronous
++ var new_item = collection.Items.FirstOrDefault ((item) => item.DefaultVersionUri == new_item_uri);
++ directory_view.Selection.Clear ();
++ directory_view.Selection.Add (new_item);
++ } else if (undo_history.ContainsUndoInformationFor (saveTo)) {
++ // We've decided to save to the original file.
++ undo_history.MarkImageSaved (saveTo);
++ }
++ }
++
++ chooser.Destroy ();
++ }
++
+ private void Open (FileChooserAction action)
+ {
+ string title = Catalog.GetString ("Open");
+@@ -437,10 +603,78 @@
+ popup_menu.Popup (null, null, null, 0, Gtk.Global.CurrentEventTime);
+ }
+
++
++ bool PresentUnsavedChangesDialog ()
++ {
++ var changed_uris = undo_history.ChangedUris ().ToList ();
++ if (changed_uris.Count == 0) {
++ // We've got no changed files, so we can just allow the quit to proceed.
++ return true;
++ }
++
++ System.Text.StringBuilder changed_filenames = new System.Text.StringBuilder (GLib.Markup.EscapeText (Uri.UnescapeDataString (changed_uris[0].GetFilename ())));
++ for (int i = 1; i < changed_uris.Count; ++i) {
++ changed_filenames.Append ("\n");
++ changed_filenames.Append (GLib.Markup.EscapeText (Uri.UnescapeDataString (changed_uris[i].GetFilename ())));
++ }
++ string message;
++ message = Catalog.GetPluralString ("Save changes to image: {0} before closing?", "Save changes to images:\n{0}\nbefore closing?", changed_uris.Count);
++ var save_dialogue = new HigMessageDialog (Window,
++ DialogFlags.Modal,
++ MessageType.Warning,
++ ButtonsType.None,
++ String.Format (message, changed_filenames),
++ Catalog.GetPluralString ("If you don't save, the changes you have made to this image will be permanently lost",
++ "If you don't save, the changes you have made to these images will be permanently lost",
++ changed_uris.Count));
++
++ Gtk.Button button = new Gtk.Button ();
++ button.Label = Catalog.GetString ("Discard changes");
++ button.CanDefault = true;
++ button.Show ();
++ save_dialogue.AddActionWidget (button, ResponseType.Close);
++
++ button = new Gtk.Button ();
++ button.Label = Catalog.GetString ("Continue editing");
++ button.CanDefault = true;
++ button.Show ();
++ save_dialogue.AddActionWidget (button, ResponseType.Cancel);
++
++ button = new Gtk.Button ();
++ button.Label = Catalog.GetPluralString ("Save", "Save all", changed_uris.Count);
++ button.CanDefault = true;
++ button.Show ();
++ save_dialogue.AddActionWidget (button, ResponseType.Accept);
++ save_dialogue.Default = button;
++ save_dialogue.DefaultResponse = ResponseType.Accept;
++ button.GrabDefault ();
++
++ ResponseType response = (ResponseType)save_dialogue.Run ();
++ save_dialogue.Destroy ();
++ switch (response) {
++ case ResponseType.Close:
++ return true;
++ case ResponseType.Cancel:
++ return false;
++ case ResponseType.Accept:
++ foreach (var uri in changed_uris) {
++ Pixbuf current_image = undo_history.Pixbuf (uri);
++ PixbufOrientation current_orientation = undo_history.Orientation (uri);
++ SafeSaveFile (uri, uri, current_image, current_orientation);
++ Log.Debug ("Saved changes to {0}", uri.AbsoluteUri);
++ }
++ break;
++ }
++ return true;
++ }
++
+ void HandleDeleteEvent (object sender, DeleteEventArgs args)
+ {
+- SavePreferences ();
+- this.Window.Destroy ();
++ if (PresentUnsavedChangesDialog ()) {
++ SavePreferences ();
++ undo_history.Dispose ();
++ this.Window.Destroy ();
++ }
+ args.RetVal = true;
+ }
+
+@@ -477,8 +711,11 @@
+
+ private void HandleFileClose (object sender, System.EventArgs args)
+ {
+- SavePreferences ();
+- this.Window.Destroy ();
++ if (PresentUnsavedChangesDialog ()) {
++ SavePreferences ();
++ undo_history.Dispose ();
++ this.Window.Destroy ();
++ }
+ }
+
+ private void SavePreferences ()
+Index: f-spot.git/src/Utils/UndoCache.cs
+===================================================================
+--- /dev/null 1970-01-01 00:00:00.000000000 +0000
++++ f-spot.git/src/Utils/UndoCache.cs 2010-05-19 14:28:58.127960001 +0100
+@@ -0,0 +1,244 @@
++//
++// UndoCache.cs
++//
++// Author:
++// Christopher James Halse Rogers <christopher.halse.rogers at canonical.com>
++//
++// Copyright © 2010 Christopher James Halse Rogers <christopher.halse.rogers at canonical.com>
++//
++// This program is free software; you can redistribute it and/or modify
++// it under the terms of the GNU General Public License as published by
++// the Free Software Foundation; either version 2 of the License, or
++// (at your option) any later version.
++//
++// This program is distributed in the hope that it will be useful,
++// but WITHOUT ANY WARRANTY; without even the implied warranty of
++// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++// GNU General Public License for more details.
++//
++// You should have received a copy of the GNU General Public License
++// along with this program; if not, write to the Free Software
++// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
++//
++
++using System;
++using System.Collections.Generic;
++using System.Threading;
++using System.IO;
++
++using Gdk;
++
++using Cms;
++
++namespace FSpot.Utils
++{
++ public class UndoStateChangedArgs : EventArgs
++ {
++ public UndoStateChangedArgs (Uri changedFor)
++ {
++ ChangedFor = changedFor;
++ }
++
++ public Uri ChangedFor { get; private set; }
++ }
++
++ public class UndoCache : IDisposable
++ {
++ class CachedPixbuf : IDisposable
++ {
++ Pixbuf mem_cache;
++ FileStream file_cache;
++ ManualResetEvent write_done;
++
++ public CachedPixbuf (Gdk.Pixbuf image)
++ {
++ // TODO: We can be smarter than always disc caching or always mem caching
++ // Making the cache memory-backed with eviction to disc should be investigated.
++ mem_cache = image.Copy ();
++
++ write_done = new ManualResetEvent (false);
++ ThreadPool.QueueUserWorkItem ((state) => {
++ uint id = Log.DebugTimerStart ("Saving undo information");
++ // PNG is lossless, and can handle all the bitdepths we're likely to encounter
++ byte[] buffer = mem_cache.SaveToBuffer ("png");
++
++ file_cache = File.Create (Path.GetTempFileName (), 4096, FileOptions.DeleteOnClose);
++
++ file_cache.Write (buffer, 0, buffer.Length);
++ Log.DebugTimerPrint (id, "Saving undo information took: {0}");
++ lock (mem_cache) {
++ mem_cache.Dispose ();
++ }
++ write_done.Set ();
++ });
++ }
++
++ public Pixbuf GetPixbuf ()
++ {
++ lock (mem_cache) {
++ if (mem_cache.Handle != IntPtr.Zero) {
++ // Pixbuf.Dispose sets the handle to IntPtr.Zero
++ return mem_cache.Copy ();
++ }
++ }
++ write_done.WaitOne ();
++ file_cache.Seek (0, SeekOrigin.Begin);
++ return new Pixbuf (file_cache);
++ }
++
++ public void Dispose ()
++ {
++ if (write_done.WaitOne (0)) {
++ //We've written to disc, which means we've already disposed mem_cache.
++ file_cache.Dispose ();
++ } else {
++ //We're still writing out to disc. Wait for for it to finish, then dispose.
++ //Do this on the threadpool, as it might take some time.
++ //TODO: Use a cancellable write method.
++ ThreadPool.QueueUserWorkItem ((state) => {
++ write_done.WaitOne ();
++ file_cache.Dispose ();
++ });
++ }
++ }
++ }
++
++ class PixbufProfilePair
++ {
++ public PixbufProfilePair ()
++ {
++ pixbuf_history = new Stack<CachedPixbuf> ();
++ }
++ public Stack<CachedPixbuf> pixbuf_history;
++ public Profile profile;
++ public PixbufOrientation orientation;
++ public bool on_disc;
++ }
++
++ Dictionary<Uri, UndoCache.PixbufProfilePair> history;
++ public UndoCache ()
++ {
++ history = new Dictionary<Uri, UndoCache.PixbufProfilePair> ();
++ }
++
++ public void InitialiseImage (Uri photoUri, Pixbuf baseImage, PixbufOrientation orientation, Profile profile)
++ {
++ if (history.ContainsKey (photoUri)) {
++ throw new ApplicationException (String.Format ("Attempted to initialise the undo cache twice for image {0}", photoUri.AbsolutePath));
++ }
++ history[photoUri] = new PixbufProfilePair ();
++ history[photoUri].profile = profile;
++ history[photoUri].orientation = orientation;
++ history[photoUri].pixbuf_history.Push (new CachedPixbuf (baseImage));
++ history[photoUri].on_disc = true;
++ }
++
++ public void MarkImageSaved (Uri photoUri)
++ {
++ history[photoUri].on_disc = true;
++
++ var handler = UndoStateChanged;
++ if (handler != null) {
++ handler (this, new UndoStateChangedArgs (photoUri));
++ }
++ }
++
++ public void PushEdit (Uri photoUri, Pixbuf image)
++ {
++ if (!history.ContainsKey (photoUri)) {
++ throw new ApplicationException ("Must initialise the undo cache before adding a new edit");
++ }
++ history[photoUri].pixbuf_history.Push (new CachedPixbuf (image));
++ history[photoUri].on_disc = false;
++
++ var handler = UndoStateChanged;
++ if (handler != null) {
++ handler (this, new UndoStateChangedArgs (photoUri));
++ }
++ }
++
++ public bool ContainsImageFor (Uri uri)
++ {
++ return history.ContainsKey (uri);
++ }
++
++ public bool ContainsUndoInformationFor (Uri uri)
++ {
++ return ContainsImageFor (uri) && history[uri].pixbuf_history.Count > 1;
++ }
++
++ public IEnumerable<Uri> ChangedUris ()
++ {
++ foreach (var uri in history.Keys) {
++ if (ContainsUndoInformationFor (uri) && !history[uri].on_disc) {
++ yield return uri;
++ }
++ }
++ yield break;
++ }
++
++ public Profile Profile (Uri uri)
++ {
++ if (!history.ContainsKey (uri)) {
++ throw new KeyNotFoundException (String.Format ("Undo cache has not yet been initialised for {0}", uri.AbsolutePath));
++ }
++ return history[uri].profile;
++ }
++
++ public Pixbuf Pixbuf (Uri uri)
++ {
++ if (!history.ContainsKey (uri)) {
++ throw new KeyNotFoundException (String.Format ("Undo cache has not yet been initialised for {0}", uri.AbsolutePath));
++ }
++ return history[uri].pixbuf_history.Peek ().GetPixbuf ();
++ }
++
++ public PixbufOrientation Orientation (Uri uri)
++ {
++ if (!history.ContainsKey (uri)) {
++ throw new KeyNotFoundException (String.Format ("Undo cache has not yet been initialised for {0}", uri.AbsolutePath));
++ }
++ return history[uri].orientation;
++ }
++
++ public void PopEdit (Uri photoUri)
++ {
++ if (!ContainsUndoInformationFor (photoUri)) {
++ throw new KeyNotFoundException (String.Format ("No Undo info for image {0}!", photoUri.AbsolutePath));
++ }
++ history[photoUri].pixbuf_history.Pop ().Dispose ();
++ history[photoUri].on_disc = false;
++
++ var handler = UndoStateChanged;
++ if (handler != null) {
++ handler (this, new UndoStateChangedArgs (photoUri));
++ }
++ }
++
++ public void Clear (Uri uri)
++ {
++ if (history.ContainsKey (uri)) {
++ foreach (var pixbuf in history[uri].pixbuf_history) {
++ pixbuf.Dispose ();
++ }
++ history.Remove (uri);
++
++ var handler = UndoStateChanged;
++ if (handler != null) {
++ handler (this, new UndoStateChangedArgs (uri));
++ }
++ }
++ }
++
++ public void Dispose ()
++ {
++ foreach (var pair in history.Values) {
++ foreach (var pixbuf in pair.pixbuf_history) {
++ pixbuf.Dispose ();
++ }
++ }
++ }
++
++ public event EventHandler<UndoStateChangedArgs> UndoStateChanged;
++ }
++}
+Index: f-spot.git/src/Widgets/EditorPage.cs
+===================================================================
+--- f-spot.git.orig/src/Widgets/EditorPage.cs 2010-05-19 14:28:58.077959584 +0100
++++ f-spot.git/src/Widgets/EditorPage.cs 2010-05-19 14:30:15.747958503 +0100
+@@ -42,7 +42,12 @@
+
+ public override Gtk.Window ParentWindow {
+ get { return EditorPageWidget.ParentWindow; }
+- set { EditorPageWidget.ParentWindow = value ;}
++ set { EditorPageWidget.ParentWindow = value; }
++ }
++
++ public UndoCache History {
++ get { return EditorPageWidget.History; }
++ set { EditorPageWidget.History = value; }
+ }
+
+ protected override void AddedToSidebar () {
+@@ -52,7 +57,7 @@
+
+ private void HandleContextChanged (object sender, EventArgs args)
+ {
+- InPhotoView = (Sidebar.Context == ViewContext.Edit) || (Sidebar.Context == ViewContext.Single);
++ InPhotoView = ((Sidebar as Sidebar).Context == ViewContext.Edit) || ((Sidebar as Sidebar).Context == ViewContext.Single);
+ EditorPageWidget.ChangeButtonVisibility ();
+ }
+ }
+@@ -65,7 +70,8 @@
+ List<Editor> editors;
+ Editor current_editor;
+
+- public PhotoImageView PhotoImageView { get; set;}
++ public PhotoImageView PhotoImageView { get; set; }
++ public UndoCache History { get; set; }
+ public Gtk.Window ParentWindow { get; set; }
+
+ // Used to make buttons insensitive when selecting multiple images.
+@@ -184,7 +190,8 @@
+ Apply (editor); // Instant apply
+ }
+
+- private bool SetupEditor (Editor editor) {
++ private bool SetupEditor (Editor editor)
++ {
+ EditorState state = editor.CreateState ();
+
+ PhotoImageView photo_view = PhotoImageView;
+@@ -199,6 +206,8 @@
+ return false;
+ state.Items = (Page.Sidebar as Sidebar).Selection.Items;
+
++ state.History = History;
++
+ editor.Initialize (state);
+ return true;
+ }
+Index: f-spot.git/src/f-spot.glade
+===================================================================
+--- f-spot.git.orig/src/f-spot.glade 2010-05-19 14:28:57.987959630 +0100
++++ f-spot.git/src/f-spot.glade 2010-05-19 14:30:51.307958832 +0100
+@@ -2440,6 +2440,31 @@
+ </widget>
+ </child>
+ <child>
++ <widget class="GtkImageMenuItem" id="save">
++ <property name="label">gtk-save</property>
++ <property name="visible">True</property>
++ <property name="use_underline">True</property>
++ <property name="use_stock">True</property>
++ <signal name="activate" handler="HandleSave"/>
++ <accelerator key="N" signal="activate" modifiers="GDK_CONTROL_MASK"/>
++ </widget>
++ </child>
++ <child>
++ <widget class="GtkImageMenuItem" id="save_as">
++ <property name="label">gtk-save-as</property>
++ <property name="visible">True</property>
++ <property name="use_underline">True</property>
++ <property name="use_stock">True</property>
++ <signal name="activate" handler="HandleSaveAs"/>
++ <accelerator key="N" signal="activate" modifiers="GDK_CONTROL_MASK"/>
++ </widget>
++ </child>
++ <child>
++ <widget class="GtkSeparatorMenuItem" id="separator1">
++ <property name="visible">True</property>
++ </widget>
++ </child>
++ <child>
+ <widget class="GtkMenuItem" id="export">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">_Export</property>
+Index: f-spot.git/src/UI.Dialog/HigMessageDialog.cs
+===================================================================
+--- f-spot.git.orig/src/UI.Dialog/HigMessageDialog.cs 2010-05-19 14:27:22.496709303 +0100
++++ f-spot.git/src/UI.Dialog/HigMessageDialog.cs 2010-05-19 14:28:58.127960001 +0100
+@@ -77,6 +77,7 @@
+ label.Justify = Gtk.Justification.Left;
+ label.LineWrap = true;
+ label.SetAlignment (0.0f, 0.5f);
++ label.UseUnderline = false;
+ label.Show ();
+ label_vbox.PackStart (label, false, false, 0);
+
diff --git a/debian/patches/ubuntu_clear_selection_on_crop.patch b/debian/patches/ubuntu_clear_selection_on_crop.patch
new file mode 100644
index 0000000..0549cda
--- /dev/null
+++ b/debian/patches/ubuntu_clear_selection_on_crop.patch
@@ -0,0 +1,30 @@
+Description: Clear the selection after crop
+Author: Christopher James Halse Rogers <christopher.halse.rogers at canonical.com>
+Bug-LP: https://bugs.edge.launchpad.net/ubuntu/+source/f-spot/+bug/535186
+Forwarded: No
+
+------------------------------------------------------------
+revno: 99
+committer: Christopher James Halse Rogers <christopher.halse.rogers at canonical.com>
+branch nick: edit-fixes
+timestamp: Thu 2010-03-11 15:08:26 +1100
+message:
+ Clear selection after crop
+modified:
+ src/Editors/CropEditor.cs
+diff:
+=== modified file 'src/Editors/CropEditor.cs'
+Index: f-spot-0.6.1.5/src/Editors/CropEditor.cs
+===================================================================
+--- f-spot-0.6.1.5.orig/src/Editors/CropEditor.cs 2010-03-18 10:17:40.334269406 +1100
++++ f-spot-0.6.1.5/src/Editors/CropEditor.cs 2010-03-18 10:24:25.564260496 +1100
+@@ -178,7 +178,8 @@
+ selection.Width, selection.Height);
+
+ input.CopyArea (selection.X, selection.Y,
+- selection.Width, selection.Height, edited, 0, 0);
++ selection.Width, selection.Height, edited, 0, 0);
++ State.PhotoImageView.Selection = Rectangle.Zero;
+ return edited;
+ }
+ }
diff --git a/debian/patches/ubuntu_default_view_size.patch b/debian/patches/ubuntu_default_view_size.patch
new file mode 100644
index 0000000..25cd672
--- /dev/null
+++ b/debian/patches/ubuntu_default_view_size.patch
@@ -0,0 +1,27 @@
+------------------------------------------------------------
+revno: 112
+committer: Christopher James Halse Rogers <christopher.halse.rogers at canonical.com>
+branch nick: edit-fixes
+timestamp: Wed 2010-03-31 17:23:34 +1100
+message:
+ Set a more reasonable default size for View mode
+modified:
+ src/Preferences.cs
+diff:
+=== modified file 'src/Preferences.cs'
+--- a/src/Preferences.cs 2009-09-01 22:12:30 +0000
++++ b/src/Preferences.cs 2010-03-31 06:23:34 +0000
+@@ -110,7 +110,12 @@
+ case IMPORT_WINDOW_PANE_POSITION:
+ case FILMSTRIP_ORIENTATION:
+ return 0;
+-
++
++ case VIEWER_WIDTH:
++ return 700;
++ case VIEWER_HEIGHT:
++ return 550;
++
+ case METADATA_EMBED_IN_IMAGE:
+ case MAIN_WINDOW_MAXIMIZED:
+ case GROUP_ADAPTOR_ORDER_ASC:
diff --git a/debian/patches/ubuntu_fix_disposed_pixbuf_errors_in_adjustment.patch b/debian/patches/ubuntu_fix_disposed_pixbuf_errors_in_adjustment.patch
new file mode 100644
index 0000000..0445826
--- /dev/null
+++ b/debian/patches/ubuntu_fix_disposed_pixbuf_errors_in_adjustment.patch
@@ -0,0 +1,36 @@
+Description: Fix disposed pixmap bug in Adjust Colours tool
+ The code paths in Adjustment.Adjust () for the alpha- and non- alpha channel
+ cases have an important difference - the alpha channel codepath modifies
+ Input and returns it, the non-alpha channel path returns a modified copy of
+ Input.
+ .
+ In the alpha case, returning Input eventually results in the preview pixmap
+ being disposed, causing debug spew and (in View mode) the window to display
+ garbage.
+Author: Christopher Halse Rogers <christopher.halse.rogers at canonical.com>
+Bug: https://bugzilla.gnome.org/show_bug.cgi?id=611773
+
+=== modified file 'src/ColorAdjustment/Adjustment.cs'
+--- a/src/ColorAdjustment/Adjustment.cs 2008-10-06 23:11:41 +0000
++++ b/src/ColorAdjustment/Adjustment.cs 2010-03-04 04:24:09 +0000
+@@ -64,16 +64,17 @@
+ Cms.Profile [] list = GenerateAdjustments ().ToArray ();
+
+ if (Input.HasAlpha) {
++ Gdk.Pixbuf input_copy = (Gdk.Pixbuf)Input.Clone ();
+ Pixbuf alpha = PixbufUtils.Flatten (Input);
+ Transform transform = new Transform (list,
+ PixbufUtils.PixbufCmsFormat (alpha),
+ PixbufUtils.PixbufCmsFormat (final),
+ intent, 0x0000);
+ PixbufUtils.ColorAdjust (alpha, final, transform);
+- PixbufUtils.ReplaceColor (final, Input);
++ PixbufUtils.ReplaceColor (final, input_copy);
+ alpha.Dispose ();
+ final.Dispose ();
+- final = Input;
++ final = input_copy;
+ } else {
+ Cms.Transform transform = new Cms.Transform (list,
+ PixbufUtils.PixbufCmsFormat (Input),
+
diff --git a/debian/patches/ubuntu_fix_folder_export_hang.patch b/debian/patches/ubuntu_fix_folder_export_hang.patch
new file mode 100644
index 0000000..fe0c091
--- /dev/null
+++ b/debian/patches/ubuntu_fix_folder_export_hang.patch
@@ -0,0 +1,18 @@
+Description: Fix hang when pressing the “export” button in the folder exporter
+Author: Christopher James Halse Rogers <christopher.halse.rogers at canonical.com>
+Bug-LP: https://bugs.edge.launchpad.net/ubuntu/+source/f-spot/+bug/533435
+Forwarded: No
+
+Index: f-spot.git/extensions/Exporters/FolderExport/FolderExport.cs
+===================================================================
+--- f-spot.git.orig/extensions/Exporters/FolderExport/FolderExport.cs 2010-05-19 13:49:44.416709675 +0100
++++ f-spot.git/extensions/Exporters/FolderExport/FolderExport.cs 2010-05-19 14:28:58.257959751 +0100
+@@ -186,7 +186,7 @@
+ Gnome.Vfs.Result result = Gnome.Vfs.Result.Ok;
+
+ try {
+- Dialog.Hide ();
++ Gtk.Application.Invoke (delegate {Dialog.Hide ();});
+
+ Gnome.Vfs.Uri source = new Gnome.Vfs.Uri (Path.Combine (gallery_path, gallery_name));
+ Gnome.Vfs.Uri target = dest.Clone();
diff --git a/debian/patches/ubuntu_handle_broken_uris_to_view_mode.patch b/debian/patches/ubuntu_handle_broken_uris_to_view_mode.patch
new file mode 100644
index 0000000..b0edfe5
--- /dev/null
+++ b/debian/patches/ubuntu_handle_broken_uris_to_view_mode.patch
@@ -0,0 +1,38 @@
+------------------------------------------------------------
+revno: 109
+committer: Christopher James Halse Rogers <christopher.halse.rogers at canonical.com>
+branch nick: edit-fixes
+timestamp: Thu 2010-03-18 10:13:37 +1100
+message:
+ Catch exceptions in creating the URI list
+modified:
+ src/Util.cs
+diff:
+=== modified file 'src/Util.cs'
+Index: f-spot-0.6.1.5/src/Util.cs
+===================================================================
+--- f-spot-0.6.1.5.orig/src/Util.cs 2010-04-01 13:13:49.305538478 +1100
++++ f-spot-0.6.1.5/src/Util.cs 2010-04-01 13:13:55.915539697 +1100
+@@ -84,12 +84,16 @@
+ {
+ Uri uri;
+
+- if (File.Exists (unknown) || Directory.Exists (unknown))
+- uri = UriUtils.PathToFileUri (unknown);
+- else
+- uri = new Uri (unknown);
+-
+- Add (uri);
++ try {
++ if (File.Exists (unknown) || Directory.Exists (unknown))
++ uri = UriUtils.PathToFileUri (unknown);
++ else
++ uri = new Uri (unknown);
++
++ Add (uri);
++ } catch (Exception e) {
++ Log.Exception ("Error handling file or directory to view", e);
++ }
+ }
+
+ public UriList (string data)
diff --git a/debian/patches/ubuntu_only_update_timestamp_of_unprotected_versions.patch b/debian/patches/ubuntu_only_update_timestamp_of_unprotected_versions.patch
new file mode 100644
index 0000000..4acdfb4
--- /dev/null
+++ b/debian/patches/ubuntu_only_update_timestamp_of_unprotected_versions.patch
@@ -0,0 +1,17 @@
+Index: f-spot.git/src/Jobs/SyncMetadataJob.cs
+===================================================================
+--- f-spot.git.orig/src/Jobs/SyncMetadataJob.cs 2010-05-18 17:30:51.000000000 +0100
++++ f-spot.git/src/Jobs/SyncMetadataJob.cs 2010-05-19 12:07:32.506709827 +0100
+@@ -53,7 +53,11 @@
+ FSpot.JpegFile jimg = img as FSpot.JpegFile;
+
+ jimg.SetDescription (photo.Description);
+- jimg.SetDateTimeOriginal (photo.Time);
++ if (!photo.DefaultVersion.IsProtected) {
++ // If the version is protected we should be able to assume that the data hasn't changed.
++ // Thus, the DateTime shouldn't change, either.
++ jimg.SetDateTimeOriginal (photo.Time);
++ }
+ jimg.SetXmp (UpdateXmp (photo, jimg.Header.GetXmp ()));
+
+ jimg.SaveMetaData (path);
diff --git a/debian/patches/ubuntu_slider_animation_tweaking.patch b/debian/patches/ubuntu_slider_animation_tweaking.patch
new file mode 100644
index 0000000..a8ef22b
--- /dev/null
+++ b/debian/patches/ubuntu_slider_animation_tweaking.patch
@@ -0,0 +1,28 @@
+# Description: tweak the slider animation value
+# Ubuntu: https://bugs.launchpad.net/hundredpapercuts/+bug/513004
+# Upstream: https://bugzilla.gnome.org/show_bug.cgi?id=608443
+From 77800058c950021f2c26bd07a1aef149fb42d753 Mon Sep 17 00:00:00 2001
+From: Alex Launi <alex at jolicloud.org>
+Date: Sat, 20 Feb 2010 23:48:51 -0500
+Subject: [PATCH] Decreases the animation time for the filmstrip slider from 4 seconds to 1.5
+
+---
+ src/Widgets/Filmstrip.cs | 2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/src/Widgets/Filmstrip.cs b/src/Widgets/Filmstrip.cs
+index fbd6745..d6a47bd 100644
+--- a/src/Widgets/Filmstrip.cs
++++ b/src/Widgets/Filmstrip.cs
+@@ -308,7 +308,7 @@ namespace FSpot.Widgets
+ thumb_cache = new DisposableCache<Uri, Pixbuf> (30);
+ ThumbnailGenerator.Default.OnPixbufLoaded += HandlePixbufLoaded;
+
+- animation = new DoubleAnimation (0, 0, TimeSpan.FromSeconds (4), SetPositionCore, new CubicEase (EasingMode.EaseOut));
++ animation = new DoubleAnimation (0, 0, TimeSpan.FromSeconds (1.5), SetPositionCore, new CubicEase (EasingMode.EaseOut));
+ }
+
+ int min_length = 400;
+--
+1.6.3.3
+
--
f-spot
More information about the Pkg-cli-apps-commits
mailing list