* When saving an attachment, the ^Y and ^V commands allow a user to scroll
[alpine.git] / alpine / mailcmd.c
blobf066c8982db8af3da007cfeb2b12c80bf01ff738
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: mailcmd.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2013-2015 Eduardo Chappa
8 * Copyright 2006-2009 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
20 mailcmd.c
21 The meat and pototoes of mail processing here:
22 - initial command processing and dispatch
23 - save message
24 - capture address off incoming mail
25 - jump to specific numbered message
26 - open (broach) a new folder
27 - search message headers (where is) command
28 ====*/
31 #include "headers.h"
32 #include "mailcmd.h"
33 #include "status.h"
34 #include "mailview.h"
35 #include "flagmaint.h"
36 #include "listsel.h"
37 #include "keymenu.h"
38 #include "alpine.h"
39 #include "mailpart.h"
40 #include "mailindx.h"
41 #include "folder.h"
42 #include "reply.h"
43 #include "help.h"
44 #include "titlebar.h"
45 #include "signal.h"
46 #include "radio.h"
47 #include "pipe.h"
48 #include "send.h"
49 #include "takeaddr.h"
50 #include "roleconf.h"
51 #include "smime.h"
52 #include "../pith/state.h"
53 #include "../pith/msgno.h"
54 #include "../pith/store.h"
55 #include "../pith/thread.h"
56 #include "../pith/flag.h"
57 #include "../pith/sort.h"
58 #include "../pith/maillist.h"
59 #include "../pith/save.h"
60 #include "../pith/pipe.h"
61 #include "../pith/news.h"
62 #include "../pith/util.h"
63 #include "../pith/sequence.h"
64 #include "../pith/keyword.h"
65 #include "../pith/stream.h"
66 #include "../pith/mailcmd.h"
67 #include "../pith/hist.h"
68 #include "../pith/list.h"
69 #include "../pith/icache.h"
70 #include "../pith/busy.h"
71 #include "../pith/mimedesc.h"
72 #include "../pith/pattern.h"
73 #include "../pith/tempfile.h"
74 #include "../pith/search.h"
75 #include "../pith/margin.h"
76 #ifdef _WINDOWS
77 #include "../pico/osdep/mswin.h"
78 #endif
81 * Internal Prototypes
83 int cmd_flag(struct pine *, MSGNO_S *, int);
84 int cmd_flag_prompt(struct pine *, struct flag_screen *, int);
85 void free_flag_table(struct flag_table **);
86 int cmd_reply(struct pine *, MSGNO_S *, int);
87 int cmd_forward(struct pine *, MSGNO_S *, int);
88 int cmd_bounce(struct pine *, MSGNO_S *, int);
89 int cmd_save(struct pine *, MAILSTREAM *, MSGNO_S *, int, CmdWhere);
90 void role_compose(struct pine *);
91 int cmd_expunge(struct pine *, MAILSTREAM *, MSGNO_S *, int);
92 int cmd_export(struct pine *, MSGNO_S *, int, int);
93 char *cmd_delete_action(struct pine *, MSGNO_S *, CmdWhere);
94 char *cmd_delete_view(struct pine *, MSGNO_S *);
95 char *cmd_delete_index(struct pine *, MSGNO_S *);
96 long get_level(int, UCS, SCROLL_S *);
97 long closest_jump_target(long, MAILSTREAM *, MSGNO_S *, int, CmdWhere, char *, size_t);
98 int update_folder_spec(char *, size_t, char *);
99 int cmd_print(struct pine *, MSGNO_S *, int, CmdWhere);
100 int cmd_pipe(struct pine *, MSGNO_S *, int);
101 STORE_S *list_mgmt_text(RFC2369_S *, long);
102 void list_mgmt_screen(STORE_S *);
103 int aggregate_select(struct pine *, MSGNO_S *, int, CmdWhere);
104 int select_by_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
105 int select_by_thrd_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
106 int select_by_date(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
107 int select_by_text(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
108 int select_by_size(MAILSTREAM *, SEARCHSET **);
109 SEARCHSET *visible_searchset(MAILSTREAM *, MSGNO_S *);
110 int select_by_status(MAILSTREAM *, SEARCHSET **);
111 int select_by_rule(MAILSTREAM *, SEARCHSET **);
112 int select_by_thread(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
113 char *choose_a_rule(int);
114 int select_by_keyword(MAILSTREAM *, SEARCHSET **);
115 char *choose_a_keyword(void);
116 int select_sort(struct pine *, int, SortOrder *, int *);
117 int print_index(struct pine *, MSGNO_S *, int);
120 * List of Select options used by apply_* functions...
122 static char *sel_pmt1 = N_("ALTER message selection : ");
123 ESCKEY_S sel_opts1[] = {
124 /* TRANSLATORS: these are keymenu names for selecting. Broaden selection means
125 we will add more messages to the selection, Narrow selection means we will
126 remove some selections (like a logical AND instead of logical OR), and Flip
127 Selected means that all the messages that are currently selected become unselected,
128 and all the unselected messages become selected. */
129 {'a', 'a', "A", N_("unselect All")},
130 {'c', 'c', "C", NULL},
131 {'b', 'b', "B", N_("Broaden selctn")},
132 {'n', 'n', "N", N_("Narrow selctn")},
133 {'f', 'f', "F", N_("Flip selected")},
134 {-1, 0, NULL, NULL}
138 #define SEL_OPTS_THREAD 9 /* index number of "tHread" */
139 #define SEL_OPTS_THREAD_CH 'h'
141 char *sel_pmt2 = "SELECT criteria : ";
142 static ESCKEY_S sel_opts2[] = {
143 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
144 means select the currently highlighted message; select by Number is by message
145 number; Status is by status of the message, for example the message might be
146 New or it might be Unseen or marked Important; Size has the Z upper case because
147 it is a Z command; Keyword is an alpine keyword that has been set by the user;
148 and Rule is an alpine rule */
149 {'a', 'a', "A", N_("select All")},
150 {'c', 'c', "C", N_("select Cur")},
151 {'n', 'n', "N", N_("Number")},
152 {'d', 'd', "D", N_("Date")},
153 {'t', 't', "T", N_("Text")},
154 {'s', 's', "S", N_("Status")},
155 {'z', 'z', "Z", N_("siZe")},
156 {'k', 'k', "K", N_("Keyword")},
157 {'r', 'r', "R", N_("Rule")},
158 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
159 {-1, 0, NULL, NULL}
163 static ESCKEY_S sel_opts3[] = {
164 /* TRANSLATORS: these are operations we can do on a set of selected messages.
165 Del is Delete; Undel is Undelete; TakeAddr means to Take some Addresses into
166 the address book; Save means to save the messages into another alpine folder;
167 Export means to copy the messages to a file outside of alpine, external to
168 alpine's world. */
169 {'d', 'd', "D", N_("Del")},
170 {'u', 'u', "U", N_("Undel")},
171 {'r', 'r', "R", N_("Reply")},
172 {'f', 'f', "F", N_("Forward")},
173 {'%', '%', "%", N_("Print")},
174 {'t', 't', "T", N_("TakeAddr")},
175 {'s', 's', "S", N_("Save")},
176 {'e', 'e', "E", N_("Export")},
177 { -1, 0, NULL, NULL},
178 { -1, 0, NULL, NULL},
179 { -1, 0, NULL, NULL},
180 { -1, 0, NULL, NULL},
181 { -1, 0, NULL, NULL},
182 { -1, 0, NULL, NULL},
183 { -1, 0, NULL, NULL},
184 {-1, 0, NULL, NULL}
187 static ESCKEY_S sel_opts4[] = {
188 {'a', 'a', "A", N_("select All")},
189 /* TRANSLATORS: select currrently highlighted message Thread */
190 {'c', 'c', "C", N_("select Curthrd")},
191 {'n', 'n', "N", N_("Number")},
192 {'d', 'd', "D", N_("Date")},
193 {'t', 't', "T", N_("Text")},
194 {'s', 's', "S", N_("Status")},
195 {'z', 'z', "Z", N_("siZe")},
196 {'k', 'k', "K", N_("Keyword")},
197 {'r', 'r', "R", N_("Rule")},
198 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
199 {-1, 0, NULL, NULL}
203 static char *sel_flag =
204 N_("Select New, Deleted, Answered, Forwarded, or Important messages ? ");
205 static char *sel_flag_not =
206 N_("Select NOT New, NOT Deleted, NOT Answered, NOT Forwarded or NOT Important msgs ? ");
207 static ESCKEY_S sel_flag_opt[] = {
208 /* TRANSLATORS: When selecting messages by message Status these are the
209 different types of Status you can select on. Is the message New, Recent,
210 and so on. Not means flip the meaning of the selection to the opposite
211 thing, so message is not New or not Important. */
212 {'n', 'n', "N", N_("New")},
213 {'*', '*', "*", N_("Important")},
214 {'d', 'd', "D", N_("Deleted")},
215 {'a', 'a', "A", N_("Answered")},
216 {'f', 'f', "F", N_("Forwarded")},
217 {-2, 0, NULL, NULL},
218 {'!', '!', "!", N_("Not")},
219 {-2, 0, NULL, NULL},
220 {'r', 'r', "R", N_("Recent")},
221 {'u', 'u', "U", N_("Unseen")},
222 {-1, 0, NULL, NULL}
226 static ESCKEY_S sel_date_opt[] = {
227 {0, 0, NULL, NULL},
228 /* TRANSLATORS: options when selecting messages by Date */
229 {ctrl('P'), 12, "^P", N_("Prev Day")},
230 {ctrl('N'), 13, "^N", N_("Next Day")},
231 {ctrl('X'), 11, "^X", N_("Cur Msg")},
232 {ctrl('W'), 14, "^W", N_("Toggle When")},
233 {KEY_UP, 12, "", ""},
234 {KEY_DOWN, 13, "", ""},
235 {-1, 0, NULL, NULL}
239 static char *sel_text =
240 N_("Select based on To, From, Cc, Recip, Partic, Subject fields or All msg text ? ");
241 static char *sel_text_not =
242 N_("Select based on NOT To, From, Cc, Recip, Partic, Subject or All msg text ? ");
243 static ESCKEY_S sel_text_opt[] = {
244 /* TRANSLATORS: Select messages based on the text contained in the From line, or
245 the Subject line, and so on. */
246 {'f', 'f', "F", N_("From")},
247 {'s', 's', "S", N_("Subject")},
248 {'t', 't', "T", N_("To")},
249 {'a', 'a', "A", N_("All Text")},
250 {'c', 'c', "C", N_("Cc")},
251 {'!', '!', "!", N_("Not")},
252 {'r', 'r', "R", N_("Recipient")},
253 {'p', 'p', "P", N_("Participant")},
254 {'b', 'b', "B", N_("Body")},
255 {'h', 'h', "H", N_("Header")},
256 {-1, 0, NULL, NULL}
259 static ESCKEY_S choose_action[] = {
260 {'c', 'c', "C", N_("Compose")},
261 {'r', 'r', "R", N_("Reply")},
262 {'f', 'f', "F", N_("Forward")},
263 {'b', 'b', "B", N_("Bounce")},
264 {-1, 0, NULL, NULL}
267 static char *select_num =
268 N_("Enter comma-delimited list of numbers (dash between ranges): ");
270 static char *select_size_larger_msg =
271 N_("Select messages with size larger than: ");
273 static char *select_size_smaller_msg =
274 N_("Select messages with size smaller than: ");
276 static char *sel_size_larger = N_("Larger");
277 static char *sel_size_smaller = N_("Smaller");
278 static ESCKEY_S sel_size_opt[] = {
279 {0, 0, NULL, NULL},
280 {ctrl('W'), 14, "^W", NULL},
281 {-1, 0, NULL, NULL}
284 static ESCKEY_S sel_key_opt[] = {
285 {0, 0, NULL, NULL},
286 {ctrl('T'), 14, "^T", N_("To List")},
287 {0, 0, NULL, NULL},
288 {'!', '!', "!", N_("Not")},
289 {-1, 0, NULL, NULL}
292 static ESCKEY_S flag_text_opt[] = {
293 /* TRANSLATORS: these are types of flags (markers) that the user can
294 set. For example, they can flag the message as an important message. */
295 {'n', 'n', "N", N_("New")},
296 {'*', '*', "*", N_("Important")},
297 {'d', 'd', "D", N_("Deleted")},
298 {'a', 'a', "A", N_("Answered")},
299 {'f', 'f', "F", N_("Forwarded")},
300 {'!', '!', "!", N_("Not")},
301 {ctrl('T'), 10, "^T", N_("To Flag Details")},
302 {-1, 0, NULL, NULL}
305 int
306 alpine_get_password(char *prompt, char *pass, size_t len)
308 int flags = OE_PASSWD | OE_DISALLOW_HELP;
309 pass[0] = '\0';
310 return optionally_enter(pass,
311 -(ps_global->ttyo ? FOOTER_ROWS(ps_global) : 3),
312 0, len, prompt, NULL, NO_HELP, &flags);
315 int smime_import_certificate(char *filename, char *full_filename, size_t len)
317 int r = 1;
318 static HISTORY_S *history = NULL;
319 static ESCKEY_S eopts[] = {
320 {ctrl('T'), 10, "^T", N_("To Files")},
321 {-1, 0, NULL, NULL},
322 {-1, 0, NULL, NULL}};
324 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
325 eopts[r].ch = ctrl('I');
326 eopts[r].rval = 11;
327 eopts[r].name = "TAB";
328 eopts[r].label = N_("Complete");
331 eopts[++r].ch = -1;
333 filename[0] = '\0';
334 full_filename[0] = '\0';
336 r = get_export_filename(ps_global, filename, NULL, full_filename,
337 len, "certificate", "IMPORT", eopts, NULL,
338 -FOOTER_ROWS(ps_global), GE_IS_IMPORT, &history);
340 return r;
344 /*----------------------------------------------------------------------
345 The giant switch on the commands for index and viewing
347 Input: command -- The command char/code
348 in_index -- flag indicating command is from index
349 orig_command -- The original command typed before pre-processing
350 Output: force_mailchk -- Set to tell caller to force call to new_mail().
352 Result: Manifold
354 Returns 1 if the message number or attachment to show changed
355 ---*/
357 process_cmd(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
358 int command, CmdWhere in_index, int *force_mailchk)
360 int question_line, a_changed, flags = 0, ret, j;
361 int notrealinbox;
362 long new_msgno, del_count, old_msgno, i;
363 long start;
364 char *newfolder, prompt[MAX_SCREEN_COLS+1];
365 CONTEXT_S *tc;
366 MESSAGECACHE *mc;
367 #if defined(DOS) && !defined(_WINDOWS)
368 extern long coreleft();
369 #endif
371 dprint((4, "\n - process_cmd(cmd=%d) -\n", command));
373 question_line = -FOOTER_ROWS(state);
374 state->mangled_screen = 0;
375 state->mangled_footer = 0;
376 state->mangled_header = 0;
377 state->next_screen = SCREEN_FUN_NULL;
378 old_msgno = mn_get_cur(msgmap);
379 a_changed = FALSE;
380 *force_mailchk = 0;
382 switch (command) {
383 /*------------- Help --------*/
384 case MC_HELP :
386 * We're not using the h_mail_view portion of this right now because
387 * that call is being handled in scrolltool() before it gets
388 * here. Leave it in case we change how it works.
390 helper((in_index == MsgIndx)
391 ? h_mail_index
392 : (in_index == View)
393 ? h_mail_view
394 : h_mail_thread_index,
395 (in_index == MsgIndx)
396 ? _("HELP FOR MESSAGE INDEX")
397 : (in_index == View)
398 ? _("HELP FOR MESSAGE TEXT")
399 : _("HELP FOR THREAD INDEX"),
400 HLPD_NONE);
401 dprint((4,"MAIL_CMD: did help command\n"));
402 state->mangled_screen = 1;
403 break;
406 /*--------- Return to main menu ------------*/
407 case MC_MAIN :
408 state->next_screen = main_menu_screen;
409 dprint((2,"MAIL_CMD: going back to main menu\n"));
410 break;
413 /*------- View message text --------*/
414 case MC_VIEW_TEXT :
415 view_text:
416 if(any_messages(msgmap, NULL, "to View")){
417 state->next_screen = mail_view_screen;
420 break;
423 /*------- View attachment --------*/
424 case MC_VIEW_ATCH :
425 state->next_screen = attachment_screen;
426 dprint((2,"MAIL_CMD: going to attachment screen\n"));
427 break;
430 /*---------- Previous message ----------*/
431 case MC_PREVITEM :
432 if(any_messages(msgmap, NULL, NULL)){
433 if((i = mn_get_cur(msgmap)) > 1L){
434 mn_dec_cur(stream, msgmap,
435 (in_index == View && THREADING()
436 && sp_viewing_a_thread(stream))
437 ? MH_THISTHD
438 : (in_index == View)
439 ? MH_ANYTHD : MH_NONE);
440 if(i == mn_get_cur(msgmap)){
441 PINETHRD_S *thrd = NULL, *topthrd = NULL;
443 if(THRD_INDX_ENABLED()){
444 mn_dec_cur(stream, msgmap, MH_ANYTHD);
445 if(i == mn_get_cur(msgmap))
446 q_status_message1(SM_ORDER, 0, 2,
447 _("Already on first %s in Zoomed Index"),
448 THRD_INDX() ? _("thread") : _("message"));
449 else{
450 if(in_index == View
451 || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
452 ret = 'y';
453 else
454 ret = want_to(_("View previous thread"), 'y', 'x',
455 NO_HELP, WT_NORM);
457 if(ret == 'y'){
458 q_status_message(SM_ORDER, 0, 2,
459 _("Viewing previous thread"));
460 new_msgno = mn_get_cur(msgmap);
461 mn_set_cur(msgmap, i);
462 if(unview_thread(state, stream, msgmap)){
463 state->next_screen = mail_index_screen;
464 state->view_skipped_index = 0;
465 state->mangled_screen = 1;
468 mn_set_cur(msgmap, new_msgno);
469 if(THRD_AUTO_VIEW() && in_index == View){
471 thrd = fetch_thread(stream,
472 mn_m2raw(msgmap,
473 new_msgno));
474 if(count_lflags_in_thread(stream, thrd,
475 msgmap,
476 MN_NONE) == 1){
477 if(view_thread(state, stream, msgmap, 1)){
478 if(current_index_state)
479 msgmap->top_after_thrd = current_index_state->msg_at_top;
481 state->view_skipped_index = 1;
482 command = MC_VIEW_TEXT;
483 goto view_text;
488 j = 0;
489 if(THRD_AUTO_VIEW() && in_index != View){
490 thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
491 if(thrd && thrd->top)
492 topthrd = fetch_thread(stream, thrd->top);
494 if(topthrd)
495 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
498 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
499 if(view_thread(state, stream, msgmap, 1)
500 && current_index_state)
501 msgmap->top_after_thrd = current_index_state->msg_at_top;
505 state->next_screen = SCREEN_FUN_NULL;
507 else
508 mn_set_cur(msgmap, i); /* put it back */
511 else
512 q_status_message1(SM_ORDER, 0, 2,
513 _("Already on first %s in Zoomed Index"),
514 THRD_INDX() ? _("thread") : _("message"));
517 else{
518 time_t now;
520 if(!IS_NEWS(stream)
521 && ((now = time(0)) > state->last_nextitem_forcechk)){
522 *force_mailchk = 1;
523 /* check at most once a second */
524 state->last_nextitem_forcechk = now;
527 q_status_message1(SM_ORDER, 0, 1, _("Already on first %s"),
528 THRD_INDX() ? _("thread") : _("message"));
532 break;
535 /*---------- Next Message ----------*/
536 case MC_NEXTITEM :
537 if(mn_get_total(msgmap) > 0L
538 && ((i = mn_get_cur(msgmap)) < mn_get_total(msgmap))){
539 mn_inc_cur(stream, msgmap,
540 (in_index == View && THREADING()
541 && sp_viewing_a_thread(stream))
542 ? MH_THISTHD
543 : (in_index == View)
544 ? MH_ANYTHD : MH_NONE);
545 if(i == mn_get_cur(msgmap)){
546 PINETHRD_S *thrd, *topthrd;
548 if(THRD_INDX_ENABLED()){
549 if(!THRD_INDX())
550 mn_inc_cur(stream, msgmap, MH_ANYTHD);
552 if(i == mn_get_cur(msgmap)){
553 if(any_lflagged(msgmap, MN_HIDE))
554 any_messages(NULL, "more", "in Zoomed Index");
555 else
556 goto nfolder;
558 else{
559 if(in_index == View
560 || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
561 ret = 'y';
562 else
563 ret = want_to(_("View next thread"), 'y', 'x',
564 NO_HELP, WT_NORM);
566 if(ret == 'y'){
567 q_status_message(SM_ORDER, 0, 2,
568 _("Viewing next thread"));
569 new_msgno = mn_get_cur(msgmap);
570 mn_set_cur(msgmap, i);
571 if(unview_thread(state, stream, msgmap)){
572 state->next_screen = mail_index_screen;
573 state->view_skipped_index = 0;
574 state->mangled_screen = 1;
577 mn_set_cur(msgmap, new_msgno);
578 if(THRD_AUTO_VIEW() && in_index == View){
580 thrd = fetch_thread(stream,
581 mn_m2raw(msgmap,
582 new_msgno));
583 if(count_lflags_in_thread(stream, thrd,
584 msgmap,
585 MN_NONE) == 1){
586 if(view_thread(state, stream, msgmap, 1)){
587 if(current_index_state)
588 msgmap->top_after_thrd = current_index_state->msg_at_top;
590 state->view_skipped_index = 1;
591 command = MC_VIEW_TEXT;
592 goto view_text;
597 j = 0;
598 if(THRD_AUTO_VIEW() && in_index != View){
599 thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
600 if(thrd && thrd->top)
601 topthrd = fetch_thread(stream, thrd->top);
603 if(topthrd)
604 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
607 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
608 if(view_thread(state, stream, msgmap, 1)
609 && current_index_state)
610 msgmap->top_after_thrd = current_index_state->msg_at_top;
614 state->next_screen = SCREEN_FUN_NULL;
616 else
617 mn_set_cur(msgmap, i); /* put it back */
620 else if(THREADING()
621 && (thrd = fetch_thread(stream, mn_m2raw(msgmap, i)))
622 && thrd->next
623 && get_lflag(stream, NULL, thrd->rawno, MN_COLL)){
624 q_status_message(SM_ORDER, 0, 2,
625 _("Expand collapsed thread to see more messages"));
627 else
628 any_messages(NULL, "more", "in Zoomed Index");
631 else{
632 time_t now;
633 nfolder:
634 prompt[0] = '\0';
635 if(IS_NEWS(stream)
636 || (state->context_current->use & CNTXT_INCMNG)){
637 char nextfolder[MAXPATH];
639 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
640 nextfolder[sizeof(nextfolder)-1] = '\0';
641 if(next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
642 state->context_current, NULL, NULL))
643 strncpy(prompt, _(". Press TAB for next folder."),
644 sizeof(prompt));
645 else
646 strncpy(prompt, _(". No more folders to TAB to."),
647 sizeof(prompt));
649 prompt[sizeof(prompt)-1] = '\0';
652 any_messages(NULL, (mn_get_total(msgmap) > 0L) ? "more" : NULL,
653 prompt[0] ? prompt : NULL);
655 if(!IS_NEWS(stream)
656 && ((now = time(0)) > state->last_nextitem_forcechk)){
657 *force_mailchk = 1;
658 /* check at most once a second */
659 state->last_nextitem_forcechk = now;
663 break;
666 /*---------- Delete message ----------*/
667 case MC_DELETE :
668 (void) cmd_delete(state, msgmap, MCMD_NONE,
669 (in_index == View) ? cmd_delete_view : cmd_delete_index);
670 break;
673 /*---------- Undelete message ----------*/
674 case MC_UNDELETE :
675 (void) cmd_undelete(state, msgmap, MCMD_NONE);
676 update_titlebar_status();
677 break;
680 /*---------- Reply to message ----------*/
681 case MC_REPLY :
682 (void) cmd_reply(state, msgmap, MCMD_NONE);
683 break;
686 /*---------- Forward message ----------*/
687 case MC_FORWARD :
688 (void) cmd_forward(state, msgmap, MCMD_NONE);
689 break;
692 /*---------- Quit pine ------------*/
693 case MC_QUIT :
694 state->next_screen = quit_screen;
695 dprint((1,"MAIL_CMD: quit\n"));
696 break;
699 /*---------- Compose message ----------*/
700 case MC_COMPOSE :
701 state->prev_screen = (in_index == View) ? mail_view_screen
702 : mail_index_screen;
703 compose_screen(state);
704 state->mangled_screen = 1;
705 if (state->next_screen)
706 a_changed = TRUE;
707 break;
710 /*---------- Alt Compose message ----------*/
711 case MC_ROLE :
712 state->prev_screen = (in_index == View) ? mail_view_screen
713 : mail_index_screen;
714 role_compose(state);
715 if(state->next_screen)
716 a_changed = TRUE;
718 break;
721 /*--------- Folders menu ------------*/
722 case MC_FOLDERS :
723 state->start_in_context = 1;
725 /*--------- Top of Folders list menu ------------*/
726 case MC_COLLECTIONS :
727 state->next_screen = folder_screen;
728 dprint((2,"MAIL_CMD: going to folder/collection menu\n"));
729 break;
732 /*---------- Open specific new folder ----------*/
733 case MC_GOTO :
734 tc = (state->context_last && !NEWS_TEST(state->context_current))
735 ? state->context_last : state->context_current;
737 newfolder = broach_folder(question_line, 1, &notrealinbox, &tc);
738 if(newfolder){
739 visit_folder(state, newfolder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT);
740 a_changed = TRUE;
743 break;
746 /*------- Go to Index Screen ----------*/
747 case MC_INDEX :
748 state->next_screen = mail_index_screen;
749 break;
751 /*------- Skip to next interesting message -----------*/
752 case MC_TAB :
753 if(THRD_INDX()){
754 PINETHRD_S *thrd;
757 * If we're in the thread index, start looking after this
758 * thread. We don't want to match something in the current
759 * thread.
761 start = mn_get_cur(msgmap);
762 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
763 if(mn_get_revsort(msgmap)){
764 /* if reversed, top of thread is last one before next thread */
765 if(thrd && thrd->top)
766 start = mn_raw2m(msgmap, thrd->top);
768 else{
769 /* last msg of thread is at the ends of the branches/nexts */
770 while(thrd){
771 start = mn_raw2m(msgmap, thrd->rawno);
772 if(thrd->branch)
773 thrd = fetch_thread(stream, thrd->branch);
774 else if(thrd->next)
775 thrd = fetch_thread(stream, thrd->next);
776 else
777 thrd = NULL;
782 * Flags is 0 in this case because we want to not skip
783 * messages inside of threads so that we can find threads
784 * which have some unseen messages even though the top-level
785 * of the thread is already seen.
786 * If new_msgno ends up being a message which is not visible
787 * because it isn't at the top-level, the current message #
788 * will be adjusted below in adjust_cur.
790 flags = 0;
791 new_msgno = next_sorted_flagged((F_UNDEL
792 | F_UNSEEN
793 | ((F_ON(F_TAB_TO_NEW,state))
794 ? 0 : F_OR_FLAG)),
795 stream, start, &flags);
797 else if(THREADING() && sp_viewing_a_thread(stream)){
798 PINETHRD_S *thrd, *topthrd = NULL;
800 start = mn_get_cur(msgmap);
803 * Things are especially complicated when we're viewing_a_thread
804 * from the thread index. First we have to check within the
805 * current thread for a new message. If none is found, then
806 * we search in the next threads and offer to continue in
807 * them. Then we offer to go to the next folder.
809 flags = NSF_SKIP_CHID;
810 new_msgno = next_sorted_flagged((F_UNDEL
811 | F_UNSEEN
812 | ((F_ON(F_TAB_TO_NEW,state))
813 ? 0 : F_OR_FLAG)),
814 stream, start, &flags);
816 * If we found a match then we are done, that is another message
817 * in the current thread index. Otherwise, we have to look
818 * further.
820 if(!(flags & NSF_FLAG_MATCH)){
821 ret = 'n';
822 while(1){
824 flags = 0;
825 new_msgno = next_sorted_flagged((F_UNDEL
826 | F_UNSEEN
827 | ((F_ON(F_TAB_TO_NEW,
828 state))
829 ? 0 : F_OR_FLAG)),
830 stream, start, &flags);
832 * If we got a match, new_msgno is a message in
833 * a different thread from the one we are viewing.
835 if(flags & NSF_FLAG_MATCH){
836 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
837 if(thrd && thrd->top)
838 topthrd = fetch_thread(stream, thrd->top);
840 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
841 static ESCKEY_S next_opt[] = {
842 {'y', 'y', "Y", N_("Yes")},
843 {'n', 'n', "N", N_("No")},
844 {TAB, 'n', "Tab", N_("NextNew")},
845 {-1, 0, NULL, NULL}
848 if(in_index)
849 snprintf(prompt, sizeof(prompt), _("View thread number %s? "),
850 topthrd ? comatose(topthrd->thrdno) : "?");
851 else
852 snprintf(prompt, sizeof(prompt),
853 _("View message in thread number %s? "),
854 topthrd ? comatose(topthrd->thrdno) : "?");
856 prompt[sizeof(prompt)-1] = '\0';
858 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
859 next_opt, 'y', 'x', NO_HELP,
860 RB_NORM);
861 if(ret == 'x'){
862 cmd_cancelled(NULL);
863 goto get_out;
866 else
867 ret = 'y';
869 if(ret == 'y'){
870 if(unview_thread(state, stream, msgmap)){
871 state->next_screen = mail_index_screen;
872 state->view_skipped_index = 0;
873 state->mangled_screen = 1;
876 mn_set_cur(msgmap, new_msgno);
877 if(THRD_AUTO_VIEW()){
879 if(count_lflags_in_thread(stream, topthrd,
880 msgmap, MN_NONE) == 1){
881 if(view_thread(state, stream, msgmap, 1)){
882 if(current_index_state)
883 msgmap->top_after_thrd = current_index_state->msg_at_top;
885 state->view_skipped_index = 1;
886 command = MC_VIEW_TEXT;
887 goto view_text;
892 if(view_thread(state, stream, msgmap, 1) && current_index_state)
893 msgmap->top_after_thrd = current_index_state->msg_at_top;
895 state->next_screen = SCREEN_FUN_NULL;
896 break;
898 else if(ret == 'n' && topthrd){
900 * skip to end of this thread and look starting
901 * in the next thread.
903 if(mn_get_revsort(msgmap)){
905 * if reversed, top of thread is last one
906 * before next thread
908 start = mn_raw2m(msgmap, topthrd->rawno);
910 else{
912 * last msg of thread is at the ends of
913 * the branches/nexts
915 thrd = topthrd;
916 while(thrd){
917 start = mn_raw2m(msgmap, thrd->rawno);
918 if(thrd->branch)
919 thrd = fetch_thread(stream, thrd->branch);
920 else if(thrd->next)
921 thrd = fetch_thread(stream, thrd->next);
922 else
923 thrd = NULL;
927 else if(ret == 'n')
928 break;
930 else
931 break;
935 else{
937 start = mn_get_cur(msgmap);
940 * If we are on a collapsed thread, start looking after the
941 * collapsed part, unless we are viewing the message.
943 if(THREADING() && in_index != View){
944 PINETHRD_S *thrd;
945 long rawno;
946 int collapsed;
948 rawno = mn_m2raw(msgmap, start);
949 thrd = fetch_thread(stream, rawno);
950 collapsed = thrd && thrd->next
951 && get_lflag(stream, NULL, rawno, MN_COLL);
953 if(collapsed){
954 if(mn_get_revsort(msgmap)){
955 if(thrd && thrd->top)
956 start = mn_raw2m(msgmap, thrd->top);
958 else{
959 while(thrd){
960 start = mn_raw2m(msgmap, thrd->rawno);
961 if(thrd->branch)
962 thrd = fetch_thread(stream, thrd->branch);
963 else if(thrd->next)
964 thrd = fetch_thread(stream, thrd->next);
965 else
966 thrd = NULL;
973 new_msgno = next_sorted_flagged((F_UNDEL
974 | F_UNSEEN
975 | ((F_ON(F_TAB_TO_NEW,state))
976 ? 0 : F_OR_FLAG)),
977 stream, start, &flags);
981 * If there weren't any unread messages left, OR there
982 * aren't any messages at all, we may want to offer to
983 * go on to the next folder...
985 if(flags & NSF_FLAG_MATCH){
986 mn_set_cur(msgmap, new_msgno);
987 if(in_index != View)
988 adjust_cur_to_visible(stream, msgmap);
990 else{
991 int in_inbox = sp_flagged(stream, SP_INBOX);
993 if(state->context_current
994 && ((NEWS_TEST(state->context_current)
995 && context_isambig(state->cur_folder))
996 || ((state->context_current->use & CNTXT_INCMNG)
997 && (in_inbox
998 || folder_index(state->cur_folder,
999 state->context_current,
1000 FI_FOLDER) >= 0)))){
1001 char nextfolder[MAXPATH];
1002 MAILSTREAM *nextstream = NULL;
1003 long recent_cnt;
1004 int did_cancel = 0;
1006 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
1007 nextfolder[sizeof(nextfolder)-1] = '\0';
1008 while(1){
1009 if(!(next_folder(&nextstream, nextfolder, sizeof(nextfolder), nextfolder,
1010 state->context_current, &recent_cnt,
1011 F_ON(F_TAB_NO_CONFIRM,state)
1012 ? NULL : &did_cancel))){
1013 if(!in_inbox){
1014 static ESCKEY_S inbox_opt[] = {
1015 {'y', 'y', "Y", N_("Yes")},
1016 {'n', 'n', "N", N_("No")},
1017 {TAB, 'z', "Tab", N_("To Inbox")},
1018 {-1, 0, NULL, NULL}
1021 if(F_ON(F_RET_INBOX_NO_CONFIRM,state))
1022 ret = 'y';
1023 else{
1024 /* TRANSLATORS: this is a question, with some information followed
1025 by Return to INBOX? */
1026 if(state->context_current->use&CNTXT_INCMNG)
1027 snprintf(prompt, sizeof(prompt), _("No more incoming folders. Return to \"%s\"? "), state->inbox_name);
1028 else
1029 snprintf(prompt, sizeof(prompt), _("No more news groups. Return to \"%s\"? "), state->inbox_name);
1031 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1032 inbox_opt, 'y', 'x',
1033 NO_HELP, RB_NORM);
1037 * 'z' is a synonym for 'y'. It is not 'y'
1038 * so that it isn't displayed as a default
1039 * action with square-brackets around it
1040 * in the keymenu...
1042 if(ret == 'y' || ret == 'z'){
1043 visit_folder(state, state->inbox_name,
1044 state->context_current,
1045 NULL, DB_INBOXWOCNTXT);
1046 a_changed = TRUE;
1049 else if (did_cancel)
1050 cmd_cancelled(NULL);
1051 else{
1052 if(state->context_current->use&CNTXT_INCMNG)
1053 q_status_message(SM_ORDER, 0, 2, _("No more incoming folders"));
1054 else
1055 q_status_message(SM_ORDER, 0, 2, _("No more news groups"));
1058 break;
1062 #define CNTLEN 80
1063 char *front, type[80], cnt[CNTLEN], fbuf[MAX_SCREEN_COLS/2+1];
1064 int rbspace, avail, need, take_back;
1067 * View_next_
1068 * Incoming_folder_ or news_group_ or folder_ or group_
1069 * "foldername"
1070 * _(13 recent) or _(some recent) or nothing
1071 * ?_
1073 front = "View next";
1074 strncpy(type,
1075 (state->context_current->use & CNTXT_INCMNG)
1076 ? "Incoming folder" : "news group",
1077 sizeof(type));
1078 type[sizeof(type)-1] = '\0';
1079 snprintf(cnt, sizeof(cnt), " (%.*s %s)", CNTLEN-20,
1080 recent_cnt ? long2string(recent_cnt) : "some",
1081 F_ON(F_TAB_USES_UNSEEN, ps_global)
1082 ? "unseen" : "recent");
1083 cnt[sizeof(cnt)-1] = '\0';
1086 * Space reserved for radio_buttons call.
1087 * If we make this 3 then radio_buttons won't mess
1088 * with the prompt. If we make it 2, then we get
1089 * one more character to use but radio_buttons will
1090 * cut off the last character of our prompt, which is
1091 * ok because it is a space.
1093 rbspace = 2;
1094 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols
1095 : 80;
1096 need = strlen(front)+1 + strlen(type)+1 +
1097 + strlen(nextfolder)+2 + strlen(cnt) +
1098 2 + rbspace;
1099 if(avail < need){
1100 take_back = strlen(type);
1101 strncpy(type,
1102 (state->context_current->use & CNTXT_INCMNG)
1103 ? "folder" : "group", sizeof(type));
1104 take_back -= strlen(type);
1105 need -= take_back;
1106 if(avail < need){
1107 need -= strlen(cnt);
1108 cnt[0] = '\0';
1111 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1112 snprintf(prompt, sizeof(prompt), "%.*s %.*s \"%.*s\"%.*s? ",
1113 (MAX_SCREEN_COLS+1)/8, front,
1114 (MAX_SCREEN_COLS+1)/8, type,
1115 (MAX_SCREEN_COLS+1)/2,
1116 short_str(nextfolder, fbuf, sizeof(fbuf),
1117 strlen(nextfolder) -
1118 ((need>avail) ? (need-avail) : 0),
1119 MidDots),
1120 (MAX_SCREEN_COLS+1)/8, cnt);
1121 prompt[sizeof(prompt)-1] = '\0';
1125 * When help gets added, this'll have to become
1126 * a loop like the rest...
1128 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
1129 static ESCKEY_S next_opt[] = {
1130 {'y', 'y', "Y", N_("Yes")},
1131 {'n', 'n', "N", N_("No")},
1132 {TAB, 'n', "Tab", N_("NextNew")},
1133 {-1, 0, NULL, NULL}
1136 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1137 next_opt, 'y', 'x', NO_HELP,
1138 RB_NORM);
1139 if(ret == 'x'){
1140 cmd_cancelled(NULL);
1141 break;
1144 else
1145 ret = 'y';
1147 if(ret == 'y'){
1148 if(nextstream && sp_dead_stream(nextstream))
1149 nextstream = NULL;
1151 visit_folder(state, nextfolder,
1152 state->context_current, nextstream,
1153 DB_FROMTAB);
1154 /* visit_folder takes care of nextstream */
1155 nextstream = NULL;
1156 a_changed = TRUE;
1157 break;
1161 if(nextstream)
1162 pine_mail_close(nextstream);
1164 else
1165 any_messages(NULL,
1166 (mn_get_total(msgmap) > 0L)
1167 ? IS_NEWS(stream) ? "more undeleted" : "more new"
1168 : NULL,
1169 NULL);
1172 get_out:
1174 break;
1177 /*------- Zoom -----------*/
1178 case MC_ZOOM :
1180 * Right now the way zoom is implemented is sort of silly.
1181 * There are two per-message flags where just one and a
1182 * global "zoom mode" flag to suppress messags from the index
1183 * should suffice.
1185 if(any_messages(msgmap, NULL, "to Zoom on")){
1186 if(unzoom_index(state, stream, msgmap)){
1187 dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
1188 q_status_message(SM_ORDER,0,2, _("Index Zoom Mode is now off"));
1190 else if((i = zoom_index(state, stream, msgmap, MN_SLCT)) != 0){
1191 if(any_lflagged(msgmap, MN_HIDE)){
1192 dprint((4,"\n\n ---- Entering ZOOM mode ----\n"));
1193 q_status_message4(SM_ORDER, 0, 2,
1194 _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"),
1195 THRD_INDX() ? "" : comatose(i),
1196 THRD_INDX() ? "" : " ",
1197 THRD_INDX() ? _("threads") : _("message"),
1198 THRD_INDX() ? "" : plural(i));
1200 else
1201 q_status_message(SM_ORDER, 0, 2,
1202 _("All messages selected, so not entering Index Zoom Mode"));
1204 else
1205 any_messages(NULL, "selected", "to Zoom on");
1208 break;
1211 /*---------- print message on paper ----------*/
1212 case MC_PRINTMSG :
1213 if(any_messages(msgmap, NULL, "to print"))
1214 (void) cmd_print(state, msgmap, MCMD_NONE, in_index);
1216 break;
1219 /*---------- Take Address ----------*/
1220 case MC_TAKE :
1221 if(F_ON(F_ENABLE_ROLE_TAKE, state) ||
1222 any_messages(msgmap, NULL, "to Take address from"))
1223 (void) cmd_take_addr(state, msgmap, MCMD_NONE);
1225 break;
1228 /*---------- Save Message ----------*/
1229 case MC_SAVE :
1230 if(any_messages(msgmap, NULL, "to Save"))
1231 (void) cmd_save(state, stream, msgmap, MCMD_NONE, in_index);
1233 break;
1236 /*---------- Export message ----------*/
1237 case MC_EXPORT :
1238 if(any_messages(msgmap, NULL, "to Export")){
1239 (void) cmd_export(state, msgmap, question_line, MCMD_NONE);
1240 state->mangled_footer = 1;
1243 break;
1246 /*---------- Expunge ----------*/
1247 case MC_EXPUNGE :
1248 (void) cmd_expunge(state, stream, msgmap, MCMD_NONE);
1249 break;
1252 /*------- Unexclude -----------*/
1253 case MC_UNEXCLUDE :
1254 if(!(IS_NEWS(stream) && stream->rdonly)){
1255 q_status_message(SM_ORDER, 0, 3,
1256 _("Unexclude not available for mail folders"));
1258 else if(any_lflagged(msgmap, MN_EXLD)){
1259 SEARCHPGM *pgm;
1260 long i;
1261 int exbits;
1264 * Since excluded means "hidden deleted" and "killed",
1265 * the count should reflect the former.
1267 pgm = mail_newsearchpgm();
1268 pgm->deleted = 1;
1269 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
1270 for(i = 1L, del_count = 0L; i <= stream->nmsgs; i++)
1271 if((mc = mail_elt(stream, i)) && mc->searched
1272 && get_lflag(stream, NULL, i, MN_EXLD)
1273 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
1274 && (exbits & MSG_EX_FILTERED)))
1275 del_count++;
1277 if(del_count > 0L){
1278 state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1279 snprintf(prompt, sizeof(prompt), "UNexclude %ld message%s in %.*s", del_count,
1280 plural(del_count), MAX_SCREEN_COLS+1-40,
1281 pretty_fn(state->cur_folder));
1282 prompt[sizeof(prompt)-1] = '\0';
1283 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
1284 || (F_ON(F_AUTO_EXPUNGE, state)
1285 && (state->context_current
1286 && (state->context_current->use & CNTXT_INCMNG))
1287 && context_isambig(state->cur_folder))
1288 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
1289 long save_cur_rawno;
1290 int were_viewing_a_thread;
1292 save_cur_rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
1293 were_viewing_a_thread = (THREADING()
1294 && sp_viewing_a_thread(stream));
1296 if(msgno_include(stream, msgmap, MI_NONE)){
1297 clear_index_cache(stream, 0);
1299 if(stream && stream->spare)
1300 erase_threading_info(stream, msgmap);
1302 refresh_sort(stream, msgmap, SRT_NON);
1305 if(were_viewing_a_thread){
1306 if(save_cur_rawno > 0L)
1307 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1309 if(view_thread(state, stream, msgmap, 1) && current_index_state)
1310 msgmap->top_after_thrd = current_index_state->msg_at_top;
1313 if(save_cur_rawno > 0L)
1314 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1316 state->mangled_screen = 1;
1317 q_status_message2(SM_ORDER, 0, 4,
1318 "%s message%s UNexcluded",
1319 long2string(del_count),
1320 plural(del_count));
1322 if(in_index != View)
1323 adjust_cur_to_visible(stream, msgmap);
1325 else
1326 any_messages(NULL, NULL, "UNexcluded");
1328 else
1329 any_messages(NULL, "excluded", "to UNexclude");
1331 else
1332 any_messages(NULL, "excluded", "to UNexclude");
1334 break;
1337 /*------- Make Selection -----------*/
1338 case MC_SELECT :
1339 if(any_messages(msgmap, NULL, "to Select")){
1340 if(aggregate_select(state, msgmap, question_line, in_index) == 0
1341 && (in_index == MsgIndx || in_index == ThrdIndx)
1342 && F_ON(F_AUTO_ZOOM, state)
1343 && any_lflagged(msgmap, MN_SLCT) > 0L
1344 && !any_lflagged(msgmap, MN_HIDE))
1345 (void) zoom_index(state, stream, msgmap, MN_SLCT);
1348 break;
1351 /*------- Toggle Current Message Selection State -----------*/
1352 case MC_SELCUR :
1353 if(any_messages(msgmap, NULL, NULL)){
1354 if((select_by_current(state, msgmap, in_index)
1355 || (F_OFF(F_UNSELECT_WONT_ADVANCE, state)
1356 && !any_lflagged(msgmap, MN_HIDE)))
1357 && (i = mn_get_cur(msgmap)) < mn_get_total(msgmap)){
1358 /* advance current */
1359 mn_inc_cur(stream, msgmap,
1360 (in_index == View && THREADING()
1361 && sp_viewing_a_thread(stream))
1362 ? MH_THISTHD
1363 : (in_index == View)
1364 ? MH_ANYTHD : MH_NONE);
1368 break;
1371 /*------- Apply command -----------*/
1372 case MC_APPLY :
1373 if(any_messages(msgmap, NULL, NULL)){
1374 if(any_lflagged(msgmap, MN_SLCT) > 0L){
1375 if(apply_command(state, stream, msgmap, 0,
1376 AC_NONE, question_line)){
1377 if(F_ON(F_AUTO_UNSELECT, state)){
1378 agg_select_all(stream, msgmap, NULL, 0);
1379 unzoom_index(state, stream, msgmap);
1381 else if(F_ON(F_AUTO_UNZOOM, state))
1382 unzoom_index(state, stream, msgmap);
1385 else
1386 any_messages(NULL, NULL, "to Apply command to. Try \"Select\"");
1389 break;
1392 /*-------- Sort command -------*/
1393 case MC_SORT :
1395 int were_threading = THREADING();
1396 SortOrder sort = mn_get_sort(msgmap);
1397 int rev = mn_get_revsort(msgmap);
1399 dprint((1,"MAIL_CMD: sort\n"));
1400 if(select_sort(state, question_line, &sort, &rev)){
1401 /* $ command reinitializes threading collapsed/expanded info */
1402 if(SORT_IS_THREADED(msgmap) && !SEP_THRDINDX())
1403 erase_threading_info(stream, msgmap);
1405 if(ps_global && ps_global->ttyo){
1406 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
1407 ps_global->mangled_footer = 1;
1410 sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN);
1413 state->mangled_footer = 1;
1416 * We've changed whether we are threading or not so we need to
1417 * exit the index and come back in so that we switch between the
1418 * thread index and the regular index. Sort_folder will have
1419 * reset viewing_a_thread if necessary.
1421 if(SEP_THRDINDX()
1422 && ((!were_threading && THREADING())
1423 || (were_threading && !THREADING()))){
1424 state->next_screen = mail_index_screen;
1425 state->mangled_screen = 1;
1429 break;
1432 /*------- Toggle Full Headers -----------*/
1433 case MC_FULLHDR :
1434 state->full_header++;
1435 if(state->full_header == 1){
1436 if(!(state->quote_suppression_threshold
1437 && (state->some_quoting_was_suppressed || in_index != View)))
1438 state->full_header++;
1440 else if(state->full_header > 2)
1441 state->full_header = 0;
1443 switch(state->full_header){
1444 case 0:
1445 q_status_message(SM_ORDER, 0, 3,
1446 _("Display of full headers is now off."));
1447 break;
1449 case 1:
1450 q_status_message1(SM_ORDER, 0, 3,
1451 _("Quotes displayed, use %s to see full headers"),
1452 F_ON(F_USE_FK, state) ? "F9" : "H");
1453 break;
1455 case 2:
1456 q_status_message(SM_ORDER, 0, 3,
1457 _("Display of full headers is now on."));
1458 break;
1462 a_changed = TRUE;
1463 break;
1466 case MC_TOGGLE :
1467 a_changed = TRUE;
1468 break;
1471 #ifdef SMIME
1472 /*------- Try to decrypt message -----------*/
1473 case MC_DECRYPT:
1474 if(state->smime && state->smime->need_passphrase)
1475 smime_get_passphrase();
1477 a_changed = TRUE;
1478 break;
1480 case MC_SECURITY:
1481 smime_info_screen(state);
1482 break;
1483 #endif
1486 /*------- Bounce -----------*/
1487 case MC_BOUNCE :
1488 (void) cmd_bounce(state, msgmap, MCMD_NONE);
1489 break;
1492 /*------- Flag -----------*/
1493 case MC_FLAG :
1494 dprint((4, "\n - flag message -\n"));
1495 (void) cmd_flag(state, msgmap, MCMD_NONE);
1496 break;
1499 /*------- Pipe message -----------*/
1500 case MC_PIPE :
1501 (void) cmd_pipe(state, msgmap, MCMD_NONE);
1502 break;
1505 /*--------- Default, unknown command ----------*/
1506 default:
1507 alpine_panic("Unexpected command case");
1508 break;
1511 return((a_changed || mn_get_cur(msgmap) != old_msgno) ? 1 : 0);
1516 /*----------------------------------------------------------------------
1517 Map some of the special characters into sensible strings for human
1518 consumption.
1519 c is a UCS-4 character!
1520 ----*/
1521 char *
1522 pretty_command(UCS c)
1524 static char buf[10];
1525 char *s;
1527 buf[0] = '\0';
1528 s = buf;
1530 switch(c){
1531 case ' ' : s = "SPACE"; break;
1532 case '\033' : s = "ESC"; break;
1533 case '\177' : s = "DEL"; break;
1534 case ctrl('I') : s = "TAB"; break;
1535 case ctrl('J') : s = "LINEFEED"; break;
1536 case ctrl('M') : s = "RETURN"; break;
1537 case ctrl('Q') : s = "XON"; break;
1538 case ctrl('S') : s = "XOFF"; break;
1539 case KEY_UP : s = "Up Arrow"; break;
1540 case KEY_DOWN : s = "Down Arrow"; break;
1541 case KEY_RIGHT : s = "Right Arrow"; break;
1542 case KEY_LEFT : s = "Left Arrow"; break;
1543 case KEY_PGUP : s = "Prev Page"; break;
1544 case KEY_PGDN : s = "Next Page"; break;
1545 case KEY_HOME : s = "Home"; break;
1546 case KEY_END : s = "End"; break;
1547 case KEY_DEL : s = "Delete"; break; /* Not necessary DEL! */
1548 case KEY_JUNK : s = "Junk!"; break;
1549 case BADESC : s = "Bad Esc"; break;
1550 case NO_OP_IDLE : s = "NO_OP_IDLE"; break;
1551 case NO_OP_COMMAND : s = "NO_OP_COMMAND"; break;
1552 case KEY_RESIZE : s = "KEY_RESIZE"; break;
1553 case KEY_UTF8 : s = "KEY_UTF8"; break;
1554 case KEY_MOUSE : s = "KEY_MOUSE"; break;
1555 case KEY_SCRLUPL : s = "KEY_SCRLUPL"; break;
1556 case KEY_SCRLDNL : s = "KEY_SCRLDNL"; break;
1557 case KEY_SCRLTO : s = "KEY_SCRLTO"; break;
1558 case KEY_XTERM_MOUSE : s = "KEY_XTERM_MOUSE"; break;
1559 case KEY_DOUBLE_ESC : s = "KEY_DOUBLE_ESC"; break;
1560 case CTRL_KEY_UP : s = "Ctrl Up Arrow"; break;
1561 case CTRL_KEY_DOWN : s = "Ctrl Down Arrow"; break;
1562 case CTRL_KEY_RIGHT : s = "Ctrl Right Arrow"; break;
1563 case CTRL_KEY_LEFT : s = "Ctrl Left Arrow"; break;
1564 case PF1 :
1565 case PF2 :
1566 case PF3 :
1567 case PF4 :
1568 case PF5 :
1569 case PF6 :
1570 case PF7 :
1571 case PF8 :
1572 case PF9 :
1573 case PF10 :
1574 case PF11 :
1575 case PF12 :
1576 snprintf(s = buf, sizeof(buf), "F%ld", (long) (c - PF1 + 1));
1577 break;
1579 default:
1580 if(c < ' ' || (c >= 0x80 && c < 0xA0)){
1581 char d;
1582 int c1;
1584 c1 = (c >= 0x80);
1585 d = (c & 0x1f) + 'A' - 1;
1586 snprintf(s = buf, sizeof(buf), "%c%c", c1 ? '~' : '^', d);
1588 else{
1589 memset(buf, 0, sizeof(buf));
1590 utf8_put((unsigned char *) buf, (unsigned long) c);
1593 break;
1596 return(s);
1600 /*----------------------------------------------------------------------
1601 Complain about bogus input
1603 Args: ch -- input command to complain about
1604 help -- string indicating where to get help
1606 ----*/
1607 void
1608 bogus_command(UCS cmd, char *help)
1610 if(cmd == ctrl('Q') || cmd == ctrl('S'))
1611 q_status_message1(SM_ASYNC, 0, 2,
1612 "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.",
1613 pretty_command(cmd));
1614 else if(cmd == KEY_JUNK)
1615 q_status_message3(SM_ORDER, 0, 2,
1616 "Invalid key pressed.%s%s%s",
1617 (help) ? " Use " : "",
1618 (help) ? help : "",
1619 (help) ? " for help" : "");
1620 else
1621 q_status_message4(SM_ORDER, 0, 2,
1622 "Command \"%s\" not defined for this screen.%s%s%s",
1623 pretty_command(cmd),
1624 (help) ? " Use " : "",
1625 (help) ? help : "",
1626 (help) ? " for help" : "");
1630 void
1631 bogus_utf8_command(char *cmd, char *help)
1633 q_status_message4(SM_ORDER, 0, 2,
1634 "Command \"%s\" not defined for this screen.%s%s%s",
1635 cmd ? cmd : "?",
1636 (help) ? " Use " : "",
1637 (help) ? help : "",
1638 (help) ? " for help" : "");
1642 /*----------------------------------------------------------------------
1643 Execute FLAG message command
1645 Args: state -- Various satate info
1646 msgmap -- map of c-client to local message numbers
1648 Result: with side effect of "current" message FLAG flag set or UNset
1650 ----*/
1652 cmd_flag(struct pine *state, MSGNO_S *msgmap, int aopt)
1654 char *flagit, *seq, *screen_text[20], **exp, **p, *answer = NULL;
1655 char *keyword_array[2];
1656 int user_defined_flags = 0, mailbox_flags = 0;
1657 int directly_to_maint_screen = 0;
1658 long unflagged, flagged, flags, rawno;
1659 MESSAGECACHE *mc = NULL;
1660 KEYWORD_S *kw;
1661 int i, cnt, is_set, trouble = 0, rv = 0;
1662 size_t len;
1663 struct flag_table *fp, *ftbl = NULL;
1664 struct flag_screen flag_screen;
1665 static char *flag_screen_text1[] = {
1666 N_(" Set desired flags for current message below. An 'X' means set"),
1667 N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."),
1668 NULL
1671 static char *flag_screen_text2[] = {
1672 N_(" Set desired flags below for selected messages. A '?' means to"),
1673 N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"),
1674 N_(" to unset it. Use the \"Return\" key to toggle, and choose"),
1675 N_(" \"Exit\" when finished."),
1676 NULL
1679 static struct flag_table default_ftbl[] = {
1680 {N_("Important"), h_flag_important, F_FLAG, 0, 0, NULL, NULL},
1681 {N_("New"), h_flag_new, F_SEEN, 0, 0, NULL, NULL},
1682 {N_("Answered"), h_flag_answered, F_ANS, 0, 0, NULL, NULL},
1683 {N_("Forwarded"), h_flag_forwarded, F_FWD, 0, 0, NULL, NULL},
1684 {N_("Deleted"), h_flag_deleted, F_DEL, 0, 0, NULL, NULL},
1685 {NULL, NO_HELP, 0, 0, 0, NULL, NULL}
1688 /* Only check for dead stream for now. Should check permanent flags
1689 * eventually
1691 if(!(any_messages(msgmap, NULL, "to Flag") && can_set_flag(state, "flag", 1)))
1692 return rv;
1694 if(sp_io_error_on_stream(state->mail_stream)){
1695 sp_set_io_error_on_stream(state->mail_stream, 0);
1696 pine_mail_check(state->mail_stream); /* forces write */
1697 return rv;
1700 go_again:
1701 answer = NULL;
1702 user_defined_flags = 0;
1703 mailbox_flags = 0;
1704 mc = NULL;
1705 trouble = 0;
1706 ftbl = NULL;
1708 /* count how large ftbl will be */
1709 for(cnt = 0; default_ftbl[cnt].name; cnt++)
1712 /* add user flags */
1713 for(kw = ps_global->keywords; kw; kw = kw->next){
1714 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick)) || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1715 user_defined_flags++;
1716 cnt++;
1721 * Add mailbox flags that aren't user-defined flags.
1722 * Don't consider it if it matches either one of our defined
1723 * keywords or one of our defined nicknames for a keyword.
1725 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1726 char *q;
1728 q = stream_to_user_flag_name(state->mail_stream, i);
1729 if(q && q[0]){
1730 for(kw = ps_global->keywords; kw; kw = kw->next){
1731 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1732 break;
1736 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1737 mailbox_flags++;
1738 cnt++;
1742 cnt += (user_defined_flags ? 2 : 0) + (mailbox_flags ? 2 : 0);
1744 /* set up ftbl, first the system flags */
1745 ftbl = (struct flag_table *) fs_get((cnt+1) * sizeof(*ftbl));
1746 memset(ftbl, 0, (cnt+1) * sizeof(*ftbl));
1747 for(i = 0, fp = ftbl; default_ftbl[i].name; i++, fp++){
1748 fp->name = cpystr(default_ftbl[i].name);
1749 fp->help = default_ftbl[i].help;
1750 fp->flag = default_ftbl[i].flag;
1751 fp->set = default_ftbl[i].set;
1752 fp->ukn = default_ftbl[i].ukn;
1755 if(user_defined_flags){
1756 fp->flag = F_COMMENT;
1757 fp->name = cpystr("");
1758 fp++;
1759 fp->flag = F_COMMENT;
1760 len = strlen(_("User-defined Keywords from Setup/Config"));
1761 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1762 snprintf(fp->name, len+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config"));
1763 fp++;
1766 /* then the user-defined keywords */
1767 if(user_defined_flags)
1768 for(kw = ps_global->keywords; kw; kw = kw->next){
1769 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick))
1770 || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1771 fp->name = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
1772 fp->keyword = cpystr(kw->kw ? kw->kw : "");
1773 if(kw->nick && kw->kw){
1774 size_t l;
1776 l = strlen(kw->kw)+2;
1777 fp->comment = (char *) fs_get((l+1) * sizeof(char));
1778 snprintf(fp->comment, l+1, "(%.*s)", (int) strlen(kw->kw), kw->kw);
1779 fp->comment[l] = '\0';
1782 fp->help = h_flag_user_flag;
1783 fp->flag = F_KEYWORD;
1784 fp->set = 0;
1785 fp->ukn = 0;
1786 fp++;
1790 if(mailbox_flags){
1791 fp->flag = F_COMMENT;
1792 fp->name = cpystr("");
1793 fp++;
1794 fp->flag = F_COMMENT;
1795 len = strlen(_("Other keywords in the mailbox that are not user-defined"));
1796 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1797 snprintf(fp->name, len+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined"));
1798 fp++;
1801 /* then the extra mailbox-defined keywords */
1802 if(mailbox_flags)
1803 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1804 char *q;
1806 q = stream_to_user_flag_name(state->mail_stream, i);
1807 if(q && q[0]){
1808 for(kw = ps_global->keywords; kw; kw = kw->next){
1809 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1810 break;
1814 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1815 fp->name = cpystr(q);
1816 fp->keyword = cpystr(q);
1817 fp->help = h_flag_user_flag;
1818 fp->flag = F_KEYWORD;
1819 fp->set = 0;
1820 fp->ukn = 0;
1821 fp++;
1825 flag_screen.flag_table = &ftbl;
1826 flag_screen.explanation = screen_text;
1828 if(MCMD_ISAGG(aopt)){
1829 if(!pseudo_selected(ps_global->mail_stream, msgmap)){
1830 free_flag_table(&ftbl);
1831 return rv;
1834 exp = flag_screen_text2;
1835 for(fp = ftbl; fp->name; fp++){
1836 fp->set = CMD_FLAG_UNKN; /* set to unknown */
1837 fp->ukn = TRUE;
1840 else if(state->mail_stream
1841 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
1842 && rawno <= state->mail_stream->nmsgs
1843 && (mc = mail_elt(state->mail_stream, rawno))){
1844 exp = flag_screen_text1;
1845 for(fp = &ftbl[0]; fp->name; fp++){
1846 fp->ukn = 0;
1847 if(fp->flag == F_KEYWORD){
1848 /* see if this keyword is defined for this message */
1849 fp->set = CMD_FLAG_CLEAR;
1850 if(user_flag_is_set(state->mail_stream,
1851 rawno, fp->keyword))
1852 fp->set = CMD_FLAG_SET;
1854 else if(fp->flag == F_FWD){
1855 /* see if forwarded keyword is defined for this message */
1856 fp->set = CMD_FLAG_CLEAR;
1857 if(user_flag_is_set(state->mail_stream,
1858 rawno, FORWARDED_FLAG))
1859 fp->set = CMD_FLAG_SET;
1861 else if(fp->flag != F_COMMENT)
1862 fp->set = ((fp->flag == F_SEEN && !mc->seen)
1863 || (fp->flag == F_DEL && mc->deleted)
1864 || (fp->flag == F_FLAG && mc->flagged)
1865 || (fp->flag == F_ANS && mc->answered))
1866 ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
1869 else{
1870 q_status_message(SM_ORDER | SM_DING, 3, 4,
1871 _("Error accessing message data"));
1872 free_flag_table(&ftbl);
1873 return rv;
1876 if(directly_to_maint_screen)
1877 goto the_maint_screen;
1879 #ifdef _WINDOWS
1880 if (mswin_usedialog ()) {
1881 if (!os_flagmsgdialog (&ftbl[0])){
1882 free_flag_table(&ftbl);
1883 return rv;
1886 else
1887 #endif
1889 int use_maint_screen;
1890 int keyword_shortcut = 0;
1892 use_maint_screen = F_ON(F_FLAG_SCREEN_DFLT, ps_global);
1894 if(!use_maint_screen){
1896 * We're going to call cmd_flag_prompt(). We need
1897 * to decide whether or not to offer the keyword setting
1898 * shortcut. We'll offer it if the user has the feature
1899 * enabled AND there are some possible keywords that could
1900 * be set.
1902 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global)){
1903 for(fp = &ftbl[0]; fp->name && !keyword_shortcut; fp++){
1904 if(fp->flag == F_KEYWORD){
1905 int first_char;
1906 ESCKEY_S *tp;
1908 first_char = (fp->name && fp->name[0])
1909 ? fp->name[0] : -2;
1910 if(isascii(first_char) && isupper(first_char))
1911 first_char = tolower((unsigned char) first_char);
1913 for(tp=flag_text_opt; tp->ch != -1; tp++)
1914 if(tp->ch == first_char)
1915 break;
1917 if(tp->ch == -1)
1918 keyword_shortcut++;
1923 use_maint_screen = !cmd_flag_prompt(state, &flag_screen,
1924 keyword_shortcut);
1927 the_maint_screen:
1928 if(use_maint_screen){
1929 for(p = &screen_text[0]; *exp; p++, exp++)
1930 *p = *exp;
1932 *p = NULL;
1934 directly_to_maint_screen = flag_maintenance_screen(state, &flag_screen);
1938 /* reaquire the elt pointer */
1939 mc = (state->mail_stream
1940 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
1941 && rawno <= state->mail_stream->nmsgs)
1942 ? mail_elt(state->mail_stream, rawno) : NULL;
1944 for(fp = ftbl; mc && fp->name; fp++){
1945 flags = -1;
1946 switch(fp->flag){
1947 case F_SEEN:
1948 if((!MCMD_ISAGG(aopt) && fp->set != !mc->seen)
1949 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
1950 flagit = "\\SEEN";
1951 if(fp->set){
1952 flags = 0L;
1953 unflagged = F_SEEN;
1955 else{
1956 flags = ST_SET;
1957 unflagged = F_UNSEEN;
1961 break;
1963 case F_ANS:
1964 if((!MCMD_ISAGG(aopt) && fp->set != mc->answered)
1965 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
1966 flagit = "\\ANSWERED";
1967 if(fp->set){
1968 flags = ST_SET;
1969 unflagged = F_UNANS;
1971 else{
1972 flags = 0L;
1973 unflagged = F_ANS;
1977 break;
1979 case F_DEL:
1980 if((!MCMD_ISAGG(aopt) && fp->set != mc->deleted)
1981 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
1982 flagit = "\\DELETED";
1983 if(fp->set){
1984 flags = ST_SET;
1985 unflagged = F_UNDEL;
1987 else{
1988 flags = 0L;
1989 unflagged = F_DEL;
1993 break;
1995 case F_FLAG:
1996 if((!MCMD_ISAGG(aopt) && fp->set != mc->flagged)
1997 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
1998 flagit = "\\FLAGGED";
1999 if(fp->set){
2000 flags = ST_SET;
2001 unflagged = F_UNFLAG;
2003 else{
2004 flags = 0L;
2005 unflagged = F_FLAG;
2009 break;
2011 case F_FWD :
2012 if(!MCMD_ISAGG(aopt)){
2013 /* see if forwarded is defined for this message */
2014 is_set = CMD_FLAG_CLEAR;
2015 if(user_flag_is_set(state->mail_stream,
2016 mn_m2raw(msgmap, mn_get_cur(msgmap)),
2017 FORWARDED_FLAG))
2018 is_set = CMD_FLAG_SET;
2021 if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2022 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2023 flagit = FORWARDED_FLAG;
2024 if(fp->set){
2025 flags = ST_SET;
2026 unflagged = F_UNFWD;
2028 else{
2029 flags = 0L;
2030 unflagged = F_FWD;
2034 break;
2036 case F_KEYWORD:
2037 if(!MCMD_ISAGG(aopt)){
2038 /* see if this keyword is defined for this message */
2039 is_set = CMD_FLAG_CLEAR;
2040 if(user_flag_is_set(state->mail_stream,
2041 mn_m2raw(msgmap, mn_get_cur(msgmap)),
2042 fp->keyword))
2043 is_set = CMD_FLAG_SET;
2046 if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2047 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2048 flagit = fp->keyword;
2049 keyword_array[0] = fp->keyword;
2050 keyword_array[1] = NULL;
2051 if(fp->set){
2052 flags = ST_SET;
2053 unflagged = F_UNKEYWORD;
2055 else{
2056 flags = 0L;
2057 unflagged = F_KEYWORD;
2061 break;
2063 default:
2064 break;
2067 flagged = 0L;
2068 if(flags >= 0L
2069 && (seq = currentf_sequence(state->mail_stream, msgmap,
2070 unflagged, &flagged, unflagged & F_DEL,
2071 (fp->flag == F_KEYWORD
2072 && unflagged == F_KEYWORD)
2073 ? keyword_array : NULL,
2074 (fp->flag == F_KEYWORD
2075 && unflagged == F_UNKEYWORD)
2076 ? keyword_array : NULL))){
2078 * For user keywords, we may have to create the flag in
2079 * the folder if it doesn't already exist and we are setting
2080 * it (as opposed to clearing it). Mail_flag will
2081 * do that for us, but it's failure isn't very friendly
2082 * error-wise. So we try to make it a little smoother.
2084 if(!(fp->flag == F_KEYWORD || fp->flag == F_FWD) || !fp->set
2085 || ((i=user_flag_index(state->mail_stream, flagit)) >= 0
2086 && i < NUSERFLAGS))
2087 mail_flag(state->mail_stream, seq, flagit, flags);
2088 else{
2089 /* trouble, see if we can add the user flag */
2090 if(state->mail_stream->kwd_create)
2091 mail_flag(state->mail_stream, seq, flagit, flags);
2092 else{
2093 trouble++;
2095 if(some_user_flags_defined(state->mail_stream))
2096 q_status_message(SM_ORDER, 3, 4,
2097 _("No more keywords allowed in this folder!"));
2098 else if(fp->flag == F_FWD)
2099 q_status_message(SM_ORDER, 3, 4,
2100 _("Cannot add keywords for this folder so cannot set Forwarded flag"));
2101 else
2102 q_status_message(SM_ORDER, 3, 4,
2103 _("Cannot add keywords for this folder"));
2107 fs_give((void **) &seq);
2108 if(flagged && !trouble){
2109 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%slagged%s%s%s%s%s message%s%s \"%s\"",
2110 (fp->set) ? "F" : "Unf",
2111 MCMD_ISAGG(aopt) ? " " : "",
2112 MCMD_ISAGG(aopt) ? long2string(flagged) : "",
2113 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2114 ? " (of " : "",
2115 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2116 ? comatose(mn_total_cur(msgmap)) : "",
2117 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2118 ? ")" : "",
2119 MCMD_ISAGG(aopt) ? plural(flagged) : " ",
2120 MCMD_ISAGG(aopt) ? "" : long2string(mn_get_cur(msgmap)),
2121 fp->name);
2122 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2123 q_status_message(SM_ORDER, 0, 2, answer = tmp_20k_buf);
2124 rv++;
2129 free_flag_table(&ftbl);
2131 if(directly_to_maint_screen)
2132 goto go_again;
2134 if(MCMD_ISAGG(aopt))
2135 restore_selected(msgmap);
2137 if(!answer)
2138 q_status_message(SM_ORDER, 0, 2, _("No flags changed."));
2140 return rv;
2144 /*----------------------------------------------------------------------
2145 Offer concise status line flag prompt
2147 Args: state -- Various satate info
2148 flags -- flags to offer setting
2150 Result: TRUE if flag to set specified in flags struct or FALSE otw
2152 ----*/
2154 cmd_flag_prompt(struct pine *state, struct flag_screen *flags, int allow_keyword_shortcuts)
2156 int r, setflag = 1, first_char;
2157 struct flag_table *fp;
2158 ESCKEY_S *ek;
2159 char *ftext, *ftext_not;
2160 static char *flag_text =
2161 N_("Flag New, Deleted, Answered, Forwarded or Important ? ");
2162 static char *flag_text_ak =
2163 N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? ");
2164 static char *flag_text_not =
2165 N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? ");
2166 static char *flag_text_ak_not =
2167 N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? ");
2169 if(allow_keyword_shortcuts){
2170 int cnt = 0;
2171 ESCKEY_S *dp, *sp, *tp;
2173 for(sp=flag_text_opt; sp->ch != -1; sp++)
2174 cnt++;
2176 for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++)
2177 if(fp->flag == F_KEYWORD)
2178 cnt++;
2180 /* set up an ESCKEY_S list which includes invisible keys for keywords */
2181 ek = (ESCKEY_S *) fs_get((cnt + 1) * sizeof(*ek));
2182 memset(ek, 0, (cnt+1) * sizeof(*ek));
2183 for(dp=ek, sp=flag_text_opt; sp->ch != -1; sp++, dp++)
2184 *dp = *sp;
2186 for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2187 if(fp->flag == F_KEYWORD){
2188 first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2189 if(isascii(first_char) && isupper(first_char))
2190 first_char = tolower((unsigned char) first_char);
2193 * Check to see if an earlier keyword in the list, or one of
2194 * the builtin system letters already uses this character.
2195 * If so, the first one wins.
2197 for(tp=ek; tp->ch != 0; tp++)
2198 if(tp->ch == first_char)
2199 break;
2201 if(tp->ch != 0)
2202 continue; /* skip it, already used that char */
2204 dp->ch = first_char;
2205 dp->rval = first_char;
2206 dp->name = "";
2207 dp->label = "";
2208 dp++;
2212 dp->ch = -1;
2213 ftext = _(flag_text_ak);
2214 ftext_not = _(flag_text_ak_not);
2216 else{
2217 ek = flag_text_opt;
2218 ftext = _(flag_text);
2219 ftext_not = _(flag_text_not);
2222 while(1){
2223 r = radio_buttons(setflag ? ftext : ftext_not,
2224 -FOOTER_ROWS(state), ek, '*', SEQ_EXCEPTION-1,
2225 NO_HELP, RB_NORM | RB_SEQ_SENSITIVE);
2227 * It is SEQ_EXCEPTION-1 just so that it is some value that isn't
2228 * being used otherwise. The keywords use up all the possible
2229 * letters, so a negative number is good, but it has to be different
2230 * from other negative return values.
2232 if(r == SEQ_EXCEPTION-1) /* ol'cancelrooney */
2233 return(TRUE);
2234 else if(r == 10) /* return and goto flag screen */
2235 return(FALSE);
2236 else if(r == '!') /* flip intention */
2237 setflag = !setflag;
2238 else
2239 break;
2242 for(fp = (flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2243 if(r == 'n' || r == '*' || r == 'd' || r == 'a' || r == 'f'){
2244 if((r == 'n' && fp->flag == F_SEEN)
2245 || (r == '*' && fp->flag == F_FLAG)
2246 || (r == 'd' && fp->flag == F_DEL)
2247 || (r == 'f' && fp->flag == F_FWD)
2248 || (r == 'a' && fp->flag == F_ANS)){
2249 fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2250 break;
2253 else if(allow_keyword_shortcuts && fp->flag == F_KEYWORD){
2254 first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2255 if(isascii(first_char) && isupper(first_char))
2256 first_char = tolower((unsigned char) first_char);
2258 if(r == first_char){
2259 fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2260 break;
2265 if(ek != flag_text_opt)
2266 fs_give((void **) &ek);
2268 return(TRUE);
2273 * (*ft) is an array of flag_table entries.
2275 void
2276 free_flag_table(struct flag_table **ft)
2278 struct flag_table *fp;
2280 if(ft && *ft){
2281 for(fp = (*ft); fp->name; fp++){
2282 if(fp->name)
2283 fs_give((void **) &fp->name);
2285 if(fp->keyword)
2286 fs_give((void **) &fp->keyword);
2288 if(fp->comment)
2289 fs_give((void **) &fp->comment);
2292 fs_give((void **) ft);
2297 /*----------------------------------------------------------------------
2298 Execute REPLY message command
2300 Args: state -- Various satate info
2301 msgmap -- map of c-client to local message numbers
2303 Result: reply sent or not
2305 ----*/
2307 cmd_reply(struct pine *state, MSGNO_S *msgmap, int aopt)
2309 int rv = 0;
2311 if(any_messages(msgmap, NULL, "to Reply to")){
2312 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2313 return rv;
2315 rv = reply(state, NULL);
2317 if(MCMD_ISAGG(aopt))
2318 restore_selected(msgmap);
2320 state->mangled_screen = 1;
2323 return rv;
2327 /*----------------------------------------------------------------------
2328 Execute FORWARD message command
2330 Args: state -- Various satate info
2331 msgmap -- map of c-client to local message numbers
2333 Result: selected message[s] forwarded or not
2335 ----*/
2337 cmd_forward(struct pine *state, MSGNO_S *msgmap, int aopt)
2339 int rv = 0;
2341 if(any_messages(msgmap, NULL, "to Forward")){
2342 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2343 return rv;
2345 rv = forward(state, NULL);
2347 if(MCMD_ISAGG(aopt))
2348 restore_selected(msgmap);
2350 state->mangled_screen = 1;
2353 return rv;
2357 /*----------------------------------------------------------------------
2358 Execute BOUNCE message command
2360 Args: state -- Various satate info
2361 msgmap -- map of c-client to local message numbers
2362 aopt -- aggregate options
2364 Result: selected message[s] bounced or not
2366 ----*/
2368 cmd_bounce(struct pine *state, MSGNO_S *msgmap, int aopt)
2370 int rv = 0;
2371 ACTION_S *role = NULL;
2373 if(any_messages(msgmap, NULL, "to Bounce")){
2374 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2375 return rv;
2377 if(MCMD_ISAGG(aopt)){ /* check for possible role */
2378 PAT_STATE pstate;
2379 int action;
2381 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
2382 static ESCKEY_S yesno_opts[] = {
2383 {'y', 'y', "Y", N_("Yes")},
2384 {'n', 'n', "N", N_("No")},
2385 {-1, 0, NULL, NULL}
2388 action = radio_buttons(_("Bounce messages using a role? "),
2389 -FOOTER_ROWS(state), yesno_opts,
2390 'y', 'x', h_role_compose, RB_NORM);
2391 if(action == 'y'){
2392 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
2394 redraw = state->redrawer;
2395 state->redrawer = NULL;
2396 prev_screen = state->prev_screen;
2397 role = NULL;
2398 state->next_screen = SCREEN_FUN_NULL;
2400 if(role_select_screen(state, &role, MC_BOUNCE) < 0)
2401 cmd_cancelled(_("Bounce"));
2402 else{
2403 if(role)
2404 role = combine_inherited_role(role);
2405 else{
2406 role = (ACTION_S *) fs_get(sizeof(*role));
2407 memset((void *) role, 0, sizeof(*role));
2408 role->nick = cpystr("Default Role");
2412 if(redraw)
2413 (*redraw)();
2415 state->next_screen = prev_screen;
2416 state->redrawer = redraw;
2417 state->mangled_screen = 1;
2422 rv = bounce(state, role);
2424 if(role)
2425 free_action(&role);
2427 if(MCMD_ISAGG(aopt))
2428 restore_selected(msgmap);
2430 state->mangled_footer = 1;
2433 return rv;
2437 /*----------------------------------------------------------------------
2438 Execute save message command: prompt for folder and call function to save
2440 Args: screen_line -- Line on the screen to prompt on
2441 message -- The MESSAGECACHE entry of message to save
2443 Result: The folder lister can be called to make selection; mangled screen set
2445 This does the prompting for the folder name to save to, possibly calling
2446 up the folder display for selection of folder by user.
2447 ----*/
2449 cmd_save(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
2451 char newfolder[MAILTMPLEN], nmsgs[32], *nick;
2452 int we_cancel = 0, rv = 0, save_flags;
2453 long i, raw;
2454 CONTEXT_S *cntxt = NULL;
2455 ENVELOPE *e = NULL;
2456 SaveDel del = DontAsk;
2457 SavePreserveOrder pre = DontAskPreserve;
2459 dprint((4, "\n - saving message -\n"));
2461 if(MCMD_ISAGG(aopt) && !pseudo_selected(stream, msgmap))
2462 return rv;
2464 state->ugly_consider_advancing_bit = 0;
2465 if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM, state)
2466 && msgno_any_deletedparts(stream, msgmap)
2467 && want_to(_("Saved copy will NOT include entire message! Continue"),
2468 'y', 'n', NO_HELP, WT_FLUSH_IN | WT_SEQ_SENSITIVE) != 'y'){
2469 restore_selected(msgmap);
2470 cmd_cancelled("Save message");
2471 return rv;
2474 raw = mn_m2raw(msgmap, mn_get_cur(msgmap));
2476 if(mn_total_cur(msgmap) <= 1L){
2477 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld ", mn_get_cur(msgmap));
2478 nmsgs[sizeof(nmsgs)-1] = '\0';
2479 e = pine_mail_fetchstructure(stream, raw, NULL);
2480 if(!e) {
2481 q_status_message(SM_ORDER | SM_DING, 3, 4,
2482 _("Can't save message. Error accessing folder"));
2483 restore_selected(msgmap);
2484 return rv;
2487 else{
2488 snprintf(nmsgs, sizeof(nmsgs), "%s msgs ", comatose(mn_total_cur(msgmap)));
2489 nmsgs[sizeof(nmsgs)-1] = '\0';
2491 /* e is just used to get a default save folder from the first msg */
2492 e = pine_mail_fetchstructure(stream,
2493 mn_m2raw(msgmap, mn_first_cur(msgmap)),
2494 NULL);
2497 del = (!READONLY_FOLDER(stream) && F_OFF(F_SAVE_WONT_DELETE, ps_global))
2498 ? Del : NoDel;
2499 if(mn_total_cur(msgmap) > 1L)
2500 pre = F_OFF(F_AGG_SEQ_COPY, ps_global) ? Preserve : NoPreserve;
2501 else
2502 pre = DontAskPreserve;
2504 if(save_prompt(state, &cntxt, newfolder, sizeof(newfolder), nmsgs, e,
2505 raw, NULL, &del, &pre)){
2507 if(ps_global && ps_global->ttyo){
2508 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2509 ps_global->mangled_footer = 1;
2512 save_flags = SV_FIX_DELS;
2513 if(pre == RetPreserve)
2514 save_flags |= SV_PRESERVE;
2515 if(del == RetDel)
2516 save_flags |= SV_DELETE;
2517 if(ps_global->context_list == cntxt && !strucmp(newfolder, ps_global->inbox_name))
2518 save_flags |= SV_INBOXWOCNTXT;
2520 we_cancel = busy_cue(_("Saving"), NULL, 1);
2521 i = save(state, stream, cntxt, newfolder, msgmap, save_flags);
2522 if(we_cancel)
2523 cancel_busy_cue(0);
2525 if(i == mn_total_cur(msgmap)){
2526 rv++;
2527 if(mn_total_cur(msgmap) <= 1L){
2528 int need, avail = ps_global->ttyo->screen_cols - 2;
2529 int lennick, lenfldr;
2531 if(cntxt
2532 && ps_global->context_list->next
2533 && context_isambig(newfolder)){
2534 lennick = MIN(strlen(cntxt->nickname), 500);
2535 lenfldr = MIN(strlen(newfolder), 500);
2536 need = 27 + strlen(long2string(mn_get_cur(msgmap))) +
2537 lenfldr + lennick;
2538 if(need > avail){
2539 if(lennick > 10){
2540 need -= MIN(lennick-10, need-avail);
2541 lennick -= MIN(lennick-10, need-avail);
2544 if(need > avail && lenfldr > 10)
2545 lenfldr -= MIN(lenfldr-10, need-avail);
2548 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2549 "Message %s copied to \"%s\" in <%s>",
2550 long2string(mn_get_cur(msgmap)),
2551 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2552 lenfldr, MidDots),
2553 short_str(cntxt->nickname,
2554 (char *)(tmp_20k_buf+2000), 1000,
2555 lennick, EndDots));
2556 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2558 else if((nick=folder_is_target_of_nick(newfolder, cntxt)) != NULL){
2559 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2560 "Message %s copied to \"%s\"",
2561 long2string(mn_get_cur(msgmap)),
2562 nick);
2563 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2565 else{
2566 char *f = " folder";
2568 lenfldr = MIN(strlen(newfolder), 500);
2569 need = 28 + strlen(long2string(mn_get_cur(msgmap))) +
2570 lenfldr;
2571 if(need > avail){
2572 need -= strlen(f);
2573 f = "";
2574 if(need > avail && lenfldr > 10)
2575 lenfldr -= MIN(lenfldr-10, need-avail);
2578 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
2579 "Message %s copied to%s \"%s\"",
2580 long2string(mn_get_cur(msgmap)), f,
2581 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2582 lenfldr, MidDots));
2583 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2586 else{
2587 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s messages saved",
2588 comatose(mn_total_cur(msgmap)));
2589 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2592 if(del == RetDel){
2593 strncat(tmp_20k_buf, " and deleted", SIZEOF_20KBUF-strlen(tmp_20k_buf)-1);
2594 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2597 q_status_message(SM_ORDER, 0, 3, tmp_20k_buf);
2599 if(!MCMD_ISAGG(aopt) && F_ON(F_SAVE_ADVANCES, state)){
2600 if(sp_new_mail_count(stream))
2601 process_filter_patterns(stream, msgmap,
2602 sp_new_mail_count(stream));
2604 mn_inc_cur(stream, msgmap,
2605 (in_index == View && THREADING()
2606 && sp_viewing_a_thread(stream))
2607 ? MH_THISTHD
2608 : (in_index == View)
2609 ? MH_ANYTHD : MH_NONE);
2612 state->ugly_consider_advancing_bit = 1;
2616 if(MCMD_ISAGG(aopt)) /* straighten out fakes */
2617 restore_selected(msgmap);
2619 if(del == RetDel)
2620 update_titlebar_status(); /* make sure they see change */
2622 return rv;
2626 void
2627 role_compose(struct pine *state)
2629 int action;
2631 if(F_ON(F_ALT_ROLE_MENU, state) && mn_get_total(state->msgmap) > 0L){
2632 PAT_STATE pstate;
2634 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
2635 action = radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
2636 -FOOTER_ROWS(state), choose_action,
2637 'c', 'x', h_role_compose, RB_NORM);
2639 else{
2640 q_status_message(SM_ORDER, 0, 3,
2641 _("No roles available. Use Setup/Rules to add roles."));
2642 return;
2645 else
2646 action = 'c';
2648 if(action == 'c' || action == 'r' || action == 'f' || action == 'b'){
2649 ACTION_S *role = NULL;
2650 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
2652 redraw = state->redrawer;
2653 state->redrawer = NULL;
2654 prev_screen = state->prev_screen;
2655 role = NULL;
2656 state->next_screen = SCREEN_FUN_NULL;
2658 /* Setup role */
2659 if(role_select_screen(state, &role,
2660 action == 'f' ? MC_FORWARD :
2661 action == 'r' ? MC_REPLY :
2662 action == 'b' ? MC_BOUNCE :
2663 action == 'c' ? MC_COMPOSE : 0) < 0){
2664 cmd_cancelled(action == 'f' ? _("Forward") :
2665 action == 'r' ? _("Reply") :
2666 action == 'c' ? _("Composition") : _("Bounce"));
2667 state->next_screen = prev_screen;
2668 state->redrawer = redraw;
2669 state->mangled_screen = 1;
2671 else{
2673 * If default role was selected (NULL) we need to make
2674 * up a role which won't do anything, but will cause
2675 * compose_mail to think there's already a role so that
2676 * it won't try to confirm the default.
2678 if(role)
2679 role = combine_inherited_role(role);
2680 else{
2681 role = (ACTION_S *) fs_get(sizeof(*role));
2682 memset((void *) role, 0, sizeof(*role));
2683 role->nick = cpystr("Default Role");
2686 state->redrawer = NULL;
2687 switch(action){
2688 case 'c':
2689 compose_mail(NULL, NULL, role, NULL, NULL);
2690 break;
2692 case 'r':
2693 (void) reply(state, role);
2694 break;
2696 case 'f':
2697 (void) forward(state, role);
2698 break;
2700 case 'b':
2701 (void) bounce(state, role);
2702 break;
2705 if(role)
2706 free_action(&role);
2708 state->next_screen = prev_screen;
2709 state->redrawer = redraw;
2710 state->mangled_screen = 1;
2716 /*----------------------------------------------------------------------
2717 Do the dirty work of prompting the user for a folder name
2719 Args:
2720 nfldr should be a buffer at least MAILTMPLEN long
2721 dela -- a pointer to a SaveDel. If it is
2722 DontAsk on input, don't offer Delete prompt
2723 Del on input, offer Delete command with default of Delete
2724 NoDel NoDelete
2725 RetDel and RetNoDel are return values
2728 Result:
2730 ----*/
2732 save_prompt(struct pine *state, CONTEXT_S **cntxt, char *nfldr, size_t len_nfldr,
2733 char *nmsgs, ENVELOPE *env, long int rawmsgno, char *section,
2734 SaveDel *dela, SavePreserveOrder *prea)
2736 int rc, ku = -1, n, flags, last_rc = 0, saveable_count = 0, done = 0;
2737 int delindex, preindex, r;
2738 char prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN];
2739 char *buf = tmp_20k_buf;
2740 char shortbuf[200];
2741 char *folder;
2742 HelpType help;
2743 SaveDel del = DontAsk;
2744 SavePreserveOrder pre = DontAskPreserve;
2745 char *deltext = NULL;
2746 static HISTORY_S *history = NULL;
2747 CONTEXT_S *tc;
2748 ESCKEY_S ekey[10];
2750 if(!cntxt)
2751 alpine_panic("no context ptr in save_prompt");
2753 init_hist(&history, HISTSIZE);
2755 if(!(folder = save_get_default(state, env, rawmsgno, section, cntxt)))
2756 return(0); /* message expunged! */
2758 /* how many context's can be saved to... */
2759 for(tc = state->context_list; tc; tc = tc->next)
2760 if(!NEWS_TEST(tc))
2761 saveable_count++;
2763 /* set up extra command option keys */
2764 rc = 0;
2765 ekey[rc].ch = ctrl('T');
2766 ekey[rc].rval = 2;
2767 ekey[rc].name = "^T";
2768 /* TRANSLATORS: command means go to Folders list */
2769 ekey[rc++].label = N_("To Fldrs");
2771 if(saveable_count > 1){
2772 ekey[rc].ch = ctrl('P');
2773 ekey[rc].rval = 10;
2774 ekey[rc].name = "^P";
2775 ekey[rc++].label = N_("Prev Collection");
2777 ekey[rc].ch = ctrl('N');
2778 ekey[rc].rval = 11;
2779 ekey[rc].name = "^N";
2780 ekey[rc++].label = N_("Next Collection");
2783 if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
2784 ekey[rc].ch = TAB;
2785 ekey[rc].rval = 12;
2786 ekey[rc].name = "TAB";
2787 /* TRANSLATORS: command asks alpine to complete the name when tab is typed */
2788 ekey[rc++].label = N_("Complete");
2791 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
2792 ekey[rc].ch = ctrl('X');
2793 ekey[rc].rval = 14;
2794 ekey[rc].name = "^X";
2795 /* TRANSLATORS: list all the matches */
2796 ekey[rc++].label = N_("ListMatches");
2799 if(dela && (*dela == NoDel || *dela == Del)){
2800 ekey[rc].ch = ctrl('R');
2801 ekey[rc].rval = 15;
2802 ekey[rc].name = "^R";
2803 delindex = rc++;
2804 del = *dela;
2807 if(prea && (*prea == NoPreserve || *prea == Preserve)){
2808 ekey[rc].ch = ctrl('W');
2809 ekey[rc].rval = 16;
2810 ekey[rc].name = "^W";
2811 preindex = rc++;
2812 pre = *prea;
2815 if(saveable_count > 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2816 ekey[rc].ch = KEY_UP;
2817 ekey[rc].rval = 10;
2818 ekey[rc].name = "";
2819 ekey[rc++].label = "";
2821 ekey[rc].ch = KEY_DOWN;
2822 ekey[rc].rval = 11;
2823 ekey[rc].name = "";
2824 ekey[rc++].label = "";
2826 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2827 ekey[rc].ch = KEY_UP;
2828 ekey[rc].rval = 30;
2829 ekey[rc].name = "";
2830 ku = rc;
2831 ekey[rc++].label = "";
2833 ekey[rc].ch = KEY_DOWN;
2834 ekey[rc].rval = 31;
2835 ekey[rc].name = "";
2836 ekey[rc++].label = "";
2839 ekey[rc].ch = -1;
2841 *nfldr = '\0';
2842 help = NO_HELP;
2843 while(!done){
2844 /* only show collection number if more than one available */
2845 if(ps_global->context_list->next)
2846 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder in <%s> [%s] : ",
2847 deltext ? deltext : "",
2848 nmsgs,
2849 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2850 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2851 else
2852 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder [%s] : ",
2853 deltext ? deltext : "",
2854 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2856 prompt[sizeof(prompt)-1] = '\0';
2859 * If the prompt won't fit, try removing deltext.
2861 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && deltext){
2862 if(ps_global->context_list->next)
2863 snprintf(prompt, sizeof(prompt), "SAVE %sto folder in <%s> [%s] : ",
2864 nmsgs,
2865 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2866 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2867 else
2868 snprintf(prompt, sizeof(prompt), "SAVE %sto folder [%s] : ",
2869 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2871 prompt[sizeof(prompt)-1] = '\0';
2875 * If the prompt still won't fit, remove the extra info contained
2876 * in nmsgs.
2878 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && *nmsgs){
2879 if(ps_global->context_list->next)
2880 snprintf(prompt, sizeof(prompt), "SAVE to folder in <%s> [%s] : ",
2881 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2882 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2883 else
2884 snprintf(prompt, sizeof(prompt), "SAVE to folder [%s] : ",
2885 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2887 prompt[sizeof(prompt)-1] = '\0';
2890 if(del != DontAsk)
2891 ekey[delindex].label = (del == NoDel) ? "Delete" : "No Delete";
2893 if(pre != DontAskPreserve)
2894 ekey[preindex].label = (pre == NoPreserve) ? "Preserve Order" : "Any Order";
2896 if(ku >= 0){
2897 if(items_in_hist(history) > 1){
2898 ekey[ku].name = HISTORY_UP_KEYNAME;
2899 ekey[ku].label = HISTORY_KEYLABEL;
2900 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
2901 ekey[ku+1].label = HISTORY_KEYLABEL;
2903 else{
2904 ekey[ku].name = "";
2905 ekey[ku].label = "";
2906 ekey[ku+1].name = "";
2907 ekey[ku+1].label = "";
2911 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
2912 rc = optionally_enter(nfldr, -FOOTER_ROWS(state), 0, len_nfldr,
2913 prompt, ekey, help, &flags);
2915 switch(rc){
2916 case -1 :
2917 q_status_message(SM_ORDER | SM_DING, 3, 3,
2918 _("Error reading folder name"));
2919 done--;
2920 break;
2922 case 0 :
2923 removing_trailing_white_space(nfldr);
2924 removing_leading_white_space(nfldr);
2926 if(*nfldr || *folder){
2927 char *p, *name, *fullname = NULL;
2928 int exists, breakout = FALSE;
2930 if(!*nfldr){
2931 strncpy(nfldr, folder, len_nfldr-1);
2932 nfldr[len_nfldr-1] = '\0';
2935 save_hist(history, nfldr, 0, (void *) *cntxt);
2937 if(!(name = folder_is_nick(nfldr, FOLDERS(*cntxt), 0)))
2938 name = nfldr;
2940 if(update_folder_spec(expanded, sizeof(expanded), name)){
2941 strncpy(name = nfldr, expanded, len_nfldr-1);
2942 nfldr[len_nfldr-1] = '\0';
2945 exists = folder_name_exists(*cntxt, name, &fullname);
2947 if(exists == FEX_ERROR){
2948 q_status_message1(SM_ORDER, 0, 3,
2949 _("Problem accessing folder \"%s\""),
2950 nfldr);
2951 done--;
2953 else{
2954 if(fullname){
2955 strncpy(name = nfldr, fullname, len_nfldr-1);
2956 nfldr[len_nfldr-1] = '\0';
2957 fs_give((void **) &fullname);
2958 breakout = TRUE;
2961 if(exists & FEX_ISFILE){
2962 done++;
2964 else if((exists & FEX_ISDIR)){
2965 char tmp[MAILTMPLEN];
2967 tc = *cntxt;
2968 if(breakout){
2969 CONTEXT_S *fake_context;
2970 size_t l;
2972 strncpy(tmp, name, sizeof(tmp));
2973 tmp[sizeof(tmp)-2-1] = '\0';
2974 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
2975 if(l < sizeof(tmp)){
2976 tmp[l] = tc->dir->delim;
2977 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
2980 else
2981 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
2983 tmp[sizeof(tmp)-1] = '\0';
2985 fake_context = new_context(tmp, 0);
2986 nfldr[0] = '\0';
2987 done = display_folder_list(&fake_context, nfldr,
2988 1, folders_for_save);
2989 free_context(&fake_context);
2991 else if(tc->dir->delim
2992 && (p = strrindex(name, tc->dir->delim))
2993 && *(p+1) == '\0')
2994 done = display_folder_list(cntxt, nfldr,
2995 1, folders_for_save);
2996 else{
2997 q_status_message1(SM_ORDER, 3, 3,
2998 _("\"%s\" is a directory"), name);
2999 if(tc->dir->delim
3000 && !((p=strrindex(name, tc->dir->delim)) && *(p+1) == '\0')){
3001 strncpy(tmp, name, sizeof(tmp));
3002 tmp[sizeof(tmp)-1] = '\0';
3003 snprintf(nfldr, len_nfldr, "%s%c", tmp, tc->dir->delim);
3007 else{ /* Doesn't exist, create! */
3008 if((fullname = folder_as_breakout(*cntxt, name)) != NULL){
3009 strncpy(name = nfldr, fullname, len_nfldr-1);
3010 nfldr[len_nfldr-1] = '\0';
3011 fs_give((void **) &fullname);
3014 switch(create_for_save(*cntxt, name)){
3015 case 1 : /* success */
3016 done++;
3017 break;
3018 case 0 : /* error */
3019 case -1 : /* declined */
3020 done--;
3021 break;
3026 break;
3028 /* else fall thru like they cancelled */
3030 case 1 :
3031 cmd_cancelled("Save message");
3032 done--;
3033 break;
3035 case 2 :
3036 r = display_folder_list(cntxt, nfldr, 0, folders_for_save);
3038 if(r)
3039 done++;
3041 break;
3043 case 3 :
3044 helper(h_save, _("HELP FOR SAVE"), HLPD_SIMPLE);
3045 ps_global->mangled_screen = 1;
3046 break;
3048 case 4 : /* redraw */
3049 break;
3051 case 10 : /* previous collection */
3052 for(tc = (*cntxt)->prev; tc; tc = tc->prev)
3053 if(!NEWS_TEST(tc))
3054 break;
3056 if(!tc){
3057 CONTEXT_S *tc2;
3059 for(tc2 = (tc = (*cntxt))->next; tc2; tc2 = tc2->next)
3060 if(!NEWS_TEST(tc2))
3061 tc = tc2;
3064 *cntxt = tc;
3065 break;
3067 case 11 : /* next collection */
3068 tc = (*cntxt);
3071 if(((*cntxt) = (*cntxt)->next) == NULL)
3072 (*cntxt) = ps_global->context_list;
3073 while(NEWS_TEST(*cntxt) && (*cntxt) != tc);
3074 break;
3076 case 12 : /* file name completion */
3077 if(!folder_complete(*cntxt, nfldr, len_nfldr, &n)){
3078 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
3079 r = display_folder_list(cntxt, nfldr, 1, folders_for_save);
3080 if(r)
3081 done++; /* bingo! */
3082 else
3083 rc = 0; /* burn last_rc */
3085 else
3086 Writechar(BELL, 0);
3089 break;
3091 case 14 : /* file name completion */
3092 r = display_folder_list(cntxt, nfldr, 2, folders_for_save);
3093 if(r)
3094 done++; /* bingo! */
3095 else
3096 rc = 0; /* burn last_rc */
3098 break;
3100 case 15 : /* Delete / No Delete */
3101 del = (del == NoDel) ? Del : NoDel;
3102 deltext = (del == NoDel) ? " (no delete)" : " (and delete)";
3103 break;
3105 case 16 : /* Preserve Order or not */
3106 pre = (pre == NoPreserve) ? Preserve : NoPreserve;
3107 break;
3109 case 30 :
3110 if((p = get_prev_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3111 strncpy(nfldr, p, len_nfldr);
3112 nfldr[len_nfldr-1] = '\0';
3113 if(history->hist[history->curindex])
3114 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3116 else
3117 Writechar(BELL, 0);
3119 break;
3121 case 31 :
3122 if((p = get_next_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3123 strncpy(nfldr, p, len_nfldr);
3124 nfldr[len_nfldr-1] = '\0';
3125 if(history->hist[history->curindex])
3126 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3128 else
3129 Writechar(BELL, 0);
3131 break;
3133 default :
3134 alpine_panic("Unhandled case");
3135 break;
3138 last_rc = rc;
3141 ps_global->mangled_footer = 1;
3143 if(done < 0)
3144 return(0);
3146 if(*nfldr){
3147 strncpy(ps_global->last_save_folder, nfldr, sizeof(ps_global->last_save_folder)-1);
3148 ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0';
3149 if(*cntxt)
3150 ps_global->last_save_context = *cntxt;
3152 else{
3153 strncpy(nfldr, folder, len_nfldr-1);
3154 nfldr[len_nfldr-1] = '\0';
3157 /* nickname? Copy real name to nfldr */
3158 if(*cntxt
3159 && context_isambig(nfldr)
3160 && (p = folder_is_nick(nfldr, FOLDERS(*cntxt), 0))){
3161 strncpy(nfldr, p, len_nfldr-1);
3162 nfldr[len_nfldr-1] = '\0';
3165 if(dela && (*dela == NoDel || *dela == Del))
3166 *dela = (del == NoDel) ? RetNoDel : RetDel;
3168 if(prea && (*prea == NoPreserve || *prea == Preserve))
3169 *prea = (pre == NoPreserve) ? RetNoPreserve : RetPreserve;
3171 return(1);
3175 /*----------------------------------------------------------------------
3176 Prompt user before implicitly creating a folder for saving
3178 Args: context - context to create folder in
3179 folder - folder name to create
3181 Result: 1 on proceed, -1 on decline, 0 on error
3183 ----*/
3185 create_for_save_prompt(CONTEXT_S *context, char *folder, int sequence_sensitive)
3187 if(context && ps_global->context_list->next && context_isambig(folder)){
3188 if(context->use & CNTXT_INCMNG){
3189 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3190 _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
3191 folder, (strlen(folder) > 15) ? "..." : "");
3192 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
3193 return(0); /* error */
3196 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3197 _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
3198 folder, (strlen(folder) > 15) ? "..." : "",
3199 context->nickname,
3200 (strlen(context->nickname) > 15) ? "..." : "");
3202 else
3203 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
3204 _("Folder \"%.40s%s\" doesn't exist. Create"),
3205 folder, strlen(folder) > 40 ? "..." : "");
3207 if(want_to(tmp_20k_buf, 'y', 'n',
3208 NO_HELP, (sequence_sensitive) ? WT_SEQ_SENSITIVE : WT_NORM) != 'y'){
3209 cmd_cancelled("Save message");
3210 return(-1);
3213 return(1);
3218 /*----------------------------------------------------------------------
3219 Expunge messages from current folder
3221 Args: state -- pointer to struct holding a bunch of pine state
3222 msgmap -- table mapping msg nums to c-client sequence nums
3223 qline -- screen line to ask questions on
3224 agg -- boolean indicating we're to operate on aggregate set
3226 Result:
3227 ----*/
3229 cmd_expunge(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int agg)
3231 long del_count, prefilter_del_count;
3232 int we_cancel = 0, rv = 0;
3233 char prompt[MAX_SCREEN_COLS+1];
3234 char *sequence;
3235 COLOR_PAIR *lastc = NULL;
3237 dprint((2, "\n - expunge -\n"));
3239 del_count = 0;
3241 sequence = MCMD_ISAGG(agg) ? selected_sequence(stream, msgmap, NULL, 0) : NULL;
3243 if(MCMD_ISAGG(agg)){
3244 long i;
3245 MESSAGECACHE *mc;
3246 for(i = 1L; i <= stream->nmsgs; i++){
3247 if((mc = mail_elt(stream, i)) != NULL
3248 && mc->sequence && mc->deleted)
3249 del_count++;
3251 if(del_count == 0){
3252 q_status_message(SM_ORDER, 0, 4,
3253 _("No selected messages are deleted"));
3254 return 0;
3256 } else {
3257 if(!any_messages(msgmap, NULL, "to Expunge"))
3258 return rv;
3261 if(IS_NEWS(stream) && stream->rdonly){
3262 if(!MCMD_ISAGG(agg))
3263 del_count = count_flagged(stream, F_DEL);
3264 if(del_count > 0L){
3265 state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3266 snprintf(prompt, sizeof(prompt), "Exclude %ld message%s from %.*s", del_count,
3267 plural(del_count), MAX_SCREEN_COLS+1-40,
3268 pretty_fn(state->cur_folder));
3269 prompt[sizeof(prompt)-1] = '\0';
3270 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3271 || (F_ON(F_AUTO_EXPUNGE, state)
3272 && (state->context_current
3273 && (state->context_current->use & CNTXT_INCMNG))
3274 && context_isambig(state->cur_folder))
3275 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
3277 if(F_ON(F_NEWS_CROSS_DELETE, state))
3278 cross_delete_crossposts(stream);
3280 msgno_exclude_deleted(stream, msgmap, sequence);
3281 clear_index_cache(stream, 0);
3284 * This is kind of surprising at first. For most sort
3285 * orders, if the whole set is sorted, then any subset
3286 * is also sorted. Not so for threaded sorts.
3288 if(SORT_IS_THREADED(msgmap))
3289 refresh_sort(stream, msgmap, SRT_NON);
3291 state->mangled_body = 1;
3292 state->mangled_header = 1;
3293 q_status_message2(SM_ORDER, 0, 4,
3294 "%s message%s excluded",
3295 long2string(del_count),
3296 plural(del_count));
3298 else
3299 any_messages(NULL, NULL, "Excluded");
3301 else
3302 any_messages(NULL, "deleted", "to Exclude");
3304 return del_count;
3306 else if(READONLY_FOLDER(stream)){
3307 q_status_message(SM_ORDER, 0, 4,
3308 _("Can't expunge. Folder is read-only"));
3309 return del_count;
3312 if(!MCMD_ISAGG(agg)){
3313 prefilter_del_count = count_flagged(stream, F_DEL|F_NOFILT);
3314 mail_expunge_prefilter(stream, MI_NONE);
3315 del_count = count_flagged(stream, F_DEL|F_NOFILT);
3318 if(del_count != 0){
3319 int ret;
3320 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3321 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3322 snprintf(prompt, sizeof(prompt), "Expunge %ld message%s from %.*s", del_count,
3323 plural(del_count), MAX_SCREEN_COLS+1-40,
3324 pretty_fn((char *) fname));
3325 if(fname) fs_give((void **)&fname);
3326 prompt[sizeof(prompt)-1] = '\0';
3327 state->mangled_footer = 1;
3329 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3330 || (F_ON(F_AUTO_EXPUNGE, state)
3331 && ((!strucmp(state->cur_folder,state->inbox_name))
3332 || (state->context_current->use & CNTXT_INCMNG))
3333 && context_isambig(state->cur_folder))
3334 || (ret=want_to(prompt, 'y', 0, NO_HELP, WT_NORM)) == 'y')
3335 ret = 'y';
3337 if(ret == 'x')
3338 cmd_cancelled("Expunge");
3340 if(ret != 'y')
3341 return 0;
3344 dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
3345 mn_get_total(msgmap), mn_get_cur(msgmap), del_count));
3347 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3348 state->VAR_TITLE_BACK_COLOR,
3349 PSC_REV|PSC_RET);
3351 PutLine0(0, 0, "**"); /* indicate delay */
3353 if(lastc){
3354 (void)pico_set_colorp(lastc, PSC_NONE);
3355 free_color_pair(&lastc);
3358 MoveCursor(state->ttyo->screen_rows -FOOTER_ROWS(state), 0);
3359 fflush(stdout);
3361 we_cancel = busy_cue(_("Expunging"), NULL, 1);
3363 if(cmd_expunge_work(stream, msgmap, sequence))
3364 state->mangled_body = 1;
3366 if(sequence)
3367 fs_give((void **)&sequence);
3369 if(we_cancel)
3370 cancel_busy_cue((sp_expunge_count(stream) > 0) ? 0 : -1);
3372 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3373 state->VAR_TITLE_BACK_COLOR,
3374 PSC_REV|PSC_RET);
3375 PutLine0(0, 0, " "); /* indicate delay's over */
3377 if(lastc){
3378 (void)pico_set_colorp(lastc, PSC_NONE);
3379 free_color_pair(&lastc);
3382 fflush(stdout);
3384 if(sp_expunge_count(stream) > 0){
3386 * This is kind of surprising at first. For most sort
3387 * orders, if the whole set is sorted, then any subset
3388 * is also sorted. Not so for threaded sorts.
3390 if(SORT_IS_THREADED(msgmap))
3391 refresh_sort(stream, msgmap, SRT_NON);
3393 else{
3394 if(del_count){
3395 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3396 q_status_message1(SM_ORDER, 0, 3,
3397 _("No messages expunged from folder \"%s\""),
3398 pretty_fn((char *) fname));
3399 if(fname) fs_give((void **)&fname);
3401 else if(!prefilter_del_count)
3402 q_status_message(SM_ORDER, 0, 3,
3403 _("No messages marked deleted. No messages expunged."));
3405 return del_count;
3409 /*----------------------------------------------------------------------
3410 Expunge_and_close callback to prompt user for confirmation
3412 Args: stream -- folder's stream
3413 folder -- name of folder containing folders
3414 deleted -- number of del'd msgs
3416 Result: 'y' to continue with expunge
3417 ----*/
3419 expunge_prompt(MAILSTREAM *stream, char *folder, long int deleted)
3421 long max_folder;
3422 int charcnt = 0;
3423 char prompt_b[MAX_SCREEN_COLS+1], temp[MAILTMPLEN+1], buff[MAX_SCREEN_COLS+1];
3424 char *short_folder_name;
3426 if(deleted == 1)
3427 charcnt = 1;
3428 else{
3429 snprintf(temp, sizeof(temp), "%ld", deleted);
3430 charcnt = strlen(temp)+1;
3433 max_folder = MAX(1,MAXPROMPT - (36+charcnt));
3434 strncpy(temp, folder, sizeof(temp));
3435 temp[sizeof(temp)-1] = '\0';
3436 short_folder_name = short_str(temp,buff,sizeof(buff),max_folder,FrontDots);
3438 if(IS_NEWS(stream))
3439 snprintf(prompt_b, sizeof(prompt_b),
3440 "Delete %s%ld message%s from \"%s\"",
3441 (deleted > 1L) ? "all " : "", deleted,
3442 plural(deleted), short_folder_name);
3443 else
3444 snprintf(prompt_b, sizeof(prompt_b),
3445 "Expunge the %ld deleted message%s from \"%s\"",
3446 deleted, deleted == 1 ? "" : "s",
3447 short_folder_name);
3449 return(want_to(prompt_b, 'y', 0, NO_HELP, WT_NORM));
3454 * This is used with multiple append saves. Call it once before
3455 * the series of appends with SSCP_INIT and once after all are
3456 * done with SSCP_END. In between, it is called automatically
3457 * from save_fetch_append or save_fetch_append_cb when we need
3458 * to ask the user if he or she wants to continue even though
3459 * announced message size doesn't match the actual message size.
3460 * As of 2008-02-29 the gmail IMAP server has these size mismatches
3461 * on a regular basis even though the data is ok.
3464 save_size_changed_prompt(long msgno, int flags)
3466 int ret;
3467 char prompt[100];
3468 static int remember_the_yes = 0;
3469 static int possible_corruption = 0;
3470 static ESCKEY_S save_size_opts[] = {
3471 {'y', 'y', "Y", "Yes"},
3472 {'n', 'n', "N", "No"},
3473 {'a', 'a', "A", "yes to All"},
3474 {-1, 0, NULL, NULL}
3477 if(flags & SSCP_INIT || flags & SSCP_END){
3478 if(flags & SSCP_END && possible_corruption)
3479 q_status_message(SM_ORDER, 3, 3, "There is possible data corruption, check the results");
3481 remember_the_yes = 0;
3482 possible_corruption = 0;
3483 ps_global->noshow_error = 0;
3484 ps_global->noshow_warn = 0;
3485 return(0);
3488 if(remember_the_yes){
3489 snprintf(prompt, sizeof(prompt),
3490 "Message to save shrank! (msg # %ld): Continuing", msgno);
3491 q_status_message(SM_ORDER, 0, 3, prompt);
3492 display_message('x');
3493 return(remember_the_yes);
3496 snprintf(prompt, sizeof(prompt),
3497 "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno);
3498 ret = radio_buttons(prompt, -FOOTER_ROWS(ps_global), save_size_opts,
3499 'n', 0, h_save_size_changed, RB_NORM);
3501 switch(ret){
3502 case 'a':
3503 remember_the_yes = 'y';
3504 possible_corruption++;
3505 return(remember_the_yes);
3507 case 'y':
3508 possible_corruption++;
3509 return('y');
3511 default:
3512 possible_corruption = 0;
3513 ps_global->noshow_error = 1;
3514 ps_global->noshow_warn = 1;
3515 break;
3518 return('n');
3522 /*----------------------------------------------------------------------
3523 Expunge_and_close callback that happens once the decision to expunge
3524 and close has been made and before expunging and closing begins
3527 Args: stream -- folder's stream
3528 folder -- name of folder containing folders
3529 deleted -- number of del'd msgs
3531 Result: 'y' to continue with expunge
3532 ----*/
3533 void
3534 expunge_and_close_begins(int flags, char *folder)
3536 if(!(flags & EC_NO_CLOSE)){
3537 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
3538 q_status_message1(SM_INFO, 0, 1, "Closing \"%.200s\"...", (char *) fname);
3539 flush_status_messages(1);
3540 if(fname) fs_give((void **)&fname);
3545 /*----------------------------------------------------------------------
3546 Export a message to a plain file in users home directory
3548 Args: state -- pointer to struct holding a bunch of pine state
3549 msgmap -- table mapping msg nums to c-client sequence nums
3550 qline -- screen line to ask questions on
3551 agg -- boolean indicating we're to operate on aggregate set
3553 Result:
3554 ----*/
3556 cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt)
3558 char filename[MAXPATH+1], full_filename[MAXPATH+1], *err;
3559 char nmsgs[80];
3560 int r, leading_nl, failure = 0, orig_errno, rflags = GER_NONE;
3561 int flags = GE_IS_EXPORT | GE_SEQ_SENSITIVE, rv = 0;
3562 ENVELOPE *env;
3563 MESSAGECACHE *mc;
3564 BODY *b;
3565 long i, count = 0L, start_of_append, rawno;
3566 gf_io_t pc;
3567 STORE_S *store;
3568 struct variable *vars = ps_global->vars;
3569 ESCKEY_S export_opts[5];
3570 static HISTORY_S *history = NULL;
3572 if(ps_global->restricted){
3573 q_status_message(SM_ORDER, 0, 3,
3574 "Alpine demo can't export messages to files");
3575 return rv;
3578 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
3579 return rv;
3581 export_opts[i = 0].ch = ctrl('T');
3582 export_opts[i].rval = 10;
3583 export_opts[i].name = "^T";
3584 export_opts[i++].label = N_("To Files");
3586 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3587 if(ps_global->VAR_DOWNLOAD_CMD && ps_global->VAR_DOWNLOAD_CMD[0]){
3588 export_opts[i].ch = ctrl('V');
3589 export_opts[i].rval = 12;
3590 export_opts[i].name = "^V";
3591 /* TRANSLATORS: this is an abbreviation for Download Messages */
3592 export_opts[i++].label = N_("Downld Msg");
3594 #endif /* !(DOS || MAC) */
3596 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
3597 export_opts[i].ch = ctrl('I');
3598 export_opts[i].rval = 11;
3599 export_opts[i].name = "TAB";
3600 export_opts[i++].label = N_("Complete");
3603 #if 0
3604 /* Commented out since it's not yet support! */
3605 if(F_ON(F_ENABLE_SUB_LISTS,ps_global)){
3606 export_opts[i].ch = ctrl('X');
3607 export_opts[i].rval = 14;
3608 export_opts[i].name = "^X";
3609 export_opts[i++].label = N_("ListMatches");
3611 #endif
3614 * If message has attachments, add a toggle that will allow the user
3615 * to save all of the attachments to a single directory, using the
3616 * names provided with the attachments or part names. What we'll do is
3617 * export the message as usual, and then export the attachments into
3618 * a subdirectory that did not exist before. The subdir will be named
3619 * something based on the name of the file being saved to, but a
3620 * unique, new name.
3622 if(!MCMD_ISAGG(aopt)
3623 && state->mail_stream
3624 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3625 && rawno <= state->mail_stream->nmsgs
3626 && (env = pine_mail_fetchstructure(state->mail_stream, rawno, &b))
3627 && b
3628 && b->type == TYPEMULTIPART
3629 && b->subtype
3630 && strucmp(b->subtype, "ALTERNATIVE") != 0){
3631 PART *part;
3633 part = b->nested.part; /* 1st part */
3634 if(part && part->next)
3635 flags |= GE_ALLPARTS;
3638 export_opts[i].ch = -1;
3639 filename[0] = '\0';
3641 if(mn_total_cur(msgmap) <= 1L){
3642 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld", mn_get_cur(msgmap));
3643 nmsgs[sizeof(nmsgs)-1] = '\0';
3645 else{
3646 snprintf(nmsgs, sizeof(nmsgs), "%s messages", comatose(mn_total_cur(msgmap)));
3647 nmsgs[sizeof(nmsgs)-1] = '\0';
3650 r = get_export_filename(state, filename, NULL, full_filename,
3651 sizeof(filename), nmsgs, "EXPORT",
3652 export_opts, &rflags, qline, flags, &history);
3654 if(r < 0){
3655 switch(r){
3656 case -1:
3657 cmd_cancelled("Export message");
3658 break;
3660 case -2:
3661 q_status_message1(SM_ORDER, 0, 2,
3662 _("Can't export to file outside of %s"),
3663 VAR_OPER_DIR);
3664 break;
3667 goto fini;
3669 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3670 else if(r == 12){ /* Download */
3671 char cmd[MAXPATH], *tfp = NULL;
3672 int next = 0;
3673 PIPE_S *syspipe;
3674 STORE_S *so;
3675 gf_io_t pc;
3677 if(ps_global->restricted){
3678 q_status_message(SM_ORDER | SM_DING, 3, 3,
3679 "Download disallowed in restricted mode");
3680 goto fini;
3683 err = NULL;
3684 tfp = temp_nam(NULL, "pd");
3685 build_updown_cmd(cmd, sizeof(cmd), ps_global->VAR_DOWNLOAD_CMD_PREFIX,
3686 ps_global->VAR_DOWNLOAD_CMD, tfp);
3687 dprint((1, "Download cmd called: \"%s\"\n", cmd));
3688 if((so = so_get(FileStar, tfp, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){
3689 gf_set_so_writec(&pc, so);
3691 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
3692 if(!(state->mail_stream
3693 && (rawno = mn_m2raw(msgmap, i)) > 0L
3694 && rawno <= state->mail_stream->nmsgs
3695 && (mc = mail_elt(state->mail_stream, rawno))
3696 && mc->valid))
3697 mc = NULL;
3699 if(!(env = pine_mail_fetchstructure(state->mail_stream,
3700 mn_m2raw(msgmap, i), &b))
3701 || !bezerk_delimiter(env, mc, pc, next++)
3702 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
3703 env, b, NULL, FM_NEW_MESS | FM_NOWRAP, pc)){
3704 q_status_message(SM_ORDER | SM_DING, 3, 3,
3705 err = "Error writing tempfile for download");
3706 break;
3710 gf_clear_so_writec(so);
3711 if(so_give(&so)){ /* close file */
3712 if(!err)
3713 err = "Error writing tempfile for download";
3716 if(!err){
3717 if((syspipe = open_system_pipe(cmd, NULL, NULL,
3718 PIPE_USER | PIPE_RESET,
3719 0, pipe_callback, pipe_report_error)) != NULL)
3720 (void) close_system_pipe(&syspipe, NULL, pipe_callback);
3721 else
3722 q_status_message(SM_ORDER | SM_DING, 3, 3,
3723 err = _("Error running download command"));
3726 else
3727 q_status_message(SM_ORDER | SM_DING, 3, 3,
3728 err = "Error building temp file for download");
3730 if(tfp){
3731 our_unlink(tfp);
3732 fs_give((void **)&tfp);
3735 if(!err)
3736 q_status_message(SM_ORDER, 0, 3, _("Download Command Completed"));
3738 goto fini;
3740 #endif /* !(DOS || MAC) */
3743 if(rflags & GER_APPEND)
3744 leading_nl = 1;
3745 else
3746 leading_nl = 0;
3748 dprint((5, "Opening file \"%s\" for export\n",
3749 full_filename ? full_filename : "?"));
3751 if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE))){
3752 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3753 /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
3754 _("Error opening file \"%s\" to export message: %s"),
3755 full_filename, error_description(errno));
3756 goto fini;
3758 else
3759 gf_set_so_writec(&pc, store);
3761 err = NULL;
3762 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), count++){
3763 env = pine_mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i),
3764 &b);
3765 if(!env) {
3766 err = _("Can't export message. Error accessing mail folder");
3767 failure = 1;
3768 break;
3771 if(!(state->mail_stream
3772 && (rawno = mn_m2raw(msgmap, i)) > 0L
3773 && rawno <= state->mail_stream->nmsgs
3774 && (mc = mail_elt(state->mail_stream, rawno))
3775 && mc->valid))
3776 mc = NULL;
3778 start_of_append = so_tell(store);
3779 if(!bezerk_delimiter(env, mc, pc, leading_nl)
3780 || !format_message(mn_m2raw(msgmap, i), env, b, NULL,
3781 FM_NEW_MESS | FM_NOWRAP, pc)){
3782 orig_errno = errno; /* save incase things are really bad */
3783 failure = 1; /* pop out of here */
3784 break;
3787 leading_nl = 1;
3790 gf_clear_so_writec(store);
3791 if(so_give(&store)) /* release storage */
3792 failure++;
3794 if(failure){
3795 our_truncate(full_filename, (off_t)start_of_append);
3796 if(err){
3797 dprint((1, "FAILED Export: fetch(%ld): %s\n",
3798 i, err ? err : "?"));
3799 q_status_message(SM_ORDER | SM_DING, 3, 4, err);
3801 else{
3802 dprint((1, "FAILED Export: file \"%s\" : %s\n",
3803 full_filename ? full_filename : "?",
3804 error_description(orig_errno)));
3805 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3806 /* TRANSLATORS: Error exporting to <filename>: <error text> */
3807 _("Error exporting to \"%s\" : %s"),
3808 filename, error_description(orig_errno));
3811 else{
3812 if(rflags & GER_ALLPARTS && full_filename[0]){
3813 char dir[MAXPATH+1];
3814 char lfile[MAXPATH+1];
3815 int ok = 0, tries = 0, saved = 0, errs = 0;
3816 ATTACH_S *a;
3819 * Now we want to save all of the attachments to a subdirectory.
3820 * To make it easier for us and probably easier for the user, and
3821 * to prevent the user from shooting himself in the foot, we
3822 * make a new subdirectory so that we can't possibly step on
3823 * any existing files, and we don't need any interaction with the
3824 * user while saving.
3826 * We'll just use the directory name full_filename.d or if that
3827 * already exists and isn't empty, we'll try adding a suffix to
3828 * that until we get something to use.
3831 if(strlen(full_filename) + strlen(".d") + 1 > sizeof(dir)){
3832 q_status_message1(SM_ORDER | SM_DING, 3, 4,
3833 _("Can't save attachments, filename too long: %s"),
3834 full_filename);
3835 goto fini;
3838 ok = 0;
3839 snprintf(dir, sizeof(dir), "%s.d", full_filename);
3840 dir[sizeof(dir)-1] = '\0';
3842 do {
3843 tries++;
3844 switch(r = is_writable_dir(dir)){
3845 case 0: /* exists and is a writable dir */
3847 * We could figure out if it is empty and use it in
3848 * that case, but that sounds like a lot of work, so
3849 * just fall through to default.
3852 default:
3853 if(strlen(full_filename) + strlen(".d") + 1 +
3854 1 + strlen(long2string((long) tries)) > sizeof(dir)){
3855 q_status_message(SM_ORDER | SM_DING, 3, 4,
3856 "Problem saving attachments");
3857 goto fini;
3860 snprintf(dir, sizeof(dir), "%s.d_%s", full_filename,
3861 long2string((long) tries));
3862 dir[sizeof(dir)-1] = '\0';
3863 break;
3865 case 3: /* doesn't exist, that's good! */
3866 /* make new directory */
3867 ok++;
3868 break;
3870 } while(!ok && tries < 1000);
3872 if(tries >= 1000){
3873 q_status_message(SM_ORDER | SM_DING, 3, 4,
3874 _("Problem saving attachments"));
3875 goto fini;
3878 /* create the new directory */
3879 if(our_mkdir(dir, 0700)){
3880 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3881 _("Problem saving attachments: %s: %s"), dir,
3882 error_description(errno));
3883 goto fini;
3886 if(!(state->mail_stream
3887 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3888 && rawno <= state->mail_stream->nmsgs
3889 && (env=pine_mail_fetchstructure(state->mail_stream,rawno,&b))
3890 && b)){
3891 q_status_message(SM_ORDER | SM_DING, 3, 4,
3892 _("Problem reading message"));
3893 goto fini;
3896 zero_atmts(state->atmts);
3897 describe_mime(b, "", 1, 1, 0, 0);
3899 a = state->atmts;
3900 if(a && a->description) /* skip main body part */
3901 a++;
3903 for(; a->description != NULL; a++){
3904 /* skip over these parts of the message */
3905 if(MIME_MSG_A(a) || MIME_DGST_A(a) || MIME_VCARD_A(a))
3906 continue;
3908 lfile[0] = '\0';
3909 (void) get_filename_parameter(lfile, sizeof(lfile), a->body, NULL);
3911 if(lfile[0] == '\0'){ /* MAXPATH + 1 = sizeof(lfile) */
3912 snprintf(lfile, sizeof(lfile), "part_%.*s", MAXPATH+1-6,
3913 a->number ? a->number : "?");
3914 lfile[sizeof(lfile)-1] = '\0';
3917 if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + 1
3918 > sizeof(filename)){
3919 dprint((2,
3920 "FAILED Att Export: name too long: %s\n",
3921 dir, S_FILESEP, lfile));
3922 errs++;
3923 continue;
3926 snprintf(filename, sizeof(filename), "%s%s%s", dir, S_FILESEP, lfile);
3927 filename[sizeof(filename)-1] = '\0';
3929 if(write_attachment_to_file(state->mail_stream, rawno,
3930 a, GER_NONE, filename) == 1)
3931 saved++;
3932 else
3933 errs++;
3936 if(errs){
3937 if(saved)
3938 q_status_message1(SM_ORDER, 3, 3,
3939 "Errors saving some attachments, %s attachments saved",
3940 long2string((long) saved));
3941 else
3942 q_status_message(SM_ORDER, 3, 3,
3943 _("Problems saving attachments"));
3945 else{
3946 if(saved)
3947 q_status_message2(SM_ORDER, 0, 3,
3948 /* TRANSLATORS: Saved <how many> attachements to <directory name> */
3949 _("Saved %s attachments to %s"),
3950 long2string((long) saved), dir);
3951 else
3952 q_status_message(SM_ORDER, 3, 3, _("No attachments to save"));
3955 else if(mn_total_cur(msgmap) > 1L)
3956 q_status_message4(SM_ORDER,0,3,
3957 "%s message%s %s to file \"%s\"",
3958 long2string(count), plural(count),
3959 rflags & GER_OVER
3960 ? "overwritten"
3961 : rflags & GER_APPEND ? "appended" : "exported",
3962 filename);
3963 else
3964 q_status_message3(SM_ORDER,0,3,
3965 "Message %s %s to file \"%s\"",
3966 long2string(mn_get_cur(msgmap)),
3967 rflags & GER_OVER
3968 ? "overwritten"
3969 : rflags & GER_APPEND ? "appended" : "exported",
3970 filename);
3971 rv++;
3974 fini:
3975 if(MCMD_ISAGG(aopt))
3976 restore_selected(msgmap);
3978 return rv;
3983 * Ask user what file to export to. Export from srcstore to that file.
3985 * Args ps -- pine struct
3986 * srctext -- pointer to source text
3987 * srctype -- type of that source text
3988 * prompt_msg -- see get_export_filename
3989 * lister_msg -- "
3991 * Returns: != 0 : error
3992 * 0 : ok
3995 simple_export(struct pine *ps, void *srctext, SourceType srctype, char *prompt_msg, char *lister_msg)
3997 int r = 1, rflags = GER_NONE;
3998 char filename[MAXPATH+1], full_filename[MAXPATH+1];
3999 STORE_S *store = NULL;
4000 struct variable *vars = ps->vars;
4001 static HISTORY_S *history = NULL;
4002 static ESCKEY_S simple_export_opts[] = {
4003 {ctrl('T'), 10, "^T", N_("To Files")},
4004 {-1, 0, NULL, NULL},
4005 {-1, 0, NULL, NULL}};
4007 if(F_ON(F_ENABLE_TAB_COMPLETE,ps)){
4008 simple_export_opts[r].ch = ctrl('I');
4009 simple_export_opts[r].rval = 11;
4010 simple_export_opts[r].name = "TAB";
4011 simple_export_opts[r].label = N_("Complete");
4014 if(!srctext){
4015 q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
4016 r = -3;
4017 goto fini;
4020 simple_export_opts[++r].ch = -1;
4021 filename[0] = '\0';
4022 full_filename[0] = '\0';
4024 r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename),
4025 prompt_msg, lister_msg, simple_export_opts, &rflags,
4026 -FOOTER_ROWS(ps), GE_IS_EXPORT, &history);
4028 if(r < 0)
4029 goto fini;
4030 else if(!full_filename[0]){
4031 r = -1;
4032 goto fini;
4035 dprint((5, "Opening file \"%s\" for export\n",
4036 full_filename ? full_filename : "?"));
4038 if((store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
4039 char *pipe_err;
4040 gf_io_t pc, gc;
4042 gf_set_so_writec(&pc, store);
4043 gf_set_readc(&gc, srctext, (srctype == CharStar)
4044 ? strlen((char *)srctext)
4045 : 0L,
4046 srctype, 0);
4047 gf_filter_init();
4048 if((pipe_err = gf_pipe(gc, pc)) != NULL){
4049 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4050 /* TRANSLATORS: Problem saving to <filename>: <error text> */
4051 _("Problem saving to \"%s\": %s"),
4052 filename, pipe_err);
4053 r = -3;
4055 else
4056 r = 0;
4058 gf_clear_so_writec(store);
4059 if(so_give(&store)){
4060 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4061 _("Problem saving to \"%s\": %s"),
4062 filename, error_description(errno));
4063 r = -3;
4066 else{
4067 q_status_message2(SM_ORDER | SM_DING, 3, 4,
4068 _("Error opening file \"%s\" for export: %s"),
4069 full_filename, error_description(errno));
4070 r = -3;
4073 fini:
4074 switch(r){
4075 case 0:
4076 /* overloading full_filename */
4077 snprintf(full_filename, sizeof(full_filename), "%c%s",
4078 (prompt_msg && prompt_msg[0])
4079 ? (islower((unsigned char)prompt_msg[0])
4080 ? toupper((unsigned char)prompt_msg[0]) : prompt_msg[0])
4081 : 'T',
4082 (prompt_msg && prompt_msg[0]) ? prompt_msg+1 : "ext");
4083 full_filename[sizeof(full_filename)-1] = '\0';
4084 q_status_message3(SM_ORDER,0,2,"%s %s to \"%s\"",
4085 full_filename,
4086 rflags & GER_OVER
4087 ? "overwritten"
4088 : rflags & GER_APPEND ? "appended" : "exported",
4089 filename);
4090 break;
4092 case -1:
4093 cmd_cancelled("Export");
4094 break;
4096 case -2:
4097 q_status_message1(SM_ORDER, 0, 2,
4098 _("Can't export to file outside of %s"), VAR_OPER_DIR);
4099 break;
4102 ps->mangled_footer = 1;
4103 return(r);
4108 * Ask user what file to export to.
4110 * filename -- On input, this is the filename to start with. On exit,
4111 * this is the filename chosen. (but this isn't used)
4112 * deefault -- This is the default value if user hits return. The
4113 * prompt will have [deefault] added to it automatically.
4114 * full_filename -- This is the full filename on exit.
4115 * len -- Minimum length of _both_ filename and full_filename.
4116 * prompt_msg -- Message to insert in prompt.
4117 * lister_msg -- Message to insert in file_lister.
4118 * opts -- Key options.
4119 * There is a tangled relationship between the callers
4120 * and this routine as far as opts are concerned. Some
4121 * of the opts are handled here. In particular, r == 3,
4122 * r == 10, r == 11, and r == 13 are all handled here.
4123 * Don't use those values unless you want what happens
4124 * here. r == 12 and others are handled by the caller.
4125 * rflags -- Return flags
4126 * GER_OVER - overwrite of existing file
4127 * GER_APPEND - append of existing file
4128 * else file did not exist before
4130 * GER_ALLPARTS - AllParts toggle was turned on
4132 * qline -- Command line to prompt on.
4133 * flags -- Logically OR'd flags
4134 * GE_IS_EXPORT - The command was an Export command
4135 * so the prompt should include
4136 * EXPORT:.
4137 * GE_SEQ_SENSITIVE - The command that got us here is
4138 * sensitive to sequence number changes
4139 * caused by unsolicited expunges.
4140 * GE_NO_APPEND - We will not allow append to an
4141 * existing file, only removal of the
4142 * file if it exists.
4143 * GE_IS_IMPORT - We are selecting for reading.
4144 * No overwriting or checking for
4145 * existence at all. Don't use this
4146 * together with GE_NO_APPEND.
4147 * GE_ALLPARTS - Turn on AllParts toggle.
4148 * GE_BINARY - Turn on Binary toggle.
4150 * Returns: -1 cancelled
4151 * -2 prohibited by VAR_OPER_DIR
4152 * -3 other error, already reported here
4153 * 0 ok
4154 * 12 user chose 12 command from opts
4157 get_export_filename(struct pine *ps, char *filename, char *deefault,
4158 char *full_filename, size_t len, char *prompt_msg,
4159 char *lister_msg, ESCKEY_S *optsarg, int *rflags,
4160 int qline, int flags, HISTORY_S **history)
4162 char dir[MAXPATH+1], dir2[MAXPATH+1];
4163 char precolon[MAXPATH+1], postcolon[MAXPATH+1];
4164 char filename2[MAXPATH+1], tmp[MAXPATH+1], *fn, *ill;
4165 int l, i, ku = -1, r, fatal, homedir = 0, was_abs_path=0, avail, ret = 0;
4166 int allparts = 0, binary = 0;
4167 char prompt_buf[400];
4168 char def[500];
4169 ESCKEY_S *opts = NULL;
4170 struct variable *vars = ps->vars;
4171 static HISTORY_S *dir_hist = NULL;
4173 if(flags & GE_ALLPARTS || history){
4175 * Copy the opts and add one to the end of the list.
4177 for(i = 0; optsarg[i].ch != -1; i++)
4180 if(history)
4181 i += 4;
4183 if(flags & GE_ALLPARTS)
4184 i++;
4186 if(flags & GE_BINARY)
4187 i++;
4189 opts = (ESCKEY_S *) fs_get((i+1) * sizeof(*opts));
4190 memset(opts, 0, (i+1) * sizeof(*opts));
4192 for(i = 0; optsarg[i].ch != -1; i++){
4193 opts[i].ch = optsarg[i].ch;
4194 opts[i].rval = optsarg[i].rval;
4195 opts[i].name = optsarg[i].name; /* no need to make a copy */
4196 opts[i].label = optsarg[i].label; /* " */
4199 if(flags & GE_ALLPARTS){
4200 allparts = i;
4201 opts[i].ch = ctrl('P');
4202 opts[i].rval = 13;
4203 opts[i].name = "^P";
4204 /* TRANSLATORS: Export all attachment parts */
4205 opts[i++].label = N_("AllParts");
4208 if(flags & GE_BINARY){
4209 binary = i;
4210 opts[i].ch = ctrl('R');
4211 opts[i].rval = 15;
4212 opts[i].name = "^R";
4213 opts[i++].label = N_("Binary");
4216 if(history){
4217 opts[i].ch = KEY_UP;
4218 opts[i].rval = 30;
4219 opts[i].name = "";
4220 ku = i;
4221 opts[i++].label = "";
4223 opts[i].ch = KEY_DOWN;
4224 opts[i].rval = 31;
4225 opts[i].name = "";
4226 opts[i++].label = "";
4228 opts[i].ch = ctrl('Y');
4229 opts[i].rval = 32;
4230 opts[i].name = "";
4231 opts[i++].label = "";
4233 opts[i].ch = ctrl('V');
4234 opts[i].rval = 33;
4235 opts[i].name = "";
4236 opts[i++].label = "";
4239 opts[i].ch = -1;
4241 if(history){
4242 init_hist(history, HISTSIZE);
4243 init_hist(&dir_hist, HISTSIZE);
4246 else
4247 opts = optsarg;
4249 if(rflags)
4250 *rflags = GER_NONE;
4252 if(F_ON(F_USE_CURRENT_DIR, ps))
4253 dir[0] = '\0';
4254 else if(VAR_OPER_DIR){
4255 strncpy(dir, VAR_OPER_DIR, sizeof(dir));
4256 dir[sizeof(dir)-1] = '\0';
4258 #if defined(DOS) || defined(OS2)
4259 else if(VAR_FILE_DIR){
4260 strncpy(dir, VAR_FILE_DIR, sizeof(dir));
4261 dir[sizeof(dir)-1] = '\0';
4263 #endif
4264 else{
4265 dir[0] = '~';
4266 dir[1] = '\0';
4267 homedir=1;
4270 postcolon[0] = '\0';
4271 strncpy(precolon, dir, sizeof(precolon));
4272 precolon[sizeof(precolon)-1] = '\0';
4273 if(deefault){
4274 strncpy(def, deefault, sizeof(def)-1);
4275 def[sizeof(def)-1] = '\0';
4276 removing_leading_and_trailing_white_space(def);
4278 else
4279 def[0] = '\0';
4281 avail = MAX(20, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - MIN_OPT_ENT_WIDTH;
4283 /*---------- Prompt the user for the file name -------------*/
4284 while(1){
4285 int oeflags;
4286 char dirb[50], fileb[50];
4287 int l1, l2, l3, l4, l5, needed;
4288 char *p, p1[100], p2[100], *p3, p4[100], p5[100];
4290 snprintf(p1, sizeof(p1), "%sCopy ",
4291 (flags & GE_IS_EXPORT) ? "EXPORT: " :
4292 (flags & GE_IS_IMPORT) ? "IMPORT: " : "SAVE: ");
4293 p1[sizeof(p1)-1] = '\0';
4294 l1 = strlen(p1);
4296 strncpy(p2, prompt_msg ? prompt_msg : "", sizeof(p2)-1);
4297 p2[sizeof(p2)-1] = '\0';
4298 l2 = strlen(p2);
4300 if(rflags && *rflags & GER_ALLPARTS)
4301 p3 = " (and atts)";
4302 else
4303 p3 = "";
4305 l3 = strlen(p3);
4307 snprintf(p4, sizeof(p4), " %s file%s%s",
4308 (flags & GE_IS_IMPORT) ? "from" : "to",
4309 is_absolute_path(filename) ? "" : " in ",
4310 is_absolute_path(filename) ? "" :
4311 (!dir[0] ? "current directory"
4312 : (dir[0] == '~' && !dir[1]) ? "home directory"
4313 : short_str(dir,dirb,sizeof(dirb),30,FrontDots)));
4314 p4[sizeof(p4)-1] = '\0';
4315 l4 = strlen(p4);
4317 snprintf(p5, sizeof(p5), "%s%s%s: ",
4318 *def ? " [" : "",
4319 *def ? short_str(def,fileb,sizeof(fileb),40,EndDots) : "",
4320 *def ? "]" : "");
4321 p5[sizeof(p5)-1] = '\0';
4322 l5 = strlen(p5);
4324 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4325 snprintf(p4, sizeof(p4), " %s file%s%s",
4326 (flags & GE_IS_IMPORT) ? "from" : "to",
4327 is_absolute_path(filename) ? "" : " in ",
4328 is_absolute_path(filename) ? "" :
4329 (!dir[0] ? "current dir"
4330 : (dir[0] == '~' && !dir[1]) ? "home dir"
4331 : short_str(dir,dirb,sizeof(dirb),10,FrontDots)));
4332 p4[sizeof(p4)-1] = '\0';
4333 l4 = strlen(p4);
4336 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4337 snprintf(p5, sizeof(p5), "%s%s%s: ",
4338 *def ? " [" : "",
4339 *def ? short_str(def,fileb,sizeof(fileb),
4340 MAX(15,l5-5-needed),EndDots) : "",
4341 *def ? "]" : "");
4342 p5[sizeof(p5)-1] = '\0';
4343 l5 = strlen(p5);
4346 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l2 > 0){
4349 * 14 is about the shortest we can make this, because there are
4350 * fixed length strings of length 14 coming in here.
4352 p = short_str(prompt_msg, p2, sizeof(p2), MAX(14,l2-needed), FrontDots);
4353 if(p != p2){
4354 strncpy(p2, p, sizeof(p2)-1);
4355 p2[sizeof(p2)-1] = '\0';
4358 l2 = strlen(p2);
4361 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4362 strncpy(p1, "Copy ", sizeof(p1)-1);
4363 p1[sizeof(p1)-1] = '\0';
4364 l1 = strlen(p1);
4367 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4368 snprintf(p5, sizeof(p5), "%s%s%s: ",
4369 *def ? " [" : "",
4370 *def ? short_str(def,fileb, sizeof(fileb),
4371 MAX(10,l5-5-needed),EndDots) : "",
4372 *def ? "]" : "");
4373 p5[sizeof(p5)-1] = '\0';
4374 l5 = strlen(p5);
4377 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l3 > 0){
4378 if(needed <= l3 - strlen(" (+ atts)"))
4379 p3 = " (+ atts)";
4380 else if(needed <= l3 - strlen(" (atts)"))
4381 p3 = " (atts)";
4382 else if(needed <= l3 - strlen(" (+)"))
4383 p3 = " (+)";
4384 else if(needed <= l3 - strlen("+"))
4385 p3 = "+";
4386 else
4387 p3 = "";
4389 l3 = strlen(p3);
4392 snprintf(prompt_buf, sizeof(prompt_buf), "%s%s%s%s%s", p1, p2, p3, p4, p5);
4393 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4395 if(ku >= 0){
4396 if(items_in_hist(*history) > 0){
4397 opts[ku].name = HISTORY_UP_KEYNAME;
4398 opts[ku].label = HISTORY_KEYLABEL;
4399 opts[ku+1].name = HISTORY_DOWN_KEYNAME;
4400 opts[ku+1].label = HISTORY_KEYLABEL;
4401 if(items_in_hist(dir_hist) > 0){ /* any directories */
4402 opts[ku+2].name = "^Y";
4403 opts[ku+2].label = "Prev Dir";
4404 opts[ku+3].name = "^V";
4405 opts[ku+3].label = "Next Dir";
4407 else{
4408 opts[ku+2].name = "";
4409 opts[ku+2].label = "";
4410 opts[ku+3].name = "";
4411 opts[ku+3].label = "";
4414 else{
4415 opts[ku].name = "";
4416 opts[ku].label = "";
4417 opts[ku+1].name = "";
4418 opts[ku+1].label = "";
4422 oeflags = OE_APPEND_CURRENT |
4423 ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0);
4424 r = optionally_enter(filename, qline, 0, len, prompt_buf,
4425 opts, NO_HELP, &oeflags);
4427 dprint((2, "\n - export_filename = \"%s\", r = %d -\n", filename, r));
4428 /*--- Help ----*/
4429 if(r == 3){
4431 * Helps may not be right if you add another caller or change
4432 * things. Check it out.
4434 if(flags & GE_IS_IMPORT)
4435 helper(h_ge_import, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE);
4436 else if(flags & GE_ALLPARTS)
4437 helper(h_ge_allparts, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4438 else
4439 helper(h_ge_export, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4441 ps->mangled_screen = 1;
4443 continue;
4445 else if(r == 10 || r == 11){ /* Browser or File Completion */
4446 if(filename[0]=='~'){
4447 if(filename[1] == C_FILESEP && filename[2]!='\0'){
4448 precolon[0] = '~';
4449 precolon[1] = '\0';
4450 for(i=0; filename[i+2] != '\0' && i+2 < len-1; i++)
4451 filename[i] = filename[i+2];
4452 filename[i] = '\0';
4453 strncpy(dir, precolon, sizeof(dir)-1);
4454 dir[sizeof(dir)-1] = '\0';
4456 else if(filename[1]=='\0' ||
4457 (filename[1] == C_FILESEP && filename[2] == '\0')){
4458 precolon[0] = '~';
4459 precolon[1] = '\0';
4460 filename[0] = '\0';
4461 strncpy(dir, precolon, sizeof(dir)-1);
4462 dir[sizeof(dir)-1] = '\0';
4465 else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){
4466 if(homedir){
4467 precolon[0] = '~';
4468 precolon[1] = '\0';
4469 strncpy(dir, precolon, sizeof(dir)-1);
4470 dir[sizeof(dir)-1] = '\0';
4472 else{
4473 precolon[0] = '\0';
4474 dir[0] = '\0';
4477 l = MAXPATH;
4478 dir2[0] = '\0';
4479 strncpy(tmp, filename, sizeof(tmp)-1);
4480 tmp[sizeof(tmp)-1] = '\0';
4481 if(*tmp && is_absolute_path(tmp))
4482 fnexpand(tmp, sizeof(tmp));
4483 if(strncmp(tmp,postcolon, strlen(postcolon)))
4484 postcolon[0] = '\0';
4486 if(*tmp && (fn = last_cmpnt(tmp))){
4487 l -= fn - tmp;
4488 strncpy(filename2, fn, sizeof(filename2)-1);
4489 filename2[sizeof(filename2)-1] = '\0';
4490 if(is_absolute_path(tmp)){
4491 strncpy(dir2, tmp, MIN(fn - tmp, sizeof(dir2)-1));
4492 dir2[MIN(fn - tmp, sizeof(dir2)-1)] = '\0';
4493 #ifdef _WINDOWS
4494 if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){
4495 dir2[2] = '\\';
4496 dir2[3] = '\0';
4498 #endif
4499 strncpy(postcolon, dir2, sizeof(postcolon)-1);
4500 postcolon[sizeof(postcolon)-1] = '\0';
4501 precolon[0] = '\0';
4503 else{
4504 char *p = NULL;
4506 * Just building the directory name in dir2,
4507 * full_filename is overloaded.
4509 snprintf(full_filename, len, "%.*s", (int) MIN(fn-tmp,len-1), tmp);
4510 full_filename[len-1] = '\0';
4511 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4512 postcolon[sizeof(postcolon)-1] = '\0';
4513 build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH)
4514 : (dir[0] == '~' && !dir[1])
4515 ? ps->home_dir
4516 : dir,
4517 full_filename, sizeof(dir2));
4518 if(p)
4519 free(p);
4522 else{
4523 if(is_absolute_path(tmp)){
4524 strncpy(dir2, tmp, sizeof(dir2)-1);
4525 dir2[sizeof(dir2)-1] = '\0';
4526 #ifdef _WINDOWS
4527 if(dir2[2]=='\0' && dir2[1]==':'){
4528 dir2[2]='\\';
4529 dir2[3]='\0';
4530 strncpy(postcolon,dir2,sizeof(postcolon)-1);
4531 postcolon[sizeof(postcolon)-1] = '\0';
4533 #endif
4534 filename2[0] = '\0';
4535 precolon[0] = '\0';
4537 else{
4538 strncpy(filename2, tmp, sizeof(filename2)-1);
4539 filename2[sizeof(filename2)-1] = '\0';
4540 if(!dir[0])
4541 (void)getcwd(dir2, sizeof(dir2));
4542 else if(dir[0] == '~' && !dir[1]){
4543 strncpy(dir2, ps->home_dir, sizeof(dir2)-1);
4544 dir2[sizeof(dir2)-1] = '\0';
4546 else{
4547 strncpy(dir2, dir, sizeof(dir2)-1);
4548 dir2[sizeof(dir2)-1] = '\0';
4551 postcolon[0] = '\0';
4555 build_path(full_filename, dir2, filename2, len);
4556 if(!strcmp(full_filename, dir2))
4557 filename2[0] = '\0';
4558 if(full_filename[strlen(full_filename)-1] == C_FILESEP
4559 && isdir(full_filename,NULL,NULL)){
4560 if(strlen(full_filename) == 1)
4561 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4562 else if(filename2[0])
4563 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4564 postcolon[sizeof(postcolon)-1] = '\0';
4565 strncpy(dir2, full_filename, sizeof(dir2)-1);
4566 dir2[sizeof(dir2)-1] = '\0';
4567 filename2[0] = '\0';
4569 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4570 else if(full_filename[strlen(full_filename)-1] == C_FILESEP){
4571 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4572 postcolon[sizeof(postcolon)-1] = '\0';
4573 strncpy(dir2, full_filename, sizeof(dir2)-1);
4574 dir2[sizeof(dir2)-1] = '\0';
4575 filename2[0] = '\0';
4577 #endif
4578 if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1
4579 && strcmp(dir2+1, ":\\"))
4580 /* last condition to prevent stripping of '\\'
4581 in windows partition */
4582 dir2[strlen(dir2)-1] = '\0';
4584 if(r == 10){ /* File Browser */
4585 r = file_lister(lister_msg ? lister_msg : "EXPORT",
4586 dir2, sizeof(dir2), filename2, sizeof(filename2),
4587 TRUE,
4588 (flags & GE_IS_IMPORT) ? FB_READ : FB_SAVE);
4589 #ifdef _WINDOWS
4590 /* Windows has a special "feature" in which entering the file browser will
4591 change the working directory if the directory is changed at all (even
4592 clicking "Cancel" will change the working directory).
4594 if(F_ON(F_USE_CURRENT_DIR, ps))
4595 (void)getcwd(dir2,sizeof(dir2));
4596 #endif
4597 if(isdir(dir2,NULL,NULL)){
4598 strncpy(precolon, dir2, sizeof(precolon)-1);
4599 precolon[sizeof(precolon)-1] = '\0';
4601 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4602 postcolon[sizeof(postcolon)-1] = '\0';
4603 if(r == 1){
4604 build_path(full_filename, dir2, filename2, len);
4605 if(isdir(full_filename, NULL, NULL)){
4606 strncpy(dir, full_filename, sizeof(dir)-1);
4607 dir[sizeof(dir)-1] = '\0';
4608 filename[0] = '\0';
4610 else{
4611 fn = last_cmpnt(full_filename);
4612 strncpy(dir, full_filename,
4613 MIN(fn - full_filename, sizeof(dir)-1));
4614 dir[MIN(fn - full_filename, sizeof(dir)-1)] = '\0';
4615 if(fn - full_filename > 1)
4616 dir[fn - full_filename - 1] = '\0';
4619 if(!strcmp(dir, ps->home_dir)){
4620 dir[0] = '~';
4621 dir[1] = '\0';
4624 strncpy(filename, fn, len-1);
4625 filename[len-1] = '\0';
4628 else{ /* File Completion */
4629 if(!pico_fncomplete(dir2, filename2, sizeof(filename2)))
4630 Writechar(BELL, 0);
4631 strncat(postcolon, filename2,
4632 sizeof(postcolon)-1-strlen(postcolon));
4633 postcolon[sizeof(postcolon)-1] = '\0';
4635 was_abs_path = is_absolute_path(filename);
4637 if(!strcmp(dir, ps->home_dir)){
4638 dir[0] = '~';
4639 dir[1] = '\0';
4642 strncpy(filename, postcolon, len-1);
4643 filename[len-1] = '\0';
4644 strncpy(dir, precolon, sizeof(dir)-1);
4645 dir[sizeof(dir)-1] = '\0';
4647 if(filename[0] == '~' && !filename[1]){
4648 dir[0] = '~';
4649 dir[1] = '\0';
4650 filename[0] = '\0';
4653 continue;
4655 else if(r == 12){ /* Download, caller handles it */
4656 ret = r;
4657 goto done;
4659 else if(r == 13){ /* toggle AllParts bit */
4660 if(rflags){
4661 if(*rflags & GER_ALLPARTS){
4662 *rflags &= ~GER_ALLPARTS;
4663 opts[allparts].label = N_("AllParts");
4665 else{
4666 *rflags |= GER_ALLPARTS;
4667 /* opposite of All Parts, No All Parts */
4668 opts[allparts].label = N_("NoAllParts");
4672 continue;
4674 #if 0
4675 else if(r == 14){ /* List file names matching partial? */
4676 continue;
4678 #endif
4679 else if(r == 15){ /* toggle Binary bit */
4680 if(rflags){
4681 if(*rflags & GER_BINARY){
4682 *rflags &= ~GER_BINARY;
4683 opts[binary].label = N_("Binary");
4685 else{
4686 *rflags |= GER_BINARY;
4687 opts[binary].label = N_("No Binary");
4691 continue;
4693 else if(r == 1){ /* Cancel */
4694 ret = -1;
4695 goto done;
4697 else if(r == 4){
4698 continue;
4700 else if(r >= 30 && r <= 33){
4701 char *p = NULL;
4703 if(history){
4704 switch(r){
4705 case 30: p = get_prev_hist(*history, filename, 0, NULL); break;
4706 case 31: p = get_next_hist(*history, filename, 0, NULL); break;
4707 case 32: p = get_prev_hist(dir_hist, NULL, 0, NULL); break;
4708 case 33: p = get_next_hist(dir_hist, NULL, 0, NULL); break;
4709 default: alpine_panic("Impossible case in save attachment"); break;
4713 if(p != NULL && *p != '\0'){
4714 if(r == 30 || r == 31){
4715 if((fn = last_cmpnt(p)) != NULL){
4716 strncpy(dir, p, MIN(fn - p, sizeof(dir)-1));
4717 dir[MIN(fn - p, sizeof(dir)-1)] = '\0';
4718 if(fn - p > 1)
4719 dir[fn - p - 1] = '\0';
4720 strncpy(filename, fn, len-1);
4721 filename[len-1] = '\0';
4723 } else { /* r == 32 || r == 33 */
4724 strncpy(dir, p, sizeof(dir)-1);
4725 dir[sizeof(dir)-1] = '\0';
4728 if(!strcmp(dir, ps->home_dir)){
4729 dir[0] = '~';
4730 dir[1] = '\0';
4733 else
4734 Writechar(BELL, 0);
4735 continue;
4737 else if(r != 0){
4738 Writechar(BELL, 0);
4739 continue;
4742 removing_leading_and_trailing_white_space(filename);
4744 if(!*filename){
4745 if(!*def){ /* Cancel */
4746 ret = -1;
4747 goto done;
4750 strncpy(filename, def, len-1);
4751 filename[len-1] = '\0';
4754 #if defined(DOS) || defined(OS2)
4755 if(is_absolute_path(filename)){
4756 fixpath(filename, len);
4758 #else
4759 if(filename[0] == '~'){
4760 if(fnexpand(filename, len) == NULL){
4761 char *p = strindex(filename, '/');
4762 if(p != NULL)
4763 *p = '\0';
4764 q_status_message1(SM_ORDER | SM_DING, 3, 3,
4765 _("Error expanding file name: \"%s\" unknown user"),
4766 filename);
4767 continue;
4770 #endif
4772 if(is_absolute_path(filename)){
4773 strncpy(full_filename, filename, len-1);
4774 full_filename[len-1] = '\0';
4776 else{
4777 if(!dir[0])
4778 build_path(full_filename, (char *)getcwd(dir,sizeof(dir)),
4779 filename, len);
4780 else if(dir[0] == '~' && !dir[1])
4781 build_path(full_filename, ps->home_dir, filename, len);
4782 else
4783 build_path(full_filename, dir, filename, len);
4786 if((ill = filter_filename(full_filename, &fatal,
4787 ps_global->restricted || ps_global->VAR_OPER_DIR)) != NULL){
4788 if(fatal){
4789 q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill);
4790 continue;
4792 else{
4793 /* BUG: we should beep when the key's pressed rather than bitch later */
4794 /* Warn and ask for confirmation. */
4795 snprintf(prompt_buf, sizeof(prompt_buf), "File name contains a '%s'. %s anyway",
4796 ill, (flags & GE_IS_EXPORT) ? "Export" : "Save");
4797 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4798 if(want_to(prompt_buf, 'n', 0, NO_HELP,
4799 ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y')
4800 continue;
4804 break; /* Must have got an OK file name */
4807 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){
4808 ret = -2;
4809 goto done;
4812 if(!can_access(full_filename, ACCESS_EXISTS)){
4813 int rbflags;
4814 static ESCKEY_S access_opts[] = {
4815 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4816 a file or append to the end of the file */
4817 {'o', 'o', "O", N_("Overwrite")},
4818 {'a', 'a', "A", N_("Append")},
4819 {-1, 0, NULL, NULL}};
4821 rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0);
4823 if(flags & GE_NO_APPEND){
4824 r = strlen(filename);
4825 snprintf(prompt_buf, sizeof(prompt_buf),
4826 /* TRANSLATORS: asking user whether to overwrite a file or not,
4827 File <filename> already exists. Overwrite it ? */
4828 _("File \"%s%s\" already exists. Overwrite it "),
4829 (r > 20) ? "..." : "",
4830 filename + ((r > 20) ? r - 20 : 0));
4831 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4832 if(want_to(prompt_buf, 'n', 'x', NO_HELP, rbflags) == 'y'){
4833 if(rflags)
4834 *rflags |= GER_OVER;
4836 if(our_unlink(full_filename) < 0){
4837 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4838 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4839 _("Cannot remove old %s: %s"),
4840 full_filename, error_description(errno));
4843 else{
4844 ret = -1;
4845 goto done;
4848 else if(!(flags & GE_IS_IMPORT)){
4849 r = strlen(filename);
4850 snprintf(prompt_buf, sizeof(prompt_buf),
4851 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4852 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4853 (r > 20) ? "..." : "",
4854 filename + ((r > 20) ? r - 20 : 0));
4855 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4856 switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global),
4857 access_opts, 'a', 'x', NO_HELP, rbflags)){
4858 case 'o' :
4859 if(rflags)
4860 *rflags |= GER_OVER;
4862 if(our_truncate(full_filename, (off_t)0) < 0)
4863 /* trouble truncating, but we'll give it a try anyway */
4864 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4865 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
4866 _("Warning: Cannot truncate old %s: %s"),
4867 full_filename, error_description(errno));
4868 break;
4870 case 'a' :
4871 if(rflags)
4872 *rflags |= GER_APPEND;
4874 break;
4876 case 'x' :
4877 default :
4878 ret = -1;
4879 goto done;
4884 done:
4885 if(history && ret == 0){
4886 save_hist(*history, full_filename, 0, NULL);
4887 strncpy(tmp, full_filename, MAXPATH);
4888 tmp[MAXPATH] = '\0';
4889 if((fn = strrchr(tmp, C_FILESEP)) != NULL)
4890 *fn = '\0';
4891 save_hist(dir_hist, tmp, 0, NULL);
4894 if(opts && opts != optsarg)
4895 fs_give((void **) &opts);
4897 return(ret);
4901 /*----------------------------------------------------------------------
4902 parse the config'd upload/download command
4904 Args: cmd -- buffer to return command fit for shellin'
4905 prefix --
4906 cfg_str --
4907 fname -- file name to build into the command
4909 Returns: pointer to cmd_str buffer or NULL on real bad error
4911 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
4912 cfg_str is written to standard out right before a successful
4913 return of this function. The call immediately following this
4914 function darn well better be the shell exec...
4915 ----*/
4916 char *
4917 build_updown_cmd(char *cmd, size_t cmdlen, char *prefix, char *cfg_str, char *fname)
4919 char *p;
4920 int fname_found = 0;
4922 if(prefix && *prefix){
4923 /* loop thru replacing all occurances of _FILE_ */
4924 p = strncpy(cmd, prefix, cmdlen);
4925 cmd[cmdlen-1] = '\0';
4926 while((p = strstr(p, "_FILE_")))
4927 rplstr(p, cmdlen-(p-cmd), 6, fname);
4929 fputs(cmd, stdout);
4932 /* loop thru replacing all occurances of _FILE_ */
4933 p = strncpy(cmd, cfg_str, cmdlen);
4934 cmd[cmdlen-1] = '\0';
4935 while((p = strstr(p, "_FILE_"))){
4936 rplstr(p, cmdlen-(p-cmd), 6, fname);
4937 fname_found = 1;
4940 if(!fname_found)
4941 snprintf(cmd+strlen(cmd), cmdlen-strlen(cmd), " %s", fname);
4943 cmd[cmdlen-1] = '\0';
4945 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
4946 cmd ? cmd : "?"));
4947 return(cmd);
4951 /*----------------------------------------------------------------------
4952 Write a berzerk format message delimiter using the given putc function
4954 Args: e -- envelope of message to write
4955 pc -- function to use
4957 Returns: TRUE if we could write it, FALSE if there was a problem
4959 NOTE: follows delimiter with OS-dependent newline
4960 ----*/
4962 bezerk_delimiter(ENVELOPE *env, MESSAGECACHE *mc, gf_io_t pc, int leading_newline)
4964 MESSAGECACHE telt;
4965 time_t when;
4966 char *p;
4968 /* write "[\n]From mailbox[@host] " */
4969 if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1)
4970 && gf_puts("From ", pc)
4971 && gf_puts((env && env->from) ? env->from->mailbox
4972 : "the-concourse-on-high", pc)
4973 && gf_puts((env && env->from && env->from->host) ? "@" : "", pc)
4974 && gf_puts((env && env->from && env->from->host) ? env->from->host
4975 : "", pc)
4976 && (*pc)(' ')))
4977 return(0);
4979 if(mc && mc->valid)
4980 when = mail_longdate(mc);
4981 else if(env && env->date && env->date[0]
4982 && mail_parse_date(&telt,env->date))
4983 when = mail_longdate(&telt);
4984 else
4985 when = time(0);
4987 p = ctime(&when);
4989 while(p && *p && *p != '\n') /* write date */
4990 if(!(*pc)(*p++))
4991 return(0);
4993 if(!gf_puts(NEWLINE, pc)) /* write terminating newline */
4994 return(0);
4996 return(1);
5000 /*----------------------------------------------------------------------
5001 Execute command to jump to a given message number
5003 Args: qline -- Line to ask question on
5005 Result: returns true if the use selected a new message, false otherwise
5007 ----*/
5008 long
5009 jump_to(MSGNO_S *msgmap, int qline, UCS first_num, SCROLL_S *sparms, CmdWhere in_index)
5011 char jump_num_string[80], *j, prompt[70];
5012 HelpType help;
5013 int rc;
5014 static ESCKEY_S jump_to_key[] = { {0, 0, NULL, NULL},
5015 /* TRANSLATORS: go to First Message */
5016 {ctrl('Y'), 10, "^Y", N_("First Msg")},
5017 {ctrl('V'), 11, "^V", N_("Last Msg")},
5018 {-1, 0, NULL, NULL} };
5020 dprint((4, "\n - jump_to -\n"));
5022 #ifdef DEBUG
5023 if(sparms && sparms->jump_is_debug)
5024 return(get_level(qline, first_num, sparms));
5025 #endif
5027 if(!any_messages(msgmap, NULL, "to Jump to"))
5028 return(0L);
5030 if(first_num && first_num < 0x80 && isdigit((unsigned char) first_num)){
5031 jump_num_string[0] = first_num;
5032 jump_num_string[1] = '\0';
5034 else
5035 jump_num_string[0] = '\0';
5037 if(mn_total_cur(msgmap) > 1L){
5038 snprintf(prompt, sizeof(prompt), "Unselect %s msgs in favor of number to be entered",
5039 comatose(mn_total_cur(msgmap)));
5040 prompt[sizeof(prompt)-1] = '\0';
5041 if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n')
5042 return(0L);
5045 snprintf(prompt, sizeof(prompt), "%s number to jump to : ", in_index == ThrdIndx
5046 ? "Thread"
5047 : "Message");
5048 prompt[sizeof(prompt)-1] = '\0';
5050 help = NO_HELP;
5051 while(1){
5052 int flags = OE_APPEND_CURRENT;
5054 rc = optionally_enter(jump_num_string, qline, 0,
5055 sizeof(jump_num_string), prompt,
5056 jump_to_key, help, &flags);
5057 if(rc == 3){
5058 help = help == NO_HELP
5059 ? (in_index == ThrdIndx ? h_oe_jump_thd : h_oe_jump)
5060 : NO_HELP;
5061 continue;
5063 else if(rc == 10 || rc == 11){
5064 char warning[100];
5065 long closest;
5067 closest = closest_jump_target(rc == 10 ? 1L
5068 : ((in_index == ThrdIndx)
5069 ? msgmap->max_thrdno
5070 : mn_get_total(msgmap)),
5071 ps_global->mail_stream,
5072 msgmap, 0,
5073 in_index, warning, sizeof(warning));
5074 /* ignore warning */
5075 return(closest);
5079 * If we take out the *jump_num_string nonempty test in this if
5080 * then the closest_jump_target routine will offer a jump to the
5081 * last message. However, it is slow because you have to wait for
5082 * the status message and it is annoying for people who hit J command
5083 * by mistake and just want to hit return to do nothing, like has
5084 * always worked. So the test is there for now. Hubert 2002-08-19
5086 * Jumping to first/last message is now possible through ^Y/^V
5087 * commands above. jpf 2002-08-21
5088 * (and through "end" hubert 2006-07-07)
5090 if(rc == 0 && *jump_num_string != '\0'){
5091 removing_leading_and_trailing_white_space(jump_num_string);
5092 for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++)
5095 if(*j != '\0'){
5096 if(!strucmp("end", j))
5097 return((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap));
5099 q_status_message(SM_ORDER | SM_DING, 2, 2,
5100 _("Invalid number entered. Use only digits 0-9"));
5101 jump_num_string[0] = '\0';
5103 else{
5104 char warning[100];
5105 long closest, jump_num;
5107 if(*jump_num_string)
5108 jump_num = atol(jump_num_string);
5109 else
5110 jump_num = -1L;
5112 warning[0] = '\0';
5113 closest = closest_jump_target(jump_num, ps_global->mail_stream,
5114 msgmap,
5115 *jump_num_string ? 0 : 1,
5116 in_index, warning, sizeof(warning));
5117 if(warning[0])
5118 q_status_message(SM_ORDER | SM_DING, 2, 2, warning);
5120 if(closest == jump_num)
5121 return(jump_num);
5123 if(closest == 0L)
5124 jump_num_string[0] = '\0';
5125 else
5126 strncpy(jump_num_string, long2string(closest),
5127 sizeof(jump_num_string));
5130 continue;
5133 if(rc != 4)
5134 break;
5137 return(0L);
5142 * cmd_delete_action - handle msgno advance and such after single message deletion
5144 char *
5145 cmd_delete_action(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
5147 int opts;
5148 long msgno;
5149 char *rv = NULL;
5151 msgno = mn_get_cur(msgmap);
5152 advance_cur_after_delete(state, state->mail_stream, msgmap, in_index);
5154 if(IS_NEWS(state->mail_stream)
5155 || ((state->context_current->use & CNTXT_INCMNG)
5156 && context_isambig(state->cur_folder))){
5158 opts = (NSF_TRUST_FLAGS | NSF_SKIP_CHID);
5159 if(in_index == View)
5160 opts &= ~NSF_SKIP_CHID;
5162 (void)next_sorted_flagged(F_UNDEL|F_UNSEEN, state->mail_stream, msgno, &opts);
5163 if(!(opts & NSF_FLAG_MATCH)){
5164 char nextfolder[MAXPATH];
5166 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
5167 nextfolder[sizeof(nextfolder)-1] = '\0';
5168 rv = next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
5169 state->context_current, NULL, NULL)
5170 ? ". Press TAB for next folder."
5171 : ". No more folders to TAB to.";
5175 return(rv);
5180 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5182 char *
5183 cmd_delete_index(struct pine *state, MSGNO_S *msgmap)
5185 return(cmd_delete_action(state, msgmap,MsgIndx));
5189 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5191 char *
5192 cmd_delete_view(struct pine *state, MSGNO_S *msgmap)
5194 return(cmd_delete_action(state, msgmap, View));
5198 void
5199 advance_cur_after_delete(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, CmdWhere in_index)
5201 long new_msgno, msgno;
5202 int opts;
5204 new_msgno = msgno = mn_get_cur(msgmap);
5205 opts = NSF_TRUST_FLAGS;
5207 if(F_ON(F_DEL_SKIPS_DEL, state)){
5209 if(THREADING() && sp_viewing_a_thread(stream))
5210 opts |= NSF_SKIP_CHID;
5212 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5214 else{
5215 mn_inc_cur(stream, msgmap,
5216 (in_index == View && THREADING()
5217 && sp_viewing_a_thread(stream))
5218 ? MH_THISTHD
5219 : (in_index == View)
5220 ? MH_ANYTHD : MH_NONE);
5221 new_msgno = mn_get_cur(msgmap);
5222 if(new_msgno != msgno)
5223 opts |= NSF_FLAG_MATCH;
5227 * Viewing_a_thread is the complicated case because we want to ignore
5228 * other threads at first and then look in other threads if we have to.
5229 * By ignoring other threads we also ignore collapsed partial threads
5230 * in our own thread.
5232 if(THREADING() && sp_viewing_a_thread(stream) && !(opts & NSF_FLAG_MATCH)){
5233 long rawno, orig_thrdno;
5234 PINETHRD_S *thrd, *topthrd = NULL;
5236 rawno = mn_m2raw(msgmap, msgno);
5237 thrd = fetch_thread(stream, rawno);
5238 if(thrd && thrd->top)
5239 topthrd = fetch_thread(stream, thrd->top);
5241 orig_thrdno = topthrd ? topthrd->thrdno : -1L;
5243 opts = NSF_TRUST_FLAGS;
5244 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5247 * If we got a match, new_msgno may be a message in
5248 * a different thread from the one we are viewing, or it could be
5249 * in a collapsed part of this thread.
5251 if(opts & NSF_FLAG_MATCH){
5252 int ret;
5253 char pmt[128];
5255 topthrd = NULL;
5256 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
5257 if(thrd && thrd->top)
5258 topthrd = fetch_thread(stream, thrd->top);
5261 * If this match is in the same thread we're already in
5262 * then we're done, else we have to ask the user and maybe
5263 * switch threads.
5265 if(!(orig_thrdno > 0L && topthrd
5266 && topthrd->thrdno == orig_thrdno)){
5268 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
5269 if(in_index == View)
5270 snprintf(pmt, sizeof(pmt),
5271 "View message in thread number %.10s",
5272 topthrd ? comatose(topthrd->thrdno) : "?");
5273 else
5274 snprintf(pmt, sizeof(pmt), "View thread number %.10s",
5275 topthrd ? comatose(topthrd->thrdno) : "?");
5277 ret = want_to(pmt, 'y', 'x', NO_HELP, WT_NORM);
5279 else
5280 ret = 'y';
5282 if(ret == 'y'){
5283 unview_thread(state, stream, msgmap);
5284 mn_set_cur(msgmap, new_msgno);
5285 if(THRD_AUTO_VIEW()
5286 && (count_lflags_in_thread(stream, topthrd, msgmap,
5287 MN_NONE) == 1)
5288 && view_thread(state, stream, msgmap, 1)){
5289 if(current_index_state)
5290 msgmap->top_after_thrd = current_index_state->msg_at_top;
5292 state->view_skipped_index = 1;
5293 state->next_screen = mail_view_screen;
5295 else{
5296 view_thread(state, stream, msgmap, 1);
5297 if(current_index_state)
5298 msgmap->top_after_thrd = current_index_state->msg_at_top;
5300 state->next_screen = SCREEN_FUN_NULL;
5303 else
5304 new_msgno = msgno; /* stick with original */
5309 mn_set_cur(msgmap, new_msgno);
5310 if(in_index != View)
5311 adjust_cur_to_visible(stream, msgmap);
5315 #ifdef DEBUG
5316 long
5317 get_level(int qline, UCS first_num, SCROLL_S *sparms)
5319 char debug_num_string[80], *j, prompt[70];
5320 HelpType help;
5321 int rc;
5322 long debug_num;
5324 if(first_num && first_num < 0x80 && isdigit((unsigned char)first_num)){
5325 debug_num_string[0] = first_num;
5326 debug_num_string[1] = '\0';
5327 debug_num = atol(debug_num_string);
5328 *(int *)(sparms->proc.data.p) = debug_num;
5329 q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s",
5330 comatose(debug_num));
5331 return(1L);
5333 else
5334 debug_num_string[0] = '\0';
5336 snprintf(prompt, sizeof(prompt), "Show debug <= this level (0-%d) : ", MAX(debug, 9));
5337 prompt[sizeof(prompt)-1] = '\0';
5339 help = NO_HELP;
5340 while(1){
5341 int flags = OE_APPEND_CURRENT;
5343 rc = optionally_enter(debug_num_string, qline, 0,
5344 sizeof(debug_num_string), prompt,
5345 NULL, help, &flags);
5346 if(rc == 3){
5347 help = help == NO_HELP ? h_oe_debuglevel : NO_HELP;
5348 continue;
5351 if(rc == 0){
5352 removing_leading_and_trailing_white_space(debug_num_string);
5353 for(j=debug_num_string; isdigit((unsigned char)*j); j++)
5356 if(*j != '\0'){
5357 q_status_message(SM_ORDER | SM_DING, 2, 2,
5358 _("Invalid number entered. Use only digits 0-9"));
5359 debug_num_string[0] = '\0';
5361 else{
5362 debug_num = atol(debug_num_string);
5363 if(debug_num < 0)
5364 q_status_message(SM_ORDER | SM_DING, 2, 2,
5365 _("Number should be >= 0"));
5366 else if(debug_num > MAX(debug,9))
5367 q_status_message1(SM_ORDER | SM_DING, 2, 2,
5368 _("Maximum is %s"), comatose(MAX(debug,9)));
5369 else{
5370 *(int *)(sparms->proc.data.p) = debug_num;
5371 q_status_message1(SM_ORDER, 0, 3,
5372 "Show debug <= level %s",
5373 comatose(debug_num));
5374 return(1L);
5378 continue;
5381 if(rc != 4)
5382 break;
5385 return(0L);
5387 #endif /* DEBUG */
5391 * Returns the message number closest to target that isn't hidden.
5392 * Make warning at least 100 chars.
5393 * A return of 0 means there is no message to jump to.
5395 long
5396 closest_jump_target(long int target, MAILSTREAM *stream, MSGNO_S *msgmap, int no_target, CmdWhere in_index, char *warning, size_t warninglen)
5398 long i, start, closest = 0L;
5399 char buf[80];
5400 long maxnum;
5402 warning[0] = '\0';
5403 maxnum = (in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap);
5405 if(no_target){
5406 target = maxnum;
5407 start = 1L;
5408 snprintf(warning, warninglen, "No %s number entered, jump to end? ",
5409 (in_index == ThrdIndx) ? "thread" : "message");
5410 warning[warninglen-1] = '\0';
5412 else if(target < 1L)
5413 start = 1L - target;
5414 else if(target > maxnum)
5415 start = target - maxnum;
5416 else
5417 start = 1L;
5419 if(target > 0L && target <= maxnum)
5420 if(in_index == ThrdIndx
5421 || !msgline_hidden(stream, msgmap, target, 0))
5422 return(target);
5424 for(i = start; target+i <= maxnum || target-i > 0L; i++){
5426 if(target+i > 0L && target+i <= maxnum &&
5427 (in_index == ThrdIndx
5428 || !msgline_hidden(stream, msgmap, target+i, 0))){
5429 closest = target+i;
5430 break;
5433 if(target-i > 0L && target-i <= maxnum &&
5434 (in_index == ThrdIndx
5435 || !msgline_hidden(stream, msgmap, target-i, 0))){
5436 closest = target-i;
5437 break;
5441 strncpy(buf, long2string(closest), sizeof(buf));
5442 buf[sizeof(buf)-1] = '\0';
5444 if(closest == 0L)
5445 strncpy(warning, "Nothing to jump to", warninglen);
5446 else if(target < 1L)
5447 snprintf(warning, warninglen, "%s number (%s) must be at least %s",
5448 (in_index == ThrdIndx) ? "Thread" : "Message",
5449 long2string(target), buf);
5450 else if(target > maxnum)
5451 snprintf(warning, warninglen, "%s number (%s) may be no more than %s",
5452 (in_index == ThrdIndx) ? "Thread" : "Message",
5453 long2string(target), buf);
5454 else if(!no_target)
5455 snprintf(warning, warninglen,
5456 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5457 long2string(target), buf);
5459 warning[warninglen-1] = '\0';
5461 return(closest);
5465 /*----------------------------------------------------------------------
5466 Prompt for folder name to open, expand the name and return it
5468 Args: qline -- Screen line to prompt on
5469 allow_list -- if 1, allow ^T to bring up collection lister
5471 Result: returns the folder name or NULL
5472 pine structure mangled_footer flag is set
5473 may call the collection lister in which case mangled screen will be set
5475 This prompts the user for the folder to open, possibly calling up
5476 the collection lister if the user types ^T.
5477 ----------------------------------------------------------------------*/
5478 char *
5479 broach_folder(int qline, int allow_list, int *notrealinbox, CONTEXT_S **context)
5481 HelpType help;
5482 static char newfolder[MAILTMPLEN];
5483 char expanded[MAXPATH+1],
5484 prompt[MAX_SCREEN_COLS+1],
5485 *last_folder, *p;
5486 unsigned char *f1, *f2, *f3;
5487 static HISTORY_S *history = NULL;
5488 CONTEXT_S *tc, *tc2;
5489 ESCKEY_S ekey[9];
5490 int rc, r, ku = -1, n, flags, last_rc = 0, inbox, done = 0;
5493 * the idea is to provide a clue for the context the file name
5494 * will be saved in (if a non-imap names is typed), and to
5495 * only show the previous if it was also in the same context
5497 help = NO_HELP;
5498 *expanded = '\0';
5499 *newfolder = '\0';
5500 last_folder = NULL;
5501 if(notrealinbox)
5502 (*notrealinbox) = 1;
5504 init_hist(&history, HISTSIZE);
5506 tc = broach_get_folder(context ? *context : NULL, &inbox, NULL);
5508 /* set up extra command option keys */
5509 rc = 0;
5510 ekey[rc].ch = (allow_list) ? ctrl('T') : 0 ;
5511 ekey[rc].rval = (allow_list) ? 2 : 0;
5512 ekey[rc].name = (allow_list) ? "^T" : "";
5513 ekey[rc++].label = (allow_list) ? N_("ToFldrs") : "";
5515 if(ps_global->context_list->next){
5516 ekey[rc].ch = ctrl('P');
5517 ekey[rc].rval = 10;
5518 ekey[rc].name = "^P";
5519 ekey[rc++].label = N_("Prev Collection");
5521 ekey[rc].ch = ctrl('N');
5522 ekey[rc].rval = 11;
5523 ekey[rc].name = "^N";
5524 ekey[rc++].label = N_("Next Collection");
5527 ekey[rc].ch = ctrl('W');
5528 ekey[rc].rval = 17;
5529 ekey[rc].name = "^W";
5530 ekey[rc++].label = N_("INBOX");
5532 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
5533 ekey[rc].ch = TAB;
5534 ekey[rc].rval = 12;
5535 ekey[rc].name = "TAB";
5536 ekey[rc++].label = N_("Complete");
5539 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
5540 ekey[rc].ch = ctrl('X');
5541 ekey[rc].rval = 14;
5542 ekey[rc].name = "^X";
5543 ekey[rc++].label = N_("ListMatches");
5546 if(ps_global->context_list->next && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5547 ekey[rc].ch = KEY_UP;
5548 ekey[rc].rval = 10;
5549 ekey[rc].name = "";
5550 ekey[rc++].label = "";
5552 ekey[rc].ch = KEY_DOWN;
5553 ekey[rc].rval = 11;
5554 ekey[rc].name = "";
5555 ekey[rc++].label = "";
5557 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5558 ekey[rc].ch = KEY_UP;
5559 ekey[rc].rval = 30;
5560 ekey[rc].name = "";
5561 ku = rc;
5562 ekey[rc++].label = "";
5564 ekey[rc].ch = KEY_DOWN;
5565 ekey[rc].rval = 31;
5566 ekey[rc].name = "";
5567 ekey[rc++].label = "";
5570 ekey[rc].ch = -1;
5572 while(!done) {
5574 * Figure out next default value for this context. The idea
5575 * is that in each context the last folder opened is cached.
5576 * It's up to pick it out and display it. This is fine
5577 * and dandy if we've currently got the inbox open, BUT
5578 * if not, make the inbox the default the first time thru.
5580 if(!inbox){
5581 last_folder = ps_global->inbox_name;
5582 inbox = 1; /* pretend we're in inbox from here on out */
5584 else
5585 last_folder = (ps_global->last_unambig_folder[0])
5586 ? ps_global->last_unambig_folder
5587 : ((tc->last_folder[0]) ? tc->last_folder : NULL);
5589 if(last_folder){ /* MAXPATH + 1 = sizeof(expanded) */
5590 unsigned char *fname = folder_name_decoded((unsigned char *)last_folder);
5591 snprintf(expanded, sizeof(expanded), " [%.*s]", MAXPATH+1-5,
5592 fname ? (char *) fname : last_folder);
5593 if(fname) fs_give((void **)&fname);
5595 else
5596 *expanded = '\0';
5598 expanded[sizeof(expanded)-1] = '\0';
5600 /* only show collection number if more than one available */
5601 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5602 snprintf(prompt, sizeof(prompt), "GOTO %s in <%s> %.*s%s: ",
5603 NEWS_TEST(tc) ? "news group" : "folder",
5604 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5605 *expanded ? " " : "");
5606 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5607 snprintf(prompt, sizeof(prompt), "GOTO folder %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5608 *expanded ? " " : "");
5610 prompt[sizeof(prompt)-1] = '\0';
5612 if(utf8_width(prompt) > MAXPROMPT){
5613 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5614 snprintf(prompt, sizeof(prompt), "GOTO <%s> %.*s%s: ",
5615 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5616 *expanded ? " " : "");
5617 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5618 snprintf(prompt, sizeof(prompt), "GOTO %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5619 *expanded ? " " : "");
5621 prompt[sizeof(prompt)-1] = '\0';
5623 if(utf8_width(prompt) > MAXPROMPT){
5624 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5625 snprintf(prompt, sizeof(prompt), "<%s> %.*s%s: ",
5626 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5627 *expanded ? " " : "");
5628 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5629 snprintf(prompt, sizeof(prompt), "%.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5630 *expanded ? " " : "");
5632 prompt[sizeof(prompt)-1] = '\0';
5636 if(ku >= 0){
5637 if(items_in_hist(history) > 1){
5638 ekey[ku].name = HISTORY_UP_KEYNAME;
5639 ekey[ku].label = HISTORY_KEYLABEL;
5640 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
5641 ekey[ku+1].label = HISTORY_KEYLABEL;
5643 else{
5644 ekey[ku].name = "";
5645 ekey[ku].label = "";
5646 ekey[ku+1].name = "";
5647 ekey[ku+1].label = "";
5651 /* is there any other way to do this? The point is that we
5652 * are trying to hide mutf7 from the user, and use the utf8
5653 * equivalent. So we create a variable f to take place of
5654 * newfolder, including content and size. f2 is copy of f1
5655 * that has to freed. Sigh!
5657 f3 = (unsigned char *) cpystr(newfolder);
5658 f1 = fs_get(sizeof(newfolder));
5659 f2 = folder_name_decoded(f3);
5660 if(f3) fs_give((void **)&f3);
5661 strncpy((char *)f1, (char *)f2, sizeof(newfolder));
5662 f1[sizeof(newfolder)-1] = '\0';
5663 if(f2) fs_give((void **)&f2);
5665 flags = OE_APPEND_CURRENT;
5666 rc = optionally_enter((char *) f1, qline, 0, sizeof(newfolder),
5667 (char *) prompt, ekey, help, &flags);
5669 f2 = folder_name_encoded(f1);
5670 strncpy(newfolder, (char *)f2, sizeof(newfolder));
5671 if(f1) fs_give((void **)&f1);
5672 if(f2) fs_give((void **)&f2);
5674 ps_global->mangled_footer = 1;
5676 switch(rc){
5677 case -1 : /* o_e says error! */
5678 q_status_message(SM_ORDER | SM_DING, 3, 3,
5679 _("Error reading folder name"));
5680 return(NULL);
5682 case 0 : /* o_e says normal entry */
5683 removing_trailing_white_space(newfolder);
5684 removing_leading_white_space(newfolder);
5686 if(*newfolder){
5687 char *name, *fullname = NULL;
5688 int exists, breakout = 0;
5690 save_hist(history, newfolder, 0, tc);
5692 if(!(name = folder_is_nick(newfolder, FOLDERS(tc),
5693 FN_WHOLE_NAME)))
5694 name = newfolder;
5696 if(update_folder_spec(expanded, sizeof(expanded), name)){
5697 strncpy(name = newfolder, expanded, sizeof(newfolder));
5698 newfolder[sizeof(newfolder)-1] = '\0';
5701 exists = folder_name_exists(tc, name, &fullname);
5703 if(fullname){
5704 strncpy(name = newfolder, fullname, sizeof(newfolder));
5705 newfolder[sizeof(newfolder)-1] = '\0';
5706 fs_give((void **) &fullname);
5707 breakout = TRUE;
5711 * if we know the things a folder, open it.
5712 * else if we know its a directory, visit it.
5713 * else we're not sure (it either doesn't really
5714 * exist or its unLISTable) so try opening it anyway
5716 if(exists & FEX_ISFILE){
5717 done++;
5718 break;
5720 else if((exists & FEX_ISDIR)){
5721 if(breakout){
5722 CONTEXT_S *fake_context;
5723 char tmp[MAILTMPLEN];
5724 size_t l;
5726 strncpy(tmp, name, sizeof(tmp));
5727 tmp[sizeof(tmp)-2-1] = '\0';
5728 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
5729 if(l < sizeof(tmp)){
5730 tmp[l] = tc->dir->delim;
5731 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
5734 else
5735 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
5737 tmp[sizeof(tmp)-1] = '\0';
5739 fake_context = new_context(tmp, 0);
5740 newfolder[0] = '\0';
5741 done = display_folder_list(&fake_context, newfolder,
5742 1, folders_for_goto);
5743 free_context(&fake_context);
5744 break;
5746 else if(!(tc->use & CNTXT_INCMNG)){
5747 done = display_folder_list(&tc, newfolder,
5748 1, folders_for_goto);
5749 break;
5752 else if((exists & FEX_ERROR)){
5753 q_status_message1(SM_ORDER, 0, 3,
5754 _("Problem accessing folder \"%s\""),
5755 newfolder);
5756 return(NULL);
5758 else{
5759 done++;
5760 break;
5763 if(exists == FEX_ERROR)
5764 q_status_message1(SM_ORDER, 0, 3,
5765 _("Problem accessing folder \"%s\""),
5766 newfolder);
5767 else if(tc->use & CNTXT_INCMNG)
5768 q_status_message1(SM_ORDER, 0, 3,
5769 _("Can't find Incoming Folder: %s"),
5770 newfolder);
5771 else if(context_isambig(newfolder))
5772 q_status_message2(SM_ORDER, 0, 3,
5773 _("Can't find folder \"%s\" in %s"),
5774 newfolder, (void *) tc->nickname);
5775 else
5776 q_status_message1(SM_ORDER, 0, 3,
5777 _("Can't find folder \"%s\""),
5778 newfolder);
5780 return(NULL);
5782 else if(last_folder){
5783 if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX
5784 && !strucmp(last_folder, ps_global->inbox_name)
5785 && tc == ((ps_global->context_list->use & CNTXT_INCMNG)
5786 ? ps_global->context_list->next : ps_global->context_list)){
5787 if(notrealinbox)
5788 (*notrealinbox) = 0;
5790 tc = ps_global->context_list;
5793 strncpy(newfolder, last_folder, sizeof(newfolder));
5794 newfolder[sizeof(newfolder)-1] = '\0';
5795 save_hist(history, newfolder, 0, tc);
5796 done++;
5797 break;
5799 /* fall thru like they cancelled */
5801 case 1 : /* o_e says user cancel */
5802 cmd_cancelled("Open folder");
5803 return(NULL);
5805 case 2 : /* o_e says user wants list */
5806 r = display_folder_list(&tc, newfolder, 0, folders_for_goto);
5807 if(r)
5808 done++;
5810 break;
5812 case 3 : /* o_e says user wants help */
5813 help = help == NO_HELP ? h_oe_broach : NO_HELP;
5814 break;
5816 case 4 : /* redraw */
5817 break;
5819 case 10 : /* Previous collection */
5820 tc2 = ps_global->context_list;
5821 while(tc2->next && tc2->next != tc)
5822 tc2 = tc2->next;
5824 tc = tc2;
5825 break;
5827 case 11 : /* Next collection */
5828 tc = (tc->next) ? tc->next : ps_global->context_list;
5829 break;
5831 case 12 : /* file name completion */
5832 if(!folder_complete(tc, newfolder, sizeof(newfolder), &n)){
5833 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
5834 r = display_folder_list(&tc, newfolder, 1,folders_for_goto);
5835 if(r)
5836 done++; /* bingo! */
5837 else
5838 rc = 0; /* burn last_rc */
5840 else
5841 Writechar(BELL, 0);
5844 break;
5846 case 14 : /* file name completion */
5847 r = display_folder_list(&tc, newfolder, 2, folders_for_goto);
5848 if(r)
5849 done++; /* bingo! */
5850 else
5851 rc = 0; /* burn last_rc */
5853 break;
5855 case 17 : /* GoTo INBOX */
5856 done++;
5857 strncpy(newfolder, ps_global->inbox_name, sizeof(newfolder)-1);
5858 newfolder[sizeof(newfolder)-1] = '\0';
5859 if(notrealinbox)
5860 (*notrealinbox) = 0;
5862 tc = ps_global->context_list;
5863 save_hist(history, newfolder, 0, tc);
5865 break;
5867 case 30 :
5868 if((p = get_prev_hist(history, newfolder, 0, tc)) != NULL){
5869 strncpy(newfolder, p, sizeof(newfolder));
5870 newfolder[sizeof(newfolder)-1] = '\0';
5871 if(history->hist[history->curindex])
5872 tc = history->hist[history->curindex]->cntxt;
5874 else
5875 Writechar(BELL, 0);
5877 break;
5879 case 31 :
5880 if((p = get_next_hist(history, newfolder, 0, tc)) != NULL){
5881 strncpy(newfolder, p, sizeof(newfolder));
5882 newfolder[sizeof(newfolder)-1] = '\0';
5883 if(history->hist[history->curindex])
5884 tc = history->hist[history->curindex]->cntxt;
5886 else
5887 Writechar(BELL, 0);
5889 break;
5891 default :
5892 alpine_panic("Unhandled case");
5893 break;
5896 last_rc = rc;
5899 dprint((2, "broach folder, name entered \"%s\"\n",
5900 newfolder ? newfolder : "?"));
5902 /*-- Just check that we can expand this. It gets done for real later --*/
5903 strncpy(expanded, newfolder, sizeof(expanded));
5904 expanded[sizeof(expanded)-1] = '\0';
5906 if(!expand_foldername(expanded, sizeof(expanded))) {
5907 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
5908 expanded ? expanded : "?"));
5909 return(NULL);
5912 *context = tc;
5913 return(newfolder);
5917 /*----------------------------------------------------------------------
5918 Check to see if user wants to reopen dead stream.
5920 Args: ps --
5921 reopenp --
5923 Result: 1 if the folder was successfully updatedn
5924 0 if not necessary
5926 ----*/
5928 ask_mailbox_reopen(struct pine *ps, int *reopenp)
5930 if(((ps->mail_stream->dtb
5931 && ((ps->mail_stream->dtb->flags & DR_NONEWMAIL)
5932 || (ps->mail_stream->rdonly
5933 && ps->mail_stream->dtb->flags & DR_NONEWMAILRONLY)))
5934 && (ps->reopen_rule == REOPEN_ASK_ASK_Y
5935 || ps->reopen_rule == REOPEN_ASK_ASK_N
5936 || ps->reopen_rule == REOPEN_ASK_NO_Y
5937 || ps->reopen_rule == REOPEN_ASK_NO_N))
5938 || ((ps->mail_stream->dtb
5939 && ps->mail_stream->rdonly
5940 && !(ps->mail_stream->dtb->flags & DR_LOCAL))
5941 && (ps->reopen_rule == REOPEN_YES_ASK_Y
5942 || ps->reopen_rule == REOPEN_YES_ASK_N
5943 || ps->reopen_rule == REOPEN_ASK_ASK_Y
5944 || ps->reopen_rule == REOPEN_ASK_ASK_N))){
5945 int deefault;
5947 switch(ps->reopen_rule){
5948 case REOPEN_YES_ASK_Y:
5949 case REOPEN_ASK_ASK_Y:
5950 case REOPEN_ASK_NO_Y:
5951 deefault = 'y';
5952 break;
5954 default:
5955 deefault = 'n';
5956 break;
5959 switch(want_to("Re-open folder to check for new messages", deefault,
5960 'x', h_reopen_folder, WT_NORM)){
5961 case 'y':
5962 (*reopenp)++;
5963 break;
5965 case 'x':
5966 return(-1);
5970 return(0);
5975 /*----------------------------------------------------------------------
5976 Check to see if user input is in form of old c-client mailbox speck
5978 Args: old --
5979 new --
5981 Result: 1 if the folder was successfully updatedn
5982 0 if not necessary
5984 ----*/
5986 update_folder_spec(char *new, size_t newlen, char *old)
5988 char *p, *orignew;
5989 int nntp = 0;
5991 orignew = new;
5992 if(*(p = old) == '*') /* old form? */
5993 old++;
5995 if(*old == '{') /* copy host spec */
5997 switch(*new = *old++){
5998 case '\0' :
5999 return(FALSE);
6001 case '/' :
6002 if(!struncmp(old, "nntp", 4))
6003 nntp++;
6005 break;
6007 default :
6008 break;
6010 while(*new++ != '}' && (new-orignew) < newlen-1);
6012 if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){
6014 * OK, some heuristics here. If it looks like a newsgroup
6015 * then we plunk it into the #news namespace else we
6016 * assume that they're trying to get at a #public folder...
6018 for(p = old;
6019 *p && (isalnum((unsigned char) *p) || strindex(".-", *p));
6020 p++)
6023 sstrncpy(&new, (*p && !nntp) ? "#public/" : "#news.", newlen-(new-orignew));
6024 strncpy(new, old, newlen-(new-orignew));
6025 return(TRUE);
6028 orignew[newlen-1] = '\0';
6030 return(FALSE);
6034 /*----------------------------------------------------------------------
6035 Open the requested folder in the requested context
6037 Args: state -- usual pine state struct
6038 newfolder -- folder to open
6039 new_context -- folder context might live in
6040 stream -- candidate for recycling
6042 Result: New folder open or not (if error), and we're set to
6043 enter the index screen.
6044 ----*/
6045 void
6046 visit_folder(struct pine *state, char *newfolder, CONTEXT_S *new_context,
6047 MAILSTREAM *stream, long unsigned int flags)
6049 dprint((9, "visit_folder(%s, %s)\n",
6050 newfolder ? newfolder : "?",
6051 (new_context && new_context->context)
6052 ? new_context->context : "(NULL)"));
6054 if(ps_global && ps_global->ttyo){
6055 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
6056 ps_global->mangled_footer = 1;
6059 if(do_broach_folder(newfolder, new_context, stream ? &stream : NULL,
6060 flags) >= 0
6061 || !sp_flagged(state->mail_stream, SP_LOCKED))
6062 state->next_screen = mail_index_screen;
6063 else
6064 state->next_screen = folder_screen;
6068 /*----------------------------------------------------------------------
6069 Move read messages from folder if listed in archive
6071 Args:
6073 ----*/
6075 read_msg_prompt(long int n, char *f)
6077 char buf[MAX_SCREEN_COLS+1];
6079 snprintf(buf, sizeof(buf), "Save the %ld read message%s in \"%s\"", n, plural(n), f);
6080 buf[sizeof(buf)-1] = '\0';
6081 return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y');
6085 /*----------------------------------------------------------------------
6086 Print current message[s] or folder index
6088 Args: state -- pointer to struct holding a bunch of pine state
6089 msgmap -- table mapping msg nums to c-client sequence nums
6090 aopt -- aggregate options
6091 in_index -- boolean indicating we're called from Index Screen
6093 Filters the original header and sends stuff to printer
6094 ---*/
6096 cmd_print(struct pine *state, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
6098 char prompt[250];
6099 long i, msgs, rawno;
6100 int next = 0, do_index = 0, rv = 0;
6101 ENVELOPE *e;
6102 BODY *b;
6103 MESSAGECACHE *mc;
6105 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
6106 return rv;
6108 msgs = mn_total_cur(msgmap);
6110 if((in_index != View) && F_ON(F_PRINT_INDEX, state)){
6111 char m[10];
6112 int ans;
6113 static ESCKEY_S prt_opts[] = {
6114 {'i', 'i', "I", N_("Index")},
6115 {'m', 'm', "M", NULL},
6116 {-1, 0, NULL, NULL}};
6118 if(in_index == ThrdIndx){
6119 /* TRANSLATORS: This is a question, Print Index ? */
6120 if(want_to(_("Print Index"), 'y', 'x', NO_HELP, WT_NORM) == 'y')
6121 ans = 'i';
6122 else
6123 ans = 'x';
6125 else{
6126 snprintf(m, sizeof(m), "Message%s", (msgs>1L) ? "s" : "");
6127 m[sizeof(m)-1] = '\0';
6128 prt_opts[1].label = m;
6129 snprintf(prompt, sizeof(prompt), "Print %sFolder Index or %s %s? ",
6130 (aopt & MCMD_AGG_2) ? "thread " : MCMD_ISAGG(aopt) ? "selected " : "",
6131 (aopt & MCMD_AGG_2) ? "thread" : MCMD_ISAGG(aopt) ? "selected" : "current", m);
6132 prompt[sizeof(prompt)-1] = '\0';
6134 ans = radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x',
6135 NO_HELP, RB_NORM|RB_SEQ_SENSITIVE);
6138 switch(ans){
6139 case 'x' :
6140 cmd_cancelled("Print");
6141 if(MCMD_ISAGG(aopt))
6142 restore_selected(msgmap);
6144 return rv;
6146 case 'i':
6147 do_index = 1;
6148 break;
6150 default :
6151 case 'm':
6152 break;
6156 if(do_index)
6157 snprintf(prompt, sizeof(prompt), "%sFolder Index",
6158 (aopt & MCMD_AGG_2) ? "Thread " : MCMD_ISAGG(aopt) ? "Selected " : "");
6159 else if(msgs > 1L)
6160 snprintf(prompt, sizeof(prompt), "%s messages", long2string(msgs));
6161 else
6162 snprintf(prompt, sizeof(prompt), "Message %s", long2string(mn_get_cur(msgmap)));
6164 prompt[sizeof(prompt)-1] = '\0';
6166 if(open_printer(prompt) < 0){
6167 if(MCMD_ISAGG(aopt))
6168 restore_selected(msgmap);
6170 return rv;
6173 if(do_index){
6174 TITLE_S *tc;
6176 tc = format_titlebar();
6178 /* Print titlebar... */
6179 print_text1("%s\n\n", tc ? tc->titlebar_line : "");
6180 /* then all the index members... */
6181 if(!print_index(state, msgmap, MCMD_ISAGG(aopt)))
6182 q_status_message(SM_ORDER | SM_DING, 3, 3,
6183 _("Error printing folder index"));
6184 else
6185 rv++;
6187 else{
6188 rv++;
6189 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){
6190 if(next && F_ON(F_AGG_PRINT_FF, state))
6191 if(!print_char(FORMFEED)){
6192 rv = 0;
6193 break;
6196 if(!(state->mail_stream
6197 && (rawno = mn_m2raw(msgmap, i)) > 0L
6198 && rawno <= state->mail_stream->nmsgs
6199 && (mc = mail_elt(state->mail_stream, rawno))
6200 && mc->valid))
6201 mc = NULL;
6203 if(!(e=pine_mail_fetchstructure(state->mail_stream,
6204 mn_m2raw(msgmap,i),
6205 &b))
6206 || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global)
6207 && !bezerk_delimiter(e, mc, print_char, next))
6208 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
6209 e, b, NULL, FM_NEW_MESS | FM_NOINDENT,
6210 print_char)){
6211 q_status_message(SM_ORDER | SM_DING, 3, 3,
6212 _("Error printing message"));
6213 rv = 0;
6214 break;
6219 close_printer();
6221 if(MCMD_ISAGG(aopt))
6222 restore_selected(msgmap);
6224 return rv;
6228 /*----------------------------------------------------------------------
6229 Pipe message text
6231 Args: state -- various pine state bits
6232 msgmap -- Message number mapping table
6233 aopt -- option flags
6235 Filters the original header and sends stuff to specified command
6236 ---*/
6238 cmd_pipe(struct pine *state, MSGNO_S *msgmap, int aopt)
6240 ENVELOPE *e;
6241 MESSAGECACHE *mc;
6242 BODY *b;
6243 PIPE_S *syspipe;
6244 char *resultfilename = NULL, prompt[80], *p;
6245 int done = 0, rv = 0;
6246 gf_io_t pc;
6247 int fourlabel = -1, j = 0, next = 0, ku;
6248 int pipe_rv; /* rv of proc to separate from close_system_pipe rv */
6249 long i, rawno;
6250 unsigned flagsforhist = 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6251 static HISTORY_S *history = NULL;
6252 int capture = 1, raw = 0, delimit = 0, newpipe = 0;
6253 char pipe_command[MAXPATH];
6254 ESCKEY_S pipe_opt[8];
6256 if(ps_global->restricted){
6257 q_status_message(SM_ORDER | SM_DING, 0, 4,
6258 "Alpine demo can't pipe messages");
6259 return rv;
6261 else if(!any_messages(msgmap, NULL, "to Pipe"))
6262 return rv;
6264 pipe_command[0] = '\0';
6265 init_hist(&history, HISTSIZE);
6266 flagsforhist = (raw ? 0x8 : 0) +
6267 (delimit ? 0x4 : 0) +
6268 (newpipe ? 0x2 : 0) +
6269 (capture ? 0x1 : 0);
6270 if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){
6271 strncpy(pipe_command, p, sizeof(pipe_command));
6272 pipe_command[sizeof(pipe_command)-1] = '\0';
6273 if(history->hist[history->curindex]){
6274 flagsforhist = history->hist[history->curindex]->flags;
6275 raw = (flagsforhist & 0x8) ? 1 : 0;
6276 delimit = (flagsforhist & 0x4) ? 1 : 0;
6277 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6278 capture = (flagsforhist & 0x1) ? 1 : 0;
6282 pipe_opt[j].ch = 0;
6283 pipe_opt[j].rval = 0;
6284 pipe_opt[j].name = "";
6285 pipe_opt[j++].label = "";
6287 pipe_opt[j].ch = ctrl('W');
6288 pipe_opt[j].rval = 10;
6289 pipe_opt[j].name = "^W";
6290 pipe_opt[j++].label = NULL;
6292 pipe_opt[j].ch = ctrl('Y');
6293 pipe_opt[j].rval = 11;
6294 pipe_opt[j].name = "^Y";
6295 pipe_opt[j++].label = NULL;
6297 pipe_opt[j].ch = ctrl('R');
6298 pipe_opt[j].rval = 12;
6299 pipe_opt[j].name = "^R";
6300 pipe_opt[j++].label = NULL;
6302 if(MCMD_ISAGG(aopt)){
6303 if(!pseudo_selected(state->mail_stream, msgmap))
6304 return rv;
6305 else{
6306 fourlabel = j;
6307 pipe_opt[j].ch = ctrl('T');
6308 pipe_opt[j].rval = 13;
6309 pipe_opt[j].name = "^T";
6310 pipe_opt[j++].label = NULL;
6314 pipe_opt[j].ch = KEY_UP;
6315 pipe_opt[j].rval = 30;
6316 pipe_opt[j].name = "";
6317 ku = j;
6318 pipe_opt[j++].label = "";
6320 pipe_opt[j].ch = KEY_DOWN;
6321 pipe_opt[j].rval = 31;
6322 pipe_opt[j].name = "";
6323 pipe_opt[j++].label = "";
6325 pipe_opt[j].ch = -1;
6327 while (!done) {
6328 int flags;
6330 snprintf(prompt, sizeof(prompt), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6331 raw ? "RAW " : "",
6332 MCMD_ISAGG(aopt) ? "s" : " ",
6333 MCMD_ISAGG(aopt) ? "" : comatose(mn_get_cur(msgmap)),
6334 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? "(" : "",
6335 capture ? "" : "uncaptured",
6336 (!capture && delimit) ? "," : "",
6337 delimit ? "delimited" : "",
6338 ((!capture || delimit) && newpipe && MCMD_ISAGG(aopt)) ? "," : "",
6339 (newpipe && MCMD_ISAGG(aopt)) ? "new pipe" : "",
6340 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? ") " : "");
6341 prompt[sizeof(prompt)-1] = '\0';
6342 pipe_opt[1].label = raw ? N_("Shown Text") : N_("Raw Text");
6343 pipe_opt[2].label = capture ? N_("Free Output") : N_("Capture Output");
6344 pipe_opt[3].label = delimit ? N_("No Delimiter") : N_("With Delimiter");
6345 if(fourlabel > 0)
6346 pipe_opt[fourlabel].label = newpipe ? N_("To Same Pipe") : N_("To Individual Pipes");
6350 * 2 is really 1 because there will be one real entry and
6351 * one entry of "" because of the get_prev_hist above.
6353 if(items_in_hist(history) > 2){
6354 pipe_opt[ku].name = HISTORY_UP_KEYNAME;
6355 pipe_opt[ku].label = HISTORY_KEYLABEL;
6356 pipe_opt[ku+1].name = HISTORY_DOWN_KEYNAME;
6357 pipe_opt[ku+1].label = HISTORY_KEYLABEL;
6359 else{
6360 pipe_opt[ku].name = "";
6361 pipe_opt[ku].label = "";
6362 pipe_opt[ku+1].name = "";
6363 pipe_opt[ku+1].label = "";
6366 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
6367 switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0,
6368 sizeof(pipe_command), prompt,
6369 pipe_opt, NO_HELP, &flags)){
6370 case -1 :
6371 q_status_message(SM_ORDER | SM_DING, 3, 4,
6372 _("Internal problem encountered"));
6373 done++;
6374 break;
6376 case 10 : /* flip raw bit */
6377 raw = !raw;
6378 break;
6380 case 11 : /* flip capture bit */
6381 capture = !capture;
6382 break;
6384 case 12 : /* flip delimit bit */
6385 delimit = !delimit;
6386 break;
6388 case 13 : /* flip newpipe bit */
6389 newpipe = !newpipe;
6390 break;
6392 case 30 :
6393 flagsforhist = (raw ? 0x8 : 0) +
6394 (delimit ? 0x4 : 0) +
6395 (newpipe ? 0x2 : 0) +
6396 (capture ? 0x1 : 0);
6397 if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6398 strncpy(pipe_command, p, sizeof(pipe_command));
6399 pipe_command[sizeof(pipe_command)-1] = '\0';
6400 if(history->hist[history->curindex]){
6401 flagsforhist = history->hist[history->curindex]->flags;
6402 raw = (flagsforhist & 0x8) ? 1 : 0;
6403 delimit = (flagsforhist & 0x4) ? 1 : 0;
6404 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6405 capture = (flagsforhist & 0x1) ? 1 : 0;
6408 else
6409 Writechar(BELL, 0);
6411 break;
6413 case 31 :
6414 flagsforhist = (raw ? 0x8 : 0) +
6415 (delimit ? 0x4 : 0) +
6416 (newpipe ? 0x2 : 0) +
6417 (capture ? 0x1 : 0);
6418 if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6419 strncpy(pipe_command, p, sizeof(pipe_command));
6420 pipe_command[sizeof(pipe_command)-1] = '\0';
6421 if(history->hist[history->curindex]){
6422 flagsforhist = history->hist[history->curindex]->flags;
6423 raw = (flagsforhist & 0x8) ? 1 : 0;
6424 delimit = (flagsforhist & 0x4) ? 1 : 0;
6425 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6426 capture = (flagsforhist & 0x1) ? 1 : 0;
6429 else
6430 Writechar(BELL, 0);
6432 break;
6434 case 0 :
6435 if(pipe_command[0]){
6437 flagsforhist = (raw ? 0x8 : 0) +
6438 (delimit ? 0x4 : 0) +
6439 (newpipe ? 0x2 : 0) +
6440 (capture ? 0x1 : 0);
6441 save_hist(history, pipe_command, flagsforhist, NULL);
6443 flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
6444 flags |= (raw ? PIPE_RAW : 0);
6445 if(!capture){
6446 #ifndef _WINDOWS
6447 ClearScreen();
6448 fflush(stdout);
6449 clear_cursor_pos();
6450 ps_global->mangled_screen = 1;
6451 ps_global->in_init_seq = 1;
6452 #endif
6453 flags |= PIPE_RESET;
6456 if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command,
6457 (flags & PIPE_RESET)
6458 ? NULL
6459 : &resultfilename,
6460 flags, &pc)))
6461 done++;
6463 for(i = mn_first_cur(msgmap);
6464 i > 0L && !done;
6465 i = mn_next_cur(msgmap)){
6466 e = pine_mail_fetchstructure(ps_global->mail_stream,
6467 mn_m2raw(msgmap, i), &b);
6468 if(!(state->mail_stream
6469 && (rawno = mn_m2raw(msgmap, i)) > 0L
6470 && rawno <= state->mail_stream->nmsgs
6471 && (mc = mail_elt(state->mail_stream, rawno))
6472 && mc->valid))
6473 mc = NULL;
6475 if((newpipe
6476 && !(syspipe = cmd_pipe_open(pipe_command,
6477 (flags & PIPE_RESET)
6478 ? NULL
6479 : &resultfilename,
6480 flags, &pc)))
6481 || (delimit && !bezerk_delimiter(e, mc, pc, next++)))
6482 done++;
6484 if(!done){
6485 if(raw){
6486 char *pipe_err;
6488 prime_raw_pipe_getc(ps_global->mail_stream,
6489 mn_m2raw(msgmap, i), -1L, 0L);
6490 gf_filter_init();
6491 gf_link_filter(gf_nvtnl_local, NULL);
6492 if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){
6493 q_status_message1(SM_ORDER|SM_DING,
6494 3, 3,
6495 _("Internal Error: %s"),
6496 pipe_err);
6497 done++;
6500 else if(!format_message(mn_m2raw(msgmap, i), e, b,
6501 NULL, FM_NEW_MESS | FM_NOWRAP, pc))
6502 done++;
6505 if(newpipe)
6506 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6507 done++;
6510 if(!capture)
6511 ps_global->in_init_seq = 0;
6513 if(!newpipe)
6514 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6515 done++;
6516 if(done) /* say we had a problem */
6517 q_status_message(SM_ORDER | SM_DING, 3, 3,
6518 _("Error piping message"));
6519 else if(resultfilename){
6520 rv++;
6521 /* only display if no error */
6522 display_output_file(resultfilename, "PIPE MESSAGE",
6523 NULL, DOF_EMPTY);
6524 fs_give((void **)&resultfilename);
6526 else{
6527 rv++;
6528 q_status_message(SM_ORDER, 0, 2, _("Pipe command completed"));
6531 done++;
6532 break;
6534 /* else fall thru as if cancelled */
6536 case 1 :
6537 cmd_cancelled("Pipe command");
6538 done++;
6539 break;
6541 case 3 :
6542 helper(h_common_pipe, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE);
6543 ps_global->mangled_screen = 1;
6544 break;
6546 case 2 : /* no place to escape to */
6547 case 4 : /* can't suspend */
6548 default :
6549 break;
6553 ps_global->mangled_footer = 1;
6554 if(MCMD_ISAGG(aopt))
6555 restore_selected(msgmap);
6557 return rv;
6561 /*----------------------------------------------------------------------
6562 Screen to offer list management commands contained in message
6564 Args: state -- pointer to struct holding a bunch of pine state
6565 msgmap -- table mapping msg nums to c-client sequence nums
6566 aopt -- aggregate options
6568 Result:
6570 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6571 ----*/
6572 void
6573 rfc2369_display(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno)
6575 int winner = 0;
6576 char *h, *hdrs[MLCMD_COUNT + 1];
6577 long index_no = mn_raw2m(msgmap, msgno);
6578 RFC2369_S data[MLCMD_COUNT];
6580 /* for each header field */
6581 if((h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))) != NULL){
6582 memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT);
6583 if(rfc2369_parse_fields(h, &data[0])){
6584 STORE_S *explain;
6586 if((explain = list_mgmt_text(data, index_no)) != NULL){
6587 list_mgmt_screen(explain);
6588 ps_global->mangled_screen = 1;
6589 so_give(&explain);
6590 winner++;
6594 fs_give((void **) &h);
6597 if(!winner)
6598 q_status_message1(SM_ORDER, 0, 3,
6599 "Message %s contains no list management information",
6600 comatose(index_no));
6604 STORE_S *
6605 list_mgmt_text(RFC2369_S *data, long int msgno)
6607 STORE_S *store;
6608 int i, j, n, fields = 0;
6609 static char *rfc2369_intro1 =
6610 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6611 static char *rfc2369_intro2[] = {
6612 N_(" has information associated with it "),
6613 N_("that explains how to participate in an email list. An "),
6614 N_("email list is represented by a single email address that "),
6615 N_("users sharing a common interest can send messages to (known "),
6616 N_("as posting) which are then redistributed to all members "),
6617 N_("of the list (sometimes after review by a moderator)."),
6618 N_("<P>List participation commands in this message include:"),
6619 NULL
6622 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6624 /* Insert introductory text */
6625 so_puts(store, rfc2369_intro1);
6627 so_puts(store, comatose(msgno));
6629 for(i = 0; rfc2369_intro2[i]; i++)
6630 so_puts(store, _(rfc2369_intro2[i]));
6632 so_puts(store, "<P>");
6633 for(i = 0; i < MLCMD_COUNT; i++)
6634 if(data[i].data[0].value
6635 || data[i].data[0].comment
6636 || data[i].data[0].error){
6637 if(!fields++)
6638 so_puts(store, "<UL>");
6640 so_puts(store, "<LI>");
6641 so_puts(store,
6642 (n = (data[i].data[1].value || data[i].data[1].comment))
6643 ? "Methods to "
6644 : "A method to ");
6646 so_puts(store, data[i].field.description);
6647 so_puts(store, ". ");
6649 if(n)
6650 so_puts(store, "<OL>");
6652 for(j = 0;
6653 j < MLCMD_MAXDATA
6654 && (data[i].data[j].comment
6655 || data[i].data[j].value
6656 || data[i].data[j].error);
6657 j++){
6659 so_puts(store, n ? "<P><LI>" : "<P>");
6661 if(data[i].data[j].comment){
6662 so_puts(store,
6663 _("With the provided comment:<P><BLOCKQUOTE>"));
6664 so_puts(store, data[i].data[j].comment);
6665 so_puts(store, "</BLOCKQUOTE><P>");
6668 if(data[i].data[j].value){
6669 if(i == MLCMD_POST
6670 && !strucmp(data[i].data[j].value, "NO")){
6671 so_puts(store,
6672 _("Posting is <EM>not</EM> allowed on this list"));
6674 else{
6675 so_puts(store, "Select <A HREF=\"");
6676 so_puts(store, data[i].data[j].value);
6677 so_puts(store, "\">HERE</A> to ");
6678 so_puts(store, (data[i].field.action)
6679 ? data[i].field.action
6680 : "try it");
6683 so_puts(store, ".");
6686 if(data[i].data[j].error){
6687 so_puts(store, "<P>Unfortunately, Alpine can not offer");
6688 so_puts(store, " to take direct action based upon it");
6689 so_puts(store, " because it was improperly formatted.");
6690 so_puts(store, " The unrecognized data associated with");
6691 so_puts(store, " the \"");
6692 so_puts(store, data[i].field.name);
6693 so_puts(store, "\" header field was:<P><BLOCKQUOTE>");
6694 so_puts(store, data[i].data[j].error);
6695 so_puts(store, "</BLOCKQUOTE>");
6698 so_puts(store, "<P>");
6701 if(n)
6702 so_puts(store, "</OL>");
6705 if(fields)
6706 so_puts(store, "</UL>");
6708 so_puts(store, "</BODY></HTML>");
6711 return(store);
6715 void
6716 list_mgmt_screen(STORE_S *html)
6718 int cmd = MC_NONE;
6719 long offset = 0L;
6720 char *error = NULL;
6721 STORE_S *store;
6722 HANDLE_S *handles = NULL;
6723 gf_io_t gc, pc;
6726 so_seek(html, 0L, 0);
6727 gf_set_so_readc(&gc, html);
6729 init_handles(&handles);
6731 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6732 gf_set_so_writec(&pc, store);
6733 gf_filter_init();
6735 gf_link_filter(gf_html2plain,
6736 gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols,
6737 non_messageview_margin(), &handles, NULL, 0));
6739 error = gf_pipe(gc, pc);
6741 gf_clear_so_writec(store);
6743 if(!error){
6744 SCROLL_S sargs;
6746 memset(&sargs, 0, sizeof(SCROLL_S));
6747 sargs.text.text = so_text(store);
6748 sargs.text.src = CharStar;
6749 sargs.text.desc = "list commands";
6750 sargs.text.handles = handles;
6751 if(offset){
6752 sargs.start.on = Offset;
6753 sargs.start.loc.offset = offset;
6756 sargs.bar.title = _("MAIL LIST COMMANDS");
6757 sargs.bar.style = MessageNumber;
6758 sargs.resize_exit = 1;
6759 sargs.help.text = h_special_list_commands;
6760 sargs.help.title = _("HELP FOR LIST COMMANDS");
6761 sargs.keys.menu = &listmgr_keymenu;
6762 setbitmap(sargs.keys.bitmap);
6763 if(!handles){
6764 clrbitn(LM_TRY_KEY, sargs.keys.bitmap);
6765 clrbitn(LM_PREV_KEY, sargs.keys.bitmap);
6766 clrbitn(LM_NEXT_KEY, sargs.keys.bitmap);
6769 cmd = scrolltool(&sargs);
6770 offset = sargs.start.loc.offset;
6773 so_give(&store);
6776 free_handles(&handles);
6777 gf_clear_so_readc(html);
6779 while(cmd == MC_RESIZE);
6783 /*----------------------------------------------------------------------
6784 Prompt the user for the type of select desired
6786 NOTE: any and all functions that successfully exit the second
6787 switch() statement below (currently "select_*() functions"),
6788 *MUST* update the folder's MESSAGECACHE element's "searched"
6789 bits to reflect the search result. Functions using
6790 mail_search() get this for free, the others must update 'em
6791 by hand.
6793 Returns -1 if canceled without changing selection
6794 0 if selection may have changed
6795 ----*/
6797 aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index)
6799 long i, diff, old_tot, msgno, raw;
6800 int q = 0, rv = 0, narrow = 0, hidden, ret = -1;
6801 ESCKEY_S *sel_opts;
6802 MESSAGECACHE *mc;
6803 SEARCHSET *limitsrch = NULL;
6804 PINETHRD_S *thrd;
6805 extern MAILSTREAM *mm_search_stream;
6806 extern long mm_search_count;
6808 hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
6809 mm_search_stream = state->mail_stream;
6810 mm_search_count = 0L;
6812 sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2;
6813 if(THREADING()){
6814 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH;
6816 else{
6817 sel_opts[SEL_OPTS_THREAD].ch = -1;
6820 if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){
6821 if(THRD_INDX()){
6822 i = 0;
6823 thrd = fetch_thread(state->mail_stream,
6824 mn_m2raw(msgmap, mn_get_cur(msgmap)));
6825 /* check if whole thread is selected or not */
6826 if(thrd &&
6827 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT)
6829 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE))
6830 i = 1;
6832 sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd");
6834 else{
6835 i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap),
6836 MN_SLCT);
6837 sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur");
6840 sel_opts += 2; /* disable extra options */
6841 switch(q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6842 RB_NORM)){
6843 case 'f' : /* flip selection */
6844 msgno = 0L;
6845 for(i = 1L; i <= mn_get_total(msgmap); i++){
6846 ret = 0;
6847 q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT);
6848 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q);
6849 if(hidden){
6850 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q);
6851 if(!msgno && q)
6852 mn_reset_cur(msgmap, msgno = i);
6856 return(ret);
6858 case 'n' : /* narrow selection */
6859 narrow++;
6860 case 'b' : /* broaden selection */
6861 q = 0; /* offer criteria prompt */
6862 break;
6864 case 'c' : /* Un/Select Current */
6865 case 'a' : /* Unselect All */
6866 case 'x' : /* cancel */
6867 break;
6869 default :
6870 q_status_message(SM_ORDER | SM_DING, 3, 3,
6871 "Unsupported Select option");
6872 return(ret);
6876 if(!q){
6877 while(1){
6878 q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x',
6879 NO_HELP, RB_NORM|RB_RET_HELP);
6881 if(q == 3){
6882 helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE);
6883 ps_global->mangled_screen = 1;
6885 else
6886 break;
6891 * The purpose of this is to add the appropriate searchset to the
6892 * search so that the search can be limited to only looking at what
6893 * it needs to look at. That is, if we are narrowing then we only need
6894 * to look at messages which are already selected, and if we are
6895 * broadening, then we only need to look at messages which are not
6896 * yet selected. This routine will work whether or not
6897 * limiting_searchset properly limits the search set. In particular,
6898 * the searchset returned by limiting_searchset may include messages
6899 * which really shouldn't be included. We do that because a too-large
6900 * searchset will break some IMAP servers. It is even possible that it
6901 * becomes inefficient to send the whole set. If the select function
6902 * frees limitsrch, it should be sure to set it to NULL so we won't
6903 * try freeing it again here.
6905 limitsrch = limiting_searchset(state->mail_stream, narrow);
6908 * NOTE: See note about MESSAGECACHE "searched" bits above!
6910 switch(q){
6911 case 'x': /* cancel */
6912 cmd_cancelled("Select command");
6913 return(ret);
6915 case 'c' : /* select/unselect current */
6916 (void) select_by_current(state, msgmap, in_index);
6917 ret = 0;
6918 return(ret);
6920 case 'a' : /* select/unselect all */
6921 msgno = any_lflagged(msgmap, MN_SLCT);
6922 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
6923 ret = 0;
6924 agg_select_all(state->mail_stream, msgmap, &diff,
6925 any_lflagged(msgmap, MN_SLCT) <= 0L);
6926 q_status_message4(SM_ORDER,0,2,
6927 "%s%s message%s %sselected",
6928 msgno ? "" : "All ", comatose(diff),
6929 plural(diff), msgno ? "UN" : "");
6930 return(ret);
6932 case 'n' : /* Select by Number */
6933 ret = 0;
6934 if(THRD_INDX())
6935 rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch);
6936 else
6937 rv = select_by_number(state->mail_stream, msgmap, &limitsrch);
6939 break;
6941 case 'd' : /* Select by Date */
6942 ret = 0;
6943 rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap),
6944 &limitsrch);
6945 break;
6947 case 't' : /* Text */
6948 ret = 0;
6949 rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap),
6950 &limitsrch);
6951 break;
6953 case 'z' : /* Size */
6954 ret = 0;
6955 rv = select_by_size(state->mail_stream, &limitsrch);
6956 break;
6958 case 's' : /* Status */
6959 ret = 0;
6960 rv = select_by_status(state->mail_stream, &limitsrch);
6961 break;
6963 case 'k' : /* Keyword */
6964 ret = 0;
6965 rv = select_by_keyword(state->mail_stream, &limitsrch);
6966 break;
6968 case 'r' : /* Rule */
6969 ret = 0;
6970 rv = select_by_rule(state->mail_stream, &limitsrch);
6971 break;
6973 case 'h' : /* Thread */
6974 ret = 0;
6975 rv = select_by_thread(state->mail_stream, msgmap, &limitsrch);
6976 break;
6978 default :
6979 q_status_message(SM_ORDER | SM_DING, 3, 3,
6980 "Unsupported Select option");
6981 return(ret);
6984 if(limitsrch)
6985 mail_free_searchset(&limitsrch);
6987 if(rv) /* bad return value.. */
6988 return(ret); /* error already displayed */
6990 if(narrow) /* make sure something was selected */
6991 for(i = 1L; i <= mn_get_total(msgmap); i++)
6992 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
6993 && raw <= state->mail_stream->nmsgs
6994 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
6995 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
6996 break;
6997 else
6998 mm_search_count--;
7001 diff = 0L;
7002 if(mm_search_count){
7004 * loop thru all the messages, adjusting local flag bits
7005 * based on their "searched" bit...
7007 for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++)
7008 if(narrow){
7009 /* turning OFF selectedness if the "searched" bit isn't lit. */
7010 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7011 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7012 && raw <= state->mail_stream->nmsgs
7013 && (mc = mail_elt(state->mail_stream, raw))
7014 && !mc->searched){
7015 diff--;
7016 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
7017 if(hidden)
7018 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
7020 /* adjust current message in case we unselect and hide it */
7021 else if(msgno < mn_get_cur(msgmap)
7022 && (!THRD_INDX()
7023 || !get_lflag(state->mail_stream, msgmap,
7024 i, MN_CHID)))
7025 msgno = i;
7028 else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7029 && raw <= state->mail_stream->nmsgs
7030 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7031 /* turn ON selectedness if "searched" bit is lit. */
7032 if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7033 diff++;
7034 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
7035 if(hidden)
7036 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
7040 /* if we're zoomed and the current message was unselected */
7041 if(narrow && msgno
7042 && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE))
7043 mn_reset_cur(msgmap, msgno);
7046 if(!diff){
7047 if(narrow)
7048 q_status_message4(SM_ORDER, 3, 3,
7049 "%s. %s message%s remain%s selected.",
7050 mm_search_count
7051 ? "No change resulted"
7052 : "No messages in intersection",
7053 comatose(old_tot), plural(old_tot),
7054 (old_tot == 1L) ? "s" : "");
7055 else if(old_tot)
7056 q_status_message(SM_ORDER, 3, 3,
7057 _("No change resulted. Matching messages already selected."));
7058 else
7059 q_status_message1(SM_ORDER | SM_DING, 3, 3,
7060 _("Select failed. No %smessages selected."),
7061 old_tot ? _("additional ") : "");
7063 else if(old_tot){
7064 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
7065 "Select matched %ld message%s. %s %smessage%s %sselected.",
7066 (diff > 0) ? diff : old_tot + diff,
7067 plural((diff > 0) ? diff : old_tot + diff),
7068 comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7069 (diff > 0) ? "total " : "",
7070 plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7071 (diff > 0) ? "" : "UN");
7072 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
7073 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
7075 else
7076 q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"),
7077 comatose(diff), plural(diff));
7079 return(ret);
7083 /*----------------------------------------------------------------------
7084 Toggle the state of the current message
7086 Args: state -- pointer pine's state variables
7087 msgmap -- message collection to operate on
7088 in_index -- in the message index view
7089 Returns: TRUE if current marked selected, FALSE otw
7090 ----*/
7092 select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
7094 long cur;
7095 int all_selected = 0;
7096 unsigned long was, tot, rawno;
7097 PINETHRD_S *thrd;
7099 cur = mn_get_cur(msgmap);
7101 if(THRD_INDX()){
7102 thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur));
7103 if(!thrd)
7104 return 0;
7106 was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT);
7107 tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE);
7108 if(was == tot)
7109 all_selected++;
7111 if(all_selected){
7112 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0);
7113 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7114 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1);
7116 * See if there's anything left to zoom on. If so,
7117 * pick an adjacent one for highlighting, else make
7118 * sure nothing is left hidden...
7120 if(any_lflagged(msgmap, MN_SLCT)){
7121 mn_inc_cur(state->mail_stream, msgmap,
7122 (in_index == View && THREADING()
7123 && sp_viewing_a_thread(state->mail_stream))
7124 ? MH_THISTHD
7125 : (in_index == View)
7126 ? MH_ANYTHD : MH_NONE);
7127 if(mn_get_cur(msgmap) == cur)
7128 mn_dec_cur(state->mail_stream, msgmap,
7129 (in_index == View && THREADING()
7130 && sp_viewing_a_thread(state->mail_stream))
7131 ? MH_THISTHD
7132 : (in_index == View)
7133 ? MH_ANYTHD : MH_NONE);
7135 else /* clear all hidden flags */
7136 (void) unzoom_index(state, state->mail_stream, msgmap);
7139 else
7140 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1);
7142 q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected",
7143 comatose(all_selected ? was : tot-was),
7144 plural(all_selected ? was : tot-was),
7145 all_selected ? "UN" : "");
7147 /* collapsed thread */
7148 else if(THREADING()
7149 && ((rawno = mn_m2raw(msgmap, cur)) != 0L)
7150 && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL)
7151 && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){
7153 * This doesn't work quite the same as the colon command works, but
7154 * it is arguably doing the correct thing. The difference is
7155 * that aggregate_select will zoom after selecting back where it
7156 * was called from, but selecting a thread with colon won't zoom.
7157 * Maybe it makes sense to zoom after a select but not after a colon
7158 * command even though they are very similar.
7160 thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state));
7162 else{
7163 if((all_selected =
7164 get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */
7165 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0);
7166 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7167 set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1);
7169 * See if there's anything left to zoom on. If so,
7170 * pick an adjacent one for highlighting, else make
7171 * sure nothing is left hidden...
7173 if(any_lflagged(msgmap, MN_SLCT)){
7174 mn_inc_cur(state->mail_stream, msgmap,
7175 (in_index == View && THREADING()
7176 && sp_viewing_a_thread(state->mail_stream))
7177 ? MH_THISTHD
7178 : (in_index == View)
7179 ? MH_ANYTHD : MH_NONE);
7180 if(mn_get_cur(msgmap) == cur)
7181 mn_dec_cur(state->mail_stream, msgmap,
7182 (in_index == View && THREADING()
7183 && sp_viewing_a_thread(state->mail_stream))
7184 ? MH_THISTHD
7185 : (in_index == View)
7186 ? MH_ANYTHD : MH_NONE);
7188 else /* clear all hidden flags */
7189 (void) unzoom_index(state, state->mail_stream, msgmap);
7192 else
7193 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1);
7195 q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected",
7196 long2string(cur), all_selected ? "UN" : "");
7200 return(!all_selected);
7204 /*----------------------------------------------------------------------
7205 Prompt the user for the command to perform on selected messages
7207 Args: state -- pointer pine's state variables
7208 msgmap -- message collection to operate on
7209 q_line -- line on display to write prompts
7210 Returns: 1 if the selected messages are suitably commanded,
7211 0 if the choice to pick the command was declined
7213 ----*/
7215 apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
7216 UCS preloadkeystroke, int flags, int q_line)
7218 int i = 8, /* number of static entries in sel_opts3 */
7219 rv = 0,
7220 cmd,
7221 we_cancel = 0,
7222 agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG;
7223 char prompt[80];
7226 * To do this "right", we really ought to have access to the keymenu
7227 * here and change the typed command into a real command by running
7228 * it through menu_command. Then the switch below would be against
7229 * results from menu_command. If we did that we'd also pass the
7230 * results of menu_command in as preloadkeystroke instead of passing
7231 * the keystroke itself. But we don't have the keymenu handy,
7232 * so we have to fake it. The only complication that we run into
7233 * is that KEY_DEL is an escape sequence so we change a typed
7234 * KEY_DEL esc seq into the letter D.
7237 if(!preloadkeystroke){
7238 if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
7239 sel_opts3[i].ch = '*';
7240 sel_opts3[i].rval = '*';
7241 sel_opts3[i].name = "*";
7242 sel_opts3[i++].label = N_("Flag");
7245 if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
7246 sel_opts3[i].ch = '|';
7247 sel_opts3[i].rval = '|';
7248 sel_opts3[i].name = "|";
7249 sel_opts3[i++].label = N_("Pipe");
7252 if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */
7253 sel_opts3[i].ch = 'b';
7254 sel_opts3[i].rval = 'b';
7255 sel_opts3[i].name = "B";
7256 sel_opts3[i++].label = N_("Bounce");
7259 if(flags & AC_FROM_THREAD){
7260 if(flags & (AC_COLL | AC_EXPN)){
7261 sel_opts3[i].ch = '/';
7262 sel_opts3[i].rval = '/';
7263 sel_opts3[i].name = "/";
7264 sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse")
7265 : N_("Expand");
7268 sel_opts3[i].ch = ';';
7269 sel_opts3[i].rval = ';';
7270 sel_opts3[i].name = ";";
7271 if(flags & AC_UNSEL)
7272 sel_opts3[i++].label = N_("UnSelect");
7273 else
7274 sel_opts3[i++].label = N_("Select");
7277 if(F_ON(F_ENABLE_PRYNT, state)){ /* this one is invisible */
7278 sel_opts3[i].ch = 'y';
7279 sel_opts3[i].rval = '%';
7280 sel_opts3[i].name = "";
7281 sel_opts3[i++].label = "";
7284 if(!is_imap_stream(stream) || LEVELUIDPLUS(stream)){ /* expunge selected messages */
7285 sel_opts3[i].ch = 'x';
7286 sel_opts3[i].rval = 'x';
7287 sel_opts3[i].name = "X";
7288 sel_opts3[i++].label = N_("Expunge");
7291 sel_opts3[i].ch = KEY_DEL; /* also invisible */
7292 sel_opts3[i].rval = 'd';
7293 sel_opts3[i].name = "";
7294 sel_opts3[i++].label = "";
7296 sel_opts3[i].ch = -1;
7298 snprintf(prompt, sizeof(prompt), "%s command : ",
7299 (flags & AC_FROM_THREAD) ? "THREAD" : "APPLY");
7300 prompt[sizeof(prompt)-1] = '\0';
7301 cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'c', NO_HELP,
7302 RB_SEQ_SENSITIVE);
7303 if(isupper(cmd))
7304 cmd = tolower(cmd);
7306 else{
7307 if(preloadkeystroke == KEY_DEL)
7308 cmd = 'd';
7309 else{
7310 if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke))
7311 cmd = tolower((int) preloadkeystroke);
7312 else
7313 cmd = (int) preloadkeystroke; /* shouldn't happen */
7317 switch(cmd){
7318 case 'd' : /* delete */
7319 we_cancel = busy_cue(NULL, NULL, 1);
7320 rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */
7321 if(we_cancel)
7322 cancel_busy_cue(0);
7323 break;
7325 case 'u' : /* undelete */
7326 we_cancel = busy_cue(NULL, NULL, 1);
7327 rv = cmd_undelete(state, msgmap, agg);
7328 if(we_cancel)
7329 cancel_busy_cue(0);
7330 break;
7332 case 'r' : /* reply */
7333 rv = cmd_reply(state, msgmap, agg);
7334 break;
7336 case 'f' : /* Forward */
7337 rv = cmd_forward(state, msgmap, agg);
7338 break;
7340 case '%' : /* print */
7341 rv = cmd_print(state, msgmap, agg, MsgIndx);
7342 break;
7344 case 't' : /* take address */
7345 rv = cmd_take_addr(state, msgmap, agg);
7346 break;
7348 case 's' : /* save */
7349 rv = cmd_save(state, stream, msgmap, agg, MsgIndx);
7350 break;
7352 case 'e' : /* export */
7353 rv = cmd_export(state, msgmap, q_line, agg);
7354 break;
7356 case '|' : /* pipe */
7357 rv = cmd_pipe(state, msgmap, agg);
7358 break;
7360 case '*' : /* flag */
7361 we_cancel = busy_cue(NULL, NULL, 1);
7362 rv = cmd_flag(state, msgmap, agg);
7363 if(we_cancel)
7364 cancel_busy_cue(0);
7365 break;
7367 case 'b' : /* bounce */
7368 rv = cmd_bounce(state, msgmap, agg);
7369 break;
7371 case '/' :
7372 collapse_or_expand(state, stream, msgmap,
7373 F_ON(F_SLASH_COLL_ENTIRE, ps_global)
7374 ? 0L
7375 : mn_get_cur(msgmap));
7376 break;
7378 case ':' :
7379 select_thread_stmp(state, stream, msgmap);
7380 break;
7382 case 'x' : /* Expunge */
7383 rv = cmd_expunge(state, stream, msgmap, agg);
7384 break;
7386 case 'c' : /* cancel */
7387 cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command"
7388 : "Apply command");
7389 break;
7391 case 'z' : /* default */
7392 q_status_message(SM_INFO, 0, 2,
7393 "Cancelled, there is no default command");
7394 break;
7396 default:
7397 break;
7400 return(rv);
7405 * Select by message number ranges.
7406 * Sets searched bits in mail_elts
7408 * Args limitsrch -- limit search to this searchset
7410 * Returns 0 on success.
7413 select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
7415 int r, end;
7416 long n1, n2, raw;
7417 char number1[16], number2[16], numbers[80], *p, *t;
7418 HelpType help;
7419 MESSAGECACHE *mc;
7421 numbers[0] = '\0';
7422 ps_global->mangled_footer = 1;
7423 help = NO_HELP;
7424 while(1){
7425 int flags = OE_APPEND_CURRENT;
7427 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7428 sizeof(numbers), _(select_num), NULL, help, &flags);
7429 if(r == 4)
7430 continue;
7432 if(r == 3){
7433 help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
7434 continue;
7437 for(t = p = numbers; *p ; p++) /* strip whitespace */
7438 if(!isspace((unsigned char)*p))
7439 *t++ = *p;
7441 *t = '\0';
7443 if(r == 1 || numbers[0] == '\0'){
7444 cmd_cancelled("Selection by number");
7445 return(1);
7447 else
7448 break;
7451 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7452 if((mc = mail_elt(stream, n1)) != NULL)
7453 mc->searched = 0; /* clear searched bits */
7455 for(p = numbers; *p ; p++){
7456 t = number1;
7457 while(*p && isdigit((unsigned char)*p))
7458 *t++ = *p++;
7460 *t = '\0';
7462 end = 0;
7463 if(number1[0] == '\0'){
7464 if(*p == '-'){
7465 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7466 _("Invalid number range, missing number before \"-\": %s"),
7467 numbers);
7468 return(1);
7470 else if(!strucmp("end", p)){
7471 end = 1;
7472 p += strlen("end");
7474 else{
7475 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7476 _("Invalid message number: %s"), numbers);
7477 return(1);
7481 if(end)
7482 n1 = mn_get_total(msgmap);
7483 else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
7484 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7485 _("\"%s\" out of message number range"),
7486 long2string(n1));
7487 return(1);
7490 t = number2;
7491 if(*p == '-'){
7492 while(*++p && isdigit((unsigned char)*p))
7493 *t++ = *p;
7495 *t = '\0';
7497 end = 0;
7498 if(number2[0] == '\0'){
7499 if(!strucmp("end", p)){
7500 end = 1;
7501 p += strlen("end");
7503 else{
7504 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7505 _("Invalid number range, missing number after \"-\": %s"),
7506 numbers);
7507 return(1);
7511 if(end)
7512 n2 = mn_get_total(msgmap);
7513 else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){
7514 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7515 _("\"%s\" out of message number range"),
7516 long2string(n2));
7517 return(1);
7520 if(n2 <= n1){
7521 char t[20];
7523 strncpy(t, long2string(n1), sizeof(t));
7524 t[sizeof(t)-1] = '\0';
7525 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7526 _("Invalid reverse message number range: %s-%s"),
7527 t, long2string(n2));
7528 return(1);
7531 for(;n1 <= n2; n1++){
7532 raw = mn_m2raw(msgmap, n1);
7533 if(raw > 0L
7534 && (!(limitsrch && *limitsrch)
7535 || in_searchset(*limitsrch, (unsigned long) raw)))
7536 mm_searched(stream, raw);
7539 else{
7540 raw = mn_m2raw(msgmap, n1);
7541 if(raw > 0L
7542 && (!(limitsrch && *limitsrch)
7543 || in_searchset(*limitsrch, (unsigned long) raw)))
7544 mm_searched(stream, raw);
7547 if(*p == '\0')
7548 break;
7551 return(0);
7556 * Select by thread number ranges.
7557 * Sets searched bits in mail_elts
7559 * Args limitsrch -- limit search to this searchset
7561 * Returns 0 on success.
7564 select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset)
7566 int r, end;
7567 long n1, n2;
7568 char number1[16], number2[16], numbers[80], *p, *t;
7569 HelpType help;
7570 PINETHRD_S *thrd = NULL;
7571 MESSAGECACHE *mc;
7573 numbers[0] = '\0';
7574 ps_global->mangled_footer = 1;
7575 help = NO_HELP;
7576 while(1){
7577 int flags = OE_APPEND_CURRENT;
7579 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7580 sizeof(numbers), _(select_num), NULL, help, &flags);
7581 if(r == 4)
7582 continue;
7584 if(r == 3){
7585 help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP;
7586 continue;
7589 for(t = p = numbers; *p ; p++) /* strip whitespace */
7590 if(!isspace((unsigned char)*p))
7591 *t++ = *p;
7593 *t = '\0';
7595 if(r == 1 || numbers[0] == '\0'){
7596 cmd_cancelled("Selection by number");
7597 return(1);
7599 else
7600 break;
7603 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7604 if((mc = mail_elt(stream, n1)) != NULL)
7605 mc->searched = 0; /* clear searched bits */
7607 for(p = numbers; *p ; p++){
7608 t = number1;
7609 while(*p && isdigit((unsigned char)*p))
7610 *t++ = *p++;
7612 *t = '\0';
7614 end = 0;
7615 if(number1[0] == '\0'){
7616 if(*p == '-'){
7617 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7618 _("Invalid number range, missing number before \"-\": %s"),
7619 numbers);
7620 return(1);
7622 else if(!strucmp("end", p)){
7623 end = 1;
7624 p += strlen("end");
7626 else{
7627 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7628 _("Invalid thread number: %s"), numbers);
7629 return(1);
7633 if(end)
7634 n1 = msgmap->max_thrdno;
7635 else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){
7636 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7637 _("\"%s\" out of thread number range"),
7638 long2string(n1));
7639 return(1);
7642 t = number2;
7643 if(*p == '-'){
7645 while(*++p && isdigit((unsigned char)*p))
7646 *t++ = *p;
7648 *t = '\0';
7650 end = 0;
7651 if(number2[0] == '\0'){
7652 if(!strucmp("end", p)){
7653 end = 1;
7654 p += strlen("end");
7656 else{
7657 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7658 _("Invalid number range, missing number after \"-\": %s"),
7659 numbers);
7660 return(1);
7664 if(end)
7665 n2 = msgmap->max_thrdno;
7666 else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){
7667 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7668 _("\"%s\" out of thread number range"),
7669 long2string(n2));
7670 return(1);
7673 if(n2 <= n1){
7674 char t[20];
7676 strncpy(t, long2string(n1), sizeof(t));
7677 t[sizeof(t)-1] = '\0';
7678 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7679 _("Invalid reverse message number range: %s-%s"),
7680 t, long2string(n2));
7681 return(1);
7684 for(;n1 <= n2; n1++){
7685 thrd = find_thread_by_number(stream, msgmap, n1, thrd);
7687 if(thrd)
7688 set_search_bit_for_thread(stream, thrd, msgset);
7691 else{
7692 thrd = find_thread_by_number(stream, msgmap, n1, NULL);
7694 if(thrd)
7695 set_search_bit_for_thread(stream, thrd, msgset);
7698 if(*p == '\0')
7699 break;
7702 return(0);
7707 * Select by message dates.
7708 * Sets searched bits in mail_elts
7710 * Args limitsrch -- limit search to this searchset
7712 * Returns 0 on success.
7715 select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
7717 int r, we_cancel = 0, when = 0;
7718 char date[100], defdate[100], prompt[128];
7719 time_t seldate = time(0);
7720 struct tm *seldate_tm;
7721 SEARCHPGM *pgm;
7722 HelpType help;
7723 static struct _tense {
7724 char *preamble,
7725 *range,
7726 *scope;
7727 } tense[] = {
7728 {"were ", "SENT SINCE", " (inclusive)"},
7729 {"were ", "SENT BEFORE", " (exclusive)"},
7730 {"were ", "SENT ON", "" },
7731 {"", "ARRIVED SINCE", " (inclusive)"},
7732 {"", "ARRIVED BEFORE", " (exclusive)"},
7733 {"", "ARRIVED ON", "" }
7736 date[0] = '\0';
7737 ps_global->mangled_footer = 1;
7738 help = NO_HELP;
7741 * If talking to an old server, default to SINCE instead of
7742 * SENTSINCE, which was added later.
7744 if(is_imap_stream(stream) && !modern_imap_stream(stream))
7745 when = 3;
7747 while(1){
7748 int flags = OE_APPEND_CURRENT;
7750 seldate_tm = localtime(&seldate);
7751 snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday,
7752 month_abbrev(seldate_tm->tm_mon + 1),
7753 seldate_tm->tm_year + 1900);
7754 defdate[sizeof(defdate)-1] = '\0';
7755 snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ",
7756 tense[when].preamble, tense[when].range,
7757 tense[when].scope, defdate);
7758 prompt[sizeof(prompt)-1] = '\0';
7759 r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date),
7760 prompt, sel_date_opt, help, &flags);
7761 switch (r){
7762 case 1 :
7763 cmd_cancelled("Selection by date");
7764 return(1);
7766 case 3 :
7767 help = (help == NO_HELP) ? h_select_date : NO_HELP;
7768 continue;
7770 case 4 :
7771 continue;
7773 case 11 :
7775 MESSAGECACHE *mc;
7776 long rawno;
7778 if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L
7779 && rawno <= stream->nmsgs
7780 && (mc = mail_elt(stream, rawno))){
7782 /* cache not filled in yet? */
7783 if(mc->day == 0){
7784 char seq[20];
7786 if(stream->dtb && stream->dtb->flags & DR_NEWS){
7787 strncpy(seq,
7788 ulong2string(mail_uid(stream, rawno)),
7789 sizeof(seq));
7790 seq[sizeof(seq)-1] = '\0';
7791 mail_fetch_overview(stream, seq, NULL);
7793 else{
7794 strncpy(seq, long2string(rawno),
7795 sizeof(seq));
7796 seq[sizeof(seq)-1] = '\0';
7797 mail_fetch_fast(stream, seq, 0L);
7801 /* mail_date returns fixed field width date */
7802 mail_date(date, mc);
7803 date[11] = '\0';
7807 continue;
7809 case 12 : /* set default to PREVIOUS day */
7810 seldate -= 86400;
7811 continue;
7813 case 13 : /* set default to NEXT day */
7814 seldate += 86400;
7815 continue;
7817 case 14 :
7818 when = (when+1) % (sizeof(tense) / sizeof(struct _tense));
7819 continue;
7821 default:
7822 break;
7825 removing_leading_white_space(date);
7826 removing_trailing_white_space(date);
7827 if(!*date){
7828 strncpy(date, defdate, sizeof(date));
7829 date[sizeof(date)-1] = '\0';
7832 break;
7835 if((pgm = mail_newsearchpgm()) != NULL){
7836 MESSAGECACHE elt;
7837 short converted_date;
7839 if(mail_parse_date(&elt, (unsigned char *) date)){
7840 converted_date = mail_shortdate(elt.year, elt.month, elt.day);
7842 switch(when){
7843 case 0:
7844 pgm->sentsince = converted_date;
7845 break;
7846 case 1:
7847 pgm->sentbefore = converted_date;
7848 break;
7849 case 2:
7850 pgm->senton = converted_date;
7851 break;
7852 case 3:
7853 pgm->since = converted_date;
7854 break;
7855 case 4:
7856 pgm->before = converted_date;
7857 break;
7858 case 5:
7859 pgm->on = converted_date;
7860 break;
7863 pgm->msgno = (limitsrch ? *limitsrch : NULL);
7865 if(ps_global && ps_global->ttyo){
7866 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
7867 ps_global->mangled_footer = 1;
7870 we_cancel = busy_cue(_("Selecting"), NULL, 1);
7872 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
7874 if(we_cancel)
7875 cancel_busy_cue(0);
7877 /* we know this was freed in mail_search, let caller know */
7878 if(limitsrch)
7879 *limitsrch = NULL;
7881 else{
7882 mail_free_searchpgm(&pgm);
7883 q_status_message1(SM_ORDER, 3, 3,
7884 _("Invalid date entered: %s"), date);
7885 return(1);
7889 return(0);
7894 * Select by searching in message headers or body.
7895 * Sets searched bits in mail_elts
7897 * Args limitsrch -- limit search to this searchset
7899 * Returns 0 on success.
7902 select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
7904 int r, ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
7905 int not = 0, me = 0;
7906 char sstring[80], savedsstring[80], tmp[128];
7907 char *p, *sval = NULL;
7908 char buftmp[MAILTMPLEN], namehdr[80];
7909 ESCKEY_S ekey[8];
7910 ENVELOPE *env = NULL;
7911 HelpType help;
7912 unsigned flagsforhist = 0;
7913 static HISTORY_S *history = NULL;
7914 static char *recip = "RECIPIENTS";
7915 static char *partic = "PARTICIPANTS";
7916 static char *match_me = N_("[Match_My_Addresses]");
7917 static char *dont_match_me = N_("[Don't_Match_My_Addresses]");
7919 ps_global->mangled_footer = 1;
7920 savedsstring[0] = '\0';
7921 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
7923 while(1){
7924 type = radio_buttons(not ? _(sel_text_not) : _(sel_text),
7925 -FOOTER_ROWS(ps_global), sel_text_opt,
7926 's', 'x', NO_HELP, RB_NORM|RB_RET_HELP);
7928 if(type == '!')
7929 not = !not;
7930 else if(type == 3){
7931 helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS",
7932 HLPD_SIMPLE);
7933 ps_global->mangled_screen = 1;
7935 else
7936 break;
7940 * prepare some friendly defaults...
7942 switch(type){
7943 case 't' : /* address fields, offer To or From */
7944 case 'f' :
7945 case 'c' :
7946 case 'r' :
7947 case 'p' :
7948 sval = (type == 't') ? "TO" :
7949 (type == 'f') ? "FROM" :
7950 (type == 'c') ? "CC" :
7951 (type == 'r') ? recip : partic;
7952 ekey[ekeyi].ch = ctrl('T');
7953 ekey[ekeyi].name = "^T";
7954 ekey[ekeyi].rval = 10;
7955 /* TRANSLATORS: use Current To Address */
7956 ekey[ekeyi++].label = N_("Cur To");
7957 ekey[ekeyi].ch = ctrl('R');
7958 ekey[ekeyi].name = "^R";
7959 ekey[ekeyi].rval = 11;
7960 /* TRANSLATORS: use Current From Address */
7961 ekey[ekeyi++].label = N_("Cur From");
7962 ekey[ekeyi].ch = ctrl('W');
7963 ekey[ekeyi].name = "^W";
7964 ekey[ekeyi].rval = 12;
7965 /* TRANSLATORS: use Current Cc Address */
7966 ekey[ekeyi++].label = N_("Cur Cc");
7967 ekey[ekeyi].ch = ctrl('Y');
7968 ekey[ekeyi].name = "^Y";
7969 ekey[ekeyi].rval = 13;
7970 /* TRANSLATORS: Match Me means match my address */
7971 ekey[ekeyi++].label = N_("Match Me");
7972 ekey[ekeyi].ch = 0;
7973 ekey[ekeyi].name = "";
7974 ekey[ekeyi].rval = 0;
7975 ekey[ekeyi++].label = "";
7976 break;
7978 case 's' :
7979 sval = "SUBJECT";
7980 ekey[ekeyi].ch = ctrl('X');
7981 ekey[ekeyi].name = "^X";
7982 ekey[ekeyi].rval = 14;
7983 /* TRANSLATORS: use Current Subject */
7984 ekey[ekeyi++].label = N_("Cur Subject");
7985 break;
7987 case 'a' :
7988 sval = "TEXT";
7989 break;
7991 case 'b' :
7992 sval = "BODYTEXT";
7993 break;
7995 case 'h' :
7996 strncpy(tmp, "Name of HEADER to match : ", sizeof(tmp)-1);
7997 tmp[sizeof(tmp)-1] = '\0';
7998 flags = OE_APPEND_CURRENT;
7999 namehdr[0] = '\0';
8000 r = 'x';
8001 while (r == 'x'){
8002 int done = 0;
8004 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8005 sizeof(namehdr), tmp, ekey, NO_HELP, &flags);
8006 if (r == 1){
8007 cmd_cancelled("Selection by text");
8008 return(1);
8010 removing_leading_white_space(namehdr);
8011 while(!done){
8012 while ((namehdr[0] != '\0') && /* remove trailing ":" */
8013 (namehdr[strlen(namehdr) - 1] == ':'))
8014 namehdr[strlen(namehdr) - 1] = '\0';
8015 if ((namehdr[0] != '\0')
8016 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8017 removing_trailing_white_space(namehdr);
8018 else
8019 done++;
8021 if (strchr(namehdr,' ') || strchr(namehdr,'\t') ||
8022 strchr(namehdr,':'))
8023 namehdr[0] = '\0';
8024 if (namehdr[0] == '\0')
8025 r = 'x';
8027 sval = namehdr;
8028 break;
8030 case 'x':
8031 break;
8033 default:
8034 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
8035 return(1);
8038 ekey[ekeyi].ch = KEY_UP;
8039 ekey[ekeyi].rval = 30;
8040 ekey[ekeyi].name = "";
8041 ku = ekeyi;
8042 ekey[ekeyi++].label = "";
8044 ekey[ekeyi].ch = KEY_DOWN;
8045 ekey[ekeyi].rval = 31;
8046 ekey[ekeyi].name = "";
8047 ekey[ekeyi++].label = "";
8049 ekey[ekeyi].ch = -1;
8051 if(type != 'x'){
8053 init_hist(&history, HISTSIZE);
8055 if(ekey[0].ch > -1 && msgno > 0L
8056 && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),
8057 NULL)))
8058 ekey[0].ch = -1;
8060 sstring[0] = '\0';
8061 help = NO_HELP;
8062 r = type;
8063 while(r != 'x'){
8064 if(not)
8065 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8066 snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval);
8067 else
8068 snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval);
8070 if(items_in_hist(history) > 0){
8071 ekey[ku].name = HISTORY_UP_KEYNAME;
8072 ekey[ku].label = HISTORY_KEYLABEL;
8073 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
8074 ekey[ku+1].label = HISTORY_KEYLABEL;
8076 else{
8077 ekey[ku].name = "";
8078 ekey[ku].label = "";
8079 ekey[ku+1].name = "";
8080 ekey[ku+1].label = "";
8083 flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
8084 r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
8085 79, tmp, ekey, help, &flags);
8087 if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me)))))
8088 me = 0;
8090 switch(r){
8091 case 3 :
8092 help = (help == NO_HELP)
8093 ? (not
8094 ? ((type == 'f') ? h_select_txt_not_from
8095 : (type == 't') ? h_select_txt_not_to
8096 : (type == 'c') ? h_select_txt_not_cc
8097 : (type == 's') ? h_select_txt_not_subj
8098 : (type == 'a') ? h_select_txt_not_all
8099 : (type == 'r') ? h_select_txt_not_recip
8100 : (type == 'p') ? h_select_txt_not_partic
8101 : (type == 'b') ? h_select_txt_not_body
8102 : NO_HELP)
8103 : ((type == 'f') ? h_select_txt_from
8104 : (type == 't') ? h_select_txt_to
8105 : (type == 'c') ? h_select_txt_cc
8106 : (type == 's') ? h_select_txt_subj
8107 : (type == 'a') ? h_select_txt_all
8108 : (type == 'r') ? h_select_txt_recip
8109 : (type == 'p') ? h_select_txt_partic
8110 : (type == 'b') ? h_select_txt_body
8111 : NO_HELP))
8112 : NO_HELP;
8114 case 4 :
8115 continue;
8117 case 10 : /* To: default */
8118 if(env && env->to && env->to->mailbox){
8119 snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox,
8120 env->to->host ? "@" : "",
8121 env->to->host ? env->to->host : "");
8122 sstring[sizeof(sstring)-1] = '\0';
8124 continue;
8126 case 11 : /* From: default */
8127 if(env && env->from && env->from->mailbox){
8128 snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox,
8129 env->from->host ? "@" : "",
8130 env->from->host ? env->from->host : "");
8131 sstring[sizeof(sstring)-1] = '\0';
8133 continue;
8135 case 12 : /* Cc: default */
8136 if(env && env->cc && env->cc->mailbox){
8137 snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox,
8138 env->cc->host ? "@" : "",
8139 env->cc->host ? env->cc->host : "");
8140 sstring[sizeof(sstring)-1] = '\0';
8142 continue;
8144 case 13 : /* Match my addresses */
8145 me++;
8146 snprintf(sstring, sizeof(sstring), "%s", not ? _(dont_match_me) : _(match_me));
8147 continue;
8149 case 14 : /* Subject: default */
8150 if(env && env->subject && env->subject[0]){
8151 char *q = NULL;
8153 snprintf(buftmp, sizeof(buftmp), "%.75s", env->subject);
8154 buftmp[sizeof(buftmp)-1] = '\0';
8155 q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
8156 SIZEOF_20KBUF, buftmp);
8157 if(q != env->subject){
8158 snprintf(savedsstring, sizeof(savedsstring), "%.70s", q);
8159 savedsstring[sizeof(savedsstring)-1] = '\0';
8162 snprintf(sstring, sizeof(sstring), "%s", q);
8163 sstring[sizeof(sstring)-1] = '\0';
8166 continue;
8168 case 30 :
8169 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8170 if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){
8171 strncpy(sstring, p, sizeof(sstring));
8172 sstring[sizeof(sstring)-1] = '\0';
8173 if(history->hist[history->curindex]){
8174 flagsforhist = history->hist[history->curindex]->flags;
8175 not = (flagsforhist & 0x1) ? 1 : 0;
8176 me = (flagsforhist & 0x2) ? 1 : 0;
8179 else
8180 Writechar(BELL, 0);
8182 continue;
8184 case 31 :
8185 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8186 if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){
8187 strncpy(sstring, p, sizeof(sstring));
8188 sstring[sizeof(sstring)-1] = '\0';
8189 if(history->hist[history->curindex]){
8190 flagsforhist = history->hist[history->curindex]->flags;
8191 not = (flagsforhist & 0x1) ? 1 : 0;
8192 me = (flagsforhist & 0x2) ? 1 : 0;
8195 else
8196 Writechar(BELL, 0);
8198 continue;
8200 default :
8201 break;
8204 if(r == 1 || sstring[0] == '\0')
8205 r = 'x';
8207 break;
8211 if(type == 'x' || r == 'x'){
8212 cmd_cancelled("Selection by text");
8213 return(1);
8216 if(ps_global && ps_global->ttyo){
8217 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8218 ps_global->mangled_footer = 1;
8221 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8223 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8224 save_hist(history, sstring, flagsforhist, NULL);
8226 rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch);
8227 if(we_cancel)
8228 cancel_busy_cue(0);
8230 return(rv);
8235 * Select by message size.
8236 * Sets searched bits in mail_elts
8238 * Args limitsrch -- limit search to this searchset
8240 * Returns 0 on success.
8243 select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch)
8245 int r, large = 1, we_cancel = 0;
8246 unsigned long n, mult = 1L, numerator = 0L, divisor = 1L;
8247 char size[16], numbers[80], *p, *t;
8248 HelpType help;
8249 SEARCHPGM *pgm;
8250 long flags = (SE_NOPREFETCH | SE_FREE);
8252 numbers[0] = '\0';
8253 ps_global->mangled_footer = 1;
8255 help = NO_HELP;
8256 while(1){
8257 int flgs = OE_APPEND_CURRENT;
8259 sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger;
8261 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
8262 sizeof(numbers), large ? _(select_size_larger_msg)
8263 : _(select_size_smaller_msg),
8264 sel_size_opt, help, &flgs);
8265 if(r == 4)
8266 continue;
8268 if(r == 14){
8269 large = 1 - large;
8270 continue;
8273 if(r == 3){
8274 help = (help == NO_HELP) ? (large ? h_select_by_larger_size
8275 : h_select_by_smaller_size)
8276 : NO_HELP;
8277 continue;
8280 for(t = p = numbers; *p ; p++) /* strip whitespace */
8281 if(!isspace((unsigned char)*p))
8282 *t++ = *p;
8284 *t = '\0';
8286 if(r == 1 || numbers[0] == '\0'){
8287 cmd_cancelled("Selection by size");
8288 return(1);
8290 else
8291 break;
8294 if(numbers[0] == '-'){
8295 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8296 _("Invalid size entered: %s"), numbers);
8297 return(1);
8300 t = size;
8301 p = numbers;
8303 while(*p && isdigit((unsigned char)*p))
8304 *t++ = *p++;
8306 *t = '\0';
8308 if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){
8309 size[0] = '0';
8310 size[1] = '\0';
8313 if(size[0] == '\0'){
8314 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8315 _("Invalid size entered: %s"), numbers);
8316 return(1);
8319 n = strtoul(size, (char **)NULL, 10);
8321 size[0] = '\0';
8322 if(*p == '.'){
8324 * We probably ought to just use atof() to convert 1.1 into a
8325 * double, but since we haven't used atof() anywhere else I'm
8326 * reluctant to use it because of portability concerns.
8328 p++;
8329 t = size;
8330 while(*p && isdigit((unsigned char)*p)){
8331 *t++ = *p++;
8332 divisor *= 10;
8335 *t = '\0';
8337 if(size[0])
8338 numerator = strtoul(size, (char **)NULL, 10);
8341 switch(*p){
8342 case 'g':
8343 case 'G':
8344 mult *= 1000;
8345 /* fall through */
8347 case 'm':
8348 case 'M':
8349 mult *= 1000;
8350 /* fall through */
8352 case 'k':
8353 case 'K':
8354 mult *= 1000;
8355 break;
8358 n = n * mult + (numerator * mult) / divisor;
8360 pgm = mail_newsearchpgm();
8361 if(large)
8362 pgm->larger = n;
8363 else
8364 pgm->smaller = n;
8366 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8367 flags |= SE_NOSERVER;
8369 if(ps_global && ps_global->ttyo){
8370 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8371 ps_global->mangled_footer = 1;
8374 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8376 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8377 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8378 /* we know this was freed in mail_search, let caller know */
8379 if(limitsrch)
8380 *limitsrch = NULL;
8382 if(we_cancel)
8383 cancel_busy_cue(0);
8385 return(0);
8390 * visible_searchset -- return c-client search set unEXLDed
8391 * sequence numbers
8393 SEARCHSET *
8394 visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap)
8396 long n, run;
8397 SEARCHSET *full_set = NULL, **set;
8400 * If we're talking to anything other than a server older than
8401 * imap 4rev1, build a searchset otherwise it'll choke.
8403 if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
8404 if(any_lflagged(msgmap, MN_EXLD)){
8405 for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
8406 if(get_lflag(stream, NULL, n, MN_EXLD)){
8407 if(run){ /* previous NOT excluded? */
8408 if(run > 1L)
8409 (*set)->last = n - 1L;
8411 set = &(*set)->next;
8412 run = 0L;
8415 else if(run++){ /* next in run */
8416 (*set)->last = n;
8418 else{ /* start of run */
8419 *set = mail_newsearchset();
8420 (*set)->first = n;
8423 else{
8424 full_set = mail_newsearchset();
8425 full_set->first = 1L;
8426 full_set->last = stream->nmsgs;
8430 return(full_set);
8435 * Select by message status bits.
8436 * Sets searched bits in mail_elts
8438 * Args limitsrch -- limit search to this searchset
8440 * Returns 0 on success.
8443 select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch)
8445 int s, not = 0, we_cancel = 0, rv;
8447 while(1){
8448 s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag),
8449 -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x',
8450 NO_HELP, RB_NORM|RB_RET_HELP);
8452 if(s == 'x'){
8453 cmd_cancelled("Selection by status");
8454 return(1);
8456 else if(s == 3){
8457 helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"),
8458 HLPD_SIMPLE);
8459 ps_global->mangled_screen = 1;
8461 else if(s == '!')
8462 not = !not;
8463 else
8464 break;
8467 if(ps_global && ps_global->ttyo){
8468 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8469 ps_global->mangled_footer = 1;
8472 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8473 rv = agg_flag_select(stream, not, s, limitsrch);
8474 if(we_cancel)
8475 cancel_busy_cue(0);
8477 return(rv);
8482 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8483 * Sets searched bits in mail_elts
8485 * Args limitsrch -- limit search to this searchset
8487 * Returns 0 on success.
8490 select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
8492 char rulenick[1000], *nick;
8493 PATGRP_S *patgrp;
8494 int r, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
8495 | ROLE_DO_INCOLS
8496 | ROLE_DO_ROLES
8497 | ROLE_DO_SCORES
8498 | ROLE_DO_OTHER
8499 | ROLE_DO_FILTER;
8501 rulenick[0] = '\0';
8502 ps_global->mangled_footer = 1;
8505 int oe_flags;
8507 oe_flags = OE_APPEND_CURRENT;
8508 r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0,
8509 sizeof(rulenick),
8510 not ? _("Rule to NOT match: ")
8511 : _("Rule to match: "),
8512 sel_key_opt, NO_HELP, &oe_flags);
8514 if(r == 14){
8515 /* select rulenick from a list */
8516 if((nick=choose_a_rule(rflags)) != NULL){
8517 strncpy(rulenick, nick, sizeof(rulenick)-1);
8518 rulenick[sizeof(rulenick)-1] = '\0';
8519 fs_give((void **) &nick);
8521 else
8522 r = 4;
8524 else if(r == '!')
8525 not = !not;
8527 if(r == 3){
8528 helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE);
8529 ps_global->mangled_screen = 1;
8531 else if(r == 1){
8532 cmd_cancelled("Selection by Rule");
8533 return(1);
8536 removing_leading_and_trailing_white_space(rulenick);
8538 }while(r == 3 || r == 4 || r == '!');
8542 * The approach of requiring a nickname instead of just allowing the
8543 * user to select from the list of rules has the drawback that a rule
8544 * may not have a nickname, or there may be more than one rule with
8545 * the same nickname. However, it has the benefit of allowing the user
8546 * to type in the nickname and, most importantly, allows us to set
8547 * up the ! (not). We could incorporate the ! into the selection
8548 * screen, but this is easier and also allows the typing of nicks.
8549 * User can just set up nicknames if they want to use this feature.
8551 patgrp = nick_to_patgrp(rulenick, rflags);
8553 if(patgrp){
8554 if(ps_global && ps_global->ttyo){
8555 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8556 ps_global->mangled_footer = 1;
8559 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8560 match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL,
8561 get_msg_score,
8562 (not ? MP_NOT : 0) | SE_NOPREFETCH);
8563 free_patgrp(&patgrp);
8564 if(we_cancel)
8565 cancel_busy_cue(0);
8568 if(limitsrch && *limitsrch){
8569 mail_free_searchset(limitsrch);
8570 *limitsrch = NULL;
8573 return(0);
8578 * Allow user to choose a rule from their list of rules.
8580 * Returns an allocated rule nickname on success, NULL otherwise.
8582 char *
8583 choose_a_rule(int rflags)
8585 char *choice = NULL;
8586 char **rule_list, **lp;
8587 int cnt = 0;
8588 PAT_S *pat;
8589 PAT_STATE pstate;
8591 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){
8592 q_status_message(SM_ORDER, 3, 3,
8593 _("No rules available. Use Setup/Rules to add some."));
8594 return(choice);
8598 * Build a list of rules to choose from.
8601 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8602 cnt++;
8604 if(cnt <= 0){
8605 q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
8606 return(choice);
8609 lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
8610 memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
8612 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8613 *lp++ = cpystr((pat->patgrp && pat->patgrp->nick)
8614 ? pat->patgrp->nick : "?");
8616 /* TRANSLATORS: SELECT A RULE is a screen title
8617 TRANSLATORS: Print something1 using something2.
8618 "rules" is something1 */
8619 choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"),
8620 _("rules"), h_select_rule_screen,
8621 _("HELP FOR SELECTING A RULE NICKNAME"), NULL);
8623 if(!choice)
8624 q_status_message(SM_ORDER, 1, 4, "No choice");
8626 free_list_array(&rule_list);
8628 return(choice);
8633 * Select by current thread.
8634 * Sets searched bits in mail_elts for this entire thread
8636 * Args limitsrch -- limit search to this searchset
8638 * Returns 0 on success.
8641 select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
8643 long n;
8644 PINETHRD_S *thrd = NULL;
8645 int ret = 1;
8646 MESSAGECACHE *mc;
8648 if(!stream)
8649 return(ret);
8651 for(n = 1L; n <= stream->nmsgs; n++)
8652 if((mc = mail_elt(stream, n)) != NULL)
8653 mc->searched = 0; /* clear searched bits */
8655 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
8656 if(thrd && thrd->top && thrd->top != thrd->rawno)
8657 thrd = fetch_thread(stream, thrd->top);
8660 * This doesn't unselect if the thread is already selected
8661 * (like select current does), it always selects.
8662 * There is no way to select ! this thread.
8664 if(thrd){
8665 set_search_bit_for_thread(stream, thrd, limitsrch);
8666 ret = 0;
8669 return(ret);
8674 * Select by message keywords.
8675 * Sets searched bits in mail_elts
8677 * Args limitsrch -- limit search to this searchset
8679 * Returns 0 on success.
8682 select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
8684 int r, not = 0, we_cancel = 0;
8685 char keyword[MAXUSERFLAG+1], *kword;
8686 char *error = NULL, *p, *prompt;
8687 HelpType help;
8688 SEARCHPGM *pgm;
8690 keyword[0] = '\0';
8691 ps_global->mangled_footer = 1;
8693 help = NO_HELP;
8695 int oe_flags;
8697 if(error){
8698 q_status_message(SM_ORDER, 3, 4, error);
8699 fs_give((void **) &error);
8702 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
8703 if(not)
8704 prompt = _("Keyword (or keyword initial) to NOT match: ");
8705 else
8706 prompt = _("Keyword (or keyword initial) to match: ");
8708 else{
8709 if(not)
8710 prompt = _("Keyword to NOT match: ");
8711 else
8712 prompt = _("Keyword to match: ");
8715 oe_flags = OE_APPEND_CURRENT;
8716 r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0,
8717 sizeof(keyword),
8718 prompt, sel_key_opt, help, &oe_flags);
8720 if(r == 14){
8721 /* select keyword from a list */
8722 if((kword=choose_a_keyword()) != NULL){
8723 strncpy(keyword, kword, sizeof(keyword)-1);
8724 keyword[sizeof(keyword)-1] = '\0';
8725 fs_give((void **) &kword);
8727 else
8728 r = 4;
8730 else if(r == '!')
8731 not = !not;
8733 if(r == 3)
8734 help = help == NO_HELP ? h_select_keyword : NO_HELP;
8735 else if(r == 1){
8736 cmd_cancelled("Selection by keyword");
8737 return(1);
8740 removing_leading_and_trailing_white_space(keyword);
8742 }while(r == 3 || r == 4 || r == '!' || keyword_check(keyword, &error));
8745 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
8746 p = initial_to_keyword(keyword);
8747 if(p != keyword){
8748 strncpy(keyword, p, sizeof(keyword)-1);
8749 keyword[sizeof(keyword)-1] = '\0';
8754 * We want to check the keyword, not the nickname of the keyword,
8755 * so convert it to the keyword if necessary.
8757 p = nick_to_keyword(keyword);
8758 if(p != keyword){
8759 strncpy(keyword, p, sizeof(keyword)-1);
8760 keyword[sizeof(keyword)-1] = '\0';
8763 pgm = mail_newsearchpgm();
8764 if(not){
8765 pgm->unkeyword = mail_newstringlist();
8766 pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword);
8767 pgm->unkeyword->text.size = strlen(keyword);
8769 else{
8770 pgm->keyword = mail_newstringlist();
8771 pgm->keyword->text.data = (unsigned char *) cpystr(keyword);
8772 pgm->keyword->text.size = strlen(keyword);
8775 if(ps_global && ps_global->ttyo){
8776 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8777 ps_global->mangled_footer = 1;
8780 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8782 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8783 pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE);
8784 /* we know this was freed in mail_search, let caller know */
8785 if(limitsrch)
8786 *limitsrch = NULL;
8788 if(we_cancel)
8789 cancel_busy_cue(0);
8791 return(0);
8796 * Allow user to choose a keyword from their list of keywords.
8798 * Returns an allocated keyword on success, NULL otherwise.
8800 char *
8801 choose_a_keyword(void)
8803 char *choice = NULL;
8804 char **keyword_list, **lp;
8805 int cnt;
8806 KEYWORD_S *kw;
8809 * Build a list of keywords to choose from.
8812 for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next)
8813 cnt++;
8815 if(cnt <= 0){
8816 q_status_message(SM_ORDER, 3, 4,
8817 _("No keywords defined, use \"keywords\" option in Setup/Config"));
8818 return(choice);
8821 lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list));
8822 memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list));
8824 for(kw = ps_global->keywords; kw; kw = kw->next)
8825 *lp++ = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
8827 /* TRANSLATORS: SELECT A KEYWORD is a screen title
8828 TRANSLATORS: Print something1 using something2.
8829 "keywords" is something1 */
8830 choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"),
8831 _("keywords"), h_select_keyword_screen,
8832 _("HELP FOR SELECTING A KEYWORD"), NULL);
8834 if(!choice)
8835 q_status_message(SM_ORDER, 1, 4, "No choice");
8837 free_list_array(&keyword_list);
8839 return(choice);
8844 * Allow user to choose a list of keywords from their list of keywords.
8846 * Returns allocated list.
8848 char **
8849 choose_list_of_keywords(void)
8851 LIST_SEL_S *listhead, *ls, *p;
8852 char **ret = NULL;
8853 int cnt, i;
8854 KEYWORD_S *kw;
8857 * Build a list of keywords to choose from.
8860 p = listhead = NULL;
8861 for(kw = ps_global->keywords; kw; kw = kw->next){
8863 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
8864 memset(ls, 0, sizeof(*ls));
8865 ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
8867 if(p){
8868 p->next = ls;
8869 p = p->next;
8871 else
8872 listhead = p = ls;
8875 if(!listhead)
8876 return(ret);
8878 /* TRANSLATORS: SELECT KEYWORDS is a screen title
8879 Print something1 using something2.
8880 "keywords" is something1 */
8881 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
8882 _("SELECT KEYWORDS"), _("keywords"),
8883 h_select_multkeyword_screen,
8884 _("HELP FOR SELECTING KEYWORDS"), NULL)){
8885 for(cnt = 0, p = listhead; p; p = p->next)
8886 if(p->selected)
8887 cnt++;
8889 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
8890 memset(ret, 0, (cnt+1) * sizeof(*ret));
8891 for(i = 0, p = listhead; p; p = p->next)
8892 if(p->selected)
8893 ret[i++] = cpystr(p->item ? p->item : "");
8896 free_list_sel(&listhead);
8898 return(ret);
8903 * Allow user to choose a charset
8905 * Returns an allocated charset on success, NULL otherwise.
8907 char *
8908 choose_a_charset(int which_charsets)
8910 char *choice = NULL;
8911 char **charset_list, **lp;
8912 const CHARSET *cs;
8913 int cnt;
8916 * Build a list of charsets to choose from.
8919 for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){
8920 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
8921 && ((which_charsets & CAC_ALL)
8922 || (which_charsets & CAC_POSTING
8923 && cs->flags & CF_POSTING)
8924 || (which_charsets & CAC_DISPLAY
8925 && cs->type != CT_2022
8926 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
8927 cnt++;
8930 if(cnt <= 0){
8931 q_status_message(SM_ORDER, 3, 4,
8932 _("No charsets found? Enter charset manually."));
8933 return(choice);
8936 lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list));
8937 memset(charset_list, 0, (cnt+1) * sizeof(*charset_list));
8939 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
8940 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
8941 && ((which_charsets & CAC_ALL)
8942 || (which_charsets & CAC_POSTING
8943 && cs->flags & CF_POSTING)
8944 || (which_charsets & CAC_DISPLAY
8945 && cs->type != CT_2022
8946 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
8947 *lp++ = cpystr(cs->name);
8950 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
8951 TRANSLATORS: Print something1 using something2.
8952 "character sets" is something1 */
8953 choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"),
8954 _("character sets"), h_select_charset_screen,
8955 _("HELP FOR SELECTING A CHARACTER SET"), NULL);
8957 if(!choice)
8958 q_status_message(SM_ORDER, 1, 4, "No choice");
8960 free_list_array(&charset_list);
8962 return(choice);
8967 * Allow user to choose a list of character sets and/or scripts
8969 * Returns allocated list.
8971 char **
8972 choose_list_of_charsets(void)
8974 LIST_SEL_S *listhead, *ls, *p;
8975 char **ret = NULL;
8976 int cnt, i, got_one;
8977 const CHARSET *cs;
8978 SCRIPT *s;
8979 char *q, *t;
8980 long width, limit;
8981 char buf[1024], *folded;
8984 * Build a list of charsets to choose from.
8987 p = listhead = NULL;
8989 /* this width is determined by select_from_list_screen() */
8990 width = ps_global->ttyo->screen_cols - 4;
8992 /* first comes a list of scripts (sets of character sets) */
8993 for(s = utf8_script(NIL); s && s->name; s++){
8995 limit = sizeof(buf)-1;
8996 q = buf;
8997 memset(q, 0, limit+1);
8999 if(s->name)
9000 sstrncpy(&q, s->name, limit);
9002 if(s->description){
9003 sstrncpy(&q, " (", limit-(q-buf));
9004 sstrncpy(&q, s->description, limit-(q-buf));
9005 sstrncpy(&q, ")", limit-(q-buf));
9008 /* add the list of charsets that are in this script */
9009 got_one = 0;
9010 for(cs = utf8_charset(NIL);
9011 cs && cs->name && (q-buf) < limit; cs++){
9012 if(cs->script & s->script){
9014 * Filter out some un-useful members of the list.
9015 * UTF-7 and UTF-8 weren't actually in the list at the
9016 * time this was written. Just making sure.
9018 if(!strucmp(cs->name, "ISO-2022-JP-2")
9019 || !strucmp(cs->name, "UTF-7")
9020 || !strucmp(cs->name, "UTF-8"))
9021 continue;
9023 if(got_one)
9024 sstrncpy(&q, " ", limit-(q-buf));
9025 else{
9026 got_one = 1;
9027 sstrncpy(&q, " {", limit-(q-buf));
9030 sstrncpy(&q, cs->name, limit-(q-buf));
9034 if(got_one)
9035 sstrncpy(&q, "}", limit-(q-buf));
9037 /* fold this line so that it can all be seen on the screen */
9038 folded = fold(buf, width, width, "", " ", FLD_NONE);
9039 if(folded){
9040 t = folded;
9041 while(t && *t && (q = strindex(t, '\n')) != NULL){
9042 *q = '\0';
9044 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9045 memset(ls, 0, sizeof(*ls));
9046 if(t == folded)
9047 ls->item = cpystr(s->name);
9048 else
9049 ls->flags = SFL_NOSELECT;
9051 ls->display_item = cpystr(t);
9053 t = q+1;
9055 if(p){
9056 p->next = ls;
9057 p = p->next;
9059 else{
9060 /* add a heading */
9061 listhead = (LIST_SEL_S *) fs_get(sizeof(*ls));
9062 memset(listhead, 0, sizeof(*listhead));
9063 listhead->flags = SFL_NOSELECT;
9064 listhead->display_item =
9065 cpystr(_("Scripts representing groups of related character sets"));
9066 listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls));
9067 memset(listhead->next, 0, sizeof(*listhead));
9068 listhead->next->flags = SFL_NOSELECT;
9069 listhead->next->display_item =
9070 cpystr(repeat_char(width, '-'));
9072 listhead->next->next = ls;
9073 p = ls;
9077 fs_give((void **) &folded);
9081 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9082 memset(ls, 0, sizeof(*ls));
9083 ls->flags = SFL_NOSELECT;
9084 if(p){
9085 p->next = ls;
9086 p = p->next;
9088 else
9089 listhead = p = ls;
9091 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9092 memset(ls, 0, sizeof(*ls));
9093 ls->flags = SFL_NOSELECT;
9094 ls->display_item =
9095 cpystr(_("Individual character sets, may be mixed with scripts"));
9096 p->next = ls;
9097 p = p->next;
9099 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9100 memset(ls, 0, sizeof(*ls));
9101 ls->flags = SFL_NOSELECT;
9102 ls->display_item =
9103 cpystr(repeat_char(width, '-'));
9104 p->next = ls;
9105 p = p->next;
9107 /* then comes a list of individual character sets */
9108 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9109 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9110 memset(ls, 0, sizeof(*ls));
9111 ls->item = cpystr(cs->name);
9113 if(p){
9114 p->next = ls;
9115 p = p->next;
9117 else
9118 listhead = p = ls;
9121 if(!listhead)
9122 return(ret);
9124 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9125 Print something1 using something2.
9126 "character sets" is something1 */
9127 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9128 _("SELECT CHARACTER SETS"), _("character sets"),
9129 h_select_multcharsets_screen,
9130 _("HELP FOR SELECTING CHARACTER SETS"), NULL)){
9131 for(cnt = 0, p = listhead; p; p = p->next)
9132 if(p->selected)
9133 cnt++;
9135 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9136 memset(ret, 0, (cnt+1) * sizeof(*ret));
9137 for(i = 0, p = listhead; p; p = p->next)
9138 if(p->selected)
9139 ret[i++] = cpystr(p->item ? p->item : "");
9142 free_list_sel(&listhead);
9144 return(ret);
9147 /* Report quota summary resources in an IMAP server */
9149 void cmd_quota (struct pine *state)
9151 QUOTALIST *imapquota;
9152 NETMBX mb;
9153 STORE_S *store;
9154 SCROLL_S sargs;
9156 if(!state->mail_stream || !is_imap_stream(state->mail_stream)){
9157 q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders");
9158 return;
9161 if (state->mail_stream
9162 && !sp_dead_stream(state->mail_stream)
9163 && state->mail_stream->mailbox
9164 && *state->mail_stream->mailbox
9165 && mail_valid_net_parse(state->mail_stream->mailbox, &mb))
9166 imap_getquotaroot(state->mail_stream, mb.mailbox);
9168 if(!state->quota) /* failed ? */
9169 return; /* go back... */
9171 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
9172 q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space.");
9173 return;
9176 so_puts(store, "Quota Report for ");
9177 so_puts(store, state->mail_stream->original_mailbox);
9178 so_puts(store, "\n\n");
9180 for (imapquota = state->quota; imapquota; imapquota = imapquota->next){
9182 so_puts(store, _("Resource : "));
9183 so_puts(store, imapquota->name);
9184 so_writec('\n', store);
9186 so_puts(store, _("Usage : "));
9187 so_puts(store, long2string(imapquota->usage));
9188 if(!strucmp(imapquota->name,"STORAGE"))
9189 so_puts(store, " KiB ");
9190 if(!strucmp(imapquota->name,"MESSAGE")){
9191 so_puts(store, _(" message"));
9192 if(imapquota->usage != 1)
9193 so_puts(store, _("s ")); /* plural */
9194 else
9195 so_puts(store, _(" "));
9197 so_writec('(', store);
9198 so_puts(store, long2string(100*imapquota->usage/imapquota->limit));
9199 so_puts(store, "%)\n");
9201 so_puts(store, _("Limit : "));
9202 so_puts(store, long2string(imapquota->limit));
9203 if(!strucmp(imapquota->name,"STORAGE"))
9204 so_puts(store, " KiB\n\n");
9205 if(!strucmp(imapquota->name,"MESSAGE")){
9206 so_puts(store, _(" message"));
9207 if(imapquota->usage != 1)
9208 so_puts(store, _("s\n\n")); /* plural */
9209 else
9210 so_puts(store, _("\n\n"));
9214 memset(&sargs, 0, sizeof(SCROLL_S));
9215 sargs.text.text = so_text(store);
9216 sargs.text.src = CharStar;
9217 sargs.text.desc = _("Quota Resources Summary");
9218 sargs.bar.title = _("QUOTA SUMMARY");
9219 sargs.proc.tool = NULL;
9220 sargs.help.text = h_quota_command;
9221 sargs.help.title = NULL;
9222 sargs.keys.menu = NULL;
9223 setbitmap(sargs.keys.bitmap);
9225 scrolltool(&sargs);
9226 so_give(&store);
9228 if (state->quota)
9229 mail_free_quotalist(&(state->quota));
9232 /*----------------------------------------------------------------------
9233 Prompt the user for the type of sort he desires
9235 Args: state -- pine state pointer
9236 q1 -- Line to prompt on
9238 Returns 0 if it was cancelled, 1 otherwise.
9239 ----*/
9241 select_sort(struct pine *state, int ql, SortOrder *sort, int *rev)
9243 char prompt[200], tmp[3], *p;
9244 int s, i;
9245 int deefault = 'a', retval = 1;
9246 HelpType help;
9247 ESCKEY_S sorts[14];
9249 #ifdef _WINDOWS
9250 DLG_SORTPARAM sortsel;
9252 if (mswin_usedialog ()) {
9254 sortsel.reverse = mn_get_revsort (state->msgmap);
9255 sortsel.cursort = mn_get_sort (state->msgmap);
9256 /* assumption here that HelpType is char ** */
9257 sortsel.helptext = h_select_sort;
9258 sortsel.rval = 0;
9260 if ((retval = os_sortdialog (&sortsel))) {
9261 *sort = sortsel.cursort;
9262 *rev = sortsel.reverse;
9265 return (retval);
9267 #endif
9269 /*----- String together the prompt ------*/
9270 tmp[1] = '\0';
9271 if(F_ON(F_USE_FK,ps_global))
9272 strncpy(prompt, _("Choose type of sort : "), sizeof(prompt));
9273 else
9274 strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "),
9275 sizeof(prompt));
9277 for(i = 0; state->sort_types[i] != EndofList; i++) {
9278 sorts[i].rval = i;
9279 p = sorts[i].label = sort_name(state->sort_types[i]);
9280 while(*(p+1) && islower((unsigned char)*p))
9281 p++;
9283 sorts[i].ch = tolower((unsigned char)(tmp[0] = *p));
9284 sorts[i].name = cpystr(tmp);
9286 if(mn_get_sort(state->msgmap) == state->sort_types[i])
9287 deefault = sorts[i].rval;
9290 sorts[i].ch = 'r';
9291 sorts[i].rval = 'r';
9292 sorts[i].name = cpystr("R");
9293 if(F_ON(F_USE_FK,ps_global))
9294 sorts[i].label = N_("Reverse");
9295 else
9296 sorts[i].label = "";
9298 sorts[++i].ch = -1;
9299 help = h_select_sort;
9301 if((F_ON(F_USE_FK,ps_global)
9302 && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x',
9303 help,RB_NORM)) != 'x'))
9305 (F_OFF(F_USE_FK,ps_global)
9306 && ((s = radio_buttons(prompt,ql,sorts,deefault,'x',
9307 help,RB_NORM)) != 'x'))){
9308 state->mangled_body = 1; /* signal screen's changed */
9309 if(s == 'r')
9310 *rev = !mn_get_revsort(state->msgmap);
9311 else
9312 *sort = state->sort_types[s];
9314 if(F_ON(F_SHOW_SORT, ps_global))
9315 ps_global->mangled_header = 1;
9317 else{
9318 retval = 0;
9319 cmd_cancelled("Sort");
9322 while(--i >= 0)
9323 fs_give((void **)&sorts[i].name);
9325 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9326 return(retval);
9330 /*---------------------------------------------------------------------
9331 Build list of folders in the given context for user selection
9333 Args: c -- pointer to pointer to folder's context context
9334 f -- folder prefix to display
9335 sublist -- whether or not to use 'f's contents as prefix
9336 lister -- function used to do the actual display
9338 Returns: malloc'd string containing sequence, else NULL if
9339 no messages in msgmap with local "selected" flag.
9340 ----*/
9342 display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int))
9344 int rc;
9345 CONTEXT_S *tc;
9346 void (*redraw)(void) = ps_global->redrawer;
9348 push_titlebar_state();
9349 tc = *c;
9350 if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0)
9351 *c = tc;
9353 ClearScreen();
9354 pop_titlebar_state();
9355 redraw_titlebar();
9356 if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
9357 (*ps_global->redrawer)();
9359 if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
9360 return(1);
9362 return(0);
9367 * Allow user to choose a single item from a list of strings.
9369 * Args list -- Array of strings to choose from, NULL terminated.
9370 * displist -- Array of strings to display instead of displaying list.
9371 * Indices correspond to the list array. Display the displist
9372 * but return the item from list if displist non-NULL.
9373 * title -- For conf_scroll_screen
9374 * pdesc -- For conf_scroll_screen
9375 * help -- For conf_scroll_screen
9376 * htitle -- For conf_scroll_screen
9378 * Returns an allocated copy of the chosen item or NULL.
9380 char *
9381 choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help,
9382 char *htitle, char *cursor_location)
9384 LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL;
9385 char **t, **dl;
9386 char *ret = NULL, *choice = NULL;
9388 /* build the LIST_SEL_S list */
9389 p = listhead = NULL;
9390 for(t = list, dl = displist; *t; t++, dl++){
9391 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9392 memset(ls, 0, sizeof(*ls));
9393 ls->item = cpystr(*t);
9394 if(displist)
9395 ls->display_item = cpystr(*dl);
9397 if(cursor_location && (cursor_location == (*t)))
9398 starting_val = ls;
9400 if(p){
9401 p->next = ls;
9402 p = p->next;
9404 else
9405 listhead = p = ls;
9408 if(!listhead)
9409 return(ret);
9411 if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc,
9412 help, htitle, starting_val))
9413 for(p = listhead; !choice && p; p = p->next)
9414 if(p->selected)
9415 choice = p->item;
9417 if(choice)
9418 ret = cpystr(choice);
9420 free_list_sel(&listhead);
9422 return(ret);
9426 void
9427 free_list_sel(LIST_SEL_S **lsel)
9429 if(lsel && *lsel){
9430 free_list_sel(&(*lsel)->next);
9431 if((*lsel)->item)
9432 fs_give((void **) &(*lsel)->item);
9434 if((*lsel)->display_item)
9435 fs_give((void **) &(*lsel)->display_item);
9437 fs_give((void **) lsel);
9443 * file_lister - call pico library's file lister
9446 file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags)
9448 PICO pbf;
9449 int rv;
9450 void (*redraw)(void) = ps_global->redrawer;
9452 standard_picobuf_setup(&pbf);
9453 push_titlebar_state();
9454 if(!newmail)
9455 pbf.newmail = NULL;
9457 /* BUG: what about help command and text? */
9458 pbf.pine_anchor = title;
9460 rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags);
9461 standard_picobuf_teardown(&pbf);
9462 fix_windsize(ps_global);
9463 init_signals(); /* has it's own signal stuff */
9465 /* Restore display's titlebar and body */
9466 pop_titlebar_state();
9467 redraw_titlebar();
9468 if((ps_global->redrawer = redraw) != NULL)
9469 (*ps_global->redrawer)();
9471 return(rv);
9475 /*----------------------------------------------------------------------
9476 Print current folder index
9478 ---*/
9480 print_index(struct pine *state, MSGNO_S *msgmap, int agg)
9482 long i;
9483 ICE_S *ice;
9484 char buf[MAX_SCREEN_COLS+1];
9486 for(i = 1L; i <= mn_get_total(msgmap); i++){
9487 if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
9488 continue;
9490 if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0))
9491 continue;
9493 ice = build_header_line(state, state->mail_stream, msgmap, i, NULL);
9495 if(ice){
9497 * I don't understand why we'd want to mark the current message
9498 * instead of printing out the first character of the status
9499 * so I'm taking it out and including the first character of the
9500 * line instead. Hubert 2006-02-09
9502 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
9503 return(0);
9506 if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i),
9507 print_char)
9508 || !gf_puts(NEWLINE, print_char))
9509 return(0);
9513 return(1);
9517 #ifdef _WINDOWS
9520 * windows callback to get/set header mode state
9523 header_mode_callback(set, args)
9524 int set;
9525 long args;
9527 return(ps_global->full_header);
9532 * windows callback to get/set zoom mode state
9535 zoom_mode_callback(set, args)
9536 int set;
9537 long args;
9539 return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0);
9544 * windows callback to get/set zoom mode state
9547 any_selected_callback(set, args)
9548 int set;
9549 long args;
9551 return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0);
9559 flag_callback(set, flags)
9560 int set;
9561 long flags;
9563 MESSAGECACHE *mc;
9564 int newflags = 0;
9565 long msgno;
9566 int permflag = 0;
9568 switch (set) {
9569 case 1: /* Important */
9570 permflag = ps_global->mail_stream->perm_flagged;
9571 break;
9573 case 2: /* New */
9574 permflag = ps_global->mail_stream->perm_seen;
9575 break;
9577 case 3: /* Answered */
9578 permflag = ps_global->mail_stream->perm_answered;
9579 break;
9581 case 4: /* Deleted */
9582 permflag = ps_global->mail_stream->perm_deleted;
9583 break;
9587 if(!(any_messages(ps_global->msgmap, NULL, "to Flag")
9588 && can_set_flag(ps_global, "flag", permflag)))
9589 return(0);
9591 if(sp_io_error_on_stream(ps_global->mail_stream)){
9592 sp_set_io_error_on_stream(ps_global->mail_stream, 0);
9593 pine_mail_check(ps_global->mail_stream); /* forces write */
9594 return(0);
9597 msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
9598 if(msgno > 0L && ps_global->mail_stream
9599 && msgno <= ps_global->mail_stream->nmsgs
9600 && (mc = mail_elt(ps_global->mail_stream, msgno))
9601 && mc->valid){
9603 * NOTE: code below is *VERY* sensitive to the order of
9604 * the messages defined in resource.h for flag handling.
9605 * Don't change it unless you know what you're doing.
9607 if(set){
9608 char *flagstr;
9609 long mflag;
9611 switch(set){
9612 case 1 : /* Important */
9613 flagstr = "\\FLAGGED";
9614 mflag = (mc->flagged) ? 0L : ST_SET;
9615 break;
9617 case 2 : /* New */
9618 flagstr = "\\SEEN";
9619 mflag = (mc->seen) ? 0L : ST_SET;
9620 break;
9622 case 3 : /* Answered */
9623 flagstr = "\\ANSWERED";
9624 mflag = (mc->answered) ? 0L : ST_SET;
9625 break;
9627 case 4 : /* Deleted */
9628 flagstr = "\\DELETED";
9629 mflag = (mc->deleted) ? 0L : ST_SET;
9630 break;
9632 default : /* bogus */
9633 return(0);
9636 mail_flag(ps_global->mail_stream, long2string(msgno),
9637 flagstr, mflag);
9639 if(ps_global->redrawer)
9640 (*ps_global->redrawer)();
9642 else{
9643 /* Important */
9644 if(mc->flagged)
9645 newflags |= 0x0001;
9647 /* New */
9648 if(!mc->seen)
9649 newflags |= 0x0002;
9651 /* Answered */
9652 if(mc->answered)
9653 newflags |= 0x0004;
9655 /* Deleted */
9656 if(mc->deleted)
9657 newflags |= 0x0008;
9661 return(newflags);
9667 * BUG: Should teach this about keywords
9669 MPopup *
9670 flag_submenu(mc)
9671 MESSAGECACHE *mc;
9673 static MPopup flag_submenu[] = {
9674 {tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}},
9675 {tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}},
9676 {tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}},
9677 {tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}},
9678 {tTail}
9681 /* Important */
9682 flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal;
9684 /* New */
9685 flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked;
9687 /* Answered */
9688 flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal;
9690 /* Deleted */
9691 flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal;
9693 return(flag_submenu);
9696 #endif /* _WINDOWS */