* Remove unnecesary escape character in question to user when creating
[alpine.git] / alpine / mailcmd.c
blob85fe8224f32a034ae15c60cbfb6406b8371ee7ae
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-2017 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);
325 int smime_import_certificate(char *filename, char *full_filename, char *what, size_t len)
327 int r = 1;
328 static HISTORY_S *history = NULL;
329 static ESCKEY_S eopts[] = {
330 {ctrl('T'), 10, "^T", N_("To Files")},
331 {-1, 0, NULL, NULL},
332 {-1, 0, NULL, NULL}};
334 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
335 eopts[r].ch = ctrl('I');
336 eopts[r].rval = 11;
337 eopts[r].name = "TAB";
338 eopts[r].label = N_("Complete");
341 eopts[++r].ch = -1;
343 filename[0] = '\0';
344 full_filename[0] = '\0';
346 r = get_export_filename(ps_global, filename, NULL, full_filename,
347 len, what, "IMPORT", eopts, NULL,
348 -FOOTER_ROWS(ps_global), GE_IS_IMPORT, &history);
350 return r;
354 /*----------------------------------------------------------------------
355 The giant switch on the commands for index and viewing
357 Input: command -- The command char/code
358 in_index -- flag indicating command is from index
359 orig_command -- The original command typed before pre-processing
360 Output: force_mailchk -- Set to tell caller to force call to new_mail().
362 Result: Manifold
364 Returns 1 if the message number or attachment to show changed
365 ---*/
367 process_cmd(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
368 int command, CmdWhere in_index, int *force_mailchk)
370 int question_line, a_changed, flags = 0, ret, j;
371 int notrealinbox;
372 long new_msgno, del_count, old_msgno, i;
373 long start;
374 char *newfolder, prompt[MAX_SCREEN_COLS+1];
375 CONTEXT_S *tc;
376 MESSAGECACHE *mc;
377 #if defined(DOS) && !defined(_WINDOWS)
378 extern long coreleft();
379 #endif
381 dprint((4, "\n - process_cmd(cmd=%d) -\n", command));
383 question_line = -FOOTER_ROWS(state);
384 state->mangled_screen = 0;
385 state->mangled_footer = 0;
386 state->mangled_header = 0;
387 state->next_screen = SCREEN_FUN_NULL;
388 old_msgno = mn_get_cur(msgmap);
389 a_changed = FALSE;
390 *force_mailchk = 0;
392 switch (command) {
393 /*------------- Help --------*/
394 case MC_HELP :
396 * We're not using the h_mail_view portion of this right now because
397 * that call is being handled in scrolltool() before it gets
398 * here. Leave it in case we change how it works.
400 helper((in_index == MsgIndx)
401 ? h_mail_index
402 : (in_index == View)
403 ? h_mail_view
404 : h_mail_thread_index,
405 (in_index == MsgIndx)
406 ? _("HELP FOR MESSAGE INDEX")
407 : (in_index == View)
408 ? _("HELP FOR MESSAGE TEXT")
409 : _("HELP FOR THREAD INDEX"),
410 HLPD_NONE);
411 dprint((4,"MAIL_CMD: did help command\n"));
412 state->mangled_screen = 1;
413 break;
416 /*--------- Return to main menu ------------*/
417 case MC_MAIN :
418 state->next_screen = main_menu_screen;
419 dprint((2,"MAIL_CMD: going back to main menu\n"));
420 break;
423 /*------- View message text --------*/
424 case MC_VIEW_TEXT :
425 view_text:
426 if(any_messages(msgmap, NULL, "to View")){
427 state->next_screen = mail_view_screen;
430 break;
433 /*------- View attachment --------*/
434 case MC_VIEW_ATCH :
435 state->next_screen = attachment_screen;
436 dprint((2,"MAIL_CMD: going to attachment screen\n"));
437 break;
440 /*---------- Previous message ----------*/
441 case MC_PREVITEM :
442 if(any_messages(msgmap, NULL, NULL)){
443 if((i = mn_get_cur(msgmap)) > 1L){
444 mn_dec_cur(stream, msgmap,
445 (in_index == View && THREADING()
446 && sp_viewing_a_thread(stream))
447 ? MH_THISTHD
448 : (in_index == View)
449 ? MH_ANYTHD : MH_NONE);
450 if(i == mn_get_cur(msgmap)){
451 PINETHRD_S *thrd = NULL, *topthrd = NULL;
453 if(THRD_INDX_ENABLED()){
454 mn_dec_cur(stream, msgmap, MH_ANYTHD);
455 if(i == mn_get_cur(msgmap))
456 q_status_message1(SM_ORDER, 0, 2,
457 _("Already on first %s in Zoomed Index"),
458 THRD_INDX() ? _("thread") : _("message"));
459 else{
460 if(in_index == View
461 || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
462 ret = 'y';
463 else
464 ret = want_to(_("View previous thread"), 'y', 'x',
465 NO_HELP, WT_NORM);
467 if(ret == 'y'){
468 q_status_message(SM_ORDER, 0, 2,
469 _("Viewing previous thread"));
470 new_msgno = mn_get_cur(msgmap);
471 mn_set_cur(msgmap, i);
472 if(unview_thread(state, stream, msgmap)){
473 state->next_screen = mail_index_screen;
474 state->view_skipped_index = 0;
475 state->mangled_screen = 1;
478 mn_set_cur(msgmap, new_msgno);
479 if(THRD_AUTO_VIEW() && in_index == View){
481 thrd = fetch_thread(stream,
482 mn_m2raw(msgmap,
483 new_msgno));
484 if(count_lflags_in_thread(stream, thrd,
485 msgmap,
486 MN_NONE) == 1){
487 if(view_thread(state, stream, msgmap, 1)){
488 if(current_index_state)
489 msgmap->top_after_thrd = current_index_state->msg_at_top;
491 state->view_skipped_index = 1;
492 command = MC_VIEW_TEXT;
493 goto view_text;
498 j = 0;
499 if(THRD_AUTO_VIEW() && in_index != View){
500 thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
501 if(thrd && thrd->top)
502 topthrd = fetch_thread(stream, thrd->top);
504 if(topthrd)
505 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
508 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
509 if(view_thread(state, stream, msgmap, 1)
510 && current_index_state)
511 msgmap->top_after_thrd = current_index_state->msg_at_top;
515 state->next_screen = SCREEN_FUN_NULL;
517 else
518 mn_set_cur(msgmap, i); /* put it back */
521 else
522 q_status_message1(SM_ORDER, 0, 2,
523 _("Already on first %s in Zoomed Index"),
524 THRD_INDX() ? _("thread") : _("message"));
527 else{
528 time_t now;
530 if(!IS_NEWS(stream)
531 && ((now = time(0)) > state->last_nextitem_forcechk)){
532 *force_mailchk = 1;
533 /* check at most once a second */
534 state->last_nextitem_forcechk = now;
537 q_status_message1(SM_ORDER, 0, 1, _("Already on first %s"),
538 THRD_INDX() ? _("thread") : _("message"));
542 break;
545 /*---------- Next Message ----------*/
546 case MC_NEXTITEM :
547 if(mn_get_total(msgmap) > 0L
548 && ((i = mn_get_cur(msgmap)) < mn_get_total(msgmap))){
549 mn_inc_cur(stream, msgmap,
550 (in_index == View && THREADING()
551 && sp_viewing_a_thread(stream))
552 ? MH_THISTHD
553 : (in_index == View)
554 ? MH_ANYTHD : MH_NONE);
555 if(i == mn_get_cur(msgmap)){
556 PINETHRD_S *thrd, *topthrd;
558 if(THRD_INDX_ENABLED()){
559 if(!THRD_INDX())
560 mn_inc_cur(stream, msgmap, MH_ANYTHD);
562 if(i == mn_get_cur(msgmap)){
563 if(any_lflagged(msgmap, MN_HIDE))
564 any_messages(NULL, "more", "in Zoomed Index");
565 else
566 goto nfolder;
568 else{
569 if(in_index == View
570 || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
571 ret = 'y';
572 else
573 ret = want_to(_("View next thread"), 'y', 'x',
574 NO_HELP, WT_NORM);
576 if(ret == 'y'){
577 q_status_message(SM_ORDER, 0, 2,
578 _("Viewing next thread"));
579 new_msgno = mn_get_cur(msgmap);
580 mn_set_cur(msgmap, i);
581 if(unview_thread(state, stream, msgmap)){
582 state->next_screen = mail_index_screen;
583 state->view_skipped_index = 0;
584 state->mangled_screen = 1;
587 mn_set_cur(msgmap, new_msgno);
588 if(THRD_AUTO_VIEW() && in_index == View){
590 thrd = fetch_thread(stream,
591 mn_m2raw(msgmap,
592 new_msgno));
593 if(count_lflags_in_thread(stream, thrd,
594 msgmap,
595 MN_NONE) == 1){
596 if(view_thread(state, stream, msgmap, 1)){
597 if(current_index_state)
598 msgmap->top_after_thrd = current_index_state->msg_at_top;
600 state->view_skipped_index = 1;
601 command = MC_VIEW_TEXT;
602 goto view_text;
607 j = 0;
608 if(THRD_AUTO_VIEW() && in_index != View){
609 thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
610 if(thrd && thrd->top)
611 topthrd = fetch_thread(stream, thrd->top);
613 if(topthrd)
614 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
617 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
618 if(view_thread(state, stream, msgmap, 1)
619 && current_index_state)
620 msgmap->top_after_thrd = current_index_state->msg_at_top;
624 state->next_screen = SCREEN_FUN_NULL;
626 else
627 mn_set_cur(msgmap, i); /* put it back */
630 else if(THREADING()
631 && (thrd = fetch_thread(stream, mn_m2raw(msgmap, i)))
632 && thrd->next
633 && get_lflag(stream, NULL, thrd->rawno, MN_COLL)){
634 q_status_message(SM_ORDER, 0, 2,
635 _("Expand collapsed thread to see more messages"));
637 else
638 any_messages(NULL, "more", "in Zoomed Index");
641 else{
642 time_t now;
643 nfolder:
644 prompt[0] = '\0';
645 if(IS_NEWS(stream)
646 || (state->context_current->use & CNTXT_INCMNG)){
647 char nextfolder[MAXPATH];
649 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
650 nextfolder[sizeof(nextfolder)-1] = '\0';
651 if(next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
652 state->context_current, NULL, NULL))
653 strncpy(prompt, _(". Press TAB for next folder."),
654 sizeof(prompt));
655 else
656 strncpy(prompt, _(". No more folders to TAB to."),
657 sizeof(prompt));
659 prompt[sizeof(prompt)-1] = '\0';
662 any_messages(NULL, (mn_get_total(msgmap) > 0L) ? "more" : NULL,
663 prompt[0] ? prompt : NULL);
665 if(!IS_NEWS(stream)
666 && ((now = time(0)) > state->last_nextitem_forcechk)){
667 *force_mailchk = 1;
668 /* check at most once a second */
669 state->last_nextitem_forcechk = now;
673 break;
676 /*---------- Delete message ----------*/
677 case MC_DELETE :
678 (void) cmd_delete(state, msgmap, MCMD_NONE,
679 (in_index == View) ? cmd_delete_view : cmd_delete_index);
680 break;
683 /*---------- Undelete message ----------*/
684 case MC_UNDELETE :
685 (void) cmd_undelete(state, msgmap, MCMD_NONE);
686 update_titlebar_status();
687 break;
690 /*---------- Reply to message ----------*/
691 case MC_REPLY :
692 (void) cmd_reply(state, msgmap, MCMD_NONE, NULL);
693 break;
696 /*---------- Forward message ----------*/
697 case MC_FORWARD :
698 (void) cmd_forward(state, msgmap, MCMD_NONE, NULL);
699 break;
702 /*---------- Quit pine ------------*/
703 case MC_QUIT :
704 state->next_screen = quit_screen;
705 dprint((1,"MAIL_CMD: quit\n"));
706 break;
709 /*---------- Compose message ----------*/
710 case MC_COMPOSE :
711 state->prev_screen = (in_index == View) ? mail_view_screen
712 : mail_index_screen;
713 compose_screen(state);
714 state->mangled_screen = 1;
715 if (state->next_screen)
716 a_changed = TRUE;
717 break;
720 /*---------- Alt Compose message ----------*/
721 case MC_ROLE :
722 state->prev_screen = (in_index == View) ? mail_view_screen
723 : mail_index_screen;
724 role_compose(state);
725 if(state->next_screen)
726 a_changed = TRUE;
728 break;
731 /*--------- Folders menu ------------*/
732 case MC_FOLDERS :
733 state->start_in_context = 1;
735 /*--------- Top of Folders list menu ------------*/
736 case MC_COLLECTIONS :
737 state->next_screen = folder_screen;
738 dprint((2,"MAIL_CMD: going to folder/collection menu\n"));
739 break;
742 /*---------- Open specific new folder ----------*/
743 case MC_GOTO :
744 tc = (state->context_last && !NEWS_TEST(state->context_current))
745 ? state->context_last : state->context_current;
747 newfolder = broach_folder(question_line, 1, &notrealinbox, &tc);
748 if(newfolder){
749 visit_folder(state, newfolder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT);
750 a_changed = TRUE;
753 break;
756 /*------- Go to Index Screen ----------*/
757 case MC_INDEX :
758 state->next_screen = mail_index_screen;
759 break;
761 /*------- Skip to next interesting message -----------*/
762 case MC_TAB :
763 if(THRD_INDX()){
764 PINETHRD_S *thrd;
767 * If we're in the thread index, start looking after this
768 * thread. We don't want to match something in the current
769 * thread.
771 start = mn_get_cur(msgmap);
772 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
773 if(mn_get_revsort(msgmap)){
774 /* if reversed, top of thread is last one before next thread */
775 if(thrd && thrd->top)
776 start = mn_raw2m(msgmap, thrd->top);
778 else{
779 /* last msg of thread is at the ends of the branches/nexts */
780 while(thrd){
781 start = mn_raw2m(msgmap, thrd->rawno);
782 if(thrd->branch)
783 thrd = fetch_thread(stream, thrd->branch);
784 else if(thrd->next)
785 thrd = fetch_thread(stream, thrd->next);
786 else
787 thrd = NULL;
792 * Flags is 0 in this case because we want to not skip
793 * messages inside of threads so that we can find threads
794 * which have some unseen messages even though the top-level
795 * of the thread is already seen.
796 * If new_msgno ends up being a message which is not visible
797 * because it isn't at the top-level, the current message #
798 * will be adjusted below in adjust_cur.
800 flags = 0;
801 new_msgno = next_sorted_flagged((F_UNDEL
802 | F_UNSEEN
803 | ((F_ON(F_TAB_TO_NEW,state))
804 ? 0 : F_OR_FLAG)),
805 stream, start, &flags);
807 else if(THREADING() && sp_viewing_a_thread(stream)){
808 PINETHRD_S *thrd, *topthrd = NULL;
810 start = mn_get_cur(msgmap);
813 * Things are especially complicated when we're viewing_a_thread
814 * from the thread index. First we have to check within the
815 * current thread for a new message. If none is found, then
816 * we search in the next threads and offer to continue in
817 * them. Then we offer to go to the next folder.
819 flags = NSF_SKIP_CHID;
820 new_msgno = next_sorted_flagged((F_UNDEL
821 | F_UNSEEN
822 | ((F_ON(F_TAB_TO_NEW,state))
823 ? 0 : F_OR_FLAG)),
824 stream, start, &flags);
826 * If we found a match then we are done, that is another message
827 * in the current thread index. Otherwise, we have to look
828 * further.
830 if(!(flags & NSF_FLAG_MATCH)){
831 ret = 'n';
832 while(1){
834 flags = 0;
835 new_msgno = next_sorted_flagged((F_UNDEL
836 | F_UNSEEN
837 | ((F_ON(F_TAB_TO_NEW,
838 state))
839 ? 0 : F_OR_FLAG)),
840 stream, start, &flags);
842 * If we got a match, new_msgno is a message in
843 * a different thread from the one we are viewing.
845 if(flags & NSF_FLAG_MATCH){
846 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
847 if(thrd && thrd->top)
848 topthrd = fetch_thread(stream, thrd->top);
850 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
851 static ESCKEY_S next_opt[] = {
852 {'y', 'y', "Y", N_("Yes")},
853 {'n', 'n', "N", N_("No")},
854 {TAB, 'n', "Tab", N_("NextNew")},
855 {-1, 0, NULL, NULL}
858 if(in_index)
859 snprintf(prompt, sizeof(prompt), _("View thread number %s? "),
860 topthrd ? comatose(topthrd->thrdno) : "?");
861 else
862 snprintf(prompt, sizeof(prompt),
863 _("View message in thread number %s? "),
864 topthrd ? comatose(topthrd->thrdno) : "?");
866 prompt[sizeof(prompt)-1] = '\0';
868 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
869 next_opt, 'y', 'x', NO_HELP,
870 RB_NORM);
871 if(ret == 'x'){
872 cmd_cancelled(NULL);
873 goto get_out;
876 else
877 ret = 'y';
879 if(ret == 'y'){
880 if(unview_thread(state, stream, msgmap)){
881 state->next_screen = mail_index_screen;
882 state->view_skipped_index = 0;
883 state->mangled_screen = 1;
886 mn_set_cur(msgmap, new_msgno);
887 if(THRD_AUTO_VIEW()){
889 if(count_lflags_in_thread(stream, topthrd,
890 msgmap, MN_NONE) == 1){
891 if(view_thread(state, stream, msgmap, 1)){
892 if(current_index_state)
893 msgmap->top_after_thrd = current_index_state->msg_at_top;
895 state->view_skipped_index = 1;
896 command = MC_VIEW_TEXT;
897 goto view_text;
902 if(view_thread(state, stream, msgmap, 1) && current_index_state)
903 msgmap->top_after_thrd = current_index_state->msg_at_top;
905 state->next_screen = SCREEN_FUN_NULL;
906 break;
908 else if(ret == 'n' && topthrd){
910 * skip to end of this thread and look starting
911 * in the next thread.
913 if(mn_get_revsort(msgmap)){
915 * if reversed, top of thread is last one
916 * before next thread
918 start = mn_raw2m(msgmap, topthrd->rawno);
920 else{
922 * last msg of thread is at the ends of
923 * the branches/nexts
925 thrd = topthrd;
926 while(thrd){
927 start = mn_raw2m(msgmap, thrd->rawno);
928 if(thrd->branch)
929 thrd = fetch_thread(stream, thrd->branch);
930 else if(thrd->next)
931 thrd = fetch_thread(stream, thrd->next);
932 else
933 thrd = NULL;
937 else if(ret == 'n')
938 break;
940 else
941 break;
945 else{
947 start = mn_get_cur(msgmap);
950 * If we are on a collapsed thread, start looking after the
951 * collapsed part, unless we are viewing the message.
953 if(THREADING() && in_index != View){
954 PINETHRD_S *thrd;
955 long rawno;
956 int collapsed;
958 rawno = mn_m2raw(msgmap, start);
959 thrd = fetch_thread(stream, rawno);
960 collapsed = thrd && thrd->next
961 && get_lflag(stream, NULL, rawno, MN_COLL);
963 if(collapsed){
964 if(mn_get_revsort(msgmap)){
965 if(thrd && thrd->top)
966 start = mn_raw2m(msgmap, thrd->top);
968 else{
969 while(thrd){
970 start = mn_raw2m(msgmap, thrd->rawno);
971 if(thrd->branch)
972 thrd = fetch_thread(stream, thrd->branch);
973 else if(thrd->next)
974 thrd = fetch_thread(stream, thrd->next);
975 else
976 thrd = NULL;
983 new_msgno = next_sorted_flagged((F_UNDEL
984 | F_UNSEEN
985 | ((F_ON(F_TAB_TO_NEW,state))
986 ? 0 : F_OR_FLAG)),
987 stream, start, &flags);
991 * If there weren't any unread messages left, OR there
992 * aren't any messages at all, we may want to offer to
993 * go on to the next folder...
995 if(flags & NSF_FLAG_MATCH){
996 mn_set_cur(msgmap, new_msgno);
997 if(in_index != View)
998 adjust_cur_to_visible(stream, msgmap);
1000 else{
1001 int in_inbox = sp_flagged(stream, SP_INBOX);
1003 if(state->context_current
1004 && ((NEWS_TEST(state->context_current)
1005 && context_isambig(state->cur_folder))
1006 || ((state->context_current->use & CNTXT_INCMNG)
1007 && (in_inbox
1008 || folder_index(state->cur_folder,
1009 state->context_current,
1010 FI_FOLDER) >= 0)))){
1011 char nextfolder[MAXPATH];
1012 MAILSTREAM *nextstream = NULL;
1013 long recent_cnt;
1014 int did_cancel = 0;
1016 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
1017 nextfolder[sizeof(nextfolder)-1] = '\0';
1018 while(1){
1019 if(!(next_folder(&nextstream, nextfolder, sizeof(nextfolder), nextfolder,
1020 state->context_current, &recent_cnt,
1021 F_ON(F_TAB_NO_CONFIRM,state)
1022 ? NULL : &did_cancel))){
1023 if(!in_inbox){
1024 static ESCKEY_S inbox_opt[] = {
1025 {'y', 'y', "Y", N_("Yes")},
1026 {'n', 'n', "N", N_("No")},
1027 {TAB, 'z', "Tab", N_("To Inbox")},
1028 {-1, 0, NULL, NULL}
1031 if(F_ON(F_RET_INBOX_NO_CONFIRM,state))
1032 ret = 'y';
1033 else{
1034 /* TRANSLATORS: this is a question, with some information followed
1035 by Return to INBOX? */
1036 if(state->context_current->use&CNTXT_INCMNG)
1037 snprintf(prompt, sizeof(prompt), _("No more incoming folders. Return to \"%s\"? "), state->inbox_name);
1038 else
1039 snprintf(prompt, sizeof(prompt), _("No more news groups. Return to \"%s\"? "), state->inbox_name);
1041 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1042 inbox_opt, 'y', 'x',
1043 NO_HELP, RB_NORM);
1047 * 'z' is a synonym for 'y'. It is not 'y'
1048 * so that it isn't displayed as a default
1049 * action with square-brackets around it
1050 * in the keymenu...
1052 if(ret == 'y' || ret == 'z'){
1053 visit_folder(state, state->inbox_name,
1054 state->context_current,
1055 NULL, DB_INBOXWOCNTXT);
1056 a_changed = TRUE;
1059 else if (did_cancel)
1060 cmd_cancelled(NULL);
1061 else{
1062 if(state->context_current->use&CNTXT_INCMNG)
1063 q_status_message(SM_ORDER, 0, 2, _("No more incoming folders"));
1064 else
1065 q_status_message(SM_ORDER, 0, 2, _("No more news groups"));
1068 break;
1072 #define CNTLEN 80
1073 char *front, type[80], cnt[CNTLEN], fbuf[MAX_SCREEN_COLS/2+1];
1074 int rbspace, avail, need, take_back;
1077 * View_next_
1078 * Incoming_folder_ or news_group_ or folder_ or group_
1079 * "foldername"
1080 * _(13 recent) or _(some recent) or nothing
1081 * ?_
1083 front = "View next";
1084 strncpy(type,
1085 (state->context_current->use & CNTXT_INCMNG)
1086 ? "Incoming folder" : "news group",
1087 sizeof(type));
1088 type[sizeof(type)-1] = '\0';
1089 snprintf(cnt, sizeof(cnt), " (%.*s %s)", CNTLEN-20,
1090 recent_cnt ? long2string(recent_cnt) : "some",
1091 F_ON(F_TAB_USES_UNSEEN, ps_global)
1092 ? "unseen" : "recent");
1093 cnt[sizeof(cnt)-1] = '\0';
1096 * Space reserved for radio_buttons call.
1097 * If we make this 3 then radio_buttons won't mess
1098 * with the prompt. If we make it 2, then we get
1099 * one more character to use but radio_buttons will
1100 * cut off the last character of our prompt, which is
1101 * ok because it is a space.
1103 rbspace = 2;
1104 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols
1105 : 80;
1106 need = strlen(front)+1 + strlen(type)+1 +
1107 + strlen(nextfolder)+2 + strlen(cnt) +
1108 2 + rbspace;
1109 if(avail < need){
1110 take_back = strlen(type);
1111 strncpy(type,
1112 (state->context_current->use & CNTXT_INCMNG)
1113 ? "folder" : "group", sizeof(type));
1114 take_back -= strlen(type);
1115 need -= take_back;
1116 if(avail < need){
1117 need -= strlen(cnt);
1118 cnt[0] = '\0';
1121 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1122 snprintf(prompt, sizeof(prompt), "%.*s %.*s \"%.*s\"%.*s? ",
1123 (MAX_SCREEN_COLS+1)/8, front,
1124 (MAX_SCREEN_COLS+1)/8, type,
1125 (MAX_SCREEN_COLS+1)/2,
1126 short_str(nextfolder, fbuf, sizeof(fbuf),
1127 strlen(nextfolder) -
1128 ((need>avail) ? (need-avail) : 0),
1129 MidDots),
1130 (MAX_SCREEN_COLS+1)/8, cnt);
1131 prompt[sizeof(prompt)-1] = '\0';
1135 * When help gets added, this'll have to become
1136 * a loop like the rest...
1138 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
1139 static ESCKEY_S next_opt[] = {
1140 {'y', 'y', "Y", N_("Yes")},
1141 {'n', 'n', "N", N_("No")},
1142 {TAB, 'n', "Tab", N_("NextNew")},
1143 {-1, 0, NULL, NULL}
1146 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1147 next_opt, 'y', 'x', NO_HELP,
1148 RB_NORM);
1149 if(ret == 'x'){
1150 cmd_cancelled(NULL);
1151 break;
1154 else
1155 ret = 'y';
1157 if(ret == 'y'){
1158 if(nextstream && sp_dead_stream(nextstream))
1159 nextstream = NULL;
1161 visit_folder(state, nextfolder,
1162 state->context_current, nextstream,
1163 DB_FROMTAB);
1164 /* visit_folder takes care of nextstream */
1165 nextstream = NULL;
1166 a_changed = TRUE;
1167 break;
1171 if(nextstream)
1172 pine_mail_close(nextstream);
1174 else
1175 any_messages(NULL,
1176 (mn_get_total(msgmap) > 0L)
1177 ? IS_NEWS(stream) ? "more undeleted" : "more new"
1178 : NULL,
1179 NULL);
1182 get_out:
1184 break;
1187 /*------- Zoom -----------*/
1188 case MC_ZOOM :
1190 * Right now the way zoom is implemented is sort of silly.
1191 * There are two per-message flags where just one and a
1192 * global "zoom mode" flag to suppress messags from the index
1193 * should suffice.
1195 if(any_messages(msgmap, NULL, "to Zoom on")){
1196 if(unzoom_index(state, stream, msgmap)){
1197 dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
1198 q_status_message(SM_ORDER,0,2, _("Index Zoom Mode is now off"));
1200 else if((i = zoom_index(state, stream, msgmap, MN_SLCT)) != 0){
1201 if(any_lflagged(msgmap, MN_HIDE)){
1202 dprint((4,"\n\n ---- Entering ZOOM mode ----\n"));
1203 q_status_message4(SM_ORDER, 0, 2,
1204 _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"),
1205 THRD_INDX() ? "" : comatose(i),
1206 THRD_INDX() ? "" : " ",
1207 THRD_INDX() ? _("threads") : _("message"),
1208 THRD_INDX() ? "" : plural(i));
1210 else
1211 q_status_message(SM_ORDER, 0, 2,
1212 _("All messages selected, so not entering Index Zoom Mode"));
1214 else
1215 any_messages(NULL, "selected", "to Zoom on");
1218 break;
1221 /*---------- print message on paper ----------*/
1222 case MC_PRINTMSG :
1223 if(any_messages(msgmap, NULL, "to print"))
1224 (void) cmd_print(state, msgmap, MCMD_NONE, in_index);
1226 break;
1229 /*---------- Take Address ----------*/
1230 case MC_TAKE :
1231 if(F_ON(F_ENABLE_ROLE_TAKE, state) ||
1232 any_messages(msgmap, NULL, "to Take address from"))
1233 (void) cmd_take_addr(state, msgmap, MCMD_NONE);
1235 break;
1238 /*---------- Save Message ----------*/
1239 case MC_SAVE :
1240 if(any_messages(msgmap, NULL, "to Save"))
1241 (void) cmd_save(state, stream, msgmap, MCMD_NONE, in_index);
1243 break;
1246 /*---------- Export message ----------*/
1247 case MC_EXPORT :
1248 if(any_messages(msgmap, NULL, "to Export")){
1249 (void) cmd_export(state, msgmap, question_line, MCMD_NONE);
1250 state->mangled_footer = 1;
1253 break;
1256 /*---------- Expunge ----------*/
1257 case MC_EXPUNGE :
1258 (void) cmd_expunge(state, stream, msgmap, MCMD_NONE);
1259 break;
1262 /*------- Unexclude -----------*/
1263 case MC_UNEXCLUDE :
1264 if(!(IS_NEWS(stream) && stream->rdonly)){
1265 q_status_message(SM_ORDER, 0, 3,
1266 _("Unexclude not available for mail folders"));
1268 else if(any_lflagged(msgmap, MN_EXLD)){
1269 SEARCHPGM *pgm;
1270 long i;
1271 int exbits;
1274 * Since excluded means "hidden deleted" and "killed",
1275 * the count should reflect the former.
1277 pgm = mail_newsearchpgm();
1278 pgm->deleted = 1;
1279 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
1280 for(i = 1L, del_count = 0L; i <= stream->nmsgs; i++)
1281 if((mc = mail_elt(stream, i)) && mc->searched
1282 && get_lflag(stream, NULL, i, MN_EXLD)
1283 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
1284 && (exbits & MSG_EX_FILTERED)))
1285 del_count++;
1287 if(del_count > 0L){
1288 state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1289 snprintf(prompt, sizeof(prompt), "UNexclude %ld message%s in %.*s", del_count,
1290 plural(del_count), MAX_SCREEN_COLS+1-40,
1291 pretty_fn(state->cur_folder));
1292 prompt[sizeof(prompt)-1] = '\0';
1293 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
1294 || (F_ON(F_AUTO_EXPUNGE, state)
1295 && (state->context_current
1296 && (state->context_current->use & CNTXT_INCMNG))
1297 && context_isambig(state->cur_folder))
1298 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
1299 long save_cur_rawno;
1300 int were_viewing_a_thread;
1302 save_cur_rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
1303 were_viewing_a_thread = (THREADING()
1304 && sp_viewing_a_thread(stream));
1306 if(msgno_include(stream, msgmap, MI_NONE)){
1307 clear_index_cache(stream, 0);
1309 if(stream && stream->spare)
1310 erase_threading_info(stream, msgmap);
1312 refresh_sort(stream, msgmap, SRT_NON);
1315 if(were_viewing_a_thread){
1316 if(save_cur_rawno > 0L)
1317 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1319 if(view_thread(state, stream, msgmap, 1) && current_index_state)
1320 msgmap->top_after_thrd = current_index_state->msg_at_top;
1323 if(save_cur_rawno > 0L)
1324 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1326 state->mangled_screen = 1;
1327 q_status_message2(SM_ORDER, 0, 4,
1328 "%s message%s UNexcluded",
1329 long2string(del_count),
1330 plural(del_count));
1332 if(in_index != View)
1333 adjust_cur_to_visible(stream, msgmap);
1335 else
1336 any_messages(NULL, NULL, "UNexcluded");
1338 else
1339 any_messages(NULL, "excluded", "to UNexclude");
1341 else
1342 any_messages(NULL, "excluded", "to UNexclude");
1344 break;
1347 /*------- Make Selection -----------*/
1348 case MC_SELECT :
1349 if(any_messages(msgmap, NULL, "to Select")){
1350 if(aggregate_select(state, msgmap, question_line, in_index) == 0
1351 && (in_index == MsgIndx || in_index == ThrdIndx)
1352 && F_ON(F_AUTO_ZOOM, state)
1353 && any_lflagged(msgmap, MN_SLCT) > 0L
1354 && !any_lflagged(msgmap, MN_HIDE))
1355 (void) zoom_index(state, stream, msgmap, MN_SLCT);
1358 break;
1361 /*------- Toggle Current Message Selection State -----------*/
1362 case MC_SELCUR :
1363 if(any_messages(msgmap, NULL, NULL)){
1364 if((select_by_current(state, msgmap, in_index)
1365 || (F_OFF(F_UNSELECT_WONT_ADVANCE, state)
1366 && !any_lflagged(msgmap, MN_HIDE)))
1367 && (i = mn_get_cur(msgmap)) < mn_get_total(msgmap)){
1368 /* advance current */
1369 mn_inc_cur(stream, msgmap,
1370 (in_index == View && THREADING()
1371 && sp_viewing_a_thread(stream))
1372 ? MH_THISTHD
1373 : (in_index == View)
1374 ? MH_ANYTHD : MH_NONE);
1378 break;
1381 /*------- Apply command -----------*/
1382 case MC_APPLY :
1383 if(any_messages(msgmap, NULL, NULL)){
1384 if(any_lflagged(msgmap, MN_SLCT) > 0L){
1385 if(apply_command(state, stream, msgmap, 0,
1386 AC_NONE, question_line)){
1387 if(F_ON(F_AUTO_UNSELECT, state)){
1388 agg_select_all(stream, msgmap, NULL, 0);
1389 unzoom_index(state, stream, msgmap);
1391 else if(F_ON(F_AUTO_UNZOOM, state))
1392 unzoom_index(state, stream, msgmap);
1395 else
1396 any_messages(NULL, NULL, "to Apply command to. Try \"Select\"");
1399 break;
1402 /*-------- Sort command -------*/
1403 case MC_SORT :
1405 int were_threading = THREADING();
1406 SortOrder sort = mn_get_sort(msgmap);
1407 int rev = mn_get_revsort(msgmap);
1409 dprint((1,"MAIL_CMD: sort\n"));
1410 if(select_sort(state, question_line, &sort, &rev)){
1411 /* $ command reinitializes threading collapsed/expanded info */
1412 if(SORT_IS_THREADED(msgmap) && !SEP_THRDINDX())
1413 erase_threading_info(stream, msgmap);
1415 if(ps_global && ps_global->ttyo){
1416 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
1417 ps_global->mangled_footer = 1;
1420 sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN);
1423 state->mangled_footer = 1;
1426 * We've changed whether we are threading or not so we need to
1427 * exit the index and come back in so that we switch between the
1428 * thread index and the regular index. Sort_folder will have
1429 * reset viewing_a_thread if necessary.
1431 if(SEP_THRDINDX()
1432 && ((!were_threading && THREADING())
1433 || (were_threading && !THREADING()))){
1434 state->next_screen = mail_index_screen;
1435 state->mangled_screen = 1;
1439 break;
1442 /*------- Toggle Full Headers -----------*/
1443 case MC_FULLHDR :
1444 state->full_header++;
1445 if(state->full_header == 1){
1446 if(!(state->quote_suppression_threshold
1447 && (state->some_quoting_was_suppressed || in_index != View)))
1448 state->full_header++;
1450 else if(state->full_header > 2)
1451 state->full_header = 0;
1453 switch(state->full_header){
1454 case 0:
1455 q_status_message(SM_ORDER, 0, 3,
1456 _("Display of full headers is now off."));
1457 break;
1459 case 1:
1460 q_status_message1(SM_ORDER, 0, 3,
1461 _("Quotes displayed, use %s to see full headers"),
1462 F_ON(F_USE_FK, state) ? "F9" : "H");
1463 break;
1465 case 2:
1466 q_status_message(SM_ORDER, 0, 3,
1467 _("Display of full headers is now on."));
1468 break;
1472 a_changed = TRUE;
1473 break;
1476 case MC_TOGGLE :
1477 a_changed = TRUE;
1478 break;
1481 #ifdef SMIME
1482 /*------- Try to decrypt message -----------*/
1483 case MC_DECRYPT:
1484 if(state->smime && state->smime->need_passphrase)
1485 smime_get_passphrase();
1487 a_changed = TRUE;
1488 break;
1490 case MC_SECURITY:
1491 smime_info_screen(state);
1492 break;
1493 #endif
1496 /*------- Bounce -----------*/
1497 case MC_BOUNCE :
1498 (void) cmd_bounce(state, msgmap, MCMD_NONE, NULL);
1499 break;
1502 /*------- Flag -----------*/
1503 case MC_FLAG :
1504 dprint((4, "\n - flag message -\n"));
1505 (void) cmd_flag(state, msgmap, MCMD_NONE);
1506 break;
1509 /*------- Pipe message -----------*/
1510 case MC_PIPE :
1511 (void) cmd_pipe(state, msgmap, MCMD_NONE);
1512 break;
1515 /*--------- Default, unknown command ----------*/
1516 default:
1517 alpine_panic("Unexpected command case");
1518 break;
1521 return((a_changed || mn_get_cur(msgmap) != old_msgno) ? 1 : 0);
1526 /*----------------------------------------------------------------------
1527 Map some of the special characters into sensible strings for human
1528 consumption.
1529 c is a UCS-4 character!
1530 ----*/
1531 char *
1532 pretty_command(UCS c)
1534 static char buf[10];
1535 char *s;
1537 buf[0] = '\0';
1538 s = buf;
1540 switch(c){
1541 case ' ' : s = "SPACE"; break;
1542 case '\033' : s = "ESC"; break;
1543 case '\177' : s = "DEL"; break;
1544 case ctrl('I') : s = "TAB"; break;
1545 case ctrl('J') : s = "LINEFEED"; break;
1546 case ctrl('M') : s = "RETURN"; break;
1547 case ctrl('Q') : s = "XON"; break;
1548 case ctrl('S') : s = "XOFF"; break;
1549 case KEY_UP : s = "Up Arrow"; break;
1550 case KEY_DOWN : s = "Down Arrow"; break;
1551 case KEY_RIGHT : s = "Right Arrow"; break;
1552 case KEY_LEFT : s = "Left Arrow"; break;
1553 case KEY_PGUP : s = "Prev Page"; break;
1554 case KEY_PGDN : s = "Next Page"; break;
1555 case KEY_HOME : s = "Home"; break;
1556 case KEY_END : s = "End"; break;
1557 case KEY_DEL : s = "Delete"; break; /* Not necessary DEL! */
1558 case KEY_JUNK : s = "Junk!"; break;
1559 case BADESC : s = "Bad Esc"; break;
1560 case NO_OP_IDLE : s = "NO_OP_IDLE"; break;
1561 case NO_OP_COMMAND : s = "NO_OP_COMMAND"; break;
1562 case KEY_RESIZE : s = "KEY_RESIZE"; break;
1563 case KEY_UTF8 : s = "KEY_UTF8"; break;
1564 case KEY_MOUSE : s = "KEY_MOUSE"; break;
1565 case KEY_SCRLUPL : s = "KEY_SCRLUPL"; break;
1566 case KEY_SCRLDNL : s = "KEY_SCRLDNL"; break;
1567 case KEY_SCRLTO : s = "KEY_SCRLTO"; break;
1568 case KEY_XTERM_MOUSE : s = "KEY_XTERM_MOUSE"; break;
1569 case KEY_DOUBLE_ESC : s = "KEY_DOUBLE_ESC"; break;
1570 case CTRL_KEY_UP : s = "Ctrl Up Arrow"; break;
1571 case CTRL_KEY_DOWN : s = "Ctrl Down Arrow"; break;
1572 case CTRL_KEY_RIGHT : s = "Ctrl Right Arrow"; break;
1573 case CTRL_KEY_LEFT : s = "Ctrl Left Arrow"; break;
1574 case PF1 :
1575 case PF2 :
1576 case PF3 :
1577 case PF4 :
1578 case PF5 :
1579 case PF6 :
1580 case PF7 :
1581 case PF8 :
1582 case PF9 :
1583 case PF10 :
1584 case PF11 :
1585 case PF12 :
1586 snprintf(s = buf, sizeof(buf), "F%ld", (long) (c - PF1 + 1));
1587 break;
1589 default:
1590 if(c < ' ' || (c >= 0x80 && c < 0xA0)){
1591 char d;
1592 int c1;
1594 c1 = (c >= 0x80);
1595 d = (c & 0x1f) + 'A' - 1;
1596 snprintf(s = buf, sizeof(buf), "%c%c", c1 ? '~' : '^', d);
1598 else{
1599 memset(buf, 0, sizeof(buf));
1600 utf8_put((unsigned char *) buf, (unsigned long) c);
1603 break;
1606 return(s);
1610 /*----------------------------------------------------------------------
1611 Complain about bogus input
1613 Args: ch -- input command to complain about
1614 help -- string indicating where to get help
1616 ----*/
1617 void
1618 bogus_command(UCS cmd, char *help)
1620 if(cmd == ctrl('Q') || cmd == ctrl('S'))
1621 q_status_message1(SM_ASYNC, 0, 2,
1622 "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.",
1623 pretty_command(cmd));
1624 else if(cmd == KEY_JUNK)
1625 q_status_message3(SM_ORDER, 0, 2,
1626 "Invalid key pressed.%s%s%s",
1627 (help) ? " Use " : "",
1628 (help) ? help : "",
1629 (help) ? " for help" : "");
1630 else
1631 q_status_message4(SM_ORDER, 0, 2,
1632 "Command \"%s\" not defined for this screen.%s%s%s",
1633 pretty_command(cmd),
1634 (help) ? " Use " : "",
1635 (help) ? help : "",
1636 (help) ? " for help" : "");
1640 void
1641 bogus_utf8_command(char *cmd, char *help)
1643 q_status_message4(SM_ORDER, 0, 2,
1644 "Command \"%s\" not defined for this screen.%s%s%s",
1645 cmd ? cmd : "?",
1646 (help) ? " Use " : "",
1647 (help) ? help : "",
1648 (help) ? " for help" : "");
1652 /*----------------------------------------------------------------------
1653 Execute FLAG message command
1655 Args: state -- Various satate info
1656 msgmap -- map of c-client to local message numbers
1658 Result: with side effect of "current" message FLAG flag set or UNset
1660 ----*/
1662 cmd_flag(struct pine *state, MSGNO_S *msgmap, int aopt)
1664 char *flagit, *seq, *screen_text[20], **exp, **p, *answer = NULL;
1665 char *keyword_array[2];
1666 int user_defined_flags = 0, mailbox_flags = 0;
1667 int directly_to_maint_screen = 0;
1668 long unflagged, flagged, flags, rawno;
1669 MESSAGECACHE *mc = NULL;
1670 KEYWORD_S *kw;
1671 int i, cnt, is_set, trouble = 0, rv = 0;
1672 size_t len;
1673 struct flag_table *fp, *ftbl = NULL;
1674 struct flag_screen flag_screen;
1675 static char *flag_screen_text1[] = {
1676 N_(" Set desired flags for current message below. An 'X' means set"),
1677 N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."),
1678 NULL
1681 static char *flag_screen_text2[] = {
1682 N_(" Set desired flags below for selected messages. A '?' means to"),
1683 N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"),
1684 N_(" to unset it. Use the \"Return\" key to toggle, and choose"),
1685 N_(" \"Exit\" when finished."),
1686 NULL
1689 static struct flag_table default_ftbl[] = {
1690 {N_("Important"), h_flag_important, F_FLAG, 0, 0, NULL, NULL},
1691 {N_("New"), h_flag_new, F_SEEN, 0, 0, NULL, NULL},
1692 {N_("Answered"), h_flag_answered, F_ANS, 0, 0, NULL, NULL},
1693 {N_("Forwarded"), h_flag_forwarded, F_FWD, 0, 0, NULL, NULL},
1694 {N_("Deleted"), h_flag_deleted, F_DEL, 0, 0, NULL, NULL},
1695 {NULL, NO_HELP, 0, 0, 0, NULL, NULL}
1698 /* Only check for dead stream for now. Should check permanent flags
1699 * eventually
1701 if(!(any_messages(msgmap, NULL, "to Flag") && can_set_flag(state, "flag", 1)))
1702 return rv;
1704 if(sp_io_error_on_stream(state->mail_stream)){
1705 sp_set_io_error_on_stream(state->mail_stream, 0);
1706 pine_mail_check(state->mail_stream); /* forces write */
1707 return rv;
1710 go_again:
1711 answer = NULL;
1712 user_defined_flags = 0;
1713 mailbox_flags = 0;
1714 mc = NULL;
1715 trouble = 0;
1716 ftbl = NULL;
1718 /* count how large ftbl will be */
1719 for(cnt = 0; default_ftbl[cnt].name; cnt++)
1722 /* add user flags */
1723 for(kw = ps_global->keywords; kw; kw = kw->next){
1724 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick)) || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1725 user_defined_flags++;
1726 cnt++;
1731 * Add mailbox flags that aren't user-defined flags.
1732 * Don't consider it if it matches either one of our defined
1733 * keywords or one of our defined nicknames for a keyword.
1735 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1736 char *q;
1738 q = stream_to_user_flag_name(state->mail_stream, i);
1739 if(q && q[0]){
1740 for(kw = ps_global->keywords; kw; kw = kw->next){
1741 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1742 break;
1746 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1747 mailbox_flags++;
1748 cnt++;
1752 cnt += (user_defined_flags ? 2 : 0) + (mailbox_flags ? 2 : 0);
1754 /* set up ftbl, first the system flags */
1755 ftbl = (struct flag_table *) fs_get((cnt+1) * sizeof(*ftbl));
1756 memset(ftbl, 0, (cnt+1) * sizeof(*ftbl));
1757 for(i = 0, fp = ftbl; default_ftbl[i].name; i++, fp++){
1758 fp->name = cpystr(default_ftbl[i].name);
1759 fp->help = default_ftbl[i].help;
1760 fp->flag = default_ftbl[i].flag;
1761 fp->set = default_ftbl[i].set;
1762 fp->ukn = default_ftbl[i].ukn;
1765 if(user_defined_flags){
1766 fp->flag = F_COMMENT;
1767 fp->name = cpystr("");
1768 fp++;
1769 fp->flag = F_COMMENT;
1770 len = strlen(_("User-defined Keywords from Setup/Config"));
1771 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1772 snprintf(fp->name, len+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config"));
1773 fp++;
1776 /* then the user-defined keywords */
1777 if(user_defined_flags)
1778 for(kw = ps_global->keywords; kw; kw = kw->next){
1779 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick))
1780 || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1781 fp->name = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
1782 fp->keyword = cpystr(kw->kw ? kw->kw : "");
1783 if(kw->nick && kw->kw){
1784 size_t l;
1786 l = strlen(kw->kw)+2;
1787 fp->comment = (char *) fs_get((l+1) * sizeof(char));
1788 snprintf(fp->comment, l+1, "(%.*s)", (int) strlen(kw->kw), kw->kw);
1789 fp->comment[l] = '\0';
1792 fp->help = h_flag_user_flag;
1793 fp->flag = F_KEYWORD;
1794 fp->set = 0;
1795 fp->ukn = 0;
1796 fp++;
1800 if(mailbox_flags){
1801 fp->flag = F_COMMENT;
1802 fp->name = cpystr("");
1803 fp++;
1804 fp->flag = F_COMMENT;
1805 len = strlen(_("Other keywords in the mailbox that are not user-defined"));
1806 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1807 snprintf(fp->name, len+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined"));
1808 fp++;
1811 /* then the extra mailbox-defined keywords */
1812 if(mailbox_flags)
1813 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1814 char *q;
1816 q = stream_to_user_flag_name(state->mail_stream, i);
1817 if(q && q[0]){
1818 for(kw = ps_global->keywords; kw; kw = kw->next){
1819 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1820 break;
1824 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1825 fp->name = cpystr(q);
1826 fp->keyword = cpystr(q);
1827 fp->help = h_flag_user_flag;
1828 fp->flag = F_KEYWORD;
1829 fp->set = 0;
1830 fp->ukn = 0;
1831 fp++;
1835 flag_screen.flag_table = &ftbl;
1836 flag_screen.explanation = screen_text;
1838 if(MCMD_ISAGG(aopt)){
1839 if(!pseudo_selected(ps_global->mail_stream, msgmap)){
1840 free_flag_table(&ftbl);
1841 return rv;
1844 exp = flag_screen_text2;
1845 for(fp = ftbl; fp->name; fp++){
1846 fp->set = CMD_FLAG_UNKN; /* set to unknown */
1847 fp->ukn = TRUE;
1850 else if(state->mail_stream
1851 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
1852 && rawno <= state->mail_stream->nmsgs
1853 && (mc = mail_elt(state->mail_stream, rawno))){
1854 exp = flag_screen_text1;
1855 for(fp = &ftbl[0]; fp->name; fp++){
1856 fp->ukn = 0;
1857 if(fp->flag == F_KEYWORD){
1858 /* see if this keyword is defined for this message */
1859 fp->set = CMD_FLAG_CLEAR;
1860 if(user_flag_is_set(state->mail_stream,
1861 rawno, fp->keyword))
1862 fp->set = CMD_FLAG_SET;
1864 else if(fp->flag == F_FWD){
1865 /* see if forwarded keyword is defined for this message */
1866 fp->set = CMD_FLAG_CLEAR;
1867 if(user_flag_is_set(state->mail_stream,
1868 rawno, FORWARDED_FLAG))
1869 fp->set = CMD_FLAG_SET;
1871 else if(fp->flag != F_COMMENT)
1872 fp->set = ((fp->flag == F_SEEN && !mc->seen)
1873 || (fp->flag == F_DEL && mc->deleted)
1874 || (fp->flag == F_FLAG && mc->flagged)
1875 || (fp->flag == F_ANS && mc->answered))
1876 ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
1879 else{
1880 q_status_message(SM_ORDER | SM_DING, 3, 4,
1881 _("Error accessing message data"));
1882 free_flag_table(&ftbl);
1883 return rv;
1886 if(directly_to_maint_screen)
1887 goto the_maint_screen;
1889 #ifdef _WINDOWS
1890 if (mswin_usedialog ()) {
1891 if (!os_flagmsgdialog (&ftbl[0])){
1892 free_flag_table(&ftbl);
1893 return rv;
1896 else
1897 #endif
1899 int use_maint_screen;
1900 int keyword_shortcut = 0;
1902 use_maint_screen = F_ON(F_FLAG_SCREEN_DFLT, ps_global);
1904 if(!use_maint_screen){
1906 * We're going to call cmd_flag_prompt(). We need
1907 * to decide whether or not to offer the keyword setting
1908 * shortcut. We'll offer it if the user has the feature
1909 * enabled AND there are some possible keywords that could
1910 * be set.
1912 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global)){
1913 for(fp = &ftbl[0]; fp->name && !keyword_shortcut; fp++){
1914 if(fp->flag == F_KEYWORD){
1915 int first_char;
1916 ESCKEY_S *tp;
1918 first_char = (fp->name && fp->name[0])
1919 ? fp->name[0] : -2;
1920 if(isascii(first_char) && isupper(first_char))
1921 first_char = tolower((unsigned char) first_char);
1923 for(tp=flag_text_opt; tp->ch != -1; tp++)
1924 if(tp->ch == first_char)
1925 break;
1927 if(tp->ch == -1)
1928 keyword_shortcut++;
1933 use_maint_screen = !cmd_flag_prompt(state, &flag_screen,
1934 keyword_shortcut);
1937 the_maint_screen:
1938 if(use_maint_screen){
1939 for(p = &screen_text[0]; *exp; p++, exp++)
1940 *p = *exp;
1942 *p = NULL;
1944 directly_to_maint_screen = flag_maintenance_screen(state, &flag_screen);
1948 /* reaquire the elt pointer */
1949 mc = (state->mail_stream
1950 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
1951 && rawno <= state->mail_stream->nmsgs)
1952 ? mail_elt(state->mail_stream, rawno) : NULL;
1954 for(fp = ftbl; mc && fp->name; fp++){
1955 flags = -1;
1956 switch(fp->flag){
1957 case F_SEEN:
1958 if((!MCMD_ISAGG(aopt) && fp->set != !mc->seen)
1959 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
1960 flagit = "\\SEEN";
1961 if(fp->set){
1962 flags = 0L;
1963 unflagged = F_SEEN;
1965 else{
1966 flags = ST_SET;
1967 unflagged = F_UNSEEN;
1971 break;
1973 case F_ANS:
1974 if((!MCMD_ISAGG(aopt) && fp->set != mc->answered)
1975 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
1976 flagit = "\\ANSWERED";
1977 if(fp->set){
1978 flags = ST_SET;
1979 unflagged = F_UNANS;
1981 else{
1982 flags = 0L;
1983 unflagged = F_ANS;
1987 break;
1989 case F_DEL:
1990 if((!MCMD_ISAGG(aopt) && fp->set != mc->deleted)
1991 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
1992 flagit = "\\DELETED";
1993 if(fp->set){
1994 flags = ST_SET;
1995 unflagged = F_UNDEL;
1997 else{
1998 flags = 0L;
1999 unflagged = F_DEL;
2003 break;
2005 case F_FLAG:
2006 if((!MCMD_ISAGG(aopt) && fp->set != mc->flagged)
2007 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2008 flagit = "\\FLAGGED";
2009 if(fp->set){
2010 flags = ST_SET;
2011 unflagged = F_UNFLAG;
2013 else{
2014 flags = 0L;
2015 unflagged = F_FLAG;
2019 break;
2021 case F_FWD :
2022 if(!MCMD_ISAGG(aopt)){
2023 /* see if forwarded is defined for this message */
2024 is_set = CMD_FLAG_CLEAR;
2025 if(user_flag_is_set(state->mail_stream,
2026 mn_m2raw(msgmap, mn_get_cur(msgmap)),
2027 FORWARDED_FLAG))
2028 is_set = CMD_FLAG_SET;
2031 if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2032 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2033 flagit = FORWARDED_FLAG;
2034 if(fp->set){
2035 flags = ST_SET;
2036 unflagged = F_UNFWD;
2038 else{
2039 flags = 0L;
2040 unflagged = F_FWD;
2044 break;
2046 case F_KEYWORD:
2047 if(!MCMD_ISAGG(aopt)){
2048 /* see if this keyword is defined for this message */
2049 is_set = CMD_FLAG_CLEAR;
2050 if(user_flag_is_set(state->mail_stream,
2051 mn_m2raw(msgmap, mn_get_cur(msgmap)),
2052 fp->keyword))
2053 is_set = CMD_FLAG_SET;
2056 if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2057 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2058 flagit = fp->keyword;
2059 keyword_array[0] = fp->keyword;
2060 keyword_array[1] = NULL;
2061 if(fp->set){
2062 flags = ST_SET;
2063 unflagged = F_UNKEYWORD;
2065 else{
2066 flags = 0L;
2067 unflagged = F_KEYWORD;
2071 break;
2073 default:
2074 break;
2077 flagged = 0L;
2078 if(flags >= 0L
2079 && (seq = currentf_sequence(state->mail_stream, msgmap,
2080 unflagged, &flagged, unflagged & F_DEL,
2081 (fp->flag == F_KEYWORD
2082 && unflagged == F_KEYWORD)
2083 ? keyword_array : NULL,
2084 (fp->flag == F_KEYWORD
2085 && unflagged == F_UNKEYWORD)
2086 ? keyword_array : NULL))){
2088 * For user keywords, we may have to create the flag in
2089 * the folder if it doesn't already exist and we are setting
2090 * it (as opposed to clearing it). Mail_flag will
2091 * do that for us, but it's failure isn't very friendly
2092 * error-wise. So we try to make it a little smoother.
2094 if(!(fp->flag == F_KEYWORD || fp->flag == F_FWD) || !fp->set
2095 || ((i=user_flag_index(state->mail_stream, flagit)) >= 0
2096 && i < NUSERFLAGS))
2097 mail_flag(state->mail_stream, seq, flagit, flags);
2098 else{
2099 /* trouble, see if we can add the user flag */
2100 if(state->mail_stream->kwd_create)
2101 mail_flag(state->mail_stream, seq, flagit, flags);
2102 else{
2103 trouble++;
2105 if(some_user_flags_defined(state->mail_stream))
2106 q_status_message(SM_ORDER, 3, 4,
2107 _("No more keywords allowed in this folder!"));
2108 else if(fp->flag == F_FWD)
2109 q_status_message(SM_ORDER, 3, 4,
2110 _("Cannot add keywords for this folder so cannot set Forwarded flag"));
2111 else
2112 q_status_message(SM_ORDER, 3, 4,
2113 _("Cannot add keywords for this folder"));
2117 fs_give((void **) &seq);
2118 if(flagged && !trouble){
2119 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%slagged%s%s%s%s%s message%s%s \"%s\"",
2120 (fp->set) ? "F" : "Unf",
2121 MCMD_ISAGG(aopt) ? " " : "",
2122 MCMD_ISAGG(aopt) ? long2string(flagged) : "",
2123 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2124 ? " (of " : "",
2125 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2126 ? comatose(mn_total_cur(msgmap)) : "",
2127 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2128 ? ")" : "",
2129 MCMD_ISAGG(aopt) ? plural(flagged) : " ",
2130 MCMD_ISAGG(aopt) ? "" : long2string(mn_get_cur(msgmap)),
2131 fp->name);
2132 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2133 q_status_message(SM_ORDER, 0, 2, answer = tmp_20k_buf);
2134 rv++;
2139 free_flag_table(&ftbl);
2141 if(directly_to_maint_screen)
2142 goto go_again;
2144 if(MCMD_ISAGG(aopt))
2145 restore_selected(msgmap);
2147 if(!answer)
2148 q_status_message(SM_ORDER, 0, 2, _("No flags changed."));
2150 return rv;
2154 /*----------------------------------------------------------------------
2155 Offer concise status line flag prompt
2157 Args: state -- Various satate info
2158 flags -- flags to offer setting
2160 Result: TRUE if flag to set specified in flags struct or FALSE otw
2162 ----*/
2164 cmd_flag_prompt(struct pine *state, struct flag_screen *flags, int allow_keyword_shortcuts)
2166 int r, setflag = 1, first_char;
2167 struct flag_table *fp;
2168 ESCKEY_S *ek;
2169 char *ftext, *ftext_not;
2170 static char *flag_text =
2171 N_("Flag New, Deleted, Answered, Forwarded or Important ? ");
2172 static char *flag_text_ak =
2173 N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? ");
2174 static char *flag_text_not =
2175 N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? ");
2176 static char *flag_text_ak_not =
2177 N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? ");
2179 if(allow_keyword_shortcuts){
2180 int cnt = 0;
2181 ESCKEY_S *dp, *sp, *tp;
2183 for(sp=flag_text_opt; sp->ch != -1; sp++)
2184 cnt++;
2186 for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++)
2187 if(fp->flag == F_KEYWORD)
2188 cnt++;
2190 /* set up an ESCKEY_S list which includes invisible keys for keywords */
2191 ek = (ESCKEY_S *) fs_get((cnt + 1) * sizeof(*ek));
2192 memset(ek, 0, (cnt+1) * sizeof(*ek));
2193 for(dp=ek, sp=flag_text_opt; sp->ch != -1; sp++, dp++)
2194 *dp = *sp;
2196 for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2197 if(fp->flag == F_KEYWORD){
2198 first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2199 if(isascii(first_char) && isupper(first_char))
2200 first_char = tolower((unsigned char) first_char);
2203 * Check to see if an earlier keyword in the list, or one of
2204 * the builtin system letters already uses this character.
2205 * If so, the first one wins.
2207 for(tp=ek; tp->ch != 0; tp++)
2208 if(tp->ch == first_char)
2209 break;
2211 if(tp->ch != 0)
2212 continue; /* skip it, already used that char */
2214 dp->ch = first_char;
2215 dp->rval = first_char;
2216 dp->name = "";
2217 dp->label = "";
2218 dp++;
2222 dp->ch = -1;
2223 ftext = _(flag_text_ak);
2224 ftext_not = _(flag_text_ak_not);
2226 else{
2227 ek = flag_text_opt;
2228 ftext = _(flag_text);
2229 ftext_not = _(flag_text_not);
2232 while(1){
2233 r = radio_buttons(setflag ? ftext : ftext_not,
2234 -FOOTER_ROWS(state), ek, '*', SEQ_EXCEPTION-1,
2235 NO_HELP, RB_NORM | RB_SEQ_SENSITIVE);
2237 * It is SEQ_EXCEPTION-1 just so that it is some value that isn't
2238 * being used otherwise. The keywords use up all the possible
2239 * letters, so a negative number is good, but it has to be different
2240 * from other negative return values.
2242 if(r == SEQ_EXCEPTION-1) /* ol'cancelrooney */
2243 return(TRUE);
2244 else if(r == 10) /* return and goto flag screen */
2245 return(FALSE);
2246 else if(r == '!') /* flip intention */
2247 setflag = !setflag;
2248 else
2249 break;
2252 for(fp = (flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2253 if(r == 'n' || r == '*' || r == 'd' || r == 'a' || r == 'f'){
2254 if((r == 'n' && fp->flag == F_SEEN)
2255 || (r == '*' && fp->flag == F_FLAG)
2256 || (r == 'd' && fp->flag == F_DEL)
2257 || (r == 'f' && fp->flag == F_FWD)
2258 || (r == 'a' && fp->flag == F_ANS)){
2259 fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2260 break;
2263 else if(allow_keyword_shortcuts && fp->flag == F_KEYWORD){
2264 first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2265 if(isascii(first_char) && isupper(first_char))
2266 first_char = tolower((unsigned char) first_char);
2268 if(r == first_char){
2269 fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2270 break;
2275 if(ek != flag_text_opt)
2276 fs_give((void **) &ek);
2278 return(TRUE);
2283 * (*ft) is an array of flag_table entries.
2285 void
2286 free_flag_table(struct flag_table **ft)
2288 struct flag_table *fp;
2290 if(ft && *ft){
2291 for(fp = (*ft); fp->name; fp++){
2292 if(fp->name)
2293 fs_give((void **) &fp->name);
2295 if(fp->keyword)
2296 fs_give((void **) &fp->keyword);
2298 if(fp->comment)
2299 fs_give((void **) &fp->comment);
2302 fs_give((void **) ft);
2307 /*----------------------------------------------------------------------
2308 Execute REPLY message command
2310 Args: state -- Various satate info
2311 msgmap -- map of c-client to local message numbers
2313 Result: reply sent or not
2315 ----*/
2317 cmd_reply(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2319 int rv = 0;
2321 if(any_messages(msgmap, NULL, "to Reply to")){
2322 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2323 return rv;
2325 rv = reply(state, role);
2327 if(MCMD_ISAGG(aopt))
2328 restore_selected(msgmap);
2330 state->mangled_screen = 1;
2333 return rv;
2337 /*----------------------------------------------------------------------
2338 Execute FORWARD message command
2340 Args: state -- Various satate info
2341 msgmap -- map of c-client to local message numbers
2343 Result: selected message[s] forwarded or not
2345 ----*/
2347 cmd_forward(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2349 int rv = 0;
2351 if(any_messages(msgmap, NULL, "to Forward")){
2352 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2353 return rv;
2355 rv = forward(state, role);
2357 if(MCMD_ISAGG(aopt))
2358 restore_selected(msgmap);
2360 state->mangled_screen = 1;
2363 return rv;
2367 /*----------------------------------------------------------------------
2368 Execute BOUNCE message command
2370 Args: state -- Various satate info
2371 msgmap -- map of c-client to local message numbers
2372 aopt -- aggregate options
2374 Result: selected message[s] bounced or not
2376 ----*/
2378 cmd_bounce(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2380 int rv = 0;
2382 if(any_messages(msgmap, NULL, "to Bounce")){
2383 long i;
2384 if(MCMD_ISAGG(aopt)){
2385 if(!pseudo_selected(state->mail_stream, msgmap))
2386 return rv;
2388 else if((i = any_lflagged(msgmap, MN_SLCT)) > 0
2389 && get_lflag(state->mail_stream, msgmap,
2390 mn_m2raw(msgmap, mn_get_cur(msgmap)), MN_SLCT) == 0)
2391 q_status_message(SM_ORDER | SM_DING, 3, 4,
2392 _("WARNING: non-selected message is being bounced!"));
2393 else if (i > 1L
2394 && get_lflag(state->mail_stream, msgmap,
2395 mn_m2raw(msgmap, mn_get_cur(msgmap)), MN_SLCT))
2396 q_status_message(SM_ORDER | SM_DING, 3, 4,
2397 _("WARNING: not bouncing all selected messages!"));
2399 rv = bounce(state, role);
2401 if(MCMD_ISAGG(aopt))
2402 restore_selected(msgmap);
2404 state->mangled_footer = 1;
2407 return rv;
2411 /*----------------------------------------------------------------------
2412 Execute save message command: prompt for folder and call function to save
2414 Args: screen_line -- Line on the screen to prompt on
2415 message -- The MESSAGECACHE entry of message to save
2417 Result: The folder lister can be called to make selection; mangled screen set
2419 This does the prompting for the folder name to save to, possibly calling
2420 up the folder display for selection of folder by user.
2421 ----*/
2423 cmd_save(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
2425 char newfolder[MAILTMPLEN], nmsgs[32], *nick;
2426 int we_cancel = 0, rv = 0, save_flags;
2427 long i, raw;
2428 CONTEXT_S *cntxt = NULL;
2429 ENVELOPE *e = NULL;
2430 SaveDel del = DontAsk;
2431 SavePreserveOrder pre = DontAskPreserve;
2433 dprint((4, "\n - saving message -\n"));
2435 if(MCMD_ISAGG(aopt) && !pseudo_selected(stream, msgmap))
2436 return rv;
2438 state->ugly_consider_advancing_bit = 0;
2439 if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM, state)
2440 && msgno_any_deletedparts(stream, msgmap)
2441 && want_to(_("Saved copy will NOT include entire message! Continue"),
2442 'y', 'n', NO_HELP, WT_FLUSH_IN | WT_SEQ_SENSITIVE) != 'y'){
2443 restore_selected(msgmap);
2444 cmd_cancelled("Save message");
2445 return rv;
2448 raw = mn_m2raw(msgmap, mn_get_cur(msgmap));
2450 if(mn_total_cur(msgmap) <= 1L){
2451 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld ", mn_get_cur(msgmap));
2452 nmsgs[sizeof(nmsgs)-1] = '\0';
2453 e = pine_mail_fetchstructure(stream, raw, NULL);
2454 if(!e) {
2455 q_status_message(SM_ORDER | SM_DING, 3, 4,
2456 _("Can't save message. Error accessing folder"));
2457 restore_selected(msgmap);
2458 return rv;
2461 else{
2462 snprintf(nmsgs, sizeof(nmsgs), "%s msgs ", comatose(mn_total_cur(msgmap)));
2463 nmsgs[sizeof(nmsgs)-1] = '\0';
2465 /* e is just used to get a default save folder from the first msg */
2466 e = pine_mail_fetchstructure(stream,
2467 mn_m2raw(msgmap, mn_first_cur(msgmap)),
2468 NULL);
2471 del = (!READONLY_FOLDER(stream) && F_OFF(F_SAVE_WONT_DELETE, ps_global))
2472 ? Del : NoDel;
2473 if(mn_total_cur(msgmap) > 1L)
2474 pre = F_OFF(F_AGG_SEQ_COPY, ps_global) ? Preserve : NoPreserve;
2475 else
2476 pre = DontAskPreserve;
2478 if(save_prompt(state, &cntxt, newfolder, sizeof(newfolder), nmsgs, e,
2479 raw, NULL, &del, &pre)){
2481 if(ps_global && ps_global->ttyo){
2482 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2483 ps_global->mangled_footer = 1;
2486 save_flags = SV_FIX_DELS;
2487 if(pre == RetPreserve)
2488 save_flags |= SV_PRESERVE;
2489 if(del == RetDel)
2490 save_flags |= SV_DELETE;
2491 if(ps_global->context_list == cntxt && !strucmp(newfolder, ps_global->inbox_name))
2492 save_flags |= SV_INBOXWOCNTXT;
2494 we_cancel = busy_cue(_("Saving"), NULL, 1);
2495 i = save(state, stream, cntxt, newfolder, msgmap, save_flags);
2496 if(we_cancel)
2497 cancel_busy_cue(0);
2499 if(i == mn_total_cur(msgmap)){
2500 rv++;
2501 if(mn_total_cur(msgmap) <= 1L){
2502 int need, avail = ps_global->ttyo->screen_cols - 2;
2503 int lennick, lenfldr;
2505 if(cntxt
2506 && ps_global->context_list->next
2507 && context_isambig(newfolder)){
2508 lennick = MIN(strlen(cntxt->nickname), 500);
2509 lenfldr = MIN(strlen(newfolder), 500);
2510 need = 27 + strlen(long2string(mn_get_cur(msgmap))) +
2511 lenfldr + lennick;
2512 if(need > avail){
2513 if(lennick > 10){
2514 need -= MIN(lennick-10, need-avail);
2515 lennick -= MIN(lennick-10, need-avail);
2518 if(need > avail && lenfldr > 10)
2519 lenfldr -= MIN(lenfldr-10, need-avail);
2522 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2523 "Message %s copied to \"%s\" in <%s>",
2524 long2string(mn_get_cur(msgmap)),
2525 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2526 lenfldr, MidDots),
2527 short_str(cntxt->nickname,
2528 (char *)(tmp_20k_buf+2000), 1000,
2529 lennick, EndDots));
2530 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2532 else if((nick=folder_is_target_of_nick(newfolder, cntxt)) != NULL){
2533 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2534 "Message %s copied to \"%s\"",
2535 long2string(mn_get_cur(msgmap)),
2536 nick);
2537 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2539 else{
2540 char *f = " folder";
2542 lenfldr = MIN(strlen(newfolder), 500);
2543 need = 28 + strlen(long2string(mn_get_cur(msgmap))) +
2544 lenfldr;
2545 if(need > avail){
2546 need -= strlen(f);
2547 f = "";
2548 if(need > avail && lenfldr > 10)
2549 lenfldr -= MIN(lenfldr-10, need-avail);
2552 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
2553 "Message %s copied to%s \"%s\"",
2554 long2string(mn_get_cur(msgmap)), f,
2555 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2556 lenfldr, MidDots));
2557 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2560 else{
2561 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s messages saved",
2562 comatose(mn_total_cur(msgmap)));
2563 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2566 if(del == RetDel){
2567 strncat(tmp_20k_buf, " and deleted", SIZEOF_20KBUF-strlen(tmp_20k_buf)-1);
2568 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2571 q_status_message(SM_ORDER, 0, 3, tmp_20k_buf);
2573 if(!MCMD_ISAGG(aopt) && F_ON(F_SAVE_ADVANCES, state)){
2574 if(sp_new_mail_count(stream))
2575 process_filter_patterns(stream, msgmap,
2576 sp_new_mail_count(stream));
2578 mn_inc_cur(stream, msgmap,
2579 (in_index == View && THREADING()
2580 && sp_viewing_a_thread(stream))
2581 ? MH_THISTHD
2582 : (in_index == View)
2583 ? MH_ANYTHD : MH_NONE);
2586 state->ugly_consider_advancing_bit = 1;
2590 if(MCMD_ISAGG(aopt)) /* straighten out fakes */
2591 restore_selected(msgmap);
2593 if(del == RetDel)
2594 update_titlebar_status(); /* make sure they see change */
2596 return rv;
2600 void
2601 role_compose(struct pine *state)
2603 int action;
2605 if(F_ON(F_ALT_ROLE_MENU, state) && mn_get_total(state->msgmap) > 0L){
2606 PAT_STATE pstate;
2608 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
2609 action = radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
2610 -FOOTER_ROWS(state), choose_action,
2611 'c', 'x', h_role_compose, RB_NORM);
2613 else{
2614 q_status_message(SM_ORDER, 0, 3,
2615 _("No roles available. Use Setup/Rules to add roles."));
2616 return;
2619 else
2620 action = 'c';
2622 if(action == 'c' || action == 'r' || action == 'f' || action == 'b'){
2623 ACTION_S *role = NULL;
2624 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
2626 redraw = state->redrawer;
2627 state->redrawer = NULL;
2628 prev_screen = state->prev_screen;
2629 role = NULL;
2630 state->next_screen = SCREEN_FUN_NULL;
2632 /* Setup role */
2633 if(role_select_screen(state, &role,
2634 action == 'f' ? MC_FORWARD :
2635 action == 'r' ? MC_REPLY :
2636 action == 'b' ? MC_BOUNCE :
2637 action == 'c' ? MC_COMPOSE : 0) < 0){
2638 cmd_cancelled(action == 'f' ? _("Forward") :
2639 action == 'r' ? _("Reply") :
2640 action == 'c' ? _("Composition") : _("Bounce"));
2641 state->next_screen = prev_screen;
2642 state->redrawer = redraw;
2643 state->mangled_screen = 1;
2645 else{
2647 * If default role was selected (NULL) we need to make
2648 * up a role which won't do anything, but will cause
2649 * compose_mail to think there's already a role so that
2650 * it won't try to confirm the default.
2652 if(role)
2653 role = combine_inherited_role(role);
2654 else{
2655 role = (ACTION_S *) fs_get(sizeof(*role));
2656 memset((void *) role, 0, sizeof(*role));
2657 role->nick = cpystr("Default Role");
2660 state->redrawer = NULL;
2661 switch(action){
2662 case 'c':
2663 compose_mail(NULL, NULL, role, NULL, NULL);
2664 break;
2666 case 'r':
2667 (void) reply(state, role);
2668 break;
2670 case 'f':
2671 (void) forward(state, role);
2672 break;
2674 case 'b':
2675 (void) bounce(state, role);
2676 break;
2679 if(role)
2680 free_action(&role);
2682 state->next_screen = prev_screen;
2683 state->redrawer = redraw;
2684 state->mangled_screen = 1;
2690 /*----------------------------------------------------------------------
2691 Do the dirty work of prompting the user for a folder name
2693 Args:
2694 nfldr should be a buffer at least MAILTMPLEN long
2695 dela -- a pointer to a SaveDel. If it is
2696 DontAsk on input, don't offer Delete prompt
2697 Del on input, offer Delete command with default of Delete
2698 NoDel NoDelete
2699 RetDel and RetNoDel are return values
2702 Result:
2704 ----*/
2706 save_prompt(struct pine *state, CONTEXT_S **cntxt, char *nfldr, size_t len_nfldr,
2707 char *nmsgs, ENVELOPE *env, long int rawmsgno, char *section,
2708 SaveDel *dela, SavePreserveOrder *prea)
2710 int rc, ku = -1, n, flags, last_rc = 0, saveable_count = 0, done = 0;
2711 int delindex, preindex, r;
2712 char prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN];
2713 char *buf = tmp_20k_buf;
2714 char shortbuf[200];
2715 char *folder;
2716 HelpType help;
2717 SaveDel del = DontAsk;
2718 SavePreserveOrder pre = DontAskPreserve;
2719 char *deltext = NULL;
2720 static HISTORY_S *history = NULL;
2721 CONTEXT_S *tc;
2722 ESCKEY_S ekey[10];
2724 if(!cntxt)
2725 alpine_panic("no context ptr in save_prompt");
2727 init_hist(&history, HISTSIZE);
2729 if(!(folder = save_get_default(state, env, rawmsgno, section, cntxt)))
2730 return(0); /* message expunged! */
2732 /* how many context's can be saved to... */
2733 for(tc = state->context_list; tc; tc = tc->next)
2734 if(!NEWS_TEST(tc))
2735 saveable_count++;
2737 /* set up extra command option keys */
2738 rc = 0;
2739 ekey[rc].ch = ctrl('T');
2740 ekey[rc].rval = 2;
2741 ekey[rc].name = "^T";
2742 /* TRANSLATORS: command means go to Folders list */
2743 ekey[rc++].label = N_("To Fldrs");
2745 if(saveable_count > 1){
2746 ekey[rc].ch = ctrl('P');
2747 ekey[rc].rval = 10;
2748 ekey[rc].name = "^P";
2749 ekey[rc++].label = N_("Prev Collection");
2751 ekey[rc].ch = ctrl('N');
2752 ekey[rc].rval = 11;
2753 ekey[rc].name = "^N";
2754 ekey[rc++].label = N_("Next Collection");
2757 if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
2758 ekey[rc].ch = TAB;
2759 ekey[rc].rval = 12;
2760 ekey[rc].name = "TAB";
2761 /* TRANSLATORS: command asks alpine to complete the name when tab is typed */
2762 ekey[rc++].label = N_("Complete");
2765 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
2766 ekey[rc].ch = ctrl('X');
2767 ekey[rc].rval = 14;
2768 ekey[rc].name = "^X";
2769 /* TRANSLATORS: list all the matches */
2770 ekey[rc++].label = N_("ListMatches");
2773 if(dela && (*dela == NoDel || *dela == Del)){
2774 ekey[rc].ch = ctrl('R');
2775 ekey[rc].rval = 15;
2776 ekey[rc].name = "^R";
2777 delindex = rc++;
2778 del = *dela;
2781 if(prea && (*prea == NoPreserve || *prea == Preserve)){
2782 ekey[rc].ch = ctrl('W');
2783 ekey[rc].rval = 16;
2784 ekey[rc].name = "^W";
2785 preindex = rc++;
2786 pre = *prea;
2789 if(saveable_count > 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2790 ekey[rc].ch = KEY_UP;
2791 ekey[rc].rval = 10;
2792 ekey[rc].name = "";
2793 ekey[rc++].label = "";
2795 ekey[rc].ch = KEY_DOWN;
2796 ekey[rc].rval = 11;
2797 ekey[rc].name = "";
2798 ekey[rc++].label = "";
2800 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2801 ekey[rc].ch = KEY_UP;
2802 ekey[rc].rval = 30;
2803 ekey[rc].name = "";
2804 ku = rc;
2805 ekey[rc++].label = "";
2807 ekey[rc].ch = KEY_DOWN;
2808 ekey[rc].rval = 31;
2809 ekey[rc].name = "";
2810 ekey[rc++].label = "";
2813 ekey[rc].ch = -1;
2815 *nfldr = '\0';
2816 help = NO_HELP;
2817 while(!done){
2818 /* only show collection number if more than one available */
2819 if(ps_global->context_list->next)
2820 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder in <%s> [%s] : ",
2821 deltext ? deltext : "",
2822 nmsgs,
2823 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2824 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2825 else
2826 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder [%s] : ",
2827 deltext ? deltext : "",
2828 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2830 prompt[sizeof(prompt)-1] = '\0';
2833 * If the prompt won't fit, try removing deltext.
2835 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && deltext){
2836 if(ps_global->context_list->next)
2837 snprintf(prompt, sizeof(prompt), "SAVE %sto folder in <%s> [%s] : ",
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 %sto folder [%s] : ",
2843 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2845 prompt[sizeof(prompt)-1] = '\0';
2849 * If the prompt still won't fit, remove the extra info contained
2850 * in nmsgs.
2852 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && *nmsgs){
2853 if(ps_global->context_list->next)
2854 snprintf(prompt, sizeof(prompt), "SAVE to folder in <%s> [%s] : ",
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 to folder [%s] : ",
2859 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2861 prompt[sizeof(prompt)-1] = '\0';
2864 if(del != DontAsk)
2865 ekey[delindex].label = (del == NoDel) ? "Delete" : "No Delete";
2867 if(pre != DontAskPreserve)
2868 ekey[preindex].label = (pre == NoPreserve) ? "Preserve Order" : "Any Order";
2870 if(ku >= 0){
2871 if(items_in_hist(history) > 1){
2872 ekey[ku].name = HISTORY_UP_KEYNAME;
2873 ekey[ku].label = HISTORY_KEYLABEL;
2874 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
2875 ekey[ku+1].label = HISTORY_KEYLABEL;
2877 else{
2878 ekey[ku].name = "";
2879 ekey[ku].label = "";
2880 ekey[ku+1].name = "";
2881 ekey[ku+1].label = "";
2885 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
2886 rc = optionally_enter(nfldr, -FOOTER_ROWS(state), 0, len_nfldr,
2887 prompt, ekey, help, &flags);
2889 switch(rc){
2890 case -1 :
2891 q_status_message(SM_ORDER | SM_DING, 3, 3,
2892 _("Error reading folder name"));
2893 done--;
2894 break;
2896 case 0 :
2897 removing_trailing_white_space(nfldr);
2898 removing_leading_white_space(nfldr);
2900 if(*nfldr || *folder){
2901 char *p, *name, *fullname = NULL;
2902 int exists, breakout = FALSE;
2904 if(!*nfldr){
2905 strncpy(nfldr, folder, len_nfldr-1);
2906 nfldr[len_nfldr-1] = '\0';
2909 save_hist(history, nfldr, 0, (void *) *cntxt);
2911 if(!(name = folder_is_nick(nfldr, FOLDERS(*cntxt), 0)))
2912 name = nfldr;
2914 if(update_folder_spec(expanded, sizeof(expanded), name)){
2915 strncpy(name = nfldr, expanded, len_nfldr-1);
2916 nfldr[len_nfldr-1] = '\0';
2919 exists = folder_name_exists(*cntxt, name, &fullname);
2921 if(exists == FEX_ERROR){
2922 q_status_message1(SM_ORDER, 0, 3,
2923 _("Problem accessing folder \"%s\""),
2924 nfldr);
2925 done--;
2927 else{
2928 if(fullname){
2929 strncpy(name = nfldr, fullname, len_nfldr-1);
2930 nfldr[len_nfldr-1] = '\0';
2931 fs_give((void **) &fullname);
2932 breakout = TRUE;
2935 if(exists & FEX_ISFILE){
2936 done++;
2938 else if((exists & FEX_ISDIR)){
2939 char tmp[MAILTMPLEN];
2941 tc = *cntxt;
2942 if(breakout){
2943 CONTEXT_S *fake_context;
2944 size_t l;
2946 strncpy(tmp, name, sizeof(tmp));
2947 tmp[sizeof(tmp)-2-1] = '\0';
2948 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
2949 if(l < sizeof(tmp)){
2950 tmp[l] = tc->dir->delim;
2951 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
2954 else
2955 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
2957 tmp[sizeof(tmp)-1] = '\0';
2959 fake_context = new_context(tmp, 0);
2960 nfldr[0] = '\0';
2961 done = display_folder_list(&fake_context, nfldr,
2962 1, folders_for_save);
2963 free_context(&fake_context);
2965 else if(tc->dir->delim
2966 && (p = strrindex(name, tc->dir->delim))
2967 && *(p+1) == '\0')
2968 done = display_folder_list(cntxt, nfldr,
2969 1, folders_for_save);
2970 else{
2971 q_status_message1(SM_ORDER, 3, 3,
2972 _("\"%s\" is a directory"), name);
2973 if(tc->dir->delim
2974 && !((p=strrindex(name, tc->dir->delim)) && *(p+1) == '\0')){
2975 strncpy(tmp, name, sizeof(tmp));
2976 tmp[sizeof(tmp)-1] = '\0';
2977 snprintf(nfldr, len_nfldr, "%s%c", tmp, tc->dir->delim);
2981 else{ /* Doesn't exist, create! */
2982 if((fullname = folder_as_breakout(*cntxt, name)) != NULL){
2983 strncpy(name = nfldr, fullname, len_nfldr-1);
2984 nfldr[len_nfldr-1] = '\0';
2985 fs_give((void **) &fullname);
2988 switch(create_for_save(*cntxt, name)){
2989 case 1 : /* success */
2990 done++;
2991 break;
2992 case 0 : /* error */
2993 case -1 : /* declined */
2994 done--;
2995 break;
3000 break;
3002 /* else fall thru like they cancelled */
3004 case 1 :
3005 cmd_cancelled("Save message");
3006 done--;
3007 break;
3009 case 2 :
3010 r = display_folder_list(cntxt, nfldr, 0, folders_for_save);
3012 if(r)
3013 done++;
3015 break;
3017 case 3 :
3018 helper(h_save, _("HELP FOR SAVE"), HLPD_SIMPLE);
3019 ps_global->mangled_screen = 1;
3020 break;
3022 case 4 : /* redraw */
3023 break;
3025 case 10 : /* previous collection */
3026 for(tc = (*cntxt)->prev; tc; tc = tc->prev)
3027 if(!NEWS_TEST(tc))
3028 break;
3030 if(!tc){
3031 CONTEXT_S *tc2;
3033 for(tc2 = (tc = (*cntxt))->next; tc2; tc2 = tc2->next)
3034 if(!NEWS_TEST(tc2))
3035 tc = tc2;
3038 *cntxt = tc;
3039 break;
3041 case 11 : /* next collection */
3042 tc = (*cntxt);
3045 if(((*cntxt) = (*cntxt)->next) == NULL)
3046 (*cntxt) = ps_global->context_list;
3047 while(NEWS_TEST(*cntxt) && (*cntxt) != tc);
3048 break;
3050 case 12 : /* file name completion */
3051 if(!folder_complete(*cntxt, nfldr, len_nfldr, &n)){
3052 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
3053 r = display_folder_list(cntxt, nfldr, 1, folders_for_save);
3054 if(r)
3055 done++; /* bingo! */
3056 else
3057 rc = 0; /* burn last_rc */
3059 else
3060 Writechar(BELL, 0);
3063 break;
3065 case 14 : /* file name completion */
3066 r = display_folder_list(cntxt, nfldr, 2, folders_for_save);
3067 if(r)
3068 done++; /* bingo! */
3069 else
3070 rc = 0; /* burn last_rc */
3072 break;
3074 case 15 : /* Delete / No Delete */
3075 del = (del == NoDel) ? Del : NoDel;
3076 deltext = (del == NoDel) ? " (no delete)" : " (and delete)";
3077 break;
3079 case 16 : /* Preserve Order or not */
3080 pre = (pre == NoPreserve) ? Preserve : NoPreserve;
3081 break;
3083 case 30 :
3084 if((p = get_prev_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3085 strncpy(nfldr, p, len_nfldr);
3086 nfldr[len_nfldr-1] = '\0';
3087 if(history->hist[history->curindex])
3088 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3090 else
3091 Writechar(BELL, 0);
3093 break;
3095 case 31 :
3096 if((p = get_next_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3097 strncpy(nfldr, p, len_nfldr);
3098 nfldr[len_nfldr-1] = '\0';
3099 if(history->hist[history->curindex])
3100 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3102 else
3103 Writechar(BELL, 0);
3105 break;
3107 default :
3108 alpine_panic("Unhandled case");
3109 break;
3112 last_rc = rc;
3115 ps_global->mangled_footer = 1;
3117 if(done < 0)
3118 return(0);
3120 if(*nfldr){
3121 strncpy(ps_global->last_save_folder, nfldr, sizeof(ps_global->last_save_folder)-1);
3122 ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0';
3123 if(*cntxt)
3124 ps_global->last_save_context = *cntxt;
3126 else{
3127 strncpy(nfldr, folder, len_nfldr-1);
3128 nfldr[len_nfldr-1] = '\0';
3131 /* nickname? Copy real name to nfldr */
3132 if(*cntxt
3133 && context_isambig(nfldr)
3134 && (p = folder_is_nick(nfldr, FOLDERS(*cntxt), 0))){
3135 strncpy(nfldr, p, len_nfldr-1);
3136 nfldr[len_nfldr-1] = '\0';
3139 if(dela && (*dela == NoDel || *dela == Del))
3140 *dela = (del == NoDel) ? RetNoDel : RetDel;
3142 if(prea && (*prea == NoPreserve || *prea == Preserve))
3143 *prea = (pre == NoPreserve) ? RetNoPreserve : RetPreserve;
3145 return(1);
3149 /*----------------------------------------------------------------------
3150 Prompt user before implicitly creating a folder for saving
3152 Args: context - context to create folder in
3153 folder - folder name to create
3155 Result: 1 on proceed, -1 on decline, 0 on error
3157 ----*/
3159 create_for_save_prompt(CONTEXT_S *context, char *folder, int sequence_sensitive)
3161 if(context && ps_global->context_list->next && context_isambig(folder)){
3162 if(context->use & CNTXT_INCMNG){
3163 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3164 _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
3165 folder, (strlen(folder) > 15) ? "..." : "");
3166 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
3167 return(0); /* error */
3170 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3171 _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
3172 folder, (strlen(folder) > 15) ? "..." : "",
3173 context->nickname,
3174 (strlen(context->nickname) > 15) ? "..." : "");
3176 else
3177 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
3178 _("Folder \"%.40s%s\" doesn't exist. Create"),
3179 folder, strlen(folder) > 40 ? "..." : "");
3181 if(want_to(tmp_20k_buf, 'y', 'n',
3182 NO_HELP, (sequence_sensitive) ? WT_SEQ_SENSITIVE : WT_NORM) != 'y'){
3183 cmd_cancelled("Save message");
3184 return(-1);
3187 return(1);
3192 /*----------------------------------------------------------------------
3193 Expunge messages from current folder
3195 Args: state -- pointer to struct holding a bunch of pine state
3196 msgmap -- table mapping msg nums to c-client sequence nums
3197 qline -- screen line to ask questions on
3198 agg -- boolean indicating we're to operate on aggregate set
3200 Result:
3201 ----*/
3203 cmd_expunge(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int agg)
3205 long del_count, prefilter_del_count;
3206 int we_cancel = 0, rv = 0;
3207 char prompt[MAX_SCREEN_COLS+1];
3208 char *sequence;
3209 COLOR_PAIR *lastc = NULL;
3211 dprint((2, "\n - expunge -\n"));
3213 del_count = 0;
3215 sequence = MCMD_ISAGG(agg) ? selected_sequence(stream, msgmap, NULL, 0) : NULL;
3217 if(MCMD_ISAGG(agg)){
3218 long i;
3219 MESSAGECACHE *mc;
3220 for(i = 1L; i <= stream->nmsgs; i++){
3221 if((mc = mail_elt(stream, i)) != NULL
3222 && mc->sequence && mc->deleted)
3223 del_count++;
3225 if(del_count == 0){
3226 q_status_message(SM_ORDER, 0, 4,
3227 _("No selected messages are deleted"));
3228 return 0;
3230 } else {
3231 if(!any_messages(msgmap, NULL, "to Expunge"))
3232 return rv;
3235 if(IS_NEWS(stream) && stream->rdonly){
3236 if(!MCMD_ISAGG(agg))
3237 del_count = count_flagged(stream, F_DEL);
3238 if(del_count > 0L){
3239 state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3240 snprintf(prompt, sizeof(prompt), "Exclude %ld message%s from %.*s", del_count,
3241 plural(del_count), MAX_SCREEN_COLS+1-40,
3242 pretty_fn(state->cur_folder));
3243 prompt[sizeof(prompt)-1] = '\0';
3244 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3245 || (F_ON(F_AUTO_EXPUNGE, state)
3246 && (state->context_current
3247 && (state->context_current->use & CNTXT_INCMNG))
3248 && context_isambig(state->cur_folder))
3249 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
3251 if(F_ON(F_NEWS_CROSS_DELETE, state))
3252 cross_delete_crossposts(stream);
3254 msgno_exclude_deleted(stream, msgmap, sequence);
3255 clear_index_cache(stream, 0);
3258 * This is kind of surprising at first. For most sort
3259 * orders, if the whole set is sorted, then any subset
3260 * is also sorted. Not so for threaded sorts.
3262 if(SORT_IS_THREADED(msgmap))
3263 refresh_sort(stream, msgmap, SRT_NON);
3265 state->mangled_body = 1;
3266 state->mangled_header = 1;
3267 q_status_message2(SM_ORDER, 0, 4,
3268 "%s message%s excluded",
3269 long2string(del_count),
3270 plural(del_count));
3272 else
3273 any_messages(NULL, NULL, "Excluded");
3275 else
3276 any_messages(NULL, "deleted", "to Exclude");
3278 return del_count;
3280 else if(READONLY_FOLDER(stream)){
3281 q_status_message(SM_ORDER, 0, 4,
3282 _("Can't expunge. Folder is read-only"));
3283 return del_count;
3286 if(!MCMD_ISAGG(agg)){
3287 prefilter_del_count = count_flagged(stream, F_DEL|F_NOFILT);
3288 mail_expunge_prefilter(stream, MI_NONE);
3289 del_count = count_flagged(stream, F_DEL|F_NOFILT);
3292 if(del_count != 0){
3293 int ret;
3294 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3295 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3296 snprintf(prompt, sizeof(prompt), "Expunge %ld message%s from %.*s", del_count,
3297 plural(del_count), MAX_SCREEN_COLS+1-40,
3298 pretty_fn((char *) fname));
3299 if(fname) fs_give((void **)&fname);
3300 prompt[sizeof(prompt)-1] = '\0';
3301 state->mangled_footer = 1;
3303 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3304 || (F_ON(F_AUTO_EXPUNGE, state)
3305 && ((!strucmp(state->cur_folder,state->inbox_name))
3306 || (state->context_current->use & CNTXT_INCMNG))
3307 && context_isambig(state->cur_folder))
3308 || (ret=want_to(prompt, 'y', 0, NO_HELP, WT_NORM)) == 'y')
3309 ret = 'y';
3311 if(ret == 'x')
3312 cmd_cancelled("Expunge");
3314 if(ret != 'y')
3315 return 0;
3318 dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
3319 mn_get_total(msgmap), mn_get_cur(msgmap), del_count));
3321 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3322 state->VAR_TITLE_BACK_COLOR,
3323 PSC_REV|PSC_RET);
3325 PutLine0(0, 0, "**"); /* indicate delay */
3327 if(lastc){
3328 (void)pico_set_colorp(lastc, PSC_NONE);
3329 free_color_pair(&lastc);
3332 MoveCursor(state->ttyo->screen_rows -FOOTER_ROWS(state), 0);
3333 fflush(stdout);
3335 we_cancel = busy_cue(_("Expunging"), NULL, 1);
3337 if(cmd_expunge_work(stream, msgmap, sequence))
3338 state->mangled_body = 1;
3340 if(sequence)
3341 fs_give((void **)&sequence);
3343 if(we_cancel)
3344 cancel_busy_cue((sp_expunge_count(stream) > 0) ? 0 : -1);
3346 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3347 state->VAR_TITLE_BACK_COLOR,
3348 PSC_REV|PSC_RET);
3349 PutLine0(0, 0, " "); /* indicate delay's over */
3351 if(lastc){
3352 (void)pico_set_colorp(lastc, PSC_NONE);
3353 free_color_pair(&lastc);
3356 fflush(stdout);
3358 if(sp_expunge_count(stream) > 0){
3360 * This is kind of surprising at first. For most sort
3361 * orders, if the whole set is sorted, then any subset
3362 * is also sorted. Not so for threaded sorts.
3364 if(SORT_IS_THREADED(msgmap))
3365 refresh_sort(stream, msgmap, SRT_NON);
3367 else{
3368 if(del_count){
3369 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3370 q_status_message1(SM_ORDER, 0, 3,
3371 _("No messages expunged from folder \"%s\""),
3372 pretty_fn((char *) fname));
3373 if(fname) fs_give((void **)&fname);
3375 else if(!prefilter_del_count)
3376 q_status_message(SM_ORDER, 0, 3,
3377 _("No messages marked deleted. No messages expunged."));
3379 return del_count;
3383 /*----------------------------------------------------------------------
3384 Expunge_and_close callback to prompt user for confirmation
3386 Args: stream -- folder's stream
3387 folder -- name of folder containing folders
3388 deleted -- number of del'd msgs
3390 Result: 'y' to continue with expunge
3391 ----*/
3393 expunge_prompt(MAILSTREAM *stream, char *folder, long int deleted)
3395 long max_folder;
3396 int charcnt = 0;
3397 char prompt_b[MAX_SCREEN_COLS+1], temp[MAILTMPLEN+1], buff[MAX_SCREEN_COLS+1];
3398 char *short_folder_name;
3400 if(deleted == 1)
3401 charcnt = 1;
3402 else{
3403 snprintf(temp, sizeof(temp), "%ld", deleted);
3404 charcnt = strlen(temp)+1;
3407 max_folder = MAX(1,MAXPROMPT - (36+charcnt));
3408 strncpy(temp, folder, sizeof(temp));
3409 temp[sizeof(temp)-1] = '\0';
3410 short_folder_name = short_str(temp,buff,sizeof(buff),max_folder,FrontDots);
3412 if(IS_NEWS(stream))
3413 snprintf(prompt_b, sizeof(prompt_b),
3414 "Delete %s%ld message%s from \"%s\"",
3415 (deleted > 1L) ? "all " : "", deleted,
3416 plural(deleted), short_folder_name);
3417 else
3418 snprintf(prompt_b, sizeof(prompt_b),
3419 "Expunge the %ld deleted message%s from \"%s\"",
3420 deleted, deleted == 1 ? "" : "s",
3421 short_folder_name);
3423 return(want_to(prompt_b, 'y', 0, NO_HELP, WT_NORM));
3428 * This is used with multiple append saves. Call it once before
3429 * the series of appends with SSCP_INIT and once after all are
3430 * done with SSCP_END. In between, it is called automatically
3431 * from save_fetch_append or save_fetch_append_cb when we need
3432 * to ask the user if he or she wants to continue even though
3433 * announced message size doesn't match the actual message size.
3434 * As of 2008-02-29 the gmail IMAP server has these size mismatches
3435 * on a regular basis even though the data is ok.
3438 save_size_changed_prompt(long msgno, int flags)
3440 int ret;
3441 char prompt[100];
3442 static int remember_the_yes = 0;
3443 static int possible_corruption = 0;
3444 static ESCKEY_S save_size_opts[] = {
3445 {'y', 'y', "Y", "Yes"},
3446 {'n', 'n', "N", "No"},
3447 {'a', 'a', "A", "yes to All"},
3448 {-1, 0, NULL, NULL}
3451 if(F_ON(F_IGNORE_SIZE, ps_global))
3452 return 'y';
3454 if(flags & SSCP_INIT || flags & SSCP_END){
3455 if(flags & SSCP_END && possible_corruption)
3456 q_status_message(SM_ORDER, 3, 3, "There is possible data corruption, check the results");
3458 remember_the_yes = 0;
3459 possible_corruption = 0;
3460 ps_global->noshow_error = 0;
3461 ps_global->noshow_warn = 0;
3462 return(0);
3465 if(remember_the_yes){
3466 snprintf(prompt, sizeof(prompt),
3467 "Message to save shrank! (msg # %ld): Continuing", msgno);
3468 q_status_message(SM_ORDER, 0, 3, prompt);
3469 display_message('x');
3470 return(remember_the_yes);
3473 snprintf(prompt, sizeof(prompt),
3474 "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno);
3475 ret = radio_buttons(prompt, -FOOTER_ROWS(ps_global), save_size_opts,
3476 'n', 0, h_save_size_changed, RB_NORM);
3478 switch(ret){
3479 case 'a':
3480 remember_the_yes = 'y';
3481 possible_corruption++;
3482 return(remember_the_yes);
3484 case 'y':
3485 possible_corruption++;
3486 return('y');
3488 default:
3489 possible_corruption = 0;
3490 ps_global->noshow_error = 1;
3491 ps_global->noshow_warn = 1;
3492 break;
3495 return('n');
3499 /*----------------------------------------------------------------------
3500 Expunge_and_close callback that happens once the decision to expunge
3501 and close has been made and before expunging and closing begins
3504 Args: stream -- folder's stream
3505 folder -- name of folder containing folders
3506 deleted -- number of del'd msgs
3508 Result: 'y' to continue with expunge
3509 ----*/
3510 void
3511 expunge_and_close_begins(int flags, char *folder)
3513 if(!(flags & EC_NO_CLOSE)){
3514 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
3515 q_status_message1(SM_INFO, 0, 1, "Closing \"%.200s\"...", (char *) fname);
3516 flush_status_messages(1);
3517 if(fname) fs_give((void **)&fname);
3522 /*----------------------------------------------------------------------
3523 Export a message to a plain file in users home directory
3525 Args: state -- pointer to struct holding a bunch of pine state
3526 msgmap -- table mapping msg nums to c-client sequence nums
3527 qline -- screen line to ask questions on
3528 agg -- boolean indicating we're to operate on aggregate set
3530 Result:
3531 ----*/
3533 cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt)
3535 char filename[MAXPATH+1], full_filename[MAXPATH+1], *err;
3536 char nmsgs[80];
3537 int r, leading_nl, failure = 0, orig_errno, rflags = GER_NONE;
3538 int flags = GE_IS_EXPORT | GE_SEQ_SENSITIVE, rv = 0;
3539 ENVELOPE *env;
3540 MESSAGECACHE *mc;
3541 BODY *b;
3542 long i, count = 0L, start_of_append, rawno;
3543 gf_io_t pc;
3544 STORE_S *store;
3545 struct variable *vars = ps_global->vars;
3546 ESCKEY_S export_opts[5];
3547 static HISTORY_S *history = NULL;
3549 if(ps_global->restricted){
3550 q_status_message(SM_ORDER, 0, 3,
3551 "Alpine demo can't export messages to files");
3552 return rv;
3555 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
3556 return rv;
3558 export_opts[i = 0].ch = ctrl('T');
3559 export_opts[i].rval = 10;
3560 export_opts[i].name = "^T";
3561 export_opts[i++].label = N_("To Files");
3563 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3564 if(ps_global->VAR_DOWNLOAD_CMD && ps_global->VAR_DOWNLOAD_CMD[0]){
3565 export_opts[i].ch = ctrl('V');
3566 export_opts[i].rval = 12;
3567 export_opts[i].name = "^V";
3568 /* TRANSLATORS: this is an abbreviation for Download Messages */
3569 export_opts[i++].label = N_("Downld Msg");
3571 #endif /* !(DOS || MAC) */
3573 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
3574 export_opts[i].ch = ctrl('I');
3575 export_opts[i].rval = 11;
3576 export_opts[i].name = "TAB";
3577 export_opts[i++].label = N_("Complete");
3580 #if 0
3581 /* Commented out since it's not yet support! */
3582 if(F_ON(F_ENABLE_SUB_LISTS,ps_global)){
3583 export_opts[i].ch = ctrl('X');
3584 export_opts[i].rval = 14;
3585 export_opts[i].name = "^X";
3586 export_opts[i++].label = N_("ListMatches");
3588 #endif
3591 * If message has attachments, add a toggle that will allow the user
3592 * to save all of the attachments to a single directory, using the
3593 * names provided with the attachments or part names. What we'll do is
3594 * export the message as usual, and then export the attachments into
3595 * a subdirectory that did not exist before. The subdir will be named
3596 * something based on the name of the file being saved to, but a
3597 * unique, new name.
3599 if(!MCMD_ISAGG(aopt)
3600 && state->mail_stream
3601 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3602 && rawno <= state->mail_stream->nmsgs
3603 && (env = pine_mail_fetchstructure(state->mail_stream, rawno, &b))
3604 && b
3605 && b->type == TYPEMULTIPART
3606 && b->subtype
3607 && strucmp(b->subtype, "ALTERNATIVE") != 0){
3608 PART *part;
3610 part = b->nested.part; /* 1st part */
3611 if(part && part->next)
3612 flags |= GE_ALLPARTS;
3615 export_opts[i].ch = -1;
3616 filename[0] = '\0';
3618 if(mn_total_cur(msgmap) <= 1L){
3619 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld", mn_get_cur(msgmap));
3620 nmsgs[sizeof(nmsgs)-1] = '\0';
3622 else{
3623 snprintf(nmsgs, sizeof(nmsgs), "%s messages", comatose(mn_total_cur(msgmap)));
3624 nmsgs[sizeof(nmsgs)-1] = '\0';
3627 r = get_export_filename(state, filename, NULL, full_filename,
3628 sizeof(filename), nmsgs, "EXPORT",
3629 export_opts, &rflags, qline, flags, &history);
3631 if(r < 0){
3632 switch(r){
3633 case -1:
3634 cmd_cancelled("Export message");
3635 break;
3637 case -2:
3638 q_status_message1(SM_ORDER, 0, 2,
3639 _("Can't export to file outside of %s"),
3640 VAR_OPER_DIR);
3641 break;
3644 goto fini;
3646 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3647 else if(r == 12){ /* Download */
3648 char cmd[MAXPATH], *tfp = NULL;
3649 int next = 0;
3650 PIPE_S *syspipe;
3651 STORE_S *so;
3652 gf_io_t pc;
3654 if(ps_global->restricted){
3655 q_status_message(SM_ORDER | SM_DING, 3, 3,
3656 "Download disallowed in restricted mode");
3657 goto fini;
3660 err = NULL;
3661 tfp = temp_nam(NULL, "pd");
3662 build_updown_cmd(cmd, sizeof(cmd), ps_global->VAR_DOWNLOAD_CMD_PREFIX,
3663 ps_global->VAR_DOWNLOAD_CMD, tfp);
3664 dprint((1, "Download cmd called: \"%s\"\n", cmd));
3665 if((so = so_get(FileStar, tfp, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){
3666 gf_set_so_writec(&pc, so);
3668 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
3669 if(!(state->mail_stream
3670 && (rawno = mn_m2raw(msgmap, i)) > 0L
3671 && rawno <= state->mail_stream->nmsgs
3672 && (mc = mail_elt(state->mail_stream, rawno))
3673 && mc->valid))
3674 mc = NULL;
3676 if(!(env = pine_mail_fetchstructure(state->mail_stream,
3677 mn_m2raw(msgmap, i), &b))
3678 || !bezerk_delimiter(env, mc, pc, next++)
3679 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
3680 env, b, NULL, FM_NEW_MESS | FM_NOWRAP, pc)){
3681 q_status_message(SM_ORDER | SM_DING, 3, 3,
3682 err = "Error writing tempfile for download");
3683 break;
3687 gf_clear_so_writec(so);
3688 if(so_give(&so)){ /* close file */
3689 if(!err)
3690 err = "Error writing tempfile for download";
3693 if(!err){
3694 if((syspipe = open_system_pipe(cmd, NULL, NULL,
3695 PIPE_USER | PIPE_RESET,
3696 0, pipe_callback, pipe_report_error)) != NULL)
3697 (void) close_system_pipe(&syspipe, NULL, pipe_callback);
3698 else
3699 q_status_message(SM_ORDER | SM_DING, 3, 3,
3700 err = _("Error running download command"));
3703 else
3704 q_status_message(SM_ORDER | SM_DING, 3, 3,
3705 err = "Error building temp file for download");
3707 if(tfp){
3708 our_unlink(tfp);
3709 fs_give((void **)&tfp);
3712 if(!err)
3713 q_status_message(SM_ORDER, 0, 3, _("Download Command Completed"));
3715 goto fini;
3717 #endif /* !(DOS || MAC) */
3720 if(rflags & GER_APPEND)
3721 leading_nl = 1;
3722 else
3723 leading_nl = 0;
3725 dprint((5, "Opening file \"%s\" for export\n",
3726 full_filename ? full_filename : "?"));
3728 if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE))){
3729 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3730 /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
3731 _("Error opening file \"%s\" to export message: %s"),
3732 full_filename, error_description(errno));
3733 goto fini;
3735 else
3736 gf_set_so_writec(&pc, store);
3738 err = NULL;
3739 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), count++){
3740 env = pine_mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i),
3741 &b);
3742 if(!env) {
3743 err = _("Can't export message. Error accessing mail folder");
3744 failure = 1;
3745 break;
3748 if(!(state->mail_stream
3749 && (rawno = mn_m2raw(msgmap, i)) > 0L
3750 && rawno <= state->mail_stream->nmsgs
3751 && (mc = mail_elt(state->mail_stream, rawno))
3752 && mc->valid))
3753 mc = NULL;
3755 start_of_append = so_tell(store);
3756 if(!bezerk_delimiter(env, mc, pc, leading_nl)
3757 || !format_message(mn_m2raw(msgmap, i), env, b, NULL,
3758 FM_NEW_MESS | FM_NOWRAP, pc)){
3759 orig_errno = errno; /* save incase things are really bad */
3760 failure = 1; /* pop out of here */
3761 break;
3764 leading_nl = 1;
3767 gf_clear_so_writec(store);
3768 if(so_give(&store)) /* release storage */
3769 failure++;
3771 if(failure){
3772 our_truncate(full_filename, (off_t)start_of_append);
3773 if(err){
3774 dprint((1, "FAILED Export: fetch(%ld): %s\n",
3775 i, err ? err : "?"));
3776 q_status_message(SM_ORDER | SM_DING, 3, 4, err);
3778 else{
3779 dprint((1, "FAILED Export: file \"%s\" : %s\n",
3780 full_filename ? full_filename : "?",
3781 error_description(orig_errno)));
3782 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3783 /* TRANSLATORS: Error exporting to <filename>: <error text> */
3784 _("Error exporting to \"%s\" : %s"),
3785 filename, error_description(orig_errno));
3788 else{
3789 if(rflags & GER_ALLPARTS && full_filename[0]){
3790 char dir[MAXPATH+1];
3791 char lfile[MAXPATH+1];
3792 int ok = 0, tries = 0, saved = 0, errs = 0, counter = 2;
3793 ATTACH_S *a;
3796 * Now we want to save all of the attachments to a subdirectory.
3797 * To make it easier for us and probably easier for the user, and
3798 * to prevent the user from shooting himself in the foot, we
3799 * make a new subdirectory so that we can't possibly step on
3800 * any existing files, and we don't need any interaction with the
3801 * user while saving.
3803 * We'll just use the directory name full_filename.d or if that
3804 * already exists and isn't empty, we'll try adding a suffix to
3805 * that until we get something to use.
3808 if(strlen(full_filename) + strlen(".d") + 1 > sizeof(dir)){
3809 q_status_message1(SM_ORDER | SM_DING, 3, 4,
3810 _("Can't save attachments, filename too long: %s"),
3811 full_filename);
3812 goto fini;
3815 ok = 0;
3816 snprintf(dir, sizeof(dir), "%s.d", full_filename);
3817 dir[sizeof(dir)-1] = '\0';
3819 do {
3820 tries++;
3821 switch(r = is_writable_dir(dir)){
3822 case 0: /* exists and is a writable dir */
3824 * We could figure out if it is empty and use it in
3825 * that case, but that sounds like a lot of work, so
3826 * just fall through to default.
3829 default:
3830 if(strlen(full_filename) + strlen(".d") + 1 +
3831 1 + strlen(long2string((long) tries)) > sizeof(dir)){
3832 q_status_message(SM_ORDER | SM_DING, 3, 4,
3833 "Problem saving attachments");
3834 goto fini;
3837 snprintf(dir, sizeof(dir), "%s.d_%s", full_filename,
3838 long2string((long) tries));
3839 dir[sizeof(dir)-1] = '\0';
3840 break;
3842 case 3: /* doesn't exist, that's good! */
3843 /* make new directory */
3844 ok++;
3845 break;
3847 } while(!ok && tries < 1000);
3849 if(tries >= 1000){
3850 q_status_message(SM_ORDER | SM_DING, 3, 4,
3851 _("Problem saving attachments"));
3852 goto fini;
3855 /* create the new directory */
3856 if(our_mkdir(dir, 0700)){
3857 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3858 _("Problem saving attachments: %s: %s"), dir,
3859 error_description(errno));
3860 goto fini;
3863 if(!(state->mail_stream
3864 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3865 && rawno <= state->mail_stream->nmsgs
3866 && (env=pine_mail_fetchstructure(state->mail_stream,rawno,&b))
3867 && b)){
3868 q_status_message(SM_ORDER | SM_DING, 3, 4,
3869 _("Problem reading message"));
3870 goto fini;
3873 zero_atmts(state->atmts);
3874 describe_mime(b, "", 1, 1, 0, 0);
3876 a = state->atmts;
3877 if(a && a->description) /* skip main body part */
3878 a++;
3880 for(; a->description != NULL; a++){
3881 /* skip over these parts of the message */
3882 if(MIME_MSG_A(a) || MIME_DGST_A(a) || MIME_VCARD_A(a))
3883 continue;
3885 lfile[0] = '\0';
3886 (void) get_filename_parameter(lfile, sizeof(lfile), a->body, NULL);
3888 if(lfile[0] == '\0'){ /* MAXPATH + 1 = sizeof(lfile) */
3889 snprintf(lfile, sizeof(lfile), "part_%.*s", MAXPATH+1-6,
3890 a->number ? a->number : "?");
3891 lfile[sizeof(lfile)-1] = '\0';
3894 if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + 1
3895 > sizeof(filename)){
3896 dprint((2,
3897 "FAILED Att Export: name too long: %s\n",
3898 dir, S_FILESEP, lfile));
3899 errs++;
3900 continue;
3903 /* although files are being saved in a unique directory, there is
3904 * no guarantee that attachment names have unique names, so we have
3905 * to make sure that we are not constantly rewriting the same file name
3906 * over and over. In order to avoid this we test if the file already exists,
3907 * and if so, we write a counter name in the file name, just before the
3908 * extension of the file, and separate it with an underscore.
3910 snprintf(filename, sizeof(filename), "%s%s%s", dir, S_FILESEP, lfile);
3911 filename[sizeof(filename)-1] = '\0';
3912 while((ok = can_access(filename, ACCESS_EXISTS)) == 0 && errs == 0){
3913 char *ext;
3914 snprintf(filename, sizeof(filename), "%d", counter);
3915 if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + strlen(filename) + 2
3916 > sizeof(filename)){
3917 dprint((2,
3918 "FAILED Att Export: name too long: %s\n",
3919 dir, S_FILESEP, lfile));
3920 errs++;
3921 continue;
3923 if((ext = strrchr(lfile, '.')) != NULL)
3924 *ext = '\0';
3925 snprintf(filename, sizeof(filename), "%s%s%s%s%d%s%s",
3926 dir, S_FILESEP, lfile,
3927 ext ? "_" : "", counter++, ext ? "." : "", ext ? ext+1 : "");
3928 filename[sizeof(filename)-1] = '\0';
3931 if(write_attachment_to_file(state->mail_stream, rawno,
3932 a, GER_NONE, filename) == 1)
3933 saved++;
3934 else
3935 errs++;
3938 if(errs){
3939 if(saved)
3940 q_status_message1(SM_ORDER, 3, 3,
3941 "Errors saving some attachments, %s attachments saved",
3942 long2string((long) saved));
3943 else
3944 q_status_message(SM_ORDER, 3, 3,
3945 _("Problems saving attachments"));
3947 else{
3948 if(saved)
3949 q_status_message2(SM_ORDER, 0, 3,
3950 /* TRANSLATORS: Saved <how many> attachements to <directory name> */
3951 _("Saved %s attachments to %s"),
3952 long2string((long) saved), dir);
3953 else
3954 q_status_message(SM_ORDER, 3, 3, _("No attachments to save"));
3957 else if(mn_total_cur(msgmap) > 1L)
3958 q_status_message4(SM_ORDER,0,3,
3959 "%s message%s %s to file \"%s\"",
3960 long2string(count), plural(count),
3961 rflags & GER_OVER
3962 ? "overwritten"
3963 : rflags & GER_APPEND ? "appended" : "exported",
3964 filename);
3965 else
3966 q_status_message3(SM_ORDER,0,3,
3967 "Message %s %s to file \"%s\"",
3968 long2string(mn_get_cur(msgmap)),
3969 rflags & GER_OVER
3970 ? "overwritten"
3971 : rflags & GER_APPEND ? "appended" : "exported",
3972 filename);
3973 rv++;
3976 fini:
3977 if(MCMD_ISAGG(aopt))
3978 restore_selected(msgmap);
3980 return rv;
3985 * Ask user what file to export to. Export from srcstore to that file.
3987 * Args ps -- pine struct
3988 * srctext -- pointer to source text
3989 * srctype -- type of that source text
3990 * prompt_msg -- see get_export_filename
3991 * lister_msg -- "
3993 * Returns: != 0 : error
3994 * 0 : ok
3997 simple_export(struct pine *ps, void *srctext, SourceType srctype, char *prompt_msg, char *lister_msg)
3999 int r = 1, rflags = GER_NONE;
4000 char filename[MAXPATH+1], full_filename[MAXPATH+1];
4001 STORE_S *store = NULL;
4002 struct variable *vars = ps->vars;
4003 static HISTORY_S *history = NULL;
4004 static ESCKEY_S simple_export_opts[] = {
4005 {ctrl('T'), 10, "^T", N_("To Files")},
4006 {-1, 0, NULL, NULL},
4007 {-1, 0, NULL, NULL}};
4009 if(F_ON(F_ENABLE_TAB_COMPLETE,ps)){
4010 simple_export_opts[r].ch = ctrl('I');
4011 simple_export_opts[r].rval = 11;
4012 simple_export_opts[r].name = "TAB";
4013 simple_export_opts[r].label = N_("Complete");
4016 if(!srctext){
4017 q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
4018 r = -3;
4019 goto fini;
4022 simple_export_opts[++r].ch = -1;
4023 filename[0] = '\0';
4024 full_filename[0] = '\0';
4026 r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename),
4027 prompt_msg, lister_msg, simple_export_opts, &rflags,
4028 -FOOTER_ROWS(ps), GE_IS_EXPORT, &history);
4030 if(r < 0)
4031 goto fini;
4032 else if(!full_filename[0]){
4033 r = -1;
4034 goto fini;
4037 dprint((5, "Opening file \"%s\" for export\n",
4038 full_filename ? full_filename : "?"));
4040 if((store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
4041 char *pipe_err;
4042 gf_io_t pc, gc;
4044 gf_set_so_writec(&pc, store);
4045 gf_set_readc(&gc, srctext, (srctype == CharStar)
4046 ? strlen((char *)srctext)
4047 : 0L,
4048 srctype, 0);
4049 gf_filter_init();
4050 if((pipe_err = gf_pipe(gc, pc)) != NULL){
4051 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4052 /* TRANSLATORS: Problem saving to <filename>: <error text> */
4053 _("Problem saving to \"%s\": %s"),
4054 filename, pipe_err);
4055 r = -3;
4057 else
4058 r = 0;
4060 gf_clear_so_writec(store);
4061 if(so_give(&store)){
4062 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4063 _("Problem saving to \"%s\": %s"),
4064 filename, error_description(errno));
4065 r = -3;
4068 else{
4069 q_status_message2(SM_ORDER | SM_DING, 3, 4,
4070 _("Error opening file \"%s\" for export: %s"),
4071 full_filename, error_description(errno));
4072 r = -3;
4075 fini:
4076 switch(r){
4077 case 0:
4078 /* overloading full_filename */
4079 snprintf(full_filename, sizeof(full_filename), "%c%s",
4080 (prompt_msg && prompt_msg[0])
4081 ? (islower((unsigned char)prompt_msg[0])
4082 ? toupper((unsigned char)prompt_msg[0]) : prompt_msg[0])
4083 : 'T',
4084 (prompt_msg && prompt_msg[0]) ? prompt_msg+1 : "ext");
4085 full_filename[sizeof(full_filename)-1] = '\0';
4086 q_status_message3(SM_ORDER,0,2,"%s %s to \"%s\"",
4087 full_filename,
4088 rflags & GER_OVER
4089 ? "overwritten"
4090 : rflags & GER_APPEND ? "appended" : "exported",
4091 filename);
4092 break;
4094 case -1:
4095 cmd_cancelled("Export");
4096 break;
4098 case -2:
4099 q_status_message1(SM_ORDER, 0, 2,
4100 _("Can't export to file outside of %s"), VAR_OPER_DIR);
4101 break;
4104 ps->mangled_footer = 1;
4105 return(r);
4110 * Ask user what file to export to.
4112 * filename -- On input, this is the filename to start with. On exit,
4113 * this is the filename chosen. (but this isn't used)
4114 * deefault -- This is the default value if user hits return. The
4115 * prompt will have [deefault] added to it automatically.
4116 * full_filename -- This is the full filename on exit.
4117 * len -- Minimum length of _both_ filename and full_filename.
4118 * prompt_msg -- Message to insert in prompt.
4119 * lister_msg -- Message to insert in file_lister.
4120 * opts -- Key options.
4121 * There is a tangled relationship between the callers
4122 * and this routine as far as opts are concerned. Some
4123 * of the opts are handled here. In particular, r == 3,
4124 * r == 10, r == 11, and r == 13 are all handled here.
4125 * Don't use those values unless you want what happens
4126 * here. r == 12 and others are handled by the caller.
4127 * rflags -- Return flags
4128 * GER_OVER - overwrite of existing file
4129 * GER_APPEND - append of existing file
4130 * else file did not exist before
4132 * GER_ALLPARTS - AllParts toggle was turned on
4134 * qline -- Command line to prompt on.
4135 * flags -- Logically OR'd flags
4136 * GE_IS_EXPORT - The command was an Export command
4137 * so the prompt should include
4138 * EXPORT:.
4139 * GE_SEQ_SENSITIVE - The command that got us here is
4140 * sensitive to sequence number changes
4141 * caused by unsolicited expunges.
4142 * GE_NO_APPEND - We will not allow append to an
4143 * existing file, only removal of the
4144 * file if it exists.
4145 * GE_IS_IMPORT - We are selecting for reading.
4146 * No overwriting or checking for
4147 * existence at all. Don't use this
4148 * together with GE_NO_APPEND.
4149 * GE_ALLPARTS - Turn on AllParts toggle.
4150 * GE_BINARY - Turn on Binary toggle.
4152 * Returns: -1 cancelled
4153 * -2 prohibited by VAR_OPER_DIR
4154 * -3 other error, already reported here
4155 * 0 ok
4156 * 12 user chose 12 command from opts
4159 get_export_filename(struct pine *ps, char *filename, char *deefault,
4160 char *full_filename, size_t len, char *prompt_msg,
4161 char *lister_msg, ESCKEY_S *optsarg, int *rflags,
4162 int qline, int flags, HISTORY_S **history)
4164 char dir[MAXPATH+1], dir2[MAXPATH+1], orig_dir[MAXPATH+1];
4165 char precolon[MAXPATH+1], postcolon[MAXPATH+1];
4166 char filename2[MAXPATH+1], tmp[MAXPATH+1], *fn, *ill;
4167 int l, i, ku = -1, kp = -1, r, fatal, homedir = 0, was_abs_path=0, avail, ret = 0;
4168 int allparts = 0, binary = 0;
4169 char prompt_buf[400];
4170 char def[500];
4171 ESCKEY_S *opts = NULL;
4172 struct variable *vars = ps->vars;
4173 static HISTORY_S *dir_hist = NULL;
4174 static char *last;
4175 int pos, hist_len = 0;
4178 /* we will fake a history with the ps_global->VAR_HISTORY variable
4179 * We fake that we combine this variable into a history variable
4180 * by stacking VAR_HISTORY on top of dir_hist. We keep track of this
4181 * by looking at the variable pos.
4183 if(ps_global->VAR_HISTORY != NULL)
4184 for(hist_len = 0; ps_global->VAR_HISTORY[hist_len]
4185 && ps_global->VAR_HISTORY[hist_len][0]; hist_len++)
4188 pos = hist_len + items_in_hist(dir_hist);
4190 if(flags & GE_ALLPARTS || history || dir_hist){
4192 * Copy the opts and add one to the end of the list.
4194 for(i = 0; optsarg[i].ch != -1; i++)
4197 if(dir_hist || hist_len > 0)
4198 i += 2;
4200 if(history)
4201 i += dir_hist || hist_len > 0 ? 2 : 4;
4203 if(flags & GE_ALLPARTS)
4204 i++;
4206 if(flags & GE_BINARY)
4207 i++;
4209 opts = (ESCKEY_S *) fs_get((i+1) * sizeof(*opts));
4210 memset(opts, 0, (i+1) * sizeof(*opts));
4212 for(i = 0; optsarg[i].ch != -1; i++){
4213 opts[i].ch = optsarg[i].ch;
4214 opts[i].rval = optsarg[i].rval;
4215 opts[i].name = optsarg[i].name; /* no need to make a copy */
4216 opts[i].label = optsarg[i].label; /* " */
4219 if(flags & GE_ALLPARTS){
4220 allparts = i;
4221 opts[i].ch = ctrl('P');
4222 opts[i].rval = 13;
4223 opts[i].name = "^P";
4224 /* TRANSLATORS: Export all attachment parts */
4225 opts[i++].label = N_("AllParts");
4228 if(flags & GE_BINARY){
4229 binary = i;
4230 opts[i].ch = ctrl('R');
4231 opts[i].rval = 15;
4232 opts[i].name = "^R";
4233 opts[i++].label = N_("Binary");
4236 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4237 SIZEOF_20KBUF, filename);
4238 if(strcmp(tmp_20k_buf, filename)){
4239 opts[i].ch = ctrl('N');
4240 opts[i].rval = 40;
4241 opts[i].name = "^N";
4242 opts[i++].label = "Name UTF8";
4245 if(dir_hist || hist_len > 0){
4246 opts[i].ch = ctrl('Y');
4247 opts[i].rval = 32;
4248 opts[i].name = "";
4249 kp = i;
4250 opts[i++].label = "";
4252 opts[i].ch = ctrl('V');
4253 opts[i].rval = 33;
4254 opts[i].name = "";
4255 opts[i++].label = "";
4258 if(history){
4259 opts[i].ch = KEY_UP;
4260 opts[i].rval = 30;
4261 opts[i].name = "";
4262 ku = i;
4263 opts[i++].label = "";
4265 opts[i].ch = KEY_DOWN;
4266 opts[i].rval = 31;
4267 opts[i].name = "";
4268 opts[i++].label = "";
4271 opts[i].ch = -1;
4273 if(history)
4274 init_hist(history, HISTSIZE);
4275 init_hist(&dir_hist, HISTSIZE); /* reset history to the end */
4277 else
4278 opts = optsarg;
4280 if(rflags)
4281 *rflags = GER_NONE;
4283 if(F_ON(F_USE_CURRENT_DIR, ps))
4284 dir[0] = '\0';
4285 else if(VAR_OPER_DIR){
4286 strncpy(dir, VAR_OPER_DIR, sizeof(dir));
4287 dir[sizeof(dir)-1] = '\0';
4289 #if defined(DOS) || defined(OS2)
4290 else if(VAR_FILE_DIR){
4291 strncpy(dir, VAR_FILE_DIR, sizeof(dir));
4292 dir[sizeof(dir)-1] = '\0';
4294 #endif
4295 else{
4296 dir[0] = '~';
4297 dir[1] = '\0';
4298 homedir=1;
4300 strncpy(orig_dir, dir, sizeof(orig_dir));
4301 orig_dir[sizeof(orig_dir)-1] = '\0';
4303 postcolon[0] = '\0';
4304 strncpy(precolon, dir, sizeof(precolon));
4305 precolon[sizeof(precolon)-1] = '\0';
4306 if(deefault){
4307 strncpy(def, deefault, sizeof(def)-1);
4308 def[sizeof(def)-1] = '\0';
4309 removing_leading_and_trailing_white_space(def);
4311 else
4312 def[0] = '\0';
4314 avail = MAX(20, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - MIN_OPT_ENT_WIDTH;
4316 /*---------- Prompt the user for the file name -------------*/
4317 while(1){
4318 int oeflags;
4319 char dirb[50], fileb[50];
4320 int l1, l2, l3, l4, l5, needed;
4321 char *p, p1[100], p2[100], *p3, p4[100], p5[100];
4323 snprintf(p1, sizeof(p1), "%sCopy ",
4324 (flags & GE_IS_EXPORT) ? "EXPORT: " :
4325 (flags & GE_IS_IMPORT) ? "IMPORT: " : "SAVE: ");
4326 p1[sizeof(p1)-1] = '\0';
4327 l1 = strlen(p1);
4329 strncpy(p2, prompt_msg ? prompt_msg : "", sizeof(p2)-1);
4330 p2[sizeof(p2)-1] = '\0';
4331 l2 = strlen(p2);
4333 if(rflags && *rflags & GER_ALLPARTS)
4334 p3 = " (and atts)";
4335 else
4336 p3 = "";
4338 l3 = strlen(p3);
4340 snprintf(p4, sizeof(p4), " %s file%s%s",
4341 (flags & GE_IS_IMPORT) ? "from" : "to",
4342 is_absolute_path(filename) ? "" : " in ",
4343 is_absolute_path(filename) ? "" :
4344 (!dir[0] ? "current directory"
4345 : (dir[0] == '~' && !dir[1]) ? "home directory"
4346 : short_str(dir,dirb,sizeof(dirb),30,FrontDots)));
4347 p4[sizeof(p4)-1] = '\0';
4348 l4 = strlen(p4);
4350 snprintf(p5, sizeof(p5), "%s%s%s: ",
4351 *def ? " [" : "",
4352 *def ? short_str(def,fileb,sizeof(fileb),40,EndDots) : "",
4353 *def ? "]" : "");
4354 p5[sizeof(p5)-1] = '\0';
4355 l5 = strlen(p5);
4357 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4358 snprintf(p4, sizeof(p4), " %s file%s%s",
4359 (flags & GE_IS_IMPORT) ? "from" : "to",
4360 is_absolute_path(filename) ? "" : " in ",
4361 is_absolute_path(filename) ? "" :
4362 (!dir[0] ? "current dir"
4363 : (dir[0] == '~' && !dir[1]) ? "home dir"
4364 : short_str(dir,dirb,sizeof(dirb),10,FrontDots)));
4365 p4[sizeof(p4)-1] = '\0';
4366 l4 = strlen(p4);
4369 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4370 snprintf(p5, sizeof(p5), "%s%s%s: ",
4371 *def ? " [" : "",
4372 *def ? short_str(def,fileb,sizeof(fileb),
4373 MAX(15,l5-5-needed),EndDots) : "",
4374 *def ? "]" : "");
4375 p5[sizeof(p5)-1] = '\0';
4376 l5 = strlen(p5);
4379 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l2 > 0){
4382 * 14 is about the shortest we can make this, because there are
4383 * fixed length strings of length 14 coming in here.
4385 p = short_str(prompt_msg, p2, sizeof(p2), MAX(14,l2-needed), FrontDots);
4386 if(p != p2){
4387 strncpy(p2, p, sizeof(p2)-1);
4388 p2[sizeof(p2)-1] = '\0';
4391 l2 = strlen(p2);
4394 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4395 strncpy(p1, "Copy ", sizeof(p1)-1);
4396 p1[sizeof(p1)-1] = '\0';
4397 l1 = strlen(p1);
4400 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4401 snprintf(p5, sizeof(p5), "%s%s%s: ",
4402 *def ? " [" : "",
4403 *def ? short_str(def,fileb, sizeof(fileb),
4404 MAX(10,l5-5-needed),EndDots) : "",
4405 *def ? "]" : "");
4406 p5[sizeof(p5)-1] = '\0';
4407 l5 = strlen(p5);
4410 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l3 > 0){
4411 if(needed <= l3 - strlen(" (+ atts)"))
4412 p3 = " (+ atts)";
4413 else if(needed <= l3 - strlen(" (atts)"))
4414 p3 = " (atts)";
4415 else if(needed <= l3 - strlen(" (+)"))
4416 p3 = " (+)";
4417 else if(needed <= l3 - strlen("+"))
4418 p3 = "+";
4419 else
4420 p3 = "";
4422 l3 = strlen(p3);
4425 snprintf(prompt_buf, sizeof(prompt_buf), "%s%s%s%s%s", p1, p2, p3, p4, p5);
4426 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4428 if(kp >= 0){
4429 if(items_in_hist(dir_hist) > 0 || hist_len > 0){ /* any directories */
4430 opts[kp].name = "^Y";
4431 opts[kp].label = "Prev Dir";
4432 opts[kp+1].name = "^V";
4433 opts[kp+1].label = "Next Dir";
4435 else{
4436 opts[kp].name = "";
4437 opts[kp].label = "";
4438 opts[kp+1].name = "";
4439 opts[kp+1].label = "";
4443 if(ku >= 0){
4444 if(items_in_hist(*history) > 0){
4445 opts[ku].name = HISTORY_UP_KEYNAME;
4446 opts[ku].label = HISTORY_KEYLABEL;
4447 opts[ku+1].name = HISTORY_DOWN_KEYNAME;
4448 opts[ku+1].label = HISTORY_KEYLABEL;
4450 else{
4451 opts[ku].name = "";
4452 opts[ku].label = "";
4453 opts[ku+1].name = "";
4454 opts[ku+1].label = "";
4458 oeflags = OE_APPEND_CURRENT |
4459 ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0);
4460 r = optionally_enter(filename, qline, 0, len, prompt_buf,
4461 opts, NO_HELP, &oeflags);
4463 dprint((2, "\n - export_filename = \"%s\", r = %d -\n", filename, r));
4464 /*--- Help ----*/
4465 if(r == 3){
4467 * Helps may not be right if you add another caller or change
4468 * things. Check it out.
4470 if(flags & GE_IS_IMPORT)
4471 helper(h_ge_import, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE);
4472 else if(flags & GE_ALLPARTS)
4473 helper(h_ge_allparts, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4474 else
4475 helper(h_ge_export, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4477 ps->mangled_screen = 1;
4479 continue;
4481 else if(r == 10 || r == 11){ /* Browser or File Completion */
4482 if(filename[0]=='~'){
4483 if(filename[1] == C_FILESEP && filename[2]!='\0'){
4484 precolon[0] = '~';
4485 precolon[1] = '\0';
4486 for(i=0; filename[i+2] != '\0' && i+2 < len-1; i++)
4487 filename[i] = filename[i+2];
4488 filename[i] = '\0';
4489 strncpy(dir, precolon, sizeof(dir)-1);
4490 dir[sizeof(dir)-1] = '\0';
4492 else if(filename[1]=='\0' ||
4493 (filename[1] == C_FILESEP && filename[2] == '\0')){
4494 precolon[0] = '~';
4495 precolon[1] = '\0';
4496 filename[0] = '\0';
4497 strncpy(dir, precolon, sizeof(dir)-1);
4498 dir[sizeof(dir)-1] = '\0';
4501 else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){
4502 if(homedir){
4503 precolon[0] = '~';
4504 precolon[1] = '\0';
4505 strncpy(dir, precolon, sizeof(dir)-1);
4506 dir[sizeof(dir)-1] = '\0';
4508 else{
4509 precolon[0] = '\0';
4510 dir[0] = '\0';
4513 l = MAXPATH;
4514 dir2[0] = '\0';
4515 strncpy(tmp, filename, sizeof(tmp)-1);
4516 tmp[sizeof(tmp)-1] = '\0';
4517 if(*tmp && is_absolute_path(tmp))
4518 fnexpand(tmp, sizeof(tmp));
4519 if(strncmp(tmp,postcolon, strlen(postcolon)))
4520 postcolon[0] = '\0';
4522 if(*tmp && (fn = last_cmpnt(tmp))){
4523 l -= fn - tmp;
4524 strncpy(filename2, fn, sizeof(filename2)-1);
4525 filename2[sizeof(filename2)-1] = '\0';
4526 if(is_absolute_path(tmp)){
4527 strncpy(dir2, tmp, MIN(fn - tmp, sizeof(dir2)-1));
4528 dir2[MIN(fn - tmp, sizeof(dir2)-1)] = '\0';
4529 #ifdef _WINDOWS
4530 if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){
4531 dir2[2] = '\\';
4532 dir2[3] = '\0';
4534 #endif
4535 strncpy(postcolon, dir2, sizeof(postcolon)-1);
4536 postcolon[sizeof(postcolon)-1] = '\0';
4537 precolon[0] = '\0';
4539 else{
4540 char *p = NULL;
4542 * Just building the directory name in dir2,
4543 * full_filename is overloaded.
4545 snprintf(full_filename, len, "%.*s", (int) MIN(fn-tmp,len-1), tmp);
4546 full_filename[len-1] = '\0';
4547 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4548 postcolon[sizeof(postcolon)-1] = '\0';
4549 build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH)
4550 : (dir[0] == '~' && !dir[1])
4551 ? ps->home_dir
4552 : dir,
4553 full_filename, sizeof(dir2));
4554 if(p)
4555 free(p);
4558 else{
4559 if(is_absolute_path(tmp)){
4560 strncpy(dir2, tmp, sizeof(dir2)-1);
4561 dir2[sizeof(dir2)-1] = '\0';
4562 #ifdef _WINDOWS
4563 if(dir2[2]=='\0' && dir2[1]==':'){
4564 dir2[2]='\\';
4565 dir2[3]='\0';
4566 strncpy(postcolon,dir2,sizeof(postcolon)-1);
4567 postcolon[sizeof(postcolon)-1] = '\0';
4569 #endif
4570 filename2[0] = '\0';
4571 precolon[0] = '\0';
4573 else{
4574 strncpy(filename2, tmp, sizeof(filename2)-1);
4575 filename2[sizeof(filename2)-1] = '\0';
4576 if(!dir[0]){
4577 if(getcwd(dir2, sizeof(dir2)) == NULL)
4578 alpine_panic(_("getcwd() call failed at get_export_filename"));
4580 else if(dir[0] == '~' && !dir[1]){
4581 strncpy(dir2, ps->home_dir, sizeof(dir2)-1);
4582 dir2[sizeof(dir2)-1] = '\0';
4584 else{
4585 strncpy(dir2, dir, sizeof(dir2)-1);
4586 dir2[sizeof(dir2)-1] = '\0';
4589 postcolon[0] = '\0';
4593 build_path(full_filename, dir2, filename2, len);
4594 if(!strcmp(full_filename, dir2))
4595 filename2[0] = '\0';
4596 if(full_filename[strlen(full_filename)-1] == C_FILESEP
4597 && isdir(full_filename,NULL,NULL)){
4598 if(strlen(full_filename) == 1)
4599 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4600 else if(filename2[0])
4601 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4602 postcolon[sizeof(postcolon)-1] = '\0';
4603 strncpy(dir2, full_filename, sizeof(dir2)-1);
4604 dir2[sizeof(dir2)-1] = '\0';
4605 filename2[0] = '\0';
4607 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4608 else if(full_filename[strlen(full_filename)-1] == C_FILESEP){
4609 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4610 postcolon[sizeof(postcolon)-1] = '\0';
4611 strncpy(dir2, full_filename, sizeof(dir2)-1);
4612 dir2[sizeof(dir2)-1] = '\0';
4613 filename2[0] = '\0';
4615 #endif
4616 if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1
4617 && strcmp(dir2+1, ":\\"))
4618 /* last condition to prevent stripping of '\\'
4619 in windows partition */
4620 dir2[strlen(dir2)-1] = '\0';
4622 if(r == 10){ /* File Browser */
4623 r = file_lister(lister_msg ? lister_msg : "EXPORT",
4624 dir2, sizeof(dir2), filename2, sizeof(filename2),
4625 TRUE,
4626 (flags & GE_IS_IMPORT) ? FB_READ : FB_SAVE);
4627 #ifdef _WINDOWS
4628 /* Windows has a special "feature" in which entering the file browser will
4629 change the working directory if the directory is changed at all (even
4630 clicking "Cancel" will change the working directory).
4632 if(F_ON(F_USE_CURRENT_DIR, ps))
4633 (void)getcwd(dir2,sizeof(dir2));
4634 #endif
4635 if(isdir(dir2,NULL,NULL)){
4636 strncpy(precolon, dir2, sizeof(precolon)-1);
4637 precolon[sizeof(precolon)-1] = '\0';
4639 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4640 postcolon[sizeof(postcolon)-1] = '\0';
4641 if(r == 1){
4642 build_path(full_filename, dir2, filename2, len);
4643 if(isdir(full_filename, NULL, NULL)){
4644 strncpy(dir, full_filename, sizeof(dir)-1);
4645 dir[sizeof(dir)-1] = '\0';
4646 filename[0] = '\0';
4648 else{
4649 fn = last_cmpnt(full_filename);
4650 strncpy(dir, full_filename,
4651 MIN(fn - full_filename, sizeof(dir)-1));
4652 dir[MIN(fn - full_filename, sizeof(dir)-1)] = '\0';
4653 if(fn - full_filename > 1)
4654 dir[fn - full_filename - 1] = '\0';
4657 if(!strcmp(dir, ps->home_dir)){
4658 dir[0] = '~';
4659 dir[1] = '\0';
4662 strncpy(filename, fn, len-1);
4663 filename[len-1] = '\0';
4666 else{ /* File Completion */
4667 if(!pico_fncomplete(dir2, filename2, sizeof(filename2)))
4668 Writechar(BELL, 0);
4669 strncat(postcolon, filename2,
4670 sizeof(postcolon)-1-strlen(postcolon));
4671 postcolon[sizeof(postcolon)-1] = '\0';
4673 was_abs_path = is_absolute_path(filename);
4675 if(!strcmp(dir, ps->home_dir)){
4676 dir[0] = '~';
4677 dir[1] = '\0';
4680 strncpy(filename, postcolon, len-1);
4681 filename[len-1] = '\0';
4682 strncpy(dir, precolon, sizeof(dir)-1);
4683 dir[sizeof(dir)-1] = '\0';
4685 if(filename[0] == '~' && !filename[1]){
4686 dir[0] = '~';
4687 dir[1] = '\0';
4688 filename[0] = '\0';
4691 continue;
4693 else if(r == 12){ /* Download, caller handles it */
4694 ret = r;
4695 goto done;
4697 else if(r == 13){ /* toggle AllParts bit */
4698 if(rflags){
4699 if(*rflags & GER_ALLPARTS){
4700 *rflags &= ~GER_ALLPARTS;
4701 opts[allparts].label = N_("AllParts");
4703 else{
4704 *rflags |= GER_ALLPARTS;
4705 /* opposite of All Parts, No All Parts */
4706 opts[allparts].label = N_("NoAllParts");
4710 continue;
4712 #if 0
4713 else if(r == 14){ /* List file names matching partial? */
4714 continue;
4716 #endif
4717 else if(r == 15){ /* toggle Binary bit */
4718 if(rflags){
4719 if(*rflags & GER_BINARY){
4720 *rflags &= ~GER_BINARY;
4721 opts[binary].label = N_("Binary");
4723 else{
4724 *rflags |= GER_BINARY;
4725 opts[binary].label = N_("No Binary");
4729 continue;
4731 else if(r == 1){ /* Cancel */
4732 ret = -1;
4733 goto done;
4735 else if(r == 4){
4736 continue;
4738 else if(r >= 30 && r <= 33){
4739 char *p = NULL;
4741 if(r == 30 || r == 31){
4742 if(history){
4743 if(r == 30)
4744 p = get_prev_hist(*history, filename, 0, NULL);
4745 else if (r == 31)
4746 p = get_next_hist(*history, filename, 0, NULL);
4750 if(r == 32 || r == 33){
4751 int nitems = items_in_hist(dir_hist);
4752 if(dir_hist || hist_len > 0){
4753 if(r == 32){
4754 if(pos > 0)
4755 p = hist_in_pos(--pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4756 else p = last;
4758 else if (r == 33){
4759 if(pos < hist_len + nitems)
4760 p = hist_in_pos(++pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4762 if(p == NULL || *p == '\0')
4763 p = orig_dir;
4766 last = p; /* save it! */
4768 if(p != NULL && *p != '\0'){
4769 if(r == 30 || r == 31){
4770 if((fn = last_cmpnt(p)) != NULL){
4771 strncpy(dir, p, MIN(fn - p, sizeof(dir)-1));
4772 dir[MIN(fn - p, sizeof(dir)-1)] = '\0';
4773 if(fn - p > 1)
4774 dir[fn - p - 1] = '\0';
4775 strncpy(filename, fn, len-1);
4776 filename[len-1] = '\0';
4778 } else { /* r == 32 || r == 33 */
4779 strncpy(dir, p, sizeof(dir)-1);
4780 dir[sizeof(dir)-1] = '\0';
4783 if(!strcmp(dir, ps->home_dir)){
4784 dir[0] = '~';
4785 dir[1] = '\0';
4788 else
4789 Writechar(BELL, 0);
4790 continue;
4792 else if(r == 40){
4793 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4794 SIZEOF_20KBUF, filename);
4795 strncpy(filename, tmp_20k_buf, len);
4796 filename[len-1] = '\0';
4797 continue;
4799 else if(r != 0){
4800 Writechar(BELL, 0);
4801 continue;
4804 removing_leading_and_trailing_white_space(filename);
4806 if(!*filename){
4807 if(!*def){ /* Cancel */
4808 ret = -1;
4809 goto done;
4812 strncpy(filename, def, len-1);
4813 filename[len-1] = '\0';
4816 #if defined(DOS) || defined(OS2)
4817 if(is_absolute_path(filename)){
4818 fixpath(filename, len);
4820 #else
4821 if(filename[0] == '~'){
4822 if(fnexpand(filename, len) == NULL){
4823 char *p = strindex(filename, '/');
4824 if(p != NULL)
4825 *p = '\0';
4826 q_status_message1(SM_ORDER | SM_DING, 3, 3,
4827 _("Error expanding file name: \"%s\" unknown user"),
4828 filename);
4829 continue;
4832 #endif
4834 if(is_absolute_path(filename)){
4835 strncpy(full_filename, filename, len-1);
4836 full_filename[len-1] = '\0';
4838 else{
4839 if(!dir[0])
4840 build_path(full_filename, (char *)getcwd(dir,sizeof(dir)),
4841 filename, len);
4842 else if(dir[0] == '~' && !dir[1])
4843 build_path(full_filename, ps->home_dir, filename, len);
4844 else
4845 build_path(full_filename, dir, filename, len);
4848 if((ill = filter_filename(full_filename, &fatal,
4849 ps_global->restricted || ps_global->VAR_OPER_DIR)) != NULL){
4850 if(fatal){
4851 q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill);
4852 continue;
4854 else{
4855 /* BUG: we should beep when the key's pressed rather than bitch later */
4856 /* Warn and ask for confirmation. */
4857 snprintf(prompt_buf, sizeof(prompt_buf), "File name contains a '%s'. %s anyway",
4858 ill, (flags & GE_IS_EXPORT) ? "Export" : "Save");
4859 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4860 if(want_to(prompt_buf, 'n', 0, NO_HELP,
4861 ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y')
4862 continue;
4866 break; /* Must have got an OK file name */
4869 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){
4870 ret = -2;
4871 goto done;
4874 if(!can_access(full_filename, ACCESS_EXISTS)){
4875 int rbflags;
4876 static ESCKEY_S access_opts[] = {
4877 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4878 a file or append to the end of the file */
4879 {'o', 'o', "O", N_("Overwrite")},
4880 {'a', 'a', "A", N_("Append")},
4881 {-1, 0, NULL, NULL}};
4883 rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0);
4885 if(flags & GE_NO_APPEND){
4886 r = strlen(filename);
4887 snprintf(prompt_buf, sizeof(prompt_buf),
4888 /* TRANSLATORS: asking user whether to overwrite a file or not,
4889 File <filename> already exists. Overwrite it ? */
4890 _("File \"%s%s\" already exists. Overwrite it "),
4891 (r > 20) ? "..." : "",
4892 filename + ((r > 20) ? r - 20 : 0));
4893 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4894 if(want_to(prompt_buf, 'n', 'x', NO_HELP, rbflags) == 'y'){
4895 if(rflags)
4896 *rflags |= GER_OVER;
4898 if(our_unlink(full_filename) < 0){
4899 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4900 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4901 _("Cannot remove old %s: %s"),
4902 full_filename, error_description(errno));
4905 else{
4906 ret = -1;
4907 goto done;
4910 else if(!(flags & GE_IS_IMPORT)){
4911 r = strlen(filename);
4912 snprintf(prompt_buf, sizeof(prompt_buf),
4913 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4914 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4915 (r > 20) ? "..." : "",
4916 filename + ((r > 20) ? r - 20 : 0));
4917 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4918 switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global),
4919 access_opts, 'a', 'x', NO_HELP, rbflags)){
4920 case 'o' :
4921 if(rflags)
4922 *rflags |= GER_OVER;
4924 if(our_truncate(full_filename, (off_t)0) < 0)
4925 /* trouble truncating, but we'll give it a try anyway */
4926 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4927 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
4928 _("Warning: Cannot truncate old %s: %s"),
4929 full_filename, error_description(errno));
4930 break;
4932 case 'a' :
4933 if(rflags)
4934 *rflags |= GER_APPEND;
4936 break;
4938 case 'x' :
4939 default :
4940 ret = -1;
4941 goto done;
4946 done:
4947 if(history && ret == 0){
4948 save_hist(*history, full_filename, 0, NULL);
4949 strncpy(tmp, full_filename, MAXPATH);
4950 tmp[MAXPATH] = '\0';
4951 if((fn = strrchr(tmp, C_FILESEP)) != NULL)
4952 *fn = '\0';
4953 else
4954 tmp[0] = '\0';
4955 if(tmp[0])
4956 save_hist(dir_hist, tmp, 0, NULL);
4959 if(opts && opts != optsarg)
4960 fs_give((void **) &opts);
4962 return(ret);
4966 /*----------------------------------------------------------------------
4967 parse the config'd upload/download command
4969 Args: cmd -- buffer to return command fit for shellin'
4970 prefix --
4971 cfg_str --
4972 fname -- file name to build into the command
4974 Returns: pointer to cmd_str buffer or NULL on real bad error
4976 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
4977 cfg_str is written to standard out right before a successful
4978 return of this function. The call immediately following this
4979 function darn well better be the shell exec...
4980 ----*/
4981 char *
4982 build_updown_cmd(char *cmd, size_t cmdlen, char *prefix, char *cfg_str, char *fname)
4984 char *p;
4985 int fname_found = 0;
4987 if(prefix && *prefix){
4988 /* loop thru replacing all occurances of _FILE_ */
4989 p = strncpy(cmd, prefix, cmdlen);
4990 cmd[cmdlen-1] = '\0';
4991 while((p = strstr(p, "_FILE_")))
4992 rplstr(p, cmdlen-(p-cmd), 6, fname);
4994 fputs(cmd, stdout);
4997 /* loop thru replacing all occurances of _FILE_ */
4998 p = strncpy(cmd, cfg_str, cmdlen);
4999 cmd[cmdlen-1] = '\0';
5000 while((p = strstr(p, "_FILE_"))){
5001 rplstr(p, cmdlen-(p-cmd), 6, fname);
5002 fname_found = 1;
5005 if(!fname_found)
5006 snprintf(cmd+strlen(cmd), cmdlen-strlen(cmd), " %s", fname);
5008 cmd[cmdlen-1] = '\0';
5010 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
5011 cmd ? cmd : "?"));
5012 return(cmd);
5016 /*----------------------------------------------------------------------
5017 Write a berzerk format message delimiter using the given putc function
5019 Args: e -- envelope of message to write
5020 pc -- function to use
5022 Returns: TRUE if we could write it, FALSE if there was a problem
5024 NOTE: follows delimiter with OS-dependent newline
5025 ----*/
5027 bezerk_delimiter(ENVELOPE *env, MESSAGECACHE *mc, gf_io_t pc, int leading_newline)
5029 MESSAGECACHE telt;
5030 time_t when;
5031 char *p;
5033 /* write "[\n]From mailbox[@host] " */
5034 if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1)
5035 && gf_puts("From ", pc)
5036 && gf_puts((env && env->from) ? env->from->mailbox
5037 : "the-concourse-on-high", pc)
5038 && gf_puts((env && env->from && env->from->host) ? "@" : "", pc)
5039 && gf_puts((env && env->from && env->from->host) ? env->from->host
5040 : "", pc)
5041 && (*pc)(' ')))
5042 return(0);
5044 if(mc && mc->valid)
5045 when = mail_longdate(mc);
5046 else if(env && env->date && env->date[0]
5047 && mail_parse_date(&telt,env->date))
5048 when = mail_longdate(&telt);
5049 else
5050 when = time(0);
5052 p = ctime(&when);
5054 while(p && *p && *p != '\n') /* write date */
5055 if(!(*pc)(*p++))
5056 return(0);
5058 if(!gf_puts(NEWLINE, pc)) /* write terminating newline */
5059 return(0);
5061 return(1);
5065 /*----------------------------------------------------------------------
5066 Execute command to jump to a given message number
5068 Args: qline -- Line to ask question on
5070 Result: returns true if the use selected a new message, false otherwise
5072 ----*/
5073 long
5074 jump_to(MSGNO_S *msgmap, int qline, UCS first_num, SCROLL_S *sparms, CmdWhere in_index)
5076 char jump_num_string[80], *j, prompt[70];
5077 HelpType help;
5078 int rc;
5079 static ESCKEY_S jump_to_key[] = { {0, 0, NULL, NULL},
5080 /* TRANSLATORS: go to First Message */
5081 {ctrl('Y'), 10, "^Y", N_("First Msg")},
5082 {ctrl('V'), 11, "^V", N_("Last Msg")},
5083 {-1, 0, NULL, NULL} };
5085 dprint((4, "\n - jump_to -\n"));
5087 #ifdef DEBUG
5088 if(sparms && sparms->jump_is_debug)
5089 return(get_level(qline, first_num, sparms));
5090 #endif
5092 if(!any_messages(msgmap, NULL, "to Jump to"))
5093 return(0L);
5095 if(first_num && first_num < 0x80 && isdigit((unsigned char) first_num)){
5096 jump_num_string[0] = first_num;
5097 jump_num_string[1] = '\0';
5099 else
5100 jump_num_string[0] = '\0';
5102 if(mn_total_cur(msgmap) > 1L){
5103 snprintf(prompt, sizeof(prompt), "Unselect %s msgs in favor of number to be entered",
5104 comatose(mn_total_cur(msgmap)));
5105 prompt[sizeof(prompt)-1] = '\0';
5106 if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n')
5107 return(0L);
5110 snprintf(prompt, sizeof(prompt), "%s number to jump to : ", in_index == ThrdIndx
5111 ? "Thread"
5112 : "Message");
5113 prompt[sizeof(prompt)-1] = '\0';
5115 help = NO_HELP;
5116 while(1){
5117 int flags = OE_APPEND_CURRENT;
5119 rc = optionally_enter(jump_num_string, qline, 0,
5120 sizeof(jump_num_string), prompt,
5121 jump_to_key, help, &flags);
5122 if(rc == 3){
5123 help = help == NO_HELP
5124 ? (in_index == ThrdIndx ? h_oe_jump_thd : h_oe_jump)
5125 : NO_HELP;
5126 continue;
5128 else if(rc == 10 || rc == 11){
5129 char warning[100];
5130 long closest;
5132 closest = closest_jump_target(rc == 10 ? 1L
5133 : ((in_index == ThrdIndx)
5134 ? msgmap->max_thrdno
5135 : mn_get_total(msgmap)),
5136 ps_global->mail_stream,
5137 msgmap, 0,
5138 in_index, warning, sizeof(warning));
5139 /* ignore warning */
5140 return(closest);
5144 * If we take out the *jump_num_string nonempty test in this if
5145 * then the closest_jump_target routine will offer a jump to the
5146 * last message. However, it is slow because you have to wait for
5147 * the status message and it is annoying for people who hit J command
5148 * by mistake and just want to hit return to do nothing, like has
5149 * always worked. So the test is there for now. Hubert 2002-08-19
5151 * Jumping to first/last message is now possible through ^Y/^V
5152 * commands above. jpf 2002-08-21
5153 * (and through "end" hubert 2006-07-07)
5155 if(rc == 0 && *jump_num_string != '\0'){
5156 removing_leading_and_trailing_white_space(jump_num_string);
5157 for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++)
5160 if(*j != '\0'){
5161 if(!strucmp("end", j))
5162 return((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap));
5164 q_status_message(SM_ORDER | SM_DING, 2, 2,
5165 _("Invalid number entered. Use only digits 0-9"));
5166 jump_num_string[0] = '\0';
5168 else{
5169 char warning[100];
5170 long closest, jump_num;
5172 if(*jump_num_string)
5173 jump_num = atol(jump_num_string);
5174 else
5175 jump_num = -1L;
5177 warning[0] = '\0';
5178 closest = closest_jump_target(jump_num, ps_global->mail_stream,
5179 msgmap,
5180 *jump_num_string ? 0 : 1,
5181 in_index, warning, sizeof(warning));
5182 if(warning[0])
5183 q_status_message(SM_ORDER | SM_DING, 2, 2, warning);
5185 if(closest == jump_num)
5186 return(jump_num);
5188 if(closest == 0L)
5189 jump_num_string[0] = '\0';
5190 else
5191 strncpy(jump_num_string, long2string(closest),
5192 sizeof(jump_num_string));
5195 continue;
5198 if(rc != 4)
5199 break;
5202 return(0L);
5207 * cmd_delete_action - handle msgno advance and such after single message deletion
5209 char *
5210 cmd_delete_action(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
5212 int opts;
5213 long msgno;
5214 char *rv = NULL;
5216 msgno = mn_get_cur(msgmap);
5217 advance_cur_after_delete(state, state->mail_stream, msgmap, in_index);
5219 if(IS_NEWS(state->mail_stream)
5220 || ((state->context_current->use & CNTXT_INCMNG)
5221 && context_isambig(state->cur_folder))){
5223 opts = (NSF_TRUST_FLAGS | NSF_SKIP_CHID);
5224 if(in_index == View)
5225 opts &= ~NSF_SKIP_CHID;
5227 (void)next_sorted_flagged(F_UNDEL|F_UNSEEN, state->mail_stream, msgno, &opts);
5228 if(!(opts & NSF_FLAG_MATCH)){
5229 char nextfolder[MAXPATH];
5231 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
5232 nextfolder[sizeof(nextfolder)-1] = '\0';
5233 rv = next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
5234 state->context_current, NULL, NULL)
5235 ? ". Press TAB for next folder."
5236 : ". No more folders to TAB to.";
5240 return(rv);
5245 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5247 char *
5248 cmd_delete_index(struct pine *state, MSGNO_S *msgmap)
5250 return(cmd_delete_action(state, msgmap,MsgIndx));
5254 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5256 char *
5257 cmd_delete_view(struct pine *state, MSGNO_S *msgmap)
5259 return(cmd_delete_action(state, msgmap, View));
5263 void
5264 advance_cur_after_delete(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, CmdWhere in_index)
5266 long new_msgno, msgno;
5267 int opts;
5269 new_msgno = msgno = mn_get_cur(msgmap);
5270 opts = NSF_TRUST_FLAGS;
5272 if(F_ON(F_DEL_SKIPS_DEL, state)){
5274 if(THREADING() && sp_viewing_a_thread(stream))
5275 opts |= NSF_SKIP_CHID;
5277 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5279 else{
5280 mn_inc_cur(stream, msgmap,
5281 (in_index == View && THREADING()
5282 && sp_viewing_a_thread(stream))
5283 ? MH_THISTHD
5284 : (in_index == View)
5285 ? MH_ANYTHD : MH_NONE);
5286 new_msgno = mn_get_cur(msgmap);
5287 if(new_msgno != msgno)
5288 opts |= NSF_FLAG_MATCH;
5292 * Viewing_a_thread is the complicated case because we want to ignore
5293 * other threads at first and then look in other threads if we have to.
5294 * By ignoring other threads we also ignore collapsed partial threads
5295 * in our own thread.
5297 if(THREADING() && sp_viewing_a_thread(stream) && !(opts & NSF_FLAG_MATCH)){
5298 long rawno, orig_thrdno;
5299 PINETHRD_S *thrd, *topthrd = NULL;
5301 rawno = mn_m2raw(msgmap, msgno);
5302 thrd = fetch_thread(stream, rawno);
5303 if(thrd && thrd->top)
5304 topthrd = fetch_thread(stream, thrd->top);
5306 orig_thrdno = topthrd ? topthrd->thrdno : -1L;
5308 opts = NSF_TRUST_FLAGS;
5309 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5312 * If we got a match, new_msgno may be a message in
5313 * a different thread from the one we are viewing, or it could be
5314 * in a collapsed part of this thread.
5316 if(opts & NSF_FLAG_MATCH){
5317 int ret;
5318 char pmt[128];
5320 topthrd = NULL;
5321 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
5322 if(thrd && thrd->top)
5323 topthrd = fetch_thread(stream, thrd->top);
5326 * If this match is in the same thread we're already in
5327 * then we're done, else we have to ask the user and maybe
5328 * switch threads.
5330 if(!(orig_thrdno > 0L && topthrd
5331 && topthrd->thrdno == orig_thrdno)){
5333 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
5334 if(in_index == View)
5335 snprintf(pmt, sizeof(pmt),
5336 "View message in thread number %.10s",
5337 topthrd ? comatose(topthrd->thrdno) : "?");
5338 else
5339 snprintf(pmt, sizeof(pmt), "View thread number %.10s",
5340 topthrd ? comatose(topthrd->thrdno) : "?");
5342 ret = want_to(pmt, 'y', 'x', NO_HELP, WT_NORM);
5344 else
5345 ret = 'y';
5347 if(ret == 'y'){
5348 unview_thread(state, stream, msgmap);
5349 mn_set_cur(msgmap, new_msgno);
5350 if(THRD_AUTO_VIEW()
5351 && (count_lflags_in_thread(stream, topthrd, msgmap,
5352 MN_NONE) == 1)
5353 && view_thread(state, stream, msgmap, 1)){
5354 if(current_index_state)
5355 msgmap->top_after_thrd = current_index_state->msg_at_top;
5357 state->view_skipped_index = 1;
5358 state->next_screen = mail_view_screen;
5360 else{
5361 view_thread(state, stream, msgmap, 1);
5362 if(current_index_state)
5363 msgmap->top_after_thrd = current_index_state->msg_at_top;
5365 state->next_screen = SCREEN_FUN_NULL;
5368 else
5369 new_msgno = msgno; /* stick with original */
5374 mn_set_cur(msgmap, new_msgno);
5375 if(in_index != View)
5376 adjust_cur_to_visible(stream, msgmap);
5380 #ifdef DEBUG
5381 long
5382 get_level(int qline, UCS first_num, SCROLL_S *sparms)
5384 char debug_num_string[80], *j, prompt[70];
5385 HelpType help;
5386 int rc;
5387 long debug_num;
5389 if(first_num && first_num < 0x80 && isdigit((unsigned char)first_num)){
5390 debug_num_string[0] = first_num;
5391 debug_num_string[1] = '\0';
5392 debug_num = atol(debug_num_string);
5393 *(int *)(sparms->proc.data.p) = debug_num;
5394 q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s",
5395 comatose(debug_num));
5396 return(1L);
5398 else
5399 debug_num_string[0] = '\0';
5401 snprintf(prompt, sizeof(prompt), "Show debug <= this level (0-%d) : ", MAX(debug, 9));
5402 prompt[sizeof(prompt)-1] = '\0';
5404 help = NO_HELP;
5405 while(1){
5406 int flags = OE_APPEND_CURRENT;
5408 rc = optionally_enter(debug_num_string, qline, 0,
5409 sizeof(debug_num_string), prompt,
5410 NULL, help, &flags);
5411 if(rc == 3){
5412 help = help == NO_HELP ? h_oe_debuglevel : NO_HELP;
5413 continue;
5416 if(rc == 0){
5417 removing_leading_and_trailing_white_space(debug_num_string);
5418 for(j=debug_num_string; isdigit((unsigned char)*j); j++)
5421 if(*j != '\0'){
5422 q_status_message(SM_ORDER | SM_DING, 2, 2,
5423 _("Invalid number entered. Use only digits 0-9"));
5424 debug_num_string[0] = '\0';
5426 else{
5427 debug_num = atol(debug_num_string);
5428 if(debug_num < 0)
5429 q_status_message(SM_ORDER | SM_DING, 2, 2,
5430 _("Number should be >= 0"));
5431 else if(debug_num > MAX(debug,9))
5432 q_status_message1(SM_ORDER | SM_DING, 2, 2,
5433 _("Maximum is %s"), comatose(MAX(debug,9)));
5434 else{
5435 *(int *)(sparms->proc.data.p) = debug_num;
5436 q_status_message1(SM_ORDER, 0, 3,
5437 "Show debug <= level %s",
5438 comatose(debug_num));
5439 return(1L);
5443 continue;
5446 if(rc != 4)
5447 break;
5450 return(0L);
5452 #endif /* DEBUG */
5456 * Returns the message number closest to target that isn't hidden.
5457 * Make warning at least 100 chars.
5458 * A return of 0 means there is no message to jump to.
5460 long
5461 closest_jump_target(long int target, MAILSTREAM *stream, MSGNO_S *msgmap, int no_target, CmdWhere in_index, char *warning, size_t warninglen)
5463 long i, start, closest = 0L;
5464 char buf[80];
5465 long maxnum;
5467 warning[0] = '\0';
5468 maxnum = (in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap);
5470 if(no_target){
5471 target = maxnum;
5472 start = 1L;
5473 snprintf(warning, warninglen, "No %s number entered, jump to end? ",
5474 (in_index == ThrdIndx) ? "thread" : "message");
5475 warning[warninglen-1] = '\0';
5477 else if(target < 1L)
5478 start = 1L - target;
5479 else if(target > maxnum)
5480 start = target - maxnum;
5481 else
5482 start = 1L;
5484 if(target > 0L && target <= maxnum)
5485 if(in_index == ThrdIndx
5486 || !msgline_hidden(stream, msgmap, target, 0))
5487 return(target);
5489 for(i = start; target+i <= maxnum || target-i > 0L; i++){
5491 if(target+i > 0L && target+i <= maxnum &&
5492 (in_index == ThrdIndx
5493 || !msgline_hidden(stream, msgmap, target+i, 0))){
5494 closest = target+i;
5495 break;
5498 if(target-i > 0L && target-i <= maxnum &&
5499 (in_index == ThrdIndx
5500 || !msgline_hidden(stream, msgmap, target-i, 0))){
5501 closest = target-i;
5502 break;
5506 strncpy(buf, long2string(closest), sizeof(buf));
5507 buf[sizeof(buf)-1] = '\0';
5509 if(closest == 0L)
5510 strncpy(warning, "Nothing to jump to", warninglen);
5511 else if(target < 1L)
5512 snprintf(warning, warninglen, "%s number (%s) must be at least %s",
5513 (in_index == ThrdIndx) ? "Thread" : "Message",
5514 long2string(target), buf);
5515 else if(target > maxnum)
5516 snprintf(warning, warninglen, "%s number (%s) may be no more than %s",
5517 (in_index == ThrdIndx) ? "Thread" : "Message",
5518 long2string(target), buf);
5519 else if(!no_target)
5520 snprintf(warning, warninglen,
5521 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5522 long2string(target), buf);
5524 warning[warninglen-1] = '\0';
5526 return(closest);
5530 /*----------------------------------------------------------------------
5531 Prompt for folder name to open, expand the name and return it
5533 Args: qline -- Screen line to prompt on
5534 allow_list -- if 1, allow ^T to bring up collection lister
5536 Result: returns the folder name or NULL
5537 pine structure mangled_footer flag is set
5538 may call the collection lister in which case mangled screen will be set
5540 This prompts the user for the folder to open, possibly calling up
5541 the collection lister if the user types ^T.
5542 ----------------------------------------------------------------------*/
5543 char *
5544 broach_folder(int qline, int allow_list, int *notrealinbox, CONTEXT_S **context)
5546 HelpType help;
5547 static char newfolder[MAILTMPLEN];
5548 char expanded[MAXPATH+1],
5549 prompt[MAX_SCREEN_COLS+1],
5550 *last_folder, *p;
5551 unsigned char *f1, *f2, *f3;
5552 static HISTORY_S *history = NULL;
5553 CONTEXT_S *tc, *tc2;
5554 ESCKEY_S ekey[9];
5555 int rc, r, ku = -1, n, flags, last_rc = 0, inbox, done = 0;
5558 * the idea is to provide a clue for the context the file name
5559 * will be saved in (if a non-imap names is typed), and to
5560 * only show the previous if it was also in the same context
5562 help = NO_HELP;
5563 *expanded = '\0';
5564 *newfolder = '\0';
5565 last_folder = NULL;
5566 if(notrealinbox)
5567 (*notrealinbox) = 1;
5569 init_hist(&history, HISTSIZE);
5571 tc = broach_get_folder(context ? *context : NULL, &inbox, NULL);
5573 /* set up extra command option keys */
5574 rc = 0;
5575 ekey[rc].ch = (allow_list) ? ctrl('T') : 0 ;
5576 ekey[rc].rval = (allow_list) ? 2 : 0;
5577 ekey[rc].name = (allow_list) ? "^T" : "";
5578 ekey[rc++].label = (allow_list) ? N_("ToFldrs") : "";
5580 if(ps_global->context_list->next){
5581 ekey[rc].ch = ctrl('P');
5582 ekey[rc].rval = 10;
5583 ekey[rc].name = "^P";
5584 ekey[rc++].label = N_("Prev Collection");
5586 ekey[rc].ch = ctrl('N');
5587 ekey[rc].rval = 11;
5588 ekey[rc].name = "^N";
5589 ekey[rc++].label = N_("Next Collection");
5592 ekey[rc].ch = ctrl('W');
5593 ekey[rc].rval = 17;
5594 ekey[rc].name = "^W";
5595 ekey[rc++].label = N_("INBOX");
5597 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
5598 ekey[rc].ch = TAB;
5599 ekey[rc].rval = 12;
5600 ekey[rc].name = "TAB";
5601 ekey[rc++].label = N_("Complete");
5604 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
5605 ekey[rc].ch = ctrl('X');
5606 ekey[rc].rval = 14;
5607 ekey[rc].name = "^X";
5608 ekey[rc++].label = N_("ListMatches");
5611 if(ps_global->context_list->next && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5612 ekey[rc].ch = KEY_UP;
5613 ekey[rc].rval = 10;
5614 ekey[rc].name = "";
5615 ekey[rc++].label = "";
5617 ekey[rc].ch = KEY_DOWN;
5618 ekey[rc].rval = 11;
5619 ekey[rc].name = "";
5620 ekey[rc++].label = "";
5622 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5623 ekey[rc].ch = KEY_UP;
5624 ekey[rc].rval = 30;
5625 ekey[rc].name = "";
5626 ku = rc;
5627 ekey[rc++].label = "";
5629 ekey[rc].ch = KEY_DOWN;
5630 ekey[rc].rval = 31;
5631 ekey[rc].name = "";
5632 ekey[rc++].label = "";
5635 ekey[rc].ch = -1;
5637 while(!done) {
5639 * Figure out next default value for this context. The idea
5640 * is that in each context the last folder opened is cached.
5641 * It's up to pick it out and display it. This is fine
5642 * and dandy if we've currently got the inbox open, BUT
5643 * if not, make the inbox the default the first time thru.
5645 if(!inbox){
5646 last_folder = ps_global->inbox_name;
5647 inbox = 1; /* pretend we're in inbox from here on out */
5649 else
5650 last_folder = (ps_global->last_unambig_folder[0])
5651 ? ps_global->last_unambig_folder
5652 : ((tc->last_folder[0]) ? tc->last_folder : NULL);
5654 if(last_folder){ /* MAXPATH + 1 = sizeof(expanded) */
5655 unsigned char *fname = folder_name_decoded((unsigned char *)last_folder);
5656 snprintf(expanded, sizeof(expanded), " [%.*s]", MAXPATH+1-5,
5657 fname ? (char *) fname : last_folder);
5658 if(fname) fs_give((void **)&fname);
5660 else
5661 *expanded = '\0';
5663 expanded[sizeof(expanded)-1] = '\0';
5665 /* only show collection number if more than one available */
5666 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5667 snprintf(prompt, sizeof(prompt), "GOTO %s in <%s> %.*s%s: ",
5668 NEWS_TEST(tc) ? "news group" : "folder",
5669 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5670 *expanded ? " " : "");
5671 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5672 snprintf(prompt, sizeof(prompt), "GOTO folder %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5673 *expanded ? " " : "");
5675 prompt[sizeof(prompt)-1] = '\0';
5677 if(utf8_width(prompt) > MAXPROMPT){
5678 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5679 snprintf(prompt, sizeof(prompt), "GOTO <%s> %.*s%s: ",
5680 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5681 *expanded ? " " : "");
5682 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5683 snprintf(prompt, sizeof(prompt), "GOTO %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5684 *expanded ? " " : "");
5686 prompt[sizeof(prompt)-1] = '\0';
5688 if(utf8_width(prompt) > MAXPROMPT){
5689 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5690 snprintf(prompt, sizeof(prompt), "<%s> %.*s%s: ",
5691 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5692 *expanded ? " " : "");
5693 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5694 snprintf(prompt, sizeof(prompt), "%.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5695 *expanded ? " " : "");
5697 prompt[sizeof(prompt)-1] = '\0';
5701 if(ku >= 0){
5702 if(items_in_hist(history) > 1){
5703 ekey[ku].name = HISTORY_UP_KEYNAME;
5704 ekey[ku].label = HISTORY_KEYLABEL;
5705 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
5706 ekey[ku+1].label = HISTORY_KEYLABEL;
5708 else{
5709 ekey[ku].name = "";
5710 ekey[ku].label = "";
5711 ekey[ku+1].name = "";
5712 ekey[ku+1].label = "";
5716 /* is there any other way to do this? The point is that we
5717 * are trying to hide mutf7 from the user, and use the utf8
5718 * equivalent. So we create a variable f to take place of
5719 * newfolder, including content and size. f2 is copy of f1
5720 * that has to freed. Sigh!
5722 f3 = (unsigned char *) cpystr(newfolder);
5723 f1 = fs_get(sizeof(newfolder));
5724 f2 = folder_name_decoded(f3);
5725 if(f3) fs_give((void **)&f3);
5726 strncpy((char *)f1, (char *)f2, sizeof(newfolder));
5727 f1[sizeof(newfolder)-1] = '\0';
5728 if(f2) fs_give((void **)&f2);
5730 flags = OE_APPEND_CURRENT;
5731 rc = optionally_enter((char *) f1, qline, 0, sizeof(newfolder),
5732 (char *) prompt, ekey, help, &flags);
5734 f2 = folder_name_encoded(f1);
5735 strncpy(newfolder, (char *)f2, sizeof(newfolder));
5736 if(f1) fs_give((void **)&f1);
5737 if(f2) fs_give((void **)&f2);
5739 ps_global->mangled_footer = 1;
5741 switch(rc){
5742 case -1 : /* o_e says error! */
5743 q_status_message(SM_ORDER | SM_DING, 3, 3,
5744 _("Error reading folder name"));
5745 return(NULL);
5747 case 0 : /* o_e says normal entry */
5748 removing_trailing_white_space(newfolder);
5749 removing_leading_white_space(newfolder);
5751 if(*newfolder){
5752 char *name, *fullname = NULL;
5753 int exists, breakout = 0;
5755 save_hist(history, newfolder, 0, tc);
5757 if(!(name = folder_is_nick(newfolder, FOLDERS(tc),
5758 FN_WHOLE_NAME)))
5759 name = newfolder;
5761 if(update_folder_spec(expanded, sizeof(expanded), name)){
5762 strncpy(name = newfolder, expanded, sizeof(newfolder));
5763 newfolder[sizeof(newfolder)-1] = '\0';
5766 exists = folder_name_exists(tc, name, &fullname);
5768 if(fullname){
5769 strncpy(name = newfolder, fullname, sizeof(newfolder));
5770 newfolder[sizeof(newfolder)-1] = '\0';
5771 fs_give((void **) &fullname);
5772 breakout = TRUE;
5776 * if we know the things a folder, open it.
5777 * else if we know its a directory, visit it.
5778 * else we're not sure (it either doesn't really
5779 * exist or its unLISTable) so try opening it anyway
5781 if(exists & FEX_ISFILE){
5782 done++;
5783 break;
5785 else if((exists & FEX_ISDIR)){
5786 if(breakout){
5787 CONTEXT_S *fake_context;
5788 char tmp[MAILTMPLEN];
5789 size_t l;
5791 strncpy(tmp, name, sizeof(tmp));
5792 tmp[sizeof(tmp)-2-1] = '\0';
5793 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
5794 if(l < sizeof(tmp)){
5795 tmp[l] = tc->dir->delim;
5796 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
5799 else
5800 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
5802 tmp[sizeof(tmp)-1] = '\0';
5804 fake_context = new_context(tmp, 0);
5805 newfolder[0] = '\0';
5806 done = display_folder_list(&fake_context, newfolder,
5807 1, folders_for_goto);
5808 free_context(&fake_context);
5809 break;
5811 else if(!(tc->use & CNTXT_INCMNG)){
5812 done = display_folder_list(&tc, newfolder,
5813 1, folders_for_goto);
5814 break;
5817 else if((exists & FEX_ERROR)){
5818 q_status_message1(SM_ORDER, 0, 3,
5819 _("Problem accessing folder \"%s\""),
5820 newfolder);
5821 return(NULL);
5823 else{
5824 done++;
5825 break;
5828 if(exists == FEX_ERROR)
5829 q_status_message1(SM_ORDER, 0, 3,
5830 _("Problem accessing folder \"%s\""),
5831 newfolder);
5832 else if(tc->use & CNTXT_INCMNG)
5833 q_status_message1(SM_ORDER, 0, 3,
5834 _("Can't find Incoming Folder: %s"),
5835 newfolder);
5836 else if(context_isambig(newfolder))
5837 q_status_message2(SM_ORDER, 0, 3,
5838 _("Can't find folder \"%s\" in %s"),
5839 newfolder, (void *) tc->nickname);
5840 else
5841 q_status_message1(SM_ORDER, 0, 3,
5842 _("Can't find folder \"%s\""),
5843 newfolder);
5845 return(NULL);
5847 else if(last_folder){
5848 if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX
5849 && !strucmp(last_folder, ps_global->inbox_name)
5850 && tc == ((ps_global->context_list->use & CNTXT_INCMNG)
5851 ? ps_global->context_list->next : ps_global->context_list)){
5852 if(notrealinbox)
5853 (*notrealinbox) = 0;
5855 tc = ps_global->context_list;
5858 strncpy(newfolder, last_folder, sizeof(newfolder));
5859 newfolder[sizeof(newfolder)-1] = '\0';
5860 save_hist(history, newfolder, 0, tc);
5861 done++;
5862 break;
5864 /* fall thru like they cancelled */
5866 case 1 : /* o_e says user cancel */
5867 cmd_cancelled("Open folder");
5868 return(NULL);
5870 case 2 : /* o_e says user wants list */
5871 r = display_folder_list(&tc, newfolder, 0, folders_for_goto);
5872 if(r)
5873 done++;
5875 break;
5877 case 3 : /* o_e says user wants help */
5878 help = help == NO_HELP ? h_oe_broach : NO_HELP;
5879 break;
5881 case 4 : /* redraw */
5882 break;
5884 case 10 : /* Previous collection */
5885 tc2 = ps_global->context_list;
5886 while(tc2->next && tc2->next != tc)
5887 tc2 = tc2->next;
5889 tc = tc2;
5890 break;
5892 case 11 : /* Next collection */
5893 tc = (tc->next) ? tc->next : ps_global->context_list;
5894 break;
5896 case 12 : /* file name completion */
5897 if(!folder_complete(tc, newfolder, sizeof(newfolder), &n)){
5898 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
5899 r = display_folder_list(&tc, newfolder, 1,folders_for_goto);
5900 if(r)
5901 done++; /* bingo! */
5902 else
5903 rc = 0; /* burn last_rc */
5905 else
5906 Writechar(BELL, 0);
5909 break;
5911 case 14 : /* file name completion */
5912 r = display_folder_list(&tc, newfolder, 2, folders_for_goto);
5913 if(r)
5914 done++; /* bingo! */
5915 else
5916 rc = 0; /* burn last_rc */
5918 break;
5920 case 17 : /* GoTo INBOX */
5921 done++;
5922 strncpy(newfolder, ps_global->inbox_name, sizeof(newfolder)-1);
5923 newfolder[sizeof(newfolder)-1] = '\0';
5924 if(notrealinbox)
5925 (*notrealinbox) = 0;
5927 tc = ps_global->context_list;
5928 save_hist(history, newfolder, 0, tc);
5930 break;
5932 case 30 :
5933 if((p = get_prev_hist(history, newfolder, 0, tc)) != NULL){
5934 strncpy(newfolder, p, sizeof(newfolder));
5935 newfolder[sizeof(newfolder)-1] = '\0';
5936 if(history->hist[history->curindex])
5937 tc = history->hist[history->curindex]->cntxt;
5939 else
5940 Writechar(BELL, 0);
5942 break;
5944 case 31 :
5945 if((p = get_next_hist(history, newfolder, 0, tc)) != NULL){
5946 strncpy(newfolder, p, sizeof(newfolder));
5947 newfolder[sizeof(newfolder)-1] = '\0';
5948 if(history->hist[history->curindex])
5949 tc = history->hist[history->curindex]->cntxt;
5951 else
5952 Writechar(BELL, 0);
5954 break;
5956 default :
5957 alpine_panic("Unhandled case");
5958 break;
5961 last_rc = rc;
5964 dprint((2, "broach folder, name entered \"%s\"\n",
5965 newfolder ? newfolder : "?"));
5967 /*-- Just check that we can expand this. It gets done for real later --*/
5968 strncpy(expanded, newfolder, sizeof(expanded));
5969 expanded[sizeof(expanded)-1] = '\0';
5971 if(!expand_foldername(expanded, sizeof(expanded))) {
5972 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
5973 expanded ? expanded : "?"));
5974 return(NULL);
5977 *context = tc;
5978 return(newfolder);
5982 /*----------------------------------------------------------------------
5983 Check to see if user wants to reopen dead stream.
5985 Args: ps --
5986 reopenp --
5988 Result: 1 if the folder was successfully updatedn
5989 0 if not necessary
5991 ----*/
5993 ask_mailbox_reopen(struct pine *ps, int *reopenp)
5995 if(((ps->mail_stream->dtb
5996 && ((ps->mail_stream->dtb->flags & DR_NONEWMAIL)
5997 || (ps->mail_stream->rdonly
5998 && ps->mail_stream->dtb->flags & DR_NONEWMAILRONLY)))
5999 && (ps->reopen_rule == REOPEN_ASK_ASK_Y
6000 || ps->reopen_rule == REOPEN_ASK_ASK_N
6001 || ps->reopen_rule == REOPEN_ASK_NO_Y
6002 || ps->reopen_rule == REOPEN_ASK_NO_N))
6003 || ((ps->mail_stream->dtb
6004 && ps->mail_stream->rdonly
6005 && !(ps->mail_stream->dtb->flags & DR_LOCAL))
6006 && (ps->reopen_rule == REOPEN_YES_ASK_Y
6007 || ps->reopen_rule == REOPEN_YES_ASK_N
6008 || ps->reopen_rule == REOPEN_ASK_ASK_Y
6009 || ps->reopen_rule == REOPEN_ASK_ASK_N))){
6010 int deefault;
6012 switch(ps->reopen_rule){
6013 case REOPEN_YES_ASK_Y:
6014 case REOPEN_ASK_ASK_Y:
6015 case REOPEN_ASK_NO_Y:
6016 deefault = 'y';
6017 break;
6019 default:
6020 deefault = 'n';
6021 break;
6024 switch(want_to("Re-open folder to check for new messages", deefault,
6025 'x', h_reopen_folder, WT_NORM)){
6026 case 'y':
6027 (*reopenp)++;
6028 break;
6030 case 'x':
6031 return(-1);
6035 return(0);
6040 /*----------------------------------------------------------------------
6041 Check to see if user input is in form of old c-client mailbox speck
6043 Args: old --
6044 new --
6046 Result: 1 if the folder was successfully updatedn
6047 0 if not necessary
6049 ----*/
6051 update_folder_spec(char *new, size_t newlen, char *old)
6053 char *p, *orignew;
6054 int nntp = 0;
6056 orignew = new;
6057 if(*(p = old) == '*') /* old form? */
6058 old++;
6060 if(*old == '{') /* copy host spec */
6062 switch(*new = *old++){
6063 case '\0' :
6064 return(FALSE);
6066 case '/' :
6067 if(!struncmp(old, "nntp", 4))
6068 nntp++;
6070 break;
6072 default :
6073 break;
6075 while(*new++ != '}' && (new-orignew) < newlen-1);
6077 if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){
6079 * OK, some heuristics here. If it looks like a newsgroup
6080 * then we plunk it into the #news namespace else we
6081 * assume that they're trying to get at a #public folder...
6083 for(p = old;
6084 *p && (isalnum((unsigned char) *p) || strindex(".-", *p));
6085 p++)
6088 sstrncpy(&new, (*p && !nntp) ? "#public/" : "#news.", newlen-(new-orignew));
6089 strncpy(new, old, newlen-(new-orignew));
6090 return(TRUE);
6093 orignew[newlen-1] = '\0';
6095 return(FALSE);
6099 /*----------------------------------------------------------------------
6100 Open the requested folder in the requested context
6102 Args: state -- usual pine state struct
6103 newfolder -- folder to open
6104 new_context -- folder context might live in
6105 stream -- candidate for recycling
6107 Result: New folder open or not (if error), and we're set to
6108 enter the index screen.
6109 ----*/
6110 void
6111 visit_folder(struct pine *state, char *newfolder, CONTEXT_S *new_context,
6112 MAILSTREAM *stream, long unsigned int flags)
6114 dprint((9, "visit_folder(%s, %s)\n",
6115 newfolder ? newfolder : "?",
6116 (new_context && new_context->context)
6117 ? new_context->context : "(NULL)"));
6119 if(ps_global && ps_global->ttyo){
6120 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
6121 ps_global->mangled_footer = 1;
6124 if(do_broach_folder(newfolder, new_context, stream ? &stream : NULL,
6125 flags) >= 0
6126 || !sp_flagged(state->mail_stream, SP_LOCKED))
6127 state->next_screen = mail_index_screen;
6128 else
6129 state->next_screen = folder_screen;
6133 /*----------------------------------------------------------------------
6134 Move read messages from folder if listed in archive
6136 Args:
6138 ----*/
6140 read_msg_prompt(long int n, char *f)
6142 char buf[MAX_SCREEN_COLS+1];
6144 snprintf(buf, sizeof(buf), "Save the %ld read message%s in \"%s\"", n, plural(n), f);
6145 buf[sizeof(buf)-1] = '\0';
6146 return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y');
6150 /*----------------------------------------------------------------------
6151 Print current message[s] or folder index
6153 Args: state -- pointer to struct holding a bunch of pine state
6154 msgmap -- table mapping msg nums to c-client sequence nums
6155 aopt -- aggregate options
6156 in_index -- boolean indicating we're called from Index Screen
6158 Filters the original header and sends stuff to printer
6159 ---*/
6161 cmd_print(struct pine *state, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
6163 char prompt[250];
6164 long i, msgs, rawno;
6165 int next = 0, do_index = 0, rv = 0;
6166 ENVELOPE *e;
6167 BODY *b;
6168 MESSAGECACHE *mc;
6170 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
6171 return rv;
6173 msgs = mn_total_cur(msgmap);
6175 if((in_index != View) && F_ON(F_PRINT_INDEX, state)){
6176 char m[10];
6177 int ans;
6178 static ESCKEY_S prt_opts[] = {
6179 {'i', 'i', "I", N_("Index")},
6180 {'m', 'm', "M", NULL},
6181 {-1, 0, NULL, NULL}};
6183 if(in_index == ThrdIndx){
6184 /* TRANSLATORS: This is a question, Print Index ? */
6185 if(want_to(_("Print Index"), 'y', 'x', NO_HELP, WT_NORM) == 'y')
6186 ans = 'i';
6187 else
6188 ans = 'x';
6190 else{
6191 snprintf(m, sizeof(m), "Message%s", (msgs>1L) ? "s" : "");
6192 m[sizeof(m)-1] = '\0';
6193 prt_opts[1].label = m;
6194 snprintf(prompt, sizeof(prompt), "Print %sFolder Index or %s %s? ",
6195 (aopt & MCMD_AGG_2) ? "thread " : MCMD_ISAGG(aopt) ? "selected " : "",
6196 (aopt & MCMD_AGG_2) ? "thread" : MCMD_ISAGG(aopt) ? "selected" : "current", m);
6197 prompt[sizeof(prompt)-1] = '\0';
6199 ans = radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x',
6200 NO_HELP, RB_NORM|RB_SEQ_SENSITIVE);
6203 switch(ans){
6204 case 'x' :
6205 cmd_cancelled("Print");
6206 if(MCMD_ISAGG(aopt))
6207 restore_selected(msgmap);
6209 return rv;
6211 case 'i':
6212 do_index = 1;
6213 break;
6215 default :
6216 case 'm':
6217 break;
6221 if(do_index)
6222 snprintf(prompt, sizeof(prompt), "%sFolder Index",
6223 (aopt & MCMD_AGG_2) ? "Thread " : MCMD_ISAGG(aopt) ? "Selected " : "");
6224 else if(msgs > 1L)
6225 snprintf(prompt, sizeof(prompt), "%s messages", long2string(msgs));
6226 else
6227 snprintf(prompt, sizeof(prompt), "Message %s", long2string(mn_get_cur(msgmap)));
6229 prompt[sizeof(prompt)-1] = '\0';
6231 if(open_printer(prompt) < 0){
6232 if(MCMD_ISAGG(aopt))
6233 restore_selected(msgmap);
6235 return rv;
6238 if(do_index){
6239 TITLE_S *tc;
6241 tc = format_titlebar();
6243 /* Print titlebar... */
6244 print_text1("%s\n\n", tc ? tc->titlebar_line : "");
6245 /* then all the index members... */
6246 if(!print_index(state, msgmap, MCMD_ISAGG(aopt)))
6247 q_status_message(SM_ORDER | SM_DING, 3, 3,
6248 _("Error printing folder index"));
6249 else
6250 rv++;
6252 else{
6253 rv++;
6254 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){
6255 if(next && F_ON(F_AGG_PRINT_FF, state))
6256 if(!print_char(FORMFEED)){
6257 rv = 0;
6258 break;
6261 if(!(state->mail_stream
6262 && (rawno = mn_m2raw(msgmap, i)) > 0L
6263 && rawno <= state->mail_stream->nmsgs
6264 && (mc = mail_elt(state->mail_stream, rawno))
6265 && mc->valid))
6266 mc = NULL;
6268 if(!(e=pine_mail_fetchstructure(state->mail_stream,
6269 mn_m2raw(msgmap,i),
6270 &b))
6271 || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global)
6272 && !bezerk_delimiter(e, mc, print_char, next))
6273 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
6274 e, b, NULL, FM_NEW_MESS | FM_NOINDENT,
6275 print_char)){
6276 q_status_message(SM_ORDER | SM_DING, 3, 3,
6277 _("Error printing message"));
6278 rv = 0;
6279 break;
6284 close_printer();
6286 if(MCMD_ISAGG(aopt))
6287 restore_selected(msgmap);
6289 return rv;
6293 /*----------------------------------------------------------------------
6294 Pipe message text
6296 Args: state -- various pine state bits
6297 msgmap -- Message number mapping table
6298 aopt -- option flags
6300 Filters the original header and sends stuff to specified command
6301 ---*/
6303 cmd_pipe(struct pine *state, MSGNO_S *msgmap, int aopt)
6305 ENVELOPE *e;
6306 MESSAGECACHE *mc;
6307 BODY *b;
6308 PIPE_S *syspipe;
6309 char *resultfilename = NULL, prompt[80], *p;
6310 int done = 0, rv = 0;
6311 gf_io_t pc;
6312 int fourlabel = -1, j = 0, next = 0, ku;
6313 int pipe_rv; /* rv of proc to separate from close_system_pipe rv */
6314 long i, rawno;
6315 unsigned flagsforhist = 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6316 static HISTORY_S *history = NULL;
6317 int capture = 1, raw = 0, delimit = 0, newpipe = 0;
6318 char pipe_command[MAXPATH];
6319 ESCKEY_S pipe_opt[8];
6321 if(ps_global->restricted){
6322 q_status_message(SM_ORDER | SM_DING, 0, 4,
6323 "Alpine demo can't pipe messages");
6324 return rv;
6326 else if(!any_messages(msgmap, NULL, "to Pipe"))
6327 return rv;
6329 pipe_command[0] = '\0';
6330 init_hist(&history, HISTSIZE);
6331 flagsforhist = (raw ? 0x8 : 0) +
6332 (delimit ? 0x4 : 0) +
6333 (newpipe ? 0x2 : 0) +
6334 (capture ? 0x1 : 0);
6335 if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){
6336 strncpy(pipe_command, p, sizeof(pipe_command));
6337 pipe_command[sizeof(pipe_command)-1] = '\0';
6338 if(history->hist[history->curindex]){
6339 flagsforhist = history->hist[history->curindex]->flags;
6340 raw = (flagsforhist & 0x8) ? 1 : 0;
6341 delimit = (flagsforhist & 0x4) ? 1 : 0;
6342 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6343 capture = (flagsforhist & 0x1) ? 1 : 0;
6347 pipe_opt[j].ch = 0;
6348 pipe_opt[j].rval = 0;
6349 pipe_opt[j].name = "";
6350 pipe_opt[j++].label = "";
6352 pipe_opt[j].ch = ctrl('W');
6353 pipe_opt[j].rval = 10;
6354 pipe_opt[j].name = "^W";
6355 pipe_opt[j++].label = NULL;
6357 pipe_opt[j].ch = ctrl('Y');
6358 pipe_opt[j].rval = 11;
6359 pipe_opt[j].name = "^Y";
6360 pipe_opt[j++].label = NULL;
6362 pipe_opt[j].ch = ctrl('R');
6363 pipe_opt[j].rval = 12;
6364 pipe_opt[j].name = "^R";
6365 pipe_opt[j++].label = NULL;
6367 if(MCMD_ISAGG(aopt)){
6368 if(!pseudo_selected(state->mail_stream, msgmap))
6369 return rv;
6370 else{
6371 fourlabel = j;
6372 pipe_opt[j].ch = ctrl('T');
6373 pipe_opt[j].rval = 13;
6374 pipe_opt[j].name = "^T";
6375 pipe_opt[j++].label = NULL;
6379 pipe_opt[j].ch = KEY_UP;
6380 pipe_opt[j].rval = 30;
6381 pipe_opt[j].name = "";
6382 ku = j;
6383 pipe_opt[j++].label = "";
6385 pipe_opt[j].ch = KEY_DOWN;
6386 pipe_opt[j].rval = 31;
6387 pipe_opt[j].name = "";
6388 pipe_opt[j++].label = "";
6390 pipe_opt[j].ch = -1;
6392 while (!done) {
6393 int flags;
6395 snprintf(prompt, sizeof(prompt), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6396 raw ? "RAW " : "",
6397 MCMD_ISAGG(aopt) ? "s" : " ",
6398 MCMD_ISAGG(aopt) ? "" : comatose(mn_get_cur(msgmap)),
6399 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? "(" : "",
6400 capture ? "" : "uncaptured",
6401 (!capture && delimit) ? "," : "",
6402 delimit ? "delimited" : "",
6403 ((!capture || delimit) && newpipe && MCMD_ISAGG(aopt)) ? "," : "",
6404 (newpipe && MCMD_ISAGG(aopt)) ? "new pipe" : "",
6405 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? ") " : "");
6406 prompt[sizeof(prompt)-1] = '\0';
6407 pipe_opt[1].label = raw ? N_("Shown Text") : N_("Raw Text");
6408 pipe_opt[2].label = capture ? N_("Free Output") : N_("Capture Output");
6409 pipe_opt[3].label = delimit ? N_("No Delimiter") : N_("With Delimiter");
6410 if(fourlabel > 0)
6411 pipe_opt[fourlabel].label = newpipe ? N_("To Same Pipe") : N_("To Individual Pipes");
6415 * 2 is really 1 because there will be one real entry and
6416 * one entry of "" because of the get_prev_hist above.
6418 if(items_in_hist(history) > 2){
6419 pipe_opt[ku].name = HISTORY_UP_KEYNAME;
6420 pipe_opt[ku].label = HISTORY_KEYLABEL;
6421 pipe_opt[ku+1].name = HISTORY_DOWN_KEYNAME;
6422 pipe_opt[ku+1].label = HISTORY_KEYLABEL;
6424 else{
6425 pipe_opt[ku].name = "";
6426 pipe_opt[ku].label = "";
6427 pipe_opt[ku+1].name = "";
6428 pipe_opt[ku+1].label = "";
6431 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
6432 switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0,
6433 sizeof(pipe_command), prompt,
6434 pipe_opt, NO_HELP, &flags)){
6435 case -1 :
6436 q_status_message(SM_ORDER | SM_DING, 3, 4,
6437 _("Internal problem encountered"));
6438 done++;
6439 break;
6441 case 10 : /* flip raw bit */
6442 raw = !raw;
6443 break;
6445 case 11 : /* flip capture bit */
6446 capture = !capture;
6447 break;
6449 case 12 : /* flip delimit bit */
6450 delimit = !delimit;
6451 break;
6453 case 13 : /* flip newpipe bit */
6454 newpipe = !newpipe;
6455 break;
6457 case 30 :
6458 flagsforhist = (raw ? 0x8 : 0) +
6459 (delimit ? 0x4 : 0) +
6460 (newpipe ? 0x2 : 0) +
6461 (capture ? 0x1 : 0);
6462 if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6463 strncpy(pipe_command, p, sizeof(pipe_command));
6464 pipe_command[sizeof(pipe_command)-1] = '\0';
6465 if(history->hist[history->curindex]){
6466 flagsforhist = history->hist[history->curindex]->flags;
6467 raw = (flagsforhist & 0x8) ? 1 : 0;
6468 delimit = (flagsforhist & 0x4) ? 1 : 0;
6469 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6470 capture = (flagsforhist & 0x1) ? 1 : 0;
6473 else
6474 Writechar(BELL, 0);
6476 break;
6478 case 31 :
6479 flagsforhist = (raw ? 0x8 : 0) +
6480 (delimit ? 0x4 : 0) +
6481 (newpipe ? 0x2 : 0) +
6482 (capture ? 0x1 : 0);
6483 if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6484 strncpy(pipe_command, p, sizeof(pipe_command));
6485 pipe_command[sizeof(pipe_command)-1] = '\0';
6486 if(history->hist[history->curindex]){
6487 flagsforhist = history->hist[history->curindex]->flags;
6488 raw = (flagsforhist & 0x8) ? 1 : 0;
6489 delimit = (flagsforhist & 0x4) ? 1 : 0;
6490 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6491 capture = (flagsforhist & 0x1) ? 1 : 0;
6494 else
6495 Writechar(BELL, 0);
6497 break;
6499 case 0 :
6500 if(pipe_command[0]){
6502 flagsforhist = (raw ? 0x8 : 0) +
6503 (delimit ? 0x4 : 0) +
6504 (newpipe ? 0x2 : 0) +
6505 (capture ? 0x1 : 0);
6506 save_hist(history, pipe_command, flagsforhist, NULL);
6508 flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
6509 flags |= (raw ? PIPE_RAW : 0);
6510 if(!capture){
6511 #ifndef _WINDOWS
6512 ClearScreen();
6513 fflush(stdout);
6514 clear_cursor_pos();
6515 ps_global->mangled_screen = 1;
6516 ps_global->in_init_seq = 1;
6517 #endif
6518 flags |= PIPE_RESET;
6521 if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command,
6522 (flags & PIPE_RESET)
6523 ? NULL
6524 : &resultfilename,
6525 flags, &pc)))
6526 done++;
6528 for(i = mn_first_cur(msgmap);
6529 i > 0L && !done;
6530 i = mn_next_cur(msgmap)){
6531 e = pine_mail_fetchstructure(ps_global->mail_stream,
6532 mn_m2raw(msgmap, i), &b);
6533 if(!(state->mail_stream
6534 && (rawno = mn_m2raw(msgmap, i)) > 0L
6535 && rawno <= state->mail_stream->nmsgs
6536 && (mc = mail_elt(state->mail_stream, rawno))
6537 && mc->valid))
6538 mc = NULL;
6540 if((newpipe
6541 && !(syspipe = cmd_pipe_open(pipe_command,
6542 (flags & PIPE_RESET)
6543 ? NULL
6544 : &resultfilename,
6545 flags, &pc)))
6546 || (delimit && !bezerk_delimiter(e, mc, pc, next++)))
6547 done++;
6549 if(!done){
6550 if(raw){
6551 char *pipe_err;
6553 prime_raw_pipe_getc(ps_global->mail_stream,
6554 mn_m2raw(msgmap, i), -1L, 0L);
6555 gf_filter_init();
6556 gf_link_filter(gf_nvtnl_local, NULL);
6557 if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){
6558 q_status_message1(SM_ORDER|SM_DING,
6559 3, 3,
6560 _("Internal Error: %s"),
6561 pipe_err);
6562 done++;
6565 else if(!format_message(mn_m2raw(msgmap, i), e, b,
6566 NULL, FM_NEW_MESS | FM_NOWRAP, pc))
6567 done++;
6570 if(newpipe)
6571 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6572 done++;
6575 if(!capture)
6576 ps_global->in_init_seq = 0;
6578 if(!newpipe)
6579 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6580 done++;
6581 if(done) /* say we had a problem */
6582 q_status_message(SM_ORDER | SM_DING, 3, 3,
6583 _("Error piping message"));
6584 else if(resultfilename){
6585 rv++;
6586 /* only display if no error */
6587 display_output_file(resultfilename, "PIPE MESSAGE",
6588 NULL, DOF_EMPTY);
6589 fs_give((void **)&resultfilename);
6591 else{
6592 rv++;
6593 q_status_message(SM_ORDER, 0, 2, _("Pipe command completed"));
6596 done++;
6597 break;
6599 /* else fall thru as if cancelled */
6601 case 1 :
6602 cmd_cancelled("Pipe command");
6603 done++;
6604 break;
6606 case 3 :
6607 helper(h_common_pipe, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE);
6608 ps_global->mangled_screen = 1;
6609 break;
6611 case 2 : /* no place to escape to */
6612 case 4 : /* can't suspend */
6613 default :
6614 break;
6618 ps_global->mangled_footer = 1;
6619 if(MCMD_ISAGG(aopt))
6620 restore_selected(msgmap);
6622 return rv;
6626 /*----------------------------------------------------------------------
6627 Screen to offer list management commands contained in message
6629 Args: state -- pointer to struct holding a bunch of pine state
6630 msgmap -- table mapping msg nums to c-client sequence nums
6631 aopt -- aggregate options
6633 Result:
6635 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6636 ----*/
6637 void
6638 rfc2369_display(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno)
6640 int winner = 0;
6641 char *h, *hdrs[MLCMD_COUNT + 1];
6642 long index_no = mn_raw2m(msgmap, msgno);
6643 RFC2369_S data[MLCMD_COUNT];
6645 /* for each header field */
6646 if((h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))) != NULL){
6647 memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT);
6648 if(rfc2369_parse_fields(h, &data[0])){
6649 STORE_S *explain;
6651 if((explain = list_mgmt_text(data, index_no)) != NULL){
6652 list_mgmt_screen(explain);
6653 ps_global->mangled_screen = 1;
6654 so_give(&explain);
6655 winner++;
6659 fs_give((void **) &h);
6662 if(!winner)
6663 q_status_message1(SM_ORDER, 0, 3,
6664 "Message %s contains no list management information",
6665 comatose(index_no));
6669 STORE_S *
6670 list_mgmt_text(RFC2369_S *data, long int msgno)
6672 STORE_S *store;
6673 int i, j, n, fields = 0;
6674 static char *rfc2369_intro1 =
6675 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6676 static char *rfc2369_intro2[] = {
6677 N_(" has information associated with it "),
6678 N_("that explains how to participate in an email list. An "),
6679 N_("email list is represented by a single email address that "),
6680 N_("users sharing a common interest can send messages to (known "),
6681 N_("as posting) which are then redistributed to all members "),
6682 N_("of the list (sometimes after review by a moderator)."),
6683 N_("<P>List participation commands in this message include:"),
6684 NULL
6687 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6689 /* Insert introductory text */
6690 so_puts(store, rfc2369_intro1);
6692 so_puts(store, comatose(msgno));
6694 for(i = 0; rfc2369_intro2[i]; i++)
6695 so_puts(store, _(rfc2369_intro2[i]));
6697 so_puts(store, "<P>");
6698 for(i = 0; i < MLCMD_COUNT; i++)
6699 if(data[i].data[0].value
6700 || data[i].data[0].comment
6701 || data[i].data[0].error){
6702 if(!fields++)
6703 so_puts(store, "<UL>");
6705 so_puts(store, "<LI>");
6706 so_puts(store,
6707 (n = (data[i].data[1].value || data[i].data[1].comment))
6708 ? "Methods to "
6709 : "A method to ");
6711 so_puts(store, data[i].field.description);
6712 so_puts(store, ". ");
6714 if(n)
6715 so_puts(store, "<OL>");
6717 for(j = 0;
6718 j < MLCMD_MAXDATA
6719 && (data[i].data[j].comment
6720 || data[i].data[j].value
6721 || data[i].data[j].error);
6722 j++){
6724 so_puts(store, n ? "<P><LI>" : "<P>");
6726 if(data[i].data[j].comment){
6727 so_puts(store,
6728 _("With the provided comment:<P><BLOCKQUOTE>"));
6729 so_puts(store, data[i].data[j].comment);
6730 so_puts(store, "</BLOCKQUOTE><P>");
6733 if(data[i].data[j].value){
6734 if(i == MLCMD_POST
6735 && !strucmp(data[i].data[j].value, "NO")){
6736 so_puts(store,
6737 _("Posting is <EM>not</EM> allowed on this list"));
6739 else{
6740 so_puts(store, "Select <A HREF=\"");
6741 so_puts(store, data[i].data[j].value);
6742 so_puts(store, "\">HERE</A> to ");
6743 so_puts(store, (data[i].field.action)
6744 ? data[i].field.action
6745 : "try it");
6748 so_puts(store, ".");
6751 if(data[i].data[j].error){
6752 so_puts(store, "<P>Unfortunately, Alpine can not offer");
6753 so_puts(store, " to take direct action based upon it");
6754 so_puts(store, " because it was improperly formatted.");
6755 so_puts(store, " The unrecognized data associated with");
6756 so_puts(store, " the \"");
6757 so_puts(store, data[i].field.name);
6758 so_puts(store, "\" header field was:<P><BLOCKQUOTE>");
6759 so_puts(store, data[i].data[j].error);
6760 so_puts(store, "</BLOCKQUOTE>");
6763 so_puts(store, "<P>");
6766 if(n)
6767 so_puts(store, "</OL>");
6770 if(fields)
6771 so_puts(store, "</UL>");
6773 so_puts(store, "</BODY></HTML>");
6776 return(store);
6780 void
6781 list_mgmt_screen(STORE_S *html)
6783 int cmd = MC_NONE;
6784 long offset = 0L;
6785 char *error = NULL;
6786 STORE_S *store;
6787 HANDLE_S *handles = NULL;
6788 gf_io_t gc, pc;
6791 so_seek(html, 0L, 0);
6792 gf_set_so_readc(&gc, html);
6794 init_handles(&handles);
6796 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6797 gf_set_so_writec(&pc, store);
6798 gf_filter_init();
6800 gf_link_filter(gf_html2plain,
6801 gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols,
6802 non_messageview_margin(), &handles, NULL, 0));
6804 error = gf_pipe(gc, pc);
6806 gf_clear_so_writec(store);
6808 if(!error){
6809 SCROLL_S sargs;
6811 memset(&sargs, 0, sizeof(SCROLL_S));
6812 sargs.text.text = so_text(store);
6813 sargs.text.src = CharStar;
6814 sargs.text.desc = "list commands";
6815 sargs.text.handles = handles;
6816 if(offset){
6817 sargs.start.on = Offset;
6818 sargs.start.loc.offset = offset;
6821 sargs.bar.title = _("MAIL LIST COMMANDS");
6822 sargs.bar.style = MessageNumber;
6823 sargs.resize_exit = 1;
6824 sargs.help.text = h_special_list_commands;
6825 sargs.help.title = _("HELP FOR LIST COMMANDS");
6826 sargs.keys.menu = &listmgr_keymenu;
6827 setbitmap(sargs.keys.bitmap);
6828 if(!handles){
6829 clrbitn(LM_TRY_KEY, sargs.keys.bitmap);
6830 clrbitn(LM_PREV_KEY, sargs.keys.bitmap);
6831 clrbitn(LM_NEXT_KEY, sargs.keys.bitmap);
6834 cmd = scrolltool(&sargs);
6835 offset = sargs.start.loc.offset;
6838 so_give(&store);
6841 free_handles(&handles);
6842 gf_clear_so_readc(html);
6844 while(cmd == MC_RESIZE);
6848 /*----------------------------------------------------------------------
6849 Prompt the user for the type of select desired
6851 NOTE: any and all functions that successfully exit the second
6852 switch() statement below (currently "select_*() functions"),
6853 *MUST* update the folder's MESSAGECACHE element's "searched"
6854 bits to reflect the search result. Functions using
6855 mail_search() get this for free, the others must update 'em
6856 by hand.
6858 Returns -1 if canceled without changing selection
6859 0 if selection may have changed
6860 ----*/
6862 aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index)
6864 long i, diff, old_tot, msgno, raw;
6865 int q = 0, rv = 0, narrow = 0, hidden, ret = -1;
6866 ESCKEY_S *sel_opts;
6867 MESSAGECACHE *mc;
6868 SEARCHSET *limitsrch = NULL;
6869 PINETHRD_S *thrd;
6870 extern MAILSTREAM *mm_search_stream;
6871 extern long mm_search_count;
6873 hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
6874 mm_search_stream = state->mail_stream;
6875 mm_search_count = 0L;
6877 sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2;
6878 if(THREADING()){
6879 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH;
6881 else{
6882 sel_opts[SEL_OPTS_THREAD].ch = -1;
6885 if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){
6886 if(THRD_INDX()){
6887 i = 0;
6888 thrd = fetch_thread(state->mail_stream,
6889 mn_m2raw(msgmap, mn_get_cur(msgmap)));
6890 /* check if whole thread is selected or not */
6891 if(thrd &&
6892 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT)
6894 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE))
6895 i = 1;
6897 sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd");
6899 else{
6900 i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap),
6901 MN_SLCT);
6902 sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur");
6905 sel_opts += 2; /* disable extra options */
6906 switch(q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6907 RB_NORM)){
6908 case 'f' : /* flip selection */
6909 msgno = 0L;
6910 for(i = 1L; i <= mn_get_total(msgmap); i++){
6911 ret = 0;
6912 q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT);
6913 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q);
6914 if(hidden){
6915 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q);
6916 if(!msgno && q)
6917 mn_reset_cur(msgmap, msgno = i);
6921 return(ret);
6923 case 'n' : /* narrow selection */
6924 narrow++;
6925 case 'b' : /* broaden selection */
6926 q = 0; /* offer criteria prompt */
6927 break;
6929 case 'c' : /* Un/Select Current */
6930 case 'a' : /* Unselect All */
6931 case 'x' : /* cancel */
6932 break;
6934 default :
6935 q_status_message(SM_ORDER | SM_DING, 3, 3,
6936 "Unsupported Select option");
6937 return(ret);
6941 if(!q){
6942 while(1){
6943 q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x',
6944 NO_HELP, RB_NORM|RB_RET_HELP);
6946 if(q == 3){
6947 helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE);
6948 ps_global->mangled_screen = 1;
6950 else
6951 break;
6956 * The purpose of this is to add the appropriate searchset to the
6957 * search so that the search can be limited to only looking at what
6958 * it needs to look at. That is, if we are narrowing then we only need
6959 * to look at messages which are already selected, and if we are
6960 * broadening, then we only need to look at messages which are not
6961 * yet selected. This routine will work whether or not
6962 * limiting_searchset properly limits the search set. In particular,
6963 * the searchset returned by limiting_searchset may include messages
6964 * which really shouldn't be included. We do that because a too-large
6965 * searchset will break some IMAP servers. It is even possible that it
6966 * becomes inefficient to send the whole set. If the select function
6967 * frees limitsrch, it should be sure to set it to NULL so we won't
6968 * try freeing it again here.
6970 limitsrch = limiting_searchset(state->mail_stream, narrow);
6973 * NOTE: See note about MESSAGECACHE "searched" bits above!
6975 switch(q){
6976 case 'x': /* cancel */
6977 cmd_cancelled("Select command");
6978 return(ret);
6980 case 'c' : /* select/unselect current */
6981 (void) select_by_current(state, msgmap, in_index);
6982 ret = 0;
6983 return(ret);
6985 case 'a' : /* select/unselect all */
6986 msgno = any_lflagged(msgmap, MN_SLCT);
6987 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
6988 ret = 0;
6989 agg_select_all(state->mail_stream, msgmap, &diff,
6990 any_lflagged(msgmap, MN_SLCT) <= 0L);
6991 q_status_message4(SM_ORDER,0,2,
6992 "%s%s message%s %sselected",
6993 msgno ? "" : "All ", comatose(diff),
6994 plural(diff), msgno ? "UN" : "");
6995 return(ret);
6997 case 'n' : /* Select by Number */
6998 ret = 0;
6999 if(THRD_INDX())
7000 rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch);
7001 else
7002 rv = select_by_number(state->mail_stream, msgmap, &limitsrch);
7004 break;
7006 case 'd' : /* Select by Date */
7007 ret = 0;
7008 rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap),
7009 &limitsrch);
7010 break;
7012 case 't' : /* Text */
7013 ret = 0;
7014 rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap),
7015 &limitsrch);
7016 break;
7018 case 'z' : /* Size */
7019 ret = 0;
7020 rv = select_by_size(state->mail_stream, &limitsrch);
7021 break;
7023 case 's' : /* Status */
7024 ret = 0;
7025 rv = select_by_status(state->mail_stream, &limitsrch);
7026 break;
7028 case 'k' : /* Keyword */
7029 ret = 0;
7030 rv = select_by_keyword(state->mail_stream, &limitsrch);
7031 break;
7033 case 'r' : /* Rule */
7034 ret = 0;
7035 rv = select_by_rule(state->mail_stream, &limitsrch);
7036 break;
7038 case 'h' : /* Thread */
7039 ret = 0;
7040 rv = select_by_thread(state->mail_stream, msgmap, &limitsrch);
7041 break;
7043 default :
7044 q_status_message(SM_ORDER | SM_DING, 3, 3,
7045 "Unsupported Select option");
7046 return(ret);
7049 if(limitsrch)
7050 mail_free_searchset(&limitsrch);
7052 if(rv) /* bad return value.. */
7053 return(ret); /* error already displayed */
7055 if(narrow) /* make sure something was selected */
7056 for(i = 1L; i <= mn_get_total(msgmap); i++)
7057 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7058 && raw <= state->mail_stream->nmsgs
7059 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7060 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
7061 break;
7062 else
7063 mm_search_count--;
7066 diff = 0L;
7067 if(mm_search_count){
7069 * loop thru all the messages, adjusting local flag bits
7070 * based on their "searched" bit...
7072 for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++)
7073 if(narrow){
7074 /* turning OFF selectedness if the "searched" bit isn't lit. */
7075 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7076 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7077 && raw <= state->mail_stream->nmsgs
7078 && (mc = mail_elt(state->mail_stream, raw))
7079 && !mc->searched){
7080 diff--;
7081 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
7082 if(hidden)
7083 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
7085 /* adjust current message in case we unselect and hide it */
7086 else if(msgno < mn_get_cur(msgmap)
7087 && (!THRD_INDX()
7088 || !get_lflag(state->mail_stream, msgmap,
7089 i, MN_CHID)))
7090 msgno = i;
7093 else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7094 && raw <= state->mail_stream->nmsgs
7095 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7096 /* turn ON selectedness if "searched" bit is lit. */
7097 if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7098 diff++;
7099 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
7100 if(hidden)
7101 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
7105 /* if we're zoomed and the current message was unselected */
7106 if(narrow && msgno
7107 && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE))
7108 mn_reset_cur(msgmap, msgno);
7111 if(!diff){
7112 if(narrow)
7113 q_status_message4(SM_ORDER, 3, 3,
7114 "%s. %s message%s remain%s selected.",
7115 mm_search_count
7116 ? "No change resulted"
7117 : "No messages in intersection",
7118 comatose(old_tot), plural(old_tot),
7119 (old_tot == 1L) ? "s" : "");
7120 else if(old_tot)
7121 q_status_message(SM_ORDER, 3, 3,
7122 _("No change resulted. Matching messages already selected."));
7123 else
7124 q_status_message1(SM_ORDER | SM_DING, 3, 3,
7125 _("Select failed. No %smessages selected."),
7126 old_tot ? _("additional ") : "");
7128 else if(old_tot){
7129 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
7130 "Select matched %ld message%s. %s %smessage%s %sselected.",
7131 (diff > 0) ? diff : old_tot + diff,
7132 plural((diff > 0) ? diff : old_tot + diff),
7133 comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7134 (diff > 0) ? "total " : "",
7135 plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7136 (diff > 0) ? "" : "UN");
7137 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
7138 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
7140 else
7141 q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"),
7142 comatose(diff), plural(diff));
7144 return(ret);
7148 /*----------------------------------------------------------------------
7149 Toggle the state of the current message
7151 Args: state -- pointer pine's state variables
7152 msgmap -- message collection to operate on
7153 in_index -- in the message index view
7154 Returns: TRUE if current marked selected, FALSE otw
7155 ----*/
7157 select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
7159 long cur;
7160 int all_selected = 0;
7161 unsigned long was, tot, rawno;
7162 PINETHRD_S *thrd;
7164 cur = mn_get_cur(msgmap);
7166 if(THRD_INDX()){
7167 thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur));
7168 if(!thrd)
7169 return 0;
7171 was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT);
7172 tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE);
7173 if(was == tot)
7174 all_selected++;
7176 if(all_selected){
7177 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0);
7178 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7179 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1);
7181 * See if there's anything left to zoom on. If so,
7182 * pick an adjacent one for highlighting, else make
7183 * sure nothing is left hidden...
7185 if(any_lflagged(msgmap, MN_SLCT)){
7186 mn_inc_cur(state->mail_stream, msgmap,
7187 (in_index == View && THREADING()
7188 && sp_viewing_a_thread(state->mail_stream))
7189 ? MH_THISTHD
7190 : (in_index == View)
7191 ? MH_ANYTHD : MH_NONE);
7192 if(mn_get_cur(msgmap) == cur)
7193 mn_dec_cur(state->mail_stream, msgmap,
7194 (in_index == View && THREADING()
7195 && sp_viewing_a_thread(state->mail_stream))
7196 ? MH_THISTHD
7197 : (in_index == View)
7198 ? MH_ANYTHD : MH_NONE);
7200 else /* clear all hidden flags */
7201 (void) unzoom_index(state, state->mail_stream, msgmap);
7204 else
7205 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1);
7207 q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected",
7208 comatose(all_selected ? was : tot-was),
7209 plural(all_selected ? was : tot-was),
7210 all_selected ? "UN" : "");
7212 /* collapsed thread */
7213 else if(THREADING()
7214 && ((rawno = mn_m2raw(msgmap, cur)) != 0L)
7215 && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL)
7216 && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){
7218 * This doesn't work quite the same as the colon command works, but
7219 * it is arguably doing the correct thing. The difference is
7220 * that aggregate_select will zoom after selecting back where it
7221 * was called from, but selecting a thread with colon won't zoom.
7222 * Maybe it makes sense to zoom after a select but not after a colon
7223 * command even though they are very similar.
7225 thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state));
7227 else{
7228 if((all_selected =
7229 get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */
7230 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0);
7231 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7232 set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1);
7234 * See if there's anything left to zoom on. If so,
7235 * pick an adjacent one for highlighting, else make
7236 * sure nothing is left hidden...
7238 if(any_lflagged(msgmap, MN_SLCT)){
7239 mn_inc_cur(state->mail_stream, msgmap,
7240 (in_index == View && THREADING()
7241 && sp_viewing_a_thread(state->mail_stream))
7242 ? MH_THISTHD
7243 : (in_index == View)
7244 ? MH_ANYTHD : MH_NONE);
7245 if(mn_get_cur(msgmap) == cur)
7246 mn_dec_cur(state->mail_stream, msgmap,
7247 (in_index == View && THREADING()
7248 && sp_viewing_a_thread(state->mail_stream))
7249 ? MH_THISTHD
7250 : (in_index == View)
7251 ? MH_ANYTHD : MH_NONE);
7253 else /* clear all hidden flags */
7254 (void) unzoom_index(state, state->mail_stream, msgmap);
7257 else
7258 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1);
7260 q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected",
7261 long2string(cur), all_selected ? "UN" : "");
7265 return(!all_selected);
7269 /*----------------------------------------------------------------------
7270 Prompt the user for the command to perform on selected messages
7272 Args: state -- pointer pine's state variables
7273 msgmap -- message collection to operate on
7274 q_line -- line on display to write prompts
7275 Returns: 1 if the selected messages are suitably commanded,
7276 0 if the choice to pick the command was declined
7278 ----*/
7280 apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
7281 UCS preloadkeystroke, int flags, int q_line)
7283 int i = 8, /* number of static entries in sel_opts3 */
7284 rv = 0,
7285 cmd,
7286 we_cancel = 0,
7287 agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG;
7288 char prompt[80];
7289 PAT_STATE pstate;
7292 * To do this "right", we really ought to have access to the keymenu
7293 * here and change the typed command into a real command by running
7294 * it through menu_command. Then the switch below would be against
7295 * results from menu_command. If we did that we'd also pass the
7296 * results of menu_command in as preloadkeystroke instead of passing
7297 * the keystroke itself. But we don't have the keymenu handy,
7298 * so we have to fake it. The only complication that we run into
7299 * is that KEY_DEL is an escape sequence so we change a typed
7300 * KEY_DEL esc seq into the letter D.
7303 if(!preloadkeystroke){
7304 if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
7305 sel_opts3[i].ch = '*';
7306 sel_opts3[i].rval = '*';
7307 sel_opts3[i].name = "*";
7308 sel_opts3[i++].label = N_("Flag");
7311 if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
7312 sel_opts3[i].ch = '|';
7313 sel_opts3[i].rval = '|';
7314 sel_opts3[i].name = "|";
7315 sel_opts3[i++].label = N_("Pipe");
7318 if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */
7319 sel_opts3[i].ch = 'b';
7320 sel_opts3[i].rval = 'b';
7321 sel_opts3[i].name = "B";
7322 sel_opts3[i++].label = N_("Bounce");
7325 if(flags & AC_FROM_THREAD){
7326 if(flags & (AC_COLL | AC_EXPN)){
7327 sel_opts3[i].ch = '/';
7328 sel_opts3[i].rval = '/';
7329 sel_opts3[i].name = "/";
7330 sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse")
7331 : N_("Expand");
7334 sel_opts3[i].ch = ';';
7335 sel_opts3[i].rval = ';';
7336 sel_opts3[i].name = ";";
7337 if(flags & AC_UNSEL)
7338 sel_opts3[i++].label = N_("UnSelect");
7339 else
7340 sel_opts3[i++].label = N_("Select");
7343 if(F_ON(F_ENABLE_PRYNT, state)){ /* this one is invisible */
7344 sel_opts3[i].ch = 'y';
7345 sel_opts3[i].rval = '%';
7346 sel_opts3[i].name = "";
7347 sel_opts3[i++].label = "";
7350 if(!is_imap_stream(stream) || LEVELUIDPLUS(stream)){ /* expunge selected messages */
7351 sel_opts3[i].ch = 'x';
7352 sel_opts3[i].rval = 'x';
7353 sel_opts3[i].name = "X";
7354 sel_opts3[i++].label = N_("Expunge");
7357 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7358 sel_opts3[i].ch = '#';
7359 sel_opts3[i].rval = '#';
7360 sel_opts3[i].name = "#";
7361 sel_opts3[i++].label = N_("Set Role");
7364 sel_opts3[i].ch = KEY_DEL; /* also invisible */
7365 sel_opts3[i].rval = 'd';
7366 sel_opts3[i].name = "";
7367 sel_opts3[i++].label = "";
7369 sel_opts3[i].ch = -1;
7371 snprintf(prompt, sizeof(prompt), "%s command : ",
7372 (flags & AC_FROM_THREAD) ? "THREAD" : "APPLY");
7373 prompt[sizeof(prompt)-1] = '\0';
7374 cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'c', NO_HELP,
7375 RB_SEQ_SENSITIVE);
7376 if(isupper(cmd))
7377 cmd = tolower(cmd);
7379 else{
7380 if(preloadkeystroke == KEY_DEL)
7381 cmd = 'd';
7382 else{
7383 if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke))
7384 cmd = tolower((int) preloadkeystroke);
7385 else
7386 cmd = (int) preloadkeystroke; /* shouldn't happen */
7390 switch(cmd){
7391 case 'd' : /* delete */
7392 we_cancel = busy_cue(NULL, NULL, 1);
7393 rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */
7394 if(we_cancel)
7395 cancel_busy_cue(0);
7396 break;
7398 case 'u' : /* undelete */
7399 we_cancel = busy_cue(NULL, NULL, 1);
7400 rv = cmd_undelete(state, msgmap, agg);
7401 if(we_cancel)
7402 cancel_busy_cue(0);
7403 break;
7405 case 'r' : /* reply */
7406 rv = cmd_reply(state, msgmap, agg, NULL);
7407 break;
7409 case 'f' : /* Forward */
7410 rv = cmd_forward(state, msgmap, agg, NULL);
7411 break;
7413 case '%' : /* print */
7414 rv = cmd_print(state, msgmap, agg, MsgIndx);
7415 break;
7417 case 't' : /* take address */
7418 rv = cmd_take_addr(state, msgmap, agg);
7419 break;
7421 case 's' : /* save */
7422 rv = cmd_save(state, stream, msgmap, agg, MsgIndx);
7423 break;
7425 case 'e' : /* export */
7426 rv = cmd_export(state, msgmap, q_line, agg);
7427 break;
7429 case '|' : /* pipe */
7430 rv = cmd_pipe(state, msgmap, agg);
7431 break;
7433 case '*' : /* flag */
7434 we_cancel = busy_cue(NULL, NULL, 1);
7435 rv = cmd_flag(state, msgmap, agg);
7436 if(we_cancel)
7437 cancel_busy_cue(0);
7438 break;
7440 case 'b' : /* bounce */
7441 rv = cmd_bounce(state, msgmap, agg, NULL);
7442 break;
7444 case '/' :
7445 collapse_or_expand(state, stream, msgmap,
7446 F_ON(F_SLASH_COLL_ENTIRE, ps_global)
7447 ? 0L
7448 : mn_get_cur(msgmap));
7449 break;
7451 case ':' :
7452 select_thread_stmp(state, stream, msgmap);
7453 break;
7455 case 'x' : /* Expunge */
7456 rv = cmd_expunge(state, stream, msgmap, agg);
7457 break;
7459 case 'c' : /* cancel */
7460 cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command"
7461 : "Apply command");
7462 break;
7464 case '#' :
7465 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7466 static ESCKEY_S choose_role[] = {
7467 {'r', 'r', "R", N_("Reply")},
7468 {'f', 'f', "F", N_("Forward")},
7469 {'b', 'b', "B", N_("Bounce")},
7470 {-1, 0, NULL, NULL}
7472 int action;
7473 ACTION_S *role = NULL;
7475 action = radio_buttons(_("Reply, Forward or Bounce using a role? "),
7476 -FOOTER_ROWS(state), choose_role,
7477 'r', 'x', h_role_aggregate, RB_NORM);
7478 if(action == 'r' || action == 'f' || action == 'b'){
7479 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
7481 redraw = state->redrawer;
7482 state->redrawer = NULL;
7483 prev_screen = state->prev_screen;
7484 role = NULL;
7485 state->next_screen = SCREEN_FUN_NULL;
7487 if(role_select_screen(state, &role,
7488 action == 'f' ? MC_FORWARD :
7489 action == 'r' ? MC_REPLY :
7490 action == 'b' ? MC_BOUNCE : 0) < 0){
7491 cmd_cancelled(action == 'f' ? _("Forward") :
7492 action == 'r' ? _("Reply") : _("Bounce"));
7493 state->next_screen = prev_screen;
7494 state->redrawer = redraw;
7495 state->mangled_screen = 1;
7497 else{
7498 if(role)
7499 role = combine_inherited_role(role);
7500 else{
7501 role = (ACTION_S *) fs_get(sizeof(*role));
7502 memset((void *) role, 0, sizeof(*role));
7503 role->nick = cpystr("Default Role");
7506 state->redrawer = NULL;
7507 switch(action){
7508 case 'r':
7509 (void) cmd_reply(state, msgmap, agg, role);
7510 break;
7512 case 'f':
7513 (void) cmd_forward(state, msgmap, agg, role);
7514 break;
7516 case 'b':
7517 (void) cmd_bounce(state, msgmap, agg, role);
7518 break;
7521 if(role)
7522 free_action(&role);
7524 if(redraw)
7525 (*redraw)();
7527 state->next_screen = prev_screen;
7528 state->redrawer = redraw;
7529 state->mangled_screen = 1;
7533 break;
7535 case 'z' : /* default */
7536 q_status_message(SM_INFO, 0, 2,
7537 "Cancelled, there is no default command");
7538 break;
7540 default:
7541 break;
7544 return(rv);
7549 * Select by message number ranges.
7550 * Sets searched bits in mail_elts
7552 * Args limitsrch -- limit search to this searchset
7554 * Returns 0 on success.
7557 select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
7559 int r, end, cur;
7560 long n1, n2, raw;
7561 char number1[16], number2[16], numbers[80], *p, *t;
7562 HelpType help;
7563 MESSAGECACHE *mc;
7565 numbers[0] = '\0';
7566 ps_global->mangled_footer = 1;
7567 help = NO_HELP;
7568 while(1){
7569 int flags = OE_APPEND_CURRENT;
7571 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7572 sizeof(numbers), _(select_num), NULL, help, &flags);
7573 if(r == 4)
7574 continue;
7576 if(r == 3){
7577 help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
7578 continue;
7581 for(t = p = numbers; *p ; p++) /* strip whitespace */
7582 if(!isspace((unsigned char)*p))
7583 *t++ = *p;
7585 *t = '\0';
7587 if(r == 1 || numbers[0] == '\0'){
7588 cmd_cancelled("Selection by number");
7589 return(1);
7591 else
7592 break;
7595 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7596 if((mc = mail_elt(stream, n1)) != NULL)
7597 mc->searched = 0; /* clear searched bits */
7599 for(p = numbers; *p ; p++){
7600 t = number1;
7601 while(*p && isdigit((unsigned char)*p))
7602 *t++ = *p++;
7604 *t = '\0';
7606 end = cur = 0;
7607 if(number1[0] == '\0'){
7608 if(*p == '-'){
7609 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7610 _("Invalid number range, missing number before \"-\": %s"),
7611 numbers);
7612 return(1);
7614 else if(!strucmp("end", p)){
7615 end = 1;
7616 p += strlen("end");
7618 else if(!strucmp("$", p)){
7619 end = 1;
7620 p++;
7622 else if(*p == '.'){
7623 cur = 1;
7624 p++;
7626 else{
7627 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7628 _("Invalid message number: %s"), numbers);
7629 return(1);
7633 if(end)
7634 n1 = mn_get_total(msgmap);
7635 else if(cur)
7636 n1 = mn_get_cur(msgmap);
7637 else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
7638 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7639 _("\"%s\" out of message number range"),
7640 long2string(n1));
7641 return(1);
7644 t = number2;
7645 if(*p == '-'){
7646 while(*++p && isdigit((unsigned char)*p))
7647 *t++ = *p;
7649 *t = '\0';
7651 end = cur = 0;
7652 if(number2[0] == '\0'){
7653 if(!strucmp("end", p)){
7654 end = 1;
7655 p += strlen("end");
7657 else if(!strucmp(p, "$")){
7658 end = 1;
7659 p++;
7661 else if(*p == '.'){
7662 cur = 1;
7663 p++;
7665 else{
7666 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7667 _("Invalid number range, missing number after \"-\": %s"),
7668 numbers);
7669 return(1);
7673 if(end)
7674 n2 = mn_get_total(msgmap);
7675 else if(cur)
7676 n2 = mn_get_cur(msgmap);
7677 else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){
7678 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7679 _("\"%s\" out of message number range"),
7680 long2string(n2));
7681 return(1);
7684 if(n2 <= n1){
7685 char t[20];
7687 strncpy(t, long2string(n1), sizeof(t));
7688 t[sizeof(t)-1] = '\0';
7689 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7690 _("Invalid reverse message number range: %s-%s"),
7691 t, long2string(n2));
7692 return(1);
7695 for(;n1 <= n2; n1++){
7696 raw = mn_m2raw(msgmap, n1);
7697 if(raw > 0L
7698 && (!(limitsrch && *limitsrch)
7699 || in_searchset(*limitsrch, (unsigned long) raw)))
7700 mm_searched(stream, raw);
7703 else{
7704 raw = mn_m2raw(msgmap, n1);
7705 if(raw > 0L
7706 && (!(limitsrch && *limitsrch)
7707 || in_searchset(*limitsrch, (unsigned long) raw)))
7708 mm_searched(stream, raw);
7711 if(*p == '\0')
7712 break;
7715 return(0);
7720 * Select by thread number ranges.
7721 * Sets searched bits in mail_elts
7723 * Args limitsrch -- limit search to this searchset
7725 * Returns 0 on success.
7728 select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset)
7730 int r, end, cur;
7731 long n1, n2;
7732 char number1[16], number2[16], numbers[80], *p, *t;
7733 HelpType help;
7734 PINETHRD_S *thrd = NULL, *th;
7735 MESSAGECACHE *mc;
7737 numbers[0] = '\0';
7738 ps_global->mangled_footer = 1;
7739 help = NO_HELP;
7740 while(1){
7741 int flags = OE_APPEND_CURRENT;
7743 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7744 sizeof(numbers), _(select_num), NULL, help, &flags);
7745 if(r == 4)
7746 continue;
7748 if(r == 3){
7749 help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP;
7750 continue;
7753 for(t = p = numbers; *p ; p++) /* strip whitespace */
7754 if(!isspace((unsigned char)*p))
7755 *t++ = *p;
7757 *t = '\0';
7759 if(r == 1 || numbers[0] == '\0'){
7760 cmd_cancelled("Selection by number");
7761 return(1);
7763 else
7764 break;
7767 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7768 if((mc = mail_elt(stream, n1)) != NULL)
7769 mc->searched = 0; /* clear searched bits */
7771 for(p = numbers; *p ; p++){
7772 t = number1;
7773 while(*p && isdigit((unsigned char)*p))
7774 *t++ = *p++;
7776 *t = '\0';
7778 end = cur = 0;
7779 if(number1[0] == '\0'){
7780 if(*p == '-'){
7781 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7782 _("Invalid number range, missing number before \"-\": %s"),
7783 numbers);
7784 return(1);
7786 else if(!strucmp("end", p)){
7787 end = 1;
7788 p += strlen("end");
7790 else if(!strucmp(p, "$")){
7791 end = 1;
7792 p++;
7794 else if(*p == '.'){
7795 cur = 1;
7796 p++;
7798 else{
7799 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7800 _("Invalid thread number: %s"), numbers);
7801 return(1);
7805 if(end)
7806 n1 = msgmap->max_thrdno;
7807 else if(cur){
7808 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7809 n1 = th->thrdno;
7811 else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){
7812 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7813 _("\"%s\" out of thread number range"),
7814 long2string(n1));
7815 return(1);
7818 t = number2;
7819 if(*p == '-'){
7821 while(*++p && isdigit((unsigned char)*p))
7822 *t++ = *p;
7824 *t = '\0';
7826 end = 0;
7827 if(number2[0] == '\0'){
7828 if(!strucmp("end", p)){
7829 end = 1;
7830 p += strlen("end");
7832 else if(!strucmp("$", p)){
7833 end = 1;
7834 p++;
7836 else if(*p == '.'){
7837 cur = 1;
7838 p++;
7840 else{
7841 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7842 _("Invalid number range, missing number after \"-\": %s"),
7843 numbers);
7844 return(1);
7848 if(end)
7849 n2 = msgmap->max_thrdno;
7850 else if(cur){
7851 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7852 n2 = th->thrdno;
7854 else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){
7855 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7856 _("\"%s\" out of thread number range"),
7857 long2string(n2));
7858 return(1);
7861 if(n2 <= n1){
7862 char t[20];
7864 strncpy(t, long2string(n1), sizeof(t));
7865 t[sizeof(t)-1] = '\0';
7866 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7867 _("Invalid reverse message number range: %s-%s"),
7868 t, long2string(n2));
7869 return(1);
7872 for(;n1 <= n2; n1++){
7873 thrd = find_thread_by_number(stream, msgmap, n1, thrd);
7875 if(thrd)
7876 set_search_bit_for_thread(stream, thrd, msgset);
7879 else{
7880 thrd = find_thread_by_number(stream, msgmap, n1, NULL);
7882 if(thrd)
7883 set_search_bit_for_thread(stream, thrd, msgset);
7886 if(*p == '\0')
7887 break;
7890 return(0);
7895 * Select by message dates.
7896 * Sets searched bits in mail_elts
7898 * Args limitsrch -- limit search to this searchset
7900 * Returns 0 on success.
7903 select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
7905 int r, we_cancel = 0, when = 0;
7906 char date[100], defdate[100], prompt[128];
7907 time_t seldate = time(0);
7908 struct tm *seldate_tm;
7909 SEARCHPGM *pgm;
7910 HelpType help;
7911 static struct _tense {
7912 char *preamble,
7913 *range,
7914 *scope;
7915 } tense[] = {
7916 {"were ", "SENT SINCE", " (inclusive)"},
7917 {"were ", "SENT BEFORE", " (exclusive)"},
7918 {"were ", "SENT ON", "" },
7919 {"", "ARRIVED SINCE", " (inclusive)"},
7920 {"", "ARRIVED BEFORE", " (exclusive)"},
7921 {"", "ARRIVED ON", "" }
7924 date[0] = '\0';
7925 ps_global->mangled_footer = 1;
7926 help = NO_HELP;
7929 * If talking to an old server, default to SINCE instead of
7930 * SENTSINCE, which was added later.
7932 if(is_imap_stream(stream) && !modern_imap_stream(stream))
7933 when = 3;
7935 while(1){
7936 int flags = OE_APPEND_CURRENT;
7938 seldate_tm = localtime(&seldate);
7939 snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday,
7940 month_abbrev(seldate_tm->tm_mon + 1),
7941 seldate_tm->tm_year + 1900);
7942 defdate[sizeof(defdate)-1] = '\0';
7943 snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ",
7944 tense[when].preamble, tense[when].range,
7945 tense[when].scope, defdate);
7946 prompt[sizeof(prompt)-1] = '\0';
7947 r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date),
7948 prompt, sel_date_opt, help, &flags);
7949 switch (r){
7950 case 1 :
7951 cmd_cancelled("Selection by date");
7952 return(1);
7954 case 3 :
7955 help = (help == NO_HELP) ? h_select_date : NO_HELP;
7956 continue;
7958 case 4 :
7959 continue;
7961 case 11 :
7963 MESSAGECACHE *mc;
7964 long rawno;
7966 if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L
7967 && rawno <= stream->nmsgs
7968 && (mc = mail_elt(stream, rawno))){
7970 /* cache not filled in yet? */
7971 if(mc->day == 0){
7972 char seq[20];
7974 if(stream->dtb && stream->dtb->flags & DR_NEWS){
7975 strncpy(seq,
7976 ulong2string(mail_uid(stream, rawno)),
7977 sizeof(seq));
7978 seq[sizeof(seq)-1] = '\0';
7979 mail_fetch_overview(stream, seq, NULL);
7981 else{
7982 strncpy(seq, long2string(rawno),
7983 sizeof(seq));
7984 seq[sizeof(seq)-1] = '\0';
7985 mail_fetch_fast(stream, seq, 0L);
7989 /* mail_date returns fixed field width date */
7990 mail_date(date, mc);
7991 date[11] = '\0';
7995 continue;
7997 case 12 : /* set default to PREVIOUS day */
7998 seldate -= 86400;
7999 continue;
8001 case 13 : /* set default to NEXT day */
8002 seldate += 86400;
8003 continue;
8005 case 14 :
8006 when = (when+1) % (sizeof(tense) / sizeof(struct _tense));
8007 continue;
8009 default:
8010 break;
8013 removing_leading_white_space(date);
8014 removing_trailing_white_space(date);
8015 if(!*date){
8016 strncpy(date, defdate, sizeof(date));
8017 date[sizeof(date)-1] = '\0';
8020 break;
8023 if((pgm = mail_newsearchpgm()) != NULL){
8024 MESSAGECACHE elt;
8025 short converted_date;
8027 if(mail_parse_date(&elt, (unsigned char *) date)){
8028 converted_date = mail_shortdate(elt.year, elt.month, elt.day);
8030 switch(when){
8031 case 0:
8032 pgm->sentsince = converted_date;
8033 break;
8034 case 1:
8035 pgm->sentbefore = converted_date;
8036 break;
8037 case 2:
8038 pgm->senton = converted_date;
8039 break;
8040 case 3:
8041 pgm->since = converted_date;
8042 break;
8043 case 4:
8044 pgm->before = converted_date;
8045 break;
8046 case 5:
8047 pgm->on = converted_date;
8048 break;
8051 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8053 if(ps_global && ps_global->ttyo){
8054 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8055 ps_global->mangled_footer = 1;
8058 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8060 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8062 if(we_cancel)
8063 cancel_busy_cue(0);
8065 /* we know this was freed in mail_search, let caller know */
8066 if(limitsrch)
8067 *limitsrch = NULL;
8069 else{
8070 mail_free_searchpgm(&pgm);
8071 q_status_message1(SM_ORDER, 3, 3,
8072 _("Invalid date entered: %s"), date);
8073 return(1);
8077 return(0);
8082 * Select by searching in message headers or body.
8083 * Sets searched bits in mail_elts
8085 * Args limitsrch -- limit search to this searchset
8087 * Returns 0 on success.
8090 select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8092 int r, ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
8093 int not = 0, me = 0;
8094 char sstring[80], savedsstring[80], tmp[128];
8095 char *p, *sval = NULL;
8096 char buftmp[MAILTMPLEN], namehdr[80];
8097 ESCKEY_S ekey[8];
8098 ENVELOPE *env = NULL;
8099 HelpType help;
8100 unsigned flagsforhist = 0;
8101 static HISTORY_S *history = NULL;
8102 static char *recip = "RECIPIENTS";
8103 static char *partic = "PARTICIPANTS";
8104 static char *match_me = N_("[Match_My_Addresses]");
8105 static char *dont_match_me = N_("[Don't_Match_My_Addresses]");
8107 ps_global->mangled_footer = 1;
8108 savedsstring[0] = '\0';
8109 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8111 while(1){
8112 type = radio_buttons(not ? _(sel_text_not) : _(sel_text),
8113 -FOOTER_ROWS(ps_global), sel_text_opt,
8114 's', 'x', NO_HELP, RB_NORM|RB_RET_HELP);
8116 if(type == '!')
8117 not = !not;
8118 else if(type == 3){
8119 helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS",
8120 HLPD_SIMPLE);
8121 ps_global->mangled_screen = 1;
8123 else
8124 break;
8128 * prepare some friendly defaults...
8130 switch(type){
8131 case 't' : /* address fields, offer To or From */
8132 case 'f' :
8133 case 'c' :
8134 case 'r' :
8135 case 'p' :
8136 sval = (type == 't') ? "TO" :
8137 (type == 'f') ? "FROM" :
8138 (type == 'c') ? "CC" :
8139 (type == 'r') ? recip : partic;
8140 ekey[ekeyi].ch = ctrl('T');
8141 ekey[ekeyi].name = "^T";
8142 ekey[ekeyi].rval = 10;
8143 /* TRANSLATORS: use Current To Address */
8144 ekey[ekeyi++].label = N_("Cur To");
8145 ekey[ekeyi].ch = ctrl('R');
8146 ekey[ekeyi].name = "^R";
8147 ekey[ekeyi].rval = 11;
8148 /* TRANSLATORS: use Current From Address */
8149 ekey[ekeyi++].label = N_("Cur From");
8150 ekey[ekeyi].ch = ctrl('W');
8151 ekey[ekeyi].name = "^W";
8152 ekey[ekeyi].rval = 12;
8153 /* TRANSLATORS: use Current Cc Address */
8154 ekey[ekeyi++].label = N_("Cur Cc");
8155 ekey[ekeyi].ch = ctrl('Y');
8156 ekey[ekeyi].name = "^Y";
8157 ekey[ekeyi].rval = 13;
8158 /* TRANSLATORS: Match Me means match my address */
8159 ekey[ekeyi++].label = N_("Match Me");
8160 ekey[ekeyi].ch = 0;
8161 ekey[ekeyi].name = "";
8162 ekey[ekeyi].rval = 0;
8163 ekey[ekeyi++].label = "";
8164 break;
8166 case 's' :
8167 sval = "SUBJECT";
8168 ekey[ekeyi].ch = ctrl('X');
8169 ekey[ekeyi].name = "^X";
8170 ekey[ekeyi].rval = 14;
8171 /* TRANSLATORS: use Current Subject */
8172 ekey[ekeyi++].label = N_("Cur Subject");
8173 break;
8175 case 'a' :
8176 sval = "TEXT";
8177 break;
8179 case 'b' :
8180 sval = "BODYTEXT";
8181 break;
8183 case 'h' :
8184 strncpy(tmp, "Name of HEADER to match : ", sizeof(tmp)-1);
8185 tmp[sizeof(tmp)-1] = '\0';
8186 flags = OE_APPEND_CURRENT;
8187 namehdr[0] = '\0';
8188 r = 'x';
8189 while (r == 'x'){
8190 int done = 0;
8192 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8193 sizeof(namehdr), tmp, ekey, NO_HELP, &flags);
8194 if (r == 1){
8195 cmd_cancelled("Selection by text");
8196 return(1);
8198 removing_leading_white_space(namehdr);
8199 while(!done){
8200 while ((namehdr[0] != '\0') && /* remove trailing ":" */
8201 (namehdr[strlen(namehdr) - 1] == ':'))
8202 namehdr[strlen(namehdr) - 1] = '\0';
8203 if ((namehdr[0] != '\0')
8204 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8205 removing_trailing_white_space(namehdr);
8206 else
8207 done++;
8209 if (strchr(namehdr,' ') || strchr(namehdr,'\t') ||
8210 strchr(namehdr,':'))
8211 namehdr[0] = '\0';
8212 if (namehdr[0] == '\0')
8213 r = 'x';
8215 sval = namehdr;
8216 break;
8218 case 'x':
8219 break;
8221 default:
8222 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
8223 return(1);
8226 ekey[ekeyi].ch = KEY_UP;
8227 ekey[ekeyi].rval = 30;
8228 ekey[ekeyi].name = "";
8229 ku = ekeyi;
8230 ekey[ekeyi++].label = "";
8232 ekey[ekeyi].ch = KEY_DOWN;
8233 ekey[ekeyi].rval = 31;
8234 ekey[ekeyi].name = "";
8235 ekey[ekeyi++].label = "";
8237 ekey[ekeyi].ch = -1;
8239 if(type != 'x'){
8241 init_hist(&history, HISTSIZE);
8243 if(ekey[0].ch > -1 && msgno > 0L
8244 && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),
8245 NULL)))
8246 ekey[0].ch = -1;
8248 sstring[0] = '\0';
8249 help = NO_HELP;
8250 r = type;
8251 while(r != 'x'){
8252 if(not)
8253 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8254 snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval);
8255 else
8256 snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval);
8258 if(items_in_hist(history) > 0){
8259 ekey[ku].name = HISTORY_UP_KEYNAME;
8260 ekey[ku].label = HISTORY_KEYLABEL;
8261 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
8262 ekey[ku+1].label = HISTORY_KEYLABEL;
8264 else{
8265 ekey[ku].name = "";
8266 ekey[ku].label = "";
8267 ekey[ku+1].name = "";
8268 ekey[ku+1].label = "";
8271 flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
8272 r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
8273 79, tmp, ekey, help, &flags);
8275 if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me)))))
8276 me = 0;
8278 switch(r){
8279 case 3 :
8280 help = (help == NO_HELP)
8281 ? (not
8282 ? ((type == 'f') ? h_select_txt_not_from
8283 : (type == 't') ? h_select_txt_not_to
8284 : (type == 'c') ? h_select_txt_not_cc
8285 : (type == 's') ? h_select_txt_not_subj
8286 : (type == 'a') ? h_select_txt_not_all
8287 : (type == 'r') ? h_select_txt_not_recip
8288 : (type == 'p') ? h_select_txt_not_partic
8289 : (type == 'b') ? h_select_txt_not_body
8290 : NO_HELP)
8291 : ((type == 'f') ? h_select_txt_from
8292 : (type == 't') ? h_select_txt_to
8293 : (type == 'c') ? h_select_txt_cc
8294 : (type == 's') ? h_select_txt_subj
8295 : (type == 'a') ? h_select_txt_all
8296 : (type == 'r') ? h_select_txt_recip
8297 : (type == 'p') ? h_select_txt_partic
8298 : (type == 'b') ? h_select_txt_body
8299 : NO_HELP))
8300 : NO_HELP;
8302 case 4 :
8303 continue;
8305 case 10 : /* To: default */
8306 if(env && env->to && env->to->mailbox){
8307 snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox,
8308 env->to->host ? "@" : "",
8309 env->to->host ? env->to->host : "");
8310 sstring[sizeof(sstring)-1] = '\0';
8312 continue;
8314 case 11 : /* From: default */
8315 if(env && env->from && env->from->mailbox){
8316 snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox,
8317 env->from->host ? "@" : "",
8318 env->from->host ? env->from->host : "");
8319 sstring[sizeof(sstring)-1] = '\0';
8321 continue;
8323 case 12 : /* Cc: default */
8324 if(env && env->cc && env->cc->mailbox){
8325 snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox,
8326 env->cc->host ? "@" : "",
8327 env->cc->host ? env->cc->host : "");
8328 sstring[sizeof(sstring)-1] = '\0';
8330 continue;
8332 case 13 : /* Match my addresses */
8333 me++;
8334 snprintf(sstring, sizeof(sstring), "%s", not ? _(dont_match_me) : _(match_me));
8335 continue;
8337 case 14 : /* Subject: default */
8338 if(env && env->subject && env->subject[0]){
8339 char *q = NULL;
8341 snprintf(buftmp, sizeof(buftmp), "%.75s", env->subject);
8342 buftmp[sizeof(buftmp)-1] = '\0';
8343 q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
8344 SIZEOF_20KBUF, buftmp);
8345 if(q != env->subject){
8346 snprintf(savedsstring, sizeof(savedsstring), "%.70s", q);
8347 savedsstring[sizeof(savedsstring)-1] = '\0';
8350 snprintf(sstring, sizeof(sstring), "%s", q);
8351 sstring[sizeof(sstring)-1] = '\0';
8354 continue;
8356 case 30 :
8357 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8358 if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){
8359 strncpy(sstring, p, sizeof(sstring));
8360 sstring[sizeof(sstring)-1] = '\0';
8361 if(history->hist[history->curindex]){
8362 flagsforhist = history->hist[history->curindex]->flags;
8363 not = (flagsforhist & 0x1) ? 1 : 0;
8364 me = (flagsforhist & 0x2) ? 1 : 0;
8367 else
8368 Writechar(BELL, 0);
8370 continue;
8372 case 31 :
8373 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8374 if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){
8375 strncpy(sstring, p, sizeof(sstring));
8376 sstring[sizeof(sstring)-1] = '\0';
8377 if(history->hist[history->curindex]){
8378 flagsforhist = history->hist[history->curindex]->flags;
8379 not = (flagsforhist & 0x1) ? 1 : 0;
8380 me = (flagsforhist & 0x2) ? 1 : 0;
8383 else
8384 Writechar(BELL, 0);
8386 continue;
8388 default :
8389 break;
8392 if(r == 1 || sstring[0] == '\0')
8393 r = 'x';
8395 break;
8399 if(type == 'x' || r == 'x'){
8400 cmd_cancelled("Selection by text");
8401 return(1);
8404 if(ps_global && ps_global->ttyo){
8405 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8406 ps_global->mangled_footer = 1;
8409 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8411 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8412 save_hist(history, sstring, flagsforhist, NULL);
8414 rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch);
8415 if(we_cancel)
8416 cancel_busy_cue(0);
8418 return(rv);
8423 * Select by message size.
8424 * Sets searched bits in mail_elts
8426 * Args limitsrch -- limit search to this searchset
8428 * Returns 0 on success.
8431 select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch)
8433 int r, large = 1, we_cancel = 0;
8434 unsigned long n, mult = 1L, numerator = 0L, divisor = 1L;
8435 char size[16], numbers[80], *p, *t;
8436 HelpType help;
8437 SEARCHPGM *pgm;
8438 long flags = (SE_NOPREFETCH | SE_FREE);
8440 numbers[0] = '\0';
8441 ps_global->mangled_footer = 1;
8443 help = NO_HELP;
8444 while(1){
8445 int flgs = OE_APPEND_CURRENT;
8447 sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger;
8449 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
8450 sizeof(numbers), large ? _(select_size_larger_msg)
8451 : _(select_size_smaller_msg),
8452 sel_size_opt, help, &flgs);
8453 if(r == 4)
8454 continue;
8456 if(r == 14){
8457 large = 1 - large;
8458 continue;
8461 if(r == 3){
8462 help = (help == NO_HELP) ? (large ? h_select_by_larger_size
8463 : h_select_by_smaller_size)
8464 : NO_HELP;
8465 continue;
8468 for(t = p = numbers; *p ; p++) /* strip whitespace */
8469 if(!isspace((unsigned char)*p))
8470 *t++ = *p;
8472 *t = '\0';
8474 if(r == 1 || numbers[0] == '\0'){
8475 cmd_cancelled("Selection by size");
8476 return(1);
8478 else
8479 break;
8482 if(numbers[0] == '-'){
8483 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8484 _("Invalid size entered: %s"), numbers);
8485 return(1);
8488 t = size;
8489 p = numbers;
8491 while(*p && isdigit((unsigned char)*p))
8492 *t++ = *p++;
8494 *t = '\0';
8496 if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){
8497 size[0] = '0';
8498 size[1] = '\0';
8501 if(size[0] == '\0'){
8502 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8503 _("Invalid size entered: %s"), numbers);
8504 return(1);
8507 n = strtoul(size, (char **)NULL, 10);
8509 size[0] = '\0';
8510 if(*p == '.'){
8512 * We probably ought to just use atof() to convert 1.1 into a
8513 * double, but since we haven't used atof() anywhere else I'm
8514 * reluctant to use it because of portability concerns.
8516 p++;
8517 t = size;
8518 while(*p && isdigit((unsigned char)*p)){
8519 *t++ = *p++;
8520 divisor *= 10;
8523 *t = '\0';
8525 if(size[0])
8526 numerator = strtoul(size, (char **)NULL, 10);
8529 switch(*p){
8530 case 'g':
8531 case 'G':
8532 mult *= 1000;
8533 /* fall through */
8535 case 'm':
8536 case 'M':
8537 mult *= 1000;
8538 /* fall through */
8540 case 'k':
8541 case 'K':
8542 mult *= 1000;
8543 break;
8546 n = n * mult + (numerator * mult) / divisor;
8548 pgm = mail_newsearchpgm();
8549 if(large)
8550 pgm->larger = n;
8551 else
8552 pgm->smaller = n;
8554 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8555 flags |= SE_NOSERVER;
8557 if(ps_global && ps_global->ttyo){
8558 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8559 ps_global->mangled_footer = 1;
8562 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8564 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8565 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8566 /* we know this was freed in mail_search, let caller know */
8567 if(limitsrch)
8568 *limitsrch = NULL;
8570 if(we_cancel)
8571 cancel_busy_cue(0);
8573 return(0);
8578 * visible_searchset -- return c-client search set unEXLDed
8579 * sequence numbers
8581 SEARCHSET *
8582 visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap)
8584 long n, run;
8585 SEARCHSET *full_set = NULL, **set;
8588 * If we're talking to anything other than a server older than
8589 * imap 4rev1, build a searchset otherwise it'll choke.
8591 if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
8592 if(any_lflagged(msgmap, MN_EXLD)){
8593 for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
8594 if(get_lflag(stream, NULL, n, MN_EXLD)){
8595 if(run){ /* previous NOT excluded? */
8596 if(run > 1L)
8597 (*set)->last = n - 1L;
8599 set = &(*set)->next;
8600 run = 0L;
8603 else if(run++){ /* next in run */
8604 (*set)->last = n;
8606 else{ /* start of run */
8607 *set = mail_newsearchset();
8608 (*set)->first = n;
8611 else{
8612 full_set = mail_newsearchset();
8613 full_set->first = 1L;
8614 full_set->last = stream->nmsgs;
8618 return(full_set);
8623 * Select by message status bits.
8624 * Sets searched bits in mail_elts
8626 * Args limitsrch -- limit search to this searchset
8628 * Returns 0 on success.
8631 select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch)
8633 int s, not = 0, we_cancel = 0, rv;
8635 while(1){
8636 s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag),
8637 -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x',
8638 NO_HELP, RB_NORM|RB_RET_HELP);
8640 if(s == 'x'){
8641 cmd_cancelled("Selection by status");
8642 return(1);
8644 else if(s == 3){
8645 helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"),
8646 HLPD_SIMPLE);
8647 ps_global->mangled_screen = 1;
8649 else if(s == '!')
8650 not = !not;
8651 else
8652 break;
8655 if(ps_global && ps_global->ttyo){
8656 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8657 ps_global->mangled_footer = 1;
8660 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8661 rv = agg_flag_select(stream, not, s, limitsrch);
8662 if(we_cancel)
8663 cancel_busy_cue(0);
8665 return(rv);
8670 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8671 * Sets searched bits in mail_elts
8673 * Args limitsrch -- limit search to this searchset
8675 * Returns 0 on success.
8678 select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
8680 char rulenick[1000], *nick;
8681 PATGRP_S *patgrp;
8682 int r, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
8683 | ROLE_DO_INCOLS
8684 | ROLE_DO_ROLES
8685 | ROLE_DO_SCORES
8686 | ROLE_DO_OTHER
8687 | ROLE_DO_FILTER;
8689 rulenick[0] = '\0';
8690 ps_global->mangled_footer = 1;
8693 int oe_flags;
8695 oe_flags = OE_APPEND_CURRENT;
8696 r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0,
8697 sizeof(rulenick),
8698 not ? _("Rule to NOT match: ")
8699 : _("Rule to match: "),
8700 sel_key_opt, NO_HELP, &oe_flags);
8702 if(r == 14){
8703 /* select rulenick from a list */
8704 if((nick=choose_a_rule(rflags)) != NULL){
8705 strncpy(rulenick, nick, sizeof(rulenick)-1);
8706 rulenick[sizeof(rulenick)-1] = '\0';
8707 fs_give((void **) &nick);
8709 else
8710 r = 4;
8712 else if(r == '!')
8713 not = !not;
8715 if(r == 3){
8716 helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE);
8717 ps_global->mangled_screen = 1;
8719 else if(r == 1){
8720 cmd_cancelled("Selection by Rule");
8721 return(1);
8724 removing_leading_and_trailing_white_space(rulenick);
8726 }while(r == 3 || r == 4 || r == '!');
8730 * The approach of requiring a nickname instead of just allowing the
8731 * user to select from the list of rules has the drawback that a rule
8732 * may not have a nickname, or there may be more than one rule with
8733 * the same nickname. However, it has the benefit of allowing the user
8734 * to type in the nickname and, most importantly, allows us to set
8735 * up the ! (not). We could incorporate the ! into the selection
8736 * screen, but this is easier and also allows the typing of nicks.
8737 * User can just set up nicknames if they want to use this feature.
8739 patgrp = nick_to_patgrp(rulenick, rflags);
8741 if(patgrp){
8742 if(ps_global && ps_global->ttyo){
8743 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8744 ps_global->mangled_footer = 1;
8747 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8748 match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL,
8749 get_msg_score,
8750 (not ? MP_NOT : 0) | SE_NOPREFETCH);
8751 free_patgrp(&patgrp);
8752 if(we_cancel)
8753 cancel_busy_cue(0);
8756 if(limitsrch && *limitsrch){
8757 mail_free_searchset(limitsrch);
8758 *limitsrch = NULL;
8761 return(0);
8766 * Allow user to choose a rule from their list of rules.
8768 * Returns an allocated rule nickname on success, NULL otherwise.
8770 char *
8771 choose_a_rule(int rflags)
8773 char *choice = NULL;
8774 char **rule_list, **lp;
8775 int cnt = 0;
8776 PAT_S *pat;
8777 PAT_STATE pstate;
8779 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){
8780 q_status_message(SM_ORDER, 3, 3,
8781 _("No rules available. Use Setup/Rules to add some."));
8782 return(choice);
8786 * Build a list of rules to choose from.
8789 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8790 cnt++;
8792 if(cnt <= 0){
8793 q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
8794 return(choice);
8797 lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
8798 memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
8800 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8801 *lp++ = cpystr((pat->patgrp && pat->patgrp->nick)
8802 ? pat->patgrp->nick : "?");
8804 /* TRANSLATORS: SELECT A RULE is a screen title
8805 TRANSLATORS: Print something1 using something2.
8806 "rules" is something1 */
8807 choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"),
8808 _("rules"), h_select_rule_screen,
8809 _("HELP FOR SELECTING A RULE NICKNAME"), NULL);
8811 if(!choice)
8812 q_status_message(SM_ORDER, 1, 4, "No choice");
8814 free_list_array(&rule_list);
8816 return(choice);
8821 * Select by current thread.
8822 * Sets searched bits in mail_elts for this entire thread
8824 * Args limitsrch -- limit search to this searchset
8826 * Returns 0 on success.
8829 select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
8831 long n;
8832 PINETHRD_S *thrd = NULL;
8833 int ret = 1;
8834 MESSAGECACHE *mc;
8836 if(!stream)
8837 return(ret);
8839 for(n = 1L; n <= stream->nmsgs; n++)
8840 if((mc = mail_elt(stream, n)) != NULL)
8841 mc->searched = 0; /* clear searched bits */
8843 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
8844 if(thrd && thrd->top && thrd->top != thrd->rawno)
8845 thrd = fetch_thread(stream, thrd->top);
8848 * This doesn't unselect if the thread is already selected
8849 * (like select current does), it always selects.
8850 * There is no way to select ! this thread.
8852 if(thrd){
8853 set_search_bit_for_thread(stream, thrd, limitsrch);
8854 ret = 0;
8857 return(ret);
8862 * Select by message keywords.
8863 * Sets searched bits in mail_elts
8865 * Args limitsrch -- limit search to this searchset
8867 * Returns 0 on success.
8870 select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
8872 int r, not = 0, we_cancel = 0;
8873 char keyword[MAXUSERFLAG+1], *kword;
8874 char *error = NULL, *p, *prompt;
8875 HelpType help;
8876 SEARCHPGM *pgm;
8878 keyword[0] = '\0';
8879 ps_global->mangled_footer = 1;
8881 help = NO_HELP;
8883 int oe_flags;
8885 if(error){
8886 q_status_message(SM_ORDER, 3, 4, error);
8887 fs_give((void **) &error);
8890 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
8891 if(not)
8892 prompt = _("Keyword (or keyword initial) to NOT match: ");
8893 else
8894 prompt = _("Keyword (or keyword initial) to match: ");
8896 else{
8897 if(not)
8898 prompt = _("Keyword to NOT match: ");
8899 else
8900 prompt = _("Keyword to match: ");
8903 oe_flags = OE_APPEND_CURRENT;
8904 r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0,
8905 sizeof(keyword),
8906 prompt, sel_key_opt, help, &oe_flags);
8908 if(r == 14){
8909 /* select keyword from a list */
8910 if((kword=choose_a_keyword()) != NULL){
8911 strncpy(keyword, kword, sizeof(keyword)-1);
8912 keyword[sizeof(keyword)-1] = '\0';
8913 fs_give((void **) &kword);
8915 else
8916 r = 4;
8918 else if(r == '!')
8919 not = !not;
8921 if(r == 3)
8922 help = help == NO_HELP ? h_select_keyword : NO_HELP;
8923 else if(r == 1){
8924 cmd_cancelled("Selection by keyword");
8925 return(1);
8928 removing_leading_and_trailing_white_space(keyword);
8930 }while(r == 3 || r == 4 || r == '!' || keyword_check(keyword, &error));
8933 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
8934 p = initial_to_keyword(keyword);
8935 if(p != keyword){
8936 strncpy(keyword, p, sizeof(keyword)-1);
8937 keyword[sizeof(keyword)-1] = '\0';
8942 * We want to check the keyword, not the nickname of the keyword,
8943 * so convert it to the keyword if necessary.
8945 p = nick_to_keyword(keyword);
8946 if(p != keyword){
8947 strncpy(keyword, p, sizeof(keyword)-1);
8948 keyword[sizeof(keyword)-1] = '\0';
8951 pgm = mail_newsearchpgm();
8952 if(not){
8953 pgm->unkeyword = mail_newstringlist();
8954 pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword);
8955 pgm->unkeyword->text.size = strlen(keyword);
8957 else{
8958 pgm->keyword = mail_newstringlist();
8959 pgm->keyword->text.data = (unsigned char *) cpystr(keyword);
8960 pgm->keyword->text.size = strlen(keyword);
8963 if(ps_global && ps_global->ttyo){
8964 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8965 ps_global->mangled_footer = 1;
8968 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8970 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8971 pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE);
8972 /* we know this was freed in mail_search, let caller know */
8973 if(limitsrch)
8974 *limitsrch = NULL;
8976 if(we_cancel)
8977 cancel_busy_cue(0);
8979 return(0);
8984 * Allow user to choose a keyword from their list of keywords.
8986 * Returns an allocated keyword on success, NULL otherwise.
8988 char *
8989 choose_a_keyword(void)
8991 char *choice = NULL;
8992 char **keyword_list, **lp;
8993 int cnt;
8994 KEYWORD_S *kw;
8997 * Build a list of keywords to choose from.
9000 for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next)
9001 cnt++;
9003 if(cnt <= 0){
9004 q_status_message(SM_ORDER, 3, 4,
9005 _("No keywords defined, use \"keywords\" option in Setup/Config"));
9006 return(choice);
9009 lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list));
9010 memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list));
9012 for(kw = ps_global->keywords; kw; kw = kw->next)
9013 *lp++ = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9015 /* TRANSLATORS: SELECT A KEYWORD is a screen title
9016 TRANSLATORS: Print something1 using something2.
9017 "keywords" is something1 */
9018 choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"),
9019 _("keywords"), h_select_keyword_screen,
9020 _("HELP FOR SELECTING A KEYWORD"), NULL);
9022 if(!choice)
9023 q_status_message(SM_ORDER, 1, 4, "No choice");
9025 free_list_array(&keyword_list);
9027 return(choice);
9032 * Allow user to choose a list of keywords from their list of keywords.
9034 * Returns allocated list.
9036 char **
9037 choose_list_of_keywords(void)
9039 LIST_SEL_S *listhead, *ls, *p;
9040 char **ret = NULL;
9041 int cnt, i;
9042 KEYWORD_S *kw;
9045 * Build a list of keywords to choose from.
9048 p = listhead = NULL;
9049 for(kw = ps_global->keywords; kw; kw = kw->next){
9051 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9052 memset(ls, 0, sizeof(*ls));
9053 ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9055 if(p){
9056 p->next = ls;
9057 p = p->next;
9059 else
9060 listhead = p = ls;
9063 if(!listhead)
9064 return(ret);
9066 /* TRANSLATORS: SELECT KEYWORDS is a screen title
9067 Print something1 using something2.
9068 "keywords" is something1 */
9069 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9070 _("SELECT KEYWORDS"), _("keywords"),
9071 h_select_multkeyword_screen,
9072 _("HELP FOR SELECTING KEYWORDS"), NULL)){
9073 for(cnt = 0, p = listhead; p; p = p->next)
9074 if(p->selected)
9075 cnt++;
9077 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9078 memset(ret, 0, (cnt+1) * sizeof(*ret));
9079 for(i = 0, p = listhead; p; p = p->next)
9080 if(p->selected)
9081 ret[i++] = cpystr(p->item ? p->item : "");
9084 free_list_sel(&listhead);
9086 return(ret);
9091 * Allow user to choose a charset
9093 * Returns an allocated charset on success, NULL otherwise.
9095 char *
9096 choose_a_charset(int which_charsets)
9098 char *choice = NULL;
9099 char **charset_list, **lp;
9100 const CHARSET *cs;
9101 int cnt;
9104 * Build a list of charsets to choose from.
9107 for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){
9108 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9109 && ((which_charsets & CAC_ALL)
9110 || (which_charsets & CAC_POSTING
9111 && cs->flags & CF_POSTING)
9112 || (which_charsets & CAC_DISPLAY
9113 && cs->type != CT_2022
9114 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9115 cnt++;
9118 if(cnt <= 0){
9119 q_status_message(SM_ORDER, 3, 4,
9120 _("No charsets found? Enter charset manually."));
9121 return(choice);
9124 lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list));
9125 memset(charset_list, 0, (cnt+1) * sizeof(*charset_list));
9127 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9128 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9129 && ((which_charsets & CAC_ALL)
9130 || (which_charsets & CAC_POSTING
9131 && cs->flags & CF_POSTING)
9132 || (which_charsets & CAC_DISPLAY
9133 && cs->type != CT_2022
9134 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9135 *lp++ = cpystr(cs->name);
9138 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
9139 TRANSLATORS: Print something1 using something2.
9140 "character sets" is something1 */
9141 choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"),
9142 _("character sets"), h_select_charset_screen,
9143 _("HELP FOR SELECTING A CHARACTER SET"), NULL);
9145 if(!choice)
9146 q_status_message(SM_ORDER, 1, 4, "No choice");
9148 free_list_array(&charset_list);
9150 return(choice);
9155 * Allow user to choose a list of character sets and/or scripts
9157 * Returns allocated list.
9159 char **
9160 choose_list_of_charsets(void)
9162 LIST_SEL_S *listhead, *ls, *p;
9163 char **ret = NULL;
9164 int cnt, i, got_one;
9165 const CHARSET *cs;
9166 SCRIPT *s;
9167 char *q, *t;
9168 long width, limit;
9169 char buf[1024], *folded;
9172 * Build a list of charsets to choose from.
9175 p = listhead = NULL;
9177 /* this width is determined by select_from_list_screen() */
9178 width = ps_global->ttyo->screen_cols - 4;
9180 /* first comes a list of scripts (sets of character sets) */
9181 for(s = utf8_script(NIL); s && s->name; s++){
9183 limit = sizeof(buf)-1;
9184 q = buf;
9185 memset(q, 0, limit+1);
9187 if(s->name)
9188 sstrncpy(&q, s->name, limit);
9190 if(s->description){
9191 sstrncpy(&q, " (", limit-(q-buf));
9192 sstrncpy(&q, s->description, limit-(q-buf));
9193 sstrncpy(&q, ")", limit-(q-buf));
9196 /* add the list of charsets that are in this script */
9197 got_one = 0;
9198 for(cs = utf8_charset(NIL);
9199 cs && cs->name && (q-buf) < limit; cs++){
9200 if(cs->script & s->script){
9202 * Filter out some un-useful members of the list.
9203 * UTF-7 and UTF-8 weren't actually in the list at the
9204 * time this was written. Just making sure.
9206 if(!strucmp(cs->name, "ISO-2022-JP-2")
9207 || !strucmp(cs->name, "UTF-7")
9208 || !strucmp(cs->name, "UTF-8"))
9209 continue;
9211 if(got_one)
9212 sstrncpy(&q, " ", limit-(q-buf));
9213 else{
9214 got_one = 1;
9215 sstrncpy(&q, " {", limit-(q-buf));
9218 sstrncpy(&q, cs->name, limit-(q-buf));
9222 if(got_one)
9223 sstrncpy(&q, "}", limit-(q-buf));
9225 /* fold this line so that it can all be seen on the screen */
9226 folded = fold(buf, width, width, "", " ", FLD_NONE);
9227 if(folded){
9228 t = folded;
9229 while(t && *t && (q = strindex(t, '\n')) != NULL){
9230 *q = '\0';
9232 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9233 memset(ls, 0, sizeof(*ls));
9234 if(t == folded)
9235 ls->item = cpystr(s->name);
9236 else
9237 ls->flags = SFL_NOSELECT;
9239 ls->display_item = cpystr(t);
9241 t = q+1;
9243 if(p){
9244 p->next = ls;
9245 p = p->next;
9247 else{
9248 /* add a heading */
9249 listhead = (LIST_SEL_S *) fs_get(sizeof(*ls));
9250 memset(listhead, 0, sizeof(*listhead));
9251 listhead->flags = SFL_NOSELECT;
9252 listhead->display_item =
9253 cpystr(_("Scripts representing groups of related character sets"));
9254 listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls));
9255 memset(listhead->next, 0, sizeof(*listhead));
9256 listhead->next->flags = SFL_NOSELECT;
9257 listhead->next->display_item =
9258 cpystr(repeat_char(width, '-'));
9260 listhead->next->next = ls;
9261 p = ls;
9265 fs_give((void **) &folded);
9269 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9270 memset(ls, 0, sizeof(*ls));
9271 ls->flags = SFL_NOSELECT;
9272 if(p){
9273 p->next = ls;
9274 p = p->next;
9276 else
9277 listhead = p = ls;
9279 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9280 memset(ls, 0, sizeof(*ls));
9281 ls->flags = SFL_NOSELECT;
9282 ls->display_item =
9283 cpystr(_("Individual character sets, may be mixed with scripts"));
9284 p->next = ls;
9285 p = p->next;
9287 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9288 memset(ls, 0, sizeof(*ls));
9289 ls->flags = SFL_NOSELECT;
9290 ls->display_item =
9291 cpystr(repeat_char(width, '-'));
9292 p->next = ls;
9293 p = p->next;
9295 /* then comes a list of individual character sets */
9296 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9297 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9298 memset(ls, 0, sizeof(*ls));
9299 ls->item = cpystr(cs->name);
9301 if(p){
9302 p->next = ls;
9303 p = p->next;
9305 else
9306 listhead = p = ls;
9309 if(!listhead)
9310 return(ret);
9312 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9313 Print something1 using something2.
9314 "character sets" is something1 */
9315 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9316 _("SELECT CHARACTER SETS"), _("character sets"),
9317 h_select_multcharsets_screen,
9318 _("HELP FOR SELECTING CHARACTER SETS"), NULL)){
9319 for(cnt = 0, p = listhead; p; p = p->next)
9320 if(p->selected)
9321 cnt++;
9323 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9324 memset(ret, 0, (cnt+1) * sizeof(*ret));
9325 for(i = 0, p = listhead; p; p = p->next)
9326 if(p->selected)
9327 ret[i++] = cpystr(p->item ? p->item : "");
9330 free_list_sel(&listhead);
9332 return(ret);
9335 /* Report quota summary resources in an IMAP server */
9337 void cmd_quota (struct pine *state)
9339 QUOTALIST *imapquota;
9340 NETMBX mb;
9341 STORE_S *store;
9342 SCROLL_S sargs;
9344 if(!state->mail_stream || !is_imap_stream(state->mail_stream)){
9345 q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders");
9346 return;
9349 if (state->mail_stream
9350 && !sp_dead_stream(state->mail_stream)
9351 && state->mail_stream->mailbox
9352 && *state->mail_stream->mailbox
9353 && mail_valid_net_parse(state->mail_stream->mailbox, &mb))
9354 imap_getquotaroot(state->mail_stream, mb.mailbox);
9356 if(!state->quota) /* failed ? */
9357 return; /* go back... */
9359 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
9360 q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space.");
9361 return;
9364 so_puts(store, "Quota Report for ");
9365 so_puts(store, state->mail_stream->original_mailbox);
9366 so_puts(store, "\n\n");
9368 for (imapquota = state->quota; imapquota; imapquota = imapquota->next){
9370 so_puts(store, _("Resource : "));
9371 so_puts(store, imapquota->name);
9372 so_writec('\n', store);
9374 so_puts(store, _("Usage : "));
9375 so_puts(store, long2string(imapquota->usage));
9376 if(!strucmp(imapquota->name,"STORAGE"))
9377 so_puts(store, " KiB ");
9378 if(!strucmp(imapquota->name,"MESSAGE")){
9379 so_puts(store, _(" message"));
9380 if(imapquota->usage != 1)
9381 so_puts(store, _("s ")); /* plural */
9382 else
9383 so_puts(store, _(" "));
9385 so_writec('(', store);
9386 so_puts(store, long2string(100*imapquota->usage/imapquota->limit));
9387 so_puts(store, "%)\n");
9389 so_puts(store, _("Limit : "));
9390 so_puts(store, long2string(imapquota->limit));
9391 if(!strucmp(imapquota->name,"STORAGE"))
9392 so_puts(store, " KiB\n\n");
9393 if(!strucmp(imapquota->name,"MESSAGE")){
9394 so_puts(store, _(" message"));
9395 if(imapquota->usage != 1)
9396 so_puts(store, _("s\n\n")); /* plural */
9397 else
9398 so_puts(store, _("\n\n"));
9402 memset(&sargs, 0, sizeof(SCROLL_S));
9403 sargs.text.text = so_text(store);
9404 sargs.text.src = CharStar;
9405 sargs.text.desc = _("Quota Resources Summary");
9406 sargs.bar.title = _("QUOTA SUMMARY");
9407 sargs.proc.tool = NULL;
9408 sargs.help.text = h_quota_command;
9409 sargs.help.title = NULL;
9410 sargs.keys.menu = NULL;
9411 setbitmap(sargs.keys.bitmap);
9413 scrolltool(&sargs);
9414 so_give(&store);
9416 if (state->quota)
9417 mail_free_quotalist(&(state->quota));
9420 /*----------------------------------------------------------------------
9421 Prompt the user for the type of sort he desires
9423 Args: state -- pine state pointer
9424 q1 -- Line to prompt on
9426 Returns 0 if it was cancelled, 1 otherwise.
9427 ----*/
9429 select_sort(struct pine *state, int ql, SortOrder *sort, int *rev)
9431 char prompt[200], tmp[3], *p;
9432 int s, i;
9433 int deefault = 'a', retval = 1;
9434 HelpType help;
9435 ESCKEY_S sorts[14];
9437 #ifdef _WINDOWS
9438 DLG_SORTPARAM sortsel;
9440 if (mswin_usedialog ()) {
9442 sortsel.reverse = mn_get_revsort (state->msgmap);
9443 sortsel.cursort = mn_get_sort (state->msgmap);
9444 /* assumption here that HelpType is char ** */
9445 sortsel.helptext = h_select_sort;
9446 sortsel.rval = 0;
9448 if ((retval = os_sortdialog (&sortsel))) {
9449 *sort = sortsel.cursort;
9450 *rev = sortsel.reverse;
9453 return (retval);
9455 #endif
9457 /*----- String together the prompt ------*/
9458 tmp[1] = '\0';
9459 if(F_ON(F_USE_FK,ps_global))
9460 strncpy(prompt, _("Choose type of sort : "), sizeof(prompt));
9461 else
9462 strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "),
9463 sizeof(prompt));
9465 for(i = 0; state->sort_types[i] != EndofList; i++) {
9466 sorts[i].rval = i;
9467 p = sorts[i].label = sort_name(state->sort_types[i]);
9468 while(*(p+1) && islower((unsigned char)*p))
9469 p++;
9471 sorts[i].ch = tolower((unsigned char)(tmp[0] = *p));
9472 sorts[i].name = cpystr(tmp);
9474 if(mn_get_sort(state->msgmap) == state->sort_types[i])
9475 deefault = sorts[i].rval;
9478 sorts[i].ch = 'r';
9479 sorts[i].rval = 'r';
9480 sorts[i].name = cpystr("R");
9481 if(F_ON(F_USE_FK,ps_global))
9482 sorts[i].label = N_("Reverse");
9483 else
9484 sorts[i].label = "";
9486 sorts[++i].ch = -1;
9487 help = h_select_sort;
9489 if((F_ON(F_USE_FK,ps_global)
9490 && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x',
9491 help,RB_NORM)) != 'x'))
9493 (F_OFF(F_USE_FK,ps_global)
9494 && ((s = radio_buttons(prompt,ql,sorts,deefault,'x',
9495 help,RB_NORM)) != 'x'))){
9496 state->mangled_body = 1; /* signal screen's changed */
9497 if(s == 'r')
9498 *rev = !mn_get_revsort(state->msgmap);
9499 else
9500 *sort = state->sort_types[s];
9502 if(F_ON(F_SHOW_SORT, ps_global))
9503 ps_global->mangled_header = 1;
9505 else{
9506 retval = 0;
9507 cmd_cancelled("Sort");
9510 while(--i >= 0)
9511 fs_give((void **)&sorts[i].name);
9513 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9514 return(retval);
9518 /*---------------------------------------------------------------------
9519 Build list of folders in the given context for user selection
9521 Args: c -- pointer to pointer to folder's context context
9522 f -- folder prefix to display
9523 sublist -- whether or not to use 'f's contents as prefix
9524 lister -- function used to do the actual display
9526 Returns: malloc'd string containing sequence, else NULL if
9527 no messages in msgmap with local "selected" flag.
9528 ----*/
9530 display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int))
9532 int rc;
9533 CONTEXT_S *tc;
9534 void (*redraw)(void) = ps_global->redrawer;
9536 push_titlebar_state();
9537 tc = *c;
9538 if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0)
9539 *c = tc;
9541 ClearScreen();
9542 pop_titlebar_state();
9543 redraw_titlebar();
9544 if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
9545 (*ps_global->redrawer)();
9547 if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
9548 return(1);
9550 return(0);
9555 * Allow user to choose a single item from a list of strings.
9557 * Args list -- Array of strings to choose from, NULL terminated.
9558 * displist -- Array of strings to display instead of displaying list.
9559 * Indices correspond to the list array. Display the displist
9560 * but return the item from list if displist non-NULL.
9561 * title -- For conf_scroll_screen
9562 * pdesc -- For conf_scroll_screen
9563 * help -- For conf_scroll_screen
9564 * htitle -- For conf_scroll_screen
9566 * Returns an allocated copy of the chosen item or NULL.
9568 char *
9569 choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help,
9570 char *htitle, char *cursor_location)
9572 LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL;
9573 char **t, **dl;
9574 char *ret = NULL, *choice = NULL;
9576 /* build the LIST_SEL_S list */
9577 p = listhead = NULL;
9578 for(t = list, dl = displist; *t; t++, dl++){
9579 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9580 memset(ls, 0, sizeof(*ls));
9581 ls->item = cpystr(*t);
9582 if(displist)
9583 ls->display_item = cpystr(*dl);
9585 if(cursor_location && (cursor_location == (*t)))
9586 starting_val = ls;
9588 if(p){
9589 p->next = ls;
9590 p = p->next;
9592 else
9593 listhead = p = ls;
9596 if(!listhead)
9597 return(ret);
9599 if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc,
9600 help, htitle, starting_val))
9601 for(p = listhead; !choice && p; p = p->next)
9602 if(p->selected)
9603 choice = p->item;
9605 if(choice)
9606 ret = cpystr(choice);
9608 free_list_sel(&listhead);
9610 return(ret);
9614 void
9615 free_list_sel(LIST_SEL_S **lsel)
9617 if(lsel && *lsel){
9618 free_list_sel(&(*lsel)->next);
9619 if((*lsel)->item)
9620 fs_give((void **) &(*lsel)->item);
9622 if((*lsel)->display_item)
9623 fs_give((void **) &(*lsel)->display_item);
9625 fs_give((void **) lsel);
9631 * file_lister - call pico library's file lister
9634 file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags)
9636 PICO pbf;
9637 int rv;
9638 void (*redraw)(void) = ps_global->redrawer;
9640 standard_picobuf_setup(&pbf);
9641 push_titlebar_state();
9642 if(!newmail)
9643 pbf.newmail = NULL;
9645 /* BUG: what about help command and text? */
9646 pbf.pine_anchor = title;
9648 rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags);
9649 standard_picobuf_teardown(&pbf);
9650 fix_windsize(ps_global);
9651 init_signals(); /* has it's own signal stuff */
9653 /* Restore display's titlebar and body */
9654 pop_titlebar_state();
9655 redraw_titlebar();
9656 if((ps_global->redrawer = redraw) != NULL)
9657 (*ps_global->redrawer)();
9659 return(rv);
9663 /*----------------------------------------------------------------------
9664 Print current folder index
9666 ---*/
9668 print_index(struct pine *state, MSGNO_S *msgmap, int agg)
9670 long i;
9671 ICE_S *ice;
9672 char buf[MAX_SCREEN_COLS+1];
9674 for(i = 1L; i <= mn_get_total(msgmap); i++){
9675 if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
9676 continue;
9678 if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0))
9679 continue;
9681 ice = build_header_line(state, state->mail_stream, msgmap, i, NULL);
9683 if(ice){
9685 * I don't understand why we'd want to mark the current message
9686 * instead of printing out the first character of the status
9687 * so I'm taking it out and including the first character of the
9688 * line instead. Hubert 2006-02-09
9690 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
9691 return(0);
9694 if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i),
9695 print_char)
9696 || !gf_puts(NEWLINE, print_char))
9697 return(0);
9701 return(1);
9705 #ifdef _WINDOWS
9708 * windows callback to get/set header mode state
9711 header_mode_callback(set, args)
9712 int set;
9713 long args;
9715 return(ps_global->full_header);
9720 * windows callback to get/set zoom mode state
9723 zoom_mode_callback(set, args)
9724 int set;
9725 long args;
9727 return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0);
9732 * windows callback to get/set zoom mode state
9735 any_selected_callback(set, args)
9736 int set;
9737 long args;
9739 return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0);
9747 flag_callback(set, flags)
9748 int set;
9749 long flags;
9751 MESSAGECACHE *mc;
9752 int newflags = 0;
9753 long msgno;
9754 int permflag = 0;
9756 switch (set) {
9757 case 1: /* Important */
9758 permflag = ps_global->mail_stream->perm_flagged;
9759 break;
9761 case 2: /* New */
9762 permflag = ps_global->mail_stream->perm_seen;
9763 break;
9765 case 3: /* Answered */
9766 permflag = ps_global->mail_stream->perm_answered;
9767 break;
9769 case 4: /* Deleted */
9770 permflag = ps_global->mail_stream->perm_deleted;
9771 break;
9775 if(!(any_messages(ps_global->msgmap, NULL, "to Flag")
9776 && can_set_flag(ps_global, "flag", permflag)))
9777 return(0);
9779 if(sp_io_error_on_stream(ps_global->mail_stream)){
9780 sp_set_io_error_on_stream(ps_global->mail_stream, 0);
9781 pine_mail_check(ps_global->mail_stream); /* forces write */
9782 return(0);
9785 msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
9786 if(msgno > 0L && ps_global->mail_stream
9787 && msgno <= ps_global->mail_stream->nmsgs
9788 && (mc = mail_elt(ps_global->mail_stream, msgno))
9789 && mc->valid){
9791 * NOTE: code below is *VERY* sensitive to the order of
9792 * the messages defined in resource.h for flag handling.
9793 * Don't change it unless you know what you're doing.
9795 if(set){
9796 char *flagstr;
9797 long mflag;
9799 switch(set){
9800 case 1 : /* Important */
9801 flagstr = "\\FLAGGED";
9802 mflag = (mc->flagged) ? 0L : ST_SET;
9803 break;
9805 case 2 : /* New */
9806 flagstr = "\\SEEN";
9807 mflag = (mc->seen) ? 0L : ST_SET;
9808 break;
9810 case 3 : /* Answered */
9811 flagstr = "\\ANSWERED";
9812 mflag = (mc->answered) ? 0L : ST_SET;
9813 break;
9815 case 4 : /* Deleted */
9816 flagstr = "\\DELETED";
9817 mflag = (mc->deleted) ? 0L : ST_SET;
9818 break;
9820 default : /* bogus */
9821 return(0);
9824 mail_flag(ps_global->mail_stream, long2string(msgno),
9825 flagstr, mflag);
9827 if(ps_global->redrawer)
9828 (*ps_global->redrawer)();
9830 else{
9831 /* Important */
9832 if(mc->flagged)
9833 newflags |= 0x0001;
9835 /* New */
9836 if(!mc->seen)
9837 newflags |= 0x0002;
9839 /* Answered */
9840 if(mc->answered)
9841 newflags |= 0x0004;
9843 /* Deleted */
9844 if(mc->deleted)
9845 newflags |= 0x0008;
9849 return(newflags);
9855 * BUG: Should teach this about keywords
9857 MPopup *
9858 flag_submenu(mc)
9859 MESSAGECACHE *mc;
9861 static MPopup flag_submenu[] = {
9862 {tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}},
9863 {tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}},
9864 {tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}},
9865 {tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}},
9866 {tTail}
9869 /* Important */
9870 flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal;
9872 /* New */
9873 flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked;
9875 /* Answered */
9876 flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal;
9878 /* Deleted */
9879 flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal;
9881 return(flag_submenu);
9884 #endif /* _WINDOWS */