]> www.fi.muni.cz Git - evince.git/blob - shell/ev-page-action.c
A bit different fix for rounding problem but it has no
[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         
184                 gtk_entry_set_width_chars (GTK_ENTRY (proxy->entry), 
185                                            CLAMP (ev_page_cache_get_max_label_chars (page_cache), 
186                                            4, 12));     
187                 
188                 gchar *page_label = ev_page_cache_get_page_label (page_cache, page);
189                 gtk_entry_set_text (GTK_ENTRY (proxy->entry), page_label);
190                 gtk_editable_set_position (GTK_EDITABLE (proxy->entry), -1);
191                 g_free (page_label);
192                 
193         } else {
194                 gtk_entry_set_text (GTK_ENTRY (proxy->entry), "");
195         }
196
197         update_pages_label (proxy, page, page_cache);
198 }
199
200 static void
201 activate_cb (GtkWidget *entry, GtkAction *action)
202 {
203         EvPageAction *page = EV_PAGE_ACTION (action);
204         EvPageCache *page_cache;
205         const char *text;
206         gboolean changed;
207
208         text = gtk_entry_get_text (GTK_ENTRY (entry));
209         page_cache = page->priv->page_cache;
210
211         g_signal_emit (action, signals[ACTIVATE_LABEL], 0, text, &changed);
212
213         if (!changed) {
214                 /* rest the entry to the current page if we were unable to
215                  * change it */
216                 gchar *page_label =
217                         ev_page_cache_get_page_label (page_cache,
218                                                       ev_page_cache_get_current_page (page_cache));
219                 gtk_entry_set_text (GTK_ENTRY (entry), page_label);
220                 gtk_editable_set_position (GTK_EDITABLE (entry), -1);
221                 g_free (page_label);
222         }
223 }
224
225 static GtkWidget *
226 create_tool_item (GtkAction *action)
227 {
228         EvPageActionWidget *proxy;
229         GtkWidget *hbox;
230
231         proxy = g_object_new (ev_page_action_widget_get_type (), NULL);
232         gtk_container_set_border_width (GTK_CONTAINER (proxy), 6); 
233         gtk_widget_show (GTK_WIDGET (proxy));
234
235         hbox = gtk_hbox_new (FALSE, 0);
236         gtk_box_set_spacing (GTK_BOX (hbox), 6);
237
238         proxy->entry = gtk_entry_new ();
239         gtk_entry_set_width_chars (GTK_ENTRY (proxy->entry), 5);
240         gtk_box_pack_start (GTK_BOX (hbox), proxy->entry, FALSE, FALSE, 0);
241         gtk_widget_show (proxy->entry);
242         g_signal_connect (proxy->entry, "activate",
243                           G_CALLBACK (activate_cb),
244                           action);
245
246         proxy->label = gtk_label_new (NULL);
247         gtk_box_pack_start (GTK_BOX (hbox), proxy->label, FALSE, FALSE, 0);
248         gtk_widget_show (proxy->label);
249
250         gtk_container_add (GTK_CONTAINER (proxy), hbox);
251         gtk_widget_show (hbox);
252
253         return GTK_WIDGET (proxy);
254 }
255
256 static void
257 update_page_cache (EvPageAction *page, GParamSpec *pspec, EvPageActionWidget *proxy)
258 {
259         EvPageCache *page_cache;
260         guint signal_id;
261
262         page_cache = page->priv->page_cache;
263
264         /* clear the old signal */
265         if (proxy->signal_id > 0 && proxy->page_cache)
266                 g_signal_handler_disconnect (proxy->page_cache, proxy->signal_id);
267         
268         if (page_cache != NULL) {
269                 signal_id = g_signal_connect_object (page_cache,
270                                                      "page-changed",
271                                                      G_CALLBACK (page_changed_cb),
272                                                      proxy, 0);
273                 /* Set the initial value */
274                 page_changed_cb (page_cache,
275                                  ev_page_cache_get_current_page (page_cache),
276                                  proxy);
277         } else {
278                 /* Or clear the entry */
279                 signal_id = 0;
280                 page_changed_cb (NULL, 0, proxy);
281         }
282         ev_page_action_widget_set_page_cache (proxy, page_cache);
283         proxy->signal_id = signal_id;
284 }
285
286 static gboolean
287 build_new_tree_cb (GtkTreeModel *model,
288                    GtkTreePath  *path,
289                    GtkTreeIter  *iter,
290                    gpointer      data)
291 {
292         GtkTreeModel *filter_model = GTK_TREE_MODEL (data);
293         EvLink *link;
294
295         gtk_tree_model_get (model, iter,
296                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
297                             -1);
298
299         if (link && ev_link_get_link_type (link) == EV_LINK_TYPE_PAGE) {
300                 GtkTreeIter filter_iter;
301
302                 gtk_list_store_append (GTK_LIST_STORE (filter_model), &filter_iter);
303                 gtk_list_store_set (GTK_LIST_STORE (filter_model), &filter_iter,
304                                     0, iter,
305                                     -1);
306         }
307         
308         return FALSE;
309 }
310
311 static GtkTreeModel *
312 get_filter_model_from_model (GtkTreeModel *model)
313 {
314         GtkTreeModel *filter_model;
315
316         filter_model =
317                 (GtkTreeModel *) g_object_get_data (G_OBJECT (model), EPA_FILTER_MODEL_DATA);
318         if (filter_model == NULL) {
319                 filter_model = (GtkTreeModel *) gtk_list_store_new (1, GTK_TYPE_TREE_ITER);
320
321                 gtk_tree_model_foreach (model,
322                                         build_new_tree_cb,
323                                         filter_model);
324                 g_object_set_data_full (G_OBJECT (model), EPA_FILTER_MODEL_DATA, filter_model, g_object_unref);
325         }
326
327         return filter_model;
328 }
329
330 static gboolean
331 match_selected_cb (GtkEntryCompletion *completion,
332                    GtkTreeModel       *filter_model,
333                    GtkTreeIter        *filter_iter,
334                    EvPageActionWidget *proxy)
335 {
336         EvLink *link;
337         GtkTreeIter *iter;
338
339         gtk_tree_model_get (filter_model, filter_iter,
340                             0, &iter,
341                             -1);
342         gtk_tree_model_get (proxy->model, iter,
343                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
344                             -1);
345
346         g_signal_emit (proxy, signals[ACTIVATE_LINK], 0, link);
347         
348         return TRUE;
349 }
350                    
351
352 static void
353 display_completion_text (GtkCellLayout      *cell_layout,
354                          GtkCellRenderer    *renderer,
355                          GtkTreeModel       *filter_model,
356                          GtkTreeIter        *filter_iter,
357                          EvPageActionWidget *proxy)
358 {
359         EvLink *link;
360         GtkTreeIter *iter;
361
362         gtk_tree_model_get (filter_model, filter_iter,
363                             0, &iter,
364                             -1);
365         gtk_tree_model_get (proxy->model, iter,
366                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
367                             -1);
368
369         g_object_set (renderer, "text", ev_link_get_title (link), NULL);
370 }
371
372 static gboolean
373 match_completion (GtkEntryCompletion *completion,
374                   const gchar        *key,
375                   GtkTreeIter        *filter_iter,
376                   EvPageActionWidget *proxy)
377 {
378         EvLink *link;
379         GtkTreeIter *iter;
380         const gchar *text = NULL;
381
382         gtk_tree_model_get (gtk_entry_completion_get_model (completion),
383                             filter_iter,
384                             0, &iter,
385                             -1);
386         gtk_tree_model_get (proxy->model, iter,
387                             EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
388                             -1);
389
390
391         if (link)
392                 text = ev_link_get_title (link);
393
394         if (text && key ) {
395                 gchar *normalized_text;
396                 gchar *normalized_key;
397                 gchar *case_normalized_text;
398                 gchar *case_normalized_key;
399                 gboolean retval = FALSE;
400
401                 normalized_text = g_utf8_normalize (text, -1, G_NORMALIZE_ALL);
402                 normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL);
403                 case_normalized_text = g_utf8_casefold (normalized_text, -1);
404                 case_normalized_key = g_utf8_casefold (normalized_key, -1);
405
406                 if (strstr (case_normalized_text, case_normalized_key))
407                         retval = TRUE;
408
409                 g_free (normalized_text);
410                 g_free (normalized_key);
411                 g_free (case_normalized_text);
412                 g_free (case_normalized_key);
413
414                 return retval;
415         }
416
417         return FALSE;
418 }
419
420
421 static void
422 update_model (EvPageAction *page, GParamSpec *pspec, EvPageActionWidget *proxy)
423 {
424         GtkTreeModel *model;
425         GtkTreeModel *filter_model;
426
427         g_object_get (G_OBJECT (page),
428                       "model", &model,
429                       NULL);
430         if (model != NULL) {
431                 /* Magik */
432                 GtkEntryCompletion *completion;
433                 GtkCellRenderer *renderer;
434
435                 proxy->model = model;
436                 filter_model = get_filter_model_from_model (model);
437
438                 completion = gtk_entry_completion_new ();
439
440                 /* popup-set-width is 2.7.0 only */
441                 g_object_set (G_OBJECT (completion),
442                               "popup-set-width", FALSE,
443                               "model", filter_model,
444                               NULL);
445
446                 g_signal_connect (completion, "match-selected", G_CALLBACK (match_selected_cb), proxy);
447                 gtk_entry_completion_set_match_func (completion,
448                                                      (GtkEntryCompletionMatchFunc) match_completion,
449                                                      proxy, NULL);
450
451                 /* Set up the layout */
452                 renderer = (GtkCellRenderer *)
453                         g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
454                                       "ellipsize", PANGO_ELLIPSIZE_END,
455                                       "width_chars", 30,
456                                       NULL);
457                 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), renderer, TRUE);
458                 gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (completion),
459                                                     renderer,
460                                                     (GtkCellLayoutDataFunc) display_completion_text,
461                                                     proxy, NULL);
462                 gtk_entry_set_completion (GTK_ENTRY (proxy->entry), completion);
463         }
464 }
465
466 static void
467 activate_link_cb (EvPageActionWidget *proxy, EvLink *link, EvPageAction *action)
468 {
469         g_signal_emit (action, signals[ACTIVATE_LINK], 0, link);
470 }
471
472 static void
473 connect_proxy (GtkAction *action, GtkWidget *proxy)
474 {
475         if (GTK_IS_TOOL_ITEM (proxy)) {
476                 g_signal_connect_object (action, "notify::page-cache",
477                                          G_CALLBACK (update_page_cache),
478                                          proxy, 0);
479                 g_signal_connect (proxy, "activate_link",
480                                   G_CALLBACK (activate_link_cb),
481                                   action);
482                 update_page_cache (EV_PAGE_ACTION (action), NULL,
483                                    EV_PAGE_ACTION_WIDGET (proxy));
484                 /* We only go through this whole rigmarole if we can set
485                  * GtkEntryCompletion::popup-set-width, which appeared in
486                  * GTK+-2.7.0 */
487                 if (gtk_check_version (2, 7, 0) == NULL) {
488                         g_signal_connect_object (action, "notify::model",
489                                                  G_CALLBACK (update_model),
490                                                  proxy, 0);
491                 }
492         }
493
494         GTK_ACTION_CLASS (ev_page_action_parent_class)->connect_proxy (action, proxy);
495 }
496
497 static void
498 ev_page_action_dispose (GObject *object)
499 {
500         EvPageAction *page = EV_PAGE_ACTION (object);
501
502         if (page->priv->page_cache) {
503                 g_object_unref (page->priv->page_cache);
504                 page->priv->page_cache = NULL;
505         }
506
507         G_OBJECT_CLASS (ev_page_action_parent_class)->dispose (object);
508 }
509
510 static void
511 ev_page_action_set_property (GObject      *object,
512                              guint         prop_id,
513                              const GValue *value,
514                              GParamSpec   *pspec)
515 {
516         EvPageAction *page;
517         EvPageCache *page_cache;
518         GtkTreeModel *model;
519   
520         page = EV_PAGE_ACTION (object);
521
522         switch (prop_id)
523         {
524         case PROP_PAGE_CACHE:
525                 page_cache = page->priv->page_cache;
526                 page->priv->page_cache = EV_PAGE_CACHE (g_value_dup_object (value));
527                 if (page_cache)
528                         g_object_unref (page_cache);
529                 break;
530         case PROP_MODEL:
531                 model = page->priv->model;
532                 page->priv->model = GTK_TREE_MODEL (g_value_dup_object (value));
533                 if (model)
534                         g_object_unref (model);
535                 break;
536         default:
537                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
538                 break;
539         }
540 }
541
542 static void
543 ev_page_action_get_property (GObject    *object,
544                              guint       prop_id,
545                              GValue     *value,
546                              GParamSpec *pspec)
547 {
548         EvPageAction *page;
549   
550         page = EV_PAGE_ACTION (object);
551
552         switch (prop_id)
553         {
554         case PROP_PAGE_CACHE:
555                 g_value_set_object (value, page->priv->page_cache);
556                 break;
557         case PROP_MODEL:
558                 g_value_set_object (value, page->priv->model);
559                 break;
560         default:
561                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
562                 break;
563         }
564 }
565
566 void
567 ev_page_action_set_document (EvPageAction *page, EvDocument *document)
568 {
569         EvPageCache *page_cache = NULL;
570
571         if (document)
572                 page_cache = ev_document_get_page_cache (document);
573         
574         g_object_set (page,
575                       "page-cache", page_cache,
576                       "model", NULL,
577                       NULL);
578 }
579
580 void
581 ev_page_action_set_model (EvPageAction *page_action,
582                           GtkTreeModel *model)
583 {
584         g_object_set (page_action,
585                       "model", model,
586                       NULL);
587 }
588
589 void
590 ev_page_action_grab_focus (EvPageAction *page_action)
591 {
592         GSList *proxies;
593
594         proxies = gtk_action_get_proxies (GTK_ACTION (page_action));
595         for (; proxies != NULL; proxies = proxies->next) {
596                 EvPageActionWidget *proxy;
597
598                 proxy = EV_PAGE_ACTION_WIDGET (proxies->data);
599                 gtk_widget_grab_focus (proxy->entry);
600         }
601 }
602
603 static void
604 ev_page_action_init (EvPageAction *page)
605 {
606         page->priv = EV_PAGE_ACTION_GET_PRIVATE (page);
607 }
608
609 static void
610 ev_page_action_class_init (EvPageActionClass *class)
611 {
612         GObjectClass *object_class = G_OBJECT_CLASS (class);
613         GtkActionClass *action_class = GTK_ACTION_CLASS (class);
614
615         object_class->dispose = ev_page_action_dispose;
616         object_class->set_property = ev_page_action_set_property;
617         object_class->get_property = ev_page_action_get_property;
618
619         action_class->toolbar_item_type = GTK_TYPE_TOOL_ITEM;
620         action_class->create_tool_item = create_tool_item;
621         action_class->connect_proxy = connect_proxy;
622
623         signals[ACTIVATE_LINK] = g_signal_new ("activate_link",
624                                                G_OBJECT_CLASS_TYPE (object_class),
625                                                G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
626                                                G_STRUCT_OFFSET (EvPageActionClass, activate_link),
627                                                NULL, NULL,
628                                                g_cclosure_marshal_VOID__OBJECT,
629                                                G_TYPE_NONE, 1,
630                                                G_TYPE_OBJECT);
631         signals[ACTIVATE_LABEL] = g_signal_new ("activate_label",
632                                                 G_OBJECT_CLASS_TYPE (object_class),
633                                                 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
634                                                 G_STRUCT_OFFSET (EvPageActionClass, activate_link),
635                                                 NULL, NULL,
636                                                 ev_marshal_BOOLEAN__STRING,
637                                                 G_TYPE_BOOLEAN, 1,
638                                                 G_TYPE_STRING);
639
640         g_object_class_install_property (object_class,
641                                          PROP_PAGE_CACHE,
642                                          g_param_spec_object ("page-cache",
643                                                               "Page Cache",
644                                                               "Current page cache",
645                                                               EV_TYPE_PAGE_CACHE,
646                                                               G_PARAM_READWRITE));
647
648         g_object_class_install_property (object_class,
649                                          PROP_MODEL,
650                                          g_param_spec_object ("model",
651                                                               "Model",
652                                                               "Current Model",
653                                                               GTK_TYPE_TREE_MODEL,
654                                                               G_PARAM_READWRITE));
655
656         g_type_class_add_private (object_class, sizeof (EvPageActionPrivate));
657 }