* Nothing to see here, this is just reformatting of the code to
[alpine.git] / alpine / mailcmd.c
blobb741261b0981848f17ee56745f045f3f52e209e0
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-2018 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, ACTION_S *);
87 int cmd_forward(struct pine *, MSGNO_S *, int, ACTION_S *);
88 int cmd_bounce(struct pine *, MSGNO_S *, int, ACTION_S *);
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}
306 alpine_smime_confirm_save(char *email)
308 char prompt[128];
310 snprintf(prompt, sizeof(prompt), _("Save certificate for <%s>"),
311 email ? email : _("missing address"));
312 return want_to(prompt, 'n', 'x', NO_HELP, WT_NORM) == 'y';
315 int
316 alpine_get_password(char *prompt, char *pass, size_t len)
318 int flags = OE_PASSWD | OE_DISALLOW_HELP;
319 pass[0] = '\0';
320 return optionally_enter(pass,
321 -(ps_global->ttyo ? FOOTER_ROWS(ps_global) : 3),
322 0, len, prompt, NULL, NO_HELP, &flags);
326 smime_import_certificate(char *filename, char *full_filename, char *what, size_t len)
328 int r = 1;
329 static HISTORY_S *history = NULL;
330 static ESCKEY_S eopts[] = {
331 {ctrl('T'), 10, "^T", N_("To Files")},
332 {-1, 0, NULL, NULL},
333 {-1, 0, NULL, NULL}};
335 /* special call to free history */
336 if(filename == NULL && full_filename == NULL && what == NULL && len == 0){
337 if(history != NULL)
338 free_hist(&history);
339 return 0;
342 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
343 eopts[r].ch = ctrl('I');
344 eopts[r].rval = 11;
345 eopts[r].name = "TAB";
346 eopts[r].label = N_("Complete");
349 eopts[++r].ch = -1;
351 filename[0] = '\0';
352 full_filename[0] = '\0';
354 r = get_export_filename(ps_global, filename, NULL, full_filename,
355 len, what, "IMPORT", eopts, NULL,
356 -FOOTER_ROWS(ps_global), GE_IS_IMPORT, &history);
358 return r;
362 /*----------------------------------------------------------------------
363 The giant switch on the commands for index and viewing
365 Input: command -- The command char/code
366 in_index -- flag indicating command is from index
367 orig_command -- The original command typed before pre-processing
368 Output: force_mailchk -- Set to tell caller to force call to new_mail().
370 Result: Manifold
372 Returns 1 if the message number or attachment to show changed
373 ---*/
375 process_cmd(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
376 int command, CmdWhere in_index, int *force_mailchk)
378 int question_line, a_changed, flags = 0, ret, j;
379 int notrealinbox;
380 long new_msgno, del_count, old_msgno, i;
381 long start;
382 char *newfolder, prompt[MAX_SCREEN_COLS+1];
383 CONTEXT_S *tc;
384 MESSAGECACHE *mc;
385 #if defined(DOS) && !defined(_WINDOWS)
386 extern long coreleft();
387 #endif
389 dprint((4, "\n - process_cmd(cmd=%d) -\n", command));
391 question_line = -FOOTER_ROWS(state);
392 state->mangled_screen = 0;
393 state->mangled_footer = 0;
394 state->mangled_header = 0;
395 state->next_screen = SCREEN_FUN_NULL;
396 old_msgno = mn_get_cur(msgmap);
397 a_changed = FALSE;
398 *force_mailchk = 0;
400 switch (command) {
401 /*------------- Help --------*/
402 case MC_HELP :
404 * We're not using the h_mail_view portion of this right now because
405 * that call is being handled in scrolltool() before it gets
406 * here. Leave it in case we change how it works.
408 helper((in_index == MsgIndx)
409 ? h_mail_index
410 : (in_index == View)
411 ? h_mail_view
412 : h_mail_thread_index,
413 (in_index == MsgIndx)
414 ? _("HELP FOR MESSAGE INDEX")
415 : (in_index == View)
416 ? _("HELP FOR MESSAGE TEXT")
417 : _("HELP FOR THREAD INDEX"),
418 HLPD_NONE);
419 dprint((4,"MAIL_CMD: did help command\n"));
420 state->mangled_screen = 1;
421 break;
424 /*--------- Return to main menu ------------*/
425 case MC_MAIN :
426 state->next_screen = main_menu_screen;
427 dprint((2,"MAIL_CMD: going back to main menu\n"));
428 break;
431 /*------- View message text --------*/
432 case MC_VIEW_TEXT :
433 view_text:
434 if(any_messages(msgmap, NULL, "to View")){
435 state->next_screen = mail_view_screen;
438 break;
441 /*------- View attachment --------*/
442 case MC_VIEW_ATCH :
443 state->next_screen = attachment_screen;
444 dprint((2,"MAIL_CMD: going to attachment screen\n"));
445 break;
448 /*---------- Previous message ----------*/
449 case MC_PREVITEM :
450 if(any_messages(msgmap, NULL, NULL)){
451 if((i = mn_get_cur(msgmap)) > 1L){
452 mn_dec_cur(stream, msgmap,
453 (in_index == View && THREADING()
454 && sp_viewing_a_thread(stream))
455 ? MH_THISTHD
456 : (in_index == View)
457 ? MH_ANYTHD : MH_NONE);
458 if(i == mn_get_cur(msgmap)){
459 PINETHRD_S *thrd = NULL, *topthrd = NULL;
461 if(THRD_INDX_ENABLED()){
462 mn_dec_cur(stream, msgmap, MH_ANYTHD);
463 if(i == mn_get_cur(msgmap))
464 q_status_message1(SM_ORDER, 0, 2,
465 _("Already on first %s in Zoomed Index"),
466 THRD_INDX() ? _("thread") : _("message"));
467 else{
468 if(in_index == View
469 || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
470 ret = 'y';
471 else
472 ret = want_to(_("View previous thread"), 'y', 'x',
473 NO_HELP, WT_NORM);
475 if(ret == 'y'){
476 q_status_message(SM_ORDER, 0, 2,
477 _("Viewing previous thread"));
478 new_msgno = mn_get_cur(msgmap);
479 mn_set_cur(msgmap, i);
480 if(unview_thread(state, stream, msgmap)){
481 state->next_screen = mail_index_screen;
482 state->view_skipped_index = 0;
483 state->mangled_screen = 1;
486 mn_set_cur(msgmap, new_msgno);
487 if(THRD_AUTO_VIEW() && in_index == View){
489 thrd = fetch_thread(stream,
490 mn_m2raw(msgmap,
491 new_msgno));
492 if(count_lflags_in_thread(stream, thrd,
493 msgmap,
494 MN_NONE) == 1){
495 if(view_thread(state, stream, msgmap, 1)){
496 if(current_index_state)
497 msgmap->top_after_thrd = current_index_state->msg_at_top;
499 state->view_skipped_index = 1;
500 command = MC_VIEW_TEXT;
501 goto view_text;
506 j = 0;
507 if(THRD_AUTO_VIEW() && in_index != View){
508 thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
509 if(thrd && thrd->top)
510 topthrd = fetch_thread(stream, thrd->top);
512 if(topthrd)
513 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
516 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
517 if(view_thread(state, stream, msgmap, 1)
518 && current_index_state)
519 msgmap->top_after_thrd = current_index_state->msg_at_top;
523 state->next_screen = SCREEN_FUN_NULL;
525 else
526 mn_set_cur(msgmap, i); /* put it back */
529 else
530 q_status_message1(SM_ORDER, 0, 2,
531 _("Already on first %s in Zoomed Index"),
532 THRD_INDX() ? _("thread") : _("message"));
535 else{
536 time_t now;
538 if(!IS_NEWS(stream)
539 && ((now = time(0)) > state->last_nextitem_forcechk)){
540 *force_mailchk = 1;
541 /* check at most once a second */
542 state->last_nextitem_forcechk = now;
545 q_status_message1(SM_ORDER, 0, 1, _("Already on first %s"),
546 THRD_INDX() ? _("thread") : _("message"));
550 break;
553 /*---------- Next Message ----------*/
554 case MC_NEXTITEM :
555 if(mn_get_total(msgmap) > 0L
556 && ((i = mn_get_cur(msgmap)) < mn_get_total(msgmap))){
557 mn_inc_cur(stream, msgmap,
558 (in_index == View && THREADING()
559 && sp_viewing_a_thread(stream))
560 ? MH_THISTHD
561 : (in_index == View)
562 ? MH_ANYTHD : MH_NONE);
563 if(i == mn_get_cur(msgmap)){
564 PINETHRD_S *thrd, *topthrd;
566 if(THRD_INDX_ENABLED()){
567 if(!THRD_INDX())
568 mn_inc_cur(stream, msgmap, MH_ANYTHD);
570 if(i == mn_get_cur(msgmap)){
571 if(any_lflagged(msgmap, MN_HIDE))
572 any_messages(NULL, "more", "in Zoomed Index");
573 else
574 goto nfolder;
576 else{
577 if(in_index == View
578 || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
579 ret = 'y';
580 else
581 ret = want_to(_("View next thread"), 'y', 'x',
582 NO_HELP, WT_NORM);
584 if(ret == 'y'){
585 q_status_message(SM_ORDER, 0, 2,
586 _("Viewing next thread"));
587 new_msgno = mn_get_cur(msgmap);
588 mn_set_cur(msgmap, i);
589 if(unview_thread(state, stream, msgmap)){
590 state->next_screen = mail_index_screen;
591 state->view_skipped_index = 0;
592 state->mangled_screen = 1;
595 mn_set_cur(msgmap, new_msgno);
596 if(THRD_AUTO_VIEW() && in_index == View){
598 thrd = fetch_thread(stream,
599 mn_m2raw(msgmap,
600 new_msgno));
601 if(count_lflags_in_thread(stream, thrd,
602 msgmap,
603 MN_NONE) == 1){
604 if(view_thread(state, stream, msgmap, 1)){
605 if(current_index_state)
606 msgmap->top_after_thrd = current_index_state->msg_at_top;
608 state->view_skipped_index = 1;
609 command = MC_VIEW_TEXT;
610 goto view_text;
615 j = 0;
616 if(THRD_AUTO_VIEW() && in_index != View){
617 thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
618 if(thrd && thrd->top)
619 topthrd = fetch_thread(stream, thrd->top);
621 if(topthrd)
622 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
625 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
626 if(view_thread(state, stream, msgmap, 1)
627 && current_index_state)
628 msgmap->top_after_thrd = current_index_state->msg_at_top;
632 state->next_screen = SCREEN_FUN_NULL;
634 else
635 mn_set_cur(msgmap, i); /* put it back */
638 else if(THREADING()
639 && (thrd = fetch_thread(stream, mn_m2raw(msgmap, i)))
640 && thrd->next
641 && get_lflag(stream, NULL, thrd->rawno, MN_COLL)){
642 q_status_message(SM_ORDER, 0, 2,
643 _("Expand collapsed thread to see more messages"));
645 else
646 any_messages(NULL, "more", "in Zoomed Index");
649 else{
650 time_t now;
651 nfolder:
652 prompt[0] = '\0';
653 if(IS_NEWS(stream)
654 || (state->context_current->use & CNTXT_INCMNG)){
655 char nextfolder[MAXPATH];
657 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
658 nextfolder[sizeof(nextfolder)-1] = '\0';
659 if(next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
660 state->context_current, NULL, NULL))
661 strncpy(prompt, _(". Press TAB for next folder."),
662 sizeof(prompt));
663 else
664 strncpy(prompt, _(". No more folders to TAB to."),
665 sizeof(prompt));
667 prompt[sizeof(prompt)-1] = '\0';
670 any_messages(NULL, (mn_get_total(msgmap) > 0L) ? "more" : NULL,
671 prompt[0] ? prompt : NULL);
673 if(!IS_NEWS(stream)
674 && ((now = time(0)) > state->last_nextitem_forcechk)){
675 *force_mailchk = 1;
676 /* check at most once a second */
677 state->last_nextitem_forcechk = now;
681 break;
684 /*---------- Delete message ----------*/
685 case MC_DELETE :
686 (void) cmd_delete(state, msgmap, MCMD_NONE,
687 (in_index == View) ? cmd_delete_view : cmd_delete_index);
688 break;
691 /*---------- Undelete message ----------*/
692 case MC_UNDELETE :
693 (void) cmd_undelete(state, msgmap, MCMD_NONE);
694 update_titlebar_status();
695 break;
698 /*---------- Reply to message ----------*/
699 case MC_REPLY :
700 (void) cmd_reply(state, msgmap, MCMD_NONE, NULL);
701 break;
704 /*---------- Forward message ----------*/
705 case MC_FORWARD :
706 (void) cmd_forward(state, msgmap, MCMD_NONE, NULL);
707 break;
710 /*---------- Quit pine ------------*/
711 case MC_QUIT :
712 state->next_screen = quit_screen;
713 dprint((1,"MAIL_CMD: quit\n"));
714 break;
717 /*---------- Compose message ----------*/
718 case MC_COMPOSE :
719 state->prev_screen = (in_index == View) ? mail_view_screen
720 : mail_index_screen;
721 compose_screen(state);
722 state->mangled_screen = 1;
723 if (state->next_screen)
724 a_changed = TRUE;
725 break;
728 /*---------- Alt Compose message ----------*/
729 case MC_ROLE :
730 state->prev_screen = (in_index == View) ? mail_view_screen
731 : mail_index_screen;
732 role_compose(state);
733 if(state->next_screen)
734 a_changed = TRUE;
736 break;
739 /*--------- Folders menu ------------*/
740 case MC_FOLDERS :
741 state->start_in_context = 1;
743 /*--------- Top of Folders list menu ------------*/
744 case MC_COLLECTIONS :
745 state->next_screen = folder_screen;
746 dprint((2,"MAIL_CMD: going to folder/collection menu\n"));
747 break;
750 /*---------- Open specific new folder ----------*/
751 case MC_GOTO :
752 tc = (state->context_last && !NEWS_TEST(state->context_current))
753 ? state->context_last : state->context_current;
755 newfolder = broach_folder(question_line, 1, &notrealinbox, &tc);
756 if(newfolder){
757 visit_folder(state, newfolder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT);
758 a_changed = TRUE;
761 break;
764 /*------- Go to Index Screen ----------*/
765 case MC_INDEX :
766 state->next_screen = mail_index_screen;
767 break;
769 /*------- Skip to next interesting message -----------*/
770 case MC_TAB :
771 if(THRD_INDX()){
772 PINETHRD_S *thrd;
775 * If we're in the thread index, start looking after this
776 * thread. We don't want to match something in the current
777 * thread.
779 start = mn_get_cur(msgmap);
780 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
781 if(mn_get_revsort(msgmap)){
782 /* if reversed, top of thread is last one before next thread */
783 if(thrd && thrd->top)
784 start = mn_raw2m(msgmap, thrd->top);
786 else{
787 /* last msg of thread is at the ends of the branches/nexts */
788 while(thrd){
789 start = mn_raw2m(msgmap, thrd->rawno);
790 if(thrd->branch)
791 thrd = fetch_thread(stream, thrd->branch);
792 else if(thrd->next)
793 thrd = fetch_thread(stream, thrd->next);
794 else
795 thrd = NULL;
800 * Flags is 0 in this case because we want to not skip
801 * messages inside of threads so that we can find threads
802 * which have some unseen messages even though the top-level
803 * of the thread is already seen.
804 * If new_msgno ends up being a message which is not visible
805 * because it isn't at the top-level, the current message #
806 * will be adjusted below in adjust_cur.
808 flags = 0;
809 new_msgno = next_sorted_flagged((F_UNDEL
810 | F_UNSEEN
811 | ((F_ON(F_TAB_TO_NEW,state))
812 ? 0 : F_OR_FLAG)),
813 stream, start, &flags);
815 else if(THREADING() && sp_viewing_a_thread(stream)){
816 PINETHRD_S *thrd, *topthrd = NULL;
818 start = mn_get_cur(msgmap);
821 * Things are especially complicated when we're viewing_a_thread
822 * from the thread index. First we have to check within the
823 * current thread for a new message. If none is found, then
824 * we search in the next threads and offer to continue in
825 * them. Then we offer to go to the next folder.
827 flags = NSF_SKIP_CHID;
828 new_msgno = next_sorted_flagged((F_UNDEL
829 | F_UNSEEN
830 | ((F_ON(F_TAB_TO_NEW,state))
831 ? 0 : F_OR_FLAG)),
832 stream, start, &flags);
834 * If we found a match then we are done, that is another message
835 * in the current thread index. Otherwise, we have to look
836 * further.
838 if(!(flags & NSF_FLAG_MATCH)){
839 ret = 'n';
840 while(1){
842 flags = 0;
843 new_msgno = next_sorted_flagged((F_UNDEL
844 | F_UNSEEN
845 | ((F_ON(F_TAB_TO_NEW,
846 state))
847 ? 0 : F_OR_FLAG)),
848 stream, start, &flags);
850 * If we got a match, new_msgno is a message in
851 * a different thread from the one we are viewing.
853 if(flags & NSF_FLAG_MATCH){
854 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
855 if(thrd && thrd->top)
856 topthrd = fetch_thread(stream, thrd->top);
858 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
859 static ESCKEY_S next_opt[] = {
860 {'y', 'y', "Y", N_("Yes")},
861 {'n', 'n', "N", N_("No")},
862 {TAB, 'n', "Tab", N_("NextNew")},
863 {-1, 0, NULL, NULL}
866 if(in_index)
867 snprintf(prompt, sizeof(prompt), _("View thread number %s? "),
868 topthrd ? comatose(topthrd->thrdno) : "?");
869 else
870 snprintf(prompt, sizeof(prompt),
871 _("View message in thread number %s? "),
872 topthrd ? comatose(topthrd->thrdno) : "?");
874 prompt[sizeof(prompt)-1] = '\0';
876 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
877 next_opt, 'y', 'x', NO_HELP,
878 RB_NORM);
879 if(ret == 'x'){
880 cmd_cancelled(NULL);
881 goto get_out;
884 else
885 ret = 'y';
887 if(ret == 'y'){
888 if(unview_thread(state, stream, msgmap)){
889 state->next_screen = mail_index_screen;
890 state->view_skipped_index = 0;
891 state->mangled_screen = 1;
894 mn_set_cur(msgmap, new_msgno);
895 if(THRD_AUTO_VIEW()){
897 if(count_lflags_in_thread(stream, topthrd,
898 msgmap, MN_NONE) == 1){
899 if(view_thread(state, stream, msgmap, 1)){
900 if(current_index_state)
901 msgmap->top_after_thrd = current_index_state->msg_at_top;
903 state->view_skipped_index = 1;
904 command = MC_VIEW_TEXT;
905 goto view_text;
910 if(view_thread(state, stream, msgmap, 1) && current_index_state)
911 msgmap->top_after_thrd = current_index_state->msg_at_top;
913 state->next_screen = SCREEN_FUN_NULL;
914 break;
916 else if(ret == 'n' && topthrd){
918 * skip to end of this thread and look starting
919 * in the next thread.
921 if(mn_get_revsort(msgmap)){
923 * if reversed, top of thread is last one
924 * before next thread
926 start = mn_raw2m(msgmap, topthrd->rawno);
928 else{
930 * last msg of thread is at the ends of
931 * the branches/nexts
933 thrd = topthrd;
934 while(thrd){
935 start = mn_raw2m(msgmap, thrd->rawno);
936 if(thrd->branch)
937 thrd = fetch_thread(stream, thrd->branch);
938 else if(thrd->next)
939 thrd = fetch_thread(stream, thrd->next);
940 else
941 thrd = NULL;
945 else if(ret == 'n')
946 break;
948 else
949 break;
953 else{
955 start = mn_get_cur(msgmap);
958 * If we are on a collapsed thread, start looking after the
959 * collapsed part, unless we are viewing the message.
961 if(THREADING() && in_index != View){
962 PINETHRD_S *thrd;
963 long rawno;
964 int collapsed;
966 rawno = mn_m2raw(msgmap, start);
967 thrd = fetch_thread(stream, rawno);
968 collapsed = thrd && thrd->next
969 && get_lflag(stream, NULL, rawno, MN_COLL);
971 if(collapsed){
972 if(mn_get_revsort(msgmap)){
973 if(thrd && thrd->top)
974 start = mn_raw2m(msgmap, thrd->top);
976 else{
977 while(thrd){
978 start = mn_raw2m(msgmap, thrd->rawno);
979 if(thrd->branch)
980 thrd = fetch_thread(stream, thrd->branch);
981 else if(thrd->next)
982 thrd = fetch_thread(stream, thrd->next);
983 else
984 thrd = NULL;
991 new_msgno = next_sorted_flagged((F_UNDEL
992 | F_UNSEEN
993 | ((F_ON(F_TAB_TO_NEW,state))
994 ? 0 : F_OR_FLAG)),
995 stream, start, &flags);
999 * If there weren't any unread messages left, OR there
1000 * aren't any messages at all, we may want to offer to
1001 * go on to the next folder...
1003 if(flags & NSF_FLAG_MATCH){
1004 mn_set_cur(msgmap, new_msgno);
1005 if(in_index != View)
1006 adjust_cur_to_visible(stream, msgmap);
1008 else{
1009 int in_inbox = sp_flagged(stream, SP_INBOX);
1011 if(state->context_current
1012 && ((NEWS_TEST(state->context_current)
1013 && context_isambig(state->cur_folder))
1014 || ((state->context_current->use & CNTXT_INCMNG)
1015 && (in_inbox
1016 || folder_index(state->cur_folder,
1017 state->context_current,
1018 FI_FOLDER) >= 0)))){
1019 char nextfolder[MAXPATH];
1020 MAILSTREAM *nextstream = NULL;
1021 long recent_cnt;
1022 int did_cancel = 0;
1024 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
1025 nextfolder[sizeof(nextfolder)-1] = '\0';
1026 while(1){
1027 if(!(next_folder(&nextstream, nextfolder, sizeof(nextfolder), nextfolder,
1028 state->context_current, &recent_cnt,
1029 F_ON(F_TAB_NO_CONFIRM,state)
1030 ? NULL : &did_cancel))){
1031 if(!in_inbox){
1032 static ESCKEY_S inbox_opt[] = {
1033 {'y', 'y', "Y", N_("Yes")},
1034 {'n', 'n', "N", N_("No")},
1035 {TAB, 'z', "Tab", N_("To Inbox")},
1036 {-1, 0, NULL, NULL}
1039 if(F_ON(F_RET_INBOX_NO_CONFIRM,state))
1040 ret = 'y';
1041 else{
1042 /* TRANSLATORS: this is a question, with some information followed
1043 by Return to INBOX? */
1044 if(state->context_current->use&CNTXT_INCMNG)
1045 snprintf(prompt, sizeof(prompt), _("No more incoming folders. Return to \"%s\"? "), state->inbox_name);
1046 else
1047 snprintf(prompt, sizeof(prompt), _("No more news groups. Return to \"%s\"? "), state->inbox_name);
1049 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1050 inbox_opt, 'y', 'x',
1051 NO_HELP, RB_NORM);
1055 * 'z' is a synonym for 'y'. It is not 'y'
1056 * so that it isn't displayed as a default
1057 * action with square-brackets around it
1058 * in the keymenu...
1060 if(ret == 'y' || ret == 'z'){
1061 visit_folder(state, state->inbox_name,
1062 state->context_current,
1063 NULL, DB_INBOXWOCNTXT);
1064 a_changed = TRUE;
1067 else if (did_cancel)
1068 cmd_cancelled(NULL);
1069 else{
1070 if(state->context_current->use&CNTXT_INCMNG)
1071 q_status_message(SM_ORDER, 0, 2, _("No more incoming folders"));
1072 else
1073 q_status_message(SM_ORDER, 0, 2, _("No more news groups"));
1076 break;
1080 #define CNTLEN 80
1081 char *front, type[80], cnt[CNTLEN], fbuf[MAX_SCREEN_COLS/2+1];
1082 int rbspace, avail, need, take_back;
1085 * View_next_
1086 * Incoming_folder_ or news_group_ or folder_ or group_
1087 * "foldername"
1088 * _(13 recent) or _(some recent) or nothing
1089 * ?_
1091 front = "View next";
1092 strncpy(type,
1093 (state->context_current->use & CNTXT_INCMNG)
1094 ? "Incoming folder" : "news group",
1095 sizeof(type));
1096 type[sizeof(type)-1] = '\0';
1097 snprintf(cnt, sizeof(cnt), " (%.*s %s)", CNTLEN-20,
1098 recent_cnt ? long2string(recent_cnt) : "some",
1099 F_ON(F_TAB_USES_UNSEEN, ps_global)
1100 ? "unseen" : "recent");
1101 cnt[sizeof(cnt)-1] = '\0';
1104 * Space reserved for radio_buttons call.
1105 * If we make this 3 then radio_buttons won't mess
1106 * with the prompt. If we make it 2, then we get
1107 * one more character to use but radio_buttons will
1108 * cut off the last character of our prompt, which is
1109 * ok because it is a space.
1111 rbspace = 2;
1112 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols
1113 : 80;
1114 need = strlen(front)+1 + strlen(type)+1 +
1115 + strlen(nextfolder)+2 + strlen(cnt) +
1116 2 + rbspace;
1117 if(avail < need){
1118 take_back = strlen(type);
1119 strncpy(type,
1120 (state->context_current->use & CNTXT_INCMNG)
1121 ? "folder" : "group", sizeof(type));
1122 take_back -= strlen(type);
1123 need -= take_back;
1124 if(avail < need){
1125 need -= strlen(cnt);
1126 cnt[0] = '\0';
1129 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1130 snprintf(prompt, sizeof(prompt), "%.*s %.*s \"%.*s\"%.*s? ",
1131 (MAX_SCREEN_COLS+1)/8, front,
1132 (MAX_SCREEN_COLS+1)/8, type,
1133 (MAX_SCREEN_COLS+1)/2,
1134 short_str(nextfolder, fbuf, sizeof(fbuf),
1135 strlen(nextfolder) -
1136 ((need>avail) ? (need-avail) : 0),
1137 MidDots),
1138 (MAX_SCREEN_COLS+1)/8, cnt);
1139 prompt[sizeof(prompt)-1] = '\0';
1143 * When help gets added, this'll have to become
1144 * a loop like the rest...
1146 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
1147 static ESCKEY_S next_opt[] = {
1148 {'y', 'y', "Y", N_("Yes")},
1149 {'n', 'n', "N", N_("No")},
1150 {TAB, 'n', "Tab", N_("NextNew")},
1151 {-1, 0, NULL, NULL}
1154 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1155 next_opt, 'y', 'x', NO_HELP,
1156 RB_NORM);
1157 if(ret == 'x'){
1158 cmd_cancelled(NULL);
1159 break;
1162 else
1163 ret = 'y';
1165 if(ret == 'y'){
1166 if(nextstream && sp_dead_stream(nextstream))
1167 nextstream = NULL;
1169 visit_folder(state, nextfolder,
1170 state->context_current, nextstream,
1171 DB_FROMTAB);
1172 /* visit_folder takes care of nextstream */
1173 nextstream = NULL;
1174 a_changed = TRUE;
1175 break;
1179 if(nextstream)
1180 pine_mail_close(nextstream);
1182 else
1183 any_messages(NULL,
1184 (mn_get_total(msgmap) > 0L)
1185 ? IS_NEWS(stream) ? "more undeleted" : "more new"
1186 : NULL,
1187 NULL);
1190 get_out:
1192 break;
1195 /*------- Zoom -----------*/
1196 case MC_ZOOM :
1198 * Right now the way zoom is implemented is sort of silly.
1199 * There are two per-message flags where just one and a
1200 * global "zoom mode" flag to suppress messags from the index
1201 * should suffice.
1203 if(any_messages(msgmap, NULL, "to Zoom on")){
1204 if(unzoom_index(state, stream, msgmap)){
1205 dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
1206 q_status_message(SM_ORDER,0,2, _("Index Zoom Mode is now off"));
1208 else if((i = zoom_index(state, stream, msgmap, MN_SLCT)) != 0){
1209 if(any_lflagged(msgmap, MN_HIDE)){
1210 dprint((4,"\n\n ---- Entering ZOOM mode ----\n"));
1211 q_status_message4(SM_ORDER, 0, 2,
1212 _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"),
1213 THRD_INDX() ? "" : comatose(i),
1214 THRD_INDX() ? "" : " ",
1215 THRD_INDX() ? _("threads") : _("message"),
1216 THRD_INDX() ? "" : plural(i));
1218 else
1219 q_status_message(SM_ORDER, 0, 2,
1220 _("All messages selected, so not entering Index Zoom Mode"));
1222 else
1223 any_messages(NULL, "selected", "to Zoom on");
1226 break;
1229 /*---------- print message on paper ----------*/
1230 case MC_PRINTMSG :
1231 if(any_messages(msgmap, NULL, "to print"))
1232 (void) cmd_print(state, msgmap, MCMD_NONE, in_index);
1234 break;
1237 /*---------- Take Address ----------*/
1238 case MC_TAKE :
1239 if(F_ON(F_ENABLE_ROLE_TAKE, state) ||
1240 any_messages(msgmap, NULL, "to Take address from"))
1241 (void) cmd_take_addr(state, msgmap, MCMD_NONE);
1243 break;
1246 /*---------- Save Message ----------*/
1247 case MC_SAVE :
1248 if(any_messages(msgmap, NULL, "to Save"))
1249 (void) cmd_save(state, stream, msgmap, MCMD_NONE, in_index);
1251 break;
1254 /*---------- Export message ----------*/
1255 case MC_EXPORT :
1256 if(any_messages(msgmap, NULL, "to Export")){
1257 (void) cmd_export(state, msgmap, question_line, MCMD_NONE);
1258 state->mangled_footer = 1;
1261 break;
1264 /*---------- Expunge ----------*/
1265 case MC_EXPUNGE :
1266 (void) cmd_expunge(state, stream, msgmap, MCMD_NONE);
1267 break;
1270 /*------- Unexclude -----------*/
1271 case MC_UNEXCLUDE :
1272 if(!(IS_NEWS(stream) && stream->rdonly)){
1273 q_status_message(SM_ORDER, 0, 3,
1274 _("Unexclude not available for mail folders"));
1276 else if(any_lflagged(msgmap, MN_EXLD)){
1277 SEARCHPGM *pgm;
1278 long i;
1279 int exbits;
1282 * Since excluded means "hidden deleted" and "killed",
1283 * the count should reflect the former.
1285 pgm = mail_newsearchpgm();
1286 pgm->deleted = 1;
1287 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
1288 for(i = 1L, del_count = 0L; i <= stream->nmsgs; i++)
1289 if((mc = mail_elt(stream, i)) && mc->searched
1290 && get_lflag(stream, NULL, i, MN_EXLD)
1291 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
1292 && (exbits & MSG_EX_FILTERED)))
1293 del_count++;
1295 if(del_count > 0L){
1296 state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1297 snprintf(prompt, sizeof(prompt), "UNexclude %ld message%s in %.*s", del_count,
1298 plural(del_count), MAX_SCREEN_COLS+1-40,
1299 pretty_fn(state->cur_folder));
1300 prompt[sizeof(prompt)-1] = '\0';
1301 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
1302 || (F_ON(F_AUTO_EXPUNGE, state)
1303 && (state->context_current
1304 && (state->context_current->use & CNTXT_INCMNG))
1305 && context_isambig(state->cur_folder))
1306 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
1307 long save_cur_rawno;
1308 int were_viewing_a_thread;
1310 save_cur_rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
1311 were_viewing_a_thread = (THREADING()
1312 && sp_viewing_a_thread(stream));
1314 if(msgno_include(stream, msgmap, MI_NONE)){
1315 clear_index_cache(stream, 0);
1317 if(stream && stream->spare)
1318 erase_threading_info(stream, msgmap);
1320 refresh_sort(stream, msgmap, SRT_NON);
1323 if(were_viewing_a_thread){
1324 if(save_cur_rawno > 0L)
1325 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1327 if(view_thread(state, stream, msgmap, 1) && current_index_state)
1328 msgmap->top_after_thrd = current_index_state->msg_at_top;
1331 if(save_cur_rawno > 0L)
1332 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1334 state->mangled_screen = 1;
1335 q_status_message2(SM_ORDER, 0, 4,
1336 "%s message%s UNexcluded",
1337 long2string(del_count),
1338 plural(del_count));
1340 if(in_index != View)
1341 adjust_cur_to_visible(stream, msgmap);
1343 else
1344 any_messages(NULL, NULL, "UNexcluded");
1346 else
1347 any_messages(NULL, "excluded", "to UNexclude");
1349 else
1350 any_messages(NULL, "excluded", "to UNexclude");
1352 break;
1355 /*------- Make Selection -----------*/
1356 case MC_SELECT :
1357 if(any_messages(msgmap, NULL, "to Select")){
1358 if(aggregate_select(state, msgmap, question_line, in_index) == 0
1359 && (in_index == MsgIndx || in_index == ThrdIndx)
1360 && F_ON(F_AUTO_ZOOM, state)
1361 && any_lflagged(msgmap, MN_SLCT) > 0L
1362 && !any_lflagged(msgmap, MN_HIDE))
1363 (void) zoom_index(state, stream, msgmap, MN_SLCT);
1366 break;
1369 /*------- Toggle Current Message Selection State -----------*/
1370 case MC_SELCUR :
1371 if(any_messages(msgmap, NULL, NULL)){
1372 if((select_by_current(state, msgmap, in_index)
1373 || (F_OFF(F_UNSELECT_WONT_ADVANCE, state)
1374 && !any_lflagged(msgmap, MN_HIDE)))
1375 && (i = mn_get_cur(msgmap)) < mn_get_total(msgmap)){
1376 /* advance current */
1377 mn_inc_cur(stream, msgmap,
1378 (in_index == View && THREADING()
1379 && sp_viewing_a_thread(stream))
1380 ? MH_THISTHD
1381 : (in_index == View)
1382 ? MH_ANYTHD : MH_NONE);
1386 break;
1389 /*------- Apply command -----------*/
1390 case MC_APPLY :
1391 if(any_messages(msgmap, NULL, NULL)){
1392 if(any_lflagged(msgmap, MN_SLCT) > 0L){
1393 if(apply_command(state, stream, msgmap, 0,
1394 AC_NONE, question_line)){
1395 if(F_ON(F_AUTO_UNSELECT, state)){
1396 agg_select_all(stream, msgmap, NULL, 0);
1397 unzoom_index(state, stream, msgmap);
1399 else if(F_ON(F_AUTO_UNZOOM, state))
1400 unzoom_index(state, stream, msgmap);
1403 else
1404 any_messages(NULL, NULL, "to Apply command to. Try \"Select\"");
1407 break;
1410 /*-------- Sort command -------*/
1411 case MC_SORT :
1413 int were_threading = THREADING();
1414 SortOrder sort = mn_get_sort(msgmap);
1415 int rev = mn_get_revsort(msgmap);
1417 dprint((1,"MAIL_CMD: sort\n"));
1418 if(select_sort(state, question_line, &sort, &rev)){
1419 /* $ command reinitializes threading collapsed/expanded info */
1420 if(SORT_IS_THREADED(msgmap) && !SEP_THRDINDX())
1421 erase_threading_info(stream, msgmap);
1423 if(ps_global && ps_global->ttyo){
1424 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
1425 ps_global->mangled_footer = 1;
1428 sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN);
1431 state->mangled_footer = 1;
1434 * We've changed whether we are threading or not so we need to
1435 * exit the index and come back in so that we switch between the
1436 * thread index and the regular index. Sort_folder will have
1437 * reset viewing_a_thread if necessary.
1439 if(SEP_THRDINDX()
1440 && ((!were_threading && THREADING())
1441 || (were_threading && !THREADING()))){
1442 state->next_screen = mail_index_screen;
1443 state->mangled_screen = 1;
1447 break;
1450 /*------- Toggle Full Headers -----------*/
1451 case MC_FULLHDR :
1452 state->full_header++;
1453 if(state->full_header == 1){
1454 if(!(state->quote_suppression_threshold
1455 && (state->some_quoting_was_suppressed || in_index != View)))
1456 state->full_header++;
1458 else if(state->full_header > 2)
1459 state->full_header = 0;
1461 switch(state->full_header){
1462 case 0:
1463 q_status_message(SM_ORDER, 0, 3,
1464 _("Display of full headers is now off."));
1465 break;
1467 case 1:
1468 q_status_message1(SM_ORDER, 0, 3,
1469 _("Quotes displayed, use %s to see full headers"),
1470 F_ON(F_USE_FK, state) ? "F9" : "H");
1471 break;
1473 case 2:
1474 q_status_message(SM_ORDER, 0, 3,
1475 _("Display of full headers is now on."));
1476 break;
1480 a_changed = TRUE;
1481 break;
1484 case MC_TOGGLE :
1485 a_changed = TRUE;
1486 break;
1489 #ifdef SMIME
1490 /*------- Try to decrypt message -----------*/
1491 case MC_DECRYPT:
1492 if(state->smime && state->smime->need_passphrase)
1493 smime_get_passphrase();
1495 a_changed = TRUE;
1496 break;
1498 case MC_SECURITY:
1499 smime_info_screen(state);
1500 break;
1501 #endif
1504 /*------- Bounce -----------*/
1505 case MC_BOUNCE :
1506 (void) cmd_bounce(state, msgmap, MCMD_NONE, NULL);
1507 break;
1510 /*------- Flag -----------*/
1511 case MC_FLAG :
1512 dprint((4, "\n - flag message -\n"));
1513 (void) cmd_flag(state, msgmap, MCMD_NONE);
1514 break;
1517 /*------- Pipe message -----------*/
1518 case MC_PIPE :
1519 (void) cmd_pipe(state, msgmap, MCMD_NONE);
1520 break;
1523 /*--------- Default, unknown command ----------*/
1524 default:
1525 alpine_panic("Unexpected command case");
1526 break;
1529 return((a_changed || mn_get_cur(msgmap) != old_msgno) ? 1 : 0);
1534 /*----------------------------------------------------------------------
1535 Map some of the special characters into sensible strings for human
1536 consumption.
1537 c is a UCS-4 character!
1538 ----*/
1539 char *
1540 pretty_command(UCS c)
1542 static char buf[10];
1543 char *s;
1545 buf[0] = '\0';
1546 s = buf;
1548 switch(c){
1549 case ' ' : s = "SPACE"; break;
1550 case '\033' : s = "ESC"; break;
1551 case '\177' : s = "DEL"; break;
1552 case ctrl('I') : s = "TAB"; break;
1553 case ctrl('J') : s = "LINEFEED"; break;
1554 case ctrl('M') : s = "RETURN"; break;
1555 case ctrl('Q') : s = "XON"; break;
1556 case ctrl('S') : s = "XOFF"; break;
1557 case KEY_UP : s = "Up Arrow"; break;
1558 case KEY_DOWN : s = "Down Arrow"; break;
1559 case KEY_RIGHT : s = "Right Arrow"; break;
1560 case KEY_LEFT : s = "Left Arrow"; break;
1561 case KEY_PGUP : s = "Prev Page"; break;
1562 case KEY_PGDN : s = "Next Page"; break;
1563 case KEY_HOME : s = "Home"; break;
1564 case KEY_END : s = "End"; break;
1565 case KEY_DEL : s = "Delete"; break; /* Not necessary DEL! */
1566 case KEY_JUNK : s = "Junk!"; break;
1567 case BADESC : s = "Bad Esc"; break;
1568 case NO_OP_IDLE : s = "NO_OP_IDLE"; break;
1569 case NO_OP_COMMAND : s = "NO_OP_COMMAND"; break;
1570 case KEY_RESIZE : s = "KEY_RESIZE"; break;
1571 case KEY_UTF8 : s = "KEY_UTF8"; break;
1572 case KEY_MOUSE : s = "KEY_MOUSE"; break;
1573 case KEY_SCRLUPL : s = "KEY_SCRLUPL"; break;
1574 case KEY_SCRLDNL : s = "KEY_SCRLDNL"; break;
1575 case KEY_SCRLTO : s = "KEY_SCRLTO"; break;
1576 case KEY_XTERM_MOUSE : s = "KEY_XTERM_MOUSE"; break;
1577 case KEY_DOUBLE_ESC : s = "KEY_DOUBLE_ESC"; break;
1578 case CTRL_KEY_UP : s = "Ctrl Up Arrow"; break;
1579 case CTRL_KEY_DOWN : s = "Ctrl Down Arrow"; break;
1580 case CTRL_KEY_RIGHT : s = "Ctrl Right Arrow"; break;
1581 case CTRL_KEY_LEFT : s = "Ctrl Left Arrow"; break;
1582 case PF1 :
1583 case PF2 :
1584 case PF3 :
1585 case PF4 :
1586 case PF5 :
1587 case PF6 :
1588 case PF7 :
1589 case PF8 :
1590 case PF9 :
1591 case PF10 :
1592 case PF11 :
1593 case PF12 :
1594 snprintf(s = buf, sizeof(buf), "F%ld", (long) (c - PF1 + 1));
1595 break;
1597 default:
1598 if(c < ' ' || (c >= 0x80 && c < 0xA0)){
1599 char d;
1600 int c1;
1602 c1 = (c >= 0x80);
1603 d = (c & 0x1f) + 'A' - 1;
1604 snprintf(s = buf, sizeof(buf), "%c%c", c1 ? '~' : '^', d);
1606 else{
1607 memset(buf, 0, sizeof(buf));
1608 utf8_put((unsigned char *) buf, (unsigned long) c);
1611 break;
1614 return(s);
1618 /*----------------------------------------------------------------------
1619 Complain about bogus input
1621 Args: ch -- input command to complain about
1622 help -- string indicating where to get help
1624 ----*/
1625 void
1626 bogus_command(UCS cmd, char *help)
1628 if(cmd == ctrl('Q') || cmd == ctrl('S'))
1629 q_status_message1(SM_ASYNC, 0, 2,
1630 "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.",
1631 pretty_command(cmd));
1632 else if(cmd == KEY_JUNK)
1633 q_status_message3(SM_ORDER, 0, 2,
1634 "Invalid key pressed.%s%s%s",
1635 (help) ? " Use " : "",
1636 (help) ? help : "",
1637 (help) ? " for help" : "");
1638 else
1639 q_status_message4(SM_ORDER, 0, 2,
1640 "Command \"%s\" not defined for this screen.%s%s%s",
1641 pretty_command(cmd),
1642 (help) ? " Use " : "",
1643 (help) ? help : "",
1644 (help) ? " for help" : "");
1648 void
1649 bogus_utf8_command(char *cmd, char *help)
1651 q_status_message4(SM_ORDER, 0, 2,
1652 "Command \"%s\" not defined for this screen.%s%s%s",
1653 cmd ? cmd : "?",
1654 (help) ? " Use " : "",
1655 (help) ? help : "",
1656 (help) ? " for help" : "");
1660 /*----------------------------------------------------------------------
1661 Execute FLAG message command
1663 Args: state -- Various satate info
1664 msgmap -- map of c-client to local message numbers
1666 Result: with side effect of "current" message FLAG flag set or UNset
1668 ----*/
1670 cmd_flag(struct pine *state, MSGNO_S *msgmap, int aopt)
1672 char *flagit, *seq, *screen_text[20], **exp, **p, *answer = NULL;
1673 char *keyword_array[2];
1674 int user_defined_flags = 0, mailbox_flags = 0;
1675 int directly_to_maint_screen = 0;
1676 long unflagged, flagged, flags, rawno;
1677 MESSAGECACHE *mc = NULL;
1678 KEYWORD_S *kw;
1679 int i, cnt, is_set, trouble = 0, rv = 0;
1680 size_t len;
1681 struct flag_table *fp, *ftbl = NULL;
1682 struct flag_screen flag_screen;
1683 static char *flag_screen_text1[] = {
1684 N_(" Set desired flags for current message below. An 'X' means set"),
1685 N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."),
1686 NULL
1689 static char *flag_screen_text2[] = {
1690 N_(" Set desired flags below for selected messages. A '?' means to"),
1691 N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"),
1692 N_(" to unset it. Use the \"Return\" key to toggle, and choose"),
1693 N_(" \"Exit\" when finished."),
1694 NULL
1697 static struct flag_table default_ftbl[] = {
1698 {N_("Important"), h_flag_important, F_FLAG, 0, 0, NULL, NULL},
1699 {N_("New"), h_flag_new, F_SEEN, 0, 0, NULL, NULL},
1700 {N_("Answered"), h_flag_answered, F_ANS, 0, 0, NULL, NULL},
1701 {N_("Forwarded"), h_flag_forwarded, F_FWD, 0, 0, NULL, NULL},
1702 {N_("Deleted"), h_flag_deleted, F_DEL, 0, 0, NULL, NULL},
1703 {NULL, NO_HELP, 0, 0, 0, NULL, NULL}
1706 /* Only check for dead stream for now. Should check permanent flags
1707 * eventually
1709 if(!(any_messages(msgmap, NULL, "to Flag") && can_set_flag(state, "flag", 1)))
1710 return rv;
1712 if(sp_io_error_on_stream(state->mail_stream)){
1713 sp_set_io_error_on_stream(state->mail_stream, 0);
1714 pine_mail_check(state->mail_stream); /* forces write */
1715 return rv;
1718 go_again:
1719 answer = NULL;
1720 user_defined_flags = 0;
1721 mailbox_flags = 0;
1722 mc = NULL;
1723 trouble = 0;
1724 ftbl = NULL;
1726 /* count how large ftbl will be */
1727 for(cnt = 0; default_ftbl[cnt].name; cnt++)
1730 /* add user flags */
1731 for(kw = ps_global->keywords; kw; kw = kw->next){
1732 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick)) || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1733 user_defined_flags++;
1734 cnt++;
1739 * Add mailbox flags that aren't user-defined flags.
1740 * Don't consider it if it matches either one of our defined
1741 * keywords or one of our defined nicknames for a keyword.
1743 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1744 char *q;
1746 q = stream_to_user_flag_name(state->mail_stream, i);
1747 if(q && q[0]){
1748 for(kw = ps_global->keywords; kw; kw = kw->next){
1749 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1750 break;
1754 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1755 mailbox_flags++;
1756 cnt++;
1760 cnt += (user_defined_flags ? 2 : 0) + (mailbox_flags ? 2 : 0);
1762 /* set up ftbl, first the system flags */
1763 ftbl = (struct flag_table *) fs_get((cnt+1) * sizeof(*ftbl));
1764 memset(ftbl, 0, (cnt+1) * sizeof(*ftbl));
1765 for(i = 0, fp = ftbl; default_ftbl[i].name; i++, fp++){
1766 fp->name = cpystr(default_ftbl[i].name);
1767 fp->help = default_ftbl[i].help;
1768 fp->flag = default_ftbl[i].flag;
1769 fp->set = default_ftbl[i].set;
1770 fp->ukn = default_ftbl[i].ukn;
1773 if(user_defined_flags){
1774 fp->flag = F_COMMENT;
1775 fp->name = cpystr("");
1776 fp++;
1777 fp->flag = F_COMMENT;
1778 len = strlen(_("User-defined Keywords from Setup/Config"));
1779 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1780 snprintf(fp->name, len+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config"));
1781 fp++;
1784 /* then the user-defined keywords */
1785 if(user_defined_flags)
1786 for(kw = ps_global->keywords; kw; kw = kw->next){
1787 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick))
1788 || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1789 fp->name = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
1790 fp->keyword = cpystr(kw->kw ? kw->kw : "");
1791 if(kw->nick && kw->kw){
1792 size_t l;
1794 l = strlen(kw->kw)+2;
1795 fp->comment = (char *) fs_get((l+1) * sizeof(char));
1796 snprintf(fp->comment, l+1, "(%.*s)", (int) strlen(kw->kw), kw->kw);
1797 fp->comment[l] = '\0';
1800 fp->help = h_flag_user_flag;
1801 fp->flag = F_KEYWORD;
1802 fp->set = 0;
1803 fp->ukn = 0;
1804 fp++;
1808 if(mailbox_flags){
1809 fp->flag = F_COMMENT;
1810 fp->name = cpystr("");
1811 fp++;
1812 fp->flag = F_COMMENT;
1813 len = strlen(_("Other keywords in the mailbox that are not user-defined"));
1814 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1815 snprintf(fp->name, len+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined"));
1816 fp++;
1819 /* then the extra mailbox-defined keywords */
1820 if(mailbox_flags)
1821 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1822 char *q;
1824 q = stream_to_user_flag_name(state->mail_stream, i);
1825 if(q && q[0]){
1826 for(kw = ps_global->keywords; kw; kw = kw->next){
1827 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1828 break;
1832 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1833 fp->name = cpystr(q);
1834 fp->keyword = cpystr(q);
1835 fp->help = h_flag_user_flag;
1836 fp->flag = F_KEYWORD;
1837 fp->set = 0;
1838 fp->ukn = 0;
1839 fp++;
1843 flag_screen.flag_table = &ftbl;
1844 flag_screen.explanation = screen_text;
1846 if(MCMD_ISAGG(aopt)){
1847 if(!pseudo_selected(ps_global->mail_stream, msgmap)){
1848 free_flag_table(&ftbl);
1849 return rv;
1852 exp = flag_screen_text2;
1853 for(fp = ftbl; fp->name; fp++){
1854 fp->set = CMD_FLAG_UNKN; /* set to unknown */
1855 fp->ukn = TRUE;
1858 else if(state->mail_stream
1859 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
1860 && rawno <= state->mail_stream->nmsgs
1861 && (mc = mail_elt(state->mail_stream, rawno))){
1862 exp = flag_screen_text1;
1863 for(fp = &ftbl[0]; fp->name; fp++){
1864 fp->ukn = 0;
1865 if(fp->flag == F_KEYWORD){
1866 /* see if this keyword is defined for this message */
1867 fp->set = CMD_FLAG_CLEAR;
1868 if(user_flag_is_set(state->mail_stream,
1869 rawno, fp->keyword))
1870 fp->set = CMD_FLAG_SET;
1872 else if(fp->flag == F_FWD){
1873 /* see if forwarded keyword is defined for this message */
1874 fp->set = CMD_FLAG_CLEAR;
1875 if(user_flag_is_set(state->mail_stream,
1876 rawno, FORWARDED_FLAG))
1877 fp->set = CMD_FLAG_SET;
1879 else if(fp->flag != F_COMMENT)
1880 fp->set = ((fp->flag == F_SEEN && !mc->seen)
1881 || (fp->flag == F_DEL && mc->deleted)
1882 || (fp->flag == F_FLAG && mc->flagged)
1883 || (fp->flag == F_ANS && mc->answered))
1884 ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
1887 else{
1888 q_status_message(SM_ORDER | SM_DING, 3, 4,
1889 _("Error accessing message data"));
1890 free_flag_table(&ftbl);
1891 return rv;
1894 if(directly_to_maint_screen)
1895 goto the_maint_screen;
1897 #ifdef _WINDOWS
1898 if (mswin_usedialog ()) {
1899 if (!os_flagmsgdialog (&ftbl[0])){
1900 free_flag_table(&ftbl);
1901 return rv;
1904 else
1905 #endif
1907 int use_maint_screen;
1908 int keyword_shortcut = 0;
1910 use_maint_screen = F_ON(F_FLAG_SCREEN_DFLT, ps_global);
1912 if(!use_maint_screen){
1914 * We're going to call cmd_flag_prompt(). We need
1915 * to decide whether or not to offer the keyword setting
1916 * shortcut. We'll offer it if the user has the feature
1917 * enabled AND there are some possible keywords that could
1918 * be set.
1920 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global)){
1921 for(fp = &ftbl[0]; fp->name && !keyword_shortcut; fp++){
1922 if(fp->flag == F_KEYWORD){
1923 int first_char;
1924 ESCKEY_S *tp;
1926 first_char = (fp->name && fp->name[0])
1927 ? fp->name[0] : -2;
1928 if(isascii(first_char) && isupper(first_char))
1929 first_char = tolower((unsigned char) first_char);
1931 for(tp=flag_text_opt; tp->ch != -1; tp++)
1932 if(tp->ch == first_char)
1933 break;
1935 if(tp->ch == -1)
1936 keyword_shortcut++;
1941 use_maint_screen = !cmd_flag_prompt(state, &flag_screen,
1942 keyword_shortcut);
1945 the_maint_screen:
1946 if(use_maint_screen){
1947 for(p = &screen_text[0]; *exp; p++, exp++)
1948 *p = *exp;
1950 *p = NULL;
1952 directly_to_maint_screen = flag_maintenance_screen(state, &flag_screen);
1956 /* reaquire the elt pointer */
1957 mc = (state->mail_stream
1958 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
1959 && rawno <= state->mail_stream->nmsgs)
1960 ? mail_elt(state->mail_stream, rawno) : NULL;
1962 for(fp = ftbl; mc && fp->name; fp++){
1963 flags = -1;
1964 switch(fp->flag){
1965 case F_SEEN:
1966 if((!MCMD_ISAGG(aopt) && fp->set != !mc->seen)
1967 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
1968 flagit = "\\SEEN";
1969 if(fp->set){
1970 flags = 0L;
1971 unflagged = F_SEEN;
1973 else{
1974 flags = ST_SET;
1975 unflagged = F_UNSEEN;
1979 break;
1981 case F_ANS:
1982 if((!MCMD_ISAGG(aopt) && fp->set != mc->answered)
1983 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
1984 flagit = "\\ANSWERED";
1985 if(fp->set){
1986 flags = ST_SET;
1987 unflagged = F_UNANS;
1989 else{
1990 flags = 0L;
1991 unflagged = F_ANS;
1995 break;
1997 case F_DEL:
1998 if((!MCMD_ISAGG(aopt) && fp->set != mc->deleted)
1999 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2000 flagit = "\\DELETED";
2001 if(fp->set){
2002 flags = ST_SET;
2003 unflagged = F_UNDEL;
2005 else{
2006 flags = 0L;
2007 unflagged = F_DEL;
2011 break;
2013 case F_FLAG:
2014 if((!MCMD_ISAGG(aopt) && fp->set != mc->flagged)
2015 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2016 flagit = "\\FLAGGED";
2017 if(fp->set){
2018 flags = ST_SET;
2019 unflagged = F_UNFLAG;
2021 else{
2022 flags = 0L;
2023 unflagged = F_FLAG;
2027 break;
2029 case F_FWD :
2030 if(!MCMD_ISAGG(aopt)){
2031 /* see if forwarded is defined for this message */
2032 is_set = CMD_FLAG_CLEAR;
2033 if(user_flag_is_set(state->mail_stream,
2034 mn_m2raw(msgmap, mn_get_cur(msgmap)),
2035 FORWARDED_FLAG))
2036 is_set = CMD_FLAG_SET;
2039 if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2040 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2041 flagit = FORWARDED_FLAG;
2042 if(fp->set){
2043 flags = ST_SET;
2044 unflagged = F_UNFWD;
2046 else{
2047 flags = 0L;
2048 unflagged = F_FWD;
2052 break;
2054 case F_KEYWORD:
2055 if(!MCMD_ISAGG(aopt)){
2056 /* see if this keyword is defined for this message */
2057 is_set = CMD_FLAG_CLEAR;
2058 if(user_flag_is_set(state->mail_stream,
2059 mn_m2raw(msgmap, mn_get_cur(msgmap)),
2060 fp->keyword))
2061 is_set = CMD_FLAG_SET;
2064 if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2065 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2066 flagit = fp->keyword;
2067 keyword_array[0] = fp->keyword;
2068 keyword_array[1] = NULL;
2069 if(fp->set){
2070 flags = ST_SET;
2071 unflagged = F_UNKEYWORD;
2073 else{
2074 flags = 0L;
2075 unflagged = F_KEYWORD;
2079 break;
2081 default:
2082 break;
2085 flagged = 0L;
2086 if(flags >= 0L
2087 && (seq = currentf_sequence(state->mail_stream, msgmap,
2088 unflagged, &flagged, unflagged & F_DEL,
2089 (fp->flag == F_KEYWORD
2090 && unflagged == F_KEYWORD)
2091 ? keyword_array : NULL,
2092 (fp->flag == F_KEYWORD
2093 && unflagged == F_UNKEYWORD)
2094 ? keyword_array : NULL))){
2096 * For user keywords, we may have to create the flag in
2097 * the folder if it doesn't already exist and we are setting
2098 * it (as opposed to clearing it). Mail_flag will
2099 * do that for us, but it's failure isn't very friendly
2100 * error-wise. So we try to make it a little smoother.
2102 if(!(fp->flag == F_KEYWORD || fp->flag == F_FWD) || !fp->set
2103 || ((i=user_flag_index(state->mail_stream, flagit)) >= 0
2104 && i < NUSERFLAGS))
2105 mail_flag(state->mail_stream, seq, flagit, flags);
2106 else{
2107 /* trouble, see if we can add the user flag */
2108 if(state->mail_stream->kwd_create)
2109 mail_flag(state->mail_stream, seq, flagit, flags);
2110 else{
2111 trouble++;
2113 if(some_user_flags_defined(state->mail_stream))
2114 q_status_message(SM_ORDER, 3, 4,
2115 _("No more keywords allowed in this folder!"));
2116 else if(fp->flag == F_FWD)
2117 q_status_message(SM_ORDER, 3, 4,
2118 _("Cannot add keywords for this folder so cannot set Forwarded flag"));
2119 else
2120 q_status_message(SM_ORDER, 3, 4,
2121 _("Cannot add keywords for this folder"));
2125 fs_give((void **) &seq);
2126 if(flagged && !trouble){
2127 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%slagged%s%s%s%s%s message%s%s \"%s\"",
2128 (fp->set) ? "F" : "Unf",
2129 MCMD_ISAGG(aopt) ? " " : "",
2130 MCMD_ISAGG(aopt) ? long2string(flagged) : "",
2131 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2132 ? " (of " : "",
2133 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2134 ? comatose(mn_total_cur(msgmap)) : "",
2135 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2136 ? ")" : "",
2137 MCMD_ISAGG(aopt) ? plural(flagged) : " ",
2138 MCMD_ISAGG(aopt) ? "" : long2string(mn_get_cur(msgmap)),
2139 fp->name);
2140 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2141 q_status_message(SM_ORDER, 0, 2, answer = tmp_20k_buf);
2142 rv++;
2147 free_flag_table(&ftbl);
2149 if(directly_to_maint_screen)
2150 goto go_again;
2152 if(MCMD_ISAGG(aopt))
2153 restore_selected(msgmap);
2155 if(!answer)
2156 q_status_message(SM_ORDER, 0, 2, _("No flags changed."));
2158 return rv;
2162 /*----------------------------------------------------------------------
2163 Offer concise status line flag prompt
2165 Args: state -- Various satate info
2166 flags -- flags to offer setting
2168 Result: TRUE if flag to set specified in flags struct or FALSE otw
2170 ----*/
2172 cmd_flag_prompt(struct pine *state, struct flag_screen *flags, int allow_keyword_shortcuts)
2174 int r, setflag = 1, first_char;
2175 struct flag_table *fp;
2176 ESCKEY_S *ek;
2177 char *ftext, *ftext_not;
2178 static char *flag_text =
2179 N_("Flag New, Deleted, Answered, Forwarded or Important ? ");
2180 static char *flag_text_ak =
2181 N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? ");
2182 static char *flag_text_not =
2183 N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? ");
2184 static char *flag_text_ak_not =
2185 N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? ");
2187 if(allow_keyword_shortcuts){
2188 int cnt = 0;
2189 ESCKEY_S *dp, *sp, *tp;
2191 for(sp=flag_text_opt; sp->ch != -1; sp++)
2192 cnt++;
2194 for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++)
2195 if(fp->flag == F_KEYWORD)
2196 cnt++;
2198 /* set up an ESCKEY_S list which includes invisible keys for keywords */
2199 ek = (ESCKEY_S *) fs_get((cnt + 1) * sizeof(*ek));
2200 memset(ek, 0, (cnt+1) * sizeof(*ek));
2201 for(dp=ek, sp=flag_text_opt; sp->ch != -1; sp++, dp++)
2202 *dp = *sp;
2204 for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2205 if(fp->flag == F_KEYWORD){
2206 first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2207 if(isascii(first_char) && isupper(first_char))
2208 first_char = tolower((unsigned char) first_char);
2211 * Check to see if an earlier keyword in the list, or one of
2212 * the builtin system letters already uses this character.
2213 * If so, the first one wins.
2215 for(tp=ek; tp->ch != 0; tp++)
2216 if(tp->ch == first_char)
2217 break;
2219 if(tp->ch != 0)
2220 continue; /* skip it, already used that char */
2222 dp->ch = first_char;
2223 dp->rval = first_char;
2224 dp->name = "";
2225 dp->label = "";
2226 dp++;
2230 dp->ch = -1;
2231 ftext = _(flag_text_ak);
2232 ftext_not = _(flag_text_ak_not);
2234 else{
2235 ek = flag_text_opt;
2236 ftext = _(flag_text);
2237 ftext_not = _(flag_text_not);
2240 while(1){
2241 r = radio_buttons(setflag ? ftext : ftext_not,
2242 -FOOTER_ROWS(state), ek, '*', SEQ_EXCEPTION-1,
2243 NO_HELP, RB_NORM | RB_SEQ_SENSITIVE);
2245 * It is SEQ_EXCEPTION-1 just so that it is some value that isn't
2246 * being used otherwise. The keywords use up all the possible
2247 * letters, so a negative number is good, but it has to be different
2248 * from other negative return values.
2250 if(r == SEQ_EXCEPTION-1) /* ol'cancelrooney */
2251 return(TRUE);
2252 else if(r == 10) /* return and goto flag screen */
2253 return(FALSE);
2254 else if(r == '!') /* flip intention */
2255 setflag = !setflag;
2256 else
2257 break;
2260 for(fp = (flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2261 if(r == 'n' || r == '*' || r == 'd' || r == 'a' || r == 'f'){
2262 if((r == 'n' && fp->flag == F_SEEN)
2263 || (r == '*' && fp->flag == F_FLAG)
2264 || (r == 'd' && fp->flag == F_DEL)
2265 || (r == 'f' && fp->flag == F_FWD)
2266 || (r == 'a' && fp->flag == F_ANS)){
2267 fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2268 break;
2271 else if(allow_keyword_shortcuts && fp->flag == F_KEYWORD){
2272 first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2273 if(isascii(first_char) && isupper(first_char))
2274 first_char = tolower((unsigned char) first_char);
2276 if(r == first_char){
2277 fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2278 break;
2283 if(ek != flag_text_opt)
2284 fs_give((void **) &ek);
2286 return(TRUE);
2291 * (*ft) is an array of flag_table entries.
2293 void
2294 free_flag_table(struct flag_table **ft)
2296 struct flag_table *fp;
2298 if(ft && *ft){
2299 for(fp = (*ft); fp->name; fp++){
2300 if(fp->name)
2301 fs_give((void **) &fp->name);
2303 if(fp->keyword)
2304 fs_give((void **) &fp->keyword);
2306 if(fp->comment)
2307 fs_give((void **) &fp->comment);
2310 fs_give((void **) ft);
2315 /*----------------------------------------------------------------------
2316 Execute REPLY message command
2318 Args: state -- Various satate info
2319 msgmap -- map of c-client to local message numbers
2321 Result: reply sent or not
2323 ----*/
2325 cmd_reply(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2327 int rv = 0;
2329 if(any_messages(msgmap, NULL, "to Reply to")){
2330 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2331 return rv;
2333 rv = reply(state, role);
2335 if(MCMD_ISAGG(aopt))
2336 restore_selected(msgmap);
2338 state->mangled_screen = 1;
2341 return rv;
2345 /*----------------------------------------------------------------------
2346 Execute FORWARD message command
2348 Args: state -- Various satate info
2349 msgmap -- map of c-client to local message numbers
2351 Result: selected message[s] forwarded or not
2353 ----*/
2355 cmd_forward(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2357 int rv = 0;
2359 if(any_messages(msgmap, NULL, "to Forward")){
2360 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2361 return rv;
2363 rv = forward(state, role);
2365 if(MCMD_ISAGG(aopt))
2366 restore_selected(msgmap);
2368 state->mangled_screen = 1;
2371 return rv;
2375 /*----------------------------------------------------------------------
2376 Execute BOUNCE message command
2378 Args: state -- Various satate info
2379 msgmap -- map of c-client to local message numbers
2380 aopt -- aggregate options
2382 Result: selected message[s] bounced or not
2384 ----*/
2386 cmd_bounce(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2388 int rv = 0;
2390 if(any_messages(msgmap, NULL, "to Bounce")){
2391 long i;
2392 if(MCMD_ISAGG(aopt)){
2393 if(!pseudo_selected(state->mail_stream, msgmap))
2394 return rv;
2396 else if((i = any_lflagged(msgmap, MN_SLCT)) > 0
2397 && get_lflag(state->mail_stream, msgmap,
2398 mn_m2raw(msgmap, mn_get_cur(msgmap)), MN_SLCT) == 0)
2399 q_status_message(SM_ORDER | SM_DING, 3, 4,
2400 _("WARNING: non-selected message is being bounced!"));
2401 else if (i > 1L
2402 && get_lflag(state->mail_stream, msgmap,
2403 mn_m2raw(msgmap, mn_get_cur(msgmap)), MN_SLCT))
2404 q_status_message(SM_ORDER | SM_DING, 3, 4,
2405 _("WARNING: not bouncing all selected messages!"));
2407 rv = bounce(state, role);
2409 if(MCMD_ISAGG(aopt))
2410 restore_selected(msgmap);
2412 state->mangled_footer = 1;
2415 return rv;
2419 /*----------------------------------------------------------------------
2420 Execute save message command: prompt for folder and call function to save
2422 Args: screen_line -- Line on the screen to prompt on
2423 message -- The MESSAGECACHE entry of message to save
2425 Result: The folder lister can be called to make selection; mangled screen set
2427 This does the prompting for the folder name to save to, possibly calling
2428 up the folder display for selection of folder by user.
2429 ----*/
2431 cmd_save(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
2433 char newfolder[MAILTMPLEN], nmsgs[32], *nick;
2434 int we_cancel = 0, rv = 0, save_flags;
2435 long i, raw;
2436 CONTEXT_S *cntxt = NULL;
2437 ENVELOPE *e = NULL;
2438 SaveDel del = DontAsk;
2439 SavePreserveOrder pre = DontAskPreserve;
2441 dprint((4, "\n - saving message -\n"));
2443 if(MCMD_ISAGG(aopt) && !pseudo_selected(stream, msgmap))
2444 return rv;
2446 state->ugly_consider_advancing_bit = 0;
2447 if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM, state)
2448 && msgno_any_deletedparts(stream, msgmap)
2449 && want_to(_("Saved copy will NOT include entire message! Continue"),
2450 'y', 'n', NO_HELP, WT_FLUSH_IN | WT_SEQ_SENSITIVE) != 'y'){
2451 restore_selected(msgmap);
2452 cmd_cancelled("Save message");
2453 return rv;
2456 raw = mn_m2raw(msgmap, mn_get_cur(msgmap));
2458 if(mn_total_cur(msgmap) <= 1L){
2459 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld ", mn_get_cur(msgmap));
2460 nmsgs[sizeof(nmsgs)-1] = '\0';
2461 e = pine_mail_fetchstructure(stream, raw, NULL);
2462 if(!e) {
2463 q_status_message(SM_ORDER | SM_DING, 3, 4,
2464 _("Can't save message. Error accessing folder"));
2465 restore_selected(msgmap);
2466 return rv;
2469 else{
2470 snprintf(nmsgs, sizeof(nmsgs), "%s msgs ", comatose(mn_total_cur(msgmap)));
2471 nmsgs[sizeof(nmsgs)-1] = '\0';
2473 /* e is just used to get a default save folder from the first msg */
2474 e = pine_mail_fetchstructure(stream,
2475 mn_m2raw(msgmap, mn_first_cur(msgmap)),
2476 NULL);
2479 del = (!READONLY_FOLDER(stream) && F_OFF(F_SAVE_WONT_DELETE, ps_global))
2480 ? Del : NoDel;
2481 if(mn_total_cur(msgmap) > 1L)
2482 pre = F_OFF(F_AGG_SEQ_COPY, ps_global) ? Preserve : NoPreserve;
2483 else
2484 pre = DontAskPreserve;
2486 if(save_prompt(state, &cntxt, newfolder, sizeof(newfolder), nmsgs, e,
2487 raw, NULL, &del, &pre)){
2489 if(ps_global && ps_global->ttyo){
2490 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2491 ps_global->mangled_footer = 1;
2494 save_flags = SV_FIX_DELS;
2495 if(pre == RetPreserve)
2496 save_flags |= SV_PRESERVE;
2497 if(del == RetDel)
2498 save_flags |= SV_DELETE;
2499 if(ps_global->context_list == cntxt && !strucmp(newfolder, ps_global->inbox_name))
2500 save_flags |= SV_INBOXWOCNTXT;
2502 we_cancel = busy_cue(_("Saving"), NULL, 1);
2503 i = save(state, stream, cntxt, newfolder, msgmap, save_flags);
2504 if(we_cancel)
2505 cancel_busy_cue(0);
2507 if(i == mn_total_cur(msgmap)){
2508 rv++;
2509 if(mn_total_cur(msgmap) <= 1L){
2510 int need, avail = ps_global->ttyo->screen_cols - 2;
2511 int lennick, lenfldr;
2513 if(cntxt
2514 && ps_global->context_list->next
2515 && context_isambig(newfolder)){
2516 lennick = MIN(strlen(cntxt->nickname), 500);
2517 lenfldr = MIN(strlen(newfolder), 500);
2518 need = 27 + strlen(long2string(mn_get_cur(msgmap))) +
2519 lenfldr + lennick;
2520 if(need > avail){
2521 if(lennick > 10){
2522 need -= MIN(lennick-10, need-avail);
2523 lennick -= MIN(lennick-10, need-avail);
2526 if(need > avail && lenfldr > 10)
2527 lenfldr -= MIN(lenfldr-10, need-avail);
2530 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2531 "Message %s copied to \"%s\" in <%s>",
2532 long2string(mn_get_cur(msgmap)),
2533 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2534 lenfldr, MidDots),
2535 short_str(cntxt->nickname,
2536 (char *)(tmp_20k_buf+2000), 1000,
2537 lennick, EndDots));
2538 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2540 else if((nick=folder_is_target_of_nick(newfolder, cntxt)) != NULL){
2541 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2542 "Message %s copied to \"%s\"",
2543 long2string(mn_get_cur(msgmap)),
2544 nick);
2545 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2547 else{
2548 char *f = " folder";
2550 lenfldr = MIN(strlen(newfolder), 500);
2551 need = 28 + strlen(long2string(mn_get_cur(msgmap))) +
2552 lenfldr;
2553 if(need > avail){
2554 need -= strlen(f);
2555 f = "";
2556 if(need > avail && lenfldr > 10)
2557 lenfldr -= MIN(lenfldr-10, need-avail);
2560 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
2561 "Message %s copied to%s \"%s\"",
2562 long2string(mn_get_cur(msgmap)), f,
2563 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2564 lenfldr, MidDots));
2565 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2568 else{
2569 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s messages saved",
2570 comatose(mn_total_cur(msgmap)));
2571 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2574 if(del == RetDel){
2575 strncat(tmp_20k_buf, " and deleted", SIZEOF_20KBUF-strlen(tmp_20k_buf)-1);
2576 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2579 q_status_message(SM_ORDER, 0, 3, tmp_20k_buf);
2581 if(!MCMD_ISAGG(aopt) && F_ON(F_SAVE_ADVANCES, state)){
2582 if(sp_new_mail_count(stream))
2583 process_filter_patterns(stream, msgmap,
2584 sp_new_mail_count(stream));
2586 mn_inc_cur(stream, msgmap,
2587 (in_index == View && THREADING()
2588 && sp_viewing_a_thread(stream))
2589 ? MH_THISTHD
2590 : (in_index == View)
2591 ? MH_ANYTHD : MH_NONE);
2594 state->ugly_consider_advancing_bit = 1;
2598 if(MCMD_ISAGG(aopt)) /* straighten out fakes */
2599 restore_selected(msgmap);
2601 if(del == RetDel)
2602 update_titlebar_status(); /* make sure they see change */
2604 return rv;
2608 void
2609 role_compose(struct pine *state)
2611 int action;
2613 if(F_ON(F_ALT_ROLE_MENU, state) && mn_get_total(state->msgmap) > 0L){
2614 PAT_STATE pstate;
2616 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
2617 action = radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
2618 -FOOTER_ROWS(state), choose_action,
2619 'c', 'x', h_role_compose, RB_NORM);
2621 else{
2622 q_status_message(SM_ORDER, 0, 3,
2623 _("No roles available. Use Setup/Rules to add roles."));
2624 return;
2627 else
2628 action = 'c';
2630 if(action == 'c' || action == 'r' || action == 'f' || action == 'b'){
2631 ACTION_S *role = NULL;
2632 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
2634 redraw = state->redrawer;
2635 state->redrawer = NULL;
2636 prev_screen = state->prev_screen;
2637 role = NULL;
2638 state->next_screen = SCREEN_FUN_NULL;
2640 /* Setup role */
2641 if(role_select_screen(state, &role,
2642 action == 'f' ? MC_FORWARD :
2643 action == 'r' ? MC_REPLY :
2644 action == 'b' ? MC_BOUNCE :
2645 action == 'c' ? MC_COMPOSE : 0) < 0){
2646 cmd_cancelled(action == 'f' ? _("Forward") :
2647 action == 'r' ? _("Reply") :
2648 action == 'c' ? _("Composition") : _("Bounce"));
2649 state->next_screen = prev_screen;
2650 state->redrawer = redraw;
2651 state->mangled_screen = 1;
2653 else{
2655 * If default role was selected (NULL) we need to make
2656 * up a role which won't do anything, but will cause
2657 * compose_mail to think there's already a role so that
2658 * it won't try to confirm the default.
2660 if(role)
2661 role = combine_inherited_role(role);
2662 else{
2663 role = (ACTION_S *) fs_get(sizeof(*role));
2664 memset((void *) role, 0, sizeof(*role));
2665 role->nick = cpystr("Default Role");
2668 state->redrawer = NULL;
2669 switch(action){
2670 case 'c':
2671 compose_mail(NULL, NULL, role, NULL, NULL);
2672 break;
2674 case 'r':
2675 (void) reply(state, role);
2676 break;
2678 case 'f':
2679 (void) forward(state, role);
2680 break;
2682 case 'b':
2683 (void) bounce(state, role);
2684 break;
2687 if(role)
2688 free_action(&role);
2690 state->next_screen = prev_screen;
2691 state->redrawer = redraw;
2692 state->mangled_screen = 1;
2698 /*----------------------------------------------------------------------
2699 Do the dirty work of prompting the user for a folder name
2701 Args:
2702 nfldr should be a buffer at least MAILTMPLEN long
2703 dela -- a pointer to a SaveDel. If it is
2704 DontAsk on input, don't offer Delete prompt
2705 Del on input, offer Delete command with default of Delete
2706 NoDel NoDelete
2707 RetDel and RetNoDel are return values
2710 Result:
2712 ----*/
2714 save_prompt(struct pine *state, CONTEXT_S **cntxt, char *nfldr, size_t len_nfldr,
2715 char *nmsgs, ENVELOPE *env, long int rawmsgno, char *section,
2716 SaveDel *dela, SavePreserveOrder *prea)
2718 int rc, ku = -1, n, flags, last_rc = 0, saveable_count = 0, done = 0;
2719 int delindex, preindex, r;
2720 char prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN];
2721 char *buf = tmp_20k_buf;
2722 char shortbuf[200];
2723 char *folder;
2724 HelpType help;
2725 SaveDel del = DontAsk;
2726 SavePreserveOrder pre = DontAskPreserve;
2727 char *deltext = NULL;
2728 static HISTORY_S *history = NULL;
2729 CONTEXT_S *tc;
2730 ESCKEY_S ekey[10];
2732 if(state == NULL && cntxt == NULL && nfldr == NULL && len_nfldr == 0
2733 && nmsgs == NULL && env == NULL && rawmsgno == 0L && section == NULL
2734 && dela == NULL && prea == NULL){
2735 if(history != NULL)
2736 free_hist(&history);
2737 return 0;
2740 if(!cntxt)
2741 alpine_panic("no context ptr in save_prompt");
2743 init_hist(&history, HISTSIZE);
2745 if(!(folder = save_get_default(state, env, rawmsgno, section, cntxt)))
2746 return(0); /* message expunged! */
2748 /* how many context's can be saved to... */
2749 for(tc = state->context_list; tc; tc = tc->next)
2750 if(!NEWS_TEST(tc))
2751 saveable_count++;
2753 /* set up extra command option keys */
2754 rc = 0;
2755 ekey[rc].ch = ctrl('T');
2756 ekey[rc].rval = 2;
2757 ekey[rc].name = "^T";
2758 /* TRANSLATORS: command means go to Folders list */
2759 ekey[rc++].label = N_("To Fldrs");
2761 if(saveable_count > 1){
2762 ekey[rc].ch = ctrl('P');
2763 ekey[rc].rval = 10;
2764 ekey[rc].name = "^P";
2765 ekey[rc++].label = N_("Prev Collection");
2767 ekey[rc].ch = ctrl('N');
2768 ekey[rc].rval = 11;
2769 ekey[rc].name = "^N";
2770 ekey[rc++].label = N_("Next Collection");
2773 if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
2774 ekey[rc].ch = TAB;
2775 ekey[rc].rval = 12;
2776 ekey[rc].name = "TAB";
2777 /* TRANSLATORS: command asks alpine to complete the name when tab is typed */
2778 ekey[rc++].label = N_("Complete");
2781 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
2782 ekey[rc].ch = ctrl('X');
2783 ekey[rc].rval = 14;
2784 ekey[rc].name = "^X";
2785 /* TRANSLATORS: list all the matches */
2786 ekey[rc++].label = N_("ListMatches");
2789 if(dela && (*dela == NoDel || *dela == Del)){
2790 ekey[rc].ch = ctrl('R');
2791 ekey[rc].rval = 15;
2792 ekey[rc].name = "^R";
2793 delindex = rc++;
2794 del = *dela;
2797 if(prea && (*prea == NoPreserve || *prea == Preserve)){
2798 ekey[rc].ch = ctrl('W');
2799 ekey[rc].rval = 16;
2800 ekey[rc].name = "^W";
2801 preindex = rc++;
2802 pre = *prea;
2805 if(saveable_count > 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2806 ekey[rc].ch = KEY_UP;
2807 ekey[rc].rval = 10;
2808 ekey[rc].name = "";
2809 ekey[rc++].label = "";
2811 ekey[rc].ch = KEY_DOWN;
2812 ekey[rc].rval = 11;
2813 ekey[rc].name = "";
2814 ekey[rc++].label = "";
2816 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2817 ekey[rc].ch = KEY_UP;
2818 ekey[rc].rval = 30;
2819 ekey[rc].name = "";
2820 ku = rc;
2821 ekey[rc++].label = "";
2823 ekey[rc].ch = KEY_DOWN;
2824 ekey[rc].rval = 31;
2825 ekey[rc].name = "";
2826 ekey[rc++].label = "";
2829 ekey[rc].ch = -1;
2831 *nfldr = '\0';
2832 help = NO_HELP;
2833 while(!done){
2834 /* only show collection number if more than one available */
2835 if(ps_global->context_list->next)
2836 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder in <%s> [%s] : ",
2837 deltext ? deltext : "",
2838 nmsgs,
2839 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2840 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2841 else
2842 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder [%s] : ",
2843 deltext ? deltext : "",
2844 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2846 prompt[sizeof(prompt)-1] = '\0';
2849 * If the prompt won't fit, try removing deltext.
2851 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && deltext){
2852 if(ps_global->context_list->next)
2853 snprintf(prompt, sizeof(prompt), "SAVE %sto folder in <%s> [%s] : ",
2854 nmsgs,
2855 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2856 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2857 else
2858 snprintf(prompt, sizeof(prompt), "SAVE %sto folder [%s] : ",
2859 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2861 prompt[sizeof(prompt)-1] = '\0';
2865 * If the prompt still won't fit, remove the extra info contained
2866 * in nmsgs.
2868 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && *nmsgs){
2869 if(ps_global->context_list->next)
2870 snprintf(prompt, sizeof(prompt), "SAVE to folder in <%s> [%s] : ",
2871 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2872 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2873 else
2874 snprintf(prompt, sizeof(prompt), "SAVE to folder [%s] : ",
2875 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2877 prompt[sizeof(prompt)-1] = '\0';
2880 if(del != DontAsk)
2881 ekey[delindex].label = (del == NoDel) ? "Delete" : "No Delete";
2883 if(pre != DontAskPreserve)
2884 ekey[preindex].label = (pre == NoPreserve) ? "Preserve Order" : "Any Order";
2886 if(ku >= 0){
2887 if(items_in_hist(history) > 1){
2888 ekey[ku].name = HISTORY_UP_KEYNAME;
2889 ekey[ku].label = HISTORY_KEYLABEL;
2890 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
2891 ekey[ku+1].label = HISTORY_KEYLABEL;
2893 else{
2894 ekey[ku].name = "";
2895 ekey[ku].label = "";
2896 ekey[ku+1].name = "";
2897 ekey[ku+1].label = "";
2901 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
2902 rc = optionally_enter(nfldr, -FOOTER_ROWS(state), 0, len_nfldr,
2903 prompt, ekey, help, &flags);
2905 switch(rc){
2906 case -1 :
2907 q_status_message(SM_ORDER | SM_DING, 3, 3,
2908 _("Error reading folder name"));
2909 done--;
2910 break;
2912 case 0 :
2913 removing_trailing_white_space(nfldr);
2914 removing_leading_white_space(nfldr);
2916 if(*nfldr || *folder){
2917 char *p, *name, *fullname = NULL;
2918 int exists, breakout = FALSE;
2920 if(!*nfldr){
2921 strncpy(nfldr, folder, len_nfldr-1);
2922 nfldr[len_nfldr-1] = '\0';
2925 save_hist(history, nfldr, 0, (void *) *cntxt);
2927 if(!(name = folder_is_nick(nfldr, FOLDERS(*cntxt), 0)))
2928 name = nfldr;
2930 if(update_folder_spec(expanded, sizeof(expanded), name)){
2931 strncpy(name = nfldr, expanded, len_nfldr-1);
2932 nfldr[len_nfldr-1] = '\0';
2935 exists = folder_name_exists(*cntxt, name, &fullname);
2937 if(exists == FEX_ERROR){
2938 q_status_message1(SM_ORDER, 0, 3,
2939 _("Problem accessing folder \"%s\""),
2940 nfldr);
2941 done--;
2943 else{
2944 if(fullname){
2945 strncpy(name = nfldr, fullname, len_nfldr-1);
2946 nfldr[len_nfldr-1] = '\0';
2947 fs_give((void **) &fullname);
2948 breakout = TRUE;
2951 if(exists & FEX_ISFILE){
2952 done++;
2954 else if((exists & FEX_ISDIR)){
2955 char tmp[MAILTMPLEN];
2957 tc = *cntxt;
2958 if(breakout){
2959 CONTEXT_S *fake_context;
2960 size_t l;
2962 strncpy(tmp, name, sizeof(tmp));
2963 tmp[sizeof(tmp)-2-1] = '\0';
2964 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
2965 if(l < sizeof(tmp)){
2966 tmp[l] = tc->dir->delim;
2967 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
2970 else
2971 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
2973 tmp[sizeof(tmp)-1] = '\0';
2975 fake_context = new_context(tmp, 0);
2976 nfldr[0] = '\0';
2977 done = display_folder_list(&fake_context, nfldr,
2978 1, folders_for_save);
2979 free_context(&fake_context);
2981 else if(tc->dir->delim
2982 && (p = strrindex(name, tc->dir->delim))
2983 && *(p+1) == '\0')
2984 done = display_folder_list(cntxt, nfldr,
2985 1, folders_for_save);
2986 else{
2987 q_status_message1(SM_ORDER, 3, 3,
2988 _("\"%s\" is a directory"), name);
2989 if(tc->dir->delim
2990 && !((p=strrindex(name, tc->dir->delim)) && *(p+1) == '\0')){
2991 strncpy(tmp, name, sizeof(tmp));
2992 tmp[sizeof(tmp)-1] = '\0';
2993 snprintf(nfldr, len_nfldr, "%s%c", tmp, tc->dir->delim);
2997 else{ /* Doesn't exist, create! */
2998 if((fullname = folder_as_breakout(*cntxt, name)) != NULL){
2999 strncpy(name = nfldr, fullname, len_nfldr-1);
3000 nfldr[len_nfldr-1] = '\0';
3001 fs_give((void **) &fullname);
3004 switch(create_for_save(*cntxt, name)){
3005 case 1 : /* success */
3006 done++;
3007 break;
3008 case 0 : /* error */
3009 case -1 : /* declined */
3010 done--;
3011 break;
3016 break;
3018 /* else fall thru like they cancelled */
3020 case 1 :
3021 cmd_cancelled("Save message");
3022 done--;
3023 break;
3025 case 2 :
3026 r = display_folder_list(cntxt, nfldr, 0, folders_for_save);
3028 if(r)
3029 done++;
3031 break;
3033 case 3 :
3034 helper(h_save, _("HELP FOR SAVE"), HLPD_SIMPLE);
3035 ps_global->mangled_screen = 1;
3036 break;
3038 case 4 : /* redraw */
3039 break;
3041 case 10 : /* previous collection */
3042 for(tc = (*cntxt)->prev; tc; tc = tc->prev)
3043 if(!NEWS_TEST(tc))
3044 break;
3046 if(!tc){
3047 CONTEXT_S *tc2;
3049 for(tc2 = (tc = (*cntxt))->next; tc2; tc2 = tc2->next)
3050 if(!NEWS_TEST(tc2))
3051 tc = tc2;
3054 *cntxt = tc;
3055 break;
3057 case 11 : /* next collection */
3058 tc = (*cntxt);
3061 if(((*cntxt) = (*cntxt)->next) == NULL)
3062 (*cntxt) = ps_global->context_list;
3063 while(NEWS_TEST(*cntxt) && (*cntxt) != tc);
3064 break;
3066 case 12 : /* file name completion */
3067 if(!folder_complete(*cntxt, nfldr, len_nfldr, &n)){
3068 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
3069 r = display_folder_list(cntxt, nfldr, 1, folders_for_save);
3070 if(r)
3071 done++; /* bingo! */
3072 else
3073 rc = 0; /* burn last_rc */
3075 else
3076 Writechar(BELL, 0);
3079 break;
3081 case 14 : /* file name completion */
3082 r = display_folder_list(cntxt, nfldr, 2, folders_for_save);
3083 if(r)
3084 done++; /* bingo! */
3085 else
3086 rc = 0; /* burn last_rc */
3088 break;
3090 case 15 : /* Delete / No Delete */
3091 del = (del == NoDel) ? Del : NoDel;
3092 deltext = (del == NoDel) ? " (no delete)" : " (and delete)";
3093 break;
3095 case 16 : /* Preserve Order or not */
3096 pre = (pre == NoPreserve) ? Preserve : NoPreserve;
3097 break;
3099 case 30 :
3100 if((p = get_prev_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3101 strncpy(nfldr, p, len_nfldr);
3102 nfldr[len_nfldr-1] = '\0';
3103 if(history->hist[history->curindex])
3104 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3106 else
3107 Writechar(BELL, 0);
3109 break;
3111 case 31 :
3112 if((p = get_next_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3113 strncpy(nfldr, p, len_nfldr);
3114 nfldr[len_nfldr-1] = '\0';
3115 if(history->hist[history->curindex])
3116 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3118 else
3119 Writechar(BELL, 0);
3121 break;
3123 default :
3124 alpine_panic("Unhandled case");
3125 break;
3128 last_rc = rc;
3131 ps_global->mangled_footer = 1;
3133 if(done < 0)
3134 return(0);
3136 if(*nfldr){
3137 strncpy(ps_global->last_save_folder, nfldr, sizeof(ps_global->last_save_folder)-1);
3138 ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0';
3139 if(*cntxt)
3140 ps_global->last_save_context = *cntxt;
3142 else{
3143 strncpy(nfldr, folder, len_nfldr-1);
3144 nfldr[len_nfldr-1] = '\0';
3147 /* nickname? Copy real name to nfldr */
3148 if(*cntxt
3149 && context_isambig(nfldr)
3150 && (p = folder_is_nick(nfldr, FOLDERS(*cntxt), 0))){
3151 strncpy(nfldr, p, len_nfldr-1);
3152 nfldr[len_nfldr-1] = '\0';
3155 if(dela && (*dela == NoDel || *dela == Del))
3156 *dela = (del == NoDel) ? RetNoDel : RetDel;
3158 if(prea && (*prea == NoPreserve || *prea == Preserve))
3159 *prea = (pre == NoPreserve) ? RetNoPreserve : RetPreserve;
3161 return(1);
3165 /*----------------------------------------------------------------------
3166 Prompt user before implicitly creating a folder for saving
3168 Args: context - context to create folder in
3169 folder - folder name to create
3171 Result: 1 on proceed, -1 on decline, 0 on error
3173 ----*/
3175 create_for_save_prompt(CONTEXT_S *context, char *folder, int sequence_sensitive)
3177 if(context && ps_global->context_list->next && context_isambig(folder)){
3178 if(context->use & CNTXT_INCMNG){
3179 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3180 _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
3181 folder, (strlen(folder) > 15) ? "..." : "");
3182 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
3183 return(0); /* error */
3186 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3187 _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
3188 folder, (strlen(folder) > 15) ? "..." : "",
3189 context->nickname,
3190 (strlen(context->nickname) > 15) ? "..." : "");
3192 else
3193 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
3194 _("Folder \"%.40s%s\" doesn't exist. Create"),
3195 folder, strlen(folder) > 40 ? "..." : "");
3197 if(want_to(tmp_20k_buf, 'y', 'n',
3198 NO_HELP, (sequence_sensitive) ? WT_SEQ_SENSITIVE : WT_NORM) != 'y'){
3199 cmd_cancelled("Save message");
3200 return(-1);
3203 return(1);
3208 /*----------------------------------------------------------------------
3209 Expunge messages from current folder
3211 Args: state -- pointer to struct holding a bunch of pine state
3212 msgmap -- table mapping msg nums to c-client sequence nums
3213 qline -- screen line to ask questions on
3214 agg -- boolean indicating we're to operate on aggregate set
3216 Result:
3217 ----*/
3219 cmd_expunge(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int agg)
3221 long del_count, prefilter_del_count;
3222 int we_cancel = 0, rv = 0;
3223 char prompt[MAX_SCREEN_COLS+1];
3224 char *sequence;
3225 COLOR_PAIR *lastc = NULL;
3227 dprint((2, "\n - expunge -\n"));
3229 del_count = 0;
3231 sequence = MCMD_ISAGG(agg) ? selected_sequence(stream, msgmap, NULL, 0) : NULL;
3233 if(MCMD_ISAGG(agg)){
3234 long i;
3235 MESSAGECACHE *mc;
3236 for(i = 1L; i <= stream->nmsgs; i++){
3237 if((mc = mail_elt(stream, i)) != NULL
3238 && mc->sequence && mc->deleted)
3239 del_count++;
3241 if(del_count == 0){
3242 q_status_message(SM_ORDER, 0, 4,
3243 _("No selected messages are deleted"));
3244 return 0;
3246 } else {
3247 if(!any_messages(msgmap, NULL, "to Expunge"))
3248 return rv;
3251 if(IS_NEWS(stream) && stream->rdonly){
3252 if(!MCMD_ISAGG(agg))
3253 del_count = count_flagged(stream, F_DEL);
3254 if(del_count > 0L){
3255 state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3256 snprintf(prompt, sizeof(prompt), "Exclude %ld message%s from %.*s", del_count,
3257 plural(del_count), MAX_SCREEN_COLS+1-40,
3258 pretty_fn(state->cur_folder));
3259 prompt[sizeof(prompt)-1] = '\0';
3260 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3261 || (F_ON(F_AUTO_EXPUNGE, state)
3262 && (state->context_current
3263 && (state->context_current->use & CNTXT_INCMNG))
3264 && context_isambig(state->cur_folder))
3265 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
3267 if(F_ON(F_NEWS_CROSS_DELETE, state))
3268 cross_delete_crossposts(stream);
3270 msgno_exclude_deleted(stream, msgmap, sequence);
3271 clear_index_cache(stream, 0);
3274 * This is kind of surprising at first. For most sort
3275 * orders, if the whole set is sorted, then any subset
3276 * is also sorted. Not so for threaded sorts.
3278 if(SORT_IS_THREADED(msgmap))
3279 refresh_sort(stream, msgmap, SRT_NON);
3281 state->mangled_body = 1;
3282 state->mangled_header = 1;
3283 q_status_message2(SM_ORDER, 0, 4,
3284 "%s message%s excluded",
3285 long2string(del_count),
3286 plural(del_count));
3288 else
3289 any_messages(NULL, NULL, "Excluded");
3291 else
3292 any_messages(NULL, "deleted", "to Exclude");
3294 return del_count;
3296 else if(READONLY_FOLDER(stream)){
3297 q_status_message(SM_ORDER, 0, 4,
3298 _("Can't expunge. Folder is read-only"));
3299 return del_count;
3302 if(!MCMD_ISAGG(agg)){
3303 prefilter_del_count = count_flagged(stream, F_DEL|F_NOFILT);
3304 mail_expunge_prefilter(stream, MI_NONE);
3305 del_count = count_flagged(stream, F_DEL|F_NOFILT);
3308 if(del_count != 0){
3309 int ret;
3310 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3311 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3312 snprintf(prompt, sizeof(prompt), "Expunge %ld message%s from %.*s", del_count,
3313 plural(del_count), MAX_SCREEN_COLS+1-40,
3314 pretty_fn((char *) fname));
3315 if(fname) fs_give((void **)&fname);
3316 prompt[sizeof(prompt)-1] = '\0';
3317 state->mangled_footer = 1;
3319 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3320 || (F_ON(F_AUTO_EXPUNGE, state)
3321 && ((!strucmp(state->cur_folder,state->inbox_name))
3322 || (state->context_current->use & CNTXT_INCMNG))
3323 && context_isambig(state->cur_folder))
3324 || (ret=want_to(prompt, 'y', 0, NO_HELP, WT_NORM)) == 'y')
3325 ret = 'y';
3327 if(ret == 'x')
3328 cmd_cancelled("Expunge");
3330 if(ret != 'y')
3331 return 0;
3334 dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
3335 mn_get_total(msgmap), mn_get_cur(msgmap), del_count));
3337 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3338 state->VAR_TITLE_BACK_COLOR,
3339 PSC_REV|PSC_RET);
3341 PutLine0(0, 0, "**"); /* indicate delay */
3343 if(lastc){
3344 (void)pico_set_colorp(lastc, PSC_NONE);
3345 free_color_pair(&lastc);
3348 MoveCursor(state->ttyo->screen_rows -FOOTER_ROWS(state), 0);
3349 fflush(stdout);
3351 we_cancel = busy_cue(_("Expunging"), NULL, 1);
3353 if(cmd_expunge_work(stream, msgmap, sequence))
3354 state->mangled_body = 1;
3356 if(sequence)
3357 fs_give((void **)&sequence);
3359 if(we_cancel)
3360 cancel_busy_cue((sp_expunge_count(stream) > 0) ? 0 : -1);
3362 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3363 state->VAR_TITLE_BACK_COLOR,
3364 PSC_REV|PSC_RET);
3365 PutLine0(0, 0, " "); /* indicate delay's over */
3367 if(lastc){
3368 (void)pico_set_colorp(lastc, PSC_NONE);
3369 free_color_pair(&lastc);
3372 fflush(stdout);
3374 if(sp_expunge_count(stream) > 0){
3376 * This is kind of surprising at first. For most sort
3377 * orders, if the whole set is sorted, then any subset
3378 * is also sorted. Not so for threaded sorts.
3380 if(SORT_IS_THREADED(msgmap))
3381 refresh_sort(stream, msgmap, SRT_NON);
3383 else{
3384 if(del_count){
3385 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3386 q_status_message1(SM_ORDER, 0, 3,
3387 _("No messages expunged from folder \"%s\""),
3388 pretty_fn((char *) fname));
3389 if(fname) fs_give((void **)&fname);
3391 else if(!prefilter_del_count)
3392 q_status_message(SM_ORDER, 0, 3,
3393 _("No messages marked deleted. No messages expunged."));
3395 return del_count;
3399 /*----------------------------------------------------------------------
3400 Expunge_and_close callback to prompt user for confirmation
3402 Args: stream -- folder's stream
3403 folder -- name of folder containing folders
3404 deleted -- number of del'd msgs
3406 Result: 'y' to continue with expunge
3407 ----*/
3409 expunge_prompt(MAILSTREAM *stream, char *folder, long int deleted)
3411 long max_folder;
3412 int charcnt = 0;
3413 char prompt_b[MAX_SCREEN_COLS+1], temp[MAILTMPLEN+1], buff[MAX_SCREEN_COLS+1];
3414 char *short_folder_name;
3416 if(deleted == 1)
3417 charcnt = 1;
3418 else{
3419 snprintf(temp, sizeof(temp), "%ld", deleted);
3420 charcnt = strlen(temp)+1;
3423 max_folder = MAX(1,MAXPROMPT - (36+charcnt));
3424 strncpy(temp, folder, sizeof(temp));
3425 temp[sizeof(temp)-1] = '\0';
3426 short_folder_name = short_str(temp,buff,sizeof(buff),max_folder,FrontDots);
3428 if(IS_NEWS(stream))
3429 snprintf(prompt_b, sizeof(prompt_b),
3430 "Delete %s%ld message%s from \"%s\"",
3431 (deleted > 1L) ? "all " : "", deleted,
3432 plural(deleted), short_folder_name);
3433 else
3434 snprintf(prompt_b, sizeof(prompt_b),
3435 "Expunge the %ld deleted message%s from \"%s\"",
3436 deleted, deleted == 1 ? "" : "s",
3437 short_folder_name);
3439 return(want_to(prompt_b, 'y', 0, NO_HELP, WT_NORM));
3444 * This is used with multiple append saves. Call it once before
3445 * the series of appends with SSCP_INIT and once after all are
3446 * done with SSCP_END. In between, it is called automatically
3447 * from save_fetch_append or save_fetch_append_cb when we need
3448 * to ask the user if he or she wants to continue even though
3449 * announced message size doesn't match the actual message size.
3450 * As of 2008-02-29 the gmail IMAP server has these size mismatches
3451 * on a regular basis even though the data is ok.
3454 save_size_changed_prompt(long msgno, int flags)
3456 int ret;
3457 char prompt[100];
3458 static int remember_the_yes = 0;
3459 static int possible_corruption = 0;
3460 static ESCKEY_S save_size_opts[] = {
3461 {'y', 'y', "Y", "Yes"},
3462 {'n', 'n', "N", "No"},
3463 {'a', 'a', "A", "yes to All"},
3464 {-1, 0, NULL, NULL}
3467 if(F_ON(F_IGNORE_SIZE, ps_global))
3468 return 'y';
3470 if(flags & SSCP_INIT || flags & SSCP_END){
3471 if(flags & SSCP_END && possible_corruption)
3472 q_status_message(SM_ORDER, 3, 3, "There is possible data corruption, check the results");
3474 remember_the_yes = 0;
3475 possible_corruption = 0;
3476 ps_global->noshow_error = 0;
3477 ps_global->noshow_warn = 0;
3478 return(0);
3481 if(remember_the_yes){
3482 snprintf(prompt, sizeof(prompt),
3483 "Message to save shrank! (msg # %ld): Continuing", msgno);
3484 q_status_message(SM_ORDER, 0, 3, prompt);
3485 display_message('x');
3486 return(remember_the_yes);
3489 snprintf(prompt, sizeof(prompt),
3490 "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno);
3491 ret = radio_buttons(prompt, -FOOTER_ROWS(ps_global), save_size_opts,
3492 'n', 0, h_save_size_changed, RB_NORM);
3494 switch(ret){
3495 case 'a':
3496 remember_the_yes = 'y';
3497 possible_corruption++;
3498 return(remember_the_yes);
3500 case 'y':
3501 possible_corruption++;
3502 return('y');
3504 default:
3505 possible_corruption = 0;
3506 ps_global->noshow_error = 1;
3507 ps_global->noshow_warn = 1;
3508 break;
3511 return('n');
3515 /*----------------------------------------------------------------------
3516 Expunge_and_close callback that happens once the decision to expunge
3517 and close has been made and before expunging and closing begins
3520 Args: stream -- folder's stream
3521 folder -- name of folder containing folders
3522 deleted -- number of del'd msgs
3524 Result: 'y' to continue with expunge
3525 ----*/
3526 void
3527 expunge_and_close_begins(int flags, char *folder)
3529 if(!(flags & EC_NO_CLOSE)){
3530 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
3531 q_status_message1(SM_INFO, 0, 1, "Closing \"%.200s\"...", (char *) fname);
3532 flush_status_messages(1);
3533 if(fname) fs_give((void **)&fname);
3538 /*----------------------------------------------------------------------
3539 Export a message to a plain file in users home directory
3541 Args: state -- pointer to struct holding a bunch of pine state
3542 msgmap -- table mapping msg nums to c-client sequence nums
3543 qline -- screen line to ask questions on
3544 agg -- boolean indicating we're to operate on aggregate set
3546 Result:
3547 ----*/
3549 cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt)
3551 char filename[MAXPATH+1], full_filename[MAXPATH+1], *err;
3552 char nmsgs[80];
3553 int r, leading_nl, failure = 0, orig_errno, rflags = GER_NONE;
3554 int flags = GE_IS_EXPORT | GE_SEQ_SENSITIVE, rv = 0;
3555 ENVELOPE *env;
3556 MESSAGECACHE *mc;
3557 BODY *b;
3558 long i, count = 0L, start_of_append, rawno;
3559 gf_io_t pc;
3560 STORE_S *store;
3561 struct variable *vars = state ? ps_global->vars : NULL;
3562 ESCKEY_S export_opts[5];
3563 static HISTORY_S *history = NULL;
3565 if(state == NULL && msgmap == NULL && qline == 0 && aopt == 0){
3566 if(history != NULL)
3567 free_hist(&history);
3568 return 0;
3571 if(ps_global->restricted){
3572 q_status_message(SM_ORDER, 0, 3,
3573 "Alpine demo can't export messages to files");
3574 return rv;
3577 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
3578 return rv;
3580 export_opts[i = 0].ch = ctrl('T');
3581 export_opts[i].rval = 10;
3582 export_opts[i].name = "^T";
3583 export_opts[i++].label = N_("To Files");
3585 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3586 if(ps_global->VAR_DOWNLOAD_CMD && ps_global->VAR_DOWNLOAD_CMD[0]){
3587 export_opts[i].ch = ctrl('V');
3588 export_opts[i].rval = 12;
3589 export_opts[i].name = "^V";
3590 /* TRANSLATORS: this is an abbreviation for Download Messages */
3591 export_opts[i++].label = N_("Downld Msg");
3593 #endif /* !(DOS || MAC) */
3595 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
3596 export_opts[i].ch = ctrl('I');
3597 export_opts[i].rval = 11;
3598 export_opts[i].name = "TAB";
3599 export_opts[i++].label = N_("Complete");
3602 #if 0
3603 /* Commented out since it's not yet support! */
3604 if(F_ON(F_ENABLE_SUB_LISTS,ps_global)){
3605 export_opts[i].ch = ctrl('X');
3606 export_opts[i].rval = 14;
3607 export_opts[i].name = "^X";
3608 export_opts[i++].label = N_("ListMatches");
3610 #endif
3613 * If message has attachments, add a toggle that will allow the user
3614 * to save all of the attachments to a single directory, using the
3615 * names provided with the attachments or part names. What we'll do is
3616 * export the message as usual, and then export the attachments into
3617 * a subdirectory that did not exist before. The subdir will be named
3618 * something based on the name of the file being saved to, but a
3619 * unique, new name.
3621 if(!MCMD_ISAGG(aopt)
3622 && state->mail_stream
3623 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3624 && rawno <= state->mail_stream->nmsgs
3625 && (env = pine_mail_fetchstructure(state->mail_stream, rawno, &b))
3626 && b
3627 && b->type == TYPEMULTIPART
3628 && b->subtype
3629 && strucmp(b->subtype, "ALTERNATIVE") != 0){
3630 PART *part;
3632 part = b->nested.part; /* 1st part */
3633 if(part && part->next)
3634 flags |= GE_ALLPARTS;
3637 export_opts[i].ch = -1;
3638 filename[0] = '\0';
3640 if(mn_total_cur(msgmap) <= 1L){
3641 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld", mn_get_cur(msgmap));
3642 nmsgs[sizeof(nmsgs)-1] = '\0';
3644 else{
3645 snprintf(nmsgs, sizeof(nmsgs), "%s messages", comatose(mn_total_cur(msgmap)));
3646 nmsgs[sizeof(nmsgs)-1] = '\0';
3649 r = get_export_filename(state, filename, NULL, full_filename,
3650 sizeof(filename), nmsgs, "EXPORT",
3651 export_opts, &rflags, qline, flags, &history);
3653 if(r < 0){
3654 switch(r){
3655 case -1:
3656 cmd_cancelled("Export message");
3657 break;
3659 case -2:
3660 q_status_message1(SM_ORDER, 0, 2,
3661 _("Can't export to file outside of %s"),
3662 VAR_OPER_DIR);
3663 break;
3666 goto fini;
3668 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3669 else if(r == 12){ /* Download */
3670 char cmd[MAXPATH], *tfp = NULL;
3671 int next = 0;
3672 PIPE_S *syspipe;
3673 STORE_S *so;
3674 gf_io_t pc;
3676 if(ps_global->restricted){
3677 q_status_message(SM_ORDER | SM_DING, 3, 3,
3678 "Download disallowed in restricted mode");
3679 goto fini;
3682 err = NULL;
3683 tfp = temp_nam(NULL, "pd");
3684 build_updown_cmd(cmd, sizeof(cmd), ps_global->VAR_DOWNLOAD_CMD_PREFIX,
3685 ps_global->VAR_DOWNLOAD_CMD, tfp);
3686 dprint((1, "Download cmd called: \"%s\"\n", cmd));
3687 if((so = so_get(FileStar, tfp, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){
3688 gf_set_so_writec(&pc, so);
3690 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
3691 if(!(state->mail_stream
3692 && (rawno = mn_m2raw(msgmap, i)) > 0L
3693 && rawno <= state->mail_stream->nmsgs
3694 && (mc = mail_elt(state->mail_stream, rawno))
3695 && mc->valid))
3696 mc = NULL;
3698 if(!(env = pine_mail_fetchstructure(state->mail_stream,
3699 mn_m2raw(msgmap, i), &b))
3700 || !bezerk_delimiter(env, mc, pc, next++)
3701 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
3702 env, b, NULL, FM_NEW_MESS | FM_NOWRAP, pc)){
3703 q_status_message(SM_ORDER | SM_DING, 3, 3,
3704 err = "Error writing tempfile for download");
3705 break;
3709 gf_clear_so_writec(so);
3710 if(so_give(&so)){ /* close file */
3711 if(!err)
3712 err = "Error writing tempfile for download";
3715 if(!err){
3716 if((syspipe = open_system_pipe(cmd, NULL, NULL,
3717 PIPE_USER | PIPE_RESET,
3718 0, pipe_callback, pipe_report_error)) != NULL)
3719 (void) close_system_pipe(&syspipe, NULL, pipe_callback);
3720 else
3721 q_status_message(SM_ORDER | SM_DING, 3, 3,
3722 err = _("Error running download command"));
3725 else
3726 q_status_message(SM_ORDER | SM_DING, 3, 3,
3727 err = "Error building temp file for download");
3729 if(tfp){
3730 our_unlink(tfp);
3731 fs_give((void **)&tfp);
3734 if(!err)
3735 q_status_message(SM_ORDER, 0, 3, _("Download Command Completed"));
3737 goto fini;
3739 #endif /* !(DOS || MAC) */
3742 if(rflags & GER_APPEND)
3743 leading_nl = 1;
3744 else
3745 leading_nl = 0;
3747 dprint((5, "Opening file \"%s\" for export\n",
3748 full_filename ? full_filename : "?"));
3750 if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE))){
3751 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3752 /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
3753 _("Error opening file \"%s\" to export message: %s"),
3754 full_filename, error_description(errno));
3755 goto fini;
3757 else
3758 gf_set_so_writec(&pc, store);
3760 err = NULL;
3761 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), count++){
3762 env = pine_mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i),
3763 &b);
3764 if(!env) {
3765 err = _("Can't export message. Error accessing mail folder");
3766 failure = 1;
3767 break;
3770 if(!(state->mail_stream
3771 && (rawno = mn_m2raw(msgmap, i)) > 0L
3772 && rawno <= state->mail_stream->nmsgs
3773 && (mc = mail_elt(state->mail_stream, rawno))
3774 && mc->valid))
3775 mc = NULL;
3777 start_of_append = so_tell(store);
3778 if(!bezerk_delimiter(env, mc, pc, leading_nl)
3779 || !format_message(mn_m2raw(msgmap, i), env, b, NULL,
3780 FM_NEW_MESS | FM_NOWRAP, pc)){
3781 orig_errno = errno; /* save incase things are really bad */
3782 failure = 1; /* pop out of here */
3783 break;
3786 leading_nl = 1;
3789 gf_clear_so_writec(store);
3790 if(so_give(&store)) /* release storage */
3791 failure++;
3793 if(failure){
3794 our_truncate(full_filename, (off_t)start_of_append);
3795 if(err){
3796 dprint((1, "FAILED Export: fetch(%ld): %s\n",
3797 i, err ? err : "?"));
3798 q_status_message(SM_ORDER | SM_DING, 3, 4, err);
3800 else{
3801 dprint((1, "FAILED Export: file \"%s\" : %s\n",
3802 full_filename ? full_filename : "?",
3803 error_description(orig_errno)));
3804 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3805 /* TRANSLATORS: Error exporting to <filename>: <error text> */
3806 _("Error exporting to \"%s\" : %s"),
3807 filename, error_description(orig_errno));
3810 else{
3811 if(rflags & GER_ALLPARTS && full_filename[0]){
3812 char dir[MAXPATH+1];
3813 char lfile[MAXPATH+1];
3814 int ok = 0, tries = 0, saved = 0, errs = 0, counter = 2;
3815 ATTACH_S *a;
3818 * Now we want to save all of the attachments to a subdirectory.
3819 * To make it easier for us and probably easier for the user, and
3820 * to prevent the user from shooting himself in the foot, we
3821 * make a new subdirectory so that we can't possibly step on
3822 * any existing files, and we don't need any interaction with the
3823 * user while saving.
3825 * We'll just use the directory name full_filename.d or if that
3826 * already exists and isn't empty, we'll try adding a suffix to
3827 * that until we get something to use.
3830 if(strlen(full_filename) + strlen(".d") + 1 > sizeof(dir)){
3831 q_status_message1(SM_ORDER | SM_DING, 3, 4,
3832 _("Can't save attachments, filename too long: %s"),
3833 full_filename);
3834 goto fini;
3837 ok = 0;
3838 snprintf(dir, sizeof(dir), "%s.d", full_filename);
3839 dir[sizeof(dir)-1] = '\0';
3841 do {
3842 tries++;
3843 switch(r = is_writable_dir(dir)){
3844 case 0: /* exists and is a writable dir */
3846 * We could figure out if it is empty and use it in
3847 * that case, but that sounds like a lot of work, so
3848 * just fall through to default.
3851 default:
3852 if(strlen(full_filename) + strlen(".d") + 1 +
3853 1 + strlen(long2string((long) tries)) > sizeof(dir)){
3854 q_status_message(SM_ORDER | SM_DING, 3, 4,
3855 "Problem saving attachments");
3856 goto fini;
3859 snprintf(dir, sizeof(dir), "%s.d_%s", full_filename,
3860 long2string((long) tries));
3861 dir[sizeof(dir)-1] = '\0';
3862 break;
3864 case 3: /* doesn't exist, that's good! */
3865 /* make new directory */
3866 ok++;
3867 break;
3869 } while(!ok && tries < 1000);
3871 if(tries >= 1000){
3872 q_status_message(SM_ORDER | SM_DING, 3, 4,
3873 _("Problem saving attachments"));
3874 goto fini;
3877 /* create the new directory */
3878 if(our_mkdir(dir, 0700)){
3879 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3880 _("Problem saving attachments: %s: %s"), dir,
3881 error_description(errno));
3882 goto fini;
3885 if(!(state->mail_stream
3886 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3887 && rawno <= state->mail_stream->nmsgs
3888 && (env=pine_mail_fetchstructure(state->mail_stream,rawno,&b))
3889 && b)){
3890 q_status_message(SM_ORDER | SM_DING, 3, 4,
3891 _("Problem reading message"));
3892 goto fini;
3895 zero_atmts(state->atmts);
3896 describe_mime(b, "", 1, 1, 0, 0);
3898 a = state->atmts;
3899 if(a && a->description) /* skip main body part */
3900 a++;
3902 for(; a->description != NULL; a++){
3903 /* skip over these parts of the message */
3904 if(MIME_MSG_A(a) || MIME_DGST_A(a) || MIME_VCARD_A(a))
3905 continue;
3907 lfile[0] = '\0';
3908 (void) get_filename_parameter(lfile, sizeof(lfile), a->body, NULL);
3910 if(lfile[0] == '\0'){ /* MAXPATH + 1 = sizeof(lfile) */
3911 snprintf(lfile, sizeof(lfile), "part_%.*s", MAXPATH+1-6,
3912 a->number ? a->number : "?");
3913 lfile[sizeof(lfile)-1] = '\0';
3916 if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + 1
3917 > sizeof(filename)){
3918 dprint((2,
3919 "FAILED Att Export: name too long: %s\n",
3920 dir, S_FILESEP, lfile));
3921 errs++;
3922 continue;
3925 /* although files are being saved in a unique directory, there is
3926 * no guarantee that attachment names have unique names, so we have
3927 * to make sure that we are not constantly rewriting the same file name
3928 * over and over. In order to avoid this we test if the file already exists,
3929 * and if so, we write a counter name in the file name, just before the
3930 * extension of the file, and separate it with an underscore.
3932 snprintf(filename, sizeof(filename), "%s%s%s", dir, S_FILESEP, lfile);
3933 filename[sizeof(filename)-1] = '\0';
3934 while((ok = can_access(filename, ACCESS_EXISTS)) == 0 && errs == 0){
3935 char *ext;
3936 snprintf(filename, sizeof(filename), "%d", counter);
3937 if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + strlen(filename) + 2
3938 > sizeof(filename)){
3939 dprint((2,
3940 "FAILED Att Export: name too long: %s\n",
3941 dir, S_FILESEP, lfile));
3942 errs++;
3943 continue;
3945 if((ext = strrchr(lfile, '.')) != NULL)
3946 *ext = '\0';
3947 snprintf(filename, sizeof(filename), "%s%s%s%s%d%s%s",
3948 dir, S_FILESEP, lfile,
3949 ext ? "_" : "", counter++, ext ? "." : "", ext ? ext+1 : "");
3950 filename[sizeof(filename)-1] = '\0';
3953 if(write_attachment_to_file(state->mail_stream, rawno,
3954 a, GER_NONE, filename) == 1)
3955 saved++;
3956 else
3957 errs++;
3960 if(errs){
3961 if(saved)
3962 q_status_message1(SM_ORDER, 3, 3,
3963 "Errors saving some attachments, %s attachments saved",
3964 long2string((long) saved));
3965 else
3966 q_status_message(SM_ORDER, 3, 3,
3967 _("Problems saving attachments"));
3969 else{
3970 if(saved)
3971 q_status_message2(SM_ORDER, 0, 3,
3972 /* TRANSLATORS: Saved <how many> attachements to <directory name> */
3973 _("Saved %s attachments to %s"),
3974 long2string((long) saved), dir);
3975 else
3976 q_status_message(SM_ORDER, 3, 3, _("No attachments to save"));
3979 else if(mn_total_cur(msgmap) > 1L)
3980 q_status_message4(SM_ORDER,0,3,
3981 "%s message%s %s to file \"%s\"",
3982 long2string(count), plural(count),
3983 rflags & GER_OVER
3984 ? "overwritten"
3985 : rflags & GER_APPEND ? "appended" : "exported",
3986 filename);
3987 else
3988 q_status_message3(SM_ORDER,0,3,
3989 "Message %s %s to file \"%s\"",
3990 long2string(mn_get_cur(msgmap)),
3991 rflags & GER_OVER
3992 ? "overwritten"
3993 : rflags & GER_APPEND ? "appended" : "exported",
3994 filename);
3995 rv++;
3998 fini:
3999 if(MCMD_ISAGG(aopt))
4000 restore_selected(msgmap);
4002 return rv;
4007 * Ask user what file to export to. Export from srcstore to that file.
4009 * Args ps -- pine struct
4010 * srctext -- pointer to source text
4011 * srctype -- type of that source text
4012 * prompt_msg -- see get_export_filename
4013 * lister_msg -- "
4015 * Returns: != 0 : error
4016 * 0 : ok
4019 simple_export(struct pine *ps, void *srctext, SourceType srctype, char *prompt_msg, char *lister_msg)
4021 int r = 1, rflags = GER_NONE;
4022 char filename[MAXPATH+1], full_filename[MAXPATH+1];
4023 STORE_S *store = NULL;
4024 struct variable *vars = ps ? ps->vars : NULL;
4025 static HISTORY_S *history = NULL;
4026 static ESCKEY_S simple_export_opts[] = {
4027 {ctrl('T'), 10, "^T", N_("To Files")},
4028 {-1, 0, NULL, NULL},
4029 {-1, 0, NULL, NULL}};
4031 if(ps == NULL && srctext == NULL && srctype == CharStar
4032 && prompt_msg == NULL && lister_msg == NULL){
4033 if(history != NULL)
4034 free_hist(&history);
4035 return 0;
4038 if(F_ON(F_ENABLE_TAB_COMPLETE,ps)){
4039 simple_export_opts[r].ch = ctrl('I');
4040 simple_export_opts[r].rval = 11;
4041 simple_export_opts[r].name = "TAB";
4042 simple_export_opts[r].label = N_("Complete");
4045 if(!srctext){
4046 q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
4047 r = -3;
4048 goto fini;
4051 simple_export_opts[++r].ch = -1;
4052 filename[0] = '\0';
4053 full_filename[0] = '\0';
4055 r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename),
4056 prompt_msg, lister_msg, simple_export_opts, &rflags,
4057 -FOOTER_ROWS(ps), GE_IS_EXPORT, &history);
4059 if(r < 0)
4060 goto fini;
4061 else if(!full_filename[0]){
4062 r = -1;
4063 goto fini;
4066 dprint((5, "Opening file \"%s\" for export\n",
4067 full_filename ? full_filename : "?"));
4069 if((store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
4070 char *pipe_err;
4071 gf_io_t pc, gc;
4073 gf_set_so_writec(&pc, store);
4074 gf_set_readc(&gc, srctext, (srctype == CharStar)
4075 ? strlen((char *)srctext)
4076 : 0L,
4077 srctype, 0);
4078 gf_filter_init();
4079 if((pipe_err = gf_pipe(gc, pc)) != NULL){
4080 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4081 /* TRANSLATORS: Problem saving to <filename>: <error text> */
4082 _("Problem saving to \"%s\": %s"),
4083 filename, pipe_err);
4084 r = -3;
4086 else
4087 r = 0;
4089 gf_clear_so_writec(store);
4090 if(so_give(&store)){
4091 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4092 _("Problem saving to \"%s\": %s"),
4093 filename, error_description(errno));
4094 r = -3;
4097 else{
4098 q_status_message2(SM_ORDER | SM_DING, 3, 4,
4099 _("Error opening file \"%s\" for export: %s"),
4100 full_filename, error_description(errno));
4101 r = -3;
4104 fini:
4105 switch(r){
4106 case 0:
4107 /* overloading full_filename */
4108 snprintf(full_filename, sizeof(full_filename), "%c%s",
4109 (prompt_msg && prompt_msg[0])
4110 ? (islower((unsigned char)prompt_msg[0])
4111 ? toupper((unsigned char)prompt_msg[0]) : prompt_msg[0])
4112 : 'T',
4113 (prompt_msg && prompt_msg[0]) ? prompt_msg+1 : "ext");
4114 full_filename[sizeof(full_filename)-1] = '\0';
4115 q_status_message3(SM_ORDER,0,2,"%s %s to \"%s\"",
4116 full_filename,
4117 rflags & GER_OVER
4118 ? "overwritten"
4119 : rflags & GER_APPEND ? "appended" : "exported",
4120 filename);
4121 break;
4123 case -1:
4124 cmd_cancelled("Export");
4125 break;
4127 case -2:
4128 q_status_message1(SM_ORDER, 0, 2,
4129 _("Can't export to file outside of %s"), VAR_OPER_DIR);
4130 break;
4133 ps->mangled_footer = 1;
4134 return(r);
4139 * Ask user what file to export to.
4141 * filename -- On input, this is the filename to start with. On exit,
4142 * this is the filename chosen. (but this isn't used)
4143 * deefault -- This is the default value if user hits return. The
4144 * prompt will have [deefault] added to it automatically.
4145 * full_filename -- This is the full filename on exit.
4146 * len -- Minimum length of _both_ filename and full_filename.
4147 * prompt_msg -- Message to insert in prompt.
4148 * lister_msg -- Message to insert in file_lister.
4149 * opts -- Key options.
4150 * There is a tangled relationship between the callers
4151 * and this routine as far as opts are concerned. Some
4152 * of the opts are handled here. In particular, r == 3,
4153 * r == 10, r == 11, and r == 13 are all handled here.
4154 * Don't use those values unless you want what happens
4155 * here. r == 12 and others are handled by the caller.
4156 * rflags -- Return flags
4157 * GER_OVER - overwrite of existing file
4158 * GER_APPEND - append of existing file
4159 * else file did not exist before
4161 * GER_ALLPARTS - AllParts toggle was turned on
4163 * qline -- Command line to prompt on.
4164 * flags -- Logically OR'd flags
4165 * GE_IS_EXPORT - The command was an Export command
4166 * so the prompt should include
4167 * EXPORT:.
4168 * GE_SEQ_SENSITIVE - The command that got us here is
4169 * sensitive to sequence number changes
4170 * caused by unsolicited expunges.
4171 * GE_NO_APPEND - We will not allow append to an
4172 * existing file, only removal of the
4173 * file if it exists.
4174 * GE_IS_IMPORT - We are selecting for reading.
4175 * No overwriting or checking for
4176 * existence at all. Don't use this
4177 * together with GE_NO_APPEND.
4178 * GE_ALLPARTS - Turn on AllParts toggle.
4179 * GE_BINARY - Turn on Binary toggle.
4181 * Returns: -1 cancelled
4182 * -2 prohibited by VAR_OPER_DIR
4183 * -3 other error, already reported here
4184 * 0 ok
4185 * 12 user chose 12 command from opts
4188 get_export_filename(struct pine *ps, char *filename, char *deefault,
4189 char *full_filename, size_t len, char *prompt_msg,
4190 char *lister_msg, ESCKEY_S *optsarg, int *rflags,
4191 int qline, int flags, HISTORY_S **history)
4193 char dir[MAXPATH+1], dir2[MAXPATH+1], orig_dir[MAXPATH+1];
4194 char precolon[MAXPATH+1], postcolon[MAXPATH+1];
4195 char filename2[MAXPATH+1], tmp[MAXPATH+1], *fn, *ill;
4196 int l, i, ku = -1, kp = -1, r, fatal, homedir = 0, was_abs_path=0, avail, ret = 0;
4197 int allparts = 0, binary = 0;
4198 char prompt_buf[400];
4199 char def[500];
4200 ESCKEY_S *opts = NULL;
4201 struct variable *vars = ps->vars;
4202 static HISTORY_S *dir_hist = NULL;
4203 static char *last;
4204 int pos, hist_len = 0;
4207 /* we will fake a history with the ps_global->VAR_HISTORY variable
4208 * We fake that we combine this variable into a history variable
4209 * by stacking VAR_HISTORY on top of dir_hist. We keep track of this
4210 * by looking at the variable pos.
4212 if(ps_global->VAR_HISTORY != NULL)
4213 for(hist_len = 0; ps_global->VAR_HISTORY[hist_len]
4214 && ps_global->VAR_HISTORY[hist_len][0]; hist_len++)
4217 pos = hist_len + items_in_hist(dir_hist);
4219 if(flags & GE_ALLPARTS || history || dir_hist){
4221 * Copy the opts and add one to the end of the list.
4223 for(i = 0; optsarg[i].ch != -1; i++)
4226 if(dir_hist || hist_len > 0)
4227 i += 2;
4229 if(history)
4230 i += dir_hist || hist_len > 0 ? 2 : 4;
4232 if(flags & GE_ALLPARTS)
4233 i++;
4235 if(flags & GE_BINARY)
4236 i++;
4238 opts = (ESCKEY_S *) fs_get((i+1) * sizeof(*opts));
4239 memset(opts, 0, (i+1) * sizeof(*opts));
4241 for(i = 0; optsarg[i].ch != -1; i++){
4242 opts[i].ch = optsarg[i].ch;
4243 opts[i].rval = optsarg[i].rval;
4244 opts[i].name = optsarg[i].name; /* no need to make a copy */
4245 opts[i].label = optsarg[i].label; /* " */
4248 if(flags & GE_ALLPARTS){
4249 allparts = i;
4250 opts[i].ch = ctrl('P');
4251 opts[i].rval = 13;
4252 opts[i].name = "^P";
4253 /* TRANSLATORS: Export all attachment parts */
4254 opts[i++].label = N_("AllParts");
4257 if(flags & GE_BINARY){
4258 binary = i;
4259 opts[i].ch = ctrl('R');
4260 opts[i].rval = 15;
4261 opts[i].name = "^R";
4262 opts[i++].label = N_("Binary");
4265 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4266 SIZEOF_20KBUF, filename);
4267 if(strcmp(tmp_20k_buf, filename)){
4268 opts[i].ch = ctrl('N');
4269 opts[i].rval = 40;
4270 opts[i].name = "^N";
4271 opts[i++].label = "Name UTF8";
4274 if(dir_hist || hist_len > 0){
4275 opts[i].ch = ctrl('Y');
4276 opts[i].rval = 32;
4277 opts[i].name = "";
4278 kp = i;
4279 opts[i++].label = "";
4281 opts[i].ch = ctrl('V');
4282 opts[i].rval = 33;
4283 opts[i].name = "";
4284 opts[i++].label = "";
4287 if(history){
4288 opts[i].ch = KEY_UP;
4289 opts[i].rval = 30;
4290 opts[i].name = "";
4291 ku = i;
4292 opts[i++].label = "";
4294 opts[i].ch = KEY_DOWN;
4295 opts[i].rval = 31;
4296 opts[i].name = "";
4297 opts[i++].label = "";
4300 opts[i].ch = -1;
4302 if(history)
4303 init_hist(history, HISTSIZE);
4304 init_hist(&dir_hist, HISTSIZE); /* reset history to the end */
4306 else
4307 opts = optsarg;
4309 if(rflags)
4310 *rflags = GER_NONE;
4312 if(F_ON(F_USE_CURRENT_DIR, ps))
4313 dir[0] = '\0';
4314 else if(VAR_OPER_DIR){
4315 strncpy(dir, VAR_OPER_DIR, sizeof(dir));
4316 dir[sizeof(dir)-1] = '\0';
4318 #if defined(DOS) || defined(OS2)
4319 else if(VAR_FILE_DIR){
4320 strncpy(dir, VAR_FILE_DIR, sizeof(dir));
4321 dir[sizeof(dir)-1] = '\0';
4323 #endif
4324 else{
4325 dir[0] = '~';
4326 dir[1] = '\0';
4327 homedir=1;
4329 strncpy(orig_dir, dir, sizeof(orig_dir));
4330 orig_dir[sizeof(orig_dir)-1] = '\0';
4332 postcolon[0] = '\0';
4333 strncpy(precolon, dir, sizeof(precolon));
4334 precolon[sizeof(precolon)-1] = '\0';
4335 if(deefault){
4336 strncpy(def, deefault, sizeof(def)-1);
4337 def[sizeof(def)-1] = '\0';
4338 removing_leading_and_trailing_white_space(def);
4340 else
4341 def[0] = '\0';
4343 avail = MAX(20, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - MIN_OPT_ENT_WIDTH;
4345 /*---------- Prompt the user for the file name -------------*/
4346 while(1){
4347 int oeflags;
4348 char dirb[50], fileb[50];
4349 int l1, l2, l3, l4, l5, needed;
4350 char *p, p1[100], p2[100], *p3, p4[100], p5[100];
4352 snprintf(p1, sizeof(p1), "%sCopy ",
4353 (flags & GE_IS_EXPORT) ? "EXPORT: " :
4354 (flags & GE_IS_IMPORT) ? "IMPORT: " : "SAVE: ");
4355 p1[sizeof(p1)-1] = '\0';
4356 l1 = strlen(p1);
4358 strncpy(p2, prompt_msg ? prompt_msg : "", sizeof(p2)-1);
4359 p2[sizeof(p2)-1] = '\0';
4360 l2 = strlen(p2);
4362 if(rflags && *rflags & GER_ALLPARTS)
4363 p3 = " (and atts)";
4364 else
4365 p3 = "";
4367 l3 = strlen(p3);
4369 snprintf(p4, sizeof(p4), " %s file%s%s",
4370 (flags & GE_IS_IMPORT) ? "from" : "to",
4371 is_absolute_path(filename) ? "" : " in ",
4372 is_absolute_path(filename) ? "" :
4373 (!dir[0] ? "current directory"
4374 : (dir[0] == '~' && !dir[1]) ? "home directory"
4375 : short_str(dir,dirb,sizeof(dirb),30,FrontDots)));
4376 p4[sizeof(p4)-1] = '\0';
4377 l4 = strlen(p4);
4379 snprintf(p5, sizeof(p5), "%s%s%s: ",
4380 *def ? " [" : "",
4381 *def ? short_str(def,fileb,sizeof(fileb),40,EndDots) : "",
4382 *def ? "]" : "");
4383 p5[sizeof(p5)-1] = '\0';
4384 l5 = strlen(p5);
4386 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4387 snprintf(p4, sizeof(p4), " %s file%s%s",
4388 (flags & GE_IS_IMPORT) ? "from" : "to",
4389 is_absolute_path(filename) ? "" : " in ",
4390 is_absolute_path(filename) ? "" :
4391 (!dir[0] ? "current dir"
4392 : (dir[0] == '~' && !dir[1]) ? "home dir"
4393 : short_str(dir,dirb,sizeof(dirb),10,FrontDots)));
4394 p4[sizeof(p4)-1] = '\0';
4395 l4 = strlen(p4);
4398 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4399 snprintf(p5, sizeof(p5), "%s%s%s: ",
4400 *def ? " [" : "",
4401 *def ? short_str(def,fileb,sizeof(fileb),
4402 MAX(15,l5-5-needed),EndDots) : "",
4403 *def ? "]" : "");
4404 p5[sizeof(p5)-1] = '\0';
4405 l5 = strlen(p5);
4408 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l2 > 0){
4411 * 14 is about the shortest we can make this, because there are
4412 * fixed length strings of length 14 coming in here.
4414 p = short_str(prompt_msg, p2, sizeof(p2), MAX(14,l2-needed), FrontDots);
4415 if(p != p2){
4416 strncpy(p2, p, sizeof(p2)-1);
4417 p2[sizeof(p2)-1] = '\0';
4420 l2 = strlen(p2);
4423 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4424 strncpy(p1, "Copy ", sizeof(p1)-1);
4425 p1[sizeof(p1)-1] = '\0';
4426 l1 = strlen(p1);
4429 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4430 snprintf(p5, sizeof(p5), "%s%s%s: ",
4431 *def ? " [" : "",
4432 *def ? short_str(def,fileb, sizeof(fileb),
4433 MAX(10,l5-5-needed),EndDots) : "",
4434 *def ? "]" : "");
4435 p5[sizeof(p5)-1] = '\0';
4436 l5 = strlen(p5);
4439 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l3 > 0){
4440 if(needed <= l3 - strlen(" (+ atts)"))
4441 p3 = " (+ atts)";
4442 else if(needed <= l3 - strlen(" (atts)"))
4443 p3 = " (atts)";
4444 else if(needed <= l3 - strlen(" (+)"))
4445 p3 = " (+)";
4446 else if(needed <= l3 - strlen("+"))
4447 p3 = "+";
4448 else
4449 p3 = "";
4451 l3 = strlen(p3);
4454 snprintf(prompt_buf, sizeof(prompt_buf), "%s%s%s%s%s", p1, p2, p3, p4, p5);
4455 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4457 if(kp >= 0){
4458 if(items_in_hist(dir_hist) > 0 || hist_len > 0){ /* any directories */
4459 opts[kp].name = "^Y";
4460 opts[kp].label = "Prev Dir";
4461 opts[kp+1].name = "^V";
4462 opts[kp+1].label = "Next Dir";
4464 else{
4465 opts[kp].name = "";
4466 opts[kp].label = "";
4467 opts[kp+1].name = "";
4468 opts[kp+1].label = "";
4472 if(ku >= 0){
4473 if(items_in_hist(*history) > 0){
4474 opts[ku].name = HISTORY_UP_KEYNAME;
4475 opts[ku].label = HISTORY_KEYLABEL;
4476 opts[ku+1].name = HISTORY_DOWN_KEYNAME;
4477 opts[ku+1].label = HISTORY_KEYLABEL;
4479 else{
4480 opts[ku].name = "";
4481 opts[ku].label = "";
4482 opts[ku+1].name = "";
4483 opts[ku+1].label = "";
4487 oeflags = OE_APPEND_CURRENT |
4488 ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0);
4489 r = optionally_enter(filename, qline, 0, len, prompt_buf,
4490 opts, NO_HELP, &oeflags);
4492 dprint((2, "\n - export_filename = \"%s\", r = %d -\n", filename, r));
4493 /*--- Help ----*/
4494 if(r == 3){
4496 * Helps may not be right if you add another caller or change
4497 * things. Check it out.
4499 if(flags & GE_IS_IMPORT)
4500 helper(h_ge_import, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE);
4501 else if(flags & GE_ALLPARTS)
4502 helper(h_ge_allparts, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4503 else
4504 helper(h_ge_export, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4506 ps->mangled_screen = 1;
4508 continue;
4510 else if(r == 10 || r == 11){ /* Browser or File Completion */
4511 if(filename[0]=='~'){
4512 if(filename[1] == C_FILESEP && filename[2]!='\0'){
4513 precolon[0] = '~';
4514 precolon[1] = '\0';
4515 for(i=0; filename[i+2] != '\0' && i+2 < len-1; i++)
4516 filename[i] = filename[i+2];
4517 filename[i] = '\0';
4518 strncpy(dir, precolon, sizeof(dir)-1);
4519 dir[sizeof(dir)-1] = '\0';
4521 else if(filename[1]=='\0' ||
4522 (filename[1] == C_FILESEP && filename[2] == '\0')){
4523 precolon[0] = '~';
4524 precolon[1] = '\0';
4525 filename[0] = '\0';
4526 strncpy(dir, precolon, sizeof(dir)-1);
4527 dir[sizeof(dir)-1] = '\0';
4530 else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){
4531 if(homedir){
4532 precolon[0] = '~';
4533 precolon[1] = '\0';
4534 strncpy(dir, precolon, sizeof(dir)-1);
4535 dir[sizeof(dir)-1] = '\0';
4537 else{
4538 precolon[0] = '\0';
4539 dir[0] = '\0';
4542 l = MAXPATH;
4543 dir2[0] = '\0';
4544 strncpy(tmp, filename, sizeof(tmp)-1);
4545 tmp[sizeof(tmp)-1] = '\0';
4546 if(*tmp && is_absolute_path(tmp))
4547 fnexpand(tmp, sizeof(tmp));
4548 if(strncmp(tmp,postcolon, strlen(postcolon)))
4549 postcolon[0] = '\0';
4551 if(*tmp && (fn = last_cmpnt(tmp))){
4552 l -= fn - tmp;
4553 strncpy(filename2, fn, sizeof(filename2)-1);
4554 filename2[sizeof(filename2)-1] = '\0';
4555 if(is_absolute_path(tmp)){
4556 strncpy(dir2, tmp, MIN(fn - tmp, sizeof(dir2)-1));
4557 dir2[MIN(fn - tmp, sizeof(dir2)-1)] = '\0';
4558 #ifdef _WINDOWS
4559 if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){
4560 dir2[2] = '\\';
4561 dir2[3] = '\0';
4563 #endif
4564 strncpy(postcolon, dir2, sizeof(postcolon)-1);
4565 postcolon[sizeof(postcolon)-1] = '\0';
4566 precolon[0] = '\0';
4568 else{
4569 char *p = NULL;
4571 * Just building the directory name in dir2,
4572 * full_filename is overloaded.
4574 snprintf(full_filename, len, "%.*s", (int) MIN(fn-tmp,len-1), tmp);
4575 full_filename[len-1] = '\0';
4576 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4577 postcolon[sizeof(postcolon)-1] = '\0';
4578 build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH)
4579 : (dir[0] == '~' && !dir[1])
4580 ? ps->home_dir
4581 : dir,
4582 full_filename, sizeof(dir2));
4583 if(p)
4584 free(p);
4587 else{
4588 if(is_absolute_path(tmp)){
4589 strncpy(dir2, tmp, sizeof(dir2)-1);
4590 dir2[sizeof(dir2)-1] = '\0';
4591 #ifdef _WINDOWS
4592 if(dir2[2]=='\0' && dir2[1]==':'){
4593 dir2[2]='\\';
4594 dir2[3]='\0';
4595 strncpy(postcolon,dir2,sizeof(postcolon)-1);
4596 postcolon[sizeof(postcolon)-1] = '\0';
4598 #endif
4599 filename2[0] = '\0';
4600 precolon[0] = '\0';
4602 else{
4603 strncpy(filename2, tmp, sizeof(filename2)-1);
4604 filename2[sizeof(filename2)-1] = '\0';
4605 if(!dir[0]){
4606 if(getcwd(dir2, sizeof(dir2)) == NULL)
4607 alpine_panic(_("getcwd() call failed at get_export_filename"));
4609 else if(dir[0] == '~' && !dir[1]){
4610 strncpy(dir2, ps->home_dir, sizeof(dir2)-1);
4611 dir2[sizeof(dir2)-1] = '\0';
4613 else{
4614 strncpy(dir2, dir, sizeof(dir2)-1);
4615 dir2[sizeof(dir2)-1] = '\0';
4618 postcolon[0] = '\0';
4622 build_path(full_filename, dir2, filename2, len);
4623 if(!strcmp(full_filename, dir2))
4624 filename2[0] = '\0';
4625 if(full_filename[strlen(full_filename)-1] == C_FILESEP
4626 && isdir(full_filename,NULL,NULL)){
4627 if(strlen(full_filename) == 1)
4628 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4629 else if(filename2[0])
4630 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4631 postcolon[sizeof(postcolon)-1] = '\0';
4632 strncpy(dir2, full_filename, sizeof(dir2)-1);
4633 dir2[sizeof(dir2)-1] = '\0';
4634 filename2[0] = '\0';
4636 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4637 else if(full_filename[strlen(full_filename)-1] == C_FILESEP){
4638 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4639 postcolon[sizeof(postcolon)-1] = '\0';
4640 strncpy(dir2, full_filename, sizeof(dir2)-1);
4641 dir2[sizeof(dir2)-1] = '\0';
4642 filename2[0] = '\0';
4644 #endif
4645 if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1
4646 && strcmp(dir2+1, ":\\"))
4647 /* last condition to prevent stripping of '\\'
4648 in windows partition */
4649 dir2[strlen(dir2)-1] = '\0';
4651 if(r == 10){ /* File Browser */
4652 r = file_lister(lister_msg ? lister_msg : "EXPORT",
4653 dir2, sizeof(dir2), filename2, sizeof(filename2),
4654 TRUE,
4655 (flags & GE_IS_IMPORT) ? FB_READ : FB_SAVE);
4656 #ifdef _WINDOWS
4657 /* Windows has a special "feature" in which entering the file browser will
4658 change the working directory if the directory is changed at all (even
4659 clicking "Cancel" will change the working directory).
4661 if(F_ON(F_USE_CURRENT_DIR, ps))
4662 (void)getcwd(dir2,sizeof(dir2));
4663 #endif
4664 if(isdir(dir2,NULL,NULL)){
4665 strncpy(precolon, dir2, sizeof(precolon)-1);
4666 precolon[sizeof(precolon)-1] = '\0';
4668 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4669 postcolon[sizeof(postcolon)-1] = '\0';
4670 if(r == 1){
4671 build_path(full_filename, dir2, filename2, len);
4672 if(isdir(full_filename, NULL, NULL)){
4673 strncpy(dir, full_filename, sizeof(dir)-1);
4674 dir[sizeof(dir)-1] = '\0';
4675 filename[0] = '\0';
4677 else{
4678 fn = last_cmpnt(full_filename);
4679 strncpy(dir, full_filename,
4680 MIN(fn - full_filename, sizeof(dir)-1));
4681 dir[MIN(fn - full_filename, sizeof(dir)-1)] = '\0';
4682 if(fn - full_filename > 1)
4683 dir[fn - full_filename - 1] = '\0';
4686 if(!strcmp(dir, ps->home_dir)){
4687 dir[0] = '~';
4688 dir[1] = '\0';
4691 strncpy(filename, fn, len-1);
4692 filename[len-1] = '\0';
4695 else{ /* File Completion */
4696 if(!pico_fncomplete(dir2, filename2, sizeof(filename2)))
4697 Writechar(BELL, 0);
4698 strncat(postcolon, filename2,
4699 sizeof(postcolon)-1-strlen(postcolon));
4700 postcolon[sizeof(postcolon)-1] = '\0';
4702 was_abs_path = is_absolute_path(filename);
4704 if(!strcmp(dir, ps->home_dir)){
4705 dir[0] = '~';
4706 dir[1] = '\0';
4709 strncpy(filename, postcolon, len-1);
4710 filename[len-1] = '\0';
4711 strncpy(dir, precolon, sizeof(dir)-1);
4712 dir[sizeof(dir)-1] = '\0';
4714 if(filename[0] == '~' && !filename[1]){
4715 dir[0] = '~';
4716 dir[1] = '\0';
4717 filename[0] = '\0';
4720 continue;
4722 else if(r == 12){ /* Download, caller handles it */
4723 ret = r;
4724 goto done;
4726 else if(r == 13){ /* toggle AllParts bit */
4727 if(rflags){
4728 if(*rflags & GER_ALLPARTS){
4729 *rflags &= ~GER_ALLPARTS;
4730 opts[allparts].label = N_("AllParts");
4732 else{
4733 *rflags |= GER_ALLPARTS;
4734 /* opposite of All Parts, No All Parts */
4735 opts[allparts].label = N_("NoAllParts");
4739 continue;
4741 #if 0
4742 else if(r == 14){ /* List file names matching partial? */
4743 continue;
4745 #endif
4746 else if(r == 15){ /* toggle Binary bit */
4747 if(rflags){
4748 if(*rflags & GER_BINARY){
4749 *rflags &= ~GER_BINARY;
4750 opts[binary].label = N_("Binary");
4752 else{
4753 *rflags |= GER_BINARY;
4754 opts[binary].label = N_("No Binary");
4758 continue;
4760 else if(r == 1){ /* Cancel */
4761 ret = -1;
4762 goto done;
4764 else if(r == 4){
4765 continue;
4767 else if(r >= 30 && r <= 33){
4768 char *p = NULL;
4770 if(r == 30 || r == 31){
4771 if(history){
4772 if(r == 30)
4773 p = get_prev_hist(*history, filename, 0, NULL);
4774 else if (r == 31)
4775 p = get_next_hist(*history, filename, 0, NULL);
4779 if(r == 32 || r == 33){
4780 int nitems = items_in_hist(dir_hist);
4781 if(dir_hist || hist_len > 0){
4782 if(r == 32){
4783 if(pos > 0)
4784 p = hist_in_pos(--pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4785 else p = last;
4787 else if (r == 33){
4788 if(pos < hist_len + nitems)
4789 p = hist_in_pos(++pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4791 if(p == NULL || *p == '\0')
4792 p = orig_dir;
4795 last = p; /* save it! */
4797 if(p != NULL && *p != '\0'){
4798 if(r == 30 || r == 31){
4799 if((fn = last_cmpnt(p)) != NULL){
4800 strncpy(dir, p, MIN(fn - p, sizeof(dir)-1));
4801 dir[MIN(fn - p, sizeof(dir)-1)] = '\0';
4802 if(fn - p > 1)
4803 dir[fn - p - 1] = '\0';
4804 strncpy(filename, fn, len-1);
4805 filename[len-1] = '\0';
4807 } else { /* r == 32 || r == 33 */
4808 strncpy(dir, p, sizeof(dir)-1);
4809 dir[sizeof(dir)-1] = '\0';
4812 if(!strcmp(dir, ps->home_dir)){
4813 dir[0] = '~';
4814 dir[1] = '\0';
4817 else
4818 Writechar(BELL, 0);
4819 continue;
4821 else if(r == 40){
4822 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4823 SIZEOF_20KBUF, filename);
4824 strncpy(filename, tmp_20k_buf, len);
4825 filename[len-1] = '\0';
4826 continue;
4828 else if(r != 0){
4829 Writechar(BELL, 0);
4830 continue;
4833 removing_leading_and_trailing_white_space(filename);
4835 if(!*filename){
4836 if(!*def){ /* Cancel */
4837 ret = -1;
4838 goto done;
4841 strncpy(filename, def, len-1);
4842 filename[len-1] = '\0';
4845 #if defined(DOS) || defined(OS2)
4846 if(is_absolute_path(filename)){
4847 fixpath(filename, len);
4849 #else
4850 if(filename[0] == '~'){
4851 if(fnexpand(filename, len) == NULL){
4852 char *p = strindex(filename, '/');
4853 if(p != NULL)
4854 *p = '\0';
4855 q_status_message1(SM_ORDER | SM_DING, 3, 3,
4856 _("Error expanding file name: \"%s\" unknown user"),
4857 filename);
4858 continue;
4861 #endif
4863 if(is_absolute_path(filename)){
4864 strncpy(full_filename, filename, len-1);
4865 full_filename[len-1] = '\0';
4867 else{
4868 if(!dir[0])
4869 build_path(full_filename, (char *)getcwd(dir,sizeof(dir)),
4870 filename, len);
4871 else if(dir[0] == '~' && !dir[1])
4872 build_path(full_filename, ps->home_dir, filename, len);
4873 else
4874 build_path(full_filename, dir, filename, len);
4877 if((ill = filter_filename(full_filename, &fatal,
4878 ps_global->restricted || ps_global->VAR_OPER_DIR)) != NULL){
4879 if(fatal){
4880 q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill);
4881 continue;
4883 else{
4884 /* BUG: we should beep when the key's pressed rather than bitch later */
4885 /* Warn and ask for confirmation. */
4886 snprintf(prompt_buf, sizeof(prompt_buf), "File name contains a '%s'. %s anyway",
4887 ill, (flags & GE_IS_EXPORT) ? "Export" : "Save");
4888 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4889 if(want_to(prompt_buf, 'n', 0, NO_HELP,
4890 ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y')
4891 continue;
4895 break; /* Must have got an OK file name */
4898 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){
4899 ret = -2;
4900 goto done;
4903 if(!can_access(full_filename, ACCESS_EXISTS)){
4904 int rbflags;
4905 static ESCKEY_S access_opts[] = {
4906 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4907 a file or append to the end of the file */
4908 {'o', 'o', "O", N_("Overwrite")},
4909 {'a', 'a', "A", N_("Append")},
4910 {-1, 0, NULL, NULL}};
4912 rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0);
4914 if(flags & GE_NO_APPEND){
4915 r = strlen(filename);
4916 snprintf(prompt_buf, sizeof(prompt_buf),
4917 /* TRANSLATORS: asking user whether to overwrite a file or not,
4918 File <filename> already exists. Overwrite it ? */
4919 _("File \"%s%s\" already exists. Overwrite it "),
4920 (r > 20) ? "..." : "",
4921 filename + ((r > 20) ? r - 20 : 0));
4922 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4923 if(want_to(prompt_buf, 'n', 'x', NO_HELP, rbflags) == 'y'){
4924 if(rflags)
4925 *rflags |= GER_OVER;
4927 if(our_unlink(full_filename) < 0){
4928 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4929 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4930 _("Cannot remove old %s: %s"),
4931 full_filename, error_description(errno));
4934 else{
4935 ret = -1;
4936 goto done;
4939 else if(!(flags & GE_IS_IMPORT)){
4940 r = strlen(filename);
4941 snprintf(prompt_buf, sizeof(prompt_buf),
4942 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4943 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4944 (r > 20) ? "..." : "",
4945 filename + ((r > 20) ? r - 20 : 0));
4946 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4947 switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global),
4948 access_opts, 'a', 'x', NO_HELP, rbflags)){
4949 case 'o' :
4950 if(rflags)
4951 *rflags |= GER_OVER;
4953 if(our_truncate(full_filename, (off_t)0) < 0)
4954 /* trouble truncating, but we'll give it a try anyway */
4955 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4956 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
4957 _("Warning: Cannot truncate old %s: %s"),
4958 full_filename, error_description(errno));
4959 break;
4961 case 'a' :
4962 if(rflags)
4963 *rflags |= GER_APPEND;
4965 break;
4967 case 'x' :
4968 default :
4969 ret = -1;
4970 goto done;
4975 done:
4976 if(history && ret == 0){
4977 save_hist(*history, full_filename, 0, NULL);
4978 strncpy(tmp, full_filename, MAXPATH);
4979 tmp[MAXPATH] = '\0';
4980 if((fn = strrchr(tmp, C_FILESEP)) != NULL)
4981 *fn = '\0';
4982 else
4983 tmp[0] = '\0';
4984 if(tmp[0])
4985 save_hist(dir_hist, tmp, 0, NULL);
4988 if(opts && opts != optsarg)
4989 fs_give((void **) &opts);
4991 return(ret);
4995 /*----------------------------------------------------------------------
4996 parse the config'd upload/download command
4998 Args: cmd -- buffer to return command fit for shellin'
4999 prefix --
5000 cfg_str --
5001 fname -- file name to build into the command
5003 Returns: pointer to cmd_str buffer or NULL on real bad error
5005 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
5006 cfg_str is written to standard out right before a successful
5007 return of this function. The call immediately following this
5008 function darn well better be the shell exec...
5009 ----*/
5010 char *
5011 build_updown_cmd(char *cmd, size_t cmdlen, char *prefix, char *cfg_str, char *fname)
5013 char *p;
5014 int fname_found = 0;
5016 if(prefix && *prefix){
5017 /* loop thru replacing all occurances of _FILE_ */
5018 p = strncpy(cmd, prefix, cmdlen);
5019 cmd[cmdlen-1] = '\0';
5020 while((p = strstr(p, "_FILE_")))
5021 rplstr(p, cmdlen-(p-cmd), 6, fname);
5023 fputs(cmd, stdout);
5026 /* loop thru replacing all occurances of _FILE_ */
5027 p = strncpy(cmd, cfg_str, cmdlen);
5028 cmd[cmdlen-1] = '\0';
5029 while((p = strstr(p, "_FILE_"))){
5030 rplstr(p, cmdlen-(p-cmd), 6, fname);
5031 fname_found = 1;
5034 if(!fname_found)
5035 snprintf(cmd+strlen(cmd), cmdlen-strlen(cmd), " %s", fname);
5037 cmd[cmdlen-1] = '\0';
5039 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
5040 cmd ? cmd : "?"));
5041 return(cmd);
5045 /*----------------------------------------------------------------------
5046 Write a berzerk format message delimiter using the given putc function
5048 Args: e -- envelope of message to write
5049 pc -- function to use
5051 Returns: TRUE if we could write it, FALSE if there was a problem
5053 NOTE: follows delimiter with OS-dependent newline
5054 ----*/
5056 bezerk_delimiter(ENVELOPE *env, MESSAGECACHE *mc, gf_io_t pc, int leading_newline)
5058 MESSAGECACHE telt;
5059 time_t when;
5060 char *p;
5062 /* write "[\n]From mailbox[@host] " */
5063 if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1)
5064 && gf_puts("From ", pc)
5065 && gf_puts((env && env->from) ? env->from->mailbox
5066 : "the-concourse-on-high", pc)
5067 && gf_puts((env && env->from && env->from->host) ? "@" : "", pc)
5068 && gf_puts((env && env->from && env->from->host) ? env->from->host
5069 : "", pc)
5070 && (*pc)(' ')))
5071 return(0);
5073 if(mc && mc->valid)
5074 when = mail_longdate(mc);
5075 else if(env && env->date && env->date[0]
5076 && mail_parse_date(&telt,env->date))
5077 when = mail_longdate(&telt);
5078 else
5079 when = time(0);
5081 p = ctime(&when);
5083 while(p && *p && *p != '\n') /* write date */
5084 if(!(*pc)(*p++))
5085 return(0);
5087 if(!gf_puts(NEWLINE, pc)) /* write terminating newline */
5088 return(0);
5090 return(1);
5094 /*----------------------------------------------------------------------
5095 Execute command to jump to a given message number
5097 Args: qline -- Line to ask question on
5099 Result: returns true if the use selected a new message, false otherwise
5101 ----*/
5102 long
5103 jump_to(MSGNO_S *msgmap, int qline, UCS first_num, SCROLL_S *sparms, CmdWhere in_index)
5105 char jump_num_string[80], *j, prompt[70];
5106 HelpType help;
5107 int rc;
5108 static ESCKEY_S jump_to_key[] = { {0, 0, NULL, NULL},
5109 /* TRANSLATORS: go to First Message */
5110 {ctrl('Y'), 10, "^Y", N_("First Msg")},
5111 {ctrl('V'), 11, "^V", N_("Last Msg")},
5112 {-1, 0, NULL, NULL} };
5114 dprint((4, "\n - jump_to -\n"));
5116 #ifdef DEBUG
5117 if(sparms && sparms->jump_is_debug)
5118 return(get_level(qline, first_num, sparms));
5119 #endif
5121 if(!any_messages(msgmap, NULL, "to Jump to"))
5122 return(0L);
5124 if(first_num && first_num < 0x80 && isdigit((unsigned char) first_num)){
5125 jump_num_string[0] = first_num;
5126 jump_num_string[1] = '\0';
5128 else
5129 jump_num_string[0] = '\0';
5131 if(mn_total_cur(msgmap) > 1L){
5132 snprintf(prompt, sizeof(prompt), "Unselect %s msgs in favor of number to be entered",
5133 comatose(mn_total_cur(msgmap)));
5134 prompt[sizeof(prompt)-1] = '\0';
5135 if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n')
5136 return(0L);
5139 snprintf(prompt, sizeof(prompt), "%s number to jump to : ", in_index == ThrdIndx
5140 ? "Thread"
5141 : "Message");
5142 prompt[sizeof(prompt)-1] = '\0';
5144 help = NO_HELP;
5145 while(1){
5146 int flags = OE_APPEND_CURRENT;
5148 rc = optionally_enter(jump_num_string, qline, 0,
5149 sizeof(jump_num_string), prompt,
5150 jump_to_key, help, &flags);
5151 if(rc == 3){
5152 help = help == NO_HELP
5153 ? (in_index == ThrdIndx ? h_oe_jump_thd : h_oe_jump)
5154 : NO_HELP;
5155 continue;
5157 else if(rc == 10 || rc == 11){
5158 char warning[100];
5159 long closest;
5161 closest = closest_jump_target(rc == 10 ? 1L
5162 : ((in_index == ThrdIndx)
5163 ? msgmap->max_thrdno
5164 : mn_get_total(msgmap)),
5165 ps_global->mail_stream,
5166 msgmap, 0,
5167 in_index, warning, sizeof(warning));
5168 /* ignore warning */
5169 return(closest);
5173 * If we take out the *jump_num_string nonempty test in this if
5174 * then the closest_jump_target routine will offer a jump to the
5175 * last message. However, it is slow because you have to wait for
5176 * the status message and it is annoying for people who hit J command
5177 * by mistake and just want to hit return to do nothing, like has
5178 * always worked. So the test is there for now. Hubert 2002-08-19
5180 * Jumping to first/last message is now possible through ^Y/^V
5181 * commands above. jpf 2002-08-21
5182 * (and through "end" hubert 2006-07-07)
5184 if(rc == 0 && *jump_num_string != '\0'){
5185 removing_leading_and_trailing_white_space(jump_num_string);
5186 for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++)
5189 if(*j != '\0'){
5190 if(!strucmp("end", j))
5191 return((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap));
5193 q_status_message(SM_ORDER | SM_DING, 2, 2,
5194 _("Invalid number entered. Use only digits 0-9"));
5195 jump_num_string[0] = '\0';
5197 else{
5198 char warning[100];
5199 long closest, jump_num;
5201 if(*jump_num_string)
5202 jump_num = atol(jump_num_string);
5203 else
5204 jump_num = -1L;
5206 warning[0] = '\0';
5207 closest = closest_jump_target(jump_num, ps_global->mail_stream,
5208 msgmap,
5209 *jump_num_string ? 0 : 1,
5210 in_index, warning, sizeof(warning));
5211 if(warning[0])
5212 q_status_message(SM_ORDER | SM_DING, 2, 2, warning);
5214 if(closest == jump_num)
5215 return(jump_num);
5217 if(closest == 0L)
5218 jump_num_string[0] = '\0';
5219 else
5220 strncpy(jump_num_string, long2string(closest),
5221 sizeof(jump_num_string));
5224 continue;
5227 if(rc != 4)
5228 break;
5231 return(0L);
5236 * cmd_delete_action - handle msgno advance and such after single message deletion
5238 char *
5239 cmd_delete_action(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
5241 int opts;
5242 long msgno;
5243 char *rv = NULL;
5245 msgno = mn_get_cur(msgmap);
5246 advance_cur_after_delete(state, state->mail_stream, msgmap, in_index);
5248 if(IS_NEWS(state->mail_stream)
5249 || ((state->context_current->use & CNTXT_INCMNG)
5250 && context_isambig(state->cur_folder))){
5252 opts = (NSF_TRUST_FLAGS | NSF_SKIP_CHID);
5253 if(in_index == View)
5254 opts &= ~NSF_SKIP_CHID;
5256 (void)next_sorted_flagged(F_UNDEL|F_UNSEEN, state->mail_stream, msgno, &opts);
5257 if(!(opts & NSF_FLAG_MATCH)){
5258 char nextfolder[MAXPATH];
5260 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
5261 nextfolder[sizeof(nextfolder)-1] = '\0';
5262 rv = next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
5263 state->context_current, NULL, NULL)
5264 ? ". Press TAB for next folder."
5265 : ". No more folders to TAB to.";
5269 return(rv);
5274 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5276 char *
5277 cmd_delete_index(struct pine *state, MSGNO_S *msgmap)
5279 return(cmd_delete_action(state, msgmap,MsgIndx));
5283 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5285 char *
5286 cmd_delete_view(struct pine *state, MSGNO_S *msgmap)
5288 return(cmd_delete_action(state, msgmap, View));
5292 void
5293 advance_cur_after_delete(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, CmdWhere in_index)
5295 long new_msgno, msgno;
5296 int opts;
5298 new_msgno = msgno = mn_get_cur(msgmap);
5299 opts = NSF_TRUST_FLAGS;
5301 if(F_ON(F_DEL_SKIPS_DEL, state)){
5303 if(THREADING() && sp_viewing_a_thread(stream))
5304 opts |= NSF_SKIP_CHID;
5306 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5308 else{
5309 mn_inc_cur(stream, msgmap,
5310 (in_index == View && THREADING()
5311 && sp_viewing_a_thread(stream))
5312 ? MH_THISTHD
5313 : (in_index == View)
5314 ? MH_ANYTHD : MH_NONE);
5315 new_msgno = mn_get_cur(msgmap);
5316 if(new_msgno != msgno)
5317 opts |= NSF_FLAG_MATCH;
5321 * Viewing_a_thread is the complicated case because we want to ignore
5322 * other threads at first and then look in other threads if we have to.
5323 * By ignoring other threads we also ignore collapsed partial threads
5324 * in our own thread.
5326 if(THREADING() && sp_viewing_a_thread(stream) && !(opts & NSF_FLAG_MATCH)){
5327 long rawno, orig_thrdno;
5328 PINETHRD_S *thrd, *topthrd = NULL;
5330 rawno = mn_m2raw(msgmap, msgno);
5331 thrd = fetch_thread(stream, rawno);
5332 if(thrd && thrd->top)
5333 topthrd = fetch_thread(stream, thrd->top);
5335 orig_thrdno = topthrd ? topthrd->thrdno : -1L;
5337 opts = NSF_TRUST_FLAGS;
5338 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5341 * If we got a match, new_msgno may be a message in
5342 * a different thread from the one we are viewing, or it could be
5343 * in a collapsed part of this thread.
5345 if(opts & NSF_FLAG_MATCH){
5346 int ret;
5347 char pmt[128];
5349 topthrd = NULL;
5350 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
5351 if(thrd && thrd->top)
5352 topthrd = fetch_thread(stream, thrd->top);
5355 * If this match is in the same thread we're already in
5356 * then we're done, else we have to ask the user and maybe
5357 * switch threads.
5359 if(!(orig_thrdno > 0L && topthrd
5360 && topthrd->thrdno == orig_thrdno)){
5362 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
5363 if(in_index == View)
5364 snprintf(pmt, sizeof(pmt),
5365 "View message in thread number %.10s",
5366 topthrd ? comatose(topthrd->thrdno) : "?");
5367 else
5368 snprintf(pmt, sizeof(pmt), "View thread number %.10s",
5369 topthrd ? comatose(topthrd->thrdno) : "?");
5371 ret = want_to(pmt, 'y', 'x', NO_HELP, WT_NORM);
5373 else
5374 ret = 'y';
5376 if(ret == 'y'){
5377 unview_thread(state, stream, msgmap);
5378 mn_set_cur(msgmap, new_msgno);
5379 if(THRD_AUTO_VIEW()
5380 && (count_lflags_in_thread(stream, topthrd, msgmap,
5381 MN_NONE) == 1)
5382 && view_thread(state, stream, msgmap, 1)){
5383 if(current_index_state)
5384 msgmap->top_after_thrd = current_index_state->msg_at_top;
5386 state->view_skipped_index = 1;
5387 state->next_screen = mail_view_screen;
5389 else{
5390 view_thread(state, stream, msgmap, 1);
5391 if(current_index_state)
5392 msgmap->top_after_thrd = current_index_state->msg_at_top;
5394 state->next_screen = SCREEN_FUN_NULL;
5397 else
5398 new_msgno = msgno; /* stick with original */
5403 mn_set_cur(msgmap, new_msgno);
5404 if(in_index != View)
5405 adjust_cur_to_visible(stream, msgmap);
5409 #ifdef DEBUG
5410 long
5411 get_level(int qline, UCS first_num, SCROLL_S *sparms)
5413 char debug_num_string[80], *j, prompt[70];
5414 HelpType help;
5415 int rc;
5416 long debug_num;
5418 if(first_num && first_num < 0x80 && isdigit((unsigned char)first_num)){
5419 debug_num_string[0] = first_num;
5420 debug_num_string[1] = '\0';
5421 debug_num = atol(debug_num_string);
5422 *(int *)(sparms->proc.data.p) = debug_num;
5423 q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s",
5424 comatose(debug_num));
5425 return(1L);
5427 else
5428 debug_num_string[0] = '\0';
5430 snprintf(prompt, sizeof(prompt), "Show debug <= this level (0-%d) : ", MAX(debug, 9));
5431 prompt[sizeof(prompt)-1] = '\0';
5433 help = NO_HELP;
5434 while(1){
5435 int flags = OE_APPEND_CURRENT;
5437 rc = optionally_enter(debug_num_string, qline, 0,
5438 sizeof(debug_num_string), prompt,
5439 NULL, help, &flags);
5440 if(rc == 3){
5441 help = help == NO_HELP ? h_oe_debuglevel : NO_HELP;
5442 continue;
5445 if(rc == 0){
5446 removing_leading_and_trailing_white_space(debug_num_string);
5447 for(j=debug_num_string; isdigit((unsigned char)*j); j++)
5450 if(*j != '\0'){
5451 q_status_message(SM_ORDER | SM_DING, 2, 2,
5452 _("Invalid number entered. Use only digits 0-9"));
5453 debug_num_string[0] = '\0';
5455 else{
5456 debug_num = atol(debug_num_string);
5457 if(debug_num < 0)
5458 q_status_message(SM_ORDER | SM_DING, 2, 2,
5459 _("Number should be >= 0"));
5460 else if(debug_num > MAX(debug,9))
5461 q_status_message1(SM_ORDER | SM_DING, 2, 2,
5462 _("Maximum is %s"), comatose(MAX(debug,9)));
5463 else{
5464 *(int *)(sparms->proc.data.p) = debug_num;
5465 q_status_message1(SM_ORDER, 0, 3,
5466 "Show debug <= level %s",
5467 comatose(debug_num));
5468 return(1L);
5472 continue;
5475 if(rc != 4)
5476 break;
5479 return(0L);
5481 #endif /* DEBUG */
5485 * Returns the message number closest to target that isn't hidden.
5486 * Make warning at least 100 chars.
5487 * A return of 0 means there is no message to jump to.
5489 long
5490 closest_jump_target(long int target, MAILSTREAM *stream, MSGNO_S *msgmap, int no_target, CmdWhere in_index, char *warning, size_t warninglen)
5492 long i, start, closest = 0L;
5493 char buf[80];
5494 long maxnum;
5496 warning[0] = '\0';
5497 maxnum = (in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap);
5499 if(no_target){
5500 target = maxnum;
5501 start = 1L;
5502 snprintf(warning, warninglen, "No %s number entered, jump to end? ",
5503 (in_index == ThrdIndx) ? "thread" : "message");
5504 warning[warninglen-1] = '\0';
5506 else if(target < 1L)
5507 start = 1L - target;
5508 else if(target > maxnum)
5509 start = target - maxnum;
5510 else
5511 start = 1L;
5513 if(target > 0L && target <= maxnum)
5514 if(in_index == ThrdIndx
5515 || !msgline_hidden(stream, msgmap, target, 0))
5516 return(target);
5518 for(i = start; target+i <= maxnum || target-i > 0L; i++){
5520 if(target+i > 0L && target+i <= maxnum &&
5521 (in_index == ThrdIndx
5522 || !msgline_hidden(stream, msgmap, target+i, 0))){
5523 closest = target+i;
5524 break;
5527 if(target-i > 0L && target-i <= maxnum &&
5528 (in_index == ThrdIndx
5529 || !msgline_hidden(stream, msgmap, target-i, 0))){
5530 closest = target-i;
5531 break;
5535 strncpy(buf, long2string(closest), sizeof(buf));
5536 buf[sizeof(buf)-1] = '\0';
5538 if(closest == 0L)
5539 strncpy(warning, "Nothing to jump to", warninglen);
5540 else if(target < 1L)
5541 snprintf(warning, warninglen, "%s number (%s) must be at least %s",
5542 (in_index == ThrdIndx) ? "Thread" : "Message",
5543 long2string(target), buf);
5544 else if(target > maxnum)
5545 snprintf(warning, warninglen, "%s number (%s) may be no more than %s",
5546 (in_index == ThrdIndx) ? "Thread" : "Message",
5547 long2string(target), buf);
5548 else if(!no_target)
5549 snprintf(warning, warninglen,
5550 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5551 long2string(target), buf);
5553 warning[warninglen-1] = '\0';
5555 return(closest);
5559 /*----------------------------------------------------------------------
5560 Prompt for folder name to open, expand the name and return it
5562 Args: qline -- Screen line to prompt on
5563 allow_list -- if 1, allow ^T to bring up collection lister
5565 Result: returns the folder name or NULL
5566 pine structure mangled_footer flag is set
5567 may call the collection lister in which case mangled screen will be set
5569 This prompts the user for the folder to open, possibly calling up
5570 the collection lister if the user types ^T.
5571 ----------------------------------------------------------------------*/
5572 char *
5573 broach_folder(int qline, int allow_list, int *notrealinbox, CONTEXT_S **context)
5575 HelpType help;
5576 static char newfolder[MAILTMPLEN];
5577 char expanded[MAXPATH+1],
5578 prompt[MAX_SCREEN_COLS+1],
5579 *last_folder, *p;
5580 unsigned char *f1, *f2, *f3;
5581 static HISTORY_S *history = NULL;
5582 CONTEXT_S *tc, *tc2;
5583 ESCKEY_S ekey[9];
5584 int rc, r, ku = -1, n, flags, last_rc = 0, inbox, done = 0;
5586 if(qline == 0 && allow_list == 0 && notrealinbox == NULL && context == NULL){
5587 if(history != NULL)
5588 free_hist(&history);
5589 return NULL;
5593 * the idea is to provide a clue for the context the file name
5594 * will be saved in (if a non-imap names is typed), and to
5595 * only show the previous if it was also in the same context
5597 help = NO_HELP;
5598 *expanded = '\0';
5599 *newfolder = '\0';
5600 last_folder = NULL;
5601 if(notrealinbox)
5602 (*notrealinbox) = 1;
5604 init_hist(&history, HISTSIZE);
5606 tc = broach_get_folder(context ? *context : NULL, &inbox, NULL);
5608 /* set up extra command option keys */
5609 rc = 0;
5610 ekey[rc].ch = (allow_list) ? ctrl('T') : 0 ;
5611 ekey[rc].rval = (allow_list) ? 2 : 0;
5612 ekey[rc].name = (allow_list) ? "^T" : "";
5613 ekey[rc++].label = (allow_list) ? N_("ToFldrs") : "";
5615 if(ps_global->context_list->next){
5616 ekey[rc].ch = ctrl('P');
5617 ekey[rc].rval = 10;
5618 ekey[rc].name = "^P";
5619 ekey[rc++].label = N_("Prev Collection");
5621 ekey[rc].ch = ctrl('N');
5622 ekey[rc].rval = 11;
5623 ekey[rc].name = "^N";
5624 ekey[rc++].label = N_("Next Collection");
5627 ekey[rc].ch = ctrl('W');
5628 ekey[rc].rval = 17;
5629 ekey[rc].name = "^W";
5630 ekey[rc++].label = N_("INBOX");
5632 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
5633 ekey[rc].ch = TAB;
5634 ekey[rc].rval = 12;
5635 ekey[rc].name = "TAB";
5636 ekey[rc++].label = N_("Complete");
5639 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
5640 ekey[rc].ch = ctrl('X');
5641 ekey[rc].rval = 14;
5642 ekey[rc].name = "^X";
5643 ekey[rc++].label = N_("ListMatches");
5646 if(ps_global->context_list->next && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5647 ekey[rc].ch = KEY_UP;
5648 ekey[rc].rval = 10;
5649 ekey[rc].name = "";
5650 ekey[rc++].label = "";
5652 ekey[rc].ch = KEY_DOWN;
5653 ekey[rc].rval = 11;
5654 ekey[rc].name = "";
5655 ekey[rc++].label = "";
5657 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5658 ekey[rc].ch = KEY_UP;
5659 ekey[rc].rval = 30;
5660 ekey[rc].name = "";
5661 ku = rc;
5662 ekey[rc++].label = "";
5664 ekey[rc].ch = KEY_DOWN;
5665 ekey[rc].rval = 31;
5666 ekey[rc].name = "";
5667 ekey[rc++].label = "";
5670 ekey[rc].ch = -1;
5672 while(!done) {
5674 * Figure out next default value for this context. The idea
5675 * is that in each context the last folder opened is cached.
5676 * It's up to pick it out and display it. This is fine
5677 * and dandy if we've currently got the inbox open, BUT
5678 * if not, make the inbox the default the first time thru.
5680 if(!inbox){
5681 last_folder = ps_global->inbox_name;
5682 inbox = 1; /* pretend we're in inbox from here on out */
5684 else
5685 last_folder = (ps_global->last_unambig_folder[0])
5686 ? ps_global->last_unambig_folder
5687 : ((tc->last_folder[0]) ? tc->last_folder : NULL);
5689 if(last_folder){ /* MAXPATH + 1 = sizeof(expanded) */
5690 unsigned char *fname = folder_name_decoded((unsigned char *)last_folder);
5691 snprintf(expanded, sizeof(expanded), " [%.*s]", MAXPATH+1-5,
5692 fname ? (char *) fname : last_folder);
5693 if(fname) fs_give((void **)&fname);
5695 else
5696 *expanded = '\0';
5698 expanded[sizeof(expanded)-1] = '\0';
5700 /* only show collection number if more than one available */
5701 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5702 snprintf(prompt, sizeof(prompt), "GOTO %s in <%s> %.*s%s: ",
5703 NEWS_TEST(tc) ? "news group" : "folder",
5704 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5705 *expanded ? " " : "");
5706 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5707 snprintf(prompt, sizeof(prompt), "GOTO folder %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5708 *expanded ? " " : "");
5710 prompt[sizeof(prompt)-1] = '\0';
5712 if(utf8_width(prompt) > MAXPROMPT){
5713 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5714 snprintf(prompt, sizeof(prompt), "GOTO <%s> %.*s%s: ",
5715 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5716 *expanded ? " " : "");
5717 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5718 snprintf(prompt, sizeof(prompt), "GOTO %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5719 *expanded ? " " : "");
5721 prompt[sizeof(prompt)-1] = '\0';
5723 if(utf8_width(prompt) > MAXPROMPT){
5724 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5725 snprintf(prompt, sizeof(prompt), "<%s> %.*s%s: ",
5726 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5727 *expanded ? " " : "");
5728 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5729 snprintf(prompt, sizeof(prompt), "%.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5730 *expanded ? " " : "");
5732 prompt[sizeof(prompt)-1] = '\0';
5736 if(ku >= 0){
5737 if(items_in_hist(history) > 1){
5738 ekey[ku].name = HISTORY_UP_KEYNAME;
5739 ekey[ku].label = HISTORY_KEYLABEL;
5740 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
5741 ekey[ku+1].label = HISTORY_KEYLABEL;
5743 else{
5744 ekey[ku].name = "";
5745 ekey[ku].label = "";
5746 ekey[ku+1].name = "";
5747 ekey[ku+1].label = "";
5751 /* is there any other way to do this? The point is that we
5752 * are trying to hide mutf7 from the user, and use the utf8
5753 * equivalent. So we create a variable f to take place of
5754 * newfolder, including content and size. f2 is copy of f1
5755 * that has to freed. Sigh!
5757 f3 = (unsigned char *) cpystr(newfolder);
5758 f1 = fs_get(sizeof(newfolder));
5759 f2 = folder_name_decoded(f3);
5760 if(f3) fs_give((void **)&f3);
5761 strncpy((char *)f1, (char *)f2, sizeof(newfolder));
5762 f1[sizeof(newfolder)-1] = '\0';
5763 if(f2) fs_give((void **)&f2);
5765 flags = OE_APPEND_CURRENT;
5766 rc = optionally_enter((char *) f1, qline, 0, sizeof(newfolder),
5767 (char *) prompt, ekey, help, &flags);
5769 f2 = folder_name_encoded(f1);
5770 strncpy(newfolder, (char *)f2, sizeof(newfolder));
5771 if(f1) fs_give((void **)&f1);
5772 if(f2) fs_give((void **)&f2);
5774 ps_global->mangled_footer = 1;
5776 switch(rc){
5777 case -1 : /* o_e says error! */
5778 q_status_message(SM_ORDER | SM_DING, 3, 3,
5779 _("Error reading folder name"));
5780 return(NULL);
5782 case 0 : /* o_e says normal entry */
5783 removing_trailing_white_space(newfolder);
5784 removing_leading_white_space(newfolder);
5786 if(*newfolder){
5787 char *name, *fullname = NULL;
5788 int exists, breakout = 0;
5790 save_hist(history, newfolder, 0, tc);
5792 if(!(name = folder_is_nick(newfolder, FOLDERS(tc),
5793 FN_WHOLE_NAME)))
5794 name = newfolder;
5796 if(update_folder_spec(expanded, sizeof(expanded), name)){
5797 strncpy(name = newfolder, expanded, sizeof(newfolder));
5798 newfolder[sizeof(newfolder)-1] = '\0';
5801 exists = folder_name_exists(tc, name, &fullname);
5803 if(fullname){
5804 strncpy(name = newfolder, fullname, sizeof(newfolder));
5805 newfolder[sizeof(newfolder)-1] = '\0';
5806 fs_give((void **) &fullname);
5807 breakout = TRUE;
5811 * if we know the things a folder, open it.
5812 * else if we know its a directory, visit it.
5813 * else we're not sure (it either doesn't really
5814 * exist or its unLISTable) so try opening it anyway
5816 if(exists & FEX_ISFILE){
5817 done++;
5818 break;
5820 else if((exists & FEX_ISDIR)){
5821 if(breakout){
5822 CONTEXT_S *fake_context;
5823 char tmp[MAILTMPLEN];
5824 size_t l;
5826 strncpy(tmp, name, sizeof(tmp));
5827 tmp[sizeof(tmp)-2-1] = '\0';
5828 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
5829 if(l < sizeof(tmp)){
5830 tmp[l] = tc->dir->delim;
5831 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
5834 else
5835 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
5837 tmp[sizeof(tmp)-1] = '\0';
5839 fake_context = new_context(tmp, 0);
5840 newfolder[0] = '\0';
5841 done = display_folder_list(&fake_context, newfolder,
5842 1, folders_for_goto);
5843 free_context(&fake_context);
5844 break;
5846 else if(!(tc->use & CNTXT_INCMNG)){
5847 done = display_folder_list(&tc, newfolder,
5848 1, folders_for_goto);
5849 break;
5852 else if((exists & FEX_ERROR)){
5853 q_status_message1(SM_ORDER, 0, 3,
5854 _("Problem accessing folder \"%s\""),
5855 newfolder);
5856 return(NULL);
5858 else{
5859 done++;
5860 break;
5863 if(exists == FEX_ERROR)
5864 q_status_message1(SM_ORDER, 0, 3,
5865 _("Problem accessing folder \"%s\""),
5866 newfolder);
5867 else if(tc->use & CNTXT_INCMNG)
5868 q_status_message1(SM_ORDER, 0, 3,
5869 _("Can't find Incoming Folder: %s"),
5870 newfolder);
5871 else if(context_isambig(newfolder))
5872 q_status_message2(SM_ORDER, 0, 3,
5873 _("Can't find folder \"%s\" in %s"),
5874 newfolder, (void *) tc->nickname);
5875 else
5876 q_status_message1(SM_ORDER, 0, 3,
5877 _("Can't find folder \"%s\""),
5878 newfolder);
5880 return(NULL);
5882 else if(last_folder){
5883 if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX
5884 && !strucmp(last_folder, ps_global->inbox_name)
5885 && tc == ((ps_global->context_list->use & CNTXT_INCMNG)
5886 ? ps_global->context_list->next : ps_global->context_list)){
5887 if(notrealinbox)
5888 (*notrealinbox) = 0;
5890 tc = ps_global->context_list;
5893 strncpy(newfolder, last_folder, sizeof(newfolder));
5894 newfolder[sizeof(newfolder)-1] = '\0';
5895 save_hist(history, newfolder, 0, tc);
5896 done++;
5897 break;
5899 /* fall thru like they cancelled */
5901 case 1 : /* o_e says user cancel */
5902 cmd_cancelled("Open folder");
5903 return(NULL);
5905 case 2 : /* o_e says user wants list */
5906 r = display_folder_list(&tc, newfolder, 0, folders_for_goto);
5907 if(r)
5908 done++;
5910 break;
5912 case 3 : /* o_e says user wants help */
5913 help = help == NO_HELP ? h_oe_broach : NO_HELP;
5914 break;
5916 case 4 : /* redraw */
5917 break;
5919 case 10 : /* Previous collection */
5920 tc2 = ps_global->context_list;
5921 while(tc2->next && tc2->next != tc)
5922 tc2 = tc2->next;
5924 tc = tc2;
5925 break;
5927 case 11 : /* Next collection */
5928 tc = (tc->next) ? tc->next : ps_global->context_list;
5929 break;
5931 case 12 : /* file name completion */
5932 if(!folder_complete(tc, newfolder, sizeof(newfolder), &n)){
5933 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
5934 r = display_folder_list(&tc, newfolder, 1,folders_for_goto);
5935 if(r)
5936 done++; /* bingo! */
5937 else
5938 rc = 0; /* burn last_rc */
5940 else
5941 Writechar(BELL, 0);
5944 break;
5946 case 14 : /* file name completion */
5947 r = display_folder_list(&tc, newfolder, 2, folders_for_goto);
5948 if(r)
5949 done++; /* bingo! */
5950 else
5951 rc = 0; /* burn last_rc */
5953 break;
5955 case 17 : /* GoTo INBOX */
5956 done++;
5957 strncpy(newfolder, ps_global->inbox_name, sizeof(newfolder)-1);
5958 newfolder[sizeof(newfolder)-1] = '\0';
5959 if(notrealinbox)
5960 (*notrealinbox) = 0;
5962 tc = ps_global->context_list;
5963 save_hist(history, newfolder, 0, tc);
5965 break;
5967 case 30 :
5968 if((p = get_prev_hist(history, newfolder, 0, tc)) != NULL){
5969 strncpy(newfolder, p, sizeof(newfolder));
5970 newfolder[sizeof(newfolder)-1] = '\0';
5971 if(history->hist[history->curindex])
5972 tc = history->hist[history->curindex]->cntxt;
5974 else
5975 Writechar(BELL, 0);
5977 break;
5979 case 31 :
5980 if((p = get_next_hist(history, newfolder, 0, tc)) != NULL){
5981 strncpy(newfolder, p, sizeof(newfolder));
5982 newfolder[sizeof(newfolder)-1] = '\0';
5983 if(history->hist[history->curindex])
5984 tc = history->hist[history->curindex]->cntxt;
5986 else
5987 Writechar(BELL, 0);
5989 break;
5991 default :
5992 alpine_panic("Unhandled case");
5993 break;
5996 last_rc = rc;
5999 dprint((2, "broach folder, name entered \"%s\"\n",
6000 newfolder ? newfolder : "?"));
6002 /*-- Just check that we can expand this. It gets done for real later --*/
6003 strncpy(expanded, newfolder, sizeof(expanded));
6004 expanded[sizeof(expanded)-1] = '\0';
6006 if(!expand_foldername(expanded, sizeof(expanded))) {
6007 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
6008 expanded ? expanded : "?"));
6009 return(NULL);
6012 *context = tc;
6013 return(newfolder);
6017 /*----------------------------------------------------------------------
6018 Check to see if user wants to reopen dead stream.
6020 Args: ps --
6021 reopenp --
6023 Result: 1 if the folder was successfully updatedn
6024 0 if not necessary
6026 ----*/
6028 ask_mailbox_reopen(struct pine *ps, int *reopenp)
6030 if(((ps->mail_stream->dtb
6031 && ((ps->mail_stream->dtb->flags & DR_NONEWMAIL)
6032 || (ps->mail_stream->rdonly
6033 && ps->mail_stream->dtb->flags & DR_NONEWMAILRONLY)))
6034 && (ps->reopen_rule == REOPEN_ASK_ASK_Y
6035 || ps->reopen_rule == REOPEN_ASK_ASK_N
6036 || ps->reopen_rule == REOPEN_ASK_NO_Y
6037 || ps->reopen_rule == REOPEN_ASK_NO_N))
6038 || ((ps->mail_stream->dtb
6039 && ps->mail_stream->rdonly
6040 && !(ps->mail_stream->dtb->flags & DR_LOCAL))
6041 && (ps->reopen_rule == REOPEN_YES_ASK_Y
6042 || ps->reopen_rule == REOPEN_YES_ASK_N
6043 || ps->reopen_rule == REOPEN_ASK_ASK_Y
6044 || ps->reopen_rule == REOPEN_ASK_ASK_N))){
6045 int deefault;
6047 switch(ps->reopen_rule){
6048 case REOPEN_YES_ASK_Y:
6049 case REOPEN_ASK_ASK_Y:
6050 case REOPEN_ASK_NO_Y:
6051 deefault = 'y';
6052 break;
6054 default:
6055 deefault = 'n';
6056 break;
6059 switch(want_to("Re-open folder to check for new messages", deefault,
6060 'x', h_reopen_folder, WT_NORM)){
6061 case 'y':
6062 (*reopenp)++;
6063 break;
6065 case 'x':
6066 return(-1);
6070 return(0);
6075 /*----------------------------------------------------------------------
6076 Check to see if user input is in form of old c-client mailbox speck
6078 Args: old --
6079 new --
6081 Result: 1 if the folder was successfully updatedn
6082 0 if not necessary
6084 ----*/
6086 update_folder_spec(char *new, size_t newlen, char *old)
6088 char *p, *orignew;
6089 int nntp = 0;
6091 orignew = new;
6092 if(*(p = old) == '*') /* old form? */
6093 old++;
6095 if(*old == '{') /* copy host spec */
6097 switch(*new = *old++){
6098 case '\0' :
6099 return(FALSE);
6101 case '/' :
6102 if(!struncmp(old, "nntp", 4))
6103 nntp++;
6105 break;
6107 default :
6108 break;
6110 while(*new++ != '}' && (new-orignew) < newlen-1);
6112 if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){
6114 * OK, some heuristics here. If it looks like a newsgroup
6115 * then we plunk it into the #news namespace else we
6116 * assume that they're trying to get at a #public folder...
6118 for(p = old;
6119 *p && (isalnum((unsigned char) *p) || strindex(".-", *p));
6120 p++)
6123 sstrncpy(&new, (*p && !nntp) ? "#public/" : "#news.", newlen-(new-orignew));
6124 strncpy(new, old, newlen-(new-orignew));
6125 return(TRUE);
6128 orignew[newlen-1] = '\0';
6130 return(FALSE);
6134 /*----------------------------------------------------------------------
6135 Open the requested folder in the requested context
6137 Args: state -- usual pine state struct
6138 newfolder -- folder to open
6139 new_context -- folder context might live in
6140 stream -- candidate for recycling
6142 Result: New folder open or not (if error), and we're set to
6143 enter the index screen.
6144 ----*/
6145 void
6146 visit_folder(struct pine *state, char *newfolder, CONTEXT_S *new_context,
6147 MAILSTREAM *stream, long unsigned int flags)
6149 dprint((9, "visit_folder(%s, %s)\n",
6150 newfolder ? newfolder : "?",
6151 (new_context && new_context->context)
6152 ? new_context->context : "(NULL)"));
6154 if(ps_global && ps_global->ttyo){
6155 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
6156 ps_global->mangled_footer = 1;
6159 if(do_broach_folder(newfolder, new_context, stream ? &stream : NULL,
6160 flags) >= 0
6161 || !sp_flagged(state->mail_stream, SP_LOCKED))
6162 state->next_screen = mail_index_screen;
6163 else
6164 state->next_screen = folder_screen;
6168 /*----------------------------------------------------------------------
6169 Move read messages from folder if listed in archive
6171 Args:
6173 ----*/
6175 read_msg_prompt(long int n, char *f)
6177 char buf[MAX_SCREEN_COLS+1];
6179 snprintf(buf, sizeof(buf), "Save the %ld read message%s in \"%s\"", n, plural(n), f);
6180 buf[sizeof(buf)-1] = '\0';
6181 return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y');
6185 /*----------------------------------------------------------------------
6186 Print current message[s] or folder index
6188 Args: state -- pointer to struct holding a bunch of pine state
6189 msgmap -- table mapping msg nums to c-client sequence nums
6190 aopt -- aggregate options
6191 in_index -- boolean indicating we're called from Index Screen
6193 Filters the original header and sends stuff to printer
6194 ---*/
6196 cmd_print(struct pine *state, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
6198 char prompt[250];
6199 long i, msgs, rawno;
6200 int next = 0, do_index = 0, rv = 0;
6201 ENVELOPE *e;
6202 BODY *b;
6203 MESSAGECACHE *mc;
6205 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
6206 return rv;
6208 msgs = mn_total_cur(msgmap);
6210 if((in_index != View) && F_ON(F_PRINT_INDEX, state)){
6211 char m[10];
6212 int ans;
6213 static ESCKEY_S prt_opts[] = {
6214 {'i', 'i', "I", N_("Index")},
6215 {'m', 'm', "M", NULL},
6216 {-1, 0, NULL, NULL}};
6218 if(in_index == ThrdIndx){
6219 /* TRANSLATORS: This is a question, Print Index ? */
6220 if(want_to(_("Print Index"), 'y', 'x', NO_HELP, WT_NORM) == 'y')
6221 ans = 'i';
6222 else
6223 ans = 'x';
6225 else{
6226 snprintf(m, sizeof(m), "Message%s", (msgs>1L) ? "s" : "");
6227 m[sizeof(m)-1] = '\0';
6228 prt_opts[1].label = m;
6229 snprintf(prompt, sizeof(prompt), "Print %sFolder Index or %s %s? ",
6230 (aopt & MCMD_AGG_2) ? "thread " : MCMD_ISAGG(aopt) ? "selected " : "",
6231 (aopt & MCMD_AGG_2) ? "thread" : MCMD_ISAGG(aopt) ? "selected" : "current", m);
6232 prompt[sizeof(prompt)-1] = '\0';
6234 ans = radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x',
6235 NO_HELP, RB_NORM|RB_SEQ_SENSITIVE);
6238 switch(ans){
6239 case 'x' :
6240 cmd_cancelled("Print");
6241 if(MCMD_ISAGG(aopt))
6242 restore_selected(msgmap);
6244 return rv;
6246 case 'i':
6247 do_index = 1;
6248 break;
6250 default :
6251 case 'm':
6252 break;
6256 if(do_index)
6257 snprintf(prompt, sizeof(prompt), "%sFolder Index",
6258 (aopt & MCMD_AGG_2) ? "Thread " : MCMD_ISAGG(aopt) ? "Selected " : "");
6259 else if(msgs > 1L)
6260 snprintf(prompt, sizeof(prompt), "%s messages", long2string(msgs));
6261 else
6262 snprintf(prompt, sizeof(prompt), "Message %s", long2string(mn_get_cur(msgmap)));
6264 prompt[sizeof(prompt)-1] = '\0';
6266 if(open_printer(prompt) < 0){
6267 if(MCMD_ISAGG(aopt))
6268 restore_selected(msgmap);
6270 return rv;
6273 if(do_index){
6274 TITLE_S *tc;
6276 tc = format_titlebar();
6278 /* Print titlebar... */
6279 print_text1("%s\n\n", tc ? tc->titlebar_line : "");
6280 /* then all the index members... */
6281 if(!print_index(state, msgmap, MCMD_ISAGG(aopt)))
6282 q_status_message(SM_ORDER | SM_DING, 3, 3,
6283 _("Error printing folder index"));
6284 else
6285 rv++;
6287 else{
6288 rv++;
6289 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){
6290 if(next && F_ON(F_AGG_PRINT_FF, state))
6291 if(!print_char(FORMFEED)){
6292 rv = 0;
6293 break;
6296 if(!(state->mail_stream
6297 && (rawno = mn_m2raw(msgmap, i)) > 0L
6298 && rawno <= state->mail_stream->nmsgs
6299 && (mc = mail_elt(state->mail_stream, rawno))
6300 && mc->valid))
6301 mc = NULL;
6303 if(!(e=pine_mail_fetchstructure(state->mail_stream,
6304 mn_m2raw(msgmap,i),
6305 &b))
6306 || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global)
6307 && !bezerk_delimiter(e, mc, print_char, next))
6308 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
6309 e, b, NULL, FM_NEW_MESS | FM_NOINDENT,
6310 print_char)){
6311 q_status_message(SM_ORDER | SM_DING, 3, 3,
6312 _("Error printing message"));
6313 rv = 0;
6314 break;
6319 close_printer();
6321 if(MCMD_ISAGG(aopt))
6322 restore_selected(msgmap);
6324 return rv;
6328 /*----------------------------------------------------------------------
6329 Pipe message text
6331 Args: state -- various pine state bits
6332 msgmap -- Message number mapping table
6333 aopt -- option flags
6335 Filters the original header and sends stuff to specified command
6336 ---*/
6338 cmd_pipe(struct pine *state, MSGNO_S *msgmap, int aopt)
6340 ENVELOPE *e;
6341 MESSAGECACHE *mc;
6342 BODY *b;
6343 PIPE_S *syspipe;
6344 char *resultfilename = NULL, prompt[80], *p;
6345 int done = 0, rv = 0;
6346 gf_io_t pc;
6347 int fourlabel = -1, j = 0, next = 0, ku;
6348 int pipe_rv; /* rv of proc to separate from close_system_pipe rv */
6349 long i, rawno;
6350 unsigned flagsforhist = 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6351 static HISTORY_S *history = NULL;
6352 int capture = 1, raw = 0, delimit = 0, newpipe = 0;
6353 char pipe_command[MAXPATH];
6354 ESCKEY_S pipe_opt[8];
6356 if(state == NULL && msgmap == NULL && aopt == 0){
6357 if(history != NULL)
6358 free_hist(&history);
6359 return 0;
6362 if(ps_global->restricted){
6363 q_status_message(SM_ORDER | SM_DING, 0, 4,
6364 "Alpine demo can't pipe messages");
6365 return rv;
6367 else if(!any_messages(msgmap, NULL, "to Pipe"))
6368 return rv;
6370 pipe_command[0] = '\0';
6371 init_hist(&history, HISTSIZE);
6372 flagsforhist = (raw ? 0x8 : 0) +
6373 (delimit ? 0x4 : 0) +
6374 (newpipe ? 0x2 : 0) +
6375 (capture ? 0x1 : 0);
6376 if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){
6377 strncpy(pipe_command, p, sizeof(pipe_command));
6378 pipe_command[sizeof(pipe_command)-1] = '\0';
6379 if(history->hist[history->curindex]){
6380 flagsforhist = history->hist[history->curindex]->flags;
6381 raw = (flagsforhist & 0x8) ? 1 : 0;
6382 delimit = (flagsforhist & 0x4) ? 1 : 0;
6383 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6384 capture = (flagsforhist & 0x1) ? 1 : 0;
6388 pipe_opt[j].ch = 0;
6389 pipe_opt[j].rval = 0;
6390 pipe_opt[j].name = "";
6391 pipe_opt[j++].label = "";
6393 pipe_opt[j].ch = ctrl('W');
6394 pipe_opt[j].rval = 10;
6395 pipe_opt[j].name = "^W";
6396 pipe_opt[j++].label = NULL;
6398 pipe_opt[j].ch = ctrl('Y');
6399 pipe_opt[j].rval = 11;
6400 pipe_opt[j].name = "^Y";
6401 pipe_opt[j++].label = NULL;
6403 pipe_opt[j].ch = ctrl('R');
6404 pipe_opt[j].rval = 12;
6405 pipe_opt[j].name = "^R";
6406 pipe_opt[j++].label = NULL;
6408 if(MCMD_ISAGG(aopt)){
6409 if(!pseudo_selected(state->mail_stream, msgmap))
6410 return rv;
6411 else{
6412 fourlabel = j;
6413 pipe_opt[j].ch = ctrl('T');
6414 pipe_opt[j].rval = 13;
6415 pipe_opt[j].name = "^T";
6416 pipe_opt[j++].label = NULL;
6420 pipe_opt[j].ch = KEY_UP;
6421 pipe_opt[j].rval = 30;
6422 pipe_opt[j].name = "";
6423 ku = j;
6424 pipe_opt[j++].label = "";
6426 pipe_opt[j].ch = KEY_DOWN;
6427 pipe_opt[j].rval = 31;
6428 pipe_opt[j].name = "";
6429 pipe_opt[j++].label = "";
6431 pipe_opt[j].ch = -1;
6433 while (!done) {
6434 int flags;
6436 snprintf(prompt, sizeof(prompt), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6437 raw ? "RAW " : "",
6438 MCMD_ISAGG(aopt) ? "s" : " ",
6439 MCMD_ISAGG(aopt) ? "" : comatose(mn_get_cur(msgmap)),
6440 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? "(" : "",
6441 capture ? "" : "uncaptured",
6442 (!capture && delimit) ? "," : "",
6443 delimit ? "delimited" : "",
6444 ((!capture || delimit) && newpipe && MCMD_ISAGG(aopt)) ? "," : "",
6445 (newpipe && MCMD_ISAGG(aopt)) ? "new pipe" : "",
6446 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? ") " : "");
6447 prompt[sizeof(prompt)-1] = '\0';
6448 pipe_opt[1].label = raw ? N_("Shown Text") : N_("Raw Text");
6449 pipe_opt[2].label = capture ? N_("Free Output") : N_("Capture Output");
6450 pipe_opt[3].label = delimit ? N_("No Delimiter") : N_("With Delimiter");
6451 if(fourlabel > 0)
6452 pipe_opt[fourlabel].label = newpipe ? N_("To Same Pipe") : N_("To Individual Pipes");
6456 * 2 is really 1 because there will be one real entry and
6457 * one entry of "" because of the get_prev_hist above.
6459 if(items_in_hist(history) > 2){
6460 pipe_opt[ku].name = HISTORY_UP_KEYNAME;
6461 pipe_opt[ku].label = HISTORY_KEYLABEL;
6462 pipe_opt[ku+1].name = HISTORY_DOWN_KEYNAME;
6463 pipe_opt[ku+1].label = HISTORY_KEYLABEL;
6465 else{
6466 pipe_opt[ku].name = "";
6467 pipe_opt[ku].label = "";
6468 pipe_opt[ku+1].name = "";
6469 pipe_opt[ku+1].label = "";
6472 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
6473 switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0,
6474 sizeof(pipe_command), prompt,
6475 pipe_opt, NO_HELP, &flags)){
6476 case -1 :
6477 q_status_message(SM_ORDER | SM_DING, 3, 4,
6478 _("Internal problem encountered"));
6479 done++;
6480 break;
6482 case 10 : /* flip raw bit */
6483 raw = !raw;
6484 break;
6486 case 11 : /* flip capture bit */
6487 capture = !capture;
6488 break;
6490 case 12 : /* flip delimit bit */
6491 delimit = !delimit;
6492 break;
6494 case 13 : /* flip newpipe bit */
6495 newpipe = !newpipe;
6496 break;
6498 case 30 :
6499 flagsforhist = (raw ? 0x8 : 0) +
6500 (delimit ? 0x4 : 0) +
6501 (newpipe ? 0x2 : 0) +
6502 (capture ? 0x1 : 0);
6503 if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6504 strncpy(pipe_command, p, sizeof(pipe_command));
6505 pipe_command[sizeof(pipe_command)-1] = '\0';
6506 if(history->hist[history->curindex]){
6507 flagsforhist = history->hist[history->curindex]->flags;
6508 raw = (flagsforhist & 0x8) ? 1 : 0;
6509 delimit = (flagsforhist & 0x4) ? 1 : 0;
6510 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6511 capture = (flagsforhist & 0x1) ? 1 : 0;
6514 else
6515 Writechar(BELL, 0);
6517 break;
6519 case 31 :
6520 flagsforhist = (raw ? 0x8 : 0) +
6521 (delimit ? 0x4 : 0) +
6522 (newpipe ? 0x2 : 0) +
6523 (capture ? 0x1 : 0);
6524 if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6525 strncpy(pipe_command, p, sizeof(pipe_command));
6526 pipe_command[sizeof(pipe_command)-1] = '\0';
6527 if(history->hist[history->curindex]){
6528 flagsforhist = history->hist[history->curindex]->flags;
6529 raw = (flagsforhist & 0x8) ? 1 : 0;
6530 delimit = (flagsforhist & 0x4) ? 1 : 0;
6531 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6532 capture = (flagsforhist & 0x1) ? 1 : 0;
6535 else
6536 Writechar(BELL, 0);
6538 break;
6540 case 0 :
6541 if(pipe_command[0]){
6543 flagsforhist = (raw ? 0x8 : 0) +
6544 (delimit ? 0x4 : 0) +
6545 (newpipe ? 0x2 : 0) +
6546 (capture ? 0x1 : 0);
6547 save_hist(history, pipe_command, flagsforhist, NULL);
6549 flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
6550 flags |= (raw ? PIPE_RAW : 0);
6551 if(!capture){
6552 #ifndef _WINDOWS
6553 ClearScreen();
6554 fflush(stdout);
6555 clear_cursor_pos();
6556 ps_global->mangled_screen = 1;
6557 ps_global->in_init_seq = 1;
6558 #endif
6559 flags |= PIPE_RESET;
6562 if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command,
6563 (flags & PIPE_RESET)
6564 ? NULL
6565 : &resultfilename,
6566 flags, &pc)))
6567 done++;
6569 for(i = mn_first_cur(msgmap);
6570 i > 0L && !done;
6571 i = mn_next_cur(msgmap)){
6572 e = pine_mail_fetchstructure(ps_global->mail_stream,
6573 mn_m2raw(msgmap, i), &b);
6574 if(!(state->mail_stream
6575 && (rawno = mn_m2raw(msgmap, i)) > 0L
6576 && rawno <= state->mail_stream->nmsgs
6577 && (mc = mail_elt(state->mail_stream, rawno))
6578 && mc->valid))
6579 mc = NULL;
6581 if((newpipe
6582 && !(syspipe = cmd_pipe_open(pipe_command,
6583 (flags & PIPE_RESET)
6584 ? NULL
6585 : &resultfilename,
6586 flags, &pc)))
6587 || (delimit && !bezerk_delimiter(e, mc, pc, next++)))
6588 done++;
6590 if(!done){
6591 if(raw){
6592 char *pipe_err;
6594 prime_raw_pipe_getc(ps_global->mail_stream,
6595 mn_m2raw(msgmap, i), -1L, 0L);
6596 gf_filter_init();
6597 gf_link_filter(gf_nvtnl_local, NULL);
6598 if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){
6599 q_status_message1(SM_ORDER|SM_DING,
6600 3, 3,
6601 _("Internal Error: %s"),
6602 pipe_err);
6603 done++;
6606 else if(!format_message(mn_m2raw(msgmap, i), e, b,
6607 NULL, FM_NEW_MESS | FM_NOWRAP, pc))
6608 done++;
6611 if(newpipe)
6612 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6613 done++;
6616 if(!capture)
6617 ps_global->in_init_seq = 0;
6619 if(!newpipe)
6620 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6621 done++;
6622 if(done) /* say we had a problem */
6623 q_status_message(SM_ORDER | SM_DING, 3, 3,
6624 _("Error piping message"));
6625 else if(resultfilename){
6626 rv++;
6627 /* only display if no error */
6628 display_output_file(resultfilename, "PIPE MESSAGE",
6629 NULL, DOF_EMPTY);
6630 fs_give((void **)&resultfilename);
6632 else{
6633 rv++;
6634 q_status_message(SM_ORDER, 0, 2, _("Pipe command completed"));
6637 done++;
6638 break;
6640 /* else fall thru as if cancelled */
6642 case 1 :
6643 cmd_cancelled("Pipe command");
6644 done++;
6645 break;
6647 case 3 :
6648 helper(h_common_pipe, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE);
6649 ps_global->mangled_screen = 1;
6650 break;
6652 case 2 : /* no place to escape to */
6653 case 4 : /* can't suspend */
6654 default :
6655 break;
6659 ps_global->mangled_footer = 1;
6660 if(MCMD_ISAGG(aopt))
6661 restore_selected(msgmap);
6663 return rv;
6667 /*----------------------------------------------------------------------
6668 Screen to offer list management commands contained in message
6670 Args: state -- pointer to struct holding a bunch of pine state
6671 msgmap -- table mapping msg nums to c-client sequence nums
6672 aopt -- aggregate options
6674 Result:
6676 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6677 ----*/
6678 void
6679 rfc2369_display(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno)
6681 int winner = 0;
6682 char *h, *hdrs[MLCMD_COUNT + 1];
6683 long index_no = mn_raw2m(msgmap, msgno);
6684 RFC2369_S data[MLCMD_COUNT];
6686 /* for each header field */
6687 if((h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))) != NULL){
6688 memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT);
6689 if(rfc2369_parse_fields(h, &data[0])){
6690 STORE_S *explain;
6692 if((explain = list_mgmt_text(data, index_no)) != NULL){
6693 list_mgmt_screen(explain);
6694 ps_global->mangled_screen = 1;
6695 so_give(&explain);
6696 winner++;
6700 fs_give((void **) &h);
6703 if(!winner)
6704 q_status_message1(SM_ORDER, 0, 3,
6705 "Message %s contains no list management information",
6706 comatose(index_no));
6710 STORE_S *
6711 list_mgmt_text(RFC2369_S *data, long int msgno)
6713 STORE_S *store;
6714 int i, j, n, fields = 0;
6715 static char *rfc2369_intro1 =
6716 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6717 static char *rfc2369_intro2[] = {
6718 N_(" has information associated with it "),
6719 N_("that explains how to participate in an email list. An "),
6720 N_("email list is represented by a single email address that "),
6721 N_("users sharing a common interest can send messages to (known "),
6722 N_("as posting) which are then redistributed to all members "),
6723 N_("of the list (sometimes after review by a moderator)."),
6724 N_("<P>List participation commands in this message include:"),
6725 NULL
6728 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6730 /* Insert introductory text */
6731 so_puts(store, rfc2369_intro1);
6733 so_puts(store, comatose(msgno));
6735 for(i = 0; rfc2369_intro2[i]; i++)
6736 so_puts(store, _(rfc2369_intro2[i]));
6738 so_puts(store, "<P>");
6739 for(i = 0; i < MLCMD_COUNT; i++)
6740 if(data[i].data[0].value
6741 || data[i].data[0].comment
6742 || data[i].data[0].error){
6743 if(!fields++)
6744 so_puts(store, "<UL>");
6746 so_puts(store, "<LI>");
6747 so_puts(store,
6748 (n = (data[i].data[1].value || data[i].data[1].comment))
6749 ? "Methods to "
6750 : "A method to ");
6752 so_puts(store, data[i].field.description);
6753 so_puts(store, ". ");
6755 if(n)
6756 so_puts(store, "<OL>");
6758 for(j = 0;
6759 j < MLCMD_MAXDATA
6760 && (data[i].data[j].comment
6761 || data[i].data[j].value
6762 || data[i].data[j].error);
6763 j++){
6765 so_puts(store, n ? "<P><LI>" : "<P>");
6767 if(data[i].data[j].comment){
6768 so_puts(store,
6769 _("With the provided comment:<P><BLOCKQUOTE>"));
6770 so_puts(store, data[i].data[j].comment);
6771 so_puts(store, "</BLOCKQUOTE><P>");
6774 if(data[i].data[j].value){
6775 if(i == MLCMD_POST
6776 && !strucmp(data[i].data[j].value, "NO")){
6777 so_puts(store,
6778 _("Posting is <EM>not</EM> allowed on this list"));
6780 else{
6781 so_puts(store, "Select <A HREF=\"");
6782 so_puts(store, data[i].data[j].value);
6783 so_puts(store, "\">HERE</A> to ");
6784 so_puts(store, (data[i].field.action)
6785 ? data[i].field.action
6786 : "try it");
6789 so_puts(store, ".");
6792 if(data[i].data[j].error){
6793 so_puts(store, "<P>Unfortunately, Alpine can not offer");
6794 so_puts(store, " to take direct action based upon it");
6795 so_puts(store, " because it was improperly formatted.");
6796 so_puts(store, " The unrecognized data associated with");
6797 so_puts(store, " the \"");
6798 so_puts(store, data[i].field.name);
6799 so_puts(store, "\" header field was:<P><BLOCKQUOTE>");
6800 so_puts(store, data[i].data[j].error);
6801 so_puts(store, "</BLOCKQUOTE>");
6804 so_puts(store, "<P>");
6807 if(n)
6808 so_puts(store, "</OL>");
6811 if(fields)
6812 so_puts(store, "</UL>");
6814 so_puts(store, "</BODY></HTML>");
6817 return(store);
6821 void
6822 list_mgmt_screen(STORE_S *html)
6824 int cmd = MC_NONE;
6825 long offset = 0L;
6826 char *error = NULL;
6827 STORE_S *store;
6828 HANDLE_S *handles = NULL;
6829 gf_io_t gc, pc;
6832 so_seek(html, 0L, 0);
6833 gf_set_so_readc(&gc, html);
6835 init_handles(&handles);
6837 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6838 gf_set_so_writec(&pc, store);
6839 gf_filter_init();
6841 gf_link_filter(gf_html2plain,
6842 gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols,
6843 non_messageview_margin(), &handles, NULL, 0));
6845 error = gf_pipe(gc, pc);
6847 gf_clear_so_writec(store);
6849 if(!error){
6850 SCROLL_S sargs;
6852 memset(&sargs, 0, sizeof(SCROLL_S));
6853 sargs.text.text = so_text(store);
6854 sargs.text.src = CharStar;
6855 sargs.text.desc = "list commands";
6856 sargs.text.handles = handles;
6857 if(offset){
6858 sargs.start.on = Offset;
6859 sargs.start.loc.offset = offset;
6862 sargs.bar.title = _("MAIL LIST COMMANDS");
6863 sargs.bar.style = MessageNumber;
6864 sargs.resize_exit = 1;
6865 sargs.help.text = h_special_list_commands;
6866 sargs.help.title = _("HELP FOR LIST COMMANDS");
6867 sargs.keys.menu = &listmgr_keymenu;
6868 setbitmap(sargs.keys.bitmap);
6869 if(!handles){
6870 clrbitn(LM_TRY_KEY, sargs.keys.bitmap);
6871 clrbitn(LM_PREV_KEY, sargs.keys.bitmap);
6872 clrbitn(LM_NEXT_KEY, sargs.keys.bitmap);
6875 cmd = scrolltool(&sargs);
6876 offset = sargs.start.loc.offset;
6879 so_give(&store);
6882 free_handles(&handles);
6883 gf_clear_so_readc(html);
6885 while(cmd == MC_RESIZE);
6889 /*----------------------------------------------------------------------
6890 Prompt the user for the type of select desired
6892 NOTE: any and all functions that successfully exit the second
6893 switch() statement below (currently "select_*() functions"),
6894 *MUST* update the folder's MESSAGECACHE element's "searched"
6895 bits to reflect the search result. Functions using
6896 mail_search() get this for free, the others must update 'em
6897 by hand.
6899 Returns -1 if canceled without changing selection
6900 0 if selection may have changed
6901 ----*/
6903 aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index)
6905 long i, diff, old_tot, msgno, raw;
6906 int q = 0, rv = 0, narrow = 0, hidden, ret = -1;
6907 ESCKEY_S *sel_opts;
6908 MESSAGECACHE *mc;
6909 SEARCHSET *limitsrch = NULL;
6910 PINETHRD_S *thrd;
6911 extern MAILSTREAM *mm_search_stream;
6912 extern long mm_search_count;
6914 hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
6915 mm_search_stream = state->mail_stream;
6916 mm_search_count = 0L;
6918 sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2;
6919 if(THREADING()){
6920 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH;
6922 else{
6923 sel_opts[SEL_OPTS_THREAD].ch = -1;
6926 if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){
6927 if(THRD_INDX()){
6928 i = 0;
6929 thrd = fetch_thread(state->mail_stream,
6930 mn_m2raw(msgmap, mn_get_cur(msgmap)));
6931 /* check if whole thread is selected or not */
6932 if(thrd &&
6933 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT)
6935 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE))
6936 i = 1;
6938 sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd");
6940 else{
6941 i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap),
6942 MN_SLCT);
6943 sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur");
6946 sel_opts += 2; /* disable extra options */
6947 switch(q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6948 RB_NORM)){
6949 case 'f' : /* flip selection */
6950 msgno = 0L;
6951 for(i = 1L; i <= mn_get_total(msgmap); i++){
6952 ret = 0;
6953 q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT);
6954 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q);
6955 if(hidden){
6956 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q);
6957 if(!msgno && q)
6958 mn_reset_cur(msgmap, msgno = i);
6962 return(ret);
6964 case 'n' : /* narrow selection */
6965 narrow++;
6966 case 'b' : /* broaden selection */
6967 q = 0; /* offer criteria prompt */
6968 break;
6970 case 'c' : /* Un/Select Current */
6971 case 'a' : /* Unselect All */
6972 case 'x' : /* cancel */
6973 break;
6975 default :
6976 q_status_message(SM_ORDER | SM_DING, 3, 3,
6977 "Unsupported Select option");
6978 return(ret);
6982 if(!q){
6983 while(1){
6984 q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x',
6985 NO_HELP, RB_NORM|RB_RET_HELP);
6987 if(q == 3){
6988 helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE);
6989 ps_global->mangled_screen = 1;
6991 else
6992 break;
6997 * The purpose of this is to add the appropriate searchset to the
6998 * search so that the search can be limited to only looking at what
6999 * it needs to look at. That is, if we are narrowing then we only need
7000 * to look at messages which are already selected, and if we are
7001 * broadening, then we only need to look at messages which are not
7002 * yet selected. This routine will work whether or not
7003 * limiting_searchset properly limits the search set. In particular,
7004 * the searchset returned by limiting_searchset may include messages
7005 * which really shouldn't be included. We do that because a too-large
7006 * searchset will break some IMAP servers. It is even possible that it
7007 * becomes inefficient to send the whole set. If the select function
7008 * frees limitsrch, it should be sure to set it to NULL so we won't
7009 * try freeing it again here.
7011 limitsrch = limiting_searchset(state->mail_stream, narrow);
7014 * NOTE: See note about MESSAGECACHE "searched" bits above!
7016 switch(q){
7017 case 'x': /* cancel */
7018 cmd_cancelled("Select command");
7019 return(ret);
7021 case 'c' : /* select/unselect current */
7022 (void) select_by_current(state, msgmap, in_index);
7023 ret = 0;
7024 return(ret);
7026 case 'a' : /* select/unselect all */
7027 msgno = any_lflagged(msgmap, MN_SLCT);
7028 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
7029 ret = 0;
7030 agg_select_all(state->mail_stream, msgmap, &diff,
7031 any_lflagged(msgmap, MN_SLCT) <= 0L);
7032 q_status_message4(SM_ORDER,0,2,
7033 "%s%s message%s %sselected",
7034 msgno ? "" : "All ", comatose(diff),
7035 plural(diff), msgno ? "UN" : "");
7036 return(ret);
7038 case 'n' : /* Select by Number */
7039 ret = 0;
7040 if(THRD_INDX())
7041 rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch);
7042 else
7043 rv = select_by_number(state->mail_stream, msgmap, &limitsrch);
7045 break;
7047 case 'd' : /* Select by Date */
7048 ret = 0;
7049 rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap),
7050 &limitsrch);
7051 break;
7053 case 't' : /* Text */
7054 ret = 0;
7055 rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap),
7056 &limitsrch);
7057 break;
7059 case 'z' : /* Size */
7060 ret = 0;
7061 rv = select_by_size(state->mail_stream, &limitsrch);
7062 break;
7064 case 's' : /* Status */
7065 ret = 0;
7066 rv = select_by_status(state->mail_stream, &limitsrch);
7067 break;
7069 case 'k' : /* Keyword */
7070 ret = 0;
7071 rv = select_by_keyword(state->mail_stream, &limitsrch);
7072 break;
7074 case 'r' : /* Rule */
7075 ret = 0;
7076 rv = select_by_rule(state->mail_stream, &limitsrch);
7077 break;
7079 case 'h' : /* Thread */
7080 ret = 0;
7081 rv = select_by_thread(state->mail_stream, msgmap, &limitsrch);
7082 break;
7084 default :
7085 q_status_message(SM_ORDER | SM_DING, 3, 3,
7086 "Unsupported Select option");
7087 return(ret);
7090 if(limitsrch)
7091 mail_free_searchset(&limitsrch);
7093 if(rv) /* bad return value.. */
7094 return(ret); /* error already displayed */
7096 if(narrow) /* make sure something was selected */
7097 for(i = 1L; i <= mn_get_total(msgmap); i++)
7098 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7099 && raw <= state->mail_stream->nmsgs
7100 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7101 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
7102 break;
7103 else
7104 mm_search_count--;
7107 diff = 0L;
7108 if(mm_search_count){
7110 * loop thru all the messages, adjusting local flag bits
7111 * based on their "searched" bit...
7113 for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++)
7114 if(narrow){
7115 /* turning OFF selectedness if the "searched" bit isn't lit. */
7116 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7117 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7118 && raw <= state->mail_stream->nmsgs
7119 && (mc = mail_elt(state->mail_stream, raw))
7120 && !mc->searched){
7121 diff--;
7122 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
7123 if(hidden)
7124 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
7126 /* adjust current message in case we unselect and hide it */
7127 else if(msgno < mn_get_cur(msgmap)
7128 && (!THRD_INDX()
7129 || !get_lflag(state->mail_stream, msgmap,
7130 i, MN_CHID)))
7131 msgno = i;
7134 else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7135 && raw <= state->mail_stream->nmsgs
7136 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7137 /* turn ON selectedness if "searched" bit is lit. */
7138 if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7139 diff++;
7140 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
7141 if(hidden)
7142 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
7146 /* if we're zoomed and the current message was unselected */
7147 if(narrow && msgno
7148 && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE))
7149 mn_reset_cur(msgmap, msgno);
7152 if(!diff){
7153 if(narrow)
7154 q_status_message4(SM_ORDER, 3, 3,
7155 "%s. %s message%s remain%s selected.",
7156 mm_search_count
7157 ? "No change resulted"
7158 : "No messages in intersection",
7159 comatose(old_tot), plural(old_tot),
7160 (old_tot == 1L) ? "s" : "");
7161 else if(old_tot)
7162 q_status_message(SM_ORDER, 3, 3,
7163 _("No change resulted. Matching messages already selected."));
7164 else
7165 q_status_message1(SM_ORDER | SM_DING, 3, 3,
7166 _("Select failed. No %smessages selected."),
7167 old_tot ? _("additional ") : "");
7169 else if(old_tot){
7170 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
7171 "Select matched %ld message%s. %s %smessage%s %sselected.",
7172 (diff > 0) ? diff : old_tot + diff,
7173 plural((diff > 0) ? diff : old_tot + diff),
7174 comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7175 (diff > 0) ? "total " : "",
7176 plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7177 (diff > 0) ? "" : "UN");
7178 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
7179 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
7181 else
7182 q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"),
7183 comatose(diff), plural(diff));
7185 return(ret);
7189 /*----------------------------------------------------------------------
7190 Toggle the state of the current message
7192 Args: state -- pointer pine's state variables
7193 msgmap -- message collection to operate on
7194 in_index -- in the message index view
7195 Returns: TRUE if current marked selected, FALSE otw
7196 ----*/
7198 select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
7200 long cur;
7201 int all_selected = 0;
7202 unsigned long was, tot, rawno;
7203 PINETHRD_S *thrd;
7205 cur = mn_get_cur(msgmap);
7207 if(THRD_INDX()){
7208 thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur));
7209 if(!thrd)
7210 return 0;
7212 was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT);
7213 tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE);
7214 if(was == tot)
7215 all_selected++;
7217 if(all_selected){
7218 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0);
7219 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7220 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1);
7222 * See if there's anything left to zoom on. If so,
7223 * pick an adjacent one for highlighting, else make
7224 * sure nothing is left hidden...
7226 if(any_lflagged(msgmap, MN_SLCT)){
7227 mn_inc_cur(state->mail_stream, msgmap,
7228 (in_index == View && THREADING()
7229 && sp_viewing_a_thread(state->mail_stream))
7230 ? MH_THISTHD
7231 : (in_index == View)
7232 ? MH_ANYTHD : MH_NONE);
7233 if(mn_get_cur(msgmap) == cur)
7234 mn_dec_cur(state->mail_stream, msgmap,
7235 (in_index == View && THREADING()
7236 && sp_viewing_a_thread(state->mail_stream))
7237 ? MH_THISTHD
7238 : (in_index == View)
7239 ? MH_ANYTHD : MH_NONE);
7241 else /* clear all hidden flags */
7242 (void) unzoom_index(state, state->mail_stream, msgmap);
7245 else
7246 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1);
7248 q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected",
7249 comatose(all_selected ? was : tot-was),
7250 plural(all_selected ? was : tot-was),
7251 all_selected ? "UN" : "");
7253 /* collapsed thread */
7254 else if(THREADING()
7255 && ((rawno = mn_m2raw(msgmap, cur)) != 0L)
7256 && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL)
7257 && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){
7259 * This doesn't work quite the same as the colon command works, but
7260 * it is arguably doing the correct thing. The difference is
7261 * that aggregate_select will zoom after selecting back where it
7262 * was called from, but selecting a thread with colon won't zoom.
7263 * Maybe it makes sense to zoom after a select but not after a colon
7264 * command even though they are very similar.
7266 thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state));
7268 else{
7269 if((all_selected =
7270 get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */
7271 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0);
7272 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7273 set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1);
7275 * See if there's anything left to zoom on. If so,
7276 * pick an adjacent one for highlighting, else make
7277 * sure nothing is left hidden...
7279 if(any_lflagged(msgmap, MN_SLCT)){
7280 mn_inc_cur(state->mail_stream, msgmap,
7281 (in_index == View && THREADING()
7282 && sp_viewing_a_thread(state->mail_stream))
7283 ? MH_THISTHD
7284 : (in_index == View)
7285 ? MH_ANYTHD : MH_NONE);
7286 if(mn_get_cur(msgmap) == cur)
7287 mn_dec_cur(state->mail_stream, msgmap,
7288 (in_index == View && THREADING()
7289 && sp_viewing_a_thread(state->mail_stream))
7290 ? MH_THISTHD
7291 : (in_index == View)
7292 ? MH_ANYTHD : MH_NONE);
7294 else /* clear all hidden flags */
7295 (void) unzoom_index(state, state->mail_stream, msgmap);
7298 else
7299 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1);
7301 q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected",
7302 long2string(cur), all_selected ? "UN" : "");
7306 return(!all_selected);
7310 /*----------------------------------------------------------------------
7311 Prompt the user for the command to perform on selected messages
7313 Args: state -- pointer pine's state variables
7314 msgmap -- message collection to operate on
7315 q_line -- line on display to write prompts
7316 Returns: 1 if the selected messages are suitably commanded,
7317 0 if the choice to pick the command was declined
7319 ----*/
7321 apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
7322 UCS preloadkeystroke, int flags, int q_line)
7324 int i = 8, /* number of static entries in sel_opts3 */
7325 rv = 0,
7326 cmd,
7327 we_cancel = 0,
7328 agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG;
7329 char prompt[80];
7330 PAT_STATE pstate;
7333 * To do this "right", we really ought to have access to the keymenu
7334 * here and change the typed command into a real command by running
7335 * it through menu_command. Then the switch below would be against
7336 * results from menu_command. If we did that we'd also pass the
7337 * results of menu_command in as preloadkeystroke instead of passing
7338 * the keystroke itself. But we don't have the keymenu handy,
7339 * so we have to fake it. The only complication that we run into
7340 * is that KEY_DEL is an escape sequence so we change a typed
7341 * KEY_DEL esc seq into the letter D.
7344 if(!preloadkeystroke){
7345 if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
7346 sel_opts3[i].ch = '*';
7347 sel_opts3[i].rval = '*';
7348 sel_opts3[i].name = "*";
7349 sel_opts3[i++].label = N_("Flag");
7352 if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
7353 sel_opts3[i].ch = '|';
7354 sel_opts3[i].rval = '|';
7355 sel_opts3[i].name = "|";
7356 sel_opts3[i++].label = N_("Pipe");
7359 if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */
7360 sel_opts3[i].ch = 'b';
7361 sel_opts3[i].rval = 'b';
7362 sel_opts3[i].name = "B";
7363 sel_opts3[i++].label = N_("Bounce");
7366 if(flags & AC_FROM_THREAD){
7367 if(flags & (AC_COLL | AC_EXPN)){
7368 sel_opts3[i].ch = '/';
7369 sel_opts3[i].rval = '/';
7370 sel_opts3[i].name = "/";
7371 sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse")
7372 : N_("Expand");
7375 sel_opts3[i].ch = ';';
7376 sel_opts3[i].rval = ';';
7377 sel_opts3[i].name = ";";
7378 if(flags & AC_UNSEL)
7379 sel_opts3[i++].label = N_("UnSelect");
7380 else
7381 sel_opts3[i++].label = N_("Select");
7384 if(F_ON(F_ENABLE_PRYNT, state)){ /* this one is invisible */
7385 sel_opts3[i].ch = 'y';
7386 sel_opts3[i].rval = '%';
7387 sel_opts3[i].name = "";
7388 sel_opts3[i++].label = "";
7391 if(!is_imap_stream(stream) || LEVELUIDPLUS(stream)){ /* expunge selected messages */
7392 sel_opts3[i].ch = 'x';
7393 sel_opts3[i].rval = 'x';
7394 sel_opts3[i].name = "X";
7395 sel_opts3[i++].label = N_("Expunge");
7398 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7399 sel_opts3[i].ch = '#';
7400 sel_opts3[i].rval = '#';
7401 sel_opts3[i].name = "#";
7402 sel_opts3[i++].label = N_("Set Role");
7405 sel_opts3[i].ch = KEY_DEL; /* also invisible */
7406 sel_opts3[i].rval = 'd';
7407 sel_opts3[i].name = "";
7408 sel_opts3[i++].label = "";
7410 sel_opts3[i].ch = -1;
7412 snprintf(prompt, sizeof(prompt), "%s command : ",
7413 (flags & AC_FROM_THREAD) ? "THREAD" : "APPLY");
7414 prompt[sizeof(prompt)-1] = '\0';
7415 cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'c', NO_HELP,
7416 RB_SEQ_SENSITIVE);
7417 if(isupper(cmd))
7418 cmd = tolower(cmd);
7420 else{
7421 if(preloadkeystroke == KEY_DEL)
7422 cmd = 'd';
7423 else{
7424 if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke))
7425 cmd = tolower((int) preloadkeystroke);
7426 else
7427 cmd = (int) preloadkeystroke; /* shouldn't happen */
7431 switch(cmd){
7432 case 'd' : /* delete */
7433 we_cancel = busy_cue(NULL, NULL, 1);
7434 rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */
7435 if(we_cancel)
7436 cancel_busy_cue(0);
7437 break;
7439 case 'u' : /* undelete */
7440 we_cancel = busy_cue(NULL, NULL, 1);
7441 rv = cmd_undelete(state, msgmap, agg);
7442 if(we_cancel)
7443 cancel_busy_cue(0);
7444 break;
7446 case 'r' : /* reply */
7447 rv = cmd_reply(state, msgmap, agg, NULL);
7448 break;
7450 case 'f' : /* Forward */
7451 rv = cmd_forward(state, msgmap, agg, NULL);
7452 break;
7454 case '%' : /* print */
7455 rv = cmd_print(state, msgmap, agg, MsgIndx);
7456 break;
7458 case 't' : /* take address */
7459 rv = cmd_take_addr(state, msgmap, agg);
7460 break;
7462 case 's' : /* save */
7463 rv = cmd_save(state, stream, msgmap, agg, MsgIndx);
7464 break;
7466 case 'e' : /* export */
7467 rv = cmd_export(state, msgmap, q_line, agg);
7468 break;
7470 case '|' : /* pipe */
7471 rv = cmd_pipe(state, msgmap, agg);
7472 break;
7474 case '*' : /* flag */
7475 we_cancel = busy_cue(NULL, NULL, 1);
7476 rv = cmd_flag(state, msgmap, agg);
7477 if(we_cancel)
7478 cancel_busy_cue(0);
7479 break;
7481 case 'b' : /* bounce */
7482 rv = cmd_bounce(state, msgmap, agg, NULL);
7483 break;
7485 case '/' :
7486 collapse_or_expand(state, stream, msgmap,
7487 F_ON(F_SLASH_COLL_ENTIRE, ps_global)
7488 ? 0L
7489 : mn_get_cur(msgmap));
7490 break;
7492 case ':' :
7493 select_thread_stmp(state, stream, msgmap);
7494 break;
7496 case 'x' : /* Expunge */
7497 rv = cmd_expunge(state, stream, msgmap, agg);
7498 break;
7500 case 'c' : /* cancel */
7501 cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command"
7502 : "Apply command");
7503 break;
7505 case '#' :
7506 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7507 static ESCKEY_S choose_role[] = {
7508 {'r', 'r', "R", N_("Reply")},
7509 {'f', 'f', "F", N_("Forward")},
7510 {'b', 'b', "B", N_("Bounce")},
7511 {-1, 0, NULL, NULL}
7513 int action;
7514 ACTION_S *role = NULL;
7516 action = radio_buttons(_("Reply, Forward or Bounce using a role? "),
7517 -FOOTER_ROWS(state), choose_role,
7518 'r', 'x', h_role_aggregate, RB_NORM);
7519 if(action == 'r' || action == 'f' || action == 'b'){
7520 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
7522 redraw = state->redrawer;
7523 state->redrawer = NULL;
7524 prev_screen = state->prev_screen;
7525 role = NULL;
7526 state->next_screen = SCREEN_FUN_NULL;
7528 if(role_select_screen(state, &role,
7529 action == 'f' ? MC_FORWARD :
7530 action == 'r' ? MC_REPLY :
7531 action == 'b' ? MC_BOUNCE : 0) < 0){
7532 cmd_cancelled(action == 'f' ? _("Forward") :
7533 action == 'r' ? _("Reply") : _("Bounce"));
7534 state->next_screen = prev_screen;
7535 state->redrawer = redraw;
7536 state->mangled_screen = 1;
7538 else{
7539 if(role)
7540 role = combine_inherited_role(role);
7541 else{
7542 role = (ACTION_S *) fs_get(sizeof(*role));
7543 memset((void *) role, 0, sizeof(*role));
7544 role->nick = cpystr("Default Role");
7547 state->redrawer = NULL;
7548 switch(action){
7549 case 'r':
7550 (void) cmd_reply(state, msgmap, agg, role);
7551 break;
7553 case 'f':
7554 (void) cmd_forward(state, msgmap, agg, role);
7555 break;
7557 case 'b':
7558 (void) cmd_bounce(state, msgmap, agg, role);
7559 break;
7562 if(role)
7563 free_action(&role);
7565 if(redraw)
7566 (*redraw)();
7568 state->next_screen = prev_screen;
7569 state->redrawer = redraw;
7570 state->mangled_screen = 1;
7574 break;
7576 case 'z' : /* default */
7577 q_status_message(SM_INFO, 0, 2,
7578 "Cancelled, there is no default command");
7579 break;
7581 default:
7582 break;
7585 return(rv);
7590 * Select by message number ranges.
7591 * Sets searched bits in mail_elts
7593 * Args limitsrch -- limit search to this searchset
7595 * Returns 0 on success.
7598 select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
7600 int r, end, cur;
7601 long n1, n2, raw;
7602 char number1[16], number2[16], numbers[80], *p, *t;
7603 HelpType help;
7604 MESSAGECACHE *mc;
7606 numbers[0] = '\0';
7607 ps_global->mangled_footer = 1;
7608 help = NO_HELP;
7609 while(1){
7610 int flags = OE_APPEND_CURRENT;
7612 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7613 sizeof(numbers), _(select_num), NULL, help, &flags);
7614 if(r == 4)
7615 continue;
7617 if(r == 3){
7618 help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
7619 continue;
7622 for(t = p = numbers; *p ; p++) /* strip whitespace */
7623 if(!isspace((unsigned char)*p))
7624 *t++ = *p;
7626 *t = '\0';
7628 if(r == 1 || numbers[0] == '\0'){
7629 cmd_cancelled("Selection by number");
7630 return(1);
7632 else
7633 break;
7636 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7637 if((mc = mail_elt(stream, n1)) != NULL)
7638 mc->searched = 0; /* clear searched bits */
7640 for(p = numbers; *p ; p++){
7641 t = number1;
7642 while(*p && isdigit((unsigned char)*p))
7643 *t++ = *p++;
7645 *t = '\0';
7647 end = cur = 0;
7648 if(number1[0] == '\0'){
7649 if(*p == '-'){
7650 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7651 _("Invalid number range, missing number before \"-\": %s"),
7652 numbers);
7653 return(1);
7655 else if(!strucmp("end", p)){
7656 end = 1;
7657 p += strlen("end");
7659 else if(!strucmp("$", p)){
7660 end = 1;
7661 p++;
7663 else if(*p == '.'){
7664 cur = 1;
7665 p++;
7667 else{
7668 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7669 _("Invalid message number: %s"), numbers);
7670 return(1);
7674 if(end)
7675 n1 = mn_get_total(msgmap);
7676 else if(cur)
7677 n1 = mn_get_cur(msgmap);
7678 else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
7679 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7680 _("\"%s\" out of message number range"),
7681 long2string(n1));
7682 return(1);
7685 t = number2;
7686 if(*p == '-'){
7687 while(*++p && isdigit((unsigned char)*p))
7688 *t++ = *p;
7690 *t = '\0';
7692 end = cur = 0;
7693 if(number2[0] == '\0'){
7694 if(!strucmp("end", p)){
7695 end = 1;
7696 p += strlen("end");
7698 else if(!strucmp(p, "$")){
7699 end = 1;
7700 p++;
7702 else if(*p == '.'){
7703 cur = 1;
7704 p++;
7706 else{
7707 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7708 _("Invalid number range, missing number after \"-\": %s"),
7709 numbers);
7710 return(1);
7714 if(end)
7715 n2 = mn_get_total(msgmap);
7716 else if(cur)
7717 n2 = mn_get_cur(msgmap);
7718 else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){
7719 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7720 _("\"%s\" out of message number range"),
7721 long2string(n2));
7722 return(1);
7725 if(n2 <= n1){
7726 char t[20];
7728 strncpy(t, long2string(n1), sizeof(t));
7729 t[sizeof(t)-1] = '\0';
7730 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7731 _("Invalid reverse message number range: %s-%s"),
7732 t, long2string(n2));
7733 return(1);
7736 for(;n1 <= n2; n1++){
7737 raw = mn_m2raw(msgmap, n1);
7738 if(raw > 0L
7739 && (!(limitsrch && *limitsrch)
7740 || in_searchset(*limitsrch, (unsigned long) raw)))
7741 mm_searched(stream, raw);
7744 else{
7745 raw = mn_m2raw(msgmap, n1);
7746 if(raw > 0L
7747 && (!(limitsrch && *limitsrch)
7748 || in_searchset(*limitsrch, (unsigned long) raw)))
7749 mm_searched(stream, raw);
7752 if(*p == '\0')
7753 break;
7756 return(0);
7761 * Select by thread number ranges.
7762 * Sets searched bits in mail_elts
7764 * Args limitsrch -- limit search to this searchset
7766 * Returns 0 on success.
7769 select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset)
7771 int r, end, cur;
7772 long n1, n2;
7773 char number1[16], number2[16], numbers[80], *p, *t;
7774 HelpType help;
7775 PINETHRD_S *thrd = NULL, *th;
7776 MESSAGECACHE *mc;
7778 numbers[0] = '\0';
7779 ps_global->mangled_footer = 1;
7780 help = NO_HELP;
7781 while(1){
7782 int flags = OE_APPEND_CURRENT;
7784 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7785 sizeof(numbers), _(select_num), NULL, help, &flags);
7786 if(r == 4)
7787 continue;
7789 if(r == 3){
7790 help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP;
7791 continue;
7794 for(t = p = numbers; *p ; p++) /* strip whitespace */
7795 if(!isspace((unsigned char)*p))
7796 *t++ = *p;
7798 *t = '\0';
7800 if(r == 1 || numbers[0] == '\0'){
7801 cmd_cancelled("Selection by number");
7802 return(1);
7804 else
7805 break;
7808 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7809 if((mc = mail_elt(stream, n1)) != NULL)
7810 mc->searched = 0; /* clear searched bits */
7812 for(p = numbers; *p ; p++){
7813 t = number1;
7814 while(*p && isdigit((unsigned char)*p))
7815 *t++ = *p++;
7817 *t = '\0';
7819 end = cur = 0;
7820 if(number1[0] == '\0'){
7821 if(*p == '-'){
7822 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7823 _("Invalid number range, missing number before \"-\": %s"),
7824 numbers);
7825 return(1);
7827 else if(!strucmp("end", p)){
7828 end = 1;
7829 p += strlen("end");
7831 else if(!strucmp(p, "$")){
7832 end = 1;
7833 p++;
7835 else if(*p == '.'){
7836 cur = 1;
7837 p++;
7839 else{
7840 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7841 _("Invalid thread number: %s"), numbers);
7842 return(1);
7846 if(end)
7847 n1 = msgmap->max_thrdno;
7848 else if(cur){
7849 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7850 n1 = th->thrdno;
7852 else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){
7853 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7854 _("\"%s\" out of thread number range"),
7855 long2string(n1));
7856 return(1);
7859 t = number2;
7860 if(*p == '-'){
7862 while(*++p && isdigit((unsigned char)*p))
7863 *t++ = *p;
7865 *t = '\0';
7867 end = 0;
7868 if(number2[0] == '\0'){
7869 if(!strucmp("end", p)){
7870 end = 1;
7871 p += strlen("end");
7873 else if(!strucmp("$", p)){
7874 end = 1;
7875 p++;
7877 else if(*p == '.'){
7878 cur = 1;
7879 p++;
7881 else{
7882 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7883 _("Invalid number range, missing number after \"-\": %s"),
7884 numbers);
7885 return(1);
7889 if(end)
7890 n2 = msgmap->max_thrdno;
7891 else if(cur){
7892 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7893 n2 = th->thrdno;
7895 else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){
7896 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7897 _("\"%s\" out of thread number range"),
7898 long2string(n2));
7899 return(1);
7902 if(n2 <= n1){
7903 char t[20];
7905 strncpy(t, long2string(n1), sizeof(t));
7906 t[sizeof(t)-1] = '\0';
7907 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7908 _("Invalid reverse message number range: %s-%s"),
7909 t, long2string(n2));
7910 return(1);
7913 for(;n1 <= n2; n1++){
7914 thrd = find_thread_by_number(stream, msgmap, n1, thrd);
7916 if(thrd)
7917 set_search_bit_for_thread(stream, thrd, msgset);
7920 else{
7921 thrd = find_thread_by_number(stream, msgmap, n1, NULL);
7923 if(thrd)
7924 set_search_bit_for_thread(stream, thrd, msgset);
7927 if(*p == '\0')
7928 break;
7931 return(0);
7936 * Select by message dates.
7937 * Sets searched bits in mail_elts
7939 * Args limitsrch -- limit search to this searchset
7941 * Returns 0 on success.
7944 select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
7946 int r, we_cancel = 0, when = 0;
7947 char date[100], defdate[100], prompt[128];
7948 time_t seldate = time(0);
7949 struct tm *seldate_tm;
7950 SEARCHPGM *pgm;
7951 HelpType help;
7952 static struct _tense {
7953 char *preamble,
7954 *range,
7955 *scope;
7956 } tense[] = {
7957 {"were ", "SENT SINCE", " (inclusive)"},
7958 {"were ", "SENT BEFORE", " (exclusive)"},
7959 {"were ", "SENT ON", "" },
7960 {"", "ARRIVED SINCE", " (inclusive)"},
7961 {"", "ARRIVED BEFORE", " (exclusive)"},
7962 {"", "ARRIVED ON", "" }
7965 date[0] = '\0';
7966 ps_global->mangled_footer = 1;
7967 help = NO_HELP;
7970 * If talking to an old server, default to SINCE instead of
7971 * SENTSINCE, which was added later.
7973 if(is_imap_stream(stream) && !modern_imap_stream(stream))
7974 when = 3;
7976 while(1){
7977 int flags = OE_APPEND_CURRENT;
7979 seldate_tm = localtime(&seldate);
7980 snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday,
7981 month_abbrev(seldate_tm->tm_mon + 1),
7982 seldate_tm->tm_year + 1900);
7983 defdate[sizeof(defdate)-1] = '\0';
7984 snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ",
7985 tense[when].preamble, tense[when].range,
7986 tense[when].scope, defdate);
7987 prompt[sizeof(prompt)-1] = '\0';
7988 r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date),
7989 prompt, sel_date_opt, help, &flags);
7990 switch (r){
7991 case 1 :
7992 cmd_cancelled("Selection by date");
7993 return(1);
7995 case 3 :
7996 help = (help == NO_HELP) ? h_select_date : NO_HELP;
7997 continue;
7999 case 4 :
8000 continue;
8002 case 11 :
8004 MESSAGECACHE *mc;
8005 long rawno;
8007 if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L
8008 && rawno <= stream->nmsgs
8009 && (mc = mail_elt(stream, rawno))){
8011 /* cache not filled in yet? */
8012 if(mc->day == 0){
8013 char seq[20];
8015 if(stream->dtb && stream->dtb->flags & DR_NEWS){
8016 strncpy(seq,
8017 ulong2string(mail_uid(stream, rawno)),
8018 sizeof(seq));
8019 seq[sizeof(seq)-1] = '\0';
8020 mail_fetch_overview(stream, seq, NULL);
8022 else{
8023 strncpy(seq, long2string(rawno),
8024 sizeof(seq));
8025 seq[sizeof(seq)-1] = '\0';
8026 mail_fetch_fast(stream, seq, 0L);
8030 /* mail_date returns fixed field width date */
8031 mail_date(date, mc);
8032 date[11] = '\0';
8036 continue;
8038 case 12 : /* set default to PREVIOUS day */
8039 seldate -= 86400;
8040 continue;
8042 case 13 : /* set default to NEXT day */
8043 seldate += 86400;
8044 continue;
8046 case 14 :
8047 when = (when+1) % (sizeof(tense) / sizeof(struct _tense));
8048 continue;
8050 default:
8051 break;
8054 removing_leading_white_space(date);
8055 removing_trailing_white_space(date);
8056 if(!*date){
8057 strncpy(date, defdate, sizeof(date));
8058 date[sizeof(date)-1] = '\0';
8061 break;
8064 if((pgm = mail_newsearchpgm()) != NULL){
8065 MESSAGECACHE elt;
8066 short converted_date;
8068 if(mail_parse_date(&elt, (unsigned char *) date)){
8069 converted_date = mail_shortdate(elt.year, elt.month, elt.day);
8071 switch(when){
8072 case 0:
8073 pgm->sentsince = converted_date;
8074 break;
8075 case 1:
8076 pgm->sentbefore = converted_date;
8077 break;
8078 case 2:
8079 pgm->senton = converted_date;
8080 break;
8081 case 3:
8082 pgm->since = converted_date;
8083 break;
8084 case 4:
8085 pgm->before = converted_date;
8086 break;
8087 case 5:
8088 pgm->on = converted_date;
8089 break;
8092 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8094 if(ps_global && ps_global->ttyo){
8095 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8096 ps_global->mangled_footer = 1;
8099 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8101 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8103 if(we_cancel)
8104 cancel_busy_cue(0);
8106 /* we know this was freed in mail_search, let caller know */
8107 if(limitsrch)
8108 *limitsrch = NULL;
8110 else{
8111 mail_free_searchpgm(&pgm);
8112 q_status_message1(SM_ORDER, 3, 3,
8113 _("Invalid date entered: %s"), date);
8114 return(1);
8118 return(0);
8123 * Select by searching in message headers or body.
8124 * Sets searched bits in mail_elts
8126 * Args limitsrch -- limit search to this searchset
8128 * Returns 0 on success.
8131 select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8133 int r, ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
8134 int not = 0, me = 0;
8135 char sstring[80], savedsstring[80], tmp[128];
8136 char *p, *sval = NULL;
8137 char buftmp[MAILTMPLEN], namehdr[80];
8138 ESCKEY_S ekey[8];
8139 ENVELOPE *env = NULL;
8140 HelpType help;
8141 unsigned flagsforhist = 0;
8142 static HISTORY_S *history = NULL;
8143 static char *recip = "RECIPIENTS";
8144 static char *partic = "PARTICIPANTS";
8145 static char *match_me = N_("[Match_My_Addresses]");
8146 static char *dont_match_me = N_("[Don't_Match_My_Addresses]");
8148 if(stream == NULL && msgmap == NULL && msgno == 0 && limitsrch == NULL){
8149 if(history != NULL)
8150 free_hist(&history);
8151 return 0;
8154 ps_global->mangled_footer = 1;
8155 savedsstring[0] = '\0';
8156 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8158 while(1){
8159 type = radio_buttons(not ? _(sel_text_not) : _(sel_text),
8160 -FOOTER_ROWS(ps_global), sel_text_opt,
8161 's', 'x', NO_HELP, RB_NORM|RB_RET_HELP);
8163 if(type == '!')
8164 not = !not;
8165 else if(type == 3){
8166 helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS",
8167 HLPD_SIMPLE);
8168 ps_global->mangled_screen = 1;
8170 else
8171 break;
8175 * prepare some friendly defaults...
8177 switch(type){
8178 case 't' : /* address fields, offer To or From */
8179 case 'f' :
8180 case 'c' :
8181 case 'r' :
8182 case 'p' :
8183 sval = (type == 't') ? "TO" :
8184 (type == 'f') ? "FROM" :
8185 (type == 'c') ? "CC" :
8186 (type == 'r') ? recip : partic;
8187 ekey[ekeyi].ch = ctrl('T');
8188 ekey[ekeyi].name = "^T";
8189 ekey[ekeyi].rval = 10;
8190 /* TRANSLATORS: use Current To Address */
8191 ekey[ekeyi++].label = N_("Cur To");
8192 ekey[ekeyi].ch = ctrl('R');
8193 ekey[ekeyi].name = "^R";
8194 ekey[ekeyi].rval = 11;
8195 /* TRANSLATORS: use Current From Address */
8196 ekey[ekeyi++].label = N_("Cur From");
8197 ekey[ekeyi].ch = ctrl('W');
8198 ekey[ekeyi].name = "^W";
8199 ekey[ekeyi].rval = 12;
8200 /* TRANSLATORS: use Current Cc Address */
8201 ekey[ekeyi++].label = N_("Cur Cc");
8202 ekey[ekeyi].ch = ctrl('Y');
8203 ekey[ekeyi].name = "^Y";
8204 ekey[ekeyi].rval = 13;
8205 /* TRANSLATORS: Match Me means match my address */
8206 ekey[ekeyi++].label = N_("Match Me");
8207 ekey[ekeyi].ch = 0;
8208 ekey[ekeyi].name = "";
8209 ekey[ekeyi].rval = 0;
8210 ekey[ekeyi++].label = "";
8211 break;
8213 case 's' :
8214 sval = "SUBJECT";
8215 ekey[ekeyi].ch = ctrl('X');
8216 ekey[ekeyi].name = "^X";
8217 ekey[ekeyi].rval = 14;
8218 /* TRANSLATORS: use Current Subject */
8219 ekey[ekeyi++].label = N_("Cur Subject");
8220 break;
8222 case 'a' :
8223 sval = "TEXT";
8224 break;
8226 case 'b' :
8227 sval = "BODYTEXT";
8228 break;
8230 case 'h' :
8231 strncpy(tmp, "Name of HEADER to match : ", sizeof(tmp)-1);
8232 tmp[sizeof(tmp)-1] = '\0';
8233 flags = OE_APPEND_CURRENT;
8234 namehdr[0] = '\0';
8235 r = 'x';
8236 while (r == 'x'){
8237 int done = 0;
8239 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8240 sizeof(namehdr), tmp, ekey, NO_HELP, &flags);
8241 if (r == 1){
8242 cmd_cancelled("Selection by text");
8243 return(1);
8245 removing_leading_white_space(namehdr);
8246 while(!done){
8247 while ((namehdr[0] != '\0') && /* remove trailing ":" */
8248 (namehdr[strlen(namehdr) - 1] == ':'))
8249 namehdr[strlen(namehdr) - 1] = '\0';
8250 if ((namehdr[0] != '\0')
8251 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8252 removing_trailing_white_space(namehdr);
8253 else
8254 done++;
8256 if (strchr(namehdr,' ') || strchr(namehdr,'\t') ||
8257 strchr(namehdr,':'))
8258 namehdr[0] = '\0';
8259 if (namehdr[0] == '\0')
8260 r = 'x';
8262 sval = namehdr;
8263 break;
8265 case 'x':
8266 break;
8268 default:
8269 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
8270 return(1);
8273 ekey[ekeyi].ch = KEY_UP;
8274 ekey[ekeyi].rval = 30;
8275 ekey[ekeyi].name = "";
8276 ku = ekeyi;
8277 ekey[ekeyi++].label = "";
8279 ekey[ekeyi].ch = KEY_DOWN;
8280 ekey[ekeyi].rval = 31;
8281 ekey[ekeyi].name = "";
8282 ekey[ekeyi++].label = "";
8284 ekey[ekeyi].ch = -1;
8286 if(type != 'x'){
8288 init_hist(&history, HISTSIZE);
8290 if(ekey[0].ch > -1 && msgno > 0L
8291 && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),
8292 NULL)))
8293 ekey[0].ch = -1;
8295 sstring[0] = '\0';
8296 help = NO_HELP;
8297 r = type;
8298 while(r != 'x'){
8299 if(not)
8300 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8301 snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval);
8302 else
8303 snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval);
8305 if(items_in_hist(history) > 0){
8306 ekey[ku].name = HISTORY_UP_KEYNAME;
8307 ekey[ku].label = HISTORY_KEYLABEL;
8308 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
8309 ekey[ku+1].label = HISTORY_KEYLABEL;
8311 else{
8312 ekey[ku].name = "";
8313 ekey[ku].label = "";
8314 ekey[ku+1].name = "";
8315 ekey[ku+1].label = "";
8318 flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
8319 r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
8320 79, tmp, ekey, help, &flags);
8322 if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me)))))
8323 me = 0;
8325 switch(r){
8326 case 3 :
8327 help = (help == NO_HELP)
8328 ? (not
8329 ? ((type == 'f') ? h_select_txt_not_from
8330 : (type == 't') ? h_select_txt_not_to
8331 : (type == 'c') ? h_select_txt_not_cc
8332 : (type == 's') ? h_select_txt_not_subj
8333 : (type == 'a') ? h_select_txt_not_all
8334 : (type == 'r') ? h_select_txt_not_recip
8335 : (type == 'p') ? h_select_txt_not_partic
8336 : (type == 'b') ? h_select_txt_not_body
8337 : NO_HELP)
8338 : ((type == 'f') ? h_select_txt_from
8339 : (type == 't') ? h_select_txt_to
8340 : (type == 'c') ? h_select_txt_cc
8341 : (type == 's') ? h_select_txt_subj
8342 : (type == 'a') ? h_select_txt_all
8343 : (type == 'r') ? h_select_txt_recip
8344 : (type == 'p') ? h_select_txt_partic
8345 : (type == 'b') ? h_select_txt_body
8346 : NO_HELP))
8347 : NO_HELP;
8349 case 4 :
8350 continue;
8352 case 10 : /* To: default */
8353 if(env && env->to && env->to->mailbox){
8354 snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox,
8355 env->to->host ? "@" : "",
8356 env->to->host ? env->to->host : "");
8357 sstring[sizeof(sstring)-1] = '\0';
8359 continue;
8361 case 11 : /* From: default */
8362 if(env && env->from && env->from->mailbox){
8363 snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox,
8364 env->from->host ? "@" : "",
8365 env->from->host ? env->from->host : "");
8366 sstring[sizeof(sstring)-1] = '\0';
8368 continue;
8370 case 12 : /* Cc: default */
8371 if(env && env->cc && env->cc->mailbox){
8372 snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox,
8373 env->cc->host ? "@" : "",
8374 env->cc->host ? env->cc->host : "");
8375 sstring[sizeof(sstring)-1] = '\0';
8377 continue;
8379 case 13 : /* Match my addresses */
8380 me++;
8381 snprintf(sstring, sizeof(sstring), "%s", not ? _(dont_match_me) : _(match_me));
8382 continue;
8384 case 14 : /* Subject: default */
8385 if(env && env->subject && env->subject[0]){
8386 char *q = NULL;
8388 snprintf(buftmp, sizeof(buftmp), "%.75s", env->subject);
8389 buftmp[sizeof(buftmp)-1] = '\0';
8390 q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
8391 SIZEOF_20KBUF, buftmp);
8392 if(q != env->subject){
8393 snprintf(savedsstring, sizeof(savedsstring), "%.70s", q);
8394 savedsstring[sizeof(savedsstring)-1] = '\0';
8397 snprintf(sstring, sizeof(sstring), "%s", q);
8398 sstring[sizeof(sstring)-1] = '\0';
8401 continue;
8403 case 30 :
8404 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8405 if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){
8406 strncpy(sstring, p, sizeof(sstring));
8407 sstring[sizeof(sstring)-1] = '\0';
8408 if(history->hist[history->curindex]){
8409 flagsforhist = history->hist[history->curindex]->flags;
8410 not = (flagsforhist & 0x1) ? 1 : 0;
8411 me = (flagsforhist & 0x2) ? 1 : 0;
8414 else
8415 Writechar(BELL, 0);
8417 continue;
8419 case 31 :
8420 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8421 if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){
8422 strncpy(sstring, p, sizeof(sstring));
8423 sstring[sizeof(sstring)-1] = '\0';
8424 if(history->hist[history->curindex]){
8425 flagsforhist = history->hist[history->curindex]->flags;
8426 not = (flagsforhist & 0x1) ? 1 : 0;
8427 me = (flagsforhist & 0x2) ? 1 : 0;
8430 else
8431 Writechar(BELL, 0);
8433 continue;
8435 default :
8436 break;
8439 if(r == 1 || sstring[0] == '\0')
8440 r = 'x';
8442 break;
8446 if(type == 'x' || r == 'x'){
8447 cmd_cancelled("Selection by text");
8448 return(1);
8451 if(ps_global && ps_global->ttyo){
8452 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8453 ps_global->mangled_footer = 1;
8456 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8458 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8459 save_hist(history, sstring, flagsforhist, NULL);
8461 rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch);
8462 if(we_cancel)
8463 cancel_busy_cue(0);
8465 return(rv);
8470 * Select by message size.
8471 * Sets searched bits in mail_elts
8473 * Args limitsrch -- limit search to this searchset
8475 * Returns 0 on success.
8478 select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch)
8480 int r, large = 1, we_cancel = 0;
8481 unsigned long n, mult = 1L, numerator = 0L, divisor = 1L;
8482 char size[16], numbers[80], *p, *t;
8483 HelpType help;
8484 SEARCHPGM *pgm;
8485 long flags = (SE_NOPREFETCH | SE_FREE);
8487 numbers[0] = '\0';
8488 ps_global->mangled_footer = 1;
8490 help = NO_HELP;
8491 while(1){
8492 int flgs = OE_APPEND_CURRENT;
8494 sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger;
8496 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
8497 sizeof(numbers), large ? _(select_size_larger_msg)
8498 : _(select_size_smaller_msg),
8499 sel_size_opt, help, &flgs);
8500 if(r == 4)
8501 continue;
8503 if(r == 14){
8504 large = 1 - large;
8505 continue;
8508 if(r == 3){
8509 help = (help == NO_HELP) ? (large ? h_select_by_larger_size
8510 : h_select_by_smaller_size)
8511 : NO_HELP;
8512 continue;
8515 for(t = p = numbers; *p ; p++) /* strip whitespace */
8516 if(!isspace((unsigned char)*p))
8517 *t++ = *p;
8519 *t = '\0';
8521 if(r == 1 || numbers[0] == '\0'){
8522 cmd_cancelled("Selection by size");
8523 return(1);
8525 else
8526 break;
8529 if(numbers[0] == '-'){
8530 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8531 _("Invalid size entered: %s"), numbers);
8532 return(1);
8535 t = size;
8536 p = numbers;
8538 while(*p && isdigit((unsigned char)*p))
8539 *t++ = *p++;
8541 *t = '\0';
8543 if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){
8544 size[0] = '0';
8545 size[1] = '\0';
8548 if(size[0] == '\0'){
8549 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8550 _("Invalid size entered: %s"), numbers);
8551 return(1);
8554 n = strtoul(size, (char **)NULL, 10);
8556 size[0] = '\0';
8557 if(*p == '.'){
8559 * We probably ought to just use atof() to convert 1.1 into a
8560 * double, but since we haven't used atof() anywhere else I'm
8561 * reluctant to use it because of portability concerns.
8563 p++;
8564 t = size;
8565 while(*p && isdigit((unsigned char)*p)){
8566 *t++ = *p++;
8567 divisor *= 10;
8570 *t = '\0';
8572 if(size[0])
8573 numerator = strtoul(size, (char **)NULL, 10);
8576 switch(*p){
8577 case 'g':
8578 case 'G':
8579 mult *= 1000;
8580 /* fall through */
8582 case 'm':
8583 case 'M':
8584 mult *= 1000;
8585 /* fall through */
8587 case 'k':
8588 case 'K':
8589 mult *= 1000;
8590 break;
8593 n = n * mult + (numerator * mult) / divisor;
8595 pgm = mail_newsearchpgm();
8596 if(large)
8597 pgm->larger = n;
8598 else
8599 pgm->smaller = n;
8601 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8602 flags |= SE_NOSERVER;
8604 if(ps_global && ps_global->ttyo){
8605 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8606 ps_global->mangled_footer = 1;
8609 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8611 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8612 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8613 /* we know this was freed in mail_search, let caller know */
8614 if(limitsrch)
8615 *limitsrch = NULL;
8617 if(we_cancel)
8618 cancel_busy_cue(0);
8620 return(0);
8625 * visible_searchset -- return c-client search set unEXLDed
8626 * sequence numbers
8628 SEARCHSET *
8629 visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap)
8631 long n, run;
8632 SEARCHSET *full_set = NULL, **set;
8635 * If we're talking to anything other than a server older than
8636 * imap 4rev1, build a searchset otherwise it'll choke.
8638 if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
8639 if(any_lflagged(msgmap, MN_EXLD)){
8640 for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
8641 if(get_lflag(stream, NULL, n, MN_EXLD)){
8642 if(run){ /* previous NOT excluded? */
8643 if(run > 1L)
8644 (*set)->last = n - 1L;
8646 set = &(*set)->next;
8647 run = 0L;
8650 else if(run++){ /* next in run */
8651 (*set)->last = n;
8653 else{ /* start of run */
8654 *set = mail_newsearchset();
8655 (*set)->first = n;
8658 else{
8659 full_set = mail_newsearchset();
8660 full_set->first = 1L;
8661 full_set->last = stream->nmsgs;
8665 return(full_set);
8670 * Select by message status bits.
8671 * Sets searched bits in mail_elts
8673 * Args limitsrch -- limit search to this searchset
8675 * Returns 0 on success.
8678 select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch)
8680 int s, not = 0, we_cancel = 0, rv;
8682 while(1){
8683 s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag),
8684 -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x',
8685 NO_HELP, RB_NORM|RB_RET_HELP);
8687 if(s == 'x'){
8688 cmd_cancelled("Selection by status");
8689 return(1);
8691 else if(s == 3){
8692 helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"),
8693 HLPD_SIMPLE);
8694 ps_global->mangled_screen = 1;
8696 else if(s == '!')
8697 not = !not;
8698 else
8699 break;
8702 if(ps_global && ps_global->ttyo){
8703 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8704 ps_global->mangled_footer = 1;
8707 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8708 rv = agg_flag_select(stream, not, s, limitsrch);
8709 if(we_cancel)
8710 cancel_busy_cue(0);
8712 return(rv);
8717 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8718 * Sets searched bits in mail_elts
8720 * Args limitsrch -- limit search to this searchset
8722 * Returns 0 on success.
8725 select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
8727 char rulenick[1000], *nick;
8728 PATGRP_S *patgrp;
8729 int r, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
8730 | ROLE_DO_INCOLS
8731 | ROLE_DO_ROLES
8732 | ROLE_DO_SCORES
8733 | ROLE_DO_OTHER
8734 | ROLE_DO_FILTER;
8736 rulenick[0] = '\0';
8737 ps_global->mangled_footer = 1;
8740 int oe_flags;
8742 oe_flags = OE_APPEND_CURRENT;
8743 r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0,
8744 sizeof(rulenick),
8745 not ? _("Rule to NOT match: ")
8746 : _("Rule to match: "),
8747 sel_key_opt, NO_HELP, &oe_flags);
8749 if(r == 14){
8750 /* select rulenick from a list */
8751 if((nick=choose_a_rule(rflags)) != NULL){
8752 strncpy(rulenick, nick, sizeof(rulenick)-1);
8753 rulenick[sizeof(rulenick)-1] = '\0';
8754 fs_give((void **) &nick);
8756 else
8757 r = 4;
8759 else if(r == '!')
8760 not = !not;
8762 if(r == 3){
8763 helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE);
8764 ps_global->mangled_screen = 1;
8766 else if(r == 1){
8767 cmd_cancelled("Selection by Rule");
8768 return(1);
8771 removing_leading_and_trailing_white_space(rulenick);
8773 }while(r == 3 || r == 4 || r == '!');
8777 * The approach of requiring a nickname instead of just allowing the
8778 * user to select from the list of rules has the drawback that a rule
8779 * may not have a nickname, or there may be more than one rule with
8780 * the same nickname. However, it has the benefit of allowing the user
8781 * to type in the nickname and, most importantly, allows us to set
8782 * up the ! (not). We could incorporate the ! into the selection
8783 * screen, but this is easier and also allows the typing of nicks.
8784 * User can just set up nicknames if they want to use this feature.
8786 patgrp = nick_to_patgrp(rulenick, rflags);
8788 if(patgrp){
8789 if(ps_global && ps_global->ttyo){
8790 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8791 ps_global->mangled_footer = 1;
8794 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8795 match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL,
8796 get_msg_score,
8797 (not ? MP_NOT : 0) | SE_NOPREFETCH);
8798 free_patgrp(&patgrp);
8799 if(we_cancel)
8800 cancel_busy_cue(0);
8803 if(limitsrch && *limitsrch){
8804 mail_free_searchset(limitsrch);
8805 *limitsrch = NULL;
8808 return(0);
8813 * Allow user to choose a rule from their list of rules.
8815 * Returns an allocated rule nickname on success, NULL otherwise.
8817 char *
8818 choose_a_rule(int rflags)
8820 char *choice = NULL;
8821 char **rule_list, **lp;
8822 int cnt = 0;
8823 PAT_S *pat;
8824 PAT_STATE pstate;
8826 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){
8827 q_status_message(SM_ORDER, 3, 3,
8828 _("No rules available. Use Setup/Rules to add some."));
8829 return(choice);
8833 * Build a list of rules to choose from.
8836 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8837 cnt++;
8839 if(cnt <= 0){
8840 q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
8841 return(choice);
8844 lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
8845 memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
8847 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8848 *lp++ = cpystr((pat->patgrp && pat->patgrp->nick)
8849 ? pat->patgrp->nick : "?");
8851 /* TRANSLATORS: SELECT A RULE is a screen title
8852 TRANSLATORS: Print something1 using something2.
8853 "rules" is something1 */
8854 choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"),
8855 _("rules"), h_select_rule_screen,
8856 _("HELP FOR SELECTING A RULE NICKNAME"), NULL);
8858 if(!choice)
8859 q_status_message(SM_ORDER, 1, 4, "No choice");
8861 free_list_array(&rule_list);
8863 return(choice);
8868 * Select by current thread.
8869 * Sets searched bits in mail_elts for this entire thread
8871 * Args limitsrch -- limit search to this searchset
8873 * Returns 0 on success.
8876 select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
8878 long n;
8879 PINETHRD_S *thrd = NULL;
8880 int ret = 1;
8881 MESSAGECACHE *mc;
8883 if(!stream)
8884 return(ret);
8886 for(n = 1L; n <= stream->nmsgs; n++)
8887 if((mc = mail_elt(stream, n)) != NULL)
8888 mc->searched = 0; /* clear searched bits */
8890 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
8891 if(thrd && thrd->top && thrd->top != thrd->rawno)
8892 thrd = fetch_thread(stream, thrd->top);
8895 * This doesn't unselect if the thread is already selected
8896 * (like select current does), it always selects.
8897 * There is no way to select ! this thread.
8899 if(thrd){
8900 set_search_bit_for_thread(stream, thrd, limitsrch);
8901 ret = 0;
8904 return(ret);
8909 * Select by message keywords.
8910 * Sets searched bits in mail_elts
8912 * Args limitsrch -- limit search to this searchset
8914 * Returns 0 on success.
8917 select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
8919 int r, not = 0, we_cancel = 0;
8920 char keyword[MAXUSERFLAG+1], *kword;
8921 char *error = NULL, *p, *prompt;
8922 HelpType help;
8923 SEARCHPGM *pgm;
8925 keyword[0] = '\0';
8926 ps_global->mangled_footer = 1;
8928 help = NO_HELP;
8930 int oe_flags;
8932 if(error){
8933 q_status_message(SM_ORDER, 3, 4, error);
8934 fs_give((void **) &error);
8937 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
8938 if(not)
8939 prompt = _("Keyword (or keyword initial) to NOT match: ");
8940 else
8941 prompt = _("Keyword (or keyword initial) to match: ");
8943 else{
8944 if(not)
8945 prompt = _("Keyword to NOT match: ");
8946 else
8947 prompt = _("Keyword to match: ");
8950 oe_flags = OE_APPEND_CURRENT;
8951 r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0,
8952 sizeof(keyword),
8953 prompt, sel_key_opt, help, &oe_flags);
8955 if(r == 14){
8956 /* select keyword from a list */
8957 if((kword=choose_a_keyword()) != NULL){
8958 strncpy(keyword, kword, sizeof(keyword)-1);
8959 keyword[sizeof(keyword)-1] = '\0';
8960 fs_give((void **) &kword);
8962 else
8963 r = 4;
8965 else if(r == '!')
8966 not = !not;
8968 if(r == 3)
8969 help = help == NO_HELP ? h_select_keyword : NO_HELP;
8970 else if(r == 1){
8971 cmd_cancelled("Selection by keyword");
8972 return(1);
8975 removing_leading_and_trailing_white_space(keyword);
8977 }while(r == 3 || r == 4 || r == '!' || keyword_check(keyword, &error));
8980 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
8981 p = initial_to_keyword(keyword);
8982 if(p != keyword){
8983 strncpy(keyword, p, sizeof(keyword)-1);
8984 keyword[sizeof(keyword)-1] = '\0';
8989 * We want to check the keyword, not the nickname of the keyword,
8990 * so convert it to the keyword if necessary.
8992 p = nick_to_keyword(keyword);
8993 if(p != keyword){
8994 strncpy(keyword, p, sizeof(keyword)-1);
8995 keyword[sizeof(keyword)-1] = '\0';
8998 pgm = mail_newsearchpgm();
8999 if(not){
9000 pgm->unkeyword = mail_newstringlist();
9001 pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword);
9002 pgm->unkeyword->text.size = strlen(keyword);
9004 else{
9005 pgm->keyword = mail_newstringlist();
9006 pgm->keyword->text.data = (unsigned char *) cpystr(keyword);
9007 pgm->keyword->text.size = strlen(keyword);
9010 if(ps_global && ps_global->ttyo){
9011 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9012 ps_global->mangled_footer = 1;
9015 we_cancel = busy_cue(_("Selecting"), NULL, 1);
9017 pgm->msgno = (limitsrch ? *limitsrch : NULL);
9018 pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE);
9019 /* we know this was freed in mail_search, let caller know */
9020 if(limitsrch)
9021 *limitsrch = NULL;
9023 if(we_cancel)
9024 cancel_busy_cue(0);
9026 return(0);
9031 * Allow user to choose a keyword from their list of keywords.
9033 * Returns an allocated keyword on success, NULL otherwise.
9035 char *
9036 choose_a_keyword(void)
9038 char *choice = NULL;
9039 char **keyword_list, **lp;
9040 int cnt;
9041 KEYWORD_S *kw;
9044 * Build a list of keywords to choose from.
9047 for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next)
9048 cnt++;
9050 if(cnt <= 0){
9051 q_status_message(SM_ORDER, 3, 4,
9052 _("No keywords defined, use \"keywords\" option in Setup/Config"));
9053 return(choice);
9056 lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list));
9057 memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list));
9059 for(kw = ps_global->keywords; kw; kw = kw->next)
9060 *lp++ = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9062 /* TRANSLATORS: SELECT A KEYWORD is a screen title
9063 TRANSLATORS: Print something1 using something2.
9064 "keywords" is something1 */
9065 choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"),
9066 _("keywords"), h_select_keyword_screen,
9067 _("HELP FOR SELECTING A KEYWORD"), NULL);
9069 if(!choice)
9070 q_status_message(SM_ORDER, 1, 4, "No choice");
9072 free_list_array(&keyword_list);
9074 return(choice);
9079 * Allow user to choose a list of keywords from their list of keywords.
9081 * Returns allocated list.
9083 char **
9084 choose_list_of_keywords(void)
9086 LIST_SEL_S *listhead, *ls, *p;
9087 char **ret = NULL;
9088 int cnt, i;
9089 KEYWORD_S *kw;
9092 * Build a list of keywords to choose from.
9095 p = listhead = NULL;
9096 for(kw = ps_global->keywords; kw; kw = kw->next){
9098 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9099 memset(ls, 0, sizeof(*ls));
9100 ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9102 if(p){
9103 p->next = ls;
9104 p = p->next;
9106 else
9107 listhead = p = ls;
9110 if(!listhead)
9111 return(ret);
9113 /* TRANSLATORS: SELECT KEYWORDS is a screen title
9114 Print something1 using something2.
9115 "keywords" is something1 */
9116 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9117 _("SELECT KEYWORDS"), _("keywords"),
9118 h_select_multkeyword_screen,
9119 _("HELP FOR SELECTING KEYWORDS"), NULL)){
9120 for(cnt = 0, p = listhead; p; p = p->next)
9121 if(p->selected)
9122 cnt++;
9124 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9125 memset(ret, 0, (cnt+1) * sizeof(*ret));
9126 for(i = 0, p = listhead; p; p = p->next)
9127 if(p->selected)
9128 ret[i++] = cpystr(p->item ? p->item : "");
9131 free_list_sel(&listhead);
9133 return(ret);
9138 * Allow user to choose a charset
9140 * Returns an allocated charset on success, NULL otherwise.
9142 char *
9143 choose_a_charset(int which_charsets)
9145 char *choice = NULL;
9146 char **charset_list, **lp;
9147 const CHARSET *cs;
9148 int cnt;
9151 * Build a list of charsets to choose from.
9154 for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){
9155 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9156 && ((which_charsets & CAC_ALL)
9157 || (which_charsets & CAC_POSTING
9158 && cs->flags & CF_POSTING)
9159 || (which_charsets & CAC_DISPLAY
9160 && cs->type != CT_2022
9161 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9162 cnt++;
9165 if(cnt <= 0){
9166 q_status_message(SM_ORDER, 3, 4,
9167 _("No charsets found? Enter charset manually."));
9168 return(choice);
9171 lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list));
9172 memset(charset_list, 0, (cnt+1) * sizeof(*charset_list));
9174 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9175 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9176 && ((which_charsets & CAC_ALL)
9177 || (which_charsets & CAC_POSTING
9178 && cs->flags & CF_POSTING)
9179 || (which_charsets & CAC_DISPLAY
9180 && cs->type != CT_2022
9181 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9182 *lp++ = cpystr(cs->name);
9185 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
9186 TRANSLATORS: Print something1 using something2.
9187 "character sets" is something1 */
9188 choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"),
9189 _("character sets"), h_select_charset_screen,
9190 _("HELP FOR SELECTING A CHARACTER SET"), NULL);
9192 if(!choice)
9193 q_status_message(SM_ORDER, 1, 4, "No choice");
9195 free_list_array(&charset_list);
9197 return(choice);
9202 * Allow user to choose a list of character sets and/or scripts
9204 * Returns allocated list.
9206 char **
9207 choose_list_of_charsets(void)
9209 LIST_SEL_S *listhead, *ls, *p;
9210 char **ret = NULL;
9211 int cnt, i, got_one;
9212 const CHARSET *cs;
9213 SCRIPT *s;
9214 char *q, *t;
9215 long width, limit;
9216 char buf[1024], *folded;
9219 * Build a list of charsets to choose from.
9222 p = listhead = NULL;
9224 /* this width is determined by select_from_list_screen() */
9225 width = ps_global->ttyo->screen_cols - 4;
9227 /* first comes a list of scripts (sets of character sets) */
9228 for(s = utf8_script(NIL); s && s->name; s++){
9230 limit = sizeof(buf)-1;
9231 q = buf;
9232 memset(q, 0, limit+1);
9234 if(s->name)
9235 sstrncpy(&q, s->name, limit);
9237 if(s->description){
9238 sstrncpy(&q, " (", limit-(q-buf));
9239 sstrncpy(&q, s->description, limit-(q-buf));
9240 sstrncpy(&q, ")", limit-(q-buf));
9243 /* add the list of charsets that are in this script */
9244 got_one = 0;
9245 for(cs = utf8_charset(NIL);
9246 cs && cs->name && (q-buf) < limit; cs++){
9247 if(cs->script & s->script){
9249 * Filter out some un-useful members of the list.
9250 * UTF-7 and UTF-8 weren't actually in the list at the
9251 * time this was written. Just making sure.
9253 if(!strucmp(cs->name, "ISO-2022-JP-2")
9254 || !strucmp(cs->name, "UTF-7")
9255 || !strucmp(cs->name, "UTF-8"))
9256 continue;
9258 if(got_one)
9259 sstrncpy(&q, " ", limit-(q-buf));
9260 else{
9261 got_one = 1;
9262 sstrncpy(&q, " {", limit-(q-buf));
9265 sstrncpy(&q, cs->name, limit-(q-buf));
9269 if(got_one)
9270 sstrncpy(&q, "}", limit-(q-buf));
9272 /* fold this line so that it can all be seen on the screen */
9273 folded = fold(buf, width, width, "", " ", FLD_NONE);
9274 if(folded){
9275 t = folded;
9276 while(t && *t && (q = strindex(t, '\n')) != NULL){
9277 *q = '\0';
9279 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9280 memset(ls, 0, sizeof(*ls));
9281 if(t == folded)
9282 ls->item = cpystr(s->name);
9283 else
9284 ls->flags = SFL_NOSELECT;
9286 ls->display_item = cpystr(t);
9288 t = q+1;
9290 if(p){
9291 p->next = ls;
9292 p = p->next;
9294 else{
9295 /* add a heading */
9296 listhead = (LIST_SEL_S *) fs_get(sizeof(*ls));
9297 memset(listhead, 0, sizeof(*listhead));
9298 listhead->flags = SFL_NOSELECT;
9299 listhead->display_item =
9300 cpystr(_("Scripts representing groups of related character sets"));
9301 listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls));
9302 memset(listhead->next, 0, sizeof(*listhead));
9303 listhead->next->flags = SFL_NOSELECT;
9304 listhead->next->display_item =
9305 cpystr(repeat_char(width, '-'));
9307 listhead->next->next = ls;
9308 p = ls;
9312 fs_give((void **) &folded);
9316 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9317 memset(ls, 0, sizeof(*ls));
9318 ls->flags = SFL_NOSELECT;
9319 if(p){
9320 p->next = ls;
9321 p = p->next;
9323 else
9324 listhead = p = ls;
9326 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9327 memset(ls, 0, sizeof(*ls));
9328 ls->flags = SFL_NOSELECT;
9329 ls->display_item =
9330 cpystr(_("Individual character sets, may be mixed with scripts"));
9331 p->next = ls;
9332 p = p->next;
9334 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9335 memset(ls, 0, sizeof(*ls));
9336 ls->flags = SFL_NOSELECT;
9337 ls->display_item =
9338 cpystr(repeat_char(width, '-'));
9339 p->next = ls;
9340 p = p->next;
9342 /* then comes a list of individual character sets */
9343 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9344 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9345 memset(ls, 0, sizeof(*ls));
9346 ls->item = cpystr(cs->name);
9348 if(p){
9349 p->next = ls;
9350 p = p->next;
9352 else
9353 listhead = p = ls;
9356 if(!listhead)
9357 return(ret);
9359 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9360 Print something1 using something2.
9361 "character sets" is something1 */
9362 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9363 _("SELECT CHARACTER SETS"), _("character sets"),
9364 h_select_multcharsets_screen,
9365 _("HELP FOR SELECTING CHARACTER SETS"), NULL)){
9366 for(cnt = 0, p = listhead; p; p = p->next)
9367 if(p->selected)
9368 cnt++;
9370 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9371 memset(ret, 0, (cnt+1) * sizeof(*ret));
9372 for(i = 0, p = listhead; p; p = p->next)
9373 if(p->selected)
9374 ret[i++] = cpystr(p->item ? p->item : "");
9377 free_list_sel(&listhead);
9379 return(ret);
9382 /* Report quota summary resources in an IMAP server */
9384 void
9385 cmd_quota (struct pine *state)
9387 QUOTALIST *imapquota;
9388 NETMBX mb;
9389 STORE_S *store;
9390 SCROLL_S sargs;
9392 if(!state->mail_stream || !is_imap_stream(state->mail_stream)){
9393 q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders");
9394 return;
9397 if (state->mail_stream
9398 && !sp_dead_stream(state->mail_stream)
9399 && state->mail_stream->mailbox
9400 && *state->mail_stream->mailbox
9401 && mail_valid_net_parse(state->mail_stream->mailbox, &mb))
9402 imap_getquotaroot(state->mail_stream, mb.mailbox);
9404 if(!state->quota) /* failed ? */
9405 return; /* go back... */
9407 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
9408 q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space.");
9409 return;
9412 so_puts(store, "Quota Report for ");
9413 so_puts(store, state->mail_stream->original_mailbox);
9414 so_puts(store, "\n\n");
9416 for (imapquota = state->quota; imapquota; imapquota = imapquota->next){
9418 so_puts(store, _("Resource : "));
9419 so_puts(store, imapquota->name);
9420 so_writec('\n', store);
9422 so_puts(store, _("Usage : "));
9423 so_puts(store, long2string(imapquota->usage));
9424 if(!strucmp(imapquota->name,"STORAGE"))
9425 so_puts(store, " KiB ");
9426 if(!strucmp(imapquota->name,"MESSAGE")){
9427 so_puts(store, _(" message"));
9428 if(imapquota->usage != 1)
9429 so_puts(store, _("s ")); /* plural */
9430 else
9431 so_puts(store, _(" "));
9433 so_writec('(', store);
9434 so_puts(store, long2string(100*imapquota->usage/imapquota->limit));
9435 so_puts(store, "%)\n");
9437 so_puts(store, _("Limit : "));
9438 so_puts(store, long2string(imapquota->limit));
9439 if(!strucmp(imapquota->name,"STORAGE"))
9440 so_puts(store, " KiB\n\n");
9441 if(!strucmp(imapquota->name,"MESSAGE")){
9442 so_puts(store, _(" message"));
9443 if(imapquota->usage != 1)
9444 so_puts(store, _("s\n\n")); /* plural */
9445 else
9446 so_puts(store, _("\n\n"));
9450 memset(&sargs, 0, sizeof(SCROLL_S));
9451 sargs.text.text = so_text(store);
9452 sargs.text.src = CharStar;
9453 sargs.text.desc = _("Quota Resources Summary");
9454 sargs.bar.title = _("QUOTA SUMMARY");
9455 sargs.proc.tool = NULL;
9456 sargs.help.text = h_quota_command;
9457 sargs.help.title = NULL;
9458 sargs.keys.menu = NULL;
9459 setbitmap(sargs.keys.bitmap);
9461 scrolltool(&sargs);
9462 so_give(&store);
9464 if (state->quota)
9465 mail_free_quotalist(&(state->quota));
9468 /*----------------------------------------------------------------------
9469 Prompt the user for the type of sort he desires
9471 Args: state -- pine state pointer
9472 q1 -- Line to prompt on
9474 Returns 0 if it was cancelled, 1 otherwise.
9475 ----*/
9477 select_sort(struct pine *state, int ql, SortOrder *sort, int *rev)
9479 char prompt[200], tmp[3], *p;
9480 int s, i;
9481 int deefault = 'a', retval = 1;
9482 HelpType help;
9483 ESCKEY_S sorts[14];
9485 #ifdef _WINDOWS
9486 DLG_SORTPARAM sortsel;
9488 if (mswin_usedialog ()) {
9490 sortsel.reverse = mn_get_revsort (state->msgmap);
9491 sortsel.cursort = mn_get_sort (state->msgmap);
9492 /* assumption here that HelpType is char ** */
9493 sortsel.helptext = h_select_sort;
9494 sortsel.rval = 0;
9496 if ((retval = os_sortdialog (&sortsel))) {
9497 *sort = sortsel.cursort;
9498 *rev = sortsel.reverse;
9501 return (retval);
9503 #endif
9505 /*----- String together the prompt ------*/
9506 tmp[1] = '\0';
9507 if(F_ON(F_USE_FK,ps_global))
9508 strncpy(prompt, _("Choose type of sort : "), sizeof(prompt));
9509 else
9510 strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "),
9511 sizeof(prompt));
9513 for(i = 0; state->sort_types[i] != EndofList; i++) {
9514 sorts[i].rval = i;
9515 p = sorts[i].label = sort_name(state->sort_types[i]);
9516 while(*(p+1) && islower((unsigned char)*p))
9517 p++;
9519 sorts[i].ch = tolower((unsigned char)(tmp[0] = *p));
9520 sorts[i].name = cpystr(tmp);
9522 if(mn_get_sort(state->msgmap) == state->sort_types[i])
9523 deefault = sorts[i].rval;
9526 sorts[i].ch = 'r';
9527 sorts[i].rval = 'r';
9528 sorts[i].name = cpystr("R");
9529 if(F_ON(F_USE_FK,ps_global))
9530 sorts[i].label = N_("Reverse");
9531 else
9532 sorts[i].label = "";
9534 sorts[++i].ch = -1;
9535 help = h_select_sort;
9537 if((F_ON(F_USE_FK,ps_global)
9538 && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x',
9539 help,RB_NORM)) != 'x'))
9541 (F_OFF(F_USE_FK,ps_global)
9542 && ((s = radio_buttons(prompt,ql,sorts,deefault,'x',
9543 help,RB_NORM)) != 'x'))){
9544 state->mangled_body = 1; /* signal screen's changed */
9545 if(s == 'r')
9546 *rev = !mn_get_revsort(state->msgmap);
9547 else
9548 *sort = state->sort_types[s];
9550 if(F_ON(F_SHOW_SORT, ps_global))
9551 ps_global->mangled_header = 1;
9553 else{
9554 retval = 0;
9555 cmd_cancelled("Sort");
9558 while(--i >= 0)
9559 fs_give((void **)&sorts[i].name);
9561 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9562 return(retval);
9566 /*---------------------------------------------------------------------
9567 Build list of folders in the given context for user selection
9569 Args: c -- pointer to pointer to folder's context context
9570 f -- folder prefix to display
9571 sublist -- whether or not to use 'f's contents as prefix
9572 lister -- function used to do the actual display
9574 Returns: malloc'd string containing sequence, else NULL if
9575 no messages in msgmap with local "selected" flag.
9576 ----*/
9578 display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int))
9580 int rc;
9581 CONTEXT_S *tc;
9582 void (*redraw)(void) = ps_global->redrawer;
9584 push_titlebar_state();
9585 tc = *c;
9586 if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0)
9587 *c = tc;
9589 ClearScreen();
9590 pop_titlebar_state();
9591 redraw_titlebar();
9592 if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
9593 (*ps_global->redrawer)();
9595 if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
9596 return(1);
9598 return(0);
9603 * Allow user to choose a single item from a list of strings.
9605 * Args list -- Array of strings to choose from, NULL terminated.
9606 * displist -- Array of strings to display instead of displaying list.
9607 * Indices correspond to the list array. Display the displist
9608 * but return the item from list if displist non-NULL.
9609 * title -- For conf_scroll_screen
9610 * pdesc -- For conf_scroll_screen
9611 * help -- For conf_scroll_screen
9612 * htitle -- For conf_scroll_screen
9614 * Returns an allocated copy of the chosen item or NULL.
9616 char *
9617 choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help,
9618 char *htitle, char *cursor_location)
9620 LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL;
9621 char **t, **dl;
9622 char *ret = NULL, *choice = NULL;
9624 /* build the LIST_SEL_S list */
9625 p = listhead = NULL;
9626 for(t = list, dl = displist; *t; t++, dl++){
9627 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9628 memset(ls, 0, sizeof(*ls));
9629 ls->item = cpystr(*t);
9630 if(displist)
9631 ls->display_item = cpystr(*dl);
9633 if(cursor_location && (cursor_location == (*t)))
9634 starting_val = ls;
9636 if(p){
9637 p->next = ls;
9638 p = p->next;
9640 else
9641 listhead = p = ls;
9644 if(!listhead)
9645 return(ret);
9647 if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc,
9648 help, htitle, starting_val))
9649 for(p = listhead; !choice && p; p = p->next)
9650 if(p->selected)
9651 choice = p->item;
9653 if(choice)
9654 ret = cpystr(choice);
9656 free_list_sel(&listhead);
9658 return(ret);
9662 void
9663 free_list_sel(LIST_SEL_S **lsel)
9665 if(lsel && *lsel){
9666 free_list_sel(&(*lsel)->next);
9667 if((*lsel)->item)
9668 fs_give((void **) &(*lsel)->item);
9670 if((*lsel)->display_item)
9671 fs_give((void **) &(*lsel)->display_item);
9673 fs_give((void **) lsel);
9679 * file_lister - call pico library's file lister
9682 file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags)
9684 PICO pbf;
9685 int rv;
9686 void (*redraw)(void) = ps_global->redrawer;
9688 standard_picobuf_setup(&pbf);
9689 push_titlebar_state();
9690 if(!newmail)
9691 pbf.newmail = NULL;
9693 /* BUG: what about help command and text? */
9694 pbf.pine_anchor = title;
9696 rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags);
9697 standard_picobuf_teardown(&pbf);
9698 fix_windsize(ps_global);
9699 init_signals(); /* has it's own signal stuff */
9701 /* Restore display's titlebar and body */
9702 pop_titlebar_state();
9703 redraw_titlebar();
9704 if((ps_global->redrawer = redraw) != NULL)
9705 (*ps_global->redrawer)();
9707 return(rv);
9711 /*----------------------------------------------------------------------
9712 Print current folder index
9714 ---*/
9716 print_index(struct pine *state, MSGNO_S *msgmap, int agg)
9718 long i;
9719 ICE_S *ice;
9720 char buf[MAX_SCREEN_COLS+1];
9722 for(i = 1L; i <= mn_get_total(msgmap); i++){
9723 if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
9724 continue;
9726 if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0))
9727 continue;
9729 ice = build_header_line(state, state->mail_stream, msgmap, i, NULL);
9731 if(ice){
9733 * I don't understand why we'd want to mark the current message
9734 * instead of printing out the first character of the status
9735 * so I'm taking it out and including the first character of the
9736 * line instead. Hubert 2006-02-09
9738 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
9739 return(0);
9742 if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i),
9743 print_char)
9744 || !gf_puts(NEWLINE, print_char))
9745 return(0);
9749 return(1);
9753 void
9754 free_mailcmd_globals(void)
9756 /* these calls are not possible during normal operations, so these
9757 * are hacks to free history memory
9759 smime_import_certificate(NULL, NULL, NULL, 0);
9760 save_prompt(NULL, NULL, NULL, 0, NULL, NULL, 0, NULL, NULL, NULL);
9761 cmd_export(NULL, NULL, 0, 0);
9762 simple_export(NULL, NULL, CharStar, NULL, NULL);
9763 broach_folder(0, 0, NULL, NULL);
9764 cmd_pipe(NULL, NULL, 0);
9765 select_by_text(NULL, NULL, 0, NULL);
9769 #ifdef _WINDOWS
9772 * windows callback to get/set header mode state
9775 header_mode_callback(set, args)
9776 int set;
9777 long args;
9779 return(ps_global->full_header);
9784 * windows callback to get/set zoom mode state
9787 zoom_mode_callback(set, args)
9788 int set;
9789 long args;
9791 return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0);
9796 * windows callback to get/set zoom mode state
9799 any_selected_callback(set, args)
9800 int set;
9801 long args;
9803 return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0);
9811 flag_callback(set, flags)
9812 int set;
9813 long flags;
9815 MESSAGECACHE *mc;
9816 int newflags = 0;
9817 long msgno;
9818 int permflag = 0;
9820 switch (set) {
9821 case 1: /* Important */
9822 permflag = ps_global->mail_stream->perm_flagged;
9823 break;
9825 case 2: /* New */
9826 permflag = ps_global->mail_stream->perm_seen;
9827 break;
9829 case 3: /* Answered */
9830 permflag = ps_global->mail_stream->perm_answered;
9831 break;
9833 case 4: /* Deleted */
9834 permflag = ps_global->mail_stream->perm_deleted;
9835 break;
9839 if(!(any_messages(ps_global->msgmap, NULL, "to Flag")
9840 && can_set_flag(ps_global, "flag", permflag)))
9841 return(0);
9843 if(sp_io_error_on_stream(ps_global->mail_stream)){
9844 sp_set_io_error_on_stream(ps_global->mail_stream, 0);
9845 pine_mail_check(ps_global->mail_stream); /* forces write */
9846 return(0);
9849 msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
9850 if(msgno > 0L && ps_global->mail_stream
9851 && msgno <= ps_global->mail_stream->nmsgs
9852 && (mc = mail_elt(ps_global->mail_stream, msgno))
9853 && mc->valid){
9855 * NOTE: code below is *VERY* sensitive to the order of
9856 * the messages defined in resource.h for flag handling.
9857 * Don't change it unless you know what you're doing.
9859 if(set){
9860 char *flagstr;
9861 long mflag;
9863 switch(set){
9864 case 1 : /* Important */
9865 flagstr = "\\FLAGGED";
9866 mflag = (mc->flagged) ? 0L : ST_SET;
9867 break;
9869 case 2 : /* New */
9870 flagstr = "\\SEEN";
9871 mflag = (mc->seen) ? 0L : ST_SET;
9872 break;
9874 case 3 : /* Answered */
9875 flagstr = "\\ANSWERED";
9876 mflag = (mc->answered) ? 0L : ST_SET;
9877 break;
9879 case 4 : /* Deleted */
9880 flagstr = "\\DELETED";
9881 mflag = (mc->deleted) ? 0L : ST_SET;
9882 break;
9884 default : /* bogus */
9885 return(0);
9888 mail_flag(ps_global->mail_stream, long2string(msgno),
9889 flagstr, mflag);
9891 if(ps_global->redrawer)
9892 (*ps_global->redrawer)();
9894 else{
9895 /* Important */
9896 if(mc->flagged)
9897 newflags |= 0x0001;
9899 /* New */
9900 if(!mc->seen)
9901 newflags |= 0x0002;
9903 /* Answered */
9904 if(mc->answered)
9905 newflags |= 0x0004;
9907 /* Deleted */
9908 if(mc->deleted)
9909 newflags |= 0x0008;
9913 return(newflags);
9919 * BUG: Should teach this about keywords
9921 MPopup *
9922 flag_submenu(mc)
9923 MESSAGECACHE *mc;
9925 static MPopup flag_submenu[] = {
9926 {tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}},
9927 {tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}},
9928 {tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}},
9929 {tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}},
9930 {tTail}
9933 /* Important */
9934 flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal;
9936 /* New */
9937 flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked;
9939 /* Answered */
9940 flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal;
9942 /* Deleted */
9943 flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal;
9945 return(flag_submenu);
9948 #endif /* _WINDOWS */