OSDN Git Service

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