diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/AppWindow.vala | 12 | ||||
| -rw-r--r-- | src/CheckerboardLayout.vala | 40 | ||||
| -rw-r--r-- | src/CollectionPage.vala | 14 | ||||
| -rw-r--r-- | src/DesktopIntegration.vala | 25 | ||||
| -rw-r--r-- | src/Dialogs.vala | 92 | ||||
| -rw-r--r-- | src/Dimensions.vala | 22 | ||||
| -rw-r--r-- | src/MediaPage.vala | 8 | ||||
| -rw-r--r-- | src/Page.vala | 40 | ||||
| -rw-r--r-- | src/Photo.vala | 28 | ||||
| -rw-r--r-- | src/PhotoPage.vala | 28 | ||||
| -rw-r--r-- | src/config/ConfigurationInterfaces.vala | 32 | ||||
| -rw-r--r-- | src/config/GSettingsEngine.vala | 10 | ||||
| -rw-r--r-- | src/direct/DirectPhotoPage.vala | 11 | ||||
| -rw-r--r-- | src/editing_tools/EditingTools.vala | 29 | ||||
| -rw-r--r-- | src/photos/BmpSupport.vala | 11 | ||||
| -rw-r--r-- | src/photos/GdkSupport.vala | 7 | ||||
| -rw-r--r-- | src/photos/JfifSupport.vala | 7 | ||||
| -rw-r--r-- | src/photos/PhotoFileFormat.vala | 4 | ||||
| -rw-r--r-- | src/photos/PhotoFileSniffer.vala | 20 | ||||
| -rw-r--r-- | src/photos/PngSupport.vala | 11 | ||||
| -rw-r--r-- | src/photos/RawSupport.vala | 5 | ||||
| -rw-r--r-- | src/photos/TiffSupport.vala | 7 | 
22 files changed, 358 insertions, 105 deletions
| 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<Gdk.Pixbuf> pixbuf_list = new GLib.List<Gdk.Pixbuf>();          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<DataView> collection = new Gee.HashSet<DataView>(); +        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<DataObject>? added,           Gee.Iterable<DataObject>? 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<MediaSource>) 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<Photo> photos, double duration, double transition) { +public void set_background_slideshow(Gee.Collection<Photo> 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<string> exceptions = new Gee.HashSet<string>(); @@ -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"; @@ -461,6 +469,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      //      public virtual string? get_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,14 +76,27 @@ 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; | 
