OSDN Git Service

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