/* X-Chat
 * Copyright (C) 2004-2007 Peter Zelezny.
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>

#include "../common/zoitechat.h"

#include <gio/gio.h>
#include <glib/gstdio.h>
#include "../common/cfgfiles.h"
#include "../common/fe.h"
#include "../common/text.h"
#include "../common/userlist.h"
#include "../common/util.h"
#include "../common/zoitechatc.h"
#include "../common/outbound.h"
#include "fe-gtk.h"
#include "theme/theme-manager.h"
#include "theme/theme-preferences.h"
#include "gtkutil.h"
#include "maingui.h"
#include "pixmaps.h"
#include "menu.h"
#include "preferences-persistence.h"
#include "plugin-tray.h"
#include "notifications/notification-backend.h"

#ifdef WIN32
#include "../common/fe.h"
#endif
#include "sexy-spell-entry.h"

#define LABEL_INDENT 12

static GtkWidget *setup_window = NULL;
static int last_selected_page = 0;
static int last_selected_row = 0; /* sound row */
static gboolean color_change;
static struct zoitechatprefs setup_prefs;
static GtkWidget *cancel_button;
static GtkWidget *font_dialog = NULL;
static GtkWidget *setup_topicbar_multiline_toggle = NULL;

enum
{
        ST_END,
        ST_TOGGLE,
        ST_TOGGLR,
        ST_3OGGLE,
        ST_ENTRY,
        ST_EFONT,
        ST_EFILE,
        ST_EFOLDER,
        ST_MENU,
        ST_RADIO,
        ST_NUMBER,
        ST_HSCALE,
        ST_HEADER,
        ST_LABEL,
        ST_ALERTHEAD
};

typedef struct
{
        int type;
        char *label;
        int offset;
        char *tooltip;
        char const *const *list;
        int extra;
} setting;

typedef GtkWidget *(*setup_page_factory) (const setting *settings);

#define SETUP_MAX_PAGES 12
static setup_page_factory setup_page_factories[SETUP_MAX_PAGES];
static const setting *setup_page_settings[SETUP_MAX_PAGES];
static GtkWidget *setup_page_containers[SETUP_MAX_PAGES];
static int setup_page_count = 0;

#ifdef WIN32
static const char *const langsmenu[] =
{
        N_("Afrikaans"),
        N_("Albanian"),
        N_("Amharic"),
        N_("Asturian"),
        N_("Azerbaijani"),
        N_("Basque"),
        N_("Belarusian"),
        N_("Bulgarian"),
        N_("Catalan"),
        N_("Chinese (Simplified)"),
        N_("Chinese (Traditional)"),
        N_("Czech"),
        N_("Danish"),
        N_("Dutch"),
        N_("English (British)"),
        N_("English"),
        N_("Estonian"),
        N_("Finnish"),
        N_("French"),
        N_("Galician"),
        N_("German"),
        N_("Greek"),
        N_("Gujarati"),
        N_("Hindi"),
        N_("Hungarian"),
        N_("Indonesian"),
        N_("Italian"),
        N_("Japanese"),
        N_("Kannada"),
        N_("Kinyarwanda"),
        N_("Korean"),
        N_("Latvian"),
        N_("Lithuanian"),
        N_("Macedonian"),
        N_("Malay"),
        N_("Malayalam"),
        N_("Norwegian (Bokmal)"),
        N_("Norwegian (Nynorsk)"),
        N_("Polish"),
        N_("Portuguese"),
        N_("Portuguese (Brazilian)"),
        N_("Punjabi"),
        N_("Russian"),
        N_("Serbian"),
        N_("Slovak"),
        N_("Slovenian"),
        N_("Spanish"),
        N_("Swedish"),
        N_("Thai"),
        N_("Turkish"),
        N_("Ukrainian"),
        N_("Vietnamese"),
        N_("Walloon"),
        NULL
};
#endif

static const setting appearance_settings[] =
{
        {ST_HEADER,     N_("General"),0,0,0},
#ifdef WIN32
        {ST_MENU,   N_("Language:"), P_OFFINTNL(hex_gui_lang), 0, langsmenu, 0},
        {ST_EFONT,  N_("Main font:"), P_OFFSETNL(hex_text_font_main), 0, 0, sizeof prefs.hex_text_font_main},
#else
        {ST_EFONT,  N_("Font:"), P_OFFSETNL(hex_text_font), 0, 0, sizeof prefs.hex_text_font},
#endif

        {ST_HEADER,     N_("Text Box"),0,0,0},
        {ST_TOGGLE, N_("Colored nick names"), P_OFFINTNL(hex_text_color_nicks), N_("Give each person on IRC a different color"),0,0},
        {ST_TOGGLR, N_("Indent nick names"), P_OFFINTNL(hex_text_indent), N_("Make nick names right-justified"),0,0},
        {ST_TOGGLE, N_ ("Show marker line"), P_OFFINTNL (hex_text_show_marker), N_ ("Insert a red line after the last read text."), 0, 0},

        {ST_HEADER,     N_("Timestamps"),0,0,0},
        {ST_TOGGLE, N_("Enable timestamps"), P_OFFINTNL(hex_stamp_text),0,0,1},
        {ST_ENTRY,  N_("Timestamp format:"), P_OFFSETNL(hex_stamp_text_format),
#ifdef WIN32
                                        N_("See the strftime MSDN article for details."),0,sizeof prefs.hex_stamp_text_format},
#else
                                        N_("See the strftime manpage for details."),0,sizeof prefs.hex_stamp_text_format},
#endif

        {ST_HEADER,     N_("Title Bar"),0,0,0},
        {ST_TOGGLE, N_("Show channel modes"), P_OFFINTNL(hex_gui_win_modes),0,0,0},
        {ST_TOGGLR, N_("Show number of users"), P_OFFINTNL(hex_gui_win_ucount),0,0,0},
        {ST_TOGGLE, N_("Show nickname"), P_OFFINTNL(hex_gui_win_nick),0,0,0},

        {ST_HEADER,     N_("Topic Bar"),0,0,0},
        {ST_TOGGLE, N_("Place mode buttons beside the topic"), P_OFFINTNL(hex_gui_mode_buttons_inline), 0, 0, 0},
        {ST_TOGGLE, N_("Allow multi-line topics"), P_OFFINTNL(hex_gui_topicbar_multiline), 0, 0, 0},

        {ST_END, 0, 0, 0, 0, 0}
};

static const setting appearance_advanced_settings[] =
{
        {ST_HEADER,     N_("Advanced"),0,0,0},
        {ST_EFILE, N_ ("Background image:"), P_OFFSETNL (hex_text_background), 0, 0, sizeof prefs.hex_text_background},
        {ST_HSCALE, N_("Window opacity:"), P_OFFINTNL(hex_gui_transparency),0,0,0},
        {ST_HSCALE, N_("Mouse wheel scroll speed (Slower ← → Faster):"), P_OFFINTNL(hex_gui_mouse_scroll_speed), 0, 0, 100},

        {ST_END, 0, 0, 0, 0, 0}
};

static const char *const tabcompmenu[] = 
{
        N_("A-Z"),
        N_("Last-spoke order"),
        NULL
};

static const setting inputbox_settings[] =
{
        {ST_HEADER, N_("Input Box"),0,0,0},
        {ST_TOGGLE, N_("Render colors and attributes"), P_OFFINTNL (hex_gui_input_attr),0,0,0},
        {ST_TOGGLE, N_("Show nick box"), P_OFFINTNL(hex_gui_input_nick),0,0,1},
        {ST_TOGGLE, N_("Show user mode icon in nick box"), P_OFFINTNL(hex_gui_input_icon),0,0,0},
        {ST_TOGGLE, N_("Spell checking"), P_OFFINTNL(hex_gui_input_spell),0,0,1},
        {ST_ENTRY,      N_("Dictionaries to use:"), P_OFFSETNL(hex_text_spell_langs),0,0,sizeof prefs.hex_text_spell_langs},
#ifdef WIN32
        {ST_LABEL,      N_("Use language codes (as in \"%LOCALAPPDATA%\\enchant\\myspell\\dicts\").\nSeparate multiple entries with commas.")},
#else
        {ST_LABEL,      N_("Use language codes. Separate multiple entries with commas.")},
#endif

        {ST_HEADER, N_("Nick Completion"),0,0,0},
        {ST_ENTRY,      N_("Nick completion suffix:"), P_OFFSETNL(hex_completion_suffix),0,0,sizeof prefs.hex_completion_suffix},
        {ST_MENU,       N_("Nick completion sorted:"), P_OFFINTNL(hex_completion_sort), 0, tabcompmenu, 0},
        {ST_NUMBER,     N_("Nick completion amount:"), P_OFFINTNL(hex_completion_amount), N_("Threshold of nicks to start listing instead of completing"), (const char **)N_("nicks."), 1000},

        {ST_END, 0, 0, 0, 0, 0}
};

static const char *const lagmenutext[] = 
{
        N_("Off"),
        N_("Graphical"),
        N_("Text"),
        N_("Both"),
        NULL
};

static const char *const ulmenutext[] = 
{
        N_("A-Z, ops first"),
        N_("A-Z"),
        N_("Z-A, ops last"),
        N_("Z-A"),
        N_("Unsorted"),
        NULL
};

static const char *const cspos[] =
{
        N_("Left (upper)"),
        N_("Left (lower)"),
        N_("Right (upper)"),
        N_("Right (lower)"),
        N_("Top"),
        N_("Bottom"),
        N_("Hidden"),
        NULL
};

static const char *const ulpos[] =
{
        N_("Left (upper)"),
        N_("Left (lower)"),
        N_("Right (upper)"),
        N_("Right (lower)"),
        NULL
};

static const setting userlist_settings[] =
{
        {ST_HEADER,     N_("User List"),0,0,0},
        {ST_TOGGLE, N_("Show hostnames in user list"), P_OFFINTNL(hex_gui_ulist_show_hosts), 0, 0, 0},
        {ST_TOGGLE, N_("Show icons for user modes"), P_OFFINTNL(hex_gui_ulist_icons), N_("Use graphical icons instead of text symbols in the user list."), 0, 0},
        {ST_TOGGLE, N_("Color nicknames in userlist"), P_OFFINTNL(hex_gui_ulist_color), N_("Will color nicknames the same as in chat."), 0, 0},
        {ST_TOGGLE, N_("Show user count in channels"), P_OFFINTNL(hex_gui_ulist_count), 0, 0, 0},
        {ST_MENU,       N_("User list sorted by:"), P_OFFINTNL(hex_gui_ulist_sort), 0, ulmenutext, 0},
        {ST_MENU,       N_("Show user list at:"), P_OFFINTNL(hex_gui_ulist_pos), 0, ulpos, 1},

        {ST_HEADER,     N_("Away Tracking"),0,0,0},
        {ST_TOGGLE,     N_("Track the away status of users and mark them in a different color"), P_OFFINTNL(hex_away_track),0,0,1},
        {ST_NUMBER, N_("On channels smaller than:"), P_OFFINTNL(hex_away_size_max),0,0,10000},

        {ST_HEADER,     N_("Action Upon Double Click"),0,0,0},
        {ST_ENTRY,      N_("Execute command:"), P_OFFSETNL(hex_gui_ulist_doubleclick), 0, 0, sizeof prefs.hex_gui_ulist_doubleclick},

        {ST_HEADER,     N_("Extra Gadgets"),0,0,0},
        {ST_MENU,       N_("Lag meter:"), P_OFFINTNL(hex_gui_lagometer), 0, lagmenutext, 0},
        {ST_MENU,       N_("Throttle meter:"), P_OFFINTNL(hex_gui_throttlemeter), 0, lagmenutext, 0},

        {ST_END, 0, 0, 0, 0, 0}
};

static const char *const tabwin[] =
{
        N_("Windows"),
        N_("Tabs"),
        NULL
};

static const char *const focusnewtabsmenu[] =
{
        N_("Never"),
        N_("Always"),
        N_("Only requested tabs"),
        NULL
};

static const char *const noticeposmenu[] =
{
        N_("Automatic"),
        N_("In an extra tab"),
        N_("In the front tab"),
        NULL
};

static const char *const swtype[] =
{
        N_("Tabs"),
        "",
        N_("Tree"),
        NULL
};

static const setting tabs_settings[] =
{
        {ST_RADIO,  N_("Switcher type:"),P_OFFINTNL(hex_gui_tab_layout), 0, swtype, 0},
        {ST_TOGGLE, N_("Open an extra tab for server messages"), P_OFFINTNL(hex_gui_tab_server), 0, 0, 0},
        {ST_TOGGLE, N_("Open a new tab when you receive a private message"), P_OFFINTNL(hex_gui_autoopen_dialog), 0, 0, 0},
        {ST_TOGGLE, N_("Sort tabs in alphabetical order"), P_OFFINTNL(hex_gui_tab_sort), 0, 0, 0},
        {ST_TOGGLE, N_("Show icons in the channel tree"), P_OFFINTNL(hex_gui_tab_icons), 0, 0, 0},
        {ST_TOGGLE, N_("Show dotted lines in the channel tree"), P_OFFINTNL(hex_gui_tab_dots), 0, 0, 0},
        {ST_TOGGLE, N_("Scroll mouse-wheel to change tabs"), P_OFFINTNL (hex_gui_tab_scrollchans), 0, 0, 0},
        {ST_TOGGLE, N_("Show close button on tabs"), P_OFFINTNL(hex_gui_tab_closebuttons), 0, 0, 0},
        {ST_TOGGLE, N_("Middle click to close tab"), P_OFFINTNL(hex_gui_tab_middleclose), 0, 0, 0},
        {ST_TOGGLE, N_("Smaller text"), P_OFFINTNL(hex_gui_tab_small), 0, 0, 0},
        {ST_MENU,       N_("Focus new tabs:"), P_OFFINTNL(hex_gui_tab_newtofront), 0, focusnewtabsmenu, 0},
        {ST_MENU,       N_("Placement of notices:"), P_OFFINTNL(hex_irc_notice_pos), 0, noticeposmenu, 0},
        {ST_MENU,       N_("Show channel switcher at:"), P_OFFINTNL(hex_gui_tab_pos), 0, cspos, 1},
        {ST_NUMBER,     N_("Shorten tab labels to:"), P_OFFINTNL(hex_gui_tab_trunc), 0, (const char **)N_("letters."), 99},

        {ST_HEADER,     N_("Tabs or Windows"),0,0,0},
        {ST_MENU,       N_("Open channels in:"), P_OFFINTNL(hex_gui_tab_chans), 0, tabwin, 0},
        {ST_MENU,       N_("Open dialogs in:"), P_OFFINTNL(hex_gui_tab_dialogs), 0, tabwin, 0},
        {ST_MENU,       N_("Open utilities in:"), P_OFFINTNL(hex_gui_tab_utils), N_("Open DCC, Ignore, Notify etc, in tabs or windows?"), tabwin, 0},

        {ST_END, 0, 0, 0, 0, 0}
};

static const char *const dccaccept[] =
{
        N_("Ask for confirmation"),
        N_("Ask for download folder"),
        N_("Save without interaction"),
        NULL
};

static const setting filexfer_settings[] =
{
        {ST_HEADER, N_("Files and Directories"), 0, 0, 0},
        {ST_MENU,       N_("Auto accept file offers:"), P_OFFINTNL(hex_dcc_auto_recv), 0, dccaccept, 0},
        {ST_EFOLDER,N_("Download files to:"), P_OFFSETNL(hex_dcc_dir), 0, 0, sizeof prefs.hex_dcc_dir},
        {ST_EFOLDER,N_("Move completed files to:"), P_OFFSETNL(hex_dcc_completed_dir), 0, 0, sizeof prefs.hex_dcc_completed_dir},
        {ST_TOGGLE, N_("Save nick name in filenames"), P_OFFINTNL(hex_dcc_save_nick), 0, 0, 0},

        {ST_HEADER,     N_("Auto Open DCC Windows"),0,0,0},
        {ST_TOGGLE, N_("Send window"), P_OFFINTNL(hex_gui_autoopen_send), 0, 0, 0},
        {ST_TOGGLE, N_("Receive window"), P_OFFINTNL(hex_gui_autoopen_recv), 0, 0, 0},
        {ST_TOGGLE, N_("Chat window"), P_OFFINTNL(hex_gui_autoopen_chat), 0, 0, 0},

        {ST_HEADER, N_("Maximum File Transfer Speeds (KiB/s or MiB/s)"), 0, 0, 0},
        {ST_NUMBER,     N_("One upload:"), P_OFFINTNL(hex_dcc_max_send_cps), 
                                        N_("Maximum speed for one transfer"), 0, 10000000},
        {ST_NUMBER,     N_("One download:"), P_OFFINTNL(hex_dcc_max_get_cps),
                                        N_("Maximum speed for one transfer"), 0, 10000000},
        {ST_NUMBER,     N_("All uploads combined:"), P_OFFINTNL(hex_dcc_global_max_send_cps),
                                        N_("Maximum speed for all files"), 0, 10000000},
        {ST_NUMBER,     N_("All downloads combined:"), P_OFFINTNL(hex_dcc_global_max_get_cps),
                                        N_("Maximum speed for all files"), 0, 10000000},

        {ST_END, 0, 0, 0, 0, 0}
};

static const int balloonlist[3] =
{
        P_OFFINTNL(hex_input_balloon_chans), P_OFFINTNL(hex_input_balloon_priv), P_OFFINTNL(hex_input_balloon_hilight)
};

static const int trayblinklist[3] =
{
        P_OFFINTNL(hex_input_tray_chans), P_OFFINTNL(hex_input_tray_priv), P_OFFINTNL(hex_input_tray_hilight)
};

static const int taskbarlist[3] =
{
        P_OFFINTNL(hex_input_flash_chans), P_OFFINTNL(hex_input_flash_priv), P_OFFINTNL(hex_input_flash_hilight)
};

static const int beeplist[3] =
{
        P_OFFINTNL(hex_input_beep_chans), P_OFFINTNL(hex_input_beep_priv), P_OFFINTNL(hex_input_beep_hilight)
};

static const setting alert_settings[] =
{
        {ST_HEADER,     N_("Alerts"),0,0,0},

        {ST_ALERTHEAD},


        {ST_3OGGLE, N_("Show notifications on:"), 0, 0, (void *)balloonlist, 0},
        {ST_3OGGLE, N_("Blink tray icon on:"), 0, 0, (void *)trayblinklist, 0},
        {ST_3OGGLE, N_("Blink task bar on:"), 0, 0, (void *)taskbarlist, 0},
#ifdef WIN32
        {ST_3OGGLE, N_("Make a beep sound on:"), 0, N_("Play the \"Instant Message Notification\" system sound upon the selected events"), (void *)beeplist, 0},
#else
#ifdef USE_LIBCANBERRA
        {ST_3OGGLE, N_("Make a beep sound on:"), 0, N_("Play \"message-new-instant\" from the freedesktop.org sound theme upon the selected events"), (void *)beeplist, 0},
#else
        {ST_3OGGLE, N_("Make a beep sound on:"), 0, N_("Play a GTK beep upon the selected events"), (void *)beeplist, 0},
#endif
#endif

        {ST_TOGGLE,     N_("Omit alerts when marked as being away"), P_OFFINTNL(hex_away_omit_alerts), 0, 0, 0},
        {ST_TOGGLE,     N_("Omit alerts while the window is focused"), P_OFFINTNL(hex_gui_focus_omitalerts), 0, 0, 0},

        {ST_HEADER,     N_("Tray Behavior"), 0, 0, 0},
        {ST_TOGGLE,     N_("Enable system tray icon"), P_OFFINTNL(hex_gui_tray), 0, 0, 4},
        {ST_TOGGLE,     N_("Minimize to tray"), P_OFFINTNL(hex_gui_tray_minimize), 0, 0, 0},
        {ST_TOGGLE,     N_("Close to tray"), P_OFFINTNL(hex_gui_tray_close), 0, 0, 0},
        {ST_TOGGLE,     N_("Automatically mark away/back"), P_OFFINTNL(hex_gui_tray_away), N_("Automatically change status when hiding to tray."), 0, 0},
        {ST_TOGGLE,     N_("Only show notifications when hidden or iconified"), P_OFFINTNL(hex_gui_tray_quiet), 0, 0, 0},

        {ST_HEADER,     N_("Highlighted Messages"),0,0,0},
        {ST_LABEL,      N_("Highlighted messages are ones where your nickname is mentioned, but also:"), 0, 0, 0, 1},

        {ST_ENTRY,      N_("Extra words to highlight:"), P_OFFSETNL(hex_irc_extra_hilight), 0, 0, sizeof prefs.hex_irc_extra_hilight},
        {ST_ENTRY,      N_("Nick names not to highlight:"), P_OFFSETNL(hex_irc_no_hilight), 0, 0, sizeof prefs.hex_irc_no_hilight},
        {ST_ENTRY,      N_("Nick names to always highlight:"), P_OFFSETNL(hex_irc_nick_hilight), 0, 0, sizeof prefs.hex_irc_nick_hilight},
        {ST_LABEL,      N_("Separate multiple words with commas.\nWildcards are accepted.")},

        {ST_END, 0, 0, 0, 0, 0}
};

static const setting alert_settings_nonotifications[] =
{
        {ST_HEADER,     N_("Alerts"),0,0,0},

        {ST_ALERTHEAD},
        {ST_3OGGLE, N_("Blink tray icon on:"), 0, 0, (void *)trayblinklist, 0},
#ifdef HAVE_GTK_MAC
        {ST_3OGGLE, N_("Bounce dock icon on:"), 0, 0, (void *)taskbarlist, 0},
#else
#ifndef __APPLE__
        {ST_3OGGLE, N_("Blink task bar on:"), 0, 0, (void *)taskbarlist, 0},
#endif
#endif
#ifdef WIN32
        {ST_3OGGLE, N_("Make a beep sound on:"), 0, N_("Play the \"Instant Message Notification\" system sound upon the selected events"), (void *)beeplist, 0},
#else
#ifdef USE_LIBCANBERRA
        {ST_3OGGLE, N_("Make a beep sound on:"), 0, N_("Play \"message-new-instant\" from the freedesktop.org sound theme upon the selected events"), (void *)beeplist, 0},
#else
        {ST_3OGGLE, N_("Make a beep sound on:"), 0, N_("Play a GTK beep upon the selected events"), (void *)beeplist, 0},
#endif
#endif

        {ST_TOGGLE,     N_("Omit alerts when marked as being away"), P_OFFINTNL(hex_away_omit_alerts), 0, 0, 0},
        {ST_TOGGLE,     N_("Omit alerts while the window is focused"), P_OFFINTNL(hex_gui_focus_omitalerts), 0, 0, 0},

        {ST_HEADER,     N_("Tray Behavior"), 0, 0, 0},
        {ST_TOGGLE,     N_("Enable system tray icon"), P_OFFINTNL(hex_gui_tray), 0, 0, 4},
        {ST_TOGGLE,     N_("Minimize to tray"), P_OFFINTNL(hex_gui_tray_minimize), 0, 0, 0},
        {ST_TOGGLE,     N_("Close to tray"), P_OFFINTNL(hex_gui_tray_close), 0, 0, 0},
        {ST_TOGGLE,     N_("Automatically mark away/back"), P_OFFINTNL(hex_gui_tray_away), N_("Automatically change status when hiding to tray."), 0, 0},

        {ST_HEADER,     N_("Highlighted Messages"),0,0,0},
        {ST_LABEL,      N_("Highlighted messages are ones where your nickname is mentioned, but also:"), 0, 0, 0, 1},

        {ST_ENTRY,      N_("Extra words to highlight:"), P_OFFSETNL(hex_irc_extra_hilight), 0, 0, sizeof prefs.hex_irc_extra_hilight},
        {ST_ENTRY,      N_("Nick names not to highlight:"), P_OFFSETNL(hex_irc_no_hilight), 0, 0, sizeof prefs.hex_irc_no_hilight},
        {ST_ENTRY,      N_("Nick names to always highlight:"), P_OFFSETNL(hex_irc_nick_hilight), 0, 0, sizeof prefs.hex_irc_nick_hilight},
        {ST_LABEL,      N_("Separate multiple words with commas.\nWildcards are accepted.")},

        {ST_END, 0, 0, 0, 0, 0}
};

static const setting alert_settings_unity[] =
{
        {ST_HEADER,     N_("Alerts"),0,0,0},

        {ST_ALERTHEAD},
        {ST_3OGGLE, N_("Show notifications on:"), 0, 0, (void *)balloonlist, 0},
        {ST_3OGGLE, N_("Blink task bar on:"), 0, 0, (void *)taskbarlist, 0},
        {ST_3OGGLE, N_("Make a beep sound on:"), 0, 0, (void *)beeplist, 0},

        {ST_TOGGLE,     N_("Omit alerts when marked as being away"), P_OFFINTNL(hex_away_omit_alerts), 0, 0, 0},
        {ST_TOGGLE,     N_("Omit alerts while the window is focused"), P_OFFINTNL(hex_gui_focus_omitalerts), 0, 0, 0},

        {ST_HEADER,     N_("Highlighted Messages"),0,0,0},
        {ST_LABEL,      N_("Highlighted messages are ones where your nickname is mentioned, but also:"), 0, 0, 0, 1},

        {ST_ENTRY,      N_("Extra words to highlight:"), P_OFFSETNL(hex_irc_extra_hilight), 0, 0, sizeof prefs.hex_irc_extra_hilight},
        {ST_ENTRY,      N_("Nick names not to highlight:"), P_OFFSETNL(hex_irc_no_hilight), 0, 0, sizeof prefs.hex_irc_no_hilight},
        {ST_ENTRY,      N_("Nick names to always highlight:"), P_OFFSETNL(hex_irc_nick_hilight), 0, 0, sizeof prefs.hex_irc_nick_hilight},
        {ST_LABEL,      N_("Separate multiple words with commas.\nWildcards are accepted.")},

        {ST_END, 0, 0, 0, 0, 0}
};

static const setting alert_settings_unityandnonotifications[] =
{
        {ST_HEADER, N_("Alerts"), 0, 0, 0},

        {ST_ALERTHEAD},
        {ST_3OGGLE, N_("Blink task bar on:"), 0, 0, (void *)taskbarlist, 0},
        {ST_3OGGLE, N_("Make a beep sound on:"), 0, 0, (void *)beeplist, 0},

        {ST_TOGGLE, N_("Omit alerts when marked as being away"), P_OFFINTNL (hex_away_omit_alerts), 0, 0, 0},
        {ST_TOGGLE, N_("Omit alerts while the window is focused"), P_OFFINTNL (hex_gui_focus_omitalerts), 0, 0, 0},

        {ST_HEADER, N_("Highlighted Messages"), 0, 0, 0},
        {ST_LABEL, N_("Highlighted messages are ones where your nickname is mentioned, but also:"), 0, 0, 0, 1},

        {ST_ENTRY, N_("Extra words to highlight:"), P_OFFSETNL (hex_irc_extra_hilight), 0, 0, sizeof prefs.hex_irc_extra_hilight},
        {ST_ENTRY, N_("Nick names not to highlight:"), P_OFFSETNL (hex_irc_no_hilight), 0, 0, sizeof prefs.hex_irc_no_hilight},
        {ST_ENTRY, N_("Nick names to always highlight:"), P_OFFSETNL (hex_irc_nick_hilight), 0, 0, sizeof prefs.hex_irc_nick_hilight},
        {ST_LABEL, N_("Separate multiple words with commas.\nWildcards are accepted.")},

        {ST_END, 0, 0, 0, 0, 0}
};

static const setting general_settings[] =
{
        {ST_HEADER,     N_("Default Messages"),0,0,0},
        {ST_ENTRY,      N_("Quit:"), P_OFFSETNL(hex_irc_quit_reason), 0, 0, sizeof prefs.hex_irc_quit_reason},
        {ST_ENTRY,      N_("Leave channel:"), P_OFFSETNL(hex_irc_part_reason), 0, 0, sizeof prefs.hex_irc_part_reason},
        {ST_ENTRY,      N_("Away:"), P_OFFSETNL(hex_away_reason), 0, 0, sizeof prefs.hex_away_reason},

        {ST_HEADER,     N_("Away"),0,0,0},
        {ST_TOGGLE,     N_("Show away once"), P_OFFINTNL(hex_away_show_once), N_("Show identical away messages only once."), 0, 0},
        {ST_TOGGLE,     N_("Automatically unmark away"), P_OFFINTNL(hex_away_auto_unmark), N_("Unmark yourself as away before sending messages."), 0, 0},

        {ST_HEADER,     N_("Miscellaneous"),0,0,0},
        {ST_TOGGLE,     N_("Display MODEs in raw form"), P_OFFINTNL(hex_irc_raw_modes), 0, 0, 0},
        {ST_TOGGLE,     N_("WHOIS on notify"), P_OFFINTNL(hex_notify_whois_online), N_("Sends a /WHOIS when a user comes online in your notify list."), 0, 0},
        {ST_TOGGLE,     N_("Hide join and part messages"), P_OFFINTNL(hex_irc_conf_mode), N_("Hide channel join/part messages by default."), 0, 0},
        {ST_TOGGLE,     N_("Hide nick change messages"), P_OFFINTNL(hex_irc_hide_nickchange), 0, 0, 0},
        {ST_TOGGLE,     N_("Hide hostmasks in join and part messages"), P_OFFINTNL(hex_irc_hide_join_part_hostmask), 0, 0, 0},
        {ST_TOGGLE,     N_("Enable Ctrl+Q to quit"), P_OFFINTNL(hex_gui_ctrlq_quit), 0, 0, 0},

        {ST_END, 0, 0, 0, 0, 0}
};

static const char *const bantypemenu[] = 
{
        N_("*!*@*.host"),
        N_("*!*@domain"),
        N_("*!*user@*.host"),
        N_("*!*user@domain"),
        NULL
};

static const setting advanced_settings[] =
{
        {ST_HEADER,     N_("Auto Copy Behavior"),0,0,0},
        {ST_TOGGLE, N_("Automatically copy selected text"), P_OFFINTNL(hex_text_autocopy_text),
                                        N_("Copy selected text to clipboard when left mouse button is released. "
                                                "Otherwise, Ctrl+Shift+C will copy the "
                                                "selected text to the clipboard."), 0, 0},
        {ST_TOGGLE, N_("Automatically include timestamps"), P_OFFINTNL(hex_text_autocopy_stamp),
                                        N_("Automatically include timestamps in copied lines of text. Otherwise, "
                                                "include timestamps if the Shift key is held down while selecting."), 0, 0},
        {ST_TOGGLE, N_("Automatically include color information"), P_OFFINTNL(hex_text_autocopy_color),
                                        N_("Automatically include color information in copied lines of text.  "
                                                "Otherwise, include color information if the Ctrl key is held down "
                                                "while selecting."), 0, 0},

        {ST_HEADER,     N_("Miscellaneous"), 0, 0, 0},
        {ST_ENTRY,  N_("Real name:"), P_OFFSETNL(hex_irc_real_name), 0, 0, sizeof prefs.hex_irc_real_name},
#ifdef WIN32
        {ST_ENTRY,  N_("Alternative fonts:"), P_OFFSETNL(hex_text_font_alternative), N_("Separate multiple entries with commas without spaces before or after."), 0, sizeof prefs.hex_text_font_alternative},
#endif
        {ST_TOGGLE,     N_("Display lists in compact mode"), P_OFFINTNL(hex_gui_compact), N_("Use less spacing between user list/channel tree rows."), 0, 0},
        {ST_TOGGLE,     N_("Use server time if supported"), P_OFFINTNL(hex_irc_cap_server_time), N_("Display timestamps obtained from server if it supports the time-server extension."), 0, 0},
        {ST_TOGGLE,     N_("Automatically reconnect to servers on disconnect"), P_OFFINTNL(hex_net_auto_reconnect), 0, 0, 1},
        {ST_NUMBER,     N_("Lag check interval:"), P_OFFINTNL(hex_net_lag_check), 0, (const char **)N_("seconds."), 9999},
        {ST_NUMBER,     N_("Auto reconnect delay:"), P_OFFINTNL(hex_net_reconnect_delay), 0, 0, 9999},
        {ST_NUMBER,     N_("Auto join delay:"), P_OFFINTNL(hex_irc_join_delay), 0, 0, 9999},
        {ST_MENU,       N_("Ban Type:"), P_OFFINTNL(hex_irc_ban_type), N_("Attempt to use this banmask when banning or quieting. (requires irc_who_join)"), bantypemenu, 0},

        {ST_END, 0, 0, 0, 0, 0}
};

static const setting logging_settings[] =
{
        {ST_HEADER,     N_("Logging"),0,0,0},
        {ST_TOGGLE,     N_("Display scrollback from previous session"), P_OFFINTNL(hex_text_replay), 0, 0, 0},
        {ST_NUMBER,     N_("Scrollback lines:"), P_OFFINTNL(hex_text_max_lines),0,0,100000},
        {ST_TOGGLE,     N_("Enable logging of conversations to disk"), P_OFFINTNL(hex_irc_logging), 0, 0, 0},
        {ST_ENTRY,      N_("Log filename:"), P_OFFSETNL(hex_irc_logmask), 0, 0, sizeof prefs.hex_irc_logmask},
        {ST_LABEL,      N_("%s=Server %c=Channel %n=Network.")},

        {ST_HEADER,     N_("Timestamps"),0,0,0},
        {ST_TOGGLE,     N_("Insert timestamps in logs"), P_OFFINTNL(hex_stamp_log), 0, 0, 1},
        {ST_ENTRY,      N_("Log timestamp format:"), P_OFFSETNL(hex_stamp_log_format), 0, 0, sizeof prefs.hex_stamp_log_format},
#ifdef WIN32
        {ST_LABEL,      N_("See the strftime MSDN article for details.")},
#else
        {ST_LABEL,      N_("See the strftime manpage for details.")},
#endif

        {ST_HEADER,     N_("URLs"),0,0,0},
        {ST_TOGGLE,     N_("Enable logging of URLs to disk"), P_OFFINTNL(hex_url_logging), 0, 0, 0},
        {ST_TOGGLE,     N_("Enable URL grabber"), P_OFFINTNL(hex_url_grabber), 0, 0, 1},
        {ST_NUMBER,     N_("Maximum number of URLs to grab:"), P_OFFINTNL(hex_url_grabber_limit), 0, 0, 9999},

        {ST_END, 0, 0, 0, 0, 0}
};

static const char *const proxytypes[] =
{
        N_("(Disabled)"),
        N_("Wingate"),
        N_("SOCKS4"),
        N_("SOCKS5"),
        N_("HTTP"),
        N_("Auto"),
        NULL
};

static const char *const proxyuse[] =
{
        N_("All connections"),
        N_("IRC server only"),
        N_("DCC only"),
        NULL
};

static const setting network_settings[] =
{
        {ST_HEADER,     N_("Your Address"), 0, 0, 0, 0},
        {ST_ENTRY,      N_("Bind to:"), P_OFFSETNL(hex_net_bind_host), 0, 0, sizeof prefs.hex_net_bind_host},
        {ST_LABEL,      N_("Only useful for computers with multiple addresses.")},

        {ST_HEADER, N_("File Transfers"), 0, 0, 0},
        {ST_TOGGLE, N_("Get my address from the IRC server"), P_OFFINTNL(hex_dcc_ip_from_server),
                                        N_("Asks the IRC server for your real address. Use this if you have a 192.168.*.* address!"), 0, 0},
        {ST_ENTRY,      N_("DCC IP address:"), P_OFFSETNL(hex_dcc_ip),
                                        N_("Claim you are at this address when offering files."), 0, sizeof prefs.hex_dcc_ip},
        {ST_NUMBER,     N_("First DCC listen port:"), P_OFFINTNL(hex_dcc_port_first), 0, 0, 65535},
        {ST_NUMBER,     N_("Last DCC listen port:"), P_OFFINTNL(hex_dcc_port_last), 0, 
                (const char **)N_("!Leave ports at zero for full range."), 65535},

        {ST_HEADER,     N_("Proxy Server"), 0, 0, 0, 0},
        {ST_ENTRY,      N_("Hostname:"), P_OFFSETNL(hex_net_proxy_host), 0, 0, sizeof prefs.hex_net_proxy_host},
        {ST_NUMBER,     N_("Port:"), P_OFFINTNL(hex_net_proxy_port), 0, 0, 65535},
        {ST_MENU,       N_("Type:"), P_OFFINTNL(hex_net_proxy_type), 0, proxytypes, 0},
        {ST_MENU,       N_("Use proxy for:"), P_OFFINTNL(hex_net_proxy_use), 0, proxyuse, 0},

        {ST_HEADER,     N_("Connection Health"), 0, 0, 0, 0},
        {ST_NUMBER,     N_("TCP keepalive idle:"), P_OFFINTNL(hex_net_keepalive_idle), 0, (const char **)N_("seconds."), 7200},
        {ST_NUMBER,     N_("TCP keepalive interval:"), P_OFFINTNL(hex_net_keepalive_interval), 0, (const char **)N_("seconds."), 600},
        {ST_NUMBER,     N_("TCP keepalive probes:"), P_OFFINTNL(hex_net_keepalive_count), 0, 0, 20},

        {ST_HEADER,     N_("Proxy Authentication"), 0, 0, 0, 0},
        {ST_TOGGLE,     N_("Use authentication (HTTP or SOCKS5 only)"), P_OFFINTNL(hex_net_proxy_auth), 0, 0, 0},
        {ST_ENTRY,      N_("Username:"), P_OFFSETNL(hex_net_proxy_user), 0, 0, sizeof prefs.hex_net_proxy_user},
        {ST_ENTRY,      N_("Password:"), P_OFFSETNL(hex_net_proxy_pass), 0, GINT_TO_POINTER(1), sizeof prefs.hex_net_proxy_pass},

        {ST_END, 0, 0, 0, 0, 0}
};

static const setting identd_settings[] =
{
        {ST_HEADER, N_("Identd Server"), 0, 0, 0, 0},
        {ST_TOGGLE, N_("Enabled"), P_OFFINTNL(hex_identd_server), N_("Server will respond with the networks username"), 0, 1},
        {ST_NUMBER,     N_("Port:"), P_OFFINTNL(hex_identd_port), N_("You must have permissions to listen on this port. "
                                                                                                   "If not 113 (0 defaults to this) then you must configure port-forwarding."), 0, 65535},

        {ST_END, 0, 0, 0, 0, 0}
};

#define setup_get_str(pr,set) (((char *)pr)+set->offset)
#define setup_get_int(pr,set) *(((int *)pr)+set->offset)
#define setup_get_int3(pr,off) *(((int *)pr)+off) 

#define setup_set_int(pr,set,num) *((int *)pr+set->offset)=num
#define setup_set_str(pr,set,str) strcpy(((char *)pr)+set->offset,str)

typedef enum
{
	SETUP_ALIGN_START,
	SETUP_ALIGN_CENTER,
	SETUP_ALIGN_FILL
} setup_align;

static GtkAlign
setup_align_to_gtk (setup_align align)
{
	switch (align)
	{
	case SETUP_ALIGN_FILL:
		return GTK_ALIGN_FILL;
	case SETUP_ALIGN_CENTER:
		return GTK_ALIGN_CENTER;
	case SETUP_ALIGN_START:
	default:
		return GTK_ALIGN_START;
	}
}

static void
setup_table_attach (GtkWidget *table, GtkWidget *child,
		    guint left_attach, guint right_attach,
		    guint top_attach, guint bottom_attach,
		    gboolean hexpand, gboolean vexpand,
		    setup_align halign, setup_align valign,
		    guint xpad, guint ypad)
{
	gtk_widget_set_hexpand (child, hexpand);
	gtk_widget_set_vexpand (child, vexpand);
	gtk_widget_set_halign (child, setup_align_to_gtk (halign));
	gtk_widget_set_valign (child, setup_align_to_gtk (valign));
	gtk_widget_set_margin_start (child, xpad);
	gtk_widget_set_margin_end (child, xpad);
	gtk_widget_set_margin_top (child, ypad);
	gtk_widget_set_margin_bottom (child, ypad);

	gtk_grid_attach (GTK_GRID (table), child, left_attach, top_attach,
			 right_attach - left_attach, bottom_attach - top_attach);
}


static void
setup_3oggle_cb (GtkToggleButton *but, unsigned int *setting)
{
        *setting = gtk_toggle_button_get_active (but);
}

static void
setup_headlabel (GtkWidget *tab, int row, int col, char *text)
{
        GtkWidget *label;
        char buf[128];
        char *sp;

        g_snprintf (buf, sizeof (buf), "<b><span size=\"smaller\">%s</span></b>", text);
        sp = strchr (buf + 17, ' ');
        if (sp)
                *sp = '\n';

        label = gtk_label_new (NULL);
        gtk_label_set_markup (GTK_LABEL (label), buf);
        gtk_widget_set_halign (label, GTK_ALIGN_START);
        gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
        setup_table_attach (tab, label, col, col + 1, row, row + 1, FALSE, FALSE,
                            SETUP_ALIGN_START, SETUP_ALIGN_CENTER, 4, 0);
}

static void
setup_create_alert_header (GtkWidget *tab, int row, const setting *set)
{
        setup_headlabel (tab, row, 3, _("Channel Message"));
        setup_headlabel (tab, row, 4, _("Private Message"));
        setup_headlabel (tab, row, 5, _("Highlighted Message"));
}

/* makes 3 toggles side-by-side */

static void
setup_create_3oggle (GtkWidget *tab, int row, const setting *set)
{
        GtkWidget *label, *wid;
        int *offsets = (int *)set->list;

        label = gtk_label_new (_(set->label));
        gtk_widget_set_halign (label, GTK_ALIGN_START);
        gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
        if (set->tooltip)
        {
                gtk_widget_set_tooltip_text (label, _(set->tooltip));
        }
        setup_table_attach (tab, label, 2, 3, row, row + 1, FALSE, FALSE,
                            SETUP_ALIGN_START, SETUP_ALIGN_CENTER,
                            LABEL_INDENT, 0);

        wid = gtk_check_button_new ();
        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid),
                                                                                        setup_get_int3 (&setup_prefs, offsets[0]));
        g_signal_connect (G_OBJECT (wid), "toggled",
                                                        G_CALLBACK (setup_3oggle_cb), ((int *)&setup_prefs) + offsets[0]);
        setup_table_attach (tab, wid, 3, 4, row, row + 1, FALSE, FALSE,
                            SETUP_ALIGN_CENTER, SETUP_ALIGN_CENTER, 0, 0);

        wid = gtk_check_button_new ();
        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid),
                                                                                        setup_get_int3 (&setup_prefs, offsets[1]));
        g_signal_connect (G_OBJECT (wid), "toggled",
                                                        G_CALLBACK (setup_3oggle_cb), ((int *)&setup_prefs) + offsets[1]);
        setup_table_attach (tab, wid, 4, 5, row, row + 1, FALSE, FALSE,
                            SETUP_ALIGN_CENTER, SETUP_ALIGN_CENTER, 0, 0);

        wid = gtk_check_button_new ();
        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid),
                                                                                        setup_get_int3 (&setup_prefs, offsets[2]));
        g_signal_connect (G_OBJECT (wid), "toggled",
                                                        G_CALLBACK (setup_3oggle_cb), ((int *)&setup_prefs) + offsets[2]);
        setup_table_attach (tab, wid, 5, 6, row, row + 1, FALSE, FALSE,
                            SETUP_ALIGN_CENTER, SETUP_ALIGN_CENTER, 0, 0);
}

static void
setup_toggle_cb (GtkToggleButton *but, const setting *set)
{
        GtkWidget *label, *disable_wid;

        setup_set_int (&setup_prefs, set, gtk_toggle_button_get_active (but));

        /* does this toggle also enable/disable another widget? */
        disable_wid = g_object_get_data (G_OBJECT (but), "nxt");
        if (disable_wid)
        {
                gtk_widget_set_sensitive (disable_wid, gtk_toggle_button_get_active (but));
                label = g_object_get_data (G_OBJECT (disable_wid), "lbl");
                gtk_widget_set_sensitive (label, gtk_toggle_button_get_active (but));
        }
}

static void
setup_toggle_sensitive_cb (GtkToggleButton *but, GtkWidget *wid)
{
        gtk_widget_set_sensitive (wid, gtk_toggle_button_get_active (but));
}

static void
setup_topicbar_inline_toggled_cb (GtkToggleButton *but, gpointer userdata)
{
        (void) userdata;

        if (setup_topicbar_multiline_toggle)
                gtk_widget_set_sensitive (setup_topicbar_multiline_toggle,
                        !gtk_toggle_button_get_active (but));
}

static void
setup_create_toggleR (GtkWidget *tab, int row, const setting *set)
{
        GtkWidget *wid;

        wid = gtk_check_button_new_with_label (_(set->label));
        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid),
                                                                                        setup_get_int (&setup_prefs, set));
        g_signal_connect (G_OBJECT (wid), "toggled",
                                                        G_CALLBACK (setup_toggle_cb), (gpointer)set);
        if (set->tooltip)
                gtk_widget_set_tooltip_text (wid, _(set->tooltip));
        setup_table_attach (tab, wid, 4, 5, row, row + 1, TRUE, FALSE,
                            SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 0);
}

static GtkWidget *
setup_create_toggleL (GtkWidget *tab, int row, const setting *set)
{
        GtkWidget *wid;

        wid = gtk_check_button_new_with_label (_(set->label));
        gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid),
                                                                                        setup_get_int (&setup_prefs, set));
        g_signal_connect (G_OBJECT (wid), "toggled",
                                                        G_CALLBACK (setup_toggle_cb), (gpointer)set);
        if (set->offset == STRUCT_OFFSET_INT (struct zoitechatprefs, hex_gui_mode_buttons_inline))
                g_signal_connect (G_OBJECT (wid), "toggled",
                                                        G_CALLBACK (setup_topicbar_inline_toggled_cb), NULL);
        if (set->offset == STRUCT_OFFSET_INT (struct zoitechatprefs, hex_gui_topicbar_multiline))
        {
                setup_topicbar_multiline_toggle = wid;
                gtk_widget_set_sensitive (wid, !setup_prefs.hex_gui_mode_buttons_inline);
        }
        if (set->tooltip)
                gtk_widget_set_tooltip_text (wid, _(set->tooltip));
        setup_table_attach (tab, wid, 2, row==6 ? 6 : 4, row, row + 1, FALSE, FALSE,
                            SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, LABEL_INDENT, 0);

        return wid;
}

static GtkWidget *
setup_create_italic_label (char *text)
{
        GtkWidget *label;
        char buf[256];

        label = gtk_label_new (NULL);
        g_snprintf (buf, sizeof (buf), "<i><span size=\"smaller\">%s</span></i>", text);
        gtk_label_set_markup (GTK_LABEL (label), buf);
        gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER);

        return label;
}

typedef struct
{
        const setting *set;
        GtkSpinButton *spin;
        GtkComboBox *unit;
} setup_dcc_speed_data;

static gboolean
setup_is_dcc_speed_setting (const setting *set)
{
        return set->offset == P_OFFINTNL (hex_dcc_max_send_cps)
                || set->offset == P_OFFINTNL (hex_dcc_max_get_cps)
                || set->offset == P_OFFINTNL (hex_dcc_global_max_send_cps)
                || set->offset == P_OFFINTNL (hex_dcc_global_max_get_cps);
}

static int
setup_dcc_speed_multiplier (GtkComboBox *combo)
{
        return gtk_combo_box_get_active (combo) == 1 ? 1024 * 1024 : 1024;
}

static void
setup_dcc_speed_spin_range (setup_dcc_speed_data *data)
{
        int max = data->set->extra / setup_dcc_speed_multiplier (data->unit);
        gtk_spin_button_set_range (data->spin, 0, max > 0 ? max : 0);
}

static void
setup_dcc_speed_store (setup_dcc_speed_data *data)
{
        int value = gtk_spin_button_get_value_as_int (data->spin);
        int speed = value * setup_dcc_speed_multiplier (data->unit);
        if (speed > data->set->extra)
                speed = data->set->extra;
        setup_set_int (&setup_prefs, data->set, speed);
}

static void
setup_dcc_speed_spin_cb (GtkSpinButton *spin, setup_dcc_speed_data *data)
{
        (void)spin;
        setup_dcc_speed_store (data);
}

static void
setup_dcc_speed_unit_cb (GtkComboBox *combo, setup_dcc_speed_data *data)
{
        int speed = setup_get_int (&setup_prefs, data->set);
        int multiplier;
        int value;

        (void)combo;
        setup_dcc_speed_spin_range (data);
        multiplier = setup_dcc_speed_multiplier (data->unit);
        value = (speed + (multiplier / 2)) / multiplier;
        gtk_spin_button_set_value (data->spin, value);
        setup_dcc_speed_store (data);
}

static void
setup_spin_cb (GtkSpinButton *spin, const setting *set)
{
        setup_set_int (&setup_prefs, set, gtk_spin_button_get_value_as_int (spin));
}

static GtkWidget *
setup_create_spin (GtkWidget *table, int row, const setting *set)
{
        GtkWidget *label, *wid, *rbox;
        char *text;

        label = gtk_label_new (_(set->label));
        gtk_widget_set_halign (label, GTK_ALIGN_START);
        gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
        setup_table_attach (table, label, 2, 3, row, row + 1, FALSE, FALSE,
                            SETUP_ALIGN_START, SETUP_ALIGN_CENTER,
                            LABEL_INDENT, 0);

        rbox = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
        gtk_widget_set_halign (rbox, GTK_ALIGN_START);
        gtk_widget_set_valign (rbox, GTK_ALIGN_CENTER);
        setup_table_attach (table, rbox, 3, 4, row, row + 1, TRUE, FALSE,
                            SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 0);

        wid = gtk_spin_button_new_with_range (0, set->extra, 1);
        g_object_set_data (G_OBJECT (wid), "lbl", label);
        if (set->tooltip)
                gtk_widget_set_tooltip_text (wid, _(set->tooltip));
        if (setup_is_dcc_speed_setting (set))
        {
                GtkWidget *unit;
                setup_dcc_speed_data *data = g_new0 (setup_dcc_speed_data, 1);
                int speed = setup_get_int (&setup_prefs, set);
                int use_mib = speed >= (1024 * 1024) && speed % (1024 * 1024) == 0;
                int multiplier;

                data->set = set;
                data->spin = GTK_SPIN_BUTTON (wid);

                unit = gtk_combo_box_text_new ();
                gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (unit), _("KiB/s"));
                gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (unit), _("MiB/s"));
                gtk_combo_box_set_wrap_width (GTK_COMBO_BOX (unit), 1);
                gtk_combo_box_set_active (GTK_COMBO_BOX (unit), use_mib ? 1 : 0);
                data->unit = GTK_COMBO_BOX (unit);

                setup_dcc_speed_spin_range (data);
                multiplier = setup_dcc_speed_multiplier (data->unit);
                gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid), (speed + (multiplier / 2)) / multiplier);

                g_signal_connect (G_OBJECT (wid), "value-changed",
                                                        G_CALLBACK (setup_dcc_speed_spin_cb), data);
                g_signal_connect (G_OBJECT (unit), "changed",
                                                        G_CALLBACK (setup_dcc_speed_unit_cb), data);

                gtk_box_pack_start (GTK_BOX (rbox), wid, 0, 0, 0);
                gtk_box_pack_start (GTK_BOX (rbox), unit, 0, 0, 6);
        } else
        {
                gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid),
                                                                                setup_get_int (&setup_prefs, set));
                g_signal_connect (G_OBJECT (wid), "value-changed",
                                                        G_CALLBACK (setup_spin_cb), (gpointer)set);
                gtk_box_pack_start (GTK_BOX (rbox), wid, 0, 0, 0);
        }

        if (set->list)
        {
                text = _((char *)set->list);
                if (text[0] == '!')
                        label = setup_create_italic_label (text + 1);
                else
                        label = gtk_label_new (text);
                gtk_box_pack_start (GTK_BOX (rbox), label, 0, 0, 6);
        }

        return wid;
}

static guint setup_trans_apply_tag = 0;
/* the window opacity the dialog opened with, so cancelling can undo the
 * live preview done by the slider */
static int setup_transparency_snapshot;

static gint
setup_apply_trans (gpointer userdata)
{
        prefs.hex_gui_transparency = setup_prefs.hex_gui_transparency;
        if (current_sess)
                gtk_widget_set_opacity (current_sess->gui->window,
                                                                (prefs.hex_gui_transparency / 255.));

        setup_trans_apply_tag = 0;
        return 0;
}

static void
setup_hscale_cb (GtkRange *wid, const setting *set)
{
        setup_set_int (&setup_prefs, set, (int) gtk_range_get_value (wid));

        if (setup_trans_apply_tag == 0)
                setup_trans_apply_tag = g_idle_add ((GSourceFunc) setup_apply_trans, NULL);
}

static void
setup_create_hscale (GtkWidget *table, int row, const setting *set)
{
        GtkWidget *wid;

        wid = gtk_label_new (_(set->label));
        gtk_widget_set_halign (wid, GTK_ALIGN_START);
        gtk_widget_set_valign (wid, GTK_ALIGN_CENTER);
        setup_table_attach (table, wid, 2, 3, row, row + 1, FALSE, FALSE,
                            SETUP_ALIGN_START, SETUP_ALIGN_CENTER,
                            LABEL_INDENT, 0);

        wid = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 0., 255., 1.);
        gtk_scale_set_value_pos (GTK_SCALE (wid), GTK_POS_RIGHT);
        gtk_range_set_value (GTK_RANGE (wid), setup_get_int (&setup_prefs, set));
        g_signal_connect (G_OBJECT(wid), "value-changed",
                                                        G_CALLBACK (setup_hscale_cb), (gpointer)set);
        setup_table_attach (table, wid, 3, 6, row, row + 1, TRUE, FALSE,
                            SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 0);

#ifndef WIN32 /* Windows always supports this */
        /* Only used for transparency currently */
        if (current_sess &&
                 !gdk_screen_is_composited (gtk_widget_get_screen (current_sess->gui->window)))
                gtk_widget_set_sensitive (wid, FALSE);
#endif
}


static GtkWidget *proxy_user;
static GtkWidget *proxy_pass;

static void
setup_menu_cb (GtkWidget *cbox, const setting *set)
{
        int n = gtk_combo_box_get_active (GTK_COMBO_BOX (cbox));

        /* set the prefs.<field> */
        setup_set_int (&setup_prefs, set, n + set->extra);

        if (set->list == proxytypes)
        {
                /* only HTTP and SOCKS5 can use a username/pass */
                gtk_widget_set_sensitive (proxy_user, (n == 3 || n == 4 || n == 5));
                gtk_widget_set_sensitive (proxy_pass, (n == 3 || n == 4 || n == 5));
        }
}

static void
setup_radio_cb (GtkWidget *item, const setting *set)
{
        if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (item)))
        {
                int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "n"));
                /* set the prefs.<field> */
                setup_set_int (&setup_prefs, set, n);
        }
}

static int
setup_create_radio (GtkWidget *table, int row, const setting *set)
{
        GtkWidget *wid, *hbox;
        int i;
        const char **text = (const char **)set->list;
        GSList *group;

        wid = gtk_label_new (_(set->label));
        gtk_widget_set_halign (wid, GTK_ALIGN_START);
        gtk_widget_set_valign (wid, GTK_ALIGN_CENTER);
        setup_table_attach (table, wid, 2, 3, row, row + 1, FALSE, FALSE,
                            SETUP_ALIGN_START, SETUP_ALIGN_CENTER,
                            LABEL_INDENT, 0);

        hbox = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
        setup_table_attach (table, hbox, 3, 4, row, row + 1, FALSE, FALSE,
                            SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 0);

        i = 0;
        group = NULL;
        while (text[i])
        {
                if (text[i][0] != 0)
                {
                        wid = gtk_radio_button_new_with_mnemonic (group, _(text[i]));
                        /*if (set->tooltip)
                                gtk_widget_set_tooltip_text (wid, _(set->tooltip));*/
                        group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (wid));
                        gtk_container_add (GTK_CONTAINER (hbox), wid);
                        if (i == setup_get_int (&setup_prefs, set))
                                gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), TRUE);
                        g_object_set_data (G_OBJECT (wid), "n", GINT_TO_POINTER (i));
                        g_signal_connect (G_OBJECT (wid), "toggled",
                                                                        G_CALLBACK (setup_radio_cb), (gpointer)set);
                }
                i++;
                row++;
        }

        return i;
}

/*
static const char *id_strings[] =
{
        "",
        "*",
        "%C4*%C18%B%B",
        "%U"
};

static void
setup_id_menu_cb (GtkWidget *item, char *dest)
{
        int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "n"));

        strcpy (dest, id_strings[n]);
}

static void
setup_create_id_menu (GtkWidget *table, char *label, int row, char *dest)
{
        GtkWidget *wid, *menu, *item;
        int i, def = 0;
        static const char *text[] =
        {
                ("(disabled)"),
                ("A star (*)"),
                ("A red star (*)"),
                ("Underlined")
        };

        wid = gtk_label_new (label);
        gtk_widget_set_halign (wid, GTK_ALIGN_START);
        gtk_widget_set_valign (wid, GTK_ALIGN_CENTER);
        setup_table_attach (table, wid, 2, 3, row, row + 1, FALSE, FALSE,
                            SETUP_ALIGN_START, SETUP_ALIGN_CENTER,
                            LABEL_INDENT, 0);

        wid = gtk_option_menu_new ();
        menu = gtk_menu_new ();

        for (i = 0; i < 4; i++)
        {
                if (strcmp (id_strings[i], dest) == 0)
                {
                        def = i;
                        break;
                }
        }

        i = 0;
        while (text[i])
        {
                item = gtk_menu_item_new_with_label (_(text[i]));
                g_object_set_data (G_OBJECT (item), "n", GINT_TO_POINTER (i));

                gtk_widget_show (item);
                gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
                g_signal_connect (G_OBJECT (item), "activate",
                                                                G_CALLBACK (setup_id_menu_cb), dest);
                i++;
        }

        gtk_option_menu_set_menu (GTK_OPTION_MENU (wid), menu);
        gtk_option_menu_set_history (GTK_OPTION_MENU (wid), def);

        setup_table_attach (table, wid, 3, 4, row, row + 1, FALSE, FALSE,
                            SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 0);
}

*/

static void
setup_create_menu (GtkWidget *table, int row, const setting *set)
{
        GtkWidget *wid, *cbox, *box;
        const char **text = (const char **)set->list;
        int i;

        wid = gtk_label_new (_(set->label));
        gtk_widget_set_halign (wid, GTK_ALIGN_START);
        gtk_widget_set_valign (wid, GTK_ALIGN_CENTER);
        setup_table_attach (table, wid, 2, 3, row, row + 1, FALSE, FALSE,
                            SETUP_ALIGN_START, SETUP_ALIGN_CENTER,
                            LABEL_INDENT, 0);

        cbox = gtk_combo_box_text_new ();

        for (i = 0; text[i]; i++)
                gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (cbox), _(text[i]));

        gtk_combo_box_set_active (GTK_COMBO_BOX (cbox),
                                                                          setup_get_int (&setup_prefs, set) - set->extra);
        g_signal_connect (G_OBJECT (cbox), "changed",
                                                        G_CALLBACK (setup_menu_cb), (gpointer)set);

        box = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 0);
        gtk_box_pack_start (GTK_BOX (box), cbox, 0, 0, 0);
        setup_table_attach (table, box, 3, 4, row, row + 1, TRUE, FALSE,
                            SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 0);
}

static void
setup_filereq_cb (GtkWidget *entry, char *file)
{
        if (file)
        {
                if (file[0])
                        gtk_entry_set_text (GTK_ENTRY (entry), file);
        }
}

static void
setup_browsefile_response_cb (GtkNativeDialog *dialog, gint response, gpointer user_data)
{
        GtkWidget *entry = user_data;

        if (response == GTK_RESPONSE_ACCEPT)
        {
                char *file = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
                setup_filereq_cb (entry, file);
                g_free (file);
        }

        g_object_unref (dialog);
}

static void
setup_browsefile_cb (GtkWidget *button, GtkWidget *entry)
{
        GtkFileChooserNative *dialog;
        GtkFileFilter *filefilter;
        const char *current;
        char *dirname;

        (void)button;
        dialog = gtk_file_chooser_native_new (_("Select an Image File"),
                                              GTK_WINDOW (setup_window),
                                              GTK_FILE_CHOOSER_ACTION_OPEN,
                                              _("_Open"),
                                              _("_Cancel"));
        gtk_native_dialog_set_modal (GTK_NATIVE_DIALOG (dialog), TRUE);

        current = gtk_entry_get_text (GTK_ENTRY (entry));
        if (current && current[0])
        {
                dirname = g_path_get_dirname (current);
                if (dirname && dirname[0] && g_file_test (dirname, G_FILE_TEST_IS_DIR))
                        gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), dirname);
                else if (g_file_test (current, G_FILE_TEST_IS_DIR))
                        gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), current);
                g_free (dirname);
        }

        filefilter = gtk_file_filter_new ();
#ifdef WIN32
        gtk_file_filter_add_pattern (filefilter, "*.png");
        gtk_file_filter_add_pattern (filefilter, "*.tiff");
        gtk_file_filter_add_pattern (filefilter, "*.gif");
        gtk_file_filter_add_pattern (filefilter, "*.jpeg");
        gtk_file_filter_add_pattern (filefilter, "*.jpg");
#else
        gtk_file_filter_add_mime_type (filefilter, "image/*");
#endif
        gtk_file_filter_set_name (filefilter, _("Images"));
        gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (dialog), filefilter);

        g_signal_connect (G_OBJECT (dialog), "response",
                          G_CALLBACK (setup_browsefile_response_cb), entry);
        gtk_native_dialog_show (GTK_NATIVE_DIALOG (dialog));
}


static void
setup_fontchooser_response (GtkDialog *dialog, gint response, GtkWidget *entry)
{
        if (response == GTK_RESPONSE_OK)
        {
                char *font_name;

                font_name = gtk_font_chooser_get_font (GTK_FONT_CHOOSER (dialog));
                if (font_name)
                {
                        gtk_entry_set_text (GTK_ENTRY (entry), font_name);
                        g_free (font_name);
                }
        }

        gtk_widget_destroy (GTK_WIDGET (dialog));
        font_dialog = NULL;
}

static void
setup_browsefolder_cb (GtkWidget *button, GtkEntry *entry)
{
        gtkutil_file_req (GTK_WINDOW (setup_window), _("Select Download Folder"), setup_filereq_cb, entry, (char*)gtk_entry_get_text (entry), NULL, FRF_CHOOSEFOLDER|FRF_MODAL);
}

static void
setup_browsefont_cb (GtkWidget *button, GtkWidget *entry)
{
        GtkWidget *dialog;
        const char *font_name;

        dialog = gtk_font_chooser_dialog_new (_("Select font"), GTK_WINDOW (setup_window));
	theme_manager_attach_window (dialog);
        font_dialog = dialog;      /* global var */

        gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);

        font_name = gtk_entry_get_text (GTK_ENTRY (entry));
        if (font_name[0])
                gtk_font_chooser_set_font (GTK_FONT_CHOOSER (dialog), font_name);

        g_signal_connect (G_OBJECT (dialog), "response",
                                                        G_CALLBACK (setup_fontchooser_response), entry);

        gtk_widget_show (dialog);
}

static void
setup_entry_cb (GtkEntry *entry, setting *set)
{
        int size;
        int pos;
        unsigned char *p = (unsigned char*)gtk_entry_get_text (entry);
        size_t len = strlen ((const char *) p);

        /* need to truncate? */
        if (len >= set->extra)
        {
                len = pos = 0;
                while (1)
                {
                        size = g_utf8_skip [*p];
                        len += size;
                        p += size;
                        /* truncate to "set->extra" BYTES */
                        if (len >= set->extra)
                        {
                                gtk_editable_delete_text (GTK_EDITABLE (entry), pos, -1);
                                break;
                        }
                        pos++;
                }
        }
        else
        {
                setup_set_str (&setup_prefs, set, gtk_entry_get_text (entry));
        }
}

static void
setup_create_label (GtkWidget *table, int row, const setting *set)
{
        setup_table_attach (table, setup_create_italic_label (_(set->label)),
                            set->extra ? 1 : 3, 5, row, row + 1, FALSE, FALSE,
                            SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 0);
}

static GtkWidget *
setup_create_entry (GtkWidget *table, int row, const setting *set)
{
        GtkWidget *label;
        GtkWidget *wid, *bwid;

        label = gtk_label_new (_(set->label));
        gtk_widget_set_halign (label, GTK_ALIGN_START);
        gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
        setup_table_attach (table, label, 2, 3, row, row + 1, FALSE, FALSE,
                            SETUP_ALIGN_START, SETUP_ALIGN_CENTER,
                            LABEL_INDENT, 0);

        wid = gtk_entry_new ();
        g_object_set_data (G_OBJECT (wid), "lbl", label);
        if (set->list)
                gtk_entry_set_visibility (GTK_ENTRY (wid), FALSE);
        if (set->tooltip)
                gtk_widget_set_tooltip_text (wid, _(set->tooltip));
        gtk_entry_set_max_length (GTK_ENTRY (wid), set->extra - 1);
        gtk_entry_set_text (GTK_ENTRY (wid), setup_get_str (&setup_prefs, set));
        g_signal_connect (G_OBJECT (wid), "changed",
                                                        G_CALLBACK (setup_entry_cb), (gpointer)set);

        if (set->offset == P_OFFSETNL(hex_net_proxy_user))
                proxy_user = wid;
        if (set->offset == P_OFFSETNL(hex_net_proxy_pass))
                proxy_pass = wid; 

        /* only http and Socks5 can auth */
        if ( (set->offset == P_OFFSETNL(hex_net_proxy_pass) ||
                        set->offset == P_OFFSETNL(hex_net_proxy_user)) &&
             (setup_prefs.hex_net_proxy_type != 4 && setup_prefs.hex_net_proxy_type != 3 && setup_prefs.hex_net_proxy_type != 5) )
                gtk_widget_set_sensitive (wid, FALSE);

        if (set->type == ST_ENTRY)
                setup_table_attach (table, wid, 3, 6, row, row + 1, TRUE, FALSE,
                                    SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 0);
        else
        {
                setup_table_attach (table, wid, 3, 5, row, row + 1, TRUE, FALSE,
                                    SETUP_ALIGN_FILL, SETUP_ALIGN_CENTER, 0, 0);
                bwid = gtk_button_new_with_label (_("Browse..."));
                setup_table_attach (table, bwid, 5, 6, row, row + 1, FALSE, FALSE,
                                    SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 0);
                if (set->type == ST_EFILE)
                        g_signal_connect (G_OBJECT (bwid), "clicked",
                                                                        G_CALLBACK (setup_browsefile_cb), wid);
                if (set->type == ST_EFONT)
                        g_signal_connect (G_OBJECT (bwid), "clicked",
                                                                        G_CALLBACK (setup_browsefont_cb), wid);
                if (set->type == ST_EFOLDER)
                        g_signal_connect (G_OBJECT (bwid), "clicked",
                                                                        G_CALLBACK (setup_browsefolder_cb), wid);
        }

        return wid;
}

static void
setup_create_header (GtkWidget *table, int row, char *labeltext)
{
        GtkWidget *label;
        char buf[128];

        if (row == 0)
                g_snprintf (buf, sizeof (buf), "<b>%s</b>", _(labeltext));
        else
                g_snprintf (buf, sizeof (buf), "\n<b>%s</b>", _(labeltext));

        label = gtk_label_new (NULL);
        gtk_label_set_markup (GTK_LABEL (label), buf);
        gtk_widget_set_halign (label, GTK_ALIGN_START);
        gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
        setup_table_attach (table, label, 0, 4, row, row + 1, FALSE, FALSE,
                            SETUP_ALIGN_START, SETUP_ALIGN_CENTER, 0, 5);
}

static void
setup_create_button (GtkWidget *table, int row, char *label, GCallback callback)
{
        GtkWidget *but = gtk_button_new_with_label (label);
        setup_table_attach (table, but, 2, 3, row, row + 1, FALSE, FALSE,
                            SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 5);
        g_signal_connect (G_OBJECT (but), "clicked", callback, NULL);
}

static GtkWidget *
setup_create_frame (void)
{
        GtkWidget *tab;

        tab = gtkutil_grid_new (3, 2, FALSE);
        gtk_container_set_border_width (GTK_CONTAINER (tab), 6);
        gtk_grid_set_row_spacing (GTK_GRID (tab), 2);
        gtk_grid_set_column_spacing (GTK_GRID (tab), 3);

        return tab;
}

static void
open_data_cb (GtkWidget *button, gpointer data)
{
        fe_open_url (get_xdir ());
}

static GtkWidget *
setup_create_page (const setting *set)
{
        int i, row, do_disable;
        GtkWidget *tab;
        GtkWidget *wid = NULL, *parentwid = NULL;

        tab = setup_create_frame ();
        gtk_container_set_border_width (GTK_CONTAINER (tab), 6);

        i = row = do_disable = 0;
        while (set[i].type != ST_END)
        {
                switch (set[i].type)
                {
                case ST_HEADER:
                        setup_create_header (tab, row, set[i].label);
                        break;
                case ST_EFONT:
                case ST_ENTRY:
                case ST_EFILE:
                case ST_EFOLDER:
                        wid = setup_create_entry (tab, row, &set[i]);
                        break;
                case ST_TOGGLR:
                        row--;
                        setup_create_toggleR (tab, row, &set[i]);
                        break;
                case ST_TOGGLE:
                        wid = setup_create_toggleL (tab, row, &set[i]);
                        if (set[i].extra)
                                do_disable = set[i].extra;
                        break;
                case ST_3OGGLE:
                        setup_create_3oggle (tab, row, &set[i]);
                        break;
                case ST_MENU:
                        setup_create_menu (tab, row, &set[i]);
                        break;
                case ST_RADIO:
                        row += setup_create_radio (tab, row, &set[i]);
                        break;
                case ST_NUMBER:
                        wid = setup_create_spin (tab, row, &set[i]);
                        break;
                case ST_HSCALE:
                        setup_create_hscale (tab, row, &set[i]);
                        break;
                case ST_LABEL:
                        setup_create_label (tab, row, &set[i]);
                        break;
                case ST_ALERTHEAD:
                        setup_create_alert_header (tab, row, &set[i]);
                }

                if (do_disable)
                {
                        if (GTK_IS_WIDGET (parentwid))
                        {
                                g_signal_connect (G_OBJECT (parentwid), "toggled", G_CALLBACK(setup_toggle_sensitive_cb), wid);
                                gtk_widget_set_sensitive (wid, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (parentwid)));
                                do_disable--;
                                if (!do_disable)
                                        parentwid = NULL;
                        }
                        else
                                parentwid = wid;
                }

                i++;
                row++;
        }

        if (set == logging_settings)
        {
                setup_create_button (tab, row, _("Open Data Folder"), G_CALLBACK(open_data_cb));
        }

        return tab;
}

static GtkWidget *
setup_create_theme_page (void)
{
        return theme_preferences_create_page (GTK_WINDOW (setup_window),
                                                    &setup_prefs,
                                                    &color_change);
}

static GtkWidget *
setup_create_appearance_page (void)
{
        GtkWidget *box;
        GtkWidget *appearance_page;
        GtkWidget *theme_label;
        GtkWidget *theme_page;
        GtkWidget *advanced_page;

        box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
        appearance_page = setup_create_page (appearance_settings);
        theme_label = gtk_label_new (NULL);
        theme_page = setup_create_theme_page ();
        advanced_page = setup_create_page (appearance_advanced_settings);

        {
                char *markup = g_markup_printf_escaped ("<b>%s</b>", _("GTK3 Theme"));
                gtk_label_set_markup (GTK_LABEL (theme_label), markup);
                g_free (markup);
        }
        gtk_widget_set_halign (theme_label, GTK_ALIGN_START);
        gtk_widget_set_valign (theme_label, GTK_ALIGN_CENTER);
        gtk_widget_set_margin_start (theme_label, 2);
        gtk_widget_set_margin_end (theme_label, 2);
        gtk_widget_set_margin_top (theme_label, 1);
        gtk_widget_set_margin_bottom (theme_label, 1);

        gtk_box_pack_start (GTK_BOX (box), appearance_page, FALSE, FALSE, 0);
        gtk_box_pack_start (GTK_BOX (box), theme_label, FALSE, FALSE, 2);
        gtk_box_pack_start (GTK_BOX (box), theme_page, FALSE, FALSE, 0);
        gtk_box_pack_start (GTK_BOX (box), advanced_page, FALSE, FALSE, 0);

        return box;
}

/* === GLOBALS for sound GUI === */

static GtkWidget *sndfile_entry;
static int ignore_changed = FALSE;

extern struct text_event te[]; /* text.c */
extern char *sound_files[];

static void
setup_snd_populate (GtkTreeView * treeview)
{
        GtkListStore *store;
        GtkTreeIter iter;
        GtkTreeSelection *sel;
        GtkTreePath *path;
        int i;

        sel = gtk_tree_view_get_selection (treeview);
        store = (GtkListStore *)gtk_tree_view_get_model (treeview);

        for (i = NUM_XP-1; i >= 0; i--)
        {
                gtk_list_store_prepend (store, &iter);
                if (sound_files[i])
                        gtk_list_store_set (store, &iter, 0, te[i].name, 1, sound_files[i], 2, i, -1);
                else
                        gtk_list_store_set (store, &iter, 0, te[i].name, 1, "", 2, i, -1);
                if (i == last_selected_row)
                {
                        gtk_tree_selection_select_iter (sel, &iter);
                        path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
                        if (path)
                        {
                                gtk_tree_view_scroll_to_cell (treeview, path, NULL, TRUE, 0.5, 0.5);
                                gtk_tree_view_set_cursor (treeview, path, NULL, FALSE);
                                gtk_tree_path_free (path);
                        }
                }
        }
}

static int
setup_snd_get_selected (GtkTreeSelection *sel, GtkTreeIter *iter)
{
        int n;
        GtkTreeModel *model;

        if (!gtk_tree_selection_get_selected (sel, &model, iter))
                return -1;

        gtk_tree_model_get (model, iter, 2, &n, -1);
        return n;
}

static void
setup_snd_row_cb (GtkTreeSelection *sel, gpointer user_data)
{
        int n;
        GtkTreeIter iter;

        n = setup_snd_get_selected (sel, &iter);
        if (n == -1)
                return;
        last_selected_row = n;

        ignore_changed = TRUE;
        if (sound_files[n])
                gtk_entry_set_text (GTK_ENTRY (sndfile_entry), sound_files[n]);
        else
                gtk_entry_set_text (GTK_ENTRY (sndfile_entry), "");
        ignore_changed = FALSE;
}

static void
setup_snd_add_columns (GtkTreeView * treeview)
{
        GtkCellRenderer *renderer;
        GtkTreeModel *model;

        /* event column */
        renderer = gtk_cell_renderer_text_new ();
        gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
                                                                                                                                -1, _("Event"), renderer,
                                                                                                                                "text", 0, NULL);

        /* file column */
        renderer = gtk_cell_renderer_text_new ();
        gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
                                                                                                                                -1, _("Sound file"), renderer,
                                                                                                                                "text", 1, NULL);

        model = GTK_TREE_MODEL (gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT));
        gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), model);
        g_object_unref (model);
}

static void
setup_snd_filereq_cb (GtkWidget *entry, char *file)
{
        if (file)
        {
                if (file[0])
                {
                        /* Use just the filename if the given sound file is in the default <config>/sounds directory.
                         * We're comparing absolute paths so this won't work in portable mode which uses a relative path.
                         */
                        if (!strcmp (g_path_get_dirname (file), g_build_filename (get_xdir (), ZOITECHAT_SOUND_DIR, NULL)))
                        {
                                gtk_entry_set_text (GTK_ENTRY (entry), g_path_get_basename (file));
                        }
                        else
                        {
                                gtk_entry_set_text (GTK_ENTRY (entry), file);
                        }
                }
        }
}

static void
setup_snd_browse_cb (GtkWidget *button, GtkEntry *entry)
{
        char *sounds_dir = g_build_filename (get_xdir (), ZOITECHAT_SOUND_DIR, NULL);
        char *filter = NULL;
        int filter_type;
#ifdef WIN32 /* win32 only supports wav, others could support anything */
        filter = "*.wav";
        filter_type = FRF_EXTENSIONS;
#else
        filter = "audio/*";
        filter_type = FRF_MIMETYPES;
#endif

        gtkutil_file_req (GTK_WINDOW (setup_window), _("Select a sound file"), setup_snd_filereq_cb, entry,
                                                sounds_dir, filter, FRF_MODAL|FRF_FILTERISINITIAL|filter_type);
        g_free (sounds_dir);
}

static void
setup_snd_play_cb (GtkWidget *button, GtkEntry *entry)
{
        sound_play (gtk_entry_get_text (entry), FALSE);
}

static void
setup_snd_changed_cb (GtkEntry *ent, GtkTreeView *tree)
{
        int n;
        GtkTreeIter iter;
        GtkListStore *store;
        GtkTreeSelection *sel;

        if (ignore_changed)
                return;

        sel = gtk_tree_view_get_selection (tree);
        n = setup_snd_get_selected (sel, &iter);
        if (n == -1)
                return;

        /* get the new sound file */
        g_free (sound_files[n]);
        sound_files[n] = g_strdup (gtk_entry_get_text (GTK_ENTRY (ent)));

        /* update the TreeView list */
        store = (GtkListStore *)gtk_tree_view_get_model (tree);
        gtk_list_store_set (store, &iter, 1, sound_files[n], -1);

        gtk_widget_set_sensitive (cancel_button, FALSE);
}

static GtkWidget *
setup_create_sound_page (void)
{
        GtkWidget *vbox1;
        GtkWidget *vbox2;
        GtkWidget *scrolledwindow1;
        GtkWidget *sound_tree;
        GtkWidget *table1;
        GtkWidget *sound_label;
        GtkWidget *sound_browse;
        GtkWidget *sound_play;
        GtkTreeSelection *sel;

        vbox1 = gtkutil_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
        gtk_container_set_border_width (GTK_CONTAINER (vbox1), 6);
        gtk_widget_show (vbox1);

        vbox2 = gtkutil_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
        gtk_widget_show (vbox2);
        gtk_box_pack_start (GTK_BOX (vbox1), vbox2, TRUE, TRUE, 0);

        scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL);
        gtk_widget_show (scrolledwindow1);
        gtk_box_pack_start (GTK_BOX (vbox2), scrolledwindow1, TRUE, TRUE, 0);
        gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow1),
                                                                                          GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
        gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow1),
                                                                                                         GTK_SHADOW_IN);

        sound_tree = gtk_tree_view_new ();
        sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (sound_tree));
        gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE);
        setup_snd_add_columns (GTK_TREE_VIEW (sound_tree));
        setup_snd_populate (GTK_TREE_VIEW (sound_tree));
        g_signal_connect (G_OBJECT (sel), "changed",
                                                        G_CALLBACK (setup_snd_row_cb), NULL);
        gtk_widget_show (sound_tree);
        gtk_container_add (GTK_CONTAINER (scrolledwindow1), sound_tree);
        gtk_tree_view_set_grid_lines (GTK_TREE_VIEW (sound_tree), GTK_TREE_VIEW_GRID_LINES_HORIZONTAL);

        table1 = gtkutil_grid_new (2, 3, FALSE);
        gtk_widget_show (table1);
        gtk_box_pack_start (GTK_BOX (vbox2), table1, FALSE, TRUE, 8);
        gtk_grid_set_row_spacing (GTK_GRID (table1), 2);
        gtk_grid_set_column_spacing (GTK_GRID (table1), 4);

        sound_label = gtk_label_new_with_mnemonic (_("Sound file:"));
        gtk_widget_show (sound_label);
        setup_table_attach (table1, sound_label, 0, 1, 0, 1, FALSE, FALSE,
                            SETUP_ALIGN_START, SETUP_ALIGN_CENTER, 0, 0);

        sndfile_entry = gtk_entry_new ();
        g_signal_connect (G_OBJECT (sndfile_entry), "changed",
                                                        G_CALLBACK (setup_snd_changed_cb), sound_tree);
        gtk_widget_show (sndfile_entry);
        setup_table_attach (table1, sndfile_entry, 0, 1, 1, 2, TRUE, FALSE,
                            SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 0);

        sound_browse = gtk_button_new_with_mnemonic (_("_Browse..."));
        g_signal_connect (G_OBJECT (sound_browse), "clicked",
                                                        G_CALLBACK (setup_snd_browse_cb), sndfile_entry);
        gtk_widget_show (sound_browse);
        setup_table_attach (table1, sound_browse, 1, 2, 1, 2, FALSE, FALSE,
                            SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 0);

        sound_play = gtkutil_button_new_from_stock ("gtk-media-play", _("_Play"));
        g_signal_connect (G_OBJECT (sound_play), "clicked",
                                                        G_CALLBACK (setup_snd_play_cb), sndfile_entry);
        gtk_widget_show (sound_play);
        setup_table_attach (table1, sound_play, 2, 3, 1, 2, FALSE, FALSE,
                            SETUP_ALIGN_FILL, SETUP_ALIGN_FILL, 0, 0);

        setup_snd_row_cb (sel, NULL);

        return vbox1;
}

static GtkWidget *
setup_page_from_settings (const setting *settings)
{
        return setup_create_page (settings);
}

static GtkWidget *
setup_page_from_appearance (const setting *settings)
{
        (void)settings;
        return setup_create_appearance_page ();
}

static GtkWidget *
setup_page_from_sound (const setting *settings)
{
        (void)settings;
        return setup_create_sound_page ();
}

static void
setup_add_page (const char *title, GtkWidget *book, GtkWidget *tab)
{
        GtkWidget *label, *vvbox, *viewport;
        GtkScrolledWindow *sw;
        char buf[128];

        vvbox = gtkutil_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);

        /* label */
        label = gtk_label_new (NULL);
        g_snprintf (buf, sizeof (buf), "<b><big>%s</big></b>", _(title));
        gtk_label_set_markup (GTK_LABEL (label), buf);
        gtk_widget_set_halign (label, GTK_ALIGN_START);
        gtk_widget_set_valign (label, GTK_ALIGN_CENTER);
        gtk_widget_set_margin_start (label, 2);
        gtk_widget_set_margin_end (label, 2);
        gtk_widget_set_margin_top (label, 1);
        gtk_widget_set_margin_bottom (label, 1);
        gtk_box_pack_start (GTK_BOX (vvbox), label, FALSE, FALSE, 2);

        gtk_box_pack_start (GTK_BOX (vvbox), tab, TRUE, TRUE, 0);

        sw = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new (NULL, NULL));
        gtk_scrolled_window_set_shadow_type (sw, GTK_SHADOW_IN);
        gtk_scrolled_window_set_policy (sw, GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
        gtk_container_add (GTK_CONTAINER (sw), vvbox);

        viewport = gtk_bin_get_child (GTK_BIN(sw));
        gtk_viewport_set_shadow_type (GTK_VIEWPORT(viewport), GTK_SHADOW_NONE);

        gtk_notebook_append_page (GTK_NOTEBOOK (book), GTK_WIDGET(sw), NULL);
}

static void
setup_register_page (const char *title, GtkWidget *book, setup_page_factory factory, const setting *settings)
{
        GtkWidget *container;

        if (setup_page_count >= SETUP_MAX_PAGES)
                return;

        setup_page_factories[setup_page_count] = factory;
        setup_page_settings[setup_page_count] = settings;
        container = gtkutil_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
        setup_page_containers[setup_page_count] = container;
        setup_add_page (title, book, container);
        setup_page_count++;
}

static void
setup_ensure_page_created (int page)
{
        GList *children;
        GtkWidget *page_widget;
        GtkWidget *container;

        if (page < 0 || page >= setup_page_count)
                return;

        container = setup_page_containers[page];
        children = gtk_container_get_children (GTK_CONTAINER (container));
        if (children != NULL)
        {
                g_list_free (children);
                return;
        }

        page_widget = setup_page_factories[page] (setup_page_settings[page]);
        gtk_container_add (GTK_CONTAINER (container), page_widget);
        gtk_widget_show_all (container);
}

static const char *const cata_interface[] =
{
        N_("Appearance"),
        N_("Input box"),
        N_("User list"),
        N_("Channel switcher"),
        NULL
};

static const char *const cata_chatting[] =
{
        N_("General"),
        N_("Alerts"),
        N_("Sounds"),
        N_("Logging"),
        N_("Advanced"),
        NULL
};

static const char *const cata_network[] =
{
        N_("Network setup"),
        N_("File transfers"),
        N_("Identd"),
        NULL
};

static GtkWidget *
setup_create_pages (GtkWidget *box)
{
        GtkWidget *book;
        GtkWindow *win = GTK_WINDOW (setup_window);

        (void)box;
        book = gtk_notebook_new ();

        memset (setup_page_factories, 0, sizeof (setup_page_factories));
        memset (setup_page_settings, 0, sizeof (setup_page_settings));
        memset (setup_page_containers, 0, sizeof (setup_page_containers));
        setup_page_count = 0;

        setup_register_page (cata_interface[0], book, setup_page_from_appearance, NULL);
        setup_register_page (cata_interface[1], book, setup_page_from_settings, inputbox_settings);
        setup_register_page (cata_interface[2], book, setup_page_from_settings, userlist_settings);
        setup_register_page (cata_interface[3], book, setup_page_from_settings, tabs_settings);

        setup_register_page (cata_chatting[0], book, setup_page_from_settings, general_settings);

        if (!gtkutil_tray_icon_supported (win) && !notification_backend_supported ())
        {
                setup_register_page (cata_chatting[1], book, setup_page_from_settings, alert_settings_unityandnonotifications);
        }
        else if (!gtkutil_tray_icon_supported (win))
        {
                setup_register_page (cata_chatting[1], book, setup_page_from_settings, alert_settings_unity);
        }
        else if (!notification_backend_supported ())
        {
                setup_register_page (cata_chatting[1], book, setup_page_from_settings, alert_settings_nonotifications);
        }
        else
        {
                setup_register_page (cata_chatting[1], book, setup_page_from_settings, alert_settings);
        }

        setup_register_page (cata_chatting[2], book, setup_page_from_sound, NULL);
        setup_register_page (cata_chatting[3], book, setup_page_from_settings, logging_settings);
        setup_register_page (cata_chatting[4], book, setup_page_from_settings, advanced_settings);

        setup_register_page (cata_network[0], book, setup_page_from_settings, network_settings);
        setup_register_page (cata_network[1], book, setup_page_from_settings, filexfer_settings);
        setup_register_page (cata_network[2], book, setup_page_from_settings, identd_settings);

        gtk_notebook_set_show_tabs (GTK_NOTEBOOK (book), FALSE);
        gtk_notebook_set_show_border (GTK_NOTEBOOK (book), FALSE);
        gtk_box_pack_start (GTK_BOX (box), book, TRUE, TRUE, 0);

        return book;
}

static void
setup_tree_cb (GtkTreeView *treeview, GtkWidget *book)
{
        GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
        GtkTreeIter iter;
        GtkTreeModel *model;
        int page;

        if (gtk_tree_selection_get_selected (selection, &model, &iter))
        {
                gtk_tree_model_get (model, &iter, 1, &page, -1);
                if (page != -1)
                {
                        setup_ensure_page_created (page);
                        gtk_notebook_set_current_page (GTK_NOTEBOOK (book), page);
                        last_selected_page = page;
                }
        }
}

static gboolean
setup_tree_select_filter (GtkTreeSelection *selection, GtkTreeModel *model,
                                                                  GtkTreePath *path, gboolean path_selected,
                                                                  gpointer data)
{
        if (gtk_tree_path_get_depth (path) > 1)
                return TRUE;
        return FALSE;
}

static void
setup_create_tree (GtkWidget *box, GtkWidget *book)
{
        GtkWidget *tree;
        GtkWidget *frame;
        GtkTreeStore *model;
        GtkTreeIter iter;
        GtkTreeIter child_iter;
        GtkTreeIter *sel_iter = NULL;
        GtkCellRenderer *renderer;
        GtkTreeSelection *sel;
        int i, page;

        model = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_INT);

        page = 0;

        gtk_tree_store_append (model, &iter, NULL);
        gtk_tree_store_set (model, &iter, 0, _("Interface"), 1, -1, -1);
        for (i = 0; cata_interface[i]; i++)
        {
                gtk_tree_store_append (model, &child_iter, &iter);
                gtk_tree_store_set (model, &child_iter, 0, _(cata_interface[i]), 1, page, -1);
                if (page == last_selected_page)
                        sel_iter = gtk_tree_iter_copy (&child_iter);
                page++;
        }

        gtk_tree_store_append (model, &iter, NULL);
        gtk_tree_store_set (model, &iter, 0, _("Chatting"), 1, -1, -1);
        for (i = 0; cata_chatting[i]; i++)
        {
                gtk_tree_store_append (model, &child_iter, &iter);
                gtk_tree_store_set (model, &child_iter, 0, _(cata_chatting[i]), 1, page, -1);
                if (page == last_selected_page)
                        sel_iter = gtk_tree_iter_copy (&child_iter);
                page++;
        }

        gtk_tree_store_append (model, &iter, NULL);
        gtk_tree_store_set (model, &iter, 0, _("Network"), 1, -1, -1);
        for (i = 0; cata_network[i]; i++)
        {
                gtk_tree_store_append (model, &child_iter, &iter);
                gtk_tree_store_set (model, &child_iter, 0, _(cata_network[i]), 1, page, -1);
                if (page == last_selected_page)
                        sel_iter = gtk_tree_iter_copy (&child_iter);
                page++;
        }

        tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
        g_object_unref (G_OBJECT (model));
        sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
        gtk_tree_selection_set_mode (sel, GTK_SELECTION_BROWSE);
        gtk_tree_selection_set_select_function (sel, setup_tree_select_filter,
                                                                                                                 NULL, NULL);
        g_signal_connect (G_OBJECT (tree), "cursor-changed",
                                                        G_CALLBACK (setup_tree_cb), book);

        renderer = gtk_cell_renderer_text_new ();
        gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree),
                                                            -1, _("Categories"), renderer, "text", 0, NULL);
        gtk_tree_view_expand_all (GTK_TREE_VIEW (tree));

        frame = gtk_frame_new (NULL);
        gtk_container_add (GTK_CONTAINER (frame), tree);
        gtk_box_pack_start (GTK_BOX (box), frame, 0, 0, 0);
        gtk_box_reorder_child (GTK_BOX (box), frame, 0);

        if (sel_iter)
        {
                gtk_tree_selection_select_iter (sel, sel_iter);
                gtk_tree_iter_free (sel_iter);
                setup_tree_cb (GTK_TREE_VIEW (tree), book);
        }
}

static void
setup_apply_to_sess (session_gui *gui)
{
        mg_update_xtext (gui->xtext);

        mg_apply_session_font_prefs (gui);

        if (prefs.hex_gui_ulist_buttons)
                gtk_widget_show (gui->button_box);
        else
                gtk_widget_hide (gui->button_box);

        /* update active languages */
        sexy_spell_entry_deactivate_language((SexySpellEntry *)gui->input_box,NULL);
        sexy_spell_entry_activate_default_languages((SexySpellEntry *)gui->input_box);

        sexy_spell_entry_set_checked ((SexySpellEntry *)gui->input_box, prefs.hex_gui_input_spell);
        sexy_spell_entry_set_parse_attributes ((SexySpellEntry *)gui->input_box, prefs.hex_gui_input_attr);
}

static void
unslash (char *dir)
{
        if (dir[0])
        {
                size_t len = strlen (dir) - 1;
#ifdef WIN32
                if (dir[len] == '/' || dir[len] == '\\')
#else
                if (dir[len] == '/')
#endif
                        dir[len] = 0;
        }
}

void
setup_apply_real (const ThemeChangedEvent *event)
{
        GSList *list;
        session *sess;
        int done_main = FALSE;

        /* remove trailing slashes */
        unslash (prefs.hex_dcc_dir);
        unslash (prefs.hex_dcc_completed_dir);

        g_mkdir (prefs.hex_dcc_dir, 0700);
        g_mkdir (prefs.hex_dcc_completed_dir, 0700);

        if (theme_changed_event_has_reason (event, THEME_CHANGED_REASON_PIXMAP))
        {
                if (channelwin_pix)
                        cairo_surface_destroy (channelwin_pix);
                channelwin_pix = pixmap_load_from_file (prefs.hex_text_background);
        }

        theme_manager_reload_input_style ();

        list = sess_list;
        while (list)
        {
                sess = list->data;
                if (sess->gui->is_tab)
                {
                        /* only apply to main tabwindow once */
                        if (!done_main)
                        {
                                done_main = TRUE;
                                setup_apply_to_sess (sess->gui);
                        }
                } else
                {
                        setup_apply_to_sess (sess->gui);
                }

                log_open_or_close (sess);

                if (theme_changed_event_has_reason (event, THEME_CHANGED_REASON_USERLIST))
                        userlist_rehash (sess);

                list = list->next;
        }

        mg_apply_setup ();
        menu_update_quit_accel ();
        tray_apply_setup ();
        zoitechat_reinit_timers ();

        if (theme_changed_event_has_reason (event, THEME_CHANGED_REASON_LAYOUT))
                menu_change_layout ();

        if (theme_changed_event_has_reason (event, THEME_CHANGED_REASON_IDENTD))
                handle_command (current_sess, "IDENTD reload", FALSE);
}

static void
setup_apply (struct zoitechatprefs *pr)
{
#ifdef WIN32
        PangoFontDescription *old_desc;
        PangoFontDescription *new_desc;
        char buffer[4 * FONTNAMELEN + 1];
#endif
	int noapply = FALSE;
	ThemeChangedEvent event;
	struct zoitechatprefs old_prefs = prefs;
	int old_dark_mode = prefs.hex_gui_dark_mode;
	mg_live_prefs live_changes;

	memset (&live_changes, 0, sizeof (live_changes));

#define DIFF(a) (pr->a != prefs.a)

#ifdef WIN32
        if (DIFF (hex_gui_lang))
                noapply = TRUE;
#endif
        /* server tabs are only created when a connection is opened, so
           existing connections keep their current tabs until a restart;
           the channel tree indentation is still re-applied below */
        if (DIFF (hex_gui_tab_server))
        {
                noapply = TRUE;
                live_changes.chanview = TRUE;
        }

        if (DIFF (hex_gui_compact))
        {
                live_changes.chanview = TRUE;
                live_changes.ulist_columns = TRUE;
        }
        if (DIFF (hex_gui_input_icon) || DIFF (hex_gui_input_nick))
                live_changes.input_box = TRUE;
        if (DIFF (hex_gui_lagometer) || DIFF (hex_gui_throttlemeter))
                live_changes.meters = TRUE;
        if (DIFF (hex_gui_mode_buttons_inline) || DIFF (hex_gui_topicbar_multiline))
                live_changes.topic_bar = TRUE;
        if (DIFF (hex_gui_tab_icons) || DIFF (hex_gui_tab_closebuttons) ||
                 DIFF (hex_gui_tab_small))
                live_changes.chanview = TRUE;
        if (DIFF (hex_gui_tab_sort))
                live_changes.tab_resort = TRUE;
        if (DIFF (hex_gui_tab_trunc))
                live_changes.tab_trunc = TRUE;
        if (DIFF (hex_gui_ulist_count))
                live_changes.ulist_count = TRUE;
        if (DIFF (hex_gui_ulist_icons))
                live_changes.ulist_rows = TRUE;
        if (DIFF (hex_gui_ulist_show_hosts))
        {
                live_changes.ulist_columns = TRUE;
                live_changes.ulist_rows = TRUE; /* hostnames may be stale */
        }
        if (DIFF (hex_gui_ulist_sort))
                live_changes.ulist_sort = TRUE;
        /* compare against the pre-dialog value: the slider previews the
           opacity live on the current window only, so OK still needs to
           apply it to the other windows */
        if (pr->hex_gui_transparency != setup_transparency_snapshot)
                live_changes.transparency = TRUE;

        if ((pr->hex_gui_tab_pos == 5 || pr->hex_gui_tab_pos == 6) &&
                 pr->hex_gui_tab_layout == 2 && pr->hex_gui_tab_pos != prefs.hex_gui_tab_pos)
                fe_message (_("You cannot place the tree on the top or bottom!\n"
                                                "Please change to the <b>Tabs</b> layout in the <b>View</b>"
                                                " menu first."),
                                                FE_MSG_WARN | FE_MSG_MARKUP);

        /* format cannot be blank, there is already a setting for this */
        if (pr->hex_stamp_text_format[0] == 0)
        {
                pr->hex_stamp_text = 0;
                strcpy (pr->hex_stamp_text_format, prefs.hex_stamp_text_format);
        }

        memcpy (&prefs, pr, sizeof (prefs));

	event = theme_manager_on_preferences_changed (&old_prefs, &prefs, old_dark_mode, &color_change);

#ifdef WIN32
        /* merge hex_font_main and hex_font_alternative into hex_font_normal */
        old_desc = pango_font_description_from_string (prefs.hex_text_font_main);
        sprintf (buffer, "%s,%s", pango_font_description_get_family (old_desc), prefs.hex_text_font_alternative);
        new_desc = pango_font_description_from_string (buffer);
        pango_font_description_set_weight (new_desc, pango_font_description_get_weight (old_desc));
        pango_font_description_set_style (new_desc, pango_font_description_get_style (old_desc));
        pango_font_description_set_size (new_desc, pango_font_description_get_size (old_desc));
        sprintf (prefs.hex_text_font, "%s", pango_font_description_to_string (new_desc));

        /* FIXME this is not required after pango_font_description_from_string()
        g_free (old_desc);
        g_free (new_desc);
        */
#endif

        if (prefs.hex_irc_real_name[0] == 0)
        {
                fe_message (_("The Real name option cannot be left blank. Falling back to \"realname\"."), FE_MSG_WARN);
                strcpy (prefs.hex_irc_real_name, "realname");
        }

	theme_manager_dispatch_setup_apply (&event);

	mg_apply_live_prefs (&live_changes);

        if (noapply)
                fe_message (_("Some settings were changed that require a"
                                                " restart to take full effect."), FE_MSG_WARN);

#ifndef WIN32
        if (prefs.hex_dcc_auto_recv == 2) /* Auto */
        {
                if (!strcmp ((char *)g_get_home_dir (), prefs.hex_dcc_dir))
                {
                        fe_message (_("*WARNING*\n"
                                                         "Auto accepting DCC to your home directory\n"
                                                         "can be dangerous and is exploitable. Eg:\n"
                                                         "Someone could send you a .bash_profile"), FE_MSG_WARN);
                }
        }
#endif
}

static void
setup_ok_cb (GtkWidget *but, GtkWidget *win)
{
        PreferencesPersistenceResult save_result;
        struct zoitechatprefs old_prefs;
        char buffer[192];

        memcpy (&old_prefs, &prefs, sizeof (prefs));
        setup_apply (&setup_prefs);
        theme_preferences_stage_apply ();
        save_result = preferences_persistence_save_all ();
        if (save_result.success)
        {
                theme_preferences_stage_commit ();
                /* the previewed values were accepted, so the close handler
                   must not revert them */
                setup_transparency_snapshot = prefs.hex_gui_transparency;
                gtk_widget_destroy (win);
                return;
        }

        memcpy (&prefs, &old_prefs, sizeof (prefs));
        theme_preferences_stage_discard ();

        if (save_result.partial_failure)
        {
                fe_message (_("Preferences were partially saved. zoitechat.conf succeeded, colors.conf failed. Retry is possible."), FE_MSG_ERROR);
                return;
        }

        g_snprintf (buffer, sizeof (buffer), _("Could not save preferences (%s). Retry is possible."), save_result.failed_file ? save_result.failed_file : _("unknown file"));
        fe_message (buffer, FE_MSG_ERROR);
}

static GtkWidget *
setup_window_open (void)
{
        GtkWidget *wid, *win, *vbox, *hbox, *hbbox;
        char buf[128];

        g_snprintf(buf, sizeof(buf), _("Preferences - %s"), _(DISPLAY_NAME));
        win = gtkutil_window_new (buf, "prefs", 0, 600, 2);
        setup_window = win;

        vbox = gtkutil_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 5);
        gtk_container_set_border_width (GTK_CONTAINER (vbox), 6);
        gtk_container_add (GTK_CONTAINER (win), vbox);

        hbox = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 4);
        gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);

        setup_create_tree (hbox, setup_create_pages (hbox));

        /* prepare the button box */
        hbbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
        gtk_button_box_set_layout (GTK_BUTTON_BOX (hbbox), GTK_BUTTONBOX_END);
        gtk_box_set_spacing (GTK_BOX (hbbox), 4);
        gtk_box_pack_end (GTK_BOX (vbox), hbbox, FALSE, FALSE, 0);

        cancel_button = wid = gtkutil_button_new_from_stock ("gtk-cancel", _("_Cancel"));
        g_signal_connect (G_OBJECT (wid), "clicked",
                                                        G_CALLBACK (gtkutil_destroy), win);
        gtk_box_pack_start (GTK_BOX (hbbox), wid, FALSE, FALSE, 0);

        wid = gtkutil_button_new_from_stock ("gtk-ok", _("_OK"));
        g_signal_connect (G_OBJECT (wid), "clicked",
                                                        G_CALLBACK (setup_ok_cb), win);
        gtk_box_pack_start (GTK_BOX (hbbox), wid, FALSE, FALSE, 0);

        gtk_widget_show_all (win);

        return win;
}

static void
setup_close_cb (GtkWidget *win, GtkWidget **swin)
{
        *swin = NULL;

        /* drop any pending transparency preview and undo the previewed
           opacity when the dialog is cancelled; after OK the snapshot
           was updated so this is a no-op */
        if (setup_trans_apply_tag)
        {
                g_source_remove (setup_trans_apply_tag);
                setup_trans_apply_tag = 0;
        }
        if (prefs.hex_gui_transparency != setup_transparency_snapshot)
        {
                mg_live_prefs revert_changes;

                prefs.hex_gui_transparency = setup_transparency_snapshot;
                memset (&revert_changes, 0, sizeof (revert_changes));
                revert_changes.transparency = TRUE;
                mg_apply_live_prefs (&revert_changes);
        }

        theme_preferences_stage_discard ();

        if (font_dialog)
        {
                gtk_widget_destroy (font_dialog);
                font_dialog = NULL;
        }
}

void
setup_open (void)
{
        if (setup_window)
        {
                gtk_window_present (GTK_WINDOW (setup_window));
                return;
        }

        memcpy (&setup_prefs, &prefs, sizeof (prefs));

        color_change = FALSE;
        setup_transparency_snapshot = prefs.hex_gui_transparency;
        theme_preferences_stage_begin ();
        setup_window = setup_window_open ();

        g_signal_connect (G_OBJECT (setup_window), "destroy",
                                                        G_CALLBACK (setup_close_cb), &setup_window);
}
