]> www.fi.muni.cz Git - evince.git/blob - shell/ev-sidebar-bookmarks.c
Hook up bookmarks navigation
[evince.git] / shell / ev-sidebar-bookmarks.c
1 /* this file is part of evince, a gnome document viewer
2  *
3  *  Copyright (C) 2004 Red Hat, Inc.
4  *
5  *  Author:
6  *    Jonathan Blandford <jrb@alum.mit.edu>
7  *
8  * Evince is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * Evince is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
21  */
22
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 #include <string.h>
28 #include <gtk/gtk.h>
29
30 #include "ev-sidebar-bookmarks.h"
31 #include "ev-document-bookmarks.h"
32
33 /* Amount of time we devote to each iteration of the idle, in microseconds */
34 #define IDLE_WORK_LENGTH 5000
35
36 typedef struct {
37         EvDocumentBookmarksIter *bookmarks_iter;
38         GtkTreeIter *tree_iter;
39 } IdleStackData;
40
41 struct _EvSidebarBookmarksPrivate {
42         GtkWidget *tree_view;
43         GtkTreeModel *model;
44         EvDocument *current_document;
45         GList *idle_stack;
46         guint idle_id;
47 };
48
49 enum {
50         BOOKMARKS_COLUMN_MARKUP,
51         BOOKMARKS_COLUMN_PAGE_NUM,
52         BOOKMARKS_COLUMN_PAGE_VALID,
53         BOOKMARKS_COLUMN_BOOKMARK,
54         BOOKMARKS_COLUMN_NUM_COLUMNS
55 };
56
57 static void bookmarks_page_num_func (GtkTreeViewColumn *tree_column,
58                                      GtkCellRenderer   *cell,
59                                      GtkTreeModel      *tree_model,
60                                      GtkTreeIter       *iter,
61                                      gpointer           data);
62
63 G_DEFINE_TYPE (EvSidebarBookmarks, ev_sidebar_bookmarks, GTK_TYPE_VBOX)
64
65 #define EV_SIDEBAR_BOOKMARKS_GET_PRIVATE(object) \
66         (G_TYPE_INSTANCE_GET_PRIVATE ((object), EV_TYPE_SIDEBAR_BOOKMARKS, EvSidebarBookmarksPrivate))
67
68
69 static void
70 ev_sidebar_bookmarks_destroy (GtkObject *object)
71 {
72         EvSidebarBookmarks *ev_sidebar_bookmarks = (EvSidebarBookmarks *) object;
73
74         g_print ("ev_sidebar_bookmarks_destroy!\n");
75         ev_sidebar_bookmarks_clear_document (ev_sidebar_bookmarks);
76 }
77
78 static void
79 ev_sidebar_bookmarks_class_init (EvSidebarBookmarksClass *ev_sidebar_bookmarks_class)
80 {
81         GObjectClass *g_object_class;
82         GtkObjectClass *gtk_object_class;
83
84         g_object_class = G_OBJECT_CLASS (ev_sidebar_bookmarks_class);
85         gtk_object_class = GTK_OBJECT_CLASS (ev_sidebar_bookmarks_class);
86
87         gtk_object_class->destroy = ev_sidebar_bookmarks_destroy;
88
89         g_type_class_add_private (g_object_class, sizeof (EvSidebarBookmarksPrivate));
90 }
91
92 static void
93 selection_changed_cb (GtkTreeSelection   *selection,
94                       EvSidebarBookmarks *ev_sidebar_bookmarks)
95 {
96         EvDocument *document;
97         GtkTreeModel *model;
98         GtkTreeIter iter;
99
100         g_return_if_fail (EV_IS_SIDEBAR_BOOKMARKS (ev_sidebar_bookmarks));
101
102         document = EV_DOCUMENT (ev_sidebar_bookmarks->priv->current_document);
103         g_return_if_fail (ev_sidebar_bookmarks->priv->current_document != NULL);
104
105         if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
106                 EvBookmark *bookmark;
107                 GValue value = {0, };
108                 int page;
109
110                 gtk_tree_model_get_value (model, &iter,
111                                           BOOKMARKS_COLUMN_BOOKMARK, &value);
112
113                 bookmark = EV_BOOKMARK (g_value_get_object (&value));
114                 g_return_if_fail (bookmark != NULL);
115
116                 page = ev_bookmark_get_page (bookmark);
117                 ev_document_set_page (document, page);
118         }
119 }
120
121 static void
122 ev_sidebar_bookmarks_construct (EvSidebarBookmarks *ev_sidebar_bookmarks)
123 {
124         EvSidebarBookmarksPrivate *priv;
125         GtkWidget *swindow;
126         GtkTreeViewColumn *column;
127         GtkCellRenderer *renderer;
128         GtkTreeSelection *selection;
129
130         priv = ev_sidebar_bookmarks->priv;
131         priv->model = (GtkTreeModel *) gtk_tree_store_new (BOOKMARKS_COLUMN_NUM_COLUMNS,
132                                                            G_TYPE_STRING,
133                                                            G_TYPE_INT,
134                                                            G_TYPE_BOOLEAN,
135                                                            G_TYPE_OBJECT);
136
137         swindow = gtk_scrolled_window_new (NULL, NULL);
138
139         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swindow),
140                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
141         gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (swindow),
142                                              GTK_SHADOW_IN);
143
144         /* Create tree view */
145         priv->tree_view = gtk_tree_view_new_with_model (priv->model);
146         g_object_unref (priv->model);
147         gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->tree_view), FALSE);
148         gtk_container_add (GTK_CONTAINER (swindow), priv->tree_view);
149         gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (priv->tree_view), TRUE);
150
151         gtk_box_pack_start (GTK_BOX (ev_sidebar_bookmarks), swindow, TRUE, TRUE, 0);
152         gtk_widget_show_all (GTK_WIDGET (ev_sidebar_bookmarks));
153
154         column = gtk_tree_view_column_new ();
155         gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column), TRUE);
156         gtk_tree_view_append_column (GTK_TREE_VIEW (priv->tree_view), column);
157
158         renderer = (GtkCellRenderer*)
159                 g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
160                               "ellipsize", PANGO_ELLIPSIZE_END,
161                               NULL);
162         gtk_tree_view_column_pack_start (GTK_TREE_VIEW_COLUMN (column), renderer, TRUE);
163         gtk_tree_view_column_set_attributes (GTK_TREE_VIEW_COLUMN (column), renderer,
164                                              "markup", BOOKMARKS_COLUMN_MARKUP,
165                                              NULL);
166
167         renderer = gtk_cell_renderer_text_new ();
168         gtk_tree_view_column_pack_end (GTK_TREE_VIEW_COLUMN (column), renderer, FALSE);
169         gtk_tree_view_column_set_cell_data_func (GTK_TREE_VIEW_COLUMN (column), renderer,
170                                                  (GtkTreeCellDataFunc) bookmarks_page_num_func,
171                                                  NULL, NULL);
172
173
174         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->tree_view));
175         g_signal_connect (selection, "changed",
176                           G_CALLBACK (selection_changed_cb),
177                           ev_sidebar_bookmarks);
178 }
179
180 static void
181 ev_sidebar_bookmarks_init (EvSidebarBookmarks *ev_sidebar_bookmarks)
182 {
183         ev_sidebar_bookmarks->priv = EV_SIDEBAR_BOOKMARKS_GET_PRIVATE (ev_sidebar_bookmarks);
184
185         ev_sidebar_bookmarks_construct (ev_sidebar_bookmarks);
186 }
187
188 static void
189 bookmarks_page_num_func (GtkTreeViewColumn *tree_column,
190                          GtkCellRenderer   *cell,
191                          GtkTreeModel      *tree_model,
192                          GtkTreeIter       *iter,
193                          gpointer           data)
194 {
195         int page_num;
196         gboolean page_valid;
197
198         gtk_tree_model_get (tree_model, iter,
199                             BOOKMARKS_COLUMN_PAGE_NUM, &page_num,
200                             BOOKMARKS_COLUMN_PAGE_VALID, &page_valid,
201                             -1);
202
203         if (page_valid) {
204                 gchar *markup = g_strdup_printf ("<i>%d</i>", page_num);
205                 g_object_set (cell,
206                               "markup", markup,
207                               "visible", TRUE,
208                               NULL);
209                 g_free (markup);
210         } else {
211                 g_object_set (cell,
212                               "visible", FALSE,
213                               NULL);
214         }
215 }
216
217 /* Public Functions */
218
219 GtkWidget *
220 ev_sidebar_bookmarks_new (void)
221 {
222         GtkWidget *ev_sidebar_bookmarks;
223
224         ev_sidebar_bookmarks = g_object_new (EV_TYPE_SIDEBAR_BOOKMARKS, NULL);
225
226         return ev_sidebar_bookmarks;
227 }
228
229 static void
230 stack_data_free (IdleStackData       *stack_data,
231                  EvDocumentBookmarks *document_bookmarks)
232 {
233         g_assert (stack_data);
234
235         if (stack_data->tree_iter)
236                 gtk_tree_iter_free (stack_data->tree_iter);
237         if (stack_data->bookmarks_iter)
238                 ev_document_bookmarks_free_iter (document_bookmarks, stack_data->bookmarks_iter);
239         g_free (stack_data);
240 }
241
242 static gboolean
243 do_one_iteration (EvSidebarBookmarks *ev_sidebar_bookmarks)
244 {
245         EvSidebarBookmarksPrivate *priv = ev_sidebar_bookmarks->priv;
246         EvBookmark *bookmark;
247         IdleStackData *stack_data;
248         GtkTreeIter tree_iter;
249         EvDocumentBookmarksIter *child_iter;
250         gint page;
251
252         g_assert (priv->idle_stack);
253
254         stack_data = (IdleStackData *) priv->idle_stack->data;
255
256         bookmark = ev_document_bookmarks_get_bookmark
257                 (EV_DOCUMENT_BOOKMARKS (priv->current_document),
258                  stack_data->bookmarks_iter);
259         if (bookmark == NULL) {
260                 g_warning ("mismatch in model.  No values available at current level.\n");
261                 return FALSE;
262         }
263
264         page = ev_bookmark_get_page (bookmark);
265         gtk_tree_store_append (GTK_TREE_STORE (priv->model), &tree_iter, stack_data->tree_iter);
266         gtk_tree_store_set (GTK_TREE_STORE (priv->model), &tree_iter,
267                             BOOKMARKS_COLUMN_MARKUP, ev_bookmark_get_title (bookmark),
268                             BOOKMARKS_COLUMN_PAGE_NUM, page,
269                             /* FIXME: Handle links for real. */
270                             BOOKMARKS_COLUMN_PAGE_VALID, (page >= 0),
271                             BOOKMARKS_COLUMN_BOOKMARK, bookmark,
272                             -1);
273         g_object_unref (bookmark);
274         
275         child_iter = ev_document_bookmarks_get_child (EV_DOCUMENT_BOOKMARKS (priv->current_document),
276                                                       stack_data->bookmarks_iter);
277         if (child_iter) {
278                 IdleStackData *child_stack_data;
279
280                 child_stack_data = g_new0 (IdleStackData, 1);
281                 child_stack_data->tree_iter = gtk_tree_iter_copy (&tree_iter);
282                 child_stack_data->bookmarks_iter = child_iter;
283                 priv->idle_stack = g_list_prepend (priv->idle_stack, child_stack_data);
284
285                 return TRUE;
286         }
287
288         /* We don't have children, so we need to walk to the next node */
289         while (TRUE) {
290                 if (ev_document_bookmarks_next (EV_DOCUMENT_BOOKMARKS (priv->current_document),
291                                                 stack_data->bookmarks_iter))
292                         return TRUE;
293
294                 /* We're done with this level.  Pop it off the idle stack and go
295                  * to the next level */
296                 stack_data_free (stack_data, EV_DOCUMENT_BOOKMARKS (priv->current_document));
297                 priv->idle_stack = g_list_delete_link (priv->idle_stack, priv->idle_stack);
298                 if (priv->idle_stack == NULL)
299                         return FALSE;
300                 stack_data = priv->idle_stack->data;
301         }
302 }
303
304 static gboolean
305 populate_bookmarks_idle (gpointer data)
306 {
307         GTimer *timer;
308         gint i;
309         gulong microseconds = 0;
310
311         EvSidebarBookmarks *ev_sidebar_bookmarks = (EvSidebarBookmarks *)data;
312         EvSidebarBookmarksPrivate *priv = ev_sidebar_bookmarks->priv;
313
314         if (priv->idle_stack == NULL) {
315                 priv->idle_id = 0;
316                 return FALSE;
317         }
318
319         /* The amount of time that reading the next bookmark takes is wildly
320          * inconsistent, so we constrain it to IDLE_WORK_LENGTH microseconds per
321          * idle iteration. */
322         timer = g_timer_new ();
323         i = 0;
324         g_timer_start (timer);
325         while (do_one_iteration (ev_sidebar_bookmarks)) {
326                 i++;
327                 g_timer_elapsed (timer, &microseconds);
328                 if (microseconds > IDLE_WORK_LENGTH)
329                         break;
330         }
331         g_timer_destroy (timer);
332 #if 0
333         g_print ("%d rows done this idle in %d\n", i, (int)microseconds);
334 #endif
335         return TRUE;
336 }
337
338 void
339 ev_sidebar_bookmarks_clear_document (EvSidebarBookmarks *sidebar_bookmarks)
340 {
341         EvSidebarBookmarksPrivate *priv;
342
343         g_return_if_fail (EV_IS_SIDEBAR_BOOKMARKS (sidebar_bookmarks));
344
345         priv = sidebar_bookmarks->priv;
346         if (priv->current_document) {
347                 g_object_unref (priv->current_document);
348                 priv->current_document = NULL;
349         }
350         gtk_tree_store_clear (GTK_TREE_STORE (priv->model));
351
352         /* Clear the idle */
353         if (priv->idle_id != 0) {
354                 g_source_remove (priv->idle_id);
355                 priv->idle_id = 0;
356         }
357         g_list_foreach (priv->idle_stack, (GFunc) stack_data_free, priv->current_document);
358         g_list_free (priv->idle_stack);
359         priv->idle_stack = NULL;
360
361 }
362
363 void
364 ev_sidebar_bookmarks_set_document (EvSidebarBookmarks *sidebar_bookmarks,
365                                    EvDocument         *document)
366 {
367         EvSidebarBookmarksPrivate *priv;
368         EvDocumentBookmarksIter *bookmarks_iter;
369
370         g_return_if_fail (EV_IS_SIDEBAR_BOOKMARKS (sidebar_bookmarks));
371         g_return_if_fail (EV_IS_DOCUMENT (document));
372
373         priv = sidebar_bookmarks->priv;
374
375         g_object_ref (document);
376         ev_sidebar_bookmarks_clear_document (sidebar_bookmarks);
377
378         priv->current_document = document;
379         bookmarks_iter = ev_document_bookmarks_begin_read (EV_DOCUMENT_BOOKMARKS (document));
380         if (bookmarks_iter) {
381                 IdleStackData *stack_data;
382
383                 stack_data = g_new0 (IdleStackData, 1);
384                 stack_data->bookmarks_iter = bookmarks_iter;
385                 stack_data->tree_iter = NULL;
386
387                 priv->idle_stack = g_list_prepend (priv->idle_stack, stack_data);
388                 priv->idle_id = g_idle_add (populate_bookmarks_idle, sidebar_bookmarks);
389         }
390 }
391