OSDN Git Service

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