OSDN Git Service

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