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