MediaPage.vala 45.8 KB
Newer Older
1
/* Copyright 2010-2012 Yorba Foundation
2 3 4 5 6
 *
 * This software is licensed under the GNU LGPL (version 2.1 or later).
 * See the COPYING file in this distribution. 
 */

7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
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,
        bool marked_up = false, Pango.Alignment alignment = Pango.Alignment.LEFT) {
        base(source, initial_pixbuf_dim, title, marked_up, alignment);
        if (basis_sprocket_pixbuf == null)
            basis_sprocket_pixbuf = Resources.load_icon("sprocket.png", 0);
    }

22
    protected override void paint_image(Cairo.Context ctx, Gdk.Pixbuf pixbuf,
23
        Gdk.Point origin) {
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
        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);
            }
Lucas Beeler's avatar
Lucas Beeler committed
40 41

            set_horizontal_trinket_offset(current_sprocket_pixbuf.width);
42 43
        }
                
44
        base.paint_image(ctx, pixbuf, origin);
45 46

        if (enable_sprockets) {
47
            paint_sprockets(ctx, origin, pixbuf_dim);
48 49 50
        }
    }

51 52 53 54 55
    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();
56 57
    }

58
    protected void paint_sprockets(Cairo.Context ctx, Gdk.Point item_origin,
59 60 61 62 63 64 65
        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++) {
66 67
            paint_one_sprocket(ctx, left_paint_location);
            paint_one_sprocket(ctx, right_paint_location);
68 69 70 71 72 73 74

            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) {
75 76 77 78 79 80 81 82 83 84
            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);
85
            ctx.rectangle(right_paint_location.x, right_paint_location.y,
86 87 88 89
                current_sprocket_pixbuf.get_width(), straggler_pixels);
            ctx.fill();

            ctx.restore();
90 91 92 93 94 95 96 97
        }
    }
    
    public void set_enable_sprockets(bool enable_sprockets) {
        this.enable_sprockets = enable_sprockets;
    }
}

98 99 100 101 102 103 104 105 106
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 {
107
        MIN = 1,
108 109
        TITLE = 1,
        EXPOSURE_DATE = 2,
110 111
        RATING = 3,
        MAX = 3
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
    }

    protected class ZoomSliderAssembly : Gtk.ToolItem {
        public static Gtk.Adjustment global_slider_adjustment = null;

        private Gtk.HScale slider;
        
        public signal void zoom_changed();

        public ZoomSliderAssembly() {
            Gtk.HBox zoom_group = new Gtk.HBox(false, 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);

            // triggers lazy init of the global slider adjustment if it hasn't already been init'd
            get_global_thumbnail_scale();

            slider = new Gtk.HScale(global_slider_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);

155
            add(zoom_group);
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
        }
        
        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());
        }
    }
    
    private ZoomSliderAssembly? connected_slider = null;
220
    private DragAndDropHandler dnd_handler = null;
221 222
    private MediaViewTracker tracker;
    
223
    public MediaPage(string page_name) {
224
        base (page_name);
225 226 227
        
        tracker = new MediaViewTracker(get_view());
        
228
        get_view().set_comparator(get_sort_comparator(), get_sort_comparator_predicate());
Jim Nelson's avatar
Jim Nelson committed
229
        get_view().items_altered.connect(on_media_altered);
230 231 232

        get_view().freeze_notifications();
        get_view().set_property(CheckerboardItem.PROP_SHOW_TITLES, 
Lucas Beeler's avatar
Lucas Beeler committed
233
            Config.Facade.get_instance().get_display_photo_titles());
234
        get_view().set_property(Thumbnail.PROP_SHOW_TAGS, 
Lucas Beeler's avatar
Lucas Beeler committed
235
            Config.Facade.get_instance().get_display_photo_tags());
236 237
        get_view().set_property(Thumbnail.PROP_SIZE, get_thumb_size());
        get_view().set_property(Thumbnail.PROP_SHOW_RATINGS,
Lucas Beeler's avatar
Lucas Beeler committed
238
            Config.Facade.get_instance().get_display_photo_ratings());
239
        get_view().thaw_notifications();
240 241 242

        // enable drag-and-drop export of media
        dnd_handler = new DragAndDropHandler(this);
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
    }
    
    private static void set_global_thumbnail_scale(int new_scale) {
        if (get_global_thumbnail_scale() == new_scale)
            return;

        ZoomSliderAssembly.global_slider_adjustment.set_value(
            ZoomSliderAssembly.scale_to_slider(new_scale));
    }
   
    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);
    }
262 263 264 265 266 267 268 269 270
    
    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();
271
        
272
        Gtk.ActionEntry file = { "FileMenu", null, TRANSLATABLE, null, null, null };
273 274 275
        file.label = _("_File");
        actions += file;

276
        Gtk.ActionEntry export = { "Export", Gtk.Stock.SAVE_AS, TRANSLATABLE, "<Ctrl><Shift>E",
277 278 279 280
            TRANSLATABLE, on_export };
        export.label = Resources.EXPORT_MENU;
        actions += export;
       
281 282 283 284 285
        Gtk.ActionEntry send_to = { "SendTo", "document-send", TRANSLATABLE, null, 
            TRANSLATABLE, on_send_to };
        send_to.label = Resources.SEND_TO_MENU;
        actions += send_to;
        
286
        Gtk.ActionEntry edit = { "EditMenu", null, TRANSLATABLE, null, null, null };
287 288
        edit.label = _("_Edit");
        actions += edit;
289
        
290
        Gtk.ActionEntry remove_from_library = { "RemoveFromLibrary", Gtk.Stock.REMOVE, TRANSLATABLE,
291 292 293 294
            "<Shift>Delete", TRANSLATABLE, on_remove_from_library };
        remove_from_library.label = Resources.REMOVE_FROM_LIBRARY_MENU;
        actions += remove_from_library;
        
295 296 297 298 299
        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;
        
300
        Gtk.ActionEntry photos = { "PhotosMenu", null, TRANSLATABLE, null, null, null };
301 302
        photos.label = _("_Photos");
        actions += photos;
303 304 305 306 307 308 309 310 311

        Gtk.ActionEntry event = { "EventsMenu", null, TRANSLATABLE, null, null, null };
        event.label = _("Even_ts");
        actions += event;

        Gtk.ActionEntry tags = { "TagsMenu", null, TRANSLATABLE, null, null, null };
        tags.label = _("Ta_gs");
        actions += tags;

312
        Gtk.ActionEntry new_event = { "NewEvent", Gtk.Stock.NEW, TRANSLATABLE, "<Ctrl>N",
313 314 315 316 317 318 319 320
            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;
321
        
322 323 324 325 326
        Gtk.ActionEntry modify_tags = { "ModifyTags", null, TRANSLATABLE, "<Ctrl>M", TRANSLATABLE, 
            on_modify_tags };
        modify_tags.label = Resources.MODIFY_TAGS_MENU;
        actions += modify_tags;

327
        Gtk.ActionEntry increase_size = { "IncreaseSize", Gtk.Stock.ZOOM_IN, TRANSLATABLE,
328 329 330 331 332
            "<Ctrl>plus", TRANSLATABLE, on_increase_size };
        increase_size.label = _("Zoom _In");
        increase_size.tooltip = _("Increase the magnification of the thumbnails");
        actions += increase_size;

333
        Gtk.ActionEntry decrease_size = { "DecreaseSize", Gtk.Stock.ZOOM_OUT, TRANSLATABLE,
334 335 336 337
            "<Ctrl>minus", TRANSLATABLE, on_decrease_size };
        decrease_size.label = _("Zoom _Out");
        decrease_size.tooltip = _("Decrease the magnification of the thumbnails");
        actions += decrease_size;
Jim Nelson's avatar
Jim Nelson committed
338
        
339
        Gtk.ActionEntry flag = { "Flag", null, TRANSLATABLE, "<Ctrl>G", TRANSLATABLE, on_flag_unflag };
Jim Nelson's avatar
Jim Nelson committed
340 341 342
        flag.label = Resources.FLAG_MENU;
        actions += flag;
        
343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
        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;

397
        Gtk.ActionEntry view = { "ViewMenu", null, TRANSLATABLE, null, null, null };
398 399 400 401 402 403 404 405 406 407 408 409 410 411
        view.label = _("_View");
        actions += view;

        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 help = { "HelpMenu", null, TRANSLATABLE, null, null, null };
        help.label = _("_Help");
        actions += help;
412

413
        Gtk.ActionEntry play = { "PlayVideo", Gtk.Stock.MEDIA_PLAY, TRANSLATABLE, "<Ctrl>Y",
414 415 416 417
            TRANSLATABLE, on_play_video };
        play.label = _("_Play Video");
        play.tooltip = _("Open the selected videos in the system video player");
        actions += play;
418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
        
        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;
434

435 436 437
        return actions;
    }
    
438 439
    protected override Gtk.ToggleActionEntry[] init_collect_toggle_action_entries() {
        Gtk.ToggleActionEntry[] toggle_actions = base.init_collect_toggle_action_entries();
440 441
        
        Gtk.ToggleActionEntry titles = { "ViewTitle", null, TRANSLATABLE, "<Ctrl><Shift>T",
Lucas Beeler's avatar
Lucas Beeler committed
442
            TRANSLATABLE, on_display_titles, Config.Facade.get_instance().get_display_photo_titles() };
443 444 445 446
        titles.label = _("_Titles");
        titles.tooltip = _("Display the title of each photo");
        toggle_actions += titles;
        
447
        Gtk.ToggleActionEntry ratings = { "ViewRatings", null, TRANSLATABLE, "<Ctrl><Shift>N",
Lucas Beeler's avatar
Lucas Beeler committed
448
            TRANSLATABLE, on_display_ratings, Config.Facade.get_instance().get_display_photo_ratings() };
449 450 451
        ratings.label = Resources.VIEW_RATINGS_MENU;
        ratings.tooltip = Resources.VIEW_RATINGS_TOOLTIP;
        toggle_actions += ratings;
452 453

        Gtk.ToggleActionEntry tags = { "ViewTags", null, TRANSLATABLE, "<Ctrl><Shift>G",
Lucas Beeler's avatar
Lucas Beeler committed
454
            TRANSLATABLE, on_display_tags, Config.Facade.get_instance().get_display_photo_tags() };
455 456 457
        tags.label = _("Ta_gs");
        tags.tooltip = _("Display each photo's tags");
        toggle_actions += tags;
458
        
459 460 461
        return toggle_actions;
    }
    
462 463 464 465 466
    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);
        
467
        // Sort criteria.
468
        Gtk.RadioActionEntry[] sort_crit_actions = new Gtk.RadioActionEntry[0];
469
        
470 471 472 473 474
        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;
475
        
476 477 478 479 480
        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;
481
        
482 483 484 485 486
        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;
487 488 489
        
        action_group.add_radio_actions(sort_crit_actions, sort_by, on_sort_changed);
        
490
        // Sort order.
491
        Gtk.RadioActionEntry[] sort_order_actions = new Gtk.RadioActionEntry[0];
492
        
493
        Gtk.RadioActionEntry ascending = { "SortAscending", Gtk.Stock.SORT_ASCENDING,
494 495 496 497
            TRANSLATABLE, null, TRANSLATABLE, SORT_ORDER_ASCENDING };
        ascending.label = _("_Ascending");
        ascending.tooltip = _("Sort photos in an ascending order");
        sort_order_actions += ascending;
498
        
499
        Gtk.RadioActionEntry descending = { "SortDescending", Gtk.Stock.SORT_DESCENDING,
500 501 502 503
            TRANSLATABLE, null, TRANSLATABLE, SORT_ORDER_DESCENDING };
        descending.label = _("D_escending");
        descending.tooltip = _("Sort photos in a descending order");
        sort_order_actions += descending;
504 505 506 507 508
        
        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);
509
    }
510 511 512
    
    protected override void update_actions(int selected_count, int count) {
        set_action_sensitive("Export", selected_count > 0);
513
        set_action_sensitive("EditTitle", selected_count > 0);
514 515
        set_action_sensitive("IncreaseSize", get_thumb_size() < Thumbnail.MAX_SCALE);
        set_action_sensitive("DecreaseSize", get_thumb_size() > Thumbnail.MIN_SCALE);
516
        set_action_sensitive("RemoveFromLibrary", selected_count > 0);
517
        set_action_sensitive("MoveToTrash", selected_count > 0);
518 519 520 521 522 523
        
        if (DesktopIntegration.is_send_to_installed())
            set_action_sensitive("SendTo", selected_count > 0);
        else
            set_action_visible("SendTo", false);
        
524 525 526
        set_action_sensitive("Rate", selected_count > 0);
        update_rating_sensitivities();
        
527 528
        update_development_menu_item_sensitivity();
        
529 530 531
        set_action_sensitive("PlayVideo", selected_count == 1
            && get_view().get_selected_source_at(0) is Video);
        
Jim Nelson's avatar
Jim Nelson committed
532 533
        update_flag_action(selected_count);
        
534 535 536
        base.update_actions(selected_count, count);
    }
    
537 538 539 540 541 542 543 544
    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;
            }
        }
Jim Nelson's avatar
Jim Nelson committed
545 546
    }
    
547 548 549 550 551 552 553 554 555 556 557
    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());
    }
558
    
559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596
    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);
        }
    }
    
Jim Nelson's avatar
Jim Nelson committed
597 598 599 600
    private void update_flag_action(int selected_count) {
        set_action_sensitive("Flag", selected_count > 0);
        
        string flag_label = Resources.FLAG_MENU;
601

Jim Nelson's avatar
Jim Nelson committed
602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623
        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;
        }
    }
    
624 625 626 627
    public override Core.ViewTracker? get_view_tracker() {
        return tracker;
    }
    
628 629 630 631 632
    public void set_display_ratings(bool display) {
        get_view().freeze_notifications();
        get_view().set_property(Thumbnail.PROP_SHOW_RATINGS, display);
        get_view().thaw_notifications();
        
633
        Gtk.ToggleAction? action = get_action("ViewRatings") as Gtk.ToggleAction;
634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670
        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();
    }
    
    public static int get_global_thumbnail_scale() {
        if (ZoomSliderAssembly.global_slider_adjustment == null) {
Lucas Beeler's avatar
Lucas Beeler committed
671
            int persisted_scale = Config.Facade.get_instance().get_photo_thumbnail_scale();
672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696
            ZoomSliderAssembly.global_slider_adjustment = new Gtk.Adjustment(
                ZoomSliderAssembly.scale_to_slider(persisted_scale), 0,
                ZoomSliderAssembly.scale_to_slider(Thumbnail.MAX_SCALE), 1, 10, 0);
        }

        return ZoomSliderAssembly.slider_to_scale(ZoomSliderAssembly.global_slider_adjustment.get_value());
    }

    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);
        }
    }
Eric Gregory's avatar
Eric Gregory committed
697
    
698 699 700 701
    private void on_send_to() {
        DesktopIntegration.send_to((Gee.Collection<MediaSource>) get_view().get_selected_sources());
    }
    
702 703 704 705
    protected void on_play_video() {
        if (get_view().get_selected_count() != 1)
            return;
        
706 707 708
        Video? video = get_view().get_selected_at(0).get_source() as Video;
        if (video == null)
            return;
709 710 711 712 713 714 715 716 717
        
        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));
        }
    }

718 719
    protected override bool on_app_key_pressed(Gdk.EventKey event) {
        bool handled = true;
720
        switch (Gdk.keyval_name(event.keyval)) {
721 722 723
            case "equal":
            case "plus":
            case "KP_Add":
724
                activate_action("IncreaseSize");
725 726 727 728 729
            break;
            
            case "minus":
            case "underscore":
            case "KP_Subtract":
730
                activate_action("DecreaseSize");
731
            break;
732
            
733
            case "period":
734
                activate_action("IncreaseRating");
735 736 737
            break;
            
            case "comma":
738
                activate_action("DecreaseRating");
739
            break;
740
            
741
            case "KP_1":
742
                activate_action("RateOne");
743 744 745
            break;
            
            case "KP_2":
746
                activate_action("RateTwo");
747
            break;
748
            
749
            case "KP_3":
750
                activate_action("RateThree");
751
            break;
752
            
753
            case "KP_4":
754
                activate_action("RateFour");
755
            break;
756
            
757
            case "KP_5":
758
                activate_action("RateFive");
759
            break;
760
            
761
            case "KP_0":
762
                activate_action("RateUnrated");
763
            break;
764
            
765
            case "KP_9":
766
                activate_action("RateRejected");
767 768 769 770
            break;
            
            case "exclam":
                if (get_ctrl_pressed())
771
                    get_search_view_filter().set_rating_filter(RatingFilter.ONE_OR_HIGHER);
772
            break;
773
            
774 775
            case "at":
                if (get_ctrl_pressed())
776
                    get_search_view_filter().set_rating_filter(RatingFilter.TWO_OR_HIGHER);
777 778 779 780
            break;

            case "numbersign":
                if (get_ctrl_pressed())
781
                    get_search_view_filter().set_rating_filter(RatingFilter.THREE_OR_HIGHER);
782 783 784 785
            break;

            case "dollar":
                if (get_ctrl_pressed())
786
                    get_search_view_filter().set_rating_filter(RatingFilter.FOUR_OR_HIGHER);
787 788 789 790
            break;

            case "percent":
                if (get_ctrl_pressed())
791
                    get_search_view_filter().set_rating_filter(RatingFilter.FIVE_OR_HIGHER);
792 793 794 795
            break;

            case "parenright":
                if (get_ctrl_pressed())
796
                    get_search_view_filter().set_rating_filter(RatingFilter.UNRATED_OR_HIGHER);
797 798 799 800
            break;

            case "parenleft":
                if (get_ctrl_pressed())
801
                    get_search_view_filter().set_rating_filter(RatingFilter.REJECTED_OR_HIGHER);
802
            break;
803 804 805
            
            case "asterisk":
                if (get_ctrl_pressed())
806
                    get_search_view_filter().set_rating_filter(RatingFilter.REJECTED_ONLY);
807 808
            break;
            
809
            case "slash":
810
                activate_action("Flag");
811 812
            break;
            
813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830
            default:
                handled = false;
            break;
        }
        
        return handled ? true : base.on_app_key_pressed(event);
    }

    public override void switched_to() {
        base.switched_to();
        
        // the global thumbnail scale could've changed while another page was displayed, so
        // make sure that this page's thumb size matches the global thumbnail scale
        if (get_global_thumbnail_scale() != get_thumb_size())
            set_thumb_size(get_global_thumbnail_scale());

        // set display options to match Configuration toggles (which can change while switched away)
        get_view().freeze_notifications();
Lucas Beeler's avatar
Lucas Beeler committed
831 832 833
        set_display_titles(Config.Facade.get_instance().get_display_photo_titles());
        set_display_ratings(Config.Facade.get_instance().get_display_photo_ratings());
        set_display_tags(Config.Facade.get_instance().get_display_photo_tags());
834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856
        get_view().thaw_notifications();

        sync_sort();
    }

    protected void connect_slider(ZoomSliderAssembly slider) {
        connected_slider = slider;
        connected_slider.zoom_changed.connect(on_zoom_changed);
    }
    
    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());
    }
    
857
    protected abstract void on_export();
858 859 860 861 862 863 864 865

    protected virtual void on_increase_size() {
        increase_zoom_level();
    }

    protected virtual void on_decrease_size() {
        decrease_zoom_level();
    }
866 867 868 869 870 871 872

    private void on_add_tags() {
        if (get_view().get_selected_count() == 0)
            return;
        
        AddTagsDialog dialog = new AddTagsDialog();
        string[]? names = dialog.execute();
873
        
874
        if (names != null) {
875 876
            get_command_manager().execute(new AddTagsCommand(
                HierarchicalTagIndex.get_global_index().get_paths_for_names_array(names),
877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
                (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();
        
901
        Gtk.ToggleAction? action = get_action("ViewTags") as Gtk.ToggleAction;
902 903 904 905 906 907 908 909
        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()));
    }
Jim Nelson's avatar
Jim Nelson committed
910 911 912 913 914
    
    private void on_flag_unflag() {
        if (get_view().get_selected_count() == 0)
            return;
        
915 916
        Gee.Collection<MediaSource> sources =
            (Gee.Collection<MediaSource>) get_view().get_selected_sources_of_type(typeof(MediaSource));
Jim Nelson's avatar
Jim Nelson committed
917 918 919
        
        // If all are flagged, then unflag, otherwise flag
        bool flag = false;
920
        foreach (MediaSource source in sources) {
Jim Nelson's avatar
Jim Nelson committed
921 922 923 924 925 926 927 928 929 930 931
            Flaggable? flaggable = source as Flaggable;
            if (flaggable != null && !flaggable.is_flagged()) {
                flag = true;
                
                break;
            }
        }
        
        get_command_manager().execute(new FlagUnflagCommand(sources, flag));
    }
    
932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989
    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);
    }

990 991 992 993
    private void on_remove_from_library() {
        remove_photos_from_library((Gee.Collection<LibraryPhoto>) get_view().get_selected_sources());
    }
    
994
    protected virtual void on_move_to_trash() {
995 996
        CheckerboardItem? restore_point = get_view().get_next(cursor) as CheckerboardItem; 
        
997 998 999 1000
        if (get_view().get_selected_count() > 0) {
            get_command_manager().execute(new TrashUntrashPhotosCommand(
                (Gee.Collection<MediaSource>) get_view().get_selected_sources(), true));
        }
1001 1002 1003 1004

        if ((restore_point != null) && (get_view().contains(restore_point))) {
            set_cursor(restore_point);
        }
1005 1006
    }

1007
    protected virtual void on_edit_title() {
1008
        if (get_view().get_selected_count() == 0)
1009 1010
            return;
        
1011
        Gee.List<MediaSource> media_sources = (Gee.List<MediaSource>) get_view().get_selected_sources();
1012
        
1013 1014 1015 1016
        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));
1017 1018 1019 1020 1021 1022 1023
    }

    protected virtual void on_display_titles(Gtk.Action action) {
        bool display = ((Gtk.ToggleAction) action).get_active();
        
        set_display_titles(display);
        
Lucas Beeler's avatar
Lucas Beeler committed
1024
        Config.Facade.get_instance().set_display_photo_titles(display);
1025 1026 1027 1028 1029 1030 1031
    }

    protected virtual void on_display_ratings(Gtk.Action action) {
        bool display = ((Gtk.ToggleAction) action).get_active();
        
        set_display_ratings(display);
        
Lucas Beeler's avatar
Lucas Beeler committed
1032
        Config.Facade.get_instance().set_display_photo_ratings(display);
1033 1034
    }

1035 1036 1037 1038 1039
    protected virtual void on_display_tags(Gtk.Action action) {
        bool display = ((Gtk.ToggleAction) action).get_active();
        
        set_display_tags(display);
        
Lucas Beeler's avatar
Lucas Beeler committed
1040
        Config.Facade.get_instance().set_display_photo_tags(display);
1041 1042
    }

1043 1044 1045 1046 1047 1048 1049 1050 1051
    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);

    protected virtual void on_sort_changed() {
        get_view().set_comparator(get_sort_comparator(), get_sort_comparator_predicate());

        set_config_photos_sort(get_sort_order() == SORT_ORDER_ASCENDING, get_sort_criteria());
    }
1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064
    
    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;
        
1065 1066 1067
        // 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()) {
1068 1069 1070
            Photo? p = view.get_source() as Photo;
            if (p != null && (!rd.is_equivalent(p.get_raw_developer())))
                to_set.add(view);
1071 1072 1073
        }
        
        SetRawDeveloperCommand command = new SetRawDeveloperCommand(to_set, rd);
1074 1075 1076
        get_command_manager().execute(command);
        update_development_menu_item_sensitivity();
    }
1077 1078 1079 1080

    protected override void set_display_titles(bool display) {
        base.set_display_titles(display);
    
1081
        Gtk.ToggleAction? action = get_action("ViewTitle") as Gtk.ToggleAction;
1082 1083 1084 1085 1086 1087 1088
        if (action != null)
            action.set_active(display);
    }

    protected int get_sort_criteria() {
        // any member of the group knows the current value
        Gtk.RadioAction action = (Gtk.RadioAction) ui.get_action(
1089
            "/MenuBar/ViewMenu/SortPhotos/SortByTitle");
1090 1091 1092 1093 1094 1095 1096 1097 1098 1099
        assert(action != null);
        
        int value = action.get_current_value();

        return value;
    }
    
    protected int get_sort_order() {
        // any member of the group knows the current value
        Gtk.RadioAction action = (Gtk.RadioAction) ui.get_action(
1100
            "/MenuBar/ViewMenu/SortPhotos/SortAscending");
1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155
        assert(action != null);
        
        int value = action.get_current_value();
        
        return value;
    }
    
    protected bool is_sort_ascending() {
        return get_sort_order() == SORT_ORDER_ASCENDING;
    }
       
    protected Comparator get_sort_comparator() {
        switch (get_sort_criteria()) {
            case SortBy.TITLE:
                if (is_sort_ascending())
                    return Thumbnail.title_ascending_comparator;
                else
                    return Thumbnail.title_descending_comparator;
            
            case SortBy.EXPOSURE_DATE:
                if (is_sort_ascending())
                    return Thumbnail.exposure_time_ascending_comparator;
                else
                    return Thumbnail.exposure_time_desending_comparator;
            
            case SortBy.RATING:
                if (is_sort_ascending())
                    return Thumbnail.rating_ascending_comparator;
                else
                    return Thumbnail.rating_descending_comparator;
            
            default:
                error("Unknown sort criteria: %s", get_sort_criteria().to_string());
        }
    }
    
    protected ComparatorPredicate get_sort_comparator_predicate() {
        switch (get_sort_criteria()) {
            case SortBy.TITLE:
                return Thumbnail.title_comparator_predicate;
            
            case SortBy.EXPOSURE_DATE:
                return Thumbnail.exposure_time_comparator_predicate;
            
            case SortBy.RATING:
                return Thumbnail.rating_comparator_predicate;
            
            default:
                error("Unknown sort criteria: %s", get_sort_criteria().to_string());
        }
    }
          
    protected string get_sortby_path(int sort_by) {
        switch(sort_by) {
            case SortBy.TITLE:
1156
                return "/MenuBar/ViewMenu/SortPhotos/SortByTitle";
1157
            
1158
            case SortBy.EXPOSURE_DATE:
1159
                return "/MenuBar/ViewMenu/SortPhotos/SortByExposureDate";
1160
            
1161
            case SortBy.RATING:
1162
                return "/MenuBar/ViewMenu/SortPhotos/SortByRating";
1163
            
1164 1165 1166 1167 1168 1169
            default:
                error("Unknown sort criteria: %d", sort_by);
        }
    }

    protected void sync_sort() {
1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183
        // 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.
        get_view().set_comparator(get_sort_comparator(), get_sort_comparator_predicate());
1184 1185 1186 1187
    }

    public override void destroy() {
        disconnect_slider();
1188
        
1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228
        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());
            set_global_thumbnail_scale(new_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());
            set_global_thumbnail_scale(new_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();
1229 1230 1231
        
        set_action_sensitive("IncreaseSize", new_scale < Thumbnail.MAX_SCALE);
        set_action_sensitive("DecreaseSize", new_scale > Thumbnail.MIN_SCALE);
1232 1233 1234 1235
    }

    public int get_thumb_size() {
        if (get_checkerboard_layout().get_scale() <= 0)
Lucas Beeler's avatar
Lucas Beeler committed
1236
            get_checkerboard_layout().set_scale(Config.Facade.get_instance().get_photo_thumbnail_scale());
1237 1238 1239 1240 1241
            
        return get_checkerboard_layout().get_scale();
    }
}