Add support for tab-completion when selecting by keyword
[alpine.git] / alpine / mailcmd.c
blob0dd4c50b906a0cb41500a9ab93848e791cf50a84
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(char *);
114 int keyword_complete(char *, int);
115 int select_sort(struct pine *, int, SortOrder *, int *);
116 int print_index(struct pine *, MSGNO_S *, int);
119 * List of Select options used by apply_* functions...
121 static char *sel_pmt1 = N_("ALTER message selection : ");
122 ESCKEY_S sel_opts1[] = {
123 /* TRANSLATORS: these are keymenu names for selecting. Broaden selection means
124 we will add more messages to the selection, Narrow selection means we will
125 remove some selections (like a logical AND instead of logical OR), and Flip
126 Selected means that all the messages that are currently selected become unselected,
127 and all the unselected messages become selected. */
128 {'a', 'a', "A", N_("unselect All")},
129 {'c', 'c', "C", NULL},
130 {'b', 'b', "B", N_("Broaden selctn")},
131 {'n', 'n', "N", N_("Narrow selctn")},
132 {'f', 'f', "F", N_("Flip selected")},
133 {'r', 'r', "R", N_("Replace selctn")},
134 {-1, 0, NULL, NULL}
138 #define SEL_OPTS_THREAD 9 /* index number of "tHread" */
139 #define SEL_OPTS_THREAD_CH 'h'
140 #define SEL_OPTS_XGMEXT 10 /* index number of "boX" */
141 #define SEL_OPTS_XGMEXT_CH 'g'
143 char *sel_pmt2 = "SELECT criteria : ";
144 static ESCKEY_S sel_opts2[] = {
145 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
146 means select the currently highlighted message; select by Number is by message
147 number; Status is by status of the message, for example the message might be
148 New or it might be Unseen or marked Important; Size has the Z upper case because
149 it is a Z command; Keyword is an alpine keyword that has been set by the user;
150 and Rule is an alpine rule */
151 {'a', 'a', "A", N_("select All")},
152 {'c', 'c', "C", N_("select Cur")},
153 {'n', 'n', "N", N_("Number")},
154 {'d', 'd', "D", N_("Date")},
155 {'t', 't', "T", N_("Text")},
156 {'s', 's', "S", N_("Status")},
157 {'z', 'z', "Z", N_("siZe")},
158 {'k', 'k', "K", N_("Keyword")},
159 {'r', 'r', "R", N_("Rule")},
160 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
161 {-1, 0, NULL, NULL}
165 static ESCKEY_S sel_opts3[] = {
166 /* TRANSLATORS: these are operations we can do on a set of selected messages.
167 Del is Delete; Undel is Undelete; TakeAddr means to Take some Addresses into
168 the address book; Save means to save the messages into another alpine folder;
169 Export means to copy the messages to a file outside of alpine, external to
170 alpine's world. */
171 {'d', 'd', "D", N_("Del")},
172 {'u', 'u', "U", N_("Undel")},
173 {'r', 'r', "R", N_("Reply")},
174 {'f', 'f', "F", N_("Forward")},
175 {'%', '%', "%", N_("Print")},
176 {'t', 't', "T", N_("TakeAddr")},
177 {'s', 's', "S", N_("Save")},
178 {'e', 'e', "E", N_("Export")},
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},
186 {-1, 0, NULL, NULL}
189 static ESCKEY_S sel_opts4[] = {
190 {'a', 'a', "A", N_("select All")},
191 /* TRANSLATORS: select currently highlighted message Thread */
192 {'c', 'c', "C", N_("select Curthrd")},
193 {'n', 'n', "N", N_("Number")},
194 {'d', 'd', "D", N_("Date")},
195 {'t', 't', "T", N_("Text")},
196 {'s', 's', "S", N_("Status")},
197 {'z', 'z', "Z", N_("siZe")},
198 {'k', 'k', "K", N_("Keyword")},
199 {'r', 'r', "R", N_("Rule")},
200 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
201 {-1, 0, NULL, NULL}
204 static ESCKEY_S sel_opts5[] = {
205 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
206 means select the currently highlighted message; select by Number is by message
207 number; Status is by status of the message, for example the message might be
208 New or it might be Unseen or marked Important; Size has the Z upper case because
209 it is a Z command; Keyword is an alpine keyword that has been set by the user;
210 and Rule is an alpine rule */
211 {'a', 'a', "A", N_("select All")},
212 {'c', 'c', "C", N_("select Cur")},
213 {'n', 'n', "N", N_("Number")},
214 {'d', 'd', "D", N_("Date")},
215 {'t', 't', "T", N_("Text")},
216 {'s', 's', "S", N_("Status")},
217 {'z', 'z', "Z", N_("siZe")},
218 {'k', 'k', "K", N_("Keyword")},
219 {'r', 'r', "R", N_("Rule")},
220 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
221 {SEL_OPTS_XGMEXT_CH, 'g', "G", N_("GmSearch")},
222 {-1, 0, NULL, NULL},
223 {-1, 0, NULL, NULL},
224 {-1, 0, NULL, NULL},
225 {-1, 0, NULL, NULL},
226 {-1, 0, NULL, NULL}
229 static ESCKEY_S sel_opts6[] = {
230 {'a', 'a', "A", N_("select All")},
231 /* TRANSLATORS: select currently highlighted message Thread */
232 {'c', 'c', "C", N_("select Curthrd")},
233 {'n', 'n', "N", N_("Number")},
234 {'d', 'd', "D", N_("Date")},
235 {'t', 't', "T", N_("Text")},
236 {'s', 's', "S", N_("Status")},
237 {'z', 'z', "Z", N_("siZe")},
238 {'k', 'k', "K", N_("Keyword")},
239 {'r', 'r', "R", N_("Rule")},
240 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
241 {SEL_OPTS_XGMEXT_CH, 'g', "G", N_("Gmail")},
242 {-1, 0, NULL, NULL},
243 {-1, 0, NULL, NULL},
244 {-1, 0, NULL, NULL},
245 {-1, 0, NULL, NULL},
246 {-1, 0, NULL, NULL}
250 static char *sel_flag =
251 N_("Select New, Deleted, Answered, Forwarded, or Important messages ? ");
252 static char *sel_flag_not =
253 N_("Select NOT New, NOT Deleted, NOT Answered, NOT Forwarded or NOT Important msgs ? ");
254 static ESCKEY_S sel_flag_opt[] = {
255 /* TRANSLATORS: When selecting messages by message Status these are the
256 different types of Status you can select on. Is the message New, Recent,
257 and so on. Not means flip the meaning of the selection to the opposite
258 thing, so message is not New or not Important. */
259 {'n', 'n', "N", N_("New")},
260 {'*', '*', "*", N_("Important")},
261 {'d', 'd', "D", N_("Deleted")},
262 {'a', 'a', "A", N_("Answered")},
263 {'f', 'f', "F", N_("Forwarded")},
264 {-2, 0, NULL, NULL},
265 {'!', '!', "!", N_("Not")},
266 {-2, 0, NULL, NULL},
267 {'r', 'r', "R", N_("Recent")},
268 {'u', 'u', "U", N_("Unseen")},
269 {-1, 0, NULL, NULL}
273 static ESCKEY_S sel_date_opt[] = {
274 {0, 0, NULL, NULL},
275 /* TRANSLATORS: options when selecting messages by Date */
276 {ctrl('P'), 12, "^P", N_("Prev Day")},
277 {ctrl('N'), 13, "^N", N_("Next Day")},
278 {ctrl('X'), 11, "^X", N_("Cur Msg")},
279 {ctrl('W'), 14, "^W", N_("Toggle When")},
280 {KEY_UP, 12, "", ""},
281 {KEY_DOWN, 13, "", ""},
282 {-1, 0, NULL, NULL}
286 static char *sel_x_gm_ext =
287 N_("Search: ");
288 static char *sel_text =
289 N_("Select based on To, From, Cc, Recip, Partic, Subject fields or All msg text ? ");
290 static char *sel_text_not =
291 N_("Select based on NOT To, From, Cc, Recip, Partic, Subject or All msg text ? ");
292 static ESCKEY_S sel_text_opt[] = {
293 /* TRANSLATORS: Select messages based on the text contained in the From line, or
294 the Subject line, and so on. */
295 {'f', 'f', "F", N_("From")},
296 {'s', 's', "S", N_("Subject")},
297 {'t', 't', "T", N_("To")},
298 {'a', 'a', "A", N_("All Text")},
299 {'c', 'c', "C", N_("Cc")},
300 {'!', '!', "!", N_("Not")},
301 {'r', 'r', "R", N_("Recipient")},
302 {'p', 'p', "P", N_("Participant")},
303 {'b', 'b', "B", N_("Body")},
304 {'h', 'h', "H", N_("Header")},
305 {-1, 0, NULL, NULL}
308 static ESCKEY_S choose_action[] = {
309 {'c', 'c', "C", N_("Compose")},
310 {'r', 'r', "R", N_("Reply")},
311 {'f', 'f', "F", N_("Forward")},
312 {'b', 'b', "B", N_("Bounce")},
313 {-1, 0, NULL, NULL}
316 static char *select_num =
317 N_("Enter comma-delimited list of numbers (dash between ranges): ");
319 static char *select_size_larger_msg =
320 N_("Select messages with size larger than: ");
322 static char *select_size_smaller_msg =
323 N_("Select messages with size smaller than: ");
325 static char *sel_size_larger = N_("Larger");
326 static char *sel_size_smaller = N_("Smaller");
327 static ESCKEY_S sel_size_opt[] = {
328 {0, 0, NULL, NULL},
329 {ctrl('W'), 14, "^W", NULL},
330 {-1, 0, NULL, NULL}
333 static ESCKEY_S sel_rule_opt[] = {
334 {0, 0, NULL, NULL},
335 {ctrl('T'), 14, "^T", N_("To List")},
336 {0, 0, NULL, NULL}, /* Reserved for TAB completion */
337 {'!', '!', "!", N_("Not")},
338 {-1, 0, NULL, NULL}
341 static ESCKEY_S sel_key_opt[] = {
342 {0, 0, NULL, NULL},
343 {ctrl('T'), 14, "^T", N_("To List")},
344 {0, 0, NULL, NULL}, /* Reserved for TAB completion */
345 {'!', '!', "!", N_("Not")},
346 {-1, 0, NULL, NULL}
349 static ESCKEY_S flag_text_opt[] = {
350 /* TRANSLATORS: these are types of flags (markers) that the user can
351 set. For example, they can flag the message as an important message. */
352 {'n', 'n', "N", N_("New")},
353 {'*', '*', "*", N_("Important")},
354 {'d', 'd', "D", N_("Deleted")},
355 {'a', 'a', "A", N_("Answered")},
356 {'f', 'f', "F", N_("Forwarded")},
357 {'!', '!', "!", N_("Not")},
358 {ctrl('T'), 10, "^T", N_("To Flag Details")},
359 {-1, 0, NULL, NULL}
363 alpine_smime_confirm_save(char *email)
365 char prompt[128];
367 snprintf(prompt, sizeof(prompt), _("Save certificate for <%s>"),
368 email ? email : _("missing address"));
369 return want_to(prompt, 'n', 'x', NO_HELP, WT_NORM) == 'y';
372 int
373 alpine_get_password(char *prompt, char *pass, size_t len)
375 int flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD;
376 int rv;
377 flags |= OE_DISALLOW_HELP;
378 pass[0] = '\0';
379 rv = optionally_enter(pass,
380 -(ps_global->ttyo ? FOOTER_ROWS(ps_global) : 3),
381 0, len, prompt, NULL, NO_HELP, &flags);
382 if(rv == 1) ps_global->user_says_cancel = 1;
383 return rv;
387 smime_import_certificate(char *filename, char *full_filename, char *what, size_t len)
389 int r = 1;
390 static HISTORY_S *history = NULL;
391 static ESCKEY_S eopts[] = {
392 {ctrl('T'), 10, "^T", N_("To Files")},
393 {-1, 0, NULL, NULL},
394 {-1, 0, NULL, NULL}};
396 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
397 eopts[r].ch = ctrl('I');
398 eopts[r].rval = 11;
399 eopts[r].name = "TAB";
400 eopts[r].label = N_("Complete");
403 eopts[++r].ch = -1;
405 filename[0] = '\0';
406 full_filename[0] = '\0';
408 r = get_export_filename(ps_global, filename, NULL, full_filename,
409 len, what, "IMPORT", eopts, NULL,
410 -FOOTER_ROWS(ps_global), GE_IS_IMPORT, &history);
412 return r;
416 /*----------------------------------------------------------------------
417 The giant switch on the commands for index and viewing
419 Input: command -- The command char/code
420 in_index -- flag indicating command is from index
421 orig_command -- The original command typed before pre-processing
422 Output: force_mailchk -- Set to tell caller to force call to new_mail().
424 Result: Manifold
426 Returns 1 if the message number or attachment to show changed
427 ---*/
429 process_cmd(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
430 int command, CmdWhere in_index, int *force_mailchk)
432 int question_line, a_changed, flags = 0, ret, j;
433 int notrealinbox;
434 long new_msgno, del_count, old_msgno, i;
435 long start;
436 char *newfolder, prompt[MAX_SCREEN_COLS+1];
437 CONTEXT_S *tc;
438 MESSAGECACHE *mc;
439 #if defined(DOS) && !defined(_WINDOWS)
440 extern long coreleft();
441 #endif
443 dprint((4, "\n - process_cmd(cmd=%d) -\n", command));
445 question_line = -FOOTER_ROWS(state);
446 state->mangled_screen = 0;
447 state->mangled_footer = 0;
448 state->mangled_header = 0;
449 state->next_screen = SCREEN_FUN_NULL;
450 old_msgno = mn_get_cur(msgmap);
451 a_changed = FALSE;
452 *force_mailchk = 0;
454 switch (command) {
455 /*------------- Help --------*/
456 case MC_HELP :
458 * We're not using the h_mail_view portion of this right now because
459 * that call is being handled in scrolltool() before it gets
460 * here. Leave it in case we change how it works.
462 helper((in_index == MsgIndx)
463 ? h_mail_index
464 : (in_index == View)
465 ? h_mail_view
466 : h_mail_thread_index,
467 (in_index == MsgIndx)
468 ? _("HELP FOR MESSAGE INDEX")
469 : (in_index == View)
470 ? _("HELP FOR MESSAGE TEXT")
471 : _("HELP FOR THREAD INDEX"),
472 HLPD_NONE);
473 dprint((4,"MAIL_CMD: did help command\n"));
474 state->mangled_screen = 1;
475 break;
478 /*--------- Return to main menu ------------*/
479 case MC_MAIN :
480 state->next_screen = main_menu_screen;
481 dprint((2,"MAIL_CMD: going back to main menu\n"));
482 break;
485 /*------- View message text --------*/
486 case MC_VIEW_TEXT :
487 view_text:
488 if(any_messages(msgmap, NULL, "to View")){
489 state->next_screen = mail_view_screen;
492 break;
495 /*------- View attachment --------*/
496 case MC_VIEW_ATCH :
497 state->next_screen = attachment_screen;
498 dprint((2,"MAIL_CMD: going to attachment screen\n"));
499 break;
502 /*---------- Previous message ----------*/
503 case MC_PREVITEM :
504 if(any_messages(msgmap, NULL, NULL)){
505 if((i = mn_get_cur(msgmap)) > 1L){
506 mn_dec_cur(stream, msgmap,
507 (in_index == View && THREADING()
508 && sp_viewing_a_thread(stream))
509 ? MH_THISTHD
510 : (in_index == View)
511 ? MH_ANYTHD : MH_NONE);
512 if(i == mn_get_cur(msgmap)){
513 PINETHRD_S *thrd = NULL, *topthrd = NULL;
515 if(THRD_INDX_ENABLED()){
516 mn_dec_cur(stream, msgmap, MH_ANYTHD);
517 if(i == mn_get_cur(msgmap))
518 q_status_message1(SM_ORDER, 0, 2,
519 _("Already on first %s in Zoomed Index"),
520 THRD_INDX() ? _("thread") : _("message"));
521 else{
522 if(in_index == View
523 || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
524 ret = 'y';
525 else
526 ret = want_to(_("View previous thread"), 'y', 'x',
527 NO_HELP, WT_NORM);
529 if(ret == 'y'){
530 q_status_message(SM_ORDER, 0, 2,
531 _("Viewing previous thread"));
532 new_msgno = mn_get_cur(msgmap);
533 mn_set_cur(msgmap, i);
534 if(unview_thread(state, stream, msgmap)){
535 state->next_screen = mail_index_screen;
536 state->view_skipped_index = 0;
537 state->mangled_screen = 1;
540 mn_set_cur(msgmap, new_msgno);
541 if(THRD_AUTO_VIEW() && in_index == View){
543 thrd = fetch_thread(stream,
544 mn_m2raw(msgmap,
545 new_msgno));
546 if(count_lflags_in_thread(stream, thrd,
547 msgmap,
548 MN_NONE) == 1){
549 if(view_thread(state, stream, msgmap, 1)){
550 if(current_index_state)
551 msgmap->top_after_thrd = current_index_state->msg_at_top;
553 state->view_skipped_index = 1;
554 command = MC_VIEW_TEXT;
555 goto view_text;
560 j = 0;
561 if(THRD_AUTO_VIEW() && in_index != View){
562 thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
563 if(thrd && thrd->top)
564 topthrd = fetch_thread(stream, thrd->top);
566 if(topthrd)
567 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
570 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
571 if(view_thread(state, stream, msgmap, 1)
572 && current_index_state)
573 msgmap->top_after_thrd = current_index_state->msg_at_top;
577 state->next_screen = SCREEN_FUN_NULL;
579 else
580 mn_set_cur(msgmap, i); /* put it back */
583 else
584 q_status_message1(SM_ORDER, 0, 2,
585 _("Already on first %s in Zoomed Index"),
586 THRD_INDX() ? _("thread") : _("message"));
589 else{
590 time_t now;
592 if(!IS_NEWS(stream)
593 && ((now = time(0)) > state->last_nextitem_forcechk)){
594 *force_mailchk = 1;
595 /* check at most once a second */
596 state->last_nextitem_forcechk = now;
599 q_status_message1(SM_ORDER, 0, 1, _("Already on first %s"),
600 THRD_INDX() ? _("thread") : _("message"));
604 break;
607 /*---------- Next Message ----------*/
608 case MC_NEXTITEM :
609 if(mn_get_total(msgmap) > 0L
610 && ((i = mn_get_cur(msgmap)) < mn_get_total(msgmap))){
611 mn_inc_cur(stream, msgmap,
612 (in_index == View && THREADING()
613 && sp_viewing_a_thread(stream))
614 ? MH_THISTHD
615 : (in_index == View)
616 ? MH_ANYTHD : MH_NONE);
617 if(i == mn_get_cur(msgmap)){
618 PINETHRD_S *thrd, *topthrd;
620 if(THRD_INDX_ENABLED()){
621 if(!THRD_INDX())
622 mn_inc_cur(stream, msgmap, MH_ANYTHD);
624 if(i == mn_get_cur(msgmap)){
625 if(any_lflagged(msgmap, MN_HIDE))
626 any_messages(NULL, "more", "in Zoomed Index");
627 else
628 goto nfolder;
630 else{
631 if(in_index == View
632 || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
633 ret = 'y';
634 else
635 ret = want_to(_("View next thread"), 'y', 'x',
636 NO_HELP, WT_NORM);
638 if(ret == 'y'){
639 q_status_message(SM_ORDER, 0, 2,
640 _("Viewing next thread"));
641 new_msgno = mn_get_cur(msgmap);
642 mn_set_cur(msgmap, i);
643 if(unview_thread(state, stream, msgmap)){
644 state->next_screen = mail_index_screen;
645 state->view_skipped_index = 0;
646 state->mangled_screen = 1;
649 mn_set_cur(msgmap, new_msgno);
650 if(THRD_AUTO_VIEW() && in_index == View){
652 thrd = fetch_thread(stream,
653 mn_m2raw(msgmap,
654 new_msgno));
655 if(count_lflags_in_thread(stream, thrd,
656 msgmap,
657 MN_NONE) == 1){
658 if(view_thread(state, stream, msgmap, 1)){
659 if(current_index_state)
660 msgmap->top_after_thrd = current_index_state->msg_at_top;
662 state->view_skipped_index = 1;
663 command = MC_VIEW_TEXT;
664 goto view_text;
669 j = 0;
670 if(THRD_AUTO_VIEW() && in_index != View){
671 thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
672 if(thrd && thrd->top)
673 topthrd = fetch_thread(stream, thrd->top);
674 else
675 topthrd = NULL;
677 if(topthrd)
678 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
681 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
682 if(view_thread(state, stream, msgmap, 1)
683 && current_index_state)
684 msgmap->top_after_thrd = current_index_state->msg_at_top;
688 state->next_screen = SCREEN_FUN_NULL;
690 else
691 mn_set_cur(msgmap, i); /* put it back */
694 else if(THREADING()
695 && (thrd = fetch_thread(stream, mn_m2raw(msgmap, i)))
696 && thrd->next
697 && get_lflag(stream, NULL, thrd->rawno, MN_COLL)){
698 q_status_message(SM_ORDER, 0, 2,
699 _("Expand collapsed thread to see more messages"));
701 else
702 any_messages(NULL, "more", "in Zoomed Index");
705 else{
706 time_t now;
707 nfolder:
708 prompt[0] = '\0';
709 if(IS_NEWS(stream)
710 || (state->context_current->use & CNTXT_INCMNG)){
711 char nextfolder[MAXPATH];
713 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
714 nextfolder[sizeof(nextfolder)-1] = '\0';
715 if(next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
716 state->context_current, NULL, NULL))
717 strncpy(prompt, _(". Press TAB for next folder."),
718 sizeof(prompt));
719 else
720 strncpy(prompt, _(". No more folders to TAB to."),
721 sizeof(prompt));
723 prompt[sizeof(prompt)-1] = '\0';
726 any_messages(NULL, (mn_get_total(msgmap) > 0L) ? "more" : NULL,
727 prompt[0] ? prompt : NULL);
729 if(!IS_NEWS(stream)
730 && ((now = time(0)) > state->last_nextitem_forcechk)){
731 *force_mailchk = 1;
732 /* check at most once a second */
733 state->last_nextitem_forcechk = now;
737 break;
740 /*---------- Delete message ----------*/
741 case MC_DELETE :
742 (void) cmd_delete(state, msgmap, MCMD_NONE,
743 (in_index == View) ? cmd_delete_view : cmd_delete_index);
744 break;
747 /*---------- Undelete message ----------*/
748 case MC_UNDELETE :
749 (void) cmd_undelete(state, msgmap, MCMD_NONE);
750 update_titlebar_status();
751 break;
754 /*---------- Reply to message ----------*/
755 case MC_REPLY :
756 (void) cmd_reply(state, msgmap, MCMD_NONE, NULL);
757 break;
760 /*---------- Forward message ----------*/
761 case MC_FORWARD :
762 (void) cmd_forward(state, msgmap, MCMD_NONE, NULL);
763 break;
766 /*---------- Quit pine ------------*/
767 case MC_QUIT :
768 state->next_screen = quit_screen;
769 dprint((1,"MAIL_CMD: quit\n"));
770 break;
773 /*---------- Compose message ----------*/
774 case MC_COMPOSE :
775 state->prev_screen = (in_index == View) ? mail_view_screen
776 : mail_index_screen;
777 compose_screen(state);
778 state->mangled_screen = 1;
779 if (state->next_screen)
780 a_changed = TRUE;
781 break;
784 /*---------- Alt Compose message ----------*/
785 case MC_ROLE :
786 state->prev_screen = (in_index == View) ? mail_view_screen
787 : mail_index_screen;
788 role_compose(state);
789 if(state->next_screen)
790 a_changed = TRUE;
792 break;
795 /*--------- Folders menu ------------*/
796 case MC_FOLDERS :
797 state->start_in_context = 1;
799 /*--------- Top of Folders list menu ------------*/
800 case MC_COLLECTIONS :
801 state->next_screen = folder_screen;
802 dprint((2,"MAIL_CMD: going to folder/collection menu\n"));
803 break;
806 /*---------- Open specific new folder ----------*/
807 case MC_GOTO :
808 tc = (state->context_last && !NEWS_TEST(state->context_current))
809 ? state->context_last : state->context_current;
811 newfolder = broach_folder(question_line, 1, &notrealinbox, &tc);
812 if(newfolder){
813 visit_folder(state, newfolder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT);
814 a_changed = TRUE;
817 break;
820 /*------- Go to Index Screen ----------*/
821 case MC_INDEX :
822 state->next_screen = mail_index_screen;
823 break;
825 /*------- Skip to next interesting message -----------*/
826 case MC_TAB :
827 if(THRD_INDX()){
828 PINETHRD_S *thrd;
831 * If we're in the thread index, start looking after this
832 * thread. We don't want to match something in the current
833 * thread.
835 start = mn_get_cur(msgmap);
836 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
837 if(mn_get_revsort(msgmap)){
838 /* if reversed, top of thread is last one before next thread */
839 if(thrd && thrd->top)
840 start = mn_raw2m(msgmap, thrd->top);
842 else{
843 /* last msg of thread is at the ends of the branches/nexts */
844 while(thrd){
845 start = mn_raw2m(msgmap, thrd->rawno);
846 if(thrd->branch)
847 thrd = fetch_thread(stream, thrd->branch);
848 else if(thrd->next)
849 thrd = fetch_thread(stream, thrd->next);
850 else
851 thrd = NULL;
856 * Flags is 0 in this case because we want to not skip
857 * messages inside of threads so that we can find threads
858 * which have some unseen messages even though the top-level
859 * of the thread is already seen.
860 * If new_msgno ends up being a message which is not visible
861 * because it isn't at the top-level, the current message #
862 * will be adjusted below in adjust_cur.
864 flags = 0;
865 new_msgno = next_sorted_flagged((F_UNDEL
866 | F_UNSEEN
867 | ((F_ON(F_TAB_TO_NEW,state))
868 ? 0 : F_OR_FLAG)),
869 stream, start, &flags);
871 else if(THREADING() && sp_viewing_a_thread(stream)){
872 PINETHRD_S *thrd, *topthrd = NULL;
874 start = mn_get_cur(msgmap);
877 * Things are especially complicated when we're viewing_a_thread
878 * from the thread index. First we have to check within the
879 * current thread for a new message. If none is found, then
880 * we search in the next threads and offer to continue in
881 * them. Then we offer to go to the next folder.
883 flags = NSF_SKIP_CHID;
884 new_msgno = next_sorted_flagged((F_UNDEL
885 | F_UNSEEN
886 | ((F_ON(F_TAB_TO_NEW,state))
887 ? 0 : F_OR_FLAG)),
888 stream, start, &flags);
890 * If we found a match then we are done, that is another message
891 * in the current thread index. Otherwise, we have to look
892 * further.
894 if(!(flags & NSF_FLAG_MATCH)){
895 ret = 'n';
896 while(1){
898 flags = 0;
899 new_msgno = next_sorted_flagged((F_UNDEL
900 | F_UNSEEN
901 | ((F_ON(F_TAB_TO_NEW,
902 state))
903 ? 0 : F_OR_FLAG)),
904 stream, start, &flags);
906 * If we got a match, new_msgno is a message in
907 * a different thread from the one we are viewing.
909 if(flags & NSF_FLAG_MATCH){
910 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
911 if(thrd && thrd->top)
912 topthrd = fetch_thread(stream, thrd->top);
914 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
915 static ESCKEY_S next_opt[] = {
916 {'y', 'y', "Y", N_("Yes")},
917 {'n', 'n', "N", N_("No")},
918 {TAB, 'n', "Tab", N_("NextNew")},
919 {-1, 0, NULL, NULL}
922 if(in_index)
923 snprintf(prompt, sizeof(prompt), _("View thread number %s? "),
924 topthrd ? comatose(topthrd->thrdno) : "?");
925 else
926 snprintf(prompt, sizeof(prompt),
927 _("View message in thread number %s? "),
928 topthrd ? comatose(topthrd->thrdno) : "?");
930 prompt[sizeof(prompt)-1] = '\0';
932 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
933 next_opt, 'y', 'x', NO_HELP,
934 RB_NORM);
935 if(ret == 'x'){
936 cmd_cancelled(NULL);
937 goto get_out;
940 else
941 ret = 'y';
943 if(ret == 'y'){
944 if(unview_thread(state, stream, msgmap)){
945 state->next_screen = mail_index_screen;
946 state->view_skipped_index = 0;
947 state->mangled_screen = 1;
950 mn_set_cur(msgmap, new_msgno);
951 if(THRD_AUTO_VIEW()){
953 if(count_lflags_in_thread(stream, topthrd,
954 msgmap, MN_NONE) == 1){
955 if(view_thread(state, stream, msgmap, 1)){
956 if(current_index_state)
957 msgmap->top_after_thrd = current_index_state->msg_at_top;
959 state->view_skipped_index = 1;
960 command = MC_VIEW_TEXT;
961 goto view_text;
966 if(view_thread(state, stream, msgmap, 1) && current_index_state)
967 msgmap->top_after_thrd = current_index_state->msg_at_top;
969 state->next_screen = SCREEN_FUN_NULL;
970 break;
972 else if(ret == 'n' && topthrd){
974 * skip to end of this thread and look starting
975 * in the next thread.
977 if(mn_get_revsort(msgmap)){
979 * if reversed, top of thread is last one
980 * before next thread
982 start = mn_raw2m(msgmap, topthrd->rawno);
984 else{
986 * last msg of thread is at the ends of
987 * the branches/nexts
989 thrd = topthrd;
990 while(thrd){
991 start = mn_raw2m(msgmap, thrd->rawno);
992 if(thrd->branch)
993 thrd = fetch_thread(stream, thrd->branch);
994 else if(thrd->next)
995 thrd = fetch_thread(stream, thrd->next);
996 else
997 thrd = NULL;
1001 else if(ret == 'n')
1002 break;
1004 else
1005 break;
1009 else{
1011 start = mn_get_cur(msgmap);
1014 * If we are on a collapsed thread, start looking after the
1015 * collapsed part, unless we are viewing the message.
1017 if(THREADING() && in_index != View){
1018 PINETHRD_S *thrd;
1019 long rawno;
1020 int collapsed;
1022 rawno = mn_m2raw(msgmap, start);
1023 thrd = fetch_thread(stream, rawno);
1024 collapsed = thrd && thrd->next
1025 && get_lflag(stream, NULL, rawno, MN_COLL);
1027 if(collapsed){
1028 if(mn_get_revsort(msgmap)){
1029 if(thrd && thrd->top)
1030 start = mn_raw2m(msgmap, thrd->top);
1032 else{
1033 while(thrd){
1034 start = mn_raw2m(msgmap, thrd->rawno);
1035 if(thrd->branch)
1036 thrd = fetch_thread(stream, thrd->branch);
1037 else if(thrd->next)
1038 thrd = fetch_thread(stream, thrd->next);
1039 else
1040 thrd = NULL;
1047 new_msgno = next_sorted_flagged((F_UNDEL
1048 | F_UNSEEN
1049 | ((F_ON(F_TAB_TO_NEW,state))
1050 ? 0 : F_OR_FLAG)),
1051 stream, start, &flags);
1055 * If there weren't any unread messages left, OR there
1056 * aren't any messages at all, we may want to offer to
1057 * go on to the next folder...
1059 if(flags & NSF_FLAG_MATCH){
1060 mn_set_cur(msgmap, new_msgno);
1061 if(in_index != View)
1062 adjust_cur_to_visible(stream, msgmap);
1064 else{
1065 int in_inbox = sp_flagged(stream, SP_INBOX);
1067 if(state->context_current
1068 && ((NEWS_TEST(state->context_current)
1069 && context_isambig(state->cur_folder))
1070 || ((state->context_current->use & CNTXT_INCMNG)
1071 && (in_inbox
1072 || folder_index(state->cur_folder,
1073 state->context_current,
1074 FI_FOLDER) >= 0)))){
1075 char nextfolder[MAXPATH];
1076 MAILSTREAM *nextstream = NULL;
1077 long recent_cnt;
1078 int did_cancel = 0;
1080 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
1081 nextfolder[sizeof(nextfolder)-1] = '\0';
1082 while(1){
1083 if(!(next_folder(&nextstream, nextfolder, sizeof(nextfolder), nextfolder,
1084 state->context_current, &recent_cnt,
1085 F_ON(F_TAB_NO_CONFIRM,state)
1086 ? NULL : &did_cancel))){
1087 if(!in_inbox){
1088 static ESCKEY_S inbox_opt[] = {
1089 {'y', 'y', "Y", N_("Yes")},
1090 {'n', 'n', "N", N_("No")},
1091 {TAB, 'z', "Tab", N_("To Inbox")},
1092 {-1, 0, NULL, NULL}
1095 if(F_ON(F_RET_INBOX_NO_CONFIRM,state))
1096 ret = 'y';
1097 else{
1098 /* TRANSLATORS: this is a question, with some information followed
1099 by Return to INBOX? */
1100 if(state->context_current->use&CNTXT_INCMNG)
1101 snprintf(prompt, sizeof(prompt), _("No more incoming folders. Return to \"%s\"? "), state->inbox_name);
1102 else
1103 snprintf(prompt, sizeof(prompt), _("No more news groups. Return to \"%s\"? "), state->inbox_name);
1105 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1106 inbox_opt, 'y', 'x',
1107 NO_HELP, RB_NORM);
1111 * 'z' is a synonym for 'y'. It is not 'y'
1112 * so that it isn't displayed as a default
1113 * action with square-brackets around it
1114 * in the keymenu...
1116 if(ret == 'y' || ret == 'z'){
1117 visit_folder(state, state->inbox_name,
1118 state->context_current,
1119 NULL, DB_INBOXWOCNTXT);
1120 a_changed = TRUE;
1123 else if (did_cancel)
1124 cmd_cancelled(NULL);
1125 else{
1126 if(state->context_current->use&CNTXT_INCMNG)
1127 q_status_message(SM_ORDER, 0, 2, _("No more incoming folders"));
1128 else
1129 q_status_message(SM_ORDER, 0, 2, _("No more news groups"));
1132 break;
1136 #define CNTLEN 80
1137 char *front, type[80], cnt[CNTLEN], fbuf[MAX_SCREEN_COLS/2+1];
1138 int rbspace, avail, need, take_back;
1141 * View_next_
1142 * Incoming_folder_ or news_group_ or folder_ or group_
1143 * "foldername"
1144 * _(13 recent) or _(some recent) or nothing
1145 * ?_
1147 front = "View next";
1148 strncpy(type,
1149 (state->context_current->use & CNTXT_INCMNG)
1150 ? "Incoming folder" : "news group",
1151 sizeof(type));
1152 type[sizeof(type)-1] = '\0';
1153 snprintf(cnt, sizeof(cnt), " (%.*s %s)", CNTLEN-20,
1154 recent_cnt ? long2string(recent_cnt) : "some",
1155 F_ON(F_TAB_USES_UNSEEN, ps_global)
1156 ? "unseen" : "recent");
1157 cnt[sizeof(cnt)-1] = '\0';
1160 * Space reserved for radio_buttons call.
1161 * If we make this 3 then radio_buttons won't mess
1162 * with the prompt. If we make it 2, then we get
1163 * one more character to use but radio_buttons will
1164 * cut off the last character of our prompt, which is
1165 * ok because it is a space.
1167 rbspace = 2;
1168 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols
1169 : 80;
1170 need = strlen(front)+1 + strlen(type)+1 +
1171 + strlen(nextfolder)+2 + strlen(cnt) +
1172 2 + rbspace;
1173 if(avail < need){
1174 take_back = strlen(type);
1175 strncpy(type,
1176 (state->context_current->use & CNTXT_INCMNG)
1177 ? "folder" : "group", sizeof(type));
1178 take_back -= strlen(type);
1179 need -= take_back;
1180 if(avail < need){
1181 need -= strlen(cnt);
1182 cnt[0] = '\0';
1185 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1186 snprintf(prompt, sizeof(prompt), "%.*s %.*s \"%.*s\"%.*s? ",
1187 (MAX_SCREEN_COLS+1)/8, front,
1188 (MAX_SCREEN_COLS+1)/8, type,
1189 (MAX_SCREEN_COLS+1)/2,
1190 short_str(nextfolder, fbuf, sizeof(fbuf),
1191 strlen(nextfolder) -
1192 ((need>avail) ? (need-avail) : 0),
1193 MidDots),
1194 (MAX_SCREEN_COLS+1)/8, cnt);
1195 prompt[sizeof(prompt)-1] = '\0';
1199 * When help gets added, this'll have to become
1200 * a loop like the rest...
1202 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
1203 static ESCKEY_S next_opt[] = {
1204 {'y', 'y', "Y", N_("Yes")},
1205 {'n', 'n', "N", N_("No")},
1206 {TAB, 'n', "Tab", N_("NextNew")},
1207 {-1, 0, NULL, NULL}
1210 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1211 next_opt, 'y', 'x', NO_HELP,
1212 RB_NORM);
1213 if(ret == 'x'){
1214 cmd_cancelled(NULL);
1215 break;
1218 else
1219 ret = 'y';
1221 if(ret == 'y'){
1222 if(nextstream && sp_dead_stream(nextstream))
1223 nextstream = NULL;
1225 visit_folder(state, nextfolder,
1226 state->context_current, nextstream,
1227 DB_FROMTAB);
1228 /* visit_folder takes care of nextstream */
1229 nextstream = NULL;
1230 a_changed = TRUE;
1231 break;
1235 if(nextstream)
1236 pine_mail_close(nextstream);
1238 else
1239 any_messages(NULL,
1240 (mn_get_total(msgmap) > 0L)
1241 ? IS_NEWS(stream) ? "more undeleted" : "more new"
1242 : NULL,
1243 NULL);
1246 get_out:
1248 break;
1251 /*------- Zoom -----------*/
1252 case MC_ZOOM :
1254 * Right now the way zoom is implemented is sort of silly.
1255 * There are two per-message flags where just one and a
1256 * global "zoom mode" flag to suppress messages from the index
1257 * should suffice.
1259 if(any_messages(msgmap, NULL, "to Zoom on")){
1260 if(unzoom_index(state, stream, msgmap)){
1261 dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
1262 q_status_message(SM_ORDER,0,2, _("Index Zoom Mode is now off"));
1264 else if((i = zoom_index(state, stream, msgmap, MN_SLCT)) != 0){
1265 if(any_lflagged(msgmap, MN_HIDE)){
1266 dprint((4,"\n\n ---- Entering ZOOM mode ----\n"));
1267 q_status_message4(SM_ORDER, 0, 2,
1268 _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"),
1269 THRD_INDX() ? "" : comatose(i),
1270 THRD_INDX() ? "" : " ",
1271 THRD_INDX() ? _("threads") : _("message"),
1272 THRD_INDX() ? "" : plural(i));
1274 else
1275 q_status_message(SM_ORDER, 0, 2,
1276 _("All messages selected, so not entering Index Zoom Mode"));
1278 else
1279 any_messages(NULL, "selected", "to Zoom on");
1282 break;
1285 /*---------- print message on paper ----------*/
1286 case MC_PRINTMSG :
1287 if(any_messages(msgmap, NULL, "to print"))
1288 (void) cmd_print(state, msgmap, MCMD_NONE, in_index);
1290 break;
1293 /*---------- Take Address ----------*/
1294 case MC_TAKE :
1295 if(F_ON(F_ENABLE_ROLE_TAKE, state) ||
1296 any_messages(msgmap, NULL, "to Take address from"))
1297 (void) cmd_take_addr(state, msgmap, MCMD_NONE);
1299 break;
1302 /*---------- Save Message ----------*/
1303 case MC_SAVE :
1304 if(any_messages(msgmap, NULL, "to Save"))
1305 (void) cmd_save(state, stream, msgmap, MCMD_NONE, in_index);
1307 break;
1310 /*---------- Export message ----------*/
1311 case MC_EXPORT :
1312 if(any_messages(msgmap, NULL, "to Export")){
1313 (void) cmd_export(state, msgmap, question_line, MCMD_NONE);
1314 state->mangled_footer = 1;
1317 break;
1320 /*---------- Expunge ----------*/
1321 case MC_EXPUNGE :
1322 (void) cmd_expunge(state, stream, msgmap, MCMD_NONE);
1323 break;
1326 /*------- Unexclude -----------*/
1327 case MC_UNEXCLUDE :
1328 if(!(IS_NEWS(stream) && stream->rdonly)){
1329 q_status_message(SM_ORDER, 0, 3,
1330 _("Unexclude not available for mail folders"));
1332 else if(any_lflagged(msgmap, MN_EXLD)){
1333 SEARCHPGM *pgm;
1334 long i;
1335 int exbits;
1338 * Since excluded means "hidden deleted" and "killed",
1339 * the count should reflect the former.
1341 pgm = mail_newsearchpgm();
1342 pgm->deleted = 1;
1343 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
1344 for(i = 1L, del_count = 0L; i <= stream->nmsgs; i++)
1345 if((mc = mail_elt(stream, i)) && mc->searched
1346 && get_lflag(stream, NULL, i, MN_EXLD)
1347 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
1348 && (exbits & MSG_EX_FILTERED)))
1349 del_count++;
1351 if(del_count > 0L){
1352 state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1353 snprintf(prompt, sizeof(prompt), "UNexclude %ld message%s in %.*s", del_count,
1354 plural(del_count), MAX_SCREEN_COLS+1-45,
1355 pretty_fn(state->cur_folder));
1356 prompt[sizeof(prompt)-1] = '\0';
1357 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
1358 || (F_ON(F_AUTO_EXPUNGE, state)
1359 && (state->context_current
1360 && (state->context_current->use & CNTXT_INCMNG))
1361 && context_isambig(state->cur_folder))
1362 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
1363 long save_cur_rawno;
1364 int were_viewing_a_thread;
1366 save_cur_rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
1367 were_viewing_a_thread = (THREADING()
1368 && sp_viewing_a_thread(stream));
1370 if(msgno_include(stream, msgmap, MI_NONE)){
1371 clear_index_cache(stream, 0);
1373 if(stream && stream->spare)
1374 erase_threading_info(stream, msgmap);
1376 refresh_sort(stream, msgmap, SRT_NON);
1379 if(were_viewing_a_thread){
1380 if(save_cur_rawno > 0L)
1381 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1383 if(view_thread(state, stream, msgmap, 1) && current_index_state)
1384 msgmap->top_after_thrd = current_index_state->msg_at_top;
1387 if(save_cur_rawno > 0L)
1388 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1390 state->mangled_screen = 1;
1391 q_status_message2(SM_ORDER, 0, 4,
1392 "%s message%s UNexcluded",
1393 long2string(del_count),
1394 plural(del_count));
1396 if(in_index != View)
1397 adjust_cur_to_visible(stream, msgmap);
1399 else
1400 any_messages(NULL, NULL, "UNexcluded");
1402 else
1403 any_messages(NULL, "excluded", "to UNexclude");
1405 else
1406 any_messages(NULL, "excluded", "to UNexclude");
1408 break;
1411 /*------- Make Selection -----------*/
1412 case MC_SELECT :
1413 if(any_messages(msgmap, NULL, "to Select")){
1414 if(aggregate_select(state, msgmap, question_line, in_index) == 0
1415 && (in_index == MsgIndx || in_index == ThrdIndx)
1416 && F_ON(F_AUTO_ZOOM, state)
1417 && any_lflagged(msgmap, MN_SLCT) > 0L
1418 && !any_lflagged(msgmap, MN_HIDE))
1419 (void) zoom_index(state, stream, msgmap, MN_SLCT);
1422 break;
1425 /*------- Toggle Current Message Selection State -----------*/
1426 case MC_SELCUR :
1427 if(any_messages(msgmap, NULL, NULL)){
1428 if((select_by_current(state, msgmap, in_index)
1429 || (F_OFF(F_UNSELECT_WONT_ADVANCE, state)
1430 && !any_lflagged(msgmap, MN_HIDE)))
1431 && (i = mn_get_cur(msgmap)) < mn_get_total(msgmap)){
1432 /* advance current */
1433 mn_inc_cur(stream, msgmap,
1434 (in_index == View && THREADING()
1435 && sp_viewing_a_thread(stream))
1436 ? MH_THISTHD
1437 : (in_index == View)
1438 ? MH_ANYTHD : MH_NONE);
1442 break;
1445 /*------- Apply command -----------*/
1446 case MC_APPLY :
1447 if(any_messages(msgmap, NULL, NULL)){
1448 if(any_lflagged(msgmap, MN_SLCT) > 0L){
1449 if(apply_command(state, stream, msgmap, 0,
1450 AC_NONE, question_line)){
1451 if(F_ON(F_AUTO_UNSELECT, state)){
1452 agg_select_all(stream, msgmap, NULL, 0);
1453 unzoom_index(state, stream, msgmap);
1455 else if(F_ON(F_AUTO_UNZOOM, state))
1456 unzoom_index(state, stream, msgmap);
1459 else
1460 any_messages(NULL, NULL, "to Apply command to. Try \"Select\"");
1463 break;
1466 /*-------- Sort command -------*/
1467 case MC_SORT :
1469 int were_threading = THREADING();
1470 SortOrder sort = mn_get_sort(msgmap);
1471 int rev = mn_get_revsort(msgmap);
1473 dprint((1,"MAIL_CMD: sort\n"));
1474 if(select_sort(state, question_line, &sort, &rev)){
1475 /* $ command reinitializes threading collapsed/expanded info */
1476 if(SORT_IS_THREADED(msgmap) && !SEP_THRDINDX())
1477 erase_threading_info(stream, msgmap);
1479 if(ps_global && ps_global->ttyo){
1480 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
1481 ps_global->mangled_footer = 1;
1484 sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN);
1487 state->mangled_footer = 1;
1490 * We've changed whether we are threading or not so we need to
1491 * exit the index and come back in so that we switch between the
1492 * thread index and the regular index. Sort_folder will have
1493 * reset viewing_a_thread if necessary.
1495 if(SEP_THRDINDX()
1496 && ((!were_threading && THREADING())
1497 || (were_threading && !THREADING()))){
1498 state->next_screen = mail_index_screen;
1499 state->mangled_screen = 1;
1503 break;
1506 /*------- Toggle Full Headers -----------*/
1507 case MC_FULLHDR :
1508 state->full_header++;
1509 if(state->full_header == 1){
1510 if(!(state->quote_suppression_threshold
1511 && (state->some_quoting_was_suppressed || in_index != View)))
1512 state->full_header++;
1514 else if(state->full_header > 2)
1515 state->full_header = 0;
1517 switch(state->full_header){
1518 case 0:
1519 q_status_message(SM_ORDER, 0, 3,
1520 _("Display of full headers is now off."));
1521 break;
1523 case 1:
1524 q_status_message1(SM_ORDER, 0, 3,
1525 _("Quotes displayed, use %s to see full headers"),
1526 F_ON(F_USE_FK, state) ? "F9" : "H");
1527 break;
1529 case 2:
1530 q_status_message(SM_ORDER, 0, 3,
1531 _("Display of full headers is now on."));
1532 break;
1536 a_changed = TRUE;
1537 break;
1540 case MC_TOGGLE :
1541 a_changed = TRUE;
1542 break;
1545 #ifdef SMIME
1546 /*------- Try to decrypt message -----------*/
1547 case MC_DECRYPT:
1548 if(state->smime && state->smime->need_passphrase)
1549 smime_get_passphrase();
1551 a_changed = TRUE;
1552 break;
1554 case MC_SECURITY:
1555 smime_info_screen(state);
1556 break;
1557 #endif
1560 /*------- Bounce -----------*/
1561 case MC_BOUNCE :
1562 (void) cmd_bounce(state, msgmap, MCMD_NONE, NULL);
1563 break;
1566 /*------- Flag -----------*/
1567 case MC_FLAG :
1568 dprint((4, "\n - flag message -\n"));
1569 (void) cmd_flag(state, msgmap, MCMD_NONE);
1570 break;
1573 /*------- Pipe message -----------*/
1574 case MC_PIPE :
1575 (void) cmd_pipe(state, msgmap, MCMD_NONE);
1576 break;
1579 /*--------- Default, unknown command ----------*/
1580 default:
1581 alpine_panic("Unexpected command case");
1582 break;
1585 return((a_changed || mn_get_cur(msgmap) != old_msgno) ? 1 : 0);
1590 /*----------------------------------------------------------------------
1591 Map some of the special characters into sensible strings for human
1592 consumption.
1593 c is a UCS-4 character!
1594 ----*/
1595 char *
1596 pretty_command(UCS c)
1598 static char buf[10];
1599 char *s;
1601 buf[0] = '\0';
1602 s = buf;
1604 switch(c){
1605 case ' ' : s = "SPACE"; break;
1606 case '\033' : s = "ESC"; break;
1607 case '\177' : s = "DEL"; break;
1608 case ctrl('I') : s = "TAB"; break;
1609 case ctrl('J') : s = "LINEFEED"; break;
1610 case ctrl('M') : s = "RETURN"; break;
1611 case ctrl('Q') : s = "XON"; break;
1612 case ctrl('S') : s = "XOFF"; break;
1613 case KEY_UP : s = "Up Arrow"; break;
1614 case KEY_DOWN : s = "Down Arrow"; break;
1615 case KEY_RIGHT : s = "Right Arrow"; break;
1616 case KEY_LEFT : s = "Left Arrow"; break;
1617 case KEY_PGUP : s = "Prev Page"; break;
1618 case KEY_PGDN : s = "Next Page"; break;
1619 case KEY_HOME : s = "Home"; break;
1620 case KEY_END : s = "End"; break;
1621 case KEY_DEL : s = "Delete"; break; /* Not necessary DEL! */
1622 case KEY_JUNK : s = "Junk!"; break;
1623 case BADESC : s = "Bad Esc"; break;
1624 case NO_OP_IDLE : s = "NO_OP_IDLE"; break;
1625 case NO_OP_COMMAND : s = "NO_OP_COMMAND"; break;
1626 case KEY_RESIZE : s = "KEY_RESIZE"; break;
1627 case KEY_UTF8 : s = "KEY_UTF8"; break;
1628 case KEY_MOUSE : s = "KEY_MOUSE"; break;
1629 case KEY_SCRLUPL : s = "KEY_SCRLUPL"; break;
1630 case KEY_SCRLDNL : s = "KEY_SCRLDNL"; break;
1631 case KEY_SCRLTO : s = "KEY_SCRLTO"; break;
1632 case KEY_XTERM_MOUSE : s = "KEY_XTERM_MOUSE"; break;
1633 case KEY_DOUBLE_ESC : s = "KEY_DOUBLE_ESC"; break;
1634 case CTRL_KEY_UP : s = "Ctrl Up Arrow"; break;
1635 case CTRL_KEY_DOWN : s = "Ctrl Down Arrow"; break;
1636 case CTRL_KEY_RIGHT : s = "Ctrl Right Arrow"; break;
1637 case CTRL_KEY_LEFT : s = "Ctrl Left Arrow"; break;
1638 case PF1 :
1639 case PF2 :
1640 case PF3 :
1641 case PF4 :
1642 case PF5 :
1643 case PF6 :
1644 case PF7 :
1645 case PF8 :
1646 case PF9 :
1647 case PF10 :
1648 case PF11 :
1649 case PF12 :
1650 snprintf(s = buf, sizeof(buf), "F%ld", (long) (c - PF1 + 1));
1651 break;
1653 default:
1654 if(c < ' ' || (c >= 0x80 && c < 0xA0)){
1655 char d;
1656 int c1;
1658 c1 = (c >= 0x80);
1659 d = (c & 0x1f) + 'A' - 1;
1660 snprintf(s = buf, sizeof(buf), "%c%c", c1 ? '~' : '^', d);
1662 else{
1663 memset(buf, 0, sizeof(buf));
1664 utf8_put((unsigned char *) buf, (unsigned long) c);
1667 break;
1670 return(s);
1674 /*----------------------------------------------------------------------
1675 Complain about bogus input
1677 Args: ch -- input command to complain about
1678 help -- string indicating where to get help
1680 ----*/
1681 void
1682 bogus_command(UCS cmd, char *help)
1684 if(cmd == ctrl('Q') || cmd == ctrl('S'))
1685 q_status_message1(SM_ASYNC, 0, 2,
1686 "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.",
1687 pretty_command(cmd));
1688 else if(cmd == KEY_JUNK)
1689 q_status_message3(SM_ORDER, 0, 2,
1690 "Invalid key pressed.%s%s%s",
1691 (help) ? " Use " : "",
1692 (help) ? help : "",
1693 (help) ? " for help" : "");
1694 else
1695 q_status_message4(SM_ORDER, 0, 2,
1696 "Command \"%s\" not defined for this screen.%s%s%s",
1697 pretty_command(cmd),
1698 (help) ? " Use " : "",
1699 (help) ? help : "",
1700 (help) ? " for help" : "");
1704 void
1705 bogus_utf8_command(char *cmd, char *help)
1707 q_status_message4(SM_ORDER, 0, 2,
1708 "Command \"%s\" not defined for this screen.%s%s%s",
1709 cmd ? cmd : "?",
1710 (help) ? " Use " : "",
1711 (help) ? help : "",
1712 (help) ? " for help" : "");
1716 /*----------------------------------------------------------------------
1717 Execute FLAG message command
1719 Args: state -- Various satate info
1720 msgmap -- map of c-client to local message numbers
1722 Result: with side effect of "current" message FLAG flag set or UNset
1724 ----*/
1726 cmd_flag(struct pine *state, MSGNO_S *msgmap, int aopt)
1728 char *flagit, *seq, *screen_text[20], **exp, **p, *answer = NULL;
1729 char *keyword_array[2];
1730 int user_defined_flags = 0, mailbox_flags = 0;
1731 int directly_to_maint_screen = 0;
1732 int use_maint_screen = F_ON(F_FLAG_SCREEN_DFLT, ps_global);
1733 long unflagged, flagged, flags, rawno;
1734 MESSAGECACHE *mc = NULL;
1735 KEYWORD_S *kw;
1736 int i, cnt, is_set, trouble = 0, rv = 0;
1737 size_t len;
1738 struct flag_table *fp, *ftbl = NULL;
1739 struct flag_screen flag_screen;
1740 static char *flag_screen_text1[] = {
1741 N_(" Set desired flags for current message below. An 'X' means set"),
1742 N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."),
1743 NULL
1746 static char *flag_screen_text2[] = {
1747 N_(" Set desired flags below for selected messages. A '?' means to"),
1748 N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"),
1749 N_(" to unset it. Use the \"Return\" key to toggle, and choose"),
1750 N_(" \"Exit\" when finished."),
1751 NULL
1754 static struct flag_table default_ftbl[] = {
1755 {N_("Important"), h_flag_important, F_FLAG, 0, 0, NULL, NULL},
1756 {N_("New"), h_flag_new, F_SEEN, 0, 0, NULL, NULL},
1757 {N_("Answered"), h_flag_answered, F_ANS, 0, 0, NULL, NULL},
1758 {N_("Forwarded"), h_flag_forwarded, F_FWD, 0, 0, NULL, NULL},
1759 {N_("Deleted"), h_flag_deleted, F_DEL, 0, 0, NULL, NULL},
1760 {NULL, NO_HELP, 0, 0, 0, NULL, NULL}
1763 /* Only check for dead stream for now. Should check permanent flags
1764 * eventually
1766 if(!(any_messages(msgmap, NULL, "to Flag") && can_set_flag(state, "flag", 1)))
1767 return rv;
1769 if(sp_io_error_on_stream(state->mail_stream)){
1770 sp_set_io_error_on_stream(state->mail_stream, 0);
1771 pine_mail_check(state->mail_stream); /* forces write */
1772 return rv;
1775 go_again:
1776 answer = NULL;
1777 user_defined_flags = 0;
1778 mailbox_flags = 0;
1779 mc = NULL;
1780 trouble = 0;
1781 ftbl = NULL;
1783 /* count how large ftbl will be */
1784 for(cnt = 0; default_ftbl[cnt].name; cnt++)
1787 /* add user flags */
1788 for(kw = ps_global->keywords; kw; kw = kw->next){
1789 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick)) || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1790 user_defined_flags++;
1791 cnt++;
1796 * Add mailbox flags that aren't user-defined flags.
1797 * Don't consider it if it matches either one of our defined
1798 * keywords or one of our defined nicknames for a keyword.
1800 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1801 char *q;
1803 q = stream_to_user_flag_name(state->mail_stream, i);
1804 if(q && q[0]){
1805 for(kw = ps_global->keywords; kw; kw = kw->next){
1806 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1807 break;
1811 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1812 mailbox_flags++;
1813 cnt++;
1817 cnt += (user_defined_flags ? 2 : 0) + (mailbox_flags ? 2 : 0);
1819 /* set up ftbl, first the system flags */
1820 ftbl = (struct flag_table *) fs_get((cnt+1) * sizeof(*ftbl));
1821 memset(ftbl, 0, (cnt+1) * sizeof(*ftbl));
1822 for(i = 0, fp = ftbl; default_ftbl[i].name; i++, fp++){
1823 fp->name = cpystr(default_ftbl[i].name);
1824 fp->help = default_ftbl[i].help;
1825 fp->flag = default_ftbl[i].flag;
1826 fp->set = default_ftbl[i].set;
1827 fp->ukn = default_ftbl[i].ukn;
1830 if(user_defined_flags){
1831 fp->flag = F_COMMENT;
1832 fp->name = cpystr("");
1833 fp++;
1834 fp->flag = F_COMMENT;
1835 len = strlen(_("User-defined Keywords from Setup/Config"));
1836 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1837 snprintf(fp->name, len+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config"));
1838 fp++;
1841 /* then the user-defined keywords */
1842 if(user_defined_flags)
1843 for(kw = ps_global->keywords; kw; kw = kw->next){
1844 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick))
1845 || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1846 fp->name = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
1847 fp->keyword = cpystr(kw->kw ? kw->kw : "");
1848 if(kw->nick && kw->kw){
1849 size_t l;
1851 l = strlen(kw->kw)+2;
1852 fp->comment = (char *) fs_get((l+1) * sizeof(char));
1853 snprintf(fp->comment, l+1, "(%.*s)", (int) strlen(kw->kw), kw->kw);
1854 fp->comment[l] = '\0';
1857 fp->help = h_flag_user_flag;
1858 fp->flag = F_KEYWORD;
1859 fp->set = 0;
1860 fp->ukn = 0;
1861 fp++;
1865 if(mailbox_flags){
1866 fp->flag = F_COMMENT;
1867 fp->name = cpystr("");
1868 fp++;
1869 fp->flag = F_COMMENT;
1870 len = strlen(_("Other keywords in the mailbox that are not user-defined"));
1871 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1872 snprintf(fp->name, len+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined"));
1873 fp++;
1876 /* then the extra mailbox-defined keywords */
1877 if(mailbox_flags)
1878 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1879 char *q;
1881 q = stream_to_user_flag_name(state->mail_stream, i);
1882 if(q && q[0]){
1883 for(kw = ps_global->keywords; kw; kw = kw->next){
1884 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1885 break;
1889 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1890 fp->name = cpystr(q);
1891 fp->keyword = cpystr(q);
1892 fp->help = h_flag_user_flag;
1893 fp->flag = F_KEYWORD;
1894 fp->set = 0;
1895 fp->ukn = 0;
1896 fp++;
1900 flag_screen.flag_table = &ftbl;
1901 flag_screen.explanation = screen_text;
1903 if(MCMD_ISAGG(aopt)){
1904 if(!pseudo_selected(ps_global->mail_stream, msgmap)){
1905 free_flag_table(&ftbl);
1906 return rv;
1909 exp = flag_screen_text2;
1910 for(fp = ftbl; fp->name; fp++){
1911 fp->set = CMD_FLAG_UNKN; /* set to unknown */
1912 fp->ukn = TRUE;
1915 else if(state->mail_stream
1916 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
1917 && rawno <= state->mail_stream->nmsgs
1918 && (mc = mail_elt(state->mail_stream, rawno))){
1919 exp = flag_screen_text1;
1920 for(fp = &ftbl[0]; fp->name; fp++){
1921 fp->ukn = 0;
1922 if(fp->flag == F_KEYWORD){
1923 /* see if this keyword is defined for this message */
1924 fp->set = CMD_FLAG_CLEAR;
1925 if(user_flag_is_set(state->mail_stream,
1926 rawno, fp->keyword))
1927 fp->set = CMD_FLAG_SET;
1929 else if(fp->flag == F_FWD){
1930 /* see if forwarded keyword is defined for this message */
1931 fp->set = CMD_FLAG_CLEAR;
1932 if(user_flag_is_set(state->mail_stream,
1933 rawno, FORWARDED_FLAG))
1934 fp->set = CMD_FLAG_SET;
1936 else if(fp->flag != F_COMMENT)
1937 fp->set = ((fp->flag == F_SEEN && !mc->seen)
1938 || (fp->flag == F_DEL && mc->deleted)
1939 || (fp->flag == F_FLAG && mc->flagged)
1940 || (fp->flag == F_ANS && mc->answered))
1941 ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
1944 else{
1945 q_status_message(SM_ORDER | SM_DING, 3, 4,
1946 _("Error accessing message data"));
1947 free_flag_table(&ftbl);
1948 return rv;
1951 if(directly_to_maint_screen)
1952 goto the_maint_screen;
1954 #ifdef _WINDOWS
1955 if (mswin_usedialog ()) {
1956 if (!os_flagmsgdialog (&ftbl[0])){
1957 free_flag_table(&ftbl);
1958 return rv;
1961 else
1962 #endif
1964 int keyword_shortcut = 0;
1966 if(!use_maint_screen){
1968 * We're going to call cmd_flag_prompt(). We need
1969 * to decide whether or not to offer the keyword setting
1970 * shortcut. We'll offer it if the user has the feature
1971 * enabled AND there are some possible keywords that could
1972 * be set.
1974 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global)){
1975 for(fp = &ftbl[0]; fp->name && !keyword_shortcut; fp++){
1976 if(fp->flag == F_KEYWORD){
1977 int first_char;
1978 ESCKEY_S *tp;
1980 first_char = (fp->name && fp->name[0])
1981 ? fp->name[0] : -2;
1982 if(isascii(first_char) && isupper(first_char))
1983 first_char = tolower((unsigned char) first_char);
1985 for(tp=flag_text_opt; tp->ch != -1; tp++)
1986 if(tp->ch == first_char)
1987 break;
1989 if(tp->ch == -1)
1990 keyword_shortcut++;
1995 use_maint_screen = !cmd_flag_prompt(state, &flag_screen,
1996 keyword_shortcut);
1999 the_maint_screen:
2000 if(use_maint_screen){
2001 for(p = &screen_text[0]; *exp; p++, exp++)
2002 *p = *exp;
2004 *p = NULL;
2006 directly_to_maint_screen = flag_maintenance_screen(state, &flag_screen);
2010 /* reacquire the elt pointer */
2011 mc = (state->mail_stream
2012 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
2013 && rawno <= state->mail_stream->nmsgs)
2014 ? mail_elt(state->mail_stream, rawno) : NULL;
2016 for(fp = ftbl; mc && fp->name; fp++){
2017 flags = -1;
2018 switch(fp->flag){
2019 case F_SEEN:
2020 if((!MCMD_ISAGG(aopt) && fp->set != !mc->seen)
2021 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2022 flagit = "\\SEEN";
2023 if(fp->set){
2024 flags = 0L;
2025 unflagged = F_SEEN;
2027 else{
2028 flags = ST_SET;
2029 unflagged = F_UNSEEN;
2033 break;
2035 case F_ANS:
2036 if((!MCMD_ISAGG(aopt) && fp->set != mc->answered)
2037 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2038 flagit = "\\ANSWERED";
2039 if(fp->set){
2040 flags = ST_SET;
2041 unflagged = F_UNANS;
2043 else{
2044 flags = 0L;
2045 unflagged = F_ANS;
2049 break;
2051 case F_DEL:
2052 if((!MCMD_ISAGG(aopt) && fp->set != mc->deleted)
2053 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2054 flagit = "\\DELETED";
2055 if(fp->set){
2056 flags = ST_SET;
2057 unflagged = F_UNDEL;
2059 else{
2060 flags = 0L;
2061 unflagged = F_DEL;
2065 break;
2067 case F_FLAG:
2068 if((!MCMD_ISAGG(aopt) && fp->set != mc->flagged)
2069 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2070 flagit = "\\FLAGGED";
2071 if(fp->set){
2072 flags = ST_SET;
2073 unflagged = F_UNFLAG;
2075 else{
2076 flags = 0L;
2077 unflagged = F_FLAG;
2081 break;
2083 case F_FWD :
2084 if(!MCMD_ISAGG(aopt)){
2085 /* see if forwarded is defined for this message */
2086 is_set = CMD_FLAG_CLEAR;
2087 if(user_flag_is_set(state->mail_stream,
2088 mn_m2raw(msgmap, mn_get_cur(msgmap)),
2089 FORWARDED_FLAG))
2090 is_set = CMD_FLAG_SET;
2093 if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2094 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2095 flagit = FORWARDED_FLAG;
2096 if(fp->set){
2097 flags = ST_SET;
2098 unflagged = F_UNFWD;
2100 else{
2101 flags = 0L;
2102 unflagged = F_FWD;
2106 break;
2108 case F_KEYWORD:
2109 if(!MCMD_ISAGG(aopt)){
2110 /* see if this keyword is defined for this message */
2111 is_set = CMD_FLAG_CLEAR;
2112 if(user_flag_is_set(state->mail_stream,
2113 mn_m2raw(msgmap, mn_get_cur(msgmap)),
2114 fp->keyword))
2115 is_set = CMD_FLAG_SET;
2118 if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2119 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2120 flagit = fp->keyword;
2121 keyword_array[0] = fp->keyword;
2122 keyword_array[1] = NULL;
2123 if(fp->set){
2124 flags = ST_SET;
2125 unflagged = F_UNKEYWORD;
2127 else{
2128 flags = 0L;
2129 unflagged = F_KEYWORD;
2133 break;
2135 default:
2136 break;
2139 flagged = 0L;
2140 if(flags >= 0L
2141 && (seq = currentf_sequence(state->mail_stream, msgmap,
2142 unflagged, &flagged, unflagged & F_DEL,
2143 (fp->flag == F_KEYWORD
2144 && unflagged == F_KEYWORD)
2145 ? keyword_array : NULL,
2146 (fp->flag == F_KEYWORD
2147 && unflagged == F_UNKEYWORD)
2148 ? keyword_array : NULL))){
2150 * For user keywords, we may have to create the flag in
2151 * the folder if it doesn't already exist and we are setting
2152 * it (as opposed to clearing it). Mail_flag will
2153 * do that for us, but it's failure isn't very friendly
2154 * error-wise. So we try to make it a little smoother.
2156 if(!(fp->flag == F_KEYWORD || fp->flag == F_FWD) || !fp->set
2157 || ((i=user_flag_index(state->mail_stream, flagit)) >= 0
2158 && i < NUSERFLAGS))
2159 mail_flag(state->mail_stream, seq, flagit, flags);
2160 else{
2161 /* trouble, see if we can add the user flag */
2162 if(state->mail_stream->kwd_create)
2163 mail_flag(state->mail_stream, seq, flagit, flags);
2164 else{
2165 trouble++;
2167 if(some_user_flags_defined(state->mail_stream))
2168 q_status_message(SM_ORDER, 3, 4,
2169 _("No more keywords allowed in this folder!"));
2170 else if(fp->flag == F_FWD)
2171 q_status_message(SM_ORDER, 3, 4,
2172 _("Cannot add keywords for this folder so cannot set Forwarded flag"));
2173 else
2174 q_status_message(SM_ORDER, 3, 4,
2175 _("Cannot add keywords for this folder"));
2179 fs_give((void **) &seq);
2180 if(flagged && !trouble){
2181 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%slagged%s%s%s%s%s message%s%s \"%s\"",
2182 (fp->set) ? "F" : "Unf",
2183 MCMD_ISAGG(aopt) ? " " : "",
2184 MCMD_ISAGG(aopt) ? long2string(flagged) : "",
2185 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2186 ? " (of " : "",
2187 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2188 ? comatose(mn_total_cur(msgmap)) : "",
2189 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2190 ? ")" : "",
2191 MCMD_ISAGG(aopt) ? plural(flagged) : " ",
2192 MCMD_ISAGG(aopt) ? "" : long2string(mn_get_cur(msgmap)),
2193 fp->name);
2194 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2195 q_status_message(SM_ORDER, 0, 2, answer = tmp_20k_buf);
2196 rv++;
2201 free_flag_table(&ftbl);
2203 if(directly_to_maint_screen)
2204 goto go_again;
2206 if(MCMD_ISAGG(aopt))
2207 restore_selected(msgmap);
2209 if(!answer)
2210 q_status_message(SM_ORDER, 0, 2, _("No flags changed."));
2212 return rv;
2216 /*----------------------------------------------------------------------
2217 Offer concise status line flag prompt
2219 Args: state -- Various satate info
2220 flags -- flags to offer setting
2222 Result: TRUE if flag to set specified in flags struct or FALSE otw
2224 ----*/
2226 cmd_flag_prompt(struct pine *state, struct flag_screen *flags, int allow_keyword_shortcuts)
2228 int r, setflag = 1, first_char;
2229 struct flag_table *fp;
2230 ESCKEY_S *ek;
2231 char *ftext, *ftext_not;
2232 static char *flag_text =
2233 N_("Flag New, Deleted, Answered, Forwarded or Important ? ");
2234 static char *flag_text_ak =
2235 N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? ");
2236 static char *flag_text_not =
2237 N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? ");
2238 static char *flag_text_ak_not =
2239 N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? ");
2241 if(allow_keyword_shortcuts){
2242 int cnt = 0;
2243 ESCKEY_S *dp, *sp, *tp;
2245 for(sp=flag_text_opt; sp->ch != -1; sp++)
2246 cnt++;
2248 for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++)
2249 if(fp->flag == F_KEYWORD)
2250 cnt++;
2252 /* set up an ESCKEY_S list which includes invisible keys for keywords */
2253 ek = (ESCKEY_S *) fs_get((cnt + 1) * sizeof(*ek));
2254 memset(ek, 0, (cnt+1) * sizeof(*ek));
2255 for(dp=ek, sp=flag_text_opt; sp->ch != -1; sp++, dp++)
2256 *dp = *sp;
2258 for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2259 if(fp->flag == F_KEYWORD){
2260 first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2261 if(isascii(first_char) && isupper(first_char))
2262 first_char = tolower((unsigned char) first_char);
2265 * Check to see if an earlier keyword in the list, or one of
2266 * the builtin system letters already uses this character.
2267 * If so, the first one wins.
2269 for(tp=ek; tp->ch != 0; tp++)
2270 if(tp->ch == first_char)
2271 break;
2273 if(tp->ch != 0)
2274 continue; /* skip it, already used that char */
2276 dp->ch = first_char;
2277 dp->rval = first_char;
2278 dp->name = "";
2279 dp->label = "";
2280 dp++;
2284 dp->ch = -1;
2285 ftext = _(flag_text_ak);
2286 ftext_not = _(flag_text_ak_not);
2288 else{
2289 ek = flag_text_opt;
2290 ftext = _(flag_text);
2291 ftext_not = _(flag_text_not);
2294 while(1){
2295 r = radio_buttons(setflag ? ftext : ftext_not,
2296 -FOOTER_ROWS(state), ek, '*', SEQ_EXCEPTION-1,
2297 NO_HELP, RB_NORM | RB_SEQ_SENSITIVE);
2299 * It is SEQ_EXCEPTION-1 just so that it is some value that isn't
2300 * being used otherwise. The keywords use up all the possible
2301 * letters, so a negative number is good, but it has to be different
2302 * from other negative return values.
2304 if(r == SEQ_EXCEPTION-1) /* ol'cancelrooney */
2305 return(TRUE);
2306 else if(r == 10) /* return and goto flag screen */
2307 return(FALSE);
2308 else if(r == '!') /* flip intention */
2309 setflag = !setflag;
2310 else
2311 break;
2314 for(fp = (flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2315 if(r == 'n' || r == '*' || r == 'd' || r == 'a' || r == 'f'){
2316 if((r == 'n' && fp->flag == F_SEEN)
2317 || (r == '*' && fp->flag == F_FLAG)
2318 || (r == 'd' && fp->flag == F_DEL)
2319 || (r == 'f' && fp->flag == F_FWD)
2320 || (r == 'a' && fp->flag == F_ANS)){
2321 fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2322 break;
2325 else if(allow_keyword_shortcuts && fp->flag == F_KEYWORD){
2326 first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2327 if(isascii(first_char) && isupper(first_char))
2328 first_char = tolower((unsigned char) first_char);
2330 if(r == first_char){
2331 fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2332 break;
2337 if(ek != flag_text_opt)
2338 fs_give((void **) &ek);
2340 return(TRUE);
2345 * (*ft) is an array of flag_table entries.
2347 void
2348 free_flag_table(struct flag_table **ft)
2350 struct flag_table *fp;
2352 if(ft && *ft){
2353 for(fp = (*ft); fp->name; fp++){
2354 if(fp->name)
2355 fs_give((void **) &fp->name);
2357 if(fp->keyword)
2358 fs_give((void **) &fp->keyword);
2360 if(fp->comment)
2361 fs_give((void **) &fp->comment);
2364 fs_give((void **) ft);
2369 /*----------------------------------------------------------------------
2370 Execute REPLY message command
2372 Args: state -- Various satate info
2373 msgmap -- map of c-client to local message numbers
2375 Result: reply sent or not
2377 ----*/
2379 cmd_reply(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2381 int rv = 0;
2383 if(any_messages(msgmap, NULL, "to Reply to")){
2384 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2385 return rv;
2387 rv = reply(state, role);
2389 if(MCMD_ISAGG(aopt))
2390 restore_selected(msgmap);
2392 state->mangled_screen = 1;
2395 return rv;
2399 /*----------------------------------------------------------------------
2400 Execute FORWARD message command
2402 Args: state -- Various satate info
2403 msgmap -- map of c-client to local message numbers
2405 Result: selected message[s] forwarded or not
2407 ----*/
2409 cmd_forward(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2411 int rv = 0;
2413 if(any_messages(msgmap, NULL, "to Forward")){
2414 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2415 return rv;
2417 rv = forward(state, role);
2419 if(MCMD_ISAGG(aopt))
2420 restore_selected(msgmap);
2422 state->mangled_screen = 1;
2425 return rv;
2429 /*----------------------------------------------------------------------
2430 Execute BOUNCE message command
2432 Args: state -- Various satate info
2433 msgmap -- map of c-client to local message numbers
2434 aopt -- aggregate options
2436 Result: selected message[s] bounced or not
2438 ----*/
2440 cmd_bounce(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2442 int rv = 0;
2444 if(any_messages(msgmap, NULL, "to Bounce")){
2445 long i;
2446 if(MCMD_ISAGG(aopt)){
2447 if(!pseudo_selected(state->mail_stream, msgmap))
2448 return rv;
2450 else if((i = any_lflagged(msgmap, MN_SLCT)) > 0
2451 && get_lflag(state->mail_stream, msgmap,
2452 mn_m2raw(msgmap, mn_get_cur(msgmap)), MN_SLCT) == 0)
2453 q_status_message(SM_ORDER | SM_DING, 3, 4,
2454 _("WARNING: non-selected message is being bounced!"));
2455 else if (i > 1L
2456 && get_lflag(state->mail_stream, msgmap,
2457 mn_m2raw(msgmap, mn_get_cur(msgmap)), MN_SLCT))
2458 q_status_message(SM_ORDER | SM_DING, 3, 4,
2459 _("WARNING: not bouncing all selected messages!"));
2461 rv = bounce(state, role);
2463 if(MCMD_ISAGG(aopt))
2464 restore_selected(msgmap);
2466 state->mangled_footer = 1;
2469 return rv;
2473 /*----------------------------------------------------------------------
2474 Execute save message command: prompt for folder and call function to save
2476 Args: screen_line -- Line on the screen to prompt on
2477 message -- The MESSAGECACHE entry of message to save
2479 Result: The folder lister can be called to make selection; mangled screen set
2481 This does the prompting for the folder name to save to, possibly calling
2482 up the folder display for selection of folder by user.
2483 ----*/
2485 cmd_save(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
2487 char newfolder[MAILTMPLEN], nmsgs[32], *nick;
2488 int we_cancel = 0, rv = 0, save_flags;
2489 long i, raw;
2490 CONTEXT_S *cntxt = NULL;
2491 ENVELOPE *e = NULL;
2492 SaveDel del = DontAsk;
2493 SavePreserveOrder pre = DontAskPreserve;
2495 dprint((4, "\n - saving message -\n"));
2497 if(MCMD_ISAGG(aopt) && !pseudo_selected(stream, msgmap))
2498 return rv;
2500 state->ugly_consider_advancing_bit = 0;
2501 if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM, state)
2502 && msgno_any_deletedparts(stream, msgmap)
2503 && want_to(_("Saved copy will NOT include entire message! Continue"),
2504 'y', 'n', NO_HELP, WT_FLUSH_IN | WT_SEQ_SENSITIVE) != 'y'){
2505 restore_selected(msgmap);
2506 cmd_cancelled("Save message");
2507 return rv;
2510 raw = mn_m2raw(msgmap, mn_get_cur(msgmap));
2512 if(mn_total_cur(msgmap) <= 1L){
2513 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld ", mn_get_cur(msgmap));
2514 nmsgs[sizeof(nmsgs)-1] = '\0';
2515 e = pine_mail_fetchstructure(stream, raw, NULL);
2516 if(!e) {
2517 q_status_message(SM_ORDER | SM_DING, 3, 4,
2518 _("Can't save message. Error accessing folder"));
2519 restore_selected(msgmap);
2520 return rv;
2523 else{
2524 snprintf(nmsgs, sizeof(nmsgs), "%s msgs ", comatose(mn_total_cur(msgmap)));
2525 nmsgs[sizeof(nmsgs)-1] = '\0';
2527 /* e is just used to get a default save folder from the first msg */
2528 e = pine_mail_fetchstructure(stream,
2529 mn_m2raw(msgmap, mn_first_cur(msgmap)),
2530 NULL);
2533 del = (!READONLY_FOLDER(stream) && F_OFF(F_SAVE_WONT_DELETE, ps_global))
2534 ? Del : NoDel;
2535 if(mn_total_cur(msgmap) > 1L)
2536 pre = F_OFF(F_AGG_SEQ_COPY, ps_global) ? Preserve : NoPreserve;
2537 else
2538 pre = DontAskPreserve;
2540 if(save_prompt(state, &cntxt, newfolder, sizeof(newfolder), nmsgs, e,
2541 raw, NULL, &del, &pre)){
2543 if(ps_global && ps_global->ttyo){
2544 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2545 ps_global->mangled_footer = 1;
2548 save_flags = SV_FIX_DELS;
2549 if(pre == RetPreserve)
2550 save_flags |= SV_PRESERVE;
2551 if(del == RetDel)
2552 save_flags |= SV_DELETE;
2553 if(ps_global->context_list == cntxt && !strucmp(newfolder, ps_global->inbox_name))
2554 save_flags |= SV_INBOXWOCNTXT;
2556 we_cancel = busy_cue(_("Saving"), NULL, 1);
2557 i = save(state, stream, cntxt, newfolder, msgmap, save_flags);
2558 if(we_cancel)
2559 cancel_busy_cue(0);
2561 if(i == mn_total_cur(msgmap)){
2562 rv++;
2563 if(mn_total_cur(msgmap) <= 1L){
2564 int need, avail = ps_global->ttyo->screen_cols - 2;
2565 int lennick, lenfldr;
2567 if(cntxt
2568 && ps_global->context_list->next
2569 && context_isambig(newfolder)){
2570 lennick = MIN(strlen(cntxt->nickname), 500);
2571 lenfldr = MIN(strlen(newfolder), 500);
2572 need = 27 + strlen(long2string(mn_get_cur(msgmap))) +
2573 lenfldr + lennick;
2574 if(need > avail){
2575 if(lennick > 10){
2576 need -= MIN(lennick-10, need-avail);
2577 lennick -= MIN(lennick-10, need-avail);
2580 if(need > avail && lenfldr > 10)
2581 lenfldr -= MIN(lenfldr-10, need-avail);
2584 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2585 "Message %s copied to \"%s\" in <%s>",
2586 long2string(mn_get_cur(msgmap)),
2587 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2588 lenfldr, MidDots),
2589 short_str(cntxt->nickname,
2590 (char *)(tmp_20k_buf+2000), 1000,
2591 lennick, EndDots));
2592 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2594 else if((nick=folder_is_target_of_nick(newfolder, cntxt)) != NULL){
2595 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2596 "Message %s copied to \"%s\"",
2597 long2string(mn_get_cur(msgmap)),
2598 nick);
2599 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2601 else{
2602 char *f = " folder";
2604 lenfldr = MIN(strlen(newfolder), 500);
2605 need = 28 + strlen(long2string(mn_get_cur(msgmap))) +
2606 lenfldr;
2607 if(need > avail){
2608 need -= strlen(f);
2609 f = "";
2610 if(need > avail && lenfldr > 10)
2611 lenfldr -= MIN(lenfldr-10, need-avail);
2614 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
2615 "Message %s copied to%s \"%s\"",
2616 long2string(mn_get_cur(msgmap)), f,
2617 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2618 lenfldr, MidDots));
2619 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2622 else{
2623 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s messages saved",
2624 comatose(mn_total_cur(msgmap)));
2625 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2628 if(del == RetDel){
2629 strncat(tmp_20k_buf, " and deleted", SIZEOF_20KBUF-strlen(tmp_20k_buf)-1);
2630 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2633 q_status_message(SM_ORDER, 0, 3, tmp_20k_buf);
2635 if(!MCMD_ISAGG(aopt) && F_ON(F_SAVE_ADVANCES, state)){
2636 if(sp_new_mail_count(stream))
2637 process_filter_patterns(stream, msgmap,
2638 sp_new_mail_count(stream));
2640 mn_inc_cur(stream, msgmap,
2641 (in_index == View && THREADING()
2642 && sp_viewing_a_thread(stream))
2643 ? MH_THISTHD
2644 : (in_index == View)
2645 ? MH_ANYTHD : MH_NONE);
2648 state->ugly_consider_advancing_bit = 1;
2652 if(MCMD_ISAGG(aopt)) /* straighten out fakes */
2653 restore_selected(msgmap);
2655 if(del == RetDel)
2656 update_titlebar_status(); /* make sure they see change */
2658 return rv;
2662 void
2663 role_compose(struct pine *state)
2665 int action;
2667 if(F_ON(F_ALT_ROLE_MENU, state) && mn_get_total(state->msgmap) > 0L){
2668 PAT_STATE pstate;
2670 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
2671 action = radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
2672 -FOOTER_ROWS(state), choose_action,
2673 'c', 'x', h_role_compose, RB_NORM);
2675 else{
2676 q_status_message(SM_ORDER, 0, 3,
2677 _("No roles available. Use Setup/Rules to add roles."));
2678 return;
2681 else
2682 action = 'c';
2684 if(action == 'c' || action == 'r' || action == 'f' || action == 'b'){
2685 ACTION_S *role = NULL;
2686 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
2688 redraw = state->redrawer;
2689 state->redrawer = NULL;
2690 prev_screen = state->prev_screen;
2691 role = NULL;
2692 state->next_screen = SCREEN_FUN_NULL;
2694 /* Setup role */
2695 if(role_select_screen(state, &role,
2696 action == 'f' ? MC_FORWARD :
2697 action == 'r' ? MC_REPLY :
2698 action == 'b' ? MC_BOUNCE :
2699 action == 'c' ? MC_COMPOSE : 0) < 0){
2700 cmd_cancelled(action == 'f' ? _("Forward") :
2701 action == 'r' ? _("Reply") :
2702 action == 'c' ? _("Composition") : _("Bounce"));
2703 state->next_screen = prev_screen;
2704 state->redrawer = redraw;
2705 state->mangled_screen = 1;
2707 else{
2709 * If default role was selected (NULL) we need to make
2710 * up a role which won't do anything, but will cause
2711 * compose_mail to think there's already a role so that
2712 * it won't try to confirm the default.
2714 if(role)
2715 role = combine_inherited_role(role);
2716 else{
2717 role = (ACTION_S *) fs_get(sizeof(*role));
2718 memset((void *) role, 0, sizeof(*role));
2719 role->nick = cpystr("Default Role");
2722 state->redrawer = NULL;
2723 switch(action){
2724 case 'c':
2725 compose_mail(NULL, NULL, role, NULL, NULL);
2726 break;
2728 case 'r':
2729 (void) reply(state, role);
2730 break;
2732 case 'f':
2733 (void) forward(state, role);
2734 break;
2736 case 'b':
2737 (void) bounce(state, role);
2738 break;
2741 if(role)
2742 free_action(&role);
2744 state->next_screen = prev_screen;
2745 state->redrawer = redraw;
2746 state->mangled_screen = 1;
2752 /*----------------------------------------------------------------------
2753 Do the dirty work of prompting the user for a folder name
2755 Args:
2756 nfldr should be a buffer at least MAILTMPLEN long
2757 dela -- a pointer to a SaveDel. If it is
2758 DontAsk on input, don't offer Delete prompt
2759 Del on input, offer Delete command with default of Delete
2760 NoDel NoDelete
2761 RetDel and RetNoDel are return values
2764 Result:
2766 ----*/
2768 save_prompt(struct pine *state, CONTEXT_S **cntxt, char *nfldr, size_t len_nfldr,
2769 char *nmsgs, ENVELOPE *env, long int rawmsgno, char *section,
2770 SaveDel *dela, SavePreserveOrder *prea)
2772 int rc, ku = -1, n, flags, last_rc = 0, saveable_count = 0, done = 0;
2773 int delindex = 0, preindex = 0, r;
2774 char prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN];
2775 char *buf = tmp_20k_buf;
2776 char shortbuf[200];
2777 char *folder;
2778 HelpType help;
2779 SaveDel del = DontAsk;
2780 SavePreserveOrder pre = DontAskPreserve;
2781 char *deltext = NULL;
2782 static HISTORY_S *history = NULL;
2783 CONTEXT_S *tc;
2784 ESCKEY_S ekey[10];
2786 if(!cntxt)
2787 alpine_panic("no context ptr in save_prompt");
2789 init_hist(&history, HISTSIZE);
2791 if(!(folder = save_get_default(state, env, rawmsgno, section, cntxt)))
2792 return(0); /* message expunged! */
2794 /* how many context's can be saved to... */
2795 for(tc = state->context_list; tc; tc = tc->next)
2796 if(!NEWS_TEST(tc))
2797 saveable_count++;
2799 /* set up extra command option keys */
2800 rc = 0;
2801 ekey[rc].ch = ctrl('T');
2802 ekey[rc].rval = 2;
2803 ekey[rc].name = "^T";
2804 /* TRANSLATORS: command means go to Folders list */
2805 ekey[rc++].label = N_("To Fldrs");
2807 if(saveable_count > 1){
2808 ekey[rc].ch = ctrl('P');
2809 ekey[rc].rval = 10;
2810 ekey[rc].name = "^P";
2811 ekey[rc++].label = N_("Prev Collection");
2813 ekey[rc].ch = ctrl('N');
2814 ekey[rc].rval = 11;
2815 ekey[rc].name = "^N";
2816 ekey[rc++].label = N_("Next Collection");
2819 if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
2820 ekey[rc].ch = TAB;
2821 ekey[rc].rval = 12;
2822 ekey[rc].name = "TAB";
2823 /* TRANSLATORS: command asks alpine to complete the name when tab is typed */
2824 ekey[rc++].label = N_("Complete");
2827 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
2828 ekey[rc].ch = ctrl('X');
2829 ekey[rc].rval = 14;
2830 ekey[rc].name = "^X";
2831 /* TRANSLATORS: list all the matches */
2832 ekey[rc++].label = N_("ListMatches");
2835 if(dela && (*dela == NoDel || *dela == Del)){
2836 ekey[rc].ch = ctrl('R');
2837 ekey[rc].rval = 15;
2838 ekey[rc].name = "^R";
2839 delindex = rc++;
2840 del = *dela;
2843 if(prea && (*prea == NoPreserve || *prea == Preserve)){
2844 ekey[rc].ch = ctrl('W');
2845 ekey[rc].rval = 16;
2846 ekey[rc].name = "^W";
2847 preindex = rc++;
2848 pre = *prea;
2851 if(saveable_count > 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2852 ekey[rc].ch = KEY_UP;
2853 ekey[rc].rval = 10;
2854 ekey[rc].name = "";
2855 ekey[rc++].label = "";
2857 ekey[rc].ch = KEY_DOWN;
2858 ekey[rc].rval = 11;
2859 ekey[rc].name = "";
2860 ekey[rc++].label = "";
2862 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2863 ekey[rc].ch = KEY_UP;
2864 ekey[rc].rval = 30;
2865 ekey[rc].name = "";
2866 ku = rc;
2867 ekey[rc++].label = "";
2869 ekey[rc].ch = KEY_DOWN;
2870 ekey[rc].rval = 31;
2871 ekey[rc].name = "";
2872 ekey[rc++].label = "";
2875 ekey[rc].ch = -1;
2877 *nfldr = '\0';
2878 help = NO_HELP;
2879 while(!done){
2880 /* only show collection number if more than one available */
2881 if(ps_global->context_list->next)
2882 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder in <%s> [%s] : ",
2883 deltext ? deltext : "",
2884 nmsgs,
2885 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2886 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2887 else
2888 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder [%s] : ",
2889 deltext ? deltext : "",
2890 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2892 prompt[sizeof(prompt)-1] = '\0';
2895 * If the prompt won't fit, try removing deltext.
2897 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && deltext){
2898 if(ps_global->context_list->next)
2899 snprintf(prompt, sizeof(prompt), "SAVE %sto folder in <%s> [%s] : ",
2900 nmsgs,
2901 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2902 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2903 else
2904 snprintf(prompt, sizeof(prompt), "SAVE %sto folder [%s] : ",
2905 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2907 prompt[sizeof(prompt)-1] = '\0';
2911 * If the prompt still won't fit, remove the extra info contained
2912 * in nmsgs.
2914 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && *nmsgs){
2915 if(ps_global->context_list->next)
2916 snprintf(prompt, sizeof(prompt), "SAVE to folder in <%s> [%s] : ",
2917 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2918 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2919 else
2920 snprintf(prompt, sizeof(prompt), "SAVE to folder [%s] : ",
2921 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2923 prompt[sizeof(prompt)-1] = '\0';
2926 if(del != DontAsk)
2927 ekey[delindex].label = (del == NoDel) ? "Delete" : "No Delete";
2929 if(pre != DontAskPreserve)
2930 ekey[preindex].label = (pre == NoPreserve) ? "Preserve Order" : "Any Order";
2932 if(ku >= 0){
2933 if(items_in_hist(history) > 1){
2934 ekey[ku].name = HISTORY_UP_KEYNAME;
2935 ekey[ku].label = HISTORY_KEYLABEL;
2936 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
2937 ekey[ku+1].label = HISTORY_KEYLABEL;
2939 else{
2940 ekey[ku].name = "";
2941 ekey[ku].label = "";
2942 ekey[ku+1].name = "";
2943 ekey[ku+1].label = "";
2947 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
2948 rc = optionally_enter(nfldr, -FOOTER_ROWS(state), 0, len_nfldr,
2949 prompt, ekey, help, &flags);
2951 switch(rc){
2952 case -1 :
2953 q_status_message(SM_ORDER | SM_DING, 3, 3,
2954 _("Error reading folder name"));
2955 done--;
2956 break;
2958 case 0 :
2959 removing_trailing_white_space(nfldr);
2960 removing_leading_white_space(nfldr);
2962 if(*nfldr || *folder){
2963 char *p, *name, *fullname = NULL;
2964 int exists, breakout = FALSE;
2966 if(!*nfldr){
2967 strncpy(nfldr, folder, len_nfldr-1);
2968 nfldr[len_nfldr-1] = '\0';
2971 save_hist(history, nfldr, 0, (void *) *cntxt);
2973 if(!(name = folder_is_nick(nfldr, FOLDERS(*cntxt), 0)))
2974 name = nfldr;
2976 if(update_folder_spec(expanded, sizeof(expanded), name)){
2977 strncpy(name = nfldr, expanded, len_nfldr-1);
2978 nfldr[len_nfldr-1] = '\0';
2981 exists = folder_name_exists(*cntxt, name, &fullname);
2983 if(exists == FEX_ERROR){
2984 q_status_message1(SM_ORDER, 0, 3,
2985 _("Problem accessing folder \"%s\""),
2986 nfldr);
2987 done--;
2989 else{
2990 if(fullname){
2991 strncpy(name = nfldr, fullname, len_nfldr-1);
2992 nfldr[len_nfldr-1] = '\0';
2993 fs_give((void **) &fullname);
2994 breakout = TRUE;
2997 if(exists & FEX_ISFILE){
2998 done++;
3000 else if((exists & FEX_ISDIR)){
3001 char tmp[MAILTMPLEN];
3003 tc = *cntxt;
3004 if(breakout){
3005 CONTEXT_S *fake_context;
3006 size_t l;
3008 strncpy(tmp, name, sizeof(tmp));
3009 tmp[sizeof(tmp)-2-1] = '\0';
3010 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
3011 if(l < sizeof(tmp)){
3012 tmp[l] = tc->dir->delim;
3013 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
3016 else
3017 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
3019 tmp[sizeof(tmp)-1] = '\0';
3021 fake_context = new_context(tmp, 0);
3022 nfldr[0] = '\0';
3023 done = display_folder_list(&fake_context, nfldr,
3024 1, folders_for_save);
3025 free_context(&fake_context);
3027 else if(tc->dir->delim
3028 && (p = strrindex(name, tc->dir->delim))
3029 && *(p+1) == '\0')
3030 done = display_folder_list(cntxt, nfldr,
3031 1, folders_for_save);
3032 else{
3033 q_status_message1(SM_ORDER, 3, 3,
3034 _("\"%s\" is a directory"), name);
3035 if(tc->dir->delim
3036 && !((p=strrindex(name, tc->dir->delim)) && *(p+1) == '\0')){
3037 strncpy(tmp, name, sizeof(tmp));
3038 tmp[sizeof(tmp)-1] = '\0';
3039 snprintf(nfldr, len_nfldr, "%s%c", tmp, tc->dir->delim);
3043 else{ /* Doesn't exist, create! */
3044 if((fullname = folder_as_breakout(*cntxt, name)) != NULL){
3045 strncpy(name = nfldr, fullname, len_nfldr-1);
3046 nfldr[len_nfldr-1] = '\0';
3047 fs_give((void **) &fullname);
3050 switch(create_for_save(*cntxt, name)){
3051 case 1 : /* success */
3052 done++;
3053 break;
3054 case 0 : /* error */
3055 case -1 : /* declined */
3056 done--;
3057 break;
3062 break;
3064 /* else fall thru like they cancelled */
3066 case 1 :
3067 cmd_cancelled("Save message");
3068 done--;
3069 break;
3071 case 2 :
3072 r = display_folder_list(cntxt, nfldr, 0, folders_for_save);
3074 if(r)
3075 done++;
3077 break;
3079 case 3 :
3080 helper(h_save, _("HELP FOR SAVE"), HLPD_SIMPLE);
3081 ps_global->mangled_screen = 1;
3082 break;
3084 case 4 : /* redraw */
3085 break;
3087 case 10 : /* previous collection */
3088 for(tc = (*cntxt)->prev; tc; tc = tc->prev)
3089 if(!NEWS_TEST(tc))
3090 break;
3092 if(!tc){
3093 CONTEXT_S *tc2;
3095 for(tc2 = (tc = (*cntxt))->next; tc2; tc2 = tc2->next)
3096 if(!NEWS_TEST(tc2))
3097 tc = tc2;
3100 *cntxt = tc;
3101 break;
3103 case 11 : /* next collection */
3104 tc = (*cntxt);
3107 if(((*cntxt) = (*cntxt)->next) == NULL)
3108 (*cntxt) = ps_global->context_list;
3109 while(NEWS_TEST(*cntxt) && (*cntxt) != tc);
3110 break;
3112 case 12 : /* file name completion */
3113 if(!folder_complete(*cntxt, nfldr, len_nfldr, &n)){
3114 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
3115 r = display_folder_list(cntxt, nfldr, 1, folders_for_save);
3116 if(r)
3117 done++; /* bingo! */
3118 else
3119 rc = 0; /* burn last_rc */
3121 else
3122 Writechar(BELL, 0);
3125 break;
3127 case 14 : /* file name completion */
3128 r = display_folder_list(cntxt, nfldr, 2, folders_for_save);
3129 if(r)
3130 done++; /* bingo! */
3131 else
3132 rc = 0; /* burn last_rc */
3134 break;
3136 case 15 : /* Delete / No Delete */
3137 del = (del == NoDel) ? Del : NoDel;
3138 deltext = (del == NoDel) ? " (no delete)" : " (and delete)";
3139 break;
3141 case 16 : /* Preserve Order or not */
3142 pre = (pre == NoPreserve) ? Preserve : NoPreserve;
3143 break;
3145 case 30 :
3146 if((p = get_prev_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3147 strncpy(nfldr, p, len_nfldr);
3148 nfldr[len_nfldr-1] = '\0';
3149 if(history->hist[history->curindex])
3150 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3152 else
3153 Writechar(BELL, 0);
3155 break;
3157 case 31 :
3158 if((p = get_next_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3159 strncpy(nfldr, p, len_nfldr);
3160 nfldr[len_nfldr-1] = '\0';
3161 if(history->hist[history->curindex])
3162 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3164 else
3165 Writechar(BELL, 0);
3167 break;
3169 default :
3170 alpine_panic("Unhandled case");
3171 break;
3174 last_rc = rc;
3177 ps_global->mangled_footer = 1;
3179 if(done < 0)
3180 return(0);
3182 if(*nfldr){
3183 strncpy(ps_global->last_save_folder, nfldr, sizeof(ps_global->last_save_folder)-1);
3184 ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0';
3185 if(*cntxt)
3186 ps_global->last_save_context = *cntxt;
3188 else{
3189 strncpy(nfldr, folder, len_nfldr-1);
3190 nfldr[len_nfldr-1] = '\0';
3193 /* nickname? Copy real name to nfldr */
3194 if(*cntxt
3195 && context_isambig(nfldr)
3196 && (p = folder_is_nick(nfldr, FOLDERS(*cntxt), 0))){
3197 strncpy(nfldr, p, len_nfldr-1);
3198 nfldr[len_nfldr-1] = '\0';
3201 if(dela && (*dela == NoDel || *dela == Del))
3202 *dela = (del == NoDel) ? RetNoDel : RetDel;
3204 if(prea && (*prea == NoPreserve || *prea == Preserve))
3205 *prea = (pre == NoPreserve) ? RetNoPreserve : RetPreserve;
3207 return(1);
3211 /*----------------------------------------------------------------------
3212 Prompt user before implicitly creating a folder for saving
3214 Args: context - context to create folder in
3215 folder - folder name to create
3217 Result: 1 on proceed, -1 on decline, 0 on error
3219 ----*/
3221 create_for_save_prompt(CONTEXT_S *context, char *folder, int sequence_sensitive)
3223 if(context && ps_global->context_list->next && context_isambig(folder)){
3224 if(context->use & CNTXT_INCMNG){
3225 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3226 _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
3227 folder, (strlen(folder) > 15) ? "..." : "");
3228 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
3229 return(0); /* error */
3232 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3233 _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
3234 folder, (strlen(folder) > 15) ? "..." : "",
3235 context->nickname,
3236 (strlen(context->nickname) > 15) ? "..." : "");
3238 else
3239 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
3240 _("Folder \"%.40s%s\" doesn't exist. Create"),
3241 folder, strlen(folder) > 40 ? "..." : "");
3243 if(want_to(tmp_20k_buf, 'y', 'n',
3244 NO_HELP, (sequence_sensitive) ? WT_SEQ_SENSITIVE : WT_NORM) != 'y'){
3245 cmd_cancelled("Save message");
3246 return(-1);
3249 return(1);
3254 /*----------------------------------------------------------------------
3255 Expunge messages from current folder
3257 Args: state -- pointer to struct holding a bunch of pine state
3258 msgmap -- table mapping msg nums to c-client sequence nums
3259 qline -- screen line to ask questions on
3260 agg -- boolean indicating we're to operate on aggregate set
3262 Result:
3263 ----*/
3265 cmd_expunge(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int agg)
3267 long del_count, prefilter_del_count = 0;
3268 int we_cancel = 0, rv = 0;
3269 char prompt[MAX_SCREEN_COLS+1];
3270 char *sequence;
3271 COLOR_PAIR *lastc = NULL;
3273 dprint((2, "\n - expunge -\n"));
3275 del_count = 0;
3277 sequence = MCMD_ISAGG(agg) ? selected_sequence(stream, msgmap, NULL, 0) : NULL;
3279 if(MCMD_ISAGG(agg)){
3280 long i;
3281 MESSAGECACHE *mc;
3282 for(i = 1L; i <= stream->nmsgs; i++){
3283 if((mc = mail_elt(stream, i)) != NULL
3284 && mc->sequence && mc->deleted)
3285 del_count++;
3287 if(del_count == 0){
3288 q_status_message(SM_ORDER, 0, 4,
3289 _("No selected messages are deleted"));
3290 return 0;
3292 } else {
3293 if(!any_messages(msgmap, NULL, "to Expunge"))
3294 return rv;
3297 if(IS_NEWS(stream) && stream->rdonly){
3298 if(!MCMD_ISAGG(agg))
3299 del_count = count_flagged(stream, F_DEL);
3300 if(del_count > 0L){
3301 state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3302 snprintf(prompt, sizeof(prompt), "Exclude %ld message%s from %.*s", del_count,
3303 plural(del_count), MAX_SCREEN_COLS+1-40,
3304 pretty_fn(state->cur_folder));
3305 prompt[sizeof(prompt)-1] = '\0';
3306 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3307 || (F_ON(F_AUTO_EXPUNGE, state)
3308 && (state->context_current
3309 && (state->context_current->use & CNTXT_INCMNG))
3310 && context_isambig(state->cur_folder))
3311 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
3313 if(F_ON(F_NEWS_CROSS_DELETE, state))
3314 cross_delete_crossposts(stream);
3316 msgno_exclude_deleted(stream, msgmap, sequence);
3317 clear_index_cache(stream, 0);
3320 * This is kind of surprising at first. For most sort
3321 * orders, if the whole set is sorted, then any subset
3322 * is also sorted. Not so for threaded sorts.
3324 if(SORT_IS_THREADED(msgmap))
3325 refresh_sort(stream, msgmap, SRT_NON);
3327 state->mangled_body = 1;
3328 state->mangled_header = 1;
3329 q_status_message2(SM_ORDER, 0, 4,
3330 "%s message%s excluded",
3331 long2string(del_count),
3332 plural(del_count));
3334 else
3335 any_messages(NULL, NULL, "Excluded");
3337 else
3338 any_messages(NULL, "deleted", "to Exclude");
3340 return del_count;
3342 else if(READONLY_FOLDER(stream)){
3343 q_status_message(SM_ORDER, 0, 4,
3344 _("Can't expunge. Folder is read-only"));
3345 return del_count;
3348 if(!MCMD_ISAGG(agg)){
3349 prefilter_del_count = count_flagged(stream, F_DEL|F_NOFILT);
3350 mail_expunge_prefilter(stream, MI_NONE);
3351 del_count = count_flagged(stream, F_DEL|F_NOFILT);
3354 if(del_count != 0){
3355 int ret;
3356 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3357 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3358 snprintf(prompt, sizeof(prompt), "Expunge %ld message%s from %.*s", del_count,
3359 plural(del_count), MAX_SCREEN_COLS+1-40,
3360 pretty_fn((char *) fname));
3361 if(fname) fs_give((void **)&fname);
3362 prompt[sizeof(prompt)-1] = '\0';
3363 state->mangled_footer = 1;
3365 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3366 || (F_ON(F_AUTO_EXPUNGE, state)
3367 && ((!strucmp(state->cur_folder,state->inbox_name))
3368 || (state->context_current->use & CNTXT_INCMNG))
3369 && context_isambig(state->cur_folder))
3370 || (ret=want_to(prompt, 'y', 0, NO_HELP, WT_NORM)) == 'y')
3371 ret = 'y';
3373 if(ret == 'x')
3374 cmd_cancelled("Expunge");
3376 if(ret != 'y')
3377 return 0;
3380 dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
3381 mn_get_total(msgmap), mn_get_cur(msgmap), del_count));
3383 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3384 state->VAR_TITLE_BACK_COLOR,
3385 PSC_REV|PSC_RET);
3387 PutLine0(0, 0, "**"); /* indicate delay */
3389 if(lastc){
3390 (void)pico_set_colorp(lastc, PSC_NONE);
3391 free_color_pair(&lastc);
3394 MoveCursor(state->ttyo->screen_rows -FOOTER_ROWS(state), 0);
3395 fflush(stdout);
3397 we_cancel = busy_cue(_("Expunging"), NULL, 1);
3399 if(cmd_expunge_work(stream, msgmap, sequence))
3400 state->mangled_body = 1;
3402 if(sequence)
3403 fs_give((void **)&sequence);
3405 if(we_cancel)
3406 cancel_busy_cue((sp_expunge_count(stream) > 0) ? 0 : -1);
3408 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3409 state->VAR_TITLE_BACK_COLOR,
3410 PSC_REV|PSC_RET);
3411 PutLine0(0, 0, " "); /* indicate delay's over */
3413 if(lastc){
3414 (void)pico_set_colorp(lastc, PSC_NONE);
3415 free_color_pair(&lastc);
3418 fflush(stdout);
3420 if(sp_expunge_count(stream) > 0){
3422 * This is kind of surprising at first. For most sort
3423 * orders, if the whole set is sorted, then any subset
3424 * is also sorted. Not so for threaded sorts.
3426 if(SORT_IS_THREADED(msgmap))
3427 refresh_sort(stream, msgmap, SRT_NON);
3429 else{
3430 if(del_count){
3431 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3432 q_status_message1(SM_ORDER, 0, 3,
3433 _("No messages expunged from folder \"%s\""),
3434 pretty_fn((char *) fname));
3435 if(fname) fs_give((void **)&fname);
3437 else if(!prefilter_del_count)
3438 q_status_message(SM_ORDER, 0, 3,
3439 _("No messages marked deleted. No messages expunged."));
3441 return del_count;
3445 /*----------------------------------------------------------------------
3446 Expunge_and_close callback to prompt user for confirmation
3448 Args: stream -- folder's stream
3449 folder -- name of folder containing folders
3450 deleted -- number of del'd msgs
3452 Result: 'y' to continue with expunge
3453 ----*/
3455 expunge_prompt(MAILSTREAM *stream, char *folder, long int deleted)
3457 long max_folder;
3458 int charcnt = 0;
3459 char prompt_b[MAX_SCREEN_COLS+1], temp[MAILTMPLEN+1], buff[MAX_SCREEN_COLS+1];
3460 char *short_folder_name;
3462 if(deleted == 1)
3463 charcnt = 1;
3464 else{
3465 snprintf(temp, sizeof(temp), "%ld", deleted);
3466 charcnt = strlen(temp)+1;
3469 max_folder = MAX(1,MAXPROMPT - (36+charcnt));
3470 strncpy(temp, folder, sizeof(temp));
3471 temp[sizeof(temp)-1] = '\0';
3472 short_folder_name = short_str(temp,buff,sizeof(buff),max_folder,FrontDots);
3474 if(IS_NEWS(stream))
3475 snprintf(prompt_b, sizeof(prompt_b),
3476 "Delete %s%ld message%s from \"%s\"",
3477 (deleted > 1L) ? "all " : "", deleted,
3478 plural(deleted), short_folder_name);
3479 else
3480 snprintf(prompt_b, sizeof(prompt_b),
3481 "Expunge the %ld deleted message%s from \"%s\"",
3482 deleted, deleted == 1 ? "" : "s",
3483 short_folder_name);
3485 return(want_to(prompt_b, 'y', 0, NO_HELP, WT_NORM));
3490 * This is used with multiple append saves. Call it once before
3491 * the series of appends with SSCP_INIT and once after all are
3492 * done with SSCP_END. In between, it is called automatically
3493 * from save_fetch_append or save_fetch_append_cb when we need
3494 * to ask the user if he or she wants to continue even though
3495 * announced message size doesn't match the actual message size.
3496 * As of 2008-02-29 the gmail IMAP server has these size mismatches
3497 * on a regular basis even though the data is ok.
3500 save_size_changed_prompt(long msgno, int flags)
3502 int ret;
3503 char prompt[100];
3504 static int remember_the_yes = 0;
3505 static int possible_corruption = 0;
3506 static ESCKEY_S save_size_opts[] = {
3507 {'y', 'y', "Y", "Yes"},
3508 {'n', 'n', "N", "No"},
3509 {'a', 'a', "A", "yes to All"},
3510 {-1, 0, NULL, NULL}
3513 if(F_ON(F_IGNORE_SIZE, ps_global))
3514 return 'y';
3516 if(flags & SSCP_INIT || flags & SSCP_END){
3517 if(flags & SSCP_END && possible_corruption)
3518 q_status_message(SM_ORDER, 3, 3, "There is possible data corruption, check the results");
3520 remember_the_yes = 0;
3521 possible_corruption = 0;
3522 ps_global->noshow_error = 0;
3523 ps_global->noshow_warn = 0;
3524 return(0);
3527 if(remember_the_yes){
3528 snprintf(prompt, sizeof(prompt),
3529 "Message to save shrank! (msg # %ld): Continuing", msgno);
3530 q_status_message(SM_ORDER, 0, 3, prompt);
3531 display_message('x');
3532 return(remember_the_yes);
3535 snprintf(prompt, sizeof(prompt),
3536 "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno);
3537 ret = radio_buttons(prompt, -FOOTER_ROWS(ps_global), save_size_opts,
3538 'n', 0, h_save_size_changed, RB_NORM|RB_NO_NEWMAIL);
3540 switch(ret){
3541 case 'a':
3542 remember_the_yes = 'y';
3543 possible_corruption++;
3544 return(remember_the_yes);
3546 case 'y':
3547 possible_corruption++;
3548 return('y');
3550 default:
3551 possible_corruption = 0;
3552 ps_global->noshow_error = 1;
3553 ps_global->noshow_warn = 1;
3554 break;
3557 return('n');
3561 /*----------------------------------------------------------------------
3562 Expunge_and_close callback that happens once the decision to expunge
3563 and close has been made and before expunging and closing begins
3566 Args: stream -- folder's stream
3567 folder -- name of folder containing folders
3568 deleted -- number of del'd msgs
3570 Result: 'y' to continue with expunge
3571 ----*/
3572 void
3573 expunge_and_close_begins(int flags, char *folder)
3575 if(!(flags & EC_NO_CLOSE)){
3576 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
3577 q_status_message1(SM_INFO, 0, 1, "Closing \"%.200s\"...", (char *) fname);
3578 flush_status_messages(1);
3579 if(fname) fs_give((void **)&fname);
3584 /*----------------------------------------------------------------------
3585 Export a message to a plain file in users home directory
3587 Args: state -- pointer to struct holding a bunch of pine state
3588 msgmap -- table mapping msg nums to c-client sequence nums
3589 qline -- screen line to ask questions on
3590 agg -- boolean indicating we're to operate on aggregate set
3592 Result:
3593 ----*/
3595 cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt)
3597 char filename[MAXPATH+1], full_filename[MAXPATH+1], *err;
3598 char nmsgs[80];
3599 int r, leading_nl, failure = 0, orig_errno = 0, rflags = GER_NONE;
3600 int flags = GE_IS_EXPORT | GE_SEQ_SENSITIVE, rv = 0;
3601 ENVELOPE *env;
3602 MESSAGECACHE *mc;
3603 BODY *b;
3604 long i, count = 0L, start_of_append = 0, rawno;
3605 gf_io_t pc;
3606 STORE_S *store;
3607 struct variable *vars = state ? ps_global->vars : NULL;
3608 ESCKEY_S export_opts[5];
3609 static HISTORY_S *history = NULL;
3611 if(ps_global->restricted){
3612 q_status_message(SM_ORDER, 0, 3,
3613 "Alpine demo can't export messages to files");
3614 return rv;
3617 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
3618 return rv;
3620 export_opts[i = 0].ch = ctrl('T');
3621 export_opts[i].rval = 10;
3622 export_opts[i].name = "^T";
3623 export_opts[i++].label = N_("To Files");
3625 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3626 if(ps_global->VAR_DOWNLOAD_CMD && ps_global->VAR_DOWNLOAD_CMD[0]){
3627 export_opts[i].ch = ctrl('V');
3628 export_opts[i].rval = 12;
3629 export_opts[i].name = "^V";
3630 /* TRANSLATORS: this is an abbreviation for Download Messages */
3631 export_opts[i++].label = N_("Downld Msg");
3633 #endif /* !(DOS || MAC) */
3635 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
3636 export_opts[i].ch = ctrl('I');
3637 export_opts[i].rval = 11;
3638 export_opts[i].name = "TAB";
3639 export_opts[i++].label = N_("Complete");
3642 #if 0
3643 /* Commented out since it's not yet support! */
3644 if(F_ON(F_ENABLE_SUB_LISTS,ps_global)){
3645 export_opts[i].ch = ctrl('X');
3646 export_opts[i].rval = 14;
3647 export_opts[i].name = "^X";
3648 export_opts[i++].label = N_("ListMatches");
3650 #endif
3653 * If message has attachments, add a toggle that will allow the user
3654 * to save all of the attachments to a single directory, using the
3655 * names provided with the attachments or part names. What we'll do is
3656 * export the message as usual, and then export the attachments into
3657 * a subdirectory that did not exist before. The subdir will be named
3658 * something based on the name of the file being saved to, but a
3659 * unique, new name.
3661 if(!MCMD_ISAGG(aopt)
3662 && state->mail_stream
3663 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3664 && rawno <= state->mail_stream->nmsgs
3665 && (env = pine_mail_fetchstructure(state->mail_stream, rawno, &b))
3666 && b
3667 && b->type == TYPEMULTIPART
3668 && b->subtype
3669 && strucmp(b->subtype, "ALTERNATIVE") != 0){
3670 PART *part;
3672 part = b->nested.part; /* 1st part */
3673 if(part && part->next)
3674 flags |= GE_ALLPARTS;
3677 export_opts[i].ch = -1;
3678 filename[0] = '\0';
3680 if(mn_total_cur(msgmap) <= 1L){
3681 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld", mn_get_cur(msgmap));
3682 nmsgs[sizeof(nmsgs)-1] = '\0';
3684 else{
3685 snprintf(nmsgs, sizeof(nmsgs), "%s messages", comatose(mn_total_cur(msgmap)));
3686 nmsgs[sizeof(nmsgs)-1] = '\0';
3689 r = get_export_filename(state, filename, NULL, full_filename,
3690 sizeof(filename), nmsgs, "EXPORT",
3691 export_opts, &rflags, qline, flags, &history);
3693 if(r < 0){
3694 switch(r){
3695 case -1:
3696 cmd_cancelled("Export message");
3697 break;
3699 case -2:
3700 q_status_message1(SM_ORDER, 0, 2,
3701 _("Can't export to file outside of %s"),
3702 VAR_OPER_DIR);
3703 break;
3706 goto fini;
3708 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3709 else if(r == 12){ /* Download */
3710 char cmd[MAXPATH], *tfp = NULL;
3711 int next = 0;
3712 PIPE_S *syspipe;
3713 STORE_S *so;
3714 gf_io_t pc;
3716 if(ps_global->restricted){
3717 q_status_message(SM_ORDER | SM_DING, 3, 3,
3718 "Download disallowed in restricted mode");
3719 goto fini;
3722 err = NULL;
3723 tfp = temp_nam(NULL, "pd");
3724 build_updown_cmd(cmd, sizeof(cmd), ps_global->VAR_DOWNLOAD_CMD_PREFIX,
3725 ps_global->VAR_DOWNLOAD_CMD, tfp);
3726 dprint((1, "Download cmd called: \"%s\"\n", cmd));
3727 if((so = so_get(FileStar, tfp, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){
3728 gf_set_so_writec(&pc, so);
3730 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
3731 if(!(state->mail_stream
3732 && (rawno = mn_m2raw(msgmap, i)) > 0L
3733 && rawno <= state->mail_stream->nmsgs
3734 && (mc = mail_elt(state->mail_stream, rawno))
3735 && mc->valid))
3736 mc = NULL;
3738 if(!(env = pine_mail_fetchstructure(state->mail_stream,
3739 mn_m2raw(msgmap, i), &b))
3740 || !bezerk_delimiter(env, mc, pc, next++)
3741 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
3742 env, b, NULL, FM_NEW_MESS | FM_NOWRAP, pc)){
3743 q_status_message(SM_ORDER | SM_DING, 3, 3,
3744 err = "Error writing tempfile for download");
3745 break;
3749 gf_clear_so_writec(so);
3750 if(so_give(&so)){ /* close file */
3751 if(!err)
3752 err = "Error writing tempfile for download";
3755 if(!err){
3756 if((syspipe = open_system_pipe(cmd, NULL, NULL,
3757 PIPE_USER | PIPE_RESET,
3758 0, pipe_callback, pipe_report_error)) != NULL)
3759 (void) close_system_pipe(&syspipe, NULL, pipe_callback);
3760 else
3761 q_status_message(SM_ORDER | SM_DING, 3, 3,
3762 err = _("Error running download command"));
3765 else
3766 q_status_message(SM_ORDER | SM_DING, 3, 3,
3767 err = "Error building temp file for download");
3769 if(tfp){
3770 our_unlink(tfp);
3771 fs_give((void **)&tfp);
3774 if(!err)
3775 q_status_message(SM_ORDER, 0, 3, _("Download Command Completed"));
3777 goto fini;
3779 #endif /* !(DOS || MAC) */
3782 if(rflags & GER_APPEND)
3783 leading_nl = 1;
3784 else
3785 leading_nl = 0;
3787 dprint((5, "Opening file \"%s\" for export\n",
3788 full_filename ? full_filename : "?"));
3790 if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE))){
3791 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3792 /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
3793 _("Error opening file \"%s\" to export message: %s"),
3794 full_filename, error_description(errno));
3795 goto fini;
3797 else
3798 gf_set_so_writec(&pc, store);
3800 err = NULL;
3801 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), count++){
3802 env = pine_mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i),
3803 &b);
3804 if(!env) {
3805 err = _("Can't export message. Error accessing mail folder");
3806 failure = 1;
3807 break;
3810 if(!(state->mail_stream
3811 && (rawno = mn_m2raw(msgmap, i)) > 0L
3812 && rawno <= state->mail_stream->nmsgs
3813 && (mc = mail_elt(state->mail_stream, rawno))
3814 && mc->valid))
3815 mc = NULL;
3817 start_of_append = so_tell(store);
3818 if(!bezerk_delimiter(env, mc, pc, leading_nl)
3819 || !format_message(mn_m2raw(msgmap, i), env, b, NULL,
3820 FM_NEW_MESS | FM_NOWRAP, pc)){
3821 orig_errno = errno; /* save in case things are really bad */
3822 failure = 1; /* pop out of here */
3823 break;
3826 leading_nl = 1;
3829 gf_clear_so_writec(store);
3830 if(so_give(&store)) /* release storage */
3831 failure++;
3833 if(failure){
3834 our_truncate(full_filename, (off_t)start_of_append);
3835 if(err){
3836 dprint((1, "FAILED Export: fetch(%ld): %s\n",
3837 i, err ? err : "?"));
3838 q_status_message(SM_ORDER | SM_DING, 3, 4, err);
3840 else{
3841 dprint((1, "FAILED Export: file \"%s\" : %s\n",
3842 full_filename ? full_filename : "?",
3843 error_description(orig_errno)));
3844 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3845 /* TRANSLATORS: Error exporting to <filename>: <error text> */
3846 _("Error exporting to \"%s\" : %s"),
3847 filename, error_description(orig_errno));
3850 else{
3851 if(rflags & GER_ALLPARTS && full_filename[0]){
3852 char dir[MAXPATH+1];
3853 char lfile[MAXPATH+1];
3854 int ok = 0, tries = 0, saved = 0, errs = 0, counter = 2;
3855 ATTACH_S *a;
3858 * Now we want to save all of the attachments to a subdirectory.
3859 * To make it easier for us and probably easier for the user, and
3860 * to prevent the user from shooting himself in the foot, we
3861 * make a new subdirectory so that we can't possibly step on
3862 * any existing files, and we don't need any interaction with the
3863 * user while saving.
3865 * We'll just use the directory name full_filename.d or if that
3866 * already exists and isn't empty, we'll try adding a suffix to
3867 * that until we get something to use.
3870 if(strlen(full_filename) + strlen(".d") + 1 > sizeof(dir)){
3871 q_status_message1(SM_ORDER | SM_DING, 3, 4,
3872 _("Can't save attachments, filename too long: %s"),
3873 full_filename);
3874 goto fini;
3877 ok = 0;
3878 snprintf(dir, sizeof(dir), "%.*s.d", MAXPATH-2, full_filename);
3879 dir[sizeof(dir)-1] = '\0';
3881 do {
3882 tries++;
3883 switch(r = is_writable_dir(dir)){
3884 case 0: /* exists and is a writable dir */
3886 * We could figure out if it is empty and use it in
3887 * that case, but that sounds like a lot of work, so
3888 * just fall through to default.
3891 default:
3892 if(strlen(full_filename) + strlen(".d") + 1 +
3893 1 + strlen(long2string((long) tries)) > sizeof(dir)){
3894 q_status_message(SM_ORDER | SM_DING, 3, 4,
3895 "Problem saving attachments");
3896 goto fini;
3899 snprintf(dir, sizeof(dir), "%.*s.d_%s", MAXPATH- (int) strlen(long2string((long) tries))-3, full_filename,
3900 long2string((long) tries));
3901 dir[sizeof(dir)-1] = '\0';
3902 break;
3904 case 3: /* doesn't exist, that's good! */
3905 /* make new directory */
3906 ok++;
3907 break;
3909 } while(!ok && tries < 1000);
3911 if(tries >= 1000){
3912 q_status_message(SM_ORDER | SM_DING, 3, 4,
3913 _("Problem saving attachments"));
3914 goto fini;
3917 /* create the new directory */
3918 if(our_mkdir(dir, 0700)){
3919 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3920 _("Problem saving attachments: %s: %s"), dir,
3921 error_description(errno));
3922 goto fini;
3925 if(!(state->mail_stream
3926 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3927 && rawno <= state->mail_stream->nmsgs
3928 && (env=pine_mail_fetchstructure(state->mail_stream,rawno,&b))
3929 && b)){
3930 q_status_message(SM_ORDER | SM_DING, 3, 4,
3931 _("Problem reading message"));
3932 goto fini;
3935 zero_atmts(state->atmts);
3936 describe_mime(b, "", 1, 1, 0, 0);
3938 a = state->atmts;
3939 if(a && a->description) /* skip main body part */
3940 a++;
3942 for(; a->description != NULL; a++){
3943 /* skip over these parts of the message */
3944 if(MIME_MSG_A(a) || MIME_DGST_A(a) || MIME_VCARD_A(a))
3945 continue;
3947 lfile[0] = '\0';
3948 (void) get_filename_parameter(lfile, sizeof(lfile), a->body, NULL);
3950 if(lfile[0] == '\0'){ /* MAXPATH + 1 = sizeof(lfile) */
3951 snprintf(lfile, sizeof(lfile), "part_%.*s", MAXPATH+1-6,
3952 a->number ? a->number : "?");
3953 lfile[sizeof(lfile)-1] = '\0';
3956 if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + 1
3957 > sizeof(filename)){
3958 dprint((2,
3959 "FAILED Att Export: name too long: %s\n",
3960 dir, S_FILESEP, lfile));
3961 errs++;
3962 continue;
3965 /* although files are being saved in a unique directory, there is
3966 * no guarantee that attachment names have unique names, so we have
3967 * to make sure that we are not constantly rewriting the same file name
3968 * over and over. In order to avoid this we test if the file already exists,
3969 * and if so, we write a counter name in the file name, just before the
3970 * extension of the file, and separate it with an underscore.
3972 snprintf(filename, sizeof(filename), "%.*s%.*s%.*s", (int) strlen(dir), dir,
3973 (int) strlen(S_FILESEP), S_FILESEP,
3974 MAXPATH - (int) strlen(dir) - (int) strlen(S_FILESEP), lfile);
3975 filename[sizeof(filename)-1] = '\0';
3976 while((ok = can_access(filename, ACCESS_EXISTS)) == 0 && errs == 0){
3977 char *ext, count[MAXPATH+1];
3978 unsigned long total;
3979 snprintf(count, sizeof(count), "%d", counter);
3980 if((ext = strrchr(lfile, '.')) != NULL)
3981 *ext = '\0';
3982 total = strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + strlen(count) + 3
3983 + (ext ? strlen(ext+1) : 0);
3984 if(total > sizeof(filename)){
3985 dprint((2,
3986 "FAILED Att Export: name too long: %s\n",
3987 dir, S_FILESEP, lfile));
3988 errs++;
3989 continue;
3991 snprintf(filename, sizeof(filename), "%.*s%.*s%.*s%.*s%.*d%.*s%.*s",
3992 (int) strlen(dir), dir, (int) strlen(S_FILESEP), S_FILESEP,
3993 (int) strlen(lfile), lfile,
3994 ext ? 1 : 0, ext ? "_" : "",
3995 (int) strlen(count), counter++,
3996 ext ? 1 : 0, ext ? "." : "",
3997 ext ? (int) (sizeof(filename) - total) : 0,
3998 ext ? ext+1 : "");
3999 filename[sizeof(filename)-1] = '\0';
4002 if(write_attachment_to_file(state->mail_stream, rawno,
4003 a, GER_NONE, filename) == 1)
4004 saved++;
4005 else
4006 errs++;
4009 if(errs){
4010 if(saved)
4011 q_status_message1(SM_ORDER, 3, 3,
4012 "Errors saving some attachments, %s attachments saved",
4013 long2string((long) saved));
4014 else
4015 q_status_message(SM_ORDER, 3, 3,
4016 _("Problems saving attachments"));
4018 else{
4019 if(saved)
4020 q_status_message2(SM_ORDER, 0, 3,
4021 /* TRANSLATORS: Saved <how many> attachments to <directory name> */
4022 _("Saved %s attachments to %s"),
4023 long2string((long) saved), dir);
4024 else
4025 q_status_message(SM_ORDER, 3, 3, _("No attachments to save"));
4028 else if(mn_total_cur(msgmap) > 1L)
4029 q_status_message4(SM_ORDER,0,3,
4030 "%s message%s %s to file \"%s\"",
4031 long2string(count), plural(count),
4032 rflags & GER_OVER
4033 ? "overwritten"
4034 : rflags & GER_APPEND ? "appended" : "exported",
4035 filename);
4036 else
4037 q_status_message3(SM_ORDER,0,3,
4038 "Message %s %s to file \"%s\"",
4039 long2string(mn_get_cur(msgmap)),
4040 rflags & GER_OVER
4041 ? "overwritten"
4042 : rflags & GER_APPEND ? "appended" : "exported",
4043 filename);
4044 rv++;
4047 fini:
4048 if(MCMD_ISAGG(aopt))
4049 restore_selected(msgmap);
4051 return rv;
4056 * Ask user what file to export to. Export from srcstore to that file.
4058 * Args ps -- pine struct
4059 * srctext -- pointer to source text
4060 * srctype -- type of that source text
4061 * prompt_msg -- see get_export_filename
4062 * lister_msg -- "
4064 * Returns: != 0 : error
4065 * 0 : ok
4068 simple_export(struct pine *ps, void *srctext, SourceType srctype, char *prompt_msg, char *lister_msg)
4070 int r = 1, rflags = GER_NONE;
4071 char filename[MAXPATH+1], full_filename[MAXPATH+1];
4072 STORE_S *store = NULL;
4073 struct variable *vars = ps ? ps->vars : NULL;
4074 static HISTORY_S *history = NULL;
4075 static ESCKEY_S simple_export_opts[] = {
4076 {ctrl('T'), 10, "^T", N_("To Files")},
4077 {-1, 0, NULL, NULL},
4078 {-1, 0, NULL, NULL}};
4080 if(F_ON(F_ENABLE_TAB_COMPLETE,ps)){
4081 simple_export_opts[r].ch = ctrl('I');
4082 simple_export_opts[r].rval = 11;
4083 simple_export_opts[r].name = "TAB";
4084 simple_export_opts[r].label = N_("Complete");
4087 if(!srctext){
4088 q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
4089 r = -3;
4090 goto fini;
4093 simple_export_opts[++r].ch = -1;
4094 filename[0] = '\0';
4095 full_filename[0] = '\0';
4097 r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename),
4098 prompt_msg, lister_msg, simple_export_opts, &rflags,
4099 -FOOTER_ROWS(ps), GE_IS_EXPORT, &history);
4101 if(r < 0)
4102 goto fini;
4103 else if(!full_filename[0]){
4104 r = -1;
4105 goto fini;
4108 dprint((5, "Opening file \"%s\" for export\n",
4109 full_filename ? full_filename : "?"));
4111 if((store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
4112 char *pipe_err;
4113 gf_io_t pc, gc;
4115 gf_set_so_writec(&pc, store);
4116 gf_set_readc(&gc, srctext, (srctype == CharStar)
4117 ? strlen((char *)srctext)
4118 : 0L,
4119 srctype, 0);
4120 gf_filter_init();
4121 if((pipe_err = gf_pipe(gc, pc)) != NULL){
4122 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4123 /* TRANSLATORS: Problem saving to <filename>: <error text> */
4124 _("Problem saving to \"%s\": %s"),
4125 filename, pipe_err);
4126 r = -3;
4128 else
4129 r = 0;
4131 gf_clear_so_writec(store);
4132 if(so_give(&store)){
4133 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4134 _("Problem saving to \"%s\": %s"),
4135 filename, error_description(errno));
4136 r = -3;
4139 else{
4140 q_status_message2(SM_ORDER | SM_DING, 3, 4,
4141 _("Error opening file \"%s\" for export: %s"),
4142 full_filename, error_description(errno));
4143 r = -3;
4146 fini:
4147 switch(r){
4148 case 0:
4149 /* overloading full_filename */
4150 snprintf(full_filename, sizeof(full_filename), "%c%s",
4151 (prompt_msg && prompt_msg[0])
4152 ? (islower((unsigned char)prompt_msg[0])
4153 ? toupper((unsigned char)prompt_msg[0]) : prompt_msg[0])
4154 : 'T',
4155 (prompt_msg && prompt_msg[0]) ? prompt_msg+1 : "ext");
4156 full_filename[sizeof(full_filename)-1] = '\0';
4157 q_status_message3(SM_ORDER,0,2,"%s %s to \"%s\"",
4158 full_filename,
4159 rflags & GER_OVER
4160 ? "overwritten"
4161 : rflags & GER_APPEND ? "appended" : "exported",
4162 filename);
4163 break;
4165 case -1:
4166 cmd_cancelled("Export");
4167 break;
4169 case -2:
4170 q_status_message1(SM_ORDER, 0, 2,
4171 _("Can't export to file outside of %s"), VAR_OPER_DIR);
4172 break;
4175 ps->mangled_footer = 1;
4176 return(r);
4181 * Ask user what file to export to.
4183 * filename -- On input, this is the filename to start with. On exit,
4184 * this is the filename chosen. (but this isn't used)
4185 * deefault -- This is the default value if user hits return. The
4186 * prompt will have [deefault] added to it automatically.
4187 * full_filename -- This is the full filename on exit.
4188 * len -- Minimum length of _both_ filename and full_filename.
4189 * prompt_msg -- Message to insert in prompt.
4190 * lister_msg -- Message to insert in file_lister.
4191 * opts -- Key options.
4192 * There is a tangled relationship between the callers
4193 * and this routine as far as opts are concerned. Some
4194 * of the opts are handled here. In particular, r == 3,
4195 * r == 10, r == 11, and r == 13 are all handled here.
4196 * Don't use those values unless you want what happens
4197 * here. r == 12 and others are handled by the caller.
4198 * rflags -- Return flags
4199 * GER_OVER - overwrite of existing file
4200 * GER_APPEND - append of existing file
4201 * else file did not exist before
4203 * GER_ALLPARTS - AllParts toggle was turned on
4205 * qline -- Command line to prompt on.
4206 * flags -- Logically OR'd flags
4207 * GE_IS_EXPORT - The command was an Export command
4208 * so the prompt should include
4209 * EXPORT:.
4210 * GE_SEQ_SENSITIVE - The command that got us here is
4211 * sensitive to sequence number changes
4212 * caused by unsolicited expunges.
4213 * GE_NO_APPEND - We will not allow append to an
4214 * existing file, only removal of the
4215 * file if it exists.
4216 * GE_IS_IMPORT - We are selecting for reading.
4217 * No overwriting or checking for
4218 * existence at all. Don't use this
4219 * together with GE_NO_APPEND.
4220 * GE_ALLPARTS - Turn on AllParts toggle.
4221 * GE_BINARY - Turn on Binary toggle.
4223 * Returns: -1 cancelled
4224 * -2 prohibited by VAR_OPER_DIR
4225 * -3 other error, already reported here
4226 * 0 ok
4227 * 12 user chose 12 command from opts
4230 get_export_filename(struct pine *ps, char *filename, char *deefault,
4231 char *full_filename, size_t len, char *prompt_msg,
4232 char *lister_msg, ESCKEY_S *optsarg, int *rflags,
4233 int qline, int flags, HISTORY_S **history)
4235 char dir[MAXPATH+1], dir2[MAXPATH+1], orig_dir[MAXPATH+1];
4236 char precolon[MAXPATH+1], postcolon[MAXPATH+1];
4237 char filename2[MAXPATH+1], tmp[MAXPATH+1], *fn, *ill;
4238 int l, i, ku = -1, kp = -1, r, fatal, homedir = 0, was_abs_path=0, avail, ret = 0;
4239 int allparts = 0, binary = 0;
4240 char prompt_buf[400];
4241 char def[500];
4242 ESCKEY_S *opts = NULL;
4243 struct variable *vars = ps->vars;
4244 static HISTORY_S *dir_hist = NULL;
4245 static char *last;
4246 int pos, hist_len = 0;
4249 /* we will fake a history with the ps_global->VAR_HISTORY variable
4250 * We fake that we combine this variable into a history variable
4251 * by stacking VAR_HISTORY on top of dir_hist. We keep track of this
4252 * by looking at the variable pos.
4254 if(ps_global->VAR_HISTORY != NULL)
4255 for(hist_len = 0; ps_global->VAR_HISTORY[hist_len]
4256 && ps_global->VAR_HISTORY[hist_len][0]; hist_len++)
4259 pos = hist_len + items_in_hist(dir_hist);
4261 if(flags & GE_ALLPARTS || history || dir_hist){
4263 * Copy the opts and add one to the end of the list.
4265 for(i = 0; optsarg[i].ch != -1; i++)
4268 if(dir_hist || hist_len > 0)
4269 i += 2;
4271 if(history)
4272 i += dir_hist || hist_len > 0 ? 2 : 4;
4274 if(flags & GE_ALLPARTS)
4275 i++;
4277 if(flags & GE_BINARY)
4278 i++;
4280 opts = (ESCKEY_S *) fs_get((i+1) * sizeof(*opts));
4281 memset(opts, 0, (i+1) * sizeof(*opts));
4283 for(i = 0; optsarg[i].ch != -1; i++){
4284 opts[i].ch = optsarg[i].ch;
4285 opts[i].rval = optsarg[i].rval;
4286 opts[i].name = optsarg[i].name; /* no need to make a copy */
4287 opts[i].label = optsarg[i].label; /* " */
4290 if(flags & GE_ALLPARTS){
4291 allparts = i;
4292 opts[i].ch = ctrl('P');
4293 opts[i].rval = 13;
4294 opts[i].name = "^P";
4295 /* TRANSLATORS: Export all attachment parts */
4296 opts[i++].label = N_("AllParts");
4299 if(flags & GE_BINARY){
4300 binary = i;
4301 opts[i].ch = ctrl('R');
4302 opts[i].rval = 15;
4303 opts[i].name = "^R";
4304 opts[i++].label = N_("Binary");
4307 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4308 SIZEOF_20KBUF, filename);
4309 #ifndef _WINDOWS
4310 /* In the Windows operating system we always return the UTF8 encoded name */
4311 if(strcmp(tmp_20k_buf, filename)){
4312 opts[i].ch = ctrl('N');
4313 opts[i].rval = 40;
4314 opts[i].name = "^N";
4315 opts[i++].label = "Name UTF8";
4317 #else
4318 strncpy(filename, tmp_20k_buf, len);
4319 filename[len-1] = '\0';
4320 #endif /* _WINDOWS */
4322 if(dir_hist || hist_len > 0){
4323 opts[i].ch = ctrl('Y');
4324 opts[i].rval = 32;
4325 opts[i].name = "";
4326 kp = i;
4327 opts[i++].label = "";
4329 opts[i].ch = ctrl('V');
4330 opts[i].rval = 33;
4331 opts[i].name = "";
4332 opts[i++].label = "";
4335 if(history){
4336 opts[i].ch = KEY_UP;
4337 opts[i].rval = 30;
4338 opts[i].name = "";
4339 ku = i;
4340 opts[i++].label = "";
4342 opts[i].ch = KEY_DOWN;
4343 opts[i].rval = 31;
4344 opts[i].name = "";
4345 opts[i++].label = "";
4348 opts[i].ch = -1;
4350 if(history)
4351 init_hist(history, HISTSIZE);
4352 init_hist(&dir_hist, HISTSIZE); /* reset history to the end */
4354 else
4355 opts = optsarg;
4357 if(rflags)
4358 *rflags = GER_NONE;
4360 if(F_ON(F_USE_CURRENT_DIR, ps))
4361 dir[0] = '\0';
4362 else if(VAR_OPER_DIR){
4363 strncpy(dir, VAR_OPER_DIR, sizeof(dir));
4364 dir[sizeof(dir)-1] = '\0';
4366 #if defined(DOS) || defined(OS2)
4367 else if(VAR_FILE_DIR){
4368 strncpy(dir, VAR_FILE_DIR, sizeof(dir));
4369 dir[sizeof(dir)-1] = '\0';
4371 #endif
4372 else{
4373 dir[0] = '~';
4374 dir[1] = '\0';
4375 homedir=1;
4377 strncpy(orig_dir, dir, sizeof(orig_dir));
4378 orig_dir[sizeof(orig_dir)-1] = '\0';
4380 postcolon[0] = '\0';
4381 strncpy(precolon, dir, sizeof(precolon));
4382 precolon[sizeof(precolon)-1] = '\0';
4383 if(deefault){
4384 strncpy(def, deefault, sizeof(def)-1);
4385 def[sizeof(def)-1] = '\0';
4386 removing_leading_and_trailing_white_space(def);
4388 else
4389 def[0] = '\0';
4391 avail = MAX(20, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - MIN_OPT_ENT_WIDTH;
4393 /*---------- Prompt the user for the file name -------------*/
4394 while(1){
4395 int oeflags;
4396 char dirb[50], fileb[50];
4397 int l1, l2, l3, l4, l5, needed;
4398 char *p, p1[100], p2[100], *p3, p4[100], p5[100];
4400 snprintf(p1, sizeof(p1), "%sCopy ",
4401 (flags & GE_IS_EXPORT) ? "EXPORT: " :
4402 (flags & GE_IS_IMPORT) ? "IMPORT: " : "SAVE: ");
4403 p1[sizeof(p1)-1] = '\0';
4404 l1 = strlen(p1);
4406 strncpy(p2, prompt_msg ? prompt_msg : "", sizeof(p2)-1);
4407 p2[sizeof(p2)-1] = '\0';
4408 l2 = strlen(p2);
4410 if(rflags && *rflags & GER_ALLPARTS)
4411 p3 = " (and atts)";
4412 else
4413 p3 = "";
4415 l3 = strlen(p3);
4417 snprintf(p4, sizeof(p4), " %s file%s%s",
4418 (flags & GE_IS_IMPORT) ? "from" : "to",
4419 is_absolute_path(filename) ? "" : " in ",
4420 is_absolute_path(filename) ? "" :
4421 (!dir[0] ? "current directory"
4422 : (dir[0] == '~' && !dir[1]) ? "home directory"
4423 : short_str(dir,dirb,sizeof(dirb),30,FrontDots)));
4424 p4[sizeof(p4)-1] = '\0';
4425 l4 = strlen(p4);
4427 snprintf(p5, sizeof(p5), "%s%s%s: ",
4428 *def ? " [" : "",
4429 *def ? short_str(def,fileb,sizeof(fileb),40,EndDots) : "",
4430 *def ? "]" : "");
4431 p5[sizeof(p5)-1] = '\0';
4432 l5 = strlen(p5);
4434 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4435 snprintf(p4, sizeof(p4), " %s file%s%s",
4436 (flags & GE_IS_IMPORT) ? "from" : "to",
4437 is_absolute_path(filename) ? "" : " in ",
4438 is_absolute_path(filename) ? "" :
4439 (!dir[0] ? "current dir"
4440 : (dir[0] == '~' && !dir[1]) ? "home dir"
4441 : short_str(dir,dirb,sizeof(dirb),10,FrontDots)));
4442 p4[sizeof(p4)-1] = '\0';
4443 l4 = strlen(p4);
4446 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4447 snprintf(p5, sizeof(p5), "%s%s%s: ",
4448 *def ? " [" : "",
4449 *def ? short_str(def,fileb,sizeof(fileb),
4450 MAX(15,l5-5-needed),EndDots) : "",
4451 *def ? "]" : "");
4452 p5[sizeof(p5)-1] = '\0';
4453 l5 = strlen(p5);
4456 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l2 > 0){
4459 * 14 is about the shortest we can make this, because there are
4460 * fixed length strings of length 14 coming in here.
4462 p = short_str(prompt_msg, p2, sizeof(p2), MAX(14,l2-needed), FrontDots);
4463 if(p != p2){
4464 strncpy(p2, p, sizeof(p2)-1);
4465 p2[sizeof(p2)-1] = '\0';
4468 l2 = strlen(p2);
4471 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4472 strncpy(p1, "Copy ", sizeof(p1)-1);
4473 p1[sizeof(p1)-1] = '\0';
4474 l1 = strlen(p1);
4477 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4478 snprintf(p5, sizeof(p5), "%s%s%s: ",
4479 *def ? " [" : "",
4480 *def ? short_str(def,fileb, sizeof(fileb),
4481 MAX(10,l5-5-needed),EndDots) : "",
4482 *def ? "]" : "");
4483 p5[sizeof(p5)-1] = '\0';
4484 l5 = strlen(p5);
4487 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l3 > 0){
4488 if(needed <= l3 - strlen(" (+ atts)"))
4489 p3 = " (+ atts)";
4490 else if(needed <= l3 - strlen(" (atts)"))
4491 p3 = " (atts)";
4492 else if(needed <= l3 - strlen(" (+)"))
4493 p3 = " (+)";
4494 else if(needed <= l3 - strlen("+"))
4495 p3 = "+";
4496 else
4497 p3 = "";
4499 l3 = strlen(p3);
4502 snprintf(prompt_buf, sizeof(prompt_buf), "%s%s%s%s%s", p1, p2, p3, p4, p5);
4503 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4505 if(kp >= 0){
4506 if(items_in_hist(dir_hist) > 0 || hist_len > 0){ /* any directories */
4507 opts[kp].name = "^Y";
4508 opts[kp].label = "Prev Dir";
4509 opts[kp+1].name = "^V";
4510 opts[kp+1].label = "Next Dir";
4512 else{
4513 opts[kp].name = "";
4514 opts[kp].label = "";
4515 opts[kp+1].name = "";
4516 opts[kp+1].label = "";
4520 if(ku >= 0){
4521 if(items_in_hist(*history) > 0){
4522 opts[ku].name = HISTORY_UP_KEYNAME;
4523 opts[ku].label = HISTORY_KEYLABEL;
4524 opts[ku+1].name = HISTORY_DOWN_KEYNAME;
4525 opts[ku+1].label = HISTORY_KEYLABEL;
4527 else{
4528 opts[ku].name = "";
4529 opts[ku].label = "";
4530 opts[ku+1].name = "";
4531 opts[ku+1].label = "";
4535 oeflags = OE_APPEND_CURRENT |
4536 ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0);
4537 r = optionally_enter(filename, qline, 0, len, prompt_buf,
4538 opts, NO_HELP, &oeflags);
4540 dprint((2, "\n - export_filename = \"%s\", r = %d -\n", filename, r));
4541 /*--- Help ----*/
4542 if(r == 3){
4544 * Helps may not be right if you add another caller or change
4545 * things. Check it out.
4547 if(flags & GE_IS_IMPORT)
4548 helper(h_ge_import, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE);
4549 else if(flags & GE_ALLPARTS)
4550 helper(h_ge_allparts, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4551 else
4552 helper(h_ge_export, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4554 ps->mangled_screen = 1;
4556 continue;
4558 else if(r == 10 || r == 11){ /* Browser or File Completion */
4559 if(filename[0]=='~'){
4560 if(filename[1] == C_FILESEP && filename[2]!='\0'){
4561 precolon[0] = '~';
4562 precolon[1] = '\0';
4563 for(i=0; filename[i+2] != '\0' && i+2 < len-1; i++)
4564 filename[i] = filename[i+2];
4565 filename[i] = '\0';
4566 strncpy(dir, precolon, sizeof(dir)-1);
4567 dir[sizeof(dir)-1] = '\0';
4569 else if(filename[1]=='\0' ||
4570 (filename[1] == C_FILESEP && filename[2] == '\0')){
4571 precolon[0] = '~';
4572 precolon[1] = '\0';
4573 filename[0] = '\0';
4574 strncpy(dir, precolon, sizeof(dir)-1);
4575 dir[sizeof(dir)-1] = '\0';
4578 else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){
4579 if(homedir){
4580 precolon[0] = '~';
4581 precolon[1] = '\0';
4582 strncpy(dir, precolon, sizeof(dir)-1);
4583 dir[sizeof(dir)-1] = '\0';
4585 else{
4586 precolon[0] = '\0';
4587 dir[0] = '\0';
4590 l = MAXPATH;
4591 dir2[0] = '\0';
4592 strncpy(tmp, filename, sizeof(tmp)-1);
4593 tmp[sizeof(tmp)-1] = '\0';
4594 if(*tmp && is_absolute_path(tmp))
4595 fnexpand(tmp, sizeof(tmp));
4596 if(strncmp(tmp,postcolon, strlen(postcolon)))
4597 postcolon[0] = '\0';
4599 if(*tmp && (fn = last_cmpnt(tmp))){
4600 l -= fn - tmp;
4601 strncpy(filename2, fn, sizeof(filename2)-1);
4602 filename2[sizeof(filename2)-1] = '\0';
4603 if(is_absolute_path(tmp)){
4604 strncpy(dir2, tmp, MIN(fn - tmp, sizeof(dir2)-1));
4605 dir2[MIN(fn - tmp, sizeof(dir2)-1)] = '\0';
4606 #ifdef _WINDOWS
4607 if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){
4608 dir2[2] = '\\';
4609 dir2[3] = '\0';
4611 #endif
4612 strncpy(postcolon, dir2, sizeof(postcolon)-1);
4613 postcolon[sizeof(postcolon)-1] = '\0';
4614 precolon[0] = '\0';
4616 else{
4617 char *p = NULL;
4619 * Just building the directory name in dir2,
4620 * full_filename is overloaded.
4622 snprintf(full_filename, len, "%.*s", (int) MIN(fn-tmp,len-1), tmp);
4623 full_filename[len-1] = '\0';
4624 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4625 postcolon[sizeof(postcolon)-1] = '\0';
4626 build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH)
4627 : (dir[0] == '~' && !dir[1])
4628 ? ps->home_dir
4629 : dir,
4630 full_filename, sizeof(dir2));
4631 if(p)
4632 free(p);
4635 else{
4636 if(is_absolute_path(tmp)){
4637 strncpy(dir2, tmp, sizeof(dir2)-1);
4638 dir2[sizeof(dir2)-1] = '\0';
4639 #ifdef _WINDOWS
4640 if(dir2[2]=='\0' && dir2[1]==':'){
4641 dir2[2]='\\';
4642 dir2[3]='\0';
4643 strncpy(postcolon,dir2,sizeof(postcolon)-1);
4644 postcolon[sizeof(postcolon)-1] = '\0';
4646 #endif
4647 filename2[0] = '\0';
4648 precolon[0] = '\0';
4650 else{
4651 strncpy(filename2, tmp, sizeof(filename2)-1);
4652 filename2[sizeof(filename2)-1] = '\0';
4653 if(!dir[0]){
4654 if(getcwd(dir2, sizeof(dir2)) == NULL)
4655 alpine_panic(_("getcwd() call failed at get_export_filename"));
4657 else if(dir[0] == '~' && !dir[1]){
4658 strncpy(dir2, ps->home_dir, sizeof(dir2)-1);
4659 dir2[sizeof(dir2)-1] = '\0';
4661 else{
4662 strncpy(dir2, dir, sizeof(dir2)-1);
4663 dir2[sizeof(dir2)-1] = '\0';
4666 postcolon[0] = '\0';
4670 build_path(full_filename, dir2, filename2, len);
4671 if(!strcmp(full_filename, dir2))
4672 filename2[0] = '\0';
4673 if(full_filename[strlen(full_filename)-1] == C_FILESEP
4674 && isdir(full_filename,NULL,NULL)){
4675 if(strlen(full_filename) == 1)
4676 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4677 else if(filename2[0])
4678 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4679 postcolon[sizeof(postcolon)-1] = '\0';
4680 strncpy(dir2, full_filename, sizeof(dir2)-1);
4681 dir2[sizeof(dir2)-1] = '\0';
4682 filename2[0] = '\0';
4684 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4685 else if(full_filename[strlen(full_filename)-1] == C_FILESEP){
4686 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4687 postcolon[sizeof(postcolon)-1] = '\0';
4688 strncpy(dir2, full_filename, sizeof(dir2)-1);
4689 dir2[sizeof(dir2)-1] = '\0';
4690 filename2[0] = '\0';
4692 #endif
4693 if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1
4694 && strcmp(dir2+1, ":\\"))
4695 /* last condition to prevent stripping of '\\'
4696 in windows partition */
4697 dir2[strlen(dir2)-1] = '\0';
4699 if(r == 10){ /* File Browser */
4700 r = file_lister(lister_msg ? lister_msg : "EXPORT",
4701 dir2, sizeof(dir2), filename2, sizeof(filename2),
4702 TRUE,
4703 (flags & GE_IS_IMPORT) ? FB_READ : FB_SAVE);
4704 #ifdef _WINDOWS
4705 /* Windows has a special "feature" in which entering the file browser will
4706 change the working directory if the directory is changed at all (even
4707 clicking "Cancel" will change the working directory).
4709 if(F_ON(F_USE_CURRENT_DIR, ps))
4710 (void)getcwd(dir2,sizeof(dir2));
4711 #endif
4712 if(isdir(dir2,NULL,NULL)){
4713 strncpy(precolon, dir2, sizeof(precolon)-1);
4714 precolon[sizeof(precolon)-1] = '\0';
4716 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4717 postcolon[sizeof(postcolon)-1] = '\0';
4718 if(r == 1){
4719 build_path(full_filename, dir2, filename2, len);
4720 if(isdir(full_filename, NULL, NULL)){
4721 strncpy(dir, full_filename, sizeof(dir)-1);
4722 dir[sizeof(dir)-1] = '\0';
4723 filename[0] = '\0';
4725 else{
4726 fn = last_cmpnt(full_filename);
4727 strncpy(dir, full_filename,
4728 MIN(fn - full_filename, sizeof(dir)-1));
4729 dir[MIN(fn - full_filename, sizeof(dir)-1)] = '\0';
4730 if(fn - full_filename > 1)
4731 dir[fn - full_filename - 1] = '\0';
4734 if(!strcmp(dir, ps->home_dir)){
4735 dir[0] = '~';
4736 dir[1] = '\0';
4739 strncpy(filename, fn, len-1);
4740 filename[len-1] = '\0';
4743 else{ /* File Completion */
4744 if(!pico_fncomplete(dir2, filename2, sizeof(filename2)))
4745 Writechar(BELL, 0);
4746 strncat(postcolon, filename2,
4747 sizeof(postcolon)-1-strlen(postcolon));
4748 postcolon[sizeof(postcolon)-1] = '\0';
4750 was_abs_path = is_absolute_path(filename);
4752 if(!strcmp(dir, ps->home_dir)){
4753 dir[0] = '~';
4754 dir[1] = '\0';
4757 strncpy(filename, postcolon, len-1);
4758 filename[len-1] = '\0';
4759 strncpy(dir, precolon, sizeof(dir)-1);
4760 dir[sizeof(dir)-1] = '\0';
4762 if(filename[0] == '~' && !filename[1]){
4763 dir[0] = '~';
4764 dir[1] = '\0';
4765 filename[0] = '\0';
4768 continue;
4770 else if(r == 12){ /* Download, caller handles it */
4771 ret = r;
4772 goto done;
4774 else if(r == 13){ /* toggle AllParts bit */
4775 if(rflags){
4776 if(*rflags & GER_ALLPARTS){
4777 *rflags &= ~GER_ALLPARTS;
4778 opts[allparts].label = N_("AllParts");
4780 else{
4781 *rflags |= GER_ALLPARTS;
4782 /* opposite of All Parts, No All Parts */
4783 opts[allparts].label = N_("NoAllParts");
4787 continue;
4789 #if 0
4790 else if(r == 14){ /* List file names matching partial? */
4791 continue;
4793 #endif
4794 else if(r == 15){ /* toggle Binary bit */
4795 if(rflags){
4796 if(*rflags & GER_BINARY){
4797 *rflags &= ~GER_BINARY;
4798 opts[binary].label = N_("Binary");
4800 else{
4801 *rflags |= GER_BINARY;
4802 opts[binary].label = N_("No Binary");
4806 continue;
4808 else if(r == 1){ /* Cancel */
4809 ret = -1;
4810 goto done;
4812 else if(r == 4){
4813 continue;
4815 else if(r >= 30 && r <= 33){
4816 char *p = NULL;
4818 if(r == 30 || r == 31){
4819 if(history){
4820 if(r == 30)
4821 p = get_prev_hist(*history, filename, 0, NULL);
4822 else if (r == 31)
4823 p = get_next_hist(*history, filename, 0, NULL);
4827 if(r == 32 || r == 33){
4828 int nitems = items_in_hist(dir_hist);
4829 if(dir_hist || hist_len > 0){
4830 if(r == 32){
4831 if(pos > 0)
4832 p = hist_in_pos(--pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4833 else p = last;
4835 else if (r == 33){
4836 if(pos < hist_len + nitems)
4837 p = hist_in_pos(++pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4839 if(p == NULL || *p == '\0')
4840 p = orig_dir;
4843 last = p; /* save it! */
4845 if(p != NULL && *p != '\0'){
4846 if(r == 30 || r == 31){
4847 if((fn = last_cmpnt(p)) != NULL){
4848 strncpy(dir, p, MIN(fn - p, sizeof(dir)-1));
4849 dir[MIN(fn - p, sizeof(dir)-1)] = '\0';
4850 if(fn - p > 1)
4851 dir[fn - p - 1] = '\0';
4852 strncpy(filename, fn, len-1);
4853 filename[len-1] = '\0';
4855 } else { /* r == 32 || r == 33 */
4856 strncpy(dir, p, sizeof(dir)-1);
4857 dir[sizeof(dir)-1] = '\0';
4860 if(!strcmp(dir, ps->home_dir)){
4861 dir[0] = '~';
4862 dir[1] = '\0';
4865 else
4866 Writechar(BELL, 0);
4867 continue;
4869 #ifndef _WINDOWS
4870 else if(r == 40){
4871 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4872 SIZEOF_20KBUF, filename);
4873 strncpy(filename, tmp_20k_buf, len);
4874 filename[len-1] = '\0';
4875 continue;
4877 #endif /* _WINDOWS */
4878 else if(r != 0){
4879 Writechar(BELL, 0);
4880 continue;
4883 removing_leading_and_trailing_white_space(filename);
4885 if(!*filename){
4886 if(!*def){ /* Cancel */
4887 ret = -1;
4888 goto done;
4891 strncpy(filename, def, len-1);
4892 filename[len-1] = '\0';
4895 #if defined(DOS) || defined(OS2)
4896 if(is_absolute_path(filename)){
4897 fixpath(filename, len);
4899 #else
4900 if(filename[0] == '~'){
4901 if(fnexpand(filename, len) == NULL){
4902 char *p = strindex(filename, '/');
4903 if(p != NULL)
4904 *p = '\0';
4905 q_status_message1(SM_ORDER | SM_DING, 3, 3,
4906 _("Error expanding file name: \"%s\" unknown user"),
4907 filename);
4908 continue;
4911 #endif
4913 if(is_absolute_path(filename)){
4914 strncpy(full_filename, filename, len-1);
4915 full_filename[len-1] = '\0';
4917 else{
4918 if(!dir[0])
4919 build_path(full_filename, (char *)getcwd(dir,sizeof(dir)),
4920 filename, len);
4921 else if(dir[0] == '~' && !dir[1])
4922 build_path(full_filename, ps->home_dir, filename, len);
4923 else
4924 build_path(full_filename, dir, filename, len);
4927 if((ill = filter_filename(full_filename, &fatal,
4928 ps_global->restricted || ps_global->VAR_OPER_DIR)) != NULL){
4929 if(fatal){
4930 q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill);
4931 continue;
4933 else{
4934 /* BUG: we should beep when the key's pressed rather than bitch later */
4935 /* Warn and ask for confirmation. */
4936 snprintf(prompt_buf, sizeof(prompt_buf), "File name contains a '%s'. %s anyway",
4937 ill, (flags & GE_IS_EXPORT) ? "Export" : "Save");
4938 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4939 if(want_to(prompt_buf, 'n', 0, NO_HELP,
4940 ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y')
4941 continue;
4945 break; /* Must have got an OK file name */
4948 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){
4949 ret = -2;
4950 goto done;
4953 if(!can_access(full_filename, ACCESS_EXISTS)){
4954 int rbflags;
4955 static ESCKEY_S access_opts[] = {
4956 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4957 a file or append to the end of the file */
4958 {'o', 'o', "O", N_("Overwrite")},
4959 {'a', 'a', "A", N_("Append")},
4960 {-1, 0, NULL, NULL}};
4962 rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0);
4964 if(flags & GE_NO_APPEND){
4965 r = strlen(filename);
4966 snprintf(prompt_buf, sizeof(prompt_buf),
4967 /* TRANSLATORS: asking user whether to overwrite a file or not,
4968 File <filename> already exists. Overwrite it ? */
4969 _("File \"%s%s\" already exists. Overwrite it "),
4970 (r > 20) ? "..." : "",
4971 filename + ((r > 20) ? r - 20 : 0));
4972 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4973 if(want_to(prompt_buf, 'n', 'x', NO_HELP, rbflags) == 'y'){
4974 if(rflags)
4975 *rflags |= GER_OVER;
4977 if(our_unlink(full_filename) < 0){
4978 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4979 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4980 _("Cannot remove old %s: %s"),
4981 full_filename, error_description(errno));
4984 else{
4985 ret = -1;
4986 goto done;
4989 else if(!(flags & GE_IS_IMPORT)){
4990 r = strlen(filename);
4991 snprintf(prompt_buf, sizeof(prompt_buf),
4992 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4993 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4994 (r > 20) ? "..." : "",
4995 filename + ((r > 20) ? r - 20 : 0));
4996 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4997 switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global),
4998 access_opts, 'a', 'x', NO_HELP, rbflags)){
4999 case 'o' :
5000 if(rflags)
5001 *rflags |= GER_OVER;
5003 if(our_truncate(full_filename, (off_t)0) < 0)
5004 /* trouble truncating, but we'll give it a try anyway */
5005 q_status_message2(SM_ORDER | SM_DING, 3, 5,
5006 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
5007 _("Warning: Cannot truncate old %s: %s"),
5008 full_filename, error_description(errno));
5009 break;
5011 case 'a' :
5012 if(rflags)
5013 *rflags |= GER_APPEND;
5015 break;
5017 case 'x' :
5018 default :
5019 ret = -1;
5020 goto done;
5025 done:
5026 if(history && ret == 0){
5027 save_hist(*history, full_filename, 0, NULL);
5028 strncpy(tmp, full_filename, MAXPATH);
5029 tmp[MAXPATH] = '\0';
5030 if((fn = strrchr(tmp, C_FILESEP)) != NULL)
5031 *fn = '\0';
5032 else
5033 tmp[0] = '\0';
5034 if(tmp[0])
5035 save_hist(dir_hist, tmp, 0, NULL);
5038 if(opts && opts != optsarg)
5039 fs_give((void **) &opts);
5041 return(ret);
5045 /*----------------------------------------------------------------------
5046 parse the config'd upload/download command
5048 Args: cmd -- buffer to return command fit for shellin'
5049 prefix --
5050 cfg_str --
5051 fname -- file name to build into the command
5053 Returns: pointer to cmd_str buffer or NULL on real bad error
5055 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
5056 cfg_str is written to standard out right before a successful
5057 return of this function. The call immediately following this
5058 function darn well better be the shell exec...
5059 ----*/
5060 char *
5061 build_updown_cmd(char *cmd, size_t cmdlen, char *prefix, char *cfg_str, char *fname)
5063 char *p;
5064 int fname_found = 0;
5066 if(prefix && *prefix){
5067 /* loop thru replacing all occurrences of _FILE_ */
5068 p = strncpy(cmd, prefix, cmdlen);
5069 cmd[cmdlen-1] = '\0';
5070 while((p = strstr(p, "_FILE_")))
5071 rplstr(p, cmdlen-(p-cmd), 6, fname);
5073 fputs(cmd, stdout);
5076 /* loop thru replacing all occurrences of _FILE_ */
5077 p = strncpy(cmd, cfg_str, cmdlen);
5078 cmd[cmdlen-1] = '\0';
5079 while((p = strstr(p, "_FILE_"))){
5080 rplstr(p, cmdlen-(p-cmd), 6, fname);
5081 fname_found = 1;
5084 if(!fname_found)
5085 snprintf(cmd+strlen(cmd), cmdlen-strlen(cmd), " %s", fname);
5087 cmd[cmdlen-1] = '\0';
5089 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
5090 cmd ? cmd : "?"));
5091 return(cmd);
5095 /*----------------------------------------------------------------------
5096 Write a berzerk format message delimiter using the given putc function
5098 Args: e -- envelope of message to write
5099 pc -- function to use
5101 Returns: TRUE if we could write it, FALSE if there was a problem
5103 NOTE: follows delimiter with OS-dependent newline
5104 ----*/
5106 bezerk_delimiter(ENVELOPE *env, MESSAGECACHE *mc, gf_io_t pc, int leading_newline)
5108 MESSAGECACHE telt;
5109 time_t when;
5110 char *p;
5112 /* write "[\n]From mailbox[@host] " */
5113 if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1)
5114 && gf_puts("From ", pc)
5115 && gf_puts((env && env->from) ? env->from->mailbox
5116 : "the-concourse-on-high", pc)
5117 && gf_puts((env && env->from && env->from->host) ? "@" : "", pc)
5118 && gf_puts((env && env->from && env->from->host) ? env->from->host
5119 : "", pc)
5120 && (*pc)(' ')))
5121 return(0);
5123 if(mc && mc->valid)
5124 when = mail_longdate(mc);
5125 else if(env && env->date && env->date[0]
5126 && mail_parse_date(&telt,env->date))
5127 when = mail_longdate(&telt);
5128 else
5129 when = time(0);
5131 p = ctime(&when);
5133 while(p && *p && *p != '\n') /* write date */
5134 if(!(*pc)(*p++))
5135 return(0);
5137 if(!gf_puts(NEWLINE, pc)) /* write terminating newline */
5138 return(0);
5140 return(1);
5144 /*----------------------------------------------------------------------
5145 Execute command to jump to a given message number
5147 Args: qline -- Line to ask question on
5149 Result: returns true if the use selected a new message, false otherwise
5151 ----*/
5152 long
5153 jump_to(MSGNO_S *msgmap, int qline, UCS first_num, SCROLL_S *sparms, CmdWhere in_index)
5155 char jump_num_string[80], *j, prompt[70];
5156 HelpType help;
5157 int rc;
5158 static ESCKEY_S jump_to_key[] = { {0, 0, NULL, NULL},
5159 /* TRANSLATORS: go to First Message */
5160 {ctrl('Y'), 10, "^Y", N_("First Msg")},
5161 {ctrl('V'), 11, "^V", N_("Last Msg")},
5162 {-1, 0, NULL, NULL} };
5164 dprint((4, "\n - jump_to -\n"));
5166 #ifdef DEBUG
5167 if(sparms && sparms->jump_is_debug)
5168 return(get_level(qline, first_num, sparms));
5169 #endif
5171 if(!any_messages(msgmap, NULL, "to Jump to"))
5172 return(0L);
5174 if(first_num && first_num < 0x80 && isdigit((unsigned char) first_num)){
5175 jump_num_string[0] = first_num;
5176 jump_num_string[1] = '\0';
5178 else
5179 jump_num_string[0] = '\0';
5181 if(mn_total_cur(msgmap) > 1L){
5182 snprintf(prompt, sizeof(prompt), "Unselect %s msgs in favor of number to be entered",
5183 comatose(mn_total_cur(msgmap)));
5184 prompt[sizeof(prompt)-1] = '\0';
5185 if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n')
5186 return(0L);
5189 snprintf(prompt, sizeof(prompt), "%s number to jump to : ", in_index == ThrdIndx
5190 ? "Thread"
5191 : "Message");
5192 prompt[sizeof(prompt)-1] = '\0';
5194 help = NO_HELP;
5195 while(1){
5196 int flags = OE_APPEND_CURRENT;
5198 rc = optionally_enter(jump_num_string, qline, 0,
5199 sizeof(jump_num_string), prompt,
5200 jump_to_key, help, &flags);
5201 if(rc == 3){
5202 help = help == NO_HELP
5203 ? (in_index == ThrdIndx ? h_oe_jump_thd : h_oe_jump)
5204 : NO_HELP;
5205 continue;
5207 else if(rc == 10 || rc == 11){
5208 char warning[100];
5209 long closest;
5211 closest = closest_jump_target(rc == 10 ? 1L
5212 : ((in_index == ThrdIndx)
5213 ? msgmap->max_thrdno
5214 : mn_get_total(msgmap)),
5215 ps_global->mail_stream,
5216 msgmap, 0,
5217 in_index, warning, sizeof(warning));
5218 /* ignore warning */
5219 return(closest);
5223 * If we take out the *jump_num_string nonempty test in this if
5224 * then the closest_jump_target routine will offer a jump to the
5225 * last message. However, it is slow because you have to wait for
5226 * the status message and it is annoying for people who hit J command
5227 * by mistake and just want to hit return to do nothing, like has
5228 * always worked. So the test is there for now. Hubert 2002-08-19
5230 * Jumping to first/last message is now possible through ^Y/^V
5231 * commands above. jpf 2002-08-21
5232 * (and through "end" hubert 2006-07-07)
5234 if(rc == 0 && *jump_num_string != '\0'){
5235 removing_leading_and_trailing_white_space(jump_num_string);
5236 for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++)
5239 if(*j != '\0'){
5240 if(!strucmp("end", j))
5241 return((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap));
5243 q_status_message(SM_ORDER | SM_DING, 2, 2,
5244 _("Invalid number entered. Use only digits 0-9"));
5245 jump_num_string[0] = '\0';
5247 else{
5248 char warning[100];
5249 long closest, jump_num;
5251 if(*jump_num_string)
5252 jump_num = atol(jump_num_string);
5253 else
5254 jump_num = -1L;
5256 warning[0] = '\0';
5257 closest = closest_jump_target(jump_num, ps_global->mail_stream,
5258 msgmap,
5259 *jump_num_string ? 0 : 1,
5260 in_index, warning, sizeof(warning));
5261 if(warning[0])
5262 q_status_message(SM_ORDER | SM_DING, 2, 2, warning);
5264 if(closest == jump_num)
5265 return(jump_num);
5267 if(closest == 0L)
5268 jump_num_string[0] = '\0';
5269 else
5270 strncpy(jump_num_string, long2string(closest),
5271 sizeof(jump_num_string));
5274 continue;
5277 if(rc != 4)
5278 break;
5281 return(0L);
5286 * cmd_delete_action - handle msgno advance and such after single message deletion
5288 char *
5289 cmd_delete_action(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
5291 int opts;
5292 long msgno;
5293 char *rv = NULL;
5295 msgno = mn_get_cur(msgmap);
5296 advance_cur_after_delete(state, state->mail_stream, msgmap, in_index);
5298 if(IS_NEWS(state->mail_stream)
5299 || ((state->context_current->use & CNTXT_INCMNG)
5300 && context_isambig(state->cur_folder))){
5302 opts = (NSF_TRUST_FLAGS | NSF_SKIP_CHID);
5303 if(in_index == View)
5304 opts &= ~NSF_SKIP_CHID;
5306 (void)next_sorted_flagged(F_UNDEL|F_UNSEEN, state->mail_stream, msgno, &opts);
5307 if(!(opts & NSF_FLAG_MATCH)){
5308 char nextfolder[MAXPATH];
5310 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
5311 nextfolder[sizeof(nextfolder)-1] = '\0';
5312 rv = next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
5313 state->context_current, NULL, NULL)
5314 ? ". Press TAB for next folder."
5315 : ". No more folders to TAB to.";
5319 return(rv);
5324 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5326 char *
5327 cmd_delete_index(struct pine *state, MSGNO_S *msgmap)
5329 return(cmd_delete_action(state, msgmap,MsgIndx));
5333 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5335 char *
5336 cmd_delete_view(struct pine *state, MSGNO_S *msgmap)
5338 return(cmd_delete_action(state, msgmap, View));
5342 void
5343 advance_cur_after_delete(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, CmdWhere in_index)
5345 long new_msgno, msgno;
5346 int opts;
5348 new_msgno = msgno = mn_get_cur(msgmap);
5349 opts = NSF_TRUST_FLAGS;
5351 if(F_ON(F_DEL_SKIPS_DEL, state)){
5353 if(THREADING() && sp_viewing_a_thread(stream))
5354 opts |= NSF_SKIP_CHID;
5356 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5358 else{
5359 mn_inc_cur(stream, msgmap,
5360 (in_index == View && THREADING()
5361 && sp_viewing_a_thread(stream))
5362 ? MH_THISTHD
5363 : (in_index == View)
5364 ? MH_ANYTHD : MH_NONE);
5365 new_msgno = mn_get_cur(msgmap);
5366 if(new_msgno != msgno)
5367 opts |= NSF_FLAG_MATCH;
5371 * Viewing_a_thread is the complicated case because we want to ignore
5372 * other threads at first and then look in other threads if we have to.
5373 * By ignoring other threads we also ignore collapsed partial threads
5374 * in our own thread.
5376 if(THREADING() && sp_viewing_a_thread(stream) && !(opts & NSF_FLAG_MATCH)){
5377 long rawno, orig_thrdno;
5378 PINETHRD_S *thrd, *topthrd = NULL;
5380 rawno = mn_m2raw(msgmap, msgno);
5381 thrd = fetch_thread(stream, rawno);
5382 if(thrd && thrd->top)
5383 topthrd = fetch_thread(stream, thrd->top);
5385 orig_thrdno = topthrd ? topthrd->thrdno : -1L;
5387 opts = NSF_TRUST_FLAGS;
5388 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5391 * If we got a match, new_msgno may be a message in
5392 * a different thread from the one we are viewing, or it could be
5393 * in a collapsed part of this thread.
5395 if(opts & NSF_FLAG_MATCH){
5396 int ret;
5397 char pmt[128];
5399 topthrd = NULL;
5400 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
5401 if(thrd && thrd->top)
5402 topthrd = fetch_thread(stream, thrd->top);
5405 * If this match is in the same thread we're already in
5406 * then we're done, else we have to ask the user and maybe
5407 * switch threads.
5409 if(!(orig_thrdno > 0L && topthrd
5410 && topthrd->thrdno == orig_thrdno)){
5412 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
5413 if(in_index == View)
5414 snprintf(pmt, sizeof(pmt),
5415 "View message in thread number %.10s",
5416 topthrd ? comatose(topthrd->thrdno) : "?");
5417 else
5418 snprintf(pmt, sizeof(pmt), "View thread number %.10s",
5419 topthrd ? comatose(topthrd->thrdno) : "?");
5421 ret = want_to(pmt, 'y', 'x', NO_HELP, WT_NORM);
5423 else
5424 ret = 'y';
5426 if(ret == 'y'){
5427 unview_thread(state, stream, msgmap);
5428 mn_set_cur(msgmap, new_msgno);
5429 if(THRD_AUTO_VIEW()
5430 && (count_lflags_in_thread(stream, topthrd, msgmap,
5431 MN_NONE) == 1)
5432 && view_thread(state, stream, msgmap, 1)){
5433 if(current_index_state)
5434 msgmap->top_after_thrd = current_index_state->msg_at_top;
5436 state->view_skipped_index = 1;
5437 state->next_screen = mail_view_screen;
5439 else{
5440 view_thread(state, stream, msgmap, 1);
5441 if(current_index_state)
5442 msgmap->top_after_thrd = current_index_state->msg_at_top;
5444 state->next_screen = SCREEN_FUN_NULL;
5447 else
5448 new_msgno = msgno; /* stick with original */
5453 mn_set_cur(msgmap, new_msgno);
5454 if(in_index != View)
5455 adjust_cur_to_visible(stream, msgmap);
5459 #ifdef DEBUG
5460 long
5461 get_level(int qline, UCS first_num, SCROLL_S *sparms)
5463 char debug_num_string[80], *j, prompt[70];
5464 HelpType help;
5465 int rc;
5466 long debug_num;
5468 if(first_num && first_num < 0x80 && isdigit((unsigned char)first_num)){
5469 debug_num_string[0] = first_num;
5470 debug_num_string[1] = '\0';
5471 debug_num = atol(debug_num_string);
5472 *(int *)(sparms->proc.data.p) = debug_num;
5473 q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s",
5474 comatose(debug_num));
5475 return(1L);
5477 else
5478 debug_num_string[0] = '\0';
5480 snprintf(prompt, sizeof(prompt), "Show debug <= this level (0-%d) : ", MAX(debug, 9));
5481 prompt[sizeof(prompt)-1] = '\0';
5483 help = NO_HELP;
5484 while(1){
5485 int flags = OE_APPEND_CURRENT;
5487 rc = optionally_enter(debug_num_string, qline, 0,
5488 sizeof(debug_num_string), prompt,
5489 NULL, help, &flags);
5490 if(rc == 3){
5491 help = help == NO_HELP ? h_oe_debuglevel : NO_HELP;
5492 continue;
5495 if(rc == 0){
5496 removing_leading_and_trailing_white_space(debug_num_string);
5497 for(j=debug_num_string; isdigit((unsigned char)*j); j++)
5500 if(*j != '\0'){
5501 q_status_message(SM_ORDER | SM_DING, 2, 2,
5502 _("Invalid number entered. Use only digits 0-9"));
5503 debug_num_string[0] = '\0';
5505 else{
5506 debug_num = atol(debug_num_string);
5507 if(debug_num < 0)
5508 q_status_message(SM_ORDER | SM_DING, 2, 2,
5509 _("Number should be >= 0"));
5510 else if(debug_num > MAX(debug,9))
5511 q_status_message1(SM_ORDER | SM_DING, 2, 2,
5512 _("Maximum is %s"), comatose(MAX(debug,9)));
5513 else{
5514 *(int *)(sparms->proc.data.p) = debug_num;
5515 q_status_message1(SM_ORDER, 0, 3,
5516 "Show debug <= level %s",
5517 comatose(debug_num));
5518 return(1L);
5522 continue;
5525 if(rc != 4)
5526 break;
5529 return(0L);
5531 #endif /* DEBUG */
5535 * Returns the message number closest to target that isn't hidden.
5536 * Make warning at least 100 chars.
5537 * A return of 0 means there is no message to jump to.
5539 long
5540 closest_jump_target(long int target, MAILSTREAM *stream, MSGNO_S *msgmap, int no_target, CmdWhere in_index, char *warning, size_t warninglen)
5542 long i, start, closest = 0L;
5543 char buf[80];
5544 long maxnum;
5546 warning[0] = '\0';
5547 maxnum = (in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap);
5549 if(no_target){
5550 target = maxnum;
5551 start = 1L;
5552 snprintf(warning, warninglen, "No %s number entered, jump to end? ",
5553 (in_index == ThrdIndx) ? "thread" : "message");
5554 warning[warninglen-1] = '\0';
5556 else if(target < 1L)
5557 start = 1L - target;
5558 else if(target > maxnum)
5559 start = target - maxnum;
5560 else
5561 start = 1L;
5563 if(target > 0L && target <= maxnum)
5564 if(in_index == ThrdIndx
5565 || !msgline_hidden(stream, msgmap, target, 0))
5566 return(target);
5568 for(i = start; target+i <= maxnum || target-i > 0L; i++){
5570 if(target+i > 0L && target+i <= maxnum &&
5571 (in_index == ThrdIndx
5572 || !msgline_hidden(stream, msgmap, target+i, 0))){
5573 closest = target+i;
5574 break;
5577 if(target-i > 0L && target-i <= maxnum &&
5578 (in_index == ThrdIndx
5579 || !msgline_hidden(stream, msgmap, target-i, 0))){
5580 closest = target-i;
5581 break;
5585 strncpy(buf, long2string(closest), sizeof(buf));
5586 buf[sizeof(buf)-1] = '\0';
5588 if(closest == 0L)
5589 strncpy(warning, "Nothing to jump to", warninglen);
5590 else if(target < 1L)
5591 snprintf(warning, warninglen, "%s number (%s) must be at least %s",
5592 (in_index == ThrdIndx) ? "Thread" : "Message",
5593 long2string(target), buf);
5594 else if(target > maxnum)
5595 snprintf(warning, warninglen, "%s number (%s) may be no more than %s",
5596 (in_index == ThrdIndx) ? "Thread" : "Message",
5597 long2string(target), buf);
5598 else if(!no_target)
5599 snprintf(warning, warninglen,
5600 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5601 long2string(target), buf);
5603 warning[warninglen-1] = '\0';
5605 return(closest);
5609 /*----------------------------------------------------------------------
5610 Prompt for folder name to open, expand the name and return it
5612 Args: qline -- Screen line to prompt on
5613 allow_list -- if 1, allow ^T to bring up collection lister
5615 Result: returns the folder name or NULL
5616 pine structure mangled_footer flag is set
5617 may call the collection lister in which case mangled screen will be set
5619 This prompts the user for the folder to open, possibly calling up
5620 the collection lister if the user types ^T.
5621 ----------------------------------------------------------------------*/
5622 char *
5623 broach_folder(int qline, int allow_list, int *notrealinbox, CONTEXT_S **context)
5625 HelpType help;
5626 static char newfolder[MAILTMPLEN];
5627 char expanded[MAXPATH+1],
5628 prompt[MAX_SCREEN_COLS+1],
5629 *last_folder, *p;
5630 unsigned char *f1, *f2, *f3;
5631 static HISTORY_S *history = NULL;
5632 CONTEXT_S *tc, *tc2;
5633 ESCKEY_S ekey[9];
5634 int rc, r, ku = -1, n, flags, last_rc = 0, inbox, done = 0;
5637 * the idea is to provide a clue for the context the file name
5638 * will be saved in (if a non-imap names is typed), and to
5639 * only show the previous if it was also in the same context
5641 help = NO_HELP;
5642 *expanded = '\0';
5643 *newfolder = '\0';
5644 last_folder = NULL;
5645 if(notrealinbox)
5646 (*notrealinbox) = 1;
5648 init_hist(&history, HISTSIZE);
5650 tc = broach_get_folder(context ? *context : NULL, &inbox, NULL);
5652 /* set up extra command option keys */
5653 rc = 0;
5654 ekey[rc].ch = (allow_list) ? ctrl('T') : 0 ;
5655 ekey[rc].rval = (allow_list) ? 2 : 0;
5656 ekey[rc].name = (allow_list) ? "^T" : "";
5657 ekey[rc++].label = (allow_list) ? N_("ToFldrs") : "";
5659 if(ps_global->context_list->next){
5660 ekey[rc].ch = ctrl('P');
5661 ekey[rc].rval = 10;
5662 ekey[rc].name = "^P";
5663 ekey[rc++].label = N_("Prev Collection");
5665 ekey[rc].ch = ctrl('N');
5666 ekey[rc].rval = 11;
5667 ekey[rc].name = "^N";
5668 ekey[rc++].label = N_("Next Collection");
5671 ekey[rc].ch = ctrl('W');
5672 ekey[rc].rval = 17;
5673 ekey[rc].name = "^W";
5674 ekey[rc++].label = N_("INBOX");
5676 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
5677 ekey[rc].ch = TAB;
5678 ekey[rc].rval = 12;
5679 ekey[rc].name = "TAB";
5680 ekey[rc++].label = N_("Complete");
5683 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
5684 ekey[rc].ch = ctrl('X');
5685 ekey[rc].rval = 14;
5686 ekey[rc].name = "^X";
5687 ekey[rc++].label = N_("ListMatches");
5690 if(ps_global->context_list->next && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5691 ekey[rc].ch = KEY_UP;
5692 ekey[rc].rval = 10;
5693 ekey[rc].name = "";
5694 ekey[rc++].label = "";
5696 ekey[rc].ch = KEY_DOWN;
5697 ekey[rc].rval = 11;
5698 ekey[rc].name = "";
5699 ekey[rc++].label = "";
5701 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5702 ekey[rc].ch = KEY_UP;
5703 ekey[rc].rval = 30;
5704 ekey[rc].name = "";
5705 ku = rc;
5706 ekey[rc++].label = "";
5708 ekey[rc].ch = KEY_DOWN;
5709 ekey[rc].rval = 31;
5710 ekey[rc].name = "";
5711 ekey[rc++].label = "";
5714 ekey[rc].ch = -1;
5716 while(!done) {
5718 * Figure out next default value for this context. The idea
5719 * is that in each context the last folder opened is cached.
5720 * It's up to pick it out and display it. This is fine
5721 * and dandy if we've currently got the inbox open, BUT
5722 * if not, make the inbox the default the first time thru.
5724 if(!inbox){
5725 last_folder = ps_global->inbox_name;
5726 inbox = 1; /* pretend we're in inbox from here on out */
5728 else
5729 last_folder = (ps_global->last_unambig_folder[0])
5730 ? ps_global->last_unambig_folder
5731 : ((tc->last_folder[0]) ? tc->last_folder : NULL);
5733 if(last_folder){ /* MAXPATH + 1 = sizeof(expanded) */
5734 unsigned char *fname = folder_name_decoded((unsigned char *)last_folder);
5735 snprintf(expanded, sizeof(expanded), " [%.*s]", MAXPATH+1-5,
5736 fname ? (char *) fname : last_folder);
5737 if(fname) fs_give((void **)&fname);
5739 else
5740 *expanded = '\0';
5742 expanded[sizeof(expanded)-1] = '\0';
5744 /* only show collection number if more than one available */
5745 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5746 snprintf(prompt, sizeof(prompt), "GOTO %s in <%s> %.*s%s: ",
5747 NEWS_TEST(tc) ? "news group" : "folder",
5748 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5749 *expanded ? " " : "");
5750 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5751 snprintf(prompt, sizeof(prompt), "GOTO folder %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5752 *expanded ? " " : "");
5754 prompt[sizeof(prompt)-1] = '\0';
5756 if(utf8_width(prompt) > MAXPROMPT){
5757 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5758 snprintf(prompt, sizeof(prompt), "GOTO <%s> %.*s%s: ",
5759 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5760 *expanded ? " " : "");
5761 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5762 snprintf(prompt, sizeof(prompt), "GOTO %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5763 *expanded ? " " : "");
5765 prompt[sizeof(prompt)-1] = '\0';
5767 if(utf8_width(prompt) > MAXPROMPT){
5768 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5769 snprintf(prompt, sizeof(prompt), "<%s> %.*s%s: ",
5770 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5771 *expanded ? " " : "");
5772 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5773 snprintf(prompt, sizeof(prompt), "%.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5774 *expanded ? " " : "");
5776 prompt[sizeof(prompt)-1] = '\0';
5780 if(ku >= 0){
5781 if(items_in_hist(history) > 1){
5782 ekey[ku].name = HISTORY_UP_KEYNAME;
5783 ekey[ku].label = HISTORY_KEYLABEL;
5784 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
5785 ekey[ku+1].label = HISTORY_KEYLABEL;
5787 else{
5788 ekey[ku].name = "";
5789 ekey[ku].label = "";
5790 ekey[ku+1].name = "";
5791 ekey[ku+1].label = "";
5795 /* is there any other way to do this? The point is that we
5796 * are trying to hide mutf7 from the user, and use the utf8
5797 * equivalent. So we create a variable f to take place of
5798 * newfolder, including content and size. f2 is copy of f1
5799 * that has to freed. Sigh!
5801 f3 = (unsigned char *) cpystr(newfolder);
5802 f1 = fs_get(sizeof(newfolder));
5803 f2 = folder_name_decoded(f3);
5804 if(f3) fs_give((void **)&f3);
5805 strncpy((char *)f1, (char *)f2, sizeof(newfolder));
5806 f1[sizeof(newfolder)-1] = '\0';
5807 if(f2) fs_give((void **)&f2);
5809 flags = OE_APPEND_CURRENT;
5810 rc = optionally_enter((char *) f1, qline, 0, sizeof(newfolder),
5811 (char *) prompt, ekey, help, &flags);
5813 f2 = folder_name_encoded(f1);
5814 strncpy(newfolder, (char *)f2, sizeof(newfolder));
5815 if(f1) fs_give((void **)&f1);
5816 if(f2) fs_give((void **)&f2);
5818 ps_global->mangled_footer = 1;
5820 switch(rc){
5821 case -1 : /* o_e says error! */
5822 q_status_message(SM_ORDER | SM_DING, 3, 3,
5823 _("Error reading folder name"));
5824 return(NULL);
5826 case 0 : /* o_e says normal entry */
5827 removing_trailing_white_space(newfolder);
5828 removing_leading_white_space(newfolder);
5830 if(*newfolder){
5831 char *name, *fullname = NULL;
5832 int exists, breakout = 0;
5834 save_hist(history, newfolder, 0, tc);
5836 if(!(name = folder_is_nick(newfolder, FOLDERS(tc),
5837 FN_WHOLE_NAME)))
5838 name = newfolder;
5840 if(update_folder_spec(expanded, sizeof(expanded), name)){
5841 strncpy(name = newfolder, expanded, sizeof(newfolder));
5842 newfolder[sizeof(newfolder)-1] = '\0';
5845 exists = folder_name_exists(tc, name, &fullname);
5847 if(fullname){
5848 strncpy(name = newfolder, fullname, sizeof(newfolder));
5849 newfolder[sizeof(newfolder)-1] = '\0';
5850 fs_give((void **) &fullname);
5851 breakout = TRUE;
5855 * if we know the things a folder, open it.
5856 * else if we know its a directory, visit it.
5857 * else we're not sure (it either doesn't really
5858 * exist or its unLISTable) so try opening it anyway
5860 if(exists & FEX_ISFILE){
5861 done++;
5862 break;
5864 else if((exists & FEX_ISDIR)){
5865 if(breakout){
5866 CONTEXT_S *fake_context;
5867 char tmp[MAILTMPLEN];
5868 size_t l;
5870 strncpy(tmp, name, sizeof(tmp));
5871 tmp[sizeof(tmp)-2-1] = '\0';
5872 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
5873 if(l < sizeof(tmp)){
5874 tmp[l] = tc->dir->delim;
5875 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
5878 else
5879 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
5881 tmp[sizeof(tmp)-1] = '\0';
5883 fake_context = new_context(tmp, 0);
5884 newfolder[0] = '\0';
5885 done = display_folder_list(&fake_context, newfolder,
5886 1, folders_for_goto);
5887 free_context(&fake_context);
5888 break;
5890 else if(!(tc->use & CNTXT_INCMNG)){
5891 done = display_folder_list(&tc, newfolder,
5892 1, folders_for_goto);
5893 break;
5896 else if((exists & FEX_ERROR)){
5897 q_status_message1(SM_ORDER, 0, 3,
5898 _("Problem accessing folder \"%s\""),
5899 newfolder);
5900 return(NULL);
5902 else{
5903 done++;
5904 break;
5907 if(exists == FEX_ERROR)
5908 q_status_message1(SM_ORDER, 0, 3,
5909 _("Problem accessing folder \"%s\""),
5910 newfolder);
5911 else if(tc->use & CNTXT_INCMNG)
5912 q_status_message1(SM_ORDER, 0, 3,
5913 _("Can't find Incoming Folder: %s"),
5914 newfolder);
5915 else if(context_isambig(newfolder))
5916 q_status_message2(SM_ORDER, 0, 3,
5917 _("Can't find folder \"%s\" in %s"),
5918 newfolder, (void *) tc->nickname);
5919 else
5920 q_status_message1(SM_ORDER, 0, 3,
5921 _("Can't find folder \"%s\""),
5922 newfolder);
5924 return(NULL);
5926 else if(last_folder){
5927 if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX
5928 && !strucmp(last_folder, ps_global->inbox_name)
5929 && tc == ((ps_global->context_list->use & CNTXT_INCMNG)
5930 ? ps_global->context_list->next : ps_global->context_list)){
5931 if(notrealinbox)
5932 (*notrealinbox) = 0;
5934 tc = ps_global->context_list;
5937 strncpy(newfolder, last_folder, sizeof(newfolder));
5938 newfolder[sizeof(newfolder)-1] = '\0';
5939 save_hist(history, newfolder, 0, tc);
5940 done++;
5941 break;
5943 /* fall thru like they cancelled */
5945 case 1 : /* o_e says user cancel */
5946 cmd_cancelled("Open folder");
5947 return(NULL);
5949 case 2 : /* o_e says user wants list */
5950 r = display_folder_list(&tc, newfolder, 0, folders_for_goto);
5951 if(r)
5952 done++;
5954 break;
5956 case 3 : /* o_e says user wants help */
5957 help = help == NO_HELP ? h_oe_broach : NO_HELP;
5958 break;
5960 case 4 : /* redraw */
5961 break;
5963 case 10 : /* Previous collection */
5964 tc2 = ps_global->context_list;
5965 while(tc2->next && tc2->next != tc)
5966 tc2 = tc2->next;
5968 tc = tc2;
5969 break;
5971 case 11 : /* Next collection */
5972 tc = (tc->next) ? tc->next : ps_global->context_list;
5973 break;
5975 case 12 : /* file name completion */
5976 if(!folder_complete(tc, newfolder, sizeof(newfolder), &n)){
5977 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
5978 r = display_folder_list(&tc, newfolder, 1,folders_for_goto);
5979 if(r)
5980 done++; /* bingo! */
5981 else
5982 rc = 0; /* burn last_rc */
5984 else
5985 Writechar(BELL, 0);
5988 break;
5990 case 14 : /* file name completion */
5991 r = display_folder_list(&tc, newfolder, 2, folders_for_goto);
5992 if(r)
5993 done++; /* bingo! */
5994 else
5995 rc = 0; /* burn last_rc */
5997 break;
5999 case 17 : /* GoTo INBOX */
6000 done++;
6001 strncpy(newfolder, ps_global->inbox_name, sizeof(newfolder)-1);
6002 newfolder[sizeof(newfolder)-1] = '\0';
6003 if(notrealinbox)
6004 (*notrealinbox) = 0;
6006 tc = ps_global->context_list;
6007 save_hist(history, newfolder, 0, tc);
6009 break;
6011 case 30 :
6012 if((p = get_prev_hist(history, newfolder, 0, tc)) != NULL){
6013 strncpy(newfolder, p, sizeof(newfolder));
6014 newfolder[sizeof(newfolder)-1] = '\0';
6015 if(history->hist[history->curindex])
6016 tc = history->hist[history->curindex]->cntxt;
6018 else
6019 Writechar(BELL, 0);
6021 break;
6023 case 31 :
6024 if((p = get_next_hist(history, newfolder, 0, tc)) != NULL){
6025 strncpy(newfolder, p, sizeof(newfolder));
6026 newfolder[sizeof(newfolder)-1] = '\0';
6027 if(history->hist[history->curindex])
6028 tc = history->hist[history->curindex]->cntxt;
6030 else
6031 Writechar(BELL, 0);
6033 break;
6035 default :
6036 alpine_panic("Unhandled case");
6037 break;
6040 last_rc = rc;
6043 dprint((2, "broach folder, name entered \"%s\"\n",
6044 newfolder ? newfolder : "?"));
6046 /*-- Just check that we can expand this. It gets done for real later --*/
6047 strncpy(expanded, newfolder, sizeof(expanded));
6048 expanded[sizeof(expanded)-1] = '\0';
6050 if(!expand_foldername(expanded, sizeof(expanded))) {
6051 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
6052 expanded ? expanded : "?"));
6053 return(NULL);
6056 *context = tc;
6057 return(newfolder);
6061 /*----------------------------------------------------------------------
6062 Check to see if user wants to reopen dead stream.
6064 Args: ps --
6065 reopenp --
6067 Result: 1 if the folder was successfully updatedn
6068 0 if not necessary
6070 ----*/
6072 ask_mailbox_reopen(struct pine *ps, int *reopenp)
6074 if(((ps->mail_stream->dtb
6075 && ((ps->mail_stream->dtb->flags & DR_NONEWMAIL)
6076 || (ps->mail_stream->rdonly
6077 && ps->mail_stream->dtb->flags & DR_NONEWMAILRONLY)))
6078 && (ps->reopen_rule == REOPEN_ASK_ASK_Y
6079 || ps->reopen_rule == REOPEN_ASK_ASK_N
6080 || ps->reopen_rule == REOPEN_ASK_NO_Y
6081 || ps->reopen_rule == REOPEN_ASK_NO_N))
6082 || ((ps->mail_stream->dtb
6083 && ps->mail_stream->rdonly
6084 && !(ps->mail_stream->dtb->flags & DR_LOCAL))
6085 && (ps->reopen_rule == REOPEN_YES_ASK_Y
6086 || ps->reopen_rule == REOPEN_YES_ASK_N
6087 || ps->reopen_rule == REOPEN_ASK_ASK_Y
6088 || ps->reopen_rule == REOPEN_ASK_ASK_N))){
6089 int deefault;
6091 switch(ps->reopen_rule){
6092 case REOPEN_YES_ASK_Y:
6093 case REOPEN_ASK_ASK_Y:
6094 case REOPEN_ASK_NO_Y:
6095 deefault = 'y';
6096 break;
6098 default:
6099 deefault = 'n';
6100 break;
6103 switch(want_to("Re-open folder to check for new messages", deefault,
6104 'x', h_reopen_folder, WT_NORM)){
6105 case 'y':
6106 (*reopenp)++;
6107 break;
6109 case 'x':
6110 return(-1);
6114 return(0);
6119 /*----------------------------------------------------------------------
6120 Check to see if user input is in form of old c-client mailbox speck
6122 Args: old --
6123 new --
6125 Result: 1 if the folder was successfully updatedn
6126 0 if not necessary
6128 ----*/
6130 update_folder_spec(char *new, size_t newlen, char *old)
6132 char *p, *orignew;
6133 int nntp = 0;
6135 orignew = new;
6136 if(*(p = old) == '*') /* old form? */
6137 old++;
6139 if(*old == '{') /* copy host spec */
6141 switch(*new = *old++){
6142 case '\0' :
6143 return(FALSE);
6145 case '/' :
6146 if(!struncmp(old, "nntp", 4))
6147 nntp++;
6149 break;
6151 default :
6152 break;
6154 while(*new++ != '}' && (new-orignew) < newlen-1);
6156 if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){
6158 * OK, some heuristics here. If it looks like a newsgroup
6159 * then we plunk it into the #news namespace else we
6160 * assume that they're trying to get at a #public folder...
6162 for(p = old;
6163 *p && (isalnum((unsigned char) *p) || strindex(".-", *p));
6164 p++)
6167 sstrncpy(&new, (*p && !nntp) ? "#public/" : "#news.", newlen-(new-orignew));
6168 strncpy(new, old, newlen-(new-orignew));
6169 return(TRUE);
6172 orignew[newlen-1] = '\0';
6174 return(FALSE);
6178 /*----------------------------------------------------------------------
6179 Open the requested folder in the requested context
6181 Args: state -- usual pine state struct
6182 newfolder -- folder to open
6183 new_context -- folder context might live in
6184 stream -- candidate for recycling
6186 Result: New folder open or not (if error), and we're set to
6187 enter the index screen.
6188 ----*/
6189 void
6190 visit_folder(struct pine *state, char *newfolder, CONTEXT_S *new_context,
6191 MAILSTREAM *stream, long unsigned int flags)
6193 dprint((9, "visit_folder(%s, %s)\n",
6194 newfolder ? newfolder : "?",
6195 (new_context && new_context->context)
6196 ? new_context->context : "(NULL)"));
6198 if(ps_global && ps_global->ttyo){
6199 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
6200 ps_global->mangled_footer = 1;
6203 if(do_broach_folder(newfolder, new_context, stream ? &stream : NULL,
6204 flags) >= 0
6205 || !sp_flagged(state->mail_stream, SP_LOCKED))
6206 state->next_screen = mail_index_screen;
6207 else
6208 state->next_screen = folder_screen;
6212 /*----------------------------------------------------------------------
6213 Move read messages from folder if listed in archive
6215 Args:
6217 ----*/
6219 read_msg_prompt(long int n, char *f)
6221 char buf[MAX_SCREEN_COLS+1];
6223 snprintf(buf, sizeof(buf), "Save the %ld read message%s in \"%s\"", n, plural(n), f);
6224 buf[sizeof(buf)-1] = '\0';
6225 return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y');
6229 /*----------------------------------------------------------------------
6230 Print current message[s] or folder index
6232 Args: state -- pointer to struct holding a bunch of pine state
6233 msgmap -- table mapping msg nums to c-client sequence nums
6234 aopt -- aggregate options
6235 in_index -- boolean indicating we're called from Index Screen
6237 Filters the original header and sends stuff to printer
6238 ---*/
6240 cmd_print(struct pine *state, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
6242 char prompt[250];
6243 long i, msgs, rawno;
6244 int next = 0, do_index = 0, rv = 0;
6245 ENVELOPE *e;
6246 BODY *b;
6247 MESSAGECACHE *mc;
6249 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
6250 return rv;
6252 msgs = mn_total_cur(msgmap);
6254 if((in_index != View) && F_ON(F_PRINT_INDEX, state)){
6255 char m[10];
6256 int ans;
6257 static ESCKEY_S prt_opts[] = {
6258 {'i', 'i', "I", N_("Index")},
6259 {'m', 'm', "M", NULL},
6260 {-1, 0, NULL, NULL}};
6262 if(in_index == ThrdIndx){
6263 /* TRANSLATORS: This is a question, Print Index ? */
6264 if(want_to(_("Print Index"), 'y', 'x', NO_HELP, WT_NORM) == 'y')
6265 ans = 'i';
6266 else
6267 ans = 'x';
6269 else{
6270 snprintf(m, sizeof(m), "Message%s", (msgs>1L) ? "s" : "");
6271 m[sizeof(m)-1] = '\0';
6272 prt_opts[1].label = m;
6273 snprintf(prompt, sizeof(prompt), "Print %sFolder Index or %s %s? ",
6274 (aopt & MCMD_AGG_2) ? "thread " : MCMD_ISAGG(aopt) ? "selected " : "",
6275 (aopt & MCMD_AGG_2) ? "thread" : MCMD_ISAGG(aopt) ? "selected" : "current", m);
6276 prompt[sizeof(prompt)-1] = '\0';
6278 ans = radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x',
6279 NO_HELP, RB_NORM|RB_SEQ_SENSITIVE);
6282 switch(ans){
6283 case 'x' :
6284 cmd_cancelled("Print");
6285 if(MCMD_ISAGG(aopt))
6286 restore_selected(msgmap);
6288 return rv;
6290 case 'i':
6291 do_index = 1;
6292 break;
6294 default :
6295 case 'm':
6296 break;
6300 if(do_index)
6301 snprintf(prompt, sizeof(prompt), "%sFolder Index",
6302 (aopt & MCMD_AGG_2) ? "Thread " : MCMD_ISAGG(aopt) ? "Selected " : "");
6303 else if(msgs > 1L)
6304 snprintf(prompt, sizeof(prompt), "%s messages", long2string(msgs));
6305 else
6306 snprintf(prompt, sizeof(prompt), "Message %s", long2string(mn_get_cur(msgmap)));
6308 prompt[sizeof(prompt)-1] = '\0';
6310 if(open_printer(prompt) < 0){
6311 if(MCMD_ISAGG(aopt))
6312 restore_selected(msgmap);
6314 return rv;
6317 if(do_index){
6318 TITLE_S *tc;
6320 tc = format_titlebar();
6322 /* Print titlebar... */
6323 print_text1("%s\n\n", tc ? tc->titlebar_line : "");
6324 /* then all the index members... */
6325 if(!print_index(state, msgmap, MCMD_ISAGG(aopt)))
6326 q_status_message(SM_ORDER | SM_DING, 3, 3,
6327 _("Error printing folder index"));
6328 else
6329 rv++;
6331 else{
6332 rv++;
6333 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){
6334 if(next && F_ON(F_AGG_PRINT_FF, state))
6335 if(!print_char(FORMFEED)){
6336 rv = 0;
6337 break;
6340 if(!(state->mail_stream
6341 && (rawno = mn_m2raw(msgmap, i)) > 0L
6342 && rawno <= state->mail_stream->nmsgs
6343 && (mc = mail_elt(state->mail_stream, rawno))
6344 && mc->valid))
6345 mc = NULL;
6347 if(!(e=pine_mail_fetchstructure(state->mail_stream,
6348 mn_m2raw(msgmap,i),
6349 &b))
6350 || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global)
6351 && !bezerk_delimiter(e, mc, print_char, next))
6352 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
6353 e, b, NULL, FM_NEW_MESS | FM_NOINDENT,
6354 print_char)){
6355 q_status_message(SM_ORDER | SM_DING, 3, 3,
6356 _("Error printing message"));
6357 rv = 0;
6358 break;
6363 close_printer();
6365 if(MCMD_ISAGG(aopt))
6366 restore_selected(msgmap);
6368 return rv;
6372 /*----------------------------------------------------------------------
6373 Pipe message text
6375 Args: state -- various pine state bits
6376 msgmap -- Message number mapping table
6377 aopt -- option flags
6379 Filters the original header and sends stuff to specified command
6380 ---*/
6382 cmd_pipe(struct pine *state, MSGNO_S *msgmap, int aopt)
6384 ENVELOPE *e;
6385 MESSAGECACHE *mc;
6386 BODY *b;
6387 PIPE_S *syspipe;
6388 char *resultfilename = NULL, prompt[80], *p;
6389 int done = 0, rv = 0;
6390 gf_io_t pc;
6391 int fourlabel = -1, j = 0, next = 0, ku;
6392 int pipe_rv; /* rv of proc to separate from close_system_pipe rv */
6393 long i, rawno;
6394 unsigned flagsforhist = 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6395 static HISTORY_S *history = NULL;
6396 int capture = 1, raw = 0, delimit = 0, newpipe = 0;
6397 char pipe_command[MAXPATH];
6398 ESCKEY_S pipe_opt[8];
6400 if(ps_global->restricted){
6401 q_status_message(SM_ORDER | SM_DING, 0, 4,
6402 "Alpine demo can't pipe messages");
6403 return rv;
6405 else if(!any_messages(msgmap, NULL, "to Pipe"))
6406 return rv;
6408 pipe_command[0] = '\0';
6409 init_hist(&history, HISTSIZE);
6410 flagsforhist = (raw ? 0x8 : 0) +
6411 (delimit ? 0x4 : 0) +
6412 (newpipe ? 0x2 : 0) +
6413 (capture ? 0x1 : 0);
6414 if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){
6415 strncpy(pipe_command, p, sizeof(pipe_command));
6416 pipe_command[sizeof(pipe_command)-1] = '\0';
6417 if(history->hist[history->curindex]){
6418 flagsforhist = history->hist[history->curindex]->flags;
6419 raw = (flagsforhist & 0x8) ? 1 : 0;
6420 delimit = (flagsforhist & 0x4) ? 1 : 0;
6421 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6422 capture = (flagsforhist & 0x1) ? 1 : 0;
6426 pipe_opt[j].ch = 0;
6427 pipe_opt[j].rval = 0;
6428 pipe_opt[j].name = "";
6429 pipe_opt[j++].label = "";
6431 pipe_opt[j].ch = ctrl('W');
6432 pipe_opt[j].rval = 10;
6433 pipe_opt[j].name = "^W";
6434 pipe_opt[j++].label = NULL;
6436 pipe_opt[j].ch = ctrl('Y');
6437 pipe_opt[j].rval = 11;
6438 pipe_opt[j].name = "^Y";
6439 pipe_opt[j++].label = NULL;
6441 pipe_opt[j].ch = ctrl('R');
6442 pipe_opt[j].rval = 12;
6443 pipe_opt[j].name = "^R";
6444 pipe_opt[j++].label = NULL;
6446 if(MCMD_ISAGG(aopt)){
6447 if(!pseudo_selected(state->mail_stream, msgmap))
6448 return rv;
6449 else{
6450 fourlabel = j;
6451 pipe_opt[j].ch = ctrl('T');
6452 pipe_opt[j].rval = 13;
6453 pipe_opt[j].name = "^T";
6454 pipe_opt[j++].label = NULL;
6458 pipe_opt[j].ch = KEY_UP;
6459 pipe_opt[j].rval = 30;
6460 pipe_opt[j].name = "";
6461 ku = j;
6462 pipe_opt[j++].label = "";
6464 pipe_opt[j].ch = KEY_DOWN;
6465 pipe_opt[j].rval = 31;
6466 pipe_opt[j].name = "";
6467 pipe_opt[j++].label = "";
6469 pipe_opt[j].ch = -1;
6471 while (!done) {
6472 int flags;
6474 snprintf(prompt, sizeof(prompt), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6475 raw ? "RAW " : "",
6476 MCMD_ISAGG(aopt) ? "s" : " ",
6477 MCMD_ISAGG(aopt) ? "" : comatose(mn_get_cur(msgmap)),
6478 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? "(" : "",
6479 capture ? "" : "uncaptured",
6480 (!capture && delimit) ? "," : "",
6481 delimit ? "delimited" : "",
6482 ((!capture || delimit) && newpipe && MCMD_ISAGG(aopt)) ? "," : "",
6483 (newpipe && MCMD_ISAGG(aopt)) ? "new pipe" : "",
6484 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? ") " : "");
6485 prompt[sizeof(prompt)-1] = '\0';
6486 pipe_opt[1].label = raw ? N_("Shown Text") : N_("Raw Text");
6487 pipe_opt[2].label = capture ? N_("Free Output") : N_("Capture Output");
6488 pipe_opt[3].label = delimit ? N_("No Delimiter") : N_("With Delimiter");
6489 if(fourlabel > 0)
6490 pipe_opt[fourlabel].label = newpipe ? N_("To Same Pipe") : N_("To Individual Pipes");
6494 * 2 is really 1 because there will be one real entry and
6495 * one entry of "" because of the get_prev_hist above.
6497 if(items_in_hist(history) > 2){
6498 pipe_opt[ku].name = HISTORY_UP_KEYNAME;
6499 pipe_opt[ku].label = HISTORY_KEYLABEL;
6500 pipe_opt[ku+1].name = HISTORY_DOWN_KEYNAME;
6501 pipe_opt[ku+1].label = HISTORY_KEYLABEL;
6503 else{
6504 pipe_opt[ku].name = "";
6505 pipe_opt[ku].label = "";
6506 pipe_opt[ku+1].name = "";
6507 pipe_opt[ku+1].label = "";
6510 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
6511 switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0,
6512 sizeof(pipe_command), prompt,
6513 pipe_opt, NO_HELP, &flags)){
6514 case -1 :
6515 q_status_message(SM_ORDER | SM_DING, 3, 4,
6516 _("Internal problem encountered"));
6517 done++;
6518 break;
6520 case 10 : /* flip raw bit */
6521 raw = !raw;
6522 break;
6524 case 11 : /* flip capture bit */
6525 capture = !capture;
6526 break;
6528 case 12 : /* flip delimit bit */
6529 delimit = !delimit;
6530 break;
6532 case 13 : /* flip newpipe bit */
6533 newpipe = !newpipe;
6534 break;
6536 case 30 :
6537 flagsforhist = (raw ? 0x8 : 0) +
6538 (delimit ? 0x4 : 0) +
6539 (newpipe ? 0x2 : 0) +
6540 (capture ? 0x1 : 0);
6541 if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6542 strncpy(pipe_command, p, sizeof(pipe_command));
6543 pipe_command[sizeof(pipe_command)-1] = '\0';
6544 if(history->hist[history->curindex]){
6545 flagsforhist = history->hist[history->curindex]->flags;
6546 raw = (flagsforhist & 0x8) ? 1 : 0;
6547 delimit = (flagsforhist & 0x4) ? 1 : 0;
6548 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6549 capture = (flagsforhist & 0x1) ? 1 : 0;
6552 else
6553 Writechar(BELL, 0);
6555 break;
6557 case 31 :
6558 flagsforhist = (raw ? 0x8 : 0) +
6559 (delimit ? 0x4 : 0) +
6560 (newpipe ? 0x2 : 0) +
6561 (capture ? 0x1 : 0);
6562 if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6563 strncpy(pipe_command, p, sizeof(pipe_command));
6564 pipe_command[sizeof(pipe_command)-1] = '\0';
6565 if(history->hist[history->curindex]){
6566 flagsforhist = history->hist[history->curindex]->flags;
6567 raw = (flagsforhist & 0x8) ? 1 : 0;
6568 delimit = (flagsforhist & 0x4) ? 1 : 0;
6569 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6570 capture = (flagsforhist & 0x1) ? 1 : 0;
6573 else
6574 Writechar(BELL, 0);
6576 break;
6578 case 0 :
6579 if(pipe_command[0]){
6581 flagsforhist = (raw ? 0x8 : 0) +
6582 (delimit ? 0x4 : 0) +
6583 (newpipe ? 0x2 : 0) +
6584 (capture ? 0x1 : 0);
6585 save_hist(history, pipe_command, flagsforhist, NULL);
6587 flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
6588 flags |= (raw ? PIPE_RAW : 0);
6589 if(!capture){
6590 #ifndef _WINDOWS
6591 ClearScreen();
6592 fflush(stdout);
6593 clear_cursor_pos();
6594 ps_global->mangled_screen = 1;
6595 ps_global->in_init_seq = 1;
6596 #endif
6597 flags |= PIPE_RESET;
6600 if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command,
6601 (flags & PIPE_RESET)
6602 ? NULL
6603 : &resultfilename,
6604 flags, &pc)))
6605 done++;
6607 for(i = mn_first_cur(msgmap);
6608 i > 0L && !done;
6609 i = mn_next_cur(msgmap)){
6610 e = pine_mail_fetchstructure(ps_global->mail_stream,
6611 mn_m2raw(msgmap, i), &b);
6612 if(!(state->mail_stream
6613 && (rawno = mn_m2raw(msgmap, i)) > 0L
6614 && rawno <= state->mail_stream->nmsgs
6615 && (mc = mail_elt(state->mail_stream, rawno))
6616 && mc->valid))
6617 mc = NULL;
6619 if((newpipe
6620 && !(syspipe = cmd_pipe_open(pipe_command,
6621 (flags & PIPE_RESET)
6622 ? NULL
6623 : &resultfilename,
6624 flags, &pc)))
6625 || (delimit && !bezerk_delimiter(e, mc, pc, next++)))
6626 done++;
6628 if(!done){
6629 if(raw){
6630 char *pipe_err;
6632 prime_raw_pipe_getc(ps_global->mail_stream,
6633 mn_m2raw(msgmap, i), -1L, 0L);
6634 gf_filter_init();
6635 gf_link_filter(gf_nvtnl_local, NULL);
6636 if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){
6637 q_status_message1(SM_ORDER|SM_DING,
6638 3, 3,
6639 _("Internal Error: %s"),
6640 pipe_err);
6641 done++;
6644 else if(!format_message(mn_m2raw(msgmap, i), e, b,
6645 NULL, FM_NEW_MESS | FM_NOWRAP, pc))
6646 done++;
6649 if(newpipe)
6650 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6651 done++;
6654 if(!capture)
6655 ps_global->in_init_seq = 0;
6657 if(!newpipe)
6658 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6659 done++;
6660 if(done) /* say we had a problem */
6661 q_status_message(SM_ORDER | SM_DING, 3, 3,
6662 _("Error piping message"));
6663 else if(resultfilename){
6664 rv++;
6665 /* only display if no error */
6666 display_output_file(resultfilename, "PIPE MESSAGE",
6667 NULL, DOF_EMPTY);
6668 fs_give((void **)&resultfilename);
6670 else{
6671 rv++;
6672 q_status_message(SM_ORDER, 0, 2, _("Pipe command completed"));
6675 done++;
6676 break;
6678 /* else fall thru as if cancelled */
6680 case 1 :
6681 cmd_cancelled("Pipe command");
6682 done++;
6683 break;
6685 case 3 :
6686 helper(h_common_pipe, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE);
6687 ps_global->mangled_screen = 1;
6688 break;
6690 case 2 : /* no place to escape to */
6691 case 4 : /* can't suspend */
6692 default :
6693 break;
6697 ps_global->mangled_footer = 1;
6698 if(MCMD_ISAGG(aopt))
6699 restore_selected(msgmap);
6701 return rv;
6705 /*----------------------------------------------------------------------
6706 Screen to offer list management commands contained in message
6708 Args: state -- pointer to struct holding a bunch of pine state
6709 msgmap -- table mapping msg nums to c-client sequence nums
6710 aopt -- aggregate options
6712 Result:
6714 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6715 ----*/
6716 void
6717 rfc2369_display(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno)
6719 int winner = 0;
6720 char *h, *hdrs[MLCMD_COUNT + 1];
6721 long index_no = mn_raw2m(msgmap, msgno);
6722 RFC2369_S data[MLCMD_COUNT];
6724 /* for each header field */
6725 if((h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))) != NULL){
6726 memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT);
6727 if(rfc2369_parse_fields(h, &data[0])){
6728 STORE_S *explain;
6730 if((explain = list_mgmt_text(data, index_no)) != NULL){
6731 list_mgmt_screen(explain);
6732 ps_global->mangled_screen = 1;
6733 so_give(&explain);
6734 winner++;
6738 fs_give((void **) &h);
6741 if(!winner)
6742 q_status_message1(SM_ORDER, 0, 3,
6743 "Message %s contains no list management information",
6744 comatose(index_no));
6748 STORE_S *
6749 list_mgmt_text(RFC2369_S *data, long int msgno)
6751 STORE_S *store;
6752 int i, j, n, fields = 0;
6753 static char *rfc2369_intro1 =
6754 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6755 static char *rfc2369_intro2[] = {
6756 N_(" has information associated with it "),
6757 N_("that explains how to participate in an email list. An "),
6758 N_("email list is represented by a single email address that "),
6759 N_("users sharing a common interest can send messages to (known "),
6760 N_("as posting) which are then redistributed to all members "),
6761 N_("of the list (sometimes after review by a moderator)."),
6762 N_("<P>List participation commands in this message include:"),
6763 NULL
6766 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6768 /* Insert introductory text */
6769 so_puts(store, rfc2369_intro1);
6771 so_puts(store, comatose(msgno));
6773 for(i = 0; rfc2369_intro2[i]; i++)
6774 so_puts(store, _(rfc2369_intro2[i]));
6776 so_puts(store, "<P>");
6777 for(i = 0; i < MLCMD_COUNT; i++)
6778 if(data[i].data[0].value
6779 || data[i].data[0].comment
6780 || data[i].data[0].error){
6781 if(!fields++)
6782 so_puts(store, "<UL>");
6784 so_puts(store, "<LI>");
6785 so_puts(store,
6786 (n = (data[i].data[1].value || data[i].data[1].comment))
6787 ? "Methods to "
6788 : "A method to ");
6790 so_puts(store, data[i].field.description);
6791 so_puts(store, ". ");
6793 if(n)
6794 so_puts(store, "<OL>");
6796 for(j = 0;
6797 j < MLCMD_MAXDATA
6798 && (data[i].data[j].comment
6799 || data[i].data[j].value
6800 || data[i].data[j].error);
6801 j++){
6803 so_puts(store, n ? "<P><LI>" : "<P>");
6805 if(data[i].data[j].comment){
6806 so_puts(store,
6807 _("With the provided comment:<P><BLOCKQUOTE>"));
6808 so_puts(store, data[i].data[j].comment);
6809 so_puts(store, "</BLOCKQUOTE><P>");
6812 if(data[i].data[j].value){
6813 if(i == MLCMD_POST
6814 && !strucmp(data[i].data[j].value, "NO")){
6815 so_puts(store,
6816 _("Posting is <EM>not</EM> allowed on this list"));
6818 else{
6819 so_puts(store, "Select <A HREF=\"");
6820 so_puts(store, data[i].data[j].value);
6821 so_puts(store, "\">HERE</A> to ");
6822 so_puts(store, (data[i].field.action)
6823 ? data[i].field.action
6824 : "try it");
6827 so_puts(store, ".");
6830 if(data[i].data[j].error){
6831 so_puts(store, "<P>Unfortunately, Alpine can not offer");
6832 so_puts(store, " to take direct action based upon it");
6833 so_puts(store, " because it was improperly formatted.");
6834 so_puts(store, " The unrecognized data associated with");
6835 so_puts(store, " the \"");
6836 so_puts(store, data[i].field.name);
6837 so_puts(store, "\" header field was:<P><BLOCKQUOTE>");
6838 so_puts(store, data[i].data[j].error);
6839 so_puts(store, "</BLOCKQUOTE>");
6842 so_puts(store, "<P>");
6845 if(n)
6846 so_puts(store, "</OL>");
6849 if(fields)
6850 so_puts(store, "</UL>");
6852 so_puts(store, "</BODY></HTML>");
6855 return(store);
6859 void
6860 list_mgmt_screen(STORE_S *html)
6862 int cmd = MC_NONE;
6863 long offset = 0L;
6864 char *error = NULL;
6865 STORE_S *store;
6866 HANDLE_S *handles = NULL;
6867 gf_io_t gc, pc;
6870 so_seek(html, 0L, 0);
6871 gf_set_so_readc(&gc, html);
6873 init_handles(&handles);
6875 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6876 gf_set_so_writec(&pc, store);
6877 gf_filter_init();
6879 gf_link_filter(gf_html2plain,
6880 gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols,
6881 non_messageview_margin(), &handles, NULL, 0));
6883 error = gf_pipe(gc, pc);
6885 gf_clear_so_writec(store);
6887 if(!error){
6888 SCROLL_S sargs;
6890 memset(&sargs, 0, sizeof(SCROLL_S));
6891 sargs.text.text = so_text(store);
6892 sargs.text.src = CharStar;
6893 sargs.text.desc = "list commands";
6894 sargs.text.handles = handles;
6895 if(offset){
6896 sargs.start.on = Offset;
6897 sargs.start.loc.offset = offset;
6900 sargs.bar.title = _("MAIL LIST COMMANDS");
6901 sargs.bar.style = MessageNumber;
6902 sargs.resize_exit = 1;
6903 sargs.help.text = h_special_list_commands;
6904 sargs.help.title = _("HELP FOR LIST COMMANDS");
6905 sargs.keys.menu = &listmgr_keymenu;
6906 setbitmap(sargs.keys.bitmap);
6907 if(!handles){
6908 clrbitn(LM_TRY_KEY, sargs.keys.bitmap);
6909 clrbitn(LM_PREV_KEY, sargs.keys.bitmap);
6910 clrbitn(LM_NEXT_KEY, sargs.keys.bitmap);
6913 cmd = scrolltool(&sargs);
6914 offset = sargs.start.loc.offset;
6917 so_give(&store);
6920 free_handles(&handles);
6921 gf_clear_so_readc(html);
6923 while(cmd == MC_RESIZE);
6927 /*----------------------------------------------------------------------
6928 Prompt the user for the type of select desired
6930 NOTE: any and all functions that successfully exit the second
6931 switch() statement below (currently "select_*() functions"),
6932 *MUST* update the folder's MESSAGECACHE element's "searched"
6933 bits to reflect the search result. Functions using
6934 mail_search() get this for free, the others must update 'em
6935 by hand.
6937 Returns -1 if canceled without changing selection
6938 0 if selection may have changed
6939 ----*/
6941 aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index)
6943 long i, diff, old_tot, msgno, raw;
6944 int p = 0, q = 0, rv = 0, narrow = 0, hidden, ret = -1;
6945 ESCKEY_S *sel_opts;
6946 MESSAGECACHE *mc;
6947 SEARCHSET *limitsrch = NULL;
6948 PINETHRD_S *thrd;
6949 extern MAILSTREAM *mm_search_stream;
6950 extern long mm_search_count;
6952 hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
6953 mm_search_stream = state->mail_stream;
6954 mm_search_count = 0L;
6956 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
6957 sel_opts = THRD_INDX() ? sel_opts6 : sel_opts5;
6958 else
6959 sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2;
6961 if(THREADING()){
6962 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH;
6964 else{
6965 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream)){
6966 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_XGMEXT_CH;
6967 sel_opts[SEL_OPTS_THREAD].rval = sel_opts[SEL_OPTS_XGMEXT].rval;
6968 sel_opts[SEL_OPTS_THREAD].name = sel_opts[SEL_OPTS_XGMEXT].name;
6969 sel_opts[SEL_OPTS_THREAD].label = sel_opts[SEL_OPTS_XGMEXT].label;
6970 sel_opts[SEL_OPTS_XGMEXT].ch = -1;
6972 else
6973 sel_opts[SEL_OPTS_THREAD].ch = -1;
6976 if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){
6977 if(THRD_INDX()){
6978 i = 0;
6979 thrd = fetch_thread(state->mail_stream,
6980 mn_m2raw(msgmap, mn_get_cur(msgmap)));
6981 /* check if whole thread is selected or not */
6982 if(thrd &&
6983 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT)
6985 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE))
6986 i = 1;
6988 sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd");
6990 else{
6991 i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap),
6992 MN_SLCT);
6993 sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur");
6996 sel_opts += 2; /* disable extra options */
6997 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
6998 q = double_radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6999 RB_NORM);
7000 else
7001 q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
7002 RB_NORM);
7003 switch(q){
7004 case 'f' : /* flip selection */
7005 msgno = 0L;
7006 for(i = 1L; i <= mn_get_total(msgmap); i++){
7007 ret = 0;
7008 q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT);
7009 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q);
7010 if(hidden){
7011 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q);
7012 if(!msgno && q)
7013 mn_reset_cur(msgmap, msgno = i);
7017 return(ret);
7019 case 'n' : /* narrow selection */
7020 narrow++;
7021 q = 0;
7022 break;
7023 case 'r' : /* replace selection */
7024 p = 1; /* set flag we want to replace */
7025 sel_opts -= 2; /* re-enable first two options */
7026 case 'b' : /* broaden selection */
7027 q = 0; /* offer criteria prompt */
7028 break;
7030 case 'c' : /* Un/Select Current */
7031 case 'a' : /* Unselect All */
7032 case 'x' : /* cancel */
7033 break;
7035 default :
7036 q_status_message(SM_ORDER | SM_DING, 3, 3,
7037 "Unsupported Select option");
7038 return(ret);
7042 if(!q){
7043 while(1){
7044 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
7045 q = double_radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP,
7046 RB_NORM);
7047 else
7048 q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP,
7049 RB_NORM|RB_RET_HELP);
7051 if(q == 3){
7052 helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE);
7053 ps_global->mangled_screen = 1;
7055 else
7056 break;
7060 /* When we are replacing the search, unselect all messages first, unless
7061 * we are cancelling, in whose case, leave the screen as is, because we
7062 * are cancelling!
7064 if(p == 1 && q != 'x'){
7065 msgno = any_lflagged(msgmap, MN_SLCT);
7066 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
7067 agg_select_all(state->mail_stream, msgmap, &diff,
7068 any_lflagged(msgmap, MN_SLCT) <= 0L);
7072 * The purpose of this is to add the appropriate searchset to the
7073 * search so that the search can be limited to only looking at what
7074 * it needs to look at. That is, if we are narrowing then we only need
7075 * to look at messages which are already selected, and if we are
7076 * broadening, then we only need to look at messages which are not
7077 * yet selected. This routine will work whether or not
7078 * limiting_searchset properly limits the search set. In particular,
7079 * the searchset returned by limiting_searchset may include messages
7080 * which really shouldn't be included. We do that because a too-large
7081 * searchset will break some IMAP servers. It is even possible that it
7082 * becomes inefficient to send the whole set. If the select function
7083 * frees limitsrch, it should be sure to set it to NULL so we won't
7084 * try freeing it again here.
7086 limitsrch = limiting_searchset(state->mail_stream, narrow);
7089 * NOTE: See note about MESSAGECACHE "searched" bits above!
7091 switch(q){
7092 case 'x': /* cancel */
7093 cmd_cancelled("Select command");
7094 return(ret);
7096 case 'c' : /* select/unselect current */
7097 (void) select_by_current(state, msgmap, in_index);
7098 ret = 0;
7099 return(ret);
7101 case 'a' : /* select/unselect all */
7102 msgno = any_lflagged(msgmap, MN_SLCT);
7103 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
7104 ret = 0;
7105 agg_select_all(state->mail_stream, msgmap, &diff,
7106 any_lflagged(msgmap, MN_SLCT) <= 0L);
7107 q_status_message4(SM_ORDER,0,2,
7108 "%s%s message%s %sselected",
7109 msgno ? "" : "All ", comatose(diff),
7110 plural(diff), msgno ? "UN" : "");
7111 return(ret);
7113 case 'n' : /* Select by Number */
7114 ret = 0;
7115 if(THRD_INDX())
7116 rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch);
7117 else
7118 rv = select_by_number(state->mail_stream, msgmap, &limitsrch);
7120 break;
7122 case 'd' : /* Select by Date */
7123 ret = 0;
7124 rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap),
7125 &limitsrch);
7126 break;
7128 case 't' : /* Text */
7129 ret = 0;
7130 rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap),
7131 &limitsrch);
7132 break;
7134 case 'z' : /* Size */
7135 ret = 0;
7136 rv = select_by_size(state->mail_stream, &limitsrch);
7137 break;
7139 case 's' : /* Status */
7140 ret = 0;
7141 rv = select_by_status(state->mail_stream, &limitsrch);
7142 break;
7144 case 'k' : /* Keyword */
7145 ret = 0;
7146 rv = select_by_keyword(state->mail_stream, &limitsrch);
7147 break;
7149 case 'r' : /* Rule */
7150 ret = 0;
7151 rv = select_by_rule(state->mail_stream, &limitsrch);
7152 break;
7154 case 'h' : /* Thread */
7155 ret = 0;
7156 rv = select_by_thread(state->mail_stream, msgmap, &limitsrch);
7157 break;
7159 case 'g' : /* X-GM-EXT-1 */
7160 ret = 0;
7161 rv = select_by_gm_content(state->mail_stream, msgmap, mn_get_cur(msgmap), &limitsrch);
7162 break;
7164 default :
7165 q_status_message(SM_ORDER | SM_DING, 3, 3,
7166 "Unsupported Select option");
7167 return(ret);
7170 if(limitsrch)
7171 mail_free_searchset(&limitsrch);
7173 if(rv) /* bad return value.. */
7174 return(ret); /* error already displayed */
7176 if(narrow) /* make sure something was selected */
7177 for(i = 1L; i <= mn_get_total(msgmap); i++)
7178 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7179 && raw <= state->mail_stream->nmsgs
7180 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7181 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
7182 break;
7183 else
7184 mm_search_count--;
7187 diff = 0L;
7188 if(mm_search_count){
7190 * loop thru all the messages, adjusting local flag bits
7191 * based on their "searched" bit...
7193 for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++)
7194 if(narrow){
7195 /* turning OFF selectedness if the "searched" bit isn't lit. */
7196 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7197 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7198 && raw <= state->mail_stream->nmsgs
7199 && (mc = mail_elt(state->mail_stream, raw))
7200 && !mc->searched){
7201 diff--;
7202 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
7203 if(hidden)
7204 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
7206 /* adjust current message in case we unselect and hide it */
7207 else if(msgno < mn_get_cur(msgmap)
7208 && (!THRD_INDX()
7209 || !get_lflag(state->mail_stream, msgmap,
7210 i, MN_CHID)))
7211 msgno = i;
7214 else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7215 && raw <= state->mail_stream->nmsgs
7216 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7217 /* turn ON selectedness if "searched" bit is lit. */
7218 if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7219 diff++;
7220 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
7221 if(hidden)
7222 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
7226 /* if we're zoomed and the current message was unselected */
7227 if(narrow && msgno
7228 && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE))
7229 mn_reset_cur(msgmap, msgno);
7232 if(!diff){
7233 if(narrow)
7234 q_status_message4(SM_ORDER, 3, 3,
7235 "%s. %s message%s remain%s selected.",
7236 mm_search_count
7237 ? "No change resulted"
7238 : "No messages in intersection",
7239 comatose(old_tot), plural(old_tot),
7240 (old_tot == 1L) ? "s" : "");
7241 else if(old_tot)
7242 q_status_message(SM_ORDER, 3, 3,
7243 _("No change resulted. Matching messages already selected."));
7244 else
7245 q_status_message1(SM_ORDER | SM_DING, 3, 3,
7246 _("Select failed. No %smessages selected."),
7247 old_tot ? _("additional ") : "");
7249 else if(old_tot){
7250 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
7251 "Select matched %ld message%s. %s %smessage%s %sselected.",
7252 (diff > 0) ? diff : old_tot + diff,
7253 plural((diff > 0) ? diff : old_tot + diff),
7254 comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7255 (diff > 0) ? "total " : "",
7256 plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7257 (diff > 0) ? "" : "UN");
7258 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
7259 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
7261 else
7262 q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"),
7263 comatose(diff), plural(diff));
7265 return(ret);
7269 /*----------------------------------------------------------------------
7270 Toggle the state of the current message
7272 Args: state -- pointer pine's state variables
7273 msgmap -- message collection to operate on
7274 in_index -- in the message index view
7275 Returns: TRUE if current marked selected, FALSE otw
7276 ----*/
7278 select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
7280 long cur;
7281 int all_selected = 0;
7282 unsigned long was, tot, rawno;
7283 PINETHRD_S *thrd;
7285 cur = mn_get_cur(msgmap);
7287 if(THRD_INDX()){
7288 thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur));
7289 if(!thrd)
7290 return 0;
7292 was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT);
7293 tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE);
7294 if(was == tot)
7295 all_selected++;
7297 if(all_selected){
7298 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0);
7299 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7300 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1);
7302 * See if there's anything left to zoom on. If so,
7303 * pick an adjacent one for highlighting, else make
7304 * sure nothing is left hidden...
7306 if(any_lflagged(msgmap, MN_SLCT)){
7307 mn_inc_cur(state->mail_stream, msgmap,
7308 (in_index == View && THREADING()
7309 && sp_viewing_a_thread(state->mail_stream))
7310 ? MH_THISTHD
7311 : (in_index == View)
7312 ? MH_ANYTHD : MH_NONE);
7313 if(mn_get_cur(msgmap) == cur)
7314 mn_dec_cur(state->mail_stream, msgmap,
7315 (in_index == View && THREADING()
7316 && sp_viewing_a_thread(state->mail_stream))
7317 ? MH_THISTHD
7318 : (in_index == View)
7319 ? MH_ANYTHD : MH_NONE);
7321 else /* clear all hidden flags */
7322 (void) unzoom_index(state, state->mail_stream, msgmap);
7325 else
7326 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1);
7328 q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected",
7329 comatose(all_selected ? was : tot-was),
7330 plural(all_selected ? was : tot-was),
7331 all_selected ? "UN" : "");
7333 /* collapsed thread */
7334 else if(THREADING()
7335 && ((rawno = mn_m2raw(msgmap, cur)) != 0L)
7336 && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL)
7337 && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){
7339 * This doesn't work quite the same as the colon command works, but
7340 * it is arguably doing the correct thing. The difference is
7341 * that aggregate_select will zoom after selecting back where it
7342 * was called from, but selecting a thread with colon won't zoom.
7343 * Maybe it makes sense to zoom after a select but not after a colon
7344 * command even though they are very similar.
7346 thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state));
7348 else{
7349 if((all_selected =
7350 get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */
7351 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0);
7352 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7353 set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1);
7355 * See if there's anything left to zoom on. If so,
7356 * pick an adjacent one for highlighting, else make
7357 * sure nothing is left hidden...
7359 if(any_lflagged(msgmap, MN_SLCT)){
7360 mn_inc_cur(state->mail_stream, msgmap,
7361 (in_index == View && THREADING()
7362 && sp_viewing_a_thread(state->mail_stream))
7363 ? MH_THISTHD
7364 : (in_index == View)
7365 ? MH_ANYTHD : MH_NONE);
7366 if(mn_get_cur(msgmap) == cur)
7367 mn_dec_cur(state->mail_stream, msgmap,
7368 (in_index == View && THREADING()
7369 && sp_viewing_a_thread(state->mail_stream))
7370 ? MH_THISTHD
7371 : (in_index == View)
7372 ? MH_ANYTHD : MH_NONE);
7374 else /* clear all hidden flags */
7375 (void) unzoom_index(state, state->mail_stream, msgmap);
7378 else
7379 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1);
7381 q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected",
7382 long2string(cur), all_selected ? "UN" : "");
7386 return(!all_selected);
7390 /*----------------------------------------------------------------------
7391 Prompt the user for the command to perform on selected messages
7393 Args: state -- pointer pine's state variables
7394 msgmap -- message collection to operate on
7395 q_line -- line on display to write prompts
7396 Returns: 1 if the selected messages are suitably commanded,
7397 0 if the choice to pick the command was declined
7399 ----*/
7401 apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
7402 UCS preloadkeystroke, int flags, int q_line)
7404 int i = 8, /* number of static entries in sel_opts3 */
7405 rv = 0,
7406 cmd,
7407 we_cancel = 0,
7408 agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG;
7409 char prompt[80];
7410 PAT_STATE pstate;
7413 * To do this "right", we really ought to have access to the keymenu
7414 * here and change the typed command into a real command by running
7415 * it through menu_command. Then the switch below would be against
7416 * results from menu_command. If we did that we'd also pass the
7417 * results of menu_command in as preloadkeystroke instead of passing
7418 * the keystroke itself. But we don't have the keymenu handy,
7419 * so we have to fake it. The only complication that we run into
7420 * is that KEY_DEL is an escape sequence so we change a typed
7421 * KEY_DEL esc seq into the letter D.
7424 if(!preloadkeystroke){
7425 if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
7426 sel_opts3[i].ch = '*';
7427 sel_opts3[i].rval = '*';
7428 sel_opts3[i].name = "*";
7429 sel_opts3[i++].label = N_("Flag");
7432 if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
7433 sel_opts3[i].ch = '|';
7434 sel_opts3[i].rval = '|';
7435 sel_opts3[i].name = "|";
7436 sel_opts3[i++].label = N_("Pipe");
7439 if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */
7440 sel_opts3[i].ch = 'b';
7441 sel_opts3[i].rval = 'b';
7442 sel_opts3[i].name = "B";
7443 sel_opts3[i++].label = N_("Bounce");
7446 if(flags & AC_FROM_THREAD){
7447 if(flags & (AC_COLL | AC_EXPN)){
7448 sel_opts3[i].ch = '/';
7449 sel_opts3[i].rval = '/';
7450 sel_opts3[i].name = "/";
7451 sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse")
7452 : N_("Expand");
7455 sel_opts3[i].ch = ';';
7456 sel_opts3[i].rval = ';';
7457 sel_opts3[i].name = ";";
7458 if(flags & AC_UNSEL)
7459 sel_opts3[i++].label = N_("UnSelect");
7460 else
7461 sel_opts3[i++].label = N_("Select");
7464 if(F_ON(F_ENABLE_PRYNT, state)){ /* this one is invisible */
7465 sel_opts3[i].ch = 'y';
7466 sel_opts3[i].rval = '%';
7467 sel_opts3[i].name = "";
7468 sel_opts3[i++].label = "";
7471 if(!is_imap_stream(stream) || LEVELUIDPLUS(stream)){ /* expunge selected messages */
7472 sel_opts3[i].ch = 'x';
7473 sel_opts3[i].rval = 'x';
7474 sel_opts3[i].name = "X";
7475 sel_opts3[i++].label = N_("Expunge");
7478 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7479 sel_opts3[i].ch = '#';
7480 sel_opts3[i].rval = '#';
7481 sel_opts3[i].name = "#";
7482 sel_opts3[i++].label = N_("Set Role");
7485 sel_opts3[i].ch = KEY_DEL; /* also invisible */
7486 sel_opts3[i].rval = 'd';
7487 sel_opts3[i].name = "";
7488 sel_opts3[i++].label = "";
7490 sel_opts3[i].ch = -1;
7492 snprintf(prompt, sizeof(prompt), "%s command : ",
7493 (flags & AC_FROM_THREAD) ? "THREAD" : "APPLY");
7494 prompt[sizeof(prompt)-1] = '\0';
7495 cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'c', NO_HELP,
7496 RB_SEQ_SENSITIVE);
7497 if(isupper(cmd))
7498 cmd = tolower(cmd);
7500 else{
7501 if(preloadkeystroke == KEY_DEL)
7502 cmd = 'd';
7503 else{
7504 if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke))
7505 cmd = tolower((int) preloadkeystroke);
7506 else
7507 cmd = (int) preloadkeystroke; /* shouldn't happen */
7511 switch(cmd){
7512 case 'd' : /* delete */
7513 we_cancel = busy_cue(NULL, NULL, 1);
7514 rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */
7515 if(we_cancel)
7516 cancel_busy_cue(0);
7517 break;
7519 case 'u' : /* undelete */
7520 we_cancel = busy_cue(NULL, NULL, 1);
7521 rv = cmd_undelete(state, msgmap, agg);
7522 if(we_cancel)
7523 cancel_busy_cue(0);
7524 break;
7526 case 'r' : /* reply */
7527 rv = cmd_reply(state, msgmap, agg, NULL);
7528 break;
7530 case 'f' : /* Forward */
7531 rv = cmd_forward(state, msgmap, agg, NULL);
7532 break;
7534 case '%' : /* print */
7535 rv = cmd_print(state, msgmap, agg, MsgIndx);
7536 break;
7538 case 't' : /* take address */
7539 rv = cmd_take_addr(state, msgmap, agg);
7540 break;
7542 case 's' : /* save */
7543 rv = cmd_save(state, stream, msgmap, agg, MsgIndx);
7544 break;
7546 case 'e' : /* export */
7547 rv = cmd_export(state, msgmap, q_line, agg);
7548 break;
7550 case '|' : /* pipe */
7551 rv = cmd_pipe(state, msgmap, agg);
7552 break;
7554 case '*' : /* flag */
7555 we_cancel = busy_cue(NULL, NULL, 1);
7556 rv = cmd_flag(state, msgmap, agg);
7557 if(we_cancel)
7558 cancel_busy_cue(0);
7559 break;
7561 case 'b' : /* bounce */
7562 rv = cmd_bounce(state, msgmap, agg, NULL);
7563 break;
7565 case '/' :
7566 collapse_or_expand(state, stream, msgmap,
7567 F_ON(F_SLASH_COLL_ENTIRE, ps_global)
7568 ? 0L
7569 : mn_get_cur(msgmap));
7570 break;
7572 case ':' :
7573 select_thread_stmp(state, stream, msgmap);
7574 break;
7576 case 'x' : /* Expunge */
7577 rv = cmd_expunge(state, stream, msgmap, agg);
7578 break;
7580 case 'c' : /* cancel */
7581 cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command"
7582 : "Apply command");
7583 break;
7585 case '#' :
7586 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7587 static ESCKEY_S choose_role[] = {
7588 {'r', 'r', "R", N_("Reply")},
7589 {'f', 'f', "F", N_("Forward")},
7590 {'b', 'b', "B", N_("Bounce")},
7591 {-1, 0, NULL, NULL}
7593 int action;
7594 ACTION_S *role = NULL;
7596 action = radio_buttons(_("Reply, Forward or Bounce using a role? "),
7597 -FOOTER_ROWS(state), choose_role,
7598 'r', 'x', h_role_aggregate, RB_NORM);
7599 if(action == 'r' || action == 'f' || action == 'b'){
7600 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
7602 redraw = state->redrawer;
7603 state->redrawer = NULL;
7604 prev_screen = state->prev_screen;
7605 role = NULL;
7606 state->next_screen = SCREEN_FUN_NULL;
7608 if(role_select_screen(state, &role,
7609 action == 'f' ? MC_FORWARD :
7610 action == 'r' ? MC_REPLY :
7611 action == 'b' ? MC_BOUNCE : 0) < 0){
7612 cmd_cancelled(action == 'f' ? _("Forward") :
7613 action == 'r' ? _("Reply") : _("Bounce"));
7614 state->next_screen = prev_screen;
7615 state->redrawer = redraw;
7616 state->mangled_screen = 1;
7618 else{
7619 if(role)
7620 role = combine_inherited_role(role);
7621 else{
7622 role = (ACTION_S *) fs_get(sizeof(*role));
7623 memset((void *) role, 0, sizeof(*role));
7624 role->nick = cpystr("Default Role");
7627 state->redrawer = NULL;
7628 switch(action){
7629 case 'r':
7630 (void) cmd_reply(state, msgmap, agg, role);
7631 break;
7633 case 'f':
7634 (void) cmd_forward(state, msgmap, agg, role);
7635 break;
7637 case 'b':
7638 (void) cmd_bounce(state, msgmap, agg, role);
7639 break;
7642 if(role)
7643 free_action(&role);
7645 if(redraw)
7646 (*redraw)();
7648 state->next_screen = prev_screen;
7649 state->redrawer = redraw;
7650 state->mangled_screen = 1;
7654 break;
7656 case 'z' : /* default */
7657 q_status_message(SM_INFO, 0, 2,
7658 "Cancelled, there is no default command");
7659 break;
7661 default:
7662 break;
7665 return(rv);
7670 * Select by message number ranges.
7671 * Sets searched bits in mail_elts
7673 * Args limitsrch -- limit search to this searchset
7675 * Returns 0 on success.
7678 select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
7680 int r, end, cur;
7681 long n1, n2, raw;
7682 char number1[16], number2[16], numbers[80], *p, *t;
7683 HelpType help;
7684 MESSAGECACHE *mc;
7686 numbers[0] = '\0';
7687 ps_global->mangled_footer = 1;
7688 help = NO_HELP;
7689 while(1){
7690 int flags = OE_APPEND_CURRENT;
7692 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7693 sizeof(numbers), _(select_num), NULL, help, &flags);
7694 if(r == 4)
7695 continue;
7697 if(r == 3){
7698 help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
7699 continue;
7702 for(t = p = numbers; *p ; p++) /* strip whitespace */
7703 if(!isspace((unsigned char)*p))
7704 *t++ = *p;
7706 *t = '\0';
7708 if(r == 1 || numbers[0] == '\0'){
7709 cmd_cancelled("Selection by number");
7710 return(1);
7712 else
7713 break;
7716 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7717 if((mc = mail_elt(stream, n1)) != NULL)
7718 mc->searched = 0; /* clear searched bits */
7720 for(p = numbers; *p ; p++){
7721 t = number1;
7722 while(*p && isdigit((unsigned char)*p))
7723 *t++ = *p++;
7725 *t = '\0';
7727 end = cur = 0;
7728 if(number1[0] == '\0'){
7729 if(*p == '-'){
7730 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7731 _("Invalid number range, missing number before \"-\": %s"),
7732 numbers);
7733 return(1);
7735 else if(!strucmp("end", p)){
7736 end = 1;
7737 p += strlen("end");
7739 else if(!strucmp("$", p)){
7740 end = 1;
7741 p++;
7743 else if(*p == '.'){
7744 cur = 1;
7745 p++;
7747 else{
7748 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7749 _("Invalid message number: %s"), numbers);
7750 return(1);
7754 if(end)
7755 n1 = mn_get_total(msgmap);
7756 else if(cur)
7757 n1 = mn_get_cur(msgmap);
7758 else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
7759 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7760 _("\"%s\" out of message number range"),
7761 long2string(n1));
7762 return(1);
7765 t = number2;
7766 if(*p == '-'){
7767 while(*++p && isdigit((unsigned char)*p))
7768 *t++ = *p;
7770 *t = '\0';
7772 end = cur = 0;
7773 if(number2[0] == '\0'){
7774 if(!strucmp("end", p)){
7775 end = 1;
7776 p += strlen("end");
7778 else if(!strucmp(p, "$")){
7779 end = 1;
7780 p++;
7782 else if(*p == '.'){
7783 cur = 1;
7784 p++;
7786 else{
7787 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7788 _("Invalid number range, missing number after \"-\": %s"),
7789 numbers);
7790 return(1);
7794 if(end)
7795 n2 = mn_get_total(msgmap);
7796 else if(cur)
7797 n2 = mn_get_cur(msgmap);
7798 else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){
7799 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7800 _("\"%s\" out of message number range"),
7801 long2string(n2));
7802 return(1);
7805 if(n2 <= n1){
7806 char t[20];
7808 strncpy(t, long2string(n1), sizeof(t));
7809 t[sizeof(t)-1] = '\0';
7810 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7811 _("Invalid reverse message number range: %s-%s"),
7812 t, long2string(n2));
7813 return(1);
7816 for(;n1 <= n2; n1++){
7817 raw = mn_m2raw(msgmap, n1);
7818 if(raw > 0L
7819 && (!(limitsrch && *limitsrch)
7820 || in_searchset(*limitsrch, (unsigned long) raw)))
7821 mm_searched(stream, raw);
7824 else{
7825 raw = mn_m2raw(msgmap, n1);
7826 if(raw > 0L
7827 && (!(limitsrch && *limitsrch)
7828 || in_searchset(*limitsrch, (unsigned long) raw)))
7829 mm_searched(stream, raw);
7832 if(*p == '\0')
7833 break;
7836 return(0);
7841 * Select by thread number ranges.
7842 * Sets searched bits in mail_elts
7844 * Args limitsrch -- limit search to this searchset
7846 * Returns 0 on success.
7849 select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset)
7851 int r, end, cur;
7852 long n1, n2;
7853 char number1[16], number2[16], numbers[80], *p, *t;
7854 HelpType help;
7855 PINETHRD_S *thrd = NULL, *th;
7856 MESSAGECACHE *mc;
7858 numbers[0] = '\0';
7859 ps_global->mangled_footer = 1;
7860 help = NO_HELP;
7861 while(1){
7862 int flags = OE_APPEND_CURRENT;
7864 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7865 sizeof(numbers), _(select_num), NULL, help, &flags);
7866 if(r == 4)
7867 continue;
7869 if(r == 3){
7870 help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP;
7871 continue;
7874 for(t = p = numbers; *p ; p++) /* strip whitespace */
7875 if(!isspace((unsigned char)*p))
7876 *t++ = *p;
7878 *t = '\0';
7880 if(r == 1 || numbers[0] == '\0'){
7881 cmd_cancelled("Selection by number");
7882 return(1);
7884 else
7885 break;
7888 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7889 if((mc = mail_elt(stream, n1)) != NULL)
7890 mc->searched = 0; /* clear searched bits */
7892 for(p = numbers; *p ; p++){
7893 t = number1;
7894 while(*p && isdigit((unsigned char)*p))
7895 *t++ = *p++;
7897 *t = '\0';
7899 end = cur = 0;
7900 if(number1[0] == '\0'){
7901 if(*p == '-'){
7902 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7903 _("Invalid number range, missing number before \"-\": %s"),
7904 numbers);
7905 return(1);
7907 else if(!strucmp("end", p)){
7908 end = 1;
7909 p += strlen("end");
7911 else if(!strucmp(p, "$")){
7912 end = 1;
7913 p++;
7915 else if(*p == '.'){
7916 cur = 1;
7917 p++;
7919 else{
7920 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7921 _("Invalid thread number: %s"), numbers);
7922 return(1);
7926 if(end)
7927 n1 = msgmap->max_thrdno;
7928 else if(cur){
7929 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7930 n1 = th->thrdno;
7932 else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){
7933 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7934 _("\"%s\" out of thread number range"),
7935 long2string(n1));
7936 return(1);
7939 t = number2;
7940 if(*p == '-'){
7942 while(*++p && isdigit((unsigned char)*p))
7943 *t++ = *p;
7945 *t = '\0';
7947 end = 0;
7948 if(number2[0] == '\0'){
7949 if(!strucmp("end", p)){
7950 end = 1;
7951 p += strlen("end");
7953 else if(!strucmp("$", p)){
7954 end = 1;
7955 p++;
7957 else if(*p == '.'){
7958 cur = 1;
7959 p++;
7961 else{
7962 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7963 _("Invalid number range, missing number after \"-\": %s"),
7964 numbers);
7965 return(1);
7969 if(end)
7970 n2 = msgmap->max_thrdno;
7971 else if(cur){
7972 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7973 n2 = th->thrdno;
7975 else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){
7976 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7977 _("\"%s\" out of thread number range"),
7978 long2string(n2));
7979 return(1);
7982 if(n2 <= n1){
7983 char t[20];
7985 strncpy(t, long2string(n1), sizeof(t));
7986 t[sizeof(t)-1] = '\0';
7987 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7988 _("Invalid reverse message number range: %s-%s"),
7989 t, long2string(n2));
7990 return(1);
7993 for(;n1 <= n2; n1++){
7994 thrd = find_thread_by_number(stream, msgmap, n1, thrd);
7996 if(thrd)
7997 set_search_bit_for_thread(stream, thrd, msgset);
8000 else{
8001 thrd = find_thread_by_number(stream, msgmap, n1, NULL);
8003 if(thrd)
8004 set_search_bit_for_thread(stream, thrd, msgset);
8007 if(*p == '\0')
8008 break;
8011 return(0);
8016 * Select by message dates.
8017 * Sets searched bits in mail_elts
8019 * Args limitsrch -- limit search to this searchset
8021 * Returns 0 on success.
8024 select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8026 int r, we_cancel = 0, when = 0;
8027 char date[100], defdate[100], prompt[128];
8028 time_t seldate = time(0);
8029 struct tm *seldate_tm;
8030 SEARCHPGM *pgm;
8031 HelpType help;
8032 static struct _tense {
8033 char *preamble,
8034 *range,
8035 *scope;
8036 } tense[] = {
8037 {"were ", "SENT SINCE", " (inclusive)"},
8038 {"were ", "SENT BEFORE", " (exclusive)"},
8039 {"were ", "SENT ON", "" },
8040 {"", "ARRIVED SINCE", " (inclusive)"},
8041 {"", "ARRIVED BEFORE", " (exclusive)"},
8042 {"", "ARRIVED ON", "" }
8045 date[0] = '\0';
8046 ps_global->mangled_footer = 1;
8047 help = NO_HELP;
8050 * If talking to an old server, default to SINCE instead of
8051 * SENTSINCE, which was added later.
8053 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8054 when = 3;
8056 while(1){
8057 int flags = OE_APPEND_CURRENT;
8059 seldate_tm = localtime(&seldate);
8060 snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday,
8061 month_abbrev(seldate_tm->tm_mon + 1),
8062 seldate_tm->tm_year + 1900);
8063 defdate[sizeof(defdate)-1] = '\0';
8064 snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ",
8065 tense[when].preamble, tense[when].range,
8066 tense[when].scope, defdate);
8067 prompt[sizeof(prompt)-1] = '\0';
8068 r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date),
8069 prompt, sel_date_opt, help, &flags);
8070 switch (r){
8071 case 1 :
8072 cmd_cancelled("Selection by date");
8073 return(1);
8075 case 3 :
8076 help = (help == NO_HELP) ? h_select_date : NO_HELP;
8077 continue;
8079 case 4 :
8080 continue;
8082 case 11 :
8084 MESSAGECACHE *mc;
8085 long rawno;
8087 if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L
8088 && rawno <= stream->nmsgs
8089 && (mc = mail_elt(stream, rawno))){
8091 /* cache not filled in yet? */
8092 if(mc->day == 0){
8093 char seq[20];
8095 if(stream->dtb && stream->dtb->flags & DR_NEWS){
8096 strncpy(seq,
8097 ulong2string(mail_uid(stream, rawno)),
8098 sizeof(seq));
8099 seq[sizeof(seq)-1] = '\0';
8100 mail_fetch_overview(stream, seq, NULL);
8102 else{
8103 strncpy(seq, long2string(rawno),
8104 sizeof(seq));
8105 seq[sizeof(seq)-1] = '\0';
8106 mail_fetch_fast(stream, seq, 0L);
8110 /* mail_date returns fixed field width date */
8111 mail_date(date, mc);
8112 date[11] = '\0';
8116 continue;
8118 case 12 : /* set default to PREVIOUS day */
8119 seldate -= 86400;
8120 continue;
8122 case 13 : /* set default to NEXT day */
8123 seldate += 86400;
8124 continue;
8126 case 14 :
8127 when = (when+1) % (sizeof(tense) / sizeof(struct _tense));
8128 continue;
8130 default:
8131 break;
8134 removing_leading_white_space(date);
8135 removing_trailing_white_space(date);
8136 if(!*date){
8137 strncpy(date, defdate, sizeof(date));
8138 date[sizeof(date)-1] = '\0';
8141 break;
8144 if((pgm = mail_newsearchpgm()) != NULL){
8145 MESSAGECACHE elt;
8146 short converted_date;
8148 if(mail_parse_date(&elt, (unsigned char *) date)){
8149 converted_date = mail_shortdate(elt.year, elt.month, elt.day);
8151 switch(when){
8152 case 0:
8153 pgm->sentsince = converted_date;
8154 break;
8155 case 1:
8156 pgm->sentbefore = converted_date;
8157 break;
8158 case 2:
8159 pgm->senton = converted_date;
8160 break;
8161 case 3:
8162 pgm->since = converted_date;
8163 break;
8164 case 4:
8165 pgm->before = converted_date;
8166 break;
8167 case 5:
8168 pgm->on = converted_date;
8169 break;
8172 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8174 if(ps_global && ps_global->ttyo){
8175 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8176 ps_global->mangled_footer = 1;
8179 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8181 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8183 if(we_cancel)
8184 cancel_busy_cue(0);
8186 /* we know this was freed in mail_search, let caller know */
8187 if(limitsrch)
8188 *limitsrch = NULL;
8190 else{
8191 mail_free_searchpgm(&pgm);
8192 q_status_message1(SM_ORDER, 3, 3,
8193 _("Invalid date entered: %s"), date);
8194 return(1);
8198 return(0);
8202 * Select by searching in message headers or body
8203 * using the x-gm-ext-1 capaility. This function
8204 * reads the input from the user and passes it to
8205 * the server directly. We need a x-gm-ext-1 variable
8206 * in the search pgm to carry this information to the
8207 * server.
8209 * Sets searched bits in mail_elts
8211 * Args limitsrch -- limit search to this searchset
8213 * Returns 0 on success.
8216 select_by_gm_content(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8218 int r, we_cancel = 0, rv;
8219 char tmp[128];
8220 char namehdr[MAILTMPLEN];
8221 ESCKEY_S ekey[8];
8222 HelpType help;
8224 ps_global->mangled_footer = 1;
8225 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8227 strncpy(tmp, sel_x_gm_ext, sizeof(tmp)-1);
8228 tmp[sizeof(tmp)-1] = '\0';
8230 namehdr[0] = '\0';
8232 help = NO_HELP;
8233 while (1){
8234 int flags = OE_APPEND_CURRENT;
8236 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8237 sizeof(namehdr), tmp, ekey, help, &flags);
8239 if(r == 4)
8240 continue;
8242 if(r == 3){
8243 help = (help == NO_HELP) ? h_select_by_gm_content : NO_HELP;
8244 continue;
8247 if (r == 1){
8248 cmd_cancelled("Selection by content");
8249 return(1);
8252 removing_leading_white_space(namehdr);
8254 if ((namehdr[0] != '\0')
8255 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8256 removing_trailing_white_space(namehdr);
8258 if (namehdr[0] != '\0')
8259 break;
8262 if(ps_global && ps_global->ttyo){
8263 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8264 ps_global->mangled_footer = 1;
8267 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8269 rv = agg_text_select(stream, msgmap, 'g', namehdr, 0, 0, NULL, "utf-8", limitsrch);
8270 if(we_cancel)
8271 cancel_busy_cue(0);
8273 return(rv);
8277 * Select by searching in message headers or body.
8278 * Sets searched bits in mail_elts
8280 * Args limitsrch -- limit search to this searchset
8282 * Returns 0 on success.
8285 select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8287 int r = '\0', ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
8288 int not = 0, me = 0;
8289 char sstring[512], tmp[128];
8290 char *p, *sval = NULL;
8291 char namehdr[80];
8292 ESCKEY_S ekey[8];
8293 ENVELOPE *env = NULL;
8294 HelpType help;
8295 unsigned flagsforhist = 0;
8296 static HISTORY_S *history = NULL;
8297 static char *recip = "RECIPIENTS";
8298 static char *partic = "PARTICIPANTS";
8299 static char *match_me = N_("[Match_My_Addresses]");
8300 static char *dont_match_me = N_("[Don't_Match_My_Addresses]");
8302 ps_global->mangled_footer = 1;
8303 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8305 while(1){
8306 type = radio_buttons(not ? _(sel_text_not) : _(sel_text),
8307 -FOOTER_ROWS(ps_global), sel_text_opt,
8308 's', 'x', NO_HELP, RB_NORM|RB_RET_HELP);
8310 if(type == '!')
8311 not = !not;
8312 else if(type == 3){
8313 helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS",
8314 HLPD_SIMPLE);
8315 ps_global->mangled_screen = 1;
8317 else
8318 break;
8322 * prepare some friendly defaults...
8324 switch(type){
8325 case 't' : /* address fields, offer To or From */
8326 case 'f' :
8327 case 'c' :
8328 case 'r' :
8329 case 'p' :
8330 sval = (type == 't') ? "TO" :
8331 (type == 'f') ? "FROM" :
8332 (type == 'c') ? "CC" :
8333 (type == 'r') ? recip : partic;
8334 ekey[ekeyi].ch = ctrl('T');
8335 ekey[ekeyi].name = "^T";
8336 ekey[ekeyi].rval = 10;
8337 /* TRANSLATORS: use Current To Address */
8338 ekey[ekeyi++].label = N_("Cur To");
8339 ekey[ekeyi].ch = ctrl('R');
8340 ekey[ekeyi].name = "^R";
8341 ekey[ekeyi].rval = 11;
8342 /* TRANSLATORS: use Current From Address */
8343 ekey[ekeyi++].label = N_("Cur From");
8344 ekey[ekeyi].ch = ctrl('W');
8345 ekey[ekeyi].name = "^W";
8346 ekey[ekeyi].rval = 12;
8347 /* TRANSLATORS: use Current Cc Address */
8348 ekey[ekeyi++].label = N_("Cur Cc");
8349 ekey[ekeyi].ch = ctrl('Y');
8350 ekey[ekeyi].name = "^Y";
8351 ekey[ekeyi].rval = 13;
8352 /* TRANSLATORS: Match Me means match my address */
8353 ekey[ekeyi++].label = N_("Match Me");
8354 ekey[ekeyi].ch = 0;
8355 ekey[ekeyi].name = "";
8356 ekey[ekeyi].rval = 0;
8357 ekey[ekeyi++].label = "";
8358 break;
8360 case 's' :
8361 sval = "SUBJECT";
8362 ekey[ekeyi].ch = ctrl('X');
8363 ekey[ekeyi].name = "^X";
8364 ekey[ekeyi].rval = 14;
8365 /* TRANSLATORS: use Current Subject */
8366 ekey[ekeyi++].label = N_("Cur Subject");
8367 break;
8369 case 'a' :
8370 sval = "TEXT";
8371 break;
8373 case 'b' :
8374 sval = "BODYTEXT";
8375 break;
8377 case 'h' :
8378 strncpy(tmp, "Name of HEADER to match : ", sizeof(tmp)-1);
8379 tmp[sizeof(tmp)-1] = '\0';
8380 flags = OE_APPEND_CURRENT;
8381 namehdr[0] = '\0';
8382 r = 'x';
8383 while (r == 'x'){
8384 int done = 0;
8386 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8387 sizeof(namehdr), tmp, ekey, NO_HELP, &flags);
8388 if (r == 1){
8389 cmd_cancelled("Selection by text");
8390 return(1);
8392 removing_leading_white_space(namehdr);
8393 while(!done){
8394 while ((namehdr[0] != '\0') && /* remove trailing ":" */
8395 (namehdr[strlen(namehdr) - 1] == ':'))
8396 namehdr[strlen(namehdr) - 1] = '\0';
8397 if ((namehdr[0] != '\0')
8398 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8399 removing_trailing_white_space(namehdr);
8400 else
8401 done++;
8403 if (strchr(namehdr,' ') || strchr(namehdr,'\t') ||
8404 strchr(namehdr,':'))
8405 namehdr[0] = '\0';
8406 if (namehdr[0] == '\0')
8407 r = 'x';
8409 sval = namehdr;
8410 break;
8412 case 'x':
8413 break;
8415 default:
8416 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
8417 return(1);
8420 ekey[ekeyi].ch = KEY_UP;
8421 ekey[ekeyi].rval = 30;
8422 ekey[ekeyi].name = "";
8423 ku = ekeyi;
8424 ekey[ekeyi++].label = "";
8426 ekey[ekeyi].ch = KEY_DOWN;
8427 ekey[ekeyi].rval = 31;
8428 ekey[ekeyi].name = "";
8429 ekey[ekeyi++].label = "";
8431 ekey[ekeyi].ch = -1;
8433 if(type != 'x'){
8435 init_hist(&history, HISTSIZE);
8437 if(ekey[0].ch > -1 && msgno > 0L
8438 && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),
8439 NULL)))
8440 ekey[0].ch = -1;
8442 sstring[0] = '\0';
8443 help = NO_HELP;
8444 r = type;
8445 while(r != 'x'){
8446 if(not)
8447 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8448 snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval);
8449 else
8450 snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval);
8452 if(items_in_hist(history) > 0){
8453 ekey[ku].name = HISTORY_UP_KEYNAME;
8454 ekey[ku].label = HISTORY_KEYLABEL;
8455 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
8456 ekey[ku+1].label = HISTORY_KEYLABEL;
8458 else{
8459 ekey[ku].name = "";
8460 ekey[ku].label = "";
8461 ekey[ku+1].name = "";
8462 ekey[ku+1].label = "";
8465 flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
8466 r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
8467 sizeof(sstring), tmp, ekey, help, &flags);
8469 if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me)))))
8470 me = 0;
8472 switch(r){
8473 case 3 :
8474 help = (help == NO_HELP)
8475 ? (not
8476 ? ((type == 'f') ? h_select_txt_not_from
8477 : (type == 't') ? h_select_txt_not_to
8478 : (type == 'c') ? h_select_txt_not_cc
8479 : (type == 's') ? h_select_txt_not_subj
8480 : (type == 'a') ? h_select_txt_not_all
8481 : (type == 'r') ? h_select_txt_not_recip
8482 : (type == 'p') ? h_select_txt_not_partic
8483 : (type == 'b') ? h_select_txt_not_body
8484 : NO_HELP)
8485 : ((type == 'f') ? h_select_txt_from
8486 : (type == 't') ? h_select_txt_to
8487 : (type == 'c') ? h_select_txt_cc
8488 : (type == 's') ? h_select_txt_subj
8489 : (type == 'a') ? h_select_txt_all
8490 : (type == 'r') ? h_select_txt_recip
8491 : (type == 'p') ? h_select_txt_partic
8492 : (type == 'b') ? h_select_txt_body
8493 : NO_HELP))
8494 : NO_HELP;
8496 case 4 :
8497 continue;
8499 case 10 : /* To: default */
8500 if(env && env->to && env->to->mailbox){
8501 snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox,
8502 env->to->host ? "@" : "",
8503 env->to->host ? env->to->host : "");
8504 sstring[sizeof(sstring)-1] = '\0';
8506 continue;
8508 case 11 : /* From: default */
8509 if(env && env->from && env->from->mailbox){
8510 snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox,
8511 env->from->host ? "@" : "",
8512 env->from->host ? env->from->host : "");
8513 sstring[sizeof(sstring)-1] = '\0';
8515 continue;
8517 case 12 : /* Cc: default */
8518 if(env && env->cc && env->cc->mailbox){
8519 snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox,
8520 env->cc->host ? "@" : "",
8521 env->cc->host ? env->cc->host : "");
8522 sstring[sizeof(sstring)-1] = '\0';
8524 continue;
8526 case 13 : /* Match my addresses */
8527 me++;
8528 snprintf(sstring, sizeof(sstring), "%s", not ? _(dont_match_me) : _(match_me));
8529 continue;
8531 case 14 : /* Subject: default */
8532 if(env && env->subject && env->subject[0]){
8533 char *q = NULL;
8535 q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
8536 SIZEOF_20KBUF, env->subject);
8537 snprintf(sstring, sizeof(sstring), "%s", q);
8538 sstring[sizeof(sstring)-1] = '\0';
8541 continue;
8543 case 30 :
8544 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8545 if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){
8546 strncpy(sstring, p, sizeof(sstring));
8547 sstring[sizeof(sstring)-1] = '\0';
8548 if(history->hist[history->curindex]){
8549 flagsforhist = history->hist[history->curindex]->flags;
8550 not = (flagsforhist & 0x1) ? 1 : 0;
8551 me = (flagsforhist & 0x2) ? 1 : 0;
8554 else
8555 Writechar(BELL, 0);
8557 continue;
8559 case 31 :
8560 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8561 if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){
8562 strncpy(sstring, p, sizeof(sstring));
8563 sstring[sizeof(sstring)-1] = '\0';
8564 if(history->hist[history->curindex]){
8565 flagsforhist = history->hist[history->curindex]->flags;
8566 not = (flagsforhist & 0x1) ? 1 : 0;
8567 me = (flagsforhist & 0x2) ? 1 : 0;
8570 else
8571 Writechar(BELL, 0);
8573 continue;
8575 default :
8576 break;
8579 if(r == 1 || sstring[0] == '\0')
8580 r = 'x';
8582 break;
8586 if(type == 'x' || r == 'x'){
8587 cmd_cancelled("Selection by text");
8588 return(1);
8591 if(ps_global && ps_global->ttyo){
8592 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8593 ps_global->mangled_footer = 1;
8596 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8598 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8599 save_hist(history, sstring, flagsforhist, NULL);
8601 rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch);
8602 if(we_cancel)
8603 cancel_busy_cue(0);
8605 return(rv);
8610 * Select by message size.
8611 * Sets searched bits in mail_elts
8613 * Args limitsrch -- limit search to this searchset
8615 * Returns 0 on success.
8618 select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch)
8620 int r, large = 1, we_cancel = 0;
8621 unsigned long n, mult = 1L, numerator = 0L, divisor = 1L;
8622 char size[16], numbers[80], *p, *t;
8623 HelpType help;
8624 SEARCHPGM *pgm;
8625 long flags = (SE_NOPREFETCH | SE_FREE);
8627 numbers[0] = '\0';
8628 ps_global->mangled_footer = 1;
8630 help = NO_HELP;
8631 while(1){
8632 int flgs = OE_APPEND_CURRENT;
8634 sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger;
8636 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
8637 sizeof(numbers), large ? _(select_size_larger_msg)
8638 : _(select_size_smaller_msg),
8639 sel_size_opt, help, &flgs);
8640 if(r == 4)
8641 continue;
8643 if(r == 14){
8644 large = 1 - large;
8645 continue;
8648 if(r == 3){
8649 help = (help == NO_HELP) ? (large ? h_select_by_larger_size
8650 : h_select_by_smaller_size)
8651 : NO_HELP;
8652 continue;
8655 for(t = p = numbers; *p ; p++) /* strip whitespace */
8656 if(!isspace((unsigned char)*p))
8657 *t++ = *p;
8659 *t = '\0';
8661 if(r == 1 || numbers[0] == '\0'){
8662 cmd_cancelled("Selection by size");
8663 return(1);
8665 else
8666 break;
8669 if(numbers[0] == '-'){
8670 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8671 _("Invalid size entered: %s"), numbers);
8672 return(1);
8675 t = size;
8676 p = numbers;
8678 while(*p && isdigit((unsigned char)*p))
8679 *t++ = *p++;
8681 *t = '\0';
8683 if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){
8684 size[0] = '0';
8685 size[1] = '\0';
8688 if(size[0] == '\0'){
8689 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8690 _("Invalid size entered: %s"), numbers);
8691 return(1);
8694 n = strtoul(size, (char **)NULL, 10);
8696 size[0] = '\0';
8697 if(*p == '.'){
8699 * We probably ought to just use atof() to convert 1.1 into a
8700 * double, but since we haven't used atof() anywhere else I'm
8701 * reluctant to use it because of portability concerns.
8703 p++;
8704 t = size;
8705 while(*p && isdigit((unsigned char)*p)){
8706 *t++ = *p++;
8707 divisor *= 10;
8710 *t = '\0';
8712 if(size[0])
8713 numerator = strtoul(size, (char **)NULL, 10);
8716 switch(*p){
8717 case 'g':
8718 case 'G':
8719 mult *= 1000;
8720 /* fall through */
8722 case 'm':
8723 case 'M':
8724 mult *= 1000;
8725 /* fall through */
8727 case 'k':
8728 case 'K':
8729 mult *= 1000;
8730 break;
8733 n = n * mult + (numerator * mult) / divisor;
8735 pgm = mail_newsearchpgm();
8736 if(large)
8737 pgm->larger = n;
8738 else
8739 pgm->smaller = n;
8741 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8742 flags |= SE_NOSERVER;
8744 if(ps_global && ps_global->ttyo){
8745 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8746 ps_global->mangled_footer = 1;
8749 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8751 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8752 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8753 /* we know this was freed in mail_search, let caller know */
8754 if(limitsrch)
8755 *limitsrch = NULL;
8757 if(we_cancel)
8758 cancel_busy_cue(0);
8760 return(0);
8765 * visible_searchset -- return c-client search set unEXLDed
8766 * sequence numbers
8768 SEARCHSET *
8769 visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap)
8771 long n, run;
8772 SEARCHSET *full_set = NULL, **set;
8775 * If we're talking to anything other than a server older than
8776 * imap 4rev1, build a searchset otherwise it'll choke.
8778 if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
8779 if(any_lflagged(msgmap, MN_EXLD)){
8780 for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
8781 if(get_lflag(stream, NULL, n, MN_EXLD)){
8782 if(run){ /* previous NOT excluded? */
8783 if(run > 1L)
8784 (*set)->last = n - 1L;
8786 set = &(*set)->next;
8787 run = 0L;
8790 else if(run++){ /* next in run */
8791 (*set)->last = n;
8793 else{ /* start of run */
8794 *set = mail_newsearchset();
8795 (*set)->first = n;
8798 else{
8799 full_set = mail_newsearchset();
8800 full_set->first = 1L;
8801 full_set->last = stream->nmsgs;
8805 return(full_set);
8810 * Select by message status bits.
8811 * Sets searched bits in mail_elts
8813 * Args limitsrch -- limit search to this searchset
8815 * Returns 0 on success.
8818 select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch)
8820 int s, not = 0, we_cancel = 0, rv;
8822 while(1){
8823 s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag),
8824 -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x',
8825 NO_HELP, RB_NORM|RB_RET_HELP);
8827 if(s == 'x'){
8828 cmd_cancelled("Selection by status");
8829 return(1);
8831 else if(s == 3){
8832 helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"),
8833 HLPD_SIMPLE);
8834 ps_global->mangled_screen = 1;
8836 else if(s == '!')
8837 not = !not;
8838 else
8839 break;
8842 if(ps_global && ps_global->ttyo){
8843 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8844 ps_global->mangled_footer = 1;
8847 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8848 rv = agg_flag_select(stream, not, s, limitsrch);
8849 if(we_cancel)
8850 cancel_busy_cue(0);
8852 return(rv);
8857 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8858 * Sets searched bits in mail_elts
8860 * Args limitsrch -- limit search to this searchset
8862 * Returns 0 on success.
8865 select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
8867 char rulenick[1000], *nick;
8868 PATGRP_S *patgrp;
8869 int r, last_r = 0, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
8870 | ROLE_DO_INCOLS
8871 | ROLE_DO_ROLES
8872 | ROLE_DO_SCORES
8873 | ROLE_DO_OTHER
8874 | ROLE_DO_FILTER;
8876 rulenick[0] = '\0';
8877 ps_global->mangled_footer = 1;
8879 if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
8880 sel_rule_opt[2].ch = TAB;
8881 sel_rule_opt[2].rval = 15;
8882 sel_rule_opt[2].name = "TAB";
8883 sel_rule_opt[2].label = N_("Complete");
8885 else{
8886 memset(&sel_rule_opt[2], 0, sizeof(sel_rule_opt[2]));
8890 int oe_flags;
8892 oe_flags = OE_APPEND_CURRENT;
8893 r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0,
8894 sizeof(rulenick),
8895 not ? _("Rule to NOT match: ")
8896 : _("Rule to match: "),
8897 sel_rule_opt, NO_HELP, &oe_flags);
8899 if(r == 14){
8900 /* select rulenick from a list */
8901 if((nick=choose_a_rule(rflags, NULL)) != NULL){
8902 strncpy(rulenick, nick, sizeof(rulenick)-1);
8903 rulenick[sizeof(rulenick)-1] = '\0';
8904 fs_give((void **) &nick);
8906 else
8907 r = 4;
8909 else if(r == 15){
8910 int n = rulenick_complete(rflags, rulenick, sizeof(rulenick));
8912 if(n > 1 && last_r == 15 && !(oe_flags & OE_USER_MODIFIED)){
8913 /* double tab with multiple completions: select from list */
8914 if((nick=choose_a_rule(rflags, rulenick)) != NULL){
8915 strncpy(rulenick, nick, sizeof(rulenick)-1);
8916 rulenick[sizeof(rulenick)-1] = '\0';
8917 fs_give((void **) &nick);
8918 r = 0;
8920 else
8921 r = 4;
8923 else if(n != 1)
8924 Writechar(BELL, 0);
8926 else if(r == '!')
8927 not = !not;
8929 if(r == 3){
8930 helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE);
8931 ps_global->mangled_screen = 1;
8933 else if(r == 1){
8934 cmd_cancelled("Selection by Rule");
8935 return(1);
8938 removing_leading_and_trailing_white_space(rulenick);
8939 last_r = r;
8941 }while(r == 3 || r == 4 || r == 15 || r == '!');
8945 * The approach of requiring a nickname instead of just allowing the
8946 * user to select from the list of rules has the drawback that a rule
8947 * may not have a nickname, or there may be more than one rule with
8948 * the same nickname. However, it has the benefit of allowing the user
8949 * to type in the nickname and, most importantly, allows us to set
8950 * up the ! (not). We could incorporate the ! into the selection
8951 * screen, but this is easier and also allows the typing of nicks.
8952 * User can just set up nicknames if they want to use this feature.
8954 patgrp = nick_to_patgrp(rulenick, rflags);
8956 if(patgrp){
8957 if(ps_global && ps_global->ttyo){
8958 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8959 ps_global->mangled_footer = 1;
8962 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8963 match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL,
8964 get_msg_score,
8965 (not ? MP_NOT : 0) | SE_NOPREFETCH);
8966 free_patgrp(&patgrp);
8967 if(we_cancel)
8968 cancel_busy_cue(0);
8971 if(limitsrch && *limitsrch){
8972 mail_free_searchset(limitsrch);
8973 *limitsrch = NULL;
8976 return(0);
8981 * Allow user to choose a rule from their list of rules.
8983 * Args rflags -- Pattern types to choose from
8984 * prefix -- Only list rules matching the given prefix
8986 * Returns an allocated rule nickname on success, NULL otherwise.
8988 char *
8989 choose_a_rule(int rflags, char *prefix)
8991 char *choice = NULL;
8992 char **rule_list, **lp;
8993 int cnt = 0;
8994 int prefix_length = 0;
8995 PAT_S *pat;
8996 PAT_STATE pstate;
8997 void (*redraw)(void) = ps_global->redrawer;
8999 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){
9000 q_status_message(SM_ORDER, 3, 3,
9001 _("No rules available. Use Setup/Rules to add some."));
9002 return(choice);
9006 * Build a list of rules to choose from.
9009 if(prefix)
9010 prefix_length = strlen(prefix);
9012 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)) {
9013 if(!pat->patgrp || !pat->patgrp->nick)
9014 continue;
9015 if(!prefix || strncmp(pat->patgrp->nick, prefix, prefix_length) == 0)
9016 cnt++;
9019 if(cnt <= 0){
9020 q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
9021 return(choice);
9024 lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
9025 memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
9027 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)) {
9028 if(!pat->patgrp || !pat->patgrp->nick)
9029 continue;
9030 if(!prefix || strncmp(pat->patgrp->nick, prefix, prefix_length) == 0)
9031 *lp++ = cpystr(pat->patgrp->nick);
9034 /* TRANSLATORS: SELECT A RULE is a screen title
9035 TRANSLATORS: Print something1 using something2.
9036 "rules" is something1 */
9037 choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"),
9038 _("rules"), h_select_rule_screen,
9039 _("HELP FOR SELECTING A RULE NICKNAME"), NULL);
9041 if(!choice){
9042 q_status_message(SM_ORDER, 1, 4, "No choice");
9043 ps_global->redrawer = redraw;
9046 free_list_array(&rule_list);
9048 return(choice);
9053 * Complete a partial rule name against the user's list of rules.
9055 * Args rflags -- Pattern types to choose from
9056 * nick -- Current rule nickname to complete
9057 * nick_size -- Maximum length of the nick array to store completion in
9059 * Returns the number of valid completions, i.e.:
9061 * 0 if there are no valid completions. The value of the nick argument has not
9062 * been changed.
9063 * 1 if there is only a single valid completion. The value of the nick argument
9064 * has been replaced with the full text of that completion.
9065 * >1 if there is more than one valid completion. The value of the nick argument
9066 * has been replaced with the longest substring common to all the appropriate
9067 * completions.
9070 rulenick_complete(int rflags, char *nick, int nick_size)
9072 char *candidate = NULL;
9073 int cnt = 0;
9074 int common_prefix_length = 0;
9075 int nick_length;
9076 PAT_S *pat;
9077 PAT_STATE pstate;
9079 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate)))
9080 return 0;
9082 nick_length = strlen(nick);
9084 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){
9085 if(!pat->patgrp || !pat->patgrp->nick)
9086 continue;
9087 if(strncmp(pat->patgrp->nick, nick, nick_length) == 0){
9088 /* This is a candidate for completion. */
9089 cnt++;
9090 if(!candidate){
9091 /* This is the first candidate. Keep it as a future reference to
9092 * compare against to find the longest common prefix length of
9093 * all the matches. */
9094 candidate = pat->patgrp->nick;
9095 common_prefix_length = strlen(pat->patgrp->nick);
9097 else{
9098 /* Find the common prefix length between the first candidate and
9099 * this one. */
9100 int i;
9101 for(i = 0; i < common_prefix_length; i++){
9102 if(pat->patgrp->nick[i] != candidate[i]){
9103 /* In the event that we ended up in the middle of a
9104 * UTF-8 code point, backtrack to the byte before the
9105 * start of this code point. */
9106 while(i > 0 && (candidate[i] & 0xC0) == 0x80)
9107 i--;
9108 common_prefix_length = i;
9109 break;
9116 if(cnt > 0){
9117 int length = MIN(nick_size, common_prefix_length);
9118 strncpy(nick + nick_length, candidate + nick_length, length - nick_length);
9119 nick[length] = '\0';
9122 return cnt;
9127 * Select by current thread.
9128 * Sets searched bits in mail_elts for this entire thread
9130 * Args limitsrch -- limit search to this searchset
9132 * Returns 0 on success.
9135 select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
9137 long n;
9138 PINETHRD_S *thrd = NULL;
9139 int ret = 1;
9140 MESSAGECACHE *mc;
9142 if(!stream)
9143 return(ret);
9145 for(n = 1L; n <= stream->nmsgs; n++)
9146 if((mc = mail_elt(stream, n)) != NULL)
9147 mc->searched = 0; /* clear searched bits */
9149 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
9150 if(thrd && thrd->top && thrd->top != thrd->rawno)
9151 thrd = fetch_thread(stream, thrd->top);
9154 * This doesn't unselect if the thread is already selected
9155 * (like select current does), it always selects.
9156 * There is no way to select ! this thread.
9158 if(thrd){
9159 set_search_bit_for_thread(stream, thrd, limitsrch);
9160 ret = 0;
9163 return(ret);
9168 * Select by message keywords.
9169 * Sets searched bits in mail_elts
9171 * Args limitsrch -- limit search to this searchset
9173 * Returns 0 on success.
9176 select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
9178 int r, last_r = 0, not = 0, we_cancel = 0;
9179 char keyword[MAXUSERFLAG+1], *kword;
9180 char *error = NULL, *p, *prompt;
9181 HelpType help;
9182 SEARCHPGM *pgm;
9184 keyword[0] = '\0';
9185 ps_global->mangled_footer = 1;
9187 if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
9188 sel_key_opt[2].ch = TAB;
9189 sel_key_opt[2].rval = 15;
9190 sel_key_opt[2].name = "TAB";
9191 sel_key_opt[2].label = N_("Complete");
9193 else{
9194 memset(&sel_key_opt[2], 0, sizeof(sel_key_opt[2]));
9197 help = NO_HELP;
9199 int oe_flags;
9201 if(error){
9202 q_status_message(SM_ORDER, 3, 4, error);
9203 fs_give((void **) &error);
9206 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
9207 if(not)
9208 prompt = _("Keyword (or keyword initial) to NOT match: ");
9209 else
9210 prompt = _("Keyword (or keyword initial) to match: ");
9212 else{
9213 if(not)
9214 prompt = _("Keyword to NOT match: ");
9215 else
9216 prompt = _("Keyword to match: ");
9219 oe_flags = OE_APPEND_CURRENT;
9220 r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0,
9221 sizeof(keyword),
9222 prompt, sel_key_opt, help, &oe_flags);
9224 if(r == 14){
9225 /* select keyword from a list */
9226 if((kword=choose_a_keyword(NULL)) != NULL){
9227 strncpy(keyword, kword, sizeof(keyword)-1);
9228 keyword[sizeof(keyword)-1] = '\0';
9229 fs_give((void **) &kword);
9231 else
9232 r = 4;
9234 else if(r == 15){
9235 int n = keyword_complete(keyword, sizeof(keyword));
9237 if(n > 1 && last_r == 15 && !(oe_flags & OE_USER_MODIFIED)){
9238 /* double tab with multiple completions: select from list */
9239 if((kword=choose_a_keyword(keyword)) != NULL){
9240 strncpy(keyword, kword, sizeof(keyword)-1);
9241 keyword[sizeof(keyword)-1] = '\0';
9242 fs_give((void **) &kword);
9243 r = 0;
9245 else
9246 r = 4;
9248 else if(n != 1)
9249 Writechar(BELL, 0);
9251 else if(r == '!')
9252 not = !not;
9254 if(r == 3)
9255 help = help == NO_HELP ? h_select_keyword : NO_HELP;
9256 else if(r == 1){
9257 cmd_cancelled("Selection by keyword");
9258 return(1);
9261 removing_leading_and_trailing_white_space(keyword);
9262 last_r = r;
9264 }while(r == 3 || r == 4 || r == 15 || r == '!' || keyword_check(keyword, &error));
9267 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
9268 p = initial_to_keyword(keyword);
9269 if(p != keyword){
9270 strncpy(keyword, p, sizeof(keyword)-1);
9271 keyword[sizeof(keyword)-1] = '\0';
9276 * We want to check the keyword, not the nickname of the keyword,
9277 * so convert it to the keyword if necessary.
9279 p = nick_to_keyword(keyword);
9280 if(p != keyword){
9281 strncpy(keyword, p, sizeof(keyword)-1);
9282 keyword[sizeof(keyword)-1] = '\0';
9285 pgm = mail_newsearchpgm();
9286 if(not){
9287 pgm->unkeyword = mail_newstringlist();
9288 pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword);
9289 pgm->unkeyword->text.size = strlen(keyword);
9291 else{
9292 pgm->keyword = mail_newstringlist();
9293 pgm->keyword->text.data = (unsigned char *) cpystr(keyword);
9294 pgm->keyword->text.size = strlen(keyword);
9297 if(ps_global && ps_global->ttyo){
9298 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9299 ps_global->mangled_footer = 1;
9302 we_cancel = busy_cue(_("Selecting"), NULL, 1);
9304 pgm->msgno = (limitsrch ? *limitsrch : NULL);
9305 pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE);
9306 /* we know this was freed in mail_search, let caller know */
9307 if(limitsrch)
9308 *limitsrch = NULL;
9310 if(we_cancel)
9311 cancel_busy_cue(0);
9313 return(0);
9318 * Allow user to choose a keyword from their list of keywords.
9320 * Args prefix -- Only list keywords matching the given prefix
9322 * Returns an allocated keyword on success, NULL otherwise.
9324 char *
9325 choose_a_keyword(char *prefix)
9327 char *choice = NULL;
9328 char **keyword_list, **lp;
9329 int cnt;
9330 int prefix_length = 0;
9331 KEYWORD_S *kw;
9332 void (*redraw)(void) = ps_global->redrawer;
9335 * Build a list of keywords to choose from.
9338 if(prefix)
9339 prefix_length = strlen(prefix);
9341 for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next){
9342 char *kw_name = kw->nick ? kw->nick : kw->kw;
9343 if(!prefix || kw_name && strncmp(kw_name, prefix, prefix_length) == 0)
9344 cnt++;
9347 if(cnt <= 0){
9348 q_status_message(SM_ORDER, 3, 4,
9349 _("No keywords defined, use \"keywords\" option in Setup/Config"));
9350 return(choice);
9353 lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list));
9354 memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list));
9356 for(kw = ps_global->keywords; kw; kw = kw->next){
9357 char *kw_name = kw->nick ? kw->nick : kw->kw;
9358 if(!kw_name)
9359 continue;
9360 if(!prefix || kw_name && strncmp(kw_name, prefix, prefix_length) == 0)
9361 *lp++ = cpystr(kw_name);
9364 /* TRANSLATORS: SELECT A KEYWORD is a screen title
9365 TRANSLATORS: Print something1 using something2.
9366 "keywords" is something1 */
9367 choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"),
9368 _("keywords"), h_select_keyword_screen,
9369 _("HELP FOR SELECTING A KEYWORD"), NULL);
9371 if(!choice){
9372 q_status_message(SM_ORDER, 1, 4, "No choice");
9373 ps_global->redrawer = redraw;
9376 free_list_array(&keyword_list);
9378 return(choice);
9383 * Complete a partial keyword name against the user's list of keywords.
9385 * Args keyword -- Current keyword to complete
9386 * keyword_size -- Maximum length of the keyword array to store
9387 * completion in
9389 * Returns the number of valid completions, i.e.:
9391 * 0 if there are no valid completions. The value of the keyword argument has
9392 * not been changed.
9393 * 1 if there is only a single valid completion. The value of the keyword
9394 * argument has been replaced with the full text of that completion.
9395 * >1 if there is more than one valid completion. The value of the keyword
9396 * argument has been replaced with the longest substring common to all the
9397 * appropriate completions.
9400 keyword_complete(char *keyword, int keyword_size)
9402 char *candidate = NULL;
9403 int cnt = 0;
9404 int common_prefix_length = 0;
9405 int keyword_length;
9406 KEYWORD_S *kw;
9408 keyword_length = strlen(keyword);
9410 for(kw = ps_global->keywords; kw; kw = kw->next){
9411 char *kw_name = kw->nick ? kw->nick : kw->kw;
9412 if(!kw_name)
9413 continue;
9414 if(strncmp(kw_name, keyword, keyword_length) == 0){
9415 /* This is a candidate for completion. */
9416 cnt++;
9417 if(!candidate){
9418 /* This is the first candidate. Keep it as a future reference to
9419 * compare against to find the longest common prefix length of
9420 * all the matches. */
9421 candidate = kw_name;
9422 common_prefix_length = strlen(candidate);
9424 else{
9425 /* Find the common prefix length between the first candidate and
9426 * this one. */
9427 int i;
9428 for(i = 0; i < common_prefix_length; i++){
9429 if(kw_name[i] != candidate[i]){
9430 /* In the event that we ended up in the middle of a
9431 * UTF-8 code point, backtrack to the byte before the
9432 * start of this code point. */
9433 while(i > 0 && (candidate[i] & 0xC0) == 0x80)
9434 i--;
9435 common_prefix_length = i;
9436 break;
9443 if(cnt > 0){
9444 int length = MIN(keyword_size, common_prefix_length);
9445 strncpy(keyword + keyword_length, candidate + keyword_length, length - keyword_length);
9446 keyword[length] = '\0';
9449 return cnt;
9454 * Allow user to choose a list of keywords from their list of keywords.
9456 * Returns allocated list.
9458 char **
9459 choose_list_of_keywords(void)
9461 LIST_SEL_S *listhead, *ls, *p;
9462 char **ret = NULL;
9463 int cnt, i;
9464 KEYWORD_S *kw;
9467 * Build a list of keywords to choose from.
9470 p = listhead = NULL;
9471 for(kw = ps_global->keywords; kw; kw = kw->next){
9473 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9474 memset(ls, 0, sizeof(*ls));
9475 ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9477 if(p){
9478 p->next = ls;
9479 p = p->next;
9481 else
9482 listhead = p = ls;
9485 if(!listhead)
9486 return(ret);
9488 /* TRANSLATORS: SELECT KEYWORDS is a screen title
9489 Print something1 using something2.
9490 "keywords" is something1 */
9491 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9492 _("SELECT KEYWORDS"), _("keywords"),
9493 h_select_multkeyword_screen,
9494 _("HELP FOR SELECTING KEYWORDS"), NULL)){
9495 for(cnt = 0, p = listhead; p; p = p->next)
9496 if(p->selected)
9497 cnt++;
9499 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9500 memset(ret, 0, (cnt+1) * sizeof(*ret));
9501 for(i = 0, p = listhead; p; p = p->next)
9502 if(p->selected)
9503 ret[i++] = cpystr(p->item ? p->item : "");
9506 free_list_sel(&listhead);
9508 return(ret);
9513 * Allow user to choose a charset
9515 * Returns an allocated charset on success, NULL otherwise.
9517 char *
9518 choose_a_charset(int which_charsets)
9520 char *choice = NULL;
9521 char **charset_list, **lp;
9522 const CHARSET *cs;
9523 int cnt;
9524 void (*redraw)(void) = ps_global->redrawer;
9527 * Build a list of charsets to choose from.
9530 for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){
9531 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9532 && ((which_charsets & CAC_ALL)
9533 || (which_charsets & CAC_POSTING
9534 && cs->flags & CF_POSTING)
9535 || (which_charsets & CAC_DISPLAY
9536 && cs->type != CT_2022
9537 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9538 cnt++;
9541 if(cnt <= 0){
9542 q_status_message(SM_ORDER, 3, 4,
9543 _("No charsets found? Enter charset manually."));
9544 return(choice);
9547 lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list));
9548 memset(charset_list, 0, (cnt+1) * sizeof(*charset_list));
9550 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9551 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9552 && ((which_charsets & CAC_ALL)
9553 || (which_charsets & CAC_POSTING
9554 && cs->flags & CF_POSTING)
9555 || (which_charsets & CAC_DISPLAY
9556 && cs->type != CT_2022
9557 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9558 *lp++ = cpystr(cs->name);
9561 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
9562 TRANSLATORS: Print something1 using something2.
9563 "character sets" is something1 */
9564 choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"),
9565 _("character sets"), h_select_charset_screen,
9566 _("HELP FOR SELECTING A CHARACTER SET"), NULL);
9568 if(!choice){
9569 q_status_message(SM_ORDER, 1, 4, "No choice");
9570 ps_global->redrawer = redraw;
9573 free_list_array(&charset_list);
9575 return(choice);
9580 * Allow user to choose a list of character sets and/or scripts
9582 * Returns allocated list.
9584 char **
9585 choose_list_of_charsets(void)
9587 LIST_SEL_S *listhead, *ls, *p;
9588 char **ret = NULL;
9589 int cnt, i, got_one;
9590 const CHARSET *cs;
9591 SCRIPT *s;
9592 char *q, *t;
9593 long width, limit;
9594 char buf[1024], *folded;
9597 * Build a list of charsets to choose from.
9600 p = listhead = NULL;
9602 /* this width is determined by select_from_list_screen() */
9603 width = ps_global->ttyo->screen_cols - 4;
9605 /* first comes a list of scripts (sets of character sets) */
9606 for(s = utf8_script(NIL); s && s->name; s++){
9608 limit = sizeof(buf)-1;
9609 q = buf;
9610 memset(q, 0, limit+1);
9612 if(s->name)
9613 sstrncpy(&q, s->name, limit);
9615 if(s->description){
9616 sstrncpy(&q, " (", limit-(q-buf));
9617 sstrncpy(&q, s->description, limit-(q-buf));
9618 sstrncpy(&q, ")", limit-(q-buf));
9621 /* add the list of charsets that are in this script */
9622 got_one = 0;
9623 for(cs = utf8_charset(NIL);
9624 cs && cs->name && (q-buf) < limit; cs++){
9625 if(cs->script & s->script){
9627 * Filter out some un-useful members of the list.
9628 * UTF-7 and UTF-8 weren't actually in the list at the
9629 * time this was written. Just making sure.
9631 if(!strucmp(cs->name, "ISO-2022-JP-2")
9632 || !strucmp(cs->name, "UTF-7")
9633 || !strucmp(cs->name, "UTF-8"))
9634 continue;
9636 if(got_one)
9637 sstrncpy(&q, " ", limit-(q-buf));
9638 else{
9639 got_one = 1;
9640 sstrncpy(&q, " {", limit-(q-buf));
9643 sstrncpy(&q, cs->name, limit-(q-buf));
9647 if(got_one)
9648 sstrncpy(&q, "}", limit-(q-buf));
9650 /* fold this line so that it can all be seen on the screen */
9651 folded = fold(buf, width, width, "", " ", FLD_NONE);
9652 if(folded){
9653 t = folded;
9654 while(t && *t && (q = strindex(t, '\n')) != NULL){
9655 *q = '\0';
9657 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9658 memset(ls, 0, sizeof(*ls));
9659 if(t == folded)
9660 ls->item = cpystr(s->name);
9661 else
9662 ls->flags = SFL_NOSELECT;
9664 ls->display_item = cpystr(t);
9666 t = q+1;
9668 if(p){
9669 p->next = ls;
9670 p = p->next;
9672 else{
9673 /* add a heading */
9674 listhead = (LIST_SEL_S *) fs_get(sizeof(*ls));
9675 memset(listhead, 0, sizeof(*listhead));
9676 listhead->flags = SFL_NOSELECT;
9677 listhead->display_item =
9678 cpystr(_("Scripts representing groups of related character sets"));
9679 listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls));
9680 memset(listhead->next, 0, sizeof(*listhead));
9681 listhead->next->flags = SFL_NOSELECT;
9682 listhead->next->display_item =
9683 cpystr(repeat_char(width, '-'));
9685 listhead->next->next = ls;
9686 p = ls;
9690 fs_give((void **) &folded);
9694 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9695 memset(ls, 0, sizeof(*ls));
9696 ls->flags = SFL_NOSELECT;
9697 if(p){
9698 p->next = ls;
9699 p = p->next;
9701 else
9702 listhead = p = ls;
9704 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9705 memset(ls, 0, sizeof(*ls));
9706 ls->flags = SFL_NOSELECT;
9707 ls->display_item =
9708 cpystr(_("Individual character sets, may be mixed with scripts"));
9709 p->next = ls;
9710 p = p->next;
9712 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9713 memset(ls, 0, sizeof(*ls));
9714 ls->flags = SFL_NOSELECT;
9715 ls->display_item =
9716 cpystr(repeat_char(width, '-'));
9717 p->next = ls;
9718 p = p->next;
9720 /* then comes a list of individual character sets */
9721 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9722 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9723 memset(ls, 0, sizeof(*ls));
9724 ls->item = cpystr(cs->name);
9726 if(p){
9727 p->next = ls;
9728 p = p->next;
9730 else
9731 listhead = p = ls;
9734 if(!listhead)
9735 return(ret);
9737 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9738 Print something1 using something2.
9739 "character sets" is something1 */
9740 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9741 _("SELECT CHARACTER SETS"), _("character sets"),
9742 h_select_multcharsets_screen,
9743 _("HELP FOR SELECTING CHARACTER SETS"), NULL)){
9744 for(cnt = 0, p = listhead; p; p = p->next)
9745 if(p->selected)
9746 cnt++;
9748 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9749 memset(ret, 0, (cnt+1) * sizeof(*ret));
9750 for(i = 0, p = listhead; p; p = p->next)
9751 if(p->selected)
9752 ret[i++] = cpystr(p->item ? p->item : "");
9755 free_list_sel(&listhead);
9757 return(ret);
9760 /* Report quota summary resources in an IMAP server */
9762 void
9763 cmd_quota (struct pine *state)
9765 QUOTALIST *imapquota;
9766 NETMBX mb;
9767 STORE_S *store;
9768 SCROLL_S sargs;
9770 if(!state->mail_stream || !is_imap_stream(state->mail_stream)){
9771 q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders");
9772 return;
9775 if (state->mail_stream
9776 && !sp_dead_stream(state->mail_stream)
9777 && state->mail_stream->mailbox
9778 && *state->mail_stream->mailbox
9779 && mail_valid_net_parse(state->mail_stream->mailbox, &mb))
9780 imap_getquotaroot(state->mail_stream, mb.mailbox);
9782 if(!state->quota) /* failed ? */
9783 return; /* go back... */
9785 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
9786 q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space.");
9787 return;
9790 so_puts(store, "Quota Report for ");
9791 so_puts(store, state->mail_stream->original_mailbox);
9792 so_puts(store, "\n\n");
9794 for (imapquota = state->quota; imapquota; imapquota = imapquota->next){
9796 so_puts(store, _("Resource : "));
9797 so_puts(store, imapquota->name);
9798 so_writec('\n', store);
9800 so_puts(store, _("Usage : "));
9801 so_puts(store, long2string(imapquota->usage));
9802 if(!strucmp(imapquota->name,"STORAGE"))
9803 so_puts(store, " KiB ");
9804 if(!strucmp(imapquota->name,"MESSAGE")){
9805 so_puts(store, _(" message"));
9806 if(imapquota->usage != 1)
9807 so_puts(store, _("s ")); /* plural */
9808 else
9809 so_puts(store, _(" "));
9811 so_writec('(', store);
9812 so_puts(store, long2string(100*imapquota->usage/imapquota->limit));
9813 so_puts(store, "%)\n");
9815 so_puts(store, _("Limit : "));
9816 so_puts(store, long2string(imapquota->limit));
9817 if(!strucmp(imapquota->name,"STORAGE"))
9818 so_puts(store, " KiB\n\n");
9819 if(!strucmp(imapquota->name,"MESSAGE")){
9820 so_puts(store, _(" message"));
9821 if(imapquota->usage != 1)
9822 so_puts(store, _("s\n\n")); /* plural */
9823 else
9824 so_puts(store, _("\n\n"));
9828 memset(&sargs, 0, sizeof(SCROLL_S));
9829 sargs.text.text = so_text(store);
9830 sargs.text.src = CharStar;
9831 sargs.text.desc = _("Quota Resources Summary");
9832 sargs.bar.title = _("QUOTA SUMMARY");
9833 sargs.proc.tool = NULL;
9834 sargs.help.text = h_quota_command;
9835 sargs.help.title = NULL;
9836 sargs.keys.menu = NULL;
9837 setbitmap(sargs.keys.bitmap);
9839 scrolltool(&sargs);
9840 so_give(&store);
9842 if (state->quota)
9843 mail_free_quotalist(&(state->quota));
9846 /*----------------------------------------------------------------------
9847 Prompt the user for the type of sort he desires
9849 Args: state -- pine state pointer
9850 q1 -- Line to prompt on
9852 Returns 0 if it was cancelled, 1 otherwise.
9853 ----*/
9855 select_sort(struct pine *state, int ql, SortOrder *sort, int *rev)
9857 char prompt[200], tmp[3], *p;
9858 int s, i;
9859 int deefault = 'a', retval = 1;
9860 HelpType help;
9861 ESCKEY_S sorts[14];
9863 #ifdef _WINDOWS
9864 DLG_SORTPARAM sortsel;
9866 if (mswin_usedialog ()) {
9868 sortsel.reverse = mn_get_revsort (state->msgmap);
9869 sortsel.cursort = mn_get_sort (state->msgmap);
9870 /* assumption here that HelpType is char ** */
9871 sortsel.helptext = h_select_sort;
9872 sortsel.rval = 0;
9874 if ((retval = os_sortdialog (&sortsel))) {
9875 *sort = sortsel.cursort;
9876 *rev = sortsel.reverse;
9879 return (retval);
9881 #endif
9883 /*----- String together the prompt ------*/
9884 tmp[1] = '\0';
9885 if(F_ON(F_USE_FK,ps_global))
9886 strncpy(prompt, _("Choose type of sort : "), sizeof(prompt));
9887 else
9888 strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "),
9889 sizeof(prompt));
9891 for(i = 0; state->sort_types[i] != EndofList; i++) {
9892 sorts[i].rval = i;
9893 p = sorts[i].label = sort_name(state->sort_types[i]);
9894 while(*(p+1) && islower((unsigned char)*p))
9895 p++;
9897 sorts[i].ch = tolower((unsigned char)(tmp[0] = *p));
9898 sorts[i].name = cpystr(tmp);
9900 if(mn_get_sort(state->msgmap) == state->sort_types[i])
9901 deefault = sorts[i].rval;
9904 sorts[i].ch = 'r';
9905 sorts[i].rval = 'r';
9906 sorts[i].name = cpystr("R");
9907 if(F_ON(F_USE_FK,ps_global))
9908 sorts[i].label = N_("Reverse");
9909 else
9910 sorts[i].label = "";
9912 sorts[++i].ch = -1;
9913 help = h_select_sort;
9915 if((F_ON(F_USE_FK,ps_global)
9916 && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x',
9917 help,RB_NORM)) != 'x'))
9919 (F_OFF(F_USE_FK,ps_global)
9920 && ((s = radio_buttons(prompt,ql,sorts,deefault,'x',
9921 help,RB_NORM)) != 'x'))){
9922 state->mangled_body = 1; /* signal screen's changed */
9923 if(s == 'r')
9924 *rev = !mn_get_revsort(state->msgmap);
9925 else
9926 *sort = state->sort_types[s];
9928 if(F_ON(F_SHOW_SORT, ps_global))
9929 ps_global->mangled_header = 1;
9931 else{
9932 retval = 0;
9933 cmd_cancelled("Sort");
9936 while(--i >= 0)
9937 fs_give((void **)&sorts[i].name);
9939 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9940 return(retval);
9944 /*---------------------------------------------------------------------
9945 Build list of folders in the given context for user selection
9947 Args: c -- pointer to pointer to folder's context context
9948 f -- folder prefix to display
9949 sublist -- whether or not to use 'f's contents as prefix
9950 lister -- function used to do the actual display
9952 Returns: malloc'd string containing sequence, else NULL if
9953 no messages in msgmap with local "selected" flag.
9954 ----*/
9956 display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int))
9958 int rc;
9959 CONTEXT_S *tc;
9960 void (*redraw)(void) = ps_global->redrawer;
9962 push_titlebar_state();
9963 tc = *c;
9964 if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0)
9965 *c = tc;
9967 ClearScreen();
9968 pop_titlebar_state();
9969 redraw_titlebar();
9970 if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
9971 (*ps_global->redrawer)();
9973 if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
9974 return(1);
9976 return(0);
9981 * Allow user to choose a single item from a list of strings.
9983 * Args list -- Array of strings to choose from, NULL terminated.
9984 * displist -- Array of strings to display instead of displaying list.
9985 * Indices correspond to the list array. Display the displist
9986 * but return the item from list if displist non-NULL.
9987 * title -- For conf_scroll_screen
9988 * pdesc -- For conf_scroll_screen
9989 * help -- For conf_scroll_screen
9990 * htitle -- For conf_scroll_screen
9992 * Returns an allocated copy of the chosen item or NULL.
9994 char *
9995 choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help,
9996 char *htitle, char *cursor_location)
9998 LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL;
9999 char **t, **dl;
10000 char *ret = NULL, *choice = NULL;
10002 /* build the LIST_SEL_S list */
10003 p = listhead = NULL;
10004 for(t = list, dl = displist; *t; t++, dl++){
10005 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
10006 memset(ls, 0, sizeof(*ls));
10007 ls->item = cpystr(*t);
10008 if(displist)
10009 ls->display_item = cpystr(*dl);
10011 if(cursor_location && (cursor_location == (*t)))
10012 starting_val = ls;
10014 if(p){
10015 p->next = ls;
10016 p = p->next;
10018 else
10019 listhead = p = ls;
10022 if(!listhead)
10023 return(ret);
10025 if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc,
10026 help, htitle, starting_val))
10027 for(p = listhead; !choice && p; p = p->next)
10028 if(p->selected)
10029 choice = p->item;
10031 if(choice)
10032 ret = cpystr(choice);
10034 free_list_sel(&listhead);
10036 return(ret);
10040 void
10041 free_list_sel(LIST_SEL_S **lsel)
10043 if(lsel && *lsel){
10044 free_list_sel(&(*lsel)->next);
10045 if((*lsel)->item)
10046 fs_give((void **) &(*lsel)->item);
10048 if((*lsel)->display_item)
10049 fs_give((void **) &(*lsel)->display_item);
10051 fs_give((void **) lsel);
10057 * file_lister - call pico library's file lister
10060 file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags)
10062 PICO pbf;
10063 int rv;
10064 void (*redraw)(void) = ps_global->redrawer;
10066 standard_picobuf_setup(&pbf);
10067 push_titlebar_state();
10068 if(!newmail)
10069 pbf.newmail = NULL;
10071 /* BUG: what about help command and text? */
10072 pbf.pine_anchor = title;
10074 rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags);
10075 standard_picobuf_teardown(&pbf);
10076 fix_windsize(ps_global);
10077 init_signals(); /* has it's own signal stuff */
10079 /* Restore display's titlebar and body */
10080 pop_titlebar_state();
10081 redraw_titlebar();
10082 if((ps_global->redrawer = redraw) != NULL)
10083 (*ps_global->redrawer)();
10085 return(rv);
10089 /*----------------------------------------------------------------------
10090 Print current folder index
10092 ---*/
10094 print_index(struct pine *state, MSGNO_S *msgmap, int agg)
10096 long i;
10097 ICE_S *ice;
10098 char buf[MAX_SCREEN_COLS+1];
10100 for(i = 1L; i <= mn_get_total(msgmap); i++){
10101 if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
10102 continue;
10104 if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0))
10105 continue;
10107 ice = build_header_line(state, state->mail_stream, msgmap, i, NULL);
10109 if(ice){
10111 * I don't understand why we'd want to mark the current message
10112 * instead of printing out the first character of the status
10113 * so I'm taking it out and including the first character of the
10114 * line instead. Hubert 2006-02-09
10116 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
10117 return(0);
10120 if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i),
10121 print_char)
10122 || !gf_puts(NEWLINE, print_char))
10123 return(0);
10127 return(1);
10130 #ifdef _WINDOWS
10133 * windows callback to get/set header mode state
10136 header_mode_callback(set, args)
10137 int set;
10138 long args;
10140 return(ps_global->full_header);
10145 * windows callback to get/set zoom mode state
10148 zoom_mode_callback(set, args)
10149 int set;
10150 long args;
10152 return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0);
10157 * windows callback to get/set zoom mode state
10160 any_selected_callback(set, args)
10161 int set;
10162 long args;
10164 return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0);
10172 flag_callback(set, flags)
10173 int set;
10174 long flags;
10176 MESSAGECACHE *mc;
10177 int newflags = 0;
10178 long msgno;
10179 int permflag = 0;
10181 switch (set) {
10182 case 1: /* Important */
10183 permflag = ps_global->mail_stream->perm_flagged;
10184 break;
10186 case 2: /* New */
10187 permflag = ps_global->mail_stream->perm_seen;
10188 break;
10190 case 3: /* Answered */
10191 permflag = ps_global->mail_stream->perm_answered;
10192 break;
10194 case 4: /* Deleted */
10195 permflag = ps_global->mail_stream->perm_deleted;
10196 break;
10200 if(!(any_messages(ps_global->msgmap, NULL, "to Flag")
10201 && can_set_flag(ps_global, "flag", permflag)))
10202 return(0);
10204 if(sp_io_error_on_stream(ps_global->mail_stream)){
10205 sp_set_io_error_on_stream(ps_global->mail_stream, 0);
10206 pine_mail_check(ps_global->mail_stream); /* forces write */
10207 return(0);
10210 msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
10211 if(msgno > 0L && ps_global->mail_stream
10212 && msgno <= ps_global->mail_stream->nmsgs
10213 && (mc = mail_elt(ps_global->mail_stream, msgno))
10214 && mc->valid){
10216 * NOTE: code below is *VERY* sensitive to the order of
10217 * the messages defined in resource.h for flag handling.
10218 * Don't change it unless you know what you're doing.
10220 if(set){
10221 char *flagstr;
10222 long mflag;
10224 switch(set){
10225 case 1 : /* Important */
10226 flagstr = "\\FLAGGED";
10227 mflag = (mc->flagged) ? 0L : ST_SET;
10228 break;
10230 case 2 : /* New */
10231 flagstr = "\\SEEN";
10232 mflag = (mc->seen) ? 0L : ST_SET;
10233 break;
10235 case 3 : /* Answered */
10236 flagstr = "\\ANSWERED";
10237 mflag = (mc->answered) ? 0L : ST_SET;
10238 break;
10240 case 4 : /* Deleted */
10241 flagstr = "\\DELETED";
10242 mflag = (mc->deleted) ? 0L : ST_SET;
10243 break;
10245 default : /* bogus */
10246 return(0);
10249 mail_flag(ps_global->mail_stream, long2string(msgno),
10250 flagstr, mflag);
10252 if(ps_global->redrawer)
10253 (*ps_global->redrawer)();
10255 else{
10256 /* Important */
10257 if(mc->flagged)
10258 newflags |= 0x0001;
10260 /* New */
10261 if(!mc->seen)
10262 newflags |= 0x0002;
10264 /* Answered */
10265 if(mc->answered)
10266 newflags |= 0x0004;
10268 /* Deleted */
10269 if(mc->deleted)
10270 newflags |= 0x0008;
10274 return(newflags);
10280 * BUG: Should teach this about keywords
10282 MPopup *
10283 flag_submenu(mc)
10284 MESSAGECACHE *mc;
10286 static MPopup flag_submenu[] = {
10287 {tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}},
10288 {tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}},
10289 {tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}},
10290 {tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}},
10291 {tTail}
10294 /* Important */
10295 flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal;
10297 /* New */
10298 flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked;
10300 /* Answered */
10301 flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal;
10303 /* Deleted */
10304 flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal;
10306 return(flag_submenu);
10309 #endif /* _WINDOWS */