/*
 * nvidia-settings: A tool for configuring the NVIDIA X driver on Unix
 * and Linux systems.
 *
 * Copyright (C) 2004 NVIDIA Corporation.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of Version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See Version 2
 * of the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the:
 *
 *           Free Software Foundation, Inc.
 *           59 Temple Place - Suite 330
 *           Boston, MA 02111-1307, USA
 *
 */

#include <gtk/gtk.h>
#include <NvCtrlAttributes.h>

#include <string.h>

#include "ctkbanner.h"

#include "ctkgvo.h"
#include "ctkgvo-sync.h"

#include "ctkconfig.h"
#include "ctkhelp.h"

#include "ctkdropdownmenu.h"


/*
 * The CtkGvoSync widget is used to provide a way for configuring
 * how the SDI device synchronises with an input signal source.
 */

#define FRAME_BORDER 5
#define TABLE_PADDING 5


#define DEFAULT_DETECT_INPUT_TIME_INTERVAL 2000


#define SYNC_FORMAT_SDI 0
#define SYNC_FORMAT_COMP_AUTO 1
#define SYNC_FORMAT_COMP_BI_LEVEL 2
#define SYNC_FORMAT_COMP_TRI_LEVEL 3

static const GvioFormatName syncFormatNames[] = {
    { SYNC_FORMAT_SDI,            "SDI Sync" },
    { SYNC_FORMAT_COMP_AUTO,      "COMP Sync" },
    { SYNC_FORMAT_COMP_BI_LEVEL,  "COMP Sync (Bi-level)" },
    { SYNC_FORMAT_COMP_TRI_LEVEL, "COMP Sync (Tri-level)" },
    { -1, NULL },
};

extern const GvioFormatName videoFormatNames[];



/* local prototypes */

static GtkWidget *start_menu(const gchar *name, GtkWidget *table,
                             const gint row);
static void finish_menu(GtkWidget *menu, GtkWidget *table, const gint row);


static gboolean query_init_gvo_sync_state(CtkGvoSync *ctk_gvo_sync);
static void init_composite_termination(CtkGvoSync *ctk_gvo_sync);
static void init_sync_format_menu(CtkGvoSync *ctk_gvo_sync);
static void register_for_gvo_sync_events(CtkGvoSync *ctk_gvo_sync);


static void update_gvo_sync_sensitivity(CtkGvoSync *ctk_gvo_sync);
static void update_input_video_format_text_entry(CtkGvoSync *ctk_gvo_sync);
static void post_composite_termination_toggled(CtkGvoSync *ctk_gvo_sync,
                                               gboolean enabled);


static void detect_input_toggled(GtkToggleButton *togglebutton,
                                 CtkGvoSync *ctk_gvo_sync);
static gint detect_input_done(gpointer data);
static void composite_termination_toggled(GtkWidget *button,
                                          CtkGvoSync *ctk_gvo_sync);
static void sync_mode_changed(CtkDropDownMenu *menu, gpointer user_data);
static void sync_format_changed(CtkDropDownMenu *menu, gpointer user_data);
static void hsync_delay_changed(GtkSpinButton *spinbutton, gpointer user_data);
static void vsync_delay_changed(GtkSpinButton *spinbutton, gpointer user_data);


static void gvo_sync_event_received(GtkObject *object,
                                    gpointer arg1,
                                    gpointer user_data);
static gint gvo_sync_probe_callback(gpointer data);



/* Help tooltips */

static const char * __input_video_format_help =
"The Input Video Format text entry indicates the input video format detected "
"on the input BNC.";

static const char * __input_video_format_detect_help =
"The Input Video Format Detect button will (re)detect the video format on "
"the input BNC.";

static const char * __composite_termination_help =
"The Enable Composite Termination checkbox enables the 75 ohm termination "
"of the composite input signal";

static const char * __sync_mode_help =
"The Sync Mode dropdown allows you to select how the SDI device syncs its "
"output to the input signal.";

static const char * __sync_format_help =
"The Sync Format dropdown allows you to select the format of the input sync "
"signal.";

static const char * __sync_status_help =
"The Sync Status reports on how the SDI device is currently syncing to the "
"input sync signal.";

static const char * __hsync_delay_help =
"The HSync Delay entry allows you to specify the horizontal delay between the "
"input signal and the output signal generated by the SDI device.";

static const char * __hsync_advance_help =
"The HSync Advance entry allows you to specify the horizontal advance between "
"the input signal and the output signal generated by the SDI device.";

static const char * __vsync_delay_help =
"The VSync Delay entry allows you to specify the vertical delay between the "
"input signal and the output signal generated by the SDI device.";

static const char * __vsync_advance_help =
"The VSync Advance entry allows you to specify the vertical advance between "
"the input signal and the output signal generated by the SDI device.";




/**** Utility Functions ******************************************************/

/*
 * ctk_gvo_sync_get_type() - Returns the CtkGvoSync "class" type
 */

GType ctk_gvo_sync_get_type(void)
{
    static GType ctk_gvo_sync_type = 0;
    
    if (!ctk_gvo_sync_type) {
        static const GTypeInfo ctk_gvo_sync_info = {
            sizeof (CtkGvoSyncClass),
            NULL, /* base_init */
            NULL, /* base_finalize */
            NULL, /* class_init, */
            NULL, /* class_finalize */
            NULL, /* class_data */
            sizeof (CtkGvoSync),
            0, /* n_preallocs */
            NULL, /* instance_init */
        };

        ctk_gvo_sync_type =
            g_type_register_static (GTK_TYPE_VBOX,
                                    "CtkGvoSync", &ctk_gvo_sync_info, 0);
    }
    
    return ctk_gvo_sync_type;

} /* ctk_gvo_sync_get_type() */



/*
 * max_input_video_format_text_entry_length()
 */

static int max_input_video_format_text_entry_length(void)
{
    gint i, tmp, max = 0;

    for (i = 0; videoFormatNames[i].name; i++) {
        tmp = strlen(videoFormatNames[i].name);
        if (max < tmp) max = tmp;
    }

    return max;

} /* max_input_video_format_text_entry_length() */



/*
 * get_current_sync_format() - given the current state of the
 * sync source and composite detection mode, return the value
 * to use for the sync format dropdown.
 */

static gint get_current_sync_format(CtkGvoSync *ctk_gvo_sync)
{
    /* Setup the sync format menu */

    if (ctk_gvo_sync->sync_source == NV_CTRL_GVO_SYNC_SOURCE_SDI) {
        return SYNC_FORMAT_SDI;
    } else if (ctk_gvo_sync->comp_mode ==
               NV_CTRL_GVO_COMPOSITE_SYNC_INPUT_DETECT_MODE_AUTO) {
        return SYNC_FORMAT_COMP_AUTO;
    } else if (ctk_gvo_sync->comp_mode ==
               NV_CTRL_GVO_COMPOSITE_SYNC_INPUT_DETECT_MODE_BI_LEVEL) {
        return SYNC_FORMAT_COMP_BI_LEVEL;
    } else if (ctk_gvo_sync->comp_mode ==
               NV_CTRL_GVO_COMPOSITE_SYNC_INPUT_DETECT_MODE_TRI_LEVEL){
        return SYNC_FORMAT_COMP_TRI_LEVEL;
    }

    // should not get here
    return SYNC_FORMAT_SDI;

} /* get_current_sync_format() */



/*
 * sync_signal_detected() - Returns whether or not a sync signal
 * is currently detected.
 */

static gboolean sync_signal_detected(CtkGvoSync *ctk_gvo_sync)
{
    /* Is a sync signal detected? */
    
    if ((ctk_gvo_sync->comp_sync_input_detected !=
         NV_CTRL_GVO_COMPOSITE_SYNC_INPUT_DETECTED_FALSE) ||
        (ctk_gvo_sync->sdi_sync_input_detected !=
         NV_CTRL_GVO_SDI_SYNC_INPUT_DETECTED_NONE)) {

        return TRUE;
    }

    return FALSE;
}



/*
 * set_sync_format_menu() - Selects the sync format drop down menu
 * entry based on the current sync source and composite detection mode.
 */

static void set_sync_format_menu(CtkGvoSync *ctk_gvo_sync)
{
    gint val = get_current_sync_format(ctk_gvo_sync);
    
    ctk_drop_down_menu_set_current_value
        (CTK_DROP_DOWN_MENU(ctk_gvo_sync->sync_format_menu), val);
}



/*
 * update_sync_lock_status_text() - Sets the correct text based on the
 * current state of input sync lock.
 */

static void update_sync_lock_status_text(CtkGvoSync *ctk_gvo_sync)
{
    gchar *str = "Free Running";

    switch (ctk_gvo_sync->sync_mode) {

    case NV_CTRL_GVO_SYNC_MODE_FREE_RUNNING:
        gtk_widget_set_sensitive(ctk_gvo_sync->sync_lock_status_text, FALSE);
        break;

    case NV_CTRL_GVO_SYNC_MODE_GENLOCK:
        gtk_widget_set_sensitive(ctk_gvo_sync->sync_lock_status_text, TRUE);
        if (ctk_gvo_sync->sync_lock_status ==
            NV_CTRL_GVO_SYNC_LOCK_STATUS_LOCKED) {
            str = "GenLocked";
        }
        break;

    case NV_CTRL_GVO_SYNC_MODE_FRAMELOCK:
        gtk_widget_set_sensitive(ctk_gvo_sync->sync_lock_status_text, TRUE);
        if (ctk_gvo_sync->sync_lock_status ==
            NV_CTRL_GVO_SYNC_LOCK_STATUS_LOCKED) {
            str = "FrameLocked";
        }        break;

    default:
        gtk_widget_set_sensitive(ctk_gvo_sync->sync_lock_status_text, FALSE);
        str = "Unknown";
        break;
    }

    gtk_label_set_text(GTK_LABEL(ctk_gvo_sync->sync_lock_status_text), str);

} /* update_sync_lock_status_text() */




/**** Creation Functions *****************************************************/

/*
 * ctk_gvo_sync_new() - create a CtkGvoSync widget
 */

GtkWidget* ctk_gvo_sync_new(NvCtrlAttributeHandle *handle,
                            GtkWidget *parent_window,
                            CtkConfig *ctk_config,
                            CtkEvent *ctk_event,
                            CtkGvo *gvo_parent)
{
    GObject *object;
    CtkGvoSync *ctk_gvo_sync;
    GtkWidget *frame;
    GtkWidget *hbox;
    GtkWidget *label;
    GtkWidget *alignment;
    GtkWidget *button;

    GtkWidget *table, *menu;

    gint val, i;
    NVCTRLAttributeValidValuesRec valid;
    ReturnStatus ret;
    gint row;

    const char *help_text;
    

    /* make sure we have a handle */
    
    g_return_val_if_fail(handle != NULL, NULL);
    
    /* create and initialize the object */

    object = g_object_new(CTK_TYPE_GVO_SYNC, NULL);
    
    ctk_gvo_sync = CTK_GVO_SYNC(object);
    ctk_gvo_sync->handle = handle;
    ctk_gvo_sync->parent_window = parent_window;
    ctk_gvo_sync->ctk_config = ctk_config;
    ctk_gvo_sync->ctk_event = ctk_event;
    ctk_gvo_sync->gvo_parent = gvo_parent;
    
    /* Query the current GVO state */

    if ( !query_init_gvo_sync_state(ctk_gvo_sync) ) {
        // Free the object
        g_object_ref(object);
        gtk_object_sink(GTK_OBJECT(object));
        g_object_unref(object);
        return NULL;
    }

    /* set container properties for the widget */

    gtk_box_set_spacing(GTK_BOX(object), 10);
    
    /* banner */
    
    hbox = gtk_hbox_new(FALSE, 0);
    gtk_box_pack_start(GTK_BOX(object), hbox, FALSE, FALSE, 0);

    ctk_gvo_sync->banner_box = hbox;

    /*
     * Sync options
     */
    
    frame = gtk_frame_new("Sync Options");
    ctk_gvo_sync->frame = frame;
    
    gtk_box_pack_start(GTK_BOX(object), frame, FALSE, FALSE, 0);
    
    table = gtk_table_new(6, 2, FALSE);
    gtk_table_set_row_spacings(GTK_TABLE(table), 0);
    gtk_table_set_col_spacings(GTK_TABLE(table), 0);

    gtk_container_add(GTK_CONTAINER(frame), table);

    /* input video format */

    label = gtk_label_new("Input Video Format: ");
    gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
    gtk_table_attach(GTK_TABLE(table), label,
                     0, 1,  0, 1, GTK_FILL, GTK_FILL,
                     TABLE_PADDING, TABLE_PADDING);

    hbox = gtk_hbox_new(FALSE, 5);

    ctk_gvo_sync->input_video_format_text_entry = gtk_entry_new();

    ctk_config_set_tooltip(ctk_config,
                           ctk_gvo_sync->input_video_format_text_entry,
                           __input_video_format_help);
    
    gtk_entry_set_width_chars
        (GTK_ENTRY(ctk_gvo_sync->input_video_format_text_entry),
         max_input_video_format_text_entry_length());

    gtk_widget_set_sensitive(ctk_gvo_sync->input_video_format_text_entry,
                             FALSE);

    update_input_video_format_text_entry(ctk_gvo_sync);

    gtk_box_pack_start(GTK_BOX(hbox),
                       ctk_gvo_sync->input_video_format_text_entry,
                       TRUE, TRUE, 0);

    /* Input video format detect button */

    button = gtk_toggle_button_new_with_label("Detect");
    alignment = gtk_alignment_new(1, 1, 0, 0);
    gtk_container_add(GTK_CONTAINER(alignment), button);

    ctk_gvo_sync->input_video_format_detect_button = button;

    ctk_config_set_tooltip(ctk_config, button,
                           __input_video_format_detect_help);
    
    g_signal_connect(G_OBJECT(button), "toggled",
                     G_CALLBACK(detect_input_toggled), ctk_gvo_sync);

    gtk_box_pack_start(GTK_BOX(hbox), alignment, FALSE, FALSE, 0);

    gtk_table_attach(GTK_TABLE(table), hbox,
                     1, 2, 0, 1, GTK_FILL | GTK_EXPAND, GTK_FILL,
                     TABLE_PADDING, TABLE_PADDING);

    /* Composite Termination */

    if (ctk_gvo_sync->caps & NV_CTRL_GVO_CAPABILITIES_COMPOSITE_TERMINATION) {

        button =
            gtk_check_button_new_with_label("Enable Composite Termination");
        
        ctk_config_set_tooltip(ctk_config, button,
                               __composite_termination_help);

        alignment = gtk_alignment_new(1, 1, 0, 0);
        
        gtk_container_add(GTK_CONTAINER(alignment), button);
        gtk_table_attach(GTK_TABLE(table), alignment,
                         0, 2, 2, 3, GTK_FILL | GTK_EXPAND, GTK_FILL,
                         TABLE_PADDING, TABLE_PADDING);
        
        ctk_gvo_sync->composite_termination_button = button;
        
        init_composite_termination(ctk_gvo_sync);
        
        g_signal_connect(G_OBJECT(button), "toggled",
                         G_CALLBACK(composite_termination_toggled),
                         ctk_gvo_sync);

        row = 3;
    } else {
        ctk_gvo_sync->composite_termination_button = NULL;
        row = 2;
    }

    /* Sync Mode */

    menu = start_menu("Sync Mode: ", table, row);
    
    ctk_drop_down_menu_append_item(CTK_DROP_DOWN_MENU(menu), "Free Running",
                                   NV_CTRL_GVO_SYNC_MODE_FREE_RUNNING);
    
    ctk_drop_down_menu_append_item(CTK_DROP_DOWN_MENU(menu), "GenLock",
                                   NV_CTRL_GVO_SYNC_MODE_GENLOCK);
    
    ctk_drop_down_menu_append_item(CTK_DROP_DOWN_MENU(menu), "FrameLock",
                                   NV_CTRL_GVO_SYNC_MODE_FRAMELOCK);
    
    finish_menu(menu, table, row);
    row++;
    
    ctk_gvo_sync->sync_mode_menu = menu;

    ctk_config_set_tooltip(ctk_config, CTK_DROP_DOWN_MENU(menu)->option_menu,
                           __sync_mode_help);

    ctk_drop_down_menu_set_current_value
        (CTK_DROP_DOWN_MENU(ctk_gvo_sync->sync_mode_menu),
         ctk_gvo_sync->sync_mode);

    g_signal_connect(G_OBJECT(ctk_gvo_sync->sync_mode_menu), "changed",
                     G_CALLBACK(sync_mode_changed), (gpointer) ctk_gvo_sync);

    /* Sync Format */

    menu = start_menu("Sync Format: ", table, row);
    
    for (i = 0; syncFormatNames[i].name; i++) {
        ctk_drop_down_menu_append_item(CTK_DROP_DOWN_MENU(menu),
                                       syncFormatNames[i].name,
                                       syncFormatNames[i].format);
    }
    
    finish_menu(menu, table, row);
    row++;

    ctk_gvo_sync->sync_format_menu = menu;

    ctk_config_set_tooltip(ctk_config, CTK_DROP_DOWN_MENU(menu)->option_menu,
                           __sync_format_help);

    init_sync_format_menu(ctk_gvo_sync);

    g_signal_connect(G_OBJECT(ctk_gvo_sync->sync_format_menu),
                     "changed", G_CALLBACK(sync_format_changed),
                     (gpointer) ctk_gvo_sync);

    /* Sync Status */

    label = gtk_label_new("Sync Status:");
    gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
    gtk_table_attach(GTK_TABLE(table), label, 0, 1, row, row+1,
                     GTK_FILL, GTK_FILL, TABLE_PADDING, TABLE_PADDING);

    ctk_gvo_sync->sync_lock_status_text = gtk_label_new("");

    update_sync_lock_status_text(ctk_gvo_sync);

    hbox = gtk_hbox_new(FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), ctk_gvo_sync->sync_lock_status_text,
                       FALSE, FALSE, 0);

    gtk_table_attach(GTK_TABLE(table), hbox,
                     1, 2, row, row+1,
                     GTK_FILL /*| GTK_EXPAND*/, GTK_FILL,
                     TABLE_PADDING, TABLE_PADDING);
    row++;


    /*
     * Synchronization Skew (Delay/Advance)
     */

    /* NV_CTRL_GVO_SYNC_DELAY_PIXELS */

    ret = NvCtrlGetValidAttributeValues(handle, NV_CTRL_GVO_SYNC_DELAY_PIXELS,
                                        &valid);

    if ((ret == NvCtrlSuccess) && (valid.type == ATTRIBUTE_TYPE_RANGE)) {
        ret = NvCtrlGetAttribute(handle, NV_CTRL_GVO_SYNC_DELAY_PIXELS, &val);
        if (ret != NvCtrlSuccess) val = 0;

        if (ctk_gvo_sync->caps & NV_CTRL_GVO_CAPABILITIES_ADVANCE_SYNC_SKEW) {
            label = gtk_label_new("HSync Advance:");
            help_text = __hsync_advance_help;
        } else {
            label = gtk_label_new("HSync Delay:");
            help_text = __hsync_delay_help;
        }

        gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
        gtk_table_attach(GTK_TABLE(table), label, 0, 1, row, row+1,
                         GTK_FILL, GTK_FILL, TABLE_PADDING, TABLE_PADDING);

        ctk_gvo_sync->hsync_delay_spin_button =
            gtk_spin_button_new_with_range(valid.u.range.min,
                                           valid.u.range.max, 1);

        ctk_config_set_tooltip(ctk_config,
                               ctk_gvo_sync->hsync_delay_spin_button,
                               help_text);

        gtk_spin_button_set_value
            (GTK_SPIN_BUTTON(ctk_gvo_sync->hsync_delay_spin_button), val);
    
        g_signal_connect(G_OBJECT(ctk_gvo_sync->hsync_delay_spin_button),
                         "value-changed",
                         G_CALLBACK(hsync_delay_changed), ctk_gvo_sync);

        hbox = gtk_hbox_new(FALSE, 5);
        gtk_box_pack_start(GTK_BOX(hbox),
                           ctk_gvo_sync->hsync_delay_spin_button,
                           FALSE, FALSE, 0);

        label = gtk_label_new("(pixels)");
        gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
        gtk_box_pack_start(GTK_BOX(hbox), label,
                           FALSE, FALSE, 0);

        gtk_table_attach(GTK_TABLE(table), hbox,
                         1, 2, row, row+1,
                         GTK_FILL /*| GTK_EXPAND*/, GTK_FILL,
                         TABLE_PADDING, TABLE_PADDING);
        row++;
    }

    /* NV_CTRL_GVO_SYNC_DELAY_LINES */
    
    ret = NvCtrlGetValidAttributeValues(handle, NV_CTRL_GVO_SYNC_DELAY_LINES,
                                        &valid);

    if ((ret == NvCtrlSuccess) && (valid.type == ATTRIBUTE_TYPE_RANGE)) {
        ret = NvCtrlGetAttribute(handle, NV_CTRL_GVO_SYNC_DELAY_LINES, &val);
        if (ret != NvCtrlSuccess) val = 0;

        if (ctk_gvo_sync->caps & NV_CTRL_GVO_CAPABILITIES_ADVANCE_SYNC_SKEW) {
            label = gtk_label_new("VSync Advance:");
            help_text = __vsync_advance_help;
        } else {
            label = gtk_label_new("VSync Delay:");
            help_text = __vsync_delay_help;
        }

        gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
        gtk_table_attach(GTK_TABLE(table), label, 0, 1, row, row+1,
                         GTK_FILL, GTK_FILL, TABLE_PADDING, TABLE_PADDING);

        ctk_gvo_sync->vsync_delay_spin_button =
            gtk_spin_button_new_with_range(valid.u.range.min,
                                           valid.u.range.max, 1);

        ctk_config_set_tooltip(ctk_config,
                               ctk_gvo_sync->vsync_delay_spin_button,
                               help_text);

        gtk_spin_button_set_value
            (GTK_SPIN_BUTTON(ctk_gvo_sync->vsync_delay_spin_button), val);
        
        g_signal_connect(G_OBJECT(ctk_gvo_sync->vsync_delay_spin_button),
                         "value-changed",
                         G_CALLBACK(vsync_delay_changed), ctk_gvo_sync);

        hbox = gtk_hbox_new(FALSE, 5);
        gtk_box_pack_start(GTK_BOX(hbox),
                           ctk_gvo_sync->vsync_delay_spin_button,
                           FALSE, FALSE, 0);

        label = gtk_label_new("(lines)");
        gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
        gtk_box_pack_start(GTK_BOX(hbox), label,
                           FALSE, FALSE, 0);

        gtk_table_attach(GTK_TABLE(table), hbox,
                         1, 2, row, row+1,
                         GTK_FILL /*| GTK_EXPAND*/, GTK_FILL,
                         TABLE_PADDING, TABLE_PADDING);
        row++;
    }

    /* create the watch cursor (for use when the "Detect" button is toggled" */
    
    ctk_gvo_sync->wait_cursor = gdk_cursor_new(GDK_WATCH);

    /* Set UI sensitivity */

    update_gvo_sync_sensitivity(ctk_gvo_sync);

    /* Start listening for events */

    register_for_gvo_sync_events(ctk_gvo_sync);

    /* show the page */

    gtk_widget_show_all(GTK_WIDGET(object));

    return GTK_WIDGET(object);

} /* ctk_gvo_sync_new() */



/*
 * start_menu() - Start the creation of a labled dropdown menu.  (Packs
 * the dropdown label into the table row.
 */

static GtkWidget *start_menu(const gchar *name, GtkWidget *table,
                             const gint row)
{
    GtkWidget *menu, *label, *alignment;
    
    label = gtk_label_new(name);
    alignment = gtk_alignment_new(0, 0, 0, 0);
    gtk_container_add(GTK_CONTAINER(alignment), label);

    gtk_table_attach(GTK_TABLE(table),
                     alignment, 0, 1, row, row+1, GTK_FILL, GTK_FILL,
                     TABLE_PADDING, TABLE_PADDING);
    
    menu = ctk_drop_down_menu_new(CTK_DROP_DOWN_MENU_FLAG_MONOSPACE);
    
    return menu;
}



/*
 * finish_menu() - Finish/Finalize a dropdown menu. (Packs the menu in
 * the table row.)
 */

static void finish_menu(GtkWidget *menu, GtkWidget *table, const gint row)
{
    gtk_widget_show_all(menu);

    gtk_table_attach(GTK_TABLE(table), menu, 1, 2, row, row+1,
                     GTK_FILL | GTK_EXPAND, GTK_FILL,
                     TABLE_PADDING, TABLE_PADDING);
}




/**** Initialization Functions ***********************************************/

/*
 * query_init_gvo_sync_state() - Query the initial GVO state so we can setup
 * the UI correctly.
 */

static gboolean query_init_gvo_sync_state(CtkGvoSync *ctk_gvo_sync)
{
    gint val;
    ReturnStatus ret;


    /* Check if this screen supports GVO */
    
    ret = NvCtrlGetAttribute(ctk_gvo_sync->handle, NV_CTRL_GVO_SUPPORTED,
                             &val);
    if ((ret != NvCtrlSuccess) || (val != NV_CTRL_GVO_SUPPORTED_TRUE)) {
        /* GVO not available */
        return FALSE;
    }

    /* Get this GVO device's capabilities */
    
    ret = NvCtrlGetAttribute(ctk_gvo_sync->handle, NV_CTRL_GVO_CAPABILITIES,
                             &val);
    if (ret != NvCtrlSuccess) return FALSE;
    ctk_gvo_sync->caps = val;

    /* Query the current input video formats */

    ret = NvCtrlGetAttribute(ctk_gvo_sync->handle,
                             NV_CTRL_GVIO_DETECTED_VIDEO_FORMAT, &val);
    if (ret != NvCtrlSuccess) {
        val = NV_CTRL_GVIO_VIDEO_FORMAT_NONE;
    }
    ctk_gvo_sync->input_video_format = val;

    /* Query the sync mode */

    ret = NvCtrlGetAttribute(ctk_gvo_sync->handle, NV_CTRL_GVO_SYNC_MODE,
                             &val);
    if (ret != NvCtrlSuccess) {
        val = NV_CTRL_GVO_SYNC_MODE_FREE_RUNNING;
    }
    ctk_gvo_sync->sync_mode = val;

    /* query COMPOSITE_SYNC_INPUT_DETECTED */

    ret = NvCtrlGetAttribute(ctk_gvo_sync->handle,
                             NV_CTRL_GVO_COMPOSITE_SYNC_INPUT_DETECTED, &val);
    if (ret != NvCtrlSuccess) {
        val = NV_CTRL_GVO_COMPOSITE_SYNC_INPUT_DETECTED_FALSE;
    }
    ctk_gvo_sync->comp_sync_input_detected = val;

    /* query COMPOSITE_SYNC_INPUT_DETECT_MODE */

    ret = NvCtrlGetAttribute(ctk_gvo_sync->handle, 
                             NV_CTRL_GVO_COMPOSITE_SYNC_INPUT_DETECT_MODE,
                             &val);
    if (ret != NvCtrlSuccess) {
        val = NV_CTRL_GVO_COMPOSITE_SYNC_INPUT_DETECT_MODE_AUTO;
    }
    ctk_gvo_sync->comp_mode = val;
    
    /* query SDI_SYNC_INPUT_DETECTED */

    ret = NvCtrlGetAttribute(ctk_gvo_sync->handle,
                             NV_CTRL_GVO_SDI_SYNC_INPUT_DETECTED, &val);
    if (ret != NvCtrlSuccess) {
        val = NV_CTRL_GVO_SDI_SYNC_INPUT_DETECTED_NONE;
    }
    ctk_gvo_sync->sdi_sync_input_detected = val;

    /* query sync source */

    ret = NvCtrlGetAttribute(ctk_gvo_sync->handle,
                             NV_CTRL_GVO_SYNC_SOURCE,
                             &val);
    if (ret != NvCtrlSuccess) {
        val = NV_CTRL_GVO_SYNC_SOURCE_SDI;
    }
    ctk_gvo_sync->sync_source = val;

    /* Query framelock/genlock status */

    ret = NvCtrlGetAttribute(ctk_gvo_sync->handle,
                             NV_CTRL_GVO_SYNC_LOCK_STATUS,
                             &val);
    if (ret != NvCtrlSuccess) {
        val = NV_CTRL_GVO_SYNC_LOCK_STATUS_UNLOCKED;
    }
    ctk_gvo_sync->sync_lock_status = val;

    return TRUE;

} /* query_init_gvo_sync_state() */



/*
 * init_composite_termination() - Initialize the state of the composite
 * termination button.
 */

static void init_composite_termination(CtkGvoSync *ctk_gvo_sync)
{
    ReturnStatus ret;
    gint val;

    if (!ctk_gvo_sync->composite_termination_button) return;

    ret = NvCtrlGetAttribute(ctk_gvo_sync->handle,
                             NV_CTRL_GVO_COMPOSITE_TERMINATION, &val);
    if (ret != NvCtrlSuccess) {
        val = NV_CTRL_GVO_COMPOSITE_TERMINATION_DISABLE;
    }
    
    gtk_toggle_button_set_active
        (GTK_TOGGLE_BUTTON(ctk_gvo_sync->composite_termination_button),
         val == NV_CTRL_GVO_COMPOSITE_TERMINATION_ENABLE);

} /* init_composite_termination() */



/*
 * init_sync_format_menu() - initialize the sync format menu
 */

static void init_sync_format_menu(CtkGvoSync *ctk_gvo_sync)
{
    set_sync_format_menu(ctk_gvo_sync);

} /* init_sync_format_menu() */



/*
 * register_for_gvo_sync_events() - Configure ctk_gvo_sync object to listen
 * for GVO synchronization related evens.
 */

static void register_for_gvo_sync_events(CtkGvoSync *ctk_gvo_sync)
{
    g_signal_connect(G_OBJECT(ctk_gvo_sync->ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_GVO_SYNC_MODE),
                     G_CALLBACK(gvo_sync_event_received),
                     (gpointer) ctk_gvo_sync);

    g_signal_connect(G_OBJECT(ctk_gvo_sync->ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_GVO_SYNC_SOURCE),
                     G_CALLBACK(gvo_sync_event_received),
                     (gpointer) ctk_gvo_sync);

    g_signal_connect(G_OBJECT(ctk_gvo_sync->ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_GVO_COMPOSITE_SYNC_INPUT_DETECT_MODE),
                     G_CALLBACK(gvo_sync_event_received),
                     (gpointer) ctk_gvo_sync);

    g_signal_connect(G_OBJECT(ctk_gvo_sync->ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_GVO_SYNC_DELAY_PIXELS),
                     G_CALLBACK(gvo_sync_event_received),
                     (gpointer) ctk_gvo_sync);

    g_signal_connect(G_OBJECT(ctk_gvo_sync->ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_GVO_SYNC_DELAY_LINES),
                     G_CALLBACK(gvo_sync_event_received),
                     (gpointer) ctk_gvo_sync);

    g_signal_connect(G_OBJECT(ctk_gvo_sync->ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_GVO_COMPOSITE_TERMINATION),
                     G_CALLBACK(gvo_sync_event_received),
                     (gpointer) ctk_gvo_sync);

} /* register_for_gvo_sync_events() */




/**** Common Update Functions ************************************************/

/*
 * update_gvo_sync_sensitivity() - Updates the sensitivity of various UI
 * wigets based on whether or not an input sync signal is detected.
 *
 */

static void update_gvo_sync_sensitivity(CtkGvoSync *ctk_gvo_sync)
{
    gboolean sensitive;

    /* Allow selection of the sync format if we're not free-running */

    sensitive = (ctk_gvo_sync->sync_mode != NV_CTRL_GVO_SYNC_MODE_FREE_RUNNING);

    gtk_widget_set_sensitive(ctk_gvo_sync->sync_format_menu, sensitive);

    /* Update options that require a valid sync signal */

    sensitive = (sensitive && sync_signal_detected(ctk_gvo_sync));

    if (ctk_gvo_sync->hsync_delay_spin_button) {
        gtk_widget_set_sensitive(ctk_gvo_sync->hsync_delay_spin_button,
                                 sensitive);
    }
    if (ctk_gvo_sync->vsync_delay_spin_button) {
        gtk_widget_set_sensitive(ctk_gvo_sync->vsync_delay_spin_button,
                                 sensitive);
    }

} /* update_gvo_sync_sensitivity() */



/*
 * update_input_video_format_text_entry() - Displays the currently detected
 * input video format.
 */

static void update_input_video_format_text_entry(CtkGvoSync *ctk_gvo_sync)
{
    gint i;
    const gchar *str;
    
    if (ctk_gvo_sync->sync_mode == NV_CTRL_GVO_SYNC_MODE_FREE_RUNNING) {
        str = "Free Running";
    } else {
        str = "No incoming signal detected";
        for (i = 0; videoFormatNames[i].name; i++) {
            if (videoFormatNames[i].format == ctk_gvo_sync->input_video_format) {
                str = videoFormatNames[i].name;
            }
        }
    }
    gtk_entry_set_text(GTK_ENTRY(ctk_gvo_sync->input_video_format_text_entry), str);
    
} /* update_input_video_format_text_entry() */



/*
 * post_composite_termination_toggled() - Call this function after
 * the composite termination attribute has changed.
 */

static void post_composite_termination_toggled(CtkGvoSync *ctk_gvo_sync,
                                               gboolean enabled)
{
    /* update the statusbar */
    
    ctk_config_statusbar_message(ctk_gvo_sync->ctk_config,
                                 "Composite Termination %s.",
                                 enabled ? "Enabled" : "Disabled");

} /* post_composite_termination_toggled() */



/*
 * post_sync_mode_menu_changed() - Call this function after the sync mode
 * menu has changed.
 *
 */

static void post_sync_mode_menu_changed(CtkGvoSync *ctk_gvo_sync, gint value)
{
    char *name;

    /* Update the UI */

    update_input_video_format_text_entry(ctk_gvo_sync);
    update_gvo_sync_sensitivity(ctk_gvo_sync);
    
    switch (value) {
    case NV_CTRL_GVO_SYNC_MODE_FREE_RUNNING: name = "Free Running"; break;
    case NV_CTRL_GVO_SYNC_MODE_GENLOCK:      name = "GenLock";      break;
    case NV_CTRL_GVO_SYNC_MODE_FRAMELOCK:    name = "FrameLock";    break;
    default: return;
    }

    ctk_config_statusbar_message(ctk_gvo_sync->ctk_config,
                                 "Sync Mode set to %s.", name);    

} /* post_sync_mode_menu_changed() */



/*
 * post_sync_format_menu_changed() - Call this function after teh sync
 * format has changed.
 */

static void post_sync_format_menu_changed(CtkGvoSync *ctk_gvo_sync)
{
    const char *name = "Unknown";
    gint value;
    gint i;

    
    /* Update the status bar */

    value = get_current_sync_format(ctk_gvo_sync);

    for (i = 0; syncFormatNames[i].name; i++) {
        if (syncFormatNames[i].format == value) {
            name = syncFormatNames[i].name;
            break;
        }
    }

    ctk_config_statusbar_message(ctk_gvo_sync->ctk_config,
                                 "Sync Format set to \"%s\".", name);

} /* post_sync_format_menu_changed() */




/**** UI Event Handlers ************************************************/

/*
 * detect_input_toggled() - if the Detect button is enabled, then enable
 * INPUT_VIDEO_FORMAT_REACQUIRE, make everything else insensitive,
 * switch to the wait cursor, and queue detect_input_done() to be
 * called.
 */

static void detect_input_toggled(GtkToggleButton *togglebutton,
                                 CtkGvoSync *ctk_gvo_sync)
{
    gboolean enabled;

    enabled = gtk_toggle_button_get_active(togglebutton);

    if (!enabled) return;
        
    /* grab the server */
    
    gtk_grab_add(ctk_gvo_sync->input_video_format_detect_button);

    /* change the cursor */

    gdk_window_set_cursor
        ((GTK_WIDGET(ctk_gvo_sync->parent_window))->window,
         ctk_gvo_sync->wait_cursor);
    
    /* make all other widgets insensitive */

    gtk_widget_set_sensitive(ctk_gvo_sync->frame, FALSE);
    
    /* enable REACQUIRE */

    NvCtrlSetAttribute(ctk_gvo_sync->handle,
                       NV_CTRL_GVO_INPUT_VIDEO_FORMAT_REACQUIRE,
                       NV_CTRL_GVO_INPUT_VIDEO_FORMAT_REACQUIRE_TRUE);
    
    /* update the statusbar */

    ctk_config_statusbar_message(ctk_gvo_sync->ctk_config,
                                 "Detecting incoming signal...");
    
    /* register the "done" function */
    
    g_timeout_add(DEFAULT_DETECT_INPUT_TIME_INTERVAL,
                  detect_input_done, (gpointer) ctk_gvo_sync);
    
} /* detect_input_toggled() */



/*
 * detect_input_done() - done detecting; disable REACQUIRE, make all
 * widgets sensitive again, and probe the new state
 */

static gint detect_input_done(gpointer data)
{
    CtkGvoSync *ctk_gvo_sync = CTK_GVO_SYNC(data);
    
    /* disable REACQUIRE */

    NvCtrlSetAttribute(ctk_gvo_sync->handle,
                       NV_CTRL_GVO_INPUT_VIDEO_FORMAT_REACQUIRE,
                       NV_CTRL_GVO_INPUT_VIDEO_FORMAT_REACQUIRE_FALSE);
    
    /* reprobe */
    
    ctk_gvo_banner_probe((gpointer)(ctk_gvo_sync->gvo_parent->banner));
    
    /* un-press the detect button */
    
    g_signal_handlers_block_by_func
        (G_OBJECT(ctk_gvo_sync->input_video_format_detect_button),
         G_CALLBACK(detect_input_toggled),
         (gpointer) ctk_gvo_sync);
    
    gtk_toggle_button_set_active
        (GTK_TOGGLE_BUTTON(ctk_gvo_sync->input_video_format_detect_button), FALSE);

    g_signal_handlers_unblock_by_func
        (G_OBJECT(ctk_gvo_sync->input_video_format_detect_button),
         G_CALLBACK(detect_input_toggled),
         (gpointer) ctk_gvo_sync);
    
    /* update the status bar */
    
    ctk_config_statusbar_message(ctk_gvo_sync->ctk_config,
                                 "Done detecting incoming signal.");
    
    /* restore sensitivity */
    
    gtk_widget_set_sensitive(ctk_gvo_sync->frame, TRUE);

    /* restore the cursor */

    gdk_window_set_cursor((GTK_WIDGET(ctk_gvo_sync->parent_window))->window,
                          NULL);

    /* ungrab the server */
    
    gtk_grab_remove(ctk_gvo_sync->input_video_format_detect_button);
    
    return FALSE;

} /* detect_input_done() */



/*
 * composite_termination_toggled() - Called when the user clicks
 * on the "Enable Composite Termination" check button.
 */

static void composite_termination_toggled(GtkWidget *button,
                                          CtkGvoSync *ctk_gvo_sync)
{
    gboolean enabled;
    
    enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));

    NvCtrlSetAttribute(ctk_gvo_sync->handle,
                       NV_CTRL_GVO_COMPOSITE_TERMINATION,
                       (enabled ? NV_CTRL_GVO_COMPOSITE_TERMINATION_ENABLE :
                        NV_CTRL_GVO_COMPOSITE_TERMINATION_DISABLE));

    post_composite_termination_toggled(ctk_gvo_sync, enabled);

} /* composite_termination_toggled() */



/*
 * sync_mode_changed() - callback when the user makes a selection from the
 * sync mode menu.
 */

static void sync_mode_changed(CtkDropDownMenu *menu, gpointer user_data)
{
    CtkGvoSync *ctk_gvo_sync = CTK_GVO_SYNC(user_data);
    gint value;
    char *name;
    
    value = ctk_drop_down_menu_get_current_value(menu);
    
    switch (value) {
    case NV_CTRL_GVO_SYNC_MODE_FREE_RUNNING: name = "Free Running"; break;
    case NV_CTRL_GVO_SYNC_MODE_GENLOCK:      name = "GenLock";      break;
    case NV_CTRL_GVO_SYNC_MODE_FRAMELOCK:    name = "FrameLock";    break;
    default: return;
    }
    
    NvCtrlSetAttribute(ctk_gvo_sync->handle, NV_CTRL_GVO_SYNC_MODE, value);
    
    if (value != NV_CTRL_GVO_SYNC_MODE_FREE_RUNNING) {
        NvCtrlSetAttribute(ctk_gvo_sync->handle, NV_CTRL_GVO_SYNC_SOURCE,
                           ctk_gvo_sync->sync_source);
    }

    ctk_gvo_sync->sync_mode = value;

    post_sync_mode_menu_changed(ctk_gvo_sync, value);
    
} /* sync_mode_changed() */



/*
 * sync_format_changed() - callback when the user makes a selection from the
 * sync format menu (from the UI.)
 */

static void sync_format_changed(CtkDropDownMenu *menu, gpointer user_data)
{
    CtkGvoSync *ctk_gvo_sync = CTK_GVO_SYNC(user_data);
    gint value, sync_source, comp_mode;

    value = ctk_drop_down_menu_get_current_value(menu);
    
    switch (value) {
    case SYNC_FORMAT_SDI:
        sync_source = NV_CTRL_GVO_SYNC_SOURCE_SDI;
        comp_mode = NV_CTRL_GVO_COMPOSITE_SYNC_INPUT_DETECT_MODE_AUTO;
        break;
    case SYNC_FORMAT_COMP_AUTO:
        sync_source = NV_CTRL_GVO_SYNC_SOURCE_COMPOSITE;
        comp_mode = NV_CTRL_GVO_COMPOSITE_SYNC_INPUT_DETECT_MODE_AUTO;
        break;
    case SYNC_FORMAT_COMP_BI_LEVEL:
        sync_source = NV_CTRL_GVO_SYNC_SOURCE_COMPOSITE;
        comp_mode = NV_CTRL_GVO_COMPOSITE_SYNC_INPUT_DETECT_MODE_BI_LEVEL;
        break;
    case SYNC_FORMAT_COMP_TRI_LEVEL:
        sync_source = NV_CTRL_GVO_SYNC_SOURCE_COMPOSITE;
        comp_mode = NV_CTRL_GVO_COMPOSITE_SYNC_INPUT_DETECT_MODE_TRI_LEVEL;
        break;
    default:
        return;
    }

    ctk_gvo_sync->sync_source = sync_source;
    ctk_gvo_sync->comp_mode = comp_mode;

    NvCtrlSetAttribute(ctk_gvo_sync->handle,
                       NV_CTRL_GVO_SYNC_SOURCE, sync_source);

    NvCtrlSetAttribute(ctk_gvo_sync->handle, 
                       NV_CTRL_GVO_COMPOSITE_SYNC_INPUT_DETECT_MODE,
                       comp_mode);

    post_sync_format_menu_changed(ctk_gvo_sync);
    
} /* sync_format_changed() */



/*
 * hsync_delay_changed() - UI Callback function for when the user changes
 * the hsync delay.
 */

static void hsync_delay_changed(GtkSpinButton *spinbutton, gpointer user_data)
{
    CtkGvoSync *ctk_gvo_sync = CTK_GVO_SYNC(user_data);
    gint val;
    
    val = gtk_spin_button_get_value(spinbutton);
    
    NvCtrlSetAttribute(ctk_gvo_sync->handle, NV_CTRL_GVO_SYNC_DELAY_PIXELS,
                       val);
    
} /* hsync_delay_changed() */



/*
 * vsync_delay_changed() - UI Callback function for when the user changes
 * the vsync delay.
 */

static void vsync_delay_changed(GtkSpinButton *spinbutton, gpointer user_data)
{
    CtkGvoSync *ctk_gvo_sync = CTK_GVO_SYNC(user_data);
    gint val;
    
    val = gtk_spin_button_get_value(spinbutton);
    
    NvCtrlSetAttribute(ctk_gvo_sync->handle, NV_CTRL_GVO_SYNC_DELAY_LINES,
                       val);
    
} /* vsync_delay_changed() */




/**** NV-CONTROL/Misc Event Handlers *****************************************/

/*
 * gvo_sync_event_received() - Callback function for handling
 * NV-CONTROL events.
 */

static void gvo_sync_event_received(GtkObject *object,
                                    gpointer arg1,
                                    gpointer user_data)
{
    CtkEventStruct *event_struct = (CtkEventStruct *) arg1;
    CtkGvoSync *ctk_gvo_sync = CTK_GVO_SYNC(user_data);
    GtkWidget *widget;    
    gint attribute = event_struct->attribute;
    gint value = event_struct->value;
    gboolean update_sync_format = FALSE;

    switch (attribute) {
    case NV_CTRL_GVO_SYNC_MODE:
        ctk_gvo_sync->sync_mode = value;
        widget = ctk_gvo_sync->sync_mode_menu;
        
        g_signal_handlers_block_by_func(G_OBJECT(widget),
                                        G_CALLBACK(sync_mode_changed),
                                        (gpointer) ctk_gvo_sync);

        ctk_drop_down_menu_set_current_value
            (CTK_DROP_DOWN_MENU(widget), value);
        
        g_signal_handlers_unblock_by_func(G_OBJECT(widget),
                                          G_CALLBACK(sync_mode_changed),
                                          (gpointer) ctk_gvo_sync);
            
        post_sync_mode_menu_changed(ctk_gvo_sync, value);
        break;

    case NV_CTRL_GVO_SYNC_SOURCE:
        ctk_gvo_sync->sync_source = value;
        update_sync_format = TRUE;
        break;

    case NV_CTRL_GVO_COMPOSITE_SYNC_INPUT_DETECT_MODE:
        ctk_gvo_sync->comp_mode = value;
        update_sync_format = TRUE;
        break;

    case NV_CTRL_GVO_SYNC_DELAY_PIXELS:
        widget = ctk_gvo_sync->hsync_delay_spin_button;

        if (widget) {
            g_signal_handlers_block_by_func(G_OBJECT(widget),
                                            G_CALLBACK(hsync_delay_changed),
                                            (gpointer) ctk_gvo_sync);
        
            gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), value);
        
            g_signal_handlers_unblock_by_func(G_OBJECT(widget),
                                              G_CALLBACK(hsync_delay_changed),
                                              (gpointer) ctk_gvo_sync);
        }
        break;
        
    case NV_CTRL_GVO_SYNC_DELAY_LINES:
        widget = ctk_gvo_sync->vsync_delay_spin_button;
        
        if (widget) {
            g_signal_handlers_block_by_func(G_OBJECT(widget),
                                            G_CALLBACK(vsync_delay_changed),
                                            (gpointer) ctk_gvo_sync);
        
            gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), value);
            
            g_signal_handlers_unblock_by_func(G_OBJECT(widget),
                                              G_CALLBACK(vsync_delay_changed),
                                              (gpointer) ctk_gvo_sync);
        }
        break;

    case NV_CTRL_GVO_COMPOSITE_TERMINATION:
        widget = ctk_gvo_sync->composite_termination_button;

        if (widget) {
            g_signal_handlers_block_by_func
                (G_OBJECT(widget),
                 G_CALLBACK(composite_termination_toggled),
                 ctk_gvo_sync);
            
            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), value);
            
            g_signal_handlers_unblock_by_func
                (G_OBJECT(widget),
                 G_CALLBACK(composite_termination_toggled),
                 ctk_gvo_sync);

            post_composite_termination_toggled(ctk_gvo_sync, value);
        }
        break;
    }


    /* Update the sync format menu */
    if (update_sync_format) {
        widget = ctk_gvo_sync->sync_format_menu;
        
        g_signal_handlers_block_by_func(G_OBJECT(widget),
                                        G_CALLBACK(sync_format_changed),
                                        (gpointer) ctk_gvo_sync);

        set_sync_format_menu(ctk_gvo_sync);
        
        g_signal_handlers_unblock_by_func(G_OBJECT(widget),
                                          G_CALLBACK(sync_format_changed),
                                          (gpointer) ctk_gvo_sync);

        post_sync_format_menu_changed(ctk_gvo_sync);
    }

} /* gvo_sync_event_received() */



/*
 * gvo_sync_probe_callback() - Callback function for when the
 * sync signals are probed.
 */

static gint gvo_sync_probe_callback(gpointer data)
{
    CtkGvoSync *ctk_gvo_sync = CTK_GVO_SYNC(data);
    CtkGvo *ctk_gvo = ctk_gvo_sync->gvo_parent;

    /* update our copies of some SDI state variables */

    ctk_gvo_sync->input_video_format =
        CTK_GVO_BANNER(ctk_gvo->banner)->input_video_format;
    
    ctk_gvo_sync->comp_sync_input_detected =
        CTK_GVO_BANNER(ctk_gvo->banner)->composite_sync_input_detected;

    ctk_gvo_sync->sdi_sync_input_detected =
        CTK_GVO_BANNER(ctk_gvo->banner)->sdi_sync_input_detected;

    ctk_gvo_sync->sync_lock_status =
        CTK_GVO_BANNER(ctk_gvo->banner)->sync_lock_status;

    /* update the UI */
    
    update_input_video_format_text_entry(ctk_gvo_sync);

    update_sync_lock_status_text(ctk_gvo_sync);

    update_gvo_sync_sensitivity(ctk_gvo_sync);

    return TRUE;
    
} /* gvo_sync_probe_callback() */



/*
 * ctk_gvo_sync_select() - Callback function for when the GVO
 * sync config page is selected.
 */
void ctk_gvo_sync_select(GtkWidget *widget)
{
    CtkGvoSync *ctk_gvo_sync = CTK_GVO_SYNC(widget);
    CtkGvo *ctk_gvo = ctk_gvo_sync->gvo_parent;

    /* Grab the GVO banner */

    ctk_gvo_banner_set_parent(CTK_GVO_BANNER(ctk_gvo->banner),
                              ctk_gvo_sync->banner_box,
                              gvo_sync_probe_callback, ctk_gvo_sync);
}



/*
 * ctk_gvo_sync_unselect() - Callback function for when the GVO
 * sync config page is unselected.
 */

void ctk_gvo_sync_unselect(GtkWidget *widget)
{
    CtkGvoSync *ctk_gvo_sync = CTK_GVO_SYNC(widget);
    CtkGvo *ctk_gvo = ctk_gvo_sync->gvo_parent;

    /* Release the GVO banner */

    ctk_gvo_banner_set_parent(CTK_GVO_BANNER(ctk_gvo->banner),
                              NULL, NULL, NULL);
}



/*
 * ctk_gvo_sync_create_help() - GVO sync configuration help page
 * creation.
 */

GtkTextBuffer* ctk_gvo_sync_create_help(GtkTextTagTable *table,
                                        CtkGvoSync *ctk_gvo_sync)
{
    GtkTextIter i;
    GtkTextBuffer *b;

    b = gtk_text_buffer_new(table);
    
    gtk_text_buffer_get_iter_at_offset(b, &i, 0);

    ctk_help_title(b, &i, "GVO (Graphics to Video Out) Synchronization "
                   "options");
    ctk_help_para(b, &i, "This page gives access to configuration of the "
                  "SDI synchronization options.");

    ctk_help_heading(b, &i, "Input Video Format");
    ctk_help_para(b, &i, __input_video_format_help);
    ctk_help_heading(b, &i, "Input Video Format Detect");
    ctk_help_para(b, &i, __input_video_format_detect_help);
    ctk_help_heading(b, &i, "Composite Termination");
    ctk_help_para(b, &i, "%s.  This allows the composite signal to be daisy "
                  "chained from a server load.",
                  __composite_termination_help);
    ctk_help_heading(b, &i, "Sync Mode");
    ctk_help_para(b, &i, __sync_mode_help);
    ctk_help_heading(b, &i, "Sync Format");
    ctk_help_para(b, &i, __sync_format_help);
    ctk_help_heading(b, &i, "Sync Status");
    ctk_help_para(b, &i, __sync_status_help);

    if ( ctk_gvo_sync->caps & NV_CTRL_GVO_CAPABILITIES_ADVANCE_SYNC_SKEW ) {
        ctk_help_heading(b, &i, "HSync Advance");
        ctk_help_para(b, &i, __hsync_advance_help);
        ctk_help_heading(b, &i, "VSync Advance");
        ctk_help_para(b, &i, __vsync_advance_help);
    } else {
        ctk_help_heading(b, &i, "HSync Delay");
        ctk_help_para(b, &i, __hsync_delay_help);
        ctk_help_heading(b, &i, "VSync Delay");
        ctk_help_para(b, &i, __vsync_delay_help);
    }

    ctk_help_finish(b);

    return b;
}
