sync with sylpheed 0.7.0cvs39
[claws.git] / src / prefs_actions.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2002 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 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <gtk/gtk.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <gdk/gdkx.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <errno.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36
37 #include "intl.h"
38 #include "prefs.h"
39 #include "inc.h"
40 #include "utils.h"
41 #include "gtkutils.h"
42 #include "manage_window.h"
43 #include "mainwindow.h"
44 #include "prefs_common.h"
45 #include "alertpanel.h"
46 #include "prefs_actions.h"
47 #include "compose.h"
48 #include "procmsg.h"
49 #include "gtkstext.h"
50
51 static struct Actions {
52         GtkWidget *window;
53
54         GtkWidget *ok_btn;
55
56         GtkWidget *name_entry;
57         GtkWidget *cmd_entry;
58
59         GtkWidget *actions_clist;
60 } actions;
61
62 /* widget creating functions */
63 static void prefs_actions_create        (MainWindow *mainwin);
64 static void prefs_actions_set_dialog    (void);
65 static gint prefs_actions_clist_set_row (gint row);
66
67 /* callback functions */
68 static void prefs_actions_register_cb   (GtkWidget *w, gpointer data);
69 static void prefs_actions_substitute_cb (GtkWidget *w, gpointer data);
70 static void prefs_actions_delete_cb     (GtkWidget *w, gpointer data);
71 static void prefs_actions_up            (GtkWidget *w, gpointer data);
72 static void prefs_actions_down          (GtkWidget *w, gpointer data);
73 static void prefs_actions_select        (GtkCList       *clist,
74                                          gint            row,
75                                          gint            column,
76                                          GdkEvent       *event);
77 static void prefs_actions_row_move      (GtkCList       *clist,
78                                          gint            source_row,
79                                          gint            dest_row);
80 static gint prefs_actions_deleted       (GtkWidget      *widget,
81                                          GdkEventAny    *event,
82                                          gpointer       *data);
83 static void prefs_actions_key_pressed   (GtkWidget      *widget,
84                                          GdkEventKey    *event,
85                                          gpointer        data);
86 static void prefs_actions_cancel        (GtkWidget *w, gpointer data);
87 static void prefs_actions_ok            (GtkWidget *w, gpointer data);
88 static void update_actions_menu         (GtkItemFactory *ifactory,
89                                          gchar *branch_path,
90                                          gpointer callback,
91                                          gpointer data);
92 static void mainwin_actions_execute_cb  (MainWindow     *mainwin,
93                                          guint           action_nb,
94                                          GtkWidget      *widget);
95 static void compose_actions_execute_cb  (Compose        *compose,
96                                          guint           action_nb,
97                                          GtkWidget      *widget);
98 static gboolean actions_check           (gchar *action);
99
100 static void pipe_command                (gchar *action,
101                                          GtkEditable *editable);
102
103 static void message_command             (gchar *action, 
104                                          SummaryView *summaryview);
105 static gint message_command_execute     (gchar *action, 
106                                          MsgInfo *msginfo);
107
108 void prefs_actions_open(MainWindow *mainwin)
109 {
110         if (prefs_rc_is_readonly(ACTIONS_RC))
111                 return;
112         inc_lock();
113
114         if (!actions.window)
115                 prefs_actions_create(mainwin);
116         
117         manage_window_set_transient(GTK_WINDOW(actions.window));
118         gtk_widget_grab_focus(actions.ok_btn);
119
120         prefs_actions_set_dialog();
121
122         gtk_widget_show(actions.window);
123 }
124
125 static void prefs_actions_create(MainWindow *mainwin)
126 {
127         GtkWidget *window;
128         GtkWidget *vbox;
129         GtkWidget *ok_btn;
130         GtkWidget *cancel_btn;
131         GtkWidget *confirm_area;
132
133         GtkWidget *vbox1;
134         GtkWidget *hbox;
135         GtkWidget *name_label;
136         GtkWidget *name_entry;
137         GtkWidget *cmd_label;
138         GtkWidget *cmd_entry;
139         GtkWidget *help_label;
140
141         GtkWidget *reg_hbox;
142         GtkWidget *btn_hbox;
143         GtkWidget *arrow;
144         GtkWidget *reg_btn;
145         GtkWidget *subst_btn;
146         GtkWidget *del_btn;
147
148         GtkWidget *cond_hbox;
149         GtkWidget *cond_scrolledwin;
150         GtkWidget *cond_clist;
151
152         GtkWidget *btn_vbox;
153         GtkWidget *up_btn;
154         GtkWidget *down_btn;
155         
156         gchar *title[1];
157
158         debug_print(_("Creating actions setting window...\n"));
159
160         window = gtk_window_new (GTK_WINDOW_DIALOG);
161                 
162         gtk_container_set_border_width (GTK_CONTAINER (window), 8);
163         gtk_window_position (GTK_WINDOW (window), GTK_WIN_POS_CENTER);
164         gtk_window_set_modal (GTK_WINDOW (window), TRUE);
165         gtk_window_set_policy (GTK_WINDOW (window), FALSE, TRUE, FALSE);
166
167         vbox = gtk_vbox_new (FALSE, 6);
168         gtk_widget_show (vbox);
169         gtk_container_add (GTK_CONTAINER (window), vbox);
170
171         gtkut_button_set_create(&confirm_area, &ok_btn, _("OK"),
172                                 &cancel_btn, _("Cancel"), NULL, NULL);
173         gtk_widget_show (confirm_area);
174         gtk_box_pack_end (GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
175         gtk_widget_grab_default (ok_btn);
176                                          
177         gtk_signal_connect (GTK_OBJECT(window), "delete_event",
178                             GTK_SIGNAL_FUNC(prefs_actions_deleted), NULL);
179         gtk_signal_connect (GTK_OBJECT(window), "key_press_event",
180                             GTK_SIGNAL_FUNC(prefs_actions_key_pressed), NULL);
181         gtk_signal_connect (GTK_OBJECT(window), "focus_in_event",
182                             GTK_SIGNAL_FUNC(manage_window_focus_in), NULL);
183         gtk_signal_connect (GTK_OBJECT(window), "focus_out_event",
184                             GTK_SIGNAL_FUNC(manage_window_focus_out), NULL);
185         gtk_signal_connect (GTK_OBJECT(ok_btn), "clicked",
186                             GTK_SIGNAL_FUNC(prefs_actions_ok), mainwin);
187         gtk_signal_connect (GTK_OBJECT(cancel_btn), "clicked",
188                             GTK_SIGNAL_FUNC(prefs_actions_cancel), NULL);
189
190         vbox1 = gtk_vbox_new (FALSE, VSPACING);
191         gtk_widget_show (vbox1);
192         gtk_box_pack_start (GTK_BOX (vbox), vbox1, TRUE, TRUE, 0);
193         gtk_container_set_border_width (GTK_CONTAINER (vbox1), 2);
194
195         hbox = gtk_hbox_new (FALSE, 0);
196         gtk_widget_show (hbox);
197         gtk_box_pack_start (GTK_BOX(vbox1), hbox, TRUE, TRUE, 0);
198         
199         name_label = gtk_label_new(_("Menu name: "));
200         gtk_widget_show(name_label);
201         gtk_box_pack_start (GTK_BOX (hbox), name_label, FALSE, FALSE, 0);
202
203         name_entry = gtk_entry_new();
204         gtk_widget_show(name_entry);
205         gtk_box_pack_start (GTK_BOX (hbox), name_entry, TRUE, TRUE, 0);
206
207         help_label = gtk_label_new (_("Use '/' in menu name to make submenus."));
208         gtk_misc_set_alignment(GTK_MISC(help_label), 0, 0.5);
209         gtk_widget_show (help_label);
210         gtk_box_pack_start (GTK_BOX (vbox1), help_label, TRUE, TRUE, 0);
211         
212         hbox = gtk_hbox_new (FALSE, 0);
213         gtk_widget_show (hbox);
214         gtk_box_pack_start (GTK_BOX(vbox1), hbox, TRUE, TRUE, 0);
215                  
216         cmd_label = gtk_label_new(_("Command line: "));
217         gtk_widget_show(cmd_label);
218         gtk_box_pack_start (GTK_BOX (hbox), cmd_label, FALSE, FALSE, 0);
219
220         cmd_entry = gtk_entry_new();
221         gtk_widget_show(cmd_entry);
222         gtk_box_pack_start (GTK_BOX (hbox), cmd_entry, TRUE, TRUE, 0);
223
224         help_label = gtk_label_new (_("Prepend command with '|' to pipe selection through it.\nUse '%f' to apply the command to the message file."));
225         gtk_misc_set_alignment(GTK_MISC(help_label), 0, 0.5);
226         gtk_label_set_justify (GTK_LABEL(help_label), GTK_JUSTIFY_LEFT);
227         gtk_widget_show (help_label);
228         gtk_box_pack_start (GTK_BOX (vbox1), help_label, TRUE, TRUE, 0);
229
230                 /* register / substitute / delete */
231
232         reg_hbox = gtk_hbox_new (FALSE, 4);
233         gtk_widget_show (reg_hbox);
234         gtk_box_pack_start (GTK_BOX (vbox1), reg_hbox, FALSE, FALSE, 0);
235
236         arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT);
237         gtk_widget_show (arrow);
238         gtk_box_pack_start (GTK_BOX (reg_hbox), arrow, FALSE, FALSE, 0);
239         gtk_widget_set_usize (arrow, -1, 16);
240
241         btn_hbox = gtk_hbox_new (TRUE, 4);
242         gtk_widget_show (btn_hbox);
243         gtk_box_pack_start (GTK_BOX (reg_hbox), btn_hbox, FALSE, FALSE, 0);
244
245         reg_btn = gtk_button_new_with_label (_("Register"));
246         gtk_widget_show (reg_btn);
247         gtk_box_pack_start (GTK_BOX (btn_hbox), reg_btn, FALSE, TRUE, 0);
248         gtk_signal_connect (GTK_OBJECT (reg_btn), "clicked",
249                             GTK_SIGNAL_FUNC (prefs_actions_register_cb), NULL);
250
251         subst_btn = gtk_button_new_with_label (_(" Substitute "));
252         gtk_widget_show (subst_btn);
253         gtk_box_pack_start (GTK_BOX (btn_hbox), subst_btn, FALSE, TRUE, 0);
254         gtk_signal_connect (GTK_OBJECT (subst_btn), "clicked",
255                             GTK_SIGNAL_FUNC (prefs_actions_substitute_cb),
256                             NULL);
257
258         del_btn = gtk_button_new_with_label (_("Delete"));
259         gtk_widget_show (del_btn);
260         gtk_box_pack_start (GTK_BOX (btn_hbox), del_btn, FALSE, TRUE, 0);
261         gtk_signal_connect (GTK_OBJECT (del_btn), "clicked",
262                             GTK_SIGNAL_FUNC (prefs_actions_delete_cb), NULL);
263
264         cond_hbox = gtk_hbox_new (FALSE, 8);
265         gtk_widget_show (cond_hbox);
266         gtk_box_pack_start (GTK_BOX (vbox1), cond_hbox, TRUE, TRUE, 0);
267
268         cond_scrolledwin = gtk_scrolled_window_new (NULL, NULL);
269         gtk_widget_show (cond_scrolledwin);
270         gtk_widget_set_usize (cond_scrolledwin, -1, 150);
271         gtk_box_pack_start (GTK_BOX (cond_hbox), cond_scrolledwin,
272                             TRUE, TRUE, 0);
273         gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (cond_scrolledwin),
274                                         GTK_POLICY_AUTOMATIC,
275                                         GTK_POLICY_AUTOMATIC);
276
277         title[0] = _("Registered actions");
278         cond_clist = gtk_clist_new_with_titles(1, title);
279         gtk_widget_show (cond_clist);
280         gtk_container_add (GTK_CONTAINER (cond_scrolledwin), cond_clist);
281         gtk_clist_set_column_width (GTK_CLIST (cond_clist), 0, 80);
282         gtk_clist_set_selection_mode (GTK_CLIST (cond_clist),
283                                       GTK_SELECTION_BROWSE);
284         GTK_WIDGET_UNSET_FLAGS (GTK_CLIST (cond_clist)->column[0].button,
285                                 GTK_CAN_FOCUS);
286         gtk_signal_connect (GTK_OBJECT (cond_clist), "select_row",
287                             GTK_SIGNAL_FUNC (prefs_actions_select), NULL);
288         gtk_signal_connect_after (GTK_OBJECT (cond_clist), "row_move",
289                                   GTK_SIGNAL_FUNC (prefs_actions_row_move),
290                                   NULL);
291
292         btn_vbox = gtk_vbox_new (FALSE, 8);
293         gtk_widget_show (btn_vbox);
294         gtk_box_pack_start (GTK_BOX (cond_hbox), btn_vbox, FALSE, FALSE, 0);
295
296         up_btn = gtk_button_new_with_label (_("Up"));
297         gtk_widget_show (up_btn);
298         gtk_box_pack_start (GTK_BOX (btn_vbox), up_btn, FALSE, FALSE, 0);
299         gtk_signal_connect (GTK_OBJECT (up_btn), "clicked",
300                             GTK_SIGNAL_FUNC (prefs_actions_up), NULL);
301
302         down_btn = gtk_button_new_with_label (_("Down"));
303         gtk_widget_show (down_btn);
304         gtk_box_pack_start (GTK_BOX (btn_vbox), down_btn, FALSE, FALSE, 0);
305         gtk_signal_connect (GTK_OBJECT (down_btn), "clicked",
306                             GTK_SIGNAL_FUNC (prefs_actions_down), NULL);
307
308         gtk_widget_show_all(window);
309
310         actions.window = window;
311         actions.ok_btn = ok_btn;
312
313         actions.name_entry = name_entry;
314         actions.cmd_entry  = cmd_entry;
315         
316         actions.actions_clist = cond_clist;
317 }
318
319 void prefs_actions_read_config()
320 {
321         gchar *rcpath;
322         FILE *fp;
323         gchar buf[PREFSBUFSIZE];
324         gchar *act;
325
326         debug_print(_("Reading actions configurations...\n"));
327
328         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACTIONS_RC, NULL);
329         if ((fp = fopen(rcpath, "r")) == NULL) {
330                 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
331                 g_free(rcpath);
332                 return;
333         }
334         g_free(rcpath);
335
336         while (prefs_common.actionslst != NULL) {
337                 act = (gchar *) prefs_common.actionslst->data;
338                 prefs_common.actionslst = g_slist_remove(
339                                                 prefs_common.actionslst,
340                                                 act);
341                 g_free(act);
342         }
343         
344         while (fgets(buf, sizeof(buf), fp) != NULL) {
345                 g_strchomp(buf);
346                 if (actions_check(buf))
347                         prefs_common.actionslst = g_slist_append(
348                                                         prefs_common.actionslst,
349                                                         g_strdup(buf));
350         }
351         fclose(fp);
352 }
353
354 void prefs_actions_write_config()
355 {
356         gchar *rcpath;
357         PrefFile *pfile;
358         GSList *cur;
359
360         debug_print(_("Writing actions configuration...\n"));
361
362         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACTIONS_RC, NULL);
363         if ((pfile= prefs_write_open(rcpath)) == NULL) {
364                 g_warning(_("failed to write configuration to file\n"));
365                 g_free(rcpath);
366                 return;
367         }
368
369         for (cur = prefs_common.actionslst; cur != NULL; cur = cur->next) {
370                 gchar *act = (gchar *) cur->data;
371                 if (fputs(act, pfile->fp) == EOF ||
372                     fputc('\n', pfile->fp) == EOF) {
373                         FILE_OP_ERROR(rcpath, "fputs || fputc");
374                         prefs_write_close_revert(pfile);
375                         g_free(rcpath);
376                         return;
377                 }
378         }
379         
380         g_free(rcpath);
381
382         if (prefs_write_close(pfile) < 0) {
383                 g_warning(_("failed to write configuration to file\n"));
384                 return;
385         }
386 }
387
388 static gboolean actions_check(gchar *action)
389 {
390         /* FIXME: make a real syntax checking function */
391         return TRUE;
392 }
393
394 static void prefs_actions_set_dialog    (void)
395 {
396         GtkCList *clist = GTK_CLIST(actions.actions_clist);
397         GSList *cur;
398         gchar *action_str[1];
399         gint row;
400
401         gtk_clist_freeze(clist);
402         gtk_clist_clear(clist);
403
404         action_str[0] = _("(New)");
405         row = gtk_clist_append(clist, action_str);
406         gtk_clist_set_row_data(clist, row, NULL);
407
408         for (cur = prefs_common.actionslst; cur != NULL; cur = cur->next) {
409                 gchar *action[1];
410                 action[0] = (gchar *) cur->data;
411
412                 row = gtk_clist_append(clist, action);
413                 gtk_clist_set_row_data(clist, row, action[0]);
414         }
415         
416         gtk_clist_thaw(clist);
417 }
418 static void prefs_actions_set_list(void)
419 {
420         gint row = 1;
421         gchar *action;
422         
423         g_slist_free(prefs_common.actionslst);
424         prefs_common.actionslst = NULL;
425
426         while ( (action = (gchar *) gtk_clist_get_row_data(GTK_CLIST(
427                                                  actions.actions_clist), row))
428                 != NULL) {
429                 prefs_common.actionslst = g_slist_append(prefs_common.actionslst,
430                                                          action);
431                 row++;
432         }
433 }
434                 
435
436 #define GET_ENTRY(entry) \
437         entry_text = gtk_entry_get_text(GTK_ENTRY(entry))
438 static gint prefs_actions_clist_set_row (gint row)
439 {
440         GtkCList *clist = GTK_CLIST(actions.actions_clist);
441         gchar *entry_text;
442         gint len;
443         gchar action[PREFSBUFSIZE];
444         gchar *buf[1];
445         
446         
447         g_return_val_if_fail(row != 0, -1);
448
449         
450
451         GET_ENTRY(actions.name_entry);
452         if (entry_text[0] == '\0') {
453                 alertpanel_error(_("Menu name is not set."));
454                 return -1;
455         }
456
457         if (strchr(entry_text, ':')) {
458                 alertpanel_error(_("Colon ':' is not allowed in the menu name."));
459                 return -1;
460         }
461         
462         strncpy(action, entry_text, PREFSBUFSIZE - 1);
463         g_strstrip(action);
464
465         /* Keep space for the ': ' delimiter */
466         len = strlen(action) + 2;
467         if (len >= PREFSBUFSIZE - 1) {
468                 alertpanel_error(_("Menu name is too long."));
469                 return -1;
470         }
471
472         strcat(action, ": ");
473
474         GET_ENTRY(actions.cmd_entry);
475
476         if (entry_text[0] == '\0') {
477                 alertpanel_error(_("Command line not set."));
478                 return -1;
479         }
480
481         if (len + strlen(entry_text) >= PREFSBUFSIZE - 1) {
482                 alertpanel_error(_("Menu name and command are too long."));
483                 return -1;
484         }
485
486         if (!actions_check(entry_text)) {
487                 alertpanel_error(_("Command syntax error."));
488                 return -1;
489         }
490         
491         strcat(action, entry_text);
492
493         buf[0] = action;
494         if (row < 0)
495                 row = gtk_clist_append(clist, buf);
496         else {
497                 gchar *old_action;
498                 gtk_clist_set_text(clist, row, 0, action);
499                 old_action = (gchar *) gtk_clist_get_row_data(clist, row);
500                 if (old_action)
501                         g_free(old_action);
502         }
503
504         buf[0] = g_strdup(action);
505         
506         gtk_clist_set_row_data(clist, row, buf[0]);
507
508         prefs_actions_set_list();
509
510         return 0;
511 }
512         
513 /* callback functions */
514 static void prefs_actions_register_cb   (GtkWidget *w, gpointer data)
515 {
516         prefs_actions_clist_set_row(-1);
517 }
518
519 static void prefs_actions_substitute_cb (GtkWidget *w, gpointer data)
520 {
521         GtkCList *clist = GTK_CLIST(actions.actions_clist);
522         gchar *action;
523         gint row;
524
525         if (!clist->selection) return;
526
527         row = GPOINTER_TO_INT(clist->selection->data);
528         if (row == 0) return;
529
530         action = gtk_clist_get_row_data(clist, row);
531         if (!action) return;
532         
533         prefs_actions_clist_set_row(row);
534 }
535
536 static void prefs_actions_delete_cb     (GtkWidget *w, gpointer data)
537 {
538         GtkCList *clist = GTK_CLIST(actions.actions_clist);
539         gchar *action;
540         gint row;
541
542         if (!clist->selection) return;
543         row = GPOINTER_TO_INT(clist->selection->data);
544         if (row == 0) return;
545
546         if (alertpanel(_("Delete action"),
547                        _("Do you really want to delete this action?"),
548                        _("Yes"), _("No"), NULL) == G_ALERTALTERNATE)
549                 return;
550
551         action = gtk_clist_get_row_data(clist, row);
552         g_free(action);
553         gtk_clist_remove(clist, row);
554         prefs_common.actionslst = g_slist_remove(prefs_common.actionslst, action);
555 }
556
557 static void prefs_actions_up            (GtkWidget *w, gpointer data)
558 {
559         GtkCList *clist = GTK_CLIST(actions.actions_clist);
560         gint row;
561
562         if (!clist->selection) return;
563
564         row = GPOINTER_TO_INT(clist->selection->data);
565         if (row > 1)
566                 gtk_clist_row_move(clist, row, row - 1);
567 }
568
569 static void prefs_actions_down          (GtkWidget *w, gpointer data)
570 {
571         GtkCList *clist = GTK_CLIST(actions.actions_clist);
572         gint row;
573
574         if (!clist->selection) return;
575
576         row = GPOINTER_TO_INT(clist->selection->data);
577         if (row > 0 && row < clist->rows - 1)
578                 gtk_clist_row_move(clist, row, row + 1);
579 }
580
581 #define ENTRY_SET_TEXT(entry, str) \
582         gtk_entry_set_text(GTK_ENTRY(entry), str ? str : "")
583 static void prefs_actions_select        (GtkCList       *clist,
584                                          gint            row,
585                                          gint            column,
586                                          GdkEvent       *event)
587 {
588         gchar *action;
589         gchar *cmd;
590         gchar buf[PREFSBUFSIZE];
591         action = gtk_clist_get_row_data(clist, row);
592
593         if (!action) {
594                 ENTRY_SET_TEXT(actions.name_entry, "");
595                 ENTRY_SET_TEXT(actions.cmd_entry, "");
596                 return;
597         }
598         
599         strncpy(buf, action, PREFSBUFSIZE - 1);
600         buf[PREFSBUFSIZE - 1] = 0x00;
601         cmd = strstr2(buf, ": ");
602
603         if (cmd && cmd[2])
604                 ENTRY_SET_TEXT(actions.cmd_entry, &cmd[2]);
605         else
606                 return;
607
608         *cmd = 0x00;
609         ENTRY_SET_TEXT(actions.name_entry, buf);
610 }
611         
612 static void prefs_actions_row_move      (GtkCList       *clist,
613                                          gint            source_row,
614                                          gint            dest_row)
615 {
616         prefs_actions_set_list();
617         if (gtk_clist_row_is_visible(clist, dest_row) != GTK_VISIBILITY_FULL) {
618                 gtk_clist_moveto(clist, dest_row, -1,
619                                  source_row < dest_row ? 1.0 : 0.0, 0.0);
620         }
621 }
622
623 static gint prefs_actions_deleted       (GtkWidget      *widget,
624                                          GdkEventAny    *event,
625                                          gpointer       *data)
626 {
627         prefs_actions_cancel(widget, data);
628         return TRUE;
629 }
630
631 static void prefs_actions_key_pressed   (GtkWidget      *widget,
632                                          GdkEventKey    *event,
633                                          gpointer        data)
634 {
635         if (event && event->keyval == GDK_Escape)
636                 prefs_actions_cancel(widget, data);
637 }
638
639 static void prefs_actions_cancel        (GtkWidget *w, gpointer data)
640 {
641         prefs_actions_read_config();
642         gtk_widget_hide(actions.window);
643         inc_unlock();
644 }
645
646 static void prefs_actions_ok            (GtkWidget *widget, gpointer data)
647 {
648         GtkItemFactory *ifactory;
649         MainWindow *mainwin = (MainWindow *) data;
650
651         prefs_actions_write_config();
652         ifactory = gtk_item_factory_from_widget(mainwin->menubar);
653         update_mainwin_actions_menu(ifactory, mainwin);
654         gtk_widget_hide(actions.window);
655         inc_unlock();
656 }
657
658 void update_mainwin_actions_menu(GtkItemFactory *ifactory, 
659                                  MainWindow *mainwin)
660 {
661         update_actions_menu(ifactory, "/Edit/Actions", 
662                             mainwin_actions_execute_cb, 
663                             mainwin);
664 }
665
666 void update_compose_actions_menu(GtkItemFactory *ifactory, 
667                                  gchar *branch_path,
668                                  Compose *compose)
669 {
670         update_actions_menu(ifactory, branch_path, 
671                             compose_actions_execute_cb, 
672                             compose);
673 }
674                                  
675 static void update_actions_menu(GtkItemFactory *ifactory,
676                                 gchar *branch_path,
677                                 gpointer callback,
678                                 gpointer data)
679 {
680         GtkWidget *menuitem;
681         gchar *menu_path;
682         GSList *cur;
683         gchar *buf;
684         gint sensitive;
685         GtkItemFactoryEntry ifentry = {
686                 branch_path, NULL, NULL, 0, "<Branch>"};
687         sensitive = GTK_WIDGET_IS_SENSITIVE(gtk_item_factory_get_item(ifactory, branch_path));
688         gtk_item_factory_delete_item(ifactory, branch_path);
689         gtk_item_factory_create_item(ifactory, &ifentry, NULL, 1);
690         menuitem = gtk_item_factory_get_item(ifactory, branch_path);
691         gtk_widget_set_sensitive(menuitem, sensitive);
692
693         ifentry.accelerator     = NULL;
694         ifentry.callback_action = 0;
695         ifentry.callback        = callback;
696         ifentry.item_type       = NULL;
697
698         for (cur = prefs_common.actionslst; cur; cur = cur->next) {
699                 menu_path = (gchar *) cur->data;
700                 if (actions_check(menu_path)) {
701                         buf = g_strdup_printf("%s/%s", branch_path, menu_path);
702                         menu_path = strstr(buf, ": ");
703                         if (menu_path) {
704                                 *menu_path = 0x00;
705                                 menu_path = strchr(buf, '/');
706                                 ifentry.path = menu_path;
707                                 gtk_item_factory_create_item(ifactory, &ifentry,
708                                                 data, 1);
709                                 }
710                         g_free(menu_path);
711                 }
712                 ifentry.callback_action++;      
713         }
714 }
715
716 static void compose_actions_execute_cb  (Compose        *compose,
717                                          guint           action_nb,
718                                          GtkWidget      *widget)
719 {
720         gchar *buf, *action;
721         
722         g_return_if_fail(action_nb < g_slist_length(prefs_common.actionslst));
723         
724         buf = (gchar *) g_slist_nth_data(prefs_common.actionslst, action_nb);
725         
726         g_return_if_fail(buf);
727
728         g_return_if_fail(action = strstr(buf, ": "));
729
730         /* Point to the beginning of the command-line */
731         action++;
732         action++;
733         
734         if (action[0] == '|')
735                 pipe_command(&action[1], GTK_EDITABLE(compose->text));
736         else {
737                 alertpanel_warning(_("The selected action is not a pipe action.\n You can only use pipe actions when composing a message."));
738                 return;
739         }
740 }
741
742 static void mainwin_actions_execute_cb  (MainWindow     *mainwin,
743                                          guint           action_nb,
744                                          GtkWidget      *widget)
745 {
746         MessageView *messageview = mainwin->messageview;
747         GtkEditable *editable = NULL;
748         gchar *buf, *action;
749         
750         g_return_if_fail (action_nb < g_slist_length(prefs_common.actionslst));
751
752         buf = (gchar *) g_slist_nth_data(prefs_common.actionslst, action_nb);
753         
754         g_return_if_fail(buf);
755
756         g_return_if_fail(action = strstr(buf, ": "));
757
758         switch (messageview->type) {
759                 case MVIEW_TEXT:
760                         if (messageview->textview && messageview->textview->text)
761                                 editable = GTK_EDITABLE(messageview->textview->text);
762                         break;
763                 case MVIEW_MIME:
764                         if (messageview->mimeview && messageview->mimeview->type == MIMEVIEW_TEXT &&
765                             messageview->mimeview->textview && messageview->mimeview->textview->text)
766                                 editable = GTK_EDITABLE(messageview->mimeview->textview->text);
767                         break;
768                 default:
769                         return;
770         }
771
772         if (!editable)
773                 return;
774
775         /* Point to the beginning of the command-line */
776         action++;
777         action++;
778         
779
780         if (action[0] == '|')
781                 pipe_command(&action[1], editable);
782         else 
783                 message_command(action, mainwin->summaryview);
784 }
785
786 static void pipe_command(gchar *action, GtkEditable *editable)
787 {
788         gchar *selection;
789         gint start =  0;
790         gint end   = gtk_stext_get_length(GTK_STEXT(editable));
791         gint orig_pos;
792         gint fd1[2], fd2[2];
793         pid_t pid;
794         gchar **cmdline;
795
796         if (editable->has_selection) {
797                 start = editable->selection_start_pos;
798                 end   = editable->selection_end_pos;
799                 if (start > end) {
800                         gint tmp;
801                         tmp = start;
802                         start = end;
803                         end = tmp;
804                 }
805                 if (start == end) {
806                         start =  0;
807                         end   = gtk_stext_get_length(GTK_STEXT(editable));
808                 }
809         }
810
811
812         if (pipe(fd1)) {
813                 alertpanel_error(_("Command could not started. Pipe creation failed.\n%s"), g_strerror(errno));
814                 return;
815         }
816         if (pipe(fd2)) {
817                 alertpanel_error(_("Command could not started. Pipe creation failed.\n%s"), g_strerror(errno));
818                 return;
819         }
820
821         pid = fork();
822         if (pid == (pid_t) 0) {
823                 close(0);
824                 dup(fd1[0]);
825                 close(fd1[0]);
826                 close(fd1[1]);
827         
828                 close(1);
829                 dup(fd2[1]);
830                 close(fd2[0]);
831                 close(fd2[1]);
832                 close(ConnectionNumber(gdk_display));
833
834                 cmdline = strsplit_with_quote(action, " ", 1024);
835                 execvp(cmdline[0], cmdline);
836                 
837                 perror("execvp");
838                 g_strfreev(cmdline);
839
840                 _exit(1);
841         } else if (pid < (pid_t) 0) {
842                 alertpanel_error(_("Could not fork to execute command.\n%s"), g_strerror(errno));
843                 return;
844         } else {
845                 gchar buf[PREFSBUFSIZE];
846                 gint c;
847                 
848                 buf[PREFSBUFSIZE - 1] = 0x00;
849
850                 gtk_stext_freeze(GTK_STEXT(editable));
851                 
852                 selection = gtk_editable_get_chars(editable, start, end);
853
854                 close(fd1[0]);
855                 close(fd2[1]);
856
857                 write(fd1[1], selection, strlen(selection));
858
859                 close(fd1[1]);
860                 close(fd2[1]);
861                 
862                 orig_pos = gtk_editable_get_position(editable); 
863
864                 gtk_stext_set_point(GTK_STEXT(editable), start);
865                 gtk_stext_forward_delete(GTK_STEXT(editable), end - start);
866
867                 while (TRUE) {
868                         c = read(fd2[0], buf, PREFSBUFSIZE - 1);
869                         gtk_stext_insert(GTK_STEXT(editable), NULL, NULL, NULL, buf, c);
870                         if (c == 0) break;
871                 }
872                 close(fd2[0]);
873                 /* FIXME: should we wait kindly as in message_command_execute 
874                    and blocking interactions? */
875                 waitpid(0, NULL, 0); 
876
877                 end = gtk_stext_get_length(GTK_STEXT(editable));
878                 if (end < orig_pos)
879                         orig_pos = end;
880
881                 gtk_stext_thaw(GTK_STEXT(editable));
882                 gtk_stext_set_point(GTK_STEXT(editable), orig_pos);
883                 gtk_editable_set_position(editable, orig_pos);
884         }
885 }
886         
887 static void message_command(gchar *action, SummaryView *summaryview)
888 {
889         GtkCTree *ctree = GTK_CTREE(summaryview->ctree);
890         GList *cur;
891         MsgInfo *msginfo;
892
893         main_window_cursor_wait(summaryview->mainwin);
894         /* Do no allow interaction, as these commmands can mess with the message files */
895         gtk_widget_set_sensitive(summaryview->mainwin->window, FALSE);
896         for (cur = GTK_CLIST(ctree)->selection; cur != NULL; cur = cur->next) {
897                 msginfo = gtk_ctree_node_get_row_data(ctree, 
898                                                 GTK_CTREE_NODE(cur->data));
899                 if(message_command_execute(action, msginfo))
900                         break;
901         }
902         main_window_cursor_normal(summaryview->mainwin);
903         gtk_widget_set_sensitive(summaryview->mainwin->window, TRUE);
904 }
905
906 static gint message_command_execute(gchar *action, MsgInfo *msginfo)
907 {
908         gchar *filename, *p;    
909         gchar buf[PREFSBUFSIZE];
910         pid_t pid;
911         gchar **cmdline;
912
913         filename = procmsg_get_message_file(msginfo);
914
915         if ((p = strchr(action, '%')) && p[1] == 'f' && !strchr(p + 2, '%')) {
916                 p[1] = 's';
917                 g_snprintf(buf, sizeof(buf) - 1, action, filename);
918         } else {
919                 alertpanel_error(_("Syntax error in command line. Only one '%%' is alloawed, and it must be followed by 'f'."));
920                 return -1;
921         }
922         
923         g_free(filename);
924         
925         pid = fork();
926         if (pid == (pid_t) 0) {
927                 close(ConnectionNumber(gdk_display));
928
929                 cmdline = strsplit_with_quote(buf, " ", 1024);
930                 execvp(cmdline[0], cmdline);
931
932                 perror("execvp");
933                 g_strfreev(cmdline);
934         
935                 _exit(1);
936         } else if (pid < (pid_t) 0) {
937                 alertpanel_error(_("Could not fork to execute command.\n%s"), g_strerror(errno));
938                 return -1;
939         } else {
940                 while (!waitpid(0, NULL, WNOHANG))
941                         while (gtk_events_pending())
942                                 gtk_main_iteration();
943         }
944         return 0;
945 }