/*
 * 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 and conditions of the GNU General Public License,
 * version 2, 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 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, see <http://www.gnu.org/licenses>.
 */

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

#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>

#include "msg.h"
#include "parse.h"
#include "common-utils.h"

#include "ctkbanner.h"

#include "ctkslimm.h"
#include "ctkdisplayconfig-utils.h"
#include "ctkhelp.h"
#include "ctkutils.h"
#include "ctkdropdownmenu.h"


/* Static function declarations */
static void setup_display_refresh_dropdown(CtkSLIMM *ctk_object);
static void setup_display_resolution_dropdown(CtkSLIMM *ctk_object);
static void setup_total_size_label(CtkSLIMM *ctk_object);
static void display_refresh_changed(GtkWidget *widget, gpointer user_data);
static void display_resolution_changed(GtkWidget *widget, gpointer user_data);
static void display_config_changed(GtkWidget *widget, gpointer user_data);
static void txt_overlap_activated(GtkWidget *widget, gpointer user_data);
static void slimm_checkbox_toggled(GtkWidget *widget, gpointer user_data);
static void save_xconfig_button_clicked(GtkWidget *widget, gpointer user_data);
static void add_slimm_options(XConfigPtr xconf, gchar *metamode_str);
static void remove_slimm_options(XConfigPtr xconf);
static nvDisplayPtr find_active_display(nvLayoutPtr layout);
static nvDisplayPtr intersect_modelines(nvLayoutPtr layout);
static void remove_duplicate_modelines(nvDisplayPtr display);
static Bool other_displays_have_modeline(nvLayoutPtr layout, 
                                         nvDisplayPtr display,
                                         nvModeLinePtr modeline);


typedef struct DpyLocRec { // Display Location
    int x;
    int y;

} DpyLoc;




GType ctk_slimm_get_type()
{
    static GType ctk_slimm_type = 0;

    if (!ctk_slimm_type) {
        static const GTypeInfo info_ctk_slimm = {
            sizeof (CtkSLIMMClass),
            NULL, /* base_init */
            NULL, /* base_finalize */
            NULL, /* class_init */
            NULL, /* class_finalize */
            NULL, /* class_data */
            sizeof (CtkSLIMM),
            0, /* n_preallocs */
            NULL, /* instance_init */
            NULL  /* value_table */
        };

        ctk_slimm_type =
            g_type_register_static(GTK_TYPE_VBOX,
                                   "CtkSLIMM", &info_ctk_slimm, 0);
    }
    
    return ctk_slimm_type;
}

static void remove_slimm_options(XConfigPtr xconf)
{
    /* Remove SLI Mosaic Option */
    xconfigRemoveNamedOption(&xconf->layouts->adjacencies->screen->options,
                             "SLI", NULL);

    /* Remove MetaMode Option */
    xconfigRemoveNamedOption(&xconf->layouts->adjacencies->screen->options,
                             "MetaModes", NULL);
}


static void add_slimm_options(XConfigPtr xconf, gchar *metamode_str)
{
    XConfigAdjacencyPtr adj;
    XConfigScreenPtr screen;

    /* Make sure there is only one screen specified in the main layout */
    adj = xconf->layouts->adjacencies;
    while (adj->next) {
        xconfigRemoveListItem((GenericListPtr *)(&adj),
                              (GenericListPtr)adj->next);
    }

    /* 
     * Now fix up the screen in the Device section (to prevent failure with
     * separate x screen config
     *
     */
    xconf->layouts->adjacencies->screen->device->screen = -1;

    /* Write out SLI Mosaic Option */
    xconfigAddNewOption(&(xconf->layouts->adjacencies->screen->options),
                        "SLI", "Mosaic");

    /* Write out MetaMode Option */
    xconfigAddNewOption(&(xconf->layouts->adjacencies->screen->options),
                        "MetaModes", metamode_str);

    /* Remove Virtual size specification */
    for (screen = xconf->layouts->adjacencies->screen; screen;
         screen = screen->next) {
        if ((screen->displays->virtualX) || (screen->displays->virtualY)) {
            screen->displays->virtualX = 0;
            screen->displays->virtualY = 0;
        }
    }
}



static XConfigPtr xconfig_generate(XConfigPtr xconfCur,
                                   Bool merge,
                                   Bool *merged,
                                   void *callback_data)
{
    CtkSLIMM *ctk_object = (CtkSLIMM *)callback_data;
    CtkDropDownMenu *menu = CTK_DROP_DOWN_MENU(ctk_object->mnu_display_config);

    gint idx;

    gint xctr,yctr;

    gint x_displays,y_displays;
    gint h_overlap, v_overlap;

    gchar *metamode_str = NULL;
    gchar *tmpstr;

    gint checkbox_state = 
        gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ctk_object->cbtn_slimm_enable));


    /* Make sure we're being asked to merge */
    if (!xconfCur || !merge) {
        *merged = FALSE;
        return NULL;
    }


    if (checkbox_state) {
        /* SLI MM needs to be enabled */
        idx = ctk_drop_down_menu_get_current_value(menu);

        /* Get grid configuration values from index */

        if (idx < ctk_object->num_grid_configs) {
            x_displays = ctk_object->grid_configs[idx].columns;
            y_displays = ctk_object->grid_configs[idx].rows;
        } else {
            x_displays = y_displays = 0;
        }

        h_overlap = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(ctk_object->spbtn_hedge_overlap));
        v_overlap = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(ctk_object->spbtn_vedge_overlap));

        for (yctr = 0; yctr < y_displays;yctr++) {
            for (xctr = 0; xctr < x_displays;xctr++) {
                tmpstr = g_strdup_printf("%s +%d+%d",
                                         ctk_object->cur_modeline->data.identifier,
                                         ctk_object->cur_modeline->data.hdisplay * xctr - 
                                         h_overlap * xctr,
                                         ctk_object->cur_modeline->data.vdisplay * yctr - 
                                         v_overlap * yctr);
                if (metamode_str) {
                    metamode_str = g_strconcat(metamode_str, ", ", tmpstr, NULL);
                    g_free(tmpstr);
                } else {
                    metamode_str = tmpstr;
                }
            }
        }

        add_slimm_options(xconfCur, metamode_str);
    } else {
        /* SLI MM needs to be disabled */

        remove_slimm_options(xconfCur);
    }

    *merged = TRUE;

    return xconfCur;
}



static void set_overlap_controls_status(CtkSLIMM *ctk_object)
{
    CtkDropDownMenu *menu;
    gint config_idx, x_displays, y_displays;

    menu = CTK_DROP_DOWN_MENU(ctk_object->mnu_display_config);
    config_idx = ctk_drop_down_menu_get_current_value(menu);

    /* Get grid configuration values from index */
    if (config_idx < ctk_object->num_grid_configs) {
        x_displays = ctk_object->grid_configs[config_idx].columns;
        y_displays = ctk_object->grid_configs[config_idx].rows;
    } else {
        x_displays = y_displays = 0;
    }

    gtk_widget_set_sensitive(ctk_object->spbtn_hedge_overlap,
                             x_displays > 1 ? True : False);
    gtk_widget_set_sensitive(ctk_object->spbtn_vedge_overlap,
                             y_displays > 1 ? True : False);
}



static Bool compute_screen_size(CtkSLIMM *ctk_object, gint *width,
                                gint *height)
{
    gint config_idx;
    gint x_displays,y_displays;
    gint h_overlap, v_overlap;

    CtkDropDownMenu *menu;


    if (!ctk_object->cur_modeline) {
        return FALSE;
    }

    menu = CTK_DROP_DOWN_MENU(ctk_object->mnu_display_config);
    config_idx = ctk_drop_down_menu_get_current_value(menu);

    /* Get grid configuration values from index */
    if (config_idx < ctk_object->num_grid_configs) {
        x_displays = ctk_object->grid_configs[config_idx].columns;
        y_displays = ctk_object->grid_configs[config_idx].rows;
    } else {
        x_displays = y_displays = 0;
    }

    h_overlap = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(ctk_object->spbtn_hedge_overlap));
    v_overlap = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(ctk_object->spbtn_vedge_overlap));

    /* Total X Screen Size Calculation */
    *width = x_displays * ctk_object->cur_modeline->data.hdisplay - 
        (x_displays - 1) * h_overlap;
    *height = y_displays * ctk_object->cur_modeline->data.vdisplay - 
        (y_displays - 1) * v_overlap;

    return TRUE;
}



static void save_xconfig_button_clicked(GtkWidget *widget, gpointer user_data)
{
    CtkSLIMM *ctk_object = CTK_SLIMM(user_data); 
    gint width, height;
    Bool error = FALSE;
    gchar *err_msg = NULL;


    /* Make sure the screen size is acceptable */
    if (!compute_screen_size(ctk_object, &width, &height)) {
        error = TRUE;
        err_msg = g_strdup("Unknown screen size!");

    } else if ((width > ctk_object->max_screen_width) ||
               (height > ctk_object->max_screen_height)) {
        error = TRUE;
        err_msg = g_strdup_printf("The configured X screen size of %dx%d is \n"
                                  "too large.  The maximum supported size is\n"
                                  "%dx%d.",
                                  width, height,
                                  ctk_object->max_screen_width,
                                  ctk_object->max_screen_height);
    }

    if (error) {
        GtkWidget *dlg;
        GtkWidget *parent;

        parent = ctk_get_parent_window(GTK_WIDGET(ctk_object));

        dlg = gtk_message_dialog_new
            (GTK_WINDOW(parent),
             GTK_DIALOG_DESTROY_WITH_PARENT,
             GTK_MESSAGE_WARNING,
             GTK_BUTTONS_OK,
             "%s", err_msg);
            
        gtk_dialog_run(GTK_DIALOG(dlg));
        gtk_widget_destroy(dlg);
        g_free(err_msg);
        return;
    }


    /* Run the save dialog */
    if (run_save_xconfig_dialog(ctk_object->save_xconfig_dlg)) {

        /* Config file written */
        ctk_object->ctk_config->pending_config &=
            ~CTK_CONFIG_PENDING_WRITE_MOSAIC_CONFIG;
    }
}



static void txt_overlap_activated(GtkWidget *widget, gpointer user_data)
{
    CtkSLIMM *ctk_object = CTK_SLIMM(user_data);
    /* Update total size label */
    setup_total_size_label(ctk_object);

    ctk_object->ctk_config->pending_config |=
        CTK_CONFIG_PENDING_WRITE_MOSAIC_CONFIG;
}

static void display_config_changed(GtkWidget *widget, gpointer user_data)
{
    CtkSLIMM *ctk_object = CTK_SLIMM(user_data);
    /* Update total size label */
    setup_total_size_label(ctk_object);

    /* Update the sensitivity of the overlap controls */
    set_overlap_controls_status(ctk_object);
}


static void display_refresh_changed(GtkWidget *widget, gpointer user_data)
{
    CtkSLIMM *ctk_object = CTK_SLIMM(user_data);
    CtkDropDownMenu *menu = CTK_DROP_DOWN_MENU(widget);
    gint idx;

    /* Get the modeline and display to set */
    idx = ctk_drop_down_menu_get_current_value(menu);

    /* Select the new modeline as current modeline */
    ctk_object->cur_modeline = ctk_object->refresh_table[idx];

    ctk_object->ctk_config->pending_config |=
        CTK_CONFIG_PENDING_WRITE_MOSAIC_CONFIG;
}


static void display_resolution_changed(GtkWidget *widget, gpointer user_data)
{
    CtkSLIMM *ctk_object = CTK_SLIMM(user_data);
    CtkDropDownMenu *menu = CTK_DROP_DOWN_MENU(widget);

    gint idx;
    nvModeLinePtr modeline;

    /* Get the modeline and display to set */
    idx = ctk_drop_down_menu_get_current_value(menu);
    modeline = ctk_object->resolution_table[idx];

    /* Ignore selecting same resolution */
    if (ctk_object->cur_modeline == modeline ||
        (ctk_object->cur_modeline && modeline &&
         ctk_object->cur_modeline->data.hdisplay == modeline->data.hdisplay &&
         ctk_object->cur_modeline->data.vdisplay == modeline->data.vdisplay)) {
        return;
    }

    /* Select the new modeline as current modeline */
    ctk_object->cur_modeline = modeline;

    /* Adjust H and V overlap maximums and redraw total size label */
    gtk_spin_button_set_range(GTK_SPIN_BUTTON(ctk_object->spbtn_hedge_overlap),
                              -modeline->data.hdisplay,
                              modeline->data.hdisplay);

    gtk_spin_button_set_range(GTK_SPIN_BUTTON(ctk_object->spbtn_vedge_overlap),
                              -modeline->data.vdisplay,
                              modeline->data.vdisplay);

    setup_total_size_label(ctk_object);

    /* Regenerate the refresh menu */
    setup_display_refresh_dropdown(ctk_object);

    ctk_object->ctk_config->pending_config |=
        CTK_CONFIG_PENDING_WRITE_MOSAIC_CONFIG;
}



static void slimm_checkbox_toggled(GtkWidget *widget, gpointer user_data)
{
    CtkSLIMM *ctk_object = CTK_SLIMM(user_data);

    gint enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));

    if (enabled) {
        if (ctk_object->mnu_refresh_disabled) {
            ctk_object->mnu_refresh_disabled = False;
            gtk_widget_set_sensitive(ctk_object->mnu_display_refresh, True);
        }
        gtk_widget_set_sensitive(ctk_object->mnu_display_resolution, True);
        gtk_widget_set_sensitive(ctk_object->mnu_display_config, True);
        gtk_widget_set_sensitive(ctk_object->box_total_size, True);
        set_overlap_controls_status(ctk_object);
    } else {
        if (ctk_widget_get_sensitive(ctk_object->mnu_display_refresh)) {
            ctk_object->mnu_refresh_disabled = True;
            gtk_widget_set_sensitive(ctk_object->mnu_display_refresh, False);
        }
        gtk_widget_set_sensitive(ctk_object->mnu_display_resolution, False);
        gtk_widget_set_sensitive(ctk_object->mnu_display_config, False);
        gtk_widget_set_sensitive(ctk_object->spbtn_hedge_overlap, False);
        gtk_widget_set_sensitive(ctk_object->spbtn_vedge_overlap, False);
        gtk_widget_set_sensitive(ctk_object->box_total_size, False);
    }

    ctk_object->ctk_config->pending_config |=
        CTK_CONFIG_PENDING_WRITE_MOSAIC_CONFIG;
}



/** setup_total_size_label() *********************************
 *
 * Generates and sets the label showing total X Screen size of all displays
 * combined.
 *
 **/

static void setup_total_size_label(CtkSLIMM *ctk_object)
{
    gint width, height;
    gchar *xscreen_size;


    if (!compute_screen_size(ctk_object, &width, &height)) {
        return;
    }

    xscreen_size = g_strdup_printf("%d x %d", width, height);
    gtk_label_set_text(GTK_LABEL(ctk_object->lbl_total_size), xscreen_size);
    g_free(xscreen_size);
}



/** setup_display_refresh_dropdown() *********************************
 *
 * Generates the refresh rate dropdown based on the currently selected
 * display.
 *
 **/

static void setup_display_refresh_dropdown(CtkSLIMM *ctk_object)
{
    CtkDropDownMenu *menu;
    nvModeLinePtr modeline;
    float cur_rate; /* Refresh Rate */
    int cur_idx = 0; /* Currently selected modeline */

    gchar *name; /* Modeline's label for the dropdown menu */

    /* Get selection information */
    if (!ctk_object->cur_modeline) {
        goto fail;
    }


    cur_rate = ctk_object->cur_modeline->refresh_rate;


    /* Create the menu index -> modeline pointer lookup table */
    if (ctk_object->refresh_table) {
        free(ctk_object->refresh_table);
        ctk_object->refresh_table_len = 0;
    }
    ctk_object->refresh_table =
        calloc(ctk_object->num_modelines, sizeof(nvModeLinePtr));
    if (!ctk_object->refresh_table) {
        goto fail;
    }


    /* Generate the refresh dropdown */
    menu = CTK_DROP_DOWN_MENU(ctk_object->mnu_display_refresh);
    
    g_signal_handlers_block_by_func
        (G_OBJECT(ctk_object->mnu_display_refresh),
         G_CALLBACK(display_refresh_changed), (gpointer) ctk_object);

    ctk_drop_down_menu_reset(menu);

    /* Generate the refresh rate dropdown from the modelines list */
    for (modeline = ctk_object->modelines; modeline; modeline = modeline->next) {

        float modeline_rate;
        nvModeLinePtr m;
        int count_ref; /* # modelines with similar refresh rates */
        int num_ref;   /* Modeline # in a group of similar refresh rates */

        gchar *extra = NULL;
        gchar *tmp;

        /* Ignore modelines of different resolution */
        if (modeline->data.hdisplay != ctk_object->cur_modeline->data.hdisplay ||
            modeline->data.vdisplay != ctk_object->cur_modeline->data.vdisplay) {
            continue;
        }

        modeline_rate = modeline->refresh_rate;

        name = g_strdup_printf("%.0f Hz", modeline_rate);


        /* Get a unique number for this modeline */
        count_ref = 0; /* # modelines with similar refresh rates */
        num_ref = 0;   /* Modeline # in a group of similar refresh rates */
        for (m = ctk_object->modelines; m; m = m->next) {
            float m_rate = m->refresh_rate;
            gchar *tmp = g_strdup_printf("%.0f Hz", m_rate);

            if (m->data.hdisplay == modeline->data.hdisplay &&
                m->data.vdisplay == modeline->data.vdisplay &&
                !g_ascii_strcasecmp(tmp, name)) {

                count_ref++;
                /* Modelines with similar refresh rates get a unique # (num_ref) */
                if (m == modeline) {
                    num_ref = count_ref; /* This modeline's # */
                }
            }
            g_free(tmp);
        }

        if (num_ref > 1) {
            continue;
        }

        /* Add "DoubleScan" and "Interlace" information */
        
        if (modeline->data.flags & V_DBLSCAN) {
            extra = g_strdup_printf("DoubleScan");
        }

        if (modeline->data.flags & V_INTERLACE) {
            if (extra) {
                tmp = g_strdup_printf("%s, Interlace", extra);
                g_free(extra);
                extra = tmp;
            } else {
                extra = g_strdup_printf("Interlace");
            }
        }

        if (extra) {
            tmp = g_strdup_printf("%s (%s)", name, extra);
            g_free(extra);
            g_free(name);
            name = tmp;
        }
        

        /* Keep track of the selected modeline */
        if (ctk_object->cur_modeline == modeline) {
            cur_idx = ctk_object->refresh_table_len;

        /* Find a close match  to the selected modeline */
        } else if (ctk_object->refresh_table_len &&
                   ctk_object->refresh_table[cur_idx] != ctk_object->cur_modeline) {

            /* Resolution must match */
            if (modeline->data.hdisplay == ctk_object->cur_modeline->data.hdisplay &&
                modeline->data.vdisplay == ctk_object->cur_modeline->data.vdisplay) {

                float prev_rate = ctk_object->refresh_table[cur_idx]->refresh_rate;
                float rate = modeline->refresh_rate;

                /* Found better resolution */
                if (ctk_object->refresh_table[cur_idx]->data.hdisplay != 
                    ctk_object->cur_modeline->data.hdisplay ||
                    ctk_object->refresh_table[cur_idx]->data.vdisplay != 
                    ctk_object->cur_modeline->data.vdisplay) {
                    cur_idx = ctk_object->refresh_table_len;
                }

                /* Found a better refresh rate */
                if (rate == cur_rate && prev_rate != cur_rate) {
                    cur_idx = ctk_object->refresh_table_len;
                }
            }
        }


        /* Add the modeline entry to the dropdown */
        ctk_drop_down_menu_append_item(menu, name, ctk_object->refresh_table_len);
        g_free(name);
        ctk_object->refresh_table[ctk_object->refresh_table_len++] = modeline;
    }

    /* Setup the menu and select the current mode */
    ctk_object->cur_modeline = ctk_object->refresh_table[cur_idx];
    ctk_drop_down_menu_set_current_value(menu, cur_idx);
    gtk_widget_set_sensitive(ctk_object->mnu_display_refresh, True);

    g_signal_handlers_unblock_by_func
        (G_OBJECT(ctk_object->mnu_display_refresh),
         G_CALLBACK(display_refresh_changed), (gpointer) ctk_object);

    return;


    /* Handle failures */
 fail:
    gtk_widget_set_sensitive(ctk_object->mnu_display_refresh, False);


} /* setup_display_refresh_dropdown() */



/** setup_display_resolution_dropdown() ******************************
 *
 * Generates the resolution dropdown based on the currently selected
 * display.
 *
 **/

static void setup_display_resolution_dropdown(CtkSLIMM *ctk_object)
{
    CtkDropDownMenu *menu;

    nvModeLinePtr  modeline;
    nvModeLinePtr  cur_modeline = ctk_object->cur_modeline;

    int cur_idx = 0;  /* Currently selected modeline (resolution) */

    /* Create the modeline lookup table for the dropdown */
    if (ctk_object->resolution_table) {
        free(ctk_object->resolution_table);
        ctk_object->resolution_table_len = 0;
    }
    ctk_object->resolution_table =
        calloc((ctk_object->num_modelines + 1), sizeof(nvModeLinePtr));
    if (!ctk_object->resolution_table) {
        goto fail;
    }

    /* Start the menu generation */
    menu = CTK_DROP_DOWN_MENU(ctk_object->mnu_display_resolution);

    modeline = ctk_object->modelines;
    cur_idx = 0;

    g_signal_handlers_block_by_func
        (G_OBJECT(ctk_object->mnu_display_resolution),
         G_CALLBACK(display_resolution_changed), (gpointer) ctk_object);
    /* Generate the resolution menu */

    while (modeline) {
        nvModeLinePtr m;
        gchar *name;

        /* Find the first resolution that matches the current res W & H */
        m = ctk_object->modelines;
        while (m != modeline) {
            if (modeline->data.hdisplay == m->data.hdisplay &&
                modeline->data.vdisplay == m->data.vdisplay) {
                break;
            }
            m = m->next;
        }

        /* Add resolution if it is the first of its kind */
        if (m == modeline) {

            /* Set the current modeline idx if not already set by default */
            if (cur_modeline) {
                if (!IS_NVIDIA_DEFAULT_MODE(cur_modeline) &&
                    cur_modeline->data.hdisplay == modeline->data.hdisplay &&
                    cur_modeline->data.vdisplay == modeline->data.vdisplay) {
                    cur_idx = ctk_object->resolution_table_len;
                }
            }

            name = g_strdup_printf("%dx%d", modeline->data.hdisplay,
                                   modeline->data.vdisplay);
            ctk_drop_down_menu_append_item(menu, name,
                                           ctk_object->resolution_table_len);
            g_free(name);
            ctk_object->resolution_table[ctk_object->resolution_table_len++] =
                modeline;
        }
        modeline = modeline->next;
    }

    /* Setup the menu and select the current mode */

    ctk_drop_down_menu_set_current_value(menu, cur_idx);

    /* If dropdown has only one item, disable menu selection */
    if (ctk_object->resolution_table_len > 1) {
        gtk_widget_set_sensitive(ctk_object->mnu_display_resolution, True);
    } else {
        gtk_widget_set_sensitive(ctk_object->mnu_display_resolution, False);
    }

    g_signal_handlers_unblock_by_func
        (G_OBJECT(ctk_object->mnu_display_resolution),
         G_CALLBACK(display_resolution_changed), (gpointer) ctk_object);

    return;

    /* Handle failures */
 fail:

    gtk_widget_set_sensitive(ctk_object->mnu_display_resolution, False);

} /* setup_display_resolution_dropdown() */



// Adds the value to the array if it does not already exist
static Bool add_array_value(int array[][2], int max_len, int *cur_len, int val)
{
    int i;

    /* Find the value */
    for (i = 0; i < *cur_len; i++) {
        if (array[i][0] == val) {
            array[i][1]++;
            return TRUE;
        }
    }

    /* Add the value */
    if (*cur_len < max_len) {
        array[*cur_len][0] = val;
        array[*cur_len][1] = 1;
        (*cur_len)++;
        return TRUE;
    }

    /* Value not found and array is full */
    return FALSE;
}

static Bool parse_slimm_layout(CtkSLIMM *ctk_slimm,
                               nvLayoutPtr layout,
                               int *hoverlap,
                               int *voverlap)
{
    CtrlTarget *ctrl_target = ctk_slimm->ctrl_target;
    ReturnStatus ret;
    char *metamode_str = NULL;
    char *str;
    const char *mode_str;
    const char *tmp;
    char *mode_name = NULL;
    gchar *err_msg = NULL;

    const int max_locs = ctk_slimm->num_displays;
    int row_loc[max_locs][2]; // As position, count
    int col_loc[max_locs][2]; // As position, count
    DpyLoc locs[max_locs];    // Location of displays
    int loc_idx;
    int num_locs;
    int rows;
    int cols;

    int i;
    int found;
    nvModeLinePtr *cur_modeline; // Used to assign the current modeline
    
    nvDisplayPtr display = find_active_display(layout);
    if (display == NULL) {
        err_msg = "Active display not found.";
        goto fail;
    }

    /* Point at the display's current modeline so we can patch it */
    cur_modeline = &(display->cur_mode->modeline);
    *cur_modeline = NULL;

    /* Get the current metamode string */
    ret = NvCtrlGetStringAttribute(ctrl_target,
                                   NV_CTRL_STRING_CURRENT_METAMODE,
                                   &metamode_str);
    if ((ret != NvCtrlSuccess) || !metamode_str) {
        err_msg = "Error querying current MetaMode.";
        goto fail;
    }


    /* Point to the start of the metamodes, skipping any tokens */
    str = strstr(metamode_str, "::");
    if (str) {
        str += 2;
    } else {
        str = metamode_str;
    }

    /* Parse each metamode */
    num_locs = 0;
    mode_str = strtok(str, ",");
    while (mode_str) {

        /* Parse each mode */
        mode_str = parse_skip_whitespace(mode_str);


        /* Skip the display name */
        tmp = strstr(mode_str, ":");
        if (tmp) tmp++;
        tmp = parse_skip_whitespace(tmp);


        /* Read the mode name */
        tmp = parse_read_name(tmp, &mode_name, 0);
        if (!tmp || !mode_name) {
            err_msg = "Failed to parse mode name from MetaMode.";
            goto fail;
        }

        if (!(*cur_modeline)) {
            /* Match the mode name to one of the modelines */
            *cur_modeline = display->modelines;
            while (*cur_modeline) {
                if (!strcmp(mode_name, (*cur_modeline)->data.identifier)) {
                    break;
                }
                *cur_modeline = (*cur_modeline)->next;
            }
        } else if (strcmp(mode_name, (*cur_modeline)->data.identifier)) {
            /* Modes don't all have the same mode name */
            free(mode_name);
            err_msg = "MetaMode using mismatched modes.";
            goto fail;
        }
        free(mode_name);


        /* Read mode for position information */
        found = 0;
        while (*tmp && !found) {
            if (*tmp == '+') {
                if (num_locs >= max_locs) {
                    /* Too many displays, not supported */
                    err_msg = "Too many displays in MetaMode.";
                    goto fail;
                }
                tmp++;
                tmp = parse_read_integer_pair(tmp, 0,
                                              &(locs[num_locs].x),
                                              &(locs[num_locs].y));
                num_locs++;
                found = 1;
            } else {
                tmp++;
            }

            /* Catch errors */
            if (!tmp) {
                err_msg = "Failed to parse location information from "
                    "MetaMode.";
                goto fail;
            }
        }

        /* Assume 0,0 positioning if position info not found */
        if (!found) {
            if (num_locs >= max_locs) {
                /* Too many displays, not supported */
                err_msg = "Too many displays in MetaMode.";
                goto fail;
            }
            tmp++;
            tmp = parse_read_integer_pair(tmp, 0,
                                          &(locs[num_locs].x),
                                          &(locs[num_locs].y));
            num_locs++;
        }
 
        /* Parse next mode */
        mode_str = strtok(NULL, ",");
    }


    /* Make sure we were able to find the current modeline */
    if ( !(*cur_modeline)) {
        err_msg = "Unable to identify current resolution and refresh rate.";
        goto fail;
    }


    // Now that we've parsed all the points, count the number of rows/cols.
    rows = 0;
    cols = 0;

    for (loc_idx = 0; loc_idx < num_locs; loc_idx++) {
        if (!add_array_value(row_loc, max_locs, &rows, locs[loc_idx].y)) {
            err_msg = "Too many rows.";
            goto fail;
        }
        if (!add_array_value(col_loc, max_locs, &cols, locs[loc_idx].x)) {
            err_msg = "Too many columns.";
            goto fail;
        }
    }

    /* Make sure that each row has the same number of columns,
     *  and that each column has the same number of rows
     */
    for (i = 0; i < rows; i++) {
        if (row_loc[i][1] != cols) {
            err_msg = "Rows have varying number of columns.";
            goto fail;
        }
    }
    for (i = 0; i < cols; i++) {
        if (col_loc[i][1] != rows) {
            err_msg = "Columns have varying number of rows.";
            goto fail;
        }
    }

    /* Calculate row overlap */
    *voverlap = 0;
    found = 0;
    if (rows > 1) {
        int loc = row_loc[0][0];
        int best_dist = 0; // Best overlap distance
        for (i = 1; i < rows; i++) {
            int overlap = (row_loc[i][0] - loc);
            int dist = (overlap >= 0) ? overlap : -overlap;
            if (!found || dist < best_dist) {
                best_dist = dist;
                *voverlap = overlap;
                found = 1;
            }
        }
    }
    if (*voverlap > 0) {
        *voverlap = (*cur_modeline)->data.vdisplay - *voverlap;
    } else if (*voverlap < 0) {
        *voverlap += (*cur_modeline)->data.vdisplay;
    }


    /* Calculate column overlap */
    *hoverlap = 0;
    found = 0;
    if (cols > 1) {
        int loc = col_loc[0][0];
        int best_dist = 0; // Best overlap distance
        for (i = 1; i < cols; i++) {
            int overlap = (col_loc[i][0] - loc);
            int dist = (overlap >= 0) ? overlap : -overlap;
            if (!found || dist < best_dist) {
                best_dist = dist;
                *hoverlap = overlap;
                found = 1;
            }
        }
    }
    if (*hoverlap > 0) {
        *hoverlap = (*cur_modeline)->data.hdisplay - *hoverlap;
    } else if (*hoverlap < 0) {
        *hoverlap += (*cur_modeline)->data.hdisplay;
    }

    ctk_slimm->parsed_rows = rows;
    ctk_slimm->parsed_cols = cols;

    free(metamode_str);
    return TRUE;


 fail:
    *hoverlap = 0;
    *voverlap = 0;

    if (err_msg) {
        nv_warning_msg("Unable to determine current SLI Mosaic Mode "
                       "configuration (will fall back to default): %s\n",
                       err_msg);
    }

    free(metamode_str);

    return FALSE;
}



static void remove_duplicate_modelines(nvDisplayPtr display)
{
    nvModeLinePtr m, nextm;
    m = display->modelines;
    if (!m) {
        return;
    }

    /* Remove nvidia-auto-select modeline first */
    if (IS_NVIDIA_DEFAULT_MODE(m)) {
        display->modelines = m->next;
        if (m == display->cur_mode->modeline) {
            display->cur_mode->modeline = m->next;
        }
        modeline_free(m);
        display->num_modelines--;
    }
    
    /* Remove duplicate modelines in active display - assuming sorted order*/
    for (m = display->modelines; m;) {
        nextm = m->next;
        if (!nextm) break; 
            
        if (modelines_match(m, nextm)) {
            /* nextm is a duplicate - remove it. */
            m->next = nextm->next;
            if (nextm == display->cur_mode->modeline) {
                display->cur_mode->modeline = m;
            }
            modeline_free(nextm);
            display->num_modelines--;
        }
        else {
            m = nextm;
        }
    }

}


static Bool other_displays_have_modeline(nvLayoutPtr layout, 
                                         nvDisplayPtr display,
                                         nvModeLinePtr modeline)
{
    nvGpuPtr gpu;
    nvDisplayPtr d;

    for (gpu = layout->gpus; gpu; gpu = gpu->next_in_layout) {
        for (d = gpu->displays; d; d = d->next_on_gpu) {
            if (display == d) continue;
            if (d->modelines == NULL) continue;
            if (!display_has_modeline(d, modeline)) {
                return FALSE;
            }
        }
    }

    return TRUE;
}


static nvDisplayPtr find_active_display(nvLayoutPtr layout)
{
    nvGpuPtr gpu;
    nvDisplayPtr display;

    for (gpu = layout->gpus; gpu; gpu = gpu->next_in_layout) {
        for (display = gpu->displays;
             display;
             display = display->next_on_gpu) {
            if (display->modelines) return display;
        }
    }
    return NULL;
}


static nvDisplayPtr intersect_modelines(nvLayoutPtr layout)
{
    nvDisplayPtr display;
    nvModeLinePtr m, prev;

    /** 
     * 
     * Only need to go through one active display, and eliminate all modelines
     * in this display that do not exist in other displays (being driven by
     * this or any other GPU)
     *
     */
    display = find_active_display(layout);
    if (display == NULL) return NULL;

    prev = NULL;
    m = display->modelines;
    while (m) {
        if (!other_displays_have_modeline(layout, display, m)) {
            if (prev) {
                /* Remove past beginning */
                prev->next = m->next;
            } else {
                /* Remove first entry */
                display->modelines = m->next;
            }

            if (m == display->cur_mode->modeline) {
                display->cur_mode->modeline = 0;
            }
            modeline_free(m);
            display->num_modelines--;

            if (prev) {
                m = prev->next;
            } else {
                m = display->modelines;
            }
        } else {
            prev = m;
            m = m->next;
        }
    }

    remove_duplicate_modelines(display);

    return display;
}

#define STEREO_IS_3D_VISION(stereo) \
    (((stereo) == NV_CTRL_STEREO_3D_VISION) || \
     ((stereo) == NV_CTRL_STEREO_3D_VISION_PRO))

static int get_display_stereo_mode(nvDisplayPtr display)
{
    if ((display->screen == NULL) ||
        (display->screen->stereo_supported == FALSE)) {
        return NV_CTRL_STEREO_OFF;
    } else {
        return display->screen->stereo;
    }
}



/*
 * Return number of configs, and assign 'configs' if non-NULL.
 */
static int generate_configs_helper(const int num_displays,
                                   gboolean only_max,
                                   GridConfig *configs)
{
    int i, j, c = 0;

    for (i = 1; i <= num_displays; i++) {
        if (only_max) {
            if (num_displays % i == 0) {
                if (configs) {
                    configs[c].rows = i;
                    configs[c].columns = num_displays / i;
                }
                c++;
            }
        } else {
            for (j = 1; j * i <= num_displays; j++) {
                if (i == 1 && j == 1) {
                    continue;
                }
                if (configs) {
                    configs[c].rows = i;
                    configs[c].columns = j;
                }
                c++;
            }
        }
    }

    return c;
}



static void generate_configs(CtkSLIMM *ctk_slimm, gboolean only_max)
{
    int n_configs = generate_configs_helper(ctk_slimm->num_displays, only_max,
                                            NULL);

    ctk_slimm->grid_configs = calloc(n_configs, sizeof(GridConfig));
    ctk_slimm->num_grid_configs = n_configs;

    generate_configs_helper(ctk_slimm->num_displays, only_max,
                            ctk_slimm->grid_configs);
}



static void populate_dropdown(CtkSLIMM *ctk_slimm, gboolean only_max)
{
    int iter;
    int rows, cols;
    int cur_rows, cur_cols;
    char *tmp;

    CtkDropDownMenu *menu = CTK_DROP_DOWN_MENU(ctk_slimm->mnu_display_config);
    int grid_config_id = ctk_drop_down_menu_get_current_value(menu);

    if (ctk_slimm->grid_configs && grid_config_id >= 0) {
        cur_rows = ctk_slimm->grid_configs[grid_config_id].rows;
        cur_cols = ctk_slimm->grid_configs[grid_config_id].columns;
    } else {
        cur_rows = ctk_slimm->parsed_rows;
        cur_cols = ctk_slimm->parsed_cols;
    }

    if (ctk_slimm->grid_configs) {
        free(ctk_slimm->grid_configs);
        ctk_slimm->num_grid_configs = 0;
    }
    ctk_drop_down_menu_reset(menu);


    generate_configs(ctk_slimm, only_max);


    for (iter = 0; iter < ctk_slimm->num_grid_configs; iter++) {
        rows = ctk_slimm->grid_configs[iter].rows;
        cols = ctk_slimm->grid_configs[iter].columns;

        tmp = g_strdup_printf("%d x %d grid", rows, cols);

        ctk_drop_down_menu_append_item(menu, tmp, iter);

        if (cur_rows == rows && cur_cols == cols) {
            grid_config_id = iter;
        }
    }

    ctk_drop_down_menu_set_current_value(menu, grid_config_id);
}



static void restrict_display_config_changed(GtkWidget *widget,
                                            gpointer user_data)
{
    CtkSLIMM *ctk_slimm = CTK_SLIMM(user_data);

    populate_dropdown(ctk_slimm,
                      gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
}




GtkWidget* ctk_slimm_new(CtrlTarget *ctrl_target,
                         CtkEvent *ctk_event,
                         CtkConfig *ctk_config)
{
    GObject *object;
    CtkSLIMM *ctk_slimm;
    GtkWidget *label;
    GtkWidget *vbox;
    GtkWidget *hbox;
    GtkWidget *banner;
    GtkWidget *checkbutton;
    GtkWidget *hseparator;
    GtkWidget *table;
    GtkWidget *button;

    GtkWidget *spinbutton;
    CtkSLIMM *ctk_object;
    CtkDropDownMenu *menu;

    gchar *err_str = NULL;
    gchar *str;
    gchar *tmp;
    gchar *sli_mode = NULL;
    ReturnStatus ret;
    ReturnStatus ret1;
    int major = 0, minor = 0;
    gint val;

    nvLayoutPtr layout;
    nvDisplayPtr display;

    Bool trust_slimm_available = FALSE;
    Bool only_max;

    int hoverlap = 0;
    int voverlap = 0;

    /* now, create the object */

    object = g_object_new(CTK_TYPE_SLIMM, NULL);
    ctk_slimm = CTK_SLIMM(object);

    ctk_slimm->ctrl_target = ctrl_target;
    ctk_slimm->ctk_config = ctk_config;
    ctk_object = ctk_slimm;

    /*
     * Check for NV-CONTROL protocol version.
     * This is used to not trust old X drivers which always reported
     * it available (on NV50+).
     */
    ret = NvCtrlGetAttribute(ctrl_target,
                             NV_CTRL_ATTR_NV_MAJOR_VERSION, &major);
    ret1 = NvCtrlGetAttribute(ctrl_target,
                              NV_CTRL_ATTR_NV_MINOR_VERSION, &minor);

    if ((ret == NvCtrlSuccess) && (ret1 == NvCtrlSuccess) &&
        ((major > 1) || ((major == 1) && (minor > 23)))) {
        trust_slimm_available = TRUE;
    }

    /* return on old X drivers. */
    if (!trust_slimm_available) {
      return NULL;
    }

    /* Check if this screen supports SLI Mosaic Mode */
    ret = NvCtrlGetAttribute(ctrl_target,
                             NV_CTRL_SLI_MOSAIC_MODE_AVAILABLE, &val);
    if ((ret == NvCtrlSuccess) &&
        (val == NV_CTRL_SLI_MOSAIC_MODE_AVAILABLE_FALSE)) {
        /* Mosaic not supported */
        return NULL;
    }

    /* Query the maximum screen sizes */
    ret = NvCtrlGetAttribute(ctrl_target,
                             NV_CTRL_MAX_SCREEN_WIDTH,
                             &ctk_slimm->max_screen_width);
    if (ret != NvCtrlSuccess) {
        return NULL;
    }

    ret = NvCtrlGetAttribute(ctrl_target,
                             NV_CTRL_MAX_SCREEN_HEIGHT,
                             &ctk_slimm->max_screen_height);
    if (ret != NvCtrlSuccess) {
        return NULL;
    }

    /*
     * Create the display configuration widgets
     *
     */

    /* Load the layout structure from the X server */
    layout = layout_load_from_server(ctrl_target, &err_str);

    if (!err_str && layout) {
        nvGpuPtr gpu;
        int num_displays = 0;

        for (gpu = layout->gpus; gpu; gpu = gpu->next_in_layout) {
            num_displays += gpu->num_displays;
        }

        ctk_slimm->num_displays = num_displays;


        /* Make sure we have enough displays for the minimum config */
        if (num_displays < 2) {
            err_str = g_strdup_printf("Not enough display devices to "
                                      "configure SLI Mosaic Mode.\nYou must "
                                      "have at least 2 Displays connected, "
                                      "but only %d Display%s detected.",
                                      num_displays,
                                      (num_displays != 1) ? "s were" : " was");
            layout_free(layout);
            layout = NULL;

        } else {
            parse_slimm_layout(ctk_slimm,
                               layout,
                               &hoverlap,
                               &voverlap);
        }
    }


    /* If we failed to load, tell the user why */
    if (err_str || !layout) {
        goto slimm_fail;
    }

    display = intersect_modelines(layout);

    if (display == NULL) {
        err_str = g_strdup("Unable to find active display with "
                           "intersected modelines.");
        goto slimm_fail;
    } else if ((display->modelines == NULL) &&
               (display->cur_mode->modeline == NULL)) {
        /* The modepool for the active display did not have any modes in
         * its modepool matching any of the modes on the modepool of any
         * other display in the layout, causing intersect_modelines to
         * remove every mode from the list of available modes for SLI mosaic
         * mode.
         *
         * This can happen if one display had its modepool trimmed and modified
         * to support 3D vision, while other displays (either on X screens
         * without stereo currently enabled, or on screenless GPUs) did not.
         * Find if that is the case, and display an informative message if so.
         */
        nvGpuPtr gpu;
        nvDisplayPtr d;
        int stereo = get_display_stereo_mode(display);

        for (gpu = layout->gpus; gpu; gpu = gpu->next_in_layout) {
            for (d = gpu->displays; d; d = d->next_on_gpu) {
                int other_stereo;

                if (display == d) {
                    continue;
                }

                other_stereo = get_display_stereo_mode(d);

                if ((STEREO_IS_3D_VISION(stereo) &&
                    !STEREO_IS_3D_VISION(other_stereo)) ||
                    (!STEREO_IS_3D_VISION(stereo) &&
                     STEREO_IS_3D_VISION(other_stereo))) {

                    err_str = g_strdup("Unable to find common modelines between\n"
                                       "all connected displays due to 3D vision\n"
                                       "being enabled on some displays and not\n"
                                       "others. Please make sure that 3D vision\n"
                                       "is enabled on all connected displays\n"
                                       "before enabling SLI mosaic mode.");

                    goto slimm_fail;
                }
            }
        }

        /* The intersected modepool was empty, but not because of a mismatch
         * in 3D Vision settings.
         */
        err_str = g_strdup("Unable to find find common modelines between "
                           "all connected displays.");

        goto slimm_fail;
    }


    /* Extract modelines and cur_modeline and free layout structure */
    ctk_object->modelines = display->modelines;
    if (display->cur_mode->modeline) {
        ctk_object->cur_modeline = display->cur_mode->modeline; 
    } else {
        ctk_object->cur_modeline = ctk_object->modelines;
    }
    ctk_object->num_modelines = display->num_modelines;

    /* XXX Since we've hijacked the layout's modelines,
     *     we can stub out the layout's pointer and free it.
     */
    display->modelines = NULL;
    display->cur_mode->modeline = NULL;
    display->num_modelines = 0;
    layout_free(layout);
    layout = NULL;

    /* set container properties of the object */

    gtk_box_set_spacing(GTK_BOX(ctk_slimm), 10);

    /* banner */

    banner = ctk_banner_image_new(BANNER_ARTWORK_SLIMM);
    gtk_box_pack_start(GTK_BOX(ctk_slimm), banner, FALSE, FALSE, 0);
    
    vbox = gtk_vbox_new(FALSE, 5);
    gtk_box_pack_start(GTK_BOX(ctk_slimm), vbox, TRUE, TRUE, 0);

    hbox = gtk_hbox_new(FALSE, 0);
    checkbutton = gtk_check_button_new_with_label("Use SLI Mosaic Mode");
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton), TRUE);
    ctk_slimm->cbtn_slimm_enable = checkbutton;
    g_signal_connect(G_OBJECT(checkbutton), "toggled",
                     G_CALLBACK(slimm_checkbox_toggled),
                     (gpointer) ctk_object);
    gtk_box_pack_start(GTK_BOX(hbox), checkbutton, TRUE, TRUE, 0);
    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);

    hbox = gtk_hbox_new(FALSE, 0);
    label = gtk_label_new("Display Configuration (rows x columns)");
    hseparator = gtk_hseparator_new();
    gtk_widget_show(hseparator);
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 10);
    gtk_box_pack_start(GTK_BOX(hbox), hseparator, TRUE, TRUE, 10);
    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
    

    hbox = gtk_hbox_new(FALSE, 0);

    /* Option menu for Display Grid Configuration */
    menu = (CtkDropDownMenu *)
        ctk_drop_down_menu_new(CTK_DROP_DOWN_MENU_FLAG_READONLY);
    ctk_slimm->mnu_display_config = GTK_WIDGET(menu);

    only_max = (ctk_slimm->parsed_rows * ctk_slimm->parsed_cols ==
                ctk_slimm->num_displays);

    populate_dropdown(ctk_slimm, only_max);

    g_signal_connect(G_OBJECT(ctk_object->mnu_display_config), "changed",
                     G_CALLBACK(display_config_changed),
                     (gpointer) ctk_object);

    checkbutton = gtk_check_button_new_with_label("Only show configurations "
                                                  "using all displays");
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(checkbutton), only_max);
    g_signal_connect(G_OBJECT(checkbutton), "toggled",
                     G_CALLBACK(restrict_display_config_changed),
                     (gpointer) ctk_object);

    label = gtk_label_new("");
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(menu), TRUE, TRUE, 5);
    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(checkbutton), FALSE, FALSE, 5);

    table = gtk_table_new(20, 2, FALSE);
    gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0);
    gtk_table_set_row_spacings(GTK_TABLE(table), 3);
    gtk_table_set_col_spacings(GTK_TABLE(table), 15);
    gtk_container_set_border_width(GTK_CONTAINER(table), 5);

    hbox = gtk_hbox_new(FALSE, 0);
    label = gtk_label_new("Resolution (per display)");
    hseparator = gtk_hseparator_new();
    gtk_widget_show(hseparator);
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), hseparator, TRUE, TRUE, 5);

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

    hbox = gtk_hbox_new(FALSE, 0);
    label = gtk_label_new("Refresh Rate");
    hseparator = gtk_hseparator_new();
    gtk_widget_show(hseparator);
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), hseparator, TRUE, TRUE, 5);
    gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 2, 3, GTK_EXPAND | GTK_FILL,
                     GTK_EXPAND | GTK_FILL, 0.5, 0.5);


    /* Option menu for resolutions */
    hbox = gtk_hbox_new(FALSE, 0);
    ctk_slimm->mnu_display_resolution =
        ctk_drop_down_menu_new(CTK_DROP_DOWN_MENU_FLAG_READONLY);

    /* Create a drop down menu */
    setup_display_resolution_dropdown(ctk_object);
    label = gtk_label_new("");
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
    gtk_box_pack_end(GTK_BOX(hbox), ctk_slimm->mnu_display_resolution, 
                     TRUE, TRUE, 0);
    gtk_table_attach(GTK_TABLE(table), hbox, 0, 1, 3, 4, GTK_EXPAND | GTK_FILL,
                     GTK_EXPAND | GTK_FILL, 0.5, 0.5);
    g_signal_connect(G_OBJECT(ctk_object->mnu_display_resolution), "changed",
                     G_CALLBACK(display_resolution_changed),
                     (gpointer) ctk_object);


    /* Option menu for refresh rates */
    hbox = gtk_hbox_new(FALSE, 0);
    ctk_slimm->mnu_display_refresh =
        ctk_drop_down_menu_new(CTK_DROP_DOWN_MENU_FLAG_READONLY);
    setup_display_refresh_dropdown(ctk_object);
    g_signal_connect(G_OBJECT(ctk_object->mnu_display_refresh), "changed",
                     G_CALLBACK(display_refresh_changed),
                     (gpointer) ctk_object);

    label = gtk_label_new("");
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
    gtk_box_pack_end(GTK_BOX(hbox), ctk_slimm->mnu_display_refresh, TRUE, TRUE, 0);

    gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 3, 4, GTK_EXPAND | GTK_FILL,
                     GTK_EXPAND | GTK_FILL, 0.5, 0.5);

    /* Edge Overlap section */
    hbox = gtk_hbox_new(FALSE, 0);
    label = gtk_label_new("Edge Overlap");
    hseparator = gtk_hseparator_new();
    gtk_widget_show(hseparator);
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), hseparator, TRUE, TRUE, 5);

    gtk_table_attach(GTK_TABLE(table), hbox, 0, 1, 8, 9, GTK_EXPAND | GTK_FILL,
                     GTK_EXPAND | GTK_FILL, 0.5, 0.5);

    hbox = gtk_hbox_new(FALSE, 0);
    label = gtk_label_new("Total Size");
    hseparator = gtk_hseparator_new();
    gtk_widget_show(hseparator);
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), hseparator, TRUE, TRUE, 5);
    gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 8, 9, GTK_EXPAND | GTK_FILL,
                     GTK_EXPAND | GTK_FILL, 0.5, 0.5);


    hbox = gtk_hbox_new(FALSE, 0);
    label = gtk_label_new("Horizontal:");
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 10);

    spinbutton = gtk_spin_button_new_with_range(-ctk_object->cur_modeline->data.hdisplay, 
                                                ctk_object->cur_modeline->data.hdisplay, 
                                                1);

    ctk_slimm->spbtn_hedge_overlap = spinbutton;
    gtk_spin_button_set_value(GTK_SPIN_BUTTON(spinbutton), hoverlap);

    g_signal_connect(G_OBJECT(ctk_object->spbtn_hedge_overlap), "value-changed",
                     G_CALLBACK(txt_overlap_activated),
                     (gpointer) ctk_object);

    gtk_box_pack_start(GTK_BOX(hbox), spinbutton, FALSE, FALSE, 5);

    label = gtk_label_new("pixels");
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);

    gtk_table_attach(GTK_TABLE(table), hbox, 0, 1, 9, 10, GTK_EXPAND | GTK_FILL,
                     GTK_EXPAND | GTK_FILL, 0.5, 0.5);


    hbox = gtk_hbox_new(FALSE, 0);
    label = gtk_label_new("Vertical:    ");
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 10);

    spinbutton = gtk_spin_button_new_with_range(-ctk_object->cur_modeline->data.vdisplay, 
                                                ctk_object->cur_modeline->data.vdisplay, 
                                                1);
    ctk_slimm->spbtn_vedge_overlap = spinbutton;
    gtk_spin_button_set_value(GTK_SPIN_BUTTON(spinbutton), voverlap);

    g_signal_connect(G_OBJECT(ctk_object->spbtn_vedge_overlap), "value-changed",
                     G_CALLBACK(txt_overlap_activated),
                     (gpointer) ctk_object);

    gtk_box_pack_start(GTK_BOX(hbox), spinbutton, FALSE, FALSE, 5);

    label = gtk_label_new("pixels");
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);

    gtk_table_attach(GTK_TABLE(table), hbox, 0, 1, 10, 11, GTK_EXPAND | GTK_FILL,
                     GTK_EXPAND | GTK_FILL, 0.5, 0.5);

    label = gtk_label_new("NULL");
    ctk_slimm->lbl_total_size = label;
    setup_total_size_label(ctk_slimm);

    hbox = gtk_hbox_new(FALSE, 0);
    ctk_slimm->box_total_size = hbox;
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 10);
    gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 9, 10, GTK_EXPAND | GTK_FILL,
                     GTK_EXPAND | GTK_FILL, 0.5, 0.5);

    hbox = gtk_hbox_new(FALSE, 0);
    label = gtk_label_new("Maximum Size");
    hseparator = gtk_hseparator_new();
    gtk_widget_show(hseparator);
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 5);
    gtk_box_pack_start(GTK_BOX(hbox), hseparator, TRUE, TRUE, 5);
    gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 10, 11, GTK_EXPAND | GTK_FILL,
                     GTK_EXPAND | GTK_FILL, 0.5, 0.5);

    tmp = g_strdup_printf("%dx%d", ctk_slimm->max_screen_width,
                          ctk_slimm->max_screen_height);
    label = gtk_label_new(tmp);
    g_free(tmp);
    hbox = gtk_hbox_new(FALSE, 0);
    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 10);
    gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 11, 12, GTK_EXPAND | GTK_FILL,
                     GTK_EXPAND | GTK_FILL, 0.5, 0.5);


    label = gtk_label_new("Save to X Configuration File");
    hbox = gtk_hbox_new(FALSE, 0);
    button = gtk_button_new();
    ctk_slimm->btn_save_config = button; 
    g_signal_connect(G_OBJECT(ctk_object->btn_save_config), "clicked",
                     G_CALLBACK(save_xconfig_button_clicked),
                     (gpointer) ctk_object);

    ctk_slimm->save_xconfig_dlg =
        create_save_xconfig_dialog(GTK_WIDGET(ctk_slimm),
                                   FALSE, // Merge toggleable
                                   xconfig_generate,
                                   (void *)ctk_slimm);

    gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
    gtk_container_add(GTK_CONTAINER(button), hbox);

    gtk_table_attach(GTK_TABLE(table), button, 1, 2, 19, 20, GTK_EXPAND | GTK_FILL,
                     GTK_EXPAND | GTK_FILL, 0, 0);

    /* If current SLI Mode != Mosaic, disable UI elements initially */
    ret = NvCtrlGetStringAttribute(ctrl_target,
                                   NV_CTRL_STRING_SLI_MODE,
                                   &sli_mode);

    set_overlap_controls_status(ctk_slimm);

    if ((ret != NvCtrlSuccess) ||
        (ret == NvCtrlSuccess && g_ascii_strcasecmp(sli_mode, "Mosaic"))) {
        gtk_toggle_button_set_active(
            GTK_TOGGLE_BUTTON(ctk_slimm->cbtn_slimm_enable), FALSE);
        slimm_checkbox_toggled(ctk_slimm->cbtn_slimm_enable,
                               (gpointer) ctk_slimm);
    }

    ctk_object->ctk_config->pending_config &=
        ~CTK_CONFIG_PENDING_WRITE_MOSAIC_CONFIG;

    free(sli_mode);

    gtk_widget_show_all(GTK_WIDGET(object));    

    return GTK_WIDGET(object);

slimm_fail:

    if (layout) {
        layout_free(layout);
    }

    if (!err_str) {
        str = g_strdup("Unable to load SLI Mosaic Mode Settings page.");
    } else {
        str = g_strdup_printf("Unable to load SLI Mosaic Mode Settings "
                              "page:\n\n%s", err_str);
        g_free(err_str);
    }

    label = gtk_label_new(str);
    g_free(str);
    gtk_label_set_selectable(GTK_LABEL(label), TRUE);
    gtk_container_add(GTK_CONTAINER(object), label);

    /* Show the GUI */
    gtk_widget_show_all(GTK_WIDGET(ctk_object));

    return GTK_WIDGET(ctk_object);
}


    
GtkTextBuffer *ctk_slimm_create_help(GtkTextTagTable *table,
                                      const gchar *slimm_name)
{
    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, "SLI Mosaic Mode Settings Help");

    ctk_help_para(b, &i, "This page allows easy configuration "
                  "of SLI Mosaic Mode.");
    
    ctk_help_heading(b, &i, "Use SLI Mosaic Mode");
    ctk_help_para(b, &i, "This checkbox controls whether SLI Mosaic Mode is enabled "
                  "or disabled.");
    
    ctk_help_heading(b, &i, "Display Configuration");
    ctk_help_para(b, &i, "This drop down menu allows selection of the display grid "
                  "configuration for SLI Mosaic Mode; the possible configurations "
                  "are described as rows x columns.");
    
    ctk_help_heading(b, &i, "Resolution");
    ctk_help_para(b, &i, "This drop down menu allows selection of the resolution to "
                  "use for each of the displays in SLI Mosaic Mode.  Note that only "
                  "the resolutions that are available for each display will be "
                  "shown here.");
    
    ctk_help_heading(b, &i, "Refresh Rate");
    ctk_help_para(b, &i, "This drop down menu allows selection of the refresh rate "
                  "to use for each of the displays in SLI Mosaic Mode.  By default "
                  "the highest refresh rate each of the displays can achieve at "
                  "the selected resolution is chosen.  This combo box gets updated "
                  "when a new resolution is picked.");

    ctk_help_heading(b, &i, "Edge Overlap");
    ctk_help_para(b, &i, "These two controls allow the user to specify the "
                  "Horizontal and Vertical Edge Overlap values.  The displays "
                  "will overlap by the specified number of pixels when forming "
                  "the grid configuration.  For example, 4 flat panel displays "
                  "forming a 2 x 2 grid in SLI Mosaic Mode with a resolution of "
                  "1600x1200 and a Horizontal and Vertical Edge overlap of 50 "
                  "will generate the following MetaMode: \"1600x1200+0+0,"
                  "1600x1200+1550+0,1600x1200+0+1150,1600x1200+1550+1150\".");

    ctk_help_heading(b, &i, "Total Size");
    ctk_help_para(b, &i, "This is the total size of the X screen formed using all "
                  "displays in SLI Mosaic Mode.");

    ctk_help_heading(b, &i, "Maximum Size");
    ctk_help_para(b, &i, "This is the maximum allowable size of the X screen "
                  "formed using all displays in SLI Mosaic Mode.");

    ctk_help_heading(b, &i, "Save to X Configuration File");
    ctk_help_para(b, &i, "Clicking this button saves the selected SLI Mosaic Mode "
                  "settings into the X Configuration File.");

    ctk_help_finish(b);

    return b;
}


