1 /*
2 roxterm - VTE/GTK terminal emulator with tabs
3 Copyright (C) 2004-2015 Tony Houghton <h@realh.co.uk>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20
21 #include "capplet.h"
22 #include "colourgui.h"
23 #include "configlet.h"
24 #include "dlg.h"
25 #include "dynopts.h"
26 #include "getname.h"
27 #include "optsdbus.h"
28 #include "optsfile.h"
29 #include "profilegui.h"
30 #include "resources.h"
31 #include "shortcuts.h"
32
33 #include <ctype.h>
34 #include <stdarg.h>
35 #include <stdlib.h>
36 #include <string.h>
37
38 #include <unistd.h>
39 #include <glib/gstdio.h>
40
41 extern gboolean g_file_set_contents(const gchar * filename,
42 const gchar * contents, gssize length, GError ** error);
43
44 static int profile_lock = 0;
45 static int colours_lock = 0;
46 static int shortcuts_lock = 0;
47
48 struct _ConfigletList {
49 const char *family;
50 GtkTreeView *tvwidget;
51 GtkListStore *list;
52 gpointer foreach_data;
53 ConfigletData *cg;
54 };
55
56 struct _ConfigletData {
57 CappletData capp;
58 gboolean ignore_destroy;
59 GtkWidget *widget;
60 GtkSizeGroup *size_group;
61 ConfigletList profile;
62 ConfigletList colours;
63 ConfigletList shortcuts;
64 };
65
66 enum {
67 cfColumn_Radio,
68 cfColumn_Name,
69
70 cfColumn_NColumns
71 };
72
73 static gboolean ignore_changes = FALSE;
74
75 static ConfigletData *configlet_data;
76
77 static void set_sensitive_for_list(ConfigletList *cl);
78
79 /* Converts "Profiles" into "profile" etc */
convert_family_name(const char * family)80 static char *convert_family_name(const char *family)
81 {
82 char *optkey = g_strdup(family);
83
84 /* Strip final 's' from "Profiles" and make first letter lower-case */
85 if (optkey[0] == 'P')
86 optkey[strlen(optkey) - 1] = 0;
87 optkey[0] = tolower(optkey[0]);
88 return optkey;
89 }
90
full_name_from_family(const char * family)91 static const char *full_name_from_family(const char *family)
92 {
93 if (!strcmp(family, "Profiles"))
94 {
95 return _("Profile");
96 }
97 else if (!strcmp(family, "Colours"))
98 {
99 return _("Colour Scheme");
100 }
101 else if (!strcmp(family, "Shortcuts"))
102 {
103 return _("Keyboard Shortcuts Scheme");
104 }
105 else
106 {
107 g_critical(_("Full name for family '%s' not known"), family);
108 return family;
109 }
110 }
111
configlet_set_sensitive_button(const char * wbasename,const char * butname,gboolean sensitive)112 static void configlet_set_sensitive_button(const char *wbasename,
113 const char *butname, gboolean sensitive)
114 {
115 char *button_name = g_strdup_printf("%s_%s", wbasename, butname);
116 GtkWidget *widget =
117 GTK_WIDGET(gtk_builder_get_object(configlet_data->capp.builder,
118 button_name));
119
120 g_free(button_name);
121 g_return_if_fail(widget != NULL);
122 gtk_widget_set_sensitive(widget, sensitive);
123 }
124
configlet_set_sensitive(const char * wbasename,gboolean sensitive)125 static void configlet_set_sensitive(const char *wbasename, gboolean sensitive)
126 {
127 int lock;
128
129 if (!configlet_data)
130 return;
131 if (!strcmp(wbasename, "profile"))
132 {
133 lock = profile_lock;
134 }
135 else if (!strcmp(wbasename, "colours"))
136 {
137 lock = colours_lock;
138 }
139 else if (!strcmp(wbasename, "shortcuts"))
140 {
141 lock = shortcuts_lock;
142 }
143 else
144 {
145 g_critical(_("Bad options family basename '%s'"), wbasename);
146 return;
147 }
148 configlet_set_sensitive_button(wbasename, "delete", !lock && sensitive);
149 configlet_set_sensitive_button(wbasename, "rename", !lock && sensitive);
150 }
151
is_in_user_dir(const char * family,const char * profile_name)152 static gboolean is_in_user_dir(const char *family, const char *profile_name)
153 {
154 char *pathname = options_file_build_filename(family, profile_name,
155 NULL);
156 char *savename = options_file_filename_for_saving(family,
157 profile_name, NULL);
158 gboolean result = (pathname != NULL) && !strcmp(savename, pathname);
159
160 g_free(savename);
161 g_free(pathname);
162
163 return result;
164 }
165
shade_actions_for_name(ConfigletList * cl,const char * name)166 static void shade_actions_for_name(ConfigletList *cl, const char *name)
167 {
168 char *wbasename = convert_family_name(cl->family);
169
170 configlet_set_sensitive(wbasename, is_in_user_dir(cl->family, name));
171 g_free(wbasename);
172 }
173
configlet_delete(ConfigletData * cg)174 static void configlet_delete(ConfigletData *cg)
175 {
176 if (cg->widget)
177 {
178 cg->ignore_destroy = TRUE;
179 gtk_widget_destroy(cg->widget);
180 cg->widget = NULL;
181 }
182 if (cg->capp.builder)
183 {
184 UNREF_LOG(g_object_unref(cg->capp.builder));
185 cg->capp.builder = NULL;
186 }
187 g_free(cg);
188 configlet_data = NULL;
189 capplet_dec_windows();
190 }
191
family_name_to_opt_key(const char * family)192 static const char *family_name_to_opt_key(const char *family)
193 {
194 if (!strcmp(family, "Profiles"))
195 return "profile";
196 else if (!strcmp(family, "Colours"))
197 return "colour_scheme";
198 else if (!strcmp(family, "Shortcuts"))
199 return "shortcut_scheme";
200 return NULL;
201 }
202
configlet_get_configured_name(ConfigletList * cl)203 static char *configlet_get_configured_name(ConfigletList *cl)
204 {
205 const char *optkey = family_name_to_opt_key(cl->family);
206
207 g_return_val_if_fail(optkey, NULL);
208 return options_lookup_string_with_default(cl->cg->capp.options, optkey,
209 strcmp(cl->family, "Colours") ? "Default" : "GTK");
210 }
211
configlet_list_build(ConfigletList * cl)212 static void configlet_list_build(ConfigletList *cl)
213 {
214 char const **item_list;
215 char const **pitem;
216 char *selected_name = configlet_get_configured_name(cl);
217
218 item_list = (char const **) dynamic_options_list_sorted(
219 dynamic_options_get(cl->family));
220
221 if (cl->list)
222 {
223 gtk_list_store_clear(cl->list);
224 }
225 else
226 {
227 cl->list = gtk_list_store_new(cfColumn_NColumns,
228 G_TYPE_BOOLEAN, G_TYPE_STRING);
229 }
230
231 for (pitem = item_list; *pitem; ++pitem)
232 {
233 GtkTreeIter iter;
234
235 gtk_list_store_append(cl->list, &iter);
236 gtk_list_store_set(cl->list, &iter,
237 cfColumn_Radio, !strcmp(selected_name, *pitem),
238 cfColumn_Name, *pitem,
239 -1);
240 }
241 g_strfreev((char **) item_list);
242 g_free(selected_name);
243 }
244
245 /* Selects matching name and clears cl->foreach_data if it finds a match */
foreach_find_name(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)246 static gboolean foreach_find_name(GtkTreeModel *model, GtkTreePath *path,
247 GtkTreeIter *iter, gpointer data)
248 {
249 char *name = NULL;
250 ConfigletList *cl = data;
251 (void) path;
252
253 gtk_tree_model_get(model, iter, cfColumn_Name, &name, -1);
254 if (name && cl->foreach_data && !strcmp(name, cl->foreach_data))
255 {
256 gtk_tree_selection_select_iter(
257 gtk_tree_view_get_selection(cl->tvwidget), iter);
258 cl->foreach_data = NULL;
259 return TRUE;
260 }
261 g_free(name);
262 return FALSE;
263 }
264
265 /* If name isn't in list, first item is selected */
configlet_select_name(ConfigletList * cl,const char * name)266 static void configlet_select_name(ConfigletList *cl, const char *name)
267 {
268 GtkTreeIter iter;
269
270 ignore_changes = TRUE;
271 /* foreach_data is set to NULL if our function finds name */
272 cl->foreach_data = (gpointer) name;
273 if (name)
274 gtk_tree_model_foreach(GTK_TREE_MODEL(cl->list), foreach_find_name, cl);
275 if (cl->foreach_data || !name)
276 {
277 /* No match so select first item */
278 if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(cl->list), &iter))
279 {
280 gtk_tree_selection_select_iter(
281 gtk_tree_view_get_selection(cl->tvwidget), &iter);
282 }
283 }
284 ignore_changes = FALSE;
285 }
286
287 /* Sets the radio of whichever item matches string pointed to by data */
update_radios(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)288 gboolean update_radios(GtkTreeModel *model,
289 GtkTreePath *path, GtkTreeIter *iter, gpointer data)
290 {
291 char *name = NULL;
292 (void) path;
293
294 gtk_tree_model_get(model, iter, cfColumn_Name, &name, -1);
295 gtk_list_store_set(GTK_LIST_STORE(model), iter, cfColumn_Radio,
296 name && !strcmp(name, data), -1);
297 return FALSE;
298 }
299
300 /* When we receive this event, the tree model's radio is in its prior state so
301 * if it isn't already selected we select it, otherwise do nothing. */
configlet_cell_toggled(GtkCellRendererToggle * cell,gchar * path_string,ConfigletList * cl)302 static void configlet_cell_toggled(GtkCellRendererToggle *cell,
303 gchar *path_string, ConfigletList *cl)
304 {
305 GtkTreeIter iter;
306 char *name;
307 gboolean active;
308 (void) cell;
309
310 g_return_if_fail(
311 gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(cl->list),
312 &iter, path_string));
313 gtk_tree_model_get(GTK_TREE_MODEL(cl->list), &iter,
314 cfColumn_Radio, &active,
315 cfColumn_Name, &name, -1);
316 if (!active)
317 {
318 gtk_tree_model_foreach(GTK_TREE_MODEL(cl->list), update_radios, name);
319 options_set_string(cl->cg->capp.options,
320 family_name_to_opt_key(cl->family), name);
321 capplet_save_file(cl->cg->capp.options);
322 }
323 g_free(name);
324 }
325
configlet_list_init(ConfigletList * cl,GtkWidget * widget,const char * family)326 static void configlet_list_init(ConfigletList *cl, GtkWidget *widget,
327 const char *family)
328 {
329 GtkTreeViewColumn *rcolumn, *tcolumn;
330 GtkCellRenderer *radio;
331 char *cname;
332
333 cl->tvwidget = GTK_TREE_VIEW(widget);
334 cl->family = family;
335 cl->list = gtk_list_store_new(cfColumn_NColumns,
336 G_TYPE_BOOLEAN, G_TYPE_STRING);
337 gtk_tree_view_set_model(cl->tvwidget, GTK_TREE_MODEL(cl->list));
338
339 radio = gtk_cell_renderer_toggle_new();
340 gtk_cell_renderer_toggle_set_radio(GTK_CELL_RENDERER_TOGGLE(radio),
341 TRUE);
342 g_object_set(radio, "activatable", TRUE, NULL);
343 g_signal_connect(radio, "toggled",
344 G_CALLBACK(configlet_cell_toggled), cl);
345 rcolumn = gtk_tree_view_column_new_with_attributes(
346 NULL,
347 radio,
348 "active",
349 cfColumn_Radio,
350 NULL);
351 gtk_tree_view_append_column(cl->tvwidget, rcolumn);
352 gtk_tree_view_column_set_visible(rcolumn, TRUE);
353
354 tcolumn = gtk_tree_view_column_new_with_attributes(
355 NULL,
356 gtk_cell_renderer_text_new(),
357 "text",
358 cfColumn_Name,
359 NULL);
360 gtk_tree_view_append_column(cl->tvwidget, tcolumn);
361 gtk_tree_view_column_set_visible(tcolumn, TRUE);
362
363 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(cl->tvwidget),
364 GTK_SELECTION_BROWSE);
365 configlet_list_build(cl);
366 configlet_select_name(cl, cname = configlet_get_configured_name(cl));
367 g_free(cname);
368 gtk_widget_realize(widget);
369 gtk_tree_view_columns_autosize(cl->tvwidget);
370 }
371
372 /********************************************************************/
373 /* Generic handlers */
374
on_Configlet_destroy(GtkWidget * widget,ConfigletData * cg)375 void on_Configlet_destroy(GtkWidget * widget, ConfigletData * cg)
376 {
377 (void) widget;
378 if (cg->ignore_destroy)
379 {
380 return;
381 }
382 else
383 {
384 cg->ignore_destroy = TRUE;
385 configlet_delete(cg);
386 }
387 }
388
on_Configlet_response(GtkWidget * widget,int response,ConfigletData * cg)389 void on_Configlet_response(GtkWidget * widget, int response, ConfigletData * cg)
390 {
391 (void) response;
392 (void) widget;
393 configlet_delete(cg);
394 }
395
on_Configlet_close(GtkWidget * widget,ConfigletData * cg)396 void on_Configlet_close(GtkWidget * widget, ConfigletData * cg)
397 {
398 (void) widget;
399 configlet_delete(cg);
400 }
401
402
403 /********************************************************************/
404 /* Family-specific handlers and helpers */
405
get_selected_iter(ConfigletList * cl,GtkTreeModel ** pmodel,GtkTreeIter * piter)406 static gboolean get_selected_iter(ConfigletList *cl,
407 GtkTreeModel **pmodel, GtkTreeIter *piter)
408 {
409 return gtk_tree_selection_get_selected(
410 gtk_tree_view_get_selection(cl->tvwidget), pmodel, piter);
411 }
412
get_selected_index(ConfigletList * cl)413 static int get_selected_index(ConfigletList *cl)
414 {
415 /* FIXME: Is there a better way of doing this? */
416 GtkTreeIter iter;
417 GtkTreeModel *model = gtk_tree_view_get_model(cl->tvwidget);
418 GtkTreeSelection *selection;
419 int n = 0;
420
421 selection = gtk_tree_view_get_selection(cl->tvwidget);
422 if (!gtk_tree_model_get_iter_first(model, &iter))
423 return -1;
424 while (!gtk_tree_selection_iter_is_selected(selection, &iter))
425 {
426 if (!gtk_tree_model_iter_next(model, &iter))
427 return -1;
428 ++n;
429 }
430 return n;
431 }
432
get_selected_name(ConfigletList * cl)433 static char *get_selected_name(ConfigletList *cl)
434 {
435 GtkTreeIter iter;
436 GtkTreeModel *model;
437 char *name = NULL;
438
439 if (get_selected_iter(cl, &model, &iter))
440 {
441 gtk_tree_model_get(model, &iter, cfColumn_Name, &name, -1);
442 }
443 return name;
444 }
445
get_selected_state(ConfigletList * cl)446 static gboolean get_selected_state(ConfigletList *cl)
447 {
448 GtkTreeIter iter;
449 GtkTreeModel *model;
450 gboolean state = FALSE;
451
452 if (get_selected_iter(cl, &model, &iter))
453 {
454 gtk_tree_model_get(model, &iter, cfColumn_Radio, &state, -1);
455 }
456 return state;
457 }
458
459 /* Finds an iter to insert a name before to preserve ordering. If result is
460 * FALSE iter is not valid and you should append instead.
461 */
find_list_insert_point(ConfigletList * cl,const char * new_name,GtkTreeIter * iter)462 static gboolean find_list_insert_point(ConfigletList *cl, const char *new_name,
463 GtkTreeIter *iter)
464 {
465 GtkTreeModel *tm = GTK_TREE_MODEL(cl->list);
466
467 if (gtk_tree_model_get_iter_first(tm, iter))
468 {
469 do
470 {
471 char *n;
472 int cmp;
473
474 gtk_tree_model_get(tm, iter, cfColumn_Name, &n, -1);
475 cmp = dynamic_options_strcmp(new_name, n);
476 g_free(n);
477 if (cmp < 0)
478 {
479 return TRUE;
480 }
481 }
482 while (gtk_tree_model_iter_next(tm, iter));
483 }
484 return FALSE;
485 }
486
add_name_to_list(ConfigletList * cl,const char * new_name)487 static void add_name_to_list(ConfigletList *cl, const char *new_name)
488 {
489 GtkTreeIter iter, insert;
490 char *cname = configlet_get_configured_name(cl);
491
492 if (find_list_insert_point(cl, new_name, &insert))
493 {
494 gtk_list_store_insert_before(cl->list, &iter, &insert);
495 }
496 else
497 {
498 gtk_list_store_append(cl->list, &iter);
499 }
500 gtk_list_store_set(cl->list, &iter,
501 cfColumn_Radio,
502 cname && !strcmp(new_name, cname),
503 cfColumn_Name, new_name,
504 -1);
505 gtk_tree_selection_select_iter(gtk_tree_view_get_selection(cl->tvwidget),
506 &iter);
507 g_free(cname);
508 set_sensitive_for_list(cl);
509 }
510
511 /*This checks whether the name is still available in dynopts family - which
512 * can happen if the removed name was in ~/.config overriding a system
513 * profile/scheme with the same name - and leaves it if so, returning FALSE.
514 */
remove_name_from_list(ConfigletList * cl,const char * old_name)515 static gboolean remove_name_from_list(ConfigletList *cl, const char *old_name)
516 {
517 gboolean remove = TRUE;
518 char **name_list =
519 dynamic_options_list(dynamic_options_get(cl->family));
520 char **pname;
521
522 for (pname = name_list; *pname; ++pname)
523 {
524 if (!strcmp(old_name, *pname))
525 {
526 remove = FALSE;
527 dlg_message(GTK_WINDOW(cl->cg->widget),
528 _("'%s' will now refer to a %s provided by the system"),
529 old_name, full_name_from_family(cl->family));
530 }
531 }
532 g_strfreev(name_list);
533
534 if (remove)
535 {
536 GtkTreeIter iter;
537
538 gtk_tree_selection_get_selected(
539 gtk_tree_view_get_selection(cl->tvwidget), NULL, &iter);
540 gtk_list_store_remove(cl->list, &iter);
541 }
542 set_sensitive_for_list(cl);
543 return remove;
544 }
545
configlet_copy(ConfigletList * cl,const char * old_leaf,const char * new_leaf)546 static void configlet_copy(ConfigletList *cl,
547 const char *old_leaf, const char *new_leaf)
548 {
549 gboolean success = FALSE;
550 char *old_path = options_file_build_filename(cl->family, old_leaf,
551 NULL);
552
553 success = options_file_copy_to_user_dir(GTK_WINDOW(cl->cg->widget),
554 old_path, cl->family, new_leaf);
555 g_free(old_path);
556 if (success)
557 {
558 add_name_to_list(cl, new_leaf);
559 optsdbus_send_stuff_changed_signal(OPTSDBUS_ADDED, cl->family,
560 new_leaf, NULL);
561 }
562 }
563
configlet_rename(ConfigletList * cl,const char * old_leaf,const char * new_leaf)564 static void configlet_rename(ConfigletList *cl,
565 const char *old_leaf, const char *new_leaf)
566 {
567 gboolean success = FALSE;
568 char *old_path = options_file_build_filename(cl->family, old_leaf,
569 NULL);
570 char *new_path = options_file_filename_for_saving(cl->family,
571 new_leaf, NULL);
572
573 success = (g_rename(old_path, new_path) == 0);
574 g_free(new_path);
575 g_free(old_path);
576 if (success)
577 {
578 GtkTreeModel *model;
579 GtkTreeIter iter, insert;
580 gboolean state;
581
582 get_selected_iter(cl, &model, &iter);
583 gtk_tree_model_get(model, &iter, cfColumn_Radio, &state, -1);
584 gtk_list_store_set(cl->list, &iter,
585 cfColumn_Radio, state, cfColumn_Name, new_leaf, -1);
586 if (find_list_insert_point(cl, new_leaf, &insert))
587 {
588 gtk_list_store_move_before(cl->list, &iter, &insert);
589 }
590 else
591 {
592 gtk_list_store_move_before(cl->list, &iter, NULL);
593 }
594 /*
595 g_debug("Sending d-bus message: %s, %s, %s, %s",
596 OPTSDBUS_RENAMED, cl->family, old_leaf, new_leaf);
597 */
598 optsdbus_send_stuff_changed_signal(OPTSDBUS_RENAMED, cl->family,
599 old_leaf, new_leaf);
600 }
601 else
602 {
603 dlg_warning(GTK_WINDOW(cl->cg->widget),
604 _("Unable to rename profile/scheme"));
605 }
606 }
607
edit_thing_by_name(ConfigletList * cl,const char * name)608 static void edit_thing_by_name(ConfigletList *cl, const char *name)
609 {
610 if (!strcmp(cl->family, "Profiles"))
611 {
612 profilegui_open(name);
613 }
614 else if (!strcmp(cl->family, "Colours"))
615 {
616 colourgui_open(name);
617 }
618 else if (!strcmp(cl->family, "Shortcuts"))
619 {
620 shortcuts_edit(GTK_WINDOW(cl->cg->widget), name);
621 }
622 else
623 {
624 g_critical(_("Option family name '%s' not recognised for editing"),
625 cl->family);
626 }
627 }
628
edit_thing_by_iter(ConfigletList * cl,GtkTreeIter * iter)629 static void edit_thing_by_iter(ConfigletList *cl, GtkTreeIter *iter)
630 {
631 char *name;
632
633 gtk_tree_model_get(GTK_TREE_MODEL(cl->list), iter,
634 cfColumn_Name, &name, -1);
635 g_return_if_fail(name != NULL);
636 edit_thing_by_name(cl, name);
637 g_free(name);
638 }
639
edit_selected_thing(ConfigletList * cl)640 static gboolean edit_selected_thing(ConfigletList *cl)
641 {
642 char *name = get_selected_name(cl);
643
644 if (!name)
645 {
646 g_warning(_("No selection to edit"));
647 return FALSE;
648 }
649
650 edit_thing_by_name(cl, name);
651 g_free(name);
652 return TRUE;
653 }
654
on_profile_edit_clicked(GtkButton * button,ConfigletData * cg)655 void on_profile_edit_clicked(GtkButton *button, ConfigletData *cg)
656 {
657 (void) button;
658 edit_selected_thing(&cg->profile);
659 }
660
on_colours_edit_clicked(GtkButton * button,ConfigletData * cg)661 void on_colours_edit_clicked(GtkButton *button, ConfigletData *cg)
662 {
663 (void) button;
664 edit_selected_thing(&cg->colours);
665 }
666
on_row_activated(GtkTreeView * tvwidget,GtkTreePath * path,GtkTreeViewColumn * column,ConfigletData * cg)667 void on_row_activated(GtkTreeView *tvwidget, GtkTreePath *path,
668 GtkTreeViewColumn *column, ConfigletData *cg)
669 {
670 GtkTreeIter iter;
671 ConfigletList *cl = g_object_get_data(G_OBJECT(tvwidget),
672 "ROXTermConfigletList");
673 (void) column;
674 (void) cg;
675
676 if (gtk_tree_model_get_iter(GTK_TREE_MODEL(cl->list), &iter, path))
677 edit_thing_by_iter(cl, &iter);
678 else
679 g_warning(_("Can't get iterator for activated item"));
680 }
681
on_tree_selection_changed(GtkTreeSelection * selection,ConfigletList * cl)682 static void on_tree_selection_changed(GtkTreeSelection *selection,
683 ConfigletList *cl)
684 {
685 char *name = get_selected_name(cl);
686 (void) selection;
687
688 if (name)
689 {
690 shade_actions_for_name(cl, name);
691 g_free(name);
692 }
693 }
694
on_copy_clicked(ConfigletList * cl)695 static void on_copy_clicked(ConfigletList *cl)
696 {
697 char *old_name = get_selected_name(cl);
698 char *title = NULL;
699 const char *button_label = NULL;
700 const char **existing = NULL;
701 DynamicOptions *dynopts = dynamic_options_get(cl->family);
702
703 title = g_strdup_printf(_("Copy %s"),
704 full_name_from_family(cl->family));
705 button_label = _("_Copy");
706 existing = (char const **) dynamic_options_list(dynopts);
707 if (old_name)
708 {
709 char *new_name = getname_run_dialog(GTK_WINDOW(cl->cg->widget),
710 old_name, existing, title, button_label, NULL, TRUE);
711
712 if (new_name)
713 {
714 configlet_copy(cl, old_name, new_name);
715 g_free(new_name);
716 }
717 g_free(old_name);
718 }
719 else
720 {
721 g_warning(_("No selection to copy"));
722 }
723 g_strfreev((char **) existing);
724 g_free(title);
725 }
726
on_profile_copy_clicked(GtkButton * button,ConfigletData * cg)727 void on_profile_copy_clicked(GtkButton *button, ConfigletData *cg)
728 {
729 (void) button;
730 on_copy_clicked(&cg->profile);
731 }
732
on_colours_copy_clicked(GtkButton * button,ConfigletData * cg)733 void on_colours_copy_clicked(GtkButton *button, ConfigletData *cg)
734 {
735 (void) button;
736 on_copy_clicked(&cg->colours);
737 }
738
on_shortcuts_copy_clicked(GtkButton * button,ConfigletData * cg)739 void on_shortcuts_copy_clicked(GtkButton *button, ConfigletData *cg)
740 {
741 (void) button;
742 on_copy_clicked(&cg->shortcuts);
743 }
744
on_delete_clicked(ConfigletList * cl)745 static void on_delete_clicked(ConfigletList *cl)
746 {
747 char *name;
748
749 if (get_selected_state(cl))
750 {
751 dlg_warning(GTK_WINDOW(cl->cg->widget),
752 _("You may not delete the selected default %s"),
753 full_name_from_family(cl->family));
754 return;
755 }
756 name = get_selected_name(cl);
757 if (!name)
758 {
759 g_warning(_("No selection to delete"));
760 return;
761 }
762 if (!is_in_user_dir(cl->family, name))
763 {
764 dlg_warning(GTK_WINDOW(cl->cg->widget),
765 _("'%s' is a system %s and can not be deleted"),
766 name, full_name_from_family(cl->family));
767 }
768 else
769 {
770 gboolean remove = TRUE;
771 char *filename = options_file_build_filename(cl->family,
772 name, NULL);
773
774 if (g_unlink(filename))
775 {
776 dlg_warning(GTK_WINDOW(cl->cg->widget),
777 _("Unable to delete '%s'"), filename);
778 remove = FALSE;
779 }
780 g_free(filename);
781 if (remove)
782 {
783 if (remove_name_from_list(cl, name))
784 {
785 optsdbus_send_stuff_changed_signal(OPTSDBUS_DELETED,
786 cl->family, name, NULL);
787 }
788 }
789 }
790 g_free(name);
791 }
792
on_profile_delete_clicked(GtkButton * button,ConfigletData * cg)793 void on_profile_delete_clicked(GtkButton *button, ConfigletData *cg)
794 {
795 (void) button;
796 on_delete_clicked(&cg->profile);
797 }
798
on_colours_delete_clicked(GtkButton * button,ConfigletData * cg)799 void on_colours_delete_clicked(GtkButton *button, ConfigletData *cg)
800 {
801 (void) button;
802 on_delete_clicked(&cg->colours);
803 }
804
on_shortcuts_delete_clicked(GtkButton * button,ConfigletData * cg)805 void on_shortcuts_delete_clicked(GtkButton *button, ConfigletData *cg)
806 {
807 (void) button;
808 on_delete_clicked(&cg->shortcuts);
809 }
810
on_shortcuts_edit_clicked(GtkButton * button,ConfigletData * cg)811 void on_shortcuts_edit_clicked(GtkButton *button, ConfigletData *cg)
812 {
813 (void) button;
814 edit_selected_thing(&cg->shortcuts);
815 }
816
on_rename_clicked(ConfigletList * cl)817 void on_rename_clicked(ConfigletList *cl)
818 {
819 char *title = NULL;
820 char const **existing = NULL;
821 char *old_name = get_selected_name(cl);
822 DynamicOptions *dynopts = dynamic_options_get(cl->family);
823
824 title = g_strdup_printf(_("Rename %s"),
825 full_name_from_family(cl->family));
826 existing = (char const **) dynamic_options_list(dynopts);
827 if (old_name)
828 {
829 char *new_name = getname_run_dialog(GTK_WINDOW(cl->cg->widget),
830 old_name, existing, title, _("Apply"), NULL, TRUE);
831
832 if (new_name)
833 {
834 configlet_rename(cl, old_name, new_name);
835 g_free(new_name);
836 }
837 g_free(old_name);
838 }
839 else
840 {
841 g_warning(_("No selection to copy"));
842 }
843 g_strfreev((char **) existing);
844 g_free(title);
845 }
846
on_profile_rename_clicked(GtkButton * button,ConfigletData * cg)847 void on_profile_rename_clicked(GtkButton *button, ConfigletData *cg)
848 {
849 (void) button;
850 on_rename_clicked(&cg->profile);
851 }
852
on_colours_rename_clicked(GtkButton * button,ConfigletData * cg)853 void on_colours_rename_clicked(GtkButton *button, ConfigletData *cg)
854 {
855 (void) button;
856 on_rename_clicked(&cg->colours);
857 }
858
on_shortcuts_rename_clicked(GtkButton * button,ConfigletData * cg)859 void on_shortcuts_rename_clicked(GtkButton *button, ConfigletData *cg)
860 {
861 (void) button;
862 on_rename_clicked(&cg->shortcuts);
863 }
864
865 /********************************************************************/
866
configlet_add_button_to_size_group(ConfigletData * cg,const char * family,const char * button)867 static void configlet_add_button_to_size_group(ConfigletData *cg,
868 const char *family, const char *button)
869 {
870 char *obj_name = g_strdup_printf("%c%s_%s",
871 tolower(family[0]), family + 1, button);
872 gtk_size_group_add_widget(cg->size_group,
873 GTK_WIDGET(gtk_builder_get_object(cg->capp.builder, obj_name)));
874 g_free(obj_name);
875 }
876
configlet_add_family_to_size_group(ConfigletData * cg,const char * family)877 static void configlet_add_family_to_size_group(ConfigletData *cg,
878 const char *family)
879 {
880 const char *f2 = strcmp(family, "Profiles") ? family : "profile";
881 configlet_add_button_to_size_group(cg, f2, "copy");
882 configlet_add_button_to_size_group(cg, f2, "rename");
883 configlet_add_button_to_size_group(cg, f2, "delete");
884 configlet_add_button_to_size_group(cg, f2, "edit");
885 }
886
configlet_setup_family(ConfigletData * cg,ConfigletList * cl,const char * family)887 static void configlet_setup_family(ConfigletData *cg, ConfigletList *cl,
888 const char *family)
889 {
890 char *wbasename = convert_family_name(family);
891 char *widget_name = g_strdup_printf("%s_treeview", wbasename);
892 GtkWidget *widget =
893 GTK_WIDGET(gtk_builder_get_object(cg->capp.builder, widget_name));
894 char *cname;
895
896 g_free(widget_name);
897 g_return_if_fail(widget != NULL);
898 cl->cg = cg;
899 g_object_set_data(G_OBJECT(widget), "ROXTermConfigletList", cl);
900 configlet_list_init(cl, widget, family);
901 shade_actions_for_name(cl, cname = configlet_get_configured_name(cl));
902 g_free(cname);
903 g_free(wbasename);
904 /* If size_group is NULL it means actions_sizegroup was found in the UI
905 * file and we don't need to do anything.
906 */
907 if (cg->size_group)
908 {
909 configlet_add_family_to_size_group(cg, family);
910 }
911 }
912
configlet_open()913 gboolean configlet_open()
914 {
915
916 if (configlet_data)
917 {
918 gtk_window_present(GTK_WINDOW(configlet_data->widget));
919 }
920 else
921 {
922 static char const *build_objs[] = { "Configlet", NULL };
923 ConfigletData *cg = configlet_data = g_new0(ConfigletData, 1);
924 GError *error = NULL;
925
926 cg->capp.builder = gtk_builder_new();
927 if (gtk_builder_add_objects_from_resource(cg->capp.builder,
928 ROXTERM_RESOURCE_UI, (char **) build_objs, &error))
929 {
930 cg->widget =
931 GTK_WIDGET(gtk_builder_get_object(cg->capp.builder,
932 "Configlet"));
933 }
934 else
935 {
936 g_critical(_("Unable to create 'Configlet' from UI definition: %s"),
937 error->message);
938 g_error_free(error);
939 }
940 if (!cg->widget)
941 {
942 configlet_delete(cg);
943 return FALSE;
944 }
945
946 /* GtkBuilder doesn't seem to load size group. Bug? If the size group
947 * does load after all we probably don't need to do anything further,
948 * otherwise we have to create our own and add widgets manually.
949 */
950 GObject *size_group = gtk_builder_get_object(cg->capp.builder,
951 "actions_sizegroup");
952 cg->size_group = size_group ? NULL :
953 gtk_size_group_new(GTK_SIZE_GROUP_BOTH);
954
955 cg->capp.options = options_open("Global", "roxterm options");
956
957 gtk_builder_connect_signals(cg->capp.builder, cg);
958
959 configlet_setup_family(cg, &cg->profile, "Profiles");
960 configlet_setup_family(cg, &cg->colours, "Colours");
961 configlet_setup_family(cg, &cg->shortcuts, "Shortcuts");
962
963 capplet_set_radio(&cg->capp, "warn_close", 3);
964 capplet_set_boolean_toggle(&cg->capp, "only_warn_running", FALSE);
965 if (g_object_class_find_property(
966 G_OBJECT_GET_CLASS(gtk_settings_get_default()),
967 "gtk-application-prefer-dark-theme"))
968 {
969 capplet_set_boolean_toggle(&cg->capp, "prefer_dark_theme", FALSE);
970 }
971 else
972 {
973 gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(cg->capp.builder,
974 "prefer_dark_theme")));
975 }
976
977 capplet_inc_windows();
978 gtk_widget_show(cg->widget);
979 }
980 return TRUE;
981 }
982
set_sensitive_for_list(ConfigletList * cl)983 static void set_sensitive_for_list(ConfigletList *cl)
984 {
985 on_tree_selection_changed(gtk_tree_view_get_selection(cl->tvwidget), cl);
986 }
987
configlet_lock_profiles(void)988 void configlet_lock_profiles(void)
989 {
990 ++profile_lock;
991 if (configlet_data)
992 set_sensitive_for_list(&configlet_data->profile);
993 }
994
configlet_unlock_profiles(void)995 void configlet_unlock_profiles(void)
996 {
997 if (--profile_lock < 0)
998 {
999 g_critical(_("Trying to decrease profile_lock below 0"));
1000 profile_lock = 0;
1001 }
1002 if (configlet_data)
1003 set_sensitive_for_list(&configlet_data->profile);
1004 }
1005
configlet_lock_colour_schemes(void)1006 void configlet_lock_colour_schemes(void)
1007 {
1008 ++colours_lock;
1009 if (configlet_data)
1010 set_sensitive_for_list(&configlet_data->colours);
1011 }
1012
configlet_unlock_colour_schemes(void)1013 void configlet_unlock_colour_schemes(void)
1014 {
1015 if (--colours_lock < 0)
1016 {
1017 g_critical(_("Trying to decrease colours_lock below 0"));
1018 colours_lock = 0;
1019 }
1020 if (configlet_data)
1021 set_sensitive_for_list(&configlet_data->colours);
1022 }
1023
configlet_lock_shortcuts(void)1024 void configlet_lock_shortcuts(void)
1025 {
1026 ++shortcuts_lock;
1027 if (configlet_data)
1028 set_sensitive_for_list(&configlet_data->shortcuts);
1029 }
1030
configlet_unlock_shortcuts(void)1031 void configlet_unlock_shortcuts(void)
1032 {
1033 if (--shortcuts_lock < 0)
1034 {
1035 g_critical(_("Trying to decrease shortcuts_lock below 0"));
1036 shortcuts_lock = 0;
1037 }
1038 if (configlet_data)
1039 set_sensitive_for_list(&configlet_data->shortcuts);
1040 }
1041
1042 /* vi:set sw=4 ts=4 noet cindent cino= */
1043