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