* src/send_message.c
[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 #include <signal.h>
37 #include <unistd.h>
38
39 #include "intl.h"
40 #include "prefs_gtk.h"
41 #include "inc.h"
42 #include "utils.h"
43 #include "gtkutils.h"
44 #include "manage_window.h"
45 #include "mainwindow.h"
46 #include "prefs_common.h"
47 #include "alertpanel.h"
48 #include "prefs_actions.h"
49 #include "compose.h"
50 #include "procmsg.h"
51 #include "gtkstext.h"
52 #include "mimeview.h"
53 #include "description_window.h"
54 #include "textview.h"
55
56 typedef enum
57 {
58         ACTION_NONE             = 1 << 0,
59         ACTION_PIPE_IN          = 1 << 1,
60         ACTION_PIPE_OUT         = 1 << 2,
61         ACTION_SINGLE           = 1 << 3,
62         ACTION_MULTIPLE         = 1 << 4,
63         ACTION_ASYNC            = 1 << 5,
64         ACTION_USER_IN          = 1 << 6,
65         ACTION_USER_HIDDEN_IN   = 1 << 7,
66         ACTION_INSERT           = 1 << 8,
67         ACTION_USER_STR         = 1 << 9,
68         ACTION_USER_HIDDEN_STR  = 1 << 10,
69         ACTION_SELECTION_STR    = 1 << 11,
70         ACTION_ERROR            = 1 << 30,
71 } ActionType;
72
73 static struct Actions
74 {
75         GtkWidget *window;
76
77         GtkWidget *ok_btn;
78
79         GtkWidget *name_entry;
80         GtkWidget *cmd_entry;
81
82         GtkWidget *actions_clist;
83 } actions;
84
85 typedef struct _Children Children;
86 typedef struct _ChildInfo ChildInfo;
87 typedef struct _UserStringDialog UserStringDialog;
88
89 struct _Children
90 {
91         GtkWidget       *dialog;
92         GtkWidget       *text;
93         GtkWidget       *input_entry;
94         GtkWidget       *input_hbox;
95         GtkWidget       *abort_btn;
96         GtkWidget       *close_btn;
97         GtkWidget       *scrolledwin;
98
99         gchar           *action;
100         GSList          *list;
101         gint             nb;
102         gint             open_in;
103         gboolean         output;
104 };
105
106 struct _ChildInfo
107 {
108         Children        *children;
109         gchar           *cmd;
110         guint            type;
111         pid_t            pid;
112         gint             chld_in;
113         gint             chld_out;
114         gint             chld_err;
115         gint             chld_status;
116         gint             tag_in;
117         gint             tag_out;
118         gint             tag_err;
119         gint             tag_status;
120         gint             new_out;
121         GString         *output;
122         GtkWidget       *text;
123         GdkFont         *msgfont;
124 };
125
126 struct _UserStringDialog {
127         GtkWidget       *dialog;
128         GtkWidget       *entry;
129         gchar           *user_str;
130 };
131
132 /* widget creating functions */
133 static void prefs_actions_create        (MainWindow *mainwin);
134 static void prefs_actions_set_dialog    (void);
135 static gint prefs_actions_clist_set_row (gint row);
136
137 /* callback functions */
138 static void prefs_actions_help_cb       (GtkWidget      *w,
139                                          gpointer        data);
140 static void prefs_actions_register_cb   (GtkWidget      *w,
141                                          gpointer        data);
142 static void prefs_actions_substitute_cb (GtkWidget      *w,
143                                          gpointer        data);
144 static void prefs_actions_delete_cb     (GtkWidget      *w,
145                                          gpointer        data);
146 static void prefs_actions_up            (GtkWidget      *w,
147                                          gpointer        data);
148 static void prefs_actions_down          (GtkWidget      *w,
149                                          gpointer        data);
150 static void prefs_actions_select        (GtkCList       *clist,
151                                          gint            row,
152                                          gint            column,
153                                          GdkEvent       *event);
154 static void prefs_actions_row_move      (GtkCList       *clist,
155                                          gint            source_row,
156                                          gint            dest_row);
157 static gint prefs_actions_deleted       (GtkWidget      *widget,
158                                          GdkEventAny    *event,
159                                          gpointer       *data);
160 static void prefs_actions_key_pressed   (GtkWidget      *widget,
161                                          GdkEventKey    *event,
162                                          gpointer        data);
163 static void prefs_actions_cancel        (GtkWidget      *w,
164                                          gpointer        data);
165 static void prefs_actions_ok            (GtkWidget      *w,
166                                          gpointer        data);
167 static void update_actions_menu         (GtkItemFactory *ifactory,
168                                          gchar          *branch_path,
169                                          gpointer        callback,
170                                          gpointer        data);
171 static void compose_actions_execute_cb  (Compose        *compose,
172                                          guint           action_nb,
173                                          GtkWidget      *widget);
174 static void mainwin_actions_execute_cb  (MainWindow     *mainwin,
175                                          guint           action_nb,
176                                          GtkWidget      *widget);
177 static void msgview_actions_execute_cb  (MessageView    *msgview, 
178                                          guint           action_nb,
179                                          GtkWidget      *widget);
180 static void message_actions_execute     (MessageView    *msgview,
181                                          guint           action_nb,
182                                          GtkCTree       *ctree);
183 static guint get_action_type            (gchar          *action);
184
185 static gboolean execute_actions         (gchar          *action, 
186                                          GtkCTree       *ctree, 
187                                          GtkWidget      *text,
188                                          GdkFont        *msgfont,
189                                          gint            body_pos,
190                                          MimeView       *mimeview);
191
192 static gchar *parse_action_cmd          (gchar          *action,
193                                          MsgInfo        *msginfo,
194                                          GtkCTree       *ctree,
195                                          MimeView       *mimeview,
196                                          const gchar    *user_str,
197                                          const gchar    *user_hidden_str,
198                                          const gchar    *sel_str);
199 static gboolean parse_append_filename   (GString        **cmd,
200                                          MsgInfo        *msginfo);
201
202 static gboolean parse_append_msgpart    (GString        **cmd,
203                                          MsgInfo        *msginfo,
204                                          MimeView       *mimeview);
205
206 ChildInfo *fork_child                   (gchar          *cmd,
207                                          gint            action_type,
208                                          GtkWidget      *text,
209                                          GdkFont        *msgfont,
210                                          gint            body_pos,
211                                          Children       *children);
212
213 static gint wait_for_children           (gpointer        data);
214
215 static void free_children               (Children       *children);
216
217 static void childinfo_close_pipes       (ChildInfo      *child_info);
218
219 static void create_io_dialog            (Children       *children);
220 static void update_io_dialog            (Children       *children);
221
222 static void hide_io_dialog_cb           (GtkWidget      *widget,
223                                          gpointer        data);
224 static gint io_dialog_key_pressed_cb    (GtkWidget      *widget,
225                                          GdkEventKey    *event,
226                                          gpointer        data);
227 static void catch_output                (gpointer                data,
228                                          gint                    source,
229                                          GdkInputCondition       cond);
230 static void catch_input                 (gpointer                data, 
231                                          gint                    source,
232                                          GdkInputCondition       cond);
233 static void catch_status                (gpointer                data,
234                                          gint                    source,
235                                          GdkInputCondition       cond);
236
237 static gboolean user_string_dialog_delete_cb
238                                         (GtkWidget              *widget,
239                                          GdkEvent               *event,
240                                          gpointer                data);
241 static void user_string_dialog_destroy_cb
242                                         (GtkWidget              *widget,
243                                          gpointer                data);
244
245
246 static void user_string_dialog_activate_cb
247                                         (GtkWidget              *widget,
248                                          gpointer                data);
249
250 static gchar *get_user_string           (const gchar            *action,
251                                          ActionType              type);
252
253 void prefs_actions_open(MainWindow *mainwin)
254 {
255 #if 0
256         if (prefs_rc_is_readonly(ACTIONS_RC))
257                 return;
258 #endif
259         inc_lock();
260
261         if (!actions.window)
262                 prefs_actions_create(mainwin);
263
264         manage_window_set_transient(GTK_WINDOW(actions.window));
265         gtk_widget_grab_focus(actions.ok_btn);
266
267         prefs_actions_set_dialog();
268
269         gtk_widget_show(actions.window);
270 }
271
272 static void prefs_actions_create(MainWindow *mainwin)
273 {
274         GtkWidget *window;
275         GtkWidget *vbox;
276         GtkWidget *ok_btn;
277         GtkWidget *cancel_btn;
278         GtkWidget *confirm_area;
279
280         GtkWidget *vbox1;
281
282         GtkWidget *entry_vbox;
283         GtkWidget *hbox;
284         GtkWidget *name_label;
285         GtkWidget *name_entry;
286         GtkWidget *cmd_label;
287         GtkWidget *cmd_entry;
288
289         GtkWidget *reg_hbox;
290         GtkWidget *btn_hbox;
291         GtkWidget *arrow;
292         GtkWidget *reg_btn;
293         GtkWidget *subst_btn;
294         GtkWidget *del_btn;
295
296         GtkWidget *cond_hbox;
297         GtkWidget *cond_scrolledwin;
298         GtkWidget *cond_clist;
299
300         GtkWidget *help_button;
301
302         GtkWidget *btn_vbox;
303         GtkWidget *up_btn;
304         GtkWidget *down_btn;
305
306         gchar *title[1];
307
308         debug_print("Creating actions configuration window...\n");
309
310         window = gtk_window_new (GTK_WINDOW_DIALOG);
311
312         gtk_container_set_border_width(GTK_CONTAINER (window), 8);
313         gtk_window_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
314         gtk_window_set_modal(GTK_WINDOW(window), TRUE);
315         gtk_window_set_policy(GTK_WINDOW(window), FALSE, TRUE, TRUE);
316         gtk_window_set_default_size(GTK_WINDOW(window), 400, -1);
317
318         vbox = gtk_vbox_new(FALSE, 6);
319         gtk_widget_show(vbox);
320         gtk_container_add(GTK_CONTAINER(window), vbox);
321
322         gtkut_button_set_create(&confirm_area, &ok_btn, _("OK"),
323                                 &cancel_btn, _("Cancel"), NULL, NULL);
324         gtk_widget_show(confirm_area);
325         gtk_box_pack_end(GTK_BOX(vbox), confirm_area, FALSE, FALSE, 0);
326         gtk_widget_grab_default(ok_btn);
327
328         gtk_window_set_title(GTK_WINDOW(window), _("Actions configuration"));
329         gtk_signal_connect(GTK_OBJECT(window), "delete_event",
330                            GTK_SIGNAL_FUNC(prefs_actions_deleted), NULL);
331         gtk_signal_connect(GTK_OBJECT(window), "key_press_event",
332                            GTK_SIGNAL_FUNC(prefs_actions_key_pressed), NULL);
333         MANAGE_WINDOW_SIGNALS_CONNECT(window);
334         gtk_signal_connect(GTK_OBJECT(ok_btn), "clicked",
335                            GTK_SIGNAL_FUNC(prefs_actions_ok), mainwin);
336         gtk_signal_connect(GTK_OBJECT(cancel_btn), "clicked",
337                            GTK_SIGNAL_FUNC(prefs_actions_cancel), NULL);
338
339         vbox1 = gtk_vbox_new(FALSE, 8);
340         gtk_widget_show(vbox1);
341         gtk_box_pack_start(GTK_BOX(vbox), vbox1, TRUE, TRUE, 0);
342         gtk_container_set_border_width(GTK_CONTAINER(vbox1), 2);
343
344         entry_vbox = gtk_vbox_new(FALSE, 4);
345         gtk_box_pack_start(GTK_BOX(vbox1), entry_vbox, FALSE, FALSE, 0);
346
347         hbox = gtk_hbox_new(FALSE, 8);
348         gtk_box_pack_start(GTK_BOX(entry_vbox), hbox, FALSE, FALSE, 0);
349
350         name_label = gtk_label_new(_("Menu name:"));
351         gtk_box_pack_start(GTK_BOX(hbox), name_label, FALSE, FALSE, 0);
352
353         name_entry = gtk_entry_new();
354         gtk_box_pack_start(GTK_BOX(hbox), name_entry, TRUE, TRUE, 0);
355
356         hbox = gtk_hbox_new(FALSE, 8);
357         gtk_box_pack_start(GTK_BOX(entry_vbox), hbox, TRUE, TRUE, 0);
358
359         cmd_label = gtk_label_new(_("Command line:"));
360         gtk_box_pack_start(GTK_BOX(hbox), cmd_label, FALSE, FALSE, 0);
361
362         cmd_entry = gtk_entry_new();
363         gtk_box_pack_start(GTK_BOX(hbox), cmd_entry, TRUE, TRUE, 0);
364
365         gtk_widget_show_all(entry_vbox);
366
367         /* register / substitute / delete */
368
369         reg_hbox = gtk_hbox_new(FALSE, 4);
370         gtk_widget_show(reg_hbox);
371         gtk_box_pack_start(GTK_BOX(vbox1), reg_hbox, FALSE, FALSE, 0);
372
373         arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
374         gtk_widget_show(arrow);
375         gtk_box_pack_start(GTK_BOX(reg_hbox), arrow, FALSE, FALSE, 0);
376         gtk_widget_set_usize(arrow, -1, 16);
377
378         btn_hbox = gtk_hbox_new(TRUE, 4);
379         gtk_widget_show(btn_hbox);
380         gtk_box_pack_start(GTK_BOX(reg_hbox), btn_hbox, FALSE, FALSE, 0);
381
382         reg_btn = gtk_button_new_with_label(_("Add"));
383         gtk_widget_show(reg_btn);
384         gtk_box_pack_start(GTK_BOX(btn_hbox), reg_btn, FALSE, TRUE, 0);
385         gtk_signal_connect(GTK_OBJECT(reg_btn), "clicked",
386                            GTK_SIGNAL_FUNC(prefs_actions_register_cb), NULL);
387
388         subst_btn = gtk_button_new_with_label(_("  Replace  "));
389         gtk_widget_show(subst_btn);
390         gtk_box_pack_start(GTK_BOX(btn_hbox), subst_btn, FALSE, TRUE, 0);
391         gtk_signal_connect(GTK_OBJECT(subst_btn), "clicked",
392                            GTK_SIGNAL_FUNC(prefs_actions_substitute_cb),
393                            NULL);
394
395         del_btn = gtk_button_new_with_label(_("Delete"));
396         gtk_widget_show(del_btn);
397         gtk_box_pack_start(GTK_BOX(btn_hbox), del_btn, FALSE, TRUE, 0);
398         gtk_signal_connect(GTK_OBJECT(del_btn), "clicked",
399                            GTK_SIGNAL_FUNC(prefs_actions_delete_cb), NULL);
400
401         help_button = gtk_button_new_with_label(_(" Syntax help "));
402         gtk_widget_show(help_button);
403         gtk_box_pack_end(GTK_BOX(reg_hbox), help_button, FALSE, FALSE, 0);
404         gtk_signal_connect(GTK_OBJECT(help_button), "clicked",
405                            GTK_SIGNAL_FUNC(prefs_actions_help_cb), NULL);
406
407         cond_hbox = gtk_hbox_new(FALSE, 8);
408         gtk_widget_show(cond_hbox);
409         gtk_box_pack_start(GTK_BOX(vbox1), cond_hbox, TRUE, TRUE, 0);
410
411         cond_scrolledwin = gtk_scrolled_window_new(NULL, NULL);
412         gtk_widget_show(cond_scrolledwin);
413         gtk_widget_set_usize(cond_scrolledwin, -1, 150);
414         gtk_box_pack_start(GTK_BOX(cond_hbox), cond_scrolledwin,
415                            TRUE, TRUE, 0);
416         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (cond_scrolledwin),
417                                        GTK_POLICY_AUTOMATIC,
418                                        GTK_POLICY_AUTOMATIC);
419
420         title[0] = _("Current actions");
421         cond_clist = gtk_clist_new_with_titles(1, title);
422         gtk_widget_show(cond_clist);
423         gtk_container_add(GTK_CONTAINER (cond_scrolledwin), cond_clist);
424         gtk_clist_set_column_width(GTK_CLIST (cond_clist), 0, 80);
425         gtk_clist_set_selection_mode(GTK_CLIST (cond_clist),
426                                      GTK_SELECTION_BROWSE);
427         GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(cond_clist)->column[0].button,
428                                GTK_CAN_FOCUS);
429         gtk_signal_connect(GTK_OBJECT(cond_clist), "select_row",
430                            GTK_SIGNAL_FUNC(prefs_actions_select), NULL);
431         gtk_signal_connect_after(GTK_OBJECT(cond_clist), "row_move",
432                                  GTK_SIGNAL_FUNC(prefs_actions_row_move),
433                                  NULL);
434
435         btn_vbox = gtk_vbox_new(FALSE, 8);
436         gtk_widget_show(btn_vbox);
437         gtk_box_pack_start(GTK_BOX(cond_hbox), btn_vbox, FALSE, FALSE, 0);
438
439         up_btn = gtk_button_new_with_label(_("Up"));
440         gtk_widget_show(up_btn);
441         gtk_box_pack_start(GTK_BOX(btn_vbox), up_btn, FALSE, FALSE, 0);
442         gtk_signal_connect(GTK_OBJECT(up_btn), "clicked",
443                            GTK_SIGNAL_FUNC(prefs_actions_up), NULL);
444
445         down_btn = gtk_button_new_with_label(_("Down"));
446         gtk_widget_show(down_btn);
447         gtk_box_pack_start(GTK_BOX(btn_vbox), down_btn, FALSE, FALSE, 0);
448         gtk_signal_connect(GTK_OBJECT(down_btn), "clicked",
449                            GTK_SIGNAL_FUNC(prefs_actions_down), NULL);
450
451         gtk_widget_show(window);
452
453         actions.window = window;
454         actions.ok_btn = ok_btn;
455
456         actions.name_entry = name_entry;
457         actions.cmd_entry  = cmd_entry;
458
459         actions.actions_clist = cond_clist;
460 }
461
462
463 void prefs_actions_read_config(void)
464 {
465         gchar *rcpath;
466         FILE *fp;
467         gchar buf[PREFSBUFSIZE];
468         gchar *act;
469
470         debug_print("Reading actions configurations...\n");
471
472         rcpath = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S, ACTIONS_RC, NULL);
473         if ((fp = fopen(rcpath, "rb")) == NULL) {
474                 if (ENOENT != errno) FILE_OP_ERROR(rcpath, "fopen");
475                 g_free(rcpath);
476                 return;
477         }
478         g_free(rcpath);
479
480         while (prefs_common.actions_list != NULL) {
481                 act = (gchar *)prefs_common.actions_list->data;
482                 prefs_common.actions_list =
483                         g_slist_remove(prefs_common.actions_list, act);
484                 g_free(act);
485         }
486
487         while (fgets(buf, sizeof(buf), fp) != NULL) {
488                 g_strchomp(buf);
489                 act = strstr(buf, ": ");
490                 if (act && act[2] && 
491                     get_action_type(&act[2]) != ACTION_ERROR)
492                         prefs_common.actions_list =
493                                 g_slist_append(prefs_common.actions_list,
494                                                g_strdup(buf));
495         }
496         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\n");
510                 g_free(rcpath);
511                 return;
512         }
513
514         for (cur = prefs_common.actions_list; cur != NULL; cur = cur->next) {
515                 gchar *act = (gchar *)cur->data;
516                 if (fputs(act, pfile->fp) == EOF ||
517                     fputc('\n', pfile->fp) == EOF) {
518                         FILE_OP_ERROR(rcpath, "fputs || fputc");
519                         prefs_file_close_revert(pfile);
520                         g_free(rcpath);
521                         return;
522                 }
523         }
524         
525         g_free(rcpath);
526
527         if (prefs_file_close(pfile) < 0) {
528                 g_warning("failed to write configuration to file\n");
529                 return;
530         }
531 }
532
533 static guint get_action_type(gchar *action)
534 {
535         gchar *p;
536         guint action_type = ACTION_NONE;
537
538         g_return_val_if_fail(action,  ACTION_ERROR);
539         g_return_val_if_fail(*action, ACTION_ERROR);
540
541         p = action;
542
543         if (p[0] == '|') {
544                 action_type |= ACTION_PIPE_IN;
545                 p++;
546         } else if (p[0] == '>') {
547                 action_type |= ACTION_USER_IN;
548                 p++;
549         } else if (p[0] == '*') {
550                 action_type |= ACTION_USER_HIDDEN_IN;
551                 p++;
552         }
553
554         if (p[0] == 0x00)
555                 return ACTION_ERROR;
556
557         while (*p && action_type != ACTION_ERROR) {
558                 if (p[0] == '%') {
559                         switch (p[1]) {
560                         case 'f':
561                                 action_type |= ACTION_SINGLE;
562                                 break;
563                         case 'F':
564                                 action_type |= ACTION_MULTIPLE;
565                                 break;
566                         case 'p':
567                                 action_type |= ACTION_SINGLE;
568                                 break;
569                         case 's':
570                                 action_type |= ACTION_SELECTION_STR;
571                                 break;
572                         case 'u':
573                                 action_type |= ACTION_USER_STR;
574                                 break;
575                         case 'h':
576                                 action_type |= ACTION_USER_HIDDEN_STR;
577                                 break;
578                         default:
579                                 action_type = ACTION_ERROR;
580                                 break;
581                         }
582                 } else if (p[0] == '|') {
583                         if (p[1] == 0x00)
584                                 action_type |= ACTION_PIPE_OUT;
585                 } else if (p[0] == '>') {
586                         if (p[1] == 0x00)
587                                 action_type |= ACTION_INSERT;
588                 } else if (p[0] == '&') {
589                         if (p[1] == 0x00)
590                                 action_type |= ACTION_ASYNC;
591                 }
592                 p++;
593         }
594
595         return action_type;
596 }
597
598 static gchar *parse_action_cmd(gchar *action, MsgInfo *msginfo,
599                                GtkCTree *ctree, MimeView *mimeview,
600                                const gchar *user_str, 
601                                const gchar *user_hidden_str,
602                                const gchar *sel_str)
603 {
604         GString *cmd;
605         gchar *p;
606         GList *cur;
607         MsgInfo *msg;
608         
609         p = action;
610         
611         if (p[0] == '|' || p[0] == '>' || p[0] == '*')
612                 p++;
613
614         cmd = g_string_sized_new(strlen(action));
615
616         while (p[0] &&
617                !((p[0] == '|' || p[0] == '>' || p[0] == '&') && !p[1])) {
618                 if (p[0] == '%' && p[1]) {
619                         switch (p[1]) {
620                         case 'f':
621                                 if (!parse_append_filename(&cmd, msginfo)) {
622                                         g_string_free(cmd, TRUE);
623                                         return NULL;
624                                 }
625                                 p++;
626                                 break;
627                         case 'F':
628                                 for (cur = GTK_CLIST(ctree)->selection;
629                                      cur != NULL; cur = cur->next) {
630                                         msg = gtk_ctree_node_get_row_data(ctree,
631                                               GTK_CTREE_NODE(cur->data));
632                                         if (!parse_append_filename(&cmd, msg)) {
633                                                 g_string_free(cmd, TRUE);
634                                                 return NULL;
635                                         }
636                                         if (cur->next)
637                                                 cmd = g_string_append_c(cmd, ' ');
638                                 }
639
640                                 p++;
641                                 break;
642                         case 'p':
643                                 if (!parse_append_msgpart(&cmd, msginfo,
644                                                           mimeview)) {
645                                         g_string_free(cmd, TRUE);
646                                         return NULL;
647                                 }
648                                 p++;
649                                 break;
650                         case 's':
651                                 if (sel_str)
652                                         cmd = g_string_append(cmd, sel_str);
653                                 p++;
654                                 break;
655
656                         case 'u':
657                                 if (user_str)
658                                         cmd = g_string_append(cmd, user_str);
659                                 p++;
660                                 break;
661                         case 'h':
662                                 if (user_hidden_str)
663                                         cmd = g_string_append(cmd,
664                                                               user_hidden_str);
665                                 p++;
666                                 break;
667
668                         default:
669                                 cmd = g_string_append_c(cmd, p[0]);
670                                 cmd = g_string_append_c(cmd, p[1]);
671                                 p++;
672                         }
673                 } else {
674                         cmd = g_string_append_c(cmd, p[0]);
675                 }
676                 p++;
677         }
678         if (cmd->len == 0) {
679                 g_string_free(cmd, TRUE);
680                 return NULL;
681         }
682
683         p = cmd->str;
684         g_string_free(cmd, FALSE);
685         return p;
686 }
687
688 static gboolean parse_append_filename(GString **cmd, MsgInfo *msginfo)
689 {
690         gchar *filename;
691
692         g_return_val_if_fail(msginfo, FALSE);
693
694         filename = procmsg_get_message_file(msginfo);
695
696         if (filename) {
697                 *cmd = g_string_append(*cmd, filename);
698                 g_free(filename);
699         } else {
700                 alertpanel_error(_("Could not get message file %d"),
701                                 msginfo->msgnum);
702                 return FALSE;
703         }
704
705         return TRUE;
706 }
707
708 static gboolean parse_append_msgpart(GString **cmd, MsgInfo *msginfo,
709                                      MimeView *mimeview)
710 {
711         gchar    *filename;
712         gchar    *partname;
713         MimeInfo *partinfo;
714         gint      ret;
715         FILE     *fp;
716
717         if (!mimeview) {
718 #if USE_GPGME
719                 if ((fp = procmsg_open_message_decrypted(msginfo, &partinfo))
720                     == NULL) {
721                         alertpanel_error(_("Could not get message file."));
722                         return FALSE;
723                 }
724 #else
725                 if ((fp = procmsg_open_message(msginfo)) == NULL) {
726                         alertpanel_error(_("Could not get message file."));
727                         return FALSE;
728                 }
729                 partinfo = procmime_scan_mime_header(fp);
730 #endif
731                 fclose(fp);
732                 if (!partinfo) {
733                         procmime_mimeinfo_free_all(partinfo);
734                         alertpanel_error(_("Could not get message part."));
735                         return FALSE;
736                 }
737                 filename = procmsg_get_message_file(msginfo);
738         } else {
739                 if (!mimeview->opened) {
740                         alertpanel_error(_("No message part selected."));
741                         return FALSE;
742                 }
743                 if (!mimeview->file) {
744                         alertpanel_error(_("No message file selected."));
745                         return FALSE;
746                 }
747                 partinfo = gtk_ctree_node_get_row_data
748                                 (GTK_CTREE(mimeview->ctree),
749                                  mimeview->opened);
750                 g_return_val_if_fail(partinfo != NULL, FALSE);
751                 filename = mimeview->file;
752         }
753         partname = procmime_get_tmp_file_name(partinfo);
754
755         ret = procmime_get_part(partname, filename, partinfo); 
756
757         if (!mimeview) {
758                 procmime_mimeinfo_free_all(partinfo);
759                 g_free(filename);
760         }
761
762         if (ret < 0) {
763                 alertpanel_error(_("Can't get part of multipart message"));
764                 g_free(partname);
765                 return FALSE;
766         }
767
768         *cmd = g_string_append(*cmd, partname);
769
770         g_free(partname);
771
772         return TRUE;
773 }
774
775 static void prefs_actions_set_dialog(void)
776 {
777         GtkCList *clist = GTK_CLIST(actions.actions_clist);
778         GSList *cur;
779         gchar *action_str[1];
780         gint row;
781
782         gtk_clist_freeze(clist);
783         gtk_clist_clear(clist);
784
785         action_str[0] = _("(New)");
786         row = gtk_clist_append(clist, action_str);
787         gtk_clist_set_row_data(clist, row, NULL);
788
789         for (cur = prefs_common.actions_list; cur != NULL; cur = cur->next) {
790                 gchar *action[1];
791
792                 action[0] = (gchar *)cur->data;
793                 row = gtk_clist_append(clist, action);
794                 gtk_clist_set_row_data(clist, row, action[0]);
795         }
796
797         gtk_clist_thaw(clist);
798 }
799
800 static void prefs_actions_set_list(void)
801 {
802         gint row = 1;
803         gchar *action;
804
805         g_slist_free(prefs_common.actions_list);
806         prefs_common.actions_list = NULL;
807
808         while ((action = (gchar *)gtk_clist_get_row_data
809                 (GTK_CLIST(actions.actions_clist), row)) != NULL) {
810                 prefs_common.actions_list =
811                         g_slist_append(prefs_common.actions_list, action);
812                 row++;
813         }
814 }
815
816 #define GET_ENTRY(entry) \
817         entry_text = gtk_entry_get_text(GTK_ENTRY(entry))
818
819 static gint prefs_actions_clist_set_row(gint row)
820 {
821         GtkCList *clist = GTK_CLIST(actions.actions_clist);
822         gchar *entry_text;
823         gint len;
824         gchar action[PREFSBUFSIZE];
825         gchar *buf[1];
826
827         g_return_val_if_fail(row != 0, -1);
828
829         GET_ENTRY(actions.name_entry);
830         if (entry_text[0] == '\0') {
831                 alertpanel_error(_("Menu name is not set."));
832                 return -1;
833         }
834
835         if (strchr(entry_text, ':')) {
836                 alertpanel_error(_("Colon ':' is not allowed in the menu name."));
837                 return -1;
838         }
839
840         strncpy(action, entry_text, PREFSBUFSIZE - 1);
841         g_strstrip(action);
842
843         /* Keep space for the ': ' delimiter */
844         len = strlen(action) + 2;
845         if (len >= PREFSBUFSIZE - 1) {
846                 alertpanel_error(_("Menu name is too long."));
847                 return -1;
848         }
849
850         strcat(action, ": ");
851
852         GET_ENTRY(actions.cmd_entry);
853
854         if (entry_text[0] == '\0') {
855                 alertpanel_error(_("Command line not set."));
856                 return -1;
857         }
858
859         if (len + strlen(entry_text) >= PREFSBUFSIZE - 1) {
860                 alertpanel_error(_("Menu name and command are too long."));
861                 return -1;
862         }
863
864         if (get_action_type(entry_text) == ACTION_ERROR) {
865                 alertpanel_error(_("The command\n%s\nhas a syntax error."), 
866                                  entry_text);
867                 return -1;
868         }
869
870         strcat(action, entry_text);
871
872         buf[0] = action;
873         if (row < 0)
874                 row = gtk_clist_append(clist, buf);
875         else {
876                 gchar *old_action;
877                 gtk_clist_set_text(clist, row, 0, action);
878                 old_action = (gchar *) gtk_clist_get_row_data(clist, row);
879                 if (old_action)
880                         g_free(old_action);
881         }
882
883         buf[0] = g_strdup(action);
884
885         gtk_clist_set_row_data(clist, row, buf[0]);
886
887         prefs_actions_set_list();
888
889         return 0;
890 }
891
892 /* callback functions */
893
894 static void prefs_actions_register_cb(GtkWidget *w, gpointer data)
895 {
896         prefs_actions_clist_set_row(-1);
897 }
898
899 static void prefs_actions_substitute_cb(GtkWidget *w, gpointer data)
900 {
901         GtkCList *clist = GTK_CLIST(actions.actions_clist);
902         gchar *action;
903         gint row;
904
905         if (!clist->selection) return;
906
907         row = GPOINTER_TO_INT(clist->selection->data);
908         if (row == 0) return;
909
910         action = gtk_clist_get_row_data(clist, row);
911         if (!action) return;
912
913         prefs_actions_clist_set_row(row);
914 }
915
916 static void prefs_actions_delete_cb(GtkWidget *w, gpointer data)
917 {
918         GtkCList *clist = GTK_CLIST(actions.actions_clist);
919         gchar *action;
920         gint row;
921
922         if (!clist->selection) return;
923         row = GPOINTER_TO_INT(clist->selection->data);
924         if (row == 0) return;
925
926         if (alertpanel(_("Delete action"),
927                        _("Do you really want to delete this action?"),
928                        _("Yes"), _("No"), NULL) == G_ALERTALTERNATE)
929                 return;
930
931         action = gtk_clist_get_row_data(clist, row);
932         g_free(action);
933         gtk_clist_remove(clist, row);
934         prefs_common.actions_list = g_slist_remove(prefs_common.actions_list,
935                                                    action);
936 }
937
938 static void prefs_actions_up(GtkWidget *w, gpointer data)
939 {
940         GtkCList *clist = GTK_CLIST(actions.actions_clist);
941         gint row;
942
943         if (!clist->selection) return;
944
945         row = GPOINTER_TO_INT(clist->selection->data);
946         if (row > 1)
947                 gtk_clist_row_move(clist, row, row - 1);
948 }
949
950 static void prefs_actions_down(GtkWidget *w, gpointer data)
951 {
952         GtkCList *clist = GTK_CLIST(actions.actions_clist);
953         gint row;
954
955         if (!clist->selection) return;
956
957         row = GPOINTER_TO_INT(clist->selection->data);
958         if (row > 0 && row < clist->rows - 1)
959                 gtk_clist_row_move(clist, row, row + 1);
960 }
961
962 #define ENTRY_SET_TEXT(entry, str) \
963         gtk_entry_set_text(GTK_ENTRY(entry), str ? str : "")
964
965 static void prefs_actions_select(GtkCList *clist, gint row, gint column,
966                                  GdkEvent *event)
967 {
968         gchar *action;
969         gchar *cmd;
970         gchar buf[PREFSBUFSIZE];
971         action = gtk_clist_get_row_data(clist, row);
972
973         if (!action) {
974                 ENTRY_SET_TEXT(actions.name_entry, "");
975                 ENTRY_SET_TEXT(actions.cmd_entry, "");
976                 return;
977         }
978
979         strncpy(buf, action, PREFSBUFSIZE - 1);
980         buf[PREFSBUFSIZE - 1] = 0x00;
981         cmd = strstr(buf, ": ");
982
983         if (cmd && cmd[2])
984                 ENTRY_SET_TEXT(actions.cmd_entry, &cmd[2]);
985         else
986                 return;
987
988         *cmd = 0x00;
989         ENTRY_SET_TEXT(actions.name_entry, buf);
990 }
991
992 static void prefs_actions_row_move(GtkCList *clist,
993                                    gint source_row, gint dest_row)
994 {
995         prefs_actions_set_list();
996         if (gtk_clist_row_is_visible(clist, dest_row) != GTK_VISIBILITY_FULL) {
997                 gtk_clist_moveto(clist, dest_row, -1,
998                                  source_row < dest_row ? 1.0 : 0.0, 0.0);
999         }
1000 }
1001
1002 static gint prefs_actions_deleted(GtkWidget *widget, GdkEventAny *event,
1003                                   gpointer *data)
1004 {
1005         prefs_actions_cancel(widget, data);
1006         return TRUE;
1007 }
1008
1009 static void prefs_actions_key_pressed(GtkWidget *widget, GdkEventKey *event,
1010                                       gpointer data)
1011 {
1012         if (event && event->keyval == GDK_Escape)
1013                 prefs_actions_cancel(widget, data);
1014 }
1015
1016 static void prefs_actions_cancel(GtkWidget *w, gpointer data)
1017 {
1018         prefs_actions_read_config();
1019         gtk_widget_hide(actions.window);
1020         inc_unlock();
1021 }
1022
1023 static void prefs_actions_ok(GtkWidget *widget, gpointer data)
1024 {
1025         GtkItemFactory *ifactory;
1026         MainWindow *mainwin = (MainWindow *)data;
1027
1028         prefs_actions_write_config();
1029         ifactory = gtk_item_factory_from_widget(mainwin->menubar);
1030         update_mainwin_actions_menu(ifactory, mainwin);
1031         gtk_widget_hide(actions.window);
1032         inc_unlock();
1033 }
1034
1035 void update_mainwin_actions_menu(GtkItemFactory *ifactory,
1036                                  MainWindow *mainwin)
1037 {
1038         update_actions_menu(ifactory, "/Tools/Actions",
1039                             mainwin_actions_execute_cb,
1040                             mainwin);
1041 }
1042
1043 void update_compose_actions_menu(GtkItemFactory *ifactory,
1044                                  gchar *branch_path,
1045                                  Compose *compose)
1046 {
1047         update_actions_menu(ifactory, branch_path,
1048                             compose_actions_execute_cb,
1049                             compose);
1050 }
1051
1052
1053 void actions_execute(gpointer data, 
1054                      guint action_nb,
1055                      GtkWidget *widget,
1056                      gint source)
1057 {
1058         if (source == TOOLBAR_MAIN) 
1059                 mainwin_actions_execute_cb((MainWindow*)data, action_nb, widget);
1060         else if (source == TOOLBAR_COMPOSE)
1061                 compose_actions_execute_cb((Compose*)data, action_nb, widget);
1062         else if (source == TOOLBAR_MSGVIEW)
1063                 msgview_actions_execute_cb((MessageView*)data, action_nb, widget);      
1064 }
1065
1066
1067 static void update_actions_menu(GtkItemFactory *ifactory,
1068                                 gchar *branch_path,
1069                                 gpointer callback,
1070                                 gpointer data)
1071 {
1072         GtkWidget *menuitem;
1073         gchar *menu_path;
1074         GSList *cur;
1075         gchar *action, *action_p;
1076         GList *amenu;
1077         GtkItemFactoryEntry ifentry = {NULL, NULL, NULL, 0, "<Branch>"};
1078
1079         ifentry.path = branch_path;
1080         menuitem = gtk_item_factory_get_widget(ifactory, branch_path);
1081         g_return_if_fail(menuitem != NULL);
1082
1083         amenu = GTK_MENU_SHELL(menuitem)->children;
1084         while (amenu != NULL) {
1085                 GList *alist = amenu->next;
1086                 gtk_widget_destroy(GTK_WIDGET(amenu->data));
1087                 amenu = alist;
1088         }
1089
1090         ifentry.accelerator     = NULL;
1091         ifentry.callback_action = 0;
1092         ifentry.callback        = callback;
1093         ifentry.item_type       = NULL;
1094
1095         for (cur = prefs_common.actions_list; cur; cur = cur->next) {
1096                 action   = g_strdup((gchar *)cur->data);
1097                 action_p = strstr(action, ": ");
1098                 if (action_p && action_p[2] &&
1099                     get_action_type(&action_p[2]) != ACTION_ERROR) {
1100                         action_p[0] = 0x00;
1101                         menu_path = g_strdup_printf("%s/%s", branch_path,
1102                                                     action);
1103                         ifentry.path = menu_path;
1104                         gtk_item_factory_create_item(ifactory, &ifentry, data,
1105                                                      1);
1106                         g_free(menu_path);
1107                 }
1108                 g_free(action);
1109                 ifentry.callback_action++;
1110         }
1111 }
1112
1113 static void compose_actions_execute_cb(Compose *compose, guint action_nb,
1114                                        GtkWidget *widget)
1115 {
1116         gchar *buf, *action;
1117         guint action_type;
1118
1119         g_return_if_fail(action_nb < g_slist_length(prefs_common.actions_list));
1120
1121         buf = (gchar *)g_slist_nth_data(prefs_common.actions_list, action_nb);
1122         g_return_if_fail(buf != NULL);
1123         action = strstr(buf, ": ");
1124         g_return_if_fail(action != NULL);
1125
1126         /* Point to the beginning of the command-line */
1127         action += 2;
1128
1129         action_type = get_action_type(action);
1130         if (action_type & (ACTION_SINGLE | ACTION_MULTIPLE)) {
1131                 alertpanel_warning
1132                         (_("The selected action cannot be used in the compose window\n"
1133                            "because it contains %%f, %%F or %%p."));
1134                 return;
1135         }
1136
1137         execute_actions(action, NULL, compose->text, NULL, 0, NULL);
1138 }
1139
1140 static void mainwin_actions_execute_cb(MainWindow *mainwin, guint action_nb,
1141                                        GtkWidget *widget)
1142 {
1143         message_actions_execute(mainwin->messageview, action_nb,
1144                                 GTK_CTREE(mainwin->summaryview->ctree));
1145 }
1146
1147 static void msgview_actions_execute_cb(MessageView *msgview, guint action_nb,
1148                                        GtkWidget *widget)
1149 {
1150         message_actions_execute(msgview, action_nb, NULL);
1151         
1152 }
1153
1154 static void message_actions_execute(MessageView *msgview, guint action_nb,
1155                                     GtkCTree *ctree)
1156 {
1157         TextView    *textview = NULL;
1158         gchar       *buf,
1159                     *action;
1160         MimeView    *mimeview = NULL;
1161         GdkFont     *msgfont  = NULL;
1162         guint        body_pos = 0;
1163         GtkWidget   *text     = NULL;
1164
1165         g_return_if_fail(action_nb < g_slist_length(prefs_common.actions_list));
1166
1167         buf = (gchar *)g_slist_nth_data(prefs_common.actions_list, action_nb);
1168
1169         g_return_if_fail(buf);
1170         g_return_if_fail(action = strstr(buf, ": "));
1171
1172         /* Point to the beginning of the command-line */
1173         action += 2;
1174
1175         switch (msgview->type) {
1176         case MVIEW_TEXT:
1177                 if (msgview->textview && msgview->textview->text)
1178                         textview = msgview->textview;
1179                 break;
1180         case MVIEW_MIME:
1181                 if (msgview->mimeview) {
1182                         mimeview = msgview->mimeview;
1183                         if (msgview->mimeview->type == MIMEVIEW_TEXT &&
1184                             msgview->mimeview->textview &&
1185                             msgview->mimeview->textview->text)
1186                                 textview = msgview->mimeview->textview;
1187                 } 
1188                 break;
1189         }
1190
1191         if (textview) {
1192                 text     = textview->text;
1193                 msgfont  = textview->msgfont;
1194                 body_pos = textview->body_pos;
1195         }
1196         
1197         execute_actions(action, ctree, text, msgfont, body_pos, mimeview);
1198 }
1199
1200 static gboolean execute_actions(gchar     *action, 
1201                                 GtkCTree  *ctree,
1202                                 GtkWidget *text, 
1203                                 GdkFont   *msgfont,
1204                                 gint       body_pos,
1205                                 MimeView  *mimeview)
1206 {
1207         GList *cur, *selection = NULL;
1208         GSList *children_list = NULL;
1209         gint is_ok  = TRUE;
1210         gint selection_len = 0;
1211         Children *children;
1212         ChildInfo *child_info;
1213         gint action_type;
1214         MsgInfo *msginfo;
1215         gchar *cmd;
1216         gchar *sel_str = NULL;
1217         gchar *user_str = NULL;
1218         gchar *user_hidden_str = NULL;
1219
1220         g_return_val_if_fail(action && *action, FALSE);
1221
1222         action_type = get_action_type(action);
1223
1224         if (action_type == ACTION_ERROR)
1225                 return FALSE;         /* ERR: syntax error */
1226
1227         if (action_type & (ACTION_SINGLE | ACTION_MULTIPLE) && 
1228             !(ctree && GTK_CLIST(ctree)->selection))
1229                 return FALSE;         /* ERR: file command without selection */
1230
1231         if (ctree) {
1232                 selection = GTK_CLIST(ctree)->selection;
1233                 selection_len = g_list_length(selection);
1234         }
1235
1236         if (action_type & (ACTION_PIPE_OUT | ACTION_PIPE_IN | ACTION_INSERT)) {
1237                 if (ctree && selection_len > 1)
1238                         return FALSE; /* ERR: pipe + multiple selection */
1239                 if (!text)
1240                         return FALSE; /* ERR: pipe and no displayed text */
1241         }
1242         
1243         if (action_type & ACTION_SELECTION_STR) {
1244                 if (!text)
1245                         return FALSE; /* ERR: selection string but no text */
1246                 else {
1247                         guint start = 0, end = 0;
1248                         if (GTK_EDITABLE(text)->has_selection) {
1249                                 start = GTK_EDITABLE(text)->selection_start_pos;
1250                                 end   = GTK_EDITABLE(text)->selection_end_pos;
1251                                 if (start > end) {
1252                                         guint tmp;
1253                                         tmp = start;
1254                                         start = end;
1255                                         end = tmp;
1256                                 }
1257                         }
1258                         sel_str = gtk_editable_get_chars(GTK_EDITABLE(text),
1259                                         start, end);
1260                 }
1261                         
1262         }
1263         
1264
1265         if (action_type & (ACTION_USER_STR))
1266                 if (!(user_str = get_user_string(action, ACTION_USER_STR)))
1267                         return FALSE;
1268                                 
1269         if (action_type & (ACTION_USER_HIDDEN_STR))
1270                 if (!(user_hidden_str = get_user_string(action,
1271                                                   ACTION_USER_HIDDEN_STR)))
1272                         return FALSE;
1273                 
1274         children = g_new0(Children, 1);
1275
1276         if (action_type & ACTION_SINGLE) {
1277                 for (cur = selection; cur && is_ok == TRUE; cur = cur->next) {
1278                         msginfo = gtk_ctree_node_get_row_data(ctree,
1279                                         GTK_CTREE_NODE(cur->data));
1280                         if (!msginfo) {
1281                                 is_ok  = FALSE; /* ERR: msginfo missing */
1282                                 break;
1283                         }
1284                         cmd = parse_action_cmd(action, msginfo, ctree,
1285                                                mimeview, user_str, 
1286                                                user_hidden_str, sel_str);
1287                         if (!cmd) {
1288                                 debug_print("Action command error\n");
1289                                 is_ok  = FALSE; /* ERR: incorrect command */
1290                                 break;
1291                         }
1292                         if ((child_info = fork_child(cmd, action_type, text,
1293                                                      msgfont, body_pos,
1294                                                      children))) {
1295                                 children_list = g_slist_append(children_list,
1296                                                                child_info);
1297                                 children->open_in = (selection_len == 1) ?
1298                                                     (action_type &
1299                                                      (ACTION_USER_IN |
1300                                                       ACTION_USER_HIDDEN_IN)) : 0;
1301                         }
1302                         g_free(cmd);
1303                 }
1304         } else {
1305                 cmd = parse_action_cmd(action, NULL, ctree, mimeview, user_str,
1306                                        user_hidden_str, sel_str);
1307                 if (cmd) {
1308                         if ((child_info = fork_child(cmd, action_type, text,
1309                                                      msgfont, body_pos,
1310                                                      children))) {
1311                                 children_list = g_slist_append(children_list,
1312                                                                child_info);
1313                                 children->open_in = action_type &
1314                                                     (ACTION_USER_IN |
1315                                                      ACTION_USER_HIDDEN_IN);
1316                         }
1317                         g_free(cmd);
1318                 } else
1319                         is_ok  = FALSE;         /* ERR: incorrect command */
1320         }
1321
1322         if (user_str)
1323                 g_free(user_str);
1324         if (user_hidden_str)
1325                 g_free(user_hidden_str);
1326
1327         if (!children_list) {
1328                  /* If not waiting for children, return */
1329                 g_free(children);
1330         } else {
1331                 GSList *cur;
1332
1333                 children->action  = g_strdup(action);
1334                 children->dialog  = NULL;
1335                 children->list    = children_list;
1336                 children->nb      = g_slist_length(children_list);
1337
1338                 for (cur = children_list; cur; cur = cur->next) {
1339                         child_info = (ChildInfo *) cur->data;
1340                         child_info->tag_status = 
1341                                 gdk_input_add(child_info->chld_status,
1342                                               GDK_INPUT_READ,
1343                                               catch_status, child_info);
1344                 }
1345
1346                 create_io_dialog(children);
1347         }
1348
1349         return is_ok;
1350 }
1351
1352 ChildInfo *fork_child(gchar *cmd, gint action_type, GtkWidget *text,
1353                       GdkFont *msgfont, gint body_pos, Children *children)
1354 {
1355         gint chld_in[2], chld_out[2], chld_err[2], chld_status[2];
1356         gchar *cmdline[4];
1357         guint start, end;
1358         gint is_selection;
1359         gchar *selection;
1360         pid_t pid, gch_pid;
1361         ChildInfo *child_info;
1362         gint sync;
1363
1364         sync = !(action_type & ACTION_ASYNC);
1365
1366         chld_in[0] = chld_in[1] = chld_out[0] = chld_out[1] = chld_err[0]
1367                 = chld_err[1] = chld_status[0] = chld_status[1] = -1;
1368
1369         if (sync) {
1370                 if (pipe(chld_status) || pipe(chld_in) || pipe(chld_out) ||
1371                     pipe(chld_err)) {
1372                         alertpanel_error(_("Command could not be started. "
1373                                            "Pipe creation failed.\n%s"),
1374                                         g_strerror(errno));
1375                         /* Closing fd = -1 fails silently */
1376                         close(chld_in[0]);
1377                         close(chld_in[1]);
1378                         close(chld_out[0]);
1379                         close(chld_out[1]);
1380                         close(chld_err[0]);
1381                         close(chld_err[1]);
1382                         close(chld_status[0]);
1383                         close(chld_status[1]);
1384                         return NULL; /* Pipe error */
1385                 }
1386         }
1387
1388         debug_print("Forking child and grandchild.\n");
1389
1390         pid = fork();
1391         if (pid == 0) { /* Child */
1392                 if (setpgid(0, 0))
1393                         perror("setpgid");
1394
1395                 close(ConnectionNumber(gdk_display));
1396
1397                 gch_pid = fork();
1398
1399                 if (gch_pid == 0) {
1400                         if (setpgid(0, getppid()))
1401                                 perror("setpgid");
1402                         if (sync) {
1403                                 if (action_type &
1404                                     (ACTION_PIPE_IN |
1405                                      ACTION_USER_IN |
1406                                      ACTION_USER_HIDDEN_IN)) {
1407                                         close(fileno(stdin));
1408                                         dup  (chld_in[0]);
1409                                 }
1410                                 close(chld_in[0]);
1411                                 close(chld_in[1]);
1412
1413                                 close(fileno(stdout));
1414                                 dup  (chld_out[1]);
1415                                 close(chld_out[0]);
1416                                 close(chld_out[1]);
1417
1418                                 close(fileno(stderr));
1419                                 dup  (chld_err[1]);
1420                                 close(chld_err[0]);
1421                                 close(chld_err[1]);
1422                         }
1423
1424                         cmdline[0] = "sh";
1425                         cmdline[1] = "-c";
1426                         cmdline[2] = cmd;
1427                         cmdline[3] = 0;
1428                         execvp("/bin/sh", cmdline);
1429
1430                         perror("execvp");
1431                         _exit(1);
1432                 } else if (gch_pid < (pid_t) 0) { /* Fork error */
1433                         if (sync)
1434                                 write(chld_status[1], "1\n", 2);
1435                         perror("fork");
1436                         _exit(1);
1437                 } else {/* Child */
1438                         if (sync) {
1439                                 close(chld_in[0]);
1440                                 close(chld_in[1]);
1441                                 close(chld_out[0]);
1442                                 close(chld_out[1]);
1443                                 close(chld_err[0]);
1444                                 close(chld_err[1]);
1445                                 close(chld_status[0]);
1446                         }
1447                         if (sync) {
1448                                 debug_print("Child: Waiting for grandchild\n");
1449                                 waitpid(gch_pid, NULL, 0);
1450                                 debug_print("Child: grandchild ended\n");
1451                                 write(chld_status[1], "0\n", 2);
1452                                 close(chld_status[1]);
1453                         }
1454                         _exit(0);
1455                 }
1456         } else if (pid < 0) { /* Fork error */
1457                 alertpanel_error(_("Could not fork to execute the following "
1458                                    "command:\n%s\n%s"),
1459                                  cmd, g_strerror(errno));
1460                 return NULL; 
1461         }
1462
1463         /* Parent */
1464
1465         if (!sync) {
1466                 waitpid(pid, NULL, 0);
1467                 return NULL;
1468         }
1469
1470         close(chld_in[0]);
1471         if (!(action_type & (ACTION_PIPE_IN | ACTION_USER_IN | ACTION_USER_HIDDEN_IN)))
1472                 close(chld_in[1]);
1473         close(chld_out[1]);
1474         close(chld_err[1]);
1475         close(chld_status[1]);
1476
1477         child_info = g_new0(ChildInfo, 1);
1478
1479         child_info->children    = children;
1480
1481         child_info->pid         = pid;
1482         child_info->cmd         = g_strdup(cmd);
1483         child_info->type        = action_type;
1484         child_info->new_out     = FALSE;
1485         child_info->output      = g_string_sized_new(0);
1486         child_info->chld_in     =
1487                 (action_type &
1488                  (ACTION_PIPE_IN | ACTION_USER_IN | ACTION_USER_HIDDEN_IN))
1489                         ? chld_in [1] : -1;
1490         child_info->chld_out    = chld_out[0];
1491         child_info->chld_err    = chld_err[0];
1492         child_info->chld_status = chld_status[0];
1493         child_info->tag_in      = -1;
1494         child_info->tag_out     = gdk_input_add(chld_out[0], GDK_INPUT_READ,
1495                                                 catch_output, child_info);
1496         child_info->tag_err     = gdk_input_add(chld_err[0], GDK_INPUT_READ,
1497                                                 catch_output, child_info);
1498
1499         if (!(action_type & (ACTION_PIPE_IN | ACTION_PIPE_OUT | ACTION_INSERT)))
1500                 return child_info;
1501
1502         child_info->text        = text;
1503         child_info->msgfont     = msgfont;
1504
1505         start = body_pos;
1506         end   = gtk_stext_get_length(GTK_STEXT(text));
1507
1508         if (GTK_EDITABLE(text)->has_selection) {
1509                 start = GTK_EDITABLE(text)->selection_start_pos;
1510                 end   = GTK_EDITABLE(text)->selection_end_pos;
1511                 if (start > end) {
1512                         guint tmp;
1513                         tmp = start;
1514                         start = end;
1515                         end = tmp;
1516                 }
1517                 is_selection = TRUE;
1518                 if (start == end) {
1519                         start = 0;
1520                         end = gtk_stext_get_length(GTK_STEXT(text));
1521                         is_selection = FALSE;
1522                 }
1523         }
1524
1525         selection = gtk_editable_get_chars(GTK_EDITABLE(text), start, end);
1526
1527         if (action_type & ACTION_PIPE_IN) {
1528                 write(chld_in[1], selection, strlen(selection));
1529                 if (!(action_type & (ACTION_USER_IN | ACTION_USER_HIDDEN_IN)))
1530                         close(chld_in[1]);
1531                 child_info->chld_in = -1; /* No more input */
1532         }
1533         g_free(selection);
1534
1535         gtk_stext_freeze(GTK_STEXT(text));
1536         if (action_type & ACTION_PIPE_OUT) {
1537                 gtk_stext_set_point(GTK_STEXT(text), start);
1538                 gtk_stext_forward_delete(GTK_STEXT(text), end - start);
1539         }
1540
1541         gtk_stext_thaw(GTK_STEXT(text));
1542
1543         return child_info;
1544 }
1545
1546 static void kill_children_cb(GtkWidget *widget, gpointer data)
1547 {
1548         GSList *cur;
1549         Children *children = (Children *) data;
1550         ChildInfo *child_info;
1551
1552         for (cur = children->list; cur; cur = cur->next) {
1553                 child_info = (ChildInfo *)(cur->data);
1554                 debug_print("Killing child group id %d\n", child_info->pid);
1555                 if (child_info->pid && kill(-child_info->pid, SIGTERM) < 0)
1556                         perror("kill");
1557         }
1558 }
1559
1560 static gint wait_for_children(gpointer data)
1561 {
1562         gboolean new_output;
1563         Children *children = (Children *)data;
1564         ChildInfo *child_info;
1565         GSList *cur;
1566         gint nb = children->nb;
1567
1568         children->nb = 0;
1569
1570         cur = children->list;
1571         new_output = FALSE;
1572         while (cur) {
1573                 child_info = (ChildInfo *)cur->data;
1574                 if (child_info->pid)
1575                         children->nb++;
1576                 new_output |= child_info->new_out;
1577                 cur = cur->next;
1578         }
1579
1580         children->output |= new_output;
1581
1582         if (new_output || (children->dialog && (nb != children->nb)))
1583                 update_io_dialog(children);
1584
1585         if (children->nb)
1586                 return FALSE;
1587
1588         if (!children->dialog) {
1589                 free_children(children);
1590         } else if (!children->output) {
1591                 gtk_widget_destroy(children->dialog);
1592         }
1593
1594         return FALSE;
1595 }
1596
1597 static void send_input(GtkWidget *w, gpointer data)
1598 {
1599         Children *children = (Children *) data;
1600         ChildInfo *child_info = (ChildInfo *) children->list->data;
1601
1602         child_info->tag_in = gdk_input_add(child_info->chld_in,
1603                                            GDK_INPUT_WRITE,
1604                                            catch_input, children);
1605         gtk_widget_set_sensitive(children->input_hbox, FALSE);
1606 }
1607
1608 static gint delete_io_dialog_cb(GtkWidget *w, GdkEvent *e, gpointer data)
1609 {
1610         hide_io_dialog_cb(w, data);
1611         return TRUE;
1612 }
1613
1614 static void hide_io_dialog_cb(GtkWidget *w, gpointer data)
1615 {
1616
1617         Children *children = (Children *)data;
1618
1619         if (!children->nb) {
1620                 gtk_signal_disconnect_by_data(GTK_OBJECT(children->dialog),
1621                                               children);
1622                 gtk_widget_destroy(children->dialog);
1623                 free_children(children);
1624         }
1625 }
1626
1627 static gint io_dialog_key_pressed_cb(GtkWidget  *widget,
1628                                      GdkEventKey        *event,
1629                                      gpointer    data)
1630 {
1631         if (event && event->keyval == GDK_Escape)
1632                 hide_io_dialog_cb(widget, data);
1633         return TRUE;    
1634 }
1635
1636 static void childinfo_close_pipes(ChildInfo *child_info)
1637 {
1638         if (child_info->tag_in > 0)
1639                 gdk_input_remove(child_info->tag_in);
1640         gdk_input_remove(child_info->tag_out);
1641         gdk_input_remove(child_info->tag_err);
1642
1643         if (child_info->chld_in >= 0)
1644                 close(child_info->chld_in);
1645         close(child_info->chld_out);
1646         close(child_info->chld_err);
1647         close(child_info->chld_status);
1648 }
1649
1650 static void free_children(Children *children)
1651 {
1652         GSList *cur;
1653         ChildInfo *child_info;
1654
1655         debug_print("Freeing children data %p\n", children);
1656
1657         g_free(children->action);
1658         for (cur = children->list; cur;) {
1659                 child_info = (ChildInfo *)cur->data;
1660                 g_free(child_info->cmd);
1661                 g_string_free(child_info->output, TRUE);
1662                 children->list = g_slist_remove(children->list, child_info);
1663                 g_free(child_info);
1664                 cur = children->list;
1665         }
1666         g_free(children);
1667 }
1668
1669 static void update_io_dialog(Children *children)
1670 {
1671         GSList *cur;
1672
1673         debug_print("Updating actions input/output dialog.\n");
1674
1675         if (!children->nb) {
1676                 gtk_widget_set_sensitive(children->abort_btn, FALSE);
1677                 gtk_widget_set_sensitive(children->close_btn, TRUE);
1678                 if (children->input_hbox)
1679                         gtk_widget_set_sensitive(children->input_hbox, FALSE);
1680                 gtk_widget_grab_focus(children->close_btn);
1681                 gtk_signal_connect(GTK_OBJECT(children->dialog), 
1682                                    "key_press_event",
1683                                    GTK_SIGNAL_FUNC(io_dialog_key_pressed_cb),
1684                                    children);
1685         }
1686
1687         if (children->output) {
1688                 GtkWidget *text = children->text;
1689                 gchar *caption;
1690                 ChildInfo *child_info;
1691
1692                 gtk_widget_show(children->scrolledwin);
1693                 gtk_text_freeze(GTK_TEXT(text));
1694                 gtk_text_set_point(GTK_TEXT(text), 0);
1695                 gtk_text_forward_delete(GTK_TEXT(text), 
1696                                         gtk_text_get_length(GTK_TEXT(text)));
1697                 for (cur = children->list; cur; cur = cur->next) {
1698                         child_info = (ChildInfo *)cur->data;
1699                         if (child_info->pid)
1700                                 caption = g_strdup_printf
1701                                         (_("--- Running: %s\n"),
1702                                          child_info->cmd);
1703                         else
1704                                 caption = g_strdup_printf
1705                                         (_("--- Ended: %s\n"),
1706                                          child_info->cmd);
1707
1708                         gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL,
1709                                         caption, -1);
1710                         gtk_text_insert(GTK_TEXT(text), NULL, NULL, NULL,
1711                                         child_info->output->str, -1);
1712                         g_free(caption);
1713                         child_info->new_out = FALSE;
1714                 }
1715                 gtk_text_thaw(GTK_TEXT(text));
1716         }
1717 }
1718
1719 static void create_io_dialog(Children *children)
1720 {
1721         GtkWidget *dialog;
1722         GtkWidget *vbox;
1723         GtkWidget *entry = NULL;
1724         GtkWidget *input_hbox = NULL;
1725         GtkWidget *send_button;
1726         GtkWidget *label;
1727         GtkWidget *text;
1728         GtkWidget *scrolledwin;
1729         GtkWidget *hbox;
1730         GtkWidget *abort_button;
1731         GtkWidget *close_button;
1732
1733         debug_print("Creating action IO dialog\n");
1734
1735         dialog = gtk_dialog_new();
1736         gtk_container_set_border_width
1737                 (GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), 5);
1738         gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
1739         gtk_window_set_title(GTK_WINDOW(dialog), _("Action's input/output"));
1740         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
1741         manage_window_set_transient(GTK_WINDOW(dialog));
1742         gtk_signal_connect(GTK_OBJECT(dialog), "delete_event",
1743                         GTK_SIGNAL_FUNC(delete_io_dialog_cb), children);
1744         gtk_signal_connect(GTK_OBJECT(dialog), "destroy",
1745                         GTK_SIGNAL_FUNC(hide_io_dialog_cb),
1746                         children);
1747
1748         vbox = gtk_vbox_new(FALSE, 8);
1749         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), vbox);
1750         gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
1751         gtk_widget_show(vbox);
1752
1753         label = gtk_label_new(children->action);
1754         gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
1755         gtk_widget_show(label);
1756
1757         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
1758         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
1759                                        GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
1760         gtk_box_pack_start(GTK_BOX(vbox), scrolledwin, TRUE, TRUE, 0);
1761         gtk_widget_set_usize(scrolledwin, 480, 200);
1762         gtk_widget_hide(scrolledwin);
1763
1764         text = gtk_text_new(gtk_scrolled_window_get_hadjustment
1765                             (GTK_SCROLLED_WINDOW(scrolledwin)),
1766                             gtk_scrolled_window_get_vadjustment
1767                             (GTK_SCROLLED_WINDOW(scrolledwin)));
1768         gtk_text_set_editable(GTK_TEXT(text), FALSE);
1769         gtk_container_add(GTK_CONTAINER(scrolledwin), text);
1770         gtk_widget_show(text);
1771
1772         if (children->open_in) {
1773                 input_hbox = gtk_hbox_new(FALSE, 8);
1774                 gtk_widget_show(input_hbox);
1775
1776                 entry = gtk_entry_new();
1777                 gtk_widget_set_usize(entry, 320, -1);
1778                 gtk_signal_connect(GTK_OBJECT(entry), "activate",
1779                                    GTK_SIGNAL_FUNC(send_input), children);
1780                 gtk_box_pack_start(GTK_BOX(input_hbox), entry, TRUE, TRUE, 0);
1781                 if (children->open_in & ACTION_USER_HIDDEN_IN)
1782                         gtk_entry_set_visibility(GTK_ENTRY(entry), FALSE);
1783                 gtk_widget_show(entry);
1784
1785                 send_button = gtk_button_new_with_label(_(" Send "));
1786                 gtk_signal_connect(GTK_OBJECT(send_button), "clicked",
1787                                    GTK_SIGNAL_FUNC(send_input), children);
1788                 gtk_box_pack_start(GTK_BOX(input_hbox), send_button, FALSE,
1789                                    FALSE, 0);
1790                 gtk_widget_show(send_button);
1791
1792                 gtk_box_pack_start(GTK_BOX(vbox), input_hbox, FALSE, FALSE, 0);
1793                 gtk_widget_grab_focus(entry);
1794         }
1795
1796         gtkut_button_set_create(&hbox, &abort_button, _("Abort"),
1797                                 &close_button, _("Close"), NULL, NULL);
1798         gtk_signal_connect(GTK_OBJECT(abort_button), "clicked",
1799                         GTK_SIGNAL_FUNC(kill_children_cb), children);
1800         gtk_signal_connect(GTK_OBJECT(close_button), "clicked",
1801                         GTK_SIGNAL_FUNC(hide_io_dialog_cb), children);
1802         gtk_widget_show(hbox);
1803
1804         if (children->nb)
1805                 gtk_widget_set_sensitive(close_button, FALSE);
1806
1807         gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), hbox);
1808
1809         children->dialog      = dialog;
1810         children->scrolledwin = scrolledwin;
1811         children->text        = text;
1812         children->input_hbox  = children->open_in ? input_hbox : NULL;
1813         children->input_entry = children->open_in ? entry : NULL;
1814         children->abort_btn   = abort_button;
1815         children->close_btn   = close_button;
1816
1817         gtk_widget_show(dialog);
1818 }
1819
1820 static void catch_status(gpointer data, gint source, GdkInputCondition cond)
1821 {
1822         ChildInfo *child_info = (ChildInfo *)data;
1823         gchar buf;
1824         gint c;
1825
1826         gdk_input_remove(child_info->tag_status);
1827
1828         c = read(source, &buf, 1);
1829         debug_print("Child returned %c\n", buf);
1830
1831         waitpid(-child_info->pid, NULL, 0);
1832         childinfo_close_pipes(child_info);
1833         child_info->pid = 0;
1834
1835         wait_for_children(child_info->children);
1836 }
1837         
1838 static void catch_input(gpointer data, gint source, GdkInputCondition cond)
1839 {
1840         Children *children = (Children *)data;
1841         ChildInfo *child_info = (ChildInfo *)children->list->data;
1842         gchar *input;
1843         gint c, count, len;
1844
1845         debug_print("Sending input to grand child.\n");
1846         if (!(cond && GDK_INPUT_WRITE))
1847                 return;
1848
1849         gdk_input_remove(child_info->tag_in);
1850         child_info->tag_in = -1;
1851
1852         input = gtk_editable_get_chars(GTK_EDITABLE(children->input_entry),
1853                                        0, -1);
1854         len = strlen(input);
1855         count = 0;
1856         
1857         do {
1858                 c = write(child_info->chld_in, input + count, len - count);
1859                 if (c >= 0)
1860                         count += c;
1861
1862         } while (c >= 0 && count < len);
1863
1864         if (c >= 0)
1865                 write(child_info->chld_in, "\n", 2);
1866
1867         g_free(input);
1868
1869         gtk_entry_set_text(GTK_ENTRY(children->input_entry), "");
1870         gtk_widget_set_sensitive(children->input_hbox, TRUE);
1871         close(child_info->chld_in);
1872         child_info->chld_in = -1;
1873         debug_print("Input to grand child sent.\n");
1874 }
1875
1876 static void catch_output(gpointer data, gint source, GdkInputCondition cond)
1877 {
1878         ChildInfo *child_info = (ChildInfo *)data;
1879         gint c, i;
1880         gchar buf[PREFSBUFSIZE];
1881
1882         debug_print("Catching grand child's output.\n");
1883         if (child_info->type & (ACTION_PIPE_OUT | ACTION_INSERT)
1884             && source == child_info->chld_out) {
1885                 gboolean is_selection = FALSE;
1886                 GtkWidget *text = child_info->text;
1887                 if (GTK_EDITABLE(text)->has_selection)
1888                         is_selection = TRUE;
1889                 gtk_stext_freeze(GTK_STEXT(text));
1890                 while (TRUE) {
1891                         c = read(source, buf, PREFSBUFSIZE - 1);
1892                         if (c == 0)
1893                                 break;
1894                         gtk_stext_insert(GTK_STEXT(text), child_info->msgfont,
1895                                          NULL, NULL, buf, c);
1896                 }
1897                 if (is_selection) {
1898                         /* Using the select_region draws things. Should not.
1899                          * so we just change selection position and 
1900                          * defere drawing when thawing. Hack?
1901                          */
1902                         GTK_EDITABLE(text)->selection_end_pos =
1903                                         gtk_stext_get_point(GTK_STEXT(text));
1904                 }
1905                 gtk_stext_thaw(GTK_STEXT(child_info->text));
1906         } else {
1907                 c = read(source, buf, PREFSBUFSIZE - 1);
1908                 for (i = 0; i < c; i++)
1909                         child_info->output = g_string_append_c
1910                                 (child_info->output, buf[i]);
1911                 if (c > 0)
1912                         child_info->new_out = TRUE;
1913         }
1914         wait_for_children(child_info->children);
1915 }
1916
1917 static gboolean user_string_dialog_delete_cb(GtkWidget  *widget,
1918                                              GdkEvent   *event,
1919                                              gpointer    data)
1920 {
1921         return FALSE;
1922 }
1923
1924 static void user_string_dialog_destroy_cb(GtkWidget *widget, gpointer data)
1925 {
1926         gtk_main_quit();
1927 }
1928
1929 static void user_string_dialog_activate_cb(GtkWidget *widget, gpointer data)
1930 {
1931         UserStringDialog *user_dialog = (UserStringDialog *) data;
1932         if (user_dialog->user_str)
1933                 g_free(user_dialog->user_str);
1934         user_dialog->user_str =
1935                 gtk_editable_get_chars(GTK_EDITABLE(user_dialog->entry), 0, -1);
1936         gtk_widget_destroy(user_dialog->dialog);
1937 }
1938
1939 static gchar *get_user_string(const gchar *action, ActionType type )
1940 {
1941         GtkWidget       *dialog;
1942         GtkWidget       *label;
1943         GtkWidget       *entry;
1944         GtkWidget       *ok_button;
1945         GtkWidget       *cancel_button;
1946         gchar           *label_text;
1947         UserStringDialog user_dialog;
1948         
1949         dialog = gtk_dialog_new();
1950         gtk_window_set_policy(GTK_WINDOW(dialog), FALSE, FALSE, FALSE);
1951         gtk_container_set_border_width(
1952                         GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), 8);
1953         gtk_container_set_border_width(
1954                         GTK_CONTAINER(GTK_DIALOG(dialog)->action_area), 5);
1955         gtk_window_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
1956
1957         switch (type) {
1958                 case ACTION_USER_HIDDEN_STR:
1959                         gtk_window_set_title(GTK_WINDOW(dialog),
1960                                         _("Action's user hidden argument"));
1961                         label_text = g_strdup_printf(_("Enter the '%%h' " 
1962                                                 "argument for the following "
1963                                                 "action:\n%s"), action);
1964                         break;
1965                 case ACTION_USER_STR:
1966                         gtk_window_set_title(GTK_WINDOW(dialog),
1967                                         _("Action's user argument"));
1968                         label_text = g_strdup_printf(_("Enter the '%%u' " 
1969                                                 "argument for the following "
1970                                                 "action:\n%s"), action);
1971                         break;
1972                 default:
1973                         label_text = NULL;
1974                         debug_print("Unsupported action type %d", type);
1975         }
1976
1977         label = gtk_label_new(label_text);
1978         g_free(label_text);
1979         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), label,
1980                            TRUE, TRUE, 0);
1981         
1982         entry = gtk_entry_new();
1983         gtk_entry_set_visibility(GTK_ENTRY(entry), 
1984                                  type != ACTION_USER_HIDDEN_STR);
1985         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), entry,
1986                            TRUE, TRUE, 0);
1987
1988         ok_button     = gtk_button_new_with_label(_("OK"));
1989         gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1990                          ok_button, TRUE, TRUE, 0);
1991                                 
1992         cancel_button = gtk_button_new_with_label(_("Cancel"));
1993         gtk_box_pack_end(GTK_BOX(GTK_DIALOG(dialog)->action_area),
1994                          cancel_button, TRUE, TRUE, 0);
1995
1996         user_dialog.dialog   = dialog;
1997         user_dialog.user_str = NULL;
1998         user_dialog.entry    = entry;
1999
2000         gtk_signal_connect(GTK_OBJECT(dialog), "delete_event",
2001                            GTK_SIGNAL_FUNC(user_string_dialog_delete_cb),
2002                            &user_dialog);
2003         gtk_signal_connect(GTK_OBJECT(dialog), "destroy",
2004                            GTK_SIGNAL_FUNC(user_string_dialog_destroy_cb),
2005                            &user_dialog);
2006         gtk_signal_connect(GTK_OBJECT(entry), "activate",
2007                            GTK_SIGNAL_FUNC(user_string_dialog_activate_cb),
2008                            &user_dialog);
2009         gtk_signal_connect(GTK_OBJECT(ok_button),     "clicked",
2010                            GTK_SIGNAL_FUNC(user_string_dialog_activate_cb),
2011                            &user_dialog);
2012         gtk_signal_connect_object(GTK_OBJECT(cancel_button), "clicked",
2013                            GTK_SIGNAL_FUNC(gtk_widget_destroy),
2014                            GTK_OBJECT(dialog));
2015         
2016         gtk_widget_grab_focus(entry);
2017
2018         gtk_widget_show_all(dialog);
2019
2020         gtk_main();
2021
2022         return user_dialog.user_str;
2023 }
2024
2025 /*
2026  * Strings describing action format strings
2027  * 
2028  * When adding new lines, remember to put one string for each line
2029  */
2030 static gchar *actions_desc_strings[] = {
2031         N_("MENU NAME:"), NULL,
2032         "      ",   N_("Use / in menu name to make submenus."),
2033         "", NULL,
2034         N_("COMMAND LINE:"), NULL,
2035         N_("Begin with:"), NULL,
2036         "     |",   N_("to send message body or selection to command"),
2037         "     >",   N_("to send user provided text to command"),
2038         "     *",   N_("to send user provided hidden text to command"),
2039         N_("End with:"), NULL, 
2040         "     |",   N_("to replace message body or selection with command output"),
2041         "     >",   N_("to insert command's output without replacing old text"),
2042         "     &",   N_("to run command asynchronously"),
2043         N_("Use:"), NULL, 
2044         "     %f",  N_("for message file name"),
2045         "     %F",  N_("for the list of the file names of selected messages"),
2046         "     %p",  N_("for the selected message MIME part"),
2047         "     %u",  N_("for a user provided argument"),
2048         "     %h",  N_("for a user provided hidden argument"),
2049         "     %s",  N_("for the text selection"),
2050         NULL
2051 };
2052
2053
2054 static DescriptionWindow actions_desc_win = { 
2055         NULL, 
2056         2,
2057         N_("Description of symbols"),
2058         actions_desc_strings
2059 };
2060
2061
2062 static void prefs_actions_help_cb(GtkWidget *w, gpointer data)
2063 {
2064         description_window_create(&actions_desc_win);
2065 }