summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJörg Frings-Fürst <debian@jff-webhosting.net>2014-09-23 09:36:45 +0200
committerJörg Frings-Fürst <debian@jff-webhosting.net>2014-09-23 09:36:45 +0200
commit566dc060676b41e1e58a446b7dcc4159e242fee6 (patch)
tree6eaeba1cf78d3f03b8a1d5bfa998eb104ca47425 /src
parent4ea2cc3bd4a7d9b1c54a9d33e6a1cf82e7c8c21d (diff)
Imported Upstream version 0.20.0upstream/0.20.0
Diffstat (limited to 'src')
-rw-r--r--src/AppWindow.vala12
-rw-r--r--src/CheckerboardLayout.vala40
-rw-r--r--src/CollectionPage.vala14
-rw-r--r--src/DesktopIntegration.vala25
-rw-r--r--src/Dialogs.vala92
-rw-r--r--src/Dimensions.vala22
-rw-r--r--src/MediaPage.vala8
-rw-r--r--src/Page.vala40
-rw-r--r--src/Photo.vala28
-rw-r--r--src/PhotoPage.vala28
-rw-r--r--src/config/ConfigurationInterfaces.vala32
-rw-r--r--src/config/GSettingsEngine.vala10
-rw-r--r--src/direct/DirectPhotoPage.vala11
-rw-r--r--src/editing_tools/EditingTools.vala29
-rw-r--r--src/photos/BmpSupport.vala11
-rw-r--r--src/photos/GdkSupport.vala7
-rw-r--r--src/photos/JfifSupport.vala7
-rw-r--r--src/photos/PhotoFileFormat.vala4
-rw-r--r--src/photos/PhotoFileSniffer.vala20
-rw-r--r--src/photos/PngSupport.vala11
-rw-r--r--src/photos/RawSupport.vala5
-rw-r--r--src/photos/TiffSupport.vala7
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;