char *rcsid_gtk_config_c = "$Id: config.c,v 1.21 2005/05/30 21:01:57 ryo_saeba Exp $"; /* Crossfire client, a client program for the crossfire program. Copyright (C) 2001 Mark Wedel & Crossfire Development Team 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., 675 Mass Ave, Cambridge, MA 02139, USA. The author can be reached via e-mail to crossfire-devel@real-time.com */ /* This file handles the loading and saving of the configuration options, * as well as presenting a nice gui to select the * options */ #include "config.h" #ifdef __CYGWIN__ #include #endif /* gtk */ #include #ifndef WIN32 #include #else #include #endif #include /* always include our local headers after the system headers are included */ #include "client.h" /*#include "clientbmap.h"*/ #include "item.h" #include "gx11.h" #include "gtkproto.h" #include /* Abstract this out a bit - rather than have a whole bunch of * duplicated code that generates these values, instead use * pointers to functions that set and get the values - this * makes adding new widgets a lot easier in most cases. * * button is the actual widget that is created. * * label is the label that is to be printed * * type if the type of widget. As long as the widget only * needs to deal with numeric type values, this works * fine - this means that dials can get just as easily * added to this list * * config holds the corresponding CONFIG value from * the common/client.h file. This allows * use to use a fairly common function for most * values. * * flags holds flags. Currently, the * only flag is FLAG_UPDATE - if set, * then we automatically the running total * immediately. Otherwise, the want_config value * only gets updated, and depending on the value, * that value may get used/copied into the use_config * at a later point. Many values which can change at * runtime do not have this flag set simply so that it * is easier to notice if it has changed and do the * appropraite thing (eg, stop/start sound daemon, etc) * * Note on RBUTTON (radio button usage): Since a radio * button is a collection of buttons of only which one can * be pressed, the logic the program uses is this: * 1) If the previous widget was a radio button, we add * this one to the same group. This means if you want to * have multiple sets of radio buttons, you should seperate * them with something. * 2) Since the radio button is several widgets, its not as simple * as normal buttons to map them to a config value. Instead, * use a range so that it is easy to tell what config value * your button belongs to, ie, 100-199 is for the lighting * options, 200-299 is for the resistance options, etc. */ #define MAX_BUTTONS 36 #define RBUTTON 1 #define CBUTTON 2 #define SEPERATOR 3 /* Seperator in the window */ #define SPIN 0x100 #define SPIN_SCALE 0x101 /* Spin Button that is image scale */ #define SPIN_MAP 0x102 /* Spin button that is map size */ #define SPIN_CWINDOW 0x103 /* Spin command window */ #define FLAG_UPDATE 0x1 #define FLAG_MAPPANE 0x2 /* Display on the map/image pane */ typedef struct { GtkWidget *widget; int type; int config; int flags; char *label; } CButtons; static GtkWidget *gtkwin_config = NULL, /* main window */ *faceset_combo; /* Combo box for faceset selection */ /* A dispatch table that can deal with the entire selection of * config gui elements. */ CButtons cbuttons[MAX_BUTTONS] = { {NULL, CBUTTON, CONFIG_FOODBEEP, FLAG_UPDATE, "Beep When Food is Low"}, {NULL, CBUTTON, CONFIG_COLORINV, 0, "Colored Inventory Lists"}, {NULL, CBUTTON, CONFIG_COLORTXT, FLAG_UPDATE, "Colored Information Text"}, {NULL, SPIN_CWINDOW, CONFIG_CWINDOW, FLAG_UPDATE, "Command Window"}, {NULL, CBUTTON, CONFIG_ECHO, FLAG_UPDATE, "Echo Bound Commands"}, {NULL, CBUTTON, CONFIG_FASTTCP, 0, "Fast TCP Send (May improve performance at expense\n of outgoing bandwidth)"}, {NULL, CBUTTON, CONFIG_GRAD_COLOR, FLAG_UPDATE, "Gradually change stat bar color based on value of the stat.\nThis option will result in some extra CPU usage."}, {NULL, CBUTTON, CONFIG_POPUPS, FLAG_UPDATE, "Popup Windows"}, {NULL, CBUTTON, CONFIG_SPLASH, FLAG_UPDATE, "Splash Window"}, {NULL, CBUTTON, CONFIG_SHOWICON, FLAG_UPDATE, "Show Inventory Icon"}, {NULL, CBUTTON, CONFIG_TOOLTIPS, 0, "Show Tooltips"}, {NULL, CBUTTON, CONFIG_SOUND, 0, "Sound"}, {NULL, CBUTTON, CONFIG_SPLITINFO, 0, "Split Information Window (Takes effect next run)"}, {NULL, CBUTTON, CONFIG_SPLITWIN, 0, "Split Windows"}, {NULL, CBUTTON, CONFIG_TRIMINFO, FLAG_UPDATE, "Trims text in the information window - " /**/ "improves performance but bugs in\n gtk make the client unstable if this is used." /**/ "This may work better with gtk 2.0"}, {NULL, CBUTTON, CONFIG_APPLY_CONTAINER, FLAG_UPDATE, "Automatically re-applies a container when you use apply to close it. If off, when you use apply to close the container, it stays unapplied"}, {NULL, SEPERATOR, 0, 0, "Options on how to display resistances:"}, {NULL, RBUTTON, 200, 0, "Don't use a scrollbar, will show a maximum of 7 resistances."}, {NULL, RBUTTON, 201, 0, "Display all resistances in a single column, will use a single scrollbar."}, {NULL, RBUTTON, 202, 0, "Display all resistances in the form of a double column, may result in one\nor two scrollbars being created"}, /* The following items are shown in the map tag. * I grouped them together to make reading them a bit easier, * but in fact, they could be intermixed with the other * options. */ {NULL, CBUTTON, CONFIG_CACHE, FLAG_MAPPANE, "Cache Images"}, {NULL, CBUTTON, CONFIG_DOWNLOAD, FLAG_MAPPANE | FLAG_UPDATE, "Download All Image Information (Takes effect on next server connection)"}, {NULL, CBUTTON, CONFIG_FOGWAR, FLAG_MAPPANE | FLAG_UPDATE, "Fog of War"}, {NULL, SPIN_SCALE, CONFIG_ICONSCALE, FLAG_MAPPANE, "Icon Scale (Takes effect next run)"}, {NULL, SPIN_SCALE, CONFIG_MAPSCALE, FLAG_MAPPANE, "Map Scale (Takes effect next run)"}, {NULL, CBUTTON, CONFIG_SMOOTH, FLAG_MAPPANE | FLAG_UPDATE, "Enable smoothing - Use additionnal CPU (Take effect on next connection)."}, {NULL, CBUTTON, CONFIG_DISPLAYMODE, FLAG_MAPPANE, "SDL Image Support (Take effect next run)"}, {NULL, CBUTTON, CONFIG_SHOWGRID, FLAG_MAPPANE | FLAG_UPDATE, "Print Grid Overlay (SDL only, Slow, useful for debugging/development"}, {NULL, SEPERATOR, 0, FLAG_MAPPANE, "Lighting options, per pixel is prettier, per tile is faster.\nIf the darkness code is off, the pixel/tile options will be ignored."}, {NULL, RBUTTON, 100 + CFG_LT_PIXEL_BEST, FLAG_MAPPANE, "Best Per Pixel Lighting (slowest)"}, {NULL, RBUTTON, 100 + CFG_LT_PIXEL, FLAG_MAPPANE, "Fast Per Pixel Lighting"}, {NULL, RBUTTON, 100 + CFG_LT_TILE, FLAG_MAPPANE, "Per Tile Lighting"}, {NULL, CBUTTON, CONFIG_DARKNESS, FLAG_MAPPANE | FLAG_UPDATE, "Enable darkness code - if off, all spaces will not be dimmed."}, {NULL, SEPERATOR, 0, FLAG_MAPPANE, "Map Size: Larger map lets you see more information, but takes more CPU\npower and bandwidth. Changing these will not take effect until the next time\nyou connect to a server"}, {NULL, SPIN_MAP, CONFIG_MAPHEIGHT, FLAG_MAPPANE, "Map Height"}, {NULL, SPIN_MAP, CONFIG_MAPWIDTH, FLAG_MAPPANE, "Map Width"}, }; static void set_config_value(int cval, int value) { want_config[cbuttons[cval].config] = value; if (cbuttons[cval].flags & FLAG_UPDATE) use_config[cbuttons[cval].config] = value; } static void toggle_splitwin(int newval) { inventory_splitwin_toggling(); gtk_widget_destroy(gtkwin_root); if (newval) { ; /* Currently don't have it, but want splitwindows */ } else { /* opposite - do have it, but don't want it */ gtk_widget_destroy(gtkwin_info); gtk_widget_destroy(gtkwin_stats); gtk_widget_destroy(gtkwin_message); gtk_widget_destroy(gtkwin_inv); gtk_widget_destroy(gtkwin_look); } create_windows(); display_map_doneupdate(TRUE); draw_stats (1); update_list_labels(&inv_list); /* After exploding or unexploding client, redraw weight labels. */ update_list_labels(&look_list); } /* Ok, here it sets the config and saves it. This is sorta dangerous, and I'm not sure * if it's actually possible to do dynamic reconfiguration of everything this way. Something may * blow up in our faces. */ #define IS_DIFFERENT(TYPE) (want_config[TYPE] != use_config[TYPE]) void applyconfig () { int onbutton; int lighting = 0, resistances=0; if (face_info.want_faceset) free(face_info.want_faceset); face_info.want_faceset = strdup_local(gtk_entry_get_text(GTK_ENTRY(GTK_COMBO(faceset_combo)->entry))); for (onbutton =0; onbutton < MAX_BUTTONS; onbutton++) { if (cbuttons[onbutton].type == CBUTTON) { set_config_value(onbutton, GTK_TOGGLE_BUTTON (cbuttons[onbutton].widget)->active); } else if (cbuttons[onbutton].type & SPIN) { set_config_value(onbutton, gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(cbuttons[onbutton].widget))); /* * Nothing special for command window, icon_scale, map_scale, * map width and height. It should be possible to dynamically * change the width and height values, but that is for another day. */ } else if (cbuttons[onbutton].type == RBUTTON) { /* We know that the only radio buttons currently in use are those for * lighting. IF other radio buttons are added later, this should * be changed. */ if ( GTK_TOGGLE_BUTTON (cbuttons[onbutton].widget)->active) { if ( cbuttons[onbutton].config >= 100 && cbuttons[onbutton].config < 200) lighting = cbuttons[onbutton].config - 100; else if ( cbuttons[onbutton].config >= 200 && cbuttons[onbutton].config < 300) resistances = cbuttons[onbutton].config - 200; } } } /* for onbutton ... loop */ /* User has toggled splitwindows - adjust accordingly */ if (IS_DIFFERENT(CONFIG_SPLITWIN)) { use_config[CONFIG_SPLITWIN] = want_config[CONFIG_SPLITWIN]; toggle_splitwin(want_config[CONFIG_SPLITWIN]); } if (IS_DIFFERENT(CONFIG_SOUND)) { int tmp; if (want_config[CONFIG_SOUND]) { tmp = init_sounds(); if (csocket.fd) cs_print_string(csocket.fd, "setup sound %d", tmp >= 0); } else { if (csocket.fd) cs_print_string(csocket.fd, "setup sound 0"); } use_config[CONFIG_SOUND] = want_config[CONFIG_SOUND]; } if (IS_DIFFERENT(CONFIG_COLORINV)) { use_config[CONFIG_COLORINV] = want_config[CONFIG_COLORINV]; inventory_update_colorinv(); } if (IS_DIFFERENT(CONFIG_TOOLTIPS)) { if (want_config[CONFIG_TOOLTIPS]) gtk_tooltips_enable(tooltips); else gtk_tooltips_disable(tooltips); use_config[CONFIG_TOOLTIPS] = want_config[CONFIG_TOOLTIPS]; } else if (IS_DIFFERENT(CONFIG_FASTTCP)) { #ifdef TCP_NODELAY #ifndef WIN32 int q = want_config[CONFIG_FASTTCP]; if (csocket.fd && setsockopt(csocket.fd, SOL_TCP, TCP_NODELAY, &q, sizeof(q)) == -1) perror("TCP_NODELAY"); #else int q = want_config[CONFIG_FASTTCP]; if (csocket.fd && setsockopt(csocket.fd, SOL_TCP, TCP_NODELAY, ( const char* )&q, sizeof(q)) == -1) perror("TCP_NODELAY"); #endif #endif use_config[CONFIG_FASTTCP] = want_config[CONFIG_FASTTCP]; } if (IS_DIFFERENT(CONFIG_SHOWICON)) { itemlist_set_show_icon(&inv_list, want_config[CONFIG_SHOWICON]); /* TODO What about the look list? And should showicon propogate back here? */ use_config[CONFIG_SHOWICON] = want_config[CONFIG_SHOWICON]; } if (IS_DIFFERENT(CONFIG_RESISTS)) { use_config[CONFIG_RESISTS] = want_config[CONFIG_RESISTS]; } if (!use_config[CONFIG_GRAD_COLOR]) { reset_stat_bars(); } if (lighting) { if (want_config[CONFIG_LIGHTING] != lighting) { want_config[CONFIG_LIGHTING] = lighting; use_config[CONFIG_LIGHTING] = lighting; } #ifdef HAVE_SDL if (use_config[CONFIG_DISPLAYMODE]==CFG_DM_SDL) /* This is done to make the 'lightmap' in the proper format */ init_SDL( NULL, 1); #endif if( csocket.fd) cs_print_string(csocket.fd, "mapredraw"); } want_config[CONFIG_RESISTS] = resistances; if (want_config[CONFIG_RESISTS] != use_config[CONFIG_RESISTS]) { resize_resistance_table(want_config[CONFIG_RESISTS]); use_config[CONFIG_RESISTS] = want_config[CONFIG_RESISTS]; draw_message_window(1); } } /* Ok, here it sets the config and saves it. This is sorta dangerous, and I'm not sure * if it's actually possible to do dynamic reconfiguration of everything this way. */ void saveconfig () { /* No idea why applyconfig was basically replicated - just call the * function instead! */ applyconfig(); save_defaults(); } /* * GUI Config dialog. * * */ void configdialog(GtkWidget *widget) { GtkWidget *vbox; GtkWidget *tablabel; GtkWidget *notebook; GtkWidget *vbox1; GtkWidget *vbox2; GtkWidget *hbox1; GtkWidget *applybutton; GtkWidget *cancelbutton; GtkWidget *savebutton; GtkWidget *frame1; GtkWidget *frame_map, *vbox_map; /* frame and vbox for map notebook */ GtkWidget *addwidget; /* Used in buildin the tab to point to the widget to add to */ GtkWidget *ehbox; GtkWidget *clabel1, *clabel2, *clabel4, *clabel5, *cb1, *cb2, *cb3; GtkWidget *cclists; GtkWidget *extras[250]; GList *flist; int i, num_extras=0; gchar *titles[] ={"#","Key","(#)","Mods","Command"}; /* If the window isnt already up (in which case it's just raised) */ if(!gtkwin_config) { int x, y, wx, wy, w, h; gtkwin_config = gtk_window_new (GTK_WINDOW_DIALOG); /* Pet peeve - center new window on top of parent, and not on the * the center of the screen - the later is really annoying in * xinerama mode. Thankfully, GTK 2.0 adds an option to * center on parent - for now, just fake it by getting the parents * geometry. */ /*gtk_window_position (GTK_WINDOW (gtkwin_config), GTK_WIN_POS_CENTER);*/ get_window_coord(gtkwin_root, &x,&y, &wx,&wy,&w,&h); gtk_widget_set_uposition(gtkwin_config, (wx + w - 450)/2, (wy + h-500) / 2); gtk_widget_set_usize (gtkwin_config,450,600); gtk_window_set_title (GTK_WINDOW (gtkwin_config), "Crossfire Configure"); gtk_window_set_policy (GTK_WINDOW (gtkwin_config), TRUE, TRUE, FALSE); gtk_signal_connect (GTK_OBJECT (gtkwin_config), "destroy", GTK_SIGNAL_FUNC(gtk_widget_destroyed), >kwin_config); gtk_container_border_width (GTK_CONTAINER (gtkwin_config), 0); /* vbox splits the window - top portion is the notebook, bottom * portion is for the tabs for apply/save/config. */ vbox = gtk_vbox_new(FALSE, 2); gtk_container_add (GTK_CONTAINER(gtkwin_config),vbox); notebook = gtk_notebook_new (); gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP ); gtk_box_pack_start (GTK_BOX(vbox),notebook, TRUE, TRUE, 0); tablabel = gtk_label_new ("General"); gtk_widget_show (tablabel); frame1 = gtk_frame_new("General options"); gtk_frame_set_shadow_type (GTK_FRAME(frame1), GTK_SHADOW_ETCHED_IN); gtk_notebook_append_page(GTK_NOTEBOOK(notebook), frame1, tablabel); vbox1 = gtk_vbox_new(FALSE, 0); gtk_container_add (GTK_CONTAINER(frame1), vbox1); tablabel = gtk_label_new ("Map & Image"); gtk_widget_show (tablabel); frame_map = gtk_frame_new("Map and Image options"); gtk_frame_set_shadow_type (GTK_FRAME(frame_map), GTK_SHADOW_ETCHED_IN); gtk_notebook_append_page(GTK_NOTEBOOK(notebook), frame_map, tablabel); vbox_map = gtk_vbox_new(FALSE, 0); gtk_container_add (GTK_CONTAINER(frame_map), vbox_map); for (i=0; i < MAX_BUTTONS; i++) { if (cbuttons[i].flags & FLAG_MAPPANE) addwidget = vbox_map; else addwidget = vbox1; if (cbuttons[i].type == CBUTTON) { cbuttons[i].widget = gtk_check_button_new_with_label(cbuttons[i].label); gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(cbuttons[i].widget), want_config[cbuttons[i].config]); } else if (cbuttons[i].type == RBUTTON) { if ((i>0) && (cbuttons[i-1].type == RBUTTON)) { cbuttons[i].widget = gtk_radio_button_new_with_label_from_widget( GTK_RADIO_BUTTON(cbuttons[i-1].widget), cbuttons[i].label); } else { cbuttons[i].widget = gtk_radio_button_new_with_label(NULL, cbuttons[i].label); } if ((want_config[CONFIG_LIGHTING]+100) == cbuttons[i].config) gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(cbuttons[i].widget), 1); if ((want_config[CONFIG_RESISTS]+200) == cbuttons[i].config) gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(cbuttons[i].widget), 1); } else if (cbuttons[i].type & SPIN) { GtkAdjustment *adj=NULL; if (cbuttons[i].type == SPIN_SCALE) adj = (GtkAdjustment *) gtk_adjustment_new(want_config[cbuttons[i].config], 25, 200, 1, 5, 5); else if (cbuttons[i].type == SPIN_MAP) adj = (GtkAdjustment *) gtk_adjustment_new(want_config[cbuttons[i].config], 9, MAP_MAX_SIZE, 1, 5, 5); else if (cbuttons[i].type == SPIN_CWINDOW) adj = (GtkAdjustment *) gtk_adjustment_new(want_config[cbuttons[i].config], 1, 127, 1, 5, 5); cbuttons[i].widget = gtk_spin_button_new(adj, 1, 0); extras[num_extras] = gtk_hbox_new(FALSE, 2); gtk_box_pack_start(GTK_BOX(extras[num_extras]), cbuttons[i].widget, FALSE, FALSE, 0); extras[++num_extras] = gtk_label_new(cbuttons[i].label); gtk_box_pack_start(GTK_BOX(extras[num_extras-1]), extras[num_extras], FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(addwidget), extras[num_extras-1], FALSE, FALSE, 0); num_extras++; extras[num_extras++] = cbuttons[i].widget; continue; /* What to skip the box_pack_start below */ } else if (cbuttons[i].type == SEPERATOR) { extras[num_extras] = (GtkWidget*)gtk_hseparator_new (); gtk_box_pack_start (GTK_BOX (addwidget), extras[num_extras], FALSE, FALSE, 0); cbuttons[i].widget = gtk_label_new(cbuttons[i].label); gtk_label_set_justify(GTK_LABEL(cbuttons[i].widget), GTK_JUSTIFY_LEFT); num_extras++; } else { LOG(LOG_WARNING,"gtk::configdialog","Unknown cbutton type %d", cbuttons[i].type); } if (cbuttons[i].widget) { extras[num_extras++] = cbuttons[i].widget; gtk_box_pack_start(GTK_BOX(addwidget), cbuttons[i].widget, FALSE, FALSE, 0); } } for (i=0; i < num_extras; i++) { gtk_widget_show(extras[i]); } /* faceset is special because it is string data. */ faceset_combo = gtk_combo_new(); flist = NULL; if (face_info.want_faceset) { gtk_entry_set_text(GTK_ENTRY(GTK_COMBO(faceset_combo)->entry), face_info.want_faceset); flist = g_list_append(flist, face_info.want_faceset); } /* If we have real faceset info from the server, use it */ if (face_info.have_faceset_info) { for (i=0; iwindow); } } void load_defaults() { char path[MAX_BUF],inbuf[MAX_BUF],*cp; FILE *fp; int i, val; /* Copy over the want values to use values now */ for (i=0; i200) { LOG(LOG_WARNING,"gtk::load_defaults","Ignoring iconscale value read for gdefaults file.\n" "Invalid iconscale range (%d), valid range for -iconscale is 25 through 200", want_config[CONFIG_ICONSCALE]); want_config[CONFIG_ICONSCALE] = use_config[CONFIG_ICONSCALE]; } if (want_config[CONFIG_MAPSCALE]< 25 || want_config[CONFIG_MAPSCALE]>200) { LOG(LOG_WARNING,"gtk::load_defaults","ignoring mapscale value read for gdefaults file.\n" "Invalid mapscale range (%d), valid range for -iconscale is 25 through 200", want_config[CONFIG_MAPSCALE]); want_config[CONFIG_MAPSCALE] = use_config[CONFIG_MAPSCALE]; } if (!want_config[CONFIG_LIGHTING]) { LOG(LOG_WARNING,"gtk::load_defaults","No lighting mechanism selected - will not use darkness code"); want_config[CONFIG_DARKNESS] = FALSE; } if (want_config[CONFIG_RESISTS] > 2) { LOG(LOG_WARNING,"gtk::load_defaults","ignoring resists display value read for gdafaults file.\n" "Invalid value (%d), must be one value of 0,1 or 2.", want_config[CONFIG_RESISTS]); want_config[CONFIG_RESISTS] = 0; } /* Make sure the map size os OK */ if (want_config[CONFIG_MAPWIDTH] < 9 || want_config[CONFIG_MAPWIDTH] > MAP_MAX_SIZE) { LOG(LOG_WARNING,"gtk::load_defaults", "Invalid map width (%d) option in gdefaults. Valid range is 9 to %d", want_config[CONFIG_MAPWIDTH], MAP_MAX_SIZE); want_config[CONFIG_MAPWIDTH] = use_config[CONFIG_MAPWIDTH]; } if (want_config[CONFIG_MAPHEIGHT] < 9 || want_config[CONFIG_MAPHEIGHT] > MAP_MAX_SIZE) { LOG(LOG_WARNING,"gtk::load_defaults", "Invalid map height (%d) option in gdefaults. Valid range is 9 to %d", want_config[CONFIG_MAPHEIGHT], MAP_MAX_SIZE); want_config[CONFIG_MAPHEIGHT] = use_config[CONFIG_MAPHEIGHT]; } /* Now copy over the values just loaded */ for (i=0; i