OSDN Git Service

MacGui: Remove Target Size as a rate control option as it doesn't really work correct...
[handbrake-jp/handbrake-jp-git.git] / gtk / src / preview.c
index 20de012..796700d 100644 (file)
@@ -1,7 +1,7 @@
 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
 /*
  * preview.c
- * Copyright (C) John Stebbins 2008 <stebbins@stebbins>
+ * Copyright (C) John Stebbins 2008-2011 <stebbins@stebbins>
  * 
  * preview.c is free software.
  * 
 #include <glib/gstdio.h>
 #include <glib-object.h>
 #include <gtk/gtk.h>
+
+#if !defined(_WIN32)
 #include <gdk/gdkx.h>
+#endif
+
+#if defined(_ENABLE_GST)
 #include <gst/gst.h>
 #include <gst/interfaces/xoverlay.h>
 #include <gst/video/video.h>
 #include <gst/pbutils/missing-plugins.h>
+#endif
+
 #include "settings.h"
+#include "presets.h"
 #include "callbacks.h"
 #include "hb-backend.h"
 #include "preview.h"
 
 struct preview_s
 {
+#if defined(_ENABLE_GST)
        GstElement *play;
+       gulong xid;
+#endif
        gint64 len;
        gint64 pos;
        gboolean seek_lock;
@@ -41,7 +52,6 @@ struct preview_s
        gint width;
        gint height;
        GtkWidget *view;
-       gulong xid;
        GdkPixbuf *pix;
        gint button_width;
        gint button_height;
@@ -50,39 +60,165 @@ struct preview_s
        gboolean pause;
        gboolean encoded[10];
        gint encode_frame;
+       gint live_id;
        gchar *current;
+       gint live_enabled;
 };
 
-static gboolean live_preview_cb(GstBus *bus, GstMessage *msg, gpointer data);
+#if defined(_ENABLE_GST)
+G_MODULE_EXPORT gboolean live_preview_cb(GstBus *bus, GstMessage *msg, gpointer data);
 static GstBusSyncReply create_window(GstBus *bus, GstMessage *msg, 
                                gpointer data);
-gboolean preview_expose_cb(GtkWidget *widget, GdkEventExpose *event, 
+#endif
+
+G_MODULE_EXPORT gboolean preview_expose_cb(GtkWidget *widget, GdkEventExpose *event, 
                                signal_user_data_t *ud);
 
 void
-ghb_preview_init(signal_user_data_t *ud)
+ghb_screen_par(signal_user_data_t *ud, gint *par_n, gint *par_d)
 {
-       GstBus *bus;
+#if defined(_ENABLE_GST)
+       GValue disp_par = {0,};
        GstElement *xover;
+       GObjectClass *klass;
+       GParamSpec *pspec;
+
+       if (!ud->preview->live_enabled)
+               goto fail;
+
+       g_value_init(&disp_par, GST_TYPE_FRACTION);
+       gst_value_set_fraction(&disp_par, 1, 1);
+       g_object_get(ud->preview->play, "video-sink", &xover, NULL);
+       if (xover == NULL)
+               goto fail;
+
+       klass = G_OBJECT_GET_CLASS(xover);
+       if (klass == NULL)
+               goto fail;
+
+       pspec = g_object_class_find_property(klass, "pixel-aspect_ratio");
+       if (pspec)
+       {
+               GValue par_prop = {0,};
+
+               g_value_init(&par_prop, pspec->value_type);
+               g_object_get_property(G_OBJECT(xover), "pixel-aspect-ratio",
+                                                               &par_prop);
+               if (!g_value_transform(&par_prop, &disp_par))
+               {
+                       g_warning("transform failed");
+                       gst_value_set_fraction(&disp_par, 1, 1);
+               }
+               g_value_unset(&par_prop);
+       }
+       *par_n = gst_value_get_fraction_numerator(&disp_par);
+       *par_d = gst_value_get_fraction_denominator(&disp_par);
+       g_value_unset(&disp_par);
+       return;
+
+fail:
+       *par_n = 1;
+       *par_d = 1;
+#else
+       *par_n = 1;
+       *par_d = 1;
+#endif
+}
+
+void
+ghb_par_scale(signal_user_data_t *ud, gint *width, gint *height, gint par_n, gint par_d)
+{
+       gint disp_par_n, disp_par_d;
+       gint64 num, den;
+
+       ghb_screen_par(ud, &disp_par_n, &disp_par_d);
+       if (disp_par_n < 1) disp_par_n = 1;
+       if (disp_par_d < 1) disp_par_d = 1;
+       num = par_n * disp_par_d;
+       den = par_d * disp_par_n;
+
+       if (par_n > par_d)
+               *width = *width * num / den;
+       else
+               *height = *height * den / num;
+}
+
+void
+ghb_preview_init(signal_user_data_t *ud)
+{
+       GtkWidget *widget;
 
        ud->preview = g_malloc0(sizeof(preview_t));
        ud->preview->view = GHB_WIDGET(ud->builder, "preview_image");
        gtk_widget_realize(ud->preview->view);
        g_signal_connect(G_OBJECT(ud->preview->view), "expose_event",
                                        G_CALLBACK(preview_expose_cb), ud);
-       ud->preview->xid = GDK_DRAWABLE_XID(ud->preview->view->window);
 
-       ud->preview->play = gst_element_factory_make("playbin", "play");
        ud->preview->pause = TRUE;
-       //xover = gst_element_factory_make("xvimagesink", "xover");
+       ud->preview->encode_frame = -1;
+       ud->preview->live_id = -1;
+       widget = GHB_WIDGET (ud->builder, "preview_button_image");
+       gtk_widget_get_size_request(widget, &ud->preview->button_width, &ud->preview->button_height);
+       
+#if defined(_ENABLE_GST)
+       GstBus *bus;
+       GstElement *xover;
+
+#if GTK_CHECK_VERSION(2,18,0)
+       if (!gdk_window_ensure_native(ud->preview->view->window))
+       {
+               g_message("Couldn't create native window for GstXOverlay. Disabling live preview.");
+               GtkWidget *widget = GHB_WIDGET(ud->builder, "live_preview_box");
+               gtk_widget_hide (widget);
+               widget = GHB_WIDGET(ud->builder, "live_preview_duration_box");
+               gtk_widget_hide (widget);
+               return;
+       }
+#endif
+
+#if !defined(_WIN32)
+       ud->preview->xid = GDK_DRAWABLE_XID(ud->preview->view->window);
+#else
+       ud->preview->xid = GDK_WINDOW_HWND(ud->preview->view->window);
+#endif
+       ud->preview->play = gst_element_factory_make("playbin", "play");
        xover = gst_element_factory_make("gconfvideosink", "xover");
-       g_object_set(G_OBJECT(ud->preview->play), "video-sink", xover, NULL);
-       //g_object_set(G_OBJECT(xover), "force-aspect-ratio", TRUE, NULL);
+       if (xover == NULL)
+       {
+               xover = gst_element_factory_make("xvimagesink", "xover");
+       }
+       if (xover == NULL)
+       {
+               xover = gst_element_factory_make("ximagesink", "xover");
+       }
+       if (ud->preview->play == NULL || xover == NULL)
+       {
+               g_message("Couldn't initialize gstreamer. Disabling live preview.");
+               GtkWidget *widget = GHB_WIDGET(ud->builder, "live_preview_box");
+               gtk_widget_hide (widget);
+               widget = GHB_WIDGET(ud->builder, "live_preview_duration_box");
+               gtk_widget_hide (widget);
+               return;
+       }
+       else
+       {
+
+               g_object_set(G_OBJECT(ud->preview->play), "video-sink", xover, NULL);
+               g_object_set(ud->preview->play, "subtitle-font-desc", 
+                                       "sans bold 20", NULL);
 
-       bus = gst_pipeline_get_bus(GST_PIPELINE(ud->preview->play));
-       gst_bus_add_watch(bus, live_preview_cb, ud);
-       gst_bus_set_sync_handler(bus, create_window, ud->preview);
-       gst_object_unref(bus);
+               bus = gst_pipeline_get_bus(GST_PIPELINE(ud->preview->play));
+               gst_bus_add_watch(bus, live_preview_cb, ud);
+               gst_bus_set_sync_handler(bus, create_window, ud->preview);
+               gst_object_unref(bus);
+               ud->preview->live_enabled = 1;
+       }
+#else
+       widget = GHB_WIDGET(ud->builder, "live_preview_box");
+       gtk_widget_hide (widget);
+       widget = GHB_WIDGET(ud->builder, "live_preview_duration_box");
+       gtk_widget_hide (widget);
+#endif
 }
 
 void
@@ -95,6 +231,7 @@ ghb_preview_cleanup(signal_user_data_t *ud)
        }
 }
 
+#if defined(_ENABLE_GST)
 static GstBusSyncReply
 create_window(GstBus *bus, GstMessage *msg, gpointer data)
 {
@@ -106,8 +243,13 @@ create_window(GstBus *bus, GstMessage *msg, gpointer data)
        {
                if (!gst_structure_has_name(msg->structure, "prepare-xwindow-id"))
                        return GST_BUS_PASS;
+#if !defined(_WIN32)
                gst_x_overlay_set_xwindow_id(
                        GST_X_OVERLAY(GST_MESSAGE_SRC(msg)), preview->xid);
+#else
+               gst_directdraw_sink_set_window_id(
+                       GST_X_OVERLAY(GST_MESSAGE_SRC(msg)), preview->xid);
+#endif
                gst_message_unref(msg);
                return GST_BUS_DROP;
        } break;
@@ -166,7 +308,7 @@ get_stream_info_objects_for_type (GstElement *play, const gchar *typestr)
 }
 
 static void
-caps_set(GstCaps *caps, preview_t *preview)
+caps_set(GstCaps *caps, signal_user_data_t *ud)
 {
        GstStructure *ss;
 
@@ -175,12 +317,8 @@ caps_set(GstCaps *caps, preview_t *preview)
        {
                gint fps_n, fps_d, width, height;
                guint num, den, par_n, par_d;
-               guint disp_par_n, disp_par_d;
+               gint disp_par_n, disp_par_d;
                const GValue *par;
-               GValue disp_par = {0,};
-               GstElement *xover;
-               GObjectClass *klass;
-               GParamSpec *pspec;
 
                gst_structure_get_fraction(ss, "framerate", &fps_n, &fps_d);
                gst_structure_get_int(ss, "width", &width);
@@ -189,28 +327,7 @@ caps_set(GstCaps *caps, preview_t *preview)
                par_n = gst_value_get_fraction_numerator(par);
                par_d = gst_value_get_fraction_denominator(par);
 
-               g_value_init(&disp_par, GST_TYPE_FRACTION);
-               gst_value_set_fraction(&disp_par, 1, 1);
-               g_object_get(preview->play, "video-sink", &xover, NULL);
-               klass = G_OBJECT_GET_CLASS(xover);
-               pspec = g_object_class_find_property(klass, "pixel-aspect_ratio");
-               if (pspec)
-               {
-                       GValue par_prop = {0,};
-
-                       g_value_init(&par_prop, pspec->value_type);
-                       g_object_get_property(G_OBJECT(xover), "pixel-aspect-ratio",
-                                                                       &par_prop);
-                       if (!g_value_transform(&par_prop, &disp_par))
-                       {
-                               g_warning("transform failed");
-                               gst_value_set_fraction(&disp_par, 1, 1);
-                       }
-                       g_value_unset(&par_prop);
-               }
-               disp_par_n = gst_value_get_fraction_numerator(&disp_par);
-               disp_par_d = gst_value_get_fraction_denominator(&disp_par);
-               g_value_unset(&disp_par);
+               ghb_screen_par(ud, &disp_par_n, &disp_par_d);
                gst_video_calculate_display_ratio(
                        &num, &den, width, height, par_n, par_d, disp_par_n, disp_par_d);
 
@@ -218,23 +335,44 @@ caps_set(GstCaps *caps, preview_t *preview)
                        width = gst_util_uint64_scale_int(height, num, den);
                else
                        height = gst_util_uint64_scale_int(width, den, num);
+
+               if (ghb_settings_get_boolean(ud->settings, "reduce_hd_preview"))
+               {
+                       GdkScreen *ss;
+                       gint s_w, s_h;
+
+                       ss = gdk_screen_get_default();
+                       s_w = gdk_screen_get_width(ss);
+                       s_h = gdk_screen_get_height(ss);
+
+                       if (width > s_w * 80 / 100)
+                       {
+                               width = s_w * 80 / 100;
+                               height = gst_util_uint64_scale_int(width, den, num);
+                       }
+                       if (height > s_h * 80 / 100)
+                       {
+                               height = s_h * 80 / 100;
+                               width = gst_util_uint64_scale_int(height, num, den);
+                       }
+               }
                
-               if (width != preview->width || height != preview->height)
+               if (width != ud->preview->width || height != ud->preview->height)
                {
-                       gtk_widget_set_size_request(preview->view, width, height);
-                       preview->width = width;
-                       preview->height = height;
+                       gtk_widget_set_size_request(ud->preview->view, width, height);
+                       ud->preview->width = width;
+                       ud->preview->height = height;
                }
        }
 }
 
 static void
-update_stream_info(preview_t *preview)
+update_stream_info(signal_user_data_t *ud)
 {
        GList *vstreams, *ll;
        GstPad *vpad = NULL;
 
-       vstreams = get_stream_info_objects_for_type(preview->play, "video");
+       vstreams = get_stream_info_objects_for_type(ud->preview->play, "video");
        if (vstreams)
        {
                for (ll = vstreams; vpad == NULL && ll != NULL; ll = ll->next)
@@ -249,7 +387,7 @@ update_stream_info(preview_t *preview)
                caps = gst_pad_get_negotiated_caps(vpad);
                if (caps)
                {
-                       caps_set(caps, preview);
+                       caps_set(caps, ud);
                        gst_caps_unref(caps);
                }
                //g_signal_connect(vpad, "notify::caps", G_CALLBACK(caps_set_cb), preview);
@@ -259,7 +397,7 @@ update_stream_info(preview_t *preview)
        g_list_free(vstreams);
 }
 
-static gboolean
+G_MODULE_EXPORT gboolean
 live_preview_cb(GstBus *bus, GstMessage *msg, gpointer data)
 {
        signal_user_data_t *ud = (signal_user_data_t*)data;
@@ -300,7 +438,7 @@ live_preview_cb(GstBus *bus, GstMessage *msg, gpointer data)
                gst_element_get_state(ud->preview->play, &state, &pending, 0);
                if (state == GST_STATE_PAUSED || state == GST_STATE_PLAYING)
                {
-                       update_stream_info(ud->preview);
+                       update_stream_info(ud);
                }
        } break;
 
@@ -333,6 +471,9 @@ live_preview_start(signal_user_data_t *ud)
        GtkImage *img;
        gchar *uri;
 
+       if (!ud->preview->live_enabled)
+               return;
+
        img = GTK_IMAGE(GHB_WIDGET(ud->builder, "live_preview_play_image"));
        if (!ud->preview->encoded[ud->preview->frame])
        {
@@ -356,11 +497,15 @@ live_preview_pause(signal_user_data_t *ud)
 {
        GtkImage *img;
 
+       if (!ud->preview->live_enabled)
+               return;
+
        img = GTK_IMAGE(GHB_WIDGET(ud->builder, "live_preview_play_image"));
        gtk_image_set_from_stock(img, "gtk-media-play", GTK_ICON_SIZE_BUTTON);
        gst_element_set_state(ud->preview->play, GST_STATE_PAUSED);
        ud->preview->pause = TRUE;
 }
+#endif
 
 void
 live_preview_stop(signal_user_data_t *ud)
@@ -368,9 +513,14 @@ live_preview_stop(signal_user_data_t *ud)
        GtkImage *img;
        GtkRange *progress;
 
+       if (!ud->preview->live_enabled)
+               return;
+
        img = GTK_IMAGE(GHB_WIDGET(ud->builder, "live_preview_play_image"));
        gtk_image_set_from_stock(img, "gtk-media-play", GTK_ICON_SIZE_BUTTON);
+#if defined(_ENABLE_GST)
        gst_element_set_state(ud->preview->play, GST_STATE_NULL);
+#endif
        ud->preview->pause = TRUE;
        ud->preview->state = PREVIEW_STATE_IMAGE;
 
@@ -383,6 +533,12 @@ ghb_live_reset(signal_user_data_t *ud)
 {
        gboolean encoded;
 
+       if (ud->preview->live_id >= 0)
+       {
+               ghb_stop_live_encode();
+       }
+       ud->preview->live_id = -1;
+       ud->preview->encode_frame = -1;
        if (!ud->preview->pause)
                live_preview_stop(ud);
        if (ud->preview->current)
@@ -396,9 +552,7 @@ ghb_live_reset(signal_user_data_t *ud)
                ghb_set_preview_image(ud);
 }
 
-extern void hb_get_tempory_directory(hb_handle_t *h, char path[512]);
-
-void
+G_MODULE_EXPORT void
 live_preview_start_cb(GtkWidget *xwidget, signal_user_data_t *ud)
 {
        gchar *tmp_dir;
@@ -414,10 +568,12 @@ live_preview_start_cb(GtkWidget *xwidget, signal_user_data_t *ud)
        if (ud->preview->encoded[frame] &&
                g_file_test(name, G_FILE_TEST_IS_REGULAR))
        {
+#if defined(_ENABLE_GST)
                if (ud->preview->pause)
                        live_preview_start(ud);
                else
                        live_preview_pause(ud);
+#endif
        }
        else
        {
@@ -427,8 +583,8 @@ live_preview_start_cb(GtkWidget *xwidget, signal_user_data_t *ud)
                js = ghb_value_dup(ud->settings);
                ghb_settings_set_string(js, "destination", name);
                ghb_settings_set_int(js, "start_frame", ud->preview->frame);
-               ghb_settings_set_int(js, "live_duration", 15);
-               ghb_add_live_job(js, 0);
+               ud->preview->live_id = 0;
+               ghb_add_live_job(js, ud->preview->live_id);
                ghb_start_live_encode();
                ghb_value_free(js);
        }
@@ -440,6 +596,7 @@ ghb_live_encode_done(signal_user_data_t *ud, gboolean success)
        GtkWidget *widget;
        GtkWidget *prog;
 
+       ud->preview->live_id = -1;
        prog = GHB_WIDGET(ud->builder, "live_encode_progress");
        if (success && 
                ud->preview->encode_frame == ud->preview->frame)
@@ -447,7 +604,9 @@ ghb_live_encode_done(signal_user_data_t *ud, gboolean success)
                gtk_progress_bar_set_text(GTK_PROGRESS_BAR(prog), "Done");
                gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(prog), 1);
                ud->preview->encoded[ud->preview->encode_frame] = TRUE;
+#if defined(_ENABLE_GST)
                live_preview_start(ud);
+#endif
                widget = GHB_WIDGET(ud->builder, "live_progress_box");
                gtk_widget_hide (widget);
                widget = GHB_WIDGET(ud->builder, "live_preview_progress");
@@ -461,7 +620,8 @@ ghb_live_encode_done(signal_user_data_t *ud, gboolean success)
        }
 }
 
-static gboolean
+#if defined(_ENABLE_GST)
+G_MODULE_EXPORT gboolean
 unlock_progress_cb(signal_user_data_t *ud)
 {
        ud->preview->progress_lock = FALSE;
@@ -469,13 +629,18 @@ unlock_progress_cb(signal_user_data_t *ud)
        // so that it is not called again
        return FALSE;
 }
+#endif
 
 void
 ghb_live_preview_progress(signal_user_data_t *ud)
 {
+#if defined(_ENABLE_GST)
        GstFormat fmt = GST_FORMAT_TIME;
        gint64 len = -1, pos = -1;
 
+       if (!ud->preview->live_enabled)
+               return;
+
        if (ud->preview->state != PREVIEW_STATE_LIVE || ud->preview->seek_lock)
                return;
 
@@ -504,9 +669,11 @@ ghb_live_preview_progress(signal_user_data_t *ud)
                gtk_range_set_value(progress, percent);
        }
        g_idle_add((GSourceFunc)unlock_progress_cb, ud);
+#endif
 }
 
-static gboolean
+#if defined(_ENABLE_GST)
+G_MODULE_EXPORT gboolean
 unlock_seek_cb(signal_user_data_t *ud)
 {
        ud->preview->seek_lock = FALSE;
@@ -514,13 +681,18 @@ unlock_seek_cb(signal_user_data_t *ud)
        // so that it is not called again
        return FALSE;
 }
+#endif
 
-void
+G_MODULE_EXPORT void
 live_preview_seek_cb(GtkWidget *widget, signal_user_data_t *ud)
 {
+#if defined(_ENABLE_GST)
        gdouble dval;
        gint64 pos;
 
+       if (!ud->preview->live_enabled)
+               return;
+
        if (ud->preview->progress_lock)
                return;
 
@@ -532,6 +704,7 @@ live_preview_seek_cb(GtkWidget *widget, signal_user_data_t *ud)
                GST_SEEK_TYPE_SET, pos,
                GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
        g_idle_add((GSourceFunc)unlock_seek_cb, ud);
+#endif
 }
 
 void
@@ -568,8 +741,10 @@ ghb_set_preview_image(signal_user_data_t *ud)
        }
        if (ud->preview->pix != NULL)
                g_object_unref(ud->preview->pix);
-       ud->preview->pix = ghb_get_preview_image(titleindex, ud->preview->frame, 
-                                                                                       ud->settings, TRUE);
+
+       ud->preview->pix = 
+               ghb_get_preview_image(titleindex, ud->preview->frame, 
+                                                               ud, &width, &height);
        if (ud->preview->pix == NULL) return;
        preview_width = gdk_pixbuf_get_width(ud->preview->pix);
        preview_height = gdk_pixbuf_get_height(ud->preview->pix);
@@ -585,15 +760,20 @@ ghb_set_preview_image(signal_user_data_t *ud)
                widget->window, NULL, ud->preview->pix, 0, 0, 0, 0,
                -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
 
-       gchar *text = g_strdup_printf("%d x %d", preview_width, preview_height);
+       gchar *text = g_strdup_printf("%d x %d", width, height);
        widget = GHB_WIDGET (ud->builder, "preview_dims");
        gtk_label_set_text(GTK_LABEL(widget), text);
        g_free(text);
        
        g_debug("preview %d x %d", preview_width, preview_height);
-       target_height = MIN(ud->preview->button_height, 128);
+       target_height = MIN(ud->preview->button_height, 200);
        height = target_height;
        width = preview_width * height / preview_height;
+       if (width > 400)
+       {
+               width = 400;
+               height = preview_height * width / preview_width;
+       }
 
        if ((height >= 16) && (width >= 16))
        {
@@ -609,13 +789,20 @@ ghb_set_preview_image(signal_user_data_t *ud)
        }
 }
 
-static gboolean
+#if defined(_ENABLE_GST)
+G_MODULE_EXPORT gboolean
 delayed_expose_cb(signal_user_data_t *ud)
 {
        GstElement *vsink;
        GstXOverlay *xover;
 
+       if (!ud->preview->live_enabled)
+               return FALSE;
+
        g_object_get(ud->preview->play, "video-sink", &vsink, NULL);
+       if (vsink == NULL)
+               return FALSE;
+
        if (GST_IS_BIN(vsink))
                xover = GST_X_OVERLAY(gst_bin_get_by_interface(
                                                                GST_BIN(vsink), GST_TYPE_X_OVERLAY));
@@ -626,14 +813,16 @@ delayed_expose_cb(signal_user_data_t *ud)
        // so that it is not called again
        return FALSE;
 }
+#endif
 
-gboolean
+G_MODULE_EXPORT gboolean
 preview_expose_cb(
        GtkWidget *widget, 
        GdkEventExpose *event, 
        signal_user_data_t *ud)
 {
-       if (ud->preview->state == PREVIEW_STATE_LIVE)
+#if defined(_ENABLE_GST)
+       if (ud->preview->live_enabled && ud->preview->state == PREVIEW_STATE_LIVE)
        {
                if (GST_STATE(ud->preview->play) >= GST_STATE_PAUSED)
                {
@@ -655,14 +844,18 @@ preview_expose_cb(
                }
                return TRUE;
        }
+#endif
 
-       gdk_draw_pixbuf(
-               widget->window, NULL, ud->preview->pix, 0, 0, 0, 0,
-               -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
+       if (ud->preview->pix != NULL)
+       {
+               gdk_draw_pixbuf(
+                       widget->window, NULL, ud->preview->pix, 0, 0, 0, 0,
+                       -1, -1, GDK_RGB_DITHER_NONE, 0, 0);
+       }
        return TRUE;
 }
 
-void
+G_MODULE_EXPORT void
 preview_button_size_allocate_cb(GtkWidget *widget, GtkAllocation *allocation, signal_user_data_t *ud)
 {
        g_debug("allocate %d x %d", allocation->width, allocation->height);
@@ -680,22 +873,400 @@ preview_button_size_allocate_cb(GtkWidget *widget, GtkAllocation *allocation, si
        ghb_set_preview_image(ud);
 }
 
+static void
+set_visible(GtkWidget *widget, gboolean visible)
+{
+       if (visible)
+       {
+               gtk_widget_show_now(widget);
+       }
+       else
+       {
+               gtk_widget_hide(widget);
+       }
+}
+
 void
-preview_button_clicked_cb(GtkWidget *xwidget, signal_user_data_t *ud)
+ghb_preview_set_visible(signal_user_data_t *ud)
 {
        gint titleindex;
+       GtkWidget *widget;
+       gboolean settings_active;
 
+       settings_active = ghb_settings_get_boolean(ud->settings, "show_picture");
+       widget = GHB_WIDGET (ud->builder, "preview_window");
        titleindex = ghb_settings_combo_int(ud->settings, "title");
-       if (titleindex < 0) return;
-       g_debug("titleindex %d", titleindex);
+       if (settings_active && titleindex >= 0)
+       {
+               gint x, y;
+               x = ghb_settings_get_int(ud->settings, "preview_x");
+               y = ghb_settings_get_int(ud->settings, "preview_y");
+               if (x >= 0 && y >= 0)
+                       gtk_window_move(GTK_WINDOW(widget), x, y);
+               set_visible(widget, 
+                                       ghb_settings_get_boolean(ud->settings, "show_preview"));
+       }
+       else
+       {
+               set_visible(widget, FALSE);
+       }
+}
 
-       GtkWidget *widget = GHB_WIDGET (ud->builder, "preview_window");
-       gtk_widget_show (widget);
+G_MODULE_EXPORT void
+preview_button_clicked_cb(GtkWidget *xwidget, signal_user_data_t *ud)
+{
+       g_debug("preview_button_clicked_cb()");
+       ghb_widget_to_setting (ud->settings, xwidget);
+       ghb_preview_set_visible(ud);
+       ghb_check_dependency(ud, xwidget, NULL);
+       const gchar *name = ghb_get_setting_key(xwidget);
+       ghb_pref_save(ud->settings, name);
 }
 
-void
+G_MODULE_EXPORT void
+picture_settings_clicked_cb(GtkWidget *xwidget, signal_user_data_t *ud)
+{
+       GtkWidget *widget;
+       gboolean active, hide_settings;
+       gint x, y;
+
+       g_debug("picture_settings_clicked_cb()");
+       ghb_widget_to_setting (ud->settings, xwidget);
+
+       hide_settings = ghb_settings_get_boolean(ud->settings, "hide_settings");
+
+       active = ghb_settings_get_boolean(ud->settings, "show_picture");
+       widget = GHB_WIDGET (ud->builder, "settings_window");
+       x = ghb_settings_get_int(ud->settings, "settings_x");
+       y = ghb_settings_get_int(ud->settings, "settings_y");
+       if (x >= 0 && y >= 0)
+               gtk_window_move(GTK_WINDOW(widget), x, y);
+       set_visible(widget, active && !hide_settings);
+       ghb_preview_set_visible(ud);
+}
+
+G_MODULE_EXPORT void
+picture_settings_alt_clicked_cb(GtkWidget *xwidget, signal_user_data_t *ud)
+{
+       GtkWidget *toggle;
+       gboolean active;
+
+       g_debug("picture_settings_alt_clicked_cb()");
+       toggle = GHB_WIDGET (ud->builder, "show_picture");
+       active = gtk_toggle_tool_button_get_active(GTK_TOGGLE_TOOL_BUTTON(toggle));
+       gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(toggle), !active);
+}
+
+static gboolean
+go_full(signal_user_data_t *ud)
+{
+       GtkWindow *window;
+       window = GTK_WINDOW(GHB_WIDGET (ud->builder, "preview_window"));
+       gtk_window_fullscreen(window);
+       ghb_set_preview_image(ud);
+       return FALSE;
+}
+
+G_MODULE_EXPORT void
+fullscreen_clicked_cb(GtkWidget *toggle, signal_user_data_t *ud)
+{
+       gboolean active;
+       GtkWindow *window;
+
+       g_debug("fullscreen_clicked_cb()");
+       ghb_widget_to_setting (ud->settings, toggle);
+       ghb_check_dependency(ud, toggle, NULL);
+       const gchar *name = ghb_get_setting_key(toggle);
+       ghb_pref_save(ud->settings, name);
+
+       window = GTK_WINDOW(GHB_WIDGET (ud->builder, "preview_window"));
+       active = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(toggle));
+       if (active)
+       {
+               gtk_window_set_resizable(window, TRUE);
+               gtk_button_set_label(GTK_BUTTON(toggle), "Windowed");
+               // Changing resizable property doesn't take effect immediately
+               // need to delay fullscreen till after this callback returns
+               // to mainloop
+               g_idle_add((GSourceFunc)go_full, ud);
+       }
+       else
+       {
+               gtk_window_unfullscreen(window);
+               gtk_window_set_resizable(window, FALSE);
+               gtk_button_set_label(GTK_BUTTON(toggle), "Fullscreen");
+               ghb_set_preview_image(ud);
+       }
+}
+
+G_MODULE_EXPORT void
+picture_settings_alt2_clicked_cb(GtkWidget *xwidget, signal_user_data_t *ud)
+{
+       GtkWidget *toggle;
+       gboolean active;
+       GtkWidget *window;
+
+       g_debug("picture_settings_alt2_clicked_cb()");
+       ghb_widget_to_setting (ud->settings, xwidget);
+       active = ghb_settings_get_boolean(ud->settings, "hide_settings");
+
+       toggle = GHB_WIDGET (ud->builder, "hide_settings");
+       window = GHB_WIDGET(ud->builder, "settings_window");
+       if (!active)
+       {
+               gtk_button_set_label(GTK_BUTTON(toggle), "Hide Settings");
+               gtk_widget_set_tooltip_text(toggle, 
+                       "Hide the picture settings window while "
+                       "leaving the preview visible.");
+               gtk_widget_show(window);
+       }
+       else
+       {
+               gtk_button_set_label(GTK_BUTTON(toggle), "Show Settings");
+               gtk_widget_set_tooltip_text(toggle, "Show picture settings.");
+               gtk_widget_hide(window);
+       }
+}
+
+G_MODULE_EXPORT void
 preview_frame_value_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
 {
+       if (ud->preview->live_id >= 0)
+       {
+               ghb_stop_live_encode();
+               ud->preview->live_id = -1;
+               ud->preview->encode_frame = -1;
+       }
        ghb_set_preview_image(ud);
 }
 
+G_MODULE_EXPORT gboolean
+preview_window_delete_cb(
+       GtkWidget *widget, 
+       GdkEvent *event, 
+       signal_user_data_t *ud)
+{
+       live_preview_stop(ud);
+       widget = GHB_WIDGET (ud->builder, "show_picture");
+       gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(widget), FALSE);
+       return TRUE;
+}
+
+G_MODULE_EXPORT gboolean
+settings_window_delete_cb(
+       GtkWidget *widget, 
+       GdkEvent *event, 
+       signal_user_data_t *ud)
+{
+       live_preview_stop(ud);
+       widget = GHB_WIDGET (ud->builder, "show_picture");
+       gtk_toggle_tool_button_set_active(GTK_TOGGLE_TOOL_BUTTON(widget), FALSE);
+
+       return TRUE;
+}
+
+G_MODULE_EXPORT void
+preview_duration_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
+{
+       g_debug("preview_duration_changed_cb ()");
+       ghb_live_reset(ud);
+       ghb_widget_to_setting (ud->settings, widget);
+       ghb_check_dependency(ud, widget, NULL);
+       const gchar *name = ghb_get_setting_key(widget);
+       ghb_pref_save(ud->settings, name);
+}
+
+static guint hud_timeout_id = 0;
+
+static gboolean
+hud_timeout(signal_user_data_t *ud)
+{
+       GtkWidget *widget;
+
+       g_debug("hud_timeout()");
+       widget = GHB_WIDGET(ud->builder, "preview_hud");
+       gtk_widget_hide(widget);
+       hud_timeout_id = 0;
+       return FALSE;
+}
+
+G_MODULE_EXPORT gboolean
+hud_enter_cb(
+       GtkWidget *widget,
+       GdkEventCrossing *event,
+       signal_user_data_t *ud)
+{
+       g_debug("hud_enter_cb()");
+       if (hud_timeout_id != 0)
+       {
+               GMainContext *mc;
+               GSource *source;
+
+               mc = g_main_context_default();
+               source = g_main_context_find_source_by_id(mc, hud_timeout_id);
+               if (source != NULL)
+                       g_source_destroy(source);
+       }
+       widget = GHB_WIDGET(ud->builder, "preview_hud");
+       gtk_widget_show(widget);
+       hud_timeout_id = 0;
+       return FALSE;
+}
+
+G_MODULE_EXPORT gboolean
+preview_leave_cb(
+       GtkWidget *widget,
+       GdkEventCrossing *event,
+       signal_user_data_t *ud)
+{
+       g_debug("hud_leave_cb()");
+       if (hud_timeout_id != 0)
+       {
+               GMainContext *mc;
+               GSource *source;
+
+               mc = g_main_context_default();
+               source = g_main_context_find_source_by_id(mc, hud_timeout_id);
+               if (source != NULL)
+                       g_source_destroy(source);
+       }
+       hud_timeout_id = g_timeout_add(300, (GSourceFunc)hud_timeout, ud);
+       return FALSE;
+}
+
+G_MODULE_EXPORT gboolean
+preview_motion_cb(
+       GtkWidget *widget,
+       GdkEventMotion *event,
+       signal_user_data_t *ud)
+{
+       //g_debug("hud_motion_cb %d", hud_timeout_id);
+       if (hud_timeout_id != 0)
+       {
+               GMainContext *mc;
+               GSource *source;
+
+               mc = g_main_context_default();
+               source = g_main_context_find_source_by_id(mc, hud_timeout_id);
+               if (source != NULL)
+                       g_source_destroy(source);
+       }
+       widget = GHB_WIDGET(ud->builder, "preview_hud");
+       if (!GTK_WIDGET_VISIBLE(widget))
+       {
+               gtk_widget_show(widget);
+       }
+       hud_timeout_id = g_timeout_add_seconds(4, (GSourceFunc)hud_timeout, ud);
+       return FALSE;
+}
+
+GdkDrawable*
+ghb_curved_rect_mask(gint width, gint height, gint radius)
+{
+       GdkDrawable *shape;
+       cairo_t *cr;
+       double w, h;
+
+       if (!width || !height)
+               return NULL;
+
+       shape = (GdkDrawable *)gdk_pixmap_new (NULL, width, height, 1);
+
+       cr = gdk_cairo_create (shape);
+
+       w = width;
+       h = height;
+       if (radius > width / 2)
+               radius = width / 2;
+       if (radius > height / 2)
+               radius = height / 2;
+
+       // fill shape with black
+       cairo_save(cr);
+       cairo_rectangle (cr, 0, 0, width, height);
+       cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
+       cairo_fill (cr);
+       cairo_restore (cr);
+
+       cairo_move_to  (cr, 0, radius);
+       cairo_curve_to (cr, 0 , 0, 0 , 0, radius, 0);
+       cairo_line_to (cr, w - radius, 0);
+       cairo_curve_to (cr, w, 0, w, 0, w, radius);
+       cairo_line_to (cr, w , h - radius);
+       cairo_curve_to (cr, w, h, w, h, w - radius, h);
+       cairo_line_to (cr, 0 + radius, h);
+       cairo_curve_to (cr, 0, h, 0, h, 0, h - radius);
+
+       cairo_close_path(cr);
+
+       cairo_set_source_rgb(cr, 1, 1, 1);
+       cairo_fill(cr);
+
+       cairo_destroy(cr);
+
+       return shape;
+}
+
+G_MODULE_EXPORT void
+preview_hud_size_alloc_cb(
+       GtkWidget *widget,
+       GtkAllocation *allocation,
+       signal_user_data_t *ud)
+{
+       GdkDrawable *shape;
+
+       //g_message("preview_hud_size_alloc_cb()");
+       if (GTK_WIDGET_VISIBLE(widget) && allocation->height > 50)
+       {
+               shape = ghb_curved_rect_mask(allocation->width, 
+                                                                       allocation->height, allocation->height/4);
+               if (shape != NULL)
+               {
+                       gtk_widget_shape_combine_mask(widget, shape, 0, 0);
+                       gdk_pixmap_unref(shape);
+               }
+       }
+}
+
+G_MODULE_EXPORT gboolean
+preview_configure_cb(
+       GtkWidget *widget,
+       GdkEventConfigure *event,
+       signal_user_data_t *ud)
+{
+       gint x, y;
+
+       //g_message("preview_configure_cb()");
+       if (GTK_WIDGET_VISIBLE(widget))
+       {
+               gtk_window_get_position(GTK_WINDOW(widget), &x, &y);
+               ghb_settings_set_int(ud->settings, "preview_x", x);
+               ghb_settings_set_int(ud->settings, "preview_y", y);
+               ghb_pref_set(ud->settings, "preview_x");
+               ghb_pref_set(ud->settings, "preview_y");
+               ghb_prefs_store();
+       }
+       return FALSE;
+}
+
+G_MODULE_EXPORT gboolean
+settings_configure_cb(
+       GtkWidget *widget,
+       GdkEventConfigure *event,
+       signal_user_data_t *ud)
+{
+       gint x, y;
+
+       //g_message("settings_configure_cb()");
+       if (GTK_WIDGET_VISIBLE(widget))
+       {
+               gtk_window_get_position(GTK_WINDOW(widget), &x, &y);
+               ghb_settings_set_int(ud->settings, "settings_x", x);
+               ghb_settings_set_int(ud->settings, "settings_y", y);
+               ghb_pref_set(ud->settings, "settings_x");
+               ghb_pref_set(ud->settings, "settings_y");
+               ghb_prefs_store();
+       }
+       return FALSE;
+}
+