GtkWidget* ctk_display_device_new(CtrlTarget *ctrl_target,
                                  CtkConfig *ctk_config,
                                  CtkEvent *ctk_event,
                                  CtkEvent *ctk_event_gpu,
                                  char *name,
                                  char *typeBaseName,
                                  ParsedAttribute *p)
{
    GObject *object;
    CtkDisplayDevice *ctk_object;
    GtkWidget *banner;
    GtkWidget *hbox, *tmpbox;

    GtkWidget *alignment;
    GtkWidget *notebook;
    GtkWidget *nbox;
    GtkWidget *align;
    GtkWidget *label;
    GtkWidget *hseparator;
    GtkWidget *button;
    gchar *str;
    int i;

    object = g_object_new(CTK_TYPE_DISPLAY_DEVICE, NULL);
    if (!object) return NULL;

    ctk_object = CTK_DISPLAY_DEVICE(object);
    ctk_object->ctrl_target = ctrl_target;
    ctk_object->ctk_event = ctk_event;
    ctk_object->ctk_event_gpu = ctk_event_gpu;
    ctk_object->ctk_config = ctk_config;
    ctk_object->name = g_strdup(name);
    ctk_object->color_correction_available = FALSE;

    gtk_box_set_spacing(GTK_BOX(object), 10);

    /* Banner */

    if (strcmp(typeBaseName, "CRT") == 0) {
        banner = ctk_banner_image_new(BANNER_ARTWORK_CRT);
    } else {
        banner = ctk_banner_image_new(BANNER_ARTWORK_DFP);
    }
    gtk_box_pack_start(GTK_BOX(object), banner, FALSE, FALSE, 0);

    /* Create tabbed notebook for widget */

    notebook = gtk_notebook_new();
    gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_TOP);
    gtk_box_pack_start(GTK_BOX(object), notebook, TRUE, TRUE, 0);

    /* Create first tab for device info */

    nbox = gtk_vbox_new(FALSE, FRAME_PADDING);
    gtk_container_set_border_width(GTK_CONTAINER(nbox), FRAME_PADDING);
    gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nbox,
                             gtk_label_new("Information"));


    /* Device info */

    hbox = gtk_hbox_new(FALSE, 0);
    gtk_box_pack_start(GTK_BOX(nbox), hbox, FALSE, FALSE, 0);

    label = gtk_label_new("Display Device Information");
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);

    hseparator = gtk_hseparator_new();
    gtk_box_pack_start(GTK_BOX(hbox), hseparator, TRUE, TRUE, 5);


    /* create the hbox to store device info */

    hbox = gtk_hbox_new(FALSE, FRAME_PADDING);
    gtk_box_pack_start(GTK_BOX(nbox), hbox, FALSE, FALSE, FRAME_PADDING);

    /*
     * insert a vbox between the frame and the widgets, so that the
     * widgets don't expand to fill all of the space within the
     * frame
     */

    tmpbox = gtk_vbox_new(FALSE, FRAME_PADDING);
    gtk_container_set_border_width(GTK_CONTAINER(tmpbox), FRAME_PADDING);
    gtk_container_add(GTK_CONTAINER(hbox), tmpbox);

    /* Create and add the information widgets */

    ctk_object->num_info_entries = ARRAY_LEN(__info_entry_data);
    ctk_object->info_entries = calloc(ctk_object->num_info_entries,
                                      sizeof(InfoEntry));
    if (!ctk_object->info_entries) {
        ctk_object->num_info_entries = 0;
    }

    for (i = 0; i < ctk_object->num_info_entries; i++) {
        InfoEntryData *entryData = __info_entry_data+i;
        InfoEntry *entry = ctk_object->info_entries+i;
        gchar *str;

        entry->ctk_object = ctk_object;
        str = g_strconcat(entryData->str, ":", NULL);
        entry->label = gtk_label_new(str);
        g_free(str);

        entry->txt = gtk_label_new("");

        gtk_misc_set_alignment(GTK_MISC(entry->label), 0.0f, 0.5f);
        gtk_misc_set_alignment(GTK_MISC(entry->txt), 0.0f, 0.5f);

        ctk_config_set_tooltip(ctk_config,
                               entry->label,
                               *(entryData->tooltip));
        ctk_config_set_tooltip(ctk_config,
                               entry->txt,
                               *(entryData->tooltip));

        entry->hbox = gtk_hbox_new(FALSE, FRAME_PADDING);
        gtk_box_pack_start(GTK_BOX(entry->hbox), entry->label,
                           FALSE, TRUE, FRAME_PADDING);
        gtk_box_pack_start(GTK_BOX(entry->hbox), entry->txt,
                           FALSE, TRUE, FRAME_PADDING);

        gtk_box_pack_start(GTK_BOX(tmpbox), entry->hbox, FALSE, FALSE, 0);
    }

    /* pack the EDID button */

    ctk_object->edid = ctk_edid_new(ctrl_target,
                                    ctk_object->ctk_config,
                                    ctk_object->ctk_event,
                                    ctk_object->name);

    hbox = gtk_hbox_new(FALSE, 0);
    align = gtk_alignment_new(0, 1, 1, 1);
    gtk_container_add(GTK_CONTAINER(align), hbox);
    gtk_box_pack_end(GTK_BOX(nbox), align, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(hbox), ctk_object->edid, TRUE, TRUE, 0);

    /*
     * Create layout for second tab for controls but don't add the tab until we
     * make sure it's required
     */

    nbox = gtk_vbox_new(FALSE, FRAME_PADDING);
    gtk_container_set_border_width(GTK_CONTAINER(nbox), FRAME_PADDING);

    /* pack the reset button */

    button = gtk_button_new_with_label("Reset Hardware Defaults");
    str = ctk_help_create_reset_hardware_defaults_text(typeBaseName,
                                                       name);
    ctk_config_set_tooltip(ctk_config, button, str);
    ctk_object->reset_button = button;

    alignment = gtk_alignment_new(1, 1, 0, 0);
    gtk_container_add(GTK_CONTAINER(alignment), button);
    gtk_box_pack_end(GTK_BOX(nbox), alignment, FALSE, FALSE, 0);

    /* pack the color controls */

    ctk_object->color_controls =
        ctk_color_controls_new(ctrl_target, ctk_config, ctk_event,
                               ctk_object->reset_button, name);

    if (ctk_object->color_controls) {
        gtk_box_pack_start(GTK_BOX(nbox), ctk_object->color_controls,
                           FALSE, FALSE, 0);
    }

    /* pack the dithering controls */

    ctk_object->dithering_controls =
        ctk_dithering_controls_new(ctrl_target, ctk_config, ctk_event,
                                   ctk_object->reset_button, name);

    if (ctk_object->dithering_controls) {
        gtk_box_pack_start(GTK_BOX(nbox), ctk_object->dithering_controls,
                           FALSE, FALSE, 0);
    }

    /* pack the image sliders */

    ctk_object->image_sliders =
        ctk_image_sliders_new(ctrl_target, ctk_config, ctk_event,
                              ctk_object->reset_button, name);
    if (ctk_object->image_sliders) {
        gtk_box_pack_start(GTK_BOX(nbox), ctk_object->image_sliders,
                           FALSE, FALSE, 0);
    }

    /* If no controls are created, don't add a controls tab */

    if (ctk_object->color_controls ||
        ctk_object->dithering_controls ||
        ctk_object->image_sliders) {
        gtk_notebook_append_page(GTK_NOTEBOOK(notebook), nbox,
                                 gtk_label_new("Controls"));
    }

    /*
     * Show all widgets on this page so far. After this, the color correction
     * tab and other widgets can control their own visibility.
     */

    gtk_widget_show_all(GTK_WIDGET(object));

    /* add the color correction tab if RandR is available */

    add_color_correction_tab(ctk_object, ctk_config, ctk_event, notebook, p);

    /* Update the GUI */

    display_device_setup(ctk_object);

    /* Listen to events */

    g_signal_connect(G_OBJECT(ctk_object->reset_button),
                     "clicked", G_CALLBACK(reset_button_clicked),
                     (gpointer) ctk_object);

    g_signal_connect(G_OBJECT(ctk_event_gpu),
                     CTK_EVENT_NAME(NV_CTRL_ENABLED_DISPLAYS),
                     G_CALLBACK(enabled_displays_received),
                     (gpointer) ctk_object);

    for (i = 0; i < ctk_object->num_info_entries; i++) {
        InfoEntryData *entryData = __info_entry_data+i;
        InfoEntry *entry = ctk_object->info_entries+i;

        if (entryData->register_events_func) {
            entryData->register_events_func(entry);
        }
    }

    return GTK_WIDGET(object);

} /* ctk_display_device_new() */
GtkWidget* ctk_display_device_dfp_new(NvCtrlAttributeHandle *handle,
                                      CtkConfig *ctk_config,
                                      CtkEvent *ctk_event,
                                      unsigned int display_device_mask,
                                      char *name)
{
    GObject *object;
    CtkDisplayDeviceDfp *ctk_display_device_dfp;
    GtkWidget *banner;
    GtkWidget *frame;
    GtkWidget *hbox, *vbox, *tmpbox;
    GtkWidget *eventbox;

    GtkWidget *button;
    GtkWidget *radio0;
    GtkWidget *radio1;
    GtkWidget *radio2;
    GtkWidget *alignment;
    
    GtkWidget *table;
    ReturnStatus ret1, ret2;
    gint val_target, val_method;

    object = g_object_new(CTK_TYPE_DISPLAY_DEVICE_DFP, NULL);
    if (!object) return NULL;

    ctk_display_device_dfp = CTK_DISPLAY_DEVICE_DFP(object);
    ctk_display_device_dfp->handle = handle;
    ctk_display_device_dfp->ctk_event = ctk_event;
    ctk_display_device_dfp->ctk_config = ctk_config;
    ctk_display_device_dfp->display_device_mask = display_device_mask;
    ctk_display_device_dfp->name = g_strdup(name);

    /* cache the default scaling target & method values */
    ret1 =
        NvCtrlGetDisplayAttribute(ctk_display_device_dfp->handle,
                                  ctk_display_device_dfp->display_device_mask,
                                  NV_CTRL_GPU_SCALING_DEFAULT_TARGET,
                                  &val_target);
    ret2 =
        NvCtrlGetDisplayAttribute(ctk_display_device_dfp->handle,
                                  ctk_display_device_dfp->display_device_mask,
                                  NV_CTRL_GPU_SCALING_DEFAULT_METHOD,
                                  &val_method);

    if (ret1 != NvCtrlSuccess || ret2 != NvCtrlSuccess ||
        val_target == NV_CTRL_GPU_SCALING_TARGET_INVALID ||
        val_method == NV_CTRL_GPU_SCALING_METHOD_INVALID) {
        val_target = NV_CTRL_GPU_SCALING_TARGET_FLATPANEL_BEST_FIT;
        val_method = NV_CTRL_GPU_SCALING_METHOD_STRETCHED;
    }

    ctk_display_device_dfp->default_scaling_target = val_target;
    ctk_display_device_dfp->default_scaling_method = val_method;

    gtk_box_set_spacing(GTK_BOX(object), 10);

    /* banner */

    banner = ctk_banner_image_new(BANNER_ARTWORK_DFP);
    gtk_box_pack_start(GTK_BOX(object), banner, FALSE, FALSE, 0);
    
    /*
     * create the reset button (which we need while creating the
     * controls in this page so that we can set the button's
     * sensitivity), though we pack it at the bottom of the page
     */

    ctk_display_device_dfp->reset_button =
        gtk_button_new_with_label("Reset Hardware Defaults");

    alignment = gtk_alignment_new(1, 1, 0, 0);
    gtk_container_add(GTK_CONTAINER(alignment),
                      ctk_display_device_dfp->reset_button);
    gtk_box_pack_end(GTK_BOX(object), alignment, TRUE, TRUE, 0);

    g_signal_connect(G_OBJECT(ctk_display_device_dfp->reset_button),
                     "clicked", G_CALLBACK(reset_button_clicked),
                     (gpointer) ctk_display_device_dfp);
    
    ctk_config_set_tooltip(ctk_config, ctk_display_device_dfp->reset_button,
                           "The Reset Hardware Defaults button restores "
                           "the DFP settings to their default values.");

    /* create the hbox to store dfp info, scaling */

    hbox = gtk_hbox_new(FALSE, FRAME_PADDING);
    gtk_box_pack_start(GTK_BOX(object), hbox, FALSE, FALSE, FRAME_PADDING);

    /* DFP info */

    frame = gtk_frame_new("Flat Panel Information");
    gtk_box_pack_start(GTK_BOX(hbox), frame, FALSE, FALSE, 0);
    
    /*
     * insert a vbox between the frame and the widgets, so that the
     * widgets don't expand to fill all of the space within the
     * frame
     */
    
    tmpbox = gtk_vbox_new(FALSE, 5);
    gtk_container_add(GTK_CONTAINER(frame), tmpbox);
    
    /* Make the txt widgets that will get updated */
    ctk_display_device_dfp->txt_chip_location = gtk_label_new("");
    ctk_display_device_dfp->txt_link = gtk_label_new("");
    ctk_display_device_dfp->txt_signal = gtk_label_new("");
    ctk_display_device_dfp->txt_native_resolution = gtk_label_new("");
    ctk_display_device_dfp->txt_best_fit_resolution = gtk_label_new("");
    ctk_display_device_dfp->txt_frontend_resolution = gtk_label_new("");
    ctk_display_device_dfp->txt_backend_resolution = gtk_label_new("");
    ctk_display_device_dfp->txt_refresh_rate = gtk_label_new("");

    /* Add information widget lines */
    {
        typedef struct {
            GtkWidget *label;
            GtkWidget *txt;
            const gchar *tooltip;
        } TextLineInfo;

        TextLineInfo lines[] = {
            {
                gtk_label_new("Chip location:"),
                ctk_display_device_dfp->txt_chip_location,
                NULL
            },
            {
                gtk_label_new("Connection link:"),
                ctk_display_device_dfp->txt_link,
                NULL
            },
            {
                gtk_label_new("Signal:"),
                ctk_display_device_dfp->txt_signal,
                NULL
            },
            {
                gtk_label_new("Native Resolution:"),
                ctk_display_device_dfp->txt_native_resolution,
                __native_res_help,
            },
            {
                gtk_label_new("Best Fit Resolution:"),
                ctk_display_device_dfp->txt_best_fit_resolution,
                __best_fit_res_help,
            },
            {
                gtk_label_new("Frontend Resolution:"),
                ctk_display_device_dfp->txt_frontend_resolution,
                __frontend_res_help,
            },
            {
                gtk_label_new("Backend Resolution:"),
                ctk_display_device_dfp->txt_backend_resolution,
                __backend_res_help,
            },
            {
                gtk_label_new("Refresh Rate:"),
                ctk_display_device_dfp->txt_refresh_rate,
                __refresh_rate_help,
            },
            { NULL, NULL, NULL }
        };
        int i;

        GtkRequisition req;
        int max_width;

        /* Compute max width of lables and setup text alignments */
        max_width = 0;
        for (i = 0; lines[i].label; i++) {
            gtk_misc_set_alignment(GTK_MISC(lines[i].label), 0.0f, 0.5f);
            gtk_misc_set_alignment(GTK_MISC(lines[i].txt), 0.0f, 0.5f);

            gtk_widget_size_request(lines[i].label, &req);
            if (max_width < req.width) {
                max_width = req.width;
            }
        }

        /* Pack labels */
        for (i = 0; lines[i].label; i++) {
            GtkWidget *tmphbox;

            /* Add separators */
            if (i == 3 || i == 5 || i == 7) {
                GtkWidget *separator = gtk_hseparator_new();
                gtk_box_pack_start(GTK_BOX(tmpbox), separator,
                                   FALSE, FALSE, 0);
            }

            /* Set the label's width */
            gtk_widget_set_size_request(lines[i].label, max_width, -1);

            /* add the widgets for this line */
            tmphbox = gtk_hbox_new(FALSE, 5);
            gtk_box_pack_start(GTK_BOX(tmphbox), lines[i].label,
                               FALSE, TRUE, 5);
            gtk_box_pack_start(GTK_BOX(tmphbox), lines[i].txt,
                               FALSE, TRUE, 5);

            /* Include tooltips */
            if (!lines[i].tooltip) {
                gtk_box_pack_start(GTK_BOX(tmpbox), tmphbox, FALSE, FALSE, 0);
            } else {
                eventbox = gtk_event_box_new();
                gtk_container_add(GTK_CONTAINER(eventbox), tmphbox);
                ctk_config_set_tooltip(ctk_config, eventbox, lines[i].tooltip);
                gtk_box_pack_start(GTK_BOX(tmpbox), eventbox, FALSE, FALSE, 0);
            }
        }
    }

    
    /* Flat Panel Scaling */
    
    frame = gtk_frame_new("Flat Panel Scaling");
    eventbox = gtk_event_box_new();
    gtk_container_add(GTK_CONTAINER(eventbox), frame);
    gtk_box_pack_start(GTK_BOX(hbox), eventbox, FALSE, FALSE, 0);
    ctk_display_device_dfp->scaling_frame = eventbox;
    
    ctk_config_set_tooltip(ctk_config, eventbox, __scaling_help);

    vbox = gtk_vbox_new(FALSE, FRAME_PADDING);
    gtk_container_set_border_width(GTK_CONTAINER(vbox), FRAME_PADDING);
    gtk_container_add(GTK_CONTAINER(frame), vbox);

    button = gtk_check_button_new_with_label("Force Full GPU Scaling");
    ctk_display_device_dfp->scaling_gpu_button = button;
    ctk_config_set_tooltip(ctk_config, button, __force_gpu_scaling_help);

    gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0);

    table = gtk_table_new(1, 2, FALSE);
    gtk_table_set_row_spacings(GTK_TABLE(table), 6);
    gtk_table_set_col_spacings(GTK_TABLE(table), 15);
    
    gtk_container_set_border_width(GTK_CONTAINER(table), 5);
    
    ctk_display_device_dfp->txt_scaling = 
        add_table_row(table, 0,
                      0, 0.5, "Scaling:",
                      0, 0.5,  "");

    gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);

    frame = gtk_frame_new("GPU Scaling Method");
    gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 0);
    vbox = gtk_vbox_new(FALSE, FRAME_PADDING);
    gtk_container_set_border_width(GTK_CONTAINER(vbox), FRAME_PADDING);
    gtk_container_add(GTK_CONTAINER(frame), vbox);

    g_signal_connect(G_OBJECT(button), "toggled",
                     G_CALLBACK(dfp_scaling_changed),
                     (gpointer) ctk_display_device_dfp);
    
    radio0 = make_scaling_radio_button
        (ctk_display_device_dfp, vbox, NULL, "Stretched",
         NV_CTRL_GPU_SCALING_METHOD_STRETCHED);
    
    radio1 = make_scaling_radio_button
        (ctk_display_device_dfp, vbox, radio0, "Centered",
         NV_CTRL_GPU_SCALING_METHOD_CENTERED);
    
    radio2 = make_scaling_radio_button
        (ctk_display_device_dfp, vbox, radio1, "Aspect Ratio Scaled",
         NV_CTRL_GPU_SCALING_METHOD_ASPECT_SCALED);
    
    g_signal_connect(G_OBJECT(ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_GPU_SCALING),
                     G_CALLBACK(dfp_update_received),
                     (gpointer) ctk_display_device_dfp);

    /* pack the color controls */

    ctk_display_device_dfp->color_controls =
        ctk_color_controls_new(handle, ctk_config, ctk_event,
                                   ctk_display_device_dfp->reset_button,
                                   display_device_mask, name);

    if (ctk_display_device_dfp->color_controls) {
        gtk_box_pack_start(GTK_BOX(object),
                           ctk_display_device_dfp->color_controls,
                           FALSE, FALSE, 0);
    }

    /* pack the dithering controls */

    ctk_display_device_dfp->dithering_controls =
        ctk_dithering_controls_new(handle, ctk_config, ctk_event,
                                   ctk_display_device_dfp->reset_button,
                                   display_device_mask, name);

    if (ctk_display_device_dfp->dithering_controls) {
        gtk_box_pack_start(GTK_BOX(object),
                           ctk_display_device_dfp->dithering_controls,
                           FALSE, FALSE, 0);
    }

    /* pack the image sliders */
    
    ctk_display_device_dfp->image_sliders =
        ctk_image_sliders_new(handle, ctk_config, ctk_event,
                              ctk_display_device_dfp->reset_button,
                              display_device_mask, name);
    if (ctk_display_device_dfp->image_sliders) {
        gtk_box_pack_start(GTK_BOX(object),
                           ctk_display_device_dfp->image_sliders,
                           FALSE, FALSE, 0);
    }

    /* pack the EDID button */

    hbox = gtk_hbox_new(FALSE, 0);
    gtk_box_pack_start(GTK_BOX(object), hbox, FALSE, FALSE, 0);
    ctk_display_device_dfp->edid_box = hbox;
    
    /* show the page */

    gtk_widget_show_all(GTK_WIDGET(object));

    /* Update the GUI */

    update_display_enabled_flag(ctk_display_device_dfp->handle,
                                &ctk_display_device_dfp->display_enabled,
                                ctk_display_device_dfp->display_device_mask);

    ctk_display_device_dfp_setup(ctk_display_device_dfp);
    
    /* handle enable/disable events on the display device */

    g_signal_connect(G_OBJECT(ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_ENABLED_DISPLAYS),
                     G_CALLBACK(enabled_displays_received),
                     (gpointer) ctk_display_device_dfp);

    g_signal_connect(G_OBJECT(ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_GPU_SCALING_ACTIVE),
                     G_CALLBACK(info_update_received),
                     (gpointer) ctk_display_device_dfp);

    g_signal_connect(G_OBJECT(ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_DFP_SCALING_ACTIVE),
                     G_CALLBACK(info_update_received),
                     (gpointer) ctk_display_device_dfp);

    g_signal_connect(G_OBJECT(ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_FRONTEND_RESOLUTION),
                     G_CALLBACK(info_update_received),
                     (gpointer) ctk_display_device_dfp);

    g_signal_connect(G_OBJECT(ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_FLATPANEL_BEST_FIT_RESOLUTION),
                     G_CALLBACK(info_update_received),
                     (gpointer) ctk_display_device_dfp);

    g_signal_connect(G_OBJECT(ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_BACKEND_RESOLUTION),
                     G_CALLBACK(info_update_received),
                     (gpointer) ctk_display_device_dfp);
    g_signal_connect(G_OBJECT(ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_REFRESH_RATE),
                     G_CALLBACK(info_update_received),
                     (gpointer) ctk_display_device_dfp);

    return GTK_WIDGET(object);

} /* ctk_display_device_dfp_new() */
GtkWidget* ctk_display_device_tv_new(NvCtrlAttributeHandle *handle,
                                     CtkConfig *ctk_config,
                                     CtkEvent *ctk_event,
                                     char *name)
{
    GObject *object;
    CtkDisplayDeviceTv *ctk_display_device_tv;
    GtkWidget *banner;
    GtkWidget *frame;
    GtkWidget *eventbox;
    GtkWidget *tmpbox;
    GtkWidget *hbox;
    GtkWidget *alignment;

    
    object = g_object_new(CTK_TYPE_DISPLAY_DEVICE_TV, NULL);
    if (!object) return NULL;
    
    ctk_display_device_tv = CTK_DISPLAY_DEVICE_TV(object);
    ctk_display_device_tv->handle = handle;
    ctk_display_device_tv->ctk_config = ctk_config;
    ctk_display_device_tv->ctk_event = ctk_event;
    ctk_display_device_tv->name = g_strdup(name);
    
    gtk_box_set_spacing(GTK_BOX(object), 10);
    
    /* banner */

    banner = ctk_banner_image_new(BANNER_ARTWORK_TV);
    gtk_box_pack_start(GTK_BOX(object), banner, FALSE, FALSE, 0);

    /* Information */

    frame = gtk_frame_new(NULL);
    gtk_box_pack_start(GTK_BOX(object), frame, FALSE, FALSE, 0);
    ctk_display_device_tv->info_frame = frame;
    
    hbox = gtk_hbox_new(FALSE, FRAME_PADDING);
    gtk_container_set_border_width(GTK_CONTAINER(hbox), FRAME_PADDING);
    gtk_container_add(GTK_CONTAINER(frame), hbox);
    
    tmpbox = gtk_vbox_new(FALSE, 5);
    gtk_container_add(GTK_CONTAINER(hbox), tmpbox);
    
    ctk_display_device_tv->txt_encoder_name = gtk_label_new("");
    ctk_display_device_tv->txt_refresh_rate = gtk_label_new("");
    
    /* pack the Refresh Rate Label */
    {
        typedef struct {
            GtkWidget *label;
            GtkWidget *txt;
            const gchar *tooltip;
        } TextLineInfo;

        TextLineInfo lines[] = {
            {
                gtk_label_new("TV Encoder:"),
                ctk_display_device_tv->txt_encoder_name,
                __tv_encoder_name_help,
            },
            {
                gtk_label_new("TV Refresh Rate:"),
                ctk_display_device_tv->txt_refresh_rate,
                __tv_refresh_rate_help,
            },
            { NULL, NULL, NULL }
        };
        
        int i;
        GtkRequisition req;
        int max_width;

        /* Compute max width of lables and setup text alignments */
        max_width = 0;
        for (i = 0; lines[i].label; i++) {
            gtk_misc_set_alignment(GTK_MISC(lines[i].label), 0.0f, 0.5f);
            gtk_misc_set_alignment(GTK_MISC(lines[i].txt), 0.0f, 0.5f);
            gtk_widget_size_request(lines[i].label, &req);
            
            if (max_width < req.width) {
                max_width = req.width;
            }
        }

        /* Pack labels */
        for (i = 0; lines[i].label; i++) {
            GtkWidget *tmphbox;

            /* Add separators */
            
            if (i == 1) {
                GtkWidget *separator = gtk_hseparator_new();
                gtk_box_pack_start(GTK_BOX(tmpbox), separator,
                                   FALSE, FALSE, 0);
            }
            /* Set the label's width */
            gtk_widget_set_size_request(lines[i].label, max_width, -1);
            /* add the widgets for this line */
            tmphbox = gtk_hbox_new(FALSE, 5);
            gtk_box_pack_start(GTK_BOX(tmphbox), lines[i].label,
                               FALSE, TRUE, 5);
            gtk_box_pack_start(GTK_BOX(tmphbox), lines[i].txt,
                               FALSE, TRUE, 5);

            /* Include tooltips */
            if (!lines[i].tooltip) {
                gtk_box_pack_start(GTK_BOX(tmpbox), tmphbox, FALSE, FALSE, 0);
            } else {
                eventbox = gtk_event_box_new();
                gtk_container_add(GTK_CONTAINER(eventbox), tmphbox);
                ctk_config_set_tooltip(ctk_config, eventbox, lines[i].tooltip);
                gtk_box_pack_start(GTK_BOX(tmpbox), eventbox, FALSE, FALSE, 0);
            }
        }
    }
    
    /* NV_CTRL_REFRESH_RATE */
    
    g_signal_connect(G_OBJECT(ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_REFRESH_RATE),
                     G_CALLBACK(info_update_received),
                     (gpointer) ctk_display_device_tv);
    

    /* NV_CTRL_TV_OVERSCAN */

    ctk_display_device_tv->overscan =
        add_scale(ctk_display_device_tv, NV_CTRL_TV_OVERSCAN,
                  "TV OverScan", __tv_overscan_help);

    g_signal_connect(G_OBJECT(ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_TV_OVERSCAN),
                     G_CALLBACK(value_received),
                     (gpointer) ctk_display_device_tv);
    
    /* NV_CTRL_TV_FLICKER_FILTER */

    ctk_display_device_tv->flicker_filter =
        add_scale(ctk_display_device_tv, NV_CTRL_TV_FLICKER_FILTER,
                  "TV Flicker Filter", __tv_flicker_filter_help);

    g_signal_connect(G_OBJECT(ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_TV_FLICKER_FILTER),
                     G_CALLBACK(value_received),
                     (gpointer) ctk_display_device_tv);

    /* NV_CTRL_TV_BRIGHTNESS */

    ctk_display_device_tv->brightness =
        add_scale(ctk_display_device_tv, NV_CTRL_TV_BRIGHTNESS,
                  "TV Brightness", __tv_brightness_help);

    g_signal_connect(G_OBJECT(ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_TV_BRIGHTNESS),
                     G_CALLBACK(value_received),
                     (gpointer) ctk_display_device_tv);

    /* NV_CTRL_TV_HUE */
    
    ctk_display_device_tv->hue =
        add_scale(ctk_display_device_tv, NV_CTRL_TV_HUE,
                  "TV Hue", __tv_hue_help);
    
    g_signal_connect(G_OBJECT(ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_TV_HUE),
                     G_CALLBACK(value_received),
                     (gpointer) ctk_display_device_tv);

    /* NV_CTRL_TV_CONTRAST */
    
    ctk_display_device_tv->contrast =
        add_scale(ctk_display_device_tv, NV_CTRL_TV_CONTRAST,
                  "TV Contrast", __tv_contrast_help);

    g_signal_connect(G_OBJECT(ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_TV_CONTRAST),
                     G_CALLBACK(value_received),
                     (gpointer) ctk_display_device_tv);

    /* NV_CTRL_TV_SATURATION */
    
    ctk_display_device_tv->saturation =
        add_scale(ctk_display_device_tv, NV_CTRL_TV_SATURATION,
                  "TV Saturation", __tv_saturation_help);
    
    g_signal_connect(G_OBJECT(ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_TV_SATURATION),
                     G_CALLBACK(value_received),
                     (gpointer) ctk_display_device_tv);

    /* Create the reset button here so it can be used by the image sliders */

    ctk_display_device_tv->reset_button =
        gtk_button_new_with_label("Reset TV Hardware Defaults");
    
    /* create and pack the image sliders */
    
    ctk_display_device_tv->image_sliders =
        ctk_image_sliders_new(handle, ctk_config, ctk_event,
                              ctk_display_device_tv->reset_button,
                              name);
    if (ctk_display_device_tv->image_sliders) {
        gtk_box_pack_start(GTK_BOX(object),
                           ctk_display_device_tv->image_sliders,
                           FALSE, FALSE, 0);
    }
    
    /* reset button */

    g_signal_connect(G_OBJECT(ctk_display_device_tv->reset_button), "clicked",
                     G_CALLBACK(reset_button_clicked),
                     (gpointer) ctk_display_device_tv);
    
    alignment = gtk_alignment_new(1, 1, 0, 0);
    gtk_container_add(GTK_CONTAINER(alignment),
                      ctk_display_device_tv->reset_button);
    gtk_box_pack_end(GTK_BOX(object), alignment, TRUE, TRUE, 0);
    
    g_signal_connect(G_OBJECT(ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_TV_RESET_SETTINGS),
                     G_CALLBACK(value_received),
                     (gpointer) ctk_display_device_tv);
    
    ctk_config_set_tooltip(ctk_config, ctk_display_device_tv->reset_button,
                           ctk_help_create_reset_hardware_defaults_text("TV", name));

    /* EDID button box */

    ctk_display_device_tv->edid =
        ctk_edid_new(ctk_display_device_tv->handle,
                     ctk_display_device_tv->ctk_config,
                     ctk_display_device_tv->ctk_event,
                     ctk_display_device_tv->name);

    hbox = gtk_hbox_new(FALSE, 0);
    gtk_box_pack_start(GTK_BOX(object), hbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(hbox), ctk_display_device_tv->edid,
                       TRUE, TRUE, 0);

    /* finally, display the widget */

    gtk_widget_show_all(GTK_WIDGET(object));

    /* update the GUI */

    update_display_enabled_flag(ctk_display_device_tv->handle,
                                &ctk_display_device_tv->display_enabled);

    ctk_display_device_tv_setup(ctk_display_device_tv);
    
    /* handle enable/disable events on the display device */

    g_signal_connect(G_OBJECT(ctk_event),
                     CTK_EVENT_NAME(NV_CTRL_ENABLED_DISPLAYS),
                     G_CALLBACK(enabled_displays_received),
                     (gpointer) ctk_display_device_tv);
    
    return GTK_WIDGET(object);
    
} /* ctk_display_device_tv_new() */