summaryrefslogtreecommitdiff
path: root/src/MediaPage.vala
diff options
context:
space:
mode:
authorJörg Frings-Fürst <debian@jff-webhosting.net>2014-07-23 09:06:59 +0200
committerJörg Frings-Fürst <debian@jff-webhosting.net>2014-07-23 09:06:59 +0200
commit4ea2cc3bd4a7d9b1c54a9d33e6a1cf82e7c8c21d (patch)
treed2e54377d14d604356c86862a326f64ae64dadd6 /src/MediaPage.vala
Imported Upstream version 0.18.1upstream/0.18.1
Diffstat (limited to 'src/MediaPage.vala')
-rw-r--r--src/MediaPage.vala1308
1 files changed, 1308 insertions, 0 deletions
diff --git a/src/MediaPage.vala b/src/MediaPage.vala
new file mode 100644
index 0000000..4d7ee2a
--- /dev/null
+++ b/src/MediaPage.vala
@@ -0,0 +1,1308 @@
+/* Copyright 2010-2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU LGPL (version 2.1 or later).
+ * See the COPYING file in this distribution.
+ */
+
+public class MediaSourceItem : CheckerboardItem {
+ private static Gdk.Pixbuf basis_sprocket_pixbuf = null;
+ private static Gdk.Pixbuf current_sprocket_pixbuf = null;
+
+ private bool enable_sprockets = false;
+
+ // preserve the same constructor arguments and semantics as CheckerboardItem so that we're
+ // a drop-in replacement
+ public MediaSourceItem(ThumbnailSource source, Dimensions initial_pixbuf_dim, string title,
+ string? comment, bool marked_up = false, Pango.Alignment alignment = Pango.Alignment.LEFT) {
+ base(source, initial_pixbuf_dim, title, comment, marked_up, alignment);
+ if (basis_sprocket_pixbuf == null)
+ basis_sprocket_pixbuf = Resources.load_icon("sprocket.png", 0);
+ }
+
+ protected override void paint_image(Cairo.Context ctx, Gdk.Pixbuf pixbuf,
+ Gdk.Point origin) {
+ Dimensions pixbuf_dim = Dimensions.for_pixbuf(pixbuf);
+ // sprocket geometry calculation (and possible adjustment) has to occur before we call
+ // base.paint_image( ) because the base-class method needs the correct trinket horizontal
+ // offset
+
+ if (!enable_sprockets) {
+ set_horizontal_trinket_offset(0);
+ } else {
+ double reduction_factor = ((double) pixbuf_dim.major_axis()) /
+ ((double) ThumbnailCache.Size.LARGEST);
+ int reduced_size = (int) (reduction_factor * basis_sprocket_pixbuf.width);
+
+ if (current_sprocket_pixbuf == null || reduced_size != current_sprocket_pixbuf.width) {
+ current_sprocket_pixbuf = basis_sprocket_pixbuf.scale_simple(reduced_size,
+ reduced_size, Gdk.InterpType.HYPER);
+ }
+
+ set_horizontal_trinket_offset(current_sprocket_pixbuf.width);
+ }
+
+ base.paint_image(ctx, pixbuf, origin);
+
+ if (enable_sprockets) {
+ paint_sprockets(ctx, origin, pixbuf_dim);
+ }
+ }
+
+ protected void paint_one_sprocket(Cairo.Context ctx, Gdk.Point origin) {
+ ctx.save();
+ Gdk.cairo_set_source_pixbuf(ctx, current_sprocket_pixbuf, origin.x, origin.y);
+ ctx.paint();
+ ctx.restore();
+ }
+
+ protected void paint_sprockets(Cairo.Context ctx, Gdk.Point item_origin,
+ Dimensions item_dimensions) {
+ int num_sprockets = item_dimensions.height / current_sprocket_pixbuf.height;
+
+ Gdk.Point left_paint_location = item_origin;
+ Gdk.Point right_paint_location = item_origin;
+ right_paint_location.x += (item_dimensions.width - current_sprocket_pixbuf.width);
+ for (int i = 0; i < num_sprockets; i++) {
+ paint_one_sprocket(ctx, left_paint_location);
+ paint_one_sprocket(ctx, right_paint_location);
+
+ left_paint_location.y += current_sprocket_pixbuf.height;
+ right_paint_location.y += current_sprocket_pixbuf.height;
+ }
+
+ int straggler_pixels = item_dimensions.height % current_sprocket_pixbuf.height;
+ if (straggler_pixels > 0) {
+ ctx.save();
+
+ Gdk.cairo_set_source_pixbuf(ctx, current_sprocket_pixbuf, left_paint_location.x,
+ left_paint_location.y);
+ ctx.rectangle(left_paint_location.x, left_paint_location.y,
+ current_sprocket_pixbuf.get_width(), straggler_pixels);
+ ctx.fill();
+
+ Gdk.cairo_set_source_pixbuf(ctx, current_sprocket_pixbuf, right_paint_location.x,
+ right_paint_location.y);
+ ctx.rectangle(right_paint_location.x, right_paint_location.y,
+ current_sprocket_pixbuf.get_width(), straggler_pixels);
+ ctx.fill();
+
+ ctx.restore();
+ }
+ }
+
+ public void set_enable_sprockets(bool enable_sprockets) {
+ this.enable_sprockets = enable_sprockets;
+ }
+}
+
+public abstract class MediaPage : CheckerboardPage {
+ public const int SORT_ORDER_ASCENDING = 0;
+ public const int SORT_ORDER_DESCENDING = 1;
+
+ // steppings should divide evenly into (Thumbnail.MAX_SCALE - Thumbnail.MIN_SCALE)
+ public const int MANUAL_STEPPING = 16;
+ public const int SLIDER_STEPPING = 4;
+
+ public enum SortBy {
+ MIN = 1,
+ TITLE = 1,
+ EXPOSURE_DATE = 2,
+ RATING = 3,
+ MAX = 3
+ }
+
+ protected class ZoomSliderAssembly : Gtk.ToolItem {
+ private Gtk.Scale slider;
+ private Gtk.Adjustment adjustment;
+
+ public signal void zoom_changed();
+
+ public ZoomSliderAssembly() {
+ Gtk.Box zoom_group = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
+
+ Gtk.Image zoom_out = new Gtk.Image.from_pixbuf(Resources.load_icon(
+ Resources.ICON_ZOOM_OUT, Resources.ICON_ZOOM_SCALE));
+ Gtk.EventBox zoom_out_box = new Gtk.EventBox();
+ zoom_out_box.set_above_child(true);
+ zoom_out_box.set_visible_window(false);
+ zoom_out_box.add(zoom_out);
+ zoom_out_box.button_press_event.connect(on_zoom_out_pressed);
+
+ zoom_group.pack_start(zoom_out_box, false, false, 0);
+
+ // virgin ZoomSliderAssemblies are created such that they have whatever value is
+ // persisted in the configuration system for the photo thumbnail scale
+ int persisted_scale = Config.Facade.get_instance().get_photo_thumbnail_scale();
+ adjustment = new Gtk.Adjustment(ZoomSliderAssembly.scale_to_slider(persisted_scale), 0,
+ ZoomSliderAssembly.scale_to_slider(Thumbnail.MAX_SCALE), 1, 10, 0);
+
+ slider = new Gtk.Scale(Gtk.Orientation.HORIZONTAL, adjustment);
+ slider.value_changed.connect(on_slider_changed);
+ slider.set_draw_value(false);
+ slider.set_size_request(200, -1);
+ slider.set_tooltip_text(_("Adjust the size of the thumbnails"));
+
+ zoom_group.pack_start(slider, false, false, 0);
+
+ Gtk.Image zoom_in = new Gtk.Image.from_pixbuf(Resources.load_icon(
+ Resources.ICON_ZOOM_IN, Resources.ICON_ZOOM_SCALE));
+ Gtk.EventBox zoom_in_box = new Gtk.EventBox();
+ zoom_in_box.set_above_child(true);
+ zoom_in_box.set_visible_window(false);
+ zoom_in_box.add(zoom_in);
+ zoom_in_box.button_press_event.connect(on_zoom_in_pressed);
+
+ zoom_group.pack_start(zoom_in_box, false, false, 0);
+
+ add(zoom_group);
+ }
+
+ public static double scale_to_slider(int value) {
+ assert(value >= Thumbnail.MIN_SCALE);
+ assert(value <= Thumbnail.MAX_SCALE);
+
+ return (double) ((value - Thumbnail.MIN_SCALE) / SLIDER_STEPPING);
+ }
+
+ public static int slider_to_scale(double value) {
+ int res = ((int) (value * SLIDER_STEPPING)) + Thumbnail.MIN_SCALE;
+
+ assert(res >= Thumbnail.MIN_SCALE);
+ assert(res <= Thumbnail.MAX_SCALE);
+
+ return res;
+ }
+
+ private bool on_zoom_out_pressed(Gdk.EventButton event) {
+ snap_to_min();
+ return true;
+ }
+
+ private bool on_zoom_in_pressed(Gdk.EventButton event) {
+ snap_to_max();
+ return true;
+ }
+
+ private void on_slider_changed() {
+ zoom_changed();
+ }
+
+ public void snap_to_min() {
+ slider.set_value(scale_to_slider(Thumbnail.MIN_SCALE));
+ }
+
+ public void snap_to_max() {
+ slider.set_value(scale_to_slider(Thumbnail.MAX_SCALE));
+ }
+
+ public void increase_step() {
+ int new_scale = compute_zoom_scale_increase(get_scale());
+
+ if (get_scale() == new_scale)
+ return;
+
+ slider.set_value(scale_to_slider(new_scale));
+ }
+
+ public void decrease_step() {
+ int new_scale = compute_zoom_scale_decrease(get_scale());
+
+ if (get_scale() == new_scale)
+ return;
+
+ slider.set_value(scale_to_slider(new_scale));
+ }
+
+ public int get_scale() {
+ return slider_to_scale(slider.get_value());
+ }
+
+ public void set_scale(int scale) {
+ if (get_scale() == scale)
+ return;
+
+ slider.set_value(scale_to_slider(scale));
+ }
+ }
+
+ private ZoomSliderAssembly? connected_slider = null;
+ private DragAndDropHandler dnd_handler = null;
+ private MediaViewTracker tracker;
+
+ public MediaPage(string page_name) {
+ base (page_name);
+
+ tracker = new MediaViewTracker(get_view());
+
+ get_view().items_altered.connect(on_media_altered);
+
+ get_view().freeze_notifications();
+ get_view().set_property(CheckerboardItem.PROP_SHOW_TITLES,
+ Config.Facade.get_instance().get_display_photo_titles());
+ get_view().set_property(CheckerboardItem.PROP_SHOW_COMMENTS,
+ Config.Facade.get_instance().get_display_photo_comments());
+ get_view().set_property(Thumbnail.PROP_SHOW_TAGS,
+ Config.Facade.get_instance().get_display_photo_tags());
+ get_view().set_property(Thumbnail.PROP_SIZE, get_thumb_size());
+ get_view().set_property(Thumbnail.PROP_SHOW_RATINGS,
+ Config.Facade.get_instance().get_display_photo_ratings());
+ get_view().thaw_notifications();
+
+ // enable drag-and-drop export of media
+ dnd_handler = new DragAndDropHandler(this);
+ }
+
+ private static int compute_zoom_scale_increase(int current_scale) {
+ int new_scale = current_scale + MANUAL_STEPPING;
+ return new_scale.clamp(Thumbnail.MIN_SCALE, Thumbnail.MAX_SCALE);
+ }
+
+ private static int compute_zoom_scale_decrease(int current_scale) {
+ int new_scale = current_scale - MANUAL_STEPPING;
+ return new_scale.clamp(Thumbnail.MIN_SCALE, Thumbnail.MAX_SCALE);
+ }
+
+ protected override void init_collect_ui_filenames(Gee.List<string> ui_filenames) {
+ base.init_collect_ui_filenames(ui_filenames);
+
+ ui_filenames.add("media.ui");
+ }
+
+ protected override Gtk.ActionEntry[] init_collect_action_entries() {
+ Gtk.ActionEntry[] actions = base.init_collect_action_entries();
+
+ Gtk.ActionEntry export = { "Export", Gtk.Stock.SAVE_AS, TRANSLATABLE, "<Ctrl><Shift>E",
+ TRANSLATABLE, on_export };
+ export.label = Resources.EXPORT_MENU;
+ actions += export;
+
+ Gtk.ActionEntry send_to = { "SendTo", "document-send", TRANSLATABLE, null,
+ TRANSLATABLE, on_send_to };
+ send_to.label = Resources.SEND_TO_MENU;
+ actions += send_to;
+
+ // This is identical to the above action, except that it has different
+ // mnemonics and is _only_ for use in the context menu.
+ Gtk.ActionEntry send_to_context_menu = { "SendToContextMenu", "document-send", TRANSLATABLE, null,
+ TRANSLATABLE, on_send_to };
+ send_to_context_menu.label = Resources.SEND_TO_CONTEXT_MENU;
+ actions += send_to_context_menu;
+
+ Gtk.ActionEntry remove_from_library = { "RemoveFromLibrary", Gtk.Stock.REMOVE, TRANSLATABLE,
+ "<Shift>Delete", TRANSLATABLE, on_remove_from_library };
+ remove_from_library.label = Resources.REMOVE_FROM_LIBRARY_MENU;
+ actions += remove_from_library;
+
+ Gtk.ActionEntry move_to_trash = { "MoveToTrash", "user-trash-full", TRANSLATABLE, "Delete",
+ TRANSLATABLE, on_move_to_trash };
+ move_to_trash.label = Resources.MOVE_TO_TRASH_MENU;
+ actions += move_to_trash;
+
+ Gtk.ActionEntry new_event = { "NewEvent", Gtk.Stock.NEW, TRANSLATABLE, "<Ctrl>N",
+ TRANSLATABLE, on_new_event };
+ new_event.label = Resources.NEW_EVENT_MENU;
+ actions += new_event;
+
+ Gtk.ActionEntry add_tags = { "AddTags", null, TRANSLATABLE, "<Ctrl>T", TRANSLATABLE,
+ on_add_tags };
+ add_tags.label = Resources.ADD_TAGS_MENU;
+ actions += add_tags;
+
+ // This is identical to the above action, except that it has different
+ // mnemonics and is _only_ for use in the context menu.
+ Gtk.ActionEntry add_tags_context_menu = { "AddTagsContextMenu", null, TRANSLATABLE, "<Ctrl>A", TRANSLATABLE,
+ on_add_tags };
+ add_tags_context_menu.label = Resources.ADD_TAGS_CONTEXT_MENU;
+ actions += add_tags_context_menu;
+
+ Gtk.ActionEntry modify_tags = { "ModifyTags", null, TRANSLATABLE, "<Ctrl>M", TRANSLATABLE,
+ on_modify_tags };
+ modify_tags.label = Resources.MODIFY_TAGS_MENU;
+ actions += modify_tags;
+
+ Gtk.ActionEntry increase_size = { "IncreaseSize", Gtk.Stock.ZOOM_IN, TRANSLATABLE,
+ "<Ctrl>plus", TRANSLATABLE, on_increase_size };
+ increase_size.label = _("Zoom _In");
+ increase_size.tooltip = _("Increase the magnification of the thumbnails");
+ actions += increase_size;
+
+ Gtk.ActionEntry decrease_size = { "DecreaseSize", Gtk.Stock.ZOOM_OUT, TRANSLATABLE,
+ "<Ctrl>minus", TRANSLATABLE, on_decrease_size };
+ decrease_size.label = _("Zoom _Out");
+ decrease_size.tooltip = _("Decrease the magnification of the thumbnails");
+ actions += decrease_size;
+
+ Gtk.ActionEntry flag = { "Flag", null, TRANSLATABLE, "<Ctrl>G", TRANSLATABLE, on_flag_unflag };
+ flag.label = Resources.FLAG_MENU;
+ actions += flag;
+
+ Gtk.ActionEntry set_rating = { "Rate", null, TRANSLATABLE, null, null, null };
+ set_rating.label = Resources.RATING_MENU;
+ actions += set_rating;
+
+ Gtk.ActionEntry increase_rating = { "IncreaseRating", null, TRANSLATABLE,
+ "greater", TRANSLATABLE, on_increase_rating };
+ increase_rating.label = Resources.INCREASE_RATING_MENU;
+ actions += increase_rating;
+
+ Gtk.ActionEntry decrease_rating = { "DecreaseRating", null, TRANSLATABLE,
+ "less", TRANSLATABLE, on_decrease_rating };
+ decrease_rating.label = Resources.DECREASE_RATING_MENU;
+ actions += decrease_rating;
+
+ Gtk.ActionEntry rate_rejected = { "RateRejected", null, TRANSLATABLE,
+ "9", TRANSLATABLE, on_rate_rejected };
+ rate_rejected.label = Resources.rating_menu(Rating.REJECTED);
+ actions += rate_rejected;
+
+ Gtk.ActionEntry rate_unrated = { "RateUnrated", null, TRANSLATABLE,
+ "0", TRANSLATABLE, on_rate_unrated };
+ rate_unrated.label = Resources.rating_menu(Rating.UNRATED);
+ actions += rate_unrated;
+
+ Gtk.ActionEntry rate_one = { "RateOne", null, TRANSLATABLE,
+ "1", TRANSLATABLE, on_rate_one };
+ rate_one.label = Resources.rating_menu(Rating.ONE);
+ actions += rate_one;
+
+ Gtk.ActionEntry rate_two = { "RateTwo", null, TRANSLATABLE,
+ "2", TRANSLATABLE, on_rate_two };
+ rate_two.label = Resources.rating_menu(Rating.TWO);
+ actions += rate_two;
+
+ Gtk.ActionEntry rate_three = { "RateThree", null, TRANSLATABLE,
+ "3", TRANSLATABLE, on_rate_three };
+ rate_three.label = Resources.rating_menu(Rating.THREE);
+ actions += rate_three;
+
+ Gtk.ActionEntry rate_four = { "RateFour", null, TRANSLATABLE,
+ "4", TRANSLATABLE, on_rate_four };
+ rate_four.label = Resources.rating_menu(Rating.FOUR);
+ actions += rate_four;
+
+ Gtk.ActionEntry rate_five = { "RateFive", null, TRANSLATABLE,
+ "5", TRANSLATABLE, on_rate_five };
+ rate_five.label = Resources.rating_menu(Rating.FIVE);
+ actions += rate_five;
+
+ Gtk.ActionEntry edit_title = { "EditTitle", null, TRANSLATABLE, "F2", TRANSLATABLE,
+ on_edit_title };
+ edit_title.label = Resources.EDIT_TITLE_MENU;
+ actions += edit_title;
+
+ Gtk.ActionEntry edit_comment = { "EditComment", null, TRANSLATABLE, "F3", TRANSLATABLE,
+ on_edit_comment };
+ edit_comment.label = Resources.EDIT_COMMENT_MENU;
+ actions += edit_comment;
+
+ Gtk.ActionEntry sort_photos = { "SortPhotos", null, TRANSLATABLE, null, null, null };
+ sort_photos.label = _("Sort _Photos");
+ actions += sort_photos;
+
+ Gtk.ActionEntry filter_photos = { "FilterPhotos", null, TRANSLATABLE, null, null, null };
+ filter_photos.label = Resources.FILTER_PHOTOS_MENU;
+ actions += filter_photos;
+
+ Gtk.ActionEntry play = { "PlayVideo", Gtk.Stock.MEDIA_PLAY, TRANSLATABLE, "<Ctrl>Y",
+ TRANSLATABLE, on_play_video };
+ play.label = _("_Play Video");
+ play.tooltip = _("Open the selected videos in the system video player");
+ actions += play;
+
+ Gtk.ActionEntry raw_developer = { "RawDeveloper", null, TRANSLATABLE, null, null, null };
+ raw_developer.label = _("_Developer");
+ actions += raw_developer;
+
+ // RAW developers.
+
+ Gtk.ActionEntry dev_shotwell = { "RawDeveloperShotwell", null, TRANSLATABLE, null, TRANSLATABLE,
+ on_raw_developer_shotwell };
+ dev_shotwell.label = _("Shotwell");
+ actions += dev_shotwell;
+
+ Gtk.ActionEntry dev_camera = { "RawDeveloperCamera", null, TRANSLATABLE, null, TRANSLATABLE,
+ on_raw_developer_camera };
+ dev_camera.label = _("Camera");
+ actions += dev_camera;
+
+ return actions;
+ }
+
+ protected override Gtk.ToggleActionEntry[] init_collect_toggle_action_entries() {
+ Gtk.ToggleActionEntry[] toggle_actions = base.init_collect_toggle_action_entries();
+
+ Gtk.ToggleActionEntry titles = { "ViewTitle", null, TRANSLATABLE, "<Ctrl><Shift>T",
+ TRANSLATABLE, on_display_titles, Config.Facade.get_instance().get_display_photo_titles() };
+ titles.label = _("_Titles");
+ titles.tooltip = _("Display the title of each photo");
+ toggle_actions += titles;
+
+ Gtk.ToggleActionEntry comments = { "ViewComment", null, TRANSLATABLE, "<Ctrl><Shift>C",
+ TRANSLATABLE, on_display_comments, Config.Facade.get_instance().get_display_photo_comments() };
+ comments.label = _("_Comments");
+ comments.tooltip = _("Display the comment of each photo");
+ toggle_actions += comments;
+
+ Gtk.ToggleActionEntry ratings = { "ViewRatings", null, TRANSLATABLE, "<Ctrl><Shift>N",
+ TRANSLATABLE, on_display_ratings, Config.Facade.get_instance().get_display_photo_ratings() };
+ ratings.label = Resources.VIEW_RATINGS_MENU;
+ ratings.tooltip = Resources.VIEW_RATINGS_TOOLTIP;
+ toggle_actions += ratings;
+
+ Gtk.ToggleActionEntry tags = { "ViewTags", null, TRANSLATABLE, "<Ctrl><Shift>G",
+ TRANSLATABLE, on_display_tags, Config.Facade.get_instance().get_display_photo_tags() };
+ tags.label = _("Ta_gs");
+ tags.tooltip = _("Display each photo's tags");
+ toggle_actions += tags;
+
+ return toggle_actions;
+ }
+
+ protected override void register_radio_actions(Gtk.ActionGroup action_group) {
+ bool sort_order;
+ int sort_by;
+ get_config_photos_sort(out sort_order, out sort_by);
+
+ // Sort criteria.
+ Gtk.RadioActionEntry[] sort_crit_actions = new Gtk.RadioActionEntry[0];
+
+ Gtk.RadioActionEntry by_title = { "SortByTitle", null, TRANSLATABLE, null, TRANSLATABLE,
+ SortBy.TITLE };
+ by_title.label = _("By _Title");
+ by_title.tooltip = _("Sort photos by title");
+ sort_crit_actions += by_title;
+
+ Gtk.RadioActionEntry by_date = { "SortByExposureDate", null, TRANSLATABLE, null,
+ TRANSLATABLE, SortBy.EXPOSURE_DATE };
+ by_date.label = _("By Exposure _Date");
+ by_date.tooltip = _("Sort photos by exposure date");
+ sort_crit_actions += by_date;
+
+ Gtk.RadioActionEntry by_rating = { "SortByRating", null, TRANSLATABLE, null,
+ TRANSLATABLE, SortBy.RATING };
+ by_rating.label = _("By _Rating");
+ by_rating.tooltip = _("Sort photos by rating");
+ sort_crit_actions += by_rating;
+
+ action_group.add_radio_actions(sort_crit_actions, sort_by, on_sort_changed);
+
+ // Sort order.
+ Gtk.RadioActionEntry[] sort_order_actions = new Gtk.RadioActionEntry[0];
+
+ Gtk.RadioActionEntry ascending = { "SortAscending", Gtk.Stock.SORT_ASCENDING,
+ TRANSLATABLE, null, TRANSLATABLE, SORT_ORDER_ASCENDING };
+ ascending.label = _("_Ascending");
+ ascending.tooltip = _("Sort photos in an ascending order");
+ sort_order_actions += ascending;
+
+ Gtk.RadioActionEntry descending = { "SortDescending", Gtk.Stock.SORT_DESCENDING,
+ TRANSLATABLE, null, TRANSLATABLE, SORT_ORDER_DESCENDING };
+ descending.label = _("D_escending");
+ descending.tooltip = _("Sort photos in a descending order");
+ sort_order_actions += descending;
+
+ action_group.add_radio_actions(sort_order_actions,
+ sort_order ? SORT_ORDER_ASCENDING : SORT_ORDER_DESCENDING, on_sort_changed);
+
+ base.register_radio_actions(action_group);
+ }
+
+ protected override void update_actions(int selected_count, int count) {
+ set_action_sensitive("Export", selected_count > 0);
+ set_action_sensitive("EditTitle", selected_count > 0);
+ set_action_sensitive("EditComment", selected_count > 0);
+ set_action_sensitive("IncreaseSize", get_thumb_size() < Thumbnail.MAX_SCALE);
+ set_action_sensitive("DecreaseSize", get_thumb_size() > Thumbnail.MIN_SCALE);
+ set_action_sensitive("RemoveFromLibrary", selected_count > 0);
+ set_action_sensitive("MoveToTrash", selected_count > 0);
+
+ if (DesktopIntegration.is_send_to_installed())
+ set_action_sensitive("SendTo", selected_count > 0);
+ else
+ set_action_visible("SendTo", false);
+
+ set_action_sensitive("Rate", selected_count > 0);
+ update_rating_sensitivities();
+
+ update_development_menu_item_sensitivity();
+
+ set_action_sensitive("PlayVideo", selected_count == 1
+ && get_view().get_selected_source_at(0) is Video);
+
+ update_flag_action(selected_count);
+
+ base.update_actions(selected_count, count);
+ }
+
+ private void on_media_altered(Gee.Map<DataObject, Alteration> altered) {
+ foreach (DataObject object in altered.keys) {
+ if (altered.get(object).has_detail("metadata", "flagged")) {
+ update_flag_action(get_view().get_selected_count());
+
+ break;
+ }
+ }
+ }
+
+ private void update_rating_sensitivities() {
+ set_action_sensitive("RateRejected", can_rate_selected(Rating.REJECTED));
+ set_action_sensitive("RateUnrated", can_rate_selected(Rating.UNRATED));
+ set_action_sensitive("RateOne", can_rate_selected(Rating.ONE));
+ set_action_sensitive("RateTwo", can_rate_selected(Rating.TWO));
+ set_action_sensitive("RateThree", can_rate_selected(Rating.THREE));
+ set_action_sensitive("RateFour", can_rate_selected(Rating.FOUR));
+ set_action_sensitive("RateFive", can_rate_selected(Rating.FIVE));
+ set_action_sensitive("IncreaseRating", can_increase_selected_rating());
+ set_action_sensitive("DecreaseRating", can_decrease_selected_rating());
+ }
+
+ private void update_development_menu_item_sensitivity() {
+ if (get_view().get_selected().size == 0) {
+ set_action_sensitive("RawDeveloper", false);
+ return;
+ }
+
+ // Collect some stats about what's selected.
+ bool avail_shotwell = false; // True if Shotwell developer is available.
+ bool avail_camera = false; // True if camera developer is available.
+ bool is_raw = false; // True if any RAW photos are selected
+ foreach (DataView view in get_view().get_selected()) {
+ Photo? photo = ((Thumbnail) view).get_media_source() as Photo;
+ if (photo != null && photo.get_master_file_format() == PhotoFileFormat.RAW) {
+ is_raw = true;
+
+ if (!avail_shotwell && photo.is_raw_developer_available(RawDeveloper.SHOTWELL))
+ avail_shotwell = true;
+
+ if (!avail_camera && (photo.is_raw_developer_available(RawDeveloper.CAMERA) ||
+ photo.is_raw_developer_available(RawDeveloper.EMBEDDED)))
+ avail_camera = true;
+
+ if (avail_shotwell && avail_camera)
+ break; // optimization: break out of loop when all options available
+
+ }
+ }
+
+ // Enable/disable menu.
+ set_action_sensitive("RawDeveloper", is_raw);
+
+ if (is_raw) {
+ // Set which developers are available.
+ set_action_sensitive("RawDeveloperShotwell", avail_shotwell);
+ set_action_sensitive("RawDeveloperCamera", avail_camera);
+ }
+ }
+
+ private void update_flag_action(int selected_count) {
+ set_action_sensitive("Flag", selected_count > 0);
+
+ string flag_label = Resources.FLAG_MENU;
+
+ if (selected_count > 0) {
+ bool all_flagged = true;
+ foreach (DataSource source in get_view().get_selected_sources()) {
+ Flaggable? flaggable = source as Flaggable;
+ if (flaggable != null && !flaggable.is_flagged()) {
+ all_flagged = false;
+
+ break;
+ }
+ }
+
+ if (all_flagged) {
+ flag_label = Resources.UNFLAG_MENU;
+ }
+ }
+
+ Gtk.Action? flag_action = get_action("Flag");
+ if (flag_action != null) {
+ flag_action.label = flag_label;
+ }
+ }
+
+ public override Core.ViewTracker? get_view_tracker() {
+ return tracker;
+ }
+
+ public void set_display_ratings(bool display) {
+ get_view().freeze_notifications();
+ get_view().set_property(Thumbnail.PROP_SHOW_RATINGS, display);
+ get_view().thaw_notifications();
+
+ Gtk.ToggleAction? action = get_action("ViewRatings") as Gtk.ToggleAction;
+ if (action != null)
+ action.set_active(display);
+ }
+
+ private bool can_rate_selected(Rating rating) {
+ foreach (DataView view in get_view().get_selected()) {
+ if(((Thumbnail) view).get_media_source().get_rating() != rating)
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool can_increase_selected_rating() {
+ foreach (DataView view in get_view().get_selected()) {
+ if(((Thumbnail) view).get_media_source().get_rating().can_increase())
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool can_decrease_selected_rating() {
+ foreach (DataView view in get_view().get_selected()) {
+ if(((Thumbnail) view).get_media_source().get_rating().can_decrease())
+ return true;
+ }
+
+ return false;
+ }
+
+ public ZoomSliderAssembly create_zoom_slider_assembly() {
+ return new ZoomSliderAssembly();
+ }
+
+ protected override bool on_mousewheel_up(Gdk.EventScroll event) {
+ if ((event.state & Gdk.ModifierType.CONTROL_MASK) != 0) {
+ increase_zoom_level();
+ return true;
+ } else {
+ return base.on_mousewheel_up(event);
+ }
+ }
+
+ protected override bool on_mousewheel_down(Gdk.EventScroll event) {
+ if ((event.state & Gdk.ModifierType.CONTROL_MASK) != 0) {
+ decrease_zoom_level();
+ return true;
+ } else {
+ return base.on_mousewheel_down(event);
+ }
+ }
+
+ private void on_send_to() {
+ DesktopIntegration.send_to((Gee.Collection<MediaSource>) get_view().get_selected_sources());
+ }
+
+ protected void on_play_video() {
+ if (get_view().get_selected_count() != 1)
+ return;
+
+ Video? video = get_view().get_selected_at(0).get_source() as Video;
+ if (video == null)
+ return;
+
+ try {
+ AppInfo.launch_default_for_uri(video.get_file().get_uri(), null);
+ } catch (Error e) {
+ AppWindow.error_message(_("Shotwell was unable to play the selected video:\n%s").printf(
+ e.message));
+ }
+ }
+
+ protected override bool on_app_key_pressed(Gdk.EventKey event) {
+ bool handled = true;
+ switch (Gdk.keyval_name(event.keyval)) {
+ case "equal":
+ case "plus":
+ case "KP_Add":
+ activate_action("IncreaseSize");
+ break;
+
+ case "minus":
+ case "underscore":
+ case "KP_Subtract":
+ activate_action("DecreaseSize");
+ break;
+
+ case "period":
+ activate_action("IncreaseRating");
+ break;
+
+ case "comma":
+ activate_action("DecreaseRating");
+ break;
+
+ case "KP_1":
+ activate_action("RateOne");
+ break;
+
+ case "KP_2":
+ activate_action("RateTwo");
+ break;
+
+ case "KP_3":
+ activate_action("RateThree");
+ break;
+
+ case "KP_4":
+ activate_action("RateFour");
+ break;
+
+ case "KP_5":
+ activate_action("RateFive");
+ break;
+
+ case "KP_0":
+ activate_action("RateUnrated");
+ break;
+
+ case "KP_9":
+ activate_action("RateRejected");
+ break;
+
+ case "exclam":
+ if (get_ctrl_pressed())
+ get_search_view_filter().set_rating_filter(RatingFilter.ONE_OR_HIGHER);
+ break;
+
+ case "at":
+ if (get_ctrl_pressed())
+ get_search_view_filter().set_rating_filter(RatingFilter.TWO_OR_HIGHER);
+ break;
+
+ case "numbersign":
+ if (get_ctrl_pressed())
+ get_search_view_filter().set_rating_filter(RatingFilter.THREE_OR_HIGHER);
+ break;
+
+ case "dollar":
+ if (get_ctrl_pressed())
+ get_search_view_filter().set_rating_filter(RatingFilter.FOUR_OR_HIGHER);
+ break;
+
+ case "percent":
+ if (get_ctrl_pressed())
+ get_search_view_filter().set_rating_filter(RatingFilter.FIVE_OR_HIGHER);
+ break;
+
+ case "parenright":
+ if (get_ctrl_pressed())
+ get_search_view_filter().set_rating_filter(RatingFilter.UNRATED_OR_HIGHER);
+ break;
+
+ case "parenleft":
+ if (get_ctrl_pressed())
+ get_search_view_filter().set_rating_filter(RatingFilter.REJECTED_OR_HIGHER);
+ break;
+
+ case "asterisk":
+ if (get_ctrl_pressed())
+ get_search_view_filter().set_rating_filter(RatingFilter.REJECTED_ONLY);
+ break;
+
+ case "slash":
+ activate_action("Flag");
+ break;
+
+ default:
+ handled = false;
+ break;
+ }
+
+ return handled ? true : base.on_app_key_pressed(event);
+ }
+
+ public override void switched_to() {
+ base.switched_to();
+
+ // set display options to match Configuration toggles (which can change while switched away)
+ get_view().freeze_notifications();
+ set_display_titles(Config.Facade.get_instance().get_display_photo_titles());
+ set_display_comments(Config.Facade.get_instance().get_display_photo_comments());
+ set_display_ratings(Config.Facade.get_instance().get_display_photo_ratings());
+ set_display_tags(Config.Facade.get_instance().get_display_photo_tags());
+ get_view().thaw_notifications();
+
+ sync_sort();
+ }
+
+ public override void switching_from() {
+ disconnect_slider();
+
+ base.switching_from();
+ }
+
+ protected void connect_slider(ZoomSliderAssembly slider) {
+ connected_slider = slider;
+ connected_slider.zoom_changed.connect(on_zoom_changed);
+ load_persistent_thumbnail_scale();
+ }
+
+ private void save_persistent_thumbnail_scale() {
+ if (connected_slider == null)
+ return;
+
+ Config.Facade.get_instance().set_photo_thumbnail_scale(connected_slider.get_scale());
+ }
+
+ private void load_persistent_thumbnail_scale() {
+ if (connected_slider == null)
+ return;
+
+ int persistent_scale = Config.Facade.get_instance().get_photo_thumbnail_scale();
+
+ connected_slider.set_scale(persistent_scale);
+ set_thumb_size(persistent_scale);
+ }
+
+ protected void disconnect_slider() {
+ if (connected_slider == null)
+ return;
+
+ connected_slider.zoom_changed.disconnect(on_zoom_changed);
+ connected_slider = null;
+ }
+
+ protected virtual void on_zoom_changed() {
+ if (connected_slider != null)
+ set_thumb_size(connected_slider.get_scale());
+
+ save_persistent_thumbnail_scale();
+ }
+
+ protected abstract void on_export();
+
+ protected virtual void on_increase_size() {
+ increase_zoom_level();
+ }
+
+ protected virtual void on_decrease_size() {
+ decrease_zoom_level();
+ }
+
+ private void on_add_tags() {
+ if (get_view().get_selected_count() == 0)
+ return;
+
+ AddTagsDialog dialog = new AddTagsDialog();
+ string[]? names = dialog.execute();
+
+ if (names != null) {
+ get_command_manager().execute(new AddTagsCommand(
+ HierarchicalTagIndex.get_global_index().get_paths_for_names_array(names),
+ (Gee.Collection<MediaSource>) get_view().get_selected_sources()));
+ }
+ }
+
+ private void on_modify_tags() {
+ if (get_view().get_selected_count() != 1)
+ return;
+
+ MediaSource media = (MediaSource) get_view().get_selected_at(0).get_source();
+
+ ModifyTagsDialog dialog = new ModifyTagsDialog(media);
+ Gee.ArrayList<Tag>? new_tags = dialog.execute();
+
+ if (new_tags == null)
+ return;
+
+ get_command_manager().execute(new ModifyTagsCommand(media, new_tags));
+ }
+
+ private void set_display_tags(bool display) {
+ get_view().freeze_notifications();
+ get_view().set_property(Thumbnail.PROP_SHOW_TAGS, display);
+ get_view().thaw_notifications();
+
+ Gtk.ToggleAction? action = get_action("ViewTags") as Gtk.ToggleAction;
+ if (action != null)
+ action.set_active(display);
+ }
+
+ private void on_new_event() {
+ if (get_view().get_selected_count() > 0)
+ get_command_manager().execute(new NewEventCommand(get_view().get_selected()));
+ }
+
+ private void on_flag_unflag() {
+ if (get_view().get_selected_count() == 0)
+ return;
+
+ Gee.Collection<MediaSource> sources =
+ (Gee.Collection<MediaSource>) get_view().get_selected_sources_of_type(typeof(MediaSource));
+
+ // If all are flagged, then unflag, otherwise flag
+ bool flag = false;
+ foreach (MediaSource source in sources) {
+ Flaggable? flaggable = source as Flaggable;
+ if (flaggable != null && !flaggable.is_flagged()) {
+ flag = true;
+
+ break;
+ }
+ }
+
+ get_command_manager().execute(new FlagUnflagCommand(sources, flag));
+ }
+
+ protected virtual void on_increase_rating() {
+ if (get_view().get_selected_count() == 0)
+ return;
+
+ SetRatingCommand command = new SetRatingCommand.inc_dec(get_view().get_selected(), true);
+ get_command_manager().execute(command);
+
+ update_rating_sensitivities();
+ }
+
+ protected virtual void on_decrease_rating() {
+ if (get_view().get_selected_count() == 0)
+ return;
+
+ SetRatingCommand command = new SetRatingCommand.inc_dec(get_view().get_selected(), false);
+ get_command_manager().execute(command);
+
+ update_rating_sensitivities();
+ }
+
+ protected virtual void on_set_rating(Rating rating) {
+ if (get_view().get_selected_count() == 0)
+ return;
+
+ SetRatingCommand command = new SetRatingCommand(get_view().get_selected(), rating);
+ get_command_manager().execute(command);
+
+ update_rating_sensitivities();
+ }
+
+ protected virtual void on_rate_rejected() {
+ on_set_rating(Rating.REJECTED);
+ }
+
+ protected virtual void on_rate_unrated() {
+ on_set_rating(Rating.UNRATED);
+ }
+
+ protected virtual void on_rate_one() {
+ on_set_rating(Rating.ONE);
+ }
+
+ protected virtual void on_rate_two() {
+ on_set_rating(Rating.TWO);
+ }
+
+ protected virtual void on_rate_three() {
+ on_set_rating(Rating.THREE);
+ }
+
+ protected virtual void on_rate_four() {
+ on_set_rating(Rating.FOUR);
+ }
+
+ protected virtual void on_rate_five() {
+ on_set_rating(Rating.FIVE);
+ }
+
+ private void on_remove_from_library() {
+ remove_photos_from_library((Gee.Collection<LibraryPhoto>) get_view().get_selected_sources());
+ }
+
+ protected virtual void on_move_to_trash() {
+ CheckerboardItem? restore_point = null;
+
+ if (cursor != null) {
+ restore_point = get_view().get_next(cursor) as CheckerboardItem;
+ }
+
+ if (get_view().get_selected_count() > 0) {
+ get_command_manager().execute(new TrashUntrashPhotosCommand(
+ (Gee.Collection<MediaSource>) get_view().get_selected_sources(), true));
+ }
+
+ if ((restore_point != null) && (get_view().contains(restore_point))) {
+ set_cursor(restore_point);
+ }
+ }
+
+ protected virtual void on_edit_title() {
+ if (get_view().get_selected_count() == 0)
+ return;
+
+ Gee.List<MediaSource> media_sources = (Gee.List<MediaSource>) get_view().get_selected_sources();
+
+ EditTitleDialog edit_title_dialog = new EditTitleDialog(media_sources[0].get_title());
+ string? new_title = edit_title_dialog.execute();
+ if (new_title != null)
+ get_command_manager().execute(new EditMultipleTitlesCommand(media_sources, new_title));
+ }
+
+ protected virtual void on_edit_comment() {
+ if (get_view().get_selected_count() == 0)
+ return;
+
+ Gee.List<MediaSource> media_sources = (Gee.List<MediaSource>) get_view().get_selected_sources();
+
+ EditCommentDialog edit_comment_dialog = new EditCommentDialog(media_sources[0].get_comment());
+ string? new_comment = edit_comment_dialog.execute();
+ if (new_comment != null)
+ get_command_manager().execute(new EditMultipleCommentsCommand(media_sources, new_comment));
+ }
+
+ protected virtual void on_display_titles(Gtk.Action action) {
+ bool display = ((Gtk.ToggleAction) action).get_active();
+
+ set_display_titles(display);
+
+ Config.Facade.get_instance().set_display_photo_titles(display);
+ }
+
+ protected virtual void on_display_comments(Gtk.Action action) {
+ bool display = ((Gtk.ToggleAction) action).get_active();
+
+ set_display_comments(display);
+
+ Config.Facade.get_instance().set_display_photo_comments(display);
+ }
+
+ protected virtual void on_display_ratings(Gtk.Action action) {
+ bool display = ((Gtk.ToggleAction) action).get_active();
+
+ set_display_ratings(display);
+
+ Config.Facade.get_instance().set_display_photo_ratings(display);
+ }
+
+ protected virtual void on_display_tags(Gtk.Action action) {
+ bool display = ((Gtk.ToggleAction) action).get_active();
+
+ set_display_tags(display);
+
+ Config.Facade.get_instance().set_display_photo_tags(display);
+ }
+
+ protected abstract void get_config_photos_sort(out bool sort_order, out int sort_by);
+
+ protected abstract void set_config_photos_sort(bool sort_order, int sort_by);
+
+ public virtual void on_sort_changed() {
+ int sort_by = get_menu_sort_by();
+ bool sort_order = get_menu_sort_order();
+
+ set_view_comparator(sort_by, sort_order);
+ set_config_photos_sort(sort_order, sort_by);
+ }
+
+ public void on_raw_developer_shotwell(Gtk.Action action) {
+ developer_changed(RawDeveloper.SHOTWELL);
+ }
+
+ public void on_raw_developer_camera(Gtk.Action action) {
+ developer_changed(RawDeveloper.CAMERA);
+ }
+
+ protected virtual void developer_changed(RawDeveloper rd) {
+ if (get_view().get_selected_count() == 0)
+ return;
+
+ // Check if any photo has edits
+
+ // Display warning only when edits could be destroyed
+ bool need_warn = false;
+
+ // Make a list of all photos that need their developer changed.
+ Gee.ArrayList<DataView> to_set = new Gee.ArrayList<DataView>();
+ foreach (DataView view in get_view().get_selected()) {
+ Photo? p = view.get_source() as Photo;
+ if (p != null && (!rd.is_equivalent(p.get_raw_developer()))) {
+ to_set.add(view);
+
+ if (p.has_transformations()) {
+ need_warn = true;
+ }
+ }
+ }
+
+ if (!need_warn || Dialogs.confirm_warn_developer_changed(to_set.size)) {
+ SetRawDeveloperCommand command = new SetRawDeveloperCommand(to_set, rd);
+ get_command_manager().execute(command);
+
+ update_development_menu_item_sensitivity();
+ }
+ }
+
+ protected override void set_display_titles(bool display) {
+ base.set_display_titles(display);
+
+ Gtk.ToggleAction? action = get_action("ViewTitle") as Gtk.ToggleAction;
+ if (action != null)
+ action.set_active(display);
+ }
+
+ protected override void set_display_comments(bool display) {
+ base.set_display_comments(display);
+
+ Gtk.ToggleAction? action = get_action("ViewComment") as Gtk.ToggleAction;
+ if (action != null)
+ action.set_active(display);
+ }
+
+ private Gtk.RadioAction sort_by_title_action() {
+ Gtk.RadioAction action = (Gtk.RadioAction) get_action("SortByTitle");
+ assert(action != null);
+ return action;
+ }
+
+ private Gtk.RadioAction sort_ascending_action() {
+ Gtk.RadioAction action = (Gtk.RadioAction) get_action("SortAscending");
+ assert(action != null);
+ return action;
+ }
+
+ protected int get_menu_sort_by() {
+ // any member of the group knows the current value
+ return sort_by_title_action().get_current_value();
+ }
+
+ protected void set_menu_sort_by(int val) {
+ sort_by_title_action().set_current_value(val);
+ }
+
+ protected bool get_menu_sort_order() {
+ // any member of the group knows the current value
+ return sort_ascending_action().get_current_value() == SORT_ORDER_ASCENDING;
+ }
+
+ protected void set_menu_sort_order(bool ascending) {
+ sort_ascending_action().set_current_value(
+ ascending ? SORT_ORDER_ASCENDING : SORT_ORDER_DESCENDING);
+ }
+
+ void set_view_comparator(int sort_by, bool ascending) {
+ Comparator comparator;
+ ComparatorPredicate predicate;
+
+ switch (sort_by) {
+ case SortBy.TITLE:
+ if (ascending)
+ comparator = Thumbnail.title_ascending_comparator;
+ else comparator = Thumbnail.title_descending_comparator;
+ predicate = Thumbnail.title_comparator_predicate;
+ break;
+
+ case SortBy.EXPOSURE_DATE:
+ if (ascending)
+ comparator = Thumbnail.exposure_time_ascending_comparator;
+ else comparator = Thumbnail.exposure_time_desending_comparator;
+ predicate = Thumbnail.exposure_time_comparator_predicate;
+ break;
+
+ case SortBy.RATING:
+ if (ascending)
+ comparator = Thumbnail.rating_ascending_comparator;
+ else comparator = Thumbnail.rating_descending_comparator;
+ predicate = Thumbnail.rating_comparator_predicate;
+ break;
+
+ default:
+ debug("Unknown sort criteria: %s", get_menu_sort_by().to_string());
+ comparator = Thumbnail.title_descending_comparator;
+ predicate = Thumbnail.title_comparator_predicate;
+ break;
+ }
+
+ get_view().set_comparator(comparator, predicate);
+ }
+
+ protected string get_sortby_path(int sort_by) {
+ switch(sort_by) {
+ case SortBy.TITLE:
+ return "/MenuBar/ViewMenu/SortPhotos/SortByTitle";
+
+ case SortBy.EXPOSURE_DATE:
+ return "/MenuBar/ViewMenu/SortPhotos/SortByExposureDate";
+
+ case SortBy.RATING:
+ return "/MenuBar/ViewMenu/SortPhotos/SortByRating";
+
+ default:
+ debug("Unknown sort criteria: %d", sort_by);
+ return "/MenuBar/ViewMenu/SortPhotos/SortByTitle";
+ }
+ }
+
+ protected void sync_sort() {
+ // It used to be that the config and UI could both agree on what
+ // sort order and criteria were selected, but the sorting wouldn't
+ // match them, due to the current view's comparator not actually
+ // being set to match, and since there was a check to see if the
+ // config and UI matched that would frequently succeed in this case,
+ // the sorting was often wrong until the user went in and changed
+ // it. Because there is no tidy way to query the current view's
+ // comparator, we now set it any time we even think the sorting
+ // might have changed to force them to always stay in sync.
+ //
+ // Although this means we pay for a re-sort every time, in practice,
+ // this isn't terribly expensive - it _might_ take as long as .5 sec.
+ // with a media page containing over 15000 items on a modern CPU.
+
+ bool sort_ascending;
+ int sort_by;
+ get_config_photos_sort(out sort_ascending, out sort_by);
+
+ set_menu_sort_by(sort_by);
+ set_menu_sort_order(sort_ascending);
+
+ set_view_comparator(sort_by, sort_ascending);
+ }
+
+ public override void destroy() {
+ disconnect_slider();
+
+ base.destroy();
+ }
+
+ public void increase_zoom_level() {
+ if (connected_slider != null) {
+ connected_slider.increase_step();
+ } else {
+ int new_scale = compute_zoom_scale_increase(get_thumb_size());
+ save_persistent_thumbnail_scale();
+ set_thumb_size(new_scale);
+ }
+ }
+
+ public void decrease_zoom_level() {
+ if (connected_slider != null) {
+ connected_slider.decrease_step();
+ } else {
+ int new_scale = compute_zoom_scale_decrease(get_thumb_size());
+ save_persistent_thumbnail_scale();
+ set_thumb_size(new_scale);
+ }
+ }
+
+ public virtual DataView create_thumbnail(DataSource source) {
+ return new Thumbnail((MediaSource) source, get_thumb_size());
+ }
+
+ // this is a view-level operation on this page only; it does not affect the persistent global
+ // thumbnail scale
+ public void set_thumb_size(int new_scale) {
+ if (get_thumb_size() == new_scale || !is_in_view())
+ return;
+
+ new_scale = new_scale.clamp(Thumbnail.MIN_SCALE, Thumbnail.MAX_SCALE);
+ get_checkerboard_layout().set_scale(new_scale);
+
+ // when doing mass operations on LayoutItems, freeze individual notifications
+ get_view().freeze_notifications();
+ get_view().set_property(Thumbnail.PROP_SIZE, new_scale);
+ get_view().thaw_notifications();
+
+ set_action_sensitive("IncreaseSize", new_scale < Thumbnail.MAX_SCALE);
+ set_action_sensitive("DecreaseSize", new_scale > Thumbnail.MIN_SCALE);
+ }
+
+ public int get_thumb_size() {
+ if (get_checkerboard_layout().get_scale() <= 0)
+ get_checkerboard_layout().set_scale(Config.Facade.get_instance().get_photo_thumbnail_scale());
+
+ return get_checkerboard_layout().get_scale();
+ }
+}
+