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