/*
 * Copyright (C) 2001-2003 Gary Wong <gtw@gnu.org>
 * Copyright (C) 2002-2022 the AUTHORS
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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 <https://www.gnu.org/licenses/>.
 *
 * $Id: gtkprefs.c,v 1.234 2022/08/30 18:38:35 plm Exp $
 */

#include "config.h"
#include "gtklocdefs.h"
#include "backgammon.h"

#include <ctype.h>
#include <gtk/gtk.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "drawboard.h"
#include "gtkboard.h"
#include "gtkgame.h"
#include "gtkfile.h"
#include "gtkprefs.h"
#include "render.h"
#include "renderprefs.h"
#include "boarddim.h"
#include "gtkwindows.h"
#include "util.h"

#if defined(USE_BOARD3D)
#include "inc3d.h"
#define NUM_NONPREVIEW_PAGES 2
#else
#define NUM_NONPREVIEW_PAGES 1
#endif

typedef enum {
    PI_DESIGN, PI_CHEQUERS0, PI_CHEQUERS1, PI_BOARD, PI_BORDER, PI_DICE0,
    PI_DICE1, PI_CUBE, NUM_PIXMAPS
} pixmapindex;

static GtkAdjustment *apadj[2], *paAzimuth, *paElevation,
    *apadjCoefficient[2], *apadjExponent[2], *apadjBoard[4], *padjRound;
static GtkAdjustment *apadjDiceExponent[2], *apadjDiceCoefficient[2];
static GtkWidget *apwColour[2], *apwBoard[4],
    *pwWood, *pwWoodType, *pwHinges, *pwMoveIndicator,
    *pwWoodF, *pwNotebook, *pwLabels, *pwDynamicLabels;
#if GTK_CHECK_VERSION(3,0,0)
static GtkWidget *pwLightGrid;
#else
static GtkWidget *pwLightTable;
#endif

static GList *plBoardDesigns = NULL;
#if defined(USE_BOARD3D)
static GtkWidget *pwBoardType, *pwShowShadows, *pwAnimateRoll, *pwAnimateFlag,
    *pwDarkness, *lightLab, *darkLab, *pwLightSource, *pwDirectionalSource,
    *pwTestPerformance, *pmHingeCol, *frame3dOptions, *dtTextureTypeFrame,
    *pwPlanView, *pwBoardAngle, *pwSkewFactor, *skewLab, *anglelab, *pwBgTrays, *pwRoundPoints,
    *dtLightPositionFrame, *dtLightLevelsFrame, *pwRoundedEdges, *pwRoundedPiece, *pwFlatPiece,
    *pwTextureTopPiece, *pwTextureAllPiece;

static GtkAdjustment *padjDarkness, *padjAccuracy, *padjBoardAngle, *padjSkewFactor, *padjLightPosX,
    *padjLightLevelAmbient, *padjLightLevelDiffuse, *padjLightLevelSpecular,
    *padjLightPosY, *padjLightPosZ, *padjDiceSize;

static int redrawChange;

static int pc3dDiceId[2], pcChequer2;
#endif

GtkWidget *pwPrevBoard;
static renderdata rdPrefs;

static GtkWidget *pwDesignAdd, *pwDesignRemove, *pwDesignUpdate;
static GtkWidget *pwDesignImport, *pwDesignExport;
static GtkWidget *pwDesignList;
static GtkWidget *pwDesignAddAuthor;
static GtkWidget *pwDesignAddTitle;

static GtkListStore *designListStore;

enum { NAME_COL = 0, DATA_COL = 1 };

static GtkWidget *apwDiceColour[2];
static GtkWidget *pwCubeColour;
static GtkWidget *apwDiceDotColour[2];
static GtkWidget *apwDieColour[2];
static GtkWidget *apwDiceColourBox[2];
static int /*fWood, */ fUpdate;

static void GetPrefs(renderdata * prd);
static void AddPages(BoardData * bd, GtkWidget * pwNotebook, GList * plBoardDesigns);

static void AddDesignRow(gpointer data, gpointer user_data);
static void AddDesignRowIfNew(gpointer data, gpointer user_data);

static GList *ParseBoardDesigns(const char *szFile, const int fDeletable);

typedef struct {
    gchar *szTitle;             /* Title of board design */
    gchar *szAuthor;            /* Name of author */
    gchar *szBoardDesign;       /* Command for setting board */

    int fDeletable;             /* is the board design deletable */
} boarddesign;

static boarddesign *pbdeSelected = NULL, *pbdeModified;

static int
FindDesgin(GtkTreeModel * model, boarddesign * const pbde, GtkTreeIter * pIter)
{
    if (gtk_tree_model_get_iter_first(model, pIter)) {
        do {
            boarddesign *testPtr;
            gtk_tree_model_get(model, pIter, DATA_COL, &testPtr, -1);
            if (testPtr == pbde)
                return TRUE;
        } while (gtk_tree_model_iter_next(model, pIter));
    }
    return FALSE;
}

static void
SelectRow(boarddesign * pbde)
{
    GtkTreeIter iter;
    GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(pwDesignList));
    if (FindDesgin(model, pbde, &iter)) {       /* Select row */
        GtkTreePath *start, *end;
        GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(pwDesignList));
        gtk_tree_selection_select_iter(sel, &iter);

        /* Make sure selection is visible */
        if (gtk_notebook_get_current_page(GTK_NOTEBOOK(pwNotebook)) == NUM_NONPREVIEW_PAGES
            && gtk_tree_view_get_visible_range(GTK_TREE_VIEW(pwDesignList), &start, &end)) {
            do {
                boarddesign *testPtr;
                GtkTreeIter testIter;
                gtk_tree_model_get_iter(model, &testIter, start);
                gtk_tree_model_get(model, &testIter, DATA_COL, &testPtr, -1);
                if (testPtr == pbde)
                    return;     /* Visible */
                gtk_tree_path_next(start);
            } while (gtk_tree_path_compare(start, end) != 0);

            /* Scroll list so item is visible */
            gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(pwDesignList),
                                         gtk_tree_model_get_path(model, &iter), NULL, TRUE, 1, 0);
        }
    }
}

static void
RemoveListDesign(boarddesign * pbdeSelected)
{
    GtkTreeIter iter;
    if (FindDesgin(gtk_tree_view_get_model(GTK_TREE_VIEW(pwDesignList)), pbdeSelected, &iter))
        gtk_list_store_remove(designListStore, &iter);
}

static GList *
read_board_designs(void)
{
    GList *plUser, *plSystem, *plFinal;
    gchar *sz;

    sz = BuildFilename("boards.xml");
    plSystem = ParseBoardDesigns(sz, FALSE);
    g_free(sz);

    sz = g_build_filename(szHomeDirectory, "boards.xml", NULL);
    plUser = ParseBoardDesigns(sz, TRUE);
    g_free(sz);

    /* Add user list to system list (doesn't copy the user list) */
    plFinal = g_list_concat(plSystem, plUser);

    return plFinal;

}

static void
free_board_design(boarddesign * pbde, void *UNUSED(dummy))
{

    if (pbde == NULL)
        return;

    g_free(pbde->szTitle);
    g_free(pbde->szAuthor);
    g_free(pbde->szBoardDesign);

    g_free(pbde);

}

static void
free_board_designs(GList * pl)
{

    g_list_foreach(pl, (GFunc) free_board_design, NULL);
    g_list_free(pl);

}

static void
ParsePreferences(boarddesign * pbde, renderdata * prdNew)
{
    char *apch[2];
    gchar *sz, *pch;

    *prdNew = rdPrefs;
    pch = sz = g_strdup(pbde->szBoardDesign);
    while (ParseKeyValue(&sz, apch))
        RenderPreferencesParam(prdNew, apch[0], apch[1]);

    g_free(pch);
}

static boarddesign *
FindDesign(GList * plBoardDesigns, renderdata * prdDesign)
{
    int i;
    renderdata rdTest;
    for (i = 0; i < (int) g_list_length(plBoardDesigns); i++) {
        boarddesign *pbde = g_list_nth_data(plBoardDesigns, i);
        if (pbde) {
            ParsePreferences(pbde, &rdTest);
            if (PreferenceCompare(&rdTest, prdDesign))
                return pbde;
        }
    }
    return NULL;
}

static void
SetTitle(void)
{                               /* Update dialog title to include design name + author */
    boarddesign *pbde;
    char title[1024];
    GtkWidget *pwDialog = gtk_widget_get_toplevel(pwPrevBoard);

    strcpy(title, _("GNU Backgammon - Appearance"));

    /* Search for current settings in designs */
    strcat(title, ": ");

    pbde = FindDesign(plBoardDesigns, &rdPrefs);
    if (pbde) {
        char design[1024];
        SelectRow(pbde);

        gtk_widget_set_sensitive(GTK_WIDGET(pwDesignRemove), pbde->fDeletable);

        sprintf(design, "%s by %s (%s)", pbde->szTitle, pbde->szAuthor,
                pbde->fDeletable ? _("user defined") : _("predefined"));
        strcat(title, design);
        pbdeSelected = pbde;

        gtk_widget_set_sensitive(GTK_WIDGET(pwDesignAdd), FALSE);
        gtk_widget_set_sensitive(pwDesignUpdate, FALSE);
    } else {
        strcat(title, _("Custom design"));
        gtk_widget_set_sensitive(GTK_WIDGET(pwDesignAdd), TRUE);
        if (gtk_widget_is_sensitive(pwDesignRemove)) {
            gtk_widget_set_sensitive(pwDesignUpdate, TRUE);
            gtk_widget_set_sensitive(pwDesignRemove, FALSE);
            pbdeModified = pbdeSelected;
        }
        pbdeSelected = 0;
    }
    gtk_window_set_title(GTK_WINDOW(pwDialog), title);
}

extern void
UpdatePreview(void)
{
    if (!fUpdate)
        return;
    {
        BoardData *bd = BOARD(pwPrevBoard)->board_data;
#if defined(USE_BOARD3D)
        BoardData3d *bd3d = bd->bd3d;
        renderdata *prd = bd->rd;

        if (display_is_3d(prd)) {       /* Sort out chequer and dice special settings */
            if (prd->ChequerMat[0].textureInfo != prd->ChequerMat[1].textureInfo) {     /* Make both chequers have the same texture */
                prd->ChequerMat[1].textureInfo = prd->ChequerMat[0].textureInfo;
                prd->ChequerMat[1].pTexture = prd->ChequerMat[0].pTexture;
                UpdateColPreview(pcChequer2);
                /* Change to main area and recreate piece display lists */
                MakeCurrent3d(bd3d);
                preDraw3d(bd, bd3d, prd);
            }
            if (prd->afDieColour3d[0] && !MaterialCompare(&prd->DiceMat[0], &prd->ChequerMat[0])) {
                memcpy(&prd->DiceMat[0], &prd->ChequerMat[0], sizeof(Material));
                prd->DiceMat[0].textureInfo = 0;
                prd->DiceMat[0].pTexture = 0;
                UpdateColPreview(pc3dDiceId[0]);
            }
            if (prd->afDieColour3d[1] && !MaterialCompare(&prd->DiceMat[1], &prd->ChequerMat[1])) {
                memcpy(&prd->DiceMat[1], &prd->ChequerMat[1], sizeof(Material));
                prd->DiceMat[1].textureInfo = 0;
                prd->DiceMat[1].pTexture = 0;
                UpdateColPreview(pc3dDiceId[1]);
            }
            gtk_widget_set_sensitive(GTK_WIDGET(dtTextureTypeFrame), (prd->ChequerMat[0].textureInfo != NULL));
        } else
#endif
        {                       /* Create new 2d pixmaps */
            board_free_pixmaps(bd);
            GetPrefs(&rdPrefs);
            board_create_pixmaps(pwPrevBoard, bd);
        }
    }
    SetTitle();
    gtk_widget_queue_draw(pwPrevBoard);
}

static void
DieColourChanged(GtkWidget * pw, gpointer pf)
{
    int f = GPOINTER_TO_INT(pf);
    int set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pw));
    gtk_widget_set_sensitive(apwDiceColourBox[f], !set);

    if (!fUpdate)
        return;

#if defined(USE_BOARD3D)
    if (display_is_3d(&rdPrefs)) {
        BoardData *bd = BOARD(pwPrevBoard)->board_data;
        bd->rd->afDieColour3d[f] = set;
        memcpy(&bd->rd->DiceMat[f], bd->rd->afDieColour3d[f] ? &bd->rd->ChequerMat[f] : &bd->rd->DiceMat[f],
               sizeof(Material));
        bd->rd->DiceMat[f].textureInfo = 0;
        bd->rd->DiceMat[f].pTexture = 0;

        UpdateColPreview(pc3dDiceId[f]);
    }
#endif

    UpdatePreview();
}

static void
option_changed(GtkWidget * UNUSED(widget), GtkWidget * UNUSED(pw))
{
    BoardData *bd = BOARD(pwPrevBoard)->board_data;
    if (!fUpdate)
        return;

    {
#if defined(USE_BOARD3D)
        BoardData3d *bd3d = bd->bd3d;
        renderdata *prd = bd->rd;

        if (display_is_3d(prd)) {
            ClearTextures(bd3d);

            GetPrefs(&rdPrefs);
            GetTextures(bd3d, prd);

            preDraw3d(bd, bd3d, prd);
        } else
#endif
        {
            board_free_pixmaps(bd);
            board_create_pixmaps(pwPrevBoard, bd);
        }
    }
    UpdatePreview();
}

#if defined(USE_BOARD3D)

static void
redraw_changed(GtkWidget * UNUSED(widget), GtkWidget ** UNUSED(ppw))
{                               /* Update 3D colour previews */
    if (!fUpdate)
        return;

    GetPrefs(&rdPrefs);
    SetPreviewLightLevel(rdPrefs.lightLevels);
    UpdateColPreviews();
}

static void
DiceSizeChanged(GtkWidget * UNUSED(pw))
{
    BoardData *bd = BOARD(pwPrevBoard)->board_data;
    bd->rd->diceSize = (float) gtk_adjustment_get_value(padjDiceSize);
    if (DiceTooClose(bd->bd3d, bd->rd))
        setDicePos(bd, bd->bd3d);
    option_changed(0, 0);
}

static void
HingeChanged(GtkWidget * UNUSED(pw))
{
    gtk_widget_set_sensitive(pmHingeCol, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwHinges)));
    option_changed(0, 0);
}

static GtkWidget *
ChequerPrefs3d(BoardData * bd)
{
    GtkWidget *pw, *pwx, *vbox, *pwhbox, *dtPieceTypeFrame;

#if GTK_CHECK_VERSION(3,0,0)
    pwx = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    pw = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwx = gtk_hbox_new(FALSE, 0);
    pw = gtk_vbox_new(FALSE, 4);
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pwx), pw, TRUE, TRUE, 0);

    gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Chequer 0:")), FALSE, FALSE, 4);

    gtk_box_pack_start(GTK_BOX(pwhbox),
                       gtk_colour_picker_new3d(&bd->rd->ChequerMat[0], DF_VARIABLE_OPACITY, TT_PIECE), FALSE, FALSE,
                       TT_PIECE);

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Chequer 1:")), FALSE, FALSE, 4);

    gtk_box_pack_start(GTK_BOX(pwhbox),
                       gtk_colour_picker_new3d(&bd->rd->ChequerMat[1], DF_VARIABLE_OPACITY, TT_PIECE | TT_DISABLED),
                       FALSE, FALSE, TT_PIECE);
    pcChequer2 = GetPreviewId();

    dtPieceTypeFrame = gtk_frame_new(_("Piece type"));
    gtk_container_set_border_width(GTK_CONTAINER(dtPieceTypeFrame), 4);
    gtk_box_pack_start(GTK_BOX(pw), dtPieceTypeFrame, FALSE, FALSE, 0);

#if GTK_CHECK_VERSION(3,0,0)
    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
    vbox = gtk_vbox_new(FALSE, 0);
#endif
    gtk_container_add(GTK_CONTAINER(dtPieceTypeFrame), vbox);

    pwRoundedPiece = gtk_radio_button_new_with_label(NULL, _("Rounded disc"));
    gtk_widget_set_tooltip_text(pwRoundedPiece, _("Piece will be a rounded disc"));
    gtk_box_pack_start(GTK_BOX(vbox), pwRoundedPiece, FALSE, FALSE, 0);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwRoundedPiece), (bd->rd->pieceType == PT_ROUNDED));

    pwFlatPiece = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(pwRoundedPiece), _("Flat edged disc"));
    gtk_widget_set_tooltip_text(pwFlatPiece, _("Piece will be a flat sided disc"));
    gtk_box_pack_start(GTK_BOX(vbox), pwFlatPiece, FALSE, FALSE, 0);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwFlatPiece), (bd->rd->pieceType == PT_FLAT));
    g_signal_connect(G_OBJECT(pwFlatPiece), "toggled", G_CALLBACK(option_changed), bd);


    dtTextureTypeFrame = gtk_frame_new(_("Texture type"));
    gtk_container_set_border_width(GTK_CONTAINER(dtTextureTypeFrame), 4);
    gtk_box_pack_start(GTK_BOX(pw), dtTextureTypeFrame, FALSE, FALSE, 0);

#if GTK_CHECK_VERSION(3,0,0)
    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
    vbox = gtk_vbox_new(FALSE, 0);
#endif
    gtk_container_add(GTK_CONTAINER(dtTextureTypeFrame), vbox);
    gtk_widget_set_sensitive(GTK_WIDGET(dtTextureTypeFrame), (bd->rd->ChequerMat[0].textureInfo != NULL));

    pwTextureAllPiece = gtk_radio_button_new_with_label(NULL, _("All of piece"));
    gtk_widget_set_tooltip_text(pwTextureAllPiece, _("All of piece will be textured"));
    gtk_box_pack_start(GTK_BOX(vbox), pwTextureAllPiece, FALSE, FALSE, 0);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwTextureAllPiece), (bd->rd->pieceTextureType == PTT_ALL));

    pwTextureTopPiece = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(pwTextureAllPiece), _("Top Only"));
    gtk_widget_set_tooltip_text(pwTextureTopPiece, _("Only top of piece will be textured"));
    gtk_box_pack_start(GTK_BOX(vbox), pwTextureTopPiece, FALSE, FALSE, 0);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwTextureTopPiece), (bd->rd->pieceTextureType == PTT_TOP));
    g_signal_connect(G_OBJECT(pwTextureTopPiece), "toggled", G_CALLBACK(option_changed), bd);

    return pwx;
}

static GtkWidget *
DicePrefs3d(BoardData * bd, int f)
{
    GtkWidget *pw, *pwhbox;
    GtkWidget *pwx;

#if GTK_CHECK_VERSION(3,0,0)
    pwx = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    pw = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwx = gtk_hbox_new(FALSE, 0);
    pw = gtk_vbox_new(FALSE, 4);
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pwx), pw, TRUE, TRUE, 0);

    apwDieColour[f] = gtk_check_button_new_with_label(_("Die colour same " "as chequer colour"));
    gtk_box_pack_start(GTK_BOX(pw), apwDieColour[f], FALSE, FALSE, 0);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(apwDieColour[f]), bd->rd->afDieColour3d[f]);

    gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Die colour:")), FALSE, FALSE, 4);

    apwDiceColourBox[f] = gtk_colour_picker_new3d(&bd->rd->DiceMat[f], DF_VARIABLE_OPACITY, TT_NONE);
    pc3dDiceId[f] = GetPreviewId();
    gtk_widget_set_sensitive(GTK_WIDGET(apwDiceColourBox[f]), !bd->rd->afDieColour3d[f]);
    gtk_box_pack_start(GTK_BOX(pwhbox), apwDiceColourBox[f], FALSE, FALSE, 4);

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Pip colour:")), FALSE, FALSE, 4);

    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_colour_picker_new3d(&bd->rd->DiceDotMat[f], DF_FULL_ALPHA, TT_NONE), FALSE,
                       FALSE, TT_PIECE);

    g_signal_connect(G_OBJECT(apwDieColour[f]), "toggled", G_CALLBACK(DieColourChanged), GINT_TO_POINTER(f));

    return pwx;
}

static GtkWidget *
CubePrefs3d(BoardData * bd)
{
    GtkWidget *pw, *pwhbox;
    GtkWidget *pwx;

#if GTK_CHECK_VERSION(3,0,0)
    pwx = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    pw = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwx = gtk_hbox_new(FALSE, 0);
    pw = gtk_vbox_new(FALSE, 4);
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pwx), pw, TRUE, TRUE, 0);

    gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Cube colour:")), FALSE, FALSE, 4);

    gtk_box_pack_start(GTK_BOX(pwhbox),
                       gtk_colour_picker_new3d(&bd->rd->CubeMat, DF_NO_ALPHA, TT_NONE), FALSE, FALSE, TT_PIECE);

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Text colour:")), FALSE, FALSE, 4);

    gtk_box_pack_start(GTK_BOX(pwhbox),
                       gtk_colour_picker_new3d(&bd->rd->CubeNumberMat, DF_FULL_ALPHA, TT_NONE), FALSE, FALSE, TT_PIECE);

    return pwx;
}

static GtkWidget *
BoardPage3d(BoardData * bd)
{
    GtkWidget *pw, *pwhbox;
    GtkWidget *pwx;

#if GTK_CHECK_VERSION(3,0,0)
    pwx = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    pw = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwx = gtk_hbox_new(FALSE, 0);
    pw = gtk_vbox_new(FALSE, 4);
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pwx), pw, TRUE, TRUE, 0);

    gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Background\ncolour:")), FALSE, FALSE, 4);

    gtk_box_pack_start(GTK_BOX(pwhbox),
                       gtk_colour_picker_new3d(&bd->rd->BaseMat, DF_NO_ALPHA, TT_GENERAL), FALSE, FALSE, TT_PIECE);

    pwBgTrays = gtk_check_button_new_with_label(_("Show background in bear-off trays"));
    gtk_widget_set_tooltip_text(pwBgTrays, _("If unset the bear-off trays will be drawn with the board colour"));
    gtk_box_pack_start(GTK_BOX(pw), pwBgTrays, FALSE, FALSE, 0);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwBgTrays), bd->rd->bgInTrays);
    g_signal_connect(G_OBJECT(pwBgTrays), "toggled", G_CALLBACK(option_changed), 0);

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("First\npoint colour:")), FALSE, FALSE, 4);

    gtk_box_pack_start(GTK_BOX(pwhbox),
                       gtk_colour_picker_new3d(&bd->rd->PointMat[0], DF_FULL_ALPHA, TT_GENERAL), FALSE, FALSE,
                       TT_PIECE);

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Second\npoint colour:")), FALSE, FALSE, 4);

    gtk_box_pack_start(GTK_BOX(pwhbox),
                       gtk_colour_picker_new3d(&bd->rd->PointMat[1], DF_FULL_ALPHA, TT_GENERAL), FALSE, FALSE,
                       TT_PIECE);

    pwRoundPoints = gtk_check_button_new_with_label(_("Rounded points"));
    gtk_widget_set_tooltip_text(pwRoundPoints, _("Display the points with a rounded end"));
    gtk_box_pack_start(GTK_BOX(pw), pwRoundPoints, FALSE, FALSE, 0);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwRoundPoints), bd->rd->roundedPoints);
    g_signal_connect(G_OBJECT(pwRoundPoints), "toggled", G_CALLBACK(option_changed), 0);

    return pwx;
}

static GtkWidget *
BorderPage3d(BoardData * bd)
{
    GtkWidget *pw, *pwhbox;
    GtkWidget *pwx;

#if GTK_CHECK_VERSION(3,0,0)
    pwx = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    pw = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwx = gtk_hbox_new(FALSE, 0);
    pw = gtk_vbox_new(FALSE, 4);
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pwx), pw, TRUE, TRUE, 0);

    gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Border colour:")), FALSE, FALSE, 4);

    gtk_box_pack_start(GTK_BOX(pwhbox),
                       gtk_colour_picker_new3d(&bd->rd->BoxMat, DF_FULL_ALPHA, TT_GENERAL), FALSE, FALSE, TT_PIECE);

    pwRoundedEdges = gtk_check_button_new_with_label(_("Rounded board edges"));
    gtk_widget_set_tooltip_text(pwRoundedEdges, _("Toggle rounded or square edges to the board"));
    gtk_box_pack_start(GTK_BOX(pw), pwRoundedEdges, FALSE, FALSE, 0);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwRoundedEdges), bd->rd->roundedEdges);
    g_signal_connect(G_OBJECT(pwRoundedEdges), "toggled", G_CALLBACK(option_changed), 0);

    pwHinges = gtk_check_button_new_with_label(_("Show hinges"));
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwHinges), bd->rd->fHinges3d);
    gtk_box_pack_start(GTK_BOX(pw), pwHinges, FALSE, FALSE, 0);
    g_signal_connect(G_OBJECT(pwHinges), "toggled", G_CALLBACK(HingeChanged), NULL);

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Hinge colour:")), FALSE, FALSE, 4);

    pmHingeCol = gtk_colour_picker_new3d(&bd->rd->HingeMat, DF_NO_ALPHA, TT_HINGE);
    gtk_widget_set_sensitive(pmHingeCol, bd->rd->fHinges3d);
    gtk_box_pack_start(GTK_BOX(pwhbox), pmHingeCol, FALSE, FALSE, 4);

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Point number\ncolour:")), FALSE, FALSE, 4);

    gtk_box_pack_start(GTK_BOX(pwhbox),
                       gtk_colour_picker_new3d(&bd->rd->PointNumberMat, DF_FULL_ALPHA, TT_NONE), FALSE, FALSE,
                       TT_PIECE);

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Background\ncolour:")), FALSE, FALSE, 4);

    gtk_box_pack_start(GTK_BOX(pwhbox),
                       gtk_colour_picker_new3d(&bd->rd->BackGroundMat, DF_NO_ALPHA, TT_GENERAL), FALSE, FALSE,
                       TT_PIECE);

    return pwx;
}

#endif

extern void
gtk_color_button_get_array(GtkColorButton * button, float array[4])
{
#if GTK_CHECK_VERSION(3,4,0)
    GdkRGBA color;

    gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(button), &color);

    array[0] = (float) color.red;
    array[1] = (float) color.green;
    array[2] = (float) color.blue;
    array[3] = (float) color.alpha;
#else
    GdkColor color;
    guint16 alpha;

    gtk_color_button_get_color(button, &color);
    alpha = gtk_color_button_get_alpha(button);

    array[0] = (float) color.red / 65535.0f;
    array[1] = (float) color.green / 65535.0f;
    array[2] = (float) color.blue / 65535.0f;
    array[3] = (float) alpha / 65535.0f;
#endif
}

extern void
gtk_color_button_set_from_array(GtkColorButton * button, float const colarray[4])
{
#if GTK_CHECK_VERSION(3,4,0)
    GdkRGBA color;

    color.red = (gdouble) colarray[0];
    color.green = (gdouble) colarray[1];
    color.blue = (gdouble) colarray[2];
    color.alpha = (gdouble) colarray[3];

    gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(button), &color);
#else
    GdkColor color;
    guint16 alpha;

    color.red = (colarray[0] == 1.0f) ? 0xffff : (guint16) (colarray[0] * 65536);
    color.green = (colarray[1] == 1.0f) ? 0xffff : (guint16) (colarray[1] * 65536);
    color.blue = (colarray[2] == 1.0f) ? 0xffff : (guint16) (colarray[2] * 65536);
    alpha = (colarray[3] == 1.0f) ? 0xffff : (guint16) (colarray[3] * 65536);

    gtk_color_button_set_color(button, &color);
    gtk_color_button_set_alpha(button, alpha);
#endif
}

static GtkWidget *
ChequerPrefs(BoardData * bd, int f)
{

    GtkWidget *pw, *pwhbox, *pwx;

#if GTK_CHECK_VERSION(3,0,0)
    pwx = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    pw = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwx = gtk_hbox_new(FALSE, 0);
    pw = gtk_vbox_new(FALSE, 0);
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif

    gtk_box_pack_start(GTK_BOX(pwx), pw, TRUE, TRUE, 0);

    apadj[f] = GTK_ADJUSTMENT(gtk_adjustment_new(bd->rd->arRefraction[f], 1.0, 3.5, 0.1, 1.0, 0.0));
    apadjCoefficient[f] = GTK_ADJUSTMENT(gtk_adjustment_new(bd->rd->arCoefficient[f], 0.0, 1.0, 0.1, 0.1, 0.0));
    apadjExponent[f] = GTK_ADJUSTMENT(gtk_adjustment_new(bd->rd->arExponent[f], 1.0, 100.0, 1.0, 10.0, 0.0));

    gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Colour:")), FALSE, FALSE, 4);

    apwColour[f] = gtk_color_button_new();
    g_object_set(G_OBJECT(apwColour[f]), "use-alpha", TRUE, NULL);
    g_signal_connect(G_OBJECT(apwColour[f]), "color-set", UpdatePreview, NULL);
    gtk_box_pack_start(GTK_BOX(pwhbox), apwColour[f], TRUE, TRUE, 4);

    gtk_color_button_set_from_array(GTK_COLOR_BUTTON(apwColour[f]), bd->rd->aarColour[f]);

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 4);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Refractive Index:")), FALSE, FALSE, 4);
#if GTK_CHECK_VERSION(3,0,0)
    gtk_box_pack_end(GTK_BOX(pwhbox), gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, apadj[f]), TRUE, TRUE, 4);
#else
    gtk_box_pack_end(GTK_BOX(pwhbox), gtk_hscale_new(apadj[f]), TRUE, TRUE, 4);
#endif
    g_signal_connect(G_OBJECT(apadj[f]), "value-changed", G_CALLBACK(UpdatePreview), NULL);

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 4);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Dull")), FALSE, FALSE, 4);
#if GTK_CHECK_VERSION(3,0,0)
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, apadjCoefficient[f]), TRUE, TRUE, 4);
#else
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_hscale_new(apadjCoefficient[f]), TRUE, TRUE, 4);
#endif
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Shiny")), FALSE, FALSE, 4);
    g_signal_connect(G_OBJECT(apadjCoefficient[f]), "value-changed", G_CALLBACK(UpdatePreview), NULL);

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 4);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Diffuse")), FALSE, FALSE, 4);
#if GTK_CHECK_VERSION(3,0,0)
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, apadjExponent[f]), TRUE, TRUE, 4);
#else
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_hscale_new(apadjExponent[f]), TRUE, TRUE, 4);
#endif
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Specular")), FALSE, FALSE, 4);
    g_signal_connect(G_OBJECT(apadjExponent[f]), "value-changed", G_CALLBACK(UpdatePreview), NULL);

    if (f == 0) {
        GtkWidget *pwScale, *pwBox;
        
        padjRound = GTK_ADJUSTMENT(gtk_adjustment_new(1.0f - bd->rd->rRound, 0, 1, 0.1, 0.1, 0));
        g_signal_connect(G_OBJECT(padjRound), "value-changed", G_CALLBACK(UpdatePreview), NULL);
#if GTK_CHECK_VERSION(3,0,0)
        pwScale = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, padjRound);
#else
        pwScale = gtk_hscale_new(padjRound);
#endif
        gtk_widget_set_size_request(pwScale, 100, -1);
        gtk_scale_set_draw_value(GTK_SCALE(pwScale), FALSE);
        gtk_scale_set_digits(GTK_SCALE(pwScale), 2);

#if GTK_CHECK_VERSION(3,0,0)
        pwBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
        pwBox = gtk_hbox_new(FALSE, 0);
#endif
        gtk_box_pack_start(GTK_BOX(pwBox), gtk_label_new(_("Chequer shape:")), FALSE, FALSE, 0);
        gtk_box_pack_start(GTK_BOX(pw), pwBox, FALSE, FALSE, 4);

#if GTK_CHECK_VERSION(3,0,0)
        pwBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
        pwBox = gtk_hbox_new(FALSE, 0);
#endif

        gtk_box_pack_start(GTK_BOX(pwBox), gtk_label_new(_("Flat")), FALSE, FALSE, 4);
        gtk_box_pack_start(GTK_BOX(pwBox), pwScale, FALSE, FALSE, 0);
        gtk_box_pack_start(GTK_BOX(pwBox), gtk_label_new(_("Rounded")), FALSE, FALSE, 4);

        gtk_box_pack_start(GTK_BOX(pw), pwBox, FALSE, FALSE, 4);
    }

    return pwx;
}

static GtkWidget *
DicePrefs(BoardData * bd, int f)
{

    GtkWidget *pw, *pwhbox;
    GtkWidget *pwvbox;
    GtkWidget *pwFrame;
    GtkWidget *pwx;

#if GTK_CHECK_VERSION(3,0,0)
    pwx = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    pw = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
    pwvbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
    pwx = gtk_hbox_new(FALSE, 0);
    pw = gtk_vbox_new(FALSE, 4);
    pwvbox = gtk_vbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pwx), pw, TRUE, TRUE, 0);

    /* frame with colour selections for the dice */

    pwFrame = gtk_frame_new(_("Die colour"));
    gtk_box_pack_start(GTK_BOX(pw), pwFrame, FALSE, FALSE, 0);

    gtk_container_add(GTK_CONTAINER(pwFrame), pwvbox);

    apwDieColour[f] = gtk_check_button_new_with_label(_("Die colour same " "as chequer colour"));
    gtk_box_pack_start(GTK_BOX(pwvbox), apwDieColour[f], FALSE, FALSE, 0);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(apwDieColour[f]), bd->rd->afDieColour[f]);

#if GTK_CHECK_VERSION(3,0,0)
    apwDiceColourBox[f] = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
    apwDiceColourBox[f] = gtk_vbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pwvbox), apwDiceColourBox[f], FALSE, FALSE, 0);

    apadjDiceCoefficient[f] = GTK_ADJUSTMENT(gtk_adjustment_new(bd->rd->arDiceCoefficient[f], 0.0, 1.0, 0.1, 0.1, 0.0));
    apadjDiceExponent[f] = GTK_ADJUSTMENT(gtk_adjustment_new(bd->rd->arDiceExponent[f], 1.0, 100.0, 1.0, 10.0, 0.0));

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(apwDiceColourBox[f]), pwhbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Die colour:")), FALSE, FALSE, 4);
    apwDiceColour[f] = gtk_color_button_new();
    g_signal_connect(G_OBJECT(apwDiceColour[f]), "color-set", UpdatePreview, NULL);
    gtk_color_button_set_from_array(GTK_COLOR_BUTTON(apwDiceColour[f]), bd->rd->aarDiceColour[f]);
    gtk_box_pack_start(GTK_BOX(pwhbox), apwDiceColour[f], TRUE, TRUE, 4);

    /* Vertical space */
#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(apwDiceColourBox[f]), pwhbox, FALSE, FALSE, 4);

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(apwDiceColourBox[f]), pwhbox, FALSE, FALSE, 4);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Dull")), FALSE, FALSE, 4);
#if GTK_CHECK_VERSION(3,0,0)
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, apadjDiceCoefficient[f]), TRUE, TRUE, 4);
#else
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_hscale_new(apadjDiceCoefficient[f]), TRUE, TRUE, 4);
#endif
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Shiny")), FALSE, FALSE, 4);
    g_signal_connect(G_OBJECT(apadjDiceCoefficient[f]), "value-changed", G_CALLBACK(UpdatePreview), NULL);

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(apwDiceColourBox[f]), pwhbox, FALSE, FALSE, 4);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Diffuse")), FALSE, FALSE, 4);
#if GTK_CHECK_VERSION(3,0,0)
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, apadjDiceExponent[f]), TRUE, TRUE, 4);
#else
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_hscale_new(apadjDiceExponent[f]), TRUE, TRUE, 4);
#endif
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Specular")), FALSE, FALSE, 4);
    g_signal_connect(G_OBJECT(apadjDiceExponent[f]), "value-changed", G_CALLBACK(UpdatePreview), NULL);

    gtk_widget_set_sensitive(GTK_WIDGET(apwDiceColourBox[f]), !bd->rd->afDieColour[f]);

    /* colour of dot on dice */

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Pip colour:")), FALSE, FALSE, 4);

    apwDiceDotColour[f] = gtk_color_button_new();
    g_signal_connect(G_OBJECT(apwDiceDotColour[f]), "color-set", UpdatePreview, NULL);
    gtk_color_button_set_from_array(GTK_COLOR_BUTTON(apwDiceDotColour[f]), bd->rd->aarDiceDotColour[f]);
    gtk_box_pack_start(GTK_BOX(pwhbox), apwDiceDotColour[f], TRUE, TRUE, 4);

    g_signal_connect(G_OBJECT(apwDieColour[f]), "toggled", G_CALLBACK(DieColourChanged), GINT_TO_POINTER(f));

    return pwx;
}

static GtkWidget *
CubePrefs(BoardData * bd)
{

    GtkWidget *pw;
    GtkWidget *pwx;

#if GTK_CHECK_VERSION(3,0,0)
    pwx = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
    pw = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
#else
    pwx = gtk_hbox_new(FALSE, 4);
    pw = gtk_vbox_new(FALSE, 4);
#endif

    gtk_box_pack_start(GTK_BOX(pw), pwx, FALSE, FALSE, 0);

    gtk_box_pack_start(GTK_BOX(pwx), gtk_label_new(_("Cube colour:")), FALSE, FALSE, 0);

    pwCubeColour = gtk_color_button_new();
    g_signal_connect(G_OBJECT(pwCubeColour), "color-set", UpdatePreview, NULL);
    gtk_color_button_set_from_array(GTK_COLOR_BUTTON(pwCubeColour), bd->rd->arCubeColour);
    gtk_box_pack_start(GTK_BOX(pwx), pwCubeColour, TRUE, TRUE, 0);

    /* FIXME add cube text colour settings */

    return pw;

}

static GtkWidget *
BoardPage(BoardData * bd)
{

    GtkWidget *pw, *pwhbox;
    float ar[4];
    int i, j;
    GtkWidget *pwx;
    static const char *asz[4] = {
        N_("Background colour:"),
        NULL,
        N_("First point colour:"),
        N_("Second point colour:"),
    };

#if GTK_CHECK_VERSION(3,0,0)
    pwx = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    pw = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
    pwx = gtk_hbox_new(FALSE, 0);
    pw = gtk_vbox_new(FALSE, 0);
#endif

    gtk_box_pack_start(GTK_BOX(pwx), pw, TRUE, TRUE, 0);

    for (j = 0; j < 4; j++) {
        if (j == 1)
            continue;           /* colour 1 unused */
        apadjBoard[j] = GTK_ADJUSTMENT(gtk_adjustment_new(bd->rd->aSpeckle[j] / 128.0, 0, 1, 0.1, 0.1, 0));

        for (i = 0; i < 4; i++)
            ar[i] = bd->rd->aanBoardColour[j][i] / 255.0f;

#if GTK_CHECK_VERSION(3,0,0)
        pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
        pwhbox = gtk_hbox_new(FALSE, 0);
#endif
        gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 0);
        gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(gettext(asz[j])), FALSE, FALSE, 4);
        apwBoard[j] = gtk_color_button_new();
        g_signal_connect(G_OBJECT(apwBoard[j]), "color-set", UpdatePreview, NULL);
        gtk_color_button_set_from_array(GTK_COLOR_BUTTON(apwBoard[j]), ar);
        gtk_box_pack_start(GTK_BOX(pwhbox), apwBoard[j], TRUE, TRUE, 4);

#if GTK_CHECK_VERSION(3,0,0)
        pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
        pwhbox = gtk_hbox_new(FALSE, 0);
#endif
        gtk_box_pack_start(GTK_BOX(pw), pwhbox, FALSE, FALSE, 4);
        gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Smooth")), FALSE, FALSE, 4);
#if GTK_CHECK_VERSION(3,0,0)
        gtk_box_pack_start(GTK_BOX(pwhbox), gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, apadjBoard[j]), TRUE, TRUE, 4);
#else
        gtk_box_pack_start(GTK_BOX(pwhbox), gtk_hscale_new(apadjBoard[j]), TRUE, TRUE, 4);
#endif
        gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Speckled")), FALSE, FALSE, 4);
        g_signal_connect(G_OBJECT(apadjBoard[j]), "value-changed", G_CALLBACK(UpdatePreview), NULL);
    }

    return pwx;
}

static void
ToggleWood(GtkWidget * pw, BoardData * UNUSED(bd))
{
    int fWood;

    fWood = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pw));

    gtk_widget_set_sensitive(pwWoodType, fWood);
    gtk_widget_set_sensitive(apwBoard[1], !fWood);
}

static GtkWidget *
BorderPage(BoardData * bd)
{

    GtkWidget *pw;
    float ar[4];
    int i;
    static const char *aszWood[] = {
        N_("Alder"),
        N_("Ash"),
        N_("Basswood"),
        N_("Beech"),
        N_("Cedar"),
        N_("Ebony"),
        N_("Fir"),
        N_("Maple"),
        N_("Oak"),
        N_("Pine"),
        N_("Redwood"),
        N_("Walnut"),
        N_("Willow")
    };
    woodtype bw;
    GtkWidget *pwx;

#if GTK_CHECK_VERSION(3,0,0)
    pwx = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    pw = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
    pwx = gtk_hbox_new(FALSE, 0);
    pw = gtk_vbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pwx), pw, TRUE, TRUE, 0);

    gtk_box_pack_start(GTK_BOX(pw), pwWood = gtk_radio_button_new_with_label(NULL, _("Wooden")), FALSE, FALSE, 0);

    gtk_box_pack_start(GTK_BOX(pw), pwWoodType = gtk_combo_box_text_new(), FALSE, FALSE, 4);

    for (bw = 0; bw < WOOD_PAINT; bw++)
        gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(pwWoodType), gettext(aszWood[bw]));

    if (bd->rd->wt == WOOD_PAINT)
        gtk_combo_box_set_active(GTK_COMBO_BOX(pwWoodType), 0);
    else
        gtk_combo_box_set_active(GTK_COMBO_BOX(pwWoodType), bd->rd->wt);

    g_signal_connect(G_OBJECT(pwWoodType), "changed", G_CALLBACK(UpdatePreview), NULL);

    gtk_box_pack_start(GTK_BOX(pw),
                       pwWoodF = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(pwWood), _("Painted")),
                       FALSE, FALSE, 0);

    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bd->rd->wt != WOOD_PAINT ? pwWood : pwWoodF), TRUE);

    for (i = 0; i < 3; i++)
        ar[i] = bd->rd->aanBoardColour[1][i] / 255.0f;
    ar[3] = 0.0f;

    apwBoard[1] = gtk_color_button_new();
    g_signal_connect(G_OBJECT(apwBoard[1]), "color-set", UpdatePreview, NULL);
    gtk_box_pack_start(GTK_BOX(pw), apwBoard[1], FALSE, FALSE, 0);
    gtk_color_button_set_from_array(GTK_COLOR_BUTTON(apwBoard[1]), ar);

    pwHinges = gtk_check_button_new_with_label(_("Show hinges"));
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwHinges), bd->rd->fHinges);
    gtk_box_pack_start(GTK_BOX(pw), pwHinges, FALSE, FALSE, 0);
    g_signal_connect(G_OBJECT(pwHinges), "toggled", G_CALLBACK(UpdatePreview), NULL);

    g_signal_connect(G_OBJECT(pwWood), "toggled", G_CALLBACK(ToggleWood), bd);
    g_signal_connect(G_OBJECT(pwWood), "toggled", G_CALLBACK(UpdatePreview), NULL);

    gtk_widget_set_sensitive(pwWoodType, bd->rd->wt != WOOD_PAINT);
    gtk_widget_set_sensitive(apwBoard[1], bd->rd->wt == WOOD_PAINT);

    return pwx;
}

static void
BoardPrefsOK(GtkWidget * pw, GtkWidget * mainBoard)
{
    BoardData *bd = BOARD(mainBoard)->board_data;
    GetPrefs(&rdPrefs);

#if defined(USE_BOARD3D)
    if (widget3dValid) {
        redrawChange = FALSE;
    }
    if (display_is_3d(&rdPrefs)) {
        /* Delete old objects */
        ClearTextures(bd->bd3d);
    } else
#endif
    {
        if (bd->diceShown == DICE_ON_BOARD && bd->x_dice[0] <= 0) {     /* Make sure dice are visible */
            RollDice2d(bd);
        }
    }
    board_free_pixmaps(bd);

    rdPrefs.fDiceArea = bd->rd->fDiceArea;
    rdPrefs.fShowGameInfo = bd->rd->fShowGameInfo;
    rdPrefs.nSize = bd->rd->nSize;

    /* Copy new settings to main board */
    *bd->rd = rdPrefs;

#if defined(USE_BOARD3D)
    {
        BoardData3d *bd3d = bd->bd3d;
        renderdata *prd = bd->rd;

        DisplayCorrectBoardType(bd, bd3d, prd);
        SetSwitchModeMenuText();

        if (display_is_3d(prd)) {
            MakeCurrent3d(bd3d);
            GetTextures(bd3d, prd);
            preDraw3d(bd, bd3d, prd);
            ShowFlag3d(bd, bd3d, prd);
            if (bd->diceShown == DICE_ON_BOARD)
                setDicePos(bd, bd3d);   /* Make sure dice appear ok */
        }
    }
#endif
    board_create_pixmaps(mainBoard, bd);
    gtk_widget_destroy(gtk_widget_get_toplevel(pw));
    /* Make sure chequers correct below board */
    gtk_widget_queue_draw(bd->table);
    UserCommand("save settings");
}

static void
WorkOut2dLight(renderdata * prd)
{
    prd->arLight[2] = sinf((float)gtk_adjustment_get_value(paElevation) / 180.f * F_PI);
    prd->arLight[0] = cosf((float)gtk_adjustment_get_value(paAzimuth) / 180.f * F_PI) *
                               sqrtf(1.0f - prd->arLight[2] * prd->arLight[2]);
    prd->arLight[1] = sinf((float)gtk_adjustment_get_value(paAzimuth) / 180.f * F_PI) *
                               sqrtf(1.0f - prd->arLight[2] * prd->arLight[2]);
}

static void
LightChanged2d(GtkWidget * UNUSED(pwWidget), void *UNUSED(data))
{
    BoardData *bd = BOARD(pwPrevBoard)->board_data;
    if (!fUpdate)
        return;
    WorkOut2dLight(bd->rd);
    board_free_pixmaps(bd);
    board_create_pixmaps(pwPrevBoard, bd);

    UpdatePreview();
}

static void
LabelsToggled(GtkWidget * UNUSED(pwWidget), void *UNUSED(data))
{
    BoardData *bd = BOARD(pwPrevBoard)->board_data;
    int showLabels = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwLabels));
    gtk_widget_set_sensitive(GTK_WIDGET(pwDynamicLabels), showLabels);
    /* Update preview */
    if (!fUpdate)
        return;
    bd->rd->fLabels = showLabels;
    bd->rd->fDynamicLabels = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwDynamicLabels));
#if defined(USE_BOARD3D)
    if (display_is_2d(bd->rd))
#endif
    {
        board_free_pixmaps(bd);
        board_create_pixmaps(pwPrevBoard, bd);
    }
    option_changed(0, 0);
}

static void
MoveIndicatorToggled(GtkWidget * UNUSED(pwWidget), void *UNUSED(data))
{
    BoardData *bd = BOARD(pwPrevBoard)->board_data;
    /* Update preview */
    if (!fUpdate)
        return;
    bd->rd->showMoveIndicator = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwMoveIndicator));
#if defined(USE_BOARD3D)
    if (display_is_2d(bd->rd))
#endif
    {
        board_free_pixmaps(bd);
        board_create_pixmaps(pwPrevBoard, bd);
    }
    UpdatePreview();
}

#if defined(USE_BOARD3D)

static void
toggle_display_type(GtkWidget * widget, BoardData * bd)
{
    int i;
    int state = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
    int numPages = g_list_length(gtk_container_get_children(GTK_CONTAINER(GTK_NOTEBOOK(pwNotebook))));
    /* Show pages with correct 2D/3D settings */
    for (i = numPages - 1; i >= NUM_NONPREVIEW_PAGES; i--)
        gtk_notebook_remove_page(GTK_NOTEBOOK(pwNotebook), i);

    gtk_notebook_remove_page(GTK_NOTEBOOK(pwNotebook), 1);

    rdPrefs.fDisplayType = state ? DT_3D : DT_2D;

    if (display_is_3d(&rdPrefs)) {
        updateDiceOccPos(bd, bd->bd3d);
    } else {
        board_free_pixmaps(bd);
        board_create_pixmaps(pwPrevBoard, bd);
    }

    AddPages(bd, pwNotebook, plBoardDesigns);
    gtk_widget_set_sensitive(pwTestPerformance, (display_is_3d(&rdPrefs)));

#if defined(USE_BOARD3D)
    DisplayCorrectBoardType(bd, bd->bd3d, bd->rd);
#endif
    /* Make sure everything is correctly sized */
    gtk_widget_queue_resize(gtk_widget_get_toplevel(pwNotebook));

    SetTitle();
}

static void
toggle_show_shadows(GtkWidget * widget, int init)
{
    int set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
    gtk_widget_set_sensitive(lightLab, set);
    gtk_widget_set_sensitive(pwDarkness, set);
    gtk_widget_set_sensitive(darkLab, set);
    if (set && init != -1)
        GTKShowWarning(WARN_SET_SHADOWS, widget);

    option_changed(0, 0);
}

static void
toggle_planview(GtkWidget * widget, GtkWidget * UNUSED(pw))
{
    int set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
    gtk_widget_set_sensitive(pwSkewFactor, !set);
    gtk_widget_set_sensitive(pwBoardAngle, !set);
    gtk_widget_set_sensitive(skewLab, !set);
    gtk_widget_set_sensitive(anglelab, !set);

    option_changed(0, 0);
}

static void
DoTestPerformance(GtkWidget * pw, GtkWidget * board)
{
    BoardData *bd = BOARD(board)->board_data;
    char str[255];
    char *msg;
    float fps;

    GTKSetCurrentParent(pw);
    if (!GetInputYN(_("Save settings and test 3D performance for 3 seconds?")))
        return;

    BoardPrefsOK(pw, board);

    ProcessEvents();

    fps = TestPerformance3d(bd);

    if (fps >= 120)
        msg = _("3D performance is very good.\n");
    else if (fps >= 60)
        msg = _("3D performance is good.\n");
    else if (fps >= 30)
        msg = _("3D performance is ok.\n");
    else if (fps >= 15)
        msg = _("3D performance is poor.\n");
    else
        msg = _("3D performance is very poor.\n");

    sprintf(str, _("%s\n(%.1f frames per second)\n"), msg, fps);

    outputl(str);

    if (fps <= 20) {             /* Give some advice, hopefully to speed things up */
        if (bd->rd->showShadows)
            outputl(_("Disable shadows to improve performance"));
    }
    outputx();
}

#endif

static void
Add2dLightOptions(GtkWidget * pwx, renderdata * prd)
{
    float rAzimuth, rElevation;
    GtkWidget *pScale;

#if GTK_CHECK_VERSION(3,0,0)
    GtkWidget *pw;

    pwLightGrid = gtk_grid_new();
    gtk_box_pack_start(GTK_BOX(pwx), pwLightGrid, FALSE, FALSE, 4);

    gtk_grid_attach(GTK_GRID(pwLightGrid), pw = gtk_label_new(_("Light azimuth")), 0, 0, 1, 1);
    gtk_widget_set_halign(pw, GTK_ALIGN_START);

#if GTK_CHECK_VERSION(3,12,0)
    gtk_widget_set_margin_start(pw, 4);
    gtk_widget_set_margin_end(pw, 4);
#else
    gtk_widget_set_margin_left(pw, 4);
    gtk_widget_set_margin_right(pw, 4);
#endif
    gtk_widget_set_margin_top(pw, 2);
    gtk_widget_set_margin_bottom(pw, 2);

    gtk_grid_attach(GTK_GRID(pwLightGrid), pw = gtk_label_new(_("Light elevation")), 0, 1, 1, 1);
    gtk_widget_set_halign(pw, GTK_ALIGN_START);

#if GTK_CHECK_VERSION(3,12,0)
    gtk_widget_set_margin_start(pw, 4);
    gtk_widget_set_margin_end(pw, 4);
#else
    gtk_widget_set_margin_left(pw, 4);
    gtk_widget_set_margin_right(pw, 4);
#endif
    gtk_widget_set_margin_top(pw, 2);
    gtk_widget_set_margin_bottom(pw, 2);

#else
    pwLightTable = gtk_table_new(2, 2, FALSE);
    gtk_box_pack_start(GTK_BOX(pwx), pwLightTable, FALSE, FALSE, 4);

    gtk_table_attach(GTK_TABLE(pwLightTable), gtk_label_new(_("Light azimuth")), 0, 1, 0, 1, 0, 0, 4, 2);
    gtk_table_attach(GTK_TABLE(pwLightTable), gtk_label_new(_("Light elevation")), 0, 1, 1, 2, 0, 0, 4, 2);
#endif

    rElevation = asinf(prd->arLight[2]) * 180.0f / F_PI;
    {
        float s = sqrtf(1.0f - prd->arLight[2] * prd->arLight[2]);
        if (s == 0)
            rAzimuth = 0;
        else {
            float ac = acosf(prd->arLight[0] / s);
            if (ac == 0)
                rAzimuth = 0;
            else
                rAzimuth = ac * 180.0f / F_PI;
        }
    }
    if (prd->arLight[1] < 0)
        rAzimuth = 360 - rAzimuth;

    paAzimuth = GTK_ADJUSTMENT(gtk_adjustment_new(rAzimuth, 0.0, 360.0, 1.0, 30.0, 0.0));
#if GTK_CHECK_VERSION(3,0,0)
    pScale = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, paAzimuth);
#else
    pScale = gtk_hscale_new(paAzimuth);
#endif
    gtk_widget_set_size_request(pScale, 150, -1);

#if GTK_CHECK_VERSION(3,0,0)
    gtk_grid_attach(GTK_GRID(pwLightGrid), pScale, 1, 0, 1, 1);
#if GTK_CHECK_VERSION(3,12,0)
    gtk_widget_set_margin_start(pScale, 4);
    gtk_widget_set_margin_end(pScale, 4);
#else
    gtk_widget_set_margin_left(pScale, 4);
    gtk_widget_set_margin_right(pScale, 4);
#endif
    gtk_widget_set_margin_top(pScale, 2);
    gtk_widget_set_margin_bottom(pScale, 2);
#else
    gtk_table_attach(GTK_TABLE(pwLightTable), pScale, 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, 0, 4, 2);
#endif
    g_signal_connect(G_OBJECT(paAzimuth), "value-changed", G_CALLBACK(LightChanged2d), NULL);

    paElevation = GTK_ADJUSTMENT(gtk_adjustment_new(rElevation, 0.0, 90.0, 1.0, 10.0, 0.0));
#if GTK_CHECK_VERSION(3,0,0)
    gtk_grid_attach(GTK_GRID(pwLightGrid), pw = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, paElevation), 1, 1, 1, 1);
#if GTK_CHECK_VERSION(3,12,0)
    gtk_widget_set_margin_start(pw, 4);
    gtk_widget_set_margin_end(pw, 4);
#else
    gtk_widget_set_margin_left(pw, 4);
    gtk_widget_set_margin_right(pw, 4);
#endif
    gtk_widget_set_margin_top(pw, 2);
    gtk_widget_set_margin_bottom(pw, 2);
#else
    gtk_table_attach(GTK_TABLE(pwLightTable), gtk_hscale_new(paElevation), 1, 2, 1, 2, GTK_EXPAND | GTK_FILL, 0, 4, 2);
#endif
    g_signal_connect(G_OBJECT(paElevation), "value-changed", G_CALLBACK(LightChanged2d), NULL);

    /* FIXME add settings for ambient light */
}

#if defined(USE_BOARD3D)
static GtkWidget *
LightingPage(BoardData * bd)
{
    GtkWidget *dtBox, *pwx;

#if GTK_CHECK_VERSION(3,0,0)
    pwx = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    dtBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
#else
    pwx = gtk_hbox_new(FALSE, 0);
    dtBox = gtk_vbox_new(FALSE, 4);
#endif

    gtk_box_pack_start(GTK_BOX(pwx), dtBox, FALSE, FALSE, 0);

    if (display_is_3d(&rdPrefs)) {
        GtkWidget *vbox, *vbox2, *frameBox, *hBox, *lab;
        GtkWidget *pwLightPosX, *pwLightLevelAmbient, *pwLightLevelDiffuse, *pwLightLevelSpecular;
        GtkWidget *pwLightPosY, *pwLightPosZ, *dtLightSourceFrame;

#if GTK_CHECK_VERSION(3,0,0)
        dtBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
        dtBox = gtk_vbox_new(FALSE, 0);
#endif
        gtk_box_pack_start(GTK_BOX(pwx), dtBox, FALSE, FALSE, 0);

        dtLightSourceFrame = gtk_frame_new(_("Light Source Type"));
        gtk_container_set_border_width(GTK_CONTAINER(dtLightSourceFrame), 4);
        gtk_box_pack_start(GTK_BOX(dtBox), dtLightSourceFrame, FALSE, FALSE, 0);

#if GTK_CHECK_VERSION(3,0,0)
        vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
        vbox = gtk_vbox_new(FALSE, 0);
#endif
        gtk_container_add(GTK_CONTAINER(dtLightSourceFrame), vbox);

        pwLightSource = gtk_radio_button_new_with_label(NULL, _("Positional"));
        gtk_widget_set_tooltip_text(pwLightSource, _("This is a fixed light source, like a lamp"));
        gtk_box_pack_start(GTK_BOX(vbox), pwLightSource, FALSE, FALSE, 0);
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwLightSource), (bd->rd->lightType == LT_POSITIONAL));

        pwDirectionalSource =
            gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(pwLightSource), _("Directional"));
        gtk_widget_set_tooltip_text(pwDirectionalSource, _("This is a light direction, like the sun"));
        gtk_box_pack_start(GTK_BOX(vbox), pwDirectionalSource, FALSE, FALSE, 0);
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwDirectionalSource), (bd->rd->lightType == LT_DIRECTIONAL));
        g_signal_connect(G_OBJECT(pwDirectionalSource), "toggled", G_CALLBACK(option_changed), bd);

        dtLightPositionFrame = gtk_frame_new(_("Light Position"));
        gtk_container_set_border_width(GTK_CONTAINER(dtLightPositionFrame), 4);
        gtk_box_pack_start(GTK_BOX(dtBox), dtLightPositionFrame, FALSE, FALSE, 0);

#if GTK_CHECK_VERSION(3,0,0)
        frameBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
        frameBox = gtk_vbox_new(FALSE, 0);
#endif
        gtk_container_add(GTK_CONTAINER(dtLightPositionFrame), frameBox);

#if GTK_CHECK_VERSION(3,0,0)
        hBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
        hBox = gtk_hbox_new(FALSE, 0);
#endif
        gtk_box_pack_start(GTK_BOX(frameBox), hBox, FALSE, FALSE, 0);

        lab = gtk_label_new(_("Left"));
        gtk_box_pack_start(GTK_BOX(hBox), lab, FALSE, FALSE, 0);

        padjLightPosX = GTK_ADJUSTMENT(gtk_adjustment_new(bd->rd->lightPos[0], -1.5, 4, .1, 1, 0));
        g_signal_connect(G_OBJECT(padjLightPosX), "value-changed", G_CALLBACK(option_changed), NULL);
#if GTK_CHECK_VERSION(3,0,0)
        pwLightPosX = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, padjLightPosX);
#else
        pwLightPosX = gtk_hscale_new(padjLightPosX);
#endif
        gtk_scale_set_draw_value(GTK_SCALE(pwLightPosX), FALSE);
        gtk_widget_set_size_request(pwLightPosX, 150, -1);
        gtk_box_pack_start(GTK_BOX(hBox), pwLightPosX, TRUE, TRUE, 0);

        lab = gtk_label_new(_("Right"));
        gtk_box_pack_start(GTK_BOX(hBox), lab, FALSE, FALSE, 0);

#if GTK_CHECK_VERSION(3,0,0)
        hBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
        hBox = gtk_hbox_new(FALSE, 0);
#endif
        gtk_box_pack_start(GTK_BOX(frameBox), hBox, FALSE, FALSE, 0);

#if GTK_CHECK_VERSION(3,0,0)
        vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
        vbox2 = gtk_vbox_new(FALSE, 0);
#endif
        gtk_box_pack_start(GTK_BOX(hBox), vbox2, FALSE, FALSE, 0);

        lab = gtk_label_new(_("Bottom"));
        gtk_box_pack_start(GTK_BOX(vbox2), lab, FALSE, FALSE, 0);

        padjLightPosY = GTK_ADJUSTMENT(gtk_adjustment_new(bd->rd->lightPos[1], -1.5, 4, .1, 1, 0));
        g_signal_connect(G_OBJECT(padjLightPosY), "value-changed", G_CALLBACK(option_changed), NULL);
#if GTK_CHECK_VERSION(3,0,0)
        pwLightPosY = gtk_scale_new(GTK_ORIENTATION_VERTICAL, padjLightPosY);
#else
        pwLightPosY = gtk_vscale_new(padjLightPosY);
#endif
        gtk_scale_set_draw_value(GTK_SCALE(pwLightPosY), FALSE);

        gtk_widget_set_size_request(pwLightPosY, -1, 70);
        gtk_box_pack_start(GTK_BOX(vbox2), pwLightPosY, TRUE, TRUE, 0);

        lab = gtk_label_new(_("Top"));
        gtk_box_pack_start(GTK_BOX(vbox2), lab, FALSE, FALSE, 0);

#if GTK_CHECK_VERSION(3,0,0)
        vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
        vbox2 = gtk_vbox_new(FALSE, 0);
#endif
        gtk_box_pack_start(GTK_BOX(hBox), vbox2, FALSE, FALSE, 0);

        lab = gtk_label_new(_("Low"));
        gtk_box_pack_start(GTK_BOX(vbox2), lab, FALSE, FALSE, 0);

        padjLightPosZ = GTK_ADJUSTMENT(gtk_adjustment_new(bd->rd->lightPos[2], .5, 5, .1, 1, 0));
        g_signal_connect(G_OBJECT(padjLightPosZ), "value-changed", G_CALLBACK(option_changed), NULL);
#if GTK_CHECK_VERSION(3,0,0)
        pwLightPosZ = gtk_scale_new(GTK_ORIENTATION_VERTICAL, padjLightPosZ);
#else
        pwLightPosZ = gtk_vscale_new(padjLightPosZ);
#endif
        gtk_scale_set_draw_value(GTK_SCALE(pwLightPosZ), FALSE);

        gtk_box_pack_start(GTK_BOX(vbox2), pwLightPosZ, TRUE, TRUE, 0);

        lab = gtk_label_new(_("High"));
        gtk_box_pack_start(GTK_BOX(vbox2), lab, FALSE, FALSE, 0);

        dtLightLevelsFrame = gtk_frame_new(_("Light Levels"));
        gtk_container_set_border_width(GTK_CONTAINER(dtLightLevelsFrame), 4);
        gtk_box_pack_start(GTK_BOX(dtBox), dtLightLevelsFrame, FALSE, FALSE, 0);

#if GTK_CHECK_VERSION(3,0,0)
        frameBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
        frameBox = gtk_vbox_new(FALSE, 0);
#endif
        gtk_container_add(GTK_CONTAINER(dtLightLevelsFrame), frameBox);

#if GTK_CHECK_VERSION(3,0,0)
        hBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
        hBox = gtk_hbox_new(FALSE, 0);
#endif
        gtk_box_pack_start(GTK_BOX(frameBox), hBox, FALSE, FALSE, 0);

        lab = gtk_label_new(_("Ambient"));
        gtk_box_pack_start(GTK_BOX(hBox), lab, FALSE, FALSE, 0);

        padjLightLevelAmbient = GTK_ADJUSTMENT(gtk_adjustment_new(bd->rd->lightLevels[0], 0, 100, 1, 10, 0));
        g_signal_connect(G_OBJECT(padjLightLevelAmbient), "value-changed", G_CALLBACK(option_changed), NULL);
#if GTK_CHECK_VERSION(3,0,0)
        pwLightLevelAmbient = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, padjLightLevelAmbient);
#else
        pwLightLevelAmbient = gtk_hscale_new(padjLightLevelAmbient);
#endif
        gtk_widget_set_tooltip_text(pwLightLevelAmbient, _("Ambient light specifies the general light level"));
        gtk_box_pack_start(GTK_BOX(hBox), pwLightLevelAmbient, TRUE, TRUE, 0);

#if GTK_CHECK_VERSION(3,0,0)
        hBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
        hBox = gtk_hbox_new(FALSE, 0);
#endif
        gtk_box_pack_start(GTK_BOX(frameBox), hBox, FALSE, FALSE, 0);

        lab = gtk_label_new(_("Diffuse"));
        gtk_box_pack_start(GTK_BOX(hBox), lab, FALSE, FALSE, 0);

        padjLightLevelDiffuse = GTK_ADJUSTMENT(gtk_adjustment_new(bd->rd->lightLevels[1], 0, 100, 1, 10, 0));
        g_signal_connect(G_OBJECT(padjLightLevelDiffuse), "value-changed", G_CALLBACK(option_changed), NULL);
#if GTK_CHECK_VERSION(3,0,0)
        pwLightLevelDiffuse = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, padjLightLevelDiffuse);
#else
        pwLightLevelDiffuse = gtk_hscale_new(padjLightLevelDiffuse);
#endif
        gtk_widget_set_tooltip_text(pwLightLevelDiffuse, _("Diffuse light specifies light from the light source"));
        gtk_box_pack_start(GTK_BOX(hBox), pwLightLevelDiffuse, TRUE, TRUE, 0);

#if GTK_CHECK_VERSION(3,0,0)
        hBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
        hBox = gtk_hbox_new(FALSE, 0);
#endif
        gtk_box_pack_start(GTK_BOX(frameBox), hBox, FALSE, FALSE, 0);

        lab = gtk_label_new(_("Specular"));
        gtk_box_pack_start(GTK_BOX(hBox), lab, FALSE, FALSE, 0);

        padjLightLevelSpecular = GTK_ADJUSTMENT(gtk_adjustment_new(bd->rd->lightLevels[2], 0, 100, 1, 10, 0));
        g_signal_connect(G_OBJECT(padjLightLevelSpecular), "value-changed", G_CALLBACK(option_changed), NULL);
#if GTK_CHECK_VERSION(3,0,0)
        pwLightLevelSpecular = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, padjLightLevelSpecular);
#else
        pwLightLevelSpecular = gtk_hscale_new(padjLightLevelSpecular);
#endif
        gtk_widget_set_tooltip_text(pwLightLevelSpecular, _("Specular light is reflected light off shiny surfaces"));
        gtk_box_pack_start(GTK_BOX(hBox), pwLightLevelSpecular, TRUE, TRUE, 0);
    } else
        Add2dLightOptions(dtBox, bd->rd);

    gtk_widget_show_all(pwx);

    return pwx;
}

#endif

#if defined(USE_BOARD3D)
static GtkWidget *
GeneralPage(BoardData * bd, GtkWidget * bdMain)
{
#else
static GtkWidget *
GeneralPage(BoardData * bd, GtkWidget * UNUSED(bdMain))
{
#endif

    GtkWidget *pw, *pwx;
#if defined(USE_BOARD3D)
    GtkWidget *dtBox, *button, *dtFrame, *hBox, *lab, *pwev, *pwhbox, *pwvbox, *pwAccuracy, *pwDiceSize;
#endif

#if GTK_CHECK_VERSION(3,0,0)
    pwx = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    pw = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
    pwx = gtk_hbox_new(FALSE, 0);
    pw = gtk_vbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pwx), pw, FALSE, FALSE, 0);

#if defined(USE_BOARD3D)
    dtFrame = gtk_frame_new(_("Display Type"));
    gtk_container_set_border_width(GTK_CONTAINER(dtFrame), 4);
    gtk_box_pack_start(GTK_BOX(pw), dtFrame, FALSE, FALSE, 0);

#if GTK_CHECK_VERSION(3,0,0)
    dtBox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
    dtBox = gtk_vbox_new(FALSE, 0);
#endif
    gtk_container_add(GTK_CONTAINER(dtFrame), dtBox);

    pwBoardType = gtk_radio_button_new_with_label(NULL, _("2D board"));
    gtk_box_pack_start(GTK_BOX(dtBox), pwBoardType, FALSE, FALSE, 0);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwBoardType), (display_is_2d(bd->rd)));

    button = gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(pwBoardType), _("3D board"));
    if (!widget3dValid)
        gtk_widget_set_sensitive(button, FALSE);
    gtk_box_pack_start(GTK_BOX(dtBox), button, FALSE, FALSE, 0);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), (display_is_3d(bd->rd)));
    g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(toggle_display_type), bd);
#endif

    pwLabels = gtk_check_button_new_with_label(_("Numbered point labels"));
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwLabels), bd->rd->fLabels);
    gtk_box_pack_start(GTK_BOX(pw), pwLabels, FALSE, FALSE, 0);
    gtk_widget_set_tooltip_text(pwLabels, _("Show or hide point numbers"));

    pwDynamicLabels = gtk_check_button_new_with_label(_("Dynamic labels"));
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwDynamicLabels), bd->rd->fDynamicLabels);
    gtk_box_pack_start(GTK_BOX(pw), pwDynamicLabels, FALSE, FALSE, 0);
    g_signal_connect(G_OBJECT(pwDynamicLabels), "toggled", G_CALLBACK(LabelsToggled), 0);

    g_signal_connect(G_OBJECT(pwLabels), "toggled", G_CALLBACK(LabelsToggled), 0);
    LabelsToggled(0, 0);

    gtk_widget_set_tooltip_text(pwDynamicLabels, _("Update the labels so they are correct " "for the player on roll"));

    pwMoveIndicator = gtk_check_button_new_with_label(_("Show move indicator"));
    gtk_widget_set_tooltip_text(pwMoveIndicator, _("Show or hide arrow indicating who is moving"));
    gtk_box_pack_start(GTK_BOX(pw), pwMoveIndicator, FALSE, FALSE, 0);

    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwMoveIndicator), bd->rd->showMoveIndicator);
    g_signal_connect(G_OBJECT(pwMoveIndicator), "toggled", G_CALLBACK(MoveIndicatorToggled), 0);
#if defined(USE_BOARD3D)
#if GTK_CHECK_VERSION(3,0,0)
    pwvbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
    pwvbox = gtk_vbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pwx), pwvbox, FALSE, FALSE, 0);

    frame3dOptions = gtk_frame_new(_("3D options"));
    gtk_container_set_border_width(GTK_CONTAINER(frame3dOptions), 4);
    gtk_box_pack_start(GTK_BOX(pwvbox), frame3dOptions, FALSE, FALSE, 0);

#if GTK_CHECK_VERSION(3,0,0)
    pw = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
#else
    pw = gtk_vbox_new(FALSE, 0);
#endif
    gtk_container_add(GTK_CONTAINER(frame3dOptions), pw);

    pwShowShadows = gtk_check_button_new_with_label(_("Show shadows"));
    gtk_widget_set_tooltip_text(pwShowShadows, _("Display shadows, this option requires a fast graphics card"));
    gtk_box_pack_start(GTK_BOX(pw), pwShowShadows, FALSE, FALSE, 0);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwShowShadows), bd->rd->showShadows);
    g_signal_connect(G_OBJECT(pwShowShadows), "toggled", G_CALLBACK(toggle_show_shadows), NULL);

#if GTK_CHECK_VERSION(3,0,0)
    hBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    hBox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pw), hBox, FALSE, FALSE, 0);

    lightLab = gtk_label_new(_("light"));
    gtk_box_pack_start(GTK_BOX(hBox), lightLab, FALSE, FALSE, 0);

    padjDarkness = GTK_ADJUSTMENT(gtk_adjustment_new(bd->rd->shadowDarkness, 3, 100, 1, 10, 0));
    g_signal_connect(G_OBJECT(padjDarkness), "value-changed", G_CALLBACK(option_changed), NULL);
#if GTK_CHECK_VERSION(3,0,0)
    pwDarkness = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, padjDarkness);
#else
    pwDarkness = gtk_hscale_new(padjDarkness);
#endif
    gtk_widget_set_tooltip_text(pwDarkness, _("Vary the darkness of the shadows"));
    gtk_scale_set_draw_value(GTK_SCALE(pwDarkness), FALSE);
    gtk_box_pack_start(GTK_BOX(hBox), pwDarkness, TRUE, TRUE, 0);

    darkLab = gtk_label_new(_("dark"));
    gtk_box_pack_start(GTK_BOX(hBox), darkLab, FALSE, FALSE, 0);

    toggle_show_shadows(pwShowShadows, -1);

    pwAnimateRoll = gtk_check_button_new_with_label(_("Animate dice rolls"));
    gtk_widget_set_tooltip_text(pwAnimateRoll, _("Dice rolls will shake across board"));
    gtk_box_pack_start(GTK_BOX(pw), pwAnimateRoll, FALSE, FALSE, 0);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwAnimateRoll), bd->rd->animateRoll);

    pwAnimateFlag = gtk_check_button_new_with_label(_("Animate resignation flag"));
    gtk_widget_set_tooltip_text(pwAnimateFlag, _("Waves resignation flag"));
    gtk_box_pack_start(GTK_BOX(pw), pwAnimateFlag, FALSE, FALSE, 0);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwAnimateFlag), bd->rd->animateFlag);

    pwev = gtk_event_box_new();
    gtk_event_box_set_visible_window(GTK_EVENT_BOX(pwev), FALSE);
    gtk_box_pack_start(GTK_BOX(pw), pwev, FALSE, FALSE, 0);
#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
#else
    pwhbox = gtk_hbox_new(FALSE, 4);
#endif
    gtk_container_add(GTK_CONTAINER(pwev), pwhbox);

#if GTK_CHECK_VERSION(3,0,0)
    hBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    hBox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pw), hBox, FALSE, FALSE, 0);

    anglelab = gtk_label_new(_("Board angle: "));
    gtk_box_pack_start(GTK_BOX(hBox), anglelab, FALSE, FALSE, 0);

    padjBoardAngle = GTK_ADJUSTMENT(gtk_adjustment_new(bd->rd->boardAngle, 0, 60, 1, 10, 0));
    g_signal_connect(G_OBJECT(padjBoardAngle), "value-changed", G_CALLBACK(option_changed), NULL);
#if GTK_CHECK_VERSION(3,0,0)
    pwBoardAngle = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, padjBoardAngle);
#else
    pwBoardAngle = gtk_hscale_new(padjBoardAngle);
#endif
    gtk_widget_set_tooltip_text(pwBoardAngle, _("Vary the angle the board is tilted at"));
    gtk_scale_set_digits(GTK_SCALE(pwBoardAngle), 0);
    gtk_widget_set_size_request(pwBoardAngle, 100, -1);
    gtk_box_pack_start(GTK_BOX(hBox), pwBoardAngle, FALSE, FALSE, 0);

#if GTK_CHECK_VERSION(3,0,0)
    hBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    hBox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pw), hBox, FALSE, FALSE, 0);

    skewLab = gtk_label_new(_("FOV skew: "));
    gtk_box_pack_start(GTK_BOX(hBox), skewLab, FALSE, FALSE, 0);

    padjSkewFactor = GTK_ADJUSTMENT(gtk_adjustment_new(bd->rd->skewFactor, 0, 100, 1, 10, 0));
    g_signal_connect(G_OBJECT(padjSkewFactor), "value-changed", G_CALLBACK(option_changed), NULL);
#if GTK_CHECK_VERSION(3,0,0)
    pwSkewFactor = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, padjSkewFactor);
#else
    pwSkewFactor = gtk_hscale_new(padjSkewFactor);
#endif
    gtk_widget_set_size_request(pwSkewFactor, 100, -1);
    gtk_widget_set_tooltip_text(pwSkewFactor, _("Vary the field-of-view of the 3D display"));
    gtk_scale_set_digits(GTK_SCALE(pwSkewFactor), 0);
    gtk_box_pack_start(GTK_BOX(hBox), pwSkewFactor, FALSE, FALSE, 0);

    pwPlanView = gtk_check_button_new_with_label(_("Plan view"));
    gtk_widget_set_tooltip_text(pwPlanView, _("Display the 3D board with a 2D overhead view"));
    gtk_box_pack_start(GTK_BOX(pw), pwPlanView, FALSE, FALSE, 0);
    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwPlanView), bd->rd->planView);
    g_signal_connect(G_OBJECT(pwPlanView), "toggled", G_CALLBACK(toggle_planview), NULL);
    toggle_planview(pwPlanView, 0);

    lab = gtk_label_new(_("Curve accuracy"));
    gtk_box_pack_start(GTK_BOX(pw), lab, FALSE, FALSE, 0);

#if GTK_CHECK_VERSION(3,0,0)
    hBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    hBox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pw), hBox, FALSE, FALSE, 0);

    lab = gtk_label_new(_("low"));
    gtk_box_pack_start(GTK_BOX(hBox), lab, FALSE, FALSE, 0);

    padjAccuracy = GTK_ADJUSTMENT(gtk_adjustment_new(bd->rd->curveAccuracy, 8, 60, 4, 12, 0));
    g_signal_connect(G_OBJECT(padjAccuracy), "value-changed", G_CALLBACK(option_changed), NULL);
#if GTK_CHECK_VERSION(3,0,0)
    pwAccuracy = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, padjAccuracy);
#else
    pwAccuracy = gtk_hscale_new(padjAccuracy);
#endif
    gtk_widget_set_tooltip_text(pwAccuracy, _("Change how accurately curves are drawn."
                                              " If performance is slow try lowering this value."
                                              " Increasing this value will only have an effect on large displays"));
    gtk_scale_set_draw_value(GTK_SCALE(pwAccuracy), FALSE);
    gtk_box_pack_start(GTK_BOX(hBox), pwAccuracy, TRUE, TRUE, 0);

    lab = gtk_label_new(_("high"));
    gtk_box_pack_start(GTK_BOX(hBox), lab, FALSE, FALSE, 0);


    lab = gtk_label_new(_("Dice size"));
    gtk_box_pack_start(GTK_BOX(pw), lab, FALSE, FALSE, 0);

#if GTK_CHECK_VERSION(3,0,0)
    hBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    hBox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pw), hBox, FALSE, FALSE, 0);

    lab = gtk_label_new(_("small"));
    gtk_box_pack_start(GTK_BOX(hBox), lab, FALSE, FALSE, 0);

    padjDiceSize = GTK_ADJUSTMENT(gtk_adjustment_new(bd->rd->diceSize, 1.5, 4, .1, 1, 0));
    g_signal_connect(G_OBJECT(padjDiceSize), "value-changed", G_CALLBACK(DiceSizeChanged), NULL);
#if GTK_CHECK_VERSION(3,0,0)
    pwDiceSize = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, padjDiceSize);
#else
    pwDiceSize = gtk_hscale_new(padjDiceSize);
#endif
    gtk_widget_set_tooltip_text(pwDiceSize, _("Vary the size of the dice"));
    gtk_scale_set_draw_value(GTK_SCALE(pwDiceSize), FALSE);
    gtk_box_pack_start(GTK_BOX(hBox), pwDiceSize, TRUE, TRUE, 0);

    lab = gtk_label_new(_("large"));
    gtk_box_pack_start(GTK_BOX(hBox), lab, FALSE, FALSE, 0);

    pwTestPerformance = gtk_button_new_with_label(_("Test performance"));
    gtk_widget_set_sensitive(pwTestPerformance, (display_is_3d(bd->rd)));
    gtk_box_pack_start(GTK_BOX(pwvbox), pwTestPerformance, FALSE, FALSE, 4);
    g_signal_connect(G_OBJECT(pwTestPerformance), "clicked", G_CALLBACK(DoTestPerformance), bdMain);

#else
    Add2dLightOptions(pw, bd->rd);
#endif
    return pwx;
}


/* functions for board design */

static void
UseDesign(void)
{
    gfloat rAzimuth, rElevation;
    renderdata newPrefs;
#if defined(USE_BOARD3D)
    BoardData *bd = BOARD(pwPrevBoard)->board_data;
#endif

    fUpdate = FALSE;

#if defined(USE_BOARD3D)
    if (display_is_3d(&rdPrefs))
        ClearTextures(bd->bd3d);
#endif

    ParsePreferences(pbdeSelected, &newPrefs);

#if defined(USE_BOARD3D)

    /* Set only 2D or 3D options */
    if (display_is_3d(&rdPrefs)) {
        Set3dSettings(&rdPrefs, &newPrefs);
        GetTextures(bd->bd3d, bd->rd);

        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwLightSource), (newPrefs.lightType == LT_POSITIONAL));
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwDirectionalSource), (newPrefs.lightType == LT_DIRECTIONAL));

        gtk_adjustment_set_value(GTK_ADJUSTMENT(padjLightPosX), newPrefs.lightPos[0]);

        gtk_adjustment_set_value(GTK_ADJUSTMENT(padjLightPosY), newPrefs.lightPos[1]);
        gtk_adjustment_set_value(GTK_ADJUSTMENT(padjLightPosZ), newPrefs.lightPos[2]);

        gtk_adjustment_set_value(GTK_ADJUSTMENT(padjLightLevelAmbient), newPrefs.lightLevels[0]);
        gtk_adjustment_set_value(GTK_ADJUSTMENT(padjLightLevelDiffuse), newPrefs.lightLevels[1]);
        gtk_adjustment_set_value(GTK_ADJUSTMENT(padjLightLevelSpecular), newPrefs.lightLevels[2]);

        gtk_adjustment_set_value(GTK_ADJUSTMENT(padjBoardAngle), newPrefs.boardAngle);

        gtk_adjustment_set_value(GTK_ADJUSTMENT(padjSkewFactor), newPrefs.skewFactor);

        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwTextureAllPiece), (newPrefs.pieceTextureType == PTT_ALL));
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwTextureTopPiece), (newPrefs.pieceTextureType == PTT_TOP));

        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwRoundedPiece), (newPrefs.pieceType == PT_ROUNDED));
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwFlatPiece), (newPrefs.pieceType == PT_FLAT));

        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwRoundedEdges), newPrefs.roundedEdges);
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwBgTrays), newPrefs.bgInTrays);
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwRoundPoints), newPrefs.roundedPoints);

        for (int i = 0; i < 2; i++)
            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(apwDieColour[i]), newPrefs.afDieColour3d[i]);

        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwHinges), newPrefs.fHinges3d);

        redrawChange = TRUE;
    } else
#endif
    {
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwMoveIndicator), newPrefs.showMoveIndicator);

        /* chequers */

        for (int i = 0; i < 2; ++i) {
            gtk_color_button_set_from_array(GTK_COLOR_BUTTON(apwColour[i]), newPrefs.aarColour[i]);

            gtk_adjustment_set_value(GTK_ADJUSTMENT(apadj[i]), newPrefs.arRefraction[i]);

            gtk_adjustment_set_value(GTK_ADJUSTMENT(apadjCoefficient[i]), newPrefs.arCoefficient[i]);

            gtk_adjustment_set_value(GTK_ADJUSTMENT(apadjExponent[i]), newPrefs.arExponent[i]);
        }

        /* dice + dice dot */

        for (int i = 0; i < 2; ++i) {

            gtk_widget_set_sensitive(GTK_WIDGET(apwDiceColourBox[i]), !newPrefs.afDieColour[i]);

            gtk_color_button_set_from_array(GTK_COLOR_BUTTON(apwDiceColour[i]),
                                            newPrefs.afDieColour[i] ?
                                            newPrefs.aarColour[i] : newPrefs.aarDiceColour[i]);

            gtk_adjustment_set_value(GTK_ADJUSTMENT(apadjDiceExponent[i]),
                                     newPrefs.afDieColour[i] ? newPrefs.arExponent[i] : newPrefs.arDiceExponent[i]);

            gtk_adjustment_set_value(GTK_ADJUSTMENT(apadjDiceCoefficient[i]),
                                     newPrefs.afDieColour[i] ?
                                     newPrefs.arCoefficient[i] : newPrefs.arDiceCoefficient[i]);


            /* die dot */

            gtk_color_button_set_from_array(GTK_COLOR_BUTTON(apwDiceDotColour[i]), newPrefs.aarDiceDotColour[i]);

        }

        /* cube colour */

        gtk_color_button_set_from_array(GTK_COLOR_BUTTON(pwCubeColour), newPrefs.arCubeColour);

        /* board + points */

        for (int i = 0; i < 4; ++i) {
            float ar[4];

            if (i != 1)
                gtk_adjustment_set_value(GTK_ADJUSTMENT(apadjBoard[i]), newPrefs.aSpeckle[i] / 128.0);

            for (int j = 0; j < 4; j++)
                ar[j] = newPrefs.aanBoardColour[i][j] / 255.0f;

            gtk_color_button_set_from_array(GTK_COLOR_BUTTON(apwBoard[i]), ar);
        }

        /* board, border, and points */

        gtk_combo_box_set_active(GTK_COMBO_BOX(pwWoodType), newPrefs.wt != WOOD_PAINT ? newPrefs.wt : 0);
        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(newPrefs.wt != WOOD_PAINT ? pwWood : pwWoodF), TRUE);

        gtk_widget_set_sensitive(pwWoodType, newPrefs.wt != WOOD_PAINT);
        gtk_widget_set_sensitive(apwBoard[1], newPrefs.wt == WOOD_PAINT);

        /* round */

        gtk_adjustment_set_value(GTK_ADJUSTMENT(padjRound), 1.0f - newPrefs.rRound);

        /* light */

        rElevation = asinf(newPrefs.arLight[2]) * 180.0f / F_PI;
        if (fabs(newPrefs.arLight[2] - 1.0f) < 1e-5)
            rAzimuth = 0.0;
        else
            rAzimuth = acosf(newPrefs.arLight[0] / sqrtf(1.0f - newPrefs.arLight[2] * newPrefs.arLight[2])) * 180.0f / F_PI;
        if (newPrefs.arLight[1] < 0)
            rAzimuth = 360 - rAzimuth;

        gtk_adjustment_set_value(GTK_ADJUSTMENT(paAzimuth), rAzimuth);
        gtk_adjustment_set_value(GTK_ADJUSTMENT(paElevation), rElevation);

        for (int i = 0; i < 2; i++)
            gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(apwDieColour[i]), newPrefs.afDieColour[i]);

        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pwHinges), newPrefs.fHinges);

        GetPrefs(&rdPrefs);
    }

    fUpdate = TRUE;
    option_changed(0, 0);
}

static void
WriteTheDesign(boarddesign * pbde, FILE * pf)
{
    gchar *title, *author;
    if (pbde->szTitle) {
        title = g_markup_escape_text(pbde->szTitle, -1);
    } else
        title = _("unknown title");

    if (pbde->szAuthor) {
        author = g_markup_escape_text(pbde->szAuthor, -1);
    } else
        author = _("unknown author");

    fputs("   <board-design>\n\n", pf);

    /* <about> */

    fprintf(pf,
            "      <about>\n"
            "         <title>%s</title>\n"
            "         <author>%s</author>\n"
            "      </about>\n\n"
            "      <design>%s      </design>\n\n", title, author, pbde->szBoardDesign ? pbde->szBoardDesign : "\n");

    fputs("   </board-design>\n\n\n", pf);

    if (pbde->szTitle)
        g_free(title);
    if (pbde->szAuthor)
        g_free(author);
}

static void
WriteDesignOnlyDeletables(gpointer data, gpointer user_data)
{

    boarddesign *pbde = data;
    FILE *pf = user_data;

    if (!pbde)
        return;

    if (!pbde->fDeletable)
        /* predefined board design */
        return;

    WriteTheDesign(pbde, pf);

}

static void
WriteDesign(gpointer data, gpointer user_data)
{

    boarddesign *pbde = data;
    FILE *pf = user_data;

    if (!pbde)
        return;

    WriteTheDesign(pbde, pf);

}

static void
WriteDesignHeader(const char *szFile, FILE * pf)
{

    time_t t;

    fprintf(pf,
            "<?xml version=\"1.0\" encoding=\"" GNUBG_CHARSET "\"?>\n"
            "\n" "<!--\n" "\n" "    %s\n" "       generated by " VERSION_STRING "\n", szFile);
    fputs("       ", pf);
    time(&t);
    fputs(ctime(&t), pf);
    fputs("\n"
          "    $Id: gtkprefs.c,v 1.234 2022/08/30 18:38:35 plm Exp $\n"
          "\n" " -->\n" "\n" "\n" "<board-designs>\n" "\n", pf);

}

static void
WriteDesignFooter(FILE * pf)
{

    fputs("</board-designs>\n", pf);

}



static void
DesignSave(GtkWidget * UNUSED(pw), gpointer data)
{

    gchar *szFile;
    FILE *pf;
    GList *plBDs = (GList *) data;

    szFile = g_build_filename(szHomeDirectory, "boards.xml", NULL);

    if ((pf = g_fopen(szFile, "w+")) == 0) {
        outputerr(szFile);
        g_free(szFile);
        return;
    }

    WriteDesignHeader(szFile, pf);
    g_list_foreach(plBDs, WriteDesignOnlyDeletables, pf);
    WriteDesignFooter(pf);

    fclose(pf);

    g_free(szFile);

}

static void
DesignAddOK(GtkWidget * pw, boarddesign * pbde)
{

    pbde->szAuthor = gtk_editable_get_chars(GTK_EDITABLE(pwDesignAddAuthor), 0, -1);
    pbde->szTitle = gtk_editable_get_chars(GTK_EDITABLE(pwDesignAddTitle), 0, -1);

    gtk_widget_destroy(gtk_widget_get_toplevel(pw));

}

static void
DesignAddChanged(GtkWidget * UNUSED(pw), GtkWidget * pwDialog)
{

    char *szAuthor = gtk_editable_get_chars(GTK_EDITABLE(pwDesignAddAuthor),
                                            0, -1);
    char *szTitle = gtk_editable_get_chars(GTK_EDITABLE(pwDesignAddTitle),
                                           0, -1);

    GtkWidget *pwOK = DialogArea(GTK_WIDGET(pwDialog), DA_OK);

    gtk_widget_set_sensitive(GTK_WIDGET(pwOK), szAuthor && szTitle && *szAuthor && *szTitle);

}

static void
DesignAddTitle(boarddesign * pbde)
{

    GtkWidget *pwDialog;
    GtkWidget *pwvbox;
    GtkWidget *pwhbox;

    pwDialog = GTKCreateDialog(_("GNU Backgammon - Add current board design"),
                               DT_QUESTION, NULL, DIALOG_FLAG_MODAL, G_CALLBACK(DesignAddOK), pbde);

#if GTK_CHECK_VERSION(3,0,0)
    pwvbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
#else
    pwvbox = gtk_vbox_new(FALSE, 4);
#endif
    gtk_container_add(GTK_CONTAINER(DialogArea(pwDialog, DA_MAIN)), pwvbox);

    /* title */

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
#else
    pwhbox = gtk_hbox_new(FALSE, 4);
#endif
    gtk_box_pack_start(GTK_BOX(pwvbox), pwhbox, FALSE, FALSE, 4);


    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Title of new design:")), FALSE, FALSE, 4);

    pwDesignAddTitle = gtk_entry_new();
    gtk_entry_set_activates_default(GTK_ENTRY(pwDesignAddTitle), TRUE);
    gtk_box_pack_start(GTK_BOX(pwhbox), pwDesignAddTitle, FALSE, FALSE, 4);

    g_signal_connect(G_OBJECT(pwDesignAddTitle), "changed", G_CALLBACK(DesignAddChanged), pwDialog);

    /* author */

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
#else
    pwhbox = gtk_hbox_new(FALSE, 4);
#endif
    gtk_box_pack_start(GTK_BOX(pwvbox), pwhbox, FALSE, FALSE, 4);


    gtk_box_pack_start(GTK_BOX(pwhbox), gtk_label_new(_("Author of new design:")), FALSE, FALSE, 4);

    pwDesignAddAuthor = gtk_entry_new();
    gtk_entry_set_activates_default(GTK_ENTRY(pwDesignAddAuthor), TRUE);
    gtk_box_pack_start(GTK_BOX(pwhbox), pwDesignAddAuthor, FALSE, FALSE, 4);

    g_signal_connect(G_OBJECT(pwDesignAddAuthor), "changed", G_CALLBACK(DesignAddChanged), pwDialog);

    /* show dialog */

    gtk_widget_grab_focus(pwDesignAddTitle);

    DesignAddChanged(NULL, pwDialog);

    GTKRunDialog(pwDialog);
}

static void
WriteDesignString(boarddesign * pbde, renderdata * prd)
{
    char szTemp[2048], *pTemp;
    gchar buf1[G_ASCII_DTOSTR_BUF_SIZE], buf2[G_ASCII_DTOSTR_BUF_SIZE],
        buf3[G_ASCII_DTOSTR_BUF_SIZE], buf4[G_ASCII_DTOSTR_BUF_SIZE];

    float rElevation = asinf(prd->arLight[2]) * 180.0f / F_PI;
    float rAzimuth = (fabs(prd->arLight[2] - 1.0f) < 1e-5) ? 0.0f :
        acosf(prd->arLight[0] / sqrtf(1.0f - prd->arLight[2] * prd->arLight[2])) * 180.0f / F_PI;

    if (prd->arLight[1] < 0)
        rAzimuth = 360 - rAzimuth;

    pTemp = szTemp;
    pTemp += sprintf(pTemp, "\n" "         board=#%02X%02X%02X;%s\n" "         border=#%02X%02X%02X\n",
                     /* board */
                     prd->aanBoardColour[0][0], prd->aanBoardColour[0][1],
                     prd->aanBoardColour[0][2],
                     g_ascii_formatd(buf1, G_ASCII_DTOSTR_BUF_SIZE, "%0.2f", (float) prd->aSpeckle[0] / 128.0f),
                     /* border */
                     prd->aanBoardColour[1][0], prd->aanBoardColour[1][1], prd->aanBoardColour[1][2]);

    pTemp += sprintf(pTemp, "         wood=%s\n" "         hinges=%c\n" "         light=%s;%s\n" "         shape=%s\n",
                     /* wood ... */
                     aszWoodName[prd->wt],
                     prd->fHinges ? 'y' : 'n',
                     g_ascii_formatd(buf1, G_ASCII_DTOSTR_BUF_SIZE, "%0.0f", rAzimuth),
                     g_ascii_formatd(buf2, G_ASCII_DTOSTR_BUF_SIZE, "%0.0f", rElevation),
                     g_ascii_formatd(buf3, G_ASCII_DTOSTR_BUF_SIZE, "%0.1f", 1.0f - prd->rRound));

    pTemp += sprintf(pTemp, "         chequers0=#%02X%02X%02X;%s;%s;%s;%s\n",
                     /* chequers0 */
                     (int) (prd->aarColour[0][0] * 0xFF),
                     (int) (prd->aarColour[0][1] * 0xFF),
                     (int) (prd->aarColour[0][2] * 0xFF),
                     g_ascii_formatd(buf1, G_ASCII_DTOSTR_BUF_SIZE, "%0.2f", prd->aarColour[0][3]),
                     g_ascii_formatd(buf2, G_ASCII_DTOSTR_BUF_SIZE, "%0.2f", prd->arRefraction[0]),
                     g_ascii_formatd(buf3, G_ASCII_DTOSTR_BUF_SIZE, "%0.2f", prd->arCoefficient[0]),
                     g_ascii_formatd(buf4, G_ASCII_DTOSTR_BUF_SIZE, "%0.2f", prd->arExponent[0]));

    pTemp += sprintf(pTemp, "         chequers1=#%02X%02X%02X;%s;%s;%s;%s\n",
                     /* chequers1 */
                     (int) (prd->aarColour[1][0] * 0xFF),
                     (int) (prd->aarColour[1][1] * 0xFF),
                     (int) (prd->aarColour[1][2] * 0xFF),
                     g_ascii_formatd(buf1, G_ASCII_DTOSTR_BUF_SIZE, "%0.2f", prd->aarColour[1][3]),
                     g_ascii_formatd(buf2, G_ASCII_DTOSTR_BUF_SIZE, "%0.2f", prd->arRefraction[1]),
                     g_ascii_formatd(buf3, G_ASCII_DTOSTR_BUF_SIZE, "%0.2f", prd->arCoefficient[1]),
                     g_ascii_formatd(buf4, G_ASCII_DTOSTR_BUF_SIZE, "%0.2f", prd->arExponent[1]));

    pTemp += sprintf(pTemp, "         dice0=#%02X%02X%02X;%s;%s;%c\n",
                     /* dice0 */
                     (int) (prd->aarDiceColour[0][0] * 0xFF),
                     (int) (prd->aarDiceColour[0][1] * 0xFF),
                     (int) (prd->aarDiceColour[0][2] * 0xFF),
                     g_ascii_formatd(buf1, G_ASCII_DTOSTR_BUF_SIZE, "%0.2f", prd->arDiceCoefficient[0]),
                     g_ascii_formatd(buf2, G_ASCII_DTOSTR_BUF_SIZE, "%0.2f", prd->arDiceExponent[0]),
                     prd->afDieColour[0] ? 'y' : 'n');

    pTemp += sprintf(pTemp, "         dice1=#%02X%02X%02X;%s;%s;%c\n",
                     /* dice1 */
                     (int) (prd->aarDiceColour[1][0] * 0xFF),
                     (int) (prd->aarDiceColour[1][1] * 0xFF),
                     (int) (prd->aarDiceColour[1][2] * 0xFF),
                     g_ascii_formatd(buf1, G_ASCII_DTOSTR_BUF_SIZE, "%0.2f", prd->arDiceCoefficient[1]),
                     g_ascii_formatd(buf2, G_ASCII_DTOSTR_BUF_SIZE, "%0.2f", prd->arDiceExponent[1]),
                     prd->afDieColour[1] ? 'y' : 'n');

    pTemp += sprintf(pTemp,
                     "         dot0=#%02X%02X%02X\n" "         dot1=#%02X%02X%02X\n" "         cube=#%02X%02X%02X\n",
                     /* dot0 */
                     (int) (prd->aarDiceDotColour[0][0] * 0xFF),
                     (int) (prd->aarDiceDotColour[0][1] * 0xFF), (int) (prd->aarDiceDotColour[0][2] * 0xFF),
                     /* dot1 */
                     (int) (prd->aarDiceDotColour[1][0] * 0xFF),
                     (int) (prd->aarDiceDotColour[1][1] * 0xFF), (int) (prd->aarDiceDotColour[1][2] * 0xFF),
                     /* cube */
                     (int) (prd->arCubeColour[0] * 0xFF),
                     (int) (prd->arCubeColour[1] * 0xFF), (int) (prd->arCubeColour[2] * 0xFF));


#if defined(USE_BOARD3D)
    pTemp +=
#endif
        sprintf(pTemp, "         points0=#%02X%02X%02X;%s\n" "         points1=#%02X%02X%02X;%s\n",
                /* points0 */
                prd->aanBoardColour[2][0], prd->aanBoardColour[2][1],
                prd->aanBoardColour[2][2],
                g_ascii_formatd(buf1, G_ASCII_DTOSTR_BUF_SIZE, "%0.2f", (float) prd->aSpeckle[2] / 128.0f),
                /* points1 */
                prd->aanBoardColour[3][0], prd->aanBoardColour[3][1],
                prd->aanBoardColour[3][2],
                g_ascii_formatd(buf2, G_ASCII_DTOSTR_BUF_SIZE, "%0.2f", (float) prd->aSpeckle[3] / 128.0f));

#if defined(USE_BOARD3D)
    sprintf(pTemp,
            "         hinges3d=%c\n"
            "         piecetype=%d\n"
            "         piecetexturetype=%d\n"
            "         roundededges=%c\n"
            "         bgintrays=%c\n"
            "         roundedpoints=%c\n"
            "         lighttype=%c\n"
            "         lightposx=%s lightposy=%s lightposz=%s\n"
            "         lightambient=%d lightdiffuse=%d lightspecular=%d\n"
            "         chequers3d0=%s\n"
            "         chequers3d1=%s\n"
            "         dice3d0=%s\n"
            "         dice3d1=%s\n"
            "         dot3d0=%s\n"
            "         dot3d1=%s\n"
            "         cube3d=%s\n"
            "         cubetext3d=%s\n"
            "         base3d=%s\n"
            "         points3d0=%s\n"
            "         points3d1=%s\n"
            "         border3d=%s\n"
            "         hinge3d=%s\n"
            "         numbers3d=%s\n"
            "         background3d=%s\n",
            prd->fHinges3d ? 'y' : 'n',
            prd->pieceType,
            prd->pieceTextureType,
            prd->roundedEdges ? 'y' : 'n',
            prd->bgInTrays ? 'y' : 'n',
            prd->roundedPoints ? 'y' : 'n',
            prd->lightType == LT_POSITIONAL ? 'p' : 'd',
            g_ascii_formatd(buf1, G_ASCII_DTOSTR_BUF_SIZE, "%.2f", prd->lightPos[0]),
            g_ascii_formatd(buf2, G_ASCII_DTOSTR_BUF_SIZE, "%.2f", prd->lightPos[1]),
            g_ascii_formatd(buf3, G_ASCII_DTOSTR_BUF_SIZE, "%.2f", prd->lightPos[2]),
            prd->lightLevels[0], prd->lightLevels[1], prd->lightLevels[2],
            WriteMaterial(&prd->ChequerMat[0]),
            WriteMaterial(&prd->ChequerMat[1]),
            WriteMaterialDice(prd, 0),
            WriteMaterialDice(prd, 1),
            WriteMaterial(&prd->DiceDotMat[0]),
            WriteMaterial(&prd->DiceDotMat[1]),
            WriteMaterial(&prd->CubeMat),
            WriteMaterial(&prd->CubeNumberMat),
            WriteMaterial(&prd->BaseMat),
            WriteMaterial(&prd->PointMat[0]),
            WriteMaterial(&prd->PointMat[1]),
            WriteMaterial(&prd->BoxMat),
            WriteMaterial(&prd->HingeMat), WriteMaterial(&prd->PointNumberMat), WriteMaterial(&prd->BackGroundMat));
#endif

    pbde->szBoardDesign = strdup(szTemp);
}

#if defined(USE_BOARD3D)

static void
Set2dColour(float newcol[4], Material * pMat)
{
    newcol[0] = (pMat->ambientColour[0] + pMat->diffuseColour[0]) / 2;
    newcol[1] = (pMat->ambientColour[1] + pMat->diffuseColour[1]) / 2;
    newcol[2] = (pMat->ambientColour[2] + pMat->diffuseColour[2]) / 2;
    newcol[3] = (pMat->ambientColour[3] + pMat->diffuseColour[3]) / 2;
}

static void
Set2dColourChar(unsigned char newcol[4], Material * pMat)
{
    newcol[0] = (unsigned char) (((pMat->ambientColour[0] + pMat->diffuseColour[0]) / 2) * 255);
    newcol[1] = (unsigned char) (((pMat->ambientColour[1] + pMat->diffuseColour[1]) / 2) * 255);
    newcol[2] = (unsigned char) (((pMat->ambientColour[2] + pMat->diffuseColour[2]) / 2) * 255);
    newcol[3] = (unsigned char) (((pMat->ambientColour[3] + pMat->diffuseColour[3]) / 2) * 255);
}

static void
Set3dColour(Material * pMat, float const col[4])
{
    pMat->ambientColour[0] = pMat->diffuseColour[0] = col[0];
    pMat->ambientColour[1] = pMat->diffuseColour[1] = col[1];
    pMat->ambientColour[2] = pMat->diffuseColour[2] = col[2];
    pMat->ambientColour[3] = pMat->diffuseColour[3] = 1;
}

static void
Set3dColourChar(Material * pMat, unsigned char col[4])
{
    pMat->ambientColour[0] = pMat->diffuseColour[0] = ((float) col[0]) / 255;
    pMat->ambientColour[1] = pMat->diffuseColour[1] = ((float) col[1]) / 255;
    pMat->ambientColour[2] = pMat->diffuseColour[2] = ((float) col[2]) / 255;
    pMat->ambientColour[3] = pMat->diffuseColour[3] = 1;
}

static void
CopyNewSettingsToOtherDimension(renderdata * prd)
{
    if (display_is_3d(prd)) {   /* Create rough 2D settings based on new 3D settings */
        prd->wt = WOOD_PAINT;
        Set2dColour(prd->aarColour[0], &prd->ChequerMat[0]);
        Set2dColour(prd->aarColour[1], &prd->ChequerMat[1]);
        Set2dColour(prd->aarDiceColour[0], &prd->DiceMat[0]);
        Set2dColour(prd->aarDiceColour[1], &prd->DiceMat[1]);
        Set2dColour(prd->aarDiceDotColour[0], &prd->DiceDotMat[0]);
        Set2dColour(prd->aarDiceDotColour[1], &prd->DiceDotMat[1]);
        prd->afDieColour[0] = prd->afDieColour3d[0];
        prd->afDieColour[1] = prd->afDieColour3d[1];
        prd->fHinges = prd->fHinges3d;
        Set2dColour(prd->arCubeColour, &prd->CubeMat);
        Set2dColourChar(prd->aanBoardColour[0], &prd->BaseMat);
        Set2dColourChar(prd->aanBoardColour[1], &prd->BoxMat);
        Set2dColourChar(prd->aanBoardColour[2], &prd->PointMat[0]);
        Set2dColourChar(prd->aanBoardColour[3], &prd->PointMat[1]);
    } else {                    /* Create rough 3D settings based on new 2D settings */
        Set3dColour(&prd->ChequerMat[0], prd->aarColour[0]);
        Set3dColour(&prd->ChequerMat[1], prd->aarColour[1]);
        Set3dColour(&prd->DiceMat[0], prd->aarDiceColour[0]);
        Set3dColour(&prd->DiceMat[1], prd->aarDiceColour[1]);
        Set3dColour(&prd->DiceDotMat[0], prd->aarDiceDotColour[0]);
        Set3dColour(&prd->DiceDotMat[1], prd->aarDiceDotColour[1]);
        prd->afDieColour3d[0] = prd->afDieColour[0];
        prd->afDieColour3d[1] = prd->afDieColour[1];
        prd->fHinges3d = prd->fHinges;
        Set3dColour(&prd->CubeMat, prd->arCubeColour);
        Set3dColourChar(&prd->BaseMat, prd->aanBoardColour[0]);
        Set3dColourChar(&prd->BoxMat, prd->aanBoardColour[1]);
        Set3dColourChar(&prd->PointMat[0], prd->aanBoardColour[2]);
        Set3dColourChar(&prd->PointMat[1], prd->aanBoardColour[3]);
    }
}
#endif

static void
DesignAdd(GtkWidget * pw, gpointer data)
{
    boarddesign *pbde;
    GList *plBDs = data;
    renderdata rdNew;

    pbde = (boarddesign *) g_malloc(sizeof(boarddesign));

    /* name and author of board */

    pbde->szTitle = pbde->szAuthor = pbde->szBoardDesign = NULL;

    GTKSetCurrentParent(pw);
    DesignAddTitle(pbde);
    if (!pbde->szTitle || !pbde->szAuthor) {
        g_free(pbde);
        return;
    }

    /* get board design */
    GetPrefs(&rdPrefs);
    rdNew = rdPrefs;
#if defined(USE_BOARD3D)
    CopyNewSettingsToOtherDimension(&rdNew);
#endif

    WriteDesignString(pbde, &rdNew);

    pbde->fDeletable = TRUE;

    plBDs = g_list_append(plBDs, (gpointer) pbde);
    AddDesignRow(pbde, pwDesignList);

    DesignSave(pw, plBDs);

    pbdeSelected = pbde;

    SetTitle();
}

static void
ExportDesign(GtkWidget * UNUSED(pw), gpointer UNUSED(data))
{
    GList *designs;
    gchar *pch;
    gchar *szFile;
    FILE *pf;
    boarddesign *pbde;
    renderdata rdNew;

    if ((pch = szFile = GTKFileSelect(_("Export Design"), NULL, NULL, NULL, GTK_FILE_CHOOSER_ACTION_SAVE)) == 0)
        return;

#if !defined(WIN32)
    szFile = NextToken(&szFile);
#endif

    /* 
     * Copy current design 
     */

    pbde = (boarddesign *) g_malloc(sizeof(boarddesign));

    if (pbdeSelected) {         /* Exporting current design so just get settings */
        pbde->szTitle = g_strdup(pbdeSelected->szTitle);
        pbde->szAuthor = g_strdup(pbdeSelected->szAuthor);
        ParsePreferences(pbdeSelected, &rdNew);
    } else {                    /* new design, so get settings and copy to other dimension */
        pbde->szTitle = g_strdup(_("User defined"));
        pbde->szAuthor = g_strdup(_("User"));
        GetPrefs(&rdPrefs);
        rdNew = rdPrefs;
#if defined(USE_BOARD3D)
        CopyNewSettingsToOtherDimension(&rdNew);
#endif
    }

    WriteDesignString(pbde, &rdNew);

    pbde->fDeletable = TRUE;

    /* if file exists, read in any designs */
    if ((designs = ParseBoardDesigns(szFile, TRUE)) == 0) {     /* None found so create empty list */
        designs = g_list_alloc();
    }
    designs = g_list_append(designs, (gpointer) pbde);

    /* write designs to file */

    if ((pf = g_fopen(szFile, "w+")) == 0) {
        outputerr(szFile);
        free_board_design(pbde, NULL);
        g_free(pch);
        return;
    }

    WriteDesignHeader(szFile, pf);
    g_list_foreach(designs, WriteDesign, pf);
    WriteDesignFooter(pf);
    fclose(pf);

    free_board_design(pbde, NULL);
    g_free(pch);
}

static void
ImportDesign(GtkWidget * pw, gpointer data)
{
    gchar *pch;
    gchar *szFile;
    GList *new_designs;
    gint old_length;
    gint num_added;
    GList *plBDs = (GList *) data;

    if ((pch = szFile = GTKFileSelect(_("Import Design"), NULL, NULL, NULL, GTK_FILE_CHOOSER_ACTION_OPEN)) == 0)
        return;
#if !defined(WIN32)
    szFile = NextToken(&szFile);
#endif

    if ((new_designs = ParseBoardDesigns(szFile, TRUE)) == 0) {
        /* no designs found */
        outputl(_("File not found or no designs in file."));
        outputx();
        g_free(pch);
        return;
    }

    g_free(pch);

    /* add designs to current list */

    /* FIXME: show dialog instead */
    outputl(_("Adding new designs:"));

    old_length = g_list_length(plBDs);

    g_list_foreach(new_designs, AddDesignRowIfNew, pwDesignList);

    num_added = g_list_length(plBDs) - old_length;

    outputf(ngettext("%d design added.\n", "%d designs added.\n", num_added), num_added);
    outputx();

    if (num_added > 0) {
        DesignSave(pw, plBDs);
        pbdeSelected = g_list_nth_data(plBDs, old_length);
        UseDesign();
    }
}

static void
RemoveDesign(GtkWidget * pw, gpointer data)
{
    GList *plBDs = (GList *) data;
    char prompt[200];
    sprintf(prompt, _("Permanently remove design %s?"), pbdeSelected->szTitle);
    if (!GetInputYN(prompt))
        return;

    gtk_widget_set_sensitive(GTK_WIDGET(pwDesignRemove), FALSE);

    plBDs = g_list_remove(plBDs, pbdeSelected);

    RemoveListDesign(pbdeSelected);

    DesignSave(pw, plBDs);

    SetTitle();
}

static void
UpdateDesign(GtkWidget * pw, gpointer data)
{
    renderdata newPrefs;
    char prompt[200];
#if defined(USE_BOARD3D)
    if (display_is_3d(&rdPrefs))
        sprintf(prompt, _("Permanently overwrite 3D settings for design %s?"), pbdeModified->szTitle);
    else
        sprintf(prompt, _("Permanently overwrite 2D settings for design %s?"), pbdeModified->szTitle);
#else
    sprintf(prompt, _("Permanently overwrite settings for design %s?"), pbdeModified->szTitle);
#endif
    if (!GetInputYN(prompt))
        return;

    gtk_widget_set_sensitive(GTK_WIDGET(pwDesignUpdate), FALSE);

#if defined(USE_BOARD3D)
    if (display_is_3d(&rdPrefs)) {
        /* Get current (2d) settings for design */
        ParsePreferences(pbdeModified, &newPrefs);

        /* Overwrite 3D settings with current values */
        Set3dSettings(&newPrefs, &rdPrefs);
    } else
#endif
    {
#if defined(USE_BOARD3D)
        /* Get current (3d) design settings */
        renderdata designPrefs;
        ParsePreferences(pbdeModified, &designPrefs);
#endif

        /* Copy current (2d) settings */
        GetPrefs(&rdPrefs);
        newPrefs = rdPrefs;

#if defined(USE_BOARD3D)
        /* Overwrite 3D design settings */
        Set3dSettings(&newPrefs, &designPrefs);
#endif
    }
    /* Save updated design */
    WriteDesignString(pbdeModified, &newPrefs);
    DesignSave(pw, data);

    rdPrefs = newPrefs;
    SetTitle();
}

static void
AddDesignRow(gpointer data, gpointer UNUSED(user_data))
{
    boarddesign *pbde = data;
    GtkTreeIter iter;

    if (pbde == NULL)
        return;

    gtk_list_store_append(designListStore, &iter);
    gtk_list_store_set(designListStore, &iter, NAME_COL, pbde->szTitle, DATA_COL, pbde, -1);
}

static void
AddDesignRowIfNew(gpointer data, gpointer user_data)
{
    renderdata rdNew;
    boarddesign *pbde = data;

    if (pbde == NULL)
        return;

    ParsePreferences(pbde, &rdNew);
    if (FindDesign(plBoardDesigns, &rdNew)) {
        outputf(_("Design %s already present\n"), pbde->szTitle);
        return;
    }

    AddDesignRow(data, user_data);
    plBoardDesigns = g_list_append(plBoardDesigns, (gpointer) pbde);

    outputf(_("Design %s added\n"), pbde->szTitle);
}

static void
DesignSelectNew(GtkTreeView * treeview, gpointer UNUSED(userdata))
{
    GtkTreeIter selected_iter;
    GtkTreeModel *model;
    boarddesign *pbde;

    gtk_tree_selection_get_selected(gtk_tree_view_get_selection(treeview), &model, &selected_iter);
    gtk_tree_model_get(model, &selected_iter, DATA_COL, &pbde, -1);

    if (gtk_widget_is_sensitive(pwDesignAdd)) {
        GTKSetCurrentParent(GTK_WIDGET(pwDesignList));
        if (!fUpdate || !GetInputYN(_("Select new design and lose current changes?"))) {
            pbdeModified = pbde;
            gtk_widget_set_sensitive(GTK_WIDGET(pwDesignUpdate), pbdeModified->fDeletable);
            return;
        }
    }

    pbdeSelected = pbde;
    UseDesign();
}

static GtkWidget *
DesignPage(GList * plBoardDesigns, BoardData * UNUSED(bd))
{
    GtkWidget *pwhbox;
    GtkWidget *pwScrolled;
    GtkWidget *pwPage;
    GtkCellRenderer *renderer;

#if GTK_CHECK_VERSION(3,0,0)
    pwPage = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
#else
    pwPage = gtk_vbox_new(FALSE, 4);
#endif

    /* List with board designs */

    renderer = gtk_cell_renderer_text_new();
    g_object_set(renderer, "ypad", 0, NULL);

    designListStore = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
    g_list_foreach(plBoardDesigns, AddDesignRow, pwDesignList);

    pwDesignList = gtk_tree_view_new_with_model(GTK_TREE_MODEL(designListStore));
    g_object_unref(G_OBJECT(designListStore));  /* The view now holds a reference.  We can get rid of our own reference */
    gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(pwDesignList)), GTK_SELECTION_BROWSE);
    gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(pwDesignList), -1, _("Design"), renderer, "text",
                                                NAME_COL, NULL);
    gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(pwDesignList), FALSE);
    g_signal_connect(pwDesignList, "cursor-changed", G_CALLBACK(DesignSelectNew), NULL);

    pwScrolled = gtk_scrolled_window_new(NULL, NULL);
#if GTK_CHECK_VERSION(3,0,0)
    g_object_set(G_OBJECT(pwScrolled), "expand", TRUE, NULL);
#endif
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(pwScrolled), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    gtk_container_add(GTK_CONTAINER(pwScrolled), pwDesignList);
    gtk_container_add(GTK_CONTAINER(pwPage), pwScrolled);

    /* button: use design */

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pwPage), pwhbox, FALSE, FALSE, 4);

    /* 
     * buttons 
     */

    /* add current design */

    pwDesignAdd = gtk_button_new_with_label(_("Add current design"));
    gtk_box_pack_start(GTK_BOX(pwhbox), pwDesignAdd, FALSE, FALSE, 4);

    g_signal_connect(G_OBJECT(pwDesignAdd), "clicked", G_CALLBACK(DesignAdd), plBoardDesigns);

    /* remove design */

    pwDesignRemove = gtk_button_new_with_label(_("Remove design"));
    gtk_box_pack_start(GTK_BOX(pwhbox), pwDesignRemove, FALSE, FALSE, 4);

    g_signal_connect(G_OBJECT(pwDesignRemove), "clicked", G_CALLBACK(RemoveDesign), plBoardDesigns);

    gtk_widget_set_sensitive(GTK_WIDGET(pwDesignRemove), FALSE);

    /* update design */

    pwDesignUpdate = gtk_button_new_with_label(_("Update design"));
    gtk_box_pack_start(GTK_BOX(pwhbox), pwDesignUpdate, FALSE, FALSE, 4);

    g_signal_connect(G_OBJECT(pwDesignUpdate), "clicked", G_CALLBACK(UpdateDesign), plBoardDesigns);

    gtk_widget_set_sensitive(GTK_WIDGET(pwDesignUpdate), FALSE);

    /* export design */

#if GTK_CHECK_VERSION(3,0,0)
    pwhbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwhbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_box_pack_start(GTK_BOX(pwPage), pwhbox, FALSE, FALSE, 4);

    pwDesignExport = gtk_button_new_with_label(_("Export design"));
    gtk_box_pack_start(GTK_BOX(pwhbox), pwDesignExport, FALSE, FALSE, 4);

    g_signal_connect(G_OBJECT(pwDesignExport), "clicked", G_CALLBACK(ExportDesign), NULL);

    /* import design */

    pwDesignImport = gtk_button_new_with_label(_("Import design"));
    gtk_box_pack_start(GTK_BOX(pwhbox), pwDesignImport, FALSE, FALSE, 4);

    g_signal_connect(G_OBJECT(pwDesignImport), "clicked", G_CALLBACK(ImportDesign), plBoardDesigns);

    gtk_widget_show_all(pwPage);

    return pwPage;

}

static void
BoardPrefsDestroy(GtkWidget * UNUSED(pw), GList * plBoardDesigns)
{
    fUpdate = FALSE;
    free_board_designs(plBoardDesigns);
}

static void
GetPrefs(renderdata * prd)
{

    float ar[4];

#if defined(USE_BOARD3D)
    prd->fDisplayType = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwBoardType)) ? DT_2D : DT_3D;
    if (display_is_3d(&rdPrefs)) {
        int newCurveAccuracy;
        /* dice colour same as chequer colour */
        for (int i = 0; i < 2; ++i) {
            prd->afDieColour3d[i] = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(apwDieColour[i]));
        }

        prd->showShadows = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwShowShadows));
        prd->shadowDarkness = (int) gtk_adjustment_get_value(padjDarkness);
        /* Darkness as percentage of ambient light */
        prd->dimness = (((float) prd->lightLevels[1] / 100.0f) * (float) (100 - prd->shadowDarkness)) / 100.0f;

        prd->animateRoll = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwAnimateRoll));
        prd->animateFlag = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwAnimateFlag));

        newCurveAccuracy = (int) gtk_adjustment_get_value(padjAccuracy);
        newCurveAccuracy -= (newCurveAccuracy % 4);
        prd->curveAccuracy = newCurveAccuracy;

        prd->lightType =
            gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwLightSource)) ? LT_POSITIONAL : LT_DIRECTIONAL;
        prd->lightPos[0] = (float) gtk_adjustment_get_value(padjLightPosX);
        prd->lightPos[1] = (float) gtk_adjustment_get_value(padjLightPosY);
        prd->lightPos[2] = (float) gtk_adjustment_get_value(padjLightPosZ);
        prd->lightLevels[0] = (int) gtk_adjustment_get_value(padjLightLevelAmbient);
        prd->lightLevels[1] = (int) gtk_adjustment_get_value(padjLightLevelDiffuse);
        prd->lightLevels[2] = (int) gtk_adjustment_get_value(padjLightLevelSpecular);

        prd->boardAngle = (float) gtk_adjustment_get_value(padjBoardAngle);
        prd->skewFactor = (float) gtk_adjustment_get_value(padjSkewFactor);
        prd->pieceType = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwRoundedPiece)) ? PT_ROUNDED : PT_FLAT;
        prd->pieceTextureType = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwTextureAllPiece)) ? PTT_ALL : PTT_TOP;
        prd->diceSize = (float) gtk_adjustment_get_value(padjDiceSize);
        prd->roundedEdges = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwRoundedEdges));
        prd->bgInTrays = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwBgTrays));
        prd->roundedPoints = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwRoundPoints));
        prd->planView = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwPlanView));

        prd->fHinges3d = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwHinges));
    } else
#endif
    {
        /* dice colour same as chequer colour */
        for (int i = 0; i < 2; ++i) {
            prd->afDieColour[i] = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(apwDieColour[i]));
        }

        for (int i = 0; i < 2; i++) {
            prd->arRefraction[i] = (float) gtk_adjustment_get_value(apadj[i]);
            prd->arCoefficient[i] = (float) gtk_adjustment_get_value(apadjCoefficient[i]);
            prd->arExponent[i] = (float) gtk_adjustment_get_value(apadjExponent[i]);

            prd->arDiceCoefficient[i] = (float) gtk_adjustment_get_value(apadjDiceCoefficient[i]);
            prd->arDiceExponent[i] = (float) gtk_adjustment_get_value(apadjDiceExponent[i]);
        }

        gtk_color_button_get_array(GTK_COLOR_BUTTON(apwColour[0]), ar);
        for (int i = 0; i < 4; i++)
            prd->aarColour[0][i] = ar[i];

        gtk_color_button_get_array(GTK_COLOR_BUTTON(apwColour[1]), ar);
        for (int i = 0; i < 4; i++)
            prd->aarColour[1][i] = ar[i];

        gtk_color_button_get_array(GTK_COLOR_BUTTON(apwDiceColour[0]), ar);
        for (int i = 0; i < 3; i++)
            prd->aarDiceColour[0][i] = ar[i];

        gtk_color_button_get_array(GTK_COLOR_BUTTON(apwDiceColour[1]), ar);
        for (int i = 0; i < 3; i++)
            prd->aarDiceColour[1][i] = ar[i];

        for (int j = 0; j < 2; ++j) {

            gtk_color_button_get_array(GTK_COLOR_BUTTON(apwDiceDotColour[j]), ar);
            for (int i = 0; i < 3; i++)
                prd->aarDiceDotColour[j][i] = ar[i];
        }

        /* cube colour */
        gtk_color_button_get_array(GTK_COLOR_BUTTON(pwCubeColour), ar);

        for (int i = 0; i < 3; i++)
            prd->arCubeColour[i] = ar[i];

        /* board colour */
        for (int j = 0; j < 4; j++) {
            gtk_color_button_get_array(GTK_COLOR_BUTTON(apwBoard[j]), ar);

            for (int i = 0; i < 3; i++)
                prd->aanBoardColour[j][i] = (ar[i] == 1) ? 0xff : (unsigned char) (ar[i] * 0x100);
        }

        prd->aSpeckle[0] = (int) (gtk_adjustment_get_value(apadjBoard[0]) * 0x80);
        /*    prd->aSpeckle[ 1 ] = (int)(gtk_adjustment_get_value( apadjBoard[ 1 ] ) * 0x80); */
        prd->aSpeckle[2] = (int) (gtk_adjustment_get_value(apadjBoard[2]) * 0x80);
        prd->aSpeckle[3] = (int) (gtk_adjustment_get_value(apadjBoard[3]) * 0x80);

        if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwWood)))
            prd->wt = gtk_combo_box_get_active(GTK_COMBO_BOX(pwWoodType));
        else
            prd->wt = WOOD_PAINT;

        prd->rRound = 1.0f - (float) gtk_adjustment_get_value(padjRound);

        prd->fHinges = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwHinges));

        WorkOut2dLight(prd);
    }

    prd->fDynamicLabels = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwDynamicLabels));
    prd->fLabels = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwLabels));
    prd->showMoveIndicator = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pwMoveIndicator));

    prd->fClockwise = fClockwise;       /* yuck */
}

static void
append_preview_page(GtkWidget * pwNotebook, GtkWidget * pwPage, char *szLabel, pixmapindex UNUSED(pi))
{

    GtkWidget *pw;

#if GTK_CHECK_VERSION(3,0,0)
    pw = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
#else
    pw = gtk_hbox_new(FALSE, 4);
#endif

    gtk_box_pack_start(GTK_BOX(pw), pwPage, TRUE, TRUE, 0);
    gtk_widget_show_all(pw);
    gtk_notebook_append_page(GTK_NOTEBOOK(pwNotebook), pw, gtk_label_new(szLabel));
}

void
AddPages(BoardData * bd, GtkWidget * pwNotebook, GList * plBoardDesigns)
{
#if defined(USE_BOARD3D)
    gtk_notebook_append_page(GTK_NOTEBOOK(pwNotebook), LightingPage(bd), gtk_label_new(_("Lighting")));

    gtk_widget_set_sensitive(frame3dOptions, display_is_3d(&rdPrefs));

#endif

    gtk_notebook_append_page(GTK_NOTEBOOK(pwNotebook), DesignPage(plBoardDesigns, bd), gtk_label_new(_("Designs")));

#if defined(USE_BOARD3D)
    if (display_is_3d(&rdPrefs)) {
        ResetPreviews();
        append_preview_page(pwNotebook, ChequerPrefs3d(bd), _("Chequers"), PI_CHEQUERS0);
        append_preview_page(pwNotebook, BoardPage3d(bd), _("Board"), PI_BOARD);
        append_preview_page(pwNotebook, BorderPage3d(bd), _("Border"), PI_BORDER);
        append_preview_page(pwNotebook, DicePrefs3d(bd, 0), _("Dice (0)"), PI_DICE0);
        append_preview_page(pwNotebook, DicePrefs3d(bd, 1), _("Dice (1)"), PI_DICE1);
        append_preview_page(pwNotebook, CubePrefs3d(bd), _("Cube"), PI_CUBE);
    } else
#endif
    {
        append_preview_page(pwNotebook, ChequerPrefs(bd, 0), _("Chequers (0)"), PI_CHEQUERS0);
        append_preview_page(pwNotebook, ChequerPrefs(bd, 1), _("Chequers (1)"), PI_CHEQUERS1);
        append_preview_page(pwNotebook, BoardPage(bd), _("Board"), PI_BOARD);
        append_preview_page(pwNotebook, BorderPage(bd), _("Border"), PI_BORDER);
        append_preview_page(pwNotebook, DicePrefs(bd, 0), _("Dice (0)"), PI_DICE0);
        append_preview_page(pwNotebook, DicePrefs(bd, 1), _("Dice (1)"), PI_DICE1);
        append_preview_page(pwNotebook, CubePrefs(bd), _("Cube"), PI_CUBE);
    }
}

static void
ChangePage(GtkNotebook * UNUSED(notebook), GtkNotebook * UNUSED(page), guint page_num, gpointer UNUSED(user_data))
{
    BoardData *bd = BOARD(pwPrevBoard)->board_data;
    unsigned int dicePage = NUM_NONPREVIEW_PAGES + PI_DICE0;

    if (!fUpdate)
        return;

#if defined(USE_BOARD3D)
    if (display_is_3d(&rdPrefs))
        dicePage -= 1;
#endif
    /* Make sure correct dice preview visible */

    if ((page_num == dicePage && bd->turn == 1) || (page_num == dicePage + 1 && bd->turn == -1)) {
        bd->turn = -bd->turn;
#if defined(USE_BOARD3D)
        if (display_is_3d(&rdPrefs))
            setDicePos(bd, bd->bd3d);
        else
#endif
            RollDice2d(bd);

        option_changed(0, 0);
    }
#if defined(USE_BOARD3D)
    if (display_is_3d(&rdPrefs) && redrawChange) {
        redraw_changed(NULL, NULL);
        redrawChange = FALSE;
    }
#endif
}

static void
pref_dialog_map(GtkWidget * UNUSED(window), BoardData * bd)
{
#if defined(USE_BOARD3D)
    DisplayCorrectBoardType(bd, bd->bd3d, bd->rd);
    redrawChange = FALSE;
#else
    (void) bd;                  /* suppress unused parameter compiler warning */
#endif
    SetTitle();                 /* Make sure title selected properly */
    gtk_widget_grab_focus(pwDesignList);        /* Select this to stop "change" message after something is changed */
    fUpdate = TRUE;
}

extern void
BoardPreferences(GtkWidget * pwBoard)
{
    GtkWidget *pwDialog, *pwHbox;
    BoardData *bd;

    /* Set up board with current preferences, hide unwanted elements */
    CopyAppearance(&rdPrefs);
    rdPrefs.fDiceArea = FALSE;
    rdPrefs.fShowGameInfo = FALSE;
    /* Create preview board */
    pwPrevBoard = board_new(&rdPrefs, TRUE);

    bd = BOARD(pwPrevBoard)->board_data;
#if defined(USE_BOARD3D)
    InitColourSelectionDialog();
#endif
    InitBoardPreview(bd);
    RollDice2d(bd);

    pwDialog =
        GTKCreateDialog(_("GNU Backgammon - Appearance"), DT_QUESTION, NULL,
                        DIALOG_FLAG_MODAL | DIALOG_FLAG_MINMAXBUTTONS, G_CALLBACK(BoardPrefsOK), pwBoard);

#if defined(USE_BOARD3D)
    if (widget3dValid) {
        SetPreviewLightLevel(bd->rd->lightLevels);
        setDicePos(bd, bd->bd3d);
    }
#endif

#if GTK_CHECK_VERSION(3,0,0)
    pwHbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
#else
    pwHbox = gtk_hbox_new(FALSE, 0);
#endif
    gtk_container_add(GTK_CONTAINER(DialogArea(pwDialog, DA_MAIN)), pwHbox);

    pwNotebook = gtk_notebook_new();

    gtk_notebook_set_scrollable(GTK_NOTEBOOK(pwNotebook), TRUE);
    gtk_notebook_popup_enable(GTK_NOTEBOOK(pwNotebook));

    gtk_container_set_border_width(GTK_CONTAINER(pwNotebook), 4);
#if !defined(USE_BOARD3D)
    /* Make sure preview is big enough in 2d mode */
    gtk_widget_set_size_request(GTK_WIDGET(pwNotebook), -1, 360);
#endif

    gtk_box_pack_start(GTK_BOX(pwHbox), pwNotebook, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(pwHbox), pwPrevBoard, TRUE, TRUE, 0);

    gtk_notebook_append_page(GTK_NOTEBOOK(pwNotebook), GeneralPage(bd, pwBoard), gtk_label_new(_("General")));

    plBoardDesigns = read_board_designs();
    AddPages(bd, pwNotebook, plBoardDesigns);

    g_signal_connect(G_OBJECT(pwNotebook), "switch-page", G_CALLBACK(ChangePage), 0);

    g_signal_connect(G_OBJECT(pwDialog), "destroy", G_CALLBACK(BoardPrefsDestroy), plBoardDesigns);

    gtk_notebook_set_current_page(GTK_NOTEBOOK(pwNotebook), NUM_NONPREVIEW_PAGES);

    g_signal_connect(pwDialog, "map", G_CALLBACK(pref_dialog_map), bd);

    GTKRunDialog(pwDialog);
}

/* External board preference helper functions */

extern void
SetBoardPreferences(GtkWidget * pwBoard, char *sz)
{
    char *apch[2];

    while (ParseKeyValue(&sz, apch))
        RenderPreferencesParam(GetMainAppearance(), apch[0], apch[1]);

    if (fX) {
        BoardData *bd = BOARD(pwBoard)->board_data;

        if (gtk_widget_get_realized(pwBoard)) {
            board_free_pixmaps(bd);
            board_create_pixmaps(pwBoard, bd);
#if defined(USE_BOARD3D)
            DisplayCorrectBoardType(bd, bd->bd3d, bd->rd);
            if (display_is_3d(bd->rd))
                UpdateShadows(bd->bd3d);
            else
                StopIdle3d(bd, bd->bd3d);

            if (display_is_2d(bd->rd))
#endif
            {
                gtk_widget_queue_draw(bd->drawing_area);
                gtk_widget_queue_draw(bd->dice_area);
            }
            gtk_widget_queue_draw(bd->table);
        }
    }
}

#if defined(USE_BOARD3D)

static int
IsWhiteColour3d(Material * pMat)
{
    return (pMat->ambientColour[0] == 1) && (pMat->ambientColour[1] == 1) && (pMat->ambientColour[2] == 1) &&
        (pMat->diffuseColour[0] == 1) && (pMat->diffuseColour[1] == 1) && (pMat->diffuseColour[2] == 1) &&
        (pMat->specularColour[0] == 1) && (pMat->specularColour[1] == 1) && (pMat->specularColour[2] == 1);
}

static int
IsBlackColour3d(Material * pMat)
{
    return (pMat->ambientColour[0] == 0) && (pMat->ambientColour[1] == 0) && (pMat->ambientColour[2] == 0) &&
        (pMat->diffuseColour[0] == 0) && (pMat->diffuseColour[1] == 0) && (pMat->diffuseColour[2] == 0) &&
        (pMat->specularColour[0] == 0) && (pMat->specularColour[1] == 0) && (pMat->specularColour[2] == 0);
}

extern void
Default3dSettings(BoardData * bd)
{                               /* If no 3D settings loaded, set 3D appearance to first design */
    /* Check if colours are set to default values */
    if (IsWhiteColour3d(&bd->rd->ChequerMat[0]) && IsBlackColour3d(&bd->rd->ChequerMat[1]) &&
        IsWhiteColour3d(&bd->rd->DiceMat[0]) && IsBlackColour3d(&bd->rd->DiceMat[1]) &&
        IsBlackColour3d(&bd->rd->DiceDotMat[0]) && IsWhiteColour3d(&bd->rd->DiceDotMat[1]) &&
        IsWhiteColour3d(&bd->rd->CubeMat) && IsBlackColour3d(&bd->rd->CubeNumberMat) &&
        IsWhiteColour3d(&bd->rd->PointMat[0]) && IsBlackColour3d(&bd->rd->PointMat[1]) &&
        IsBlackColour3d(&bd->rd->BoxMat) &&
        IsWhiteColour3d(&bd->rd->PointNumberMat) && IsWhiteColour3d(&bd->rd->BackGroundMat)) {
        GList *plDesigns;
        /* Use new appearance settings to preserve current 2d settings */
        renderdata rdNew;
        CopyAppearance(&rdNew);

        plDesigns = read_board_designs();
        if (plDesigns && g_list_length(plDesigns) > 0) {
            boarddesign *pbde = g_list_nth_data(plDesigns, 0);
            if (pbde->szBoardDesign) {
                char *apch[2];
                gchar *sz, *pch;

                pch = sz = g_strdup(pbde->szBoardDesign);
                while (ParseKeyValue(&sz, apch))
                    RenderPreferencesParam(&rdNew, apch[0], apch[1]);

                g_free(pch);

                /* Copy 3D settings from rdNew to main appearance settings */
                bd->rd->pieceType = rdNew.pieceType;
                bd->rd->pieceTextureType = rdNew.pieceTextureType;
                bd->rd->fHinges3d = rdNew.fHinges3d;
                bd->rd->showMoveIndicator = rdNew.showMoveIndicator;
                bd->rd->showShadows = rdNew.showShadows;
                bd->rd->roundedEdges = rdNew.roundedEdges;
                bd->rd->bgInTrays = rdNew.bgInTrays;
                bd->rd->roundedPoints = rdNew.roundedPoints;
                bd->rd->shadowDarkness = rdNew.shadowDarkness;
                bd->rd->curveAccuracy = rdNew.curveAccuracy;
                bd->rd->skewFactor = rdNew.skewFactor;
                bd->rd->boardAngle = rdNew.boardAngle;
                bd->rd->diceSize = rdNew.diceSize;
                bd->rd->planView = rdNew.planView;

                memcpy(bd->rd->ChequerMat, rdNew.ChequerMat, sizeof(Material[2]));
                memcpy(bd->rd->DiceMat, rdNew.DiceMat, sizeof(Material[2]));
                bd->rd->DiceMat[0].textureInfo = bd->rd->DiceMat[1].textureInfo = 0;
                bd->rd->DiceMat[0].pTexture = bd->rd->DiceMat[1].pTexture = 0;

                memcpy(bd->rd->DiceDotMat, rdNew.DiceDotMat, sizeof(Material[2]));

                memcpy(&bd->rd->CubeMat, &rdNew.CubeMat, sizeof(Material));
                memcpy(&bd->rd->CubeNumberMat, &rdNew.CubeNumberMat, sizeof(Material));

                memcpy(&bd->rd->BaseMat, &rdNew.BaseMat, sizeof(Material));
                memcpy(bd->rd->PointMat, rdNew.PointMat, sizeof(Material[2]));

                memcpy(&bd->rd->BoxMat, &rdNew.BoxMat, sizeof(Material));
                memcpy(&bd->rd->HingeMat, &rdNew.HingeMat, sizeof(Material));
                memcpy(&bd->rd->PointNumberMat, &rdNew.PointNumberMat, sizeof(Material));
                memcpy(&bd->rd->BackGroundMat, &rdNew.BackGroundMat, sizeof(Material));

                memset(&bd->rd->lightPos, 0, sizeof(rdNew.lightPos));
                memset(&bd->rd->lightLevels, 0, sizeof(rdNew.lightLevels));
                memcpy(&bd->rd->lightPos, &rdNew.lightPos, sizeof(rdNew.lightPos));
                memcpy(&bd->rd->lightLevels, &rdNew.lightLevels, sizeof(rdNew.lightLevels));

                memcpy(&bd->rd->afDieColour3d, &rdNew.afDieColour3d, sizeof(rdNew.afDieColour3d));
            }
        }
        free_board_designs(plDesigns);
    }
}
#endif

/* Xml board parsing code */

typedef enum {
    STATE_NONE,
    STATE_BOARD_DESIGNS,
    STATE_BOARD_DESIGN,
    STATE_ABOUT,
    STATE_TITLE, STATE_AUTHOR,
    STATE_DESIGN
} parserstate;

typedef struct {
    gchar *filename;
    parserstate state;
    boarddesign *current_design;
    GList *designs;
    gboolean deletable;
} DesignParser;

static void
design_parser_start_element(GMarkupParseContext * UNUSED(context),
                            const gchar * element_name,
                            const gchar ** UNUSED(attribute_names),
                            const gchar ** UNUSED(attribute_values), gpointer user_data, GError ** UNUSED(error))
{

    DesignParser *parser = (DesignParser *) user_data;

    switch (parser->state) {
    case STATE_NONE:
        if (strcmp(element_name, "board-designs") == 0)
            parser->state = STATE_BOARD_DESIGNS;
        break;
    case STATE_BOARD_DESIGNS:
        if (strcmp(element_name, "board-design") == 0) {
            parser->state = STATE_BOARD_DESIGN;
            parser->current_design = g_new0(boarddesign, 1);
        }
        break;

    case STATE_BOARD_DESIGN:
        if (strcmp(element_name, "about") == 0)
            parser->state = STATE_ABOUT;
        if (strcmp(element_name, "design") == 0)
            parser->state = STATE_DESIGN;
        break;
    case STATE_ABOUT:
        if (strcmp(element_name, "title") == 0)
            parser->state = STATE_TITLE;
        if (strcmp(element_name, "author") == 0)
            parser->state = STATE_AUTHOR;
        break;
    case STATE_TITLE:
    case STATE_AUTHOR:
    case STATE_DESIGN:
    default:
        break;
    }
}

static void
design_parser_end_element(GMarkupParseContext * UNUSED(context),
                          const gchar * UNUSED(element_name), gpointer user_data, GError ** UNUSED(error))
{
    DesignParser *parser = (DesignParser *) user_data;

    switch (parser->state) {
    case STATE_NONE:
        g_assert_not_reached();
        break;
    case STATE_BOARD_DESIGNS:
        parser->state = STATE_NONE;
        break;
    case STATE_BOARD_DESIGN:
        parser->current_design->fDeletable = parser->deletable;
        parser->designs = g_list_prepend(parser->designs, parser->current_design);
        parser->current_design = NULL;
        parser->state = STATE_BOARD_DESIGNS;
        break;
    case STATE_ABOUT:
        parser->state = STATE_BOARD_DESIGN;
        break;
    case STATE_TITLE:
        parser->state = STATE_ABOUT;
        break;
    case STATE_AUTHOR:
        parser->state = STATE_ABOUT;
        break;
    case STATE_DESIGN:
        parser->state = STATE_BOARD_DESIGN;
        break;
    default:
        g_assert_not_reached();
    }
}

static void
design_parser_characters(GMarkupParseContext * UNUSED(context),
                         const gchar * text, gsize UNUSED(text_len), gpointer user_data, GError ** UNUSED(error))
{
    DesignParser *parser = (DesignParser *) user_data;

    switch (parser->state) {
    case STATE_TITLE:
        parser->current_design->szTitle = g_strdup(text);
        break;
    case STATE_AUTHOR:
        parser->current_design->szAuthor = g_strdup(text);
        break;
    case STATE_DESIGN:
        parser->current_design->szBoardDesign = g_strdup(text);
        break;
    default:
        break;
    }
}

static void
design_parser_error(GMarkupParseContext * UNUSED(context), GError * UNUSED(error), gpointer user_data)
{
    DesignParser *parser = (DesignParser *) user_data;
    g_warning(_("An error occurred while parsing file: %s\n"), parser->filename);
}

static GList *
ParseBoardDesigns(const char *szFile, const int fDeletable)
{
    GMarkupParser markup_parser = {
        design_parser_start_element,
        design_parser_end_element,
        design_parser_characters,
        NULL,
        design_parser_error
    };

    GList *returnlist;
    GMarkupParseContext *context;
    DesignParser *parser;
    char *contents;
    gsize size;
    GError *error = NULL;

    parser = g_new0(DesignParser, 1);

    parser->filename = g_strdup(szFile);
    parser->designs = NULL;
    parser->deletable = fDeletable;

    /* create parser context */

    if (!g_file_get_contents(szFile, &contents, &size, NULL)) {
        g_free(parser->filename);
        g_free(parser);
        return NULL;
    }

    context = g_markup_parse_context_new(&markup_parser, (GMarkupParseFlags) 0, parser, NULL);
    if (!context) {
        g_free(contents);
        g_free(parser->filename);
        g_free(parser);
        return NULL;
    }

    /* parse document */
    if (!g_markup_parse_context_parse(context, contents, size, &error)) {
        g_warning(_("Error parsing XML: %s\n"), error->message);
        g_error_free(error);
        free_board_designs(parser->designs);
        g_markup_parse_context_free(context);
        g_free(contents);
        g_free(parser->filename);
        g_free(parser);
        return NULL;
    }
    g_markup_parse_context_free(context);

    returnlist = parser->designs;

    g_free(contents);
    g_free(parser->filename);
    free_board_design(parser->current_design, NULL);
    g_free(parser);

    return g_list_reverse(returnlist);
}
