* When a filename is attached and its name is encoded, the save attachment
[alpine.git] / alpine / mailcmd.c
blob5e95d3347f6101b849a81bae1cd8b3473f079040
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-2016 Eduardo Chappa
8 * Copyright 2006-2009 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
20 mailcmd.c
21 The meat and pototoes of mail processing here:
22 - initial command processing and dispatch
23 - save message
24 - capture address off incoming mail
25 - jump to specific numbered message
26 - open (broach) a new folder
27 - search message headers (where is) command
28 ====*/
31 #include "headers.h"
32 #include "mailcmd.h"
33 #include "status.h"
34 #include "mailview.h"
35 #include "flagmaint.h"
36 #include "listsel.h"
37 #include "keymenu.h"
38 #include "alpine.h"
39 #include "mailpart.h"
40 #include "mailindx.h"
41 #include "folder.h"
42 #include "reply.h"
43 #include "help.h"
44 #include "titlebar.h"
45 #include "signal.h"
46 #include "radio.h"
47 #include "pipe.h"
48 #include "send.h"
49 #include "takeaddr.h"
50 #include "roleconf.h"
51 #include "smime.h"
52 #include "../pith/state.h"
53 #include "../pith/msgno.h"
54 #include "../pith/store.h"
55 #include "../pith/thread.h"
56 #include "../pith/flag.h"
57 #include "../pith/sort.h"
58 #include "../pith/maillist.h"
59 #include "../pith/save.h"
60 #include "../pith/pipe.h"
61 #include "../pith/news.h"
62 #include "../pith/util.h"
63 #include "../pith/sequence.h"
64 #include "../pith/keyword.h"
65 #include "../pith/stream.h"
66 #include "../pith/mailcmd.h"
67 #include "../pith/hist.h"
68 #include "../pith/list.h"
69 #include "../pith/icache.h"
70 #include "../pith/busy.h"
71 #include "../pith/mimedesc.h"
72 #include "../pith/pattern.h"
73 #include "../pith/tempfile.h"
74 #include "../pith/search.h"
75 #include "../pith/margin.h"
76 #ifdef _WINDOWS
77 #include "../pico/osdep/mswin.h"
78 #endif
81 * Internal Prototypes
83 int cmd_flag(struct pine *, MSGNO_S *, int);
84 int cmd_flag_prompt(struct pine *, struct flag_screen *, int);
85 void free_flag_table(struct flag_table **);
86 int cmd_reply(struct pine *, MSGNO_S *, int);
87 int cmd_forward(struct pine *, MSGNO_S *, int);
88 int cmd_bounce(struct pine *, MSGNO_S *, int);
89 int cmd_save(struct pine *, MAILSTREAM *, MSGNO_S *, int, CmdWhere);
90 void role_compose(struct pine *);
91 int cmd_expunge(struct pine *, MAILSTREAM *, MSGNO_S *, int);
92 int cmd_export(struct pine *, MSGNO_S *, int, int);
93 char *cmd_delete_action(struct pine *, MSGNO_S *, CmdWhere);
94 char *cmd_delete_view(struct pine *, MSGNO_S *);
95 char *cmd_delete_index(struct pine *, MSGNO_S *);
96 long get_level(int, UCS, SCROLL_S *);
97 long closest_jump_target(long, MAILSTREAM *, MSGNO_S *, int, CmdWhere, char *, size_t);
98 int update_folder_spec(char *, size_t, char *);
99 int cmd_print(struct pine *, MSGNO_S *, int, CmdWhere);
100 int cmd_pipe(struct pine *, MSGNO_S *, int);
101 STORE_S *list_mgmt_text(RFC2369_S *, long);
102 void list_mgmt_screen(STORE_S *);
103 int aggregate_select(struct pine *, MSGNO_S *, int, CmdWhere);
104 int select_by_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
105 int select_by_thrd_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
106 int select_by_date(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
107 int select_by_text(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
108 int select_by_size(MAILSTREAM *, SEARCHSET **);
109 SEARCHSET *visible_searchset(MAILSTREAM *, MSGNO_S *);
110 int select_by_status(MAILSTREAM *, SEARCHSET **);
111 int select_by_rule(MAILSTREAM *, SEARCHSET **);
112 int select_by_thread(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
113 char *choose_a_rule(int);
114 int select_by_keyword(MAILSTREAM *, SEARCHSET **);
115 char *choose_a_keyword(void);
116 int select_sort(struct pine *, int, SortOrder *, int *);
117 int print_index(struct pine *, MSGNO_S *, int);
120 * List of Select options used by apply_* functions...
122 static char *sel_pmt1 = N_("ALTER message selection : ");
123 ESCKEY_S sel_opts1[] = {
124 /* TRANSLATORS: these are keymenu names for selecting. Broaden selection means
125 we will add more messages to the selection, Narrow selection means we will
126 remove some selections (like a logical AND instead of logical OR), and Flip
127 Selected means that all the messages that are currently selected become unselected,
128 and all the unselected messages become selected. */
129 {'a', 'a', "A", N_("unselect All")},
130 {'c', 'c', "C", NULL},
131 {'b', 'b', "B", N_("Broaden selctn")},
132 {'n', 'n', "N", N_("Narrow selctn")},
133 {'f', 'f', "F", N_("Flip selected")},
134 {-1, 0, NULL, NULL}
138 #define SEL_OPTS_THREAD 9 /* index number of "tHread" */
139 #define SEL_OPTS_THREAD_CH 'h'
141 char *sel_pmt2 = "SELECT criteria : ";
142 static ESCKEY_S sel_opts2[] = {
143 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
144 means select the currently highlighted message; select by Number is by message
145 number; Status is by status of the message, for example the message might be
146 New or it might be Unseen or marked Important; Size has the Z upper case because
147 it is a Z command; Keyword is an alpine keyword that has been set by the user;
148 and Rule is an alpine rule */
149 {'a', 'a', "A", N_("select All")},
150 {'c', 'c', "C", N_("select Cur")},
151 {'n', 'n', "N", N_("Number")},
152 {'d', 'd', "D", N_("Date")},
153 {'t', 't', "T", N_("Text")},
154 {'s', 's', "S", N_("Status")},
155 {'z', 'z', "Z", N_("siZe")},
156 {'k', 'k', "K", N_("Keyword")},
157 {'r', 'r', "R", N_("Rule")},
158 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
159 {-1, 0, NULL, NULL}
163 static ESCKEY_S sel_opts3[] = {
164 /* TRANSLATORS: these are operations we can do on a set of selected messages.
165 Del is Delete; Undel is Undelete; TakeAddr means to Take some Addresses into
166 the address book; Save means to save the messages into another alpine folder;
167 Export means to copy the messages to a file outside of alpine, external to
168 alpine's world. */
169 {'d', 'd', "D", N_("Del")},
170 {'u', 'u', "U", N_("Undel")},
171 {'r', 'r', "R", N_("Reply")},
172 {'f', 'f', "F", N_("Forward")},
173 {'%', '%', "%", N_("Print")},
174 {'t', 't', "T", N_("TakeAddr")},
175 {'s', 's', "S", N_("Save")},
176 {'e', 'e', "E", N_("Export")},
177 { -1, 0, NULL, NULL},
178 { -1, 0, NULL, NULL},
179 { -1, 0, NULL, NULL},
180 { -1, 0, NULL, NULL},
181 { -1, 0, NULL, NULL},
182 { -1, 0, NULL, NULL},
183 { -1, 0, NULL, NULL},
184 {-1, 0, NULL, NULL}
187 static ESCKEY_S sel_opts4[] = {
188 {'a', 'a', "A", N_("select All")},
189 /* TRANSLATORS: select currrently highlighted message Thread */
190 {'c', 'c', "C", N_("select Curthrd")},
191 {'n', 'n', "N", N_("Number")},
192 {'d', 'd', "D", N_("Date")},
193 {'t', 't', "T", N_("Text")},
194 {'s', 's', "S", N_("Status")},
195 {'z', 'z', "Z", N_("siZe")},
196 {'k', 'k', "K", N_("Keyword")},
197 {'r', 'r', "R", N_("Rule")},
198 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
199 {-1, 0, NULL, NULL}
203 static char *sel_flag =
204 N_("Select New, Deleted, Answered, Forwarded, or Important messages ? ");
205 static char *sel_flag_not =
206 N_("Select NOT New, NOT Deleted, NOT Answered, NOT Forwarded or NOT Important msgs ? ");
207 static ESCKEY_S sel_flag_opt[] = {
208 /* TRANSLATORS: When selecting messages by message Status these are the
209 different types of Status you can select on. Is the message New, Recent,
210 and so on. Not means flip the meaning of the selection to the opposite
211 thing, so message is not New or not Important. */
212 {'n', 'n', "N", N_("New")},
213 {'*', '*', "*", N_("Important")},
214 {'d', 'd', "D", N_("Deleted")},
215 {'a', 'a', "A", N_("Answered")},
216 {'f', 'f', "F", N_("Forwarded")},
217 {-2, 0, NULL, NULL},
218 {'!', '!', "!", N_("Not")},
219 {-2, 0, NULL, NULL},
220 {'r', 'r', "R", N_("Recent")},
221 {'u', 'u', "U", N_("Unseen")},
222 {-1, 0, NULL, NULL}
226 static ESCKEY_S sel_date_opt[] = {
227 {0, 0, NULL, NULL},
228 /* TRANSLATORS: options when selecting messages by Date */
229 {ctrl('P'), 12, "^P", N_("Prev Day")},
230 {ctrl('N'), 13, "^N", N_("Next Day")},
231 {ctrl('X'), 11, "^X", N_("Cur Msg")},
232 {ctrl('W'), 14, "^W", N_("Toggle When")},
233 {KEY_UP, 12, "", ""},
234 {KEY_DOWN, 13, "", ""},
235 {-1, 0, NULL, NULL}
239 static char *sel_text =
240 N_("Select based on To, From, Cc, Recip, Partic, Subject fields or All msg text ? ");
241 static char *sel_text_not =
242 N_("Select based on NOT To, From, Cc, Recip, Partic, Subject or All msg text ? ");
243 static ESCKEY_S sel_text_opt[] = {
244 /* TRANSLATORS: Select messages based on the text contained in the From line, or
245 the Subject line, and so on. */
246 {'f', 'f', "F", N_("From")},
247 {'s', 's', "S", N_("Subject")},
248 {'t', 't', "T", N_("To")},
249 {'a', 'a', "A", N_("All Text")},
250 {'c', 'c', "C", N_("Cc")},
251 {'!', '!', "!", N_("Not")},
252 {'r', 'r', "R", N_("Recipient")},
253 {'p', 'p', "P", N_("Participant")},
254 {'b', 'b', "B", N_("Body")},
255 {'h', 'h', "H", N_("Header")},
256 {-1, 0, NULL, NULL}
259 static ESCKEY_S choose_action[] = {
260 {'c', 'c', "C", N_("Compose")},
261 {'r', 'r', "R", N_("Reply")},
262 {'f', 'f', "F", N_("Forward")},
263 {'b', 'b', "B", N_("Bounce")},
264 {-1, 0, NULL, NULL}
267 static char *select_num =
268 N_("Enter comma-delimited list of numbers (dash between ranges): ");
270 static char *select_size_larger_msg =
271 N_("Select messages with size larger than: ");
273 static char *select_size_smaller_msg =
274 N_("Select messages with size smaller than: ");
276 static char *sel_size_larger = N_("Larger");
277 static char *sel_size_smaller = N_("Smaller");
278 static ESCKEY_S sel_size_opt[] = {
279 {0, 0, NULL, NULL},
280 {ctrl('W'), 14, "^W", NULL},
281 {-1, 0, NULL, NULL}
284 static ESCKEY_S sel_key_opt[] = {
285 {0, 0, NULL, NULL},
286 {ctrl('T'), 14, "^T", N_("To List")},
287 {0, 0, NULL, NULL},
288 {'!', '!', "!", N_("Not")},
289 {-1, 0, NULL, NULL}
292 static ESCKEY_S flag_text_opt[] = {
293 /* TRANSLATORS: these are types of flags (markers) that the user can
294 set. For example, they can flag the message as an important message. */
295 {'n', 'n', "N", N_("New")},
296 {'*', '*', "*", N_("Important")},
297 {'d', 'd', "D", N_("Deleted")},
298 {'a', 'a', "A", N_("Answered")},
299 {'f', 'f', "F", N_("Forwarded")},
300 {'!', '!', "!", N_("Not")},
301 {ctrl('T'), 10, "^T", N_("To Flag Details")},
302 {-1, 0, NULL, NULL}
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);
693 break;
696 /*---------- Forward message ----------*/
697 case MC_FORWARD :
698 (void) cmd_forward(state, msgmap, MCMD_NONE);
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);
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)
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, NULL);
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)
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, NULL);
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)
2380 int rv = 0;
2381 ACTION_S *role = NULL;
2383 if(any_messages(msgmap, NULL, "to Bounce")){
2384 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2385 return rv;
2387 if(MCMD_ISAGG(aopt)){ /* check for possible role */
2388 PAT_STATE pstate;
2389 int action;
2391 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
2392 static ESCKEY_S yesno_opts[] = {
2393 {'y', 'y', "Y", N_("Yes")},
2394 {'n', 'n', "N", N_("No")},
2395 {-1, 0, NULL, NULL}
2398 action = radio_buttons(_("Bounce messages using a role? "),
2399 -FOOTER_ROWS(state), yesno_opts,
2400 'y', 'x', h_role_compose, RB_NORM);
2401 if(action == 'y'){
2402 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
2404 redraw = state->redrawer;
2405 state->redrawer = NULL;
2406 prev_screen = state->prev_screen;
2407 role = NULL;
2408 state->next_screen = SCREEN_FUN_NULL;
2410 if(role_select_screen(state, &role, MC_BOUNCE) < 0)
2411 cmd_cancelled(_("Bounce"));
2412 else{
2413 if(role)
2414 role = combine_inherited_role(role);
2415 else{
2416 role = (ACTION_S *) fs_get(sizeof(*role));
2417 memset((void *) role, 0, sizeof(*role));
2418 role->nick = cpystr("Default Role");
2422 if(redraw)
2423 (*redraw)();
2425 state->next_screen = prev_screen;
2426 state->redrawer = redraw;
2427 state->mangled_screen = 1;
2432 #ifdef SMIME
2433 /* When we bounce a message, we will leave the original message
2434 * intact, which means that it will not be signed or encrypted,
2435 * so we turn off signing and encrypting now. It will be turned
2436 * on again in send_exit_for_pico().
2438 if(ps_global->smime)
2439 ps_global->smime->do_sign = ps_global->smime->do_encrypt = 0;
2440 #endif /* SMIME */
2442 rv = bounce(state, role);
2444 if(role)
2445 free_action(&role);
2447 if(MCMD_ISAGG(aopt))
2448 restore_selected(msgmap);
2450 state->mangled_footer = 1;
2453 return rv;
2457 /*----------------------------------------------------------------------
2458 Execute save message command: prompt for folder and call function to save
2460 Args: screen_line -- Line on the screen to prompt on
2461 message -- The MESSAGECACHE entry of message to save
2463 Result: The folder lister can be called to make selection; mangled screen set
2465 This does the prompting for the folder name to save to, possibly calling
2466 up the folder display for selection of folder by user.
2467 ----*/
2469 cmd_save(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
2471 char newfolder[MAILTMPLEN], nmsgs[32], *nick;
2472 int we_cancel = 0, rv = 0, save_flags;
2473 long i, raw;
2474 CONTEXT_S *cntxt = NULL;
2475 ENVELOPE *e = NULL;
2476 SaveDel del = DontAsk;
2477 SavePreserveOrder pre = DontAskPreserve;
2479 dprint((4, "\n - saving message -\n"));
2481 if(MCMD_ISAGG(aopt) && !pseudo_selected(stream, msgmap))
2482 return rv;
2484 state->ugly_consider_advancing_bit = 0;
2485 if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM, state)
2486 && msgno_any_deletedparts(stream, msgmap)
2487 && want_to(_("Saved copy will NOT include entire message! Continue"),
2488 'y', 'n', NO_HELP, WT_FLUSH_IN | WT_SEQ_SENSITIVE) != 'y'){
2489 restore_selected(msgmap);
2490 cmd_cancelled("Save message");
2491 return rv;
2494 raw = mn_m2raw(msgmap, mn_get_cur(msgmap));
2496 if(mn_total_cur(msgmap) <= 1L){
2497 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld ", mn_get_cur(msgmap));
2498 nmsgs[sizeof(nmsgs)-1] = '\0';
2499 e = pine_mail_fetchstructure(stream, raw, NULL);
2500 if(!e) {
2501 q_status_message(SM_ORDER | SM_DING, 3, 4,
2502 _("Can't save message. Error accessing folder"));
2503 restore_selected(msgmap);
2504 return rv;
2507 else{
2508 snprintf(nmsgs, sizeof(nmsgs), "%s msgs ", comatose(mn_total_cur(msgmap)));
2509 nmsgs[sizeof(nmsgs)-1] = '\0';
2511 /* e is just used to get a default save folder from the first msg */
2512 e = pine_mail_fetchstructure(stream,
2513 mn_m2raw(msgmap, mn_first_cur(msgmap)),
2514 NULL);
2517 del = (!READONLY_FOLDER(stream) && F_OFF(F_SAVE_WONT_DELETE, ps_global))
2518 ? Del : NoDel;
2519 if(mn_total_cur(msgmap) > 1L)
2520 pre = F_OFF(F_AGG_SEQ_COPY, ps_global) ? Preserve : NoPreserve;
2521 else
2522 pre = DontAskPreserve;
2524 if(save_prompt(state, &cntxt, newfolder, sizeof(newfolder), nmsgs, e,
2525 raw, NULL, &del, &pre)){
2527 if(ps_global && ps_global->ttyo){
2528 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2529 ps_global->mangled_footer = 1;
2532 save_flags = SV_FIX_DELS;
2533 if(pre == RetPreserve)
2534 save_flags |= SV_PRESERVE;
2535 if(del == RetDel)
2536 save_flags |= SV_DELETE;
2537 if(ps_global->context_list == cntxt && !strucmp(newfolder, ps_global->inbox_name))
2538 save_flags |= SV_INBOXWOCNTXT;
2540 we_cancel = busy_cue(_("Saving"), NULL, 1);
2541 i = save(state, stream, cntxt, newfolder, msgmap, save_flags);
2542 if(we_cancel)
2543 cancel_busy_cue(0);
2545 if(i == mn_total_cur(msgmap)){
2546 rv++;
2547 if(mn_total_cur(msgmap) <= 1L){
2548 int need, avail = ps_global->ttyo->screen_cols - 2;
2549 int lennick, lenfldr;
2551 if(cntxt
2552 && ps_global->context_list->next
2553 && context_isambig(newfolder)){
2554 lennick = MIN(strlen(cntxt->nickname), 500);
2555 lenfldr = MIN(strlen(newfolder), 500);
2556 need = 27 + strlen(long2string(mn_get_cur(msgmap))) +
2557 lenfldr + lennick;
2558 if(need > avail){
2559 if(lennick > 10){
2560 need -= MIN(lennick-10, need-avail);
2561 lennick -= MIN(lennick-10, need-avail);
2564 if(need > avail && lenfldr > 10)
2565 lenfldr -= MIN(lenfldr-10, need-avail);
2568 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2569 "Message %s copied to \"%s\" in <%s>",
2570 long2string(mn_get_cur(msgmap)),
2571 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2572 lenfldr, MidDots),
2573 short_str(cntxt->nickname,
2574 (char *)(tmp_20k_buf+2000), 1000,
2575 lennick, EndDots));
2576 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2578 else if((nick=folder_is_target_of_nick(newfolder, cntxt)) != NULL){
2579 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2580 "Message %s copied to \"%s\"",
2581 long2string(mn_get_cur(msgmap)),
2582 nick);
2583 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2585 else{
2586 char *f = " folder";
2588 lenfldr = MIN(strlen(newfolder), 500);
2589 need = 28 + strlen(long2string(mn_get_cur(msgmap))) +
2590 lenfldr;
2591 if(need > avail){
2592 need -= strlen(f);
2593 f = "";
2594 if(need > avail && lenfldr > 10)
2595 lenfldr -= MIN(lenfldr-10, need-avail);
2598 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
2599 "Message %s copied to%s \"%s\"",
2600 long2string(mn_get_cur(msgmap)), f,
2601 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2602 lenfldr, MidDots));
2603 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2606 else{
2607 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s messages saved",
2608 comatose(mn_total_cur(msgmap)));
2609 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2612 if(del == RetDel){
2613 strncat(tmp_20k_buf, " and deleted", SIZEOF_20KBUF-strlen(tmp_20k_buf)-1);
2614 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2617 q_status_message(SM_ORDER, 0, 3, tmp_20k_buf);
2619 if(!MCMD_ISAGG(aopt) && F_ON(F_SAVE_ADVANCES, state)){
2620 if(sp_new_mail_count(stream))
2621 process_filter_patterns(stream, msgmap,
2622 sp_new_mail_count(stream));
2624 mn_inc_cur(stream, msgmap,
2625 (in_index == View && THREADING()
2626 && sp_viewing_a_thread(stream))
2627 ? MH_THISTHD
2628 : (in_index == View)
2629 ? MH_ANYTHD : MH_NONE);
2632 state->ugly_consider_advancing_bit = 1;
2636 if(MCMD_ISAGG(aopt)) /* straighten out fakes */
2637 restore_selected(msgmap);
2639 if(del == RetDel)
2640 update_titlebar_status(); /* make sure they see change */
2642 return rv;
2646 void
2647 role_compose(struct pine *state)
2649 int action;
2651 if(F_ON(F_ALT_ROLE_MENU, state) && mn_get_total(state->msgmap) > 0L){
2652 PAT_STATE pstate;
2654 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
2655 action = radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
2656 -FOOTER_ROWS(state), choose_action,
2657 'c', 'x', h_role_compose, RB_NORM);
2659 else{
2660 q_status_message(SM_ORDER, 0, 3,
2661 _("No roles available. Use Setup/Rules to add roles."));
2662 return;
2665 else
2666 action = 'c';
2668 if(action == 'c' || action == 'r' || action == 'f' || action == 'b'){
2669 ACTION_S *role = NULL;
2670 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
2672 redraw = state->redrawer;
2673 state->redrawer = NULL;
2674 prev_screen = state->prev_screen;
2675 role = NULL;
2676 state->next_screen = SCREEN_FUN_NULL;
2678 /* Setup role */
2679 if(role_select_screen(state, &role,
2680 action == 'f' ? MC_FORWARD :
2681 action == 'r' ? MC_REPLY :
2682 action == 'b' ? MC_BOUNCE :
2683 action == 'c' ? MC_COMPOSE : 0) < 0){
2684 cmd_cancelled(action == 'f' ? _("Forward") :
2685 action == 'r' ? _("Reply") :
2686 action == 'c' ? _("Composition") : _("Bounce"));
2687 state->next_screen = prev_screen;
2688 state->redrawer = redraw;
2689 state->mangled_screen = 1;
2691 else{
2693 * If default role was selected (NULL) we need to make
2694 * up a role which won't do anything, but will cause
2695 * compose_mail to think there's already a role so that
2696 * it won't try to confirm the default.
2698 if(role)
2699 role = combine_inherited_role(role);
2700 else{
2701 role = (ACTION_S *) fs_get(sizeof(*role));
2702 memset((void *) role, 0, sizeof(*role));
2703 role->nick = cpystr("Default Role");
2706 state->redrawer = NULL;
2707 switch(action){
2708 case 'c':
2709 compose_mail(NULL, NULL, role, NULL, NULL);
2710 break;
2712 case 'r':
2713 (void) reply(state, role);
2714 break;
2716 case 'f':
2717 (void) forward(state, role);
2718 break;
2720 case 'b':
2721 (void) bounce(state, role);
2722 break;
2725 if(role)
2726 free_action(&role);
2728 state->next_screen = prev_screen;
2729 state->redrawer = redraw;
2730 state->mangled_screen = 1;
2736 /*----------------------------------------------------------------------
2737 Do the dirty work of prompting the user for a folder name
2739 Args:
2740 nfldr should be a buffer at least MAILTMPLEN long
2741 dela -- a pointer to a SaveDel. If it is
2742 DontAsk on input, don't offer Delete prompt
2743 Del on input, offer Delete command with default of Delete
2744 NoDel NoDelete
2745 RetDel and RetNoDel are return values
2748 Result:
2750 ----*/
2752 save_prompt(struct pine *state, CONTEXT_S **cntxt, char *nfldr, size_t len_nfldr,
2753 char *nmsgs, ENVELOPE *env, long int rawmsgno, char *section,
2754 SaveDel *dela, SavePreserveOrder *prea)
2756 int rc, ku = -1, n, flags, last_rc = 0, saveable_count = 0, done = 0;
2757 int delindex, preindex, r;
2758 char prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN];
2759 char *buf = tmp_20k_buf;
2760 char shortbuf[200];
2761 char *folder;
2762 HelpType help;
2763 SaveDel del = DontAsk;
2764 SavePreserveOrder pre = DontAskPreserve;
2765 char *deltext = NULL;
2766 static HISTORY_S *history = NULL;
2767 CONTEXT_S *tc;
2768 ESCKEY_S ekey[10];
2770 if(!cntxt)
2771 alpine_panic("no context ptr in save_prompt");
2773 init_hist(&history, HISTSIZE);
2775 if(!(folder = save_get_default(state, env, rawmsgno, section, cntxt)))
2776 return(0); /* message expunged! */
2778 /* how many context's can be saved to... */
2779 for(tc = state->context_list; tc; tc = tc->next)
2780 if(!NEWS_TEST(tc))
2781 saveable_count++;
2783 /* set up extra command option keys */
2784 rc = 0;
2785 ekey[rc].ch = ctrl('T');
2786 ekey[rc].rval = 2;
2787 ekey[rc].name = "^T";
2788 /* TRANSLATORS: command means go to Folders list */
2789 ekey[rc++].label = N_("To Fldrs");
2791 if(saveable_count > 1){
2792 ekey[rc].ch = ctrl('P');
2793 ekey[rc].rval = 10;
2794 ekey[rc].name = "^P";
2795 ekey[rc++].label = N_("Prev Collection");
2797 ekey[rc].ch = ctrl('N');
2798 ekey[rc].rval = 11;
2799 ekey[rc].name = "^N";
2800 ekey[rc++].label = N_("Next Collection");
2803 if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
2804 ekey[rc].ch = TAB;
2805 ekey[rc].rval = 12;
2806 ekey[rc].name = "TAB";
2807 /* TRANSLATORS: command asks alpine to complete the name when tab is typed */
2808 ekey[rc++].label = N_("Complete");
2811 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
2812 ekey[rc].ch = ctrl('X');
2813 ekey[rc].rval = 14;
2814 ekey[rc].name = "^X";
2815 /* TRANSLATORS: list all the matches */
2816 ekey[rc++].label = N_("ListMatches");
2819 if(dela && (*dela == NoDel || *dela == Del)){
2820 ekey[rc].ch = ctrl('R');
2821 ekey[rc].rval = 15;
2822 ekey[rc].name = "^R";
2823 delindex = rc++;
2824 del = *dela;
2827 if(prea && (*prea == NoPreserve || *prea == Preserve)){
2828 ekey[rc].ch = ctrl('W');
2829 ekey[rc].rval = 16;
2830 ekey[rc].name = "^W";
2831 preindex = rc++;
2832 pre = *prea;
2835 if(saveable_count > 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2836 ekey[rc].ch = KEY_UP;
2837 ekey[rc].rval = 10;
2838 ekey[rc].name = "";
2839 ekey[rc++].label = "";
2841 ekey[rc].ch = KEY_DOWN;
2842 ekey[rc].rval = 11;
2843 ekey[rc].name = "";
2844 ekey[rc++].label = "";
2846 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2847 ekey[rc].ch = KEY_UP;
2848 ekey[rc].rval = 30;
2849 ekey[rc].name = "";
2850 ku = rc;
2851 ekey[rc++].label = "";
2853 ekey[rc].ch = KEY_DOWN;
2854 ekey[rc].rval = 31;
2855 ekey[rc].name = "";
2856 ekey[rc++].label = "";
2859 ekey[rc].ch = -1;
2861 *nfldr = '\0';
2862 help = NO_HELP;
2863 while(!done){
2864 /* only show collection number if more than one available */
2865 if(ps_global->context_list->next)
2866 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder in <%s> [%s] : ",
2867 deltext ? deltext : "",
2868 nmsgs,
2869 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2870 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2871 else
2872 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder [%s] : ",
2873 deltext ? deltext : "",
2874 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2876 prompt[sizeof(prompt)-1] = '\0';
2879 * If the prompt won't fit, try removing deltext.
2881 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && deltext){
2882 if(ps_global->context_list->next)
2883 snprintf(prompt, sizeof(prompt), "SAVE %sto folder in <%s> [%s] : ",
2884 nmsgs,
2885 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2886 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2887 else
2888 snprintf(prompt, sizeof(prompt), "SAVE %sto folder [%s] : ",
2889 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2891 prompt[sizeof(prompt)-1] = '\0';
2895 * If the prompt still won't fit, remove the extra info contained
2896 * in nmsgs.
2898 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && *nmsgs){
2899 if(ps_global->context_list->next)
2900 snprintf(prompt, sizeof(prompt), "SAVE to folder in <%s> [%s] : ",
2901 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2902 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2903 else
2904 snprintf(prompt, sizeof(prompt), "SAVE to folder [%s] : ",
2905 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2907 prompt[sizeof(prompt)-1] = '\0';
2910 if(del != DontAsk)
2911 ekey[delindex].label = (del == NoDel) ? "Delete" : "No Delete";
2913 if(pre != DontAskPreserve)
2914 ekey[preindex].label = (pre == NoPreserve) ? "Preserve Order" : "Any Order";
2916 if(ku >= 0){
2917 if(items_in_hist(history) > 1){
2918 ekey[ku].name = HISTORY_UP_KEYNAME;
2919 ekey[ku].label = HISTORY_KEYLABEL;
2920 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
2921 ekey[ku+1].label = HISTORY_KEYLABEL;
2923 else{
2924 ekey[ku].name = "";
2925 ekey[ku].label = "";
2926 ekey[ku+1].name = "";
2927 ekey[ku+1].label = "";
2931 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
2932 rc = optionally_enter(nfldr, -FOOTER_ROWS(state), 0, len_nfldr,
2933 prompt, ekey, help, &flags);
2935 switch(rc){
2936 case -1 :
2937 q_status_message(SM_ORDER | SM_DING, 3, 3,
2938 _("Error reading folder name"));
2939 done--;
2940 break;
2942 case 0 :
2943 removing_trailing_white_space(nfldr);
2944 removing_leading_white_space(nfldr);
2946 if(*nfldr || *folder){
2947 char *p, *name, *fullname = NULL;
2948 int exists, breakout = FALSE;
2950 if(!*nfldr){
2951 strncpy(nfldr, folder, len_nfldr-1);
2952 nfldr[len_nfldr-1] = '\0';
2955 save_hist(history, nfldr, 0, (void *) *cntxt);
2957 if(!(name = folder_is_nick(nfldr, FOLDERS(*cntxt), 0)))
2958 name = nfldr;
2960 if(update_folder_spec(expanded, sizeof(expanded), name)){
2961 strncpy(name = nfldr, expanded, len_nfldr-1);
2962 nfldr[len_nfldr-1] = '\0';
2965 exists = folder_name_exists(*cntxt, name, &fullname);
2967 if(exists == FEX_ERROR){
2968 q_status_message1(SM_ORDER, 0, 3,
2969 _("Problem accessing folder \"%s\""),
2970 nfldr);
2971 done--;
2973 else{
2974 if(fullname){
2975 strncpy(name = nfldr, fullname, len_nfldr-1);
2976 nfldr[len_nfldr-1] = '\0';
2977 fs_give((void **) &fullname);
2978 breakout = TRUE;
2981 if(exists & FEX_ISFILE){
2982 done++;
2984 else if((exists & FEX_ISDIR)){
2985 char tmp[MAILTMPLEN];
2987 tc = *cntxt;
2988 if(breakout){
2989 CONTEXT_S *fake_context;
2990 size_t l;
2992 strncpy(tmp, name, sizeof(tmp));
2993 tmp[sizeof(tmp)-2-1] = '\0';
2994 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
2995 if(l < sizeof(tmp)){
2996 tmp[l] = tc->dir->delim;
2997 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
3000 else
3001 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
3003 tmp[sizeof(tmp)-1] = '\0';
3005 fake_context = new_context(tmp, 0);
3006 nfldr[0] = '\0';
3007 done = display_folder_list(&fake_context, nfldr,
3008 1, folders_for_save);
3009 free_context(&fake_context);
3011 else if(tc->dir->delim
3012 && (p = strrindex(name, tc->dir->delim))
3013 && *(p+1) == '\0')
3014 done = display_folder_list(cntxt, nfldr,
3015 1, folders_for_save);
3016 else{
3017 q_status_message1(SM_ORDER, 3, 3,
3018 _("\"%s\" is a directory"), name);
3019 if(tc->dir->delim
3020 && !((p=strrindex(name, tc->dir->delim)) && *(p+1) == '\0')){
3021 strncpy(tmp, name, sizeof(tmp));
3022 tmp[sizeof(tmp)-1] = '\0';
3023 snprintf(nfldr, len_nfldr, "%s%c", tmp, tc->dir->delim);
3027 else{ /* Doesn't exist, create! */
3028 if((fullname = folder_as_breakout(*cntxt, name)) != NULL){
3029 strncpy(name = nfldr, fullname, len_nfldr-1);
3030 nfldr[len_nfldr-1] = '\0';
3031 fs_give((void **) &fullname);
3034 switch(create_for_save(*cntxt, name)){
3035 case 1 : /* success */
3036 done++;
3037 break;
3038 case 0 : /* error */
3039 case -1 : /* declined */
3040 done--;
3041 break;
3046 break;
3048 /* else fall thru like they cancelled */
3050 case 1 :
3051 cmd_cancelled("Save message");
3052 done--;
3053 break;
3055 case 2 :
3056 r = display_folder_list(cntxt, nfldr, 0, folders_for_save);
3058 if(r)
3059 done++;
3061 break;
3063 case 3 :
3064 helper(h_save, _("HELP FOR SAVE"), HLPD_SIMPLE);
3065 ps_global->mangled_screen = 1;
3066 break;
3068 case 4 : /* redraw */
3069 break;
3071 case 10 : /* previous collection */
3072 for(tc = (*cntxt)->prev; tc; tc = tc->prev)
3073 if(!NEWS_TEST(tc))
3074 break;
3076 if(!tc){
3077 CONTEXT_S *tc2;
3079 for(tc2 = (tc = (*cntxt))->next; tc2; tc2 = tc2->next)
3080 if(!NEWS_TEST(tc2))
3081 tc = tc2;
3084 *cntxt = tc;
3085 break;
3087 case 11 : /* next collection */
3088 tc = (*cntxt);
3091 if(((*cntxt) = (*cntxt)->next) == NULL)
3092 (*cntxt) = ps_global->context_list;
3093 while(NEWS_TEST(*cntxt) && (*cntxt) != tc);
3094 break;
3096 case 12 : /* file name completion */
3097 if(!folder_complete(*cntxt, nfldr, len_nfldr, &n)){
3098 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
3099 r = display_folder_list(cntxt, nfldr, 1, folders_for_save);
3100 if(r)
3101 done++; /* bingo! */
3102 else
3103 rc = 0; /* burn last_rc */
3105 else
3106 Writechar(BELL, 0);
3109 break;
3111 case 14 : /* file name completion */
3112 r = display_folder_list(cntxt, nfldr, 2, folders_for_save);
3113 if(r)
3114 done++; /* bingo! */
3115 else
3116 rc = 0; /* burn last_rc */
3118 break;
3120 case 15 : /* Delete / No Delete */
3121 del = (del == NoDel) ? Del : NoDel;
3122 deltext = (del == NoDel) ? " (no delete)" : " (and delete)";
3123 break;
3125 case 16 : /* Preserve Order or not */
3126 pre = (pre == NoPreserve) ? Preserve : NoPreserve;
3127 break;
3129 case 30 :
3130 if((p = get_prev_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3131 strncpy(nfldr, p, len_nfldr);
3132 nfldr[len_nfldr-1] = '\0';
3133 if(history->hist[history->curindex])
3134 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3136 else
3137 Writechar(BELL, 0);
3139 break;
3141 case 31 :
3142 if((p = get_next_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3143 strncpy(nfldr, p, len_nfldr);
3144 nfldr[len_nfldr-1] = '\0';
3145 if(history->hist[history->curindex])
3146 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3148 else
3149 Writechar(BELL, 0);
3151 break;
3153 default :
3154 alpine_panic("Unhandled case");
3155 break;
3158 last_rc = rc;
3161 ps_global->mangled_footer = 1;
3163 if(done < 0)
3164 return(0);
3166 if(*nfldr){
3167 strncpy(ps_global->last_save_folder, nfldr, sizeof(ps_global->last_save_folder)-1);
3168 ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0';
3169 if(*cntxt)
3170 ps_global->last_save_context = *cntxt;
3172 else{
3173 strncpy(nfldr, folder, len_nfldr-1);
3174 nfldr[len_nfldr-1] = '\0';
3177 /* nickname? Copy real name to nfldr */
3178 if(*cntxt
3179 && context_isambig(nfldr)
3180 && (p = folder_is_nick(nfldr, FOLDERS(*cntxt), 0))){
3181 strncpy(nfldr, p, len_nfldr-1);
3182 nfldr[len_nfldr-1] = '\0';
3185 if(dela && (*dela == NoDel || *dela == Del))
3186 *dela = (del == NoDel) ? RetNoDel : RetDel;
3188 if(prea && (*prea == NoPreserve || *prea == Preserve))
3189 *prea = (pre == NoPreserve) ? RetNoPreserve : RetPreserve;
3191 return(1);
3195 /*----------------------------------------------------------------------
3196 Prompt user before implicitly creating a folder for saving
3198 Args: context - context to create folder in
3199 folder - folder name to create
3201 Result: 1 on proceed, -1 on decline, 0 on error
3203 ----*/
3205 create_for_save_prompt(CONTEXT_S *context, char *folder, int sequence_sensitive)
3207 if(context && ps_global->context_list->next && context_isambig(folder)){
3208 if(context->use & CNTXT_INCMNG){
3209 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3210 _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
3211 folder, (strlen(folder) > 15) ? "..." : "");
3212 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
3213 return(0); /* error */
3216 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3217 _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
3218 folder, (strlen(folder) > 15) ? "..." : "",
3219 context->nickname,
3220 (strlen(context->nickname) > 15) ? "..." : "");
3222 else
3223 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
3224 _("Folder \"%.40s%s\" doesn't exist. Create"),
3225 folder, strlen(folder) > 40 ? "..." : "");
3227 if(want_to(tmp_20k_buf, 'y', 'n',
3228 NO_HELP, (sequence_sensitive) ? WT_SEQ_SENSITIVE : WT_NORM) != 'y'){
3229 cmd_cancelled("Save message");
3230 return(-1);
3233 return(1);
3238 /*----------------------------------------------------------------------
3239 Expunge messages from current folder
3241 Args: state -- pointer to struct holding a bunch of pine state
3242 msgmap -- table mapping msg nums to c-client sequence nums
3243 qline -- screen line to ask questions on
3244 agg -- boolean indicating we're to operate on aggregate set
3246 Result:
3247 ----*/
3249 cmd_expunge(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int agg)
3251 long del_count, prefilter_del_count;
3252 int we_cancel = 0, rv = 0;
3253 char prompt[MAX_SCREEN_COLS+1];
3254 char *sequence;
3255 COLOR_PAIR *lastc = NULL;
3257 dprint((2, "\n - expunge -\n"));
3259 del_count = 0;
3261 sequence = MCMD_ISAGG(agg) ? selected_sequence(stream, msgmap, NULL, 0) : NULL;
3263 if(MCMD_ISAGG(agg)){
3264 long i;
3265 MESSAGECACHE *mc;
3266 for(i = 1L; i <= stream->nmsgs; i++){
3267 if((mc = mail_elt(stream, i)) != NULL
3268 && mc->sequence && mc->deleted)
3269 del_count++;
3271 if(del_count == 0){
3272 q_status_message(SM_ORDER, 0, 4,
3273 _("No selected messages are deleted"));
3274 return 0;
3276 } else {
3277 if(!any_messages(msgmap, NULL, "to Expunge"))
3278 return rv;
3281 if(IS_NEWS(stream) && stream->rdonly){
3282 if(!MCMD_ISAGG(agg))
3283 del_count = count_flagged(stream, F_DEL);
3284 if(del_count > 0L){
3285 state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3286 snprintf(prompt, sizeof(prompt), "Exclude %ld message%s from %.*s", del_count,
3287 plural(del_count), MAX_SCREEN_COLS+1-40,
3288 pretty_fn(state->cur_folder));
3289 prompt[sizeof(prompt)-1] = '\0';
3290 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3291 || (F_ON(F_AUTO_EXPUNGE, state)
3292 && (state->context_current
3293 && (state->context_current->use & CNTXT_INCMNG))
3294 && context_isambig(state->cur_folder))
3295 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
3297 if(F_ON(F_NEWS_CROSS_DELETE, state))
3298 cross_delete_crossposts(stream);
3300 msgno_exclude_deleted(stream, msgmap, sequence);
3301 clear_index_cache(stream, 0);
3304 * This is kind of surprising at first. For most sort
3305 * orders, if the whole set is sorted, then any subset
3306 * is also sorted. Not so for threaded sorts.
3308 if(SORT_IS_THREADED(msgmap))
3309 refresh_sort(stream, msgmap, SRT_NON);
3311 state->mangled_body = 1;
3312 state->mangled_header = 1;
3313 q_status_message2(SM_ORDER, 0, 4,
3314 "%s message%s excluded",
3315 long2string(del_count),
3316 plural(del_count));
3318 else
3319 any_messages(NULL, NULL, "Excluded");
3321 else
3322 any_messages(NULL, "deleted", "to Exclude");
3324 return del_count;
3326 else if(READONLY_FOLDER(stream)){
3327 q_status_message(SM_ORDER, 0, 4,
3328 _("Can't expunge. Folder is read-only"));
3329 return del_count;
3332 if(!MCMD_ISAGG(agg)){
3333 prefilter_del_count = count_flagged(stream, F_DEL|F_NOFILT);
3334 mail_expunge_prefilter(stream, MI_NONE);
3335 del_count = count_flagged(stream, F_DEL|F_NOFILT);
3338 if(del_count != 0){
3339 int ret;
3340 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3341 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3342 snprintf(prompt, sizeof(prompt), "Expunge %ld message%s from %.*s", del_count,
3343 plural(del_count), MAX_SCREEN_COLS+1-40,
3344 pretty_fn((char *) fname));
3345 if(fname) fs_give((void **)&fname);
3346 prompt[sizeof(prompt)-1] = '\0';
3347 state->mangled_footer = 1;
3349 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3350 || (F_ON(F_AUTO_EXPUNGE, state)
3351 && ((!strucmp(state->cur_folder,state->inbox_name))
3352 || (state->context_current->use & CNTXT_INCMNG))
3353 && context_isambig(state->cur_folder))
3354 || (ret=want_to(prompt, 'y', 0, NO_HELP, WT_NORM)) == 'y')
3355 ret = 'y';
3357 if(ret == 'x')
3358 cmd_cancelled("Expunge");
3360 if(ret != 'y')
3361 return 0;
3364 dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
3365 mn_get_total(msgmap), mn_get_cur(msgmap), del_count));
3367 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3368 state->VAR_TITLE_BACK_COLOR,
3369 PSC_REV|PSC_RET);
3371 PutLine0(0, 0, "**"); /* indicate delay */
3373 if(lastc){
3374 (void)pico_set_colorp(lastc, PSC_NONE);
3375 free_color_pair(&lastc);
3378 MoveCursor(state->ttyo->screen_rows -FOOTER_ROWS(state), 0);
3379 fflush(stdout);
3381 we_cancel = busy_cue(_("Expunging"), NULL, 1);
3383 if(cmd_expunge_work(stream, msgmap, sequence))
3384 state->mangled_body = 1;
3386 if(sequence)
3387 fs_give((void **)&sequence);
3389 if(we_cancel)
3390 cancel_busy_cue((sp_expunge_count(stream) > 0) ? 0 : -1);
3392 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3393 state->VAR_TITLE_BACK_COLOR,
3394 PSC_REV|PSC_RET);
3395 PutLine0(0, 0, " "); /* indicate delay's over */
3397 if(lastc){
3398 (void)pico_set_colorp(lastc, PSC_NONE);
3399 free_color_pair(&lastc);
3402 fflush(stdout);
3404 if(sp_expunge_count(stream) > 0){
3406 * This is kind of surprising at first. For most sort
3407 * orders, if the whole set is sorted, then any subset
3408 * is also sorted. Not so for threaded sorts.
3410 if(SORT_IS_THREADED(msgmap))
3411 refresh_sort(stream, msgmap, SRT_NON);
3413 else{
3414 if(del_count){
3415 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3416 q_status_message1(SM_ORDER, 0, 3,
3417 _("No messages expunged from folder \"%s\""),
3418 pretty_fn((char *) fname));
3419 if(fname) fs_give((void **)&fname);
3421 else if(!prefilter_del_count)
3422 q_status_message(SM_ORDER, 0, 3,
3423 _("No messages marked deleted. No messages expunged."));
3425 return del_count;
3429 /*----------------------------------------------------------------------
3430 Expunge_and_close callback to prompt user for confirmation
3432 Args: stream -- folder's stream
3433 folder -- name of folder containing folders
3434 deleted -- number of del'd msgs
3436 Result: 'y' to continue with expunge
3437 ----*/
3439 expunge_prompt(MAILSTREAM *stream, char *folder, long int deleted)
3441 long max_folder;
3442 int charcnt = 0;
3443 char prompt_b[MAX_SCREEN_COLS+1], temp[MAILTMPLEN+1], buff[MAX_SCREEN_COLS+1];
3444 char *short_folder_name;
3446 if(deleted == 1)
3447 charcnt = 1;
3448 else{
3449 snprintf(temp, sizeof(temp), "%ld", deleted);
3450 charcnt = strlen(temp)+1;
3453 max_folder = MAX(1,MAXPROMPT - (36+charcnt));
3454 strncpy(temp, folder, sizeof(temp));
3455 temp[sizeof(temp)-1] = '\0';
3456 short_folder_name = short_str(temp,buff,sizeof(buff),max_folder,FrontDots);
3458 if(IS_NEWS(stream))
3459 snprintf(prompt_b, sizeof(prompt_b),
3460 "Delete %s%ld message%s from \"%s\"",
3461 (deleted > 1L) ? "all " : "", deleted,
3462 plural(deleted), short_folder_name);
3463 else
3464 snprintf(prompt_b, sizeof(prompt_b),
3465 "Expunge the %ld deleted message%s from \"%s\"",
3466 deleted, deleted == 1 ? "" : "s",
3467 short_folder_name);
3469 return(want_to(prompt_b, 'y', 0, NO_HELP, WT_NORM));
3474 * This is used with multiple append saves. Call it once before
3475 * the series of appends with SSCP_INIT and once after all are
3476 * done with SSCP_END. In between, it is called automatically
3477 * from save_fetch_append or save_fetch_append_cb when we need
3478 * to ask the user if he or she wants to continue even though
3479 * announced message size doesn't match the actual message size.
3480 * As of 2008-02-29 the gmail IMAP server has these size mismatches
3481 * on a regular basis even though the data is ok.
3484 save_size_changed_prompt(long msgno, int flags)
3486 int ret;
3487 char prompt[100];
3488 static int remember_the_yes = 0;
3489 static int possible_corruption = 0;
3490 static ESCKEY_S save_size_opts[] = {
3491 {'y', 'y', "Y", "Yes"},
3492 {'n', 'n', "N", "No"},
3493 {'a', 'a', "A", "yes to All"},
3494 {-1, 0, NULL, NULL}
3497 if(F_ON(F_IGNORE_SIZE, ps_global))
3498 return 'y';
3500 if(flags & SSCP_INIT || flags & SSCP_END){
3501 if(flags & SSCP_END && possible_corruption)
3502 q_status_message(SM_ORDER, 3, 3, "There is possible data corruption, check the results");
3504 remember_the_yes = 0;
3505 possible_corruption = 0;
3506 ps_global->noshow_error = 0;
3507 ps_global->noshow_warn = 0;
3508 return(0);
3511 if(remember_the_yes){
3512 snprintf(prompt, sizeof(prompt),
3513 "Message to save shrank! (msg # %ld): Continuing", msgno);
3514 q_status_message(SM_ORDER, 0, 3, prompt);
3515 display_message('x');
3516 return(remember_the_yes);
3519 snprintf(prompt, sizeof(prompt),
3520 "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno);
3521 ret = radio_buttons(prompt, -FOOTER_ROWS(ps_global), save_size_opts,
3522 'n', 0, h_save_size_changed, RB_NORM);
3524 switch(ret){
3525 case 'a':
3526 remember_the_yes = 'y';
3527 possible_corruption++;
3528 return(remember_the_yes);
3530 case 'y':
3531 possible_corruption++;
3532 return('y');
3534 default:
3535 possible_corruption = 0;
3536 ps_global->noshow_error = 1;
3537 ps_global->noshow_warn = 1;
3538 break;
3541 return('n');
3545 /*----------------------------------------------------------------------
3546 Expunge_and_close callback that happens once the decision to expunge
3547 and close has been made and before expunging and closing begins
3550 Args: stream -- folder's stream
3551 folder -- name of folder containing folders
3552 deleted -- number of del'd msgs
3554 Result: 'y' to continue with expunge
3555 ----*/
3556 void
3557 expunge_and_close_begins(int flags, char *folder)
3559 if(!(flags & EC_NO_CLOSE)){
3560 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
3561 q_status_message1(SM_INFO, 0, 1, "Closing \"%.200s\"...", (char *) fname);
3562 flush_status_messages(1);
3563 if(fname) fs_give((void **)&fname);
3568 /*----------------------------------------------------------------------
3569 Export a message to a plain file in users home directory
3571 Args: state -- pointer to struct holding a bunch of pine state
3572 msgmap -- table mapping msg nums to c-client sequence nums
3573 qline -- screen line to ask questions on
3574 agg -- boolean indicating we're to operate on aggregate set
3576 Result:
3577 ----*/
3579 cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt)
3581 char filename[MAXPATH+1], full_filename[MAXPATH+1], *err;
3582 char nmsgs[80];
3583 int r, leading_nl, failure = 0, orig_errno, rflags = GER_NONE;
3584 int flags = GE_IS_EXPORT | GE_SEQ_SENSITIVE, rv = 0;
3585 ENVELOPE *env;
3586 MESSAGECACHE *mc;
3587 BODY *b;
3588 long i, count = 0L, start_of_append, rawno;
3589 gf_io_t pc;
3590 STORE_S *store;
3591 struct variable *vars = ps_global->vars;
3592 ESCKEY_S export_opts[5];
3593 static HISTORY_S *history = NULL;
3595 if(ps_global->restricted){
3596 q_status_message(SM_ORDER, 0, 3,
3597 "Alpine demo can't export messages to files");
3598 return rv;
3601 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
3602 return rv;
3604 export_opts[i = 0].ch = ctrl('T');
3605 export_opts[i].rval = 10;
3606 export_opts[i].name = "^T";
3607 export_opts[i++].label = N_("To Files");
3609 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3610 if(ps_global->VAR_DOWNLOAD_CMD && ps_global->VAR_DOWNLOAD_CMD[0]){
3611 export_opts[i].ch = ctrl('V');
3612 export_opts[i].rval = 12;
3613 export_opts[i].name = "^V";
3614 /* TRANSLATORS: this is an abbreviation for Download Messages */
3615 export_opts[i++].label = N_("Downld Msg");
3617 #endif /* !(DOS || MAC) */
3619 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
3620 export_opts[i].ch = ctrl('I');
3621 export_opts[i].rval = 11;
3622 export_opts[i].name = "TAB";
3623 export_opts[i++].label = N_("Complete");
3626 #if 0
3627 /* Commented out since it's not yet support! */
3628 if(F_ON(F_ENABLE_SUB_LISTS,ps_global)){
3629 export_opts[i].ch = ctrl('X');
3630 export_opts[i].rval = 14;
3631 export_opts[i].name = "^X";
3632 export_opts[i++].label = N_("ListMatches");
3634 #endif
3637 * If message has attachments, add a toggle that will allow the user
3638 * to save all of the attachments to a single directory, using the
3639 * names provided with the attachments or part names. What we'll do is
3640 * export the message as usual, and then export the attachments into
3641 * a subdirectory that did not exist before. The subdir will be named
3642 * something based on the name of the file being saved to, but a
3643 * unique, new name.
3645 if(!MCMD_ISAGG(aopt)
3646 && state->mail_stream
3647 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3648 && rawno <= state->mail_stream->nmsgs
3649 && (env = pine_mail_fetchstructure(state->mail_stream, rawno, &b))
3650 && b
3651 && b->type == TYPEMULTIPART
3652 && b->subtype
3653 && strucmp(b->subtype, "ALTERNATIVE") != 0){
3654 PART *part;
3656 part = b->nested.part; /* 1st part */
3657 if(part && part->next)
3658 flags |= GE_ALLPARTS;
3661 export_opts[i].ch = -1;
3662 filename[0] = '\0';
3664 if(mn_total_cur(msgmap) <= 1L){
3665 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld", mn_get_cur(msgmap));
3666 nmsgs[sizeof(nmsgs)-1] = '\0';
3668 else{
3669 snprintf(nmsgs, sizeof(nmsgs), "%s messages", comatose(mn_total_cur(msgmap)));
3670 nmsgs[sizeof(nmsgs)-1] = '\0';
3673 r = get_export_filename(state, filename, NULL, full_filename,
3674 sizeof(filename), nmsgs, "EXPORT",
3675 export_opts, &rflags, qline, flags, &history);
3677 if(r < 0){
3678 switch(r){
3679 case -1:
3680 cmd_cancelled("Export message");
3681 break;
3683 case -2:
3684 q_status_message1(SM_ORDER, 0, 2,
3685 _("Can't export to file outside of %s"),
3686 VAR_OPER_DIR);
3687 break;
3690 goto fini;
3692 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3693 else if(r == 12){ /* Download */
3694 char cmd[MAXPATH], *tfp = NULL;
3695 int next = 0;
3696 PIPE_S *syspipe;
3697 STORE_S *so;
3698 gf_io_t pc;
3700 if(ps_global->restricted){
3701 q_status_message(SM_ORDER | SM_DING, 3, 3,
3702 "Download disallowed in restricted mode");
3703 goto fini;
3706 err = NULL;
3707 tfp = temp_nam(NULL, "pd");
3708 build_updown_cmd(cmd, sizeof(cmd), ps_global->VAR_DOWNLOAD_CMD_PREFIX,
3709 ps_global->VAR_DOWNLOAD_CMD, tfp);
3710 dprint((1, "Download cmd called: \"%s\"\n", cmd));
3711 if((so = so_get(FileStar, tfp, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){
3712 gf_set_so_writec(&pc, so);
3714 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
3715 if(!(state->mail_stream
3716 && (rawno = mn_m2raw(msgmap, i)) > 0L
3717 && rawno <= state->mail_stream->nmsgs
3718 && (mc = mail_elt(state->mail_stream, rawno))
3719 && mc->valid))
3720 mc = NULL;
3722 if(!(env = pine_mail_fetchstructure(state->mail_stream,
3723 mn_m2raw(msgmap, i), &b))
3724 || !bezerk_delimiter(env, mc, pc, next++)
3725 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
3726 env, b, NULL, FM_NEW_MESS | FM_NOWRAP, pc)){
3727 q_status_message(SM_ORDER | SM_DING, 3, 3,
3728 err = "Error writing tempfile for download");
3729 break;
3733 gf_clear_so_writec(so);
3734 if(so_give(&so)){ /* close file */
3735 if(!err)
3736 err = "Error writing tempfile for download";
3739 if(!err){
3740 if((syspipe = open_system_pipe(cmd, NULL, NULL,
3741 PIPE_USER | PIPE_RESET,
3742 0, pipe_callback, pipe_report_error)) != NULL)
3743 (void) close_system_pipe(&syspipe, NULL, pipe_callback);
3744 else
3745 q_status_message(SM_ORDER | SM_DING, 3, 3,
3746 err = _("Error running download command"));
3749 else
3750 q_status_message(SM_ORDER | SM_DING, 3, 3,
3751 err = "Error building temp file for download");
3753 if(tfp){
3754 our_unlink(tfp);
3755 fs_give((void **)&tfp);
3758 if(!err)
3759 q_status_message(SM_ORDER, 0, 3, _("Download Command Completed"));
3761 goto fini;
3763 #endif /* !(DOS || MAC) */
3766 if(rflags & GER_APPEND)
3767 leading_nl = 1;
3768 else
3769 leading_nl = 0;
3771 dprint((5, "Opening file \"%s\" for export\n",
3772 full_filename ? full_filename : "?"));
3774 if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE))){
3775 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3776 /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
3777 _("Error opening file \"%s\" to export message: %s"),
3778 full_filename, error_description(errno));
3779 goto fini;
3781 else
3782 gf_set_so_writec(&pc, store);
3784 err = NULL;
3785 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), count++){
3786 env = pine_mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i),
3787 &b);
3788 if(!env) {
3789 err = _("Can't export message. Error accessing mail folder");
3790 failure = 1;
3791 break;
3794 if(!(state->mail_stream
3795 && (rawno = mn_m2raw(msgmap, i)) > 0L
3796 && rawno <= state->mail_stream->nmsgs
3797 && (mc = mail_elt(state->mail_stream, rawno))
3798 && mc->valid))
3799 mc = NULL;
3801 start_of_append = so_tell(store);
3802 if(!bezerk_delimiter(env, mc, pc, leading_nl)
3803 || !format_message(mn_m2raw(msgmap, i), env, b, NULL,
3804 FM_NEW_MESS | FM_NOWRAP, pc)){
3805 orig_errno = errno; /* save incase things are really bad */
3806 failure = 1; /* pop out of here */
3807 break;
3810 leading_nl = 1;
3813 gf_clear_so_writec(store);
3814 if(so_give(&store)) /* release storage */
3815 failure++;
3817 if(failure){
3818 our_truncate(full_filename, (off_t)start_of_append);
3819 if(err){
3820 dprint((1, "FAILED Export: fetch(%ld): %s\n",
3821 i, err ? err : "?"));
3822 q_status_message(SM_ORDER | SM_DING, 3, 4, err);
3824 else{
3825 dprint((1, "FAILED Export: file \"%s\" : %s\n",
3826 full_filename ? full_filename : "?",
3827 error_description(orig_errno)));
3828 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3829 /* TRANSLATORS: Error exporting to <filename>: <error text> */
3830 _("Error exporting to \"%s\" : %s"),
3831 filename, error_description(orig_errno));
3834 else{
3835 if(rflags & GER_ALLPARTS && full_filename[0]){
3836 char dir[MAXPATH+1];
3837 char lfile[MAXPATH+1];
3838 int ok = 0, tries = 0, saved = 0, errs = 0, counter = 2;
3839 ATTACH_S *a;
3842 * Now we want to save all of the attachments to a subdirectory.
3843 * To make it easier for us and probably easier for the user, and
3844 * to prevent the user from shooting himself in the foot, we
3845 * make a new subdirectory so that we can't possibly step on
3846 * any existing files, and we don't need any interaction with the
3847 * user while saving.
3849 * We'll just use the directory name full_filename.d or if that
3850 * already exists and isn't empty, we'll try adding a suffix to
3851 * that until we get something to use.
3854 if(strlen(full_filename) + strlen(".d") + 1 > sizeof(dir)){
3855 q_status_message1(SM_ORDER | SM_DING, 3, 4,
3856 _("Can't save attachments, filename too long: %s"),
3857 full_filename);
3858 goto fini;
3861 ok = 0;
3862 snprintf(dir, sizeof(dir), "%s.d", full_filename);
3863 dir[sizeof(dir)-1] = '\0';
3865 do {
3866 tries++;
3867 switch(r = is_writable_dir(dir)){
3868 case 0: /* exists and is a writable dir */
3870 * We could figure out if it is empty and use it in
3871 * that case, but that sounds like a lot of work, so
3872 * just fall through to default.
3875 default:
3876 if(strlen(full_filename) + strlen(".d") + 1 +
3877 1 + strlen(long2string((long) tries)) > sizeof(dir)){
3878 q_status_message(SM_ORDER | SM_DING, 3, 4,
3879 "Problem saving attachments");
3880 goto fini;
3883 snprintf(dir, sizeof(dir), "%s.d_%s", full_filename,
3884 long2string((long) tries));
3885 dir[sizeof(dir)-1] = '\0';
3886 break;
3888 case 3: /* doesn't exist, that's good! */
3889 /* make new directory */
3890 ok++;
3891 break;
3893 } while(!ok && tries < 1000);
3895 if(tries >= 1000){
3896 q_status_message(SM_ORDER | SM_DING, 3, 4,
3897 _("Problem saving attachments"));
3898 goto fini;
3901 /* create the new directory */
3902 if(our_mkdir(dir, 0700)){
3903 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3904 _("Problem saving attachments: %s: %s"), dir,
3905 error_description(errno));
3906 goto fini;
3909 if(!(state->mail_stream
3910 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3911 && rawno <= state->mail_stream->nmsgs
3912 && (env=pine_mail_fetchstructure(state->mail_stream,rawno,&b))
3913 && b)){
3914 q_status_message(SM_ORDER | SM_DING, 3, 4,
3915 _("Problem reading message"));
3916 goto fini;
3919 zero_atmts(state->atmts);
3920 describe_mime(b, "", 1, 1, 0, 0);
3922 a = state->atmts;
3923 if(a && a->description) /* skip main body part */
3924 a++;
3926 for(; a->description != NULL; a++){
3927 /* skip over these parts of the message */
3928 if(MIME_MSG_A(a) || MIME_DGST_A(a) || MIME_VCARD_A(a))
3929 continue;
3931 lfile[0] = '\0';
3932 (void) get_filename_parameter(lfile, sizeof(lfile), a->body, NULL);
3934 if(lfile[0] == '\0'){ /* MAXPATH + 1 = sizeof(lfile) */
3935 snprintf(lfile, sizeof(lfile), "part_%.*s", MAXPATH+1-6,
3936 a->number ? a->number : "?");
3937 lfile[sizeof(lfile)-1] = '\0';
3940 if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + 1
3941 > sizeof(filename)){
3942 dprint((2,
3943 "FAILED Att Export: name too long: %s\n",
3944 dir, S_FILESEP, lfile));
3945 errs++;
3946 continue;
3949 /* although files are being saved in a unique directory, there is
3950 * no guarantee that attachment names have unique names, so we have
3951 * to make sure that we are not constantly rewriting the same file name
3952 * over and over. In order to avoid this we test if the file already exists,
3953 * and if so, we write a counter name in the file name, just before the
3954 * extension of the file, and separate it with an underscore.
3956 snprintf(filename, sizeof(filename), "%s%s%s", dir, S_FILESEP, lfile);
3957 filename[sizeof(filename)-1] = '\0';
3958 while((ok = can_access(filename, ACCESS_EXISTS)) == 0 && errs == 0){
3959 char *ext;
3960 snprintf(filename, sizeof(filename), "%d", counter);
3961 if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + strlen(filename) + 2
3962 > sizeof(filename)){
3963 dprint((2,
3964 "FAILED Att Export: name too long: %s\n",
3965 dir, S_FILESEP, lfile));
3966 errs++;
3967 continue;
3969 if((ext = strrchr(lfile, '.')) != NULL)
3970 *ext = '\0';
3971 snprintf(filename, sizeof(filename), "%s%s%s%s%d%s%s",
3972 dir, S_FILESEP, lfile,
3973 ext ? "_" : "", counter++, ext ? "." : "", ext ? ext+1 : "");
3974 filename[sizeof(filename)-1] = '\0';
3977 if(write_attachment_to_file(state->mail_stream, rawno,
3978 a, GER_NONE, filename) == 1)
3979 saved++;
3980 else
3981 errs++;
3984 if(errs){
3985 if(saved)
3986 q_status_message1(SM_ORDER, 3, 3,
3987 "Errors saving some attachments, %s attachments saved",
3988 long2string((long) saved));
3989 else
3990 q_status_message(SM_ORDER, 3, 3,
3991 _("Problems saving attachments"));
3993 else{
3994 if(saved)
3995 q_status_message2(SM_ORDER, 0, 3,
3996 /* TRANSLATORS: Saved <how many> attachements to <directory name> */
3997 _("Saved %s attachments to %s"),
3998 long2string((long) saved), dir);
3999 else
4000 q_status_message(SM_ORDER, 3, 3, _("No attachments to save"));
4003 else if(mn_total_cur(msgmap) > 1L)
4004 q_status_message4(SM_ORDER,0,3,
4005 "%s message%s %s to file \"%s\"",
4006 long2string(count), plural(count),
4007 rflags & GER_OVER
4008 ? "overwritten"
4009 : rflags & GER_APPEND ? "appended" : "exported",
4010 filename);
4011 else
4012 q_status_message3(SM_ORDER,0,3,
4013 "Message %s %s to file \"%s\"",
4014 long2string(mn_get_cur(msgmap)),
4015 rflags & GER_OVER
4016 ? "overwritten"
4017 : rflags & GER_APPEND ? "appended" : "exported",
4018 filename);
4019 rv++;
4022 fini:
4023 if(MCMD_ISAGG(aopt))
4024 restore_selected(msgmap);
4026 return rv;
4031 * Ask user what file to export to. Export from srcstore to that file.
4033 * Args ps -- pine struct
4034 * srctext -- pointer to source text
4035 * srctype -- type of that source text
4036 * prompt_msg -- see get_export_filename
4037 * lister_msg -- "
4039 * Returns: != 0 : error
4040 * 0 : ok
4043 simple_export(struct pine *ps, void *srctext, SourceType srctype, char *prompt_msg, char *lister_msg)
4045 int r = 1, rflags = GER_NONE;
4046 char filename[MAXPATH+1], full_filename[MAXPATH+1];
4047 STORE_S *store = NULL;
4048 struct variable *vars = ps->vars;
4049 static HISTORY_S *history = NULL;
4050 static ESCKEY_S simple_export_opts[] = {
4051 {ctrl('T'), 10, "^T", N_("To Files")},
4052 {-1, 0, NULL, NULL},
4053 {-1, 0, NULL, NULL}};
4055 if(F_ON(F_ENABLE_TAB_COMPLETE,ps)){
4056 simple_export_opts[r].ch = ctrl('I');
4057 simple_export_opts[r].rval = 11;
4058 simple_export_opts[r].name = "TAB";
4059 simple_export_opts[r].label = N_("Complete");
4062 if(!srctext){
4063 q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
4064 r = -3;
4065 goto fini;
4068 simple_export_opts[++r].ch = -1;
4069 filename[0] = '\0';
4070 full_filename[0] = '\0';
4072 r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename),
4073 prompt_msg, lister_msg, simple_export_opts, &rflags,
4074 -FOOTER_ROWS(ps), GE_IS_EXPORT, &history);
4076 if(r < 0)
4077 goto fini;
4078 else if(!full_filename[0]){
4079 r = -1;
4080 goto fini;
4083 dprint((5, "Opening file \"%s\" for export\n",
4084 full_filename ? full_filename : "?"));
4086 if((store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
4087 char *pipe_err;
4088 gf_io_t pc, gc;
4090 gf_set_so_writec(&pc, store);
4091 gf_set_readc(&gc, srctext, (srctype == CharStar)
4092 ? strlen((char *)srctext)
4093 : 0L,
4094 srctype, 0);
4095 gf_filter_init();
4096 if((pipe_err = gf_pipe(gc, pc)) != NULL){
4097 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4098 /* TRANSLATORS: Problem saving to <filename>: <error text> */
4099 _("Problem saving to \"%s\": %s"),
4100 filename, pipe_err);
4101 r = -3;
4103 else
4104 r = 0;
4106 gf_clear_so_writec(store);
4107 if(so_give(&store)){
4108 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4109 _("Problem saving to \"%s\": %s"),
4110 filename, error_description(errno));
4111 r = -3;
4114 else{
4115 q_status_message2(SM_ORDER | SM_DING, 3, 4,
4116 _("Error opening file \"%s\" for export: %s"),
4117 full_filename, error_description(errno));
4118 r = -3;
4121 fini:
4122 switch(r){
4123 case 0:
4124 /* overloading full_filename */
4125 snprintf(full_filename, sizeof(full_filename), "%c%s",
4126 (prompt_msg && prompt_msg[0])
4127 ? (islower((unsigned char)prompt_msg[0])
4128 ? toupper((unsigned char)prompt_msg[0]) : prompt_msg[0])
4129 : 'T',
4130 (prompt_msg && prompt_msg[0]) ? prompt_msg+1 : "ext");
4131 full_filename[sizeof(full_filename)-1] = '\0';
4132 q_status_message3(SM_ORDER,0,2,"%s %s to \"%s\"",
4133 full_filename,
4134 rflags & GER_OVER
4135 ? "overwritten"
4136 : rflags & GER_APPEND ? "appended" : "exported",
4137 filename);
4138 break;
4140 case -1:
4141 cmd_cancelled("Export");
4142 break;
4144 case -2:
4145 q_status_message1(SM_ORDER, 0, 2,
4146 _("Can't export to file outside of %s"), VAR_OPER_DIR);
4147 break;
4150 ps->mangled_footer = 1;
4151 return(r);
4156 * Ask user what file to export to.
4158 * filename -- On input, this is the filename to start with. On exit,
4159 * this is the filename chosen. (but this isn't used)
4160 * deefault -- This is the default value if user hits return. The
4161 * prompt will have [deefault] added to it automatically.
4162 * full_filename -- This is the full filename on exit.
4163 * len -- Minimum length of _both_ filename and full_filename.
4164 * prompt_msg -- Message to insert in prompt.
4165 * lister_msg -- Message to insert in file_lister.
4166 * opts -- Key options.
4167 * There is a tangled relationship between the callers
4168 * and this routine as far as opts are concerned. Some
4169 * of the opts are handled here. In particular, r == 3,
4170 * r == 10, r == 11, and r == 13 are all handled here.
4171 * Don't use those values unless you want what happens
4172 * here. r == 12 and others are handled by the caller.
4173 * rflags -- Return flags
4174 * GER_OVER - overwrite of existing file
4175 * GER_APPEND - append of existing file
4176 * else file did not exist before
4178 * GER_ALLPARTS - AllParts toggle was turned on
4180 * qline -- Command line to prompt on.
4181 * flags -- Logically OR'd flags
4182 * GE_IS_EXPORT - The command was an Export command
4183 * so the prompt should include
4184 * EXPORT:.
4185 * GE_SEQ_SENSITIVE - The command that got us here is
4186 * sensitive to sequence number changes
4187 * caused by unsolicited expunges.
4188 * GE_NO_APPEND - We will not allow append to an
4189 * existing file, only removal of the
4190 * file if it exists.
4191 * GE_IS_IMPORT - We are selecting for reading.
4192 * No overwriting or checking for
4193 * existence at all. Don't use this
4194 * together with GE_NO_APPEND.
4195 * GE_ALLPARTS - Turn on AllParts toggle.
4196 * GE_BINARY - Turn on Binary toggle.
4198 * Returns: -1 cancelled
4199 * -2 prohibited by VAR_OPER_DIR
4200 * -3 other error, already reported here
4201 * 0 ok
4202 * 12 user chose 12 command from opts
4205 get_export_filename(struct pine *ps, char *filename, char *deefault,
4206 char *full_filename, size_t len, char *prompt_msg,
4207 char *lister_msg, ESCKEY_S *optsarg, int *rflags,
4208 int qline, int flags, HISTORY_S **history)
4210 char dir[MAXPATH+1], dir2[MAXPATH+1], orig_dir[MAXPATH+1];
4211 char precolon[MAXPATH+1], postcolon[MAXPATH+1];
4212 char filename2[MAXPATH+1], tmp[MAXPATH+1], *fn, *ill;
4213 int l, i, ku = -1, kp = -1, r, fatal, homedir = 0, was_abs_path=0, avail, ret = 0;
4214 int allparts = 0, binary = 0;
4215 char prompt_buf[400];
4216 char def[500];
4217 ESCKEY_S *opts = NULL;
4218 struct variable *vars = ps->vars;
4219 static HISTORY_S *dir_hist = NULL;
4220 static char *last;
4221 int pos, hist_len = 0;
4224 /* we will fake a history with the ps_global->VAR_HISTORY variable
4225 * We fake that we combine this variable into a history variable
4226 * by stacking VAR_HISTORY on top of dir_hist. We keep track of this
4227 * by looking at the variable pos.
4229 if(ps_global->VAR_HISTORY != NULL)
4230 for(hist_len = 0; ps_global->VAR_HISTORY[hist_len]
4231 && ps_global->VAR_HISTORY[hist_len][0]; hist_len++)
4234 pos = hist_len + items_in_hist(dir_hist);
4236 if(flags & GE_ALLPARTS || history || dir_hist){
4238 * Copy the opts and add one to the end of the list.
4240 for(i = 0; optsarg[i].ch != -1; i++)
4243 if(dir_hist || hist_len > 0)
4244 i += 2;
4246 if(history)
4247 i += dir_hist || hist_len > 0 ? 2 : 4;
4249 if(flags & GE_ALLPARTS)
4250 i++;
4252 if(flags & GE_BINARY)
4253 i++;
4255 opts = (ESCKEY_S *) fs_get((i+1) * sizeof(*opts));
4256 memset(opts, 0, (i+1) * sizeof(*opts));
4258 for(i = 0; optsarg[i].ch != -1; i++){
4259 opts[i].ch = optsarg[i].ch;
4260 opts[i].rval = optsarg[i].rval;
4261 opts[i].name = optsarg[i].name; /* no need to make a copy */
4262 opts[i].label = optsarg[i].label; /* " */
4265 if(flags & GE_ALLPARTS){
4266 allparts = i;
4267 opts[i].ch = ctrl('P');
4268 opts[i].rval = 13;
4269 opts[i].name = "^P";
4270 /* TRANSLATORS: Export all attachment parts */
4271 opts[i++].label = N_("AllParts");
4274 if(flags & GE_BINARY){
4275 binary = i;
4276 opts[i].ch = ctrl('R');
4277 opts[i].rval = 15;
4278 opts[i].name = "^R";
4279 opts[i++].label = N_("Binary");
4282 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4283 SIZEOF_20KBUF, filename);
4284 if(strcmp(tmp_20k_buf, filename)){
4285 opts[i].ch = ctrl('N');
4286 opts[i].rval = 40;
4287 opts[i].name = "^N";
4288 opts[i++].label = "Name UTF8";
4291 if(dir_hist || hist_len > 0){
4292 opts[i].ch = ctrl('Y');
4293 opts[i].rval = 32;
4294 opts[i].name = "";
4295 kp = i;
4296 opts[i++].label = "";
4298 opts[i].ch = ctrl('V');
4299 opts[i].rval = 33;
4300 opts[i].name = "";
4301 opts[i++].label = "";
4304 if(history){
4305 opts[i].ch = KEY_UP;
4306 opts[i].rval = 30;
4307 opts[i].name = "";
4308 ku = i;
4309 opts[i++].label = "";
4311 opts[i].ch = KEY_DOWN;
4312 opts[i].rval = 31;
4313 opts[i].name = "";
4314 opts[i++].label = "";
4317 opts[i].ch = -1;
4319 if(history)
4320 init_hist(history, HISTSIZE);
4321 init_hist(&dir_hist, HISTSIZE); /* reset history to the end */
4323 else
4324 opts = optsarg;
4326 if(rflags)
4327 *rflags = GER_NONE;
4329 if(F_ON(F_USE_CURRENT_DIR, ps))
4330 dir[0] = '\0';
4331 else if(VAR_OPER_DIR){
4332 strncpy(dir, VAR_OPER_DIR, sizeof(dir));
4333 dir[sizeof(dir)-1] = '\0';
4335 #if defined(DOS) || defined(OS2)
4336 else if(VAR_FILE_DIR){
4337 strncpy(dir, VAR_FILE_DIR, sizeof(dir));
4338 dir[sizeof(dir)-1] = '\0';
4340 #endif
4341 else{
4342 dir[0] = '~';
4343 dir[1] = '\0';
4344 homedir=1;
4346 strncpy(orig_dir, dir, sizeof(orig_dir));
4347 orig_dir[sizeof(orig_dir)-1] = '\0';
4349 postcolon[0] = '\0';
4350 strncpy(precolon, dir, sizeof(precolon));
4351 precolon[sizeof(precolon)-1] = '\0';
4352 if(deefault){
4353 strncpy(def, deefault, sizeof(def)-1);
4354 def[sizeof(def)-1] = '\0';
4355 removing_leading_and_trailing_white_space(def);
4357 else
4358 def[0] = '\0';
4360 avail = MAX(20, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - MIN_OPT_ENT_WIDTH;
4362 /*---------- Prompt the user for the file name -------------*/
4363 while(1){
4364 int oeflags;
4365 char dirb[50], fileb[50];
4366 int l1, l2, l3, l4, l5, needed;
4367 char *p, p1[100], p2[100], *p3, p4[100], p5[100];
4369 snprintf(p1, sizeof(p1), "%sCopy ",
4370 (flags & GE_IS_EXPORT) ? "EXPORT: " :
4371 (flags & GE_IS_IMPORT) ? "IMPORT: " : "SAVE: ");
4372 p1[sizeof(p1)-1] = '\0';
4373 l1 = strlen(p1);
4375 strncpy(p2, prompt_msg ? prompt_msg : "", sizeof(p2)-1);
4376 p2[sizeof(p2)-1] = '\0';
4377 l2 = strlen(p2);
4379 if(rflags && *rflags & GER_ALLPARTS)
4380 p3 = " (and atts)";
4381 else
4382 p3 = "";
4384 l3 = strlen(p3);
4386 snprintf(p4, sizeof(p4), " %s file%s%s",
4387 (flags & GE_IS_IMPORT) ? "from" : "to",
4388 is_absolute_path(filename) ? "" : " in ",
4389 is_absolute_path(filename) ? "" :
4390 (!dir[0] ? "current directory"
4391 : (dir[0] == '~' && !dir[1]) ? "home directory"
4392 : short_str(dir,dirb,sizeof(dirb),30,FrontDots)));
4393 p4[sizeof(p4)-1] = '\0';
4394 l4 = strlen(p4);
4396 snprintf(p5, sizeof(p5), "%s%s%s: ",
4397 *def ? " [" : "",
4398 *def ? short_str(def,fileb,sizeof(fileb),40,EndDots) : "",
4399 *def ? "]" : "");
4400 p5[sizeof(p5)-1] = '\0';
4401 l5 = strlen(p5);
4403 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4404 snprintf(p4, sizeof(p4), " %s file%s%s",
4405 (flags & GE_IS_IMPORT) ? "from" : "to",
4406 is_absolute_path(filename) ? "" : " in ",
4407 is_absolute_path(filename) ? "" :
4408 (!dir[0] ? "current dir"
4409 : (dir[0] == '~' && !dir[1]) ? "home dir"
4410 : short_str(dir,dirb,sizeof(dirb),10,FrontDots)));
4411 p4[sizeof(p4)-1] = '\0';
4412 l4 = strlen(p4);
4415 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4416 snprintf(p5, sizeof(p5), "%s%s%s: ",
4417 *def ? " [" : "",
4418 *def ? short_str(def,fileb,sizeof(fileb),
4419 MAX(15,l5-5-needed),EndDots) : "",
4420 *def ? "]" : "");
4421 p5[sizeof(p5)-1] = '\0';
4422 l5 = strlen(p5);
4425 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l2 > 0){
4428 * 14 is about the shortest we can make this, because there are
4429 * fixed length strings of length 14 coming in here.
4431 p = short_str(prompt_msg, p2, sizeof(p2), MAX(14,l2-needed), FrontDots);
4432 if(p != p2){
4433 strncpy(p2, p, sizeof(p2)-1);
4434 p2[sizeof(p2)-1] = '\0';
4437 l2 = strlen(p2);
4440 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4441 strncpy(p1, "Copy ", sizeof(p1)-1);
4442 p1[sizeof(p1)-1] = '\0';
4443 l1 = strlen(p1);
4446 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4447 snprintf(p5, sizeof(p5), "%s%s%s: ",
4448 *def ? " [" : "",
4449 *def ? short_str(def,fileb, sizeof(fileb),
4450 MAX(10,l5-5-needed),EndDots) : "",
4451 *def ? "]" : "");
4452 p5[sizeof(p5)-1] = '\0';
4453 l5 = strlen(p5);
4456 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l3 > 0){
4457 if(needed <= l3 - strlen(" (+ atts)"))
4458 p3 = " (+ atts)";
4459 else if(needed <= l3 - strlen(" (atts)"))
4460 p3 = " (atts)";
4461 else if(needed <= l3 - strlen(" (+)"))
4462 p3 = " (+)";
4463 else if(needed <= l3 - strlen("+"))
4464 p3 = "+";
4465 else
4466 p3 = "";
4468 l3 = strlen(p3);
4471 snprintf(prompt_buf, sizeof(prompt_buf), "%s%s%s%s%s", p1, p2, p3, p4, p5);
4472 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4474 if(kp >= 0){
4475 if(items_in_hist(dir_hist) > 0 || hist_len > 0){ /* any directories */
4476 opts[kp].name = "^Y";
4477 opts[kp].label = "Prev Dir";
4478 opts[kp+1].name = "^V";
4479 opts[kp+1].label = "Next Dir";
4481 else{
4482 opts[kp].name = "";
4483 opts[kp].label = "";
4484 opts[kp+1].name = "";
4485 opts[kp+1].label = "";
4489 if(ku >= 0){
4490 if(items_in_hist(*history) > 0){
4491 opts[ku].name = HISTORY_UP_KEYNAME;
4492 opts[ku].label = HISTORY_KEYLABEL;
4493 opts[ku+1].name = HISTORY_DOWN_KEYNAME;
4494 opts[ku+1].label = HISTORY_KEYLABEL;
4496 else{
4497 opts[ku].name = "";
4498 opts[ku].label = "";
4499 opts[ku+1].name = "";
4500 opts[ku+1].label = "";
4504 oeflags = OE_APPEND_CURRENT |
4505 ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0);
4506 r = optionally_enter(filename, qline, 0, len, prompt_buf,
4507 opts, NO_HELP, &oeflags);
4509 dprint((2, "\n - export_filename = \"%s\", r = %d -\n", filename, r));
4510 /*--- Help ----*/
4511 if(r == 3){
4513 * Helps may not be right if you add another caller or change
4514 * things. Check it out.
4516 if(flags & GE_IS_IMPORT)
4517 helper(h_ge_import, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE);
4518 else if(flags & GE_ALLPARTS)
4519 helper(h_ge_allparts, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4520 else
4521 helper(h_ge_export, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4523 ps->mangled_screen = 1;
4525 continue;
4527 else if(r == 10 || r == 11){ /* Browser or File Completion */
4528 if(filename[0]=='~'){
4529 if(filename[1] == C_FILESEP && filename[2]!='\0'){
4530 precolon[0] = '~';
4531 precolon[1] = '\0';
4532 for(i=0; filename[i+2] != '\0' && i+2 < len-1; i++)
4533 filename[i] = filename[i+2];
4534 filename[i] = '\0';
4535 strncpy(dir, precolon, sizeof(dir)-1);
4536 dir[sizeof(dir)-1] = '\0';
4538 else if(filename[1]=='\0' ||
4539 (filename[1] == C_FILESEP && filename[2] == '\0')){
4540 precolon[0] = '~';
4541 precolon[1] = '\0';
4542 filename[0] = '\0';
4543 strncpy(dir, precolon, sizeof(dir)-1);
4544 dir[sizeof(dir)-1] = '\0';
4547 else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){
4548 if(homedir){
4549 precolon[0] = '~';
4550 precolon[1] = '\0';
4551 strncpy(dir, precolon, sizeof(dir)-1);
4552 dir[sizeof(dir)-1] = '\0';
4554 else{
4555 precolon[0] = '\0';
4556 dir[0] = '\0';
4559 l = MAXPATH;
4560 dir2[0] = '\0';
4561 strncpy(tmp, filename, sizeof(tmp)-1);
4562 tmp[sizeof(tmp)-1] = '\0';
4563 if(*tmp && is_absolute_path(tmp))
4564 fnexpand(tmp, sizeof(tmp));
4565 if(strncmp(tmp,postcolon, strlen(postcolon)))
4566 postcolon[0] = '\0';
4568 if(*tmp && (fn = last_cmpnt(tmp))){
4569 l -= fn - tmp;
4570 strncpy(filename2, fn, sizeof(filename2)-1);
4571 filename2[sizeof(filename2)-1] = '\0';
4572 if(is_absolute_path(tmp)){
4573 strncpy(dir2, tmp, MIN(fn - tmp, sizeof(dir2)-1));
4574 dir2[MIN(fn - tmp, sizeof(dir2)-1)] = '\0';
4575 #ifdef _WINDOWS
4576 if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){
4577 dir2[2] = '\\';
4578 dir2[3] = '\0';
4580 #endif
4581 strncpy(postcolon, dir2, sizeof(postcolon)-1);
4582 postcolon[sizeof(postcolon)-1] = '\0';
4583 precolon[0] = '\0';
4585 else{
4586 char *p = NULL;
4588 * Just building the directory name in dir2,
4589 * full_filename is overloaded.
4591 snprintf(full_filename, len, "%.*s", (int) MIN(fn-tmp,len-1), tmp);
4592 full_filename[len-1] = '\0';
4593 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4594 postcolon[sizeof(postcolon)-1] = '\0';
4595 build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH)
4596 : (dir[0] == '~' && !dir[1])
4597 ? ps->home_dir
4598 : dir,
4599 full_filename, sizeof(dir2));
4600 if(p)
4601 free(p);
4604 else{
4605 if(is_absolute_path(tmp)){
4606 strncpy(dir2, tmp, sizeof(dir2)-1);
4607 dir2[sizeof(dir2)-1] = '\0';
4608 #ifdef _WINDOWS
4609 if(dir2[2]=='\0' && dir2[1]==':'){
4610 dir2[2]='\\';
4611 dir2[3]='\0';
4612 strncpy(postcolon,dir2,sizeof(postcolon)-1);
4613 postcolon[sizeof(postcolon)-1] = '\0';
4615 #endif
4616 filename2[0] = '\0';
4617 precolon[0] = '\0';
4619 else{
4620 strncpy(filename2, tmp, sizeof(filename2)-1);
4621 filename2[sizeof(filename2)-1] = '\0';
4622 if(!dir[0])
4623 (void)getcwd(dir2, sizeof(dir2));
4624 else if(dir[0] == '~' && !dir[1]){
4625 strncpy(dir2, ps->home_dir, sizeof(dir2)-1);
4626 dir2[sizeof(dir2)-1] = '\0';
4628 else{
4629 strncpy(dir2, dir, sizeof(dir2)-1);
4630 dir2[sizeof(dir2)-1] = '\0';
4633 postcolon[0] = '\0';
4637 build_path(full_filename, dir2, filename2, len);
4638 if(!strcmp(full_filename, dir2))
4639 filename2[0] = '\0';
4640 if(full_filename[strlen(full_filename)-1] == C_FILESEP
4641 && isdir(full_filename,NULL,NULL)){
4642 if(strlen(full_filename) == 1)
4643 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4644 else if(filename2[0])
4645 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4646 postcolon[sizeof(postcolon)-1] = '\0';
4647 strncpy(dir2, full_filename, sizeof(dir2)-1);
4648 dir2[sizeof(dir2)-1] = '\0';
4649 filename2[0] = '\0';
4651 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4652 else if(full_filename[strlen(full_filename)-1] == C_FILESEP){
4653 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4654 postcolon[sizeof(postcolon)-1] = '\0';
4655 strncpy(dir2, full_filename, sizeof(dir2)-1);
4656 dir2[sizeof(dir2)-1] = '\0';
4657 filename2[0] = '\0';
4659 #endif
4660 if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1
4661 && strcmp(dir2+1, ":\\"))
4662 /* last condition to prevent stripping of '\\'
4663 in windows partition */
4664 dir2[strlen(dir2)-1] = '\0';
4666 if(r == 10){ /* File Browser */
4667 r = file_lister(lister_msg ? lister_msg : "EXPORT",
4668 dir2, sizeof(dir2), filename2, sizeof(filename2),
4669 TRUE,
4670 (flags & GE_IS_IMPORT) ? FB_READ : FB_SAVE);
4671 #ifdef _WINDOWS
4672 /* Windows has a special "feature" in which entering the file browser will
4673 change the working directory if the directory is changed at all (even
4674 clicking "Cancel" will change the working directory).
4676 if(F_ON(F_USE_CURRENT_DIR, ps))
4677 (void)getcwd(dir2,sizeof(dir2));
4678 #endif
4679 if(isdir(dir2,NULL,NULL)){
4680 strncpy(precolon, dir2, sizeof(precolon)-1);
4681 precolon[sizeof(precolon)-1] = '\0';
4683 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4684 postcolon[sizeof(postcolon)-1] = '\0';
4685 if(r == 1){
4686 build_path(full_filename, dir2, filename2, len);
4687 if(isdir(full_filename, NULL, NULL)){
4688 strncpy(dir, full_filename, sizeof(dir)-1);
4689 dir[sizeof(dir)-1] = '\0';
4690 filename[0] = '\0';
4692 else{
4693 fn = last_cmpnt(full_filename);
4694 strncpy(dir, full_filename,
4695 MIN(fn - full_filename, sizeof(dir)-1));
4696 dir[MIN(fn - full_filename, sizeof(dir)-1)] = '\0';
4697 if(fn - full_filename > 1)
4698 dir[fn - full_filename - 1] = '\0';
4701 if(!strcmp(dir, ps->home_dir)){
4702 dir[0] = '~';
4703 dir[1] = '\0';
4706 strncpy(filename, fn, len-1);
4707 filename[len-1] = '\0';
4710 else{ /* File Completion */
4711 if(!pico_fncomplete(dir2, filename2, sizeof(filename2)))
4712 Writechar(BELL, 0);
4713 strncat(postcolon, filename2,
4714 sizeof(postcolon)-1-strlen(postcolon));
4715 postcolon[sizeof(postcolon)-1] = '\0';
4717 was_abs_path = is_absolute_path(filename);
4719 if(!strcmp(dir, ps->home_dir)){
4720 dir[0] = '~';
4721 dir[1] = '\0';
4724 strncpy(filename, postcolon, len-1);
4725 filename[len-1] = '\0';
4726 strncpy(dir, precolon, sizeof(dir)-1);
4727 dir[sizeof(dir)-1] = '\0';
4729 if(filename[0] == '~' && !filename[1]){
4730 dir[0] = '~';
4731 dir[1] = '\0';
4732 filename[0] = '\0';
4735 continue;
4737 else if(r == 12){ /* Download, caller handles it */
4738 ret = r;
4739 goto done;
4741 else if(r == 13){ /* toggle AllParts bit */
4742 if(rflags){
4743 if(*rflags & GER_ALLPARTS){
4744 *rflags &= ~GER_ALLPARTS;
4745 opts[allparts].label = N_("AllParts");
4747 else{
4748 *rflags |= GER_ALLPARTS;
4749 /* opposite of All Parts, No All Parts */
4750 opts[allparts].label = N_("NoAllParts");
4754 continue;
4756 #if 0
4757 else if(r == 14){ /* List file names matching partial? */
4758 continue;
4760 #endif
4761 else if(r == 15){ /* toggle Binary bit */
4762 if(rflags){
4763 if(*rflags & GER_BINARY){
4764 *rflags &= ~GER_BINARY;
4765 opts[binary].label = N_("Binary");
4767 else{
4768 *rflags |= GER_BINARY;
4769 opts[binary].label = N_("No Binary");
4773 continue;
4775 else if(r == 1){ /* Cancel */
4776 ret = -1;
4777 goto done;
4779 else if(r == 4){
4780 continue;
4782 else if(r >= 30 && r <= 33){
4783 char *p = NULL;
4785 if(r == 30 || r == 31){
4786 if(history){
4787 if(r == 30)
4788 p = get_prev_hist(*history, filename, 0, NULL);
4789 else if (r == 31)
4790 p = get_next_hist(*history, filename, 0, NULL);
4794 if(r == 32 || r == 33){
4795 int nitems = items_in_hist(dir_hist);
4796 if(dir_hist || hist_len > 0){
4797 if(r == 32){
4798 if(pos > 0)
4799 p = hist_in_pos(--pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4800 else p = last;
4802 else if (r == 33){
4803 if(pos < hist_len + nitems)
4804 p = hist_in_pos(++pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4806 if(p == NULL || *p == '\0')
4807 p = orig_dir;
4810 last = p; /* save it! */
4812 if(p != NULL && *p != '\0'){
4813 if(r == 30 || r == 31){
4814 if((fn = last_cmpnt(p)) != NULL){
4815 strncpy(dir, p, MIN(fn - p, sizeof(dir)-1));
4816 dir[MIN(fn - p, sizeof(dir)-1)] = '\0';
4817 if(fn - p > 1)
4818 dir[fn - p - 1] = '\0';
4819 strncpy(filename, fn, len-1);
4820 filename[len-1] = '\0';
4822 } else { /* r == 32 || r == 33 */
4823 strncpy(dir, p, sizeof(dir)-1);
4824 dir[sizeof(dir)-1] = '\0';
4827 if(!strcmp(dir, ps->home_dir)){
4828 dir[0] = '~';
4829 dir[1] = '\0';
4832 else
4833 Writechar(BELL, 0);
4834 continue;
4836 else if(r == 40){
4837 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4838 SIZEOF_20KBUF, filename);
4839 strncpy(filename, tmp_20k_buf, len);
4840 filename[len-1] = '\0';
4841 continue;
4843 else if(r != 0){
4844 Writechar(BELL, 0);
4845 continue;
4848 removing_leading_and_trailing_white_space(filename);
4850 if(!*filename){
4851 if(!*def){ /* Cancel */
4852 ret = -1;
4853 goto done;
4856 strncpy(filename, def, len-1);
4857 filename[len-1] = '\0';
4860 #if defined(DOS) || defined(OS2)
4861 if(is_absolute_path(filename)){
4862 fixpath(filename, len);
4864 #else
4865 if(filename[0] == '~'){
4866 if(fnexpand(filename, len) == NULL){
4867 char *p = strindex(filename, '/');
4868 if(p != NULL)
4869 *p = '\0';
4870 q_status_message1(SM_ORDER | SM_DING, 3, 3,
4871 _("Error expanding file name: \"%s\" unknown user"),
4872 filename);
4873 continue;
4876 #endif
4878 if(is_absolute_path(filename)){
4879 strncpy(full_filename, filename, len-1);
4880 full_filename[len-1] = '\0';
4882 else{
4883 if(!dir[0])
4884 build_path(full_filename, (char *)getcwd(dir,sizeof(dir)),
4885 filename, len);
4886 else if(dir[0] == '~' && !dir[1])
4887 build_path(full_filename, ps->home_dir, filename, len);
4888 else
4889 build_path(full_filename, dir, filename, len);
4892 if((ill = filter_filename(full_filename, &fatal,
4893 ps_global->restricted || ps_global->VAR_OPER_DIR)) != NULL){
4894 if(fatal){
4895 q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill);
4896 continue;
4898 else{
4899 /* BUG: we should beep when the key's pressed rather than bitch later */
4900 /* Warn and ask for confirmation. */
4901 snprintf(prompt_buf, sizeof(prompt_buf), "File name contains a '%s'. %s anyway",
4902 ill, (flags & GE_IS_EXPORT) ? "Export" : "Save");
4903 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4904 if(want_to(prompt_buf, 'n', 0, NO_HELP,
4905 ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y')
4906 continue;
4910 break; /* Must have got an OK file name */
4913 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){
4914 ret = -2;
4915 goto done;
4918 if(!can_access(full_filename, ACCESS_EXISTS)){
4919 int rbflags;
4920 static ESCKEY_S access_opts[] = {
4921 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4922 a file or append to the end of the file */
4923 {'o', 'o', "O", N_("Overwrite")},
4924 {'a', 'a', "A", N_("Append")},
4925 {-1, 0, NULL, NULL}};
4927 rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0);
4929 if(flags & GE_NO_APPEND){
4930 r = strlen(filename);
4931 snprintf(prompt_buf, sizeof(prompt_buf),
4932 /* TRANSLATORS: asking user whether to overwrite a file or not,
4933 File <filename> already exists. Overwrite it ? */
4934 _("File \"%s%s\" already exists. Overwrite it "),
4935 (r > 20) ? "..." : "",
4936 filename + ((r > 20) ? r - 20 : 0));
4937 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4938 if(want_to(prompt_buf, 'n', 'x', NO_HELP, rbflags) == 'y'){
4939 if(rflags)
4940 *rflags |= GER_OVER;
4942 if(our_unlink(full_filename) < 0){
4943 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4944 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4945 _("Cannot remove old %s: %s"),
4946 full_filename, error_description(errno));
4949 else{
4950 ret = -1;
4951 goto done;
4954 else if(!(flags & GE_IS_IMPORT)){
4955 r = strlen(filename);
4956 snprintf(prompt_buf, sizeof(prompt_buf),
4957 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4958 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4959 (r > 20) ? "..." : "",
4960 filename + ((r > 20) ? r - 20 : 0));
4961 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4962 switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global),
4963 access_opts, 'a', 'x', NO_HELP, rbflags)){
4964 case 'o' :
4965 if(rflags)
4966 *rflags |= GER_OVER;
4968 if(our_truncate(full_filename, (off_t)0) < 0)
4969 /* trouble truncating, but we'll give it a try anyway */
4970 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4971 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
4972 _("Warning: Cannot truncate old %s: %s"),
4973 full_filename, error_description(errno));
4974 break;
4976 case 'a' :
4977 if(rflags)
4978 *rflags |= GER_APPEND;
4980 break;
4982 case 'x' :
4983 default :
4984 ret = -1;
4985 goto done;
4990 done:
4991 if(history && ret == 0){
4992 save_hist(*history, full_filename, 0, NULL);
4993 strncpy(tmp, full_filename, MAXPATH);
4994 tmp[MAXPATH] = '\0';
4995 if((fn = strrchr(tmp, C_FILESEP)) != NULL)
4996 *fn = '\0';
4997 else
4998 tmp[0] = '\0';
4999 if(tmp[0])
5000 save_hist(dir_hist, tmp, 0, NULL);
5003 if(opts && opts != optsarg)
5004 fs_give((void **) &opts);
5006 return(ret);
5010 /*----------------------------------------------------------------------
5011 parse the config'd upload/download command
5013 Args: cmd -- buffer to return command fit for shellin'
5014 prefix --
5015 cfg_str --
5016 fname -- file name to build into the command
5018 Returns: pointer to cmd_str buffer or NULL on real bad error
5020 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
5021 cfg_str is written to standard out right before a successful
5022 return of this function. The call immediately following this
5023 function darn well better be the shell exec...
5024 ----*/
5025 char *
5026 build_updown_cmd(char *cmd, size_t cmdlen, char *prefix, char *cfg_str, char *fname)
5028 char *p;
5029 int fname_found = 0;
5031 if(prefix && *prefix){
5032 /* loop thru replacing all occurances of _FILE_ */
5033 p = strncpy(cmd, prefix, cmdlen);
5034 cmd[cmdlen-1] = '\0';
5035 while((p = strstr(p, "_FILE_")))
5036 rplstr(p, cmdlen-(p-cmd), 6, fname);
5038 fputs(cmd, stdout);
5041 /* loop thru replacing all occurances of _FILE_ */
5042 p = strncpy(cmd, cfg_str, cmdlen);
5043 cmd[cmdlen-1] = '\0';
5044 while((p = strstr(p, "_FILE_"))){
5045 rplstr(p, cmdlen-(p-cmd), 6, fname);
5046 fname_found = 1;
5049 if(!fname_found)
5050 snprintf(cmd+strlen(cmd), cmdlen-strlen(cmd), " %s", fname);
5052 cmd[cmdlen-1] = '\0';
5054 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
5055 cmd ? cmd : "?"));
5056 return(cmd);
5060 /*----------------------------------------------------------------------
5061 Write a berzerk format message delimiter using the given putc function
5063 Args: e -- envelope of message to write
5064 pc -- function to use
5066 Returns: TRUE if we could write it, FALSE if there was a problem
5068 NOTE: follows delimiter with OS-dependent newline
5069 ----*/
5071 bezerk_delimiter(ENVELOPE *env, MESSAGECACHE *mc, gf_io_t pc, int leading_newline)
5073 MESSAGECACHE telt;
5074 time_t when;
5075 char *p;
5077 /* write "[\n]From mailbox[@host] " */
5078 if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1)
5079 && gf_puts("From ", pc)
5080 && gf_puts((env && env->from) ? env->from->mailbox
5081 : "the-concourse-on-high", pc)
5082 && gf_puts((env && env->from && env->from->host) ? "@" : "", pc)
5083 && gf_puts((env && env->from && env->from->host) ? env->from->host
5084 : "", pc)
5085 && (*pc)(' ')))
5086 return(0);
5088 if(mc && mc->valid)
5089 when = mail_longdate(mc);
5090 else if(env && env->date && env->date[0]
5091 && mail_parse_date(&telt,env->date))
5092 when = mail_longdate(&telt);
5093 else
5094 when = time(0);
5096 p = ctime(&when);
5098 while(p && *p && *p != '\n') /* write date */
5099 if(!(*pc)(*p++))
5100 return(0);
5102 if(!gf_puts(NEWLINE, pc)) /* write terminating newline */
5103 return(0);
5105 return(1);
5109 /*----------------------------------------------------------------------
5110 Execute command to jump to a given message number
5112 Args: qline -- Line to ask question on
5114 Result: returns true if the use selected a new message, false otherwise
5116 ----*/
5117 long
5118 jump_to(MSGNO_S *msgmap, int qline, UCS first_num, SCROLL_S *sparms, CmdWhere in_index)
5120 char jump_num_string[80], *j, prompt[70];
5121 HelpType help;
5122 int rc;
5123 static ESCKEY_S jump_to_key[] = { {0, 0, NULL, NULL},
5124 /* TRANSLATORS: go to First Message */
5125 {ctrl('Y'), 10, "^Y", N_("First Msg")},
5126 {ctrl('V'), 11, "^V", N_("Last Msg")},
5127 {-1, 0, NULL, NULL} };
5129 dprint((4, "\n - jump_to -\n"));
5131 #ifdef DEBUG
5132 if(sparms && sparms->jump_is_debug)
5133 return(get_level(qline, first_num, sparms));
5134 #endif
5136 if(!any_messages(msgmap, NULL, "to Jump to"))
5137 return(0L);
5139 if(first_num && first_num < 0x80 && isdigit((unsigned char) first_num)){
5140 jump_num_string[0] = first_num;
5141 jump_num_string[1] = '\0';
5143 else
5144 jump_num_string[0] = '\0';
5146 if(mn_total_cur(msgmap) > 1L){
5147 snprintf(prompt, sizeof(prompt), "Unselect %s msgs in favor of number to be entered",
5148 comatose(mn_total_cur(msgmap)));
5149 prompt[sizeof(prompt)-1] = '\0';
5150 if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n')
5151 return(0L);
5154 snprintf(prompt, sizeof(prompt), "%s number to jump to : ", in_index == ThrdIndx
5155 ? "Thread"
5156 : "Message");
5157 prompt[sizeof(prompt)-1] = '\0';
5159 help = NO_HELP;
5160 while(1){
5161 int flags = OE_APPEND_CURRENT;
5163 rc = optionally_enter(jump_num_string, qline, 0,
5164 sizeof(jump_num_string), prompt,
5165 jump_to_key, help, &flags);
5166 if(rc == 3){
5167 help = help == NO_HELP
5168 ? (in_index == ThrdIndx ? h_oe_jump_thd : h_oe_jump)
5169 : NO_HELP;
5170 continue;
5172 else if(rc == 10 || rc == 11){
5173 char warning[100];
5174 long closest;
5176 closest = closest_jump_target(rc == 10 ? 1L
5177 : ((in_index == ThrdIndx)
5178 ? msgmap->max_thrdno
5179 : mn_get_total(msgmap)),
5180 ps_global->mail_stream,
5181 msgmap, 0,
5182 in_index, warning, sizeof(warning));
5183 /* ignore warning */
5184 return(closest);
5188 * If we take out the *jump_num_string nonempty test in this if
5189 * then the closest_jump_target routine will offer a jump to the
5190 * last message. However, it is slow because you have to wait for
5191 * the status message and it is annoying for people who hit J command
5192 * by mistake and just want to hit return to do nothing, like has
5193 * always worked. So the test is there for now. Hubert 2002-08-19
5195 * Jumping to first/last message is now possible through ^Y/^V
5196 * commands above. jpf 2002-08-21
5197 * (and through "end" hubert 2006-07-07)
5199 if(rc == 0 && *jump_num_string != '\0'){
5200 removing_leading_and_trailing_white_space(jump_num_string);
5201 for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++)
5204 if(*j != '\0'){
5205 if(!strucmp("end", j))
5206 return((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap));
5208 q_status_message(SM_ORDER | SM_DING, 2, 2,
5209 _("Invalid number entered. Use only digits 0-9"));
5210 jump_num_string[0] = '\0';
5212 else{
5213 char warning[100];
5214 long closest, jump_num;
5216 if(*jump_num_string)
5217 jump_num = atol(jump_num_string);
5218 else
5219 jump_num = -1L;
5221 warning[0] = '\0';
5222 closest = closest_jump_target(jump_num, ps_global->mail_stream,
5223 msgmap,
5224 *jump_num_string ? 0 : 1,
5225 in_index, warning, sizeof(warning));
5226 if(warning[0])
5227 q_status_message(SM_ORDER | SM_DING, 2, 2, warning);
5229 if(closest == jump_num)
5230 return(jump_num);
5232 if(closest == 0L)
5233 jump_num_string[0] = '\0';
5234 else
5235 strncpy(jump_num_string, long2string(closest),
5236 sizeof(jump_num_string));
5239 continue;
5242 if(rc != 4)
5243 break;
5246 return(0L);
5251 * cmd_delete_action - handle msgno advance and such after single message deletion
5253 char *
5254 cmd_delete_action(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
5256 int opts;
5257 long msgno;
5258 char *rv = NULL;
5260 msgno = mn_get_cur(msgmap);
5261 advance_cur_after_delete(state, state->mail_stream, msgmap, in_index);
5263 if(IS_NEWS(state->mail_stream)
5264 || ((state->context_current->use & CNTXT_INCMNG)
5265 && context_isambig(state->cur_folder))){
5267 opts = (NSF_TRUST_FLAGS | NSF_SKIP_CHID);
5268 if(in_index == View)
5269 opts &= ~NSF_SKIP_CHID;
5271 (void)next_sorted_flagged(F_UNDEL|F_UNSEEN, state->mail_stream, msgno, &opts);
5272 if(!(opts & NSF_FLAG_MATCH)){
5273 char nextfolder[MAXPATH];
5275 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
5276 nextfolder[sizeof(nextfolder)-1] = '\0';
5277 rv = next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
5278 state->context_current, NULL, NULL)
5279 ? ". Press TAB for next folder."
5280 : ". No more folders to TAB to.";
5284 return(rv);
5289 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5291 char *
5292 cmd_delete_index(struct pine *state, MSGNO_S *msgmap)
5294 return(cmd_delete_action(state, msgmap,MsgIndx));
5298 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5300 char *
5301 cmd_delete_view(struct pine *state, MSGNO_S *msgmap)
5303 return(cmd_delete_action(state, msgmap, View));
5307 void
5308 advance_cur_after_delete(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, CmdWhere in_index)
5310 long new_msgno, msgno;
5311 int opts;
5313 new_msgno = msgno = mn_get_cur(msgmap);
5314 opts = NSF_TRUST_FLAGS;
5316 if(F_ON(F_DEL_SKIPS_DEL, state)){
5318 if(THREADING() && sp_viewing_a_thread(stream))
5319 opts |= NSF_SKIP_CHID;
5321 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5323 else{
5324 mn_inc_cur(stream, msgmap,
5325 (in_index == View && THREADING()
5326 && sp_viewing_a_thread(stream))
5327 ? MH_THISTHD
5328 : (in_index == View)
5329 ? MH_ANYTHD : MH_NONE);
5330 new_msgno = mn_get_cur(msgmap);
5331 if(new_msgno != msgno)
5332 opts |= NSF_FLAG_MATCH;
5336 * Viewing_a_thread is the complicated case because we want to ignore
5337 * other threads at first and then look in other threads if we have to.
5338 * By ignoring other threads we also ignore collapsed partial threads
5339 * in our own thread.
5341 if(THREADING() && sp_viewing_a_thread(stream) && !(opts & NSF_FLAG_MATCH)){
5342 long rawno, orig_thrdno;
5343 PINETHRD_S *thrd, *topthrd = NULL;
5345 rawno = mn_m2raw(msgmap, msgno);
5346 thrd = fetch_thread(stream, rawno);
5347 if(thrd && thrd->top)
5348 topthrd = fetch_thread(stream, thrd->top);
5350 orig_thrdno = topthrd ? topthrd->thrdno : -1L;
5352 opts = NSF_TRUST_FLAGS;
5353 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5356 * If we got a match, new_msgno may be a message in
5357 * a different thread from the one we are viewing, or it could be
5358 * in a collapsed part of this thread.
5360 if(opts & NSF_FLAG_MATCH){
5361 int ret;
5362 char pmt[128];
5364 topthrd = NULL;
5365 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
5366 if(thrd && thrd->top)
5367 topthrd = fetch_thread(stream, thrd->top);
5370 * If this match is in the same thread we're already in
5371 * then we're done, else we have to ask the user and maybe
5372 * switch threads.
5374 if(!(orig_thrdno > 0L && topthrd
5375 && topthrd->thrdno == orig_thrdno)){
5377 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
5378 if(in_index == View)
5379 snprintf(pmt, sizeof(pmt),
5380 "View message in thread number %.10s",
5381 topthrd ? comatose(topthrd->thrdno) : "?");
5382 else
5383 snprintf(pmt, sizeof(pmt), "View thread number %.10s",
5384 topthrd ? comatose(topthrd->thrdno) : "?");
5386 ret = want_to(pmt, 'y', 'x', NO_HELP, WT_NORM);
5388 else
5389 ret = 'y';
5391 if(ret == 'y'){
5392 unview_thread(state, stream, msgmap);
5393 mn_set_cur(msgmap, new_msgno);
5394 if(THRD_AUTO_VIEW()
5395 && (count_lflags_in_thread(stream, topthrd, msgmap,
5396 MN_NONE) == 1)
5397 && view_thread(state, stream, msgmap, 1)){
5398 if(current_index_state)
5399 msgmap->top_after_thrd = current_index_state->msg_at_top;
5401 state->view_skipped_index = 1;
5402 state->next_screen = mail_view_screen;
5404 else{
5405 view_thread(state, stream, msgmap, 1);
5406 if(current_index_state)
5407 msgmap->top_after_thrd = current_index_state->msg_at_top;
5409 state->next_screen = SCREEN_FUN_NULL;
5412 else
5413 new_msgno = msgno; /* stick with original */
5418 mn_set_cur(msgmap, new_msgno);
5419 if(in_index != View)
5420 adjust_cur_to_visible(stream, msgmap);
5424 #ifdef DEBUG
5425 long
5426 get_level(int qline, UCS first_num, SCROLL_S *sparms)
5428 char debug_num_string[80], *j, prompt[70];
5429 HelpType help;
5430 int rc;
5431 long debug_num;
5433 if(first_num && first_num < 0x80 && isdigit((unsigned char)first_num)){
5434 debug_num_string[0] = first_num;
5435 debug_num_string[1] = '\0';
5436 debug_num = atol(debug_num_string);
5437 *(int *)(sparms->proc.data.p) = debug_num;
5438 q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s",
5439 comatose(debug_num));
5440 return(1L);
5442 else
5443 debug_num_string[0] = '\0';
5445 snprintf(prompt, sizeof(prompt), "Show debug <= this level (0-%d) : ", MAX(debug, 9));
5446 prompt[sizeof(prompt)-1] = '\0';
5448 help = NO_HELP;
5449 while(1){
5450 int flags = OE_APPEND_CURRENT;
5452 rc = optionally_enter(debug_num_string, qline, 0,
5453 sizeof(debug_num_string), prompt,
5454 NULL, help, &flags);
5455 if(rc == 3){
5456 help = help == NO_HELP ? h_oe_debuglevel : NO_HELP;
5457 continue;
5460 if(rc == 0){
5461 removing_leading_and_trailing_white_space(debug_num_string);
5462 for(j=debug_num_string; isdigit((unsigned char)*j); j++)
5465 if(*j != '\0'){
5466 q_status_message(SM_ORDER | SM_DING, 2, 2,
5467 _("Invalid number entered. Use only digits 0-9"));
5468 debug_num_string[0] = '\0';
5470 else{
5471 debug_num = atol(debug_num_string);
5472 if(debug_num < 0)
5473 q_status_message(SM_ORDER | SM_DING, 2, 2,
5474 _("Number should be >= 0"));
5475 else if(debug_num > MAX(debug,9))
5476 q_status_message1(SM_ORDER | SM_DING, 2, 2,
5477 _("Maximum is %s"), comatose(MAX(debug,9)));
5478 else{
5479 *(int *)(sparms->proc.data.p) = debug_num;
5480 q_status_message1(SM_ORDER, 0, 3,
5481 "Show debug <= level %s",
5482 comatose(debug_num));
5483 return(1L);
5487 continue;
5490 if(rc != 4)
5491 break;
5494 return(0L);
5496 #endif /* DEBUG */
5500 * Returns the message number closest to target that isn't hidden.
5501 * Make warning at least 100 chars.
5502 * A return of 0 means there is no message to jump to.
5504 long
5505 closest_jump_target(long int target, MAILSTREAM *stream, MSGNO_S *msgmap, int no_target, CmdWhere in_index, char *warning, size_t warninglen)
5507 long i, start, closest = 0L;
5508 char buf[80];
5509 long maxnum;
5511 warning[0] = '\0';
5512 maxnum = (in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap);
5514 if(no_target){
5515 target = maxnum;
5516 start = 1L;
5517 snprintf(warning, warninglen, "No %s number entered, jump to end? ",
5518 (in_index == ThrdIndx) ? "thread" : "message");
5519 warning[warninglen-1] = '\0';
5521 else if(target < 1L)
5522 start = 1L - target;
5523 else if(target > maxnum)
5524 start = target - maxnum;
5525 else
5526 start = 1L;
5528 if(target > 0L && target <= maxnum)
5529 if(in_index == ThrdIndx
5530 || !msgline_hidden(stream, msgmap, target, 0))
5531 return(target);
5533 for(i = start; target+i <= maxnum || target-i > 0L; i++){
5535 if(target+i > 0L && target+i <= maxnum &&
5536 (in_index == ThrdIndx
5537 || !msgline_hidden(stream, msgmap, target+i, 0))){
5538 closest = target+i;
5539 break;
5542 if(target-i > 0L && target-i <= maxnum &&
5543 (in_index == ThrdIndx
5544 || !msgline_hidden(stream, msgmap, target-i, 0))){
5545 closest = target-i;
5546 break;
5550 strncpy(buf, long2string(closest), sizeof(buf));
5551 buf[sizeof(buf)-1] = '\0';
5553 if(closest == 0L)
5554 strncpy(warning, "Nothing to jump to", warninglen);
5555 else if(target < 1L)
5556 snprintf(warning, warninglen, "%s number (%s) must be at least %s",
5557 (in_index == ThrdIndx) ? "Thread" : "Message",
5558 long2string(target), buf);
5559 else if(target > maxnum)
5560 snprintf(warning, warninglen, "%s number (%s) may be no more than %s",
5561 (in_index == ThrdIndx) ? "Thread" : "Message",
5562 long2string(target), buf);
5563 else if(!no_target)
5564 snprintf(warning, warninglen,
5565 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5566 long2string(target), buf);
5568 warning[warninglen-1] = '\0';
5570 return(closest);
5574 /*----------------------------------------------------------------------
5575 Prompt for folder name to open, expand the name and return it
5577 Args: qline -- Screen line to prompt on
5578 allow_list -- if 1, allow ^T to bring up collection lister
5580 Result: returns the folder name or NULL
5581 pine structure mangled_footer flag is set
5582 may call the collection lister in which case mangled screen will be set
5584 This prompts the user for the folder to open, possibly calling up
5585 the collection lister if the user types ^T.
5586 ----------------------------------------------------------------------*/
5587 char *
5588 broach_folder(int qline, int allow_list, int *notrealinbox, CONTEXT_S **context)
5590 HelpType help;
5591 static char newfolder[MAILTMPLEN];
5592 char expanded[MAXPATH+1],
5593 prompt[MAX_SCREEN_COLS+1],
5594 *last_folder, *p;
5595 unsigned char *f1, *f2, *f3;
5596 static HISTORY_S *history = NULL;
5597 CONTEXT_S *tc, *tc2;
5598 ESCKEY_S ekey[9];
5599 int rc, r, ku = -1, n, flags, last_rc = 0, inbox, done = 0;
5602 * the idea is to provide a clue for the context the file name
5603 * will be saved in (if a non-imap names is typed), and to
5604 * only show the previous if it was also in the same context
5606 help = NO_HELP;
5607 *expanded = '\0';
5608 *newfolder = '\0';
5609 last_folder = NULL;
5610 if(notrealinbox)
5611 (*notrealinbox) = 1;
5613 init_hist(&history, HISTSIZE);
5615 tc = broach_get_folder(context ? *context : NULL, &inbox, NULL);
5617 /* set up extra command option keys */
5618 rc = 0;
5619 ekey[rc].ch = (allow_list) ? ctrl('T') : 0 ;
5620 ekey[rc].rval = (allow_list) ? 2 : 0;
5621 ekey[rc].name = (allow_list) ? "^T" : "";
5622 ekey[rc++].label = (allow_list) ? N_("ToFldrs") : "";
5624 if(ps_global->context_list->next){
5625 ekey[rc].ch = ctrl('P');
5626 ekey[rc].rval = 10;
5627 ekey[rc].name = "^P";
5628 ekey[rc++].label = N_("Prev Collection");
5630 ekey[rc].ch = ctrl('N');
5631 ekey[rc].rval = 11;
5632 ekey[rc].name = "^N";
5633 ekey[rc++].label = N_("Next Collection");
5636 ekey[rc].ch = ctrl('W');
5637 ekey[rc].rval = 17;
5638 ekey[rc].name = "^W";
5639 ekey[rc++].label = N_("INBOX");
5641 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
5642 ekey[rc].ch = TAB;
5643 ekey[rc].rval = 12;
5644 ekey[rc].name = "TAB";
5645 ekey[rc++].label = N_("Complete");
5648 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
5649 ekey[rc].ch = ctrl('X');
5650 ekey[rc].rval = 14;
5651 ekey[rc].name = "^X";
5652 ekey[rc++].label = N_("ListMatches");
5655 if(ps_global->context_list->next && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5656 ekey[rc].ch = KEY_UP;
5657 ekey[rc].rval = 10;
5658 ekey[rc].name = "";
5659 ekey[rc++].label = "";
5661 ekey[rc].ch = KEY_DOWN;
5662 ekey[rc].rval = 11;
5663 ekey[rc].name = "";
5664 ekey[rc++].label = "";
5666 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5667 ekey[rc].ch = KEY_UP;
5668 ekey[rc].rval = 30;
5669 ekey[rc].name = "";
5670 ku = rc;
5671 ekey[rc++].label = "";
5673 ekey[rc].ch = KEY_DOWN;
5674 ekey[rc].rval = 31;
5675 ekey[rc].name = "";
5676 ekey[rc++].label = "";
5679 ekey[rc].ch = -1;
5681 while(!done) {
5683 * Figure out next default value for this context. The idea
5684 * is that in each context the last folder opened is cached.
5685 * It's up to pick it out and display it. This is fine
5686 * and dandy if we've currently got the inbox open, BUT
5687 * if not, make the inbox the default the first time thru.
5689 if(!inbox){
5690 last_folder = ps_global->inbox_name;
5691 inbox = 1; /* pretend we're in inbox from here on out */
5693 else
5694 last_folder = (ps_global->last_unambig_folder[0])
5695 ? ps_global->last_unambig_folder
5696 : ((tc->last_folder[0]) ? tc->last_folder : NULL);
5698 if(last_folder){ /* MAXPATH + 1 = sizeof(expanded) */
5699 unsigned char *fname = folder_name_decoded((unsigned char *)last_folder);
5700 snprintf(expanded, sizeof(expanded), " [%.*s]", MAXPATH+1-5,
5701 fname ? (char *) fname : last_folder);
5702 if(fname) fs_give((void **)&fname);
5704 else
5705 *expanded = '\0';
5707 expanded[sizeof(expanded)-1] = '\0';
5709 /* only show collection number if more than one available */
5710 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5711 snprintf(prompt, sizeof(prompt), "GOTO %s in <%s> %.*s%s: ",
5712 NEWS_TEST(tc) ? "news group" : "folder",
5713 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5714 *expanded ? " " : "");
5715 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5716 snprintf(prompt, sizeof(prompt), "GOTO folder %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5717 *expanded ? " " : "");
5719 prompt[sizeof(prompt)-1] = '\0';
5721 if(utf8_width(prompt) > MAXPROMPT){
5722 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5723 snprintf(prompt, sizeof(prompt), "GOTO <%s> %.*s%s: ",
5724 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5725 *expanded ? " " : "");
5726 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5727 snprintf(prompt, sizeof(prompt), "GOTO %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5728 *expanded ? " " : "");
5730 prompt[sizeof(prompt)-1] = '\0';
5732 if(utf8_width(prompt) > MAXPROMPT){
5733 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5734 snprintf(prompt, sizeof(prompt), "<%s> %.*s%s: ",
5735 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5736 *expanded ? " " : "");
5737 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5738 snprintf(prompt, sizeof(prompt), "%.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5739 *expanded ? " " : "");
5741 prompt[sizeof(prompt)-1] = '\0';
5745 if(ku >= 0){
5746 if(items_in_hist(history) > 1){
5747 ekey[ku].name = HISTORY_UP_KEYNAME;
5748 ekey[ku].label = HISTORY_KEYLABEL;
5749 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
5750 ekey[ku+1].label = HISTORY_KEYLABEL;
5752 else{
5753 ekey[ku].name = "";
5754 ekey[ku].label = "";
5755 ekey[ku+1].name = "";
5756 ekey[ku+1].label = "";
5760 /* is there any other way to do this? The point is that we
5761 * are trying to hide mutf7 from the user, and use the utf8
5762 * equivalent. So we create a variable f to take place of
5763 * newfolder, including content and size. f2 is copy of f1
5764 * that has to freed. Sigh!
5766 f3 = (unsigned char *) cpystr(newfolder);
5767 f1 = fs_get(sizeof(newfolder));
5768 f2 = folder_name_decoded(f3);
5769 if(f3) fs_give((void **)&f3);
5770 strncpy((char *)f1, (char *)f2, sizeof(newfolder));
5771 f1[sizeof(newfolder)-1] = '\0';
5772 if(f2) fs_give((void **)&f2);
5774 flags = OE_APPEND_CURRENT;
5775 rc = optionally_enter((char *) f1, qline, 0, sizeof(newfolder),
5776 (char *) prompt, ekey, help, &flags);
5778 f2 = folder_name_encoded(f1);
5779 strncpy(newfolder, (char *)f2, sizeof(newfolder));
5780 if(f1) fs_give((void **)&f1);
5781 if(f2) fs_give((void **)&f2);
5783 ps_global->mangled_footer = 1;
5785 switch(rc){
5786 case -1 : /* o_e says error! */
5787 q_status_message(SM_ORDER | SM_DING, 3, 3,
5788 _("Error reading folder name"));
5789 return(NULL);
5791 case 0 : /* o_e says normal entry */
5792 removing_trailing_white_space(newfolder);
5793 removing_leading_white_space(newfolder);
5795 if(*newfolder){
5796 char *name, *fullname = NULL;
5797 int exists, breakout = 0;
5799 save_hist(history, newfolder, 0, tc);
5801 if(!(name = folder_is_nick(newfolder, FOLDERS(tc),
5802 FN_WHOLE_NAME)))
5803 name = newfolder;
5805 if(update_folder_spec(expanded, sizeof(expanded), name)){
5806 strncpy(name = newfolder, expanded, sizeof(newfolder));
5807 newfolder[sizeof(newfolder)-1] = '\0';
5810 exists = folder_name_exists(tc, name, &fullname);
5812 if(fullname){
5813 strncpy(name = newfolder, fullname, sizeof(newfolder));
5814 newfolder[sizeof(newfolder)-1] = '\0';
5815 fs_give((void **) &fullname);
5816 breakout = TRUE;
5820 * if we know the things a folder, open it.
5821 * else if we know its a directory, visit it.
5822 * else we're not sure (it either doesn't really
5823 * exist or its unLISTable) so try opening it anyway
5825 if(exists & FEX_ISFILE){
5826 done++;
5827 break;
5829 else if((exists & FEX_ISDIR)){
5830 if(breakout){
5831 CONTEXT_S *fake_context;
5832 char tmp[MAILTMPLEN];
5833 size_t l;
5835 strncpy(tmp, name, sizeof(tmp));
5836 tmp[sizeof(tmp)-2-1] = '\0';
5837 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
5838 if(l < sizeof(tmp)){
5839 tmp[l] = tc->dir->delim;
5840 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
5843 else
5844 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
5846 tmp[sizeof(tmp)-1] = '\0';
5848 fake_context = new_context(tmp, 0);
5849 newfolder[0] = '\0';
5850 done = display_folder_list(&fake_context, newfolder,
5851 1, folders_for_goto);
5852 free_context(&fake_context);
5853 break;
5855 else if(!(tc->use & CNTXT_INCMNG)){
5856 done = display_folder_list(&tc, newfolder,
5857 1, folders_for_goto);
5858 break;
5861 else if((exists & FEX_ERROR)){
5862 q_status_message1(SM_ORDER, 0, 3,
5863 _("Problem accessing folder \"%s\""),
5864 newfolder);
5865 return(NULL);
5867 else{
5868 done++;
5869 break;
5872 if(exists == FEX_ERROR)
5873 q_status_message1(SM_ORDER, 0, 3,
5874 _("Problem accessing folder \"%s\""),
5875 newfolder);
5876 else if(tc->use & CNTXT_INCMNG)
5877 q_status_message1(SM_ORDER, 0, 3,
5878 _("Can't find Incoming Folder: %s"),
5879 newfolder);
5880 else if(context_isambig(newfolder))
5881 q_status_message2(SM_ORDER, 0, 3,
5882 _("Can't find folder \"%s\" in %s"),
5883 newfolder, (void *) tc->nickname);
5884 else
5885 q_status_message1(SM_ORDER, 0, 3,
5886 _("Can't find folder \"%s\""),
5887 newfolder);
5889 return(NULL);
5891 else if(last_folder){
5892 if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX
5893 && !strucmp(last_folder, ps_global->inbox_name)
5894 && tc == ((ps_global->context_list->use & CNTXT_INCMNG)
5895 ? ps_global->context_list->next : ps_global->context_list)){
5896 if(notrealinbox)
5897 (*notrealinbox) = 0;
5899 tc = ps_global->context_list;
5902 strncpy(newfolder, last_folder, sizeof(newfolder));
5903 newfolder[sizeof(newfolder)-1] = '\0';
5904 save_hist(history, newfolder, 0, tc);
5905 done++;
5906 break;
5908 /* fall thru like they cancelled */
5910 case 1 : /* o_e says user cancel */
5911 cmd_cancelled("Open folder");
5912 return(NULL);
5914 case 2 : /* o_e says user wants list */
5915 r = display_folder_list(&tc, newfolder, 0, folders_for_goto);
5916 if(r)
5917 done++;
5919 break;
5921 case 3 : /* o_e says user wants help */
5922 help = help == NO_HELP ? h_oe_broach : NO_HELP;
5923 break;
5925 case 4 : /* redraw */
5926 break;
5928 case 10 : /* Previous collection */
5929 tc2 = ps_global->context_list;
5930 while(tc2->next && tc2->next != tc)
5931 tc2 = tc2->next;
5933 tc = tc2;
5934 break;
5936 case 11 : /* Next collection */
5937 tc = (tc->next) ? tc->next : ps_global->context_list;
5938 break;
5940 case 12 : /* file name completion */
5941 if(!folder_complete(tc, newfolder, sizeof(newfolder), &n)){
5942 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
5943 r = display_folder_list(&tc, newfolder, 1,folders_for_goto);
5944 if(r)
5945 done++; /* bingo! */
5946 else
5947 rc = 0; /* burn last_rc */
5949 else
5950 Writechar(BELL, 0);
5953 break;
5955 case 14 : /* file name completion */
5956 r = display_folder_list(&tc, newfolder, 2, folders_for_goto);
5957 if(r)
5958 done++; /* bingo! */
5959 else
5960 rc = 0; /* burn last_rc */
5962 break;
5964 case 17 : /* GoTo INBOX */
5965 done++;
5966 strncpy(newfolder, ps_global->inbox_name, sizeof(newfolder)-1);
5967 newfolder[sizeof(newfolder)-1] = '\0';
5968 if(notrealinbox)
5969 (*notrealinbox) = 0;
5971 tc = ps_global->context_list;
5972 save_hist(history, newfolder, 0, tc);
5974 break;
5976 case 30 :
5977 if((p = get_prev_hist(history, newfolder, 0, tc)) != NULL){
5978 strncpy(newfolder, p, sizeof(newfolder));
5979 newfolder[sizeof(newfolder)-1] = '\0';
5980 if(history->hist[history->curindex])
5981 tc = history->hist[history->curindex]->cntxt;
5983 else
5984 Writechar(BELL, 0);
5986 break;
5988 case 31 :
5989 if((p = get_next_hist(history, newfolder, 0, tc)) != NULL){
5990 strncpy(newfolder, p, sizeof(newfolder));
5991 newfolder[sizeof(newfolder)-1] = '\0';
5992 if(history->hist[history->curindex])
5993 tc = history->hist[history->curindex]->cntxt;
5995 else
5996 Writechar(BELL, 0);
5998 break;
6000 default :
6001 alpine_panic("Unhandled case");
6002 break;
6005 last_rc = rc;
6008 dprint((2, "broach folder, name entered \"%s\"\n",
6009 newfolder ? newfolder : "?"));
6011 /*-- Just check that we can expand this. It gets done for real later --*/
6012 strncpy(expanded, newfolder, sizeof(expanded));
6013 expanded[sizeof(expanded)-1] = '\0';
6015 if(!expand_foldername(expanded, sizeof(expanded))) {
6016 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
6017 expanded ? expanded : "?"));
6018 return(NULL);
6021 *context = tc;
6022 return(newfolder);
6026 /*----------------------------------------------------------------------
6027 Check to see if user wants to reopen dead stream.
6029 Args: ps --
6030 reopenp --
6032 Result: 1 if the folder was successfully updatedn
6033 0 if not necessary
6035 ----*/
6037 ask_mailbox_reopen(struct pine *ps, int *reopenp)
6039 if(((ps->mail_stream->dtb
6040 && ((ps->mail_stream->dtb->flags & DR_NONEWMAIL)
6041 || (ps->mail_stream->rdonly
6042 && ps->mail_stream->dtb->flags & DR_NONEWMAILRONLY)))
6043 && (ps->reopen_rule == REOPEN_ASK_ASK_Y
6044 || ps->reopen_rule == REOPEN_ASK_ASK_N
6045 || ps->reopen_rule == REOPEN_ASK_NO_Y
6046 || ps->reopen_rule == REOPEN_ASK_NO_N))
6047 || ((ps->mail_stream->dtb
6048 && ps->mail_stream->rdonly
6049 && !(ps->mail_stream->dtb->flags & DR_LOCAL))
6050 && (ps->reopen_rule == REOPEN_YES_ASK_Y
6051 || ps->reopen_rule == REOPEN_YES_ASK_N
6052 || ps->reopen_rule == REOPEN_ASK_ASK_Y
6053 || ps->reopen_rule == REOPEN_ASK_ASK_N))){
6054 int deefault;
6056 switch(ps->reopen_rule){
6057 case REOPEN_YES_ASK_Y:
6058 case REOPEN_ASK_ASK_Y:
6059 case REOPEN_ASK_NO_Y:
6060 deefault = 'y';
6061 break;
6063 default:
6064 deefault = 'n';
6065 break;
6068 switch(want_to("Re-open folder to check for new messages", deefault,
6069 'x', h_reopen_folder, WT_NORM)){
6070 case 'y':
6071 (*reopenp)++;
6072 break;
6074 case 'x':
6075 return(-1);
6079 return(0);
6084 /*----------------------------------------------------------------------
6085 Check to see if user input is in form of old c-client mailbox speck
6087 Args: old --
6088 new --
6090 Result: 1 if the folder was successfully updatedn
6091 0 if not necessary
6093 ----*/
6095 update_folder_spec(char *new, size_t newlen, char *old)
6097 char *p, *orignew;
6098 int nntp = 0;
6100 orignew = new;
6101 if(*(p = old) == '*') /* old form? */
6102 old++;
6104 if(*old == '{') /* copy host spec */
6106 switch(*new = *old++){
6107 case '\0' :
6108 return(FALSE);
6110 case '/' :
6111 if(!struncmp(old, "nntp", 4))
6112 nntp++;
6114 break;
6116 default :
6117 break;
6119 while(*new++ != '}' && (new-orignew) < newlen-1);
6121 if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){
6123 * OK, some heuristics here. If it looks like a newsgroup
6124 * then we plunk it into the #news namespace else we
6125 * assume that they're trying to get at a #public folder...
6127 for(p = old;
6128 *p && (isalnum((unsigned char) *p) || strindex(".-", *p));
6129 p++)
6132 sstrncpy(&new, (*p && !nntp) ? "#public/" : "#news.", newlen-(new-orignew));
6133 strncpy(new, old, newlen-(new-orignew));
6134 return(TRUE);
6137 orignew[newlen-1] = '\0';
6139 return(FALSE);
6143 /*----------------------------------------------------------------------
6144 Open the requested folder in the requested context
6146 Args: state -- usual pine state struct
6147 newfolder -- folder to open
6148 new_context -- folder context might live in
6149 stream -- candidate for recycling
6151 Result: New folder open or not (if error), and we're set to
6152 enter the index screen.
6153 ----*/
6154 void
6155 visit_folder(struct pine *state, char *newfolder, CONTEXT_S *new_context,
6156 MAILSTREAM *stream, long unsigned int flags)
6158 dprint((9, "visit_folder(%s, %s)\n",
6159 newfolder ? newfolder : "?",
6160 (new_context && new_context->context)
6161 ? new_context->context : "(NULL)"));
6163 if(ps_global && ps_global->ttyo){
6164 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
6165 ps_global->mangled_footer = 1;
6168 if(do_broach_folder(newfolder, new_context, stream ? &stream : NULL,
6169 flags) >= 0
6170 || !sp_flagged(state->mail_stream, SP_LOCKED))
6171 state->next_screen = mail_index_screen;
6172 else
6173 state->next_screen = folder_screen;
6177 /*----------------------------------------------------------------------
6178 Move read messages from folder if listed in archive
6180 Args:
6182 ----*/
6184 read_msg_prompt(long int n, char *f)
6186 char buf[MAX_SCREEN_COLS+1];
6188 snprintf(buf, sizeof(buf), "Save the %ld read message%s in \"%s\"", n, plural(n), f);
6189 buf[sizeof(buf)-1] = '\0';
6190 return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y');
6194 /*----------------------------------------------------------------------
6195 Print current message[s] or folder index
6197 Args: state -- pointer to struct holding a bunch of pine state
6198 msgmap -- table mapping msg nums to c-client sequence nums
6199 aopt -- aggregate options
6200 in_index -- boolean indicating we're called from Index Screen
6202 Filters the original header and sends stuff to printer
6203 ---*/
6205 cmd_print(struct pine *state, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
6207 char prompt[250];
6208 long i, msgs, rawno;
6209 int next = 0, do_index = 0, rv = 0;
6210 ENVELOPE *e;
6211 BODY *b;
6212 MESSAGECACHE *mc;
6214 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
6215 return rv;
6217 msgs = mn_total_cur(msgmap);
6219 if((in_index != View) && F_ON(F_PRINT_INDEX, state)){
6220 char m[10];
6221 int ans;
6222 static ESCKEY_S prt_opts[] = {
6223 {'i', 'i', "I", N_("Index")},
6224 {'m', 'm', "M", NULL},
6225 {-1, 0, NULL, NULL}};
6227 if(in_index == ThrdIndx){
6228 /* TRANSLATORS: This is a question, Print Index ? */
6229 if(want_to(_("Print Index"), 'y', 'x', NO_HELP, WT_NORM) == 'y')
6230 ans = 'i';
6231 else
6232 ans = 'x';
6234 else{
6235 snprintf(m, sizeof(m), "Message%s", (msgs>1L) ? "s" : "");
6236 m[sizeof(m)-1] = '\0';
6237 prt_opts[1].label = m;
6238 snprintf(prompt, sizeof(prompt), "Print %sFolder Index or %s %s? ",
6239 (aopt & MCMD_AGG_2) ? "thread " : MCMD_ISAGG(aopt) ? "selected " : "",
6240 (aopt & MCMD_AGG_2) ? "thread" : MCMD_ISAGG(aopt) ? "selected" : "current", m);
6241 prompt[sizeof(prompt)-1] = '\0';
6243 ans = radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x',
6244 NO_HELP, RB_NORM|RB_SEQ_SENSITIVE);
6247 switch(ans){
6248 case 'x' :
6249 cmd_cancelled("Print");
6250 if(MCMD_ISAGG(aopt))
6251 restore_selected(msgmap);
6253 return rv;
6255 case 'i':
6256 do_index = 1;
6257 break;
6259 default :
6260 case 'm':
6261 break;
6265 if(do_index)
6266 snprintf(prompt, sizeof(prompt), "%sFolder Index",
6267 (aopt & MCMD_AGG_2) ? "Thread " : MCMD_ISAGG(aopt) ? "Selected " : "");
6268 else if(msgs > 1L)
6269 snprintf(prompt, sizeof(prompt), "%s messages", long2string(msgs));
6270 else
6271 snprintf(prompt, sizeof(prompt), "Message %s", long2string(mn_get_cur(msgmap)));
6273 prompt[sizeof(prompt)-1] = '\0';
6275 if(open_printer(prompt) < 0){
6276 if(MCMD_ISAGG(aopt))
6277 restore_selected(msgmap);
6279 return rv;
6282 if(do_index){
6283 TITLE_S *tc;
6285 tc = format_titlebar();
6287 /* Print titlebar... */
6288 print_text1("%s\n\n", tc ? tc->titlebar_line : "");
6289 /* then all the index members... */
6290 if(!print_index(state, msgmap, MCMD_ISAGG(aopt)))
6291 q_status_message(SM_ORDER | SM_DING, 3, 3,
6292 _("Error printing folder index"));
6293 else
6294 rv++;
6296 else{
6297 rv++;
6298 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){
6299 if(next && F_ON(F_AGG_PRINT_FF, state))
6300 if(!print_char(FORMFEED)){
6301 rv = 0;
6302 break;
6305 if(!(state->mail_stream
6306 && (rawno = mn_m2raw(msgmap, i)) > 0L
6307 && rawno <= state->mail_stream->nmsgs
6308 && (mc = mail_elt(state->mail_stream, rawno))
6309 && mc->valid))
6310 mc = NULL;
6312 if(!(e=pine_mail_fetchstructure(state->mail_stream,
6313 mn_m2raw(msgmap,i),
6314 &b))
6315 || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global)
6316 && !bezerk_delimiter(e, mc, print_char, next))
6317 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
6318 e, b, NULL, FM_NEW_MESS | FM_NOINDENT,
6319 print_char)){
6320 q_status_message(SM_ORDER | SM_DING, 3, 3,
6321 _("Error printing message"));
6322 rv = 0;
6323 break;
6328 close_printer();
6330 if(MCMD_ISAGG(aopt))
6331 restore_selected(msgmap);
6333 return rv;
6337 /*----------------------------------------------------------------------
6338 Pipe message text
6340 Args: state -- various pine state bits
6341 msgmap -- Message number mapping table
6342 aopt -- option flags
6344 Filters the original header and sends stuff to specified command
6345 ---*/
6347 cmd_pipe(struct pine *state, MSGNO_S *msgmap, int aopt)
6349 ENVELOPE *e;
6350 MESSAGECACHE *mc;
6351 BODY *b;
6352 PIPE_S *syspipe;
6353 char *resultfilename = NULL, prompt[80], *p;
6354 int done = 0, rv = 0;
6355 gf_io_t pc;
6356 int fourlabel = -1, j = 0, next = 0, ku;
6357 int pipe_rv; /* rv of proc to separate from close_system_pipe rv */
6358 long i, rawno;
6359 unsigned flagsforhist = 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6360 static HISTORY_S *history = NULL;
6361 int capture = 1, raw = 0, delimit = 0, newpipe = 0;
6362 char pipe_command[MAXPATH];
6363 ESCKEY_S pipe_opt[8];
6365 if(ps_global->restricted){
6366 q_status_message(SM_ORDER | SM_DING, 0, 4,
6367 "Alpine demo can't pipe messages");
6368 return rv;
6370 else if(!any_messages(msgmap, NULL, "to Pipe"))
6371 return rv;
6373 pipe_command[0] = '\0';
6374 init_hist(&history, HISTSIZE);
6375 flagsforhist = (raw ? 0x8 : 0) +
6376 (delimit ? 0x4 : 0) +
6377 (newpipe ? 0x2 : 0) +
6378 (capture ? 0x1 : 0);
6379 if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){
6380 strncpy(pipe_command, p, sizeof(pipe_command));
6381 pipe_command[sizeof(pipe_command)-1] = '\0';
6382 if(history->hist[history->curindex]){
6383 flagsforhist = history->hist[history->curindex]->flags;
6384 raw = (flagsforhist & 0x8) ? 1 : 0;
6385 delimit = (flagsforhist & 0x4) ? 1 : 0;
6386 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6387 capture = (flagsforhist & 0x1) ? 1 : 0;
6391 pipe_opt[j].ch = 0;
6392 pipe_opt[j].rval = 0;
6393 pipe_opt[j].name = "";
6394 pipe_opt[j++].label = "";
6396 pipe_opt[j].ch = ctrl('W');
6397 pipe_opt[j].rval = 10;
6398 pipe_opt[j].name = "^W";
6399 pipe_opt[j++].label = NULL;
6401 pipe_opt[j].ch = ctrl('Y');
6402 pipe_opt[j].rval = 11;
6403 pipe_opt[j].name = "^Y";
6404 pipe_opt[j++].label = NULL;
6406 pipe_opt[j].ch = ctrl('R');
6407 pipe_opt[j].rval = 12;
6408 pipe_opt[j].name = "^R";
6409 pipe_opt[j++].label = NULL;
6411 if(MCMD_ISAGG(aopt)){
6412 if(!pseudo_selected(state->mail_stream, msgmap))
6413 return rv;
6414 else{
6415 fourlabel = j;
6416 pipe_opt[j].ch = ctrl('T');
6417 pipe_opt[j].rval = 13;
6418 pipe_opt[j].name = "^T";
6419 pipe_opt[j++].label = NULL;
6423 pipe_opt[j].ch = KEY_UP;
6424 pipe_opt[j].rval = 30;
6425 pipe_opt[j].name = "";
6426 ku = j;
6427 pipe_opt[j++].label = "";
6429 pipe_opt[j].ch = KEY_DOWN;
6430 pipe_opt[j].rval = 31;
6431 pipe_opt[j].name = "";
6432 pipe_opt[j++].label = "";
6434 pipe_opt[j].ch = -1;
6436 while (!done) {
6437 int flags;
6439 snprintf(prompt, sizeof(prompt), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6440 raw ? "RAW " : "",
6441 MCMD_ISAGG(aopt) ? "s" : " ",
6442 MCMD_ISAGG(aopt) ? "" : comatose(mn_get_cur(msgmap)),
6443 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? "(" : "",
6444 capture ? "" : "uncaptured",
6445 (!capture && delimit) ? "," : "",
6446 delimit ? "delimited" : "",
6447 ((!capture || delimit) && newpipe && MCMD_ISAGG(aopt)) ? "," : "",
6448 (newpipe && MCMD_ISAGG(aopt)) ? "new pipe" : "",
6449 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? ") " : "");
6450 prompt[sizeof(prompt)-1] = '\0';
6451 pipe_opt[1].label = raw ? N_("Shown Text") : N_("Raw Text");
6452 pipe_opt[2].label = capture ? N_("Free Output") : N_("Capture Output");
6453 pipe_opt[3].label = delimit ? N_("No Delimiter") : N_("With Delimiter");
6454 if(fourlabel > 0)
6455 pipe_opt[fourlabel].label = newpipe ? N_("To Same Pipe") : N_("To Individual Pipes");
6459 * 2 is really 1 because there will be one real entry and
6460 * one entry of "" because of the get_prev_hist above.
6462 if(items_in_hist(history) > 2){
6463 pipe_opt[ku].name = HISTORY_UP_KEYNAME;
6464 pipe_opt[ku].label = HISTORY_KEYLABEL;
6465 pipe_opt[ku+1].name = HISTORY_DOWN_KEYNAME;
6466 pipe_opt[ku+1].label = HISTORY_KEYLABEL;
6468 else{
6469 pipe_opt[ku].name = "";
6470 pipe_opt[ku].label = "";
6471 pipe_opt[ku+1].name = "";
6472 pipe_opt[ku+1].label = "";
6475 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
6476 switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0,
6477 sizeof(pipe_command), prompt,
6478 pipe_opt, NO_HELP, &flags)){
6479 case -1 :
6480 q_status_message(SM_ORDER | SM_DING, 3, 4,
6481 _("Internal problem encountered"));
6482 done++;
6483 break;
6485 case 10 : /* flip raw bit */
6486 raw = !raw;
6487 break;
6489 case 11 : /* flip capture bit */
6490 capture = !capture;
6491 break;
6493 case 12 : /* flip delimit bit */
6494 delimit = !delimit;
6495 break;
6497 case 13 : /* flip newpipe bit */
6498 newpipe = !newpipe;
6499 break;
6501 case 30 :
6502 flagsforhist = (raw ? 0x8 : 0) +
6503 (delimit ? 0x4 : 0) +
6504 (newpipe ? 0x2 : 0) +
6505 (capture ? 0x1 : 0);
6506 if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6507 strncpy(pipe_command, p, sizeof(pipe_command));
6508 pipe_command[sizeof(pipe_command)-1] = '\0';
6509 if(history->hist[history->curindex]){
6510 flagsforhist = history->hist[history->curindex]->flags;
6511 raw = (flagsforhist & 0x8) ? 1 : 0;
6512 delimit = (flagsforhist & 0x4) ? 1 : 0;
6513 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6514 capture = (flagsforhist & 0x1) ? 1 : 0;
6517 else
6518 Writechar(BELL, 0);
6520 break;
6522 case 31 :
6523 flagsforhist = (raw ? 0x8 : 0) +
6524 (delimit ? 0x4 : 0) +
6525 (newpipe ? 0x2 : 0) +
6526 (capture ? 0x1 : 0);
6527 if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6528 strncpy(pipe_command, p, sizeof(pipe_command));
6529 pipe_command[sizeof(pipe_command)-1] = '\0';
6530 if(history->hist[history->curindex]){
6531 flagsforhist = history->hist[history->curindex]->flags;
6532 raw = (flagsforhist & 0x8) ? 1 : 0;
6533 delimit = (flagsforhist & 0x4) ? 1 : 0;
6534 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6535 capture = (flagsforhist & 0x1) ? 1 : 0;
6538 else
6539 Writechar(BELL, 0);
6541 break;
6543 case 0 :
6544 if(pipe_command[0]){
6546 flagsforhist = (raw ? 0x8 : 0) +
6547 (delimit ? 0x4 : 0) +
6548 (newpipe ? 0x2 : 0) +
6549 (capture ? 0x1 : 0);
6550 save_hist(history, pipe_command, flagsforhist, NULL);
6552 flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
6553 flags |= (raw ? PIPE_RAW : 0);
6554 if(!capture){
6555 #ifndef _WINDOWS
6556 ClearScreen();
6557 fflush(stdout);
6558 clear_cursor_pos();
6559 ps_global->mangled_screen = 1;
6560 ps_global->in_init_seq = 1;
6561 #endif
6562 flags |= PIPE_RESET;
6565 if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command,
6566 (flags & PIPE_RESET)
6567 ? NULL
6568 : &resultfilename,
6569 flags, &pc)))
6570 done++;
6572 for(i = mn_first_cur(msgmap);
6573 i > 0L && !done;
6574 i = mn_next_cur(msgmap)){
6575 e = pine_mail_fetchstructure(ps_global->mail_stream,
6576 mn_m2raw(msgmap, i), &b);
6577 if(!(state->mail_stream
6578 && (rawno = mn_m2raw(msgmap, i)) > 0L
6579 && rawno <= state->mail_stream->nmsgs
6580 && (mc = mail_elt(state->mail_stream, rawno))
6581 && mc->valid))
6582 mc = NULL;
6584 if((newpipe
6585 && !(syspipe = cmd_pipe_open(pipe_command,
6586 (flags & PIPE_RESET)
6587 ? NULL
6588 : &resultfilename,
6589 flags, &pc)))
6590 || (delimit && !bezerk_delimiter(e, mc, pc, next++)))
6591 done++;
6593 if(!done){
6594 if(raw){
6595 char *pipe_err;
6597 prime_raw_pipe_getc(ps_global->mail_stream,
6598 mn_m2raw(msgmap, i), -1L, 0L);
6599 gf_filter_init();
6600 gf_link_filter(gf_nvtnl_local, NULL);
6601 if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){
6602 q_status_message1(SM_ORDER|SM_DING,
6603 3, 3,
6604 _("Internal Error: %s"),
6605 pipe_err);
6606 done++;
6609 else if(!format_message(mn_m2raw(msgmap, i), e, b,
6610 NULL, FM_NEW_MESS | FM_NOWRAP, pc))
6611 done++;
6614 if(newpipe)
6615 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6616 done++;
6619 if(!capture)
6620 ps_global->in_init_seq = 0;
6622 if(!newpipe)
6623 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6624 done++;
6625 if(done) /* say we had a problem */
6626 q_status_message(SM_ORDER | SM_DING, 3, 3,
6627 _("Error piping message"));
6628 else if(resultfilename){
6629 rv++;
6630 /* only display if no error */
6631 display_output_file(resultfilename, "PIPE MESSAGE",
6632 NULL, DOF_EMPTY);
6633 fs_give((void **)&resultfilename);
6635 else{
6636 rv++;
6637 q_status_message(SM_ORDER, 0, 2, _("Pipe command completed"));
6640 done++;
6641 break;
6643 /* else fall thru as if cancelled */
6645 case 1 :
6646 cmd_cancelled("Pipe command");
6647 done++;
6648 break;
6650 case 3 :
6651 helper(h_common_pipe, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE);
6652 ps_global->mangled_screen = 1;
6653 break;
6655 case 2 : /* no place to escape to */
6656 case 4 : /* can't suspend */
6657 default :
6658 break;
6662 ps_global->mangled_footer = 1;
6663 if(MCMD_ISAGG(aopt))
6664 restore_selected(msgmap);
6666 return rv;
6670 /*----------------------------------------------------------------------
6671 Screen to offer list management commands contained in message
6673 Args: state -- pointer to struct holding a bunch of pine state
6674 msgmap -- table mapping msg nums to c-client sequence nums
6675 aopt -- aggregate options
6677 Result:
6679 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6680 ----*/
6681 void
6682 rfc2369_display(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno)
6684 int winner = 0;
6685 char *h, *hdrs[MLCMD_COUNT + 1];
6686 long index_no = mn_raw2m(msgmap, msgno);
6687 RFC2369_S data[MLCMD_COUNT];
6689 /* for each header field */
6690 if((h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))) != NULL){
6691 memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT);
6692 if(rfc2369_parse_fields(h, &data[0])){
6693 STORE_S *explain;
6695 if((explain = list_mgmt_text(data, index_no)) != NULL){
6696 list_mgmt_screen(explain);
6697 ps_global->mangled_screen = 1;
6698 so_give(&explain);
6699 winner++;
6703 fs_give((void **) &h);
6706 if(!winner)
6707 q_status_message1(SM_ORDER, 0, 3,
6708 "Message %s contains no list management information",
6709 comatose(index_no));
6713 STORE_S *
6714 list_mgmt_text(RFC2369_S *data, long int msgno)
6716 STORE_S *store;
6717 int i, j, n, fields = 0;
6718 static char *rfc2369_intro1 =
6719 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6720 static char *rfc2369_intro2[] = {
6721 N_(" has information associated with it "),
6722 N_("that explains how to participate in an email list. An "),
6723 N_("email list is represented by a single email address that "),
6724 N_("users sharing a common interest can send messages to (known "),
6725 N_("as posting) which are then redistributed to all members "),
6726 N_("of the list (sometimes after review by a moderator)."),
6727 N_("<P>List participation commands in this message include:"),
6728 NULL
6731 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6733 /* Insert introductory text */
6734 so_puts(store, rfc2369_intro1);
6736 so_puts(store, comatose(msgno));
6738 for(i = 0; rfc2369_intro2[i]; i++)
6739 so_puts(store, _(rfc2369_intro2[i]));
6741 so_puts(store, "<P>");
6742 for(i = 0; i < MLCMD_COUNT; i++)
6743 if(data[i].data[0].value
6744 || data[i].data[0].comment
6745 || data[i].data[0].error){
6746 if(!fields++)
6747 so_puts(store, "<UL>");
6749 so_puts(store, "<LI>");
6750 so_puts(store,
6751 (n = (data[i].data[1].value || data[i].data[1].comment))
6752 ? "Methods to "
6753 : "A method to ");
6755 so_puts(store, data[i].field.description);
6756 so_puts(store, ". ");
6758 if(n)
6759 so_puts(store, "<OL>");
6761 for(j = 0;
6762 j < MLCMD_MAXDATA
6763 && (data[i].data[j].comment
6764 || data[i].data[j].value
6765 || data[i].data[j].error);
6766 j++){
6768 so_puts(store, n ? "<P><LI>" : "<P>");
6770 if(data[i].data[j].comment){
6771 so_puts(store,
6772 _("With the provided comment:<P><BLOCKQUOTE>"));
6773 so_puts(store, data[i].data[j].comment);
6774 so_puts(store, "</BLOCKQUOTE><P>");
6777 if(data[i].data[j].value){
6778 if(i == MLCMD_POST
6779 && !strucmp(data[i].data[j].value, "NO")){
6780 so_puts(store,
6781 _("Posting is <EM>not</EM> allowed on this list"));
6783 else{
6784 so_puts(store, "Select <A HREF=\"");
6785 so_puts(store, data[i].data[j].value);
6786 so_puts(store, "\">HERE</A> to ");
6787 so_puts(store, (data[i].field.action)
6788 ? data[i].field.action
6789 : "try it");
6792 so_puts(store, ".");
6795 if(data[i].data[j].error){
6796 so_puts(store, "<P>Unfortunately, Alpine can not offer");
6797 so_puts(store, " to take direct action based upon it");
6798 so_puts(store, " because it was improperly formatted.");
6799 so_puts(store, " The unrecognized data associated with");
6800 so_puts(store, " the \"");
6801 so_puts(store, data[i].field.name);
6802 so_puts(store, "\" header field was:<P><BLOCKQUOTE>");
6803 so_puts(store, data[i].data[j].error);
6804 so_puts(store, "</BLOCKQUOTE>");
6807 so_puts(store, "<P>");
6810 if(n)
6811 so_puts(store, "</OL>");
6814 if(fields)
6815 so_puts(store, "</UL>");
6817 so_puts(store, "</BODY></HTML>");
6820 return(store);
6824 void
6825 list_mgmt_screen(STORE_S *html)
6827 int cmd = MC_NONE;
6828 long offset = 0L;
6829 char *error = NULL;
6830 STORE_S *store;
6831 HANDLE_S *handles = NULL;
6832 gf_io_t gc, pc;
6835 so_seek(html, 0L, 0);
6836 gf_set_so_readc(&gc, html);
6838 init_handles(&handles);
6840 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6841 gf_set_so_writec(&pc, store);
6842 gf_filter_init();
6844 gf_link_filter(gf_html2plain,
6845 gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols,
6846 non_messageview_margin(), &handles, NULL, 0));
6848 error = gf_pipe(gc, pc);
6850 gf_clear_so_writec(store);
6852 if(!error){
6853 SCROLL_S sargs;
6855 memset(&sargs, 0, sizeof(SCROLL_S));
6856 sargs.text.text = so_text(store);
6857 sargs.text.src = CharStar;
6858 sargs.text.desc = "list commands";
6859 sargs.text.handles = handles;
6860 if(offset){
6861 sargs.start.on = Offset;
6862 sargs.start.loc.offset = offset;
6865 sargs.bar.title = _("MAIL LIST COMMANDS");
6866 sargs.bar.style = MessageNumber;
6867 sargs.resize_exit = 1;
6868 sargs.help.text = h_special_list_commands;
6869 sargs.help.title = _("HELP FOR LIST COMMANDS");
6870 sargs.keys.menu = &listmgr_keymenu;
6871 setbitmap(sargs.keys.bitmap);
6872 if(!handles){
6873 clrbitn(LM_TRY_KEY, sargs.keys.bitmap);
6874 clrbitn(LM_PREV_KEY, sargs.keys.bitmap);
6875 clrbitn(LM_NEXT_KEY, sargs.keys.bitmap);
6878 cmd = scrolltool(&sargs);
6879 offset = sargs.start.loc.offset;
6882 so_give(&store);
6885 free_handles(&handles);
6886 gf_clear_so_readc(html);
6888 while(cmd == MC_RESIZE);
6892 /*----------------------------------------------------------------------
6893 Prompt the user for the type of select desired
6895 NOTE: any and all functions that successfully exit the second
6896 switch() statement below (currently "select_*() functions"),
6897 *MUST* update the folder's MESSAGECACHE element's "searched"
6898 bits to reflect the search result. Functions using
6899 mail_search() get this for free, the others must update 'em
6900 by hand.
6902 Returns -1 if canceled without changing selection
6903 0 if selection may have changed
6904 ----*/
6906 aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index)
6908 long i, diff, old_tot, msgno, raw;
6909 int q = 0, rv = 0, narrow = 0, hidden, ret = -1;
6910 ESCKEY_S *sel_opts;
6911 MESSAGECACHE *mc;
6912 SEARCHSET *limitsrch = NULL;
6913 PINETHRD_S *thrd;
6914 extern MAILSTREAM *mm_search_stream;
6915 extern long mm_search_count;
6917 hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
6918 mm_search_stream = state->mail_stream;
6919 mm_search_count = 0L;
6921 sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2;
6922 if(THREADING()){
6923 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH;
6925 else{
6926 sel_opts[SEL_OPTS_THREAD].ch = -1;
6929 if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){
6930 if(THRD_INDX()){
6931 i = 0;
6932 thrd = fetch_thread(state->mail_stream,
6933 mn_m2raw(msgmap, mn_get_cur(msgmap)));
6934 /* check if whole thread is selected or not */
6935 if(thrd &&
6936 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT)
6938 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE))
6939 i = 1;
6941 sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd");
6943 else{
6944 i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap),
6945 MN_SLCT);
6946 sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur");
6949 sel_opts += 2; /* disable extra options */
6950 switch(q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6951 RB_NORM)){
6952 case 'f' : /* flip selection */
6953 msgno = 0L;
6954 for(i = 1L; i <= mn_get_total(msgmap); i++){
6955 ret = 0;
6956 q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT);
6957 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q);
6958 if(hidden){
6959 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q);
6960 if(!msgno && q)
6961 mn_reset_cur(msgmap, msgno = i);
6965 return(ret);
6967 case 'n' : /* narrow selection */
6968 narrow++;
6969 case 'b' : /* broaden selection */
6970 q = 0; /* offer criteria prompt */
6971 break;
6973 case 'c' : /* Un/Select Current */
6974 case 'a' : /* Unselect All */
6975 case 'x' : /* cancel */
6976 break;
6978 default :
6979 q_status_message(SM_ORDER | SM_DING, 3, 3,
6980 "Unsupported Select option");
6981 return(ret);
6985 if(!q){
6986 while(1){
6987 q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x',
6988 NO_HELP, RB_NORM|RB_RET_HELP);
6990 if(q == 3){
6991 helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE);
6992 ps_global->mangled_screen = 1;
6994 else
6995 break;
7000 * The purpose of this is to add the appropriate searchset to the
7001 * search so that the search can be limited to only looking at what
7002 * it needs to look at. That is, if we are narrowing then we only need
7003 * to look at messages which are already selected, and if we are
7004 * broadening, then we only need to look at messages which are not
7005 * yet selected. This routine will work whether or not
7006 * limiting_searchset properly limits the search set. In particular,
7007 * the searchset returned by limiting_searchset may include messages
7008 * which really shouldn't be included. We do that because a too-large
7009 * searchset will break some IMAP servers. It is even possible that it
7010 * becomes inefficient to send the whole set. If the select function
7011 * frees limitsrch, it should be sure to set it to NULL so we won't
7012 * try freeing it again here.
7014 limitsrch = limiting_searchset(state->mail_stream, narrow);
7017 * NOTE: See note about MESSAGECACHE "searched" bits above!
7019 switch(q){
7020 case 'x': /* cancel */
7021 cmd_cancelled("Select command");
7022 return(ret);
7024 case 'c' : /* select/unselect current */
7025 (void) select_by_current(state, msgmap, in_index);
7026 ret = 0;
7027 return(ret);
7029 case 'a' : /* select/unselect all */
7030 msgno = any_lflagged(msgmap, MN_SLCT);
7031 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
7032 ret = 0;
7033 agg_select_all(state->mail_stream, msgmap, &diff,
7034 any_lflagged(msgmap, MN_SLCT) <= 0L);
7035 q_status_message4(SM_ORDER,0,2,
7036 "%s%s message%s %sselected",
7037 msgno ? "" : "All ", comatose(diff),
7038 plural(diff), msgno ? "UN" : "");
7039 return(ret);
7041 case 'n' : /* Select by Number */
7042 ret = 0;
7043 if(THRD_INDX())
7044 rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch);
7045 else
7046 rv = select_by_number(state->mail_stream, msgmap, &limitsrch);
7048 break;
7050 case 'd' : /* Select by Date */
7051 ret = 0;
7052 rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap),
7053 &limitsrch);
7054 break;
7056 case 't' : /* Text */
7057 ret = 0;
7058 rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap),
7059 &limitsrch);
7060 break;
7062 case 'z' : /* Size */
7063 ret = 0;
7064 rv = select_by_size(state->mail_stream, &limitsrch);
7065 break;
7067 case 's' : /* Status */
7068 ret = 0;
7069 rv = select_by_status(state->mail_stream, &limitsrch);
7070 break;
7072 case 'k' : /* Keyword */
7073 ret = 0;
7074 rv = select_by_keyword(state->mail_stream, &limitsrch);
7075 break;
7077 case 'r' : /* Rule */
7078 ret = 0;
7079 rv = select_by_rule(state->mail_stream, &limitsrch);
7080 break;
7082 case 'h' : /* Thread */
7083 ret = 0;
7084 rv = select_by_thread(state->mail_stream, msgmap, &limitsrch);
7085 break;
7087 default :
7088 q_status_message(SM_ORDER | SM_DING, 3, 3,
7089 "Unsupported Select option");
7090 return(ret);
7093 if(limitsrch)
7094 mail_free_searchset(&limitsrch);
7096 if(rv) /* bad return value.. */
7097 return(ret); /* error already displayed */
7099 if(narrow) /* make sure something was selected */
7100 for(i = 1L; i <= mn_get_total(msgmap); i++)
7101 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7102 && raw <= state->mail_stream->nmsgs
7103 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7104 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
7105 break;
7106 else
7107 mm_search_count--;
7110 diff = 0L;
7111 if(mm_search_count){
7113 * loop thru all the messages, adjusting local flag bits
7114 * based on their "searched" bit...
7116 for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++)
7117 if(narrow){
7118 /* turning OFF selectedness if the "searched" bit isn't lit. */
7119 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7120 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7121 && raw <= state->mail_stream->nmsgs
7122 && (mc = mail_elt(state->mail_stream, raw))
7123 && !mc->searched){
7124 diff--;
7125 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
7126 if(hidden)
7127 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
7129 /* adjust current message in case we unselect and hide it */
7130 else if(msgno < mn_get_cur(msgmap)
7131 && (!THRD_INDX()
7132 || !get_lflag(state->mail_stream, msgmap,
7133 i, MN_CHID)))
7134 msgno = i;
7137 else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7138 && raw <= state->mail_stream->nmsgs
7139 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7140 /* turn ON selectedness if "searched" bit is lit. */
7141 if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7142 diff++;
7143 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
7144 if(hidden)
7145 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
7149 /* if we're zoomed and the current message was unselected */
7150 if(narrow && msgno
7151 && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE))
7152 mn_reset_cur(msgmap, msgno);
7155 if(!diff){
7156 if(narrow)
7157 q_status_message4(SM_ORDER, 3, 3,
7158 "%s. %s message%s remain%s selected.",
7159 mm_search_count
7160 ? "No change resulted"
7161 : "No messages in intersection",
7162 comatose(old_tot), plural(old_tot),
7163 (old_tot == 1L) ? "s" : "");
7164 else if(old_tot)
7165 q_status_message(SM_ORDER, 3, 3,
7166 _("No change resulted. Matching messages already selected."));
7167 else
7168 q_status_message1(SM_ORDER | SM_DING, 3, 3,
7169 _("Select failed. No %smessages selected."),
7170 old_tot ? _("additional ") : "");
7172 else if(old_tot){
7173 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
7174 "Select matched %ld message%s. %s %smessage%s %sselected.",
7175 (diff > 0) ? diff : old_tot + diff,
7176 plural((diff > 0) ? diff : old_tot + diff),
7177 comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7178 (diff > 0) ? "total " : "",
7179 plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7180 (diff > 0) ? "" : "UN");
7181 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
7182 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
7184 else
7185 q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"),
7186 comatose(diff), plural(diff));
7188 return(ret);
7192 /*----------------------------------------------------------------------
7193 Toggle the state of the current message
7195 Args: state -- pointer pine's state variables
7196 msgmap -- message collection to operate on
7197 in_index -- in the message index view
7198 Returns: TRUE if current marked selected, FALSE otw
7199 ----*/
7201 select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
7203 long cur;
7204 int all_selected = 0;
7205 unsigned long was, tot, rawno;
7206 PINETHRD_S *thrd;
7208 cur = mn_get_cur(msgmap);
7210 if(THRD_INDX()){
7211 thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur));
7212 if(!thrd)
7213 return 0;
7215 was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT);
7216 tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE);
7217 if(was == tot)
7218 all_selected++;
7220 if(all_selected){
7221 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0);
7222 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7223 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1);
7225 * See if there's anything left to zoom on. If so,
7226 * pick an adjacent one for highlighting, else make
7227 * sure nothing is left hidden...
7229 if(any_lflagged(msgmap, MN_SLCT)){
7230 mn_inc_cur(state->mail_stream, msgmap,
7231 (in_index == View && THREADING()
7232 && sp_viewing_a_thread(state->mail_stream))
7233 ? MH_THISTHD
7234 : (in_index == View)
7235 ? MH_ANYTHD : MH_NONE);
7236 if(mn_get_cur(msgmap) == cur)
7237 mn_dec_cur(state->mail_stream, msgmap,
7238 (in_index == View && THREADING()
7239 && sp_viewing_a_thread(state->mail_stream))
7240 ? MH_THISTHD
7241 : (in_index == View)
7242 ? MH_ANYTHD : MH_NONE);
7244 else /* clear all hidden flags */
7245 (void) unzoom_index(state, state->mail_stream, msgmap);
7248 else
7249 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1);
7251 q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected",
7252 comatose(all_selected ? was : tot-was),
7253 plural(all_selected ? was : tot-was),
7254 all_selected ? "UN" : "");
7256 /* collapsed thread */
7257 else if(THREADING()
7258 && ((rawno = mn_m2raw(msgmap, cur)) != 0L)
7259 && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL)
7260 && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){
7262 * This doesn't work quite the same as the colon command works, but
7263 * it is arguably doing the correct thing. The difference is
7264 * that aggregate_select will zoom after selecting back where it
7265 * was called from, but selecting a thread with colon won't zoom.
7266 * Maybe it makes sense to zoom after a select but not after a colon
7267 * command even though they are very similar.
7269 thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state));
7271 else{
7272 if((all_selected =
7273 get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */
7274 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0);
7275 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7276 set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1);
7278 * See if there's anything left to zoom on. If so,
7279 * pick an adjacent one for highlighting, else make
7280 * sure nothing is left hidden...
7282 if(any_lflagged(msgmap, MN_SLCT)){
7283 mn_inc_cur(state->mail_stream, msgmap,
7284 (in_index == View && THREADING()
7285 && sp_viewing_a_thread(state->mail_stream))
7286 ? MH_THISTHD
7287 : (in_index == View)
7288 ? MH_ANYTHD : MH_NONE);
7289 if(mn_get_cur(msgmap) == cur)
7290 mn_dec_cur(state->mail_stream, msgmap,
7291 (in_index == View && THREADING()
7292 && sp_viewing_a_thread(state->mail_stream))
7293 ? MH_THISTHD
7294 : (in_index == View)
7295 ? MH_ANYTHD : MH_NONE);
7297 else /* clear all hidden flags */
7298 (void) unzoom_index(state, state->mail_stream, msgmap);
7301 else
7302 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1);
7304 q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected",
7305 long2string(cur), all_selected ? "UN" : "");
7309 return(!all_selected);
7313 /*----------------------------------------------------------------------
7314 Prompt the user for the command to perform on selected messages
7316 Args: state -- pointer pine's state variables
7317 msgmap -- message collection to operate on
7318 q_line -- line on display to write prompts
7319 Returns: 1 if the selected messages are suitably commanded,
7320 0 if the choice to pick the command was declined
7322 ----*/
7324 apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
7325 UCS preloadkeystroke, int flags, int q_line)
7327 int i = 8, /* number of static entries in sel_opts3 */
7328 rv = 0,
7329 cmd,
7330 we_cancel = 0,
7331 agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG;
7332 char prompt[80];
7335 * To do this "right", we really ought to have access to the keymenu
7336 * here and change the typed command into a real command by running
7337 * it through menu_command. Then the switch below would be against
7338 * results from menu_command. If we did that we'd also pass the
7339 * results of menu_command in as preloadkeystroke instead of passing
7340 * the keystroke itself. But we don't have the keymenu handy,
7341 * so we have to fake it. The only complication that we run into
7342 * is that KEY_DEL is an escape sequence so we change a typed
7343 * KEY_DEL esc seq into the letter D.
7346 if(!preloadkeystroke){
7347 if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
7348 sel_opts3[i].ch = '*';
7349 sel_opts3[i].rval = '*';
7350 sel_opts3[i].name = "*";
7351 sel_opts3[i++].label = N_("Flag");
7354 if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
7355 sel_opts3[i].ch = '|';
7356 sel_opts3[i].rval = '|';
7357 sel_opts3[i].name = "|";
7358 sel_opts3[i++].label = N_("Pipe");
7361 if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */
7362 sel_opts3[i].ch = 'b';
7363 sel_opts3[i].rval = 'b';
7364 sel_opts3[i].name = "B";
7365 sel_opts3[i++].label = N_("Bounce");
7368 if(flags & AC_FROM_THREAD){
7369 if(flags & (AC_COLL | AC_EXPN)){
7370 sel_opts3[i].ch = '/';
7371 sel_opts3[i].rval = '/';
7372 sel_opts3[i].name = "/";
7373 sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse")
7374 : N_("Expand");
7377 sel_opts3[i].ch = ';';
7378 sel_opts3[i].rval = ';';
7379 sel_opts3[i].name = ";";
7380 if(flags & AC_UNSEL)
7381 sel_opts3[i++].label = N_("UnSelect");
7382 else
7383 sel_opts3[i++].label = N_("Select");
7386 if(F_ON(F_ENABLE_PRYNT, state)){ /* this one is invisible */
7387 sel_opts3[i].ch = 'y';
7388 sel_opts3[i].rval = '%';
7389 sel_opts3[i].name = "";
7390 sel_opts3[i++].label = "";
7393 if(!is_imap_stream(stream) || LEVELUIDPLUS(stream)){ /* expunge selected messages */
7394 sel_opts3[i].ch = 'x';
7395 sel_opts3[i].rval = 'x';
7396 sel_opts3[i].name = "X";
7397 sel_opts3[i++].label = N_("Expunge");
7400 sel_opts3[i].ch = KEY_DEL; /* also invisible */
7401 sel_opts3[i].rval = 'd';
7402 sel_opts3[i].name = "";
7403 sel_opts3[i++].label = "";
7405 sel_opts3[i].ch = -1;
7407 snprintf(prompt, sizeof(prompt), "%s command : ",
7408 (flags & AC_FROM_THREAD) ? "THREAD" : "APPLY");
7409 prompt[sizeof(prompt)-1] = '\0';
7410 cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'c', NO_HELP,
7411 RB_SEQ_SENSITIVE);
7412 if(isupper(cmd))
7413 cmd = tolower(cmd);
7415 else{
7416 if(preloadkeystroke == KEY_DEL)
7417 cmd = 'd';
7418 else{
7419 if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke))
7420 cmd = tolower((int) preloadkeystroke);
7421 else
7422 cmd = (int) preloadkeystroke; /* shouldn't happen */
7426 switch(cmd){
7427 case 'd' : /* delete */
7428 we_cancel = busy_cue(NULL, NULL, 1);
7429 rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */
7430 if(we_cancel)
7431 cancel_busy_cue(0);
7432 break;
7434 case 'u' : /* undelete */
7435 we_cancel = busy_cue(NULL, NULL, 1);
7436 rv = cmd_undelete(state, msgmap, agg);
7437 if(we_cancel)
7438 cancel_busy_cue(0);
7439 break;
7441 case 'r' : /* reply */
7442 rv = cmd_reply(state, msgmap, agg);
7443 break;
7445 case 'f' : /* Forward */
7446 rv = cmd_forward(state, msgmap, agg);
7447 break;
7449 case '%' : /* print */
7450 rv = cmd_print(state, msgmap, agg, MsgIndx);
7451 break;
7453 case 't' : /* take address */
7454 rv = cmd_take_addr(state, msgmap, agg);
7455 break;
7457 case 's' : /* save */
7458 rv = cmd_save(state, stream, msgmap, agg, MsgIndx);
7459 break;
7461 case 'e' : /* export */
7462 rv = cmd_export(state, msgmap, q_line, agg);
7463 break;
7465 case '|' : /* pipe */
7466 rv = cmd_pipe(state, msgmap, agg);
7467 break;
7469 case '*' : /* flag */
7470 we_cancel = busy_cue(NULL, NULL, 1);
7471 rv = cmd_flag(state, msgmap, agg);
7472 if(we_cancel)
7473 cancel_busy_cue(0);
7474 break;
7476 case 'b' : /* bounce */
7477 rv = cmd_bounce(state, msgmap, agg);
7478 break;
7480 case '/' :
7481 collapse_or_expand(state, stream, msgmap,
7482 F_ON(F_SLASH_COLL_ENTIRE, ps_global)
7483 ? 0L
7484 : mn_get_cur(msgmap));
7485 break;
7487 case ':' :
7488 select_thread_stmp(state, stream, msgmap);
7489 break;
7491 case 'x' : /* Expunge */
7492 rv = cmd_expunge(state, stream, msgmap, agg);
7493 break;
7495 case 'c' : /* cancel */
7496 cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command"
7497 : "Apply command");
7498 break;
7500 case 'z' : /* default */
7501 q_status_message(SM_INFO, 0, 2,
7502 "Cancelled, there is no default command");
7503 break;
7505 default:
7506 break;
7509 return(rv);
7514 * Select by message number ranges.
7515 * Sets searched bits in mail_elts
7517 * Args limitsrch -- limit search to this searchset
7519 * Returns 0 on success.
7522 select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
7524 int r, end;
7525 long n1, n2, raw;
7526 char number1[16], number2[16], numbers[80], *p, *t;
7527 HelpType help;
7528 MESSAGECACHE *mc;
7530 numbers[0] = '\0';
7531 ps_global->mangled_footer = 1;
7532 help = NO_HELP;
7533 while(1){
7534 int flags = OE_APPEND_CURRENT;
7536 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7537 sizeof(numbers), _(select_num), NULL, help, &flags);
7538 if(r == 4)
7539 continue;
7541 if(r == 3){
7542 help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
7543 continue;
7546 for(t = p = numbers; *p ; p++) /* strip whitespace */
7547 if(!isspace((unsigned char)*p))
7548 *t++ = *p;
7550 *t = '\0';
7552 if(r == 1 || numbers[0] == '\0'){
7553 cmd_cancelled("Selection by number");
7554 return(1);
7556 else
7557 break;
7560 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7561 if((mc = mail_elt(stream, n1)) != NULL)
7562 mc->searched = 0; /* clear searched bits */
7564 for(p = numbers; *p ; p++){
7565 t = number1;
7566 while(*p && isdigit((unsigned char)*p))
7567 *t++ = *p++;
7569 *t = '\0';
7571 end = 0;
7572 if(number1[0] == '\0'){
7573 if(*p == '-'){
7574 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7575 _("Invalid number range, missing number before \"-\": %s"),
7576 numbers);
7577 return(1);
7579 else if(!strucmp("end", p)){
7580 end = 1;
7581 p += strlen("end");
7583 else{
7584 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7585 _("Invalid message number: %s"), numbers);
7586 return(1);
7590 if(end)
7591 n1 = mn_get_total(msgmap);
7592 else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
7593 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7594 _("\"%s\" out of message number range"),
7595 long2string(n1));
7596 return(1);
7599 t = number2;
7600 if(*p == '-'){
7601 while(*++p && isdigit((unsigned char)*p))
7602 *t++ = *p;
7604 *t = '\0';
7606 end = 0;
7607 if(number2[0] == '\0'){
7608 if(!strucmp("end", p)){
7609 end = 1;
7610 p += strlen("end");
7612 else{
7613 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7614 _("Invalid number range, missing number after \"-\": %s"),
7615 numbers);
7616 return(1);
7620 if(end)
7621 n2 = mn_get_total(msgmap);
7622 else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){
7623 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7624 _("\"%s\" out of message number range"),
7625 long2string(n2));
7626 return(1);
7629 if(n2 <= n1){
7630 char t[20];
7632 strncpy(t, long2string(n1), sizeof(t));
7633 t[sizeof(t)-1] = '\0';
7634 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7635 _("Invalid reverse message number range: %s-%s"),
7636 t, long2string(n2));
7637 return(1);
7640 for(;n1 <= n2; n1++){
7641 raw = mn_m2raw(msgmap, n1);
7642 if(raw > 0L
7643 && (!(limitsrch && *limitsrch)
7644 || in_searchset(*limitsrch, (unsigned long) raw)))
7645 mm_searched(stream, raw);
7648 else{
7649 raw = mn_m2raw(msgmap, n1);
7650 if(raw > 0L
7651 && (!(limitsrch && *limitsrch)
7652 || in_searchset(*limitsrch, (unsigned long) raw)))
7653 mm_searched(stream, raw);
7656 if(*p == '\0')
7657 break;
7660 return(0);
7665 * Select by thread number ranges.
7666 * Sets searched bits in mail_elts
7668 * Args limitsrch -- limit search to this searchset
7670 * Returns 0 on success.
7673 select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset)
7675 int r, end;
7676 long n1, n2;
7677 char number1[16], number2[16], numbers[80], *p, *t;
7678 HelpType help;
7679 PINETHRD_S *thrd = NULL;
7680 MESSAGECACHE *mc;
7682 numbers[0] = '\0';
7683 ps_global->mangled_footer = 1;
7684 help = NO_HELP;
7685 while(1){
7686 int flags = OE_APPEND_CURRENT;
7688 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7689 sizeof(numbers), _(select_num), NULL, help, &flags);
7690 if(r == 4)
7691 continue;
7693 if(r == 3){
7694 help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP;
7695 continue;
7698 for(t = p = numbers; *p ; p++) /* strip whitespace */
7699 if(!isspace((unsigned char)*p))
7700 *t++ = *p;
7702 *t = '\0';
7704 if(r == 1 || numbers[0] == '\0'){
7705 cmd_cancelled("Selection by number");
7706 return(1);
7708 else
7709 break;
7712 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7713 if((mc = mail_elt(stream, n1)) != NULL)
7714 mc->searched = 0; /* clear searched bits */
7716 for(p = numbers; *p ; p++){
7717 t = number1;
7718 while(*p && isdigit((unsigned char)*p))
7719 *t++ = *p++;
7721 *t = '\0';
7723 end = 0;
7724 if(number1[0] == '\0'){
7725 if(*p == '-'){
7726 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7727 _("Invalid number range, missing number before \"-\": %s"),
7728 numbers);
7729 return(1);
7731 else if(!strucmp("end", p)){
7732 end = 1;
7733 p += strlen("end");
7735 else{
7736 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7737 _("Invalid thread number: %s"), numbers);
7738 return(1);
7742 if(end)
7743 n1 = msgmap->max_thrdno;
7744 else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){
7745 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7746 _("\"%s\" out of thread number range"),
7747 long2string(n1));
7748 return(1);
7751 t = number2;
7752 if(*p == '-'){
7754 while(*++p && isdigit((unsigned char)*p))
7755 *t++ = *p;
7757 *t = '\0';
7759 end = 0;
7760 if(number2[0] == '\0'){
7761 if(!strucmp("end", p)){
7762 end = 1;
7763 p += strlen("end");
7765 else{
7766 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7767 _("Invalid number range, missing number after \"-\": %s"),
7768 numbers);
7769 return(1);
7773 if(end)
7774 n2 = msgmap->max_thrdno;
7775 else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){
7776 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7777 _("\"%s\" out of thread number range"),
7778 long2string(n2));
7779 return(1);
7782 if(n2 <= n1){
7783 char t[20];
7785 strncpy(t, long2string(n1), sizeof(t));
7786 t[sizeof(t)-1] = '\0';
7787 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7788 _("Invalid reverse message number range: %s-%s"),
7789 t, long2string(n2));
7790 return(1);
7793 for(;n1 <= n2; n1++){
7794 thrd = find_thread_by_number(stream, msgmap, n1, thrd);
7796 if(thrd)
7797 set_search_bit_for_thread(stream, thrd, msgset);
7800 else{
7801 thrd = find_thread_by_number(stream, msgmap, n1, NULL);
7803 if(thrd)
7804 set_search_bit_for_thread(stream, thrd, msgset);
7807 if(*p == '\0')
7808 break;
7811 return(0);
7816 * Select by message dates.
7817 * Sets searched bits in mail_elts
7819 * Args limitsrch -- limit search to this searchset
7821 * Returns 0 on success.
7824 select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
7826 int r, we_cancel = 0, when = 0;
7827 char date[100], defdate[100], prompt[128];
7828 time_t seldate = time(0);
7829 struct tm *seldate_tm;
7830 SEARCHPGM *pgm;
7831 HelpType help;
7832 static struct _tense {
7833 char *preamble,
7834 *range,
7835 *scope;
7836 } tense[] = {
7837 {"were ", "SENT SINCE", " (inclusive)"},
7838 {"were ", "SENT BEFORE", " (exclusive)"},
7839 {"were ", "SENT ON", "" },
7840 {"", "ARRIVED SINCE", " (inclusive)"},
7841 {"", "ARRIVED BEFORE", " (exclusive)"},
7842 {"", "ARRIVED ON", "" }
7845 date[0] = '\0';
7846 ps_global->mangled_footer = 1;
7847 help = NO_HELP;
7850 * If talking to an old server, default to SINCE instead of
7851 * SENTSINCE, which was added later.
7853 if(is_imap_stream(stream) && !modern_imap_stream(stream))
7854 when = 3;
7856 while(1){
7857 int flags = OE_APPEND_CURRENT;
7859 seldate_tm = localtime(&seldate);
7860 snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday,
7861 month_abbrev(seldate_tm->tm_mon + 1),
7862 seldate_tm->tm_year + 1900);
7863 defdate[sizeof(defdate)-1] = '\0';
7864 snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ",
7865 tense[when].preamble, tense[when].range,
7866 tense[when].scope, defdate);
7867 prompt[sizeof(prompt)-1] = '\0';
7868 r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date),
7869 prompt, sel_date_opt, help, &flags);
7870 switch (r){
7871 case 1 :
7872 cmd_cancelled("Selection by date");
7873 return(1);
7875 case 3 :
7876 help = (help == NO_HELP) ? h_select_date : NO_HELP;
7877 continue;
7879 case 4 :
7880 continue;
7882 case 11 :
7884 MESSAGECACHE *mc;
7885 long rawno;
7887 if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L
7888 && rawno <= stream->nmsgs
7889 && (mc = mail_elt(stream, rawno))){
7891 /* cache not filled in yet? */
7892 if(mc->day == 0){
7893 char seq[20];
7895 if(stream->dtb && stream->dtb->flags & DR_NEWS){
7896 strncpy(seq,
7897 ulong2string(mail_uid(stream, rawno)),
7898 sizeof(seq));
7899 seq[sizeof(seq)-1] = '\0';
7900 mail_fetch_overview(stream, seq, NULL);
7902 else{
7903 strncpy(seq, long2string(rawno),
7904 sizeof(seq));
7905 seq[sizeof(seq)-1] = '\0';
7906 mail_fetch_fast(stream, seq, 0L);
7910 /* mail_date returns fixed field width date */
7911 mail_date(date, mc);
7912 date[11] = '\0';
7916 continue;
7918 case 12 : /* set default to PREVIOUS day */
7919 seldate -= 86400;
7920 continue;
7922 case 13 : /* set default to NEXT day */
7923 seldate += 86400;
7924 continue;
7926 case 14 :
7927 when = (when+1) % (sizeof(tense) / sizeof(struct _tense));
7928 continue;
7930 default:
7931 break;
7934 removing_leading_white_space(date);
7935 removing_trailing_white_space(date);
7936 if(!*date){
7937 strncpy(date, defdate, sizeof(date));
7938 date[sizeof(date)-1] = '\0';
7941 break;
7944 if((pgm = mail_newsearchpgm()) != NULL){
7945 MESSAGECACHE elt;
7946 short converted_date;
7948 if(mail_parse_date(&elt, (unsigned char *) date)){
7949 converted_date = mail_shortdate(elt.year, elt.month, elt.day);
7951 switch(when){
7952 case 0:
7953 pgm->sentsince = converted_date;
7954 break;
7955 case 1:
7956 pgm->sentbefore = converted_date;
7957 break;
7958 case 2:
7959 pgm->senton = converted_date;
7960 break;
7961 case 3:
7962 pgm->since = converted_date;
7963 break;
7964 case 4:
7965 pgm->before = converted_date;
7966 break;
7967 case 5:
7968 pgm->on = converted_date;
7969 break;
7972 pgm->msgno = (limitsrch ? *limitsrch : NULL);
7974 if(ps_global && ps_global->ttyo){
7975 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
7976 ps_global->mangled_footer = 1;
7979 we_cancel = busy_cue(_("Selecting"), NULL, 1);
7981 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
7983 if(we_cancel)
7984 cancel_busy_cue(0);
7986 /* we know this was freed in mail_search, let caller know */
7987 if(limitsrch)
7988 *limitsrch = NULL;
7990 else{
7991 mail_free_searchpgm(&pgm);
7992 q_status_message1(SM_ORDER, 3, 3,
7993 _("Invalid date entered: %s"), date);
7994 return(1);
7998 return(0);
8003 * Select by searching in message headers or body.
8004 * Sets searched bits in mail_elts
8006 * Args limitsrch -- limit search to this searchset
8008 * Returns 0 on success.
8011 select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8013 int r, ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
8014 int not = 0, me = 0;
8015 char sstring[80], savedsstring[80], tmp[128];
8016 char *p, *sval = NULL;
8017 char buftmp[MAILTMPLEN], namehdr[80];
8018 ESCKEY_S ekey[8];
8019 ENVELOPE *env = NULL;
8020 HelpType help;
8021 unsigned flagsforhist = 0;
8022 static HISTORY_S *history = NULL;
8023 static char *recip = "RECIPIENTS";
8024 static char *partic = "PARTICIPANTS";
8025 static char *match_me = N_("[Match_My_Addresses]");
8026 static char *dont_match_me = N_("[Don't_Match_My_Addresses]");
8028 ps_global->mangled_footer = 1;
8029 savedsstring[0] = '\0';
8030 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8032 while(1){
8033 type = radio_buttons(not ? _(sel_text_not) : _(sel_text),
8034 -FOOTER_ROWS(ps_global), sel_text_opt,
8035 's', 'x', NO_HELP, RB_NORM|RB_RET_HELP);
8037 if(type == '!')
8038 not = !not;
8039 else if(type == 3){
8040 helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS",
8041 HLPD_SIMPLE);
8042 ps_global->mangled_screen = 1;
8044 else
8045 break;
8049 * prepare some friendly defaults...
8051 switch(type){
8052 case 't' : /* address fields, offer To or From */
8053 case 'f' :
8054 case 'c' :
8055 case 'r' :
8056 case 'p' :
8057 sval = (type == 't') ? "TO" :
8058 (type == 'f') ? "FROM" :
8059 (type == 'c') ? "CC" :
8060 (type == 'r') ? recip : partic;
8061 ekey[ekeyi].ch = ctrl('T');
8062 ekey[ekeyi].name = "^T";
8063 ekey[ekeyi].rval = 10;
8064 /* TRANSLATORS: use Current To Address */
8065 ekey[ekeyi++].label = N_("Cur To");
8066 ekey[ekeyi].ch = ctrl('R');
8067 ekey[ekeyi].name = "^R";
8068 ekey[ekeyi].rval = 11;
8069 /* TRANSLATORS: use Current From Address */
8070 ekey[ekeyi++].label = N_("Cur From");
8071 ekey[ekeyi].ch = ctrl('W');
8072 ekey[ekeyi].name = "^W";
8073 ekey[ekeyi].rval = 12;
8074 /* TRANSLATORS: use Current Cc Address */
8075 ekey[ekeyi++].label = N_("Cur Cc");
8076 ekey[ekeyi].ch = ctrl('Y');
8077 ekey[ekeyi].name = "^Y";
8078 ekey[ekeyi].rval = 13;
8079 /* TRANSLATORS: Match Me means match my address */
8080 ekey[ekeyi++].label = N_("Match Me");
8081 ekey[ekeyi].ch = 0;
8082 ekey[ekeyi].name = "";
8083 ekey[ekeyi].rval = 0;
8084 ekey[ekeyi++].label = "";
8085 break;
8087 case 's' :
8088 sval = "SUBJECT";
8089 ekey[ekeyi].ch = ctrl('X');
8090 ekey[ekeyi].name = "^X";
8091 ekey[ekeyi].rval = 14;
8092 /* TRANSLATORS: use Current Subject */
8093 ekey[ekeyi++].label = N_("Cur Subject");
8094 break;
8096 case 'a' :
8097 sval = "TEXT";
8098 break;
8100 case 'b' :
8101 sval = "BODYTEXT";
8102 break;
8104 case 'h' :
8105 strncpy(tmp, "Name of HEADER to match : ", sizeof(tmp)-1);
8106 tmp[sizeof(tmp)-1] = '\0';
8107 flags = OE_APPEND_CURRENT;
8108 namehdr[0] = '\0';
8109 r = 'x';
8110 while (r == 'x'){
8111 int done = 0;
8113 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8114 sizeof(namehdr), tmp, ekey, NO_HELP, &flags);
8115 if (r == 1){
8116 cmd_cancelled("Selection by text");
8117 return(1);
8119 removing_leading_white_space(namehdr);
8120 while(!done){
8121 while ((namehdr[0] != '\0') && /* remove trailing ":" */
8122 (namehdr[strlen(namehdr) - 1] == ':'))
8123 namehdr[strlen(namehdr) - 1] = '\0';
8124 if ((namehdr[0] != '\0')
8125 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8126 removing_trailing_white_space(namehdr);
8127 else
8128 done++;
8130 if (strchr(namehdr,' ') || strchr(namehdr,'\t') ||
8131 strchr(namehdr,':'))
8132 namehdr[0] = '\0';
8133 if (namehdr[0] == '\0')
8134 r = 'x';
8136 sval = namehdr;
8137 break;
8139 case 'x':
8140 break;
8142 default:
8143 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
8144 return(1);
8147 ekey[ekeyi].ch = KEY_UP;
8148 ekey[ekeyi].rval = 30;
8149 ekey[ekeyi].name = "";
8150 ku = ekeyi;
8151 ekey[ekeyi++].label = "";
8153 ekey[ekeyi].ch = KEY_DOWN;
8154 ekey[ekeyi].rval = 31;
8155 ekey[ekeyi].name = "";
8156 ekey[ekeyi++].label = "";
8158 ekey[ekeyi].ch = -1;
8160 if(type != 'x'){
8162 init_hist(&history, HISTSIZE);
8164 if(ekey[0].ch > -1 && msgno > 0L
8165 && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),
8166 NULL)))
8167 ekey[0].ch = -1;
8169 sstring[0] = '\0';
8170 help = NO_HELP;
8171 r = type;
8172 while(r != 'x'){
8173 if(not)
8174 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8175 snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval);
8176 else
8177 snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval);
8179 if(items_in_hist(history) > 0){
8180 ekey[ku].name = HISTORY_UP_KEYNAME;
8181 ekey[ku].label = HISTORY_KEYLABEL;
8182 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
8183 ekey[ku+1].label = HISTORY_KEYLABEL;
8185 else{
8186 ekey[ku].name = "";
8187 ekey[ku].label = "";
8188 ekey[ku+1].name = "";
8189 ekey[ku+1].label = "";
8192 flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
8193 r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
8194 79, tmp, ekey, help, &flags);
8196 if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me)))))
8197 me = 0;
8199 switch(r){
8200 case 3 :
8201 help = (help == NO_HELP)
8202 ? (not
8203 ? ((type == 'f') ? h_select_txt_not_from
8204 : (type == 't') ? h_select_txt_not_to
8205 : (type == 'c') ? h_select_txt_not_cc
8206 : (type == 's') ? h_select_txt_not_subj
8207 : (type == 'a') ? h_select_txt_not_all
8208 : (type == 'r') ? h_select_txt_not_recip
8209 : (type == 'p') ? h_select_txt_not_partic
8210 : (type == 'b') ? h_select_txt_not_body
8211 : NO_HELP)
8212 : ((type == 'f') ? h_select_txt_from
8213 : (type == 't') ? h_select_txt_to
8214 : (type == 'c') ? h_select_txt_cc
8215 : (type == 's') ? h_select_txt_subj
8216 : (type == 'a') ? h_select_txt_all
8217 : (type == 'r') ? h_select_txt_recip
8218 : (type == 'p') ? h_select_txt_partic
8219 : (type == 'b') ? h_select_txt_body
8220 : NO_HELP))
8221 : NO_HELP;
8223 case 4 :
8224 continue;
8226 case 10 : /* To: default */
8227 if(env && env->to && env->to->mailbox){
8228 snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox,
8229 env->to->host ? "@" : "",
8230 env->to->host ? env->to->host : "");
8231 sstring[sizeof(sstring)-1] = '\0';
8233 continue;
8235 case 11 : /* From: default */
8236 if(env && env->from && env->from->mailbox){
8237 snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox,
8238 env->from->host ? "@" : "",
8239 env->from->host ? env->from->host : "");
8240 sstring[sizeof(sstring)-1] = '\0';
8242 continue;
8244 case 12 : /* Cc: default */
8245 if(env && env->cc && env->cc->mailbox){
8246 snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox,
8247 env->cc->host ? "@" : "",
8248 env->cc->host ? env->cc->host : "");
8249 sstring[sizeof(sstring)-1] = '\0';
8251 continue;
8253 case 13 : /* Match my addresses */
8254 me++;
8255 snprintf(sstring, sizeof(sstring), "%s", not ? _(dont_match_me) : _(match_me));
8256 continue;
8258 case 14 : /* Subject: default */
8259 if(env && env->subject && env->subject[0]){
8260 char *q = NULL;
8262 snprintf(buftmp, sizeof(buftmp), "%.75s", env->subject);
8263 buftmp[sizeof(buftmp)-1] = '\0';
8264 q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
8265 SIZEOF_20KBUF, buftmp);
8266 if(q != env->subject){
8267 snprintf(savedsstring, sizeof(savedsstring), "%.70s", q);
8268 savedsstring[sizeof(savedsstring)-1] = '\0';
8271 snprintf(sstring, sizeof(sstring), "%s", q);
8272 sstring[sizeof(sstring)-1] = '\0';
8275 continue;
8277 case 30 :
8278 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8279 if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){
8280 strncpy(sstring, p, sizeof(sstring));
8281 sstring[sizeof(sstring)-1] = '\0';
8282 if(history->hist[history->curindex]){
8283 flagsforhist = history->hist[history->curindex]->flags;
8284 not = (flagsforhist & 0x1) ? 1 : 0;
8285 me = (flagsforhist & 0x2) ? 1 : 0;
8288 else
8289 Writechar(BELL, 0);
8291 continue;
8293 case 31 :
8294 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8295 if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){
8296 strncpy(sstring, p, sizeof(sstring));
8297 sstring[sizeof(sstring)-1] = '\0';
8298 if(history->hist[history->curindex]){
8299 flagsforhist = history->hist[history->curindex]->flags;
8300 not = (flagsforhist & 0x1) ? 1 : 0;
8301 me = (flagsforhist & 0x2) ? 1 : 0;
8304 else
8305 Writechar(BELL, 0);
8307 continue;
8309 default :
8310 break;
8313 if(r == 1 || sstring[0] == '\0')
8314 r = 'x';
8316 break;
8320 if(type == 'x' || r == 'x'){
8321 cmd_cancelled("Selection by text");
8322 return(1);
8325 if(ps_global && ps_global->ttyo){
8326 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8327 ps_global->mangled_footer = 1;
8330 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8332 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8333 save_hist(history, sstring, flagsforhist, NULL);
8335 rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch);
8336 if(we_cancel)
8337 cancel_busy_cue(0);
8339 return(rv);
8344 * Select by message size.
8345 * Sets searched bits in mail_elts
8347 * Args limitsrch -- limit search to this searchset
8349 * Returns 0 on success.
8352 select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch)
8354 int r, large = 1, we_cancel = 0;
8355 unsigned long n, mult = 1L, numerator = 0L, divisor = 1L;
8356 char size[16], numbers[80], *p, *t;
8357 HelpType help;
8358 SEARCHPGM *pgm;
8359 long flags = (SE_NOPREFETCH | SE_FREE);
8361 numbers[0] = '\0';
8362 ps_global->mangled_footer = 1;
8364 help = NO_HELP;
8365 while(1){
8366 int flgs = OE_APPEND_CURRENT;
8368 sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger;
8370 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
8371 sizeof(numbers), large ? _(select_size_larger_msg)
8372 : _(select_size_smaller_msg),
8373 sel_size_opt, help, &flgs);
8374 if(r == 4)
8375 continue;
8377 if(r == 14){
8378 large = 1 - large;
8379 continue;
8382 if(r == 3){
8383 help = (help == NO_HELP) ? (large ? h_select_by_larger_size
8384 : h_select_by_smaller_size)
8385 : NO_HELP;
8386 continue;
8389 for(t = p = numbers; *p ; p++) /* strip whitespace */
8390 if(!isspace((unsigned char)*p))
8391 *t++ = *p;
8393 *t = '\0';
8395 if(r == 1 || numbers[0] == '\0'){
8396 cmd_cancelled("Selection by size");
8397 return(1);
8399 else
8400 break;
8403 if(numbers[0] == '-'){
8404 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8405 _("Invalid size entered: %s"), numbers);
8406 return(1);
8409 t = size;
8410 p = numbers;
8412 while(*p && isdigit((unsigned char)*p))
8413 *t++ = *p++;
8415 *t = '\0';
8417 if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){
8418 size[0] = '0';
8419 size[1] = '\0';
8422 if(size[0] == '\0'){
8423 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8424 _("Invalid size entered: %s"), numbers);
8425 return(1);
8428 n = strtoul(size, (char **)NULL, 10);
8430 size[0] = '\0';
8431 if(*p == '.'){
8433 * We probably ought to just use atof() to convert 1.1 into a
8434 * double, but since we haven't used atof() anywhere else I'm
8435 * reluctant to use it because of portability concerns.
8437 p++;
8438 t = size;
8439 while(*p && isdigit((unsigned char)*p)){
8440 *t++ = *p++;
8441 divisor *= 10;
8444 *t = '\0';
8446 if(size[0])
8447 numerator = strtoul(size, (char **)NULL, 10);
8450 switch(*p){
8451 case 'g':
8452 case 'G':
8453 mult *= 1000;
8454 /* fall through */
8456 case 'm':
8457 case 'M':
8458 mult *= 1000;
8459 /* fall through */
8461 case 'k':
8462 case 'K':
8463 mult *= 1000;
8464 break;
8467 n = n * mult + (numerator * mult) / divisor;
8469 pgm = mail_newsearchpgm();
8470 if(large)
8471 pgm->larger = n;
8472 else
8473 pgm->smaller = n;
8475 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8476 flags |= SE_NOSERVER;
8478 if(ps_global && ps_global->ttyo){
8479 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8480 ps_global->mangled_footer = 1;
8483 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8485 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8486 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8487 /* we know this was freed in mail_search, let caller know */
8488 if(limitsrch)
8489 *limitsrch = NULL;
8491 if(we_cancel)
8492 cancel_busy_cue(0);
8494 return(0);
8499 * visible_searchset -- return c-client search set unEXLDed
8500 * sequence numbers
8502 SEARCHSET *
8503 visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap)
8505 long n, run;
8506 SEARCHSET *full_set = NULL, **set;
8509 * If we're talking to anything other than a server older than
8510 * imap 4rev1, build a searchset otherwise it'll choke.
8512 if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
8513 if(any_lflagged(msgmap, MN_EXLD)){
8514 for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
8515 if(get_lflag(stream, NULL, n, MN_EXLD)){
8516 if(run){ /* previous NOT excluded? */
8517 if(run > 1L)
8518 (*set)->last = n - 1L;
8520 set = &(*set)->next;
8521 run = 0L;
8524 else if(run++){ /* next in run */
8525 (*set)->last = n;
8527 else{ /* start of run */
8528 *set = mail_newsearchset();
8529 (*set)->first = n;
8532 else{
8533 full_set = mail_newsearchset();
8534 full_set->first = 1L;
8535 full_set->last = stream->nmsgs;
8539 return(full_set);
8544 * Select by message status bits.
8545 * Sets searched bits in mail_elts
8547 * Args limitsrch -- limit search to this searchset
8549 * Returns 0 on success.
8552 select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch)
8554 int s, not = 0, we_cancel = 0, rv;
8556 while(1){
8557 s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag),
8558 -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x',
8559 NO_HELP, RB_NORM|RB_RET_HELP);
8561 if(s == 'x'){
8562 cmd_cancelled("Selection by status");
8563 return(1);
8565 else if(s == 3){
8566 helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"),
8567 HLPD_SIMPLE);
8568 ps_global->mangled_screen = 1;
8570 else if(s == '!')
8571 not = !not;
8572 else
8573 break;
8576 if(ps_global && ps_global->ttyo){
8577 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8578 ps_global->mangled_footer = 1;
8581 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8582 rv = agg_flag_select(stream, not, s, limitsrch);
8583 if(we_cancel)
8584 cancel_busy_cue(0);
8586 return(rv);
8591 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8592 * Sets searched bits in mail_elts
8594 * Args limitsrch -- limit search to this searchset
8596 * Returns 0 on success.
8599 select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
8601 char rulenick[1000], *nick;
8602 PATGRP_S *patgrp;
8603 int r, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
8604 | ROLE_DO_INCOLS
8605 | ROLE_DO_ROLES
8606 | ROLE_DO_SCORES
8607 | ROLE_DO_OTHER
8608 | ROLE_DO_FILTER;
8610 rulenick[0] = '\0';
8611 ps_global->mangled_footer = 1;
8614 int oe_flags;
8616 oe_flags = OE_APPEND_CURRENT;
8617 r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0,
8618 sizeof(rulenick),
8619 not ? _("Rule to NOT match: ")
8620 : _("Rule to match: "),
8621 sel_key_opt, NO_HELP, &oe_flags);
8623 if(r == 14){
8624 /* select rulenick from a list */
8625 if((nick=choose_a_rule(rflags)) != NULL){
8626 strncpy(rulenick, nick, sizeof(rulenick)-1);
8627 rulenick[sizeof(rulenick)-1] = '\0';
8628 fs_give((void **) &nick);
8630 else
8631 r = 4;
8633 else if(r == '!')
8634 not = !not;
8636 if(r == 3){
8637 helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE);
8638 ps_global->mangled_screen = 1;
8640 else if(r == 1){
8641 cmd_cancelled("Selection by Rule");
8642 return(1);
8645 removing_leading_and_trailing_white_space(rulenick);
8647 }while(r == 3 || r == 4 || r == '!');
8651 * The approach of requiring a nickname instead of just allowing the
8652 * user to select from the list of rules has the drawback that a rule
8653 * may not have a nickname, or there may be more than one rule with
8654 * the same nickname. However, it has the benefit of allowing the user
8655 * to type in the nickname and, most importantly, allows us to set
8656 * up the ! (not). We could incorporate the ! into the selection
8657 * screen, but this is easier and also allows the typing of nicks.
8658 * User can just set up nicknames if they want to use this feature.
8660 patgrp = nick_to_patgrp(rulenick, rflags);
8662 if(patgrp){
8663 if(ps_global && ps_global->ttyo){
8664 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8665 ps_global->mangled_footer = 1;
8668 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8669 match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL,
8670 get_msg_score,
8671 (not ? MP_NOT : 0) | SE_NOPREFETCH);
8672 free_patgrp(&patgrp);
8673 if(we_cancel)
8674 cancel_busy_cue(0);
8677 if(limitsrch && *limitsrch){
8678 mail_free_searchset(limitsrch);
8679 *limitsrch = NULL;
8682 return(0);
8687 * Allow user to choose a rule from their list of rules.
8689 * Returns an allocated rule nickname on success, NULL otherwise.
8691 char *
8692 choose_a_rule(int rflags)
8694 char *choice = NULL;
8695 char **rule_list, **lp;
8696 int cnt = 0;
8697 PAT_S *pat;
8698 PAT_STATE pstate;
8700 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){
8701 q_status_message(SM_ORDER, 3, 3,
8702 _("No rules available. Use Setup/Rules to add some."));
8703 return(choice);
8707 * Build a list of rules to choose from.
8710 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8711 cnt++;
8713 if(cnt <= 0){
8714 q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
8715 return(choice);
8718 lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
8719 memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
8721 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8722 *lp++ = cpystr((pat->patgrp && pat->patgrp->nick)
8723 ? pat->patgrp->nick : "?");
8725 /* TRANSLATORS: SELECT A RULE is a screen title
8726 TRANSLATORS: Print something1 using something2.
8727 "rules" is something1 */
8728 choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"),
8729 _("rules"), h_select_rule_screen,
8730 _("HELP FOR SELECTING A RULE NICKNAME"), NULL);
8732 if(!choice)
8733 q_status_message(SM_ORDER, 1, 4, "No choice");
8735 free_list_array(&rule_list);
8737 return(choice);
8742 * Select by current thread.
8743 * Sets searched bits in mail_elts for this entire thread
8745 * Args limitsrch -- limit search to this searchset
8747 * Returns 0 on success.
8750 select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
8752 long n;
8753 PINETHRD_S *thrd = NULL;
8754 int ret = 1;
8755 MESSAGECACHE *mc;
8757 if(!stream)
8758 return(ret);
8760 for(n = 1L; n <= stream->nmsgs; n++)
8761 if((mc = mail_elt(stream, n)) != NULL)
8762 mc->searched = 0; /* clear searched bits */
8764 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
8765 if(thrd && thrd->top && thrd->top != thrd->rawno)
8766 thrd = fetch_thread(stream, thrd->top);
8769 * This doesn't unselect if the thread is already selected
8770 * (like select current does), it always selects.
8771 * There is no way to select ! this thread.
8773 if(thrd){
8774 set_search_bit_for_thread(stream, thrd, limitsrch);
8775 ret = 0;
8778 return(ret);
8783 * Select by message keywords.
8784 * Sets searched bits in mail_elts
8786 * Args limitsrch -- limit search to this searchset
8788 * Returns 0 on success.
8791 select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
8793 int r, not = 0, we_cancel = 0;
8794 char keyword[MAXUSERFLAG+1], *kword;
8795 char *error = NULL, *p, *prompt;
8796 HelpType help;
8797 SEARCHPGM *pgm;
8799 keyword[0] = '\0';
8800 ps_global->mangled_footer = 1;
8802 help = NO_HELP;
8804 int oe_flags;
8806 if(error){
8807 q_status_message(SM_ORDER, 3, 4, error);
8808 fs_give((void **) &error);
8811 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
8812 if(not)
8813 prompt = _("Keyword (or keyword initial) to NOT match: ");
8814 else
8815 prompt = _("Keyword (or keyword initial) to match: ");
8817 else{
8818 if(not)
8819 prompt = _("Keyword to NOT match: ");
8820 else
8821 prompt = _("Keyword to match: ");
8824 oe_flags = OE_APPEND_CURRENT;
8825 r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0,
8826 sizeof(keyword),
8827 prompt, sel_key_opt, help, &oe_flags);
8829 if(r == 14){
8830 /* select keyword from a list */
8831 if((kword=choose_a_keyword()) != NULL){
8832 strncpy(keyword, kword, sizeof(keyword)-1);
8833 keyword[sizeof(keyword)-1] = '\0';
8834 fs_give((void **) &kword);
8836 else
8837 r = 4;
8839 else if(r == '!')
8840 not = !not;
8842 if(r == 3)
8843 help = help == NO_HELP ? h_select_keyword : NO_HELP;
8844 else if(r == 1){
8845 cmd_cancelled("Selection by keyword");
8846 return(1);
8849 removing_leading_and_trailing_white_space(keyword);
8851 }while(r == 3 || r == 4 || r == '!' || keyword_check(keyword, &error));
8854 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
8855 p = initial_to_keyword(keyword);
8856 if(p != keyword){
8857 strncpy(keyword, p, sizeof(keyword)-1);
8858 keyword[sizeof(keyword)-1] = '\0';
8863 * We want to check the keyword, not the nickname of the keyword,
8864 * so convert it to the keyword if necessary.
8866 p = nick_to_keyword(keyword);
8867 if(p != keyword){
8868 strncpy(keyword, p, sizeof(keyword)-1);
8869 keyword[sizeof(keyword)-1] = '\0';
8872 pgm = mail_newsearchpgm();
8873 if(not){
8874 pgm->unkeyword = mail_newstringlist();
8875 pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword);
8876 pgm->unkeyword->text.size = strlen(keyword);
8878 else{
8879 pgm->keyword = mail_newstringlist();
8880 pgm->keyword->text.data = (unsigned char *) cpystr(keyword);
8881 pgm->keyword->text.size = strlen(keyword);
8884 if(ps_global && ps_global->ttyo){
8885 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8886 ps_global->mangled_footer = 1;
8889 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8891 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8892 pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE);
8893 /* we know this was freed in mail_search, let caller know */
8894 if(limitsrch)
8895 *limitsrch = NULL;
8897 if(we_cancel)
8898 cancel_busy_cue(0);
8900 return(0);
8905 * Allow user to choose a keyword from their list of keywords.
8907 * Returns an allocated keyword on success, NULL otherwise.
8909 char *
8910 choose_a_keyword(void)
8912 char *choice = NULL;
8913 char **keyword_list, **lp;
8914 int cnt;
8915 KEYWORD_S *kw;
8918 * Build a list of keywords to choose from.
8921 for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next)
8922 cnt++;
8924 if(cnt <= 0){
8925 q_status_message(SM_ORDER, 3, 4,
8926 _("No keywords defined, use \"keywords\" option in Setup/Config"));
8927 return(choice);
8930 lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list));
8931 memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list));
8933 for(kw = ps_global->keywords; kw; kw = kw->next)
8934 *lp++ = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
8936 /* TRANSLATORS: SELECT A KEYWORD is a screen title
8937 TRANSLATORS: Print something1 using something2.
8938 "keywords" is something1 */
8939 choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"),
8940 _("keywords"), h_select_keyword_screen,
8941 _("HELP FOR SELECTING A KEYWORD"), NULL);
8943 if(!choice)
8944 q_status_message(SM_ORDER, 1, 4, "No choice");
8946 free_list_array(&keyword_list);
8948 return(choice);
8953 * Allow user to choose a list of keywords from their list of keywords.
8955 * Returns allocated list.
8957 char **
8958 choose_list_of_keywords(void)
8960 LIST_SEL_S *listhead, *ls, *p;
8961 char **ret = NULL;
8962 int cnt, i;
8963 KEYWORD_S *kw;
8966 * Build a list of keywords to choose from.
8969 p = listhead = NULL;
8970 for(kw = ps_global->keywords; kw; kw = kw->next){
8972 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
8973 memset(ls, 0, sizeof(*ls));
8974 ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
8976 if(p){
8977 p->next = ls;
8978 p = p->next;
8980 else
8981 listhead = p = ls;
8984 if(!listhead)
8985 return(ret);
8987 /* TRANSLATORS: SELECT KEYWORDS is a screen title
8988 Print something1 using something2.
8989 "keywords" is something1 */
8990 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
8991 _("SELECT KEYWORDS"), _("keywords"),
8992 h_select_multkeyword_screen,
8993 _("HELP FOR SELECTING KEYWORDS"), NULL)){
8994 for(cnt = 0, p = listhead; p; p = p->next)
8995 if(p->selected)
8996 cnt++;
8998 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
8999 memset(ret, 0, (cnt+1) * sizeof(*ret));
9000 for(i = 0, p = listhead; p; p = p->next)
9001 if(p->selected)
9002 ret[i++] = cpystr(p->item ? p->item : "");
9005 free_list_sel(&listhead);
9007 return(ret);
9012 * Allow user to choose a charset
9014 * Returns an allocated charset on success, NULL otherwise.
9016 char *
9017 choose_a_charset(int which_charsets)
9019 char *choice = NULL;
9020 char **charset_list, **lp;
9021 const CHARSET *cs;
9022 int cnt;
9025 * Build a list of charsets to choose from.
9028 for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){
9029 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9030 && ((which_charsets & CAC_ALL)
9031 || (which_charsets & CAC_POSTING
9032 && cs->flags & CF_POSTING)
9033 || (which_charsets & CAC_DISPLAY
9034 && cs->type != CT_2022
9035 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9036 cnt++;
9039 if(cnt <= 0){
9040 q_status_message(SM_ORDER, 3, 4,
9041 _("No charsets found? Enter charset manually."));
9042 return(choice);
9045 lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list));
9046 memset(charset_list, 0, (cnt+1) * sizeof(*charset_list));
9048 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9049 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9050 && ((which_charsets & CAC_ALL)
9051 || (which_charsets & CAC_POSTING
9052 && cs->flags & CF_POSTING)
9053 || (which_charsets & CAC_DISPLAY
9054 && cs->type != CT_2022
9055 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9056 *lp++ = cpystr(cs->name);
9059 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
9060 TRANSLATORS: Print something1 using something2.
9061 "character sets" is something1 */
9062 choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"),
9063 _("character sets"), h_select_charset_screen,
9064 _("HELP FOR SELECTING A CHARACTER SET"), NULL);
9066 if(!choice)
9067 q_status_message(SM_ORDER, 1, 4, "No choice");
9069 free_list_array(&charset_list);
9071 return(choice);
9076 * Allow user to choose a list of character sets and/or scripts
9078 * Returns allocated list.
9080 char **
9081 choose_list_of_charsets(void)
9083 LIST_SEL_S *listhead, *ls, *p;
9084 char **ret = NULL;
9085 int cnt, i, got_one;
9086 const CHARSET *cs;
9087 SCRIPT *s;
9088 char *q, *t;
9089 long width, limit;
9090 char buf[1024], *folded;
9093 * Build a list of charsets to choose from.
9096 p = listhead = NULL;
9098 /* this width is determined by select_from_list_screen() */
9099 width = ps_global->ttyo->screen_cols - 4;
9101 /* first comes a list of scripts (sets of character sets) */
9102 for(s = utf8_script(NIL); s && s->name; s++){
9104 limit = sizeof(buf)-1;
9105 q = buf;
9106 memset(q, 0, limit+1);
9108 if(s->name)
9109 sstrncpy(&q, s->name, limit);
9111 if(s->description){
9112 sstrncpy(&q, " (", limit-(q-buf));
9113 sstrncpy(&q, s->description, limit-(q-buf));
9114 sstrncpy(&q, ")", limit-(q-buf));
9117 /* add the list of charsets that are in this script */
9118 got_one = 0;
9119 for(cs = utf8_charset(NIL);
9120 cs && cs->name && (q-buf) < limit; cs++){
9121 if(cs->script & s->script){
9123 * Filter out some un-useful members of the list.
9124 * UTF-7 and UTF-8 weren't actually in the list at the
9125 * time this was written. Just making sure.
9127 if(!strucmp(cs->name, "ISO-2022-JP-2")
9128 || !strucmp(cs->name, "UTF-7")
9129 || !strucmp(cs->name, "UTF-8"))
9130 continue;
9132 if(got_one)
9133 sstrncpy(&q, " ", limit-(q-buf));
9134 else{
9135 got_one = 1;
9136 sstrncpy(&q, " {", limit-(q-buf));
9139 sstrncpy(&q, cs->name, limit-(q-buf));
9143 if(got_one)
9144 sstrncpy(&q, "}", limit-(q-buf));
9146 /* fold this line so that it can all be seen on the screen */
9147 folded = fold(buf, width, width, "", " ", FLD_NONE);
9148 if(folded){
9149 t = folded;
9150 while(t && *t && (q = strindex(t, '\n')) != NULL){
9151 *q = '\0';
9153 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9154 memset(ls, 0, sizeof(*ls));
9155 if(t == folded)
9156 ls->item = cpystr(s->name);
9157 else
9158 ls->flags = SFL_NOSELECT;
9160 ls->display_item = cpystr(t);
9162 t = q+1;
9164 if(p){
9165 p->next = ls;
9166 p = p->next;
9168 else{
9169 /* add a heading */
9170 listhead = (LIST_SEL_S *) fs_get(sizeof(*ls));
9171 memset(listhead, 0, sizeof(*listhead));
9172 listhead->flags = SFL_NOSELECT;
9173 listhead->display_item =
9174 cpystr(_("Scripts representing groups of related character sets"));
9175 listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls));
9176 memset(listhead->next, 0, sizeof(*listhead));
9177 listhead->next->flags = SFL_NOSELECT;
9178 listhead->next->display_item =
9179 cpystr(repeat_char(width, '-'));
9181 listhead->next->next = ls;
9182 p = ls;
9186 fs_give((void **) &folded);
9190 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9191 memset(ls, 0, sizeof(*ls));
9192 ls->flags = SFL_NOSELECT;
9193 if(p){
9194 p->next = ls;
9195 p = p->next;
9197 else
9198 listhead = p = ls;
9200 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9201 memset(ls, 0, sizeof(*ls));
9202 ls->flags = SFL_NOSELECT;
9203 ls->display_item =
9204 cpystr(_("Individual character sets, may be mixed with scripts"));
9205 p->next = ls;
9206 p = p->next;
9208 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9209 memset(ls, 0, sizeof(*ls));
9210 ls->flags = SFL_NOSELECT;
9211 ls->display_item =
9212 cpystr(repeat_char(width, '-'));
9213 p->next = ls;
9214 p = p->next;
9216 /* then comes a list of individual character sets */
9217 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9218 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9219 memset(ls, 0, sizeof(*ls));
9220 ls->item = cpystr(cs->name);
9222 if(p){
9223 p->next = ls;
9224 p = p->next;
9226 else
9227 listhead = p = ls;
9230 if(!listhead)
9231 return(ret);
9233 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9234 Print something1 using something2.
9235 "character sets" is something1 */
9236 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9237 _("SELECT CHARACTER SETS"), _("character sets"),
9238 h_select_multcharsets_screen,
9239 _("HELP FOR SELECTING CHARACTER SETS"), NULL)){
9240 for(cnt = 0, p = listhead; p; p = p->next)
9241 if(p->selected)
9242 cnt++;
9244 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9245 memset(ret, 0, (cnt+1) * sizeof(*ret));
9246 for(i = 0, p = listhead; p; p = p->next)
9247 if(p->selected)
9248 ret[i++] = cpystr(p->item ? p->item : "");
9251 free_list_sel(&listhead);
9253 return(ret);
9256 /* Report quota summary resources in an IMAP server */
9258 void cmd_quota (struct pine *state)
9260 QUOTALIST *imapquota;
9261 NETMBX mb;
9262 STORE_S *store;
9263 SCROLL_S sargs;
9265 if(!state->mail_stream || !is_imap_stream(state->mail_stream)){
9266 q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders");
9267 return;
9270 if (state->mail_stream
9271 && !sp_dead_stream(state->mail_stream)
9272 && state->mail_stream->mailbox
9273 && *state->mail_stream->mailbox
9274 && mail_valid_net_parse(state->mail_stream->mailbox, &mb))
9275 imap_getquotaroot(state->mail_stream, mb.mailbox);
9277 if(!state->quota) /* failed ? */
9278 return; /* go back... */
9280 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
9281 q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space.");
9282 return;
9285 so_puts(store, "Quota Report for ");
9286 so_puts(store, state->mail_stream->original_mailbox);
9287 so_puts(store, "\n\n");
9289 for (imapquota = state->quota; imapquota; imapquota = imapquota->next){
9291 so_puts(store, _("Resource : "));
9292 so_puts(store, imapquota->name);
9293 so_writec('\n', store);
9295 so_puts(store, _("Usage : "));
9296 so_puts(store, long2string(imapquota->usage));
9297 if(!strucmp(imapquota->name,"STORAGE"))
9298 so_puts(store, " KiB ");
9299 if(!strucmp(imapquota->name,"MESSAGE")){
9300 so_puts(store, _(" message"));
9301 if(imapquota->usage != 1)
9302 so_puts(store, _("s ")); /* plural */
9303 else
9304 so_puts(store, _(" "));
9306 so_writec('(', store);
9307 so_puts(store, long2string(100*imapquota->usage/imapquota->limit));
9308 so_puts(store, "%)\n");
9310 so_puts(store, _("Limit : "));
9311 so_puts(store, long2string(imapquota->limit));
9312 if(!strucmp(imapquota->name,"STORAGE"))
9313 so_puts(store, " KiB\n\n");
9314 if(!strucmp(imapquota->name,"MESSAGE")){
9315 so_puts(store, _(" message"));
9316 if(imapquota->usage != 1)
9317 so_puts(store, _("s\n\n")); /* plural */
9318 else
9319 so_puts(store, _("\n\n"));
9323 memset(&sargs, 0, sizeof(SCROLL_S));
9324 sargs.text.text = so_text(store);
9325 sargs.text.src = CharStar;
9326 sargs.text.desc = _("Quota Resources Summary");
9327 sargs.bar.title = _("QUOTA SUMMARY");
9328 sargs.proc.tool = NULL;
9329 sargs.help.text = h_quota_command;
9330 sargs.help.title = NULL;
9331 sargs.keys.menu = NULL;
9332 setbitmap(sargs.keys.bitmap);
9334 scrolltool(&sargs);
9335 so_give(&store);
9337 if (state->quota)
9338 mail_free_quotalist(&(state->quota));
9341 /*----------------------------------------------------------------------
9342 Prompt the user for the type of sort he desires
9344 Args: state -- pine state pointer
9345 q1 -- Line to prompt on
9347 Returns 0 if it was cancelled, 1 otherwise.
9348 ----*/
9350 select_sort(struct pine *state, int ql, SortOrder *sort, int *rev)
9352 char prompt[200], tmp[3], *p;
9353 int s, i;
9354 int deefault = 'a', retval = 1;
9355 HelpType help;
9356 ESCKEY_S sorts[14];
9358 #ifdef _WINDOWS
9359 DLG_SORTPARAM sortsel;
9361 if (mswin_usedialog ()) {
9363 sortsel.reverse = mn_get_revsort (state->msgmap);
9364 sortsel.cursort = mn_get_sort (state->msgmap);
9365 /* assumption here that HelpType is char ** */
9366 sortsel.helptext = h_select_sort;
9367 sortsel.rval = 0;
9369 if ((retval = os_sortdialog (&sortsel))) {
9370 *sort = sortsel.cursort;
9371 *rev = sortsel.reverse;
9374 return (retval);
9376 #endif
9378 /*----- String together the prompt ------*/
9379 tmp[1] = '\0';
9380 if(F_ON(F_USE_FK,ps_global))
9381 strncpy(prompt, _("Choose type of sort : "), sizeof(prompt));
9382 else
9383 strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "),
9384 sizeof(prompt));
9386 for(i = 0; state->sort_types[i] != EndofList; i++) {
9387 sorts[i].rval = i;
9388 p = sorts[i].label = sort_name(state->sort_types[i]);
9389 while(*(p+1) && islower((unsigned char)*p))
9390 p++;
9392 sorts[i].ch = tolower((unsigned char)(tmp[0] = *p));
9393 sorts[i].name = cpystr(tmp);
9395 if(mn_get_sort(state->msgmap) == state->sort_types[i])
9396 deefault = sorts[i].rval;
9399 sorts[i].ch = 'r';
9400 sorts[i].rval = 'r';
9401 sorts[i].name = cpystr("R");
9402 if(F_ON(F_USE_FK,ps_global))
9403 sorts[i].label = N_("Reverse");
9404 else
9405 sorts[i].label = "";
9407 sorts[++i].ch = -1;
9408 help = h_select_sort;
9410 if((F_ON(F_USE_FK,ps_global)
9411 && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x',
9412 help,RB_NORM)) != 'x'))
9414 (F_OFF(F_USE_FK,ps_global)
9415 && ((s = radio_buttons(prompt,ql,sorts,deefault,'x',
9416 help,RB_NORM)) != 'x'))){
9417 state->mangled_body = 1; /* signal screen's changed */
9418 if(s == 'r')
9419 *rev = !mn_get_revsort(state->msgmap);
9420 else
9421 *sort = state->sort_types[s];
9423 if(F_ON(F_SHOW_SORT, ps_global))
9424 ps_global->mangled_header = 1;
9426 else{
9427 retval = 0;
9428 cmd_cancelled("Sort");
9431 while(--i >= 0)
9432 fs_give((void **)&sorts[i].name);
9434 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9435 return(retval);
9439 /*---------------------------------------------------------------------
9440 Build list of folders in the given context for user selection
9442 Args: c -- pointer to pointer to folder's context context
9443 f -- folder prefix to display
9444 sublist -- whether or not to use 'f's contents as prefix
9445 lister -- function used to do the actual display
9447 Returns: malloc'd string containing sequence, else NULL if
9448 no messages in msgmap with local "selected" flag.
9449 ----*/
9451 display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int))
9453 int rc;
9454 CONTEXT_S *tc;
9455 void (*redraw)(void) = ps_global->redrawer;
9457 push_titlebar_state();
9458 tc = *c;
9459 if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0)
9460 *c = tc;
9462 ClearScreen();
9463 pop_titlebar_state();
9464 redraw_titlebar();
9465 if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
9466 (*ps_global->redrawer)();
9468 if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
9469 return(1);
9471 return(0);
9476 * Allow user to choose a single item from a list of strings.
9478 * Args list -- Array of strings to choose from, NULL terminated.
9479 * displist -- Array of strings to display instead of displaying list.
9480 * Indices correspond to the list array. Display the displist
9481 * but return the item from list if displist non-NULL.
9482 * title -- For conf_scroll_screen
9483 * pdesc -- For conf_scroll_screen
9484 * help -- For conf_scroll_screen
9485 * htitle -- For conf_scroll_screen
9487 * Returns an allocated copy of the chosen item or NULL.
9489 char *
9490 choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help,
9491 char *htitle, char *cursor_location)
9493 LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL;
9494 char **t, **dl;
9495 char *ret = NULL, *choice = NULL;
9497 /* build the LIST_SEL_S list */
9498 p = listhead = NULL;
9499 for(t = list, dl = displist; *t; t++, dl++){
9500 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9501 memset(ls, 0, sizeof(*ls));
9502 ls->item = cpystr(*t);
9503 if(displist)
9504 ls->display_item = cpystr(*dl);
9506 if(cursor_location && (cursor_location == (*t)))
9507 starting_val = ls;
9509 if(p){
9510 p->next = ls;
9511 p = p->next;
9513 else
9514 listhead = p = ls;
9517 if(!listhead)
9518 return(ret);
9520 if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc,
9521 help, htitle, starting_val))
9522 for(p = listhead; !choice && p; p = p->next)
9523 if(p->selected)
9524 choice = p->item;
9526 if(choice)
9527 ret = cpystr(choice);
9529 free_list_sel(&listhead);
9531 return(ret);
9535 void
9536 free_list_sel(LIST_SEL_S **lsel)
9538 if(lsel && *lsel){
9539 free_list_sel(&(*lsel)->next);
9540 if((*lsel)->item)
9541 fs_give((void **) &(*lsel)->item);
9543 if((*lsel)->display_item)
9544 fs_give((void **) &(*lsel)->display_item);
9546 fs_give((void **) lsel);
9552 * file_lister - call pico library's file lister
9555 file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags)
9557 PICO pbf;
9558 int rv;
9559 void (*redraw)(void) = ps_global->redrawer;
9561 standard_picobuf_setup(&pbf);
9562 push_titlebar_state();
9563 if(!newmail)
9564 pbf.newmail = NULL;
9566 /* BUG: what about help command and text? */
9567 pbf.pine_anchor = title;
9569 rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags);
9570 standard_picobuf_teardown(&pbf);
9571 fix_windsize(ps_global);
9572 init_signals(); /* has it's own signal stuff */
9574 /* Restore display's titlebar and body */
9575 pop_titlebar_state();
9576 redraw_titlebar();
9577 if((ps_global->redrawer = redraw) != NULL)
9578 (*ps_global->redrawer)();
9580 return(rv);
9584 /*----------------------------------------------------------------------
9585 Print current folder index
9587 ---*/
9589 print_index(struct pine *state, MSGNO_S *msgmap, int agg)
9591 long i;
9592 ICE_S *ice;
9593 char buf[MAX_SCREEN_COLS+1];
9595 for(i = 1L; i <= mn_get_total(msgmap); i++){
9596 if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
9597 continue;
9599 if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0))
9600 continue;
9602 ice = build_header_line(state, state->mail_stream, msgmap, i, NULL);
9604 if(ice){
9606 * I don't understand why we'd want to mark the current message
9607 * instead of printing out the first character of the status
9608 * so I'm taking it out and including the first character of the
9609 * line instead. Hubert 2006-02-09
9611 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
9612 return(0);
9615 if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i),
9616 print_char)
9617 || !gf_puts(NEWLINE, print_char))
9618 return(0);
9622 return(1);
9626 #ifdef _WINDOWS
9629 * windows callback to get/set header mode state
9632 header_mode_callback(set, args)
9633 int set;
9634 long args;
9636 return(ps_global->full_header);
9641 * windows callback to get/set zoom mode state
9644 zoom_mode_callback(set, args)
9645 int set;
9646 long args;
9648 return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0);
9653 * windows callback to get/set zoom mode state
9656 any_selected_callback(set, args)
9657 int set;
9658 long args;
9660 return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0);
9668 flag_callback(set, flags)
9669 int set;
9670 long flags;
9672 MESSAGECACHE *mc;
9673 int newflags = 0;
9674 long msgno;
9675 int permflag = 0;
9677 switch (set) {
9678 case 1: /* Important */
9679 permflag = ps_global->mail_stream->perm_flagged;
9680 break;
9682 case 2: /* New */
9683 permflag = ps_global->mail_stream->perm_seen;
9684 break;
9686 case 3: /* Answered */
9687 permflag = ps_global->mail_stream->perm_answered;
9688 break;
9690 case 4: /* Deleted */
9691 permflag = ps_global->mail_stream->perm_deleted;
9692 break;
9696 if(!(any_messages(ps_global->msgmap, NULL, "to Flag")
9697 && can_set_flag(ps_global, "flag", permflag)))
9698 return(0);
9700 if(sp_io_error_on_stream(ps_global->mail_stream)){
9701 sp_set_io_error_on_stream(ps_global->mail_stream, 0);
9702 pine_mail_check(ps_global->mail_stream); /* forces write */
9703 return(0);
9706 msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
9707 if(msgno > 0L && ps_global->mail_stream
9708 && msgno <= ps_global->mail_stream->nmsgs
9709 && (mc = mail_elt(ps_global->mail_stream, msgno))
9710 && mc->valid){
9712 * NOTE: code below is *VERY* sensitive to the order of
9713 * the messages defined in resource.h for flag handling.
9714 * Don't change it unless you know what you're doing.
9716 if(set){
9717 char *flagstr;
9718 long mflag;
9720 switch(set){
9721 case 1 : /* Important */
9722 flagstr = "\\FLAGGED";
9723 mflag = (mc->flagged) ? 0L : ST_SET;
9724 break;
9726 case 2 : /* New */
9727 flagstr = "\\SEEN";
9728 mflag = (mc->seen) ? 0L : ST_SET;
9729 break;
9731 case 3 : /* Answered */
9732 flagstr = "\\ANSWERED";
9733 mflag = (mc->answered) ? 0L : ST_SET;
9734 break;
9736 case 4 : /* Deleted */
9737 flagstr = "\\DELETED";
9738 mflag = (mc->deleted) ? 0L : ST_SET;
9739 break;
9741 default : /* bogus */
9742 return(0);
9745 mail_flag(ps_global->mail_stream, long2string(msgno),
9746 flagstr, mflag);
9748 if(ps_global->redrawer)
9749 (*ps_global->redrawer)();
9751 else{
9752 /* Important */
9753 if(mc->flagged)
9754 newflags |= 0x0001;
9756 /* New */
9757 if(!mc->seen)
9758 newflags |= 0x0002;
9760 /* Answered */
9761 if(mc->answered)
9762 newflags |= 0x0004;
9764 /* Deleted */
9765 if(mc->deleted)
9766 newflags |= 0x0008;
9770 return(newflags);
9776 * BUG: Should teach this about keywords
9778 MPopup *
9779 flag_submenu(mc)
9780 MESSAGECACHE *mc;
9782 static MPopup flag_submenu[] = {
9783 {tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}},
9784 {tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}},
9785 {tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}},
9786 {tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}},
9787 {tTail}
9790 /* Important */
9791 flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal;
9793 /* New */
9794 flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked;
9796 /* Answered */
9797 flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal;
9799 /* Deleted */
9800 flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal;
9802 return(flag_submenu);
9805 #endif /* _WINDOWS */