1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8;  -*-
2  *
3  *  Copyright (C) 2010 Carlos Garcia Campos  <carlosgc@gnome.org>
4  *  Copyright (C) 2013 Red Hat, Inc.
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2, or (at your option)
9  *  any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  */
20 
21 #include "config.h"
22 
23 #include <glib/gi18n.h>
24 
25 #include <evince-document.h>
26 #include <evince-view.h>
27 
28 #include <libgd/gd.h>
29 
30 #include "gd-places-bookmarks.h"
31 #include "gd-places-page.h"
32 
33 struct _GdPlacesBookmarksPrivate {
34         EvDocumentModel *document_model;
35         GdBookmarks     *bookmarks;
36         const char      *name;
37         GtkWidget       *tree_view;
38 
39         EvJob           *job;
40 
41         guint            activated_id;
42 };
43 
44 enum {
45         PROP_0,
46         PROP_NAME,
47         PROP_DOCUMENT_MODEL,
48         PROP_BOOKMARKS,
49 };
50 
51 enum {
52         COLUMN_MARKUP,
53         COLUMN_PAGE_LABEL,
54         COLUMN_BOOKMARK,
55         N_COLUMNS
56 };
57 
58 enum {
59         BOOKMARK_ACTIVATED,
60         N_SIGNALS
61 };
62 
63 static guint signals[N_SIGNALS];
64 
65 static void gd_places_bookmarks_page_iface_init (GdPlacesPageInterface *iface);
66 
67 G_DEFINE_TYPE_EXTENDED (GdPlacesBookmarks,
68                         gd_places_bookmarks,
69                         GTK_TYPE_BOX,
70                         0,
71                         G_IMPLEMENT_INTERFACE (GD_TYPE_PLACES_PAGE,
72                                                gd_places_bookmarks_page_iface_init))
73 
74 static GdBookmark *
gd_places_bookmarks_get_selected_bookmark(GdPlacesBookmarks * self,GtkTreeSelection * selection)75 gd_places_bookmarks_get_selected_bookmark (GdPlacesBookmarks *self,
76                                            GtkTreeSelection  *selection)
77 {
78         GtkTreeModel *model;
79         GtkTreeIter   iter;
80 
81         if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
82                 GdBookmark *bookmark;
83 
84                 gtk_tree_model_get (model, &iter,
85                                     COLUMN_BOOKMARK, &bookmark,
86                                     -1);
87                 return bookmark;
88         }
89 
90         return NULL;
91 }
92 
93 typedef struct {
94         EvDocument *document;
95         guint page_number;
96         char *markup;
97 } LinkModelData;
98 
99 static gboolean
link_model_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer user_data)100 link_model_foreach (GtkTreeModel *model,
101                     GtkTreePath  *path,
102                     GtkTreeIter  *iter,
103                     gpointer      user_data)
104 {
105         LinkModelData *data = user_data;
106         EvLink *link = NULL;
107         char *markup = NULL;
108         int link_page;
109         gboolean ret = FALSE;
110 
111         gtk_tree_model_get (model, iter,
112                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
113                             EV_DOCUMENT_LINKS_COLUMN_MARKUP, &markup,
114                             -1);
115         if (link != NULL) {
116                 link_page = ev_document_links_get_link_page (EV_DOCUMENT_LINKS (data->document), link);
117                 if (link_page == data->page_number) {
118                         GtkTreeIter parent;
119 
120                         if (gtk_tree_model_iter_parent (model, &parent, iter)) {
121                                 char *parent_markup = NULL;
122                                 gtk_tree_model_get (model, &parent,
123                                                     EV_DOCUMENT_LINKS_COLUMN_MARKUP, &parent_markup,
124                                                     -1);
125                                 if (parent_markup != NULL) {
126                                         data->markup = g_strdup_printf ("%s ﹥ %s", parent_markup, markup);
127                                         g_free (parent_markup);
128                                 }
129                         }
130 
131                         if (data->markup == NULL) {
132                                 data->markup = g_strdup (markup);
133                         }
134 
135                         ret = TRUE;
136                 }
137         }
138 
139         g_free (markup);
140         g_clear_object (&link);
141 
142         return ret;
143 }
144 
145 static char *
get_link_title_for_page(EvDocument * document,GtkTreeModel * links_model,guint page)146 get_link_title_for_page (EvDocument   *document,
147                          GtkTreeModel *links_model,
148                          guint         page)
149 {
150         LinkModelData *data;
151         char *ret;
152 
153         data = g_new0 (LinkModelData, 1);
154         data->page_number = page;
155         data->document = document;
156         gtk_tree_model_foreach (links_model, link_model_foreach, data);
157         ret = data->markup;
158         g_free (data);
159 
160         return ret;
161 }
162 
163 static void
enable_selection(GdPlacesBookmarks * self,gboolean enabled)164 enable_selection (GdPlacesBookmarks *self,
165                   gboolean           enabled)
166 {
167         GtkTreeSelection *selection;
168 
169         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->tree_view));
170         gtk_tree_selection_set_mode (selection, enabled ? GTK_SELECTION_SINGLE : GTK_SELECTION_NONE);
171 }
172 
173 #define MAX_LEN_LABEL 200
174 #define MIN_LEN_LABEL 20
175 
176 static char *
get_pretty_name(const char * text)177 get_pretty_name (const char *text)
178 {
179         char *name = NULL;
180         char *trimmed;
181         char *basename = NULL;
182         int i;
183         int last_word = -1;
184         int last_sentence = -1;
185         int last_nonspace = -1;
186         int num_attrs;
187         PangoLogAttr *attrs;
188         gboolean ellipse = TRUE;
189 
190         num_attrs = MIN (g_utf8_strlen (text, -1) + 1, MAX_LEN_LABEL);
191         attrs = g_new (PangoLogAttr, num_attrs);
192         trimmed = g_utf8_substring (text, 0, num_attrs - 1);
193         pango_get_log_attrs (trimmed, -1, -1, pango_language_get_default (), attrs, num_attrs);
194 
195         /* since the end of the text will always match a word boundary don't include it */
196         for (i = 0; (i < num_attrs - 1); i++) {
197                 if (!attrs[i].is_white) {
198                         last_nonspace = i;
199                 }
200                 if (attrs[i].is_sentence_end) {
201                         last_sentence = i;
202                 }
203                 if (attrs[i].is_word_boundary) {
204                         last_word = last_nonspace;
205                 }
206         }
207         g_free (attrs);
208 
209         if (last_sentence > 0) {
210                 i = last_sentence;
211                 ellipse = FALSE;
212         } else {
213                 i = last_word;
214         }
215 
216         if (i == -1) {
217                 name = NULL;
218                 goto out;
219         }
220 
221         basename = g_utf8_substring (trimmed, 0, i);
222         if (ellipse) {
223                 name = g_strdup_printf ("“%s…”", basename);
224         } else {
225                 name = g_strdup_printf ("“%s”", basename);
226         }
227 
228  out:
229         g_free (basename);
230         g_free (trimmed);
231 
232         return name;
233 }
234 
235 static char *
remove_duplicate_whitespace(const char * old)236 remove_duplicate_whitespace (const char *old)
237 {
238         char   *new;
239         GRegex *re;
240         GError *error;
241 
242         error = NULL;
243         re = g_regex_new ("[ \t\n\r]+", G_REGEX_MULTILINE, 0, &error);
244         if (re == NULL) {
245                 g_warning ("Error building regex: %s", error->message);
246                 g_error_free (error);
247                 return g_strdup (old);
248         }
249 
250         new = g_regex_replace (re, old, -1, 0, " ", 0, &error);
251         g_regex_unref (re);
252         if (new == NULL) {
253                 g_warning ("Error replacing string: %s", error->message);
254                 g_error_free (error);
255                 return g_strdup (old);
256         }
257 
258         return new;
259 }
260 
261 static void
load_bookmark_model(GdPlacesBookmarks * self,GtkTreeModel * links_model)262 load_bookmark_model (GdPlacesBookmarks *self,
263                      GtkTreeModel      *links_model)
264 {
265         GtkListStore *model;
266         GList *items;
267         GList *l;
268         EvDocument *document;
269 
270         if (self->priv->bookmarks == NULL) {
271                 return;
272         }
273 
274         model = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (self->priv->tree_view)));
275 
276         document = ev_document_model_get_document (self->priv->document_model);
277 
278         items = gd_bookmarks_get_bookmarks (self->priv->bookmarks);
279         items = g_list_sort (items, (GCompareFunc)gd_bookmark_compare);
280         for (l = items; l; l = g_list_next (l)) {
281                 GdBookmark *bookmark = (GdBookmark *)l->data;
282                 GtkTreeIter iter;
283                 const char *title;
284                 char *label = NULL;
285                 char *markup = NULL;
286                 guint page;
287 
288                 title = gd_bookmark_get_title (bookmark);
289                 page = gd_bookmark_get_page_number (bookmark);
290 
291                 if (ev_document_has_text_page_labels (document)) {
292                         label = ev_document_get_page_label (document, page);
293                 } else {
294                         label = g_strdup_printf ("%d", page + 1);
295                 }
296 
297                 if (links_model != NULL) {
298                         markup = get_link_title_for_page (document, links_model, page);
299                 }
300 
301                 if (markup == NULL && EV_IS_DOCUMENT_TEXT (document)) {
302                         char *text;
303                         char *trimmed;
304                         char *stripped;
305                         EvPage *ev_page;
306 
307                         ev_page = ev_document_get_page (document, page);
308                         text = ev_document_text_get_text (EV_DOCUMENT_TEXT (document), ev_page);
309                         trimmed = g_utf8_substring (text, 0, MAX_LEN_LABEL * 2);
310                         g_free (text);
311                         stripped = remove_duplicate_whitespace (trimmed);
312                         g_free (trimmed);
313                         markup = get_pretty_name (stripped);
314                         g_free (stripped);
315                 }
316 
317                 if (markup == NULL) {
318                         /* Translators: %s is the number of the page, already formatted
319                          * as a string, for example "Page 5".
320                          */
321                         markup = g_strdup_printf (_("Page %s"), label);
322                 }
323 
324                 gtk_list_store_append (model, &iter);
325                 gtk_list_store_set (model, &iter,
326                                     COLUMN_MARKUP, markup != NULL ? markup : title,
327                                     COLUMN_PAGE_LABEL, label,
328                                     COLUMN_BOOKMARK, bookmark,
329                                     -1);
330                 g_free (label);
331                 g_free (markup);
332         }
333 
334         enable_selection (self, TRUE);
335 
336         g_list_free (items);
337 }
338 
339 static void
job_finished_cb(EvJobLinks * job,GdPlacesBookmarks * self)340 job_finished_cb (EvJobLinks        *job,
341                  GdPlacesBookmarks *self)
342 {
343         GdPlacesBookmarksPrivate *priv = self->priv;
344         GtkListStore             *model;
345 
346         model = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (priv->tree_view)));
347         gtk_list_store_clear (model);
348         load_bookmark_model (self, job->model);
349 
350         g_clear_object (&priv->job);
351 }
352 
353 static void
gd_places_bookmarks_update(GdPlacesBookmarks * self)354 gd_places_bookmarks_update (GdPlacesBookmarks *self)
355 {
356         GdPlacesBookmarksPrivate *priv = self->priv;
357         GtkListStore             *model;
358         GtkTreeIter               iter;
359         guint                     n_items = 0;
360         EvDocument               *document;
361 
362         if (priv->document_model == NULL) {
363                 /* not loaded yet */
364                 return;
365         }
366 
367         if (priv->job != NULL) {
368                 ev_job_cancel (priv->job);
369                 g_clear_object (&priv->job);
370         }
371 
372         model = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (priv->tree_view)));
373         gtk_list_store_clear (model);
374         enable_selection (self, FALSE);
375 
376         if (priv->bookmarks != NULL) {
377                 n_items = gd_bookmarks_get_n_items (priv->bookmarks);
378         }
379 
380         document = ev_document_model_get_document (priv->document_model);
381         if (n_items == 0) {
382                 gtk_list_store_append (model, &iter);
383                 gtk_list_store_set (model, &iter,
384                                     COLUMN_MARKUP, _("No bookmarks"),
385                                     COLUMN_PAGE_LABEL, NULL,
386                                     COLUMN_BOOKMARK, NULL,
387                                     -1);
388         } else if (EV_IS_DOCUMENT_LINKS (document) &&
389                    ev_document_links_has_document_links (EV_DOCUMENT_LINKS (document))) {
390                 gtk_list_store_append (model, &iter);
391                 gtk_list_store_set (model, &iter,
392                                     COLUMN_MARKUP, _("Loading…"),
393                                     COLUMN_PAGE_LABEL, NULL,
394                                     COLUMN_BOOKMARK, NULL,
395                                     -1);
396                 priv->job = ev_job_links_new (document);
397                 g_signal_connect (priv->job,
398                                   "finished",
399                                   G_CALLBACK (job_finished_cb),
400                                   self);
401 
402                 /* The priority doesn't matter for this job */
403                 ev_job_scheduler_push_job (priv->job, EV_JOB_PRIORITY_NONE);
404         } else {
405                 load_bookmark_model (self, NULL);
406         }
407 }
408 
409 static void
gd_places_bookmarks_changed(GdBookmarks * bookmarks,GdPlacesBookmarks * self)410 gd_places_bookmarks_changed (GdBookmarks       *bookmarks,
411                              GdPlacesBookmarks *self)
412 {
413         gd_places_bookmarks_update (self);
414 }
415 
416 static gboolean
emit_activated(GdPlacesBookmarks * self)417 emit_activated (GdPlacesBookmarks *self)
418 {
419         GtkTreeSelection *selection;
420         GdBookmark       *bookmark;
421 
422         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self->priv->tree_view));
423         bookmark = gd_places_bookmarks_get_selected_bookmark (self, selection);
424 
425         if (bookmark != NULL) {
426                 g_signal_emit (self, signals[BOOKMARK_ACTIVATED], 0, bookmark);
427 
428                 g_object_unref (bookmark);
429         }
430 
431         self->priv->activated_id = 0;
432 
433         return FALSE;
434 }
435 
436 static void
schedule_emit_activated(GdPlacesBookmarks * self)437 schedule_emit_activated (GdPlacesBookmarks *self)
438 {
439         /* jump through some hoops to avoid destroying in the middle
440            of a button release handler */
441         if (self->priv->activated_id == 0) {
442                 self->priv->activated_id = g_idle_add ((GSourceFunc) emit_activated, self);
443         }
444 }
445 
446 static void
gd_places_bookmarks_set_document_model(GdPlacesPage * page,EvDocumentModel * model)447 gd_places_bookmarks_set_document_model (GdPlacesPage    *page,
448                                         EvDocumentModel *model)
449 {
450         GdPlacesBookmarks *self = GD_PLACES_BOOKMARKS (page);
451         GdPlacesBookmarksPrivate *priv = self->priv;
452 
453         if (priv->document_model == model)
454                 return;
455 
456         if (priv->document_model != NULL) {
457                 g_signal_handlers_disconnect_by_func (priv->document_model,
458                                                       gd_places_bookmarks_update,
459                                                       page);
460         }
461 
462         g_clear_object (&priv->document_model);
463         priv->document_model = model;
464 
465         if (priv->document_model != NULL) {
466                 g_object_ref (priv->document_model);
467                 g_signal_connect_swapped (priv->document_model,
468                                           "notify::document",
469                                           G_CALLBACK (gd_places_bookmarks_update),
470                                           page);
471         }
472 
473         gd_places_bookmarks_update (self);
474 }
475 
476 void
gd_places_bookmarks_set_bookmarks(GdPlacesBookmarks * self,GdBookmarks * bookmarks)477 gd_places_bookmarks_set_bookmarks (GdPlacesBookmarks *self,
478                                    GdBookmarks       *bookmarks)
479 {
480         GdPlacesBookmarksPrivate *priv = self->priv;
481 
482         g_return_if_fail (GD_IS_BOOKMARKS (bookmarks));
483 
484         if (priv->bookmarks == bookmarks)
485                 return;
486 
487         if (priv->bookmarks != NULL) {
488                 g_signal_handlers_disconnect_by_func (priv->bookmarks,
489                                                       G_CALLBACK (gd_places_bookmarks_update),
490                                                       self);
491         }
492 
493         g_clear_object (&priv->bookmarks);
494         priv->bookmarks = g_object_ref (bookmarks);
495         g_signal_connect_swapped (priv->bookmarks, "changed",
496                                   G_CALLBACK (gd_places_bookmarks_update),
497                                   self);
498 
499         gd_places_bookmarks_update (self);
500 }
501 
502 static void
gd_places_bookmarks_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)503 gd_places_bookmarks_set_property (GObject      *object,
504                                   guint         prop_id,
505                                   const GValue *value,
506                                   GParamSpec   *pspec)
507 {
508 
509         GdPlacesBookmarks *self = GD_PLACES_BOOKMARKS (object);
510 
511         switch (prop_id) {
512         case PROP_DOCUMENT_MODEL:
513                 gd_places_bookmarks_set_document_model (GD_PLACES_PAGE (self), g_value_get_object (value));
514                 break;
515         case PROP_BOOKMARKS:
516                 gd_places_bookmarks_set_bookmarks (self, g_value_get_object (value));
517                 break;
518         default:
519                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
520                 break;
521         }
522 }
523 
524 static void
gd_places_bookmarks_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)525 gd_places_bookmarks_get_property (GObject    *object,
526                                   guint       prop_id,
527                                   GValue     *value,
528                                   GParamSpec *pspec)
529 {
530         GdPlacesBookmarks *self = GD_PLACES_BOOKMARKS (object);
531 
532         switch (prop_id) {
533         case PROP_NAME:
534                 g_value_set_string (value, self->priv->name);
535                 break;
536         case PROP_DOCUMENT_MODEL:
537                 g_value_set_object (value, self->priv->document_model);
538                 break;
539         case PROP_BOOKMARKS:
540                 g_value_set_object (value, self->priv->bookmarks);
541                 break;
542         default:
543                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
544                 break;
545         }
546 }
547 
548 static void
gd_places_bookmarks_dispose(GObject * object)549 gd_places_bookmarks_dispose (GObject *object)
550 {
551         GdPlacesBookmarks *self = GD_PLACES_BOOKMARKS (object);
552         GdPlacesBookmarksPrivate *priv = self->priv;
553 
554         if (priv->bookmarks != NULL) {
555                 g_signal_handlers_disconnect_by_func (priv->bookmarks,
556                                                       G_CALLBACK (gd_places_bookmarks_changed),
557                                                       self);
558         }
559 
560         if (priv->document_model != NULL) {
561                 g_signal_handlers_disconnect_by_func (priv->document_model,
562                                                       gd_places_bookmarks_update,
563                                                       self);
564         }
565 
566         if (self->priv->job != NULL) {
567                 ev_job_cancel (self->priv->job);
568                 g_clear_object (&self->priv->job);
569         }
570 
571         if (self->priv->activated_id > 0) {
572                 g_source_remove (self->priv->activated_id);
573                 self->priv->activated_id = 0;
574         }
575 
576         g_clear_object (&priv->document_model);
577         g_clear_object (&priv->bookmarks);
578 
579         G_OBJECT_CLASS (gd_places_bookmarks_parent_class)->dispose (object);
580 }
581 
582 static void
gd_places_bookmarks_construct(GdPlacesBookmarks * self)583 gd_places_bookmarks_construct (GdPlacesBookmarks *self)
584 {
585         GdPlacesBookmarksPrivate *priv = self->priv;
586         GtkWidget                *swindow;
587         GtkWidget                *hbox;
588         GtkListStore             *model;
589         GtkTreeViewColumn        *column;
590         GtkCellRenderer          *renderer;
591 
592         swindow = gtk_scrolled_window_new (NULL, NULL);
593         gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (swindow),
594                                              GTK_SHADOW_IN);
595         gtk_box_pack_start (GTK_BOX (self), swindow, TRUE, TRUE, 0);
596         gtk_widget_show (swindow);
597 
598         model = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, GD_TYPE_BOOKMARK);
599         priv->tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
600         enable_selection (self, FALSE);
601         gtk_tree_view_set_activate_on_single_click (GTK_TREE_VIEW (priv->tree_view), TRUE);
602         gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->tree_view), FALSE);
603         g_object_unref (model);
604 
605         g_signal_connect_swapped (priv->tree_view, "row-activated",
606                                   G_CALLBACK (schedule_emit_activated),
607                                   self);
608 
609         column = gtk_tree_view_column_new ();
610         gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column), TRUE);
611         gtk_tree_view_append_column (GTK_TREE_VIEW (priv->tree_view), column);
612 
613         renderer = gtk_cell_renderer_text_new ();
614         g_object_set (renderer,
615                       "wrap-mode", PANGO_WRAP_WORD,
616                       "wrap-width", 350,
617                       "weight", PANGO_WEIGHT_BOLD,
618                       "xpad", 10,
619                       NULL);
620         gtk_tree_view_column_pack_start (GTK_TREE_VIEW_COLUMN (column), renderer, TRUE);
621         gtk_tree_view_column_set_attributes (GTK_TREE_VIEW_COLUMN (column), renderer,
622                                              "markup", COLUMN_MARKUP,
623                                              NULL);
624 
625         renderer = gd_styled_text_renderer_new ();
626         gd_styled_text_renderer_add_class (GD_STYLED_TEXT_RENDERER (renderer), "dim-label");
627         g_object_set (renderer,
628                       "max-width-chars", 12,
629                       "scale", PANGO_SCALE_SMALL,
630                       "xalign", 1.0,
631                       "xpad", 10,
632                       NULL);
633         gtk_tree_view_column_pack_end (GTK_TREE_VIEW_COLUMN (column), renderer, FALSE);
634         gtk_tree_view_column_set_attributes (GTK_TREE_VIEW_COLUMN (column), renderer,
635                                              "text", COLUMN_PAGE_LABEL,
636                                              NULL);
637 
638         gtk_container_add (GTK_CONTAINER (swindow), priv->tree_view);
639         gtk_widget_show (priv->tree_view);
640 
641         hbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
642 
643         gtk_box_pack_end (GTK_BOX (self), hbox, FALSE, TRUE, 0);
644         gtk_widget_show (hbox);
645         gtk_widget_show (GTK_WIDGET (self));
646 }
647 
648 static void
gd_places_bookmarks_init(GdPlacesBookmarks * self)649 gd_places_bookmarks_init (GdPlacesBookmarks *self)
650 {
651 
652         self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
653                                                   GD_TYPE_PLACES_BOOKMARKS,
654                                                   GdPlacesBookmarksPrivate);
655 
656         self->priv->name = _("Bookmarks");
657 
658         gd_places_bookmarks_construct (self);
659 }
660 
661 static void
gd_places_bookmarks_class_init(GdPlacesBookmarksClass * klass)662 gd_places_bookmarks_class_init (GdPlacesBookmarksClass *klass)
663 {
664         GObjectClass   *oclass = G_OBJECT_CLASS (klass);
665 
666         oclass->get_property = gd_places_bookmarks_get_property;
667         oclass->set_property = gd_places_bookmarks_set_property;
668         oclass->dispose = gd_places_bookmarks_dispose;
669 
670         signals[BOOKMARK_ACTIVATED] = g_signal_new ("bookmark-activated",
671                                                     G_TYPE_FROM_CLASS (oclass),
672                                                     G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
673                                                     0,
674                                                     NULL, NULL,
675                                                     g_cclosure_marshal_VOID__OBJECT,
676                                                     G_TYPE_NONE, 1, G_TYPE_OBJECT);
677 
678         g_object_class_install_property (oclass,
679                                          PROP_BOOKMARKS,
680                                          g_param_spec_object ("bookmarks",
681                                                               "Bookmarks",
682                                                               "Bookmarks",
683                                                               GD_TYPE_BOOKMARKS,
684                                                               G_PARAM_READWRITE |
685                                                               G_PARAM_STATIC_STRINGS));
686 
687         g_object_class_override_property (oclass, PROP_NAME, "name");
688         g_object_class_override_property (oclass, PROP_DOCUMENT_MODEL, "document-model");
689 
690         g_type_class_add_private (oclass, sizeof (GdPlacesBookmarksPrivate));
691 }
692 
693 GtkWidget *
gd_places_bookmarks_new(void)694 gd_places_bookmarks_new (void)
695 {
696         return GTK_WIDGET (g_object_new (GD_TYPE_PLACES_BOOKMARKS, NULL));
697 }
698 
699 static gboolean
gd_places_bookmarks_supports_document(GdPlacesPage * page,EvDocument * document)700 gd_places_bookmarks_supports_document (GdPlacesPage *page,
701                                        EvDocument   *document)
702 {
703         return TRUE;
704 }
705 
706 static const char *
gd_places_bookmarks_get_name(GdPlacesPage * page)707 gd_places_bookmarks_get_name (GdPlacesPage *page)
708 {
709         return GD_PLACES_BOOKMARKS (page)->priv->name;
710 }
711 
712 static void
gd_places_bookmarks_page_iface_init(GdPlacesPageInterface * iface)713 gd_places_bookmarks_page_iface_init (GdPlacesPageInterface *iface)
714 {
715         iface->supports_document = gd_places_bookmarks_supports_document;
716         iface->set_document_model = gd_places_bookmarks_set_document_model;
717         iface->get_name = gd_places_bookmarks_get_name;
718 }
719