1 /*
2  * Copyright (C) 2007 Carlos Garcia Campos  <carlosgc@gnome.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2, or (at your option)
7  * any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include <gtk/gtk.h>
20 #include <stdio.h>
21 #include <unistd.h>
22 #include <time.h>
23 
24 #include "utils.h"
25 
26 void
pgd_table_add_property_with_custom_widget(GtkTable * table,const gchar * markup,GtkWidget * widget,gint * row)27 pgd_table_add_property_with_custom_widget (GtkTable    *table,
28 					   const gchar *markup,
29 					   GtkWidget   *widget,
30 					   gint        *row)
31 {
32 	GtkWidget *label;
33 
34 	label = gtk_label_new (NULL);
35 	g_object_set (G_OBJECT (label), "xalign", 0.0, NULL);
36 	gtk_label_set_markup (GTK_LABEL (label), markup);
37 	gtk_table_attach (GTK_TABLE (table), label, 0, 1, *row, *row + 1,
38 			  GTK_FILL, GTK_FILL, 0, 0);
39 	gtk_widget_show (label);
40 
41 	gtk_table_attach (GTK_TABLE (table), widget, 1, 2, *row, *row + 1,
42 			  GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
43 	gtk_widget_show (widget);
44 
45 	*row += 1;
46 }
47 
48 void
pgd_table_add_property_with_value_widget(GtkTable * table,const gchar * markup,GtkWidget ** value_widget,const gchar * value,gint * row)49 pgd_table_add_property_with_value_widget (GtkTable    *table,
50 					  const gchar *markup,
51 					  GtkWidget  **value_widget,
52 					  const gchar *value,
53 					  gint        *row)
54 {
55 	GtkWidget *label;
56 
57 	*value_widget = label = gtk_label_new (value);
58 	g_object_set (G_OBJECT (label),
59 		      "xalign", 0.0,
60 		      "selectable", TRUE,
61 		      "ellipsize", PANGO_ELLIPSIZE_END,
62 		      NULL);
63 	pgd_table_add_property_with_custom_widget (table, markup, label, row);
64 }
65 
66 void
pgd_table_add_property(GtkTable * table,const gchar * markup,const gchar * value,gint * row)67 pgd_table_add_property (GtkTable    *table,
68 			const gchar *markup,
69 			const gchar *value,
70 			gint        *row)
71 {
72 	GtkWidget *label;
73 
74 	pgd_table_add_property_with_value_widget (table, markup, &label, value, row);
75 }
76 
77 GtkWidget *
pgd_action_view_new(PopplerDocument * document)78 pgd_action_view_new (PopplerDocument *document)
79 {
80 	GtkWidget  *frame, *label;
81 
82 	frame = gtk_frame_new (NULL);
83 	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
84 	label = gtk_label_new (NULL);
85 	gtk_label_set_markup (GTK_LABEL (label), "<b>Action Properties</b>");
86 	gtk_frame_set_label_widget (GTK_FRAME (frame), label);
87 	gtk_widget_show (label);
88 
89 	g_object_set_data (G_OBJECT (frame), "document", document);
90 
91 	return frame;
92 }
93 
94 static void
pgd_action_view_add_destination(GtkWidget * action_view,GtkTable * table,PopplerDest * dest,gboolean remote,gint * row)95 pgd_action_view_add_destination (GtkWidget   *action_view,
96 				 GtkTable    *table,
97 				 PopplerDest *dest,
98 				 gboolean     remote,
99 				 gint        *row)
100 {
101 	PopplerDocument *document;
102 	GEnumValue      *enum_value;
103 	gchar           *str;
104 
105 	pgd_table_add_property (table, "<b>Type:</b>", "Destination", row);
106 
107 	enum_value = g_enum_get_value ((GEnumClass *) g_type_class_ref (POPPLER_TYPE_DEST_TYPE), dest->type);
108 	pgd_table_add_property (table, "<b>Destination Type:</b>", enum_value->value_name, row);
109 
110 	document = g_object_get_data (G_OBJECT (action_view), "document");
111 
112 	if (dest->type != POPPLER_DEST_NAMED) {
113 		str = NULL;
114 
115 		if (document && !remote) {
116 			PopplerPage *poppler_page;
117 			gchar       *page_label;
118 
119 			poppler_page = poppler_document_get_page (document, MAX (0, dest->page_num - 1));
120 
121 			g_object_get (G_OBJECT (poppler_page),
122 				      "label", &page_label,
123 				      NULL);
124 			if (page_label) {
125 				str = g_strdup_printf ("%d (%s)", dest->page_num, page_label);
126 				g_free (page_label);
127 			}
128 		}
129 
130 		if (!str)
131 			str = g_strdup_printf ("%d", dest->page_num);
132 		pgd_table_add_property (table, "<b>Page:</b>", str, row);
133 		g_free (str);
134 
135 		str = g_strdup_printf ("%.2f", dest->left);
136 		pgd_table_add_property (table, "<b>Left:</b>", str, row);
137 		g_free (str);
138 
139 		str = g_strdup_printf ("%.2f", dest->right);
140 		pgd_table_add_property (table, "<b>Right:</b>", str, row);
141 		g_free (str);
142 
143 		str = g_strdup_printf ("%.2f", dest->top);
144 		pgd_table_add_property (table, "<b>Top:</b>", str, row);
145 		g_free (str);
146 
147 		str = g_strdup_printf ("%.2f", dest->bottom);
148 		pgd_table_add_property (table, "<b>Bottom:</b>", str, row);
149 		g_free (str);
150 
151 		str = g_strdup_printf ("%.2f", dest->zoom);
152 		pgd_table_add_property (table, "<b>Zoom:</b>", str, row);
153 		g_free (str);
154 	} else {
155 		pgd_table_add_property (table, "<b>Named Dest:</b>", dest->named_dest, row);
156 
157 		if (document && !remote) {
158 			PopplerDest *new_dest;
159 
160 			new_dest = poppler_document_find_dest (document, dest->named_dest);
161 			if (new_dest) {
162 				GtkWidget *new_table, *alignment;
163 				gint       new_row = 0;
164 
165 				alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
166 				gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 5, 5, 12, 5);
167 
168 				new_table = gtk_table_new (8, 2, FALSE);
169 				gtk_table_set_col_spacings (GTK_TABLE (new_table), 6);
170 				gtk_table_set_row_spacings (GTK_TABLE (new_table), 6);
171 				gtk_table_attach_defaults (table, alignment, 0, 2, *row, *row + 1);
172 				gtk_widget_show (alignment);
173 
174 				pgd_action_view_add_destination (action_view, GTK_TABLE (new_table),
175 								 new_dest, FALSE, &new_row);
176 				poppler_dest_free (new_dest);
177 
178 				gtk_container_add (GTK_CONTAINER (alignment), new_table);
179 				gtk_widget_show (new_table);
180 
181 				*row += 1;
182 			}
183 		}
184 	}
185 }
186 
187 static const gchar *
get_movie_op(PopplerActionMovieOperation op)188 get_movie_op (PopplerActionMovieOperation op)
189 {
190 	switch (op) {
191 	case POPPLER_ACTION_MOVIE_PLAY:
192 		return "Play";
193 	case POPPLER_ACTION_MOVIE_PAUSE:
194 		return "Pause";
195 	case POPPLER_ACTION_MOVIE_RESUME:
196 		return "Resume";
197 	case POPPLER_ACTION_MOVIE_STOP:
198 		return "Stop";
199 	}
200 	return NULL;
201 }
202 
203 static void
free_tmp_file(GFile * tmp_file)204 free_tmp_file (GFile *tmp_file)
205 {
206 
207 	g_file_delete (tmp_file, NULL, NULL);
208 	g_object_unref (tmp_file);
209 }
210 
211 static gboolean
save_helper(const gchar * buf,gsize count,gpointer data,GError ** error)212 save_helper (const gchar  *buf,
213 	     gsize         count,
214 	     gpointer      data,
215 	     GError      **error)
216 {
217 	gint fd = GPOINTER_TO_INT (data);
218 
219 	return write (fd, buf, count) == count;
220 }
221 
222 static void
pgd_action_view_play_rendition(GtkWidget * button,PopplerMedia * media)223 pgd_action_view_play_rendition (GtkWidget    *button,
224 				PopplerMedia *media)
225 {
226 	GFile *file = NULL;
227 	gchar *uri;
228 
229 	if (poppler_media_is_embedded (media)) {
230 		gint   fd;
231 		gchar *tmp_filename = NULL;
232 
233 		fd = g_file_open_tmp (NULL, &tmp_filename, NULL);
234 		if (fd != -1) {
235 			if (poppler_media_save_to_callback (media, save_helper, GINT_TO_POINTER (fd), NULL)) {
236 				file = g_file_new_for_path (tmp_filename);
237 				g_object_set_data_full (G_OBJECT (media),
238 							"tmp-file", g_object_ref (file),
239 							(GDestroyNotify)free_tmp_file);
240 			} else {
241 				g_free (tmp_filename);
242 			}
243 			close (fd);
244 		} else if (tmp_filename) {
245 			g_free (tmp_filename);
246 		}
247 
248 	} else {
249 		const gchar *filename;
250 
251 		filename = poppler_media_get_filename (media);
252 		if (g_path_is_absolute (filename)) {
253 			file = g_file_new_for_path (filename);
254 		} else if (g_strrstr (filename, "://")) {
255 			file = g_file_new_for_uri (filename);
256 		} else {
257 			gchar *cwd;
258 			gchar *path;
259 
260 			// FIXME: relative to doc uri, not cwd
261 			cwd = g_get_current_dir ();
262 			path = g_build_filename (cwd, filename, NULL);
263 			g_free (cwd);
264 
265 			file = g_file_new_for_path (path);
266 			g_free (path);
267 		}
268 	}
269 
270 	if (file) {
271 		uri = g_file_get_uri (file);
272 		g_object_unref (file);
273 		if (uri) {
274 			gtk_show_uri (gtk_widget_get_screen (button),
275 				      uri, GDK_CURRENT_TIME, NULL);
276 			g_free (uri);
277 		}
278 	}
279 }
280 
281 static void
pgd_action_view_do_action_layer(GtkWidget * button,GList * state_list)282 pgd_action_view_do_action_layer (GtkWidget *button,
283 				 GList     *state_list)
284 {
285 	GList *l, *m;
286 
287 	for (l = state_list; l; l = g_list_next (l)) {
288 		PopplerActionLayer *action_layer = (PopplerActionLayer *)l->data;
289 
290 		for (m = action_layer->layers; m; m = g_list_next (m)) {
291 			PopplerLayer *layer = (PopplerLayer *)m->data;
292 
293 			switch (action_layer->action) {
294 			case POPPLER_ACTION_LAYER_ON:
295 				poppler_layer_show (layer);
296 				break;
297 			case POPPLER_ACTION_LAYER_OFF:
298 				poppler_layer_hide (layer);
299 				break;
300 			case POPPLER_ACTION_LAYER_TOGGLE:
301 				if (poppler_layer_is_visible (layer))
302 					poppler_layer_hide (layer);
303 				else
304 					poppler_layer_show (layer);
305 				break;
306 			}
307 		}
308 	}
309 }
310 
311 void
pgd_action_view_set_action(GtkWidget * action_view,PopplerAction * action)312 pgd_action_view_set_action (GtkWidget     *action_view,
313 			    PopplerAction *action)
314 {
315 	GtkWidget  *alignment;
316 	GtkWidget  *table;
317 	gint        row = 0;
318 
319 	alignment = gtk_bin_get_child (GTK_BIN (action_view));
320 	if (alignment) {
321 		gtk_container_remove (GTK_CONTAINER (action_view), alignment);
322 	}
323 
324 	alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
325 	gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 5, 5, 12, 5);
326 	gtk_container_add (GTK_CONTAINER (action_view), alignment);
327 	gtk_widget_show (alignment);
328 
329 	if (!action)
330 		return;
331 
332 	table = gtk_table_new (10, 2, FALSE);
333 	gtk_table_set_col_spacings (GTK_TABLE (table), 6);
334 	gtk_table_set_row_spacings (GTK_TABLE (table), 6);
335 
336 	pgd_table_add_property (GTK_TABLE (table), "<b>Title:</b>", action->any.title, &row);
337 
338 	switch (action->type) {
339 	case POPPLER_ACTION_UNKNOWN:
340 		pgd_table_add_property (GTK_TABLE (table), "<b>Type:</b>", "Unknown", &row);
341 		break;
342 	case POPPLER_ACTION_NONE:
343 		pgd_table_add_property (GTK_TABLE (table), "<b>Type:</b>", "None", &row);
344 		break;
345 	case POPPLER_ACTION_GOTO_DEST:
346 		pgd_action_view_add_destination (action_view, GTK_TABLE (table), action->goto_dest.dest, FALSE, &row);
347 		break;
348 	case POPPLER_ACTION_GOTO_REMOTE:
349 		pgd_table_add_property (GTK_TABLE (table), "<b>Type:</b>", "Remote Destination", &row);
350 		pgd_table_add_property (GTK_TABLE (table), "<b>Filename:</b>", action->goto_remote.file_name, &row);
351 		pgd_action_view_add_destination (action_view, GTK_TABLE (table), action->goto_remote.dest, TRUE, &row);
352 		break;
353 	case POPPLER_ACTION_LAUNCH:
354 		pgd_table_add_property (GTK_TABLE (table), "<b>Type:</b>", "Launch", &row);
355 		pgd_table_add_property (GTK_TABLE (table), "<b>Filename:</b>", action->launch.file_name, &row);
356 		pgd_table_add_property (GTK_TABLE (table), "<b>Params:</b>", action->launch.params, &row);
357 		break;
358 	case POPPLER_ACTION_URI:
359 		pgd_table_add_property (GTK_TABLE (table), "<b>Type:</b>", "External URI", &row);
360 		pgd_table_add_property (GTK_TABLE (table), "<b>URI</b>", action->uri.uri, &row);
361 		break;
362 	case POPPLER_ACTION_NAMED:
363 		pgd_table_add_property (GTK_TABLE (table), "<b>Type:</b>", "Named Action", &row);
364 		pgd_table_add_property (GTK_TABLE (table), "<b>Name:</b>", action->named.named_dest, &row);
365 		break;
366 	case POPPLER_ACTION_MOVIE: {
367 		GtkWidget *movie_view = pgd_movie_view_new ();
368 
369 		pgd_table_add_property (GTK_TABLE (table), "<b>Type:</b>", "Movie", &row);
370 		pgd_table_add_property (GTK_TABLE (table), "<b>Operation:</b>", get_movie_op (action->movie.operation), &row);
371 		pgd_movie_view_set_movie (movie_view, action->movie.movie);
372 		pgd_table_add_property_with_custom_widget (GTK_TABLE (table), "<b>Movie:</b>", movie_view, &row);
373 	}
374 		break;
375 	case POPPLER_ACTION_RENDITION: {
376 		gchar *text;
377 
378 		pgd_table_add_property (GTK_TABLE (table), "<b>Type:</b>", "Rendition", &row);
379 		text = g_strdup_printf ("%d", action->rendition.op);
380 		pgd_table_add_property (GTK_TABLE (table), "<b>Operation:</b>", text, &row);
381 		g_free (text);
382 		if (action->rendition.media) {
383 			gboolean   embedded = poppler_media_is_embedded (action->rendition.media);
384 			GtkWidget *button;
385 
386 			pgd_table_add_property (GTK_TABLE (table), "<b>Embedded:</b>", embedded ? "Yes": "No", &row);
387 			if (embedded) {
388 				const gchar *mime_type = poppler_media_get_mime_type (action->rendition.media);
389 				pgd_table_add_property (GTK_TABLE (table), "<b>Mime type:</b>",
390 							mime_type ? mime_type : "",
391 							&row);
392 			} else {
393 				pgd_table_add_property (GTK_TABLE (table), "<b>Filename:</b>",
394 							poppler_media_get_filename (action->rendition.media),
395 							&row);
396 			}
397 
398 			button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PLAY);
399 			g_signal_connect (button, "clicked",
400 					  G_CALLBACK (pgd_action_view_play_rendition),
401 					  action->rendition.media);
402 			pgd_table_add_property_with_custom_widget (GTK_TABLE (table), NULL, button, &row);
403 			gtk_widget_show (button);
404 		}
405 	}
406 		break;
407 	case POPPLER_ACTION_OCG_STATE: {
408 		GList     *l;
409 		GtkWidget *button;
410 
411 		pgd_table_add_property (GTK_TABLE (table), "<b>Type:</b>", "OCGState", &row);
412 
413 		for (l = action->ocg_state.state_list; l; l = g_list_next (l)) {
414 			PopplerActionLayer *action_layer = (PopplerActionLayer *)l->data;
415 			gchar *text;
416 			gint   n_layers = g_list_length (action_layer->layers);
417 
418 			switch (action_layer->action) {
419 			case POPPLER_ACTION_LAYER_ON:
420 				text = g_strdup_printf ("%d layers On", n_layers);
421 				break;
422 			case POPPLER_ACTION_LAYER_OFF:
423 				text = g_strdup_printf ("%d layers Off", n_layers);
424 				break;
425 			case POPPLER_ACTION_LAYER_TOGGLE:
426 				text = g_strdup_printf ("%d layers Toggle", n_layers);
427 				break;
428 			}
429 			pgd_table_add_property (GTK_TABLE (table), "<b>Action:</b>", text, &row);
430 			g_free (text);
431 		}
432 
433 		button = gtk_button_new_with_label ("Do action");
434 		g_signal_connect (button, "clicked",
435 				  G_CALLBACK (pgd_action_view_do_action_layer),
436 				  action->ocg_state.state_list);
437 		pgd_table_add_property_with_custom_widget (GTK_TABLE (table), NULL, button, &row);
438 		gtk_widget_show (button);
439 	}
440 		break;
441 	default:
442 		g_assert_not_reached ();
443 	}
444 
445 	gtk_container_add (GTK_CONTAINER (alignment), table);
446 	gtk_widget_show (table);
447 }
448 
449 gchar *
pgd_format_date(time_t utime)450 pgd_format_date (time_t utime)
451 {
452 	time_t time = (time_t) utime;
453 	char s[256];
454 	const char *fmt_hack = "%c";
455 	size_t len;
456 #ifdef HAVE_LOCALTIME_R
457 	struct tm t;
458 	if (time == 0 || !localtime_r (&time, &t)) return NULL;
459 	len = strftime (s, sizeof (s), fmt_hack, &t);
460 #else
461 	struct tm *t;
462 	if (time == 0 || !(t = localtime (&time)) ) return NULL;
463 	len = strftime (s, sizeof (s), fmt_hack, t);
464 #endif
465 
466 	if (len == 0 || s[0] == '\0') return NULL;
467 
468 	return g_locale_to_utf8 (s, -1, NULL, NULL, NULL);
469 }
470 
471 GtkWidget *
pgd_movie_view_new(void)472 pgd_movie_view_new (void)
473 {
474 	GtkWidget  *frame, *label;
475 
476 	frame = gtk_frame_new (NULL);
477 	gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_NONE);
478 	label = gtk_label_new (NULL);
479 	gtk_label_set_markup (GTK_LABEL (label), "<b>Movie Properties</b>");
480 	gtk_frame_set_label_widget (GTK_FRAME (frame), label);
481 	gtk_widget_show (label);
482 
483 	return frame;
484 }
485 
486 static void
pgd_movie_view_play_movie(GtkWidget * button,PopplerMovie * movie)487 pgd_movie_view_play_movie (GtkWidget    *button,
488 			   PopplerMovie *movie)
489 {
490 	const gchar *filename;
491 	GFile       *file;
492 	gchar       *uri;
493 
494 	filename = poppler_movie_get_filename (movie);
495 	if (g_path_is_absolute (filename)) {
496 		file = g_file_new_for_path (filename);
497 	} else if (g_strrstr (filename, "://")) {
498 		file = g_file_new_for_uri (filename);
499 	} else {
500 		gchar *cwd;
501 		gchar *path;
502 
503 		// FIXME: relative to doc uri, not cwd
504 		cwd = g_get_current_dir ();
505 		path = g_build_filename (cwd, filename, NULL);
506 		g_free (cwd);
507 
508 		file = g_file_new_for_path (path);
509 		g_free (path);
510 	}
511 
512 	uri = g_file_get_uri (file);
513 	g_object_unref (file);
514 	if (uri) {
515 		gtk_show_uri (gtk_widget_get_screen (button),
516 			      uri, GDK_CURRENT_TIME, NULL);
517 		g_free (uri);
518 	}
519 }
520 
521 void
pgd_movie_view_set_movie(GtkWidget * movie_view,PopplerMovie * movie)522 pgd_movie_view_set_movie (GtkWidget    *movie_view,
523 			  PopplerMovie *movie)
524 {
525 	GtkWidget  *alignment;
526 	GtkWidget  *table;
527 	GtkWidget  *button;
528 	gint        row = 0;
529 
530 	alignment = gtk_bin_get_child (GTK_BIN (movie_view));
531 	if (alignment) {
532 		gtk_container_remove (GTK_CONTAINER (movie_view), alignment);
533 	}
534 
535 	alignment = gtk_alignment_new (0.5, 0.5, 1, 1);
536 	gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 5, 5, 12, 5);
537 	gtk_container_add (GTK_CONTAINER (movie_view), alignment);
538 	gtk_widget_show (alignment);
539 
540 	if (!movie)
541 		return;
542 
543 	table = gtk_table_new (10, 2, FALSE);
544 	gtk_table_set_col_spacings (GTK_TABLE (table), 6);
545 	gtk_table_set_row_spacings (GTK_TABLE (table), 6);
546 
547 	pgd_table_add_property (GTK_TABLE (table), "<b>Filename:</b>", poppler_movie_get_filename (movie), &row);
548 	pgd_table_add_property (GTK_TABLE (table), "<b>Need Poster:</b>", poppler_movie_need_poster (movie) ? "Yes" : "No", &row);
549 	pgd_table_add_property (GTK_TABLE (table), "<b>Show Controls:</b>", poppler_movie_show_controls (movie) ? "Yes" : "No", &row);
550 
551 	button = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PLAY);
552 	g_signal_connect (button, "clicked",
553 			  G_CALLBACK (pgd_movie_view_play_movie),
554 			  movie);
555 	pgd_table_add_property_with_custom_widget (GTK_TABLE (table), NULL, button, &row);
556 	gtk_widget_show (button);
557 
558 	gtk_container_add (GTK_CONTAINER (alignment), table);
559 	gtk_widget_show (table);
560 }
561