/* $Id: gui-config.c,v 1.26 2006/03/20 22:42:24 mcmahill Exp $ */ /* * COPYRIGHT * * PCB, interactive printed circuit board design * Copyright (C) 1994,1995,1996 Thomas Nau * * 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. * */ /* This file written by Bill Wilson for the PCB Gtk port. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_STDLIB_H #include #endif #include "hid.h" #include "../hidint.h" #include "gtkhid.h" #include "gui.h" #include "global.h" #include "action.h" #include "change.h" #include "file.h" #include "error.h" #include "draw.h" #include "set.h" #include #ifdef HAVE_LIBDMALLOC #include #endif RCSID ("$Id: gui-config.c,v 1.26 2006/03/20 22:42:24 mcmahill Exp $"); enum ConfigType { CONFIG_Boolean, CONFIG_Integer, CONFIG_Real, CONFIG_String, CONFIG_Unused }; typedef struct { gchar *name; enum ConfigType type; void *value; } ConfigAttribute; enum ColorTypes { MISC_COLOR, MISC_SELECTED_COLOR, LAYER_COLOR, LAYER_SELECTED_COLOR }; typedef struct { HID_Attribute *attributes; enum ColorTypes type; GdkColor color; gboolean color_is_mapped; } ConfigColor; static GList *config_color_list, *lib_newlib_list; static gchar *lib_newlib_config, *groups_config, /* Saved in the config */ *routes_config, *groups_override, /* Overrides from command line */ *routes_override, *board_size_override; static gchar *color_file; extern void ghid_set_special_colors(HID_Attribute *ha); #define PCB_CONFIG_DIR ".pcb" #define PCB_CONFIG_FILE "preferences" #define PCB_COLORS_DIR "colors" static gchar *config_dir, *color_dir; /* CONFIG_Unused types are expected to be found in main_attribute_list and | will be assigned the type found there. NULL value pointers here are | also expected to be assigned values from the main_attribute_list. */ /* PinoutFont also not used anymore */ static ConfigAttribute config_attributes[] = { {"gui-compact-horizontal", CONFIG_Boolean, &_ghidgui.compact_horizontal}, {"gui-title-window", CONFIG_Boolean, &_ghidgui.ghid_title_window}, {"use-command-window", CONFIG_Boolean, &_ghidgui.use_command_window}, {"save-in-tmp", CONFIG_Unused, NULL}, {"grid-units-mm", CONFIG_Unused, NULL}, {"history-size", CONFIG_Integer, &_ghidgui.history_size}, {"auto-pan-speed", CONFIG_Integer, &_ghidgui.auto_pan_speed}, {"log-window-width", CONFIG_Integer, &_ghidgui.log_window_width}, {"log-window-height", CONFIG_Integer, &_ghidgui.log_window_height}, {"library-window-height", CONFIG_Integer, &_ghidgui.library_window_height}, {"netlist-window-height", CONFIG_Integer, &_ghidgui.netlist_window_height}, {"keyref-window-width", CONFIG_Integer, &_ghidgui.keyref_window_width}, {"keyref-window-height", CONFIG_Integer, &_ghidgui.keyref_window_height}, {"text-scale", CONFIG_Unused, NULL}, {"via-thickness", CONFIG_Unused, NULL}, {"via-drilling-hole", CONFIG_Unused, NULL}, {"backup-interval", CONFIG_Unused, NULL}, {"line-thickness", CONFIG_Unused, NULL}, {"rat-thickness", CONFIG_Unused, NULL}, {"bloat", CONFIG_Unused, NULL}, {"shrink", CONFIG_Unused, NULL}, {"min-width", CONFIG_Unused, NULL}, {"min-silk", CONFIG_Unused, NULL}, {"default-PCB-width", CONFIG_Unused, NULL}, {"default-PCB-height", CONFIG_Unused, NULL}, {"grid-increment-mil", CONFIG_Unused, NULL}, {"grid-increment-mm", CONFIG_Unused, NULL}, {"size-increment-mil", CONFIG_Unused, NULL}, {"size-increment-mm", CONFIG_Unused, NULL}, {"line-increment-mil", CONFIG_Unused, NULL}, {"line-increment-mm", CONFIG_Unused, NULL}, {"clear-increment-mil", CONFIG_Unused, NULL}, {"clear-increment-mm", CONFIG_Unused, NULL}, {"groups", CONFIG_String, &groups_config}, /* FIXME */ {"route-styles", CONFIG_String, &Settings.Routes}, {"library-newlib", CONFIG_String, &lib_newlib_config}, {"color-file", CONFIG_String, &color_file}, /* FIXME: construct layer-names- in a list */ {"layer-name-1", CONFIG_Unused, NULL}, {"layer-name-2", CONFIG_Unused, NULL}, {"layer-name-3", CONFIG_Unused, NULL}, {"layer-name-4", CONFIG_Unused, NULL}, {"layer-name-5", CONFIG_Unused, NULL}, {"layer-name-6", CONFIG_Unused, NULL}, {"layer-name-7", CONFIG_Unused, NULL}, {"layer-name-8", CONFIG_Unused, NULL}, }; static FILE * config_file_open(gchar *mode) { FILE *f; gchar *homedir, *fname; homedir = (gchar *) g_get_home_dir(); if (!homedir) { g_message("config_file_open: Can't get home directory!"); return NULL; } if (!config_dir) { config_dir = g_build_path(G_DIR_SEPARATOR_S, homedir, PCB_CONFIG_DIR, NULL); if ( !g_file_test(config_dir, G_FILE_TEST_IS_DIR) && mkdir(config_dir, 0755) < 0 ) { g_message("config_file_open: Can't make \"%s\" directory!", config_dir); g_free(config_dir); config_dir = NULL; return NULL; } } if (!color_dir) /* Convenient to make the color dir here */ { color_dir = g_build_path(G_DIR_SEPARATOR_S, config_dir, PCB_COLORS_DIR, NULL); if (!g_file_test(color_dir, G_FILE_TEST_IS_DIR)) { if (mkdir(color_dir, 0755) < 0) { g_message("config_file_open: Can't make \"%s\" directory!", color_dir); g_free(color_dir); color_dir = NULL; } fname = g_build_path(G_DIR_SEPARATOR_S, color_dir, "Default", NULL); dup_string(&color_file, fname); g_free(fname); } } fname = g_build_path(G_DIR_SEPARATOR_S, config_dir, PCB_CONFIG_FILE, NULL); f = fopen(fname, mode); g_free(fname); return f; } static ConfigAttribute * lookup_config_attribute(gchar *name, gboolean if_null_value) { ConfigAttribute *ca; for (ca = &config_attributes[0]; ca < &config_attributes[0] + G_N_ELEMENTS(config_attributes); ++ca) { if (name && (!strcmp(name, ca->name))) { if (ca->value && if_null_value) break; return ca; } } return NULL; } void ghid_config_init(void) { HID_AttrNode *ha; HID_Attribute *a; ConfigAttribute *ca, dummy_attribute; ConfigColor *cc; gint len; ghidgui->n_mode_button_columns = 3; ghidgui->small_label_markup = TRUE; ghidgui->auto_pan_on = TRUE; ghidgui->auto_pan_speed = 3; dup_string(&color_file, ""); for (ha=hid_attr_nodes; ha; ha=ha->next) { for (a = ha->attributes; a < ha->attributes + ha->n; ++a) { if (!a->value) continue; if ((ca = lookup_config_attribute(a->name, TRUE)) == NULL) ca = &dummy_attribute; ca->value = a->value; /* Typically &Setting.xxx */ ca->type = CONFIG_Unused; switch (a->type) { case HID_Boolean: *(char *)a->value = a->default_val.int_value; ca->type = CONFIG_Boolean; break; case HID_Integer: *(int *)a->value = a->default_val.int_value; ca->type = CONFIG_Integer; break; case HID_Real: *(double *)a->value = a->default_val.real_value; ca->type = CONFIG_Real; break; case HID_String: if (!a->name) break; *(char **)a->value = g_strdup(a->default_val.str_value); ca->type = CONFIG_String; len = strlen(a->name); if (len < 7 || strstr(a->name, "color") == NULL) break; cc = g_new0(ConfigColor, 1); cc->attributes = a; if (!strncmp(a->name, "layer-color", 11)) cc->type = LAYER_COLOR; else if (!strncmp(a->name, "layer-selected-color", 20)) cc->type = LAYER_SELECTED_COLOR; else if (!strncmp(a->name + len - 14, "selected-color",14)) cc->type = MISC_SELECTED_COLOR; else cc->type = MISC_COLOR; config_color_list = g_list_append(config_color_list, cc); break; case HID_Enum: *(int *)a->value = a->default_val.int_value; break; case HID_Label: case HID_Mixed: case HID_Path: break; default: abort(); } } } } static gint parse_option_line(gchar *line, gchar **option_result, gchar **arg_result) { gchar *s, *ss, option[64], arg[512]; gint argc = 1; if (option_result) *option_result = NULL; if (arg_result) *arg_result = NULL; s = line; while (*s == ' ' || *s == '\t') ++s; if (!*s || *s == '\n' || *s == '#' || *s == '[') return 0; if ((ss = strchr(s, '\n')) != NULL) *ss = '\0'; arg[0] = '\0'; sscanf(s, "%63s %511[^\n]", option, arg); s = option; /* Strip trailing ':' or '=' */ while (*s && *s != ':' && *s != '=') ++s; *s = '\0'; s = arg; /* Strip leading ':', '=', and whitespace */ while (*s == ' ' || *s == '\t' || *s == ':' || *s == '=' || *s == '"') ++s; if ((ss = strchr(s, '"')) != NULL) *ss = '\0'; if (option_result) *option_result = g_strdup(option); if (arg_result && *s) { *arg_result = g_strdup(s); ++argc; } return argc; } static gboolean set_config_attribute(gchar *option, gchar *arg) { ConfigAttribute *ca; #if 0 struct lconv *lc; gchar locale_point, *comma_point, *period_point; /* Until LC_NUMERIC is totally resolved, check if we need to decimal | point convert. Ultimately, data files will be POSIX and gui | presentation (hence the config file reals) will be in the users locale. */ lc = localeconv(); locale_point = *lc->decimal_point; #endif if ((ca = lookup_config_attribute(option, FALSE)) == NULL) return FALSE; switch (ca->type) { case CONFIG_Boolean: *(gchar *)ca->value = (gchar) atoi(arg); break; case CONFIG_Integer: *(gint *)ca->value = atoi(arg); break; case CONFIG_Real: /* Hopefully temporary locale decimal point check: */ #if 0 comma_point = strrchr(arg, ','); period_point = strrchr(arg, '.'); if (comma_point && *comma_point != locale_point) *comma_point = locale_point; else if (period_point && *period_point != locale_point) *period_point = locale_point; #endif *(double *)ca->value = atof(arg); break; case CONFIG_String: dup_string((char **)ca->value, arg ? arg : ""); break; default: break; } return TRUE; } static void config_file_read(void) { FILE *f; gchar buf[512], *option, *arg; if ((f = config_file_open("r")) == NULL) return; buf[0] = '\0'; while (fgets(buf, sizeof(buf), f)) { if (parse_option_line(buf, &option, &arg) > 0) set_config_attribute(option, arg); g_free(option); g_free(arg); } } static void config_colors_write(gchar *path) { FILE *f; GList *list; HID_Attribute *ha; ConfigColor *cc; if ((f = fopen(path, "w")) == NULL) return; for (list = config_color_list; list; list = list->next) { cc = (ConfigColor *) list->data; ha = cc->attributes; fprintf(f, "%s =\t%s\n", ha->name, *(char **) ha->value); } fclose(f); } static gboolean config_colors_read(gchar *path) { FILE *f; GList *list; ConfigColor *cc; HID_Attribute *ha; gchar *s, buf[512], option[64], arg[512]; if (!path || !*path || (f = fopen(path, "r")) == NULL) return FALSE; while (fgets(buf, sizeof(buf), f)) { sscanf(buf, "%63s %511[^\n]", option, arg); s = option; /* Strip trailing ':' or '=' */ while (*s && *s != ':' && *s != '=') ++s; *s = '\0'; s = arg; /* Strip leading ':', '=', and whitespace */ while (*s == ' ' || *s == '\t' || *s == ':' || *s == '=') ++s; for (list = config_color_list; list; list = list->next) { cc = (ConfigColor *) list->data; ha = cc->attributes; if (!strcmp(option, ha->name)) { *(char **) ha->value = g_strdup(s); cc->color_is_mapped = FALSE; ghid_set_special_colors(ha); break; } } } fclose(f); ghid_layer_buttons_color_update(); return TRUE; } static gchar * expand_dir(gchar *dir) { gchar *s; if (*dir == '~') s = g_build_filename((gchar *) g_get_home_dir(), dir + 1, NULL); else s = g_strdup(dir); return s; } static void add_to_paths_list(GList **list, gchar *path_string) { gchar *p, *paths; paths = g_strdup(path_string); for (p = strtok(paths, ":"); p && *p; p = strtok(NULL, ":")) *list = g_list_prepend(*list, expand_dir(p)); g_free(paths); } /* Parse command line code borrowed from hid/common/hidinit.c */ static void parse_optionv(gint *argc, gchar ***argv, gboolean from_cmd_line) { HID_AttrNode *ha; HID_Attribute *a; gchar *ep; gint e, ok, offset; gboolean matched = FALSE; offset = from_cmd_line ? 2 : 0; while ( *argc && ( ((*argv)[0][0] == '-' && (*argv)[0][1] == '-') || !from_cmd_line ) ) { for (ha = hid_attr_nodes; ha; ha = ha->next) { for (a = ha->attributes; a < ha->attributes + ha->n; ++a) { if (!a->name || strcmp ((*argv)[0] + offset, a->name)) continue; switch(a->type) { case HID_Label: break; case HID_Integer: if (a->value) *(int *) a->value = strtol ((*argv)[1], 0, 0); else a->default_val.int_value = strtol ((*argv)[1], 0, 0); (*argc)--; (*argv)++; break; case HID_Real: if (a->value) *(double *) a->value = strtod ((*argv)[1], 0); else a->default_val.real_value = strtod ((*argv)[1], 0); (*argc)--; (*argv)++; break; case HID_String: if (a->value) *(char **) a->value = (*argv)[1]; else a->default_val.str_value = (*argv)[1]; (*argc)--; (*argv)++; break; case HID_Boolean: if (a->value) *(char *) a->value = 1; else a->default_val.int_value = 1; break; case HID_Mixed: abort (); a->default_val.real_value = strtod ((*argv)[1], &ep); goto do_enum; case HID_Enum: ep = (*argv)[1]; do_enum: ok = 0; for (e = 0; a->enumerations[e]; e++) if (strcmp (a->enumerations[e], (*argv)[1]) == 0) { ok = 1; a->default_val.int_value = e; a->default_val.str_value = ep; break; } if (!ok) { fprintf (stderr, "ERROR: \"%s\" is an unknown value for the --%s option\n", (*argv)[1], a->name); exit (1); } (*argc)--; (*argv)++; break; case HID_Path: abort (); a->default_val.str_value = (*argv)[1]; (*argc)--; (*argv)++; break; } (*argc)--; (*argv)++; break; } if (a < ha->attributes + ha->n) matched = TRUE; } if (!matched) { if (from_cmd_line) { fprintf (stderr, "unrecognized option: %s\n", (*argv)[0]); exit (1); } else // ghid_log("unrecognized option: %s\n", (*argv)[0]); fprintf (stderr, "unrecognized option: %s\n", (*argv)[0]); } } (*argc)++; (*argv)--; } static void load_rc_file(gchar *path) { FILE *f; gchar buf[1024], *av[2], **argv; gint argc; f = fopen(path, "r"); if (!f) return; if (Settings.verbose) printf("Loading pcbrc file: %s\n", path); while (fgets(buf, sizeof(buf), f)) { argv = &(av[0]); if ((argc = parse_option_line(buf, &av[0], &av[1])) > 0) parse_optionv(&argc, &argv, FALSE); g_free(av[0]); g_free(av[1]); } fclose(f); } static void load_rc_files(void) { gchar *path; load_rc_file("/etc/pcbrc"); load_rc_file("/usr/local/etc/pcbrc"); path = g_build_filename(PCBLIBDIR, "pcbrc", NULL); load_rc_file(path); g_free(path); path = g_build_filename((gchar *) g_get_home_dir(), ".pcb/pcbrc", NULL); load_rc_file(path); g_free(path); load_rc_file("pcbrc"); } void ghid_config_files_read(gint *argc, gchar ***argv) { GList *list; gchar *str, *dir; gint width, height; ghidgui = &_ghidgui; ghid_config_init(); load_rc_files(); config_file_read(); config_colors_read(color_file); (*argc)--; (*argv)++; parse_optionv(argc, argv, TRUE); if (groups_override) dup_string(&Settings.Groups, groups_override); else if (groups_config) dup_string(&Settings.Groups, groups_config); if (routes_override) dup_string(&Settings.Routes, routes_override); else if (routes_config) dup_string(&Settings.Routes, routes_config); if ( board_size_override && sscanf(board_size_override, "%dx%d", &width, &height) == 2 ) { Settings.MaxWidth = TO_PCB_UNITS(width); Settings.MaxHeight = TO_PCB_UNITS(height); } if (lib_newlib_config && *lib_newlib_config) add_to_paths_list(&lib_newlib_list, lib_newlib_config); for (list = lib_newlib_list; list; list = list->next) { str = Settings.LibraryTree; dir = expand_dir((gchar *) list->data); Settings.LibraryTree = g_strconcat(str, ":", dir, NULL); g_free(dir); g_free(str); } } void ghid_config_files_write(void) { FILE *f; ConfigAttribute *ca; if (!ghidgui->config_modified || (f = config_file_open("w")) == NULL) return; fprintf(f, "### PCB configuration file. ###\n"); for (ca = &config_attributes[0]; ca < &config_attributes[0] + G_N_ELEMENTS(config_attributes); ++ca) { switch (ca->type) { case CONFIG_Boolean: fprintf(f, "%s = %d\n", ca->name, (gint) *(gchar *)ca->value); break; case CONFIG_Integer: fprintf(f, "%s = %d\n", ca->name, *(gint *)ca->value); break; case CONFIG_Real: fprintf(f, "%s = %f\n", ca->name, *(double *)ca->value); break; case CONFIG_String: if (*(char **)ca->value == NULL) fprintf (f, "# %s = NULL\n", ca->name); else fprintf(f, "%s = %s\n", ca->name, *(char **)ca->value); break; default: break; } } fclose(f); ghidgui->config_modified = FALSE; } /* =================== OK, now the gui stuff ====================== */ static GtkWidget *config_window; /* -------------- The General config page ---------------- */ static gint config_backup_interval_tmp; static void config_command_window_toggle_cb(GtkToggleButton *button, gpointer data) { gboolean active = gtk_toggle_button_get_active(button); static gboolean holdoff; if (holdoff) return; /* Can't toggle into command window mode if the status line command | entry is active. */ if (ghidgui->command_entry_status_line_active) { holdoff = TRUE; gtk_toggle_button_set_active(button, FALSE); holdoff = FALSE; return; } ghidgui->use_command_window = active; ghid_command_use_command_window_sync(); } static void config_compact_toggle_cb(GtkToggleButton *button, gpointer data) { gboolean active = gtk_toggle_button_get_active(button); ghidgui->compact_horizontal = active; if (active) { gtk_container_remove(GTK_CONTAINER(ghidgui->top_hbox), ghidgui->position_hbox); gtk_box_pack_end(GTK_BOX(ghidgui->compact_vbox), ghidgui->position_hbox, FALSE, FALSE, 0); } else { gtk_container_remove(GTK_CONTAINER(ghidgui->compact_vbox), ghidgui->position_hbox); gtk_box_pack_end(GTK_BOX(ghidgui->top_hbox), ghidgui->position_hbox, FALSE, FALSE, 0); } ghid_set_status_line_label(); ghidgui->config_modified = TRUE; } static void config_title_window_cb(GtkToggleButton *button, gpointer data) { gboolean active = gtk_toggle_button_get_active(button); ghidgui->ghid_title_window = active; ghid_window_set_name_label(ghidgui->name_label_string); ghidgui->config_modified = TRUE; } static void config_general_toggle_cb(GtkToggleButton *button, gint *setting) { *setting = gtk_toggle_button_get_active(button); ghidgui->config_modified = TRUE; } static void config_backup_spin_button_cb(GtkSpinButton *spin_button, gpointer data) { config_backup_interval_tmp = gtk_spin_button_get_value_as_int(spin_button); ghidgui->config_modified = TRUE; } static void config_history_spin_button_cb(GtkSpinButton *spin_button, gpointer data) { ghidgui->history_size = gtk_spin_button_get_value_as_int(spin_button); ghidgui->config_modified = TRUE; } static void config_auto_pan_speed_spin_button_cb(GtkSpinButton *spin_button, gpointer data) { ghidgui->auto_pan_speed = gtk_spin_button_get_value_as_int(spin_button); ghidgui->config_modified = TRUE; } static void config_general_tab_create(GtkWidget *tab_vbox) { GtkWidget *vbox; config_backup_interval_tmp = Settings.BackupInterval; gtk_container_set_border_width(GTK_CONTAINER(tab_vbox), 6); vbox = ghid_category_vbox(tab_vbox, _("Enables"), 4, 2, TRUE, TRUE); ghid_check_button_connected(vbox, NULL, ghidgui->use_command_window, TRUE, FALSE, FALSE, 2, config_command_window_toggle_cb, NULL, _("Use separate window for command entry")); ghid_check_button_connected(vbox, NULL, ghidgui->compact_horizontal, TRUE, FALSE, FALSE, 2, config_compact_toggle_cb, NULL, _("Compact horizontal top window for narrow screens")); ghid_check_button_connected(vbox, NULL, ghidgui->ghid_title_window, TRUE, FALSE, FALSE, 2, config_title_window_cb, NULL, _("Put layout name on the window title bar")); vbox = ghid_category_vbox(tab_vbox, _("Backups"), 4, 2, TRUE, TRUE); ghid_check_button_connected(vbox, NULL, Settings.SaveInTMP, TRUE, FALSE, FALSE, 2, config_general_toggle_cb, &Settings.SaveInTMP, _("If layout is modified at exit, save into /tmp/PCB.%i.save")); ghid_spin_button(vbox, NULL, Settings.BackupInterval, 0.0, 60 * 60, 60.0, 600.0, 0, 0, config_backup_spin_button_cb, NULL, FALSE, _("Seconds between auto backups to /tmp/PCB.%i.save\n" "(set to zero to disable auto backups)")); vbox = ghid_category_vbox(tab_vbox, _("Misc"), 4, 2, TRUE, TRUE); ghid_spin_button(vbox, NULL, ghidgui->history_size, 5.0, 25.0, 1.0, 1.0, 0, 0, config_history_spin_button_cb, NULL, FALSE, _("Number of commands to remember in the history list")); ghid_spin_button(vbox, NULL, ghidgui->auto_pan_speed, 1.0, 10.0, 1.0, 1.0, 0, 0, config_auto_pan_speed_spin_button_cb, NULL, FALSE, _("Auto pan speed")); } static gint backup_timeout_cb(gpointer data) { Backup (); ghid_config_files_write(); return TRUE; /* restarts timer */ } void ghid_config_start_backup_timer(void) { static gint timeout_id = 0; if (timeout_id) gtk_timeout_remove(timeout_id); timeout_id = 0; if (Settings.BackupInterval > 0) timeout_id = gtk_timeout_add(1000 * Settings.BackupInterval, (GtkFunction) backup_timeout_cb, NULL); } static void config_general_apply(void) { if (config_backup_interval_tmp != Settings.BackupInterval) ghid_config_start_backup_timer(); } /* -------------- The Sizes config page ---------------- */ #define STEP0_SMALL_SIZE (Settings.grid_units_mm ? 0.005 : 0.1) #define STEP1_SMALL_SIZE (Settings.grid_units_mm ? 0.05 : 1.0) #define STEP0_SIZE (Settings.grid_units_mm ? 5.0 : 100) #define STEP1_SIZE (Settings.grid_units_mm ? 25.0 : 1000) #define SPIN_DIGITS (Settings.grid_units_mm ? 3 : 1) static GtkWidget *config_sizes_vbox, *config_sizes_tab_vbox, *config_text_spin_button; static GtkWidget *use_board_size_default_button, *use_drc_sizes_default_button; static gint new_board_width, new_board_height; static void config_sizes_apply(void) { gboolean active; active = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(use_board_size_default_button)); if (active) { Settings.MaxWidth = new_board_width; Settings.MaxHeight = new_board_height; ghidgui->config_modified = TRUE; } active = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(use_drc_sizes_default_button)); if (active) { Settings.Bloat = PCB->Bloat; Settings.Shrink = PCB->Shrink; Settings.minWid = PCB->minWid; Settings.minSlk = PCB->minSlk; Settings.minDrill = PCB->minDrill; Settings.minRing = PCB->minRing; ghidgui->config_modified = TRUE; } if (PCB->MaxWidth != new_board_width || PCB->MaxHeight != new_board_height) ChangePCBSize(new_board_width, new_board_height); } static void text_spin_button_cb(GtkSpinButton *spin, gint *dst) { *dst = gtk_spin_button_get_value_as_int(spin); ghidgui->config_modified = TRUE; ghid_set_status_line_label(); } static void size_spin_button_cb(GtkSpinButton *spin, gint *dst) { gdouble value; value = gtk_spin_button_get_value(spin); *dst = TO_PCB_UNITS(value); ghidgui->config_modified = TRUE; } static void config_sizes_tab_create(GtkWidget *tab_vbox) { GtkWidget *table, *vbox, *hbox, *label; gchar *str; /* Need a vbox we can destroy if user changes grid units. */ if (!config_sizes_vbox) { vbox = gtk_vbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(tab_vbox), vbox, FALSE, FALSE, 0); gtk_container_set_border_width(GTK_CONTAINER(vbox), 6); config_sizes_vbox = vbox; config_sizes_tab_vbox = tab_vbox; } str = g_strdup_printf(_("%s grid units are selected"), Settings.grid_units_mm ? "mm" : "mil"); label = gtk_label_new(""); gtk_label_set_use_markup(GTK_LABEL(label), TRUE); gtk_label_set_markup(GTK_LABEL(label), str); g_free(str); gtk_box_pack_start(GTK_BOX(config_sizes_vbox), label, FALSE, FALSE, 4); /* ---- Board Size ---- */ vbox = ghid_category_vbox(config_sizes_vbox, _("Board Size"), 4, 2, TRUE, TRUE); hbox = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); table = gtk_table_new(2, 2, FALSE); gtk_box_pack_start(GTK_BOX(hbox), table, FALSE, FALSE, 0); gtk_table_set_col_spacings(GTK_TABLE(table), 6); gtk_table_set_row_spacings(GTK_TABLE(table), 3); new_board_width = PCB->MaxWidth; new_board_height = PCB->MaxHeight; ghid_table_spin_button(table, 0, 0, NULL, FROM_PCB_UNITS(PCB->MaxWidth), FROM_PCB_UNITS(MIN_SIZE), FROM_PCB_UNITS(MAX_COORD), STEP0_SIZE, STEP1_SIZE, SPIN_DIGITS, 0, size_spin_button_cb, &new_board_width, FALSE, _("Width")); ghid_table_spin_button(table, 1, 0, NULL, FROM_PCB_UNITS(PCB->MaxHeight), FROM_PCB_UNITS(MIN_SIZE), FROM_PCB_UNITS(MAX_COORD), STEP0_SIZE, STEP1_SIZE, SPIN_DIGITS, 0, size_spin_button_cb, &new_board_height, FALSE, _("Height")); ghid_check_button_connected(vbox, &use_board_size_default_button, FALSE, TRUE, FALSE, FALSE, 0, NULL, NULL, _("Use this board size as the default for new layouts")); /* ---- Text Scale ---- */ vbox = ghid_category_vbox(config_sizes_vbox, _("Text Scale"), 4, 2, TRUE, TRUE); hbox = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); table = gtk_table_new(4, 2, FALSE); gtk_box_pack_start(GTK_BOX(hbox), table, FALSE, FALSE, 0); gtk_table_set_col_spacings(GTK_TABLE(table), 6); gtk_table_set_row_spacings(GTK_TABLE(table), 3); ghid_table_spin_button(table, 0, 0, &config_text_spin_button, Settings.TextScale, MIN_TEXTSCALE, MAX_TEXTSCALE, 10.0, 10.0, 0, 0, text_spin_button_cb, &Settings.TextScale, FALSE, "%"); /* ---- DRC Sizes ---- */ vbox = ghid_category_vbox(config_sizes_vbox, _("Design Rule Checking"), 4, 2, TRUE, TRUE); hbox = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); table = gtk_table_new(4, 2, FALSE); gtk_box_pack_start(GTK_BOX(hbox), table, FALSE, FALSE, 0); gtk_table_set_col_spacings(GTK_TABLE(table), 6); gtk_table_set_row_spacings(GTK_TABLE(table), 3); ghid_table_spin_button(table, 0, 0, NULL, FROM_PCB_UNITS(PCB->Bloat), FROM_PCB_UNITS(MIN_DRC_VALUE), FROM_PCB_UNITS(MAX_DRC_VALUE), STEP0_SMALL_SIZE, STEP1_SMALL_SIZE, SPIN_DIGITS, 0, size_spin_button_cb, &PCB->Bloat, FALSE, _("Minimum copper spacing")); ghid_table_spin_button(table, 1, 0, NULL, FROM_PCB_UNITS(PCB->minWid), FROM_PCB_UNITS(MIN_DRC_VALUE), FROM_PCB_UNITS(MAX_DRC_VALUE), STEP0_SMALL_SIZE, STEP1_SMALL_SIZE, SPIN_DIGITS, 0, size_spin_button_cb, &PCB->minWid, FALSE, _("Minimum copper width")); ghid_table_spin_button(table, 2, 0, NULL, FROM_PCB_UNITS(PCB->Shrink), FROM_PCB_UNITS(MIN_DRC_VALUE), FROM_PCB_UNITS(MAX_DRC_VALUE), STEP0_SMALL_SIZE, STEP1_SMALL_SIZE, SPIN_DIGITS, 0, size_spin_button_cb, &PCB->Shrink, FALSE, _("Minimum touching copper overlap")); ghid_table_spin_button(table, 3, 0, NULL, FROM_PCB_UNITS(PCB->minSlk), FROM_PCB_UNITS(MIN_DRC_SILK), FROM_PCB_UNITS(MAX_DRC_SILK), STEP0_SMALL_SIZE, STEP1_SMALL_SIZE, SPIN_DIGITS, 0, size_spin_button_cb, &PCB->minSlk, FALSE, _("Minimum silk width")); ghid_table_spin_button(table, 4, 0, NULL, FROM_PCB_UNITS(PCB->minDrill), FROM_PCB_UNITS(MIN_DRC_DRILL), FROM_PCB_UNITS(MAX_DRC_DRILL), STEP0_SMALL_SIZE, STEP1_SMALL_SIZE, SPIN_DIGITS, 0, size_spin_button_cb, &PCB->minDrill, FALSE, _("Minimum drill diameter")); ghid_table_spin_button(table, 5, 0, NULL, FROM_PCB_UNITS(PCB->minRing), FROM_PCB_UNITS(MIN_DRC_RING), FROM_PCB_UNITS(MAX_DRC_RING), STEP0_SMALL_SIZE, STEP1_SMALL_SIZE, SPIN_DIGITS, 0, size_spin_button_cb, &PCB->minRing, FALSE, _("Minimum annular ring")); ghid_check_button_connected(vbox, &use_drc_sizes_default_button, FALSE, TRUE, FALSE, FALSE, 0, NULL, NULL, _("Use DRC values as the default for new layouts")); gtk_widget_show_all(config_sizes_vbox); } /* -------------- The Increments config page ---------------- */ /* Increment/decrement values are kept in mil and mm units and not in | PCB units. */ static GtkWidget *config_increments_vbox, *config_increments_tab_vbox; static void increment_spin_button_cb(GtkSpinButton *spin, gdouble *dst) { gdouble value; value = gtk_spin_button_get_value(spin); *dst = value; /* Not using PCB units */ if ( dst == &Settings.size_increment_mm || dst == &Settings.size_increment_mil ) ghid_change_selected_update_menu_actions(); ghidgui->config_modified = TRUE; } static void config_increments_tab_create(GtkWidget *tab_vbox) { GtkWidget *vbox, *label; gdouble *target; gchar *str; /* Need a vbox we can destroy if user changes grid units. */ if (!config_increments_vbox) { vbox = gtk_vbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(tab_vbox), vbox, FALSE, FALSE, 0); gtk_container_set_border_width(GTK_CONTAINER(vbox), 6); config_increments_vbox = vbox; config_increments_tab_vbox = tab_vbox; } str = g_strdup_printf( _("Increment/Decrement values to use in %s units mode.\n"), Settings.grid_units_mm ? "mm" : "mil"); label = gtk_label_new(""); gtk_label_set_use_markup(GTK_LABEL(label), TRUE); gtk_label_set_markup(GTK_LABEL(label), str); gtk_box_pack_start(GTK_BOX(config_increments_vbox), label, FALSE, FALSE, 4); g_free(str); /* ---- Grid Increment/Decrement ---- */ vbox = ghid_category_vbox(config_increments_vbox, _("Grid Increment/Decrement"), 4, 2, TRUE, TRUE); /* Grid increment spin button ('g' and 'g'). For mil | units, range from 1.0 to 25.0. For mm, range from 0.01 to 1.0 | Step sizes of 5 mil or .05 mm, and .01 mm precision or 1 mil precision. */ target = Settings.grid_units_mm ? &Settings.grid_increment_mm : &Settings.grid_increment_mil; ghid_spin_button(vbox, NULL, GRID_UNITS_VALUE(Settings.grid_increment_mm, Settings.grid_increment_mil), GRID_UNITS_VALUE(0.01, 1.0), GRID_UNITS_VALUE(1.0, 25.0), GRID_UNITS_VALUE(0.05, 5.0), GRID_UNITS_VALUE(0.05, 5.0), GRID_UNITS_VALUE(2, 0), 0, increment_spin_button_cb, target, FALSE, _("For 'g' and 'g' grid change actions")); /* ---- Size Increment/Decrement ---- */ vbox = ghid_category_vbox(config_increments_vbox, _("Size Increment/Decrement"), 4, 2, TRUE, TRUE); /* Size increment spin button ('s' and 's'). For mil | units, range from 1.0 to 10.0. For mm, range from 0.01 to 0.5 | Step sizes of 1 mil or .01 mm, and .01 mm precision or 1 mil precision. */ target = Settings.grid_units_mm ? &Settings.size_increment_mm : &Settings.size_increment_mil; ghid_spin_button(vbox, NULL, GRID_UNITS_VALUE(Settings.size_increment_mm, Settings.size_increment_mil), GRID_UNITS_VALUE(0.01, 1.0), GRID_UNITS_VALUE(0.5, 10.0), GRID_UNITS_VALUE(0.01, 1.0), GRID_UNITS_VALUE(0.05, 5.0), GRID_UNITS_VALUE(2, 0), 0, increment_spin_button_cb, target, FALSE, _("For 's' and 's' size change actions on lines,\n" "pads, pins and text.\n" "Use 's' and 's' for drill holes.")); /* ---- Line Increment/Decrement ---- */ vbox = ghid_category_vbox(config_increments_vbox, _("Line Increment/Decrement"), 4, 2, TRUE, TRUE); /* Line increment spin button ('l' and 'l'). For mil | units, range from 0.5 to 10.0. For mm, range from 0.005 to 0.5 | Step sizes of 0.5 mil or .005 mm, and .001 mm precision or 0.1 mil | precision. */ target = Settings.grid_units_mm ? &Settings.line_increment_mm : &Settings.line_increment_mil; ghid_spin_button(vbox, NULL, GRID_UNITS_VALUE(Settings.line_increment_mm, Settings.line_increment_mil), GRID_UNITS_VALUE(0.005, 0.5), GRID_UNITS_VALUE(0.5, 10.0), GRID_UNITS_VALUE(0.005, 0.5), GRID_UNITS_VALUE(0.05, 5.0), GRID_UNITS_VALUE(3, 1), 0, increment_spin_button_cb, target, FALSE, _("For 'l' and 'l' routing line width change actions")); /* ---- Clear Increment/Decrement ---- */ vbox = ghid_category_vbox(config_increments_vbox, _("Clear Increment/Decrement"), 4, 2, TRUE, TRUE); /* Clear increment spin button ('l' and 'l'). For mil | units, range from 0.5 to 10.0. For mm, range from 0.005 to 0.5 | Step sizes of 0.5 mil or .005 mm, and .001 mm precision or 0.1 mil | precision. */ target = Settings.grid_units_mm ? &Settings.clear_increment_mm : &Settings.clear_increment_mil; ghid_spin_button(vbox, NULL, GRID_UNITS_VALUE(Settings.clear_increment_mm, Settings.clear_increment_mil), GRID_UNITS_VALUE(0.005, 0.5), GRID_UNITS_VALUE(0.5, 10.0), GRID_UNITS_VALUE(0.005, 0.5), GRID_UNITS_VALUE(0.05, 5.0), GRID_UNITS_VALUE(3, 1), 0, increment_spin_button_cb, target, FALSE, _("For 'k' and 'k' line clearance inside polygon size\n" "change actions")); gtk_widget_show_all(config_increments_vbox); } /* -------------- The Library config page ---------------- */ static GtkWidget *library_newlib_entry; static void config_library_apply(void) { if (dup_string(&lib_newlib_config, ghid_entry_get_text(library_newlib_entry))) ghidgui->config_modified = TRUE; } static void config_library_tab_create(GtkWidget *tab_vbox) { GtkWidget *vbox, *label, *entry; gtk_container_set_border_width(GTK_CONTAINER(tab_vbox), 6); vbox = ghid_category_vbox(tab_vbox, _("Element Directories"), 4, 2, TRUE, TRUE); label = gtk_label_new(""); gtk_label_set_use_markup(GTK_LABEL(label), TRUE); gtk_label_set_markup(GTK_LABEL(label), _("Enter a colon separated list of custom top level\n" "element directories. For example:\n" "\t~/gaf/pcb-elements:packages:/usr/local/pcb-elements\n" "Elements should be organized into subdirectories below each\n" "top level directory. Restart program for changes to take effect." "")); gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); entry = gtk_entry_new(); library_newlib_entry = entry; gtk_entry_set_text(GTK_ENTRY(entry), lib_newlib_config); gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, FALSE, 4); } /* -------------- The Layers Group config page ---------------- */ static GtkWidget *layer_entry[MAX_LAYER]; static GtkWidget *group_button[MAX_LAYER + 2][MAX_LAYER]; static GtkWidget *use_layer_default_button; static gint config_layer_group[MAX_LAYER + 2]; static LayerGroupType layer_groups, /* Working copy */ *lg_monitor; /* Keep track if our working copy */ /* needs to be changed (new layout)*/ static gboolean groups_modified, groups_holdoff, layers_applying; static gchar *layer_info_text[] = { N_("Layer Names\n"), N_("You may enter layer names for the layers drawn on the screen.\n" "The special 'component side' and 'solder side' are layers which\n" "will be printed out, so they must have in their group at least one\n" "of the other layers that are drawn on the screen.\n"), "\n", N_("Layer Groups\n"), N_("Each layer on the screen may be in its own group which allows the\n" "maximum number of board layers. However, for boards with fewer\n" "layers, you may group layers together which will then print as a\n" "single layer on a printout. This allows a visual color distinction\n" "to be displayed on the screen for signal groups which will print as\n" "a single layer\n"), "\n", N_("For example, for a 4 layer board a useful layer group arrangement\n" "can be to have 3 screen displayed layers grouped into the same group\n" "as the 'component side' and 'solder side' printout layers. Then\n" "groups such as signals, ground, and supply traces can be color\n" "coded on the screen while printing as a single layer. For this\n" "you would select buttons and enter names on the Setup page to\n" "structure four layer groups similar to this:\n"), "\n", N_("Group 1:"), "\n\t", N_("solder"), "\n\t", N_("GND-solder"), "\n\t", N_("Vcc-solder"), "\n\t", N_("solder side"), "\n", N_("Group 2:"), "\n\t", N_("component"), "\n\t", N_("GND-component"), "\n\t", N_("Vcc-component"), "\n\t", N_("component side"), "\n", N_("Group 3:"), "\n\t", N_("signal1"), "\n", N_("Group 4:"), "\n\t", N_("signal2"), "\n" }; static void config_layer_groups_radio_button_cb(GtkToggleButton *button, gpointer data) { gint layer = GPOINTER_TO_INT(data) >> 8; gint group = GPOINTER_TO_INT(data) & 0xff; if (!gtk_toggle_button_get_active(button) || groups_holdoff) return; config_layer_group[layer] = group; groups_modified = TRUE; ghidgui->config_modified = TRUE; } /* Construct a layer group string. Follow logic in WritePCBDataHeader(), | but use g_string functions. */ static gchar * make_layer_group_string(LayerGroupType *lg) { GString *string; gint group, entry, layer; string = g_string_new(""); for (group = 0; group < MAX_LAYER; group++) { if (lg->Number[group] == 0) continue; for (entry = 0; entry < lg->Number[group]; entry++) { layer = lg->Entries[group][entry]; if (layer == MAX_LAYER + COMPONENT_LAYER) string = g_string_append(string, "c"); else if (layer == MAX_LAYER + SOLDER_LAYER) string = g_string_append(string, "s"); else g_string_append_printf(string, "%d", layer +1); if (entry != lg->Number[group] - 1) string = g_string_append(string, ","); } if (group != MAX_LAYER - 1) string = g_string_append(string, ":"); } return g_string_free(string, FALSE); /* Don't free string->str */ } static void config_layers_apply(void) { LayerType *layer; gchar *s; gint group, i; gint componentgroup = 0, soldergroup = 0; gboolean use_as_default, layers_modified = FALSE; use_as_default = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(use_layer_default_button)); /* Get each layer name entry and dup if modified into the PCB layer names | and, if to use as default, the Settings layer names. */ for (i = 0; i < MAX_LAYER; ++i) { layer = &PCB->Data->Layer[i]; s = ghid_entry_get_text(layer_entry[i]); if (dup_string(&layer->Name, s)) layers_modified = TRUE; /* FIXME */ if (use_as_default && dup_string(&Settings.DefaultLayerName[i], s)) ghidgui->config_modified = TRUE; } /* Layer names can be changed from the menus and that can update the | config. So holdoff the loop. */ layers_applying = TRUE; if (layers_modified) ghid_layer_buttons_update(); layers_applying = FALSE; if (groups_modified) /* If any group radio buttons were toggled. */ { /* clear all entries and read layer by layer */ for (group = 0; group < MAX_LAYER; group++) layer_groups.Number[group] = 0; for (i = 0; i < MAX_LAYER + 2; i++) { group = config_layer_group[i] - 1; layer_groups.Entries[group][layer_groups.Number[group]++] = i; if (i == MAX_LAYER + COMPONENT_LAYER) componentgroup = group; else if (i == MAX_LAYER + SOLDER_LAYER) soldergroup = group; } /* do some cross-checking | solder-side and component-side must be in different groups | solder-side and component-side must not be the only one in the group */ if ( layer_groups.Number[soldergroup] <= 1 || layer_groups.Number[componentgroup] <= 1 ) { Message ( _("Both 'solder side' or 'component side' layers must have at least\n" "\tone other layer in their group.\n")); return; } else if (soldergroup == componentgroup) { Message ( _("The 'solder side' and 'component side' layers are not allowed\n" "\tto be in the same layer group #\n")); return; } PCB->LayerGroups = layer_groups; hid_actionl ("Display", "ClearAndRedraw", 0); groups_modified = FALSE; } if (use_as_default) { s = make_layer_group_string(&PCB->LayerGroups); if (dup_string(&groups_config, s)) { ParseGroupString(groups_config, &Settings.LayerGroups); ghidgui->config_modified = TRUE; } g_free(s); } } static void config_layer_group_button_state_update(void) { gint g, i; /* Set button active corresponding to layer group state. */ groups_holdoff = TRUE; for (g = 0; g < MAX_LAYER; g++) for (i = 0; i < layer_groups.Number[g]; i++) { /* printf("layer %d in group %d\n", layer_groups.Entries[g][i], g +1); */ config_layer_group[layer_groups.Entries[g][i]] = g + 1; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON( group_button[layer_groups.Entries[g][i]] [g]), TRUE); } groups_holdoff = FALSE; } static void config_layers_tab_create(GtkWidget *tab_vbox) { GtkWidget *tabs, *vbox, *table, *button, *label, *text, *sep; GSList *group; gchar buf[32], *name; gint layer, i; tabs = gtk_notebook_new(); gtk_box_pack_start(GTK_BOX(tab_vbox), tabs, TRUE, TRUE, 0); /* -- Setup tab */ vbox = ghid_notebook_page(tabs, _("Setup"), 0, 6); table = gtk_table_new(MAX_LAYER + 3, MAX_LAYER + 1, FALSE); gtk_table_set_row_spacings(GTK_TABLE(table), 3); gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, FALSE, 0); layer_groups = PCB->LayerGroups; /* working copy */ lg_monitor = &PCB->LayerGroups; /* So can know if PCB changes on us */ label = gtk_label_new(_("Group #")); gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 0, 1); gtk_misc_set_alignment(GTK_MISC(label), 1.0, 0.5); for (i = 1; i < MAX_LAYER + 1; ++i) { snprintf(buf, sizeof(buf), "%d", i); label = gtk_label_new(buf); gtk_table_attach_defaults(GTK_TABLE(table), label, i, i + 1, 0, 1); } /* Create a row of radio toggle buttons for layer. So each layer | can have an active radio button set for the group it needs to be in. */ for (layer = 0; layer < MAX_LAYER + 2; ++layer) { if (layer == MAX_LAYER + COMPONENT_LAYER) name = _("component side"); else if (layer == MAX_LAYER + SOLDER_LAYER) name = _("solder side"); else name = UNKNOWN (PCB->Data->Layer[layer].Name); if (layer >= MAX_LAYER) { label = gtk_label_new(name); gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, layer + 1, layer + 2); } else { layer_entry[layer] = gtk_entry_new(); gtk_entry_set_text(GTK_ENTRY(layer_entry[layer]), name); gtk_table_attach_defaults(GTK_TABLE(table), layer_entry[layer], 0, 1, layer + 1, layer + 2); } group = NULL; for (i = 0; i < MAX_LAYER; ++i) { if (layer < MAX_LAYER || i > 0) button = gtk_radio_button_new(group); else /* See layer button fixup below */ button = gtk_radio_button_new_with_label(group, "8"); gtk_toggle_button_set_mode(GTK_TOGGLE_BUTTON(button), FALSE); group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button)); gtk_table_attach_defaults(GTK_TABLE(table), button, i + 1, i + 2, layer + 1, layer + 2); g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(config_layer_groups_radio_button_cb), GINT_TO_POINTER((layer << 8) | (i + 1))); group_button[layer][i] = button; } } config_layer_group_button_state_update(); sep = gtk_hseparator_new(); gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 4); ghid_check_button_connected(vbox, &use_layer_default_button, FALSE, TRUE, FALSE, FALSE, 8, NULL, NULL, _("Use these layer settings as the default for new layouts")); /* -- Info tab */ vbox = ghid_notebook_page(tabs, _("Info"), 0, 6); text = ghid_scrolled_text_view(vbox, NULL, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); for (i = 0; i < sizeof(layer_info_text)/sizeof(gchar *); ++i) ghid_text_view_append(text, _(layer_info_text[i])); } /* All the radio buttons should have no label, but the component and solder | side rows need a button with an initial label to force the button to fill | out vertically when realized so it will match in size the other buttons | which naturally fill out because the layer entries force more vertical | space on their rows. So erase that initial kludge label after showing. */ static void layer_button_post_show_fixup(void) { gint l; for (l = MAX_LAYER; l < MAX_LAYER + 2; ++l) gtk_button_set_label(GTK_BUTTON(group_button[l][0]), ""); } void ghid_config_layer_name_update(gchar *name, gint layer) { if (!config_window || layers_applying || !name) return; gtk_entry_set_text(GTK_ENTRY(layer_entry[layer]), name); /* If we get a config layer name change because a new PCB is loaded | or new layout started, need to change our working layer group copy. */ if (lg_monitor != &PCB->LayerGroups) { layer_groups = PCB->LayerGroups; lg_monitor = &PCB->LayerGroups; config_layer_group_button_state_update(); groups_modified = FALSE; } } /* -------------- The Colors config page ---------------- */ static GtkWidget *config_colors_vbox, *config_colors_tab_vbox, *config_colors_save_button, *config_color_file_label, *config_color_warn_label; static void config_colors_tab_create(GtkWidget *tab_vbox); static gboolean config_colors_modified; static void config_color_file_set_label(void) { gchar *str, *name; if (!*color_file) name = g_strdup("defaults"); else name = g_path_get_basename(color_file); str = g_strdup_printf("Current colors loaded: %s", name); gtk_label_set_markup(GTK_LABEL(config_color_file_label), str); g_free(name); g_free(str); } static void config_color_defaults_cb(gpointer data) { GList *list; ConfigColor *cc; HID_Attribute *ha; for (list = config_color_list; list; list = list->next) { cc = (ConfigColor *) list->data; ha = cc->attributes; dup_string((char **) ha->value, ha->default_val.str_value); cc->color_is_mapped = FALSE; ghid_set_special_colors(ha); } dup_string(&color_file, ""); ghidgui->config_modified = TRUE; gtk_widget_set_sensitive(config_colors_save_button, FALSE); gtk_widget_set_sensitive(config_color_warn_label, FALSE); config_color_file_set_label(); config_colors_modified = FALSE; ghid_layer_buttons_color_update(); /* Receate the colors config page to pick up new colors. */ gtk_widget_destroy(config_colors_vbox); config_colors_tab_create(config_colors_tab_vbox); hid_actionl ("Display", "ClearAndRedraw", 0); } static void config_color_load_cb(gpointer data) { gchar *path, *dir = g_strdup(color_dir); path = ghid_dialog_file_select_open(_("Load Color File"), &dir, NULL); if (path) { config_colors_read(path); dup_string(&color_file, path); ghidgui->config_modified = TRUE; gtk_widget_set_sensitive(config_colors_save_button, FALSE); gtk_widget_set_sensitive(config_color_warn_label, FALSE); config_color_file_set_label(); config_colors_modified = FALSE; } g_free(path); g_free(dir); /* Receate the colors config page to pick up new colors. */ gtk_widget_destroy(config_colors_vbox); config_colors_tab_create(config_colors_tab_vbox); hid_actionl ("Display", "ClearAndRedraw", 0); } static void config_color_save_cb(gpointer data) { gchar *name, *path, *dir = g_strdup(color_dir); path = ghid_dialog_file_select_save(_("Save Color File"), &dir, NULL, NULL); if (path) { name = g_path_get_basename(path); if (!strcmp(name, "default")) ghid_dialog_message( _("Sorry, not overwriting the default color file!")); else { config_colors_write(path); dup_string(&color_file, path); ghidgui->config_modified = TRUE; gtk_widget_set_sensitive(config_colors_save_button, FALSE); gtk_widget_set_sensitive(config_color_warn_label, FALSE); config_color_file_set_label(); config_colors_modified = FALSE; } g_free(name); } g_free(path); g_free(dir); } static void config_color_set_cb(GtkWidget *button, ConfigColor *cc) { GdkColor new_color; HID_Attribute *ha = cc->attributes; gchar *str; gtk_color_button_get_color(GTK_COLOR_BUTTON(button), &new_color); str = ghid_get_color_name(&new_color); ghid_map_color_string(str, &cc->color); *(char **) ha->value = str; /* g_free(str); Memory leak */ config_colors_modified = TRUE; gtk_widget_set_sensitive(config_colors_save_button, TRUE); gtk_widget_set_sensitive(config_color_warn_label, TRUE); ghid_layer_buttons_color_update(); /* This doesn't redaw display until mouse enters output window? */ hid_actionl ("Display", "ClearAndRedraw", 0); } static void config_color_button_create(GtkWidget *box, ConfigColor *cc) { GtkWidget *button, *hbox, *label; HID_Attribute *ha = cc->attributes; gchar *title; hbox = gtk_hbox_new(FALSE, 6); gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0); if (!cc->color_is_mapped) ghid_map_color_string(*(char **) ha->value, &cc->color); cc->color_is_mapped = TRUE; title = g_strdup_printf(_("PCB %s Color"), ha->name); button = gtk_color_button_new_with_color(&cc->color); gtk_color_button_set_title(GTK_COLOR_BUTTON(button), title); g_free(title); gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); label = gtk_label_new(ha->name); gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); g_signal_connect(G_OBJECT(button), "color-set", G_CALLBACK(config_color_set_cb), cc); } static void config_colors_tab_create(GtkWidget *tab_vbox) { GtkWidget *scrolled_vbox, *vbox, *hbox, *expander, *sep; GList *list; ConfigColor *cc; vbox = gtk_vbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(tab_vbox), vbox, TRUE, TRUE, 0); gtk_container_set_border_width(GTK_CONTAINER(vbox), 6); config_colors_vbox = vbox; /* can be destroyed if color file loaded */ config_colors_tab_vbox = tab_vbox; scrolled_vbox = ghid_scrolled_vbox(config_colors_vbox, NULL, GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); /* ---- Main colors ---- */ expander = gtk_expander_new(_("Main colors")); gtk_box_pack_start(GTK_BOX(scrolled_vbox), expander, FALSE, FALSE, 2); vbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(expander), vbox); vbox = ghid_category_vbox(vbox, NULL, 0, 2, TRUE, FALSE); for (list = config_color_list; list; list = list->next) { cc = (ConfigColor *) list->data; if (cc->type != MISC_COLOR) continue; config_color_button_create(vbox, cc); } /* ---- Layer colors ---- */ expander = gtk_expander_new(_("Layer colors")); gtk_box_pack_start(GTK_BOX(scrolled_vbox), expander, FALSE, FALSE, 2); vbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(expander), vbox); vbox = ghid_category_vbox(vbox, NULL, 0, 2, TRUE, FALSE); for (list = config_color_list; list; list = list->next) { cc = (ConfigColor *) list->data; if (cc->type != LAYER_COLOR) continue; config_color_button_create(vbox, cc); } /* ---- Selected colors ---- */ expander = gtk_expander_new(_("Selected colors")); gtk_box_pack_start(GTK_BOX(scrolled_vbox), expander, FALSE, FALSE, 2); vbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(expander), vbox); vbox = ghid_category_vbox(vbox, NULL, 0, 2, TRUE, FALSE); for (list = config_color_list; list; list = list->next) { cc = (ConfigColor *) list->data; if (cc->type != MISC_SELECTED_COLOR) continue; config_color_button_create(vbox, cc); } sep = gtk_hseparator_new(); gtk_box_pack_start(GTK_BOX(vbox), sep, FALSE, FALSE, 2); for (list = config_color_list; list; list = list->next) { cc = (ConfigColor *) list->data; if (cc->type != LAYER_SELECTED_COLOR) continue; config_color_button_create(vbox, cc); } config_color_warn_label = gtk_label_new(""); gtk_label_set_use_markup(GTK_LABEL(config_color_warn_label), TRUE); gtk_label_set_markup(GTK_LABEL(config_color_warn_label), _("Warning: unsaved color changes will be lost" " at program exit.")); gtk_box_pack_start(GTK_BOX(config_colors_vbox), config_color_warn_label, FALSE, FALSE, 4); hbox = gtk_hbox_new(FALSE, 0); gtk_box_pack_start(GTK_BOX(config_colors_vbox), hbox, FALSE, FALSE, 6); config_color_file_label = gtk_label_new(""); gtk_label_set_use_markup(GTK_LABEL(config_color_file_label), TRUE); config_color_file_set_label(); gtk_box_pack_start(GTK_BOX(hbox), config_color_file_label, FALSE, FALSE, 0); ghid_button_connected(hbox, NULL, FALSE, FALSE, FALSE, 4, config_color_load_cb, NULL, _("Load")); ghid_button_connected(hbox, &config_colors_save_button, FALSE, FALSE, FALSE, 4, config_color_save_cb, NULL, _("Save")); ghid_button_connected(hbox, NULL, FALSE, FALSE, FALSE, 4, config_color_defaults_cb, NULL, _("Defaults")); gtk_widget_set_sensitive(config_colors_save_button, config_colors_modified); gtk_widget_set_sensitive(config_color_warn_label, config_colors_modified); gtk_widget_show_all(config_colors_vbox); } /* --------------- The main config page ----------------- */ enum { CONFIG_NAME_COLUMN, CONFIG_PAGE_COLUMN, N_CONFIG_COLUMNS }; static GtkNotebook *config_notebook; static GtkWidget * config_page_create(GtkTreeStore *tree, GtkTreeIter *iter, GtkNotebook *notebook) { GtkWidget *vbox; gint page; vbox = gtk_vbox_new(FALSE, 0); gtk_notebook_append_page(notebook, vbox, NULL); page = g_list_length(notebook->children) - 1; gtk_tree_store_set(tree, iter, CONFIG_PAGE_COLUMN, page, -1); return vbox; } void ghid_config_handle_units_changed(void) { ghid_set_cursor_position_labels(); gtk_label_set_markup(GTK_LABEL(ghidgui->grid_units_label), Settings.grid_units_mm ? "mm " : "mil "); if (config_sizes_vbox) { gtk_widget_destroy(config_sizes_vbox); config_sizes_vbox = NULL; config_sizes_tab_create(config_sizes_tab_vbox); } if (config_increments_vbox) { gtk_widget_destroy(config_increments_vbox); config_increments_vbox = NULL; config_increments_tab_create(config_increments_tab_vbox); } ghidgui->config_modified = TRUE; } void ghid_config_text_scale_update(void) { if (config_window) gtk_spin_button_set_value(GTK_SPIN_BUTTON(config_text_spin_button), (gdouble) Settings.TextScale); } static void config_close_cb(gpointer data) { /* Config pages may need to check for modified entries, use as default | options, etc when the config window is closed. */ config_general_apply(); config_sizes_apply(); config_layers_apply(); config_library_apply(); config_sizes_vbox = NULL; config_increments_vbox = NULL; gtk_widget_destroy(config_window); config_window = NULL; } static void config_destroy_cb(gpointer data) { config_sizes_vbox = NULL; config_increments_vbox = NULL; config_window = NULL; } static void config_selection_changed_cb(GtkTreeSelection *selection, gpointer data) { GtkTreeIter iter; GtkTreeModel *model; gint page; if (!gtk_tree_selection_get_selected(selection, &model, &iter)) return; gtk_tree_model_get(model, &iter, CONFIG_PAGE_COLUMN, &page, -1); gtk_notebook_set_current_page(config_notebook, page); } void ghid_config_window_show(void) { GtkWidget *widget, *main_vbox, *vbox, *config_hbox, *hbox; GtkWidget *scrolled; GtkWidget *button; GtkTreeStore *model; GtkTreeView *treeview; GtkTreeIter iter; GtkCellRenderer *renderer; GtkTreeViewColumn *column; GtkTreeSelection *select; if (config_window) { gtk_window_present(GTK_WINDOW(config_window)); return; } config_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); g_signal_connect(G_OBJECT(config_window), "delete_event", G_CALLBACK(config_destroy_cb), NULL); gtk_window_set_title(GTK_WINDOW(config_window), "PCB Preferences"); gtk_window_set_wmclass(GTK_WINDOW(config_window), "Pcb_Conf", "PCB"); gtk_container_set_border_width(GTK_CONTAINER(config_window), 2); config_hbox = gtk_hbox_new(FALSE, 4); gtk_container_add(GTK_CONTAINER(config_window), config_hbox); scrolled = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); gtk_box_pack_start(GTK_BOX(config_hbox), scrolled, FALSE, FALSE, 0); main_vbox = gtk_vbox_new(FALSE, 4); gtk_box_pack_start(GTK_BOX(config_hbox), main_vbox, TRUE, TRUE, 0); widget = gtk_notebook_new(); gtk_box_pack_start(GTK_BOX(main_vbox), widget, TRUE, TRUE, 0); config_notebook = GTK_NOTEBOOK(widget); gtk_notebook_set_show_tabs(config_notebook, FALSE); model = gtk_tree_store_new(N_CONFIG_COLUMNS, G_TYPE_STRING, G_TYPE_INT); /* -- General -- */ gtk_tree_store_append(model, &iter, NULL); gtk_tree_store_set(model, &iter, CONFIG_NAME_COLUMN, _("General"), -1); vbox = config_page_create(model, &iter, config_notebook); config_general_tab_create(vbox); /* -- Sizes -- */ gtk_tree_store_append(model, &iter, NULL); gtk_tree_store_set(model, &iter, CONFIG_NAME_COLUMN, _("Sizes"), -1); vbox = config_page_create(model, &iter, config_notebook); config_sizes_tab_create(vbox); /* -- Increments -- */ gtk_tree_store_append(model, &iter, NULL); gtk_tree_store_set(model, &iter, CONFIG_NAME_COLUMN, _("Increments"), -1); vbox = config_page_create(model, &iter, config_notebook); config_increments_tab_create(vbox); /* -- Library -- */ gtk_tree_store_append(model, &iter, NULL); gtk_tree_store_set(model, &iter, CONFIG_NAME_COLUMN, _("Library"), -1); vbox = config_page_create(model, &iter, config_notebook); config_library_tab_create(vbox); /* -- Layer names and groups -- */ gtk_tree_store_append(model, &iter, NULL); gtk_tree_store_set(model, &iter, CONFIG_NAME_COLUMN, _("Layers"), -1); vbox = config_page_create(model, &iter, config_notebook); config_layers_tab_create(vbox); /* -- Colors -- */ gtk_tree_store_append(model, &iter, NULL); gtk_tree_store_set(model, &iter, CONFIG_NAME_COLUMN, _("Colors"), -1); vbox = config_page_create(model, &iter, config_notebook); config_colors_tab_create(vbox); /* Create the tree view */ treeview = GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(model))); g_object_unref(G_OBJECT(model)); /* Don't need the model anymore */ renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", CONFIG_NAME_COLUMN, NULL); gtk_tree_view_append_column(treeview, column); gtk_container_add(GTK_CONTAINER(scrolled), GTK_WIDGET(treeview)); select = gtk_tree_view_get_selection(treeview); gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE); g_signal_connect(G_OBJECT(select), "changed", G_CALLBACK(config_selection_changed_cb), NULL); hbox = gtk_hbutton_box_new(); gtk_button_box_set_layout(GTK_BUTTON_BOX(hbox), GTK_BUTTONBOX_END); gtk_box_set_spacing(GTK_BOX(hbox), 5); gtk_box_pack_start(GTK_BOX(main_vbox), hbox, FALSE, FALSE, 0); button = gtk_button_new_from_stock(GTK_STOCK_OK); GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(config_close_cb), NULL); gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0); gtk_widget_grab_default(button); gtk_widget_show_all(config_window); layer_button_post_show_fixup(); }