]> www.fi.muni.cz Git - evince.git/blob - libmisc/ev-page-action-widget.c
[libmisc] Don't sigsegv when a page is manually entered
[evince.git] / libmisc / ev-page-action-widget.c
1 /*
2  *  Copyright (C) 2003, 2004 Marco Pesenti Gritti
3  *  Copyright (C) 2003, 2004 Christian Persch
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  *
19  */
20
21 #include "config.h"
22
23 #include <string.h>
24 #include <glib/gi18n.h>
25 #include <gtk/gtk.h>
26
27 #include <evince-document.h>
28 #include "ev-page-action.h"
29 #include "ev-page-action-widget.h"
30
31 /* Widget we pass back */
32 static void  ev_page_action_widget_init       (EvPageActionWidget      *action_widget);
33 static void  ev_page_action_widget_class_init (EvPageActionWidgetClass *action_widget);
34
35 enum
36 {
37         WIDGET_ACTIVATE_LINK,
38         WIDGET_N_SIGNALS
39 };
40
41 struct _EvPageActionWidget
42 {
43         GtkToolItem parent;
44
45         EvDocument *document;
46         EvDocumentModel *doc_model;
47
48         GtkWidget *entry;
49         GtkWidget *label;
50         guint signal_id;
51         GtkTreeModel *filter_model;
52         GtkTreeModel *model;
53 };
54
55 static guint widget_signals[WIDGET_N_SIGNALS] = {0, };
56
57 G_DEFINE_TYPE (EvPageActionWidget, ev_page_action_widget, GTK_TYPE_TOOL_ITEM)
58
59 static void
60 update_pages_label (EvPageActionWidget *action_widget,
61                     gint                page)
62 {
63         char *label_text;
64         gint n_pages;
65
66         n_pages = ev_document_get_n_pages (action_widget->document);
67         if (ev_document_has_text_page_labels (action_widget->document)) {
68                 label_text = g_strdup_printf (_("(%d of %d)"), page + 1, n_pages);
69         } else {
70                 label_text = g_strdup_printf (_("of %d"), n_pages);
71         }
72         gtk_label_set_text (GTK_LABEL (action_widget->label), label_text);
73         g_free (label_text);
74 }
75
76 static void
77 ev_page_action_widget_set_current_page (EvPageActionWidget *action_widget,
78                                         gint                page)
79 {
80         if (page >= 0) {
81                 gchar *page_label;
82
83                 gtk_entry_set_width_chars (GTK_ENTRY (action_widget->entry),
84                                            CLAMP (ev_document_get_max_label_len (action_widget->document),
85                                                   6, 12));
86
87                 page_label = ev_document_get_page_label (action_widget->document, page);
88                 gtk_entry_set_text (GTK_ENTRY (action_widget->entry), page_label);
89                 gtk_editable_set_position (GTK_EDITABLE (action_widget->entry), -1);
90                 g_free (page_label);
91
92         } else {
93                 gtk_entry_set_text (GTK_ENTRY (action_widget->entry), "");
94         }
95
96         update_pages_label (action_widget, page);
97 }
98
99 static void
100 page_changed_cb (EvDocumentModel    *model,
101                  gint                old_page,
102                  gint                new_page,
103                  EvPageActionWidget *action_widget)
104 {
105         ev_page_action_widget_set_current_page (action_widget, new_page);
106 }
107
108 static gboolean
109 page_scroll_cb (EvPageActionWidget *action_widget, GdkEventScroll *event)
110 {
111         EvDocumentModel *model = action_widget->doc_model;
112         gint pageno;
113
114         pageno = ev_document_model_get_page (model);
115         if ((event->direction == GDK_SCROLL_DOWN) &&
116             (pageno < ev_document_get_n_pages (action_widget->document) - 1))
117                 pageno++;
118         if ((event->direction == GDK_SCROLL_UP) && (pageno > 0))
119                 pageno--;
120         ev_document_model_set_page (model, pageno);
121
122         return TRUE;
123 }
124
125 static void
126 activate_cb (EvPageActionWidget *action_widget)
127 {
128         EvDocumentModel *model;
129         const char *text;
130         EvLinkDest *link_dest;
131         EvLinkAction *link_action;
132         EvLink *link;
133         gchar *link_text;
134         gint current_page;
135
136         model = action_widget->doc_model;
137         current_page = ev_document_model_get_page (model);
138
139         text = gtk_entry_get_text (GTK_ENTRY (action_widget->entry));
140
141         link_dest = ev_link_dest_new_page_label (text);
142         link_action = ev_link_action_new_dest (link_dest);
143         link_text = g_strdup_printf ("Page: %s", text);
144         link = ev_link_new (link_text, link_action);
145
146         g_signal_emit (action_widget, widget_signals[WIDGET_ACTIVATE_LINK], 0, link);
147
148         g_object_unref (link);
149         g_free (link_text);
150
151         if (current_page == ev_document_model_get_page (model))
152                 ev_page_action_widget_set_current_page (action_widget, current_page);
153 }
154
155 static void
156 ev_page_action_widget_init (EvPageActionWidget *action_widget)
157 {
158         GtkWidget *hbox;
159         AtkObject *obj;
160
161         hbox = gtk_hbox_new (FALSE, 0);
162         gtk_box_set_spacing (GTK_BOX (hbox), 6);
163
164         action_widget->entry = gtk_entry_new ();
165         gtk_widget_add_events (action_widget->entry,
166                                GDK_BUTTON_MOTION_MASK);
167         gtk_entry_set_width_chars (GTK_ENTRY (action_widget->entry), 5);
168         gtk_entry_set_text (GTK_ENTRY (action_widget->entry), "");
169         g_signal_connect_swapped (action_widget->entry, "scroll-event",
170                                   G_CALLBACK (page_scroll_cb),
171                                   action_widget);
172         g_signal_connect_swapped (action_widget->entry, "activate",
173                                   G_CALLBACK (activate_cb),
174                                   action_widget);
175
176         obj = gtk_widget_get_accessible (action_widget->entry);
177         atk_object_set_name (obj, "page-label-entry");
178
179         gtk_box_pack_start (GTK_BOX (hbox), action_widget->entry,
180                             FALSE, FALSE, 0);
181         gtk_widget_show (action_widget->entry);
182
183         action_widget->label = gtk_label_new (NULL);
184         gtk_box_pack_start (GTK_BOX (hbox), action_widget->label,
185                             FALSE, FALSE, 0);
186         gtk_widget_show (action_widget->label);
187
188         gtk_container_set_border_width (GTK_CONTAINER (action_widget), 6);
189         gtk_container_add (GTK_CONTAINER (action_widget), hbox);
190         gtk_widget_show (hbox);
191
192         gtk_widget_show (GTK_WIDGET (action_widget));
193 }
194
195 static void
196 ev_page_action_widget_document_changed_cb (EvDocumentModel    *model,
197                                            GParamSpec         *pspec,
198                                            EvPageActionWidget *action_widget)
199 {
200         EvDocument *document = ev_document_model_get_document (model);
201
202         g_object_ref (document);
203         if (action_widget->document)
204                 g_object_unref (action_widget->document);
205         action_widget->document = document;
206
207         if (action_widget->signal_id > 0) {
208                 g_signal_handler_disconnect (action_widget->doc_model,
209                                              action_widget->signal_id);
210                 action_widget->signal_id = 0;
211         }
212         action_widget->signal_id =
213                 g_signal_connect_object (action_widget->doc_model,
214                                          "page-changed",
215                                          G_CALLBACK (page_changed_cb),
216                                          action_widget, 0);
217
218         ev_page_action_widget_set_current_page (action_widget,
219                                                 ev_document_model_get_page (model));
220 }
221
222 void
223 ev_page_action_widget_set_model (EvPageActionWidget *action_widget,
224                                  EvDocumentModel    *model)
225 {
226         if (action_widget->doc_model) {
227                 g_object_remove_weak_pointer (G_OBJECT (action_widget->doc_model),
228                                               (gpointer)&action_widget->doc_model);
229         }
230         action_widget->doc_model = model;
231         g_object_add_weak_pointer (G_OBJECT (model),
232                                    (gpointer)&action_widget->doc_model);
233
234         g_signal_connect (model, "notify::document",
235                           G_CALLBACK (ev_page_action_widget_document_changed_cb),
236                           action_widget);
237 }
238
239 static void
240 ev_page_action_widget_finalize (GObject *object)
241 {
242         EvPageActionWidget *action_widget = EV_PAGE_ACTION_WIDGET (object);
243
244         if (action_widget->doc_model != NULL) {
245                 if (action_widget->signal_id > 0) {
246                         g_signal_handler_disconnect (action_widget->doc_model,
247                                                      action_widget->signal_id);
248                         action_widget->signal_id = 0;
249                 }
250                 g_object_remove_weak_pointer (G_OBJECT (action_widget->doc_model),
251                                               (gpointer)&action_widget->doc_model);
252                 action_widget->doc_model = NULL;
253         }
254
255         if (action_widget->document) {
256                 g_object_unref (action_widget->document);
257                 action_widget->document = NULL;
258         }
259
260         G_OBJECT_CLASS (ev_page_action_widget_parent_class)->finalize (object);
261 }
262
263 static void
264 ev_page_action_widget_class_init (EvPageActionWidgetClass *class)
265 {
266         GObjectClass *object_class = G_OBJECT_CLASS (class);
267
268         object_class->finalize = ev_page_action_widget_finalize;
269
270         widget_signals[WIDGET_ACTIVATE_LINK] =
271                 g_signal_new ("activate_link",
272                               G_OBJECT_CLASS_TYPE (object_class),
273                               G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
274                               G_STRUCT_OFFSET (EvPageActionWidgetClass, activate_link),
275                               NULL, NULL,
276                               g_cclosure_marshal_VOID__OBJECT,
277                               G_TYPE_NONE, 1,
278                               G_TYPE_OBJECT);
279
280 }
281
282 static gboolean
283 match_selected_cb (GtkEntryCompletion *completion,
284                    GtkTreeModel       *filter_model,
285                    GtkTreeIter        *filter_iter,
286                    EvPageActionWidget *proxy)
287 {
288         EvLink *link;
289         GtkTreeIter *iter;
290
291         gtk_tree_model_get (filter_model, filter_iter,
292                             0, &iter,
293                             -1);
294         gtk_tree_model_get (proxy->model, iter,
295                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
296                             -1);
297
298         g_signal_emit (proxy, widget_signals[WIDGET_ACTIVATE_LINK], 0, link);
299
300         if (link)
301                 g_object_unref (link);
302
303         gtk_tree_iter_free (iter);
304         
305         return TRUE;
306 }
307                    
308
309 static void
310 display_completion_text (GtkCellLayout      *cell_layout,
311                          GtkCellRenderer    *renderer,
312                          GtkTreeModel       *filter_model,
313                          GtkTreeIter        *filter_iter,
314                          EvPageActionWidget *proxy)
315 {
316         EvLink *link;
317         GtkTreeIter *iter;
318
319         gtk_tree_model_get (filter_model, filter_iter,
320                             0, &iter,
321                             -1);
322         gtk_tree_model_get (proxy->model, iter,
323                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
324                             -1);
325
326         g_object_set (renderer, "text", ev_link_get_title (link), NULL);
327
328         if (link)
329                 g_object_unref (link);
330         
331         gtk_tree_iter_free (iter);
332 }
333
334 static gboolean
335 match_completion (GtkEntryCompletion *completion,
336                   const gchar        *key,
337                   GtkTreeIter        *filter_iter,
338                   EvPageActionWidget *proxy)
339 {
340         EvLink *link;
341         GtkTreeIter *iter;
342         const gchar *text = NULL;
343
344         gtk_tree_model_get (gtk_entry_completion_get_model (completion),
345                             filter_iter,
346                             0, &iter,
347                             -1);
348         gtk_tree_model_get (proxy->model, iter,
349                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
350                             -1);
351
352
353         if (link) {
354                 text = ev_link_get_title (link);
355                 g_object_unref (link);
356         }
357
358         gtk_tree_iter_free (iter);
359
360         if (text && key) {
361                 gchar *normalized_text;
362                 gchar *normalized_key;
363                 gchar *case_normalized_text;
364                 gchar *case_normalized_key;
365                 gboolean retval = FALSE;
366
367                 normalized_text = g_utf8_normalize (text, -1, G_NORMALIZE_ALL);
368                 normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL);
369                 case_normalized_text = g_utf8_casefold (normalized_text, -1);
370                 case_normalized_key = g_utf8_casefold (normalized_key, -1);
371
372                 if (strstr (case_normalized_text, case_normalized_key))
373                         retval = TRUE;
374
375                 g_free (normalized_text);
376                 g_free (normalized_key);
377                 g_free (case_normalized_text);
378                 g_free (case_normalized_key);
379
380                 return retval;
381         }
382
383         return FALSE;
384 }
385
386 /* user data to set on the widget. */
387 #define EPA_FILTER_MODEL_DATA "epa-filter-model"
388
389 static gboolean
390 build_new_tree_cb (GtkTreeModel *model,
391                    GtkTreePath  *path,
392                    GtkTreeIter  *iter,
393                    gpointer      data)
394 {
395         GtkTreeModel *filter_model = GTK_TREE_MODEL (data);
396         EvLink *link;
397         EvLinkAction *action;
398         EvLinkActionType type;
399
400         gtk_tree_model_get (model, iter,
401                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
402                             -1);
403
404         if (!link)
405                 return FALSE;
406
407         action = ev_link_get_action (link);
408         if (!action) {
409                 g_object_unref (link);
410                 return FALSE;
411         }
412         
413         type = ev_link_action_get_action_type (action);
414
415         if (type == EV_LINK_ACTION_TYPE_GOTO_DEST) {
416                 GtkTreeIter filter_iter;
417
418                 gtk_list_store_append (GTK_LIST_STORE (filter_model), &filter_iter);
419                 gtk_list_store_set (GTK_LIST_STORE (filter_model), &filter_iter,
420                                     0, iter,
421                                     -1);
422         }
423         
424         g_object_unref (link);
425         
426         return FALSE;
427 }
428
429 static GtkTreeModel *
430 get_filter_model_from_model (GtkTreeModel *model)
431 {
432         GtkTreeModel *filter_model;
433
434         filter_model =
435                 (GtkTreeModel *) g_object_get_data (G_OBJECT (model), EPA_FILTER_MODEL_DATA);
436         if (filter_model == NULL) {
437                 filter_model = (GtkTreeModel *) gtk_list_store_new (1, GTK_TYPE_TREE_ITER);
438
439                 gtk_tree_model_foreach (model,
440                                         build_new_tree_cb,
441                                         filter_model);
442                 g_object_set_data_full (G_OBJECT (model), EPA_FILTER_MODEL_DATA, filter_model, g_object_unref);
443         }
444
445         return filter_model;
446 }
447
448
449 void
450 ev_page_action_widget_update_links_model (EvPageActionWidget *proxy, GtkTreeModel *model)
451 {
452         GtkTreeModel *filter_model;
453         GtkEntryCompletion *completion;
454         GtkCellRenderer *renderer;
455
456         if (!model)
457                 return;
458
459         /* Magik */
460         proxy->model = model;
461         filter_model = get_filter_model_from_model (model);
462
463         completion = gtk_entry_completion_new ();
464         g_object_set (G_OBJECT (completion),
465                       "popup-set-width", FALSE,
466                       "model", filter_model,
467                       NULL);
468
469         g_signal_connect (completion, "match-selected", G_CALLBACK (match_selected_cb), proxy);
470         gtk_entry_completion_set_match_func (completion,
471                                              (GtkEntryCompletionMatchFunc) match_completion,
472                                              proxy, NULL);
473
474         /* Set up the layout */
475         renderer = (GtkCellRenderer *)
476                 g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
477                               "ellipsize", PANGO_ELLIPSIZE_END,
478                               "width_chars", 30,
479                               NULL);
480         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), renderer, TRUE);
481         gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (completion),
482                                             renderer,
483                                             (GtkCellLayoutDataFunc) display_completion_text,
484                                             proxy, NULL);
485         gtk_entry_set_completion (GTK_ENTRY (proxy->entry), completion);
486
487         g_object_unref (completion);
488 }
489
490 void
491 ev_page_action_widget_grab_focus (EvPageActionWidget *proxy)
492 {
493         gtk_widget_grab_focus (proxy->entry);
494 }
495