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