]> www.fi.muni.cz Git - evince.git/blob - shell/ev-page-action.c
2eed3763f13b01b731f08613ad9f1a32156d5c1a
[evince.git] / shell / ev-page-action.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  *  $Id$
20  */
21
22 #include "config.h"
23
24 #include "ev-page-action.h"
25 #include "ev-window.h"
26 #include "ev-document-links.h"
27
28 #include <glib/gi18n.h>
29 #include <gtk/gtkentry.h>
30 #include <gtk/gtktoolitem.h>
31 #include <gtk/gtklabel.h>
32 #include <gtk/gtkhbox.h>
33 #include <string.h>
34
35 typedef struct _EvPageActionWidget EvPageActionWidget;
36 typedef struct _EvPageActionWidgetClass EvPageActionWidgetClass;
37 struct _EvPageActionWidget
38 {
39         GtkToolItem parent;
40
41         GtkWidget *entry;
42         GtkWidget *label;
43         EvPageCache *page_cache;
44         guint signal_id;
45         GtkTreeModel *filter_model;
46         GtkTreeModel *model;
47 };
48
49 struct _EvPageActionWidgetClass
50 {
51         GtkToolItemClass parent_class;
52 };
53
54 struct _EvPageActionPrivate
55 {
56         EvPageCache *page_cache;
57         GtkTreeModel *model;
58 };
59
60
61 /* Widget we pass back */
62 static GType ev_page_action_widget_get_type   (void);
63 static void  ev_page_action_widget_init       (EvPageActionWidget      *action_widget);
64 static void  ev_page_action_widget_class_init (EvPageActionWidgetClass *action_widget);
65
66 #define EV_TYPE_PAGE_ACTION_WIDGET (ev_page_action_widget_get_type ())
67 #define EV_PAGE_ACTION_WIDGET(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EV_TYPE_PAGE_ACTION_WIDGET, EvPageActionWidget))
68
69 G_DEFINE_TYPE (EvPageActionWidget, ev_page_action_widget, GTK_TYPE_TOOL_ITEM)
70
71 static void
72 ev_page_action_widget_init (EvPageActionWidget *action_widget)
73 {
74
75 }
76
77 static void
78 ev_page_action_widget_set_page_cache (EvPageActionWidget *action_widget,
79                                       EvPageCache        *page_cache)
80 {
81         if (action_widget->page_cache != NULL) {
82                 g_object_remove_weak_pointer (G_OBJECT (action_widget->page_cache),
83                                               (gpointer *)&action_widget->page_cache);
84                 action_widget->page_cache = NULL;
85         }
86
87         if (page_cache != NULL) {
88                 action_widget->page_cache = page_cache;
89                 g_object_add_weak_pointer (G_OBJECT (page_cache),
90                                            (gpointer *)&action_widget->page_cache);
91         }
92 }
93
94 static void
95 ev_page_action_widget_finalize (GObject *object)
96 {
97         EvPageActionWidget *action_widget = EV_PAGE_ACTION_WIDGET (object);
98
99         ev_page_action_widget_set_page_cache (action_widget, NULL);
100 }
101
102 static void
103 ev_page_action_widget_class_init (EvPageActionWidgetClass *class)
104 {
105         GObjectClass *object_class = G_OBJECT_CLASS (class);
106
107         object_class->finalize = ev_page_action_widget_finalize;
108 }
109
110 static void ev_page_action_init       (EvPageAction *action);
111 static void ev_page_action_class_init (EvPageActionClass *class);
112
113 G_DEFINE_TYPE (EvPageAction, ev_page_action, GTK_TYPE_ACTION)
114
115 #define EV_PAGE_ACTION_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EV_TYPE_PAGE_ACTION, EvPageActionPrivate))
116
117 enum {
118         PROP_0,
119         PROP_PAGE_CACHE,
120         PROP_MODEL,
121 };
122
123 /* user data to set on the widget. */
124 #define EPA_FILTER_MODEL_DATA "epa-filter-model"
125
126 static void
127 update_pages_label (EvPageActionWidget *proxy,
128                     gint                page,
129                     EvPageCache        *page_cache)
130 {
131         char *label_text;
132         gint n_pages;
133
134         n_pages = page_cache ? ev_page_cache_get_n_pages (page_cache) : 0;
135         if (page_cache && ev_page_cache_has_nonnumeric_page_labels (page_cache)) {
136                 label_text = g_strdup_printf (_("(%d of %d)"), page + 1, n_pages);
137         } else {
138                 label_text = g_strdup_printf (_("of %d"), n_pages);
139         }
140         gtk_label_set_text (GTK_LABEL (proxy->label), label_text);
141         g_free (label_text);
142 }
143
144 static void
145 page_changed_cb (EvPageCache        *page_cache,
146                  gint                page,
147                  EvPageActionWidget *proxy)
148 {
149         g_assert (proxy);
150         
151         if (page_cache != NULL && page >= 0) {
152                 gchar *page_label = ev_page_cache_get_page_label (page_cache, page);
153                 gtk_entry_set_text (GTK_ENTRY (proxy->entry), page_label);
154                 gtk_editable_set_position (GTK_EDITABLE (proxy->entry), -1);
155                 g_free (page_label);
156         } else {
157                 gtk_entry_set_text (GTK_ENTRY (proxy->entry), "");
158         }
159
160         update_pages_label (proxy, page, page_cache);
161 }
162
163 static void
164 activate_cb (GtkWidget *entry, GtkAction *action)
165 {
166         EvPageAction *page = EV_PAGE_ACTION (action);
167         EvPageCache *page_cache;
168         const char *text;
169
170         text = gtk_entry_get_text (GTK_ENTRY (entry));
171         page_cache = page->priv->page_cache;
172
173         if (! ev_page_cache_set_page_label (page_cache, text)) {
174                 /* rest the entry to the current page if we were unable to
175                  * change it */
176                 gchar *page_label =
177                         ev_page_cache_get_page_label (page_cache,
178                                                       ev_page_cache_get_current_page (page_cache));
179                 gtk_entry_set_text (GTK_ENTRY (entry), page_label);
180                 gtk_editable_set_position (GTK_EDITABLE (entry), -1);
181                 g_free (page_label);
182         }
183 }
184
185 static GtkWidget *
186 create_tool_item (GtkAction *action)
187 {
188         EvPageActionWidget *proxy;
189         GtkWidget *hbox;
190
191         proxy = g_object_new (ev_page_action_widget_get_type (), NULL);
192         gtk_container_set_border_width (GTK_CONTAINER (proxy), 6); 
193         gtk_widget_show (GTK_WIDGET (proxy));
194
195         hbox = gtk_hbox_new (FALSE, 0);
196         gtk_box_set_spacing (GTK_BOX (hbox), 6);
197
198         proxy->entry = gtk_entry_new ();
199         gtk_entry_set_width_chars (GTK_ENTRY (proxy->entry), 5);
200         gtk_box_pack_start (GTK_BOX (hbox), proxy->entry, FALSE, FALSE, 0);
201         gtk_widget_show (proxy->entry);
202         g_signal_connect (proxy->entry, "activate",
203                           G_CALLBACK (activate_cb),
204                           action);
205
206         proxy->label = gtk_label_new (NULL);
207         gtk_box_pack_start (GTK_BOX (hbox), proxy->label, FALSE, FALSE, 0);
208         gtk_widget_show (proxy->label);
209
210         gtk_container_add (GTK_CONTAINER (proxy), hbox);
211         gtk_widget_show (hbox);
212
213         return GTK_WIDGET (proxy);
214 }
215
216 static void
217 update_page_cache (EvPageAction *page, GParamSpec *pspec, EvPageActionWidget *proxy)
218 {
219         EvPageCache *page_cache;
220         guint signal_id;
221
222         page_cache = page->priv->page_cache;
223
224         /* clear the old signal */
225         if (proxy->signal_id > 0 && proxy->page_cache)
226                 g_signal_handler_disconnect (proxy->page_cache, proxy->signal_id);
227         
228         if (page_cache != NULL) {
229                 signal_id = g_signal_connect_object (page_cache,
230                                                      "page-changed",
231                                                      G_CALLBACK (page_changed_cb),
232                                                      proxy, 0);
233                 /* Set the initial value */
234                 page_changed_cb (page_cache,
235                                  ev_page_cache_get_current_page (page_cache),
236                                  proxy);
237         } else {
238                 /* Or clear the entry */
239                 signal_id = 0;
240                 page_changed_cb (NULL, 0, proxy);
241         }
242         ev_page_action_widget_set_page_cache (proxy, page_cache);
243         proxy->signal_id = signal_id;
244 }
245
246 static gboolean
247 build_new_tree_cb (GtkTreeModel *model,
248                    GtkTreePath  *path,
249                    GtkTreeIter  *iter,
250                    gpointer      data)
251 {
252         GtkTreeModel *filter_model = GTK_TREE_MODEL (data);
253         EvLink *link;
254
255         gtk_tree_model_get (model, iter,
256                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
257                             -1);
258
259         if (link && ev_link_get_link_type (link) == EV_LINK_TYPE_PAGE) {
260                 GtkTreeIter filter_iter;
261
262                 gtk_list_store_append (GTK_LIST_STORE (filter_model), &filter_iter);
263                 gtk_list_store_set (GTK_LIST_STORE (filter_model), &filter_iter,
264                                     0, iter,
265                                     -1);
266         }
267         
268         return FALSE;
269 }
270
271 static GtkTreeModel *
272 get_filter_model_from_model (GtkTreeModel *model)
273 {
274         GtkTreeModel *filter_model;
275
276         filter_model =
277                 (GtkTreeModel *) g_object_get_data (G_OBJECT (model), EPA_FILTER_MODEL_DATA);
278         if (filter_model == NULL) {
279                 filter_model = (GtkTreeModel *) gtk_list_store_new (1, GTK_TYPE_TREE_ITER);
280
281                 gtk_tree_model_foreach (model,
282                                         build_new_tree_cb,
283                                         filter_model);
284                 g_object_set_data_full (G_OBJECT (model), EPA_FILTER_MODEL_DATA, filter_model, g_object_unref);
285         }
286
287         return filter_model;
288 }
289
290 static gboolean
291 match_selected_cb (GtkEntryCompletion *completion,
292                    GtkTreeModel       *filter_model,
293                    GtkTreeIter        *filter_iter,
294                    EvPageActionWidget *proxy)
295 {
296         EvLink *link;
297         GtkTreeIter *iter;
298
299         gtk_tree_model_get (filter_model, filter_iter,
300                             0, &iter,
301                             -1);
302         gtk_tree_model_get (proxy->model, iter,
303                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
304                             -1);
305         ev_page_cache_set_link (proxy->page_cache, link);
306         
307         return TRUE;
308 }
309                    
310
311 static void
312 display_completion_text (GtkCellLayout      *cell_layout,
313                          GtkCellRenderer    *renderer,
314                          GtkTreeModel       *filter_model,
315                          GtkTreeIter        *filter_iter,
316                          EvPageActionWidget *proxy)
317 {
318         EvLink *link;
319         GtkTreeIter *iter;
320
321         gtk_tree_model_get (filter_model, 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         g_object_set (renderer, "text", ev_link_get_title (link), NULL);
329 }
330
331 static gboolean
332 match_completion (GtkEntryCompletion *completion,
333                   const gchar        *key,
334                   GtkTreeIter        *filter_iter,
335                   EvPageActionWidget *proxy)
336 {
337         EvLink *link;
338         GtkTreeIter *iter;
339         const gchar *text = NULL;
340
341         gtk_tree_model_get (gtk_entry_completion_get_model (completion),
342                             filter_iter,
343                             0, &iter,
344                             -1);
345         gtk_tree_model_get (proxy->model, iter,
346                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
347                             -1);
348
349
350         if (link)
351                 text = ev_link_get_title (link);
352
353         if (text && key ) {
354                 gchar *normalized_text;
355                 gchar *normalized_key;
356                 gchar *case_normalized_text;
357                 gchar *case_normalized_key;
358                 gboolean retval = FALSE;
359
360                 normalized_text = g_utf8_normalize (text, -1, G_NORMALIZE_ALL);
361                 normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL);
362                 case_normalized_text = g_utf8_casefold (normalized_text, -1);
363                 case_normalized_key = g_utf8_casefold (normalized_key, -1);
364
365                 if (strstr (case_normalized_text, case_normalized_key))
366                         retval = TRUE;
367
368                 g_free (normalized_text);
369                 g_free (normalized_key);
370                 g_free (case_normalized_text);
371                 g_free (case_normalized_key);
372
373                 return retval;
374         }
375
376         return FALSE;
377 }
378
379
380 static void
381 update_model (EvPageAction *page, GParamSpec *pspec, EvPageActionWidget *proxy)
382 {
383         GtkTreeModel *model;
384         GtkTreeModel *filter_model;
385
386         g_object_get (G_OBJECT (page),
387                       "model", &model,
388                       NULL);
389         if (model != NULL) {
390                 /* Magik */
391                 GtkEntryCompletion *completion;
392                 GtkCellRenderer *renderer;
393
394                 proxy->model = model;
395                 filter_model = get_filter_model_from_model (model);
396
397                 completion = gtk_entry_completion_new ();
398
399                 /* popup-set-width is 2.7.0 only */
400                 g_object_set (G_OBJECT (completion),
401                               "popup-set-width", FALSE,
402                               "model", filter_model,
403                               NULL);
404
405                 g_signal_connect (completion, "match-selected", G_CALLBACK (match_selected_cb), proxy);
406                 gtk_entry_completion_set_match_func (completion,
407                                                      (GtkEntryCompletionMatchFunc) match_completion,
408                                                      proxy, NULL);
409
410                 /* Set up the layout */
411                 renderer = (GtkCellRenderer *)
412                         g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
413                                       "ellipsize", PANGO_ELLIPSIZE_END,
414                                       "width_chars", 30,
415                                       NULL);
416                 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), renderer, TRUE);
417                 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (completion),
418                                                     renderer,
419                                                     (GtkCellLayoutDataFunc) display_completion_text,
420                                                     proxy, NULL);
421                 gtk_entry_set_completion (GTK_ENTRY (proxy->entry), completion);
422         }
423 }
424
425 static void
426 connect_proxy (GtkAction *action, GtkWidget *proxy)
427 {
428         if (GTK_IS_TOOL_ITEM (proxy)) {
429                 g_signal_connect_object (action, "notify::page-cache",
430                                          G_CALLBACK (update_page_cache),
431                                          proxy, 0);
432                 update_page_cache (EV_PAGE_ACTION (action), NULL,
433                                    EV_PAGE_ACTION_WIDGET (proxy));
434                 /* We only go through this whole rigmarole if we can set
435                  * GtkEntryCompletion::popup-set-width, which appeared in
436                  * GTK+-2.7.0 */
437                 if (gtk_check_version (2, 7, 0) == NULL) {
438                         g_signal_connect_object (action, "notify::model",
439                                                  G_CALLBACK (update_model),
440                                                  proxy, 0);
441                 }
442         }
443
444         GTK_ACTION_CLASS (ev_page_action_parent_class)->connect_proxy (action, proxy);
445 }
446
447 static void
448 ev_page_action_dispose (GObject *object)
449 {
450         EvPageAction *page = EV_PAGE_ACTION (object);
451
452         if (page->priv->page_cache) {
453                 g_object_unref (page->priv->page_cache);
454                 page->priv->page_cache = NULL;
455         }
456
457         G_OBJECT_CLASS (ev_page_action_parent_class)->dispose (object);
458 }
459
460 static void
461 ev_page_action_set_property (GObject      *object,
462                              guint         prop_id,
463                              const GValue *value,
464                              GParamSpec   *pspec)
465 {
466         EvPageAction *page;
467         EvPageCache *page_cache;
468         GtkTreeModel *model;
469   
470         page = EV_PAGE_ACTION (object);
471
472         switch (prop_id)
473         {
474         case PROP_PAGE_CACHE:
475                 page_cache = page->priv->page_cache;
476                 page->priv->page_cache = EV_PAGE_CACHE (g_value_dup_object (value));
477                 if (page_cache)
478                         g_object_unref (page_cache);
479                 break;
480         case PROP_MODEL:
481                 model = page->priv->model;
482                 page->priv->model = GTK_TREE_MODEL (g_value_dup_object (value));
483                 if (model)
484                         g_object_unref (model);
485                 break;
486         default:
487                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
488                 break;
489         }
490 }
491
492 static void
493 ev_page_action_get_property (GObject    *object,
494                              guint       prop_id,
495                              GValue     *value,
496                              GParamSpec *pspec)
497 {
498         EvPageAction *page;
499   
500         page = EV_PAGE_ACTION (object);
501
502         switch (prop_id)
503         {
504         case PROP_PAGE_CACHE:
505                 g_value_set_object (value, page->priv->page_cache);
506                 break;
507         case PROP_MODEL:
508                 g_value_set_object (value, page->priv->model);
509                 break;
510         default:
511                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
512                 break;
513         }
514 }
515
516 void
517 ev_page_action_set_document (EvPageAction *page, EvDocument *document)
518 {
519         EvPageCache *page_cache = NULL;
520
521         if (document)
522                 page_cache = ev_document_get_page_cache (document);
523         
524         g_object_set (page,
525                       "page-cache", page_cache,
526                       "model", NULL,
527                       NULL);
528 }
529
530 void
531 ev_page_action_set_model (EvPageAction *page_action,
532                           GtkTreeModel *model)
533 {
534         g_object_set (page_action,
535                       "model", model,
536                       NULL);
537 }
538
539 void
540 ev_page_action_grab_focus (EvPageAction *page_action)
541 {
542         GSList *proxies;
543
544         proxies = gtk_action_get_proxies (GTK_ACTION (page_action));
545         for (; proxies != NULL; proxies = proxies->next) {
546                 EvPageActionWidget *proxy;
547
548                 proxy = EV_PAGE_ACTION_WIDGET (proxies->data);
549                 gtk_widget_grab_focus (proxy->entry);
550         }
551 }
552
553 static void
554 ev_page_action_init (EvPageAction *page)
555 {
556         page->priv = EV_PAGE_ACTION_GET_PRIVATE (page);
557 }
558
559 static void
560 ev_page_action_class_init (EvPageActionClass *class)
561 {
562         GObjectClass *object_class = G_OBJECT_CLASS (class);
563         GtkActionClass *action_class = GTK_ACTION_CLASS (class);
564
565         object_class->dispose = ev_page_action_dispose;
566         object_class->set_property = ev_page_action_set_property;
567         object_class->get_property = ev_page_action_get_property;
568
569         action_class->toolbar_item_type = GTK_TYPE_TOOL_ITEM;
570         action_class->create_tool_item = create_tool_item;
571         action_class->connect_proxy = connect_proxy;
572
573         g_object_class_install_property (object_class,
574                                          PROP_PAGE_CACHE,
575                                          g_param_spec_object ("page-cache",
576                                                               "Page Cache",
577                                                               "Current page cache",
578                                                               EV_TYPE_PAGE_CACHE,
579                                                               G_PARAM_READWRITE));
580
581         g_object_class_install_property (object_class,
582                                          PROP_MODEL,
583                                          g_param_spec_object ("model",
584                                                               "Model",
585                                                               "Current Model",
586                                                               GTK_TYPE_TREE_MODEL,
587                                                               G_PARAM_READWRITE));
588
589         g_type_class_add_private (object_class, sizeof (EvPageActionPrivate));
590 }