* new version 2.19.9993
[alpine.git] / alpine / mailcmd.c
blob163fdff6d3699850f74065f26af112f30857726f
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-2014 Eduardo Chappa
8 * Copyright 2006-2009 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
20 mailcmd.c
21 The meat and pototoes of mail processing here:
22 - initial command processing and dispatch
23 - save message
24 - capture address off incoming mail
25 - jump to specific numbered message
26 - open (broach) a new folder
27 - search message headers (where is) command
28 ====*/
31 #include "headers.h"
32 #include "mailcmd.h"
33 #include "status.h"
34 #include "mailview.h"
35 #include "flagmaint.h"
36 #include "listsel.h"
37 #include "keymenu.h"
38 #include "alpine.h"
39 #include "mailpart.h"
40 #include "mailindx.h"
41 #include "folder.h"
42 #include "reply.h"
43 #include "help.h"
44 #include "titlebar.h"
45 #include "signal.h"
46 #include "radio.h"
47 #include "pipe.h"
48 #include "send.h"
49 #include "takeaddr.h"
50 #include "roleconf.h"
51 #include "smime.h"
52 #include "../pith/state.h"
53 #include "../pith/msgno.h"
54 #include "../pith/store.h"
55 #include "../pith/thread.h"
56 #include "../pith/flag.h"
57 #include "../pith/sort.h"
58 #include "../pith/maillist.h"
59 #include "../pith/save.h"
60 #include "../pith/pipe.h"
61 #include "../pith/news.h"
62 #include "../pith/util.h"
63 #include "../pith/sequence.h"
64 #include "../pith/keyword.h"
65 #include "../pith/stream.h"
66 #include "../pith/mailcmd.h"
67 #include "../pith/hist.h"
68 #include "../pith/list.h"
69 #include "../pith/icache.h"
70 #include "../pith/busy.h"
71 #include "../pith/mimedesc.h"
72 #include "../pith/pattern.h"
73 #include "../pith/tempfile.h"
74 #include "../pith/search.h"
75 #include "../pith/margin.h"
76 #ifdef _WINDOWS
77 #include "../pico/osdep/mswin.h"
78 #endif
81 * Internal Prototypes
83 int cmd_flag(struct pine *, MSGNO_S *, int);
84 int cmd_flag_prompt(struct pine *, struct flag_screen *, int);
85 void free_flag_table(struct flag_table **);
86 int cmd_reply(struct pine *, MSGNO_S *, int);
87 int cmd_forward(struct pine *, MSGNO_S *, int);
88 int cmd_bounce(struct pine *, MSGNO_S *, int);
89 int cmd_save(struct pine *, MAILSTREAM *, MSGNO_S *, int, CmdWhere);
90 void role_compose(struct pine *);
91 int cmd_expunge(struct pine *, MAILSTREAM *, MSGNO_S *, int);
92 int cmd_export(struct pine *, MSGNO_S *, int, int);
93 char *cmd_delete_action(struct pine *, MSGNO_S *, CmdWhere);
94 char *cmd_delete_view(struct pine *, MSGNO_S *);
95 char *cmd_delete_index(struct pine *, MSGNO_S *);
96 long get_level(int, UCS, SCROLL_S *);
97 long closest_jump_target(long, MAILSTREAM *, MSGNO_S *, int, CmdWhere, char *, size_t);
98 int update_folder_spec(char *, size_t, char *);
99 int cmd_print(struct pine *, MSGNO_S *, int, CmdWhere);
100 int cmd_pipe(struct pine *, MSGNO_S *, int);
101 STORE_S *list_mgmt_text(RFC2369_S *, long);
102 void list_mgmt_screen(STORE_S *);
103 int aggregate_select(struct pine *, MSGNO_S *, int, CmdWhere);
104 int select_by_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
105 int select_by_thrd_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
106 int select_by_date(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
107 int select_by_text(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
108 int select_by_size(MAILSTREAM *, SEARCHSET **);
109 SEARCHSET *visible_searchset(MAILSTREAM *, MSGNO_S *);
110 int select_by_status(MAILSTREAM *, SEARCHSET **);
111 int select_by_rule(MAILSTREAM *, SEARCHSET **);
112 int select_by_thread(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
113 char *choose_a_rule(int);
114 int select_by_keyword(MAILSTREAM *, SEARCHSET **);
115 char *choose_a_keyword(void);
116 int select_sort(struct pine *, int, SortOrder *, int *);
117 int print_index(struct pine *, MSGNO_S *, int);
120 * List of Select options used by apply_* functions...
122 static char *sel_pmt1 = N_("ALTER message selection : ");
123 ESCKEY_S sel_opts1[] = {
124 /* TRANSLATORS: these are keymenu names for selecting. Broaden selection means
125 we will add more messages to the selection, Narrow selection means we will
126 remove some selections (like a logical AND instead of logical OR), and Flip
127 Selected means that all the messages that are currently selected become unselected,
128 and all the unselected messages become selected. */
129 {'a', 'a', "A", N_("unselect All")},
130 {'c', 'c', "C", NULL},
131 {'b', 'b', "B", N_("Broaden selctn")},
132 {'n', 'n', "N", N_("Narrow selctn")},
133 {'f', 'f', "F", N_("Flip selected")},
134 {-1, 0, NULL, NULL}
138 #define SEL_OPTS_THREAD 9 /* index number of "tHread" */
139 #define SEL_OPTS_THREAD_CH 'h'
141 char *sel_pmt2 = "SELECT criteria : ";
142 static ESCKEY_S sel_opts2[] = {
143 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
144 means select the currently highlighted message; select by Number is by message
145 number; Status is by status of the message, for example the message might be
146 New or it might be Unseen or marked Important; Size has the Z upper case because
147 it is a Z command; Keyword is an alpine keyword that has been set by the user;
148 and Rule is an alpine rule */
149 {'a', 'a', "A", N_("select All")},
150 {'c', 'c', "C", N_("select Cur")},
151 {'n', 'n', "N", N_("Number")},
152 {'d', 'd', "D", N_("Date")},
153 {'t', 't', "T", N_("Text")},
154 {'s', 's', "S", N_("Status")},
155 {'z', 'z', "Z", N_("siZe")},
156 {'k', 'k', "K", N_("Keyword")},
157 {'r', 'r', "R", N_("Rule")},
158 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
159 {-1, 0, NULL, NULL}
163 static ESCKEY_S sel_opts3[] = {
164 /* TRANSLATORS: these are operations we can do on a set of selected messages.
165 Del is Delete; Undel is Undelete; TakeAddr means to Take some Addresses into
166 the address book; Save means to save the messages into another alpine folder;
167 Export means to copy the messages to a file outside of alpine, external to
168 alpine's world. */
169 {'d', 'd', "D", N_("Del")},
170 {'u', 'u', "U", N_("Undel")},
171 {'r', 'r', "R", N_("Reply")},
172 {'f', 'f', "F", N_("Forward")},
173 {'%', '%', "%", N_("Print")},
174 {'t', 't', "T", N_("TakeAddr")},
175 {'s', 's', "S", N_("Save")},
176 {'e', 'e', "E", N_("Export")},
177 { -1, 0, NULL, NULL},
178 { -1, 0, NULL, NULL},
179 { -1, 0, NULL, NULL},
180 { -1, 0, NULL, NULL},
181 { -1, 0, NULL, NULL},
182 { -1, 0, NULL, NULL},
183 { -1, 0, NULL, NULL},
184 {-1, 0, NULL, NULL}
187 static ESCKEY_S sel_opts4[] = {
188 {'a', 'a', "A", N_("select All")},
189 /* TRANSLATORS: select currrently highlighted message Thread */
190 {'c', 'c', "C", N_("select Curthrd")},
191 {'n', 'n', "N", N_("Number")},
192 {'d', 'd', "D", N_("Date")},
193 {'t', 't', "T", N_("Text")},
194 {'s', 's', "S", N_("Status")},
195 {'z', 'z', "Z", N_("siZe")},
196 {'k', 'k', "K", N_("Keyword")},
197 {'r', 'r', "R", N_("Rule")},
198 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
199 {-1, 0, NULL, NULL}
203 static char *sel_flag =
204 N_("Select New, Deleted, Answered, Forwarded, or Important messages ? ");
205 static char *sel_flag_not =
206 N_("Select NOT New, NOT Deleted, NOT Answered, NOT Forwarded or NOT Important msgs ? ");
207 static ESCKEY_S sel_flag_opt[] = {
208 /* TRANSLATORS: When selecting messages by message Status these are the
209 different types of Status you can select on. Is the message New, Recent,
210 and so on. Not means flip the meaning of the selection to the opposite
211 thing, so message is not New or not Important. */
212 {'n', 'n', "N", N_("New")},
213 {'*', '*', "*", N_("Important")},
214 {'d', 'd', "D", N_("Deleted")},
215 {'a', 'a', "A", N_("Answered")},
216 {'f', 'f', "F", N_("Forwarded")},
217 {-2, 0, NULL, NULL},
218 {'!', '!', "!", N_("Not")},
219 {-2, 0, NULL, NULL},
220 {'r', 'r', "R", N_("Recent")},
221 {'u', 'u', "U", N_("Unseen")},
222 {-1, 0, NULL, NULL}
226 static ESCKEY_S sel_date_opt[] = {
227 {0, 0, NULL, NULL},
228 /* TRANSLATORS: options when selecting messages by Date */
229 {ctrl('P'), 12, "^P", N_("Prev Day")},
230 {ctrl('N'), 13, "^N", N_("Next Day")},
231 {ctrl('X'), 11, "^X", N_("Cur Msg")},
232 {ctrl('W'), 14, "^W", N_("Toggle When")},
233 {KEY_UP, 12, "", ""},
234 {KEY_DOWN, 13, "", ""},
235 {-1, 0, NULL, NULL}
239 static char *sel_text =
240 N_("Select based on To, From, Cc, Recip, Partic, Subject fields or All msg text ? ");
241 static char *sel_text_not =
242 N_("Select based on NOT To, From, Cc, Recip, Partic, Subject or All msg text ? ");
243 static ESCKEY_S sel_text_opt[] = {
244 /* TRANSLATORS: Select messages based on the text contained in the From line, or
245 the Subject line, and so on. */
246 {'f', 'f', "F", N_("From")},
247 {'s', 's', "S", N_("Subject")},
248 {'t', 't', "T", N_("To")},
249 {'a', 'a', "A", N_("All Text")},
250 {'c', 'c', "C", N_("Cc")},
251 {'!', '!', "!", N_("Not")},
252 {'r', 'r', "R", N_("Recipient")},
253 {'p', 'p', "P", N_("Participant")},
254 {'b', 'b', "B", N_("Body")},
255 {'h', 'h', "H", N_("Header")},
256 {-1, 0, NULL, NULL}
259 static ESCKEY_S choose_action[] = {
260 {'c', 'c', "C", N_("Compose")},
261 {'r', 'r', "R", N_("Reply")},
262 {'f', 'f', "F", N_("Forward")},
263 {'b', 'b', "B", N_("Bounce")},
264 {-1, 0, NULL, NULL}
267 static char *select_num =
268 N_("Enter comma-delimited list of numbers (dash between ranges): ");
270 static char *select_size_larger_msg =
271 N_("Select messages with size larger than: ");
273 static char *select_size_smaller_msg =
274 N_("Select messages with size smaller than: ");
276 static char *sel_size_larger = N_("Larger");
277 static char *sel_size_smaller = N_("Smaller");
278 static ESCKEY_S sel_size_opt[] = {
279 {0, 0, NULL, NULL},
280 {ctrl('W'), 14, "^W", NULL},
281 {-1, 0, NULL, NULL}
284 static ESCKEY_S sel_key_opt[] = {
285 {0, 0, NULL, NULL},
286 {ctrl('T'), 14, "^T", N_("To List")},
287 {0, 0, NULL, NULL},
288 {'!', '!', "!", N_("Not")},
289 {-1, 0, NULL, NULL}
292 static ESCKEY_S flag_text_opt[] = {
293 /* TRANSLATORS: these are types of flags (markers) that the user can
294 set. For example, they can flag the message as an important message. */
295 {'n', 'n', "N", N_("New")},
296 {'*', '*', "*", N_("Important")},
297 {'d', 'd', "D", N_("Deleted")},
298 {'a', 'a', "A", N_("Answered")},
299 {'f', 'f', "F", N_("Forwarded")},
300 {'!', '!', "!", N_("Not")},
301 {ctrl('T'), 10, "^T", N_("To Flag Details")},
302 {-1, 0, NULL, NULL}
305 int
306 alpine_get_password(char *prompt, char *pass, size_t len)
308 int flags = OE_PASSWD | OE_DISALLOW_HELP;
309 pass[0] = '\0';
310 return optionally_enter(pass,
311 -(ps_global->ttyo ? FOOTER_ROWS(ps_global) : 3),
312 0, len, prompt, NULL, NO_HELP, &flags);
315 int smime_import_certificate(char *filename, char *full_filename, size_t len)
317 int r = 1;
318 static HISTORY_S *history = NULL;
319 static ESCKEY_S eopts[] = {
320 {ctrl('T'), 10, "^T", N_("To Files")},
321 {-1, 0, NULL, NULL},
322 {-1, 0, NULL, NULL}};
324 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
325 eopts[r].ch = ctrl('I');
326 eopts[r].rval = 11;
327 eopts[r].name = "TAB";
328 eopts[r].label = N_("Complete");
331 eopts[++r].ch = -1;
333 filename[0] = '\0';
334 full_filename[0] = '\0';
336 r = get_export_filename(ps_global, filename, NULL, full_filename,
337 len, "certificate", "IMPORT", eopts, NULL,
338 -FOOTER_ROWS(ps_global), GE_IS_IMPORT, &history);
340 return r;
344 /*----------------------------------------------------------------------
345 The giant switch on the commands for index and viewing
347 Input: command -- The command char/code
348 in_index -- flag indicating command is from index
349 orig_command -- The original command typed before pre-processing
350 Output: force_mailchk -- Set to tell caller to force call to new_mail().
352 Result: Manifold
354 Returns 1 if the message number or attachment to show changed
355 ---*/
357 process_cmd(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
358 int command, CmdWhere in_index, int *force_mailchk)
360 int question_line, a_changed, flags = 0, ret, j;
361 int notrealinbox;
362 long new_msgno, del_count, old_msgno, i;
363 long start;
364 char *newfolder, prompt[MAX_SCREEN_COLS+1];
365 CONTEXT_S *tc;
366 MESSAGECACHE *mc;
367 #if defined(DOS) && !defined(_WINDOWS)
368 extern long coreleft();
369 #endif
371 dprint((4, "\n - process_cmd(cmd=%d) -\n", command));
373 question_line = -FOOTER_ROWS(state);
374 state->mangled_screen = 0;
375 state->mangled_footer = 0;
376 state->mangled_header = 0;
377 state->next_screen = SCREEN_FUN_NULL;
378 old_msgno = mn_get_cur(msgmap);
379 a_changed = FALSE;
380 *force_mailchk = 0;
382 switch (command) {
383 /*------------- Help --------*/
384 case MC_HELP :
386 * We're not using the h_mail_view portion of this right now because
387 * that call is being handled in scrolltool() before it gets
388 * here. Leave it in case we change how it works.
390 helper((in_index == MsgIndx)
391 ? h_mail_index
392 : (in_index == View)
393 ? h_mail_view
394 : h_mail_thread_index,
395 (in_index == MsgIndx)
396 ? _("HELP FOR MESSAGE INDEX")
397 : (in_index == View)
398 ? _("HELP FOR MESSAGE TEXT")
399 : _("HELP FOR THREAD INDEX"),
400 HLPD_NONE);
401 dprint((4,"MAIL_CMD: did help command\n"));
402 state->mangled_screen = 1;
403 break;
406 /*--------- Return to main menu ------------*/
407 case MC_MAIN :
408 state->next_screen = main_menu_screen;
409 dprint((2,"MAIL_CMD: going back to main menu\n"));
410 break;
413 /*------- View message text --------*/
414 case MC_VIEW_TEXT :
415 view_text:
416 if(any_messages(msgmap, NULL, "to View")){
417 state->next_screen = mail_view_screen;
420 break;
423 /*------- View attachment --------*/
424 case MC_VIEW_ATCH :
425 state->next_screen = attachment_screen;
426 dprint((2,"MAIL_CMD: going to attachment screen\n"));
427 break;
430 /*---------- Previous message ----------*/
431 case MC_PREVITEM :
432 if(any_messages(msgmap, NULL, NULL)){
433 if((i = mn_get_cur(msgmap)) > 1L){
434 mn_dec_cur(stream, msgmap,
435 (in_index == View && THREADING()
436 && sp_viewing_a_thread(stream))
437 ? MH_THISTHD
438 : (in_index == View)
439 ? MH_ANYTHD : MH_NONE);
440 if(i == mn_get_cur(msgmap)){
441 PINETHRD_S *thrd = NULL, *topthrd = NULL;
443 if(THRD_INDX_ENABLED()){
444 mn_dec_cur(stream, msgmap, MH_ANYTHD);
445 if(i == mn_get_cur(msgmap))
446 q_status_message1(SM_ORDER, 0, 2,
447 _("Already on first %s in Zoomed Index"),
448 THRD_INDX() ? _("thread") : _("message"));
449 else{
450 if(in_index == View
451 || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
452 ret = 'y';
453 else
454 ret = want_to(_("View previous thread"), 'y', 'x',
455 NO_HELP, WT_NORM);
457 if(ret == 'y'){
458 q_status_message(SM_ORDER, 0, 2,
459 _("Viewing previous thread"));
460 new_msgno = mn_get_cur(msgmap);
461 mn_set_cur(msgmap, i);
462 if(unview_thread(state, stream, msgmap)){
463 state->next_screen = mail_index_screen;
464 state->view_skipped_index = 0;
465 state->mangled_screen = 1;
468 mn_set_cur(msgmap, new_msgno);
469 if(THRD_AUTO_VIEW() && in_index == View){
471 thrd = fetch_thread(stream,
472 mn_m2raw(msgmap,
473 new_msgno));
474 if(count_lflags_in_thread(stream, thrd,
475 msgmap,
476 MN_NONE) == 1){
477 if(view_thread(state, stream, msgmap, 1)){
478 if(current_index_state)
479 msgmap->top_after_thrd = current_index_state->msg_at_top;
481 state->view_skipped_index = 1;
482 command = MC_VIEW_TEXT;
483 goto view_text;
488 j = 0;
489 if(THRD_AUTO_VIEW() && in_index != View){
490 thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
491 if(thrd && thrd->top)
492 topthrd = fetch_thread(stream, thrd->top);
494 if(topthrd)
495 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
498 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
499 if(view_thread(state, stream, msgmap, 1)
500 && current_index_state)
501 msgmap->top_after_thrd = current_index_state->msg_at_top;
505 state->next_screen = SCREEN_FUN_NULL;
507 else
508 mn_set_cur(msgmap, i); /* put it back */
511 else
512 q_status_message1(SM_ORDER, 0, 2,
513 _("Already on first %s in Zoomed Index"),
514 THRD_INDX() ? _("thread") : _("message"));
517 else{
518 time_t now;
520 if(!IS_NEWS(stream)
521 && ((now = time(0)) > state->last_nextitem_forcechk)){
522 *force_mailchk = 1;
523 /* check at most once a second */
524 state->last_nextitem_forcechk = now;
527 q_status_message1(SM_ORDER, 0, 1, _("Already on first %s"),
528 THRD_INDX() ? _("thread") : _("message"));
532 break;
535 /*---------- Next Message ----------*/
536 case MC_NEXTITEM :
537 if(mn_get_total(msgmap) > 0L
538 && ((i = mn_get_cur(msgmap)) < mn_get_total(msgmap))){
539 mn_inc_cur(stream, msgmap,
540 (in_index == View && THREADING()
541 && sp_viewing_a_thread(stream))
542 ? MH_THISTHD
543 : (in_index == View)
544 ? MH_ANYTHD : MH_NONE);
545 if(i == mn_get_cur(msgmap)){
546 PINETHRD_S *thrd, *topthrd;
548 if(THRD_INDX_ENABLED()){
549 if(!THRD_INDX())
550 mn_inc_cur(stream, msgmap, MH_ANYTHD);
552 if(i == mn_get_cur(msgmap)){
553 if(any_lflagged(msgmap, MN_HIDE))
554 any_messages(NULL, "more", "in Zoomed Index");
555 else
556 goto nfolder;
558 else{
559 if(in_index == View
560 || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
561 ret = 'y';
562 else
563 ret = want_to(_("View next thread"), 'y', 'x',
564 NO_HELP, WT_NORM);
566 if(ret == 'y'){
567 q_status_message(SM_ORDER, 0, 2,
568 _("Viewing next thread"));
569 new_msgno = mn_get_cur(msgmap);
570 mn_set_cur(msgmap, i);
571 if(unview_thread(state, stream, msgmap)){
572 state->next_screen = mail_index_screen;
573 state->view_skipped_index = 0;
574 state->mangled_screen = 1;
577 mn_set_cur(msgmap, new_msgno);
578 if(THRD_AUTO_VIEW() && in_index == View){
580 thrd = fetch_thread(stream,
581 mn_m2raw(msgmap,
582 new_msgno));
583 if(count_lflags_in_thread(stream, thrd,
584 msgmap,
585 MN_NONE) == 1){
586 if(view_thread(state, stream, msgmap, 1)){
587 if(current_index_state)
588 msgmap->top_after_thrd = current_index_state->msg_at_top;
590 state->view_skipped_index = 1;
591 command = MC_VIEW_TEXT;
592 goto view_text;
597 j = 0;
598 if(THRD_AUTO_VIEW() && in_index != View){
599 thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
600 if(thrd && thrd->top)
601 topthrd = fetch_thread(stream, thrd->top);
603 if(topthrd)
604 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
607 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
608 if(view_thread(state, stream, msgmap, 1)
609 && current_index_state)
610 msgmap->top_after_thrd = current_index_state->msg_at_top;
614 state->next_screen = SCREEN_FUN_NULL;
616 else
617 mn_set_cur(msgmap, i); /* put it back */
620 else if(THREADING()
621 && (thrd = fetch_thread(stream, mn_m2raw(msgmap, i)))
622 && thrd->next
623 && get_lflag(stream, NULL, thrd->rawno, MN_COLL)){
624 q_status_message(SM_ORDER, 0, 2,
625 _("Expand collapsed thread to see more messages"));
627 else
628 any_messages(NULL, "more", "in Zoomed Index");
631 else{
632 time_t now;
633 nfolder:
634 prompt[0] = '\0';
635 if(IS_NEWS(stream)
636 || (state->context_current->use & CNTXT_INCMNG)){
637 char nextfolder[MAXPATH];
639 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
640 nextfolder[sizeof(nextfolder)-1] = '\0';
641 if(next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
642 state->context_current, NULL, NULL))
643 strncpy(prompt, _(". Press TAB for next folder."),
644 sizeof(prompt));
645 else
646 strncpy(prompt, _(". No more folders to TAB to."),
647 sizeof(prompt));
649 prompt[sizeof(prompt)-1] = '\0';
652 any_messages(NULL, (mn_get_total(msgmap) > 0L) ? "more" : NULL,
653 prompt[0] ? prompt : NULL);
655 if(!IS_NEWS(stream)
656 && ((now = time(0)) > state->last_nextitem_forcechk)){
657 *force_mailchk = 1;
658 /* check at most once a second */
659 state->last_nextitem_forcechk = now;
663 break;
666 /*---------- Delete message ----------*/
667 case MC_DELETE :
668 (void) cmd_delete(state, msgmap, MCMD_NONE,
669 (in_index == View) ? cmd_delete_view : cmd_delete_index);
670 break;
673 /*---------- Undelete message ----------*/
674 case MC_UNDELETE :
675 (void) cmd_undelete(state, msgmap, MCMD_NONE);
676 update_titlebar_status();
677 break;
680 /*---------- Reply to message ----------*/
681 case MC_REPLY :
682 (void) cmd_reply(state, msgmap, MCMD_NONE);
683 break;
686 /*---------- Forward message ----------*/
687 case MC_FORWARD :
688 (void) cmd_forward(state, msgmap, MCMD_NONE);
689 break;
692 /*---------- Quit pine ------------*/
693 case MC_QUIT :
694 state->next_screen = quit_screen;
695 dprint((1,"MAIL_CMD: quit\n"));
696 break;
699 /*---------- Compose message ----------*/
700 case MC_COMPOSE :
701 state->prev_screen = (in_index == View) ? mail_view_screen
702 : mail_index_screen;
703 compose_screen(state);
704 state->mangled_screen = 1;
705 if (state->next_screen)
706 a_changed = TRUE;
707 break;
710 /*---------- Alt Compose message ----------*/
711 case MC_ROLE :
712 state->prev_screen = (in_index == View) ? mail_view_screen
713 : mail_index_screen;
714 role_compose(state);
715 if(state->next_screen)
716 a_changed = TRUE;
718 break;
721 /*--------- Folders menu ------------*/
722 case MC_FOLDERS :
723 state->start_in_context = 1;
725 /*--------- Top of Folders list menu ------------*/
726 case MC_COLLECTIONS :
727 state->next_screen = folder_screen;
728 dprint((2,"MAIL_CMD: going to folder/collection menu\n"));
729 break;
732 /*---------- Open specific new folder ----------*/
733 case MC_GOTO :
734 tc = (state->context_last && !NEWS_TEST(state->context_current))
735 ? state->context_last : state->context_current;
737 newfolder = broach_folder(question_line, 1, &notrealinbox, &tc);
738 if(newfolder){
739 visit_folder(state, newfolder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT);
740 a_changed = TRUE;
743 break;
746 /*------- Go to Index Screen ----------*/
747 case MC_INDEX :
748 state->next_screen = mail_index_screen;
749 break;
751 /*------- Skip to next interesting message -----------*/
752 case MC_TAB :
753 if(THRD_INDX()){
754 PINETHRD_S *thrd;
757 * If we're in the thread index, start looking after this
758 * thread. We don't want to match something in the current
759 * thread.
761 start = mn_get_cur(msgmap);
762 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
763 if(mn_get_revsort(msgmap)){
764 /* if reversed, top of thread is last one before next thread */
765 if(thrd && thrd->top)
766 start = mn_raw2m(msgmap, thrd->top);
768 else{
769 /* last msg of thread is at the ends of the branches/nexts */
770 while(thrd){
771 start = mn_raw2m(msgmap, thrd->rawno);
772 if(thrd->branch)
773 thrd = fetch_thread(stream, thrd->branch);
774 else if(thrd->next)
775 thrd = fetch_thread(stream, thrd->next);
776 else
777 thrd = NULL;
782 * Flags is 0 in this case because we want to not skip
783 * messages inside of threads so that we can find threads
784 * which have some unseen messages even though the top-level
785 * of the thread is already seen.
786 * If new_msgno ends up being a message which is not visible
787 * because it isn't at the top-level, the current message #
788 * will be adjusted below in adjust_cur.
790 flags = 0;
791 new_msgno = next_sorted_flagged((F_UNDEL
792 | F_UNSEEN
793 | ((F_ON(F_TAB_TO_NEW,state))
794 ? 0 : F_OR_FLAG)),
795 stream, start, &flags);
797 else if(THREADING() && sp_viewing_a_thread(stream)){
798 PINETHRD_S *thrd, *topthrd = NULL;
800 start = mn_get_cur(msgmap);
803 * Things are especially complicated when we're viewing_a_thread
804 * from the thread index. First we have to check within the
805 * current thread for a new message. If none is found, then
806 * we search in the next threads and offer to continue in
807 * them. Then we offer to go to the next folder.
809 flags = NSF_SKIP_CHID;
810 new_msgno = next_sorted_flagged((F_UNDEL
811 | F_UNSEEN
812 | ((F_ON(F_TAB_TO_NEW,state))
813 ? 0 : F_OR_FLAG)),
814 stream, start, &flags);
816 * If we found a match then we are done, that is another message
817 * in the current thread index. Otherwise, we have to look
818 * further.
820 if(!(flags & NSF_FLAG_MATCH)){
821 ret = 'n';
822 while(1){
824 flags = 0;
825 new_msgno = next_sorted_flagged((F_UNDEL
826 | F_UNSEEN
827 | ((F_ON(F_TAB_TO_NEW,
828 state))
829 ? 0 : F_OR_FLAG)),
830 stream, start, &flags);
832 * If we got a match, new_msgno is a message in
833 * a different thread from the one we are viewing.
835 if(flags & NSF_FLAG_MATCH){
836 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
837 if(thrd && thrd->top)
838 topthrd = fetch_thread(stream, thrd->top);
840 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
841 static ESCKEY_S next_opt[] = {
842 {'y', 'y', "Y", N_("Yes")},
843 {'n', 'n', "N", N_("No")},
844 {TAB, 'n', "Tab", N_("NextNew")},
845 {-1, 0, NULL, NULL}
848 if(in_index)
849 snprintf(prompt, sizeof(prompt), _("View thread number %s? "),
850 topthrd ? comatose(topthrd->thrdno) : "?");
851 else
852 snprintf(prompt, sizeof(prompt),
853 _("View message in thread number %s? "),
854 topthrd ? comatose(topthrd->thrdno) : "?");
856 prompt[sizeof(prompt)-1] = '\0';
858 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
859 next_opt, 'y', 'x', NO_HELP,
860 RB_NORM);
861 if(ret == 'x'){
862 cmd_cancelled(NULL);
863 goto get_out;
866 else
867 ret = 'y';
869 if(ret == 'y'){
870 if(unview_thread(state, stream, msgmap)){
871 state->next_screen = mail_index_screen;
872 state->view_skipped_index = 0;
873 state->mangled_screen = 1;
876 mn_set_cur(msgmap, new_msgno);
877 if(THRD_AUTO_VIEW()){
879 if(count_lflags_in_thread(stream, topthrd,
880 msgmap, MN_NONE) == 1){
881 if(view_thread(state, stream, msgmap, 1)){
882 if(current_index_state)
883 msgmap->top_after_thrd = current_index_state->msg_at_top;
885 state->view_skipped_index = 1;
886 command = MC_VIEW_TEXT;
887 goto view_text;
892 if(view_thread(state, stream, msgmap, 1) && current_index_state)
893 msgmap->top_after_thrd = current_index_state->msg_at_top;
895 state->next_screen = SCREEN_FUN_NULL;
896 break;
898 else if(ret == 'n' && topthrd){
900 * skip to end of this thread and look starting
901 * in the next thread.
903 if(mn_get_revsort(msgmap)){
905 * if reversed, top of thread is last one
906 * before next thread
908 start = mn_raw2m(msgmap, topthrd->rawno);
910 else{
912 * last msg of thread is at the ends of
913 * the branches/nexts
915 thrd = topthrd;
916 while(thrd){
917 start = mn_raw2m(msgmap, thrd->rawno);
918 if(thrd->branch)
919 thrd = fetch_thread(stream, thrd->branch);
920 else if(thrd->next)
921 thrd = fetch_thread(stream, thrd->next);
922 else
923 thrd = NULL;
927 else if(ret == 'n')
928 break;
930 else
931 break;
935 else{
937 start = mn_get_cur(msgmap);
940 * If we are on a collapsed thread, start looking after the
941 * collapsed part, unless we are viewing the message.
943 if(THREADING() && in_index != View){
944 PINETHRD_S *thrd;
945 long rawno;
946 int collapsed;
948 rawno = mn_m2raw(msgmap, start);
949 thrd = fetch_thread(stream, rawno);
950 collapsed = thrd && thrd->next
951 && get_lflag(stream, NULL, rawno, MN_COLL);
953 if(collapsed){
954 if(mn_get_revsort(msgmap)){
955 if(thrd && thrd->top)
956 start = mn_raw2m(msgmap, thrd->top);
958 else{
959 while(thrd){
960 start = mn_raw2m(msgmap, thrd->rawno);
961 if(thrd->branch)
962 thrd = fetch_thread(stream, thrd->branch);
963 else if(thrd->next)
964 thrd = fetch_thread(stream, thrd->next);
965 else
966 thrd = NULL;
973 new_msgno = next_sorted_flagged((F_UNDEL
974 | F_UNSEEN
975 | ((F_ON(F_TAB_TO_NEW,state))
976 ? 0 : F_OR_FLAG)),
977 stream, start, &flags);
981 * If there weren't any unread messages left, OR there
982 * aren't any messages at all, we may want to offer to
983 * go on to the next folder...
985 if(flags & NSF_FLAG_MATCH){
986 mn_set_cur(msgmap, new_msgno);
987 if(in_index != View)
988 adjust_cur_to_visible(stream, msgmap);
990 else{
991 int in_inbox = sp_flagged(stream, SP_INBOX);
993 if(state->context_current
994 && ((NEWS_TEST(state->context_current)
995 && context_isambig(state->cur_folder))
996 || ((state->context_current->use & CNTXT_INCMNG)
997 && (in_inbox
998 || folder_index(state->cur_folder,
999 state->context_current,
1000 FI_FOLDER) >= 0)))){
1001 char nextfolder[MAXPATH];
1002 MAILSTREAM *nextstream = NULL;
1003 long recent_cnt;
1004 int did_cancel = 0;
1006 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
1007 nextfolder[sizeof(nextfolder)-1] = '\0';
1008 while(1){
1009 if(!(next_folder(&nextstream, nextfolder, sizeof(nextfolder), nextfolder,
1010 state->context_current, &recent_cnt,
1011 F_ON(F_TAB_NO_CONFIRM,state)
1012 ? NULL : &did_cancel))){
1013 if(!in_inbox){
1014 static ESCKEY_S inbox_opt[] = {
1015 {'y', 'y', "Y", N_("Yes")},
1016 {'n', 'n', "N", N_("No")},
1017 {TAB, 'z', "Tab", N_("To Inbox")},
1018 {-1, 0, NULL, NULL}
1021 if(F_ON(F_RET_INBOX_NO_CONFIRM,state))
1022 ret = 'y';
1023 else{
1024 /* TRANSLATORS: this is a question, with some information followed
1025 by Return to INBOX? */
1026 if(state->context_current->use&CNTXT_INCMNG)
1027 snprintf(prompt, sizeof(prompt), _("No more incoming folders. Return to \"%s\"? "), state->inbox_name);
1028 else
1029 snprintf(prompt, sizeof(prompt), _("No more news groups. Return to \"%s\"? "), state->inbox_name);
1031 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1032 inbox_opt, 'y', 'x',
1033 NO_HELP, RB_NORM);
1037 * 'z' is a synonym for 'y'. It is not 'y'
1038 * so that it isn't displayed as a default
1039 * action with square-brackets around it
1040 * in the keymenu...
1042 if(ret == 'y' || ret == 'z'){
1043 visit_folder(state, state->inbox_name,
1044 state->context_current,
1045 NULL, DB_INBOXWOCNTXT);
1046 a_changed = TRUE;
1049 else if (did_cancel)
1050 cmd_cancelled(NULL);
1051 else{
1052 if(state->context_current->use&CNTXT_INCMNG)
1053 q_status_message(SM_ORDER, 0, 2, _("No more incoming folders"));
1054 else
1055 q_status_message(SM_ORDER, 0, 2, _("No more news groups"));
1058 break;
1061 {char *front, type[80], cnt[80], fbuf[MAX_SCREEN_COLS/2+1];
1062 int rbspace, avail, need, take_back;
1065 * View_next_
1066 * Incoming_folder_ or news_group_ or folder_ or group_
1067 * "foldername"
1068 * _(13 recent) or _(some recent) or nothing
1069 * ?_
1071 front = "View next";
1072 strncpy(type,
1073 (state->context_current->use & CNTXT_INCMNG)
1074 ? "Incoming folder" : "news group",
1075 sizeof(type));
1076 type[sizeof(type)-1] = '\0';
1077 snprintf(cnt, sizeof(cnt), " (%.*s %s)", sizeof(cnt)-20,
1078 recent_cnt ? long2string(recent_cnt) : "some",
1079 F_ON(F_TAB_USES_UNSEEN, ps_global)
1080 ? "unseen" : "recent");
1081 cnt[sizeof(cnt)-1] = '\0';
1084 * Space reserved for radio_buttons call.
1085 * If we make this 3 then radio_buttons won't mess
1086 * with the prompt. If we make it 2, then we get
1087 * one more character to use but radio_buttons will
1088 * cut off the last character of our prompt, which is
1089 * ok because it is a space.
1091 rbspace = 2;
1092 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols
1093 : 80;
1094 need = strlen(front)+1 + strlen(type)+1 +
1095 + strlen(nextfolder)+2 + strlen(cnt) +
1096 2 + rbspace;
1097 if(avail < need){
1098 take_back = strlen(type);
1099 strncpy(type,
1100 (state->context_current->use & CNTXT_INCMNG)
1101 ? "folder" : "group", sizeof(type));
1102 take_back -= strlen(type);
1103 need -= take_back;
1104 if(avail < need){
1105 need -= strlen(cnt);
1106 cnt[0] = '\0';
1110 snprintf(prompt, sizeof(prompt), "%.*s %.*s \"%.*s\"%.*s? ",
1111 sizeof(prompt)/8, front,
1112 sizeof(prompt)/8, type,
1113 sizeof(prompt)/2,
1114 short_str(nextfolder, fbuf, sizeof(fbuf),
1115 strlen(nextfolder) -
1116 ((need>avail) ? (need-avail) : 0),
1117 MidDots),
1118 sizeof(prompt)/8, cnt);
1119 prompt[sizeof(prompt)-1] = '\0';
1123 * When help gets added, this'll have to become
1124 * a loop like the rest...
1126 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
1127 static ESCKEY_S next_opt[] = {
1128 {'y', 'y', "Y", N_("Yes")},
1129 {'n', 'n', "N", N_("No")},
1130 {TAB, 'n', "Tab", N_("NextNew")},
1131 {-1, 0, NULL, NULL}
1134 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1135 next_opt, 'y', 'x', NO_HELP,
1136 RB_NORM);
1137 if(ret == 'x'){
1138 cmd_cancelled(NULL);
1139 break;
1142 else
1143 ret = 'y';
1145 if(ret == 'y'){
1146 if(nextstream && sp_dead_stream(nextstream))
1147 nextstream = NULL;
1149 visit_folder(state, nextfolder,
1150 state->context_current, nextstream,
1151 DB_FROMTAB);
1152 /* visit_folder takes care of nextstream */
1153 nextstream = NULL;
1154 a_changed = TRUE;
1155 break;
1159 if(nextstream)
1160 pine_mail_close(nextstream);
1162 else
1163 any_messages(NULL,
1164 (mn_get_total(msgmap) > 0L)
1165 ? IS_NEWS(stream) ? "more undeleted" : "more new"
1166 : NULL,
1167 NULL);
1170 get_out:
1172 break;
1175 /*------- Zoom -----------*/
1176 case MC_ZOOM :
1178 * Right now the way zoom is implemented is sort of silly.
1179 * There are two per-message flags where just one and a
1180 * global "zoom mode" flag to suppress messags from the index
1181 * should suffice.
1183 if(any_messages(msgmap, NULL, "to Zoom on")){
1184 if(unzoom_index(state, stream, msgmap)){
1185 dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
1186 q_status_message(SM_ORDER,0,2, _("Index Zoom Mode is now off"));
1188 else if((i = zoom_index(state, stream, msgmap, MN_SLCT)) != 0){
1189 if(any_lflagged(msgmap, MN_HIDE)){
1190 dprint((4,"\n\n ---- Entering ZOOM mode ----\n"));
1191 q_status_message4(SM_ORDER, 0, 2,
1192 _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"),
1193 THRD_INDX() ? "" : comatose(i),
1194 THRD_INDX() ? "" : " ",
1195 THRD_INDX() ? _("threads") : _("message"),
1196 THRD_INDX() ? "" : plural(i));
1198 else
1199 q_status_message(SM_ORDER, 0, 2,
1200 _("All messages selected, so not entering Index Zoom Mode"));
1202 else
1203 any_messages(NULL, "selected", "to Zoom on");
1206 break;
1209 /*---------- print message on paper ----------*/
1210 case MC_PRINTMSG :
1211 if(any_messages(msgmap, NULL, "to print"))
1212 (void) cmd_print(state, msgmap, MCMD_NONE, in_index);
1214 break;
1217 /*---------- Take Address ----------*/
1218 case MC_TAKE :
1219 if(F_ON(F_ENABLE_ROLE_TAKE, state) ||
1220 any_messages(msgmap, NULL, "to Take address from"))
1221 (void) cmd_take_addr(state, msgmap, MCMD_NONE);
1223 break;
1226 /*---------- Save Message ----------*/
1227 case MC_SAVE :
1228 if(any_messages(msgmap, NULL, "to Save"))
1229 (void) cmd_save(state, stream, msgmap, MCMD_NONE, in_index);
1231 break;
1234 /*---------- Export message ----------*/
1235 case MC_EXPORT :
1236 if(any_messages(msgmap, NULL, "to Export")){
1237 (void) cmd_export(state, msgmap, question_line, MCMD_NONE);
1238 state->mangled_footer = 1;
1241 break;
1244 /*---------- Expunge ----------*/
1245 case MC_EXPUNGE :
1246 (void) cmd_expunge(state, stream, msgmap, MCMD_NONE);
1247 break;
1250 /*------- Unexclude -----------*/
1251 case MC_UNEXCLUDE :
1252 if(!(IS_NEWS(stream) && stream->rdonly)){
1253 q_status_message(SM_ORDER, 0, 3,
1254 _("Unexclude not available for mail folders"));
1256 else if(any_lflagged(msgmap, MN_EXLD)){
1257 SEARCHPGM *pgm;
1258 long i;
1259 int exbits;
1262 * Since excluded means "hidden deleted" and "killed",
1263 * the count should reflect the former.
1265 pgm = mail_newsearchpgm();
1266 pgm->deleted = 1;
1267 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
1268 for(i = 1L, del_count = 0L; i <= stream->nmsgs; i++)
1269 if((mc = mail_elt(stream, i)) && mc->searched
1270 && get_lflag(stream, NULL, i, MN_EXLD)
1271 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
1272 && (exbits & MSG_EX_FILTERED)))
1273 del_count++;
1275 if(del_count > 0L){
1276 state->mangled_footer = 1;
1277 snprintf(prompt, sizeof(prompt), "UNexclude %ld message%s in %.*s", del_count,
1278 plural(del_count), sizeof(prompt)-40,
1279 pretty_fn(state->cur_folder));
1280 prompt[sizeof(prompt)-1] = '\0';
1281 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
1282 || (F_ON(F_AUTO_EXPUNGE, state)
1283 && (state->context_current
1284 && (state->context_current->use & CNTXT_INCMNG))
1285 && context_isambig(state->cur_folder))
1286 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
1287 long save_cur_rawno;
1288 int were_viewing_a_thread;
1290 save_cur_rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
1291 were_viewing_a_thread = (THREADING()
1292 && sp_viewing_a_thread(stream));
1294 if(msgno_include(stream, msgmap, MI_NONE)){
1295 clear_index_cache(stream, 0);
1297 if(stream && stream->spare)
1298 erase_threading_info(stream, msgmap);
1300 refresh_sort(stream, msgmap, SRT_NON);
1303 if(were_viewing_a_thread){
1304 if(save_cur_rawno > 0L)
1305 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1307 if(view_thread(state, stream, msgmap, 1) && current_index_state)
1308 msgmap->top_after_thrd = current_index_state->msg_at_top;
1311 if(save_cur_rawno > 0L)
1312 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1314 state->mangled_screen = 1;
1315 q_status_message2(SM_ORDER, 0, 4,
1316 "%s message%s UNexcluded",
1317 long2string(del_count),
1318 plural(del_count));
1320 if(in_index != View)
1321 adjust_cur_to_visible(stream, msgmap);
1323 else
1324 any_messages(NULL, NULL, "UNexcluded");
1326 else
1327 any_messages(NULL, "excluded", "to UNexclude");
1329 else
1330 any_messages(NULL, "excluded", "to UNexclude");
1332 break;
1335 /*------- Make Selection -----------*/
1336 case MC_SELECT :
1337 if(any_messages(msgmap, NULL, "to Select")){
1338 if(aggregate_select(state, msgmap, question_line, in_index) == 0
1339 && (in_index == MsgIndx || in_index == ThrdIndx)
1340 && F_ON(F_AUTO_ZOOM, state)
1341 && any_lflagged(msgmap, MN_SLCT) > 0L
1342 && !any_lflagged(msgmap, MN_HIDE))
1343 (void) zoom_index(state, stream, msgmap, MN_SLCT);
1346 break;
1349 /*------- Toggle Current Message Selection State -----------*/
1350 case MC_SELCUR :
1351 if(any_messages(msgmap, NULL, NULL)){
1352 if((select_by_current(state, msgmap, in_index)
1353 || (F_OFF(F_UNSELECT_WONT_ADVANCE, state)
1354 && !any_lflagged(msgmap, MN_HIDE)))
1355 && (i = mn_get_cur(msgmap)) < mn_get_total(msgmap)){
1356 /* advance current */
1357 mn_inc_cur(stream, msgmap,
1358 (in_index == View && THREADING()
1359 && sp_viewing_a_thread(stream))
1360 ? MH_THISTHD
1361 : (in_index == View)
1362 ? MH_ANYTHD : MH_NONE);
1366 break;
1369 /*------- Apply command -----------*/
1370 case MC_APPLY :
1371 if(any_messages(msgmap, NULL, NULL)){
1372 if(any_lflagged(msgmap, MN_SLCT) > 0L){
1373 if(apply_command(state, stream, msgmap, 0,
1374 AC_NONE, question_line)){
1375 if(F_ON(F_AUTO_UNSELECT, state)){
1376 agg_select_all(stream, msgmap, NULL, 0);
1377 unzoom_index(state, stream, msgmap);
1379 else if(F_ON(F_AUTO_UNZOOM, state))
1380 unzoom_index(state, stream, msgmap);
1383 else
1384 any_messages(NULL, NULL, "to Apply command to. Try \"Select\"");
1387 break;
1390 /*-------- Sort command -------*/
1391 case MC_SORT :
1393 int were_threading = THREADING();
1394 SortOrder sort = mn_get_sort(msgmap);
1395 int rev = mn_get_revsort(msgmap);
1397 dprint((1,"MAIL_CMD: sort\n"));
1398 if(select_sort(state, question_line, &sort, &rev)){
1399 /* $ command reinitializes threading collapsed/expanded info */
1400 if(SORT_IS_THREADED(msgmap) && !SEP_THRDINDX())
1401 erase_threading_info(stream, msgmap);
1403 if(ps_global && ps_global->ttyo){
1404 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
1405 ps_global->mangled_footer = 1;
1408 sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN);
1411 state->mangled_footer = 1;
1414 * We've changed whether we are threading or not so we need to
1415 * exit the index and come back in so that we switch between the
1416 * thread index and the regular index. Sort_folder will have
1417 * reset viewing_a_thread if necessary.
1419 if(SEP_THRDINDX()
1420 && ((!were_threading && THREADING())
1421 || (were_threading && !THREADING()))){
1422 state->next_screen = mail_index_screen;
1423 state->mangled_screen = 1;
1427 break;
1430 /*------- Toggle Full Headers -----------*/
1431 case MC_FULLHDR :
1432 state->full_header++;
1433 if(state->full_header == 1){
1434 if(!(state->quote_suppression_threshold
1435 && (state->some_quoting_was_suppressed || in_index != View)))
1436 state->full_header++;
1438 else if(state->full_header > 2)
1439 state->full_header = 0;
1441 switch(state->full_header){
1442 case 0:
1443 q_status_message(SM_ORDER, 0, 3,
1444 _("Display of full headers is now off."));
1445 break;
1447 case 1:
1448 q_status_message1(SM_ORDER, 0, 3,
1449 _("Quotes displayed, use %s to see full headers"),
1450 F_ON(F_USE_FK, state) ? "F9" : "H");
1451 break;
1453 case 2:
1454 q_status_message(SM_ORDER, 0, 3,
1455 _("Display of full headers is now on."));
1456 break;
1460 a_changed = TRUE;
1461 break;
1464 case MC_TOGGLE :
1465 a_changed = TRUE;
1466 break;
1469 #ifdef SMIME
1470 /*------- Try to decrypt message -----------*/
1471 case MC_DECRYPT:
1472 if(state->smime && state->smime->need_passphrase)
1473 smime_get_passphrase();
1475 a_changed = TRUE;
1476 break;
1478 case MC_SECURITY:
1479 state->next_screen = smime_info_screen;
1480 break;
1481 #endif
1484 /*------- Bounce -----------*/
1485 case MC_BOUNCE :
1486 (void) cmd_bounce(state, msgmap, MCMD_NONE);
1487 break;
1490 /*------- Flag -----------*/
1491 case MC_FLAG :
1492 dprint((4, "\n - flag message -\n"));
1493 (void) cmd_flag(state, msgmap, MCMD_NONE);
1494 break;
1497 /*------- Pipe message -----------*/
1498 case MC_PIPE :
1499 (void) cmd_pipe(state, msgmap, MCMD_NONE);
1500 break;
1503 /*--------- Default, unknown command ----------*/
1504 default:
1505 alpine_panic("Unexpected command case");
1506 break;
1509 return((a_changed || mn_get_cur(msgmap) != old_msgno) ? 1 : 0);
1514 /*----------------------------------------------------------------------
1515 Map some of the special characters into sensible strings for human
1516 consumption.
1517 c is a UCS-4 character!
1518 ----*/
1519 char *
1520 pretty_command(UCS c)
1522 static char buf[10];
1523 char *s;
1525 buf[0] = '\0';
1526 s = buf;
1528 switch(c){
1529 case ' ' : s = "SPACE"; break;
1530 case '\033' : s = "ESC"; break;
1531 case '\177' : s = "DEL"; break;
1532 case ctrl('I') : s = "TAB"; break;
1533 case ctrl('J') : s = "LINEFEED"; break;
1534 case ctrl('M') : s = "RETURN"; break;
1535 case ctrl('Q') : s = "XON"; break;
1536 case ctrl('S') : s = "XOFF"; break;
1537 case KEY_UP : s = "Up Arrow"; break;
1538 case KEY_DOWN : s = "Down Arrow"; break;
1539 case KEY_RIGHT : s = "Right Arrow"; break;
1540 case KEY_LEFT : s = "Left Arrow"; break;
1541 case KEY_PGUP : s = "Prev Page"; break;
1542 case KEY_PGDN : s = "Next Page"; break;
1543 case KEY_HOME : s = "Home"; break;
1544 case KEY_END : s = "End"; break;
1545 case KEY_DEL : s = "Delete"; break; /* Not necessary DEL! */
1546 case KEY_JUNK : s = "Junk!"; break;
1547 case BADESC : s = "Bad Esc"; break;
1548 case NO_OP_IDLE : s = "NO_OP_IDLE"; break;
1549 case NO_OP_COMMAND : s = "NO_OP_COMMAND"; break;
1550 case KEY_RESIZE : s = "KEY_RESIZE"; break;
1551 case KEY_UTF8 : s = "KEY_UTF8"; break;
1552 case KEY_MOUSE : s = "KEY_MOUSE"; break;
1553 case KEY_SCRLUPL : s = "KEY_SCRLUPL"; break;
1554 case KEY_SCRLDNL : s = "KEY_SCRLDNL"; break;
1555 case KEY_SCRLTO : s = "KEY_SCRLTO"; break;
1556 case KEY_XTERM_MOUSE : s = "KEY_XTERM_MOUSE"; break;
1557 case KEY_DOUBLE_ESC : s = "KEY_DOUBLE_ESC"; break;
1558 case CTRL_KEY_UP : s = "Ctrl Up Arrow"; break;
1559 case CTRL_KEY_DOWN : s = "Ctrl Down Arrow"; break;
1560 case CTRL_KEY_RIGHT : s = "Ctrl Right Arrow"; break;
1561 case CTRL_KEY_LEFT : s = "Ctrl Left Arrow"; break;
1562 case PF1 :
1563 case PF2 :
1564 case PF3 :
1565 case PF4 :
1566 case PF5 :
1567 case PF6 :
1568 case PF7 :
1569 case PF8 :
1570 case PF9 :
1571 case PF10 :
1572 case PF11 :
1573 case PF12 :
1574 snprintf(s = buf, sizeof(buf), "F%ld", (long) (c - PF1 + 1));
1575 break;
1577 default:
1578 if(c < ' ' || (c >= 0x80 && c < 0xA0)){
1579 char d;
1580 int c1;
1582 c1 = (c >= 0x80);
1583 d = (c & 0x1f) + 'A' - 1;
1584 snprintf(s = buf, sizeof(buf), "%c%c", c1 ? '~' : '^', d);
1586 else{
1587 memset(buf, 0, sizeof(buf));
1588 utf8_put((unsigned char *) buf, (unsigned long) c);
1591 break;
1594 return(s);
1598 /*----------------------------------------------------------------------
1599 Complain about bogus input
1601 Args: ch -- input command to complain about
1602 help -- string indicating where to get help
1604 ----*/
1605 void
1606 bogus_command(UCS cmd, char *help)
1608 if(cmd == ctrl('Q') || cmd == ctrl('S'))
1609 q_status_message1(SM_ASYNC, 0, 2,
1610 "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.",
1611 pretty_command(cmd));
1612 else if(cmd == KEY_JUNK)
1613 q_status_message3(SM_ORDER, 0, 2,
1614 "Invalid key pressed.%s%s%s",
1615 (help) ? " Use " : "",
1616 (help) ? help : "",
1617 (help) ? " for help" : "");
1618 else
1619 q_status_message4(SM_ORDER, 0, 2,
1620 "Command \"%s\" not defined for this screen.%s%s%s",
1621 pretty_command(cmd),
1622 (help) ? " Use " : "",
1623 (help) ? help : "",
1624 (help) ? " for help" : "");
1628 void
1629 bogus_utf8_command(char *cmd, char *help)
1631 q_status_message4(SM_ORDER, 0, 2,
1632 "Command \"%s\" not defined for this screen.%s%s%s",
1633 cmd ? cmd : "?",
1634 (help) ? " Use " : "",
1635 (help) ? help : "",
1636 (help) ? " for help" : "");
1640 /*----------------------------------------------------------------------
1641 Execute FLAG message command
1643 Args: state -- Various satate info
1644 msgmap -- map of c-client to local message numbers
1646 Result: with side effect of "current" message FLAG flag set or UNset
1648 ----*/
1650 cmd_flag(struct pine *state, MSGNO_S *msgmap, int aopt)
1652 char *flagit, *seq, *screen_text[20], **exp, **p, *answer = NULL;
1653 char *keyword_array[2];
1654 int user_defined_flags = 0, mailbox_flags = 0;
1655 int directly_to_maint_screen = 0;
1656 long unflagged, flagged, flags, rawno;
1657 MESSAGECACHE *mc = NULL;
1658 KEYWORD_S *kw;
1659 int i, cnt, is_set, trouble = 0, rv = 0;
1660 size_t len;
1661 struct flag_table *fp, *ftbl = NULL;
1662 struct flag_screen flag_screen;
1663 static char *flag_screen_text1[] = {
1664 N_(" Set desired flags for current message below. An 'X' means set"),
1665 N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."),
1666 NULL
1669 static char *flag_screen_text2[] = {
1670 N_(" Set desired flags below for selected messages. A '?' means to"),
1671 N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"),
1672 N_(" to unset it. Use the \"Return\" key to toggle, and choose"),
1673 N_(" \"Exit\" when finished."),
1674 NULL
1677 static struct flag_table default_ftbl[] = {
1678 {N_("Important"), h_flag_important, F_FLAG, 0, 0, NULL, NULL},
1679 {N_("New"), h_flag_new, F_SEEN, 0, 0, NULL, NULL},
1680 {N_("Answered"), h_flag_answered, F_ANS, 0, 0, NULL, NULL},
1681 {N_("Forwarded"), h_flag_forwarded, F_FWD, 0, 0, NULL, NULL},
1682 {N_("Deleted"), h_flag_deleted, F_DEL, 0, 0, NULL, NULL},
1683 {NULL, NO_HELP, 0, 0, 0, NULL, NULL}
1686 /* Only check for dead stream for now. Should check permanent flags
1687 * eventually
1689 if(!(any_messages(msgmap, NULL, "to Flag") && can_set_flag(state, "flag", 1)))
1690 return rv;
1692 if(sp_io_error_on_stream(state->mail_stream)){
1693 sp_set_io_error_on_stream(state->mail_stream, 0);
1694 pine_mail_check(state->mail_stream); /* forces write */
1695 return rv;
1698 go_again:
1699 answer = NULL;
1700 user_defined_flags = 0;
1701 mailbox_flags = 0;
1702 mc = NULL;
1703 trouble = 0;
1704 ftbl = NULL;
1706 /* count how large ftbl will be */
1707 for(cnt = 0; default_ftbl[cnt].name; cnt++)
1710 /* add user flags */
1711 for(kw = ps_global->keywords; kw; kw = kw->next){
1712 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick)) || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1713 user_defined_flags++;
1714 cnt++;
1719 * Add mailbox flags that aren't user-defined flags.
1720 * Don't consider it if it matches either one of our defined
1721 * keywords or one of our defined nicknames for a keyword.
1723 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1724 char *q;
1726 q = stream_to_user_flag_name(state->mail_stream, i);
1727 if(q && q[0]){
1728 for(kw = ps_global->keywords; kw; kw = kw->next){
1729 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1730 break;
1734 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1735 mailbox_flags++;
1736 cnt++;
1740 cnt += (user_defined_flags ? 2 : 0) + (mailbox_flags ? 2 : 0);
1742 /* set up ftbl, first the system flags */
1743 ftbl = (struct flag_table *) fs_get((cnt+1) * sizeof(*ftbl));
1744 memset(ftbl, 0, (cnt+1) * sizeof(*ftbl));
1745 for(i = 0, fp = ftbl; default_ftbl[i].name; i++, fp++){
1746 fp->name = cpystr(default_ftbl[i].name);
1747 fp->help = default_ftbl[i].help;
1748 fp->flag = default_ftbl[i].flag;
1749 fp->set = default_ftbl[i].set;
1750 fp->ukn = default_ftbl[i].ukn;
1753 if(user_defined_flags){
1754 fp->flag = F_COMMENT;
1755 fp->name = cpystr("");
1756 fp++;
1757 fp->flag = F_COMMENT;
1758 len = strlen(_("User-defined Keywords from Setup/Config"));
1759 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1760 snprintf(fp->name, len+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config"));
1761 fp++;
1764 /* then the user-defined keywords */
1765 if(user_defined_flags)
1766 for(kw = ps_global->keywords; kw; kw = kw->next){
1767 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick))
1768 || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1769 fp->name = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
1770 fp->keyword = cpystr(kw->kw ? kw->kw : "");
1771 if(kw->nick && kw->kw){
1772 size_t l;
1774 l = strlen(kw->kw)+2;
1775 fp->comment = (char *) fs_get((l+1) * sizeof(char));
1776 snprintf(fp->comment, l+1, "(%.*s)", strlen(kw->kw), kw->kw);
1777 fp->comment[l] = '\0';
1780 fp->help = h_flag_user_flag;
1781 fp->flag = F_KEYWORD;
1782 fp->set = 0;
1783 fp->ukn = 0;
1784 fp++;
1788 if(mailbox_flags){
1789 fp->flag = F_COMMENT;
1790 fp->name = cpystr("");
1791 fp++;
1792 fp->flag = F_COMMENT;
1793 len = strlen(_("Other keywords in the mailbox that are not user-defined"));
1794 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1795 snprintf(fp->name, len+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined"));
1796 fp++;
1799 /* then the extra mailbox-defined keywords */
1800 if(mailbox_flags)
1801 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1802 char *q;
1804 q = stream_to_user_flag_name(state->mail_stream, i);
1805 if(q && q[0]){
1806 for(kw = ps_global->keywords; kw; kw = kw->next){
1807 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1808 break;
1812 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1813 fp->name = cpystr(q);
1814 fp->keyword = cpystr(q);
1815 fp->help = h_flag_user_flag;
1816 fp->flag = F_KEYWORD;
1817 fp->set = 0;
1818 fp->ukn = 0;
1819 fp++;
1823 flag_screen.flag_table = &ftbl;
1824 flag_screen.explanation = screen_text;
1826 if(MCMD_ISAGG(aopt)){
1827 if(!pseudo_selected(ps_global->mail_stream, msgmap)){
1828 free_flag_table(&ftbl);
1829 return rv;
1832 exp = flag_screen_text2;
1833 for(fp = ftbl; fp->name; fp++){
1834 fp->set = CMD_FLAG_UNKN; /* set to unknown */
1835 fp->ukn = TRUE;
1838 else if(state->mail_stream
1839 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
1840 && rawno <= state->mail_stream->nmsgs
1841 && (mc = mail_elt(state->mail_stream, rawno))){
1842 exp = flag_screen_text1;
1843 for(fp = &ftbl[0]; fp->name; fp++){
1844 fp->ukn = 0;
1845 if(fp->flag == F_KEYWORD){
1846 /* see if this keyword is defined for this message */
1847 fp->set = CMD_FLAG_CLEAR;
1848 if(user_flag_is_set(state->mail_stream,
1849 rawno, fp->keyword))
1850 fp->set = CMD_FLAG_SET;
1852 else if(fp->flag == F_FWD){
1853 /* see if forwarded keyword is defined for this message */
1854 fp->set = CMD_FLAG_CLEAR;
1855 if(user_flag_is_set(state->mail_stream,
1856 rawno, FORWARDED_FLAG))
1857 fp->set = CMD_FLAG_SET;
1859 else if(fp->flag != F_COMMENT)
1860 fp->set = ((fp->flag == F_SEEN && !mc->seen)
1861 || (fp->flag == F_DEL && mc->deleted)
1862 || (fp->flag == F_FLAG && mc->flagged)
1863 || (fp->flag == F_ANS && mc->answered))
1864 ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
1867 else{
1868 q_status_message(SM_ORDER | SM_DING, 3, 4,
1869 _("Error accessing message data"));
1870 free_flag_table(&ftbl);
1871 return rv;
1874 if(directly_to_maint_screen)
1875 goto the_maint_screen;
1877 #ifdef _WINDOWS
1878 if (mswin_usedialog ()) {
1879 if (!os_flagmsgdialog (&ftbl[0])){
1880 free_flag_table(&ftbl);
1881 return rv;
1884 else
1885 #endif
1887 int use_maint_screen;
1888 int keyword_shortcut = 0;
1890 use_maint_screen = F_ON(F_FLAG_SCREEN_DFLT, ps_global);
1892 if(!use_maint_screen){
1894 * We're going to call cmd_flag_prompt(). We need
1895 * to decide whether or not to offer the keyword setting
1896 * shortcut. We'll offer it if the user has the feature
1897 * enabled AND there are some possible keywords that could
1898 * be set.
1900 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global)){
1901 for(fp = &ftbl[0]; fp->name && !keyword_shortcut; fp++){
1902 if(fp->flag == F_KEYWORD){
1903 int first_char;
1904 ESCKEY_S *tp;
1906 first_char = (fp->name && fp->name[0])
1907 ? fp->name[0] : -2;
1908 if(isascii(first_char) && isupper(first_char))
1909 first_char = tolower((unsigned char) first_char);
1911 for(tp=flag_text_opt; tp->ch != -1; tp++)
1912 if(tp->ch == first_char)
1913 break;
1915 if(tp->ch == -1)
1916 keyword_shortcut++;
1921 use_maint_screen = !cmd_flag_prompt(state, &flag_screen,
1922 keyword_shortcut);
1925 the_maint_screen:
1926 if(use_maint_screen){
1927 for(p = &screen_text[0]; *exp; p++, exp++)
1928 *p = *exp;
1930 *p = NULL;
1932 directly_to_maint_screen = flag_maintenance_screen(state, &flag_screen);
1936 /* reaquire the elt pointer */
1937 mc = (state->mail_stream
1938 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
1939 && rawno <= state->mail_stream->nmsgs)
1940 ? mail_elt(state->mail_stream, rawno) : NULL;
1942 for(fp = ftbl; mc && fp->name; fp++){
1943 flags = -1;
1944 switch(fp->flag){
1945 case F_SEEN:
1946 if((!MCMD_ISAGG(aopt) && fp->set != !mc->seen)
1947 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
1948 flagit = "\\SEEN";
1949 if(fp->set){
1950 flags = 0L;
1951 unflagged = F_SEEN;
1953 else{
1954 flags = ST_SET;
1955 unflagged = F_UNSEEN;
1959 break;
1961 case F_ANS:
1962 if((!MCMD_ISAGG(aopt) && fp->set != mc->answered)
1963 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
1964 flagit = "\\ANSWERED";
1965 if(fp->set){
1966 flags = ST_SET;
1967 unflagged = F_UNANS;
1969 else{
1970 flags = 0L;
1971 unflagged = F_ANS;
1975 break;
1977 case F_DEL:
1978 if((!MCMD_ISAGG(aopt) && fp->set != mc->deleted)
1979 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
1980 flagit = "\\DELETED";
1981 if(fp->set){
1982 flags = ST_SET;
1983 unflagged = F_UNDEL;
1985 else{
1986 flags = 0L;
1987 unflagged = F_DEL;
1991 break;
1993 case F_FLAG:
1994 if((!MCMD_ISAGG(aopt) && fp->set != mc->flagged)
1995 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
1996 flagit = "\\FLAGGED";
1997 if(fp->set){
1998 flags = ST_SET;
1999 unflagged = F_UNFLAG;
2001 else{
2002 flags = 0L;
2003 unflagged = F_FLAG;
2007 break;
2009 case F_FWD :
2010 if(!MCMD_ISAGG(aopt)){
2011 /* see if forwarded is defined for this message */
2012 is_set = CMD_FLAG_CLEAR;
2013 if(user_flag_is_set(state->mail_stream,
2014 mn_m2raw(msgmap, mn_get_cur(msgmap)),
2015 FORWARDED_FLAG))
2016 is_set = CMD_FLAG_SET;
2019 if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2020 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2021 flagit = FORWARDED_FLAG;
2022 if(fp->set){
2023 flags = ST_SET;
2024 unflagged = F_UNFWD;
2026 else{
2027 flags = 0L;
2028 unflagged = F_FWD;
2032 break;
2034 case F_KEYWORD:
2035 if(!MCMD_ISAGG(aopt)){
2036 /* see if this keyword is defined for this message */
2037 is_set = CMD_FLAG_CLEAR;
2038 if(user_flag_is_set(state->mail_stream,
2039 mn_m2raw(msgmap, mn_get_cur(msgmap)),
2040 fp->keyword))
2041 is_set = CMD_FLAG_SET;
2044 if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2045 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2046 flagit = fp->keyword;
2047 keyword_array[0] = fp->keyword;
2048 keyword_array[1] = NULL;
2049 if(fp->set){
2050 flags = ST_SET;
2051 unflagged = F_UNKEYWORD;
2053 else{
2054 flags = 0L;
2055 unflagged = F_KEYWORD;
2059 break;
2061 default:
2062 break;
2065 flagged = 0L;
2066 if(flags >= 0L
2067 && (seq = currentf_sequence(state->mail_stream, msgmap,
2068 unflagged, &flagged, unflagged & F_DEL,
2069 (fp->flag == F_KEYWORD
2070 && unflagged == F_KEYWORD)
2071 ? keyword_array : NULL,
2072 (fp->flag == F_KEYWORD
2073 && unflagged == F_UNKEYWORD)
2074 ? keyword_array : NULL))){
2076 * For user keywords, we may have to create the flag in
2077 * the folder if it doesn't already exist and we are setting
2078 * it (as opposed to clearing it). Mail_flag will
2079 * do that for us, but it's failure isn't very friendly
2080 * error-wise. So we try to make it a little smoother.
2082 if(!(fp->flag == F_KEYWORD || fp->flag == F_FWD) || !fp->set
2083 || ((i=user_flag_index(state->mail_stream, flagit)) >= 0
2084 && i < NUSERFLAGS))
2085 mail_flag(state->mail_stream, seq, flagit, flags);
2086 else{
2087 /* trouble, see if we can add the user flag */
2088 if(state->mail_stream->kwd_create)
2089 mail_flag(state->mail_stream, seq, flagit, flags);
2090 else{
2091 trouble++;
2093 if(some_user_flags_defined(state->mail_stream))
2094 q_status_message(SM_ORDER, 3, 4,
2095 _("No more keywords allowed in this folder!"));
2096 else if(fp->flag == F_FWD)
2097 q_status_message(SM_ORDER, 3, 4,
2098 _("Cannot add keywords for this folder so cannot set Forwarded flag"));
2099 else
2100 q_status_message(SM_ORDER, 3, 4,
2101 _("Cannot add keywords for this folder"));
2105 fs_give((void **) &seq);
2106 if(flagged && !trouble){
2107 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%slagged%s%s%s%s%s message%s%s \"%s\"",
2108 (fp->set) ? "F" : "Unf",
2109 MCMD_ISAGG(aopt) ? " " : "",
2110 MCMD_ISAGG(aopt) ? long2string(flagged) : "",
2111 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2112 ? " (of " : "",
2113 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2114 ? comatose(mn_total_cur(msgmap)) : "",
2115 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2116 ? ")" : "",
2117 MCMD_ISAGG(aopt) ? plural(flagged) : " ",
2118 MCMD_ISAGG(aopt) ? "" : long2string(mn_get_cur(msgmap)),
2119 fp->name);
2120 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2121 q_status_message(SM_ORDER, 0, 2, answer = tmp_20k_buf);
2122 rv++;
2127 free_flag_table(&ftbl);
2129 if(directly_to_maint_screen)
2130 goto go_again;
2132 if(MCMD_ISAGG(aopt))
2133 restore_selected(msgmap);
2135 if(!answer)
2136 q_status_message(SM_ORDER, 0, 2, _("No flags changed."));
2138 return rv;
2142 /*----------------------------------------------------------------------
2143 Offer concise status line flag prompt
2145 Args: state -- Various satate info
2146 flags -- flags to offer setting
2148 Result: TRUE if flag to set specified in flags struct or FALSE otw
2150 ----*/
2152 cmd_flag_prompt(struct pine *state, struct flag_screen *flags, int allow_keyword_shortcuts)
2154 int r, setflag = 1, first_char;
2155 struct flag_table *fp;
2156 ESCKEY_S *ek;
2157 char *ftext, *ftext_not;
2158 static char *flag_text =
2159 N_("Flag New, Deleted, Answered, Forwarded or Important ? ");
2160 static char *flag_text_ak =
2161 N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? ");
2162 static char *flag_text_not =
2163 N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? ");
2164 static char *flag_text_ak_not =
2165 N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? ");
2167 if(allow_keyword_shortcuts){
2168 int cnt = 0;
2169 ESCKEY_S *dp, *sp, *tp;
2171 for(sp=flag_text_opt; sp->ch != -1; sp++)
2172 cnt++;
2174 for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++)
2175 if(fp->flag == F_KEYWORD)
2176 cnt++;
2178 /* set up an ESCKEY_S list which includes invisible keys for keywords */
2179 ek = (ESCKEY_S *) fs_get((cnt + 1) * sizeof(*ek));
2180 memset(ek, 0, (cnt+1) * sizeof(*ek));
2181 for(dp=ek, sp=flag_text_opt; sp->ch != -1; sp++, dp++)
2182 *dp = *sp;
2184 for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2185 if(fp->flag == F_KEYWORD){
2186 first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2187 if(isascii(first_char) && isupper(first_char))
2188 first_char = tolower((unsigned char) first_char);
2191 * Check to see if an earlier keyword in the list, or one of
2192 * the builtin system letters already uses this character.
2193 * If so, the first one wins.
2195 for(tp=ek; tp->ch != 0; tp++)
2196 if(tp->ch == first_char)
2197 break;
2199 if(tp->ch != 0)
2200 continue; /* skip it, already used that char */
2202 dp->ch = first_char;
2203 dp->rval = first_char;
2204 dp->name = "";
2205 dp->label = "";
2206 dp++;
2210 dp->ch = -1;
2211 ftext = _(flag_text_ak);
2212 ftext_not = _(flag_text_ak_not);
2214 else{
2215 ek = flag_text_opt;
2216 ftext = _(flag_text);
2217 ftext_not = _(flag_text_not);
2220 while(1){
2221 r = radio_buttons(setflag ? ftext : ftext_not,
2222 -FOOTER_ROWS(state), ek, '*', SEQ_EXCEPTION-1,
2223 NO_HELP, RB_NORM | RB_SEQ_SENSITIVE);
2225 * It is SEQ_EXCEPTION-1 just so that it is some value that isn't
2226 * being used otherwise. The keywords use up all the possible
2227 * letters, so a negative number is good, but it has to be different
2228 * from other negative return values.
2230 if(r == SEQ_EXCEPTION-1) /* ol'cancelrooney */
2231 return(TRUE);
2232 else if(r == 10) /* return and goto flag screen */
2233 return(FALSE);
2234 else if(r == '!') /* flip intention */
2235 setflag = !setflag;
2236 else
2237 break;
2240 for(fp = (flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2241 if(r == 'n' || r == '*' || r == 'd' || r == 'a' || r == 'f'){
2242 if((r == 'n' && fp->flag == F_SEEN)
2243 || (r == '*' && fp->flag == F_FLAG)
2244 || (r == 'd' && fp->flag == F_DEL)
2245 || (r == 'f' && fp->flag == F_FWD)
2246 || (r == 'a' && fp->flag == F_ANS)){
2247 fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2248 break;
2251 else if(allow_keyword_shortcuts && fp->flag == F_KEYWORD){
2252 first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2253 if(isascii(first_char) && isupper(first_char))
2254 first_char = tolower((unsigned char) first_char);
2256 if(r == first_char){
2257 fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2258 break;
2263 if(ek != flag_text_opt)
2264 fs_give((void **) &ek);
2266 return(TRUE);
2271 * (*ft) is an array of flag_table entries.
2273 void
2274 free_flag_table(struct flag_table **ft)
2276 struct flag_table *fp;
2278 if(ft && *ft){
2279 for(fp = (*ft); fp->name; fp++){
2280 if(fp->name)
2281 fs_give((void **) &fp->name);
2283 if(fp->keyword)
2284 fs_give((void **) &fp->keyword);
2286 if(fp->comment)
2287 fs_give((void **) &fp->comment);
2290 fs_give((void **) ft);
2295 /*----------------------------------------------------------------------
2296 Execute REPLY message command
2298 Args: state -- Various satate info
2299 msgmap -- map of c-client to local message numbers
2301 Result: reply sent or not
2303 ----*/
2305 cmd_reply(struct pine *state, MSGNO_S *msgmap, int aopt)
2307 int rv = 0;
2309 if(any_messages(msgmap, NULL, "to Reply to")){
2310 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2311 return rv;
2313 rv = reply(state, NULL);
2315 if(MCMD_ISAGG(aopt))
2316 restore_selected(msgmap);
2318 state->mangled_screen = 1;
2321 return rv;
2325 /*----------------------------------------------------------------------
2326 Execute FORWARD message command
2328 Args: state -- Various satate info
2329 msgmap -- map of c-client to local message numbers
2331 Result: selected message[s] forwarded or not
2333 ----*/
2335 cmd_forward(struct pine *state, MSGNO_S *msgmap, int aopt)
2337 int rv = 0;
2339 if(any_messages(msgmap, NULL, "to Forward")){
2340 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2341 return rv;
2343 rv = forward(state, NULL);
2345 if(MCMD_ISAGG(aopt))
2346 restore_selected(msgmap);
2348 state->mangled_screen = 1;
2351 return rv;
2355 /*----------------------------------------------------------------------
2356 Execute BOUNCE message command
2358 Args: state -- Various satate info
2359 msgmap -- map of c-client to local message numbers
2360 aopt -- aggregate options
2362 Result: selected message[s] bounced or not
2364 ----*/
2366 cmd_bounce(struct pine *state, MSGNO_S *msgmap, int aopt)
2368 int rv = 0;
2369 ACTION_S *role = NULL;
2371 if(any_messages(msgmap, NULL, "to Bounce")){
2372 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2373 return rv;
2375 if(MCMD_ISAGG(aopt)){ /* check for possible role */
2376 PAT_STATE pstate;
2377 int action;
2379 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
2380 static ESCKEY_S yesno_opts[] = {
2381 {'y', 'y', "Y", N_("Yes")},
2382 {'n', 'n', "N", N_("No")},
2383 {-1, 0, NULL, NULL}
2386 action = radio_buttons(_("Bounce messages using a role? "),
2387 -FOOTER_ROWS(state), yesno_opts,
2388 'y', 'x', h_role_compose, RB_NORM);
2389 if(action == 'y'){
2390 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
2392 redraw = state->redrawer;
2393 state->redrawer = NULL;
2394 prev_screen = state->prev_screen;
2395 role = NULL;
2396 state->next_screen = SCREEN_FUN_NULL;
2398 if(role_select_screen(state, &role, MC_BOUNCE) < 0)
2399 cmd_cancelled(_("Bounce"));
2400 else{
2401 if(role)
2402 role = combine_inherited_role(role);
2403 else{
2404 role = (ACTION_S *) fs_get(sizeof(*role));
2405 memset((void *) role, 0, sizeof(*role));
2406 role->nick = cpystr("Default Role");
2410 if(redraw)
2411 (*redraw)();
2413 state->next_screen = prev_screen;
2414 state->redrawer = redraw;
2415 state->mangled_screen = 1;
2420 rv = bounce(state, role);
2422 if(role)
2423 free_action(&role);
2425 if(MCMD_ISAGG(aopt))
2426 restore_selected(msgmap);
2428 state->mangled_footer = 1;
2431 return rv;
2435 /*----------------------------------------------------------------------
2436 Execute save message command: prompt for folder and call function to save
2438 Args: screen_line -- Line on the screen to prompt on
2439 message -- The MESSAGECACHE entry of message to save
2441 Result: The folder lister can be called to make selection; mangled screen set
2443 This does the prompting for the folder name to save to, possibly calling
2444 up the folder display for selection of folder by user.
2445 ----*/
2447 cmd_save(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
2449 char newfolder[MAILTMPLEN], nmsgs[32], *nick;
2450 int we_cancel = 0, rv = 0, save_flags;
2451 long i, raw;
2452 CONTEXT_S *cntxt = NULL;
2453 ENVELOPE *e = NULL;
2454 SaveDel del = DontAsk;
2455 SavePreserveOrder pre = DontAskPreserve;
2457 dprint((4, "\n - saving message -\n"));
2459 if(MCMD_ISAGG(aopt) && !pseudo_selected(stream, msgmap))
2460 return rv;
2462 state->ugly_consider_advancing_bit = 0;
2463 if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM, state)
2464 && msgno_any_deletedparts(stream, msgmap)
2465 && want_to(_("Saved copy will NOT include entire message! Continue"),
2466 'y', 'n', NO_HELP, WT_FLUSH_IN | WT_SEQ_SENSITIVE) != 'y'){
2467 restore_selected(msgmap);
2468 cmd_cancelled("Save message");
2469 return rv;
2472 raw = mn_m2raw(msgmap, mn_get_cur(msgmap));
2474 if(mn_total_cur(msgmap) <= 1L){
2475 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld ", mn_get_cur(msgmap));
2476 nmsgs[sizeof(nmsgs)-1] = '\0';
2477 e = pine_mail_fetchstructure(stream, raw, NULL);
2478 if(!e) {
2479 q_status_message(SM_ORDER | SM_DING, 3, 4,
2480 _("Can't save message. Error accessing folder"));
2481 restore_selected(msgmap);
2482 return rv;
2485 else{
2486 snprintf(nmsgs, sizeof(nmsgs), "%s msgs ", comatose(mn_total_cur(msgmap)));
2487 nmsgs[sizeof(nmsgs)-1] = '\0';
2489 /* e is just used to get a default save folder from the first msg */
2490 e = pine_mail_fetchstructure(stream,
2491 mn_m2raw(msgmap, mn_first_cur(msgmap)),
2492 NULL);
2495 del = (!READONLY_FOLDER(stream) && F_OFF(F_SAVE_WONT_DELETE, ps_global))
2496 ? Del : NoDel;
2497 if(mn_total_cur(msgmap) > 1L)
2498 pre = F_OFF(F_AGG_SEQ_COPY, ps_global) ? Preserve : NoPreserve;
2499 else
2500 pre = DontAskPreserve;
2502 if(save_prompt(state, &cntxt, newfolder, sizeof(newfolder), nmsgs, e,
2503 raw, NULL, &del, &pre)){
2505 if(ps_global && ps_global->ttyo){
2506 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2507 ps_global->mangled_footer = 1;
2510 save_flags = SV_FIX_DELS;
2511 if(pre == RetPreserve)
2512 save_flags |= SV_PRESERVE;
2513 if(del == RetDel)
2514 save_flags |= SV_DELETE;
2515 if(ps_global->context_list == cntxt && !strucmp(newfolder, ps_global->inbox_name))
2516 save_flags |= SV_INBOXWOCNTXT;
2518 we_cancel = busy_cue(_("Saving"), NULL, 1);
2519 i = save(state, stream, cntxt, newfolder, msgmap, save_flags);
2520 if(we_cancel)
2521 cancel_busy_cue(0);
2523 if(i == mn_total_cur(msgmap)){
2524 rv++;
2525 if(mn_total_cur(msgmap) <= 1L){
2526 int need, avail = ps_global->ttyo->screen_cols - 2;
2527 int lennick, lenfldr;
2529 if(cntxt
2530 && ps_global->context_list->next
2531 && context_isambig(newfolder)){
2532 lennick = MIN(strlen(cntxt->nickname), 500);
2533 lenfldr = MIN(strlen(newfolder), 500);
2534 need = 27 + strlen(long2string(mn_get_cur(msgmap))) +
2535 lenfldr + lennick;
2536 if(need > avail){
2537 if(lennick > 10){
2538 need -= MIN(lennick-10, need-avail);
2539 lennick -= MIN(lennick-10, need-avail);
2542 if(need > avail && lenfldr > 10)
2543 lenfldr -= MIN(lenfldr-10, need-avail);
2546 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2547 "Message %s copied to \"%s\" in <%s>",
2548 long2string(mn_get_cur(msgmap)),
2549 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2550 lenfldr, MidDots),
2551 short_str(cntxt->nickname,
2552 (char *)(tmp_20k_buf+2000), 1000,
2553 lennick, EndDots));
2554 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2556 else if((nick=folder_is_target_of_nick(newfolder, cntxt)) != NULL){
2557 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2558 "Message %s copied to \"%s\"",
2559 long2string(mn_get_cur(msgmap)),
2560 nick);
2561 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2563 else{
2564 char *f = " folder";
2566 lenfldr = MIN(strlen(newfolder), 500);
2567 need = 28 + strlen(long2string(mn_get_cur(msgmap))) +
2568 lenfldr;
2569 if(need > avail){
2570 need -= strlen(f);
2571 f = "";
2572 if(need > avail && lenfldr > 10)
2573 lenfldr -= MIN(lenfldr-10, need-avail);
2576 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
2577 "Message %s copied to%s \"%s\"",
2578 long2string(mn_get_cur(msgmap)), f,
2579 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2580 lenfldr, MidDots));
2581 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2584 else{
2585 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s messages saved",
2586 comatose(mn_total_cur(msgmap)));
2587 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2590 if(del == RetDel){
2591 strncat(tmp_20k_buf, " and deleted", SIZEOF_20KBUF-strlen(tmp_20k_buf)-1);
2592 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2595 q_status_message(SM_ORDER, 0, 3, tmp_20k_buf);
2597 if(!MCMD_ISAGG(aopt) && F_ON(F_SAVE_ADVANCES, state)){
2598 if(sp_new_mail_count(stream))
2599 process_filter_patterns(stream, msgmap,
2600 sp_new_mail_count(stream));
2602 mn_inc_cur(stream, msgmap,
2603 (in_index == View && THREADING()
2604 && sp_viewing_a_thread(stream))
2605 ? MH_THISTHD
2606 : (in_index == View)
2607 ? MH_ANYTHD : MH_NONE);
2610 state->ugly_consider_advancing_bit = 1;
2614 if(MCMD_ISAGG(aopt)) /* straighten out fakes */
2615 restore_selected(msgmap);
2617 if(del == RetDel)
2618 update_titlebar_status(); /* make sure they see change */
2620 return rv;
2624 void
2625 role_compose(struct pine *state)
2627 int action;
2629 if(F_ON(F_ALT_ROLE_MENU, state) && mn_get_total(state->msgmap) > 0L){
2630 PAT_STATE pstate;
2632 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
2633 action = radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
2634 -FOOTER_ROWS(state), choose_action,
2635 'c', 'x', h_role_compose, RB_NORM);
2637 else{
2638 q_status_message(SM_ORDER, 0, 3,
2639 _("No roles available. Use Setup/Rules to add roles."));
2640 return;
2643 else
2644 action = 'c';
2646 if(action == 'c' || action == 'r' || action == 'f' || action == 'b'){
2647 ACTION_S *role = NULL;
2648 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
2650 redraw = state->redrawer;
2651 state->redrawer = NULL;
2652 prev_screen = state->prev_screen;
2653 role = NULL;
2654 state->next_screen = SCREEN_FUN_NULL;
2656 /* Setup role */
2657 if(role_select_screen(state, &role,
2658 action == 'f' ? MC_FORWARD :
2659 action == 'r' ? MC_REPLY :
2660 action == 'b' ? MC_BOUNCE :
2661 action == 'c' ? MC_COMPOSE : 0) < 0){
2662 cmd_cancelled(action == 'f' ? _("Forward") :
2663 action == 'r' ? _("Reply") :
2664 action == 'c' ? _("Composition") : _("Bounce"));
2665 state->next_screen = prev_screen;
2666 state->redrawer = redraw;
2667 state->mangled_screen = 1;
2669 else{
2671 * If default role was selected (NULL) we need to make
2672 * up a role which won't do anything, but will cause
2673 * compose_mail to think there's already a role so that
2674 * it won't try to confirm the default.
2676 if(role)
2677 role = combine_inherited_role(role);
2678 else{
2679 role = (ACTION_S *) fs_get(sizeof(*role));
2680 memset((void *) role, 0, sizeof(*role));
2681 role->nick = cpystr("Default Role");
2684 state->redrawer = NULL;
2685 switch(action){
2686 case 'c':
2687 compose_mail(NULL, NULL, role, NULL, NULL);
2688 break;
2690 case 'r':
2691 (void) reply(state, role);
2692 break;
2694 case 'f':
2695 (void) forward(state, role);
2696 break;
2698 case 'b':
2699 (void) bounce(state, role);
2700 break;
2703 if(role)
2704 free_action(&role);
2706 state->next_screen = prev_screen;
2707 state->redrawer = redraw;
2708 state->mangled_screen = 1;
2714 /*----------------------------------------------------------------------
2715 Do the dirty work of prompting the user for a folder name
2717 Args:
2718 nfldr should be a buffer at least MAILTMPLEN long
2719 dela -- a pointer to a SaveDel. If it is
2720 DontAsk on input, don't offer Delete prompt
2721 Del on input, offer Delete command with default of Delete
2722 NoDel NoDelete
2723 RetDel and RetNoDel are return values
2726 Result:
2728 ----*/
2730 save_prompt(struct pine *state, CONTEXT_S **cntxt, char *nfldr, size_t len_nfldr,
2731 char *nmsgs, ENVELOPE *env, long int rawmsgno, char *section,
2732 SaveDel *dela, SavePreserveOrder *prea)
2734 int rc, ku = -1, n, flags, last_rc = 0, saveable_count = 0, done = 0;
2735 int delindex, preindex, r;
2736 char prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN];
2737 char *buf = tmp_20k_buf;
2738 char shortbuf[200];
2739 char *folder;
2740 HelpType help;
2741 SaveDel del = DontAsk;
2742 SavePreserveOrder pre = DontAskPreserve;
2743 char *deltext = NULL;
2744 static HISTORY_S *history = NULL;
2745 CONTEXT_S *tc;
2746 ESCKEY_S ekey[10];
2748 if(!cntxt)
2749 alpine_panic("no context ptr in save_prompt");
2751 init_hist(&history, HISTSIZE);
2753 if(!(folder = save_get_default(state, env, rawmsgno, section, cntxt)))
2754 return(0); /* message expunged! */
2756 /* how many context's can be saved to... */
2757 for(tc = state->context_list; tc; tc = tc->next)
2758 if(!NEWS_TEST(tc))
2759 saveable_count++;
2761 /* set up extra command option keys */
2762 rc = 0;
2763 ekey[rc].ch = ctrl('T');
2764 ekey[rc].rval = 2;
2765 ekey[rc].name = "^T";
2766 /* TRANSLATORS: command means go to Folders list */
2767 ekey[rc++].label = N_("To Fldrs");
2769 if(saveable_count > 1){
2770 ekey[rc].ch = ctrl('P');
2771 ekey[rc].rval = 10;
2772 ekey[rc].name = "^P";
2773 ekey[rc++].label = N_("Prev Collection");
2775 ekey[rc].ch = ctrl('N');
2776 ekey[rc].rval = 11;
2777 ekey[rc].name = "^N";
2778 ekey[rc++].label = N_("Next Collection");
2781 if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
2782 ekey[rc].ch = TAB;
2783 ekey[rc].rval = 12;
2784 ekey[rc].name = "TAB";
2785 /* TRANSLATORS: command asks alpine to complete the name when tab is typed */
2786 ekey[rc++].label = N_("Complete");
2789 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
2790 ekey[rc].ch = ctrl('X');
2791 ekey[rc].rval = 14;
2792 ekey[rc].name = "^X";
2793 /* TRANSLATORS: list all the matches */
2794 ekey[rc++].label = N_("ListMatches");
2797 if(dela && (*dela == NoDel || *dela == Del)){
2798 ekey[rc].ch = ctrl('R');
2799 ekey[rc].rval = 15;
2800 ekey[rc].name = "^R";
2801 delindex = rc++;
2802 del = *dela;
2805 if(prea && (*prea == NoPreserve || *prea == Preserve)){
2806 ekey[rc].ch = ctrl('W');
2807 ekey[rc].rval = 16;
2808 ekey[rc].name = "^W";
2809 preindex = rc++;
2810 pre = *prea;
2813 if(saveable_count > 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2814 ekey[rc].ch = KEY_UP;
2815 ekey[rc].rval = 10;
2816 ekey[rc].name = "";
2817 ekey[rc++].label = "";
2819 ekey[rc].ch = KEY_DOWN;
2820 ekey[rc].rval = 11;
2821 ekey[rc].name = "";
2822 ekey[rc++].label = "";
2824 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2825 ekey[rc].ch = KEY_UP;
2826 ekey[rc].rval = 30;
2827 ekey[rc].name = "";
2828 ku = rc;
2829 ekey[rc++].label = "";
2831 ekey[rc].ch = KEY_DOWN;
2832 ekey[rc].rval = 31;
2833 ekey[rc].name = "";
2834 ekey[rc++].label = "";
2837 ekey[rc].ch = -1;
2839 *nfldr = '\0';
2840 help = NO_HELP;
2841 while(!done){
2842 /* only show collection number if more than one available */
2843 if(ps_global->context_list->next)
2844 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder in <%s> [%s] : ",
2845 deltext ? deltext : "",
2846 nmsgs,
2847 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2848 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2849 else
2850 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder [%s] : ",
2851 deltext ? deltext : "",
2852 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2854 prompt[sizeof(prompt)-1] = '\0';
2857 * If the prompt won't fit, try removing deltext.
2859 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && deltext){
2860 if(ps_global->context_list->next)
2861 snprintf(prompt, sizeof(prompt), "SAVE %sto folder in <%s> [%s] : ",
2862 nmsgs,
2863 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2864 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2865 else
2866 snprintf(prompt, sizeof(prompt), "SAVE %sto folder [%s] : ",
2867 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2869 prompt[sizeof(prompt)-1] = '\0';
2873 * If the prompt still won't fit, remove the extra info contained
2874 * in nmsgs.
2876 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && *nmsgs){
2877 if(ps_global->context_list->next)
2878 snprintf(prompt, sizeof(prompt), "SAVE to folder in <%s> [%s] : ",
2879 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2880 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2881 else
2882 snprintf(prompt, sizeof(prompt), "SAVE to folder [%s] : ",
2883 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2885 prompt[sizeof(prompt)-1] = '\0';
2888 if(del != DontAsk)
2889 ekey[delindex].label = (del == NoDel) ? "Delete" : "No Delete";
2891 if(pre != DontAskPreserve)
2892 ekey[preindex].label = (pre == NoPreserve) ? "Preserve Order" : "Any Order";
2894 if(ku >= 0){
2895 if(items_in_hist(history) > 1){
2896 ekey[ku].name = HISTORY_UP_KEYNAME;
2897 ekey[ku].label = HISTORY_KEYLABEL;
2898 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
2899 ekey[ku+1].label = HISTORY_KEYLABEL;
2901 else{
2902 ekey[ku].name = "";
2903 ekey[ku].label = "";
2904 ekey[ku+1].name = "";
2905 ekey[ku+1].label = "";
2909 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
2910 rc = optionally_enter(nfldr, -FOOTER_ROWS(state), 0, len_nfldr,
2911 prompt, ekey, help, &flags);
2913 switch(rc){
2914 case -1 :
2915 q_status_message(SM_ORDER | SM_DING, 3, 3,
2916 _("Error reading folder name"));
2917 done--;
2918 break;
2920 case 0 :
2921 removing_trailing_white_space(nfldr);
2922 removing_leading_white_space(nfldr);
2924 if(*nfldr || *folder){
2925 char *p, *name, *fullname = NULL;
2926 int exists, breakout = FALSE;
2928 if(!*nfldr){
2929 strncpy(nfldr, folder, len_nfldr-1);
2930 nfldr[len_nfldr-1] = '\0';
2933 save_hist(history, nfldr, 0, (void *) *cntxt);
2935 if(!(name = folder_is_nick(nfldr, FOLDERS(*cntxt), 0)))
2936 name = nfldr;
2938 if(update_folder_spec(expanded, sizeof(expanded), name)){
2939 strncpy(name = nfldr, expanded, len_nfldr-1);
2940 nfldr[len_nfldr-1] = '\0';
2943 exists = folder_name_exists(*cntxt, name, &fullname);
2945 if(exists == FEX_ERROR){
2946 q_status_message1(SM_ORDER, 0, 3,
2947 _("Problem accessing folder \"%s\""),
2948 nfldr);
2949 done--;
2951 else{
2952 if(fullname){
2953 strncpy(name = nfldr, fullname, len_nfldr-1);
2954 nfldr[len_nfldr-1] = '\0';
2955 fs_give((void **) &fullname);
2956 breakout = TRUE;
2959 if(exists & FEX_ISFILE){
2960 done++;
2962 else if((exists & FEX_ISDIR)){
2963 char tmp[MAILTMPLEN];
2965 tc = *cntxt;
2966 if(breakout){
2967 CONTEXT_S *fake_context;
2968 size_t l;
2970 strncpy(tmp, name, sizeof(tmp));
2971 tmp[sizeof(tmp)-2-1] = '\0';
2972 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
2973 if(l < sizeof(tmp)){
2974 tmp[l] = tc->dir->delim;
2975 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
2978 else
2979 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
2981 tmp[sizeof(tmp)-1] = '\0';
2983 fake_context = new_context(tmp, 0);
2984 nfldr[0] = '\0';
2985 done = display_folder_list(&fake_context, nfldr,
2986 1, folders_for_save);
2987 free_context(&fake_context);
2989 else if(tc->dir->delim
2990 && (p = strrindex(name, tc->dir->delim))
2991 && *(p+1) == '\0')
2992 done = display_folder_list(cntxt, nfldr,
2993 1, folders_for_save);
2994 else{
2995 q_status_message1(SM_ORDER, 3, 3,
2996 _("\"%s\" is a directory"), name);
2997 if(tc->dir->delim
2998 && !((p=strrindex(name, tc->dir->delim)) && *(p+1) == '\0')){
2999 strncpy(tmp, name, sizeof(tmp));
3000 tmp[sizeof(tmp)-1] = '\0';
3001 snprintf(nfldr, len_nfldr, "%s%c", tmp, tc->dir->delim);
3005 else{ /* Doesn't exist, create! */
3006 if((fullname = folder_as_breakout(*cntxt, name)) != NULL){
3007 strncpy(name = nfldr, fullname, len_nfldr-1);
3008 nfldr[len_nfldr-1] = '\0';
3009 fs_give((void **) &fullname);
3012 switch(create_for_save(*cntxt, name)){
3013 case 1 : /* success */
3014 done++;
3015 break;
3016 case 0 : /* error */
3017 case -1 : /* declined */
3018 done--;
3019 break;
3024 break;
3026 /* else fall thru like they cancelled */
3028 case 1 :
3029 cmd_cancelled("Save message");
3030 done--;
3031 break;
3033 case 2 :
3034 r = display_folder_list(cntxt, nfldr, 0, folders_for_save);
3036 if(r)
3037 done++;
3039 break;
3041 case 3 :
3042 helper(h_save, _("HELP FOR SAVE"), HLPD_SIMPLE);
3043 ps_global->mangled_screen = 1;
3044 break;
3046 case 4 : /* redraw */
3047 break;
3049 case 10 : /* previous collection */
3050 for(tc = (*cntxt)->prev; tc; tc = tc->prev)
3051 if(!NEWS_TEST(tc))
3052 break;
3054 if(!tc){
3055 CONTEXT_S *tc2;
3057 for(tc2 = (tc = (*cntxt))->next; tc2; tc2 = tc2->next)
3058 if(!NEWS_TEST(tc2))
3059 tc = tc2;
3062 *cntxt = tc;
3063 break;
3065 case 11 : /* next collection */
3066 tc = (*cntxt);
3069 if(((*cntxt) = (*cntxt)->next) == NULL)
3070 (*cntxt) = ps_global->context_list;
3071 while(NEWS_TEST(*cntxt) && (*cntxt) != tc);
3072 break;
3074 case 12 : /* file name completion */
3075 if(!folder_complete(*cntxt, nfldr, len_nfldr, &n)){
3076 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
3077 r = display_folder_list(cntxt, nfldr, 1, folders_for_save);
3078 if(r)
3079 done++; /* bingo! */
3080 else
3081 rc = 0; /* burn last_rc */
3083 else
3084 Writechar(BELL, 0);
3087 break;
3089 case 14 : /* file name completion */
3090 r = display_folder_list(cntxt, nfldr, 2, folders_for_save);
3091 if(r)
3092 done++; /* bingo! */
3093 else
3094 rc = 0; /* burn last_rc */
3096 break;
3098 case 15 : /* Delete / No Delete */
3099 del = (del == NoDel) ? Del : NoDel;
3100 deltext = (del == NoDel) ? " (no delete)" : " (and delete)";
3101 break;
3103 case 16 : /* Preserve Order or not */
3104 pre = (pre == NoPreserve) ? Preserve : NoPreserve;
3105 break;
3107 case 30 :
3108 if((p = get_prev_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3109 strncpy(nfldr, p, len_nfldr);
3110 nfldr[len_nfldr-1] = '\0';
3111 if(history->hist[history->curindex])
3112 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3114 else
3115 Writechar(BELL, 0);
3117 break;
3119 case 31 :
3120 if((p = get_next_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3121 strncpy(nfldr, p, len_nfldr);
3122 nfldr[len_nfldr-1] = '\0';
3123 if(history->hist[history->curindex])
3124 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3126 else
3127 Writechar(BELL, 0);
3129 break;
3131 default :
3132 alpine_panic("Unhandled case");
3133 break;
3136 last_rc = rc;
3139 ps_global->mangled_footer = 1;
3141 if(done < 0)
3142 return(0);
3144 if(*nfldr){
3145 strncpy(ps_global->last_save_folder, nfldr, sizeof(ps_global->last_save_folder)-1);
3146 ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0';
3147 if(*cntxt)
3148 ps_global->last_save_context = *cntxt;
3150 else{
3151 strncpy(nfldr, folder, len_nfldr-1);
3152 nfldr[len_nfldr-1] = '\0';
3155 /* nickname? Copy real name to nfldr */
3156 if(*cntxt
3157 && context_isambig(nfldr)
3158 && (p = folder_is_nick(nfldr, FOLDERS(*cntxt), 0))){
3159 strncpy(nfldr, p, len_nfldr-1);
3160 nfldr[len_nfldr-1] = '\0';
3163 if(dela && (*dela == NoDel || *dela == Del))
3164 *dela = (del == NoDel) ? RetNoDel : RetDel;
3166 if(prea && (*prea == NoPreserve || *prea == Preserve))
3167 *prea = (pre == NoPreserve) ? RetNoPreserve : RetPreserve;
3169 return(1);
3173 /*----------------------------------------------------------------------
3174 Prompt user before implicitly creating a folder for saving
3176 Args: context - context to create folder in
3177 folder - folder name to create
3179 Result: 1 on proceed, -1 on decline, 0 on error
3181 ----*/
3183 create_for_save_prompt(CONTEXT_S *context, char *folder, int sequence_sensitive)
3185 if(context && ps_global->context_list->next && context_isambig(folder)){
3186 if(context->use & CNTXT_INCMNG){
3187 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3188 _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
3189 folder, (strlen(folder) > 15) ? "..." : "");
3190 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
3191 return(0); /* error */
3194 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3195 _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
3196 folder, (strlen(folder) > 15) ? "..." : "",
3197 context->nickname,
3198 (strlen(context->nickname) > 15) ? "..." : "");
3200 else
3201 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
3202 _("Folder \"%.40s%s\" doesn't exist. Create"),
3203 folder, strlen(folder) > 40 ? "..." : "");
3205 if(want_to(tmp_20k_buf, 'y', 'n',
3206 NO_HELP, (sequence_sensitive) ? WT_SEQ_SENSITIVE : WT_NORM) != 'y'){
3207 cmd_cancelled("Save message");
3208 return(-1);
3211 return(1);
3216 /*----------------------------------------------------------------------
3217 Expunge messages from current folder
3219 Args: state -- pointer to struct holding a bunch of pine state
3220 msgmap -- table mapping msg nums to c-client sequence nums
3221 qline -- screen line to ask questions on
3222 agg -- boolean indicating we're to operate on aggregate set
3224 Result:
3225 ----*/
3227 cmd_expunge(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int agg)
3229 long del_count, prefilter_del_count;
3230 int we_cancel = 0, rv = 0;
3231 char prompt[MAX_SCREEN_COLS+1];
3232 char *sequence;
3233 COLOR_PAIR *lastc = NULL;
3235 dprint((2, "\n - expunge -\n"));
3237 del_count = 0;
3239 sequence = MCMD_ISAGG(agg) ? selected_sequence(stream, msgmap, NULL, 0) : NULL;
3241 if(MCMD_ISAGG(agg)){
3242 long i;
3243 MESSAGECACHE *mc;
3244 for(i = 1L; i <= stream->nmsgs; i++){
3245 if((mc = mail_elt(stream, i)) != NULL
3246 && mc->sequence && mc->deleted)
3247 del_count++;
3249 if(del_count == 0){
3250 q_status_message(SM_ORDER, 0, 4,
3251 _("No selected messages are deleted"));
3252 return 0;
3254 } else {
3255 if(!any_messages(msgmap, NULL, "to Expunge"))
3256 return rv;
3259 if(IS_NEWS(stream) && stream->rdonly){
3260 if(!MCMD_ISAGG(agg))
3261 del_count = count_flagged(stream, F_DEL);
3262 if(del_count > 0L){
3263 state->mangled_footer = 1;
3264 snprintf(prompt, sizeof(prompt), "Exclude %ld message%s from %.*s", del_count,
3265 plural(del_count), sizeof(prompt)-40,
3266 pretty_fn(state->cur_folder));
3267 prompt[sizeof(prompt)-1] = '\0';
3268 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3269 || (F_ON(F_AUTO_EXPUNGE, state)
3270 && (state->context_current
3271 && (state->context_current->use & CNTXT_INCMNG))
3272 && context_isambig(state->cur_folder))
3273 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
3275 if(F_ON(F_NEWS_CROSS_DELETE, state))
3276 cross_delete_crossposts(stream);
3278 msgno_exclude_deleted(stream, msgmap, sequence);
3279 clear_index_cache(stream, 0);
3282 * This is kind of surprising at first. For most sort
3283 * orders, if the whole set is sorted, then any subset
3284 * is also sorted. Not so for threaded sorts.
3286 if(SORT_IS_THREADED(msgmap))
3287 refresh_sort(stream, msgmap, SRT_NON);
3289 state->mangled_body = 1;
3290 state->mangled_header = 1;
3291 q_status_message2(SM_ORDER, 0, 4,
3292 "%s message%s excluded",
3293 long2string(del_count),
3294 plural(del_count));
3296 else
3297 any_messages(NULL, NULL, "Excluded");
3299 else
3300 any_messages(NULL, "deleted", "to Exclude");
3302 return del_count;
3304 else if(READONLY_FOLDER(stream)){
3305 q_status_message(SM_ORDER, 0, 4,
3306 _("Can't expunge. Folder is read-only"));
3307 return del_count;
3310 if(!MCMD_ISAGG(agg)){
3311 prefilter_del_count = count_flagged(stream, F_DEL|F_NOFILT);
3312 mail_expunge_prefilter(stream, MI_NONE);
3313 del_count = count_flagged(stream, F_DEL|F_NOFILT);
3316 if(del_count != 0){
3317 int ret;
3318 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3320 snprintf(prompt, sizeof(prompt), "Expunge %ld message%s from %.*s", del_count,
3321 plural(del_count), sizeof(prompt)-40,
3322 pretty_fn((char *) fname));
3323 if(fname) fs_give((void **)&fname);
3324 prompt[sizeof(prompt)-1] = '\0';
3325 state->mangled_footer = 1;
3327 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3328 || (F_ON(F_AUTO_EXPUNGE, state)
3329 && ((!strucmp(state->cur_folder,state->inbox_name))
3330 || (state->context_current->use & CNTXT_INCMNG))
3331 && context_isambig(state->cur_folder))
3332 || (ret=want_to(prompt, 'y', 0, NO_HELP, WT_NORM)) == 'y')
3333 ret = 'y';
3335 if(ret == 'x')
3336 cmd_cancelled("Expunge");
3338 if(ret != 'y')
3339 return 0;
3342 dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
3343 mn_get_total(msgmap), mn_get_cur(msgmap), del_count));
3345 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3346 state->VAR_TITLE_BACK_COLOR,
3347 PSC_REV|PSC_RET);
3349 PutLine0(0, 0, "**"); /* indicate delay */
3351 if(lastc){
3352 (void)pico_set_colorp(lastc, PSC_NONE);
3353 free_color_pair(&lastc);
3356 MoveCursor(state->ttyo->screen_rows -FOOTER_ROWS(state), 0);
3357 fflush(stdout);
3359 we_cancel = busy_cue(_("Expunging"), NULL, 1);
3361 if(cmd_expunge_work(stream, msgmap, sequence))
3362 state->mangled_body = 1;
3364 if(sequence)
3365 fs_give((void **)&sequence);
3367 if(we_cancel)
3368 cancel_busy_cue((sp_expunge_count(stream) > 0) ? 0 : -1);
3370 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3371 state->VAR_TITLE_BACK_COLOR,
3372 PSC_REV|PSC_RET);
3373 PutLine0(0, 0, " "); /* indicate delay's over */
3375 if(lastc){
3376 (void)pico_set_colorp(lastc, PSC_NONE);
3377 free_color_pair(&lastc);
3380 fflush(stdout);
3382 if(sp_expunge_count(stream) > 0){
3384 * This is kind of surprising at first. For most sort
3385 * orders, if the whole set is sorted, then any subset
3386 * is also sorted. Not so for threaded sorts.
3388 if(SORT_IS_THREADED(msgmap))
3389 refresh_sort(stream, msgmap, SRT_NON);
3391 else{
3392 if(del_count){
3393 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3394 q_status_message1(SM_ORDER, 0, 3,
3395 _("No messages expunged from folder \"%s\""),
3396 pretty_fn((char *) fname));
3397 if(fname) fs_give((void **)&fname);
3399 else if(!prefilter_del_count)
3400 q_status_message(SM_ORDER, 0, 3,
3401 _("No messages marked deleted. No messages expunged."));
3403 return del_count;
3407 /*----------------------------------------------------------------------
3408 Expunge_and_close callback to prompt user for confirmation
3410 Args: stream -- folder's stream
3411 folder -- name of folder containing folders
3412 deleted -- number of del'd msgs
3414 Result: 'y' to continue with expunge
3415 ----*/
3417 expunge_prompt(MAILSTREAM *stream, char *folder, long int deleted)
3419 long max_folder;
3420 int charcnt = 0;
3421 char prompt_b[MAX_SCREEN_COLS+1], temp[MAILTMPLEN+1], buff[MAX_SCREEN_COLS+1];
3422 char *short_folder_name;
3424 if(deleted == 1)
3425 charcnt = 1;
3426 else{
3427 snprintf(temp, sizeof(temp), "%ld", deleted);
3428 charcnt = strlen(temp)+1;
3431 max_folder = MAX(1,MAXPROMPT - (36+charcnt));
3432 strncpy(temp, folder, sizeof(temp));
3433 temp[sizeof(temp)-1] = '\0';
3434 short_folder_name = short_str(temp,buff,sizeof(buff),max_folder,FrontDots);
3436 if(IS_NEWS(stream))
3437 snprintf(prompt_b, sizeof(prompt_b),
3438 "Delete %s%ld message%s from \"%s\"",
3439 (deleted > 1L) ? "all " : "", deleted,
3440 plural(deleted), short_folder_name);
3441 else
3442 snprintf(prompt_b, sizeof(prompt_b),
3443 "Expunge the %ld deleted message%s from \"%s\"",
3444 deleted, deleted == 1 ? "" : "s",
3445 short_folder_name);
3447 return(want_to(prompt_b, 'y', 0, NO_HELP, WT_NORM));
3452 * This is used with multiple append saves. Call it once before
3453 * the series of appends with SSCP_INIT and once after all are
3454 * done with SSCP_END. In between, it is called automatically
3455 * from save_fetch_append or save_fetch_append_cb when we need
3456 * to ask the user if he or she wants to continue even though
3457 * announced message size doesn't match the actual message size.
3458 * As of 2008-02-29 the gmail IMAP server has these size mismatches
3459 * on a regular basis even though the data is ok.
3462 save_size_changed_prompt(long msgno, int flags)
3464 int ret;
3465 char prompt[100];
3466 static int remember_the_yes = 0;
3467 static int possible_corruption = 0;
3468 static ESCKEY_S save_size_opts[] = {
3469 {'y', 'y', "Y", "Yes"},
3470 {'n', 'n', "N", "No"},
3471 {'a', 'a', "A", "yes to All"},
3472 {-1, 0, NULL, NULL}
3475 if(flags & SSCP_INIT || flags & SSCP_END){
3476 if(flags & SSCP_END && possible_corruption)
3477 q_status_message(SM_ORDER, 3, 3, "There is possible data corruption, check the results");
3479 remember_the_yes = 0;
3480 possible_corruption = 0;
3481 ps_global->noshow_error = 0;
3482 ps_global->noshow_warn = 0;
3483 return(0);
3486 if(remember_the_yes){
3487 snprintf(prompt, sizeof(prompt),
3488 "Message to save shrank! (msg # %ld): Continuing", msgno);
3489 q_status_message(SM_ORDER, 0, 3, prompt);
3490 display_message('x');
3491 return(remember_the_yes);
3494 snprintf(prompt, sizeof(prompt),
3495 "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno);
3496 ret = radio_buttons(prompt, -FOOTER_ROWS(ps_global), save_size_opts,
3497 'n', 0, h_save_size_changed, RB_NORM);
3499 switch(ret){
3500 case 'a':
3501 remember_the_yes = 'y';
3502 possible_corruption++;
3503 return(remember_the_yes);
3505 case 'y':
3506 possible_corruption++;
3507 return('y');
3509 default:
3510 possible_corruption = 0;
3511 ps_global->noshow_error = 1;
3512 ps_global->noshow_warn = 1;
3513 break;
3516 return('n');
3520 /*----------------------------------------------------------------------
3521 Expunge_and_close callback that happens once the decision to expunge
3522 and close has been made and before expunging and closing begins
3525 Args: stream -- folder's stream
3526 folder -- name of folder containing folders
3527 deleted -- number of del'd msgs
3529 Result: 'y' to continue with expunge
3530 ----*/
3531 void
3532 expunge_and_close_begins(int flags, char *folder)
3534 if(!(flags & EC_NO_CLOSE)){
3535 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
3536 q_status_message1(SM_INFO, 0, 1, "Closing \"%.200s\"...", (char *) fname);
3537 flush_status_messages(1);
3538 if(fname) fs_give((void **)&fname);
3543 /*----------------------------------------------------------------------
3544 Export a message to a plain file in users home directory
3546 Args: state -- pointer to struct holding a bunch of pine state
3547 msgmap -- table mapping msg nums to c-client sequence nums
3548 qline -- screen line to ask questions on
3549 agg -- boolean indicating we're to operate on aggregate set
3551 Result:
3552 ----*/
3554 cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt)
3556 char filename[MAXPATH+1], full_filename[MAXPATH+1], *err;
3557 char nmsgs[80];
3558 int r, leading_nl, failure = 0, orig_errno, rflags = GER_NONE;
3559 int flags = GE_IS_EXPORT | GE_SEQ_SENSITIVE, rv = 0;
3560 ENVELOPE *env;
3561 MESSAGECACHE *mc;
3562 BODY *b;
3563 long i, count = 0L, start_of_append, rawno;
3564 gf_io_t pc;
3565 STORE_S *store;
3566 struct variable *vars = ps_global->vars;
3567 ESCKEY_S export_opts[5];
3568 static HISTORY_S *history = NULL;
3570 if(ps_global->restricted){
3571 q_status_message(SM_ORDER, 0, 3,
3572 "Alpine demo can't export messages to files");
3573 return rv;
3576 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
3577 return rv;
3579 export_opts[i = 0].ch = ctrl('T');
3580 export_opts[i].rval = 10;
3581 export_opts[i].name = "^T";
3582 export_opts[i++].label = N_("To Files");
3584 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3585 if(ps_global->VAR_DOWNLOAD_CMD && ps_global->VAR_DOWNLOAD_CMD[0]){
3586 export_opts[i].ch = ctrl('V');
3587 export_opts[i].rval = 12;
3588 export_opts[i].name = "^V";
3589 /* TRANSLATORS: this is an abbreviation for Download Messages */
3590 export_opts[i++].label = N_("Downld Msg");
3592 #endif /* !(DOS || MAC) */
3594 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
3595 export_opts[i].ch = ctrl('I');
3596 export_opts[i].rval = 11;
3597 export_opts[i].name = "TAB";
3598 export_opts[i++].label = N_("Complete");
3601 #if 0
3602 /* Commented out since it's not yet support! */
3603 if(F_ON(F_ENABLE_SUB_LISTS,ps_global)){
3604 export_opts[i].ch = ctrl('X');
3605 export_opts[i].rval = 14;
3606 export_opts[i].name = "^X";
3607 export_opts[i++].label = N_("ListMatches");
3609 #endif
3612 * If message has attachments, add a toggle that will allow the user
3613 * to save all of the attachments to a single directory, using the
3614 * names provided with the attachments or part names. What we'll do is
3615 * export the message as usual, and then export the attachments into
3616 * a subdirectory that did not exist before. The subdir will be named
3617 * something based on the name of the file being saved to, but a
3618 * unique, new name.
3620 if(!MCMD_ISAGG(aopt)
3621 && state->mail_stream
3622 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3623 && rawno <= state->mail_stream->nmsgs
3624 && (env = pine_mail_fetchstructure(state->mail_stream, rawno, &b))
3625 && b
3626 && b->type == TYPEMULTIPART
3627 && b->subtype
3628 && strucmp(b->subtype, "ALTERNATIVE") != 0){
3629 PART *part;
3631 part = b->nested.part; /* 1st part */
3632 if(part && part->next)
3633 flags |= GE_ALLPARTS;
3636 export_opts[i].ch = -1;
3637 filename[0] = '\0';
3639 if(mn_total_cur(msgmap) <= 1L){
3640 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld", mn_get_cur(msgmap));
3641 nmsgs[sizeof(nmsgs)-1] = '\0';
3643 else{
3644 snprintf(nmsgs, sizeof(nmsgs), "%s messages", comatose(mn_total_cur(msgmap)));
3645 nmsgs[sizeof(nmsgs)-1] = '\0';
3648 r = get_export_filename(state, filename, NULL, full_filename,
3649 sizeof(filename), nmsgs, "EXPORT",
3650 export_opts, &rflags, qline, flags, &history);
3652 if(r < 0){
3653 switch(r){
3654 case -1:
3655 cmd_cancelled("Export message");
3656 break;
3658 case -2:
3659 q_status_message1(SM_ORDER, 0, 2,
3660 _("Can't export to file outside of %s"),
3661 VAR_OPER_DIR);
3662 break;
3665 goto fini;
3667 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3668 else if(r == 12){ /* Download */
3669 char cmd[MAXPATH], *tfp = NULL;
3670 int next = 0;
3671 PIPE_S *syspipe;
3672 STORE_S *so;
3673 gf_io_t pc;
3675 if(ps_global->restricted){
3676 q_status_message(SM_ORDER | SM_DING, 3, 3,
3677 "Download disallowed in restricted mode");
3678 goto fini;
3681 err = NULL;
3682 tfp = temp_nam(NULL, "pd");
3683 build_updown_cmd(cmd, sizeof(cmd), ps_global->VAR_DOWNLOAD_CMD_PREFIX,
3684 ps_global->VAR_DOWNLOAD_CMD, tfp);
3685 dprint((1, "Download cmd called: \"%s\"\n", cmd));
3686 if((so = so_get(FileStar, tfp, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){
3687 gf_set_so_writec(&pc, so);
3689 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
3690 if(!(state->mail_stream
3691 && (rawno = mn_m2raw(msgmap, i)) > 0L
3692 && rawno <= state->mail_stream->nmsgs
3693 && (mc = mail_elt(state->mail_stream, rawno))
3694 && mc->valid))
3695 mc = NULL;
3697 if(!(env = pine_mail_fetchstructure(state->mail_stream,
3698 mn_m2raw(msgmap, i), &b))
3699 || !bezerk_delimiter(env, mc, pc, next++)
3700 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
3701 env, b, NULL, FM_NEW_MESS | FM_NOWRAP, pc)){
3702 q_status_message(SM_ORDER | SM_DING, 3, 3,
3703 err = "Error writing tempfile for download");
3704 break;
3708 gf_clear_so_writec(so);
3709 if(so_give(&so)){ /* close file */
3710 if(!err)
3711 err = "Error writing tempfile for download";
3714 if(!err){
3715 if((syspipe = open_system_pipe(cmd, NULL, NULL,
3716 PIPE_USER | PIPE_RESET,
3717 0, pipe_callback, pipe_report_error)) != NULL)
3718 (void) close_system_pipe(&syspipe, NULL, pipe_callback);
3719 else
3720 q_status_message(SM_ORDER | SM_DING, 3, 3,
3721 err = _("Error running download command"));
3724 else
3725 q_status_message(SM_ORDER | SM_DING, 3, 3,
3726 err = "Error building temp file for download");
3728 if(tfp){
3729 our_unlink(tfp);
3730 fs_give((void **)&tfp);
3733 if(!err)
3734 q_status_message(SM_ORDER, 0, 3, _("Download Command Completed"));
3736 goto fini;
3738 #endif /* !(DOS || MAC) */
3741 if(rflags & GER_APPEND)
3742 leading_nl = 1;
3743 else
3744 leading_nl = 0;
3746 dprint((5, "Opening file \"%s\" for export\n",
3747 full_filename ? full_filename : "?"));
3749 if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE))){
3750 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3751 /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
3752 _("Error opening file \"%s\" to export message: %s"),
3753 full_filename, error_description(errno));
3754 goto fini;
3756 else
3757 gf_set_so_writec(&pc, store);
3759 err = NULL;
3760 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), count++){
3761 env = pine_mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i),
3762 &b);
3763 if(!env) {
3764 err = _("Can't export message. Error accessing mail folder");
3765 failure = 1;
3766 break;
3769 if(!(state->mail_stream
3770 && (rawno = mn_m2raw(msgmap, i)) > 0L
3771 && rawno <= state->mail_stream->nmsgs
3772 && (mc = mail_elt(state->mail_stream, rawno))
3773 && mc->valid))
3774 mc = NULL;
3776 start_of_append = so_tell(store);
3777 if(!bezerk_delimiter(env, mc, pc, leading_nl)
3778 || !format_message(mn_m2raw(msgmap, i), env, b, NULL,
3779 FM_NEW_MESS | FM_NOWRAP, pc)){
3780 orig_errno = errno; /* save incase things are really bad */
3781 failure = 1; /* pop out of here */
3782 break;
3785 leading_nl = 1;
3788 gf_clear_so_writec(store);
3789 if(so_give(&store)) /* release storage */
3790 failure++;
3792 if(failure){
3793 our_truncate(full_filename, (off_t)start_of_append);
3794 if(err){
3795 dprint((1, "FAILED Export: fetch(%ld): %s\n",
3796 i, err ? err : "?"));
3797 q_status_message(SM_ORDER | SM_DING, 3, 4, err);
3799 else{
3800 dprint((1, "FAILED Export: file \"%s\" : %s\n",
3801 full_filename ? full_filename : "?",
3802 error_description(orig_errno)));
3803 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3804 /* TRANSLATORS: Error exporting to <filename>: <error text> */
3805 _("Error exporting to \"%s\" : %s"),
3806 filename, error_description(orig_errno));
3809 else{
3810 if(rflags & GER_ALLPARTS && full_filename[0]){
3811 char dir[MAXPATH+1];
3812 char lfile[MAXPATH+1];
3813 int ok = 0, tries = 0, saved = 0, errs = 0;
3814 ATTACH_S *a;
3817 * Now we want to save all of the attachments to a subdirectory.
3818 * To make it easier for us and probably easier for the user, and
3819 * to prevent the user from shooting himself in the foot, we
3820 * make a new subdirectory so that we can't possibly step on
3821 * any existing files, and we don't need any interaction with the
3822 * user while saving.
3824 * We'll just use the directory name full_filename.d or if that
3825 * already exists and isn't empty, we'll try adding a suffix to
3826 * that until we get something to use.
3829 if(strlen(full_filename) + strlen(".d") + 1 > sizeof(dir)){
3830 q_status_message1(SM_ORDER | SM_DING, 3, 4,
3831 _("Can't save attachments, filename too long: %s"),
3832 full_filename);
3833 goto fini;
3836 ok = 0;
3837 snprintf(dir, sizeof(dir), "%s.d", full_filename);
3838 dir[sizeof(dir)-1] = '\0';
3840 do {
3841 tries++;
3842 switch(r = is_writable_dir(dir)){
3843 case 0: /* exists and is a writable dir */
3845 * We could figure out if it is empty and use it in
3846 * that case, but that sounds like a lot of work, so
3847 * just fall through to default.
3850 default:
3851 if(strlen(full_filename) + strlen(".d") + 1 +
3852 1 + strlen(long2string((long) tries)) > sizeof(dir)){
3853 q_status_message(SM_ORDER | SM_DING, 3, 4,
3854 "Problem saving attachments");
3855 goto fini;
3858 snprintf(dir, sizeof(dir), "%s.d_%s", full_filename,
3859 long2string((long) tries));
3860 dir[sizeof(dir)-1] = '\0';
3861 break;
3863 case 3: /* doesn't exist, that's good! */
3864 /* make new directory */
3865 ok++;
3866 break;
3868 } while(!ok && tries < 1000);
3870 if(tries >= 1000){
3871 q_status_message(SM_ORDER | SM_DING, 3, 4,
3872 _("Problem saving attachments"));
3873 goto fini;
3876 /* create the new directory */
3877 if(our_mkdir(dir, 0700)){
3878 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3879 _("Problem saving attachments: %s: %s"), dir,
3880 error_description(errno));
3881 goto fini;
3884 if(!(state->mail_stream
3885 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3886 && rawno <= state->mail_stream->nmsgs
3887 && (env=pine_mail_fetchstructure(state->mail_stream,rawno,&b))
3888 && b)){
3889 q_status_message(SM_ORDER | SM_DING, 3, 4,
3890 _("Problem reading message"));
3891 goto fini;
3894 zero_atmts(state->atmts);
3895 describe_mime(b, "", 1, 1, 0, 0);
3897 a = state->atmts;
3898 if(a && a->description) /* skip main body part */
3899 a++;
3901 for(; a->description != NULL; a++){
3902 /* skip over these parts of the message */
3903 if(MIME_MSG_A(a) || MIME_DGST_A(a) || MIME_VCARD_A(a))
3904 continue;
3906 lfile[0] = '\0';
3907 (void) get_filename_parameter(lfile, sizeof(lfile), a->body, NULL);
3909 if(lfile[0] == '\0'){
3910 snprintf(lfile, sizeof(lfile), "part_%.*s", sizeof(lfile)-6,
3911 a->number ? a->number : "?");
3912 lfile[sizeof(lfile)-1] = '\0';
3915 if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + 1
3916 > sizeof(filename)){
3917 dprint((2,
3918 "FAILED Att Export: name too long: %s\n",
3919 dir, S_FILESEP, lfile));
3920 errs++;
3921 continue;
3924 snprintf(filename, sizeof(filename), "%s%s%s", dir, S_FILESEP, lfile);
3925 filename[sizeof(filename)-1] = '\0';
3927 if(write_attachment_to_file(state->mail_stream, rawno,
3928 a, GER_NONE, filename) == 1)
3929 saved++;
3930 else
3931 errs++;
3934 if(errs){
3935 if(saved)
3936 q_status_message1(SM_ORDER, 3, 3,
3937 "Errors saving some attachments, %s attachments saved",
3938 long2string((long) saved));
3939 else
3940 q_status_message(SM_ORDER, 3, 3,
3941 _("Problems saving attachments"));
3943 else{
3944 if(saved)
3945 q_status_message2(SM_ORDER, 0, 3,
3946 /* TRANSLATORS: Saved <how many> attachements to <directory name> */
3947 _("Saved %s attachments to %s"),
3948 long2string((long) saved), dir);
3949 else
3950 q_status_message(SM_ORDER, 3, 3, _("No attachments to save"));
3953 else if(mn_total_cur(msgmap) > 1L)
3954 q_status_message4(SM_ORDER,0,3,
3955 "%s message%s %s to file \"%s\"",
3956 long2string(count), plural(count),
3957 rflags & GER_OVER
3958 ? "overwritten"
3959 : rflags & GER_APPEND ? "appended" : "exported",
3960 filename);
3961 else
3962 q_status_message3(SM_ORDER,0,3,
3963 "Message %s %s to file \"%s\"",
3964 long2string(mn_get_cur(msgmap)),
3965 rflags & GER_OVER
3966 ? "overwritten"
3967 : rflags & GER_APPEND ? "appended" : "exported",
3968 filename);
3969 rv++;
3972 fini:
3973 if(MCMD_ISAGG(aopt))
3974 restore_selected(msgmap);
3976 return rv;
3981 * Ask user what file to export to. Export from srcstore to that file.
3983 * Args ps -- pine struct
3984 * srctext -- pointer to source text
3985 * srctype -- type of that source text
3986 * prompt_msg -- see get_export_filename
3987 * lister_msg -- "
3989 * Returns: != 0 : error
3990 * 0 : ok
3993 simple_export(struct pine *ps, void *srctext, SourceType srctype, char *prompt_msg, char *lister_msg)
3995 int r = 1, rflags = GER_NONE;
3996 char filename[MAXPATH+1], full_filename[MAXPATH+1];
3997 STORE_S *store = NULL;
3998 struct variable *vars = ps->vars;
3999 static HISTORY_S *history = NULL;
4000 static ESCKEY_S simple_export_opts[] = {
4001 {ctrl('T'), 10, "^T", N_("To Files")},
4002 {-1, 0, NULL, NULL},
4003 {-1, 0, NULL, NULL}};
4005 if(F_ON(F_ENABLE_TAB_COMPLETE,ps)){
4006 simple_export_opts[r].ch = ctrl('I');
4007 simple_export_opts[r].rval = 11;
4008 simple_export_opts[r].name = "TAB";
4009 simple_export_opts[r].label = N_("Complete");
4012 if(!srctext){
4013 q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
4014 r = -3;
4015 goto fini;
4018 simple_export_opts[++r].ch = -1;
4019 filename[0] = '\0';
4020 full_filename[0] = '\0';
4022 r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename),
4023 prompt_msg, lister_msg, simple_export_opts, &rflags,
4024 -FOOTER_ROWS(ps), GE_IS_EXPORT, &history);
4026 if(r < 0)
4027 goto fini;
4028 else if(!full_filename[0]){
4029 r = -1;
4030 goto fini;
4033 dprint((5, "Opening file \"%s\" for export\n",
4034 full_filename ? full_filename : "?"));
4036 if((store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
4037 char *pipe_err;
4038 gf_io_t pc, gc;
4040 gf_set_so_writec(&pc, store);
4041 gf_set_readc(&gc, srctext, (srctype == CharStar)
4042 ? strlen((char *)srctext)
4043 : 0L,
4044 srctype, 0);
4045 gf_filter_init();
4046 if((pipe_err = gf_pipe(gc, pc)) != NULL){
4047 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4048 /* TRANSLATORS: Problem saving to <filename>: <error text> */
4049 _("Problem saving to \"%s\": %s"),
4050 filename, pipe_err);
4051 r = -3;
4053 else
4054 r = 0;
4056 gf_clear_so_writec(store);
4057 if(so_give(&store)){
4058 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4059 _("Problem saving to \"%s\": %s"),
4060 filename, error_description(errno));
4061 r = -3;
4064 else{
4065 q_status_message2(SM_ORDER | SM_DING, 3, 4,
4066 _("Error opening file \"%s\" for export: %s"),
4067 full_filename, error_description(errno));
4068 r = -3;
4071 fini:
4072 switch(r){
4073 case 0:
4074 /* overloading full_filename */
4075 snprintf(full_filename, sizeof(full_filename), "%c%s",
4076 (prompt_msg && prompt_msg[0])
4077 ? (islower((unsigned char)prompt_msg[0])
4078 ? toupper((unsigned char)prompt_msg[0]) : prompt_msg[0])
4079 : 'T',
4080 (prompt_msg && prompt_msg[0]) ? prompt_msg+1 : "ext");
4081 full_filename[sizeof(full_filename)-1] = '\0';
4082 q_status_message3(SM_ORDER,0,2,"%s %s to \"%s\"",
4083 full_filename,
4084 rflags & GER_OVER
4085 ? "overwritten"
4086 : rflags & GER_APPEND ? "appended" : "exported",
4087 filename);
4088 break;
4090 case -1:
4091 cmd_cancelled("Export");
4092 break;
4094 case -2:
4095 q_status_message1(SM_ORDER, 0, 2,
4096 _("Can't export to file outside of %s"), VAR_OPER_DIR);
4097 break;
4100 ps->mangled_footer = 1;
4101 return(r);
4106 * Ask user what file to export to.
4108 * filename -- On input, this is the filename to start with. On exit,
4109 * this is the filename chosen. (but this isn't used)
4110 * deefault -- This is the default value if user hits return. The
4111 * prompt will have [deefault] added to it automatically.
4112 * full_filename -- This is the full filename on exit.
4113 * len -- Minimum length of _both_ filename and full_filename.
4114 * prompt_msg -- Message to insert in prompt.
4115 * lister_msg -- Message to insert in file_lister.
4116 * opts -- Key options.
4117 * There is a tangled relationship between the callers
4118 * and this routine as far as opts are concerned. Some
4119 * of the opts are handled here. In particular, r == 3,
4120 * r == 10, r == 11, and r == 13 are all handled here.
4121 * Don't use those values unless you want what happens
4122 * here. r == 12 and others are handled by the caller.
4123 * rflags -- Return flags
4124 * GER_OVER - overwrite of existing file
4125 * GER_APPEND - append of existing file
4126 * else file did not exist before
4128 * GER_ALLPARTS - AllParts toggle was turned on
4130 * qline -- Command line to prompt on.
4131 * flags -- Logically OR'd flags
4132 * GE_IS_EXPORT - The command was an Export command
4133 * so the prompt should include
4134 * EXPORT:.
4135 * GE_SEQ_SENSITIVE - The command that got us here is
4136 * sensitive to sequence number changes
4137 * caused by unsolicited expunges.
4138 * GE_NO_APPEND - We will not allow append to an
4139 * existing file, only removal of the
4140 * file if it exists.
4141 * GE_IS_IMPORT - We are selecting for reading.
4142 * No overwriting or checking for
4143 * existence at all. Don't use this
4144 * together with GE_NO_APPEND.
4145 * GE_ALLPARTS - Turn on AllParts toggle.
4147 * Returns: -1 cancelled
4148 * -2 prohibited by VAR_OPER_DIR
4149 * -3 other error, already reported here
4150 * 0 ok
4151 * 12 user chose 12 command from opts
4154 get_export_filename(struct pine *ps, char *filename, char *deefault,
4155 char *full_filename, size_t len, char *prompt_msg,
4156 char *lister_msg, ESCKEY_S *optsarg, int *rflags,
4157 int qline, int flags, HISTORY_S **history)
4159 char dir[MAXPATH+1], dir2[MAXPATH+1];
4160 char precolon[MAXPATH+1], postcolon[MAXPATH+1];
4161 char filename2[MAXPATH+1], tmp[MAXPATH+1], *fn, *ill;
4162 int l, i, ku = -1, r, fatal, homedir = 0, was_abs_path=0, avail, ret = 0;
4163 int allparts = 0;
4164 char prompt_buf[400];
4165 char def[500];
4166 ESCKEY_S *opts = NULL;
4167 struct variable *vars = ps->vars;
4169 if(flags & GE_ALLPARTS || history){
4171 * Copy the opts and add one to the end of the list.
4173 for(i = 0; optsarg[i].ch != -1; i++)
4176 if(history)
4177 i += 2;
4179 if(flags & GE_ALLPARTS)
4180 i++;
4182 opts = (ESCKEY_S *) fs_get((i+1) * sizeof(*opts));
4183 memset(opts, 0, (i+1) * sizeof(*opts));
4185 for(i = 0; optsarg[i].ch != -1; i++){
4186 opts[i].ch = optsarg[i].ch;
4187 opts[i].rval = optsarg[i].rval;
4188 opts[i].name = optsarg[i].name; /* no need to make a copy */
4189 opts[i].label = optsarg[i].label; /* " */
4192 if(flags & GE_ALLPARTS){
4193 allparts = i;
4194 opts[i].ch = ctrl('P');
4195 opts[i].rval = 13;
4196 opts[i].name = "^P";
4197 /* TRANSLATORS: Export all attachment parts */
4198 opts[i++].label = N_("AllParts");
4201 if(history){
4202 opts[i].ch = KEY_UP;
4203 opts[i].rval = 30;
4204 opts[i].name = "";
4205 ku = i;
4206 opts[i++].label = "";
4208 opts[i].ch = KEY_DOWN;
4209 opts[i].rval = 31;
4210 opts[i].name = "";
4211 opts[i++].label = "";
4214 opts[i].ch = -1;
4216 if(history)
4217 init_hist(history, HISTSIZE);
4219 else
4220 opts = optsarg;
4222 if(rflags)
4223 *rflags = GER_NONE;
4225 if(F_ON(F_USE_CURRENT_DIR, ps))
4226 dir[0] = '\0';
4227 else if(VAR_OPER_DIR){
4228 strncpy(dir, VAR_OPER_DIR, sizeof(dir));
4229 dir[sizeof(dir)-1] = '\0';
4231 #if defined(DOS) || defined(OS2)
4232 else if(VAR_FILE_DIR){
4233 strncpy(dir, VAR_FILE_DIR, sizeof(dir));
4234 dir[sizeof(dir)-1] = '\0';
4236 #endif
4237 else{
4238 dir[0] = '~';
4239 dir[1] = '\0';
4240 homedir=1;
4243 postcolon[0] = '\0';
4244 strncpy(precolon, dir, sizeof(precolon));
4245 precolon[sizeof(precolon)-1] = '\0';
4246 if(deefault){
4247 strncpy(def, deefault, sizeof(def)-1);
4248 def[sizeof(def)-1] = '\0';
4249 removing_leading_and_trailing_white_space(def);
4251 else
4252 def[0] = '\0';
4254 avail = MAX(20, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - MIN_OPT_ENT_WIDTH;
4256 /*---------- Prompt the user for the file name -------------*/
4257 while(1){
4258 int oeflags;
4259 char dirb[50], fileb[50];
4260 int l1, l2, l3, l4, l5, needed;
4261 char *p, p1[100], p2[100], *p3, p4[100], p5[100];
4263 snprintf(p1, sizeof(p1), "%sCopy ",
4264 (flags & GE_IS_EXPORT) ? "EXPORT: " :
4265 (flags & GE_IS_IMPORT) ? "IMPORT: " : "SAVE: ");
4266 p1[sizeof(p1)-1] = '\0';
4267 l1 = strlen(p1);
4269 strncpy(p2, prompt_msg ? prompt_msg : "", sizeof(p2)-1);
4270 p2[sizeof(p2)-1] = '\0';
4271 l2 = strlen(p2);
4273 if(rflags && *rflags & GER_ALLPARTS)
4274 p3 = " (and atts)";
4275 else
4276 p3 = "";
4278 l3 = strlen(p3);
4280 snprintf(p4, sizeof(p4), " %s file%s%s",
4281 (flags & GE_IS_IMPORT) ? "from" : "to",
4282 is_absolute_path(filename) ? "" : " in ",
4283 is_absolute_path(filename) ? "" :
4284 (!dir[0] ? "current directory"
4285 : (dir[0] == '~' && !dir[1]) ? "home directory"
4286 : short_str(dir,dirb,sizeof(dirb),30,FrontDots)));
4287 p4[sizeof(p4)-1] = '\0';
4288 l4 = strlen(p4);
4290 snprintf(p5, sizeof(p5), "%s%s%s: ",
4291 *def ? " [" : "",
4292 *def ? short_str(def,fileb,sizeof(fileb),40,EndDots) : "",
4293 *def ? "]" : "");
4294 p5[sizeof(p5)-1] = '\0';
4295 l5 = strlen(p5);
4297 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4298 snprintf(p4, sizeof(p4), " %s file%s%s",
4299 (flags & GE_IS_IMPORT) ? "from" : "to",
4300 is_absolute_path(filename) ? "" : " in ",
4301 is_absolute_path(filename) ? "" :
4302 (!dir[0] ? "current dir"
4303 : (dir[0] == '~' && !dir[1]) ? "home dir"
4304 : short_str(dir,dirb,sizeof(dirb),10,FrontDots)));
4305 p4[sizeof(p4)-1] = '\0';
4306 l4 = strlen(p4);
4309 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4310 snprintf(p5, sizeof(p5), "%s%s%s: ",
4311 *def ? " [" : "",
4312 *def ? short_str(def,fileb,sizeof(fileb),
4313 MAX(15,l5-5-needed),EndDots) : "",
4314 *def ? "]" : "");
4315 p5[sizeof(p5)-1] = '\0';
4316 l5 = strlen(p5);
4319 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l2 > 0){
4322 * 14 is about the shortest we can make this, because there are
4323 * fixed length strings of length 14 coming in here.
4325 p = short_str(prompt_msg, p2, sizeof(p2), MAX(14,l2-needed), FrontDots);
4326 if(p != p2){
4327 strncpy(p2, p, sizeof(p2)-1);
4328 p2[sizeof(p2)-1] = '\0';
4331 l2 = strlen(p2);
4334 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4335 strncpy(p1, "Copy ", sizeof(p1)-1);
4336 p1[sizeof(p1)-1] = '\0';
4337 l1 = strlen(p1);
4340 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4341 snprintf(p5, sizeof(p5), "%s%s%s: ",
4342 *def ? " [" : "",
4343 *def ? short_str(def,fileb, sizeof(fileb),
4344 MAX(10,l5-5-needed),EndDots) : "",
4345 *def ? "]" : "");
4346 p5[sizeof(p5)-1] = '\0';
4347 l5 = strlen(p5);
4350 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l3 > 0){
4351 if(needed <= l3 - strlen(" (+ atts)"))
4352 p3 = " (+ atts)";
4353 else if(needed <= l3 - strlen(" (atts)"))
4354 p3 = " (atts)";
4355 else if(needed <= l3 - strlen(" (+)"))
4356 p3 = " (+)";
4357 else if(needed <= l3 - strlen("+"))
4358 p3 = "+";
4359 else
4360 p3 = "";
4362 l3 = strlen(p3);
4365 snprintf(prompt_buf, sizeof(prompt_buf), "%s%s%s%s%s", p1, p2, p3, p4, p5);
4366 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4368 if(ku >= 0){
4369 if(items_in_hist(*history) > 0){
4370 opts[ku].name = HISTORY_UP_KEYNAME;
4371 opts[ku].label = HISTORY_KEYLABEL;
4372 opts[ku+1].name = HISTORY_DOWN_KEYNAME;
4373 opts[ku+1].label = HISTORY_KEYLABEL;
4375 else{
4376 opts[ku].name = "";
4377 opts[ku].label = "";
4378 opts[ku+1].name = "";
4379 opts[ku+1].label = "";
4383 oeflags = OE_APPEND_CURRENT |
4384 ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0);
4385 r = optionally_enter(filename, qline, 0, len, prompt_buf,
4386 opts, NO_HELP, &oeflags);
4388 /*--- Help ----*/
4389 if(r == 3){
4391 * Helps may not be right if you add another caller or change
4392 * things. Check it out.
4394 if(flags & GE_IS_IMPORT)
4395 helper(h_ge_import, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE);
4396 else if(flags & GE_ALLPARTS)
4397 helper(h_ge_allparts, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4398 else
4399 helper(h_ge_export, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4401 ps->mangled_screen = 1;
4403 continue;
4405 else if(r == 10 || r == 11){ /* Browser or File Completion */
4406 if(filename[0]=='~'){
4407 if(filename[1] == C_FILESEP && filename[2]!='\0'){
4408 precolon[0] = '~';
4409 precolon[1] = '\0';
4410 for(i=0; filename[i+2] != '\0' && i+2 < len-1; i++)
4411 filename[i] = filename[i+2];
4412 filename[i] = '\0';
4413 strncpy(dir, precolon, sizeof(dir)-1);
4414 dir[sizeof(dir)-1] = '\0';
4416 else if(filename[1]=='\0' ||
4417 (filename[1] == C_FILESEP && filename[2] == '\0')){
4418 precolon[0] = '~';
4419 precolon[1] = '\0';
4420 filename[0] = '\0';
4421 strncpy(dir, precolon, sizeof(dir)-1);
4422 dir[sizeof(dir)-1] = '\0';
4425 else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){
4426 if(homedir){
4427 precolon[0] = '~';
4428 precolon[1] = '\0';
4429 strncpy(dir, precolon, sizeof(dir)-1);
4430 dir[sizeof(dir)-1] = '\0';
4432 else{
4433 precolon[0] = '\0';
4434 dir[0] = '\0';
4437 l = MAXPATH;
4438 dir2[0] = '\0';
4439 strncpy(tmp, filename, sizeof(tmp)-1);
4440 tmp[sizeof(tmp)-1] = '\0';
4441 if(*tmp && is_absolute_path(tmp))
4442 fnexpand(tmp, sizeof(tmp));
4443 if(strncmp(tmp,postcolon, strlen(postcolon)))
4444 postcolon[0] = '\0';
4446 if(*tmp && (fn = last_cmpnt(tmp))){
4447 l -= fn - tmp;
4448 strncpy(filename2, fn, sizeof(filename2)-1);
4449 filename2[sizeof(filename2)-1] = '\0';
4450 if(is_absolute_path(tmp)){
4451 strncpy(dir2, tmp, MIN(fn - tmp, sizeof(dir2)-1));
4452 dir2[MIN(fn - tmp, sizeof(dir2)-1)] = '\0';
4453 #ifdef _WINDOWS
4454 if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){
4455 dir2[2] = '\\';
4456 dir2[3] = '\0';
4458 #endif
4459 strncpy(postcolon, dir2, sizeof(postcolon)-1);
4460 postcolon[sizeof(postcolon)-1] = '\0';
4461 precolon[0] = '\0';
4463 else{
4464 char *p = NULL;
4466 * Just building the directory name in dir2,
4467 * full_filename is overloaded.
4469 snprintf(full_filename, len, "%.*s", MIN(fn-tmp,len-1), tmp);
4470 full_filename[len-1] = '\0';
4471 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4472 postcolon[sizeof(postcolon)-1] = '\0';
4473 build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH)
4474 : (dir[0] == '~' && !dir[1])
4475 ? ps->home_dir
4476 : dir,
4477 full_filename, sizeof(dir2));
4478 if(p)
4479 free(p);
4482 else{
4483 if(is_absolute_path(tmp)){
4484 strncpy(dir2, tmp, sizeof(dir2)-1);
4485 dir2[sizeof(dir2)-1] = '\0';
4486 #ifdef _WINDOWS
4487 if(dir2[2]=='\0' && dir2[1]==':'){
4488 dir2[2]='\\';
4489 dir2[3]='\0';
4490 strncpy(postcolon,dir2,sizeof(postcolon)-1);
4491 postcolon[sizeof(postcolon)-1] = '\0';
4493 #endif
4494 filename2[0] = '\0';
4495 precolon[0] = '\0';
4497 else{
4498 strncpy(filename2, tmp, sizeof(filename2)-1);
4499 filename2[sizeof(filename2)-1] = '\0';
4500 if(!dir[0])
4501 (void)getcwd(dir2, sizeof(dir2));
4502 else if(dir[0] == '~' && !dir[1]){
4503 strncpy(dir2, ps->home_dir, sizeof(dir2)-1);
4504 dir2[sizeof(dir2)-1] = '\0';
4506 else{
4507 strncpy(dir2, dir, sizeof(dir2)-1);
4508 dir2[sizeof(dir2)-1] = '\0';
4511 postcolon[0] = '\0';
4515 build_path(full_filename, dir2, filename2, len);
4516 if(!strcmp(full_filename, dir2))
4517 filename2[0] = '\0';
4518 if(full_filename[strlen(full_filename)-1] == C_FILESEP
4519 && isdir(full_filename,NULL,NULL)){
4520 if(strlen(full_filename) == 1)
4521 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4522 else if(filename2[0])
4523 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4524 postcolon[sizeof(postcolon)-1] = '\0';
4525 strncpy(dir2, full_filename, sizeof(dir2)-1);
4526 dir2[sizeof(dir2)-1] = '\0';
4527 filename2[0] = '\0';
4529 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4530 else if(full_filename[strlen(full_filename)-1] == C_FILESEP){
4531 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4532 postcolon[sizeof(postcolon)-1] = '\0';
4533 strncpy(dir2, full_filename, sizeof(dir2)-1);
4534 dir2[sizeof(dir2)-1] = '\0';
4535 filename2[0] = '\0';
4537 #endif
4538 if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1
4539 && strcmp(dir2+1, ":\\"))
4540 /* last condition to prevent stripping of '\\'
4541 in windows partition */
4542 dir2[strlen(dir2)-1] = '\0';
4544 if(r == 10){ /* File Browser */
4545 r = file_lister(lister_msg ? lister_msg : "EXPORT",
4546 dir2, sizeof(dir2), filename2, sizeof(filename2),
4547 TRUE,
4548 (flags & GE_IS_IMPORT) ? FB_READ : FB_SAVE);
4549 #ifdef _WINDOWS
4550 /* Windows has a special "feature" in which entering the file browser will
4551 change the working directory if the directory is changed at all (even
4552 clicking "Cancel" will change the working directory).
4554 if(F_ON(F_USE_CURRENT_DIR, ps))
4555 (void)getcwd(dir2,sizeof(dir2));
4556 #endif
4557 if(isdir(dir2,NULL,NULL)){
4558 strncpy(precolon, dir2, sizeof(precolon)-1);
4559 precolon[sizeof(precolon)-1] = '\0';
4561 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4562 postcolon[sizeof(postcolon)-1] = '\0';
4563 if(r == 1){
4564 build_path(full_filename, dir2, filename2, len);
4565 if(isdir(full_filename, NULL, NULL)){
4566 strncpy(dir, full_filename, sizeof(dir)-1);
4567 dir[sizeof(dir)-1] = '\0';
4568 filename[0] = '\0';
4570 else{
4571 fn = last_cmpnt(full_filename);
4572 strncpy(dir, full_filename,
4573 MIN(fn - full_filename, sizeof(dir)-1));
4574 dir[MIN(fn - full_filename, sizeof(dir)-1)] = '\0';
4575 if(fn - full_filename > 1)
4576 dir[fn - full_filename - 1] = '\0';
4579 if(!strcmp(dir, ps->home_dir)){
4580 dir[0] = '~';
4581 dir[1] = '\0';
4584 strncpy(filename, fn, len-1);
4585 filename[len-1] = '\0';
4588 else{ /* File Completion */
4589 if(!pico_fncomplete(dir2, filename2, sizeof(filename2)))
4590 Writechar(BELL, 0);
4591 strncat(postcolon, filename2,
4592 sizeof(postcolon)-1-strlen(postcolon));
4593 postcolon[sizeof(postcolon)-1] = '\0';
4595 was_abs_path = is_absolute_path(filename);
4597 if(!strcmp(dir, ps->home_dir)){
4598 dir[0] = '~';
4599 dir[1] = '\0';
4602 strncpy(filename, postcolon, len-1);
4603 filename[len-1] = '\0';
4604 strncpy(dir, precolon, sizeof(dir)-1);
4605 dir[sizeof(dir)-1] = '\0';
4607 if(filename[0] == '~' && !filename[1]){
4608 dir[0] = '~';
4609 dir[1] = '\0';
4610 filename[0] = '\0';
4613 continue;
4615 else if(r == 12){ /* Download, caller handles it */
4616 ret = r;
4617 goto done;
4619 else if(r == 13){ /* toggle AllParts bit */
4620 if(rflags){
4621 if(*rflags & GER_ALLPARTS){
4622 *rflags &= ~GER_ALLPARTS;
4623 opts[allparts].label = N_("AllParts");
4625 else{
4626 *rflags |= GER_ALLPARTS;
4627 /* opposite of All Parts, No All Parts */
4628 opts[allparts].label = N_("NoAllParts");
4632 continue;
4634 #if 0
4635 else if(r == 14){ /* List file names matching partial? */
4636 continue;
4638 #endif
4639 else if(r == 1){ /* Cancel */
4640 ret = -1;
4641 goto done;
4643 else if(r == 4){
4644 continue;
4646 else if(r == 30 || r == 31){
4647 char *p = NULL;
4649 if(history){
4650 if(r == 30)
4651 p = get_prev_hist(*history, filename, 0, NULL);
4652 else
4653 p = get_next_hist(*history, filename, 0, NULL);
4656 if(p != NULL){
4657 fn = last_cmpnt(p);
4658 strncpy(dir, p, MIN(fn - p, sizeof(dir)-1));
4659 dir[MIN(fn - p, sizeof(dir)-1)] = '\0';
4660 if(fn - p > 1)
4661 dir[fn - p - 1] = '\0';
4663 if(!strcmp(dir, ps->home_dir)){
4664 dir[0] = '~';
4665 dir[1] = '\0';
4668 strncpy(filename, fn, len-1);
4669 filename[len-1] = '\0';
4671 else
4672 Writechar(BELL, 0);
4674 continue;
4676 else if(r != 0){
4677 Writechar(BELL, 0);
4678 continue;
4681 removing_leading_and_trailing_white_space(filename);
4683 if(!*filename){
4684 if(!*def){ /* Cancel */
4685 ret = -1;
4686 goto done;
4689 strncpy(filename, def, len-1);
4690 filename[len-1] = '\0';
4693 #if defined(DOS) || defined(OS2)
4694 if(is_absolute_path(filename)){
4695 fixpath(filename, len);
4697 #else
4698 if(filename[0] == '~'){
4699 if(fnexpand(filename, len) == NULL){
4700 char *p = strindex(filename, '/');
4701 if(p != NULL)
4702 *p = '\0';
4703 q_status_message1(SM_ORDER | SM_DING, 3, 3,
4704 _("Error expanding file name: \"%s\" unknown user"),
4705 filename);
4706 continue;
4709 #endif
4711 if(is_absolute_path(filename)){
4712 strncpy(full_filename, filename, len-1);
4713 full_filename[len-1] = '\0';
4715 else{
4716 if(!dir[0])
4717 build_path(full_filename, (char *)getcwd(dir,sizeof(dir)),
4718 filename, len);
4719 else if(dir[0] == '~' && !dir[1])
4720 build_path(full_filename, ps->home_dir, filename, len);
4721 else
4722 build_path(full_filename, dir, filename, len);
4725 if((ill = filter_filename(full_filename, &fatal,
4726 ps_global->restricted || ps_global->VAR_OPER_DIR)) != NULL){
4727 if(fatal){
4728 q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill);
4729 continue;
4731 else{
4732 /* BUG: we should beep when the key's pressed rather than bitch later */
4733 /* Warn and ask for confirmation. */
4734 snprintf(prompt_buf, sizeof(prompt_buf), "File name contains a '%s'. %s anyway",
4735 ill, (flags & GE_IS_EXPORT) ? "Export" : "Save");
4736 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4737 if(want_to(prompt_buf, 'n', 0, NO_HELP,
4738 ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y')
4739 continue;
4743 break; /* Must have got an OK file name */
4746 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){
4747 ret = -2;
4748 goto done;
4751 if(!can_access(full_filename, ACCESS_EXISTS)){
4752 int rbflags;
4753 static ESCKEY_S access_opts[] = {
4754 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4755 a file or append to the end of the file */
4756 {'o', 'o', "O", N_("Overwrite")},
4757 {'a', 'a', "A", N_("Append")},
4758 {-1, 0, NULL, NULL}};
4760 rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0);
4762 if(flags & GE_NO_APPEND){
4763 r = strlen(filename);
4764 snprintf(prompt_buf, sizeof(prompt_buf),
4765 /* TRANSLATORS: asking user whether to overwrite a file or not,
4766 File <filename> already exists. Overwrite it ? */
4767 _("File \"%s%s\" already exists. Overwrite it "),
4768 (r > 20) ? "..." : "",
4769 filename + ((r > 20) ? r - 20 : 0));
4770 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4771 if(want_to(prompt_buf, 'n', 'x', NO_HELP, rbflags) == 'y'){
4772 if(rflags)
4773 *rflags |= GER_OVER;
4775 if(our_unlink(full_filename) < 0){
4776 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4777 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4778 _("Cannot remove old %s: %s"),
4779 full_filename, error_description(errno));
4782 else{
4783 ret = -1;
4784 goto done;
4787 else if(!(flags & GE_IS_IMPORT)){
4788 r = strlen(filename);
4789 snprintf(prompt_buf, sizeof(prompt_buf),
4790 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4791 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4792 (r > 20) ? "..." : "",
4793 filename + ((r > 20) ? r - 20 : 0));
4794 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4795 switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global),
4796 access_opts, 'a', 'x', NO_HELP, rbflags)){
4797 case 'o' :
4798 if(rflags)
4799 *rflags |= GER_OVER;
4801 if(our_truncate(full_filename, (off_t)0) < 0)
4802 /* trouble truncating, but we'll give it a try anyway */
4803 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4804 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
4805 _("Warning: Cannot truncate old %s: %s"),
4806 full_filename, error_description(errno));
4807 break;
4809 case 'a' :
4810 if(rflags)
4811 *rflags |= GER_APPEND;
4813 break;
4815 case 'x' :
4816 default :
4817 ret = -1;
4818 goto done;
4823 done:
4824 if(history && ret == 0)
4825 save_hist(*history, full_filename, 0, NULL);
4827 if(opts && opts != optsarg)
4828 fs_give((void **) &opts);
4830 return(ret);
4834 /*----------------------------------------------------------------------
4835 parse the config'd upload/download command
4837 Args: cmd -- buffer to return command fit for shellin'
4838 prefix --
4839 cfg_str --
4840 fname -- file name to build into the command
4842 Returns: pointer to cmd_str buffer or NULL on real bad error
4844 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
4845 cfg_str is written to standard out right before a successful
4846 return of this function. The call immediately following this
4847 function darn well better be the shell exec...
4848 ----*/
4849 char *
4850 build_updown_cmd(char *cmd, size_t cmdlen, char *prefix, char *cfg_str, char *fname)
4852 char *p;
4853 int fname_found = 0;
4855 if(prefix && *prefix){
4856 /* loop thru replacing all occurances of _FILE_ */
4857 p = strncpy(cmd, prefix, cmdlen);
4858 cmd[cmdlen-1] = '\0';
4859 while((p = strstr(p, "_FILE_")))
4860 rplstr(p, cmdlen-(p-cmd), 6, fname);
4862 fputs(cmd, stdout);
4865 /* loop thru replacing all occurances of _FILE_ */
4866 p = strncpy(cmd, cfg_str, cmdlen);
4867 cmd[cmdlen-1] = '\0';
4868 while((p = strstr(p, "_FILE_"))){
4869 rplstr(p, cmdlen-(p-cmd), 6, fname);
4870 fname_found = 1;
4873 if(!fname_found)
4874 snprintf(cmd+strlen(cmd), cmdlen-strlen(cmd), " %s", fname);
4876 cmd[cmdlen-1] = '\0';
4878 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
4879 cmd ? cmd : "?"));
4880 return(cmd);
4884 /*----------------------------------------------------------------------
4885 Write a berzerk format message delimiter using the given putc function
4887 Args: e -- envelope of message to write
4888 pc -- function to use
4890 Returns: TRUE if we could write it, FALSE if there was a problem
4892 NOTE: follows delimiter with OS-dependent newline
4893 ----*/
4895 bezerk_delimiter(ENVELOPE *env, MESSAGECACHE *mc, gf_io_t pc, int leading_newline)
4897 MESSAGECACHE telt;
4898 time_t when;
4899 char *p;
4901 /* write "[\n]From mailbox[@host] " */
4902 if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1)
4903 && gf_puts("From ", pc)
4904 && gf_puts((env && env->from) ? env->from->mailbox
4905 : "the-concourse-on-high", pc)
4906 && gf_puts((env && env->from && env->from->host) ? "@" : "", pc)
4907 && gf_puts((env && env->from && env->from->host) ? env->from->host
4908 : "", pc)
4909 && (*pc)(' ')))
4910 return(0);
4912 if(mc && mc->valid)
4913 when = mail_longdate(mc);
4914 else if(env && env->date && env->date[0]
4915 && mail_parse_date(&telt,env->date))
4916 when = mail_longdate(&telt);
4917 else
4918 when = time(0);
4920 p = ctime(&when);
4922 while(p && *p && *p != '\n') /* write date */
4923 if(!(*pc)(*p++))
4924 return(0);
4926 if(!gf_puts(NEWLINE, pc)) /* write terminating newline */
4927 return(0);
4929 return(1);
4933 /*----------------------------------------------------------------------
4934 Execute command to jump to a given message number
4936 Args: qline -- Line to ask question on
4938 Result: returns true if the use selected a new message, false otherwise
4940 ----*/
4941 long
4942 jump_to(MSGNO_S *msgmap, int qline, UCS first_num, SCROLL_S *sparms, CmdWhere in_index)
4944 char jump_num_string[80], *j, prompt[70];
4945 HelpType help;
4946 int rc;
4947 static ESCKEY_S jump_to_key[] = { {0, 0, NULL, NULL},
4948 /* TRANSLATORS: go to First Message */
4949 {ctrl('Y'), 10, "^Y", N_("First Msg")},
4950 {ctrl('V'), 11, "^V", N_("Last Msg")},
4951 {-1, 0, NULL, NULL} };
4953 dprint((4, "\n - jump_to -\n"));
4955 #ifdef DEBUG
4956 if(sparms && sparms->jump_is_debug)
4957 return(get_level(qline, first_num, sparms));
4958 #endif
4960 if(!any_messages(msgmap, NULL, "to Jump to"))
4961 return(0L);
4963 if(first_num && first_num < 0x80 && isdigit((unsigned char) first_num)){
4964 jump_num_string[0] = first_num;
4965 jump_num_string[1] = '\0';
4967 else
4968 jump_num_string[0] = '\0';
4970 if(mn_total_cur(msgmap) > 1L){
4971 snprintf(prompt, sizeof(prompt), "Unselect %s msgs in favor of number to be entered",
4972 comatose(mn_total_cur(msgmap)));
4973 prompt[sizeof(prompt)-1] = '\0';
4974 if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n')
4975 return(0L);
4978 snprintf(prompt, sizeof(prompt), "%s number to jump to : ", in_index == ThrdIndx
4979 ? "Thread"
4980 : "Message");
4981 prompt[sizeof(prompt)-1] = '\0';
4983 help = NO_HELP;
4984 while(1){
4985 int flags = OE_APPEND_CURRENT;
4987 rc = optionally_enter(jump_num_string, qline, 0,
4988 sizeof(jump_num_string), prompt,
4989 jump_to_key, help, &flags);
4990 if(rc == 3){
4991 help = help == NO_HELP
4992 ? (in_index == ThrdIndx ? h_oe_jump_thd : h_oe_jump)
4993 : NO_HELP;
4994 continue;
4996 else if(rc == 10 || rc == 11){
4997 char warning[100];
4998 long closest;
5000 closest = closest_jump_target(rc == 10 ? 1L
5001 : ((in_index == ThrdIndx)
5002 ? msgmap->max_thrdno
5003 : mn_get_total(msgmap)),
5004 ps_global->mail_stream,
5005 msgmap, 0,
5006 in_index, warning, sizeof(warning));
5007 /* ignore warning */
5008 return(closest);
5012 * If we take out the *jump_num_string nonempty test in this if
5013 * then the closest_jump_target routine will offer a jump to the
5014 * last message. However, it is slow because you have to wait for
5015 * the status message and it is annoying for people who hit J command
5016 * by mistake and just want to hit return to do nothing, like has
5017 * always worked. So the test is there for now. Hubert 2002-08-19
5019 * Jumping to first/last message is now possible through ^Y/^V
5020 * commands above. jpf 2002-08-21
5021 * (and through "end" hubert 2006-07-07)
5023 if(rc == 0 && *jump_num_string != '\0'){
5024 removing_leading_and_trailing_white_space(jump_num_string);
5025 for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++)
5028 if(*j != '\0'){
5029 if(!strucmp("end", j))
5030 return((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap));
5032 q_status_message(SM_ORDER | SM_DING, 2, 2,
5033 _("Invalid number entered. Use only digits 0-9"));
5034 jump_num_string[0] = '\0';
5036 else{
5037 char warning[100];
5038 long closest, jump_num;
5040 if(*jump_num_string)
5041 jump_num = atol(jump_num_string);
5042 else
5043 jump_num = -1L;
5045 warning[0] = '\0';
5046 closest = closest_jump_target(jump_num, ps_global->mail_stream,
5047 msgmap,
5048 *jump_num_string ? 0 : 1,
5049 in_index, warning, sizeof(warning));
5050 if(warning[0])
5051 q_status_message(SM_ORDER | SM_DING, 2, 2, warning);
5053 if(closest == jump_num)
5054 return(jump_num);
5056 if(closest == 0L)
5057 jump_num_string[0] = '\0';
5058 else
5059 strncpy(jump_num_string, long2string(closest),
5060 sizeof(jump_num_string));
5063 continue;
5066 if(rc != 4)
5067 break;
5070 return(0L);
5075 * cmd_delete_action - handle msgno advance and such after single message deletion
5077 char *
5078 cmd_delete_action(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
5080 int opts;
5081 long msgno;
5082 char *rv = NULL;
5084 msgno = mn_get_cur(msgmap);
5085 advance_cur_after_delete(state, state->mail_stream, msgmap, in_index);
5087 if(IS_NEWS(state->mail_stream)
5088 || ((state->context_current->use & CNTXT_INCMNG)
5089 && context_isambig(state->cur_folder))){
5091 opts = (NSF_TRUST_FLAGS | NSF_SKIP_CHID);
5092 if(in_index == View)
5093 opts &= ~NSF_SKIP_CHID;
5095 (void)next_sorted_flagged(F_UNDEL|F_UNSEEN, state->mail_stream, msgno, &opts);
5096 if(!(opts & NSF_FLAG_MATCH)){
5097 char nextfolder[MAXPATH];
5099 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
5100 nextfolder[sizeof(nextfolder)-1] = '\0';
5101 rv = next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
5102 state->context_current, NULL, NULL)
5103 ? ". Press TAB for next folder."
5104 : ". No more folders to TAB to.";
5108 return(rv);
5113 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5115 char *
5116 cmd_delete_index(struct pine *state, MSGNO_S *msgmap)
5118 return(cmd_delete_action(state, msgmap,MsgIndx));
5122 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5124 char *
5125 cmd_delete_view(struct pine *state, MSGNO_S *msgmap)
5127 return(cmd_delete_action(state, msgmap, View));
5131 void
5132 advance_cur_after_delete(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, CmdWhere in_index)
5134 long new_msgno, msgno;
5135 int opts;
5137 new_msgno = msgno = mn_get_cur(msgmap);
5138 opts = NSF_TRUST_FLAGS;
5140 if(F_ON(F_DEL_SKIPS_DEL, state)){
5142 if(THREADING() && sp_viewing_a_thread(stream))
5143 opts |= NSF_SKIP_CHID;
5145 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5147 else{
5148 mn_inc_cur(stream, msgmap,
5149 (in_index == View && THREADING()
5150 && sp_viewing_a_thread(stream))
5151 ? MH_THISTHD
5152 : (in_index == View)
5153 ? MH_ANYTHD : MH_NONE);
5154 new_msgno = mn_get_cur(msgmap);
5155 if(new_msgno != msgno)
5156 opts |= NSF_FLAG_MATCH;
5160 * Viewing_a_thread is the complicated case because we want to ignore
5161 * other threads at first and then look in other threads if we have to.
5162 * By ignoring other threads we also ignore collapsed partial threads
5163 * in our own thread.
5165 if(THREADING() && sp_viewing_a_thread(stream) && !(opts & NSF_FLAG_MATCH)){
5166 long rawno, orig_thrdno;
5167 PINETHRD_S *thrd, *topthrd = NULL;
5169 rawno = mn_m2raw(msgmap, msgno);
5170 thrd = fetch_thread(stream, rawno);
5171 if(thrd && thrd->top)
5172 topthrd = fetch_thread(stream, thrd->top);
5174 orig_thrdno = topthrd ? topthrd->thrdno : -1L;
5176 opts = NSF_TRUST_FLAGS;
5177 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5180 * If we got a match, new_msgno may be a message in
5181 * a different thread from the one we are viewing, or it could be
5182 * in a collapsed part of this thread.
5184 if(opts & NSF_FLAG_MATCH){
5185 int ret;
5186 char pmt[128];
5188 topthrd = NULL;
5189 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
5190 if(thrd && thrd->top)
5191 topthrd = fetch_thread(stream, thrd->top);
5194 * If this match is in the same thread we're already in
5195 * then we're done, else we have to ask the user and maybe
5196 * switch threads.
5198 if(!(orig_thrdno > 0L && topthrd
5199 && topthrd->thrdno == orig_thrdno)){
5201 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
5202 if(in_index == View)
5203 snprintf(pmt, sizeof(pmt),
5204 "View message in thread number %.10s",
5205 topthrd ? comatose(topthrd->thrdno) : "?");
5206 else
5207 snprintf(pmt, sizeof(pmt), "View thread number %.10s",
5208 topthrd ? comatose(topthrd->thrdno) : "?");
5210 ret = want_to(pmt, 'y', 'x', NO_HELP, WT_NORM);
5212 else
5213 ret = 'y';
5215 if(ret == 'y'){
5216 unview_thread(state, stream, msgmap);
5217 mn_set_cur(msgmap, new_msgno);
5218 if(THRD_AUTO_VIEW()
5219 && (count_lflags_in_thread(stream, topthrd, msgmap,
5220 MN_NONE) == 1)
5221 && view_thread(state, stream, msgmap, 1)){
5222 if(current_index_state)
5223 msgmap->top_after_thrd = current_index_state->msg_at_top;
5225 state->view_skipped_index = 1;
5226 state->next_screen = mail_view_screen;
5228 else{
5229 view_thread(state, stream, msgmap, 1);
5230 if(current_index_state)
5231 msgmap->top_after_thrd = current_index_state->msg_at_top;
5233 state->next_screen = SCREEN_FUN_NULL;
5236 else
5237 new_msgno = msgno; /* stick with original */
5242 mn_set_cur(msgmap, new_msgno);
5243 if(in_index != View)
5244 adjust_cur_to_visible(stream, msgmap);
5248 #ifdef DEBUG
5249 long
5250 get_level(int qline, UCS first_num, SCROLL_S *sparms)
5252 char debug_num_string[80], *j, prompt[70];
5253 HelpType help;
5254 int rc;
5255 long debug_num;
5257 if(first_num && first_num < 0x80 && isdigit((unsigned char)first_num)){
5258 debug_num_string[0] = first_num;
5259 debug_num_string[1] = '\0';
5260 debug_num = atol(debug_num_string);
5261 *(int *)(sparms->proc.data.p) = debug_num;
5262 q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s",
5263 comatose(debug_num));
5264 return(1L);
5266 else
5267 debug_num_string[0] = '\0';
5269 snprintf(prompt, sizeof(prompt), "Show debug <= this level (0-%d) : ", MAX(debug, 9));
5270 prompt[sizeof(prompt)-1] = '\0';
5272 help = NO_HELP;
5273 while(1){
5274 int flags = OE_APPEND_CURRENT;
5276 rc = optionally_enter(debug_num_string, qline, 0,
5277 sizeof(debug_num_string), prompt,
5278 NULL, help, &flags);
5279 if(rc == 3){
5280 help = help == NO_HELP ? h_oe_debuglevel : NO_HELP;
5281 continue;
5284 if(rc == 0){
5285 removing_leading_and_trailing_white_space(debug_num_string);
5286 for(j=debug_num_string; isdigit((unsigned char)*j); j++)
5289 if(*j != '\0'){
5290 q_status_message(SM_ORDER | SM_DING, 2, 2,
5291 _("Invalid number entered. Use only digits 0-9"));
5292 debug_num_string[0] = '\0';
5294 else{
5295 debug_num = atol(debug_num_string);
5296 if(debug_num < 0)
5297 q_status_message(SM_ORDER | SM_DING, 2, 2,
5298 _("Number should be >= 0"));
5299 else if(debug_num > MAX(debug,9))
5300 q_status_message1(SM_ORDER | SM_DING, 2, 2,
5301 _("Maximum is %s"), comatose(MAX(debug,9)));
5302 else{
5303 *(int *)(sparms->proc.data.p) = debug_num;
5304 q_status_message1(SM_ORDER, 0, 3,
5305 "Show debug <= level %s",
5306 comatose(debug_num));
5307 return(1L);
5311 continue;
5314 if(rc != 4)
5315 break;
5318 return(0L);
5320 #endif /* DEBUG */
5324 * Returns the message number closest to target that isn't hidden.
5325 * Make warning at least 100 chars.
5326 * A return of 0 means there is no message to jump to.
5328 long
5329 closest_jump_target(long int target, MAILSTREAM *stream, MSGNO_S *msgmap, int no_target, CmdWhere in_index, char *warning, size_t warninglen)
5331 long i, start, closest = 0L;
5332 char buf[80];
5333 long maxnum;
5335 warning[0] = '\0';
5336 maxnum = (in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap);
5338 if(no_target){
5339 target = maxnum;
5340 start = 1L;
5341 snprintf(warning, warninglen, "No %s number entered, jump to end? ",
5342 (in_index == ThrdIndx) ? "thread" : "message");
5343 warning[warninglen-1] = '\0';
5345 else if(target < 1L)
5346 start = 1L - target;
5347 else if(target > maxnum)
5348 start = target - maxnum;
5349 else
5350 start = 1L;
5352 if(target > 0L && target <= maxnum)
5353 if(in_index == ThrdIndx
5354 || !msgline_hidden(stream, msgmap, target, 0))
5355 return(target);
5357 for(i = start; target+i <= maxnum || target-i > 0L; i++){
5359 if(target+i > 0L && target+i <= maxnum &&
5360 (in_index == ThrdIndx
5361 || !msgline_hidden(stream, msgmap, target+i, 0))){
5362 closest = target+i;
5363 break;
5366 if(target-i > 0L && target-i <= maxnum &&
5367 (in_index == ThrdIndx
5368 || !msgline_hidden(stream, msgmap, target-i, 0))){
5369 closest = target-i;
5370 break;
5374 strncpy(buf, long2string(closest), sizeof(buf));
5375 buf[sizeof(buf)-1] = '\0';
5377 if(closest == 0L)
5378 strncpy(warning, "Nothing to jump to", warninglen);
5379 else if(target < 1L)
5380 snprintf(warning, warninglen, "%s number (%s) must be at least %s",
5381 (in_index == ThrdIndx) ? "Thread" : "Message",
5382 long2string(target), buf);
5383 else if(target > maxnum)
5384 snprintf(warning, warninglen, "%s number (%s) may be no more than %s",
5385 (in_index == ThrdIndx) ? "Thread" : "Message",
5386 long2string(target), buf);
5387 else if(!no_target)
5388 snprintf(warning, warninglen,
5389 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5390 long2string(target), buf);
5392 warning[warninglen-1] = '\0';
5394 return(closest);
5398 /*----------------------------------------------------------------------
5399 Prompt for folder name to open, expand the name and return it
5401 Args: qline -- Screen line to prompt on
5402 allow_list -- if 1, allow ^T to bring up collection lister
5404 Result: returns the folder name or NULL
5405 pine structure mangled_footer flag is set
5406 may call the collection lister in which case mangled screen will be set
5408 This prompts the user for the folder to open, possibly calling up
5409 the collection lister if the user types ^T.
5410 ----------------------------------------------------------------------*/
5411 char *
5412 broach_folder(int qline, int allow_list, int *notrealinbox, CONTEXT_S **context)
5414 HelpType help;
5415 static char newfolder[MAILTMPLEN];
5416 char expanded[MAXPATH+1],
5417 prompt[MAX_SCREEN_COLS+1],
5418 *last_folder, *p;
5419 unsigned char *f1, *f2, *f3;
5420 static HISTORY_S *history = NULL;
5421 CONTEXT_S *tc, *tc2;
5422 ESCKEY_S ekey[9];
5423 int rc, r, ku = -1, n, flags, last_rc = 0, inbox, done = 0;
5426 * the idea is to provide a clue for the context the file name
5427 * will be saved in (if a non-imap names is typed), and to
5428 * only show the previous if it was also in the same context
5430 help = NO_HELP;
5431 *expanded = '\0';
5432 *newfolder = '\0';
5433 last_folder = NULL;
5434 if(notrealinbox)
5435 (*notrealinbox) = 1;
5437 init_hist(&history, HISTSIZE);
5439 tc = broach_get_folder(context ? *context : NULL, &inbox, NULL);
5441 /* set up extra command option keys */
5442 rc = 0;
5443 ekey[rc].ch = (allow_list) ? ctrl('T') : 0 ;
5444 ekey[rc].rval = (allow_list) ? 2 : 0;
5445 ekey[rc].name = (allow_list) ? "^T" : "";
5446 ekey[rc++].label = (allow_list) ? N_("ToFldrs") : "";
5448 if(ps_global->context_list->next){
5449 ekey[rc].ch = ctrl('P');
5450 ekey[rc].rval = 10;
5451 ekey[rc].name = "^P";
5452 ekey[rc++].label = N_("Prev Collection");
5454 ekey[rc].ch = ctrl('N');
5455 ekey[rc].rval = 11;
5456 ekey[rc].name = "^N";
5457 ekey[rc++].label = N_("Next Collection");
5460 ekey[rc].ch = ctrl('W');
5461 ekey[rc].rval = 17;
5462 ekey[rc].name = "^W";
5463 ekey[rc++].label = N_("INBOX");
5465 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
5466 ekey[rc].ch = TAB;
5467 ekey[rc].rval = 12;
5468 ekey[rc].name = "TAB";
5469 ekey[rc++].label = N_("Complete");
5472 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
5473 ekey[rc].ch = ctrl('X');
5474 ekey[rc].rval = 14;
5475 ekey[rc].name = "^X";
5476 ekey[rc++].label = N_("ListMatches");
5479 if(ps_global->context_list->next && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5480 ekey[rc].ch = KEY_UP;
5481 ekey[rc].rval = 10;
5482 ekey[rc].name = "";
5483 ekey[rc++].label = "";
5485 ekey[rc].ch = KEY_DOWN;
5486 ekey[rc].rval = 11;
5487 ekey[rc].name = "";
5488 ekey[rc++].label = "";
5490 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5491 ekey[rc].ch = KEY_UP;
5492 ekey[rc].rval = 30;
5493 ekey[rc].name = "";
5494 ku = rc;
5495 ekey[rc++].label = "";
5497 ekey[rc].ch = KEY_DOWN;
5498 ekey[rc].rval = 31;
5499 ekey[rc].name = "";
5500 ekey[rc++].label = "";
5503 ekey[rc].ch = -1;
5505 while(!done) {
5507 * Figure out next default value for this context. The idea
5508 * is that in each context the last folder opened is cached.
5509 * It's up to pick it out and display it. This is fine
5510 * and dandy if we've currently got the inbox open, BUT
5511 * if not, make the inbox the default the first time thru.
5513 if(!inbox){
5514 last_folder = ps_global->inbox_name;
5515 inbox = 1; /* pretend we're in inbox from here on out */
5517 else
5518 last_folder = (ps_global->last_unambig_folder[0])
5519 ? ps_global->last_unambig_folder
5520 : ((tc->last_folder[0]) ? tc->last_folder : NULL);
5522 if(last_folder){
5523 unsigned char *fname = folder_name_decoded((unsigned char *)last_folder);
5524 snprintf(expanded, sizeof(expanded), " [%.*s]", sizeof(expanded)-5,
5525 fname ? (char *) fname : last_folder);
5526 if(fname) fs_give((void **)&fname);
5528 else
5529 *expanded = '\0';
5531 expanded[sizeof(expanded)-1] = '\0';
5533 /* only show collection number if more than one available */
5534 if(ps_global->context_list->next)
5535 snprintf(prompt, sizeof(prompt), "GOTO %s in <%s> %.*s%s: ",
5536 NEWS_TEST(tc) ? "news group" : "folder",
5537 tc->nickname, sizeof(prompt)-50, expanded,
5538 *expanded ? " " : "");
5539 else
5540 snprintf(prompt, sizeof(prompt), "GOTO folder %.*s%s: ", sizeof(prompt)-20, expanded,
5541 *expanded ? " " : "");
5543 prompt[sizeof(prompt)-1] = '\0';
5545 if(utf8_width(prompt) > MAXPROMPT){
5546 if(ps_global->context_list->next)
5547 snprintf(prompt, sizeof(prompt), "GOTO <%s> %.*s%s: ",
5548 tc->nickname, sizeof(prompt)-50, expanded,
5549 *expanded ? " " : "");
5550 else
5551 snprintf(prompt, sizeof(prompt), "GOTO %.*s%s: ", sizeof(prompt)-20, expanded,
5552 *expanded ? " " : "");
5554 prompt[sizeof(prompt)-1] = '\0';
5556 if(utf8_width(prompt) > MAXPROMPT){
5557 if(ps_global->context_list->next)
5558 snprintf(prompt, sizeof(prompt), "<%s> %.*s%s: ",
5559 tc->nickname, sizeof(prompt)-50, expanded,
5560 *expanded ? " " : "");
5561 else
5562 snprintf(prompt, sizeof(prompt), "%.*s%s: ", sizeof(prompt)-20, expanded,
5563 *expanded ? " " : "");
5565 prompt[sizeof(prompt)-1] = '\0';
5569 if(ku >= 0){
5570 if(items_in_hist(history) > 1){
5571 ekey[ku].name = HISTORY_UP_KEYNAME;
5572 ekey[ku].label = HISTORY_KEYLABEL;
5573 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
5574 ekey[ku+1].label = HISTORY_KEYLABEL;
5576 else{
5577 ekey[ku].name = "";
5578 ekey[ku].label = "";
5579 ekey[ku+1].name = "";
5580 ekey[ku+1].label = "";
5584 /* is there any other way to do this? The point is that we
5585 * are trying to hide mutf7 from the user, and use the utf8
5586 * equivalent. So we create a variable f to take place of
5587 * newfolder, including content and size. f2 is copy of f1
5588 * that has to freed. Sigh!
5590 f3 = (unsigned char *) cpystr(newfolder);
5591 f1 = fs_get(sizeof(newfolder));
5592 f2 = folder_name_decoded(f3);
5593 if(f3) fs_give((void **)&f3);
5594 strncpy((char *)f1, (char *)f2, sizeof(newfolder));
5595 f1[sizeof(newfolder)-1] = '\0';
5596 if(f2) fs_give((void **)&f2);
5598 flags = OE_APPEND_CURRENT;
5599 rc = optionally_enter(f1, qline, 0, sizeof(newfolder),
5600 (char *) prompt, ekey, help, &flags);
5602 f2 = folder_name_encoded(f1);
5603 strncpy(newfolder, (char *)f2, sizeof(newfolder));
5604 if(f1) fs_give((void **)&f1);
5605 if(f2) fs_give((void **)&f2);
5607 ps_global->mangled_footer = 1;
5609 switch(rc){
5610 case -1 : /* o_e says error! */
5611 q_status_message(SM_ORDER | SM_DING, 3, 3,
5612 _("Error reading folder name"));
5613 return(NULL);
5615 case 0 : /* o_e says normal entry */
5616 removing_trailing_white_space(newfolder);
5617 removing_leading_white_space(newfolder);
5619 if(*newfolder){
5620 char *name, *fullname = NULL;
5621 int exists, breakout = 0;
5623 save_hist(history, newfolder, 0, tc);
5625 if(!(name = folder_is_nick(newfolder, FOLDERS(tc),
5626 FN_WHOLE_NAME)))
5627 name = newfolder;
5629 if(update_folder_spec(expanded, sizeof(expanded), name)){
5630 strncpy(name = newfolder, expanded, sizeof(newfolder));
5631 newfolder[sizeof(newfolder)-1] = '\0';
5634 exists = folder_name_exists(tc, name, &fullname);
5636 if(fullname){
5637 strncpy(name = newfolder, fullname, sizeof(newfolder));
5638 newfolder[sizeof(newfolder)-1] = '\0';
5639 fs_give((void **) &fullname);
5640 breakout = TRUE;
5644 * if we know the things a folder, open it.
5645 * else if we know its a directory, visit it.
5646 * else we're not sure (it either doesn't really
5647 * exist or its unLISTable) so try opening it anyway
5649 if(exists & FEX_ISFILE){
5650 done++;
5651 break;
5653 else if((exists & FEX_ISDIR)){
5654 if(breakout){
5655 CONTEXT_S *fake_context;
5656 char tmp[MAILTMPLEN];
5657 size_t l;
5659 strncpy(tmp, name, sizeof(tmp));
5660 tmp[sizeof(tmp)-2-1] = '\0';
5661 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
5662 if(l < sizeof(tmp)){
5663 tmp[l] = tc->dir->delim;
5664 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
5667 else
5668 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
5670 tmp[sizeof(tmp)-1] = '\0';
5672 fake_context = new_context(tmp, 0);
5673 newfolder[0] = '\0';
5674 done = display_folder_list(&fake_context, newfolder,
5675 1, folders_for_goto);
5676 free_context(&fake_context);
5677 break;
5679 else if(!(tc->use & CNTXT_INCMNG)){
5680 done = display_folder_list(&tc, newfolder,
5681 1, folders_for_goto);
5682 break;
5685 else if((exists & FEX_ERROR)){
5686 q_status_message1(SM_ORDER, 0, 3,
5687 _("Problem accessing folder \"%s\""),
5688 newfolder);
5689 return(NULL);
5691 else{
5692 done++;
5693 break;
5696 if(exists == FEX_ERROR)
5697 q_status_message1(SM_ORDER, 0, 3,
5698 _("Problem accessing folder \"%s\""),
5699 newfolder);
5700 else if(tc->use & CNTXT_INCMNG)
5701 q_status_message1(SM_ORDER, 0, 3,
5702 _("Can't find Incoming Folder: %s"),
5703 newfolder);
5704 else if(context_isambig(newfolder))
5705 q_status_message2(SM_ORDER, 0, 3,
5706 _("Can't find folder \"%s\" in %s"),
5707 newfolder, (void *) tc->nickname);
5708 else
5709 q_status_message1(SM_ORDER, 0, 3,
5710 _("Can't find folder \"%s\""),
5711 newfolder);
5713 return(NULL);
5715 else if(last_folder){
5716 if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX
5717 && !strucmp(last_folder, ps_global->inbox_name)
5718 && tc == ((ps_global->context_list->use & CNTXT_INCMNG)
5719 ? ps_global->context_list->next : ps_global->context_list)){
5720 if(notrealinbox)
5721 (*notrealinbox) = 0;
5723 tc = ps_global->context_list;
5726 strncpy(newfolder, last_folder, sizeof(newfolder));
5727 newfolder[sizeof(newfolder)-1] = '\0';
5728 save_hist(history, newfolder, 0, tc);
5729 done++;
5730 break;
5732 /* fall thru like they cancelled */
5734 case 1 : /* o_e says user cancel */
5735 cmd_cancelled("Open folder");
5736 return(NULL);
5738 case 2 : /* o_e says user wants list */
5739 r = display_folder_list(&tc, newfolder, 0, folders_for_goto);
5740 if(r)
5741 done++;
5743 break;
5745 case 3 : /* o_e says user wants help */
5746 help = help == NO_HELP ? h_oe_broach : NO_HELP;
5747 break;
5749 case 4 : /* redraw */
5750 break;
5752 case 10 : /* Previous collection */
5753 tc2 = ps_global->context_list;
5754 while(tc2->next && tc2->next != tc)
5755 tc2 = tc2->next;
5757 tc = tc2;
5758 break;
5760 case 11 : /* Next collection */
5761 tc = (tc->next) ? tc->next : ps_global->context_list;
5762 break;
5764 case 12 : /* file name completion */
5765 if(!folder_complete(tc, newfolder, sizeof(newfolder), &n)){
5766 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
5767 r = display_folder_list(&tc, newfolder, 1,folders_for_goto);
5768 if(r)
5769 done++; /* bingo! */
5770 else
5771 rc = 0; /* burn last_rc */
5773 else
5774 Writechar(BELL, 0);
5777 break;
5779 case 14 : /* file name completion */
5780 r = display_folder_list(&tc, newfolder, 2, folders_for_goto);
5781 if(r)
5782 done++; /* bingo! */
5783 else
5784 rc = 0; /* burn last_rc */
5786 break;
5788 case 17 : /* GoTo INBOX */
5789 done++;
5790 strncpy(newfolder, ps_global->inbox_name, sizeof(newfolder)-1);
5791 newfolder[sizeof(newfolder)-1] = '\0';
5792 if(notrealinbox)
5793 (*notrealinbox) = 0;
5795 tc = ps_global->context_list;
5796 save_hist(history, newfolder, 0, tc);
5798 break;
5800 case 30 :
5801 if((p = get_prev_hist(history, newfolder, 0, tc)) != NULL){
5802 strncpy(newfolder, p, sizeof(newfolder));
5803 newfolder[sizeof(newfolder)-1] = '\0';
5804 if(history->hist[history->curindex])
5805 tc = history->hist[history->curindex]->cntxt;
5807 else
5808 Writechar(BELL, 0);
5810 break;
5812 case 31 :
5813 if((p = get_next_hist(history, newfolder, 0, tc)) != NULL){
5814 strncpy(newfolder, p, sizeof(newfolder));
5815 newfolder[sizeof(newfolder)-1] = '\0';
5816 if(history->hist[history->curindex])
5817 tc = history->hist[history->curindex]->cntxt;
5819 else
5820 Writechar(BELL, 0);
5822 break;
5824 default :
5825 alpine_panic("Unhandled case");
5826 break;
5829 last_rc = rc;
5832 dprint((2, "broach folder, name entered \"%s\"\n",
5833 newfolder ? newfolder : "?"));
5835 /*-- Just check that we can expand this. It gets done for real later --*/
5836 strncpy(expanded, newfolder, sizeof(expanded));
5837 expanded[sizeof(expanded)-1] = '\0';
5839 if(!expand_foldername(expanded, sizeof(expanded))) {
5840 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
5841 expanded ? expanded : "?"));
5842 return(NULL);
5845 *context = tc;
5846 return(newfolder);
5850 /*----------------------------------------------------------------------
5851 Check to see if user wants to reopen dead stream.
5853 Args: ps --
5854 reopenp --
5856 Result: 1 if the folder was successfully updatedn
5857 0 if not necessary
5859 ----*/
5861 ask_mailbox_reopen(struct pine *ps, int *reopenp)
5863 if(((ps->mail_stream->dtb
5864 && ((ps->mail_stream->dtb->flags & DR_NONEWMAIL)
5865 || (ps->mail_stream->rdonly
5866 && ps->mail_stream->dtb->flags & DR_NONEWMAILRONLY)))
5867 && (ps->reopen_rule == REOPEN_ASK_ASK_Y
5868 || ps->reopen_rule == REOPEN_ASK_ASK_N
5869 || ps->reopen_rule == REOPEN_ASK_NO_Y
5870 || ps->reopen_rule == REOPEN_ASK_NO_N))
5871 || ((ps->mail_stream->dtb
5872 && ps->mail_stream->rdonly
5873 && !(ps->mail_stream->dtb->flags & DR_LOCAL))
5874 && (ps->reopen_rule == REOPEN_YES_ASK_Y
5875 || ps->reopen_rule == REOPEN_YES_ASK_N
5876 || ps->reopen_rule == REOPEN_ASK_ASK_Y
5877 || ps->reopen_rule == REOPEN_ASK_ASK_N))){
5878 int deefault;
5880 switch(ps->reopen_rule){
5881 case REOPEN_YES_ASK_Y:
5882 case REOPEN_ASK_ASK_Y:
5883 case REOPEN_ASK_NO_Y:
5884 deefault = 'y';
5885 break;
5887 default:
5888 deefault = 'n';
5889 break;
5892 switch(want_to("Re-open folder to check for new messages", deefault,
5893 'x', h_reopen_folder, WT_NORM)){
5894 case 'y':
5895 (*reopenp)++;
5896 break;
5898 case 'x':
5899 return(-1);
5903 return(0);
5908 /*----------------------------------------------------------------------
5909 Check to see if user input is in form of old c-client mailbox speck
5911 Args: old --
5912 new --
5914 Result: 1 if the folder was successfully updatedn
5915 0 if not necessary
5917 ----*/
5919 update_folder_spec(char *new, size_t newlen, char *old)
5921 char *p, *orignew;
5922 int nntp = 0;
5924 orignew = new;
5925 if(*(p = old) == '*') /* old form? */
5926 old++;
5928 if(*old == '{') /* copy host spec */
5930 switch(*new = *old++){
5931 case '\0' :
5932 return(FALSE);
5934 case '/' :
5935 if(!struncmp(old, "nntp", 4))
5936 nntp++;
5938 break;
5940 default :
5941 break;
5943 while(*new++ != '}' && (new-orignew) < newlen-1);
5945 if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){
5947 * OK, some heuristics here. If it looks like a newsgroup
5948 * then we plunk it into the #news namespace else we
5949 * assume that they're trying to get at a #public folder...
5951 for(p = old;
5952 *p && (isalnum((unsigned char) *p) || strindex(".-", *p));
5953 p++)
5956 sstrncpy(&new, (*p && !nntp) ? "#public/" : "#news.", newlen-(new-orignew));
5957 strncpy(new, old, newlen-(new-orignew));
5958 return(TRUE);
5961 orignew[newlen-1] = '\0';
5963 return(FALSE);
5967 /*----------------------------------------------------------------------
5968 Open the requested folder in the requested context
5970 Args: state -- usual pine state struct
5971 newfolder -- folder to open
5972 new_context -- folder context might live in
5973 stream -- candidate for recycling
5975 Result: New folder open or not (if error), and we're set to
5976 enter the index screen.
5977 ----*/
5978 void
5979 visit_folder(struct pine *state, char *newfolder, CONTEXT_S *new_context,
5980 MAILSTREAM *stream, long unsigned int flags)
5982 dprint((9, "visit_folder(%s, %s)\n",
5983 newfolder ? newfolder : "?",
5984 (new_context && new_context->context)
5985 ? new_context->context : "(NULL)"));
5987 if(ps_global && ps_global->ttyo){
5988 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
5989 ps_global->mangled_footer = 1;
5992 if(do_broach_folder(newfolder, new_context, stream ? &stream : NULL,
5993 flags) >= 0
5994 || !sp_flagged(state->mail_stream, SP_LOCKED))
5995 state->next_screen = mail_index_screen;
5996 else
5997 state->next_screen = folder_screen;
6001 /*----------------------------------------------------------------------
6002 Move read messages from folder if listed in archive
6004 Args:
6006 ----*/
6008 read_msg_prompt(long int n, char *f)
6010 char buf[MAX_SCREEN_COLS+1];
6012 snprintf(buf, sizeof(buf), "Save the %ld read message%s in \"%s\"", n, plural(n), f);
6013 buf[sizeof(buf)-1] = '\0';
6014 return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y');
6018 /*----------------------------------------------------------------------
6019 Print current message[s] or folder index
6021 Args: state -- pointer to struct holding a bunch of pine state
6022 msgmap -- table mapping msg nums to c-client sequence nums
6023 aopt -- aggregate options
6024 in_index -- boolean indicating we're called from Index Screen
6026 Filters the original header and sends stuff to printer
6027 ---*/
6029 cmd_print(struct pine *state, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
6031 char prompt[250];
6032 long i, msgs, rawno;
6033 int next = 0, do_index = 0, rv = 0;
6034 ENVELOPE *e;
6035 BODY *b;
6036 MESSAGECACHE *mc;
6038 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
6039 return rv;
6041 msgs = mn_total_cur(msgmap);
6043 if((in_index != View) && F_ON(F_PRINT_INDEX, state)){
6044 char m[10];
6045 int ans;
6046 static ESCKEY_S prt_opts[] = {
6047 {'i', 'i', "I", N_("Index")},
6048 {'m', 'm', "M", NULL},
6049 {-1, 0, NULL, NULL}};
6051 if(in_index == ThrdIndx){
6052 /* TRANSLATORS: This is a question, Print Index ? */
6053 if(want_to(_("Print Index"), 'y', 'x', NO_HELP, WT_NORM) == 'y')
6054 ans = 'i';
6055 else
6056 ans = 'x';
6058 else{
6059 snprintf(m, sizeof(m), "Message%s", (msgs>1L) ? "s" : "");
6060 m[sizeof(m)-1] = '\0';
6061 prt_opts[1].label = m;
6062 snprintf(prompt, sizeof(prompt), "Print %sFolder Index or %s %s? ",
6063 (aopt & MCMD_AGG_2) ? "thread " : MCMD_ISAGG(aopt) ? "selected " : "",
6064 (aopt & MCMD_AGG_2) ? "thread" : MCMD_ISAGG(aopt) ? "selected" : "current", m);
6065 prompt[sizeof(prompt)-1] = '\0';
6067 ans = radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x',
6068 NO_HELP, RB_NORM|RB_SEQ_SENSITIVE);
6071 switch(ans){
6072 case 'x' :
6073 cmd_cancelled("Print");
6074 if(MCMD_ISAGG(aopt))
6075 restore_selected(msgmap);
6077 return rv;
6079 case 'i':
6080 do_index = 1;
6081 break;
6083 default :
6084 case 'm':
6085 break;
6089 if(do_index)
6090 snprintf(prompt, sizeof(prompt), "%sFolder Index",
6091 (aopt & MCMD_AGG_2) ? "Thread " : MCMD_ISAGG(aopt) ? "Selected " : "");
6092 else if(msgs > 1L)
6093 snprintf(prompt, sizeof(prompt), "%s messages", long2string(msgs));
6094 else
6095 snprintf(prompt, sizeof(prompt), "Message %s", long2string(mn_get_cur(msgmap)));
6097 prompt[sizeof(prompt)-1] = '\0';
6099 if(open_printer(prompt) < 0){
6100 if(MCMD_ISAGG(aopt))
6101 restore_selected(msgmap);
6103 return rv;
6106 if(do_index){
6107 TITLE_S *tc;
6109 tc = format_titlebar();
6111 /* Print titlebar... */
6112 print_text1("%s\n\n", tc ? tc->titlebar_line : "");
6113 /* then all the index members... */
6114 if(!print_index(state, msgmap, MCMD_ISAGG(aopt)))
6115 q_status_message(SM_ORDER | SM_DING, 3, 3,
6116 _("Error printing folder index"));
6117 else
6118 rv++;
6120 else{
6121 rv++;
6122 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){
6123 if(next && F_ON(F_AGG_PRINT_FF, state))
6124 if(!print_char(FORMFEED)){
6125 rv = 0;
6126 break;
6129 if(!(state->mail_stream
6130 && (rawno = mn_m2raw(msgmap, i)) > 0L
6131 && rawno <= state->mail_stream->nmsgs
6132 && (mc = mail_elt(state->mail_stream, rawno))
6133 && mc->valid))
6134 mc = NULL;
6136 if(!(e=pine_mail_fetchstructure(state->mail_stream,
6137 mn_m2raw(msgmap,i),
6138 &b))
6139 || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global)
6140 && !bezerk_delimiter(e, mc, print_char, next))
6141 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
6142 e, b, NULL, FM_NEW_MESS | FM_NOINDENT,
6143 print_char)){
6144 q_status_message(SM_ORDER | SM_DING, 3, 3,
6145 _("Error printing message"));
6146 rv = 0;
6147 break;
6152 close_printer();
6154 if(MCMD_ISAGG(aopt))
6155 restore_selected(msgmap);
6157 return rv;
6161 /*----------------------------------------------------------------------
6162 Pipe message text
6164 Args: state -- various pine state bits
6165 msgmap -- Message number mapping table
6166 aopt -- option flags
6168 Filters the original header and sends stuff to specified command
6169 ---*/
6171 cmd_pipe(struct pine *state, MSGNO_S *msgmap, int aopt)
6173 ENVELOPE *e;
6174 MESSAGECACHE *mc;
6175 BODY *b;
6176 PIPE_S *syspipe;
6177 char *resultfilename = NULL, prompt[80], *p;
6178 int done = 0, rv = 0;
6179 gf_io_t pc;
6180 int fourlabel = -1, j = 0, next = 0, ku;
6181 int pipe_rv; /* rv of proc to separate from close_system_pipe rv */
6182 long i, rawno;
6183 unsigned flagsforhist = 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6184 static HISTORY_S *history = NULL;
6185 int capture = 1, raw = 0, delimit = 0, newpipe = 0;
6186 char pipe_command[MAXPATH];
6187 ESCKEY_S pipe_opt[8];
6189 if(ps_global->restricted){
6190 q_status_message(SM_ORDER | SM_DING, 0, 4,
6191 "Alpine demo can't pipe messages");
6192 return rv;
6194 else if(!any_messages(msgmap, NULL, "to Pipe"))
6195 return rv;
6197 pipe_command[0] = '\0';
6198 init_hist(&history, HISTSIZE);
6199 flagsforhist = (raw ? 0x8 : 0) +
6200 (delimit ? 0x4 : 0) +
6201 (newpipe ? 0x2 : 0) +
6202 (capture ? 0x1 : 0);
6203 if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){
6204 strncpy(pipe_command, p, sizeof(pipe_command));
6205 pipe_command[sizeof(pipe_command)-1] = '\0';
6206 if(history->hist[history->curindex]){
6207 flagsforhist = history->hist[history->curindex]->flags;
6208 raw = (flagsforhist & 0x8) ? 1 : 0;
6209 delimit = (flagsforhist & 0x4) ? 1 : 0;
6210 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6211 capture = (flagsforhist & 0x1) ? 1 : 0;
6215 pipe_opt[j].ch = 0;
6216 pipe_opt[j].rval = 0;
6217 pipe_opt[j].name = "";
6218 pipe_opt[j++].label = "";
6220 pipe_opt[j].ch = ctrl('W');
6221 pipe_opt[j].rval = 10;
6222 pipe_opt[j].name = "^W";
6223 pipe_opt[j++].label = NULL;
6225 pipe_opt[j].ch = ctrl('Y');
6226 pipe_opt[j].rval = 11;
6227 pipe_opt[j].name = "^Y";
6228 pipe_opt[j++].label = NULL;
6230 pipe_opt[j].ch = ctrl('R');
6231 pipe_opt[j].rval = 12;
6232 pipe_opt[j].name = "^R";
6233 pipe_opt[j++].label = NULL;
6235 if(MCMD_ISAGG(aopt)){
6236 if(!pseudo_selected(state->mail_stream, msgmap))
6237 return rv;
6238 else{
6239 fourlabel = j;
6240 pipe_opt[j].ch = ctrl('T');
6241 pipe_opt[j].rval = 13;
6242 pipe_opt[j].name = "^T";
6243 pipe_opt[j++].label = NULL;
6247 pipe_opt[j].ch = KEY_UP;
6248 pipe_opt[j].rval = 30;
6249 pipe_opt[j].name = "";
6250 ku = j;
6251 pipe_opt[j++].label = "";
6253 pipe_opt[j].ch = KEY_DOWN;
6254 pipe_opt[j].rval = 31;
6255 pipe_opt[j].name = "";
6256 pipe_opt[j++].label = "";
6258 pipe_opt[j].ch = -1;
6260 while (!done) {
6261 int flags;
6263 snprintf(prompt, sizeof(prompt), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6264 raw ? "RAW " : "",
6265 MCMD_ISAGG(aopt) ? "s" : " ",
6266 MCMD_ISAGG(aopt) ? "" : comatose(mn_get_cur(msgmap)),
6267 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? "(" : "",
6268 capture ? "" : "uncaptured",
6269 (!capture && delimit) ? "," : "",
6270 delimit ? "delimited" : "",
6271 ((!capture || delimit) && newpipe && MCMD_ISAGG(aopt)) ? "," : "",
6272 (newpipe && MCMD_ISAGG(aopt)) ? "new pipe" : "",
6273 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? ") " : "");
6274 prompt[sizeof(prompt)-1] = '\0';
6275 pipe_opt[1].label = raw ? N_("Shown Text") : N_("Raw Text");
6276 pipe_opt[2].label = capture ? N_("Free Output") : N_("Capture Output");
6277 pipe_opt[3].label = delimit ? N_("No Delimiter") : N_("With Delimiter");
6278 if(fourlabel > 0)
6279 pipe_opt[fourlabel].label = newpipe ? N_("To Same Pipe") : N_("To Individual Pipes");
6283 * 2 is really 1 because there will be one real entry and
6284 * one entry of "" because of the get_prev_hist above.
6286 if(items_in_hist(history) > 2){
6287 pipe_opt[ku].name = HISTORY_UP_KEYNAME;
6288 pipe_opt[ku].label = HISTORY_KEYLABEL;
6289 pipe_opt[ku+1].name = HISTORY_DOWN_KEYNAME;
6290 pipe_opt[ku+1].label = HISTORY_KEYLABEL;
6292 else{
6293 pipe_opt[ku].name = "";
6294 pipe_opt[ku].label = "";
6295 pipe_opt[ku+1].name = "";
6296 pipe_opt[ku+1].label = "";
6299 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
6300 switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0,
6301 sizeof(pipe_command), prompt,
6302 pipe_opt, NO_HELP, &flags)){
6303 case -1 :
6304 q_status_message(SM_ORDER | SM_DING, 3, 4,
6305 _("Internal problem encountered"));
6306 done++;
6307 break;
6309 case 10 : /* flip raw bit */
6310 raw = !raw;
6311 break;
6313 case 11 : /* flip capture bit */
6314 capture = !capture;
6315 break;
6317 case 12 : /* flip delimit bit */
6318 delimit = !delimit;
6319 break;
6321 case 13 : /* flip newpipe bit */
6322 newpipe = !newpipe;
6323 break;
6325 case 30 :
6326 flagsforhist = (raw ? 0x8 : 0) +
6327 (delimit ? 0x4 : 0) +
6328 (newpipe ? 0x2 : 0) +
6329 (capture ? 0x1 : 0);
6330 if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6331 strncpy(pipe_command, p, sizeof(pipe_command));
6332 pipe_command[sizeof(pipe_command)-1] = '\0';
6333 if(history->hist[history->curindex]){
6334 flagsforhist = history->hist[history->curindex]->flags;
6335 raw = (flagsforhist & 0x8) ? 1 : 0;
6336 delimit = (flagsforhist & 0x4) ? 1 : 0;
6337 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6338 capture = (flagsforhist & 0x1) ? 1 : 0;
6341 else
6342 Writechar(BELL, 0);
6344 break;
6346 case 31 :
6347 flagsforhist = (raw ? 0x8 : 0) +
6348 (delimit ? 0x4 : 0) +
6349 (newpipe ? 0x2 : 0) +
6350 (capture ? 0x1 : 0);
6351 if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6352 strncpy(pipe_command, p, sizeof(pipe_command));
6353 pipe_command[sizeof(pipe_command)-1] = '\0';
6354 if(history->hist[history->curindex]){
6355 flagsforhist = history->hist[history->curindex]->flags;
6356 raw = (flagsforhist & 0x8) ? 1 : 0;
6357 delimit = (flagsforhist & 0x4) ? 1 : 0;
6358 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6359 capture = (flagsforhist & 0x1) ? 1 : 0;
6362 else
6363 Writechar(BELL, 0);
6365 break;
6367 case 0 :
6368 if(pipe_command[0]){
6370 flagsforhist = (raw ? 0x8 : 0) +
6371 (delimit ? 0x4 : 0) +
6372 (newpipe ? 0x2 : 0) +
6373 (capture ? 0x1 : 0);
6374 save_hist(history, pipe_command, flagsforhist, NULL);
6376 flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
6377 flags |= (raw ? PIPE_RAW : 0);
6378 if(!capture){
6379 #ifndef _WINDOWS
6380 ClearScreen();
6381 fflush(stdout);
6382 clear_cursor_pos();
6383 ps_global->mangled_screen = 1;
6384 ps_global->in_init_seq = 1;
6385 #endif
6386 flags |= PIPE_RESET;
6389 if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command,
6390 (flags & PIPE_RESET)
6391 ? NULL
6392 : &resultfilename,
6393 flags, &pc)))
6394 done++;
6396 for(i = mn_first_cur(msgmap);
6397 i > 0L && !done;
6398 i = mn_next_cur(msgmap)){
6399 e = pine_mail_fetchstructure(ps_global->mail_stream,
6400 mn_m2raw(msgmap, i), &b);
6401 if(!(state->mail_stream
6402 && (rawno = mn_m2raw(msgmap, i)) > 0L
6403 && rawno <= state->mail_stream->nmsgs
6404 && (mc = mail_elt(state->mail_stream, rawno))
6405 && mc->valid))
6406 mc = NULL;
6408 if((newpipe
6409 && !(syspipe = cmd_pipe_open(pipe_command,
6410 (flags & PIPE_RESET)
6411 ? NULL
6412 : &resultfilename,
6413 flags, &pc)))
6414 || (delimit && !bezerk_delimiter(e, mc, pc, next++)))
6415 done++;
6417 if(!done){
6418 if(raw){
6419 char *pipe_err;
6421 prime_raw_pipe_getc(ps_global->mail_stream,
6422 mn_m2raw(msgmap, i), -1L, 0L);
6423 gf_filter_init();
6424 gf_link_filter(gf_nvtnl_local, NULL);
6425 if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){
6426 q_status_message1(SM_ORDER|SM_DING,
6427 3, 3,
6428 _("Internal Error: %s"),
6429 pipe_err);
6430 done++;
6433 else if(!format_message(mn_m2raw(msgmap, i), e, b,
6434 NULL, FM_NEW_MESS | FM_NOWRAP, pc))
6435 done++;
6438 if(newpipe)
6439 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6440 done++;
6443 if(!capture)
6444 ps_global->in_init_seq = 0;
6446 if(!newpipe)
6447 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6448 done++;
6449 if(done) /* say we had a problem */
6450 q_status_message(SM_ORDER | SM_DING, 3, 3,
6451 _("Error piping message"));
6452 else if(resultfilename){
6453 rv++;
6454 /* only display if no error */
6455 display_output_file(resultfilename, "PIPE MESSAGE",
6456 NULL, DOF_EMPTY);
6457 fs_give((void **)&resultfilename);
6459 else{
6460 rv++;
6461 q_status_message(SM_ORDER, 0, 2, _("Pipe command completed"));
6464 done++;
6465 break;
6467 /* else fall thru as if cancelled */
6469 case 1 :
6470 cmd_cancelled("Pipe command");
6471 done++;
6472 break;
6474 case 3 :
6475 helper(h_common_pipe, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE);
6476 ps_global->mangled_screen = 1;
6477 break;
6479 case 2 : /* no place to escape to */
6480 case 4 : /* can't suspend */
6481 default :
6482 break;
6486 ps_global->mangled_footer = 1;
6487 if(MCMD_ISAGG(aopt))
6488 restore_selected(msgmap);
6490 return rv;
6494 /*----------------------------------------------------------------------
6495 Screen to offer list management commands contained in message
6497 Args: state -- pointer to struct holding a bunch of pine state
6498 msgmap -- table mapping msg nums to c-client sequence nums
6499 aopt -- aggregate options
6501 Result:
6503 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6504 ----*/
6505 void
6506 rfc2369_display(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno)
6508 int winner = 0;
6509 char *h, *hdrs[MLCMD_COUNT + 1];
6510 long index_no = mn_raw2m(msgmap, msgno);
6511 RFC2369_S data[MLCMD_COUNT];
6513 /* for each header field */
6514 if((h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))) != NULL){
6515 memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT);
6516 if(rfc2369_parse_fields(h, &data[0])){
6517 STORE_S *explain;
6519 if((explain = list_mgmt_text(data, index_no)) != NULL){
6520 list_mgmt_screen(explain);
6521 ps_global->mangled_screen = 1;
6522 so_give(&explain);
6523 winner++;
6527 fs_give((void **) &h);
6530 if(!winner)
6531 q_status_message1(SM_ORDER, 0, 3,
6532 "Message %s contains no list management information",
6533 comatose(index_no));
6537 STORE_S *
6538 list_mgmt_text(RFC2369_S *data, long int msgno)
6540 STORE_S *store;
6541 int i, j, n, fields = 0;
6542 static char *rfc2369_intro1 =
6543 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6544 static char *rfc2369_intro2[] = {
6545 N_(" has information associated with it "),
6546 N_("that explains how to participate in an email list. An "),
6547 N_("email list is represented by a single email address that "),
6548 N_("users sharing a common interest can send messages to (known "),
6549 N_("as posting) which are then redistributed to all members "),
6550 N_("of the list (sometimes after review by a moderator)."),
6551 N_("<P>List participation commands in this message include:"),
6552 NULL
6555 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6557 /* Insert introductory text */
6558 so_puts(store, rfc2369_intro1);
6560 so_puts(store, comatose(msgno));
6562 for(i = 0; rfc2369_intro2[i]; i++)
6563 so_puts(store, _(rfc2369_intro2[i]));
6565 so_puts(store, "<P>");
6566 for(i = 0; i < MLCMD_COUNT; i++)
6567 if(data[i].data[0].value
6568 || data[i].data[0].comment
6569 || data[i].data[0].error){
6570 if(!fields++)
6571 so_puts(store, "<UL>");
6573 so_puts(store, "<LI>");
6574 so_puts(store,
6575 (n = (data[i].data[1].value || data[i].data[1].comment))
6576 ? "Methods to "
6577 : "A method to ");
6579 so_puts(store, data[i].field.description);
6580 so_puts(store, ". ");
6582 if(n)
6583 so_puts(store, "<OL>");
6585 for(j = 0;
6586 j < MLCMD_MAXDATA
6587 && (data[i].data[j].comment
6588 || data[i].data[j].value
6589 || data[i].data[j].error);
6590 j++){
6592 so_puts(store, n ? "<P><LI>" : "<P>");
6594 if(data[i].data[j].comment){
6595 so_puts(store,
6596 _("With the provided comment:<P><BLOCKQUOTE>"));
6597 so_puts(store, data[i].data[j].comment);
6598 so_puts(store, "</BLOCKQUOTE><P>");
6601 if(data[i].data[j].value){
6602 if(i == MLCMD_POST
6603 && !strucmp(data[i].data[j].value, "NO")){
6604 so_puts(store,
6605 _("Posting is <EM>not</EM> allowed on this list"));
6607 else{
6608 so_puts(store, "Select <A HREF=\"");
6609 so_puts(store, data[i].data[j].value);
6610 so_puts(store, "\">HERE</A> to ");
6611 so_puts(store, (data[i].field.action)
6612 ? data[i].field.action
6613 : "try it");
6616 so_puts(store, ".");
6619 if(data[i].data[j].error){
6620 so_puts(store, "<P>Unfortunately, Alpine can not offer");
6621 so_puts(store, " to take direct action based upon it");
6622 so_puts(store, " because it was improperly formatted.");
6623 so_puts(store, " The unrecognized data associated with");
6624 so_puts(store, " the \"");
6625 so_puts(store, data[i].field.name);
6626 so_puts(store, "\" header field was:<P><BLOCKQUOTE>");
6627 so_puts(store, data[i].data[j].error);
6628 so_puts(store, "</BLOCKQUOTE>");
6631 so_puts(store, "<P>");
6634 if(n)
6635 so_puts(store, "</OL>");
6638 if(fields)
6639 so_puts(store, "</UL>");
6641 so_puts(store, "</BODY></HTML>");
6644 return(store);
6648 void
6649 list_mgmt_screen(STORE_S *html)
6651 int cmd = MC_NONE;
6652 long offset = 0L;
6653 char *error = NULL;
6654 STORE_S *store;
6655 HANDLE_S *handles = NULL;
6656 gf_io_t gc, pc;
6659 so_seek(html, 0L, 0);
6660 gf_set_so_readc(&gc, html);
6662 init_handles(&handles);
6664 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6665 gf_set_so_writec(&pc, store);
6666 gf_filter_init();
6668 gf_link_filter(gf_html2plain,
6669 gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols,
6670 non_messageview_margin(), &handles, NULL, 0));
6672 error = gf_pipe(gc, pc);
6674 gf_clear_so_writec(store);
6676 if(!error){
6677 SCROLL_S sargs;
6679 memset(&sargs, 0, sizeof(SCROLL_S));
6680 sargs.text.text = so_text(store);
6681 sargs.text.src = CharStar;
6682 sargs.text.desc = "list commands";
6683 sargs.text.handles = handles;
6684 if(offset){
6685 sargs.start.on = Offset;
6686 sargs.start.loc.offset = offset;
6689 sargs.bar.title = _("MAIL LIST COMMANDS");
6690 sargs.bar.style = MessageNumber;
6691 sargs.resize_exit = 1;
6692 sargs.help.text = h_special_list_commands;
6693 sargs.help.title = _("HELP FOR LIST COMMANDS");
6694 sargs.keys.menu = &listmgr_keymenu;
6695 setbitmap(sargs.keys.bitmap);
6696 if(!handles){
6697 clrbitn(LM_TRY_KEY, sargs.keys.bitmap);
6698 clrbitn(LM_PREV_KEY, sargs.keys.bitmap);
6699 clrbitn(LM_NEXT_KEY, sargs.keys.bitmap);
6702 cmd = scrolltool(&sargs);
6703 offset = sargs.start.loc.offset;
6706 so_give(&store);
6709 free_handles(&handles);
6710 gf_clear_so_readc(html);
6712 while(cmd == MC_RESIZE);
6716 /*----------------------------------------------------------------------
6717 Prompt the user for the type of select desired
6719 NOTE: any and all functions that successfully exit the second
6720 switch() statement below (currently "select_*() functions"),
6721 *MUST* update the folder's MESSAGECACHE element's "searched"
6722 bits to reflect the search result. Functions using
6723 mail_search() get this for free, the others must update 'em
6724 by hand.
6726 Returns -1 if canceled without changing selection
6727 0 if selection may have changed
6728 ----*/
6730 aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index)
6732 long i, diff, old_tot, msgno, raw;
6733 int q = 0, rv = 0, narrow = 0, hidden, ret = -1;
6734 ESCKEY_S *sel_opts;
6735 MESSAGECACHE *mc;
6736 SEARCHSET *limitsrch = NULL;
6737 PINETHRD_S *thrd;
6738 extern MAILSTREAM *mm_search_stream;
6739 extern long mm_search_count;
6741 hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
6742 mm_search_stream = state->mail_stream;
6743 mm_search_count = 0L;
6745 sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2;
6746 if(THREADING()){
6747 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH;
6749 else{
6750 sel_opts[SEL_OPTS_THREAD].ch = -1;
6753 if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){
6754 if(THRD_INDX()){
6755 i = 0;
6756 thrd = fetch_thread(state->mail_stream,
6757 mn_m2raw(msgmap, mn_get_cur(msgmap)));
6758 /* check if whole thread is selected or not */
6759 if(thrd &&
6760 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT)
6762 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE))
6763 i = 1;
6765 sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd");
6767 else{
6768 i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap),
6769 MN_SLCT);
6770 sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur");
6773 sel_opts += 2; /* disable extra options */
6774 switch(q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6775 RB_NORM)){
6776 case 'f' : /* flip selection */
6777 msgno = 0L;
6778 for(i = 1L; i <= mn_get_total(msgmap); i++){
6779 ret = 0;
6780 q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT);
6781 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q);
6782 if(hidden){
6783 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q);
6784 if(!msgno && q)
6785 mn_reset_cur(msgmap, msgno = i);
6789 return(ret);
6791 case 'n' : /* narrow selection */
6792 narrow++;
6793 case 'b' : /* broaden selection */
6794 q = 0; /* offer criteria prompt */
6795 break;
6797 case 'c' : /* Un/Select Current */
6798 case 'a' : /* Unselect All */
6799 case 'x' : /* cancel */
6800 break;
6802 default :
6803 q_status_message(SM_ORDER | SM_DING, 3, 3,
6804 "Unsupported Select option");
6805 return(ret);
6809 if(!q){
6810 while(1){
6811 q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x',
6812 NO_HELP, RB_NORM|RB_RET_HELP);
6814 if(q == 3){
6815 helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE);
6816 ps_global->mangled_screen = 1;
6818 else
6819 break;
6824 * The purpose of this is to add the appropriate searchset to the
6825 * search so that the search can be limited to only looking at what
6826 * it needs to look at. That is, if we are narrowing then we only need
6827 * to look at messages which are already selected, and if we are
6828 * broadening, then we only need to look at messages which are not
6829 * yet selected. This routine will work whether or not
6830 * limiting_searchset properly limits the search set. In particular,
6831 * the searchset returned by limiting_searchset may include messages
6832 * which really shouldn't be included. We do that because a too-large
6833 * searchset will break some IMAP servers. It is even possible that it
6834 * becomes inefficient to send the whole set. If the select function
6835 * frees limitsrch, it should be sure to set it to NULL so we won't
6836 * try freeing it again here.
6838 limitsrch = limiting_searchset(state->mail_stream, narrow);
6841 * NOTE: See note about MESSAGECACHE "searched" bits above!
6843 switch(q){
6844 case 'x': /* cancel */
6845 cmd_cancelled("Select command");
6846 return(ret);
6848 case 'c' : /* select/unselect current */
6849 (void) select_by_current(state, msgmap, in_index);
6850 ret = 0;
6851 return(ret);
6853 case 'a' : /* select/unselect all */
6854 msgno = any_lflagged(msgmap, MN_SLCT);
6855 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
6856 ret = 0;
6857 agg_select_all(state->mail_stream, msgmap, &diff,
6858 any_lflagged(msgmap, MN_SLCT) <= 0L);
6859 q_status_message4(SM_ORDER,0,2,
6860 "%s%s message%s %sselected",
6861 msgno ? "" : "All ", comatose(diff),
6862 plural(diff), msgno ? "UN" : "");
6863 return(ret);
6865 case 'n' : /* Select by Number */
6866 ret = 0;
6867 if(THRD_INDX())
6868 rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch);
6869 else
6870 rv = select_by_number(state->mail_stream, msgmap, &limitsrch);
6872 break;
6874 case 'd' : /* Select by Date */
6875 ret = 0;
6876 rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap),
6877 &limitsrch);
6878 break;
6880 case 't' : /* Text */
6881 ret = 0;
6882 rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap),
6883 &limitsrch);
6884 break;
6886 case 'z' : /* Size */
6887 ret = 0;
6888 rv = select_by_size(state->mail_stream, &limitsrch);
6889 break;
6891 case 's' : /* Status */
6892 ret = 0;
6893 rv = select_by_status(state->mail_stream, &limitsrch);
6894 break;
6896 case 'k' : /* Keyword */
6897 ret = 0;
6898 rv = select_by_keyword(state->mail_stream, &limitsrch);
6899 break;
6901 case 'r' : /* Rule */
6902 ret = 0;
6903 rv = select_by_rule(state->mail_stream, &limitsrch);
6904 break;
6906 case 'h' : /* Thread */
6907 ret = 0;
6908 rv = select_by_thread(state->mail_stream, msgmap, &limitsrch);
6909 break;
6911 default :
6912 q_status_message(SM_ORDER | SM_DING, 3, 3,
6913 "Unsupported Select option");
6914 return(ret);
6917 if(limitsrch)
6918 mail_free_searchset(&limitsrch);
6920 if(rv) /* bad return value.. */
6921 return(ret); /* error already displayed */
6923 if(narrow) /* make sure something was selected */
6924 for(i = 1L; i <= mn_get_total(msgmap); i++)
6925 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
6926 && raw <= state->mail_stream->nmsgs
6927 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
6928 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
6929 break;
6930 else
6931 mm_search_count--;
6934 diff = 0L;
6935 if(mm_search_count){
6937 * loop thru all the messages, adjusting local flag bits
6938 * based on their "searched" bit...
6940 for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++)
6941 if(narrow){
6942 /* turning OFF selectedness if the "searched" bit isn't lit. */
6943 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
6944 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
6945 && raw <= state->mail_stream->nmsgs
6946 && (mc = mail_elt(state->mail_stream, raw))
6947 && !mc->searched){
6948 diff--;
6949 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
6950 if(hidden)
6951 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
6953 /* adjust current message in case we unselect and hide it */
6954 else if(msgno < mn_get_cur(msgmap)
6955 && (!THRD_INDX()
6956 || !get_lflag(state->mail_stream, msgmap,
6957 i, MN_CHID)))
6958 msgno = i;
6961 else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
6962 && raw <= state->mail_stream->nmsgs
6963 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
6964 /* turn ON selectedness if "searched" bit is lit. */
6965 if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
6966 diff++;
6967 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
6968 if(hidden)
6969 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
6973 /* if we're zoomed and the current message was unselected */
6974 if(narrow && msgno
6975 && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE))
6976 mn_reset_cur(msgmap, msgno);
6979 if(!diff){
6980 if(narrow)
6981 q_status_message4(SM_ORDER, 3, 3,
6982 "%s. %s message%s remain%s selected.",
6983 mm_search_count
6984 ? "No change resulted"
6985 : "No messages in intersection",
6986 comatose(old_tot), plural(old_tot),
6987 (old_tot == 1L) ? "s" : "");
6988 else if(old_tot)
6989 q_status_message(SM_ORDER, 3, 3,
6990 _("No change resulted. Matching messages already selected."));
6991 else
6992 q_status_message1(SM_ORDER | SM_DING, 3, 3,
6993 _("Select failed. No %smessages selected."),
6994 old_tot ? _("additional ") : "");
6996 else if(old_tot){
6997 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
6998 "Select matched %ld message%s. %s %smessage%s %sselected.",
6999 (diff > 0) ? diff : old_tot + diff,
7000 plural((diff > 0) ? diff : old_tot + diff),
7001 comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7002 (diff > 0) ? "total " : "",
7003 plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7004 (diff > 0) ? "" : "UN");
7005 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
7006 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
7008 else
7009 q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"),
7010 comatose(diff), plural(diff));
7012 return(ret);
7016 /*----------------------------------------------------------------------
7017 Toggle the state of the current message
7019 Args: state -- pointer pine's state variables
7020 msgmap -- message collection to operate on
7021 in_index -- in the message index view
7022 Returns: TRUE if current marked selected, FALSE otw
7023 ----*/
7025 select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
7027 long cur;
7028 int all_selected = 0;
7029 unsigned long was, tot, rawno;
7030 PINETHRD_S *thrd;
7032 cur = mn_get_cur(msgmap);
7034 if(THRD_INDX()){
7035 thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur));
7036 if(!thrd)
7037 return 0;
7039 was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT);
7040 tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE);
7041 if(was == tot)
7042 all_selected++;
7044 if(all_selected){
7045 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0);
7046 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7047 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1);
7049 * See if there's anything left to zoom on. If so,
7050 * pick an adjacent one for highlighting, else make
7051 * sure nothing is left hidden...
7053 if(any_lflagged(msgmap, MN_SLCT)){
7054 mn_inc_cur(state->mail_stream, msgmap,
7055 (in_index == View && THREADING()
7056 && sp_viewing_a_thread(state->mail_stream))
7057 ? MH_THISTHD
7058 : (in_index == View)
7059 ? MH_ANYTHD : MH_NONE);
7060 if(mn_get_cur(msgmap) == cur)
7061 mn_dec_cur(state->mail_stream, msgmap,
7062 (in_index == View && THREADING()
7063 && sp_viewing_a_thread(state->mail_stream))
7064 ? MH_THISTHD
7065 : (in_index == View)
7066 ? MH_ANYTHD : MH_NONE);
7068 else /* clear all hidden flags */
7069 (void) unzoom_index(state, state->mail_stream, msgmap);
7072 else
7073 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1);
7075 q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected",
7076 comatose(all_selected ? was : tot-was),
7077 plural(all_selected ? was : tot-was),
7078 all_selected ? "UN" : "");
7080 /* collapsed thread */
7081 else if(THREADING()
7082 && ((rawno = mn_m2raw(msgmap, cur)) != 0L)
7083 && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL)
7084 && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){
7086 * This doesn't work quite the same as the colon command works, but
7087 * it is arguably doing the correct thing. The difference is
7088 * that aggregate_select will zoom after selecting back where it
7089 * was called from, but selecting a thread with colon won't zoom.
7090 * Maybe it makes sense to zoom after a select but not after a colon
7091 * command even though they are very similar.
7093 thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state));
7095 else{
7096 if((all_selected =
7097 get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */
7098 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0);
7099 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7100 set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1);
7102 * See if there's anything left to zoom on. If so,
7103 * pick an adjacent one for highlighting, else make
7104 * sure nothing is left hidden...
7106 if(any_lflagged(msgmap, MN_SLCT)){
7107 mn_inc_cur(state->mail_stream, msgmap,
7108 (in_index == View && THREADING()
7109 && sp_viewing_a_thread(state->mail_stream))
7110 ? MH_THISTHD
7111 : (in_index == View)
7112 ? MH_ANYTHD : MH_NONE);
7113 if(mn_get_cur(msgmap) == cur)
7114 mn_dec_cur(state->mail_stream, msgmap,
7115 (in_index == View && THREADING()
7116 && sp_viewing_a_thread(state->mail_stream))
7117 ? MH_THISTHD
7118 : (in_index == View)
7119 ? MH_ANYTHD : MH_NONE);
7121 else /* clear all hidden flags */
7122 (void) unzoom_index(state, state->mail_stream, msgmap);
7125 else
7126 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1);
7128 q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected",
7129 long2string(cur), all_selected ? "UN" : "");
7133 return(!all_selected);
7137 /*----------------------------------------------------------------------
7138 Prompt the user for the command to perform on selected messages
7140 Args: state -- pointer pine's state variables
7141 msgmap -- message collection to operate on
7142 q_line -- line on display to write prompts
7143 Returns: 1 if the selected messages are suitably commanded,
7144 0 if the choice to pick the command was declined
7146 ----*/
7148 apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
7149 UCS preloadkeystroke, int flags, int q_line)
7151 int i = 8, /* number of static entries in sel_opts3 */
7152 rv = 0,
7153 cmd,
7154 we_cancel = 0,
7155 agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG;
7156 char prompt[80];
7159 * To do this "right", we really ought to have access to the keymenu
7160 * here and change the typed command into a real command by running
7161 * it through menu_command. Then the switch below would be against
7162 * results from menu_command. If we did that we'd also pass the
7163 * results of menu_command in as preloadkeystroke instead of passing
7164 * the keystroke itself. But we don't have the keymenu handy,
7165 * so we have to fake it. The only complication that we run into
7166 * is that KEY_DEL is an escape sequence so we change a typed
7167 * KEY_DEL esc seq into the letter D.
7170 if(!preloadkeystroke){
7171 if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
7172 sel_opts3[i].ch = '*';
7173 sel_opts3[i].rval = '*';
7174 sel_opts3[i].name = "*";
7175 sel_opts3[i++].label = N_("Flag");
7178 if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
7179 sel_opts3[i].ch = '|';
7180 sel_opts3[i].rval = '|';
7181 sel_opts3[i].name = "|";
7182 sel_opts3[i++].label = N_("Pipe");
7185 if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */
7186 sel_opts3[i].ch = 'b';
7187 sel_opts3[i].rval = 'b';
7188 sel_opts3[i].name = "B";
7189 sel_opts3[i++].label = N_("Bounce");
7192 if(flags & AC_FROM_THREAD){
7193 if(flags & (AC_COLL | AC_EXPN)){
7194 sel_opts3[i].ch = '/';
7195 sel_opts3[i].rval = '/';
7196 sel_opts3[i].name = "/";
7197 sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse")
7198 : N_("Expand");
7201 sel_opts3[i].ch = ';';
7202 sel_opts3[i].rval = ';';
7203 sel_opts3[i].name = ";";
7204 if(flags & AC_UNSEL)
7205 sel_opts3[i++].label = N_("UnSelect");
7206 else
7207 sel_opts3[i++].label = N_("Select");
7210 if(F_ON(F_ENABLE_PRYNT, state)){ /* this one is invisible */
7211 sel_opts3[i].ch = 'y';
7212 sel_opts3[i].rval = '%';
7213 sel_opts3[i].name = "";
7214 sel_opts3[i++].label = "";
7217 if(!is_imap_stream(stream) || LEVELUIDPLUS(stream)){ /* expunge selected messages */
7218 sel_opts3[i].ch = 'x';
7219 sel_opts3[i].rval = 'x';
7220 sel_opts3[i].name = "X";
7221 sel_opts3[i++].label = N_("Expunge");
7224 sel_opts3[i].ch = KEY_DEL; /* also invisible */
7225 sel_opts3[i].rval = 'd';
7226 sel_opts3[i].name = "";
7227 sel_opts3[i++].label = "";
7229 sel_opts3[i].ch = -1;
7231 snprintf(prompt, sizeof(prompt), "%s command : ",
7232 (flags & AC_FROM_THREAD) ? "THREAD" : "APPLY");
7233 prompt[sizeof(prompt)-1] = '\0';
7234 cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'c', NO_HELP,
7235 RB_SEQ_SENSITIVE);
7236 if(isupper(cmd))
7237 cmd = tolower(cmd);
7239 else{
7240 if(preloadkeystroke == KEY_DEL)
7241 cmd = 'd';
7242 else{
7243 if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke))
7244 cmd = tolower((int) preloadkeystroke);
7245 else
7246 cmd = (int) preloadkeystroke; /* shouldn't happen */
7250 switch(cmd){
7251 case 'd' : /* delete */
7252 we_cancel = busy_cue(NULL, NULL, 1);
7253 rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */
7254 if(we_cancel)
7255 cancel_busy_cue(0);
7256 break;
7258 case 'u' : /* undelete */
7259 we_cancel = busy_cue(NULL, NULL, 1);
7260 rv = cmd_undelete(state, msgmap, agg);
7261 if(we_cancel)
7262 cancel_busy_cue(0);
7263 break;
7265 case 'r' : /* reply */
7266 rv = cmd_reply(state, msgmap, agg);
7267 break;
7269 case 'f' : /* Forward */
7270 rv = cmd_forward(state, msgmap, agg);
7271 break;
7273 case '%' : /* print */
7274 rv = cmd_print(state, msgmap, agg, MsgIndx);
7275 break;
7277 case 't' : /* take address */
7278 rv = cmd_take_addr(state, msgmap, agg);
7279 break;
7281 case 's' : /* save */
7282 rv = cmd_save(state, stream, msgmap, agg, MsgIndx);
7283 break;
7285 case 'e' : /* export */
7286 rv = cmd_export(state, msgmap, q_line, agg);
7287 break;
7289 case '|' : /* pipe */
7290 rv = cmd_pipe(state, msgmap, agg);
7291 break;
7293 case '*' : /* flag */
7294 we_cancel = busy_cue(NULL, NULL, 1);
7295 rv = cmd_flag(state, msgmap, agg);
7296 if(we_cancel)
7297 cancel_busy_cue(0);
7298 break;
7300 case 'b' : /* bounce */
7301 rv = cmd_bounce(state, msgmap, agg);
7302 break;
7304 case '/' :
7305 collapse_or_expand(state, stream, msgmap,
7306 F_ON(F_SLASH_COLL_ENTIRE, ps_global)
7307 ? 0L
7308 : mn_get_cur(msgmap));
7309 break;
7311 case ':' :
7312 select_thread_stmp(state, stream, msgmap);
7313 break;
7315 case 'x' : /* Expunge */
7316 rv = cmd_expunge(state, stream, msgmap, agg);
7317 break;
7319 case 'c' : /* cancel */
7320 cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command"
7321 : "Apply command");
7322 break;
7324 case 'z' : /* default */
7325 q_status_message(SM_INFO, 0, 2,
7326 "Cancelled, there is no default command");
7327 break;
7329 default:
7330 break;
7333 return(rv);
7338 * Select by message number ranges.
7339 * Sets searched bits in mail_elts
7341 * Args limitsrch -- limit search to this searchset
7343 * Returns 0 on success.
7346 select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
7348 int r, end;
7349 long n1, n2, raw;
7350 char number1[16], number2[16], numbers[80], *p, *t;
7351 HelpType help;
7352 MESSAGECACHE *mc;
7354 numbers[0] = '\0';
7355 ps_global->mangled_footer = 1;
7356 help = NO_HELP;
7357 while(1){
7358 int flags = OE_APPEND_CURRENT;
7360 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7361 sizeof(numbers), _(select_num), NULL, help, &flags);
7362 if(r == 4)
7363 continue;
7365 if(r == 3){
7366 help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
7367 continue;
7370 for(t = p = numbers; *p ; p++) /* strip whitespace */
7371 if(!isspace((unsigned char)*p))
7372 *t++ = *p;
7374 *t = '\0';
7376 if(r == 1 || numbers[0] == '\0'){
7377 cmd_cancelled("Selection by number");
7378 return(1);
7380 else
7381 break;
7384 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7385 if((mc = mail_elt(stream, n1)) != NULL)
7386 mc->searched = 0; /* clear searched bits */
7388 for(p = numbers; *p ; p++){
7389 t = number1;
7390 while(*p && isdigit((unsigned char)*p))
7391 *t++ = *p++;
7393 *t = '\0';
7395 end = 0;
7396 if(number1[0] == '\0'){
7397 if(*p == '-'){
7398 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7399 _("Invalid number range, missing number before \"-\": %s"),
7400 numbers);
7401 return(1);
7403 else if(!strucmp("end", p)){
7404 end = 1;
7405 p += strlen("end");
7407 else{
7408 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7409 _("Invalid message number: %s"), numbers);
7410 return(1);
7414 if(end)
7415 n1 = mn_get_total(msgmap);
7416 else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
7417 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7418 _("\"%s\" out of message number range"),
7419 long2string(n1));
7420 return(1);
7423 t = number2;
7424 if(*p == '-'){
7425 while(*++p && isdigit((unsigned char)*p))
7426 *t++ = *p;
7428 *t = '\0';
7430 end = 0;
7431 if(number2[0] == '\0'){
7432 if(!strucmp("end", p)){
7433 end = 1;
7434 p += strlen("end");
7436 else{
7437 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7438 _("Invalid number range, missing number after \"-\": %s"),
7439 numbers);
7440 return(1);
7444 if(end)
7445 n2 = mn_get_total(msgmap);
7446 else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){
7447 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7448 _("\"%s\" out of message number range"),
7449 long2string(n2));
7450 return(1);
7453 if(n2 <= n1){
7454 char t[20];
7456 strncpy(t, long2string(n1), sizeof(t));
7457 t[sizeof(t)-1] = '\0';
7458 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7459 _("Invalid reverse message number range: %s-%s"),
7460 t, long2string(n2));
7461 return(1);
7464 for(;n1 <= n2; n1++){
7465 raw = mn_m2raw(msgmap, n1);
7466 if(raw > 0L
7467 && (!(limitsrch && *limitsrch)
7468 || in_searchset(*limitsrch, (unsigned long) raw)))
7469 mm_searched(stream, raw);
7472 else{
7473 raw = mn_m2raw(msgmap, n1);
7474 if(raw > 0L
7475 && (!(limitsrch && *limitsrch)
7476 || in_searchset(*limitsrch, (unsigned long) raw)))
7477 mm_searched(stream, raw);
7480 if(*p == '\0')
7481 break;
7484 return(0);
7489 * Select by thread number ranges.
7490 * Sets searched bits in mail_elts
7492 * Args limitsrch -- limit search to this searchset
7494 * Returns 0 on success.
7497 select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset)
7499 int r, end;
7500 long n1, n2;
7501 char number1[16], number2[16], numbers[80], *p, *t;
7502 HelpType help;
7503 PINETHRD_S *thrd = NULL;
7504 MESSAGECACHE *mc;
7506 numbers[0] = '\0';
7507 ps_global->mangled_footer = 1;
7508 help = NO_HELP;
7509 while(1){
7510 int flags = OE_APPEND_CURRENT;
7512 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7513 sizeof(numbers), _(select_num), NULL, help, &flags);
7514 if(r == 4)
7515 continue;
7517 if(r == 3){
7518 help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP;
7519 continue;
7522 for(t = p = numbers; *p ; p++) /* strip whitespace */
7523 if(!isspace((unsigned char)*p))
7524 *t++ = *p;
7526 *t = '\0';
7528 if(r == 1 || numbers[0] == '\0'){
7529 cmd_cancelled("Selection by number");
7530 return(1);
7532 else
7533 break;
7536 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7537 if((mc = mail_elt(stream, n1)) != NULL)
7538 mc->searched = 0; /* clear searched bits */
7540 for(p = numbers; *p ; p++){
7541 t = number1;
7542 while(*p && isdigit((unsigned char)*p))
7543 *t++ = *p++;
7545 *t = '\0';
7547 end = 0;
7548 if(number1[0] == '\0'){
7549 if(*p == '-'){
7550 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7551 _("Invalid number range, missing number before \"-\": %s"),
7552 numbers);
7553 return(1);
7555 else if(!strucmp("end", p)){
7556 end = 1;
7557 p += strlen("end");
7559 else{
7560 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7561 _("Invalid thread number: %s"), numbers);
7562 return(1);
7566 if(end)
7567 n1 = msgmap->max_thrdno;
7568 else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){
7569 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7570 _("\"%s\" out of thread number range"),
7571 long2string(n1));
7572 return(1);
7575 t = number2;
7576 if(*p == '-'){
7578 while(*++p && isdigit((unsigned char)*p))
7579 *t++ = *p;
7581 *t = '\0';
7583 end = 0;
7584 if(number2[0] == '\0'){
7585 if(!strucmp("end", p)){
7586 end = 1;
7587 p += strlen("end");
7589 else{
7590 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7591 _("Invalid number range, missing number after \"-\": %s"),
7592 numbers);
7593 return(1);
7597 if(end)
7598 n2 = msgmap->max_thrdno;
7599 else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){
7600 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7601 _("\"%s\" out of thread number range"),
7602 long2string(n2));
7603 return(1);
7606 if(n2 <= n1){
7607 char t[20];
7609 strncpy(t, long2string(n1), sizeof(t));
7610 t[sizeof(t)-1] = '\0';
7611 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7612 _("Invalid reverse message number range: %s-%s"),
7613 t, long2string(n2));
7614 return(1);
7617 for(;n1 <= n2; n1++){
7618 thrd = find_thread_by_number(stream, msgmap, n1, thrd);
7620 if(thrd)
7621 set_search_bit_for_thread(stream, thrd, msgset);
7624 else{
7625 thrd = find_thread_by_number(stream, msgmap, n1, NULL);
7627 if(thrd)
7628 set_search_bit_for_thread(stream, thrd, msgset);
7631 if(*p == '\0')
7632 break;
7635 return(0);
7640 * Select by message dates.
7641 * Sets searched bits in mail_elts
7643 * Args limitsrch -- limit search to this searchset
7645 * Returns 0 on success.
7648 select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
7650 int r, we_cancel = 0, when = 0;
7651 char date[100], defdate[100], prompt[128];
7652 time_t seldate = time(0);
7653 struct tm *seldate_tm;
7654 SEARCHPGM *pgm;
7655 HelpType help;
7656 static struct _tense {
7657 char *preamble,
7658 *range,
7659 *scope;
7660 } tense[] = {
7661 {"were ", "SENT SINCE", " (inclusive)"},
7662 {"were ", "SENT BEFORE", " (exclusive)"},
7663 {"were ", "SENT ON", "" },
7664 {"", "ARRIVED SINCE", " (inclusive)"},
7665 {"", "ARRIVED BEFORE", " (exclusive)"},
7666 {"", "ARRIVED ON", "" }
7669 date[0] = '\0';
7670 ps_global->mangled_footer = 1;
7671 help = NO_HELP;
7674 * If talking to an old server, default to SINCE instead of
7675 * SENTSINCE, which was added later.
7677 if(is_imap_stream(stream) && !modern_imap_stream(stream))
7678 when = 3;
7680 while(1){
7681 int flags = OE_APPEND_CURRENT;
7683 seldate_tm = localtime(&seldate);
7684 snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday,
7685 month_abbrev(seldate_tm->tm_mon + 1),
7686 seldate_tm->tm_year + 1900);
7687 defdate[sizeof(defdate)-1] = '\0';
7688 snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ",
7689 tense[when].preamble, tense[when].range,
7690 tense[when].scope, defdate);
7691 prompt[sizeof(prompt)-1] = '\0';
7692 r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date),
7693 prompt, sel_date_opt, help, &flags);
7694 switch (r){
7695 case 1 :
7696 cmd_cancelled("Selection by date");
7697 return(1);
7699 case 3 :
7700 help = (help == NO_HELP) ? h_select_date : NO_HELP;
7701 continue;
7703 case 4 :
7704 continue;
7706 case 11 :
7708 MESSAGECACHE *mc;
7709 long rawno;
7711 if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L
7712 && rawno <= stream->nmsgs
7713 && (mc = mail_elt(stream, rawno))){
7715 /* cache not filled in yet? */
7716 if(mc->day == 0){
7717 char seq[20];
7719 if(stream->dtb && stream->dtb->flags & DR_NEWS){
7720 strncpy(seq,
7721 ulong2string(mail_uid(stream, rawno)),
7722 sizeof(seq));
7723 seq[sizeof(seq)-1] = '\0';
7724 mail_fetch_overview(stream, seq, NULL);
7726 else{
7727 strncpy(seq, long2string(rawno),
7728 sizeof(seq));
7729 seq[sizeof(seq)-1] = '\0';
7730 mail_fetch_fast(stream, seq, 0L);
7734 /* mail_date returns fixed field width date */
7735 mail_date(date, mc);
7736 date[11] = '\0';
7740 continue;
7742 case 12 : /* set default to PREVIOUS day */
7743 seldate -= 86400;
7744 continue;
7746 case 13 : /* set default to NEXT day */
7747 seldate += 86400;
7748 continue;
7750 case 14 :
7751 when = (when+1) % (sizeof(tense) / sizeof(struct _tense));
7752 continue;
7754 default:
7755 break;
7758 removing_leading_white_space(date);
7759 removing_trailing_white_space(date);
7760 if(!*date){
7761 strncpy(date, defdate, sizeof(date));
7762 date[sizeof(date)-1] = '\0';
7765 break;
7768 if((pgm = mail_newsearchpgm()) != NULL){
7769 MESSAGECACHE elt;
7770 short converted_date;
7772 if(mail_parse_date(&elt, (unsigned char *) date)){
7773 converted_date = mail_shortdate(elt.year, elt.month, elt.day);
7775 switch(when){
7776 case 0:
7777 pgm->sentsince = converted_date;
7778 break;
7779 case 1:
7780 pgm->sentbefore = converted_date;
7781 break;
7782 case 2:
7783 pgm->senton = converted_date;
7784 break;
7785 case 3:
7786 pgm->since = converted_date;
7787 break;
7788 case 4:
7789 pgm->before = converted_date;
7790 break;
7791 case 5:
7792 pgm->on = converted_date;
7793 break;
7796 pgm->msgno = (limitsrch ? *limitsrch : NULL);
7798 if(ps_global && ps_global->ttyo){
7799 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
7800 ps_global->mangled_footer = 1;
7803 we_cancel = busy_cue(_("Selecting"), NULL, 1);
7805 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
7807 if(we_cancel)
7808 cancel_busy_cue(0);
7810 /* we know this was freed in mail_search, let caller know */
7811 if(limitsrch)
7812 *limitsrch = NULL;
7814 else{
7815 mail_free_searchpgm(&pgm);
7816 q_status_message1(SM_ORDER, 3, 3,
7817 _("Invalid date entered: %s"), date);
7818 return(1);
7822 return(0);
7827 * Select by searching in message headers or body.
7828 * Sets searched bits in mail_elts
7830 * Args limitsrch -- limit search to this searchset
7832 * Returns 0 on success.
7835 select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
7837 int r, ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
7838 int not = 0, me = 0;
7839 char sstring[80], savedsstring[80], tmp[128];
7840 char *p, *sval = NULL;
7841 char buftmp[MAILTMPLEN], namehdr[80];
7842 ESCKEY_S ekey[8];
7843 ENVELOPE *env = NULL;
7844 HelpType help;
7845 unsigned flagsforhist = 0;
7846 static HISTORY_S *history = NULL;
7847 static char *recip = "RECIPIENTS";
7848 static char *partic = "PARTICIPANTS";
7849 static char *match_me = N_("[Match_My_Addresses]");
7850 static char *dont_match_me = N_("[Don't_Match_My_Addresses]");
7852 ps_global->mangled_footer = 1;
7853 savedsstring[0] = '\0';
7854 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
7856 while(1){
7857 type = radio_buttons(not ? _(sel_text_not) : _(sel_text),
7858 -FOOTER_ROWS(ps_global), sel_text_opt,
7859 's', 'x', NO_HELP, RB_NORM|RB_RET_HELP);
7861 if(type == '!')
7862 not = !not;
7863 else if(type == 3){
7864 helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS",
7865 HLPD_SIMPLE);
7866 ps_global->mangled_screen = 1;
7868 else
7869 break;
7873 * prepare some friendly defaults...
7875 switch(type){
7876 case 't' : /* address fields, offer To or From */
7877 case 'f' :
7878 case 'c' :
7879 case 'r' :
7880 case 'p' :
7881 sval = (type == 't') ? "TO" :
7882 (type == 'f') ? "FROM" :
7883 (type == 'c') ? "CC" :
7884 (type == 'r') ? recip : partic;
7885 ekey[ekeyi].ch = ctrl('T');
7886 ekey[ekeyi].name = "^T";
7887 ekey[ekeyi].rval = 10;
7888 /* TRANSLATORS: use Current To Address */
7889 ekey[ekeyi++].label = N_("Cur To");
7890 ekey[ekeyi].ch = ctrl('R');
7891 ekey[ekeyi].name = "^R";
7892 ekey[ekeyi].rval = 11;
7893 /* TRANSLATORS: use Current From Address */
7894 ekey[ekeyi++].label = N_("Cur From");
7895 ekey[ekeyi].ch = ctrl('W');
7896 ekey[ekeyi].name = "^W";
7897 ekey[ekeyi].rval = 12;
7898 /* TRANSLATORS: use Current Cc Address */
7899 ekey[ekeyi++].label = N_("Cur Cc");
7900 ekey[ekeyi].ch = ctrl('Y');
7901 ekey[ekeyi].name = "^Y";
7902 ekey[ekeyi].rval = 13;
7903 /* TRANSLATORS: Match Me means match my address */
7904 ekey[ekeyi++].label = N_("Match Me");
7905 ekey[ekeyi].ch = 0;
7906 ekey[ekeyi].name = "";
7907 ekey[ekeyi].rval = 0;
7908 ekey[ekeyi++].label = "";
7909 break;
7911 case 's' :
7912 sval = "SUBJECT";
7913 ekey[ekeyi].ch = ctrl('X');
7914 ekey[ekeyi].name = "^X";
7915 ekey[ekeyi].rval = 14;
7916 /* TRANSLATORS: use Current Subject */
7917 ekey[ekeyi++].label = N_("Cur Subject");
7918 break;
7920 case 'a' :
7921 sval = "TEXT";
7922 break;
7924 case 'b' :
7925 sval = "BODYTEXT";
7926 break;
7928 case 'h' :
7929 strncpy(tmp, "Name of HEADER to match : ", sizeof(tmp)-1);
7930 tmp[sizeof(tmp)-1] = '\0';
7931 flags = OE_APPEND_CURRENT;
7932 namehdr[0] = '\0';
7933 r = 'x';
7934 while (r == 'x'){
7935 int done = 0;
7937 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
7938 sizeof(namehdr), tmp, ekey, NO_HELP, &flags);
7939 if (r == 1){
7940 cmd_cancelled("Selection by text");
7941 return(1);
7943 removing_leading_white_space(namehdr);
7944 while(!done){
7945 while ((namehdr[0] != '\0') && /* remove trailing ":" */
7946 (namehdr[strlen(namehdr) - 1] == ':'))
7947 namehdr[strlen(namehdr) - 1] = '\0';
7948 if ((namehdr[0] != '\0')
7949 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
7950 removing_trailing_white_space(namehdr);
7951 else
7952 done++;
7954 if (strchr(namehdr,' ') || strchr(namehdr,'\t') ||
7955 strchr(namehdr,':'))
7956 namehdr[0] = '\0';
7957 if (namehdr[0] == '\0')
7958 r = 'x';
7960 sval = namehdr;
7961 break;
7963 case 'x':
7964 break;
7966 default:
7967 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
7968 return(1);
7971 ekey[ekeyi].ch = KEY_UP;
7972 ekey[ekeyi].rval = 30;
7973 ekey[ekeyi].name = "";
7974 ku = ekeyi;
7975 ekey[ekeyi++].label = "";
7977 ekey[ekeyi].ch = KEY_DOWN;
7978 ekey[ekeyi].rval = 31;
7979 ekey[ekeyi].name = "";
7980 ekey[ekeyi++].label = "";
7982 ekey[ekeyi].ch = -1;
7984 if(type != 'x'){
7986 init_hist(&history, HISTSIZE);
7988 if(ekey[0].ch > -1 && msgno > 0L
7989 && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),
7990 NULL)))
7991 ekey[0].ch = -1;
7993 sstring[0] = '\0';
7994 help = NO_HELP;
7995 r = type;
7996 while(r != 'x'){
7997 if(not)
7998 /* TRANSLATORS: character String in message <message number> to NOT match : " */
7999 snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval);
8000 else
8001 snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval);
8003 if(items_in_hist(history) > 0){
8004 ekey[ku].name = HISTORY_UP_KEYNAME;
8005 ekey[ku].label = HISTORY_KEYLABEL;
8006 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
8007 ekey[ku+1].label = HISTORY_KEYLABEL;
8009 else{
8010 ekey[ku].name = "";
8011 ekey[ku].label = "";
8012 ekey[ku+1].name = "";
8013 ekey[ku+1].label = "";
8016 flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
8017 r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
8018 79, tmp, ekey, help, &flags);
8020 if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me)))))
8021 me = 0;
8023 switch(r){
8024 case 3 :
8025 help = (help == NO_HELP)
8026 ? (not
8027 ? ((type == 'f') ? h_select_txt_not_from
8028 : (type == 't') ? h_select_txt_not_to
8029 : (type == 'c') ? h_select_txt_not_cc
8030 : (type == 's') ? h_select_txt_not_subj
8031 : (type == 'a') ? h_select_txt_not_all
8032 : (type == 'r') ? h_select_txt_not_recip
8033 : (type == 'p') ? h_select_txt_not_partic
8034 : (type == 'b') ? h_select_txt_not_body
8035 : NO_HELP)
8036 : ((type == 'f') ? h_select_txt_from
8037 : (type == 't') ? h_select_txt_to
8038 : (type == 'c') ? h_select_txt_cc
8039 : (type == 's') ? h_select_txt_subj
8040 : (type == 'a') ? h_select_txt_all
8041 : (type == 'r') ? h_select_txt_recip
8042 : (type == 'p') ? h_select_txt_partic
8043 : (type == 'b') ? h_select_txt_body
8044 : NO_HELP))
8045 : NO_HELP;
8047 case 4 :
8048 continue;
8050 case 10 : /* To: default */
8051 if(env && env->to && env->to->mailbox){
8052 snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox,
8053 env->to->host ? "@" : "",
8054 env->to->host ? env->to->host : "");
8055 sstring[sizeof(sstring)-1] = '\0';
8057 continue;
8059 case 11 : /* From: default */
8060 if(env && env->from && env->from->mailbox){
8061 snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox,
8062 env->from->host ? "@" : "",
8063 env->from->host ? env->from->host : "");
8064 sstring[sizeof(sstring)-1] = '\0';
8066 continue;
8068 case 12 : /* Cc: default */
8069 if(env && env->cc && env->cc->mailbox){
8070 snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox,
8071 env->cc->host ? "@" : "",
8072 env->cc->host ? env->cc->host : "");
8073 sstring[sizeof(sstring)-1] = '\0';
8075 continue;
8077 case 13 : /* Match my addresses */
8078 me++;
8079 snprintf(sstring, sizeof(sstring), "%s", not ? _(dont_match_me) : _(match_me));
8080 continue;
8082 case 14 : /* Subject: default */
8083 if(env && env->subject && env->subject[0]){
8084 char *q = NULL;
8086 snprintf(buftmp, sizeof(buftmp), "%.75s", env->subject);
8087 buftmp[sizeof(buftmp)-1] = '\0';
8088 q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
8089 SIZEOF_20KBUF, buftmp);
8090 if(q != env->subject){
8091 snprintf(savedsstring, sizeof(savedsstring), "%.70s", q);
8092 savedsstring[sizeof(savedsstring)-1] = '\0';
8095 snprintf(sstring, sizeof(sstring), "%s", q);
8096 sstring[sizeof(sstring)-1] = '\0';
8099 continue;
8101 case 30 :
8102 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8103 if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){
8104 strncpy(sstring, p, sizeof(sstring));
8105 sstring[sizeof(sstring)-1] = '\0';
8106 if(history->hist[history->curindex]){
8107 flagsforhist = history->hist[history->curindex]->flags;
8108 not = (flagsforhist & 0x1) ? 1 : 0;
8109 me = (flagsforhist & 0x2) ? 1 : 0;
8112 else
8113 Writechar(BELL, 0);
8115 continue;
8117 case 31 :
8118 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8119 if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){
8120 strncpy(sstring, p, sizeof(sstring));
8121 sstring[sizeof(sstring)-1] = '\0';
8122 if(history->hist[history->curindex]){
8123 flagsforhist = history->hist[history->curindex]->flags;
8124 not = (flagsforhist & 0x1) ? 1 : 0;
8125 me = (flagsforhist & 0x2) ? 1 : 0;
8128 else
8129 Writechar(BELL, 0);
8131 continue;
8133 default :
8134 break;
8137 if(r == 1 || sstring[0] == '\0')
8138 r = 'x';
8140 break;
8144 if(type == 'x' || r == 'x'){
8145 cmd_cancelled("Selection by text");
8146 return(1);
8149 if(ps_global && ps_global->ttyo){
8150 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8151 ps_global->mangled_footer = 1;
8154 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8156 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8157 save_hist(history, sstring, flagsforhist, NULL);
8159 rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch);
8160 if(we_cancel)
8161 cancel_busy_cue(0);
8163 return(rv);
8168 * Select by message size.
8169 * Sets searched bits in mail_elts
8171 * Args limitsrch -- limit search to this searchset
8173 * Returns 0 on success.
8176 select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch)
8178 int r, large = 1, we_cancel = 0;
8179 unsigned long n, mult = 1L, numerator = 0L, divisor = 1L;
8180 char size[16], numbers[80], *p, *t;
8181 HelpType help;
8182 SEARCHPGM *pgm;
8183 long flags = (SE_NOPREFETCH | SE_FREE);
8185 numbers[0] = '\0';
8186 ps_global->mangled_footer = 1;
8188 help = NO_HELP;
8189 while(1){
8190 int flgs = OE_APPEND_CURRENT;
8192 sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger;
8194 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
8195 sizeof(numbers), large ? _(select_size_larger_msg)
8196 : _(select_size_smaller_msg),
8197 sel_size_opt, help, &flgs);
8198 if(r == 4)
8199 continue;
8201 if(r == 14){
8202 large = 1 - large;
8203 continue;
8206 if(r == 3){
8207 help = (help == NO_HELP) ? (large ? h_select_by_larger_size
8208 : h_select_by_smaller_size)
8209 : NO_HELP;
8210 continue;
8213 for(t = p = numbers; *p ; p++) /* strip whitespace */
8214 if(!isspace((unsigned char)*p))
8215 *t++ = *p;
8217 *t = '\0';
8219 if(r == 1 || numbers[0] == '\0'){
8220 cmd_cancelled("Selection by size");
8221 return(1);
8223 else
8224 break;
8227 if(numbers[0] == '-'){
8228 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8229 _("Invalid size entered: %s"), numbers);
8230 return(1);
8233 t = size;
8234 p = numbers;
8236 while(*p && isdigit((unsigned char)*p))
8237 *t++ = *p++;
8239 *t = '\0';
8241 if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){
8242 size[0] = '0';
8243 size[1] = '\0';
8246 if(size[0] == '\0'){
8247 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8248 _("Invalid size entered: %s"), numbers);
8249 return(1);
8252 n = strtoul(size, (char **)NULL, 10);
8254 size[0] = '\0';
8255 if(*p == '.'){
8257 * We probably ought to just use atof() to convert 1.1 into a
8258 * double, but since we haven't used atof() anywhere else I'm
8259 * reluctant to use it because of portability concerns.
8261 p++;
8262 t = size;
8263 while(*p && isdigit((unsigned char)*p)){
8264 *t++ = *p++;
8265 divisor *= 10;
8268 *t = '\0';
8270 if(size[0])
8271 numerator = strtoul(size, (char **)NULL, 10);
8274 switch(*p){
8275 case 'g':
8276 case 'G':
8277 mult *= 1000;
8278 /* fall through */
8280 case 'm':
8281 case 'M':
8282 mult *= 1000;
8283 /* fall through */
8285 case 'k':
8286 case 'K':
8287 mult *= 1000;
8288 break;
8291 n = n * mult + (numerator * mult) / divisor;
8293 pgm = mail_newsearchpgm();
8294 if(large)
8295 pgm->larger = n;
8296 else
8297 pgm->smaller = n;
8299 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8300 flags |= SE_NOSERVER;
8302 if(ps_global && ps_global->ttyo){
8303 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8304 ps_global->mangled_footer = 1;
8307 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8309 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8310 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8311 /* we know this was freed in mail_search, let caller know */
8312 if(limitsrch)
8313 *limitsrch = NULL;
8315 if(we_cancel)
8316 cancel_busy_cue(0);
8318 return(0);
8323 * visible_searchset -- return c-client search set unEXLDed
8324 * sequence numbers
8326 SEARCHSET *
8327 visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap)
8329 long n, run;
8330 SEARCHSET *full_set = NULL, **set;
8333 * If we're talking to anything other than a server older than
8334 * imap 4rev1, build a searchset otherwise it'll choke.
8336 if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
8337 if(any_lflagged(msgmap, MN_EXLD)){
8338 for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
8339 if(get_lflag(stream, NULL, n, MN_EXLD)){
8340 if(run){ /* previous NOT excluded? */
8341 if(run > 1L)
8342 (*set)->last = n - 1L;
8344 set = &(*set)->next;
8345 run = 0L;
8348 else if(run++){ /* next in run */
8349 (*set)->last = n;
8351 else{ /* start of run */
8352 *set = mail_newsearchset();
8353 (*set)->first = n;
8356 else{
8357 full_set = mail_newsearchset();
8358 full_set->first = 1L;
8359 full_set->last = stream->nmsgs;
8363 return(full_set);
8368 * Select by message status bits.
8369 * Sets searched bits in mail_elts
8371 * Args limitsrch -- limit search to this searchset
8373 * Returns 0 on success.
8376 select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch)
8378 int s, not = 0, we_cancel = 0, rv;
8380 while(1){
8381 s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag),
8382 -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x',
8383 NO_HELP, RB_NORM|RB_RET_HELP);
8385 if(s == 'x'){
8386 cmd_cancelled("Selection by status");
8387 return(1);
8389 else if(s == 3){
8390 helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"),
8391 HLPD_SIMPLE);
8392 ps_global->mangled_screen = 1;
8394 else if(s == '!')
8395 not = !not;
8396 else
8397 break;
8400 if(ps_global && ps_global->ttyo){
8401 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8402 ps_global->mangled_footer = 1;
8405 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8406 rv = agg_flag_select(stream, not, s, limitsrch);
8407 if(we_cancel)
8408 cancel_busy_cue(0);
8410 return(rv);
8415 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8416 * Sets searched bits in mail_elts
8418 * Args limitsrch -- limit search to this searchset
8420 * Returns 0 on success.
8423 select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
8425 char rulenick[1000], *nick;
8426 PATGRP_S *patgrp;
8427 int r, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
8428 | ROLE_DO_INCOLS
8429 | ROLE_DO_ROLES
8430 | ROLE_DO_SCORES
8431 | ROLE_DO_OTHER
8432 | ROLE_DO_FILTER;
8434 rulenick[0] = '\0';
8435 ps_global->mangled_footer = 1;
8438 int oe_flags;
8440 oe_flags = OE_APPEND_CURRENT;
8441 r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0,
8442 sizeof(rulenick),
8443 not ? _("Rule to NOT match: ")
8444 : _("Rule to match: "),
8445 sel_key_opt, NO_HELP, &oe_flags);
8447 if(r == 14){
8448 /* select rulenick from a list */
8449 if((nick=choose_a_rule(rflags)) != NULL){
8450 strncpy(rulenick, nick, sizeof(rulenick)-1);
8451 rulenick[sizeof(rulenick)-1] = '\0';
8452 fs_give((void **) &nick);
8454 else
8455 r = 4;
8457 else if(r == '!')
8458 not = !not;
8460 if(r == 3){
8461 helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE);
8462 ps_global->mangled_screen = 1;
8464 else if(r == 1){
8465 cmd_cancelled("Selection by Rule");
8466 return(1);
8469 removing_leading_and_trailing_white_space(rulenick);
8471 }while(r == 3 || r == 4 || r == '!');
8475 * The approach of requiring a nickname instead of just allowing the
8476 * user to select from the list of rules has the drawback that a rule
8477 * may not have a nickname, or there may be more than one rule with
8478 * the same nickname. However, it has the benefit of allowing the user
8479 * to type in the nickname and, most importantly, allows us to set
8480 * up the ! (not). We could incorporate the ! into the selection
8481 * screen, but this is easier and also allows the typing of nicks.
8482 * User can just set up nicknames if they want to use this feature.
8484 patgrp = nick_to_patgrp(rulenick, rflags);
8486 if(patgrp){
8487 if(ps_global && ps_global->ttyo){
8488 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8489 ps_global->mangled_footer = 1;
8492 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8493 match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL,
8494 get_msg_score,
8495 (not ? MP_NOT : 0) | SE_NOPREFETCH);
8496 free_patgrp(&patgrp);
8497 if(we_cancel)
8498 cancel_busy_cue(0);
8501 if(limitsrch && *limitsrch){
8502 mail_free_searchset(limitsrch);
8503 *limitsrch = NULL;
8506 return(0);
8511 * Allow user to choose a rule from their list of rules.
8513 * Returns an allocated rule nickname on success, NULL otherwise.
8515 char *
8516 choose_a_rule(int rflags)
8518 char *choice = NULL;
8519 char **rule_list, **lp;
8520 int cnt = 0;
8521 PAT_S *pat;
8522 PAT_STATE pstate;
8524 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){
8525 q_status_message(SM_ORDER, 3, 3,
8526 _("No rules available. Use Setup/Rules to add some."));
8527 return(choice);
8531 * Build a list of rules to choose from.
8534 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8535 cnt++;
8537 if(cnt <= 0){
8538 q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
8539 return(choice);
8542 lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
8543 memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
8545 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8546 *lp++ = cpystr((pat->patgrp && pat->patgrp->nick)
8547 ? pat->patgrp->nick : "?");
8549 /* TRANSLATORS: SELECT A RULE is a screen title
8550 TRANSLATORS: Print something1 using something2.
8551 "rules" is something1 */
8552 choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"),
8553 _("rules"), h_select_rule_screen,
8554 _("HELP FOR SELECTING A RULE NICKNAME"), NULL);
8556 if(!choice)
8557 q_status_message(SM_ORDER, 1, 4, "No choice");
8559 free_list_array(&rule_list);
8561 return(choice);
8566 * Select by current thread.
8567 * Sets searched bits in mail_elts for this entire thread
8569 * Args limitsrch -- limit search to this searchset
8571 * Returns 0 on success.
8574 select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
8576 long n;
8577 PINETHRD_S *thrd = NULL;
8578 int ret = 1;
8579 MESSAGECACHE *mc;
8581 if(!stream)
8582 return(ret);
8584 for(n = 1L; n <= stream->nmsgs; n++)
8585 if((mc = mail_elt(stream, n)) != NULL)
8586 mc->searched = 0; /* clear searched bits */
8588 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
8589 if(thrd && thrd->top && thrd->top != thrd->rawno)
8590 thrd = fetch_thread(stream, thrd->top);
8593 * This doesn't unselect if the thread is already selected
8594 * (like select current does), it always selects.
8595 * There is no way to select ! this thread.
8597 if(thrd){
8598 set_search_bit_for_thread(stream, thrd, limitsrch);
8599 ret = 0;
8602 return(ret);
8607 * Select by message keywords.
8608 * Sets searched bits in mail_elts
8610 * Args limitsrch -- limit search to this searchset
8612 * Returns 0 on success.
8615 select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
8617 int r, not = 0, we_cancel = 0;
8618 char keyword[MAXUSERFLAG+1], *kword;
8619 char *error = NULL, *p, *prompt;
8620 HelpType help;
8621 SEARCHPGM *pgm;
8623 keyword[0] = '\0';
8624 ps_global->mangled_footer = 1;
8626 help = NO_HELP;
8628 int oe_flags;
8630 if(error){
8631 q_status_message(SM_ORDER, 3, 4, error);
8632 fs_give((void **) &error);
8635 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
8636 if(not)
8637 prompt = _("Keyword (or keyword initial) to NOT match: ");
8638 else
8639 prompt = _("Keyword (or keyword initial) to match: ");
8641 else{
8642 if(not)
8643 prompt = _("Keyword to NOT match: ");
8644 else
8645 prompt = _("Keyword to match: ");
8648 oe_flags = OE_APPEND_CURRENT;
8649 r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0,
8650 sizeof(keyword),
8651 prompt, sel_key_opt, help, &oe_flags);
8653 if(r == 14){
8654 /* select keyword from a list */
8655 if((kword=choose_a_keyword()) != NULL){
8656 strncpy(keyword, kword, sizeof(keyword)-1);
8657 keyword[sizeof(keyword)-1] = '\0';
8658 fs_give((void **) &kword);
8660 else
8661 r = 4;
8663 else if(r == '!')
8664 not = !not;
8666 if(r == 3)
8667 help = help == NO_HELP ? h_select_keyword : NO_HELP;
8668 else if(r == 1){
8669 cmd_cancelled("Selection by keyword");
8670 return(1);
8673 removing_leading_and_trailing_white_space(keyword);
8675 }while(r == 3 || r == 4 || r == '!' || keyword_check(keyword, &error));
8678 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
8679 p = initial_to_keyword(keyword);
8680 if(p != keyword){
8681 strncpy(keyword, p, sizeof(keyword)-1);
8682 keyword[sizeof(keyword)-1] = '\0';
8687 * We want to check the keyword, not the nickname of the keyword,
8688 * so convert it to the keyword if necessary.
8690 p = nick_to_keyword(keyword);
8691 if(p != keyword){
8692 strncpy(keyword, p, sizeof(keyword)-1);
8693 keyword[sizeof(keyword)-1] = '\0';
8696 pgm = mail_newsearchpgm();
8697 if(not){
8698 pgm->unkeyword = mail_newstringlist();
8699 pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword);
8700 pgm->unkeyword->text.size = strlen(keyword);
8702 else{
8703 pgm->keyword = mail_newstringlist();
8704 pgm->keyword->text.data = (unsigned char *) cpystr(keyword);
8705 pgm->keyword->text.size = strlen(keyword);
8708 if(ps_global && ps_global->ttyo){
8709 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8710 ps_global->mangled_footer = 1;
8713 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8715 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8716 pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE);
8717 /* we know this was freed in mail_search, let caller know */
8718 if(limitsrch)
8719 *limitsrch = NULL;
8721 if(we_cancel)
8722 cancel_busy_cue(0);
8724 return(0);
8729 * Allow user to choose a keyword from their list of keywords.
8731 * Returns an allocated keyword on success, NULL otherwise.
8733 char *
8734 choose_a_keyword(void)
8736 char *choice = NULL;
8737 char **keyword_list, **lp;
8738 int cnt;
8739 KEYWORD_S *kw;
8742 * Build a list of keywords to choose from.
8745 for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next)
8746 cnt++;
8748 if(cnt <= 0){
8749 q_status_message(SM_ORDER, 3, 4,
8750 _("No keywords defined, use \"keywords\" option in Setup/Config"));
8751 return(choice);
8754 lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list));
8755 memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list));
8757 for(kw = ps_global->keywords; kw; kw = kw->next)
8758 *lp++ = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
8760 /* TRANSLATORS: SELECT A KEYWORD is a screen title
8761 TRANSLATORS: Print something1 using something2.
8762 "keywords" is something1 */
8763 choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"),
8764 _("keywords"), h_select_keyword_screen,
8765 _("HELP FOR SELECTING A KEYWORD"), NULL);
8767 if(!choice)
8768 q_status_message(SM_ORDER, 1, 4, "No choice");
8770 free_list_array(&keyword_list);
8772 return(choice);
8777 * Allow user to choose a list of keywords from their list of keywords.
8779 * Returns allocated list.
8781 char **
8782 choose_list_of_keywords(void)
8784 LIST_SEL_S *listhead, *ls, *p;
8785 char **ret = NULL;
8786 int cnt, i;
8787 KEYWORD_S *kw;
8790 * Build a list of keywords to choose from.
8793 p = listhead = NULL;
8794 for(kw = ps_global->keywords; kw; kw = kw->next){
8796 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
8797 memset(ls, 0, sizeof(*ls));
8798 ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
8800 if(p){
8801 p->next = ls;
8802 p = p->next;
8804 else
8805 listhead = p = ls;
8808 if(!listhead)
8809 return(ret);
8811 /* TRANSLATORS: SELECT KEYWORDS is a screen title
8812 Print something1 using something2.
8813 "keywords" is something1 */
8814 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
8815 _("SELECT KEYWORDS"), _("keywords"),
8816 h_select_multkeyword_screen,
8817 _("HELP FOR SELECTING KEYWORDS"), NULL)){
8818 for(cnt = 0, p = listhead; p; p = p->next)
8819 if(p->selected)
8820 cnt++;
8822 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
8823 memset(ret, 0, (cnt+1) * sizeof(*ret));
8824 for(i = 0, p = listhead; p; p = p->next)
8825 if(p->selected)
8826 ret[i++] = cpystr(p->item ? p->item : "");
8829 free_list_sel(&listhead);
8831 return(ret);
8836 * Allow user to choose a charset
8838 * Returns an allocated charset on success, NULL otherwise.
8840 char *
8841 choose_a_charset(int which_charsets)
8843 char *choice = NULL;
8844 char **charset_list, **lp;
8845 const CHARSET *cs;
8846 int cnt;
8849 * Build a list of charsets to choose from.
8852 for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){
8853 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
8854 && ((which_charsets & CAC_ALL)
8855 || (which_charsets & CAC_POSTING
8856 && cs->flags & CF_POSTING)
8857 || (which_charsets & CAC_DISPLAY
8858 && cs->type != CT_2022
8859 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
8860 cnt++;
8863 if(cnt <= 0){
8864 q_status_message(SM_ORDER, 3, 4,
8865 _("No charsets found? Enter charset manually."));
8866 return(choice);
8869 lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list));
8870 memset(charset_list, 0, (cnt+1) * sizeof(*charset_list));
8872 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
8873 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
8874 && ((which_charsets & CAC_ALL)
8875 || (which_charsets & CAC_POSTING
8876 && cs->flags & CF_POSTING)
8877 || (which_charsets & CAC_DISPLAY
8878 && cs->type != CT_2022
8879 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
8880 *lp++ = cpystr(cs->name);
8883 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
8884 TRANSLATORS: Print something1 using something2.
8885 "character sets" is something1 */
8886 choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"),
8887 _("character sets"), h_select_charset_screen,
8888 _("HELP FOR SELECTING A CHARACTER SET"), NULL);
8890 if(!choice)
8891 q_status_message(SM_ORDER, 1, 4, "No choice");
8893 free_list_array(&charset_list);
8895 return(choice);
8900 * Allow user to choose a list of character sets and/or scripts
8902 * Returns allocated list.
8904 char **
8905 choose_list_of_charsets(void)
8907 LIST_SEL_S *listhead, *ls, *p;
8908 char **ret = NULL;
8909 int cnt, i, got_one;
8910 const CHARSET *cs;
8911 SCRIPT *s;
8912 char *q, *t;
8913 long width, limit;
8914 char buf[1024], *folded;
8917 * Build a list of charsets to choose from.
8920 p = listhead = NULL;
8922 /* this width is determined by select_from_list_screen() */
8923 width = ps_global->ttyo->screen_cols - 4;
8925 /* first comes a list of scripts (sets of character sets) */
8926 for(s = utf8_script(NIL); s && s->name; s++){
8928 limit = sizeof(buf)-1;
8929 q = buf;
8930 memset(q, 0, limit+1);
8932 if(s->name)
8933 sstrncpy(&q, s->name, limit);
8935 if(s->description){
8936 sstrncpy(&q, " (", limit-(q-buf));
8937 sstrncpy(&q, s->description, limit-(q-buf));
8938 sstrncpy(&q, ")", limit-(q-buf));
8941 /* add the list of charsets that are in this script */
8942 got_one = 0;
8943 for(cs = utf8_charset(NIL);
8944 cs && cs->name && (q-buf) < limit; cs++){
8945 if(cs->script & s->script){
8947 * Filter out some un-useful members of the list.
8948 * UTF-7 and UTF-8 weren't actually in the list at the
8949 * time this was written. Just making sure.
8951 if(!strucmp(cs->name, "ISO-2022-JP-2")
8952 || !strucmp(cs->name, "UTF-7")
8953 || !strucmp(cs->name, "UTF-8"))
8954 continue;
8956 if(got_one)
8957 sstrncpy(&q, " ", limit-(q-buf));
8958 else{
8959 got_one = 1;
8960 sstrncpy(&q, " {", limit-(q-buf));
8963 sstrncpy(&q, cs->name, limit-(q-buf));
8967 if(got_one)
8968 sstrncpy(&q, "}", limit-(q-buf));
8970 /* fold this line so that it can all be seen on the screen */
8971 folded = fold(buf, width, width, "", " ", FLD_NONE);
8972 if(folded){
8973 t = folded;
8974 while(t && *t && (q = strindex(t, '\n')) != NULL){
8975 *q = '\0';
8977 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
8978 memset(ls, 0, sizeof(*ls));
8979 if(t == folded)
8980 ls->item = cpystr(s->name);
8981 else
8982 ls->flags = SFL_NOSELECT;
8984 ls->display_item = cpystr(t);
8986 t = q+1;
8988 if(p){
8989 p->next = ls;
8990 p = p->next;
8992 else{
8993 /* add a heading */
8994 listhead = (LIST_SEL_S *) fs_get(sizeof(*ls));
8995 memset(listhead, 0, sizeof(*listhead));
8996 listhead->flags = SFL_NOSELECT;
8997 listhead->display_item =
8998 cpystr(_("Scripts representing groups of related character sets"));
8999 listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls));
9000 memset(listhead->next, 0, sizeof(*listhead));
9001 listhead->next->flags = SFL_NOSELECT;
9002 listhead->next->display_item =
9003 cpystr(repeat_char(width, '-'));
9005 listhead->next->next = ls;
9006 p = ls;
9010 fs_give((void **) &folded);
9014 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9015 memset(ls, 0, sizeof(*ls));
9016 ls->flags = SFL_NOSELECT;
9017 if(p){
9018 p->next = ls;
9019 p = p->next;
9021 else
9022 listhead = p = ls;
9024 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9025 memset(ls, 0, sizeof(*ls));
9026 ls->flags = SFL_NOSELECT;
9027 ls->display_item =
9028 cpystr(_("Individual character sets, may be mixed with scripts"));
9029 p->next = ls;
9030 p = p->next;
9032 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9033 memset(ls, 0, sizeof(*ls));
9034 ls->flags = SFL_NOSELECT;
9035 ls->display_item =
9036 cpystr(repeat_char(width, '-'));
9037 p->next = ls;
9038 p = p->next;
9040 /* then comes a list of individual character sets */
9041 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9042 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9043 memset(ls, 0, sizeof(*ls));
9044 ls->item = cpystr(cs->name);
9046 if(p){
9047 p->next = ls;
9048 p = p->next;
9050 else
9051 listhead = p = ls;
9054 if(!listhead)
9055 return(ret);
9057 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9058 Print something1 using something2.
9059 "character sets" is something1 */
9060 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9061 _("SELECT CHARACTER SETS"), _("character sets"),
9062 h_select_multcharsets_screen,
9063 _("HELP FOR SELECTING CHARACTER SETS"), NULL)){
9064 for(cnt = 0, p = listhead; p; p = p->next)
9065 if(p->selected)
9066 cnt++;
9068 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9069 memset(ret, 0, (cnt+1) * sizeof(*ret));
9070 for(i = 0, p = listhead; p; p = p->next)
9071 if(p->selected)
9072 ret[i++] = cpystr(p->item ? p->item : "");
9075 free_list_sel(&listhead);
9077 return(ret);
9080 /* Report quota summary resources in an IMAP server */
9082 void cmd_quota (struct pine *state)
9084 QUOTALIST *imapquota;
9085 NETMBX mb;
9086 STORE_S *store;
9087 SCROLL_S sargs;
9089 if(!state->mail_stream || !is_imap_stream(state->mail_stream)){
9090 q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders");
9091 return;
9094 if (state->mail_stream
9095 && !sp_dead_stream(state->mail_stream)
9096 && state->mail_stream->mailbox
9097 && *state->mail_stream->mailbox
9098 && mail_valid_net_parse(state->mail_stream->mailbox, &mb))
9099 imap_getquotaroot(state->mail_stream, mb.mailbox);
9101 if(!state->quota) /* failed ? */
9102 return; /* go back... */
9104 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
9105 q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space.");
9106 return;
9109 so_puts(store, "Quota Report for ");
9110 so_puts(store, state->mail_stream->original_mailbox);
9111 so_puts(store, "\n\n");
9113 for (imapquota = state->quota; imapquota; imapquota = imapquota->next){
9115 so_puts(store, _("Resource : "));
9116 so_puts(store, imapquota->name);
9117 so_writec('\n', store);
9119 so_puts(store, _("Usage : "));
9120 so_puts(store, long2string(imapquota->usage));
9121 if(!strucmp(imapquota->name,"STORAGE"))
9122 so_puts(store, " KiB ");
9123 if(!strucmp(imapquota->name,"MESSAGE")){
9124 so_puts(store, _(" message"));
9125 if(imapquota->usage != 1)
9126 so_puts(store, _("s ")); /* plural */
9127 else
9128 so_puts(store, _(" "));
9130 so_writec('(', store);
9131 so_puts(store, long2string(100*imapquota->usage/imapquota->limit));
9132 so_puts(store, "%)\n");
9134 so_puts(store, _("Limit : "));
9135 so_puts(store, long2string(imapquota->limit));
9136 if(!strucmp(imapquota->name,"STORAGE"))
9137 so_puts(store, " KiB\n\n");
9138 if(!strucmp(imapquota->name,"MESSAGE")){
9139 so_puts(store, _(" message"));
9140 if(imapquota->usage != 1)
9141 so_puts(store, _("s\n\n")); /* plural */
9142 else
9143 so_puts(store, _("\n\n"));
9147 memset(&sargs, 0, sizeof(SCROLL_S));
9148 sargs.text.text = so_text(store);
9149 sargs.text.src = CharStar;
9150 sargs.text.desc = _("Quota Resources Summary");
9151 sargs.bar.title = _("QUOTA SUMMARY");
9152 sargs.proc.tool = NULL;
9153 sargs.help.text = h_quota_command;
9154 sargs.help.title = NULL;
9155 sargs.keys.menu = NULL;
9156 setbitmap(sargs.keys.bitmap);
9158 scrolltool(&sargs);
9159 so_give(&store);
9161 if (state->quota)
9162 mail_free_quotalist(&(state->quota));
9165 /*----------------------------------------------------------------------
9166 Prompt the user for the type of sort he desires
9168 Args: state -- pine state pointer
9169 q1 -- Line to prompt on
9171 Returns 0 if it was cancelled, 1 otherwise.
9172 ----*/
9174 select_sort(struct pine *state, int ql, SortOrder *sort, int *rev)
9176 char prompt[200], tmp[3], *p;
9177 int s, i;
9178 int deefault = 'a', retval = 1;
9179 HelpType help;
9180 ESCKEY_S sorts[14];
9182 #ifdef _WINDOWS
9183 DLG_SORTPARAM sortsel;
9185 if (mswin_usedialog ()) {
9187 sortsel.reverse = mn_get_revsort (state->msgmap);
9188 sortsel.cursort = mn_get_sort (state->msgmap);
9189 /* assumption here that HelpType is char ** */
9190 sortsel.helptext = h_select_sort;
9191 sortsel.rval = 0;
9193 if ((retval = os_sortdialog (&sortsel))) {
9194 *sort = sortsel.cursort;
9195 *rev = sortsel.reverse;
9198 return (retval);
9200 #endif
9202 /*----- String together the prompt ------*/
9203 tmp[1] = '\0';
9204 if(F_ON(F_USE_FK,ps_global))
9205 strncpy(prompt, _("Choose type of sort : "), sizeof(prompt));
9206 else
9207 strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "),
9208 sizeof(prompt));
9210 for(i = 0; state->sort_types[i] != EndofList; i++) {
9211 sorts[i].rval = i;
9212 p = sorts[i].label = sort_name(state->sort_types[i]);
9213 while(*(p+1) && islower((unsigned char)*p))
9214 p++;
9216 sorts[i].ch = tolower((unsigned char)(tmp[0] = *p));
9217 sorts[i].name = cpystr(tmp);
9219 if(mn_get_sort(state->msgmap) == state->sort_types[i])
9220 deefault = sorts[i].rval;
9223 sorts[i].ch = 'r';
9224 sorts[i].rval = 'r';
9225 sorts[i].name = cpystr("R");
9226 if(F_ON(F_USE_FK,ps_global))
9227 sorts[i].label = N_("Reverse");
9228 else
9229 sorts[i].label = "";
9231 sorts[++i].ch = -1;
9232 help = h_select_sort;
9234 if((F_ON(F_USE_FK,ps_global)
9235 && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x',
9236 help,RB_NORM)) != 'x'))
9238 (F_OFF(F_USE_FK,ps_global)
9239 && ((s = radio_buttons(prompt,ql,sorts,deefault,'x',
9240 help,RB_NORM)) != 'x'))){
9241 state->mangled_body = 1; /* signal screen's changed */
9242 if(s == 'r')
9243 *rev = !mn_get_revsort(state->msgmap);
9244 else
9245 *sort = state->sort_types[s];
9247 if(F_ON(F_SHOW_SORT, ps_global))
9248 ps_global->mangled_header = 1;
9250 else{
9251 retval = 0;
9252 cmd_cancelled("Sort");
9255 while(--i >= 0)
9256 fs_give((void **)&sorts[i].name);
9258 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9259 return(retval);
9263 /*---------------------------------------------------------------------
9264 Build list of folders in the given context for user selection
9266 Args: c -- pointer to pointer to folder's context context
9267 f -- folder prefix to display
9268 sublist -- whether or not to use 'f's contents as prefix
9269 lister -- function used to do the actual display
9271 Returns: malloc'd string containing sequence, else NULL if
9272 no messages in msgmap with local "selected" flag.
9273 ----*/
9275 display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int))
9277 int rc;
9278 CONTEXT_S *tc;
9279 void (*redraw)(void) = ps_global->redrawer;
9281 push_titlebar_state();
9282 tc = *c;
9283 if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0)
9284 *c = tc;
9286 ClearScreen();
9287 pop_titlebar_state();
9288 redraw_titlebar();
9289 if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
9290 (*ps_global->redrawer)();
9292 if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
9293 return(1);
9295 return(0);
9300 * Allow user to choose a single item from a list of strings.
9302 * Args list -- Array of strings to choose from, NULL terminated.
9303 * displist -- Array of strings to display instead of displaying list.
9304 * Indices correspond to the list array. Display the displist
9305 * but return the item from list if displist non-NULL.
9306 * title -- For conf_scroll_screen
9307 * pdesc -- For conf_scroll_screen
9308 * help -- For conf_scroll_screen
9309 * htitle -- For conf_scroll_screen
9311 * Returns an allocated copy of the chosen item or NULL.
9313 char *
9314 choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help,
9315 char *htitle, char *cursor_location)
9317 LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL;
9318 char **t, **dl;
9319 char *ret = NULL, *choice = NULL;
9321 /* build the LIST_SEL_S list */
9322 p = listhead = NULL;
9323 for(t = list, dl = displist; *t; t++, dl++){
9324 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9325 memset(ls, 0, sizeof(*ls));
9326 ls->item = cpystr(*t);
9327 if(displist)
9328 ls->display_item = cpystr(*dl);
9330 if(cursor_location && (cursor_location == (*t)))
9331 starting_val = ls;
9333 if(p){
9334 p->next = ls;
9335 p = p->next;
9337 else
9338 listhead = p = ls;
9341 if(!listhead)
9342 return(ret);
9344 if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc,
9345 help, htitle, starting_val))
9346 for(p = listhead; !choice && p; p = p->next)
9347 if(p->selected)
9348 choice = p->item;
9350 if(choice)
9351 ret = cpystr(choice);
9353 free_list_sel(&listhead);
9355 return(ret);
9359 void
9360 free_list_sel(LIST_SEL_S **lsel)
9362 if(lsel && *lsel){
9363 free_list_sel(&(*lsel)->next);
9364 if((*lsel)->item)
9365 fs_give((void **) &(*lsel)->item);
9367 if((*lsel)->display_item)
9368 fs_give((void **) &(*lsel)->display_item);
9370 fs_give((void **) lsel);
9376 * file_lister - call pico library's file lister
9379 file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags)
9381 PICO pbf;
9382 int rv;
9383 void (*redraw)(void) = ps_global->redrawer;
9385 standard_picobuf_setup(&pbf);
9386 push_titlebar_state();
9387 if(!newmail)
9388 pbf.newmail = NULL;
9390 /* BUG: what about help command and text? */
9391 pbf.pine_anchor = title;
9393 rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags);
9394 standard_picobuf_teardown(&pbf);
9395 fix_windsize(ps_global);
9396 init_signals(); /* has it's own signal stuff */
9398 /* Restore display's titlebar and body */
9399 pop_titlebar_state();
9400 redraw_titlebar();
9401 if((ps_global->redrawer = redraw) != NULL)
9402 (*ps_global->redrawer)();
9404 return(rv);
9408 /*----------------------------------------------------------------------
9409 Print current folder index
9411 ---*/
9413 print_index(struct pine *state, MSGNO_S *msgmap, int agg)
9415 long i;
9416 ICE_S *ice;
9417 char buf[MAX_SCREEN_COLS+1];
9419 for(i = 1L; i <= mn_get_total(msgmap); i++){
9420 if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
9421 continue;
9423 if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0))
9424 continue;
9426 ice = build_header_line(state, state->mail_stream, msgmap, i, NULL);
9428 if(ice){
9430 * I don't understand why we'd want to mark the current message
9431 * instead of printing out the first character of the status
9432 * so I'm taking it out and including the first character of the
9433 * line instead. Hubert 2006-02-09
9435 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
9436 return(0);
9439 if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i),
9440 print_char)
9441 || !gf_puts(NEWLINE, print_char))
9442 return(0);
9446 return(1);
9450 #ifdef _WINDOWS
9453 * windows callback to get/set header mode state
9456 header_mode_callback(set, args)
9457 int set;
9458 long args;
9460 return(ps_global->full_header);
9465 * windows callback to get/set zoom mode state
9468 zoom_mode_callback(set, args)
9469 int set;
9470 long args;
9472 return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0);
9477 * windows callback to get/set zoom mode state
9480 any_selected_callback(set, args)
9481 int set;
9482 long args;
9484 return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0);
9492 flag_callback(set, flags)
9493 int set;
9494 long flags;
9496 MESSAGECACHE *mc;
9497 int newflags = 0;
9498 long msgno;
9499 int permflag = 0;
9501 switch (set) {
9502 case 1: /* Important */
9503 permflag = ps_global->mail_stream->perm_flagged;
9504 break;
9506 case 2: /* New */
9507 permflag = ps_global->mail_stream->perm_seen;
9508 break;
9510 case 3: /* Answered */
9511 permflag = ps_global->mail_stream->perm_answered;
9512 break;
9514 case 4: /* Deleted */
9515 permflag = ps_global->mail_stream->perm_deleted;
9516 break;
9520 if(!(any_messages(ps_global->msgmap, NULL, "to Flag")
9521 && can_set_flag(ps_global, "flag", permflag)))
9522 return(0);
9524 if(sp_io_error_on_stream(ps_global->mail_stream)){
9525 sp_set_io_error_on_stream(ps_global->mail_stream, 0);
9526 pine_mail_check(ps_global->mail_stream); /* forces write */
9527 return(0);
9530 msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
9531 if(msgno > 0L && ps_global->mail_stream
9532 && msgno <= ps_global->mail_stream->nmsgs
9533 && (mc = mail_elt(ps_global->mail_stream, msgno))
9534 && mc->valid){
9536 * NOTE: code below is *VERY* sensitive to the order of
9537 * the messages defined in resource.h for flag handling.
9538 * Don't change it unless you know what you're doing.
9540 if(set){
9541 char *flagstr;
9542 long mflag;
9544 switch(set){
9545 case 1 : /* Important */
9546 flagstr = "\\FLAGGED";
9547 mflag = (mc->flagged) ? 0L : ST_SET;
9548 break;
9550 case 2 : /* New */
9551 flagstr = "\\SEEN";
9552 mflag = (mc->seen) ? 0L : ST_SET;
9553 break;
9555 case 3 : /* Answered */
9556 flagstr = "\\ANSWERED";
9557 mflag = (mc->answered) ? 0L : ST_SET;
9558 break;
9560 case 4 : /* Deleted */
9561 flagstr = "\\DELETED";
9562 mflag = (mc->deleted) ? 0L : ST_SET;
9563 break;
9565 default : /* bogus */
9566 return(0);
9569 mail_flag(ps_global->mail_stream, long2string(msgno),
9570 flagstr, mflag);
9572 if(ps_global->redrawer)
9573 (*ps_global->redrawer)();
9575 else{
9576 /* Important */
9577 if(mc->flagged)
9578 newflags |= 0x0001;
9580 /* New */
9581 if(!mc->seen)
9582 newflags |= 0x0002;
9584 /* Answered */
9585 if(mc->answered)
9586 newflags |= 0x0004;
9588 /* Deleted */
9589 if(mc->deleted)
9590 newflags |= 0x0008;
9594 return(newflags);
9600 * BUG: Should teach this about keywords
9602 MPopup *
9603 flag_submenu(mc)
9604 MESSAGECACHE *mc;
9606 static MPopup flag_submenu[] = {
9607 {tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}},
9608 {tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}},
9609 {tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}},
9610 {tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}},
9611 {tTail}
9614 /* Important */
9615 flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal;
9617 /* New */
9618 flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked;
9620 /* Answered */
9621 flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal;
9623 /* Deleted */
9624 flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal;
9626 return(flag_submenu);
9629 #endif /* _WINDOWS */