* Add the Control-R subcommand to the save command for attachments.
[alpine.git] / alpine / mailcmd.c
blob9a37e7b4adacf5e96343b46e36dd53a6bf36bf1d
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: mailcmd.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2013-2015 Eduardo Chappa
8 * Copyright 2006-2009 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
20 mailcmd.c
21 The meat and pototoes of mail processing here:
22 - initial command processing and dispatch
23 - save message
24 - capture address off incoming mail
25 - jump to specific numbered message
26 - open (broach) a new folder
27 - search message headers (where is) command
28 ====*/
31 #include "headers.h"
32 #include "mailcmd.h"
33 #include "status.h"
34 #include "mailview.h"
35 #include "flagmaint.h"
36 #include "listsel.h"
37 #include "keymenu.h"
38 #include "alpine.h"
39 #include "mailpart.h"
40 #include "mailindx.h"
41 #include "folder.h"
42 #include "reply.h"
43 #include "help.h"
44 #include "titlebar.h"
45 #include "signal.h"
46 #include "radio.h"
47 #include "pipe.h"
48 #include "send.h"
49 #include "takeaddr.h"
50 #include "roleconf.h"
51 #include "smime.h"
52 #include "../pith/state.h"
53 #include "../pith/msgno.h"
54 #include "../pith/store.h"
55 #include "../pith/thread.h"
56 #include "../pith/flag.h"
57 #include "../pith/sort.h"
58 #include "../pith/maillist.h"
59 #include "../pith/save.h"
60 #include "../pith/pipe.h"
61 #include "../pith/news.h"
62 #include "../pith/util.h"
63 #include "../pith/sequence.h"
64 #include "../pith/keyword.h"
65 #include "../pith/stream.h"
66 #include "../pith/mailcmd.h"
67 #include "../pith/hist.h"
68 #include "../pith/list.h"
69 #include "../pith/icache.h"
70 #include "../pith/busy.h"
71 #include "../pith/mimedesc.h"
72 #include "../pith/pattern.h"
73 #include "../pith/tempfile.h"
74 #include "../pith/search.h"
75 #include "../pith/margin.h"
76 #ifdef _WINDOWS
77 #include "../pico/osdep/mswin.h"
78 #endif
81 * Internal Prototypes
83 int cmd_flag(struct pine *, MSGNO_S *, int);
84 int cmd_flag_prompt(struct pine *, struct flag_screen *, int);
85 void free_flag_table(struct flag_table **);
86 int cmd_reply(struct pine *, MSGNO_S *, int);
87 int cmd_forward(struct pine *, MSGNO_S *, int);
88 int cmd_bounce(struct pine *, MSGNO_S *, int);
89 int cmd_save(struct pine *, MAILSTREAM *, MSGNO_S *, int, CmdWhere);
90 void role_compose(struct pine *);
91 int cmd_expunge(struct pine *, MAILSTREAM *, MSGNO_S *, int);
92 int cmd_export(struct pine *, MSGNO_S *, int, int);
93 char *cmd_delete_action(struct pine *, MSGNO_S *, CmdWhere);
94 char *cmd_delete_view(struct pine *, MSGNO_S *);
95 char *cmd_delete_index(struct pine *, MSGNO_S *);
96 long get_level(int, UCS, SCROLL_S *);
97 long closest_jump_target(long, MAILSTREAM *, MSGNO_S *, int, CmdWhere, char *, size_t);
98 int update_folder_spec(char *, size_t, char *);
99 int cmd_print(struct pine *, MSGNO_S *, int, CmdWhere);
100 int cmd_pipe(struct pine *, MSGNO_S *, int);
101 STORE_S *list_mgmt_text(RFC2369_S *, long);
102 void list_mgmt_screen(STORE_S *);
103 int aggregate_select(struct pine *, MSGNO_S *, int, CmdWhere);
104 int select_by_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
105 int select_by_thrd_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
106 int select_by_date(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
107 int select_by_text(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
108 int select_by_size(MAILSTREAM *, SEARCHSET **);
109 SEARCHSET *visible_searchset(MAILSTREAM *, MSGNO_S *);
110 int select_by_status(MAILSTREAM *, SEARCHSET **);
111 int select_by_rule(MAILSTREAM *, SEARCHSET **);
112 int select_by_thread(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
113 char *choose_a_rule(int);
114 int select_by_keyword(MAILSTREAM *, SEARCHSET **);
115 char *choose_a_keyword(void);
116 int select_sort(struct pine *, int, SortOrder *, int *);
117 int print_index(struct pine *, MSGNO_S *, int);
120 * List of Select options used by apply_* functions...
122 static char *sel_pmt1 = N_("ALTER message selection : ");
123 ESCKEY_S sel_opts1[] = {
124 /* TRANSLATORS: these are keymenu names for selecting. Broaden selection means
125 we will add more messages to the selection, Narrow selection means we will
126 remove some selections (like a logical AND instead of logical OR), and Flip
127 Selected means that all the messages that are currently selected become unselected,
128 and all the unselected messages become selected. */
129 {'a', 'a', "A", N_("unselect All")},
130 {'c', 'c', "C", NULL},
131 {'b', 'b', "B", N_("Broaden selctn")},
132 {'n', 'n', "N", N_("Narrow selctn")},
133 {'f', 'f', "F", N_("Flip selected")},
134 {-1, 0, NULL, NULL}
138 #define SEL_OPTS_THREAD 9 /* index number of "tHread" */
139 #define SEL_OPTS_THREAD_CH 'h'
141 char *sel_pmt2 = "SELECT criteria : ";
142 static ESCKEY_S sel_opts2[] = {
143 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
144 means select the currently highlighted message; select by Number is by message
145 number; Status is by status of the message, for example the message might be
146 New or it might be Unseen or marked Important; Size has the Z upper case because
147 it is a Z command; Keyword is an alpine keyword that has been set by the user;
148 and Rule is an alpine rule */
149 {'a', 'a', "A", N_("select All")},
150 {'c', 'c', "C", N_("select Cur")},
151 {'n', 'n', "N", N_("Number")},
152 {'d', 'd', "D", N_("Date")},
153 {'t', 't', "T", N_("Text")},
154 {'s', 's', "S", N_("Status")},
155 {'z', 'z', "Z", N_("siZe")},
156 {'k', 'k', "K", N_("Keyword")},
157 {'r', 'r', "R", N_("Rule")},
158 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
159 {-1, 0, NULL, NULL}
163 static ESCKEY_S sel_opts3[] = {
164 /* TRANSLATORS: these are operations we can do on a set of selected messages.
165 Del is Delete; Undel is Undelete; TakeAddr means to Take some Addresses into
166 the address book; Save means to save the messages into another alpine folder;
167 Export means to copy the messages to a file outside of alpine, external to
168 alpine's world. */
169 {'d', 'd', "D", N_("Del")},
170 {'u', 'u', "U", N_("Undel")},
171 {'r', 'r', "R", N_("Reply")},
172 {'f', 'f', "F", N_("Forward")},
173 {'%', '%', "%", N_("Print")},
174 {'t', 't', "T", N_("TakeAddr")},
175 {'s', 's', "S", N_("Save")},
176 {'e', 'e', "E", N_("Export")},
177 { -1, 0, NULL, NULL},
178 { -1, 0, NULL, NULL},
179 { -1, 0, NULL, NULL},
180 { -1, 0, NULL, NULL},
181 { -1, 0, NULL, NULL},
182 { -1, 0, NULL, NULL},
183 { -1, 0, NULL, NULL},
184 {-1, 0, NULL, NULL}
187 static ESCKEY_S sel_opts4[] = {
188 {'a', 'a', "A", N_("select All")},
189 /* TRANSLATORS: select currrently highlighted message Thread */
190 {'c', 'c', "C", N_("select Curthrd")},
191 {'n', 'n', "N", N_("Number")},
192 {'d', 'd', "D", N_("Date")},
193 {'t', 't', "T", N_("Text")},
194 {'s', 's', "S", N_("Status")},
195 {'z', 'z', "Z", N_("siZe")},
196 {'k', 'k', "K", N_("Keyword")},
197 {'r', 'r', "R", N_("Rule")},
198 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
199 {-1, 0, NULL, NULL}
203 static char *sel_flag =
204 N_("Select New, Deleted, Answered, Forwarded, or Important messages ? ");
205 static char *sel_flag_not =
206 N_("Select NOT New, NOT Deleted, NOT Answered, NOT Forwarded or NOT Important msgs ? ");
207 static ESCKEY_S sel_flag_opt[] = {
208 /* TRANSLATORS: When selecting messages by message Status these are the
209 different types of Status you can select on. Is the message New, Recent,
210 and so on. Not means flip the meaning of the selection to the opposite
211 thing, so message is not New or not Important. */
212 {'n', 'n', "N", N_("New")},
213 {'*', '*', "*", N_("Important")},
214 {'d', 'd', "D", N_("Deleted")},
215 {'a', 'a', "A", N_("Answered")},
216 {'f', 'f', "F", N_("Forwarded")},
217 {-2, 0, NULL, NULL},
218 {'!', '!', "!", N_("Not")},
219 {-2, 0, NULL, NULL},
220 {'r', 'r', "R", N_("Recent")},
221 {'u', 'u', "U", N_("Unseen")},
222 {-1, 0, NULL, NULL}
226 static ESCKEY_S sel_date_opt[] = {
227 {0, 0, NULL, NULL},
228 /* TRANSLATORS: options when selecting messages by Date */
229 {ctrl('P'), 12, "^P", N_("Prev Day")},
230 {ctrl('N'), 13, "^N", N_("Next Day")},
231 {ctrl('X'), 11, "^X", N_("Cur Msg")},
232 {ctrl('W'), 14, "^W", N_("Toggle When")},
233 {KEY_UP, 12, "", ""},
234 {KEY_DOWN, 13, "", ""},
235 {-1, 0, NULL, NULL}
239 static char *sel_text =
240 N_("Select based on To, From, Cc, Recip, Partic, Subject fields or All msg text ? ");
241 static char *sel_text_not =
242 N_("Select based on NOT To, From, Cc, Recip, Partic, Subject or All msg text ? ");
243 static ESCKEY_S sel_text_opt[] = {
244 /* TRANSLATORS: Select messages based on the text contained in the From line, or
245 the Subject line, and so on. */
246 {'f', 'f', "F", N_("From")},
247 {'s', 's', "S", N_("Subject")},
248 {'t', 't', "T", N_("To")},
249 {'a', 'a', "A", N_("All Text")},
250 {'c', 'c', "C", N_("Cc")},
251 {'!', '!', "!", N_("Not")},
252 {'r', 'r', "R", N_("Recipient")},
253 {'p', 'p', "P", N_("Participant")},
254 {'b', 'b', "B", N_("Body")},
255 {'h', 'h', "H", N_("Header")},
256 {-1, 0, NULL, NULL}
259 static ESCKEY_S choose_action[] = {
260 {'c', 'c', "C", N_("Compose")},
261 {'r', 'r', "R", N_("Reply")},
262 {'f', 'f', "F", N_("Forward")},
263 {'b', 'b', "B", N_("Bounce")},
264 {-1, 0, NULL, NULL}
267 static char *select_num =
268 N_("Enter comma-delimited list of numbers (dash between ranges): ");
270 static char *select_size_larger_msg =
271 N_("Select messages with size larger than: ");
273 static char *select_size_smaller_msg =
274 N_("Select messages with size smaller than: ");
276 static char *sel_size_larger = N_("Larger");
277 static char *sel_size_smaller = N_("Smaller");
278 static ESCKEY_S sel_size_opt[] = {
279 {0, 0, NULL, NULL},
280 {ctrl('W'), 14, "^W", NULL},
281 {-1, 0, NULL, NULL}
284 static ESCKEY_S sel_key_opt[] = {
285 {0, 0, NULL, NULL},
286 {ctrl('T'), 14, "^T", N_("To List")},
287 {0, 0, NULL, NULL},
288 {'!', '!', "!", N_("Not")},
289 {-1, 0, NULL, NULL}
292 static ESCKEY_S flag_text_opt[] = {
293 /* TRANSLATORS: these are types of flags (markers) that the user can
294 set. For example, they can flag the message as an important message. */
295 {'n', 'n', "N", N_("New")},
296 {'*', '*', "*", N_("Important")},
297 {'d', 'd', "D", N_("Deleted")},
298 {'a', 'a', "A", N_("Answered")},
299 {'f', 'f', "F", N_("Forwarded")},
300 {'!', '!', "!", N_("Not")},
301 {ctrl('T'), 10, "^T", N_("To Flag Details")},
302 {-1, 0, NULL, NULL}
305 int
306 alpine_get_password(char *prompt, char *pass, size_t len)
308 int flags = OE_PASSWD | OE_DISALLOW_HELP;
309 pass[0] = '\0';
310 return optionally_enter(pass,
311 -(ps_global->ttyo ? FOOTER_ROWS(ps_global) : 3),
312 0, len, prompt, NULL, NO_HELP, &flags);
315 int smime_import_certificate(char *filename, char *full_filename, size_t len)
317 int r = 1;
318 static HISTORY_S *history = NULL;
319 static ESCKEY_S eopts[] = {
320 {ctrl('T'), 10, "^T", N_("To Files")},
321 {-1, 0, NULL, NULL},
322 {-1, 0, NULL, NULL}};
324 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
325 eopts[r].ch = ctrl('I');
326 eopts[r].rval = 11;
327 eopts[r].name = "TAB";
328 eopts[r].label = N_("Complete");
331 eopts[++r].ch = -1;
333 filename[0] = '\0';
334 full_filename[0] = '\0';
336 r = get_export_filename(ps_global, filename, NULL, full_filename,
337 len, "certificate", "IMPORT", eopts, NULL,
338 -FOOTER_ROWS(ps_global), GE_IS_IMPORT, &history);
340 return r;
344 /*----------------------------------------------------------------------
345 The giant switch on the commands for index and viewing
347 Input: command -- The command char/code
348 in_index -- flag indicating command is from index
349 orig_command -- The original command typed before pre-processing
350 Output: force_mailchk -- Set to tell caller to force call to new_mail().
352 Result: Manifold
354 Returns 1 if the message number or attachment to show changed
355 ---*/
357 process_cmd(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
358 int command, CmdWhere in_index, int *force_mailchk)
360 int question_line, a_changed, flags = 0, ret, j;
361 int notrealinbox;
362 long new_msgno, del_count, old_msgno, i;
363 long start;
364 char *newfolder, prompt[MAX_SCREEN_COLS+1];
365 CONTEXT_S *tc;
366 MESSAGECACHE *mc;
367 #if defined(DOS) && !defined(_WINDOWS)
368 extern long coreleft();
369 #endif
371 dprint((4, "\n - process_cmd(cmd=%d) -\n", command));
373 question_line = -FOOTER_ROWS(state);
374 state->mangled_screen = 0;
375 state->mangled_footer = 0;
376 state->mangled_header = 0;
377 state->next_screen = SCREEN_FUN_NULL;
378 old_msgno = mn_get_cur(msgmap);
379 a_changed = FALSE;
380 *force_mailchk = 0;
382 switch (command) {
383 /*------------- Help --------*/
384 case MC_HELP :
386 * We're not using the h_mail_view portion of this right now because
387 * that call is being handled in scrolltool() before it gets
388 * here. Leave it in case we change how it works.
390 helper((in_index == MsgIndx)
391 ? h_mail_index
392 : (in_index == View)
393 ? h_mail_view
394 : h_mail_thread_index,
395 (in_index == MsgIndx)
396 ? _("HELP FOR MESSAGE INDEX")
397 : (in_index == View)
398 ? _("HELP FOR MESSAGE TEXT")
399 : _("HELP FOR THREAD INDEX"),
400 HLPD_NONE);
401 dprint((4,"MAIL_CMD: did help command\n"));
402 state->mangled_screen = 1;
403 break;
406 /*--------- Return to main menu ------------*/
407 case MC_MAIN :
408 state->next_screen = main_menu_screen;
409 dprint((2,"MAIL_CMD: going back to main menu\n"));
410 break;
413 /*------- View message text --------*/
414 case MC_VIEW_TEXT :
415 view_text:
416 if(any_messages(msgmap, NULL, "to View")){
417 state->next_screen = mail_view_screen;
420 break;
423 /*------- View attachment --------*/
424 case MC_VIEW_ATCH :
425 state->next_screen = attachment_screen;
426 dprint((2,"MAIL_CMD: going to attachment screen\n"));
427 break;
430 /*---------- Previous message ----------*/
431 case MC_PREVITEM :
432 if(any_messages(msgmap, NULL, NULL)){
433 if((i = mn_get_cur(msgmap)) > 1L){
434 mn_dec_cur(stream, msgmap,
435 (in_index == View && THREADING()
436 && sp_viewing_a_thread(stream))
437 ? MH_THISTHD
438 : (in_index == View)
439 ? MH_ANYTHD : MH_NONE);
440 if(i == mn_get_cur(msgmap)){
441 PINETHRD_S *thrd = NULL, *topthrd = NULL;
443 if(THRD_INDX_ENABLED()){
444 mn_dec_cur(stream, msgmap, MH_ANYTHD);
445 if(i == mn_get_cur(msgmap))
446 q_status_message1(SM_ORDER, 0, 2,
447 _("Already on first %s in Zoomed Index"),
448 THRD_INDX() ? _("thread") : _("message"));
449 else{
450 if(in_index == View
451 || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
452 ret = 'y';
453 else
454 ret = want_to(_("View previous thread"), 'y', 'x',
455 NO_HELP, WT_NORM);
457 if(ret == 'y'){
458 q_status_message(SM_ORDER, 0, 2,
459 _("Viewing previous thread"));
460 new_msgno = mn_get_cur(msgmap);
461 mn_set_cur(msgmap, i);
462 if(unview_thread(state, stream, msgmap)){
463 state->next_screen = mail_index_screen;
464 state->view_skipped_index = 0;
465 state->mangled_screen = 1;
468 mn_set_cur(msgmap, new_msgno);
469 if(THRD_AUTO_VIEW() && in_index == View){
471 thrd = fetch_thread(stream,
472 mn_m2raw(msgmap,
473 new_msgno));
474 if(count_lflags_in_thread(stream, thrd,
475 msgmap,
476 MN_NONE) == 1){
477 if(view_thread(state, stream, msgmap, 1)){
478 if(current_index_state)
479 msgmap->top_after_thrd = current_index_state->msg_at_top;
481 state->view_skipped_index = 1;
482 command = MC_VIEW_TEXT;
483 goto view_text;
488 j = 0;
489 if(THRD_AUTO_VIEW() && in_index != View){
490 thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
491 if(thrd && thrd->top)
492 topthrd = fetch_thread(stream, thrd->top);
494 if(topthrd)
495 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
498 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
499 if(view_thread(state, stream, msgmap, 1)
500 && current_index_state)
501 msgmap->top_after_thrd = current_index_state->msg_at_top;
505 state->next_screen = SCREEN_FUN_NULL;
507 else
508 mn_set_cur(msgmap, i); /* put it back */
511 else
512 q_status_message1(SM_ORDER, 0, 2,
513 _("Already on first %s in Zoomed Index"),
514 THRD_INDX() ? _("thread") : _("message"));
517 else{
518 time_t now;
520 if(!IS_NEWS(stream)
521 && ((now = time(0)) > state->last_nextitem_forcechk)){
522 *force_mailchk = 1;
523 /* check at most once a second */
524 state->last_nextitem_forcechk = now;
527 q_status_message1(SM_ORDER, 0, 1, _("Already on first %s"),
528 THRD_INDX() ? _("thread") : _("message"));
532 break;
535 /*---------- Next Message ----------*/
536 case MC_NEXTITEM :
537 if(mn_get_total(msgmap) > 0L
538 && ((i = mn_get_cur(msgmap)) < mn_get_total(msgmap))){
539 mn_inc_cur(stream, msgmap,
540 (in_index == View && THREADING()
541 && sp_viewing_a_thread(stream))
542 ? MH_THISTHD
543 : (in_index == View)
544 ? MH_ANYTHD : MH_NONE);
545 if(i == mn_get_cur(msgmap)){
546 PINETHRD_S *thrd, *topthrd;
548 if(THRD_INDX_ENABLED()){
549 if(!THRD_INDX())
550 mn_inc_cur(stream, msgmap, MH_ANYTHD);
552 if(i == mn_get_cur(msgmap)){
553 if(any_lflagged(msgmap, MN_HIDE))
554 any_messages(NULL, "more", "in Zoomed Index");
555 else
556 goto nfolder;
558 else{
559 if(in_index == View
560 || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
561 ret = 'y';
562 else
563 ret = want_to(_("View next thread"), 'y', 'x',
564 NO_HELP, WT_NORM);
566 if(ret == 'y'){
567 q_status_message(SM_ORDER, 0, 2,
568 _("Viewing next thread"));
569 new_msgno = mn_get_cur(msgmap);
570 mn_set_cur(msgmap, i);
571 if(unview_thread(state, stream, msgmap)){
572 state->next_screen = mail_index_screen;
573 state->view_skipped_index = 0;
574 state->mangled_screen = 1;
577 mn_set_cur(msgmap, new_msgno);
578 if(THRD_AUTO_VIEW() && in_index == View){
580 thrd = fetch_thread(stream,
581 mn_m2raw(msgmap,
582 new_msgno));
583 if(count_lflags_in_thread(stream, thrd,
584 msgmap,
585 MN_NONE) == 1){
586 if(view_thread(state, stream, msgmap, 1)){
587 if(current_index_state)
588 msgmap->top_after_thrd = current_index_state->msg_at_top;
590 state->view_skipped_index = 1;
591 command = MC_VIEW_TEXT;
592 goto view_text;
597 j = 0;
598 if(THRD_AUTO_VIEW() && in_index != View){
599 thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
600 if(thrd && thrd->top)
601 topthrd = fetch_thread(stream, thrd->top);
603 if(topthrd)
604 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
607 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
608 if(view_thread(state, stream, msgmap, 1)
609 && current_index_state)
610 msgmap->top_after_thrd = current_index_state->msg_at_top;
614 state->next_screen = SCREEN_FUN_NULL;
616 else
617 mn_set_cur(msgmap, i); /* put it back */
620 else if(THREADING()
621 && (thrd = fetch_thread(stream, mn_m2raw(msgmap, i)))
622 && thrd->next
623 && get_lflag(stream, NULL, thrd->rawno, MN_COLL)){
624 q_status_message(SM_ORDER, 0, 2,
625 _("Expand collapsed thread to see more messages"));
627 else
628 any_messages(NULL, "more", "in Zoomed Index");
631 else{
632 time_t now;
633 nfolder:
634 prompt[0] = '\0';
635 if(IS_NEWS(stream)
636 || (state->context_current->use & CNTXT_INCMNG)){
637 char nextfolder[MAXPATH];
639 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
640 nextfolder[sizeof(nextfolder)-1] = '\0';
641 if(next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
642 state->context_current, NULL, NULL))
643 strncpy(prompt, _(". Press TAB for next folder."),
644 sizeof(prompt));
645 else
646 strncpy(prompt, _(". No more folders to TAB to."),
647 sizeof(prompt));
649 prompt[sizeof(prompt)-1] = '\0';
652 any_messages(NULL, (mn_get_total(msgmap) > 0L) ? "more" : NULL,
653 prompt[0] ? prompt : NULL);
655 if(!IS_NEWS(stream)
656 && ((now = time(0)) > state->last_nextitem_forcechk)){
657 *force_mailchk = 1;
658 /* check at most once a second */
659 state->last_nextitem_forcechk = now;
663 break;
666 /*---------- Delete message ----------*/
667 case MC_DELETE :
668 (void) cmd_delete(state, msgmap, MCMD_NONE,
669 (in_index == View) ? cmd_delete_view : cmd_delete_index);
670 break;
673 /*---------- Undelete message ----------*/
674 case MC_UNDELETE :
675 (void) cmd_undelete(state, msgmap, MCMD_NONE);
676 update_titlebar_status();
677 break;
680 /*---------- Reply to message ----------*/
681 case MC_REPLY :
682 (void) cmd_reply(state, msgmap, MCMD_NONE);
683 break;
686 /*---------- Forward message ----------*/
687 case MC_FORWARD :
688 (void) cmd_forward(state, msgmap, MCMD_NONE);
689 break;
692 /*---------- Quit pine ------------*/
693 case MC_QUIT :
694 state->next_screen = quit_screen;
695 dprint((1,"MAIL_CMD: quit\n"));
696 break;
699 /*---------- Compose message ----------*/
700 case MC_COMPOSE :
701 state->prev_screen = (in_index == View) ? mail_view_screen
702 : mail_index_screen;
703 compose_screen(state);
704 state->mangled_screen = 1;
705 if (state->next_screen)
706 a_changed = TRUE;
707 break;
710 /*---------- Alt Compose message ----------*/
711 case MC_ROLE :
712 state->prev_screen = (in_index == View) ? mail_view_screen
713 : mail_index_screen;
714 role_compose(state);
715 if(state->next_screen)
716 a_changed = TRUE;
718 break;
721 /*--------- Folders menu ------------*/
722 case MC_FOLDERS :
723 state->start_in_context = 1;
725 /*--------- Top of Folders list menu ------------*/
726 case MC_COLLECTIONS :
727 state->next_screen = folder_screen;
728 dprint((2,"MAIL_CMD: going to folder/collection menu\n"));
729 break;
732 /*---------- Open specific new folder ----------*/
733 case MC_GOTO :
734 tc = (state->context_last && !NEWS_TEST(state->context_current))
735 ? state->context_last : state->context_current;
737 newfolder = broach_folder(question_line, 1, &notrealinbox, &tc);
738 if(newfolder){
739 visit_folder(state, newfolder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT);
740 a_changed = TRUE;
743 break;
746 /*------- Go to Index Screen ----------*/
747 case MC_INDEX :
748 state->next_screen = mail_index_screen;
749 break;
751 /*------- Skip to next interesting message -----------*/
752 case MC_TAB :
753 if(THRD_INDX()){
754 PINETHRD_S *thrd;
757 * If we're in the thread index, start looking after this
758 * thread. We don't want to match something in the current
759 * thread.
761 start = mn_get_cur(msgmap);
762 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
763 if(mn_get_revsort(msgmap)){
764 /* if reversed, top of thread is last one before next thread */
765 if(thrd && thrd->top)
766 start = mn_raw2m(msgmap, thrd->top);
768 else{
769 /* last msg of thread is at the ends of the branches/nexts */
770 while(thrd){
771 start = mn_raw2m(msgmap, thrd->rawno);
772 if(thrd->branch)
773 thrd = fetch_thread(stream, thrd->branch);
774 else if(thrd->next)
775 thrd = fetch_thread(stream, thrd->next);
776 else
777 thrd = NULL;
782 * Flags is 0 in this case because we want to not skip
783 * messages inside of threads so that we can find threads
784 * which have some unseen messages even though the top-level
785 * of the thread is already seen.
786 * If new_msgno ends up being a message which is not visible
787 * because it isn't at the top-level, the current message #
788 * will be adjusted below in adjust_cur.
790 flags = 0;
791 new_msgno = next_sorted_flagged((F_UNDEL
792 | F_UNSEEN
793 | ((F_ON(F_TAB_TO_NEW,state))
794 ? 0 : F_OR_FLAG)),
795 stream, start, &flags);
797 else if(THREADING() && sp_viewing_a_thread(stream)){
798 PINETHRD_S *thrd, *topthrd = NULL;
800 start = mn_get_cur(msgmap);
803 * Things are especially complicated when we're viewing_a_thread
804 * from the thread index. First we have to check within the
805 * current thread for a new message. If none is found, then
806 * we search in the next threads and offer to continue in
807 * them. Then we offer to go to the next folder.
809 flags = NSF_SKIP_CHID;
810 new_msgno = next_sorted_flagged((F_UNDEL
811 | F_UNSEEN
812 | ((F_ON(F_TAB_TO_NEW,state))
813 ? 0 : F_OR_FLAG)),
814 stream, start, &flags);
816 * If we found a match then we are done, that is another message
817 * in the current thread index. Otherwise, we have to look
818 * further.
820 if(!(flags & NSF_FLAG_MATCH)){
821 ret = 'n';
822 while(1){
824 flags = 0;
825 new_msgno = next_sorted_flagged((F_UNDEL
826 | F_UNSEEN
827 | ((F_ON(F_TAB_TO_NEW,
828 state))
829 ? 0 : F_OR_FLAG)),
830 stream, start, &flags);
832 * If we got a match, new_msgno is a message in
833 * a different thread from the one we are viewing.
835 if(flags & NSF_FLAG_MATCH){
836 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
837 if(thrd && thrd->top)
838 topthrd = fetch_thread(stream, thrd->top);
840 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
841 static ESCKEY_S next_opt[] = {
842 {'y', 'y', "Y", N_("Yes")},
843 {'n', 'n', "N", N_("No")},
844 {TAB, 'n', "Tab", N_("NextNew")},
845 {-1, 0, NULL, NULL}
848 if(in_index)
849 snprintf(prompt, sizeof(prompt), _("View thread number %s? "),
850 topthrd ? comatose(topthrd->thrdno) : "?");
851 else
852 snprintf(prompt, sizeof(prompt),
853 _("View message in thread number %s? "),
854 topthrd ? comatose(topthrd->thrdno) : "?");
856 prompt[sizeof(prompt)-1] = '\0';
858 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
859 next_opt, 'y', 'x', NO_HELP,
860 RB_NORM);
861 if(ret == 'x'){
862 cmd_cancelled(NULL);
863 goto get_out;
866 else
867 ret = 'y';
869 if(ret == 'y'){
870 if(unview_thread(state, stream, msgmap)){
871 state->next_screen = mail_index_screen;
872 state->view_skipped_index = 0;
873 state->mangled_screen = 1;
876 mn_set_cur(msgmap, new_msgno);
877 if(THRD_AUTO_VIEW()){
879 if(count_lflags_in_thread(stream, topthrd,
880 msgmap, MN_NONE) == 1){
881 if(view_thread(state, stream, msgmap, 1)){
882 if(current_index_state)
883 msgmap->top_after_thrd = current_index_state->msg_at_top;
885 state->view_skipped_index = 1;
886 command = MC_VIEW_TEXT;
887 goto view_text;
892 if(view_thread(state, stream, msgmap, 1) && current_index_state)
893 msgmap->top_after_thrd = current_index_state->msg_at_top;
895 state->next_screen = SCREEN_FUN_NULL;
896 break;
898 else if(ret == 'n' && topthrd){
900 * skip to end of this thread and look starting
901 * in the next thread.
903 if(mn_get_revsort(msgmap)){
905 * if reversed, top of thread is last one
906 * before next thread
908 start = mn_raw2m(msgmap, topthrd->rawno);
910 else{
912 * last msg of thread is at the ends of
913 * the branches/nexts
915 thrd = topthrd;
916 while(thrd){
917 start = mn_raw2m(msgmap, thrd->rawno);
918 if(thrd->branch)
919 thrd = fetch_thread(stream, thrd->branch);
920 else if(thrd->next)
921 thrd = fetch_thread(stream, thrd->next);
922 else
923 thrd = NULL;
927 else if(ret == 'n')
928 break;
930 else
931 break;
935 else{
937 start = mn_get_cur(msgmap);
940 * If we are on a collapsed thread, start looking after the
941 * collapsed part, unless we are viewing the message.
943 if(THREADING() && in_index != View){
944 PINETHRD_S *thrd;
945 long rawno;
946 int collapsed;
948 rawno = mn_m2raw(msgmap, start);
949 thrd = fetch_thread(stream, rawno);
950 collapsed = thrd && thrd->next
951 && get_lflag(stream, NULL, rawno, MN_COLL);
953 if(collapsed){
954 if(mn_get_revsort(msgmap)){
955 if(thrd && thrd->top)
956 start = mn_raw2m(msgmap, thrd->top);
958 else{
959 while(thrd){
960 start = mn_raw2m(msgmap, thrd->rawno);
961 if(thrd->branch)
962 thrd = fetch_thread(stream, thrd->branch);
963 else if(thrd->next)
964 thrd = fetch_thread(stream, thrd->next);
965 else
966 thrd = NULL;
973 new_msgno = next_sorted_flagged((F_UNDEL
974 | F_UNSEEN
975 | ((F_ON(F_TAB_TO_NEW,state))
976 ? 0 : F_OR_FLAG)),
977 stream, start, &flags);
981 * If there weren't any unread messages left, OR there
982 * aren't any messages at all, we may want to offer to
983 * go on to the next folder...
985 if(flags & NSF_FLAG_MATCH){
986 mn_set_cur(msgmap, new_msgno);
987 if(in_index != View)
988 adjust_cur_to_visible(stream, msgmap);
990 else{
991 int in_inbox = sp_flagged(stream, SP_INBOX);
993 if(state->context_current
994 && ((NEWS_TEST(state->context_current)
995 && context_isambig(state->cur_folder))
996 || ((state->context_current->use & CNTXT_INCMNG)
997 && (in_inbox
998 || folder_index(state->cur_folder,
999 state->context_current,
1000 FI_FOLDER) >= 0)))){
1001 char nextfolder[MAXPATH];
1002 MAILSTREAM *nextstream = NULL;
1003 long recent_cnt;
1004 int did_cancel = 0;
1006 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
1007 nextfolder[sizeof(nextfolder)-1] = '\0';
1008 while(1){
1009 if(!(next_folder(&nextstream, nextfolder, sizeof(nextfolder), nextfolder,
1010 state->context_current, &recent_cnt,
1011 F_ON(F_TAB_NO_CONFIRM,state)
1012 ? NULL : &did_cancel))){
1013 if(!in_inbox){
1014 static ESCKEY_S inbox_opt[] = {
1015 {'y', 'y', "Y", N_("Yes")},
1016 {'n', 'n', "N", N_("No")},
1017 {TAB, 'z', "Tab", N_("To Inbox")},
1018 {-1, 0, NULL, NULL}
1021 if(F_ON(F_RET_INBOX_NO_CONFIRM,state))
1022 ret = 'y';
1023 else{
1024 /* TRANSLATORS: this is a question, with some information followed
1025 by Return to INBOX? */
1026 if(state->context_current->use&CNTXT_INCMNG)
1027 snprintf(prompt, sizeof(prompt), _("No more incoming folders. Return to \"%s\"? "), state->inbox_name);
1028 else
1029 snprintf(prompt, sizeof(prompt), _("No more news groups. Return to \"%s\"? "), state->inbox_name);
1031 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1032 inbox_opt, 'y', 'x',
1033 NO_HELP, RB_NORM);
1037 * 'z' is a synonym for 'y'. It is not 'y'
1038 * so that it isn't displayed as a default
1039 * action with square-brackets around it
1040 * in the keymenu...
1042 if(ret == 'y' || ret == 'z'){
1043 visit_folder(state, state->inbox_name,
1044 state->context_current,
1045 NULL, DB_INBOXWOCNTXT);
1046 a_changed = TRUE;
1049 else if (did_cancel)
1050 cmd_cancelled(NULL);
1051 else{
1052 if(state->context_current->use&CNTXT_INCMNG)
1053 q_status_message(SM_ORDER, 0, 2, _("No more incoming folders"));
1054 else
1055 q_status_message(SM_ORDER, 0, 2, _("No more news groups"));
1058 break;
1061 {char *front, type[80], cnt[80], fbuf[MAX_SCREEN_COLS/2+1];
1062 int rbspace, avail, need, take_back;
1065 * View_next_
1066 * Incoming_folder_ or news_group_ or folder_ or group_
1067 * "foldername"
1068 * _(13 recent) or _(some recent) or nothing
1069 * ?_
1071 front = "View next";
1072 strncpy(type,
1073 (state->context_current->use & CNTXT_INCMNG)
1074 ? "Incoming folder" : "news group",
1075 sizeof(type));
1076 type[sizeof(type)-1] = '\0';
1077 snprintf(cnt, sizeof(cnt), " (%.*s %s)", sizeof(cnt)-20,
1078 recent_cnt ? long2string(recent_cnt) : "some",
1079 F_ON(F_TAB_USES_UNSEEN, ps_global)
1080 ? "unseen" : "recent");
1081 cnt[sizeof(cnt)-1] = '\0';
1084 * Space reserved for radio_buttons call.
1085 * If we make this 3 then radio_buttons won't mess
1086 * with the prompt. If we make it 2, then we get
1087 * one more character to use but radio_buttons will
1088 * cut off the last character of our prompt, which is
1089 * ok because it is a space.
1091 rbspace = 2;
1092 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols
1093 : 80;
1094 need = strlen(front)+1 + strlen(type)+1 +
1095 + strlen(nextfolder)+2 + strlen(cnt) +
1096 2 + rbspace;
1097 if(avail < need){
1098 take_back = strlen(type);
1099 strncpy(type,
1100 (state->context_current->use & CNTXT_INCMNG)
1101 ? "folder" : "group", sizeof(type));
1102 take_back -= strlen(type);
1103 need -= take_back;
1104 if(avail < need){
1105 need -= strlen(cnt);
1106 cnt[0] = '\0';
1110 snprintf(prompt, sizeof(prompt), "%.*s %.*s \"%.*s\"%.*s? ",
1111 sizeof(prompt)/8, front,
1112 sizeof(prompt)/8, type,
1113 sizeof(prompt)/2,
1114 short_str(nextfolder, fbuf, sizeof(fbuf),
1115 strlen(nextfolder) -
1116 ((need>avail) ? (need-avail) : 0),
1117 MidDots),
1118 sizeof(prompt)/8, cnt);
1119 prompt[sizeof(prompt)-1] = '\0';
1123 * When help gets added, this'll have to become
1124 * a loop like the rest...
1126 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
1127 static ESCKEY_S next_opt[] = {
1128 {'y', 'y', "Y", N_("Yes")},
1129 {'n', 'n', "N", N_("No")},
1130 {TAB, 'n', "Tab", N_("NextNew")},
1131 {-1, 0, NULL, NULL}
1134 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1135 next_opt, 'y', 'x', NO_HELP,
1136 RB_NORM);
1137 if(ret == 'x'){
1138 cmd_cancelled(NULL);
1139 break;
1142 else
1143 ret = 'y';
1145 if(ret == 'y'){
1146 if(nextstream && sp_dead_stream(nextstream))
1147 nextstream = NULL;
1149 visit_folder(state, nextfolder,
1150 state->context_current, nextstream,
1151 DB_FROMTAB);
1152 /* visit_folder takes care of nextstream */
1153 nextstream = NULL;
1154 a_changed = TRUE;
1155 break;
1159 if(nextstream)
1160 pine_mail_close(nextstream);
1162 else
1163 any_messages(NULL,
1164 (mn_get_total(msgmap) > 0L)
1165 ? IS_NEWS(stream) ? "more undeleted" : "more new"
1166 : NULL,
1167 NULL);
1170 get_out:
1172 break;
1175 /*------- Zoom -----------*/
1176 case MC_ZOOM :
1178 * Right now the way zoom is implemented is sort of silly.
1179 * There are two per-message flags where just one and a
1180 * global "zoom mode" flag to suppress messags from the index
1181 * should suffice.
1183 if(any_messages(msgmap, NULL, "to Zoom on")){
1184 if(unzoom_index(state, stream, msgmap)){
1185 dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
1186 q_status_message(SM_ORDER,0,2, _("Index Zoom Mode is now off"));
1188 else if((i = zoom_index(state, stream, msgmap, MN_SLCT)) != 0){
1189 if(any_lflagged(msgmap, MN_HIDE)){
1190 dprint((4,"\n\n ---- Entering ZOOM mode ----\n"));
1191 q_status_message4(SM_ORDER, 0, 2,
1192 _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"),
1193 THRD_INDX() ? "" : comatose(i),
1194 THRD_INDX() ? "" : " ",
1195 THRD_INDX() ? _("threads") : _("message"),
1196 THRD_INDX() ? "" : plural(i));
1198 else
1199 q_status_message(SM_ORDER, 0, 2,
1200 _("All messages selected, so not entering Index Zoom Mode"));
1202 else
1203 any_messages(NULL, "selected", "to Zoom on");
1206 break;
1209 /*---------- print message on paper ----------*/
1210 case MC_PRINTMSG :
1211 if(any_messages(msgmap, NULL, "to print"))
1212 (void) cmd_print(state, msgmap, MCMD_NONE, in_index);
1214 break;
1217 /*---------- Take Address ----------*/
1218 case MC_TAKE :
1219 if(F_ON(F_ENABLE_ROLE_TAKE, state) ||
1220 any_messages(msgmap, NULL, "to Take address from"))
1221 (void) cmd_take_addr(state, msgmap, MCMD_NONE);
1223 break;
1226 /*---------- Save Message ----------*/
1227 case MC_SAVE :
1228 if(any_messages(msgmap, NULL, "to Save"))
1229 (void) cmd_save(state, stream, msgmap, MCMD_NONE, in_index);
1231 break;
1234 /*---------- Export message ----------*/
1235 case MC_EXPORT :
1236 if(any_messages(msgmap, NULL, "to Export")){
1237 (void) cmd_export(state, msgmap, question_line, MCMD_NONE);
1238 state->mangled_footer = 1;
1241 break;
1244 /*---------- Expunge ----------*/
1245 case MC_EXPUNGE :
1246 (void) cmd_expunge(state, stream, msgmap, MCMD_NONE);
1247 break;
1250 /*------- Unexclude -----------*/
1251 case MC_UNEXCLUDE :
1252 if(!(IS_NEWS(stream) && stream->rdonly)){
1253 q_status_message(SM_ORDER, 0, 3,
1254 _("Unexclude not available for mail folders"));
1256 else if(any_lflagged(msgmap, MN_EXLD)){
1257 SEARCHPGM *pgm;
1258 long i;
1259 int exbits;
1262 * Since excluded means "hidden deleted" and "killed",
1263 * the count should reflect the former.
1265 pgm = mail_newsearchpgm();
1266 pgm->deleted = 1;
1267 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
1268 for(i = 1L, del_count = 0L; i <= stream->nmsgs; i++)
1269 if((mc = mail_elt(stream, i)) && mc->searched
1270 && get_lflag(stream, NULL, i, MN_EXLD)
1271 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
1272 && (exbits & MSG_EX_FILTERED)))
1273 del_count++;
1275 if(del_count > 0L){
1276 state->mangled_footer = 1;
1277 snprintf(prompt, sizeof(prompt), "UNexclude %ld message%s in %.*s", del_count,
1278 plural(del_count), sizeof(prompt)-40,
1279 pretty_fn(state->cur_folder));
1280 prompt[sizeof(prompt)-1] = '\0';
1281 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
1282 || (F_ON(F_AUTO_EXPUNGE, state)
1283 && (state->context_current
1284 && (state->context_current->use & CNTXT_INCMNG))
1285 && context_isambig(state->cur_folder))
1286 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
1287 long save_cur_rawno;
1288 int were_viewing_a_thread;
1290 save_cur_rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
1291 were_viewing_a_thread = (THREADING()
1292 && sp_viewing_a_thread(stream));
1294 if(msgno_include(stream, msgmap, MI_NONE)){
1295 clear_index_cache(stream, 0);
1297 if(stream && stream->spare)
1298 erase_threading_info(stream, msgmap);
1300 refresh_sort(stream, msgmap, SRT_NON);
1303 if(were_viewing_a_thread){
1304 if(save_cur_rawno > 0L)
1305 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1307 if(view_thread(state, stream, msgmap, 1) && current_index_state)
1308 msgmap->top_after_thrd = current_index_state->msg_at_top;
1311 if(save_cur_rawno > 0L)
1312 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1314 state->mangled_screen = 1;
1315 q_status_message2(SM_ORDER, 0, 4,
1316 "%s message%s UNexcluded",
1317 long2string(del_count),
1318 plural(del_count));
1320 if(in_index != View)
1321 adjust_cur_to_visible(stream, msgmap);
1323 else
1324 any_messages(NULL, NULL, "UNexcluded");
1326 else
1327 any_messages(NULL, "excluded", "to UNexclude");
1329 else
1330 any_messages(NULL, "excluded", "to UNexclude");
1332 break;
1335 /*------- Make Selection -----------*/
1336 case MC_SELECT :
1337 if(any_messages(msgmap, NULL, "to Select")){
1338 if(aggregate_select(state, msgmap, question_line, in_index) == 0
1339 && (in_index == MsgIndx || in_index == ThrdIndx)
1340 && F_ON(F_AUTO_ZOOM, state)
1341 && any_lflagged(msgmap, MN_SLCT) > 0L
1342 && !any_lflagged(msgmap, MN_HIDE))
1343 (void) zoom_index(state, stream, msgmap, MN_SLCT);
1346 break;
1349 /*------- Toggle Current Message Selection State -----------*/
1350 case MC_SELCUR :
1351 if(any_messages(msgmap, NULL, NULL)){
1352 if((select_by_current(state, msgmap, in_index)
1353 || (F_OFF(F_UNSELECT_WONT_ADVANCE, state)
1354 && !any_lflagged(msgmap, MN_HIDE)))
1355 && (i = mn_get_cur(msgmap)) < mn_get_total(msgmap)){
1356 /* advance current */
1357 mn_inc_cur(stream, msgmap,
1358 (in_index == View && THREADING()
1359 && sp_viewing_a_thread(stream))
1360 ? MH_THISTHD
1361 : (in_index == View)
1362 ? MH_ANYTHD : MH_NONE);
1366 break;
1369 /*------- Apply command -----------*/
1370 case MC_APPLY :
1371 if(any_messages(msgmap, NULL, NULL)){
1372 if(any_lflagged(msgmap, MN_SLCT) > 0L){
1373 if(apply_command(state, stream, msgmap, 0,
1374 AC_NONE, question_line)){
1375 if(F_ON(F_AUTO_UNSELECT, state)){
1376 agg_select_all(stream, msgmap, NULL, 0);
1377 unzoom_index(state, stream, msgmap);
1379 else if(F_ON(F_AUTO_UNZOOM, state))
1380 unzoom_index(state, stream, msgmap);
1383 else
1384 any_messages(NULL, NULL, "to Apply command to. Try \"Select\"");
1387 break;
1390 /*-------- Sort command -------*/
1391 case MC_SORT :
1393 int were_threading = THREADING();
1394 SortOrder sort = mn_get_sort(msgmap);
1395 int rev = mn_get_revsort(msgmap);
1397 dprint((1,"MAIL_CMD: sort\n"));
1398 if(select_sort(state, question_line, &sort, &rev)){
1399 /* $ command reinitializes threading collapsed/expanded info */
1400 if(SORT_IS_THREADED(msgmap) && !SEP_THRDINDX())
1401 erase_threading_info(stream, msgmap);
1403 if(ps_global && ps_global->ttyo){
1404 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
1405 ps_global->mangled_footer = 1;
1408 sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN);
1411 state->mangled_footer = 1;
1414 * We've changed whether we are threading or not so we need to
1415 * exit the index and come back in so that we switch between the
1416 * thread index and the regular index. Sort_folder will have
1417 * reset viewing_a_thread if necessary.
1419 if(SEP_THRDINDX()
1420 && ((!were_threading && THREADING())
1421 || (were_threading && !THREADING()))){
1422 state->next_screen = mail_index_screen;
1423 state->mangled_screen = 1;
1427 break;
1430 /*------- Toggle Full Headers -----------*/
1431 case MC_FULLHDR :
1432 state->full_header++;
1433 if(state->full_header == 1){
1434 if(!(state->quote_suppression_threshold
1435 && (state->some_quoting_was_suppressed || in_index != View)))
1436 state->full_header++;
1438 else if(state->full_header > 2)
1439 state->full_header = 0;
1441 switch(state->full_header){
1442 case 0:
1443 q_status_message(SM_ORDER, 0, 3,
1444 _("Display of full headers is now off."));
1445 break;
1447 case 1:
1448 q_status_message1(SM_ORDER, 0, 3,
1449 _("Quotes displayed, use %s to see full headers"),
1450 F_ON(F_USE_FK, state) ? "F9" : "H");
1451 break;
1453 case 2:
1454 q_status_message(SM_ORDER, 0, 3,
1455 _("Display of full headers is now on."));
1456 break;
1460 a_changed = TRUE;
1461 break;
1464 case MC_TOGGLE :
1465 a_changed = TRUE;
1466 break;
1469 #ifdef SMIME
1470 /*------- Try to decrypt message -----------*/
1471 case MC_DECRYPT:
1472 if(state->smime && state->smime->need_passphrase)
1473 smime_get_passphrase();
1475 a_changed = TRUE;
1476 break;
1478 case MC_SECURITY:
1479 smime_info_screen(state);
1480 break;
1481 #endif
1484 /*------- Bounce -----------*/
1485 case MC_BOUNCE :
1486 (void) cmd_bounce(state, msgmap, MCMD_NONE);
1487 break;
1490 /*------- Flag -----------*/
1491 case MC_FLAG :
1492 dprint((4, "\n - flag message -\n"));
1493 (void) cmd_flag(state, msgmap, MCMD_NONE);
1494 break;
1497 /*------- Pipe message -----------*/
1498 case MC_PIPE :
1499 (void) cmd_pipe(state, msgmap, MCMD_NONE);
1500 break;
1503 /*--------- Default, unknown command ----------*/
1504 default:
1505 alpine_panic("Unexpected command case");
1506 break;
1509 return((a_changed || mn_get_cur(msgmap) != old_msgno) ? 1 : 0);
1514 /*----------------------------------------------------------------------
1515 Map some of the special characters into sensible strings for human
1516 consumption.
1517 c is a UCS-4 character!
1518 ----*/
1519 char *
1520 pretty_command(UCS c)
1522 static char buf[10];
1523 char *s;
1525 buf[0] = '\0';
1526 s = buf;
1528 switch(c){
1529 case ' ' : s = "SPACE"; break;
1530 case '\033' : s = "ESC"; break;
1531 case '\177' : s = "DEL"; break;
1532 case ctrl('I') : s = "TAB"; break;
1533 case ctrl('J') : s = "LINEFEED"; break;
1534 case ctrl('M') : s = "RETURN"; break;
1535 case ctrl('Q') : s = "XON"; break;
1536 case ctrl('S') : s = "XOFF"; break;
1537 case KEY_UP : s = "Up Arrow"; break;
1538 case KEY_DOWN : s = "Down Arrow"; break;
1539 case KEY_RIGHT : s = "Right Arrow"; break;
1540 case KEY_LEFT : s = "Left Arrow"; break;
1541 case KEY_PGUP : s = "Prev Page"; break;
1542 case KEY_PGDN : s = "Next Page"; break;
1543 case KEY_HOME : s = "Home"; break;
1544 case KEY_END : s = "End"; break;
1545 case KEY_DEL : s = "Delete"; break; /* Not necessary DEL! */
1546 case KEY_JUNK : s = "Junk!"; break;
1547 case BADESC : s = "Bad Esc"; break;
1548 case NO_OP_IDLE : s = "NO_OP_IDLE"; break;
1549 case NO_OP_COMMAND : s = "NO_OP_COMMAND"; break;
1550 case KEY_RESIZE : s = "KEY_RESIZE"; break;
1551 case KEY_UTF8 : s = "KEY_UTF8"; break;
1552 case KEY_MOUSE : s = "KEY_MOUSE"; break;
1553 case KEY_SCRLUPL : s = "KEY_SCRLUPL"; break;
1554 case KEY_SCRLDNL : s = "KEY_SCRLDNL"; break;
1555 case KEY_SCRLTO : s = "KEY_SCRLTO"; break;
1556 case KEY_XTERM_MOUSE : s = "KEY_XTERM_MOUSE"; break;
1557 case KEY_DOUBLE_ESC : s = "KEY_DOUBLE_ESC"; break;
1558 case CTRL_KEY_UP : s = "Ctrl Up Arrow"; break;
1559 case CTRL_KEY_DOWN : s = "Ctrl Down Arrow"; break;
1560 case CTRL_KEY_RIGHT : s = "Ctrl Right Arrow"; break;
1561 case CTRL_KEY_LEFT : s = "Ctrl Left Arrow"; break;
1562 case PF1 :
1563 case PF2 :
1564 case PF3 :
1565 case PF4 :
1566 case PF5 :
1567 case PF6 :
1568 case PF7 :
1569 case PF8 :
1570 case PF9 :
1571 case PF10 :
1572 case PF11 :
1573 case PF12 :
1574 snprintf(s = buf, sizeof(buf), "F%ld", (long) (c - PF1 + 1));
1575 break;
1577 default:
1578 if(c < ' ' || (c >= 0x80 && c < 0xA0)){
1579 char d;
1580 int c1;
1582 c1 = (c >= 0x80);
1583 d = (c & 0x1f) + 'A' - 1;
1584 snprintf(s = buf, sizeof(buf), "%c%c", c1 ? '~' : '^', d);
1586 else{
1587 memset(buf, 0, sizeof(buf));
1588 utf8_put((unsigned char *) buf, (unsigned long) c);
1591 break;
1594 return(s);
1598 /*----------------------------------------------------------------------
1599 Complain about bogus input
1601 Args: ch -- input command to complain about
1602 help -- string indicating where to get help
1604 ----*/
1605 void
1606 bogus_command(UCS cmd, char *help)
1608 if(cmd == ctrl('Q') || cmd == ctrl('S'))
1609 q_status_message1(SM_ASYNC, 0, 2,
1610 "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.",
1611 pretty_command(cmd));
1612 else if(cmd == KEY_JUNK)
1613 q_status_message3(SM_ORDER, 0, 2,
1614 "Invalid key pressed.%s%s%s",
1615 (help) ? " Use " : "",
1616 (help) ? help : "",
1617 (help) ? " for help" : "");
1618 else
1619 q_status_message4(SM_ORDER, 0, 2,
1620 "Command \"%s\" not defined for this screen.%s%s%s",
1621 pretty_command(cmd),
1622 (help) ? " Use " : "",
1623 (help) ? help : "",
1624 (help) ? " for help" : "");
1628 void
1629 bogus_utf8_command(char *cmd, char *help)
1631 q_status_message4(SM_ORDER, 0, 2,
1632 "Command \"%s\" not defined for this screen.%s%s%s",
1633 cmd ? cmd : "?",
1634 (help) ? " Use " : "",
1635 (help) ? help : "",
1636 (help) ? " for help" : "");
1640 /*----------------------------------------------------------------------
1641 Execute FLAG message command
1643 Args: state -- Various satate info
1644 msgmap -- map of c-client to local message numbers
1646 Result: with side effect of "current" message FLAG flag set or UNset
1648 ----*/
1650 cmd_flag(struct pine *state, MSGNO_S *msgmap, int aopt)
1652 char *flagit, *seq, *screen_text[20], **exp, **p, *answer = NULL;
1653 char *keyword_array[2];
1654 int user_defined_flags = 0, mailbox_flags = 0;
1655 int directly_to_maint_screen = 0;
1656 long unflagged, flagged, flags, rawno;
1657 MESSAGECACHE *mc = NULL;
1658 KEYWORD_S *kw;
1659 int i, cnt, is_set, trouble = 0, rv = 0;
1660 size_t len;
1661 struct flag_table *fp, *ftbl = NULL;
1662 struct flag_screen flag_screen;
1663 static char *flag_screen_text1[] = {
1664 N_(" Set desired flags for current message below. An 'X' means set"),
1665 N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."),
1666 NULL
1669 static char *flag_screen_text2[] = {
1670 N_(" Set desired flags below for selected messages. A '?' means to"),
1671 N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"),
1672 N_(" to unset it. Use the \"Return\" key to toggle, and choose"),
1673 N_(" \"Exit\" when finished."),
1674 NULL
1677 static struct flag_table default_ftbl[] = {
1678 {N_("Important"), h_flag_important, F_FLAG, 0, 0, NULL, NULL},
1679 {N_("New"), h_flag_new, F_SEEN, 0, 0, NULL, NULL},
1680 {N_("Answered"), h_flag_answered, F_ANS, 0, 0, NULL, NULL},
1681 {N_("Forwarded"), h_flag_forwarded, F_FWD, 0, 0, NULL, NULL},
1682 {N_("Deleted"), h_flag_deleted, F_DEL, 0, 0, NULL, NULL},
1683 {NULL, NO_HELP, 0, 0, 0, NULL, NULL}
1686 /* Only check for dead stream for now. Should check permanent flags
1687 * eventually
1689 if(!(any_messages(msgmap, NULL, "to Flag") && can_set_flag(state, "flag", 1)))
1690 return rv;
1692 if(sp_io_error_on_stream(state->mail_stream)){
1693 sp_set_io_error_on_stream(state->mail_stream, 0);
1694 pine_mail_check(state->mail_stream); /* forces write */
1695 return rv;
1698 go_again:
1699 answer = NULL;
1700 user_defined_flags = 0;
1701 mailbox_flags = 0;
1702 mc = NULL;
1703 trouble = 0;
1704 ftbl = NULL;
1706 /* count how large ftbl will be */
1707 for(cnt = 0; default_ftbl[cnt].name; cnt++)
1710 /* add user flags */
1711 for(kw = ps_global->keywords; kw; kw = kw->next){
1712 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick)) || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1713 user_defined_flags++;
1714 cnt++;
1719 * Add mailbox flags that aren't user-defined flags.
1720 * Don't consider it if it matches either one of our defined
1721 * keywords or one of our defined nicknames for a keyword.
1723 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1724 char *q;
1726 q = stream_to_user_flag_name(state->mail_stream, i);
1727 if(q && q[0]){
1728 for(kw = ps_global->keywords; kw; kw = kw->next){
1729 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1730 break;
1734 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1735 mailbox_flags++;
1736 cnt++;
1740 cnt += (user_defined_flags ? 2 : 0) + (mailbox_flags ? 2 : 0);
1742 /* set up ftbl, first the system flags */
1743 ftbl = (struct flag_table *) fs_get((cnt+1) * sizeof(*ftbl));
1744 memset(ftbl, 0, (cnt+1) * sizeof(*ftbl));
1745 for(i = 0, fp = ftbl; default_ftbl[i].name; i++, fp++){
1746 fp->name = cpystr(default_ftbl[i].name);
1747 fp->help = default_ftbl[i].help;
1748 fp->flag = default_ftbl[i].flag;
1749 fp->set = default_ftbl[i].set;
1750 fp->ukn = default_ftbl[i].ukn;
1753 if(user_defined_flags){
1754 fp->flag = F_COMMENT;
1755 fp->name = cpystr("");
1756 fp++;
1757 fp->flag = F_COMMENT;
1758 len = strlen(_("User-defined Keywords from Setup/Config"));
1759 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1760 snprintf(fp->name, len+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config"));
1761 fp++;
1764 /* then the user-defined keywords */
1765 if(user_defined_flags)
1766 for(kw = ps_global->keywords; kw; kw = kw->next){
1767 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick))
1768 || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1769 fp->name = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
1770 fp->keyword = cpystr(kw->kw ? kw->kw : "");
1771 if(kw->nick && kw->kw){
1772 size_t l;
1774 l = strlen(kw->kw)+2;
1775 fp->comment = (char *) fs_get((l+1) * sizeof(char));
1776 snprintf(fp->comment, l+1, "(%.*s)", strlen(kw->kw), kw->kw);
1777 fp->comment[l] = '\0';
1780 fp->help = h_flag_user_flag;
1781 fp->flag = F_KEYWORD;
1782 fp->set = 0;
1783 fp->ukn = 0;
1784 fp++;
1788 if(mailbox_flags){
1789 fp->flag = F_COMMENT;
1790 fp->name = cpystr("");
1791 fp++;
1792 fp->flag = F_COMMENT;
1793 len = strlen(_("Other keywords in the mailbox that are not user-defined"));
1794 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1795 snprintf(fp->name, len+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined"));
1796 fp++;
1799 /* then the extra mailbox-defined keywords */
1800 if(mailbox_flags)
1801 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1802 char *q;
1804 q = stream_to_user_flag_name(state->mail_stream, i);
1805 if(q && q[0]){
1806 for(kw = ps_global->keywords; kw; kw = kw->next){
1807 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1808 break;
1812 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1813 fp->name = cpystr(q);
1814 fp->keyword = cpystr(q);
1815 fp->help = h_flag_user_flag;
1816 fp->flag = F_KEYWORD;
1817 fp->set = 0;
1818 fp->ukn = 0;
1819 fp++;
1823 flag_screen.flag_table = &ftbl;
1824 flag_screen.explanation = screen_text;
1826 if(MCMD_ISAGG(aopt)){
1827 if(!pseudo_selected(ps_global->mail_stream, msgmap)){
1828 free_flag_table(&ftbl);
1829 return rv;
1832 exp = flag_screen_text2;
1833 for(fp = ftbl; fp->name; fp++){
1834 fp->set = CMD_FLAG_UNKN; /* set to unknown */
1835 fp->ukn = TRUE;
1838 else if(state->mail_stream
1839 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
1840 && rawno <= state->mail_stream->nmsgs
1841 && (mc = mail_elt(state->mail_stream, rawno))){
1842 exp = flag_screen_text1;
1843 for(fp = &ftbl[0]; fp->name; fp++){
1844 fp->ukn = 0;
1845 if(fp->flag == F_KEYWORD){
1846 /* see if this keyword is defined for this message */
1847 fp->set = CMD_FLAG_CLEAR;
1848 if(user_flag_is_set(state->mail_stream,
1849 rawno, fp->keyword))
1850 fp->set = CMD_FLAG_SET;
1852 else if(fp->flag == F_FWD){
1853 /* see if forwarded keyword is defined for this message */
1854 fp->set = CMD_FLAG_CLEAR;
1855 if(user_flag_is_set(state->mail_stream,
1856 rawno, FORWARDED_FLAG))
1857 fp->set = CMD_FLAG_SET;
1859 else if(fp->flag != F_COMMENT)
1860 fp->set = ((fp->flag == F_SEEN && !mc->seen)
1861 || (fp->flag == F_DEL && mc->deleted)
1862 || (fp->flag == F_FLAG && mc->flagged)
1863 || (fp->flag == F_ANS && mc->answered))
1864 ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
1867 else{
1868 q_status_message(SM_ORDER | SM_DING, 3, 4,
1869 _("Error accessing message data"));
1870 free_flag_table(&ftbl);
1871 return rv;
1874 if(directly_to_maint_screen)
1875 goto the_maint_screen;
1877 #ifdef _WINDOWS
1878 if (mswin_usedialog ()) {
1879 if (!os_flagmsgdialog (&ftbl[0])){
1880 free_flag_table(&ftbl);
1881 return rv;
1884 else
1885 #endif
1887 int use_maint_screen;
1888 int keyword_shortcut = 0;
1890 use_maint_screen = F_ON(F_FLAG_SCREEN_DFLT, ps_global);
1892 if(!use_maint_screen){
1894 * We're going to call cmd_flag_prompt(). We need
1895 * to decide whether or not to offer the keyword setting
1896 * shortcut. We'll offer it if the user has the feature
1897 * enabled AND there are some possible keywords that could
1898 * be set.
1900 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global)){
1901 for(fp = &ftbl[0]; fp->name && !keyword_shortcut; fp++){
1902 if(fp->flag == F_KEYWORD){
1903 int first_char;
1904 ESCKEY_S *tp;
1906 first_char = (fp->name && fp->name[0])
1907 ? fp->name[0] : -2;
1908 if(isascii(first_char) && isupper(first_char))
1909 first_char = tolower((unsigned char) first_char);
1911 for(tp=flag_text_opt; tp->ch != -1; tp++)
1912 if(tp->ch == first_char)
1913 break;
1915 if(tp->ch == -1)
1916 keyword_shortcut++;
1921 use_maint_screen = !cmd_flag_prompt(state, &flag_screen,
1922 keyword_shortcut);
1925 the_maint_screen:
1926 if(use_maint_screen){
1927 for(p = &screen_text[0]; *exp; p++, exp++)
1928 *p = *exp;
1930 *p = NULL;
1932 directly_to_maint_screen = flag_maintenance_screen(state, &flag_screen);
1936 /* reaquire the elt pointer */
1937 mc = (state->mail_stream
1938 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
1939 && rawno <= state->mail_stream->nmsgs)
1940 ? mail_elt(state->mail_stream, rawno) : NULL;
1942 for(fp = ftbl; mc && fp->name; fp++){
1943 flags = -1;
1944 switch(fp->flag){
1945 case F_SEEN:
1946 if((!MCMD_ISAGG(aopt) && fp->set != !mc->seen)
1947 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
1948 flagit = "\\SEEN";
1949 if(fp->set){
1950 flags = 0L;
1951 unflagged = F_SEEN;
1953 else{
1954 flags = ST_SET;
1955 unflagged = F_UNSEEN;
1959 break;
1961 case F_ANS:
1962 if((!MCMD_ISAGG(aopt) && fp->set != mc->answered)
1963 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
1964 flagit = "\\ANSWERED";
1965 if(fp->set){
1966 flags = ST_SET;
1967 unflagged = F_UNANS;
1969 else{
1970 flags = 0L;
1971 unflagged = F_ANS;
1975 break;
1977 case F_DEL:
1978 if((!MCMD_ISAGG(aopt) && fp->set != mc->deleted)
1979 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
1980 flagit = "\\DELETED";
1981 if(fp->set){
1982 flags = ST_SET;
1983 unflagged = F_UNDEL;
1985 else{
1986 flags = 0L;
1987 unflagged = F_DEL;
1991 break;
1993 case F_FLAG:
1994 if((!MCMD_ISAGG(aopt) && fp->set != mc->flagged)
1995 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
1996 flagit = "\\FLAGGED";
1997 if(fp->set){
1998 flags = ST_SET;
1999 unflagged = F_UNFLAG;
2001 else{
2002 flags = 0L;
2003 unflagged = F_FLAG;
2007 break;
2009 case F_FWD :
2010 if(!MCMD_ISAGG(aopt)){
2011 /* see if forwarded is defined for this message */
2012 is_set = CMD_FLAG_CLEAR;
2013 if(user_flag_is_set(state->mail_stream,
2014 mn_m2raw(msgmap, mn_get_cur(msgmap)),
2015 FORWARDED_FLAG))
2016 is_set = CMD_FLAG_SET;
2019 if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2020 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2021 flagit = FORWARDED_FLAG;
2022 if(fp->set){
2023 flags = ST_SET;
2024 unflagged = F_UNFWD;
2026 else{
2027 flags = 0L;
2028 unflagged = F_FWD;
2032 break;
2034 case F_KEYWORD:
2035 if(!MCMD_ISAGG(aopt)){
2036 /* see if this keyword is defined for this message */
2037 is_set = CMD_FLAG_CLEAR;
2038 if(user_flag_is_set(state->mail_stream,
2039 mn_m2raw(msgmap, mn_get_cur(msgmap)),
2040 fp->keyword))
2041 is_set = CMD_FLAG_SET;
2044 if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2045 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2046 flagit = fp->keyword;
2047 keyword_array[0] = fp->keyword;
2048 keyword_array[1] = NULL;
2049 if(fp->set){
2050 flags = ST_SET;
2051 unflagged = F_UNKEYWORD;
2053 else{
2054 flags = 0L;
2055 unflagged = F_KEYWORD;
2059 break;
2061 default:
2062 break;
2065 flagged = 0L;
2066 if(flags >= 0L
2067 && (seq = currentf_sequence(state->mail_stream, msgmap,
2068 unflagged, &flagged, unflagged & F_DEL,
2069 (fp->flag == F_KEYWORD
2070 && unflagged == F_KEYWORD)
2071 ? keyword_array : NULL,
2072 (fp->flag == F_KEYWORD
2073 && unflagged == F_UNKEYWORD)
2074 ? keyword_array : NULL))){
2076 * For user keywords, we may have to create the flag in
2077 * the folder if it doesn't already exist and we are setting
2078 * it (as opposed to clearing it). Mail_flag will
2079 * do that for us, but it's failure isn't very friendly
2080 * error-wise. So we try to make it a little smoother.
2082 if(!(fp->flag == F_KEYWORD || fp->flag == F_FWD) || !fp->set
2083 || ((i=user_flag_index(state->mail_stream, flagit)) >= 0
2084 && i < NUSERFLAGS))
2085 mail_flag(state->mail_stream, seq, flagit, flags);
2086 else{
2087 /* trouble, see if we can add the user flag */
2088 if(state->mail_stream->kwd_create)
2089 mail_flag(state->mail_stream, seq, flagit, flags);
2090 else{
2091 trouble++;
2093 if(some_user_flags_defined(state->mail_stream))
2094 q_status_message(SM_ORDER, 3, 4,
2095 _("No more keywords allowed in this folder!"));
2096 else if(fp->flag == F_FWD)
2097 q_status_message(SM_ORDER, 3, 4,
2098 _("Cannot add keywords for this folder so cannot set Forwarded flag"));
2099 else
2100 q_status_message(SM_ORDER, 3, 4,
2101 _("Cannot add keywords for this folder"));
2105 fs_give((void **) &seq);
2106 if(flagged && !trouble){
2107 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%slagged%s%s%s%s%s message%s%s \"%s\"",
2108 (fp->set) ? "F" : "Unf",
2109 MCMD_ISAGG(aopt) ? " " : "",
2110 MCMD_ISAGG(aopt) ? long2string(flagged) : "",
2111 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2112 ? " (of " : "",
2113 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2114 ? comatose(mn_total_cur(msgmap)) : "",
2115 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2116 ? ")" : "",
2117 MCMD_ISAGG(aopt) ? plural(flagged) : " ",
2118 MCMD_ISAGG(aopt) ? "" : long2string(mn_get_cur(msgmap)),
2119 fp->name);
2120 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2121 q_status_message(SM_ORDER, 0, 2, answer = tmp_20k_buf);
2122 rv++;
2127 free_flag_table(&ftbl);
2129 if(directly_to_maint_screen)
2130 goto go_again;
2132 if(MCMD_ISAGG(aopt))
2133 restore_selected(msgmap);
2135 if(!answer)
2136 q_status_message(SM_ORDER, 0, 2, _("No flags changed."));
2138 return rv;
2142 /*----------------------------------------------------------------------
2143 Offer concise status line flag prompt
2145 Args: state -- Various satate info
2146 flags -- flags to offer setting
2148 Result: TRUE if flag to set specified in flags struct or FALSE otw
2150 ----*/
2152 cmd_flag_prompt(struct pine *state, struct flag_screen *flags, int allow_keyword_shortcuts)
2154 int r, setflag = 1, first_char;
2155 struct flag_table *fp;
2156 ESCKEY_S *ek;
2157 char *ftext, *ftext_not;
2158 static char *flag_text =
2159 N_("Flag New, Deleted, Answered, Forwarded or Important ? ");
2160 static char *flag_text_ak =
2161 N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? ");
2162 static char *flag_text_not =
2163 N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? ");
2164 static char *flag_text_ak_not =
2165 N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? ");
2167 if(allow_keyword_shortcuts){
2168 int cnt = 0;
2169 ESCKEY_S *dp, *sp, *tp;
2171 for(sp=flag_text_opt; sp->ch != -1; sp++)
2172 cnt++;
2174 for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++)
2175 if(fp->flag == F_KEYWORD)
2176 cnt++;
2178 /* set up an ESCKEY_S list which includes invisible keys for keywords */
2179 ek = (ESCKEY_S *) fs_get((cnt + 1) * sizeof(*ek));
2180 memset(ek, 0, (cnt+1) * sizeof(*ek));
2181 for(dp=ek, sp=flag_text_opt; sp->ch != -1; sp++, dp++)
2182 *dp = *sp;
2184 for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2185 if(fp->flag == F_KEYWORD){
2186 first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2187 if(isascii(first_char) && isupper(first_char))
2188 first_char = tolower((unsigned char) first_char);
2191 * Check to see if an earlier keyword in the list, or one of
2192 * the builtin system letters already uses this character.
2193 * If so, the first one wins.
2195 for(tp=ek; tp->ch != 0; tp++)
2196 if(tp->ch == first_char)
2197 break;
2199 if(tp->ch != 0)
2200 continue; /* skip it, already used that char */
2202 dp->ch = first_char;
2203 dp->rval = first_char;
2204 dp->name = "";
2205 dp->label = "";
2206 dp++;
2210 dp->ch = -1;
2211 ftext = _(flag_text_ak);
2212 ftext_not = _(flag_text_ak_not);
2214 else{
2215 ek = flag_text_opt;
2216 ftext = _(flag_text);
2217 ftext_not = _(flag_text_not);
2220 while(1){
2221 r = radio_buttons(setflag ? ftext : ftext_not,
2222 -FOOTER_ROWS(state), ek, '*', SEQ_EXCEPTION-1,
2223 NO_HELP, RB_NORM | RB_SEQ_SENSITIVE);
2225 * It is SEQ_EXCEPTION-1 just so that it is some value that isn't
2226 * being used otherwise. The keywords use up all the possible
2227 * letters, so a negative number is good, but it has to be different
2228 * from other negative return values.
2230 if(r == SEQ_EXCEPTION-1) /* ol'cancelrooney */
2231 return(TRUE);
2232 else if(r == 10) /* return and goto flag screen */
2233 return(FALSE);
2234 else if(r == '!') /* flip intention */
2235 setflag = !setflag;
2236 else
2237 break;
2240 for(fp = (flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2241 if(r == 'n' || r == '*' || r == 'd' || r == 'a' || r == 'f'){
2242 if((r == 'n' && fp->flag == F_SEEN)
2243 || (r == '*' && fp->flag == F_FLAG)
2244 || (r == 'd' && fp->flag == F_DEL)
2245 || (r == 'f' && fp->flag == F_FWD)
2246 || (r == 'a' && fp->flag == F_ANS)){
2247 fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2248 break;
2251 else if(allow_keyword_shortcuts && fp->flag == F_KEYWORD){
2252 first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2253 if(isascii(first_char) && isupper(first_char))
2254 first_char = tolower((unsigned char) first_char);
2256 if(r == first_char){
2257 fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2258 break;
2263 if(ek != flag_text_opt)
2264 fs_give((void **) &ek);
2266 return(TRUE);
2271 * (*ft) is an array of flag_table entries.
2273 void
2274 free_flag_table(struct flag_table **ft)
2276 struct flag_table *fp;
2278 if(ft && *ft){
2279 for(fp = (*ft); fp->name; fp++){
2280 if(fp->name)
2281 fs_give((void **) &fp->name);
2283 if(fp->keyword)
2284 fs_give((void **) &fp->keyword);
2286 if(fp->comment)
2287 fs_give((void **) &fp->comment);
2290 fs_give((void **) ft);
2295 /*----------------------------------------------------------------------
2296 Execute REPLY message command
2298 Args: state -- Various satate info
2299 msgmap -- map of c-client to local message numbers
2301 Result: reply sent or not
2303 ----*/
2305 cmd_reply(struct pine *state, MSGNO_S *msgmap, int aopt)
2307 int rv = 0;
2309 if(any_messages(msgmap, NULL, "to Reply to")){
2310 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2311 return rv;
2313 rv = reply(state, NULL);
2315 if(MCMD_ISAGG(aopt))
2316 restore_selected(msgmap);
2318 state->mangled_screen = 1;
2321 return rv;
2325 /*----------------------------------------------------------------------
2326 Execute FORWARD message command
2328 Args: state -- Various satate info
2329 msgmap -- map of c-client to local message numbers
2331 Result: selected message[s] forwarded or not
2333 ----*/
2335 cmd_forward(struct pine *state, MSGNO_S *msgmap, int aopt)
2337 int rv = 0;
2339 if(any_messages(msgmap, NULL, "to Forward")){
2340 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2341 return rv;
2343 rv = forward(state, NULL);
2345 if(MCMD_ISAGG(aopt))
2346 restore_selected(msgmap);
2348 state->mangled_screen = 1;
2351 return rv;
2355 /*----------------------------------------------------------------------
2356 Execute BOUNCE message command
2358 Args: state -- Various satate info
2359 msgmap -- map of c-client to local message numbers
2360 aopt -- aggregate options
2362 Result: selected message[s] bounced or not
2364 ----*/
2366 cmd_bounce(struct pine *state, MSGNO_S *msgmap, int aopt)
2368 int rv = 0;
2369 ACTION_S *role = NULL;
2371 if(any_messages(msgmap, NULL, "to Bounce")){
2372 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2373 return rv;
2375 if(MCMD_ISAGG(aopt)){ /* check for possible role */
2376 PAT_STATE pstate;
2377 int action;
2379 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
2380 static ESCKEY_S yesno_opts[] = {
2381 {'y', 'y', "Y", N_("Yes")},
2382 {'n', 'n', "N", N_("No")},
2383 {-1, 0, NULL, NULL}
2386 action = radio_buttons(_("Bounce messages using a role? "),
2387 -FOOTER_ROWS(state), yesno_opts,
2388 'y', 'x', h_role_compose, RB_NORM);
2389 if(action == 'y'){
2390 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
2392 redraw = state->redrawer;
2393 state->redrawer = NULL;
2394 prev_screen = state->prev_screen;
2395 role = NULL;
2396 state->next_screen = SCREEN_FUN_NULL;
2398 if(role_select_screen(state, &role, MC_BOUNCE) < 0)
2399 cmd_cancelled(_("Bounce"));
2400 else{
2401 if(role)
2402 role = combine_inherited_role(role);
2403 else{
2404 role = (ACTION_S *) fs_get(sizeof(*role));
2405 memset((void *) role, 0, sizeof(*role));
2406 role->nick = cpystr("Default Role");
2410 if(redraw)
2411 (*redraw)();
2413 state->next_screen = prev_screen;
2414 state->redrawer = redraw;
2415 state->mangled_screen = 1;
2420 rv = bounce(state, role);
2422 if(role)
2423 free_action(&role);
2425 if(MCMD_ISAGG(aopt))
2426 restore_selected(msgmap);
2428 state->mangled_footer = 1;
2431 return rv;
2435 /*----------------------------------------------------------------------
2436 Execute save message command: prompt for folder and call function to save
2438 Args: screen_line -- Line on the screen to prompt on
2439 message -- The MESSAGECACHE entry of message to save
2441 Result: The folder lister can be called to make selection; mangled screen set
2443 This does the prompting for the folder name to save to, possibly calling
2444 up the folder display for selection of folder by user.
2445 ----*/
2447 cmd_save(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
2449 char newfolder[MAILTMPLEN], nmsgs[32], *nick;
2450 int we_cancel = 0, rv = 0, save_flags;
2451 long i, raw;
2452 CONTEXT_S *cntxt = NULL;
2453 ENVELOPE *e = NULL;
2454 SaveDel del = DontAsk;
2455 SavePreserveOrder pre = DontAskPreserve;
2457 dprint((4, "\n - saving message -\n"));
2459 if(MCMD_ISAGG(aopt) && !pseudo_selected(stream, msgmap))
2460 return rv;
2462 state->ugly_consider_advancing_bit = 0;
2463 if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM, state)
2464 && msgno_any_deletedparts(stream, msgmap)
2465 && want_to(_("Saved copy will NOT include entire message! Continue"),
2466 'y', 'n', NO_HELP, WT_FLUSH_IN | WT_SEQ_SENSITIVE) != 'y'){
2467 restore_selected(msgmap);
2468 cmd_cancelled("Save message");
2469 return rv;
2472 raw = mn_m2raw(msgmap, mn_get_cur(msgmap));
2474 if(mn_total_cur(msgmap) <= 1L){
2475 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld ", mn_get_cur(msgmap));
2476 nmsgs[sizeof(nmsgs)-1] = '\0';
2477 e = pine_mail_fetchstructure(stream, raw, NULL);
2478 if(!e) {
2479 q_status_message(SM_ORDER | SM_DING, 3, 4,
2480 _("Can't save message. Error accessing folder"));
2481 restore_selected(msgmap);
2482 return rv;
2485 else{
2486 snprintf(nmsgs, sizeof(nmsgs), "%s msgs ", comatose(mn_total_cur(msgmap)));
2487 nmsgs[sizeof(nmsgs)-1] = '\0';
2489 /* e is just used to get a default save folder from the first msg */
2490 e = pine_mail_fetchstructure(stream,
2491 mn_m2raw(msgmap, mn_first_cur(msgmap)),
2492 NULL);
2495 del = (!READONLY_FOLDER(stream) && F_OFF(F_SAVE_WONT_DELETE, ps_global))
2496 ? Del : NoDel;
2497 if(mn_total_cur(msgmap) > 1L)
2498 pre = F_OFF(F_AGG_SEQ_COPY, ps_global) ? Preserve : NoPreserve;
2499 else
2500 pre = DontAskPreserve;
2502 if(save_prompt(state, &cntxt, newfolder, sizeof(newfolder), nmsgs, e,
2503 raw, NULL, &del, &pre)){
2505 if(ps_global && ps_global->ttyo){
2506 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2507 ps_global->mangled_footer = 1;
2510 save_flags = SV_FIX_DELS;
2511 if(pre == RetPreserve)
2512 save_flags |= SV_PRESERVE;
2513 if(del == RetDel)
2514 save_flags |= SV_DELETE;
2515 if(ps_global->context_list == cntxt && !strucmp(newfolder, ps_global->inbox_name))
2516 save_flags |= SV_INBOXWOCNTXT;
2518 we_cancel = busy_cue(_("Saving"), NULL, 1);
2519 i = save(state, stream, cntxt, newfolder, msgmap, save_flags);
2520 if(we_cancel)
2521 cancel_busy_cue(0);
2523 if(i == mn_total_cur(msgmap)){
2524 rv++;
2525 if(mn_total_cur(msgmap) <= 1L){
2526 int need, avail = ps_global->ttyo->screen_cols - 2;
2527 int lennick, lenfldr;
2529 if(cntxt
2530 && ps_global->context_list->next
2531 && context_isambig(newfolder)){
2532 lennick = MIN(strlen(cntxt->nickname), 500);
2533 lenfldr = MIN(strlen(newfolder), 500);
2534 need = 27 + strlen(long2string(mn_get_cur(msgmap))) +
2535 lenfldr + lennick;
2536 if(need > avail){
2537 if(lennick > 10){
2538 need -= MIN(lennick-10, need-avail);
2539 lennick -= MIN(lennick-10, need-avail);
2542 if(need > avail && lenfldr > 10)
2543 lenfldr -= MIN(lenfldr-10, need-avail);
2546 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2547 "Message %s copied to \"%s\" in <%s>",
2548 long2string(mn_get_cur(msgmap)),
2549 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2550 lenfldr, MidDots),
2551 short_str(cntxt->nickname,
2552 (char *)(tmp_20k_buf+2000), 1000,
2553 lennick, EndDots));
2554 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2556 else if((nick=folder_is_target_of_nick(newfolder, cntxt)) != NULL){
2557 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2558 "Message %s copied to \"%s\"",
2559 long2string(mn_get_cur(msgmap)),
2560 nick);
2561 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2563 else{
2564 char *f = " folder";
2566 lenfldr = MIN(strlen(newfolder), 500);
2567 need = 28 + strlen(long2string(mn_get_cur(msgmap))) +
2568 lenfldr;
2569 if(need > avail){
2570 need -= strlen(f);
2571 f = "";
2572 if(need > avail && lenfldr > 10)
2573 lenfldr -= MIN(lenfldr-10, need-avail);
2576 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
2577 "Message %s copied to%s \"%s\"",
2578 long2string(mn_get_cur(msgmap)), f,
2579 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2580 lenfldr, MidDots));
2581 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2584 else{
2585 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s messages saved",
2586 comatose(mn_total_cur(msgmap)));
2587 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2590 if(del == RetDel){
2591 strncat(tmp_20k_buf, " and deleted", SIZEOF_20KBUF-strlen(tmp_20k_buf)-1);
2592 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2595 q_status_message(SM_ORDER, 0, 3, tmp_20k_buf);
2597 if(!MCMD_ISAGG(aopt) && F_ON(F_SAVE_ADVANCES, state)){
2598 if(sp_new_mail_count(stream))
2599 process_filter_patterns(stream, msgmap,
2600 sp_new_mail_count(stream));
2602 mn_inc_cur(stream, msgmap,
2603 (in_index == View && THREADING()
2604 && sp_viewing_a_thread(stream))
2605 ? MH_THISTHD
2606 : (in_index == View)
2607 ? MH_ANYTHD : MH_NONE);
2610 state->ugly_consider_advancing_bit = 1;
2614 if(MCMD_ISAGG(aopt)) /* straighten out fakes */
2615 restore_selected(msgmap);
2617 if(del == RetDel)
2618 update_titlebar_status(); /* make sure they see change */
2620 return rv;
2624 void
2625 role_compose(struct pine *state)
2627 int action;
2629 if(F_ON(F_ALT_ROLE_MENU, state) && mn_get_total(state->msgmap) > 0L){
2630 PAT_STATE pstate;
2632 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
2633 action = radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
2634 -FOOTER_ROWS(state), choose_action,
2635 'c', 'x', h_role_compose, RB_NORM);
2637 else{
2638 q_status_message(SM_ORDER, 0, 3,
2639 _("No roles available. Use Setup/Rules to add roles."));
2640 return;
2643 else
2644 action = 'c';
2646 if(action == 'c' || action == 'r' || action == 'f' || action == 'b'){
2647 ACTION_S *role = NULL;
2648 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
2650 redraw = state->redrawer;
2651 state->redrawer = NULL;
2652 prev_screen = state->prev_screen;
2653 role = NULL;
2654 state->next_screen = SCREEN_FUN_NULL;
2656 /* Setup role */
2657 if(role_select_screen(state, &role,
2658 action == 'f' ? MC_FORWARD :
2659 action == 'r' ? MC_REPLY :
2660 action == 'b' ? MC_BOUNCE :
2661 action == 'c' ? MC_COMPOSE : 0) < 0){
2662 cmd_cancelled(action == 'f' ? _("Forward") :
2663 action == 'r' ? _("Reply") :
2664 action == 'c' ? _("Composition") : _("Bounce"));
2665 state->next_screen = prev_screen;
2666 state->redrawer = redraw;
2667 state->mangled_screen = 1;
2669 else{
2671 * If default role was selected (NULL) we need to make
2672 * up a role which won't do anything, but will cause
2673 * compose_mail to think there's already a role so that
2674 * it won't try to confirm the default.
2676 if(role)
2677 role = combine_inherited_role(role);
2678 else{
2679 role = (ACTION_S *) fs_get(sizeof(*role));
2680 memset((void *) role, 0, sizeof(*role));
2681 role->nick = cpystr("Default Role");
2684 state->redrawer = NULL;
2685 switch(action){
2686 case 'c':
2687 compose_mail(NULL, NULL, role, NULL, NULL);
2688 break;
2690 case 'r':
2691 (void) reply(state, role);
2692 break;
2694 case 'f':
2695 (void) forward(state, role);
2696 break;
2698 case 'b':
2699 (void) bounce(state, role);
2700 break;
2703 if(role)
2704 free_action(&role);
2706 state->next_screen = prev_screen;
2707 state->redrawer = redraw;
2708 state->mangled_screen = 1;
2714 /*----------------------------------------------------------------------
2715 Do the dirty work of prompting the user for a folder name
2717 Args:
2718 nfldr should be a buffer at least MAILTMPLEN long
2719 dela -- a pointer to a SaveDel. If it is
2720 DontAsk on input, don't offer Delete prompt
2721 Del on input, offer Delete command with default of Delete
2722 NoDel NoDelete
2723 RetDel and RetNoDel are return values
2726 Result:
2728 ----*/
2730 save_prompt(struct pine *state, CONTEXT_S **cntxt, char *nfldr, size_t len_nfldr,
2731 char *nmsgs, ENVELOPE *env, long int rawmsgno, char *section,
2732 SaveDel *dela, SavePreserveOrder *prea)
2734 int rc, ku = -1, n, flags, last_rc = 0, saveable_count = 0, done = 0;
2735 int delindex, preindex, r;
2736 char prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN];
2737 char *buf = tmp_20k_buf;
2738 char shortbuf[200];
2739 char *folder;
2740 HelpType help;
2741 SaveDel del = DontAsk;
2742 SavePreserveOrder pre = DontAskPreserve;
2743 char *deltext = NULL;
2744 static HISTORY_S *history = NULL;
2745 CONTEXT_S *tc;
2746 ESCKEY_S ekey[10];
2748 if(!cntxt)
2749 alpine_panic("no context ptr in save_prompt");
2751 init_hist(&history, HISTSIZE);
2753 if(!(folder = save_get_default(state, env, rawmsgno, section, cntxt)))
2754 return(0); /* message expunged! */
2756 /* how many context's can be saved to... */
2757 for(tc = state->context_list; tc; tc = tc->next)
2758 if(!NEWS_TEST(tc))
2759 saveable_count++;
2761 /* set up extra command option keys */
2762 rc = 0;
2763 ekey[rc].ch = ctrl('T');
2764 ekey[rc].rval = 2;
2765 ekey[rc].name = "^T";
2766 /* TRANSLATORS: command means go to Folders list */
2767 ekey[rc++].label = N_("To Fldrs");
2769 if(saveable_count > 1){
2770 ekey[rc].ch = ctrl('P');
2771 ekey[rc].rval = 10;
2772 ekey[rc].name = "^P";
2773 ekey[rc++].label = N_("Prev Collection");
2775 ekey[rc].ch = ctrl('N');
2776 ekey[rc].rval = 11;
2777 ekey[rc].name = "^N";
2778 ekey[rc++].label = N_("Next Collection");
2781 if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
2782 ekey[rc].ch = TAB;
2783 ekey[rc].rval = 12;
2784 ekey[rc].name = "TAB";
2785 /* TRANSLATORS: command asks alpine to complete the name when tab is typed */
2786 ekey[rc++].label = N_("Complete");
2789 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
2790 ekey[rc].ch = ctrl('X');
2791 ekey[rc].rval = 14;
2792 ekey[rc].name = "^X";
2793 /* TRANSLATORS: list all the matches */
2794 ekey[rc++].label = N_("ListMatches");
2797 if(dela && (*dela == NoDel || *dela == Del)){
2798 ekey[rc].ch = ctrl('R');
2799 ekey[rc].rval = 15;
2800 ekey[rc].name = "^R";
2801 delindex = rc++;
2802 del = *dela;
2805 if(prea && (*prea == NoPreserve || *prea == Preserve)){
2806 ekey[rc].ch = ctrl('W');
2807 ekey[rc].rval = 16;
2808 ekey[rc].name = "^W";
2809 preindex = rc++;
2810 pre = *prea;
2813 if(saveable_count > 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2814 ekey[rc].ch = KEY_UP;
2815 ekey[rc].rval = 10;
2816 ekey[rc].name = "";
2817 ekey[rc++].label = "";
2819 ekey[rc].ch = KEY_DOWN;
2820 ekey[rc].rval = 11;
2821 ekey[rc].name = "";
2822 ekey[rc++].label = "";
2824 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2825 ekey[rc].ch = KEY_UP;
2826 ekey[rc].rval = 30;
2827 ekey[rc].name = "";
2828 ku = rc;
2829 ekey[rc++].label = "";
2831 ekey[rc].ch = KEY_DOWN;
2832 ekey[rc].rval = 31;
2833 ekey[rc].name = "";
2834 ekey[rc++].label = "";
2837 ekey[rc].ch = -1;
2839 *nfldr = '\0';
2840 help = NO_HELP;
2841 while(!done){
2842 /* only show collection number if more than one available */
2843 if(ps_global->context_list->next)
2844 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder in <%s> [%s] : ",
2845 deltext ? deltext : "",
2846 nmsgs,
2847 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2848 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2849 else
2850 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder [%s] : ",
2851 deltext ? deltext : "",
2852 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2854 prompt[sizeof(prompt)-1] = '\0';
2857 * If the prompt won't fit, try removing deltext.
2859 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && deltext){
2860 if(ps_global->context_list->next)
2861 snprintf(prompt, sizeof(prompt), "SAVE %sto folder in <%s> [%s] : ",
2862 nmsgs,
2863 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2864 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2865 else
2866 snprintf(prompt, sizeof(prompt), "SAVE %sto folder [%s] : ",
2867 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2869 prompt[sizeof(prompt)-1] = '\0';
2873 * If the prompt still won't fit, remove the extra info contained
2874 * in nmsgs.
2876 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && *nmsgs){
2877 if(ps_global->context_list->next)
2878 snprintf(prompt, sizeof(prompt), "SAVE to folder in <%s> [%s] : ",
2879 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2880 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2881 else
2882 snprintf(prompt, sizeof(prompt), "SAVE to folder [%s] : ",
2883 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2885 prompt[sizeof(prompt)-1] = '\0';
2888 if(del != DontAsk)
2889 ekey[delindex].label = (del == NoDel) ? "Delete" : "No Delete";
2891 if(pre != DontAskPreserve)
2892 ekey[preindex].label = (pre == NoPreserve) ? "Preserve Order" : "Any Order";
2894 if(ku >= 0){
2895 if(items_in_hist(history) > 1){
2896 ekey[ku].name = HISTORY_UP_KEYNAME;
2897 ekey[ku].label = HISTORY_KEYLABEL;
2898 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
2899 ekey[ku+1].label = HISTORY_KEYLABEL;
2901 else{
2902 ekey[ku].name = "";
2903 ekey[ku].label = "";
2904 ekey[ku+1].name = "";
2905 ekey[ku+1].label = "";
2909 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
2910 rc = optionally_enter(nfldr, -FOOTER_ROWS(state), 0, len_nfldr,
2911 prompt, ekey, help, &flags);
2913 switch(rc){
2914 case -1 :
2915 q_status_message(SM_ORDER | SM_DING, 3, 3,
2916 _("Error reading folder name"));
2917 done--;
2918 break;
2920 case 0 :
2921 removing_trailing_white_space(nfldr);
2922 removing_leading_white_space(nfldr);
2924 if(*nfldr || *folder){
2925 char *p, *name, *fullname = NULL;
2926 int exists, breakout = FALSE;
2928 if(!*nfldr){
2929 strncpy(nfldr, folder, len_nfldr-1);
2930 nfldr[len_nfldr-1] = '\0';
2933 save_hist(history, nfldr, 0, (void *) *cntxt);
2935 if(!(name = folder_is_nick(nfldr, FOLDERS(*cntxt), 0)))
2936 name = nfldr;
2938 if(update_folder_spec(expanded, sizeof(expanded), name)){
2939 strncpy(name = nfldr, expanded, len_nfldr-1);
2940 nfldr[len_nfldr-1] = '\0';
2943 exists = folder_name_exists(*cntxt, name, &fullname);
2945 if(exists == FEX_ERROR){
2946 q_status_message1(SM_ORDER, 0, 3,
2947 _("Problem accessing folder \"%s\""),
2948 nfldr);
2949 done--;
2951 else{
2952 if(fullname){
2953 strncpy(name = nfldr, fullname, len_nfldr-1);
2954 nfldr[len_nfldr-1] = '\0';
2955 fs_give((void **) &fullname);
2956 breakout = TRUE;
2959 if(exists & FEX_ISFILE){
2960 done++;
2962 else if((exists & FEX_ISDIR)){
2963 char tmp[MAILTMPLEN];
2965 tc = *cntxt;
2966 if(breakout){
2967 CONTEXT_S *fake_context;
2968 size_t l;
2970 strncpy(tmp, name, sizeof(tmp));
2971 tmp[sizeof(tmp)-2-1] = '\0';
2972 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
2973 if(l < sizeof(tmp)){
2974 tmp[l] = tc->dir->delim;
2975 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
2978 else
2979 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
2981 tmp[sizeof(tmp)-1] = '\0';
2983 fake_context = new_context(tmp, 0);
2984 nfldr[0] = '\0';
2985 done = display_folder_list(&fake_context, nfldr,
2986 1, folders_for_save);
2987 free_context(&fake_context);
2989 else if(tc->dir->delim
2990 && (p = strrindex(name, tc->dir->delim))
2991 && *(p+1) == '\0')
2992 done = display_folder_list(cntxt, nfldr,
2993 1, folders_for_save);
2994 else{
2995 q_status_message1(SM_ORDER, 3, 3,
2996 _("\"%s\" is a directory"), name);
2997 if(tc->dir->delim
2998 && !((p=strrindex(name, tc->dir->delim)) && *(p+1) == '\0')){
2999 strncpy(tmp, name, sizeof(tmp));
3000 tmp[sizeof(tmp)-1] = '\0';
3001 snprintf(nfldr, len_nfldr, "%s%c", tmp, tc->dir->delim);
3005 else{ /* Doesn't exist, create! */
3006 if((fullname = folder_as_breakout(*cntxt, name)) != NULL){
3007 strncpy(name = nfldr, fullname, len_nfldr-1);
3008 nfldr[len_nfldr-1] = '\0';
3009 fs_give((void **) &fullname);
3012 switch(create_for_save(*cntxt, name)){
3013 case 1 : /* success */
3014 done++;
3015 break;
3016 case 0 : /* error */
3017 case -1 : /* declined */
3018 done--;
3019 break;
3024 break;
3026 /* else fall thru like they cancelled */
3028 case 1 :
3029 cmd_cancelled("Save message");
3030 done--;
3031 break;
3033 case 2 :
3034 r = display_folder_list(cntxt, nfldr, 0, folders_for_save);
3036 if(r)
3037 done++;
3039 break;
3041 case 3 :
3042 helper(h_save, _("HELP FOR SAVE"), HLPD_SIMPLE);
3043 ps_global->mangled_screen = 1;
3044 break;
3046 case 4 : /* redraw */
3047 break;
3049 case 10 : /* previous collection */
3050 for(tc = (*cntxt)->prev; tc; tc = tc->prev)
3051 if(!NEWS_TEST(tc))
3052 break;
3054 if(!tc){
3055 CONTEXT_S *tc2;
3057 for(tc2 = (tc = (*cntxt))->next; tc2; tc2 = tc2->next)
3058 if(!NEWS_TEST(tc2))
3059 tc = tc2;
3062 *cntxt = tc;
3063 break;
3065 case 11 : /* next collection */
3066 tc = (*cntxt);
3069 if(((*cntxt) = (*cntxt)->next) == NULL)
3070 (*cntxt) = ps_global->context_list;
3071 while(NEWS_TEST(*cntxt) && (*cntxt) != tc);
3072 break;
3074 case 12 : /* file name completion */
3075 if(!folder_complete(*cntxt, nfldr, len_nfldr, &n)){
3076 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
3077 r = display_folder_list(cntxt, nfldr, 1, folders_for_save);
3078 if(r)
3079 done++; /* bingo! */
3080 else
3081 rc = 0; /* burn last_rc */
3083 else
3084 Writechar(BELL, 0);
3087 break;
3089 case 14 : /* file name completion */
3090 r = display_folder_list(cntxt, nfldr, 2, folders_for_save);
3091 if(r)
3092 done++; /* bingo! */
3093 else
3094 rc = 0; /* burn last_rc */
3096 break;
3098 case 15 : /* Delete / No Delete */
3099 del = (del == NoDel) ? Del : NoDel;
3100 deltext = (del == NoDel) ? " (no delete)" : " (and delete)";
3101 break;
3103 case 16 : /* Preserve Order or not */
3104 pre = (pre == NoPreserve) ? Preserve : NoPreserve;
3105 break;
3107 case 30 :
3108 if((p = get_prev_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3109 strncpy(nfldr, p, len_nfldr);
3110 nfldr[len_nfldr-1] = '\0';
3111 if(history->hist[history->curindex])
3112 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3114 else
3115 Writechar(BELL, 0);
3117 break;
3119 case 31 :
3120 if((p = get_next_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3121 strncpy(nfldr, p, len_nfldr);
3122 nfldr[len_nfldr-1] = '\0';
3123 if(history->hist[history->curindex])
3124 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3126 else
3127 Writechar(BELL, 0);
3129 break;
3131 default :
3132 alpine_panic("Unhandled case");
3133 break;
3136 last_rc = rc;
3139 ps_global->mangled_footer = 1;
3141 if(done < 0)
3142 return(0);
3144 if(*nfldr){
3145 strncpy(ps_global->last_save_folder, nfldr, sizeof(ps_global->last_save_folder)-1);
3146 ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0';
3147 if(*cntxt)
3148 ps_global->last_save_context = *cntxt;
3150 else{
3151 strncpy(nfldr, folder, len_nfldr-1);
3152 nfldr[len_nfldr-1] = '\0';
3155 /* nickname? Copy real name to nfldr */
3156 if(*cntxt
3157 && context_isambig(nfldr)
3158 && (p = folder_is_nick(nfldr, FOLDERS(*cntxt), 0))){
3159 strncpy(nfldr, p, len_nfldr-1);
3160 nfldr[len_nfldr-1] = '\0';
3163 if(dela && (*dela == NoDel || *dela == Del))
3164 *dela = (del == NoDel) ? RetNoDel : RetDel;
3166 if(prea && (*prea == NoPreserve || *prea == Preserve))
3167 *prea = (pre == NoPreserve) ? RetNoPreserve : RetPreserve;
3169 return(1);
3173 /*----------------------------------------------------------------------
3174 Prompt user before implicitly creating a folder for saving
3176 Args: context - context to create folder in
3177 folder - folder name to create
3179 Result: 1 on proceed, -1 on decline, 0 on error
3181 ----*/
3183 create_for_save_prompt(CONTEXT_S *context, char *folder, int sequence_sensitive)
3185 if(context && ps_global->context_list->next && context_isambig(folder)){
3186 if(context->use & CNTXT_INCMNG){
3187 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3188 _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
3189 folder, (strlen(folder) > 15) ? "..." : "");
3190 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
3191 return(0); /* error */
3194 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3195 _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
3196 folder, (strlen(folder) > 15) ? "..." : "",
3197 context->nickname,
3198 (strlen(context->nickname) > 15) ? "..." : "");
3200 else
3201 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
3202 _("Folder \"%.40s%s\" doesn't exist. Create"),
3203 folder, strlen(folder) > 40 ? "..." : "");
3205 if(want_to(tmp_20k_buf, 'y', 'n',
3206 NO_HELP, (sequence_sensitive) ? WT_SEQ_SENSITIVE : WT_NORM) != 'y'){
3207 cmd_cancelled("Save message");
3208 return(-1);
3211 return(1);
3216 /*----------------------------------------------------------------------
3217 Expunge messages from current folder
3219 Args: state -- pointer to struct holding a bunch of pine state
3220 msgmap -- table mapping msg nums to c-client sequence nums
3221 qline -- screen line to ask questions on
3222 agg -- boolean indicating we're to operate on aggregate set
3224 Result:
3225 ----*/
3227 cmd_expunge(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int agg)
3229 long del_count, prefilter_del_count;
3230 int we_cancel = 0, rv = 0;
3231 char prompt[MAX_SCREEN_COLS+1];
3232 char *sequence;
3233 COLOR_PAIR *lastc = NULL;
3235 dprint((2, "\n - expunge -\n"));
3237 del_count = 0;
3239 sequence = MCMD_ISAGG(agg) ? selected_sequence(stream, msgmap, NULL, 0) : NULL;
3241 if(MCMD_ISAGG(agg)){
3242 long i;
3243 MESSAGECACHE *mc;
3244 for(i = 1L; i <= stream->nmsgs; i++){
3245 if((mc = mail_elt(stream, i)) != NULL
3246 && mc->sequence && mc->deleted)
3247 del_count++;
3249 if(del_count == 0){
3250 q_status_message(SM_ORDER, 0, 4,
3251 _("No selected messages are deleted"));
3252 return 0;
3254 } else {
3255 if(!any_messages(msgmap, NULL, "to Expunge"))
3256 return rv;
3259 if(IS_NEWS(stream) && stream->rdonly){
3260 if(!MCMD_ISAGG(agg))
3261 del_count = count_flagged(stream, F_DEL);
3262 if(del_count > 0L){
3263 state->mangled_footer = 1;
3264 snprintf(prompt, sizeof(prompt), "Exclude %ld message%s from %.*s", del_count,
3265 plural(del_count), sizeof(prompt)-40,
3266 pretty_fn(state->cur_folder));
3267 prompt[sizeof(prompt)-1] = '\0';
3268 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3269 || (F_ON(F_AUTO_EXPUNGE, state)
3270 && (state->context_current
3271 && (state->context_current->use & CNTXT_INCMNG))
3272 && context_isambig(state->cur_folder))
3273 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
3275 if(F_ON(F_NEWS_CROSS_DELETE, state))
3276 cross_delete_crossposts(stream);
3278 msgno_exclude_deleted(stream, msgmap, sequence);
3279 clear_index_cache(stream, 0);
3282 * This is kind of surprising at first. For most sort
3283 * orders, if the whole set is sorted, then any subset
3284 * is also sorted. Not so for threaded sorts.
3286 if(SORT_IS_THREADED(msgmap))
3287 refresh_sort(stream, msgmap, SRT_NON);
3289 state->mangled_body = 1;
3290 state->mangled_header = 1;
3291 q_status_message2(SM_ORDER, 0, 4,
3292 "%s message%s excluded",
3293 long2string(del_count),
3294 plural(del_count));
3296 else
3297 any_messages(NULL, NULL, "Excluded");
3299 else
3300 any_messages(NULL, "deleted", "to Exclude");
3302 return del_count;
3304 else if(READONLY_FOLDER(stream)){
3305 q_status_message(SM_ORDER, 0, 4,
3306 _("Can't expunge. Folder is read-only"));
3307 return del_count;
3310 if(!MCMD_ISAGG(agg)){
3311 prefilter_del_count = count_flagged(stream, F_DEL|F_NOFILT);
3312 mail_expunge_prefilter(stream, MI_NONE);
3313 del_count = count_flagged(stream, F_DEL|F_NOFILT);
3316 if(del_count != 0){
3317 int ret;
3318 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3320 snprintf(prompt, sizeof(prompt), "Expunge %ld message%s from %.*s", del_count,
3321 plural(del_count), sizeof(prompt)-40,
3322 pretty_fn((char *) fname));
3323 if(fname) fs_give((void **)&fname);
3324 prompt[sizeof(prompt)-1] = '\0';
3325 state->mangled_footer = 1;
3327 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3328 || (F_ON(F_AUTO_EXPUNGE, state)
3329 && ((!strucmp(state->cur_folder,state->inbox_name))
3330 || (state->context_current->use & CNTXT_INCMNG))
3331 && context_isambig(state->cur_folder))
3332 || (ret=want_to(prompt, 'y', 0, NO_HELP, WT_NORM)) == 'y')
3333 ret = 'y';
3335 if(ret == 'x')
3336 cmd_cancelled("Expunge");
3338 if(ret != 'y')
3339 return 0;
3342 dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
3343 mn_get_total(msgmap), mn_get_cur(msgmap), del_count));
3345 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3346 state->VAR_TITLE_BACK_COLOR,
3347 PSC_REV|PSC_RET);
3349 PutLine0(0, 0, "**"); /* indicate delay */
3351 if(lastc){
3352 (void)pico_set_colorp(lastc, PSC_NONE);
3353 free_color_pair(&lastc);
3356 MoveCursor(state->ttyo->screen_rows -FOOTER_ROWS(state), 0);
3357 fflush(stdout);
3359 we_cancel = busy_cue(_("Expunging"), NULL, 1);
3361 if(cmd_expunge_work(stream, msgmap, sequence))
3362 state->mangled_body = 1;
3364 if(sequence)
3365 fs_give((void **)&sequence);
3367 if(we_cancel)
3368 cancel_busy_cue((sp_expunge_count(stream) > 0) ? 0 : -1);
3370 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3371 state->VAR_TITLE_BACK_COLOR,
3372 PSC_REV|PSC_RET);
3373 PutLine0(0, 0, " "); /* indicate delay's over */
3375 if(lastc){
3376 (void)pico_set_colorp(lastc, PSC_NONE);
3377 free_color_pair(&lastc);
3380 fflush(stdout);
3382 if(sp_expunge_count(stream) > 0){
3384 * This is kind of surprising at first. For most sort
3385 * orders, if the whole set is sorted, then any subset
3386 * is also sorted. Not so for threaded sorts.
3388 if(SORT_IS_THREADED(msgmap))
3389 refresh_sort(stream, msgmap, SRT_NON);
3391 else{
3392 if(del_count){
3393 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3394 q_status_message1(SM_ORDER, 0, 3,
3395 _("No messages expunged from folder \"%s\""),
3396 pretty_fn((char *) fname));
3397 if(fname) fs_give((void **)&fname);
3399 else if(!prefilter_del_count)
3400 q_status_message(SM_ORDER, 0, 3,
3401 _("No messages marked deleted. No messages expunged."));
3403 return del_count;
3407 /*----------------------------------------------------------------------
3408 Expunge_and_close callback to prompt user for confirmation
3410 Args: stream -- folder's stream
3411 folder -- name of folder containing folders
3412 deleted -- number of del'd msgs
3414 Result: 'y' to continue with expunge
3415 ----*/
3417 expunge_prompt(MAILSTREAM *stream, char *folder, long int deleted)
3419 long max_folder;
3420 int charcnt = 0;
3421 char prompt_b[MAX_SCREEN_COLS+1], temp[MAILTMPLEN+1], buff[MAX_SCREEN_COLS+1];
3422 char *short_folder_name;
3424 if(deleted == 1)
3425 charcnt = 1;
3426 else{
3427 snprintf(temp, sizeof(temp), "%ld", deleted);
3428 charcnt = strlen(temp)+1;
3431 max_folder = MAX(1,MAXPROMPT - (36+charcnt));
3432 strncpy(temp, folder, sizeof(temp));
3433 temp[sizeof(temp)-1] = '\0';
3434 short_folder_name = short_str(temp,buff,sizeof(buff),max_folder,FrontDots);
3436 if(IS_NEWS(stream))
3437 snprintf(prompt_b, sizeof(prompt_b),
3438 "Delete %s%ld message%s from \"%s\"",
3439 (deleted > 1L) ? "all " : "", deleted,
3440 plural(deleted), short_folder_name);
3441 else
3442 snprintf(prompt_b, sizeof(prompt_b),
3443 "Expunge the %ld deleted message%s from \"%s\"",
3444 deleted, deleted == 1 ? "" : "s",
3445 short_folder_name);
3447 return(want_to(prompt_b, 'y', 0, NO_HELP, WT_NORM));
3452 * This is used with multiple append saves. Call it once before
3453 * the series of appends with SSCP_INIT and once after all are
3454 * done with SSCP_END. In between, it is called automatically
3455 * from save_fetch_append or save_fetch_append_cb when we need
3456 * to ask the user if he or she wants to continue even though
3457 * announced message size doesn't match the actual message size.
3458 * As of 2008-02-29 the gmail IMAP server has these size mismatches
3459 * on a regular basis even though the data is ok.
3462 save_size_changed_prompt(long msgno, int flags)
3464 int ret;
3465 char prompt[100];
3466 static int remember_the_yes = 0;
3467 static int possible_corruption = 0;
3468 static ESCKEY_S save_size_opts[] = {
3469 {'y', 'y', "Y", "Yes"},
3470 {'n', 'n', "N", "No"},
3471 {'a', 'a', "A", "yes to All"},
3472 {-1, 0, NULL, NULL}
3475 if(flags & SSCP_INIT || flags & SSCP_END){
3476 if(flags & SSCP_END && possible_corruption)
3477 q_status_message(SM_ORDER, 3, 3, "There is possible data corruption, check the results");
3479 remember_the_yes = 0;
3480 possible_corruption = 0;
3481 ps_global->noshow_error = 0;
3482 ps_global->noshow_warn = 0;
3483 return(0);
3486 if(remember_the_yes){
3487 snprintf(prompt, sizeof(prompt),
3488 "Message to save shrank! (msg # %ld): Continuing", msgno);
3489 q_status_message(SM_ORDER, 0, 3, prompt);
3490 display_message('x');
3491 return(remember_the_yes);
3494 snprintf(prompt, sizeof(prompt),
3495 "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno);
3496 ret = radio_buttons(prompt, -FOOTER_ROWS(ps_global), save_size_opts,
3497 'n', 0, h_save_size_changed, RB_NORM);
3499 switch(ret){
3500 case 'a':
3501 remember_the_yes = 'y';
3502 possible_corruption++;
3503 return(remember_the_yes);
3505 case 'y':
3506 possible_corruption++;
3507 return('y');
3509 default:
3510 possible_corruption = 0;
3511 ps_global->noshow_error = 1;
3512 ps_global->noshow_warn = 1;
3513 break;
3516 return('n');
3520 /*----------------------------------------------------------------------
3521 Expunge_and_close callback that happens once the decision to expunge
3522 and close has been made and before expunging and closing begins
3525 Args: stream -- folder's stream
3526 folder -- name of folder containing folders
3527 deleted -- number of del'd msgs
3529 Result: 'y' to continue with expunge
3530 ----*/
3531 void
3532 expunge_and_close_begins(int flags, char *folder)
3534 if(!(flags & EC_NO_CLOSE)){
3535 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
3536 q_status_message1(SM_INFO, 0, 1, "Closing \"%.200s\"...", (char *) fname);
3537 flush_status_messages(1);
3538 if(fname) fs_give((void **)&fname);
3543 /*----------------------------------------------------------------------
3544 Export a message to a plain file in users home directory
3546 Args: state -- pointer to struct holding a bunch of pine state
3547 msgmap -- table mapping msg nums to c-client sequence nums
3548 qline -- screen line to ask questions on
3549 agg -- boolean indicating we're to operate on aggregate set
3551 Result:
3552 ----*/
3554 cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt)
3556 char filename[MAXPATH+1], full_filename[MAXPATH+1], *err;
3557 char nmsgs[80];
3558 int r, leading_nl, failure = 0, orig_errno, rflags = GER_NONE;
3559 int flags = GE_IS_EXPORT | GE_SEQ_SENSITIVE, rv = 0;
3560 ENVELOPE *env;
3561 MESSAGECACHE *mc;
3562 BODY *b;
3563 long i, count = 0L, start_of_append, rawno;
3564 gf_io_t pc;
3565 STORE_S *store;
3566 struct variable *vars = ps_global->vars;
3567 ESCKEY_S export_opts[5];
3568 static HISTORY_S *history = NULL;
3570 if(ps_global->restricted){
3571 q_status_message(SM_ORDER, 0, 3,
3572 "Alpine demo can't export messages to files");
3573 return rv;
3576 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
3577 return rv;
3579 export_opts[i = 0].ch = ctrl('T');
3580 export_opts[i].rval = 10;
3581 export_opts[i].name = "^T";
3582 export_opts[i++].label = N_("To Files");
3584 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3585 if(ps_global->VAR_DOWNLOAD_CMD && ps_global->VAR_DOWNLOAD_CMD[0]){
3586 export_opts[i].ch = ctrl('V');
3587 export_opts[i].rval = 12;
3588 export_opts[i].name = "^V";
3589 /* TRANSLATORS: this is an abbreviation for Download Messages */
3590 export_opts[i++].label = N_("Downld Msg");
3592 #endif /* !(DOS || MAC) */
3594 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
3595 export_opts[i].ch = ctrl('I');
3596 export_opts[i].rval = 11;
3597 export_opts[i].name = "TAB";
3598 export_opts[i++].label = N_("Complete");
3601 #if 0
3602 /* Commented out since it's not yet support! */
3603 if(F_ON(F_ENABLE_SUB_LISTS,ps_global)){
3604 export_opts[i].ch = ctrl('X');
3605 export_opts[i].rval = 14;
3606 export_opts[i].name = "^X";
3607 export_opts[i++].label = N_("ListMatches");
3609 #endif
3612 * If message has attachments, add a toggle that will allow the user
3613 * to save all of the attachments to a single directory, using the
3614 * names provided with the attachments or part names. What we'll do is
3615 * export the message as usual, and then export the attachments into
3616 * a subdirectory that did not exist before. The subdir will be named
3617 * something based on the name of the file being saved to, but a
3618 * unique, new name.
3620 if(!MCMD_ISAGG(aopt)
3621 && state->mail_stream
3622 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3623 && rawno <= state->mail_stream->nmsgs
3624 && (env = pine_mail_fetchstructure(state->mail_stream, rawno, &b))
3625 && b
3626 && b->type == TYPEMULTIPART
3627 && b->subtype
3628 && strucmp(b->subtype, "ALTERNATIVE") != 0){
3629 PART *part;
3631 part = b->nested.part; /* 1st part */
3632 if(part && part->next)
3633 flags |= GE_ALLPARTS;
3636 export_opts[i].ch = -1;
3637 filename[0] = '\0';
3639 if(mn_total_cur(msgmap) <= 1L){
3640 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld", mn_get_cur(msgmap));
3641 nmsgs[sizeof(nmsgs)-1] = '\0';
3643 else{
3644 snprintf(nmsgs, sizeof(nmsgs), "%s messages", comatose(mn_total_cur(msgmap)));
3645 nmsgs[sizeof(nmsgs)-1] = '\0';
3648 r = get_export_filename(state, filename, NULL, full_filename,
3649 sizeof(filename), nmsgs, "EXPORT",
3650 export_opts, &rflags, qline, flags, &history);
3652 if(r < 0){
3653 switch(r){
3654 case -1:
3655 cmd_cancelled("Export message");
3656 break;
3658 case -2:
3659 q_status_message1(SM_ORDER, 0, 2,
3660 _("Can't export to file outside of %s"),
3661 VAR_OPER_DIR);
3662 break;
3665 goto fini;
3667 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3668 else if(r == 12){ /* Download */
3669 char cmd[MAXPATH], *tfp = NULL;
3670 int next = 0;
3671 PIPE_S *syspipe;
3672 STORE_S *so;
3673 gf_io_t pc;
3675 if(ps_global->restricted){
3676 q_status_message(SM_ORDER | SM_DING, 3, 3,
3677 "Download disallowed in restricted mode");
3678 goto fini;
3681 err = NULL;
3682 tfp = temp_nam(NULL, "pd");
3683 build_updown_cmd(cmd, sizeof(cmd), ps_global->VAR_DOWNLOAD_CMD_PREFIX,
3684 ps_global->VAR_DOWNLOAD_CMD, tfp);
3685 dprint((1, "Download cmd called: \"%s\"\n", cmd));
3686 if((so = so_get(FileStar, tfp, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){
3687 gf_set_so_writec(&pc, so);
3689 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
3690 if(!(state->mail_stream
3691 && (rawno = mn_m2raw(msgmap, i)) > 0L
3692 && rawno <= state->mail_stream->nmsgs
3693 && (mc = mail_elt(state->mail_stream, rawno))
3694 && mc->valid))
3695 mc = NULL;
3697 if(!(env = pine_mail_fetchstructure(state->mail_stream,
3698 mn_m2raw(msgmap, i), &b))
3699 || !bezerk_delimiter(env, mc, pc, next++)
3700 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
3701 env, b, NULL, FM_NEW_MESS | FM_NOWRAP, pc)){
3702 q_status_message(SM_ORDER | SM_DING, 3, 3,
3703 err = "Error writing tempfile for download");
3704 break;
3708 gf_clear_so_writec(so);
3709 if(so_give(&so)){ /* close file */
3710 if(!err)
3711 err = "Error writing tempfile for download";
3714 if(!err){
3715 if((syspipe = open_system_pipe(cmd, NULL, NULL,
3716 PIPE_USER | PIPE_RESET,
3717 0, pipe_callback, pipe_report_error)) != NULL)
3718 (void) close_system_pipe(&syspipe, NULL, pipe_callback);
3719 else
3720 q_status_message(SM_ORDER | SM_DING, 3, 3,
3721 err = _("Error running download command"));
3724 else
3725 q_status_message(SM_ORDER | SM_DING, 3, 3,
3726 err = "Error building temp file for download");
3728 if(tfp){
3729 our_unlink(tfp);
3730 fs_give((void **)&tfp);
3733 if(!err)
3734 q_status_message(SM_ORDER, 0, 3, _("Download Command Completed"));
3736 goto fini;
3738 #endif /* !(DOS || MAC) */
3741 if(rflags & GER_APPEND)
3742 leading_nl = 1;
3743 else
3744 leading_nl = 0;
3746 dprint((5, "Opening file \"%s\" for export\n",
3747 full_filename ? full_filename : "?"));
3749 if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE))){
3750 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3751 /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
3752 _("Error opening file \"%s\" to export message: %s"),
3753 full_filename, error_description(errno));
3754 goto fini;
3756 else
3757 gf_set_so_writec(&pc, store);
3759 err = NULL;
3760 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), count++){
3761 env = pine_mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i),
3762 &b);
3763 if(!env) {
3764 err = _("Can't export message. Error accessing mail folder");
3765 failure = 1;
3766 break;
3769 if(!(state->mail_stream
3770 && (rawno = mn_m2raw(msgmap, i)) > 0L
3771 && rawno <= state->mail_stream->nmsgs
3772 && (mc = mail_elt(state->mail_stream, rawno))
3773 && mc->valid))
3774 mc = NULL;
3776 start_of_append = so_tell(store);
3777 if(!bezerk_delimiter(env, mc, pc, leading_nl)
3778 || !format_message(mn_m2raw(msgmap, i), env, b, NULL,
3779 FM_NEW_MESS | FM_NOWRAP, pc)){
3780 orig_errno = errno; /* save incase things are really bad */
3781 failure = 1; /* pop out of here */
3782 break;
3785 leading_nl = 1;
3788 gf_clear_so_writec(store);
3789 if(so_give(&store)) /* release storage */
3790 failure++;
3792 if(failure){
3793 our_truncate(full_filename, (off_t)start_of_append);
3794 if(err){
3795 dprint((1, "FAILED Export: fetch(%ld): %s\n",
3796 i, err ? err : "?"));
3797 q_status_message(SM_ORDER | SM_DING, 3, 4, err);
3799 else{
3800 dprint((1, "FAILED Export: file \"%s\" : %s\n",
3801 full_filename ? full_filename : "?",
3802 error_description(orig_errno)));
3803 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3804 /* TRANSLATORS: Error exporting to <filename>: <error text> */
3805 _("Error exporting to \"%s\" : %s"),
3806 filename, error_description(orig_errno));
3809 else{
3810 if(rflags & GER_ALLPARTS && full_filename[0]){
3811 char dir[MAXPATH+1];
3812 char lfile[MAXPATH+1];
3813 int ok = 0, tries = 0, saved = 0, errs = 0;
3814 ATTACH_S *a;
3817 * Now we want to save all of the attachments to a subdirectory.
3818 * To make it easier for us and probably easier for the user, and
3819 * to prevent the user from shooting himself in the foot, we
3820 * make a new subdirectory so that we can't possibly step on
3821 * any existing files, and we don't need any interaction with the
3822 * user while saving.
3824 * We'll just use the directory name full_filename.d or if that
3825 * already exists and isn't empty, we'll try adding a suffix to
3826 * that until we get something to use.
3829 if(strlen(full_filename) + strlen(".d") + 1 > sizeof(dir)){
3830 q_status_message1(SM_ORDER | SM_DING, 3, 4,
3831 _("Can't save attachments, filename too long: %s"),
3832 full_filename);
3833 goto fini;
3836 ok = 0;
3837 snprintf(dir, sizeof(dir), "%s.d", full_filename);
3838 dir[sizeof(dir)-1] = '\0';
3840 do {
3841 tries++;
3842 switch(r = is_writable_dir(dir)){
3843 case 0: /* exists and is a writable dir */
3845 * We could figure out if it is empty and use it in
3846 * that case, but that sounds like a lot of work, so
3847 * just fall through to default.
3850 default:
3851 if(strlen(full_filename) + strlen(".d") + 1 +
3852 1 + strlen(long2string((long) tries)) > sizeof(dir)){
3853 q_status_message(SM_ORDER | SM_DING, 3, 4,
3854 "Problem saving attachments");
3855 goto fini;
3858 snprintf(dir, sizeof(dir), "%s.d_%s", full_filename,
3859 long2string((long) tries));
3860 dir[sizeof(dir)-1] = '\0';
3861 break;
3863 case 3: /* doesn't exist, that's good! */
3864 /* make new directory */
3865 ok++;
3866 break;
3868 } while(!ok && tries < 1000);
3870 if(tries >= 1000){
3871 q_status_message(SM_ORDER | SM_DING, 3, 4,
3872 _("Problem saving attachments"));
3873 goto fini;
3876 /* create the new directory */
3877 if(our_mkdir(dir, 0700)){
3878 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3879 _("Problem saving attachments: %s: %s"), dir,
3880 error_description(errno));
3881 goto fini;
3884 if(!(state->mail_stream
3885 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3886 && rawno <= state->mail_stream->nmsgs
3887 && (env=pine_mail_fetchstructure(state->mail_stream,rawno,&b))
3888 && b)){
3889 q_status_message(SM_ORDER | SM_DING, 3, 4,
3890 _("Problem reading message"));
3891 goto fini;
3894 zero_atmts(state->atmts);
3895 describe_mime(b, "", 1, 1, 0, 0);
3897 a = state->atmts;
3898 if(a && a->description) /* skip main body part */
3899 a++;
3901 for(; a->description != NULL; a++){
3902 /* skip over these parts of the message */
3903 if(MIME_MSG_A(a) || MIME_DGST_A(a) || MIME_VCARD_A(a))
3904 continue;
3906 lfile[0] = '\0';
3907 (void) get_filename_parameter(lfile, sizeof(lfile), a->body, NULL);
3909 if(lfile[0] == '\0'){
3910 snprintf(lfile, sizeof(lfile), "part_%.*s", sizeof(lfile)-6,
3911 a->number ? a->number : "?");
3912 lfile[sizeof(lfile)-1] = '\0';
3915 if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + 1
3916 > sizeof(filename)){
3917 dprint((2,
3918 "FAILED Att Export: name too long: %s\n",
3919 dir, S_FILESEP, lfile));
3920 errs++;
3921 continue;
3924 snprintf(filename, sizeof(filename), "%s%s%s", dir, S_FILESEP, lfile);
3925 filename[sizeof(filename)-1] = '\0';
3927 if(write_attachment_to_file(state->mail_stream, rawno,
3928 a, GER_NONE, filename) == 1)
3929 saved++;
3930 else
3931 errs++;
3934 if(errs){
3935 if(saved)
3936 q_status_message1(SM_ORDER, 3, 3,
3937 "Errors saving some attachments, %s attachments saved",
3938 long2string((long) saved));
3939 else
3940 q_status_message(SM_ORDER, 3, 3,
3941 _("Problems saving attachments"));
3943 else{
3944 if(saved)
3945 q_status_message2(SM_ORDER, 0, 3,
3946 /* TRANSLATORS: Saved <how many> attachements to <directory name> */
3947 _("Saved %s attachments to %s"),
3948 long2string((long) saved), dir);
3949 else
3950 q_status_message(SM_ORDER, 3, 3, _("No attachments to save"));
3953 else if(mn_total_cur(msgmap) > 1L)
3954 q_status_message4(SM_ORDER,0,3,
3955 "%s message%s %s to file \"%s\"",
3956 long2string(count), plural(count),
3957 rflags & GER_OVER
3958 ? "overwritten"
3959 : rflags & GER_APPEND ? "appended" : "exported",
3960 filename);
3961 else
3962 q_status_message3(SM_ORDER,0,3,
3963 "Message %s %s to file \"%s\"",
3964 long2string(mn_get_cur(msgmap)),
3965 rflags & GER_OVER
3966 ? "overwritten"
3967 : rflags & GER_APPEND ? "appended" : "exported",
3968 filename);
3969 rv++;
3972 fini:
3973 if(MCMD_ISAGG(aopt))
3974 restore_selected(msgmap);
3976 return rv;
3981 * Ask user what file to export to. Export from srcstore to that file.
3983 * Args ps -- pine struct
3984 * srctext -- pointer to source text
3985 * srctype -- type of that source text
3986 * prompt_msg -- see get_export_filename
3987 * lister_msg -- "
3989 * Returns: != 0 : error
3990 * 0 : ok
3993 simple_export(struct pine *ps, void *srctext, SourceType srctype, char *prompt_msg, char *lister_msg)
3995 int r = 1, rflags = GER_NONE;
3996 char filename[MAXPATH+1], full_filename[MAXPATH+1];
3997 STORE_S *store = NULL;
3998 struct variable *vars = ps->vars;
3999 static HISTORY_S *history = NULL;
4000 static ESCKEY_S simple_export_opts[] = {
4001 {ctrl('T'), 10, "^T", N_("To Files")},
4002 {-1, 0, NULL, NULL},
4003 {-1, 0, NULL, NULL}};
4005 if(F_ON(F_ENABLE_TAB_COMPLETE,ps)){
4006 simple_export_opts[r].ch = ctrl('I');
4007 simple_export_opts[r].rval = 11;
4008 simple_export_opts[r].name = "TAB";
4009 simple_export_opts[r].label = N_("Complete");
4012 if(!srctext){
4013 q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
4014 r = -3;
4015 goto fini;
4018 simple_export_opts[++r].ch = -1;
4019 filename[0] = '\0';
4020 full_filename[0] = '\0';
4022 r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename),
4023 prompt_msg, lister_msg, simple_export_opts, &rflags,
4024 -FOOTER_ROWS(ps), GE_IS_EXPORT, &history);
4026 if(r < 0)
4027 goto fini;
4028 else if(!full_filename[0]){
4029 r = -1;
4030 goto fini;
4033 dprint((5, "Opening file \"%s\" for export\n",
4034 full_filename ? full_filename : "?"));
4036 if((store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
4037 char *pipe_err;
4038 gf_io_t pc, gc;
4040 gf_set_so_writec(&pc, store);
4041 gf_set_readc(&gc, srctext, (srctype == CharStar)
4042 ? strlen((char *)srctext)
4043 : 0L,
4044 srctype, 0);
4045 gf_filter_init();
4046 if((pipe_err = gf_pipe(gc, pc)) != NULL){
4047 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4048 /* TRANSLATORS: Problem saving to <filename>: <error text> */
4049 _("Problem saving to \"%s\": %s"),
4050 filename, pipe_err);
4051 r = -3;
4053 else
4054 r = 0;
4056 gf_clear_so_writec(store);
4057 if(so_give(&store)){
4058 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4059 _("Problem saving to \"%s\": %s"),
4060 filename, error_description(errno));
4061 r = -3;
4064 else{
4065 q_status_message2(SM_ORDER | SM_DING, 3, 4,
4066 _("Error opening file \"%s\" for export: %s"),
4067 full_filename, error_description(errno));
4068 r = -3;
4071 fini:
4072 switch(r){
4073 case 0:
4074 /* overloading full_filename */
4075 snprintf(full_filename, sizeof(full_filename), "%c%s",
4076 (prompt_msg && prompt_msg[0])
4077 ? (islower((unsigned char)prompt_msg[0])
4078 ? toupper((unsigned char)prompt_msg[0]) : prompt_msg[0])
4079 : 'T',
4080 (prompt_msg && prompt_msg[0]) ? prompt_msg+1 : "ext");
4081 full_filename[sizeof(full_filename)-1] = '\0';
4082 q_status_message3(SM_ORDER,0,2,"%s %s to \"%s\"",
4083 full_filename,
4084 rflags & GER_OVER
4085 ? "overwritten"
4086 : rflags & GER_APPEND ? "appended" : "exported",
4087 filename);
4088 break;
4090 case -1:
4091 cmd_cancelled("Export");
4092 break;
4094 case -2:
4095 q_status_message1(SM_ORDER, 0, 2,
4096 _("Can't export to file outside of %s"), VAR_OPER_DIR);
4097 break;
4100 ps->mangled_footer = 1;
4101 return(r);
4106 * Ask user what file to export to.
4108 * filename -- On input, this is the filename to start with. On exit,
4109 * this is the filename chosen. (but this isn't used)
4110 * deefault -- This is the default value if user hits return. The
4111 * prompt will have [deefault] added to it automatically.
4112 * full_filename -- This is the full filename on exit.
4113 * len -- Minimum length of _both_ filename and full_filename.
4114 * prompt_msg -- Message to insert in prompt.
4115 * lister_msg -- Message to insert in file_lister.
4116 * opts -- Key options.
4117 * There is a tangled relationship between the callers
4118 * and this routine as far as opts are concerned. Some
4119 * of the opts are handled here. In particular, r == 3,
4120 * r == 10, r == 11, and r == 13 are all handled here.
4121 * Don't use those values unless you want what happens
4122 * here. r == 12 and others are handled by the caller.
4123 * rflags -- Return flags
4124 * GER_OVER - overwrite of existing file
4125 * GER_APPEND - append of existing file
4126 * else file did not exist before
4128 * GER_ALLPARTS - AllParts toggle was turned on
4130 * qline -- Command line to prompt on.
4131 * flags -- Logically OR'd flags
4132 * GE_IS_EXPORT - The command was an Export command
4133 * so the prompt should include
4134 * EXPORT:.
4135 * GE_SEQ_SENSITIVE - The command that got us here is
4136 * sensitive to sequence number changes
4137 * caused by unsolicited expunges.
4138 * GE_NO_APPEND - We will not allow append to an
4139 * existing file, only removal of the
4140 * file if it exists.
4141 * GE_IS_IMPORT - We are selecting for reading.
4142 * No overwriting or checking for
4143 * existence at all. Don't use this
4144 * together with GE_NO_APPEND.
4145 * GE_ALLPARTS - Turn on AllParts toggle.
4146 * GE_BINARY - Turn on Binary toggle.
4148 * Returns: -1 cancelled
4149 * -2 prohibited by VAR_OPER_DIR
4150 * -3 other error, already reported here
4151 * 0 ok
4152 * 12 user chose 12 command from opts
4155 get_export_filename(struct pine *ps, char *filename, char *deefault,
4156 char *full_filename, size_t len, char *prompt_msg,
4157 char *lister_msg, ESCKEY_S *optsarg, int *rflags,
4158 int qline, int flags, HISTORY_S **history)
4160 char dir[MAXPATH+1], dir2[MAXPATH+1];
4161 char precolon[MAXPATH+1], postcolon[MAXPATH+1];
4162 char filename2[MAXPATH+1], tmp[MAXPATH+1], *fn, *ill;
4163 int l, i, ku = -1, r, fatal, homedir = 0, was_abs_path=0, avail, ret = 0;
4164 int allparts = 0, binary = 0;
4165 char prompt_buf[400];
4166 char def[500];
4167 ESCKEY_S *opts = NULL;
4168 struct variable *vars = ps->vars;
4170 if(flags & GE_ALLPARTS || history){
4172 * Copy the opts and add one to the end of the list.
4174 for(i = 0; optsarg[i].ch != -1; i++)
4177 if(history)
4178 i += 2;
4180 if(flags & GE_ALLPARTS)
4181 i++;
4183 if(flags & GE_BINARY)
4184 i++;
4186 opts = (ESCKEY_S *) fs_get((i+1) * sizeof(*opts));
4187 memset(opts, 0, (i+1) * sizeof(*opts));
4189 for(i = 0; optsarg[i].ch != -1; i++){
4190 opts[i].ch = optsarg[i].ch;
4191 opts[i].rval = optsarg[i].rval;
4192 opts[i].name = optsarg[i].name; /* no need to make a copy */
4193 opts[i].label = optsarg[i].label; /* " */
4196 if(flags & GE_ALLPARTS){
4197 allparts = i;
4198 opts[i].ch = ctrl('P');
4199 opts[i].rval = 13;
4200 opts[i].name = "^P";
4201 /* TRANSLATORS: Export all attachment parts */
4202 opts[i++].label = N_("AllParts");
4205 if(flags & GE_BINARY){
4206 binary = i;
4207 opts[i].ch = ctrl('R');
4208 opts[i].rval = 15;
4209 opts[i].name = "^R";
4210 opts[i++].label = N_("Binary");
4213 if(history){
4214 opts[i].ch = KEY_UP;
4215 opts[i].rval = 30;
4216 opts[i].name = "";
4217 ku = i;
4218 opts[i++].label = "";
4220 opts[i].ch = KEY_DOWN;
4221 opts[i].rval = 31;
4222 opts[i].name = "";
4223 opts[i++].label = "";
4226 opts[i].ch = -1;
4228 if(history)
4229 init_hist(history, HISTSIZE);
4231 else
4232 opts = optsarg;
4234 if(rflags)
4235 *rflags = GER_NONE;
4237 if(F_ON(F_USE_CURRENT_DIR, ps))
4238 dir[0] = '\0';
4239 else if(VAR_OPER_DIR){
4240 strncpy(dir, VAR_OPER_DIR, sizeof(dir));
4241 dir[sizeof(dir)-1] = '\0';
4243 #if defined(DOS) || defined(OS2)
4244 else if(VAR_FILE_DIR){
4245 strncpy(dir, VAR_FILE_DIR, sizeof(dir));
4246 dir[sizeof(dir)-1] = '\0';
4248 #endif
4249 else{
4250 dir[0] = '~';
4251 dir[1] = '\0';
4252 homedir=1;
4255 postcolon[0] = '\0';
4256 strncpy(precolon, dir, sizeof(precolon));
4257 precolon[sizeof(precolon)-1] = '\0';
4258 if(deefault){
4259 strncpy(def, deefault, sizeof(def)-1);
4260 def[sizeof(def)-1] = '\0';
4261 removing_leading_and_trailing_white_space(def);
4263 else
4264 def[0] = '\0';
4266 avail = MAX(20, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - MIN_OPT_ENT_WIDTH;
4268 /*---------- Prompt the user for the file name -------------*/
4269 while(1){
4270 int oeflags;
4271 char dirb[50], fileb[50];
4272 int l1, l2, l3, l4, l5, needed;
4273 char *p, p1[100], p2[100], *p3, p4[100], p5[100];
4275 snprintf(p1, sizeof(p1), "%sCopy ",
4276 (flags & GE_IS_EXPORT) ? "EXPORT: " :
4277 (flags & GE_IS_IMPORT) ? "IMPORT: " : "SAVE: ");
4278 p1[sizeof(p1)-1] = '\0';
4279 l1 = strlen(p1);
4281 strncpy(p2, prompt_msg ? prompt_msg : "", sizeof(p2)-1);
4282 p2[sizeof(p2)-1] = '\0';
4283 l2 = strlen(p2);
4285 if(rflags && *rflags & GER_ALLPARTS)
4286 p3 = " (and atts)";
4287 else
4288 p3 = "";
4290 l3 = strlen(p3);
4292 snprintf(p4, sizeof(p4), " %s file%s%s",
4293 (flags & GE_IS_IMPORT) ? "from" : "to",
4294 is_absolute_path(filename) ? "" : " in ",
4295 is_absolute_path(filename) ? "" :
4296 (!dir[0] ? "current directory"
4297 : (dir[0] == '~' && !dir[1]) ? "home directory"
4298 : short_str(dir,dirb,sizeof(dirb),30,FrontDots)));
4299 p4[sizeof(p4)-1] = '\0';
4300 l4 = strlen(p4);
4302 snprintf(p5, sizeof(p5), "%s%s%s: ",
4303 *def ? " [" : "",
4304 *def ? short_str(def,fileb,sizeof(fileb),40,EndDots) : "",
4305 *def ? "]" : "");
4306 p5[sizeof(p5)-1] = '\0';
4307 l5 = strlen(p5);
4309 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4310 snprintf(p4, sizeof(p4), " %s file%s%s",
4311 (flags & GE_IS_IMPORT) ? "from" : "to",
4312 is_absolute_path(filename) ? "" : " in ",
4313 is_absolute_path(filename) ? "" :
4314 (!dir[0] ? "current dir"
4315 : (dir[0] == '~' && !dir[1]) ? "home dir"
4316 : short_str(dir,dirb,sizeof(dirb),10,FrontDots)));
4317 p4[sizeof(p4)-1] = '\0';
4318 l4 = strlen(p4);
4321 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4322 snprintf(p5, sizeof(p5), "%s%s%s: ",
4323 *def ? " [" : "",
4324 *def ? short_str(def,fileb,sizeof(fileb),
4325 MAX(15,l5-5-needed),EndDots) : "",
4326 *def ? "]" : "");
4327 p5[sizeof(p5)-1] = '\0';
4328 l5 = strlen(p5);
4331 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l2 > 0){
4334 * 14 is about the shortest we can make this, because there are
4335 * fixed length strings of length 14 coming in here.
4337 p = short_str(prompt_msg, p2, sizeof(p2), MAX(14,l2-needed), FrontDots);
4338 if(p != p2){
4339 strncpy(p2, p, sizeof(p2)-1);
4340 p2[sizeof(p2)-1] = '\0';
4343 l2 = strlen(p2);
4346 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4347 strncpy(p1, "Copy ", sizeof(p1)-1);
4348 p1[sizeof(p1)-1] = '\0';
4349 l1 = strlen(p1);
4352 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4353 snprintf(p5, sizeof(p5), "%s%s%s: ",
4354 *def ? " [" : "",
4355 *def ? short_str(def,fileb, sizeof(fileb),
4356 MAX(10,l5-5-needed),EndDots) : "",
4357 *def ? "]" : "");
4358 p5[sizeof(p5)-1] = '\0';
4359 l5 = strlen(p5);
4362 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l3 > 0){
4363 if(needed <= l3 - strlen(" (+ atts)"))
4364 p3 = " (+ atts)";
4365 else if(needed <= l3 - strlen(" (atts)"))
4366 p3 = " (atts)";
4367 else if(needed <= l3 - strlen(" (+)"))
4368 p3 = " (+)";
4369 else if(needed <= l3 - strlen("+"))
4370 p3 = "+";
4371 else
4372 p3 = "";
4374 l3 = strlen(p3);
4377 snprintf(prompt_buf, sizeof(prompt_buf), "%s%s%s%s%s", p1, p2, p3, p4, p5);
4378 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4380 if(ku >= 0){
4381 if(items_in_hist(*history) > 0){
4382 opts[ku].name = HISTORY_UP_KEYNAME;
4383 opts[ku].label = HISTORY_KEYLABEL;
4384 opts[ku+1].name = HISTORY_DOWN_KEYNAME;
4385 opts[ku+1].label = HISTORY_KEYLABEL;
4387 else{
4388 opts[ku].name = "";
4389 opts[ku].label = "";
4390 opts[ku+1].name = "";
4391 opts[ku+1].label = "";
4395 oeflags = OE_APPEND_CURRENT |
4396 ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0);
4397 r = optionally_enter(filename, qline, 0, len, prompt_buf,
4398 opts, NO_HELP, &oeflags);
4400 /*--- Help ----*/
4401 if(r == 3){
4403 * Helps may not be right if you add another caller or change
4404 * things. Check it out.
4406 if(flags & GE_IS_IMPORT)
4407 helper(h_ge_import, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE);
4408 else if(flags & GE_ALLPARTS)
4409 helper(h_ge_allparts, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4410 else
4411 helper(h_ge_export, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4413 ps->mangled_screen = 1;
4415 continue;
4417 else if(r == 10 || r == 11){ /* Browser or File Completion */
4418 if(filename[0]=='~'){
4419 if(filename[1] == C_FILESEP && filename[2]!='\0'){
4420 precolon[0] = '~';
4421 precolon[1] = '\0';
4422 for(i=0; filename[i+2] != '\0' && i+2 < len-1; i++)
4423 filename[i] = filename[i+2];
4424 filename[i] = '\0';
4425 strncpy(dir, precolon, sizeof(dir)-1);
4426 dir[sizeof(dir)-1] = '\0';
4428 else if(filename[1]=='\0' ||
4429 (filename[1] == C_FILESEP && filename[2] == '\0')){
4430 precolon[0] = '~';
4431 precolon[1] = '\0';
4432 filename[0] = '\0';
4433 strncpy(dir, precolon, sizeof(dir)-1);
4434 dir[sizeof(dir)-1] = '\0';
4437 else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){
4438 if(homedir){
4439 precolon[0] = '~';
4440 precolon[1] = '\0';
4441 strncpy(dir, precolon, sizeof(dir)-1);
4442 dir[sizeof(dir)-1] = '\0';
4444 else{
4445 precolon[0] = '\0';
4446 dir[0] = '\0';
4449 l = MAXPATH;
4450 dir2[0] = '\0';
4451 strncpy(tmp, filename, sizeof(tmp)-1);
4452 tmp[sizeof(tmp)-1] = '\0';
4453 if(*tmp && is_absolute_path(tmp))
4454 fnexpand(tmp, sizeof(tmp));
4455 if(strncmp(tmp,postcolon, strlen(postcolon)))
4456 postcolon[0] = '\0';
4458 if(*tmp && (fn = last_cmpnt(tmp))){
4459 l -= fn - tmp;
4460 strncpy(filename2, fn, sizeof(filename2)-1);
4461 filename2[sizeof(filename2)-1] = '\0';
4462 if(is_absolute_path(tmp)){
4463 strncpy(dir2, tmp, MIN(fn - tmp, sizeof(dir2)-1));
4464 dir2[MIN(fn - tmp, sizeof(dir2)-1)] = '\0';
4465 #ifdef _WINDOWS
4466 if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){
4467 dir2[2] = '\\';
4468 dir2[3] = '\0';
4470 #endif
4471 strncpy(postcolon, dir2, sizeof(postcolon)-1);
4472 postcolon[sizeof(postcolon)-1] = '\0';
4473 precolon[0] = '\0';
4475 else{
4476 char *p = NULL;
4478 * Just building the directory name in dir2,
4479 * full_filename is overloaded.
4481 snprintf(full_filename, len, "%.*s", MIN(fn-tmp,len-1), tmp);
4482 full_filename[len-1] = '\0';
4483 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4484 postcolon[sizeof(postcolon)-1] = '\0';
4485 build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH)
4486 : (dir[0] == '~' && !dir[1])
4487 ? ps->home_dir
4488 : dir,
4489 full_filename, sizeof(dir2));
4490 if(p)
4491 free(p);
4494 else{
4495 if(is_absolute_path(tmp)){
4496 strncpy(dir2, tmp, sizeof(dir2)-1);
4497 dir2[sizeof(dir2)-1] = '\0';
4498 #ifdef _WINDOWS
4499 if(dir2[2]=='\0' && dir2[1]==':'){
4500 dir2[2]='\\';
4501 dir2[3]='\0';
4502 strncpy(postcolon,dir2,sizeof(postcolon)-1);
4503 postcolon[sizeof(postcolon)-1] = '\0';
4505 #endif
4506 filename2[0] = '\0';
4507 precolon[0] = '\0';
4509 else{
4510 strncpy(filename2, tmp, sizeof(filename2)-1);
4511 filename2[sizeof(filename2)-1] = '\0';
4512 if(!dir[0])
4513 (void)getcwd(dir2, sizeof(dir2));
4514 else if(dir[0] == '~' && !dir[1]){
4515 strncpy(dir2, ps->home_dir, sizeof(dir2)-1);
4516 dir2[sizeof(dir2)-1] = '\0';
4518 else{
4519 strncpy(dir2, dir, sizeof(dir2)-1);
4520 dir2[sizeof(dir2)-1] = '\0';
4523 postcolon[0] = '\0';
4527 build_path(full_filename, dir2, filename2, len);
4528 if(!strcmp(full_filename, dir2))
4529 filename2[0] = '\0';
4530 if(full_filename[strlen(full_filename)-1] == C_FILESEP
4531 && isdir(full_filename,NULL,NULL)){
4532 if(strlen(full_filename) == 1)
4533 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4534 else if(filename2[0])
4535 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4536 postcolon[sizeof(postcolon)-1] = '\0';
4537 strncpy(dir2, full_filename, sizeof(dir2)-1);
4538 dir2[sizeof(dir2)-1] = '\0';
4539 filename2[0] = '\0';
4541 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4542 else if(full_filename[strlen(full_filename)-1] == C_FILESEP){
4543 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4544 postcolon[sizeof(postcolon)-1] = '\0';
4545 strncpy(dir2, full_filename, sizeof(dir2)-1);
4546 dir2[sizeof(dir2)-1] = '\0';
4547 filename2[0] = '\0';
4549 #endif
4550 if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1
4551 && strcmp(dir2+1, ":\\"))
4552 /* last condition to prevent stripping of '\\'
4553 in windows partition */
4554 dir2[strlen(dir2)-1] = '\0';
4556 if(r == 10){ /* File Browser */
4557 r = file_lister(lister_msg ? lister_msg : "EXPORT",
4558 dir2, sizeof(dir2), filename2, sizeof(filename2),
4559 TRUE,
4560 (flags & GE_IS_IMPORT) ? FB_READ : FB_SAVE);
4561 #ifdef _WINDOWS
4562 /* Windows has a special "feature" in which entering the file browser will
4563 change the working directory if the directory is changed at all (even
4564 clicking "Cancel" will change the working directory).
4566 if(F_ON(F_USE_CURRENT_DIR, ps))
4567 (void)getcwd(dir2,sizeof(dir2));
4568 #endif
4569 if(isdir(dir2,NULL,NULL)){
4570 strncpy(precolon, dir2, sizeof(precolon)-1);
4571 precolon[sizeof(precolon)-1] = '\0';
4573 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4574 postcolon[sizeof(postcolon)-1] = '\0';
4575 if(r == 1){
4576 build_path(full_filename, dir2, filename2, len);
4577 if(isdir(full_filename, NULL, NULL)){
4578 strncpy(dir, full_filename, sizeof(dir)-1);
4579 dir[sizeof(dir)-1] = '\0';
4580 filename[0] = '\0';
4582 else{
4583 fn = last_cmpnt(full_filename);
4584 strncpy(dir, full_filename,
4585 MIN(fn - full_filename, sizeof(dir)-1));
4586 dir[MIN(fn - full_filename, sizeof(dir)-1)] = '\0';
4587 if(fn - full_filename > 1)
4588 dir[fn - full_filename - 1] = '\0';
4591 if(!strcmp(dir, ps->home_dir)){
4592 dir[0] = '~';
4593 dir[1] = '\0';
4596 strncpy(filename, fn, len-1);
4597 filename[len-1] = '\0';
4600 else{ /* File Completion */
4601 if(!pico_fncomplete(dir2, filename2, sizeof(filename2)))
4602 Writechar(BELL, 0);
4603 strncat(postcolon, filename2,
4604 sizeof(postcolon)-1-strlen(postcolon));
4605 postcolon[sizeof(postcolon)-1] = '\0';
4607 was_abs_path = is_absolute_path(filename);
4609 if(!strcmp(dir, ps->home_dir)){
4610 dir[0] = '~';
4611 dir[1] = '\0';
4614 strncpy(filename, postcolon, len-1);
4615 filename[len-1] = '\0';
4616 strncpy(dir, precolon, sizeof(dir)-1);
4617 dir[sizeof(dir)-1] = '\0';
4619 if(filename[0] == '~' && !filename[1]){
4620 dir[0] = '~';
4621 dir[1] = '\0';
4622 filename[0] = '\0';
4625 continue;
4627 else if(r == 12){ /* Download, caller handles it */
4628 ret = r;
4629 goto done;
4631 else if(r == 13){ /* toggle AllParts bit */
4632 if(rflags){
4633 if(*rflags & GER_ALLPARTS){
4634 *rflags &= ~GER_ALLPARTS;
4635 opts[allparts].label = N_("AllParts");
4637 else{
4638 *rflags |= GER_ALLPARTS;
4639 /* opposite of All Parts, No All Parts */
4640 opts[allparts].label = N_("NoAllParts");
4644 continue;
4646 #if 0
4647 else if(r == 14){ /* List file names matching partial? */
4648 continue;
4650 #endif
4651 else if(r == 15){ /* toggle Binary bit */
4652 if(rflags){
4653 if(*rflags & GER_BINARY){
4654 *rflags &= ~GER_BINARY;
4655 opts[binary].label = N_("Binary");
4657 else{
4658 *rflags |= GER_BINARY;
4659 opts[binary].label = N_("No Binary");
4663 continue;
4665 else if(r == 1){ /* Cancel */
4666 ret = -1;
4667 goto done;
4669 else if(r == 4){
4670 continue;
4672 else if(r == 30 || r == 31){
4673 char *p = NULL;
4675 if(history){
4676 if(r == 30)
4677 p = get_prev_hist(*history, filename, 0, NULL);
4678 else
4679 p = get_next_hist(*history, filename, 0, NULL);
4682 if(p != NULL){
4683 fn = last_cmpnt(p);
4684 strncpy(dir, p, MIN(fn - p, sizeof(dir)-1));
4685 dir[MIN(fn - p, sizeof(dir)-1)] = '\0';
4686 if(fn - p > 1)
4687 dir[fn - p - 1] = '\0';
4689 if(!strcmp(dir, ps->home_dir)){
4690 dir[0] = '~';
4691 dir[1] = '\0';
4694 strncpy(filename, fn, len-1);
4695 filename[len-1] = '\0';
4697 else
4698 Writechar(BELL, 0);
4700 continue;
4702 else if(r != 0){
4703 Writechar(BELL, 0);
4704 continue;
4707 removing_leading_and_trailing_white_space(filename);
4709 if(!*filename){
4710 if(!*def){ /* Cancel */
4711 ret = -1;
4712 goto done;
4715 strncpy(filename, def, len-1);
4716 filename[len-1] = '\0';
4719 #if defined(DOS) || defined(OS2)
4720 if(is_absolute_path(filename)){
4721 fixpath(filename, len);
4723 #else
4724 if(filename[0] == '~'){
4725 if(fnexpand(filename, len) == NULL){
4726 char *p = strindex(filename, '/');
4727 if(p != NULL)
4728 *p = '\0';
4729 q_status_message1(SM_ORDER | SM_DING, 3, 3,
4730 _("Error expanding file name: \"%s\" unknown user"),
4731 filename);
4732 continue;
4735 #endif
4737 if(is_absolute_path(filename)){
4738 strncpy(full_filename, filename, len-1);
4739 full_filename[len-1] = '\0';
4741 else{
4742 if(!dir[0])
4743 build_path(full_filename, (char *)getcwd(dir,sizeof(dir)),
4744 filename, len);
4745 else if(dir[0] == '~' && !dir[1])
4746 build_path(full_filename, ps->home_dir, filename, len);
4747 else
4748 build_path(full_filename, dir, filename, len);
4751 if((ill = filter_filename(full_filename, &fatal,
4752 ps_global->restricted || ps_global->VAR_OPER_DIR)) != NULL){
4753 if(fatal){
4754 q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill);
4755 continue;
4757 else{
4758 /* BUG: we should beep when the key's pressed rather than bitch later */
4759 /* Warn and ask for confirmation. */
4760 snprintf(prompt_buf, sizeof(prompt_buf), "File name contains a '%s'. %s anyway",
4761 ill, (flags & GE_IS_EXPORT) ? "Export" : "Save");
4762 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4763 if(want_to(prompt_buf, 'n', 0, NO_HELP,
4764 ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y')
4765 continue;
4769 break; /* Must have got an OK file name */
4772 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){
4773 ret = -2;
4774 goto done;
4777 if(!can_access(full_filename, ACCESS_EXISTS)){
4778 int rbflags;
4779 static ESCKEY_S access_opts[] = {
4780 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4781 a file or append to the end of the file */
4782 {'o', 'o', "O", N_("Overwrite")},
4783 {'a', 'a', "A", N_("Append")},
4784 {-1, 0, NULL, NULL}};
4786 rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0);
4788 if(flags & GE_NO_APPEND){
4789 r = strlen(filename);
4790 snprintf(prompt_buf, sizeof(prompt_buf),
4791 /* TRANSLATORS: asking user whether to overwrite a file or not,
4792 File <filename> already exists. Overwrite it ? */
4793 _("File \"%s%s\" already exists. Overwrite it "),
4794 (r > 20) ? "..." : "",
4795 filename + ((r > 20) ? r - 20 : 0));
4796 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4797 if(want_to(prompt_buf, 'n', 'x', NO_HELP, rbflags) == 'y'){
4798 if(rflags)
4799 *rflags |= GER_OVER;
4801 if(our_unlink(full_filename) < 0){
4802 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4803 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4804 _("Cannot remove old %s: %s"),
4805 full_filename, error_description(errno));
4808 else{
4809 ret = -1;
4810 goto done;
4813 else if(!(flags & GE_IS_IMPORT)){
4814 r = strlen(filename);
4815 snprintf(prompt_buf, sizeof(prompt_buf),
4816 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4817 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4818 (r > 20) ? "..." : "",
4819 filename + ((r > 20) ? r - 20 : 0));
4820 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4821 switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global),
4822 access_opts, 'a', 'x', NO_HELP, rbflags)){
4823 case 'o' :
4824 if(rflags)
4825 *rflags |= GER_OVER;
4827 if(our_truncate(full_filename, (off_t)0) < 0)
4828 /* trouble truncating, but we'll give it a try anyway */
4829 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4830 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
4831 _("Warning: Cannot truncate old %s: %s"),
4832 full_filename, error_description(errno));
4833 break;
4835 case 'a' :
4836 if(rflags)
4837 *rflags |= GER_APPEND;
4839 break;
4841 case 'x' :
4842 default :
4843 ret = -1;
4844 goto done;
4849 done:
4850 if(history && ret == 0)
4851 save_hist(*history, full_filename, 0, NULL);
4853 if(opts && opts != optsarg)
4854 fs_give((void **) &opts);
4856 return(ret);
4860 /*----------------------------------------------------------------------
4861 parse the config'd upload/download command
4863 Args: cmd -- buffer to return command fit for shellin'
4864 prefix --
4865 cfg_str --
4866 fname -- file name to build into the command
4868 Returns: pointer to cmd_str buffer or NULL on real bad error
4870 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
4871 cfg_str is written to standard out right before a successful
4872 return of this function. The call immediately following this
4873 function darn well better be the shell exec...
4874 ----*/
4875 char *
4876 build_updown_cmd(char *cmd, size_t cmdlen, char *prefix, char *cfg_str, char *fname)
4878 char *p;
4879 int fname_found = 0;
4881 if(prefix && *prefix){
4882 /* loop thru replacing all occurances of _FILE_ */
4883 p = strncpy(cmd, prefix, cmdlen);
4884 cmd[cmdlen-1] = '\0';
4885 while((p = strstr(p, "_FILE_")))
4886 rplstr(p, cmdlen-(p-cmd), 6, fname);
4888 fputs(cmd, stdout);
4891 /* loop thru replacing all occurances of _FILE_ */
4892 p = strncpy(cmd, cfg_str, cmdlen);
4893 cmd[cmdlen-1] = '\0';
4894 while((p = strstr(p, "_FILE_"))){
4895 rplstr(p, cmdlen-(p-cmd), 6, fname);
4896 fname_found = 1;
4899 if(!fname_found)
4900 snprintf(cmd+strlen(cmd), cmdlen-strlen(cmd), " %s", fname);
4902 cmd[cmdlen-1] = '\0';
4904 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
4905 cmd ? cmd : "?"));
4906 return(cmd);
4910 /*----------------------------------------------------------------------
4911 Write a berzerk format message delimiter using the given putc function
4913 Args: e -- envelope of message to write
4914 pc -- function to use
4916 Returns: TRUE if we could write it, FALSE if there was a problem
4918 NOTE: follows delimiter with OS-dependent newline
4919 ----*/
4921 bezerk_delimiter(ENVELOPE *env, MESSAGECACHE *mc, gf_io_t pc, int leading_newline)
4923 MESSAGECACHE telt;
4924 time_t when;
4925 char *p;
4927 /* write "[\n]From mailbox[@host] " */
4928 if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1)
4929 && gf_puts("From ", pc)
4930 && gf_puts((env && env->from) ? env->from->mailbox
4931 : "the-concourse-on-high", pc)
4932 && gf_puts((env && env->from && env->from->host) ? "@" : "", pc)
4933 && gf_puts((env && env->from && env->from->host) ? env->from->host
4934 : "", pc)
4935 && (*pc)(' ')))
4936 return(0);
4938 if(mc && mc->valid)
4939 when = mail_longdate(mc);
4940 else if(env && env->date && env->date[0]
4941 && mail_parse_date(&telt,env->date))
4942 when = mail_longdate(&telt);
4943 else
4944 when = time(0);
4946 p = ctime(&when);
4948 while(p && *p && *p != '\n') /* write date */
4949 if(!(*pc)(*p++))
4950 return(0);
4952 if(!gf_puts(NEWLINE, pc)) /* write terminating newline */
4953 return(0);
4955 return(1);
4959 /*----------------------------------------------------------------------
4960 Execute command to jump to a given message number
4962 Args: qline -- Line to ask question on
4964 Result: returns true if the use selected a new message, false otherwise
4966 ----*/
4967 long
4968 jump_to(MSGNO_S *msgmap, int qline, UCS first_num, SCROLL_S *sparms, CmdWhere in_index)
4970 char jump_num_string[80], *j, prompt[70];
4971 HelpType help;
4972 int rc;
4973 static ESCKEY_S jump_to_key[] = { {0, 0, NULL, NULL},
4974 /* TRANSLATORS: go to First Message */
4975 {ctrl('Y'), 10, "^Y", N_("First Msg")},
4976 {ctrl('V'), 11, "^V", N_("Last Msg")},
4977 {-1, 0, NULL, NULL} };
4979 dprint((4, "\n - jump_to -\n"));
4981 #ifdef DEBUG
4982 if(sparms && sparms->jump_is_debug)
4983 return(get_level(qline, first_num, sparms));
4984 #endif
4986 if(!any_messages(msgmap, NULL, "to Jump to"))
4987 return(0L);
4989 if(first_num && first_num < 0x80 && isdigit((unsigned char) first_num)){
4990 jump_num_string[0] = first_num;
4991 jump_num_string[1] = '\0';
4993 else
4994 jump_num_string[0] = '\0';
4996 if(mn_total_cur(msgmap) > 1L){
4997 snprintf(prompt, sizeof(prompt), "Unselect %s msgs in favor of number to be entered",
4998 comatose(mn_total_cur(msgmap)));
4999 prompt[sizeof(prompt)-1] = '\0';
5000 if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n')
5001 return(0L);
5004 snprintf(prompt, sizeof(prompt), "%s number to jump to : ", in_index == ThrdIndx
5005 ? "Thread"
5006 : "Message");
5007 prompt[sizeof(prompt)-1] = '\0';
5009 help = NO_HELP;
5010 while(1){
5011 int flags = OE_APPEND_CURRENT;
5013 rc = optionally_enter(jump_num_string, qline, 0,
5014 sizeof(jump_num_string), prompt,
5015 jump_to_key, help, &flags);
5016 if(rc == 3){
5017 help = help == NO_HELP
5018 ? (in_index == ThrdIndx ? h_oe_jump_thd : h_oe_jump)
5019 : NO_HELP;
5020 continue;
5022 else if(rc == 10 || rc == 11){
5023 char warning[100];
5024 long closest;
5026 closest = closest_jump_target(rc == 10 ? 1L
5027 : ((in_index == ThrdIndx)
5028 ? msgmap->max_thrdno
5029 : mn_get_total(msgmap)),
5030 ps_global->mail_stream,
5031 msgmap, 0,
5032 in_index, warning, sizeof(warning));
5033 /* ignore warning */
5034 return(closest);
5038 * If we take out the *jump_num_string nonempty test in this if
5039 * then the closest_jump_target routine will offer a jump to the
5040 * last message. However, it is slow because you have to wait for
5041 * the status message and it is annoying for people who hit J command
5042 * by mistake and just want to hit return to do nothing, like has
5043 * always worked. So the test is there for now. Hubert 2002-08-19
5045 * Jumping to first/last message is now possible through ^Y/^V
5046 * commands above. jpf 2002-08-21
5047 * (and through "end" hubert 2006-07-07)
5049 if(rc == 0 && *jump_num_string != '\0'){
5050 removing_leading_and_trailing_white_space(jump_num_string);
5051 for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++)
5054 if(*j != '\0'){
5055 if(!strucmp("end", j))
5056 return((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap));
5058 q_status_message(SM_ORDER | SM_DING, 2, 2,
5059 _("Invalid number entered. Use only digits 0-9"));
5060 jump_num_string[0] = '\0';
5062 else{
5063 char warning[100];
5064 long closest, jump_num;
5066 if(*jump_num_string)
5067 jump_num = atol(jump_num_string);
5068 else
5069 jump_num = -1L;
5071 warning[0] = '\0';
5072 closest = closest_jump_target(jump_num, ps_global->mail_stream,
5073 msgmap,
5074 *jump_num_string ? 0 : 1,
5075 in_index, warning, sizeof(warning));
5076 if(warning[0])
5077 q_status_message(SM_ORDER | SM_DING, 2, 2, warning);
5079 if(closest == jump_num)
5080 return(jump_num);
5082 if(closest == 0L)
5083 jump_num_string[0] = '\0';
5084 else
5085 strncpy(jump_num_string, long2string(closest),
5086 sizeof(jump_num_string));
5089 continue;
5092 if(rc != 4)
5093 break;
5096 return(0L);
5101 * cmd_delete_action - handle msgno advance and such after single message deletion
5103 char *
5104 cmd_delete_action(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
5106 int opts;
5107 long msgno;
5108 char *rv = NULL;
5110 msgno = mn_get_cur(msgmap);
5111 advance_cur_after_delete(state, state->mail_stream, msgmap, in_index);
5113 if(IS_NEWS(state->mail_stream)
5114 || ((state->context_current->use & CNTXT_INCMNG)
5115 && context_isambig(state->cur_folder))){
5117 opts = (NSF_TRUST_FLAGS | NSF_SKIP_CHID);
5118 if(in_index == View)
5119 opts &= ~NSF_SKIP_CHID;
5121 (void)next_sorted_flagged(F_UNDEL|F_UNSEEN, state->mail_stream, msgno, &opts);
5122 if(!(opts & NSF_FLAG_MATCH)){
5123 char nextfolder[MAXPATH];
5125 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
5126 nextfolder[sizeof(nextfolder)-1] = '\0';
5127 rv = next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
5128 state->context_current, NULL, NULL)
5129 ? ". Press TAB for next folder."
5130 : ". No more folders to TAB to.";
5134 return(rv);
5139 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5141 char *
5142 cmd_delete_index(struct pine *state, MSGNO_S *msgmap)
5144 return(cmd_delete_action(state, msgmap,MsgIndx));
5148 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5150 char *
5151 cmd_delete_view(struct pine *state, MSGNO_S *msgmap)
5153 return(cmd_delete_action(state, msgmap, View));
5157 void
5158 advance_cur_after_delete(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, CmdWhere in_index)
5160 long new_msgno, msgno;
5161 int opts;
5163 new_msgno = msgno = mn_get_cur(msgmap);
5164 opts = NSF_TRUST_FLAGS;
5166 if(F_ON(F_DEL_SKIPS_DEL, state)){
5168 if(THREADING() && sp_viewing_a_thread(stream))
5169 opts |= NSF_SKIP_CHID;
5171 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5173 else{
5174 mn_inc_cur(stream, msgmap,
5175 (in_index == View && THREADING()
5176 && sp_viewing_a_thread(stream))
5177 ? MH_THISTHD
5178 : (in_index == View)
5179 ? MH_ANYTHD : MH_NONE);
5180 new_msgno = mn_get_cur(msgmap);
5181 if(new_msgno != msgno)
5182 opts |= NSF_FLAG_MATCH;
5186 * Viewing_a_thread is the complicated case because we want to ignore
5187 * other threads at first and then look in other threads if we have to.
5188 * By ignoring other threads we also ignore collapsed partial threads
5189 * in our own thread.
5191 if(THREADING() && sp_viewing_a_thread(stream) && !(opts & NSF_FLAG_MATCH)){
5192 long rawno, orig_thrdno;
5193 PINETHRD_S *thrd, *topthrd = NULL;
5195 rawno = mn_m2raw(msgmap, msgno);
5196 thrd = fetch_thread(stream, rawno);
5197 if(thrd && thrd->top)
5198 topthrd = fetch_thread(stream, thrd->top);
5200 orig_thrdno = topthrd ? topthrd->thrdno : -1L;
5202 opts = NSF_TRUST_FLAGS;
5203 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5206 * If we got a match, new_msgno may be a message in
5207 * a different thread from the one we are viewing, or it could be
5208 * in a collapsed part of this thread.
5210 if(opts & NSF_FLAG_MATCH){
5211 int ret;
5212 char pmt[128];
5214 topthrd = NULL;
5215 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
5216 if(thrd && thrd->top)
5217 topthrd = fetch_thread(stream, thrd->top);
5220 * If this match is in the same thread we're already in
5221 * then we're done, else we have to ask the user and maybe
5222 * switch threads.
5224 if(!(orig_thrdno > 0L && topthrd
5225 && topthrd->thrdno == orig_thrdno)){
5227 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
5228 if(in_index == View)
5229 snprintf(pmt, sizeof(pmt),
5230 "View message in thread number %.10s",
5231 topthrd ? comatose(topthrd->thrdno) : "?");
5232 else
5233 snprintf(pmt, sizeof(pmt), "View thread number %.10s",
5234 topthrd ? comatose(topthrd->thrdno) : "?");
5236 ret = want_to(pmt, 'y', 'x', NO_HELP, WT_NORM);
5238 else
5239 ret = 'y';
5241 if(ret == 'y'){
5242 unview_thread(state, stream, msgmap);
5243 mn_set_cur(msgmap, new_msgno);
5244 if(THRD_AUTO_VIEW()
5245 && (count_lflags_in_thread(stream, topthrd, msgmap,
5246 MN_NONE) == 1)
5247 && view_thread(state, stream, msgmap, 1)){
5248 if(current_index_state)
5249 msgmap->top_after_thrd = current_index_state->msg_at_top;
5251 state->view_skipped_index = 1;
5252 state->next_screen = mail_view_screen;
5254 else{
5255 view_thread(state, stream, msgmap, 1);
5256 if(current_index_state)
5257 msgmap->top_after_thrd = current_index_state->msg_at_top;
5259 state->next_screen = SCREEN_FUN_NULL;
5262 else
5263 new_msgno = msgno; /* stick with original */
5268 mn_set_cur(msgmap, new_msgno);
5269 if(in_index != View)
5270 adjust_cur_to_visible(stream, msgmap);
5274 #ifdef DEBUG
5275 long
5276 get_level(int qline, UCS first_num, SCROLL_S *sparms)
5278 char debug_num_string[80], *j, prompt[70];
5279 HelpType help;
5280 int rc;
5281 long debug_num;
5283 if(first_num && first_num < 0x80 && isdigit((unsigned char)first_num)){
5284 debug_num_string[0] = first_num;
5285 debug_num_string[1] = '\0';
5286 debug_num = atol(debug_num_string);
5287 *(int *)(sparms->proc.data.p) = debug_num;
5288 q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s",
5289 comatose(debug_num));
5290 return(1L);
5292 else
5293 debug_num_string[0] = '\0';
5295 snprintf(prompt, sizeof(prompt), "Show debug <= this level (0-%d) : ", MAX(debug, 9));
5296 prompt[sizeof(prompt)-1] = '\0';
5298 help = NO_HELP;
5299 while(1){
5300 int flags = OE_APPEND_CURRENT;
5302 rc = optionally_enter(debug_num_string, qline, 0,
5303 sizeof(debug_num_string), prompt,
5304 NULL, help, &flags);
5305 if(rc == 3){
5306 help = help == NO_HELP ? h_oe_debuglevel : NO_HELP;
5307 continue;
5310 if(rc == 0){
5311 removing_leading_and_trailing_white_space(debug_num_string);
5312 for(j=debug_num_string; isdigit((unsigned char)*j); j++)
5315 if(*j != '\0'){
5316 q_status_message(SM_ORDER | SM_DING, 2, 2,
5317 _("Invalid number entered. Use only digits 0-9"));
5318 debug_num_string[0] = '\0';
5320 else{
5321 debug_num = atol(debug_num_string);
5322 if(debug_num < 0)
5323 q_status_message(SM_ORDER | SM_DING, 2, 2,
5324 _("Number should be >= 0"));
5325 else if(debug_num > MAX(debug,9))
5326 q_status_message1(SM_ORDER | SM_DING, 2, 2,
5327 _("Maximum is %s"), comatose(MAX(debug,9)));
5328 else{
5329 *(int *)(sparms->proc.data.p) = debug_num;
5330 q_status_message1(SM_ORDER, 0, 3,
5331 "Show debug <= level %s",
5332 comatose(debug_num));
5333 return(1L);
5337 continue;
5340 if(rc != 4)
5341 break;
5344 return(0L);
5346 #endif /* DEBUG */
5350 * Returns the message number closest to target that isn't hidden.
5351 * Make warning at least 100 chars.
5352 * A return of 0 means there is no message to jump to.
5354 long
5355 closest_jump_target(long int target, MAILSTREAM *stream, MSGNO_S *msgmap, int no_target, CmdWhere in_index, char *warning, size_t warninglen)
5357 long i, start, closest = 0L;
5358 char buf[80];
5359 long maxnum;
5361 warning[0] = '\0';
5362 maxnum = (in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap);
5364 if(no_target){
5365 target = maxnum;
5366 start = 1L;
5367 snprintf(warning, warninglen, "No %s number entered, jump to end? ",
5368 (in_index == ThrdIndx) ? "thread" : "message");
5369 warning[warninglen-1] = '\0';
5371 else if(target < 1L)
5372 start = 1L - target;
5373 else if(target > maxnum)
5374 start = target - maxnum;
5375 else
5376 start = 1L;
5378 if(target > 0L && target <= maxnum)
5379 if(in_index == ThrdIndx
5380 || !msgline_hidden(stream, msgmap, target, 0))
5381 return(target);
5383 for(i = start; target+i <= maxnum || target-i > 0L; i++){
5385 if(target+i > 0L && target+i <= maxnum &&
5386 (in_index == ThrdIndx
5387 || !msgline_hidden(stream, msgmap, target+i, 0))){
5388 closest = target+i;
5389 break;
5392 if(target-i > 0L && target-i <= maxnum &&
5393 (in_index == ThrdIndx
5394 || !msgline_hidden(stream, msgmap, target-i, 0))){
5395 closest = target-i;
5396 break;
5400 strncpy(buf, long2string(closest), sizeof(buf));
5401 buf[sizeof(buf)-1] = '\0';
5403 if(closest == 0L)
5404 strncpy(warning, "Nothing to jump to", warninglen);
5405 else if(target < 1L)
5406 snprintf(warning, warninglen, "%s number (%s) must be at least %s",
5407 (in_index == ThrdIndx) ? "Thread" : "Message",
5408 long2string(target), buf);
5409 else if(target > maxnum)
5410 snprintf(warning, warninglen, "%s number (%s) may be no more than %s",
5411 (in_index == ThrdIndx) ? "Thread" : "Message",
5412 long2string(target), buf);
5413 else if(!no_target)
5414 snprintf(warning, warninglen,
5415 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5416 long2string(target), buf);
5418 warning[warninglen-1] = '\0';
5420 return(closest);
5424 /*----------------------------------------------------------------------
5425 Prompt for folder name to open, expand the name and return it
5427 Args: qline -- Screen line to prompt on
5428 allow_list -- if 1, allow ^T to bring up collection lister
5430 Result: returns the folder name or NULL
5431 pine structure mangled_footer flag is set
5432 may call the collection lister in which case mangled screen will be set
5434 This prompts the user for the folder to open, possibly calling up
5435 the collection lister if the user types ^T.
5436 ----------------------------------------------------------------------*/
5437 char *
5438 broach_folder(int qline, int allow_list, int *notrealinbox, CONTEXT_S **context)
5440 HelpType help;
5441 static char newfolder[MAILTMPLEN];
5442 char expanded[MAXPATH+1],
5443 prompt[MAX_SCREEN_COLS+1],
5444 *last_folder, *p;
5445 unsigned char *f1, *f2, *f3;
5446 static HISTORY_S *history = NULL;
5447 CONTEXT_S *tc, *tc2;
5448 ESCKEY_S ekey[9];
5449 int rc, r, ku = -1, n, flags, last_rc = 0, inbox, done = 0;
5452 * the idea is to provide a clue for the context the file name
5453 * will be saved in (if a non-imap names is typed), and to
5454 * only show the previous if it was also in the same context
5456 help = NO_HELP;
5457 *expanded = '\0';
5458 *newfolder = '\0';
5459 last_folder = NULL;
5460 if(notrealinbox)
5461 (*notrealinbox) = 1;
5463 init_hist(&history, HISTSIZE);
5465 tc = broach_get_folder(context ? *context : NULL, &inbox, NULL);
5467 /* set up extra command option keys */
5468 rc = 0;
5469 ekey[rc].ch = (allow_list) ? ctrl('T') : 0 ;
5470 ekey[rc].rval = (allow_list) ? 2 : 0;
5471 ekey[rc].name = (allow_list) ? "^T" : "";
5472 ekey[rc++].label = (allow_list) ? N_("ToFldrs") : "";
5474 if(ps_global->context_list->next){
5475 ekey[rc].ch = ctrl('P');
5476 ekey[rc].rval = 10;
5477 ekey[rc].name = "^P";
5478 ekey[rc++].label = N_("Prev Collection");
5480 ekey[rc].ch = ctrl('N');
5481 ekey[rc].rval = 11;
5482 ekey[rc].name = "^N";
5483 ekey[rc++].label = N_("Next Collection");
5486 ekey[rc].ch = ctrl('W');
5487 ekey[rc].rval = 17;
5488 ekey[rc].name = "^W";
5489 ekey[rc++].label = N_("INBOX");
5491 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
5492 ekey[rc].ch = TAB;
5493 ekey[rc].rval = 12;
5494 ekey[rc].name = "TAB";
5495 ekey[rc++].label = N_("Complete");
5498 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
5499 ekey[rc].ch = ctrl('X');
5500 ekey[rc].rval = 14;
5501 ekey[rc].name = "^X";
5502 ekey[rc++].label = N_("ListMatches");
5505 if(ps_global->context_list->next && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5506 ekey[rc].ch = KEY_UP;
5507 ekey[rc].rval = 10;
5508 ekey[rc].name = "";
5509 ekey[rc++].label = "";
5511 ekey[rc].ch = KEY_DOWN;
5512 ekey[rc].rval = 11;
5513 ekey[rc].name = "";
5514 ekey[rc++].label = "";
5516 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5517 ekey[rc].ch = KEY_UP;
5518 ekey[rc].rval = 30;
5519 ekey[rc].name = "";
5520 ku = rc;
5521 ekey[rc++].label = "";
5523 ekey[rc].ch = KEY_DOWN;
5524 ekey[rc].rval = 31;
5525 ekey[rc].name = "";
5526 ekey[rc++].label = "";
5529 ekey[rc].ch = -1;
5531 while(!done) {
5533 * Figure out next default value for this context. The idea
5534 * is that in each context the last folder opened is cached.
5535 * It's up to pick it out and display it. This is fine
5536 * and dandy if we've currently got the inbox open, BUT
5537 * if not, make the inbox the default the first time thru.
5539 if(!inbox){
5540 last_folder = ps_global->inbox_name;
5541 inbox = 1; /* pretend we're in inbox from here on out */
5543 else
5544 last_folder = (ps_global->last_unambig_folder[0])
5545 ? ps_global->last_unambig_folder
5546 : ((tc->last_folder[0]) ? tc->last_folder : NULL);
5548 if(last_folder){
5549 unsigned char *fname = folder_name_decoded((unsigned char *)last_folder);
5550 snprintf(expanded, sizeof(expanded), " [%.*s]", sizeof(expanded)-5,
5551 fname ? (char *) fname : last_folder);
5552 if(fname) fs_give((void **)&fname);
5554 else
5555 *expanded = '\0';
5557 expanded[sizeof(expanded)-1] = '\0';
5559 /* only show collection number if more than one available */
5560 if(ps_global->context_list->next)
5561 snprintf(prompt, sizeof(prompt), "GOTO %s in <%s> %.*s%s: ",
5562 NEWS_TEST(tc) ? "news group" : "folder",
5563 tc->nickname, sizeof(prompt)-50, expanded,
5564 *expanded ? " " : "");
5565 else
5566 snprintf(prompt, sizeof(prompt), "GOTO folder %.*s%s: ", sizeof(prompt)-20, expanded,
5567 *expanded ? " " : "");
5569 prompt[sizeof(prompt)-1] = '\0';
5571 if(utf8_width(prompt) > MAXPROMPT){
5572 if(ps_global->context_list->next)
5573 snprintf(prompt, sizeof(prompt), "GOTO <%s> %.*s%s: ",
5574 tc->nickname, sizeof(prompt)-50, expanded,
5575 *expanded ? " " : "");
5576 else
5577 snprintf(prompt, sizeof(prompt), "GOTO %.*s%s: ", sizeof(prompt)-20, expanded,
5578 *expanded ? " " : "");
5580 prompt[sizeof(prompt)-1] = '\0';
5582 if(utf8_width(prompt) > MAXPROMPT){
5583 if(ps_global->context_list->next)
5584 snprintf(prompt, sizeof(prompt), "<%s> %.*s%s: ",
5585 tc->nickname, sizeof(prompt)-50, expanded,
5586 *expanded ? " " : "");
5587 else
5588 snprintf(prompt, sizeof(prompt), "%.*s%s: ", sizeof(prompt)-20, expanded,
5589 *expanded ? " " : "");
5591 prompt[sizeof(prompt)-1] = '\0';
5595 if(ku >= 0){
5596 if(items_in_hist(history) > 1){
5597 ekey[ku].name = HISTORY_UP_KEYNAME;
5598 ekey[ku].label = HISTORY_KEYLABEL;
5599 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
5600 ekey[ku+1].label = HISTORY_KEYLABEL;
5602 else{
5603 ekey[ku].name = "";
5604 ekey[ku].label = "";
5605 ekey[ku+1].name = "";
5606 ekey[ku+1].label = "";
5610 /* is there any other way to do this? The point is that we
5611 * are trying to hide mutf7 from the user, and use the utf8
5612 * equivalent. So we create a variable f to take place of
5613 * newfolder, including content and size. f2 is copy of f1
5614 * that has to freed. Sigh!
5616 f3 = (unsigned char *) cpystr(newfolder);
5617 f1 = fs_get(sizeof(newfolder));
5618 f2 = folder_name_decoded(f3);
5619 if(f3) fs_give((void **)&f3);
5620 strncpy((char *)f1, (char *)f2, sizeof(newfolder));
5621 f1[sizeof(newfolder)-1] = '\0';
5622 if(f2) fs_give((void **)&f2);
5624 flags = OE_APPEND_CURRENT;
5625 rc = optionally_enter(f1, qline, 0, sizeof(newfolder),
5626 (char *) prompt, ekey, help, &flags);
5628 f2 = folder_name_encoded(f1);
5629 strncpy(newfolder, (char *)f2, sizeof(newfolder));
5630 if(f1) fs_give((void **)&f1);
5631 if(f2) fs_give((void **)&f2);
5633 ps_global->mangled_footer = 1;
5635 switch(rc){
5636 case -1 : /* o_e says error! */
5637 q_status_message(SM_ORDER | SM_DING, 3, 3,
5638 _("Error reading folder name"));
5639 return(NULL);
5641 case 0 : /* o_e says normal entry */
5642 removing_trailing_white_space(newfolder);
5643 removing_leading_white_space(newfolder);
5645 if(*newfolder){
5646 char *name, *fullname = NULL;
5647 int exists, breakout = 0;
5649 save_hist(history, newfolder, 0, tc);
5651 if(!(name = folder_is_nick(newfolder, FOLDERS(tc),
5652 FN_WHOLE_NAME)))
5653 name = newfolder;
5655 if(update_folder_spec(expanded, sizeof(expanded), name)){
5656 strncpy(name = newfolder, expanded, sizeof(newfolder));
5657 newfolder[sizeof(newfolder)-1] = '\0';
5660 exists = folder_name_exists(tc, name, &fullname);
5662 if(fullname){
5663 strncpy(name = newfolder, fullname, sizeof(newfolder));
5664 newfolder[sizeof(newfolder)-1] = '\0';
5665 fs_give((void **) &fullname);
5666 breakout = TRUE;
5670 * if we know the things a folder, open it.
5671 * else if we know its a directory, visit it.
5672 * else we're not sure (it either doesn't really
5673 * exist or its unLISTable) so try opening it anyway
5675 if(exists & FEX_ISFILE){
5676 done++;
5677 break;
5679 else if((exists & FEX_ISDIR)){
5680 if(breakout){
5681 CONTEXT_S *fake_context;
5682 char tmp[MAILTMPLEN];
5683 size_t l;
5685 strncpy(tmp, name, sizeof(tmp));
5686 tmp[sizeof(tmp)-2-1] = '\0';
5687 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
5688 if(l < sizeof(tmp)){
5689 tmp[l] = tc->dir->delim;
5690 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
5693 else
5694 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
5696 tmp[sizeof(tmp)-1] = '\0';
5698 fake_context = new_context(tmp, 0);
5699 newfolder[0] = '\0';
5700 done = display_folder_list(&fake_context, newfolder,
5701 1, folders_for_goto);
5702 free_context(&fake_context);
5703 break;
5705 else if(!(tc->use & CNTXT_INCMNG)){
5706 done = display_folder_list(&tc, newfolder,
5707 1, folders_for_goto);
5708 break;
5711 else if((exists & FEX_ERROR)){
5712 q_status_message1(SM_ORDER, 0, 3,
5713 _("Problem accessing folder \"%s\""),
5714 newfolder);
5715 return(NULL);
5717 else{
5718 done++;
5719 break;
5722 if(exists == FEX_ERROR)
5723 q_status_message1(SM_ORDER, 0, 3,
5724 _("Problem accessing folder \"%s\""),
5725 newfolder);
5726 else if(tc->use & CNTXT_INCMNG)
5727 q_status_message1(SM_ORDER, 0, 3,
5728 _("Can't find Incoming Folder: %s"),
5729 newfolder);
5730 else if(context_isambig(newfolder))
5731 q_status_message2(SM_ORDER, 0, 3,
5732 _("Can't find folder \"%s\" in %s"),
5733 newfolder, (void *) tc->nickname);
5734 else
5735 q_status_message1(SM_ORDER, 0, 3,
5736 _("Can't find folder \"%s\""),
5737 newfolder);
5739 return(NULL);
5741 else if(last_folder){
5742 if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX
5743 && !strucmp(last_folder, ps_global->inbox_name)
5744 && tc == ((ps_global->context_list->use & CNTXT_INCMNG)
5745 ? ps_global->context_list->next : ps_global->context_list)){
5746 if(notrealinbox)
5747 (*notrealinbox) = 0;
5749 tc = ps_global->context_list;
5752 strncpy(newfolder, last_folder, sizeof(newfolder));
5753 newfolder[sizeof(newfolder)-1] = '\0';
5754 save_hist(history, newfolder, 0, tc);
5755 done++;
5756 break;
5758 /* fall thru like they cancelled */
5760 case 1 : /* o_e says user cancel */
5761 cmd_cancelled("Open folder");
5762 return(NULL);
5764 case 2 : /* o_e says user wants list */
5765 r = display_folder_list(&tc, newfolder, 0, folders_for_goto);
5766 if(r)
5767 done++;
5769 break;
5771 case 3 : /* o_e says user wants help */
5772 help = help == NO_HELP ? h_oe_broach : NO_HELP;
5773 break;
5775 case 4 : /* redraw */
5776 break;
5778 case 10 : /* Previous collection */
5779 tc2 = ps_global->context_list;
5780 while(tc2->next && tc2->next != tc)
5781 tc2 = tc2->next;
5783 tc = tc2;
5784 break;
5786 case 11 : /* Next collection */
5787 tc = (tc->next) ? tc->next : ps_global->context_list;
5788 break;
5790 case 12 : /* file name completion */
5791 if(!folder_complete(tc, newfolder, sizeof(newfolder), &n)){
5792 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
5793 r = display_folder_list(&tc, newfolder, 1,folders_for_goto);
5794 if(r)
5795 done++; /* bingo! */
5796 else
5797 rc = 0; /* burn last_rc */
5799 else
5800 Writechar(BELL, 0);
5803 break;
5805 case 14 : /* file name completion */
5806 r = display_folder_list(&tc, newfolder, 2, folders_for_goto);
5807 if(r)
5808 done++; /* bingo! */
5809 else
5810 rc = 0; /* burn last_rc */
5812 break;
5814 case 17 : /* GoTo INBOX */
5815 done++;
5816 strncpy(newfolder, ps_global->inbox_name, sizeof(newfolder)-1);
5817 newfolder[sizeof(newfolder)-1] = '\0';
5818 if(notrealinbox)
5819 (*notrealinbox) = 0;
5821 tc = ps_global->context_list;
5822 save_hist(history, newfolder, 0, tc);
5824 break;
5826 case 30 :
5827 if((p = get_prev_hist(history, newfolder, 0, tc)) != NULL){
5828 strncpy(newfolder, p, sizeof(newfolder));
5829 newfolder[sizeof(newfolder)-1] = '\0';
5830 if(history->hist[history->curindex])
5831 tc = history->hist[history->curindex]->cntxt;
5833 else
5834 Writechar(BELL, 0);
5836 break;
5838 case 31 :
5839 if((p = get_next_hist(history, newfolder, 0, tc)) != NULL){
5840 strncpy(newfolder, p, sizeof(newfolder));
5841 newfolder[sizeof(newfolder)-1] = '\0';
5842 if(history->hist[history->curindex])
5843 tc = history->hist[history->curindex]->cntxt;
5845 else
5846 Writechar(BELL, 0);
5848 break;
5850 default :
5851 alpine_panic("Unhandled case");
5852 break;
5855 last_rc = rc;
5858 dprint((2, "broach folder, name entered \"%s\"\n",
5859 newfolder ? newfolder : "?"));
5861 /*-- Just check that we can expand this. It gets done for real later --*/
5862 strncpy(expanded, newfolder, sizeof(expanded));
5863 expanded[sizeof(expanded)-1] = '\0';
5865 if(!expand_foldername(expanded, sizeof(expanded))) {
5866 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
5867 expanded ? expanded : "?"));
5868 return(NULL);
5871 *context = tc;
5872 return(newfolder);
5876 /*----------------------------------------------------------------------
5877 Check to see if user wants to reopen dead stream.
5879 Args: ps --
5880 reopenp --
5882 Result: 1 if the folder was successfully updatedn
5883 0 if not necessary
5885 ----*/
5887 ask_mailbox_reopen(struct pine *ps, int *reopenp)
5889 if(((ps->mail_stream->dtb
5890 && ((ps->mail_stream->dtb->flags & DR_NONEWMAIL)
5891 || (ps->mail_stream->rdonly
5892 && ps->mail_stream->dtb->flags & DR_NONEWMAILRONLY)))
5893 && (ps->reopen_rule == REOPEN_ASK_ASK_Y
5894 || ps->reopen_rule == REOPEN_ASK_ASK_N
5895 || ps->reopen_rule == REOPEN_ASK_NO_Y
5896 || ps->reopen_rule == REOPEN_ASK_NO_N))
5897 || ((ps->mail_stream->dtb
5898 && ps->mail_stream->rdonly
5899 && !(ps->mail_stream->dtb->flags & DR_LOCAL))
5900 && (ps->reopen_rule == REOPEN_YES_ASK_Y
5901 || ps->reopen_rule == REOPEN_YES_ASK_N
5902 || ps->reopen_rule == REOPEN_ASK_ASK_Y
5903 || ps->reopen_rule == REOPEN_ASK_ASK_N))){
5904 int deefault;
5906 switch(ps->reopen_rule){
5907 case REOPEN_YES_ASK_Y:
5908 case REOPEN_ASK_ASK_Y:
5909 case REOPEN_ASK_NO_Y:
5910 deefault = 'y';
5911 break;
5913 default:
5914 deefault = 'n';
5915 break;
5918 switch(want_to("Re-open folder to check for new messages", deefault,
5919 'x', h_reopen_folder, WT_NORM)){
5920 case 'y':
5921 (*reopenp)++;
5922 break;
5924 case 'x':
5925 return(-1);
5929 return(0);
5934 /*----------------------------------------------------------------------
5935 Check to see if user input is in form of old c-client mailbox speck
5937 Args: old --
5938 new --
5940 Result: 1 if the folder was successfully updatedn
5941 0 if not necessary
5943 ----*/
5945 update_folder_spec(char *new, size_t newlen, char *old)
5947 char *p, *orignew;
5948 int nntp = 0;
5950 orignew = new;
5951 if(*(p = old) == '*') /* old form? */
5952 old++;
5954 if(*old == '{') /* copy host spec */
5956 switch(*new = *old++){
5957 case '\0' :
5958 return(FALSE);
5960 case '/' :
5961 if(!struncmp(old, "nntp", 4))
5962 nntp++;
5964 break;
5966 default :
5967 break;
5969 while(*new++ != '}' && (new-orignew) < newlen-1);
5971 if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){
5973 * OK, some heuristics here. If it looks like a newsgroup
5974 * then we plunk it into the #news namespace else we
5975 * assume that they're trying to get at a #public folder...
5977 for(p = old;
5978 *p && (isalnum((unsigned char) *p) || strindex(".-", *p));
5979 p++)
5982 sstrncpy(&new, (*p && !nntp) ? "#public/" : "#news.", newlen-(new-orignew));
5983 strncpy(new, old, newlen-(new-orignew));
5984 return(TRUE);
5987 orignew[newlen-1] = '\0';
5989 return(FALSE);
5993 /*----------------------------------------------------------------------
5994 Open the requested folder in the requested context
5996 Args: state -- usual pine state struct
5997 newfolder -- folder to open
5998 new_context -- folder context might live in
5999 stream -- candidate for recycling
6001 Result: New folder open or not (if error), and we're set to
6002 enter the index screen.
6003 ----*/
6004 void
6005 visit_folder(struct pine *state, char *newfolder, CONTEXT_S *new_context,
6006 MAILSTREAM *stream, long unsigned int flags)
6008 dprint((9, "visit_folder(%s, %s)\n",
6009 newfolder ? newfolder : "?",
6010 (new_context && new_context->context)
6011 ? new_context->context : "(NULL)"));
6013 if(ps_global && ps_global->ttyo){
6014 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
6015 ps_global->mangled_footer = 1;
6018 if(do_broach_folder(newfolder, new_context, stream ? &stream : NULL,
6019 flags) >= 0
6020 || !sp_flagged(state->mail_stream, SP_LOCKED))
6021 state->next_screen = mail_index_screen;
6022 else
6023 state->next_screen = folder_screen;
6027 /*----------------------------------------------------------------------
6028 Move read messages from folder if listed in archive
6030 Args:
6032 ----*/
6034 read_msg_prompt(long int n, char *f)
6036 char buf[MAX_SCREEN_COLS+1];
6038 snprintf(buf, sizeof(buf), "Save the %ld read message%s in \"%s\"", n, plural(n), f);
6039 buf[sizeof(buf)-1] = '\0';
6040 return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y');
6044 /*----------------------------------------------------------------------
6045 Print current message[s] or folder index
6047 Args: state -- pointer to struct holding a bunch of pine state
6048 msgmap -- table mapping msg nums to c-client sequence nums
6049 aopt -- aggregate options
6050 in_index -- boolean indicating we're called from Index Screen
6052 Filters the original header and sends stuff to printer
6053 ---*/
6055 cmd_print(struct pine *state, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
6057 char prompt[250];
6058 long i, msgs, rawno;
6059 int next = 0, do_index = 0, rv = 0;
6060 ENVELOPE *e;
6061 BODY *b;
6062 MESSAGECACHE *mc;
6064 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
6065 return rv;
6067 msgs = mn_total_cur(msgmap);
6069 if((in_index != View) && F_ON(F_PRINT_INDEX, state)){
6070 char m[10];
6071 int ans;
6072 static ESCKEY_S prt_opts[] = {
6073 {'i', 'i', "I", N_("Index")},
6074 {'m', 'm', "M", NULL},
6075 {-1, 0, NULL, NULL}};
6077 if(in_index == ThrdIndx){
6078 /* TRANSLATORS: This is a question, Print Index ? */
6079 if(want_to(_("Print Index"), 'y', 'x', NO_HELP, WT_NORM) == 'y')
6080 ans = 'i';
6081 else
6082 ans = 'x';
6084 else{
6085 snprintf(m, sizeof(m), "Message%s", (msgs>1L) ? "s" : "");
6086 m[sizeof(m)-1] = '\0';
6087 prt_opts[1].label = m;
6088 snprintf(prompt, sizeof(prompt), "Print %sFolder Index or %s %s? ",
6089 (aopt & MCMD_AGG_2) ? "thread " : MCMD_ISAGG(aopt) ? "selected " : "",
6090 (aopt & MCMD_AGG_2) ? "thread" : MCMD_ISAGG(aopt) ? "selected" : "current", m);
6091 prompt[sizeof(prompt)-1] = '\0';
6093 ans = radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x',
6094 NO_HELP, RB_NORM|RB_SEQ_SENSITIVE);
6097 switch(ans){
6098 case 'x' :
6099 cmd_cancelled("Print");
6100 if(MCMD_ISAGG(aopt))
6101 restore_selected(msgmap);
6103 return rv;
6105 case 'i':
6106 do_index = 1;
6107 break;
6109 default :
6110 case 'm':
6111 break;
6115 if(do_index)
6116 snprintf(prompt, sizeof(prompt), "%sFolder Index",
6117 (aopt & MCMD_AGG_2) ? "Thread " : MCMD_ISAGG(aopt) ? "Selected " : "");
6118 else if(msgs > 1L)
6119 snprintf(prompt, sizeof(prompt), "%s messages", long2string(msgs));
6120 else
6121 snprintf(prompt, sizeof(prompt), "Message %s", long2string(mn_get_cur(msgmap)));
6123 prompt[sizeof(prompt)-1] = '\0';
6125 if(open_printer(prompt) < 0){
6126 if(MCMD_ISAGG(aopt))
6127 restore_selected(msgmap);
6129 return rv;
6132 if(do_index){
6133 TITLE_S *tc;
6135 tc = format_titlebar();
6137 /* Print titlebar... */
6138 print_text1("%s\n\n", tc ? tc->titlebar_line : "");
6139 /* then all the index members... */
6140 if(!print_index(state, msgmap, MCMD_ISAGG(aopt)))
6141 q_status_message(SM_ORDER | SM_DING, 3, 3,
6142 _("Error printing folder index"));
6143 else
6144 rv++;
6146 else{
6147 rv++;
6148 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){
6149 if(next && F_ON(F_AGG_PRINT_FF, state))
6150 if(!print_char(FORMFEED)){
6151 rv = 0;
6152 break;
6155 if(!(state->mail_stream
6156 && (rawno = mn_m2raw(msgmap, i)) > 0L
6157 && rawno <= state->mail_stream->nmsgs
6158 && (mc = mail_elt(state->mail_stream, rawno))
6159 && mc->valid))
6160 mc = NULL;
6162 if(!(e=pine_mail_fetchstructure(state->mail_stream,
6163 mn_m2raw(msgmap,i),
6164 &b))
6165 || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global)
6166 && !bezerk_delimiter(e, mc, print_char, next))
6167 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
6168 e, b, NULL, FM_NEW_MESS | FM_NOINDENT,
6169 print_char)){
6170 q_status_message(SM_ORDER | SM_DING, 3, 3,
6171 _("Error printing message"));
6172 rv = 0;
6173 break;
6178 close_printer();
6180 if(MCMD_ISAGG(aopt))
6181 restore_selected(msgmap);
6183 return rv;
6187 /*----------------------------------------------------------------------
6188 Pipe message text
6190 Args: state -- various pine state bits
6191 msgmap -- Message number mapping table
6192 aopt -- option flags
6194 Filters the original header and sends stuff to specified command
6195 ---*/
6197 cmd_pipe(struct pine *state, MSGNO_S *msgmap, int aopt)
6199 ENVELOPE *e;
6200 MESSAGECACHE *mc;
6201 BODY *b;
6202 PIPE_S *syspipe;
6203 char *resultfilename = NULL, prompt[80], *p;
6204 int done = 0, rv = 0;
6205 gf_io_t pc;
6206 int fourlabel = -1, j = 0, next = 0, ku;
6207 int pipe_rv; /* rv of proc to separate from close_system_pipe rv */
6208 long i, rawno;
6209 unsigned flagsforhist = 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6210 static HISTORY_S *history = NULL;
6211 int capture = 1, raw = 0, delimit = 0, newpipe = 0;
6212 char pipe_command[MAXPATH];
6213 ESCKEY_S pipe_opt[8];
6215 if(ps_global->restricted){
6216 q_status_message(SM_ORDER | SM_DING, 0, 4,
6217 "Alpine demo can't pipe messages");
6218 return rv;
6220 else if(!any_messages(msgmap, NULL, "to Pipe"))
6221 return rv;
6223 pipe_command[0] = '\0';
6224 init_hist(&history, HISTSIZE);
6225 flagsforhist = (raw ? 0x8 : 0) +
6226 (delimit ? 0x4 : 0) +
6227 (newpipe ? 0x2 : 0) +
6228 (capture ? 0x1 : 0);
6229 if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){
6230 strncpy(pipe_command, p, sizeof(pipe_command));
6231 pipe_command[sizeof(pipe_command)-1] = '\0';
6232 if(history->hist[history->curindex]){
6233 flagsforhist = history->hist[history->curindex]->flags;
6234 raw = (flagsforhist & 0x8) ? 1 : 0;
6235 delimit = (flagsforhist & 0x4) ? 1 : 0;
6236 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6237 capture = (flagsforhist & 0x1) ? 1 : 0;
6241 pipe_opt[j].ch = 0;
6242 pipe_opt[j].rval = 0;
6243 pipe_opt[j].name = "";
6244 pipe_opt[j++].label = "";
6246 pipe_opt[j].ch = ctrl('W');
6247 pipe_opt[j].rval = 10;
6248 pipe_opt[j].name = "^W";
6249 pipe_opt[j++].label = NULL;
6251 pipe_opt[j].ch = ctrl('Y');
6252 pipe_opt[j].rval = 11;
6253 pipe_opt[j].name = "^Y";
6254 pipe_opt[j++].label = NULL;
6256 pipe_opt[j].ch = ctrl('R');
6257 pipe_opt[j].rval = 12;
6258 pipe_opt[j].name = "^R";
6259 pipe_opt[j++].label = NULL;
6261 if(MCMD_ISAGG(aopt)){
6262 if(!pseudo_selected(state->mail_stream, msgmap))
6263 return rv;
6264 else{
6265 fourlabel = j;
6266 pipe_opt[j].ch = ctrl('T');
6267 pipe_opt[j].rval = 13;
6268 pipe_opt[j].name = "^T";
6269 pipe_opt[j++].label = NULL;
6273 pipe_opt[j].ch = KEY_UP;
6274 pipe_opt[j].rval = 30;
6275 pipe_opt[j].name = "";
6276 ku = j;
6277 pipe_opt[j++].label = "";
6279 pipe_opt[j].ch = KEY_DOWN;
6280 pipe_opt[j].rval = 31;
6281 pipe_opt[j].name = "";
6282 pipe_opt[j++].label = "";
6284 pipe_opt[j].ch = -1;
6286 while (!done) {
6287 int flags;
6289 snprintf(prompt, sizeof(prompt), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6290 raw ? "RAW " : "",
6291 MCMD_ISAGG(aopt) ? "s" : " ",
6292 MCMD_ISAGG(aopt) ? "" : comatose(mn_get_cur(msgmap)),
6293 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? "(" : "",
6294 capture ? "" : "uncaptured",
6295 (!capture && delimit) ? "," : "",
6296 delimit ? "delimited" : "",
6297 ((!capture || delimit) && newpipe && MCMD_ISAGG(aopt)) ? "," : "",
6298 (newpipe && MCMD_ISAGG(aopt)) ? "new pipe" : "",
6299 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? ") " : "");
6300 prompt[sizeof(prompt)-1] = '\0';
6301 pipe_opt[1].label = raw ? N_("Shown Text") : N_("Raw Text");
6302 pipe_opt[2].label = capture ? N_("Free Output") : N_("Capture Output");
6303 pipe_opt[3].label = delimit ? N_("No Delimiter") : N_("With Delimiter");
6304 if(fourlabel > 0)
6305 pipe_opt[fourlabel].label = newpipe ? N_("To Same Pipe") : N_("To Individual Pipes");
6309 * 2 is really 1 because there will be one real entry and
6310 * one entry of "" because of the get_prev_hist above.
6312 if(items_in_hist(history) > 2){
6313 pipe_opt[ku].name = HISTORY_UP_KEYNAME;
6314 pipe_opt[ku].label = HISTORY_KEYLABEL;
6315 pipe_opt[ku+1].name = HISTORY_DOWN_KEYNAME;
6316 pipe_opt[ku+1].label = HISTORY_KEYLABEL;
6318 else{
6319 pipe_opt[ku].name = "";
6320 pipe_opt[ku].label = "";
6321 pipe_opt[ku+1].name = "";
6322 pipe_opt[ku+1].label = "";
6325 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
6326 switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0,
6327 sizeof(pipe_command), prompt,
6328 pipe_opt, NO_HELP, &flags)){
6329 case -1 :
6330 q_status_message(SM_ORDER | SM_DING, 3, 4,
6331 _("Internal problem encountered"));
6332 done++;
6333 break;
6335 case 10 : /* flip raw bit */
6336 raw = !raw;
6337 break;
6339 case 11 : /* flip capture bit */
6340 capture = !capture;
6341 break;
6343 case 12 : /* flip delimit bit */
6344 delimit = !delimit;
6345 break;
6347 case 13 : /* flip newpipe bit */
6348 newpipe = !newpipe;
6349 break;
6351 case 30 :
6352 flagsforhist = (raw ? 0x8 : 0) +
6353 (delimit ? 0x4 : 0) +
6354 (newpipe ? 0x2 : 0) +
6355 (capture ? 0x1 : 0);
6356 if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6357 strncpy(pipe_command, p, sizeof(pipe_command));
6358 pipe_command[sizeof(pipe_command)-1] = '\0';
6359 if(history->hist[history->curindex]){
6360 flagsforhist = history->hist[history->curindex]->flags;
6361 raw = (flagsforhist & 0x8) ? 1 : 0;
6362 delimit = (flagsforhist & 0x4) ? 1 : 0;
6363 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6364 capture = (flagsforhist & 0x1) ? 1 : 0;
6367 else
6368 Writechar(BELL, 0);
6370 break;
6372 case 31 :
6373 flagsforhist = (raw ? 0x8 : 0) +
6374 (delimit ? 0x4 : 0) +
6375 (newpipe ? 0x2 : 0) +
6376 (capture ? 0x1 : 0);
6377 if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6378 strncpy(pipe_command, p, sizeof(pipe_command));
6379 pipe_command[sizeof(pipe_command)-1] = '\0';
6380 if(history->hist[history->curindex]){
6381 flagsforhist = history->hist[history->curindex]->flags;
6382 raw = (flagsforhist & 0x8) ? 1 : 0;
6383 delimit = (flagsforhist & 0x4) ? 1 : 0;
6384 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6385 capture = (flagsforhist & 0x1) ? 1 : 0;
6388 else
6389 Writechar(BELL, 0);
6391 break;
6393 case 0 :
6394 if(pipe_command[0]){
6396 flagsforhist = (raw ? 0x8 : 0) +
6397 (delimit ? 0x4 : 0) +
6398 (newpipe ? 0x2 : 0) +
6399 (capture ? 0x1 : 0);
6400 save_hist(history, pipe_command, flagsforhist, NULL);
6402 flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
6403 flags |= (raw ? PIPE_RAW : 0);
6404 if(!capture){
6405 #ifndef _WINDOWS
6406 ClearScreen();
6407 fflush(stdout);
6408 clear_cursor_pos();
6409 ps_global->mangled_screen = 1;
6410 ps_global->in_init_seq = 1;
6411 #endif
6412 flags |= PIPE_RESET;
6415 if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command,
6416 (flags & PIPE_RESET)
6417 ? NULL
6418 : &resultfilename,
6419 flags, &pc)))
6420 done++;
6422 for(i = mn_first_cur(msgmap);
6423 i > 0L && !done;
6424 i = mn_next_cur(msgmap)){
6425 e = pine_mail_fetchstructure(ps_global->mail_stream,
6426 mn_m2raw(msgmap, i), &b);
6427 if(!(state->mail_stream
6428 && (rawno = mn_m2raw(msgmap, i)) > 0L
6429 && rawno <= state->mail_stream->nmsgs
6430 && (mc = mail_elt(state->mail_stream, rawno))
6431 && mc->valid))
6432 mc = NULL;
6434 if((newpipe
6435 && !(syspipe = cmd_pipe_open(pipe_command,
6436 (flags & PIPE_RESET)
6437 ? NULL
6438 : &resultfilename,
6439 flags, &pc)))
6440 || (delimit && !bezerk_delimiter(e, mc, pc, next++)))
6441 done++;
6443 if(!done){
6444 if(raw){
6445 char *pipe_err;
6447 prime_raw_pipe_getc(ps_global->mail_stream,
6448 mn_m2raw(msgmap, i), -1L, 0L);
6449 gf_filter_init();
6450 gf_link_filter(gf_nvtnl_local, NULL);
6451 if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){
6452 q_status_message1(SM_ORDER|SM_DING,
6453 3, 3,
6454 _("Internal Error: %s"),
6455 pipe_err);
6456 done++;
6459 else if(!format_message(mn_m2raw(msgmap, i), e, b,
6460 NULL, FM_NEW_MESS | FM_NOWRAP, pc))
6461 done++;
6464 if(newpipe)
6465 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6466 done++;
6469 if(!capture)
6470 ps_global->in_init_seq = 0;
6472 if(!newpipe)
6473 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6474 done++;
6475 if(done) /* say we had a problem */
6476 q_status_message(SM_ORDER | SM_DING, 3, 3,
6477 _("Error piping message"));
6478 else if(resultfilename){
6479 rv++;
6480 /* only display if no error */
6481 display_output_file(resultfilename, "PIPE MESSAGE",
6482 NULL, DOF_EMPTY);
6483 fs_give((void **)&resultfilename);
6485 else{
6486 rv++;
6487 q_status_message(SM_ORDER, 0, 2, _("Pipe command completed"));
6490 done++;
6491 break;
6493 /* else fall thru as if cancelled */
6495 case 1 :
6496 cmd_cancelled("Pipe command");
6497 done++;
6498 break;
6500 case 3 :
6501 helper(h_common_pipe, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE);
6502 ps_global->mangled_screen = 1;
6503 break;
6505 case 2 : /* no place to escape to */
6506 case 4 : /* can't suspend */
6507 default :
6508 break;
6512 ps_global->mangled_footer = 1;
6513 if(MCMD_ISAGG(aopt))
6514 restore_selected(msgmap);
6516 return rv;
6520 /*----------------------------------------------------------------------
6521 Screen to offer list management commands contained in message
6523 Args: state -- pointer to struct holding a bunch of pine state
6524 msgmap -- table mapping msg nums to c-client sequence nums
6525 aopt -- aggregate options
6527 Result:
6529 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6530 ----*/
6531 void
6532 rfc2369_display(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno)
6534 int winner = 0;
6535 char *h, *hdrs[MLCMD_COUNT + 1];
6536 long index_no = mn_raw2m(msgmap, msgno);
6537 RFC2369_S data[MLCMD_COUNT];
6539 /* for each header field */
6540 if((h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))) != NULL){
6541 memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT);
6542 if(rfc2369_parse_fields(h, &data[0])){
6543 STORE_S *explain;
6545 if((explain = list_mgmt_text(data, index_no)) != NULL){
6546 list_mgmt_screen(explain);
6547 ps_global->mangled_screen = 1;
6548 so_give(&explain);
6549 winner++;
6553 fs_give((void **) &h);
6556 if(!winner)
6557 q_status_message1(SM_ORDER, 0, 3,
6558 "Message %s contains no list management information",
6559 comatose(index_no));
6563 STORE_S *
6564 list_mgmt_text(RFC2369_S *data, long int msgno)
6566 STORE_S *store;
6567 int i, j, n, fields = 0;
6568 static char *rfc2369_intro1 =
6569 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6570 static char *rfc2369_intro2[] = {
6571 N_(" has information associated with it "),
6572 N_("that explains how to participate in an email list. An "),
6573 N_("email list is represented by a single email address that "),
6574 N_("users sharing a common interest can send messages to (known "),
6575 N_("as posting) which are then redistributed to all members "),
6576 N_("of the list (sometimes after review by a moderator)."),
6577 N_("<P>List participation commands in this message include:"),
6578 NULL
6581 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6583 /* Insert introductory text */
6584 so_puts(store, rfc2369_intro1);
6586 so_puts(store, comatose(msgno));
6588 for(i = 0; rfc2369_intro2[i]; i++)
6589 so_puts(store, _(rfc2369_intro2[i]));
6591 so_puts(store, "<P>");
6592 for(i = 0; i < MLCMD_COUNT; i++)
6593 if(data[i].data[0].value
6594 || data[i].data[0].comment
6595 || data[i].data[0].error){
6596 if(!fields++)
6597 so_puts(store, "<UL>");
6599 so_puts(store, "<LI>");
6600 so_puts(store,
6601 (n = (data[i].data[1].value || data[i].data[1].comment))
6602 ? "Methods to "
6603 : "A method to ");
6605 so_puts(store, data[i].field.description);
6606 so_puts(store, ". ");
6608 if(n)
6609 so_puts(store, "<OL>");
6611 for(j = 0;
6612 j < MLCMD_MAXDATA
6613 && (data[i].data[j].comment
6614 || data[i].data[j].value
6615 || data[i].data[j].error);
6616 j++){
6618 so_puts(store, n ? "<P><LI>" : "<P>");
6620 if(data[i].data[j].comment){
6621 so_puts(store,
6622 _("With the provided comment:<P><BLOCKQUOTE>"));
6623 so_puts(store, data[i].data[j].comment);
6624 so_puts(store, "</BLOCKQUOTE><P>");
6627 if(data[i].data[j].value){
6628 if(i == MLCMD_POST
6629 && !strucmp(data[i].data[j].value, "NO")){
6630 so_puts(store,
6631 _("Posting is <EM>not</EM> allowed on this list"));
6633 else{
6634 so_puts(store, "Select <A HREF=\"");
6635 so_puts(store, data[i].data[j].value);
6636 so_puts(store, "\">HERE</A> to ");
6637 so_puts(store, (data[i].field.action)
6638 ? data[i].field.action
6639 : "try it");
6642 so_puts(store, ".");
6645 if(data[i].data[j].error){
6646 so_puts(store, "<P>Unfortunately, Alpine can not offer");
6647 so_puts(store, " to take direct action based upon it");
6648 so_puts(store, " because it was improperly formatted.");
6649 so_puts(store, " The unrecognized data associated with");
6650 so_puts(store, " the \"");
6651 so_puts(store, data[i].field.name);
6652 so_puts(store, "\" header field was:<P><BLOCKQUOTE>");
6653 so_puts(store, data[i].data[j].error);
6654 so_puts(store, "</BLOCKQUOTE>");
6657 so_puts(store, "<P>");
6660 if(n)
6661 so_puts(store, "</OL>");
6664 if(fields)
6665 so_puts(store, "</UL>");
6667 so_puts(store, "</BODY></HTML>");
6670 return(store);
6674 void
6675 list_mgmt_screen(STORE_S *html)
6677 int cmd = MC_NONE;
6678 long offset = 0L;
6679 char *error = NULL;
6680 STORE_S *store;
6681 HANDLE_S *handles = NULL;
6682 gf_io_t gc, pc;
6685 so_seek(html, 0L, 0);
6686 gf_set_so_readc(&gc, html);
6688 init_handles(&handles);
6690 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6691 gf_set_so_writec(&pc, store);
6692 gf_filter_init();
6694 gf_link_filter(gf_html2plain,
6695 gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols,
6696 non_messageview_margin(), &handles, NULL, 0));
6698 error = gf_pipe(gc, pc);
6700 gf_clear_so_writec(store);
6702 if(!error){
6703 SCROLL_S sargs;
6705 memset(&sargs, 0, sizeof(SCROLL_S));
6706 sargs.text.text = so_text(store);
6707 sargs.text.src = CharStar;
6708 sargs.text.desc = "list commands";
6709 sargs.text.handles = handles;
6710 if(offset){
6711 sargs.start.on = Offset;
6712 sargs.start.loc.offset = offset;
6715 sargs.bar.title = _("MAIL LIST COMMANDS");
6716 sargs.bar.style = MessageNumber;
6717 sargs.resize_exit = 1;
6718 sargs.help.text = h_special_list_commands;
6719 sargs.help.title = _("HELP FOR LIST COMMANDS");
6720 sargs.keys.menu = &listmgr_keymenu;
6721 setbitmap(sargs.keys.bitmap);
6722 if(!handles){
6723 clrbitn(LM_TRY_KEY, sargs.keys.bitmap);
6724 clrbitn(LM_PREV_KEY, sargs.keys.bitmap);
6725 clrbitn(LM_NEXT_KEY, sargs.keys.bitmap);
6728 cmd = scrolltool(&sargs);
6729 offset = sargs.start.loc.offset;
6732 so_give(&store);
6735 free_handles(&handles);
6736 gf_clear_so_readc(html);
6738 while(cmd == MC_RESIZE);
6742 /*----------------------------------------------------------------------
6743 Prompt the user for the type of select desired
6745 NOTE: any and all functions that successfully exit the second
6746 switch() statement below (currently "select_*() functions"),
6747 *MUST* update the folder's MESSAGECACHE element's "searched"
6748 bits to reflect the search result. Functions using
6749 mail_search() get this for free, the others must update 'em
6750 by hand.
6752 Returns -1 if canceled without changing selection
6753 0 if selection may have changed
6754 ----*/
6756 aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index)
6758 long i, diff, old_tot, msgno, raw;
6759 int q = 0, rv = 0, narrow = 0, hidden, ret = -1;
6760 ESCKEY_S *sel_opts;
6761 MESSAGECACHE *mc;
6762 SEARCHSET *limitsrch = NULL;
6763 PINETHRD_S *thrd;
6764 extern MAILSTREAM *mm_search_stream;
6765 extern long mm_search_count;
6767 hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
6768 mm_search_stream = state->mail_stream;
6769 mm_search_count = 0L;
6771 sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2;
6772 if(THREADING()){
6773 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH;
6775 else{
6776 sel_opts[SEL_OPTS_THREAD].ch = -1;
6779 if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){
6780 if(THRD_INDX()){
6781 i = 0;
6782 thrd = fetch_thread(state->mail_stream,
6783 mn_m2raw(msgmap, mn_get_cur(msgmap)));
6784 /* check if whole thread is selected or not */
6785 if(thrd &&
6786 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT)
6788 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE))
6789 i = 1;
6791 sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd");
6793 else{
6794 i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap),
6795 MN_SLCT);
6796 sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur");
6799 sel_opts += 2; /* disable extra options */
6800 switch(q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6801 RB_NORM)){
6802 case 'f' : /* flip selection */
6803 msgno = 0L;
6804 for(i = 1L; i <= mn_get_total(msgmap); i++){
6805 ret = 0;
6806 q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT);
6807 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q);
6808 if(hidden){
6809 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q);
6810 if(!msgno && q)
6811 mn_reset_cur(msgmap, msgno = i);
6815 return(ret);
6817 case 'n' : /* narrow selection */
6818 narrow++;
6819 case 'b' : /* broaden selection */
6820 q = 0; /* offer criteria prompt */
6821 break;
6823 case 'c' : /* Un/Select Current */
6824 case 'a' : /* Unselect All */
6825 case 'x' : /* cancel */
6826 break;
6828 default :
6829 q_status_message(SM_ORDER | SM_DING, 3, 3,
6830 "Unsupported Select option");
6831 return(ret);
6835 if(!q){
6836 while(1){
6837 q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x',
6838 NO_HELP, RB_NORM|RB_RET_HELP);
6840 if(q == 3){
6841 helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE);
6842 ps_global->mangled_screen = 1;
6844 else
6845 break;
6850 * The purpose of this is to add the appropriate searchset to the
6851 * search so that the search can be limited to only looking at what
6852 * it needs to look at. That is, if we are narrowing then we only need
6853 * to look at messages which are already selected, and if we are
6854 * broadening, then we only need to look at messages which are not
6855 * yet selected. This routine will work whether or not
6856 * limiting_searchset properly limits the search set. In particular,
6857 * the searchset returned by limiting_searchset may include messages
6858 * which really shouldn't be included. We do that because a too-large
6859 * searchset will break some IMAP servers. It is even possible that it
6860 * becomes inefficient to send the whole set. If the select function
6861 * frees limitsrch, it should be sure to set it to NULL so we won't
6862 * try freeing it again here.
6864 limitsrch = limiting_searchset(state->mail_stream, narrow);
6867 * NOTE: See note about MESSAGECACHE "searched" bits above!
6869 switch(q){
6870 case 'x': /* cancel */
6871 cmd_cancelled("Select command");
6872 return(ret);
6874 case 'c' : /* select/unselect current */
6875 (void) select_by_current(state, msgmap, in_index);
6876 ret = 0;
6877 return(ret);
6879 case 'a' : /* select/unselect all */
6880 msgno = any_lflagged(msgmap, MN_SLCT);
6881 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
6882 ret = 0;
6883 agg_select_all(state->mail_stream, msgmap, &diff,
6884 any_lflagged(msgmap, MN_SLCT) <= 0L);
6885 q_status_message4(SM_ORDER,0,2,
6886 "%s%s message%s %sselected",
6887 msgno ? "" : "All ", comatose(diff),
6888 plural(diff), msgno ? "UN" : "");
6889 return(ret);
6891 case 'n' : /* Select by Number */
6892 ret = 0;
6893 if(THRD_INDX())
6894 rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch);
6895 else
6896 rv = select_by_number(state->mail_stream, msgmap, &limitsrch);
6898 break;
6900 case 'd' : /* Select by Date */
6901 ret = 0;
6902 rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap),
6903 &limitsrch);
6904 break;
6906 case 't' : /* Text */
6907 ret = 0;
6908 rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap),
6909 &limitsrch);
6910 break;
6912 case 'z' : /* Size */
6913 ret = 0;
6914 rv = select_by_size(state->mail_stream, &limitsrch);
6915 break;
6917 case 's' : /* Status */
6918 ret = 0;
6919 rv = select_by_status(state->mail_stream, &limitsrch);
6920 break;
6922 case 'k' : /* Keyword */
6923 ret = 0;
6924 rv = select_by_keyword(state->mail_stream, &limitsrch);
6925 break;
6927 case 'r' : /* Rule */
6928 ret = 0;
6929 rv = select_by_rule(state->mail_stream, &limitsrch);
6930 break;
6932 case 'h' : /* Thread */
6933 ret = 0;
6934 rv = select_by_thread(state->mail_stream, msgmap, &limitsrch);
6935 break;
6937 default :
6938 q_status_message(SM_ORDER | SM_DING, 3, 3,
6939 "Unsupported Select option");
6940 return(ret);
6943 if(limitsrch)
6944 mail_free_searchset(&limitsrch);
6946 if(rv) /* bad return value.. */
6947 return(ret); /* error already displayed */
6949 if(narrow) /* make sure something was selected */
6950 for(i = 1L; i <= mn_get_total(msgmap); i++)
6951 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
6952 && raw <= state->mail_stream->nmsgs
6953 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
6954 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
6955 break;
6956 else
6957 mm_search_count--;
6960 diff = 0L;
6961 if(mm_search_count){
6963 * loop thru all the messages, adjusting local flag bits
6964 * based on their "searched" bit...
6966 for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++)
6967 if(narrow){
6968 /* turning OFF selectedness if the "searched" bit isn't lit. */
6969 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
6970 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
6971 && raw <= state->mail_stream->nmsgs
6972 && (mc = mail_elt(state->mail_stream, raw))
6973 && !mc->searched){
6974 diff--;
6975 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
6976 if(hidden)
6977 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
6979 /* adjust current message in case we unselect and hide it */
6980 else if(msgno < mn_get_cur(msgmap)
6981 && (!THRD_INDX()
6982 || !get_lflag(state->mail_stream, msgmap,
6983 i, MN_CHID)))
6984 msgno = i;
6987 else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
6988 && raw <= state->mail_stream->nmsgs
6989 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
6990 /* turn ON selectedness if "searched" bit is lit. */
6991 if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
6992 diff++;
6993 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
6994 if(hidden)
6995 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
6999 /* if we're zoomed and the current message was unselected */
7000 if(narrow && msgno
7001 && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE))
7002 mn_reset_cur(msgmap, msgno);
7005 if(!diff){
7006 if(narrow)
7007 q_status_message4(SM_ORDER, 3, 3,
7008 "%s. %s message%s remain%s selected.",
7009 mm_search_count
7010 ? "No change resulted"
7011 : "No messages in intersection",
7012 comatose(old_tot), plural(old_tot),
7013 (old_tot == 1L) ? "s" : "");
7014 else if(old_tot)
7015 q_status_message(SM_ORDER, 3, 3,
7016 _("No change resulted. Matching messages already selected."));
7017 else
7018 q_status_message1(SM_ORDER | SM_DING, 3, 3,
7019 _("Select failed. No %smessages selected."),
7020 old_tot ? _("additional ") : "");
7022 else if(old_tot){
7023 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
7024 "Select matched %ld message%s. %s %smessage%s %sselected.",
7025 (diff > 0) ? diff : old_tot + diff,
7026 plural((diff > 0) ? diff : old_tot + diff),
7027 comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7028 (diff > 0) ? "total " : "",
7029 plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7030 (diff > 0) ? "" : "UN");
7031 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
7032 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
7034 else
7035 q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"),
7036 comatose(diff), plural(diff));
7038 return(ret);
7042 /*----------------------------------------------------------------------
7043 Toggle the state of the current message
7045 Args: state -- pointer pine's state variables
7046 msgmap -- message collection to operate on
7047 in_index -- in the message index view
7048 Returns: TRUE if current marked selected, FALSE otw
7049 ----*/
7051 select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
7053 long cur;
7054 int all_selected = 0;
7055 unsigned long was, tot, rawno;
7056 PINETHRD_S *thrd;
7058 cur = mn_get_cur(msgmap);
7060 if(THRD_INDX()){
7061 thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur));
7062 if(!thrd)
7063 return 0;
7065 was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT);
7066 tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE);
7067 if(was == tot)
7068 all_selected++;
7070 if(all_selected){
7071 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0);
7072 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7073 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1);
7075 * See if there's anything left to zoom on. If so,
7076 * pick an adjacent one for highlighting, else make
7077 * sure nothing is left hidden...
7079 if(any_lflagged(msgmap, MN_SLCT)){
7080 mn_inc_cur(state->mail_stream, msgmap,
7081 (in_index == View && THREADING()
7082 && sp_viewing_a_thread(state->mail_stream))
7083 ? MH_THISTHD
7084 : (in_index == View)
7085 ? MH_ANYTHD : MH_NONE);
7086 if(mn_get_cur(msgmap) == cur)
7087 mn_dec_cur(state->mail_stream, msgmap,
7088 (in_index == View && THREADING()
7089 && sp_viewing_a_thread(state->mail_stream))
7090 ? MH_THISTHD
7091 : (in_index == View)
7092 ? MH_ANYTHD : MH_NONE);
7094 else /* clear all hidden flags */
7095 (void) unzoom_index(state, state->mail_stream, msgmap);
7098 else
7099 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1);
7101 q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected",
7102 comatose(all_selected ? was : tot-was),
7103 plural(all_selected ? was : tot-was),
7104 all_selected ? "UN" : "");
7106 /* collapsed thread */
7107 else if(THREADING()
7108 && ((rawno = mn_m2raw(msgmap, cur)) != 0L)
7109 && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL)
7110 && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){
7112 * This doesn't work quite the same as the colon command works, but
7113 * it is arguably doing the correct thing. The difference is
7114 * that aggregate_select will zoom after selecting back where it
7115 * was called from, but selecting a thread with colon won't zoom.
7116 * Maybe it makes sense to zoom after a select but not after a colon
7117 * command even though they are very similar.
7119 thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state));
7121 else{
7122 if((all_selected =
7123 get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */
7124 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0);
7125 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7126 set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1);
7128 * See if there's anything left to zoom on. If so,
7129 * pick an adjacent one for highlighting, else make
7130 * sure nothing is left hidden...
7132 if(any_lflagged(msgmap, MN_SLCT)){
7133 mn_inc_cur(state->mail_stream, msgmap,
7134 (in_index == View && THREADING()
7135 && sp_viewing_a_thread(state->mail_stream))
7136 ? MH_THISTHD
7137 : (in_index == View)
7138 ? MH_ANYTHD : MH_NONE);
7139 if(mn_get_cur(msgmap) == cur)
7140 mn_dec_cur(state->mail_stream, msgmap,
7141 (in_index == View && THREADING()
7142 && sp_viewing_a_thread(state->mail_stream))
7143 ? MH_THISTHD
7144 : (in_index == View)
7145 ? MH_ANYTHD : MH_NONE);
7147 else /* clear all hidden flags */
7148 (void) unzoom_index(state, state->mail_stream, msgmap);
7151 else
7152 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1);
7154 q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected",
7155 long2string(cur), all_selected ? "UN" : "");
7159 return(!all_selected);
7163 /*----------------------------------------------------------------------
7164 Prompt the user for the command to perform on selected messages
7166 Args: state -- pointer pine's state variables
7167 msgmap -- message collection to operate on
7168 q_line -- line on display to write prompts
7169 Returns: 1 if the selected messages are suitably commanded,
7170 0 if the choice to pick the command was declined
7172 ----*/
7174 apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
7175 UCS preloadkeystroke, int flags, int q_line)
7177 int i = 8, /* number of static entries in sel_opts3 */
7178 rv = 0,
7179 cmd,
7180 we_cancel = 0,
7181 agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG;
7182 char prompt[80];
7185 * To do this "right", we really ought to have access to the keymenu
7186 * here and change the typed command into a real command by running
7187 * it through menu_command. Then the switch below would be against
7188 * results from menu_command. If we did that we'd also pass the
7189 * results of menu_command in as preloadkeystroke instead of passing
7190 * the keystroke itself. But we don't have the keymenu handy,
7191 * so we have to fake it. The only complication that we run into
7192 * is that KEY_DEL is an escape sequence so we change a typed
7193 * KEY_DEL esc seq into the letter D.
7196 if(!preloadkeystroke){
7197 if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
7198 sel_opts3[i].ch = '*';
7199 sel_opts3[i].rval = '*';
7200 sel_opts3[i].name = "*";
7201 sel_opts3[i++].label = N_("Flag");
7204 if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
7205 sel_opts3[i].ch = '|';
7206 sel_opts3[i].rval = '|';
7207 sel_opts3[i].name = "|";
7208 sel_opts3[i++].label = N_("Pipe");
7211 if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */
7212 sel_opts3[i].ch = 'b';
7213 sel_opts3[i].rval = 'b';
7214 sel_opts3[i].name = "B";
7215 sel_opts3[i++].label = N_("Bounce");
7218 if(flags & AC_FROM_THREAD){
7219 if(flags & (AC_COLL | AC_EXPN)){
7220 sel_opts3[i].ch = '/';
7221 sel_opts3[i].rval = '/';
7222 sel_opts3[i].name = "/";
7223 sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse")
7224 : N_("Expand");
7227 sel_opts3[i].ch = ';';
7228 sel_opts3[i].rval = ';';
7229 sel_opts3[i].name = ";";
7230 if(flags & AC_UNSEL)
7231 sel_opts3[i++].label = N_("UnSelect");
7232 else
7233 sel_opts3[i++].label = N_("Select");
7236 if(F_ON(F_ENABLE_PRYNT, state)){ /* this one is invisible */
7237 sel_opts3[i].ch = 'y';
7238 sel_opts3[i].rval = '%';
7239 sel_opts3[i].name = "";
7240 sel_opts3[i++].label = "";
7243 if(!is_imap_stream(stream) || LEVELUIDPLUS(stream)){ /* expunge selected messages */
7244 sel_opts3[i].ch = 'x';
7245 sel_opts3[i].rval = 'x';
7246 sel_opts3[i].name = "X";
7247 sel_opts3[i++].label = N_("Expunge");
7250 sel_opts3[i].ch = KEY_DEL; /* also invisible */
7251 sel_opts3[i].rval = 'd';
7252 sel_opts3[i].name = "";
7253 sel_opts3[i++].label = "";
7255 sel_opts3[i].ch = -1;
7257 snprintf(prompt, sizeof(prompt), "%s command : ",
7258 (flags & AC_FROM_THREAD) ? "THREAD" : "APPLY");
7259 prompt[sizeof(prompt)-1] = '\0';
7260 cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'c', NO_HELP,
7261 RB_SEQ_SENSITIVE);
7262 if(isupper(cmd))
7263 cmd = tolower(cmd);
7265 else{
7266 if(preloadkeystroke == KEY_DEL)
7267 cmd = 'd';
7268 else{
7269 if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke))
7270 cmd = tolower((int) preloadkeystroke);
7271 else
7272 cmd = (int) preloadkeystroke; /* shouldn't happen */
7276 switch(cmd){
7277 case 'd' : /* delete */
7278 we_cancel = busy_cue(NULL, NULL, 1);
7279 rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */
7280 if(we_cancel)
7281 cancel_busy_cue(0);
7282 break;
7284 case 'u' : /* undelete */
7285 we_cancel = busy_cue(NULL, NULL, 1);
7286 rv = cmd_undelete(state, msgmap, agg);
7287 if(we_cancel)
7288 cancel_busy_cue(0);
7289 break;
7291 case 'r' : /* reply */
7292 rv = cmd_reply(state, msgmap, agg);
7293 break;
7295 case 'f' : /* Forward */
7296 rv = cmd_forward(state, msgmap, agg);
7297 break;
7299 case '%' : /* print */
7300 rv = cmd_print(state, msgmap, agg, MsgIndx);
7301 break;
7303 case 't' : /* take address */
7304 rv = cmd_take_addr(state, msgmap, agg);
7305 break;
7307 case 's' : /* save */
7308 rv = cmd_save(state, stream, msgmap, agg, MsgIndx);
7309 break;
7311 case 'e' : /* export */
7312 rv = cmd_export(state, msgmap, q_line, agg);
7313 break;
7315 case '|' : /* pipe */
7316 rv = cmd_pipe(state, msgmap, agg);
7317 break;
7319 case '*' : /* flag */
7320 we_cancel = busy_cue(NULL, NULL, 1);
7321 rv = cmd_flag(state, msgmap, agg);
7322 if(we_cancel)
7323 cancel_busy_cue(0);
7324 break;
7326 case 'b' : /* bounce */
7327 rv = cmd_bounce(state, msgmap, agg);
7328 break;
7330 case '/' :
7331 collapse_or_expand(state, stream, msgmap,
7332 F_ON(F_SLASH_COLL_ENTIRE, ps_global)
7333 ? 0L
7334 : mn_get_cur(msgmap));
7335 break;
7337 case ':' :
7338 select_thread_stmp(state, stream, msgmap);
7339 break;
7341 case 'x' : /* Expunge */
7342 rv = cmd_expunge(state, stream, msgmap, agg);
7343 break;
7345 case 'c' : /* cancel */
7346 cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command"
7347 : "Apply command");
7348 break;
7350 case 'z' : /* default */
7351 q_status_message(SM_INFO, 0, 2,
7352 "Cancelled, there is no default command");
7353 break;
7355 default:
7356 break;
7359 return(rv);
7364 * Select by message number ranges.
7365 * Sets searched bits in mail_elts
7367 * Args limitsrch -- limit search to this searchset
7369 * Returns 0 on success.
7372 select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
7374 int r, end;
7375 long n1, n2, raw;
7376 char number1[16], number2[16], numbers[80], *p, *t;
7377 HelpType help;
7378 MESSAGECACHE *mc;
7380 numbers[0] = '\0';
7381 ps_global->mangled_footer = 1;
7382 help = NO_HELP;
7383 while(1){
7384 int flags = OE_APPEND_CURRENT;
7386 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7387 sizeof(numbers), _(select_num), NULL, help, &flags);
7388 if(r == 4)
7389 continue;
7391 if(r == 3){
7392 help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
7393 continue;
7396 for(t = p = numbers; *p ; p++) /* strip whitespace */
7397 if(!isspace((unsigned char)*p))
7398 *t++ = *p;
7400 *t = '\0';
7402 if(r == 1 || numbers[0] == '\0'){
7403 cmd_cancelled("Selection by number");
7404 return(1);
7406 else
7407 break;
7410 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7411 if((mc = mail_elt(stream, n1)) != NULL)
7412 mc->searched = 0; /* clear searched bits */
7414 for(p = numbers; *p ; p++){
7415 t = number1;
7416 while(*p && isdigit((unsigned char)*p))
7417 *t++ = *p++;
7419 *t = '\0';
7421 end = 0;
7422 if(number1[0] == '\0'){
7423 if(*p == '-'){
7424 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7425 _("Invalid number range, missing number before \"-\": %s"),
7426 numbers);
7427 return(1);
7429 else if(!strucmp("end", p)){
7430 end = 1;
7431 p += strlen("end");
7433 else{
7434 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7435 _("Invalid message number: %s"), numbers);
7436 return(1);
7440 if(end)
7441 n1 = mn_get_total(msgmap);
7442 else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
7443 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7444 _("\"%s\" out of message number range"),
7445 long2string(n1));
7446 return(1);
7449 t = number2;
7450 if(*p == '-'){
7451 while(*++p && isdigit((unsigned char)*p))
7452 *t++ = *p;
7454 *t = '\0';
7456 end = 0;
7457 if(number2[0] == '\0'){
7458 if(!strucmp("end", p)){
7459 end = 1;
7460 p += strlen("end");
7462 else{
7463 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7464 _("Invalid number range, missing number after \"-\": %s"),
7465 numbers);
7466 return(1);
7470 if(end)
7471 n2 = mn_get_total(msgmap);
7472 else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){
7473 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7474 _("\"%s\" out of message number range"),
7475 long2string(n2));
7476 return(1);
7479 if(n2 <= n1){
7480 char t[20];
7482 strncpy(t, long2string(n1), sizeof(t));
7483 t[sizeof(t)-1] = '\0';
7484 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7485 _("Invalid reverse message number range: %s-%s"),
7486 t, long2string(n2));
7487 return(1);
7490 for(;n1 <= n2; n1++){
7491 raw = mn_m2raw(msgmap, n1);
7492 if(raw > 0L
7493 && (!(limitsrch && *limitsrch)
7494 || in_searchset(*limitsrch, (unsigned long) raw)))
7495 mm_searched(stream, raw);
7498 else{
7499 raw = mn_m2raw(msgmap, n1);
7500 if(raw > 0L
7501 && (!(limitsrch && *limitsrch)
7502 || in_searchset(*limitsrch, (unsigned long) raw)))
7503 mm_searched(stream, raw);
7506 if(*p == '\0')
7507 break;
7510 return(0);
7515 * Select by thread number ranges.
7516 * Sets searched bits in mail_elts
7518 * Args limitsrch -- limit search to this searchset
7520 * Returns 0 on success.
7523 select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset)
7525 int r, end;
7526 long n1, n2;
7527 char number1[16], number2[16], numbers[80], *p, *t;
7528 HelpType help;
7529 PINETHRD_S *thrd = NULL;
7530 MESSAGECACHE *mc;
7532 numbers[0] = '\0';
7533 ps_global->mangled_footer = 1;
7534 help = NO_HELP;
7535 while(1){
7536 int flags = OE_APPEND_CURRENT;
7538 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7539 sizeof(numbers), _(select_num), NULL, help, &flags);
7540 if(r == 4)
7541 continue;
7543 if(r == 3){
7544 help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP;
7545 continue;
7548 for(t = p = numbers; *p ; p++) /* strip whitespace */
7549 if(!isspace((unsigned char)*p))
7550 *t++ = *p;
7552 *t = '\0';
7554 if(r == 1 || numbers[0] == '\0'){
7555 cmd_cancelled("Selection by number");
7556 return(1);
7558 else
7559 break;
7562 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7563 if((mc = mail_elt(stream, n1)) != NULL)
7564 mc->searched = 0; /* clear searched bits */
7566 for(p = numbers; *p ; p++){
7567 t = number1;
7568 while(*p && isdigit((unsigned char)*p))
7569 *t++ = *p++;
7571 *t = '\0';
7573 end = 0;
7574 if(number1[0] == '\0'){
7575 if(*p == '-'){
7576 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7577 _("Invalid number range, missing number before \"-\": %s"),
7578 numbers);
7579 return(1);
7581 else if(!strucmp("end", p)){
7582 end = 1;
7583 p += strlen("end");
7585 else{
7586 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7587 _("Invalid thread number: %s"), numbers);
7588 return(1);
7592 if(end)
7593 n1 = msgmap->max_thrdno;
7594 else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){
7595 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7596 _("\"%s\" out of thread number range"),
7597 long2string(n1));
7598 return(1);
7601 t = number2;
7602 if(*p == '-'){
7604 while(*++p && isdigit((unsigned char)*p))
7605 *t++ = *p;
7607 *t = '\0';
7609 end = 0;
7610 if(number2[0] == '\0'){
7611 if(!strucmp("end", p)){
7612 end = 1;
7613 p += strlen("end");
7615 else{
7616 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7617 _("Invalid number range, missing number after \"-\": %s"),
7618 numbers);
7619 return(1);
7623 if(end)
7624 n2 = msgmap->max_thrdno;
7625 else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){
7626 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7627 _("\"%s\" out of thread number range"),
7628 long2string(n2));
7629 return(1);
7632 if(n2 <= n1){
7633 char t[20];
7635 strncpy(t, long2string(n1), sizeof(t));
7636 t[sizeof(t)-1] = '\0';
7637 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7638 _("Invalid reverse message number range: %s-%s"),
7639 t, long2string(n2));
7640 return(1);
7643 for(;n1 <= n2; n1++){
7644 thrd = find_thread_by_number(stream, msgmap, n1, thrd);
7646 if(thrd)
7647 set_search_bit_for_thread(stream, thrd, msgset);
7650 else{
7651 thrd = find_thread_by_number(stream, msgmap, n1, NULL);
7653 if(thrd)
7654 set_search_bit_for_thread(stream, thrd, msgset);
7657 if(*p == '\0')
7658 break;
7661 return(0);
7666 * Select by message dates.
7667 * Sets searched bits in mail_elts
7669 * Args limitsrch -- limit search to this searchset
7671 * Returns 0 on success.
7674 select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
7676 int r, we_cancel = 0, when = 0;
7677 char date[100], defdate[100], prompt[128];
7678 time_t seldate = time(0);
7679 struct tm *seldate_tm;
7680 SEARCHPGM *pgm;
7681 HelpType help;
7682 static struct _tense {
7683 char *preamble,
7684 *range,
7685 *scope;
7686 } tense[] = {
7687 {"were ", "SENT SINCE", " (inclusive)"},
7688 {"were ", "SENT BEFORE", " (exclusive)"},
7689 {"were ", "SENT ON", "" },
7690 {"", "ARRIVED SINCE", " (inclusive)"},
7691 {"", "ARRIVED BEFORE", " (exclusive)"},
7692 {"", "ARRIVED ON", "" }
7695 date[0] = '\0';
7696 ps_global->mangled_footer = 1;
7697 help = NO_HELP;
7700 * If talking to an old server, default to SINCE instead of
7701 * SENTSINCE, which was added later.
7703 if(is_imap_stream(stream) && !modern_imap_stream(stream))
7704 when = 3;
7706 while(1){
7707 int flags = OE_APPEND_CURRENT;
7709 seldate_tm = localtime(&seldate);
7710 snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday,
7711 month_abbrev(seldate_tm->tm_mon + 1),
7712 seldate_tm->tm_year + 1900);
7713 defdate[sizeof(defdate)-1] = '\0';
7714 snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ",
7715 tense[when].preamble, tense[when].range,
7716 tense[when].scope, defdate);
7717 prompt[sizeof(prompt)-1] = '\0';
7718 r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date),
7719 prompt, sel_date_opt, help, &flags);
7720 switch (r){
7721 case 1 :
7722 cmd_cancelled("Selection by date");
7723 return(1);
7725 case 3 :
7726 help = (help == NO_HELP) ? h_select_date : NO_HELP;
7727 continue;
7729 case 4 :
7730 continue;
7732 case 11 :
7734 MESSAGECACHE *mc;
7735 long rawno;
7737 if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L
7738 && rawno <= stream->nmsgs
7739 && (mc = mail_elt(stream, rawno))){
7741 /* cache not filled in yet? */
7742 if(mc->day == 0){
7743 char seq[20];
7745 if(stream->dtb && stream->dtb->flags & DR_NEWS){
7746 strncpy(seq,
7747 ulong2string(mail_uid(stream, rawno)),
7748 sizeof(seq));
7749 seq[sizeof(seq)-1] = '\0';
7750 mail_fetch_overview(stream, seq, NULL);
7752 else{
7753 strncpy(seq, long2string(rawno),
7754 sizeof(seq));
7755 seq[sizeof(seq)-1] = '\0';
7756 mail_fetch_fast(stream, seq, 0L);
7760 /* mail_date returns fixed field width date */
7761 mail_date(date, mc);
7762 date[11] = '\0';
7766 continue;
7768 case 12 : /* set default to PREVIOUS day */
7769 seldate -= 86400;
7770 continue;
7772 case 13 : /* set default to NEXT day */
7773 seldate += 86400;
7774 continue;
7776 case 14 :
7777 when = (when+1) % (sizeof(tense) / sizeof(struct _tense));
7778 continue;
7780 default:
7781 break;
7784 removing_leading_white_space(date);
7785 removing_trailing_white_space(date);
7786 if(!*date){
7787 strncpy(date, defdate, sizeof(date));
7788 date[sizeof(date)-1] = '\0';
7791 break;
7794 if((pgm = mail_newsearchpgm()) != NULL){
7795 MESSAGECACHE elt;
7796 short converted_date;
7798 if(mail_parse_date(&elt, (unsigned char *) date)){
7799 converted_date = mail_shortdate(elt.year, elt.month, elt.day);
7801 switch(when){
7802 case 0:
7803 pgm->sentsince = converted_date;
7804 break;
7805 case 1:
7806 pgm->sentbefore = converted_date;
7807 break;
7808 case 2:
7809 pgm->senton = converted_date;
7810 break;
7811 case 3:
7812 pgm->since = converted_date;
7813 break;
7814 case 4:
7815 pgm->before = converted_date;
7816 break;
7817 case 5:
7818 pgm->on = converted_date;
7819 break;
7822 pgm->msgno = (limitsrch ? *limitsrch : NULL);
7824 if(ps_global && ps_global->ttyo){
7825 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
7826 ps_global->mangled_footer = 1;
7829 we_cancel = busy_cue(_("Selecting"), NULL, 1);
7831 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
7833 if(we_cancel)
7834 cancel_busy_cue(0);
7836 /* we know this was freed in mail_search, let caller know */
7837 if(limitsrch)
7838 *limitsrch = NULL;
7840 else{
7841 mail_free_searchpgm(&pgm);
7842 q_status_message1(SM_ORDER, 3, 3,
7843 _("Invalid date entered: %s"), date);
7844 return(1);
7848 return(0);
7853 * Select by searching in message headers or body.
7854 * Sets searched bits in mail_elts
7856 * Args limitsrch -- limit search to this searchset
7858 * Returns 0 on success.
7861 select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
7863 int r, ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
7864 int not = 0, me = 0;
7865 char sstring[80], savedsstring[80], tmp[128];
7866 char *p, *sval = NULL;
7867 char buftmp[MAILTMPLEN], namehdr[80];
7868 ESCKEY_S ekey[8];
7869 ENVELOPE *env = NULL;
7870 HelpType help;
7871 unsigned flagsforhist = 0;
7872 static HISTORY_S *history = NULL;
7873 static char *recip = "RECIPIENTS";
7874 static char *partic = "PARTICIPANTS";
7875 static char *match_me = N_("[Match_My_Addresses]");
7876 static char *dont_match_me = N_("[Don't_Match_My_Addresses]");
7878 ps_global->mangled_footer = 1;
7879 savedsstring[0] = '\0';
7880 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
7882 while(1){
7883 type = radio_buttons(not ? _(sel_text_not) : _(sel_text),
7884 -FOOTER_ROWS(ps_global), sel_text_opt,
7885 's', 'x', NO_HELP, RB_NORM|RB_RET_HELP);
7887 if(type == '!')
7888 not = !not;
7889 else if(type == 3){
7890 helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS",
7891 HLPD_SIMPLE);
7892 ps_global->mangled_screen = 1;
7894 else
7895 break;
7899 * prepare some friendly defaults...
7901 switch(type){
7902 case 't' : /* address fields, offer To or From */
7903 case 'f' :
7904 case 'c' :
7905 case 'r' :
7906 case 'p' :
7907 sval = (type == 't') ? "TO" :
7908 (type == 'f') ? "FROM" :
7909 (type == 'c') ? "CC" :
7910 (type == 'r') ? recip : partic;
7911 ekey[ekeyi].ch = ctrl('T');
7912 ekey[ekeyi].name = "^T";
7913 ekey[ekeyi].rval = 10;
7914 /* TRANSLATORS: use Current To Address */
7915 ekey[ekeyi++].label = N_("Cur To");
7916 ekey[ekeyi].ch = ctrl('R');
7917 ekey[ekeyi].name = "^R";
7918 ekey[ekeyi].rval = 11;
7919 /* TRANSLATORS: use Current From Address */
7920 ekey[ekeyi++].label = N_("Cur From");
7921 ekey[ekeyi].ch = ctrl('W');
7922 ekey[ekeyi].name = "^W";
7923 ekey[ekeyi].rval = 12;
7924 /* TRANSLATORS: use Current Cc Address */
7925 ekey[ekeyi++].label = N_("Cur Cc");
7926 ekey[ekeyi].ch = ctrl('Y');
7927 ekey[ekeyi].name = "^Y";
7928 ekey[ekeyi].rval = 13;
7929 /* TRANSLATORS: Match Me means match my address */
7930 ekey[ekeyi++].label = N_("Match Me");
7931 ekey[ekeyi].ch = 0;
7932 ekey[ekeyi].name = "";
7933 ekey[ekeyi].rval = 0;
7934 ekey[ekeyi++].label = "";
7935 break;
7937 case 's' :
7938 sval = "SUBJECT";
7939 ekey[ekeyi].ch = ctrl('X');
7940 ekey[ekeyi].name = "^X";
7941 ekey[ekeyi].rval = 14;
7942 /* TRANSLATORS: use Current Subject */
7943 ekey[ekeyi++].label = N_("Cur Subject");
7944 break;
7946 case 'a' :
7947 sval = "TEXT";
7948 break;
7950 case 'b' :
7951 sval = "BODYTEXT";
7952 break;
7954 case 'h' :
7955 strncpy(tmp, "Name of HEADER to match : ", sizeof(tmp)-1);
7956 tmp[sizeof(tmp)-1] = '\0';
7957 flags = OE_APPEND_CURRENT;
7958 namehdr[0] = '\0';
7959 r = 'x';
7960 while (r == 'x'){
7961 int done = 0;
7963 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
7964 sizeof(namehdr), tmp, ekey, NO_HELP, &flags);
7965 if (r == 1){
7966 cmd_cancelled("Selection by text");
7967 return(1);
7969 removing_leading_white_space(namehdr);
7970 while(!done){
7971 while ((namehdr[0] != '\0') && /* remove trailing ":" */
7972 (namehdr[strlen(namehdr) - 1] == ':'))
7973 namehdr[strlen(namehdr) - 1] = '\0';
7974 if ((namehdr[0] != '\0')
7975 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
7976 removing_trailing_white_space(namehdr);
7977 else
7978 done++;
7980 if (strchr(namehdr,' ') || strchr(namehdr,'\t') ||
7981 strchr(namehdr,':'))
7982 namehdr[0] = '\0';
7983 if (namehdr[0] == '\0')
7984 r = 'x';
7986 sval = namehdr;
7987 break;
7989 case 'x':
7990 break;
7992 default:
7993 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
7994 return(1);
7997 ekey[ekeyi].ch = KEY_UP;
7998 ekey[ekeyi].rval = 30;
7999 ekey[ekeyi].name = "";
8000 ku = ekeyi;
8001 ekey[ekeyi++].label = "";
8003 ekey[ekeyi].ch = KEY_DOWN;
8004 ekey[ekeyi].rval = 31;
8005 ekey[ekeyi].name = "";
8006 ekey[ekeyi++].label = "";
8008 ekey[ekeyi].ch = -1;
8010 if(type != 'x'){
8012 init_hist(&history, HISTSIZE);
8014 if(ekey[0].ch > -1 && msgno > 0L
8015 && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),
8016 NULL)))
8017 ekey[0].ch = -1;
8019 sstring[0] = '\0';
8020 help = NO_HELP;
8021 r = type;
8022 while(r != 'x'){
8023 if(not)
8024 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8025 snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval);
8026 else
8027 snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval);
8029 if(items_in_hist(history) > 0){
8030 ekey[ku].name = HISTORY_UP_KEYNAME;
8031 ekey[ku].label = HISTORY_KEYLABEL;
8032 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
8033 ekey[ku+1].label = HISTORY_KEYLABEL;
8035 else{
8036 ekey[ku].name = "";
8037 ekey[ku].label = "";
8038 ekey[ku+1].name = "";
8039 ekey[ku+1].label = "";
8042 flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
8043 r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
8044 79, tmp, ekey, help, &flags);
8046 if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me)))))
8047 me = 0;
8049 switch(r){
8050 case 3 :
8051 help = (help == NO_HELP)
8052 ? (not
8053 ? ((type == 'f') ? h_select_txt_not_from
8054 : (type == 't') ? h_select_txt_not_to
8055 : (type == 'c') ? h_select_txt_not_cc
8056 : (type == 's') ? h_select_txt_not_subj
8057 : (type == 'a') ? h_select_txt_not_all
8058 : (type == 'r') ? h_select_txt_not_recip
8059 : (type == 'p') ? h_select_txt_not_partic
8060 : (type == 'b') ? h_select_txt_not_body
8061 : NO_HELP)
8062 : ((type == 'f') ? h_select_txt_from
8063 : (type == 't') ? h_select_txt_to
8064 : (type == 'c') ? h_select_txt_cc
8065 : (type == 's') ? h_select_txt_subj
8066 : (type == 'a') ? h_select_txt_all
8067 : (type == 'r') ? h_select_txt_recip
8068 : (type == 'p') ? h_select_txt_partic
8069 : (type == 'b') ? h_select_txt_body
8070 : NO_HELP))
8071 : NO_HELP;
8073 case 4 :
8074 continue;
8076 case 10 : /* To: default */
8077 if(env && env->to && env->to->mailbox){
8078 snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox,
8079 env->to->host ? "@" : "",
8080 env->to->host ? env->to->host : "");
8081 sstring[sizeof(sstring)-1] = '\0';
8083 continue;
8085 case 11 : /* From: default */
8086 if(env && env->from && env->from->mailbox){
8087 snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox,
8088 env->from->host ? "@" : "",
8089 env->from->host ? env->from->host : "");
8090 sstring[sizeof(sstring)-1] = '\0';
8092 continue;
8094 case 12 : /* Cc: default */
8095 if(env && env->cc && env->cc->mailbox){
8096 snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox,
8097 env->cc->host ? "@" : "",
8098 env->cc->host ? env->cc->host : "");
8099 sstring[sizeof(sstring)-1] = '\0';
8101 continue;
8103 case 13 : /* Match my addresses */
8104 me++;
8105 snprintf(sstring, sizeof(sstring), "%s", not ? _(dont_match_me) : _(match_me));
8106 continue;
8108 case 14 : /* Subject: default */
8109 if(env && env->subject && env->subject[0]){
8110 char *q = NULL;
8112 snprintf(buftmp, sizeof(buftmp), "%.75s", env->subject);
8113 buftmp[sizeof(buftmp)-1] = '\0';
8114 q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
8115 SIZEOF_20KBUF, buftmp);
8116 if(q != env->subject){
8117 snprintf(savedsstring, sizeof(savedsstring), "%.70s", q);
8118 savedsstring[sizeof(savedsstring)-1] = '\0';
8121 snprintf(sstring, sizeof(sstring), "%s", q);
8122 sstring[sizeof(sstring)-1] = '\0';
8125 continue;
8127 case 30 :
8128 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8129 if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){
8130 strncpy(sstring, p, sizeof(sstring));
8131 sstring[sizeof(sstring)-1] = '\0';
8132 if(history->hist[history->curindex]){
8133 flagsforhist = history->hist[history->curindex]->flags;
8134 not = (flagsforhist & 0x1) ? 1 : 0;
8135 me = (flagsforhist & 0x2) ? 1 : 0;
8138 else
8139 Writechar(BELL, 0);
8141 continue;
8143 case 31 :
8144 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8145 if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){
8146 strncpy(sstring, p, sizeof(sstring));
8147 sstring[sizeof(sstring)-1] = '\0';
8148 if(history->hist[history->curindex]){
8149 flagsforhist = history->hist[history->curindex]->flags;
8150 not = (flagsforhist & 0x1) ? 1 : 0;
8151 me = (flagsforhist & 0x2) ? 1 : 0;
8154 else
8155 Writechar(BELL, 0);
8157 continue;
8159 default :
8160 break;
8163 if(r == 1 || sstring[0] == '\0')
8164 r = 'x';
8166 break;
8170 if(type == 'x' || r == 'x'){
8171 cmd_cancelled("Selection by text");
8172 return(1);
8175 if(ps_global && ps_global->ttyo){
8176 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8177 ps_global->mangled_footer = 1;
8180 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8182 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8183 save_hist(history, sstring, flagsforhist, NULL);
8185 rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch);
8186 if(we_cancel)
8187 cancel_busy_cue(0);
8189 return(rv);
8194 * Select by message size.
8195 * Sets searched bits in mail_elts
8197 * Args limitsrch -- limit search to this searchset
8199 * Returns 0 on success.
8202 select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch)
8204 int r, large = 1, we_cancel = 0;
8205 unsigned long n, mult = 1L, numerator = 0L, divisor = 1L;
8206 char size[16], numbers[80], *p, *t;
8207 HelpType help;
8208 SEARCHPGM *pgm;
8209 long flags = (SE_NOPREFETCH | SE_FREE);
8211 numbers[0] = '\0';
8212 ps_global->mangled_footer = 1;
8214 help = NO_HELP;
8215 while(1){
8216 int flgs = OE_APPEND_CURRENT;
8218 sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger;
8220 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
8221 sizeof(numbers), large ? _(select_size_larger_msg)
8222 : _(select_size_smaller_msg),
8223 sel_size_opt, help, &flgs);
8224 if(r == 4)
8225 continue;
8227 if(r == 14){
8228 large = 1 - large;
8229 continue;
8232 if(r == 3){
8233 help = (help == NO_HELP) ? (large ? h_select_by_larger_size
8234 : h_select_by_smaller_size)
8235 : NO_HELP;
8236 continue;
8239 for(t = p = numbers; *p ; p++) /* strip whitespace */
8240 if(!isspace((unsigned char)*p))
8241 *t++ = *p;
8243 *t = '\0';
8245 if(r == 1 || numbers[0] == '\0'){
8246 cmd_cancelled("Selection by size");
8247 return(1);
8249 else
8250 break;
8253 if(numbers[0] == '-'){
8254 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8255 _("Invalid size entered: %s"), numbers);
8256 return(1);
8259 t = size;
8260 p = numbers;
8262 while(*p && isdigit((unsigned char)*p))
8263 *t++ = *p++;
8265 *t = '\0';
8267 if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){
8268 size[0] = '0';
8269 size[1] = '\0';
8272 if(size[0] == '\0'){
8273 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8274 _("Invalid size entered: %s"), numbers);
8275 return(1);
8278 n = strtoul(size, (char **)NULL, 10);
8280 size[0] = '\0';
8281 if(*p == '.'){
8283 * We probably ought to just use atof() to convert 1.1 into a
8284 * double, but since we haven't used atof() anywhere else I'm
8285 * reluctant to use it because of portability concerns.
8287 p++;
8288 t = size;
8289 while(*p && isdigit((unsigned char)*p)){
8290 *t++ = *p++;
8291 divisor *= 10;
8294 *t = '\0';
8296 if(size[0])
8297 numerator = strtoul(size, (char **)NULL, 10);
8300 switch(*p){
8301 case 'g':
8302 case 'G':
8303 mult *= 1000;
8304 /* fall through */
8306 case 'm':
8307 case 'M':
8308 mult *= 1000;
8309 /* fall through */
8311 case 'k':
8312 case 'K':
8313 mult *= 1000;
8314 break;
8317 n = n * mult + (numerator * mult) / divisor;
8319 pgm = mail_newsearchpgm();
8320 if(large)
8321 pgm->larger = n;
8322 else
8323 pgm->smaller = n;
8325 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8326 flags |= SE_NOSERVER;
8328 if(ps_global && ps_global->ttyo){
8329 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8330 ps_global->mangled_footer = 1;
8333 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8335 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8336 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8337 /* we know this was freed in mail_search, let caller know */
8338 if(limitsrch)
8339 *limitsrch = NULL;
8341 if(we_cancel)
8342 cancel_busy_cue(0);
8344 return(0);
8349 * visible_searchset -- return c-client search set unEXLDed
8350 * sequence numbers
8352 SEARCHSET *
8353 visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap)
8355 long n, run;
8356 SEARCHSET *full_set = NULL, **set;
8359 * If we're talking to anything other than a server older than
8360 * imap 4rev1, build a searchset otherwise it'll choke.
8362 if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
8363 if(any_lflagged(msgmap, MN_EXLD)){
8364 for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
8365 if(get_lflag(stream, NULL, n, MN_EXLD)){
8366 if(run){ /* previous NOT excluded? */
8367 if(run > 1L)
8368 (*set)->last = n - 1L;
8370 set = &(*set)->next;
8371 run = 0L;
8374 else if(run++){ /* next in run */
8375 (*set)->last = n;
8377 else{ /* start of run */
8378 *set = mail_newsearchset();
8379 (*set)->first = n;
8382 else{
8383 full_set = mail_newsearchset();
8384 full_set->first = 1L;
8385 full_set->last = stream->nmsgs;
8389 return(full_set);
8394 * Select by message status bits.
8395 * Sets searched bits in mail_elts
8397 * Args limitsrch -- limit search to this searchset
8399 * Returns 0 on success.
8402 select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch)
8404 int s, not = 0, we_cancel = 0, rv;
8406 while(1){
8407 s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag),
8408 -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x',
8409 NO_HELP, RB_NORM|RB_RET_HELP);
8411 if(s == 'x'){
8412 cmd_cancelled("Selection by status");
8413 return(1);
8415 else if(s == 3){
8416 helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"),
8417 HLPD_SIMPLE);
8418 ps_global->mangled_screen = 1;
8420 else if(s == '!')
8421 not = !not;
8422 else
8423 break;
8426 if(ps_global && ps_global->ttyo){
8427 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8428 ps_global->mangled_footer = 1;
8431 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8432 rv = agg_flag_select(stream, not, s, limitsrch);
8433 if(we_cancel)
8434 cancel_busy_cue(0);
8436 return(rv);
8441 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8442 * Sets searched bits in mail_elts
8444 * Args limitsrch -- limit search to this searchset
8446 * Returns 0 on success.
8449 select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
8451 char rulenick[1000], *nick;
8452 PATGRP_S *patgrp;
8453 int r, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
8454 | ROLE_DO_INCOLS
8455 | ROLE_DO_ROLES
8456 | ROLE_DO_SCORES
8457 | ROLE_DO_OTHER
8458 | ROLE_DO_FILTER;
8460 rulenick[0] = '\0';
8461 ps_global->mangled_footer = 1;
8464 int oe_flags;
8466 oe_flags = OE_APPEND_CURRENT;
8467 r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0,
8468 sizeof(rulenick),
8469 not ? _("Rule to NOT match: ")
8470 : _("Rule to match: "),
8471 sel_key_opt, NO_HELP, &oe_flags);
8473 if(r == 14){
8474 /* select rulenick from a list */
8475 if((nick=choose_a_rule(rflags)) != NULL){
8476 strncpy(rulenick, nick, sizeof(rulenick)-1);
8477 rulenick[sizeof(rulenick)-1] = '\0';
8478 fs_give((void **) &nick);
8480 else
8481 r = 4;
8483 else if(r == '!')
8484 not = !not;
8486 if(r == 3){
8487 helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE);
8488 ps_global->mangled_screen = 1;
8490 else if(r == 1){
8491 cmd_cancelled("Selection by Rule");
8492 return(1);
8495 removing_leading_and_trailing_white_space(rulenick);
8497 }while(r == 3 || r == 4 || r == '!');
8501 * The approach of requiring a nickname instead of just allowing the
8502 * user to select from the list of rules has the drawback that a rule
8503 * may not have a nickname, or there may be more than one rule with
8504 * the same nickname. However, it has the benefit of allowing the user
8505 * to type in the nickname and, most importantly, allows us to set
8506 * up the ! (not). We could incorporate the ! into the selection
8507 * screen, but this is easier and also allows the typing of nicks.
8508 * User can just set up nicknames if they want to use this feature.
8510 patgrp = nick_to_patgrp(rulenick, rflags);
8512 if(patgrp){
8513 if(ps_global && ps_global->ttyo){
8514 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8515 ps_global->mangled_footer = 1;
8518 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8519 match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL,
8520 get_msg_score,
8521 (not ? MP_NOT : 0) | SE_NOPREFETCH);
8522 free_patgrp(&patgrp);
8523 if(we_cancel)
8524 cancel_busy_cue(0);
8527 if(limitsrch && *limitsrch){
8528 mail_free_searchset(limitsrch);
8529 *limitsrch = NULL;
8532 return(0);
8537 * Allow user to choose a rule from their list of rules.
8539 * Returns an allocated rule nickname on success, NULL otherwise.
8541 char *
8542 choose_a_rule(int rflags)
8544 char *choice = NULL;
8545 char **rule_list, **lp;
8546 int cnt = 0;
8547 PAT_S *pat;
8548 PAT_STATE pstate;
8550 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){
8551 q_status_message(SM_ORDER, 3, 3,
8552 _("No rules available. Use Setup/Rules to add some."));
8553 return(choice);
8557 * Build a list of rules to choose from.
8560 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8561 cnt++;
8563 if(cnt <= 0){
8564 q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
8565 return(choice);
8568 lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
8569 memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
8571 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8572 *lp++ = cpystr((pat->patgrp && pat->patgrp->nick)
8573 ? pat->patgrp->nick : "?");
8575 /* TRANSLATORS: SELECT A RULE is a screen title
8576 TRANSLATORS: Print something1 using something2.
8577 "rules" is something1 */
8578 choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"),
8579 _("rules"), h_select_rule_screen,
8580 _("HELP FOR SELECTING A RULE NICKNAME"), NULL);
8582 if(!choice)
8583 q_status_message(SM_ORDER, 1, 4, "No choice");
8585 free_list_array(&rule_list);
8587 return(choice);
8592 * Select by current thread.
8593 * Sets searched bits in mail_elts for this entire thread
8595 * Args limitsrch -- limit search to this searchset
8597 * Returns 0 on success.
8600 select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
8602 long n;
8603 PINETHRD_S *thrd = NULL;
8604 int ret = 1;
8605 MESSAGECACHE *mc;
8607 if(!stream)
8608 return(ret);
8610 for(n = 1L; n <= stream->nmsgs; n++)
8611 if((mc = mail_elt(stream, n)) != NULL)
8612 mc->searched = 0; /* clear searched bits */
8614 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
8615 if(thrd && thrd->top && thrd->top != thrd->rawno)
8616 thrd = fetch_thread(stream, thrd->top);
8619 * This doesn't unselect if the thread is already selected
8620 * (like select current does), it always selects.
8621 * There is no way to select ! this thread.
8623 if(thrd){
8624 set_search_bit_for_thread(stream, thrd, limitsrch);
8625 ret = 0;
8628 return(ret);
8633 * Select by message keywords.
8634 * Sets searched bits in mail_elts
8636 * Args limitsrch -- limit search to this searchset
8638 * Returns 0 on success.
8641 select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
8643 int r, not = 0, we_cancel = 0;
8644 char keyword[MAXUSERFLAG+1], *kword;
8645 char *error = NULL, *p, *prompt;
8646 HelpType help;
8647 SEARCHPGM *pgm;
8649 keyword[0] = '\0';
8650 ps_global->mangled_footer = 1;
8652 help = NO_HELP;
8654 int oe_flags;
8656 if(error){
8657 q_status_message(SM_ORDER, 3, 4, error);
8658 fs_give((void **) &error);
8661 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
8662 if(not)
8663 prompt = _("Keyword (or keyword initial) to NOT match: ");
8664 else
8665 prompt = _("Keyword (or keyword initial) to match: ");
8667 else{
8668 if(not)
8669 prompt = _("Keyword to NOT match: ");
8670 else
8671 prompt = _("Keyword to match: ");
8674 oe_flags = OE_APPEND_CURRENT;
8675 r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0,
8676 sizeof(keyword),
8677 prompt, sel_key_opt, help, &oe_flags);
8679 if(r == 14){
8680 /* select keyword from a list */
8681 if((kword=choose_a_keyword()) != NULL){
8682 strncpy(keyword, kword, sizeof(keyword)-1);
8683 keyword[sizeof(keyword)-1] = '\0';
8684 fs_give((void **) &kword);
8686 else
8687 r = 4;
8689 else if(r == '!')
8690 not = !not;
8692 if(r == 3)
8693 help = help == NO_HELP ? h_select_keyword : NO_HELP;
8694 else if(r == 1){
8695 cmd_cancelled("Selection by keyword");
8696 return(1);
8699 removing_leading_and_trailing_white_space(keyword);
8701 }while(r == 3 || r == 4 || r == '!' || keyword_check(keyword, &error));
8704 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
8705 p = initial_to_keyword(keyword);
8706 if(p != keyword){
8707 strncpy(keyword, p, sizeof(keyword)-1);
8708 keyword[sizeof(keyword)-1] = '\0';
8713 * We want to check the keyword, not the nickname of the keyword,
8714 * so convert it to the keyword if necessary.
8716 p = nick_to_keyword(keyword);
8717 if(p != keyword){
8718 strncpy(keyword, p, sizeof(keyword)-1);
8719 keyword[sizeof(keyword)-1] = '\0';
8722 pgm = mail_newsearchpgm();
8723 if(not){
8724 pgm->unkeyword = mail_newstringlist();
8725 pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword);
8726 pgm->unkeyword->text.size = strlen(keyword);
8728 else{
8729 pgm->keyword = mail_newstringlist();
8730 pgm->keyword->text.data = (unsigned char *) cpystr(keyword);
8731 pgm->keyword->text.size = strlen(keyword);
8734 if(ps_global && ps_global->ttyo){
8735 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8736 ps_global->mangled_footer = 1;
8739 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8741 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8742 pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE);
8743 /* we know this was freed in mail_search, let caller know */
8744 if(limitsrch)
8745 *limitsrch = NULL;
8747 if(we_cancel)
8748 cancel_busy_cue(0);
8750 return(0);
8755 * Allow user to choose a keyword from their list of keywords.
8757 * Returns an allocated keyword on success, NULL otherwise.
8759 char *
8760 choose_a_keyword(void)
8762 char *choice = NULL;
8763 char **keyword_list, **lp;
8764 int cnt;
8765 KEYWORD_S *kw;
8768 * Build a list of keywords to choose from.
8771 for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next)
8772 cnt++;
8774 if(cnt <= 0){
8775 q_status_message(SM_ORDER, 3, 4,
8776 _("No keywords defined, use \"keywords\" option in Setup/Config"));
8777 return(choice);
8780 lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list));
8781 memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list));
8783 for(kw = ps_global->keywords; kw; kw = kw->next)
8784 *lp++ = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
8786 /* TRANSLATORS: SELECT A KEYWORD is a screen title
8787 TRANSLATORS: Print something1 using something2.
8788 "keywords" is something1 */
8789 choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"),
8790 _("keywords"), h_select_keyword_screen,
8791 _("HELP FOR SELECTING A KEYWORD"), NULL);
8793 if(!choice)
8794 q_status_message(SM_ORDER, 1, 4, "No choice");
8796 free_list_array(&keyword_list);
8798 return(choice);
8803 * Allow user to choose a list of keywords from their list of keywords.
8805 * Returns allocated list.
8807 char **
8808 choose_list_of_keywords(void)
8810 LIST_SEL_S *listhead, *ls, *p;
8811 char **ret = NULL;
8812 int cnt, i;
8813 KEYWORD_S *kw;
8816 * Build a list of keywords to choose from.
8819 p = listhead = NULL;
8820 for(kw = ps_global->keywords; kw; kw = kw->next){
8822 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
8823 memset(ls, 0, sizeof(*ls));
8824 ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
8826 if(p){
8827 p->next = ls;
8828 p = p->next;
8830 else
8831 listhead = p = ls;
8834 if(!listhead)
8835 return(ret);
8837 /* TRANSLATORS: SELECT KEYWORDS is a screen title
8838 Print something1 using something2.
8839 "keywords" is something1 */
8840 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
8841 _("SELECT KEYWORDS"), _("keywords"),
8842 h_select_multkeyword_screen,
8843 _("HELP FOR SELECTING KEYWORDS"), NULL)){
8844 for(cnt = 0, p = listhead; p; p = p->next)
8845 if(p->selected)
8846 cnt++;
8848 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
8849 memset(ret, 0, (cnt+1) * sizeof(*ret));
8850 for(i = 0, p = listhead; p; p = p->next)
8851 if(p->selected)
8852 ret[i++] = cpystr(p->item ? p->item : "");
8855 free_list_sel(&listhead);
8857 return(ret);
8862 * Allow user to choose a charset
8864 * Returns an allocated charset on success, NULL otherwise.
8866 char *
8867 choose_a_charset(int which_charsets)
8869 char *choice = NULL;
8870 char **charset_list, **lp;
8871 const CHARSET *cs;
8872 int cnt;
8875 * Build a list of charsets to choose from.
8878 for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){
8879 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
8880 && ((which_charsets & CAC_ALL)
8881 || (which_charsets & CAC_POSTING
8882 && cs->flags & CF_POSTING)
8883 || (which_charsets & CAC_DISPLAY
8884 && cs->type != CT_2022
8885 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
8886 cnt++;
8889 if(cnt <= 0){
8890 q_status_message(SM_ORDER, 3, 4,
8891 _("No charsets found? Enter charset manually."));
8892 return(choice);
8895 lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list));
8896 memset(charset_list, 0, (cnt+1) * sizeof(*charset_list));
8898 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
8899 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
8900 && ((which_charsets & CAC_ALL)
8901 || (which_charsets & CAC_POSTING
8902 && cs->flags & CF_POSTING)
8903 || (which_charsets & CAC_DISPLAY
8904 && cs->type != CT_2022
8905 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
8906 *lp++ = cpystr(cs->name);
8909 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
8910 TRANSLATORS: Print something1 using something2.
8911 "character sets" is something1 */
8912 choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"),
8913 _("character sets"), h_select_charset_screen,
8914 _("HELP FOR SELECTING A CHARACTER SET"), NULL);
8916 if(!choice)
8917 q_status_message(SM_ORDER, 1, 4, "No choice");
8919 free_list_array(&charset_list);
8921 return(choice);
8926 * Allow user to choose a list of character sets and/or scripts
8928 * Returns allocated list.
8930 char **
8931 choose_list_of_charsets(void)
8933 LIST_SEL_S *listhead, *ls, *p;
8934 char **ret = NULL;
8935 int cnt, i, got_one;
8936 const CHARSET *cs;
8937 SCRIPT *s;
8938 char *q, *t;
8939 long width, limit;
8940 char buf[1024], *folded;
8943 * Build a list of charsets to choose from.
8946 p = listhead = NULL;
8948 /* this width is determined by select_from_list_screen() */
8949 width = ps_global->ttyo->screen_cols - 4;
8951 /* first comes a list of scripts (sets of character sets) */
8952 for(s = utf8_script(NIL); s && s->name; s++){
8954 limit = sizeof(buf)-1;
8955 q = buf;
8956 memset(q, 0, limit+1);
8958 if(s->name)
8959 sstrncpy(&q, s->name, limit);
8961 if(s->description){
8962 sstrncpy(&q, " (", limit-(q-buf));
8963 sstrncpy(&q, s->description, limit-(q-buf));
8964 sstrncpy(&q, ")", limit-(q-buf));
8967 /* add the list of charsets that are in this script */
8968 got_one = 0;
8969 for(cs = utf8_charset(NIL);
8970 cs && cs->name && (q-buf) < limit; cs++){
8971 if(cs->script & s->script){
8973 * Filter out some un-useful members of the list.
8974 * UTF-7 and UTF-8 weren't actually in the list at the
8975 * time this was written. Just making sure.
8977 if(!strucmp(cs->name, "ISO-2022-JP-2")
8978 || !strucmp(cs->name, "UTF-7")
8979 || !strucmp(cs->name, "UTF-8"))
8980 continue;
8982 if(got_one)
8983 sstrncpy(&q, " ", limit-(q-buf));
8984 else{
8985 got_one = 1;
8986 sstrncpy(&q, " {", limit-(q-buf));
8989 sstrncpy(&q, cs->name, limit-(q-buf));
8993 if(got_one)
8994 sstrncpy(&q, "}", limit-(q-buf));
8996 /* fold this line so that it can all be seen on the screen */
8997 folded = fold(buf, width, width, "", " ", FLD_NONE);
8998 if(folded){
8999 t = folded;
9000 while(t && *t && (q = strindex(t, '\n')) != NULL){
9001 *q = '\0';
9003 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9004 memset(ls, 0, sizeof(*ls));
9005 if(t == folded)
9006 ls->item = cpystr(s->name);
9007 else
9008 ls->flags = SFL_NOSELECT;
9010 ls->display_item = cpystr(t);
9012 t = q+1;
9014 if(p){
9015 p->next = ls;
9016 p = p->next;
9018 else{
9019 /* add a heading */
9020 listhead = (LIST_SEL_S *) fs_get(sizeof(*ls));
9021 memset(listhead, 0, sizeof(*listhead));
9022 listhead->flags = SFL_NOSELECT;
9023 listhead->display_item =
9024 cpystr(_("Scripts representing groups of related character sets"));
9025 listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls));
9026 memset(listhead->next, 0, sizeof(*listhead));
9027 listhead->next->flags = SFL_NOSELECT;
9028 listhead->next->display_item =
9029 cpystr(repeat_char(width, '-'));
9031 listhead->next->next = ls;
9032 p = ls;
9036 fs_give((void **) &folded);
9040 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9041 memset(ls, 0, sizeof(*ls));
9042 ls->flags = SFL_NOSELECT;
9043 if(p){
9044 p->next = ls;
9045 p = p->next;
9047 else
9048 listhead = p = ls;
9050 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9051 memset(ls, 0, sizeof(*ls));
9052 ls->flags = SFL_NOSELECT;
9053 ls->display_item =
9054 cpystr(_("Individual character sets, may be mixed with scripts"));
9055 p->next = ls;
9056 p = p->next;
9058 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9059 memset(ls, 0, sizeof(*ls));
9060 ls->flags = SFL_NOSELECT;
9061 ls->display_item =
9062 cpystr(repeat_char(width, '-'));
9063 p->next = ls;
9064 p = p->next;
9066 /* then comes a list of individual character sets */
9067 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9068 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9069 memset(ls, 0, sizeof(*ls));
9070 ls->item = cpystr(cs->name);
9072 if(p){
9073 p->next = ls;
9074 p = p->next;
9076 else
9077 listhead = p = ls;
9080 if(!listhead)
9081 return(ret);
9083 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9084 Print something1 using something2.
9085 "character sets" is something1 */
9086 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9087 _("SELECT CHARACTER SETS"), _("character sets"),
9088 h_select_multcharsets_screen,
9089 _("HELP FOR SELECTING CHARACTER SETS"), NULL)){
9090 for(cnt = 0, p = listhead; p; p = p->next)
9091 if(p->selected)
9092 cnt++;
9094 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9095 memset(ret, 0, (cnt+1) * sizeof(*ret));
9096 for(i = 0, p = listhead; p; p = p->next)
9097 if(p->selected)
9098 ret[i++] = cpystr(p->item ? p->item : "");
9101 free_list_sel(&listhead);
9103 return(ret);
9106 /* Report quota summary resources in an IMAP server */
9108 void cmd_quota (struct pine *state)
9110 QUOTALIST *imapquota;
9111 NETMBX mb;
9112 STORE_S *store;
9113 SCROLL_S sargs;
9115 if(!state->mail_stream || !is_imap_stream(state->mail_stream)){
9116 q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders");
9117 return;
9120 if (state->mail_stream
9121 && !sp_dead_stream(state->mail_stream)
9122 && state->mail_stream->mailbox
9123 && *state->mail_stream->mailbox
9124 && mail_valid_net_parse(state->mail_stream->mailbox, &mb))
9125 imap_getquotaroot(state->mail_stream, mb.mailbox);
9127 if(!state->quota) /* failed ? */
9128 return; /* go back... */
9130 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
9131 q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space.");
9132 return;
9135 so_puts(store, "Quota Report for ");
9136 so_puts(store, state->mail_stream->original_mailbox);
9137 so_puts(store, "\n\n");
9139 for (imapquota = state->quota; imapquota; imapquota = imapquota->next){
9141 so_puts(store, _("Resource : "));
9142 so_puts(store, imapquota->name);
9143 so_writec('\n', store);
9145 so_puts(store, _("Usage : "));
9146 so_puts(store, long2string(imapquota->usage));
9147 if(!strucmp(imapquota->name,"STORAGE"))
9148 so_puts(store, " KiB ");
9149 if(!strucmp(imapquota->name,"MESSAGE")){
9150 so_puts(store, _(" message"));
9151 if(imapquota->usage != 1)
9152 so_puts(store, _("s ")); /* plural */
9153 else
9154 so_puts(store, _(" "));
9156 so_writec('(', store);
9157 so_puts(store, long2string(100*imapquota->usage/imapquota->limit));
9158 so_puts(store, "%)\n");
9160 so_puts(store, _("Limit : "));
9161 so_puts(store, long2string(imapquota->limit));
9162 if(!strucmp(imapquota->name,"STORAGE"))
9163 so_puts(store, " KiB\n\n");
9164 if(!strucmp(imapquota->name,"MESSAGE")){
9165 so_puts(store, _(" message"));
9166 if(imapquota->usage != 1)
9167 so_puts(store, _("s\n\n")); /* plural */
9168 else
9169 so_puts(store, _("\n\n"));
9173 memset(&sargs, 0, sizeof(SCROLL_S));
9174 sargs.text.text = so_text(store);
9175 sargs.text.src = CharStar;
9176 sargs.text.desc = _("Quota Resources Summary");
9177 sargs.bar.title = _("QUOTA SUMMARY");
9178 sargs.proc.tool = NULL;
9179 sargs.help.text = h_quota_command;
9180 sargs.help.title = NULL;
9181 sargs.keys.menu = NULL;
9182 setbitmap(sargs.keys.bitmap);
9184 scrolltool(&sargs);
9185 so_give(&store);
9187 if (state->quota)
9188 mail_free_quotalist(&(state->quota));
9191 /*----------------------------------------------------------------------
9192 Prompt the user for the type of sort he desires
9194 Args: state -- pine state pointer
9195 q1 -- Line to prompt on
9197 Returns 0 if it was cancelled, 1 otherwise.
9198 ----*/
9200 select_sort(struct pine *state, int ql, SortOrder *sort, int *rev)
9202 char prompt[200], tmp[3], *p;
9203 int s, i;
9204 int deefault = 'a', retval = 1;
9205 HelpType help;
9206 ESCKEY_S sorts[14];
9208 #ifdef _WINDOWS
9209 DLG_SORTPARAM sortsel;
9211 if (mswin_usedialog ()) {
9213 sortsel.reverse = mn_get_revsort (state->msgmap);
9214 sortsel.cursort = mn_get_sort (state->msgmap);
9215 /* assumption here that HelpType is char ** */
9216 sortsel.helptext = h_select_sort;
9217 sortsel.rval = 0;
9219 if ((retval = os_sortdialog (&sortsel))) {
9220 *sort = sortsel.cursort;
9221 *rev = sortsel.reverse;
9224 return (retval);
9226 #endif
9228 /*----- String together the prompt ------*/
9229 tmp[1] = '\0';
9230 if(F_ON(F_USE_FK,ps_global))
9231 strncpy(prompt, _("Choose type of sort : "), sizeof(prompt));
9232 else
9233 strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "),
9234 sizeof(prompt));
9236 for(i = 0; state->sort_types[i] != EndofList; i++) {
9237 sorts[i].rval = i;
9238 p = sorts[i].label = sort_name(state->sort_types[i]);
9239 while(*(p+1) && islower((unsigned char)*p))
9240 p++;
9242 sorts[i].ch = tolower((unsigned char)(tmp[0] = *p));
9243 sorts[i].name = cpystr(tmp);
9245 if(mn_get_sort(state->msgmap) == state->sort_types[i])
9246 deefault = sorts[i].rval;
9249 sorts[i].ch = 'r';
9250 sorts[i].rval = 'r';
9251 sorts[i].name = cpystr("R");
9252 if(F_ON(F_USE_FK,ps_global))
9253 sorts[i].label = N_("Reverse");
9254 else
9255 sorts[i].label = "";
9257 sorts[++i].ch = -1;
9258 help = h_select_sort;
9260 if((F_ON(F_USE_FK,ps_global)
9261 && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x',
9262 help,RB_NORM)) != 'x'))
9264 (F_OFF(F_USE_FK,ps_global)
9265 && ((s = radio_buttons(prompt,ql,sorts,deefault,'x',
9266 help,RB_NORM)) != 'x'))){
9267 state->mangled_body = 1; /* signal screen's changed */
9268 if(s == 'r')
9269 *rev = !mn_get_revsort(state->msgmap);
9270 else
9271 *sort = state->sort_types[s];
9273 if(F_ON(F_SHOW_SORT, ps_global))
9274 ps_global->mangled_header = 1;
9276 else{
9277 retval = 0;
9278 cmd_cancelled("Sort");
9281 while(--i >= 0)
9282 fs_give((void **)&sorts[i].name);
9284 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9285 return(retval);
9289 /*---------------------------------------------------------------------
9290 Build list of folders in the given context for user selection
9292 Args: c -- pointer to pointer to folder's context context
9293 f -- folder prefix to display
9294 sublist -- whether or not to use 'f's contents as prefix
9295 lister -- function used to do the actual display
9297 Returns: malloc'd string containing sequence, else NULL if
9298 no messages in msgmap with local "selected" flag.
9299 ----*/
9301 display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int))
9303 int rc;
9304 CONTEXT_S *tc;
9305 void (*redraw)(void) = ps_global->redrawer;
9307 push_titlebar_state();
9308 tc = *c;
9309 if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0)
9310 *c = tc;
9312 ClearScreen();
9313 pop_titlebar_state();
9314 redraw_titlebar();
9315 if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
9316 (*ps_global->redrawer)();
9318 if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
9319 return(1);
9321 return(0);
9326 * Allow user to choose a single item from a list of strings.
9328 * Args list -- Array of strings to choose from, NULL terminated.
9329 * displist -- Array of strings to display instead of displaying list.
9330 * Indices correspond to the list array. Display the displist
9331 * but return the item from list if displist non-NULL.
9332 * title -- For conf_scroll_screen
9333 * pdesc -- For conf_scroll_screen
9334 * help -- For conf_scroll_screen
9335 * htitle -- For conf_scroll_screen
9337 * Returns an allocated copy of the chosen item or NULL.
9339 char *
9340 choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help,
9341 char *htitle, char *cursor_location)
9343 LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL;
9344 char **t, **dl;
9345 char *ret = NULL, *choice = NULL;
9347 /* build the LIST_SEL_S list */
9348 p = listhead = NULL;
9349 for(t = list, dl = displist; *t; t++, dl++){
9350 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9351 memset(ls, 0, sizeof(*ls));
9352 ls->item = cpystr(*t);
9353 if(displist)
9354 ls->display_item = cpystr(*dl);
9356 if(cursor_location && (cursor_location == (*t)))
9357 starting_val = ls;
9359 if(p){
9360 p->next = ls;
9361 p = p->next;
9363 else
9364 listhead = p = ls;
9367 if(!listhead)
9368 return(ret);
9370 if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc,
9371 help, htitle, starting_val))
9372 for(p = listhead; !choice && p; p = p->next)
9373 if(p->selected)
9374 choice = p->item;
9376 if(choice)
9377 ret = cpystr(choice);
9379 free_list_sel(&listhead);
9381 return(ret);
9385 void
9386 free_list_sel(LIST_SEL_S **lsel)
9388 if(lsel && *lsel){
9389 free_list_sel(&(*lsel)->next);
9390 if((*lsel)->item)
9391 fs_give((void **) &(*lsel)->item);
9393 if((*lsel)->display_item)
9394 fs_give((void **) &(*lsel)->display_item);
9396 fs_give((void **) lsel);
9402 * file_lister - call pico library's file lister
9405 file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags)
9407 PICO pbf;
9408 int rv;
9409 void (*redraw)(void) = ps_global->redrawer;
9411 standard_picobuf_setup(&pbf);
9412 push_titlebar_state();
9413 if(!newmail)
9414 pbf.newmail = NULL;
9416 /* BUG: what about help command and text? */
9417 pbf.pine_anchor = title;
9419 rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags);
9420 standard_picobuf_teardown(&pbf);
9421 fix_windsize(ps_global);
9422 init_signals(); /* has it's own signal stuff */
9424 /* Restore display's titlebar and body */
9425 pop_titlebar_state();
9426 redraw_titlebar();
9427 if((ps_global->redrawer = redraw) != NULL)
9428 (*ps_global->redrawer)();
9430 return(rv);
9434 /*----------------------------------------------------------------------
9435 Print current folder index
9437 ---*/
9439 print_index(struct pine *state, MSGNO_S *msgmap, int agg)
9441 long i;
9442 ICE_S *ice;
9443 char buf[MAX_SCREEN_COLS+1];
9445 for(i = 1L; i <= mn_get_total(msgmap); i++){
9446 if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
9447 continue;
9449 if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0))
9450 continue;
9452 ice = build_header_line(state, state->mail_stream, msgmap, i, NULL);
9454 if(ice){
9456 * I don't understand why we'd want to mark the current message
9457 * instead of printing out the first character of the status
9458 * so I'm taking it out and including the first character of the
9459 * line instead. Hubert 2006-02-09
9461 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
9462 return(0);
9465 if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i),
9466 print_char)
9467 || !gf_puts(NEWLINE, print_char))
9468 return(0);
9472 return(1);
9476 #ifdef _WINDOWS
9479 * windows callback to get/set header mode state
9482 header_mode_callback(set, args)
9483 int set;
9484 long args;
9486 return(ps_global->full_header);
9491 * windows callback to get/set zoom mode state
9494 zoom_mode_callback(set, args)
9495 int set;
9496 long args;
9498 return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0);
9503 * windows callback to get/set zoom mode state
9506 any_selected_callback(set, args)
9507 int set;
9508 long args;
9510 return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0);
9518 flag_callback(set, flags)
9519 int set;
9520 long flags;
9522 MESSAGECACHE *mc;
9523 int newflags = 0;
9524 long msgno;
9525 int permflag = 0;
9527 switch (set) {
9528 case 1: /* Important */
9529 permflag = ps_global->mail_stream->perm_flagged;
9530 break;
9532 case 2: /* New */
9533 permflag = ps_global->mail_stream->perm_seen;
9534 break;
9536 case 3: /* Answered */
9537 permflag = ps_global->mail_stream->perm_answered;
9538 break;
9540 case 4: /* Deleted */
9541 permflag = ps_global->mail_stream->perm_deleted;
9542 break;
9546 if(!(any_messages(ps_global->msgmap, NULL, "to Flag")
9547 && can_set_flag(ps_global, "flag", permflag)))
9548 return(0);
9550 if(sp_io_error_on_stream(ps_global->mail_stream)){
9551 sp_set_io_error_on_stream(ps_global->mail_stream, 0);
9552 pine_mail_check(ps_global->mail_stream); /* forces write */
9553 return(0);
9556 msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
9557 if(msgno > 0L && ps_global->mail_stream
9558 && msgno <= ps_global->mail_stream->nmsgs
9559 && (mc = mail_elt(ps_global->mail_stream, msgno))
9560 && mc->valid){
9562 * NOTE: code below is *VERY* sensitive to the order of
9563 * the messages defined in resource.h for flag handling.
9564 * Don't change it unless you know what you're doing.
9566 if(set){
9567 char *flagstr;
9568 long mflag;
9570 switch(set){
9571 case 1 : /* Important */
9572 flagstr = "\\FLAGGED";
9573 mflag = (mc->flagged) ? 0L : ST_SET;
9574 break;
9576 case 2 : /* New */
9577 flagstr = "\\SEEN";
9578 mflag = (mc->seen) ? 0L : ST_SET;
9579 break;
9581 case 3 : /* Answered */
9582 flagstr = "\\ANSWERED";
9583 mflag = (mc->answered) ? 0L : ST_SET;
9584 break;
9586 case 4 : /* Deleted */
9587 flagstr = "\\DELETED";
9588 mflag = (mc->deleted) ? 0L : ST_SET;
9589 break;
9591 default : /* bogus */
9592 return(0);
9595 mail_flag(ps_global->mail_stream, long2string(msgno),
9596 flagstr, mflag);
9598 if(ps_global->redrawer)
9599 (*ps_global->redrawer)();
9601 else{
9602 /* Important */
9603 if(mc->flagged)
9604 newflags |= 0x0001;
9606 /* New */
9607 if(!mc->seen)
9608 newflags |= 0x0002;
9610 /* Answered */
9611 if(mc->answered)
9612 newflags |= 0x0004;
9614 /* Deleted */
9615 if(mc->deleted)
9616 newflags |= 0x0008;
9620 return(newflags);
9626 * BUG: Should teach this about keywords
9628 MPopup *
9629 flag_submenu(mc)
9630 MESSAGECACHE *mc;
9632 static MPopup flag_submenu[] = {
9633 {tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}},
9634 {tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}},
9635 {tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}},
9636 {tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}},
9637 {tTail}
9640 /* Important */
9641 flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal;
9643 /* New */
9644 flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked;
9646 /* Answered */
9647 flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal;
9649 /* Deleted */
9650 flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal;
9652 return(flag_submenu);
9655 #endif /* _WINDOWS */