2008-03-07 [paul] 3.3.1cvs17
[claws.git] / src / prefs_actions.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2007 Hiroyuki Yamamoto & The Claws Mail Team
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
20 #ifdef HAVE_CONFIG_H
21 #  include "config.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 "gtkutils.h"
47 #include "manual.h"
48 #include "menu.h"
49
50 enum {
51         PREFS_ACTIONS_STRING,   /*!< string pointer managed by list store, 
52                                  *   and never touched or retrieved by 
53                                  *   us */ 
54         PREFS_ACTIONS_DATA,     /*!< pointer to string that is not managed by 
55                                  *   the list store, and which is retrieved
56                                  *   and touched by us */
57         PREFS_ACTIONS_VALID,    /*!< contains a valid action, otherwise "(New)" */
58         N_PREFS_ACTIONS_COLUMNS
59 };
60
61 static struct Actions
62 {
63         GtkWidget *window;
64
65         GtkWidget *ok_btn;
66
67         GtkWidget *name_entry;
68         GtkWidget *cmd_entry;
69
70         GtkWidget *actions_list_view;
71 } actions;
72
73 static int modified = FALSE;
74 static int modified_list = FALSE;
75
76 /* widget creating functions */
77 static void prefs_actions_create        (MainWindow *mainwin);
78 static void prefs_actions_set_dialog    (void);
79 static gint prefs_actions_clist_set_row (gint row);
80
81 /* callback functions */
82 static void prefs_actions_info_cb       (GtkWidget      *w,
83                                          GtkWidget      *window);
84 static void prefs_actions_register_cb   (GtkWidget      *w,
85                                          gpointer        data);
86 static void prefs_actions_substitute_cb (GtkWidget      *w,
87                                          gpointer        data);
88 static void prefs_actions_delete_cb     (GtkWidget      *w,
89                                          gpointer        data);
90 static void prefs_actions_delete_all_cb (GtkWidget      *w,
91                                          gpointer        data);
92 static void prefs_actions_clear_cb      (void);
93 static void prefs_actions_duplicate_cb  (GtkWidget      *w,
94                                          gpointer        data);
95 static void prefs_actions_up            (GtkWidget      *w,
96                                          gpointer        data);
97 static void prefs_actions_down          (GtkWidget      *w,
98                                          gpointer        data);
99 static gint prefs_actions_deleted       (GtkWidget      *widget,
100                                          GdkEventAny    *event,
101                                          gpointer       *data);
102 static gboolean prefs_actions_key_pressed(GtkWidget     *widget,
103                                           GdkEventKey   *event,
104                                           gpointer       data);
105 static void prefs_actions_cancel        (GtkWidget      *w,
106                                          gpointer        data);
107 static void prefs_actions_ok            (GtkWidget      *w,
108                                          gpointer        data);
109
110 static GtkListStore* prefs_actions_create_data_store    (void);
111
112 static void prefs_actions_list_view_insert_action       (GtkWidget *list_view,
113                                                          gint row,
114                                                          gchar *action,
115                                                          gboolean is_valid);
116 static GtkWidget *prefs_actions_list_view_create        (void);
117 static void prefs_actions_create_list_view_columns      (GtkWidget *list_view);
118 static void prefs_actions_select_row(GtkTreeView *list_view, GtkTreePath *path);
119
120 void prefs_actions_open(MainWindow *mainwin)
121 {
122         inc_lock();
123
124         if (!actions.window)
125                 prefs_actions_create(mainwin);
126
127         manage_window_set_transient(GTK_WINDOW(actions.window));
128         gtk_widget_grab_focus(actions.ok_btn);
129
130         prefs_actions_set_dialog();
131
132         gtk_widget_show(actions.window);
133 }
134
135 /*!
136  *\brief        Save Gtk object size to prefs dataset
137  */
138 static void prefs_actions_size_allocate_cb(GtkWidget *widget,
139                                          GtkAllocation *allocation)
140 {
141         g_return_if_fail(allocation != NULL);
142
143         prefs_common.actionswin_width = allocation->width;
144         prefs_common.actionswin_height = allocation->height;
145 }
146
147 static void prefs_actions_create(MainWindow *mainwin)
148 {
149         GtkWidget *window;
150         GtkWidget *vbox;
151         GtkWidget *help_btn;
152         GtkWidget *ok_btn;
153         GtkWidget *cancel_btn;
154         GtkWidget *confirm_area;
155
156         GtkWidget *vbox1;
157         GtkWidget *table;
158
159         GtkWidget *name_label;
160         GtkWidget *name_entry;
161         GtkWidget *cmd_label;
162         GtkWidget *cmd_entry;
163
164         GtkWidget *reg_hbox;
165         GtkWidget *btn_hbox;
166         GtkWidget *arrow;
167         GtkWidget *reg_btn;
168         GtkWidget *subst_btn;
169         GtkWidget *del_btn;
170         GtkWidget *clear_btn;
171
172         GtkWidget *cond_hbox;
173         GtkWidget *cond_scrolledwin;
174         GtkWidget *cond_list_view;
175
176         GtkWidget *info_btn;
177
178         GtkWidget *btn_vbox;
179         GtkWidget *up_btn;
180         GtkWidget *down_btn;
181         static GdkGeometry geometry;
182         GtkTooltips *tooltips;
183
184         debug_print("Creating actions configuration window...\n");
185
186         tooltips = gtk_tooltips_new();
187
188         window = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "prefs_actions");
189
190         gtk_container_set_border_width(GTK_CONTAINER (window), 8);
191         gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
192         gtk_window_set_modal(GTK_WINDOW(window), TRUE);
193         gtk_window_set_resizable(GTK_WINDOW(window), TRUE);
194
195         vbox = gtk_vbox_new(FALSE, 6);
196         gtk_widget_show(vbox);
197         gtk_container_add(GTK_CONTAINER(window), vbox);
198
199         gtkut_stock_button_set_create_with_help(&confirm_area, &help_btn,
200                         &cancel_btn, GTK_STOCK_CANCEL,
201                         &ok_btn, GTK_STOCK_OK,
202                         NULL, NULL);
203         gtk_widget_show(confirm_area);
204         gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
205         gtk_widget_grab_default(ok_btn);
206
207         gtk_window_set_title(GTK_WINDOW(window), _("Actions configuration"));
208         g_signal_connect(G_OBJECT(window), "delete_event",
209                          G_CALLBACK(prefs_actions_deleted), NULL);
210         g_signal_connect(G_OBJECT(window), "size_allocate",
211                          G_CALLBACK(prefs_actions_size_allocate_cb), NULL);
212         g_signal_connect(G_OBJECT(window), "key_press_event",
213                          G_CALLBACK(prefs_actions_key_pressed), NULL);
214         MANAGE_WINDOW_SIGNALS_CONNECT(window);
215         g_signal_connect(G_OBJECT(ok_btn), "clicked",
216                          G_CALLBACK(prefs_actions_ok), mainwin);
217         g_signal_connect(G_OBJECT(cancel_btn), "clicked",
218                          G_CALLBACK(prefs_actions_cancel), NULL);
219         g_signal_connect(G_OBJECT(help_btn), "clicked",
220                          G_CALLBACK(manual_open_with_anchor_cb),
221                          MANUAL_ANCHOR_ACTIONS);
222
223         vbox1 = gtk_vbox_new(FALSE, VSPACING);
224         gtk_widget_show(vbox1);
225         gtk_box_pack_start(GTK_BOX(vbox), vbox1, TRUE, TRUE, 0);
226         gtk_container_set_border_width(GTK_CONTAINER(vbox1), 2);
227
228         table = gtk_table_new(2, 2, FALSE);
229         gtk_table_set_row_spacings (GTK_TABLE (table), VSPACING_NARROW_2);
230         gtk_table_set_col_spacings (GTK_TABLE (table), 4);
231         gtk_widget_show(table);
232         gtk_box_pack_start (GTK_BOX (vbox1), table, FALSE, FALSE, 0);
233
234         name_label = gtk_label_new (_("Menu name"));
235         gtk_widget_show (name_label);
236         gtk_misc_set_alignment (GTK_MISC (name_label), 1, 0.5);
237         gtk_table_attach (GTK_TABLE (table), name_label, 0, 1, 0, 1,
238                           (GtkAttachOptions) (GTK_FILL),
239                           (GtkAttachOptions) (0), 0, 0);
240
241         name_entry = gtk_entry_new ();
242         gtk_widget_show (name_entry);
243         gtk_table_attach (GTK_TABLE (table), name_entry, 1, 2, 0, 1,
244                           (GtkAttachOptions) (GTK_FILL|GTK_EXPAND),
245                           (GtkAttachOptions) (0), 0, 0);
246
247         cmd_label = gtk_label_new (_("Command line"));
248         gtk_widget_show (cmd_label);
249         gtk_misc_set_alignment (GTK_MISC (cmd_label), 1, 0.5);
250         gtk_table_attach (GTK_TABLE (table), cmd_label, 0, 1, 1, 2,
251                           (GtkAttachOptions) (GTK_FILL),
252                           (GtkAttachOptions) (0), 0, 0);
253
254         cmd_entry = gtk_entry_new ();
255         gtk_widget_show (cmd_entry);
256         gtk_table_attach (GTK_TABLE (table), cmd_entry, 1, 2, 1, 2,
257                           (GtkAttachOptions) (GTK_FILL|GTK_EXPAND),
258                           (GtkAttachOptions) (0), 0, 0);
259
260         /* register / substitute / delete */
261
262         reg_hbox = gtk_hbox_new(FALSE, 4);
263         gtk_widget_show(reg_hbox);
264         gtk_box_pack_start(GTK_BOX(vbox1), reg_hbox, FALSE, FALSE, 0);
265
266         arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
267         gtk_widget_show(arrow);
268         gtk_box_pack_start(GTK_BOX(reg_hbox), arrow, FALSE, FALSE, 0);
269         gtk_widget_set_size_request(arrow, -1, 16);
270
271         btn_hbox = gtk_hbox_new(TRUE, 4);
272         gtk_widget_show(btn_hbox);
273         gtk_box_pack_start(GTK_BOX(reg_hbox), btn_hbox, FALSE, FALSE, 0);
274
275         reg_btn = gtk_button_new_from_stock(GTK_STOCK_ADD);
276         gtk_widget_show(reg_btn);
277         gtk_box_pack_start(GTK_BOX(btn_hbox), reg_btn, FALSE, TRUE, 0);
278         g_signal_connect(G_OBJECT(reg_btn), "clicked",
279                          G_CALLBACK(prefs_actions_register_cb), NULL);
280         gtk_tooltips_set_tip(GTK_TOOLTIPS(tooltips), reg_btn,
281                         _("Append the new action above to the list"), NULL);
282
283         subst_btn = gtkut_get_replace_btn(_("Replace"));
284         gtk_widget_show(subst_btn);
285         gtk_box_pack_start(GTK_BOX(btn_hbox), subst_btn, FALSE, TRUE, 0);
286         g_signal_connect(G_OBJECT(subst_btn), "clicked",
287                          G_CALLBACK(prefs_actions_substitute_cb), NULL);
288         gtk_tooltips_set_tip(GTK_TOOLTIPS(tooltips), subst_btn,
289                         _("Replace the selected action in list with the action above"), NULL);
290
291         del_btn = gtk_button_new_from_stock(GTK_STOCK_DELETE);
292         gtk_widget_show(del_btn);
293         gtk_box_pack_start(GTK_BOX(btn_hbox), del_btn, FALSE, TRUE, 0);
294         g_signal_connect(G_OBJECT(del_btn), "clicked",
295                          G_CALLBACK(prefs_actions_delete_cb), NULL);
296         gtk_tooltips_set_tip(GTK_TOOLTIPS(tooltips), del_btn,
297                         _("Delete the selected action from the list"), NULL);
298
299         clear_btn = gtk_button_new_from_stock (GTK_STOCK_CLEAR);
300         gtk_widget_show (clear_btn);
301         gtk_box_pack_start (GTK_BOX (btn_hbox), clear_btn, FALSE, TRUE, 0);
302         g_signal_connect(G_OBJECT (clear_btn), "clicked",
303                         G_CALLBACK(prefs_actions_clear_cb), NULL);
304         gtk_tooltips_set_tip(GTK_TOOLTIPS(tooltips), clear_btn,
305                         _("Clear all the input fields in the dialog"), NULL);
306
307 #if GTK_CHECK_VERSION(2, 8, 0)
308         info_btn = gtk_button_new_from_stock(GTK_STOCK_INFO);
309 #else
310         info_btn = gtk_button_new_with_label(_("Info..."));
311 #endif
312         gtk_widget_show(info_btn);
313         gtk_box_pack_end(GTK_BOX(reg_hbox), info_btn, FALSE, FALSE, 0);
314         g_signal_connect(G_OBJECT(info_btn), "clicked",
315                          G_CALLBACK(prefs_actions_info_cb), GTK_WINDOW(window));
316         gtk_tooltips_set_tip(GTK_TOOLTIPS(tooltips), info_btn,
317                         _("Show information on configuring actions"), NULL);
318
319         cond_hbox = gtk_hbox_new(FALSE, 8);
320         gtk_widget_show(cond_hbox);
321         gtk_box_pack_start(GTK_BOX(vbox1), cond_hbox, TRUE, TRUE, 0);
322
323         cond_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
324         gtk_widget_show(cond_scrolledwin);
325         gtk_widget_set_size_request(cond_scrolledwin, -1, 150);
326         gtk_box_pack_start(GTK_BOX(cond_hbox), cond_scrolledwin,
327                            TRUE, TRUE, 0);
328         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (cond_scrolledwin),
329                                        GTK_POLICY_AUTOMATIC,
330                                        GTK_POLICY_AUTOMATIC);
331
332         cond_list_view = prefs_actions_list_view_create();                                     
333         gtk_widget_show(cond_list_view);
334         gtk_container_add(GTK_CONTAINER (cond_scrolledwin), cond_list_view);
335
336         btn_vbox = gtk_vbox_new(FALSE, 8);
337         gtk_widget_show(btn_vbox);
338         gtk_box_pack_start(GTK_BOX(cond_hbox), btn_vbox, FALSE, FALSE, 0);
339
340         up_btn = gtk_button_new_from_stock(GTK_STOCK_GO_UP);
341         gtk_widget_show(up_btn);
342         gtk_box_pack_start(GTK_BOX(btn_vbox), up_btn, FALSE, FALSE, 0);
343         g_signal_connect(G_OBJECT(up_btn), "clicked",
344                          G_CALLBACK(prefs_actions_up), NULL);
345         gtk_tooltips_set_tip(GTK_TOOLTIPS(tooltips), up_btn,
346                         _("Move the selected action up"), NULL);
347
348         down_btn = gtk_button_new_from_stock(GTK_STOCK_GO_DOWN);
349         gtk_widget_show(down_btn);
350         gtk_box_pack_start(GTK_BOX(btn_vbox), down_btn, FALSE, FALSE, 0);
351         g_signal_connect(G_OBJECT(down_btn), "clicked",
352                          G_CALLBACK(prefs_actions_down), NULL);
353         gtk_tooltips_set_tip(GTK_TOOLTIPS(tooltips), down_btn,
354                         _("Move selected action down"), NULL);
355
356         if (!geometry.min_height) {
357                 geometry.min_width = 486;
358                 geometry.min_height = 322;
359         }
360
361         gtk_window_set_geometry_hints(GTK_WINDOW(window), NULL, &geometry,
362                                       GDK_HINT_MIN_SIZE);
363         gtk_widget_set_size_request(window, prefs_common.actionswin_width,
364                                     prefs_common.actionswin_height);
365
366         gtk_widget_show(window);
367
368         actions.window = window;
369         actions.ok_btn = ok_btn;
370
371         actions.name_entry = name_entry;
372         actions.cmd_entry  = cmd_entry;
373
374         actions.actions_list_view = cond_list_view;
375 }
376
377 static void prefs_actions_reset_dialog(void)
378 {
379         gtk_entry_set_text(GTK_ENTRY(actions.name_entry), "");
380         gtk_entry_set_text(GTK_ENTRY(actions.cmd_entry), "");
381 }
382
383 void prefs_actions_read_config(void)
384 {
385         gchar *rcpath;
386         FILE *fp;
387         gchar buf[PREFSBUFSIZE];
388         gchar *act;
389
390         debug_print("Reading actions configurations...\n");
391
392         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACTIONS_RC, NULL);
393         if ((fp = g_fopen(rcpath, "rb")) == NULL) {
394                 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
395                 g_free(rcpath);
396                 return;
397         }
398         g_free(rcpath);
399
400         while (prefs_common.actions_list != NULL) {
401                 act = (gchar *)prefs_common.actions_list->data;
402                 prefs_common.actions_list =
403                         g_slist_remove(prefs_common.actions_list, act);
404                 g_free(act);
405         }
406
407         while (fgets(buf, sizeof(buf), fp) != NULL) {
408                 const gchar *src_codeset = conv_get_locale_charset_str();
409                 const gchar *dest_codeset = CS_UTF_8;
410                 gchar *tmp;
411
412                 tmp = conv_codeset_strdup(buf, src_codeset, dest_codeset);
413                 if (!tmp) {
414                         g_warning("Failed to convert character set of action configuration\n");
415                         tmp = g_strdup(buf);
416                 }
417
418                 g_strchomp(tmp);
419                 act = strstr(tmp, ": ");
420                 if (act && act[2] && 
421                     action_get_type(&act[2]) != ACTION_ERROR)
422                         prefs_common.actions_list =
423                                 g_slist_append(prefs_common.actions_list,
424                                                tmp);
425                 else
426                         g_free(tmp);
427         }
428         fclose(fp);
429 }
430
431 void prefs_actions_write_config(void)
432 {
433         gchar *rcpath;
434         PrefFile *pfile;
435         GSList *cur;
436
437         debug_print("Writing actions configuration...\n");
438
439         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACTIONS_RC, NULL);
440         if ((pfile= prefs_write_open(rcpath)) == NULL) {
441                 g_warning("Failed to write configuration to file\n");
442                 g_free(rcpath);
443                 return;
444         }
445
446         for (cur = prefs_common.actions_list; cur != NULL; cur = cur->next) {
447                 gchar *tmp = (gchar *)cur->data;
448                 const gchar *src_codeset = CS_UTF_8;
449                 const gchar *dest_codeset = conv_get_locale_charset_str();
450                 gchar *act;
451
452                 act = conv_codeset_strdup(tmp, src_codeset, dest_codeset);
453                 if (!act) {
454                         g_warning("Failed to convert character set of action configuration\n");
455                         act = g_strdup(act);
456                 }
457
458                 if (fputs(act, pfile->fp) == EOF ||
459                     fputc('\n', pfile->fp) == EOF) {
460                         FILE_OP_ERROR(rcpath, "fputs || fputc");
461                         prefs_file_close_revert(pfile);
462                         g_free(rcpath);
463                         return;
464                 }
465                 g_free(act);
466         }
467         
468         g_free(rcpath);
469
470         if (prefs_file_close(pfile) < 0) {
471                 g_warning("failed to write configuration to file\n");
472                 return;
473         }
474 }
475
476 static void prefs_actions_clear_list(GtkListStore *list_store)
477 {
478         gtk_list_store_clear(list_store);
479
480         prefs_actions_list_view_insert_action(actions.actions_list_view,
481                                               -1, _("(New)"), FALSE);
482 }
483
484 static void prefs_actions_set_dialog(void)
485 {
486         GtkListStore *store;
487         GSList *cur;
488         GtkTreeSelection *selection;
489         GtkTreeIter iter;
490
491         store = GTK_LIST_STORE(gtk_tree_view_get_model
492                                 (GTK_TREE_VIEW(actions.actions_list_view)));
493
494         prefs_actions_clear_list(store);        
495
496         for (cur = prefs_common.actions_list; cur != NULL; cur = cur->next) {
497                 gchar *action = (gchar *) cur->data;
498                 
499                 prefs_actions_list_view_insert_action(actions.actions_list_view,
500                                                       -1, action, TRUE);
501         }
502
503         /* select first entry */
504         selection = gtk_tree_view_get_selection
505                 (GTK_TREE_VIEW(actions.actions_list_view));
506         if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store),
507                                           &iter))
508                 gtk_tree_selection_select_iter(selection, &iter);
509 }
510
511 static void prefs_actions_set_list(void)
512 {
513         GtkTreeIter iter;
514         GtkListStore *store;
515         
516         g_slist_free(prefs_common.actions_list);
517         prefs_common.actions_list = NULL;
518
519         store = GTK_LIST_STORE(gtk_tree_view_get_model
520                                 (GTK_TREE_VIEW(actions.actions_list_view)));
521
522         if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter)) {
523                 do {
524                         gchar *action;
525                         gboolean is_valid;
526
527                         gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
528                                            PREFS_ACTIONS_DATA, &action,
529                                            PREFS_ACTIONS_VALID, &is_valid,
530                                            -1);
531                         
532                         if (is_valid) 
533                                 prefs_common.actions_list = 
534                                         g_slist_append(prefs_common.actions_list,
535                                                        action);
536
537                 } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(store),
538                                                   &iter));
539         }
540 }
541
542 #define GET_ENTRY(entry) \
543         entry_text = gtk_entry_get_text(GTK_ENTRY(entry))
544
545 static gint prefs_actions_clist_set_row(gint row)
546 {
547         const gchar *entry_text;
548         gint len;
549         gchar action[PREFSBUFSIZE];
550         gchar *new_action;
551         GtkListStore *store;
552
553         store = GTK_LIST_STORE(gtk_tree_view_get_model
554                                 (GTK_TREE_VIEW(actions.actions_list_view)));
555
556         GET_ENTRY(actions.name_entry);
557         if (entry_text[0] == '\0') {
558                 alertpanel_error(_("Menu name is not set."));
559                 return -1;
560         }
561
562         if (entry_text[0] == '/') {
563                 alertpanel_error(_("A leading '/' is not allowed in the menu name."));
564                 return -1;
565         }
566
567         if (strchr(entry_text, ':')) {
568                 alertpanel_error(_("Colon ':' is not allowed in the menu name."));
569                 return -1;
570         }
571
572         strncpy(action, entry_text, PREFSBUFSIZE - 1);
573
574         while (strstr(action, "//")) {
575                 char *to_move = strstr(action, "//")+1;
576                 char *where = strstr(action, "//");
577                 int old_len = strlen(action);
578                 memmove(where, to_move, strlen(to_move));
579                 action[old_len-1] = '\0';
580         }
581         
582         g_strstrip(action);
583
584         /* Keep space for the ': ' delimiter */
585         len = strlen(action) + 2;
586         if (len >= PREFSBUFSIZE - 1) {
587                 alertpanel_error(_("Menu name is too long."));
588                 return -1;
589         }
590
591         strcat(action, ": ");
592
593         GET_ENTRY(actions.cmd_entry);
594
595         if (entry_text[0] == '\0') {
596                 alertpanel_error(_("Command line not set."));
597                 return -1;
598         }
599
600         if (len + strlen(entry_text) >= PREFSBUFSIZE - 1) {
601                 alertpanel_error(_("Menu name and command are too long."));
602                 return -1;
603         }
604
605         if (action_get_type(entry_text) == ACTION_ERROR) {
606                 gchar *message;
607                 message = g_markup_printf_escaped(_("The command\n%s\nhas a syntax error."),
608                                                 entry_text);
609                 alertpanel_error(message);
610                 g_free(message);
611                 return -1;
612         }
613
614         strcat(action, entry_text);
615
616         new_action = g_strdup(action);  
617         prefs_actions_list_view_insert_action(actions.actions_list_view,
618                                               row, new_action, TRUE);
619                                                 
620         prefs_actions_set_list();
621
622         return 0;
623 }
624
625 /* callback functions */
626
627 static void prefs_actions_register_cb(GtkWidget *w, gpointer data)
628 {
629         prefs_actions_clist_set_row(-1);
630
631         modified = FALSE;
632         modified_list = TRUE;
633 }
634
635 static void prefs_actions_substitute_cb(GtkWidget *w, gpointer data)
636 {
637         gint row;
638
639         row = gtkut_list_view_get_selected_row(actions.actions_list_view);
640         if (row <= 0)
641                 return;
642
643         prefs_actions_clist_set_row(row);
644
645         modified = FALSE;
646         modified_list = TRUE;
647 }
648
649 static void prefs_actions_delete_cb(GtkWidget *w, gpointer data)
650 {
651         GtkTreeIter sel;
652         GtkTreeModel *model;
653         gchar *action;
654         gint row;
655
656         row = gtkut_list_view_get_selected_row(actions.actions_list_view);
657         if (row <= 0) 
658                 return; 
659
660         if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection
661                                 (GTK_TREE_VIEW(actions.actions_list_view)),
662                                 &model, &sel))
663                 return;                         
664
665         if (alertpanel(_("Delete action"),
666                        _("Do you really want to delete this action?"),
667                        GTK_STOCK_CANCEL, GTK_STOCK_DELETE, NULL) != G_ALERTALTERNATE)
668                 return;
669
670         /* XXX: Here's the reason why we need to store the original 
671          * pointer: we search the slist for it. */
672         gtk_tree_model_get(model, &sel,
673                            PREFS_ACTIONS_DATA, &action,
674                            -1);
675         gtk_list_store_remove(GTK_LIST_STORE(model), &sel);
676
677         prefs_common.actions_list = g_slist_remove(prefs_common.actions_list,
678                                                    action);
679         modified_list = TRUE;
680 }
681
682 static void prefs_actions_delete_all_cb(GtkWidget *w, gpointer data)
683 {
684         GtkListStore *list_store;
685
686         if (alertpanel(_("Delete all actions"),
687                           _("Do you really want to delete all the actions?"),
688                           GTK_STOCK_CANCEL, "+"GTK_STOCK_DELETE, NULL) == G_ALERTDEFAULT)
689            return;
690
691         list_store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(actions.actions_list_view)));
692         prefs_actions_clear_list(list_store);
693         modified = FALSE;
694
695         prefs_actions_reset_dialog();
696         modified_list = TRUE;
697 }
698
699 static void prefs_actions_clear_cb(void)
700 {
701         gint row;
702
703         prefs_actions_reset_dialog();
704         row = gtkut_list_view_get_selected_row(actions.actions_list_view);
705         if (row < 1)
706                 modified = FALSE;
707         else
708                 modified = TRUE;
709 }
710
711 static void prefs_actions_duplicate_cb(GtkWidget *w, gpointer data)
712 {
713         gint row;
714         
715         row = gtkut_list_view_get_selected_row(actions.actions_list_view);
716         if (row <= 0)
717                 return;
718
719         modified_list = !prefs_actions_clist_set_row(-row-2);
720 }
721
722 static void prefs_actions_up(GtkWidget *w, gpointer data)
723 {
724         GtkTreePath *prev, *sel, *try;
725         GtkTreeIter isel;
726         GtkListStore *store = NULL;
727         GtkTreeModel *model = NULL;
728         GtkTreeIter iprev;
729         
730         if (!gtk_tree_selection_get_selected
731                 (gtk_tree_view_get_selection
732                         (GTK_TREE_VIEW(actions.actions_list_view)),
733                  &model,        
734                  &isel))
735                 return;
736         store = (GtkListStore *)model;
737         sel = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &isel);
738         if (!sel)
739                 return;
740         
741         /* no move if we're at row 0 or 1, looks phony, but other
742          * solutions are more convoluted... */
743         try = gtk_tree_path_copy(sel);
744         if (!gtk_tree_path_prev(try) || !gtk_tree_path_prev(try)) {
745                 gtk_tree_path_free(try);
746                 gtk_tree_path_free(sel);
747                 return;
748         }
749         gtk_tree_path_free(try);
750
751         prev = gtk_tree_path_copy(sel);         
752         if (!gtk_tree_path_prev(prev)) {
753                 gtk_tree_path_free(prev);
754                 gtk_tree_path_free(sel);
755                 return;
756         }
757
758         gtk_tree_model_get_iter(GTK_TREE_MODEL(store),
759                                 &iprev, prev);
760         gtk_tree_path_free(sel);
761         gtk_tree_path_free(prev);
762
763         gtk_list_store_swap(store, &iprev, &isel);
764         prefs_actions_set_list();
765         modified_list = TRUE;
766 }
767
768 static void prefs_actions_down(GtkWidget *w, gpointer data)
769 {
770         GtkListStore *store = NULL;
771         GtkTreeModel *model = NULL;
772         GtkTreeIter next, sel;
773         GtkTreePath *try;
774         
775         if (!gtk_tree_selection_get_selected
776                 (gtk_tree_view_get_selection
777                         (GTK_TREE_VIEW(actions.actions_list_view)),
778                  &model,
779                  &sel))
780                 return;
781         store = (GtkListStore *)model;
782         try = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &sel);
783         if (!try) 
784                 return;
785
786         /* no move when we're at row 0 */
787         if (!gtk_tree_path_prev(try)) {
788                 gtk_tree_path_free(try);
789                 return;
790         }
791         gtk_tree_path_free(try);
792
793         next = sel;
794         if (!gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &next)) 
795                 return;
796
797         gtk_list_store_swap(store, &next, &sel);
798         prefs_actions_set_list();
799         modified_list = TRUE;
800 }
801
802 static gint prefs_actions_deleted(GtkWidget *widget, GdkEventAny *event,
803                                   gpointer *data)
804 {
805         prefs_actions_cancel(widget, data);
806         return TRUE;
807 }
808
809 static gboolean prefs_actions_key_pressed(GtkWidget *widget, GdkEventKey *event,
810                                           gpointer data)
811 {
812         if (event && event->keyval == GDK_Escape)
813                 prefs_actions_cancel(widget, data);
814         else {
815                 GtkWidget *focused = gtkut_get_focused_child(
816                                         GTK_CONTAINER(widget));
817                 if (focused && GTK_IS_EDITABLE(focused)) {
818                         modified = TRUE;
819                 }
820         }
821         return FALSE;
822 }
823
824 static void prefs_actions_cancel(GtkWidget *w, gpointer data)
825 {
826         GtkListStore *store;
827
828         if (modified && alertpanel(_("Entry not saved"),
829                                  _("The entry was not saved. Close anyway?"),
830                                  GTK_STOCK_CLOSE, _("+_Continue editing"),
831                                  NULL) != G_ALERTDEFAULT) {
832                 return;
833         } else if (modified_list && alertpanel(_("Actions list not saved"),
834                                  _("The actions list has been modified. Close anyway?"),
835                                  GTK_STOCK_CLOSE, _("+_Continue editing"), 
836                                  NULL) != G_ALERTDEFAULT) {
837                 return;
838         }
839         modified = FALSE;
840         modified_list = FALSE;
841         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW
842                                 (actions.actions_list_view)));
843         gtk_list_store_clear(store);
844         prefs_actions_read_config();
845         gtk_widget_hide(actions.window);
846         inc_unlock();
847 }
848
849 static void prefs_actions_ok(GtkWidget *widget, gpointer data)
850 {
851         MainWindow *mainwin = (MainWindow *) data;
852         GList *list;
853         GList *iter;
854         MessageView *msgview;
855         Compose *compose;
856         GtkListStore *store;
857
858         if (modified && alertpanel(_("Entry not saved"),
859                                  _("The entry was not saved. Close anyway?"),
860                                  GTK_STOCK_CLOSE, _("+_Continue editing"),
861                                  NULL) != G_ALERTDEFAULT) {
862                 return;
863         } 
864         modified = FALSE;
865         modified_list = FALSE;
866         prefs_actions_set_list();
867         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW
868                                 (actions.actions_list_view)));
869         gtk_list_store_clear(store);
870         prefs_actions_write_config();
871
872         /* Update mainwindow actions menu */
873         main_window_update_actions_menu(mainwin);
874
875         /* Update separated message view actions menu */
876         list = messageview_get_msgview_list();
877         for (iter = list; iter; iter = iter->next) {
878                 msgview = (MessageView *) iter->data;
879                 messageview_update_actions_menu(msgview);
880         }
881
882         /* Update compose windows actions menu */
883         list = compose_get_compose_list();
884         for (iter = list; iter; iter = iter->next) {
885                 compose = (Compose *) iter->data;
886                 compose_update_actions_menu(compose);
887         }
888
889         gtk_widget_hide(actions.window);
890         inc_unlock();
891 }
892
893 /*
894  * Strings describing action format strings
895  * 
896  * When adding new lines, remember to put one string for each line
897  */
898 static gchar *actions_desc_strings[] = {
899         N_("<span weight=\"bold\" underline=\"single\">Menu name:</span>"), NULL,
900         N_("Use / in menu name to make submenus."), NULL,
901         "", NULL,
902         N_("<span weight=\"bold\" underline=\"single\">Command line:</span>"), NULL,
903         N_("<span weight=\"bold\">Begin with:</span>"), NULL,
904         "     |",   N_("to send message body or selection to command's standard input"),
905         "     &gt;",   N_("to send user provided text to command's standard input"),
906         "     *",   N_("to send user provided hidden text to command's standard input"),
907         N_("<span weight=\"bold\">End with:</span>"), NULL,
908         "     |",   N_("to replace message body or selection with command's standard output"),
909         "     &gt;",   N_("to insert command's standard output without replacing old text"),
910         "     &amp;",   N_("to run command asynchronously"),
911         N_("<span weight=\"bold\">Use:</span>"), NULL, 
912         "     %f",  N_("for the file of the selected message in RFC822/2822 format "),
913         "     %F",  N_("for the list of the files of the selected messages in RFC822/2822 format"),
914         "     %p",  N_("for the file of the selected decoded message MIME part"),
915         "     %u",  N_("for a user provided argument"),
916         "     %h",  N_("for a user provided hidden argument (e.g. password)"),
917         "     %s",  N_("for the text selection"),
918         "  %as{}",  N_("apply filtering actions between {} to selected messages"),
919         "     %%",  N_("for a literal %"),
920         NULL, NULL
921 };
922
923
924 static DescriptionWindow actions_desc_win = { 
925         NULL,
926         NULL,
927         2,
928         N_("Actions"),
929         N_("The Actions feature is a way for the user to launch "
930            "external commands to process a complete message file or just "
931            "one of its parts."),
932         actions_desc_strings
933 };
934
935
936 static void prefs_actions_info_cb(GtkWidget *w, GtkWidget *window)
937 {
938         actions_desc_win.parent = window;
939         description_window_create(&actions_desc_win);
940 }
941
942 static GtkListStore* prefs_actions_create_data_store(void)
943 {
944         return gtk_list_store_new(N_PREFS_ACTIONS_COLUMNS,
945                                   G_TYPE_STRING,        
946                                   G_TYPE_POINTER,
947                                   G_TYPE_BOOLEAN,
948                                   -1);
949 }
950
951 static void prefs_actions_list_view_insert_action(GtkWidget *list_view,
952                                                   gint row,
953                                                   gchar *action,
954                                                   gboolean is_valid) 
955 {
956         GtkTreeIter iter;
957         GtkTreeIter sibling;
958         GtkListStore *list_store = GTK_LIST_STORE(gtk_tree_view_get_model
959                                         (GTK_TREE_VIEW(list_view)));
960
961 /*      row -1 to add a new rule to store,
962         row >=0 to change an existing row
963         row <-1 insert a new row after (-row-2)
964 */
965         if (row >= 0 ) {
966                 /* modify the existing */
967                 if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store),
968                                                    &iter, NULL, row))
969                         row = -1;
970         } else if (row < -1 ) {
971                 if (!gtk_tree_model_iter_nth_child(GTK_TREE_MODEL(list_store),
972                                                    &sibling, NULL, -row-2))
973                         row = -1;               
974         }
975
976         if (row == -1 ) {
977                 /* append new */
978                 gtk_list_store_append(list_store, &iter);
979                 gtk_list_store_set(list_store, &iter,
980                                    PREFS_ACTIONS_STRING, action,
981                                    PREFS_ACTIONS_DATA, action,
982                                    PREFS_ACTIONS_VALID,  is_valid,
983                                    -1);
984         } else if (row < -1) {
985                 /* duplicate */
986                 gtk_list_store_insert_after(list_store, &iter, &sibling);
987                 gtk_list_store_set(list_store, &iter,
988                                    PREFS_ACTIONS_STRING, action,
989                                    PREFS_ACTIONS_DATA, action,
990                                    PREFS_ACTIONS_VALID,  is_valid,
991                                    -1);
992         } else {
993                 /* change existing */
994                 gchar *old_action;
995
996                 gtk_tree_model_get(GTK_TREE_MODEL(list_store), &iter,
997                                    PREFS_ACTIONS_DATA, &old_action,
998                                    -1);
999                 g_free(old_action);                             
1000
1001                 gtk_list_store_set(list_store, &iter,
1002                                    PREFS_ACTIONS_STRING, action,
1003                                    PREFS_ACTIONS_DATA, action,
1004                                    -1);
1005         }
1006 }
1007
1008 static GtkItemFactory *prefs_actions_popup_factory = NULL;
1009 static GtkWidget *prefs_actions_popup_menu = NULL;
1010
1011 static GtkItemFactoryEntry prefs_actions_popup_entries[] =
1012 {
1013    {N_("/_Delete"),             NULL, prefs_actions_delete_cb, 0, NULL, NULL},
1014    {N_("/Delete _all"), NULL, prefs_actions_delete_all_cb, 0, NULL, NULL},
1015    {N_("/D_uplicate"),  NULL, prefs_actions_duplicate_cb, 0, NULL, NULL},
1016 };
1017
1018 static gint prefs_actions_list_btn_pressed(GtkWidget *widget, GdkEventButton *event,
1019                                    GtkTreeView *list_view)
1020 {
1021    if (event) {
1022            /* left- or right-button click */
1023            if (event->button == 1 || event->button == 3) {
1024                    GtkTreePath *path = NULL;
1025                    if (gtk_tree_view_get_path_at_pos( list_view, event->x, event->y,
1026                                                            &path, NULL, NULL, NULL)) {
1027                            prefs_actions_select_row(list_view, path);
1028                    }
1029                    if (path)
1030                            gtk_tree_path_free(path);
1031            }
1032
1033            /* right-button click */
1034            if (event->button == 3) {
1035                    GtkTreeModel *model = gtk_tree_view_get_model(list_view);
1036                    GtkTreeIter iter;
1037                    gboolean non_empty;
1038                    gint row;
1039
1040                    if (!prefs_actions_popup_menu) {
1041                            gint n_entries = sizeof(prefs_actions_popup_entries) /
1042                                            sizeof(prefs_actions_popup_entries[0]);
1043                            prefs_actions_popup_menu = menu_create_items(prefs_actions_popup_entries,
1044                                                                  n_entries, "<PrefsActionsPopupMenu>",
1045                                                                  &prefs_actions_popup_factory, list_view);
1046                    }
1047
1048                    /* grey out some popup menu items if there is no selected row */
1049                    row = gtkut_list_view_get_selected_row(GTK_WIDGET(list_view));
1050                    menu_set_sensitive(prefs_actions_popup_factory, "/Delete", (row > 0));
1051                    menu_set_sensitive(prefs_actions_popup_factory, "/Duplicate", (row > 0));
1052
1053                    /* grey out seom popup menu items if there is no row
1054                           (not counting the (New) one at row 0) */
1055                    non_empty = gtk_tree_model_get_iter_first(model, &iter);
1056                    if (non_empty)
1057                            non_empty = gtk_tree_model_iter_next(model, &iter);
1058                    menu_set_sensitive(prefs_actions_popup_factory, "/Delete all", non_empty);
1059
1060                    gtk_menu_popup(GTK_MENU(prefs_actions_popup_menu), 
1061                                           NULL, NULL, NULL, NULL, 
1062                                           event->button, event->time);
1063            }
1064    }
1065    return FALSE;
1066 }
1067
1068 static gboolean prefs_actions_list_popup_menu(GtkWidget *widget, gpointer data)
1069 {
1070    GtkTreeView *list_view = (GtkTreeView *)data;
1071    GdkEventButton event;
1072    
1073    event.button = 3;
1074    event.time = gtk_get_current_event_time();
1075    
1076    prefs_actions_list_btn_pressed(NULL, &event, list_view);
1077
1078    return TRUE;
1079 }
1080
1081 static GtkWidget *prefs_actions_list_view_create(void)
1082 {
1083         GtkTreeView *list_view;
1084         GtkTreeSelection *selector;
1085         GtkTreeModel *model;
1086
1087         model = GTK_TREE_MODEL(prefs_actions_create_data_store());
1088         list_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(model));
1089         g_object_unref(model);  
1090         
1091 #ifndef MAEMO
1092         g_signal_connect(G_OBJECT(list_view), "popup-menu",
1093                          G_CALLBACK(prefs_actions_list_popup_menu), list_view);
1094 #else
1095         gtk_widget_tap_and_hold_setup(GTK_WIDGET(list_view), NULL, NULL,
1096                         GTK_TAP_AND_HOLD_NONE | GTK_TAP_AND_HOLD_NO_INTERNALS);
1097         g_signal_connect(G_OBJECT(list_view), "tap-and-hold",
1098                          G_CALLBACK(prefs_actions_list_popup_menu), list_view);
1099 #endif
1100         g_signal_connect(G_OBJECT(list_view), "button-press-event",
1101                         G_CALLBACK(prefs_actions_list_btn_pressed), list_view);
1102
1103         gtk_tree_view_set_rules_hint(list_view, prefs_common.use_stripes_everywhere);
1104         gtk_tree_view_set_reorderable(list_view, TRUE);
1105
1106         selector = gtk_tree_view_get_selection(list_view);
1107         gtk_tree_selection_set_mode(selector, GTK_SELECTION_BROWSE);
1108
1109         /* create the columns */
1110         prefs_actions_create_list_view_columns(GTK_WIDGET(list_view));
1111
1112         return GTK_WIDGET(list_view);
1113 }
1114
1115 static void prefs_actions_create_list_view_columns(GtkWidget *list_view)
1116 {
1117         GtkTreeViewColumn *column;
1118         GtkCellRenderer *renderer;
1119
1120         renderer = gtk_cell_renderer_text_new();
1121         column = gtk_tree_view_column_new_with_attributes
1122                 (_("Current actions"),
1123                  renderer,
1124                  "text", PREFS_ACTIONS_STRING,
1125                  NULL);
1126         gtk_tree_view_append_column(GTK_TREE_VIEW(list_view), column);          
1127 }
1128
1129 #define ENTRY_SET_TEXT(entry, str) \
1130         gtk_entry_set_text(GTK_ENTRY(entry), str ? str : "")
1131
1132 static void prefs_actions_select_row(GtkTreeView *list_view, GtkTreePath *path)
1133 {
1134         GtkTreeModel *model = gtk_tree_view_get_model(list_view);
1135         GtkTreeSelection *selection;
1136         gchar *action;
1137         gchar *cmd;
1138         gchar buf[PREFSBUFSIZE];
1139         GtkTreeIter iter;
1140         gboolean is_valid;
1141
1142         if (!model || !path || !gtk_tree_model_get_iter(model, &iter, path))
1143                 return;
1144
1145         /* select row */
1146         selection = gtk_tree_view_get_selection(list_view);
1147         gtk_tree_selection_select_path(selection, path);
1148
1149         gtk_tree_model_get(model, &iter, 
1150                            PREFS_ACTIONS_VALID,  &is_valid,
1151                            PREFS_ACTIONS_DATA, &action,
1152                            -1);
1153         if (!is_valid) {
1154                 prefs_actions_reset_dialog();
1155                 return;
1156         }
1157         
1158         strncpy(buf, action, PREFSBUFSIZE - 1);
1159         buf[PREFSBUFSIZE - 1] = '\0';
1160         cmd = strstr(buf, ": ");
1161
1162         if (cmd && cmd[2])
1163                 ENTRY_SET_TEXT(actions.cmd_entry, &cmd[2]);
1164         else
1165                 return;
1166
1167         *cmd = '\0';
1168         ENTRY_SET_TEXT(actions.name_entry, buf);
1169
1170         return;
1171 }