9f51c79812b6948fc1513f596e94ca3fffbecf46
[metze/wireshark/wip.git] / ui / gtk / profile_dlg.c
1 /* profile_dlg.c
2  * Dialog box for profiles editing
3  * Stig Bjorlykke <stig@bjorlykke.org>, 2008
4  *
5  * Wireshark - Network traffic analyzer
6  * By Gerald Combs <gerald@wireshark.org>
7  * Copyright 1998 Gerald Combs
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  */
23
24 #include "config.h"
25
26 #include <gtk/gtk.h>
27 #include <gdk/gdkkeysyms.h>
28 #if GTK_CHECK_VERSION(3,0,0)
29 # include <gdk/gdkkeysyms-compat.h>
30 #endif
31
32 #include <wsutil/filesystem.h>
33 #include <epan/prefs.h>
34
35 #include "ui/profile.h"
36 #include "ui/recent.h"
37 #include "ui/simple_dialog.h"
38
39 #include "ui/gtk/main.h"
40 #include "ui/gtk/menus.h"
41 #include "ui/gtk/profile_dlg.h"
42 #include "ui/gtk/dlg_utils.h"
43 #include "ui/gtk/gui_utils.h"
44 #include "ui/gtk/gtkglobals.h"
45 #include "ui/gtk/help_dlg.h"
46 #include "ui/gtk/old-gtk-compat.h"
47 #include "ui/gtk/stock_icons.h"
48
49 enum {
50   NAME_COLUMN,
51   GLOBAL_COLUMN,
52   DATA_COLUMN,
53   NUM_COLUMNS
54 };
55
56 #define E_PROF_PROFILE_L_KEY        "profile_profile_l"
57 #define E_PROF_DEL_BT_KEY           "profile_del_bt"
58 #define E_PROF_NAME_TE_KEY          "profile_name_te"
59
60 static GtkWidget *global_profile_w = NULL;
61
62 #define PROF_OPERATION_NEW  1
63 #define PROF_OPERATION_RENAME 2
64
65
66 static GtkTreeIter *
67 fill_list(GtkWidget *main_w)
68 {
69   GList        *fl_entry;
70   profile_def  *profile;
71   GtkTreeView  *profile_l;
72   GtkListStore *store;
73   GtkTreeIter   iter, *l_select = NULL;
74   const gchar  *profile_name    = get_profile_name();
75
76   profile_l = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(main_w), E_PROF_PROFILE_L_KEY));
77   store = GTK_LIST_STORE(gtk_tree_view_get_model(profile_l));
78
79   init_profile_list();
80   fl_entry = edited_profile_list();
81   while (fl_entry && fl_entry->data) {
82     profile = (profile_def *)fl_entry->data;
83     gtk_list_store_append(store, &iter);
84     gtk_list_store_set(store, &iter, NAME_COLUMN, profile->name, GLOBAL_COLUMN, profile->is_global, DATA_COLUMN, fl_entry, -1);
85
86     if (profile->name && (strcmp(profile_name, profile->name) == 0)) {
87       /*
88        * XXX - We're assuming that we can just copy a GtkTreeIter
89        * and use it later without any crashes.  This may not be a
90        * valid assumption.
91        */
92       l_select = (GtkTreeIter *)g_memdup(&iter, sizeof(iter));
93     }
94     fl_entry = g_list_next(fl_entry);
95   }
96
97   return l_select;
98 }
99
100 static void
101 profile_select(GtkWidget *main_w, GtkTreeView *profile_l, gboolean destroy)
102 {
103   GList            *fl_entry;
104   profile_def      *profile;
105   GtkTreeSelection *sel;
106   GtkTreeModel     *model;
107   GtkTreeIter       iter;
108
109   sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(profile_l));
110
111   if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
112     gtk_tree_model_get(model, &iter, DATA_COLUMN, &fl_entry, -1);
113     if (fl_entry) {
114       profile = (profile_def *)fl_entry->data;
115       if (profile_exists(profile->name, FALSE) || profile_exists(profile->name, TRUE)) {
116         /* The new profile exists, change */
117         change_configuration_profile(profile->name);
118       } else if (!profile_exists(get_profile_name(), FALSE)) {
119         /* The new profile does not exist, and the previous profile has
120            been deleted.  Change to the default profile */
121         change_configuration_profile(NULL);
122       }
123     }
124   }
125
126   if (destroy) {
127     /*
128      * Destroy the profile dialog box.
129      */
130     empty_profile_list(TRUE);
131     window_destroy(main_w);
132   }
133 }
134
135 static void
136 profile_apply(GtkWidget *main_w, GtkTreeView *profile_l, gboolean destroy)
137 {
138   const gchar *err_msg;
139
140   if ((err_msg = apply_profile_changes()) != NULL) {
141     simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s", err_msg);
142     g_free((gchar*)err_msg);
143     return;
144   }
145
146   profile_select(main_w, profile_l, destroy);
147 }
148
149 static void
150 profile_dlg_ok_cb(GtkWidget *ok_bt, gpointer data _U_)
151 {
152   GtkWidget   *main_w    = gtk_widget_get_toplevel(ok_bt);
153   GtkTreeView *profile_l = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(main_w), E_PROF_PROFILE_L_KEY));
154
155   /*
156    * Apply the profile and destroy the dialog box.
157    */
158   profile_apply(main_w, profile_l, TRUE);
159 }
160
161 static void
162 profile_dlg_apply_cb(GtkWidget *apply_bt, gpointer data _U_)
163 {
164   GtkWidget   *main_w    = gtk_widget_get_toplevel(apply_bt);
165   GtkTreeView *profile_l = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(main_w), E_PROF_PROFILE_L_KEY));
166
167   /*
168    * Apply the profile, but don't destroy the dialog box.
169    */
170   profile_apply(main_w, profile_l, FALSE);
171 }
172
173 /* cancel button pressed, revert changes and exit dialog */
174 static void
175 profile_dlg_cancel_cb(GtkWidget *cancel_bt, gpointer data _U_)
176 {
177   GtkWidget  *main_w = gtk_widget_get_toplevel(cancel_bt);
178
179   empty_profile_list(TRUE);
180   window_destroy(GTK_WIDGET(main_w));
181 }
182
183 /* Treat this as a cancel, by calling "profile_dlg_cancel_cb()" */
184 static gboolean
185 profile_dlg_delete_event_cb(GtkWidget *main_w, GdkEvent *event _U_,
186                             gpointer data)
187 {
188   profile_dlg_cancel_cb(main_w, data);
189   return FALSE;
190 }
191
192 static void
193 profile_dlg_destroy_cb(GtkWidget *w _U_, gpointer data _U_)
194 {
195   global_profile_w = NULL;
196 }
197
198
199 static gboolean
200 profile_button_press_cb(GtkWidget *list, GdkEventButton *event, gpointer data _U_)
201 {
202   if (event->type == GDK_2BUTTON_PRESS) {
203     GtkWidget *main_w = gtk_widget_get_toplevel(list);
204
205     profile_apply(main_w, GTK_TREE_VIEW(list), TRUE);
206   }
207
208   return FALSE;
209 }
210
211 static gboolean
212 profile_key_release_cb(GtkWidget *list, GdkEventKey *event, gpointer data _U_)
213 {
214   if ((event->keyval == GDK_Return) || (event->keyval == GDK_KP_Enter)) {
215     GtkWidget *main_w = gtk_widget_get_toplevel(list);
216
217     profile_apply(main_w, GTK_TREE_VIEW(list), TRUE);
218   }
219
220   return FALSE;
221 }
222
223 static void
224 profile_sel_list_cb(GtkTreeSelection *sel, gpointer data _U_)
225 {
226   GtkWidget    *profile_l   = GTK_WIDGET(gtk_tree_selection_get_tree_view(sel));
227   GtkWidget    *main_w      = gtk_widget_get_toplevel(profile_l);
228   GtkTreeModel *model;
229   GtkTreeIter   iter;
230   GtkWidget    *name_te     = (GtkWidget *)g_object_get_data(G_OBJECT(main_w), E_PROF_NAME_TE_KEY);
231   GtkWidget    *del_bt      = (GtkWidget *)g_object_get_data(G_OBJECT(main_w), E_PROF_DEL_BT_KEY);
232   profile_def  *profile;
233   gchar        *name        = NULL;
234   GList        *fl_entry;
235   gint          sensitivity = FALSE;
236
237   if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
238     gtk_tree_model_get(model, &iter, DATA_COLUMN, &fl_entry, -1);
239     if (fl_entry) {
240       profile = (profile_def *)fl_entry->data;
241       name = g_strdup(profile->name);
242       if ((profile->status != PROF_STAT_DEFAULT) && !profile->is_global) {
243         sensitivity = TRUE;
244       }
245     }
246   }
247
248   /*
249    * Did you know that this function is called when the window is destroyed?
250    * Funny, that.
251    * This means that we have to:
252    *
253    *    attach to the top-level window data items containing pointers to
254    *    the widgets we affect here;
255    *
256    *    give each of those widgets their own destroy callbacks;
257    *
258    *    clear that pointer when the widget is destroyed;
259    *
260    *    don't do anything to the widget if the pointer we get back is
261    *    null;
262    *
263    * so that if we're called after any of the widgets we'd affect are
264    * destroyed, we know that we shouldn't do anything to those widgets.
265    */
266   if (name_te != NULL) {
267     gtk_entry_set_text(GTK_ENTRY(name_te), name ? name : "");
268     gtk_widget_set_sensitive(name_te, sensitivity);
269   }
270   if (del_bt != NULL)
271     gtk_widget_set_sensitive(del_bt, sensitivity);
272   g_free(name);
273 }
274
275 static void
276 profile_new_bt_clicked_cb(GtkWidget *w, gpointer data _U_)
277 {
278   GtkWidget    *main_w    = gtk_widget_get_toplevel(w);
279   GtkWidget    *name_te   = (GtkWidget *)g_object_get_data(G_OBJECT(main_w), E_PROF_NAME_TE_KEY);
280   GtkTreeView  *profile_l = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(main_w), E_PROF_PROFILE_L_KEY));
281   GtkListStore *store;
282   GtkTreeIter   iter;
283   GList        *fl_entry;
284   const gchar  *name      = "New profile";
285
286   /* Add a new entry to the profile list. */
287   fl_entry = add_to_profile_list(name, "", PROF_STAT_NEW, FALSE, FALSE);
288
289   store = GTK_LIST_STORE(gtk_tree_view_get_model(profile_l));
290   gtk_list_store_append(store, &iter);
291   gtk_list_store_set(store, &iter, NAME_COLUMN, name, GLOBAL_COLUMN, FALSE, DATA_COLUMN, fl_entry, -1);
292   /* Select the item. */
293   gtk_tree_selection_select_iter(gtk_tree_view_get_selection(profile_l), &iter);
294
295   gtk_editable_select_region(GTK_EDITABLE(name_te), 0, -1);
296   gtk_widget_grab_focus(name_te);
297 }
298
299 static void
300 profile_copy_bt_clicked_cb(GtkWidget *w, gpointer data _U_)
301 {
302   GtkWidget    *main_w    = gtk_widget_get_toplevel(w);
303   GtkWidget    *name_te   = (GtkWidget *)g_object_get_data(G_OBJECT(main_w), E_PROF_NAME_TE_KEY);
304   GtkTreeView  *profile_l = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(main_w), E_PROF_PROFILE_L_KEY));
305   GtkListStore *store;
306   GtkTreeIter   iter;
307   GList        *fl_entry;
308   const gchar  *name      = gtk_entry_get_text(GTK_ENTRY(name_te));
309   const gchar  *parent    = NULL;
310   gchar        *new_name;
311
312   GtkTreeSelection *sel;
313   GtkTreeModel     *model;
314   profile_def      *profile = NULL;
315
316   sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(profile_l));
317   if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
318     gtk_tree_model_get(model, &iter, DATA_COLUMN, &fl_entry, -1);
319     if (fl_entry) {
320       profile = (profile_def *)fl_entry->data;
321     }
322   }
323
324   if (profile && profile->is_global) {
325     parent = profile->name;
326   } else {
327     parent = get_profile_parent(name);
328   }
329
330   if (profile && profile->is_global && !profile_exists(parent, FALSE)) {
331     new_name = g_strdup(name);
332   } else {
333     new_name = g_strdup_printf("%s (copy)", name);
334   }
335
336   /* Add a new entry to the profile list. */
337   fl_entry = add_to_profile_list(new_name, parent, PROF_STAT_COPY, FALSE, profile ? profile->from_global : FALSE);
338
339   store = GTK_LIST_STORE(gtk_tree_view_get_model(profile_l));
340   gtk_list_store_append(store, &iter);
341   gtk_list_store_set(store, &iter, NAME_COLUMN, new_name, GLOBAL_COLUMN, FALSE, DATA_COLUMN, fl_entry, -1);
342   /* Select the item. */
343   gtk_tree_selection_select_iter(gtk_tree_view_get_selection(profile_l), &iter);
344
345   gtk_editable_select_region(GTK_EDITABLE(name_te), 0, -1);
346   gtk_widget_grab_focus(name_te);
347
348   g_free(new_name);
349 }
350
351 static void
352 profile_name_te_changed_cb(GtkWidget *w, gpointer data _U_)
353 {
354   GtkWidget   *main_w    = gtk_widget_get_toplevel(w);
355   GtkWidget   *name_te   = (GtkWidget *)g_object_get_data(G_OBJECT(main_w), E_PROF_NAME_TE_KEY);
356   GtkWidget   *profile_l = (GtkWidget *)g_object_get_data(G_OBJECT(main_w), E_PROF_PROFILE_L_KEY);
357   profile_def *profile;
358   GList       *fl_entry;
359   const gchar *name;
360
361   GtkTreeSelection *sel;
362   GtkTreeModel     *model;
363   GtkTreeIter       iter;
364
365   sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(profile_l));
366   name   = gtk_entry_get_text(GTK_ENTRY(name_te));
367
368   /* if something was selected */
369   if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
370     gtk_tree_model_get(model, &iter, DATA_COLUMN, &fl_entry, -1);
371     if (fl_entry != NULL) {
372       profile = (profile_def *)fl_entry->data;
373
374       if ((strlen(name) > 0) && profile && !profile->is_global) {
375         if (profile->status != PROF_STAT_DEFAULT) {
376           g_free(profile->name);
377           profile->name = g_strdup(name);
378           if ((profile->status != PROF_STAT_NEW) &&
379               (profile->status != PROF_STAT_COPY)) {
380             profile->status = PROF_STAT_CHANGED;
381           }
382           gtk_list_store_set(GTK_LIST_STORE(model), &iter, NAME_COLUMN, name, -1);
383         }
384       }
385     }
386   }
387 }
388
389 static void
390 profile_del_bt_clicked_cb(GtkWidget *w, gpointer data _U_)
391 {
392   GtkWidget *main_w    = gtk_widget_get_toplevel(w);
393   GtkWidget *profile_l = (GtkWidget *)g_object_get_data(G_OBJECT(main_w), E_PROF_PROFILE_L_KEY);
394   GList     *fl_entry;
395
396   GtkTreeSelection *sel;
397   GtkTreeModel     *model;
398   GtkTreeIter       iter;
399
400   sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(profile_l));
401   /* If something was selected */
402   if (gtk_tree_selection_get_selected(sel, &model, &iter)) {
403     gtk_tree_model_get(model, &iter, DATA_COLUMN, &fl_entry, -1);
404
405     if (fl_entry != NULL) {
406       remove_from_profile_list(fl_entry);
407       gtk_list_store_remove(GTK_LIST_STORE(model), &iter);
408     }
409   }
410
411   if (gtk_tree_model_get_iter_first(model, &iter)) {
412     gtk_tree_selection_select_iter(sel, &iter);
413   }
414 }
415
416 static GtkWidget *
417 profile_dialog_new(void)
418 {
419   GtkWidget *main_w,      /* main window */
420     *main_vb,             /* main container */
421     *bbox,                /* button container */
422     *ok_bt,               /* "OK" button */
423     *apply_bt,            /* "Apply" button */
424     *cancel_bt,           /* "Cancel" button */
425     *help_bt;             /* "Help" button */
426   GtkWidget *profile_vb,  /* profile settings box */
427     *props_vb;
428   GtkWidget *top_hb,
429     *list_bb,
430     *new_bt,
431     *copy_bt,
432     *del_bt,
433     *profile_sc,
434     *profile_l,
435     *middle_hb,
436     *name_lb,
437     *name_te,
438     *profile_fr,
439     *edit_fr,
440     *props_fr;
441   GtkListStore      *store;
442   GtkCellRenderer   *renderer;
443   GtkTreeViewColumn *column;
444   GtkTreeSelection  *sel;
445   GtkTreeIter       *l_select;
446   gboolean           has_global = has_global_profiles();
447
448   /* Get a pointer to a static variable holding the type of profile on
449      which we're working, so we can pass that pointer to callback
450      routines. */
451
452   main_w = dlg_conf_window_new("Wireshark: Configuration Profiles");
453   gtk_window_set_default_size(GTK_WINDOW(main_w), 400, 400);
454
455   main_vb = ws_gtk_box_new(GTK_ORIENTATION_VERTICAL, 0, FALSE);
456   gtk_container_set_border_width(GTK_CONTAINER(main_vb), 5);
457   gtk_container_add(GTK_CONTAINER(main_w), main_vb);
458   gtk_widget_show(main_vb);
459
460   /* Container for each row of widgets */
461   profile_vb = ws_gtk_box_new(GTK_ORIENTATION_VERTICAL, 0, FALSE);
462   gtk_container_set_border_width(GTK_CONTAINER(profile_vb), 0);
463   gtk_box_pack_start(GTK_BOX(main_vb), profile_vb, TRUE, TRUE, 0);
464   gtk_widget_show(profile_vb);
465
466   /* Top row: Buttons and profile list */
467   top_hb = ws_gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0, FALSE);
468   gtk_box_pack_start(GTK_BOX(profile_vb), top_hb, TRUE, TRUE, 0);
469   gtk_widget_show(top_hb);
470
471   edit_fr = gtk_frame_new("Edit");
472   gtk_box_pack_start(GTK_BOX(top_hb), edit_fr, FALSE, FALSE, 0);
473   gtk_widget_show(edit_fr);
474
475   list_bb = ws_gtk_box_new(GTK_ORIENTATION_VERTICAL, 0, TRUE);
476   gtk_container_set_border_width(GTK_CONTAINER(list_bb), 5);
477   gtk_container_add(GTK_CONTAINER(edit_fr), list_bb);
478   gtk_widget_show(list_bb);
479
480   new_bt = ws_gtk_button_new_from_stock(GTK_STOCK_NEW);
481   g_signal_connect(new_bt, "clicked", G_CALLBACK(profile_new_bt_clicked_cb), NULL);
482   gtk_widget_show(new_bt);
483   gtk_box_pack_start(GTK_BOX(list_bb), new_bt, FALSE, FALSE, 0);
484   gtk_widget_set_tooltip_text(new_bt, "Create a new profile (with default properties)");
485
486   copy_bt = ws_gtk_button_new_from_stock(GTK_STOCK_COPY);
487   g_signal_connect(copy_bt, "clicked", G_CALLBACK(profile_copy_bt_clicked_cb), NULL);
488   gtk_widget_show(copy_bt);
489   gtk_box_pack_start(GTK_BOX(list_bb), copy_bt, FALSE, FALSE, 0);
490   gtk_widget_set_tooltip_text(copy_bt, "Copy the selected profile");
491
492   del_bt = ws_gtk_button_new_from_stock(GTK_STOCK_DELETE);
493   gtk_widget_set_sensitive(del_bt, FALSE);
494   g_signal_connect(del_bt, "clicked", G_CALLBACK(profile_del_bt_clicked_cb), NULL);
495   g_object_set_data(G_OBJECT(main_w), E_PROF_DEL_BT_KEY, del_bt);
496   gtk_widget_show(del_bt);
497   gtk_box_pack_start(GTK_BOX(list_bb), del_bt, FALSE, FALSE, 0);
498   gtk_widget_set_tooltip_text(del_bt, "Delete the selected profile");
499
500   profile_fr = gtk_frame_new("Configuration Profiles");
501   gtk_box_pack_start(GTK_BOX(top_hb), profile_fr, TRUE, TRUE, 0);
502   gtk_widget_show(profile_fr);
503
504   profile_sc = scrolled_window_new(NULL, NULL);
505   gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(profile_sc),
506                                       GTK_SHADOW_IN);
507
508   gtk_container_set_border_width(GTK_CONTAINER(profile_sc), 5);
509   gtk_container_add(GTK_CONTAINER(profile_fr), profile_sc);
510   gtk_widget_show(profile_sc);
511
512   store = gtk_list_store_new(NUM_COLUMNS, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_POINTER);
513   profile_l = tree_view_new(GTK_TREE_MODEL(store));
514   /* Only show headers if having more than one column */
515   gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(profile_l), has_global);
516
517   renderer = gtk_cell_renderer_text_new();
518   column = gtk_tree_view_column_new_with_attributes("Name", renderer, "text", NAME_COLUMN, NULL);
519   gtk_tree_view_column_set_expand(column, TRUE);
520   gtk_tree_view_column_set_sort_column_id(column, NAME_COLUMN);
521   gtk_tree_view_append_column(GTK_TREE_VIEW(profile_l), column);
522
523   renderer = gtk_cell_renderer_toggle_new();
524   column = gtk_tree_view_column_new_with_attributes("Global", renderer, "active", GLOBAL_COLUMN, NULL);
525   gtk_tree_view_append_column(GTK_TREE_VIEW(profile_l), column);
526   gtk_widget_set_tooltip_text(gtk_tree_view_column_get_button(column),
527                               "Global profiles will be copied to users profiles when used");
528   gtk_tree_view_column_set_visible(column, has_global);
529
530   sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(profile_l));
531   gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE);
532   g_signal_connect(sel, "changed", G_CALLBACK(profile_sel_list_cb), profile_vb);
533   g_signal_connect(profile_l, "button_press_event", G_CALLBACK(profile_button_press_cb), NULL);
534   g_signal_connect(profile_l, "key_release_event", G_CALLBACK(profile_key_release_cb), NULL);
535   g_object_set_data(G_OBJECT(main_w), E_PROF_PROFILE_L_KEY, profile_l);
536   gtk_container_add(GTK_CONTAINER(profile_sc), profile_l);
537   gtk_widget_show(profile_l);
538
539   /* fill in data */
540   l_select = fill_list(main_w);
541
542   g_object_unref(G_OBJECT(store));
543
544   props_fr = gtk_frame_new("Properties");
545   gtk_box_pack_start(GTK_BOX(profile_vb), props_fr, FALSE, FALSE, 0);
546   gtk_widget_show(props_fr);
547
548   props_vb = ws_gtk_box_new(GTK_ORIENTATION_VERTICAL, 3, FALSE);
549   gtk_container_set_border_width(GTK_CONTAINER(props_vb), 5);
550   gtk_container_add(GTK_CONTAINER(props_fr), props_vb);
551   gtk_widget_show(props_vb);
552
553   /* row: Profile name entry */
554   middle_hb = ws_gtk_box_new(GTK_ORIENTATION_VERTICAL, 3, FALSE);
555   gtk_box_pack_start(GTK_BOX(props_vb), middle_hb, TRUE, TRUE, 0);
556   gtk_widget_show(middle_hb);
557
558   name_lb = gtk_label_new("Profile name:");
559   gtk_box_pack_start(GTK_BOX(middle_hb), name_lb, FALSE, FALSE, 0);
560   gtk_widget_show(name_lb);
561
562   name_te = gtk_entry_new();
563   gtk_box_pack_start(GTK_BOX(middle_hb), name_te, TRUE, TRUE, 0);
564   g_object_set_data(G_OBJECT(main_w), E_PROF_NAME_TE_KEY, name_te);
565   g_signal_connect(name_te, "changed", G_CALLBACK(profile_name_te_changed_cb), NULL);
566 #ifdef _WIN32
567   gtk_widget_set_tooltip_text(name_te,
568                               "A profile name cannot start or end with a period (.), and cannot"
569                               " contain any of the following characters:\n   \\ / : * ? \" < > |");
570 #else
571   gtk_widget_set_tooltip_text(name_te, "A profile name cannot contain the '/' character");
572 #endif
573   gtk_widget_show(name_te);
574
575   /* button row(create all possible buttons and hide the unrequired later - it's a lot easier) */
576   bbox = dlg_button_row_new(GTK_STOCK_OK, GTK_STOCK_APPLY, GTK_STOCK_CANCEL, GTK_STOCK_HELP, NULL);
577   gtk_box_pack_start(GTK_BOX(main_vb), bbox, FALSE, FALSE, 5);
578   gtk_widget_show(bbox);
579
580   ok_bt = (GtkWidget *)g_object_get_data(G_OBJECT(bbox), GTK_STOCK_OK);
581   g_signal_connect(ok_bt, "clicked", G_CALLBACK(profile_dlg_ok_cb), NULL);
582   gtk_widget_set_tooltip_text(ok_bt, "Apply the profiles and close this dialog");
583
584   /* Catch the "activate" signal on the profile name and profile
585      list entries, so that if the user types Return
586      there, we act as if the "OK" button had been selected, as
587      happens if Return is typed if some widget that *doesn't*
588      handle the Return key has the input focus. */
589   dlg_set_activate(name_te, ok_bt);
590
591   apply_bt = (GtkWidget *)g_object_get_data(G_OBJECT(bbox), GTK_STOCK_APPLY);
592   g_signal_connect(apply_bt, "clicked", G_CALLBACK(profile_dlg_apply_cb), NULL);
593   gtk_widget_set_tooltip_text(apply_bt, "Apply the profiles and keep this dialog open");
594
595   cancel_bt = (GtkWidget *)g_object_get_data(G_OBJECT(bbox), GTK_STOCK_CANCEL);
596   gtk_widget_set_tooltip_text(cancel_bt, "Cancel the changes");
597   g_signal_connect(cancel_bt, "clicked", G_CALLBACK(profile_dlg_cancel_cb), NULL);
598   window_set_cancel_button(main_w, cancel_bt, NULL);
599
600   help_bt = (GtkWidget *)g_object_get_data(G_OBJECT(bbox), GTK_STOCK_HELP);
601   g_signal_connect(help_bt, "clicked", G_CALLBACK(topic_cb), (gpointer)HELP_CONFIG_PROFILES_DIALOG);
602   gtk_widget_set_tooltip_text(help_bt, "Show topic specific help");
603
604   if (ok_bt) {
605     gtk_widget_grab_default(ok_bt);
606   }
607
608
609   /* DO SELECTION THINGS *AFTER* SHOWING THE DIALOG! */
610   /* otherwise the updatings can get confused */
611   if (l_select) {
612     gtk_tree_selection_select_iter(sel, l_select);
613     g_free(l_select);
614   }
615
616   if (profile_l) {
617     gtk_widget_grab_focus(profile_l);
618   }
619
620   g_signal_connect(main_w, "delete_event", G_CALLBACK(profile_dlg_delete_event_cb), NULL);
621   g_signal_connect(main_w, "destroy", G_CALLBACK(profile_dlg_destroy_cb), NULL);
622
623   gtk_widget_show(main_w);
624
625   window_present(main_w);
626
627   return main_w;
628 }
629
630
631 static void
632 select_profile_cb(GtkWidget *w _U_, gpointer data)
633 {
634   const gchar *current_profile  = get_profile_name();
635   gchar       *selected_profile = (gchar *)data;
636
637   if (strcmp(selected_profile, current_profile) != 0) {
638     change_configuration_profile(selected_profile);
639   }
640 }
641
642 gboolean
643 profile_show_popup_cb(GtkWidget *w _U_, GdkEvent *event, gpointer user_data _U_)
644 {
645   GdkEventButton *bevent       = (GdkEventButton *)event;
646   const gchar    *profile_name = get_profile_name();
647   gboolean        separator_added = FALSE;
648   GList          *fl_entry;
649   profile_def    *profile;
650   GtkWidget      *menu;
651   GtkWidget      *menu_item;
652
653   menu = gtk_menu_new();
654
655   if (bevent->button != 1) {
656     GtkWidget *change_menu = menus_get_profiles_change_menu();
657
658 #if GTK_CHECK_VERSION(2,16,0)
659     GtkWidget *rename_menu = menus_get_profiles_rename_menu();
660     GtkWidget *delete_menu = menus_get_profiles_delete_menu();
661     if (strcmp(profile_name, DEFAULT_PROFILE) != 0) {
662       gchar *label;
663       label = g_strdup_printf("Rename \"%s\"...", profile_name);
664       gtk_menu_item_set_label(GTK_MENU_ITEM(rename_menu), label);
665       g_free(label);
666       label = g_strdup_printf("Delete \"%s\"", profile_name);
667       gtk_menu_item_set_label(GTK_MENU_ITEM(delete_menu), label);
668       g_free(label);
669     } else {
670       gtk_menu_item_set_label(GTK_MENU_ITEM(rename_menu), "Rename...");
671       gtk_menu_item_set_label(GTK_MENU_ITEM(delete_menu), "Delete");
672     }
673 #endif
674     gtk_menu_item_set_submenu(GTK_MENU_ITEM(change_menu), menu);
675   }
676
677   init_profile_list();
678   fl_entry = current_profile_list();
679   while (fl_entry && fl_entry->data) {
680     profile = (profile_def *)fl_entry->data;
681
682     if (!profile->is_global || !profile_exists(profile->name, FALSE)) {
683       if (profile->is_global && !separator_added) {
684         menu_item =  gtk_separator_menu_item_new();
685         gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
686         gtk_widget_show(menu_item);
687         separator_added = TRUE;
688       }
689       menu_item = gtk_check_menu_item_new_with_label(profile->name);
690       if (strcmp(profile->name, profile_name)==0) {
691         /* Check current profile */
692         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_item), TRUE);
693       }
694       g_object_set(G_OBJECT(menu_item), "draw-as-radio", TRUE, NULL);
695       g_signal_connect(menu_item, "activate", G_CALLBACK(select_profile_cb), g_strdup(profile->name));
696       gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
697       gtk_widget_show(menu_item);
698     }
699     fl_entry = g_list_next(fl_entry);
700   }
701
702   if (bevent->button != 1) {
703     /* Second-click is handled in popup_menu_handler() */
704     return FALSE;
705   }
706
707   gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
708                   bevent->button, bevent->time);
709
710   return TRUE;
711 }
712
713 static void
714 profile_name_edit_ok(GtkWidget *w _U_, gpointer parent_w)
715 {
716   gint          operation    = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w), "operation"));
717   GtkComboBox  *combo_box    = (GtkComboBox *)g_object_get_data(G_OBJECT(w), "create_from");
718   GtkWidget    *entry        = (GtkWidget *)g_object_get_data(G_OBJECT(w), "entry");
719   GtkTreeStore *store;
720   GtkTreeIter   iter;
721   const gchar  *new_name     = gtk_entry_get_text(GTK_ENTRY(entry));
722   const gchar  *profile_name = "";
723   gboolean      from_global  = FALSE;
724   char         *pf_dir_path, *pf_dir_path2, *pf_filename, *valid_name;
725
726   if (strlen(new_name) == 0) {
727     return;
728   }
729   valid_name = profile_name_is_valid(new_name);
730   if (valid_name != NULL) {
731     g_free(valid_name);
732     return;
733   }
734
735   switch (operation) {
736   case PROF_OPERATION_NEW:
737     if (gtk_combo_box_get_active_iter(combo_box, &iter)) {
738       store = GTK_TREE_STORE(gtk_combo_box_get_model(combo_box));
739       gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 0, &profile_name, 1, &from_global, -1);
740     }
741     break;
742   case PROF_OPERATION_RENAME:
743     profile_name = get_profile_name();
744     if (strcmp(new_name, profile_name) == 0) {
745       /* Rename without a change, do nothing */
746       window_destroy(GTK_WIDGET(parent_w));
747       return;
748     }
749     break;
750   default:
751     g_assert_not_reached();
752   }
753
754   if (profile_exists(new_name, FALSE)) {
755     simple_dialog(ESD_TYPE_WARN, ESD_BTN_OK,
756                   "The profile already exists:\n%s.", new_name);
757     return;
758   }
759
760   /* Write recent file for profile we are leaving */
761   write_profile_recent();
762
763   switch (operation) {
764   case PROF_OPERATION_NEW:
765     if (create_persconffile_profile(new_name, &pf_dir_path) == -1) {
766       simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
767                     "Can't create directory\n\"%s\":\n%s.",
768                     pf_dir_path, g_strerror(errno));
769
770       g_free(pf_dir_path);
771     } else if (strlen(profile_name) &&
772                (copy_persconffile_profile(new_name, profile_name, from_global, &pf_filename,
773                                           &pf_dir_path, &pf_dir_path2) != 0))
774     {
775       simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
776                     "Can't copy file \"%s\" in directory\n\"%s\" to\n\"%s\":\n%s.",
777                     pf_filename, pf_dir_path2, pf_dir_path, g_strerror(errno));
778
779       g_free(pf_filename);
780       g_free(pf_dir_path);
781       g_free(pf_dir_path2);
782     } else {
783       change_configuration_profile(new_name);
784     }
785     break;
786   case PROF_OPERATION_RENAME:
787     if (rename_persconffile_profile(profile_name, new_name,
788                                     &pf_dir_path, &pf_dir_path2) == -1) {
789       simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
790                     "Can't rename directory\n\"%s\" to\n\"%s\":\n%s.",
791                     pf_dir_path, pf_dir_path2, g_strerror(errno));
792
793       g_free(pf_dir_path);
794       g_free(pf_dir_path2);
795     } else {
796       change_configuration_profile(new_name);
797     }
798     break;
799   default:
800     g_assert_not_reached();
801   }
802
803   window_destroy(GTK_WIDGET(parent_w));
804 }
805
806 static void
807 profile_rename_cancel(GtkWidget *w _U_, gpointer parent_w)
808 {
809   window_destroy(GTK_WIDGET(parent_w));
810 }
811
812 static void
813 profile_manage_profiles_dlg(gint operation)
814 {
815   GtkWidget       *win, *main_grid, *main_vb, *bbox, *cancel_bt, *ok_bt;
816   GtkWidget       *entry, *label;
817   GtkWidget       *combo_box    = NULL;
818   GtkCellRenderer *cell;
819   GtkTreeStore    *store;
820   GtkTreeIter      iter, parent;
821   gchar           *window_title = NULL;
822   GList           *fl_entry;
823   profile_def     *profile;
824   const gchar     *profile_name;
825   gboolean         has_global   = has_global_profiles();
826
827   profile_name = get_profile_name();
828
829   switch (operation) {
830   case PROF_OPERATION_NEW:
831     window_title = g_strdup("Create New Profile");
832     break;
833   case PROF_OPERATION_RENAME:
834     window_title = g_strdup_printf("Rename: %s", profile_name);
835     break;
836   default:
837     g_assert_not_reached();
838   }
839
840   win = dlg_window_new(window_title);
841   g_free(window_title);
842
843   gtk_window_set_resizable(GTK_WINDOW(win), FALSE);
844   gtk_window_resize(GTK_WINDOW(win), 400, 100);
845
846   main_vb = ws_gtk_box_new(GTK_ORIENTATION_VERTICAL, 5, FALSE);
847   gtk_container_add(GTK_CONTAINER(win), main_vb);
848   gtk_container_set_border_width(GTK_CONTAINER(main_vb), 6);
849
850   main_grid = ws_gtk_grid_new();
851   gtk_box_pack_start(GTK_BOX(main_vb), main_grid, FALSE, FALSE, 0);
852   ws_gtk_grid_set_column_spacing(GTK_GRID(main_grid), 10);
853   ws_gtk_grid_set_row_spacing(GTK_GRID(main_grid), 5);
854
855   if (operation == PROF_OPERATION_NEW) {
856     label = gtk_label_new("Create from:");
857     gtk_widget_set_tooltip_text(label, "All configuration files will be copied from this profile");
858     ws_gtk_grid_attach_defaults(GTK_GRID(main_grid), label, 0, 0, 1, 1);
859     gtk_misc_set_alignment(GTK_MISC(label), 1.0f, 0.5f);
860
861     store = gtk_tree_store_new(3, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
862     combo_box = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
863     gtk_widget_set_tooltip_text(combo_box, "All configuration files will be copied from this profile");
864
865     cell = gtk_cell_renderer_text_new();
866     gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo_box), cell, TRUE);
867     gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(combo_box), cell,
868                                    "text", 0, "sensitive", 2,
869                                    NULL);
870
871     gtk_tree_store_append(store, &iter, NULL);
872     gtk_tree_store_set(store, &iter, 0, "", 1, FALSE, 2, TRUE, -1);
873
874     if (has_global) {
875       gtk_tree_store_append(store, &parent, NULL);
876       gtk_tree_store_set(store, &parent, 0, "Personal", 1, FALSE, 2, FALSE, -1);
877     }
878
879     init_profile_list();
880     fl_entry = current_profile_list();
881
882     while (fl_entry && fl_entry->data) {
883       profile = (profile_def *)fl_entry->data;
884       if (!profile->is_global) {
885         gtk_tree_store_append(store, &iter, has_global ? &parent : NULL);
886         gtk_tree_store_set(store, &iter, 0, profile->name, 1, FALSE, 2, TRUE, -1);
887       }
888       fl_entry = g_list_next(fl_entry);
889     }
890
891     if (has_global) {
892       gtk_tree_store_append(store, &parent, NULL);
893       gtk_tree_store_set(store, &parent, 0, "Global", 1, FALSE, 2, FALSE, -1);
894       fl_entry = current_profile_list();
895
896       while (fl_entry && fl_entry->data) {
897         profile = (profile_def *)fl_entry->data;
898         if (profile->is_global) {
899           gtk_tree_store_append(store, &iter, &parent);
900           gtk_tree_store_set(store, &iter, 0, profile->name, 1, TRUE, 2, TRUE, -1);
901         }
902         fl_entry = g_list_next(fl_entry);
903       }
904     }
905     ws_gtk_grid_attach_defaults(GTK_GRID(main_grid), combo_box, 1, 0, 1, 1);
906     g_object_unref(store);
907   }
908
909   label = gtk_label_new("Profile name:");
910   ws_gtk_grid_attach_defaults(GTK_GRID(main_grid), label, 0, 1, 1, 1);
911   gtk_misc_set_alignment(GTK_MISC(label), 1.0f, 0.5f);
912
913   entry = gtk_entry_new();
914   ws_gtk_grid_attach_defaults(GTK_GRID(main_grid), entry, 1, 1, 1, 1);
915   switch (operation) {
916   case PROF_OPERATION_NEW:
917     gtk_entry_set_text(GTK_ENTRY(entry), "New profile");
918     break;
919   case PROF_OPERATION_RENAME:
920     gtk_entry_set_text(GTK_ENTRY(entry), profile_name);
921     break;
922   default:
923     g_assert_not_reached();
924     break;
925   }
926 #ifdef _WIN32
927   gtk_widget_set_tooltip_text(entry,
928                               "A profile name cannot start or end with a period (.), and cannot"
929                               " contain any of the following characters:\n   \\ / : * ? \" < > |");
930 #else
931   gtk_widget_set_tooltip_text(entry, "A profile name cannot contain the '/' character");
932 #endif
933
934   bbox = dlg_button_row_new(GTK_STOCK_CANCEL, GTK_STOCK_OK, NULL);
935   gtk_box_pack_end(GTK_BOX(main_vb), bbox, FALSE, FALSE, 0);
936
937   ok_bt = (GtkWidget *)g_object_get_data(G_OBJECT(bbox), GTK_STOCK_OK);
938   g_object_set_data(G_OBJECT(ok_bt), "entry", entry);
939   g_object_set_data(G_OBJECT(ok_bt), "create_from", combo_box);
940   g_object_set_data(G_OBJECT(ok_bt), "operation", GINT_TO_POINTER(operation));
941   g_signal_connect(ok_bt, "clicked", G_CALLBACK(profile_name_edit_ok), win);
942
943   dlg_set_activate(entry, ok_bt);
944   gtk_widget_grab_focus(entry);
945
946   cancel_bt = (GtkWidget *)g_object_get_data(G_OBJECT(bbox), GTK_STOCK_CANCEL);
947   g_signal_connect(cancel_bt, "clicked", G_CALLBACK(profile_rename_cancel), win);
948   window_set_cancel_button(win, cancel_bt, NULL);
949
950   gtk_widget_grab_default(ok_bt);
951   gtk_widget_show_all(win);
952 }
953
954 void
955 profile_new_cb(GtkWidget *w _U_, gpointer data _U_)
956 {
957   profile_manage_profiles_dlg(PROF_OPERATION_NEW);
958 }
959
960 void
961 profile_delete_cb(GtkWidget *w _U_, gpointer data _U_)
962 {
963   if (delete_current_profile()) {
964     /* Change to the default profile (we have to do this ourselves). */
965     change_configuration_profile(NULL);
966   }
967 }
968
969 void
970 profile_rename_cb(GtkWidget *w _U_, gpointer data _U_)
971 {
972   profile_manage_profiles_dlg(PROF_OPERATION_RENAME);
973 }
974
975 /* Create a profile dialog for editing display profiles; this is to be used
976    as a callback for menu items, toolbars, etc.. */
977 void
978 profile_dialog_cb(GtkWidget *w _U_)
979 {
980   /* Has a profiles dialog box already been opened */
981   if (global_profile_w != NULL) {
982     /* Yes.  Just reactivate it. */
983     reactivate_window(global_profile_w);
984   } else {
985     global_profile_w = profile_dialog_new();
986   }
987 }
988
989 /*
990  * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
991  *
992  * Local Variables:
993  * c-basic-offset: 2
994  * tab-width: 8
995  * indent-tabs-mode: nil
996  * End:
997  *
998  * vi: set shiftwidth=2 tabstop=8 expandtab:
999  * :indentSize=2:tabSize=8:noTabs=true:
1000  */