Evince
Evince is a document viewer capable of displaying multiple and single page document formats like PDF and Postscript.
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
ev-sidebar-thumbnails.c
Go to the documentation of this file.
1 /* this file is part of evince, a gnome document viewer
2  *
3  * Copyright (C) 2004 Red Hat, Inc.
4  * Copyright (C) 2004, 2005 Anders Carlsson <andersca@gnome.org>
5  *
6  * Authors:
7  * Jonathan Blandford <jrb@alum.mit.edu>
8  * Anders Carlsson <andersca@gnome.org>
9  *
10  * Evince is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * Evince is distributed in the hope that it will be useful, but
16  * WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  * General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23  */
24 
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28 
29 #include <string.h>
30 
31 #include <glib/gi18n.h>
32 #include <gtk/gtk.h>
33 
34 #include <cairo-gobject.h>
35 
36 #include "ev-document-misc.h"
37 #include "ev-job-scheduler.h"
38 #include "ev-sidebar-page.h"
39 #include "ev-sidebar-thumbnails.h"
40 #include "ev-utils.h"
41 #include "ev-window.h"
42 
43 #define THUMBNAIL_WIDTH 100
44 
45 /* The IconView doesn't scale nearly as well as the TreeView, so we arbitrarily
46  * limit its use */
47 #define MAX_ICON_VIEW_PAGE_COUNT 1500
48 
49 typedef struct _EvThumbsSize
50 {
51  gint width;
52  gint height;
53 } EvThumbsSize;
54 
55 typedef struct _EvThumbsSizeCache {
56  gboolean uniform;
61 
63  GtkWidget *swindow;
64  GtkWidget *icon_view;
65  GtkWidget *tree_view;
66  GtkAdjustment *vadjustment;
67  GtkListStore *list_store;
68  GHashTable *loading_icons;
72  gint width;
73 
75 
76  int rotation;
77  gboolean inverted_colors;
78 
79  /* Visible pages */
81 };
82 
83 enum {
89 };
90 
91 enum {
94 };
95 
97 static gboolean ev_sidebar_thumbnails_support_document (EvSidebarPage *sidebar_page,
98  EvDocument *document);
100 static const gchar* ev_sidebar_thumbnails_get_label (EvSidebarPage *sidebar_page);
102  gint page);
104  EvSidebarThumbnails *sidebar_thumbnails);
105 static void ev_sidebar_thumbnails_reload (EvSidebarThumbnails *sidebar_thumbnails);
106 static void adjustment_changed_cb (EvSidebarThumbnails *sidebar_thumbnails);
107 
109  ev_sidebar_thumbnails,
110  GTK_TYPE_BOX,
111  0,
112  G_IMPLEMENT_INTERFACE (EV_TYPE_SIDEBAR_PAGE,
114 
115 #define EV_SIDEBAR_THUMBNAILS_GET_PRIVATE(object) \
116  (G_TYPE_INSTANCE_GET_PRIVATE ((object), EV_TYPE_SIDEBAR_THUMBNAILS, EvSidebarThumbnailsPrivate));
117 
118 /* Thumbnails dimensions cache */
119 #define EV_THUMBNAILS_SIZE_CACHE_KEY "ev-thumbnails-size-cache"
120 
121 static void
122 get_thumbnail_size_for_page (EvDocument *document,
123  guint page,
124  gint *width,
125  gint *height)
126 {
127  gdouble scale;
128  gdouble w, h;
129 
130  ev_document_get_page_size (document, page, &w, &h);
131  scale = (gdouble)THUMBNAIL_WIDTH / w;
132 
133  *width = MAX ((gint)(w * scale + 0.5), 1);
134  *height = MAX ((gint)(h * scale + 0.5), 1);
135 }
136 
137 static EvThumbsSizeCache *
139 {
140  EvThumbsSizeCache *cache;
141  gint i, n_pages;
142  EvThumbsSize *thumb_size;
143 
144  cache = g_new0 (EvThumbsSizeCache, 1);
145 
146  if (ev_document_is_page_size_uniform (document)) {
147  cache->uniform = TRUE;
148  get_thumbnail_size_for_page (document, 0,
149  &cache->uniform_width,
150  &cache->uniform_height);
151  return cache;
152  }
153 
154  n_pages = ev_document_get_n_pages (document);
155  cache->sizes = g_new0 (EvThumbsSize, n_pages);
156 
157  for (i = 0; i < n_pages; i++) {
158  thumb_size = &(cache->sizes[i]);
159  get_thumbnail_size_for_page (document, i,
160  &thumb_size->width,
161  &thumb_size->height);
162  }
163 
164  return cache;
165 }
166 
167 static void
169  gint page,
170  gint rotation,
171  gint *width,
172  gint *height)
173 {
174  gint w, h;
175 
176  if (cache->uniform) {
177  w = cache->uniform_width;
178  h = cache->uniform_height;
179  } else {
180  EvThumbsSize *thumb_size;
181 
182  thumb_size = &(cache->sizes[page]);
183 
184  w = thumb_size->width;
185  h = thumb_size->height;
186  }
187 
188  if (rotation == 0 || rotation == 180) {
189  if (width) *width = w;
190  if (height) *height = h;
191  } else {
192  if (width) *width = h;
193  if (height) *height = w;
194  }
195 }
196 
197 static void
199 {
200  if (cache->sizes) {
201  g_free (cache->sizes);
202  cache->sizes = NULL;
203  }
204 
205  g_free (cache);
206 }
207 
208 static EvThumbsSizeCache *
210 {
211  EvThumbsSizeCache *cache;
212 
213  cache = g_object_get_data (G_OBJECT (document), EV_THUMBNAILS_SIZE_CACHE_KEY);
214  if (!cache) {
215  cache = ev_thumbnails_size_cache_new (document);
216  g_object_set_data_full (G_OBJECT (document),
218  cache,
219  (GDestroyNotify)ev_thumbnails_size_cache_free);
220  }
221 
222  return cache;
223 }
224 
225 static gboolean
227  guint page)
228 {
229  GtkTreePath *path;
230  GtkTreePath *start, *end;
231  gboolean retval;
232 
233  if (sidebar->priv->tree_view) {
234  GtkTreeSelection *selection;
235  GtkTreeIter iter;
236 
237  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (sidebar->priv->tree_view));
238  if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
239  return FALSE;
240 
241  path = gtk_tree_model_get_path (GTK_TREE_MODEL (sidebar->priv->list_store), &iter);
242  if (!gtk_tree_view_get_visible_range (GTK_TREE_VIEW (sidebar->priv->tree_view), &start, &end)) {
243  gtk_tree_path_free (path);
244  return FALSE;
245  }
246  } else {
247  GList *selection;
248 
249  selection = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (sidebar->priv->icon_view));
250  if (!selection)
251  return FALSE;
252 
253  path = (GtkTreePath *)selection->data;
254 
255  /* We don't handle or expect multiple selection. */
256  g_assert (selection->next == NULL);
257  g_list_free (selection);
258 
259  if (!gtk_icon_view_get_visible_range (GTK_ICON_VIEW (sidebar->priv->icon_view), &start, &end)) {
260  gtk_tree_path_free (path);
261  return FALSE;
262  }
263  }
264 
265  retval = gtk_tree_path_compare (path, start) >= 0 && gtk_tree_path_compare (path, end) <= 0;
266  gtk_tree_path_free (path);
267  gtk_tree_path_free (start);
268  gtk_tree_path_free (end);
269 
270  return retval;
271 }
272 
273 static void
275 {
276  EvSidebarThumbnails *sidebar_thumbnails = EV_SIDEBAR_THUMBNAILS (object);
277 
278  if (sidebar_thumbnails->priv->loading_icons) {
279  g_hash_table_destroy (sidebar_thumbnails->priv->loading_icons);
280  sidebar_thumbnails->priv->loading_icons = NULL;
281  }
282 
283  if (sidebar_thumbnails->priv->list_store) {
284  ev_sidebar_thumbnails_clear_model (sidebar_thumbnails);
285  g_object_unref (sidebar_thumbnails->priv->list_store);
286  sidebar_thumbnails->priv->list_store = NULL;
287  }
288 
289  G_OBJECT_CLASS (ev_sidebar_thumbnails_parent_class)->dispose (object);
290 }
291 
292 static void
294  guint prop_id,
295  GValue *value,
296  GParamSpec *pspec)
297 {
298  EvSidebarThumbnails *sidebar = EV_SIDEBAR_THUMBNAILS (object);
299 
300  switch (prop_id) {
301  case PROP_WIDGET:
302  if (sidebar->priv->tree_view)
303  g_value_set_object (value, sidebar->priv->tree_view);
304  else
305  g_value_set_object (value, sidebar->priv->icon_view);
306  break;
307  default:
308  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
309  break;
310  }
311 }
312 
313 static void
314 ev_sidebar_thumbnails_map (GtkWidget *widget)
315 {
316  EvSidebarThumbnails *sidebar;
317 
318  sidebar = EV_SIDEBAR_THUMBNAILS (widget);
319 
320  GTK_WIDGET_CLASS (ev_sidebar_thumbnails_parent_class)->map (widget);
321 
322  adjustment_changed_cb (sidebar);
323 }
324 
325 static void
327 {
328  /* After activating or deactivating fullscreen mode, the sidebar
329  * window is automatically moved to its start, while scroll bar
330  * stays in its original position.
331  *
332  * The sidebar window move is unwanted and unsolicited, and it's
333  * most probably caused by GtkIconView or GtkScrolledWindow bug.
334  *
335  * Workaround this by having the sidebar sync its window with the
336  * current scroll position after a fullscreen operation, do that by
337  * just emitting a "value-changed" on the current scroll adjustment.
338  * Fixes https://bugzilla.gnome.org/show_bug.cgi?id=783404 */
339  g_signal_emit_by_name (sidebar->priv->vadjustment, "value-changed");
340 }
341 
342 static void
344  GtkAllocation *allocation)
345 {
346  EvSidebarThumbnails *sidebar = EV_SIDEBAR_THUMBNAILS (widget);
347 
348  GTK_WIDGET_CLASS (ev_sidebar_thumbnails_parent_class)->size_allocate (widget, allocation);
349 
350  if (allocation->width != sidebar->priv->width) {
351  guint page;
352 
353  sidebar->priv->width = allocation->width;
354 
355  /* Might have a new number of columns, reset current page */
356  if (!sidebar->priv->model)
357  return;
358 
359  page = ev_document_model_get_page (sidebar->priv->model);
362  }
363 }
364 
365 static void
367 {
368  GObjectClass *g_object_class;
369  GtkWidgetClass *widget_class;
370 
371  g_object_class = G_OBJECT_CLASS (ev_sidebar_thumbnails_class);
372  widget_class = GTK_WIDGET_CLASS (ev_sidebar_thumbnails_class);
373 
374  g_object_class->dispose = ev_sidebar_thumbnails_dispose;
375  g_object_class->get_property = ev_sidebar_thumbnails_get_property;
376  widget_class->map = ev_sidebar_thumbnails_map;
377  widget_class->size_allocate = ev_sidebar_thumbnails_size_allocate;
378 
379 #if GTK_CHECK_VERSION(3, 20, 0)
380  gtk_widget_class_set_css_name (widget_class, "evsidebarthumbnails");
381 #endif
382 
383  g_object_class_override_property (g_object_class,
384  PROP_WIDGET,
385  "main-widget");
386 
387  g_type_class_add_private (g_object_class, sizeof (EvSidebarThumbnailsPrivate));
388 }
389 
390 GtkWidget *
392 {
393  GtkWidget *ev_sidebar_thumbnails;
394 
395  ev_sidebar_thumbnails = g_object_new (EV_TYPE_SIDEBAR_THUMBNAILS,
396  "orientation", GTK_ORIENTATION_VERTICAL,
397  NULL);
398 
399 
400  return ev_sidebar_thumbnails;
401 }
402 
403 static cairo_surface_t *
405  gint width,
406  gint height)
407 {
408  EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
409  cairo_surface_t *icon;
410  gchar *key;
411 
412  key = g_strdup_printf ("%dx%d", width, height);
413  icon = g_hash_table_lookup (priv->loading_icons, key);
414  if (!icon) {
415  gboolean inverted_colors;
416  gint device_scale = 1;
417 
418 #ifdef HAVE_HIDPI_SUPPORT
419  device_scale = gtk_widget_get_scale_factor (GTK_WIDGET (sidebar_thumbnails));
420 #endif
421 
422  inverted_colors = ev_document_model_get_inverted_colors (priv->model);
423  icon = ev_document_misc_render_loading_thumbnail_surface (GTK_WIDGET (sidebar_thumbnails),
424  width * device_scale,
425  height * device_scale,
426  inverted_colors);
427  g_hash_table_insert (priv->loading_icons, key, icon);
428  } else {
429  g_free (key);
430  }
431 
432  return icon;
433 }
434 
435 static void
437  gint start_page,
438  gint end_page)
439 {
440  EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
441  GtkTreePath *path;
442  GtkTreeIter iter;
443  gboolean result;
444 
445  g_assert (start_page <= end_page);
446 
447  path = gtk_tree_path_new_from_indices (start_page, -1);
448  for (result = gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->list_store), &iter, path);
449  result && start_page <= end_page;
450  result = gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->list_store), &iter), start_page ++) {
451  EvJobThumbnail *job;
452  gboolean thumbnail_set;
453 
454  gtk_tree_model_get (GTK_TREE_MODEL (priv->list_store),
455  &iter,
456  COLUMN_JOB, &job,
457  COLUMN_THUMBNAIL_SET, &thumbnail_set,
458  -1);
459 
460  if (thumbnail_set) {
461  g_assert (job == NULL);
462  continue;
463  }
464 
465  if (job) {
466  g_signal_handlers_disconnect_by_func (job, thumbnail_job_completed_callback, sidebar_thumbnails);
467  ev_job_cancel (EV_JOB (job));
468  g_object_unref (job);
469  }
470 
471  gtk_list_store_set (priv->list_store, &iter,
472  COLUMN_JOB, NULL,
474  -1);
475  }
476  gtk_tree_path_free (path);
477 }
478 
479 static void
481  gint page,
482  gint *width_return,
483  gint *height_return)
484 {
485  EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
486  gdouble width, height;
487  gint thumbnail_height;
488  gint device_scale = 1;
489 
490 #ifdef HAVE_HIDPI_SUPPORT
491  device_scale = gtk_widget_get_scale_factor (GTK_WIDGET (sidebar_thumbnails));
492 #endif
493  ev_document_get_page_size (priv->document, page, &width, &height);
494  thumbnail_height = (int)(THUMBNAIL_WIDTH * height / width + 0.5);
495 
496  if (priv->rotation == 90 || priv->rotation == 270) {
497  *width_return = thumbnail_height * device_scale;
498  *height_return = THUMBNAIL_WIDTH * device_scale;
499  } else {
500  *width_return = THUMBNAIL_WIDTH * device_scale;
501  *height_return = thumbnail_height * device_scale;
502  }
503 }
504 
505 static void
506 add_range (EvSidebarThumbnails *sidebar_thumbnails,
507  gint start_page,
508  gint end_page)
509 {
510  EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
511  GtkTreePath *path;
512  GtkTreeIter iter;
513  gboolean result;
514  gint page = start_page;
515 
516  g_assert (start_page <= end_page);
517 
518  path = gtk_tree_path_new_from_indices (start_page, -1);
519  for (result = gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->list_store), &iter, path);
520  result && page <= end_page;
521  result = gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->list_store), &iter), page ++) {
522  EvJob *job;
523  gboolean thumbnail_set;
524 
525  gtk_tree_model_get (GTK_TREE_MODEL (priv->list_store), &iter,
526  COLUMN_JOB, &job,
527  COLUMN_THUMBNAIL_SET, &thumbnail_set,
528  -1);
529 
530  if (job == NULL && !thumbnail_set) {
531  gint thumbnail_width, thumbnail_height;
532  get_size_for_page (sidebar_thumbnails, page, &thumbnail_width, &thumbnail_height);
533 
535  page, priv->rotation,
536  thumbnail_width, thumbnail_height);
539  g_object_set_data_full (G_OBJECT (job), "tree_iter",
540  gtk_tree_iter_copy (&iter),
541  (GDestroyNotify) gtk_tree_iter_free);
542  g_signal_connect (job, "finished",
544  sidebar_thumbnails);
545  gtk_list_store_set (priv->list_store, &iter,
546  COLUMN_JOB, job,
547  -1);
549 
550  /* The queue and the list own a ref to the job now */
551  g_object_unref (job);
552  } else if (job) {
553  g_object_unref (job);
554  }
555  }
556  gtk_tree_path_free (path);
557 }
558 
559 /* This modifies start */
560 static void
562  gint start_page,
563  gint end_page)
564 {
565  EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
566  int old_start_page, old_end_page;
567  int n_pages_in_visible_range;
568 
569  /* Preload before and after current visible scrolling range, the same amount of
570  * thumbs in it, to help prevent thumbnail creation happening in the user's sight.
571  * https://bugzilla.gnome.org/show_bug.cgi?id=342110#c15 */
572  n_pages_in_visible_range = (end_page - start_page) + 1;
573  start_page = MAX (0, start_page - n_pages_in_visible_range);
574  end_page = MIN (priv->n_pages - 1, end_page + n_pages_in_visible_range);
575 
576  old_start_page = priv->start_page;
577  old_end_page = priv->end_page;
578 
579  if (start_page == old_start_page &&
580  end_page == old_end_page)
581  return;
582 
583  /* Clear the areas we no longer display */
584  if (old_start_page >= 0 && old_start_page < start_page)
585  cancel_running_jobs (sidebar_thumbnails, old_start_page, MIN (start_page - 1, old_end_page));
586 
587  if (old_end_page > 0 && old_end_page > end_page)
588  cancel_running_jobs (sidebar_thumbnails, MAX (end_page + 1, old_start_page), old_end_page);
589 
590  add_range (sidebar_thumbnails, start_page, end_page);
591 
592  priv->start_page = start_page;
593  priv->end_page = end_page;
594 }
595 
596 static void
598 {
599  EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
600  GtkTreePath *path = NULL;
601  GtkTreePath *path2 = NULL;
602  gdouble page_size;
603  gdouble value;
604  gint wy1;
605  gint wy2;
606 
607  /* Widget is not currently visible */
608  if (!gtk_widget_get_mapped (GTK_WIDGET (sidebar_thumbnails)))
609  return;
610 
611  page_size = gtk_adjustment_get_page_size (priv->vadjustment);
612 
613  if (page_size == 0)
614  return;
615 
616  value = gtk_adjustment_get_value (priv->vadjustment);
617 
618  if (priv->tree_view) {
619  if (! gtk_widget_get_realized (priv->tree_view))
620  return;
621 
622  gtk_tree_view_convert_tree_to_bin_window_coords (GTK_TREE_VIEW (priv->tree_view),
623  0, (int) value,
624  NULL, &wy1);
625  gtk_tree_view_convert_tree_to_bin_window_coords (GTK_TREE_VIEW (priv->tree_view),
626  0, (int) (value + page_size),
627  NULL, &wy2);
628  gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (priv->tree_view),
629  1, wy1 + 1, &path,
630  NULL, NULL, NULL);
631  gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (priv->tree_view),
632  1, wy2 -1, &path2,
633  NULL, NULL, NULL);
634  } else if (priv->icon_view) {
635  if (! gtk_widget_get_realized (priv->icon_view))
636  return;
637  if (! gtk_icon_view_get_visible_range (GTK_ICON_VIEW (priv->icon_view), &path, &path2))
638  return;
639  } else {
640  return;
641  }
642 
643  if (path && path2) {
644  update_visible_range (sidebar_thumbnails,
645  gtk_tree_path_get_indices (path)[0],
646  gtk_tree_path_get_indices (path2)[0]);
647  }
648 
649  gtk_tree_path_free (path);
650  gtk_tree_path_free (path2);
651 }
652 
653 static void
655 {
656  EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
657  GtkTreeIter iter;
658  int i;
659  gint prev_width = -1;
660  gint prev_height = -1;
661 
662  for (i = 0; i < sidebar_thumbnails->priv->n_pages; i++) {
663  gchar *page_label;
664  gchar *page_string;
665  cairo_surface_t *loading_icon = NULL;
666  gint width, height;
667 
668  page_label = ev_document_get_page_label (priv->document, i);
669  page_string = g_markup_printf_escaped ("<i>%s</i>", page_label);
670  ev_thumbnails_size_cache_get_size (sidebar_thumbnails->priv->size_cache, i,
671  sidebar_thumbnails->priv->rotation,
672  &width, &height);
673  if (!loading_icon || (width != prev_width && height != prev_height)) {
674  loading_icon =
675  ev_sidebar_thumbnails_get_loading_icon (sidebar_thumbnails,
676  width, height);
677  }
678 
679  prev_width = width;
680  prev_height = height;
681 
682  gtk_list_store_append (priv->list_store, &iter);
683  gtk_list_store_set (priv->list_store, &iter,
684  COLUMN_PAGE_STRING, page_string,
685  COLUMN_SURFACE, loading_icon,
687  -1);
688  g_free (page_label);
689  g_free (page_string);
690  }
691 }
692 
693 static void
694 ev_sidebar_tree_selection_changed (GtkTreeSelection *selection,
695  EvSidebarThumbnails *ev_sidebar_thumbnails)
696 {
697  EvSidebarThumbnailsPrivate *priv = ev_sidebar_thumbnails->priv;
698  GtkTreePath *path;
699  GtkTreeIter iter;
700  int page;
701 
702  if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
703  return;
704 
705  path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->list_store),
706  &iter);
707  page = gtk_tree_path_get_indices (path)[0];
708  gtk_tree_path_free (path);
709 
710  ev_document_model_set_page (priv->model, page);
711 }
712 
713 static void
714 ev_sidebar_icon_selection_changed (GtkIconView *icon_view,
715  EvSidebarThumbnails *ev_sidebar_thumbnails)
716 {
717  EvSidebarThumbnailsPrivate *priv = ev_sidebar_thumbnails->priv;
718  GtkTreePath *path;
719  GList *selected;
720  int page;
721 
722  selected = gtk_icon_view_get_selected_items (icon_view);
723  if (selected == NULL)
724  return;
725 
726  /* We don't handle or expect multiple selection. */
727  g_assert (selected->next == NULL);
728 
729  path = selected->data;
730  page = gtk_tree_path_get_indices (path)[0];
731 
732  gtk_tree_path_free (path);
733  g_list_free (selected);
734 
735  ev_document_model_set_page (priv->model, page);
736 }
737 
738 static void
740 {
742  GtkTreeSelection *selection;
743  GtkCellRenderer *renderer;
744 
745  priv = ev_sidebar_thumbnails->priv;
746  priv->tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (priv->list_store));
747 
748  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view));
749  g_signal_connect (selection, "changed",
750  G_CALLBACK (ev_sidebar_tree_selection_changed), ev_sidebar_thumbnails);
751  gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->tree_view), FALSE);
752  renderer = g_object_new (GTK_TYPE_CELL_RENDERER_PIXBUF,
753  "xpad", 2,
754  "ypad", 2,
755  NULL);
756  gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (priv->tree_view), -1,
757  NULL, renderer,
758  "surface", 1,
759  NULL);
760  gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (priv->tree_view), -1,
761  NULL, gtk_cell_renderer_text_new (),
762  "markup", 0, NULL);
763  gtk_container_add (GTK_CONTAINER (priv->swindow), priv->tree_view);
764  gtk_widget_show (priv->tree_view);
765 }
766 
767 static void
769 {
771  GtkCellRenderer *renderer;
772 
773  priv = ev_sidebar_thumbnails->priv;
774 
775  priv->icon_view = gtk_icon_view_new_with_model (GTK_TREE_MODEL (priv->list_store));
776 
777  renderer = g_object_new (GTK_TYPE_CELL_RENDERER_PIXBUF,
778  "xalign", 0.5,
779  "yalign", 1.0,
780  NULL);
781  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->icon_view), renderer, FALSE);
782  gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (priv->icon_view),
783  renderer, "surface", 1, NULL);
784 
785  renderer = g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
786  "alignment", PANGO_ALIGN_CENTER,
787  "wrap-mode", PANGO_WRAP_WORD_CHAR,
788  "xalign", 0.5,
789  "yalign", 0.0,
790  "width", THUMBNAIL_WIDTH,
791  "wrap-width", THUMBNAIL_WIDTH,
792  NULL);
793  gtk_cell_layout_pack_end (GTK_CELL_LAYOUT (priv->icon_view), renderer, FALSE);
794  gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (priv->icon_view),
795  renderer, "markup", 0, NULL);
796  g_signal_connect (priv->icon_view, "selection-changed",
797  G_CALLBACK (ev_sidebar_icon_selection_changed), ev_sidebar_thumbnails);
798 
799  gtk_container_add (GTK_CONTAINER (priv->swindow), priv->icon_view);
800  gtk_widget_show (priv->icon_view);
801 }
802 
803 static gboolean
805 {
806  EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
807 
809 }
810 
811 static void
813  GParamSpec *pspec)
814 
815 {
816  ev_sidebar_thumbnails_reload (sidebar_thumbnails);
817 }
818 
819 static void
821  GtkTreePath *path,
822  GtkTreeIter *iter,
823  gpointer data)
824 {
825  guint signal_id;
826 
827  signal_id = GPOINTER_TO_UINT (data);
828 
829  /* PREVENT GtkIconView "row-changed" handler to be reached, as it will
830  * perform a full invalidate and relayout of all items, See bug:
831  * https://bugzilla.gnome.org/show_bug.cgi?id=691448#c9 */
832  g_signal_stop_emission (model, signal_id, 0);
833 }
834 
835 static void
837 {
839  guint signal_id;
840 
841  priv = ev_sidebar_thumbnails->priv = EV_SIDEBAR_THUMBNAILS_GET_PRIVATE (ev_sidebar_thumbnails);
842 
843  priv->list_store = gtk_list_store_new (NUM_COLUMNS,
844  G_TYPE_STRING,
845  CAIRO_GOBJECT_TYPE_SURFACE,
846  G_TYPE_BOOLEAN,
848 
849  signal_id = g_signal_lookup ("row-changed", GTK_TYPE_TREE_MODEL);
850  g_signal_connect (GTK_TREE_MODEL (priv->list_store), "row-changed",
852  GUINT_TO_POINTER (signal_id));
853 
854  priv->swindow = gtk_scrolled_window_new (NULL, NULL);
855 
856  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->swindow),
857  GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
858  priv->vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->swindow));
859  g_signal_connect_data (priv->vadjustment, "value-changed",
860  G_CALLBACK (adjustment_changed_cb),
861  ev_sidebar_thumbnails, NULL,
862  G_CONNECT_SWAPPED | G_CONNECT_AFTER);
863  g_signal_connect_swapped (priv->swindow, "size-allocate",
864  G_CALLBACK (adjustment_changed_cb),
865  ev_sidebar_thumbnails);
866  gtk_box_pack_start (GTK_BOX (ev_sidebar_thumbnails), priv->swindow, TRUE, TRUE, 0);
867 
868  g_signal_connect (ev_sidebar_thumbnails, "notify::scale-factor",
870 
871  /* Put it all together */
872  gtk_widget_show_all (priv->swindow);
873 }
874 
875 static void
877  gint page)
878 {
879  GtkTreeView *tree_view;
880  GtkTreePath *path;
881 
882  path = gtk_tree_path_new_from_indices (page, -1);
883 
884  if (sidebar->priv->tree_view) {
885  tree_view = GTK_TREE_VIEW (sidebar->priv->tree_view);
886  gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
887  gtk_tree_view_scroll_to_cell (tree_view, path, NULL, FALSE, 0.0, 0.0);
888  } else if (sidebar->priv->icon_view) {
889 
890  g_signal_handlers_block_by_func
891  (sidebar->priv->icon_view,
892  G_CALLBACK (ev_sidebar_icon_selection_changed), sidebar);
893 
894  gtk_icon_view_select_path (GTK_ICON_VIEW (sidebar->priv->icon_view), path);
895 
896  g_signal_handlers_unblock_by_func
897  (sidebar->priv->icon_view,
898  G_CALLBACK (ev_sidebar_icon_selection_changed), sidebar);
899 
900  gtk_icon_view_scroll_to_path (GTK_ICON_VIEW (sidebar->priv->icon_view), path, FALSE, 0.0, 0.0);
901  }
902 
903  gtk_tree_path_free (path);
904 }
905 
906 static void
908  gint old_page,
909  gint new_page)
910 {
911  ev_sidebar_thumbnails_set_current_page (sidebar, new_page);
912 }
913 
914 static gboolean
915 refresh (EvSidebarThumbnails *sidebar_thumbnails)
916 {
917  adjustment_changed_cb (sidebar_thumbnails);
918  return FALSE;
919 }
920 
921 static void
923 {
924  EvDocumentModel *model;
925 
926  if (sidebar_thumbnails->priv->loading_icons)
927  g_hash_table_remove_all (sidebar_thumbnails->priv->loading_icons);
928 
929  if (sidebar_thumbnails->priv->document == NULL ||
930  sidebar_thumbnails->priv->n_pages <= 0)
931  return;
932 
933  model = sidebar_thumbnails->priv->model;
934 
935  ev_sidebar_thumbnails_clear_model (sidebar_thumbnails);
936  ev_sidebar_thumbnails_fill_model (sidebar_thumbnails);
937 
938  /* Trigger a redraw */
939  sidebar_thumbnails->priv->start_page = -1;
940  sidebar_thumbnails->priv->end_page = -1;
941  ev_sidebar_thumbnails_set_current_page (sidebar_thumbnails,
943  g_idle_add ((GSourceFunc)refresh, sidebar_thumbnails);
944 }
945 
946 static void
948  GParamSpec *pspec,
949  EvSidebarThumbnails *sidebar_thumbnails)
950 {
951  gint rotation = ev_document_model_get_rotation (model);
952 
953  sidebar_thumbnails->priv->rotation = rotation;
954  ev_sidebar_thumbnails_reload (sidebar_thumbnails);
955 }
956 
957 static void
959  GParamSpec *pspec,
960  EvSidebarThumbnails *sidebar_thumbnails)
961 {
962  gboolean inverted_colors = ev_document_model_get_inverted_colors (model);
963 
964  sidebar_thumbnails->priv->inverted_colors = inverted_colors;
965  ev_sidebar_thumbnails_reload (sidebar_thumbnails);
966 }
967 
968 static void
970  EvSidebarThumbnails *sidebar_thumbnails)
971 {
972  GtkWidget *widget = GTK_WIDGET (sidebar_thumbnails);
973  EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
974  GtkTreeIter *iter;
975  cairo_surface_t *surface;
976 #ifdef HAVE_HIDPI_SUPPORT
977  gint device_scale;
978 #endif
979 
980  if (ev_job_is_failed (EV_JOB (job)))
981  return;
982 
983 #ifdef HAVE_HIDPI_SUPPORT
984  device_scale = gtk_widget_get_scale_factor (widget);
985  cairo_surface_set_device_scale (job->thumbnail_surface, device_scale, device_scale);
986 #endif
987 
989  job->thumbnail_surface,
990  -1, -1);
991 
992  iter = (GtkTreeIter *) g_object_get_data (G_OBJECT (job), "tree_iter");
993  if (priv->inverted_colors)
995  gtk_list_store_set (priv->list_store,
996  iter,
997  COLUMN_SURFACE, surface,
999  COLUMN_JOB, NULL,
1000  -1);
1001  cairo_surface_destroy (surface);
1002 
1003  gtk_widget_queue_draw (priv->icon_view);
1004 }
1005 
1006 static void
1008  GParamSpec *pspec,
1009  EvSidebarThumbnails *sidebar_thumbnails)
1010 {
1011  EvDocument *document = ev_document_model_get_document (model);
1012  EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
1013 
1014  if (ev_document_get_n_pages (document) <= 0 ||
1015  !ev_document_check_dimensions (document)) {
1016  return;
1017  }
1018 
1019  priv->size_cache = ev_thumbnails_size_cache_get (document);
1020  priv->document = document;
1021  priv->n_pages = ev_document_get_n_pages (document);
1022  priv->rotation = ev_document_model_get_rotation (model);
1024  if (priv->loading_icons) {
1025  g_hash_table_remove_all (priv->loading_icons);
1026  } else {
1027  priv->loading_icons = g_hash_table_new_full (g_str_hash,
1028  g_str_equal,
1029  (GDestroyNotify)g_free,
1030  (GDestroyNotify)cairo_surface_destroy);
1031  }
1032 
1033  ev_sidebar_thumbnails_clear_model (sidebar_thumbnails);
1034  ev_sidebar_thumbnails_fill_model (sidebar_thumbnails);
1035 
1036  /* Create the view widget, and remove the old one, if needed */
1037  if (ev_sidebar_thumbnails_use_icon_view (sidebar_thumbnails)) {
1038  if (priv->tree_view) {
1039  gtk_container_remove (GTK_CONTAINER (priv->swindow), priv->tree_view);
1040  priv->tree_view = NULL;
1041  }
1042 
1043  if (! priv->icon_view) {
1044  ev_sidebar_init_icon_view (sidebar_thumbnails);
1045  g_object_notify (G_OBJECT (sidebar_thumbnails), "main_widget");
1046  } else {
1047  gtk_widget_queue_resize (priv->icon_view);
1048  }
1049  } else {
1050  if (priv->icon_view) {
1051  gtk_container_remove (GTK_CONTAINER (priv->swindow), priv->icon_view);
1052  priv->icon_view = NULL;
1053  }
1054 
1055  if (! priv->tree_view) {
1056  ev_sidebar_init_tree_view (sidebar_thumbnails);
1057  g_object_notify (G_OBJECT (sidebar_thumbnails), "main_widget");
1058  }
1059  }
1060 
1061  /* Connect to the signal and trigger a fake callback */
1062  g_signal_connect_swapped (priv->model, "page-changed",
1063  G_CALLBACK (page_changed_cb),
1064  sidebar_thumbnails);
1065  g_signal_connect (priv->model, "notify::rotation",
1067  sidebar_thumbnails);
1068  g_signal_connect (priv->model, "notify::inverted-colors",
1070  sidebar_thumbnails);
1071  g_signal_connect_swapped (priv->model, "notify::fullscreen",
1072  G_CALLBACK (ev_sidebar_fullscreen_cb),
1073  sidebar_thumbnails);
1074  sidebar_thumbnails->priv->start_page = -1;
1075  sidebar_thumbnails->priv->end_page = -1;
1076  ev_sidebar_thumbnails_set_current_page (sidebar_thumbnails,
1077  ev_document_model_get_page (model));
1078  adjustment_changed_cb (sidebar_thumbnails);
1079 }
1080 
1081 static void
1083  EvDocumentModel *model)
1084 {
1085  EvSidebarThumbnails *sidebar_thumbnails = EV_SIDEBAR_THUMBNAILS (sidebar_page);
1086  EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
1087 
1088  if (priv->model == model)
1089  return;
1090 
1091  priv->model = model;
1092  g_signal_connect (model, "notify::document",
1094  sidebar_page);
1095 }
1096 
1097 static gboolean
1098 ev_sidebar_thumbnails_clear_job (GtkTreeModel *model,
1099  GtkTreePath *path,
1100  GtkTreeIter *iter,
1101  gpointer data)
1102 {
1103  EvJob *job;
1104 
1105  gtk_tree_model_get (model, iter, COLUMN_JOB, &job, -1);
1106 
1107  if (job != NULL) {
1108  ev_job_cancel (job);
1109  g_signal_handlers_disconnect_by_func (job, thumbnail_job_completed_callback, data);
1110  g_object_unref (job);
1111  }
1112 
1113  return FALSE;
1114 }
1115 
1116 static void
1118 {
1119  EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
1120 
1121  gtk_tree_model_foreach (GTK_TREE_MODEL (priv->list_store), ev_sidebar_thumbnails_clear_job, sidebar_thumbnails);
1122  gtk_list_store_clear (priv->list_store);
1123 }
1124 
1125 static gboolean
1127  EvDocument *document)
1128 {
1129  return TRUE;
1130 }
1131 
1132 static const gchar*
1134 {
1135  return _("Thumbnails");
1136 }
1137 
1138 static void
1140 {
1144 }