fix CID 1596595: Resource leaks, and CID 1596594: (CHECKED_RETURN)
[claws.git] / src / prefs_actions.c
1 /*
2  * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2022 the Claws Mail Team and Hiroyuki Yamamoto
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 3 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, see <http://www.gnu.org/licenses/>.
17  */
18
19 #ifdef HAVE_CONFIG_H
20 #  include "config.h"
21 #include "claws-features.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <gtk/gtk.h>
29 #include <gdk/gdkkeysyms.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <errno.h>
34
35 #include "prefs_gtk.h"
36 #include "inc.h"
37 #include "utils.h"
38 #include "gtkutils.h"
39 #include "manage_window.h"
40 #include "mainwindow.h"
41 #include "prefs_common.h"
42 #include "alertpanel.h"
43 #include "prefs_actions.h"
44 #include "action.h"
45 #include "description_window.h"
46 #include "manual.h"
47 #include "menu.h"
48 #include "filtering.h"
49 #include "prefs_filtering_action.h"
50 #include "matcher_parser.h"
51 #include "prefs_toolbar.h"
52 #include "file-utils.h"
53
54 enum {
55         PREFS_ACTIONS_STRING,   /*!< string pointer managed by list store, 
56                                  *   and never touched or retrieved by 
57                                  *   us */ 
58         PREFS_ACTIONS_DATA,     /*!< pointer to string that is not managed by 
59                                  *   the list store, and which is retrieved
60                                  *   and touched by us */
61         PREFS_ACTIONS_VALID,    /*!< contains a valid action, otherwise "(New)" */
62         N_PREFS_ACTIONS_COLUMNS
63 };
64
65 static struct Actions
66 {
67         GtkWidget *window;
68
69         GtkWidget *ok_btn;
70         GtkWidget *filter_btn;
71         GtkWidget *name_entry;
72         GtkWidget *cmd_entry;
73         GtkWidget *info_btn;
74         GtkWidget *shell_radiobtn;
75         GtkWidget *filter_radiobtn;
76         
77         GtkWidget *actions_list_view;
78 } actions;
79
80 static int modified = FALSE;
81 static int modified_list = FALSE;
82
83 /* widget creating functions */
84 static void prefs_actions_create        (MainWindow *mainwin);
85 static void prefs_actions_set_dialog    (void);
86 static gint prefs_actions_clist_set_row (gint row);
87
88 /* callback functions */
89 static void prefs_actions_info_cb       (GtkWidget      *w,
90                                          GtkWidget      *window);
91 static void prefs_actions_register_cb   (GtkWidget      *w,
92                                          gpointer        data);
93 static void prefs_actions_substitute_cb (GtkWidget      *w,
94                                          gpointer        data);
95 static void prefs_actions_delete_cb     (gpointer gtk_action, gpointer data);
96 static void prefs_actions_delete_all_cb (gpointer gtk_action, gpointer data);
97 static void prefs_actions_clear_cb      (gpointer gtk_action, gpointer data);
98 static void prefs_actions_duplicate_cb  (gpointer gtk_action, gpointer data);
99 static void prefs_actions_top_cb                (GtkWidget *w, gpointer data);
100 static void prefs_actions_up_cb         (GtkWidget *w, gpointer data);
101 static void prefs_actions_down_cb       (GtkWidget *w, gpointer data);
102 static void prefs_actions_bottom_cb     (GtkWidget *w, gpointer data);
103 static gint prefs_actions_deleted       (GtkWidget      *widget,
104                                          GdkEventAny    *event,
105                                          gpointer       *data);
106 static gboolean prefs_actions_key_pressed(GtkWidget     *widget,
107                                           GdkEventKey   *event,
108                                           gpointer       data);
109 static gboolean prefs_actions_search_func_cb (GtkTreeModel *model, gint column, 
110                                                 const gchar *key, GtkTreeIter *iter, 
111                                                 gpointer search_data);
112 static void prefs_actions_cancel        (GtkWidget      *w,
113                                          gpointer        data);
114 static void prefs_actions_ok            (GtkWidget      *w,
115                                          gpointer        data);
116
117 static GtkListStore* prefs_actions_create_data_store    (void);
118
119 static void prefs_actions_list_view_insert_action       (GtkWidget *list_view,
120                                                          gint row,
121                                                          gchar *action,
122                                                          gboolean is_valid);
123 static GtkWidget *prefs_actions_list_view_create        (void);
124 static void prefs_actions_create_list_view_columns      (GtkWidget *list_view);
125 static void prefs_actions_select_row(GtkTreeView *list_view, GtkTreePath *path);
126
127 static void prefs_action_filter_radiobtn_cb(GtkWidget *widget, gpointer data);
128 static void prefs_action_shell_radiobtn_cb(GtkWidget *widget, gpointer data);
129 static void prefs_action_filterbtn_cb(GtkWidget *widget, gpointer data);
130 static void prefs_action_define_filter_done(GSList * action_list);
131
132
133 void prefs_actions_open(MainWindow *mainwin)
134 {
135         inc_lock();
136
137         if (!actions.window)
138                 prefs_actions_create(mainwin);
139
140         manage_window_set_transient(GTK_WINDOW(actions.window));
141         gtk_widget_grab_focus(actions.ok_btn);
142
143         prefs_actions_set_dialog();
144
145         gtk_widget_show(actions.window);
146         gtk_window_set_modal(GTK_WINDOW(actions.window), TRUE);
147 }
148
149 /*!
150  *\brief        Save Gtk object size to prefs dataset
151  */
152 static void prefs_actions_size_allocate_cb(GtkWidget *widget,
153                                          GtkAllocation *allocation)
154 {
155         cm_return_if_fail(allocation != NULL);
156
157         gtk_window_get_size(GTK_WINDOW(widget),
158                 &prefs_common.actionswin_width, &prefs_common.actionswin_height);
159 }
160
161 static void prefs_actions_create(MainWindow *mainwin)
162 {
163         GtkWidget *window;
164         GtkWidget *vbox;
165         GtkWidget *filter_hbox;
166         GtkWidget *help_btn;
167         GtkWidget *ok_btn;
168         GtkWidget *cancel_btn;
169         GtkWidget *confirm_area;
170
171         GtkWidget *vbox1;
172         GtkWidget *table;
173
174         GtkWidget *shell_radiobtn;
175         GtkWidget *filter_radiobtn;
176
177         GtkWidget *name_label;
178         GtkWidget *name_entry;
179         GtkWidget *cmd_label;
180         GtkWidget *cmd_entry;
181         GtkWidget *filter_btn;
182
183         GtkWidget *reg_hbox;
184         GtkWidget *btn_hbox;
185         GtkWidget *arrow;
186         GtkWidget *reg_btn;
187         GtkWidget *subst_btn;
188         GtkWidget *del_btn;
189         GtkWidget *clear_btn;
190
191         GtkWidget *cond_hbox;
192         GtkWidget *cond_scrolledwin;
193         GtkWidget *cond_list_view;
194
195         GtkWidget *info_btn;
196
197         GtkWidget *btn_vbox;
198         GtkWidget *spc_vbox;
199         GtkWidget *top_btn;
200         GtkWidget *up_btn;
201         GtkWidget *down_btn;
202         GtkWidget *bottom_btn;
203         static GdkGeometry geometry;
204
205         debug_print("Creating actions configuration window...\n");
206
207         window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "prefs_actions");
208
209         gtk_container_set_border_width(GTK_CONTAINER (window), 8);
210         gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
211         gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
212         gtk_window_set_type_hint(GTK_WINDOW(window), GDK_WINDOW_TYPE_HINT_DIALOG);
213
214         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
215         gtk_widget_show(vbox);
216         gtk_container_add(GTK_CONTAINER(window), vbox);
217
218         gtkut_stock_button_set_create_with_help(&confirm_area, &help_btn,
219                         &cancel_btn, NULL, _("_Cancel"),
220                         &ok_btn, NULL, _("_OK"),
221                         NULL, NULL, NULL);
222         gtk_widget_show(confirm_area);
223         gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
224         gtk_widget_grab_default(ok_btn);
225
226         gtk_window_set_title(GTK_WINDOW(window), _("Actions configuration"));
227         g_signal_connect(G_OBJECT(window), "delete_event",
228                          G_CALLBACK(prefs_actions_deleted), NULL);
229         g_signal_connect(G_OBJECT(window), "size_allocate",
230                          G_CALLBACK(prefs_actions_size_allocate_cb), NULL);
231         g_signal_connect(G_OBJECT(window), "key_press_event",
232                          G_CALLBACK(prefs_actions_key_pressed), NULL);
233         MANAGE_WINDOW_SIGNALS_CONNECT(window);
234         g_signal_connect(G_OBJECT(ok_btn), "clicked",
235                          G_CALLBACK(prefs_actions_ok), mainwin);
236         g_signal_connect(G_OBJECT(cancel_btn), "clicked",
237                          G_CALLBACK(prefs_actions_cancel), NULL);
238         g_signal_connect(G_OBJECT(help_btn), "clicked",
239                          G_CALLBACK(manual_open_with_anchor_cb),
240                          MANUAL_ANCHOR_ACTIONS);
241
242         vbox1 = gtk_box_new(GTK_ORIENTATION_VERTICAL, VSPACING);
243         gtk_widget_show(vbox1);
244         gtk_box_pack_start(GTK_BOX(vbox), vbox1, TRUE, TRUE, 0);
245         gtk_container_set_border_width(GTK_CONTAINER(vbox1), 2);        
246
247         table = gtk_grid_new();
248         gtk_widget_show(table);
249         gtk_grid_set_row_spacing(GTK_GRID(table), VSPACING_NARROW_2);
250         gtk_grid_set_column_spacing(GTK_GRID(table), 4);
251         gtk_box_pack_start (GTK_BOX (vbox1), table, FALSE, FALSE, 0);
252
253         name_label = gtk_label_new (_("Menu name"));
254         gtk_widget_show (name_label);
255         gtk_label_set_xalign (GTK_LABEL (name_label), 1.0);
256         gtk_grid_attach(GTK_GRID(table), name_label, 0, 0, 1, 1);
257
258         name_entry = gtk_entry_new ();
259         gtk_widget_show (name_entry);
260         gtk_grid_attach(GTK_GRID(table), name_entry, 1, 0, 1, 1);
261         gtk_widget_set_hexpand(name_entry, TRUE);
262         gtk_widget_set_halign(name_entry, GTK_ALIGN_FILL);
263
264         cmd_label = gtk_label_new (_("Command"));
265         gtk_widget_show (cmd_label);
266         gtk_label_set_xalign (GTK_LABEL (cmd_label), 1.0);
267         gtk_grid_attach(GTK_GRID(table), cmd_label, 0, 2, 1, 1);
268
269         cmd_entry = gtk_entry_new ();
270         gtk_widget_show (cmd_entry);
271         gtk_grid_attach(GTK_GRID(table), cmd_entry, 1, 2, 1, 1);
272         gtk_widget_set_hexpand(cmd_entry, TRUE);
273         gtk_widget_set_halign(cmd_entry, GTK_ALIGN_FILL);
274
275         /* radio buttons for filter actions or shell */
276         filter_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL,4);
277         gtk_grid_attach(GTK_GRID(table), filter_hbox, 1, 3, 1, 1);
278         gtk_widget_set_hexpand(filter_hbox, TRUE);
279         gtk_widget_set_halign(filter_hbox, GTK_ALIGN_FILL);
280         gtk_widget_show(filter_hbox);
281
282         shell_radiobtn = gtk_radio_button_new_with_label(NULL, _("Shell command"));
283         gtk_box_pack_start(GTK_BOX(filter_hbox), shell_radiobtn, FALSE, FALSE, 0);
284         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(shell_radiobtn), TRUE);
285         gtk_widget_show(shell_radiobtn);
286         
287         g_signal_connect(G_OBJECT(shell_radiobtn), "clicked",
288                          G_CALLBACK(prefs_action_shell_radiobtn_cb), NULL);
289
290         filter_radiobtn =
291                 gtk_radio_button_new_with_label_from_widget(GTK_RADIO_BUTTON(shell_radiobtn), 
292                                                             _("Filter action"));
293         gtk_box_pack_start(GTK_BOX(filter_hbox), filter_radiobtn, FALSE, FALSE, 0);
294         gtk_widget_show(filter_radiobtn);
295         g_signal_connect(G_OBJECT(filter_radiobtn), "clicked",
296                          G_CALLBACK(prefs_action_filter_radiobtn_cb), NULL);
297
298         filter_btn = gtk_button_new_with_label(_("Edit filter action"));
299         gtk_box_pack_start(GTK_BOX(filter_hbox), filter_btn, FALSE, FALSE, 0);
300         gtk_widget_set_sensitive(filter_btn, FALSE);
301         g_signal_connect(G_OBJECT(filter_btn), "clicked",
302                          G_CALLBACK(prefs_action_filterbtn_cb), NULL);
303         gtk_widget_show(filter_btn);
304
305         /* register / substitute / delete */
306
307         reg_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
308         gtk_widget_show(reg_hbox);
309         gtk_box_pack_start(GTK_BOX(vbox1), reg_hbox, FALSE, FALSE, 0);
310
311         arrow = gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_MENU);
312         gtk_widget_show(arrow);
313         gtk_box_pack_start(GTK_BOX(reg_hbox), arrow, FALSE, FALSE, 0);
314         gtk_widget_set_size_request(arrow, -1, 16);
315
316         btn_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
317         gtk_widget_show(btn_hbox);
318         gtk_box_pack_start(GTK_BOX(reg_hbox), btn_hbox, FALSE, FALSE, 0);
319
320         reg_btn = gtkut_stock_button("list-add", _("_Add"));
321         gtk_widget_show(reg_btn);
322         gtk_box_pack_start(GTK_BOX(btn_hbox), reg_btn, FALSE, TRUE, 0);
323         g_signal_connect(G_OBJECT(reg_btn), "clicked",
324                          G_CALLBACK(prefs_actions_register_cb), NULL);
325         CLAWS_SET_TIP(reg_btn,
326                         _("Append the new action above to the list"));
327
328         subst_btn = gtkut_get_replace_btn(_("_Replace"));
329         gtk_widget_show(subst_btn);
330         gtk_box_pack_start(GTK_BOX(btn_hbox), subst_btn, FALSE, TRUE, 0);
331         g_signal_connect(G_OBJECT(subst_btn), "clicked",
332                          G_CALLBACK(prefs_actions_substitute_cb), NULL);
333         CLAWS_SET_TIP(subst_btn,
334                         _("Replace the selected action in list with the action above"));
335
336         del_btn = gtkut_stock_button("list-remove", _("_Remove"));
337         gtk_widget_show(del_btn);
338         gtk_box_pack_start(GTK_BOX(btn_hbox), del_btn, FALSE, TRUE, 0);
339         g_signal_connect(G_OBJECT(del_btn), "clicked",
340                          G_CALLBACK(prefs_actions_delete_cb), NULL);
341         CLAWS_SET_TIP(del_btn,
342                         _("Delete the selected action from the list"));
343
344         clear_btn = gtkut_stock_button("edit-clear", _("C_lear"));
345         gtk_widget_show (clear_btn);
346         gtk_box_pack_start (GTK_BOX (btn_hbox), clear_btn, FALSE, TRUE, 0);
347         g_signal_connect(G_OBJECT (clear_btn), "clicked",
348                         G_CALLBACK(prefs_actions_clear_cb), NULL);
349         CLAWS_SET_TIP(clear_btn,
350                         _("Clear all the input fields in the dialog"));
351
352         info_btn = gtkut_stock_button("dialog-information", _("_Information"));
353         gtk_widget_show(info_btn);
354         gtk_box_pack_end(GTK_BOX(reg_hbox), info_btn, FALSE, FALSE, 0);
355         g_signal_connect(G_OBJECT(info_btn), "clicked",
356                          G_CALLBACK(prefs_actions_info_cb), GTK_WINDOW(window));
357         CLAWS_SET_TIP(info_btn,
358                         _("Show information on configuring actions"));
359
360         cond_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
361         gtk_widget_show(cond_hbox);
362         gtk_box_pack_start(GTK_BOX(vbox1), cond_hbox, TRUE, TRUE, 0);
363
364         cond_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
365         gtk_widget_show(cond_scrolledwin);
366         gtk_widget_set_size_request(cond_scrolledwin, -1, 150);
367         gtk_box_pack_start(GTK_BOX(cond_hbox), cond_scrolledwin,
368                            TRUE, TRUE, 0);
369         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (cond_scrolledwin),
370                                        GTK_POLICY_AUTOMATIC,
371                                        GTK_POLICY_AUTOMATIC);
372         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(cond_scrolledwin),
373                                             GTK_SHADOW_ETCHED_IN);
374
375         cond_list_view = prefs_actions_list_view_create();                                     
376         gtk_widget_show(cond_list_view);
377         gtk_container_add(GTK_CONTAINER (cond_scrolledwin), cond_list_view);
378
379         btn_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
380         gtk_widget_show(btn_vbox);
381         gtk_box_pack_start(GTK_BOX(cond_hbox), btn_vbox, FALSE, FALSE, 0);
382
383         top_btn = gtkut_stock_button("go-top", _("_Top"));
384         gtk_widget_show(top_btn);
385         gtk_box_pack_start(GTK_BOX(btn_vbox), top_btn, FALSE, FALSE, 0);
386         g_signal_connect(G_OBJECT(top_btn), "clicked",
387                          G_CALLBACK(prefs_actions_top_cb), NULL);
388         CLAWS_SET_TIP(top_btn,
389                         _("Move the selected action to the top"));
390
391         PACK_SPACER(btn_vbox, spc_vbox, VSPACING_NARROW_2);
392
393         up_btn = gtkut_stock_button("go-up", _("_Up"));
394         gtk_widget_show(up_btn);
395         gtk_box_pack_start(GTK_BOX(btn_vbox), up_btn, FALSE, FALSE, 0);
396         g_signal_connect(G_OBJECT(up_btn), "clicked",
397                          G_CALLBACK(prefs_actions_up_cb), NULL);
398         CLAWS_SET_TIP(up_btn,
399                         _("Move the selected action up"));
400
401         down_btn = gtkut_stock_button("go-down", _("_Down"));
402         gtk_widget_show(down_btn);
403         gtk_box_pack_start(GTK_BOX(btn_vbox), down_btn, FALSE, FALSE, 0);
404         g_signal_connect(G_OBJECT(down_btn), "clicked",
405                          G_CALLBACK(prefs_actions_down_cb), NULL);
406         CLAWS_SET_TIP(down_btn,
407                         _("Move selected action down"));
408
409         PACK_SPACER(btn_vbox, spc_vbox, VSPACING_NARROW_2);
410
411         bottom_btn = gtkut_stock_button("go-bottom", _("_Bottom"));
412         gtk_widget_show(bottom_btn);
413         gtk_box_pack_start(GTK_BOX(btn_vbox), bottom_btn, FALSE, FALSE, 0);
414         g_signal_connect(G_OBJECT(bottom_btn), "clicked",
415                          G_CALLBACK(prefs_actions_bottom_cb), NULL);
416         CLAWS_SET_TIP(bottom_btn,
417                         _("Move the selected action to the bottom"));
418
419         if (!geometry.min_height) {
420                 geometry.min_width = 486;
421                 geometry.min_height = 322;
422         }
423
424         gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL, &geometry,
425                                       GDK_HINT_MIN_SIZE);
426         gtk_window_set_default_size(GTK_WINDOW(window), prefs_common.actionswin_width,
427                                     prefs_common.actionswin_height);
428
429         gtk_widget_show(window);
430
431         actions.window = window;
432         actions.ok_btn = ok_btn;
433         actions.info_btn = info_btn;
434
435         actions.name_entry = name_entry;
436         actions.cmd_entry  = cmd_entry;
437         actions.filter_btn = filter_btn;
438         actions.shell_radiobtn = shell_radiobtn;
439         actions.filter_radiobtn = filter_radiobtn;
440         
441         actions.actions_list_view = cond_list_view;
442 }
443
444 static void prefs_actions_reset_dialog(void)
445 {
446         gtk_entry_set_text(GTK_ENTRY(actions.name_entry), "");
447         gtk_entry_set_text(GTK_ENTRY(actions.cmd_entry), "");
448         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(actions.shell_radiobtn), TRUE);
449 }
450
451 void prefs_actions_read_config(void)
452 {
453         gchar *rcpath;
454         FILE *fp;
455         gchar buf[PREFSBUFSIZE];
456         gchar *act;
457
458         debug_print("Reading actions configurations...\n");
459
460         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACTIONS_RC, NULL);
461         if ((fp = claws_fopen(rcpath, "rb")) == NULL) {
462                 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "claws_fopen");
463                 g_free(rcpath);
464                 return;
465         }
466         g_free(rcpath);
467
468         while (prefs_common.actions_list != NULL) {
469                 act = (gchar *)prefs_common.actions_list->data;
470                 prefs_common.actions_list =
471                         g_slist_remove(prefs_common.actions_list, act);
472                 g_free(act);
473         }
474
475         while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
476                 const gchar *src_codeset = conv_get_locale_charset_str();
477                 const gchar *dest_codeset = CS_UTF_8;
478                 gchar *tmp;
479
480                 tmp = conv_codeset_strdup(buf, src_codeset, dest_codeset);
481                 if (!tmp) {
482                         g_warning("failed to convert character set of action configuration");
483                         tmp = g_strdup(buf);
484                 }
485
486                 g_strchomp(tmp);
487                 act = strstr(tmp, ": ");
488                 if (act && act[2] && 
489                     action_get_type(&act[2]) != ACTION_ERROR)
490                         prefs_common.actions_list =
491                                 g_slist_append(prefs_common.actions_list,
492                                                tmp);
493                 else
494                         g_free(tmp);
495         }
496         claws_fclose(fp);
497 }
498
499 void prefs_actions_write_config(void)
500 {
501         gchar *rcpath;
502         PrefFile *pfile;
503         GSList *cur;
504
505         debug_print("Writing actions configuration...\n");
506
507         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACTIONS_RC, NULL);
508         if ((pfile= prefs_write_open(rcpath)) == NULL) {
509                 g_warning("failed to write configuration to file");
510                 g_free(rcpath);
511                 return;
512         }
513
514         for (cur = prefs_common.actions_list; cur != NULL; cur = cur->next) {
515                 gchar *tmp = (gchar *)cur->data;
516                 const gchar *src_codeset = CS_UTF_8;
517                 const gchar *dest_codeset = conv_get_locale_charset_str();
518                 gchar *act;
519
520                 act = conv_codeset_strdup(tmp, src_codeset, dest_codeset);
521                 if (!act) {
522                         g_warning("failed to convert character set of action configuration");
523                         act = g_strdup(act);
524                 }
525
526                 if (claws_fputs(act, pfile->fp) == EOF ||
527                     claws_fputc('\n', pfile->fp) == EOF) {
528                         FILE_OP_ERROR(rcpath, "claws_fputs || claws_fputc");
529                         prefs_file_close_revert(pfile);
530                         g_free(act);
531                         g_free(rcpath);
532                         return;
533                 }
534                 g_free(act);
535         }
536         
537         g_free(rcpath);
538
539         if (prefs_file_close(pfile) < 0) {
540                 g_warning("failed to write configuration to file");
541                 return;
542         }
543 }
544
545 static void prefs_actions_clear_list(GtkListStore *list_store)
546 {
547         gtk_list_store_clear(list_store);
548
549         prefs_actions_list_view_insert_action(actions.actions_list_view,
550                                               -1, _("(New)"), FALSE);
551 }
552
553 static void prefs_actions_set_dialog(void)
554 {
555         GtkListStore *store;
556         GSList *cur;
557
558         store = GTK_LIST_STORE(gtk_tree_view_get_model
559                                 (GTK_TREE_VIEW(actions.actions_list_view)));
560
561         prefs_actions_clear_list(store);        
562         prefs_actions_reset_dialog();
563
564         for (cur = prefs_common.actions_list; cur != NULL; cur = cur->next) {
565                 gchar *action = (gchar *) cur->data;
566                 
567                 prefs_actions_list_view_insert_action(actions.actions_list_view,
568                                                       -1, action, TRUE);
569         }
570 }
571
572 static void prefs_actions_set_list(void)
573 {
574         GtkTreeIter iter;
575         GtkListStore *store;
576         
577         g_slist_free(prefs_common.actions_list);
578         prefs_common.actions_list = NULL;
579
580         store = GTK_LIST_STORE(gtk_tree_view_get_model
581                                 (GTK_TREE_VIEW(actions.actions_list_view)));
582
583         if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter)) {
584                 do {
585                         gchar *action;
586                         gboolean is_valid;
587
588                         gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
589                                            PREFS_ACTIONS_DATA, &action,
590                                            PREFS_ACTIONS_VALID, &is_valid,
591                                            -1);
592                         
593                         if (is_valid) 
594                                 prefs_common.actions_list = 
595                                         g_slist_append(prefs_common.actions_list,
596                                                        action);
597
598                 } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(store),
599                                                   &iter));
600         }
601 }
602
603 #define GET_ENTRY(entry) \
604         entry_text = gtk_entry_get_text(GTK_ENTRY(entry))
605
606 static gint prefs_actions_clist_set_row(gint row)
607 {
608         const gchar *entry_text;
609         gint len;
610         gchar action[PREFSBUFSIZE];
611         gchar *new_action;
612
613         GET_ENTRY(actions.name_entry);
614         if (entry_text[0] == '\0') {
615                 alertpanel_error(_("Menu name is not set."));
616                 return -1;
617         }
618
619         if (entry_text[0] == '/') {
620                 alertpanel_error(_("A leading '/' is not allowed in the menu name."));
621                 return -1;
622         }
623
624         if (strchr(entry_text, ':')) {
625                 alertpanel_error(_("Colon ':' is not allowed in the menu name."));
626                 return -1;
627         }
628
629         strncpy(action, entry_text, PREFSBUFSIZE - 1);
630
631         while (strstr(action, "//")) {
632                 char *to_move = strstr(action, "//")+1;
633                 char *where = strstr(action, "//");
634                 int old_len = strlen(action);
635                 memmove(where, to_move, strlen(to_move));
636                 action[old_len-1] = '\0';
637         }
638         
639         g_strstrip(action);
640
641         /* Keep space for the ': ' delimiter */
642         len = strlen(action) + 2;
643         if (len >= PREFSBUFSIZE - 1) {
644                 alertpanel_error(_("Menu name is too long."));
645                 return -1;
646         }
647
648         strcat(action, ": ");
649
650         GET_ENTRY(actions.cmd_entry);
651
652         if (entry_text[0] == '\0') {
653                 alertpanel_error(_("Command-line not set."));
654                 return -1;
655         }
656
657         if (len + strlen(entry_text) >= PREFSBUFSIZE - 1) {
658                 alertpanel_error(_("Menu name and command are too long."));
659                 return -1;
660         }
661
662         if (action_get_type(entry_text) == ACTION_ERROR) {
663                 gchar *message;
664                 message = g_markup_printf_escaped(_("The command\n%s\nhas a syntax error."),
665                                                 entry_text);
666                 alertpanel_error("%s", message);
667                 g_free(message);
668                 return -1;
669         }
670
671         strcat(action, entry_text);
672
673         new_action = g_strdup(action);  
674         prefs_actions_list_view_insert_action(actions.actions_list_view,
675                                               row, new_action, TRUE);
676                                                 
677         prefs_actions_set_list();
678
679         return 0;
680 }
681
682 /* callback functions */
683
684 static void prefs_actions_register_cb(GtkWidget *w, gpointer data)
685 {
686         prefs_actions_clist_set_row(-1);
687
688         modified = FALSE;
689         modified_list = TRUE;
690 }
691
692 static void prefs_actions_substitute_cb(GtkWidget *w, gpointer data)
693 {
694         gint row;
695
696         row = gtkut_list_view_get_selected_row(actions.actions_list_view);
697         if (row <= 0)
698                 return;
699
700         prefs_actions_clist_set_row(row);
701
702         modified = FALSE;
703         modified_list = TRUE;
704 }
705
706 static void prefs_actions_delete_cb(gpointer gtk_action, gpointer data)
707 {
708         GtkTreeIter sel;
709         GtkTreeModel *model;
710         gchar *action;
711         gint row;
712
713         row = gtkut_list_view_get_selected_row(actions.actions_list_view);
714         if (row <= 0) 
715                 return; 
716
717         if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection
718                                 (GTK_TREE_VIEW(actions.actions_list_view)),
719                                 &model, &sel))
720                 return;                         
721
722         if (alertpanel(_("Delete action"),
723                        _("Do you really want to delete this action?"),
724                        NULL, _("_Cancel"), "edit-delete", _("D_elete"), NULL, NULL,
725                        ALERTFOCUS_FIRST) != G_ALERTALTERNATE)
726                 return;
727
728         /* XXX: Here's the reason why we need to store the original 
729          * pointer: we search the slist for it. */
730         gtk_tree_model_get(model, &sel,
731                            PREFS_ACTIONS_DATA, &action,
732                            -1);
733         gtk_list_store_remove(GTK_LIST_STORE(model), &sel);
734
735         prefs_common.actions_list = g_slist_remove(prefs_common.actions_list,
736                                                    action);
737         modified_list = TRUE;
738 }
739
740 static void prefs_actions_delete_all_cb(gpointer gtk_action, gpointer data)
741 {
742         GtkListStore *list_store;
743
744         if (alertpanel(_("Delete all actions"),
745                           _("Do you really want to delete all the actions?"),
746                           NULL, _("_Cancel"), "edit-delete", _("D_elete"), NULL, NULL,
747                           ALERTFOCUS_FIRST) != G_ALERTDEFAULT)
748            return;
749
750         list_store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(actions.actions_list_view)));
751         prefs_actions_clear_list(list_store);
752         modified = FALSE;
753
754         prefs_actions_reset_dialog();
755         modified_list = TRUE;
756 }
757
758 static void prefs_actions_clear_cb(gpointer gtk_action, gpointer data)
759 {
760         gint row;
761
762         prefs_actions_reset_dialog();
763         row = gtkut_list_view_get_selected_row(actions.actions_list_view);
764         if (row < 1)
765                 modified = FALSE;
766         else
767                 modified = TRUE;
768 }
769
770 static void prefs_actions_duplicate_cb(gpointer gtk_action, gpointer data)
771 {
772         gint row;
773         
774         row = gtkut_list_view_get_selected_row(actions.actions_list_view);
775         if (row <= 0)
776                 return;
777
778         modified_list = !prefs_actions_clist_set_row(-row-2);
779 }
780
781 static void prefs_actions_top_cb(GtkWidget *w, gpointer data)
782 {
783         gint row;
784         GtkTreeIter top, sel;
785         GtkTreeModel *model;
786
787         row = gtkut_list_view_get_selected_row(actions.actions_list_view);
788         if (row <= 1) 
789                 return;
790
791         model = gtk_tree_view_get_model(GTK_TREE_VIEW(actions.actions_list_view));              
792         
793         if (!gtk_tree_model_iter_nth_child(model, &top, NULL, 0)
794         ||  !gtk_tree_model_iter_nth_child(model, &sel, NULL, row))
795                 return;
796
797         gtk_list_store_move_after(GTK_LIST_STORE(model), &sel, &top);
798         gtkut_list_view_select_row(actions.actions_list_view, 1);
799         modified_list = TRUE;
800 }
801
802 static void prefs_actions_up_cb(GtkWidget *w, gpointer data)
803 {
804         gint row;
805         GtkTreeIter top, sel;
806         GtkTreeModel *model;
807
808         row = gtkut_list_view_get_selected_row(actions.actions_list_view);
809         if (row <= 1) 
810                 return;
811                 
812         model = gtk_tree_view_get_model(GTK_TREE_VIEW(actions.actions_list_view));      
813
814         if (!gtk_tree_model_iter_nth_child(model, &top, NULL, row - 1)
815         ||  !gtk_tree_model_iter_nth_child(model, &sel, NULL, row))
816                 return;
817
818         gtk_list_store_swap(GTK_LIST_STORE(model), &top, &sel);
819         gtkut_list_view_select_row(actions.actions_list_view, row - 1);
820         modified_list = TRUE;
821 }
822
823 static void prefs_actions_down_cb(GtkWidget *w, gpointer data)
824 {
825         gint row, n_rows;
826         GtkTreeIter top, sel;
827         GtkTreeModel *model;
828
829         model = gtk_tree_view_get_model(GTK_TREE_VIEW(actions.actions_list_view));      
830         n_rows = gtk_tree_model_iter_n_children(model, NULL);
831         row = gtkut_list_view_get_selected_row(actions.actions_list_view);
832         if (row < 1 || row >= n_rows - 1)
833                 return;
834
835         if (!gtk_tree_model_iter_nth_child(model, &top, NULL, row)
836         ||  !gtk_tree_model_iter_nth_child(model, &sel, NULL, row + 1))
837                 return;
838                         
839         gtk_list_store_swap(GTK_LIST_STORE(model), &top, &sel);
840         gtkut_list_view_select_row(actions.actions_list_view, row + 1);
841         modified_list = TRUE;
842 }
843
844 static void prefs_actions_bottom_cb(GtkWidget *w, gpointer data)
845 {
846         gint row, n_rows;
847         GtkTreeIter top, sel;
848         GtkTreeModel *model;
849
850         model = gtk_tree_view_get_model(GTK_TREE_VIEW(actions.actions_list_view));      
851         n_rows = gtk_tree_model_iter_n_children(model, NULL);
852         row = gtkut_list_view_get_selected_row(actions.actions_list_view);
853         if (row < 1 || row >= n_rows - 1)
854                 return;
855
856         if (!gtk_tree_model_iter_nth_child(model, &top, NULL, row)
857         ||  !gtk_tree_model_iter_nth_child(model, &sel, NULL, n_rows - 1))
858                 return;
859
860         gtk_list_store_move_after(GTK_LIST_STORE(model), &top, &sel);           
861         gtkut_list_view_select_row(actions.actions_list_view, n_rows - 1);
862         modified_list = TRUE;
863 }
864
865 static gint prefs_actions_deleted(GtkWidget *widget, GdkEventAny *event,
866                                   gpointer *data)
867 {
868         prefs_actions_cancel(widget, data);
869         return TRUE;
870 }
871
872 static gboolean prefs_actions_key_pressed(GtkWidget *widget, GdkEventKey *event,
873                                           gpointer data)
874 {
875         if (event && event->keyval == GDK_KEY_Escape)
876                 prefs_actions_cancel(widget, data);
877         else {
878                 GtkWidget *focused = gtkut_get_focused_child(
879                                 GTK_CONTAINER(widget));
880                 if (focused && GTK_IS_EDITABLE(focused))
881                         modified = TRUE;
882         }
883         return FALSE;
884 }
885
886 static gboolean prefs_actions_search_func_cb (GtkTreeModel *model, gint column, const gchar *key, 
887                                                 GtkTreeIter *iter, gpointer search_data) 
888 {
889         gchar *store_string;
890         gboolean retval;
891         GtkTreePath *path;
892
893         gtk_tree_model_get (model, iter, column, &store_string, -1);
894
895         if (!store_string || !key)
896                 return FALSE;
897
898
899         retval = (strncmp (key, store_string, strlen(key)) != 0);
900
901         g_free(store_string);
902         debug_print("selecting row\n");
903         path = gtk_tree_model_get_path(model, iter);
904         prefs_actions_select_row(GTK_TREE_VIEW(actions.actions_list_view), path);
905         gtk_tree_path_free(path);
906
907         return retval;
908 }
909 static void prefs_actions_cancel(GtkWidget *w, gpointer data)
910 {
911         GtkListStore *store;
912
913         if (modified && alertpanel(_("Entry not saved"),
914                                  _("The entry was not saved. Close anyway?"),
915                                  "window-close", _("_Close"), NULL, _("_Continue editing"),
916                                  NULL, NULL, ALERTFOCUS_SECOND) != G_ALERTDEFAULT) {
917                 return;
918         } else if (modified_list && alertpanel(_("Actions list not saved"),
919                                  _("The actions list has been modified. Close anyway?"),
920                                  "window-close", _("_Close"), NULL, _("_Continue editing"),
921                                  NULL, NULL, ALERTFOCUS_SECOND) != G_ALERTDEFAULT) {
922                 return;
923         }
924         modified = FALSE;
925         modified_list = FALSE;
926         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW
927                                 (actions.actions_list_view)));
928         gtk_list_store_clear(store);
929         prefs_actions_read_config();
930         gtk_widget_hide(actions.window);
931         gtk_window_set_modal(GTK_WINDOW(actions.window), FALSE);
932         inc_unlock();
933 }
934
935 static void prefs_actions_ok(GtkWidget *widget, gpointer data)
936 {
937         MainWindow *mainwin = (MainWindow *) data;
938         const GList *list;
939         const GList *iter;
940         MessageView *msgview;
941         Compose *compose;
942         GtkListStore *store;
943
944         if (modified && alertpanel(_("Entry not saved"),
945                                  _("The entry was not saved. Close anyway?"),
946                                  "window-close", _("_Close"), NULL, _("_Continue editing"),
947                                  NULL, NULL, ALERTFOCUS_SECOND) != G_ALERTDEFAULT) {
948                 return;
949         } 
950         modified = FALSE;
951         modified_list = FALSE;
952         prefs_actions_set_list();
953         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW
954                                 (actions.actions_list_view)));
955         gtk_list_store_clear(store);
956         prefs_actions_write_config();
957
958         /* Update mainwindow actions menu */
959         main_window_update_actions_menu(mainwin);
960
961         /* Update separated message view actions menu */
962         list = messageview_get_msgview_list();
963         for (iter = list; iter; iter = iter->next) {
964                 msgview = (MessageView *) iter->data;
965                 messageview_update_actions_menu(msgview);
966         }
967
968         /* Update compose windows actions menu */
969         list = compose_get_compose_list();
970         for (iter = list; iter; iter = iter->next) {
971                 compose = (Compose *) iter->data;
972                 compose_update_actions_menu(compose);
973         }
974
975         /* Update toolbars */
976         prefs_toolbar_update_action_btns();
977         
978         gtk_widget_hide(actions.window);
979         gtk_window_set_modal(GTK_WINDOW(actions.window), FALSE);
980         inc_unlock();
981 }
982
983 /*
984  * Strings describing action format strings
985  * 
986  * When adding new lines, remember to put one string for each line
987  */
988 static gchar *actions_desc_strings[] = {
989         N_("<span weight=\"bold\" underline=\"single\">Menu name:</span>"), NULL,
990         N_("Use / in menu name to make submenus."), NULL,
991         "", NULL,
992         N_("<span weight=\"bold\" underline=\"single\">Command-line:</span>"), NULL,
993         N_("<span weight=\"bold\">Begin with:</span>"), NULL,
994         "     |",   N_("to send message body or selection to command's standard input"),
995         "     &gt;",   N_("to send user provided text to command's standard input"),
996         "     *",   N_("to send user provided hidden text to command's standard input"),
997         N_("<span weight=\"bold\">End with:</span>"), NULL,
998         "     |",   N_("to replace message body or selection with command's standard output"),
999         "     &gt;",   N_("to insert command's standard output without replacing old text"),
1000         "     &amp;",   N_("to run command asynchronously"),
1001         N_("<span weight=\"bold\">Use:</span>"), NULL, 
1002         "     %f",  N_("for the file of the selected message in RFC822/2822 format "),
1003         "     %F",  N_("for the list of the files of the selected messages in RFC822/2822 format"),
1004         "     %p",  N_("for the file of the selected decoded message MIME part"),
1005         "     %u",  N_("for a user provided argument"),
1006         "     %h",  N_("for a user provided hidden argument (e.g. password)"),
1007         "     %s",  N_("for the text selection"),
1008         "  %as{}",  N_("apply filtering actions between {} to selected messages"),
1009         "     %%",  N_("for a literal %"),
1010         NULL, NULL
1011 };
1012
1013
1014 static DescriptionWindow actions_desc_win = { 
1015         NULL,
1016         NULL,
1017         TRUE,
1018         2,
1019         N_("Actions"),
1020         N_("The Actions feature is a way for the user to launch "
1021            "external commands to process a complete message file or just "
1022            "one of its parts."),
1023         actions_desc_strings
1024 };
1025
1026
1027 static void prefs_actions_info_cb(GtkWidget *w, GtkWidget *window)
1028 {
1029         actions_desc_win.parent = window;
1030         description_window_create(&actions_desc_win);
1031 }
1032
1033 static GtkListStore* prefs_actions_create_data_store(void)
1034 {
1035         return gtk_list_store_new(N_PREFS_ACTIONS_COLUMNS,
1036                                   G_TYPE_STRING,        
1037                                   G_TYPE_POINTER,
1038                                   G_TYPE_BOOLEAN,
1039                                   -1);
1040 }
1041
1042 static void prefs_actions_list_view_insert_action(GtkWidget *list_view,
1043                                                   gint row,
1044                                                   gchar *action,
1045                                                   gboolean is_valid) 
1046 {
1047         GtkTreeIter iter;
1048         GtkTreeIter sibling;
1049         GtkListStore *list_store = GTK_LIST_STORE(gtk_tree_view_get_model
1050                                         (GTK_TREE_VIEW(list_view)));
1051
1052 /*      row -1 to add a new rule to store,
1053         row >=0 to change an existing row
1054         row <-1 insert a new row after (-row-2)
1055 */
1056         if (row >= 0 ) {
1057                 /* modify the existing */
1058                 if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store),
1059                                                    &iter, NULL, row))
1060                         row = -1;
1061         } else if (row < -1 ) {
1062                 if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store),
1063                                                    &sibling, NULL, -row-2))
1064                         row = -1;               
1065         }
1066
1067         if (row == -1 ) {
1068                 /* append new */
1069                 gtk_list_store_append(list_store, &iter);
1070                 gtk_list_store_set(list_store, &iter,
1071                                    PREFS_ACTIONS_STRING, action,
1072                                    PREFS_ACTIONS_DATA, action,
1073                                    PREFS_ACTIONS_VALID,  is_valid,
1074                                    -1);
1075         } else if (row < -1) {
1076                 /* duplicate */
1077                 gtk_list_store_insert_after(list_store, &iter, &sibling);
1078                 gtk_list_store_set(list_store, &iter,
1079                                    PREFS_ACTIONS_STRING, action,
1080                                    PREFS_ACTIONS_DATA, action,
1081                                    PREFS_ACTIONS_VALID,  is_valid,
1082                                    -1);
1083         } else {
1084                 /* change existing */
1085                 gchar *old_action;
1086
1087                 gtk_tree_model_get(GTK_TREE_MODEL(list_store), &iter,
1088                                    PREFS_ACTIONS_DATA, &old_action,
1089                                    -1);
1090                 g_free(old_action);                             
1091
1092                 gtk_list_store_set(list_store, &iter,
1093                                    PREFS_ACTIONS_STRING, action,
1094                                    PREFS_ACTIONS_DATA, action,
1095                                    -1);
1096         }
1097 }
1098
1099 static GtkActionGroup *prefs_actions_popup_action = NULL;
1100 static GtkWidget *prefs_actions_popup_menu = NULL;
1101
1102 static GtkActionEntry prefs_actions_popup_entries[] =
1103 {
1104         {"PrefsActionsPopup",                   NULL, "PrefsActionsPopup", NULL, NULL, NULL },
1105         {"PrefsActionsPopup/Delete",    NULL, N_("_Delete"), NULL, NULL, G_CALLBACK(prefs_actions_delete_cb) },
1106         {"PrefsActionsPopup/DeleteAll", NULL, N_("Delete _all"), NULL, NULL, G_CALLBACK(prefs_actions_delete_all_cb) },
1107         {"PrefsActionsPopup/Duplicate", NULL, N_("D_uplicate"), NULL, NULL, G_CALLBACK(prefs_actions_duplicate_cb) },
1108 };
1109
1110 static void prefs_actions_row_selected(GtkTreeSelection *selection, GtkTreeView *list_view)
1111 {
1112         GtkTreePath *path;
1113         GtkTreeIter iter;
1114         GtkTreeModel *model;
1115         
1116         if (!gtk_tree_selection_get_selected(selection, &model, &iter))
1117                 return;
1118         
1119         path = gtk_tree_model_get_path(model, &iter);
1120         prefs_actions_select_row(list_view, path);
1121         gtk_tree_path_free(path);
1122 }
1123
1124 static gint prefs_actions_list_btn_pressed(GtkWidget *widget, GdkEventButton *event,
1125                                    GtkTreeView *list_view)
1126 {
1127         if (event) {
1128                 /* left- or right-button click */
1129                 if (event->button == 1 || event->button == 3) {
1130                         GtkTreePath *path = NULL;
1131                         if (gtk_tree_view_get_path_at_pos( list_view, event->x, event->y,
1132                                         &path, NULL, NULL, NULL)) {
1133                                 prefs_actions_select_row(list_view, path);
1134                 }
1135                 if (path)
1136                         gtk_tree_path_free(path);
1137                 }
1138
1139                 /* right-button click */
1140                 if (event->button == 3) {
1141                         GtkTreeModel *model = gtk_tree_view_get_model(list_view);
1142                         GtkTreeIter iter;
1143                         gboolean non_empty;
1144                         gint row;
1145
1146                         if (!prefs_actions_popup_menu) {
1147                                 prefs_actions_popup_action = cm_menu_create_action_group("PrefsActionsPopup",
1148                                                 prefs_actions_popup_entries, G_N_ELEMENTS(prefs_actions_popup_entries),
1149                                                 (gpointer)list_view);
1150                                 MENUITEM_ADDUI("/Menus", "PrefsActionsPopup", "PrefsActionsPopup", GTK_UI_MANAGER_MENU)
1151                                 MENUITEM_ADDUI("/Menus/PrefsActionsPopup", "Delete", "PrefsActionsPopup/Delete", GTK_UI_MANAGER_MENUITEM)
1152                                 MENUITEM_ADDUI("/Menus/PrefsActionsPopup", "DeleteAll", "PrefsActionsPopup/DeleteAll", GTK_UI_MANAGER_MENUITEM)
1153                                 MENUITEM_ADDUI("/Menus/PrefsActionsPopup", "Duplicate", "PrefsActionsPopup/Duplicate", GTK_UI_MANAGER_MENUITEM)
1154                                 prefs_actions_popup_menu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
1155                                                 gtk_ui_manager_get_widget(gtkut_ui_manager(), "/Menus/PrefsActionsPopup")) );
1156                         }
1157
1158                         /* grey out some popup menu items if there is no selected row */
1159                         row = gtkut_list_view_get_selected_row(GTK_WIDGET(list_view));
1160                         cm_menu_set_sensitive("PrefsActionsPopup/Delete", (row > 0));
1161                         cm_menu_set_sensitive("PrefsActionsPopup/Duplicate", (row > 0));
1162
1163                         /* grey out seom popup menu items if there is no row
1164                         (not counting the (New) one at row 0) */
1165                         non_empty = gtk_tree_model_get_iter_first(model, &iter);
1166                         if (non_empty)
1167                                 non_empty = gtk_tree_model_iter_next(model, &iter);
1168                         cm_menu_set_sensitive("PrefsActionsPopup/DeleteAll", non_empty);
1169
1170                         gtk_menu_popup_at_pointer(GTK_MENU(prefs_actions_popup_menu), NULL);
1171                 }
1172    }
1173    return FALSE;
1174 }
1175
1176 static gboolean prefs_actions_list_popup_menu(GtkWidget *widget, gpointer data)
1177 {
1178    GtkTreeView *list_view = (GtkTreeView *)data;
1179    GdkEventButton event;
1180    
1181    event.button = 3;
1182    event.time = gtk_get_current_event_time();
1183    
1184    prefs_actions_list_btn_pressed(NULL, &event, list_view);
1185
1186    return TRUE;
1187 }
1188
1189 static GtkWidget *prefs_actions_list_view_create(void)
1190 {
1191         GtkTreeView *list_view;
1192         GtkTreeSelection *selector;
1193         GtkTreeModel *model;
1194
1195         model = GTK_TREE_MODEL(prefs_actions_create_data_store());
1196         list_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(model));
1197         g_object_unref(model);  
1198         
1199         g_signal_connect(G_OBJECT(list_view), "popup-menu",
1200                          G_CALLBACK(prefs_actions_list_popup_menu), list_view);
1201         g_signal_connect(G_OBJECT(list_view), "button-press-event",
1202                         G_CALLBACK(prefs_actions_list_btn_pressed), list_view);
1203
1204         gtk_tree_view_set_rules_hint(list_view, prefs_common.use_stripes_everywhere);
1205         gtk_tree_view_set_reorderable(list_view, TRUE);
1206
1207         selector = gtk_tree_view_get_selection(list_view);
1208         gtk_tree_selection_set_mode(selector, GTK_SELECTION_BROWSE);
1209         g_signal_connect(G_OBJECT(selector), "changed",
1210                          G_CALLBACK(prefs_actions_row_selected), list_view);
1211
1212         /* create the columns */
1213         prefs_actions_create_list_view_columns(GTK_WIDGET(list_view));
1214
1215         return GTK_WIDGET(list_view);
1216 }
1217
1218 static void prefs_actions_create_list_view_columns(GtkWidget *list_view)
1219 {
1220         GtkTreeViewColumn *column;
1221         GtkCellRenderer *renderer;
1222
1223         renderer = gtk_cell_renderer_text_new();
1224         column = gtk_tree_view_column_new_with_attributes
1225                 (_("Current actions"),
1226                  renderer,
1227                  "text", PREFS_ACTIONS_STRING,
1228                  NULL);
1229         gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);          
1230         gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(list_view), prefs_actions_search_func_cb , NULL, NULL);
1231 }
1232
1233 #define ENTRY_SET_TEXT(entry, str) \
1234         gtk_entry_set_text(GTK_ENTRY(entry), str ? str : "")
1235
1236 static void prefs_actions_select_row(GtkTreeView *list_view, GtkTreePath *path)
1237 {
1238         GtkTreeModel *model = gtk_tree_view_get_model(list_view);
1239         GtkTreeSelection *selection;
1240         gchar *action;
1241         gchar *cmd;
1242         gchar buf[PREFSBUFSIZE];
1243         GtkTreeIter iter;
1244         gboolean is_valid;
1245
1246         if (!model || !path || !gtk_tree_model_get_iter(model, &iter, path))
1247                 return;
1248
1249         /* select row */
1250         selection = gtk_tree_view_get_selection(list_view);
1251         gtk_tree_selection_select_path(selection, path);
1252
1253         gtk_tree_model_get(model, &iter, 
1254                            PREFS_ACTIONS_VALID,  &is_valid,
1255                            PREFS_ACTIONS_DATA, &action,
1256                            -1);
1257         if (!is_valid) {
1258                 prefs_actions_reset_dialog();
1259                 return;
1260         }
1261         
1262         strncpy(buf, action, PREFSBUFSIZE - 1);
1263         buf[PREFSBUFSIZE - 1] = '\0';
1264         cmd = strstr(buf, ": ");
1265
1266         if (cmd && cmd[2])
1267                 ENTRY_SET_TEXT(actions.cmd_entry, &cmd[2]);
1268         else
1269                 return;
1270
1271         *cmd = '\0';
1272         gtk_entry_set_text(GTK_ENTRY(actions.name_entry), buf);
1273
1274         if (g_str_has_prefix(&cmd[2], "%as{") == TRUE)
1275                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(
1276                                                 actions.filter_radiobtn), TRUE);
1277         else
1278                 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(
1279                                                 actions.shell_radiobtn), TRUE);
1280
1281         return;
1282 }
1283
1284 static void prefs_action_filter_radiobtn_cb(GtkWidget *widget, gpointer data)
1285 {
1286         if (actions.filter_btn)
1287                 gtk_widget_set_sensitive(actions.filter_btn, TRUE);
1288         if (actions.cmd_entry)
1289                 gtk_widget_set_sensitive(actions.cmd_entry, FALSE);
1290         if (actions.info_btn)
1291                 gtk_widget_set_sensitive(actions.info_btn, FALSE);
1292 }
1293
1294 static void prefs_action_shell_radiobtn_cb(GtkWidget *widget, gpointer data)
1295 {
1296         if (actions.filter_btn)
1297                 gtk_widget_set_sensitive(actions.filter_btn, FALSE);
1298         if (actions.cmd_entry)
1299                 gtk_widget_set_sensitive(actions.cmd_entry, TRUE);
1300         if (actions.info_btn)
1301                 gtk_widget_set_sensitive(actions.info_btn, TRUE);
1302 }
1303
1304 static void prefs_action_filterbtn_cb(GtkWidget *widget, gpointer data)
1305 {
1306         gchar *action_str, **tokens;
1307         GSList *action_list = NULL, *cur;
1308
1309         action_str = gtk_editable_get_chars(GTK_EDITABLE(actions.cmd_entry), 0, -1);
1310         if(modified &&
1311            *action_str != '\0' &&
1312            alertpanel(_("Entry was modified"),
1313                         _("Opening the filter action dialog will clear current modifications "
1314                         "of the command-line."),
1315                         NULL, _("_Close"), NULL, _("_Open"), NULL, NULL,
1316                         ALERTFOCUS_SECOND) == G_ALERTDEFAULT) {
1317                 return;
1318         }
1319         tokens = g_strsplit_set(action_str, "{}", 5);
1320
1321         if (tokens[0] && tokens[1] && *tokens[1] != '\0') {
1322                 action_list = matcher_parser_get_action_list(tokens[1]);
1323                 if (action_list == NULL)
1324                         alertpanel_error(_("Action string is not valid."));
1325         }
1326                 
1327         prefs_filtering_action_open(action_list, prefs_action_define_filter_done);
1328
1329         if (action_list != NULL) {
1330                 for(cur = action_list ; cur != NULL ; cur = cur->next)
1331                         filteringaction_free(cur->data);
1332         }
1333         
1334         g_free(action_str);
1335         g_strfreev(tokens);
1336 }
1337
1338 static void prefs_action_define_filter_done(GSList * action_list)
1339 {
1340         gchar *str;
1341
1342         if (action_list == NULL)
1343                 return;
1344
1345         action_list = filtering_action_list_sort(action_list);
1346         str = filteringaction_list_to_string(action_list);
1347
1348         if (str != NULL) {
1349                 gchar *cmd;
1350                 cmd = g_strdup_printf("%%as{%s}",str);
1351                 g_free(str);
1352                 gtk_entry_set_text(GTK_ENTRY(actions.cmd_entry), cmd);
1353                 g_free(cmd);
1354                 modified = TRUE;
1355         }
1356 }
1357
1358 void prefs_actions_rename_path(const gchar *old_path, const gchar *new_path)
1359 {
1360         gchar **tokens, *action_str;
1361         GSList *action, *action_list;
1362         
1363         for (action = prefs_common.actions_list; action != NULL;
1364                         action = action->next) {
1365                 action_str = (gchar *)action->data;
1366                 tokens = g_strsplit_set(action_str, "{}", 5);
1367                 
1368                 if (tokens[0] && tokens[1] && *tokens[1] != '\0')
1369                         action_list = matcher_parser_get_action_list(tokens[1]);
1370                 else
1371                         action_list = NULL;
1372
1373                 if (action_list &&
1374                     filtering_action_list_rename_path(action_list,
1375                                                 old_path, new_path)) {
1376                         gchar *str = filteringaction_list_to_string(action_list);
1377                         g_free(action->data);
1378                         action->data = g_strconcat(tokens[0], "{",
1379                                 str ? str : "", "}", NULL);
1380                         if (str)
1381                                 g_free(str);
1382                 }
1383
1384                 g_strfreev(tokens);
1385         }
1386         prefs_actions_write_config();
1387 }
1388
1389 gint prefs_actions_find_by_name(const gchar *name)
1390 {
1391         GSList *act = prefs_common.actions_list;
1392         gchar *action_name, *action_p;
1393         gint action_nb = 0;
1394         
1395         for (; act != NULL; act = act->next) {
1396                 action_name = g_strdup((gchar *)act->data);
1397                 action_p = strstr(action_name, ": ");
1398                 action_p[0] = 0x00;
1399
1400                 if (g_utf8_collate(name, action_name) == 0) {
1401                         g_free(action_name);
1402                         return action_nb;
1403                 }
1404
1405                 g_free(action_name);
1406                 action_nb++;
1407         }
1408
1409         return -1;
1410 }