123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714 |
- /*
- ****************************************************************************
- *
- * MODULE: d.vect.thematic
- * AUTHOR(S): Moritz Lennert, based on d.vect
- * PURPOSE: Display a thematic vector map
- * TODO: Common part of code merge with d.vect (similarly as r.colors
- * and r3.colors)
- * COPYRIGHT: (C) 2007-2014 by the GRASS Development Team
- *
- * This program is free software under the GNU General Public
- * License (>=v2). Read the file COPYING that comes with GRASS
- * for details.
- *
- *****************************************************************************/
- #include <stdlib.h>
- #include <string.h>
- #include <sys/types.h>
- #include <dirent.h>
- #include <grass/config.h>
- #include <grass/gis.h>
- #include <grass/raster.h>
- #include <grass/display.h>
- #include <grass/vector.h>
- #include <grass/colors.h>
- #include <grass/dbmi.h>
- #include <grass/glocale.h>
- #include <grass/arraystats.h>
- #include "plot.h"
- #include "local_proto.h"
- int main(int argc, char **argv)
- {
- int ret, level;
- int i, stat = 0;
- int nclass = 0, nbreaks, *frequencies;
- int chcat = 0;
- int r, g, b;
- int has_color = 0;
- struct color_rgb *colors, bcolor;
- int default_width;
- int verbose = FALSE;
- char map_name[128];
- struct GModule *module;
- struct Option *map_opt;
- struct Option *column_opt;
- struct Option *breaks_opt;
- struct Option *algo_opt;
- struct Option *nbclass_opt;
- struct Option *colors_opt;
- struct Option *bcolor_opt;
- struct Option *bwidth_opt;
- struct Option *where_opt;
- struct Option *field_opt;
- struct Option *legend_file_opt;
- struct Option *icon_opt;
- struct Option *icon_line_opt;
- struct Option *icon_area_opt;
- struct Option *size_opt;
- struct Option *title_opt;
- struct Flag *legend_flag, *algoinfo_flag, *nodraw_flag, *vlegend_flag;
- char *desc, *deprecated;
- struct cat_list *Clist;
- int *cats, ncat, nrec, ctype;
- struct Map_info Map;
- struct field_info *fi;
- dbDriver *driver;
- dbHandle handle;
- dbCatValArray cvarr;
- struct Cell_head window;
- struct bound_box box;
- double overlap, *breakpoints, *data = NULL, class_info = 0.0;
- struct GASTATS stats;
- int size;
- int nfeatures;
- char title[128];
- char *leg_file;
- /* Initialize the GIS calls */
- G_gisinit(argv[0]);
- module = G_define_module();
- G_add_keyword(_("display"));
- G_add_keyword(_("cartography"));
- G_add_keyword(_("choropleth map"));
- G_add_keyword(_("legend"));
- module->description =
- _("Displays a thematic vector map "
- "in the active graphics frame.");
- map_opt = G_define_standard_option(G_OPT_V_MAP);
- field_opt = G_define_standard_option(G_OPT_V_FIELD);
- field_opt->description =
- _("Layer number. If -1, all layers are displayed.");
- field_opt->guisection = _("Selection");
- column_opt = G_define_standard_option(G_OPT_DB_COLUMN);
- column_opt->required = YES;
- column_opt->description =
- _("Name of attribute column to be classified");
- breaks_opt = G_define_option();
- breaks_opt->key = "breaks";
- breaks_opt->type = TYPE_STRING;
- breaks_opt->required = NO;
- breaks_opt->multiple = YES;
- breaks_opt->description = _("Class breaks, without minimum and maximum");
- breaks_opt->guisection = _("Classes");
-
- algo_opt = G_define_option();
- algo_opt->key = "algorithm";
- algo_opt->type = TYPE_STRING;
- algo_opt->required = NO;
- algo_opt->multiple = NO;
- algo_opt->options = "int,std,qua,equ,dis";
- algo_opt->description = _("Algorithm to use for classification");
- desc = NULL;
- G_asprintf(&desc,
- "int;%s;std;%s;qua;%s;equ;%s",
- _("simple intervals"),
- _("standard deviations"),
- _("quantiles"),
- _("equiprobable (normal distribution)"));
- algo_opt->descriptions = desc;
- /*currently disabled because of bugs "dis;discontinuities"); */
- algo_opt->guisection = _("Classes");
- nbclass_opt = G_define_option();
- nbclass_opt->key = "nclasses";
- nbclass_opt->type = TYPE_INTEGER;
- nbclass_opt->required = NO;
- nbclass_opt->multiple = NO;
- nbclass_opt->description = _("Number of classes to define");
- nbclass_opt->guisection = _("Classes");
- colors_opt = G_define_option();
- colors_opt->key = "colors";
- colors_opt->type = TYPE_STRING;
- colors_opt->required = YES;
- colors_opt->multiple = YES;
- colors_opt->description = _("Colors (one per class)");
- colors_opt->gisprompt = "old_color,color,color";
- where_opt = G_define_standard_option(G_OPT_DB_WHERE);
- where_opt->guisection = _("Selection");
- bwidth_opt = G_define_option();
- bwidth_opt->key = "boundary_width";
- bwidth_opt->type = TYPE_INTEGER;
- bwidth_opt->answer = "1";
- bwidth_opt->guisection = _("Boundaries");
- bwidth_opt->description = _("Boundary width");
- bcolor_opt = G_define_standard_option(G_OPT_CN);
- bcolor_opt->key = "boundary_color";
- bcolor_opt->label = _("Boundary color");
- bcolor_opt->guisection = _("Boundaries");
- /* Symbols */
- icon_opt = G_define_option();
- icon_opt->key = "icon";
- icon_opt->type = TYPE_STRING;
- icon_opt->required = NO;
- icon_opt->multiple = NO;
- icon_opt->guisection = _("Symbols");
- icon_opt->answer = "basic/x";
- /* This could also use ->gisprompt = "old,symbol,symbol" instead of ->options */
- icon_opt->options = icon_files();
- icon_opt->description = _("Point and centroid symbol");
- size_opt = G_define_option();
- size_opt->key = "size";
- size_opt->type = TYPE_DOUBLE;
- size_opt->answer = "5";
- size_opt->guisection = _("Symbols");
- size_opt->label = _("Symbol size");
- icon_line_opt = G_define_option();
- icon_line_opt->key = "icon_line";
- icon_line_opt->type = TYPE_STRING;
- icon_line_opt->required = NO;
- icon_line_opt->multiple = NO;
- icon_line_opt->guisection = _("Legend");
- icon_line_opt->answer = "legend/line";
- /* This could also use ->gisprompt = "old,symbol,symbol" instead of ->options */
- icon_line_opt->options = icon_files();
- icon_line_opt->description = _("Legend symbol for lines");
- icon_area_opt = G_define_option();
- icon_area_opt->key = "icon_area";
- icon_area_opt->type = TYPE_STRING;
- icon_area_opt->required = NO;
- icon_area_opt->multiple = NO;
- icon_area_opt->guisection = _("Legend");
- icon_area_opt->answer = "legend/area";
- /* This could also use ->gisprompt = "old,symbol,symbol" instead of ->options */
- icon_area_opt->options = icon_files();
- icon_area_opt->description = _("Legend symbol for areas");
- title_opt = G_define_option();
- title_opt->key = "legend_title";
- title_opt->type = TYPE_STRING;
- title_opt->guisection = _("Legend");
- title_opt->description = _("Thematic map title");
- legend_file_opt = G_define_standard_option(G_OPT_F_OUTPUT);
- legend_file_opt->key = "legendfile";
- deprecated = NULL;
- G_asprintf(&deprecated,
- "[%s] %s",
- _("DEPRECATED"),
- _("Output legend file"));
- legend_file_opt->description = deprecated;
- legend_file_opt->required = NO;
- legend_file_opt->guisection = _("Legend");
- legend_flag = G_define_flag();
- legend_flag->key = 'l';
- legend_flag->description =
- _("Create legend information and send to stdout");
- legend_flag->guisection = _("Legend");
- nodraw_flag = G_define_flag();
- nodraw_flag->key = 'n';
- nodraw_flag->description = _("Do not draw map, only output the legend information");
- nodraw_flag->guisection = _("Legend");
- algoinfo_flag = G_define_flag();
- algoinfo_flag->key = 'e';
- deprecated = NULL;
- G_asprintf(&deprecated,
- "[%s] %s",
- _("DEPRECATED"),
- _("When printing legend info, include extended statistical info from classification algorithm"));
- algoinfo_flag->description = deprecated;
- algoinfo_flag->guisection = _("Legend");
-
- vlegend_flag = G_define_flag();
- vlegend_flag->key = 's';
- vlegend_flag->label = _("Do not show this layer in vector legend");
- vlegend_flag->guisection = _("Legend");
-
- G_option_required(algo_opt, breaks_opt, NULL);
- G_option_exclusive(algo_opt, breaks_opt, NULL);
- G_option_requires(algo_opt, nbclass_opt, NULL);
- /* Check command line */
- if (G_parser(argc, argv))
- exit(EXIT_FAILURE);
- if (algoinfo_flag->answer)
- G_warning(_("Flag -e is deprecated, set verbose mode with --v to get the extended statistical info."));
- if (legend_file_opt->answer)
- G_warning(_("Option legendfile is deprecated, either use flag -l "
- "to print legend to standard output, "
- "or set GRASS_LEGEND_FILE environment variable "
- "(see d.legend.vect for details)."));
- if (G_verbose() > G_verbose_std())
- verbose = TRUE;
- G_get_set_window(&window);
- size = atof(size_opt->answer);
- /* Read map options */
- strcpy(map_name, map_opt->answer);
- /* open vector */
- level = Vect_open_old(&Map, map_name, "");
-
- if (level < 2)
- G_fatal_error(_("%s: You must build topology on vector map. Run v.build."),
- map_name);
- if (title_opt->answer)
- strcpy(title, title_opt->answer);
- else
- strcpy(title, Map.name);
- /* Check database connection and open it */
- Clist = Vect_new_cat_list();
- Clist->field = atoi(field_opt->answer);
- if (Clist->field < 1)
- G_fatal_error(_("'layer' must be > 0"));
- if ((fi = Vect_get_field(&Map, Clist->field)) == NULL)
- G_fatal_error(_("Database connection not defined"));
- if (fi != NULL) {
- driver = db_start_driver(fi->driver);
- if (driver == NULL)
- G_fatal_error(_("Unable to start driver <%s>"), fi->driver);
- db_init_handle(&handle);
- db_set_handle(&handle, fi->database, NULL);
- if (db_open_database(driver, &handle) != DB_OK)
- G_fatal_error(_("Unable to open database <%s>"), fi->database);
- }
- /*Get CatValArray needed for plotting and for legend calculations */
- db_CatValArray_init(&cvarr);
- nrec = db_select_CatValArray(driver, fi->table, fi->key,
- column_opt->answer, where_opt->answer,
- &cvarr);
- G_debug(3, "nrec (%s) = %d", column_opt->answer, nrec);
- if (cvarr.ctype != DB_C_TYPE_INT && cvarr.ctype != DB_C_TYPE_DOUBLE)
- G_fatal_error(_("Data (%s) not numeric. "
- "Column must be numeric."), column_opt->answer);
- if (nrec < 0)
- G_fatal_error(_("Cannot select data (%s) from table"),
- column_opt->answer);
- for (i = 0; i < cvarr.n_values; i++) {
- G_debug(4, "cat = %d %s = %d", cvarr.value[i].cat,
- column_opt->answer,
- (cvarr.ctype ==
- DB_C_TYPE_INT ? cvarr.value[i].val.i : (int)cvarr.value[i].
- val.d));
- }
- /*Get the sorted data */
- ret = db_CatValArray_sort_by_value(&cvarr);
- if (ret == DB_FAILED)
- G_fatal_error("Could not sort array of values..");
- data = (double *)G_malloc((nrec) * sizeof(double));
- for (i = 0; i < nrec; i++)
- data[i] = 0.0;
- ctype = cvarr.ctype;
- if (ctype == DB_C_TYPE_INT) {
- for (i = 0; i < nrec; i++)
- data[i] = cvarr.value[i].val.i;
- }
- else {
- for (i = 0; i < nrec; i++)
- data[i] = cvarr.value[i].val.d;
- }
- db_CatValArray_sort(&cvarr);
- /*Get the list of relevant cats if where option is given */
- if (where_opt->answer) {
- ncat = db_select_int(driver, fi->table, fi->key, where_opt->answer,
- &cats);
- chcat = 1;
- Vect_array_to_cat_list(cats, ncat, Clist);
- }
- db_close_database(driver);
- db_shutdown_driver(driver);
- /*get border line width */
- default_width = atoi(bwidth_opt->answer);
- if (default_width < 0)
- default_width = 0;
- /*get border line color */
- bcolor = G_standard_color_rgb(WHITE);
- ret = G_str_to_color(bcolor_opt->answer, &r, &g, &b);
- if (ret == 1) {
- has_color = 1;
- bcolor.r = r;
- bcolor.g = g;
- bcolor.b = b;
- }
- else if (ret == 2) { /* none */
- has_color = 0;
- }
- else if (ret == 0) { /* error */
- G_fatal_error(_("Unknown color: [%s]"), bcolor_opt->answer);
- }
- /* if both class breaks and (algorithm or classnumber) are given, give precedence to class
- * breaks
- */
- if (breaks_opt->answers) {
- if (algo_opt->answer || nbclass_opt->answer)
- G_warning(_("You gave both manual breaks and a classification algorithm or a number of classes. The manual breaks have precedence and will thus be used."));
- /*Get class breaks */
- nbreaks = 0;
- while (breaks_opt->answers[nbreaks] != NULL)
- nbreaks++;
- nclass = nbreaks + 1; /*add one since breaks do not include min and max values */
- G_debug(3, "nclass = %d", nclass);
- breakpoints = (double *)G_malloc((nbreaks) * sizeof(double));
- for (i = 0; i < nbreaks; i++)
- breakpoints[i] = atof(breaks_opt->answers[i]);
- }
- else {
- if (algo_opt->answer && nbclass_opt->answer) {
- nclass = atoi(nbclass_opt->answer);
- nbreaks = nclass - 1; /* we need one less classbreaks (min and
- * max exluded) than classes */
- breakpoints = (double *)G_malloc((nbreaks) * sizeof(double));
- for (i = 0; i < nbreaks; i++)
- breakpoints[i] = 0;
- /* Get classbreaks for given algorithm and number of classbreaks.
- * class_info takes any info coming from the classification algorithms */
- class_info = AS_class_apply_algorithm(AS_option_to_algorithm(algo_opt),
- data, nrec, &nbreaks,
- breakpoints);
- }
- else {
- G_fatal_error(_("You must either give classbreaks or a classification algorithm"));
- }
- };
- /* Fill colors */
- colors = (struct color_rgb *)G_malloc(nclass * sizeof(struct color_rgb));
- if (colors_opt->answers != NULL) {
- for (i = 0; i < nclass; i++) {
- if (colors_opt->answers[i] == NULL)
- G_fatal_error(_("Not enough colors or error in color specifications.\nNeed %i entries for 'colors' parameter"),
- nclass);
- ret = G_str_to_color(colors_opt->answers[i], &r, &g, &b);
- if (!ret)
- G_fatal_error(_("Error interpreting color %s"),
- colors_opt->answers[i]);
- colors[i].r = r;
- colors[i].g = g;
- colors[i].b = b;
- }
- }
- if (!nodraw_flag->answer) {
- /* Now's let's prepare the actual plotting */
- D_open_driver();
-
- D_setup(0);
- if (verbose)
- G_message(_("Plotting ..."));
- overlap = 1;
- Vect_get_map_box(&Map, &box);
- if (window.proj != PROJECTION_LL) {
- overlap =
- G_window_percentage_overlap(&window, box.N, box.S,
- box.E, box.W);
- G_debug(1, "overlap = %f \n", overlap);
- }
- if (overlap == 0) {
- G_message(_("The bounding box of the map is outside the current region, "
- "nothing drawn."));
- stat = 0;
- }
- else {
- if (overlap < 1)
- Vect_set_constraint_region(&Map, window.north, window.south,
- window.east, window.west,
- PORT_DOUBLE_MAX, -PORT_DOUBLE_MAX);
- /* default line width */
- D_line_width(default_width);
- if (Vect_get_num_primitives(&Map, GV_BOUNDARY) > 0)
- stat =
- dareatheme(&Map, Clist, &cvarr, breakpoints, nbreaks, colors,
- has_color ? &bcolor : NULL, chcat, &window,
- default_width);
- else if ((Vect_get_num_primitives(&Map, GV_POINT) > 0) ||
- (Vect_get_num_primitives(&Map, GV_LINE) > 0)){
- stat = display_lines(&Map, Clist, chcat, icon_opt->answer, size,
- default_width, &cvarr, breakpoints, nbreaks, colors,
- has_color ? &bcolor : NULL);
- }
- /* reset line width: Do we need to get line width from display
- * driver (not implemented)? It will help restore previous line
- * width (not just 0) determined by another module (e.g.,
- * d.linewidth). */
- D_line_width(0);
- } /* end window check if */
- D_save_command(G_recreate_command());
- D_close_driver();
- } /* end of nodraw_flag condition */
- frequencies = (int *)G_malloc((nbreaks + 1) * sizeof(int));
- for (i = 0; i < nbreaks + 1; i++)
- frequencies[i] = 0.0;
- AS_class_frequencies(data, nrec, nbreaks, breakpoints, frequencies);
- /*Get basic statistics about the data */
- AS_basic_stats(data, nrec, &stats);
- /* Print statistics */
- G_verbose_message(_("\nTotal number of records: %.0f\n"),
- stats.count);
- G_verbose_message(_("Classification of %s into %i classes\n"),
- column_opt->answer, nbreaks + 1);
- G_verbose_message(_("Using algorithm: *** %s ***\n"),
- algo_opt->answer);
- G_verbose_message(_("Mean: %f\tStandard deviation = %f\n"),
- stats.mean, stats.stdev);
-
- if (G_strcasecmp(algo_opt->answer, "dis") == 0)
- G_verbose_message(_("Last chi2 = %f\n"), class_info);
- if (G_strcasecmp(algo_opt->answer, "std") == 0)
- G_verbose_message(_("Stdev multiplied by %.4f to define step\n"),
- class_info);
- G_verbose_message("\n");
- /* Print legfile to stdout */
- if ((legend_flag->answer) ||
- ((legend_file_opt->answer) && (strcmp(legend_file_opt->answer,"-") == 0))) {
- while (TRUE) {
- nfeatures = Vect_get_num_primitives(&Map, GV_POINT);
- if (nfeatures > 0) {
- write_into_legend_file("stdout", icon_opt->answer,
- title, stats.min, stats.max, breakpoints,
- nbreaks, size, bcolor, colors, default_width,
- frequencies, "point");
- break;
- }
- nfeatures = Vect_get_num_primitives(&Map, GV_LINE);
- if (nfeatures > 0) {
- write_into_legend_file("stdout", icon_line_opt->answer,
- title, stats.min, stats.max, breakpoints,
- nbreaks, size, bcolor, colors, default_width,
- frequencies, "line");
- break;
- }
- nfeatures = Vect_get_num_primitives(&Map, GV_BOUNDARY);
- if (nfeatures > 0) {
- write_into_legend_file("stdout", icon_area_opt->answer,
- title, stats.min, stats.max, breakpoints,
- nbreaks, size, bcolor, colors, default_width,
- frequencies, "area");
- break;
- }
- }
- }
- /* Write into default legfile */
- leg_file = getenv("GRASS_LEGEND_FILE");
- if (leg_file && !vlegend_flag->answer) {
- while (TRUE) {
- nfeatures = Vect_get_num_primitives(&Map, GV_POINT);
- if (nfeatures > 0) {
- write_into_legend_file(leg_file, icon_opt->answer,
- title, stats.min, stats.max, breakpoints,
- nbreaks, size, bcolor, colors, default_width,
- frequencies, "point");
- break;
- }
- nfeatures = Vect_get_num_primitives(&Map, GV_LINE);
- if (nfeatures > 0) {
- write_into_legend_file(leg_file, icon_line_opt->answer,
- title, stats.min, stats.max, breakpoints,
- nbreaks, size, bcolor, colors, default_width,
- frequencies, "line");
- break;
- }
- nfeatures = Vect_get_num_primitives(&Map, GV_BOUNDARY);
- if (nfeatures > 0) {
- write_into_legend_file(leg_file, icon_area_opt->answer,
- title, stats.min, stats.max, breakpoints,
- nbreaks, size, bcolor, colors, default_width,
- frequencies, "area");
- break;
- }
- }
- }
- /* Write into user-specified output file */
- if (legend_file_opt->answer) {
- while (TRUE) {
- nfeatures = Vect_get_num_primitives(&Map, GV_POINT);
- if (nfeatures > 0) {
- write_into_legend_file(legend_file_opt->answer, icon_opt->answer,
- title, stats.min, stats.max, breakpoints,
- nbreaks, size, bcolor, colors, default_width,
- frequencies, "point");
- break;
- }
- nfeatures = Vect_get_num_primitives(&Map, GV_LINE);
- if (nfeatures > 0) {
- write_into_legend_file(legend_file_opt->answer, icon_line_opt->answer,
- title, stats.min, stats.max, breakpoints,
- nbreaks, size, bcolor, colors, default_width,
- frequencies, "line");
- break;
- }
- nfeatures = Vect_get_num_primitives(&Map, GV_BOUNDARY);
- if (nfeatures > 0) {
- write_into_legend_file(legend_file_opt->answer, icon_area_opt->answer,
- title, stats.min, stats.max, breakpoints,
- nbreaks, size, bcolor, colors, default_width,
- frequencies, "area");
- break;
- }
- }
- }
- if (verbose)
- G_done_msg(" ");
- Vect_close(&Map);
- Vect_destroy_cat_list(Clist);
- exit(stat);
- }
- int cmp(const void *a, const void *b)
- {
- return (strcmp(*(char **)a, *(char **)b));
- }
- /* adopted from r.colors */
- char *icon_files(void)
- {
- char **list, *ret;
- char buf[GNAME_MAX], path[GPATH_MAX], path_i[GPATH_MAX];
- int i, count;
- size_t len;
- DIR *dir, *dir_i;
- struct dirent *d, *d_i;
- list = NULL;
- len = 0;
- sprintf(path, "%s/etc/symbol", G_gisbase());
- dir = opendir(path);
- if (!dir)
- return NULL;
- count = 0;
- /* loop over etc/symbol */
- while ((d = readdir(dir))) {
- if (d->d_name[0] == '.')
- continue;
- sprintf(path_i, "%s/etc/symbol/%s", G_gisbase(), d->d_name);
- dir_i = opendir(path_i);
- if (!dir_i)
- continue;
- /* loop over each directory in etc/symbols */
- while ((d_i = readdir(dir_i))) {
- if (d_i->d_name[0] == '.')
- continue;
- list = G_realloc(list, (count + 1) * sizeof(char *));
- sprintf(buf, "%s/%s", d->d_name, d_i->d_name);
- list[count++] = G_store(buf);
- len += strlen(d->d_name) + strlen(d_i->d_name) + 2; /* '/' + ',' */
- }
- closedir(dir_i);
- }
- closedir(dir);
- qsort(list, count, sizeof(char *), cmp);
- if (len > 0) {
- ret = G_malloc((len + 1) * sizeof(char)); /* \0 */
- *ret = '\0';
- for (i = 0; i < count; i++) {
- if (i > 0)
- strcat(ret, ",");
- strcat(ret, list[i]);
- G_free(list[i]);
- }
- G_free(list);
- }
- else {
- ret = G_store("");
- }
- return ret;
- }
|