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