OSDN Git Service

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