]> www.fi.muni.cz Git - evince.git/blob - libmisc/ev-page-action-widget.c
Update page action when entry text changes but not current page
[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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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         gchar *page_label;
131         EvLinkDest *link_dest;
132         EvLinkAction *link_action;
133         EvLink *link;
134         gchar *link_text;
135         gint current_page;
136
137         model = action_widget->doc_model;
138         current_page = ev_document_model_get_page (model);
139
140         text = gtk_entry_get_text (GTK_ENTRY (action_widget->entry));
141
142         link_dest = ev_link_dest_new_page_label (text);
143         link_action = ev_link_action_new_dest (link_dest);
144         link_text = g_strdup_printf ("Page: %s", text);
145         link = ev_link_new (link_text, link_action);
146
147         g_signal_emit (action_widget, widget_signals[WIDGET_ACTIVATE_LINK], 0, link);
148
149         g_object_unref (link);
150         g_free (link_text);
151
152         if (current_page == ev_document_model_get_page (model))
153                 ev_page_action_widget_set_current_page (action_widget, current_page);
154 }
155
156 static void
157 ev_page_action_widget_init (EvPageActionWidget *action_widget)
158 {
159         GtkWidget *hbox;
160         AtkObject *obj;
161
162         hbox = gtk_hbox_new (FALSE, 0);
163         gtk_box_set_spacing (GTK_BOX (hbox), 6);
164
165         action_widget->entry = gtk_entry_new ();
166         gtk_widget_add_events (action_widget->entry,
167                                GDK_BUTTON_MOTION_MASK);
168         gtk_entry_set_width_chars (GTK_ENTRY (action_widget->entry), 5);
169         gtk_entry_set_text (GTK_ENTRY (action_widget->entry), "");
170         g_signal_connect_swapped (action_widget->entry, "scroll-event",
171                                   G_CALLBACK (page_scroll_cb),
172                                   action_widget);
173         g_signal_connect_swapped (action_widget->entry, "activate",
174                                   G_CALLBACK (activate_cb),
175                                   action_widget);
176
177         obj = gtk_widget_get_accessible (action_widget->entry);
178         atk_object_set_name (obj, "page-label-entry");
179
180         gtk_box_pack_start (GTK_BOX (hbox), action_widget->entry,
181                             FALSE, FALSE, 0);
182         gtk_widget_show (action_widget->entry);
183
184         action_widget->label = gtk_label_new (NULL);
185         gtk_box_pack_start (GTK_BOX (hbox), action_widget->label,
186                             FALSE, FALSE, 0);
187         gtk_widget_show (action_widget->label);
188
189         gtk_container_set_border_width (GTK_CONTAINER (action_widget), 6);
190         gtk_container_add (GTK_CONTAINER (action_widget), hbox);
191         gtk_widget_show (hbox);
192
193         gtk_widget_show (GTK_WIDGET (action_widget));
194 }
195
196 static void
197 ev_page_action_widget_document_changed_cb (EvDocumentModel    *model,
198                                            GParamSpec         *pspec,
199                                            EvPageActionWidget *action_widget)
200 {
201         EvDocument *document = ev_document_model_get_document (model);
202
203         g_object_ref (document);
204         if (action_widget->document)
205                 g_object_unref (action_widget->document);
206         action_widget->document = document;
207
208         if (action_widget->signal_id > 0) {
209                 g_signal_handler_disconnect (action_widget->doc_model,
210                                              action_widget->signal_id);
211                 action_widget->signal_id = 0;
212         }
213         action_widget->signal_id =
214                 g_signal_connect_object (action_widget->doc_model,
215                                          "page-changed",
216                                          G_CALLBACK (page_changed_cb),
217                                          action_widget, 0);
218
219         ev_page_action_widget_set_current_page (action_widget,
220                                                 ev_document_model_get_page (model));
221 }
222
223 void
224 ev_page_action_widget_set_model (EvPageActionWidget *action_widget,
225                                  EvDocumentModel    *model)
226 {
227         if (action_widget->doc_model) {
228                 g_object_remove_weak_pointer (G_OBJECT (action_widget->doc_model),
229                                               (gpointer)&action_widget->doc_model);
230         }
231         action_widget->doc_model = model;
232         g_object_add_weak_pointer (G_OBJECT (model),
233                                    (gpointer)&action_widget->doc_model);
234
235         g_signal_connect (model, "notify::document",
236                           G_CALLBACK (ev_page_action_widget_document_changed_cb),
237                           action_widget);
238 }
239
240 static void
241 ev_page_action_widget_finalize (GObject *object)
242 {
243         EvPageActionWidget *action_widget = EV_PAGE_ACTION_WIDGET (object);
244
245         if (action_widget->doc_model != NULL) {
246                 if (action_widget->signal_id > 0) {
247                         g_signal_handler_disconnect (action_widget->doc_model,
248                                                      action_widget->signal_id);
249                         action_widget->signal_id = 0;
250                 }
251                 g_object_remove_weak_pointer (G_OBJECT (action_widget->doc_model),
252                                               (gpointer)&action_widget->doc_model);
253                 action_widget->doc_model = NULL;
254         }
255
256         if (action_widget->document) {
257                 g_object_unref (action_widget->document);
258                 action_widget->document = NULL;
259         }
260
261         G_OBJECT_CLASS (ev_page_action_widget_parent_class)->finalize (object);
262 }
263
264 static void
265 ev_page_action_widget_class_init (EvPageActionWidgetClass *class)
266 {
267         GObjectClass *object_class = G_OBJECT_CLASS (class);
268
269         object_class->finalize = ev_page_action_widget_finalize;
270
271         widget_signals[WIDGET_ACTIVATE_LINK] =
272                 g_signal_new ("activate_link",
273                               G_OBJECT_CLASS_TYPE (object_class),
274                               G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
275                               G_STRUCT_OFFSET (EvPageActionClass, activate_link),
276                               NULL, NULL,
277                               g_cclosure_marshal_VOID__OBJECT,
278                               G_TYPE_NONE, 1,
279                               G_TYPE_OBJECT);
280
281 }
282
283 static gboolean
284 match_selected_cb (GtkEntryCompletion *completion,
285                    GtkTreeModel       *filter_model,
286                    GtkTreeIter        *filter_iter,
287                    EvPageActionWidget *proxy)
288 {
289         EvLink *link;
290         GtkTreeIter *iter;
291
292         gtk_tree_model_get (filter_model, filter_iter,
293                             0, &iter,
294                             -1);
295         gtk_tree_model_get (proxy->model, iter,
296                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
297                             -1);
298
299         g_signal_emit (proxy, widget_signals[WIDGET_ACTIVATE_LINK], 0, link);
300
301         if (link)
302                 g_object_unref (link);
303
304         gtk_tree_iter_free (iter);
305         
306         return TRUE;
307 }
308                    
309
310 static void
311 display_completion_text (GtkCellLayout      *cell_layout,
312                          GtkCellRenderer    *renderer,
313                          GtkTreeModel       *filter_model,
314                          GtkTreeIter        *filter_iter,
315                          EvPageActionWidget *proxy)
316 {
317         EvLink *link;
318         GtkTreeIter *iter;
319
320         gtk_tree_model_get (filter_model, filter_iter,
321                             0, &iter,
322                             -1);
323         gtk_tree_model_get (proxy->model, iter,
324                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
325                             -1);
326
327         g_object_set (renderer, "text", ev_link_get_title (link), NULL);
328
329         if (link)
330                 g_object_unref (link);
331         
332         gtk_tree_iter_free (iter);
333 }
334
335 static gboolean
336 match_completion (GtkEntryCompletion *completion,
337                   const gchar        *key,
338                   GtkTreeIter        *filter_iter,
339                   EvPageActionWidget *proxy)
340 {
341         EvLink *link;
342         GtkTreeIter *iter;
343         const gchar *text = NULL;
344
345         gtk_tree_model_get (gtk_entry_completion_get_model (completion),
346                             filter_iter,
347                             0, &iter,
348                             -1);
349         gtk_tree_model_get (proxy->model, iter,
350                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
351                             -1);
352
353
354         if (link) {
355                 text = ev_link_get_title (link);
356                 g_object_unref (link);
357         }
358
359         gtk_tree_iter_free (iter);
360
361         if (text && key) {
362                 gchar *normalized_text;
363                 gchar *normalized_key;
364                 gchar *case_normalized_text;
365                 gchar *case_normalized_key;
366                 gboolean retval = FALSE;
367
368                 normalized_text = g_utf8_normalize (text, -1, G_NORMALIZE_ALL);
369                 normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL);
370                 case_normalized_text = g_utf8_casefold (normalized_text, -1);
371                 case_normalized_key = g_utf8_casefold (normalized_key, -1);
372
373                 if (strstr (case_normalized_text, case_normalized_key))
374                         retval = TRUE;
375
376                 g_free (normalized_text);
377                 g_free (normalized_key);
378                 g_free (case_normalized_text);
379                 g_free (case_normalized_key);
380
381                 return retval;
382         }
383
384         return FALSE;
385 }
386
387 /* user data to set on the widget. */
388 #define EPA_FILTER_MODEL_DATA "epa-filter-model"
389
390 static gboolean
391 build_new_tree_cb (GtkTreeModel *model,
392                    GtkTreePath  *path,
393                    GtkTreeIter  *iter,
394                    gpointer      data)
395 {
396         GtkTreeModel *filter_model = GTK_TREE_MODEL (data);
397         EvLink *link;
398         EvLinkAction *action;
399         EvLinkActionType type;
400
401         gtk_tree_model_get (model, iter,
402                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
403                             -1);
404
405         if (!link)
406                 return FALSE;
407
408         action = ev_link_get_action (link);
409         if (!action) {
410                 g_object_unref (link);
411                 return FALSE;
412         }
413         
414         type = ev_link_action_get_action_type (action);
415
416         if (type == EV_LINK_ACTION_TYPE_GOTO_DEST) {
417                 GtkTreeIter filter_iter;
418
419                 gtk_list_store_append (GTK_LIST_STORE (filter_model), &filter_iter);
420                 gtk_list_store_set (GTK_LIST_STORE (filter_model), &filter_iter,
421                                     0, iter,
422                                     -1);
423         }
424         
425         g_object_unref (link);
426         
427         return FALSE;
428 }
429
430 static GtkTreeModel *
431 get_filter_model_from_model (GtkTreeModel *model)
432 {
433         GtkTreeModel *filter_model;
434
435         filter_model =
436                 (GtkTreeModel *) g_object_get_data (G_OBJECT (model), EPA_FILTER_MODEL_DATA);
437         if (filter_model == NULL) {
438                 filter_model = (GtkTreeModel *) gtk_list_store_new (1, GTK_TYPE_TREE_ITER);
439
440                 gtk_tree_model_foreach (model,
441                                         build_new_tree_cb,
442                                         filter_model);
443                 g_object_set_data_full (G_OBJECT (model), EPA_FILTER_MODEL_DATA, filter_model, g_object_unref);
444         }
445
446         return filter_model;
447 }
448
449
450 void
451 ev_page_action_widget_update_links_model (EvPageActionWidget *proxy, GtkTreeModel *model)
452 {
453         GtkTreeModel *filter_model;
454         GtkEntryCompletion *completion;
455         GtkCellRenderer *renderer;
456
457         if (!model)
458                 return;
459
460         /* Magik */
461         proxy->model = model;
462         filter_model = get_filter_model_from_model (model);
463
464         completion = gtk_entry_completion_new ();
465         g_object_set (G_OBJECT (completion),
466                       "popup-set-width", FALSE,
467                       "model", filter_model,
468                       NULL);
469
470         g_signal_connect (completion, "match-selected", G_CALLBACK (match_selected_cb), proxy);
471         gtk_entry_completion_set_match_func (completion,
472                                              (GtkEntryCompletionMatchFunc) match_completion,
473                                              proxy, NULL);
474
475         /* Set up the layout */
476         renderer = (GtkCellRenderer *)
477                 g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
478                               "ellipsize", PANGO_ELLIPSIZE_END,
479                               "width_chars", 30,
480                               NULL);
481         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), renderer, TRUE);
482         gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (completion),
483                                             renderer,
484                                             (GtkCellLayoutDataFunc) display_completion_text,
485                                             proxy, NULL);
486         gtk_entry_set_completion (GTK_ENTRY (proxy->entry), completion);
487
488         g_object_unref (completion);
489 }
490
491 void
492 ev_page_action_widget_grab_focus (EvPageActionWidget *proxy)
493 {
494         gtk_widget_grab_focus (proxy->entry);
495 }
496