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