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