1 /*
2  *  program.c
3  *
4  *  Copyright 2012 Dimitar Toshkov Zhekov <dimitar.zhekov@gmail.com>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23 
24 #include <errno.h>
25 #include <string.h>
26 #include <unistd.h>
27 
28 #include "common.h"
29 
30 #ifndef PATH_MAX
31 #define PATH_MAX 4096
32 #endif
33 
34 static StashGroup *program_group;
35 static StashGroup *options_group;
36 static StashGroup *thread_group;
37 static StashGroup *terminal_group;
38 
stash_foreach(GFunc func,gpointer gdata)39 static void stash_foreach(GFunc func, gpointer gdata)
40 {
41 	func(program_group, gdata);
42 	func(options_group, gdata);
43 	func(thread_group, gdata);
44 	func(terminal_group, gdata);
45 }
46 
stash_group_destroy(StashGroup * group,G_GNUC_UNUSED gpointer gdata)47 static void stash_group_destroy(StashGroup *group, G_GNUC_UNUSED gpointer gdata)
48 {
49 	utils_stash_group_free(group);
50 }
51 
52 enum
53 {
54 	PROGRAM_NAME,
55 	PROGRAM_ID    /* 1-based */
56 };
57 
58 #define RECENT_COUNT 28
59 
60 static ScpTreeStore *recent_programs;
61 static guint recent_bitmap;
62 static GtkWidget *recent_menu;
63 
recent_program_load(GKeyFile * config,const char * section)64 static gboolean recent_program_load(GKeyFile *config, const char *section)
65 {
66 	gchar *name = utils_get_setting_string(config, section, "name", NULL);
67 	gint id = utils_get_setting_integer(config, section, "id", 0);
68 	gboolean valid = FALSE;
69 
70 	if (name && id > 0 && id <= RECENT_COUNT && (recent_bitmap & (1 << id)) == 0)
71 	{
72 		scp_tree_store_append_with_values(recent_programs, NULL, NULL, PROGRAM_NAME, name,
73 			PROGRAM_ID, id, -1);
74 		recent_bitmap |= 1 << id;
75 		valid = TRUE;
76 	}
77 
78 	g_free(name);
79 	return valid;
80 }
81 
recent_program_save(GKeyFile * config,const char * section,GtkTreeIter * iter)82 static gboolean recent_program_save(GKeyFile *config, const char *section, GtkTreeIter *iter)
83 {
84 	const gchar *name;
85 	gint id;
86 
87 	scp_tree_store_get(recent_programs, iter, PROGRAM_NAME, &name, PROGRAM_ID, &id, -1);
88 	g_key_file_set_string(config, section, "name", name);
89 	g_key_file_set_integer(config, section, "id", id);
90 	return TRUE;
91 }
92 
recent_menu_item_destroy(GtkWidget * widget,G_GNUC_UNUSED gpointer gdata)93 static void recent_menu_item_destroy(GtkWidget *widget, G_GNUC_UNUSED gpointer gdata)
94 {
95 	gtk_widget_destroy(widget);
96 }
97 
98 static void on_recent_menu_item_activate(GtkMenuItem *menuitem, const gchar *name);
99 
100 static gint recent_menu_count;
101 
recent_menu_item_create(GtkTreeIter * iter,G_GNUC_UNUSED gpointer gdata)102 static void recent_menu_item_create(GtkTreeIter *iter, G_GNUC_UNUSED gpointer gdata)
103 {
104 	if (recent_menu_count < pref_show_recent_items)
105 	{
106 		const gchar *name;
107 		GtkWidget *item;
108 
109 		scp_tree_store_get(recent_programs, iter, PROGRAM_NAME, &name, -1);
110 		item = gtk_menu_item_new_with_label(name);
111 		gtk_menu_shell_append(GTK_MENU_SHELL(recent_menu), item);
112 		g_signal_connect(item, "activate", G_CALLBACK(on_recent_menu_item_activate),
113 			(gpointer) name);
114 		recent_menu_count++;
115 	}
116 }
117 
recent_menu_create(void)118 static void recent_menu_create(void)
119 {
120 	gtk_container_foreach(GTK_CONTAINER(recent_menu), recent_menu_item_destroy, NULL);
121 	recent_menu_count = 0;
122 	store_foreach(recent_programs, (GFunc) recent_menu_item_create, NULL);
123 	gtk_widget_show_all(GTK_WIDGET(recent_menu));
124 }
125 
recent_file_name(gint id)126 static char *recent_file_name(gint id)
127 {
128 	char *programfile = g_strdup_printf("program_%d.conf", id);
129 	char *configfile = g_build_filename(geany->app->configdir, "plugins", "scope",
130 		programfile, NULL);
131 
132 	g_free(programfile);
133 	return configfile;
134 }
135 
136 gchar *program_executable;
137 gchar *program_arguments;
138 gchar *program_environment;
139 gchar *program_working_dir;
140 gchar *program_load_script;
141 gboolean program_auto_run_exit;
142 gboolean program_non_stop_mode;
143 gboolean program_temp_breakpoint;
144 gchar *program_temp_break_location;
145 
program_compare(ScpTreeStore * store,GtkTreeIter * iter,const char * name)146 static gint program_compare(ScpTreeStore *store, GtkTreeIter *iter, const char *name)
147 {
148 	const char *name1;
149 
150 	scp_tree_store_get(store, iter, PROGRAM_NAME, &name1, -1);
151 	return !utils_filenamecmp(name1, name);
152 }
153 
154 #define program_find(iter, name) scp_tree_store_traverse(recent_programs, FALSE, (iter), \
155 	NULL, (ScpTreeStoreTraverseFunc) program_compare, (gpointer) (name))
156 
save_program_settings(void)157 static void save_program_settings(void)
158 {
159 	const gchar *program_name = *program_executable ? program_executable :
160 		program_load_script;
161 
162 	if (*program_name)
163 	{
164 		GtkTreeIter iter;
165 		gint id;
166 		GKeyFile *config = g_key_file_new();
167 		char *configfile;
168 
169 		if (program_find(&iter, program_name))
170 		{
171 			scp_tree_store_get(recent_programs, &iter, PROGRAM_ID, &id, -1);
172 			scp_tree_store_move(recent_programs, &iter, 0);
173 		}
174 		else
175 		{
176 			if (scp_tree_store_iter_nth_child(recent_programs, &iter, NULL,
177 				RECENT_COUNT - 1))
178 			{
179 				scp_tree_store_get(recent_programs, &iter, PROGRAM_ID, &id, -1);
180 				scp_tree_store_remove(recent_programs, &iter);
181 			}
182 			else
183 			{
184 				for (id = 1; id < RECENT_COUNT; id++)
185 					if ((recent_bitmap & (1 << id)) == 0)
186 						break;
187 
188 				recent_bitmap |= 1 << id;
189 			}
190 
191 			scp_tree_store_prepend_with_values(recent_programs, &iter, NULL,
192 				PROGRAM_NAME, program_name, PROGRAM_ID, id, -1);
193 		}
194 
195 		configfile = recent_file_name(id);
196 		stash_foreach((GFunc) stash_group_save_to_key_file, config);
197 		breaks_save(config);
198 		watches_save(config);
199 		inspects_save(config);
200 		registers_save(config);
201 		parse_save(config);
202 		utils_key_file_write_to_file(config, configfile);
203 		g_free(configfile);
204 		g_key_file_free(config);
205 	}
206 }
207 
208 gboolean option_open_panel_on_load;
209 gboolean option_open_panel_on_start;
210 gboolean option_update_all_views;
211 gint option_high_bit_mode;
212 gboolean option_member_names;
213 gboolean option_argument_names;
214 gboolean option_long_mr_format;
215 gboolean option_inspect_expand;
216 gint option_inspect_count;
217 gboolean option_library_messages;
218 gboolean option_editor_tooltips;
219 
220 extern gboolean thread_show_group;
221 extern gboolean thread_show_core;
222 extern gboolean stack_show_address;
223 
program_configure(void)224 static void program_configure(void)
225 {
226 	view_column_set_visible("thread_group_id_column", thread_show_group);
227 	view_column_set_visible("thread_core_column", thread_show_core);
228 	view_column_set_visible("stack_addr_column", stack_show_address);
229 }
230 
on_recent_menu_item_activate(G_GNUC_UNUSED GtkMenuItem * menuitem,const gchar * name)231 static void on_recent_menu_item_activate(G_GNUC_UNUSED GtkMenuItem *menuitem, const gchar *name)
232 {
233 	GtkTreeIter iter;
234 
235 	if (utils_filenamecmp(name, *program_executable ? program_executable :
236 		program_load_script) && program_find(&iter, name))
237 	{
238 		gint id;
239 		char *configfile;
240 		GKeyFile *config = g_key_file_new();
241 		GError *gerror = NULL;
242 		gchar *message;
243 
244 		scp_tree_store_get(recent_programs, &iter, PROGRAM_ID, &id, -1);
245 		configfile = recent_file_name(id);
246 
247 		if (g_key_file_load_from_file(config, configfile, G_KEY_FILE_NONE, &gerror))
248 		{
249 			scp_tree_store_move(recent_programs, &iter, 0);
250 			save_program_settings();
251 			stash_foreach((GFunc) stash_group_load_from_key_file, config);
252 			if ((unsigned) option_inspect_expand > EXPAND_MAX)
253 				option_inspect_expand = 100;
254 			breaks_load(config);
255 			watches_load(config);
256 			inspects_load(config);
257 			registers_load(config);
258 			parse_load(config);
259 			message = g_strdup_printf(_("Loaded debug settings for %s."), name);
260 			program_find(&iter, name);
261 			scp_tree_store_move(recent_programs, &iter, 0);
262 			recent_menu_create();
263 			program_configure();
264 		}
265 		else
266 		{
267 			message = g_strdup_printf(_("Could not load debug settings file %s: %s."),
268 				configfile, gerror->message);
269 			g_error_free(gerror);
270 		}
271 
272 		if (menuitem)
273 			ui_set_statusbar(TRUE, "%s", message);
274 		else
275 			msgwin_status_add("%s", message);
276 
277 		g_free(message);
278 		g_key_file_free(config);
279 		g_free(configfile);
280 	}
281 }
282 
build_get_execute(GeanyBuildCmdEntries field)283 static const gchar *build_get_execute(GeanyBuildCmdEntries field)
284 {
285 	return build_get_group_count(GEANY_GBG_EXEC) > 1 ?
286 		build_get_current_menu_item(GEANY_GBG_EXEC, 1, field) : NULL;
287 }
288 
recent_menu_items(void)289 gboolean recent_menu_items(void)
290 {
291 	return recent_menu_count != 0;
292 }
293 
program_context_changed(void)294 void program_context_changed(void)
295 {
296 	const gchar *name = build_get_execute(GEANY_BC_COMMAND);
297 
298 	if (name && debug_state() == DS_INACTIVE)
299 		on_recent_menu_item_activate(NULL, name);
300 }
301 
302 static GtkWidget *program_dialog;
303 static GtkWidget *program_page_vbox;
304 static GtkEntry *program_exec_entry;
305 static GtkEntry *working_dir_entry;
306 static GtkEntry *load_script_entry;
307 static GtkTextBuffer *environment;
308 static GtkButton *long_mr_format;
309 static GtkWidget *auto_run_exit;
310 static GtkWidget *temp_breakpoint;
311 static GtkToggleButton *delete_all_items;
312 static GtkWidget *import_button;
313 static gboolean dialog_long_mr_format;
314 static const gchar *LONG_MR_FORMAT[2];
315 
316 #define build_check_execute() (build_get_execute(GEANY_BC_COMMAND) || \
317 	build_get_execute(GEANY_BC_WORKING_DIR))
318 static gboolean last_state_inactive = TRUE;
319 
program_update_state(DebugState state)320 void program_update_state(DebugState state)
321 {
322 	gboolean inactive = state == DS_INACTIVE;
323 
324 	if (inactive != last_state_inactive)
325 	{
326 		gtk_widget_set_sensitive(program_page_vbox, inactive);
327 		gtk_widget_set_sensitive(import_button, inactive && build_check_execute());
328 		last_state_inactive = inactive;
329 	}
330 }
331 
on_program_name_entry_changed(G_GNUC_UNUSED GtkEditable * editable,G_GNUC_UNUSED gpointer gdata)332 static void on_program_name_entry_changed(G_GNUC_UNUSED GtkEditable *editable,
333 	G_GNUC_UNUSED gpointer gdata)
334 {
335 	gboolean sensitive = *gtk_entry_get_text(program_exec_entry) ||
336 		*gtk_entry_get_text(load_script_entry);
337 
338 	gtk_widget_set_sensitive(auto_run_exit, sensitive);
339 	gtk_widget_set_sensitive(temp_breakpoint, sensitive);
340 	g_signal_emit_by_name(temp_breakpoint, "toggled");
341 }
342 
on_program_setup(G_GNUC_UNUSED const MenuItem * menu_item)343 void on_program_setup(G_GNUC_UNUSED const MenuItem *menu_item)
344 {
345 	gtk_text_buffer_set_text(environment, program_environment, -1);
346 	stash_foreach((GFunc) stash_group_display, NULL);
347 	gtk_button_set_label(long_mr_format, LONG_MR_FORMAT[option_long_mr_format]);
348 	dialog_long_mr_format = option_long_mr_format;
349 	gtk_widget_set_sensitive(import_button, last_state_inactive && build_check_execute());
350 	on_program_name_entry_changed(NULL, NULL);
351 	gtk_toggle_button_set_active(delete_all_items, FALSE);
352 	if (debug_state() == DS_INACTIVE)
353 		gtk_widget_grab_focus(GTK_WIDGET(program_exec_entry));
354 	gtk_dialog_run(GTK_DIALOG(program_dialog));
355 }
356 
on_long_mr_format_clicked(GtkButton * button,G_GNUC_UNUSED gpointer gdata)357 static void on_long_mr_format_clicked(GtkButton *button, G_GNUC_UNUSED gpointer gdata)
358 {
359 	dialog_long_mr_format ^= TRUE;
360 	gtk_button_set_label(button, LONG_MR_FORMAT[dialog_long_mr_format]);
361 }
362 
on_temp_breakpoint_toggled(GtkToggleButton * togglebutton,GtkWidget * widget)363 static void on_temp_breakpoint_toggled(GtkToggleButton *togglebutton, GtkWidget *widget)
364 {
365 	gtk_widget_set_sensitive(widget, gtk_widget_get_sensitive(temp_breakpoint) &&
366 		gtk_toggle_button_get_active(togglebutton));
367 }
368 
on_program_import_button_clicked(G_GNUC_UNUSED GtkButton * button,G_GNUC_UNUSED gpointer gdata)369 static void on_program_import_button_clicked(G_GNUC_UNUSED GtkButton *button,
370 	G_GNUC_UNUSED gpointer gdata)
371 {
372 	const gchar *executable = build_get_execute(GEANY_BC_COMMAND);
373 	const char *workdir = build_get_execute(GEANY_BC_WORKING_DIR);
374 	gtk_entry_set_text(program_exec_entry, executable ? executable : "");
375 	gtk_entry_set_text(working_dir_entry, workdir ? workdir : "");
376 }
377 
check_dialog_path(GtkEntry * entry,gboolean file,int mode)378 static gboolean check_dialog_path(GtkEntry *entry, gboolean file, int mode)
379 {
380 	const gchar *pathname = gtk_entry_get_text(entry);
381 
382 	if (utils_check_path(pathname, file, mode))
383 		return TRUE;
384 
385 	if (errno == ENOENT)
386 	{
387 		return dialogs_show_question(_("%s: %s.\n\nContinue?"), pathname,
388 			g_strerror(errno));
389 	}
390 
391 	show_errno(pathname);
392 	return FALSE;
393 }
394 
on_program_ok_button_clicked(G_GNUC_UNUSED GtkButton * button,G_GNUC_UNUSED gpointer gdata)395 static void on_program_ok_button_clicked(G_GNUC_UNUSED GtkButton *button,
396 	G_GNUC_UNUSED gpointer gdata)
397 {
398 	if (check_dialog_path(program_exec_entry, TRUE, R_OK | X_OK) &&
399 		check_dialog_path(working_dir_entry, FALSE, X_OK) &&
400 		check_dialog_path(load_script_entry, TRUE, R_OK))
401 	{
402 		const gchar *program_name = gtk_entry_get_text(program_exec_entry);
403 
404 		if (*program_name == '\0')
405 			program_name = gtk_entry_get_text(load_script_entry);
406 
407 		if (utils_filenamecmp(program_name, *program_executable ? program_executable :
408 			program_load_script))
409 		{
410 			save_program_settings();
411 		}
412 
413 		stash_foreach((GFunc) stash_group_update, NULL);
414 		option_long_mr_format = dialog_long_mr_format;
415 		g_free(program_environment);
416 		program_environment = utils_text_buffer_get_text(environment, -1);
417 		save_program_settings();
418 		recent_menu_create();
419 		program_configure();
420 		gtk_widget_hide(program_dialog);
421 
422 		if (gtk_toggle_button_get_active(delete_all_items) &&
423 			dialogs_show_question(_("Delete all breakpoints, watches et cetera?")))
424 		{
425 			breaks_delete_all();
426 			watches_delete_all();
427 			inspects_delete_all();
428 			registers_delete_all();
429 		}
430 	}
431 }
432 
program_load_config(GKeyFile * config)433 void program_load_config(GKeyFile *config)
434 {
435 	utils_load(config, "recent", recent_program_load);
436 	recent_menu_create();
437 	config = g_key_file_new();
438 	/* load from empty config file == set defaults */
439 	stash_foreach((GFunc) stash_group_load_from_key_file, config);
440 	g_key_file_free(config);
441 	program_configure();
442 }
443 
program_init(void)444 void program_init(void)
445 {
446 	GtkWidget *widget;
447 	StashGroup *group = stash_group_new("program");
448 	extern gboolean thread_select_on_running;
449 	extern gboolean thread_select_on_stopped;
450 	extern gboolean thread_select_on_exited;
451 	extern gboolean thread_select_follow;
452 
453 	program_dialog = dialog_connect("program_dialog");
454 	program_page_vbox = get_widget("program_page_vbox");
455 
456 	program_exec_entry = GTK_ENTRY(get_widget("program_executable_entry"));
457 	gtk_entry_set_max_length(program_exec_entry, PATH_MAX);
458 	stash_group_add_entry(group, &program_executable, "executable", "", program_exec_entry);
459 	ui_setup_open_button_callback(get_widget("program_executable_button"), NULL,
460 		GTK_FILE_CHOOSER_ACTION_OPEN, program_exec_entry);
461 
462 	stash_group_add_entry(group, &program_arguments, "arguments", "",
463 		get_widget("program_arguments_entry"));
464 	widget = get_widget("program_environment");
465 	environment = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
466 	stash_group_add_string(group, &program_environment, "environment", "");
467 
468 	working_dir_entry = GTK_ENTRY(get_widget("program_working_dir_entry"));
469 	gtk_entry_set_max_length(working_dir_entry, PATH_MAX);
470 	stash_group_add_entry(group, &program_working_dir, "working_dir", "", working_dir_entry);
471 	ui_setup_open_button_callback(get_widget("program_working_dir_button"), NULL,
472 		GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, working_dir_entry);
473 
474 	load_script_entry = GTK_ENTRY(get_widget("program_load_script_entry"));
475 	gtk_entry_set_max_length(load_script_entry, PATH_MAX);
476 	stash_group_add_entry(group, &program_load_script, "load_script", "", load_script_entry);
477 	ui_setup_open_button_callback(get_widget("program_load_script_button"), NULL,
478 		GTK_FILE_CHOOSER_ACTION_OPEN, load_script_entry);
479 
480 	auto_run_exit = get_widget("program_auto_run_exit");
481 	stash_group_add_toggle_button(group, &program_auto_run_exit, "auto_run_exit", TRUE,
482 		auto_run_exit);
483 	stash_group_add_toggle_button(group, &program_non_stop_mode, "non_stop_mode", FALSE,
484 		get_widget("program_non_stop_mode"));
485 
486 	temp_breakpoint = get_widget("program_temp_breakpoint");
487 	stash_group_add_toggle_button(group, &program_temp_breakpoint, "temp_breakpoint", FALSE,
488 		temp_breakpoint);
489 	widget = get_widget("program_temp_break_location");
490 	gtk_entry_set_max_length(GTK_ENTRY(widget), PATH_MAX + 0xFF);
491 	stash_group_add_entry(group, &program_temp_break_location, "temp_break_location", "",
492 		widget);
493 	program_group = group;
494 	delete_all_items = GTK_TOGGLE_BUTTON(get_widget("program_delete_all_items"));
495 
496 	g_signal_connect(program_exec_entry, "changed",
497 		G_CALLBACK(on_program_name_entry_changed), NULL);
498 	g_signal_connect(load_script_entry, "changed",
499 		G_CALLBACK(on_program_name_entry_changed), NULL);
500 	g_signal_connect(temp_breakpoint, "toggled", G_CALLBACK(on_temp_breakpoint_toggled),
501 		widget);
502 
503 	import_button = get_widget("program_import");
504 	g_signal_connect(import_button, "clicked", G_CALLBACK(on_program_import_button_clicked),
505 		NULL);
506 
507 	widget = get_widget("program_ok");
508 	g_signal_connect(widget, "clicked", G_CALLBACK(on_program_ok_button_clicked), NULL);
509 	gtk_widget_grab_default(widget);
510 
511 	group = stash_group_new("options");
512 
513 	stash_group_add_toggle_button(group, &option_open_panel_on_load, "open_panel_on_load",
514 		TRUE, get_widget("option_open_panel_on_load"));
515 	stash_group_add_toggle_button(group, &option_open_panel_on_start, "open_panel_on_start",
516 		TRUE, get_widget("option_open_panel_on_start"));
517 	stash_group_add_toggle_button(group, &option_update_all_views, "update_all_views", FALSE,
518 		get_widget("option_update_all_views"));
519 
520 	stash_group_add_radio_buttons(group, &option_high_bit_mode, "high_bit_mode", HB_7BIT,
521 		get_widget("option_high_bit_mode_7bit"), HB_7BIT,
522 		get_widget("option_high_bit_mode_locale"), HB_LOCALE,
523 		get_widget("option_high_bit_mode_utf8"), HB_UTF8, NULL);
524 
525 	stash_group_add_toggle_button(group, &option_member_names, "member_names", TRUE,
526 		get_widget("option_member_names"));
527 	stash_group_add_toggle_button(group, &option_argument_names, "argument_names", TRUE,
528 		get_widget("option_argument_names"));
529 	long_mr_format = GTK_BUTTON(get_widget("option_mr_long_mr_format"));
530 	stash_group_add_boolean(group, &option_long_mr_format, "long_mr_format", TRUE);
531 	LONG_MR_FORMAT[FALSE] = _("as _Name=value");
532 	LONG_MR_FORMAT[TRUE] = _("as _Name = value");
533 	g_signal_connect(long_mr_format, "clicked", G_CALLBACK(on_long_mr_format_clicked), NULL);
534 
535 	stash_group_add_toggle_button(group, &option_inspect_expand, "inspect_expand", TRUE,
536 		get_widget("option_inspect_expand"));
537 	stash_group_add_spin_button_integer(group, &option_inspect_count, "inspect_count", 100,
538 		get_widget("option_inspect_count"));
539 
540 	stash_group_add_toggle_button(group, &option_library_messages, "library_messages", FALSE,
541 		get_widget("option_library_messages"));
542 	stash_group_add_toggle_button(group, &option_editor_tooltips, "editor_tooltips", TRUE,
543 		get_widget("option_editor_tooltips"));
544 	stash_group_add_boolean(group, &stack_show_address, "stack_show_address", TRUE);
545 	options_group = group;
546 
547 	group = stash_group_new("terminal");
548 #ifdef G_OS_UNIX
549 	stash_group_add_boolean(group, &terminal_auto_show, "auto_show", FALSE);
550 	stash_group_add_boolean(group, &terminal_auto_hide, "auto_hide", FALSE);
551 	stash_group_add_boolean(group, &terminal_show_on_error, "show_on_error", FALSE);
552 #endif
553 	terminal_group = group;
554 
555 	group = stash_group_new("thread");
556 	stash_group_add_boolean(group, &thread_select_on_running, "select_on_running", FALSE);
557 	stash_group_add_boolean(group, &thread_select_on_stopped, "select_on_stopped", TRUE);
558 	stash_group_add_boolean(group, &thread_select_on_exited, "select_on_exited", TRUE);
559 	stash_group_add_boolean(group, &thread_select_follow, "select_follow", TRUE);
560 	stash_group_add_boolean(group, &thread_show_group, "show_group", TRUE);
561 	stash_group_add_boolean(group, &thread_show_core, "show_core", TRUE);
562 	thread_group = group;
563 
564 	recent_programs = SCP_TREE_STORE(get_object("recent_program_store"));
565 	recent_bitmap = 0;
566 	recent_menu = get_widget("program_recent_menu");
567 }
568 
program_finalize(void)569 void program_finalize(void)
570 {
571 	char *configfile = prefs_file_name();
572 	GKeyFile *config = g_key_file_new();
573 
574 	save_program_settings();
575 	g_key_file_load_from_file(config, configfile, G_KEY_FILE_NONE, NULL);
576 	store_save(recent_programs, config, "recent", recent_program_save);
577 	utils_key_file_write_to_file(config, configfile);
578 	g_key_file_free(config);
579 	g_free(configfile);
580 
581 	gtk_widget_destroy(program_dialog);
582 	stash_foreach((GFunc) stash_group_destroy, NULL);
583 }
584