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