1 /*
2  *  Copyright (C) 2003, 2004 Marco Pesenti Gritti
3  *  Copyright (C) 2003, 2004 Christian Persch
4  *  Copyright (C) 2018       Germán Poo-Caamaño
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2, or (at your option)
9  *  any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19  *
20  */
21 
22 #include "config.h"
23 
24 #include <string.h>
25 #include <glib/gi18n.h>
26 #include <gtk/gtk.h>
27 
28 #include <evince-document.h>
29 #include "ev-page-action-widget.h"
30 
31 /* Widget we pass back */
32 static void  ev_page_action_widget_init       (EvPageActionWidget      *action_widget);
33 static void  ev_page_action_widget_class_init (EvPageActionWidgetClass *action_widget);
34 
35 enum
36 {
37 	WIDGET_ACTIVATE_LINK,
38 	WIDGET_N_SIGNALS
39 };
40 
41 struct _EvPageActionWidget
42 {
43 	GtkToolItem parent;
44 
45 	EvDocument *document;
46 	EvDocumentModel *doc_model;
47 
48 	GtkWidget *entry;
49 	GtkWidget *label;
50 	gulong signal_id;
51 	gulong notify_document_signal_id;
52 	GtkTreeModel *filter_model;
53 	GtkTreeModel *model;
54 };
55 
56 static guint widget_signals[WIDGET_N_SIGNALS] = {0, };
57 
G_DEFINE_TYPE(EvPageActionWidget,ev_page_action_widget,GTK_TYPE_TOOL_ITEM)58 G_DEFINE_TYPE (EvPageActionWidget, ev_page_action_widget, GTK_TYPE_TOOL_ITEM)
59 
60 static gboolean
61 show_page_number_in_pages_label (EvPageActionWidget *action_widget,
62                                  gint                page)
63 {
64         gchar   *page_label;
65         gboolean retval;
66 
67         if (!ev_document_has_text_page_labels (action_widget->document))
68                 return FALSE;
69 
70         page_label = g_strdup_printf ("%d", page + 1);
71         retval = g_strcmp0 (page_label, gtk_entry_get_text (GTK_ENTRY (action_widget->entry))) != 0;
72         g_free (page_label);
73 
74         return retval;
75 }
76 
77 static void
update_pages_label(EvPageActionWidget * action_widget,gint page)78 update_pages_label (EvPageActionWidget *action_widget,
79 		    gint                page)
80 {
81 	char *label_text;
82 	gint n_pages;
83 
84 	n_pages = ev_document_get_n_pages (action_widget->document);
85         if (show_page_number_in_pages_label (action_widget, page))
86                 label_text = g_strdup_printf (_("(%d of %d)"), page + 1, n_pages);
87         else
88                 label_text = g_strdup_printf (_("of %d"), n_pages);
89 	gtk_entry_set_text (GTK_ENTRY (action_widget->label), label_text);
90 	g_free (label_text);
91 }
92 
93 static void
ev_page_action_widget_set_current_page(EvPageActionWidget * action_widget,gint page)94 ev_page_action_widget_set_current_page (EvPageActionWidget *action_widget,
95 					gint                page)
96 {
97 	if (page >= 0) {
98 		gchar *page_label;
99 
100 		page_label = ev_document_get_page_label (action_widget->document, page);
101 		gtk_entry_set_text (GTK_ENTRY (action_widget->entry), page_label);
102 		gtk_editable_set_position (GTK_EDITABLE (action_widget->entry), -1);
103 		g_free (page_label);
104 	} else {
105 		gtk_entry_set_text (GTK_ENTRY (action_widget->entry), "");
106 	}
107 
108 	update_pages_label (action_widget, page);
109 }
110 
111 static void
ev_page_action_widget_update_max_width(EvPageActionWidget * action_widget)112 ev_page_action_widget_update_max_width (EvPageActionWidget *action_widget)
113 {
114         gchar *max_label;
115         gint   n_pages;
116         gint   max_label_len;
117         gchar *max_page_label;
118         gchar *max_page_numeric_label;
119 
120         n_pages = ev_document_get_n_pages (action_widget->document);
121 
122         max_page_label = ev_document_get_page_label (action_widget->document, n_pages - 1);
123         max_page_numeric_label = g_strdup_printf ("%d", n_pages);
124         if (ev_document_has_text_page_labels (action_widget->document) != 0) {
125                 max_label = g_strdup_printf (_("(%d of %d)"), n_pages, n_pages);
126                 /* Do not take into account the parentheses for the size computation */
127                 max_label_len = g_utf8_strlen (max_label, -1) - 2;
128         } else {
129                 max_label = g_strdup_printf (_("of %d"), n_pages);
130                 max_label_len = g_utf8_strlen (max_label, -1);
131         }
132         g_free (max_page_label);
133 
134         gtk_entry_set_width_chars (GTK_ENTRY (action_widget->label), max_label_len);
135         g_free (max_label);
136 
137         max_label_len = ev_document_get_max_label_len (action_widget->document);
138         gtk_entry_set_width_chars (GTK_ENTRY (action_widget->entry),
139                                    CLAMP (max_label_len, strlen (max_page_numeric_label) + 1, 12));
140         g_free (max_page_numeric_label);
141 }
142 
143 static void
page_changed_cb(EvDocumentModel * model,gint old_page,gint new_page,EvPageActionWidget * action_widget)144 page_changed_cb (EvDocumentModel    *model,
145 		 gint                old_page,
146 		 gint                new_page,
147 		 EvPageActionWidget *action_widget)
148 {
149 	ev_page_action_widget_set_current_page (action_widget, new_page);
150 }
151 
152 static gboolean
page_scroll_cb(EvPageActionWidget * action_widget,GdkEventScroll * event)153 page_scroll_cb (EvPageActionWidget *action_widget, GdkEventScroll *event)
154 {
155 	EvDocumentModel *model = action_widget->doc_model;
156 	gint pageno;
157 
158 	pageno = ev_document_model_get_page (model);
159 	if ((event->direction == GDK_SCROLL_DOWN) &&
160 	    (pageno < ev_document_get_n_pages (action_widget->document) - 1))
161 		pageno++;
162 	if ((event->direction == GDK_SCROLL_UP) && (pageno > 0))
163 		pageno--;
164 	ev_document_model_set_page (model, pageno);
165 
166 	return TRUE;
167 }
168 
169 static void
activate_cb(EvPageActionWidget * action_widget)170 activate_cb (EvPageActionWidget *action_widget)
171 {
172 	EvDocumentModel *model;
173 	const char *text;
174 	EvLinkDest *link_dest;
175 	EvLinkAction *link_action;
176 	EvLink *link;
177 	gchar *link_text;
178 	gint current_page;
179 
180 	model = action_widget->doc_model;
181 	current_page = ev_document_model_get_page (model);
182 
183 	text = gtk_entry_get_text (GTK_ENTRY (action_widget->entry));
184 
185 	link_dest = ev_link_dest_new_page_label (text);
186 	link_action = ev_link_action_new_dest (link_dest);
187 	link_text = g_strdup_printf (_("Page %s"), text);
188 	link = ev_link_new (link_text, link_action);
189 
190 	g_signal_emit (action_widget, widget_signals[WIDGET_ACTIVATE_LINK], 0, link);
191 
192 	g_object_unref (link_dest);
193 	g_object_unref (link_action);
194 	g_object_unref (link);
195 	g_free (link_text);
196 
197 	if (current_page == ev_document_model_get_page (model))
198 		ev_page_action_widget_set_current_page (action_widget, current_page);
199 }
200 
201 static gboolean
focus_out_cb(EvPageActionWidget * action_widget)202 focus_out_cb (EvPageActionWidget *action_widget)
203 {
204         ev_page_action_widget_set_current_page (action_widget,
205                                                 ev_document_model_get_page (action_widget->doc_model));
206         return FALSE;
207 }
208 
209 static void
ev_page_action_widget_init(EvPageActionWidget * action_widget)210 ev_page_action_widget_init (EvPageActionWidget *action_widget)
211 {
212 	GtkWidget *hbox;
213 	AtkObject *obj;
214         GtkStyleContext *style_context;
215 
216 	hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
217 
218         style_context = gtk_widget_get_style_context (hbox);
219         gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_RAISED);
220         gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_LINKED);
221 
222 	action_widget->entry = gtk_entry_new ();
223 	gtk_widget_add_events (action_widget->entry,
224 			       GDK_BUTTON_MOTION_MASK);
225 	gtk_entry_set_width_chars (GTK_ENTRY (action_widget->entry), 5);
226 	gtk_entry_set_text (GTK_ENTRY (action_widget->entry), "");
227 	g_signal_connect_swapped (action_widget->entry, "scroll-event",
228 				  G_CALLBACK (page_scroll_cb),
229 				  action_widget);
230 	g_signal_connect_swapped (action_widget->entry, "activate",
231 				  G_CALLBACK (activate_cb),
232 				  action_widget);
233         g_signal_connect_swapped (action_widget->entry, "focus-out-event",
234                                   G_CALLBACK (focus_out_cb),
235                                   action_widget);
236 	g_object_set (action_widget->entry, "xalign", 1.0, NULL);
237 
238 	obj = gtk_widget_get_accessible (action_widget->entry);
239 	atk_object_set_name (obj, "page-label-entry");
240 
241 	gtk_box_pack_start (GTK_BOX (hbox), action_widget->entry,
242 			    FALSE, FALSE, 0);
243 	gtk_widget_show (action_widget->entry);
244 
245 	action_widget->label = gtk_entry_new ();
246         gtk_widget_set_sensitive (action_widget->label, FALSE);
247         gtk_entry_set_width_chars (GTK_ENTRY (action_widget->label), 5);
248 	gtk_box_pack_start (GTK_BOX (hbox), action_widget->label,
249 			    FALSE, FALSE, 0);
250 	gtk_widget_show (action_widget->label);
251 
252 	gtk_container_add (GTK_CONTAINER (action_widget), hbox);
253 	gtk_widget_show (hbox);
254 
255         gtk_widget_set_sensitive (GTK_WIDGET (action_widget), FALSE);
256 }
257 
258 static void
ev_page_action_widget_set_document(EvPageActionWidget * action_widget,EvDocument * document)259 ev_page_action_widget_set_document (EvPageActionWidget *action_widget,
260                                     EvDocument         *document)
261 {
262         if (document) {
263                 g_object_ref (document);
264                 gtk_widget_set_sensitive (GTK_WIDGET (action_widget), ev_document_get_n_pages (document) > 0);
265         }
266 
267         if (action_widget->signal_id > 0) {
268                 if (action_widget->doc_model != NULL) {
269                         g_signal_handler_disconnect (action_widget->doc_model,
270                                                      action_widget->signal_id);
271                 }
272                 action_widget->signal_id = 0;
273         }
274 
275         if (action_widget->document)
276                 g_object_unref (action_widget->document);
277         action_widget->document = document;
278         if (!action_widget->document)
279                 return;
280 
281         action_widget->signal_id =
282                 g_signal_connect (action_widget->doc_model,
283                                   "page-changed",
284                                   G_CALLBACK (page_changed_cb),
285                                   action_widget);
286 
287         ev_page_action_widget_set_current_page (action_widget,
288                                                 ev_document_model_get_page (action_widget->doc_model));
289         ev_page_action_widget_update_max_width (action_widget);
290 }
291 
292 static void
ev_page_action_widget_document_changed_cb(EvDocumentModel * model,GParamSpec * pspec,EvPageActionWidget * action_widget)293 ev_page_action_widget_document_changed_cb (EvDocumentModel    *model,
294 					   GParamSpec         *pspec,
295 					   EvPageActionWidget *action_widget)
296 {
297         ev_page_action_widget_set_document (action_widget, ev_document_model_get_document (model));
298 }
299 
300 void
ev_page_action_widget_set_model(EvPageActionWidget * action_widget,EvDocumentModel * model)301 ev_page_action_widget_set_model (EvPageActionWidget *action_widget,
302 				 EvDocumentModel    *model)
303 {
304 	if (action_widget->doc_model) {
305 		g_object_remove_weak_pointer (G_OBJECT (action_widget->doc_model),
306 					      (gpointer)&action_widget->doc_model);
307 	}
308 	action_widget->doc_model = model;
309 	g_object_add_weak_pointer (G_OBJECT (model),
310 				   (gpointer)&action_widget->doc_model);
311 
312         ev_page_action_widget_set_document (action_widget, ev_document_model_get_document (model));
313 
314 	action_widget->notify_document_signal_id =
315 		g_signal_connect (model, "notify::document",
316 				  G_CALLBACK (ev_page_action_widget_document_changed_cb),
317 				  action_widget);
318 }
319 
320 static void
ev_page_action_widget_finalize(GObject * object)321 ev_page_action_widget_finalize (GObject *object)
322 {
323 	EvPageActionWidget *action_widget = EV_PAGE_ACTION_WIDGET (object);
324 
325 	if (action_widget->doc_model != NULL) {
326 		if (action_widget->signal_id > 0) {
327 			g_signal_handler_disconnect (action_widget->doc_model,
328 						     action_widget->signal_id);
329 			action_widget->signal_id = 0;
330 		}
331 		if (action_widget->notify_document_signal_id > 0) {
332 			g_signal_handler_disconnect (action_widget->doc_model,
333 						     action_widget->notify_document_signal_id);
334 			action_widget->notify_document_signal_id = 0;
335 		}
336 		g_object_remove_weak_pointer (G_OBJECT (action_widget->doc_model),
337 					      (gpointer)&action_widget->doc_model);
338 		action_widget->doc_model = NULL;
339 	}
340 
341         ev_page_action_widget_set_document (action_widget, NULL);
342 
343 	G_OBJECT_CLASS (ev_page_action_widget_parent_class)->finalize (object);
344 }
345 
346 static void
ev_page_action_widget_get_preferred_width(GtkWidget * widget,gint * minimum_width,gint * natural_width)347 ev_page_action_widget_get_preferred_width (GtkWidget *widget,
348                                            gint      *minimum_width,
349                                            gint      *natural_width)
350 {
351         GtkWidget *child;
352 
353         *minimum_width = *natural_width = 0;
354 
355         child = gtk_bin_get_child (GTK_BIN (widget));
356         if (!child || !gtk_widget_get_visible (child))
357                 return;
358 
359         gtk_widget_get_preferred_width (child, minimum_width, natural_width);
360         *natural_width = *minimum_width;
361 }
362 
363 static void
ev_page_action_widget_class_init(EvPageActionWidgetClass * klass)364 ev_page_action_widget_class_init (EvPageActionWidgetClass *klass)
365 {
366 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
367         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
368 
369 	object_class->finalize = ev_page_action_widget_finalize;
370         widget_class->get_preferred_width = ev_page_action_widget_get_preferred_width;
371 
372 	widget_signals[WIDGET_ACTIVATE_LINK] =
373 		g_signal_new ("activate_link",
374 			      G_OBJECT_CLASS_TYPE (object_class),
375 			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
376 			      G_STRUCT_OFFSET (EvPageActionWidgetClass, activate_link),
377 			      NULL, NULL,
378 			      g_cclosure_marshal_VOID__OBJECT,
379 			      G_TYPE_NONE, 1,
380 			      G_TYPE_OBJECT);
381 
382 }
383 
384 static gboolean
match_selected_cb(GtkEntryCompletion * completion,GtkTreeModel * filter_model,GtkTreeIter * filter_iter,EvPageActionWidget * proxy)385 match_selected_cb (GtkEntryCompletion *completion,
386 		   GtkTreeModel       *filter_model,
387 		   GtkTreeIter        *filter_iter,
388 		   EvPageActionWidget *proxy)
389 {
390 	EvLink *link;
391 	GtkTreeIter *iter;
392 
393 	gtk_tree_model_get (filter_model, filter_iter,
394 			    0, &iter,
395 			    -1);
396 	gtk_tree_model_get (proxy->model, iter,
397 			    EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
398 			    -1);
399 
400 	g_signal_emit (proxy, widget_signals[WIDGET_ACTIVATE_LINK], 0, link);
401 
402 	if (link)
403 		g_object_unref (link);
404 
405 	gtk_tree_iter_free (iter);
406 
407 	return TRUE;
408 }
409 
410 
411 static void
display_completion_text(GtkCellLayout * cell_layout,GtkCellRenderer * renderer,GtkTreeModel * filter_model,GtkTreeIter * filter_iter,EvPageActionWidget * proxy)412 display_completion_text (GtkCellLayout      *cell_layout,
413 			 GtkCellRenderer    *renderer,
414 			 GtkTreeModel       *filter_model,
415 			 GtkTreeIter        *filter_iter,
416 			 EvPageActionWidget *proxy)
417 {
418 	EvLink *link;
419 	GtkTreeIter *iter;
420 
421 	gtk_tree_model_get (filter_model, filter_iter,
422 			    0, &iter,
423 			    -1);
424 	gtk_tree_model_get (proxy->model, iter,
425 			    EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
426 			    -1);
427 
428 	g_object_set (renderer, "text", ev_link_get_title (link), NULL);
429 
430 	if (link)
431 		g_object_unref (link);
432 
433 	gtk_tree_iter_free (iter);
434 }
435 
436 static gboolean
match_completion(GtkEntryCompletion * completion,const gchar * key,GtkTreeIter * filter_iter,EvPageActionWidget * proxy)437 match_completion (GtkEntryCompletion *completion,
438 		  const gchar        *key,
439 		  GtkTreeIter        *filter_iter,
440 		  EvPageActionWidget *proxy)
441 {
442 	EvLink *link;
443 	GtkTreeIter *iter;
444 	const gchar *text = NULL;
445 
446 	gtk_tree_model_get (gtk_entry_completion_get_model (completion),
447 			    filter_iter,
448 			    0, &iter,
449 			    -1);
450 	gtk_tree_model_get (proxy->model, iter,
451 			    EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
452 			    -1);
453 
454 
455 	if (link) {
456 		text = ev_link_get_title (link);
457 		g_object_unref (link);
458 	}
459 
460 	gtk_tree_iter_free (iter);
461 
462 	if (text && key) {
463 		gchar *normalized_text;
464 		gchar *normalized_key;
465 		gchar *case_normalized_text;
466 		gchar *case_normalized_key;
467 		gboolean retval = FALSE;
468 
469 		normalized_text = g_utf8_normalize (text, -1, G_NORMALIZE_ALL);
470 		normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL);
471 		case_normalized_text = g_utf8_casefold (normalized_text, -1);
472 		case_normalized_key = g_utf8_casefold (normalized_key, -1);
473 
474 		if (strstr (case_normalized_text, case_normalized_key))
475 			retval = TRUE;
476 
477 		g_free (normalized_text);
478 		g_free (normalized_key);
479 		g_free (case_normalized_text);
480 		g_free (case_normalized_key);
481 
482 		return retval;
483 	}
484 
485 	return FALSE;
486 }
487 
488 /* user data to set on the widget. */
489 #define EPA_FILTER_MODEL_DATA "epa-filter-model"
490 
491 static gboolean
build_new_tree_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)492 build_new_tree_cb (GtkTreeModel *model,
493 		   GtkTreePath  *path,
494 		   GtkTreeIter  *iter,
495 		   gpointer      data)
496 {
497 	GtkTreeModel *filter_model = GTK_TREE_MODEL (data);
498 	EvLink *link;
499 	EvLinkAction *action;
500 	EvLinkActionType type;
501 
502 	gtk_tree_model_get (model, iter,
503 			    EV_DOCUMENT_LINKS_COLUMN_LINK, &link,
504 			    -1);
505 
506 	if (!link)
507 		return FALSE;
508 
509 	action = ev_link_get_action (link);
510 	if (!action) {
511 		g_object_unref (link);
512 		return FALSE;
513 	}
514 
515 	type = ev_link_action_get_action_type (action);
516 
517 	if (type == EV_LINK_ACTION_TYPE_GOTO_DEST) {
518 		GtkTreeIter filter_iter;
519 
520 		gtk_list_store_append (GTK_LIST_STORE (filter_model), &filter_iter);
521 		gtk_list_store_set (GTK_LIST_STORE (filter_model), &filter_iter,
522 				    0, iter,
523 				    -1);
524 	}
525 
526 	g_object_unref (link);
527 
528 	return FALSE;
529 }
530 
531 static GtkTreeModel *
get_filter_model_from_model(GtkTreeModel * model)532 get_filter_model_from_model (GtkTreeModel *model)
533 {
534 	GtkTreeModel *filter_model;
535 
536 	filter_model =
537 		(GtkTreeModel *) g_object_get_data (G_OBJECT (model), EPA_FILTER_MODEL_DATA);
538 	if (filter_model == NULL) {
539 		filter_model = (GtkTreeModel *) gtk_list_store_new (1, GTK_TYPE_TREE_ITER);
540 
541 		gtk_tree_model_foreach (model,
542 					build_new_tree_cb,
543 					filter_model);
544 		g_object_set_data_full (G_OBJECT (model), EPA_FILTER_MODEL_DATA, filter_model, g_object_unref);
545 	}
546 
547 	return filter_model;
548 }
549 
550 
551 void
ev_page_action_widget_update_links_model(EvPageActionWidget * proxy,GtkTreeModel * model)552 ev_page_action_widget_update_links_model (EvPageActionWidget *proxy, GtkTreeModel *model)
553 {
554 	GtkTreeModel *filter_model;
555 	GtkEntryCompletion *completion;
556 	GtkCellRenderer *renderer;
557 
558 	if (!model || model == proxy->model)
559 		return;
560 
561 	/* Magik */
562 	proxy->model = model;
563 	filter_model = get_filter_model_from_model (model);
564 
565 	completion = gtk_entry_completion_new ();
566 	g_object_set (G_OBJECT (completion),
567 		      "popup-set-width", FALSE,
568 		      "model", filter_model,
569 		      NULL);
570 
571 	g_signal_connect (completion, "match-selected", G_CALLBACK (match_selected_cb), proxy);
572 	gtk_entry_completion_set_match_func (completion,
573 					     (GtkEntryCompletionMatchFunc) match_completion,
574 					     proxy, NULL);
575 
576 	/* Set up the layout */
577 	renderer = (GtkCellRenderer *)
578 		g_object_new (GTK_TYPE_CELL_RENDERER_TEXT,
579 			      "ellipsize", PANGO_ELLIPSIZE_END,
580 			      "width_chars", 30,
581 			      NULL);
582 	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (completion), renderer, TRUE);
583 	gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (completion),
584 					    renderer,
585 					    (GtkCellLayoutDataFunc) display_completion_text,
586 					    proxy, NULL);
587 	gtk_entry_set_completion (GTK_ENTRY (proxy->entry), completion);
588 
589 	g_object_unref (completion);
590 }
591 
592 void
ev_page_action_widget_grab_focus(EvPageActionWidget * proxy)593 ev_page_action_widget_grab_focus (EvPageActionWidget *proxy)
594 {
595 	gtk_widget_grab_focus (proxy->entry);
596 }
597 
598