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