Add support for tab-completion when selecting by rule
[alpine.git] / alpine / mailcmd.c
blob05c7de26aecbc0802ccd2184562fc33d2f3f9860
1 /*
2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2009 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 /*======================================================================
16 mailcmd.c
17 The meat and pototoes of mail processing here:
18 - initial command processing and dispatch
19 - save message
20 - capture address off incoming mail
21 - jump to specific numbered message
22 - open (broach) a new folder
23 - search message headers (where is) command
24 ====*/
27 #include "headers.h"
28 #include "mailcmd.h"
29 #include "status.h"
30 #include "mailview.h"
31 #include "flagmaint.h"
32 #include "listsel.h"
33 #include "keymenu.h"
34 #include "alpine.h"
35 #include "mailpart.h"
36 #include "mailindx.h"
37 #include "folder.h"
38 #include "reply.h"
39 #include "help.h"
40 #include "titlebar.h"
41 #include "signal.h"
42 #include "radio.h"
43 #include "pipe.h"
44 #include "send.h"
45 #include "takeaddr.h"
46 #include "roleconf.h"
47 #include "smime.h"
48 #include "../pith/state.h"
49 #include "../pith/msgno.h"
50 #include "../pith/store.h"
51 #include "../pith/thread.h"
52 #include "../pith/flag.h"
53 #include "../pith/sort.h"
54 #include "../pith/maillist.h"
55 #include "../pith/save.h"
56 #include "../pith/pipe.h"
57 #include "../pith/news.h"
58 #include "../pith/util.h"
59 #include "../pith/sequence.h"
60 #include "../pith/keyword.h"
61 #include "../pith/stream.h"
62 #include "../pith/mailcmd.h"
63 #include "../pith/hist.h"
64 #include "../pith/list.h"
65 #include "../pith/icache.h"
66 #include "../pith/busy.h"
67 #include "../pith/mimedesc.h"
68 #include "../pith/pattern.h"
69 #include "../pith/tempfile.h"
70 #include "../pith/search.h"
71 #include "../pith/margin.h"
72 #ifdef _WINDOWS
73 #include "../pico/osdep/mswin.h"
74 #endif
77 * Internal Prototypes
79 int cmd_flag(struct pine *, MSGNO_S *, int);
80 int cmd_flag_prompt(struct pine *, struct flag_screen *, int);
81 void free_flag_table(struct flag_table **);
82 int cmd_reply(struct pine *, MSGNO_S *, int, ACTION_S *);
83 int cmd_forward(struct pine *, MSGNO_S *, int, ACTION_S *);
84 int cmd_bounce(struct pine *, MSGNO_S *, int, ACTION_S *);
85 int cmd_save(struct pine *, MAILSTREAM *, MSGNO_S *, int, CmdWhere);
86 void role_compose(struct pine *);
87 int cmd_expunge(struct pine *, MAILSTREAM *, MSGNO_S *, int);
88 int cmd_export(struct pine *, MSGNO_S *, int, int);
89 char *cmd_delete_action(struct pine *, MSGNO_S *, CmdWhere);
90 char *cmd_delete_view(struct pine *, MSGNO_S *);
91 char *cmd_delete_index(struct pine *, MSGNO_S *);
92 long get_level(int, UCS, SCROLL_S *);
93 long closest_jump_target(long, MAILSTREAM *, MSGNO_S *, int, CmdWhere, char *, size_t);
94 int update_folder_spec(char *, size_t, char *);
95 int cmd_print(struct pine *, MSGNO_S *, int, CmdWhere);
96 int cmd_pipe(struct pine *, MSGNO_S *, int);
97 STORE_S *list_mgmt_text(RFC2369_S *, long);
98 void list_mgmt_screen(STORE_S *);
99 int aggregate_select(struct pine *, MSGNO_S *, int, CmdWhere);
100 int select_by_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
101 int select_by_thrd_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
102 int select_by_date(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
103 int select_by_text(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
104 int select_by_gm_content(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
105 int select_by_size(MAILSTREAM *, SEARCHSET **);
106 SEARCHSET *visible_searchset(MAILSTREAM *, MSGNO_S *);
107 int select_by_status(MAILSTREAM *, SEARCHSET **);
108 int select_by_rule(MAILSTREAM *, SEARCHSET **);
109 int select_by_thread(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
110 char *choose_a_rule(int, char *);
111 int rulenick_complete(int, char *, int);
112 int select_by_keyword(MAILSTREAM *, SEARCHSET **);
113 char *choose_a_keyword(void);
114 int select_sort(struct pine *, int, SortOrder *, int *);
115 int print_index(struct pine *, MSGNO_S *, int);
118 * List of Select options used by apply_* functions...
120 static char *sel_pmt1 = N_("ALTER message selection : ");
121 ESCKEY_S sel_opts1[] = {
122 /* TRANSLATORS: these are keymenu names for selecting. Broaden selection means
123 we will add more messages to the selection, Narrow selection means we will
124 remove some selections (like a logical AND instead of logical OR), and Flip
125 Selected means that all the messages that are currently selected become unselected,
126 and all the unselected messages become selected. */
127 {'a', 'a', "A", N_("unselect All")},
128 {'c', 'c', "C", NULL},
129 {'b', 'b', "B", N_("Broaden selctn")},
130 {'n', 'n', "N", N_("Narrow selctn")},
131 {'f', 'f', "F", N_("Flip selected")},
132 {'r', 'r', "R", N_("Replace selctn")},
133 {-1, 0, NULL, NULL}
137 #define SEL_OPTS_THREAD 9 /* index number of "tHread" */
138 #define SEL_OPTS_THREAD_CH 'h'
139 #define SEL_OPTS_XGMEXT 10 /* index number of "boX" */
140 #define SEL_OPTS_XGMEXT_CH 'g'
142 char *sel_pmt2 = "SELECT criteria : ";
143 static ESCKEY_S sel_opts2[] = {
144 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
145 means select the currently highlighted message; select by Number is by message
146 number; Status is by status of the message, for example the message might be
147 New or it might be Unseen or marked Important; Size has the Z upper case because
148 it is a Z command; Keyword is an alpine keyword that has been set by the user;
149 and Rule is an alpine rule */
150 {'a', 'a', "A", N_("select All")},
151 {'c', 'c', "C", N_("select Cur")},
152 {'n', 'n', "N", N_("Number")},
153 {'d', 'd', "D", N_("Date")},
154 {'t', 't', "T", N_("Text")},
155 {'s', 's', "S", N_("Status")},
156 {'z', 'z', "Z", N_("siZe")},
157 {'k', 'k', "K", N_("Keyword")},
158 {'r', 'r', "R", N_("Rule")},
159 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
160 {-1, 0, NULL, NULL}
164 static ESCKEY_S sel_opts3[] = {
165 /* TRANSLATORS: these are operations we can do on a set of selected messages.
166 Del is Delete; Undel is Undelete; TakeAddr means to Take some Addresses into
167 the address book; Save means to save the messages into another alpine folder;
168 Export means to copy the messages to a file outside of alpine, external to
169 alpine's world. */
170 {'d', 'd', "D", N_("Del")},
171 {'u', 'u', "U", N_("Undel")},
172 {'r', 'r', "R", N_("Reply")},
173 {'f', 'f', "F", N_("Forward")},
174 {'%', '%', "%", N_("Print")},
175 {'t', 't', "T", N_("TakeAddr")},
176 {'s', 's', "S", N_("Save")},
177 {'e', 'e', "E", N_("Export")},
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},
185 {-1, 0, NULL, NULL}
188 static ESCKEY_S sel_opts4[] = {
189 {'a', 'a', "A", N_("select All")},
190 /* TRANSLATORS: select currently highlighted message Thread */
191 {'c', 'c', "C", N_("select Curthrd")},
192 {'n', 'n', "N", N_("Number")},
193 {'d', 'd', "D", N_("Date")},
194 {'t', 't', "T", N_("Text")},
195 {'s', 's', "S", N_("Status")},
196 {'z', 'z', "Z", N_("siZe")},
197 {'k', 'k', "K", N_("Keyword")},
198 {'r', 'r', "R", N_("Rule")},
199 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
200 {-1, 0, NULL, NULL}
203 static ESCKEY_S sel_opts5[] = {
204 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
205 means select the currently highlighted message; select by Number is by message
206 number; Status is by status of the message, for example the message might be
207 New or it might be Unseen or marked Important; Size has the Z upper case because
208 it is a Z command; Keyword is an alpine keyword that has been set by the user;
209 and Rule is an alpine rule */
210 {'a', 'a', "A", N_("select All")},
211 {'c', 'c', "C", N_("select Cur")},
212 {'n', 'n', "N", N_("Number")},
213 {'d', 'd', "D", N_("Date")},
214 {'t', 't', "T", N_("Text")},
215 {'s', 's', "S", N_("Status")},
216 {'z', 'z', "Z", N_("siZe")},
217 {'k', 'k', "K", N_("Keyword")},
218 {'r', 'r', "R", N_("Rule")},
219 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
220 {SEL_OPTS_XGMEXT_CH, 'g', "G", N_("GmSearch")},
221 {-1, 0, NULL, NULL},
222 {-1, 0, NULL, NULL},
223 {-1, 0, NULL, NULL},
224 {-1, 0, NULL, NULL},
225 {-1, 0, NULL, NULL}
228 static ESCKEY_S sel_opts6[] = {
229 {'a', 'a', "A", N_("select All")},
230 /* TRANSLATORS: select currently highlighted message Thread */
231 {'c', 'c', "C", N_("select Curthrd")},
232 {'n', 'n', "N", N_("Number")},
233 {'d', 'd', "D", N_("Date")},
234 {'t', 't', "T", N_("Text")},
235 {'s', 's', "S", N_("Status")},
236 {'z', 'z', "Z", N_("siZe")},
237 {'k', 'k', "K", N_("Keyword")},
238 {'r', 'r', "R", N_("Rule")},
239 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
240 {SEL_OPTS_XGMEXT_CH, 'g', "G", N_("Gmail")},
241 {-1, 0, NULL, NULL},
242 {-1, 0, NULL, NULL},
243 {-1, 0, NULL, NULL},
244 {-1, 0, NULL, NULL},
245 {-1, 0, NULL, NULL}
249 static char *sel_flag =
250 N_("Select New, Deleted, Answered, Forwarded, or Important messages ? ");
251 static char *sel_flag_not =
252 N_("Select NOT New, NOT Deleted, NOT Answered, NOT Forwarded or NOT Important msgs ? ");
253 static ESCKEY_S sel_flag_opt[] = {
254 /* TRANSLATORS: When selecting messages by message Status these are the
255 different types of Status you can select on. Is the message New, Recent,
256 and so on. Not means flip the meaning of the selection to the opposite
257 thing, so message is not New or not Important. */
258 {'n', 'n', "N", N_("New")},
259 {'*', '*', "*", N_("Important")},
260 {'d', 'd', "D", N_("Deleted")},
261 {'a', 'a', "A", N_("Answered")},
262 {'f', 'f', "F", N_("Forwarded")},
263 {-2, 0, NULL, NULL},
264 {'!', '!', "!", N_("Not")},
265 {-2, 0, NULL, NULL},
266 {'r', 'r', "R", N_("Recent")},
267 {'u', 'u', "U", N_("Unseen")},
268 {-1, 0, NULL, NULL}
272 static ESCKEY_S sel_date_opt[] = {
273 {0, 0, NULL, NULL},
274 /* TRANSLATORS: options when selecting messages by Date */
275 {ctrl('P'), 12, "^P", N_("Prev Day")},
276 {ctrl('N'), 13, "^N", N_("Next Day")},
277 {ctrl('X'), 11, "^X", N_("Cur Msg")},
278 {ctrl('W'), 14, "^W", N_("Toggle When")},
279 {KEY_UP, 12, "", ""},
280 {KEY_DOWN, 13, "", ""},
281 {-1, 0, NULL, NULL}
285 static char *sel_x_gm_ext =
286 N_("Search: ");
287 static char *sel_text =
288 N_("Select based on To, From, Cc, Recip, Partic, Subject fields or All msg text ? ");
289 static char *sel_text_not =
290 N_("Select based on NOT To, From, Cc, Recip, Partic, Subject or All msg text ? ");
291 static ESCKEY_S sel_text_opt[] = {
292 /* TRANSLATORS: Select messages based on the text contained in the From line, or
293 the Subject line, and so on. */
294 {'f', 'f', "F", N_("From")},
295 {'s', 's', "S", N_("Subject")},
296 {'t', 't', "T", N_("To")},
297 {'a', 'a', "A", N_("All Text")},
298 {'c', 'c', "C", N_("Cc")},
299 {'!', '!', "!", N_("Not")},
300 {'r', 'r', "R", N_("Recipient")},
301 {'p', 'p', "P", N_("Participant")},
302 {'b', 'b', "B", N_("Body")},
303 {'h', 'h', "H", N_("Header")},
304 {-1, 0, NULL, NULL}
307 static ESCKEY_S choose_action[] = {
308 {'c', 'c', "C", N_("Compose")},
309 {'r', 'r', "R", N_("Reply")},
310 {'f', 'f', "F", N_("Forward")},
311 {'b', 'b', "B", N_("Bounce")},
312 {-1, 0, NULL, NULL}
315 static char *select_num =
316 N_("Enter comma-delimited list of numbers (dash between ranges): ");
318 static char *select_size_larger_msg =
319 N_("Select messages with size larger than: ");
321 static char *select_size_smaller_msg =
322 N_("Select messages with size smaller than: ");
324 static char *sel_size_larger = N_("Larger");
325 static char *sel_size_smaller = N_("Smaller");
326 static ESCKEY_S sel_size_opt[] = {
327 {0, 0, NULL, NULL},
328 {ctrl('W'), 14, "^W", NULL},
329 {-1, 0, NULL, NULL}
332 static ESCKEY_S sel_rule_opt[] = {
333 {0, 0, NULL, NULL},
334 {ctrl('T'), 14, "^T", N_("To List")},
335 {0, 0, NULL, NULL}, /* Reserved for TAB completion */
336 {'!', '!', "!", N_("Not")},
337 {-1, 0, NULL, NULL}
340 static ESCKEY_S sel_key_opt[] = {
341 {0, 0, NULL, NULL},
342 {ctrl('T'), 14, "^T", N_("To List")},
343 {0, 0, NULL, NULL},
344 {'!', '!', "!", N_("Not")},
345 {-1, 0, NULL, NULL}
348 static ESCKEY_S flag_text_opt[] = {
349 /* TRANSLATORS: these are types of flags (markers) that the user can
350 set. For example, they can flag the message as an important message. */
351 {'n', 'n', "N", N_("New")},
352 {'*', '*', "*", N_("Important")},
353 {'d', 'd', "D", N_("Deleted")},
354 {'a', 'a', "A", N_("Answered")},
355 {'f', 'f', "F", N_("Forwarded")},
356 {'!', '!', "!", N_("Not")},
357 {ctrl('T'), 10, "^T", N_("To Flag Details")},
358 {-1, 0, NULL, NULL}
362 alpine_smime_confirm_save(char *email)
364 char prompt[128];
366 snprintf(prompt, sizeof(prompt), _("Save certificate for <%s>"),
367 email ? email : _("missing address"));
368 return want_to(prompt, 'n', 'x', NO_HELP, WT_NORM) == 'y';
371 int
372 alpine_get_password(char *prompt, char *pass, size_t len)
374 int flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD;
375 int rv;
376 flags |= OE_DISALLOW_HELP;
377 pass[0] = '\0';
378 rv = optionally_enter(pass,
379 -(ps_global->ttyo ? FOOTER_ROWS(ps_global) : 3),
380 0, len, prompt, NULL, NO_HELP, &flags);
381 if(rv == 1) ps_global->user_says_cancel = 1;
382 return rv;
386 smime_import_certificate(char *filename, char *full_filename, char *what, size_t len)
388 int r = 1;
389 static HISTORY_S *history = NULL;
390 static ESCKEY_S eopts[] = {
391 {ctrl('T'), 10, "^T", N_("To Files")},
392 {-1, 0, NULL, NULL},
393 {-1, 0, NULL, NULL}};
395 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
396 eopts[r].ch = ctrl('I');
397 eopts[r].rval = 11;
398 eopts[r].name = "TAB";
399 eopts[r].label = N_("Complete");
402 eopts[++r].ch = -1;
404 filename[0] = '\0';
405 full_filename[0] = '\0';
407 r = get_export_filename(ps_global, filename, NULL, full_filename,
408 len, what, "IMPORT", eopts, NULL,
409 -FOOTER_ROWS(ps_global), GE_IS_IMPORT, &history);
411 return r;
415 /*----------------------------------------------------------------------
416 The giant switch on the commands for index and viewing
418 Input: command -- The command char/code
419 in_index -- flag indicating command is from index
420 orig_command -- The original command typed before pre-processing
421 Output: force_mailchk -- Set to tell caller to force call to new_mail().
423 Result: Manifold
425 Returns 1 if the message number or attachment to show changed
426 ---*/
428 process_cmd(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
429 int command, CmdWhere in_index, int *force_mailchk)
431 int question_line, a_changed, flags = 0, ret, j;
432 int notrealinbox;
433 long new_msgno, del_count, old_msgno, i;
434 long start;
435 char *newfolder, prompt[MAX_SCREEN_COLS+1];
436 CONTEXT_S *tc;
437 MESSAGECACHE *mc;
438 #if defined(DOS) && !defined(_WINDOWS)
439 extern long coreleft();
440 #endif
442 dprint((4, "\n - process_cmd(cmd=%d) -\n", command));
444 question_line = -FOOTER_ROWS(state);
445 state->mangled_screen = 0;
446 state->mangled_footer = 0;
447 state->mangled_header = 0;
448 state->next_screen = SCREEN_FUN_NULL;
449 old_msgno = mn_get_cur(msgmap);
450 a_changed = FALSE;
451 *force_mailchk = 0;
453 switch (command) {
454 /*------------- Help --------*/
455 case MC_HELP :
457 * We're not using the h_mail_view portion of this right now because
458 * that call is being handled in scrolltool() before it gets
459 * here. Leave it in case we change how it works.
461 helper((in_index == MsgIndx)
462 ? h_mail_index
463 : (in_index == View)
464 ? h_mail_view
465 : h_mail_thread_index,
466 (in_index == MsgIndx)
467 ? _("HELP FOR MESSAGE INDEX")
468 : (in_index == View)
469 ? _("HELP FOR MESSAGE TEXT")
470 : _("HELP FOR THREAD INDEX"),
471 HLPD_NONE);
472 dprint((4,"MAIL_CMD: did help command\n"));
473 state->mangled_screen = 1;
474 break;
477 /*--------- Return to main menu ------------*/
478 case MC_MAIN :
479 state->next_screen = main_menu_screen;
480 dprint((2,"MAIL_CMD: going back to main menu\n"));
481 break;
484 /*------- View message text --------*/
485 case MC_VIEW_TEXT :
486 view_text:
487 if(any_messages(msgmap, NULL, "to View")){
488 state->next_screen = mail_view_screen;
491 break;
494 /*------- View attachment --------*/
495 case MC_VIEW_ATCH :
496 state->next_screen = attachment_screen;
497 dprint((2,"MAIL_CMD: going to attachment screen\n"));
498 break;
501 /*---------- Previous message ----------*/
502 case MC_PREVITEM :
503 if(any_messages(msgmap, NULL, NULL)){
504 if((i = mn_get_cur(msgmap)) > 1L){
505 mn_dec_cur(stream, msgmap,
506 (in_index == View && THREADING()
507 && sp_viewing_a_thread(stream))
508 ? MH_THISTHD
509 : (in_index == View)
510 ? MH_ANYTHD : MH_NONE);
511 if(i == mn_get_cur(msgmap)){
512 PINETHRD_S *thrd = NULL, *topthrd = NULL;
514 if(THRD_INDX_ENABLED()){
515 mn_dec_cur(stream, msgmap, MH_ANYTHD);
516 if(i == mn_get_cur(msgmap))
517 q_status_message1(SM_ORDER, 0, 2,
518 _("Already on first %s in Zoomed Index"),
519 THRD_INDX() ? _("thread") : _("message"));
520 else{
521 if(in_index == View
522 || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
523 ret = 'y';
524 else
525 ret = want_to(_("View previous thread"), 'y', 'x',
526 NO_HELP, WT_NORM);
528 if(ret == 'y'){
529 q_status_message(SM_ORDER, 0, 2,
530 _("Viewing previous thread"));
531 new_msgno = mn_get_cur(msgmap);
532 mn_set_cur(msgmap, i);
533 if(unview_thread(state, stream, msgmap)){
534 state->next_screen = mail_index_screen;
535 state->view_skipped_index = 0;
536 state->mangled_screen = 1;
539 mn_set_cur(msgmap, new_msgno);
540 if(THRD_AUTO_VIEW() && in_index == View){
542 thrd = fetch_thread(stream,
543 mn_m2raw(msgmap,
544 new_msgno));
545 if(count_lflags_in_thread(stream, thrd,
546 msgmap,
547 MN_NONE) == 1){
548 if(view_thread(state, stream, msgmap, 1)){
549 if(current_index_state)
550 msgmap->top_after_thrd = current_index_state->msg_at_top;
552 state->view_skipped_index = 1;
553 command = MC_VIEW_TEXT;
554 goto view_text;
559 j = 0;
560 if(THRD_AUTO_VIEW() && in_index != View){
561 thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
562 if(thrd && thrd->top)
563 topthrd = fetch_thread(stream, thrd->top);
565 if(topthrd)
566 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
569 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
570 if(view_thread(state, stream, msgmap, 1)
571 && current_index_state)
572 msgmap->top_after_thrd = current_index_state->msg_at_top;
576 state->next_screen = SCREEN_FUN_NULL;
578 else
579 mn_set_cur(msgmap, i); /* put it back */
582 else
583 q_status_message1(SM_ORDER, 0, 2,
584 _("Already on first %s in Zoomed Index"),
585 THRD_INDX() ? _("thread") : _("message"));
588 else{
589 time_t now;
591 if(!IS_NEWS(stream)
592 && ((now = time(0)) > state->last_nextitem_forcechk)){
593 *force_mailchk = 1;
594 /* check at most once a second */
595 state->last_nextitem_forcechk = now;
598 q_status_message1(SM_ORDER, 0, 1, _("Already on first %s"),
599 THRD_INDX() ? _("thread") : _("message"));
603 break;
606 /*---------- Next Message ----------*/
607 case MC_NEXTITEM :
608 if(mn_get_total(msgmap) > 0L
609 && ((i = mn_get_cur(msgmap)) < mn_get_total(msgmap))){
610 mn_inc_cur(stream, msgmap,
611 (in_index == View && THREADING()
612 && sp_viewing_a_thread(stream))
613 ? MH_THISTHD
614 : (in_index == View)
615 ? MH_ANYTHD : MH_NONE);
616 if(i == mn_get_cur(msgmap)){
617 PINETHRD_S *thrd, *topthrd;
619 if(THRD_INDX_ENABLED()){
620 if(!THRD_INDX())
621 mn_inc_cur(stream, msgmap, MH_ANYTHD);
623 if(i == mn_get_cur(msgmap)){
624 if(any_lflagged(msgmap, MN_HIDE))
625 any_messages(NULL, "more", "in Zoomed Index");
626 else
627 goto nfolder;
629 else{
630 if(in_index == View
631 || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
632 ret = 'y';
633 else
634 ret = want_to(_("View next thread"), 'y', 'x',
635 NO_HELP, WT_NORM);
637 if(ret == 'y'){
638 q_status_message(SM_ORDER, 0, 2,
639 _("Viewing next thread"));
640 new_msgno = mn_get_cur(msgmap);
641 mn_set_cur(msgmap, i);
642 if(unview_thread(state, stream, msgmap)){
643 state->next_screen = mail_index_screen;
644 state->view_skipped_index = 0;
645 state->mangled_screen = 1;
648 mn_set_cur(msgmap, new_msgno);
649 if(THRD_AUTO_VIEW() && in_index == View){
651 thrd = fetch_thread(stream,
652 mn_m2raw(msgmap,
653 new_msgno));
654 if(count_lflags_in_thread(stream, thrd,
655 msgmap,
656 MN_NONE) == 1){
657 if(view_thread(state, stream, msgmap, 1)){
658 if(current_index_state)
659 msgmap->top_after_thrd = current_index_state->msg_at_top;
661 state->view_skipped_index = 1;
662 command = MC_VIEW_TEXT;
663 goto view_text;
668 j = 0;
669 if(THRD_AUTO_VIEW() && in_index != View){
670 thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
671 if(thrd && thrd->top)
672 topthrd = fetch_thread(stream, thrd->top);
673 else
674 topthrd = NULL;
676 if(topthrd)
677 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
680 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
681 if(view_thread(state, stream, msgmap, 1)
682 && current_index_state)
683 msgmap->top_after_thrd = current_index_state->msg_at_top;
687 state->next_screen = SCREEN_FUN_NULL;
689 else
690 mn_set_cur(msgmap, i); /* put it back */
693 else if(THREADING()
694 && (thrd = fetch_thread(stream, mn_m2raw(msgmap, i)))
695 && thrd->next
696 && get_lflag(stream, NULL, thrd->rawno, MN_COLL)){
697 q_status_message(SM_ORDER, 0, 2,
698 _("Expand collapsed thread to see more messages"));
700 else
701 any_messages(NULL, "more", "in Zoomed Index");
704 else{
705 time_t now;
706 nfolder:
707 prompt[0] = '\0';
708 if(IS_NEWS(stream)
709 || (state->context_current->use & CNTXT_INCMNG)){
710 char nextfolder[MAXPATH];
712 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
713 nextfolder[sizeof(nextfolder)-1] = '\0';
714 if(next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
715 state->context_current, NULL, NULL))
716 strncpy(prompt, _(". Press TAB for next folder."),
717 sizeof(prompt));
718 else
719 strncpy(prompt, _(". No more folders to TAB to."),
720 sizeof(prompt));
722 prompt[sizeof(prompt)-1] = '\0';
725 any_messages(NULL, (mn_get_total(msgmap) > 0L) ? "more" : NULL,
726 prompt[0] ? prompt : NULL);
728 if(!IS_NEWS(stream)
729 && ((now = time(0)) > state->last_nextitem_forcechk)){
730 *force_mailchk = 1;
731 /* check at most once a second */
732 state->last_nextitem_forcechk = now;
736 break;
739 /*---------- Delete message ----------*/
740 case MC_DELETE :
741 (void) cmd_delete(state, msgmap, MCMD_NONE,
742 (in_index == View) ? cmd_delete_view : cmd_delete_index);
743 break;
746 /*---------- Undelete message ----------*/
747 case MC_UNDELETE :
748 (void) cmd_undelete(state, msgmap, MCMD_NONE);
749 update_titlebar_status();
750 break;
753 /*---------- Reply to message ----------*/
754 case MC_REPLY :
755 (void) cmd_reply(state, msgmap, MCMD_NONE, NULL);
756 break;
759 /*---------- Forward message ----------*/
760 case MC_FORWARD :
761 (void) cmd_forward(state, msgmap, MCMD_NONE, NULL);
762 break;
765 /*---------- Quit pine ------------*/
766 case MC_QUIT :
767 state->next_screen = quit_screen;
768 dprint((1,"MAIL_CMD: quit\n"));
769 break;
772 /*---------- Compose message ----------*/
773 case MC_COMPOSE :
774 state->prev_screen = (in_index == View) ? mail_view_screen
775 : mail_index_screen;
776 compose_screen(state);
777 state->mangled_screen = 1;
778 if (state->next_screen)
779 a_changed = TRUE;
780 break;
783 /*---------- Alt Compose message ----------*/
784 case MC_ROLE :
785 state->prev_screen = (in_index == View) ? mail_view_screen
786 : mail_index_screen;
787 role_compose(state);
788 if(state->next_screen)
789 a_changed = TRUE;
791 break;
794 /*--------- Folders menu ------------*/
795 case MC_FOLDERS :
796 state->start_in_context = 1;
798 /*--------- Top of Folders list menu ------------*/
799 case MC_COLLECTIONS :
800 state->next_screen = folder_screen;
801 dprint((2,"MAIL_CMD: going to folder/collection menu\n"));
802 break;
805 /*---------- Open specific new folder ----------*/
806 case MC_GOTO :
807 tc = (state->context_last && !NEWS_TEST(state->context_current))
808 ? state->context_last : state->context_current;
810 newfolder = broach_folder(question_line, 1, &notrealinbox, &tc);
811 if(newfolder){
812 visit_folder(state, newfolder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT);
813 a_changed = TRUE;
816 break;
819 /*------- Go to Index Screen ----------*/
820 case MC_INDEX :
821 state->next_screen = mail_index_screen;
822 break;
824 /*------- Skip to next interesting message -----------*/
825 case MC_TAB :
826 if(THRD_INDX()){
827 PINETHRD_S *thrd;
830 * If we're in the thread index, start looking after this
831 * thread. We don't want to match something in the current
832 * thread.
834 start = mn_get_cur(msgmap);
835 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
836 if(mn_get_revsort(msgmap)){
837 /* if reversed, top of thread is last one before next thread */
838 if(thrd && thrd->top)
839 start = mn_raw2m(msgmap, thrd->top);
841 else{
842 /* last msg of thread is at the ends of the branches/nexts */
843 while(thrd){
844 start = mn_raw2m(msgmap, thrd->rawno);
845 if(thrd->branch)
846 thrd = fetch_thread(stream, thrd->branch);
847 else if(thrd->next)
848 thrd = fetch_thread(stream, thrd->next);
849 else
850 thrd = NULL;
855 * Flags is 0 in this case because we want to not skip
856 * messages inside of threads so that we can find threads
857 * which have some unseen messages even though the top-level
858 * of the thread is already seen.
859 * If new_msgno ends up being a message which is not visible
860 * because it isn't at the top-level, the current message #
861 * will be adjusted below in adjust_cur.
863 flags = 0;
864 new_msgno = next_sorted_flagged((F_UNDEL
865 | F_UNSEEN
866 | ((F_ON(F_TAB_TO_NEW,state))
867 ? 0 : F_OR_FLAG)),
868 stream, start, &flags);
870 else if(THREADING() && sp_viewing_a_thread(stream)){
871 PINETHRD_S *thrd, *topthrd = NULL;
873 start = mn_get_cur(msgmap);
876 * Things are especially complicated when we're viewing_a_thread
877 * from the thread index. First we have to check within the
878 * current thread for a new message. If none is found, then
879 * we search in the next threads and offer to continue in
880 * them. Then we offer to go to the next folder.
882 flags = NSF_SKIP_CHID;
883 new_msgno = next_sorted_flagged((F_UNDEL
884 | F_UNSEEN
885 | ((F_ON(F_TAB_TO_NEW,state))
886 ? 0 : F_OR_FLAG)),
887 stream, start, &flags);
889 * If we found a match then we are done, that is another message
890 * in the current thread index. Otherwise, we have to look
891 * further.
893 if(!(flags & NSF_FLAG_MATCH)){
894 ret = 'n';
895 while(1){
897 flags = 0;
898 new_msgno = next_sorted_flagged((F_UNDEL
899 | F_UNSEEN
900 | ((F_ON(F_TAB_TO_NEW,
901 state))
902 ? 0 : F_OR_FLAG)),
903 stream, start, &flags);
905 * If we got a match, new_msgno is a message in
906 * a different thread from the one we are viewing.
908 if(flags & NSF_FLAG_MATCH){
909 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
910 if(thrd && thrd->top)
911 topthrd = fetch_thread(stream, thrd->top);
913 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
914 static ESCKEY_S next_opt[] = {
915 {'y', 'y', "Y", N_("Yes")},
916 {'n', 'n', "N", N_("No")},
917 {TAB, 'n', "Tab", N_("NextNew")},
918 {-1, 0, NULL, NULL}
921 if(in_index)
922 snprintf(prompt, sizeof(prompt), _("View thread number %s? "),
923 topthrd ? comatose(topthrd->thrdno) : "?");
924 else
925 snprintf(prompt, sizeof(prompt),
926 _("View message in thread number %s? "),
927 topthrd ? comatose(topthrd->thrdno) : "?");
929 prompt[sizeof(prompt)-1] = '\0';
931 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
932 next_opt, 'y', 'x', NO_HELP,
933 RB_NORM);
934 if(ret == 'x'){
935 cmd_cancelled(NULL);
936 goto get_out;
939 else
940 ret = 'y';
942 if(ret == 'y'){
943 if(unview_thread(state, stream, msgmap)){
944 state->next_screen = mail_index_screen;
945 state->view_skipped_index = 0;
946 state->mangled_screen = 1;
949 mn_set_cur(msgmap, new_msgno);
950 if(THRD_AUTO_VIEW()){
952 if(count_lflags_in_thread(stream, topthrd,
953 msgmap, MN_NONE) == 1){
954 if(view_thread(state, stream, msgmap, 1)){
955 if(current_index_state)
956 msgmap->top_after_thrd = current_index_state->msg_at_top;
958 state->view_skipped_index = 1;
959 command = MC_VIEW_TEXT;
960 goto view_text;
965 if(view_thread(state, stream, msgmap, 1) && current_index_state)
966 msgmap->top_after_thrd = current_index_state->msg_at_top;
968 state->next_screen = SCREEN_FUN_NULL;
969 break;
971 else if(ret == 'n' && topthrd){
973 * skip to end of this thread and look starting
974 * in the next thread.
976 if(mn_get_revsort(msgmap)){
978 * if reversed, top of thread is last one
979 * before next thread
981 start = mn_raw2m(msgmap, topthrd->rawno);
983 else{
985 * last msg of thread is at the ends of
986 * the branches/nexts
988 thrd = topthrd;
989 while(thrd){
990 start = mn_raw2m(msgmap, thrd->rawno);
991 if(thrd->branch)
992 thrd = fetch_thread(stream, thrd->branch);
993 else if(thrd->next)
994 thrd = fetch_thread(stream, thrd->next);
995 else
996 thrd = NULL;
1000 else if(ret == 'n')
1001 break;
1003 else
1004 break;
1008 else{
1010 start = mn_get_cur(msgmap);
1013 * If we are on a collapsed thread, start looking after the
1014 * collapsed part, unless we are viewing the message.
1016 if(THREADING() && in_index != View){
1017 PINETHRD_S *thrd;
1018 long rawno;
1019 int collapsed;
1021 rawno = mn_m2raw(msgmap, start);
1022 thrd = fetch_thread(stream, rawno);
1023 collapsed = thrd && thrd->next
1024 && get_lflag(stream, NULL, rawno, MN_COLL);
1026 if(collapsed){
1027 if(mn_get_revsort(msgmap)){
1028 if(thrd && thrd->top)
1029 start = mn_raw2m(msgmap, thrd->top);
1031 else{
1032 while(thrd){
1033 start = mn_raw2m(msgmap, thrd->rawno);
1034 if(thrd->branch)
1035 thrd = fetch_thread(stream, thrd->branch);
1036 else if(thrd->next)
1037 thrd = fetch_thread(stream, thrd->next);
1038 else
1039 thrd = NULL;
1046 new_msgno = next_sorted_flagged((F_UNDEL
1047 | F_UNSEEN
1048 | ((F_ON(F_TAB_TO_NEW,state))
1049 ? 0 : F_OR_FLAG)),
1050 stream, start, &flags);
1054 * If there weren't any unread messages left, OR there
1055 * aren't any messages at all, we may want to offer to
1056 * go on to the next folder...
1058 if(flags & NSF_FLAG_MATCH){
1059 mn_set_cur(msgmap, new_msgno);
1060 if(in_index != View)
1061 adjust_cur_to_visible(stream, msgmap);
1063 else{
1064 int in_inbox = sp_flagged(stream, SP_INBOX);
1066 if(state->context_current
1067 && ((NEWS_TEST(state->context_current)
1068 && context_isambig(state->cur_folder))
1069 || ((state->context_current->use & CNTXT_INCMNG)
1070 && (in_inbox
1071 || folder_index(state->cur_folder,
1072 state->context_current,
1073 FI_FOLDER) >= 0)))){
1074 char nextfolder[MAXPATH];
1075 MAILSTREAM *nextstream = NULL;
1076 long recent_cnt;
1077 int did_cancel = 0;
1079 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
1080 nextfolder[sizeof(nextfolder)-1] = '\0';
1081 while(1){
1082 if(!(next_folder(&nextstream, nextfolder, sizeof(nextfolder), nextfolder,
1083 state->context_current, &recent_cnt,
1084 F_ON(F_TAB_NO_CONFIRM,state)
1085 ? NULL : &did_cancel))){
1086 if(!in_inbox){
1087 static ESCKEY_S inbox_opt[] = {
1088 {'y', 'y', "Y", N_("Yes")},
1089 {'n', 'n', "N", N_("No")},
1090 {TAB, 'z', "Tab", N_("To Inbox")},
1091 {-1, 0, NULL, NULL}
1094 if(F_ON(F_RET_INBOX_NO_CONFIRM,state))
1095 ret = 'y';
1096 else{
1097 /* TRANSLATORS: this is a question, with some information followed
1098 by Return to INBOX? */
1099 if(state->context_current->use&CNTXT_INCMNG)
1100 snprintf(prompt, sizeof(prompt), _("No more incoming folders. Return to \"%s\"? "), state->inbox_name);
1101 else
1102 snprintf(prompt, sizeof(prompt), _("No more news groups. Return to \"%s\"? "), state->inbox_name);
1104 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1105 inbox_opt, 'y', 'x',
1106 NO_HELP, RB_NORM);
1110 * 'z' is a synonym for 'y'. It is not 'y'
1111 * so that it isn't displayed as a default
1112 * action with square-brackets around it
1113 * in the keymenu...
1115 if(ret == 'y' || ret == 'z'){
1116 visit_folder(state, state->inbox_name,
1117 state->context_current,
1118 NULL, DB_INBOXWOCNTXT);
1119 a_changed = TRUE;
1122 else if (did_cancel)
1123 cmd_cancelled(NULL);
1124 else{
1125 if(state->context_current->use&CNTXT_INCMNG)
1126 q_status_message(SM_ORDER, 0, 2, _("No more incoming folders"));
1127 else
1128 q_status_message(SM_ORDER, 0, 2, _("No more news groups"));
1131 break;
1135 #define CNTLEN 80
1136 char *front, type[80], cnt[CNTLEN], fbuf[MAX_SCREEN_COLS/2+1];
1137 int rbspace, avail, need, take_back;
1140 * View_next_
1141 * Incoming_folder_ or news_group_ or folder_ or group_
1142 * "foldername"
1143 * _(13 recent) or _(some recent) or nothing
1144 * ?_
1146 front = "View next";
1147 strncpy(type,
1148 (state->context_current->use & CNTXT_INCMNG)
1149 ? "Incoming folder" : "news group",
1150 sizeof(type));
1151 type[sizeof(type)-1] = '\0';
1152 snprintf(cnt, sizeof(cnt), " (%.*s %s)", CNTLEN-20,
1153 recent_cnt ? long2string(recent_cnt) : "some",
1154 F_ON(F_TAB_USES_UNSEEN, ps_global)
1155 ? "unseen" : "recent");
1156 cnt[sizeof(cnt)-1] = '\0';
1159 * Space reserved for radio_buttons call.
1160 * If we make this 3 then radio_buttons won't mess
1161 * with the prompt. If we make it 2, then we get
1162 * one more character to use but radio_buttons will
1163 * cut off the last character of our prompt, which is
1164 * ok because it is a space.
1166 rbspace = 2;
1167 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols
1168 : 80;
1169 need = strlen(front)+1 + strlen(type)+1 +
1170 + strlen(nextfolder)+2 + strlen(cnt) +
1171 2 + rbspace;
1172 if(avail < need){
1173 take_back = strlen(type);
1174 strncpy(type,
1175 (state->context_current->use & CNTXT_INCMNG)
1176 ? "folder" : "group", sizeof(type));
1177 take_back -= strlen(type);
1178 need -= take_back;
1179 if(avail < need){
1180 need -= strlen(cnt);
1181 cnt[0] = '\0';
1184 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1185 snprintf(prompt, sizeof(prompt), "%.*s %.*s \"%.*s\"%.*s? ",
1186 (MAX_SCREEN_COLS+1)/8, front,
1187 (MAX_SCREEN_COLS+1)/8, type,
1188 (MAX_SCREEN_COLS+1)/2,
1189 short_str(nextfolder, fbuf, sizeof(fbuf),
1190 strlen(nextfolder) -
1191 ((need>avail) ? (need-avail) : 0),
1192 MidDots),
1193 (MAX_SCREEN_COLS+1)/8, cnt);
1194 prompt[sizeof(prompt)-1] = '\0';
1198 * When help gets added, this'll have to become
1199 * a loop like the rest...
1201 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
1202 static ESCKEY_S next_opt[] = {
1203 {'y', 'y', "Y", N_("Yes")},
1204 {'n', 'n', "N", N_("No")},
1205 {TAB, 'n', "Tab", N_("NextNew")},
1206 {-1, 0, NULL, NULL}
1209 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1210 next_opt, 'y', 'x', NO_HELP,
1211 RB_NORM);
1212 if(ret == 'x'){
1213 cmd_cancelled(NULL);
1214 break;
1217 else
1218 ret = 'y';
1220 if(ret == 'y'){
1221 if(nextstream && sp_dead_stream(nextstream))
1222 nextstream = NULL;
1224 visit_folder(state, nextfolder,
1225 state->context_current, nextstream,
1226 DB_FROMTAB);
1227 /* visit_folder takes care of nextstream */
1228 nextstream = NULL;
1229 a_changed = TRUE;
1230 break;
1234 if(nextstream)
1235 pine_mail_close(nextstream);
1237 else
1238 any_messages(NULL,
1239 (mn_get_total(msgmap) > 0L)
1240 ? IS_NEWS(stream) ? "more undeleted" : "more new"
1241 : NULL,
1242 NULL);
1245 get_out:
1247 break;
1250 /*------- Zoom -----------*/
1251 case MC_ZOOM :
1253 * Right now the way zoom is implemented is sort of silly.
1254 * There are two per-message flags where just one and a
1255 * global "zoom mode" flag to suppress messages from the index
1256 * should suffice.
1258 if(any_messages(msgmap, NULL, "to Zoom on")){
1259 if(unzoom_index(state, stream, msgmap)){
1260 dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
1261 q_status_message(SM_ORDER,0,2, _("Index Zoom Mode is now off"));
1263 else if((i = zoom_index(state, stream, msgmap, MN_SLCT)) != 0){
1264 if(any_lflagged(msgmap, MN_HIDE)){
1265 dprint((4,"\n\n ---- Entering ZOOM mode ----\n"));
1266 q_status_message4(SM_ORDER, 0, 2,
1267 _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"),
1268 THRD_INDX() ? "" : comatose(i),
1269 THRD_INDX() ? "" : " ",
1270 THRD_INDX() ? _("threads") : _("message"),
1271 THRD_INDX() ? "" : plural(i));
1273 else
1274 q_status_message(SM_ORDER, 0, 2,
1275 _("All messages selected, so not entering Index Zoom Mode"));
1277 else
1278 any_messages(NULL, "selected", "to Zoom on");
1281 break;
1284 /*---------- print message on paper ----------*/
1285 case MC_PRINTMSG :
1286 if(any_messages(msgmap, NULL, "to print"))
1287 (void) cmd_print(state, msgmap, MCMD_NONE, in_index);
1289 break;
1292 /*---------- Take Address ----------*/
1293 case MC_TAKE :
1294 if(F_ON(F_ENABLE_ROLE_TAKE, state) ||
1295 any_messages(msgmap, NULL, "to Take address from"))
1296 (void) cmd_take_addr(state, msgmap, MCMD_NONE);
1298 break;
1301 /*---------- Save Message ----------*/
1302 case MC_SAVE :
1303 if(any_messages(msgmap, NULL, "to Save"))
1304 (void) cmd_save(state, stream, msgmap, MCMD_NONE, in_index);
1306 break;
1309 /*---------- Export message ----------*/
1310 case MC_EXPORT :
1311 if(any_messages(msgmap, NULL, "to Export")){
1312 (void) cmd_export(state, msgmap, question_line, MCMD_NONE);
1313 state->mangled_footer = 1;
1316 break;
1319 /*---------- Expunge ----------*/
1320 case MC_EXPUNGE :
1321 (void) cmd_expunge(state, stream, msgmap, MCMD_NONE);
1322 break;
1325 /*------- Unexclude -----------*/
1326 case MC_UNEXCLUDE :
1327 if(!(IS_NEWS(stream) && stream->rdonly)){
1328 q_status_message(SM_ORDER, 0, 3,
1329 _("Unexclude not available for mail folders"));
1331 else if(any_lflagged(msgmap, MN_EXLD)){
1332 SEARCHPGM *pgm;
1333 long i;
1334 int exbits;
1337 * Since excluded means "hidden deleted" and "killed",
1338 * the count should reflect the former.
1340 pgm = mail_newsearchpgm();
1341 pgm->deleted = 1;
1342 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
1343 for(i = 1L, del_count = 0L; i <= stream->nmsgs; i++)
1344 if((mc = mail_elt(stream, i)) && mc->searched
1345 && get_lflag(stream, NULL, i, MN_EXLD)
1346 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
1347 && (exbits & MSG_EX_FILTERED)))
1348 del_count++;
1350 if(del_count > 0L){
1351 state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1352 snprintf(prompt, sizeof(prompt), "UNexclude %ld message%s in %.*s", del_count,
1353 plural(del_count), MAX_SCREEN_COLS+1-45,
1354 pretty_fn(state->cur_folder));
1355 prompt[sizeof(prompt)-1] = '\0';
1356 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
1357 || (F_ON(F_AUTO_EXPUNGE, state)
1358 && (state->context_current
1359 && (state->context_current->use & CNTXT_INCMNG))
1360 && context_isambig(state->cur_folder))
1361 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
1362 long save_cur_rawno;
1363 int were_viewing_a_thread;
1365 save_cur_rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
1366 were_viewing_a_thread = (THREADING()
1367 && sp_viewing_a_thread(stream));
1369 if(msgno_include(stream, msgmap, MI_NONE)){
1370 clear_index_cache(stream, 0);
1372 if(stream && stream->spare)
1373 erase_threading_info(stream, msgmap);
1375 refresh_sort(stream, msgmap, SRT_NON);
1378 if(were_viewing_a_thread){
1379 if(save_cur_rawno > 0L)
1380 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1382 if(view_thread(state, stream, msgmap, 1) && current_index_state)
1383 msgmap->top_after_thrd = current_index_state->msg_at_top;
1386 if(save_cur_rawno > 0L)
1387 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1389 state->mangled_screen = 1;
1390 q_status_message2(SM_ORDER, 0, 4,
1391 "%s message%s UNexcluded",
1392 long2string(del_count),
1393 plural(del_count));
1395 if(in_index != View)
1396 adjust_cur_to_visible(stream, msgmap);
1398 else
1399 any_messages(NULL, NULL, "UNexcluded");
1401 else
1402 any_messages(NULL, "excluded", "to UNexclude");
1404 else
1405 any_messages(NULL, "excluded", "to UNexclude");
1407 break;
1410 /*------- Make Selection -----------*/
1411 case MC_SELECT :
1412 if(any_messages(msgmap, NULL, "to Select")){
1413 if(aggregate_select(state, msgmap, question_line, in_index) == 0
1414 && (in_index == MsgIndx || in_index == ThrdIndx)
1415 && F_ON(F_AUTO_ZOOM, state)
1416 && any_lflagged(msgmap, MN_SLCT) > 0L
1417 && !any_lflagged(msgmap, MN_HIDE))
1418 (void) zoom_index(state, stream, msgmap, MN_SLCT);
1421 break;
1424 /*------- Toggle Current Message Selection State -----------*/
1425 case MC_SELCUR :
1426 if(any_messages(msgmap, NULL, NULL)){
1427 if((select_by_current(state, msgmap, in_index)
1428 || (F_OFF(F_UNSELECT_WONT_ADVANCE, state)
1429 && !any_lflagged(msgmap, MN_HIDE)))
1430 && (i = mn_get_cur(msgmap)) < mn_get_total(msgmap)){
1431 /* advance current */
1432 mn_inc_cur(stream, msgmap,
1433 (in_index == View && THREADING()
1434 && sp_viewing_a_thread(stream))
1435 ? MH_THISTHD
1436 : (in_index == View)
1437 ? MH_ANYTHD : MH_NONE);
1441 break;
1444 /*------- Apply command -----------*/
1445 case MC_APPLY :
1446 if(any_messages(msgmap, NULL, NULL)){
1447 if(any_lflagged(msgmap, MN_SLCT) > 0L){
1448 if(apply_command(state, stream, msgmap, 0,
1449 AC_NONE, question_line)){
1450 if(F_ON(F_AUTO_UNSELECT, state)){
1451 agg_select_all(stream, msgmap, NULL, 0);
1452 unzoom_index(state, stream, msgmap);
1454 else if(F_ON(F_AUTO_UNZOOM, state))
1455 unzoom_index(state, stream, msgmap);
1458 else
1459 any_messages(NULL, NULL, "to Apply command to. Try \"Select\"");
1462 break;
1465 /*-------- Sort command -------*/
1466 case MC_SORT :
1468 int were_threading = THREADING();
1469 SortOrder sort = mn_get_sort(msgmap);
1470 int rev = mn_get_revsort(msgmap);
1472 dprint((1,"MAIL_CMD: sort\n"));
1473 if(select_sort(state, question_line, &sort, &rev)){
1474 /* $ command reinitializes threading collapsed/expanded info */
1475 if(SORT_IS_THREADED(msgmap) && !SEP_THRDINDX())
1476 erase_threading_info(stream, msgmap);
1478 if(ps_global && ps_global->ttyo){
1479 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
1480 ps_global->mangled_footer = 1;
1483 sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN);
1486 state->mangled_footer = 1;
1489 * We've changed whether we are threading or not so we need to
1490 * exit the index and come back in so that we switch between the
1491 * thread index and the regular index. Sort_folder will have
1492 * reset viewing_a_thread if necessary.
1494 if(SEP_THRDINDX()
1495 && ((!were_threading && THREADING())
1496 || (were_threading && !THREADING()))){
1497 state->next_screen = mail_index_screen;
1498 state->mangled_screen = 1;
1502 break;
1505 /*------- Toggle Full Headers -----------*/
1506 case MC_FULLHDR :
1507 state->full_header++;
1508 if(state->full_header == 1){
1509 if(!(state->quote_suppression_threshold
1510 && (state->some_quoting_was_suppressed || in_index != View)))
1511 state->full_header++;
1513 else if(state->full_header > 2)
1514 state->full_header = 0;
1516 switch(state->full_header){
1517 case 0:
1518 q_status_message(SM_ORDER, 0, 3,
1519 _("Display of full headers is now off."));
1520 break;
1522 case 1:
1523 q_status_message1(SM_ORDER, 0, 3,
1524 _("Quotes displayed, use %s to see full headers"),
1525 F_ON(F_USE_FK, state) ? "F9" : "H");
1526 break;
1528 case 2:
1529 q_status_message(SM_ORDER, 0, 3,
1530 _("Display of full headers is now on."));
1531 break;
1535 a_changed = TRUE;
1536 break;
1539 case MC_TOGGLE :
1540 a_changed = TRUE;
1541 break;
1544 #ifdef SMIME
1545 /*------- Try to decrypt message -----------*/
1546 case MC_DECRYPT:
1547 if(state->smime && state->smime->need_passphrase)
1548 smime_get_passphrase();
1550 a_changed = TRUE;
1551 break;
1553 case MC_SECURITY:
1554 smime_info_screen(state);
1555 break;
1556 #endif
1559 /*------- Bounce -----------*/
1560 case MC_BOUNCE :
1561 (void) cmd_bounce(state, msgmap, MCMD_NONE, NULL);
1562 break;
1565 /*------- Flag -----------*/
1566 case MC_FLAG :
1567 dprint((4, "\n - flag message -\n"));
1568 (void) cmd_flag(state, msgmap, MCMD_NONE);
1569 break;
1572 /*------- Pipe message -----------*/
1573 case MC_PIPE :
1574 (void) cmd_pipe(state, msgmap, MCMD_NONE);
1575 break;
1578 /*--------- Default, unknown command ----------*/
1579 default:
1580 alpine_panic("Unexpected command case");
1581 break;
1584 return((a_changed || mn_get_cur(msgmap) != old_msgno) ? 1 : 0);
1589 /*----------------------------------------------------------------------
1590 Map some of the special characters into sensible strings for human
1591 consumption.
1592 c is a UCS-4 character!
1593 ----*/
1594 char *
1595 pretty_command(UCS c)
1597 static char buf[10];
1598 char *s;
1600 buf[0] = '\0';
1601 s = buf;
1603 switch(c){
1604 case ' ' : s = "SPACE"; break;
1605 case '\033' : s = "ESC"; break;
1606 case '\177' : s = "DEL"; break;
1607 case ctrl('I') : s = "TAB"; break;
1608 case ctrl('J') : s = "LINEFEED"; break;
1609 case ctrl('M') : s = "RETURN"; break;
1610 case ctrl('Q') : s = "XON"; break;
1611 case ctrl('S') : s = "XOFF"; break;
1612 case KEY_UP : s = "Up Arrow"; break;
1613 case KEY_DOWN : s = "Down Arrow"; break;
1614 case KEY_RIGHT : s = "Right Arrow"; break;
1615 case KEY_LEFT : s = "Left Arrow"; break;
1616 case KEY_PGUP : s = "Prev Page"; break;
1617 case KEY_PGDN : s = "Next Page"; break;
1618 case KEY_HOME : s = "Home"; break;
1619 case KEY_END : s = "End"; break;
1620 case KEY_DEL : s = "Delete"; break; /* Not necessary DEL! */
1621 case KEY_JUNK : s = "Junk!"; break;
1622 case BADESC : s = "Bad Esc"; break;
1623 case NO_OP_IDLE : s = "NO_OP_IDLE"; break;
1624 case NO_OP_COMMAND : s = "NO_OP_COMMAND"; break;
1625 case KEY_RESIZE : s = "KEY_RESIZE"; break;
1626 case KEY_UTF8 : s = "KEY_UTF8"; break;
1627 case KEY_MOUSE : s = "KEY_MOUSE"; break;
1628 case KEY_SCRLUPL : s = "KEY_SCRLUPL"; break;
1629 case KEY_SCRLDNL : s = "KEY_SCRLDNL"; break;
1630 case KEY_SCRLTO : s = "KEY_SCRLTO"; break;
1631 case KEY_XTERM_MOUSE : s = "KEY_XTERM_MOUSE"; break;
1632 case KEY_DOUBLE_ESC : s = "KEY_DOUBLE_ESC"; break;
1633 case CTRL_KEY_UP : s = "Ctrl Up Arrow"; break;
1634 case CTRL_KEY_DOWN : s = "Ctrl Down Arrow"; break;
1635 case CTRL_KEY_RIGHT : s = "Ctrl Right Arrow"; break;
1636 case CTRL_KEY_LEFT : s = "Ctrl Left Arrow"; break;
1637 case PF1 :
1638 case PF2 :
1639 case PF3 :
1640 case PF4 :
1641 case PF5 :
1642 case PF6 :
1643 case PF7 :
1644 case PF8 :
1645 case PF9 :
1646 case PF10 :
1647 case PF11 :
1648 case PF12 :
1649 snprintf(s = buf, sizeof(buf), "F%ld", (long) (c - PF1 + 1));
1650 break;
1652 default:
1653 if(c < ' ' || (c >= 0x80 && c < 0xA0)){
1654 char d;
1655 int c1;
1657 c1 = (c >= 0x80);
1658 d = (c & 0x1f) + 'A' - 1;
1659 snprintf(s = buf, sizeof(buf), "%c%c", c1 ? '~' : '^', d);
1661 else{
1662 memset(buf, 0, sizeof(buf));
1663 utf8_put((unsigned char *) buf, (unsigned long) c);
1666 break;
1669 return(s);
1673 /*----------------------------------------------------------------------
1674 Complain about bogus input
1676 Args: ch -- input command to complain about
1677 help -- string indicating where to get help
1679 ----*/
1680 void
1681 bogus_command(UCS cmd, char *help)
1683 if(cmd == ctrl('Q') || cmd == ctrl('S'))
1684 q_status_message1(SM_ASYNC, 0, 2,
1685 "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.",
1686 pretty_command(cmd));
1687 else if(cmd == KEY_JUNK)
1688 q_status_message3(SM_ORDER, 0, 2,
1689 "Invalid key pressed.%s%s%s",
1690 (help) ? " Use " : "",
1691 (help) ? help : "",
1692 (help) ? " for help" : "");
1693 else
1694 q_status_message4(SM_ORDER, 0, 2,
1695 "Command \"%s\" not defined for this screen.%s%s%s",
1696 pretty_command(cmd),
1697 (help) ? " Use " : "",
1698 (help) ? help : "",
1699 (help) ? " for help" : "");
1703 void
1704 bogus_utf8_command(char *cmd, char *help)
1706 q_status_message4(SM_ORDER, 0, 2,
1707 "Command \"%s\" not defined for this screen.%s%s%s",
1708 cmd ? cmd : "?",
1709 (help) ? " Use " : "",
1710 (help) ? help : "",
1711 (help) ? " for help" : "");
1715 /*----------------------------------------------------------------------
1716 Execute FLAG message command
1718 Args: state -- Various satate info
1719 msgmap -- map of c-client to local message numbers
1721 Result: with side effect of "current" message FLAG flag set or UNset
1723 ----*/
1725 cmd_flag(struct pine *state, MSGNO_S *msgmap, int aopt)
1727 char *flagit, *seq, *screen_text[20], **exp, **p, *answer = NULL;
1728 char *keyword_array[2];
1729 int user_defined_flags = 0, mailbox_flags = 0;
1730 int directly_to_maint_screen = 0;
1731 int use_maint_screen = F_ON(F_FLAG_SCREEN_DFLT, ps_global);
1732 long unflagged, flagged, flags, rawno;
1733 MESSAGECACHE *mc = NULL;
1734 KEYWORD_S *kw;
1735 int i, cnt, is_set, trouble = 0, rv = 0;
1736 size_t len;
1737 struct flag_table *fp, *ftbl = NULL;
1738 struct flag_screen flag_screen;
1739 static char *flag_screen_text1[] = {
1740 N_(" Set desired flags for current message below. An 'X' means set"),
1741 N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."),
1742 NULL
1745 static char *flag_screen_text2[] = {
1746 N_(" Set desired flags below for selected messages. A '?' means to"),
1747 N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"),
1748 N_(" to unset it. Use the \"Return\" key to toggle, and choose"),
1749 N_(" \"Exit\" when finished."),
1750 NULL
1753 static struct flag_table default_ftbl[] = {
1754 {N_("Important"), h_flag_important, F_FLAG, 0, 0, NULL, NULL},
1755 {N_("New"), h_flag_new, F_SEEN, 0, 0, NULL, NULL},
1756 {N_("Answered"), h_flag_answered, F_ANS, 0, 0, NULL, NULL},
1757 {N_("Forwarded"), h_flag_forwarded, F_FWD, 0, 0, NULL, NULL},
1758 {N_("Deleted"), h_flag_deleted, F_DEL, 0, 0, NULL, NULL},
1759 {NULL, NO_HELP, 0, 0, 0, NULL, NULL}
1762 /* Only check for dead stream for now. Should check permanent flags
1763 * eventually
1765 if(!(any_messages(msgmap, NULL, "to Flag") && can_set_flag(state, "flag", 1)))
1766 return rv;
1768 if(sp_io_error_on_stream(state->mail_stream)){
1769 sp_set_io_error_on_stream(state->mail_stream, 0);
1770 pine_mail_check(state->mail_stream); /* forces write */
1771 return rv;
1774 go_again:
1775 answer = NULL;
1776 user_defined_flags = 0;
1777 mailbox_flags = 0;
1778 mc = NULL;
1779 trouble = 0;
1780 ftbl = NULL;
1782 /* count how large ftbl will be */
1783 for(cnt = 0; default_ftbl[cnt].name; cnt++)
1786 /* add user flags */
1787 for(kw = ps_global->keywords; kw; kw = kw->next){
1788 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick)) || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1789 user_defined_flags++;
1790 cnt++;
1795 * Add mailbox flags that aren't user-defined flags.
1796 * Don't consider it if it matches either one of our defined
1797 * keywords or one of our defined nicknames for a keyword.
1799 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1800 char *q;
1802 q = stream_to_user_flag_name(state->mail_stream, i);
1803 if(q && q[0]){
1804 for(kw = ps_global->keywords; kw; kw = kw->next){
1805 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1806 break;
1810 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1811 mailbox_flags++;
1812 cnt++;
1816 cnt += (user_defined_flags ? 2 : 0) + (mailbox_flags ? 2 : 0);
1818 /* set up ftbl, first the system flags */
1819 ftbl = (struct flag_table *) fs_get((cnt+1) * sizeof(*ftbl));
1820 memset(ftbl, 0, (cnt+1) * sizeof(*ftbl));
1821 for(i = 0, fp = ftbl; default_ftbl[i].name; i++, fp++){
1822 fp->name = cpystr(default_ftbl[i].name);
1823 fp->help = default_ftbl[i].help;
1824 fp->flag = default_ftbl[i].flag;
1825 fp->set = default_ftbl[i].set;
1826 fp->ukn = default_ftbl[i].ukn;
1829 if(user_defined_flags){
1830 fp->flag = F_COMMENT;
1831 fp->name = cpystr("");
1832 fp++;
1833 fp->flag = F_COMMENT;
1834 len = strlen(_("User-defined Keywords from Setup/Config"));
1835 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1836 snprintf(fp->name, len+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config"));
1837 fp++;
1840 /* then the user-defined keywords */
1841 if(user_defined_flags)
1842 for(kw = ps_global->keywords; kw; kw = kw->next){
1843 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick))
1844 || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1845 fp->name = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
1846 fp->keyword = cpystr(kw->kw ? kw->kw : "");
1847 if(kw->nick && kw->kw){
1848 size_t l;
1850 l = strlen(kw->kw)+2;
1851 fp->comment = (char *) fs_get((l+1) * sizeof(char));
1852 snprintf(fp->comment, l+1, "(%.*s)", (int) strlen(kw->kw), kw->kw);
1853 fp->comment[l] = '\0';
1856 fp->help = h_flag_user_flag;
1857 fp->flag = F_KEYWORD;
1858 fp->set = 0;
1859 fp->ukn = 0;
1860 fp++;
1864 if(mailbox_flags){
1865 fp->flag = F_COMMENT;
1866 fp->name = cpystr("");
1867 fp++;
1868 fp->flag = F_COMMENT;
1869 len = strlen(_("Other keywords in the mailbox that are not user-defined"));
1870 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1871 snprintf(fp->name, len+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined"));
1872 fp++;
1875 /* then the extra mailbox-defined keywords */
1876 if(mailbox_flags)
1877 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1878 char *q;
1880 q = stream_to_user_flag_name(state->mail_stream, i);
1881 if(q && q[0]){
1882 for(kw = ps_global->keywords; kw; kw = kw->next){
1883 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1884 break;
1888 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1889 fp->name = cpystr(q);
1890 fp->keyword = cpystr(q);
1891 fp->help = h_flag_user_flag;
1892 fp->flag = F_KEYWORD;
1893 fp->set = 0;
1894 fp->ukn = 0;
1895 fp++;
1899 flag_screen.flag_table = &ftbl;
1900 flag_screen.explanation = screen_text;
1902 if(MCMD_ISAGG(aopt)){
1903 if(!pseudo_selected(ps_global->mail_stream, msgmap)){
1904 free_flag_table(&ftbl);
1905 return rv;
1908 exp = flag_screen_text2;
1909 for(fp = ftbl; fp->name; fp++){
1910 fp->set = CMD_FLAG_UNKN; /* set to unknown */
1911 fp->ukn = TRUE;
1914 else if(state->mail_stream
1915 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
1916 && rawno <= state->mail_stream->nmsgs
1917 && (mc = mail_elt(state->mail_stream, rawno))){
1918 exp = flag_screen_text1;
1919 for(fp = &ftbl[0]; fp->name; fp++){
1920 fp->ukn = 0;
1921 if(fp->flag == F_KEYWORD){
1922 /* see if this keyword is defined for this message */
1923 fp->set = CMD_FLAG_CLEAR;
1924 if(user_flag_is_set(state->mail_stream,
1925 rawno, fp->keyword))
1926 fp->set = CMD_FLAG_SET;
1928 else if(fp->flag == F_FWD){
1929 /* see if forwarded keyword is defined for this message */
1930 fp->set = CMD_FLAG_CLEAR;
1931 if(user_flag_is_set(state->mail_stream,
1932 rawno, FORWARDED_FLAG))
1933 fp->set = CMD_FLAG_SET;
1935 else if(fp->flag != F_COMMENT)
1936 fp->set = ((fp->flag == F_SEEN && !mc->seen)
1937 || (fp->flag == F_DEL && mc->deleted)
1938 || (fp->flag == F_FLAG && mc->flagged)
1939 || (fp->flag == F_ANS && mc->answered))
1940 ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
1943 else{
1944 q_status_message(SM_ORDER | SM_DING, 3, 4,
1945 _("Error accessing message data"));
1946 free_flag_table(&ftbl);
1947 return rv;
1950 if(directly_to_maint_screen)
1951 goto the_maint_screen;
1953 #ifdef _WINDOWS
1954 if (mswin_usedialog ()) {
1955 if (!os_flagmsgdialog (&ftbl[0])){
1956 free_flag_table(&ftbl);
1957 return rv;
1960 else
1961 #endif
1963 int keyword_shortcut = 0;
1965 if(!use_maint_screen){
1967 * We're going to call cmd_flag_prompt(). We need
1968 * to decide whether or not to offer the keyword setting
1969 * shortcut. We'll offer it if the user has the feature
1970 * enabled AND there are some possible keywords that could
1971 * be set.
1973 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global)){
1974 for(fp = &ftbl[0]; fp->name && !keyword_shortcut; fp++){
1975 if(fp->flag == F_KEYWORD){
1976 int first_char;
1977 ESCKEY_S *tp;
1979 first_char = (fp->name && fp->name[0])
1980 ? fp->name[0] : -2;
1981 if(isascii(first_char) && isupper(first_char))
1982 first_char = tolower((unsigned char) first_char);
1984 for(tp=flag_text_opt; tp->ch != -1; tp++)
1985 if(tp->ch == first_char)
1986 break;
1988 if(tp->ch == -1)
1989 keyword_shortcut++;
1994 use_maint_screen = !cmd_flag_prompt(state, &flag_screen,
1995 keyword_shortcut);
1998 the_maint_screen:
1999 if(use_maint_screen){
2000 for(p = &screen_text[0]; *exp; p++, exp++)
2001 *p = *exp;
2003 *p = NULL;
2005 directly_to_maint_screen = flag_maintenance_screen(state, &flag_screen);
2009 /* reacquire the elt pointer */
2010 mc = (state->mail_stream
2011 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
2012 && rawno <= state->mail_stream->nmsgs)
2013 ? mail_elt(state->mail_stream, rawno) : NULL;
2015 for(fp = ftbl; mc && fp->name; fp++){
2016 flags = -1;
2017 switch(fp->flag){
2018 case F_SEEN:
2019 if((!MCMD_ISAGG(aopt) && fp->set != !mc->seen)
2020 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2021 flagit = "\\SEEN";
2022 if(fp->set){
2023 flags = 0L;
2024 unflagged = F_SEEN;
2026 else{
2027 flags = ST_SET;
2028 unflagged = F_UNSEEN;
2032 break;
2034 case F_ANS:
2035 if((!MCMD_ISAGG(aopt) && fp->set != mc->answered)
2036 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2037 flagit = "\\ANSWERED";
2038 if(fp->set){
2039 flags = ST_SET;
2040 unflagged = F_UNANS;
2042 else{
2043 flags = 0L;
2044 unflagged = F_ANS;
2048 break;
2050 case F_DEL:
2051 if((!MCMD_ISAGG(aopt) && fp->set != mc->deleted)
2052 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2053 flagit = "\\DELETED";
2054 if(fp->set){
2055 flags = ST_SET;
2056 unflagged = F_UNDEL;
2058 else{
2059 flags = 0L;
2060 unflagged = F_DEL;
2064 break;
2066 case F_FLAG:
2067 if((!MCMD_ISAGG(aopt) && fp->set != mc->flagged)
2068 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2069 flagit = "\\FLAGGED";
2070 if(fp->set){
2071 flags = ST_SET;
2072 unflagged = F_UNFLAG;
2074 else{
2075 flags = 0L;
2076 unflagged = F_FLAG;
2080 break;
2082 case F_FWD :
2083 if(!MCMD_ISAGG(aopt)){
2084 /* see if forwarded is defined for this message */
2085 is_set = CMD_FLAG_CLEAR;
2086 if(user_flag_is_set(state->mail_stream,
2087 mn_m2raw(msgmap, mn_get_cur(msgmap)),
2088 FORWARDED_FLAG))
2089 is_set = CMD_FLAG_SET;
2092 if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2093 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2094 flagit = FORWARDED_FLAG;
2095 if(fp->set){
2096 flags = ST_SET;
2097 unflagged = F_UNFWD;
2099 else{
2100 flags = 0L;
2101 unflagged = F_FWD;
2105 break;
2107 case F_KEYWORD:
2108 if(!MCMD_ISAGG(aopt)){
2109 /* see if this keyword is defined for this message */
2110 is_set = CMD_FLAG_CLEAR;
2111 if(user_flag_is_set(state->mail_stream,
2112 mn_m2raw(msgmap, mn_get_cur(msgmap)),
2113 fp->keyword))
2114 is_set = CMD_FLAG_SET;
2117 if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2118 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2119 flagit = fp->keyword;
2120 keyword_array[0] = fp->keyword;
2121 keyword_array[1] = NULL;
2122 if(fp->set){
2123 flags = ST_SET;
2124 unflagged = F_UNKEYWORD;
2126 else{
2127 flags = 0L;
2128 unflagged = F_KEYWORD;
2132 break;
2134 default:
2135 break;
2138 flagged = 0L;
2139 if(flags >= 0L
2140 && (seq = currentf_sequence(state->mail_stream, msgmap,
2141 unflagged, &flagged, unflagged & F_DEL,
2142 (fp->flag == F_KEYWORD
2143 && unflagged == F_KEYWORD)
2144 ? keyword_array : NULL,
2145 (fp->flag == F_KEYWORD
2146 && unflagged == F_UNKEYWORD)
2147 ? keyword_array : NULL))){
2149 * For user keywords, we may have to create the flag in
2150 * the folder if it doesn't already exist and we are setting
2151 * it (as opposed to clearing it). Mail_flag will
2152 * do that for us, but it's failure isn't very friendly
2153 * error-wise. So we try to make it a little smoother.
2155 if(!(fp->flag == F_KEYWORD || fp->flag == F_FWD) || !fp->set
2156 || ((i=user_flag_index(state->mail_stream, flagit)) >= 0
2157 && i < NUSERFLAGS))
2158 mail_flag(state->mail_stream, seq, flagit, flags);
2159 else{
2160 /* trouble, see if we can add the user flag */
2161 if(state->mail_stream->kwd_create)
2162 mail_flag(state->mail_stream, seq, flagit, flags);
2163 else{
2164 trouble++;
2166 if(some_user_flags_defined(state->mail_stream))
2167 q_status_message(SM_ORDER, 3, 4,
2168 _("No more keywords allowed in this folder!"));
2169 else if(fp->flag == F_FWD)
2170 q_status_message(SM_ORDER, 3, 4,
2171 _("Cannot add keywords for this folder so cannot set Forwarded flag"));
2172 else
2173 q_status_message(SM_ORDER, 3, 4,
2174 _("Cannot add keywords for this folder"));
2178 fs_give((void **) &seq);
2179 if(flagged && !trouble){
2180 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%slagged%s%s%s%s%s message%s%s \"%s\"",
2181 (fp->set) ? "F" : "Unf",
2182 MCMD_ISAGG(aopt) ? " " : "",
2183 MCMD_ISAGG(aopt) ? long2string(flagged) : "",
2184 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2185 ? " (of " : "",
2186 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2187 ? comatose(mn_total_cur(msgmap)) : "",
2188 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2189 ? ")" : "",
2190 MCMD_ISAGG(aopt) ? plural(flagged) : " ",
2191 MCMD_ISAGG(aopt) ? "" : long2string(mn_get_cur(msgmap)),
2192 fp->name);
2193 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2194 q_status_message(SM_ORDER, 0, 2, answer = tmp_20k_buf);
2195 rv++;
2200 free_flag_table(&ftbl);
2202 if(directly_to_maint_screen)
2203 goto go_again;
2205 if(MCMD_ISAGG(aopt))
2206 restore_selected(msgmap);
2208 if(!answer)
2209 q_status_message(SM_ORDER, 0, 2, _("No flags changed."));
2211 return rv;
2215 /*----------------------------------------------------------------------
2216 Offer concise status line flag prompt
2218 Args: state -- Various satate info
2219 flags -- flags to offer setting
2221 Result: TRUE if flag to set specified in flags struct or FALSE otw
2223 ----*/
2225 cmd_flag_prompt(struct pine *state, struct flag_screen *flags, int allow_keyword_shortcuts)
2227 int r, setflag = 1, first_char;
2228 struct flag_table *fp;
2229 ESCKEY_S *ek;
2230 char *ftext, *ftext_not;
2231 static char *flag_text =
2232 N_("Flag New, Deleted, Answered, Forwarded or Important ? ");
2233 static char *flag_text_ak =
2234 N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? ");
2235 static char *flag_text_not =
2236 N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? ");
2237 static char *flag_text_ak_not =
2238 N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? ");
2240 if(allow_keyword_shortcuts){
2241 int cnt = 0;
2242 ESCKEY_S *dp, *sp, *tp;
2244 for(sp=flag_text_opt; sp->ch != -1; sp++)
2245 cnt++;
2247 for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++)
2248 if(fp->flag == F_KEYWORD)
2249 cnt++;
2251 /* set up an ESCKEY_S list which includes invisible keys for keywords */
2252 ek = (ESCKEY_S *) fs_get((cnt + 1) * sizeof(*ek));
2253 memset(ek, 0, (cnt+1) * sizeof(*ek));
2254 for(dp=ek, sp=flag_text_opt; sp->ch != -1; sp++, dp++)
2255 *dp = *sp;
2257 for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2258 if(fp->flag == F_KEYWORD){
2259 first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2260 if(isascii(first_char) && isupper(first_char))
2261 first_char = tolower((unsigned char) first_char);
2264 * Check to see if an earlier keyword in the list, or one of
2265 * the builtin system letters already uses this character.
2266 * If so, the first one wins.
2268 for(tp=ek; tp->ch != 0; tp++)
2269 if(tp->ch == first_char)
2270 break;
2272 if(tp->ch != 0)
2273 continue; /* skip it, already used that char */
2275 dp->ch = first_char;
2276 dp->rval = first_char;
2277 dp->name = "";
2278 dp->label = "";
2279 dp++;
2283 dp->ch = -1;
2284 ftext = _(flag_text_ak);
2285 ftext_not = _(flag_text_ak_not);
2287 else{
2288 ek = flag_text_opt;
2289 ftext = _(flag_text);
2290 ftext_not = _(flag_text_not);
2293 while(1){
2294 r = radio_buttons(setflag ? ftext : ftext_not,
2295 -FOOTER_ROWS(state), ek, '*', SEQ_EXCEPTION-1,
2296 NO_HELP, RB_NORM | RB_SEQ_SENSITIVE);
2298 * It is SEQ_EXCEPTION-1 just so that it is some value that isn't
2299 * being used otherwise. The keywords use up all the possible
2300 * letters, so a negative number is good, but it has to be different
2301 * from other negative return values.
2303 if(r == SEQ_EXCEPTION-1) /* ol'cancelrooney */
2304 return(TRUE);
2305 else if(r == 10) /* return and goto flag screen */
2306 return(FALSE);
2307 else if(r == '!') /* flip intention */
2308 setflag = !setflag;
2309 else
2310 break;
2313 for(fp = (flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2314 if(r == 'n' || r == '*' || r == 'd' || r == 'a' || r == 'f'){
2315 if((r == 'n' && fp->flag == F_SEEN)
2316 || (r == '*' && fp->flag == F_FLAG)
2317 || (r == 'd' && fp->flag == F_DEL)
2318 || (r == 'f' && fp->flag == F_FWD)
2319 || (r == 'a' && fp->flag == F_ANS)){
2320 fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2321 break;
2324 else if(allow_keyword_shortcuts && fp->flag == F_KEYWORD){
2325 first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2326 if(isascii(first_char) && isupper(first_char))
2327 first_char = tolower((unsigned char) first_char);
2329 if(r == first_char){
2330 fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2331 break;
2336 if(ek != flag_text_opt)
2337 fs_give((void **) &ek);
2339 return(TRUE);
2344 * (*ft) is an array of flag_table entries.
2346 void
2347 free_flag_table(struct flag_table **ft)
2349 struct flag_table *fp;
2351 if(ft && *ft){
2352 for(fp = (*ft); fp->name; fp++){
2353 if(fp->name)
2354 fs_give((void **) &fp->name);
2356 if(fp->keyword)
2357 fs_give((void **) &fp->keyword);
2359 if(fp->comment)
2360 fs_give((void **) &fp->comment);
2363 fs_give((void **) ft);
2368 /*----------------------------------------------------------------------
2369 Execute REPLY message command
2371 Args: state -- Various satate info
2372 msgmap -- map of c-client to local message numbers
2374 Result: reply sent or not
2376 ----*/
2378 cmd_reply(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2380 int rv = 0;
2382 if(any_messages(msgmap, NULL, "to Reply to")){
2383 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2384 return rv;
2386 rv = reply(state, role);
2388 if(MCMD_ISAGG(aopt))
2389 restore_selected(msgmap);
2391 state->mangled_screen = 1;
2394 return rv;
2398 /*----------------------------------------------------------------------
2399 Execute FORWARD message command
2401 Args: state -- Various satate info
2402 msgmap -- map of c-client to local message numbers
2404 Result: selected message[s] forwarded or not
2406 ----*/
2408 cmd_forward(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2410 int rv = 0;
2412 if(any_messages(msgmap, NULL, "to Forward")){
2413 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2414 return rv;
2416 rv = forward(state, role);
2418 if(MCMD_ISAGG(aopt))
2419 restore_selected(msgmap);
2421 state->mangled_screen = 1;
2424 return rv;
2428 /*----------------------------------------------------------------------
2429 Execute BOUNCE message command
2431 Args: state -- Various satate info
2432 msgmap -- map of c-client to local message numbers
2433 aopt -- aggregate options
2435 Result: selected message[s] bounced or not
2437 ----*/
2439 cmd_bounce(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2441 int rv = 0;
2443 if(any_messages(msgmap, NULL, "to Bounce")){
2444 long i;
2445 if(MCMD_ISAGG(aopt)){
2446 if(!pseudo_selected(state->mail_stream, msgmap))
2447 return rv;
2449 else if((i = any_lflagged(msgmap, MN_SLCT)) > 0
2450 && get_lflag(state->mail_stream, msgmap,
2451 mn_m2raw(msgmap, mn_get_cur(msgmap)), MN_SLCT) == 0)
2452 q_status_message(SM_ORDER | SM_DING, 3, 4,
2453 _("WARNING: non-selected message is being bounced!"));
2454 else if (i > 1L
2455 && get_lflag(state->mail_stream, msgmap,
2456 mn_m2raw(msgmap, mn_get_cur(msgmap)), MN_SLCT))
2457 q_status_message(SM_ORDER | SM_DING, 3, 4,
2458 _("WARNING: not bouncing all selected messages!"));
2460 rv = bounce(state, role);
2462 if(MCMD_ISAGG(aopt))
2463 restore_selected(msgmap);
2465 state->mangled_footer = 1;
2468 return rv;
2472 /*----------------------------------------------------------------------
2473 Execute save message command: prompt for folder and call function to save
2475 Args: screen_line -- Line on the screen to prompt on
2476 message -- The MESSAGECACHE entry of message to save
2478 Result: The folder lister can be called to make selection; mangled screen set
2480 This does the prompting for the folder name to save to, possibly calling
2481 up the folder display for selection of folder by user.
2482 ----*/
2484 cmd_save(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
2486 char newfolder[MAILTMPLEN], nmsgs[32], *nick;
2487 int we_cancel = 0, rv = 0, save_flags;
2488 long i, raw;
2489 CONTEXT_S *cntxt = NULL;
2490 ENVELOPE *e = NULL;
2491 SaveDel del = DontAsk;
2492 SavePreserveOrder pre = DontAskPreserve;
2494 dprint((4, "\n - saving message -\n"));
2496 if(MCMD_ISAGG(aopt) && !pseudo_selected(stream, msgmap))
2497 return rv;
2499 state->ugly_consider_advancing_bit = 0;
2500 if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM, state)
2501 && msgno_any_deletedparts(stream, msgmap)
2502 && want_to(_("Saved copy will NOT include entire message! Continue"),
2503 'y', 'n', NO_HELP, WT_FLUSH_IN | WT_SEQ_SENSITIVE) != 'y'){
2504 restore_selected(msgmap);
2505 cmd_cancelled("Save message");
2506 return rv;
2509 raw = mn_m2raw(msgmap, mn_get_cur(msgmap));
2511 if(mn_total_cur(msgmap) <= 1L){
2512 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld ", mn_get_cur(msgmap));
2513 nmsgs[sizeof(nmsgs)-1] = '\0';
2514 e = pine_mail_fetchstructure(stream, raw, NULL);
2515 if(!e) {
2516 q_status_message(SM_ORDER | SM_DING, 3, 4,
2517 _("Can't save message. Error accessing folder"));
2518 restore_selected(msgmap);
2519 return rv;
2522 else{
2523 snprintf(nmsgs, sizeof(nmsgs), "%s msgs ", comatose(mn_total_cur(msgmap)));
2524 nmsgs[sizeof(nmsgs)-1] = '\0';
2526 /* e is just used to get a default save folder from the first msg */
2527 e = pine_mail_fetchstructure(stream,
2528 mn_m2raw(msgmap, mn_first_cur(msgmap)),
2529 NULL);
2532 del = (!READONLY_FOLDER(stream) && F_OFF(F_SAVE_WONT_DELETE, ps_global))
2533 ? Del : NoDel;
2534 if(mn_total_cur(msgmap) > 1L)
2535 pre = F_OFF(F_AGG_SEQ_COPY, ps_global) ? Preserve : NoPreserve;
2536 else
2537 pre = DontAskPreserve;
2539 if(save_prompt(state, &cntxt, newfolder, sizeof(newfolder), nmsgs, e,
2540 raw, NULL, &del, &pre)){
2542 if(ps_global && ps_global->ttyo){
2543 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2544 ps_global->mangled_footer = 1;
2547 save_flags = SV_FIX_DELS;
2548 if(pre == RetPreserve)
2549 save_flags |= SV_PRESERVE;
2550 if(del == RetDel)
2551 save_flags |= SV_DELETE;
2552 if(ps_global->context_list == cntxt && !strucmp(newfolder, ps_global->inbox_name))
2553 save_flags |= SV_INBOXWOCNTXT;
2555 we_cancel = busy_cue(_("Saving"), NULL, 1);
2556 i = save(state, stream, cntxt, newfolder, msgmap, save_flags);
2557 if(we_cancel)
2558 cancel_busy_cue(0);
2560 if(i == mn_total_cur(msgmap)){
2561 rv++;
2562 if(mn_total_cur(msgmap) <= 1L){
2563 int need, avail = ps_global->ttyo->screen_cols - 2;
2564 int lennick, lenfldr;
2566 if(cntxt
2567 && ps_global->context_list->next
2568 && context_isambig(newfolder)){
2569 lennick = MIN(strlen(cntxt->nickname), 500);
2570 lenfldr = MIN(strlen(newfolder), 500);
2571 need = 27 + strlen(long2string(mn_get_cur(msgmap))) +
2572 lenfldr + lennick;
2573 if(need > avail){
2574 if(lennick > 10){
2575 need -= MIN(lennick-10, need-avail);
2576 lennick -= MIN(lennick-10, need-avail);
2579 if(need > avail && lenfldr > 10)
2580 lenfldr -= MIN(lenfldr-10, need-avail);
2583 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2584 "Message %s copied to \"%s\" in <%s>",
2585 long2string(mn_get_cur(msgmap)),
2586 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2587 lenfldr, MidDots),
2588 short_str(cntxt->nickname,
2589 (char *)(tmp_20k_buf+2000), 1000,
2590 lennick, EndDots));
2591 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2593 else if((nick=folder_is_target_of_nick(newfolder, cntxt)) != NULL){
2594 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2595 "Message %s copied to \"%s\"",
2596 long2string(mn_get_cur(msgmap)),
2597 nick);
2598 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2600 else{
2601 char *f = " folder";
2603 lenfldr = MIN(strlen(newfolder), 500);
2604 need = 28 + strlen(long2string(mn_get_cur(msgmap))) +
2605 lenfldr;
2606 if(need > avail){
2607 need -= strlen(f);
2608 f = "";
2609 if(need > avail && lenfldr > 10)
2610 lenfldr -= MIN(lenfldr-10, need-avail);
2613 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
2614 "Message %s copied to%s \"%s\"",
2615 long2string(mn_get_cur(msgmap)), f,
2616 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2617 lenfldr, MidDots));
2618 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2621 else{
2622 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s messages saved",
2623 comatose(mn_total_cur(msgmap)));
2624 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2627 if(del == RetDel){
2628 strncat(tmp_20k_buf, " and deleted", SIZEOF_20KBUF-strlen(tmp_20k_buf)-1);
2629 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2632 q_status_message(SM_ORDER, 0, 3, tmp_20k_buf);
2634 if(!MCMD_ISAGG(aopt) && F_ON(F_SAVE_ADVANCES, state)){
2635 if(sp_new_mail_count(stream))
2636 process_filter_patterns(stream, msgmap,
2637 sp_new_mail_count(stream));
2639 mn_inc_cur(stream, msgmap,
2640 (in_index == View && THREADING()
2641 && sp_viewing_a_thread(stream))
2642 ? MH_THISTHD
2643 : (in_index == View)
2644 ? MH_ANYTHD : MH_NONE);
2647 state->ugly_consider_advancing_bit = 1;
2651 if(MCMD_ISAGG(aopt)) /* straighten out fakes */
2652 restore_selected(msgmap);
2654 if(del == RetDel)
2655 update_titlebar_status(); /* make sure they see change */
2657 return rv;
2661 void
2662 role_compose(struct pine *state)
2664 int action;
2666 if(F_ON(F_ALT_ROLE_MENU, state) && mn_get_total(state->msgmap) > 0L){
2667 PAT_STATE pstate;
2669 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
2670 action = radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
2671 -FOOTER_ROWS(state), choose_action,
2672 'c', 'x', h_role_compose, RB_NORM);
2674 else{
2675 q_status_message(SM_ORDER, 0, 3,
2676 _("No roles available. Use Setup/Rules to add roles."));
2677 return;
2680 else
2681 action = 'c';
2683 if(action == 'c' || action == 'r' || action == 'f' || action == 'b'){
2684 ACTION_S *role = NULL;
2685 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
2687 redraw = state->redrawer;
2688 state->redrawer = NULL;
2689 prev_screen = state->prev_screen;
2690 role = NULL;
2691 state->next_screen = SCREEN_FUN_NULL;
2693 /* Setup role */
2694 if(role_select_screen(state, &role,
2695 action == 'f' ? MC_FORWARD :
2696 action == 'r' ? MC_REPLY :
2697 action == 'b' ? MC_BOUNCE :
2698 action == 'c' ? MC_COMPOSE : 0) < 0){
2699 cmd_cancelled(action == 'f' ? _("Forward") :
2700 action == 'r' ? _("Reply") :
2701 action == 'c' ? _("Composition") : _("Bounce"));
2702 state->next_screen = prev_screen;
2703 state->redrawer = redraw;
2704 state->mangled_screen = 1;
2706 else{
2708 * If default role was selected (NULL) we need to make
2709 * up a role which won't do anything, but will cause
2710 * compose_mail to think there's already a role so that
2711 * it won't try to confirm the default.
2713 if(role)
2714 role = combine_inherited_role(role);
2715 else{
2716 role = (ACTION_S *) fs_get(sizeof(*role));
2717 memset((void *) role, 0, sizeof(*role));
2718 role->nick = cpystr("Default Role");
2721 state->redrawer = NULL;
2722 switch(action){
2723 case 'c':
2724 compose_mail(NULL, NULL, role, NULL, NULL);
2725 break;
2727 case 'r':
2728 (void) reply(state, role);
2729 break;
2731 case 'f':
2732 (void) forward(state, role);
2733 break;
2735 case 'b':
2736 (void) bounce(state, role);
2737 break;
2740 if(role)
2741 free_action(&role);
2743 state->next_screen = prev_screen;
2744 state->redrawer = redraw;
2745 state->mangled_screen = 1;
2751 /*----------------------------------------------------------------------
2752 Do the dirty work of prompting the user for a folder name
2754 Args:
2755 nfldr should be a buffer at least MAILTMPLEN long
2756 dela -- a pointer to a SaveDel. If it is
2757 DontAsk on input, don't offer Delete prompt
2758 Del on input, offer Delete command with default of Delete
2759 NoDel NoDelete
2760 RetDel and RetNoDel are return values
2763 Result:
2765 ----*/
2767 save_prompt(struct pine *state, CONTEXT_S **cntxt, char *nfldr, size_t len_nfldr,
2768 char *nmsgs, ENVELOPE *env, long int rawmsgno, char *section,
2769 SaveDel *dela, SavePreserveOrder *prea)
2771 int rc, ku = -1, n, flags, last_rc = 0, saveable_count = 0, done = 0;
2772 int delindex = 0, preindex = 0, r;
2773 char prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN];
2774 char *buf = tmp_20k_buf;
2775 char shortbuf[200];
2776 char *folder;
2777 HelpType help;
2778 SaveDel del = DontAsk;
2779 SavePreserveOrder pre = DontAskPreserve;
2780 char *deltext = NULL;
2781 static HISTORY_S *history = NULL;
2782 CONTEXT_S *tc;
2783 ESCKEY_S ekey[10];
2785 if(!cntxt)
2786 alpine_panic("no context ptr in save_prompt");
2788 init_hist(&history, HISTSIZE);
2790 if(!(folder = save_get_default(state, env, rawmsgno, section, cntxt)))
2791 return(0); /* message expunged! */
2793 /* how many context's can be saved to... */
2794 for(tc = state->context_list; tc; tc = tc->next)
2795 if(!NEWS_TEST(tc))
2796 saveable_count++;
2798 /* set up extra command option keys */
2799 rc = 0;
2800 ekey[rc].ch = ctrl('T');
2801 ekey[rc].rval = 2;
2802 ekey[rc].name = "^T";
2803 /* TRANSLATORS: command means go to Folders list */
2804 ekey[rc++].label = N_("To Fldrs");
2806 if(saveable_count > 1){
2807 ekey[rc].ch = ctrl('P');
2808 ekey[rc].rval = 10;
2809 ekey[rc].name = "^P";
2810 ekey[rc++].label = N_("Prev Collection");
2812 ekey[rc].ch = ctrl('N');
2813 ekey[rc].rval = 11;
2814 ekey[rc].name = "^N";
2815 ekey[rc++].label = N_("Next Collection");
2818 if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
2819 ekey[rc].ch = TAB;
2820 ekey[rc].rval = 12;
2821 ekey[rc].name = "TAB";
2822 /* TRANSLATORS: command asks alpine to complete the name when tab is typed */
2823 ekey[rc++].label = N_("Complete");
2826 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
2827 ekey[rc].ch = ctrl('X');
2828 ekey[rc].rval = 14;
2829 ekey[rc].name = "^X";
2830 /* TRANSLATORS: list all the matches */
2831 ekey[rc++].label = N_("ListMatches");
2834 if(dela && (*dela == NoDel || *dela == Del)){
2835 ekey[rc].ch = ctrl('R');
2836 ekey[rc].rval = 15;
2837 ekey[rc].name = "^R";
2838 delindex = rc++;
2839 del = *dela;
2842 if(prea && (*prea == NoPreserve || *prea == Preserve)){
2843 ekey[rc].ch = ctrl('W');
2844 ekey[rc].rval = 16;
2845 ekey[rc].name = "^W";
2846 preindex = rc++;
2847 pre = *prea;
2850 if(saveable_count > 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2851 ekey[rc].ch = KEY_UP;
2852 ekey[rc].rval = 10;
2853 ekey[rc].name = "";
2854 ekey[rc++].label = "";
2856 ekey[rc].ch = KEY_DOWN;
2857 ekey[rc].rval = 11;
2858 ekey[rc].name = "";
2859 ekey[rc++].label = "";
2861 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2862 ekey[rc].ch = KEY_UP;
2863 ekey[rc].rval = 30;
2864 ekey[rc].name = "";
2865 ku = rc;
2866 ekey[rc++].label = "";
2868 ekey[rc].ch = KEY_DOWN;
2869 ekey[rc].rval = 31;
2870 ekey[rc].name = "";
2871 ekey[rc++].label = "";
2874 ekey[rc].ch = -1;
2876 *nfldr = '\0';
2877 help = NO_HELP;
2878 while(!done){
2879 /* only show collection number if more than one available */
2880 if(ps_global->context_list->next)
2881 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder in <%s> [%s] : ",
2882 deltext ? deltext : "",
2883 nmsgs,
2884 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2885 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2886 else
2887 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder [%s] : ",
2888 deltext ? deltext : "",
2889 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2891 prompt[sizeof(prompt)-1] = '\0';
2894 * If the prompt won't fit, try removing deltext.
2896 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && deltext){
2897 if(ps_global->context_list->next)
2898 snprintf(prompt, sizeof(prompt), "SAVE %sto folder in <%s> [%s] : ",
2899 nmsgs,
2900 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2901 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2902 else
2903 snprintf(prompt, sizeof(prompt), "SAVE %sto folder [%s] : ",
2904 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2906 prompt[sizeof(prompt)-1] = '\0';
2910 * If the prompt still won't fit, remove the extra info contained
2911 * in nmsgs.
2913 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && *nmsgs){
2914 if(ps_global->context_list->next)
2915 snprintf(prompt, sizeof(prompt), "SAVE to folder in <%s> [%s] : ",
2916 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2917 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2918 else
2919 snprintf(prompt, sizeof(prompt), "SAVE to folder [%s] : ",
2920 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2922 prompt[sizeof(prompt)-1] = '\0';
2925 if(del != DontAsk)
2926 ekey[delindex].label = (del == NoDel) ? "Delete" : "No Delete";
2928 if(pre != DontAskPreserve)
2929 ekey[preindex].label = (pre == NoPreserve) ? "Preserve Order" : "Any Order";
2931 if(ku >= 0){
2932 if(items_in_hist(history) > 1){
2933 ekey[ku].name = HISTORY_UP_KEYNAME;
2934 ekey[ku].label = HISTORY_KEYLABEL;
2935 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
2936 ekey[ku+1].label = HISTORY_KEYLABEL;
2938 else{
2939 ekey[ku].name = "";
2940 ekey[ku].label = "";
2941 ekey[ku+1].name = "";
2942 ekey[ku+1].label = "";
2946 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
2947 rc = optionally_enter(nfldr, -FOOTER_ROWS(state), 0, len_nfldr,
2948 prompt, ekey, help, &flags);
2950 switch(rc){
2951 case -1 :
2952 q_status_message(SM_ORDER | SM_DING, 3, 3,
2953 _("Error reading folder name"));
2954 done--;
2955 break;
2957 case 0 :
2958 removing_trailing_white_space(nfldr);
2959 removing_leading_white_space(nfldr);
2961 if(*nfldr || *folder){
2962 char *p, *name, *fullname = NULL;
2963 int exists, breakout = FALSE;
2965 if(!*nfldr){
2966 strncpy(nfldr, folder, len_nfldr-1);
2967 nfldr[len_nfldr-1] = '\0';
2970 save_hist(history, nfldr, 0, (void *) *cntxt);
2972 if(!(name = folder_is_nick(nfldr, FOLDERS(*cntxt), 0)))
2973 name = nfldr;
2975 if(update_folder_spec(expanded, sizeof(expanded), name)){
2976 strncpy(name = nfldr, expanded, len_nfldr-1);
2977 nfldr[len_nfldr-1] = '\0';
2980 exists = folder_name_exists(*cntxt, name, &fullname);
2982 if(exists == FEX_ERROR){
2983 q_status_message1(SM_ORDER, 0, 3,
2984 _("Problem accessing folder \"%s\""),
2985 nfldr);
2986 done--;
2988 else{
2989 if(fullname){
2990 strncpy(name = nfldr, fullname, len_nfldr-1);
2991 nfldr[len_nfldr-1] = '\0';
2992 fs_give((void **) &fullname);
2993 breakout = TRUE;
2996 if(exists & FEX_ISFILE){
2997 done++;
2999 else if((exists & FEX_ISDIR)){
3000 char tmp[MAILTMPLEN];
3002 tc = *cntxt;
3003 if(breakout){
3004 CONTEXT_S *fake_context;
3005 size_t l;
3007 strncpy(tmp, name, sizeof(tmp));
3008 tmp[sizeof(tmp)-2-1] = '\0';
3009 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
3010 if(l < sizeof(tmp)){
3011 tmp[l] = tc->dir->delim;
3012 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
3015 else
3016 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
3018 tmp[sizeof(tmp)-1] = '\0';
3020 fake_context = new_context(tmp, 0);
3021 nfldr[0] = '\0';
3022 done = display_folder_list(&fake_context, nfldr,
3023 1, folders_for_save);
3024 free_context(&fake_context);
3026 else if(tc->dir->delim
3027 && (p = strrindex(name, tc->dir->delim))
3028 && *(p+1) == '\0')
3029 done = display_folder_list(cntxt, nfldr,
3030 1, folders_for_save);
3031 else{
3032 q_status_message1(SM_ORDER, 3, 3,
3033 _("\"%s\" is a directory"), name);
3034 if(tc->dir->delim
3035 && !((p=strrindex(name, tc->dir->delim)) && *(p+1) == '\0')){
3036 strncpy(tmp, name, sizeof(tmp));
3037 tmp[sizeof(tmp)-1] = '\0';
3038 snprintf(nfldr, len_nfldr, "%s%c", tmp, tc->dir->delim);
3042 else{ /* Doesn't exist, create! */
3043 if((fullname = folder_as_breakout(*cntxt, name)) != NULL){
3044 strncpy(name = nfldr, fullname, len_nfldr-1);
3045 nfldr[len_nfldr-1] = '\0';
3046 fs_give((void **) &fullname);
3049 switch(create_for_save(*cntxt, name)){
3050 case 1 : /* success */
3051 done++;
3052 break;
3053 case 0 : /* error */
3054 case -1 : /* declined */
3055 done--;
3056 break;
3061 break;
3063 /* else fall thru like they cancelled */
3065 case 1 :
3066 cmd_cancelled("Save message");
3067 done--;
3068 break;
3070 case 2 :
3071 r = display_folder_list(cntxt, nfldr, 0, folders_for_save);
3073 if(r)
3074 done++;
3076 break;
3078 case 3 :
3079 helper(h_save, _("HELP FOR SAVE"), HLPD_SIMPLE);
3080 ps_global->mangled_screen = 1;
3081 break;
3083 case 4 : /* redraw */
3084 break;
3086 case 10 : /* previous collection */
3087 for(tc = (*cntxt)->prev; tc; tc = tc->prev)
3088 if(!NEWS_TEST(tc))
3089 break;
3091 if(!tc){
3092 CONTEXT_S *tc2;
3094 for(tc2 = (tc = (*cntxt))->next; tc2; tc2 = tc2->next)
3095 if(!NEWS_TEST(tc2))
3096 tc = tc2;
3099 *cntxt = tc;
3100 break;
3102 case 11 : /* next collection */
3103 tc = (*cntxt);
3106 if(((*cntxt) = (*cntxt)->next) == NULL)
3107 (*cntxt) = ps_global->context_list;
3108 while(NEWS_TEST(*cntxt) && (*cntxt) != tc);
3109 break;
3111 case 12 : /* file name completion */
3112 if(!folder_complete(*cntxt, nfldr, len_nfldr, &n)){
3113 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
3114 r = display_folder_list(cntxt, nfldr, 1, folders_for_save);
3115 if(r)
3116 done++; /* bingo! */
3117 else
3118 rc = 0; /* burn last_rc */
3120 else
3121 Writechar(BELL, 0);
3124 break;
3126 case 14 : /* file name completion */
3127 r = display_folder_list(cntxt, nfldr, 2, folders_for_save);
3128 if(r)
3129 done++; /* bingo! */
3130 else
3131 rc = 0; /* burn last_rc */
3133 break;
3135 case 15 : /* Delete / No Delete */
3136 del = (del == NoDel) ? Del : NoDel;
3137 deltext = (del == NoDel) ? " (no delete)" : " (and delete)";
3138 break;
3140 case 16 : /* Preserve Order or not */
3141 pre = (pre == NoPreserve) ? Preserve : NoPreserve;
3142 break;
3144 case 30 :
3145 if((p = get_prev_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3146 strncpy(nfldr, p, len_nfldr);
3147 nfldr[len_nfldr-1] = '\0';
3148 if(history->hist[history->curindex])
3149 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3151 else
3152 Writechar(BELL, 0);
3154 break;
3156 case 31 :
3157 if((p = get_next_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3158 strncpy(nfldr, p, len_nfldr);
3159 nfldr[len_nfldr-1] = '\0';
3160 if(history->hist[history->curindex])
3161 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3163 else
3164 Writechar(BELL, 0);
3166 break;
3168 default :
3169 alpine_panic("Unhandled case");
3170 break;
3173 last_rc = rc;
3176 ps_global->mangled_footer = 1;
3178 if(done < 0)
3179 return(0);
3181 if(*nfldr){
3182 strncpy(ps_global->last_save_folder, nfldr, sizeof(ps_global->last_save_folder)-1);
3183 ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0';
3184 if(*cntxt)
3185 ps_global->last_save_context = *cntxt;
3187 else{
3188 strncpy(nfldr, folder, len_nfldr-1);
3189 nfldr[len_nfldr-1] = '\0';
3192 /* nickname? Copy real name to nfldr */
3193 if(*cntxt
3194 && context_isambig(nfldr)
3195 && (p = folder_is_nick(nfldr, FOLDERS(*cntxt), 0))){
3196 strncpy(nfldr, p, len_nfldr-1);
3197 nfldr[len_nfldr-1] = '\0';
3200 if(dela && (*dela == NoDel || *dela == Del))
3201 *dela = (del == NoDel) ? RetNoDel : RetDel;
3203 if(prea && (*prea == NoPreserve || *prea == Preserve))
3204 *prea = (pre == NoPreserve) ? RetNoPreserve : RetPreserve;
3206 return(1);
3210 /*----------------------------------------------------------------------
3211 Prompt user before implicitly creating a folder for saving
3213 Args: context - context to create folder in
3214 folder - folder name to create
3216 Result: 1 on proceed, -1 on decline, 0 on error
3218 ----*/
3220 create_for_save_prompt(CONTEXT_S *context, char *folder, int sequence_sensitive)
3222 if(context && ps_global->context_list->next && context_isambig(folder)){
3223 if(context->use & CNTXT_INCMNG){
3224 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3225 _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
3226 folder, (strlen(folder) > 15) ? "..." : "");
3227 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
3228 return(0); /* error */
3231 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3232 _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
3233 folder, (strlen(folder) > 15) ? "..." : "",
3234 context->nickname,
3235 (strlen(context->nickname) > 15) ? "..." : "");
3237 else
3238 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
3239 _("Folder \"%.40s%s\" doesn't exist. Create"),
3240 folder, strlen(folder) > 40 ? "..." : "");
3242 if(want_to(tmp_20k_buf, 'y', 'n',
3243 NO_HELP, (sequence_sensitive) ? WT_SEQ_SENSITIVE : WT_NORM) != 'y'){
3244 cmd_cancelled("Save message");
3245 return(-1);
3248 return(1);
3253 /*----------------------------------------------------------------------
3254 Expunge messages from current folder
3256 Args: state -- pointer to struct holding a bunch of pine state
3257 msgmap -- table mapping msg nums to c-client sequence nums
3258 qline -- screen line to ask questions on
3259 agg -- boolean indicating we're to operate on aggregate set
3261 Result:
3262 ----*/
3264 cmd_expunge(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int agg)
3266 long del_count, prefilter_del_count = 0;
3267 int we_cancel = 0, rv = 0;
3268 char prompt[MAX_SCREEN_COLS+1];
3269 char *sequence;
3270 COLOR_PAIR *lastc = NULL;
3272 dprint((2, "\n - expunge -\n"));
3274 del_count = 0;
3276 sequence = MCMD_ISAGG(agg) ? selected_sequence(stream, msgmap, NULL, 0) : NULL;
3278 if(MCMD_ISAGG(agg)){
3279 long i;
3280 MESSAGECACHE *mc;
3281 for(i = 1L; i <= stream->nmsgs; i++){
3282 if((mc = mail_elt(stream, i)) != NULL
3283 && mc->sequence && mc->deleted)
3284 del_count++;
3286 if(del_count == 0){
3287 q_status_message(SM_ORDER, 0, 4,
3288 _("No selected messages are deleted"));
3289 return 0;
3291 } else {
3292 if(!any_messages(msgmap, NULL, "to Expunge"))
3293 return rv;
3296 if(IS_NEWS(stream) && stream->rdonly){
3297 if(!MCMD_ISAGG(agg))
3298 del_count = count_flagged(stream, F_DEL);
3299 if(del_count > 0L){
3300 state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3301 snprintf(prompt, sizeof(prompt), "Exclude %ld message%s from %.*s", del_count,
3302 plural(del_count), MAX_SCREEN_COLS+1-40,
3303 pretty_fn(state->cur_folder));
3304 prompt[sizeof(prompt)-1] = '\0';
3305 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3306 || (F_ON(F_AUTO_EXPUNGE, state)
3307 && (state->context_current
3308 && (state->context_current->use & CNTXT_INCMNG))
3309 && context_isambig(state->cur_folder))
3310 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
3312 if(F_ON(F_NEWS_CROSS_DELETE, state))
3313 cross_delete_crossposts(stream);
3315 msgno_exclude_deleted(stream, msgmap, sequence);
3316 clear_index_cache(stream, 0);
3319 * This is kind of surprising at first. For most sort
3320 * orders, if the whole set is sorted, then any subset
3321 * is also sorted. Not so for threaded sorts.
3323 if(SORT_IS_THREADED(msgmap))
3324 refresh_sort(stream, msgmap, SRT_NON);
3326 state->mangled_body = 1;
3327 state->mangled_header = 1;
3328 q_status_message2(SM_ORDER, 0, 4,
3329 "%s message%s excluded",
3330 long2string(del_count),
3331 plural(del_count));
3333 else
3334 any_messages(NULL, NULL, "Excluded");
3336 else
3337 any_messages(NULL, "deleted", "to Exclude");
3339 return del_count;
3341 else if(READONLY_FOLDER(stream)){
3342 q_status_message(SM_ORDER, 0, 4,
3343 _("Can't expunge. Folder is read-only"));
3344 return del_count;
3347 if(!MCMD_ISAGG(agg)){
3348 prefilter_del_count = count_flagged(stream, F_DEL|F_NOFILT);
3349 mail_expunge_prefilter(stream, MI_NONE);
3350 del_count = count_flagged(stream, F_DEL|F_NOFILT);
3353 if(del_count != 0){
3354 int ret;
3355 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3356 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3357 snprintf(prompt, sizeof(prompt), "Expunge %ld message%s from %.*s", del_count,
3358 plural(del_count), MAX_SCREEN_COLS+1-40,
3359 pretty_fn((char *) fname));
3360 if(fname) fs_give((void **)&fname);
3361 prompt[sizeof(prompt)-1] = '\0';
3362 state->mangled_footer = 1;
3364 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3365 || (F_ON(F_AUTO_EXPUNGE, state)
3366 && ((!strucmp(state->cur_folder,state->inbox_name))
3367 || (state->context_current->use & CNTXT_INCMNG))
3368 && context_isambig(state->cur_folder))
3369 || (ret=want_to(prompt, 'y', 0, NO_HELP, WT_NORM)) == 'y')
3370 ret = 'y';
3372 if(ret == 'x')
3373 cmd_cancelled("Expunge");
3375 if(ret != 'y')
3376 return 0;
3379 dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
3380 mn_get_total(msgmap), mn_get_cur(msgmap), del_count));
3382 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3383 state->VAR_TITLE_BACK_COLOR,
3384 PSC_REV|PSC_RET);
3386 PutLine0(0, 0, "**"); /* indicate delay */
3388 if(lastc){
3389 (void)pico_set_colorp(lastc, PSC_NONE);
3390 free_color_pair(&lastc);
3393 MoveCursor(state->ttyo->screen_rows -FOOTER_ROWS(state), 0);
3394 fflush(stdout);
3396 we_cancel = busy_cue(_("Expunging"), NULL, 1);
3398 if(cmd_expunge_work(stream, msgmap, sequence))
3399 state->mangled_body = 1;
3401 if(sequence)
3402 fs_give((void **)&sequence);
3404 if(we_cancel)
3405 cancel_busy_cue((sp_expunge_count(stream) > 0) ? 0 : -1);
3407 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3408 state->VAR_TITLE_BACK_COLOR,
3409 PSC_REV|PSC_RET);
3410 PutLine0(0, 0, " "); /* indicate delay's over */
3412 if(lastc){
3413 (void)pico_set_colorp(lastc, PSC_NONE);
3414 free_color_pair(&lastc);
3417 fflush(stdout);
3419 if(sp_expunge_count(stream) > 0){
3421 * This is kind of surprising at first. For most sort
3422 * orders, if the whole set is sorted, then any subset
3423 * is also sorted. Not so for threaded sorts.
3425 if(SORT_IS_THREADED(msgmap))
3426 refresh_sort(stream, msgmap, SRT_NON);
3428 else{
3429 if(del_count){
3430 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3431 q_status_message1(SM_ORDER, 0, 3,
3432 _("No messages expunged from folder \"%s\""),
3433 pretty_fn((char *) fname));
3434 if(fname) fs_give((void **)&fname);
3436 else if(!prefilter_del_count)
3437 q_status_message(SM_ORDER, 0, 3,
3438 _("No messages marked deleted. No messages expunged."));
3440 return del_count;
3444 /*----------------------------------------------------------------------
3445 Expunge_and_close callback to prompt user for confirmation
3447 Args: stream -- folder's stream
3448 folder -- name of folder containing folders
3449 deleted -- number of del'd msgs
3451 Result: 'y' to continue with expunge
3452 ----*/
3454 expunge_prompt(MAILSTREAM *stream, char *folder, long int deleted)
3456 long max_folder;
3457 int charcnt = 0;
3458 char prompt_b[MAX_SCREEN_COLS+1], temp[MAILTMPLEN+1], buff[MAX_SCREEN_COLS+1];
3459 char *short_folder_name;
3461 if(deleted == 1)
3462 charcnt = 1;
3463 else{
3464 snprintf(temp, sizeof(temp), "%ld", deleted);
3465 charcnt = strlen(temp)+1;
3468 max_folder = MAX(1,MAXPROMPT - (36+charcnt));
3469 strncpy(temp, folder, sizeof(temp));
3470 temp[sizeof(temp)-1] = '\0';
3471 short_folder_name = short_str(temp,buff,sizeof(buff),max_folder,FrontDots);
3473 if(IS_NEWS(stream))
3474 snprintf(prompt_b, sizeof(prompt_b),
3475 "Delete %s%ld message%s from \"%s\"",
3476 (deleted > 1L) ? "all " : "", deleted,
3477 plural(deleted), short_folder_name);
3478 else
3479 snprintf(prompt_b, sizeof(prompt_b),
3480 "Expunge the %ld deleted message%s from \"%s\"",
3481 deleted, deleted == 1 ? "" : "s",
3482 short_folder_name);
3484 return(want_to(prompt_b, 'y', 0, NO_HELP, WT_NORM));
3489 * This is used with multiple append saves. Call it once before
3490 * the series of appends with SSCP_INIT and once after all are
3491 * done with SSCP_END. In between, it is called automatically
3492 * from save_fetch_append or save_fetch_append_cb when we need
3493 * to ask the user if he or she wants to continue even though
3494 * announced message size doesn't match the actual message size.
3495 * As of 2008-02-29 the gmail IMAP server has these size mismatches
3496 * on a regular basis even though the data is ok.
3499 save_size_changed_prompt(long msgno, int flags)
3501 int ret;
3502 char prompt[100];
3503 static int remember_the_yes = 0;
3504 static int possible_corruption = 0;
3505 static ESCKEY_S save_size_opts[] = {
3506 {'y', 'y', "Y", "Yes"},
3507 {'n', 'n', "N", "No"},
3508 {'a', 'a', "A", "yes to All"},
3509 {-1, 0, NULL, NULL}
3512 if(F_ON(F_IGNORE_SIZE, ps_global))
3513 return 'y';
3515 if(flags & SSCP_INIT || flags & SSCP_END){
3516 if(flags & SSCP_END && possible_corruption)
3517 q_status_message(SM_ORDER, 3, 3, "There is possible data corruption, check the results");
3519 remember_the_yes = 0;
3520 possible_corruption = 0;
3521 ps_global->noshow_error = 0;
3522 ps_global->noshow_warn = 0;
3523 return(0);
3526 if(remember_the_yes){
3527 snprintf(prompt, sizeof(prompt),
3528 "Message to save shrank! (msg # %ld): Continuing", msgno);
3529 q_status_message(SM_ORDER, 0, 3, prompt);
3530 display_message('x');
3531 return(remember_the_yes);
3534 snprintf(prompt, sizeof(prompt),
3535 "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno);
3536 ret = radio_buttons(prompt, -FOOTER_ROWS(ps_global), save_size_opts,
3537 'n', 0, h_save_size_changed, RB_NORM|RB_NO_NEWMAIL);
3539 switch(ret){
3540 case 'a':
3541 remember_the_yes = 'y';
3542 possible_corruption++;
3543 return(remember_the_yes);
3545 case 'y':
3546 possible_corruption++;
3547 return('y');
3549 default:
3550 possible_corruption = 0;
3551 ps_global->noshow_error = 1;
3552 ps_global->noshow_warn = 1;
3553 break;
3556 return('n');
3560 /*----------------------------------------------------------------------
3561 Expunge_and_close callback that happens once the decision to expunge
3562 and close has been made and before expunging and closing begins
3565 Args: stream -- folder's stream
3566 folder -- name of folder containing folders
3567 deleted -- number of del'd msgs
3569 Result: 'y' to continue with expunge
3570 ----*/
3571 void
3572 expunge_and_close_begins(int flags, char *folder)
3574 if(!(flags & EC_NO_CLOSE)){
3575 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
3576 q_status_message1(SM_INFO, 0, 1, "Closing \"%.200s\"...", (char *) fname);
3577 flush_status_messages(1);
3578 if(fname) fs_give((void **)&fname);
3583 /*----------------------------------------------------------------------
3584 Export a message to a plain file in users home directory
3586 Args: state -- pointer to struct holding a bunch of pine state
3587 msgmap -- table mapping msg nums to c-client sequence nums
3588 qline -- screen line to ask questions on
3589 agg -- boolean indicating we're to operate on aggregate set
3591 Result:
3592 ----*/
3594 cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt)
3596 char filename[MAXPATH+1], full_filename[MAXPATH+1], *err;
3597 char nmsgs[80];
3598 int r, leading_nl, failure = 0, orig_errno = 0, rflags = GER_NONE;
3599 int flags = GE_IS_EXPORT | GE_SEQ_SENSITIVE, rv = 0;
3600 ENVELOPE *env;
3601 MESSAGECACHE *mc;
3602 BODY *b;
3603 long i, count = 0L, start_of_append = 0, rawno;
3604 gf_io_t pc;
3605 STORE_S *store;
3606 struct variable *vars = state ? ps_global->vars : NULL;
3607 ESCKEY_S export_opts[5];
3608 static HISTORY_S *history = NULL;
3610 if(ps_global->restricted){
3611 q_status_message(SM_ORDER, 0, 3,
3612 "Alpine demo can't export messages to files");
3613 return rv;
3616 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
3617 return rv;
3619 export_opts[i = 0].ch = ctrl('T');
3620 export_opts[i].rval = 10;
3621 export_opts[i].name = "^T";
3622 export_opts[i++].label = N_("To Files");
3624 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3625 if(ps_global->VAR_DOWNLOAD_CMD && ps_global->VAR_DOWNLOAD_CMD[0]){
3626 export_opts[i].ch = ctrl('V');
3627 export_opts[i].rval = 12;
3628 export_opts[i].name = "^V";
3629 /* TRANSLATORS: this is an abbreviation for Download Messages */
3630 export_opts[i++].label = N_("Downld Msg");
3632 #endif /* !(DOS || MAC) */
3634 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
3635 export_opts[i].ch = ctrl('I');
3636 export_opts[i].rval = 11;
3637 export_opts[i].name = "TAB";
3638 export_opts[i++].label = N_("Complete");
3641 #if 0
3642 /* Commented out since it's not yet support! */
3643 if(F_ON(F_ENABLE_SUB_LISTS,ps_global)){
3644 export_opts[i].ch = ctrl('X');
3645 export_opts[i].rval = 14;
3646 export_opts[i].name = "^X";
3647 export_opts[i++].label = N_("ListMatches");
3649 #endif
3652 * If message has attachments, add a toggle that will allow the user
3653 * to save all of the attachments to a single directory, using the
3654 * names provided with the attachments or part names. What we'll do is
3655 * export the message as usual, and then export the attachments into
3656 * a subdirectory that did not exist before. The subdir will be named
3657 * something based on the name of the file being saved to, but a
3658 * unique, new name.
3660 if(!MCMD_ISAGG(aopt)
3661 && state->mail_stream
3662 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3663 && rawno <= state->mail_stream->nmsgs
3664 && (env = pine_mail_fetchstructure(state->mail_stream, rawno, &b))
3665 && b
3666 && b->type == TYPEMULTIPART
3667 && b->subtype
3668 && strucmp(b->subtype, "ALTERNATIVE") != 0){
3669 PART *part;
3671 part = b->nested.part; /* 1st part */
3672 if(part && part->next)
3673 flags |= GE_ALLPARTS;
3676 export_opts[i].ch = -1;
3677 filename[0] = '\0';
3679 if(mn_total_cur(msgmap) <= 1L){
3680 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld", mn_get_cur(msgmap));
3681 nmsgs[sizeof(nmsgs)-1] = '\0';
3683 else{
3684 snprintf(nmsgs, sizeof(nmsgs), "%s messages", comatose(mn_total_cur(msgmap)));
3685 nmsgs[sizeof(nmsgs)-1] = '\0';
3688 r = get_export_filename(state, filename, NULL, full_filename,
3689 sizeof(filename), nmsgs, "EXPORT",
3690 export_opts, &rflags, qline, flags, &history);
3692 if(r < 0){
3693 switch(r){
3694 case -1:
3695 cmd_cancelled("Export message");
3696 break;
3698 case -2:
3699 q_status_message1(SM_ORDER, 0, 2,
3700 _("Can't export to file outside of %s"),
3701 VAR_OPER_DIR);
3702 break;
3705 goto fini;
3707 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3708 else if(r == 12){ /* Download */
3709 char cmd[MAXPATH], *tfp = NULL;
3710 int next = 0;
3711 PIPE_S *syspipe;
3712 STORE_S *so;
3713 gf_io_t pc;
3715 if(ps_global->restricted){
3716 q_status_message(SM_ORDER | SM_DING, 3, 3,
3717 "Download disallowed in restricted mode");
3718 goto fini;
3721 err = NULL;
3722 tfp = temp_nam(NULL, "pd");
3723 build_updown_cmd(cmd, sizeof(cmd), ps_global->VAR_DOWNLOAD_CMD_PREFIX,
3724 ps_global->VAR_DOWNLOAD_CMD, tfp);
3725 dprint((1, "Download cmd called: \"%s\"\n", cmd));
3726 if((so = so_get(FileStar, tfp, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){
3727 gf_set_so_writec(&pc, so);
3729 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
3730 if(!(state->mail_stream
3731 && (rawno = mn_m2raw(msgmap, i)) > 0L
3732 && rawno <= state->mail_stream->nmsgs
3733 && (mc = mail_elt(state->mail_stream, rawno))
3734 && mc->valid))
3735 mc = NULL;
3737 if(!(env = pine_mail_fetchstructure(state->mail_stream,
3738 mn_m2raw(msgmap, i), &b))
3739 || !bezerk_delimiter(env, mc, pc, next++)
3740 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
3741 env, b, NULL, FM_NEW_MESS | FM_NOWRAP, pc)){
3742 q_status_message(SM_ORDER | SM_DING, 3, 3,
3743 err = "Error writing tempfile for download");
3744 break;
3748 gf_clear_so_writec(so);
3749 if(so_give(&so)){ /* close file */
3750 if(!err)
3751 err = "Error writing tempfile for download";
3754 if(!err){
3755 if((syspipe = open_system_pipe(cmd, NULL, NULL,
3756 PIPE_USER | PIPE_RESET,
3757 0, pipe_callback, pipe_report_error)) != NULL)
3758 (void) close_system_pipe(&syspipe, NULL, pipe_callback);
3759 else
3760 q_status_message(SM_ORDER | SM_DING, 3, 3,
3761 err = _("Error running download command"));
3764 else
3765 q_status_message(SM_ORDER | SM_DING, 3, 3,
3766 err = "Error building temp file for download");
3768 if(tfp){
3769 our_unlink(tfp);
3770 fs_give((void **)&tfp);
3773 if(!err)
3774 q_status_message(SM_ORDER, 0, 3, _("Download Command Completed"));
3776 goto fini;
3778 #endif /* !(DOS || MAC) */
3781 if(rflags & GER_APPEND)
3782 leading_nl = 1;
3783 else
3784 leading_nl = 0;
3786 dprint((5, "Opening file \"%s\" for export\n",
3787 full_filename ? full_filename : "?"));
3789 if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE))){
3790 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3791 /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
3792 _("Error opening file \"%s\" to export message: %s"),
3793 full_filename, error_description(errno));
3794 goto fini;
3796 else
3797 gf_set_so_writec(&pc, store);
3799 err = NULL;
3800 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), count++){
3801 env = pine_mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i),
3802 &b);
3803 if(!env) {
3804 err = _("Can't export message. Error accessing mail folder");
3805 failure = 1;
3806 break;
3809 if(!(state->mail_stream
3810 && (rawno = mn_m2raw(msgmap, i)) > 0L
3811 && rawno <= state->mail_stream->nmsgs
3812 && (mc = mail_elt(state->mail_stream, rawno))
3813 && mc->valid))
3814 mc = NULL;
3816 start_of_append = so_tell(store);
3817 if(!bezerk_delimiter(env, mc, pc, leading_nl)
3818 || !format_message(mn_m2raw(msgmap, i), env, b, NULL,
3819 FM_NEW_MESS | FM_NOWRAP, pc)){
3820 orig_errno = errno; /* save in case things are really bad */
3821 failure = 1; /* pop out of here */
3822 break;
3825 leading_nl = 1;
3828 gf_clear_so_writec(store);
3829 if(so_give(&store)) /* release storage */
3830 failure++;
3832 if(failure){
3833 our_truncate(full_filename, (off_t)start_of_append);
3834 if(err){
3835 dprint((1, "FAILED Export: fetch(%ld): %s\n",
3836 i, err ? err : "?"));
3837 q_status_message(SM_ORDER | SM_DING, 3, 4, err);
3839 else{
3840 dprint((1, "FAILED Export: file \"%s\" : %s\n",
3841 full_filename ? full_filename : "?",
3842 error_description(orig_errno)));
3843 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3844 /* TRANSLATORS: Error exporting to <filename>: <error text> */
3845 _("Error exporting to \"%s\" : %s"),
3846 filename, error_description(orig_errno));
3849 else{
3850 if(rflags & GER_ALLPARTS && full_filename[0]){
3851 char dir[MAXPATH+1];
3852 char lfile[MAXPATH+1];
3853 int ok = 0, tries = 0, saved = 0, errs = 0, counter = 2;
3854 ATTACH_S *a;
3857 * Now we want to save all of the attachments to a subdirectory.
3858 * To make it easier for us and probably easier for the user, and
3859 * to prevent the user from shooting himself in the foot, we
3860 * make a new subdirectory so that we can't possibly step on
3861 * any existing files, and we don't need any interaction with the
3862 * user while saving.
3864 * We'll just use the directory name full_filename.d or if that
3865 * already exists and isn't empty, we'll try adding a suffix to
3866 * that until we get something to use.
3869 if(strlen(full_filename) + strlen(".d") + 1 > sizeof(dir)){
3870 q_status_message1(SM_ORDER | SM_DING, 3, 4,
3871 _("Can't save attachments, filename too long: %s"),
3872 full_filename);
3873 goto fini;
3876 ok = 0;
3877 snprintf(dir, sizeof(dir), "%.*s.d", MAXPATH-2, full_filename);
3878 dir[sizeof(dir)-1] = '\0';
3880 do {
3881 tries++;
3882 switch(r = is_writable_dir(dir)){
3883 case 0: /* exists and is a writable dir */
3885 * We could figure out if it is empty and use it in
3886 * that case, but that sounds like a lot of work, so
3887 * just fall through to default.
3890 default:
3891 if(strlen(full_filename) + strlen(".d") + 1 +
3892 1 + strlen(long2string((long) tries)) > sizeof(dir)){
3893 q_status_message(SM_ORDER | SM_DING, 3, 4,
3894 "Problem saving attachments");
3895 goto fini;
3898 snprintf(dir, sizeof(dir), "%.*s.d_%s", MAXPATH- (int) strlen(long2string((long) tries))-3, full_filename,
3899 long2string((long) tries));
3900 dir[sizeof(dir)-1] = '\0';
3901 break;
3903 case 3: /* doesn't exist, that's good! */
3904 /* make new directory */
3905 ok++;
3906 break;
3908 } while(!ok && tries < 1000);
3910 if(tries >= 1000){
3911 q_status_message(SM_ORDER | SM_DING, 3, 4,
3912 _("Problem saving attachments"));
3913 goto fini;
3916 /* create the new directory */
3917 if(our_mkdir(dir, 0700)){
3918 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3919 _("Problem saving attachments: %s: %s"), dir,
3920 error_description(errno));
3921 goto fini;
3924 if(!(state->mail_stream
3925 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3926 && rawno <= state->mail_stream->nmsgs
3927 && (env=pine_mail_fetchstructure(state->mail_stream,rawno,&b))
3928 && b)){
3929 q_status_message(SM_ORDER | SM_DING, 3, 4,
3930 _("Problem reading message"));
3931 goto fini;
3934 zero_atmts(state->atmts);
3935 describe_mime(b, "", 1, 1, 0, 0);
3937 a = state->atmts;
3938 if(a && a->description) /* skip main body part */
3939 a++;
3941 for(; a->description != NULL; a++){
3942 /* skip over these parts of the message */
3943 if(MIME_MSG_A(a) || MIME_DGST_A(a) || MIME_VCARD_A(a))
3944 continue;
3946 lfile[0] = '\0';
3947 (void) get_filename_parameter(lfile, sizeof(lfile), a->body, NULL);
3949 if(lfile[0] == '\0'){ /* MAXPATH + 1 = sizeof(lfile) */
3950 snprintf(lfile, sizeof(lfile), "part_%.*s", MAXPATH+1-6,
3951 a->number ? a->number : "?");
3952 lfile[sizeof(lfile)-1] = '\0';
3955 if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + 1
3956 > sizeof(filename)){
3957 dprint((2,
3958 "FAILED Att Export: name too long: %s\n",
3959 dir, S_FILESEP, lfile));
3960 errs++;
3961 continue;
3964 /* although files are being saved in a unique directory, there is
3965 * no guarantee that attachment names have unique names, so we have
3966 * to make sure that we are not constantly rewriting the same file name
3967 * over and over. In order to avoid this we test if the file already exists,
3968 * and if so, we write a counter name in the file name, just before the
3969 * extension of the file, and separate it with an underscore.
3971 snprintf(filename, sizeof(filename), "%.*s%.*s%.*s", (int) strlen(dir), dir,
3972 (int) strlen(S_FILESEP), S_FILESEP,
3973 MAXPATH - (int) strlen(dir) - (int) strlen(S_FILESEP), lfile);
3974 filename[sizeof(filename)-1] = '\0';
3975 while((ok = can_access(filename, ACCESS_EXISTS)) == 0 && errs == 0){
3976 char *ext, count[MAXPATH+1];
3977 unsigned long total;
3978 snprintf(count, sizeof(count), "%d", counter);
3979 if((ext = strrchr(lfile, '.')) != NULL)
3980 *ext = '\0';
3981 total = strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + strlen(count) + 3
3982 + (ext ? strlen(ext+1) : 0);
3983 if(total > sizeof(filename)){
3984 dprint((2,
3985 "FAILED Att Export: name too long: %s\n",
3986 dir, S_FILESEP, lfile));
3987 errs++;
3988 continue;
3990 snprintf(filename, sizeof(filename), "%.*s%.*s%.*s%.*s%.*d%.*s%.*s",
3991 (int) strlen(dir), dir, (int) strlen(S_FILESEP), S_FILESEP,
3992 (int) strlen(lfile), lfile,
3993 ext ? 1 : 0, ext ? "_" : "",
3994 (int) strlen(count), counter++,
3995 ext ? 1 : 0, ext ? "." : "",
3996 ext ? (int) (sizeof(filename) - total) : 0,
3997 ext ? ext+1 : "");
3998 filename[sizeof(filename)-1] = '\0';
4001 if(write_attachment_to_file(state->mail_stream, rawno,
4002 a, GER_NONE, filename) == 1)
4003 saved++;
4004 else
4005 errs++;
4008 if(errs){
4009 if(saved)
4010 q_status_message1(SM_ORDER, 3, 3,
4011 "Errors saving some attachments, %s attachments saved",
4012 long2string((long) saved));
4013 else
4014 q_status_message(SM_ORDER, 3, 3,
4015 _("Problems saving attachments"));
4017 else{
4018 if(saved)
4019 q_status_message2(SM_ORDER, 0, 3,
4020 /* TRANSLATORS: Saved <how many> attachments to <directory name> */
4021 _("Saved %s attachments to %s"),
4022 long2string((long) saved), dir);
4023 else
4024 q_status_message(SM_ORDER, 3, 3, _("No attachments to save"));
4027 else if(mn_total_cur(msgmap) > 1L)
4028 q_status_message4(SM_ORDER,0,3,
4029 "%s message%s %s to file \"%s\"",
4030 long2string(count), plural(count),
4031 rflags & GER_OVER
4032 ? "overwritten"
4033 : rflags & GER_APPEND ? "appended" : "exported",
4034 filename);
4035 else
4036 q_status_message3(SM_ORDER,0,3,
4037 "Message %s %s to file \"%s\"",
4038 long2string(mn_get_cur(msgmap)),
4039 rflags & GER_OVER
4040 ? "overwritten"
4041 : rflags & GER_APPEND ? "appended" : "exported",
4042 filename);
4043 rv++;
4046 fini:
4047 if(MCMD_ISAGG(aopt))
4048 restore_selected(msgmap);
4050 return rv;
4055 * Ask user what file to export to. Export from srcstore to that file.
4057 * Args ps -- pine struct
4058 * srctext -- pointer to source text
4059 * srctype -- type of that source text
4060 * prompt_msg -- see get_export_filename
4061 * lister_msg -- "
4063 * Returns: != 0 : error
4064 * 0 : ok
4067 simple_export(struct pine *ps, void *srctext, SourceType srctype, char *prompt_msg, char *lister_msg)
4069 int r = 1, rflags = GER_NONE;
4070 char filename[MAXPATH+1], full_filename[MAXPATH+1];
4071 STORE_S *store = NULL;
4072 struct variable *vars = ps ? ps->vars : NULL;
4073 static HISTORY_S *history = NULL;
4074 static ESCKEY_S simple_export_opts[] = {
4075 {ctrl('T'), 10, "^T", N_("To Files")},
4076 {-1, 0, NULL, NULL},
4077 {-1, 0, NULL, NULL}};
4079 if(F_ON(F_ENABLE_TAB_COMPLETE,ps)){
4080 simple_export_opts[r].ch = ctrl('I');
4081 simple_export_opts[r].rval = 11;
4082 simple_export_opts[r].name = "TAB";
4083 simple_export_opts[r].label = N_("Complete");
4086 if(!srctext){
4087 q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
4088 r = -3;
4089 goto fini;
4092 simple_export_opts[++r].ch = -1;
4093 filename[0] = '\0';
4094 full_filename[0] = '\0';
4096 r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename),
4097 prompt_msg, lister_msg, simple_export_opts, &rflags,
4098 -FOOTER_ROWS(ps), GE_IS_EXPORT, &history);
4100 if(r < 0)
4101 goto fini;
4102 else if(!full_filename[0]){
4103 r = -1;
4104 goto fini;
4107 dprint((5, "Opening file \"%s\" for export\n",
4108 full_filename ? full_filename : "?"));
4110 if((store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
4111 char *pipe_err;
4112 gf_io_t pc, gc;
4114 gf_set_so_writec(&pc, store);
4115 gf_set_readc(&gc, srctext, (srctype == CharStar)
4116 ? strlen((char *)srctext)
4117 : 0L,
4118 srctype, 0);
4119 gf_filter_init();
4120 if((pipe_err = gf_pipe(gc, pc)) != NULL){
4121 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4122 /* TRANSLATORS: Problem saving to <filename>: <error text> */
4123 _("Problem saving to \"%s\": %s"),
4124 filename, pipe_err);
4125 r = -3;
4127 else
4128 r = 0;
4130 gf_clear_so_writec(store);
4131 if(so_give(&store)){
4132 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4133 _("Problem saving to \"%s\": %s"),
4134 filename, error_description(errno));
4135 r = -3;
4138 else{
4139 q_status_message2(SM_ORDER | SM_DING, 3, 4,
4140 _("Error opening file \"%s\" for export: %s"),
4141 full_filename, error_description(errno));
4142 r = -3;
4145 fini:
4146 switch(r){
4147 case 0:
4148 /* overloading full_filename */
4149 snprintf(full_filename, sizeof(full_filename), "%c%s",
4150 (prompt_msg && prompt_msg[0])
4151 ? (islower((unsigned char)prompt_msg[0])
4152 ? toupper((unsigned char)prompt_msg[0]) : prompt_msg[0])
4153 : 'T',
4154 (prompt_msg && prompt_msg[0]) ? prompt_msg+1 : "ext");
4155 full_filename[sizeof(full_filename)-1] = '\0';
4156 q_status_message3(SM_ORDER,0,2,"%s %s to \"%s\"",
4157 full_filename,
4158 rflags & GER_OVER
4159 ? "overwritten"
4160 : rflags & GER_APPEND ? "appended" : "exported",
4161 filename);
4162 break;
4164 case -1:
4165 cmd_cancelled("Export");
4166 break;
4168 case -2:
4169 q_status_message1(SM_ORDER, 0, 2,
4170 _("Can't export to file outside of %s"), VAR_OPER_DIR);
4171 break;
4174 ps->mangled_footer = 1;
4175 return(r);
4180 * Ask user what file to export to.
4182 * filename -- On input, this is the filename to start with. On exit,
4183 * this is the filename chosen. (but this isn't used)
4184 * deefault -- This is the default value if user hits return. The
4185 * prompt will have [deefault] added to it automatically.
4186 * full_filename -- This is the full filename on exit.
4187 * len -- Minimum length of _both_ filename and full_filename.
4188 * prompt_msg -- Message to insert in prompt.
4189 * lister_msg -- Message to insert in file_lister.
4190 * opts -- Key options.
4191 * There is a tangled relationship between the callers
4192 * and this routine as far as opts are concerned. Some
4193 * of the opts are handled here. In particular, r == 3,
4194 * r == 10, r == 11, and r == 13 are all handled here.
4195 * Don't use those values unless you want what happens
4196 * here. r == 12 and others are handled by the caller.
4197 * rflags -- Return flags
4198 * GER_OVER - overwrite of existing file
4199 * GER_APPEND - append of existing file
4200 * else file did not exist before
4202 * GER_ALLPARTS - AllParts toggle was turned on
4204 * qline -- Command line to prompt on.
4205 * flags -- Logically OR'd flags
4206 * GE_IS_EXPORT - The command was an Export command
4207 * so the prompt should include
4208 * EXPORT:.
4209 * GE_SEQ_SENSITIVE - The command that got us here is
4210 * sensitive to sequence number changes
4211 * caused by unsolicited expunges.
4212 * GE_NO_APPEND - We will not allow append to an
4213 * existing file, only removal of the
4214 * file if it exists.
4215 * GE_IS_IMPORT - We are selecting for reading.
4216 * No overwriting or checking for
4217 * existence at all. Don't use this
4218 * together with GE_NO_APPEND.
4219 * GE_ALLPARTS - Turn on AllParts toggle.
4220 * GE_BINARY - Turn on Binary toggle.
4222 * Returns: -1 cancelled
4223 * -2 prohibited by VAR_OPER_DIR
4224 * -3 other error, already reported here
4225 * 0 ok
4226 * 12 user chose 12 command from opts
4229 get_export_filename(struct pine *ps, char *filename, char *deefault,
4230 char *full_filename, size_t len, char *prompt_msg,
4231 char *lister_msg, ESCKEY_S *optsarg, int *rflags,
4232 int qline, int flags, HISTORY_S **history)
4234 char dir[MAXPATH+1], dir2[MAXPATH+1], orig_dir[MAXPATH+1];
4235 char precolon[MAXPATH+1], postcolon[MAXPATH+1];
4236 char filename2[MAXPATH+1], tmp[MAXPATH+1], *fn, *ill;
4237 int l, i, ku = -1, kp = -1, r, fatal, homedir = 0, was_abs_path=0, avail, ret = 0;
4238 int allparts = 0, binary = 0;
4239 char prompt_buf[400];
4240 char def[500];
4241 ESCKEY_S *opts = NULL;
4242 struct variable *vars = ps->vars;
4243 static HISTORY_S *dir_hist = NULL;
4244 static char *last;
4245 int pos, hist_len = 0;
4248 /* we will fake a history with the ps_global->VAR_HISTORY variable
4249 * We fake that we combine this variable into a history variable
4250 * by stacking VAR_HISTORY on top of dir_hist. We keep track of this
4251 * by looking at the variable pos.
4253 if(ps_global->VAR_HISTORY != NULL)
4254 for(hist_len = 0; ps_global->VAR_HISTORY[hist_len]
4255 && ps_global->VAR_HISTORY[hist_len][0]; hist_len++)
4258 pos = hist_len + items_in_hist(dir_hist);
4260 if(flags & GE_ALLPARTS || history || dir_hist){
4262 * Copy the opts and add one to the end of the list.
4264 for(i = 0; optsarg[i].ch != -1; i++)
4267 if(dir_hist || hist_len > 0)
4268 i += 2;
4270 if(history)
4271 i += dir_hist || hist_len > 0 ? 2 : 4;
4273 if(flags & GE_ALLPARTS)
4274 i++;
4276 if(flags & GE_BINARY)
4277 i++;
4279 opts = (ESCKEY_S *) fs_get((i+1) * sizeof(*opts));
4280 memset(opts, 0, (i+1) * sizeof(*opts));
4282 for(i = 0; optsarg[i].ch != -1; i++){
4283 opts[i].ch = optsarg[i].ch;
4284 opts[i].rval = optsarg[i].rval;
4285 opts[i].name = optsarg[i].name; /* no need to make a copy */
4286 opts[i].label = optsarg[i].label; /* " */
4289 if(flags & GE_ALLPARTS){
4290 allparts = i;
4291 opts[i].ch = ctrl('P');
4292 opts[i].rval = 13;
4293 opts[i].name = "^P";
4294 /* TRANSLATORS: Export all attachment parts */
4295 opts[i++].label = N_("AllParts");
4298 if(flags & GE_BINARY){
4299 binary = i;
4300 opts[i].ch = ctrl('R');
4301 opts[i].rval = 15;
4302 opts[i].name = "^R";
4303 opts[i++].label = N_("Binary");
4306 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4307 SIZEOF_20KBUF, filename);
4308 #ifndef _WINDOWS
4309 /* In the Windows operating system we always return the UTF8 encoded name */
4310 if(strcmp(tmp_20k_buf, filename)){
4311 opts[i].ch = ctrl('N');
4312 opts[i].rval = 40;
4313 opts[i].name = "^N";
4314 opts[i++].label = "Name UTF8";
4316 #else
4317 strncpy(filename, tmp_20k_buf, len);
4318 filename[len-1] = '\0';
4319 #endif /* _WINDOWS */
4321 if(dir_hist || hist_len > 0){
4322 opts[i].ch = ctrl('Y');
4323 opts[i].rval = 32;
4324 opts[i].name = "";
4325 kp = i;
4326 opts[i++].label = "";
4328 opts[i].ch = ctrl('V');
4329 opts[i].rval = 33;
4330 opts[i].name = "";
4331 opts[i++].label = "";
4334 if(history){
4335 opts[i].ch = KEY_UP;
4336 opts[i].rval = 30;
4337 opts[i].name = "";
4338 ku = i;
4339 opts[i++].label = "";
4341 opts[i].ch = KEY_DOWN;
4342 opts[i].rval = 31;
4343 opts[i].name = "";
4344 opts[i++].label = "";
4347 opts[i].ch = -1;
4349 if(history)
4350 init_hist(history, HISTSIZE);
4351 init_hist(&dir_hist, HISTSIZE); /* reset history to the end */
4353 else
4354 opts = optsarg;
4356 if(rflags)
4357 *rflags = GER_NONE;
4359 if(F_ON(F_USE_CURRENT_DIR, ps))
4360 dir[0] = '\0';
4361 else if(VAR_OPER_DIR){
4362 strncpy(dir, VAR_OPER_DIR, sizeof(dir));
4363 dir[sizeof(dir)-1] = '\0';
4365 #if defined(DOS) || defined(OS2)
4366 else if(VAR_FILE_DIR){
4367 strncpy(dir, VAR_FILE_DIR, sizeof(dir));
4368 dir[sizeof(dir)-1] = '\0';
4370 #endif
4371 else{
4372 dir[0] = '~';
4373 dir[1] = '\0';
4374 homedir=1;
4376 strncpy(orig_dir, dir, sizeof(orig_dir));
4377 orig_dir[sizeof(orig_dir)-1] = '\0';
4379 postcolon[0] = '\0';
4380 strncpy(precolon, dir, sizeof(precolon));
4381 precolon[sizeof(precolon)-1] = '\0';
4382 if(deefault){
4383 strncpy(def, deefault, sizeof(def)-1);
4384 def[sizeof(def)-1] = '\0';
4385 removing_leading_and_trailing_white_space(def);
4387 else
4388 def[0] = '\0';
4390 avail = MAX(20, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - MIN_OPT_ENT_WIDTH;
4392 /*---------- Prompt the user for the file name -------------*/
4393 while(1){
4394 int oeflags;
4395 char dirb[50], fileb[50];
4396 int l1, l2, l3, l4, l5, needed;
4397 char *p, p1[100], p2[100], *p3, p4[100], p5[100];
4399 snprintf(p1, sizeof(p1), "%sCopy ",
4400 (flags & GE_IS_EXPORT) ? "EXPORT: " :
4401 (flags & GE_IS_IMPORT) ? "IMPORT: " : "SAVE: ");
4402 p1[sizeof(p1)-1] = '\0';
4403 l1 = strlen(p1);
4405 strncpy(p2, prompt_msg ? prompt_msg : "", sizeof(p2)-1);
4406 p2[sizeof(p2)-1] = '\0';
4407 l2 = strlen(p2);
4409 if(rflags && *rflags & GER_ALLPARTS)
4410 p3 = " (and atts)";
4411 else
4412 p3 = "";
4414 l3 = strlen(p3);
4416 snprintf(p4, sizeof(p4), " %s file%s%s",
4417 (flags & GE_IS_IMPORT) ? "from" : "to",
4418 is_absolute_path(filename) ? "" : " in ",
4419 is_absolute_path(filename) ? "" :
4420 (!dir[0] ? "current directory"
4421 : (dir[0] == '~' && !dir[1]) ? "home directory"
4422 : short_str(dir,dirb,sizeof(dirb),30,FrontDots)));
4423 p4[sizeof(p4)-1] = '\0';
4424 l4 = strlen(p4);
4426 snprintf(p5, sizeof(p5), "%s%s%s: ",
4427 *def ? " [" : "",
4428 *def ? short_str(def,fileb,sizeof(fileb),40,EndDots) : "",
4429 *def ? "]" : "");
4430 p5[sizeof(p5)-1] = '\0';
4431 l5 = strlen(p5);
4433 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4434 snprintf(p4, sizeof(p4), " %s file%s%s",
4435 (flags & GE_IS_IMPORT) ? "from" : "to",
4436 is_absolute_path(filename) ? "" : " in ",
4437 is_absolute_path(filename) ? "" :
4438 (!dir[0] ? "current dir"
4439 : (dir[0] == '~' && !dir[1]) ? "home dir"
4440 : short_str(dir,dirb,sizeof(dirb),10,FrontDots)));
4441 p4[sizeof(p4)-1] = '\0';
4442 l4 = strlen(p4);
4445 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4446 snprintf(p5, sizeof(p5), "%s%s%s: ",
4447 *def ? " [" : "",
4448 *def ? short_str(def,fileb,sizeof(fileb),
4449 MAX(15,l5-5-needed),EndDots) : "",
4450 *def ? "]" : "");
4451 p5[sizeof(p5)-1] = '\0';
4452 l5 = strlen(p5);
4455 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l2 > 0){
4458 * 14 is about the shortest we can make this, because there are
4459 * fixed length strings of length 14 coming in here.
4461 p = short_str(prompt_msg, p2, sizeof(p2), MAX(14,l2-needed), FrontDots);
4462 if(p != p2){
4463 strncpy(p2, p, sizeof(p2)-1);
4464 p2[sizeof(p2)-1] = '\0';
4467 l2 = strlen(p2);
4470 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4471 strncpy(p1, "Copy ", sizeof(p1)-1);
4472 p1[sizeof(p1)-1] = '\0';
4473 l1 = strlen(p1);
4476 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4477 snprintf(p5, sizeof(p5), "%s%s%s: ",
4478 *def ? " [" : "",
4479 *def ? short_str(def,fileb, sizeof(fileb),
4480 MAX(10,l5-5-needed),EndDots) : "",
4481 *def ? "]" : "");
4482 p5[sizeof(p5)-1] = '\0';
4483 l5 = strlen(p5);
4486 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l3 > 0){
4487 if(needed <= l3 - strlen(" (+ atts)"))
4488 p3 = " (+ atts)";
4489 else if(needed <= l3 - strlen(" (atts)"))
4490 p3 = " (atts)";
4491 else if(needed <= l3 - strlen(" (+)"))
4492 p3 = " (+)";
4493 else if(needed <= l3 - strlen("+"))
4494 p3 = "+";
4495 else
4496 p3 = "";
4498 l3 = strlen(p3);
4501 snprintf(prompt_buf, sizeof(prompt_buf), "%s%s%s%s%s", p1, p2, p3, p4, p5);
4502 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4504 if(kp >= 0){
4505 if(items_in_hist(dir_hist) > 0 || hist_len > 0){ /* any directories */
4506 opts[kp].name = "^Y";
4507 opts[kp].label = "Prev Dir";
4508 opts[kp+1].name = "^V";
4509 opts[kp+1].label = "Next Dir";
4511 else{
4512 opts[kp].name = "";
4513 opts[kp].label = "";
4514 opts[kp+1].name = "";
4515 opts[kp+1].label = "";
4519 if(ku >= 0){
4520 if(items_in_hist(*history) > 0){
4521 opts[ku].name = HISTORY_UP_KEYNAME;
4522 opts[ku].label = HISTORY_KEYLABEL;
4523 opts[ku+1].name = HISTORY_DOWN_KEYNAME;
4524 opts[ku+1].label = HISTORY_KEYLABEL;
4526 else{
4527 opts[ku].name = "";
4528 opts[ku].label = "";
4529 opts[ku+1].name = "";
4530 opts[ku+1].label = "";
4534 oeflags = OE_APPEND_CURRENT |
4535 ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0);
4536 r = optionally_enter(filename, qline, 0, len, prompt_buf,
4537 opts, NO_HELP, &oeflags);
4539 dprint((2, "\n - export_filename = \"%s\", r = %d -\n", filename, r));
4540 /*--- Help ----*/
4541 if(r == 3){
4543 * Helps may not be right if you add another caller or change
4544 * things. Check it out.
4546 if(flags & GE_IS_IMPORT)
4547 helper(h_ge_import, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE);
4548 else if(flags & GE_ALLPARTS)
4549 helper(h_ge_allparts, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4550 else
4551 helper(h_ge_export, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4553 ps->mangled_screen = 1;
4555 continue;
4557 else if(r == 10 || r == 11){ /* Browser or File Completion */
4558 if(filename[0]=='~'){
4559 if(filename[1] == C_FILESEP && filename[2]!='\0'){
4560 precolon[0] = '~';
4561 precolon[1] = '\0';
4562 for(i=0; filename[i+2] != '\0' && i+2 < len-1; i++)
4563 filename[i] = filename[i+2];
4564 filename[i] = '\0';
4565 strncpy(dir, precolon, sizeof(dir)-1);
4566 dir[sizeof(dir)-1] = '\0';
4568 else if(filename[1]=='\0' ||
4569 (filename[1] == C_FILESEP && filename[2] == '\0')){
4570 precolon[0] = '~';
4571 precolon[1] = '\0';
4572 filename[0] = '\0';
4573 strncpy(dir, precolon, sizeof(dir)-1);
4574 dir[sizeof(dir)-1] = '\0';
4577 else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){
4578 if(homedir){
4579 precolon[0] = '~';
4580 precolon[1] = '\0';
4581 strncpy(dir, precolon, sizeof(dir)-1);
4582 dir[sizeof(dir)-1] = '\0';
4584 else{
4585 precolon[0] = '\0';
4586 dir[0] = '\0';
4589 l = MAXPATH;
4590 dir2[0] = '\0';
4591 strncpy(tmp, filename, sizeof(tmp)-1);
4592 tmp[sizeof(tmp)-1] = '\0';
4593 if(*tmp && is_absolute_path(tmp))
4594 fnexpand(tmp, sizeof(tmp));
4595 if(strncmp(tmp,postcolon, strlen(postcolon)))
4596 postcolon[0] = '\0';
4598 if(*tmp && (fn = last_cmpnt(tmp))){
4599 l -= fn - tmp;
4600 strncpy(filename2, fn, sizeof(filename2)-1);
4601 filename2[sizeof(filename2)-1] = '\0';
4602 if(is_absolute_path(tmp)){
4603 strncpy(dir2, tmp, MIN(fn - tmp, sizeof(dir2)-1));
4604 dir2[MIN(fn - tmp, sizeof(dir2)-1)] = '\0';
4605 #ifdef _WINDOWS
4606 if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){
4607 dir2[2] = '\\';
4608 dir2[3] = '\0';
4610 #endif
4611 strncpy(postcolon, dir2, sizeof(postcolon)-1);
4612 postcolon[sizeof(postcolon)-1] = '\0';
4613 precolon[0] = '\0';
4615 else{
4616 char *p = NULL;
4618 * Just building the directory name in dir2,
4619 * full_filename is overloaded.
4621 snprintf(full_filename, len, "%.*s", (int) MIN(fn-tmp,len-1), tmp);
4622 full_filename[len-1] = '\0';
4623 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4624 postcolon[sizeof(postcolon)-1] = '\0';
4625 build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH)
4626 : (dir[0] == '~' && !dir[1])
4627 ? ps->home_dir
4628 : dir,
4629 full_filename, sizeof(dir2));
4630 if(p)
4631 free(p);
4634 else{
4635 if(is_absolute_path(tmp)){
4636 strncpy(dir2, tmp, sizeof(dir2)-1);
4637 dir2[sizeof(dir2)-1] = '\0';
4638 #ifdef _WINDOWS
4639 if(dir2[2]=='\0' && dir2[1]==':'){
4640 dir2[2]='\\';
4641 dir2[3]='\0';
4642 strncpy(postcolon,dir2,sizeof(postcolon)-1);
4643 postcolon[sizeof(postcolon)-1] = '\0';
4645 #endif
4646 filename2[0] = '\0';
4647 precolon[0] = '\0';
4649 else{
4650 strncpy(filename2, tmp, sizeof(filename2)-1);
4651 filename2[sizeof(filename2)-1] = '\0';
4652 if(!dir[0]){
4653 if(getcwd(dir2, sizeof(dir2)) == NULL)
4654 alpine_panic(_("getcwd() call failed at get_export_filename"));
4656 else if(dir[0] == '~' && !dir[1]){
4657 strncpy(dir2, ps->home_dir, sizeof(dir2)-1);
4658 dir2[sizeof(dir2)-1] = '\0';
4660 else{
4661 strncpy(dir2, dir, sizeof(dir2)-1);
4662 dir2[sizeof(dir2)-1] = '\0';
4665 postcolon[0] = '\0';
4669 build_path(full_filename, dir2, filename2, len);
4670 if(!strcmp(full_filename, dir2))
4671 filename2[0] = '\0';
4672 if(full_filename[strlen(full_filename)-1] == C_FILESEP
4673 && isdir(full_filename,NULL,NULL)){
4674 if(strlen(full_filename) == 1)
4675 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4676 else if(filename2[0])
4677 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4678 postcolon[sizeof(postcolon)-1] = '\0';
4679 strncpy(dir2, full_filename, sizeof(dir2)-1);
4680 dir2[sizeof(dir2)-1] = '\0';
4681 filename2[0] = '\0';
4683 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4684 else if(full_filename[strlen(full_filename)-1] == C_FILESEP){
4685 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4686 postcolon[sizeof(postcolon)-1] = '\0';
4687 strncpy(dir2, full_filename, sizeof(dir2)-1);
4688 dir2[sizeof(dir2)-1] = '\0';
4689 filename2[0] = '\0';
4691 #endif
4692 if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1
4693 && strcmp(dir2+1, ":\\"))
4694 /* last condition to prevent stripping of '\\'
4695 in windows partition */
4696 dir2[strlen(dir2)-1] = '\0';
4698 if(r == 10){ /* File Browser */
4699 r = file_lister(lister_msg ? lister_msg : "EXPORT",
4700 dir2, sizeof(dir2), filename2, sizeof(filename2),
4701 TRUE,
4702 (flags & GE_IS_IMPORT) ? FB_READ : FB_SAVE);
4703 #ifdef _WINDOWS
4704 /* Windows has a special "feature" in which entering the file browser will
4705 change the working directory if the directory is changed at all (even
4706 clicking "Cancel" will change the working directory).
4708 if(F_ON(F_USE_CURRENT_DIR, ps))
4709 (void)getcwd(dir2,sizeof(dir2));
4710 #endif
4711 if(isdir(dir2,NULL,NULL)){
4712 strncpy(precolon, dir2, sizeof(precolon)-1);
4713 precolon[sizeof(precolon)-1] = '\0';
4715 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4716 postcolon[sizeof(postcolon)-1] = '\0';
4717 if(r == 1){
4718 build_path(full_filename, dir2, filename2, len);
4719 if(isdir(full_filename, NULL, NULL)){
4720 strncpy(dir, full_filename, sizeof(dir)-1);
4721 dir[sizeof(dir)-1] = '\0';
4722 filename[0] = '\0';
4724 else{
4725 fn = last_cmpnt(full_filename);
4726 strncpy(dir, full_filename,
4727 MIN(fn - full_filename, sizeof(dir)-1));
4728 dir[MIN(fn - full_filename, sizeof(dir)-1)] = '\0';
4729 if(fn - full_filename > 1)
4730 dir[fn - full_filename - 1] = '\0';
4733 if(!strcmp(dir, ps->home_dir)){
4734 dir[0] = '~';
4735 dir[1] = '\0';
4738 strncpy(filename, fn, len-1);
4739 filename[len-1] = '\0';
4742 else{ /* File Completion */
4743 if(!pico_fncomplete(dir2, filename2, sizeof(filename2)))
4744 Writechar(BELL, 0);
4745 strncat(postcolon, filename2,
4746 sizeof(postcolon)-1-strlen(postcolon));
4747 postcolon[sizeof(postcolon)-1] = '\0';
4749 was_abs_path = is_absolute_path(filename);
4751 if(!strcmp(dir, ps->home_dir)){
4752 dir[0] = '~';
4753 dir[1] = '\0';
4756 strncpy(filename, postcolon, len-1);
4757 filename[len-1] = '\0';
4758 strncpy(dir, precolon, sizeof(dir)-1);
4759 dir[sizeof(dir)-1] = '\0';
4761 if(filename[0] == '~' && !filename[1]){
4762 dir[0] = '~';
4763 dir[1] = '\0';
4764 filename[0] = '\0';
4767 continue;
4769 else if(r == 12){ /* Download, caller handles it */
4770 ret = r;
4771 goto done;
4773 else if(r == 13){ /* toggle AllParts bit */
4774 if(rflags){
4775 if(*rflags & GER_ALLPARTS){
4776 *rflags &= ~GER_ALLPARTS;
4777 opts[allparts].label = N_("AllParts");
4779 else{
4780 *rflags |= GER_ALLPARTS;
4781 /* opposite of All Parts, No All Parts */
4782 opts[allparts].label = N_("NoAllParts");
4786 continue;
4788 #if 0
4789 else if(r == 14){ /* List file names matching partial? */
4790 continue;
4792 #endif
4793 else if(r == 15){ /* toggle Binary bit */
4794 if(rflags){
4795 if(*rflags & GER_BINARY){
4796 *rflags &= ~GER_BINARY;
4797 opts[binary].label = N_("Binary");
4799 else{
4800 *rflags |= GER_BINARY;
4801 opts[binary].label = N_("No Binary");
4805 continue;
4807 else if(r == 1){ /* Cancel */
4808 ret = -1;
4809 goto done;
4811 else if(r == 4){
4812 continue;
4814 else if(r >= 30 && r <= 33){
4815 char *p = NULL;
4817 if(r == 30 || r == 31){
4818 if(history){
4819 if(r == 30)
4820 p = get_prev_hist(*history, filename, 0, NULL);
4821 else if (r == 31)
4822 p = get_next_hist(*history, filename, 0, NULL);
4826 if(r == 32 || r == 33){
4827 int nitems = items_in_hist(dir_hist);
4828 if(dir_hist || hist_len > 0){
4829 if(r == 32){
4830 if(pos > 0)
4831 p = hist_in_pos(--pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4832 else p = last;
4834 else if (r == 33){
4835 if(pos < hist_len + nitems)
4836 p = hist_in_pos(++pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4838 if(p == NULL || *p == '\0')
4839 p = orig_dir;
4842 last = p; /* save it! */
4844 if(p != NULL && *p != '\0'){
4845 if(r == 30 || r == 31){
4846 if((fn = last_cmpnt(p)) != NULL){
4847 strncpy(dir, p, MIN(fn - p, sizeof(dir)-1));
4848 dir[MIN(fn - p, sizeof(dir)-1)] = '\0';
4849 if(fn - p > 1)
4850 dir[fn - p - 1] = '\0';
4851 strncpy(filename, fn, len-1);
4852 filename[len-1] = '\0';
4854 } else { /* r == 32 || r == 33 */
4855 strncpy(dir, p, sizeof(dir)-1);
4856 dir[sizeof(dir)-1] = '\0';
4859 if(!strcmp(dir, ps->home_dir)){
4860 dir[0] = '~';
4861 dir[1] = '\0';
4864 else
4865 Writechar(BELL, 0);
4866 continue;
4868 #ifndef _WINDOWS
4869 else if(r == 40){
4870 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4871 SIZEOF_20KBUF, filename);
4872 strncpy(filename, tmp_20k_buf, len);
4873 filename[len-1] = '\0';
4874 continue;
4876 #endif /* _WINDOWS */
4877 else if(r != 0){
4878 Writechar(BELL, 0);
4879 continue;
4882 removing_leading_and_trailing_white_space(filename);
4884 if(!*filename){
4885 if(!*def){ /* Cancel */
4886 ret = -1;
4887 goto done;
4890 strncpy(filename, def, len-1);
4891 filename[len-1] = '\0';
4894 #if defined(DOS) || defined(OS2)
4895 if(is_absolute_path(filename)){
4896 fixpath(filename, len);
4898 #else
4899 if(filename[0] == '~'){
4900 if(fnexpand(filename, len) == NULL){
4901 char *p = strindex(filename, '/');
4902 if(p != NULL)
4903 *p = '\0';
4904 q_status_message1(SM_ORDER | SM_DING, 3, 3,
4905 _("Error expanding file name: \"%s\" unknown user"),
4906 filename);
4907 continue;
4910 #endif
4912 if(is_absolute_path(filename)){
4913 strncpy(full_filename, filename, len-1);
4914 full_filename[len-1] = '\0';
4916 else{
4917 if(!dir[0])
4918 build_path(full_filename, (char *)getcwd(dir,sizeof(dir)),
4919 filename, len);
4920 else if(dir[0] == '~' && !dir[1])
4921 build_path(full_filename, ps->home_dir, filename, len);
4922 else
4923 build_path(full_filename, dir, filename, len);
4926 if((ill = filter_filename(full_filename, &fatal,
4927 ps_global->restricted || ps_global->VAR_OPER_DIR)) != NULL){
4928 if(fatal){
4929 q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill);
4930 continue;
4932 else{
4933 /* BUG: we should beep when the key's pressed rather than bitch later */
4934 /* Warn and ask for confirmation. */
4935 snprintf(prompt_buf, sizeof(prompt_buf), "File name contains a '%s'. %s anyway",
4936 ill, (flags & GE_IS_EXPORT) ? "Export" : "Save");
4937 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4938 if(want_to(prompt_buf, 'n', 0, NO_HELP,
4939 ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y')
4940 continue;
4944 break; /* Must have got an OK file name */
4947 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){
4948 ret = -2;
4949 goto done;
4952 if(!can_access(full_filename, ACCESS_EXISTS)){
4953 int rbflags;
4954 static ESCKEY_S access_opts[] = {
4955 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4956 a file or append to the end of the file */
4957 {'o', 'o', "O", N_("Overwrite")},
4958 {'a', 'a', "A", N_("Append")},
4959 {-1, 0, NULL, NULL}};
4961 rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0);
4963 if(flags & GE_NO_APPEND){
4964 r = strlen(filename);
4965 snprintf(prompt_buf, sizeof(prompt_buf),
4966 /* TRANSLATORS: asking user whether to overwrite a file or not,
4967 File <filename> already exists. Overwrite it ? */
4968 _("File \"%s%s\" already exists. Overwrite it "),
4969 (r > 20) ? "..." : "",
4970 filename + ((r > 20) ? r - 20 : 0));
4971 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4972 if(want_to(prompt_buf, 'n', 'x', NO_HELP, rbflags) == 'y'){
4973 if(rflags)
4974 *rflags |= GER_OVER;
4976 if(our_unlink(full_filename) < 0){
4977 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4978 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4979 _("Cannot remove old %s: %s"),
4980 full_filename, error_description(errno));
4983 else{
4984 ret = -1;
4985 goto done;
4988 else if(!(flags & GE_IS_IMPORT)){
4989 r = strlen(filename);
4990 snprintf(prompt_buf, sizeof(prompt_buf),
4991 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4992 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4993 (r > 20) ? "..." : "",
4994 filename + ((r > 20) ? r - 20 : 0));
4995 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4996 switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global),
4997 access_opts, 'a', 'x', NO_HELP, rbflags)){
4998 case 'o' :
4999 if(rflags)
5000 *rflags |= GER_OVER;
5002 if(our_truncate(full_filename, (off_t)0) < 0)
5003 /* trouble truncating, but we'll give it a try anyway */
5004 q_status_message2(SM_ORDER | SM_DING, 3, 5,
5005 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
5006 _("Warning: Cannot truncate old %s: %s"),
5007 full_filename, error_description(errno));
5008 break;
5010 case 'a' :
5011 if(rflags)
5012 *rflags |= GER_APPEND;
5014 break;
5016 case 'x' :
5017 default :
5018 ret = -1;
5019 goto done;
5024 done:
5025 if(history && ret == 0){
5026 save_hist(*history, full_filename, 0, NULL);
5027 strncpy(tmp, full_filename, MAXPATH);
5028 tmp[MAXPATH] = '\0';
5029 if((fn = strrchr(tmp, C_FILESEP)) != NULL)
5030 *fn = '\0';
5031 else
5032 tmp[0] = '\0';
5033 if(tmp[0])
5034 save_hist(dir_hist, tmp, 0, NULL);
5037 if(opts && opts != optsarg)
5038 fs_give((void **) &opts);
5040 return(ret);
5044 /*----------------------------------------------------------------------
5045 parse the config'd upload/download command
5047 Args: cmd -- buffer to return command fit for shellin'
5048 prefix --
5049 cfg_str --
5050 fname -- file name to build into the command
5052 Returns: pointer to cmd_str buffer or NULL on real bad error
5054 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
5055 cfg_str is written to standard out right before a successful
5056 return of this function. The call immediately following this
5057 function darn well better be the shell exec...
5058 ----*/
5059 char *
5060 build_updown_cmd(char *cmd, size_t cmdlen, char *prefix, char *cfg_str, char *fname)
5062 char *p;
5063 int fname_found = 0;
5065 if(prefix && *prefix){
5066 /* loop thru replacing all occurrences of _FILE_ */
5067 p = strncpy(cmd, prefix, cmdlen);
5068 cmd[cmdlen-1] = '\0';
5069 while((p = strstr(p, "_FILE_")))
5070 rplstr(p, cmdlen-(p-cmd), 6, fname);
5072 fputs(cmd, stdout);
5075 /* loop thru replacing all occurrences of _FILE_ */
5076 p = strncpy(cmd, cfg_str, cmdlen);
5077 cmd[cmdlen-1] = '\0';
5078 while((p = strstr(p, "_FILE_"))){
5079 rplstr(p, cmdlen-(p-cmd), 6, fname);
5080 fname_found = 1;
5083 if(!fname_found)
5084 snprintf(cmd+strlen(cmd), cmdlen-strlen(cmd), " %s", fname);
5086 cmd[cmdlen-1] = '\0';
5088 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
5089 cmd ? cmd : "?"));
5090 return(cmd);
5094 /*----------------------------------------------------------------------
5095 Write a berzerk format message delimiter using the given putc function
5097 Args: e -- envelope of message to write
5098 pc -- function to use
5100 Returns: TRUE if we could write it, FALSE if there was a problem
5102 NOTE: follows delimiter with OS-dependent newline
5103 ----*/
5105 bezerk_delimiter(ENVELOPE *env, MESSAGECACHE *mc, gf_io_t pc, int leading_newline)
5107 MESSAGECACHE telt;
5108 time_t when;
5109 char *p;
5111 /* write "[\n]From mailbox[@host] " */
5112 if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1)
5113 && gf_puts("From ", pc)
5114 && gf_puts((env && env->from) ? env->from->mailbox
5115 : "the-concourse-on-high", pc)
5116 && gf_puts((env && env->from && env->from->host) ? "@" : "", pc)
5117 && gf_puts((env && env->from && env->from->host) ? env->from->host
5118 : "", pc)
5119 && (*pc)(' ')))
5120 return(0);
5122 if(mc && mc->valid)
5123 when = mail_longdate(mc);
5124 else if(env && env->date && env->date[0]
5125 && mail_parse_date(&telt,env->date))
5126 when = mail_longdate(&telt);
5127 else
5128 when = time(0);
5130 p = ctime(&when);
5132 while(p && *p && *p != '\n') /* write date */
5133 if(!(*pc)(*p++))
5134 return(0);
5136 if(!gf_puts(NEWLINE, pc)) /* write terminating newline */
5137 return(0);
5139 return(1);
5143 /*----------------------------------------------------------------------
5144 Execute command to jump to a given message number
5146 Args: qline -- Line to ask question on
5148 Result: returns true if the use selected a new message, false otherwise
5150 ----*/
5151 long
5152 jump_to(MSGNO_S *msgmap, int qline, UCS first_num, SCROLL_S *sparms, CmdWhere in_index)
5154 char jump_num_string[80], *j, prompt[70];
5155 HelpType help;
5156 int rc;
5157 static ESCKEY_S jump_to_key[] = { {0, 0, NULL, NULL},
5158 /* TRANSLATORS: go to First Message */
5159 {ctrl('Y'), 10, "^Y", N_("First Msg")},
5160 {ctrl('V'), 11, "^V", N_("Last Msg")},
5161 {-1, 0, NULL, NULL} };
5163 dprint((4, "\n - jump_to -\n"));
5165 #ifdef DEBUG
5166 if(sparms && sparms->jump_is_debug)
5167 return(get_level(qline, first_num, sparms));
5168 #endif
5170 if(!any_messages(msgmap, NULL, "to Jump to"))
5171 return(0L);
5173 if(first_num && first_num < 0x80 && isdigit((unsigned char) first_num)){
5174 jump_num_string[0] = first_num;
5175 jump_num_string[1] = '\0';
5177 else
5178 jump_num_string[0] = '\0';
5180 if(mn_total_cur(msgmap) > 1L){
5181 snprintf(prompt, sizeof(prompt), "Unselect %s msgs in favor of number to be entered",
5182 comatose(mn_total_cur(msgmap)));
5183 prompt[sizeof(prompt)-1] = '\0';
5184 if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n')
5185 return(0L);
5188 snprintf(prompt, sizeof(prompt), "%s number to jump to : ", in_index == ThrdIndx
5189 ? "Thread"
5190 : "Message");
5191 prompt[sizeof(prompt)-1] = '\0';
5193 help = NO_HELP;
5194 while(1){
5195 int flags = OE_APPEND_CURRENT;
5197 rc = optionally_enter(jump_num_string, qline, 0,
5198 sizeof(jump_num_string), prompt,
5199 jump_to_key, help, &flags);
5200 if(rc == 3){
5201 help = help == NO_HELP
5202 ? (in_index == ThrdIndx ? h_oe_jump_thd : h_oe_jump)
5203 : NO_HELP;
5204 continue;
5206 else if(rc == 10 || rc == 11){
5207 char warning[100];
5208 long closest;
5210 closest = closest_jump_target(rc == 10 ? 1L
5211 : ((in_index == ThrdIndx)
5212 ? msgmap->max_thrdno
5213 : mn_get_total(msgmap)),
5214 ps_global->mail_stream,
5215 msgmap, 0,
5216 in_index, warning, sizeof(warning));
5217 /* ignore warning */
5218 return(closest);
5222 * If we take out the *jump_num_string nonempty test in this if
5223 * then the closest_jump_target routine will offer a jump to the
5224 * last message. However, it is slow because you have to wait for
5225 * the status message and it is annoying for people who hit J command
5226 * by mistake and just want to hit return to do nothing, like has
5227 * always worked. So the test is there for now. Hubert 2002-08-19
5229 * Jumping to first/last message is now possible through ^Y/^V
5230 * commands above. jpf 2002-08-21
5231 * (and through "end" hubert 2006-07-07)
5233 if(rc == 0 && *jump_num_string != '\0'){
5234 removing_leading_and_trailing_white_space(jump_num_string);
5235 for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++)
5238 if(*j != '\0'){
5239 if(!strucmp("end", j))
5240 return((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap));
5242 q_status_message(SM_ORDER | SM_DING, 2, 2,
5243 _("Invalid number entered. Use only digits 0-9"));
5244 jump_num_string[0] = '\0';
5246 else{
5247 char warning[100];
5248 long closest, jump_num;
5250 if(*jump_num_string)
5251 jump_num = atol(jump_num_string);
5252 else
5253 jump_num = -1L;
5255 warning[0] = '\0';
5256 closest = closest_jump_target(jump_num, ps_global->mail_stream,
5257 msgmap,
5258 *jump_num_string ? 0 : 1,
5259 in_index, warning, sizeof(warning));
5260 if(warning[0])
5261 q_status_message(SM_ORDER | SM_DING, 2, 2, warning);
5263 if(closest == jump_num)
5264 return(jump_num);
5266 if(closest == 0L)
5267 jump_num_string[0] = '\0';
5268 else
5269 strncpy(jump_num_string, long2string(closest),
5270 sizeof(jump_num_string));
5273 continue;
5276 if(rc != 4)
5277 break;
5280 return(0L);
5285 * cmd_delete_action - handle msgno advance and such after single message deletion
5287 char *
5288 cmd_delete_action(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
5290 int opts;
5291 long msgno;
5292 char *rv = NULL;
5294 msgno = mn_get_cur(msgmap);
5295 advance_cur_after_delete(state, state->mail_stream, msgmap, in_index);
5297 if(IS_NEWS(state->mail_stream)
5298 || ((state->context_current->use & CNTXT_INCMNG)
5299 && context_isambig(state->cur_folder))){
5301 opts = (NSF_TRUST_FLAGS | NSF_SKIP_CHID);
5302 if(in_index == View)
5303 opts &= ~NSF_SKIP_CHID;
5305 (void)next_sorted_flagged(F_UNDEL|F_UNSEEN, state->mail_stream, msgno, &opts);
5306 if(!(opts & NSF_FLAG_MATCH)){
5307 char nextfolder[MAXPATH];
5309 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
5310 nextfolder[sizeof(nextfolder)-1] = '\0';
5311 rv = next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
5312 state->context_current, NULL, NULL)
5313 ? ". Press TAB for next folder."
5314 : ". No more folders to TAB to.";
5318 return(rv);
5323 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5325 char *
5326 cmd_delete_index(struct pine *state, MSGNO_S *msgmap)
5328 return(cmd_delete_action(state, msgmap,MsgIndx));
5332 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5334 char *
5335 cmd_delete_view(struct pine *state, MSGNO_S *msgmap)
5337 return(cmd_delete_action(state, msgmap, View));
5341 void
5342 advance_cur_after_delete(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, CmdWhere in_index)
5344 long new_msgno, msgno;
5345 int opts;
5347 new_msgno = msgno = mn_get_cur(msgmap);
5348 opts = NSF_TRUST_FLAGS;
5350 if(F_ON(F_DEL_SKIPS_DEL, state)){
5352 if(THREADING() && sp_viewing_a_thread(stream))
5353 opts |= NSF_SKIP_CHID;
5355 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5357 else{
5358 mn_inc_cur(stream, msgmap,
5359 (in_index == View && THREADING()
5360 && sp_viewing_a_thread(stream))
5361 ? MH_THISTHD
5362 : (in_index == View)
5363 ? MH_ANYTHD : MH_NONE);
5364 new_msgno = mn_get_cur(msgmap);
5365 if(new_msgno != msgno)
5366 opts |= NSF_FLAG_MATCH;
5370 * Viewing_a_thread is the complicated case because we want to ignore
5371 * other threads at first and then look in other threads if we have to.
5372 * By ignoring other threads we also ignore collapsed partial threads
5373 * in our own thread.
5375 if(THREADING() && sp_viewing_a_thread(stream) && !(opts & NSF_FLAG_MATCH)){
5376 long rawno, orig_thrdno;
5377 PINETHRD_S *thrd, *topthrd = NULL;
5379 rawno = mn_m2raw(msgmap, msgno);
5380 thrd = fetch_thread(stream, rawno);
5381 if(thrd && thrd->top)
5382 topthrd = fetch_thread(stream, thrd->top);
5384 orig_thrdno = topthrd ? topthrd->thrdno : -1L;
5386 opts = NSF_TRUST_FLAGS;
5387 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5390 * If we got a match, new_msgno may be a message in
5391 * a different thread from the one we are viewing, or it could be
5392 * in a collapsed part of this thread.
5394 if(opts & NSF_FLAG_MATCH){
5395 int ret;
5396 char pmt[128];
5398 topthrd = NULL;
5399 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
5400 if(thrd && thrd->top)
5401 topthrd = fetch_thread(stream, thrd->top);
5404 * If this match is in the same thread we're already in
5405 * then we're done, else we have to ask the user and maybe
5406 * switch threads.
5408 if(!(orig_thrdno > 0L && topthrd
5409 && topthrd->thrdno == orig_thrdno)){
5411 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
5412 if(in_index == View)
5413 snprintf(pmt, sizeof(pmt),
5414 "View message in thread number %.10s",
5415 topthrd ? comatose(topthrd->thrdno) : "?");
5416 else
5417 snprintf(pmt, sizeof(pmt), "View thread number %.10s",
5418 topthrd ? comatose(topthrd->thrdno) : "?");
5420 ret = want_to(pmt, 'y', 'x', NO_HELP, WT_NORM);
5422 else
5423 ret = 'y';
5425 if(ret == 'y'){
5426 unview_thread(state, stream, msgmap);
5427 mn_set_cur(msgmap, new_msgno);
5428 if(THRD_AUTO_VIEW()
5429 && (count_lflags_in_thread(stream, topthrd, msgmap,
5430 MN_NONE) == 1)
5431 && view_thread(state, stream, msgmap, 1)){
5432 if(current_index_state)
5433 msgmap->top_after_thrd = current_index_state->msg_at_top;
5435 state->view_skipped_index = 1;
5436 state->next_screen = mail_view_screen;
5438 else{
5439 view_thread(state, stream, msgmap, 1);
5440 if(current_index_state)
5441 msgmap->top_after_thrd = current_index_state->msg_at_top;
5443 state->next_screen = SCREEN_FUN_NULL;
5446 else
5447 new_msgno = msgno; /* stick with original */
5452 mn_set_cur(msgmap, new_msgno);
5453 if(in_index != View)
5454 adjust_cur_to_visible(stream, msgmap);
5458 #ifdef DEBUG
5459 long
5460 get_level(int qline, UCS first_num, SCROLL_S *sparms)
5462 char debug_num_string[80], *j, prompt[70];
5463 HelpType help;
5464 int rc;
5465 long debug_num;
5467 if(first_num && first_num < 0x80 && isdigit((unsigned char)first_num)){
5468 debug_num_string[0] = first_num;
5469 debug_num_string[1] = '\0';
5470 debug_num = atol(debug_num_string);
5471 *(int *)(sparms->proc.data.p) = debug_num;
5472 q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s",
5473 comatose(debug_num));
5474 return(1L);
5476 else
5477 debug_num_string[0] = '\0';
5479 snprintf(prompt, sizeof(prompt), "Show debug <= this level (0-%d) : ", MAX(debug, 9));
5480 prompt[sizeof(prompt)-1] = '\0';
5482 help = NO_HELP;
5483 while(1){
5484 int flags = OE_APPEND_CURRENT;
5486 rc = optionally_enter(debug_num_string, qline, 0,
5487 sizeof(debug_num_string), prompt,
5488 NULL, help, &flags);
5489 if(rc == 3){
5490 help = help == NO_HELP ? h_oe_debuglevel : NO_HELP;
5491 continue;
5494 if(rc == 0){
5495 removing_leading_and_trailing_white_space(debug_num_string);
5496 for(j=debug_num_string; isdigit((unsigned char)*j); j++)
5499 if(*j != '\0'){
5500 q_status_message(SM_ORDER | SM_DING, 2, 2,
5501 _("Invalid number entered. Use only digits 0-9"));
5502 debug_num_string[0] = '\0';
5504 else{
5505 debug_num = atol(debug_num_string);
5506 if(debug_num < 0)
5507 q_status_message(SM_ORDER | SM_DING, 2, 2,
5508 _("Number should be >= 0"));
5509 else if(debug_num > MAX(debug,9))
5510 q_status_message1(SM_ORDER | SM_DING, 2, 2,
5511 _("Maximum is %s"), comatose(MAX(debug,9)));
5512 else{
5513 *(int *)(sparms->proc.data.p) = debug_num;
5514 q_status_message1(SM_ORDER, 0, 3,
5515 "Show debug <= level %s",
5516 comatose(debug_num));
5517 return(1L);
5521 continue;
5524 if(rc != 4)
5525 break;
5528 return(0L);
5530 #endif /* DEBUG */
5534 * Returns the message number closest to target that isn't hidden.
5535 * Make warning at least 100 chars.
5536 * A return of 0 means there is no message to jump to.
5538 long
5539 closest_jump_target(long int target, MAILSTREAM *stream, MSGNO_S *msgmap, int no_target, CmdWhere in_index, char *warning, size_t warninglen)
5541 long i, start, closest = 0L;
5542 char buf[80];
5543 long maxnum;
5545 warning[0] = '\0';
5546 maxnum = (in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap);
5548 if(no_target){
5549 target = maxnum;
5550 start = 1L;
5551 snprintf(warning, warninglen, "No %s number entered, jump to end? ",
5552 (in_index == ThrdIndx) ? "thread" : "message");
5553 warning[warninglen-1] = '\0';
5555 else if(target < 1L)
5556 start = 1L - target;
5557 else if(target > maxnum)
5558 start = target - maxnum;
5559 else
5560 start = 1L;
5562 if(target > 0L && target <= maxnum)
5563 if(in_index == ThrdIndx
5564 || !msgline_hidden(stream, msgmap, target, 0))
5565 return(target);
5567 for(i = start; target+i <= maxnum || target-i > 0L; i++){
5569 if(target+i > 0L && target+i <= maxnum &&
5570 (in_index == ThrdIndx
5571 || !msgline_hidden(stream, msgmap, target+i, 0))){
5572 closest = target+i;
5573 break;
5576 if(target-i > 0L && target-i <= maxnum &&
5577 (in_index == ThrdIndx
5578 || !msgline_hidden(stream, msgmap, target-i, 0))){
5579 closest = target-i;
5580 break;
5584 strncpy(buf, long2string(closest), sizeof(buf));
5585 buf[sizeof(buf)-1] = '\0';
5587 if(closest == 0L)
5588 strncpy(warning, "Nothing to jump to", warninglen);
5589 else if(target < 1L)
5590 snprintf(warning, warninglen, "%s number (%s) must be at least %s",
5591 (in_index == ThrdIndx) ? "Thread" : "Message",
5592 long2string(target), buf);
5593 else if(target > maxnum)
5594 snprintf(warning, warninglen, "%s number (%s) may be no more than %s",
5595 (in_index == ThrdIndx) ? "Thread" : "Message",
5596 long2string(target), buf);
5597 else if(!no_target)
5598 snprintf(warning, warninglen,
5599 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5600 long2string(target), buf);
5602 warning[warninglen-1] = '\0';
5604 return(closest);
5608 /*----------------------------------------------------------------------
5609 Prompt for folder name to open, expand the name and return it
5611 Args: qline -- Screen line to prompt on
5612 allow_list -- if 1, allow ^T to bring up collection lister
5614 Result: returns the folder name or NULL
5615 pine structure mangled_footer flag is set
5616 may call the collection lister in which case mangled screen will be set
5618 This prompts the user for the folder to open, possibly calling up
5619 the collection lister if the user types ^T.
5620 ----------------------------------------------------------------------*/
5621 char *
5622 broach_folder(int qline, int allow_list, int *notrealinbox, CONTEXT_S **context)
5624 HelpType help;
5625 static char newfolder[MAILTMPLEN];
5626 char expanded[MAXPATH+1],
5627 prompt[MAX_SCREEN_COLS+1],
5628 *last_folder, *p;
5629 unsigned char *f1, *f2, *f3;
5630 static HISTORY_S *history = NULL;
5631 CONTEXT_S *tc, *tc2;
5632 ESCKEY_S ekey[9];
5633 int rc, r, ku = -1, n, flags, last_rc = 0, inbox, done = 0;
5636 * the idea is to provide a clue for the context the file name
5637 * will be saved in (if a non-imap names is typed), and to
5638 * only show the previous if it was also in the same context
5640 help = NO_HELP;
5641 *expanded = '\0';
5642 *newfolder = '\0';
5643 last_folder = NULL;
5644 if(notrealinbox)
5645 (*notrealinbox) = 1;
5647 init_hist(&history, HISTSIZE);
5649 tc = broach_get_folder(context ? *context : NULL, &inbox, NULL);
5651 /* set up extra command option keys */
5652 rc = 0;
5653 ekey[rc].ch = (allow_list) ? ctrl('T') : 0 ;
5654 ekey[rc].rval = (allow_list) ? 2 : 0;
5655 ekey[rc].name = (allow_list) ? "^T" : "";
5656 ekey[rc++].label = (allow_list) ? N_("ToFldrs") : "";
5658 if(ps_global->context_list->next){
5659 ekey[rc].ch = ctrl('P');
5660 ekey[rc].rval = 10;
5661 ekey[rc].name = "^P";
5662 ekey[rc++].label = N_("Prev Collection");
5664 ekey[rc].ch = ctrl('N');
5665 ekey[rc].rval = 11;
5666 ekey[rc].name = "^N";
5667 ekey[rc++].label = N_("Next Collection");
5670 ekey[rc].ch = ctrl('W');
5671 ekey[rc].rval = 17;
5672 ekey[rc].name = "^W";
5673 ekey[rc++].label = N_("INBOX");
5675 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
5676 ekey[rc].ch = TAB;
5677 ekey[rc].rval = 12;
5678 ekey[rc].name = "TAB";
5679 ekey[rc++].label = N_("Complete");
5682 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
5683 ekey[rc].ch = ctrl('X');
5684 ekey[rc].rval = 14;
5685 ekey[rc].name = "^X";
5686 ekey[rc++].label = N_("ListMatches");
5689 if(ps_global->context_list->next && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5690 ekey[rc].ch = KEY_UP;
5691 ekey[rc].rval = 10;
5692 ekey[rc].name = "";
5693 ekey[rc++].label = "";
5695 ekey[rc].ch = KEY_DOWN;
5696 ekey[rc].rval = 11;
5697 ekey[rc].name = "";
5698 ekey[rc++].label = "";
5700 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5701 ekey[rc].ch = KEY_UP;
5702 ekey[rc].rval = 30;
5703 ekey[rc].name = "";
5704 ku = rc;
5705 ekey[rc++].label = "";
5707 ekey[rc].ch = KEY_DOWN;
5708 ekey[rc].rval = 31;
5709 ekey[rc].name = "";
5710 ekey[rc++].label = "";
5713 ekey[rc].ch = -1;
5715 while(!done) {
5717 * Figure out next default value for this context. The idea
5718 * is that in each context the last folder opened is cached.
5719 * It's up to pick it out and display it. This is fine
5720 * and dandy if we've currently got the inbox open, BUT
5721 * if not, make the inbox the default the first time thru.
5723 if(!inbox){
5724 last_folder = ps_global->inbox_name;
5725 inbox = 1; /* pretend we're in inbox from here on out */
5727 else
5728 last_folder = (ps_global->last_unambig_folder[0])
5729 ? ps_global->last_unambig_folder
5730 : ((tc->last_folder[0]) ? tc->last_folder : NULL);
5732 if(last_folder){ /* MAXPATH + 1 = sizeof(expanded) */
5733 unsigned char *fname = folder_name_decoded((unsigned char *)last_folder);
5734 snprintf(expanded, sizeof(expanded), " [%.*s]", MAXPATH+1-5,
5735 fname ? (char *) fname : last_folder);
5736 if(fname) fs_give((void **)&fname);
5738 else
5739 *expanded = '\0';
5741 expanded[sizeof(expanded)-1] = '\0';
5743 /* only show collection number if more than one available */
5744 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5745 snprintf(prompt, sizeof(prompt), "GOTO %s in <%s> %.*s%s: ",
5746 NEWS_TEST(tc) ? "news group" : "folder",
5747 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5748 *expanded ? " " : "");
5749 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5750 snprintf(prompt, sizeof(prompt), "GOTO folder %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5751 *expanded ? " " : "");
5753 prompt[sizeof(prompt)-1] = '\0';
5755 if(utf8_width(prompt) > MAXPROMPT){
5756 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5757 snprintf(prompt, sizeof(prompt), "GOTO <%s> %.*s%s: ",
5758 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5759 *expanded ? " " : "");
5760 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5761 snprintf(prompt, sizeof(prompt), "GOTO %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5762 *expanded ? " " : "");
5764 prompt[sizeof(prompt)-1] = '\0';
5766 if(utf8_width(prompt) > MAXPROMPT){
5767 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5768 snprintf(prompt, sizeof(prompt), "<%s> %.*s%s: ",
5769 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5770 *expanded ? " " : "");
5771 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5772 snprintf(prompt, sizeof(prompt), "%.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5773 *expanded ? " " : "");
5775 prompt[sizeof(prompt)-1] = '\0';
5779 if(ku >= 0){
5780 if(items_in_hist(history) > 1){
5781 ekey[ku].name = HISTORY_UP_KEYNAME;
5782 ekey[ku].label = HISTORY_KEYLABEL;
5783 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
5784 ekey[ku+1].label = HISTORY_KEYLABEL;
5786 else{
5787 ekey[ku].name = "";
5788 ekey[ku].label = "";
5789 ekey[ku+1].name = "";
5790 ekey[ku+1].label = "";
5794 /* is there any other way to do this? The point is that we
5795 * are trying to hide mutf7 from the user, and use the utf8
5796 * equivalent. So we create a variable f to take place of
5797 * newfolder, including content and size. f2 is copy of f1
5798 * that has to freed. Sigh!
5800 f3 = (unsigned char *) cpystr(newfolder);
5801 f1 = fs_get(sizeof(newfolder));
5802 f2 = folder_name_decoded(f3);
5803 if(f3) fs_give((void **)&f3);
5804 strncpy((char *)f1, (char *)f2, sizeof(newfolder));
5805 f1[sizeof(newfolder)-1] = '\0';
5806 if(f2) fs_give((void **)&f2);
5808 flags = OE_APPEND_CURRENT;
5809 rc = optionally_enter((char *) f1, qline, 0, sizeof(newfolder),
5810 (char *) prompt, ekey, help, &flags);
5812 f2 = folder_name_encoded(f1);
5813 strncpy(newfolder, (char *)f2, sizeof(newfolder));
5814 if(f1) fs_give((void **)&f1);
5815 if(f2) fs_give((void **)&f2);
5817 ps_global->mangled_footer = 1;
5819 switch(rc){
5820 case -1 : /* o_e says error! */
5821 q_status_message(SM_ORDER | SM_DING, 3, 3,
5822 _("Error reading folder name"));
5823 return(NULL);
5825 case 0 : /* o_e says normal entry */
5826 removing_trailing_white_space(newfolder);
5827 removing_leading_white_space(newfolder);
5829 if(*newfolder){
5830 char *name, *fullname = NULL;
5831 int exists, breakout = 0;
5833 save_hist(history, newfolder, 0, tc);
5835 if(!(name = folder_is_nick(newfolder, FOLDERS(tc),
5836 FN_WHOLE_NAME)))
5837 name = newfolder;
5839 if(update_folder_spec(expanded, sizeof(expanded), name)){
5840 strncpy(name = newfolder, expanded, sizeof(newfolder));
5841 newfolder[sizeof(newfolder)-1] = '\0';
5844 exists = folder_name_exists(tc, name, &fullname);
5846 if(fullname){
5847 strncpy(name = newfolder, fullname, sizeof(newfolder));
5848 newfolder[sizeof(newfolder)-1] = '\0';
5849 fs_give((void **) &fullname);
5850 breakout = TRUE;
5854 * if we know the things a folder, open it.
5855 * else if we know its a directory, visit it.
5856 * else we're not sure (it either doesn't really
5857 * exist or its unLISTable) so try opening it anyway
5859 if(exists & FEX_ISFILE){
5860 done++;
5861 break;
5863 else if((exists & FEX_ISDIR)){
5864 if(breakout){
5865 CONTEXT_S *fake_context;
5866 char tmp[MAILTMPLEN];
5867 size_t l;
5869 strncpy(tmp, name, sizeof(tmp));
5870 tmp[sizeof(tmp)-2-1] = '\0';
5871 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
5872 if(l < sizeof(tmp)){
5873 tmp[l] = tc->dir->delim;
5874 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
5877 else
5878 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
5880 tmp[sizeof(tmp)-1] = '\0';
5882 fake_context = new_context(tmp, 0);
5883 newfolder[0] = '\0';
5884 done = display_folder_list(&fake_context, newfolder,
5885 1, folders_for_goto);
5886 free_context(&fake_context);
5887 break;
5889 else if(!(tc->use & CNTXT_INCMNG)){
5890 done = display_folder_list(&tc, newfolder,
5891 1, folders_for_goto);
5892 break;
5895 else if((exists & FEX_ERROR)){
5896 q_status_message1(SM_ORDER, 0, 3,
5897 _("Problem accessing folder \"%s\""),
5898 newfolder);
5899 return(NULL);
5901 else{
5902 done++;
5903 break;
5906 if(exists == FEX_ERROR)
5907 q_status_message1(SM_ORDER, 0, 3,
5908 _("Problem accessing folder \"%s\""),
5909 newfolder);
5910 else if(tc->use & CNTXT_INCMNG)
5911 q_status_message1(SM_ORDER, 0, 3,
5912 _("Can't find Incoming Folder: %s"),
5913 newfolder);
5914 else if(context_isambig(newfolder))
5915 q_status_message2(SM_ORDER, 0, 3,
5916 _("Can't find folder \"%s\" in %s"),
5917 newfolder, (void *) tc->nickname);
5918 else
5919 q_status_message1(SM_ORDER, 0, 3,
5920 _("Can't find folder \"%s\""),
5921 newfolder);
5923 return(NULL);
5925 else if(last_folder){
5926 if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX
5927 && !strucmp(last_folder, ps_global->inbox_name)
5928 && tc == ((ps_global->context_list->use & CNTXT_INCMNG)
5929 ? ps_global->context_list->next : ps_global->context_list)){
5930 if(notrealinbox)
5931 (*notrealinbox) = 0;
5933 tc = ps_global->context_list;
5936 strncpy(newfolder, last_folder, sizeof(newfolder));
5937 newfolder[sizeof(newfolder)-1] = '\0';
5938 save_hist(history, newfolder, 0, tc);
5939 done++;
5940 break;
5942 /* fall thru like they cancelled */
5944 case 1 : /* o_e says user cancel */
5945 cmd_cancelled("Open folder");
5946 return(NULL);
5948 case 2 : /* o_e says user wants list */
5949 r = display_folder_list(&tc, newfolder, 0, folders_for_goto);
5950 if(r)
5951 done++;
5953 break;
5955 case 3 : /* o_e says user wants help */
5956 help = help == NO_HELP ? h_oe_broach : NO_HELP;
5957 break;
5959 case 4 : /* redraw */
5960 break;
5962 case 10 : /* Previous collection */
5963 tc2 = ps_global->context_list;
5964 while(tc2->next && tc2->next != tc)
5965 tc2 = tc2->next;
5967 tc = tc2;
5968 break;
5970 case 11 : /* Next collection */
5971 tc = (tc->next) ? tc->next : ps_global->context_list;
5972 break;
5974 case 12 : /* file name completion */
5975 if(!folder_complete(tc, newfolder, sizeof(newfolder), &n)){
5976 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
5977 r = display_folder_list(&tc, newfolder, 1,folders_for_goto);
5978 if(r)
5979 done++; /* bingo! */
5980 else
5981 rc = 0; /* burn last_rc */
5983 else
5984 Writechar(BELL, 0);
5987 break;
5989 case 14 : /* file name completion */
5990 r = display_folder_list(&tc, newfolder, 2, folders_for_goto);
5991 if(r)
5992 done++; /* bingo! */
5993 else
5994 rc = 0; /* burn last_rc */
5996 break;
5998 case 17 : /* GoTo INBOX */
5999 done++;
6000 strncpy(newfolder, ps_global->inbox_name, sizeof(newfolder)-1);
6001 newfolder[sizeof(newfolder)-1] = '\0';
6002 if(notrealinbox)
6003 (*notrealinbox) = 0;
6005 tc = ps_global->context_list;
6006 save_hist(history, newfolder, 0, tc);
6008 break;
6010 case 30 :
6011 if((p = get_prev_hist(history, newfolder, 0, tc)) != NULL){
6012 strncpy(newfolder, p, sizeof(newfolder));
6013 newfolder[sizeof(newfolder)-1] = '\0';
6014 if(history->hist[history->curindex])
6015 tc = history->hist[history->curindex]->cntxt;
6017 else
6018 Writechar(BELL, 0);
6020 break;
6022 case 31 :
6023 if((p = get_next_hist(history, newfolder, 0, tc)) != NULL){
6024 strncpy(newfolder, p, sizeof(newfolder));
6025 newfolder[sizeof(newfolder)-1] = '\0';
6026 if(history->hist[history->curindex])
6027 tc = history->hist[history->curindex]->cntxt;
6029 else
6030 Writechar(BELL, 0);
6032 break;
6034 default :
6035 alpine_panic("Unhandled case");
6036 break;
6039 last_rc = rc;
6042 dprint((2, "broach folder, name entered \"%s\"\n",
6043 newfolder ? newfolder : "?"));
6045 /*-- Just check that we can expand this. It gets done for real later --*/
6046 strncpy(expanded, newfolder, sizeof(expanded));
6047 expanded[sizeof(expanded)-1] = '\0';
6049 if(!expand_foldername(expanded, sizeof(expanded))) {
6050 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
6051 expanded ? expanded : "?"));
6052 return(NULL);
6055 *context = tc;
6056 return(newfolder);
6060 /*----------------------------------------------------------------------
6061 Check to see if user wants to reopen dead stream.
6063 Args: ps --
6064 reopenp --
6066 Result: 1 if the folder was successfully updatedn
6067 0 if not necessary
6069 ----*/
6071 ask_mailbox_reopen(struct pine *ps, int *reopenp)
6073 if(((ps->mail_stream->dtb
6074 && ((ps->mail_stream->dtb->flags & DR_NONEWMAIL)
6075 || (ps->mail_stream->rdonly
6076 && ps->mail_stream->dtb->flags & DR_NONEWMAILRONLY)))
6077 && (ps->reopen_rule == REOPEN_ASK_ASK_Y
6078 || ps->reopen_rule == REOPEN_ASK_ASK_N
6079 || ps->reopen_rule == REOPEN_ASK_NO_Y
6080 || ps->reopen_rule == REOPEN_ASK_NO_N))
6081 || ((ps->mail_stream->dtb
6082 && ps->mail_stream->rdonly
6083 && !(ps->mail_stream->dtb->flags & DR_LOCAL))
6084 && (ps->reopen_rule == REOPEN_YES_ASK_Y
6085 || ps->reopen_rule == REOPEN_YES_ASK_N
6086 || ps->reopen_rule == REOPEN_ASK_ASK_Y
6087 || ps->reopen_rule == REOPEN_ASK_ASK_N))){
6088 int deefault;
6090 switch(ps->reopen_rule){
6091 case REOPEN_YES_ASK_Y:
6092 case REOPEN_ASK_ASK_Y:
6093 case REOPEN_ASK_NO_Y:
6094 deefault = 'y';
6095 break;
6097 default:
6098 deefault = 'n';
6099 break;
6102 switch(want_to("Re-open folder to check for new messages", deefault,
6103 'x', h_reopen_folder, WT_NORM)){
6104 case 'y':
6105 (*reopenp)++;
6106 break;
6108 case 'x':
6109 return(-1);
6113 return(0);
6118 /*----------------------------------------------------------------------
6119 Check to see if user input is in form of old c-client mailbox speck
6121 Args: old --
6122 new --
6124 Result: 1 if the folder was successfully updatedn
6125 0 if not necessary
6127 ----*/
6129 update_folder_spec(char *new, size_t newlen, char *old)
6131 char *p, *orignew;
6132 int nntp = 0;
6134 orignew = new;
6135 if(*(p = old) == '*') /* old form? */
6136 old++;
6138 if(*old == '{') /* copy host spec */
6140 switch(*new = *old++){
6141 case '\0' :
6142 return(FALSE);
6144 case '/' :
6145 if(!struncmp(old, "nntp", 4))
6146 nntp++;
6148 break;
6150 default :
6151 break;
6153 while(*new++ != '}' && (new-orignew) < newlen-1);
6155 if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){
6157 * OK, some heuristics here. If it looks like a newsgroup
6158 * then we plunk it into the #news namespace else we
6159 * assume that they're trying to get at a #public folder...
6161 for(p = old;
6162 *p && (isalnum((unsigned char) *p) || strindex(".-", *p));
6163 p++)
6166 sstrncpy(&new, (*p && !nntp) ? "#public/" : "#news.", newlen-(new-orignew));
6167 strncpy(new, old, newlen-(new-orignew));
6168 return(TRUE);
6171 orignew[newlen-1] = '\0';
6173 return(FALSE);
6177 /*----------------------------------------------------------------------
6178 Open the requested folder in the requested context
6180 Args: state -- usual pine state struct
6181 newfolder -- folder to open
6182 new_context -- folder context might live in
6183 stream -- candidate for recycling
6185 Result: New folder open or not (if error), and we're set to
6186 enter the index screen.
6187 ----*/
6188 void
6189 visit_folder(struct pine *state, char *newfolder, CONTEXT_S *new_context,
6190 MAILSTREAM *stream, long unsigned int flags)
6192 dprint((9, "visit_folder(%s, %s)\n",
6193 newfolder ? newfolder : "?",
6194 (new_context && new_context->context)
6195 ? new_context->context : "(NULL)"));
6197 if(ps_global && ps_global->ttyo){
6198 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
6199 ps_global->mangled_footer = 1;
6202 if(do_broach_folder(newfolder, new_context, stream ? &stream : NULL,
6203 flags) >= 0
6204 || !sp_flagged(state->mail_stream, SP_LOCKED))
6205 state->next_screen = mail_index_screen;
6206 else
6207 state->next_screen = folder_screen;
6211 /*----------------------------------------------------------------------
6212 Move read messages from folder if listed in archive
6214 Args:
6216 ----*/
6218 read_msg_prompt(long int n, char *f)
6220 char buf[MAX_SCREEN_COLS+1];
6222 snprintf(buf, sizeof(buf), "Save the %ld read message%s in \"%s\"", n, plural(n), f);
6223 buf[sizeof(buf)-1] = '\0';
6224 return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y');
6228 /*----------------------------------------------------------------------
6229 Print current message[s] or folder index
6231 Args: state -- pointer to struct holding a bunch of pine state
6232 msgmap -- table mapping msg nums to c-client sequence nums
6233 aopt -- aggregate options
6234 in_index -- boolean indicating we're called from Index Screen
6236 Filters the original header and sends stuff to printer
6237 ---*/
6239 cmd_print(struct pine *state, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
6241 char prompt[250];
6242 long i, msgs, rawno;
6243 int next = 0, do_index = 0, rv = 0;
6244 ENVELOPE *e;
6245 BODY *b;
6246 MESSAGECACHE *mc;
6248 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
6249 return rv;
6251 msgs = mn_total_cur(msgmap);
6253 if((in_index != View) && F_ON(F_PRINT_INDEX, state)){
6254 char m[10];
6255 int ans;
6256 static ESCKEY_S prt_opts[] = {
6257 {'i', 'i', "I", N_("Index")},
6258 {'m', 'm', "M", NULL},
6259 {-1, 0, NULL, NULL}};
6261 if(in_index == ThrdIndx){
6262 /* TRANSLATORS: This is a question, Print Index ? */
6263 if(want_to(_("Print Index"), 'y', 'x', NO_HELP, WT_NORM) == 'y')
6264 ans = 'i';
6265 else
6266 ans = 'x';
6268 else{
6269 snprintf(m, sizeof(m), "Message%s", (msgs>1L) ? "s" : "");
6270 m[sizeof(m)-1] = '\0';
6271 prt_opts[1].label = m;
6272 snprintf(prompt, sizeof(prompt), "Print %sFolder Index or %s %s? ",
6273 (aopt & MCMD_AGG_2) ? "thread " : MCMD_ISAGG(aopt) ? "selected " : "",
6274 (aopt & MCMD_AGG_2) ? "thread" : MCMD_ISAGG(aopt) ? "selected" : "current", m);
6275 prompt[sizeof(prompt)-1] = '\0';
6277 ans = radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x',
6278 NO_HELP, RB_NORM|RB_SEQ_SENSITIVE);
6281 switch(ans){
6282 case 'x' :
6283 cmd_cancelled("Print");
6284 if(MCMD_ISAGG(aopt))
6285 restore_selected(msgmap);
6287 return rv;
6289 case 'i':
6290 do_index = 1;
6291 break;
6293 default :
6294 case 'm':
6295 break;
6299 if(do_index)
6300 snprintf(prompt, sizeof(prompt), "%sFolder Index",
6301 (aopt & MCMD_AGG_2) ? "Thread " : MCMD_ISAGG(aopt) ? "Selected " : "");
6302 else if(msgs > 1L)
6303 snprintf(prompt, sizeof(prompt), "%s messages", long2string(msgs));
6304 else
6305 snprintf(prompt, sizeof(prompt), "Message %s", long2string(mn_get_cur(msgmap)));
6307 prompt[sizeof(prompt)-1] = '\0';
6309 if(open_printer(prompt) < 0){
6310 if(MCMD_ISAGG(aopt))
6311 restore_selected(msgmap);
6313 return rv;
6316 if(do_index){
6317 TITLE_S *tc;
6319 tc = format_titlebar();
6321 /* Print titlebar... */
6322 print_text1("%s\n\n", tc ? tc->titlebar_line : "");
6323 /* then all the index members... */
6324 if(!print_index(state, msgmap, MCMD_ISAGG(aopt)))
6325 q_status_message(SM_ORDER | SM_DING, 3, 3,
6326 _("Error printing folder index"));
6327 else
6328 rv++;
6330 else{
6331 rv++;
6332 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){
6333 if(next && F_ON(F_AGG_PRINT_FF, state))
6334 if(!print_char(FORMFEED)){
6335 rv = 0;
6336 break;
6339 if(!(state->mail_stream
6340 && (rawno = mn_m2raw(msgmap, i)) > 0L
6341 && rawno <= state->mail_stream->nmsgs
6342 && (mc = mail_elt(state->mail_stream, rawno))
6343 && mc->valid))
6344 mc = NULL;
6346 if(!(e=pine_mail_fetchstructure(state->mail_stream,
6347 mn_m2raw(msgmap,i),
6348 &b))
6349 || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global)
6350 && !bezerk_delimiter(e, mc, print_char, next))
6351 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
6352 e, b, NULL, FM_NEW_MESS | FM_NOINDENT,
6353 print_char)){
6354 q_status_message(SM_ORDER | SM_DING, 3, 3,
6355 _("Error printing message"));
6356 rv = 0;
6357 break;
6362 close_printer();
6364 if(MCMD_ISAGG(aopt))
6365 restore_selected(msgmap);
6367 return rv;
6371 /*----------------------------------------------------------------------
6372 Pipe message text
6374 Args: state -- various pine state bits
6375 msgmap -- Message number mapping table
6376 aopt -- option flags
6378 Filters the original header and sends stuff to specified command
6379 ---*/
6381 cmd_pipe(struct pine *state, MSGNO_S *msgmap, int aopt)
6383 ENVELOPE *e;
6384 MESSAGECACHE *mc;
6385 BODY *b;
6386 PIPE_S *syspipe;
6387 char *resultfilename = NULL, prompt[80], *p;
6388 int done = 0, rv = 0;
6389 gf_io_t pc;
6390 int fourlabel = -1, j = 0, next = 0, ku;
6391 int pipe_rv; /* rv of proc to separate from close_system_pipe rv */
6392 long i, rawno;
6393 unsigned flagsforhist = 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6394 static HISTORY_S *history = NULL;
6395 int capture = 1, raw = 0, delimit = 0, newpipe = 0;
6396 char pipe_command[MAXPATH];
6397 ESCKEY_S pipe_opt[8];
6399 if(ps_global->restricted){
6400 q_status_message(SM_ORDER | SM_DING, 0, 4,
6401 "Alpine demo can't pipe messages");
6402 return rv;
6404 else if(!any_messages(msgmap, NULL, "to Pipe"))
6405 return rv;
6407 pipe_command[0] = '\0';
6408 init_hist(&history, HISTSIZE);
6409 flagsforhist = (raw ? 0x8 : 0) +
6410 (delimit ? 0x4 : 0) +
6411 (newpipe ? 0x2 : 0) +
6412 (capture ? 0x1 : 0);
6413 if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){
6414 strncpy(pipe_command, p, sizeof(pipe_command));
6415 pipe_command[sizeof(pipe_command)-1] = '\0';
6416 if(history->hist[history->curindex]){
6417 flagsforhist = history->hist[history->curindex]->flags;
6418 raw = (flagsforhist & 0x8) ? 1 : 0;
6419 delimit = (flagsforhist & 0x4) ? 1 : 0;
6420 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6421 capture = (flagsforhist & 0x1) ? 1 : 0;
6425 pipe_opt[j].ch = 0;
6426 pipe_opt[j].rval = 0;
6427 pipe_opt[j].name = "";
6428 pipe_opt[j++].label = "";
6430 pipe_opt[j].ch = ctrl('W');
6431 pipe_opt[j].rval = 10;
6432 pipe_opt[j].name = "^W";
6433 pipe_opt[j++].label = NULL;
6435 pipe_opt[j].ch = ctrl('Y');
6436 pipe_opt[j].rval = 11;
6437 pipe_opt[j].name = "^Y";
6438 pipe_opt[j++].label = NULL;
6440 pipe_opt[j].ch = ctrl('R');
6441 pipe_opt[j].rval = 12;
6442 pipe_opt[j].name = "^R";
6443 pipe_opt[j++].label = NULL;
6445 if(MCMD_ISAGG(aopt)){
6446 if(!pseudo_selected(state->mail_stream, msgmap))
6447 return rv;
6448 else{
6449 fourlabel = j;
6450 pipe_opt[j].ch = ctrl('T');
6451 pipe_opt[j].rval = 13;
6452 pipe_opt[j].name = "^T";
6453 pipe_opt[j++].label = NULL;
6457 pipe_opt[j].ch = KEY_UP;
6458 pipe_opt[j].rval = 30;
6459 pipe_opt[j].name = "";
6460 ku = j;
6461 pipe_opt[j++].label = "";
6463 pipe_opt[j].ch = KEY_DOWN;
6464 pipe_opt[j].rval = 31;
6465 pipe_opt[j].name = "";
6466 pipe_opt[j++].label = "";
6468 pipe_opt[j].ch = -1;
6470 while (!done) {
6471 int flags;
6473 snprintf(prompt, sizeof(prompt), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6474 raw ? "RAW " : "",
6475 MCMD_ISAGG(aopt) ? "s" : " ",
6476 MCMD_ISAGG(aopt) ? "" : comatose(mn_get_cur(msgmap)),
6477 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? "(" : "",
6478 capture ? "" : "uncaptured",
6479 (!capture && delimit) ? "," : "",
6480 delimit ? "delimited" : "",
6481 ((!capture || delimit) && newpipe && MCMD_ISAGG(aopt)) ? "," : "",
6482 (newpipe && MCMD_ISAGG(aopt)) ? "new pipe" : "",
6483 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? ") " : "");
6484 prompt[sizeof(prompt)-1] = '\0';
6485 pipe_opt[1].label = raw ? N_("Shown Text") : N_("Raw Text");
6486 pipe_opt[2].label = capture ? N_("Free Output") : N_("Capture Output");
6487 pipe_opt[3].label = delimit ? N_("No Delimiter") : N_("With Delimiter");
6488 if(fourlabel > 0)
6489 pipe_opt[fourlabel].label = newpipe ? N_("To Same Pipe") : N_("To Individual Pipes");
6493 * 2 is really 1 because there will be one real entry and
6494 * one entry of "" because of the get_prev_hist above.
6496 if(items_in_hist(history) > 2){
6497 pipe_opt[ku].name = HISTORY_UP_KEYNAME;
6498 pipe_opt[ku].label = HISTORY_KEYLABEL;
6499 pipe_opt[ku+1].name = HISTORY_DOWN_KEYNAME;
6500 pipe_opt[ku+1].label = HISTORY_KEYLABEL;
6502 else{
6503 pipe_opt[ku].name = "";
6504 pipe_opt[ku].label = "";
6505 pipe_opt[ku+1].name = "";
6506 pipe_opt[ku+1].label = "";
6509 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
6510 switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0,
6511 sizeof(pipe_command), prompt,
6512 pipe_opt, NO_HELP, &flags)){
6513 case -1 :
6514 q_status_message(SM_ORDER | SM_DING, 3, 4,
6515 _("Internal problem encountered"));
6516 done++;
6517 break;
6519 case 10 : /* flip raw bit */
6520 raw = !raw;
6521 break;
6523 case 11 : /* flip capture bit */
6524 capture = !capture;
6525 break;
6527 case 12 : /* flip delimit bit */
6528 delimit = !delimit;
6529 break;
6531 case 13 : /* flip newpipe bit */
6532 newpipe = !newpipe;
6533 break;
6535 case 30 :
6536 flagsforhist = (raw ? 0x8 : 0) +
6537 (delimit ? 0x4 : 0) +
6538 (newpipe ? 0x2 : 0) +
6539 (capture ? 0x1 : 0);
6540 if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6541 strncpy(pipe_command, p, sizeof(pipe_command));
6542 pipe_command[sizeof(pipe_command)-1] = '\0';
6543 if(history->hist[history->curindex]){
6544 flagsforhist = history->hist[history->curindex]->flags;
6545 raw = (flagsforhist & 0x8) ? 1 : 0;
6546 delimit = (flagsforhist & 0x4) ? 1 : 0;
6547 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6548 capture = (flagsforhist & 0x1) ? 1 : 0;
6551 else
6552 Writechar(BELL, 0);
6554 break;
6556 case 31 :
6557 flagsforhist = (raw ? 0x8 : 0) +
6558 (delimit ? 0x4 : 0) +
6559 (newpipe ? 0x2 : 0) +
6560 (capture ? 0x1 : 0);
6561 if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6562 strncpy(pipe_command, p, sizeof(pipe_command));
6563 pipe_command[sizeof(pipe_command)-1] = '\0';
6564 if(history->hist[history->curindex]){
6565 flagsforhist = history->hist[history->curindex]->flags;
6566 raw = (flagsforhist & 0x8) ? 1 : 0;
6567 delimit = (flagsforhist & 0x4) ? 1 : 0;
6568 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6569 capture = (flagsforhist & 0x1) ? 1 : 0;
6572 else
6573 Writechar(BELL, 0);
6575 break;
6577 case 0 :
6578 if(pipe_command[0]){
6580 flagsforhist = (raw ? 0x8 : 0) +
6581 (delimit ? 0x4 : 0) +
6582 (newpipe ? 0x2 : 0) +
6583 (capture ? 0x1 : 0);
6584 save_hist(history, pipe_command, flagsforhist, NULL);
6586 flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
6587 flags |= (raw ? PIPE_RAW : 0);
6588 if(!capture){
6589 #ifndef _WINDOWS
6590 ClearScreen();
6591 fflush(stdout);
6592 clear_cursor_pos();
6593 ps_global->mangled_screen = 1;
6594 ps_global->in_init_seq = 1;
6595 #endif
6596 flags |= PIPE_RESET;
6599 if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command,
6600 (flags & PIPE_RESET)
6601 ? NULL
6602 : &resultfilename,
6603 flags, &pc)))
6604 done++;
6606 for(i = mn_first_cur(msgmap);
6607 i > 0L && !done;
6608 i = mn_next_cur(msgmap)){
6609 e = pine_mail_fetchstructure(ps_global->mail_stream,
6610 mn_m2raw(msgmap, i), &b);
6611 if(!(state->mail_stream
6612 && (rawno = mn_m2raw(msgmap, i)) > 0L
6613 && rawno <= state->mail_stream->nmsgs
6614 && (mc = mail_elt(state->mail_stream, rawno))
6615 && mc->valid))
6616 mc = NULL;
6618 if((newpipe
6619 && !(syspipe = cmd_pipe_open(pipe_command,
6620 (flags & PIPE_RESET)
6621 ? NULL
6622 : &resultfilename,
6623 flags, &pc)))
6624 || (delimit && !bezerk_delimiter(e, mc, pc, next++)))
6625 done++;
6627 if(!done){
6628 if(raw){
6629 char *pipe_err;
6631 prime_raw_pipe_getc(ps_global->mail_stream,
6632 mn_m2raw(msgmap, i), -1L, 0L);
6633 gf_filter_init();
6634 gf_link_filter(gf_nvtnl_local, NULL);
6635 if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){
6636 q_status_message1(SM_ORDER|SM_DING,
6637 3, 3,
6638 _("Internal Error: %s"),
6639 pipe_err);
6640 done++;
6643 else if(!format_message(mn_m2raw(msgmap, i), e, b,
6644 NULL, FM_NEW_MESS | FM_NOWRAP, pc))
6645 done++;
6648 if(newpipe)
6649 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6650 done++;
6653 if(!capture)
6654 ps_global->in_init_seq = 0;
6656 if(!newpipe)
6657 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6658 done++;
6659 if(done) /* say we had a problem */
6660 q_status_message(SM_ORDER | SM_DING, 3, 3,
6661 _("Error piping message"));
6662 else if(resultfilename){
6663 rv++;
6664 /* only display if no error */
6665 display_output_file(resultfilename, "PIPE MESSAGE",
6666 NULL, DOF_EMPTY);
6667 fs_give((void **)&resultfilename);
6669 else{
6670 rv++;
6671 q_status_message(SM_ORDER, 0, 2, _("Pipe command completed"));
6674 done++;
6675 break;
6677 /* else fall thru as if cancelled */
6679 case 1 :
6680 cmd_cancelled("Pipe command");
6681 done++;
6682 break;
6684 case 3 :
6685 helper(h_common_pipe, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE);
6686 ps_global->mangled_screen = 1;
6687 break;
6689 case 2 : /* no place to escape to */
6690 case 4 : /* can't suspend */
6691 default :
6692 break;
6696 ps_global->mangled_footer = 1;
6697 if(MCMD_ISAGG(aopt))
6698 restore_selected(msgmap);
6700 return rv;
6704 /*----------------------------------------------------------------------
6705 Screen to offer list management commands contained in message
6707 Args: state -- pointer to struct holding a bunch of pine state
6708 msgmap -- table mapping msg nums to c-client sequence nums
6709 aopt -- aggregate options
6711 Result:
6713 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6714 ----*/
6715 void
6716 rfc2369_display(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno)
6718 int winner = 0;
6719 char *h, *hdrs[MLCMD_COUNT + 1];
6720 long index_no = mn_raw2m(msgmap, msgno);
6721 RFC2369_S data[MLCMD_COUNT];
6723 /* for each header field */
6724 if((h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))) != NULL){
6725 memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT);
6726 if(rfc2369_parse_fields(h, &data[0])){
6727 STORE_S *explain;
6729 if((explain = list_mgmt_text(data, index_no)) != NULL){
6730 list_mgmt_screen(explain);
6731 ps_global->mangled_screen = 1;
6732 so_give(&explain);
6733 winner++;
6737 fs_give((void **) &h);
6740 if(!winner)
6741 q_status_message1(SM_ORDER, 0, 3,
6742 "Message %s contains no list management information",
6743 comatose(index_no));
6747 STORE_S *
6748 list_mgmt_text(RFC2369_S *data, long int msgno)
6750 STORE_S *store;
6751 int i, j, n, fields = 0;
6752 static char *rfc2369_intro1 =
6753 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6754 static char *rfc2369_intro2[] = {
6755 N_(" has information associated with it "),
6756 N_("that explains how to participate in an email list. An "),
6757 N_("email list is represented by a single email address that "),
6758 N_("users sharing a common interest can send messages to (known "),
6759 N_("as posting) which are then redistributed to all members "),
6760 N_("of the list (sometimes after review by a moderator)."),
6761 N_("<P>List participation commands in this message include:"),
6762 NULL
6765 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6767 /* Insert introductory text */
6768 so_puts(store, rfc2369_intro1);
6770 so_puts(store, comatose(msgno));
6772 for(i = 0; rfc2369_intro2[i]; i++)
6773 so_puts(store, _(rfc2369_intro2[i]));
6775 so_puts(store, "<P>");
6776 for(i = 0; i < MLCMD_COUNT; i++)
6777 if(data[i].data[0].value
6778 || data[i].data[0].comment
6779 || data[i].data[0].error){
6780 if(!fields++)
6781 so_puts(store, "<UL>");
6783 so_puts(store, "<LI>");
6784 so_puts(store,
6785 (n = (data[i].data[1].value || data[i].data[1].comment))
6786 ? "Methods to "
6787 : "A method to ");
6789 so_puts(store, data[i].field.description);
6790 so_puts(store, ". ");
6792 if(n)
6793 so_puts(store, "<OL>");
6795 for(j = 0;
6796 j < MLCMD_MAXDATA
6797 && (data[i].data[j].comment
6798 || data[i].data[j].value
6799 || data[i].data[j].error);
6800 j++){
6802 so_puts(store, n ? "<P><LI>" : "<P>");
6804 if(data[i].data[j].comment){
6805 so_puts(store,
6806 _("With the provided comment:<P><BLOCKQUOTE>"));
6807 so_puts(store, data[i].data[j].comment);
6808 so_puts(store, "</BLOCKQUOTE><P>");
6811 if(data[i].data[j].value){
6812 if(i == MLCMD_POST
6813 && !strucmp(data[i].data[j].value, "NO")){
6814 so_puts(store,
6815 _("Posting is <EM>not</EM> allowed on this list"));
6817 else{
6818 so_puts(store, "Select <A HREF=\"");
6819 so_puts(store, data[i].data[j].value);
6820 so_puts(store, "\">HERE</A> to ");
6821 so_puts(store, (data[i].field.action)
6822 ? data[i].field.action
6823 : "try it");
6826 so_puts(store, ".");
6829 if(data[i].data[j].error){
6830 so_puts(store, "<P>Unfortunately, Alpine can not offer");
6831 so_puts(store, " to take direct action based upon it");
6832 so_puts(store, " because it was improperly formatted.");
6833 so_puts(store, " The unrecognized data associated with");
6834 so_puts(store, " the \"");
6835 so_puts(store, data[i].field.name);
6836 so_puts(store, "\" header field was:<P><BLOCKQUOTE>");
6837 so_puts(store, data[i].data[j].error);
6838 so_puts(store, "</BLOCKQUOTE>");
6841 so_puts(store, "<P>");
6844 if(n)
6845 so_puts(store, "</OL>");
6848 if(fields)
6849 so_puts(store, "</UL>");
6851 so_puts(store, "</BODY></HTML>");
6854 return(store);
6858 void
6859 list_mgmt_screen(STORE_S *html)
6861 int cmd = MC_NONE;
6862 long offset = 0L;
6863 char *error = NULL;
6864 STORE_S *store;
6865 HANDLE_S *handles = NULL;
6866 gf_io_t gc, pc;
6869 so_seek(html, 0L, 0);
6870 gf_set_so_readc(&gc, html);
6872 init_handles(&handles);
6874 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6875 gf_set_so_writec(&pc, store);
6876 gf_filter_init();
6878 gf_link_filter(gf_html2plain,
6879 gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols,
6880 non_messageview_margin(), &handles, NULL, 0));
6882 error = gf_pipe(gc, pc);
6884 gf_clear_so_writec(store);
6886 if(!error){
6887 SCROLL_S sargs;
6889 memset(&sargs, 0, sizeof(SCROLL_S));
6890 sargs.text.text = so_text(store);
6891 sargs.text.src = CharStar;
6892 sargs.text.desc = "list commands";
6893 sargs.text.handles = handles;
6894 if(offset){
6895 sargs.start.on = Offset;
6896 sargs.start.loc.offset = offset;
6899 sargs.bar.title = _("MAIL LIST COMMANDS");
6900 sargs.bar.style = MessageNumber;
6901 sargs.resize_exit = 1;
6902 sargs.help.text = h_special_list_commands;
6903 sargs.help.title = _("HELP FOR LIST COMMANDS");
6904 sargs.keys.menu = &listmgr_keymenu;
6905 setbitmap(sargs.keys.bitmap);
6906 if(!handles){
6907 clrbitn(LM_TRY_KEY, sargs.keys.bitmap);
6908 clrbitn(LM_PREV_KEY, sargs.keys.bitmap);
6909 clrbitn(LM_NEXT_KEY, sargs.keys.bitmap);
6912 cmd = scrolltool(&sargs);
6913 offset = sargs.start.loc.offset;
6916 so_give(&store);
6919 free_handles(&handles);
6920 gf_clear_so_readc(html);
6922 while(cmd == MC_RESIZE);
6926 /*----------------------------------------------------------------------
6927 Prompt the user for the type of select desired
6929 NOTE: any and all functions that successfully exit the second
6930 switch() statement below (currently "select_*() functions"),
6931 *MUST* update the folder's MESSAGECACHE element's "searched"
6932 bits to reflect the search result. Functions using
6933 mail_search() get this for free, the others must update 'em
6934 by hand.
6936 Returns -1 if canceled without changing selection
6937 0 if selection may have changed
6938 ----*/
6940 aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index)
6942 long i, diff, old_tot, msgno, raw;
6943 int p = 0, q = 0, rv = 0, narrow = 0, hidden, ret = -1;
6944 ESCKEY_S *sel_opts;
6945 MESSAGECACHE *mc;
6946 SEARCHSET *limitsrch = NULL;
6947 PINETHRD_S *thrd;
6948 extern MAILSTREAM *mm_search_stream;
6949 extern long mm_search_count;
6951 hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
6952 mm_search_stream = state->mail_stream;
6953 mm_search_count = 0L;
6955 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
6956 sel_opts = THRD_INDX() ? sel_opts6 : sel_opts5;
6957 else
6958 sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2;
6960 if(THREADING()){
6961 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH;
6963 else{
6964 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream)){
6965 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_XGMEXT_CH;
6966 sel_opts[SEL_OPTS_THREAD].rval = sel_opts[SEL_OPTS_XGMEXT].rval;
6967 sel_opts[SEL_OPTS_THREAD].name = sel_opts[SEL_OPTS_XGMEXT].name;
6968 sel_opts[SEL_OPTS_THREAD].label = sel_opts[SEL_OPTS_XGMEXT].label;
6969 sel_opts[SEL_OPTS_XGMEXT].ch = -1;
6971 else
6972 sel_opts[SEL_OPTS_THREAD].ch = -1;
6975 if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){
6976 if(THRD_INDX()){
6977 i = 0;
6978 thrd = fetch_thread(state->mail_stream,
6979 mn_m2raw(msgmap, mn_get_cur(msgmap)));
6980 /* check if whole thread is selected or not */
6981 if(thrd &&
6982 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT)
6984 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE))
6985 i = 1;
6987 sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd");
6989 else{
6990 i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap),
6991 MN_SLCT);
6992 sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur");
6995 sel_opts += 2; /* disable extra options */
6996 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
6997 q = double_radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6998 RB_NORM);
6999 else
7000 q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
7001 RB_NORM);
7002 switch(q){
7003 case 'f' : /* flip selection */
7004 msgno = 0L;
7005 for(i = 1L; i <= mn_get_total(msgmap); i++){
7006 ret = 0;
7007 q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT);
7008 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q);
7009 if(hidden){
7010 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q);
7011 if(!msgno && q)
7012 mn_reset_cur(msgmap, msgno = i);
7016 return(ret);
7018 case 'n' : /* narrow selection */
7019 narrow++;
7020 q = 0;
7021 break;
7022 case 'r' : /* replace selection */
7023 p = 1; /* set flag we want to replace */
7024 sel_opts -= 2; /* re-enable first two options */
7025 case 'b' : /* broaden selection */
7026 q = 0; /* offer criteria prompt */
7027 break;
7029 case 'c' : /* Un/Select Current */
7030 case 'a' : /* Unselect All */
7031 case 'x' : /* cancel */
7032 break;
7034 default :
7035 q_status_message(SM_ORDER | SM_DING, 3, 3,
7036 "Unsupported Select option");
7037 return(ret);
7041 if(!q){
7042 while(1){
7043 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
7044 q = double_radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP,
7045 RB_NORM);
7046 else
7047 q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP,
7048 RB_NORM|RB_RET_HELP);
7050 if(q == 3){
7051 helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE);
7052 ps_global->mangled_screen = 1;
7054 else
7055 break;
7059 /* When we are replacing the search, unselect all messages first, unless
7060 * we are cancelling, in whose case, leave the screen as is, because we
7061 * are cancelling!
7063 if(p == 1 && q != 'x'){
7064 msgno = any_lflagged(msgmap, MN_SLCT);
7065 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
7066 agg_select_all(state->mail_stream, msgmap, &diff,
7067 any_lflagged(msgmap, MN_SLCT) <= 0L);
7071 * The purpose of this is to add the appropriate searchset to the
7072 * search so that the search can be limited to only looking at what
7073 * it needs to look at. That is, if we are narrowing then we only need
7074 * to look at messages which are already selected, and if we are
7075 * broadening, then we only need to look at messages which are not
7076 * yet selected. This routine will work whether or not
7077 * limiting_searchset properly limits the search set. In particular,
7078 * the searchset returned by limiting_searchset may include messages
7079 * which really shouldn't be included. We do that because a too-large
7080 * searchset will break some IMAP servers. It is even possible that it
7081 * becomes inefficient to send the whole set. If the select function
7082 * frees limitsrch, it should be sure to set it to NULL so we won't
7083 * try freeing it again here.
7085 limitsrch = limiting_searchset(state->mail_stream, narrow);
7088 * NOTE: See note about MESSAGECACHE "searched" bits above!
7090 switch(q){
7091 case 'x': /* cancel */
7092 cmd_cancelled("Select command");
7093 return(ret);
7095 case 'c' : /* select/unselect current */
7096 (void) select_by_current(state, msgmap, in_index);
7097 ret = 0;
7098 return(ret);
7100 case 'a' : /* select/unselect all */
7101 msgno = any_lflagged(msgmap, MN_SLCT);
7102 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
7103 ret = 0;
7104 agg_select_all(state->mail_stream, msgmap, &diff,
7105 any_lflagged(msgmap, MN_SLCT) <= 0L);
7106 q_status_message4(SM_ORDER,0,2,
7107 "%s%s message%s %sselected",
7108 msgno ? "" : "All ", comatose(diff),
7109 plural(diff), msgno ? "UN" : "");
7110 return(ret);
7112 case 'n' : /* Select by Number */
7113 ret = 0;
7114 if(THRD_INDX())
7115 rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch);
7116 else
7117 rv = select_by_number(state->mail_stream, msgmap, &limitsrch);
7119 break;
7121 case 'd' : /* Select by Date */
7122 ret = 0;
7123 rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap),
7124 &limitsrch);
7125 break;
7127 case 't' : /* Text */
7128 ret = 0;
7129 rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap),
7130 &limitsrch);
7131 break;
7133 case 'z' : /* Size */
7134 ret = 0;
7135 rv = select_by_size(state->mail_stream, &limitsrch);
7136 break;
7138 case 's' : /* Status */
7139 ret = 0;
7140 rv = select_by_status(state->mail_stream, &limitsrch);
7141 break;
7143 case 'k' : /* Keyword */
7144 ret = 0;
7145 rv = select_by_keyword(state->mail_stream, &limitsrch);
7146 break;
7148 case 'r' : /* Rule */
7149 ret = 0;
7150 rv = select_by_rule(state->mail_stream, &limitsrch);
7151 break;
7153 case 'h' : /* Thread */
7154 ret = 0;
7155 rv = select_by_thread(state->mail_stream, msgmap, &limitsrch);
7156 break;
7158 case 'g' : /* X-GM-EXT-1 */
7159 ret = 0;
7160 rv = select_by_gm_content(state->mail_stream, msgmap, mn_get_cur(msgmap), &limitsrch);
7161 break;
7163 default :
7164 q_status_message(SM_ORDER | SM_DING, 3, 3,
7165 "Unsupported Select option");
7166 return(ret);
7169 if(limitsrch)
7170 mail_free_searchset(&limitsrch);
7172 if(rv) /* bad return value.. */
7173 return(ret); /* error already displayed */
7175 if(narrow) /* make sure something was selected */
7176 for(i = 1L; i <= mn_get_total(msgmap); i++)
7177 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7178 && raw <= state->mail_stream->nmsgs
7179 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7180 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
7181 break;
7182 else
7183 mm_search_count--;
7186 diff = 0L;
7187 if(mm_search_count){
7189 * loop thru all the messages, adjusting local flag bits
7190 * based on their "searched" bit...
7192 for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++)
7193 if(narrow){
7194 /* turning OFF selectedness if the "searched" bit isn't lit. */
7195 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7196 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7197 && raw <= state->mail_stream->nmsgs
7198 && (mc = mail_elt(state->mail_stream, raw))
7199 && !mc->searched){
7200 diff--;
7201 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
7202 if(hidden)
7203 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
7205 /* adjust current message in case we unselect and hide it */
7206 else if(msgno < mn_get_cur(msgmap)
7207 && (!THRD_INDX()
7208 || !get_lflag(state->mail_stream, msgmap,
7209 i, MN_CHID)))
7210 msgno = i;
7213 else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7214 && raw <= state->mail_stream->nmsgs
7215 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7216 /* turn ON selectedness if "searched" bit is lit. */
7217 if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7218 diff++;
7219 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
7220 if(hidden)
7221 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
7225 /* if we're zoomed and the current message was unselected */
7226 if(narrow && msgno
7227 && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE))
7228 mn_reset_cur(msgmap, msgno);
7231 if(!diff){
7232 if(narrow)
7233 q_status_message4(SM_ORDER, 3, 3,
7234 "%s. %s message%s remain%s selected.",
7235 mm_search_count
7236 ? "No change resulted"
7237 : "No messages in intersection",
7238 comatose(old_tot), plural(old_tot),
7239 (old_tot == 1L) ? "s" : "");
7240 else if(old_tot)
7241 q_status_message(SM_ORDER, 3, 3,
7242 _("No change resulted. Matching messages already selected."));
7243 else
7244 q_status_message1(SM_ORDER | SM_DING, 3, 3,
7245 _("Select failed. No %smessages selected."),
7246 old_tot ? _("additional ") : "");
7248 else if(old_tot){
7249 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
7250 "Select matched %ld message%s. %s %smessage%s %sselected.",
7251 (diff > 0) ? diff : old_tot + diff,
7252 plural((diff > 0) ? diff : old_tot + diff),
7253 comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7254 (diff > 0) ? "total " : "",
7255 plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7256 (diff > 0) ? "" : "UN");
7257 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
7258 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
7260 else
7261 q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"),
7262 comatose(diff), plural(diff));
7264 return(ret);
7268 /*----------------------------------------------------------------------
7269 Toggle the state of the current message
7271 Args: state -- pointer pine's state variables
7272 msgmap -- message collection to operate on
7273 in_index -- in the message index view
7274 Returns: TRUE if current marked selected, FALSE otw
7275 ----*/
7277 select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
7279 long cur;
7280 int all_selected = 0;
7281 unsigned long was, tot, rawno;
7282 PINETHRD_S *thrd;
7284 cur = mn_get_cur(msgmap);
7286 if(THRD_INDX()){
7287 thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur));
7288 if(!thrd)
7289 return 0;
7291 was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT);
7292 tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE);
7293 if(was == tot)
7294 all_selected++;
7296 if(all_selected){
7297 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0);
7298 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7299 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1);
7301 * See if there's anything left to zoom on. If so,
7302 * pick an adjacent one for highlighting, else make
7303 * sure nothing is left hidden...
7305 if(any_lflagged(msgmap, MN_SLCT)){
7306 mn_inc_cur(state->mail_stream, msgmap,
7307 (in_index == View && THREADING()
7308 && sp_viewing_a_thread(state->mail_stream))
7309 ? MH_THISTHD
7310 : (in_index == View)
7311 ? MH_ANYTHD : MH_NONE);
7312 if(mn_get_cur(msgmap) == cur)
7313 mn_dec_cur(state->mail_stream, msgmap,
7314 (in_index == View && THREADING()
7315 && sp_viewing_a_thread(state->mail_stream))
7316 ? MH_THISTHD
7317 : (in_index == View)
7318 ? MH_ANYTHD : MH_NONE);
7320 else /* clear all hidden flags */
7321 (void) unzoom_index(state, state->mail_stream, msgmap);
7324 else
7325 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1);
7327 q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected",
7328 comatose(all_selected ? was : tot-was),
7329 plural(all_selected ? was : tot-was),
7330 all_selected ? "UN" : "");
7332 /* collapsed thread */
7333 else if(THREADING()
7334 && ((rawno = mn_m2raw(msgmap, cur)) != 0L)
7335 && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL)
7336 && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){
7338 * This doesn't work quite the same as the colon command works, but
7339 * it is arguably doing the correct thing. The difference is
7340 * that aggregate_select will zoom after selecting back where it
7341 * was called from, but selecting a thread with colon won't zoom.
7342 * Maybe it makes sense to zoom after a select but not after a colon
7343 * command even though they are very similar.
7345 thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state));
7347 else{
7348 if((all_selected =
7349 get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */
7350 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0);
7351 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7352 set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1);
7354 * See if there's anything left to zoom on. If so,
7355 * pick an adjacent one for highlighting, else make
7356 * sure nothing is left hidden...
7358 if(any_lflagged(msgmap, MN_SLCT)){
7359 mn_inc_cur(state->mail_stream, msgmap,
7360 (in_index == View && THREADING()
7361 && sp_viewing_a_thread(state->mail_stream))
7362 ? MH_THISTHD
7363 : (in_index == View)
7364 ? MH_ANYTHD : MH_NONE);
7365 if(mn_get_cur(msgmap) == cur)
7366 mn_dec_cur(state->mail_stream, msgmap,
7367 (in_index == View && THREADING()
7368 && sp_viewing_a_thread(state->mail_stream))
7369 ? MH_THISTHD
7370 : (in_index == View)
7371 ? MH_ANYTHD : MH_NONE);
7373 else /* clear all hidden flags */
7374 (void) unzoom_index(state, state->mail_stream, msgmap);
7377 else
7378 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1);
7380 q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected",
7381 long2string(cur), all_selected ? "UN" : "");
7385 return(!all_selected);
7389 /*----------------------------------------------------------------------
7390 Prompt the user for the command to perform on selected messages
7392 Args: state -- pointer pine's state variables
7393 msgmap -- message collection to operate on
7394 q_line -- line on display to write prompts
7395 Returns: 1 if the selected messages are suitably commanded,
7396 0 if the choice to pick the command was declined
7398 ----*/
7400 apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
7401 UCS preloadkeystroke, int flags, int q_line)
7403 int i = 8, /* number of static entries in sel_opts3 */
7404 rv = 0,
7405 cmd,
7406 we_cancel = 0,
7407 agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG;
7408 char prompt[80];
7409 PAT_STATE pstate;
7412 * To do this "right", we really ought to have access to the keymenu
7413 * here and change the typed command into a real command by running
7414 * it through menu_command. Then the switch below would be against
7415 * results from menu_command. If we did that we'd also pass the
7416 * results of menu_command in as preloadkeystroke instead of passing
7417 * the keystroke itself. But we don't have the keymenu handy,
7418 * so we have to fake it. The only complication that we run into
7419 * is that KEY_DEL is an escape sequence so we change a typed
7420 * KEY_DEL esc seq into the letter D.
7423 if(!preloadkeystroke){
7424 if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
7425 sel_opts3[i].ch = '*';
7426 sel_opts3[i].rval = '*';
7427 sel_opts3[i].name = "*";
7428 sel_opts3[i++].label = N_("Flag");
7431 if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
7432 sel_opts3[i].ch = '|';
7433 sel_opts3[i].rval = '|';
7434 sel_opts3[i].name = "|";
7435 sel_opts3[i++].label = N_("Pipe");
7438 if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */
7439 sel_opts3[i].ch = 'b';
7440 sel_opts3[i].rval = 'b';
7441 sel_opts3[i].name = "B";
7442 sel_opts3[i++].label = N_("Bounce");
7445 if(flags & AC_FROM_THREAD){
7446 if(flags & (AC_COLL | AC_EXPN)){
7447 sel_opts3[i].ch = '/';
7448 sel_opts3[i].rval = '/';
7449 sel_opts3[i].name = "/";
7450 sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse")
7451 : N_("Expand");
7454 sel_opts3[i].ch = ';';
7455 sel_opts3[i].rval = ';';
7456 sel_opts3[i].name = ";";
7457 if(flags & AC_UNSEL)
7458 sel_opts3[i++].label = N_("UnSelect");
7459 else
7460 sel_opts3[i++].label = N_("Select");
7463 if(F_ON(F_ENABLE_PRYNT, state)){ /* this one is invisible */
7464 sel_opts3[i].ch = 'y';
7465 sel_opts3[i].rval = '%';
7466 sel_opts3[i].name = "";
7467 sel_opts3[i++].label = "";
7470 if(!is_imap_stream(stream) || LEVELUIDPLUS(stream)){ /* expunge selected messages */
7471 sel_opts3[i].ch = 'x';
7472 sel_opts3[i].rval = 'x';
7473 sel_opts3[i].name = "X";
7474 sel_opts3[i++].label = N_("Expunge");
7477 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7478 sel_opts3[i].ch = '#';
7479 sel_opts3[i].rval = '#';
7480 sel_opts3[i].name = "#";
7481 sel_opts3[i++].label = N_("Set Role");
7484 sel_opts3[i].ch = KEY_DEL; /* also invisible */
7485 sel_opts3[i].rval = 'd';
7486 sel_opts3[i].name = "";
7487 sel_opts3[i++].label = "";
7489 sel_opts3[i].ch = -1;
7491 snprintf(prompt, sizeof(prompt), "%s command : ",
7492 (flags & AC_FROM_THREAD) ? "THREAD" : "APPLY");
7493 prompt[sizeof(prompt)-1] = '\0';
7494 cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'c', NO_HELP,
7495 RB_SEQ_SENSITIVE);
7496 if(isupper(cmd))
7497 cmd = tolower(cmd);
7499 else{
7500 if(preloadkeystroke == KEY_DEL)
7501 cmd = 'd';
7502 else{
7503 if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke))
7504 cmd = tolower((int) preloadkeystroke);
7505 else
7506 cmd = (int) preloadkeystroke; /* shouldn't happen */
7510 switch(cmd){
7511 case 'd' : /* delete */
7512 we_cancel = busy_cue(NULL, NULL, 1);
7513 rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */
7514 if(we_cancel)
7515 cancel_busy_cue(0);
7516 break;
7518 case 'u' : /* undelete */
7519 we_cancel = busy_cue(NULL, NULL, 1);
7520 rv = cmd_undelete(state, msgmap, agg);
7521 if(we_cancel)
7522 cancel_busy_cue(0);
7523 break;
7525 case 'r' : /* reply */
7526 rv = cmd_reply(state, msgmap, agg, NULL);
7527 break;
7529 case 'f' : /* Forward */
7530 rv = cmd_forward(state, msgmap, agg, NULL);
7531 break;
7533 case '%' : /* print */
7534 rv = cmd_print(state, msgmap, agg, MsgIndx);
7535 break;
7537 case 't' : /* take address */
7538 rv = cmd_take_addr(state, msgmap, agg);
7539 break;
7541 case 's' : /* save */
7542 rv = cmd_save(state, stream, msgmap, agg, MsgIndx);
7543 break;
7545 case 'e' : /* export */
7546 rv = cmd_export(state, msgmap, q_line, agg);
7547 break;
7549 case '|' : /* pipe */
7550 rv = cmd_pipe(state, msgmap, agg);
7551 break;
7553 case '*' : /* flag */
7554 we_cancel = busy_cue(NULL, NULL, 1);
7555 rv = cmd_flag(state, msgmap, agg);
7556 if(we_cancel)
7557 cancel_busy_cue(0);
7558 break;
7560 case 'b' : /* bounce */
7561 rv = cmd_bounce(state, msgmap, agg, NULL);
7562 break;
7564 case '/' :
7565 collapse_or_expand(state, stream, msgmap,
7566 F_ON(F_SLASH_COLL_ENTIRE, ps_global)
7567 ? 0L
7568 : mn_get_cur(msgmap));
7569 break;
7571 case ':' :
7572 select_thread_stmp(state, stream, msgmap);
7573 break;
7575 case 'x' : /* Expunge */
7576 rv = cmd_expunge(state, stream, msgmap, agg);
7577 break;
7579 case 'c' : /* cancel */
7580 cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command"
7581 : "Apply command");
7582 break;
7584 case '#' :
7585 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7586 static ESCKEY_S choose_role[] = {
7587 {'r', 'r', "R", N_("Reply")},
7588 {'f', 'f', "F", N_("Forward")},
7589 {'b', 'b', "B", N_("Bounce")},
7590 {-1, 0, NULL, NULL}
7592 int action;
7593 ACTION_S *role = NULL;
7595 action = radio_buttons(_("Reply, Forward or Bounce using a role? "),
7596 -FOOTER_ROWS(state), choose_role,
7597 'r', 'x', h_role_aggregate, RB_NORM);
7598 if(action == 'r' || action == 'f' || action == 'b'){
7599 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
7601 redraw = state->redrawer;
7602 state->redrawer = NULL;
7603 prev_screen = state->prev_screen;
7604 role = NULL;
7605 state->next_screen = SCREEN_FUN_NULL;
7607 if(role_select_screen(state, &role,
7608 action == 'f' ? MC_FORWARD :
7609 action == 'r' ? MC_REPLY :
7610 action == 'b' ? MC_BOUNCE : 0) < 0){
7611 cmd_cancelled(action == 'f' ? _("Forward") :
7612 action == 'r' ? _("Reply") : _("Bounce"));
7613 state->next_screen = prev_screen;
7614 state->redrawer = redraw;
7615 state->mangled_screen = 1;
7617 else{
7618 if(role)
7619 role = combine_inherited_role(role);
7620 else{
7621 role = (ACTION_S *) fs_get(sizeof(*role));
7622 memset((void *) role, 0, sizeof(*role));
7623 role->nick = cpystr("Default Role");
7626 state->redrawer = NULL;
7627 switch(action){
7628 case 'r':
7629 (void) cmd_reply(state, msgmap, agg, role);
7630 break;
7632 case 'f':
7633 (void) cmd_forward(state, msgmap, agg, role);
7634 break;
7636 case 'b':
7637 (void) cmd_bounce(state, msgmap, agg, role);
7638 break;
7641 if(role)
7642 free_action(&role);
7644 if(redraw)
7645 (*redraw)();
7647 state->next_screen = prev_screen;
7648 state->redrawer = redraw;
7649 state->mangled_screen = 1;
7653 break;
7655 case 'z' : /* default */
7656 q_status_message(SM_INFO, 0, 2,
7657 "Cancelled, there is no default command");
7658 break;
7660 default:
7661 break;
7664 return(rv);
7669 * Select by message number ranges.
7670 * Sets searched bits in mail_elts
7672 * Args limitsrch -- limit search to this searchset
7674 * Returns 0 on success.
7677 select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
7679 int r, end, cur;
7680 long n1, n2, raw;
7681 char number1[16], number2[16], numbers[80], *p, *t;
7682 HelpType help;
7683 MESSAGECACHE *mc;
7685 numbers[0] = '\0';
7686 ps_global->mangled_footer = 1;
7687 help = NO_HELP;
7688 while(1){
7689 int flags = OE_APPEND_CURRENT;
7691 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7692 sizeof(numbers), _(select_num), NULL, help, &flags);
7693 if(r == 4)
7694 continue;
7696 if(r == 3){
7697 help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
7698 continue;
7701 for(t = p = numbers; *p ; p++) /* strip whitespace */
7702 if(!isspace((unsigned char)*p))
7703 *t++ = *p;
7705 *t = '\0';
7707 if(r == 1 || numbers[0] == '\0'){
7708 cmd_cancelled("Selection by number");
7709 return(1);
7711 else
7712 break;
7715 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7716 if((mc = mail_elt(stream, n1)) != NULL)
7717 mc->searched = 0; /* clear searched bits */
7719 for(p = numbers; *p ; p++){
7720 t = number1;
7721 while(*p && isdigit((unsigned char)*p))
7722 *t++ = *p++;
7724 *t = '\0';
7726 end = cur = 0;
7727 if(number1[0] == '\0'){
7728 if(*p == '-'){
7729 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7730 _("Invalid number range, missing number before \"-\": %s"),
7731 numbers);
7732 return(1);
7734 else if(!strucmp("end", p)){
7735 end = 1;
7736 p += strlen("end");
7738 else if(!strucmp("$", p)){
7739 end = 1;
7740 p++;
7742 else if(*p == '.'){
7743 cur = 1;
7744 p++;
7746 else{
7747 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7748 _("Invalid message number: %s"), numbers);
7749 return(1);
7753 if(end)
7754 n1 = mn_get_total(msgmap);
7755 else if(cur)
7756 n1 = mn_get_cur(msgmap);
7757 else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
7758 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7759 _("\"%s\" out of message number range"),
7760 long2string(n1));
7761 return(1);
7764 t = number2;
7765 if(*p == '-'){
7766 while(*++p && isdigit((unsigned char)*p))
7767 *t++ = *p;
7769 *t = '\0';
7771 end = cur = 0;
7772 if(number2[0] == '\0'){
7773 if(!strucmp("end", p)){
7774 end = 1;
7775 p += strlen("end");
7777 else if(!strucmp(p, "$")){
7778 end = 1;
7779 p++;
7781 else if(*p == '.'){
7782 cur = 1;
7783 p++;
7785 else{
7786 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7787 _("Invalid number range, missing number after \"-\": %s"),
7788 numbers);
7789 return(1);
7793 if(end)
7794 n2 = mn_get_total(msgmap);
7795 else if(cur)
7796 n2 = mn_get_cur(msgmap);
7797 else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){
7798 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7799 _("\"%s\" out of message number range"),
7800 long2string(n2));
7801 return(1);
7804 if(n2 <= n1){
7805 char t[20];
7807 strncpy(t, long2string(n1), sizeof(t));
7808 t[sizeof(t)-1] = '\0';
7809 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7810 _("Invalid reverse message number range: %s-%s"),
7811 t, long2string(n2));
7812 return(1);
7815 for(;n1 <= n2; n1++){
7816 raw = mn_m2raw(msgmap, n1);
7817 if(raw > 0L
7818 && (!(limitsrch && *limitsrch)
7819 || in_searchset(*limitsrch, (unsigned long) raw)))
7820 mm_searched(stream, raw);
7823 else{
7824 raw = mn_m2raw(msgmap, n1);
7825 if(raw > 0L
7826 && (!(limitsrch && *limitsrch)
7827 || in_searchset(*limitsrch, (unsigned long) raw)))
7828 mm_searched(stream, raw);
7831 if(*p == '\0')
7832 break;
7835 return(0);
7840 * Select by thread number ranges.
7841 * Sets searched bits in mail_elts
7843 * Args limitsrch -- limit search to this searchset
7845 * Returns 0 on success.
7848 select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset)
7850 int r, end, cur;
7851 long n1, n2;
7852 char number1[16], number2[16], numbers[80], *p, *t;
7853 HelpType help;
7854 PINETHRD_S *thrd = NULL, *th;
7855 MESSAGECACHE *mc;
7857 numbers[0] = '\0';
7858 ps_global->mangled_footer = 1;
7859 help = NO_HELP;
7860 while(1){
7861 int flags = OE_APPEND_CURRENT;
7863 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7864 sizeof(numbers), _(select_num), NULL, help, &flags);
7865 if(r == 4)
7866 continue;
7868 if(r == 3){
7869 help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP;
7870 continue;
7873 for(t = p = numbers; *p ; p++) /* strip whitespace */
7874 if(!isspace((unsigned char)*p))
7875 *t++ = *p;
7877 *t = '\0';
7879 if(r == 1 || numbers[0] == '\0'){
7880 cmd_cancelled("Selection by number");
7881 return(1);
7883 else
7884 break;
7887 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7888 if((mc = mail_elt(stream, n1)) != NULL)
7889 mc->searched = 0; /* clear searched bits */
7891 for(p = numbers; *p ; p++){
7892 t = number1;
7893 while(*p && isdigit((unsigned char)*p))
7894 *t++ = *p++;
7896 *t = '\0';
7898 end = cur = 0;
7899 if(number1[0] == '\0'){
7900 if(*p == '-'){
7901 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7902 _("Invalid number range, missing number before \"-\": %s"),
7903 numbers);
7904 return(1);
7906 else if(!strucmp("end", p)){
7907 end = 1;
7908 p += strlen("end");
7910 else if(!strucmp(p, "$")){
7911 end = 1;
7912 p++;
7914 else if(*p == '.'){
7915 cur = 1;
7916 p++;
7918 else{
7919 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7920 _("Invalid thread number: %s"), numbers);
7921 return(1);
7925 if(end)
7926 n1 = msgmap->max_thrdno;
7927 else if(cur){
7928 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7929 n1 = th->thrdno;
7931 else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){
7932 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7933 _("\"%s\" out of thread number range"),
7934 long2string(n1));
7935 return(1);
7938 t = number2;
7939 if(*p == '-'){
7941 while(*++p && isdigit((unsigned char)*p))
7942 *t++ = *p;
7944 *t = '\0';
7946 end = 0;
7947 if(number2[0] == '\0'){
7948 if(!strucmp("end", p)){
7949 end = 1;
7950 p += strlen("end");
7952 else if(!strucmp("$", p)){
7953 end = 1;
7954 p++;
7956 else if(*p == '.'){
7957 cur = 1;
7958 p++;
7960 else{
7961 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7962 _("Invalid number range, missing number after \"-\": %s"),
7963 numbers);
7964 return(1);
7968 if(end)
7969 n2 = msgmap->max_thrdno;
7970 else if(cur){
7971 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7972 n2 = th->thrdno;
7974 else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){
7975 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7976 _("\"%s\" out of thread number range"),
7977 long2string(n2));
7978 return(1);
7981 if(n2 <= n1){
7982 char t[20];
7984 strncpy(t, long2string(n1), sizeof(t));
7985 t[sizeof(t)-1] = '\0';
7986 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7987 _("Invalid reverse message number range: %s-%s"),
7988 t, long2string(n2));
7989 return(1);
7992 for(;n1 <= n2; n1++){
7993 thrd = find_thread_by_number(stream, msgmap, n1, thrd);
7995 if(thrd)
7996 set_search_bit_for_thread(stream, thrd, msgset);
7999 else{
8000 thrd = find_thread_by_number(stream, msgmap, n1, NULL);
8002 if(thrd)
8003 set_search_bit_for_thread(stream, thrd, msgset);
8006 if(*p == '\0')
8007 break;
8010 return(0);
8015 * Select by message dates.
8016 * Sets searched bits in mail_elts
8018 * Args limitsrch -- limit search to this searchset
8020 * Returns 0 on success.
8023 select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8025 int r, we_cancel = 0, when = 0;
8026 char date[100], defdate[100], prompt[128];
8027 time_t seldate = time(0);
8028 struct tm *seldate_tm;
8029 SEARCHPGM *pgm;
8030 HelpType help;
8031 static struct _tense {
8032 char *preamble,
8033 *range,
8034 *scope;
8035 } tense[] = {
8036 {"were ", "SENT SINCE", " (inclusive)"},
8037 {"were ", "SENT BEFORE", " (exclusive)"},
8038 {"were ", "SENT ON", "" },
8039 {"", "ARRIVED SINCE", " (inclusive)"},
8040 {"", "ARRIVED BEFORE", " (exclusive)"},
8041 {"", "ARRIVED ON", "" }
8044 date[0] = '\0';
8045 ps_global->mangled_footer = 1;
8046 help = NO_HELP;
8049 * If talking to an old server, default to SINCE instead of
8050 * SENTSINCE, which was added later.
8052 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8053 when = 3;
8055 while(1){
8056 int flags = OE_APPEND_CURRENT;
8058 seldate_tm = localtime(&seldate);
8059 snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday,
8060 month_abbrev(seldate_tm->tm_mon + 1),
8061 seldate_tm->tm_year + 1900);
8062 defdate[sizeof(defdate)-1] = '\0';
8063 snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ",
8064 tense[when].preamble, tense[when].range,
8065 tense[when].scope, defdate);
8066 prompt[sizeof(prompt)-1] = '\0';
8067 r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date),
8068 prompt, sel_date_opt, help, &flags);
8069 switch (r){
8070 case 1 :
8071 cmd_cancelled("Selection by date");
8072 return(1);
8074 case 3 :
8075 help = (help == NO_HELP) ? h_select_date : NO_HELP;
8076 continue;
8078 case 4 :
8079 continue;
8081 case 11 :
8083 MESSAGECACHE *mc;
8084 long rawno;
8086 if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L
8087 && rawno <= stream->nmsgs
8088 && (mc = mail_elt(stream, rawno))){
8090 /* cache not filled in yet? */
8091 if(mc->day == 0){
8092 char seq[20];
8094 if(stream->dtb && stream->dtb->flags & DR_NEWS){
8095 strncpy(seq,
8096 ulong2string(mail_uid(stream, rawno)),
8097 sizeof(seq));
8098 seq[sizeof(seq)-1] = '\0';
8099 mail_fetch_overview(stream, seq, NULL);
8101 else{
8102 strncpy(seq, long2string(rawno),
8103 sizeof(seq));
8104 seq[sizeof(seq)-1] = '\0';
8105 mail_fetch_fast(stream, seq, 0L);
8109 /* mail_date returns fixed field width date */
8110 mail_date(date, mc);
8111 date[11] = '\0';
8115 continue;
8117 case 12 : /* set default to PREVIOUS day */
8118 seldate -= 86400;
8119 continue;
8121 case 13 : /* set default to NEXT day */
8122 seldate += 86400;
8123 continue;
8125 case 14 :
8126 when = (when+1) % (sizeof(tense) / sizeof(struct _tense));
8127 continue;
8129 default:
8130 break;
8133 removing_leading_white_space(date);
8134 removing_trailing_white_space(date);
8135 if(!*date){
8136 strncpy(date, defdate, sizeof(date));
8137 date[sizeof(date)-1] = '\0';
8140 break;
8143 if((pgm = mail_newsearchpgm()) != NULL){
8144 MESSAGECACHE elt;
8145 short converted_date;
8147 if(mail_parse_date(&elt, (unsigned char *) date)){
8148 converted_date = mail_shortdate(elt.year, elt.month, elt.day);
8150 switch(when){
8151 case 0:
8152 pgm->sentsince = converted_date;
8153 break;
8154 case 1:
8155 pgm->sentbefore = converted_date;
8156 break;
8157 case 2:
8158 pgm->senton = converted_date;
8159 break;
8160 case 3:
8161 pgm->since = converted_date;
8162 break;
8163 case 4:
8164 pgm->before = converted_date;
8165 break;
8166 case 5:
8167 pgm->on = converted_date;
8168 break;
8171 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8173 if(ps_global && ps_global->ttyo){
8174 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8175 ps_global->mangled_footer = 1;
8178 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8180 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8182 if(we_cancel)
8183 cancel_busy_cue(0);
8185 /* we know this was freed in mail_search, let caller know */
8186 if(limitsrch)
8187 *limitsrch = NULL;
8189 else{
8190 mail_free_searchpgm(&pgm);
8191 q_status_message1(SM_ORDER, 3, 3,
8192 _("Invalid date entered: %s"), date);
8193 return(1);
8197 return(0);
8201 * Select by searching in message headers or body
8202 * using the x-gm-ext-1 capaility. This function
8203 * reads the input from the user and passes it to
8204 * the server directly. We need a x-gm-ext-1 variable
8205 * in the search pgm to carry this information to the
8206 * server.
8208 * Sets searched bits in mail_elts
8210 * Args limitsrch -- limit search to this searchset
8212 * Returns 0 on success.
8215 select_by_gm_content(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8217 int r, we_cancel = 0, rv;
8218 char tmp[128];
8219 char namehdr[MAILTMPLEN];
8220 ESCKEY_S ekey[8];
8221 HelpType help;
8223 ps_global->mangled_footer = 1;
8224 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8226 strncpy(tmp, sel_x_gm_ext, sizeof(tmp)-1);
8227 tmp[sizeof(tmp)-1] = '\0';
8229 namehdr[0] = '\0';
8231 help = NO_HELP;
8232 while (1){
8233 int flags = OE_APPEND_CURRENT;
8235 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8236 sizeof(namehdr), tmp, ekey, help, &flags);
8238 if(r == 4)
8239 continue;
8241 if(r == 3){
8242 help = (help == NO_HELP) ? h_select_by_gm_content : NO_HELP;
8243 continue;
8246 if (r == 1){
8247 cmd_cancelled("Selection by content");
8248 return(1);
8251 removing_leading_white_space(namehdr);
8253 if ((namehdr[0] != '\0')
8254 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8255 removing_trailing_white_space(namehdr);
8257 if (namehdr[0] != '\0')
8258 break;
8261 if(ps_global && ps_global->ttyo){
8262 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8263 ps_global->mangled_footer = 1;
8266 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8268 rv = agg_text_select(stream, msgmap, 'g', namehdr, 0, 0, NULL, "utf-8", limitsrch);
8269 if(we_cancel)
8270 cancel_busy_cue(0);
8272 return(rv);
8276 * Select by searching in message headers or body.
8277 * Sets searched bits in mail_elts
8279 * Args limitsrch -- limit search to this searchset
8281 * Returns 0 on success.
8284 select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8286 int r = '\0', ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
8287 int not = 0, me = 0;
8288 char sstring[512], tmp[128];
8289 char *p, *sval = NULL;
8290 char namehdr[80];
8291 ESCKEY_S ekey[8];
8292 ENVELOPE *env = NULL;
8293 HelpType help;
8294 unsigned flagsforhist = 0;
8295 static HISTORY_S *history = NULL;
8296 static char *recip = "RECIPIENTS";
8297 static char *partic = "PARTICIPANTS";
8298 static char *match_me = N_("[Match_My_Addresses]");
8299 static char *dont_match_me = N_("[Don't_Match_My_Addresses]");
8301 ps_global->mangled_footer = 1;
8302 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8304 while(1){
8305 type = radio_buttons(not ? _(sel_text_not) : _(sel_text),
8306 -FOOTER_ROWS(ps_global), sel_text_opt,
8307 's', 'x', NO_HELP, RB_NORM|RB_RET_HELP);
8309 if(type == '!')
8310 not = !not;
8311 else if(type == 3){
8312 helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS",
8313 HLPD_SIMPLE);
8314 ps_global->mangled_screen = 1;
8316 else
8317 break;
8321 * prepare some friendly defaults...
8323 switch(type){
8324 case 't' : /* address fields, offer To or From */
8325 case 'f' :
8326 case 'c' :
8327 case 'r' :
8328 case 'p' :
8329 sval = (type == 't') ? "TO" :
8330 (type == 'f') ? "FROM" :
8331 (type == 'c') ? "CC" :
8332 (type == 'r') ? recip : partic;
8333 ekey[ekeyi].ch = ctrl('T');
8334 ekey[ekeyi].name = "^T";
8335 ekey[ekeyi].rval = 10;
8336 /* TRANSLATORS: use Current To Address */
8337 ekey[ekeyi++].label = N_("Cur To");
8338 ekey[ekeyi].ch = ctrl('R');
8339 ekey[ekeyi].name = "^R";
8340 ekey[ekeyi].rval = 11;
8341 /* TRANSLATORS: use Current From Address */
8342 ekey[ekeyi++].label = N_("Cur From");
8343 ekey[ekeyi].ch = ctrl('W');
8344 ekey[ekeyi].name = "^W";
8345 ekey[ekeyi].rval = 12;
8346 /* TRANSLATORS: use Current Cc Address */
8347 ekey[ekeyi++].label = N_("Cur Cc");
8348 ekey[ekeyi].ch = ctrl('Y');
8349 ekey[ekeyi].name = "^Y";
8350 ekey[ekeyi].rval = 13;
8351 /* TRANSLATORS: Match Me means match my address */
8352 ekey[ekeyi++].label = N_("Match Me");
8353 ekey[ekeyi].ch = 0;
8354 ekey[ekeyi].name = "";
8355 ekey[ekeyi].rval = 0;
8356 ekey[ekeyi++].label = "";
8357 break;
8359 case 's' :
8360 sval = "SUBJECT";
8361 ekey[ekeyi].ch = ctrl('X');
8362 ekey[ekeyi].name = "^X";
8363 ekey[ekeyi].rval = 14;
8364 /* TRANSLATORS: use Current Subject */
8365 ekey[ekeyi++].label = N_("Cur Subject");
8366 break;
8368 case 'a' :
8369 sval = "TEXT";
8370 break;
8372 case 'b' :
8373 sval = "BODYTEXT";
8374 break;
8376 case 'h' :
8377 strncpy(tmp, "Name of HEADER to match : ", sizeof(tmp)-1);
8378 tmp[sizeof(tmp)-1] = '\0';
8379 flags = OE_APPEND_CURRENT;
8380 namehdr[0] = '\0';
8381 r = 'x';
8382 while (r == 'x'){
8383 int done = 0;
8385 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8386 sizeof(namehdr), tmp, ekey, NO_HELP, &flags);
8387 if (r == 1){
8388 cmd_cancelled("Selection by text");
8389 return(1);
8391 removing_leading_white_space(namehdr);
8392 while(!done){
8393 while ((namehdr[0] != '\0') && /* remove trailing ":" */
8394 (namehdr[strlen(namehdr) - 1] == ':'))
8395 namehdr[strlen(namehdr) - 1] = '\0';
8396 if ((namehdr[0] != '\0')
8397 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8398 removing_trailing_white_space(namehdr);
8399 else
8400 done++;
8402 if (strchr(namehdr,' ') || strchr(namehdr,'\t') ||
8403 strchr(namehdr,':'))
8404 namehdr[0] = '\0';
8405 if (namehdr[0] == '\0')
8406 r = 'x';
8408 sval = namehdr;
8409 break;
8411 case 'x':
8412 break;
8414 default:
8415 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
8416 return(1);
8419 ekey[ekeyi].ch = KEY_UP;
8420 ekey[ekeyi].rval = 30;
8421 ekey[ekeyi].name = "";
8422 ku = ekeyi;
8423 ekey[ekeyi++].label = "";
8425 ekey[ekeyi].ch = KEY_DOWN;
8426 ekey[ekeyi].rval = 31;
8427 ekey[ekeyi].name = "";
8428 ekey[ekeyi++].label = "";
8430 ekey[ekeyi].ch = -1;
8432 if(type != 'x'){
8434 init_hist(&history, HISTSIZE);
8436 if(ekey[0].ch > -1 && msgno > 0L
8437 && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),
8438 NULL)))
8439 ekey[0].ch = -1;
8441 sstring[0] = '\0';
8442 help = NO_HELP;
8443 r = type;
8444 while(r != 'x'){
8445 if(not)
8446 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8447 snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval);
8448 else
8449 snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval);
8451 if(items_in_hist(history) > 0){
8452 ekey[ku].name = HISTORY_UP_KEYNAME;
8453 ekey[ku].label = HISTORY_KEYLABEL;
8454 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
8455 ekey[ku+1].label = HISTORY_KEYLABEL;
8457 else{
8458 ekey[ku].name = "";
8459 ekey[ku].label = "";
8460 ekey[ku+1].name = "";
8461 ekey[ku+1].label = "";
8464 flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
8465 r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
8466 sizeof(sstring), tmp, ekey, help, &flags);
8468 if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me)))))
8469 me = 0;
8471 switch(r){
8472 case 3 :
8473 help = (help == NO_HELP)
8474 ? (not
8475 ? ((type == 'f') ? h_select_txt_not_from
8476 : (type == 't') ? h_select_txt_not_to
8477 : (type == 'c') ? h_select_txt_not_cc
8478 : (type == 's') ? h_select_txt_not_subj
8479 : (type == 'a') ? h_select_txt_not_all
8480 : (type == 'r') ? h_select_txt_not_recip
8481 : (type == 'p') ? h_select_txt_not_partic
8482 : (type == 'b') ? h_select_txt_not_body
8483 : NO_HELP)
8484 : ((type == 'f') ? h_select_txt_from
8485 : (type == 't') ? h_select_txt_to
8486 : (type == 'c') ? h_select_txt_cc
8487 : (type == 's') ? h_select_txt_subj
8488 : (type == 'a') ? h_select_txt_all
8489 : (type == 'r') ? h_select_txt_recip
8490 : (type == 'p') ? h_select_txt_partic
8491 : (type == 'b') ? h_select_txt_body
8492 : NO_HELP))
8493 : NO_HELP;
8495 case 4 :
8496 continue;
8498 case 10 : /* To: default */
8499 if(env && env->to && env->to->mailbox){
8500 snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox,
8501 env->to->host ? "@" : "",
8502 env->to->host ? env->to->host : "");
8503 sstring[sizeof(sstring)-1] = '\0';
8505 continue;
8507 case 11 : /* From: default */
8508 if(env && env->from && env->from->mailbox){
8509 snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox,
8510 env->from->host ? "@" : "",
8511 env->from->host ? env->from->host : "");
8512 sstring[sizeof(sstring)-1] = '\0';
8514 continue;
8516 case 12 : /* Cc: default */
8517 if(env && env->cc && env->cc->mailbox){
8518 snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox,
8519 env->cc->host ? "@" : "",
8520 env->cc->host ? env->cc->host : "");
8521 sstring[sizeof(sstring)-1] = '\0';
8523 continue;
8525 case 13 : /* Match my addresses */
8526 me++;
8527 snprintf(sstring, sizeof(sstring), "%s", not ? _(dont_match_me) : _(match_me));
8528 continue;
8530 case 14 : /* Subject: default */
8531 if(env && env->subject && env->subject[0]){
8532 char *q = NULL;
8534 q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
8535 SIZEOF_20KBUF, env->subject);
8536 snprintf(sstring, sizeof(sstring), "%s", q);
8537 sstring[sizeof(sstring)-1] = '\0';
8540 continue;
8542 case 30 :
8543 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8544 if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){
8545 strncpy(sstring, p, sizeof(sstring));
8546 sstring[sizeof(sstring)-1] = '\0';
8547 if(history->hist[history->curindex]){
8548 flagsforhist = history->hist[history->curindex]->flags;
8549 not = (flagsforhist & 0x1) ? 1 : 0;
8550 me = (flagsforhist & 0x2) ? 1 : 0;
8553 else
8554 Writechar(BELL, 0);
8556 continue;
8558 case 31 :
8559 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8560 if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){
8561 strncpy(sstring, p, sizeof(sstring));
8562 sstring[sizeof(sstring)-1] = '\0';
8563 if(history->hist[history->curindex]){
8564 flagsforhist = history->hist[history->curindex]->flags;
8565 not = (flagsforhist & 0x1) ? 1 : 0;
8566 me = (flagsforhist & 0x2) ? 1 : 0;
8569 else
8570 Writechar(BELL, 0);
8572 continue;
8574 default :
8575 break;
8578 if(r == 1 || sstring[0] == '\0')
8579 r = 'x';
8581 break;
8585 if(type == 'x' || r == 'x'){
8586 cmd_cancelled("Selection by text");
8587 return(1);
8590 if(ps_global && ps_global->ttyo){
8591 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8592 ps_global->mangled_footer = 1;
8595 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8597 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8598 save_hist(history, sstring, flagsforhist, NULL);
8600 rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch);
8601 if(we_cancel)
8602 cancel_busy_cue(0);
8604 return(rv);
8609 * Select by message size.
8610 * Sets searched bits in mail_elts
8612 * Args limitsrch -- limit search to this searchset
8614 * Returns 0 on success.
8617 select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch)
8619 int r, large = 1, we_cancel = 0;
8620 unsigned long n, mult = 1L, numerator = 0L, divisor = 1L;
8621 char size[16], numbers[80], *p, *t;
8622 HelpType help;
8623 SEARCHPGM *pgm;
8624 long flags = (SE_NOPREFETCH | SE_FREE);
8626 numbers[0] = '\0';
8627 ps_global->mangled_footer = 1;
8629 help = NO_HELP;
8630 while(1){
8631 int flgs = OE_APPEND_CURRENT;
8633 sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger;
8635 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
8636 sizeof(numbers), large ? _(select_size_larger_msg)
8637 : _(select_size_smaller_msg),
8638 sel_size_opt, help, &flgs);
8639 if(r == 4)
8640 continue;
8642 if(r == 14){
8643 large = 1 - large;
8644 continue;
8647 if(r == 3){
8648 help = (help == NO_HELP) ? (large ? h_select_by_larger_size
8649 : h_select_by_smaller_size)
8650 : NO_HELP;
8651 continue;
8654 for(t = p = numbers; *p ; p++) /* strip whitespace */
8655 if(!isspace((unsigned char)*p))
8656 *t++ = *p;
8658 *t = '\0';
8660 if(r == 1 || numbers[0] == '\0'){
8661 cmd_cancelled("Selection by size");
8662 return(1);
8664 else
8665 break;
8668 if(numbers[0] == '-'){
8669 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8670 _("Invalid size entered: %s"), numbers);
8671 return(1);
8674 t = size;
8675 p = numbers;
8677 while(*p && isdigit((unsigned char)*p))
8678 *t++ = *p++;
8680 *t = '\0';
8682 if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){
8683 size[0] = '0';
8684 size[1] = '\0';
8687 if(size[0] == '\0'){
8688 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8689 _("Invalid size entered: %s"), numbers);
8690 return(1);
8693 n = strtoul(size, (char **)NULL, 10);
8695 size[0] = '\0';
8696 if(*p == '.'){
8698 * We probably ought to just use atof() to convert 1.1 into a
8699 * double, but since we haven't used atof() anywhere else I'm
8700 * reluctant to use it because of portability concerns.
8702 p++;
8703 t = size;
8704 while(*p && isdigit((unsigned char)*p)){
8705 *t++ = *p++;
8706 divisor *= 10;
8709 *t = '\0';
8711 if(size[0])
8712 numerator = strtoul(size, (char **)NULL, 10);
8715 switch(*p){
8716 case 'g':
8717 case 'G':
8718 mult *= 1000;
8719 /* fall through */
8721 case 'm':
8722 case 'M':
8723 mult *= 1000;
8724 /* fall through */
8726 case 'k':
8727 case 'K':
8728 mult *= 1000;
8729 break;
8732 n = n * mult + (numerator * mult) / divisor;
8734 pgm = mail_newsearchpgm();
8735 if(large)
8736 pgm->larger = n;
8737 else
8738 pgm->smaller = n;
8740 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8741 flags |= SE_NOSERVER;
8743 if(ps_global && ps_global->ttyo){
8744 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8745 ps_global->mangled_footer = 1;
8748 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8750 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8751 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8752 /* we know this was freed in mail_search, let caller know */
8753 if(limitsrch)
8754 *limitsrch = NULL;
8756 if(we_cancel)
8757 cancel_busy_cue(0);
8759 return(0);
8764 * visible_searchset -- return c-client search set unEXLDed
8765 * sequence numbers
8767 SEARCHSET *
8768 visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap)
8770 long n, run;
8771 SEARCHSET *full_set = NULL, **set;
8774 * If we're talking to anything other than a server older than
8775 * imap 4rev1, build a searchset otherwise it'll choke.
8777 if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
8778 if(any_lflagged(msgmap, MN_EXLD)){
8779 for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
8780 if(get_lflag(stream, NULL, n, MN_EXLD)){
8781 if(run){ /* previous NOT excluded? */
8782 if(run > 1L)
8783 (*set)->last = n - 1L;
8785 set = &(*set)->next;
8786 run = 0L;
8789 else if(run++){ /* next in run */
8790 (*set)->last = n;
8792 else{ /* start of run */
8793 *set = mail_newsearchset();
8794 (*set)->first = n;
8797 else{
8798 full_set = mail_newsearchset();
8799 full_set->first = 1L;
8800 full_set->last = stream->nmsgs;
8804 return(full_set);
8809 * Select by message status bits.
8810 * Sets searched bits in mail_elts
8812 * Args limitsrch -- limit search to this searchset
8814 * Returns 0 on success.
8817 select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch)
8819 int s, not = 0, we_cancel = 0, rv;
8821 while(1){
8822 s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag),
8823 -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x',
8824 NO_HELP, RB_NORM|RB_RET_HELP);
8826 if(s == 'x'){
8827 cmd_cancelled("Selection by status");
8828 return(1);
8830 else if(s == 3){
8831 helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"),
8832 HLPD_SIMPLE);
8833 ps_global->mangled_screen = 1;
8835 else if(s == '!')
8836 not = !not;
8837 else
8838 break;
8841 if(ps_global && ps_global->ttyo){
8842 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8843 ps_global->mangled_footer = 1;
8846 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8847 rv = agg_flag_select(stream, not, s, limitsrch);
8848 if(we_cancel)
8849 cancel_busy_cue(0);
8851 return(rv);
8856 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8857 * Sets searched bits in mail_elts
8859 * Args limitsrch -- limit search to this searchset
8861 * Returns 0 on success.
8864 select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
8866 char rulenick[1000], *nick;
8867 PATGRP_S *patgrp;
8868 int r, last_r = 0, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
8869 | ROLE_DO_INCOLS
8870 | ROLE_DO_ROLES
8871 | ROLE_DO_SCORES
8872 | ROLE_DO_OTHER
8873 | ROLE_DO_FILTER;
8875 rulenick[0] = '\0';
8876 ps_global->mangled_footer = 1;
8878 if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
8879 sel_rule_opt[2].ch = TAB;
8880 sel_rule_opt[2].rval = 15;
8881 sel_rule_opt[2].name = "TAB";
8882 sel_rule_opt[2].label = N_("Complete");
8884 else{
8885 memset(&sel_rule_opt[2], 0, sizeof(sel_rule_opt[2]));
8889 int oe_flags;
8891 oe_flags = OE_APPEND_CURRENT;
8892 r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0,
8893 sizeof(rulenick),
8894 not ? _("Rule to NOT match: ")
8895 : _("Rule to match: "),
8896 sel_rule_opt, NO_HELP, &oe_flags);
8898 if(r == 14){
8899 /* select rulenick from a list */
8900 if((nick=choose_a_rule(rflags, NULL)) != NULL){
8901 strncpy(rulenick, nick, sizeof(rulenick)-1);
8902 rulenick[sizeof(rulenick)-1] = '\0';
8903 fs_give((void **) &nick);
8905 else
8906 r = 4;
8908 else if(r == 15){
8909 int n = rulenick_complete(rflags, rulenick, sizeof(rulenick));
8911 if(n > 1 && last_r == 15 && !(oe_flags & OE_USER_MODIFIED)){
8912 /* double tab with multiple completions: select from list */
8913 if((nick=choose_a_rule(rflags, rulenick)) != NULL){
8914 strncpy(rulenick, nick, sizeof(rulenick)-1);
8915 rulenick[sizeof(rulenick)-1] = '\0';
8916 fs_give((void **) &nick);
8917 r = 0;
8919 else
8920 r = 4;
8922 else if(n != 1)
8923 Writechar(BELL, 0);
8925 else if(r == '!')
8926 not = !not;
8928 if(r == 3){
8929 helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE);
8930 ps_global->mangled_screen = 1;
8932 else if(r == 1){
8933 cmd_cancelled("Selection by Rule");
8934 return(1);
8937 removing_leading_and_trailing_white_space(rulenick);
8938 last_r = r;
8940 }while(r == 3 || r == 4 || r == 15 || r == '!');
8944 * The approach of requiring a nickname instead of just allowing the
8945 * user to select from the list of rules has the drawback that a rule
8946 * may not have a nickname, or there may be more than one rule with
8947 * the same nickname. However, it has the benefit of allowing the user
8948 * to type in the nickname and, most importantly, allows us to set
8949 * up the ! (not). We could incorporate the ! into the selection
8950 * screen, but this is easier and also allows the typing of nicks.
8951 * User can just set up nicknames if they want to use this feature.
8953 patgrp = nick_to_patgrp(rulenick, rflags);
8955 if(patgrp){
8956 if(ps_global && ps_global->ttyo){
8957 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8958 ps_global->mangled_footer = 1;
8961 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8962 match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL,
8963 get_msg_score,
8964 (not ? MP_NOT : 0) | SE_NOPREFETCH);
8965 free_patgrp(&patgrp);
8966 if(we_cancel)
8967 cancel_busy_cue(0);
8970 if(limitsrch && *limitsrch){
8971 mail_free_searchset(limitsrch);
8972 *limitsrch = NULL;
8975 return(0);
8980 * Allow user to choose a rule from their list of rules.
8982 * Args rflags -- Pattern types to choose from
8983 * prefix -- Only list rules matching the given prefix
8985 * Returns an allocated rule nickname on success, NULL otherwise.
8987 char *
8988 choose_a_rule(int rflags, char *prefix)
8990 char *choice = NULL;
8991 char **rule_list, **lp;
8992 int cnt = 0;
8993 int prefix_length = 0;
8994 PAT_S *pat;
8995 PAT_STATE pstate;
8996 void (*redraw)(void) = ps_global->redrawer;
8998 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){
8999 q_status_message(SM_ORDER, 3, 3,
9000 _("No rules available. Use Setup/Rules to add some."));
9001 return(choice);
9005 * Build a list of rules to choose from.
9008 if(prefix)
9009 prefix_length = strlen(prefix);
9011 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)) {
9012 if(!pat->patgrp || !pat->patgrp->nick)
9013 continue;
9014 if(!prefix || strncmp(pat->patgrp->nick, prefix, prefix_length) == 0)
9015 cnt++;
9018 if(cnt <= 0){
9019 q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
9020 return(choice);
9023 lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
9024 memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
9026 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)) {
9027 if(!pat->patgrp || !pat->patgrp->nick)
9028 continue;
9029 if(!prefix || strncmp(pat->patgrp->nick, prefix, prefix_length) == 0)
9030 *lp++ = cpystr(pat->patgrp->nick);
9033 /* TRANSLATORS: SELECT A RULE is a screen title
9034 TRANSLATORS: Print something1 using something2.
9035 "rules" is something1 */
9036 choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"),
9037 _("rules"), h_select_rule_screen,
9038 _("HELP FOR SELECTING A RULE NICKNAME"), NULL);
9040 if(!choice){
9041 q_status_message(SM_ORDER, 1, 4, "No choice");
9042 ps_global->redrawer = redraw;
9045 free_list_array(&rule_list);
9047 return(choice);
9052 * Complete a partial rule name against the user's list of rules.
9054 * Args rflags -- Pattern types to choose from
9055 * nick -- Current rule nickname to complete
9056 * nick_size -- Maximum length of the nick array to store completion in
9058 * Returns the number of valid completions, i.e.:
9060 * 0 if there are no valid completions. The value of the nick argument has not
9061 * been changed.
9062 * 1 if there is only a single valid completion. The value of the nick argument
9063 * has been replaced with the full text of that completion.
9064 * >1 if there is more than one valid completion. The value of the nick argument
9065 * has been replaced with the longest substring common to all the appropriate
9066 * completions.
9069 rulenick_complete(int rflags, char *nick, int nick_size)
9071 char *candidate = NULL;
9072 int cnt = 0;
9073 int common_prefix_length = 0;
9074 int nick_length;
9075 PAT_S *pat;
9076 PAT_STATE pstate;
9078 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate)))
9079 return 0;
9081 nick_length = strlen(nick);
9083 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){
9084 if(!pat->patgrp || !pat->patgrp->nick)
9085 continue;
9086 if(strncmp(pat->patgrp->nick, nick, nick_length) == 0){
9087 /* This is a candidate for completion. */
9088 cnt++;
9089 if(!candidate){
9090 /* This is the first candidate. Keep it as a future reference to
9091 * compare against to find the longest common prefix length of
9092 * all the matches. */
9093 candidate = pat->patgrp->nick;
9094 common_prefix_length = strlen(pat->patgrp->nick);
9096 else{
9097 /* Find the common prefix length between the first candidate and
9098 * this one. */
9099 int i;
9100 for(i = 0; i < common_prefix_length; i++){
9101 if(pat->patgrp->nick[i] != candidate[i]){
9102 /* In the event that we ended up in the middle of a
9103 * UTF-8 code point, backtrack to the byte before the
9104 * start of this code point. */
9105 while(i > 0 && (candidate[i] & 0xC0) == 0x80)
9106 i--;
9107 common_prefix_length = i;
9108 break;
9115 if(cnt > 0){
9116 int length = MIN(nick_size, common_prefix_length);
9117 strncpy(nick + nick_length, candidate + nick_length, length - nick_length);
9118 nick[length] = '\0';
9121 return cnt;
9126 * Select by current thread.
9127 * Sets searched bits in mail_elts for this entire thread
9129 * Args limitsrch -- limit search to this searchset
9131 * Returns 0 on success.
9134 select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
9136 long n;
9137 PINETHRD_S *thrd = NULL;
9138 int ret = 1;
9139 MESSAGECACHE *mc;
9141 if(!stream)
9142 return(ret);
9144 for(n = 1L; n <= stream->nmsgs; n++)
9145 if((mc = mail_elt(stream, n)) != NULL)
9146 mc->searched = 0; /* clear searched bits */
9148 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
9149 if(thrd && thrd->top && thrd->top != thrd->rawno)
9150 thrd = fetch_thread(stream, thrd->top);
9153 * This doesn't unselect if the thread is already selected
9154 * (like select current does), it always selects.
9155 * There is no way to select ! this thread.
9157 if(thrd){
9158 set_search_bit_for_thread(stream, thrd, limitsrch);
9159 ret = 0;
9162 return(ret);
9167 * Select by message keywords.
9168 * Sets searched bits in mail_elts
9170 * Args limitsrch -- limit search to this searchset
9172 * Returns 0 on success.
9175 select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
9177 int r, not = 0, we_cancel = 0;
9178 char keyword[MAXUSERFLAG+1], *kword;
9179 char *error = NULL, *p, *prompt;
9180 HelpType help;
9181 SEARCHPGM *pgm;
9183 keyword[0] = '\0';
9184 ps_global->mangled_footer = 1;
9186 help = NO_HELP;
9188 int oe_flags;
9190 if(error){
9191 q_status_message(SM_ORDER, 3, 4, error);
9192 fs_give((void **) &error);
9195 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
9196 if(not)
9197 prompt = _("Keyword (or keyword initial) to NOT match: ");
9198 else
9199 prompt = _("Keyword (or keyword initial) to match: ");
9201 else{
9202 if(not)
9203 prompt = _("Keyword to NOT match: ");
9204 else
9205 prompt = _("Keyword to match: ");
9208 oe_flags = OE_APPEND_CURRENT;
9209 r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0,
9210 sizeof(keyword),
9211 prompt, sel_key_opt, help, &oe_flags);
9213 if(r == 14){
9214 /* select keyword from a list */
9215 if((kword=choose_a_keyword()) != NULL){
9216 strncpy(keyword, kword, sizeof(keyword)-1);
9217 keyword[sizeof(keyword)-1] = '\0';
9218 fs_give((void **) &kword);
9220 else
9221 r = 4;
9223 else if(r == '!')
9224 not = !not;
9226 if(r == 3)
9227 help = help == NO_HELP ? h_select_keyword : NO_HELP;
9228 else if(r == 1){
9229 cmd_cancelled("Selection by keyword");
9230 return(1);
9233 removing_leading_and_trailing_white_space(keyword);
9235 }while(r == 3 || r == 4 || r == '!' || keyword_check(keyword, &error));
9238 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
9239 p = initial_to_keyword(keyword);
9240 if(p != keyword){
9241 strncpy(keyword, p, sizeof(keyword)-1);
9242 keyword[sizeof(keyword)-1] = '\0';
9247 * We want to check the keyword, not the nickname of the keyword,
9248 * so convert it to the keyword if necessary.
9250 p = nick_to_keyword(keyword);
9251 if(p != keyword){
9252 strncpy(keyword, p, sizeof(keyword)-1);
9253 keyword[sizeof(keyword)-1] = '\0';
9256 pgm = mail_newsearchpgm();
9257 if(not){
9258 pgm->unkeyword = mail_newstringlist();
9259 pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword);
9260 pgm->unkeyword->text.size = strlen(keyword);
9262 else{
9263 pgm->keyword = mail_newstringlist();
9264 pgm->keyword->text.data = (unsigned char *) cpystr(keyword);
9265 pgm->keyword->text.size = strlen(keyword);
9268 if(ps_global && ps_global->ttyo){
9269 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9270 ps_global->mangled_footer = 1;
9273 we_cancel = busy_cue(_("Selecting"), NULL, 1);
9275 pgm->msgno = (limitsrch ? *limitsrch : NULL);
9276 pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE);
9277 /* we know this was freed in mail_search, let caller know */
9278 if(limitsrch)
9279 *limitsrch = NULL;
9281 if(we_cancel)
9282 cancel_busy_cue(0);
9284 return(0);
9289 * Allow user to choose a keyword from their list of keywords.
9291 * Returns an allocated keyword on success, NULL otherwise.
9293 char *
9294 choose_a_keyword(void)
9296 char *choice = NULL;
9297 char **keyword_list, **lp;
9298 int cnt;
9299 KEYWORD_S *kw;
9300 void (*redraw)(void) = ps_global->redrawer;
9303 * Build a list of keywords to choose from.
9306 for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next)
9307 cnt++;
9309 if(cnt <= 0){
9310 q_status_message(SM_ORDER, 3, 4,
9311 _("No keywords defined, use \"keywords\" option in Setup/Config"));
9312 return(choice);
9315 lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list));
9316 memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list));
9318 for(kw = ps_global->keywords; kw; kw = kw->next)
9319 *lp++ = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9321 /* TRANSLATORS: SELECT A KEYWORD is a screen title
9322 TRANSLATORS: Print something1 using something2.
9323 "keywords" is something1 */
9324 choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"),
9325 _("keywords"), h_select_keyword_screen,
9326 _("HELP FOR SELECTING A KEYWORD"), NULL);
9328 if(!choice){
9329 q_status_message(SM_ORDER, 1, 4, "No choice");
9330 ps_global->redrawer = redraw;
9333 free_list_array(&keyword_list);
9335 return(choice);
9340 * Allow user to choose a list of keywords from their list of keywords.
9342 * Returns allocated list.
9344 char **
9345 choose_list_of_keywords(void)
9347 LIST_SEL_S *listhead, *ls, *p;
9348 char **ret = NULL;
9349 int cnt, i;
9350 KEYWORD_S *kw;
9353 * Build a list of keywords to choose from.
9356 p = listhead = NULL;
9357 for(kw = ps_global->keywords; kw; kw = kw->next){
9359 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9360 memset(ls, 0, sizeof(*ls));
9361 ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9363 if(p){
9364 p->next = ls;
9365 p = p->next;
9367 else
9368 listhead = p = ls;
9371 if(!listhead)
9372 return(ret);
9374 /* TRANSLATORS: SELECT KEYWORDS is a screen title
9375 Print something1 using something2.
9376 "keywords" is something1 */
9377 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9378 _("SELECT KEYWORDS"), _("keywords"),
9379 h_select_multkeyword_screen,
9380 _("HELP FOR SELECTING KEYWORDS"), NULL)){
9381 for(cnt = 0, p = listhead; p; p = p->next)
9382 if(p->selected)
9383 cnt++;
9385 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9386 memset(ret, 0, (cnt+1) * sizeof(*ret));
9387 for(i = 0, p = listhead; p; p = p->next)
9388 if(p->selected)
9389 ret[i++] = cpystr(p->item ? p->item : "");
9392 free_list_sel(&listhead);
9394 return(ret);
9399 * Allow user to choose a charset
9401 * Returns an allocated charset on success, NULL otherwise.
9403 char *
9404 choose_a_charset(int which_charsets)
9406 char *choice = NULL;
9407 char **charset_list, **lp;
9408 const CHARSET *cs;
9409 int cnt;
9410 void (*redraw)(void) = ps_global->redrawer;
9413 * Build a list of charsets to choose from.
9416 for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){
9417 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9418 && ((which_charsets & CAC_ALL)
9419 || (which_charsets & CAC_POSTING
9420 && cs->flags & CF_POSTING)
9421 || (which_charsets & CAC_DISPLAY
9422 && cs->type != CT_2022
9423 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9424 cnt++;
9427 if(cnt <= 0){
9428 q_status_message(SM_ORDER, 3, 4,
9429 _("No charsets found? Enter charset manually."));
9430 return(choice);
9433 lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list));
9434 memset(charset_list, 0, (cnt+1) * sizeof(*charset_list));
9436 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9437 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9438 && ((which_charsets & CAC_ALL)
9439 || (which_charsets & CAC_POSTING
9440 && cs->flags & CF_POSTING)
9441 || (which_charsets & CAC_DISPLAY
9442 && cs->type != CT_2022
9443 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9444 *lp++ = cpystr(cs->name);
9447 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
9448 TRANSLATORS: Print something1 using something2.
9449 "character sets" is something1 */
9450 choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"),
9451 _("character sets"), h_select_charset_screen,
9452 _("HELP FOR SELECTING A CHARACTER SET"), NULL);
9454 if(!choice){
9455 q_status_message(SM_ORDER, 1, 4, "No choice");
9456 ps_global->redrawer = redraw;
9459 free_list_array(&charset_list);
9461 return(choice);
9466 * Allow user to choose a list of character sets and/or scripts
9468 * Returns allocated list.
9470 char **
9471 choose_list_of_charsets(void)
9473 LIST_SEL_S *listhead, *ls, *p;
9474 char **ret = NULL;
9475 int cnt, i, got_one;
9476 const CHARSET *cs;
9477 SCRIPT *s;
9478 char *q, *t;
9479 long width, limit;
9480 char buf[1024], *folded;
9483 * Build a list of charsets to choose from.
9486 p = listhead = NULL;
9488 /* this width is determined by select_from_list_screen() */
9489 width = ps_global->ttyo->screen_cols - 4;
9491 /* first comes a list of scripts (sets of character sets) */
9492 for(s = utf8_script(NIL); s && s->name; s++){
9494 limit = sizeof(buf)-1;
9495 q = buf;
9496 memset(q, 0, limit+1);
9498 if(s->name)
9499 sstrncpy(&q, s->name, limit);
9501 if(s->description){
9502 sstrncpy(&q, " (", limit-(q-buf));
9503 sstrncpy(&q, s->description, limit-(q-buf));
9504 sstrncpy(&q, ")", limit-(q-buf));
9507 /* add the list of charsets that are in this script */
9508 got_one = 0;
9509 for(cs = utf8_charset(NIL);
9510 cs && cs->name && (q-buf) < limit; cs++){
9511 if(cs->script & s->script){
9513 * Filter out some un-useful members of the list.
9514 * UTF-7 and UTF-8 weren't actually in the list at the
9515 * time this was written. Just making sure.
9517 if(!strucmp(cs->name, "ISO-2022-JP-2")
9518 || !strucmp(cs->name, "UTF-7")
9519 || !strucmp(cs->name, "UTF-8"))
9520 continue;
9522 if(got_one)
9523 sstrncpy(&q, " ", limit-(q-buf));
9524 else{
9525 got_one = 1;
9526 sstrncpy(&q, " {", limit-(q-buf));
9529 sstrncpy(&q, cs->name, limit-(q-buf));
9533 if(got_one)
9534 sstrncpy(&q, "}", limit-(q-buf));
9536 /* fold this line so that it can all be seen on the screen */
9537 folded = fold(buf, width, width, "", " ", FLD_NONE);
9538 if(folded){
9539 t = folded;
9540 while(t && *t && (q = strindex(t, '\n')) != NULL){
9541 *q = '\0';
9543 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9544 memset(ls, 0, sizeof(*ls));
9545 if(t == folded)
9546 ls->item = cpystr(s->name);
9547 else
9548 ls->flags = SFL_NOSELECT;
9550 ls->display_item = cpystr(t);
9552 t = q+1;
9554 if(p){
9555 p->next = ls;
9556 p = p->next;
9558 else{
9559 /* add a heading */
9560 listhead = (LIST_SEL_S *) fs_get(sizeof(*ls));
9561 memset(listhead, 0, sizeof(*listhead));
9562 listhead->flags = SFL_NOSELECT;
9563 listhead->display_item =
9564 cpystr(_("Scripts representing groups of related character sets"));
9565 listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls));
9566 memset(listhead->next, 0, sizeof(*listhead));
9567 listhead->next->flags = SFL_NOSELECT;
9568 listhead->next->display_item =
9569 cpystr(repeat_char(width, '-'));
9571 listhead->next->next = ls;
9572 p = ls;
9576 fs_give((void **) &folded);
9580 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9581 memset(ls, 0, sizeof(*ls));
9582 ls->flags = SFL_NOSELECT;
9583 if(p){
9584 p->next = ls;
9585 p = p->next;
9587 else
9588 listhead = p = ls;
9590 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9591 memset(ls, 0, sizeof(*ls));
9592 ls->flags = SFL_NOSELECT;
9593 ls->display_item =
9594 cpystr(_("Individual character sets, may be mixed with scripts"));
9595 p->next = ls;
9596 p = p->next;
9598 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9599 memset(ls, 0, sizeof(*ls));
9600 ls->flags = SFL_NOSELECT;
9601 ls->display_item =
9602 cpystr(repeat_char(width, '-'));
9603 p->next = ls;
9604 p = p->next;
9606 /* then comes a list of individual character sets */
9607 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9608 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9609 memset(ls, 0, sizeof(*ls));
9610 ls->item = cpystr(cs->name);
9612 if(p){
9613 p->next = ls;
9614 p = p->next;
9616 else
9617 listhead = p = ls;
9620 if(!listhead)
9621 return(ret);
9623 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9624 Print something1 using something2.
9625 "character sets" is something1 */
9626 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9627 _("SELECT CHARACTER SETS"), _("character sets"),
9628 h_select_multcharsets_screen,
9629 _("HELP FOR SELECTING CHARACTER SETS"), NULL)){
9630 for(cnt = 0, p = listhead; p; p = p->next)
9631 if(p->selected)
9632 cnt++;
9634 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9635 memset(ret, 0, (cnt+1) * sizeof(*ret));
9636 for(i = 0, p = listhead; p; p = p->next)
9637 if(p->selected)
9638 ret[i++] = cpystr(p->item ? p->item : "");
9641 free_list_sel(&listhead);
9643 return(ret);
9646 /* Report quota summary resources in an IMAP server */
9648 void
9649 cmd_quota (struct pine *state)
9651 QUOTALIST *imapquota;
9652 NETMBX mb;
9653 STORE_S *store;
9654 SCROLL_S sargs;
9656 if(!state->mail_stream || !is_imap_stream(state->mail_stream)){
9657 q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders");
9658 return;
9661 if (state->mail_stream
9662 && !sp_dead_stream(state->mail_stream)
9663 && state->mail_stream->mailbox
9664 && *state->mail_stream->mailbox
9665 && mail_valid_net_parse(state->mail_stream->mailbox, &mb))
9666 imap_getquotaroot(state->mail_stream, mb.mailbox);
9668 if(!state->quota) /* failed ? */
9669 return; /* go back... */
9671 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
9672 q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space.");
9673 return;
9676 so_puts(store, "Quota Report for ");
9677 so_puts(store, state->mail_stream->original_mailbox);
9678 so_puts(store, "\n\n");
9680 for (imapquota = state->quota; imapquota; imapquota = imapquota->next){
9682 so_puts(store, _("Resource : "));
9683 so_puts(store, imapquota->name);
9684 so_writec('\n', store);
9686 so_puts(store, _("Usage : "));
9687 so_puts(store, long2string(imapquota->usage));
9688 if(!strucmp(imapquota->name,"STORAGE"))
9689 so_puts(store, " KiB ");
9690 if(!strucmp(imapquota->name,"MESSAGE")){
9691 so_puts(store, _(" message"));
9692 if(imapquota->usage != 1)
9693 so_puts(store, _("s ")); /* plural */
9694 else
9695 so_puts(store, _(" "));
9697 so_writec('(', store);
9698 so_puts(store, long2string(100*imapquota->usage/imapquota->limit));
9699 so_puts(store, "%)\n");
9701 so_puts(store, _("Limit : "));
9702 so_puts(store, long2string(imapquota->limit));
9703 if(!strucmp(imapquota->name,"STORAGE"))
9704 so_puts(store, " KiB\n\n");
9705 if(!strucmp(imapquota->name,"MESSAGE")){
9706 so_puts(store, _(" message"));
9707 if(imapquota->usage != 1)
9708 so_puts(store, _("s\n\n")); /* plural */
9709 else
9710 so_puts(store, _("\n\n"));
9714 memset(&sargs, 0, sizeof(SCROLL_S));
9715 sargs.text.text = so_text(store);
9716 sargs.text.src = CharStar;
9717 sargs.text.desc = _("Quota Resources Summary");
9718 sargs.bar.title = _("QUOTA SUMMARY");
9719 sargs.proc.tool = NULL;
9720 sargs.help.text = h_quota_command;
9721 sargs.help.title = NULL;
9722 sargs.keys.menu = NULL;
9723 setbitmap(sargs.keys.bitmap);
9725 scrolltool(&sargs);
9726 so_give(&store);
9728 if (state->quota)
9729 mail_free_quotalist(&(state->quota));
9732 /*----------------------------------------------------------------------
9733 Prompt the user for the type of sort he desires
9735 Args: state -- pine state pointer
9736 q1 -- Line to prompt on
9738 Returns 0 if it was cancelled, 1 otherwise.
9739 ----*/
9741 select_sort(struct pine *state, int ql, SortOrder *sort, int *rev)
9743 char prompt[200], tmp[3], *p;
9744 int s, i;
9745 int deefault = 'a', retval = 1;
9746 HelpType help;
9747 ESCKEY_S sorts[14];
9749 #ifdef _WINDOWS
9750 DLG_SORTPARAM sortsel;
9752 if (mswin_usedialog ()) {
9754 sortsel.reverse = mn_get_revsort (state->msgmap);
9755 sortsel.cursort = mn_get_sort (state->msgmap);
9756 /* assumption here that HelpType is char ** */
9757 sortsel.helptext = h_select_sort;
9758 sortsel.rval = 0;
9760 if ((retval = os_sortdialog (&sortsel))) {
9761 *sort = sortsel.cursort;
9762 *rev = sortsel.reverse;
9765 return (retval);
9767 #endif
9769 /*----- String together the prompt ------*/
9770 tmp[1] = '\0';
9771 if(F_ON(F_USE_FK,ps_global))
9772 strncpy(prompt, _("Choose type of sort : "), sizeof(prompt));
9773 else
9774 strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "),
9775 sizeof(prompt));
9777 for(i = 0; state->sort_types[i] != EndofList; i++) {
9778 sorts[i].rval = i;
9779 p = sorts[i].label = sort_name(state->sort_types[i]);
9780 while(*(p+1) && islower((unsigned char)*p))
9781 p++;
9783 sorts[i].ch = tolower((unsigned char)(tmp[0] = *p));
9784 sorts[i].name = cpystr(tmp);
9786 if(mn_get_sort(state->msgmap) == state->sort_types[i])
9787 deefault = sorts[i].rval;
9790 sorts[i].ch = 'r';
9791 sorts[i].rval = 'r';
9792 sorts[i].name = cpystr("R");
9793 if(F_ON(F_USE_FK,ps_global))
9794 sorts[i].label = N_("Reverse");
9795 else
9796 sorts[i].label = "";
9798 sorts[++i].ch = -1;
9799 help = h_select_sort;
9801 if((F_ON(F_USE_FK,ps_global)
9802 && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x',
9803 help,RB_NORM)) != 'x'))
9805 (F_OFF(F_USE_FK,ps_global)
9806 && ((s = radio_buttons(prompt,ql,sorts,deefault,'x',
9807 help,RB_NORM)) != 'x'))){
9808 state->mangled_body = 1; /* signal screen's changed */
9809 if(s == 'r')
9810 *rev = !mn_get_revsort(state->msgmap);
9811 else
9812 *sort = state->sort_types[s];
9814 if(F_ON(F_SHOW_SORT, ps_global))
9815 ps_global->mangled_header = 1;
9817 else{
9818 retval = 0;
9819 cmd_cancelled("Sort");
9822 while(--i >= 0)
9823 fs_give((void **)&sorts[i].name);
9825 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9826 return(retval);
9830 /*---------------------------------------------------------------------
9831 Build list of folders in the given context for user selection
9833 Args: c -- pointer to pointer to folder's context context
9834 f -- folder prefix to display
9835 sublist -- whether or not to use 'f's contents as prefix
9836 lister -- function used to do the actual display
9838 Returns: malloc'd string containing sequence, else NULL if
9839 no messages in msgmap with local "selected" flag.
9840 ----*/
9842 display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int))
9844 int rc;
9845 CONTEXT_S *tc;
9846 void (*redraw)(void) = ps_global->redrawer;
9848 push_titlebar_state();
9849 tc = *c;
9850 if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0)
9851 *c = tc;
9853 ClearScreen();
9854 pop_titlebar_state();
9855 redraw_titlebar();
9856 if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
9857 (*ps_global->redrawer)();
9859 if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
9860 return(1);
9862 return(0);
9867 * Allow user to choose a single item from a list of strings.
9869 * Args list -- Array of strings to choose from, NULL terminated.
9870 * displist -- Array of strings to display instead of displaying list.
9871 * Indices correspond to the list array. Display the displist
9872 * but return the item from list if displist non-NULL.
9873 * title -- For conf_scroll_screen
9874 * pdesc -- For conf_scroll_screen
9875 * help -- For conf_scroll_screen
9876 * htitle -- For conf_scroll_screen
9878 * Returns an allocated copy of the chosen item or NULL.
9880 char *
9881 choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help,
9882 char *htitle, char *cursor_location)
9884 LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL;
9885 char **t, **dl;
9886 char *ret = NULL, *choice = NULL;
9888 /* build the LIST_SEL_S list */
9889 p = listhead = NULL;
9890 for(t = list, dl = displist; *t; t++, dl++){
9891 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9892 memset(ls, 0, sizeof(*ls));
9893 ls->item = cpystr(*t);
9894 if(displist)
9895 ls->display_item = cpystr(*dl);
9897 if(cursor_location && (cursor_location == (*t)))
9898 starting_val = ls;
9900 if(p){
9901 p->next = ls;
9902 p = p->next;
9904 else
9905 listhead = p = ls;
9908 if(!listhead)
9909 return(ret);
9911 if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc,
9912 help, htitle, starting_val))
9913 for(p = listhead; !choice && p; p = p->next)
9914 if(p->selected)
9915 choice = p->item;
9917 if(choice)
9918 ret = cpystr(choice);
9920 free_list_sel(&listhead);
9922 return(ret);
9926 void
9927 free_list_sel(LIST_SEL_S **lsel)
9929 if(lsel && *lsel){
9930 free_list_sel(&(*lsel)->next);
9931 if((*lsel)->item)
9932 fs_give((void **) &(*lsel)->item);
9934 if((*lsel)->display_item)
9935 fs_give((void **) &(*lsel)->display_item);
9937 fs_give((void **) lsel);
9943 * file_lister - call pico library's file lister
9946 file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags)
9948 PICO pbf;
9949 int rv;
9950 void (*redraw)(void) = ps_global->redrawer;
9952 standard_picobuf_setup(&pbf);
9953 push_titlebar_state();
9954 if(!newmail)
9955 pbf.newmail = NULL;
9957 /* BUG: what about help command and text? */
9958 pbf.pine_anchor = title;
9960 rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags);
9961 standard_picobuf_teardown(&pbf);
9962 fix_windsize(ps_global);
9963 init_signals(); /* has it's own signal stuff */
9965 /* Restore display's titlebar and body */
9966 pop_titlebar_state();
9967 redraw_titlebar();
9968 if((ps_global->redrawer = redraw) != NULL)
9969 (*ps_global->redrawer)();
9971 return(rv);
9975 /*----------------------------------------------------------------------
9976 Print current folder index
9978 ---*/
9980 print_index(struct pine *state, MSGNO_S *msgmap, int agg)
9982 long i;
9983 ICE_S *ice;
9984 char buf[MAX_SCREEN_COLS+1];
9986 for(i = 1L; i <= mn_get_total(msgmap); i++){
9987 if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
9988 continue;
9990 if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0))
9991 continue;
9993 ice = build_header_line(state, state->mail_stream, msgmap, i, NULL);
9995 if(ice){
9997 * I don't understand why we'd want to mark the current message
9998 * instead of printing out the first character of the status
9999 * so I'm taking it out and including the first character of the
10000 * line instead. Hubert 2006-02-09
10002 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
10003 return(0);
10006 if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i),
10007 print_char)
10008 || !gf_puts(NEWLINE, print_char))
10009 return(0);
10013 return(1);
10016 #ifdef _WINDOWS
10019 * windows callback to get/set header mode state
10022 header_mode_callback(set, args)
10023 int set;
10024 long args;
10026 return(ps_global->full_header);
10031 * windows callback to get/set zoom mode state
10034 zoom_mode_callback(set, args)
10035 int set;
10036 long args;
10038 return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0);
10043 * windows callback to get/set zoom mode state
10046 any_selected_callback(set, args)
10047 int set;
10048 long args;
10050 return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0);
10058 flag_callback(set, flags)
10059 int set;
10060 long flags;
10062 MESSAGECACHE *mc;
10063 int newflags = 0;
10064 long msgno;
10065 int permflag = 0;
10067 switch (set) {
10068 case 1: /* Important */
10069 permflag = ps_global->mail_stream->perm_flagged;
10070 break;
10072 case 2: /* New */
10073 permflag = ps_global->mail_stream->perm_seen;
10074 break;
10076 case 3: /* Answered */
10077 permflag = ps_global->mail_stream->perm_answered;
10078 break;
10080 case 4: /* Deleted */
10081 permflag = ps_global->mail_stream->perm_deleted;
10082 break;
10086 if(!(any_messages(ps_global->msgmap, NULL, "to Flag")
10087 && can_set_flag(ps_global, "flag", permflag)))
10088 return(0);
10090 if(sp_io_error_on_stream(ps_global->mail_stream)){
10091 sp_set_io_error_on_stream(ps_global->mail_stream, 0);
10092 pine_mail_check(ps_global->mail_stream); /* forces write */
10093 return(0);
10096 msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
10097 if(msgno > 0L && ps_global->mail_stream
10098 && msgno <= ps_global->mail_stream->nmsgs
10099 && (mc = mail_elt(ps_global->mail_stream, msgno))
10100 && mc->valid){
10102 * NOTE: code below is *VERY* sensitive to the order of
10103 * the messages defined in resource.h for flag handling.
10104 * Don't change it unless you know what you're doing.
10106 if(set){
10107 char *flagstr;
10108 long mflag;
10110 switch(set){
10111 case 1 : /* Important */
10112 flagstr = "\\FLAGGED";
10113 mflag = (mc->flagged) ? 0L : ST_SET;
10114 break;
10116 case 2 : /* New */
10117 flagstr = "\\SEEN";
10118 mflag = (mc->seen) ? 0L : ST_SET;
10119 break;
10121 case 3 : /* Answered */
10122 flagstr = "\\ANSWERED";
10123 mflag = (mc->answered) ? 0L : ST_SET;
10124 break;
10126 case 4 : /* Deleted */
10127 flagstr = "\\DELETED";
10128 mflag = (mc->deleted) ? 0L : ST_SET;
10129 break;
10131 default : /* bogus */
10132 return(0);
10135 mail_flag(ps_global->mail_stream, long2string(msgno),
10136 flagstr, mflag);
10138 if(ps_global->redrawer)
10139 (*ps_global->redrawer)();
10141 else{
10142 /* Important */
10143 if(mc->flagged)
10144 newflags |= 0x0001;
10146 /* New */
10147 if(!mc->seen)
10148 newflags |= 0x0002;
10150 /* Answered */
10151 if(mc->answered)
10152 newflags |= 0x0004;
10154 /* Deleted */
10155 if(mc->deleted)
10156 newflags |= 0x0008;
10160 return(newflags);
10166 * BUG: Should teach this about keywords
10168 MPopup *
10169 flag_submenu(mc)
10170 MESSAGECACHE *mc;
10172 static MPopup flag_submenu[] = {
10173 {tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}},
10174 {tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}},
10175 {tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}},
10176 {tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}},
10177 {tTail}
10180 /* Important */
10181 flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal;
10183 /* New */
10184 flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked;
10186 /* Answered */
10187 flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal;
10189 /* Deleted */
10190 flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal;
10192 return(flag_submenu);
10195 #endif /* _WINDOWS */