]> www.fi.muni.cz Git - evince.git/blob - libmisc/ev-page-action-widget.c
c24c75658c886e35d83ba5f1c717ea48e4349683
[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         GtkWidget *entry;
46         GtkWidget *label;
47         EvPageCache *page_cache;
48         guint signal_id;
49         GtkTreeModel *filter_model;
50         GtkTreeModel *model;
51 };
52
53 static guint widget_signals[WIDGET_N_SIGNALS] = {0, };
54
55 G_DEFINE_TYPE (EvPageActionWidget, ev_page_action_widget, GTK_TYPE_TOOL_ITEM)
56
57 static void
58 update_pages_label (EvPageActionWidget *action_widget,
59                     gint                page,
60                     EvPageCache        *page_cache)
61 {
62         char *label_text;
63         gint n_pages;
64
65         n_pages = page_cache ? ev_page_cache_get_n_pages (page_cache) : 0;
66         if (page_cache && ev_page_cache_has_nonnumeric_page_labels (page_cache)) {
67                 label_text = g_strdup_printf (_("(%d of %d)"), page + 1, n_pages);
68         } else {
69                 label_text = g_strdup_printf (_("of %d"), n_pages);
70         }
71         gtk_label_set_text (GTK_LABEL (action_widget->label), label_text);
72         g_free (label_text);
73 }
74
75 static void
76 page_changed_cb (EvPageCache        *page_cache,
77                  gint                page,
78                  EvPageActionWidget *action_widget)
79 {
80         if (page_cache && page >= 0) {
81                 gchar *page_label;
82
83                 gtk_entry_set_width_chars (GTK_ENTRY (action_widget->entry),
84                                            CLAMP (ev_page_cache_get_max_label_chars (page_cache),
85                                                   6, 12));
86
87                 page_label = ev_page_cache_get_page_label (page_cache, 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, page_cache);
97 }
98
99 static gboolean
100 page_scroll_cb (EvPageActionWidget *action_widget, GdkEventScroll *event)
101 {
102         EvPageCache *page_cache = action_widget->page_cache;
103         gint pageno;
104
105         pageno = ev_page_cache_get_current_page (page_cache);
106         if ((event->direction == GDK_SCROLL_DOWN) &&
107             (pageno < ev_page_cache_get_n_pages (page_cache) - 1))
108                 pageno++;
109         if ((event->direction == GDK_SCROLL_UP) && (pageno > 0))
110                 pageno--;
111         ev_page_cache_set_current_page (page_cache, pageno);
112
113         return TRUE;
114 }
115
116 static void
117 activate_cb (EvPageActionWidget *action_widget)
118 {
119         EvPageCache *page_cache;
120         const char *text;
121         gchar *page_label;
122         EvLinkDest *link_dest;
123         EvLinkAction *link_action;
124         EvLink *link;
125         gchar *link_text;
126
127         text = gtk_entry_get_text (GTK_ENTRY (action_widget->entry));
128
129         link_dest = ev_link_dest_new_page_label (text);
130         link_action = ev_link_action_new_dest (link_dest);
131         link_text = g_strdup_printf ("Page: %s", text);
132         link = ev_link_new (link_text, link_action);
133
134         g_signal_emit (action_widget, widget_signals[WIDGET_ACTIVATE_LINK], 0, link);
135
136         g_object_unref (link);
137         g_free (link_text);
138
139         /* rest the entry to the current page if we were unable to
140          * change it */
141         page_cache = action_widget->page_cache;
142         page_label = ev_page_cache_get_page_label (page_cache,
143                                                    ev_page_cache_get_current_page (page_cache));
144         gtk_entry_set_text (GTK_ENTRY (action_widget->entry), page_label);
145         gtk_editable_set_position (GTK_EDITABLE (action_widget->entry), -1);
146         g_free (page_label);
147 }
148
149 static void
150 ev_page_action_widget_init (EvPageActionWidget *action_widget)
151 {
152         GtkWidget *hbox;
153         AtkObject *obj;
154
155         hbox = gtk_hbox_new (FALSE, 0);
156         gtk_box_set_spacing (GTK_BOX (hbox), 6);
157
158         action_widget->entry = gtk_entry_new ();
159         gtk_widget_add_events (action_widget->entry,
160                                GDK_BUTTON_MOTION_MASK);
161         gtk_entry_set_width_chars (GTK_ENTRY (action_widget->entry), 5);
162         g_signal_connect_swapped (action_widget->entry, "scroll-event",
163                                   G_CALLBACK (page_scroll_cb),
164                                   action_widget);
165         g_signal_connect_swapped (action_widget->entry, "activate",
166                                   G_CALLBACK (activate_cb),
167                                   action_widget);
168
169         obj = gtk_widget_get_accessible (action_widget->entry);
170         atk_object_set_name (obj, "page-label-entry");
171
172         gtk_box_pack_start (GTK_BOX (hbox), action_widget->entry,
173                             FALSE, FALSE, 0);
174         gtk_widget_show (action_widget->entry);
175
176         action_widget->label = gtk_label_new (NULL);
177         gtk_box_pack_start (GTK_BOX (hbox), action_widget->label,
178                             FALSE, FALSE, 0);
179         gtk_widget_show (action_widget->label);
180
181         gtk_container_set_border_width (GTK_CONTAINER (action_widget), 6);
182         gtk_container_add (GTK_CONTAINER (action_widget), hbox);
183         gtk_widget_show (hbox);
184
185         gtk_widget_show (GTK_WIDGET (action_widget));
186 }
187
188 void
189 ev_page_action_widget_set_page_cache (EvPageActionWidget *action_widget,
190                                       EvPageCache        *page_cache)
191 {
192         if (action_widget->page_cache != NULL) {
193                 if (action_widget->signal_id > 0) {
194                         g_signal_handler_disconnect (action_widget->page_cache,
195                                                      action_widget->signal_id);
196                         action_widget->signal_id = 0;
197                 }
198                 g_object_remove_weak_pointer (G_OBJECT (action_widget->page_cache),
199                                               (gpointer)&action_widget->page_cache);
200                 action_widget->page_cache = NULL;
201         }
202
203         if (page_cache != NULL) {
204                 action_widget->page_cache = page_cache;
205                 g_object_add_weak_pointer (G_OBJECT (page_cache),
206                                            (gpointer)&action_widget->page_cache);
207                 action_widget->signal_id =
208                         g_signal_connect_object (page_cache, "page-changed",
209                                                  G_CALLBACK (page_changed_cb),
210                                                  action_widget, 0);
211                 page_changed_cb (page_cache,
212                                  ev_page_cache_get_current_page (page_cache),
213                                  action_widget);
214         } else {
215                 action_widget->signal_id = 0;
216                 page_changed_cb (NULL, 0, action_widget);
217         }
218 }
219
220 static void
221 ev_page_action_widget_finalize (GObject *object)
222 {
223         EvPageActionWidget *action_widget = EV_PAGE_ACTION_WIDGET (object);
224
225         if (action_widget->page_cache != NULL) {
226                 if (action_widget->signal_id > 0) {
227                         g_signal_handler_disconnect (action_widget->page_cache,
228                                                      action_widget->signal_id);
229                         action_widget->signal_id = 0;
230                 }
231                 g_object_remove_weak_pointer (G_OBJECT (action_widget->page_cache),
232                                               (gpointer)&action_widget->page_cache);
233                 action_widget->page_cache = NULL;
234         }
235
236         G_OBJECT_CLASS (ev_page_action_widget_parent_class)->finalize (object);
237 }
238
239 static void
240 ev_page_action_widget_class_init (EvPageActionWidgetClass *class)
241 {
242         GObjectClass *object_class = G_OBJECT_CLASS (class);
243
244         object_class->finalize = ev_page_action_widget_finalize;
245
246         widget_signals[WIDGET_ACTIVATE_LINK] =
247                 g_signal_new ("activate_link",
248                               G_OBJECT_CLASS_TYPE (object_class),
249                               G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
250                               G_STRUCT_OFFSET (EvPageActionClass, activate_link),
251                               NULL, NULL,
252                               g_cclosure_marshal_VOID__OBJECT,
253                               G_TYPE_NONE, 1,
254                               G_TYPE_OBJECT);
255
256 }
257
258 static gboolean
259 match_selected_cb (GtkEntryCompletion *completion,
260                    GtkTreeModel       *filter_model,
261                    GtkTreeIter        *filter_iter,
262                    EvPageActionWidget *proxy)
263 {
264         EvLink *link;
265         GtkTreeIter *iter;
266
267         gtk_tree_model_get (filter_model, filter_iter,
268                             0, &iter,
269                             -1);
270         gtk_tree_model_get (proxy->model, iter,
271                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
272                             -1);
273
274         g_signal_emit (proxy, widget_signals[WIDGET_ACTIVATE_LINK], 0, link);
275
276         if (link)
277                 g_object_unref (link);
278
279         gtk_tree_iter_free (iter);
280         
281         return TRUE;
282 }
283                    
284
285 static void
286 display_completion_text (GtkCellLayout      *cell_layout,
287                          GtkCellRenderer    *renderer,
288                          GtkTreeModel       *filter_model,
289                          GtkTreeIter        *filter_iter,
290                          EvPageActionWidget *proxy)
291 {
292         EvLink *link;
293         GtkTreeIter *iter;
294
295         gtk_tree_model_get (filter_model, filter_iter,
296                             0, &iter,
297                             -1);
298         gtk_tree_model_get (proxy->model, iter,
299                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
300                             -1);
301
302         g_object_set (renderer, "text", ev_link_get_title (link), NULL);
303
304         if (link)
305                 g_object_unref (link);
306         
307         gtk_tree_iter_free (iter);
308 }
309
310 static gboolean
311 match_completion (GtkEntryCompletion *completion,
312                   const gchar        *key,
313                   GtkTreeIter        *filter_iter,
314                   EvPageActionWidget *proxy)
315 {
316         EvLink *link;
317         GtkTreeIter *iter;
318         const gchar *text = NULL;
319
320         gtk_tree_model_get (gtk_entry_completion_get_model (completion),
321                             filter_iter,
322                             0, &iter,
323                             -1);
324         gtk_tree_model_get (proxy->model, iter,
325                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
326                             -1);
327
328
329         if (link) {
330                 text = ev_link_get_title (link);
331                 g_object_unref (link);
332         }
333
334         gtk_tree_iter_free (iter);
335
336         if (text && key) {
337                 gchar *normalized_text;
338                 gchar *normalized_key;
339                 gchar *case_normalized_text;
340                 gchar *case_normalized_key;
341                 gboolean retval = FALSE;
342
343                 normalized_text = g_utf8_normalize (text, -1, G_NORMALIZE_ALL);
344                 normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL);
345                 case_normalized_text = g_utf8_casefold (normalized_text, -1);
346                 case_normalized_key = g_utf8_casefold (normalized_key, -1);
347
348                 if (strstr (case_normalized_text, case_normalized_key))
349                         retval = TRUE;
350
351                 g_free (normalized_text);
352                 g_free (normalized_key);
353                 g_free (case_normalized_text);
354                 g_free (case_normalized_key);
355
356                 return retval;
357         }
358
359         return FALSE;
360 }
361
362 /* user data to set on the widget. */
363 #define EPA_FILTER_MODEL_DATA "epa-filter-model"
364
365 static gboolean
366 build_new_tree_cb (GtkTreeModel *model,
367                    GtkTreePath  *path,
368                    GtkTreeIter  *iter,
369                    gpointer      data)
370 {
371         GtkTreeModel *filter_model = GTK_TREE_MODEL (data);
372         EvLink *link;
373         EvLinkAction *action;
374         EvLinkActionType type;
375
376         gtk_tree_model_get (model, iter,
377                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
378                             -1);
379
380         if (!link)
381                 return FALSE;
382
383         action = ev_link_get_action (link);
384         if (!action) {
385                 g_object_unref (link);
386                 return FALSE;
387         }
388         
389         type = ev_link_action_get_action_type (action);
390
391         if (type == EV_LINK_ACTION_TYPE_GOTO_DEST) {
392                 GtkTreeIter filter_iter;
393
394                 gtk_list_store_append (GTK_LIST_STORE (filter_model), &filter_iter);
395                 gtk_list_store_set (GTK_LIST_STORE (filter_model), &filter_iter,
396                                     0, iter,
397                                     -1);
398         }
399         
400         g_object_unref (link);
401         
402         return FALSE;
403 }
404
405 static GtkTreeModel *
406 get_filter_model_from_model (GtkTreeModel *model)
407 {
408         GtkTreeModel *filter_model;
409
410         filter_model =
411                 (GtkTreeModel *) g_object_get_data (G_OBJECT (model), EPA_FILTER_MODEL_DATA);
412         if (filter_model == NULL) {
413                 filter_model = (GtkTreeModel *) gtk_list_store_new (1, GTK_TYPE_TREE_ITER);
414
415                 gtk_tree_model_foreach (model,
416                                         build_new_tree_cb,
417                                         filter_model);
418                 g_object_set_data_full (G_OBJECT (model), EPA_FILTER_MODEL_DATA, filter_model, g_object_unref);
419         }
420
421         return filter_model;
422 }
423
424
425 void
426 ev_page_action_widget_update_model (EvPageActionWidget *proxy, GtkTreeModel *model)
427 {
428         GtkTreeModel *filter_model;
429         GtkEntryCompletion *completion;
430         GtkCellRenderer *renderer;
431
432         if (!model)
433                 return;
434
435         /* Magik */
436         proxy->model = model;
437         filter_model = get_filter_model_from_model (model);
438
439         completion = gtk_entry_completion_new ();
440         g_object_set (G_OBJECT (completion),
441                       "popup-set-width", FALSE,
442                       "model", filter_model,
443                       NULL);
444
445         g_signal_connect (completion, "match-selected", G_CALLBACK (match_selected_cb), proxy);
446         gtk_entry_completion_set_match_func (completion,
447                                              (GtkEntryCompletionMatchFunc) match_completion,
448                                              proxy, NULL);
449
450         /* Set up the layout */
451         renderer = (GtkCellRenderer *)
452                 g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
453                               "ellipsize", PANGO_ELLIPSIZE_END,
454                               "width_chars", 30,
455                               NULL);
456         gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), renderer, TRUE);
457         gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (completion),
458                                             renderer,
459                                             (GtkCellLayoutDataFunc) display_completion_text,
460                                             proxy, NULL);
461         gtk_entry_set_completion (GTK_ENTRY (proxy->entry), completion);
462
463         g_object_unref (completion);
464         g_object_unref (model);
465 }
466
467 void
468 ev_page_action_widget_grab_focus (EvPageActionWidget *proxy)
469 {
470         gtk_widget_grab_focus (proxy->entry);
471 }
472