1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */
2 /* this file is part of evince, a gnome document viewer
3 *
4 * Copyright (C) 2004 Red Hat, Inc.
5 * Copyright (C) 2004, 2005 Anders Carlsson <andersca@gnome.org>
6 *
7 * Authors:
8 * Jonathan Blandford <jrb@alum.mit.edu>
9 * Anders Carlsson <andersca@gnome.org>
10 *
11 * Evince is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * Evince is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24 */
25
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29
30 #include <string.h>
31
32 #include <glib/gi18n.h>
33 #include <gtk/gtk.h>
34
35 #include <cairo-gobject.h>
36
37 #include "ev-document-misc.h"
38 #include "ev-job-scheduler.h"
39 #include "ev-sidebar.h"
40 #include "ev-sidebar-page.h"
41 #include "ev-sidebar-thumbnails.h"
42 #include "ev-utils.h"
43 #include "ev-window.h"
44
45 #define THUMBNAIL_WIDTH 100
46
47 typedef struct _EvThumbsSize
48 {
49 gint width;
50 gint height;
51 } EvThumbsSize;
52
53 typedef struct _EvThumbsSizeCache {
54 gboolean uniform;
55 gint uniform_width;
56 gint uniform_height;
57 EvThumbsSize *sizes;
58 } EvThumbsSizeCache;
59
60 struct _EvSidebarThumbnailsPrivate {
61 GtkWidget *swindow;
62 GtkWidget *icon_view;
63 GtkAdjustment *vadjustment;
64 GtkListStore *list_store;
65 GHashTable *loading_icons;
66 EvDocument *document;
67 EvDocumentModel *model;
68 EvThumbsSizeCache *size_cache;
69 gint width;
70
71 gint n_pages, pages_done;
72
73 int rotation;
74 gboolean inverted_colors;
75 gboolean blank_first_dual_mode; /* flag for when we're using a blank first thumbnail
76 * for dual mode with !odd_left preference. Issue #30 */
77 /* Visible pages */
78 gint start_page, end_page;
79 };
80
81 enum {
82 COLUMN_PAGE_STRING,
83 COLUMN_SURFACE,
84 COLUMN_THUMBNAIL_SET,
85 COLUMN_JOB,
86 NUM_COLUMNS
87 };
88
89 enum {
90 PROP_0,
91 PROP_WIDGET,
92 };
93
94 static void ev_sidebar_thumbnails_clear_model (EvSidebarThumbnails *sidebar);
95 static gboolean ev_sidebar_thumbnails_support_document (EvSidebarPage *sidebar_page,
96 EvDocument *document);
97 static void ev_sidebar_thumbnails_page_iface_init (EvSidebarPageInterface *iface);
98 static const gchar* ev_sidebar_thumbnails_get_label (EvSidebarPage *sidebar_page);
99 static void ev_sidebar_thumbnails_set_current_page (EvSidebarThumbnails *sidebar,
100 gint page);
101 static void thumbnail_job_completed_callback (EvJobThumbnail *job,
102 EvSidebarThumbnails *sidebar_thumbnails);
103 static void ev_sidebar_thumbnails_reload (EvSidebarThumbnails *sidebar_thumbnails);
104 static void adjustment_changed_cb (EvSidebarThumbnails *sidebar_thumbnails);
105 static void check_toggle_blank_first_dual_mode (EvSidebarThumbnails *sidebar_thumbnails);
106 static void check_toggle_blank_first_dual_mode_when_resizing (EvSidebarThumbnails *sidebar_thumbnails);
107
108 G_DEFINE_TYPE_EXTENDED (EvSidebarThumbnails,
109 ev_sidebar_thumbnails,
110 GTK_TYPE_BOX,
111 0,
112 G_ADD_PRIVATE (EvSidebarThumbnails)
113 G_IMPLEMENT_INTERFACE (EV_TYPE_SIDEBAR_PAGE,
114 ev_sidebar_thumbnails_page_iface_init))
115
116 /* Thumbnails dimensions cache */
117 #define EV_THUMBNAILS_SIZE_CACHE_KEY "ev-thumbnails-size-cache"
118
119 static void
get_thumbnail_size_for_page(EvDocument * document,guint page,gint * width,gint * height)120 get_thumbnail_size_for_page (EvDocument *document,
121 guint page,
122 gint *width,
123 gint *height)
124 {
125 gdouble scale;
126 gdouble w, h;
127
128 ev_document_get_page_size (document, page, &w, &h);
129 scale = (gdouble)THUMBNAIL_WIDTH / w;
130
131 *width = MAX ((gint)(w * scale + 0.5), 1);
132 *height = MAX ((gint)(h * scale + 0.5), 1);
133 }
134
135 static EvThumbsSizeCache *
ev_thumbnails_size_cache_new(EvDocument * document)136 ev_thumbnails_size_cache_new (EvDocument *document)
137 {
138 EvThumbsSizeCache *cache;
139 gint i, n_pages;
140 EvThumbsSize *thumb_size;
141
142 cache = g_new0 (EvThumbsSizeCache, 1);
143
144 if (ev_document_is_page_size_uniform (document)) {
145 cache->uniform = TRUE;
146 get_thumbnail_size_for_page (document, 0,
147 &cache->uniform_width,
148 &cache->uniform_height);
149 return cache;
150 }
151
152 n_pages = ev_document_get_n_pages (document);
153 cache->sizes = g_new0 (EvThumbsSize, n_pages);
154
155 for (i = 0; i < n_pages; i++) {
156 thumb_size = &(cache->sizes[i]);
157 get_thumbnail_size_for_page (document, i,
158 &thumb_size->width,
159 &thumb_size->height);
160 }
161
162 return cache;
163 }
164
165 static void
ev_thumbnails_size_cache_get_size(EvThumbsSizeCache * cache,gint page,gint rotation,gint * width,gint * height)166 ev_thumbnails_size_cache_get_size (EvThumbsSizeCache *cache,
167 gint page,
168 gint rotation,
169 gint *width,
170 gint *height)
171 {
172 gint w, h;
173
174 if (cache->uniform) {
175 w = cache->uniform_width;
176 h = cache->uniform_height;
177 } else {
178 EvThumbsSize *thumb_size;
179
180 thumb_size = &(cache->sizes[page]);
181
182 w = thumb_size->width;
183 h = thumb_size->height;
184 }
185
186 if (rotation == 0 || rotation == 180) {
187 if (width) *width = w;
188 if (height) *height = h;
189 } else {
190 if (width) *width = h;
191 if (height) *height = w;
192 }
193 }
194
195 static void
ev_thumbnails_size_cache_free(EvThumbsSizeCache * cache)196 ev_thumbnails_size_cache_free (EvThumbsSizeCache *cache)
197 {
198 if (cache->sizes) {
199 g_free (cache->sizes);
200 cache->sizes = NULL;
201 }
202
203 g_free (cache);
204 }
205
206 static EvThumbsSizeCache *
ev_thumbnails_size_cache_get(EvDocument * document)207 ev_thumbnails_size_cache_get (EvDocument *document)
208 {
209 EvThumbsSizeCache *cache;
210
211 cache = g_object_get_data (G_OBJECT (document), EV_THUMBNAILS_SIZE_CACHE_KEY);
212 if (!cache) {
213 cache = ev_thumbnails_size_cache_new (document);
214 g_object_set_data_full (G_OBJECT (document),
215 EV_THUMBNAILS_SIZE_CACHE_KEY,
216 cache,
217 (GDestroyNotify)ev_thumbnails_size_cache_free);
218 }
219
220 return cache;
221 }
222
223 static gboolean
ev_sidebar_thumbnails_page_is_in_visible_range(EvSidebarThumbnails * sidebar,guint page)224 ev_sidebar_thumbnails_page_is_in_visible_range (EvSidebarThumbnails *sidebar,
225 guint page)
226 {
227 GtkTreePath *path;
228 GtkTreePath *start, *end;
229 gboolean retval;
230 GList *selection;
231
232 selection = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (sidebar->priv->icon_view));
233 if (!selection)
234 return FALSE;
235
236 path = (GtkTreePath *)selection->data;
237
238 /* We don't handle or expect multiple selection. */
239 g_assert (selection->next == NULL);
240 g_list_free (selection);
241
242 if (!gtk_icon_view_get_visible_range (GTK_ICON_VIEW (sidebar->priv->icon_view), &start, &end)) {
243 gtk_tree_path_free (path);
244 return FALSE;
245 }
246
247 retval = gtk_tree_path_compare (path, start) >= 0 && gtk_tree_path_compare (path, end) <= 0;
248 gtk_tree_path_free (path);
249 gtk_tree_path_free (start);
250 gtk_tree_path_free (end);
251
252 return retval;
253 }
254
255 static void
ev_sidebar_thumbnails_dispose(GObject * object)256 ev_sidebar_thumbnails_dispose (GObject *object)
257 {
258 EvSidebarThumbnails *sidebar_thumbnails = EV_SIDEBAR_THUMBNAILS (object);
259
260 if (sidebar_thumbnails->priv->loading_icons) {
261 g_hash_table_destroy (sidebar_thumbnails->priv->loading_icons);
262 sidebar_thumbnails->priv->loading_icons = NULL;
263 }
264
265 if (sidebar_thumbnails->priv->list_store) {
266 ev_sidebar_thumbnails_clear_model (sidebar_thumbnails);
267 g_object_unref (sidebar_thumbnails->priv->list_store);
268 sidebar_thumbnails->priv->list_store = NULL;
269 }
270
271 G_OBJECT_CLASS (ev_sidebar_thumbnails_parent_class)->dispose (object);
272 }
273
274 static void
ev_sidebar_thumbnails_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)275 ev_sidebar_thumbnails_get_property (GObject *object,
276 guint prop_id,
277 GValue *value,
278 GParamSpec *pspec)
279 {
280 EvSidebarThumbnails *sidebar = EV_SIDEBAR_THUMBNAILS (object);
281
282 switch (prop_id) {
283 case PROP_WIDGET:
284 g_value_set_object (value, sidebar->priv->icon_view);
285 break;
286 default:
287 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
288 break;
289 }
290 }
291
292 static void
ev_sidebar_thumbnails_map(GtkWidget * widget)293 ev_sidebar_thumbnails_map (GtkWidget *widget)
294 {
295 EvSidebarThumbnails *sidebar;
296
297 sidebar = EV_SIDEBAR_THUMBNAILS (widget);
298
299 GTK_WIDGET_CLASS (ev_sidebar_thumbnails_parent_class)->map (widget);
300
301 adjustment_changed_cb (sidebar);
302 }
303
304 static void
ev_sidebar_fullscreen_cb(EvSidebarThumbnails * sidebar)305 ev_sidebar_fullscreen_cb (EvSidebarThumbnails *sidebar)
306 {
307 /* After activating or deactivating fullscreen mode, the sidebar
308 * window is automatically moved to its start, while scroll bar
309 * stays in its original position.
310 *
311 * The sidebar window move is unwanted and unsolicited, and it's
312 * most probably caused by GtkIconView or GtkScrolledWindow bug.
313 *
314 * Workaround this by having the sidebar sync its window with the
315 * current scroll position after a fullscreen operation, do that by
316 * just emitting a "value-changed" on the current scroll adjustment.
317 * Fixes https://bugzilla.gnome.org/show_bug.cgi?id=783404 */
318 g_signal_emit_by_name (sidebar->priv->vadjustment, "value-changed");
319 }
320
321 static void
ev_sidebar_check_reset_current_page(EvSidebarThumbnails * sidebar)322 ev_sidebar_check_reset_current_page (EvSidebarThumbnails *sidebar)
323 {
324 guint page;
325
326 if (!sidebar->priv->model)
327 return;
328
329 page = ev_document_model_get_page (sidebar->priv->model);
330 if (!ev_sidebar_thumbnails_page_is_in_visible_range (sidebar, page))
331 ev_sidebar_thumbnails_set_current_page (sidebar, page);
332 }
333
334 static void
ev_sidebar_thumbnails_size_allocate(GtkWidget * widget,GtkAllocation * allocation)335 ev_sidebar_thumbnails_size_allocate (GtkWidget *widget,
336 GtkAllocation *allocation)
337 {
338 EvSidebarThumbnails *sidebar = EV_SIDEBAR_THUMBNAILS (widget);
339
340 GTK_WIDGET_CLASS (ev_sidebar_thumbnails_parent_class)->size_allocate (widget, allocation);
341
342 if (allocation->width != sidebar->priv->width) {
343 sidebar->priv->width = allocation->width;
344
345 /* Might have a new number of columns, reset current page */
346 ev_sidebar_check_reset_current_page (sidebar);
347 }
348 }
349
350 static void
ev_sidebar_thumbnails_class_init(EvSidebarThumbnailsClass * ev_sidebar_thumbnails_class)351 ev_sidebar_thumbnails_class_init (EvSidebarThumbnailsClass *ev_sidebar_thumbnails_class)
352 {
353 GObjectClass *g_object_class;
354 GtkWidgetClass *widget_class;
355
356 g_object_class = G_OBJECT_CLASS (ev_sidebar_thumbnails_class);
357 widget_class = GTK_WIDGET_CLASS (ev_sidebar_thumbnails_class);
358
359 g_object_class->dispose = ev_sidebar_thumbnails_dispose;
360 g_object_class->get_property = ev_sidebar_thumbnails_get_property;
361 widget_class->map = ev_sidebar_thumbnails_map;
362 widget_class->size_allocate = ev_sidebar_thumbnails_size_allocate;
363
364 #if GTK_CHECK_VERSION(3, 20, 0)
365 gtk_widget_class_set_css_name (widget_class, "evsidebarthumbnails");
366 #endif
367
368 g_object_class_override_property (g_object_class,
369 PROP_WIDGET,
370 "main-widget");
371 }
372
373 GtkWidget *
ev_sidebar_thumbnails_new(void)374 ev_sidebar_thumbnails_new (void)
375 {
376 GtkWidget *ev_sidebar_thumbnails;
377
378 ev_sidebar_thumbnails = g_object_new (EV_TYPE_SIDEBAR_THUMBNAILS,
379 "orientation", GTK_ORIENTATION_VERTICAL,
380 NULL);
381
382
383 return ev_sidebar_thumbnails;
384 }
385
386 static cairo_surface_t *
ev_sidebar_thumbnails_get_loading_icon(EvSidebarThumbnails * sidebar_thumbnails,gint width,gint height)387 ev_sidebar_thumbnails_get_loading_icon (EvSidebarThumbnails *sidebar_thumbnails,
388 gint width,
389 gint height)
390 {
391 EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
392 cairo_surface_t *icon;
393 gchar *key;
394
395 key = g_strdup_printf ("%dx%d", width, height);
396 icon = g_hash_table_lookup (priv->loading_icons, key);
397 if (!icon) {
398 gboolean inverted_colors;
399 gint device_scale = 1;
400
401 #ifdef HAVE_HIDPI_SUPPORT
402 device_scale = gtk_widget_get_scale_factor (GTK_WIDGET (sidebar_thumbnails));
403 #endif
404
405 inverted_colors = ev_document_model_get_inverted_colors (priv->model);
406 icon = ev_document_misc_render_loading_thumbnail_surface (GTK_WIDGET (sidebar_thumbnails),
407 width * device_scale,
408 height * device_scale,
409 inverted_colors);
410 g_hash_table_insert (priv->loading_icons, key, icon);
411 } else {
412 g_free (key);
413 }
414
415 return icon;
416 }
417
418 static void
cancel_running_jobs(EvSidebarThumbnails * sidebar_thumbnails,gint start_page,gint end_page)419 cancel_running_jobs (EvSidebarThumbnails *sidebar_thumbnails,
420 gint start_page,
421 gint end_page)
422 {
423 EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
424 GtkTreePath *path;
425 GtkTreeIter iter;
426 gboolean result;
427
428 g_assert (start_page <= end_page);
429
430 path = gtk_tree_path_new_from_indices (start_page, -1);
431 for (result = gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->list_store), &iter, path);
432 result && start_page <= end_page;
433 result = gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->list_store), &iter), start_page ++) {
434 EvJobThumbnail *job;
435 gboolean thumbnail_set;
436
437 gtk_tree_model_get (GTK_TREE_MODEL (priv->list_store),
438 &iter,
439 COLUMN_JOB, &job,
440 COLUMN_THUMBNAIL_SET, &thumbnail_set,
441 -1);
442
443 if (thumbnail_set) {
444 g_assert (job == NULL);
445 continue;
446 }
447
448 if (job) {
449 g_signal_handlers_disconnect_by_func (job, thumbnail_job_completed_callback, sidebar_thumbnails);
450 ev_job_cancel (EV_JOB (job));
451 g_object_unref (job);
452 }
453
454 gtk_list_store_set (priv->list_store, &iter,
455 COLUMN_JOB, NULL,
456 COLUMN_THUMBNAIL_SET, FALSE,
457 -1);
458 }
459 gtk_tree_path_free (path);
460 }
461
462 static void
get_size_for_page(EvSidebarThumbnails * sidebar_thumbnails,gint page,gint * width_return,gint * height_return)463 get_size_for_page (EvSidebarThumbnails *sidebar_thumbnails,
464 gint page,
465 gint *width_return,
466 gint *height_return)
467 {
468 EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
469 gdouble width, height;
470 gint thumbnail_height;
471 gint device_scale = 1;
472
473 #ifdef HAVE_HIDPI_SUPPORT
474 device_scale = gtk_widget_get_scale_factor (GTK_WIDGET (sidebar_thumbnails));
475 #endif
476 ev_document_get_page_size (priv->document, page, &width, &height);
477 thumbnail_height = (int)(THUMBNAIL_WIDTH * height / width + 0.5);
478
479 if (priv->rotation == 90 || priv->rotation == 270) {
480 *width_return = thumbnail_height * device_scale;
481 *height_return = THUMBNAIL_WIDTH * device_scale;
482 } else {
483 *width_return = THUMBNAIL_WIDTH * device_scale;
484 *height_return = thumbnail_height * device_scale;
485 }
486 }
487
488 static void
add_range(EvSidebarThumbnails * sidebar_thumbnails,gint start_page,gint end_page)489 add_range (EvSidebarThumbnails *sidebar_thumbnails,
490 gint start_page,
491 gint end_page)
492 {
493 EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
494 GtkTreePath *path;
495 GtkTreeIter iter;
496 gboolean result;
497 gint page = start_page;
498
499 g_assert (start_page <= end_page);
500
501 if (priv->blank_first_dual_mode)
502 page--;
503
504 path = gtk_tree_path_new_from_indices (start_page, -1);
505 for (result = gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->list_store), &iter, path);
506 result && page <= end_page;
507 result = gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->list_store), &iter), page ++) {
508 EvJob *job;
509 gboolean thumbnail_set;
510
511 gtk_tree_model_get (GTK_TREE_MODEL (priv->list_store), &iter,
512 COLUMN_JOB, &job,
513 COLUMN_THUMBNAIL_SET, &thumbnail_set,
514 -1);
515
516 if (job == NULL && !thumbnail_set) {
517 gint thumbnail_width, thumbnail_height;
518 get_size_for_page (sidebar_thumbnails, page, &thumbnail_width, &thumbnail_height);
519
520 job = ev_job_thumbnail_new_with_target_size (priv->document,
521 page, priv->rotation,
522 thumbnail_width, thumbnail_height);
523 ev_job_thumbnail_set_has_frame (EV_JOB_THUMBNAIL (job), FALSE);
524 ev_job_thumbnail_set_output_format (EV_JOB_THUMBNAIL (job), EV_JOB_THUMBNAIL_SURFACE);
525 g_object_set_data_full (G_OBJECT (job), "tree_iter",
526 gtk_tree_iter_copy (&iter),
527 (GDestroyNotify) gtk_tree_iter_free);
528 g_signal_connect (job, "finished",
529 G_CALLBACK (thumbnail_job_completed_callback),
530 sidebar_thumbnails);
531 gtk_list_store_set (priv->list_store, &iter,
532 COLUMN_JOB, job,
533 -1);
534 ev_job_scheduler_push_job (EV_JOB (job), EV_JOB_PRIORITY_HIGH);
535
536 /* The queue and the list own a ref to the job now */
537 g_object_unref (job);
538 } else if (job) {
539 g_object_unref (job);
540 }
541 }
542 gtk_tree_path_free (path);
543 }
544
545 /* This modifies start */
546 static void
update_visible_range(EvSidebarThumbnails * sidebar_thumbnails,gint start_page,gint end_page)547 update_visible_range (EvSidebarThumbnails *sidebar_thumbnails,
548 gint start_page,
549 gint end_page)
550 {
551 EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
552 int old_start_page, old_end_page;
553 int n_pages_in_visible_range;
554
555 /* Preload before and after current visible scrolling range, the same amount of
556 * thumbs in it, to help prevent thumbnail creation happening in the user's sight.
557 * https://bugzilla.gnome.org/show_bug.cgi?id=342110#c15 */
558 n_pages_in_visible_range = (end_page - start_page) + 1;
559 start_page = MAX (0, start_page - n_pages_in_visible_range);
560 end_page = MIN (priv->n_pages - 1, end_page + n_pages_in_visible_range);
561
562 old_start_page = priv->start_page;
563 old_end_page = priv->end_page;
564
565 if (start_page == old_start_page &&
566 end_page == old_end_page)
567 return;
568
569 /* Clear the areas we no longer display */
570 if (old_start_page >= 0 && old_start_page < start_page)
571 cancel_running_jobs (sidebar_thumbnails, old_start_page, MIN (start_page - 1, old_end_page));
572
573 if (old_end_page > 0 && old_end_page > end_page)
574 cancel_running_jobs (sidebar_thumbnails, MAX (end_page + 1, old_start_page), old_end_page);
575
576 add_range (sidebar_thumbnails, start_page, end_page);
577
578 priv->start_page = start_page;
579 priv->end_page = end_page;
580 }
581
582 static void
adjustment_changed_cb(EvSidebarThumbnails * sidebar_thumbnails)583 adjustment_changed_cb (EvSidebarThumbnails *sidebar_thumbnails)
584 {
585 EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
586 GtkTreePath *path = NULL;
587 GtkTreePath *path2 = NULL;
588 gdouble page_size;
589
590 /* Widget is not currently visible */
591 if (!gtk_widget_get_mapped (GTK_WIDGET (sidebar_thumbnails)))
592 return;
593
594 page_size = gtk_adjustment_get_page_size (priv->vadjustment);
595
596 if (page_size == 0)
597 return;
598
599 if (priv->icon_view) {
600 if (! gtk_widget_get_realized (priv->icon_view))
601 return;
602 if (! gtk_icon_view_get_visible_range (GTK_ICON_VIEW (priv->icon_view), &path, &path2))
603 return;
604 } else {
605 return;
606 }
607
608 if (path && path2) {
609 update_visible_range (sidebar_thumbnails,
610 gtk_tree_path_get_indices (path)[0],
611 gtk_tree_path_get_indices (path2)[0]);
612 }
613
614 gtk_tree_path_free (path);
615 gtk_tree_path_free (path2);
616 }
617
618 static void
ev_sidebar_thumbnails_fill_model(EvSidebarThumbnails * sidebar_thumbnails)619 ev_sidebar_thumbnails_fill_model (EvSidebarThumbnails *sidebar_thumbnails)
620 {
621 EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
622 GtkTreeIter iter;
623 int i;
624 gint prev_width = -1;
625 gint prev_height = -1;
626
627 for (i = 0; i < sidebar_thumbnails->priv->n_pages; i++) {
628 gchar *page_label;
629 gchar *page_string;
630 cairo_surface_t *loading_icon = NULL;
631 gint width, height;
632
633 page_label = ev_document_get_page_label (priv->document, i);
634 page_string = g_markup_printf_escaped ("<i>%s</i>", page_label);
635 ev_thumbnails_size_cache_get_size (sidebar_thumbnails->priv->size_cache, i,
636 sidebar_thumbnails->priv->rotation,
637 &width, &height);
638 if (!loading_icon || (width != prev_width && height != prev_height)) {
639 loading_icon =
640 ev_sidebar_thumbnails_get_loading_icon (sidebar_thumbnails,
641 width, height);
642 }
643
644 prev_width = width;
645 prev_height = height;
646
647 gtk_list_store_append (priv->list_store, &iter);
648 gtk_list_store_set (priv->list_store, &iter,
649 COLUMN_PAGE_STRING, page_string,
650 COLUMN_SURFACE, loading_icon,
651 COLUMN_THUMBNAIL_SET, FALSE,
652 -1);
653 g_free (page_label);
654 g_free (page_string);
655 }
656 }
657
658 static void
ev_sidebar_icon_selection_changed(GtkIconView * icon_view,EvSidebarThumbnails * ev_sidebar_thumbnails)659 ev_sidebar_icon_selection_changed (GtkIconView *icon_view,
660 EvSidebarThumbnails *ev_sidebar_thumbnails)
661 {
662 EvSidebarThumbnailsPrivate *priv = ev_sidebar_thumbnails->priv;
663 GtkTreePath *path;
664 GList *selected;
665 int page;
666
667 selected = gtk_icon_view_get_selected_items (icon_view);
668 if (selected == NULL)
669 return;
670
671 /* We don't handle or expect multiple selection. */
672 g_assert (selected->next == NULL);
673
674 path = selected->data;
675 page = gtk_tree_path_get_indices (path)[0];
676
677 if (priv->blank_first_dual_mode) {
678 if (page == 0) {
679 gtk_icon_view_unselect_path (icon_view, path);
680 gtk_tree_path_free (path);
681 g_list_free (selected);
682 return;
683 }
684 page--;
685
686 }
687
688 gtk_tree_path_free (path);
689 g_list_free (selected);
690
691 ev_document_model_set_page (priv->model, page);
692 }
693
694 static void
ev_sidebar_init_icon_view(EvSidebarThumbnails * ev_sidebar_thumbnails)695 ev_sidebar_init_icon_view (EvSidebarThumbnails *ev_sidebar_thumbnails)
696 {
697 EvSidebarThumbnailsPrivate *priv;
698 GtkCellRenderer *renderer;
699
700 priv = ev_sidebar_thumbnails->priv;
701
702 priv->icon_view = gtk_icon_view_new_with_model (GTK_TREE_MODEL (priv->list_store));
703
704 renderer = g_object_new (GTK_TYPE_CELL_RENDERER_PIXBUF,
705 "xalign", 0.5,
706 "yalign", 1.0,
707 NULL);
708 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->icon_view), renderer, FALSE);
709 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (priv->icon_view),
710 renderer, "surface", 1, NULL);
711
712 renderer = g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
713 "alignment", PANGO_ALIGN_CENTER,
714 "wrap-mode", PANGO_WRAP_WORD_CHAR,
715 "xalign", 0.5,
716 "yalign", 0.0,
717 "width", THUMBNAIL_WIDTH,
718 "wrap-width", THUMBNAIL_WIDTH,
719 NULL);
720 gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (priv->icon_view), renderer, FALSE);
721 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (priv->icon_view),
722 renderer, "markup", 0, NULL);
723 g_signal_connect (priv->icon_view, "selection-changed",
724 G_CALLBACK (ev_sidebar_icon_selection_changed), ev_sidebar_thumbnails);
725
726 g_signal_connect_swapped (priv->icon_view, "size-allocate",
727 G_CALLBACK (check_toggle_blank_first_dual_mode_when_resizing), ev_sidebar_thumbnails);
728
729 g_signal_connect_data (priv->model, "notify::dual-page",
730 G_CALLBACK (check_toggle_blank_first_dual_mode), ev_sidebar_thumbnails,
731 NULL, G_CONNECT_SWAPPED | G_CONNECT_AFTER);
732
733 g_signal_connect_data (priv->model, "notify::dual-odd-left",
734 G_CALLBACK (check_toggle_blank_first_dual_mode), ev_sidebar_thumbnails,
735 NULL, G_CONNECT_SWAPPED | G_CONNECT_AFTER);
736
737 check_toggle_blank_first_dual_mode (ev_sidebar_thumbnails);
738 gtk_container_add (GTK_CONTAINER (priv->swindow), priv->icon_view);
739 gtk_widget_show (priv->icon_view);
740 }
741
742 static void
ev_sidebar_thumbnails_device_scale_factor_changed_cb(EvSidebarThumbnails * sidebar_thumbnails,GParamSpec * pspec)743 ev_sidebar_thumbnails_device_scale_factor_changed_cb (EvSidebarThumbnails *sidebar_thumbnails,
744 GParamSpec *pspec)
745
746 {
747 ev_sidebar_thumbnails_reload (sidebar_thumbnails);
748 }
749
750 static void
ev_sidebar_thumbnails_row_changed(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)751 ev_sidebar_thumbnails_row_changed (GtkTreeModel *model,
752 GtkTreePath *path,
753 GtkTreeIter *iter,
754 gpointer data)
755 {
756 guint signal_id;
757
758 signal_id = GPOINTER_TO_UINT (data);
759
760 /* PREVENT GtkIconView "row-changed" handler to be reached, as it will
761 * perform a full invalidate and relayout of all items, See bug:
762 * https://bugzilla.gnome.org/show_bug.cgi?id=691448#c9 */
763 g_signal_stop_emission (model, signal_id, 0);
764 }
765
766 static void
ev_sidebar_thumbnails_init(EvSidebarThumbnails * ev_sidebar_thumbnails)767 ev_sidebar_thumbnails_init (EvSidebarThumbnails *ev_sidebar_thumbnails)
768 {
769 EvSidebarThumbnailsPrivate *priv;
770 guint signal_id;
771
772 priv = ev_sidebar_thumbnails->priv = ev_sidebar_thumbnails_get_instance_private (ev_sidebar_thumbnails);
773 priv->blank_first_dual_mode = FALSE;
774
775 priv->list_store = gtk_list_store_new (NUM_COLUMNS,
776 G_TYPE_STRING,
777 CAIRO_GOBJECT_TYPE_SURFACE,
778 G_TYPE_BOOLEAN,
779 EV_TYPE_JOB_THUMBNAIL);
780
781 signal_id = g_signal_lookup ("row-changed", GTK_TYPE_TREE_MODEL);
782 g_signal_connect (GTK_TREE_MODEL (priv->list_store), "row-changed",
783 G_CALLBACK (ev_sidebar_thumbnails_row_changed),
784 GUINT_TO_POINTER (signal_id));
785
786 priv->swindow = gtk_scrolled_window_new (NULL, NULL);
787
788 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->swindow),
789 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
790 priv->vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->swindow));
791 g_signal_connect_data (priv->vadjustment, "value-changed",
792 G_CALLBACK (adjustment_changed_cb),
793 ev_sidebar_thumbnails, NULL,
794 G_CONNECT_SWAPPED | G_CONNECT_AFTER);
795 g_signal_connect_swapped (priv->swindow, "size-allocate",
796 G_CALLBACK (adjustment_changed_cb),
797 ev_sidebar_thumbnails);
798 gtk_box_pack_start (GTK_BOX (ev_sidebar_thumbnails), priv->swindow, TRUE, TRUE, 0);
799
800 g_signal_connect (ev_sidebar_thumbnails, "notify::scale-factor",
801 G_CALLBACK (ev_sidebar_thumbnails_device_scale_factor_changed_cb), NULL);
802
803 /* Put it all together */
804 gtk_widget_show_all (priv->swindow);
805 }
806
807 static void
ev_sidebar_thumbnails_set_current_page(EvSidebarThumbnails * sidebar,gint page)808 ev_sidebar_thumbnails_set_current_page (EvSidebarThumbnails *sidebar,
809 gint page)
810 {
811 GtkTreePath *path;
812
813 if (sidebar->priv->blank_first_dual_mode)
814 page++;
815
816 path = gtk_tree_path_new_from_indices (page, -1);
817
818 if (sidebar->priv->icon_view) {
819
820 g_signal_handlers_block_by_func
821 (sidebar->priv->icon_view,
822 G_CALLBACK (ev_sidebar_icon_selection_changed), sidebar);
823
824 gtk_icon_view_select_path (GTK_ICON_VIEW (sidebar->priv->icon_view), path);
825
826 g_signal_handlers_unblock_by_func
827 (sidebar->priv->icon_view,
828 G_CALLBACK (ev_sidebar_icon_selection_changed), sidebar);
829
830 gtk_icon_view_scroll_to_path (GTK_ICON_VIEW (sidebar->priv->icon_view), path, FALSE, 0.0, 0.0);
831 }
832
833 gtk_tree_path_free (path);
834 }
835
836 static void
page_changed_cb(EvSidebarThumbnails * sidebar,gint old_page,gint new_page)837 page_changed_cb (EvSidebarThumbnails *sidebar,
838 gint old_page,
839 gint new_page)
840 {
841 ev_sidebar_thumbnails_set_current_page (sidebar, new_page);
842 }
843
844 static gboolean
refresh(EvSidebarThumbnails * sidebar_thumbnails)845 refresh (EvSidebarThumbnails *sidebar_thumbnails)
846 {
847 adjustment_changed_cb (sidebar_thumbnails);
848 return FALSE;
849 }
850
851 static void
ev_sidebar_thumbnails_reload(EvSidebarThumbnails * sidebar_thumbnails)852 ev_sidebar_thumbnails_reload (EvSidebarThumbnails *sidebar_thumbnails)
853 {
854 EvDocumentModel *model;
855
856 if (sidebar_thumbnails->priv->loading_icons)
857 g_hash_table_remove_all (sidebar_thumbnails->priv->loading_icons);
858
859 if (sidebar_thumbnails->priv->document == NULL ||
860 sidebar_thumbnails->priv->n_pages <= 0)
861 return;
862
863 model = sidebar_thumbnails->priv->model;
864
865 ev_sidebar_thumbnails_clear_model (sidebar_thumbnails);
866 ev_sidebar_thumbnails_fill_model (sidebar_thumbnails);
867
868 /* Trigger a redraw */
869 sidebar_thumbnails->priv->start_page = -1;
870 sidebar_thumbnails->priv->end_page = -1;
871 ev_sidebar_thumbnails_set_current_page (sidebar_thumbnails,
872 ev_document_model_get_page (model));
873 g_idle_add ((GSourceFunc)refresh, sidebar_thumbnails);
874 }
875
876 static void
ev_sidebar_thumbnails_rotation_changed_cb(EvDocumentModel * model,GParamSpec * pspec,EvSidebarThumbnails * sidebar_thumbnails)877 ev_sidebar_thumbnails_rotation_changed_cb (EvDocumentModel *model,
878 GParamSpec *pspec,
879 EvSidebarThumbnails *sidebar_thumbnails)
880 {
881 gint rotation = ev_document_model_get_rotation (model);
882
883 sidebar_thumbnails->priv->rotation = rotation;
884 ev_sidebar_thumbnails_reload (sidebar_thumbnails);
885 }
886
887 static void
ev_sidebar_thumbnails_inverted_colors_changed_cb(EvDocumentModel * model,GParamSpec * pspec,EvSidebarThumbnails * sidebar_thumbnails)888 ev_sidebar_thumbnails_inverted_colors_changed_cb (EvDocumentModel *model,
889 GParamSpec *pspec,
890 EvSidebarThumbnails *sidebar_thumbnails)
891 {
892 gboolean inverted_colors = ev_document_model_get_inverted_colors (model);
893
894 sidebar_thumbnails->priv->inverted_colors = inverted_colors;
895 ev_sidebar_thumbnails_reload (sidebar_thumbnails);
896 }
897
898 static void
thumbnail_job_completed_callback(EvJobThumbnail * job,EvSidebarThumbnails * sidebar_thumbnails)899 thumbnail_job_completed_callback (EvJobThumbnail *job,
900 EvSidebarThumbnails *sidebar_thumbnails)
901 {
902 GtkWidget *widget = GTK_WIDGET (sidebar_thumbnails);
903 EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
904 GtkTreeIter *iter;
905 cairo_surface_t *surface;
906 #ifdef HAVE_HIDPI_SUPPORT
907 gint device_scale;
908 #endif
909
910 if (ev_job_is_failed (EV_JOB (job)))
911 return;
912
913 #ifdef HAVE_HIDPI_SUPPORT
914 device_scale = gtk_widget_get_scale_factor (widget);
915 cairo_surface_set_device_scale (job->thumbnail_surface, device_scale, device_scale);
916 #endif
917
918 surface = ev_document_misc_render_thumbnail_surface_with_frame (widget,
919 job->thumbnail_surface,
920 -1, -1);
921
922 iter = (GtkTreeIter *) g_object_get_data (G_OBJECT (job), "tree_iter");
923 if (priv->inverted_colors)
924 ev_document_misc_invert_surface (surface);
925 gtk_list_store_set (priv->list_store,
926 iter,
927 COLUMN_SURFACE, surface,
928 COLUMN_THUMBNAIL_SET, TRUE,
929 COLUMN_JOB, NULL,
930 -1);
931 cairo_surface_destroy (surface);
932
933 gtk_widget_queue_draw (priv->icon_view);
934 }
935
936 static void
ev_sidebar_thumbnails_document_changed_cb(EvDocumentModel * model,GParamSpec * pspec,EvSidebarThumbnails * sidebar_thumbnails)937 ev_sidebar_thumbnails_document_changed_cb (EvDocumentModel *model,
938 GParamSpec *pspec,
939 EvSidebarThumbnails *sidebar_thumbnails)
940 {
941 EvDocument *document = ev_document_model_get_document (model);
942 EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
943
944 if (ev_document_get_n_pages (document) <= 0 ||
945 !ev_document_check_dimensions (document)) {
946 return;
947 }
948
949 priv->size_cache = ev_thumbnails_size_cache_get (document);
950 priv->document = document;
951 priv->n_pages = ev_document_get_n_pages (document);
952 priv->rotation = ev_document_model_get_rotation (model);
953 priv->inverted_colors = ev_document_model_get_inverted_colors (model);
954 if (priv->loading_icons) {
955 g_hash_table_remove_all (priv->loading_icons);
956 } else {
957 priv->loading_icons = g_hash_table_new_full (g_str_hash,
958 g_str_equal,
959 (GDestroyNotify)g_free,
960 (GDestroyNotify)cairo_surface_destroy);
961 }
962
963 ev_sidebar_thumbnails_clear_model (sidebar_thumbnails);
964 ev_sidebar_thumbnails_fill_model (sidebar_thumbnails);
965
966 if (! priv->icon_view) {
967 ev_sidebar_init_icon_view (sidebar_thumbnails);
968 g_object_notify (G_OBJECT (sidebar_thumbnails), "main_widget");
969 } else {
970 gtk_widget_queue_resize (priv->icon_view);
971 }
972
973 /* Connect to the signal and trigger a fake callback */
974 g_signal_connect_swapped (priv->model, "page-changed",
975 G_CALLBACK (page_changed_cb),
976 sidebar_thumbnails);
977 g_signal_connect (priv->model, "notify::rotation",
978 G_CALLBACK (ev_sidebar_thumbnails_rotation_changed_cb),
979 sidebar_thumbnails);
980 g_signal_connect (priv->model, "notify::inverted-colors",
981 G_CALLBACK (ev_sidebar_thumbnails_inverted_colors_changed_cb),
982 sidebar_thumbnails);
983 g_signal_connect_swapped (priv->model, "notify::fullscreen",
984 G_CALLBACK (ev_sidebar_fullscreen_cb),
985 sidebar_thumbnails);
986 sidebar_thumbnails->priv->start_page = -1;
987 sidebar_thumbnails->priv->end_page = -1;
988 ev_sidebar_thumbnails_set_current_page (sidebar_thumbnails,
989 ev_document_model_get_page (model));
990 adjustment_changed_cb (sidebar_thumbnails);
991 }
992
993 static void
ev_sidebar_thumbnails_set_model(EvSidebarPage * sidebar_page,EvDocumentModel * model)994 ev_sidebar_thumbnails_set_model (EvSidebarPage *sidebar_page,
995 EvDocumentModel *model)
996 {
997 EvSidebarThumbnails *sidebar_thumbnails = EV_SIDEBAR_THUMBNAILS (sidebar_page);
998 EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
999
1000 if (priv->model == model)
1001 return;
1002
1003 priv->model = model;
1004 g_signal_connect (model, "notify::document",
1005 G_CALLBACK (ev_sidebar_thumbnails_document_changed_cb),
1006 sidebar_page);
1007 }
1008
1009 static gboolean
ev_sidebar_thumbnails_clear_job(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)1010 ev_sidebar_thumbnails_clear_job (GtkTreeModel *model,
1011 GtkTreePath *path,
1012 GtkTreeIter *iter,
1013 gpointer data)
1014 {
1015 EvJob *job;
1016
1017 gtk_tree_model_get (model, iter, COLUMN_JOB, &job, -1);
1018
1019 if (job != NULL) {
1020 ev_job_cancel (job);
1021 g_signal_handlers_disconnect_by_func (job, thumbnail_job_completed_callback, data);
1022 g_object_unref (job);
1023 }
1024
1025 return FALSE;
1026 }
1027
1028 static void
ev_sidebar_thumbnails_clear_model(EvSidebarThumbnails * sidebar_thumbnails)1029 ev_sidebar_thumbnails_clear_model (EvSidebarThumbnails *sidebar_thumbnails)
1030 {
1031 EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
1032
1033 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->list_store), ev_sidebar_thumbnails_clear_job, sidebar_thumbnails);
1034 gtk_list_store_clear (priv->list_store);
1035 }
1036
1037 static gboolean
ev_sidebar_thumbnails_support_document(EvSidebarPage * sidebar_page,EvDocument * document)1038 ev_sidebar_thumbnails_support_document (EvSidebarPage *sidebar_page,
1039 EvDocument *document)
1040 {
1041 return TRUE;
1042 }
1043
1044 static const gchar*
ev_sidebar_thumbnails_get_label(EvSidebarPage * sidebar_page)1045 ev_sidebar_thumbnails_get_label (EvSidebarPage *sidebar_page)
1046 {
1047 return _("Thumbnails");
1048 }
1049
1050 static void
ev_sidebar_thumbnails_page_iface_init(EvSidebarPageInterface * iface)1051 ev_sidebar_thumbnails_page_iface_init (EvSidebarPageInterface *iface)
1052 {
1053 iface->support_document = ev_sidebar_thumbnails_support_document;
1054 iface->set_model = ev_sidebar_thumbnails_set_model;
1055 iface->get_label = ev_sidebar_thumbnails_get_label;
1056 }
1057
1058 static gboolean
iter_is_blank_thumbnail(GtkTreeModel * tree_model,GtkTreeIter * iter)1059 iter_is_blank_thumbnail (GtkTreeModel *tree_model,
1060 GtkTreeIter *iter)
1061 {
1062 cairo_surface_t *surface = NULL;
1063 EvJob *job = NULL;
1064 gboolean thumbnail_set = FALSE;
1065
1066 gtk_tree_model_get (tree_model, iter,
1067 COLUMN_SURFACE, &surface,
1068 COLUMN_THUMBNAIL_SET, &thumbnail_set,
1069 COLUMN_JOB, &job, -1);
1070
1071 /* The blank thumbnail item can be distinguished among all
1072 * other items in the GtkIconView as it's the only one which
1073 * has the COLUMN_SURFACE as NULL while COLUMN_THUMBNAIL_SET
1074 * is set to TRUE. */
1075 return surface == NULL && job == NULL && thumbnail_set;
1076 }
1077
1078 /* Returns the total horizontal(left+right) width of thumbnail frames.
1079 * As it was added in ev_document_misc_render_thumbnail_frame() */
1080 static gint
ev_sidebar_thumbnails_frame_horizontal_width(EvSidebarThumbnails * sidebar)1081 ev_sidebar_thumbnails_frame_horizontal_width (EvSidebarThumbnails *sidebar)
1082 {
1083 GtkWidget *widget;
1084 GtkStyleContext *context;
1085 GtkStateFlags state;
1086 GtkBorder border = {0, };
1087 gint offset;
1088
1089 widget = GTK_WIDGET (sidebar);
1090 context = gtk_widget_get_style_context (widget);
1091 state = gtk_widget_get_state_flags (widget);
1092
1093 gtk_style_context_save (context);
1094
1095 gtk_style_context_add_class (context, "page-thumbnail");
1096 gtk_style_context_get_border (context, state, &border);
1097 offset = border.left + border.right;
1098
1099 gtk_style_context_restore (context);
1100
1101 return offset;
1102 }
1103
1104 static EvWindow *
ev_sidebar_thumbnails_get_ev_window(EvSidebarThumbnails * sidebar)1105 ev_sidebar_thumbnails_get_ev_window (EvSidebarThumbnails *sidebar)
1106 {
1107 GtkWidget *toplevel;
1108
1109 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (sidebar));
1110
1111 if (toplevel && EV_IS_WINDOW (toplevel))
1112 return EV_WINDOW (toplevel);
1113
1114 return NULL;
1115 }
1116
1117 static EvSidebar *
ev_sidebar_thumbnails_get_ev_sidebar(EvSidebarThumbnails * sidebar)1118 ev_sidebar_thumbnails_get_ev_sidebar (EvSidebarThumbnails *sidebar)
1119 {
1120 EvWindow *window = ev_sidebar_thumbnails_get_ev_window (sidebar);
1121 if (window)
1122 return EV_SIDEBAR (ev_window_get_sidebar (window));
1123
1124 return NULL;
1125 }
1126
1127 static gboolean
check_reset_current_page(EvSidebarThumbnails * sidebar_thumbnails)1128 check_reset_current_page (EvSidebarThumbnails *sidebar_thumbnails)
1129 {
1130 ev_sidebar_check_reset_current_page (sidebar_thumbnails);
1131 return G_SOURCE_REMOVE;
1132 }
1133
1134 static void
ev_sidebar_thumbnails_get_column_widths(EvSidebarThumbnails * sidebar,gint * one_column_width,gint * two_columns_width,gint * three_columns_width)1135 ev_sidebar_thumbnails_get_column_widths (EvSidebarThumbnails *sidebar,
1136 gint *one_column_width,
1137 gint *two_columns_width,
1138 gint *three_columns_width)
1139 {
1140 EvSidebarThumbnailsPrivate *priv;
1141 GtkIconView *icon_view;
1142 gint margin, column_spacing, item_padding, thumbnail_width;
1143 static gint frame_horizontal_width;
1144
1145 priv = sidebar->priv;
1146 icon_view = GTK_ICON_VIEW (priv->icon_view);
1147
1148 ev_thumbnails_size_cache_get_size (priv->size_cache, 0,
1149 priv->rotation,
1150 &thumbnail_width, NULL);
1151
1152 margin = gtk_icon_view_get_margin (icon_view);
1153 column_spacing = gtk_icon_view_get_column_spacing (icon_view);
1154 item_padding = gtk_icon_view_get_item_padding (icon_view);
1155 frame_horizontal_width = ev_sidebar_thumbnails_frame_horizontal_width (sidebar);
1156
1157 if (one_column_width) {
1158 *one_column_width = 2 * margin +
1159 2 * item_padding +
1160 1 * frame_horizontal_width +
1161 1 * thumbnail_width +
1162 column_spacing;
1163 }
1164 if (two_columns_width) {
1165 *two_columns_width = 2 * margin +
1166 4 * item_padding +
1167 2 * frame_horizontal_width +
1168 2 * thumbnail_width +
1169 column_spacing;
1170 }
1171 if (three_columns_width) {
1172 *three_columns_width = 2 * margin +
1173 6 * item_padding +
1174 3 * frame_horizontal_width +
1175 3 * thumbnail_width +
1176 2 * column_spacing;
1177 }
1178 }
1179
1180 static void
ev_sidebar_thumbnails_get_sidebar_width(EvSidebarThumbnails * sidebar,gint * sidebar_width)1181 ev_sidebar_thumbnails_get_sidebar_width (EvSidebarThumbnails *sidebar,
1182 gint *sidebar_width)
1183 {
1184 EvWindow *ev_window;
1185 EvSidebarThumbnailsPrivate *priv;
1186
1187 if (!sidebar_width)
1188 return;
1189
1190 priv = sidebar->priv;
1191
1192 if (priv->width == 0) {
1193 ev_window = ev_sidebar_thumbnails_get_ev_window (sidebar);
1194 if (ev_window)
1195 *sidebar_width = ev_window_get_metadata_sidebar_size (ev_window);
1196 else
1197 *sidebar_width = 0;
1198 } else {
1199 *sidebar_width = priv->width;
1200 }
1201 }
1202
1203 static void
ev_sidebar_thumbnails_set_sidebar_width(EvSidebarThumbnails * sidebar,gint sidebar_width)1204 ev_sidebar_thumbnails_set_sidebar_width (EvSidebarThumbnails *sidebar,
1205 gint sidebar_width)
1206 {
1207 EvWindow *ev_window;
1208 EvSidebarThumbnailsPrivate *priv;
1209
1210 if (sidebar_width <= 0)
1211 return;
1212
1213 ev_window = ev_sidebar_thumbnails_get_ev_window (sidebar);
1214 if (ev_window) {
1215 priv = sidebar->priv;
1216 priv->width = sidebar_width;
1217 ev_window_set_divider_position (ev_window, sidebar_width);
1218 g_idle_add ((GSourceFunc)check_reset_current_page, sidebar);
1219 }
1220 }
1221
1222 /* Returns whether the thumbnail sidebar is currently showing
1223 * items in a two columns layout */
1224 static gboolean
ev_sidebar_thumbnails_is_two_columns(EvSidebarThumbnails * sidebar)1225 ev_sidebar_thumbnails_is_two_columns (EvSidebarThumbnails *sidebar)
1226 {
1227 gint sidebar_width, two_columns_width, three_columns_width;
1228
1229 ev_sidebar_thumbnails_get_column_widths (sidebar, NULL, &two_columns_width,
1230 &three_columns_width);
1231 ev_sidebar_thumbnails_get_sidebar_width (sidebar, &sidebar_width);
1232
1233 return sidebar_width >= two_columns_width &&
1234 sidebar_width < three_columns_width;
1235 }
1236
1237 /* Returns whether the thumbnail sidebar is currently showing
1238 * items in a one column layout */
1239 static gboolean
ev_sidebar_thumbnails_is_one_column(EvSidebarThumbnails * sidebar)1240 ev_sidebar_thumbnails_is_one_column (EvSidebarThumbnails *sidebar)
1241 {
1242 gint sidebar_width, one_column_width, two_columns_width;
1243
1244 ev_sidebar_thumbnails_get_column_widths (sidebar, &one_column_width,
1245 &two_columns_width, NULL);
1246 ev_sidebar_thumbnails_get_sidebar_width (sidebar, &sidebar_width);
1247
1248 return sidebar_width >= one_column_width &&
1249 sidebar_width < two_columns_width;
1250 }
1251
1252 /* If thumbnail sidebar is currently being displayed then
1253 * it resizes it to be of one column width layout */
1254 static void
ev_sidebar_thumbnails_to_one_column(EvSidebarThumbnails * sidebar)1255 ev_sidebar_thumbnails_to_one_column (EvSidebarThumbnails *sidebar)
1256 {
1257 gint one_column_width;
1258
1259 ev_sidebar_thumbnails_get_column_widths (sidebar, &one_column_width,
1260 NULL, NULL);
1261 ev_sidebar_thumbnails_set_sidebar_width (sidebar, one_column_width);
1262 }
1263
1264 /* If thumbnail sidebar is currently being displayed then
1265 * it resizes it to be of two columns width layout */
1266 static void
ev_sidebar_thumbnails_to_two_columns(EvSidebarThumbnails * sidebar)1267 ev_sidebar_thumbnails_to_two_columns (EvSidebarThumbnails *sidebar)
1268 {
1269 gint two_columns_width;
1270
1271 ev_sidebar_thumbnails_get_column_widths (sidebar, NULL,
1272 &two_columns_width, NULL);
1273 ev_sidebar_thumbnails_set_sidebar_width (sidebar, two_columns_width);
1274 }
1275
1276 /* This function checks whether the conditions to insert a blank first item
1277 * in dual mode are met and activates/deactivates the mode accordingly (that
1278 * is setting priv->blank_first_dual_mode on/off).
1279 *
1280 * Aditionally, we resize the sidebar when asked to do so by following
1281 * parameter:
1282 * @resize_sidebar: When true, we will resize sidebar to be one or
1283 * two columns width, according to whether dual mode is currently off/on.
1284 * Exception is when user has set sidebar to >=3 columns width, in that
1285 * case we won't do any resizing to not affect that custom setting */
1286 static void
check_toggle_blank_first_dual_mode_real(EvSidebarThumbnails * sidebar_thumbnails,gboolean resize_sidebar)1287 check_toggle_blank_first_dual_mode_real (EvSidebarThumbnails *sidebar_thumbnails,
1288 gboolean resize_sidebar)
1289 {
1290 EvSidebarThumbnailsPrivate *priv;
1291 GtkTreeModel *tree_model;
1292 EvSidebar *sidebar;
1293 GtkTreeIter first;
1294 gboolean should_be_enabled, is_two_columns, is_one_column, odd_pages_left, dual_mode;
1295
1296 priv = sidebar_thumbnails->priv;
1297
1298 dual_mode = ev_document_model_get_dual_page (priv->model);
1299 odd_pages_left = ev_document_model_get_dual_page_odd_pages_left (priv->model);
1300 should_be_enabled = dual_mode && !odd_pages_left;
1301
1302 is_two_columns = ev_sidebar_thumbnails_is_two_columns (sidebar_thumbnails);
1303 is_one_column = !is_two_columns && ev_sidebar_thumbnails_is_one_column (sidebar_thumbnails);
1304 if (should_be_enabled)
1305 should_be_enabled = is_two_columns || resize_sidebar;
1306
1307 if (should_be_enabled && !priv->blank_first_dual_mode) {
1308 /* Do enable it */
1309 tree_model = GTK_TREE_MODEL (priv->list_store);
1310
1311 if (!gtk_tree_model_get_iter_first (tree_model, &first))
1312 return;
1313
1314 if (is_two_columns || is_one_column) {
1315 priv->blank_first_dual_mode = TRUE;
1316 if (iter_is_blank_thumbnail (tree_model, &first))
1317 return; /* extra check */
1318
1319 gtk_list_store_insert_with_values (priv->list_store, &first, 0,
1320 COLUMN_SURFACE, NULL,
1321 COLUMN_THUMBNAIL_SET, TRUE,
1322 COLUMN_JOB, NULL,
1323 -1);
1324 }
1325 if (resize_sidebar && is_one_column) {
1326 sidebar = ev_sidebar_thumbnails_get_ev_sidebar (sidebar_thumbnails);
1327 /* If sidebar is set to show thumbnails */
1328 if (sidebar && ev_sidebar_get_current_page (sidebar) == GTK_WIDGET (sidebar_thumbnails)) {
1329 ev_sidebar_thumbnails_to_two_columns (sidebar_thumbnails);
1330 }
1331 }
1332 } else if (!should_be_enabled && priv->blank_first_dual_mode) {
1333 /* Do disable it */
1334 tree_model = GTK_TREE_MODEL (priv->list_store);
1335
1336 if (!gtk_tree_model_get_iter_first (tree_model, &first))
1337 return;
1338
1339 priv->blank_first_dual_mode = FALSE;
1340 if (!iter_is_blank_thumbnail (tree_model, &first))
1341 return; /* extra check */
1342
1343 gtk_list_store_remove (priv->list_store, &first);
1344
1345 if (resize_sidebar && is_two_columns) {
1346 sidebar = ev_sidebar_thumbnails_get_ev_sidebar (sidebar_thumbnails);
1347 /* If dual_mode disabled and is_two_cols and sidebar is set to show thumbnails */
1348 if (!dual_mode && sidebar && is_two_columns &&
1349 ev_sidebar_get_current_page (sidebar) == GTK_WIDGET (sidebar_thumbnails)) {
1350 ev_sidebar_thumbnails_to_one_column (sidebar_thumbnails);
1351 }
1352 } else if (resize_sidebar && !is_one_column) {
1353 ev_sidebar_check_reset_current_page (sidebar_thumbnails);
1354 }
1355 } else if (resize_sidebar) {
1356 /* Match sidebar width with dual_mode when sidebar has currently a width of 1 or 2 columns */
1357 if (dual_mode && is_one_column) {
1358 sidebar = ev_sidebar_thumbnails_get_ev_sidebar (sidebar_thumbnails);
1359 if (sidebar && ev_sidebar_get_current_page (sidebar) == GTK_WIDGET (sidebar_thumbnails)) {
1360 ev_sidebar_thumbnails_to_two_columns (sidebar_thumbnails);
1361 }
1362 } else if (!dual_mode && is_two_columns) {
1363 sidebar = ev_sidebar_thumbnails_get_ev_sidebar (sidebar_thumbnails);
1364 if (sidebar && ev_sidebar_get_current_page (sidebar) == GTK_WIDGET (sidebar_thumbnails)) {
1365 ev_sidebar_thumbnails_to_one_column (sidebar_thumbnails);
1366 }
1367 } else {
1368 ev_sidebar_check_reset_current_page (sidebar_thumbnails);
1369 }
1370 } else {
1371 ev_sidebar_check_reset_current_page (sidebar_thumbnails);
1372 }
1373 }
1374
1375 /* Callback when manually resizing the sidebar */
1376 static void
check_toggle_blank_first_dual_mode_when_resizing(EvSidebarThumbnails * sidebar_thumbnails)1377 check_toggle_blank_first_dual_mode_when_resizing (EvSidebarThumbnails *sidebar_thumbnails)
1378 {
1379 check_toggle_blank_first_dual_mode_real (sidebar_thumbnails, FALSE);
1380 }
1381
1382 /* Callback when dual_mode or odd_left preferences are enabled/disabled by the user */
1383 static void
check_toggle_blank_first_dual_mode(EvSidebarThumbnails * sidebar_thumbnails)1384 check_toggle_blank_first_dual_mode (EvSidebarThumbnails *sidebar_thumbnails)
1385 {
1386 check_toggle_blank_first_dual_mode_real (sidebar_thumbnails, TRUE);
1387 }
1388