Index: client/audio.c =================================================================== RCS file: /home/freeciv/CVS/freeciv/client/audio.c,v retrieving revision 1.11 diff -u -r1.11 audio.c --- client/audio.c 2002/07/23 02:48:45 1.11 +++ client/audio.c 2002/08/10 18:16:43 @@ -17,12 +17,15 @@ #include #include +#include #include +#include "audio.h" #include "support.h" #include "fcintl.h" #include "log.h" #include "capability.h" +#include "mem.h" #include "shared.h" #include "registry.h" #include "audio_none.h" @@ -45,7 +48,8 @@ #include "audio.h" -#define MAX_NUM_PLUGINS 3 +#define MAX_NUM_PLUGINS 3 +#define SNDSPEC_SUFFIX ".soundspec" /* keep it open throughout */ static struct section_file tagstruct, *tagfile = &tagstruct; @@ -54,6 +58,43 @@ static int num_plugins_used = 0; static int selected_plugin = -1; +/********************************************************************** + Returns a static, NULL-terminated list of all sound plugins + available on the system. This function is unfortunately similar to + audio_get_all_plugin_names(). +***********************************************************************/ +const char **get_soundplugin_list(void) +{ + static const char* plugin_list[MAX_NUM_PLUGINS + 1]; + int i; + + for (i = 0; i < num_plugins_used; i++) { + plugin_list[i] = plugins[i].name; + } + assert(i <= MAX_NUM_PLUGINS); + plugin_list[i] = NULL; + + return plugin_list; +} + +/********************************************************************** + Returns a static list of soundsets available on the system by + searching all data directories for files matching SNDSPEC_SUFFIX. + The list is NULL-terminated. +***********************************************************************/ +const char **get_soundset_list(void) +{ + static const char **audio_list = NULL; + + if (!audio_list) { + /* Note: this means you must restart the client after installing a new + soundset. */ + audio_list = datafilelist(SNDSPEC_SUFFIX); + } + + return audio_list; +} + /************************************************************************** Add a plugin. **************************************************************************/ @@ -129,15 +170,45 @@ } /************************************************************************** + Returns the filename for the given soundset. Returns NULL if + soundset couldn't be found. Caller has to free the return value. +**************************************************************************/ +static const char *soundspec_fullname(const char *soundset_name) +{ + char *soundset_default = "stdsounds"; /* Do not i18n! */ + char *fname = fc_malloc(strlen(soundset_name) + strlen(SNDSPEC_SUFFIX) + 1); + char *dname; + + sprintf(fname, "%s%s", soundset_name, SNDSPEC_SUFFIX); + + dname = datafilename(fname); + free(fname); + + if (dname) { + return mystrdup(dname); + } + + if (strcmp(soundset_name, soundset_default) == 0) { + /* avoid endless recursion */ + return NULL; + } + + freelog(LOG_ERROR, _("Couldn't find soundset \"%s\" trying \"%s\"."), + soundset_name, soundset_default); + return soundspec_fullname(soundset_default); +} + +/************************************************************************** Initialize audio system and autoselect a plugin **************************************************************************/ void audio_real_init(const char *const spec_name, const char *const prefered_plugin_name) { - char *filename, *file_capstr; + const char *filename; + char *file_capstr; char us_capstr[] = "+soundspec"; - if (!strcmp(prefered_plugin_name, "none")) { + if (strcmp(prefered_plugin_name, "none") != 0) { /* We explicitly choose none plugin, silently skip the code below */ freelog(LOG_VERBOSE, _("Proceeding with sound support disabled")); tagfile = NULL; @@ -161,7 +232,7 @@ exit(EXIT_FAILURE); } freelog(LOG_VERBOSE, "Initializing sound using %s...", spec_name); - filename = datafilename(spec_name); + filename = soundspec_fullname(spec_name); if (!filename) { freelog(LOG_ERROR, _("Cannot find audio spec-file \"%s\"."), spec_name); freelog(LOG_ERROR, _("To get sound you need to download a sound set!")); @@ -192,6 +263,8 @@ freelog(LOG_FATAL, _("supported options: %s"), us_capstr); exit(EXIT_FAILURE); } + + free((void *) filename); atexit(audio_shutdown); Index: client/audio.h =================================================================== RCS file: /home/freeciv/CVS/freeciv/client/audio.h,v retrieving revision 1.2 diff -u -r1.2 audio.h --- client/audio.h 2002/04/23 22:51:22 1.2 +++ client/audio.h 2002/08/10 18:16:43 @@ -28,6 +28,9 @@ bool (*play) (const char *const tag, const char *const path, bool repeat); }; +const char **get_soundplugin_list(void); +const char **get_soundset_list(void); + void audio_init(void); void audio_real_init(const char *const spec_name, const char *const prefered_plugin_name); Index: client/options.c =================================================================== RCS file: /home/freeciv/CVS/freeciv/client/options.c,v retrieving revision 1.62 diff -u -r1.62 options.c --- client/options.c 2002/07/23 02:48:45 1.62 +++ client/options.c 2002/08/10 18:16:44 @@ -20,6 +20,7 @@ #include #include +#include "audio.h" #include "clinet.h" #include "events.h" #include "fcintl.h" @@ -28,6 +29,7 @@ #include "registry.h" #include "shared.h" #include "support.h" +#include "tilespec.h" #include "version.h" #include "mem.h" @@ -44,7 +46,7 @@ int default_server_port = DEFAULT_SOCK_PORT; char default_metaserver[512] = METALIST_ADDR; char default_tile_set_name[512] = "\0"; -char default_sound_set_name[512] = "stdsounds.spec"; +char default_sound_set_name[512] = "stdsounds"; char default_sound_plugin_name[512] = "\0"; /** Local Options: **/ @@ -66,22 +68,27 @@ bool meta_accelerators = TRUE; #define GEN_INT_OPTION(name, desc) { #name, desc, COT_INT, \ - &name, NULL, NULL, 0, NULL } + &name, NULL, NULL, 0, NULL, NULL } #define GEN_BOOL_OPTION(name, desc) { #name, desc, COT_BOOL, \ - NULL, &name, NULL, 0, NULL } -#define GEN_STR_OPTION(name, desc) { #name, desc, COT_STR, \ - NULL, NULL, name, sizeof(name), NULL } + NULL, &name, NULL, 0, NULL, NULL } +#define GEN_STR_OPTION(name, desc, dflt) { #name, desc, COT_STR, \ + NULL, NULL, name, sizeof(name), \ + dflt, NULL } #define GEN_OPTION_TERMINATOR { NULL, NULL, COT_BOOL, \ NULL, NULL, NULL, 0, NULL } client_option options[] = { - GEN_STR_OPTION(default_player_name, N_("Default player's username")), - GEN_STR_OPTION(default_server_host, N_("Default server")), + GEN_STR_OPTION(default_player_name, N_("Default player's username"), + NULL), + GEN_STR_OPTION(default_server_host, N_("Default server"), NULL), GEN_INT_OPTION(default_server_port, N_("Default server's port")), - GEN_STR_OPTION(default_metaserver, N_("Default metaserver")), - GEN_STR_OPTION(default_tile_set_name, N_("Default tileset")), - GEN_STR_OPTION(default_sound_set_name, N_("Default name of sound set")), - GEN_STR_OPTION(default_sound_plugin_name, N_("Default sound plugin")), + GEN_STR_OPTION(default_metaserver, N_("Default metaserver"), NULL), + GEN_STR_OPTION(default_tile_set_name, N_("Default tileset"), + get_tileset_list), + GEN_STR_OPTION(default_sound_set_name, N_("Default name of sound set"), + get_soundset_list), + GEN_STR_OPTION(default_sound_plugin_name, N_("Default sound plugin"), + get_soundplugin_list), GEN_BOOL_OPTION(solid_color_behind_units, N_("Solid unit background color")), GEN_BOOL_OPTION(sound_bell_at_new_turn, N_("Sound bell at new turn")), Index: client/options.h =================================================================== RCS file: /home/freeciv/CVS/freeciv/client/options.h,v retrieving revision 1.18 diff -u -r1.18 options.h --- client/options.h 2002/07/23 02:48:45 1.18 +++ client/options.h 2002/08/10 18:16:44 @@ -59,6 +59,12 @@ char *p_string_value; size_t string_length; + /* + * A function to return a static NULL-terminated list of possible + * string values, or NULL for none. + */ + const char **(*p_string_vals)(void); + /* volatile */ void *p_gui_data; } client_option; Index: client/tilespec.c =================================================================== RCS file: /home/freeciv/CVS/freeciv/client/tilespec.c,v retrieving revision 1.81 diff -u -r1.81 tilespec.c --- client/tilespec.c 2002/07/27 14:14:23 1.81 +++ client/tilespec.c 2002/08/10 18:16:45 @@ -50,6 +50,8 @@ #include "tilespec.h" +#define TILESPEC_SUFFIX ".tilespec" + char *main_intro_filename; char *minimap_intro_filename; @@ -115,6 +117,24 @@ static bool no_backdrop = FALSE; /********************************************************************** + Returns a static list of tilesets available on the system by + searching all data directories for files matching TILESPEC_SUFFIX. + The list is NULL-terminated. +***********************************************************************/ +const char **get_tileset_list(void) +{ + static const char **tileset_list = NULL; + + if (!tileset_list) { + /* Note: this means you must restart the client after installing a new + tileset. */ + tileset_list = datafilelist(TILESPEC_SUFFIX); + } + + return tileset_list; +} + +/********************************************************************** Gets full filename for tilespec file, based on input name. Returned data is allocated, and freed by user as required. Input name may be null, in which case uses default. @@ -135,12 +155,14 @@ if (tileset_name[0] == '\0') { tileset_name = tileset_default; } - fname = fc_malloc(strlen(tileset_name)+16); - sprintf(fname, "%s.tilespec", tileset_name); + + fname = fc_malloc(strlen(tileset_name) + strlen(TILESPEC_SUFFIX) + 1); + sprintf(fname, "%s%s", tileset_name,TILESPEC_SUFFIX); dname = datafilename(fname); + free(fname); + if (dname) { - free(fname); return mystrdup(dname); } @@ -149,7 +171,6 @@ exit(EXIT_FAILURE); } freelog(LOG_ERROR, _("Trying \"%s\" tileset."), tileset_default); - free(fname); return tilespec_fullname(tileset_default); } Index: client/tilespec.h =================================================================== RCS file: /home/freeciv/CVS/freeciv/client/tilespec.h,v retrieving revision 1.28 diff -u -r1.28 tilespec.h --- client/tilespec.h 2002/04/06 11:42:57 1.28 +++ client/tilespec.h 2002/08/10 18:16:45 @@ -26,6 +26,8 @@ struct unit; struct player; +const char **get_tileset_list(void); + void tilespec_read_toplevel(const char *tileset_name); void tilespec_load_tiles(void); Index: client/gui-xaw/optiondlg.c =================================================================== RCS file: /home/freeciv/CVS/freeciv/client/gui-xaw/optiondlg.c,v retrieving revision 1.19 diff -u -r1.19 optiondlg.c --- client/gui-xaw/optiondlg.c 2002/07/23 02:47:38 1.19 +++ client/gui-xaw/optiondlg.c 2002/08/10 18:16:45 @@ -22,9 +22,12 @@ #include #include #include +#include #include +#include #include #include +#include #include #include @@ -77,7 +80,8 @@ XtVaSetValues((Widget) o->p_gui_data, XtNstring, valstr, NULL); break; case COT_STR: - XtVaSetValues((Widget) o->p_gui_data, XtNstring, + XtVaSetValues((Widget) o->p_gui_data, + o->p_string_vals ? "label" : XtNstring, o->p_string_value, NULL); break; } @@ -90,6 +94,18 @@ +/**************************************************************** + Callback to change the entry for a string option that has + a fixed list of possible entries. +*****************************************************************/ +static void stropt_change_callback(Widget w, + XtPointer client_data, + XtPointer call_data) +{ + char* val = (char*)client_data; + + XtVaSetValues(XtParent(XtParent(w)), "label", val, NULL); +} /**************************************************************** ... @@ -151,8 +167,49 @@ NULL); XtAddCallback(prev_widget, XtNcallback, toggle_callback, NULL); break; - case COT_INT: case COT_STR: + if (o->p_string_vals) { + int i; + const char **vals = (*o->p_string_vals)(); + Widget popupmenu; + + prev_widget = XtVaCreateManagedWidget(o->name, + menuButtonWidgetClass, + option_form, + XtNfromHoriz, longest_label, + XtNfromVert, o->p_gui_data, + NULL); + + popupmenu = XtVaCreatePopupShell("menu", + simpleMenuWidgetClass, + prev_widget, + NULL); + + for (i = 0; vals[i]; i++) { + Widget entry = XtVaCreateManagedWidget(vals[i], smeBSBObjectClass, + popupmenu, NULL); + XtAddCallback(entry, XtNcallback, stropt_change_callback, + (XtPointer)(vals[i])); + } + + if (i == 0) { + /* We could disable this if there was just one possible choice, + too, but for values that are uninitialized (empty) this + would be confusing. */ + XtSetSensitive(prev_widget, FALSE); + } + +#if 0 + /* Setting the lable before the widget is drawn causes it to be + sized appropriately; if the value is empty this can be a + problem. */ + XtVaSetValues(prev_widget, "label", o->p_string_value, NULL); +#endif + + break; + } + /* else fall through */ + case COT_INT: prev_widget = XtVaCreateManagedWidget("input", asciiTextWidgetClass, option_form, XtNfromHoriz, longest_label, @@ -219,7 +276,9 @@ sscanf(dp, "%d", o->p_int_value); break; case COT_STR: - XtVaGetValues(o->p_gui_data, XtNstring, &dp, NULL); + XtVaGetValues(o->p_gui_data, + o->p_string_vals ? "label" : XtNstring, + &dp, NULL); mystrlcpy(o->p_string_value,dp,o->string_length); break; } Index: common/shared.c =================================================================== RCS file: /home/freeciv/CVS/freeciv/common/shared.c,v retrieving revision 1.87 diff -u -r1.87 shared.c --- common/shared.c 2002/08/07 11:21:48 1.87 +++ common/shared.c 2002/08/10 18:16:46 @@ -15,10 +15,13 @@ #endif #include +#include +#include #include #include #include #include +#include #ifdef HAVE_UNISTD_H #include @@ -718,117 +721,240 @@ } /*************************************************************************** - Returns a filename to access the specified file from a data directory; - The file may be in any of the directories in the data path, which is - specified internally or may be set as the environment variable - $FREECIV_PATH. (A separated list of directories, where the separator - itself can specified internally.) - '~' at the start of a component (provided followed by '/' or '\0') - is expanded as $HOME. + Returns a list of data directory paths, in the order in which they should + be searched. These paths are specified internally or may be set as the + environment variable $FREECIV_PATH (a separated list of directories, + where the separator itself is specified internally, platform-dependent). + '~' at the start of a component (provided followed by '/' or '\0') is + expanded as $HOME. - If the specified 'filename' is NULL, the returned string contains - the effective data path. (But this should probably only be used - for debug output.) - - Returns NULL if the specified filename cannot be found in any of the - data directories. (A file is considered "found" if it can be read-opened.) - The returned pointer points to static memory, so this function can - only supply one filename at a time. + The returned value is a static NULL-terminated list of strings. + + num_dirs, if not NULL, will be set to the number of entries in the list. ***************************************************************************/ -char *datafilename(const char *filename) +static const char **get_data_dirs(int *num_dirs) { - static char *path = NULL; - static int num_dirs = 0; - static char **dirs = NULL; - static struct astring realfile = ASTRING_INIT; - int i; + char *path, *path2, *tok; + static int num = 0; + static const char **dirs = NULL; - if (!path) { - char *tok; - char *path2; - - path = getenv("FREECIV_PATH"); - if (!path) { - path = DEFAULT_DATA_PATH; - } else if (*path == '\0') { - freelog(LOG_ERROR, _("FREECIV_PATH is set but empty; " - "using default path instead.")); - path = DEFAULT_DATA_PATH; + /* The first time this function is called it will search and + * allocate the directory listing. Subsequently we will already + * know the list and can just return it. */ + if (dirs) { + if (num_dirs) { + *num_dirs = num; } - assert(path != NULL); + return dirs; + } - path2 = mystrdup(path); /* something we can strtok */ + path = getenv("FREECIV_PATH"); + if (!path) { + path = DEFAULT_DATA_PATH; + } else if (*path == '\0') { + freelog(LOG_ERROR, _("FREECIV_PATH is set but empty; " + "using default path instead.")); + path = DEFAULT_DATA_PATH; + } + assert(path != NULL); + + path2 = mystrdup(path); /* something we can strtok */ - tok = strtok(path2, PATH_SEPARATOR); - do { - int i; /* strlen(tok), or -1 as flag */ - - tok = skip_leading_spaces(tok); - remove_trailing_spaces(tok); - if (strcmp(tok, "/")!=0) { - remove_trailing_char(tok, '/'); - } + tok = strtok(path2, PATH_SEPARATOR); + do { + int i; /* strlen(tok), or -1 as flag */ + + tok = skip_leading_spaces(tok); + remove_trailing_spaces(tok); + if (strcmp(tok, "/")!=0) { + remove_trailing_char(tok, '/'); + } - i = strlen(tok); - if (tok[0] == '~') { - if (i>1 && tok[1] != '/') { - freelog(LOG_ERROR, "For \"%s\" in data path cannot expand '~'" - " except as '~/'; ignoring", tok); - i = 0; /* skip this one */ + i = strlen(tok); + if (tok[0] == '~') { + if (i>1 && tok[1] != '/') { + freelog(LOG_ERROR, "For \"%s\" in data path cannot expand '~'" + " except as '~/'; ignoring", tok); + i = 0; /* skip this one */ + } else { + char *home = user_home_dir(); + if (!home) { + freelog(LOG_VERBOSE, + "No HOME, skipping data path component %s", tok); + i = 0; } else { - char *home = user_home_dir(); - if (!home) { - freelog(LOG_VERBOSE, - "No HOME, skipping data path component %s", tok); - i = 0; - } else { - int len = strlen(home) + i; /* +1 -1 */ - char *tmp = fc_malloc(len); - my_snprintf(tmp, len, "%s%s", home, tok+1); - tok = tmp; - i = -1; /* flag to free tok below */ - } + int len = strlen(home) + i; /* +1 -1 */ + char *tmp = fc_malloc(len); + my_snprintf(tmp, len, "%s%s", home, tok + 1); + tok = tmp; + i = -1; /* flag to free tok below */ } + } + } + if (i != 0) { + /* We could check whether the directory exists and + * is readable etc? Don't currently. */ + num++; + dirs = fc_realloc(dirs, num * sizeof(char*)); + dirs[num - 1] = mystrdup(tok); + freelog(LOG_VERBOSE, "Data path component (%d): %s", num - 1, tok); + if (i == -1) { + free(tok); + tok = NULL; + } + } + + tok = strtok(NULL, PATH_SEPARATOR); + } while(tok); + + /* NULL-terminate the list. */ + dirs = fc_realloc(dirs, (num + 1) * sizeof(char*)); + dirs[num] = NULL; + + free(path2); + + if (num_dirs) { + *num_dirs = num; + } + return dirs; +} + +/*************************************************************************** + Returns a NULL-terminated list of filenames in the data directories + matching the given suffix. + + The list is allocated when the function is called; it should either + be stored permanently or de-allocated (by free'ing each element and + the whole list). + + The suffixes are removed from the filenames before the list is + returned. +***************************************************************************/ +const char **datafilelist(const char* suffix) +{ + const char **dirs = get_data_dirs(NULL); + char **file_list = NULL; + int num_matches = 0; + int list_size = 0; + int dir_num, i, j; + size_t suffix_len = strlen(suffix); + + assert(!strchr(suffix, '/')); + + /* First assemble a full list of names. */ + for (dir_num = 0; dirs[dir_num]; dir_num++) { + DIR* dir; + struct dirent *entry; + + /* Open the directory for reading. */ + dir = opendir(dirs[dir_num]); + if (!dir) { + if (errno == ENOENT) { + freelog(LOG_VERBOSE, "Skipping non-existing data directory %s.", + dirs[dir_num]); + } else { + freelog(LOG_ERROR, _("Could not read data directory %s: %s."), + dirs[dir_num], strerror(errno)); } - if (i != 0) { - /* We could check whether the directory exists and - * is readable etc? Don't currently. */ - num_dirs++; - dirs = fc_realloc(dirs, num_dirs*sizeof(char*)); - dirs[num_dirs-1] = mystrdup(tok); - freelog(LOG_VERBOSE, "Data path component (%d): %s", num_dirs-1, tok); - if (i == -1) { - free(tok); - tok = NULL; + continue; + } + + /* Scan all entries in the directory. */ + while ((entry = readdir(dir))) { + size_t len = strlen(entry->d_name); + + /* Make sure the file name matches. */ + if (len > suffix_len + && strcmp(suffix, entry->d_name + len - suffix_len) == 0) { + /* Strdup the entry so we can safely write to it. */ + char *match = mystrdup(entry->d_name); + + /* Make sure the list is big enough; grow exponentially to keep + constant ammortized overhead. */ + if (num_matches >= list_size) { + list_size = list_size > 0 ? list_size * 2 : 10; + file_list = fc_realloc(file_list, list_size * sizeof(*file_list)); } + + /* Clip the suffix. */ + match[len - suffix_len] = '\0'; + + file_list[num_matches++] = mystrdup(match); + + free(match); } + } + + closedir(dir); + } + + /* Sort the list. */ + qsort(file_list, num_matches, sizeof(*file_list), compare_strings_ptrs); + + /* Remove duplicates (easy since it's sorted). */ + i = j = 0; + while (j < num_matches) { + char *this = file_list[j]; + + for (j++; j < num_matches && strcmp(this, file_list[j]) == 0; j++) { + free(file_list[j]); + } - tok = strtok(NULL, PATH_SEPARATOR); - } while(tok); + file_list[i] = this; - free(path2); + i++; } + num_matches = i; + /* NULL-terminate the whole thing. */ + file_list = fc_realloc(file_list, (num_matches + 1) * sizeof(*file_list)); + file_list[num_matches] = NULL; + + return (const char **)file_list; +} + +/*************************************************************************** + Returns a filename to access the specified file from a data + directory by searching all data directories (as specified by + get_data_dirs) for the file. + + If the specified 'filename' is NULL, the returned string contains + the effective data path. (But this should probably only be used for + debug output.) + + Returns NULL if the specified filename cannot be found in any of the + data directories. (A file is considered "found" if it can be + read-opened.) The returned pointer points to static memory, so this + function can only supply one filename at a time. +***************************************************************************/ +char *datafilename(const char *filename) +{ + int num_dirs, i; + const char **dirs = get_data_dirs(&num_dirs); + static struct astring realfile = ASTRING_INIT; + if (!filename) { - int len = 1; /* in case num_dirs==0 */ - int seplen = strlen(PATH_SEPARATOR); - for(i=0; i