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