* Set default ssl configuration for Homebrew in MAC OSX to
[alpine.git] / alpine / mailcmd.c
blob0ea1617c0ecdf744135cab2780f974aa4e82068c
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, *topthrd;
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 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;
2370 if(any_messages(msgmap, NULL, "to Bounce")){
2371 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2372 return rv;
2374 rv = bounce(state, NULL);
2375 if(MCMD_ISAGG(aopt))
2376 restore_selected(msgmap);
2378 state->mangled_footer = 1;
2381 return rv;
2385 /*----------------------------------------------------------------------
2386 Execute save message command: prompt for folder and call function to save
2388 Args: screen_line -- Line on the screen to prompt on
2389 message -- The MESSAGECACHE entry of message to save
2391 Result: The folder lister can be called to make selection; mangled screen set
2393 This does the prompting for the folder name to save to, possibly calling
2394 up the folder display for selection of folder by user.
2395 ----*/
2397 cmd_save(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
2399 char newfolder[MAILTMPLEN], nmsgs[32], *nick;
2400 int we_cancel = 0, rv = 0, save_flags;
2401 long i, raw;
2402 CONTEXT_S *cntxt = NULL;
2403 ENVELOPE *e = NULL;
2404 SaveDel del = DontAsk;
2405 SavePreserveOrder pre = DontAskPreserve;
2407 dprint((4, "\n - saving message -\n"));
2409 if(MCMD_ISAGG(aopt) && !pseudo_selected(stream, msgmap))
2410 return rv;
2412 state->ugly_consider_advancing_bit = 0;
2413 if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM, state)
2414 && msgno_any_deletedparts(stream, msgmap)
2415 && want_to(_("Saved copy will NOT include entire message! Continue"),
2416 'y', 'n', NO_HELP, WT_FLUSH_IN | WT_SEQ_SENSITIVE) != 'y'){
2417 restore_selected(msgmap);
2418 cmd_cancelled("Save message");
2419 return rv;
2422 raw = mn_m2raw(msgmap, mn_get_cur(msgmap));
2424 if(mn_total_cur(msgmap) <= 1L){
2425 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld ", mn_get_cur(msgmap));
2426 nmsgs[sizeof(nmsgs)-1] = '\0';
2427 e = pine_mail_fetchstructure(stream, raw, NULL);
2428 if(!e) {
2429 q_status_message(SM_ORDER | SM_DING, 3, 4,
2430 _("Can't save message. Error accessing folder"));
2431 restore_selected(msgmap);
2432 return rv;
2435 else{
2436 snprintf(nmsgs, sizeof(nmsgs), "%s msgs ", comatose(mn_total_cur(msgmap)));
2437 nmsgs[sizeof(nmsgs)-1] = '\0';
2439 /* e is just used to get a default save folder from the first msg */
2440 e = pine_mail_fetchstructure(stream,
2441 mn_m2raw(msgmap, mn_first_cur(msgmap)),
2442 NULL);
2445 del = (!READONLY_FOLDER(stream) && F_OFF(F_SAVE_WONT_DELETE, ps_global))
2446 ? Del : NoDel;
2447 if(mn_total_cur(msgmap) > 1L)
2448 pre = F_OFF(F_AGG_SEQ_COPY, ps_global) ? Preserve : NoPreserve;
2449 else
2450 pre = DontAskPreserve;
2452 if(save_prompt(state, &cntxt, newfolder, sizeof(newfolder), nmsgs, e,
2453 raw, NULL, &del, &pre)){
2455 if(ps_global && ps_global->ttyo){
2456 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2457 ps_global->mangled_footer = 1;
2460 save_flags = SV_FIX_DELS;
2461 if(pre == RetPreserve)
2462 save_flags |= SV_PRESERVE;
2463 if(del == RetDel)
2464 save_flags |= SV_DELETE;
2465 if(ps_global->context_list == cntxt && !strucmp(newfolder, ps_global->inbox_name))
2466 save_flags |= SV_INBOXWOCNTXT;
2468 we_cancel = busy_cue(_("Saving"), NULL, 1);
2469 i = save(state, stream, cntxt, newfolder, msgmap, save_flags);
2470 if(we_cancel)
2471 cancel_busy_cue(0);
2473 if(i == mn_total_cur(msgmap)){
2474 rv++;
2475 if(mn_total_cur(msgmap) <= 1L){
2476 int need, avail = ps_global->ttyo->screen_cols - 2;
2477 int lennick, lenfldr;
2479 if(cntxt
2480 && ps_global->context_list->next
2481 && context_isambig(newfolder)){
2482 lennick = MIN(strlen(cntxt->nickname), 500);
2483 lenfldr = MIN(strlen(newfolder), 500);
2484 need = 27 + strlen(long2string(mn_get_cur(msgmap))) +
2485 lenfldr + lennick;
2486 if(need > avail){
2487 if(lennick > 10){
2488 need -= MIN(lennick-10, need-avail);
2489 lennick -= MIN(lennick-10, need-avail);
2492 if(need > avail && lenfldr > 10)
2493 lenfldr -= MIN(lenfldr-10, need-avail);
2496 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2497 "Message %s copied to \"%s\" in <%s>",
2498 long2string(mn_get_cur(msgmap)),
2499 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2500 lenfldr, MidDots),
2501 short_str(cntxt->nickname,
2502 (char *)(tmp_20k_buf+2000), 1000,
2503 lennick, EndDots));
2504 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2506 else if((nick=folder_is_target_of_nick(newfolder, cntxt)) != NULL){
2507 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2508 "Message %s copied to \"%s\"",
2509 long2string(mn_get_cur(msgmap)),
2510 nick);
2511 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2513 else{
2514 char *f = " folder";
2516 lenfldr = MIN(strlen(newfolder), 500);
2517 need = 28 + strlen(long2string(mn_get_cur(msgmap))) +
2518 lenfldr;
2519 if(need > avail){
2520 need -= strlen(f);
2521 f = "";
2522 if(need > avail && lenfldr > 10)
2523 lenfldr -= MIN(lenfldr-10, need-avail);
2526 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
2527 "Message %s copied to%s \"%s\"",
2528 long2string(mn_get_cur(msgmap)), f,
2529 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2530 lenfldr, MidDots));
2531 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2534 else{
2535 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s messages saved",
2536 comatose(mn_total_cur(msgmap)));
2537 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2540 if(del == RetDel){
2541 strncat(tmp_20k_buf, " and deleted", SIZEOF_20KBUF-strlen(tmp_20k_buf)-1);
2542 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2545 q_status_message(SM_ORDER, 0, 3, tmp_20k_buf);
2547 if(!MCMD_ISAGG(aopt) && F_ON(F_SAVE_ADVANCES, state)){
2548 if(sp_new_mail_count(stream))
2549 process_filter_patterns(stream, msgmap,
2550 sp_new_mail_count(stream));
2552 mn_inc_cur(stream, msgmap,
2553 (in_index == View && THREADING()
2554 && sp_viewing_a_thread(stream))
2555 ? MH_THISTHD
2556 : (in_index == View)
2557 ? MH_ANYTHD : MH_NONE);
2560 state->ugly_consider_advancing_bit = 1;
2564 if(MCMD_ISAGG(aopt)) /* straighten out fakes */
2565 restore_selected(msgmap);
2567 if(del == RetDel)
2568 update_titlebar_status(); /* make sure they see change */
2570 return rv;
2574 void
2575 role_compose(struct pine *state)
2577 int action;
2579 if(F_ON(F_ALT_ROLE_MENU, state) && mn_get_total(state->msgmap) > 0L){
2580 PAT_STATE pstate;
2582 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
2583 action = radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
2584 -FOOTER_ROWS(state), choose_action,
2585 'c', 'x', h_role_compose, RB_NORM);
2587 else{
2588 q_status_message(SM_ORDER, 0, 3,
2589 _("No roles available. Use Setup/Rules to add roles."));
2590 return;
2593 else
2594 action = 'c';
2596 if(action == 'c' || action == 'r' || action == 'f' || action == 'b'){
2597 ACTION_S *role = NULL;
2598 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
2600 redraw = state->redrawer;
2601 state->redrawer = NULL;
2602 prev_screen = state->prev_screen;
2603 role = NULL;
2604 state->next_screen = SCREEN_FUN_NULL;
2606 /* Setup role */
2607 if(role_select_screen(state, &role,
2608 action == 'f' ? MC_FORWARD :
2609 action == 'r' ? MC_REPLY :
2610 action == 'b' ? MC_BOUNCE :
2611 action == 'c' ? MC_COMPOSE : 0) < 0){
2612 cmd_cancelled(action == 'f' ? _("Forward") :
2613 action == 'r' ? _("Reply") :
2614 action == 'c' ? _("Composition") : _("Bounce"));
2615 state->next_screen = prev_screen;
2616 state->redrawer = redraw;
2617 state->mangled_screen = 1;
2619 else{
2621 * If default role was selected (NULL) we need to make
2622 * up a role which won't do anything, but will cause
2623 * compose_mail to think there's already a role so that
2624 * it won't try to confirm the default.
2626 if(role)
2627 role = combine_inherited_role(role);
2628 else{
2629 role = (ACTION_S *) fs_get(sizeof(*role));
2630 memset((void *) role, 0, sizeof(*role));
2631 role->nick = cpystr("Default Role");
2634 state->redrawer = NULL;
2635 switch(action){
2636 case 'c':
2637 compose_mail(NULL, NULL, role, NULL, NULL);
2638 break;
2640 case 'r':
2641 (void) reply(state, role);
2642 break;
2644 case 'f':
2645 (void) forward(state, role);
2646 break;
2648 case 'b':
2649 (void) bounce(state, role);
2650 break;
2653 if(role)
2654 free_action(&role);
2656 state->next_screen = prev_screen;
2657 state->redrawer = redraw;
2658 state->mangled_screen = 1;
2664 /*----------------------------------------------------------------------
2665 Do the dirty work of prompting the user for a folder name
2667 Args:
2668 nfldr should be a buffer at least MAILTMPLEN long
2669 dela -- a pointer to a SaveDel. If it is
2670 DontAsk on input, don't offer Delete prompt
2671 Del on input, offer Delete command with default of Delete
2672 NoDel NoDelete
2673 RetDel and RetNoDel are return values
2676 Result:
2678 ----*/
2680 save_prompt(struct pine *state, CONTEXT_S **cntxt, char *nfldr, size_t len_nfldr,
2681 char *nmsgs, ENVELOPE *env, long int rawmsgno, char *section,
2682 SaveDel *dela, SavePreserveOrder *prea)
2684 int rc, ku = -1, n, flags, last_rc = 0, saveable_count = 0, done = 0;
2685 int delindex, preindex, r;
2686 char prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN];
2687 char *buf = tmp_20k_buf;
2688 char shortbuf[200];
2689 char *folder;
2690 HelpType help;
2691 SaveDel del = DontAsk;
2692 SavePreserveOrder pre = DontAskPreserve;
2693 char *deltext = NULL;
2694 static HISTORY_S *history = NULL;
2695 CONTEXT_S *tc;
2696 ESCKEY_S ekey[10];
2698 if(!cntxt)
2699 panic("no context ptr in save_prompt");
2701 init_hist(&history, HISTSIZE);
2703 if(!(folder = save_get_default(state, env, rawmsgno, section, cntxt)))
2704 return(0); /* message expunged! */
2706 /* how many context's can be saved to... */
2707 for(tc = state->context_list; tc; tc = tc->next)
2708 if(!NEWS_TEST(tc))
2709 saveable_count++;
2711 /* set up extra command option keys */
2712 rc = 0;
2713 ekey[rc].ch = ctrl('T');
2714 ekey[rc].rval = 2;
2715 ekey[rc].name = "^T";
2716 /* TRANSLATORS: command means go to Folders list */
2717 ekey[rc++].label = N_("To Fldrs");
2719 if(saveable_count > 1){
2720 ekey[rc].ch = ctrl('P');
2721 ekey[rc].rval = 10;
2722 ekey[rc].name = "^P";
2723 ekey[rc++].label = N_("Prev Collection");
2725 ekey[rc].ch = ctrl('N');
2726 ekey[rc].rval = 11;
2727 ekey[rc].name = "^N";
2728 ekey[rc++].label = N_("Next Collection");
2731 if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
2732 ekey[rc].ch = TAB;
2733 ekey[rc].rval = 12;
2734 ekey[rc].name = "TAB";
2735 /* TRANSLATORS: command asks alpine to complete the name when tab is typed */
2736 ekey[rc++].label = N_("Complete");
2739 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
2740 ekey[rc].ch = ctrl('X');
2741 ekey[rc].rval = 14;
2742 ekey[rc].name = "^X";
2743 /* TRANSLATORS: list all the matches */
2744 ekey[rc++].label = N_("ListMatches");
2747 if(dela && (*dela == NoDel || *dela == Del)){
2748 ekey[rc].ch = ctrl('R');
2749 ekey[rc].rval = 15;
2750 ekey[rc].name = "^R";
2751 delindex = rc++;
2752 del = *dela;
2755 if(prea && (*prea == NoPreserve || *prea == Preserve)){
2756 ekey[rc].ch = ctrl('W');
2757 ekey[rc].rval = 16;
2758 ekey[rc].name = "^W";
2759 preindex = rc++;
2760 pre = *prea;
2763 if(saveable_count > 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2764 ekey[rc].ch = KEY_UP;
2765 ekey[rc].rval = 10;
2766 ekey[rc].name = "";
2767 ekey[rc++].label = "";
2769 ekey[rc].ch = KEY_DOWN;
2770 ekey[rc].rval = 11;
2771 ekey[rc].name = "";
2772 ekey[rc++].label = "";
2774 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2775 ekey[rc].ch = KEY_UP;
2776 ekey[rc].rval = 30;
2777 ekey[rc].name = "";
2778 ku = rc;
2779 ekey[rc++].label = "";
2781 ekey[rc].ch = KEY_DOWN;
2782 ekey[rc].rval = 31;
2783 ekey[rc].name = "";
2784 ekey[rc++].label = "";
2787 ekey[rc].ch = -1;
2789 *nfldr = '\0';
2790 help = NO_HELP;
2791 while(!done){
2792 /* only show collection number if more than one available */
2793 if(ps_global->context_list->next)
2794 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder in <%s> [%s] : ",
2795 deltext ? deltext : "",
2796 nmsgs,
2797 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2798 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2799 else
2800 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder [%s] : ",
2801 deltext ? deltext : "",
2802 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2804 prompt[sizeof(prompt)-1] = '\0';
2807 * If the prompt won't fit, try removing deltext.
2809 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && deltext){
2810 if(ps_global->context_list->next)
2811 snprintf(prompt, sizeof(prompt), "SAVE %sto folder in <%s> [%s] : ",
2812 nmsgs,
2813 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2814 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2815 else
2816 snprintf(prompt, sizeof(prompt), "SAVE %sto folder [%s] : ",
2817 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2819 prompt[sizeof(prompt)-1] = '\0';
2823 * If the prompt still won't fit, remove the extra info contained
2824 * in nmsgs.
2826 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && *nmsgs){
2827 if(ps_global->context_list->next)
2828 snprintf(prompt, sizeof(prompt), "SAVE to folder in <%s> [%s] : ",
2829 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2830 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2831 else
2832 snprintf(prompt, sizeof(prompt), "SAVE to folder [%s] : ",
2833 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2835 prompt[sizeof(prompt)-1] = '\0';
2838 if(del != DontAsk)
2839 ekey[delindex].label = (del == NoDel) ? "Delete" : "No Delete";
2841 if(pre != DontAskPreserve)
2842 ekey[preindex].label = (pre == NoPreserve) ? "Preserve Order" : "Any Order";
2844 if(ku >= 0){
2845 if(items_in_hist(history) > 1){
2846 ekey[ku].name = HISTORY_UP_KEYNAME;
2847 ekey[ku].label = HISTORY_KEYLABEL;
2848 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
2849 ekey[ku+1].label = HISTORY_KEYLABEL;
2851 else{
2852 ekey[ku].name = "";
2853 ekey[ku].label = "";
2854 ekey[ku+1].name = "";
2855 ekey[ku+1].label = "";
2859 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
2860 rc = optionally_enter(nfldr, -FOOTER_ROWS(state), 0, len_nfldr,
2861 prompt, ekey, help, &flags);
2863 switch(rc){
2864 case -1 :
2865 q_status_message(SM_ORDER | SM_DING, 3, 3,
2866 _("Error reading folder name"));
2867 done--;
2868 break;
2870 case 0 :
2871 removing_trailing_white_space(nfldr);
2872 removing_leading_white_space(nfldr);
2874 if(*nfldr || *folder){
2875 char *p, *name, *fullname = NULL;
2876 int exists, breakout = FALSE;
2878 if(!*nfldr){
2879 strncpy(nfldr, folder, len_nfldr-1);
2880 nfldr[len_nfldr-1] = '\0';
2883 save_hist(history, nfldr, 0, (void *) *cntxt);
2885 if(!(name = folder_is_nick(nfldr, FOLDERS(*cntxt), 0)))
2886 name = nfldr;
2888 if(update_folder_spec(expanded, sizeof(expanded), name)){
2889 strncpy(name = nfldr, expanded, len_nfldr-1);
2890 nfldr[len_nfldr-1] = '\0';
2893 exists = folder_name_exists(*cntxt, name, &fullname);
2895 if(exists == FEX_ERROR){
2896 q_status_message1(SM_ORDER, 0, 3,
2897 _("Problem accessing folder \"%s\""),
2898 nfldr);
2899 done--;
2901 else{
2902 if(fullname){
2903 strncpy(name = nfldr, fullname, len_nfldr-1);
2904 nfldr[len_nfldr-1] = '\0';
2905 fs_give((void **) &fullname);
2906 breakout = TRUE;
2909 if(exists & FEX_ISFILE){
2910 done++;
2912 else if((exists & FEX_ISDIR)){
2913 char tmp[MAILTMPLEN];
2915 tc = *cntxt;
2916 if(breakout){
2917 CONTEXT_S *fake_context;
2918 size_t l;
2920 strncpy(tmp, name, sizeof(tmp));
2921 tmp[sizeof(tmp)-2-1] = '\0';
2922 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
2923 if(l < sizeof(tmp)){
2924 tmp[l] = tc->dir->delim;
2925 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
2928 else
2929 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
2931 tmp[sizeof(tmp)-1] = '\0';
2933 fake_context = new_context(tmp, 0);
2934 nfldr[0] = '\0';
2935 done = display_folder_list(&fake_context, nfldr,
2936 1, folders_for_save);
2937 free_context(&fake_context);
2939 else if(tc->dir->delim
2940 && (p = strrindex(name, tc->dir->delim))
2941 && *(p+1) == '\0')
2942 done = display_folder_list(cntxt, nfldr,
2943 1, folders_for_save);
2944 else{
2945 q_status_message1(SM_ORDER, 3, 3,
2946 _("\"%s\" is a directory"), name);
2947 if(tc->dir->delim
2948 && !((p=strrindex(name, tc->dir->delim)) && *(p+1) == '\0')){
2949 strncpy(tmp, name, sizeof(tmp));
2950 tmp[sizeof(tmp)-1] = '\0';
2951 snprintf(nfldr, len_nfldr, "%s%c", tmp, tc->dir->delim);
2955 else{ /* Doesn't exist, create! */
2956 if((fullname = folder_as_breakout(*cntxt, name)) != NULL){
2957 strncpy(name = nfldr, fullname, len_nfldr-1);
2958 nfldr[len_nfldr-1] = '\0';
2959 fs_give((void **) &fullname);
2962 switch(create_for_save(*cntxt, name)){
2963 case 1 : /* success */
2964 done++;
2965 break;
2966 case 0 : /* error */
2967 case -1 : /* declined */
2968 done--;
2969 break;
2974 break;
2976 /* else fall thru like they cancelled */
2978 case 1 :
2979 cmd_cancelled("Save message");
2980 done--;
2981 break;
2983 case 2 :
2984 r = display_folder_list(cntxt, nfldr, 0, folders_for_save);
2986 if(r)
2987 done++;
2989 break;
2991 case 3 :
2992 helper(h_save, _("HELP FOR SAVE"), HLPD_SIMPLE);
2993 ps_global->mangled_screen = 1;
2994 break;
2996 case 4 : /* redraw */
2997 break;
2999 case 10 : /* previous collection */
3000 for(tc = (*cntxt)->prev; tc; tc = tc->prev)
3001 if(!NEWS_TEST(tc))
3002 break;
3004 if(!tc){
3005 CONTEXT_S *tc2;
3007 for(tc2 = (tc = (*cntxt))->next; tc2; tc2 = tc2->next)
3008 if(!NEWS_TEST(tc2))
3009 tc = tc2;
3012 *cntxt = tc;
3013 break;
3015 case 11 : /* next collection */
3016 tc = (*cntxt);
3019 if(((*cntxt) = (*cntxt)->next) == NULL)
3020 (*cntxt) = ps_global->context_list;
3021 while(NEWS_TEST(*cntxt) && (*cntxt) != tc);
3022 break;
3024 case 12 : /* file name completion */
3025 if(!folder_complete(*cntxt, nfldr, len_nfldr, &n)){
3026 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
3027 r = display_folder_list(cntxt, nfldr, 1, folders_for_save);
3028 if(r)
3029 done++; /* bingo! */
3030 else
3031 rc = 0; /* burn last_rc */
3033 else
3034 Writechar(BELL, 0);
3037 break;
3039 case 14 : /* file name completion */
3040 r = display_folder_list(cntxt, nfldr, 2, folders_for_save);
3041 if(r)
3042 done++; /* bingo! */
3043 else
3044 rc = 0; /* burn last_rc */
3046 break;
3048 case 15 : /* Delete / No Delete */
3049 del = (del == NoDel) ? Del : NoDel;
3050 deltext = (del == NoDel) ? " (no delete)" : " (and delete)";
3051 break;
3053 case 16 : /* Preserve Order or not */
3054 pre = (pre == NoPreserve) ? Preserve : NoPreserve;
3055 break;
3057 case 30 :
3058 if((p = get_prev_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3059 strncpy(nfldr, p, len_nfldr);
3060 nfldr[len_nfldr-1] = '\0';
3061 if(history->hist[history->curindex])
3062 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3064 else
3065 Writechar(BELL, 0);
3067 break;
3069 case 31 :
3070 if((p = get_next_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3071 strncpy(nfldr, p, len_nfldr);
3072 nfldr[len_nfldr-1] = '\0';
3073 if(history->hist[history->curindex])
3074 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3076 else
3077 Writechar(BELL, 0);
3079 break;
3081 default :
3082 panic("Unhandled case");
3083 break;
3086 last_rc = rc;
3089 ps_global->mangled_footer = 1;
3091 if(done < 0)
3092 return(0);
3094 if(*nfldr){
3095 strncpy(ps_global->last_save_folder, nfldr, sizeof(ps_global->last_save_folder)-1);
3096 ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0';
3097 if(*cntxt)
3098 ps_global->last_save_context = *cntxt;
3100 else{
3101 strncpy(nfldr, folder, len_nfldr-1);
3102 nfldr[len_nfldr-1] = '\0';
3105 /* nickname? Copy real name to nfldr */
3106 if(*cntxt
3107 && context_isambig(nfldr)
3108 && (p = folder_is_nick(nfldr, FOLDERS(*cntxt), 0))){
3109 strncpy(nfldr, p, len_nfldr-1);
3110 nfldr[len_nfldr-1] = '\0';
3113 if(dela && (*dela == NoDel || *dela == Del))
3114 *dela = (del == NoDel) ? RetNoDel : RetDel;
3116 if(prea && (*prea == NoPreserve || *prea == Preserve))
3117 *prea = (pre == NoPreserve) ? RetNoPreserve : RetPreserve;
3119 return(1);
3123 /*----------------------------------------------------------------------
3124 Prompt user before implicitly creating a folder for saving
3126 Args: context - context to create folder in
3127 folder - folder name to create
3129 Result: 1 on proceed, -1 on decline, 0 on error
3131 ----*/
3133 create_for_save_prompt(CONTEXT_S *context, char *folder, int sequence_sensitive)
3135 if(context && ps_global->context_list->next && context_isambig(folder)){
3136 if(context->use & CNTXT_INCMNG){
3137 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3138 _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
3139 folder, (strlen(folder) > 15) ? "..." : "");
3140 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
3141 return(0); /* error */
3144 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3145 _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
3146 folder, (strlen(folder) > 15) ? "..." : "",
3147 context->nickname,
3148 (strlen(context->nickname) > 15) ? "..." : "");
3150 else
3151 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
3152 _("Folder \"%.40s%s\" doesn't exist. Create"),
3153 folder, strlen(folder) > 40 ? "..." : "");
3155 if(want_to(tmp_20k_buf, 'y', 'n',
3156 NO_HELP, (sequence_sensitive) ? WT_SEQ_SENSITIVE : WT_NORM) != 'y'){
3157 cmd_cancelled("Save message");
3158 return(-1);
3161 return(1);
3166 /*----------------------------------------------------------------------
3167 Expunge messages from current folder
3169 Args: state -- pointer to struct holding a bunch of pine state
3170 msgmap -- table mapping msg nums to c-client sequence nums
3171 qline -- screen line to ask questions on
3172 agg -- boolean indicating we're to operate on aggregate set
3174 Result:
3175 ----*/
3177 cmd_expunge(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int agg)
3179 long del_count, prefilter_del_count;
3180 int we_cancel = 0, rv = 0;
3181 char prompt[MAX_SCREEN_COLS+1];
3182 char *sequence;
3183 COLOR_PAIR *lastc = NULL;
3185 dprint((2, "\n - expunge -\n"));
3187 del_count = 0;
3189 sequence = MCMD_ISAGG(agg) ? selected_sequence(stream, msgmap, NULL, 0) : NULL;
3191 if(MCMD_ISAGG(agg)){
3192 long i;
3193 MESSAGECACHE *mc;
3194 for(i = 1L; i <= stream->nmsgs; i++){
3195 if((mc = mail_elt(stream, i)) != NULL
3196 && mc->sequence && mc->deleted)
3197 del_count++;
3199 if(del_count == 0){
3200 q_status_message(SM_ORDER, 0, 4,
3201 _("No selected messages are deleted"));
3202 return 0;
3204 } else {
3205 if(!any_messages(msgmap, NULL, "to Expunge"))
3206 return rv;
3209 if(IS_NEWS(stream) && stream->rdonly){
3210 if(!MCMD_ISAGG(agg))
3211 del_count = count_flagged(stream, F_DEL);
3212 if(del_count > 0L){
3213 state->mangled_footer = 1;
3214 snprintf(prompt, sizeof(prompt), "Exclude %ld message%s from %.*s", del_count,
3215 plural(del_count), sizeof(prompt)-40,
3216 pretty_fn(state->cur_folder));
3217 prompt[sizeof(prompt)-1] = '\0';
3218 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3219 || (F_ON(F_AUTO_EXPUNGE, state)
3220 && (state->context_current
3221 && (state->context_current->use & CNTXT_INCMNG))
3222 && context_isambig(state->cur_folder))
3223 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
3225 if(F_ON(F_NEWS_CROSS_DELETE, state))
3226 cross_delete_crossposts(stream);
3228 msgno_exclude_deleted(stream, msgmap, sequence);
3229 clear_index_cache(stream, 0);
3232 * This is kind of surprising at first. For most sort
3233 * orders, if the whole set is sorted, then any subset
3234 * is also sorted. Not so for threaded sorts.
3236 if(SORT_IS_THREADED(msgmap))
3237 refresh_sort(stream, msgmap, SRT_NON);
3239 state->mangled_body = 1;
3240 state->mangled_header = 1;
3241 q_status_message2(SM_ORDER, 0, 4,
3242 "%s message%s excluded",
3243 long2string(del_count),
3244 plural(del_count));
3246 else
3247 any_messages(NULL, NULL, "Excluded");
3249 else
3250 any_messages(NULL, "deleted", "to Exclude");
3252 return del_count;
3254 else if(READONLY_FOLDER(stream)){
3255 q_status_message(SM_ORDER, 0, 4,
3256 _("Can't expunge. Folder is read-only"));
3257 return del_count;
3260 if(!MCMD_ISAGG(agg)){
3261 prefilter_del_count = count_flagged(stream, F_DEL|F_NOFILT);
3262 mail_expunge_prefilter(stream, MI_NONE);
3263 del_count = count_flagged(stream, F_DEL|F_NOFILT);
3266 if(del_count != 0){
3267 int ret;
3268 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3270 snprintf(prompt, sizeof(prompt), "Expunge %ld message%s from %.*s", del_count,
3271 plural(del_count), sizeof(prompt)-40,
3272 pretty_fn((char *) fname));
3273 if(fname) fs_give((void **)&fname);
3274 prompt[sizeof(prompt)-1] = '\0';
3275 state->mangled_footer = 1;
3277 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3278 || (F_ON(F_AUTO_EXPUNGE, state)
3279 && ((!strucmp(state->cur_folder,state->inbox_name))
3280 || (state->context_current->use & CNTXT_INCMNG))
3281 && context_isambig(state->cur_folder))
3282 || (ret=want_to(prompt, 'y', 0, NO_HELP, WT_NORM)) == 'y')
3283 ret = 'y';
3285 if(ret == 'x')
3286 cmd_cancelled("Expunge");
3288 if(ret != 'y')
3289 return 0;
3292 dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
3293 mn_get_total(msgmap), mn_get_cur(msgmap), del_count));
3295 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3296 state->VAR_TITLE_BACK_COLOR,
3297 PSC_REV|PSC_RET);
3299 PutLine0(0, 0, "**"); /* indicate delay */
3301 if(lastc){
3302 (void)pico_set_colorp(lastc, PSC_NONE);
3303 free_color_pair(&lastc);
3306 MoveCursor(state->ttyo->screen_rows -FOOTER_ROWS(state), 0);
3307 fflush(stdout);
3309 we_cancel = busy_cue(_("Expunging"), NULL, 1);
3311 if(cmd_expunge_work(stream, msgmap, sequence))
3312 state->mangled_body = 1;
3314 if(sequence)
3315 fs_give((void **)&sequence);
3317 if(we_cancel)
3318 cancel_busy_cue((sp_expunge_count(stream) > 0) ? 0 : -1);
3320 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3321 state->VAR_TITLE_BACK_COLOR,
3322 PSC_REV|PSC_RET);
3323 PutLine0(0, 0, " "); /* indicate delay's over */
3325 if(lastc){
3326 (void)pico_set_colorp(lastc, PSC_NONE);
3327 free_color_pair(&lastc);
3330 fflush(stdout);
3332 if(sp_expunge_count(stream) > 0){
3334 * This is kind of surprising at first. For most sort
3335 * orders, if the whole set is sorted, then any subset
3336 * is also sorted. Not so for threaded sorts.
3338 if(SORT_IS_THREADED(msgmap))
3339 refresh_sort(stream, msgmap, SRT_NON);
3341 else{
3342 if(del_count){
3343 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3344 q_status_message1(SM_ORDER, 0, 3,
3345 _("No messages expunged from folder \"%s\""),
3346 pretty_fn((char *) fname));
3347 if(fname) fs_give((void **)&fname);
3349 else if(!prefilter_del_count)
3350 q_status_message(SM_ORDER, 0, 3,
3351 _("No messages marked deleted. No messages expunged."));
3353 return del_count;
3357 /*----------------------------------------------------------------------
3358 Expunge_and_close callback to prompt user for confirmation
3360 Args: stream -- folder's stream
3361 folder -- name of folder containing folders
3362 deleted -- number of del'd msgs
3364 Result: 'y' to continue with expunge
3365 ----*/
3367 expunge_prompt(MAILSTREAM *stream, char *folder, long int deleted)
3369 long max_folder;
3370 int charcnt = 0;
3371 char prompt_b[MAX_SCREEN_COLS+1], temp[MAILTMPLEN+1], buff[MAX_SCREEN_COLS+1];
3372 char *short_folder_name;
3374 if(deleted == 1)
3375 charcnt = 1;
3376 else{
3377 snprintf(temp, sizeof(temp), "%ld", deleted);
3378 charcnt = strlen(temp)+1;
3381 max_folder = MAX(1,MAXPROMPT - (36+charcnt));
3382 strncpy(temp, folder, sizeof(temp));
3383 temp[sizeof(temp)-1] = '\0';
3384 short_folder_name = short_str(temp,buff,sizeof(buff),max_folder,FrontDots);
3386 if(IS_NEWS(stream))
3387 snprintf(prompt_b, sizeof(prompt_b),
3388 "Delete %s%ld message%s from \"%s\"",
3389 (deleted > 1L) ? "all " : "", deleted,
3390 plural(deleted), short_folder_name);
3391 else
3392 snprintf(prompt_b, sizeof(prompt_b),
3393 "Expunge the %ld deleted message%s from \"%s\"",
3394 deleted, deleted == 1 ? "" : "s",
3395 short_folder_name);
3397 return(want_to(prompt_b, 'y', 0, NO_HELP, WT_NORM));
3402 * This is used with multiple append saves. Call it once before
3403 * the series of appends with SSCP_INIT and once after all are
3404 * done with SSCP_END. In between, it is called automatically
3405 * from save_fetch_append or save_fetch_append_cb when we need
3406 * to ask the user if he or she wants to continue even though
3407 * announced message size doesn't match the actual message size.
3408 * As of 2008-02-29 the gmail IMAP server has these size mismatches
3409 * on a regular basis even though the data is ok.
3412 save_size_changed_prompt(long msgno, int flags)
3414 int ret;
3415 char prompt[100];
3416 static int remember_the_yes = 0;
3417 static int possible_corruption = 0;
3418 static ESCKEY_S save_size_opts[] = {
3419 {'y', 'y', "Y", "Yes"},
3420 {'n', 'n', "N", "No"},
3421 {'a', 'a', "A", "yes to All"},
3422 {-1, 0, NULL, NULL}
3425 if(flags & SSCP_INIT || flags & SSCP_END){
3426 if(flags & SSCP_END && possible_corruption)
3427 q_status_message(SM_ORDER, 3, 3, "There is possible data corruption, check the results");
3429 remember_the_yes = 0;
3430 possible_corruption = 0;
3431 ps_global->noshow_error = 0;
3432 ps_global->noshow_warn = 0;
3433 return(0);
3436 if(remember_the_yes){
3437 snprintf(prompt, sizeof(prompt),
3438 "Message to save shrank! (msg # %ld): Continuing", msgno);
3439 q_status_message(SM_ORDER, 0, 3, prompt);
3440 display_message('x');
3441 return(remember_the_yes);
3444 snprintf(prompt, sizeof(prompt),
3445 "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno);
3446 ret = radio_buttons(prompt, -FOOTER_ROWS(ps_global), save_size_opts,
3447 'n', 0, h_save_size_changed, RB_NORM);
3449 switch(ret){
3450 case 'a':
3451 remember_the_yes = 'y';
3452 possible_corruption++;
3453 return(remember_the_yes);
3455 case 'y':
3456 possible_corruption++;
3457 return('y');
3459 default:
3460 possible_corruption = 0;
3461 ps_global->noshow_error = 1;
3462 ps_global->noshow_warn = 1;
3463 break;
3466 return('n');
3470 /*----------------------------------------------------------------------
3471 Expunge_and_close callback that happens once the decision to expunge
3472 and close has been made and before expunging and closing begins
3475 Args: stream -- folder's stream
3476 folder -- name of folder containing folders
3477 deleted -- number of del'd msgs
3479 Result: 'y' to continue with expunge
3480 ----*/
3481 void
3482 expunge_and_close_begins(int flags, char *folder)
3484 if(!(flags & EC_NO_CLOSE)){
3485 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
3486 q_status_message1(SM_INFO, 0, 1, "Closing \"%.200s\"...", (char *) fname);
3487 flush_status_messages(1);
3488 if(fname) fs_give((void **)&fname);
3493 /*----------------------------------------------------------------------
3494 Export a message to a plain file in users home directory
3496 Args: state -- pointer to struct holding a bunch of pine state
3497 msgmap -- table mapping msg nums to c-client sequence nums
3498 qline -- screen line to ask questions on
3499 agg -- boolean indicating we're to operate on aggregate set
3501 Result:
3502 ----*/
3504 cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt)
3506 char filename[MAXPATH+1], full_filename[MAXPATH+1], *err;
3507 char nmsgs[80];
3508 int r, leading_nl, failure = 0, orig_errno, rflags = GER_NONE;
3509 int flags = GE_IS_EXPORT | GE_SEQ_SENSITIVE, rv = 0;
3510 ENVELOPE *env;
3511 MESSAGECACHE *mc;
3512 BODY *b;
3513 long i, count = 0L, start_of_append, rawno;
3514 gf_io_t pc;
3515 STORE_S *store;
3516 struct variable *vars = ps_global->vars;
3517 ESCKEY_S export_opts[5];
3518 static HISTORY_S *history = NULL;
3520 if(ps_global->restricted){
3521 q_status_message(SM_ORDER, 0, 3,
3522 "Alpine demo can't export messages to files");
3523 return rv;
3526 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
3527 return rv;
3529 export_opts[i = 0].ch = ctrl('T');
3530 export_opts[i].rval = 10;
3531 export_opts[i].name = "^T";
3532 export_opts[i++].label = N_("To Files");
3534 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3535 if(ps_global->VAR_DOWNLOAD_CMD && ps_global->VAR_DOWNLOAD_CMD[0]){
3536 export_opts[i].ch = ctrl('V');
3537 export_opts[i].rval = 12;
3538 export_opts[i].name = "^V";
3539 /* TRANSLATORS: this is an abbreviation for Download Messages */
3540 export_opts[i++].label = N_("Downld Msg");
3542 #endif /* !(DOS || MAC) */
3544 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
3545 export_opts[i].ch = ctrl('I');
3546 export_opts[i].rval = 11;
3547 export_opts[i].name = "TAB";
3548 export_opts[i++].label = N_("Complete");
3551 #if 0
3552 /* Commented out since it's not yet support! */
3553 if(F_ON(F_ENABLE_SUB_LISTS,ps_global)){
3554 export_opts[i].ch = ctrl('X');
3555 export_opts[i].rval = 14;
3556 export_opts[i].name = "^X";
3557 export_opts[i++].label = N_("ListMatches");
3559 #endif
3562 * If message has attachments, add a toggle that will allow the user
3563 * to save all of the attachments to a single directory, using the
3564 * names provided with the attachments or part names. What we'll do is
3565 * export the message as usual, and then export the attachments into
3566 * a subdirectory that did not exist before. The subdir will be named
3567 * something based on the name of the file being saved to, but a
3568 * unique, new name.
3570 if(!MCMD_ISAGG(aopt)
3571 && state->mail_stream
3572 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3573 && rawno <= state->mail_stream->nmsgs
3574 && (env = pine_mail_fetchstructure(state->mail_stream, rawno, &b))
3575 && b
3576 && b->type == TYPEMULTIPART
3577 && b->subtype
3578 && strucmp(b->subtype, "ALTERNATIVE") != 0){
3579 PART *part;
3581 part = b->nested.part; /* 1st part */
3582 if(part && part->next)
3583 flags |= GE_ALLPARTS;
3586 export_opts[i].ch = -1;
3587 filename[0] = '\0';
3589 if(mn_total_cur(msgmap) <= 1L){
3590 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld", mn_get_cur(msgmap));
3591 nmsgs[sizeof(nmsgs)-1] = '\0';
3593 else{
3594 snprintf(nmsgs, sizeof(nmsgs), "%s messages", comatose(mn_total_cur(msgmap)));
3595 nmsgs[sizeof(nmsgs)-1] = '\0';
3598 r = get_export_filename(state, filename, NULL, full_filename,
3599 sizeof(filename), nmsgs, "EXPORT",
3600 export_opts, &rflags, qline, flags, &history);
3602 if(r < 0){
3603 switch(r){
3604 case -1:
3605 cmd_cancelled("Export message");
3606 break;
3608 case -2:
3609 q_status_message1(SM_ORDER, 0, 2,
3610 _("Can't export to file outside of %s"),
3611 VAR_OPER_DIR);
3612 break;
3615 goto fini;
3617 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3618 else if(r == 12){ /* Download */
3619 char cmd[MAXPATH], *tfp = NULL;
3620 int next = 0;
3621 PIPE_S *syspipe;
3622 STORE_S *so;
3623 gf_io_t pc;
3625 if(ps_global->restricted){
3626 q_status_message(SM_ORDER | SM_DING, 3, 3,
3627 "Download disallowed in restricted mode");
3628 goto fini;
3631 err = NULL;
3632 tfp = temp_nam(NULL, "pd");
3633 build_updown_cmd(cmd, sizeof(cmd), ps_global->VAR_DOWNLOAD_CMD_PREFIX,
3634 ps_global->VAR_DOWNLOAD_CMD, tfp);
3635 dprint((1, "Download cmd called: \"%s\"\n", cmd));
3636 if((so = so_get(FileStar, tfp, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){
3637 gf_set_so_writec(&pc, so);
3639 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
3640 if(!(state->mail_stream
3641 && (rawno = mn_m2raw(msgmap, i)) > 0L
3642 && rawno <= state->mail_stream->nmsgs
3643 && (mc = mail_elt(state->mail_stream, rawno))
3644 && mc->valid))
3645 mc = NULL;
3647 if(!(env = pine_mail_fetchstructure(state->mail_stream,
3648 mn_m2raw(msgmap, i), &b))
3649 || !bezerk_delimiter(env, mc, pc, next++)
3650 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
3651 env, b, NULL, FM_NEW_MESS | FM_NOWRAP, pc)){
3652 q_status_message(SM_ORDER | SM_DING, 3, 3,
3653 err = "Error writing tempfile for download");
3654 break;
3658 gf_clear_so_writec(so);
3659 if(so_give(&so)){ /* close file */
3660 if(!err)
3661 err = "Error writing tempfile for download";
3664 if(!err){
3665 if((syspipe = open_system_pipe(cmd, NULL, NULL,
3666 PIPE_USER | PIPE_RESET,
3667 0, pipe_callback, pipe_report_error)) != NULL)
3668 (void) close_system_pipe(&syspipe, NULL, pipe_callback);
3669 else
3670 q_status_message(SM_ORDER | SM_DING, 3, 3,
3671 err = _("Error running download command"));
3674 else
3675 q_status_message(SM_ORDER | SM_DING, 3, 3,
3676 err = "Error building temp file for download");
3678 if(tfp){
3679 our_unlink(tfp);
3680 fs_give((void **)&tfp);
3683 if(!err)
3684 q_status_message(SM_ORDER, 0, 3, _("Download Command Completed"));
3686 goto fini;
3688 #endif /* !(DOS || MAC) */
3691 if(rflags & GER_APPEND)
3692 leading_nl = 1;
3693 else
3694 leading_nl = 0;
3696 dprint((5, "Opening file \"%s\" for export\n",
3697 full_filename ? full_filename : "?"));
3699 if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE))){
3700 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3701 /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
3702 _("Error opening file \"%s\" to export message: %s"),
3703 full_filename, error_description(errno));
3704 goto fini;
3706 else
3707 gf_set_so_writec(&pc, store);
3709 err = NULL;
3710 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), count++){
3711 env = pine_mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i),
3712 &b);
3713 if(!env) {
3714 err = _("Can't export message. Error accessing mail folder");
3715 failure = 1;
3716 break;
3719 if(!(state->mail_stream
3720 && (rawno = mn_m2raw(msgmap, i)) > 0L
3721 && rawno <= state->mail_stream->nmsgs
3722 && (mc = mail_elt(state->mail_stream, rawno))
3723 && mc->valid))
3724 mc = NULL;
3726 start_of_append = so_tell(store);
3727 if(!bezerk_delimiter(env, mc, pc, leading_nl)
3728 || !format_message(mn_m2raw(msgmap, i), env, b, NULL,
3729 FM_NEW_MESS | FM_NOWRAP, pc)){
3730 orig_errno = errno; /* save incase things are really bad */
3731 failure = 1; /* pop out of here */
3732 break;
3735 leading_nl = 1;
3738 gf_clear_so_writec(store);
3739 if(so_give(&store)) /* release storage */
3740 failure++;
3742 if(failure){
3743 our_truncate(full_filename, (off_t)start_of_append);
3744 if(err){
3745 dprint((1, "FAILED Export: fetch(%ld): %s\n",
3746 i, err ? err : "?"));
3747 q_status_message(SM_ORDER | SM_DING, 3, 4, err);
3749 else{
3750 dprint((1, "FAILED Export: file \"%s\" : %s\n",
3751 full_filename ? full_filename : "?",
3752 error_description(orig_errno)));
3753 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3754 /* TRANSLATORS: Error exporting to <filename>: <error text> */
3755 _("Error exporting to \"%s\" : %s"),
3756 filename, error_description(orig_errno));
3759 else{
3760 if(rflags & GER_ALLPARTS && full_filename[0]){
3761 char dir[MAXPATH+1];
3762 char lfile[MAXPATH+1];
3763 int ok = 0, tries = 0, saved = 0, errs = 0;
3764 ATTACH_S *a;
3767 * Now we want to save all of the attachments to a subdirectory.
3768 * To make it easier for us and probably easier for the user, and
3769 * to prevent the user from shooting himself in the foot, we
3770 * make a new subdirectory so that we can't possibly step on
3771 * any existing files, and we don't need any interaction with the
3772 * user while saving.
3774 * We'll just use the directory name full_filename.d or if that
3775 * already exists and isn't empty, we'll try adding a suffix to
3776 * that until we get something to use.
3779 if(strlen(full_filename) + strlen(".d") + 1 > sizeof(dir)){
3780 q_status_message1(SM_ORDER | SM_DING, 3, 4,
3781 _("Can't save attachments, filename too long: %s"),
3782 full_filename);
3783 goto fini;
3786 ok = 0;
3787 snprintf(dir, sizeof(dir), "%s.d", full_filename);
3788 dir[sizeof(dir)-1] = '\0';
3790 do {
3791 tries++;
3792 switch(r = is_writable_dir(dir)){
3793 case 0: /* exists and is a writable dir */
3795 * We could figure out if it is empty and use it in
3796 * that case, but that sounds like a lot of work, so
3797 * just fall through to default.
3800 default:
3801 if(strlen(full_filename) + strlen(".d") + 1 +
3802 1 + strlen(long2string((long) tries)) > sizeof(dir)){
3803 q_status_message(SM_ORDER | SM_DING, 3, 4,
3804 "Problem saving attachments");
3805 goto fini;
3808 snprintf(dir, sizeof(dir), "%s.d_%s", full_filename,
3809 long2string((long) tries));
3810 dir[sizeof(dir)-1] = '\0';
3811 break;
3813 case 3: /* doesn't exist, that's good! */
3814 /* make new directory */
3815 ok++;
3816 break;
3818 } while(!ok && tries < 1000);
3820 if(tries >= 1000){
3821 q_status_message(SM_ORDER | SM_DING, 3, 4,
3822 _("Problem saving attachments"));
3823 goto fini;
3826 /* create the new directory */
3827 if(our_mkdir(dir, 0700)){
3828 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3829 _("Problem saving attachments: %s: %s"), dir,
3830 error_description(errno));
3831 goto fini;
3834 if(!(state->mail_stream
3835 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3836 && rawno <= state->mail_stream->nmsgs
3837 && (env=pine_mail_fetchstructure(state->mail_stream,rawno,&b))
3838 && b)){
3839 q_status_message(SM_ORDER | SM_DING, 3, 4,
3840 _("Problem reading message"));
3841 goto fini;
3844 zero_atmts(state->atmts);
3845 describe_mime(b, "", 1, 1, 0, 0);
3847 a = state->atmts;
3848 if(a && a->description) /* skip main body part */
3849 a++;
3851 for(; a->description != NULL; a++){
3852 /* skip over these parts of the message */
3853 if(MIME_MSG_A(a) || MIME_DGST_A(a) || MIME_VCARD_A(a))
3854 continue;
3856 lfile[0] = '\0';
3857 (void) get_filename_parameter(lfile, sizeof(lfile), a->body, NULL);
3859 if(lfile[0] == '\0'){
3860 snprintf(lfile, sizeof(lfile), "part_%.*s", sizeof(lfile)-6,
3861 a->number ? a->number : "?");
3862 lfile[sizeof(lfile)-1] = '\0';
3865 if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + 1
3866 > sizeof(filename)){
3867 dprint((2,
3868 "FAILED Att Export: name too long: %s\n",
3869 dir, S_FILESEP, lfile));
3870 errs++;
3871 continue;
3874 snprintf(filename, sizeof(filename), "%s%s%s", dir, S_FILESEP, lfile);
3875 filename[sizeof(filename)-1] = '\0';
3877 if(write_attachment_to_file(state->mail_stream, rawno,
3878 a, GER_NONE, filename) == 1)
3879 saved++;
3880 else
3881 errs++;
3884 if(errs){
3885 if(saved)
3886 q_status_message1(SM_ORDER, 3, 3,
3887 "Errors saving some attachments, %s attachments saved",
3888 long2string((long) saved));
3889 else
3890 q_status_message(SM_ORDER, 3, 3,
3891 _("Problems saving attachments"));
3893 else{
3894 if(saved)
3895 q_status_message2(SM_ORDER, 0, 3,
3896 /* TRANSLATORS: Saved <how many> attachements to <directory name> */
3897 _("Saved %s attachments to %s"),
3898 long2string((long) saved), dir);
3899 else
3900 q_status_message(SM_ORDER, 3, 3, _("No attachments to save"));
3903 else if(mn_total_cur(msgmap) > 1L)
3904 q_status_message4(SM_ORDER,0,3,
3905 "%s message%s %s to file \"%s\"",
3906 long2string(count), plural(count),
3907 rflags & GER_OVER
3908 ? "overwritten"
3909 : rflags & GER_APPEND ? "appended" : "exported",
3910 filename);
3911 else
3912 q_status_message3(SM_ORDER,0,3,
3913 "Message %s %s to file \"%s\"",
3914 long2string(mn_get_cur(msgmap)),
3915 rflags & GER_OVER
3916 ? "overwritten"
3917 : rflags & GER_APPEND ? "appended" : "exported",
3918 filename);
3919 rv++;
3922 fini:
3923 if(MCMD_ISAGG(aopt))
3924 restore_selected(msgmap);
3926 return rv;
3931 * Ask user what file to export to. Export from srcstore to that file.
3933 * Args ps -- pine struct
3934 * srctext -- pointer to source text
3935 * srctype -- type of that source text
3936 * prompt_msg -- see get_export_filename
3937 * lister_msg -- "
3939 * Returns: != 0 : error
3940 * 0 : ok
3943 simple_export(struct pine *ps, void *srctext, SourceType srctype, char *prompt_msg, char *lister_msg)
3945 int r = 1, rflags = GER_NONE;
3946 char filename[MAXPATH+1], full_filename[MAXPATH+1];
3947 STORE_S *store = NULL;
3948 struct variable *vars = ps->vars;
3949 static HISTORY_S *history = NULL;
3950 static ESCKEY_S simple_export_opts[] = {
3951 {ctrl('T'), 10, "^T", N_("To Files")},
3952 {-1, 0, NULL, NULL},
3953 {-1, 0, NULL, NULL}};
3955 if(F_ON(F_ENABLE_TAB_COMPLETE,ps)){
3956 simple_export_opts[r].ch = ctrl('I');
3957 simple_export_opts[r].rval = 11;
3958 simple_export_opts[r].name = "TAB";
3959 simple_export_opts[r].label = N_("Complete");
3962 if(!srctext){
3963 q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
3964 r = -3;
3965 goto fini;
3968 simple_export_opts[++r].ch = -1;
3969 filename[0] = '\0';
3970 full_filename[0] = '\0';
3972 r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename),
3973 prompt_msg, lister_msg, simple_export_opts, &rflags,
3974 -FOOTER_ROWS(ps), GE_IS_EXPORT, &history);
3976 if(r < 0)
3977 goto fini;
3978 else if(!full_filename[0]){
3979 r = -1;
3980 goto fini;
3983 dprint((5, "Opening file \"%s\" for export\n",
3984 full_filename ? full_filename : "?"));
3986 if((store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
3987 char *pipe_err;
3988 gf_io_t pc, gc;
3990 gf_set_so_writec(&pc, store);
3991 gf_set_readc(&gc, srctext, (srctype == CharStar)
3992 ? strlen((char *)srctext)
3993 : 0L,
3994 srctype, 0);
3995 gf_filter_init();
3996 if((pipe_err = gf_pipe(gc, pc)) != NULL){
3997 q_status_message2(SM_ORDER | SM_DING, 3, 3,
3998 /* TRANSLATORS: Problem saving to <filename>: <error text> */
3999 _("Problem saving to \"%s\": %s"),
4000 filename, pipe_err);
4001 r = -3;
4003 else
4004 r = 0;
4006 gf_clear_so_writec(store);
4007 if(so_give(&store)){
4008 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4009 _("Problem saving to \"%s\": %s"),
4010 filename, error_description(errno));
4011 r = -3;
4014 else{
4015 q_status_message2(SM_ORDER | SM_DING, 3, 4,
4016 _("Error opening file \"%s\" for export: %s"),
4017 full_filename, error_description(errno));
4018 r = -3;
4021 fini:
4022 switch(r){
4023 case 0:
4024 /* overloading full_filename */
4025 snprintf(full_filename, sizeof(full_filename), "%c%s",
4026 (prompt_msg && prompt_msg[0])
4027 ? (islower((unsigned char)prompt_msg[0])
4028 ? toupper((unsigned char)prompt_msg[0]) : prompt_msg[0])
4029 : 'T',
4030 (prompt_msg && prompt_msg[0]) ? prompt_msg+1 : "ext");
4031 full_filename[sizeof(full_filename)-1] = '\0';
4032 q_status_message3(SM_ORDER,0,2,"%s %s to \"%s\"",
4033 full_filename,
4034 rflags & GER_OVER
4035 ? "overwritten"
4036 : rflags & GER_APPEND ? "appended" : "exported",
4037 filename);
4038 break;
4040 case -1:
4041 cmd_cancelled("Export");
4042 break;
4044 case -2:
4045 q_status_message1(SM_ORDER, 0, 2,
4046 _("Can't export to file outside of %s"), VAR_OPER_DIR);
4047 break;
4050 ps->mangled_footer = 1;
4051 return(r);
4056 * Ask user what file to export to.
4058 * filename -- On input, this is the filename to start with. On exit,
4059 * this is the filename chosen. (but this isn't used)
4060 * deefault -- This is the default value if user hits return. The
4061 * prompt will have [deefault] added to it automatically.
4062 * full_filename -- This is the full filename on exit.
4063 * len -- Minimum length of _both_ filename and full_filename.
4064 * prompt_msg -- Message to insert in prompt.
4065 * lister_msg -- Message to insert in file_lister.
4066 * opts -- Key options.
4067 * There is a tangled relationship between the callers
4068 * and this routine as far as opts are concerned. Some
4069 * of the opts are handled here. In particular, r == 3,
4070 * r == 10, r == 11, and r == 13 are all handled here.
4071 * Don't use those values unless you want what happens
4072 * here. r == 12 and others are handled by the caller.
4073 * rflags -- Return flags
4074 * GER_OVER - overwrite of existing file
4075 * GER_APPEND - append of existing file
4076 * else file did not exist before
4078 * GER_ALLPARTS - AllParts toggle was turned on
4080 * qline -- Command line to prompt on.
4081 * flags -- Logically OR'd flags
4082 * GE_IS_EXPORT - The command was an Export command
4083 * so the prompt should include
4084 * EXPORT:.
4085 * GE_SEQ_SENSITIVE - The command that got us here is
4086 * sensitive to sequence number changes
4087 * caused by unsolicited expunges.
4088 * GE_NO_APPEND - We will not allow append to an
4089 * existing file, only removal of the
4090 * file if it exists.
4091 * GE_IS_IMPORT - We are selecting for reading.
4092 * No overwriting or checking for
4093 * existence at all. Don't use this
4094 * together with GE_NO_APPEND.
4095 * GE_ALLPARTS - Turn on AllParts toggle.
4097 * Returns: -1 cancelled
4098 * -2 prohibited by VAR_OPER_DIR
4099 * -3 other error, already reported here
4100 * 0 ok
4101 * 12 user chose 12 command from opts
4104 get_export_filename(struct pine *ps, char *filename, char *deefault,
4105 char *full_filename, size_t len, char *prompt_msg,
4106 char *lister_msg, ESCKEY_S *optsarg, int *rflags,
4107 int qline, int flags, HISTORY_S **history)
4109 char dir[MAXPATH+1], dir2[MAXPATH+1];
4110 char precolon[MAXPATH+1], postcolon[MAXPATH+1];
4111 char filename2[MAXPATH+1], tmp[MAXPATH+1], *fn, *ill;
4112 int l, i, ku = -1, r, fatal, homedir = 0, was_abs_path=0, avail, ret = 0;
4113 int allparts = 0;
4114 char prompt_buf[400];
4115 char def[500];
4116 ESCKEY_S *opts = NULL;
4117 struct variable *vars = ps->vars;
4119 if(flags & GE_ALLPARTS || history){
4121 * Copy the opts and add one to the end of the list.
4123 for(i = 0; optsarg[i].ch != -1; i++)
4126 if(history)
4127 i += 2;
4129 if(flags & GE_ALLPARTS)
4130 i++;
4132 opts = (ESCKEY_S *) fs_get((i+1) * sizeof(*opts));
4133 memset(opts, 0, (i+1) * sizeof(*opts));
4135 for(i = 0; optsarg[i].ch != -1; i++){
4136 opts[i].ch = optsarg[i].ch;
4137 opts[i].rval = optsarg[i].rval;
4138 opts[i].name = optsarg[i].name; /* no need to make a copy */
4139 opts[i].label = optsarg[i].label; /* " */
4142 if(flags & GE_ALLPARTS){
4143 allparts = i;
4144 opts[i].ch = ctrl('P');
4145 opts[i].rval = 13;
4146 opts[i].name = "^P";
4147 /* TRANSLATORS: Export all attachment parts */
4148 opts[i++].label = N_("AllParts");
4151 if(history){
4152 opts[i].ch = KEY_UP;
4153 opts[i].rval = 30;
4154 opts[i].name = "";
4155 ku = i;
4156 opts[i++].label = "";
4158 opts[i].ch = KEY_DOWN;
4159 opts[i].rval = 31;
4160 opts[i].name = "";
4161 opts[i++].label = "";
4164 opts[i].ch = -1;
4166 if(history)
4167 init_hist(history, HISTSIZE);
4169 else
4170 opts = optsarg;
4172 if(rflags)
4173 *rflags = GER_NONE;
4175 if(F_ON(F_USE_CURRENT_DIR, ps))
4176 dir[0] = '\0';
4177 else if(VAR_OPER_DIR){
4178 strncpy(dir, VAR_OPER_DIR, sizeof(dir));
4179 dir[sizeof(dir)-1] = '\0';
4181 #if defined(DOS) || defined(OS2)
4182 else if(VAR_FILE_DIR){
4183 strncpy(dir, VAR_FILE_DIR, sizeof(dir));
4184 dir[sizeof(dir)-1] = '\0';
4186 #endif
4187 else{
4188 dir[0] = '~';
4189 dir[1] = '\0';
4190 homedir=1;
4193 postcolon[0] = '\0';
4194 strncpy(precolon, dir, sizeof(precolon));
4195 precolon[sizeof(precolon)-1] = '\0';
4196 if(deefault){
4197 strncpy(def, deefault, sizeof(def)-1);
4198 def[sizeof(def)-1] = '\0';
4199 removing_leading_and_trailing_white_space(def);
4201 else
4202 def[0] = '\0';
4204 avail = MAX(20, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - MIN_OPT_ENT_WIDTH;
4206 /*---------- Prompt the user for the file name -------------*/
4207 while(1){
4208 int oeflags;
4209 char dirb[50], fileb[50];
4210 int l1, l2, l3, l4, l5, needed;
4211 char *p, p1[100], p2[100], *p3, p4[100], p5[100];
4213 snprintf(p1, sizeof(p1), "%sCopy ",
4214 (flags & GE_IS_EXPORT) ? "EXPORT: " :
4215 (flags & GE_IS_IMPORT) ? "IMPORT: " : "SAVE: ");
4216 p1[sizeof(p1)-1] = '\0';
4217 l1 = strlen(p1);
4219 strncpy(p2, prompt_msg ? prompt_msg : "", sizeof(p2)-1);
4220 p2[sizeof(p2)-1] = '\0';
4221 l2 = strlen(p2);
4223 if(rflags && *rflags & GER_ALLPARTS)
4224 p3 = " (and atts)";
4225 else
4226 p3 = "";
4228 l3 = strlen(p3);
4230 snprintf(p4, sizeof(p4), " %s file%s%s",
4231 (flags & GE_IS_IMPORT) ? "from" : "to",
4232 is_absolute_path(filename) ? "" : " in ",
4233 is_absolute_path(filename) ? "" :
4234 (!dir[0] ? "current directory"
4235 : (dir[0] == '~' && !dir[1]) ? "home directory"
4236 : short_str(dir,dirb,sizeof(dirb),30,FrontDots)));
4237 p4[sizeof(p4)-1] = '\0';
4238 l4 = strlen(p4);
4240 snprintf(p5, sizeof(p5), "%s%s%s: ",
4241 *def ? " [" : "",
4242 *def ? short_str(def,fileb,sizeof(fileb),40,EndDots) : "",
4243 *def ? "]" : "");
4244 p5[sizeof(p5)-1] = '\0';
4245 l5 = strlen(p5);
4247 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4248 snprintf(p4, sizeof(p4), " %s file%s%s",
4249 (flags & GE_IS_IMPORT) ? "from" : "to",
4250 is_absolute_path(filename) ? "" : " in ",
4251 is_absolute_path(filename) ? "" :
4252 (!dir[0] ? "current dir"
4253 : (dir[0] == '~' && !dir[1]) ? "home dir"
4254 : short_str(dir,dirb,sizeof(dirb),10,FrontDots)));
4255 p4[sizeof(p4)-1] = '\0';
4256 l4 = strlen(p4);
4259 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4260 snprintf(p5, sizeof(p5), "%s%s%s: ",
4261 *def ? " [" : "",
4262 *def ? short_str(def,fileb,sizeof(fileb),
4263 MAX(15,l5-5-needed),EndDots) : "",
4264 *def ? "]" : "");
4265 p5[sizeof(p5)-1] = '\0';
4266 l5 = strlen(p5);
4269 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l2 > 0){
4272 * 14 is about the shortest we can make this, because there are
4273 * fixed length strings of length 14 coming in here.
4275 p = short_str(prompt_msg, p2, sizeof(p2), MAX(14,l2-needed), FrontDots);
4276 if(p != p2){
4277 strncpy(p2, p, sizeof(p2)-1);
4278 p2[sizeof(p2)-1] = '\0';
4281 l2 = strlen(p2);
4284 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4285 strncpy(p1, "Copy ", sizeof(p1)-1);
4286 p1[sizeof(p1)-1] = '\0';
4287 l1 = strlen(p1);
4290 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4291 snprintf(p5, sizeof(p5), "%s%s%s: ",
4292 *def ? " [" : "",
4293 *def ? short_str(def,fileb, sizeof(fileb),
4294 MAX(10,l5-5-needed),EndDots) : "",
4295 *def ? "]" : "");
4296 p5[sizeof(p5)-1] = '\0';
4297 l5 = strlen(p5);
4300 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l3 > 0){
4301 if(needed <= l3 - strlen(" (+ atts)"))
4302 p3 = " (+ atts)";
4303 else if(needed <= l3 - strlen(" (atts)"))
4304 p3 = " (atts)";
4305 else if(needed <= l3 - strlen(" (+)"))
4306 p3 = " (+)";
4307 else if(needed <= l3 - strlen("+"))
4308 p3 = "+";
4309 else
4310 p3 = "";
4312 l3 = strlen(p3);
4315 snprintf(prompt_buf, sizeof(prompt_buf), "%s%s%s%s%s", p1, p2, p3, p4, p5);
4316 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4318 if(ku >= 0){
4319 if(items_in_hist(*history) > 0){
4320 opts[ku].name = HISTORY_UP_KEYNAME;
4321 opts[ku].label = HISTORY_KEYLABEL;
4322 opts[ku+1].name = HISTORY_DOWN_KEYNAME;
4323 opts[ku+1].label = HISTORY_KEYLABEL;
4325 else{
4326 opts[ku].name = "";
4327 opts[ku].label = "";
4328 opts[ku+1].name = "";
4329 opts[ku+1].label = "";
4333 oeflags = OE_APPEND_CURRENT |
4334 ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0);
4335 r = optionally_enter(filename, qline, 0, len, prompt_buf,
4336 opts, NO_HELP, &oeflags);
4338 /*--- Help ----*/
4339 if(r == 3){
4341 * Helps may not be right if you add another caller or change
4342 * things. Check it out.
4344 if(flags & GE_IS_IMPORT)
4345 helper(h_ge_import, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE);
4346 else if(flags & GE_ALLPARTS)
4347 helper(h_ge_allparts, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4348 else
4349 helper(h_ge_export, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4351 ps->mangled_screen = 1;
4353 continue;
4355 else if(r == 10 || r == 11){ /* Browser or File Completion */
4356 if(filename[0]=='~'){
4357 if(filename[1] == C_FILESEP && filename[2]!='\0'){
4358 precolon[0] = '~';
4359 precolon[1] = '\0';
4360 for(i=0; filename[i+2] != '\0' && i+2 < len-1; i++)
4361 filename[i] = filename[i+2];
4362 filename[i] = '\0';
4363 strncpy(dir, precolon, sizeof(dir)-1);
4364 dir[sizeof(dir)-1] = '\0';
4366 else if(filename[1]=='\0' ||
4367 (filename[1] == C_FILESEP && filename[2] == '\0')){
4368 precolon[0] = '~';
4369 precolon[1] = '\0';
4370 filename[0] = '\0';
4371 strncpy(dir, precolon, sizeof(dir)-1);
4372 dir[sizeof(dir)-1] = '\0';
4375 else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){
4376 if(homedir){
4377 precolon[0] = '~';
4378 precolon[1] = '\0';
4379 strncpy(dir, precolon, sizeof(dir)-1);
4380 dir[sizeof(dir)-1] = '\0';
4382 else{
4383 precolon[0] = '\0';
4384 dir[0] = '\0';
4387 l = MAXPATH;
4388 dir2[0] = '\0';
4389 strncpy(tmp, filename, sizeof(tmp)-1);
4390 tmp[sizeof(tmp)-1] = '\0';
4391 if(*tmp && is_absolute_path(tmp))
4392 fnexpand(tmp, sizeof(tmp));
4393 if(strncmp(tmp,postcolon, strlen(postcolon)))
4394 postcolon[0] = '\0';
4396 if(*tmp && (fn = last_cmpnt(tmp))){
4397 l -= fn - tmp;
4398 strncpy(filename2, fn, sizeof(filename2)-1);
4399 filename2[sizeof(filename2)-1] = '\0';
4400 if(is_absolute_path(tmp)){
4401 strncpy(dir2, tmp, MIN(fn - tmp, sizeof(dir2)-1));
4402 dir2[MIN(fn - tmp, sizeof(dir2)-1)] = '\0';
4403 #ifdef _WINDOWS
4404 if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){
4405 dir2[2] = '\\';
4406 dir2[3] = '\0';
4408 #endif
4409 strncpy(postcolon, dir2, sizeof(postcolon)-1);
4410 postcolon[sizeof(postcolon)-1] = '\0';
4411 precolon[0] = '\0';
4413 else{
4414 char *p = NULL;
4416 * Just building the directory name in dir2,
4417 * full_filename is overloaded.
4419 snprintf(full_filename, len, "%.*s", MIN(fn-tmp,len-1), tmp);
4420 full_filename[len-1] = '\0';
4421 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4422 postcolon[sizeof(postcolon)-1] = '\0';
4423 build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH)
4424 : (dir[0] == '~' && !dir[1])
4425 ? ps->home_dir
4426 : dir,
4427 full_filename, sizeof(dir2));
4428 if(p)
4429 free(p);
4432 else{
4433 if(is_absolute_path(tmp)){
4434 strncpy(dir2, tmp, sizeof(dir2)-1);
4435 dir2[sizeof(dir2)-1] = '\0';
4436 #ifdef _WINDOWS
4437 if(dir2[2]=='\0' && dir2[1]==':'){
4438 dir2[2]='\\';
4439 dir2[3]='\0';
4440 strncpy(postcolon,dir2,sizeof(postcolon)-1);
4441 postcolon[sizeof(postcolon)-1] = '\0';
4443 #endif
4444 filename2[0] = '\0';
4445 precolon[0] = '\0';
4447 else{
4448 strncpy(filename2, tmp, sizeof(filename2)-1);
4449 filename2[sizeof(filename2)-1] = '\0';
4450 if(!dir[0])
4451 (void)getcwd(dir2, sizeof(dir2));
4452 else if(dir[0] == '~' && !dir[1]){
4453 strncpy(dir2, ps->home_dir, sizeof(dir2)-1);
4454 dir2[sizeof(dir2)-1] = '\0';
4456 else{
4457 strncpy(dir2, dir, sizeof(dir2)-1);
4458 dir2[sizeof(dir2)-1] = '\0';
4461 postcolon[0] = '\0';
4465 build_path(full_filename, dir2, filename2, len);
4466 if(!strcmp(full_filename, dir2))
4467 filename2[0] = '\0';
4468 if(full_filename[strlen(full_filename)-1] == C_FILESEP
4469 && isdir(full_filename,NULL,NULL)){
4470 if(strlen(full_filename) == 1)
4471 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4472 else if(filename2[0])
4473 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4474 postcolon[sizeof(postcolon)-1] = '\0';
4475 strncpy(dir2, full_filename, sizeof(dir2)-1);
4476 dir2[sizeof(dir2)-1] = '\0';
4477 filename2[0] = '\0';
4479 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4480 else if(full_filename[strlen(full_filename)-1] == C_FILESEP){
4481 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4482 postcolon[sizeof(postcolon)-1] = '\0';
4483 strncpy(dir2, full_filename, sizeof(dir2)-1);
4484 dir2[sizeof(dir2)-1] = '\0';
4485 filename2[0] = '\0';
4487 #endif
4488 if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1
4489 && strcmp(dir2+1, ":\\"))
4490 /* last condition to prevent stripping of '\\'
4491 in windows partition */
4492 dir2[strlen(dir2)-1] = '\0';
4494 if(r == 10){ /* File Browser */
4495 r = file_lister(lister_msg ? lister_msg : "EXPORT",
4496 dir2, sizeof(dir2), filename2, sizeof(filename2),
4497 TRUE,
4498 (flags & GE_IS_IMPORT) ? FB_READ : FB_SAVE);
4499 #ifdef _WINDOWS
4500 /* Windows has a special "feature" in which entering the file browser will
4501 change the working directory if the directory is changed at all (even
4502 clicking "Cancel" will change the working directory).
4504 if(F_ON(F_USE_CURRENT_DIR, ps))
4505 (void)getcwd(dir2,sizeof(dir2));
4506 #endif
4507 if(isdir(dir2,NULL,NULL)){
4508 strncpy(precolon, dir2, sizeof(precolon)-1);
4509 precolon[sizeof(precolon)-1] = '\0';
4511 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4512 postcolon[sizeof(postcolon)-1] = '\0';
4513 if(r == 1){
4514 build_path(full_filename, dir2, filename2, len);
4515 if(isdir(full_filename, NULL, NULL)){
4516 strncpy(dir, full_filename, sizeof(dir)-1);
4517 dir[sizeof(dir)-1] = '\0';
4518 filename[0] = '\0';
4520 else{
4521 fn = last_cmpnt(full_filename);
4522 strncpy(dir, full_filename,
4523 MIN(fn - full_filename, sizeof(dir)-1));
4524 dir[MIN(fn - full_filename, sizeof(dir)-1)] = '\0';
4525 if(fn - full_filename > 1)
4526 dir[fn - full_filename - 1] = '\0';
4529 if(!strcmp(dir, ps->home_dir)){
4530 dir[0] = '~';
4531 dir[1] = '\0';
4534 strncpy(filename, fn, len-1);
4535 filename[len-1] = '\0';
4538 else{ /* File Completion */
4539 if(!pico_fncomplete(dir2, filename2, sizeof(filename2)))
4540 Writechar(BELL, 0);
4541 strncat(postcolon, filename2,
4542 sizeof(postcolon)-1-strlen(postcolon));
4543 postcolon[sizeof(postcolon)-1] = '\0';
4545 was_abs_path = is_absolute_path(filename);
4547 if(!strcmp(dir, ps->home_dir)){
4548 dir[0] = '~';
4549 dir[1] = '\0';
4552 strncpy(filename, postcolon, len-1);
4553 filename[len-1] = '\0';
4554 strncpy(dir, precolon, sizeof(dir)-1);
4555 dir[sizeof(dir)-1] = '\0';
4557 if(filename[0] == '~' && !filename[1]){
4558 dir[0] = '~';
4559 dir[1] = '\0';
4560 filename[0] = '\0';
4563 continue;
4565 else if(r == 12){ /* Download, caller handles it */
4566 ret = r;
4567 goto done;
4569 else if(r == 13){ /* toggle AllParts bit */
4570 if(rflags){
4571 if(*rflags & GER_ALLPARTS){
4572 *rflags &= ~GER_ALLPARTS;
4573 opts[allparts].label = N_("AllParts");
4575 else{
4576 *rflags |= GER_ALLPARTS;
4577 /* opposite of All Parts, No All Parts */
4578 opts[allparts].label = N_("NoAllParts");
4582 continue;
4584 #if 0
4585 else if(r == 14){ /* List file names matching partial? */
4586 continue;
4588 #endif
4589 else if(r == 1){ /* Cancel */
4590 ret = -1;
4591 goto done;
4593 else if(r == 4){
4594 continue;
4596 else if(r == 30 || r == 31){
4597 char *p = NULL;
4599 if(history){
4600 if(r == 30)
4601 p = get_prev_hist(*history, filename, 0, NULL);
4602 else
4603 p = get_next_hist(*history, filename, 0, NULL);
4606 if(p != NULL){
4607 fn = last_cmpnt(p);
4608 strncpy(dir, p, MIN(fn - p, sizeof(dir)-1));
4609 dir[MIN(fn - p, sizeof(dir)-1)] = '\0';
4610 if(fn - p > 1)
4611 dir[fn - p - 1] = '\0';
4613 if(!strcmp(dir, ps->home_dir)){
4614 dir[0] = '~';
4615 dir[1] = '\0';
4618 strncpy(filename, fn, len-1);
4619 filename[len-1] = '\0';
4621 else
4622 Writechar(BELL, 0);
4624 continue;
4626 else if(r != 0){
4627 Writechar(BELL, 0);
4628 continue;
4631 removing_leading_and_trailing_white_space(filename);
4633 if(!*filename){
4634 if(!*def){ /* Cancel */
4635 ret = -1;
4636 goto done;
4639 strncpy(filename, def, len-1);
4640 filename[len-1] = '\0';
4643 #if defined(DOS) || defined(OS2)
4644 if(is_absolute_path(filename)){
4645 fixpath(filename, len);
4647 #else
4648 if(filename[0] == '~'){
4649 if(fnexpand(filename, len) == NULL){
4650 char *p = strindex(filename, '/');
4651 if(p != NULL)
4652 *p = '\0';
4653 q_status_message1(SM_ORDER | SM_DING, 3, 3,
4654 _("Error expanding file name: \"%s\" unknown user"),
4655 filename);
4656 continue;
4659 #endif
4661 if(is_absolute_path(filename)){
4662 strncpy(full_filename, filename, len-1);
4663 full_filename[len-1] = '\0';
4665 else{
4666 if(!dir[0])
4667 build_path(full_filename, (char *)getcwd(dir,sizeof(dir)),
4668 filename, len);
4669 else if(dir[0] == '~' && !dir[1])
4670 build_path(full_filename, ps->home_dir, filename, len);
4671 else
4672 build_path(full_filename, dir, filename, len);
4675 if((ill = filter_filename(full_filename, &fatal,
4676 ps_global->restricted || ps_global->VAR_OPER_DIR)) != NULL){
4677 if(fatal){
4678 q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill);
4679 continue;
4681 else{
4682 /* BUG: we should beep when the key's pressed rather than bitch later */
4683 /* Warn and ask for confirmation. */
4684 snprintf(prompt_buf, sizeof(prompt_buf), "File name contains a '%s'. %s anyway",
4685 ill, (flags & GE_IS_EXPORT) ? "Export" : "Save");
4686 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4687 if(want_to(prompt_buf, 'n', 0, NO_HELP,
4688 ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y')
4689 continue;
4693 break; /* Must have got an OK file name */
4696 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){
4697 ret = -2;
4698 goto done;
4701 if(!can_access(full_filename, ACCESS_EXISTS)){
4702 int rbflags;
4703 static ESCKEY_S access_opts[] = {
4704 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4705 a file or append to the end of the file */
4706 {'o', 'o', "O", N_("Overwrite")},
4707 {'a', 'a', "A", N_("Append")},
4708 {-1, 0, NULL, NULL}};
4710 rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0);
4712 if(flags & GE_NO_APPEND){
4713 r = strlen(filename);
4714 snprintf(prompt_buf, sizeof(prompt_buf),
4715 /* TRANSLATORS: asking user whether to overwrite a file or not,
4716 File <filename> already exists. Overwrite it ? */
4717 _("File \"%s%s\" already exists. Overwrite it "),
4718 (r > 20) ? "..." : "",
4719 filename + ((r > 20) ? r - 20 : 0));
4720 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4721 if(want_to(prompt_buf, 'n', 'x', NO_HELP, rbflags) == 'y'){
4722 if(rflags)
4723 *rflags |= GER_OVER;
4725 if(our_unlink(full_filename) < 0){
4726 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4727 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4728 _("Cannot remove old %s: %s"),
4729 full_filename, error_description(errno));
4732 else{
4733 ret = -1;
4734 goto done;
4737 else if(!(flags & GE_IS_IMPORT)){
4738 r = strlen(filename);
4739 snprintf(prompt_buf, sizeof(prompt_buf),
4740 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4741 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4742 (r > 20) ? "..." : "",
4743 filename + ((r > 20) ? r - 20 : 0));
4744 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4745 switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global),
4746 access_opts, 'a', 'x', NO_HELP, rbflags)){
4747 case 'o' :
4748 if(rflags)
4749 *rflags |= GER_OVER;
4751 if(our_truncate(full_filename, (off_t)0) < 0)
4752 /* trouble truncating, but we'll give it a try anyway */
4753 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4754 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
4755 _("Warning: Cannot truncate old %s: %s"),
4756 full_filename, error_description(errno));
4757 break;
4759 case 'a' :
4760 if(rflags)
4761 *rflags |= GER_APPEND;
4763 break;
4765 case 'x' :
4766 default :
4767 ret = -1;
4768 goto done;
4773 done:
4774 if(history && ret == 0)
4775 save_hist(*history, full_filename, 0, NULL);
4777 if(opts && opts != optsarg)
4778 fs_give((void **) &opts);
4780 return(ret);
4784 /*----------------------------------------------------------------------
4785 parse the config'd upload/download command
4787 Args: cmd -- buffer to return command fit for shellin'
4788 prefix --
4789 cfg_str --
4790 fname -- file name to build into the command
4792 Returns: pointer to cmd_str buffer or NULL on real bad error
4794 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
4795 cfg_str is written to standard out right before a successful
4796 return of this function. The call immediately following this
4797 function darn well better be the shell exec...
4798 ----*/
4799 char *
4800 build_updown_cmd(char *cmd, size_t cmdlen, char *prefix, char *cfg_str, char *fname)
4802 char *p;
4803 int fname_found = 0;
4805 if(prefix && *prefix){
4806 /* loop thru replacing all occurances of _FILE_ */
4807 p = strncpy(cmd, prefix, cmdlen);
4808 cmd[cmdlen-1] = '\0';
4809 while((p = strstr(p, "_FILE_")))
4810 rplstr(p, cmdlen-(p-cmd), 6, fname);
4812 fputs(cmd, stdout);
4815 /* loop thru replacing all occurances of _FILE_ */
4816 p = strncpy(cmd, cfg_str, cmdlen);
4817 cmd[cmdlen-1] = '\0';
4818 while((p = strstr(p, "_FILE_"))){
4819 rplstr(p, cmdlen-(p-cmd), 6, fname);
4820 fname_found = 1;
4823 if(!fname_found)
4824 snprintf(cmd+strlen(cmd), cmdlen-strlen(cmd), " %s", fname);
4826 cmd[cmdlen-1] = '\0';
4828 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
4829 cmd ? cmd : "?"));
4830 return(cmd);
4834 /*----------------------------------------------------------------------
4835 Write a berzerk format message delimiter using the given putc function
4837 Args: e -- envelope of message to write
4838 pc -- function to use
4840 Returns: TRUE if we could write it, FALSE if there was a problem
4842 NOTE: follows delimiter with OS-dependent newline
4843 ----*/
4845 bezerk_delimiter(ENVELOPE *env, MESSAGECACHE *mc, gf_io_t pc, int leading_newline)
4847 MESSAGECACHE telt;
4848 time_t when;
4849 char *p;
4851 /* write "[\n]From mailbox[@host] " */
4852 if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1)
4853 && gf_puts("From ", pc)
4854 && gf_puts((env && env->from) ? env->from->mailbox
4855 : "the-concourse-on-high", pc)
4856 && gf_puts((env && env->from && env->from->host) ? "@" : "", pc)
4857 && gf_puts((env && env->from && env->from->host) ? env->from->host
4858 : "", pc)
4859 && (*pc)(' ')))
4860 return(0);
4862 if(mc && mc->valid)
4863 when = mail_longdate(mc);
4864 else if(env && env->date && env->date[0]
4865 && mail_parse_date(&telt,env->date))
4866 when = mail_longdate(&telt);
4867 else
4868 when = time(0);
4870 p = ctime(&when);
4872 while(p && *p && *p != '\n') /* write date */
4873 if(!(*pc)(*p++))
4874 return(0);
4876 if(!gf_puts(NEWLINE, pc)) /* write terminating newline */
4877 return(0);
4879 return(1);
4883 /*----------------------------------------------------------------------
4884 Execute command to jump to a given message number
4886 Args: qline -- Line to ask question on
4888 Result: returns true if the use selected a new message, false otherwise
4890 ----*/
4891 long
4892 jump_to(MSGNO_S *msgmap, int qline, UCS first_num, SCROLL_S *sparms, CmdWhere in_index)
4894 char jump_num_string[80], *j, prompt[70];
4895 HelpType help;
4896 int rc;
4897 static ESCKEY_S jump_to_key[] = { {0, 0, NULL, NULL},
4898 /* TRANSLATORS: go to First Message */
4899 {ctrl('Y'), 10, "^Y", N_("First Msg")},
4900 {ctrl('V'), 11, "^V", N_("Last Msg")},
4901 {-1, 0, NULL, NULL} };
4903 dprint((4, "\n - jump_to -\n"));
4905 #ifdef DEBUG
4906 if(sparms && sparms->jump_is_debug)
4907 return(get_level(qline, first_num, sparms));
4908 #endif
4910 if(!any_messages(msgmap, NULL, "to Jump to"))
4911 return(0L);
4913 if(first_num && first_num < 0x80 && isdigit((unsigned char) first_num)){
4914 jump_num_string[0] = first_num;
4915 jump_num_string[1] = '\0';
4917 else
4918 jump_num_string[0] = '\0';
4920 if(mn_total_cur(msgmap) > 1L){
4921 snprintf(prompt, sizeof(prompt), "Unselect %s msgs in favor of number to be entered",
4922 comatose(mn_total_cur(msgmap)));
4923 prompt[sizeof(prompt)-1] = '\0';
4924 if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n')
4925 return(0L);
4928 snprintf(prompt, sizeof(prompt), "%s number to jump to : ", in_index == ThrdIndx
4929 ? "Thread"
4930 : "Message");
4931 prompt[sizeof(prompt)-1] = '\0';
4933 help = NO_HELP;
4934 while(1){
4935 int flags = OE_APPEND_CURRENT;
4937 rc = optionally_enter(jump_num_string, qline, 0,
4938 sizeof(jump_num_string), prompt,
4939 jump_to_key, help, &flags);
4940 if(rc == 3){
4941 help = help == NO_HELP
4942 ? (in_index == ThrdIndx ? h_oe_jump_thd : h_oe_jump)
4943 : NO_HELP;
4944 continue;
4946 else if(rc == 10 || rc == 11){
4947 char warning[100];
4948 long closest;
4950 closest = closest_jump_target(rc == 10 ? 1L
4951 : ((in_index == ThrdIndx)
4952 ? msgmap->max_thrdno
4953 : mn_get_total(msgmap)),
4954 ps_global->mail_stream,
4955 msgmap, 0,
4956 in_index, warning, sizeof(warning));
4957 /* ignore warning */
4958 return(closest);
4962 * If we take out the *jump_num_string nonempty test in this if
4963 * then the closest_jump_target routine will offer a jump to the
4964 * last message. However, it is slow because you have to wait for
4965 * the status message and it is annoying for people who hit J command
4966 * by mistake and just want to hit return to do nothing, like has
4967 * always worked. So the test is there for now. Hubert 2002-08-19
4969 * Jumping to first/last message is now possible through ^Y/^V
4970 * commands above. jpf 2002-08-21
4971 * (and through "end" hubert 2006-07-07)
4973 if(rc == 0 && *jump_num_string != '\0'){
4974 removing_leading_and_trailing_white_space(jump_num_string);
4975 for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++)
4978 if(*j != '\0'){
4979 if(!strucmp("end", j))
4980 return((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap));
4982 q_status_message(SM_ORDER | SM_DING, 2, 2,
4983 _("Invalid number entered. Use only digits 0-9"));
4984 jump_num_string[0] = '\0';
4986 else{
4987 char warning[100];
4988 long closest, jump_num;
4990 if(*jump_num_string)
4991 jump_num = atol(jump_num_string);
4992 else
4993 jump_num = -1L;
4995 warning[0] = '\0';
4996 closest = closest_jump_target(jump_num, ps_global->mail_stream,
4997 msgmap,
4998 *jump_num_string ? 0 : 1,
4999 in_index, warning, sizeof(warning));
5000 if(warning[0])
5001 q_status_message(SM_ORDER | SM_DING, 2, 2, warning);
5003 if(closest == jump_num)
5004 return(jump_num);
5006 if(closest == 0L)
5007 jump_num_string[0] = '\0';
5008 else
5009 strncpy(jump_num_string, long2string(closest),
5010 sizeof(jump_num_string));
5013 continue;
5016 if(rc != 4)
5017 break;
5020 return(0L);
5025 * cmd_delete_action - handle msgno advance and such after single message deletion
5027 char *
5028 cmd_delete_action(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
5030 int opts;
5031 long msgno;
5032 char *rv = NULL;
5034 msgno = mn_get_cur(msgmap);
5035 advance_cur_after_delete(state, state->mail_stream, msgmap, in_index);
5037 if(IS_NEWS(state->mail_stream)
5038 || ((state->context_current->use & CNTXT_INCMNG)
5039 && context_isambig(state->cur_folder))){
5041 opts = (NSF_TRUST_FLAGS | NSF_SKIP_CHID);
5042 if(in_index == View)
5043 opts &= ~NSF_SKIP_CHID;
5045 (void)next_sorted_flagged(F_UNDEL|F_UNSEEN, state->mail_stream, msgno, &opts);
5046 if(!(opts & NSF_FLAG_MATCH)){
5047 char nextfolder[MAXPATH];
5049 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
5050 nextfolder[sizeof(nextfolder)-1] = '\0';
5051 rv = next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
5052 state->context_current, NULL, NULL)
5053 ? ". Press TAB for next folder."
5054 : ". No more folders to TAB to.";
5058 return(rv);
5063 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5065 char *
5066 cmd_delete_index(struct pine *state, MSGNO_S *msgmap)
5068 return(cmd_delete_action(state, msgmap,MsgIndx));
5072 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5074 char *
5075 cmd_delete_view(struct pine *state, MSGNO_S *msgmap)
5077 return(cmd_delete_action(state, msgmap, View));
5081 void
5082 advance_cur_after_delete(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, CmdWhere in_index)
5084 long new_msgno, msgno;
5085 int opts;
5087 new_msgno = msgno = mn_get_cur(msgmap);
5088 opts = NSF_TRUST_FLAGS;
5090 if(F_ON(F_DEL_SKIPS_DEL, state)){
5092 if(THREADING() && sp_viewing_a_thread(stream))
5093 opts |= NSF_SKIP_CHID;
5095 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5097 else{
5098 mn_inc_cur(stream, msgmap,
5099 (in_index == View && THREADING()
5100 && sp_viewing_a_thread(stream))
5101 ? MH_THISTHD
5102 : (in_index == View)
5103 ? MH_ANYTHD : MH_NONE);
5104 new_msgno = mn_get_cur(msgmap);
5105 if(new_msgno != msgno)
5106 opts |= NSF_FLAG_MATCH;
5110 * Viewing_a_thread is the complicated case because we want to ignore
5111 * other threads at first and then look in other threads if we have to.
5112 * By ignoring other threads we also ignore collapsed partial threads
5113 * in our own thread.
5115 if(THREADING() && sp_viewing_a_thread(stream) && !(opts & NSF_FLAG_MATCH)){
5116 long rawno, orig_thrdno;
5117 PINETHRD_S *thrd, *topthrd = NULL;
5119 rawno = mn_m2raw(msgmap, msgno);
5120 thrd = fetch_thread(stream, rawno);
5121 if(thrd && thrd->top)
5122 topthrd = fetch_thread(stream, thrd->top);
5124 orig_thrdno = topthrd ? topthrd->thrdno : -1L;
5126 opts = NSF_TRUST_FLAGS;
5127 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5130 * If we got a match, new_msgno may be a message in
5131 * a different thread from the one we are viewing, or it could be
5132 * in a collapsed part of this thread.
5134 if(opts & NSF_FLAG_MATCH){
5135 int ret;
5136 char pmt[128];
5138 topthrd = NULL;
5139 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
5140 if(thrd && thrd->top)
5141 topthrd = fetch_thread(stream, thrd->top);
5144 * If this match is in the same thread we're already in
5145 * then we're done, else we have to ask the user and maybe
5146 * switch threads.
5148 if(!(orig_thrdno > 0L && topthrd
5149 && topthrd->thrdno == orig_thrdno)){
5151 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
5152 if(in_index == View)
5153 snprintf(pmt, sizeof(pmt),
5154 "View message in thread number %.10s",
5155 topthrd ? comatose(topthrd->thrdno) : "?");
5156 else
5157 snprintf(pmt, sizeof(pmt), "View thread number %.10s",
5158 topthrd ? comatose(topthrd->thrdno) : "?");
5160 ret = want_to(pmt, 'y', 'x', NO_HELP, WT_NORM);
5162 else
5163 ret = 'y';
5165 if(ret == 'y'){
5166 unview_thread(state, stream, msgmap);
5167 mn_set_cur(msgmap, new_msgno);
5168 if(THRD_AUTO_VIEW()
5169 && (count_lflags_in_thread(stream, topthrd, msgmap,
5170 MN_NONE) == 1)
5171 && view_thread(state, stream, msgmap, 1)){
5172 if(current_index_state)
5173 msgmap->top_after_thrd = current_index_state->msg_at_top;
5175 state->view_skipped_index = 1;
5176 state->next_screen = mail_view_screen;
5178 else{
5179 view_thread(state, stream, msgmap, 1);
5180 if(current_index_state)
5181 msgmap->top_after_thrd = current_index_state->msg_at_top;
5183 state->next_screen = SCREEN_FUN_NULL;
5186 else
5187 new_msgno = msgno; /* stick with original */
5192 mn_set_cur(msgmap, new_msgno);
5193 if(in_index != View)
5194 adjust_cur_to_visible(stream, msgmap);
5198 #ifdef DEBUG
5199 long
5200 get_level(int qline, UCS first_num, SCROLL_S *sparms)
5202 char debug_num_string[80], *j, prompt[70];
5203 HelpType help;
5204 int rc;
5205 long debug_num;
5207 if(first_num && first_num < 0x80 && isdigit((unsigned char)first_num)){
5208 debug_num_string[0] = first_num;
5209 debug_num_string[1] = '\0';
5210 debug_num = atol(debug_num_string);
5211 *(int *)(sparms->proc.data.p) = debug_num;
5212 q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s",
5213 comatose(debug_num));
5214 return(1L);
5216 else
5217 debug_num_string[0] = '\0';
5219 snprintf(prompt, sizeof(prompt), "Show debug <= this level (0-%d) : ", MAX(debug, 9));
5220 prompt[sizeof(prompt)-1] = '\0';
5222 help = NO_HELP;
5223 while(1){
5224 int flags = OE_APPEND_CURRENT;
5226 rc = optionally_enter(debug_num_string, qline, 0,
5227 sizeof(debug_num_string), prompt,
5228 NULL, help, &flags);
5229 if(rc == 3){
5230 help = help == NO_HELP ? h_oe_debuglevel : NO_HELP;
5231 continue;
5234 if(rc == 0){
5235 removing_leading_and_trailing_white_space(debug_num_string);
5236 for(j=debug_num_string; isdigit((unsigned char)*j); j++)
5239 if(*j != '\0'){
5240 q_status_message(SM_ORDER | SM_DING, 2, 2,
5241 _("Invalid number entered. Use only digits 0-9"));
5242 debug_num_string[0] = '\0';
5244 else{
5245 debug_num = atol(debug_num_string);
5246 if(debug_num < 0)
5247 q_status_message(SM_ORDER | SM_DING, 2, 2,
5248 _("Number should be >= 0"));
5249 else if(debug_num > MAX(debug,9))
5250 q_status_message1(SM_ORDER | SM_DING, 2, 2,
5251 _("Maximum is %s"), comatose(MAX(debug,9)));
5252 else{
5253 *(int *)(sparms->proc.data.p) = debug_num;
5254 q_status_message1(SM_ORDER, 0, 3,
5255 "Show debug <= level %s",
5256 comatose(debug_num));
5257 return(1L);
5261 continue;
5264 if(rc != 4)
5265 break;
5268 return(0L);
5270 #endif /* DEBUG */
5274 * Returns the message number closest to target that isn't hidden.
5275 * Make warning at least 100 chars.
5276 * A return of 0 means there is no message to jump to.
5278 long
5279 closest_jump_target(long int target, MAILSTREAM *stream, MSGNO_S *msgmap, int no_target, CmdWhere in_index, char *warning, size_t warninglen)
5281 long i, start, closest = 0L;
5282 char buf[80];
5283 long maxnum;
5285 warning[0] = '\0';
5286 maxnum = (in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap);
5288 if(no_target){
5289 target = maxnum;
5290 start = 1L;
5291 snprintf(warning, warninglen, "No %s number entered, jump to end? ",
5292 (in_index == ThrdIndx) ? "thread" : "message");
5293 warning[warninglen-1] = '\0';
5295 else if(target < 1L)
5296 start = 1L - target;
5297 else if(target > maxnum)
5298 start = target - maxnum;
5299 else
5300 start = 1L;
5302 if(target > 0L && target <= maxnum)
5303 if(in_index == ThrdIndx
5304 || !msgline_hidden(stream, msgmap, target, 0))
5305 return(target);
5307 for(i = start; target+i <= maxnum || target-i > 0L; i++){
5309 if(target+i > 0L && target+i <= maxnum &&
5310 (in_index == ThrdIndx
5311 || !msgline_hidden(stream, msgmap, target+i, 0))){
5312 closest = target+i;
5313 break;
5316 if(target-i > 0L && target-i <= maxnum &&
5317 (in_index == ThrdIndx
5318 || !msgline_hidden(stream, msgmap, target-i, 0))){
5319 closest = target-i;
5320 break;
5324 strncpy(buf, long2string(closest), sizeof(buf));
5325 buf[sizeof(buf)-1] = '\0';
5327 if(closest == 0L)
5328 strncpy(warning, "Nothing to jump to", warninglen);
5329 else if(target < 1L)
5330 snprintf(warning, warninglen, "%s number (%s) must be at least %s",
5331 (in_index == ThrdIndx) ? "Thread" : "Message",
5332 long2string(target), buf);
5333 else if(target > maxnum)
5334 snprintf(warning, warninglen, "%s number (%s) may be no more than %s",
5335 (in_index == ThrdIndx) ? "Thread" : "Message",
5336 long2string(target), buf);
5337 else if(!no_target)
5338 snprintf(warning, warninglen,
5339 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5340 long2string(target), buf);
5342 warning[warninglen-1] = '\0';
5344 return(closest);
5348 /*----------------------------------------------------------------------
5349 Prompt for folder name to open, expand the name and return it
5351 Args: qline -- Screen line to prompt on
5352 allow_list -- if 1, allow ^T to bring up collection lister
5354 Result: returns the folder name or NULL
5355 pine structure mangled_footer flag is set
5356 may call the collection lister in which case mangled screen will be set
5358 This prompts the user for the folder to open, possibly calling up
5359 the collection lister if the user types ^T.
5360 ----------------------------------------------------------------------*/
5361 char *
5362 broach_folder(int qline, int allow_list, int *notrealinbox, CONTEXT_S **context)
5364 HelpType help;
5365 static char newfolder[MAILTMPLEN];
5366 char expanded[MAXPATH+1],
5367 prompt[MAX_SCREEN_COLS+1],
5368 *last_folder, *p;
5369 unsigned char *f1, *f2, *f3;
5370 static HISTORY_S *history = NULL;
5371 CONTEXT_S *tc, *tc2;
5372 ESCKEY_S ekey[9];
5373 int rc, r, ku = -1, n, flags, last_rc = 0, inbox, done = 0;
5376 * the idea is to provide a clue for the context the file name
5377 * will be saved in (if a non-imap names is typed), and to
5378 * only show the previous if it was also in the same context
5380 help = NO_HELP;
5381 *expanded = '\0';
5382 *newfolder = '\0';
5383 last_folder = NULL;
5384 if(notrealinbox)
5385 (*notrealinbox) = 1;
5387 init_hist(&history, HISTSIZE);
5389 tc = broach_get_folder(context ? *context : NULL, &inbox, NULL);
5391 /* set up extra command option keys */
5392 rc = 0;
5393 ekey[rc].ch = (allow_list) ? ctrl('T') : 0 ;
5394 ekey[rc].rval = (allow_list) ? 2 : 0;
5395 ekey[rc].name = (allow_list) ? "^T" : "";
5396 ekey[rc++].label = (allow_list) ? N_("ToFldrs") : "";
5398 if(ps_global->context_list->next){
5399 ekey[rc].ch = ctrl('P');
5400 ekey[rc].rval = 10;
5401 ekey[rc].name = "^P";
5402 ekey[rc++].label = N_("Prev Collection");
5404 ekey[rc].ch = ctrl('N');
5405 ekey[rc].rval = 11;
5406 ekey[rc].name = "^N";
5407 ekey[rc++].label = N_("Next Collection");
5410 ekey[rc].ch = ctrl('W');
5411 ekey[rc].rval = 17;
5412 ekey[rc].name = "^W";
5413 ekey[rc++].label = N_("INBOX");
5415 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
5416 ekey[rc].ch = TAB;
5417 ekey[rc].rval = 12;
5418 ekey[rc].name = "TAB";
5419 ekey[rc++].label = N_("Complete");
5422 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
5423 ekey[rc].ch = ctrl('X');
5424 ekey[rc].rval = 14;
5425 ekey[rc].name = "^X";
5426 ekey[rc++].label = N_("ListMatches");
5429 if(ps_global->context_list->next && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5430 ekey[rc].ch = KEY_UP;
5431 ekey[rc].rval = 10;
5432 ekey[rc].name = "";
5433 ekey[rc++].label = "";
5435 ekey[rc].ch = KEY_DOWN;
5436 ekey[rc].rval = 11;
5437 ekey[rc].name = "";
5438 ekey[rc++].label = "";
5440 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5441 ekey[rc].ch = KEY_UP;
5442 ekey[rc].rval = 30;
5443 ekey[rc].name = "";
5444 ku = rc;
5445 ekey[rc++].label = "";
5447 ekey[rc].ch = KEY_DOWN;
5448 ekey[rc].rval = 31;
5449 ekey[rc].name = "";
5450 ekey[rc++].label = "";
5453 ekey[rc].ch = -1;
5455 while(!done) {
5457 * Figure out next default value for this context. The idea
5458 * is that in each context the last folder opened is cached.
5459 * It's up to pick it out and display it. This is fine
5460 * and dandy if we've currently got the inbox open, BUT
5461 * if not, make the inbox the default the first time thru.
5463 if(!inbox){
5464 last_folder = ps_global->inbox_name;
5465 inbox = 1; /* pretend we're in inbox from here on out */
5467 else
5468 last_folder = (ps_global->last_unambig_folder[0])
5469 ? ps_global->last_unambig_folder
5470 : ((tc->last_folder[0]) ? tc->last_folder : NULL);
5472 if(last_folder){
5473 unsigned char *fname = folder_name_decoded((unsigned char *)last_folder);
5474 snprintf(expanded, sizeof(expanded), " [%.*s]", sizeof(expanded)-5,
5475 fname ? (char *) fname : last_folder);
5476 if(fname) fs_give((void **)&fname);
5478 else
5479 *expanded = '\0';
5481 expanded[sizeof(expanded)-1] = '\0';
5483 /* only show collection number if more than one available */
5484 if(ps_global->context_list->next)
5485 snprintf(prompt, sizeof(prompt), "GOTO %s in <%s> %.*s%s: ",
5486 NEWS_TEST(tc) ? "news group" : "folder",
5487 tc->nickname, sizeof(prompt)-50, expanded,
5488 *expanded ? " " : "");
5489 else
5490 snprintf(prompt, sizeof(prompt), "GOTO folder %.*s%s: ", sizeof(prompt)-20, expanded,
5491 *expanded ? " " : "");
5493 prompt[sizeof(prompt)-1] = '\0';
5495 if(utf8_width(prompt) > MAXPROMPT){
5496 if(ps_global->context_list->next)
5497 snprintf(prompt, sizeof(prompt), "GOTO <%s> %.*s%s: ",
5498 tc->nickname, sizeof(prompt)-50, expanded,
5499 *expanded ? " " : "");
5500 else
5501 snprintf(prompt, sizeof(prompt), "GOTO %.*s%s: ", sizeof(prompt)-20, expanded,
5502 *expanded ? " " : "");
5504 prompt[sizeof(prompt)-1] = '\0';
5506 if(utf8_width(prompt) > MAXPROMPT){
5507 if(ps_global->context_list->next)
5508 snprintf(prompt, sizeof(prompt), "<%s> %.*s%s: ",
5509 tc->nickname, sizeof(prompt)-50, expanded,
5510 *expanded ? " " : "");
5511 else
5512 snprintf(prompt, sizeof(prompt), "%.*s%s: ", sizeof(prompt)-20, expanded,
5513 *expanded ? " " : "");
5515 prompt[sizeof(prompt)-1] = '\0';
5519 if(ku >= 0){
5520 if(items_in_hist(history) > 1){
5521 ekey[ku].name = HISTORY_UP_KEYNAME;
5522 ekey[ku].label = HISTORY_KEYLABEL;
5523 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
5524 ekey[ku+1].label = HISTORY_KEYLABEL;
5526 else{
5527 ekey[ku].name = "";
5528 ekey[ku].label = "";
5529 ekey[ku+1].name = "";
5530 ekey[ku+1].label = "";
5534 /* is there any other way to do this? The point is that we
5535 * are trying to hide mutf7 from the user, and use the utf8
5536 * equivalent. So we create a variable f to take place of
5537 * newfolder, including content and size. f2 is copy of f1
5538 * that has to freed. Sigh!
5540 f3 = cpystr(newfolder);
5541 f1 = fs_get(sizeof(newfolder));
5542 f2 = folder_name_decoded(f3);
5543 if(f3) fs_give((void **)&f3);
5544 strncpy(f1, f2, sizeof(newfolder));
5545 f1[sizeof(newfolder)-1] = '\0';
5546 if(f2) fs_give((void **)&f2);
5548 flags = OE_APPEND_CURRENT;
5549 rc = optionally_enter(f1, qline, 0, sizeof(newfolder),
5550 prompt, ekey, help, &flags);
5552 f2 = folder_name_encoded(f1);
5553 strncpy(newfolder, f2, sizeof(newfolder));
5554 if(f1) fs_give((void **)&f1);
5555 if(f2) fs_give((void **)&f2);
5557 ps_global->mangled_footer = 1;
5559 switch(rc){
5560 case -1 : /* o_e says error! */
5561 q_status_message(SM_ORDER | SM_DING, 3, 3,
5562 _("Error reading folder name"));
5563 return(NULL);
5565 case 0 : /* o_e says normal entry */
5566 removing_trailing_white_space(newfolder);
5567 removing_leading_white_space(newfolder);
5569 if(*newfolder){
5570 char *name, *fullname = NULL;
5571 int exists, breakout = 0;
5573 save_hist(history, newfolder, 0, tc);
5575 if(!(name = folder_is_nick(newfolder, FOLDERS(tc),
5576 FN_WHOLE_NAME)))
5577 name = newfolder;
5579 if(update_folder_spec(expanded, sizeof(expanded), name)){
5580 strncpy(name = newfolder, expanded, sizeof(newfolder));
5581 newfolder[sizeof(newfolder)-1] = '\0';
5584 exists = folder_name_exists(tc, name, &fullname);
5586 if(fullname){
5587 strncpy(name = newfolder, fullname, sizeof(newfolder));
5588 newfolder[sizeof(newfolder)-1] = '\0';
5589 fs_give((void **) &fullname);
5590 breakout = TRUE;
5594 * if we know the things a folder, open it.
5595 * else if we know its a directory, visit it.
5596 * else we're not sure (it either doesn't really
5597 * exist or its unLISTable) so try opening it anyway
5599 if(exists & FEX_ISFILE){
5600 done++;
5601 break;
5603 else if((exists & FEX_ISDIR)){
5604 if(breakout){
5605 CONTEXT_S *fake_context;
5606 char tmp[MAILTMPLEN];
5607 size_t l;
5609 strncpy(tmp, name, sizeof(tmp));
5610 tmp[sizeof(tmp)-2-1] = '\0';
5611 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
5612 if(l < sizeof(tmp)){
5613 tmp[l] = tc->dir->delim;
5614 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
5617 else
5618 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
5620 tmp[sizeof(tmp)-1] = '\0';
5622 fake_context = new_context(tmp, 0);
5623 newfolder[0] = '\0';
5624 done = display_folder_list(&fake_context, newfolder,
5625 1, folders_for_goto);
5626 free_context(&fake_context);
5627 break;
5629 else if(!(tc->use & CNTXT_INCMNG)){
5630 done = display_folder_list(&tc, newfolder,
5631 1, folders_for_goto);
5632 break;
5635 else if((exists & FEX_ERROR)){
5636 q_status_message1(SM_ORDER, 0, 3,
5637 _("Problem accessing folder \"%s\""),
5638 newfolder);
5639 return(NULL);
5641 else{
5642 done++;
5643 break;
5646 if(exists == FEX_ERROR)
5647 q_status_message1(SM_ORDER, 0, 3,
5648 _("Problem accessing folder \"%s\""),
5649 newfolder);
5650 else if(tc->use & CNTXT_INCMNG)
5651 q_status_message1(SM_ORDER, 0, 3,
5652 _("Can't find Incoming Folder: %s"),
5653 newfolder);
5654 else if(context_isambig(newfolder))
5655 q_status_message2(SM_ORDER, 0, 3,
5656 _("Can't find folder \"%s\" in %s"),
5657 newfolder, (void *) tc->nickname);
5658 else
5659 q_status_message1(SM_ORDER, 0, 3,
5660 _("Can't find folder \"%s\""),
5661 newfolder);
5663 return(NULL);
5665 else if(last_folder){
5666 if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX
5667 && !strucmp(last_folder, ps_global->inbox_name)
5668 && tc == ((ps_global->context_list->use & CNTXT_INCMNG)
5669 ? ps_global->context_list->next : ps_global->context_list)){
5670 if(notrealinbox)
5671 (*notrealinbox) = 0;
5673 tc = ps_global->context_list;
5676 strncpy(newfolder, last_folder, sizeof(newfolder));
5677 newfolder[sizeof(newfolder)-1] = '\0';
5678 save_hist(history, newfolder, 0, tc);
5679 done++;
5680 break;
5682 /* fall thru like they cancelled */
5684 case 1 : /* o_e says user cancel */
5685 cmd_cancelled("Open folder");
5686 return(NULL);
5688 case 2 : /* o_e says user wants list */
5689 r = display_folder_list(&tc, newfolder, 0, folders_for_goto);
5690 if(r)
5691 done++;
5693 break;
5695 case 3 : /* o_e says user wants help */
5696 help = help == NO_HELP ? h_oe_broach : NO_HELP;
5697 break;
5699 case 4 : /* redraw */
5700 break;
5702 case 10 : /* Previous collection */
5703 tc2 = ps_global->context_list;
5704 while(tc2->next && tc2->next != tc)
5705 tc2 = tc2->next;
5707 tc = tc2;
5708 break;
5710 case 11 : /* Next collection */
5711 tc = (tc->next) ? tc->next : ps_global->context_list;
5712 break;
5714 case 12 : /* file name completion */
5715 if(!folder_complete(tc, newfolder, sizeof(newfolder), &n)){
5716 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
5717 r = display_folder_list(&tc, newfolder, 1,folders_for_goto);
5718 if(r)
5719 done++; /* bingo! */
5720 else
5721 rc = 0; /* burn last_rc */
5723 else
5724 Writechar(BELL, 0);
5727 break;
5729 case 14 : /* file name completion */
5730 r = display_folder_list(&tc, newfolder, 2, folders_for_goto);
5731 if(r)
5732 done++; /* bingo! */
5733 else
5734 rc = 0; /* burn last_rc */
5736 break;
5738 case 17 : /* GoTo INBOX */
5739 done++;
5740 strncpy(newfolder, ps_global->inbox_name, sizeof(newfolder)-1);
5741 newfolder[sizeof(newfolder)-1] = '\0';
5742 if(notrealinbox)
5743 (*notrealinbox) = 0;
5745 tc = ps_global->context_list;
5746 save_hist(history, newfolder, 0, tc);
5748 break;
5750 case 30 :
5751 if((p = get_prev_hist(history, newfolder, 0, tc)) != NULL){
5752 strncpy(newfolder, p, sizeof(newfolder));
5753 newfolder[sizeof(newfolder)-1] = '\0';
5754 if(history->hist[history->curindex])
5755 tc = history->hist[history->curindex]->cntxt;
5757 else
5758 Writechar(BELL, 0);
5760 break;
5762 case 31 :
5763 if((p = get_next_hist(history, newfolder, 0, tc)) != NULL){
5764 strncpy(newfolder, p, sizeof(newfolder));
5765 newfolder[sizeof(newfolder)-1] = '\0';
5766 if(history->hist[history->curindex])
5767 tc = history->hist[history->curindex]->cntxt;
5769 else
5770 Writechar(BELL, 0);
5772 break;
5774 default :
5775 panic("Unhandled case");
5776 break;
5779 last_rc = rc;
5782 dprint((2, "broach folder, name entered \"%s\"\n",
5783 newfolder ? newfolder : "?"));
5785 /*-- Just check that we can expand this. It gets done for real later --*/
5786 strncpy(expanded, newfolder, sizeof(expanded));
5787 expanded[sizeof(expanded)-1] = '\0';
5789 if(!expand_foldername(expanded, sizeof(expanded))) {
5790 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
5791 expanded ? expanded : "?"));
5792 return(NULL);
5795 *context = tc;
5796 return(newfolder);
5800 /*----------------------------------------------------------------------
5801 Check to see if user wants to reopen dead stream.
5803 Args: ps --
5804 reopenp --
5806 Result: 1 if the folder was successfully updatedn
5807 0 if not necessary
5809 ----*/
5811 ask_mailbox_reopen(struct pine *ps, int *reopenp)
5813 if(((ps->mail_stream->dtb
5814 && ((ps->mail_stream->dtb->flags & DR_NONEWMAIL)
5815 || (ps->mail_stream->rdonly
5816 && ps->mail_stream->dtb->flags & DR_NONEWMAILRONLY)))
5817 && (ps->reopen_rule == REOPEN_ASK_ASK_Y
5818 || ps->reopen_rule == REOPEN_ASK_ASK_N
5819 || ps->reopen_rule == REOPEN_ASK_NO_Y
5820 || ps->reopen_rule == REOPEN_ASK_NO_N))
5821 || ((ps->mail_stream->dtb
5822 && ps->mail_stream->rdonly
5823 && !(ps->mail_stream->dtb->flags & DR_LOCAL))
5824 && (ps->reopen_rule == REOPEN_YES_ASK_Y
5825 || ps->reopen_rule == REOPEN_YES_ASK_N
5826 || ps->reopen_rule == REOPEN_ASK_ASK_Y
5827 || ps->reopen_rule == REOPEN_ASK_ASK_N))){
5828 int deefault;
5830 switch(ps->reopen_rule){
5831 case REOPEN_YES_ASK_Y:
5832 case REOPEN_ASK_ASK_Y:
5833 case REOPEN_ASK_NO_Y:
5834 deefault = 'y';
5835 break;
5837 default:
5838 deefault = 'n';
5839 break;
5842 switch(want_to("Re-open folder to check for new messages", deefault,
5843 'x', h_reopen_folder, WT_NORM)){
5844 case 'y':
5845 (*reopenp)++;
5846 break;
5848 case 'x':
5849 return(-1);
5853 return(0);
5858 /*----------------------------------------------------------------------
5859 Check to see if user input is in form of old c-client mailbox speck
5861 Args: old --
5862 new --
5864 Result: 1 if the folder was successfully updatedn
5865 0 if not necessary
5867 ----*/
5869 update_folder_spec(char *new, size_t newlen, char *old)
5871 char *p, *orignew;
5872 int nntp = 0;
5874 orignew = new;
5875 if(*(p = old) == '*') /* old form? */
5876 old++;
5878 if(*old == '{') /* copy host spec */
5880 switch(*new = *old++){
5881 case '\0' :
5882 return(FALSE);
5884 case '/' :
5885 if(!struncmp(old, "nntp", 4))
5886 nntp++;
5888 break;
5890 default :
5891 break;
5893 while(*new++ != '}' && (new-orignew) < newlen-1);
5895 if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){
5897 * OK, some heuristics here. If it looks like a newsgroup
5898 * then we plunk it into the #news namespace else we
5899 * assume that they're trying to get at a #public folder...
5901 for(p = old;
5902 *p && (isalnum((unsigned char) *p) || strindex(".-", *p));
5903 p++)
5906 sstrncpy(&new, (*p && !nntp) ? "#public/" : "#news.", newlen-(new-orignew));
5907 strncpy(new, old, newlen-(new-orignew));
5908 return(TRUE);
5911 orignew[newlen-1] = '\0';
5913 return(FALSE);
5917 /*----------------------------------------------------------------------
5918 Open the requested folder in the requested context
5920 Args: state -- usual pine state struct
5921 newfolder -- folder to open
5922 new_context -- folder context might live in
5923 stream -- candidate for recycling
5925 Result: New folder open or not (if error), and we're set to
5926 enter the index screen.
5927 ----*/
5928 void
5929 visit_folder(struct pine *state, char *newfolder, CONTEXT_S *new_context,
5930 MAILSTREAM *stream, long unsigned int flags)
5932 dprint((9, "visit_folder(%s, %s)\n",
5933 newfolder ? newfolder : "?",
5934 (new_context && new_context->context)
5935 ? new_context->context : "(NULL)"));
5937 if(ps_global && ps_global->ttyo){
5938 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
5939 ps_global->mangled_footer = 1;
5942 if(do_broach_folder(newfolder, new_context, stream ? &stream : NULL,
5943 flags) >= 0
5944 || !sp_flagged(state->mail_stream, SP_LOCKED))
5945 state->next_screen = mail_index_screen;
5946 else
5947 state->next_screen = folder_screen;
5951 /*----------------------------------------------------------------------
5952 Move read messages from folder if listed in archive
5954 Args:
5956 ----*/
5958 read_msg_prompt(long int n, char *f)
5960 char buf[MAX_SCREEN_COLS+1];
5962 snprintf(buf, sizeof(buf), "Save the %ld read message%s in \"%s\"", n, plural(n), f);
5963 buf[sizeof(buf)-1] = '\0';
5964 return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y');
5968 /*----------------------------------------------------------------------
5969 Print current message[s] or folder index
5971 Args: state -- pointer to struct holding a bunch of pine state
5972 msgmap -- table mapping msg nums to c-client sequence nums
5973 aopt -- aggregate options
5974 in_index -- boolean indicating we're called from Index Screen
5976 Filters the original header and sends stuff to printer
5977 ---*/
5979 cmd_print(struct pine *state, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
5981 char prompt[250];
5982 long i, msgs, rawno;
5983 int next = 0, do_index = 0, rv = 0;
5984 ENVELOPE *e;
5985 BODY *b;
5986 MESSAGECACHE *mc;
5988 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
5989 return rv;
5991 msgs = mn_total_cur(msgmap);
5993 if((in_index != View) && F_ON(F_PRINT_INDEX, state)){
5994 char m[10];
5995 int ans;
5996 static ESCKEY_S prt_opts[] = {
5997 {'i', 'i', "I", N_("Index")},
5998 {'m', 'm', "M", NULL},
5999 {-1, 0, NULL, NULL}};
6001 if(in_index == ThrdIndx){
6002 /* TRANSLATORS: This is a question, Print Index ? */
6003 if(want_to(_("Print Index"), 'y', 'x', NO_HELP, WT_NORM) == 'y')
6004 ans = 'i';
6005 else
6006 ans = 'x';
6008 else{
6009 snprintf(m, sizeof(m), "Message%s", (msgs>1L) ? "s" : "");
6010 m[sizeof(m)-1] = '\0';
6011 prt_opts[1].label = m;
6012 snprintf(prompt, sizeof(prompt), "Print %sFolder Index or %s %s? ",
6013 (aopt & MCMD_AGG_2) ? "thread " : MCMD_ISAGG(aopt) ? "selected " : "",
6014 (aopt & MCMD_AGG_2) ? "thread" : MCMD_ISAGG(aopt) ? "selected" : "current", m);
6015 prompt[sizeof(prompt)-1] = '\0';
6017 ans = radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x',
6018 NO_HELP, RB_NORM|RB_SEQ_SENSITIVE);
6021 switch(ans){
6022 case 'x' :
6023 cmd_cancelled("Print");
6024 if(MCMD_ISAGG(aopt))
6025 restore_selected(msgmap);
6027 return rv;
6029 case 'i':
6030 do_index = 1;
6031 break;
6033 default :
6034 case 'm':
6035 break;
6039 if(do_index)
6040 snprintf(prompt, sizeof(prompt), "%sFolder Index",
6041 (aopt & MCMD_AGG_2) ? "Thread " : MCMD_ISAGG(aopt) ? "Selected " : "");
6042 else if(msgs > 1L)
6043 snprintf(prompt, sizeof(prompt), "%s messages", long2string(msgs));
6044 else
6045 snprintf(prompt, sizeof(prompt), "Message %s", long2string(mn_get_cur(msgmap)));
6047 prompt[sizeof(prompt)-1] = '\0';
6049 if(open_printer(prompt) < 0){
6050 if(MCMD_ISAGG(aopt))
6051 restore_selected(msgmap);
6053 return rv;
6056 if(do_index){
6057 TITLE_S *tc;
6059 tc = format_titlebar();
6061 /* Print titlebar... */
6062 print_text1("%s\n\n", tc ? tc->titlebar_line : "");
6063 /* then all the index members... */
6064 if(!print_index(state, msgmap, MCMD_ISAGG(aopt)))
6065 q_status_message(SM_ORDER | SM_DING, 3, 3,
6066 _("Error printing folder index"));
6067 else
6068 rv++;
6070 else{
6071 rv++;
6072 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){
6073 if(next && F_ON(F_AGG_PRINT_FF, state))
6074 if(!print_char(FORMFEED)){
6075 rv = 0;
6076 break;
6079 if(!(state->mail_stream
6080 && (rawno = mn_m2raw(msgmap, i)) > 0L
6081 && rawno <= state->mail_stream->nmsgs
6082 && (mc = mail_elt(state->mail_stream, rawno))
6083 && mc->valid))
6084 mc = NULL;
6086 if(!(e=pine_mail_fetchstructure(state->mail_stream,
6087 mn_m2raw(msgmap,i),
6088 &b))
6089 || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global)
6090 && !bezerk_delimiter(e, mc, print_char, next))
6091 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
6092 e, b, NULL, FM_NEW_MESS | FM_NOINDENT,
6093 print_char)){
6094 q_status_message(SM_ORDER | SM_DING, 3, 3,
6095 _("Error printing message"));
6096 rv = 0;
6097 break;
6102 close_printer();
6104 if(MCMD_ISAGG(aopt))
6105 restore_selected(msgmap);
6107 return rv;
6111 /*----------------------------------------------------------------------
6112 Pipe message text
6114 Args: state -- various pine state bits
6115 msgmap -- Message number mapping table
6116 aopt -- option flags
6118 Filters the original header and sends stuff to specified command
6119 ---*/
6121 cmd_pipe(struct pine *state, MSGNO_S *msgmap, int aopt)
6123 ENVELOPE *e;
6124 MESSAGECACHE *mc;
6125 BODY *b;
6126 PIPE_S *syspipe;
6127 char *resultfilename = NULL, prompt[80], *p;
6128 int done = 0, rv = 0;
6129 gf_io_t pc;
6130 int fourlabel = -1, j = 0, next = 0, ku;
6131 int pipe_rv; /* rv of proc to separate from close_system_pipe rv */
6132 long i, rawno;
6133 unsigned flagsforhist = 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6134 static HISTORY_S *history = NULL;
6135 int capture = 1, raw = 0, delimit = 0, newpipe = 0;
6136 char pipe_command[MAXPATH];
6137 ESCKEY_S pipe_opt[8];
6139 if(ps_global->restricted){
6140 q_status_message(SM_ORDER | SM_DING, 0, 4,
6141 "Alpine demo can't pipe messages");
6142 return rv;
6144 else if(!any_messages(msgmap, NULL, "to Pipe"))
6145 return rv;
6147 pipe_command[0] = '\0';
6148 init_hist(&history, HISTSIZE);
6149 flagsforhist = (raw ? 0x8 : 0) +
6150 (delimit ? 0x4 : 0) +
6151 (newpipe ? 0x2 : 0) +
6152 (capture ? 0x1 : 0);
6153 if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){
6154 strncpy(pipe_command, p, sizeof(pipe_command));
6155 pipe_command[sizeof(pipe_command)-1] = '\0';
6156 if(history->hist[history->curindex]){
6157 flagsforhist = history->hist[history->curindex]->flags;
6158 raw = (flagsforhist & 0x8) ? 1 : 0;
6159 delimit = (flagsforhist & 0x4) ? 1 : 0;
6160 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6161 capture = (flagsforhist & 0x1) ? 1 : 0;
6165 pipe_opt[j].ch = 0;
6166 pipe_opt[j].rval = 0;
6167 pipe_opt[j].name = "";
6168 pipe_opt[j++].label = "";
6170 pipe_opt[j].ch = ctrl('W');
6171 pipe_opt[j].rval = 10;
6172 pipe_opt[j].name = "^W";
6173 pipe_opt[j++].label = NULL;
6175 pipe_opt[j].ch = ctrl('Y');
6176 pipe_opt[j].rval = 11;
6177 pipe_opt[j].name = "^Y";
6178 pipe_opt[j++].label = NULL;
6180 pipe_opt[j].ch = ctrl('R');
6181 pipe_opt[j].rval = 12;
6182 pipe_opt[j].name = "^R";
6183 pipe_opt[j++].label = NULL;
6185 if(MCMD_ISAGG(aopt)){
6186 if(!pseudo_selected(state->mail_stream, msgmap))
6187 return rv;
6188 else{
6189 fourlabel = j;
6190 pipe_opt[j].ch = ctrl('T');
6191 pipe_opt[j].rval = 13;
6192 pipe_opt[j].name = "^T";
6193 pipe_opt[j++].label = NULL;
6197 pipe_opt[j].ch = KEY_UP;
6198 pipe_opt[j].rval = 30;
6199 pipe_opt[j].name = "";
6200 ku = j;
6201 pipe_opt[j++].label = "";
6203 pipe_opt[j].ch = KEY_DOWN;
6204 pipe_opt[j].rval = 31;
6205 pipe_opt[j].name = "";
6206 pipe_opt[j++].label = "";
6208 pipe_opt[j].ch = -1;
6210 while (!done) {
6211 int flags;
6213 snprintf(prompt, sizeof(prompt), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6214 raw ? "RAW " : "",
6215 MCMD_ISAGG(aopt) ? "s" : " ",
6216 MCMD_ISAGG(aopt) ? "" : comatose(mn_get_cur(msgmap)),
6217 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? "(" : "",
6218 capture ? "" : "uncaptured",
6219 (!capture && delimit) ? "," : "",
6220 delimit ? "delimited" : "",
6221 ((!capture || delimit) && newpipe && MCMD_ISAGG(aopt)) ? "," : "",
6222 (newpipe && MCMD_ISAGG(aopt)) ? "new pipe" : "",
6223 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? ") " : "");
6224 prompt[sizeof(prompt)-1] = '\0';
6225 pipe_opt[1].label = raw ? N_("Shown Text") : N_("Raw Text");
6226 pipe_opt[2].label = capture ? N_("Free Output") : N_("Capture Output");
6227 pipe_opt[3].label = delimit ? N_("No Delimiter") : N_("With Delimiter");
6228 if(fourlabel > 0)
6229 pipe_opt[fourlabel].label = newpipe ? N_("To Same Pipe") : N_("To Individual Pipes");
6233 * 2 is really 1 because there will be one real entry and
6234 * one entry of "" because of the get_prev_hist above.
6236 if(items_in_hist(history) > 2){
6237 pipe_opt[ku].name = HISTORY_UP_KEYNAME;
6238 pipe_opt[ku].label = HISTORY_KEYLABEL;
6239 pipe_opt[ku+1].name = HISTORY_DOWN_KEYNAME;
6240 pipe_opt[ku+1].label = HISTORY_KEYLABEL;
6242 else{
6243 pipe_opt[ku].name = "";
6244 pipe_opt[ku].label = "";
6245 pipe_opt[ku+1].name = "";
6246 pipe_opt[ku+1].label = "";
6249 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
6250 switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0,
6251 sizeof(pipe_command), prompt,
6252 pipe_opt, NO_HELP, &flags)){
6253 case -1 :
6254 q_status_message(SM_ORDER | SM_DING, 3, 4,
6255 _("Internal problem encountered"));
6256 done++;
6257 break;
6259 case 10 : /* flip raw bit */
6260 raw = !raw;
6261 break;
6263 case 11 : /* flip capture bit */
6264 capture = !capture;
6265 break;
6267 case 12 : /* flip delimit bit */
6268 delimit = !delimit;
6269 break;
6271 case 13 : /* flip newpipe bit */
6272 newpipe = !newpipe;
6273 break;
6275 case 30 :
6276 flagsforhist = (raw ? 0x8 : 0) +
6277 (delimit ? 0x4 : 0) +
6278 (newpipe ? 0x2 : 0) +
6279 (capture ? 0x1 : 0);
6280 if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6281 strncpy(pipe_command, p, sizeof(pipe_command));
6282 pipe_command[sizeof(pipe_command)-1] = '\0';
6283 if(history->hist[history->curindex]){
6284 flagsforhist = history->hist[history->curindex]->flags;
6285 raw = (flagsforhist & 0x8) ? 1 : 0;
6286 delimit = (flagsforhist & 0x4) ? 1 : 0;
6287 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6288 capture = (flagsforhist & 0x1) ? 1 : 0;
6291 else
6292 Writechar(BELL, 0);
6294 break;
6296 case 31 :
6297 flagsforhist = (raw ? 0x8 : 0) +
6298 (delimit ? 0x4 : 0) +
6299 (newpipe ? 0x2 : 0) +
6300 (capture ? 0x1 : 0);
6301 if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6302 strncpy(pipe_command, p, sizeof(pipe_command));
6303 pipe_command[sizeof(pipe_command)-1] = '\0';
6304 if(history->hist[history->curindex]){
6305 flagsforhist = history->hist[history->curindex]->flags;
6306 raw = (flagsforhist & 0x8) ? 1 : 0;
6307 delimit = (flagsforhist & 0x4) ? 1 : 0;
6308 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6309 capture = (flagsforhist & 0x1) ? 1 : 0;
6312 else
6313 Writechar(BELL, 0);
6315 break;
6317 case 0 :
6318 if(pipe_command[0]){
6320 flagsforhist = (raw ? 0x8 : 0) +
6321 (delimit ? 0x4 : 0) +
6322 (newpipe ? 0x2 : 0) +
6323 (capture ? 0x1 : 0);
6324 save_hist(history, pipe_command, flagsforhist, NULL);
6326 flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
6327 flags |= (raw ? PIPE_RAW : 0);
6328 if(!capture){
6329 #ifndef _WINDOWS
6330 ClearScreen();
6331 fflush(stdout);
6332 clear_cursor_pos();
6333 ps_global->mangled_screen = 1;
6334 ps_global->in_init_seq = 1;
6335 #endif
6336 flags |= PIPE_RESET;
6339 if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command,
6340 (flags & PIPE_RESET)
6341 ? NULL
6342 : &resultfilename,
6343 flags, &pc)))
6344 done++;
6346 for(i = mn_first_cur(msgmap);
6347 i > 0L && !done;
6348 i = mn_next_cur(msgmap)){
6349 e = pine_mail_fetchstructure(ps_global->mail_stream,
6350 mn_m2raw(msgmap, i), &b);
6351 if(!(state->mail_stream
6352 && (rawno = mn_m2raw(msgmap, i)) > 0L
6353 && rawno <= state->mail_stream->nmsgs
6354 && (mc = mail_elt(state->mail_stream, rawno))
6355 && mc->valid))
6356 mc = NULL;
6358 if((newpipe
6359 && !(syspipe = cmd_pipe_open(pipe_command,
6360 (flags & PIPE_RESET)
6361 ? NULL
6362 : &resultfilename,
6363 flags, &pc)))
6364 || (delimit && !bezerk_delimiter(e, mc, pc, next++)))
6365 done++;
6367 if(!done){
6368 if(raw){
6369 char *pipe_err;
6371 prime_raw_pipe_getc(ps_global->mail_stream,
6372 mn_m2raw(msgmap, i), -1L, 0L);
6373 gf_filter_init();
6374 gf_link_filter(gf_nvtnl_local, NULL);
6375 if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){
6376 q_status_message1(SM_ORDER|SM_DING,
6377 3, 3,
6378 _("Internal Error: %s"),
6379 pipe_err);
6380 done++;
6383 else if(!format_message(mn_m2raw(msgmap, i), e, b,
6384 NULL, FM_NEW_MESS | FM_NOWRAP, pc))
6385 done++;
6388 if(newpipe)
6389 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6390 done++;
6393 if(!capture)
6394 ps_global->in_init_seq = 0;
6396 if(!newpipe)
6397 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6398 done++;
6399 if(done) /* say we had a problem */
6400 q_status_message(SM_ORDER | SM_DING, 3, 3,
6401 _("Error piping message"));
6402 else if(resultfilename){
6403 rv++;
6404 /* only display if no error */
6405 display_output_file(resultfilename, "PIPE MESSAGE",
6406 NULL, DOF_EMPTY);
6407 fs_give((void **)&resultfilename);
6409 else{
6410 rv++;
6411 q_status_message(SM_ORDER, 0, 2, _("Pipe command completed"));
6414 done++;
6415 break;
6417 /* else fall thru as if cancelled */
6419 case 1 :
6420 cmd_cancelled("Pipe command");
6421 done++;
6422 break;
6424 case 3 :
6425 helper(h_common_pipe, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE);
6426 ps_global->mangled_screen = 1;
6427 break;
6429 case 2 : /* no place to escape to */
6430 case 4 : /* can't suspend */
6431 default :
6432 break;
6436 ps_global->mangled_footer = 1;
6437 if(MCMD_ISAGG(aopt))
6438 restore_selected(msgmap);
6440 return rv;
6444 /*----------------------------------------------------------------------
6445 Screen to offer list management commands contained in message
6447 Args: state -- pointer to struct holding a bunch of pine state
6448 msgmap -- table mapping msg nums to c-client sequence nums
6449 aopt -- aggregate options
6451 Result:
6453 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6454 ----*/
6455 void
6456 rfc2369_display(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno)
6458 int winner = 0;
6459 char *h, *hdrs[MLCMD_COUNT + 1];
6460 long index_no = mn_raw2m(msgmap, msgno);
6461 RFC2369_S data[MLCMD_COUNT];
6463 /* for each header field */
6464 if((h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))) != NULL){
6465 memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT);
6466 if(rfc2369_parse_fields(h, &data[0])){
6467 STORE_S *explain;
6469 if((explain = list_mgmt_text(data, index_no)) != NULL){
6470 list_mgmt_screen(explain);
6471 ps_global->mangled_screen = 1;
6472 so_give(&explain);
6473 winner++;
6477 fs_give((void **) &h);
6480 if(!winner)
6481 q_status_message1(SM_ORDER, 0, 3,
6482 "Message %s contains no list management information",
6483 comatose(index_no));
6487 STORE_S *
6488 list_mgmt_text(RFC2369_S *data, long int msgno)
6490 STORE_S *store;
6491 int i, j, n, fields = 0;
6492 static char *rfc2369_intro1 =
6493 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6494 static char *rfc2369_intro2[] = {
6495 N_(" has information associated with it "),
6496 N_("that explains how to participate in an email list. An "),
6497 N_("email list is represented by a single email address that "),
6498 N_("users sharing a common interest can send messages to (known "),
6499 N_("as posting) which are then redistributed to all members "),
6500 N_("of the list (sometimes after review by a moderator)."),
6501 N_("<P>List participation commands in this message include:"),
6502 NULL
6505 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6507 /* Insert introductory text */
6508 so_puts(store, rfc2369_intro1);
6510 so_puts(store, comatose(msgno));
6512 for(i = 0; rfc2369_intro2[i]; i++)
6513 so_puts(store, _(rfc2369_intro2[i]));
6515 so_puts(store, "<P>");
6516 for(i = 0; i < MLCMD_COUNT; i++)
6517 if(data[i].data[0].value
6518 || data[i].data[0].comment
6519 || data[i].data[0].error){
6520 if(!fields++)
6521 so_puts(store, "<UL>");
6523 so_puts(store, "<LI>");
6524 so_puts(store,
6525 (n = (data[i].data[1].value || data[i].data[1].comment))
6526 ? "Methods to "
6527 : "A method to ");
6529 so_puts(store, data[i].field.description);
6530 so_puts(store, ". ");
6532 if(n)
6533 so_puts(store, "<OL>");
6535 for(j = 0;
6536 j < MLCMD_MAXDATA
6537 && (data[i].data[j].comment
6538 || data[i].data[j].value
6539 || data[i].data[j].error);
6540 j++){
6542 so_puts(store, n ? "<P><LI>" : "<P>");
6544 if(data[i].data[j].comment){
6545 so_puts(store,
6546 _("With the provided comment:<P><BLOCKQUOTE>"));
6547 so_puts(store, data[i].data[j].comment);
6548 so_puts(store, "</BLOCKQUOTE><P>");
6551 if(data[i].data[j].value){
6552 if(i == MLCMD_POST
6553 && !strucmp(data[i].data[j].value, "NO")){
6554 so_puts(store,
6555 _("Posting is <EM>not</EM> allowed on this list"));
6557 else{
6558 so_puts(store, "Select <A HREF=\"");
6559 so_puts(store, data[i].data[j].value);
6560 so_puts(store, "\">HERE</A> to ");
6561 so_puts(store, (data[i].field.action)
6562 ? data[i].field.action
6563 : "try it");
6566 so_puts(store, ".");
6569 if(data[i].data[j].error){
6570 so_puts(store, "<P>Unfortunately, Alpine can not offer");
6571 so_puts(store, " to take direct action based upon it");
6572 so_puts(store, " because it was improperly formatted.");
6573 so_puts(store, " The unrecognized data associated with");
6574 so_puts(store, " the \"");
6575 so_puts(store, data[i].field.name);
6576 so_puts(store, "\" header field was:<P><BLOCKQUOTE>");
6577 so_puts(store, data[i].data[j].error);
6578 so_puts(store, "</BLOCKQUOTE>");
6581 so_puts(store, "<P>");
6584 if(n)
6585 so_puts(store, "</OL>");
6588 if(fields)
6589 so_puts(store, "</UL>");
6591 so_puts(store, "</BODY></HTML>");
6594 return(store);
6598 void
6599 list_mgmt_screen(STORE_S *html)
6601 int cmd = MC_NONE;
6602 long offset = 0L;
6603 char *error = NULL;
6604 STORE_S *store;
6605 HANDLE_S *handles = NULL;
6606 gf_io_t gc, pc;
6609 so_seek(html, 0L, 0);
6610 gf_set_so_readc(&gc, html);
6612 init_handles(&handles);
6614 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6615 gf_set_so_writec(&pc, store);
6616 gf_filter_init();
6618 gf_link_filter(gf_html2plain,
6619 gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols,
6620 non_messageview_margin(), &handles, NULL, 0));
6622 error = gf_pipe(gc, pc);
6624 gf_clear_so_writec(store);
6626 if(!error){
6627 SCROLL_S sargs;
6629 memset(&sargs, 0, sizeof(SCROLL_S));
6630 sargs.text.text = so_text(store);
6631 sargs.text.src = CharStar;
6632 sargs.text.desc = "list commands";
6633 sargs.text.handles = handles;
6634 if(offset){
6635 sargs.start.on = Offset;
6636 sargs.start.loc.offset = offset;
6639 sargs.bar.title = _("MAIL LIST COMMANDS");
6640 sargs.bar.style = MessageNumber;
6641 sargs.resize_exit = 1;
6642 sargs.help.text = h_special_list_commands;
6643 sargs.help.title = _("HELP FOR LIST COMMANDS");
6644 sargs.keys.menu = &listmgr_keymenu;
6645 setbitmap(sargs.keys.bitmap);
6646 if(!handles){
6647 clrbitn(LM_TRY_KEY, sargs.keys.bitmap);
6648 clrbitn(LM_PREV_KEY, sargs.keys.bitmap);
6649 clrbitn(LM_NEXT_KEY, sargs.keys.bitmap);
6652 cmd = scrolltool(&sargs);
6653 offset = sargs.start.loc.offset;
6656 so_give(&store);
6659 free_handles(&handles);
6660 gf_clear_so_readc(html);
6662 while(cmd == MC_RESIZE);
6666 /*----------------------------------------------------------------------
6667 Prompt the user for the type of select desired
6669 NOTE: any and all functions that successfully exit the second
6670 switch() statement below (currently "select_*() functions"),
6671 *MUST* update the folder's MESSAGECACHE element's "searched"
6672 bits to reflect the search result. Functions using
6673 mail_search() get this for free, the others must update 'em
6674 by hand.
6676 Returns -1 if canceled without changing selection
6677 0 if selection may have changed
6678 ----*/
6680 aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index)
6682 long i, diff, old_tot, msgno, raw;
6683 int q = 0, rv = 0, narrow = 0, hidden, ret = -1;
6684 ESCKEY_S *sel_opts;
6685 MESSAGECACHE *mc;
6686 SEARCHSET *limitsrch = NULL;
6687 PINETHRD_S *thrd;
6688 extern MAILSTREAM *mm_search_stream;
6689 extern long mm_search_count;
6691 hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
6692 mm_search_stream = state->mail_stream;
6693 mm_search_count = 0L;
6695 sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2;
6696 if(THREADING()){
6697 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH;
6699 else{
6700 sel_opts[SEL_OPTS_THREAD].ch = -1;
6703 if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){
6704 if(THRD_INDX()){
6705 i = 0;
6706 thrd = fetch_thread(state->mail_stream,
6707 mn_m2raw(msgmap, mn_get_cur(msgmap)));
6708 /* check if whole thread is selected or not */
6709 if(thrd &&
6710 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT)
6712 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE))
6713 i = 1;
6715 sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd");
6717 else{
6718 i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap),
6719 MN_SLCT);
6720 sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur");
6723 sel_opts += 2; /* disable extra options */
6724 switch(q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6725 RB_NORM)){
6726 case 'f' : /* flip selection */
6727 msgno = 0L;
6728 for(i = 1L; i <= mn_get_total(msgmap); i++){
6729 ret = 0;
6730 q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT);
6731 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q);
6732 if(hidden){
6733 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q);
6734 if(!msgno && q)
6735 mn_reset_cur(msgmap, msgno = i);
6739 return(ret);
6741 case 'n' : /* narrow selection */
6742 narrow++;
6743 case 'b' : /* broaden selection */
6744 q = 0; /* offer criteria prompt */
6745 break;
6747 case 'c' : /* Un/Select Current */
6748 case 'a' : /* Unselect All */
6749 case 'x' : /* cancel */
6750 break;
6752 default :
6753 q_status_message(SM_ORDER | SM_DING, 3, 3,
6754 "Unsupported Select option");
6755 return(ret);
6759 if(!q){
6760 while(1){
6761 q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x',
6762 NO_HELP, RB_NORM|RB_RET_HELP);
6764 if(q == 3){
6765 helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE);
6766 ps_global->mangled_screen = 1;
6768 else
6769 break;
6774 * The purpose of this is to add the appropriate searchset to the
6775 * search so that the search can be limited to only looking at what
6776 * it needs to look at. That is, if we are narrowing then we only need
6777 * to look at messages which are already selected, and if we are
6778 * broadening, then we only need to look at messages which are not
6779 * yet selected. This routine will work whether or not
6780 * limiting_searchset properly limits the search set. In particular,
6781 * the searchset returned by limiting_searchset may include messages
6782 * which really shouldn't be included. We do that because a too-large
6783 * searchset will break some IMAP servers. It is even possible that it
6784 * becomes inefficient to send the whole set. If the select function
6785 * frees limitsrch, it should be sure to set it to NULL so we won't
6786 * try freeing it again here.
6788 limitsrch = limiting_searchset(state->mail_stream, narrow);
6791 * NOTE: See note about MESSAGECACHE "searched" bits above!
6793 switch(q){
6794 case 'x': /* cancel */
6795 cmd_cancelled("Select command");
6796 return(ret);
6798 case 'c' : /* select/unselect current */
6799 (void) select_by_current(state, msgmap, in_index);
6800 ret = 0;
6801 return(ret);
6803 case 'a' : /* select/unselect all */
6804 msgno = any_lflagged(msgmap, MN_SLCT);
6805 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
6806 ret = 0;
6807 agg_select_all(state->mail_stream, msgmap, &diff,
6808 any_lflagged(msgmap, MN_SLCT) <= 0L);
6809 q_status_message4(SM_ORDER,0,2,
6810 "%s%s message%s %sselected",
6811 msgno ? "" : "All ", comatose(diff),
6812 plural(diff), msgno ? "UN" : "");
6813 return(ret);
6815 case 'n' : /* Select by Number */
6816 ret = 0;
6817 if(THRD_INDX())
6818 rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch);
6819 else
6820 rv = select_by_number(state->mail_stream, msgmap, &limitsrch);
6822 break;
6824 case 'd' : /* Select by Date */
6825 ret = 0;
6826 rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap),
6827 &limitsrch);
6828 break;
6830 case 't' : /* Text */
6831 ret = 0;
6832 rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap),
6833 &limitsrch);
6834 break;
6836 case 'z' : /* Size */
6837 ret = 0;
6838 rv = select_by_size(state->mail_stream, &limitsrch);
6839 break;
6841 case 's' : /* Status */
6842 ret = 0;
6843 rv = select_by_status(state->mail_stream, &limitsrch);
6844 break;
6846 case 'k' : /* Keyword */
6847 ret = 0;
6848 rv = select_by_keyword(state->mail_stream, &limitsrch);
6849 break;
6851 case 'r' : /* Rule */
6852 ret = 0;
6853 rv = select_by_rule(state->mail_stream, &limitsrch);
6854 break;
6856 case 'h' : /* Thread */
6857 ret = 0;
6858 rv = select_by_thread(state->mail_stream, msgmap, &limitsrch);
6859 break;
6861 default :
6862 q_status_message(SM_ORDER | SM_DING, 3, 3,
6863 "Unsupported Select option");
6864 return(ret);
6867 if(limitsrch)
6868 mail_free_searchset(&limitsrch);
6870 if(rv) /* bad return value.. */
6871 return(ret); /* error already displayed */
6873 if(narrow) /* make sure something was selected */
6874 for(i = 1L; i <= mn_get_total(msgmap); i++)
6875 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
6876 && raw <= state->mail_stream->nmsgs
6877 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
6878 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
6879 break;
6880 else
6881 mm_search_count--;
6884 diff = 0L;
6885 if(mm_search_count){
6887 * loop thru all the messages, adjusting local flag bits
6888 * based on their "searched" bit...
6890 for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++)
6891 if(narrow){
6892 /* turning OFF selectedness if the "searched" bit isn't lit. */
6893 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
6894 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
6895 && raw <= state->mail_stream->nmsgs
6896 && (mc = mail_elt(state->mail_stream, raw))
6897 && !mc->searched){
6898 diff--;
6899 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
6900 if(hidden)
6901 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
6903 /* adjust current message in case we unselect and hide it */
6904 else if(msgno < mn_get_cur(msgmap)
6905 && (!THRD_INDX()
6906 || !get_lflag(state->mail_stream, msgmap,
6907 i, MN_CHID)))
6908 msgno = i;
6911 else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
6912 && raw <= state->mail_stream->nmsgs
6913 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
6914 /* turn ON selectedness if "searched" bit is lit. */
6915 if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
6916 diff++;
6917 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
6918 if(hidden)
6919 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
6923 /* if we're zoomed and the current message was unselected */
6924 if(narrow && msgno
6925 && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE))
6926 mn_reset_cur(msgmap, msgno);
6929 if(!diff){
6930 if(narrow)
6931 q_status_message4(SM_ORDER, 3, 3,
6932 "%s. %s message%s remain%s selected.",
6933 mm_search_count
6934 ? "No change resulted"
6935 : "No messages in intersection",
6936 comatose(old_tot), plural(old_tot),
6937 (old_tot == 1L) ? "s" : "");
6938 else if(old_tot)
6939 q_status_message(SM_ORDER, 3, 3,
6940 _("No change resulted. Matching messages already selected."));
6941 else
6942 q_status_message1(SM_ORDER | SM_DING, 3, 3,
6943 _("Select failed. No %smessages selected."),
6944 old_tot ? _("additional ") : "");
6946 else if(old_tot){
6947 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
6948 "Select matched %ld message%s. %s %smessage%s %sselected.",
6949 (diff > 0) ? diff : old_tot + diff,
6950 plural((diff > 0) ? diff : old_tot + diff),
6951 comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
6952 (diff > 0) ? "total " : "",
6953 plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
6954 (diff > 0) ? "" : "UN");
6955 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
6956 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
6958 else
6959 q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"),
6960 comatose(diff), plural(diff));
6962 return(ret);
6966 /*----------------------------------------------------------------------
6967 Toggle the state of the current message
6969 Args: state -- pointer pine's state variables
6970 msgmap -- message collection to operate on
6971 in_index -- in the message index view
6972 Returns: TRUE if current marked selected, FALSE otw
6973 ----*/
6975 select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
6977 long cur;
6978 int all_selected = 0;
6979 unsigned long was, tot, rawno;
6980 PINETHRD_S *thrd;
6982 cur = mn_get_cur(msgmap);
6984 if(THRD_INDX()){
6985 thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur));
6986 if(!thrd)
6987 return 0;
6989 was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT);
6990 tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE);
6991 if(was == tot)
6992 all_selected++;
6994 if(all_selected){
6995 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0);
6996 if(any_lflagged(msgmap, MN_HIDE) > 0L){
6997 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1);
6999 * See if there's anything left to zoom on. If so,
7000 * pick an adjacent one for highlighting, else make
7001 * sure nothing is left hidden...
7003 if(any_lflagged(msgmap, MN_SLCT)){
7004 mn_inc_cur(state->mail_stream, msgmap,
7005 (in_index == View && THREADING()
7006 && sp_viewing_a_thread(state->mail_stream))
7007 ? MH_THISTHD
7008 : (in_index == View)
7009 ? MH_ANYTHD : MH_NONE);
7010 if(mn_get_cur(msgmap) == cur)
7011 mn_dec_cur(state->mail_stream, msgmap,
7012 (in_index == View && THREADING()
7013 && sp_viewing_a_thread(state->mail_stream))
7014 ? MH_THISTHD
7015 : (in_index == View)
7016 ? MH_ANYTHD : MH_NONE);
7018 else /* clear all hidden flags */
7019 (void) unzoom_index(state, state->mail_stream, msgmap);
7022 else
7023 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1);
7025 q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected",
7026 comatose(all_selected ? was : tot-was),
7027 plural(all_selected ? was : tot-was),
7028 all_selected ? "UN" : "");
7030 /* collapsed thread */
7031 else if(THREADING()
7032 && ((rawno = mn_m2raw(msgmap, cur)) != 0L)
7033 && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL)
7034 && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){
7036 * This doesn't work quite the same as the colon command works, but
7037 * it is arguably doing the correct thing. The difference is
7038 * that aggregate_select will zoom after selecting back where it
7039 * was called from, but selecting a thread with colon won't zoom.
7040 * Maybe it makes sense to zoom after a select but not after a colon
7041 * command even though they are very similar.
7043 thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state));
7045 else{
7046 if((all_selected =
7047 get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */
7048 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0);
7049 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7050 set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1);
7052 * See if there's anything left to zoom on. If so,
7053 * pick an adjacent one for highlighting, else make
7054 * sure nothing is left hidden...
7056 if(any_lflagged(msgmap, MN_SLCT)){
7057 mn_inc_cur(state->mail_stream, msgmap,
7058 (in_index == View && THREADING()
7059 && sp_viewing_a_thread(state->mail_stream))
7060 ? MH_THISTHD
7061 : (in_index == View)
7062 ? MH_ANYTHD : MH_NONE);
7063 if(mn_get_cur(msgmap) == cur)
7064 mn_dec_cur(state->mail_stream, msgmap,
7065 (in_index == View && THREADING()
7066 && sp_viewing_a_thread(state->mail_stream))
7067 ? MH_THISTHD
7068 : (in_index == View)
7069 ? MH_ANYTHD : MH_NONE);
7071 else /* clear all hidden flags */
7072 (void) unzoom_index(state, state->mail_stream, msgmap);
7075 else
7076 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1);
7078 q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected",
7079 long2string(cur), all_selected ? "UN" : "");
7083 return(!all_selected);
7087 /*----------------------------------------------------------------------
7088 Prompt the user for the command to perform on selected messages
7090 Args: state -- pointer pine's state variables
7091 msgmap -- message collection to operate on
7092 q_line -- line on display to write prompts
7093 Returns: 1 if the selected messages are suitably commanded,
7094 0 if the choice to pick the command was declined
7096 ----*/
7098 apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
7099 UCS preloadkeystroke, int flags, int q_line)
7101 int i = 8, /* number of static entries in sel_opts3 */
7102 rv = 0,
7103 cmd,
7104 we_cancel = 0,
7105 agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG;
7106 char prompt[80];
7109 * To do this "right", we really ought to have access to the keymenu
7110 * here and change the typed command into a real command by running
7111 * it through menu_command. Then the switch below would be against
7112 * results from menu_command. If we did that we'd also pass the
7113 * results of menu_command in as preloadkeystroke instead of passing
7114 * the keystroke itself. But we don't have the keymenu handy,
7115 * so we have to fake it. The only complication that we run into
7116 * is that KEY_DEL is an escape sequence so we change a typed
7117 * KEY_DEL esc seq into the letter D.
7120 if(!preloadkeystroke){
7121 if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
7122 sel_opts3[i].ch = '*';
7123 sel_opts3[i].rval = '*';
7124 sel_opts3[i].name = "*";
7125 sel_opts3[i++].label = N_("Flag");
7128 if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
7129 sel_opts3[i].ch = '|';
7130 sel_opts3[i].rval = '|';
7131 sel_opts3[i].name = "|";
7132 sel_opts3[i++].label = N_("Pipe");
7135 if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */
7136 sel_opts3[i].ch = 'b';
7137 sel_opts3[i].rval = 'b';
7138 sel_opts3[i].name = "B";
7139 sel_opts3[i++].label = N_("Bounce");
7142 if(flags & AC_FROM_THREAD){
7143 if(flags & (AC_COLL | AC_EXPN)){
7144 sel_opts3[i].ch = '/';
7145 sel_opts3[i].rval = '/';
7146 sel_opts3[i].name = "/";
7147 sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse")
7148 : N_("Expand");
7151 sel_opts3[i].ch = ';';
7152 sel_opts3[i].rval = ';';
7153 sel_opts3[i].name = ";";
7154 if(flags & AC_UNSEL)
7155 sel_opts3[i++].label = N_("UnSelect");
7156 else
7157 sel_opts3[i++].label = N_("Select");
7160 if(F_ON(F_ENABLE_PRYNT, state)){ /* this one is invisible */
7161 sel_opts3[i].ch = 'y';
7162 sel_opts3[i].rval = '%';
7163 sel_opts3[i].name = "";
7164 sel_opts3[i++].label = "";
7167 if(!is_imap_stream(stream) || LEVELUIDPLUS(stream)){ /* expunge selected messages */
7168 sel_opts3[i].ch = 'x';
7169 sel_opts3[i].rval = 'x';
7170 sel_opts3[i].name = "X";
7171 sel_opts3[i++].label = N_("Expunge");
7174 sel_opts3[i].ch = KEY_DEL; /* also invisible */
7175 sel_opts3[i].rval = 'd';
7176 sel_opts3[i].name = "";
7177 sel_opts3[i++].label = "";
7179 sel_opts3[i].ch = -1;
7181 snprintf(prompt, sizeof(prompt), "%s command : ",
7182 (flags & AC_FROM_THREAD) ? "THREAD" : "APPLY");
7183 prompt[sizeof(prompt)-1] = '\0';
7184 cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'c', NO_HELP,
7185 RB_SEQ_SENSITIVE);
7186 if(isupper(cmd))
7187 cmd = tolower(cmd);
7189 else{
7190 if(preloadkeystroke == KEY_DEL)
7191 cmd = 'd';
7192 else{
7193 if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke))
7194 cmd = tolower((int) preloadkeystroke);
7195 else
7196 cmd = (int) preloadkeystroke; /* shouldn't happen */
7200 switch(cmd){
7201 case 'd' : /* delete */
7202 we_cancel = busy_cue(NULL, NULL, 1);
7203 rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */
7204 if(we_cancel)
7205 cancel_busy_cue(0);
7206 break;
7208 case 'u' : /* undelete */
7209 we_cancel = busy_cue(NULL, NULL, 1);
7210 rv = cmd_undelete(state, msgmap, agg);
7211 if(we_cancel)
7212 cancel_busy_cue(0);
7213 break;
7215 case 'r' : /* reply */
7216 rv = cmd_reply(state, msgmap, agg);
7217 break;
7219 case 'f' : /* Forward */
7220 rv = cmd_forward(state, msgmap, agg);
7221 break;
7223 case '%' : /* print */
7224 rv = cmd_print(state, msgmap, agg, MsgIndx);
7225 break;
7227 case 't' : /* take address */
7228 rv = cmd_take_addr(state, msgmap, agg);
7229 break;
7231 case 's' : /* save */
7232 rv = cmd_save(state, stream, msgmap, agg, MsgIndx);
7233 break;
7235 case 'e' : /* export */
7236 rv = cmd_export(state, msgmap, q_line, agg);
7237 break;
7239 case '|' : /* pipe */
7240 rv = cmd_pipe(state, msgmap, agg);
7241 break;
7243 case '*' : /* flag */
7244 we_cancel = busy_cue(NULL, NULL, 1);
7245 rv = cmd_flag(state, msgmap, agg);
7246 if(we_cancel)
7247 cancel_busy_cue(0);
7248 break;
7250 case 'b' : /* bounce */
7251 rv = cmd_bounce(state, msgmap, agg);
7252 break;
7254 case '/' :
7255 collapse_or_expand(state, stream, msgmap,
7256 F_ON(F_SLASH_COLL_ENTIRE, ps_global)
7257 ? 0L
7258 : mn_get_cur(msgmap));
7259 break;
7261 case ':' :
7262 select_thread_stmp(state, stream, msgmap);
7263 break;
7265 case 'x' : /* Expunge */
7266 rv = cmd_expunge(state, stream, msgmap, agg);
7267 break;
7269 case 'c' : /* cancel */
7270 cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command"
7271 : "Apply command");
7272 break;
7274 case 'z' : /* default */
7275 q_status_message(SM_INFO, 0, 2,
7276 "Cancelled, there is no default command");
7277 break;
7279 default:
7280 break;
7283 return(rv);
7288 * Select by message number ranges.
7289 * Sets searched bits in mail_elts
7291 * Args limitsrch -- limit search to this searchset
7293 * Returns 0 on success.
7296 select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
7298 int r, end;
7299 long n1, n2, raw;
7300 char number1[16], number2[16], numbers[80], *p, *t;
7301 HelpType help;
7302 MESSAGECACHE *mc;
7304 numbers[0] = '\0';
7305 ps_global->mangled_footer = 1;
7306 help = NO_HELP;
7307 while(1){
7308 int flags = OE_APPEND_CURRENT;
7310 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7311 sizeof(numbers), _(select_num), NULL, help, &flags);
7312 if(r == 4)
7313 continue;
7315 if(r == 3){
7316 help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
7317 continue;
7320 for(t = p = numbers; *p ; p++) /* strip whitespace */
7321 if(!isspace((unsigned char)*p))
7322 *t++ = *p;
7324 *t = '\0';
7326 if(r == 1 || numbers[0] == '\0'){
7327 cmd_cancelled("Selection by number");
7328 return(1);
7330 else
7331 break;
7334 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7335 if((mc = mail_elt(stream, n1)) != NULL)
7336 mc->searched = 0; /* clear searched bits */
7338 for(p = numbers; *p ; p++){
7339 t = number1;
7340 while(*p && isdigit((unsigned char)*p))
7341 *t++ = *p++;
7343 *t = '\0';
7345 end = 0;
7346 if(number1[0] == '\0'){
7347 if(*p == '-'){
7348 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7349 _("Invalid number range, missing number before \"-\": %s"),
7350 numbers);
7351 return(1);
7353 else if(!strucmp("end", p)){
7354 end = 1;
7355 p += strlen("end");
7357 else{
7358 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7359 _("Invalid message number: %s"), numbers);
7360 return(1);
7364 if(end)
7365 n1 = mn_get_total(msgmap);
7366 else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
7367 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7368 _("\"%s\" out of message number range"),
7369 long2string(n1));
7370 return(1);
7373 t = number2;
7374 if(*p == '-'){
7375 while(*++p && isdigit((unsigned char)*p))
7376 *t++ = *p;
7378 *t = '\0';
7380 end = 0;
7381 if(number2[0] == '\0'){
7382 if(!strucmp("end", p)){
7383 end = 1;
7384 p += strlen("end");
7386 else{
7387 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7388 _("Invalid number range, missing number after \"-\": %s"),
7389 numbers);
7390 return(1);
7394 if(end)
7395 n2 = mn_get_total(msgmap);
7396 else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){
7397 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7398 _("\"%s\" out of message number range"),
7399 long2string(n2));
7400 return(1);
7403 if(n2 <= n1){
7404 char t[20];
7406 strncpy(t, long2string(n1), sizeof(t));
7407 t[sizeof(t)-1] = '\0';
7408 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7409 _("Invalid reverse message number range: %s-%s"),
7410 t, long2string(n2));
7411 return(1);
7414 for(;n1 <= n2; n1++){
7415 raw = mn_m2raw(msgmap, n1);
7416 if(raw > 0L
7417 && (!(limitsrch && *limitsrch)
7418 || in_searchset(*limitsrch, (unsigned long) raw)))
7419 mm_searched(stream, raw);
7422 else{
7423 raw = mn_m2raw(msgmap, n1);
7424 if(raw > 0L
7425 && (!(limitsrch && *limitsrch)
7426 || in_searchset(*limitsrch, (unsigned long) raw)))
7427 mm_searched(stream, raw);
7430 if(*p == '\0')
7431 break;
7434 return(0);
7439 * Select by thread number ranges.
7440 * Sets searched bits in mail_elts
7442 * Args limitsrch -- limit search to this searchset
7444 * Returns 0 on success.
7447 select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset)
7449 int r, end;
7450 long n1, n2;
7451 char number1[16], number2[16], numbers[80], *p, *t;
7452 HelpType help;
7453 PINETHRD_S *thrd = NULL;
7454 MESSAGECACHE *mc;
7456 numbers[0] = '\0';
7457 ps_global->mangled_footer = 1;
7458 help = NO_HELP;
7459 while(1){
7460 int flags = OE_APPEND_CURRENT;
7462 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7463 sizeof(numbers), _(select_num), NULL, help, &flags);
7464 if(r == 4)
7465 continue;
7467 if(r == 3){
7468 help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP;
7469 continue;
7472 for(t = p = numbers; *p ; p++) /* strip whitespace */
7473 if(!isspace((unsigned char)*p))
7474 *t++ = *p;
7476 *t = '\0';
7478 if(r == 1 || numbers[0] == '\0'){
7479 cmd_cancelled("Selection by number");
7480 return(1);
7482 else
7483 break;
7486 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7487 if((mc = mail_elt(stream, n1)) != NULL)
7488 mc->searched = 0; /* clear searched bits */
7490 for(p = numbers; *p ; p++){
7491 t = number1;
7492 while(*p && isdigit((unsigned char)*p))
7493 *t++ = *p++;
7495 *t = '\0';
7497 end = 0;
7498 if(number1[0] == '\0'){
7499 if(*p == '-'){
7500 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7501 _("Invalid number range, missing number before \"-\": %s"),
7502 numbers);
7503 return(1);
7505 else if(!strucmp("end", p)){
7506 end = 1;
7507 p += strlen("end");
7509 else{
7510 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7511 _("Invalid thread number: %s"), numbers);
7512 return(1);
7516 if(end)
7517 n1 = msgmap->max_thrdno;
7518 else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){
7519 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7520 _("\"%s\" out of thread number range"),
7521 long2string(n1));
7522 return(1);
7525 t = number2;
7526 if(*p == '-'){
7528 while(*++p && isdigit((unsigned char)*p))
7529 *t++ = *p;
7531 *t = '\0';
7533 end = 0;
7534 if(number2[0] == '\0'){
7535 if(!strucmp("end", p)){
7536 end = 1;
7537 p += strlen("end");
7539 else{
7540 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7541 _("Invalid number range, missing number after \"-\": %s"),
7542 numbers);
7543 return(1);
7547 if(end)
7548 n2 = msgmap->max_thrdno;
7549 else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){
7550 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7551 _("\"%s\" out of thread number range"),
7552 long2string(n2));
7553 return(1);
7556 if(n2 <= n1){
7557 char t[20];
7559 strncpy(t, long2string(n1), sizeof(t));
7560 t[sizeof(t)-1] = '\0';
7561 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7562 _("Invalid reverse message number range: %s-%s"),
7563 t, long2string(n2));
7564 return(1);
7567 for(;n1 <= n2; n1++){
7568 thrd = find_thread_by_number(stream, msgmap, n1, thrd);
7570 if(thrd)
7571 set_search_bit_for_thread(stream, thrd, msgset);
7574 else{
7575 thrd = find_thread_by_number(stream, msgmap, n1, NULL);
7577 if(thrd)
7578 set_search_bit_for_thread(stream, thrd, msgset);
7581 if(*p == '\0')
7582 break;
7585 return(0);
7590 * Select by message dates.
7591 * Sets searched bits in mail_elts
7593 * Args limitsrch -- limit search to this searchset
7595 * Returns 0 on success.
7598 select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
7600 int r, we_cancel = 0, when = 0;
7601 char date[100], defdate[100], prompt[128];
7602 time_t seldate = time(0);
7603 struct tm *seldate_tm;
7604 SEARCHPGM *pgm;
7605 HelpType help;
7606 static struct _tense {
7607 char *preamble,
7608 *range,
7609 *scope;
7610 } tense[] = {
7611 {"were ", "SENT SINCE", " (inclusive)"},
7612 {"were ", "SENT BEFORE", " (exclusive)"},
7613 {"were ", "SENT ON", "" },
7614 {"", "ARRIVED SINCE", " (inclusive)"},
7615 {"", "ARRIVED BEFORE", " (exclusive)"},
7616 {"", "ARRIVED ON", "" }
7619 date[0] = '\0';
7620 ps_global->mangled_footer = 1;
7621 help = NO_HELP;
7624 * If talking to an old server, default to SINCE instead of
7625 * SENTSINCE, which was added later.
7627 if(is_imap_stream(stream) && !modern_imap_stream(stream))
7628 when = 3;
7630 while(1){
7631 int flags = OE_APPEND_CURRENT;
7633 seldate_tm = localtime(&seldate);
7634 snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday,
7635 month_abbrev(seldate_tm->tm_mon + 1),
7636 seldate_tm->tm_year + 1900);
7637 defdate[sizeof(defdate)-1] = '\0';
7638 snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ",
7639 tense[when].preamble, tense[when].range,
7640 tense[when].scope, defdate);
7641 prompt[sizeof(prompt)-1] = '\0';
7642 r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date),
7643 prompt, sel_date_opt, help, &flags);
7644 switch (r){
7645 case 1 :
7646 cmd_cancelled("Selection by date");
7647 return(1);
7649 case 3 :
7650 help = (help == NO_HELP) ? h_select_date : NO_HELP;
7651 continue;
7653 case 4 :
7654 continue;
7656 case 11 :
7658 MESSAGECACHE *mc;
7659 long rawno;
7661 if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L
7662 && rawno <= stream->nmsgs
7663 && (mc = mail_elt(stream, rawno))){
7665 /* cache not filled in yet? */
7666 if(mc->day == 0){
7667 char seq[20];
7669 if(stream->dtb && stream->dtb->flags & DR_NEWS){
7670 strncpy(seq,
7671 ulong2string(mail_uid(stream, rawno)),
7672 sizeof(seq));
7673 seq[sizeof(seq)-1] = '\0';
7674 mail_fetch_overview(stream, seq, NULL);
7676 else{
7677 strncpy(seq, long2string(rawno),
7678 sizeof(seq));
7679 seq[sizeof(seq)-1] = '\0';
7680 mail_fetch_fast(stream, seq, 0L);
7684 /* mail_date returns fixed field width date */
7685 mail_date(date, mc);
7686 date[11] = '\0';
7690 continue;
7692 case 12 : /* set default to PREVIOUS day */
7693 seldate -= 86400;
7694 continue;
7696 case 13 : /* set default to NEXT day */
7697 seldate += 86400;
7698 continue;
7700 case 14 :
7701 when = (when+1) % (sizeof(tense) / sizeof(struct _tense));
7702 continue;
7704 default:
7705 break;
7708 removing_leading_white_space(date);
7709 removing_trailing_white_space(date);
7710 if(!*date){
7711 strncpy(date, defdate, sizeof(date));
7712 date[sizeof(date)-1] = '\0';
7715 break;
7718 if((pgm = mail_newsearchpgm()) != NULL){
7719 MESSAGECACHE elt;
7720 short converted_date;
7722 if(mail_parse_date(&elt, (unsigned char *) date)){
7723 converted_date = mail_shortdate(elt.year, elt.month, elt.day);
7725 switch(when){
7726 case 0:
7727 pgm->sentsince = converted_date;
7728 break;
7729 case 1:
7730 pgm->sentbefore = converted_date;
7731 break;
7732 case 2:
7733 pgm->senton = converted_date;
7734 break;
7735 case 3:
7736 pgm->since = converted_date;
7737 break;
7738 case 4:
7739 pgm->before = converted_date;
7740 break;
7741 case 5:
7742 pgm->on = converted_date;
7743 break;
7746 pgm->msgno = (limitsrch ? *limitsrch : NULL);
7748 if(ps_global && ps_global->ttyo){
7749 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
7750 ps_global->mangled_footer = 1;
7753 we_cancel = busy_cue(_("Selecting"), NULL, 1);
7755 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
7757 if(we_cancel)
7758 cancel_busy_cue(0);
7760 /* we know this was freed in mail_search, let caller know */
7761 if(limitsrch)
7762 *limitsrch = NULL;
7764 else{
7765 mail_free_searchpgm(&pgm);
7766 q_status_message1(SM_ORDER, 3, 3,
7767 _("Invalid date entered: %s"), date);
7768 return(1);
7772 return(0);
7777 * Select by searching in message headers or body.
7778 * Sets searched bits in mail_elts
7780 * Args limitsrch -- limit search to this searchset
7782 * Returns 0 on success.
7785 select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
7787 int r, ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
7788 int not = 0, me = 0;
7789 char sstring[80], savedsstring[80], tmp[128];
7790 char *p, *sval = NULL;
7791 char buftmp[MAILTMPLEN], namehdr[80];
7792 ESCKEY_S ekey[8];
7793 ENVELOPE *env = NULL;
7794 HelpType help;
7795 unsigned flagsforhist = 0;
7796 static HISTORY_S *history = NULL;
7797 static char *recip = "RECIPIENTS";
7798 static char *partic = "PARTICIPANTS";
7799 static char *match_me = N_("[Match_My_Addresses]");
7800 static char *dont_match_me = N_("[Don't_Match_My_Addresses]");
7802 ps_global->mangled_footer = 1;
7803 savedsstring[0] = '\0';
7804 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
7806 while(1){
7807 type = radio_buttons(not ? _(sel_text_not) : _(sel_text),
7808 -FOOTER_ROWS(ps_global), sel_text_opt,
7809 's', 'x', NO_HELP, RB_NORM|RB_RET_HELP);
7811 if(type == '!')
7812 not = !not;
7813 else if(type == 3){
7814 helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS",
7815 HLPD_SIMPLE);
7816 ps_global->mangled_screen = 1;
7818 else
7819 break;
7823 * prepare some friendly defaults...
7825 switch(type){
7826 case 't' : /* address fields, offer To or From */
7827 case 'f' :
7828 case 'c' :
7829 case 'r' :
7830 case 'p' :
7831 sval = (type == 't') ? "TO" :
7832 (type == 'f') ? "FROM" :
7833 (type == 'c') ? "CC" :
7834 (type == 'r') ? recip : partic;
7835 ekey[ekeyi].ch = ctrl('T');
7836 ekey[ekeyi].name = "^T";
7837 ekey[ekeyi].rval = 10;
7838 /* TRANSLATORS: use Current To Address */
7839 ekey[ekeyi++].label = N_("Cur To");
7840 ekey[ekeyi].ch = ctrl('R');
7841 ekey[ekeyi].name = "^R";
7842 ekey[ekeyi].rval = 11;
7843 /* TRANSLATORS: use Current From Address */
7844 ekey[ekeyi++].label = N_("Cur From");
7845 ekey[ekeyi].ch = ctrl('W');
7846 ekey[ekeyi].name = "^W";
7847 ekey[ekeyi].rval = 12;
7848 /* TRANSLATORS: use Current Cc Address */
7849 ekey[ekeyi++].label = N_("Cur Cc");
7850 ekey[ekeyi].ch = ctrl('Y');
7851 ekey[ekeyi].name = "^Y";
7852 ekey[ekeyi].rval = 13;
7853 /* TRANSLATORS: Match Me means match my address */
7854 ekey[ekeyi++].label = N_("Match Me");
7855 ekey[ekeyi].ch = 0;
7856 ekey[ekeyi].name = "";
7857 ekey[ekeyi].rval = 0;
7858 ekey[ekeyi++].label = "";
7859 break;
7861 case 's' :
7862 sval = "SUBJECT";
7863 ekey[ekeyi].ch = ctrl('X');
7864 ekey[ekeyi].name = "^X";
7865 ekey[ekeyi].rval = 14;
7866 /* TRANSLATORS: use Current Subject */
7867 ekey[ekeyi++].label = N_("Cur Subject");
7868 break;
7870 case 'a' :
7871 sval = "TEXT";
7872 break;
7874 case 'b' :
7875 sval = "BODYTEXT";
7876 break;
7878 case 'h' :
7879 strncpy(tmp, "Name of HEADER to match : ", sizeof(tmp)-1);
7880 tmp[sizeof(tmp)-1] = '\0';
7881 flags = OE_APPEND_CURRENT;
7882 namehdr[0] = '\0';
7883 r = 'x';
7884 while (r == 'x'){
7885 int done = 0;
7887 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
7888 sizeof(namehdr), tmp, ekey, NO_HELP, &flags);
7889 if (r == 1){
7890 cmd_cancelled("Selection by text");
7891 return(1);
7893 removing_leading_white_space(namehdr);
7894 while(!done){
7895 while ((namehdr[0] != '\0') && /* remove trailing ":" */
7896 (namehdr[strlen(namehdr) - 1] == ':'))
7897 namehdr[strlen(namehdr) - 1] = '\0';
7898 if ((namehdr[0] != '\0')
7899 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
7900 removing_trailing_white_space(namehdr);
7901 else
7902 done++;
7904 if (strchr(namehdr,' ') || strchr(namehdr,'\t') ||
7905 strchr(namehdr,':'))
7906 namehdr[0] = '\0';
7907 if (namehdr[0] == '\0')
7908 r = 'x';
7910 sval = namehdr;
7911 break;
7913 case 'x':
7914 break;
7916 default:
7917 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
7918 return(1);
7921 ekey[ekeyi].ch = KEY_UP;
7922 ekey[ekeyi].rval = 30;
7923 ekey[ekeyi].name = "";
7924 ku = ekeyi;
7925 ekey[ekeyi++].label = "";
7927 ekey[ekeyi].ch = KEY_DOWN;
7928 ekey[ekeyi].rval = 31;
7929 ekey[ekeyi].name = "";
7930 ekey[ekeyi++].label = "";
7932 ekey[ekeyi].ch = -1;
7934 if(type != 'x'){
7936 init_hist(&history, HISTSIZE);
7938 if(ekey[0].ch > -1 && msgno > 0L
7939 && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),
7940 NULL)))
7941 ekey[0].ch = -1;
7943 sstring[0] = '\0';
7944 help = NO_HELP;
7945 r = type;
7946 while(r != 'x'){
7947 if(not)
7948 /* TRANSLATORS: character String in message <message number> to NOT match : " */
7949 snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval);
7950 else
7951 snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval);
7953 if(items_in_hist(history) > 0){
7954 ekey[ku].name = HISTORY_UP_KEYNAME;
7955 ekey[ku].label = HISTORY_KEYLABEL;
7956 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
7957 ekey[ku+1].label = HISTORY_KEYLABEL;
7959 else{
7960 ekey[ku].name = "";
7961 ekey[ku].label = "";
7962 ekey[ku+1].name = "";
7963 ekey[ku+1].label = "";
7966 flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
7967 r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
7968 79, tmp, ekey, help, &flags);
7970 if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me)))))
7971 me = 0;
7973 switch(r){
7974 case 3 :
7975 help = (help == NO_HELP)
7976 ? (not
7977 ? ((type == 'f') ? h_select_txt_not_from
7978 : (type == 't') ? h_select_txt_not_to
7979 : (type == 'c') ? h_select_txt_not_cc
7980 : (type == 's') ? h_select_txt_not_subj
7981 : (type == 'a') ? h_select_txt_not_all
7982 : (type == 'r') ? h_select_txt_not_recip
7983 : (type == 'p') ? h_select_txt_not_partic
7984 : (type == 'b') ? h_select_txt_not_body
7985 : NO_HELP)
7986 : ((type == 'f') ? h_select_txt_from
7987 : (type == 't') ? h_select_txt_to
7988 : (type == 'c') ? h_select_txt_cc
7989 : (type == 's') ? h_select_txt_subj
7990 : (type == 'a') ? h_select_txt_all
7991 : (type == 'r') ? h_select_txt_recip
7992 : (type == 'p') ? h_select_txt_partic
7993 : (type == 'b') ? h_select_txt_body
7994 : NO_HELP))
7995 : NO_HELP;
7997 case 4 :
7998 continue;
8000 case 10 : /* To: default */
8001 if(env && env->to && env->to->mailbox){
8002 snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox,
8003 env->to->host ? "@" : "",
8004 env->to->host ? env->to->host : "");
8005 sstring[sizeof(sstring)-1] = '\0';
8007 continue;
8009 case 11 : /* From: default */
8010 if(env && env->from && env->from->mailbox){
8011 snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox,
8012 env->from->host ? "@" : "",
8013 env->from->host ? env->from->host : "");
8014 sstring[sizeof(sstring)-1] = '\0';
8016 continue;
8018 case 12 : /* Cc: default */
8019 if(env && env->cc && env->cc->mailbox){
8020 snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox,
8021 env->cc->host ? "@" : "",
8022 env->cc->host ? env->cc->host : "");
8023 sstring[sizeof(sstring)-1] = '\0';
8025 continue;
8027 case 13 : /* Match my addresses */
8028 me++;
8029 snprintf(sstring, sizeof(sstring), "%s", not ? _(dont_match_me) : _(match_me));
8030 continue;
8032 case 14 : /* Subject: default */
8033 if(env && env->subject && env->subject[0]){
8034 char *q = NULL;
8036 snprintf(buftmp, sizeof(buftmp), "%.75s", env->subject);
8037 buftmp[sizeof(buftmp)-1] = '\0';
8038 q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
8039 SIZEOF_20KBUF, buftmp);
8040 if(q != env->subject){
8041 snprintf(savedsstring, sizeof(savedsstring), "%.70s", q);
8042 savedsstring[sizeof(savedsstring)-1] = '\0';
8045 snprintf(sstring, sizeof(sstring), "%s", q);
8046 sstring[sizeof(sstring)-1] = '\0';
8049 continue;
8051 case 30 :
8052 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8053 if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){
8054 strncpy(sstring, p, sizeof(sstring));
8055 sstring[sizeof(sstring)-1] = '\0';
8056 if(history->hist[history->curindex]){
8057 flagsforhist = history->hist[history->curindex]->flags;
8058 not = (flagsforhist & 0x1) ? 1 : 0;
8059 me = (flagsforhist & 0x2) ? 1 : 0;
8062 else
8063 Writechar(BELL, 0);
8065 continue;
8067 case 31 :
8068 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8069 if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){
8070 strncpy(sstring, p, sizeof(sstring));
8071 sstring[sizeof(sstring)-1] = '\0';
8072 if(history->hist[history->curindex]){
8073 flagsforhist = history->hist[history->curindex]->flags;
8074 not = (flagsforhist & 0x1) ? 1 : 0;
8075 me = (flagsforhist & 0x2) ? 1 : 0;
8078 else
8079 Writechar(BELL, 0);
8081 continue;
8083 default :
8084 break;
8087 if(r == 1 || sstring[0] == '\0')
8088 r = 'x';
8090 break;
8094 if(type == 'x' || r == 'x'){
8095 cmd_cancelled("Selection by text");
8096 return(1);
8099 if(ps_global && ps_global->ttyo){
8100 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8101 ps_global->mangled_footer = 1;
8104 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8106 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8107 save_hist(history, sstring, flagsforhist, NULL);
8109 rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch);
8110 if(we_cancel)
8111 cancel_busy_cue(0);
8113 return(rv);
8118 * Select by message size.
8119 * Sets searched bits in mail_elts
8121 * Args limitsrch -- limit search to this searchset
8123 * Returns 0 on success.
8126 select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch)
8128 int r, large = 1, we_cancel = 0;
8129 unsigned long n, mult = 1L, numerator = 0L, divisor = 1L;
8130 char size[16], numbers[80], *p, *t;
8131 HelpType help;
8132 SEARCHPGM *pgm;
8133 long flags = (SE_NOPREFETCH | SE_FREE);
8135 numbers[0] = '\0';
8136 ps_global->mangled_footer = 1;
8138 help = NO_HELP;
8139 while(1){
8140 int flgs = OE_APPEND_CURRENT;
8142 sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger;
8144 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
8145 sizeof(numbers), large ? _(select_size_larger_msg)
8146 : _(select_size_smaller_msg),
8147 sel_size_opt, help, &flgs);
8148 if(r == 4)
8149 continue;
8151 if(r == 14){
8152 large = 1 - large;
8153 continue;
8156 if(r == 3){
8157 help = (help == NO_HELP) ? (large ? h_select_by_larger_size
8158 : h_select_by_smaller_size)
8159 : NO_HELP;
8160 continue;
8163 for(t = p = numbers; *p ; p++) /* strip whitespace */
8164 if(!isspace((unsigned char)*p))
8165 *t++ = *p;
8167 *t = '\0';
8169 if(r == 1 || numbers[0] == '\0'){
8170 cmd_cancelled("Selection by size");
8171 return(1);
8173 else
8174 break;
8177 if(numbers[0] == '-'){
8178 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8179 _("Invalid size entered: %s"), numbers);
8180 return(1);
8183 t = size;
8184 p = numbers;
8186 while(*p && isdigit((unsigned char)*p))
8187 *t++ = *p++;
8189 *t = '\0';
8191 if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){
8192 size[0] = '0';
8193 size[1] = '\0';
8196 if(size[0] == '\0'){
8197 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8198 _("Invalid size entered: %s"), numbers);
8199 return(1);
8202 n = strtoul(size, (char **)NULL, 10);
8204 size[0] = '\0';
8205 if(*p == '.'){
8207 * We probably ought to just use atof() to convert 1.1 into a
8208 * double, but since we haven't used atof() anywhere else I'm
8209 * reluctant to use it because of portability concerns.
8211 p++;
8212 t = size;
8213 while(*p && isdigit((unsigned char)*p)){
8214 *t++ = *p++;
8215 divisor *= 10;
8218 *t = '\0';
8220 if(size[0])
8221 numerator = strtoul(size, (char **)NULL, 10);
8224 switch(*p){
8225 case 'g':
8226 case 'G':
8227 mult *= 1000;
8228 /* fall through */
8230 case 'm':
8231 case 'M':
8232 mult *= 1000;
8233 /* fall through */
8235 case 'k':
8236 case 'K':
8237 mult *= 1000;
8238 break;
8241 n = n * mult + (numerator * mult) / divisor;
8243 pgm = mail_newsearchpgm();
8244 if(large)
8245 pgm->larger = n;
8246 else
8247 pgm->smaller = n;
8249 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8250 flags |= SE_NOSERVER;
8252 if(ps_global && ps_global->ttyo){
8253 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8254 ps_global->mangled_footer = 1;
8257 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8259 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8260 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8261 /* we know this was freed in mail_search, let caller know */
8262 if(limitsrch)
8263 *limitsrch = NULL;
8265 if(we_cancel)
8266 cancel_busy_cue(0);
8268 return(0);
8273 * visible_searchset -- return c-client search set unEXLDed
8274 * sequence numbers
8276 SEARCHSET *
8277 visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap)
8279 long n, run;
8280 SEARCHSET *full_set = NULL, **set;
8283 * If we're talking to anything other than a server older than
8284 * imap 4rev1, build a searchset otherwise it'll choke.
8286 if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
8287 if(any_lflagged(msgmap, MN_EXLD)){
8288 for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
8289 if(get_lflag(stream, NULL, n, MN_EXLD)){
8290 if(run){ /* previous NOT excluded? */
8291 if(run > 1L)
8292 (*set)->last = n - 1L;
8294 set = &(*set)->next;
8295 run = 0L;
8298 else if(run++){ /* next in run */
8299 (*set)->last = n;
8301 else{ /* start of run */
8302 *set = mail_newsearchset();
8303 (*set)->first = n;
8306 else{
8307 full_set = mail_newsearchset();
8308 full_set->first = 1L;
8309 full_set->last = stream->nmsgs;
8313 return(full_set);
8318 * Select by message status bits.
8319 * Sets searched bits in mail_elts
8321 * Args limitsrch -- limit search to this searchset
8323 * Returns 0 on success.
8326 select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch)
8328 int s, not = 0, we_cancel = 0, rv;
8330 while(1){
8331 s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag),
8332 -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x',
8333 NO_HELP, RB_NORM|RB_RET_HELP);
8335 if(s == 'x'){
8336 cmd_cancelled("Selection by status");
8337 return(1);
8339 else if(s == 3){
8340 helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"),
8341 HLPD_SIMPLE);
8342 ps_global->mangled_screen = 1;
8344 else if(s == '!')
8345 not = !not;
8346 else
8347 break;
8350 if(ps_global && ps_global->ttyo){
8351 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8352 ps_global->mangled_footer = 1;
8355 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8356 rv = agg_flag_select(stream, not, s, limitsrch);
8357 if(we_cancel)
8358 cancel_busy_cue(0);
8360 return(rv);
8365 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8366 * Sets searched bits in mail_elts
8368 * Args limitsrch -- limit search to this searchset
8370 * Returns 0 on success.
8373 select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
8375 char rulenick[1000], *nick;
8376 PATGRP_S *patgrp;
8377 int r, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
8378 | ROLE_DO_INCOLS
8379 | ROLE_DO_ROLES
8380 | ROLE_DO_SCORES
8381 | ROLE_DO_OTHER
8382 | ROLE_DO_FILTER;
8384 rulenick[0] = '\0';
8385 ps_global->mangled_footer = 1;
8388 int oe_flags;
8390 oe_flags = OE_APPEND_CURRENT;
8391 r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0,
8392 sizeof(rulenick),
8393 not ? _("Rule to NOT match: ")
8394 : _("Rule to match: "),
8395 sel_key_opt, NO_HELP, &oe_flags);
8397 if(r == 14){
8398 /* select rulenick from a list */
8399 if((nick=choose_a_rule(rflags)) != NULL){
8400 strncpy(rulenick, nick, sizeof(rulenick)-1);
8401 rulenick[sizeof(rulenick)-1] = '\0';
8402 fs_give((void **) &nick);
8404 else
8405 r = 4;
8407 else if(r == '!')
8408 not = !not;
8410 if(r == 3){
8411 helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE);
8412 ps_global->mangled_screen = 1;
8414 else if(r == 1){
8415 cmd_cancelled("Selection by Rule");
8416 return(1);
8419 removing_leading_and_trailing_white_space(rulenick);
8421 }while(r == 3 || r == 4 || r == '!');
8425 * The approach of requiring a nickname instead of just allowing the
8426 * user to select from the list of rules has the drawback that a rule
8427 * may not have a nickname, or there may be more than one rule with
8428 * the same nickname. However, it has the benefit of allowing the user
8429 * to type in the nickname and, most importantly, allows us to set
8430 * up the ! (not). We could incorporate the ! into the selection
8431 * screen, but this is easier and also allows the typing of nicks.
8432 * User can just set up nicknames if they want to use this feature.
8434 patgrp = nick_to_patgrp(rulenick, rflags);
8436 if(patgrp){
8437 if(ps_global && ps_global->ttyo){
8438 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8439 ps_global->mangled_footer = 1;
8442 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8443 match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL,
8444 get_msg_score,
8445 (not ? MP_NOT : 0) | SE_NOPREFETCH);
8446 free_patgrp(&patgrp);
8447 if(we_cancel)
8448 cancel_busy_cue(0);
8451 if(limitsrch && *limitsrch){
8452 mail_free_searchset(limitsrch);
8453 *limitsrch = NULL;
8456 return(0);
8461 * Allow user to choose a rule from their list of rules.
8463 * Returns an allocated rule nickname on success, NULL otherwise.
8465 char *
8466 choose_a_rule(int rflags)
8468 char *choice = NULL;
8469 char **rule_list, **lp;
8470 int cnt = 0;
8471 PAT_S *pat;
8472 PAT_STATE pstate;
8474 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){
8475 q_status_message(SM_ORDER, 3, 3,
8476 _("No rules available. Use Setup/Rules to add some."));
8477 return(choice);
8481 * Build a list of rules to choose from.
8484 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8485 cnt++;
8487 if(cnt <= 0){
8488 q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
8489 return(choice);
8492 lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
8493 memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
8495 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8496 *lp++ = cpystr((pat->patgrp && pat->patgrp->nick)
8497 ? pat->patgrp->nick : "?");
8499 /* TRANSLATORS: SELECT A RULE is a screen title
8500 TRANSLATORS: Print something1 using something2.
8501 "rules" is something1 */
8502 choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"),
8503 _("rules"), h_select_rule_screen,
8504 _("HELP FOR SELECTING A RULE NICKNAME"), NULL);
8506 if(!choice)
8507 q_status_message(SM_ORDER, 1, 4, "No choice");
8509 free_list_array(&rule_list);
8511 return(choice);
8516 * Select by current thread.
8517 * Sets searched bits in mail_elts for this entire thread
8519 * Args limitsrch -- limit search to this searchset
8521 * Returns 0 on success.
8524 select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
8526 long n;
8527 PINETHRD_S *thrd = NULL;
8528 int ret = 1;
8529 MESSAGECACHE *mc;
8531 if(!stream)
8532 return(ret);
8534 for(n = 1L; n <= stream->nmsgs; n++)
8535 if((mc = mail_elt(stream, n)) != NULL)
8536 mc->searched = 0; /* clear searched bits */
8538 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
8539 if(thrd && thrd->top && thrd->top != thrd->rawno)
8540 thrd = fetch_thread(stream, thrd->top);
8543 * This doesn't unselect if the thread is already selected
8544 * (like select current does), it always selects.
8545 * There is no way to select ! this thread.
8547 if(thrd){
8548 set_search_bit_for_thread(stream, thrd, limitsrch);
8549 ret = 0;
8552 return(ret);
8557 * Select by message keywords.
8558 * Sets searched bits in mail_elts
8560 * Args limitsrch -- limit search to this searchset
8562 * Returns 0 on success.
8565 select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
8567 int r, not = 0, we_cancel = 0;
8568 char keyword[MAXUSERFLAG+1], *kword;
8569 char *error = NULL, *p, *prompt;
8570 HelpType help;
8571 SEARCHPGM *pgm;
8573 keyword[0] = '\0';
8574 ps_global->mangled_footer = 1;
8576 help = NO_HELP;
8578 int oe_flags;
8580 if(error){
8581 q_status_message(SM_ORDER, 3, 4, error);
8582 fs_give((void **) &error);
8585 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
8586 if(not)
8587 prompt = _("Keyword (or keyword initial) to NOT match: ");
8588 else
8589 prompt = _("Keyword (or keyword initial) to match: ");
8591 else{
8592 if(not)
8593 prompt = _("Keyword to NOT match: ");
8594 else
8595 prompt = _("Keyword to match: ");
8598 oe_flags = OE_APPEND_CURRENT;
8599 r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0,
8600 sizeof(keyword),
8601 prompt, sel_key_opt, help, &oe_flags);
8603 if(r == 14){
8604 /* select keyword from a list */
8605 if((kword=choose_a_keyword()) != NULL){
8606 strncpy(keyword, kword, sizeof(keyword)-1);
8607 keyword[sizeof(keyword)-1] = '\0';
8608 fs_give((void **) &kword);
8610 else
8611 r = 4;
8613 else if(r == '!')
8614 not = !not;
8616 if(r == 3)
8617 help = help == NO_HELP ? h_select_keyword : NO_HELP;
8618 else if(r == 1){
8619 cmd_cancelled("Selection by keyword");
8620 return(1);
8623 removing_leading_and_trailing_white_space(keyword);
8625 }while(r == 3 || r == 4 || r == '!' || keyword_check(keyword, &error));
8628 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
8629 p = initial_to_keyword(keyword);
8630 if(p != keyword){
8631 strncpy(keyword, p, sizeof(keyword)-1);
8632 keyword[sizeof(keyword)-1] = '\0';
8637 * We want to check the keyword, not the nickname of the keyword,
8638 * so convert it to the keyword if necessary.
8640 p = nick_to_keyword(keyword);
8641 if(p != keyword){
8642 strncpy(keyword, p, sizeof(keyword)-1);
8643 keyword[sizeof(keyword)-1] = '\0';
8646 pgm = mail_newsearchpgm();
8647 if(not){
8648 pgm->unkeyword = mail_newstringlist();
8649 pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword);
8650 pgm->unkeyword->text.size = strlen(keyword);
8652 else{
8653 pgm->keyword = mail_newstringlist();
8654 pgm->keyword->text.data = (unsigned char *) cpystr(keyword);
8655 pgm->keyword->text.size = strlen(keyword);
8658 if(ps_global && ps_global->ttyo){
8659 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8660 ps_global->mangled_footer = 1;
8663 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8665 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8666 pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE);
8667 /* we know this was freed in mail_search, let caller know */
8668 if(limitsrch)
8669 *limitsrch = NULL;
8671 if(we_cancel)
8672 cancel_busy_cue(0);
8674 return(0);
8679 * Allow user to choose a keyword from their list of keywords.
8681 * Returns an allocated keyword on success, NULL otherwise.
8683 char *
8684 choose_a_keyword(void)
8686 char *choice = NULL;
8687 char **keyword_list, **lp;
8688 int cnt;
8689 KEYWORD_S *kw;
8692 * Build a list of keywords to choose from.
8695 for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next)
8696 cnt++;
8698 if(cnt <= 0){
8699 q_status_message(SM_ORDER, 3, 4,
8700 _("No keywords defined, use \"keywords\" option in Setup/Config"));
8701 return(choice);
8704 lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list));
8705 memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list));
8707 for(kw = ps_global->keywords; kw; kw = kw->next)
8708 *lp++ = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
8710 /* TRANSLATORS: SELECT A KEYWORD is a screen title
8711 TRANSLATORS: Print something1 using something2.
8712 "keywords" is something1 */
8713 choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"),
8714 _("keywords"), h_select_keyword_screen,
8715 _("HELP FOR SELECTING A KEYWORD"), NULL);
8717 if(!choice)
8718 q_status_message(SM_ORDER, 1, 4, "No choice");
8720 free_list_array(&keyword_list);
8722 return(choice);
8727 * Allow user to choose a list of keywords from their list of keywords.
8729 * Returns allocated list.
8731 char **
8732 choose_list_of_keywords(void)
8734 LIST_SEL_S *listhead, *ls, *p;
8735 char **ret = NULL;
8736 int cnt, i;
8737 KEYWORD_S *kw;
8740 * Build a list of keywords to choose from.
8743 p = listhead = NULL;
8744 for(kw = ps_global->keywords; kw; kw = kw->next){
8746 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
8747 memset(ls, 0, sizeof(*ls));
8748 ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
8750 if(p){
8751 p->next = ls;
8752 p = p->next;
8754 else
8755 listhead = p = ls;
8758 if(!listhead)
8759 return(ret);
8761 /* TRANSLATORS: SELECT KEYWORDS is a screen title
8762 Print something1 using something2.
8763 "keywords" is something1 */
8764 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
8765 _("SELECT KEYWORDS"), _("keywords"),
8766 h_select_multkeyword_screen,
8767 _("HELP FOR SELECTING KEYWORDS"), NULL)){
8768 for(cnt = 0, p = listhead; p; p = p->next)
8769 if(p->selected)
8770 cnt++;
8772 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
8773 memset(ret, 0, (cnt+1) * sizeof(*ret));
8774 for(i = 0, p = listhead; p; p = p->next)
8775 if(p->selected)
8776 ret[i++] = cpystr(p->item ? p->item : "");
8779 free_list_sel(&listhead);
8781 return(ret);
8786 * Allow user to choose a charset
8788 * Returns an allocated charset on success, NULL otherwise.
8790 char *
8791 choose_a_charset(int which_charsets)
8793 char *choice = NULL;
8794 char **charset_list, **lp;
8795 const CHARSET *cs;
8796 int cnt;
8799 * Build a list of charsets to choose from.
8802 for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){
8803 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
8804 && ((which_charsets & CAC_ALL)
8805 || (which_charsets & CAC_POSTING
8806 && cs->flags & CF_POSTING)
8807 || (which_charsets & CAC_DISPLAY
8808 && cs->type != CT_2022
8809 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
8810 cnt++;
8813 if(cnt <= 0){
8814 q_status_message(SM_ORDER, 3, 4,
8815 _("No charsets found? Enter charset manually."));
8816 return(choice);
8819 lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list));
8820 memset(charset_list, 0, (cnt+1) * sizeof(*charset_list));
8822 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
8823 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
8824 && ((which_charsets & CAC_ALL)
8825 || (which_charsets & CAC_POSTING
8826 && cs->flags & CF_POSTING)
8827 || (which_charsets & CAC_DISPLAY
8828 && cs->type != CT_2022
8829 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
8830 *lp++ = cpystr(cs->name);
8833 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
8834 TRANSLATORS: Print something1 using something2.
8835 "character sets" is something1 */
8836 choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"),
8837 _("character sets"), h_select_charset_screen,
8838 _("HELP FOR SELECTING A CHARACTER SET"), NULL);
8840 if(!choice)
8841 q_status_message(SM_ORDER, 1, 4, "No choice");
8843 free_list_array(&charset_list);
8845 return(choice);
8850 * Allow user to choose a list of character sets and/or scripts
8852 * Returns allocated list.
8854 char **
8855 choose_list_of_charsets(void)
8857 LIST_SEL_S *listhead, *ls, *p;
8858 char **ret = NULL;
8859 int cnt, i, got_one;
8860 const CHARSET *cs;
8861 SCRIPT *s;
8862 char *q, *t;
8863 long width, limit;
8864 char buf[1024], *folded;
8867 * Build a list of charsets to choose from.
8870 p = listhead = NULL;
8872 /* this width is determined by select_from_list_screen() */
8873 width = ps_global->ttyo->screen_cols - 4;
8875 /* first comes a list of scripts (sets of character sets) */
8876 for(s = utf8_script(NIL); s && s->name; s++){
8878 limit = sizeof(buf)-1;
8879 q = buf;
8880 memset(q, 0, limit+1);
8882 if(s->name)
8883 sstrncpy(&q, s->name, limit);
8885 if(s->description){
8886 sstrncpy(&q, " (", limit-(q-buf));
8887 sstrncpy(&q, s->description, limit-(q-buf));
8888 sstrncpy(&q, ")", limit-(q-buf));
8891 /* add the list of charsets that are in this script */
8892 got_one = 0;
8893 for(cs = utf8_charset(NIL);
8894 cs && cs->name && (q-buf) < limit; cs++){
8895 if(cs->script & s->script){
8897 * Filter out some un-useful members of the list.
8898 * UTF-7 and UTF-8 weren't actually in the list at the
8899 * time this was written. Just making sure.
8901 if(!strucmp(cs->name, "ISO-2022-JP-2")
8902 || !strucmp(cs->name, "UTF-7")
8903 || !strucmp(cs->name, "UTF-8"))
8904 continue;
8906 if(got_one)
8907 sstrncpy(&q, " ", limit-(q-buf));
8908 else{
8909 got_one = 1;
8910 sstrncpy(&q, " {", limit-(q-buf));
8913 sstrncpy(&q, cs->name, limit-(q-buf));
8917 if(got_one)
8918 sstrncpy(&q, "}", limit-(q-buf));
8920 /* fold this line so that it can all be seen on the screen */
8921 folded = fold(buf, width, width, "", " ", FLD_NONE);
8922 if(folded){
8923 t = folded;
8924 while(t && *t && (q = strindex(t, '\n')) != NULL){
8925 *q = '\0';
8927 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
8928 memset(ls, 0, sizeof(*ls));
8929 if(t == folded)
8930 ls->item = cpystr(s->name);
8931 else
8932 ls->flags = SFL_NOSELECT;
8934 ls->display_item = cpystr(t);
8936 t = q+1;
8938 if(p){
8939 p->next = ls;
8940 p = p->next;
8942 else{
8943 /* add a heading */
8944 listhead = (LIST_SEL_S *) fs_get(sizeof(*ls));
8945 memset(listhead, 0, sizeof(*listhead));
8946 listhead->flags = SFL_NOSELECT;
8947 listhead->display_item =
8948 cpystr(_("Scripts representing groups of related character sets"));
8949 listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls));
8950 memset(listhead->next, 0, sizeof(*listhead));
8951 listhead->next->flags = SFL_NOSELECT;
8952 listhead->next->display_item =
8953 cpystr(repeat_char(width, '-'));
8955 listhead->next->next = ls;
8956 p = ls;
8960 fs_give((void **) &folded);
8964 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
8965 memset(ls, 0, sizeof(*ls));
8966 ls->flags = SFL_NOSELECT;
8967 if(p){
8968 p->next = ls;
8969 p = p->next;
8971 else
8972 listhead = p = ls;
8974 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
8975 memset(ls, 0, sizeof(*ls));
8976 ls->flags = SFL_NOSELECT;
8977 ls->display_item =
8978 cpystr(_("Individual character sets, may be mixed with scripts"));
8979 p->next = ls;
8980 p = p->next;
8982 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
8983 memset(ls, 0, sizeof(*ls));
8984 ls->flags = SFL_NOSELECT;
8985 ls->display_item =
8986 cpystr(repeat_char(width, '-'));
8987 p->next = ls;
8988 p = p->next;
8990 /* then comes a list of individual character sets */
8991 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
8992 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
8993 memset(ls, 0, sizeof(*ls));
8994 ls->item = cpystr(cs->name);
8996 if(p){
8997 p->next = ls;
8998 p = p->next;
9000 else
9001 listhead = p = ls;
9004 if(!listhead)
9005 return(ret);
9007 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9008 Print something1 using something2.
9009 "character sets" is something1 */
9010 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9011 _("SELECT CHARACTER SETS"), _("character sets"),
9012 h_select_multcharsets_screen,
9013 _("HELP FOR SELECTING CHARACTER SETS"), NULL)){
9014 for(cnt = 0, p = listhead; p; p = p->next)
9015 if(p->selected)
9016 cnt++;
9018 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9019 memset(ret, 0, (cnt+1) * sizeof(*ret));
9020 for(i = 0, p = listhead; p; p = p->next)
9021 if(p->selected)
9022 ret[i++] = cpystr(p->item ? p->item : "");
9025 free_list_sel(&listhead);
9027 return(ret);
9030 /* Report quota summary resources in an IMAP server */
9032 void cmd_quota (struct pine *state)
9034 QUOTALIST *imapquota;
9035 NETMBX mb;
9036 STORE_S *store;
9037 SCROLL_S sargs;
9039 if(!state->mail_stream || !is_imap_stream(state->mail_stream)){
9040 q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders");
9041 return;
9044 if (state->mail_stream
9045 && !sp_dead_stream(state->mail_stream)
9046 && state->mail_stream->mailbox
9047 && *state->mail_stream->mailbox
9048 && mail_valid_net_parse(state->mail_stream->mailbox, &mb))
9049 imap_getquotaroot(state->mail_stream, mb.mailbox);
9051 if(!state->quota) /* failed ? */
9052 return; /* go back... */
9054 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
9055 q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space.");
9056 return;
9059 so_puts(store, "Quota Report for ");
9060 so_puts(store, state->mail_stream->original_mailbox);
9061 so_puts(store, "\n\n");
9063 for (imapquota = state->quota; imapquota; imapquota = imapquota->next){
9065 so_puts(store, _("Resource : "));
9066 so_puts(store, imapquota->name);
9067 so_writec('\n', store);
9069 so_puts(store, _("Usage : "));
9070 so_puts(store, long2string(imapquota->usage));
9071 if(!strucmp(imapquota->name,"STORAGE"))
9072 so_puts(store, " KiB ");
9073 if(!strucmp(imapquota->name,"MESSAGE")){
9074 so_puts(store, _(" message"));
9075 if(imapquota->usage != 1)
9076 so_puts(store, _("s ")); /* plural */
9077 else
9078 so_puts(store, _(" "));
9080 so_writec('(', store);
9081 so_puts(store, long2string(100*imapquota->usage/imapquota->limit));
9082 so_puts(store, "%)\n");
9084 so_puts(store, _("Limit : "));
9085 so_puts(store, long2string(imapquota->limit));
9086 if(!strucmp(imapquota->name,"STORAGE"))
9087 so_puts(store, " KiB\n\n");
9088 if(!strucmp(imapquota->name,"MESSAGE")){
9089 so_puts(store, _(" message"));
9090 if(imapquota->usage != 1)
9091 so_puts(store, _("s\n\n")); /* plural */
9092 else
9093 so_puts(store, _("\n\n"));
9097 memset(&sargs, 0, sizeof(SCROLL_S));
9098 sargs.text.text = so_text(store);
9099 sargs.text.src = CharStar;
9100 sargs.text.desc = _("Quota Resources Summary");
9101 sargs.bar.title = _("QUOTA SUMMARY");
9102 sargs.proc.tool = NULL;
9103 sargs.help.text = h_quota_command;
9104 sargs.help.title = NULL;
9105 sargs.keys.menu = NULL;
9106 setbitmap(sargs.keys.bitmap);
9108 scrolltool(&sargs);
9109 so_give(&store);
9111 if (state->quota)
9112 mail_free_quotalist(&(state->quota));
9115 /*----------------------------------------------------------------------
9116 Prompt the user for the type of sort he desires
9118 Args: state -- pine state pointer
9119 q1 -- Line to prompt on
9121 Returns 0 if it was cancelled, 1 otherwise.
9122 ----*/
9124 select_sort(struct pine *state, int ql, SortOrder *sort, int *rev)
9126 char prompt[200], tmp[3], *p;
9127 int s, i;
9128 int deefault = 'a', retval = 1;
9129 HelpType help;
9130 ESCKEY_S sorts[14];
9132 #ifdef _WINDOWS
9133 DLG_SORTPARAM sortsel;
9135 if (mswin_usedialog ()) {
9137 sortsel.reverse = mn_get_revsort (state->msgmap);
9138 sortsel.cursort = mn_get_sort (state->msgmap);
9139 /* assumption here that HelpType is char ** */
9140 sortsel.helptext = h_select_sort;
9141 sortsel.rval = 0;
9143 if ((retval = os_sortdialog (&sortsel))) {
9144 *sort = sortsel.cursort;
9145 *rev = sortsel.reverse;
9148 return (retval);
9150 #endif
9152 /*----- String together the prompt ------*/
9153 tmp[1] = '\0';
9154 if(F_ON(F_USE_FK,ps_global))
9155 strncpy(prompt, _("Choose type of sort : "), sizeof(prompt));
9156 else
9157 strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "),
9158 sizeof(prompt));
9160 for(i = 0; state->sort_types[i] != EndofList; i++) {
9161 sorts[i].rval = i;
9162 p = sorts[i].label = sort_name(state->sort_types[i]);
9163 while(*(p+1) && islower((unsigned char)*p))
9164 p++;
9166 sorts[i].ch = tolower((unsigned char)(tmp[0] = *p));
9167 sorts[i].name = cpystr(tmp);
9169 if(mn_get_sort(state->msgmap) == state->sort_types[i])
9170 deefault = sorts[i].rval;
9173 sorts[i].ch = 'r';
9174 sorts[i].rval = 'r';
9175 sorts[i].name = cpystr("R");
9176 if(F_ON(F_USE_FK,ps_global))
9177 sorts[i].label = N_("Reverse");
9178 else
9179 sorts[i].label = "";
9181 sorts[++i].ch = -1;
9182 help = h_select_sort;
9184 if((F_ON(F_USE_FK,ps_global)
9185 && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x',
9186 help,RB_NORM)) != 'x'))
9188 (F_OFF(F_USE_FK,ps_global)
9189 && ((s = radio_buttons(prompt,ql,sorts,deefault,'x',
9190 help,RB_NORM)) != 'x'))){
9191 state->mangled_body = 1; /* signal screen's changed */
9192 if(s == 'r')
9193 *rev = !mn_get_revsort(state->msgmap);
9194 else
9195 *sort = state->sort_types[s];
9197 if(F_ON(F_SHOW_SORT, ps_global))
9198 ps_global->mangled_header = 1;
9200 else{
9201 retval = 0;
9202 cmd_cancelled("Sort");
9205 while(--i >= 0)
9206 fs_give((void **)&sorts[i].name);
9208 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9209 return(retval);
9213 /*---------------------------------------------------------------------
9214 Build list of folders in the given context for user selection
9216 Args: c -- pointer to pointer to folder's context context
9217 f -- folder prefix to display
9218 sublist -- whether or not to use 'f's contents as prefix
9219 lister -- function used to do the actual display
9221 Returns: malloc'd string containing sequence, else NULL if
9222 no messages in msgmap with local "selected" flag.
9223 ----*/
9225 display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int))
9227 int rc;
9228 CONTEXT_S *tc;
9229 void (*redraw)(void) = ps_global->redrawer;
9231 push_titlebar_state();
9232 tc = *c;
9233 if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0)
9234 *c = tc;
9236 ClearScreen();
9237 pop_titlebar_state();
9238 redraw_titlebar();
9239 if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
9240 (*ps_global->redrawer)();
9242 if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
9243 return(1);
9245 return(0);
9250 * Allow user to choose a single item from a list of strings.
9252 * Args list -- Array of strings to choose from, NULL terminated.
9253 * displist -- Array of strings to display instead of displaying list.
9254 * Indices correspond to the list array. Display the displist
9255 * but return the item from list if displist non-NULL.
9256 * title -- For conf_scroll_screen
9257 * pdesc -- For conf_scroll_screen
9258 * help -- For conf_scroll_screen
9259 * htitle -- For conf_scroll_screen
9261 * Returns an allocated copy of the chosen item or NULL.
9263 char *
9264 choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help,
9265 char *htitle, char *cursor_location)
9267 LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL;
9268 char **t, **dl;
9269 char *ret = NULL, *choice = NULL;
9271 /* build the LIST_SEL_S list */
9272 p = listhead = NULL;
9273 for(t = list, dl = displist; *t; t++, dl++){
9274 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9275 memset(ls, 0, sizeof(*ls));
9276 ls->item = cpystr(*t);
9277 if(displist)
9278 ls->display_item = cpystr(*dl);
9280 if(cursor_location && (cursor_location == (*t)))
9281 starting_val = ls;
9283 if(p){
9284 p->next = ls;
9285 p = p->next;
9287 else
9288 listhead = p = ls;
9291 if(!listhead)
9292 return(ret);
9294 if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc,
9295 help, htitle, starting_val))
9296 for(p = listhead; !choice && p; p = p->next)
9297 if(p->selected)
9298 choice = p->item;
9300 if(choice)
9301 ret = cpystr(choice);
9303 free_list_sel(&listhead);
9305 return(ret);
9309 void
9310 free_list_sel(LIST_SEL_S **lsel)
9312 if(lsel && *lsel){
9313 free_list_sel(&(*lsel)->next);
9314 if((*lsel)->item)
9315 fs_give((void **) &(*lsel)->item);
9317 if((*lsel)->display_item)
9318 fs_give((void **) &(*lsel)->display_item);
9320 fs_give((void **) lsel);
9326 * file_lister - call pico library's file lister
9329 file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags)
9331 PICO pbf;
9332 int rv;
9333 void (*redraw)(void) = ps_global->redrawer;
9335 standard_picobuf_setup(&pbf);
9336 push_titlebar_state();
9337 if(!newmail)
9338 pbf.newmail = NULL;
9340 /* BUG: what about help command and text? */
9341 pbf.pine_anchor = title;
9343 rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags);
9344 standard_picobuf_teardown(&pbf);
9345 fix_windsize(ps_global);
9346 init_signals(); /* has it's own signal stuff */
9348 /* Restore display's titlebar and body */
9349 pop_titlebar_state();
9350 redraw_titlebar();
9351 if((ps_global->redrawer = redraw) != NULL)
9352 (*ps_global->redrawer)();
9354 return(rv);
9358 /*----------------------------------------------------------------------
9359 Print current folder index
9361 ---*/
9363 print_index(struct pine *state, MSGNO_S *msgmap, int agg)
9365 long i;
9366 ICE_S *ice;
9367 char buf[MAX_SCREEN_COLS+1];
9369 for(i = 1L; i <= mn_get_total(msgmap); i++){
9370 if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
9371 continue;
9373 if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0))
9374 continue;
9376 ice = build_header_line(state, state->mail_stream, msgmap, i, NULL);
9378 if(ice){
9380 * I don't understand why we'd want to mark the current message
9381 * instead of printing out the first character of the status
9382 * so I'm taking it out and including the first character of the
9383 * line instead. Hubert 2006-02-09
9385 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
9386 return(0);
9389 if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i),
9390 print_char)
9391 || !gf_puts(NEWLINE, print_char))
9392 return(0);
9396 return(1);
9400 #ifdef _WINDOWS
9403 * windows callback to get/set header mode state
9406 header_mode_callback(set, args)
9407 int set;
9408 long args;
9410 return(ps_global->full_header);
9415 * windows callback to get/set zoom mode state
9418 zoom_mode_callback(set, args)
9419 int set;
9420 long args;
9422 return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0);
9427 * windows callback to get/set zoom mode state
9430 any_selected_callback(set, args)
9431 int set;
9432 long args;
9434 return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0);
9442 flag_callback(set, flags)
9443 int set;
9444 long flags;
9446 MESSAGECACHE *mc;
9447 int newflags = 0;
9448 long msgno;
9449 int permflag = 0;
9451 switch (set) {
9452 case 1: /* Important */
9453 permflag = ps_global->mail_stream->perm_flagged;
9454 break;
9456 case 2: /* New */
9457 permflag = ps_global->mail_stream->perm_seen;
9458 break;
9460 case 3: /* Answered */
9461 permflag = ps_global->mail_stream->perm_answered;
9462 break;
9464 case 4: /* Deleted */
9465 permflag = ps_global->mail_stream->perm_deleted;
9466 break;
9470 if(!(any_messages(ps_global->msgmap, NULL, "to Flag")
9471 && can_set_flag(ps_global, "flag", permflag)))
9472 return(0);
9474 if(sp_io_error_on_stream(ps_global->mail_stream)){
9475 sp_set_io_error_on_stream(ps_global->mail_stream, 0);
9476 pine_mail_check(ps_global->mail_stream); /* forces write */
9477 return(0);
9480 msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
9481 if(msgno > 0L && ps_global->mail_stream
9482 && msgno <= ps_global->mail_stream->nmsgs
9483 && (mc = mail_elt(ps_global->mail_stream, msgno))
9484 && mc->valid){
9486 * NOTE: code below is *VERY* sensitive to the order of
9487 * the messages defined in resource.h for flag handling.
9488 * Don't change it unless you know what you're doing.
9490 if(set){
9491 char *flagstr;
9492 long mflag;
9494 switch(set){
9495 case 1 : /* Important */
9496 flagstr = "\\FLAGGED";
9497 mflag = (mc->flagged) ? 0L : ST_SET;
9498 break;
9500 case 2 : /* New */
9501 flagstr = "\\SEEN";
9502 mflag = (mc->seen) ? 0L : ST_SET;
9503 break;
9505 case 3 : /* Answered */
9506 flagstr = "\\ANSWERED";
9507 mflag = (mc->answered) ? 0L : ST_SET;
9508 break;
9510 case 4 : /* Deleted */
9511 flagstr = "\\DELETED";
9512 mflag = (mc->deleted) ? 0L : ST_SET;
9513 break;
9515 default : /* bogus */
9516 return(0);
9519 mail_flag(ps_global->mail_stream, long2string(msgno),
9520 flagstr, mflag);
9522 if(ps_global->redrawer)
9523 (*ps_global->redrawer)();
9525 else{
9526 /* Important */
9527 if(mc->flagged)
9528 newflags |= 0x0001;
9530 /* New */
9531 if(!mc->seen)
9532 newflags |= 0x0002;
9534 /* Answered */
9535 if(mc->answered)
9536 newflags |= 0x0004;
9538 /* Deleted */
9539 if(mc->deleted)
9540 newflags |= 0x0008;
9544 return(newflags);
9550 * BUG: Should teach this about keywords
9552 MPopup *
9553 flag_submenu(mc)
9554 MESSAGECACHE *mc;
9556 static MPopup flag_submenu[] = {
9557 {tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}},
9558 {tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}},
9559 {tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}},
9560 {tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}},
9561 {tTail}
9564 /* Important */
9565 flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal;
9567 /* New */
9568 flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked;
9570 /* Answered */
9571 flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal;
9573 /* Deleted */
9574 flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal;
9576 return(flag_submenu);
9579 #endif /* _WINDOWS */