1 /* this file is part of evince, a gnome document viewer
3 * Copyright (C) 2004 Red Hat, Inc.
4 * Copyright (C) 2004, 2005 Anders Carlsson <andersca@gnome.org>
7 * Jonathan Blandford <jrb@alum.mit.edu>
8 * Anders Carlsson <andersca@gnome.org>
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.
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.
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
31 #include <glib/gi18n.h>
33 #include "ev-sidebar-page.h"
34 #include "ev-sidebar-thumbnails.h"
35 #include "ev-document-thumbnails.h"
36 #include "ev-document-misc.h"
37 #include "ev-job-queue.h"
38 #include "ev-window.h"
41 #define THUMBNAIL_WIDTH 100
43 /* The IconView doesn't scale nearly as well as the TreeView, so we arbitrarily
45 #define MAX_ICON_VIEW_PAGE_COUNT 1500
48 struct _EvSidebarThumbnailsPrivate {
52 GtkAdjustment *vadjustment;
53 GtkListStore *list_store;
54 GdkPixbuf *loading_icon;
57 gint n_pages, pages_done;
60 gint start_page, end_page;
71 static void ev_sidebar_thumbnails_clear_model (EvSidebarThumbnails *sidebar);
72 static gboolean ev_sidebar_thumbnails_support_document (EvSidebarPage *sidebar_page,
73 EvDocument *document);
74 static void ev_sidebar_thumbnails_page_iface_init (EvSidebarPageIface *iface);
75 static void ev_sidebar_thumbnails_set_document (EvSidebarPage *sidebar_page,
76 EvDocument *document);
77 static const gchar* ev_sidebar_thumbnails_get_label (EvSidebarPage *sidebar_page);
78 static void thumbnail_job_completed_callback (EvJobThumbnail *job,
79 EvSidebarThumbnails *sidebar_thumbnails);
81 G_DEFINE_TYPE_EXTENDED (EvSidebarThumbnails,
82 ev_sidebar_thumbnails,
85 G_IMPLEMENT_INTERFACE (EV_TYPE_SIDEBAR_PAGE,
86 ev_sidebar_thumbnails_page_iface_init))
88 #define EV_SIDEBAR_THUMBNAILS_GET_PRIVATE(object) \
89 (G_TYPE_INSTANCE_GET_PRIVATE ((object), EV_TYPE_SIDEBAR_THUMBNAILS, EvSidebarThumbnailsPrivate));
93 ev_sidebar_thumbnails_dispose (GObject *object)
95 EvSidebarThumbnails *sidebar_thumbnails = EV_SIDEBAR_THUMBNAILS (object);
97 ev_sidebar_thumbnails_clear_model (sidebar_thumbnails);
98 g_object_unref (sidebar_thumbnails->priv->loading_icon);
99 g_object_unref (sidebar_thumbnails->priv->list_store);
101 G_OBJECT_CLASS (ev_sidebar_thumbnails_parent_class)->dispose (object);
105 ev_sidebar_thumbnails_class_init (EvSidebarThumbnailsClass *ev_sidebar_thumbnails_class)
107 GObjectClass *g_object_class;
108 GtkObjectClass *gtk_object_class;
110 g_object_class = G_OBJECT_CLASS (ev_sidebar_thumbnails_class);
111 gtk_object_class = GTK_OBJECT_CLASS (ev_sidebar_thumbnails_class);
113 g_object_class->dispose = ev_sidebar_thumbnails_dispose;
115 g_type_class_add_private (g_object_class, sizeof (EvSidebarThumbnailsPrivate));
119 ev_sidebar_thumbnails_new (void)
121 GtkWidget *ev_sidebar_thumbnails;
123 ev_sidebar_thumbnails = g_object_new (EV_TYPE_SIDEBAR_THUMBNAILS, NULL);
125 return ev_sidebar_thumbnails;
129 clear_range (EvSidebarThumbnails *sidebar_thumbnails,
133 EvSidebarThumbnailsPrivate *priv;
138 priv = sidebar_thumbnails->priv = EV_SIDEBAR_THUMBNAILS_GET_PRIVATE (sidebar_thumbnails);
140 g_assert (start_page <= end_page);
142 path = gtk_tree_path_new_from_indices (start_page, -1);
143 for (result = gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->list_store), &iter, path);
144 result && start_page <= end_page;
145 result = gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->list_store), &iter), start_page ++) {
148 gtk_tree_model_get (GTK_TREE_MODEL (priv->list_store),
154 g_signal_handlers_disconnect_by_func (job, thumbnail_job_completed_callback, sidebar_thumbnails);
155 ev_job_queue_remove_job (EV_JOB (job));
156 g_object_unref (job);
159 gtk_list_store_set (priv->list_store, &iter,
161 COLUMN_THUMBNAIL_SET, FALSE,
162 COLUMN_PIXBUF, priv->loading_icon,
165 gtk_tree_path_free (path);
169 add_range (EvSidebarThumbnails *sidebar_thumbnails,
173 EvSidebarThumbnailsPrivate *priv;
177 gint page = start_page;
179 priv = sidebar_thumbnails->priv = EV_SIDEBAR_THUMBNAILS_GET_PRIVATE (sidebar_thumbnails);
181 g_assert (start_page <= end_page);
183 path = gtk_tree_path_new_from_indices (start_page, -1);
184 for (result = gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->list_store), &iter, path);
185 result && page <= end_page;
186 result = gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->list_store), &iter), page ++) {
188 gboolean thumbnail_set;
190 gtk_tree_model_get (GTK_TREE_MODEL (priv->list_store), &iter,
192 COLUMN_THUMBNAIL_SET, &thumbnail_set,
195 if (job == NULL && !thumbnail_set) {
196 job = (EvJobThumbnail *)ev_job_thumbnail_new (priv->document, page, THUMBNAIL_WIDTH);
197 ev_job_queue_add_job (EV_JOB (job), EV_JOB_PRIORITY_HIGH);
198 g_object_set_data_full (G_OBJECT (job), "tree_iter",
199 gtk_tree_iter_copy (&iter),
200 (GDestroyNotify) gtk_tree_iter_free);
201 g_signal_connect (job, "finished",
202 G_CALLBACK (thumbnail_job_completed_callback),
204 gtk_list_store_set (priv->list_store, &iter,
207 /* The queue and the list own a ref to the job now */
208 g_object_unref (job);
210 g_object_unref (job);
213 gtk_tree_path_free (path);
216 /* This modifies start */
218 update_visible_range (EvSidebarThumbnails *sidebar_thumbnails,
222 EvSidebarThumbnailsPrivate *priv;
223 int old_start_page, old_end_page;
225 priv = sidebar_thumbnails->priv = EV_SIDEBAR_THUMBNAILS_GET_PRIVATE (sidebar_thumbnails);
227 old_start_page = priv->start_page;
228 old_end_page = priv->end_page;
230 if (start_page == old_start_page &&
231 end_page == old_end_page)
234 /* Clear the areas we no longer display */
235 if (old_start_page < start_page)
236 clear_range (sidebar_thumbnails, old_start_page, MIN (start_page - 1, old_end_page));
238 if (old_end_page > end_page)
239 clear_range (sidebar_thumbnails, MAX (end_page + 1, old_start_page), old_end_page);
241 add_range (sidebar_thumbnails, start_page, end_page);
243 priv->start_page = start_page;
244 priv->end_page = end_page;
248 adjustment_changed_cb (EvSidebarThumbnails *sidebar_thumbnails)
250 EvSidebarThumbnailsPrivate *priv;
251 GtkTreePath *path = NULL;
252 GtkTreePath *path2 = NULL;
256 priv = sidebar_thumbnails->priv = EV_SIDEBAR_THUMBNAILS_GET_PRIVATE (sidebar_thumbnails);
258 if (priv->tree_view) {
259 if (! GTK_WIDGET_REALIZED (priv->tree_view))
262 gtk_tree_view_tree_to_widget_coords (GTK_TREE_VIEW (priv->tree_view),
263 0, (int) priv->vadjustment->value,
265 gtk_tree_view_tree_to_widget_coords (GTK_TREE_VIEW (priv->tree_view),
266 0, (int) (priv->vadjustment->value + priv->vadjustment->page_size),
268 gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (priv->tree_view),
271 gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (priv->tree_view),
274 } else if (priv->icon_view) {
275 #ifdef HAVE_GTK_ICON_VIEW_GET_VISIBLE_RANGE
276 if (! GTK_WIDGET_REALIZED (priv->icon_view))
278 gtk_icon_view_get_visible_range (GTK_ICON_VIEW (priv->icon_view), &path, &path2);
280 g_assert_not_reached ();
287 path = gtk_tree_path_new_first ();
289 path2 = gtk_tree_path_new_from_indices (priv->n_pages,
291 update_visible_range (sidebar_thumbnails,
292 gtk_tree_path_get_indices (path)[0],
293 gtk_tree_path_get_indices (path2)[0]);
295 gtk_tree_path_free (path);
296 gtk_tree_path_free (path2);
300 ev_sidebar_tree_selection_changed (GtkTreeSelection *selection,
301 EvSidebarThumbnails *ev_sidebar_thumbnails)
303 EvSidebarThumbnailsPrivate *priv;
304 EvPageCache *page_cache;
309 priv = ev_sidebar_thumbnails->priv = EV_SIDEBAR_THUMBNAILS_GET_PRIVATE (ev_sidebar_thumbnails);
311 if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
314 path = gtk_tree_model_get_path (GTK_TREE_MODEL (priv->list_store),
316 page = gtk_tree_path_get_indices (path)[0];
317 gtk_tree_path_free (path);
319 page_cache = ev_page_cache_get (priv->document);
320 ev_page_cache_set_current_page (page_cache, page);
324 ev_sidebar_icon_selection_changed (GtkIconView *icon_view,
325 EvSidebarThumbnails *ev_sidebar_thumbnails)
327 EvSidebarThumbnailsPrivate *priv;
328 EvPageCache *page_cache;
333 priv = ev_sidebar_thumbnails->priv = EV_SIDEBAR_THUMBNAILS_GET_PRIVATE (ev_sidebar_thumbnails);
335 selected = gtk_icon_view_get_selected_items (icon_view);
336 if (selected == NULL)
339 /* We don't handle or expect multiple selection. */
340 g_assert (selected->next == NULL);
342 path = selected->data;
343 page = gtk_tree_path_get_indices (path)[0];
345 gtk_tree_path_free (path);
346 g_list_free (selected);
348 page_cache = ev_page_cache_get (priv->document);
349 ev_page_cache_set_current_page (page_cache, page);
353 ev_sidebar_thumbnails_get_treeview (EvSidebarThumbnails *sidebar)
355 if (sidebar->priv->tree_view)
356 return sidebar->priv->tree_view;
358 return sidebar->priv->icon_view;
363 ev_sidebar_init_tree_view (EvSidebarThumbnails *ev_sidebar_thumbnails)
365 EvSidebarThumbnailsPrivate *priv;
366 GtkTreeSelection *selection;
367 GtkCellRenderer *renderer;
369 priv = ev_sidebar_thumbnails->priv;
370 priv->tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (priv->list_store));
372 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view));
373 g_signal_connect (selection, "changed",
374 G_CALLBACK (ev_sidebar_tree_selection_changed), ev_sidebar_thumbnails);
375 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->tree_view), FALSE);
376 renderer = g_object_new (GTK_TYPE_CELL_RENDERER_PIXBUF,
380 gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (priv->tree_view), -1,
384 gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (priv->tree_view), -1,
385 NULL, gtk_cell_renderer_text_new (),
387 gtk_container_add (GTK_CONTAINER (priv->swindow), priv->tree_view);
388 gtk_widget_show (priv->tree_view);
392 ev_sidebar_init_icon_view (EvSidebarThumbnails *ev_sidebar_thumbnails)
394 EvSidebarThumbnailsPrivate *priv;
396 priv = ev_sidebar_thumbnails->priv;
398 priv->icon_view = gtk_icon_view_new_with_model (GTK_TREE_MODEL (priv->list_store));
399 gtk_icon_view_set_markup_column (GTK_ICON_VIEW (priv->icon_view), 0);
400 gtk_icon_view_set_pixbuf_column (GTK_ICON_VIEW (priv->icon_view), 1);
401 g_signal_connect (priv->icon_view, "selection-changed",
402 G_CALLBACK (ev_sidebar_icon_selection_changed), ev_sidebar_thumbnails);
404 gtk_container_add (GTK_CONTAINER (priv->swindow), priv->icon_view);
405 gtk_widget_show (priv->icon_view);
409 ev_sidebar_thumbnails_use_icon_view (EvSidebarThumbnails *sidebar_thumbnails)
411 #ifdef HAVE_GTK_ICON_VIEW_GET_VISIBLE_RANGE
412 EvPageCache *page_cache;
414 page_cache = ev_page_cache_get (sidebar_thumbnails->priv->document);
416 if (ev_page_cache_get_n_pages (page_cache) > MAX_ICON_VIEW_PAGE_COUNT)
425 ev_sidebar_thumbnails_init (EvSidebarThumbnails *ev_sidebar_thumbnails)
427 EvSidebarThumbnailsPrivate *priv;
429 priv = ev_sidebar_thumbnails->priv = EV_SIDEBAR_THUMBNAILS_GET_PRIVATE (ev_sidebar_thumbnails);
431 priv->list_store = gtk_list_store_new (NUM_COLUMNS,
435 EV_TYPE_JOB_THUMBNAIL);
437 priv->swindow = gtk_scrolled_window_new (NULL, NULL);
438 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->swindow),
439 GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
440 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (priv->swindow),
442 priv->vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->swindow));
443 g_signal_connect_data (G_OBJECT (priv->vadjustment), "value-changed",
444 G_CALLBACK (adjustment_changed_cb),
445 ev_sidebar_thumbnails, NULL,
446 G_CONNECT_SWAPPED | G_CONNECT_AFTER);
447 g_signal_connect_swapped (G_OBJECT (priv->swindow), "size-allocate",
448 G_CALLBACK (adjustment_changed_cb),
449 ev_sidebar_thumbnails);
450 gtk_box_pack_start (GTK_BOX (ev_sidebar_thumbnails), priv->swindow, TRUE, TRUE, 0);
452 /* Put it all together */
453 gtk_widget_show_all (priv->swindow);
457 page_changed_cb (EvPageCache *page_cache,
459 EvSidebarThumbnails *sidebar)
461 GtkTreeView *tree_view;
464 path = gtk_tree_path_new_from_indices (page, -1);
466 if (sidebar->priv->tree_view) {
467 tree_view = GTK_TREE_VIEW (sidebar->priv->tree_view);
468 gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
469 gtk_tree_view_scroll_to_cell (tree_view, path, NULL, FALSE, 0.0, 0.0);
470 } else if (sidebar->priv->icon_view) {
471 /* Guard against gtk-2.6 */
472 #ifdef HAVE_GTK_ICON_VIEW_GET_VISIBLE_RANGE
473 gtk_icon_view_select_path (GTK_ICON_VIEW (sidebar->priv->icon_view), path);
474 gtk_icon_view_set_cursor (GTK_ICON_VIEW (sidebar->priv->icon_view), path, NULL, FALSE);
478 gtk_tree_path_free (path);
482 thumbnail_job_completed_callback (EvJobThumbnail *job,
483 EvSidebarThumbnails *sidebar_thumbnails)
485 EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
488 iter = (GtkTreeIter *) g_object_get_data (G_OBJECT (job), "tree_iter");
489 gtk_list_store_set (priv->list_store,
491 COLUMN_PIXBUF, job->thumbnail,
492 COLUMN_THUMBNAIL_SET, TRUE,
498 ev_sidebar_thumbnails_set_document (EvSidebarPage *sidebar_page,
499 EvDocument *document)
501 EvSidebarThumbnails *sidebar_thumbnails = EV_SIDEBAR_THUMBNAILS (sidebar_page);
504 gint width = THUMBNAIL_WIDTH;
505 gint height = THUMBNAIL_WIDTH;
506 EvPageCache *page_cache;
508 EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
510 g_return_if_fail (EV_IS_DOCUMENT_THUMBNAILS (document));
512 page_cache = ev_page_cache_get (document);
513 n_pages = ev_page_cache_get_n_pages (page_cache);
515 priv->document = document;
516 priv->n_pages = n_pages;
518 /* We get the dimensions of the first doc so that we can make a blank
520 ev_document_doc_mutex_lock ();
521 ev_document_thumbnails_get_dimensions (EV_DOCUMENT_THUMBNAILS (priv->document),
522 0, THUMBNAIL_WIDTH, &width, &height);
523 ev_document_doc_mutex_unlock ();
525 if (priv->loading_icon)
526 g_object_unref (priv->loading_icon);
527 priv->loading_icon = ev_document_misc_get_thumbnail_frame (width, height, NULL);
529 ev_sidebar_thumbnails_clear_model (sidebar_thumbnails);
530 for (i = 0; i < n_pages; i++) {
534 page_label = ev_page_cache_get_page_label (page_cache, i);
535 page_string = g_markup_printf_escaped ("<i>%s</i>", page_label);
537 gtk_list_store_append (priv->list_store, &iter);
538 gtk_list_store_set (priv->list_store, &iter,
539 COLUMN_PAGE_STRING, page_string,
540 COLUMN_PIXBUF, priv->loading_icon,
541 COLUMN_THUMBNAIL_SET, FALSE,
544 g_free (page_string);
548 /* Create the view widget, and remove the old one, if needed */
549 if (ev_sidebar_thumbnails_use_icon_view (sidebar_thumbnails)) {
550 if (priv->tree_view) {
551 gtk_container_remove (GTK_CONTAINER (priv->swindow), priv->tree_view);
552 priv->tree_view = NULL;
555 if (! priv->icon_view)
556 ev_sidebar_init_icon_view (sidebar_thumbnails);
558 if (priv->icon_view) {
559 gtk_container_remove (GTK_CONTAINER (priv->swindow), priv->icon_view);
560 priv->icon_view = NULL;
563 if (! priv->tree_view)
564 ev_sidebar_init_tree_view (sidebar_thumbnails);
568 /* Connect to the signal and trigger a fake callback */
569 g_signal_connect (page_cache, "page-changed", G_CALLBACK (page_changed_cb), sidebar_thumbnails);
570 adjustment_changed_cb (sidebar_thumbnails);
574 ev_sidebar_thumbnails_clear_job (GtkTreeModel *model,
581 gtk_tree_model_get (model, iter, COLUMN_JOB, &job, -1);
584 ev_job_queue_remove_job (job);
585 g_signal_handlers_disconnect_by_func (job, thumbnail_job_completed_callback, data);
586 g_object_unref (job);
593 ev_sidebar_thumbnails_clear_model (EvSidebarThumbnails *sidebar_thumbnails)
595 EvSidebarThumbnailsPrivate *priv = sidebar_thumbnails->priv;
597 gtk_tree_model_foreach (GTK_TREE_MODEL (priv->list_store), ev_sidebar_thumbnails_clear_job, sidebar_thumbnails);
598 gtk_list_store_clear (priv->list_store);
602 ev_sidebar_thumbnails_support_document (EvSidebarPage *sidebar_page,
603 EvDocument *document)
605 return (EV_IS_DOCUMENT_THUMBNAILS (document) &&
606 (ev_document_get_n_pages (document) > 1));
610 ev_sidebar_thumbnails_get_label (EvSidebarPage *sidebar_page)
612 return _("Thumbnails");
616 ev_sidebar_thumbnails_page_iface_init (EvSidebarPageIface *iface)
618 iface->support_document = ev_sidebar_thumbnails_support_document;
619 iface->set_document = ev_sidebar_thumbnails_set_document;
620 iface->get_label = ev_sidebar_thumbnails_get_label;