From 566dc060676b41e1e58a446b7dcc4159e242fee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Tue, 23 Sep 2014 09:36:45 +0200 Subject: Imported Upstream version 0.20.0 --- src/AppWindow.vala | 12 ++--- src/CheckerboardLayout.vala | 40 ++++++++++++++ src/CollectionPage.vala | 14 +++-- src/DesktopIntegration.vala | 25 +++++++-- src/Dialogs.vala | 92 ++++++++++++++++++++++++++------- src/Dimensions.vala | 22 ++++---- src/MediaPage.vala | 8 +++ src/Page.vala | 40 +++++++------- src/Photo.vala | 28 ++++++---- src/PhotoPage.vala | 28 ++++++---- src/config/ConfigurationInterfaces.vala | 32 ++++++++++++ src/config/GSettingsEngine.vala | 10 +++- src/direct/DirectPhotoPage.vala | 11 +++- src/editing_tools/EditingTools.vala | 29 +++++++++++ src/photos/BmpSupport.vala | 11 ++-- src/photos/GdkSupport.vala | 7 ++- src/photos/JfifSupport.vala | 7 ++- src/photos/PhotoFileFormat.vala | 4 +- src/photos/PhotoFileSniffer.vala | 20 +++++-- src/photos/PngSupport.vala | 11 ++-- src/photos/RawSupport.vala | 5 +- src/photos/TiffSupport.vala | 7 ++- 22 files changed, 358 insertions(+), 105 deletions(-) (limited to 'src') diff --git a/src/AppWindow.vala b/src/AppWindow.vala index 9c1f2b4..782f953 100644 --- a/src/AppWindow.vala +++ b/src/AppWindow.vala @@ -145,12 +145,9 @@ public class FullscreenWindow : PageWindow { return true; } - + // Make sure this event gets propagated to the underlying window... - AppWindow.get_instance().key_press_event(event); - - // ...then let the base class take over - return (base.key_press_event != null) ? base.key_press_event(event) : false; + return AppWindow.get_instance().key_press_event(event); } private void on_close() { @@ -450,7 +447,10 @@ public abstract class AppWindow : PageWindow { GLib.List pixbuf_list = new GLib.List(); foreach (string resource in Resources.APP_ICONS) pixbuf_list.append(Resources.get_icon(resource, 0)); - set_default_icon_list(pixbuf_list); + // Use copy() because set_default_icon_list() actually accepts an owned reference + // If we didn't hold the pixbufs in memory, would need to use copy_deep() + // See https://mail.gnome.org/archives/vala-list/2014-August/msg00022.html + set_default_icon_list(pixbuf_list.copy()); // restore previous size and maximization state if (this is LibraryWindow) { diff --git a/src/CheckerboardLayout.vala b/src/CheckerboardLayout.vala index 398152e..6d0ce61 100644 --- a/src/CheckerboardLayout.vala +++ b/src/CheckerboardLayout.vala @@ -126,6 +126,7 @@ public abstract class CheckerboardItem : ThumbnailView { private bool comment_visible = true; private CheckerboardItemText? subtitle = null; private bool subtitle_visible = false; + private bool is_cursor = false; private Gdk.Pixbuf pixbuf = null; private Gdk.Pixbuf display_pixbuf = null; private Gdk.Pixbuf brightened = null; @@ -275,6 +276,14 @@ public abstract class CheckerboardItem : ThumbnailView { recalc_size("set_subtitle_visible"); notify_view_altered(); } + + public void set_is_cursor(bool is_cursor) { + this.is_cursor = is_cursor; + } + + public bool get_is_cusor() { + return is_cursor; + } protected override void notify_membership_changed(DataCollection? collection) { bool title_visible = (bool) get_collection_property(PROP_SHOW_TITLES, true); @@ -549,6 +558,16 @@ public abstract class CheckerboardItem : ThumbnailView { ctx.restore(); } + // draw a border for the cursor with the selection width and normal border color + if (is_cursor) { + ctx.save(); + ctx.set_source_rgba(border_color.red, border_color.green, border_color.blue, + border_color.alpha); + paint_border(ctx, pixbuf_dim, pixbuf_origin, + get_selection_border_width(int.max(pixbuf_dim.width, pixbuf_dim.height))); + ctx.restore(); + } + // draw selection border if (is_selected()) { // border thickness depends on the size of the thumbnail @@ -795,6 +814,7 @@ public class CheckerboardLayout : Gtk.DrawingArea { private bool flow_scheduled = false; private bool exposure_dirty = true; private CheckerboardItem? anchor = null; + private CheckerboardItem? cursor = null; private bool in_center_on_anchor = false; private bool size_allocate_due_to_reflow = false; private bool is_in_view = false; @@ -964,6 +984,26 @@ public class CheckerboardLayout : Gtk.DrawingArea { in_center_on_anchor = false; } + + public void set_cursor(CheckerboardItem item) { + Gee.HashSet collection = new Gee.HashSet(); + if (cursor != null) { + cursor.set_is_cursor(false); + // Bug #732334, the cursor DataView might have disappeared when user drags a full screen Photo to another event + if (view.contains(cursor)) { + collection.add(cursor); + } + } + item.set_is_cursor(true); + cursor = item; + collection.add(item); + on_items_state_changed(collection); + } + + public CheckerboardItem get_cursor() { + return cursor; + } + private void on_contents_altered(Gee.Iterable? added, Gee.Iterable? removed) { diff --git a/src/CollectionPage.vala b/src/CollectionPage.vala index 070452c..22dcdee 100644 --- a/src/CollectionPage.vala +++ b/src/CollectionPage.vala @@ -701,17 +701,21 @@ public abstract class CollectionPage : MediaPage { MediaSourceCollection.filter_media((Gee.Collection) get_view().get_selected_sources(), photos, null); + bool desktop, screensaver; if (photos.size == 1) { - AppWindow.get_instance().set_busy_cursor(); - DesktopIntegration.set_background(photos[0]); - AppWindow.get_instance().set_normal_cursor(); + SetBackgroundPhotoDialog dialog = new SetBackgroundPhotoDialog(); + if (dialog.execute(out desktop, out screensaver)) { + AppWindow.get_instance().set_busy_cursor(); + DesktopIntegration.set_background(photos[0], desktop, screensaver); + AppWindow.get_instance().set_normal_cursor(); + } } else if (photos.size > 1) { SetBackgroundSlideshowDialog dialog = new SetBackgroundSlideshowDialog(); int delay; - if (dialog.execute(out delay)) { + if (dialog.execute(out delay, out desktop, out screensaver)) { AppWindow.get_instance().set_busy_cursor(); DesktopIntegration.set_background_slideshow(photos, delay, - DESKTOP_SLIDESHOW_TRANSITION_SEC); + DESKTOP_SLIDESHOW_TRANSITION_SEC, desktop, screensaver); AppWindow.get_instance().set_normal_cursor(); } } diff --git a/src/DesktopIntegration.vala b/src/DesktopIntegration.vala index ebdc45e..9978803 100644 --- a/src/DesktopIntegration.vala +++ b/src/DesktopIntegration.vala @@ -16,6 +16,9 @@ private ExporterUI desktop_slideshow_exporter = null; private double desktop_slideshow_transition = 0.0; private double desktop_slideshow_duration = 0.0; +private bool set_desktop_background = false; +private bool set_screensaver = false; + public void init() { if (init_count++ != 0) return; @@ -152,7 +155,7 @@ private void on_send_to_export_completed(Exporter exporter, bool is_cancelled) { send_to_exporter = null; } -public void set_background(Photo photo) { +public void set_background(Photo photo, bool desktop, bool screensaver) { // attempt to set the wallpaper to the photo's native format, but if not writeable, go to the // system default PhotoFileFormat file_format = photo.get_best_export_file_format(); @@ -174,7 +177,12 @@ public void set_background(Photo photo) { return; } - Config.Facade.get_instance().set_desktop_background(save_as.get_path()); + if (desktop) { + Config.Facade.get_instance().set_desktop_background(save_as.get_path()); + } + if (screensaver) { + Config.Facade.get_instance().set_screensaver(save_as.get_path()); + } GLib.FileUtils.chmod(save_as.get_parse_name(), 0644); } @@ -254,10 +262,14 @@ private class BackgroundSlideshowXMLBuilder { } } -public void set_background_slideshow(Gee.Collection photos, double duration, double transition) { +public void set_background_slideshow(Gee.Collection photos, double duration, double transition, + bool desktop_background, bool screensaver) { if (desktop_slideshow_exporter != null) return; + set_desktop_background = desktop_background; + set_screensaver = screensaver; + File wallpaper_dir = AppDirs.get_data_subdir("wallpaper"); Gee.Set exceptions = new Gee.HashSet(); @@ -302,7 +314,12 @@ private void on_desktop_slideshow_exported(Exporter exporter, bool is_cancelled) return; } - Config.Facade.get_instance().set_desktop_background(xml_file.get_path()); + if (set_desktop_background) { + Config.Facade.get_instance().set_desktop_background(xml_file.get_path()); + } + if (set_screensaver) { + Config.Facade.get_instance().set_screensaver(xml_file.get_path()); + } } } diff --git a/src/Dialogs.vala b/src/Dialogs.vala index 149a6de..1f6a5ce 100644 --- a/src/Dialogs.vala +++ b/src/Dialogs.vala @@ -1067,14 +1067,16 @@ public class EntryMultiCompletion : Gtk.EntryCompletion { } } -public class SetBackgroundSlideshowDialog { - private Gtk.Dialog dialog; - private Gtk.Label delay_value_label; - private Gtk.Scale delay_scale; - private int delay_value = 0; - - public SetBackgroundSlideshowDialog() { - Gtk.Builder builder = AppWindow.create_builder("set_background_dialog.glade", this); +public abstract class SetBackgroundDialog { + protected Gtk.Dialog dialog; + protected Gtk.CheckButton desktop_background_button; + protected Gtk.CheckButton screensaver_button; + protected Gtk.Button ok_button; + // the checkbuttons themselves are initialized to these values + protected bool desktop = true; + protected bool screensaver = false; + + public SetBackgroundDialog(Gtk.Builder builder) { dialog = builder.get_object("dialog1") as Gtk.Dialog; dialog.set_type_hint(Gdk.WindowTypeHint.DIALOG); @@ -1082,13 +1084,69 @@ public class SetBackgroundSlideshowDialog { dialog.set_transient_for(AppWindow.get_instance()); dialog.set_default_response(Gtk.ResponseType.OK); + desktop_background_button = builder.get_object("desktop_background_checkbox") as Gtk.CheckButton; + desktop_background_button.active = desktop; + desktop_background_button.toggled.connect(on_checkbox_clicked); + screensaver_button = builder.get_object("screensaver_checkbox") as Gtk.CheckButton; + screensaver_button.active = screensaver; + screensaver_button.toggled.connect(on_checkbox_clicked); + + ok_button = builder.get_object("ok_button") as Gtk.Button; + } + + protected void on_checkbox_clicked() { + desktop = desktop_background_button.active; + screensaver = screensaver_button.active; + + if (!desktop && !screensaver) { + ok_button.sensitive = false; + } else { + ok_button.sensitive = true; + } + } + + protected bool execute_base() { + dialog.show_all(); + bool result = dialog.run() == Gtk.ResponseType.OK; + dialog.destroy(); + + return result; + } +} + +public class SetBackgroundPhotoDialog : SetBackgroundDialog { + + public SetBackgroundPhotoDialog() { + Gtk.Builder builder = AppWindow.create_builder("set_background_dialog.glade", this); + base(builder); + } + + public bool execute(out bool desktop_background, out bool screensaver) { + bool result = execute_base(); + + desktop_background = this.desktop; + screensaver = this.screensaver; + + return result; + } +} + +public class SetBackgroundSlideshowDialog : SetBackgroundDialog { + private Gtk.Label delay_value_label; + private Gtk.Scale delay_scale; + private int delay_value = 0; + + public SetBackgroundSlideshowDialog() { + Gtk.Builder builder = AppWindow.create_builder("set_background_slideshow_dialog.glade", this); + base(builder); + delay_value_label = builder.get_object("delay_value_label") as Gtk.Label; delay_scale = builder.get_object("delay_scale") as Gtk.Scale; delay_scale.value_changed.connect(on_delay_scale_value_changed); delay_scale.adjustment.value = 50; } - + private void on_delay_scale_value_changed() { double value = delay_scale.adjustment.value; @@ -1116,15 +1174,13 @@ public class SetBackgroundSlideshowDialog { delay_value_label.label = text; } - - public bool execute(out int delay_value) { - dialog.show_all(); - - bool result = dialog.run() == Gtk.ResponseType.OK; - - dialog.destroy(); + + public bool execute(out int delay_value, out bool desktop_background, out bool screensaver) { + bool result = execute_base(); delay_value = this.delay_value; + desktop_background = this.desktop; + screensaver = this.screensaver; return result; } @@ -1301,11 +1357,11 @@ public class EditCommentDialog : MultiTextEntryDialogMediator { // Gtk.ResponseType.CANCEL. public Gtk.ResponseType remove_from_library_dialog(Gtk.Window owner, string title, string user_message, int count) { - string trash_action = ngettext("_Trash File", "_Trash Files", count); + string trash_action = ngettext("Remove and _Trash File", "Remove and _Trash Files", count); Gtk.MessageDialog dialog = new Gtk.MessageDialog(owner, Gtk.DialogFlags.MODAL, Gtk.MessageType.WARNING, Gtk.ButtonsType.CANCEL, "%s", user_message); - dialog.add_button(_("Only _Remove"), Gtk.ResponseType.NO); + dialog.add_button(_("_Remove From Library"), Gtk.ResponseType.NO); dialog.add_button(trash_action, Gtk.ResponseType.YES); // This dialog was previously created outright; we now 'hijack' diff --git a/src/Dimensions.vala b/src/Dimensions.vala index 0c8c895..f689aca 100644 --- a/src/Dimensions.vala +++ b/src/Dimensions.vala @@ -249,10 +249,10 @@ public struct Dimensions { public struct Scaling { private const int NO_SCALE = 0; - private ScaleConstraint constraint; - private int scale; - private Dimensions viewport; - private bool scale_up; + public ScaleConstraint constraint; + public int scale; + public Dimensions viewport; + public bool scale_up; private Scaling(ScaleConstraint constraint, int scale, Dimensions viewport, bool scale_up) { this.constraint = constraint; @@ -461,13 +461,13 @@ public struct Scaling { } public struct ZoomState { - private Dimensions content_dimensions; - private Dimensions viewport_dimensions; - private double zoom_factor; - private double interpolation_factor; - private double min_factor; - private double max_factor; - private Gdk.Point viewport_center; + public Dimensions content_dimensions; + public Dimensions viewport_dimensions; + public double zoom_factor; + public double interpolation_factor; + public double min_factor; + public double max_factor; + public Gdk.Point viewport_center; public ZoomState(Dimensions content_dimensions, Dimensions viewport_dimensions, double slider_val = 0.0, Gdk.Point? viewport_center = null) { diff --git a/src/MediaPage.vala b/src/MediaPage.vala index 4d7ee2a..9f98466 100644 --- a/src/MediaPage.vala +++ b/src/MediaPage.vala @@ -818,6 +818,14 @@ public abstract class MediaPage : CheckerboardPage { set_display_tags(Config.Facade.get_instance().get_display_photo_tags()); get_view().thaw_notifications(); + // Update cursor position to match the selection that potentially moved while the user + // navigated in SinglePhotoPage + if (get_view().get_selected_count() > 0) { + CheckerboardItem? selected = (CheckerboardItem?) get_view().get_selected_at(0); + if (selected != null) + cursor_to_item(selected); + } + sync_sort(); } diff --git a/src/Page.vala b/src/Page.vala index fd69431..807a926 100644 --- a/src/Page.vala +++ b/src/Page.vala @@ -1448,6 +1448,11 @@ public abstract class CheckerboardPage : Page { handled = false; break; + case "space": + Marker marker = get_view().mark(layout.get_cursor()); + get_view().toggle_marked(marker); + break; + default: handled = false; break; @@ -1528,6 +1533,7 @@ public abstract class CheckerboardPage : Page { cursor = item; break; } + layout.set_cursor(item); } else { // user clicked on "dead" area; only unselect if control is not pressed // do we want similar behavior for shift as well? @@ -1777,11 +1783,13 @@ public abstract class CheckerboardPage : Page { cursor = item; - get_view().unselect_all(); + if (!get_ctrl_pressed()) { + get_view().unselect_all(); + Marker marker = get_view().mark(item); + get_view().select_marked(marker); + } + layout.set_cursor(item); - Marker marker = get_view().mark(item); - get_view().select_marked(marker); - // if item is in any way out of view, scroll to it Gtk.Adjustment vadj = get_vadjustment(); if (get_adjustment_relation(vadj, item.allocation.y) == AdjustmentRelation.IN_RANGE @@ -1806,14 +1814,20 @@ public abstract class CheckerboardPage : Page { if (get_view().get_count() == 0) return; - // if nothing is selected, simply select the first and exit - if (get_view().get_selected_count() == 0 || cursor == null) { + // if there is no better starting point, simply select the first and exit + // The right half of the or is related to Bug #732334, the cursor might be non-null and still not contained in + // the view, if the user dragged a full screen Photo off screen + if (cursor == null && layout.get_cursor() == null || cursor != null && !get_view().contains(cursor)) { CheckerboardItem item = layout.get_item_at_coordinate(0, 0); cursor_to_item(item); anchor = item; return; } + + if (cursor == null) { + cursor = layout.get_cursor() as CheckerboardItem; + } // move the cursor relative to the "first" item CheckerboardItem? item = layout.get_item_relative_to(cursor, point); @@ -1931,12 +1945,6 @@ public abstract class SinglePhotoPage : Page { add(viewport); - // We used to disable GTK double buffering here. We've had to reenable it - // due to this bug: http://redmine.yorba.org/issues/4775 . - // - // all painting happens in pixmap, and is sent to the window wholesale in on_canvas_expose - // canvas.set_double_buffered(false); - canvas.add_events(Gdk.EventMask.EXPOSURE_MASK | Gdk.EventMask.STRUCTURE_MASK | Gdk.EventMask.SUBSTRUCTURE_MASK); @@ -2004,7 +2012,6 @@ public abstract class SinglePhotoPage : Page { protected void on_interactive_zoom(ZoomState interactive_zoom_state) { assert(is_zoom_supported()); - Cairo.Context canvas_ctx = Gdk.cairo_create(canvas.get_window()); set_source_color_from_string(pixmap_ctx, "#000"); pixmap_ctx.paint(); @@ -2014,13 +2021,11 @@ public abstract class SinglePhotoPage : Page { render_zoomed_to_pixmap(interactive_zoom_state); zoom_high_quality = old_quality_setting; - canvas_ctx.set_source_surface(pixmap, 0, 0); - canvas_ctx.paint(); + canvas.queue_draw(); } protected void on_interactive_pan(ZoomState interactive_zoom_state) { assert(is_zoom_supported()); - Cairo.Context canvas_ctx = Gdk.cairo_create(canvas.get_window()); set_source_color_from_string(pixmap_ctx, "#000"); pixmap_ctx.paint(); @@ -2030,8 +2035,7 @@ public abstract class SinglePhotoPage : Page { render_zoomed_to_pixmap(interactive_zoom_state); zoom_high_quality = old_quality_setting; - canvas_ctx.set_source_surface(pixmap, 0, 0); - canvas_ctx.paint(); + canvas.queue_draw(); } protected virtual bool is_zoom_supported() { diff --git a/src/Photo.vala b/src/Photo.vala index ab449dc..34b2676 100644 --- a/src/Photo.vala +++ b/src/Photo.vala @@ -619,6 +619,12 @@ public abstract class Photo : PhotoSource, Dateable { interrogator.interrogate(); DetectedPhotoInformation? detected = interrogator.get_detected_photo_information(); + if (detected == null || interrogator.get_is_photo_corrupted()) { + // TODO: Probably should remove from database, but simply exiting for now (prior code + // didn't even do this check) + return; + } + bpr.dim = detected.image_dim; bpr.filesize = info.get_size(); bpr.timestamp = timestamp.tv_sec; @@ -1149,9 +1155,12 @@ public abstract class Photo : PhotoSource, Dateable { return ImportResult.DECODE_ERROR; } + if (interrogator.get_is_photo_corrupted()) + return ImportResult.NOT_AN_IMAGE; + // if not detected photo information, unsupported DetectedPhotoInformation? detected = interrogator.get_detected_photo_information(); - if (detected == null) + if (detected == null || detected.file_format == PhotoFileFormat.UNKNOWN) return ImportResult.UNSUPPORTED_FORMAT; // copy over supplied MD5s if provided @@ -1261,7 +1270,7 @@ public abstract class Photo : PhotoSource, Dateable { try { interrogator.interrogate(); DetectedPhotoInformation? detected = interrogator.get_detected_photo_information(); - if (detected != null) + if (detected != null && !interrogator.get_is_photo_corrupted() && detected.file_format != PhotoFileFormat.UNKNOWN) params.row.master.file_format = detected.file_format; } catch (Error err) { debug("Unable to interrogate photo file %s: %s", file.get_path(), err.message); @@ -1288,7 +1297,7 @@ public abstract class Photo : PhotoSource, Dateable { PhotoFileInterrogator interrogator = new PhotoFileInterrogator(file, options); interrogator.interrogate(); detected = interrogator.get_detected_photo_information(); - if (detected == null) { + if (detected == null || interrogator.get_is_photo_corrupted()) { critical("Photo update: %s no longer a recognized image", to_string()); return null; @@ -2232,7 +2241,7 @@ public abstract class Photo : PhotoSource, Dateable { } DetectedPhotoInformation? detected = interrogator.get_detected_photo_information(); - if (detected == null) { + if (detected == null || interrogator.get_is_photo_corrupted()) { critical("file_exif_updated: %s no longer an image", to_string()); return; @@ -3216,14 +3225,15 @@ public abstract class Photo : PhotoSource, Dateable { * * @return A Pixbuf with the image data from unmodified_precached. */ - public Gdk.Pixbuf? get_prefetched_copy() { + public Gdk.Pixbuf get_prefetched_copy() throws Error { lock (unmodified_precached) { if (unmodified_precached == null) { try { populate_prefetched(); } catch (Error e) { - warning("raw pixbuf for %s could not be loaded", this.to_string()); - return null; + message("pixbuf for %s could not be loaded: %s", to_string(), e.message); + + throw e; } } @@ -3322,12 +3332,10 @@ public abstract class Photo : PhotoSource, Dateable { populate_prefetched(); Gdk.Pixbuf pixbuf = get_prefetched_copy(); - + // remember to delete the cached copy if it isn't being used. secs_since_access.start(); debug("pipeline being run against %s, timer restarted.", this.to_string()); - - assert(pixbuf != null); // // Image transformation pipeline diff --git a/src/PhotoPage.vala b/src/PhotoPage.vala index d74d004..8db84a1 100644 --- a/src/PhotoPage.vala +++ b/src/PhotoPage.vala @@ -1358,7 +1358,7 @@ public abstract class EditingHostPage : SinglePhotoPage { protected override bool on_shift_pressed(Gdk.EventKey? event) { // show quick compare of original only if no tool is in use, the original pixbuf is handy - if (current_tool == null && !get_ctrl_pressed() && !get_alt_pressed()) + if (current_tool == null && !get_ctrl_pressed() && !get_alt_pressed() && has_photo()) swap_in_original(); return base.on_shift_pressed(event); @@ -1386,13 +1386,13 @@ public abstract class EditingHostPage : SinglePhotoPage { } private void swap_in_original() { - Gdk.Pixbuf? original; - - original = - get_photo().get_original_orientation().rotate_pixbuf(get_photo().get_prefetched_copy()); - - if (original == null) + Gdk.Pixbuf original; + try { + original = get_photo().get_original_orientation().rotate_pixbuf( + get_photo().get_prefetched_copy()); + } catch (Error err) { return; + } // store what's currently displayed only for the duration of the shift pressing swapped = get_unscaled_pixbuf(); @@ -1999,8 +1999,15 @@ public abstract class EditingHostPage : SinglePhotoPage { } public void on_set_background() { - if (has_photo()) - DesktopIntegration.set_background(get_photo()); + if (has_photo()) { + SetBackgroundPhotoDialog dialog = new SetBackgroundPhotoDialog(); + bool desktop, screensaver; + if (dialog.execute(out desktop, out screensaver)) { + AppWindow.get_instance().set_busy_cursor(); + DesktopIntegration.set_background(get_photo(), desktop, screensaver); + AppWindow.get_instance().set_normal_cursor(); + } + } } protected override bool on_ctrl_pressed(Gdk.EventKey? event) { @@ -3137,6 +3144,9 @@ public class LibraryPhotoPage : EditingHostPage { // move on to the next one in the collection on_next_photo(); + + ViewCollection view = get_view(); + view.remove_marked(view.mark(view.get_view_for_source(photo))); if (photo.equals(get_photo())) { // this indicates there is only one photo in the controller, or now zero, so switch // to the Photos page, which is guaranteed to be there diff --git a/src/config/ConfigurationInterfaces.vala b/src/config/ConfigurationInterfaces.vala index 97f41cc..42a591a 100644 --- a/src/config/ConfigurationInterfaces.vala +++ b/src/config/ConfigurationInterfaces.vala @@ -25,6 +25,8 @@ public enum ConfigurableProperty { COMMIT_METADATA_TO_MASTERS, DESKTOP_BACKGROUND_FILE, DESKTOP_BACKGROUND_MODE, + SCREENSAVER_FILE, + SCREENSAVER_MODE, DIRECTORY_PATTERN, DIRECTORY_PATTERN_CUSTOM, DIRECT_WINDOW_HEIGHT, @@ -101,6 +103,12 @@ public enum ConfigurableProperty { case DESKTOP_BACKGROUND_MODE: return "DESKTOP_BACKGROUND_MODE"; + case SCREENSAVER_FILE: + return "SCREENSAVER_FILE"; + + case SCREENSAVER_MODE: + return "SCREENSAVER_MODE"; + case DIRECTORY_PATTERN: return "DIRECTORY_PATTERN"; @@ -460,6 +468,30 @@ public abstract class ConfigurationFacade : Object { } } + // + // screensaver background + // + public virtual string get_screensaver() { + try { + return get_engine().get_string_property(ConfigurableProperty.SCREENSAVER_FILE); + } catch (ConfigurationError err) { + on_configuration_error(err); + + return ""; + } + } + + public virtual void set_screensaver(string filename) { + try { + get_engine().set_string_property(ConfigurableProperty.SCREENSAVER_FILE, + filename); + get_engine().set_string_property(ConfigurableProperty.SCREENSAVER_MODE, + "zoom"); + } catch (ConfigurationError err) { + on_configuration_error(err); + } + } + // // directory pattern // diff --git a/src/config/GSettingsEngine.vala b/src/config/GSettingsEngine.vala index 3a55648..0b2e691 100644 --- a/src/config/GSettingsEngine.vala +++ b/src/config/GSettingsEngine.vala @@ -18,6 +18,7 @@ public class GSettingsConfigurationEngine : ConfigurationEngine, GLib.Object { private const string IMPORTING_SCHEMA_NAME = ROOT_SCHEMA_NAME + ".dataimports"; private const string CROP_SCHEMA_NAME = ROOT_SCHEMA_NAME + ".crop-settings"; private const string SYSTEM_DESKTOP_SCHEMA_NAME = "org.gnome.desktop.background"; + private const string SYSTEM_SCREENSAVER_SCHEMA_NAME = "org.gnome.desktop.screensaver"; private const string PLUGINS_ENABLE_DISABLE_SCHEMA_NAME = ROOT_SCHEMA_NAME + ".plugins.enable-state"; @@ -38,6 +39,8 @@ public class GSettingsConfigurationEngine : ConfigurationEngine, GLib.Object { schema_names[ConfigurableProperty.COMMIT_METADATA_TO_MASTERS] = FILES_PREFS_SCHEMA_NAME; schema_names[ConfigurableProperty.DESKTOP_BACKGROUND_FILE] = SYSTEM_DESKTOP_SCHEMA_NAME; schema_names[ConfigurableProperty.DESKTOP_BACKGROUND_MODE] = SYSTEM_DESKTOP_SCHEMA_NAME; + schema_names[ConfigurableProperty.SCREENSAVER_FILE] = SYSTEM_SCREENSAVER_SCHEMA_NAME; + schema_names[ConfigurableProperty.SCREENSAVER_MODE] = SYSTEM_SCREENSAVER_SCHEMA_NAME; schema_names[ConfigurableProperty.DIRECTORY_PATTERN] = FILES_PREFS_SCHEMA_NAME; schema_names[ConfigurableProperty.DIRECTORY_PATTERN_CUSTOM] = FILES_PREFS_SCHEMA_NAME; schema_names[ConfigurableProperty.DIRECT_WINDOW_HEIGHT] = WINDOW_PREFS_SCHEMA_NAME; @@ -101,6 +104,8 @@ public class GSettingsConfigurationEngine : ConfigurationEngine, GLib.Object { key_names[ConfigurableProperty.COMMIT_METADATA_TO_MASTERS] = "commit-metadata"; key_names[ConfigurableProperty.DESKTOP_BACKGROUND_FILE] = "picture-uri"; key_names[ConfigurableProperty.DESKTOP_BACKGROUND_MODE] = "picture-options"; + key_names[ConfigurableProperty.SCREENSAVER_FILE] = "picture-uri"; + key_names[ConfigurableProperty.SCREENSAVER_MODE] = "picture-options"; key_names[ConfigurableProperty.DIRECTORY_PATTERN] = "directory-pattern"; key_names[ConfigurableProperty.DIRECTORY_PATTERN_CUSTOM] = "directory-pattern-custom"; key_names[ConfigurableProperty.DIRECT_WINDOW_HEIGHT] = "direct-height"; @@ -308,9 +313,10 @@ public class GSettingsConfigurationEngine : ConfigurationEngine, GLib.Object { } public void set_string_property(ConfigurableProperty p, string val) throws ConfigurationError { - // if we're setting the desktop background file, convert the filename into a file URI + // if we're setting the desktop background/screensaver file, convert the filename into a file URI string converted_val = val; - if (p == ConfigurableProperty.DESKTOP_BACKGROUND_FILE) { + if (p == ConfigurableProperty.DESKTOP_BACKGROUND_FILE + || p == ConfigurableProperty.SCREENSAVER_FILE) { converted_val = "file://" + val; } diff --git a/src/direct/DirectPhotoPage.vala b/src/direct/DirectPhotoPage.vala index b2e130d..4dfd520 100644 --- a/src/direct/DirectPhotoPage.vala +++ b/src/direct/DirectPhotoPage.vala @@ -559,8 +559,15 @@ public class DirectPhotoPage : EditingHostPage { } private void on_dphoto_can_rotate_changed(bool should_allow_rotation) { - enable_rotate(should_allow_rotation); - } + // since this signal handler can be called from a background thread (gah, don't get me + // started...), chain to the "enable-rotate" signal in the foreground thread, as it's + // tied to UI elements + Idle.add(() => { + enable_rotate(should_allow_rotation); + + return false; + }); + } protected override DataView create_photo_view(DataSource source) { return new DirectView((DirectPhoto) source); diff --git a/src/editing_tools/EditingTools.vala b/src/editing_tools/EditingTools.vala index b06dbf4..f5fb144 100644 --- a/src/editing_tools/EditingTools.vala +++ b/src/editing_tools/EditingTools.vala @@ -927,6 +927,25 @@ public class CropTool : EditingTool { return result; } + + private float get_constraint_aspect_ratio_for_constraint(ConstraintDescription constraint, Photo photo) { + float result = constraint.aspect_ratio; + + if (result == ORIGINAL_ASPECT_RATIO) { + Dimensions orig_dim = photo.get_original_dimensions(); + result = ((float) orig_dim.width) / ((float) orig_dim.height); + } else if (result == SCREEN_ASPECT_RATIO) { + Gdk.Screen screen = Gdk.Screen.get_default(); + result = ((float) screen.get_width()) / ((float) screen.get_height()); + } else if (result == CUSTOM_ASPECT_RATIO) { + result = custom_aspect_ratio; + } + if (reticle_orientation == ReticleOrientation.PORTRAIT) + result = 1.0f / result; + + return result; + + } private void constraint_changed() { ConstraintDescription selected_constraint = get_selected_constraint(); @@ -1090,6 +1109,16 @@ public class CropTool : EditingTool { if (desc != null && !desc.is_separator()) crop_tool_window.constraint_combo.set_active(index); } + else { + // get aspect ratio of current photo + Photo photo = canvas.get_photo(); + Dimensions cropped_dim = photo.get_dimensions(); + float ratio = (float) cropped_dim.width / (float) cropped_dim.height; + for (int index = 1; index < constraints.length; index++) { + if (Math.fabs(ratio - get_constraint_aspect_ratio_for_constraint(constraints[index], photo)) < 0.005) + crop_tool_window.constraint_combo.set_active(index); + } + } // set up the pivot reticle button update_pivot_button_state(); diff --git a/src/photos/BmpSupport.vala b/src/photos/BmpSupport.vala index 546bed2..dbeb64c 100644 --- a/src/photos/BmpSupport.vala +++ b/src/photos/BmpSupport.vala @@ -71,14 +71,17 @@ public class BmpSniffer : GdkSniffer { return true; } - public override DetectedPhotoInformation? sniff() throws Error { + public override DetectedPhotoInformation? sniff(out bool is_corrupted) throws Error { + // Rely on GdkSniffer to detect corruption + is_corrupted = false; + if (!is_bmp_file(file)) return null; - - DetectedPhotoInformation? detected = base.sniff(); + + DetectedPhotoInformation? detected = base.sniff(out is_corrupted); if (detected == null) return null; - + return (detected.file_format == PhotoFileFormat.BMP) ? detected : null; } } diff --git a/src/photos/GdkSupport.vala b/src/photos/GdkSupport.vala index 4ca0893..ed2ff63 100644 --- a/src/photos/GdkSupport.vala +++ b/src/photos/GdkSupport.vala @@ -34,7 +34,7 @@ public abstract class GdkSniffer : PhotoFileSniffer { base (file, options); } - public override DetectedPhotoInformation? sniff() throws Error { + public override DetectedPhotoInformation? sniff(out bool is_corrupted) throws Error { detected = new DetectedPhotoInformation(); Gdk.PixbufLoader pixbuf_loader = new Gdk.PixbufLoader(); @@ -53,7 +53,7 @@ public abstract class GdkSniffer : PhotoFileSniffer { // no metadata detected detected.metadata = null; } - + if (calc_md5 && detected.metadata != null) { uint8[]? flattened_sans_thumbnail = detected.metadata.flatten_exif(false); if (flattened_sans_thumbnail != null && flattened_sans_thumbnail.length > 0) @@ -102,6 +102,9 @@ public abstract class GdkSniffer : PhotoFileSniffer { if (calc_md5) detected.md5 = md5_checksum.get_string(); + // if size and area are not ready, treat as corrupted file (entire file was read) + is_corrupted = !size_ready || !area_prepared; + return detected; } diff --git a/src/photos/JfifSupport.vala b/src/photos/JfifSupport.vala index 12ac80a..d721441 100644 --- a/src/photos/JfifSupport.vala +++ b/src/photos/JfifSupport.vala @@ -102,11 +102,14 @@ public class JfifSniffer : GdkSniffer { base (file, options); } - public override DetectedPhotoInformation? sniff() throws Error { + public override DetectedPhotoInformation? sniff(out bool is_corrupted) throws Error { + // Rely on GdkSniffer to detect corruption + is_corrupted = false; + if (!Jpeg.is_jpeg(file)) return null; - DetectedPhotoInformation? detected = base.sniff(); + DetectedPhotoInformation? detected = base.sniff(out is_corrupted); if (detected == null) return null; diff --git a/src/photos/PhotoFileFormat.vala b/src/photos/PhotoFileFormat.vala index 926254d..2ab2f00 100644 --- a/src/photos/PhotoFileFormat.vala +++ b/src/photos/PhotoFileFormat.vala @@ -202,9 +202,9 @@ public enum PhotoFileFormat { case "tiff": return PhotoFileFormat.TIFF; - + case "bmp": - return PhotoFileFormat.BMP; + return PhotoFileFormat.BMP; default: return PhotoFileFormat.UNKNOWN; diff --git a/src/photos/PhotoFileSniffer.vala b/src/photos/PhotoFileSniffer.vala index 8bd6711..3f65ac2 100644 --- a/src/photos/PhotoFileSniffer.vala +++ b/src/photos/PhotoFileSniffer.vala @@ -46,7 +46,7 @@ public abstract class PhotoFileSniffer { calc_md5 = (options & Options.NO_MD5) == 0; } - public abstract DetectedPhotoInformation? sniff() throws Error; + public abstract DetectedPhotoInformation? sniff(out bool is_corrupted) throws Error; } // @@ -62,6 +62,7 @@ public class PhotoFileInterrogator { private File file; private PhotoFileSniffer.Options options; private DetectedPhotoInformation? detected = null; + private bool is_photo_corrupted = false; public PhotoFileInterrogator(File file, PhotoFileSniffer.Options options = PhotoFileSniffer.Options.GET_ALL) { @@ -75,13 +76,26 @@ public class PhotoFileInterrogator { return detected; } + // Call after interrogate(). + public bool get_is_photo_corrupted() { + return is_photo_corrupted; + } + public void interrogate() throws Error { foreach (PhotoFileFormat file_format in PhotoFileFormat.get_supported()) { PhotoFileSniffer sniffer = file_format.create_sniffer(file, options); - detected = sniffer.sniff(); - if (detected != null) { + + bool is_corrupted; + detected = sniffer.sniff(out is_corrupted); + if (detected != null && !is_corrupted) { assert(detected.file_format == file_format); + break; + } else if (is_corrupted) { + message("Sniffing halted for %s: potentially corrupted image file", file.get_path()); + is_photo_corrupted = true; + detected = null; + break; } } diff --git a/src/photos/PngSupport.vala b/src/photos/PngSupport.vala index ffc7faa..2cde6a2 100644 --- a/src/photos/PngSupport.vala +++ b/src/photos/PngSupport.vala @@ -69,14 +69,17 @@ public class PngSniffer : GdkSniffer { return true; } - public override DetectedPhotoInformation? sniff() throws Error { + public override DetectedPhotoInformation? sniff(out bool is_corrupted) throws Error { + // Rely on GdkSniffer to detect corruption + is_corrupted = false; + if (!is_png_file(file)) return null; - - DetectedPhotoInformation? detected = base.sniff(); + + DetectedPhotoInformation? detected = base.sniff(out is_corrupted); if (detected == null) return null; - + return (detected.file_format == PhotoFileFormat.PNG) ? detected : null; } } diff --git a/src/photos/RawSupport.vala b/src/photos/RawSupport.vala index bad9572..98bc982 100644 --- a/src/photos/RawSupport.vala +++ b/src/photos/RawSupport.vala @@ -163,7 +163,10 @@ public class RawSniffer : PhotoFileSniffer { base (file, options); } - public override DetectedPhotoInformation? sniff() throws Error { + public override DetectedPhotoInformation? sniff(out bool is_corrupted) throws Error { + // this sniffer doesn't detect corrupted files + is_corrupted = false; + DetectedPhotoInformation detected = new DetectedPhotoInformation(); GRaw.Processor processor = new GRaw.Processor(); diff --git a/src/photos/TiffSupport.vala b/src/photos/TiffSupport.vala index decc052..ee8b087 100644 --- a/src/photos/TiffSupport.vala +++ b/src/photos/TiffSupport.vala @@ -104,11 +104,14 @@ private class TiffSniffer : GdkSniffer { base (file, options); } - public override DetectedPhotoInformation? sniff() throws Error { + public override DetectedPhotoInformation? sniff(out bool is_corrupted) throws Error { + // Rely on GdkSniffer to detect corruption + is_corrupted = false; + if (!is_tiff(file)) return null; - DetectedPhotoInformation? detected = base.sniff(); + DetectedPhotoInformation? detected = base.sniff(out is_corrupted); if (detected == null) return null; -- cgit v1.2.3