OSDN Git Service

LinGui: use a smaller font in audio settings controls
[handbrake-jp/handbrake-jp-git.git] / gtk / src / preview.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3  * preview.c
4  * Copyright (C) John Stebbins 2008 <stebbins@stebbins>
5  * 
6  * preview.c is free software.
7  * 
8  * You may redistribute it and/or modify it under the terms of the
9  * GNU General Public License, as published by the Free Software
10  * Foundation; either version 2 of the License, or (at your option)
11  * any later version.
12  * 
13  */
14 #include <unistd.h>
15 #include <glib.h>
16 #include <glib/gstdio.h>
17 #include <glib-object.h>
18 #include <gtk/gtk.h>
19
20 #if !defined(_WIN32)
21 #include <gdk/gdkx.h>
22 #endif
23
24 #if defined(_ENABLE_GST)
25 #include <gst/gst.h>
26 #include <gst/interfaces/xoverlay.h>
27 #include <gst/video/video.h>
28 #include <gst/pbutils/missing-plugins.h>
29 #endif
30
31 #include "settings.h"
32 #include "presets.h"
33 #include "callbacks.h"
34 #include "hb-backend.h"
35 #include "preview.h"
36 #include "values.h"
37 #include "hb.h"
38
39 #define PREVIEW_STATE_IMAGE 0
40 #define PREVIEW_STATE_LIVE 1
41
42 struct preview_s
43 {
44 #if defined(_ENABLE_GST)
45         GstElement *play;
46         gulong xid;
47 #endif
48         gint64 len;
49         gint64 pos;
50         gboolean seek_lock;
51         gboolean progress_lock;
52         gint width;
53         gint height;
54         GtkWidget *view;
55         GdkPixbuf *pix;
56         gint button_width;
57         gint button_height;
58         gint frame;
59         gint state;
60         gboolean pause;
61         gboolean encoded[10];
62         gint encode_frame;
63         gint live_id;
64         gchar *current;
65 };
66
67 #if defined(_ENABLE_GST)
68 G_MODULE_EXPORT gboolean live_preview_cb(GstBus *bus, GstMessage *msg, gpointer data);
69 static GstBusSyncReply create_window(GstBus *bus, GstMessage *msg, 
70                                 gpointer data);
71 #endif
72
73 G_MODULE_EXPORT gboolean preview_expose_cb(GtkWidget *widget, GdkEventExpose *event, 
74                                 signal_user_data_t *ud);
75
76 void
77 ghb_screen_par(signal_user_data_t *ud, gint *par_n, gint *par_d)
78 {
79 #if defined(_ENABLE_GST)
80         GValue disp_par = {0,};
81         GstElement *xover;
82         GObjectClass *klass;
83         GParamSpec *pspec;
84
85         g_value_init(&disp_par, GST_TYPE_FRACTION);
86         gst_value_set_fraction(&disp_par, 1, 1);
87         g_object_get(ud->preview->play, "video-sink", &xover, NULL);
88         if (xover == NULL)
89                 goto fail;
90
91         klass = G_OBJECT_GET_CLASS(xover);
92         if (klass == NULL)
93                 goto fail;
94
95         pspec = g_object_class_find_property(klass, "pixel-aspect_ratio");
96         if (pspec)
97         {
98                 GValue par_prop = {0,};
99
100                 g_value_init(&par_prop, pspec->value_type);
101                 g_object_get_property(G_OBJECT(xover), "pixel-aspect-ratio",
102                                                                 &par_prop);
103                 if (!g_value_transform(&par_prop, &disp_par))
104                 {
105                         g_warning("transform failed");
106                         gst_value_set_fraction(&disp_par, 1, 1);
107                 }
108                 g_value_unset(&par_prop);
109         }
110         *par_n = gst_value_get_fraction_numerator(&disp_par);
111         *par_d = gst_value_get_fraction_denominator(&disp_par);
112         g_value_unset(&disp_par);
113         return;
114
115 fail:
116         *par_n = 1;
117         *par_d = 1;
118 #else
119         *par_n = 1;
120         *par_d = 1;
121 #endif
122 }
123
124 void
125 ghb_par_scale(signal_user_data_t *ud, gint *width, gint *height, gint par_n, gint par_d)
126 {
127         gint disp_par_n, disp_par_d;
128         gint64 num, den;
129
130         ghb_screen_par(ud, &disp_par_n, &disp_par_d);
131         if (disp_par_n < 1) disp_par_n = 1;
132         if (disp_par_d < 1) disp_par_d = 1;
133         num = par_n * disp_par_d;
134         den = par_d * disp_par_n;
135
136         if (par_n > par_d)
137                 *width = *width * num / den;
138         else
139                 *height = *height * den / num;
140 }
141
142 void
143 ghb_preview_init(signal_user_data_t *ud)
144 {
145         ud->preview = g_malloc0(sizeof(preview_t));
146         ud->preview->view = GHB_WIDGET(ud->builder, "preview_image");
147         gtk_widget_realize(ud->preview->view);
148         g_signal_connect(G_OBJECT(ud->preview->view), "expose_event",
149                                         G_CALLBACK(preview_expose_cb), ud);
150
151         ud->preview->pause = TRUE;
152         ud->preview->encode_frame = -1;
153         ud->preview->live_id = -1;
154
155 #if defined(_ENABLE_GST)
156         GstBus *bus;
157         GstElement *xover;
158
159 #if !defined(_WIN32)
160         ud->preview->xid = GDK_DRAWABLE_XID(ud->preview->view->window);
161 #else
162         ud->preview->xid = GDK_WINDOW_HWND(ud->preview->view->window);
163 #endif
164         ud->preview->play = gst_element_factory_make("playbin", "play");
165         //xover = gst_element_factory_make("xvimagesink", "xover");
166         //xover = gst_element_factory_make("ximagesink", "xover");
167         xover = gst_element_factory_make("gconfvideosink", "xover");
168         if (xover == NULL)
169         {
170                 GtkWidget *widget = GHB_WIDGET(ud->builder, "live_preview_box");
171                 gtk_widget_hide (widget);
172                 widget = GHB_WIDGET(ud->builder, "live_preview_duration_box");
173                 gtk_widget_hide (widget);
174                 return;
175         }
176
177         g_object_set(G_OBJECT(ud->preview->play), "video-sink", xover, NULL);
178         g_object_set(ud->preview->play, "subtitle-font-desc", 
179                                 "sans bold 20", NULL);
180         //g_object_set(G_OBJECT(xover), "force-aspect-ratio", TRUE, NULL);
181
182         bus = gst_pipeline_get_bus(GST_PIPELINE(ud->preview->play));
183         gst_bus_add_watch(bus, live_preview_cb, ud);
184         gst_bus_set_sync_handler(bus, create_window, ud->preview);
185         gst_object_unref(bus);
186 #else
187         GtkWidget *widget = GHB_WIDGET(ud->builder, "live_preview_box");
188         gtk_widget_hide (widget);
189         widget = GHB_WIDGET(ud->builder, "live_preview_duration_box");
190         gtk_widget_hide (widget);
191 #endif
192 }
193
194 void
195 ghb_preview_cleanup(signal_user_data_t *ud)
196 {
197         if (ud->preview->current)
198         {
199                 ud->preview->current = NULL;
200                 g_free(ud->preview->current);
201         }
202 }
203
204 #if defined(_ENABLE_GST)
205 static GstBusSyncReply
206 create_window(GstBus *bus, GstMessage *msg, gpointer data)
207 {
208         preview_t *preview = (preview_t*)data;
209
210         switch (GST_MESSAGE_TYPE(msg))
211         {
212         case GST_MESSAGE_ELEMENT:
213         {
214                 if (!gst_structure_has_name(msg->structure, "prepare-xwindow-id"))
215                         return GST_BUS_PASS;
216 #if !defined(_WIN32)
217                 gst_x_overlay_set_xwindow_id(
218                         GST_X_OVERLAY(GST_MESSAGE_SRC(msg)), preview->xid);
219 #else
220                 gst_directdraw_sink_set_window_id(
221                         GST_X_OVERLAY(GST_MESSAGE_SRC(msg)), preview->xid);
222 #endif
223                 gst_message_unref(msg);
224                 return GST_BUS_DROP;
225         } break;
226
227         default:
228         {
229         } break;
230         }
231         return GST_BUS_PASS;
232 }
233
234 static GList *
235 get_stream_info_objects_for_type (GstElement *play, const gchar *typestr)
236 {
237         GValueArray *info_arr = NULL;
238         GList *ret = NULL;
239         guint ii;
240
241         if (play == NULL)
242                 return NULL;
243
244         g_object_get(play, "stream-info-value-array", &info_arr, NULL);
245         if (info_arr == NULL)
246                 return NULL;
247
248         for (ii = 0; ii < info_arr->n_values; ++ii) 
249         {
250                 GObject *info_obj;
251                 GValue *val;
252
253                 val = g_value_array_get_nth(info_arr, ii);
254                 info_obj = g_value_get_object(val);
255                 if (info_obj) 
256                 {
257                         GParamSpec *pspec;
258                         GEnumValue *value;
259                         gint type = -1;
260
261                         g_object_get(info_obj, "type", &type, NULL);
262                         pspec = g_object_class_find_property(
263                                                 G_OBJECT_GET_CLASS (info_obj), "type");
264                         value = g_enum_get_value(
265                                                 G_PARAM_SPEC_ENUM (pspec)->enum_class, type);
266                         if (value) 
267                         {
268                                 if (g_ascii_strcasecmp (value->value_nick, typestr) == 0 ||
269                                         g_ascii_strcasecmp (value->value_name, typestr) == 0) 
270                                 {
271                                         ret = g_list_prepend (ret, g_object_ref (info_obj));
272                                 }
273                         }
274                 }
275         }
276         g_value_array_free (info_arr);
277         return g_list_reverse (ret);
278 }
279
280 static void
281 caps_set(GstCaps *caps, signal_user_data_t *ud)
282 {
283         GstStructure *ss;
284
285         ss = gst_caps_get_structure(caps, 0);
286         if (ss)
287         {
288                 gint fps_n, fps_d, width, height;
289                 guint num, den, par_n, par_d;
290                 gint disp_par_n, disp_par_d;
291                 const GValue *par;
292
293                 gst_structure_get_fraction(ss, "framerate", &fps_n, &fps_d);
294                 gst_structure_get_int(ss, "width", &width);
295                 gst_structure_get_int(ss, "height", &height);
296                 par = gst_structure_get_value(ss, "pixel-aspect-ratio");
297                 par_n = gst_value_get_fraction_numerator(par);
298                 par_d = gst_value_get_fraction_denominator(par);
299
300                 ghb_screen_par(ud, &disp_par_n, &disp_par_d);
301                 gst_video_calculate_display_ratio(
302                         &num, &den, width, height, par_n, par_d, disp_par_n, disp_par_d);
303
304                 if (par_n > par_d)
305                         width = gst_util_uint64_scale_int(height, num, den);
306                 else
307                         height = gst_util_uint64_scale_int(width, den, num);
308
309                 if (ghb_settings_get_boolean(ud->settings, "reduce_hd_preview"))
310                 {
311                         GdkScreen *ss;
312                         gint s_w, s_h;
313
314                         ss = gdk_screen_get_default();
315                         s_w = gdk_screen_get_width(ss);
316                         s_h = gdk_screen_get_height(ss);
317
318                         if (width > s_w * 80 / 100)
319                         {
320                                 width = s_w * 80 / 100;
321                                 height = gst_util_uint64_scale_int(width, den, num);
322                         }
323                         if (height > s_h * 80 / 100)
324                         {
325                                 height = s_h * 80 / 100;
326                                 width = gst_util_uint64_scale_int(height, num, den);
327                         }
328                 }
329                 
330                 if (width != ud->preview->width || height != ud->preview->height)
331                 {
332                         gtk_widget_set_size_request(ud->preview->view, width, height);
333                         ud->preview->width = width;
334                         ud->preview->height = height;
335                 }
336         }
337 }
338
339 static void
340 update_stream_info(signal_user_data_t *ud)
341 {
342         GList *vstreams, *ll;
343         GstPad *vpad = NULL;
344
345         vstreams = get_stream_info_objects_for_type(ud->preview->play, "video");
346         if (vstreams)
347         {
348                 for (ll = vstreams; vpad == NULL && ll != NULL; ll = ll->next)
349                 {
350                         g_object_get(ll->data, "object", &vpad, NULL);
351                 }
352         }
353         if (vpad)
354         {
355                 GstCaps *caps;
356
357                 caps = gst_pad_get_negotiated_caps(vpad);
358                 if (caps)
359                 {
360                         caps_set(caps, ud);
361                         gst_caps_unref(caps);
362                 }
363                 //g_signal_connect(vpad, "notify::caps", G_CALLBACK(caps_set_cb), preview);
364                 gst_object_unref(vpad);
365         }
366         g_list_foreach(vstreams, (GFunc)g_object_unref, NULL);
367         g_list_free(vstreams);
368 }
369
370 G_MODULE_EXPORT gboolean
371 live_preview_cb(GstBus *bus, GstMessage *msg, gpointer data)
372 {
373         signal_user_data_t *ud = (signal_user_data_t*)data;
374
375         switch (GST_MESSAGE_TYPE(msg))
376         {
377         case GST_MESSAGE_ERROR:
378         {
379                 GError *err;
380                 gchar *debug;
381
382                 gst_message_parse_error(msg, &err, &debug);
383                 g_warning("Gstreamer Error: %s", err->message);
384                 g_error_free(err);
385                 g_free(debug);
386         } break;
387
388         case GST_MESSAGE_ELEMENT:
389         {
390                 if (gst_is_missing_plugin_message(msg))
391                 {
392                         gst_element_set_state(ud->preview->play, GST_STATE_PAUSED);
393                         gchar *message, *desc;
394                         desc = gst_missing_plugin_message_get_description(msg);
395                         message = g_strdup_printf(
396                                                 "Missing GStreamer plugin\n"
397                                                 "Audio or Video may not play as expected\n\n%s",
398                                                 desc);
399                         ghb_message_dialog(GTK_MESSAGE_WARNING, message, "Ok", NULL);
400                         g_free(message);
401                         gst_element_set_state(ud->preview->play, GST_STATE_PLAYING);
402                 }
403         } break;
404
405         case GST_MESSAGE_STATE_CHANGED:
406         {
407                 GstState state, pending;
408                 gst_element_get_state(ud->preview->play, &state, &pending, 0);
409                 if (state == GST_STATE_PAUSED || state == GST_STATE_PLAYING)
410                 {
411                         update_stream_info(ud);
412                 }
413         } break;
414
415         case GST_MESSAGE_EOS:
416         {
417                 // Done
418                 GtkImage *img;
419
420                 img = GTK_IMAGE(GHB_WIDGET(ud->builder, "live_preview_play_image"));
421                 gtk_image_set_from_stock(img, "gtk-media-play", GTK_ICON_SIZE_BUTTON);
422                 gst_element_set_state(ud->preview->play, GST_STATE_PAUSED);
423                 ud->preview->pause = TRUE;
424                 gst_element_seek(ud->preview->play, 1.0,
425                         GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
426                         GST_SEEK_TYPE_SET, 0,
427                         GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
428         } break;
429
430         default:
431         {
432                 // Ignore
433         }
434         }
435         return TRUE;
436 }
437
438 void
439 live_preview_start(signal_user_data_t *ud)
440 {
441         GtkImage *img;
442         gchar *uri;
443
444         img = GTK_IMAGE(GHB_WIDGET(ud->builder, "live_preview_play_image"));
445         if (!ud->preview->encoded[ud->preview->frame])
446         {
447                 gtk_image_set_from_stock(img, "gtk-media-play", GTK_ICON_SIZE_BUTTON);
448                 gst_element_set_state(ud->preview->play, GST_STATE_NULL);
449                 ud->preview->pause = TRUE;
450                 return;
451         }
452
453         uri = g_strdup_printf("file://%s", ud->preview->current);
454         gtk_image_set_from_stock(img, "gtk-media-pause", GTK_ICON_SIZE_BUTTON);
455         ud->preview->state = PREVIEW_STATE_LIVE;
456         g_object_set(G_OBJECT(ud->preview->play), "uri", uri, NULL);
457         gst_element_set_state(ud->preview->play, GST_STATE_PLAYING);
458         ud->preview->pause = FALSE;
459         g_free(uri);
460 }
461
462 void
463 live_preview_pause(signal_user_data_t *ud)
464 {
465         GtkImage *img;
466
467         img = GTK_IMAGE(GHB_WIDGET(ud->builder, "live_preview_play_image"));
468         gtk_image_set_from_stock(img, "gtk-media-play", GTK_ICON_SIZE_BUTTON);
469         gst_element_set_state(ud->preview->play, GST_STATE_PAUSED);
470         ud->preview->pause = TRUE;
471 }
472 #endif
473
474 void
475 live_preview_stop(signal_user_data_t *ud)
476 {
477         GtkImage *img;
478         GtkRange *progress;
479
480         img = GTK_IMAGE(GHB_WIDGET(ud->builder, "live_preview_play_image"));
481         gtk_image_set_from_stock(img, "gtk-media-play", GTK_ICON_SIZE_BUTTON);
482 #if defined(_ENABLE_GST)
483         gst_element_set_state(ud->preview->play, GST_STATE_NULL);
484 #endif
485         ud->preview->pause = TRUE;
486         ud->preview->state = PREVIEW_STATE_IMAGE;
487
488         progress = GTK_RANGE(GHB_WIDGET(ud->builder, "live_preview_progress"));
489         gtk_range_set_value(progress, 0);
490 }
491
492 void
493 ghb_live_reset(signal_user_data_t *ud)
494 {
495         gboolean encoded;
496
497         if (ud->preview->live_id >= 0)
498         {
499                 ghb_stop_live_encode();
500         }
501         ud->preview->live_id = -1;
502         ud->preview->encode_frame = -1;
503         if (!ud->preview->pause)
504                 live_preview_stop(ud);
505         if (ud->preview->current)
506         {
507                 g_free(ud->preview->current);
508                 ud->preview->current = NULL;
509         }
510         encoded = ud->preview->encoded[ud->preview->frame];
511         memset(ud->preview->encoded, 0, sizeof(gboolean) * 10);
512         if (encoded)
513                 ghb_set_preview_image(ud);
514 }
515
516 extern void hb_get_tempory_directory(hb_handle_t *h, char path[512]);
517
518 G_MODULE_EXPORT void
519 live_preview_start_cb(GtkWidget *xwidget, signal_user_data_t *ud)
520 {
521         gchar *tmp_dir;
522         gchar *name;
523         gint frame = ud->preview->frame;
524
525         tmp_dir = ghb_get_tmp_dir();
526         name = g_strdup_printf("%s/live%02d", tmp_dir, ud->preview->frame);
527         if (ud->preview->current)
528                 g_free(ud->preview->current);
529         ud->preview->current = name;
530
531         if (ud->preview->encoded[frame] &&
532                 g_file_test(name, G_FILE_TEST_IS_REGULAR))
533         {
534 #if defined(_ENABLE_GST)
535                 if (ud->preview->pause)
536                         live_preview_start(ud);
537                 else
538                         live_preview_pause(ud);
539 #endif
540         }
541         else
542         {
543                 GValue *js;
544
545                 ud->preview->encode_frame = frame;
546                 js = ghb_value_dup(ud->settings);
547                 ghb_settings_set_string(js, "destination", name);
548                 ghb_settings_set_int(js, "start_frame", ud->preview->frame);
549                 ud->preview->live_id = 0;
550                 ghb_add_live_job(js, ud->preview->live_id);
551                 ghb_start_live_encode();
552                 ghb_value_free(js);
553         }
554 }
555
556 void
557 ghb_live_encode_done(signal_user_data_t *ud, gboolean success)
558 {
559         GtkWidget *widget;
560         GtkWidget *prog;
561
562         ud->preview->live_id = -1;
563         prog = GHB_WIDGET(ud->builder, "live_encode_progress");
564         if (success && 
565                 ud->preview->encode_frame == ud->preview->frame)
566         {
567                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(prog), "Done");
568                 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(prog), 1);
569                 ud->preview->encoded[ud->preview->encode_frame] = TRUE;
570 #if defined(_ENABLE_GST)
571                 live_preview_start(ud);
572 #endif
573                 widget = GHB_WIDGET(ud->builder, "live_progress_box");
574                 gtk_widget_hide (widget);
575                 widget = GHB_WIDGET(ud->builder, "live_preview_progress");
576                 gtk_widget_show (widget);
577         }
578         else
579         {
580                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(prog), "");
581                 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(prog), 0);
582                 ud->preview->encoded[ud->preview->encode_frame] = FALSE;
583         }
584 }
585
586 #if defined(_ENABLE_GST)
587 G_MODULE_EXPORT gboolean
588 unlock_progress_cb(signal_user_data_t *ud)
589 {
590         ud->preview->progress_lock = FALSE;
591         // This function is initiated by g_idle_add.  Must return false
592         // so that it is not called again
593         return FALSE;
594 }
595 #endif
596
597 void
598 ghb_live_preview_progress(signal_user_data_t *ud)
599 {
600 #if defined(_ENABLE_GST)
601         GstFormat fmt = GST_FORMAT_TIME;
602         gint64 len = -1, pos = -1;
603
604         if (ud->preview->state != PREVIEW_STATE_LIVE || ud->preview->seek_lock)
605                 return;
606
607         ud->preview->progress_lock = TRUE;
608         if (gst_element_query_duration(ud->preview->play, &fmt, &len))
609         {
610                 if (len != -1 && fmt == GST_FORMAT_TIME)
611                 {
612                         ud->preview->len = len / GST_MSECOND;
613                 }
614         }
615         if (gst_element_query_position(ud->preview->play, &fmt, &pos))
616         {
617                 if (pos != -1 && fmt == GST_FORMAT_TIME)
618                 {
619                         ud->preview->pos = pos / GST_MSECOND;
620                 }
621         }
622         if (ud->preview->len > 0)
623         {
624                 GtkRange *progress;
625                 gdouble percent;
626
627                 percent = (gdouble)ud->preview->pos * 100 / ud->preview->len;
628                 progress = GTK_RANGE(GHB_WIDGET(ud->builder, "live_preview_progress"));
629                 gtk_range_set_value(progress, percent);
630         }
631         g_idle_add((GSourceFunc)unlock_progress_cb, ud);
632 #endif
633 }
634
635 #if defined(_ENABLE_GST)
636 G_MODULE_EXPORT gboolean
637 unlock_seek_cb(signal_user_data_t *ud)
638 {
639         ud->preview->seek_lock = FALSE;
640         // This function is initiated by g_idle_add.  Must return false
641         // so that it is not called again
642         return FALSE;
643 }
644 #endif
645
646 G_MODULE_EXPORT void
647 live_preview_seek_cb(GtkWidget *widget, signal_user_data_t *ud)
648 {
649 #if defined(_ENABLE_GST)
650         gdouble dval;
651         gint64 pos;
652
653         if (ud->preview->progress_lock)
654                 return;
655
656         ud->preview->seek_lock = TRUE;
657         dval = gtk_range_get_value(GTK_RANGE(widget));
658         pos = ((ud->preview->len * dval) / 100) * GST_MSECOND;
659         gst_element_seek(ud->preview->play, 1.0,
660                 GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
661                 GST_SEEK_TYPE_SET, pos,
662                 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
663         g_idle_add((GSourceFunc)unlock_seek_cb, ud);
664 #endif
665 }
666
667 void
668 ghb_set_preview_image(signal_user_data_t *ud)
669 {
670         GtkWidget *widget;
671         gint preview_width, preview_height, target_height, width, height;
672
673         g_debug("set_preview_button_image ()");
674         gint titleindex;
675
676         live_preview_stop(ud);
677
678         titleindex = ghb_settings_combo_int(ud->settings, "title");
679         if (titleindex < 0) return;
680         widget = GHB_WIDGET (ud->builder, "preview_frame");
681         ud->preview->frame = ghb_widget_int(widget) - 1;
682         if (ud->preview->encoded[ud->preview->frame])
683         {
684                 widget = GHB_WIDGET(ud->builder, "live_progress_box");
685                 gtk_widget_hide (widget);
686                 widget = GHB_WIDGET(ud->builder, "live_preview_progress");
687                 gtk_widget_show (widget);
688         }
689         else
690         {
691                 widget = GHB_WIDGET(ud->builder, "live_preview_progress");
692                 gtk_widget_hide (widget);
693                 widget = GHB_WIDGET(ud->builder, "live_progress_box");
694                 gtk_widget_show (widget);
695                 widget = GHB_WIDGET(ud->builder, "live_encode_progress");
696                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(widget), "");
697                 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(widget), 0);
698         }
699         if (ud->preview->pix != NULL)
700                 g_object_unref(ud->preview->pix);
701
702         ud->preview->pix = 
703                 ghb_get_preview_image(titleindex, ud->preview->frame, 
704                                                                 ud, &width, &height);
705         if (ud->preview->pix == NULL) return;
706         preview_width = gdk_pixbuf_get_width(ud->preview->pix);
707         preview_height = gdk_pixbuf_get_height(ud->preview->pix);
708         widget = GHB_WIDGET (ud->builder, "preview_image");
709         if (preview_width != ud->preview->width || 
710                 preview_height != ud->preview->height)
711         {
712                 gtk_widget_set_size_request(widget, preview_width, preview_height);
713                 ud->preview->width = preview_width;
714                 ud->preview->height = preview_height;
715         }
716         gdk_draw_pixbuf(
717                 widget->window, NULL, ud->preview->pix, 0, 0, 0, 0,
718                 -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
719
720         gchar *text = g_strdup_printf("%d x %d", width, height);
721         widget = GHB_WIDGET (ud->builder, "preview_dims");
722         gtk_label_set_text(GTK_LABEL(widget), text);
723         g_free(text);
724         
725         g_debug("preview %d x %d", preview_width, preview_height);
726         target_height = MIN(ud->preview->button_height, 200);
727         height = target_height;
728         width = preview_width * height / preview_height;
729         if (width > 400)
730         {
731                 width = 400;
732                 height = preview_height * width / preview_width;
733         }
734
735         if ((height >= 16) && (width >= 16))
736         {
737                 GdkPixbuf *scaled_preview;
738                 scaled_preview = gdk_pixbuf_scale_simple (ud->preview->pix, width, 
739                                                                                                 height, GDK_INTERP_NEAREST);
740                 if (scaled_preview != NULL)
741                 {
742                         widget = GHB_WIDGET (ud->builder, "preview_button_image");
743                         gtk_image_set_from_pixbuf(GTK_IMAGE(widget), scaled_preview);
744                         g_object_unref (scaled_preview);
745                 }
746         }
747 }
748
749 #if defined(_ENABLE_GST)
750 G_MODULE_EXPORT gboolean
751 delayed_expose_cb(signal_user_data_t *ud)
752 {
753         GstElement *vsink;
754         GstXOverlay *xover;
755
756         g_object_get(ud->preview->play, "video-sink", &vsink, NULL);
757         if (vsink == NULL)
758                 return FALSE;
759
760         if (GST_IS_BIN(vsink))
761                 xover = GST_X_OVERLAY(gst_bin_get_by_interface(
762                                                                 GST_BIN(vsink), GST_TYPE_X_OVERLAY));
763         else
764                 xover = GST_X_OVERLAY(vsink);
765         gst_x_overlay_expose(xover);
766         // This function is initiated by g_idle_add.  Must return false
767         // so that it is not called again
768         return FALSE;
769 }
770 #endif
771
772 G_MODULE_EXPORT gboolean
773 preview_expose_cb(
774         GtkWidget *widget, 
775         GdkEventExpose *event, 
776         signal_user_data_t *ud)
777 {
778 #if defined(_ENABLE_GST)
779         if (ud->preview->state == PREVIEW_STATE_LIVE)
780         {
781                 if (GST_STATE(ud->preview->play) >= GST_STATE_PAUSED)
782                 {
783                         GstElement *vsink;
784                         GstXOverlay *xover;
785
786                         g_object_get(ud->preview->play, "video-sink", &vsink, NULL);
787                         if (GST_IS_BIN(vsink))
788                                 xover = GST_X_OVERLAY(gst_bin_get_by_interface(
789                                                                                 GST_BIN(vsink), GST_TYPE_X_OVERLAY));
790                         else
791                                 xover = GST_X_OVERLAY(vsink);
792                         gst_x_overlay_expose(xover);
793                         // For some reason, the exposed region doesn't always get
794                         // cleaned up here. But a delayed gst_x_overlay_expose()
795                         // takes care of it.
796                         g_idle_add((GSourceFunc)delayed_expose_cb, ud);
797                         return FALSE;
798                 }
799                 return TRUE;
800         }
801 #endif
802
803         if (ud->preview->pix != NULL)
804         {
805                 gdk_draw_pixbuf(
806                         widget->window, NULL, ud->preview->pix, 0, 0, 0, 0,
807                         -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
808         }
809         return TRUE;
810 }
811
812 G_MODULE_EXPORT void
813 preview_button_size_allocate_cb(GtkWidget *widget, GtkAllocation *allocation, signal_user_data_t *ud)
814 {
815         g_debug("allocate %d x %d", allocation->width, allocation->height);
816         if (ud->preview->button_width == allocation->width &&
817                 ud->preview->button_height == allocation->height)
818         {
819                 // Nothing to do. Bug out.
820                 g_debug("nothing to do");
821                 return;
822         }
823         g_debug("prev allocate %d x %d", ud->preview->button_width, 
824                         ud->preview->button_height);
825         ud->preview->button_width = allocation->width;
826         ud->preview->button_height = allocation->height;
827         ghb_set_preview_image(ud);
828 }
829
830 static void
831 set_visible(GtkWidget *widget, gboolean visible)
832 {
833         if (visible)
834         {
835                 gtk_widget_show_now(widget);
836         }
837         else
838         {
839                 gtk_widget_hide(widget);
840         }
841 }
842
843 void
844 ghb_preview_set_visible(signal_user_data_t *ud)
845 {
846         gint titleindex;
847         GtkWidget *widget;
848         gboolean settings_active;
849
850         settings_active = ghb_settings_get_boolean(ud->settings, "show_picture");
851         widget = GHB_WIDGET (ud->builder, "preview_window");
852         titleindex = ghb_settings_combo_int(ud->settings, "title");
853         if (settings_active && titleindex >= 0)
854         {
855                 gint x, y;
856                 x = ghb_settings_get_int(ud->settings, "preview_x");
857                 y = ghb_settings_get_int(ud->settings, "preview_y");
858                 if (x >= 0 && y >= 0)
859                         gtk_window_move(GTK_WINDOW(widget), x, y);
860                 set_visible(widget, 
861                                         ghb_settings_get_boolean(ud->settings, "show_preview"));
862         }
863         else
864         {
865                 set_visible(widget, FALSE);
866         }
867 }
868
869 G_MODULE_EXPORT void
870 preview_button_clicked_cb(GtkWidget *xwidget, signal_user_data_t *ud)
871 {
872         g_debug("preview_button_clicked_cb()");
873         ghb_widget_to_setting (ud->settings, xwidget);
874         ghb_preview_set_visible(ud);
875         ghb_check_dependency(ud, xwidget, NULL);
876         const gchar *name = gtk_widget_get_name(xwidget);
877         ghb_pref_save(ud->settings, name);
878 }
879
880 G_MODULE_EXPORT void
881 picture_settings_clicked_cb(GtkWidget *xwidget, signal_user_data_t *ud)
882 {
883         GtkWidget *widget;
884         gboolean active, hide_settings;
885         gint x, y;
886
887         g_debug("picture_settings_clicked_cb()");
888         ghb_widget_to_setting (ud->settings, xwidget);
889
890         hide_settings = ghb_settings_get_boolean(ud->settings, "hide_settings");
891
892         active = ghb_settings_get_boolean(ud->settings, "show_picture");
893         widget = GHB_WIDGET (ud->builder, "settings_window");
894         x = ghb_settings_get_int(ud->settings, "settings_x");
895         y = ghb_settings_get_int(ud->settings, "settings_y");
896         if (x >= 0 && y >= 0)
897                 gtk_window_move(GTK_WINDOW(widget), x, y);
898         set_visible(widget, active && !hide_settings);
899         ghb_preview_set_visible(ud);
900 }
901
902 G_MODULE_EXPORT void
903 picture_settings_alt_clicked_cb(GtkWidget *xwidget, signal_user_data_t *ud)
904 {
905         GtkWidget *toggle;
906         gboolean active;
907
908         g_debug("picture_settings_alt_clicked_cb()");
909         toggle = GHB_WIDGET (ud->builder, "show_picture");
910         active = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle));
911         gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toggle), !active);
912 }
913
914 static gboolean
915 go_full(signal_user_data_t *ud)
916 {
917         GtkWindow *window;
918         window = GTK_WINDOW(GHB_WIDGET (ud->builder, "preview_window"));
919         gtk_window_fullscreen(window);
920         ghb_set_preview_image(ud);
921         return FALSE;
922 }
923
924 G_MODULE_EXPORT void
925 fullscreen_clicked_cb(GtkWidget *toggle, signal_user_data_t *ud)
926 {
927         gboolean active;
928         GtkWindow *window;
929
930         g_debug("fullscreen_clicked_cb()");
931         ghb_widget_to_setting (ud->settings, toggle);
932         ghb_check_dependency(ud, toggle, NULL);
933         const gchar *name = gtk_widget_get_name(toggle);
934         ghb_pref_save(ud->settings, name);
935
936         window = GTK_WINDOW(GHB_WIDGET (ud->builder, "preview_window"));
937         active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle));
938         if (active)
939         {
940                 gtk_window_set_resizable(window, TRUE);
941                 gtk_button_set_label(GTK_BUTTON(toggle), "Windowed");
942                 // Changing resizable property doesn't take effect immediately
943                 // need to delay fullscreen till after this callback returns
944                 // to mainloop
945                 g_idle_add((GSourceFunc)go_full, ud);
946         }
947         else
948         {
949                 gtk_window_unfullscreen(window);
950                 gtk_window_set_resizable(window, FALSE);
951                 gtk_button_set_label(GTK_BUTTON(toggle), "Fullscreen");
952                 ghb_set_preview_image(ud);
953         }
954 }
955
956 G_MODULE_EXPORT void
957 picture_settings_alt2_clicked_cb(GtkWidget *xwidget, signal_user_data_t *ud)
958 {
959         GtkWidget *toggle;
960         gboolean active;
961         GtkWidget *window;
962
963         g_debug("picture_settings_alt2_clicked_cb()");
964         ghb_widget_to_setting (ud->settings, xwidget);
965         active = ghb_settings_get_boolean(ud->settings, "hide_settings");
966
967         toggle = GHB_WIDGET (ud->builder, "hide_settings");
968         window = GHB_WIDGET(ud->builder, "settings_window");
969         if (!active)
970         {
971                 gtk_button_set_label(GTK_BUTTON(toggle), "Hide Settings");
972                 gtk_widget_set_tooltip_text(toggle, 
973                         "Hide the picture settings window while "
974                         "leaving the preview visible.");
975                 gtk_widget_show(window);
976         }
977         else
978         {
979                 gtk_button_set_label(GTK_BUTTON(toggle), "Show Settings");
980                 gtk_widget_set_tooltip_text(toggle, "Show picture settings.");
981                 gtk_widget_hide(window);
982         }
983 }
984
985 G_MODULE_EXPORT void
986 preview_frame_value_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
987 {
988         if (ud->preview->live_id >= 0)
989         {
990                 ghb_stop_live_encode();
991                 ud->preview->live_id = -1;
992                 ud->preview->encode_frame = -1;
993         }
994         ghb_set_preview_image(ud);
995 }
996
997 G_MODULE_EXPORT gboolean
998 preview_window_delete_cb(
999         GtkWidget *widget, 
1000         GdkEvent *event, 
1001         signal_user_data_t *ud)
1002 {
1003         live_preview_stop(ud);
1004         widget = GHB_WIDGET (ud->builder, "show_picture");
1005         gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(widget), FALSE);
1006         return TRUE;
1007 }
1008
1009 G_MODULE_EXPORT gboolean
1010 settings_window_delete_cb(
1011         GtkWidget *widget, 
1012         GdkEvent *event, 
1013         signal_user_data_t *ud)
1014 {
1015         live_preview_stop(ud);
1016         widget = GHB_WIDGET (ud->builder, "show_picture");
1017         gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(widget), FALSE);
1018
1019         return TRUE;
1020 }
1021
1022 G_MODULE_EXPORT void
1023 preview_duration_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
1024 {
1025         g_debug("preview_duration_changed_cb ()");
1026         ghb_live_reset(ud);
1027         ghb_widget_to_setting (ud->settings, widget);
1028         ghb_check_dependency(ud, widget, NULL);
1029         const gchar *name = gtk_widget_get_name(widget);
1030         ghb_pref_save(ud->settings, name);
1031 }
1032
1033 static guint hud_timeout_id = 0;
1034
1035 static gboolean
1036 hud_timeout(signal_user_data_t *ud)
1037 {
1038         GtkWidget *widget;
1039
1040         g_debug("hud_timeout()");
1041         widget = GHB_WIDGET(ud->builder, "preview_hud");
1042         gtk_widget_hide(widget);
1043         hud_timeout_id = 0;
1044         return FALSE;
1045 }
1046
1047 G_MODULE_EXPORT gboolean
1048 hud_enter_cb(
1049         GtkWidget *widget,
1050         GdkEventCrossing *event,
1051         signal_user_data_t *ud)
1052 {
1053         g_debug("hud_enter_cb()");
1054         if (hud_timeout_id != 0)
1055         {
1056                 GMainContext *mc;
1057                 GSource *source;
1058
1059                 mc = g_main_context_default();
1060                 source = g_main_context_find_source_by_id(mc, hud_timeout_id);
1061                 if (source != NULL)
1062                         g_source_destroy(source);
1063         }
1064         widget = GHB_WIDGET(ud->builder, "preview_hud");
1065         gtk_widget_show(widget);
1066         hud_timeout_id = 0;
1067         return FALSE;
1068 }
1069
1070 G_MODULE_EXPORT gboolean
1071 preview_leave_cb(
1072         GtkWidget *widget,
1073         GdkEventCrossing *event,
1074         signal_user_data_t *ud)
1075 {
1076         g_debug("hud_leave_cb()");
1077         if (hud_timeout_id != 0)
1078         {
1079                 GMainContext *mc;
1080                 GSource *source;
1081
1082                 mc = g_main_context_default();
1083                 source = g_main_context_find_source_by_id(mc, hud_timeout_id);
1084                 if (source != NULL)
1085                         g_source_destroy(source);
1086         }
1087         hud_timeout_id = g_timeout_add(300, (GSourceFunc)hud_timeout, ud);
1088         return FALSE;
1089 }
1090
1091 G_MODULE_EXPORT gboolean
1092 preview_motion_cb(
1093         GtkWidget *widget,
1094         GdkEventMotion *event,
1095         signal_user_data_t *ud)
1096 {
1097         //g_debug("hud_motion_cb %d", hud_timeout_id);
1098         if (hud_timeout_id != 0)
1099         {
1100                 GMainContext *mc;
1101                 GSource *source;
1102
1103                 mc = g_main_context_default();
1104                 source = g_main_context_find_source_by_id(mc, hud_timeout_id);
1105                 if (source != NULL)
1106                         g_source_destroy(source);
1107         }
1108         widget = GHB_WIDGET(ud->builder, "preview_hud");
1109         if (!GTK_WIDGET_VISIBLE(widget))
1110         {
1111                 gtk_widget_show(widget);
1112         }
1113         hud_timeout_id = g_timeout_add_seconds(4, (GSourceFunc)hud_timeout, ud);
1114         return FALSE;
1115 }
1116
1117 GdkDrawable*
1118 ghb_curved_rect_mask(gint width, gint height, gint radius)
1119 {
1120         GdkDrawable *shape;
1121         cairo_t *cr;
1122         double w, h;
1123
1124         if (!width || !height)
1125                 return NULL;
1126
1127         shape = (GdkDrawable *)gdk_pixmap_new (NULL, width, height, 1);
1128
1129         cr = gdk_cairo_create (shape);
1130
1131         w = width;
1132         h = height;
1133         if (radius > width / 2)
1134                 radius = width / 2;
1135         if (radius > height / 2)
1136                 radius = height / 2;
1137
1138         // fill shape with black
1139         cairo_save(cr);
1140         cairo_rectangle (cr, 0, 0, width, height);
1141         cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1142         cairo_fill (cr);
1143         cairo_restore (cr);
1144
1145         cairo_move_to  (cr, 0, radius);
1146         cairo_curve_to (cr, 0 , 0, 0 , 0, radius, 0);
1147         cairo_line_to (cr, w - radius, 0);
1148         cairo_curve_to (cr, w, 0, w, 0, w, radius);
1149         cairo_line_to (cr, w , h - radius);
1150         cairo_curve_to (cr, w, h, w, h, w - radius, h);
1151         cairo_line_to (cr, 0 + radius, h);
1152         cairo_curve_to (cr, 0, h, 0, h, 0, h - radius);
1153
1154         cairo_close_path(cr);
1155
1156         cairo_set_source_rgb(cr, 1, 1, 1);
1157         cairo_fill(cr);
1158
1159         cairo_destroy(cr);
1160
1161         return shape;
1162 }
1163
1164 G_MODULE_EXPORT void
1165 preview_hud_size_alloc_cb(
1166         GtkWidget *widget,
1167         GtkAllocation *allocation,
1168         signal_user_data_t *ud)
1169 {
1170         GdkDrawable *shape;
1171
1172         //g_message("preview_hud_size_alloc_cb()");
1173         if (GTK_WIDGET_VISIBLE(widget) && allocation->height > 50)
1174         {
1175                 shape = ghb_curved_rect_mask(allocation->width, 
1176                                                                         allocation->height, allocation->height/4);
1177                 if (shape != NULL)
1178                 {
1179                         gtk_widget_shape_combine_mask(widget, shape, 0, 0);
1180                         gdk_pixmap_unref(shape);
1181                 }
1182         }
1183 }
1184
1185 G_MODULE_EXPORT gboolean
1186 preview_configure_cb(
1187         GtkWidget *widget,
1188         GdkEventConfigure *event,
1189         signal_user_data_t *ud)
1190 {
1191         gint x, y;
1192
1193         //g_message("preview_configure_cb()");
1194         if (GTK_WIDGET_VISIBLE(widget))
1195         {
1196                 gtk_window_get_position(GTK_WINDOW(widget), &x, &y);
1197                 ghb_settings_set_int(ud->settings, "preview_x", x);
1198                 ghb_settings_set_int(ud->settings, "preview_y", y);
1199                 ghb_pref_set(ud->settings, "preview_x");
1200                 ghb_pref_set(ud->settings, "preview_y");
1201                 ghb_prefs_store();
1202         }
1203         return FALSE;
1204 }
1205
1206 G_MODULE_EXPORT gboolean
1207 settings_configure_cb(
1208         GtkWidget *widget,
1209         GdkEventConfigure *event,
1210         signal_user_data_t *ud)
1211 {
1212         gint x, y;
1213
1214         //g_message("settings_configure_cb()");
1215         if (GTK_WIDGET_VISIBLE(widget))
1216         {
1217                 gtk_window_get_position(GTK_WINDOW(widget), &x, &y);
1218                 ghb_settings_set_int(ud->settings, "settings_x", x);
1219                 ghb_settings_set_int(ud->settings, "settings_y", y);
1220                 ghb_pref_set(ud->settings, "settings_x");
1221                 ghb_pref_set(ud->settings, "settings_y");
1222                 ghb_prefs_store();
1223         }
1224         return FALSE;
1225 }
1226