]> www.fi.muni.cz Git - evince.git/blob - shell/ev-page-action.c
deb9b773895708913c94c0c3b16ac143a60156e6
[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_object (page_cache,
201                                                      "page-changed",
202                                                      G_CALLBACK (page_changed_cb),
203                                                      proxy, 0);
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_object_unref (page->priv->page_cache);
423                 page->priv->page_cache = NULL;
424         }
425
426         G_OBJECT_CLASS (ev_page_action_parent_class)->dispose (object);
427 }
428
429 static void
430 ev_page_action_set_property (GObject      *object,
431                              guint         prop_id,
432                              const GValue *value,
433                              GParamSpec   *pspec)
434 {
435         EvPageAction *page;
436         EvPageCache *page_cache;
437         GtkTreeModel *model;
438   
439         page = EV_PAGE_ACTION (object);
440
441         switch (prop_id)
442         {
443         case PROP_PAGE_CACHE:
444                 page_cache = page->priv->page_cache;
445                 page->priv->page_cache = EV_PAGE_CACHE (g_value_dup_object (value));
446                 if (page_cache)
447                         g_object_unref (page_cache);
448                 break;
449         case PROP_MODEL:
450                 model = page->priv->model;
451                 page->priv->model = GTK_TREE_MODEL (g_value_dup_object (value));
452                 if (model)
453                         g_object_unref (model);
454                 break;
455         default:
456                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
457                 break;
458         }
459 }
460
461 static void
462 ev_page_action_get_property (GObject    *object,
463                              guint       prop_id,
464                              GValue     *value,
465                              GParamSpec *pspec)
466 {
467         EvPageAction *page;
468   
469         page = EV_PAGE_ACTION (object);
470
471         switch (prop_id)
472         {
473         case PROP_PAGE_CACHE:
474                 g_value_set_object (value, page->priv->page_cache);
475                 break;
476         case PROP_MODEL:
477                 g_value_set_object (value, page->priv->model);
478                 break;
479         default:
480                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
481                 break;
482         }
483 }
484
485 void
486 ev_page_action_set_document (EvPageAction *page, EvDocument *document)
487 {
488         EvPageCache *page_cache = NULL;
489
490         if (document)
491                 page_cache = ev_document_get_page_cache (document);
492         
493         g_object_set (page,
494                       "page-cache", page_cache,
495                       "model", NULL,
496                       NULL);
497 }
498
499 void
500 ev_page_action_set_model (EvPageAction *page_action,
501                           GtkTreeModel *model)
502 {
503         g_object_set (page_action,
504                       "model", model,
505                       NULL);
506 }
507
508 static void
509 ev_page_action_init (EvPageAction *page)
510 {
511         page->priv = EV_PAGE_ACTION_GET_PRIVATE (page);
512 }
513
514 static void
515 ev_page_action_class_init (EvPageActionClass *class)
516 {
517         GObjectClass *object_class = G_OBJECT_CLASS (class);
518         GtkActionClass *action_class = GTK_ACTION_CLASS (class);
519
520         object_class->dispose = ev_page_action_dispose;
521         object_class->set_property = ev_page_action_set_property;
522         object_class->get_property = ev_page_action_get_property;
523
524         action_class->toolbar_item_type = GTK_TYPE_TOOL_ITEM;
525         action_class->create_tool_item = create_tool_item;
526         action_class->connect_proxy = connect_proxy;
527
528         g_object_class_install_property (object_class,
529                                          PROP_PAGE_CACHE,
530                                          g_param_spec_object ("page-cache",
531                                                               "Page Cache",
532                                                               "Current page cache",
533                                                               EV_TYPE_PAGE_CACHE,
534                                                               G_PARAM_READWRITE));
535
536         g_object_class_install_property (object_class,
537                                          PROP_MODEL,
538                                          g_param_spec_object ("model",
539                                                               "Model",
540                                                               "Current Model",
541                                                               GTK_TYPE_TREE_MODEL,
542                                                               G_PARAM_READWRITE));
543
544         g_type_class_add_private (object_class, sizeof (EvPageActionPrivate));
545 }