* For mailing lists, Alpine adds a description of the type of link
[alpine.git] / alpine / mailcmd.c
blob586ee5b43606b0b6d9f8487542eab62a0e2f1b00
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> ");
6822 if(!struncmp(data[i].data[j].value, "http", 4)) {
6823 so_puts(store, "(web link) ");
6825 else if(!struncmp(data[i].data[j].value, "mailto", 5)) {
6826 so_puts(store, " (email link) ");
6827 } so_puts(store, "to ");
6828 so_puts(store, (data[i].field.action)
6829 ? data[i].field.action
6830 : "try it");
6833 so_puts(store, ".");
6836 if(data[i].data[j].error){
6837 so_puts(store, "<P>Unfortunately, Alpine can not offer");
6838 so_puts(store, " to take direct action based upon it");
6839 so_puts(store, " because it was improperly formatted.");
6840 so_puts(store, " The unrecognized data associated with");
6841 so_puts(store, " the \"");
6842 so_puts(store, data[i].field.name);
6843 so_puts(store, "\" header field was:<P><BLOCKQUOTE>");
6844 so_puts(store, data[i].data[j].error);
6845 so_puts(store, "</BLOCKQUOTE>");
6848 so_puts(store, "<P>");
6851 if(n)
6852 so_puts(store, "</OL>");
6855 if(fields)
6856 so_puts(store, "</UL>");
6858 so_puts(store, "</BODY></HTML>");
6861 return(store);
6865 void
6866 list_mgmt_screen(STORE_S *html)
6868 int cmd = MC_NONE;
6869 long offset = 0L;
6870 char *error = NULL;
6871 STORE_S *store;
6872 HANDLE_S *handles = NULL;
6873 gf_io_t gc, pc;
6876 so_seek(html, 0L, 0);
6877 gf_set_so_readc(&gc, html);
6879 init_handles(&handles);
6881 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6882 gf_set_so_writec(&pc, store);
6883 gf_filter_init();
6885 gf_link_filter(gf_html2plain,
6886 gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols,
6887 non_messageview_margin(), &handles, NULL, 0));
6889 error = gf_pipe(gc, pc);
6891 gf_clear_so_writec(store);
6893 if(!error){
6894 SCROLL_S sargs;
6896 memset(&sargs, 0, sizeof(SCROLL_S));
6897 sargs.text.text = so_text(store);
6898 sargs.text.src = CharStar;
6899 sargs.text.desc = "list commands";
6900 sargs.text.handles = handles;
6901 if(offset){
6902 sargs.start.on = Offset;
6903 sargs.start.loc.offset = offset;
6906 sargs.bar.title = _("MAIL LIST COMMANDS");
6907 sargs.bar.style = MessageNumber;
6908 sargs.resize_exit = 1;
6909 sargs.help.text = h_special_list_commands;
6910 sargs.help.title = _("HELP FOR LIST COMMANDS");
6911 sargs.keys.menu = &listmgr_keymenu;
6912 setbitmap(sargs.keys.bitmap);
6913 if(!handles){
6914 clrbitn(LM_TRY_KEY, sargs.keys.bitmap);
6915 clrbitn(LM_PREV_KEY, sargs.keys.bitmap);
6916 clrbitn(LM_NEXT_KEY, sargs.keys.bitmap);
6919 cmd = scrolltool(&sargs);
6920 offset = sargs.start.loc.offset;
6923 so_give(&store);
6926 free_handles(&handles);
6927 gf_clear_so_readc(html);
6929 while(cmd == MC_RESIZE);
6933 /*----------------------------------------------------------------------
6934 Prompt the user for the type of select desired
6936 NOTE: any and all functions that successfully exit the second
6937 switch() statement below (currently "select_*() functions"),
6938 *MUST* update the folder's MESSAGECACHE element's "searched"
6939 bits to reflect the search result. Functions using
6940 mail_search() get this for free, the others must update 'em
6941 by hand.
6943 Returns -1 if canceled without changing selection
6944 0 if selection may have changed
6945 ----*/
6947 aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index)
6949 long i, diff, old_tot, msgno, raw;
6950 int p = 0, q = 0, rv = 0, narrow = 0, hidden, ret = -1;
6951 ESCKEY_S *sel_opts;
6952 MESSAGECACHE *mc;
6953 SEARCHSET *limitsrch = NULL;
6954 PINETHRD_S *thrd;
6955 extern MAILSTREAM *mm_search_stream;
6956 extern long mm_search_count;
6958 hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
6959 mm_search_stream = state->mail_stream;
6960 mm_search_count = 0L;
6962 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
6963 sel_opts = THRD_INDX() ? sel_opts6 : sel_opts5;
6964 else
6965 sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2;
6967 if(THREADING()){
6968 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH;
6970 else{
6971 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream)){
6972 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_XGMEXT_CH;
6973 sel_opts[SEL_OPTS_THREAD].rval = sel_opts[SEL_OPTS_XGMEXT].rval;
6974 sel_opts[SEL_OPTS_THREAD].name = sel_opts[SEL_OPTS_XGMEXT].name;
6975 sel_opts[SEL_OPTS_THREAD].label = sel_opts[SEL_OPTS_XGMEXT].label;
6976 sel_opts[SEL_OPTS_XGMEXT].ch = -1;
6978 else
6979 sel_opts[SEL_OPTS_THREAD].ch = -1;
6982 if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){
6983 if(THRD_INDX()){
6984 i = 0;
6985 thrd = fetch_thread(state->mail_stream,
6986 mn_m2raw(msgmap, mn_get_cur(msgmap)));
6987 /* check if whole thread is selected or not */
6988 if(thrd &&
6989 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT)
6991 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE))
6992 i = 1;
6994 sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd");
6996 else{
6997 i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap),
6998 MN_SLCT);
6999 sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur");
7002 sel_opts += 2; /* disable extra options */
7003 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
7004 q = double_radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
7005 RB_NORM);
7006 else
7007 q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
7008 RB_NORM);
7009 switch(q){
7010 case 'f' : /* flip selection */
7011 msgno = 0L;
7012 for(i = 1L; i <= mn_get_total(msgmap); i++){
7013 ret = 0;
7014 q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT);
7015 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q);
7016 if(hidden){
7017 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q);
7018 if(!msgno && q)
7019 mn_reset_cur(msgmap, msgno = i);
7023 return(ret);
7025 case 'n' : /* narrow selection */
7026 narrow++;
7027 q = 0;
7028 break;
7029 case 'r' : /* replace selection */
7030 p = 1; /* set flag we want to replace */
7031 sel_opts -= 2; /* re-enable first two options */
7032 case 'b' : /* broaden selection */
7033 q = 0; /* offer criteria prompt */
7034 break;
7036 case 'c' : /* Un/Select Current */
7037 case 'a' : /* Unselect All */
7038 case 'x' : /* cancel */
7039 break;
7041 default :
7042 q_status_message(SM_ORDER | SM_DING, 3, 3,
7043 "Unsupported Select option");
7044 return(ret);
7048 if(!q){
7049 while(1){
7050 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
7051 q = double_radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP,
7052 RB_NORM);
7053 else
7054 q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP,
7055 RB_NORM|RB_RET_HELP);
7057 if(q == 3){
7058 helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE);
7059 ps_global->mangled_screen = 1;
7061 else
7062 break;
7066 /* When we are replacing the search, unselect all messages first, unless
7067 * we are cancelling, in whose case, leave the screen as is, because we
7068 * are cancelling!
7070 if(p == 1 && q != 'x'){
7071 msgno = any_lflagged(msgmap, MN_SLCT);
7072 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
7073 agg_select_all(state->mail_stream, msgmap, &diff,
7074 any_lflagged(msgmap, MN_SLCT) <= 0L);
7078 * The purpose of this is to add the appropriate searchset to the
7079 * search so that the search can be limited to only looking at what
7080 * it needs to look at. That is, if we are narrowing then we only need
7081 * to look at messages which are already selected, and if we are
7082 * broadening, then we only need to look at messages which are not
7083 * yet selected. This routine will work whether or not
7084 * limiting_searchset properly limits the search set. In particular,
7085 * the searchset returned by limiting_searchset may include messages
7086 * which really shouldn't be included. We do that because a too-large
7087 * searchset will break some IMAP servers. It is even possible that it
7088 * becomes inefficient to send the whole set. If the select function
7089 * frees limitsrch, it should be sure to set it to NULL so we won't
7090 * try freeing it again here.
7092 limitsrch = limiting_searchset(state->mail_stream, narrow);
7095 * NOTE: See note about MESSAGECACHE "searched" bits above!
7097 switch(q){
7098 case 'x': /* cancel */
7099 cmd_cancelled("Select command");
7100 return(ret);
7102 case 'c' : /* select/unselect current */
7103 (void) select_by_current(state, msgmap, in_index);
7104 ret = 0;
7105 return(ret);
7107 case 'a' : /* select/unselect all */
7108 msgno = any_lflagged(msgmap, MN_SLCT);
7109 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
7110 ret = 0;
7111 agg_select_all(state->mail_stream, msgmap, &diff,
7112 any_lflagged(msgmap, MN_SLCT) <= 0L);
7113 q_status_message4(SM_ORDER,0,2,
7114 "%s%s message%s %sselected",
7115 msgno ? "" : "All ", comatose(diff),
7116 plural(diff), msgno ? "UN" : "");
7117 return(ret);
7119 case 'n' : /* Select by Number */
7120 ret = 0;
7121 if(THRD_INDX())
7122 rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch);
7123 else
7124 rv = select_by_number(state->mail_stream, msgmap, &limitsrch);
7126 break;
7128 case 'd' : /* Select by Date */
7129 ret = 0;
7130 rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap),
7131 &limitsrch);
7132 break;
7134 case 't' : /* Text */
7135 ret = 0;
7136 rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap),
7137 &limitsrch);
7138 break;
7140 case 'z' : /* Size */
7141 ret = 0;
7142 rv = select_by_size(state->mail_stream, &limitsrch);
7143 break;
7145 case 's' : /* Status */
7146 ret = 0;
7147 rv = select_by_status(state->mail_stream, &limitsrch);
7148 break;
7150 case 'k' : /* Keyword */
7151 ret = 0;
7152 rv = select_by_keyword(state->mail_stream, &limitsrch);
7153 break;
7155 case 'r' : /* Rule */
7156 ret = 0;
7157 rv = select_by_rule(state->mail_stream, &limitsrch);
7158 break;
7160 case 'h' : /* Thread */
7161 ret = 0;
7162 rv = select_by_thread(state->mail_stream, msgmap, &limitsrch);
7163 break;
7165 case 'g' : /* X-GM-EXT-1 */
7166 ret = 0;
7167 rv = select_by_gm_content(state->mail_stream, msgmap, mn_get_cur(msgmap), &limitsrch);
7168 break;
7170 default :
7171 q_status_message(SM_ORDER | SM_DING, 3, 3,
7172 "Unsupported Select option");
7173 return(ret);
7176 if(limitsrch)
7177 mail_free_searchset(&limitsrch);
7179 if(rv) /* bad return value.. */
7180 return(ret); /* error already displayed */
7182 if(narrow) /* make sure something was selected */
7183 for(i = 1L; i <= mn_get_total(msgmap); i++)
7184 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7185 && raw <= state->mail_stream->nmsgs
7186 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7187 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
7188 break;
7189 else
7190 mm_search_count--;
7193 diff = 0L;
7194 if(mm_search_count){
7196 * loop thru all the messages, adjusting local flag bits
7197 * based on their "searched" bit...
7199 for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++)
7200 if(narrow){
7201 /* turning OFF selectedness if the "searched" bit isn't lit. */
7202 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7203 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7204 && raw <= state->mail_stream->nmsgs
7205 && (mc = mail_elt(state->mail_stream, raw))
7206 && !mc->searched){
7207 diff--;
7208 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
7209 if(hidden)
7210 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
7212 /* adjust current message in case we unselect and hide it */
7213 else if(msgno < mn_get_cur(msgmap)
7214 && (!THRD_INDX()
7215 || !get_lflag(state->mail_stream, msgmap,
7216 i, MN_CHID)))
7217 msgno = i;
7220 else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7221 && raw <= state->mail_stream->nmsgs
7222 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7223 /* turn ON selectedness if "searched" bit is lit. */
7224 if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7225 diff++;
7226 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
7227 if(hidden)
7228 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
7232 /* if we're zoomed and the current message was unselected */
7233 if(narrow && msgno
7234 && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE))
7235 mn_reset_cur(msgmap, msgno);
7238 if(!diff){
7239 if(narrow)
7240 q_status_message4(SM_ORDER, 3, 3,
7241 "%s. %s message%s remain%s selected.",
7242 mm_search_count
7243 ? "No change resulted"
7244 : "No messages in intersection",
7245 comatose(old_tot), plural(old_tot),
7246 (old_tot == 1L) ? "s" : "");
7247 else if(old_tot)
7248 q_status_message(SM_ORDER, 3, 3,
7249 _("No change resulted. Matching messages already selected."));
7250 else
7251 q_status_message1(SM_ORDER | SM_DING, 3, 3,
7252 _("Select failed. No %smessages selected."),
7253 old_tot ? _("additional ") : "");
7255 else if(old_tot){
7256 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
7257 "Select matched %ld message%s. %s %smessage%s %sselected.",
7258 (diff > 0) ? diff : old_tot + diff,
7259 plural((diff > 0) ? diff : old_tot + diff),
7260 comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7261 (diff > 0) ? "total " : "",
7262 plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7263 (diff > 0) ? "" : "UN");
7264 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
7265 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
7267 else
7268 q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"),
7269 comatose(diff), plural(diff));
7271 return(ret);
7275 /*----------------------------------------------------------------------
7276 Toggle the state of the current message
7278 Args: state -- pointer pine's state variables
7279 msgmap -- message collection to operate on
7280 in_index -- in the message index view
7281 Returns: TRUE if current marked selected, FALSE otw
7282 ----*/
7284 select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
7286 long cur;
7287 int all_selected = 0;
7288 unsigned long was, tot, rawno;
7289 PINETHRD_S *thrd;
7291 cur = mn_get_cur(msgmap);
7293 if(THRD_INDX()){
7294 thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur));
7295 if(!thrd)
7296 return 0;
7298 was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT);
7299 tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE);
7300 if(was == tot)
7301 all_selected++;
7303 if(all_selected){
7304 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0);
7305 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7306 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1);
7308 * See if there's anything left to zoom on. If so,
7309 * pick an adjacent one for highlighting, else make
7310 * sure nothing is left hidden...
7312 if(any_lflagged(msgmap, MN_SLCT)){
7313 mn_inc_cur(state->mail_stream, msgmap,
7314 (in_index == View && THREADING()
7315 && sp_viewing_a_thread(state->mail_stream))
7316 ? MH_THISTHD
7317 : (in_index == View)
7318 ? MH_ANYTHD : MH_NONE);
7319 if(mn_get_cur(msgmap) == cur)
7320 mn_dec_cur(state->mail_stream, msgmap,
7321 (in_index == View && THREADING()
7322 && sp_viewing_a_thread(state->mail_stream))
7323 ? MH_THISTHD
7324 : (in_index == View)
7325 ? MH_ANYTHD : MH_NONE);
7327 else /* clear all hidden flags */
7328 (void) unzoom_index(state, state->mail_stream, msgmap);
7331 else
7332 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1);
7334 q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected",
7335 comatose(all_selected ? was : tot-was),
7336 plural(all_selected ? was : tot-was),
7337 all_selected ? "UN" : "");
7339 /* collapsed thread */
7340 else if(THREADING()
7341 && ((rawno = mn_m2raw(msgmap, cur)) != 0L)
7342 && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL)
7343 && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){
7345 * This doesn't work quite the same as the colon command works, but
7346 * it is arguably doing the correct thing. The difference is
7347 * that aggregate_select will zoom after selecting back where it
7348 * was called from, but selecting a thread with colon won't zoom.
7349 * Maybe it makes sense to zoom after a select but not after a colon
7350 * command even though they are very similar.
7352 thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state));
7354 else{
7355 if((all_selected =
7356 get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */
7357 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0);
7358 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7359 set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1);
7361 * See if there's anything left to zoom on. If so,
7362 * pick an adjacent one for highlighting, else make
7363 * sure nothing is left hidden...
7365 if(any_lflagged(msgmap, MN_SLCT)){
7366 mn_inc_cur(state->mail_stream, msgmap,
7367 (in_index == View && THREADING()
7368 && sp_viewing_a_thread(state->mail_stream))
7369 ? MH_THISTHD
7370 : (in_index == View)
7371 ? MH_ANYTHD : MH_NONE);
7372 if(mn_get_cur(msgmap) == cur)
7373 mn_dec_cur(state->mail_stream, msgmap,
7374 (in_index == View && THREADING()
7375 && sp_viewing_a_thread(state->mail_stream))
7376 ? MH_THISTHD
7377 : (in_index == View)
7378 ? MH_ANYTHD : MH_NONE);
7380 else /* clear all hidden flags */
7381 (void) unzoom_index(state, state->mail_stream, msgmap);
7384 else
7385 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1);
7387 q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected",
7388 long2string(cur), all_selected ? "UN" : "");
7392 return(!all_selected);
7396 /*----------------------------------------------------------------------
7397 Prompt the user for the command to perform on selected messages
7399 Args: state -- pointer pine's state variables
7400 msgmap -- message collection to operate on
7401 q_line -- line on display to write prompts
7402 Returns: 1 if the selected messages are suitably commanded,
7403 0 if the choice to pick the command was declined
7405 ----*/
7407 apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
7408 UCS preloadkeystroke, int flags, int q_line)
7410 int i = 8, /* number of static entries in sel_opts3 */
7411 rv = 0,
7412 cmd,
7413 we_cancel = 0,
7414 agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG;
7415 char prompt[80];
7416 PAT_STATE pstate;
7419 * To do this "right", we really ought to have access to the keymenu
7420 * here and change the typed command into a real command by running
7421 * it through menu_command. Then the switch below would be against
7422 * results from menu_command. If we did that we'd also pass the
7423 * results of menu_command in as preloadkeystroke instead of passing
7424 * the keystroke itself. But we don't have the keymenu handy,
7425 * so we have to fake it. The only complication that we run into
7426 * is that KEY_DEL is an escape sequence so we change a typed
7427 * KEY_DEL esc seq into the letter D.
7430 if(!preloadkeystroke){
7431 if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
7432 sel_opts3[i].ch = '*';
7433 sel_opts3[i].rval = '*';
7434 sel_opts3[i].name = "*";
7435 sel_opts3[i++].label = N_("Flag");
7438 if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
7439 sel_opts3[i].ch = '|';
7440 sel_opts3[i].rval = '|';
7441 sel_opts3[i].name = "|";
7442 sel_opts3[i++].label = N_("Pipe");
7445 if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */
7446 sel_opts3[i].ch = 'b';
7447 sel_opts3[i].rval = 'b';
7448 sel_opts3[i].name = "B";
7449 sel_opts3[i++].label = N_("Bounce");
7452 if(flags & AC_FROM_THREAD){
7453 if(flags & (AC_COLL | AC_EXPN)){
7454 sel_opts3[i].ch = '/';
7455 sel_opts3[i].rval = '/';
7456 sel_opts3[i].name = "/";
7457 sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse")
7458 : N_("Expand");
7461 sel_opts3[i].ch = ';';
7462 sel_opts3[i].rval = ';';
7463 sel_opts3[i].name = ";";
7464 if(flags & AC_UNSEL)
7465 sel_opts3[i++].label = N_("UnSelect");
7466 else
7467 sel_opts3[i++].label = N_("Select");
7470 if(F_ON(F_ENABLE_PRYNT, state)){ /* this one is invisible */
7471 sel_opts3[i].ch = 'y';
7472 sel_opts3[i].rval = '%';
7473 sel_opts3[i].name = "";
7474 sel_opts3[i++].label = "";
7477 if(!is_imap_stream(stream) || LEVELUIDPLUS(stream)){ /* expunge selected messages */
7478 sel_opts3[i].ch = 'x';
7479 sel_opts3[i].rval = 'x';
7480 sel_opts3[i].name = "X";
7481 sel_opts3[i++].label = N_("Expunge");
7484 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7485 sel_opts3[i].ch = '#';
7486 sel_opts3[i].rval = '#';
7487 sel_opts3[i].name = "#";
7488 sel_opts3[i++].label = N_("Set Role");
7491 sel_opts3[i].ch = KEY_DEL; /* also invisible */
7492 sel_opts3[i].rval = 'd';
7493 sel_opts3[i].name = "";
7494 sel_opts3[i++].label = "";
7496 sel_opts3[i].ch = -1;
7498 snprintf(prompt, sizeof(prompt), "%s command : ",
7499 (flags & AC_FROM_THREAD) ? "THREAD" : "APPLY");
7500 prompt[sizeof(prompt)-1] = '\0';
7501 cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'c', NO_HELP,
7502 RB_SEQ_SENSITIVE);
7503 if(isupper(cmd))
7504 cmd = tolower(cmd);
7506 else{
7507 if(preloadkeystroke == KEY_DEL)
7508 cmd = 'd';
7509 else{
7510 if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke))
7511 cmd = tolower((int) preloadkeystroke);
7512 else
7513 cmd = (int) preloadkeystroke; /* shouldn't happen */
7517 switch(cmd){
7518 case 'd' : /* delete */
7519 we_cancel = busy_cue(NULL, NULL, 1);
7520 rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */
7521 if(we_cancel)
7522 cancel_busy_cue(0);
7523 break;
7525 case 'u' : /* undelete */
7526 we_cancel = busy_cue(NULL, NULL, 1);
7527 rv = cmd_undelete(state, msgmap, agg);
7528 if(we_cancel)
7529 cancel_busy_cue(0);
7530 break;
7532 case 'r' : /* reply */
7533 rv = cmd_reply(state, msgmap, agg, NULL);
7534 break;
7536 case 'f' : /* Forward */
7537 rv = cmd_forward(state, msgmap, agg, NULL);
7538 break;
7540 case '%' : /* print */
7541 rv = cmd_print(state, msgmap, agg, MsgIndx);
7542 break;
7544 case 't' : /* take address */
7545 rv = cmd_take_addr(state, msgmap, agg);
7546 break;
7548 case 's' : /* save */
7549 rv = cmd_save(state, stream, msgmap, agg, MsgIndx);
7550 break;
7552 case 'e' : /* export */
7553 rv = cmd_export(state, msgmap, q_line, agg);
7554 break;
7556 case '|' : /* pipe */
7557 rv = cmd_pipe(state, msgmap, agg);
7558 break;
7560 case '*' : /* flag */
7561 we_cancel = busy_cue(NULL, NULL, 1);
7562 rv = cmd_flag(state, msgmap, agg);
7563 if(we_cancel)
7564 cancel_busy_cue(0);
7565 break;
7567 case 'b' : /* bounce */
7568 rv = cmd_bounce(state, msgmap, agg, NULL);
7569 break;
7571 case '/' :
7572 collapse_or_expand(state, stream, msgmap,
7573 F_ON(F_SLASH_COLL_ENTIRE, ps_global)
7574 ? 0L
7575 : mn_get_cur(msgmap));
7576 break;
7578 case ':' :
7579 select_thread_stmp(state, stream, msgmap);
7580 break;
7582 case 'x' : /* Expunge */
7583 rv = cmd_expunge(state, stream, msgmap, agg);
7584 break;
7586 case 'c' : /* cancel */
7587 cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command"
7588 : "Apply command");
7589 break;
7591 case '#' :
7592 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7593 static ESCKEY_S choose_role[] = {
7594 {'r', 'r', "R", N_("Reply")},
7595 {'f', 'f', "F", N_("Forward")},
7596 {'b', 'b', "B", N_("Bounce")},
7597 {-1, 0, NULL, NULL}
7599 int action;
7600 ACTION_S *role = NULL;
7602 action = radio_buttons(_("Reply, Forward or Bounce using a role? "),
7603 -FOOTER_ROWS(state), choose_role,
7604 'r', 'x', h_role_aggregate, RB_NORM);
7605 if(action == 'r' || action == 'f' || action == 'b'){
7606 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
7608 redraw = state->redrawer;
7609 state->redrawer = NULL;
7610 prev_screen = state->prev_screen;
7611 role = NULL;
7612 state->next_screen = SCREEN_FUN_NULL;
7614 if(role_select_screen(state, &role,
7615 action == 'f' ? MC_FORWARD :
7616 action == 'r' ? MC_REPLY :
7617 action == 'b' ? MC_BOUNCE : 0) < 0){
7618 cmd_cancelled(action == 'f' ? _("Forward") :
7619 action == 'r' ? _("Reply") : _("Bounce"));
7620 state->next_screen = prev_screen;
7621 state->redrawer = redraw;
7622 state->mangled_screen = 1;
7624 else{
7625 if(role)
7626 role = combine_inherited_role(role);
7627 else{
7628 role = (ACTION_S *) fs_get(sizeof(*role));
7629 memset((void *) role, 0, sizeof(*role));
7630 role->nick = cpystr("Default Role");
7633 state->redrawer = NULL;
7634 switch(action){
7635 case 'r':
7636 (void) cmd_reply(state, msgmap, agg, role);
7637 break;
7639 case 'f':
7640 (void) cmd_forward(state, msgmap, agg, role);
7641 break;
7643 case 'b':
7644 (void) cmd_bounce(state, msgmap, agg, role);
7645 break;
7648 if(role)
7649 free_action(&role);
7651 if(redraw)
7652 (*redraw)();
7654 state->next_screen = prev_screen;
7655 state->redrawer = redraw;
7656 state->mangled_screen = 1;
7660 break;
7662 case 'z' : /* default */
7663 q_status_message(SM_INFO, 0, 2,
7664 "Cancelled, there is no default command");
7665 break;
7667 default:
7668 break;
7671 return(rv);
7676 * Select by message number ranges.
7677 * Sets searched bits in mail_elts
7679 * Args limitsrch -- limit search to this searchset
7681 * Returns 0 on success.
7684 select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
7686 int r, end, cur;
7687 long n1, n2, raw;
7688 char number1[16], number2[16], numbers[80], *p, *t;
7689 HelpType help;
7690 MESSAGECACHE *mc;
7692 numbers[0] = '\0';
7693 ps_global->mangled_footer = 1;
7694 help = NO_HELP;
7695 while(1){
7696 int flags = OE_APPEND_CURRENT;
7698 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7699 sizeof(numbers), _(select_num), NULL, help, &flags);
7700 if(r == 4)
7701 continue;
7703 if(r == 3){
7704 help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
7705 continue;
7708 for(t = p = numbers; *p ; p++) /* strip whitespace */
7709 if(!isspace((unsigned char)*p))
7710 *t++ = *p;
7712 *t = '\0';
7714 if(r == 1 || numbers[0] == '\0'){
7715 cmd_cancelled("Selection by number");
7716 return(1);
7718 else
7719 break;
7722 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7723 if((mc = mail_elt(stream, n1)) != NULL)
7724 mc->searched = 0; /* clear searched bits */
7726 for(p = numbers; *p ; p++){
7727 t = number1;
7728 while(*p && isdigit((unsigned char)*p))
7729 *t++ = *p++;
7731 *t = '\0';
7733 end = cur = 0;
7734 if(number1[0] == '\0'){
7735 if(*p == '-'){
7736 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7737 _("Invalid number range, missing number before \"-\": %s"),
7738 numbers);
7739 return(1);
7741 else if(!strucmp("end", p)){
7742 end = 1;
7743 p += strlen("end");
7745 else if(!strucmp("$", p)){
7746 end = 1;
7747 p++;
7749 else if(*p == '.'){
7750 cur = 1;
7751 p++;
7753 else{
7754 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7755 _("Invalid message number: %s"), numbers);
7756 return(1);
7760 if(end)
7761 n1 = mn_get_total(msgmap);
7762 else if(cur)
7763 n1 = mn_get_cur(msgmap);
7764 else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
7765 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7766 _("\"%s\" out of message number range"),
7767 long2string(n1));
7768 return(1);
7771 t = number2;
7772 if(*p == '-'){
7773 while(*++p && isdigit((unsigned char)*p))
7774 *t++ = *p;
7776 *t = '\0';
7778 end = cur = 0;
7779 if(number2[0] == '\0'){
7780 if(!strucmp("end", p)){
7781 end = 1;
7782 p += strlen("end");
7784 else if(!strucmp(p, "$")){
7785 end = 1;
7786 p++;
7788 else if(*p == '.'){
7789 cur = 1;
7790 p++;
7792 else{
7793 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7794 _("Invalid number range, missing number after \"-\": %s"),
7795 numbers);
7796 return(1);
7800 if(end)
7801 n2 = mn_get_total(msgmap);
7802 else if(cur)
7803 n2 = mn_get_cur(msgmap);
7804 else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){
7805 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7806 _("\"%s\" out of message number range"),
7807 long2string(n2));
7808 return(1);
7811 if(n2 <= n1){
7812 char t[20];
7814 strncpy(t, long2string(n1), sizeof(t));
7815 t[sizeof(t)-1] = '\0';
7816 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7817 _("Invalid reverse message number range: %s-%s"),
7818 t, long2string(n2));
7819 return(1);
7822 for(;n1 <= n2; n1++){
7823 raw = mn_m2raw(msgmap, n1);
7824 if(raw > 0L
7825 && (!(limitsrch && *limitsrch)
7826 || in_searchset(*limitsrch, (unsigned long) raw)))
7827 mm_searched(stream, raw);
7830 else{
7831 raw = mn_m2raw(msgmap, n1);
7832 if(raw > 0L
7833 && (!(limitsrch && *limitsrch)
7834 || in_searchset(*limitsrch, (unsigned long) raw)))
7835 mm_searched(stream, raw);
7838 if(*p == '\0')
7839 break;
7842 return(0);
7847 * Select by thread number ranges.
7848 * Sets searched bits in mail_elts
7850 * Args limitsrch -- limit search to this searchset
7852 * Returns 0 on success.
7855 select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset)
7857 int r, end, cur;
7858 long n1, n2;
7859 char number1[16], number2[16], numbers[80], *p, *t;
7860 HelpType help;
7861 PINETHRD_S *thrd = NULL, *th;
7862 MESSAGECACHE *mc;
7864 numbers[0] = '\0';
7865 ps_global->mangled_footer = 1;
7866 help = NO_HELP;
7867 while(1){
7868 int flags = OE_APPEND_CURRENT;
7870 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7871 sizeof(numbers), _(select_num), NULL, help, &flags);
7872 if(r == 4)
7873 continue;
7875 if(r == 3){
7876 help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP;
7877 continue;
7880 for(t = p = numbers; *p ; p++) /* strip whitespace */
7881 if(!isspace((unsigned char)*p))
7882 *t++ = *p;
7884 *t = '\0';
7886 if(r == 1 || numbers[0] == '\0'){
7887 cmd_cancelled("Selection by number");
7888 return(1);
7890 else
7891 break;
7894 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7895 if((mc = mail_elt(stream, n1)) != NULL)
7896 mc->searched = 0; /* clear searched bits */
7898 for(p = numbers; *p ; p++){
7899 t = number1;
7900 while(*p && isdigit((unsigned char)*p))
7901 *t++ = *p++;
7903 *t = '\0';
7905 end = cur = 0;
7906 if(number1[0] == '\0'){
7907 if(*p == '-'){
7908 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7909 _("Invalid number range, missing number before \"-\": %s"),
7910 numbers);
7911 return(1);
7913 else if(!strucmp("end", p)){
7914 end = 1;
7915 p += strlen("end");
7917 else if(!strucmp(p, "$")){
7918 end = 1;
7919 p++;
7921 else if(*p == '.'){
7922 cur = 1;
7923 p++;
7925 else{
7926 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7927 _("Invalid thread number: %s"), numbers);
7928 return(1);
7932 if(end)
7933 n1 = msgmap->max_thrdno;
7934 else if(cur){
7935 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7936 n1 = th->thrdno;
7938 else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){
7939 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7940 _("\"%s\" out of thread number range"),
7941 long2string(n1));
7942 return(1);
7945 t = number2;
7946 if(*p == '-'){
7948 while(*++p && isdigit((unsigned char)*p))
7949 *t++ = *p;
7951 *t = '\0';
7953 end = 0;
7954 if(number2[0] == '\0'){
7955 if(!strucmp("end", p)){
7956 end = 1;
7957 p += strlen("end");
7959 else if(!strucmp("$", p)){
7960 end = 1;
7961 p++;
7963 else if(*p == '.'){
7964 cur = 1;
7965 p++;
7967 else{
7968 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7969 _("Invalid number range, missing number after \"-\": %s"),
7970 numbers);
7971 return(1);
7975 if(end)
7976 n2 = msgmap->max_thrdno;
7977 else if(cur){
7978 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7979 n2 = th->thrdno;
7981 else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){
7982 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7983 _("\"%s\" out of thread number range"),
7984 long2string(n2));
7985 return(1);
7988 if(n2 <= n1){
7989 char t[20];
7991 strncpy(t, long2string(n1), sizeof(t));
7992 t[sizeof(t)-1] = '\0';
7993 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7994 _("Invalid reverse message number range: %s-%s"),
7995 t, long2string(n2));
7996 return(1);
7999 for(;n1 <= n2; n1++){
8000 thrd = find_thread_by_number(stream, msgmap, n1, thrd);
8002 if(thrd)
8003 set_search_bit_for_thread(stream, thrd, msgset);
8006 else{
8007 thrd = find_thread_by_number(stream, msgmap, n1, NULL);
8009 if(thrd)
8010 set_search_bit_for_thread(stream, thrd, msgset);
8013 if(*p == '\0')
8014 break;
8017 return(0);
8022 * Select by message dates.
8023 * Sets searched bits in mail_elts
8025 * Args limitsrch -- limit search to this searchset
8027 * Returns 0 on success.
8030 select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8032 int r, we_cancel = 0, when = 0;
8033 char date[100], defdate[100], prompt[128];
8034 time_t seldate = time(0);
8035 struct tm *seldate_tm;
8036 SEARCHPGM *pgm;
8037 HelpType help;
8038 static struct _tense {
8039 char *preamble,
8040 *range,
8041 *scope;
8042 } tense[] = {
8043 {"were ", "SENT SINCE", " (inclusive)"},
8044 {"were ", "SENT BEFORE", " (exclusive)"},
8045 {"were ", "SENT ON", "" },
8046 {"", "ARRIVED SINCE", " (inclusive)"},
8047 {"", "ARRIVED BEFORE", " (exclusive)"},
8048 {"", "ARRIVED ON", "" }
8051 date[0] = '\0';
8052 ps_global->mangled_footer = 1;
8053 help = NO_HELP;
8056 * If talking to an old server, default to SINCE instead of
8057 * SENTSINCE, which was added later.
8059 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8060 when = 3;
8062 while(1){
8063 int flags = OE_APPEND_CURRENT;
8065 seldate_tm = localtime(&seldate);
8066 snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday,
8067 month_abbrev(seldate_tm->tm_mon + 1),
8068 seldate_tm->tm_year + 1900);
8069 defdate[sizeof(defdate)-1] = '\0';
8070 snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ",
8071 tense[when].preamble, tense[when].range,
8072 tense[when].scope, defdate);
8073 prompt[sizeof(prompt)-1] = '\0';
8074 r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date),
8075 prompt, sel_date_opt, help, &flags);
8076 switch (r){
8077 case 1 :
8078 cmd_cancelled("Selection by date");
8079 return(1);
8081 case 3 :
8082 help = (help == NO_HELP) ? h_select_date : NO_HELP;
8083 continue;
8085 case 4 :
8086 continue;
8088 case 11 :
8090 MESSAGECACHE *mc;
8091 long rawno;
8093 if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L
8094 && rawno <= stream->nmsgs
8095 && (mc = mail_elt(stream, rawno))){
8097 /* cache not filled in yet? */
8098 if(mc->day == 0){
8099 char seq[20];
8101 if(stream->dtb && stream->dtb->flags & DR_NEWS){
8102 strncpy(seq,
8103 ulong2string(mail_uid(stream, rawno)),
8104 sizeof(seq));
8105 seq[sizeof(seq)-1] = '\0';
8106 mail_fetch_overview(stream, seq, NULL);
8108 else{
8109 strncpy(seq, long2string(rawno),
8110 sizeof(seq));
8111 seq[sizeof(seq)-1] = '\0';
8112 mail_fetch_fast(stream, seq, 0L);
8116 /* mail_date returns fixed field width date */
8117 mail_date(date, mc);
8118 date[11] = '\0';
8122 continue;
8124 case 12 : /* set default to PREVIOUS day */
8125 seldate -= 86400;
8126 continue;
8128 case 13 : /* set default to NEXT day */
8129 seldate += 86400;
8130 continue;
8132 case 14 :
8133 when = (when+1) % (sizeof(tense) / sizeof(struct _tense));
8134 continue;
8136 default:
8137 break;
8140 removing_leading_white_space(date);
8141 removing_trailing_white_space(date);
8142 if(!*date){
8143 strncpy(date, defdate, sizeof(date));
8144 date[sizeof(date)-1] = '\0';
8147 break;
8150 if((pgm = mail_newsearchpgm()) != NULL){
8151 MESSAGECACHE elt;
8152 short converted_date;
8154 if(mail_parse_date(&elt, (unsigned char *) date)){
8155 converted_date = mail_shortdate(elt.year, elt.month, elt.day);
8157 switch(when){
8158 case 0:
8159 pgm->sentsince = converted_date;
8160 break;
8161 case 1:
8162 pgm->sentbefore = converted_date;
8163 break;
8164 case 2:
8165 pgm->senton = converted_date;
8166 break;
8167 case 3:
8168 pgm->since = converted_date;
8169 break;
8170 case 4:
8171 pgm->before = converted_date;
8172 break;
8173 case 5:
8174 pgm->on = converted_date;
8175 break;
8178 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8180 if(ps_global && ps_global->ttyo){
8181 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8182 ps_global->mangled_footer = 1;
8185 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8187 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8189 if(we_cancel)
8190 cancel_busy_cue(0);
8192 /* we know this was freed in mail_search, let caller know */
8193 if(limitsrch)
8194 *limitsrch = NULL;
8196 else{
8197 mail_free_searchpgm(&pgm);
8198 q_status_message1(SM_ORDER, 3, 3,
8199 _("Invalid date entered: %s"), date);
8200 return(1);
8204 return(0);
8208 * Select by searching in message headers or body
8209 * using the x-gm-ext-1 capaility. This function
8210 * reads the input from the user and passes it to
8211 * the server directly. We need a x-gm-ext-1 variable
8212 * in the search pgm to carry this information to the
8213 * server.
8215 * Sets searched bits in mail_elts
8217 * Args limitsrch -- limit search to this searchset
8219 * Returns 0 on success.
8222 select_by_gm_content(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8224 int r, we_cancel = 0, rv;
8225 char tmp[128];
8226 char namehdr[MAILTMPLEN];
8227 ESCKEY_S ekey[8];
8228 HelpType help;
8230 ps_global->mangled_footer = 1;
8231 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8233 strncpy(tmp, sel_x_gm_ext, sizeof(tmp)-1);
8234 tmp[sizeof(tmp)-1] = '\0';
8236 namehdr[0] = '\0';
8238 help = NO_HELP;
8239 while (1){
8240 int flags = OE_APPEND_CURRENT;
8242 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8243 sizeof(namehdr), tmp, ekey, help, &flags);
8245 if(r == 4)
8246 continue;
8248 if(r == 3){
8249 help = (help == NO_HELP) ? h_select_by_gm_content : NO_HELP;
8250 continue;
8253 if (r == 1){
8254 cmd_cancelled("Selection by content");
8255 return(1);
8258 removing_leading_white_space(namehdr);
8260 if ((namehdr[0] != '\0')
8261 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8262 removing_trailing_white_space(namehdr);
8264 if (namehdr[0] != '\0')
8265 break;
8268 if(ps_global && ps_global->ttyo){
8269 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8270 ps_global->mangled_footer = 1;
8273 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8275 rv = agg_text_select(stream, msgmap, 'g', namehdr, 0, 0, NULL, "utf-8", limitsrch);
8276 if(we_cancel)
8277 cancel_busy_cue(0);
8279 return(rv);
8283 * Select by searching in message headers or body.
8284 * Sets searched bits in mail_elts
8286 * Args limitsrch -- limit search to this searchset
8288 * Returns 0 on success.
8291 select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8293 int r = '\0', ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
8294 int not = 0, me = 0;
8295 char sstring[512], tmp[128];
8296 char *p, *sval = NULL;
8297 char namehdr[80];
8298 ESCKEY_S ekey[8];
8299 ENVELOPE *env = NULL;
8300 HelpType help;
8301 unsigned flagsforhist = 0;
8302 static HISTORY_S *history = NULL;
8303 static char *recip = "RECIPIENTS";
8304 static char *partic = "PARTICIPANTS";
8305 static char *match_me = N_("[Match_My_Addresses]");
8306 static char *dont_match_me = N_("[Don't_Match_My_Addresses]");
8308 ps_global->mangled_footer = 1;
8309 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8311 while(1){
8312 type = radio_buttons(not ? _(sel_text_not) : _(sel_text),
8313 -FOOTER_ROWS(ps_global), sel_text_opt,
8314 's', 'x', NO_HELP, RB_NORM|RB_RET_HELP);
8316 if(type == '!')
8317 not = !not;
8318 else if(type == 3){
8319 helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS",
8320 HLPD_SIMPLE);
8321 ps_global->mangled_screen = 1;
8323 else
8324 break;
8328 * prepare some friendly defaults...
8330 switch(type){
8331 case 't' : /* address fields, offer To or From */
8332 case 'f' :
8333 case 'c' :
8334 case 'r' :
8335 case 'p' :
8336 sval = (type == 't') ? "TO" :
8337 (type == 'f') ? "FROM" :
8338 (type == 'c') ? "CC" :
8339 (type == 'r') ? recip : partic;
8340 ekey[ekeyi].ch = ctrl('T');
8341 ekey[ekeyi].name = "^T";
8342 ekey[ekeyi].rval = 10;
8343 /* TRANSLATORS: use Current To Address */
8344 ekey[ekeyi++].label = N_("Cur To");
8345 ekey[ekeyi].ch = ctrl('R');
8346 ekey[ekeyi].name = "^R";
8347 ekey[ekeyi].rval = 11;
8348 /* TRANSLATORS: use Current From Address */
8349 ekey[ekeyi++].label = N_("Cur From");
8350 ekey[ekeyi].ch = ctrl('W');
8351 ekey[ekeyi].name = "^W";
8352 ekey[ekeyi].rval = 12;
8353 /* TRANSLATORS: use Current Cc Address */
8354 ekey[ekeyi++].label = N_("Cur Cc");
8355 ekey[ekeyi].ch = ctrl('Y');
8356 ekey[ekeyi].name = "^Y";
8357 ekey[ekeyi].rval = 13;
8358 /* TRANSLATORS: Match Me means match my address */
8359 ekey[ekeyi++].label = N_("Match Me");
8360 ekey[ekeyi].ch = 0;
8361 ekey[ekeyi].name = "";
8362 ekey[ekeyi].rval = 0;
8363 ekey[ekeyi++].label = "";
8364 break;
8366 case 's' :
8367 sval = "SUBJECT";
8368 ekey[ekeyi].ch = ctrl('X');
8369 ekey[ekeyi].name = "^X";
8370 ekey[ekeyi].rval = 14;
8371 /* TRANSLATORS: use Current Subject */
8372 ekey[ekeyi++].label = N_("Cur Subject");
8373 break;
8375 case 'a' :
8376 sval = "TEXT";
8377 break;
8379 case 'b' :
8380 sval = "BODYTEXT";
8381 break;
8383 case 'h' :
8384 strncpy(tmp, "Name of HEADER to match : ", sizeof(tmp)-1);
8385 tmp[sizeof(tmp)-1] = '\0';
8386 flags = OE_APPEND_CURRENT;
8387 namehdr[0] = '\0';
8388 r = 'x';
8389 while (r == 'x'){
8390 int done = 0;
8392 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8393 sizeof(namehdr), tmp, ekey, NO_HELP, &flags);
8394 if (r == 1){
8395 cmd_cancelled("Selection by text");
8396 return(1);
8398 removing_leading_white_space(namehdr);
8399 while(!done){
8400 while ((namehdr[0] != '\0') && /* remove trailing ":" */
8401 (namehdr[strlen(namehdr) - 1] == ':'))
8402 namehdr[strlen(namehdr) - 1] = '\0';
8403 if ((namehdr[0] != '\0')
8404 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8405 removing_trailing_white_space(namehdr);
8406 else
8407 done++;
8409 if (strchr(namehdr,' ') || strchr(namehdr,'\t') ||
8410 strchr(namehdr,':'))
8411 namehdr[0] = '\0';
8412 if (namehdr[0] == '\0')
8413 r = 'x';
8415 sval = namehdr;
8416 break;
8418 case 'x':
8419 break;
8421 default:
8422 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
8423 return(1);
8426 ekey[ekeyi].ch = KEY_UP;
8427 ekey[ekeyi].rval = 30;
8428 ekey[ekeyi].name = "";
8429 ku = ekeyi;
8430 ekey[ekeyi++].label = "";
8432 ekey[ekeyi].ch = KEY_DOWN;
8433 ekey[ekeyi].rval = 31;
8434 ekey[ekeyi].name = "";
8435 ekey[ekeyi++].label = "";
8437 ekey[ekeyi].ch = -1;
8439 if(type != 'x'){
8441 init_hist(&history, HISTSIZE);
8443 if(ekey[0].ch > -1 && msgno > 0L
8444 && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),
8445 NULL)))
8446 ekey[0].ch = -1;
8448 sstring[0] = '\0';
8449 help = NO_HELP;
8450 r = type;
8451 while(r != 'x'){
8452 if(not)
8453 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8454 snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval);
8455 else
8456 snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval);
8458 if(items_in_hist(history) > 0){
8459 ekey[ku].name = HISTORY_UP_KEYNAME;
8460 ekey[ku].label = HISTORY_KEYLABEL;
8461 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
8462 ekey[ku+1].label = HISTORY_KEYLABEL;
8464 else{
8465 ekey[ku].name = "";
8466 ekey[ku].label = "";
8467 ekey[ku+1].name = "";
8468 ekey[ku+1].label = "";
8471 flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
8472 r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
8473 sizeof(sstring), tmp, ekey, help, &flags);
8475 if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me)))))
8476 me = 0;
8478 switch(r){
8479 case 3 :
8480 help = (help == NO_HELP)
8481 ? (not
8482 ? ((type == 'f') ? h_select_txt_not_from
8483 : (type == 't') ? h_select_txt_not_to
8484 : (type == 'c') ? h_select_txt_not_cc
8485 : (type == 's') ? h_select_txt_not_subj
8486 : (type == 'a') ? h_select_txt_not_all
8487 : (type == 'r') ? h_select_txt_not_recip
8488 : (type == 'p') ? h_select_txt_not_partic
8489 : (type == 'b') ? h_select_txt_not_body
8490 : NO_HELP)
8491 : ((type == 'f') ? h_select_txt_from
8492 : (type == 't') ? h_select_txt_to
8493 : (type == 'c') ? h_select_txt_cc
8494 : (type == 's') ? h_select_txt_subj
8495 : (type == 'a') ? h_select_txt_all
8496 : (type == 'r') ? h_select_txt_recip
8497 : (type == 'p') ? h_select_txt_partic
8498 : (type == 'b') ? h_select_txt_body
8499 : NO_HELP))
8500 : NO_HELP;
8502 case 4 :
8503 continue;
8505 case 10 : /* To: default */
8506 if(env && env->to && env->to->mailbox){
8507 snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox,
8508 env->to->host ? "@" : "",
8509 env->to->host ? env->to->host : "");
8510 sstring[sizeof(sstring)-1] = '\0';
8512 continue;
8514 case 11 : /* From: default */
8515 if(env && env->from && env->from->mailbox){
8516 snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox,
8517 env->from->host ? "@" : "",
8518 env->from->host ? env->from->host : "");
8519 sstring[sizeof(sstring)-1] = '\0';
8521 continue;
8523 case 12 : /* Cc: default */
8524 if(env && env->cc && env->cc->mailbox){
8525 snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox,
8526 env->cc->host ? "@" : "",
8527 env->cc->host ? env->cc->host : "");
8528 sstring[sizeof(sstring)-1] = '\0';
8530 continue;
8532 case 13 : /* Match my addresses */
8533 me++;
8534 snprintf(sstring, sizeof(sstring), "%s", not ? _(dont_match_me) : _(match_me));
8535 continue;
8537 case 14 : /* Subject: default */
8538 if(env && env->subject && env->subject[0]){
8539 char *q = NULL;
8541 q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
8542 SIZEOF_20KBUF, env->subject);
8543 snprintf(sstring, sizeof(sstring), "%s", q);
8544 sstring[sizeof(sstring)-1] = '\0';
8547 continue;
8549 case 30 :
8550 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8551 if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){
8552 strncpy(sstring, p, sizeof(sstring));
8553 sstring[sizeof(sstring)-1] = '\0';
8554 if(history->hist[history->curindex]){
8555 flagsforhist = history->hist[history->curindex]->flags;
8556 not = (flagsforhist & 0x1) ? 1 : 0;
8557 me = (flagsforhist & 0x2) ? 1 : 0;
8560 else
8561 Writechar(BELL, 0);
8563 continue;
8565 case 31 :
8566 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8567 if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){
8568 strncpy(sstring, p, sizeof(sstring));
8569 sstring[sizeof(sstring)-1] = '\0';
8570 if(history->hist[history->curindex]){
8571 flagsforhist = history->hist[history->curindex]->flags;
8572 not = (flagsforhist & 0x1) ? 1 : 0;
8573 me = (flagsforhist & 0x2) ? 1 : 0;
8576 else
8577 Writechar(BELL, 0);
8579 continue;
8581 default :
8582 break;
8585 if(r == 1 || sstring[0] == '\0')
8586 r = 'x';
8588 break;
8592 if(type == 'x' || r == 'x'){
8593 cmd_cancelled("Selection by text");
8594 return(1);
8597 if(ps_global && ps_global->ttyo){
8598 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8599 ps_global->mangled_footer = 1;
8602 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8604 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8605 save_hist(history, sstring, flagsforhist, NULL);
8607 rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch);
8608 if(we_cancel)
8609 cancel_busy_cue(0);
8611 return(rv);
8616 * Select by message size.
8617 * Sets searched bits in mail_elts
8619 * Args limitsrch -- limit search to this searchset
8621 * Returns 0 on success.
8624 select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch)
8626 int r, large = 1, we_cancel = 0;
8627 unsigned long n, mult = 1L, numerator = 0L, divisor = 1L;
8628 char size[16], numbers[80], *p, *t;
8629 HelpType help;
8630 SEARCHPGM *pgm;
8631 long flags = (SE_NOPREFETCH | SE_FREE);
8633 numbers[0] = '\0';
8634 ps_global->mangled_footer = 1;
8636 help = NO_HELP;
8637 while(1){
8638 int flgs = OE_APPEND_CURRENT;
8640 sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger;
8642 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
8643 sizeof(numbers), large ? _(select_size_larger_msg)
8644 : _(select_size_smaller_msg),
8645 sel_size_opt, help, &flgs);
8646 if(r == 4)
8647 continue;
8649 if(r == 14){
8650 large = 1 - large;
8651 continue;
8654 if(r == 3){
8655 help = (help == NO_HELP) ? (large ? h_select_by_larger_size
8656 : h_select_by_smaller_size)
8657 : NO_HELP;
8658 continue;
8661 for(t = p = numbers; *p ; p++) /* strip whitespace */
8662 if(!isspace((unsigned char)*p))
8663 *t++ = *p;
8665 *t = '\0';
8667 if(r == 1 || numbers[0] == '\0'){
8668 cmd_cancelled("Selection by size");
8669 return(1);
8671 else
8672 break;
8675 if(numbers[0] == '-'){
8676 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8677 _("Invalid size entered: %s"), numbers);
8678 return(1);
8681 t = size;
8682 p = numbers;
8684 while(*p && isdigit((unsigned char)*p))
8685 *t++ = *p++;
8687 *t = '\0';
8689 if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){
8690 size[0] = '0';
8691 size[1] = '\0';
8694 if(size[0] == '\0'){
8695 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8696 _("Invalid size entered: %s"), numbers);
8697 return(1);
8700 n = strtoul(size, (char **)NULL, 10);
8702 size[0] = '\0';
8703 if(*p == '.'){
8705 * We probably ought to just use atof() to convert 1.1 into a
8706 * double, but since we haven't used atof() anywhere else I'm
8707 * reluctant to use it because of portability concerns.
8709 p++;
8710 t = size;
8711 while(*p && isdigit((unsigned char)*p)){
8712 *t++ = *p++;
8713 divisor *= 10;
8716 *t = '\0';
8718 if(size[0])
8719 numerator = strtoul(size, (char **)NULL, 10);
8722 switch(*p){
8723 case 'g':
8724 case 'G':
8725 mult *= 1000;
8726 /* fall through */
8728 case 'm':
8729 case 'M':
8730 mult *= 1000;
8731 /* fall through */
8733 case 'k':
8734 case 'K':
8735 mult *= 1000;
8736 break;
8739 n = n * mult + (numerator * mult) / divisor;
8741 pgm = mail_newsearchpgm();
8742 if(large)
8743 pgm->larger = n;
8744 else
8745 pgm->smaller = n;
8747 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8748 flags |= SE_NOSERVER;
8750 if(ps_global && ps_global->ttyo){
8751 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8752 ps_global->mangled_footer = 1;
8755 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8757 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8758 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8759 /* we know this was freed in mail_search, let caller know */
8760 if(limitsrch)
8761 *limitsrch = NULL;
8763 if(we_cancel)
8764 cancel_busy_cue(0);
8766 return(0);
8771 * visible_searchset -- return c-client search set unEXLDed
8772 * sequence numbers
8774 SEARCHSET *
8775 visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap)
8777 long n, run;
8778 SEARCHSET *full_set = NULL, **set;
8781 * If we're talking to anything other than a server older than
8782 * imap 4rev1, build a searchset otherwise it'll choke.
8784 if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
8785 if(any_lflagged(msgmap, MN_EXLD)){
8786 for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
8787 if(get_lflag(stream, NULL, n, MN_EXLD)){
8788 if(run){ /* previous NOT excluded? */
8789 if(run > 1L)
8790 (*set)->last = n - 1L;
8792 set = &(*set)->next;
8793 run = 0L;
8796 else if(run++){ /* next in run */
8797 (*set)->last = n;
8799 else{ /* start of run */
8800 *set = mail_newsearchset();
8801 (*set)->first = n;
8804 else{
8805 full_set = mail_newsearchset();
8806 full_set->first = 1L;
8807 full_set->last = stream->nmsgs;
8811 return(full_set);
8816 * Select by message status bits.
8817 * Sets searched bits in mail_elts
8819 * Args limitsrch -- limit search to this searchset
8821 * Returns 0 on success.
8824 select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch)
8826 int s, not = 0, we_cancel = 0, rv;
8828 while(1){
8829 s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag),
8830 -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x',
8831 NO_HELP, RB_NORM|RB_RET_HELP);
8833 if(s == 'x'){
8834 cmd_cancelled("Selection by status");
8835 return(1);
8837 else if(s == 3){
8838 helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"),
8839 HLPD_SIMPLE);
8840 ps_global->mangled_screen = 1;
8842 else if(s == '!')
8843 not = !not;
8844 else
8845 break;
8848 if(ps_global && ps_global->ttyo){
8849 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8850 ps_global->mangled_footer = 1;
8853 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8854 rv = agg_flag_select(stream, not, s, limitsrch);
8855 if(we_cancel)
8856 cancel_busy_cue(0);
8858 return(rv);
8863 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8864 * Sets searched bits in mail_elts
8866 * Args limitsrch -- limit search to this searchset
8868 * Returns 0 on success.
8871 select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
8873 char rulenick[1000], *nick;
8874 PATGRP_S *patgrp;
8875 int r, last_r = 0, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
8876 | ROLE_DO_INCOLS
8877 | ROLE_DO_ROLES
8878 | ROLE_DO_SCORES
8879 | ROLE_DO_OTHER
8880 | ROLE_DO_FILTER;
8882 rulenick[0] = '\0';
8883 ps_global->mangled_footer = 1;
8885 if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
8886 sel_rule_opt[2].ch = TAB;
8887 sel_rule_opt[2].rval = 15;
8888 sel_rule_opt[2].name = "TAB";
8889 sel_rule_opt[2].label = N_("Complete");
8891 else{
8892 memset(&sel_rule_opt[2], 0, sizeof(sel_rule_opt[2]));
8896 int oe_flags;
8898 oe_flags = OE_APPEND_CURRENT;
8899 r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0,
8900 sizeof(rulenick),
8901 not ? _("Rule to NOT match: ")
8902 : _("Rule to match: "),
8903 sel_rule_opt, NO_HELP, &oe_flags);
8905 if(r == 14){
8906 /* select rulenick from a list */
8907 if((nick=choose_a_rule(rflags, NULL)) != NULL){
8908 strncpy(rulenick, nick, sizeof(rulenick)-1);
8909 rulenick[sizeof(rulenick)-1] = '\0';
8910 fs_give((void **) &nick);
8912 else
8913 r = 4;
8915 else if(r == 15){
8916 int n = rulenick_complete(rflags, rulenick, sizeof(rulenick));
8918 if(n > 1 && last_r == 15 && !(oe_flags & OE_USER_MODIFIED)){
8919 /* double tab with multiple completions: select from list */
8920 if((nick=choose_a_rule(rflags, rulenick)) != NULL){
8921 strncpy(rulenick, nick, sizeof(rulenick)-1);
8922 rulenick[sizeof(rulenick)-1] = '\0';
8923 fs_give((void **) &nick);
8924 r = 0;
8926 else
8927 r = 4;
8929 else if(n != 1)
8930 Writechar(BELL, 0);
8932 else if(r == '!')
8933 not = !not;
8935 if(r == 3){
8936 helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE);
8937 ps_global->mangled_screen = 1;
8939 else if(r == 1){
8940 cmd_cancelled("Selection by Rule");
8941 return(1);
8944 removing_leading_and_trailing_white_space(rulenick);
8945 last_r = r;
8947 }while(r == 3 || r == 4 || r == 15 || r == '!');
8951 * The approach of requiring a nickname instead of just allowing the
8952 * user to select from the list of rules has the drawback that a rule
8953 * may not have a nickname, or there may be more than one rule with
8954 * the same nickname. However, it has the benefit of allowing the user
8955 * to type in the nickname and, most importantly, allows us to set
8956 * up the ! (not). We could incorporate the ! into the selection
8957 * screen, but this is easier and also allows the typing of nicks.
8958 * User can just set up nicknames if they want to use this feature.
8960 patgrp = nick_to_patgrp(rulenick, rflags);
8962 if(patgrp){
8963 if(ps_global && ps_global->ttyo){
8964 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8965 ps_global->mangled_footer = 1;
8968 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8969 match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL,
8970 get_msg_score,
8971 (not ? MP_NOT : 0) | SE_NOPREFETCH);
8972 free_patgrp(&patgrp);
8973 if(we_cancel)
8974 cancel_busy_cue(0);
8977 if(limitsrch && *limitsrch){
8978 mail_free_searchset(limitsrch);
8979 *limitsrch = NULL;
8982 return(0);
8987 * Allow user to choose a rule from their list of rules.
8989 * Args rflags -- Pattern types to choose from
8990 * prefix -- Only list rules matching the given prefix
8992 * Returns an allocated rule nickname on success, NULL otherwise.
8994 char *
8995 choose_a_rule(int rflags, char *prefix)
8997 char *choice = NULL;
8998 char **rule_list, **lp;
8999 int cnt = 0;
9000 int prefix_length = 0;
9001 PAT_S *pat;
9002 PAT_STATE pstate;
9003 void (*redraw)(void) = ps_global->redrawer;
9005 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){
9006 q_status_message(SM_ORDER, 3, 3,
9007 _("No rules available. Use Setup/Rules to add some."));
9008 return(choice);
9012 * Build a list of rules to choose from.
9015 if(prefix)
9016 prefix_length = strlen(prefix);
9018 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)) {
9019 if(!pat->patgrp || !pat->patgrp->nick)
9020 continue;
9021 if(!prefix || strncmp(pat->patgrp->nick, prefix, prefix_length) == 0)
9022 cnt++;
9025 if(cnt <= 0){
9026 q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
9027 return(choice);
9030 lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
9031 memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
9033 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)) {
9034 if(!pat->patgrp || !pat->patgrp->nick)
9035 continue;
9036 if(!prefix || strncmp(pat->patgrp->nick, prefix, prefix_length) == 0)
9037 *lp++ = cpystr(pat->patgrp->nick);
9040 /* TRANSLATORS: SELECT A RULE is a screen title
9041 TRANSLATORS: Print something1 using something2.
9042 "rules" is something1 */
9043 choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"),
9044 _("rules"), h_select_rule_screen,
9045 _("HELP FOR SELECTING A RULE NICKNAME"), NULL);
9047 if(!choice){
9048 q_status_message(SM_ORDER, 1, 4, "No choice");
9049 ps_global->redrawer = redraw;
9052 free_list_array(&rule_list);
9054 return(choice);
9059 * Complete a partial rule name against the user's list of rules.
9061 * Args rflags -- Pattern types to choose from
9062 * nick -- Current rule nickname to complete
9063 * nick_size -- Maximum length of the nick array to store completion in
9065 * Returns the number of valid completions, i.e.:
9067 * 0 if there are no valid completions. The value of the nick argument has not
9068 * been changed.
9069 * 1 if there is only a single valid completion. The value of the nick argument
9070 * has been replaced with the full text of that completion.
9071 * >1 if there is more than one valid completion. The value of the nick argument
9072 * has been replaced with the longest substring common to all the appropriate
9073 * completions.
9076 rulenick_complete(int rflags, char *nick, int nick_size)
9078 char *candidate = NULL;
9079 int cnt = 0;
9080 int common_prefix_length = 0;
9081 int nick_length;
9082 PAT_S *pat;
9083 PAT_STATE pstate;
9085 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate)))
9086 return 0;
9088 nick_length = strlen(nick);
9090 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){
9091 if(!pat->patgrp || !pat->patgrp->nick)
9092 continue;
9093 if(strncmp(pat->patgrp->nick, nick, nick_length) == 0){
9094 /* This is a candidate for completion. */
9095 cnt++;
9096 if(!candidate){
9097 /* This is the first candidate. Keep it as a future reference to
9098 * compare against to find the longest common prefix length of
9099 * all the matches. */
9100 candidate = pat->patgrp->nick;
9101 common_prefix_length = strlen(pat->patgrp->nick);
9103 else{
9104 /* Find the common prefix length between the first candidate and
9105 * this one. */
9106 int i;
9107 for(i = 0; i < common_prefix_length; i++){
9108 if(pat->patgrp->nick[i] != candidate[i]){
9109 /* In the event that we ended up in the middle of a
9110 * UTF-8 code point, backtrack to the byte before the
9111 * start of this code point. */
9112 while(i > 0 && (candidate[i] & 0xC0) == 0x80)
9113 i--;
9114 common_prefix_length = i;
9115 break;
9122 if(cnt > 0){
9123 int length = MIN(nick_size, common_prefix_length);
9124 strncpy(nick + nick_length, candidate + nick_length, length - nick_length);
9125 nick[length] = '\0';
9128 return cnt;
9133 * Select by current thread.
9134 * Sets searched bits in mail_elts for this entire thread
9136 * Args limitsrch -- limit search to this searchset
9138 * Returns 0 on success.
9141 select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
9143 long n;
9144 PINETHRD_S *thrd = NULL;
9145 int ret = 1;
9146 MESSAGECACHE *mc;
9148 if(!stream)
9149 return(ret);
9151 for(n = 1L; n <= stream->nmsgs; n++)
9152 if((mc = mail_elt(stream, n)) != NULL)
9153 mc->searched = 0; /* clear searched bits */
9155 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
9156 if(thrd && thrd->top && thrd->top != thrd->rawno)
9157 thrd = fetch_thread(stream, thrd->top);
9160 * This doesn't unselect if the thread is already selected
9161 * (like select current does), it always selects.
9162 * There is no way to select ! this thread.
9164 if(thrd){
9165 set_search_bit_for_thread(stream, thrd, limitsrch);
9166 ret = 0;
9169 return(ret);
9174 * Select by message keywords.
9175 * Sets searched bits in mail_elts
9177 * Args limitsrch -- limit search to this searchset
9179 * Returns 0 on success.
9182 select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
9184 int r, last_r = 0, not = 0, we_cancel = 0;
9185 char keyword[MAXUSERFLAG+1], *kword;
9186 char *error = NULL, *p, *prompt;
9187 HelpType help;
9188 SEARCHPGM *pgm;
9190 keyword[0] = '\0';
9191 ps_global->mangled_footer = 1;
9193 if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
9194 sel_key_opt[2].ch = TAB;
9195 sel_key_opt[2].rval = 15;
9196 sel_key_opt[2].name = "TAB";
9197 sel_key_opt[2].label = N_("Complete");
9199 else{
9200 memset(&sel_key_opt[2], 0, sizeof(sel_key_opt[2]));
9203 help = NO_HELP;
9205 int oe_flags;
9207 if(error){
9208 q_status_message(SM_ORDER, 3, 4, error);
9209 fs_give((void **) &error);
9212 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
9213 if(not)
9214 prompt = _("Keyword (or keyword initial) to NOT match: ");
9215 else
9216 prompt = _("Keyword (or keyword initial) to match: ");
9218 else{
9219 if(not)
9220 prompt = _("Keyword to NOT match: ");
9221 else
9222 prompt = _("Keyword to match: ");
9225 oe_flags = OE_APPEND_CURRENT;
9226 r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0,
9227 sizeof(keyword),
9228 prompt, sel_key_opt, help, &oe_flags);
9230 if(r == 14){
9231 /* select keyword from a list */
9232 if((kword=choose_a_keyword(NULL)) != NULL){
9233 strncpy(keyword, kword, sizeof(keyword)-1);
9234 keyword[sizeof(keyword)-1] = '\0';
9235 fs_give((void **) &kword);
9237 else
9238 r = 4;
9240 else if(r == 15){
9241 int n = keyword_complete(keyword, sizeof(keyword));
9243 if(n > 1 && last_r == 15 && !(oe_flags & OE_USER_MODIFIED)){
9244 /* double tab with multiple completions: select from list */
9245 if((kword=choose_a_keyword(keyword)) != NULL){
9246 strncpy(keyword, kword, sizeof(keyword)-1);
9247 keyword[sizeof(keyword)-1] = '\0';
9248 fs_give((void **) &kword);
9249 r = 0;
9251 else
9252 r = 4;
9254 else if(n != 1)
9255 Writechar(BELL, 0);
9257 else if(r == '!')
9258 not = !not;
9260 if(r == 3)
9261 help = help == NO_HELP ? h_select_keyword : NO_HELP;
9262 else if(r == 1){
9263 cmd_cancelled("Selection by keyword");
9264 return(1);
9267 removing_leading_and_trailing_white_space(keyword);
9268 last_r = r;
9270 }while(r == 3 || r == 4 || r == 15 || r == '!' || keyword_check(keyword, &error));
9273 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
9274 p = initial_to_keyword(keyword);
9275 if(p != keyword){
9276 strncpy(keyword, p, sizeof(keyword)-1);
9277 keyword[sizeof(keyword)-1] = '\0';
9282 * We want to check the keyword, not the nickname of the keyword,
9283 * so convert it to the keyword if necessary.
9285 p = nick_to_keyword(keyword);
9286 if(p != keyword){
9287 strncpy(keyword, p, sizeof(keyword)-1);
9288 keyword[sizeof(keyword)-1] = '\0';
9291 pgm = mail_newsearchpgm();
9292 if(not){
9293 pgm->unkeyword = mail_newstringlist();
9294 pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword);
9295 pgm->unkeyword->text.size = strlen(keyword);
9297 else{
9298 pgm->keyword = mail_newstringlist();
9299 pgm->keyword->text.data = (unsigned char *) cpystr(keyword);
9300 pgm->keyword->text.size = strlen(keyword);
9303 if(ps_global && ps_global->ttyo){
9304 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9305 ps_global->mangled_footer = 1;
9308 we_cancel = busy_cue(_("Selecting"), NULL, 1);
9310 pgm->msgno = (limitsrch ? *limitsrch : NULL);
9311 pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE);
9312 /* we know this was freed in mail_search, let caller know */
9313 if(limitsrch)
9314 *limitsrch = NULL;
9316 if(we_cancel)
9317 cancel_busy_cue(0);
9319 return(0);
9324 * Allow user to choose a keyword from their list of keywords.
9326 * Args prefix -- Only list keywords matching the given prefix
9328 * Returns an allocated keyword on success, NULL otherwise.
9330 char *
9331 choose_a_keyword(char *prefix)
9333 char *choice = NULL;
9334 char **keyword_list, **lp;
9335 int cnt;
9336 int prefix_length = 0;
9337 KEYWORD_S *kw;
9338 void (*redraw)(void) = ps_global->redrawer;
9341 * Build a list of keywords to choose from.
9344 if(prefix)
9345 prefix_length = strlen(prefix);
9347 for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next){
9348 char *kw_name = kw->nick ? kw->nick : kw->kw;
9349 if(!prefix || kw_name && strncmp(kw_name, prefix, prefix_length) == 0)
9350 cnt++;
9353 if(cnt <= 0){
9354 q_status_message(SM_ORDER, 3, 4,
9355 _("No keywords defined, use \"keywords\" option in Setup/Config"));
9356 return(choice);
9359 lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list));
9360 memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list));
9362 for(kw = ps_global->keywords; kw; kw = kw->next){
9363 char *kw_name = kw->nick ? kw->nick : kw->kw;
9364 if(!kw_name)
9365 continue;
9366 if(!prefix || kw_name && strncmp(kw_name, prefix, prefix_length) == 0)
9367 *lp++ = cpystr(kw_name);
9370 /* TRANSLATORS: SELECT A KEYWORD is a screen title
9371 TRANSLATORS: Print something1 using something2.
9372 "keywords" is something1 */
9373 choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"),
9374 _("keywords"), h_select_keyword_screen,
9375 _("HELP FOR SELECTING A KEYWORD"), NULL);
9377 if(!choice){
9378 q_status_message(SM_ORDER, 1, 4, "No choice");
9379 ps_global->redrawer = redraw;
9382 free_list_array(&keyword_list);
9384 return(choice);
9389 * Complete a partial keyword name against the user's list of keywords.
9391 * Args keyword -- Current keyword to complete
9392 * keyword_size -- Maximum length of the keyword array to store
9393 * completion in
9395 * Returns the number of valid completions, i.e.:
9397 * 0 if there are no valid completions. The value of the keyword argument has
9398 * not been changed.
9399 * 1 if there is only a single valid completion. The value of the keyword
9400 * argument has been replaced with the full text of that completion.
9401 * >1 if there is more than one valid completion. The value of the keyword
9402 * argument has been replaced with the longest substring common to all the
9403 * appropriate completions.
9406 keyword_complete(char *keyword, int keyword_size)
9408 char *candidate = NULL;
9409 int cnt = 0;
9410 int common_prefix_length = 0;
9411 int keyword_length;
9412 KEYWORD_S *kw;
9414 keyword_length = strlen(keyword);
9416 for(kw = ps_global->keywords; kw; kw = kw->next){
9417 char *kw_name = kw->nick ? kw->nick : kw->kw;
9418 if(!kw_name)
9419 continue;
9420 if(strncmp(kw_name, keyword, keyword_length) == 0){
9421 /* This is a candidate for completion. */
9422 cnt++;
9423 if(!candidate){
9424 /* This is the first candidate. Keep it as a future reference to
9425 * compare against to find the longest common prefix length of
9426 * all the matches. */
9427 candidate = kw_name;
9428 common_prefix_length = strlen(candidate);
9430 else{
9431 /* Find the common prefix length between the first candidate and
9432 * this one. */
9433 int i;
9434 for(i = 0; i < common_prefix_length; i++){
9435 if(kw_name[i] != candidate[i]){
9436 /* In the event that we ended up in the middle of a
9437 * UTF-8 code point, backtrack to the byte before the
9438 * start of this code point. */
9439 while(i > 0 && (candidate[i] & 0xC0) == 0x80)
9440 i--;
9441 common_prefix_length = i;
9442 break;
9449 if(cnt > 0){
9450 int length = MIN(keyword_size, common_prefix_length);
9451 strncpy(keyword + keyword_length, candidate + keyword_length, length - keyword_length);
9452 keyword[length] = '\0';
9455 return cnt;
9460 * Allow user to choose a list of keywords from their list of keywords.
9462 * Returns allocated list.
9464 char **
9465 choose_list_of_keywords(void)
9467 LIST_SEL_S *listhead, *ls, *p;
9468 char **ret = NULL;
9469 int cnt, i;
9470 KEYWORD_S *kw;
9473 * Build a list of keywords to choose from.
9476 p = listhead = NULL;
9477 for(kw = ps_global->keywords; kw; kw = kw->next){
9479 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9480 memset(ls, 0, sizeof(*ls));
9481 ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9483 if(p){
9484 p->next = ls;
9485 p = p->next;
9487 else
9488 listhead = p = ls;
9491 if(!listhead)
9492 return(ret);
9494 /* TRANSLATORS: SELECT KEYWORDS is a screen title
9495 Print something1 using something2.
9496 "keywords" is something1 */
9497 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9498 _("SELECT KEYWORDS"), _("keywords"),
9499 h_select_multkeyword_screen,
9500 _("HELP FOR SELECTING KEYWORDS"), NULL)){
9501 for(cnt = 0, p = listhead; p; p = p->next)
9502 if(p->selected)
9503 cnt++;
9505 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9506 memset(ret, 0, (cnt+1) * sizeof(*ret));
9507 for(i = 0, p = listhead; p; p = p->next)
9508 if(p->selected)
9509 ret[i++] = cpystr(p->item ? p->item : "");
9512 free_list_sel(&listhead);
9514 return(ret);
9519 * Allow user to choose a charset
9521 * Returns an allocated charset on success, NULL otherwise.
9523 char *
9524 choose_a_charset(int which_charsets)
9526 char *choice = NULL;
9527 char **charset_list, **lp;
9528 const CHARSET *cs;
9529 int cnt;
9530 void (*redraw)(void) = ps_global->redrawer;
9533 * Build a list of charsets to choose from.
9536 for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){
9537 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9538 && ((which_charsets & CAC_ALL)
9539 || (which_charsets & CAC_POSTING
9540 && cs->flags & CF_POSTING)
9541 || (which_charsets & CAC_DISPLAY
9542 && cs->type != CT_2022
9543 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9544 cnt++;
9547 if(cnt <= 0){
9548 q_status_message(SM_ORDER, 3, 4,
9549 _("No charsets found? Enter charset manually."));
9550 return(choice);
9553 lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list));
9554 memset(charset_list, 0, (cnt+1) * sizeof(*charset_list));
9556 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9557 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9558 && ((which_charsets & CAC_ALL)
9559 || (which_charsets & CAC_POSTING
9560 && cs->flags & CF_POSTING)
9561 || (which_charsets & CAC_DISPLAY
9562 && cs->type != CT_2022
9563 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9564 *lp++ = cpystr(cs->name);
9567 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
9568 TRANSLATORS: Print something1 using something2.
9569 "character sets" is something1 */
9570 choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"),
9571 _("character sets"), h_select_charset_screen,
9572 _("HELP FOR SELECTING A CHARACTER SET"), NULL);
9574 if(!choice){
9575 q_status_message(SM_ORDER, 1, 4, "No choice");
9576 ps_global->redrawer = redraw;
9579 free_list_array(&charset_list);
9581 return(choice);
9586 * Allow user to choose a list of character sets and/or scripts
9588 * Returns allocated list.
9590 char **
9591 choose_list_of_charsets(void)
9593 LIST_SEL_S *listhead, *ls, *p;
9594 char **ret = NULL;
9595 int cnt, i, got_one;
9596 const CHARSET *cs;
9597 SCRIPT *s;
9598 char *q, *t;
9599 long width, limit;
9600 char buf[1024], *folded;
9603 * Build a list of charsets to choose from.
9606 p = listhead = NULL;
9608 /* this width is determined by select_from_list_screen() */
9609 width = ps_global->ttyo->screen_cols - 4;
9611 /* first comes a list of scripts (sets of character sets) */
9612 for(s = utf8_script(NIL); s && s->name; s++){
9614 limit = sizeof(buf)-1;
9615 q = buf;
9616 memset(q, 0, limit+1);
9618 if(s->name)
9619 sstrncpy(&q, s->name, limit);
9621 if(s->description){
9622 sstrncpy(&q, " (", limit-(q-buf));
9623 sstrncpy(&q, s->description, limit-(q-buf));
9624 sstrncpy(&q, ")", limit-(q-buf));
9627 /* add the list of charsets that are in this script */
9628 got_one = 0;
9629 for(cs = utf8_charset(NIL);
9630 cs && cs->name && (q-buf) < limit; cs++){
9631 if(cs->script & s->script){
9633 * Filter out some un-useful members of the list.
9634 * UTF-7 and UTF-8 weren't actually in the list at the
9635 * time this was written. Just making sure.
9637 if(!strucmp(cs->name, "ISO-2022-JP-2")
9638 || !strucmp(cs->name, "UTF-7")
9639 || !strucmp(cs->name, "UTF-8"))
9640 continue;
9642 if(got_one)
9643 sstrncpy(&q, " ", limit-(q-buf));
9644 else{
9645 got_one = 1;
9646 sstrncpy(&q, " {", limit-(q-buf));
9649 sstrncpy(&q, cs->name, limit-(q-buf));
9653 if(got_one)
9654 sstrncpy(&q, "}", limit-(q-buf));
9656 /* fold this line so that it can all be seen on the screen */
9657 folded = fold(buf, width, width, "", " ", FLD_NONE);
9658 if(folded){
9659 t = folded;
9660 while(t && *t && (q = strindex(t, '\n')) != NULL){
9661 *q = '\0';
9663 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9664 memset(ls, 0, sizeof(*ls));
9665 if(t == folded)
9666 ls->item = cpystr(s->name);
9667 else
9668 ls->flags = SFL_NOSELECT;
9670 ls->display_item = cpystr(t);
9672 t = q+1;
9674 if(p){
9675 p->next = ls;
9676 p = p->next;
9678 else{
9679 /* add a heading */
9680 listhead = (LIST_SEL_S *) fs_get(sizeof(*ls));
9681 memset(listhead, 0, sizeof(*listhead));
9682 listhead->flags = SFL_NOSELECT;
9683 listhead->display_item =
9684 cpystr(_("Scripts representing groups of related character sets"));
9685 listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls));
9686 memset(listhead->next, 0, sizeof(*listhead));
9687 listhead->next->flags = SFL_NOSELECT;
9688 listhead->next->display_item =
9689 cpystr(repeat_char(width, '-'));
9691 listhead->next->next = ls;
9692 p = ls;
9696 fs_give((void **) &folded);
9700 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9701 memset(ls, 0, sizeof(*ls));
9702 ls->flags = SFL_NOSELECT;
9703 if(p){
9704 p->next = ls;
9705 p = p->next;
9707 else
9708 listhead = p = ls;
9710 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9711 memset(ls, 0, sizeof(*ls));
9712 ls->flags = SFL_NOSELECT;
9713 ls->display_item =
9714 cpystr(_("Individual character sets, may be mixed with scripts"));
9715 p->next = ls;
9716 p = p->next;
9718 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9719 memset(ls, 0, sizeof(*ls));
9720 ls->flags = SFL_NOSELECT;
9721 ls->display_item =
9722 cpystr(repeat_char(width, '-'));
9723 p->next = ls;
9724 p = p->next;
9726 /* then comes a list of individual character sets */
9727 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9728 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9729 memset(ls, 0, sizeof(*ls));
9730 ls->item = cpystr(cs->name);
9732 if(p){
9733 p->next = ls;
9734 p = p->next;
9736 else
9737 listhead = p = ls;
9740 if(!listhead)
9741 return(ret);
9743 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9744 Print something1 using something2.
9745 "character sets" is something1 */
9746 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9747 _("SELECT CHARACTER SETS"), _("character sets"),
9748 h_select_multcharsets_screen,
9749 _("HELP FOR SELECTING CHARACTER SETS"), NULL)){
9750 for(cnt = 0, p = listhead; p; p = p->next)
9751 if(p->selected)
9752 cnt++;
9754 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9755 memset(ret, 0, (cnt+1) * sizeof(*ret));
9756 for(i = 0, p = listhead; p; p = p->next)
9757 if(p->selected)
9758 ret[i++] = cpystr(p->item ? p->item : "");
9761 free_list_sel(&listhead);
9763 return(ret);
9766 /* Report quota summary resources in an IMAP server */
9768 void
9769 cmd_quota (struct pine *state)
9771 QUOTALIST *imapquota;
9772 NETMBX mb;
9773 STORE_S *store;
9774 SCROLL_S sargs;
9776 if(!state->mail_stream || !is_imap_stream(state->mail_stream)){
9777 q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders");
9778 return;
9781 if (state->mail_stream
9782 && !sp_dead_stream(state->mail_stream)
9783 && state->mail_stream->mailbox
9784 && *state->mail_stream->mailbox
9785 && mail_valid_net_parse(state->mail_stream->mailbox, &mb))
9786 imap_getquotaroot(state->mail_stream, mb.mailbox);
9788 if(!state->quota) /* failed ? */
9789 return; /* go back... */
9791 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
9792 q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space.");
9793 return;
9796 so_puts(store, "Quota Report for ");
9797 so_puts(store, state->mail_stream->original_mailbox);
9798 so_puts(store, "\n\n");
9800 for (imapquota = state->quota; imapquota; imapquota = imapquota->next){
9802 so_puts(store, _("Resource : "));
9803 so_puts(store, imapquota->name);
9804 so_writec('\n', store);
9806 so_puts(store, _("Usage : "));
9807 so_puts(store, long2string(imapquota->usage));
9808 if(!strucmp(imapquota->name,"STORAGE"))
9809 so_puts(store, " KiB ");
9810 if(!strucmp(imapquota->name,"MESSAGE")){
9811 so_puts(store, _(" message"));
9812 if(imapquota->usage != 1)
9813 so_puts(store, _("s ")); /* plural */
9814 else
9815 so_puts(store, _(" "));
9817 so_writec('(', store);
9818 so_puts(store, long2string(100*imapquota->usage/imapquota->limit));
9819 so_puts(store, "%)\n");
9821 so_puts(store, _("Limit : "));
9822 so_puts(store, long2string(imapquota->limit));
9823 if(!strucmp(imapquota->name,"STORAGE"))
9824 so_puts(store, " KiB\n\n");
9825 if(!strucmp(imapquota->name,"MESSAGE")){
9826 so_puts(store, _(" message"));
9827 if(imapquota->usage != 1)
9828 so_puts(store, _("s\n\n")); /* plural */
9829 else
9830 so_puts(store, _("\n\n"));
9834 memset(&sargs, 0, sizeof(SCROLL_S));
9835 sargs.text.text = so_text(store);
9836 sargs.text.src = CharStar;
9837 sargs.text.desc = _("Quota Resources Summary");
9838 sargs.bar.title = _("QUOTA SUMMARY");
9839 sargs.proc.tool = NULL;
9840 sargs.help.text = h_quota_command;
9841 sargs.help.title = NULL;
9842 sargs.keys.menu = NULL;
9843 setbitmap(sargs.keys.bitmap);
9845 scrolltool(&sargs);
9846 so_give(&store);
9848 if (state->quota)
9849 mail_free_quotalist(&(state->quota));
9852 /*----------------------------------------------------------------------
9853 Prompt the user for the type of sort he desires
9855 Args: state -- pine state pointer
9856 q1 -- Line to prompt on
9858 Returns 0 if it was cancelled, 1 otherwise.
9859 ----*/
9861 select_sort(struct pine *state, int ql, SortOrder *sort, int *rev)
9863 char prompt[200], tmp[3], *p;
9864 int s, i;
9865 int deefault = 'a', retval = 1;
9866 HelpType help;
9867 ESCKEY_S sorts[14];
9869 #ifdef _WINDOWS
9870 DLG_SORTPARAM sortsel;
9872 if (mswin_usedialog ()) {
9874 sortsel.reverse = mn_get_revsort (state->msgmap);
9875 sortsel.cursort = mn_get_sort (state->msgmap);
9876 /* assumption here that HelpType is char ** */
9877 sortsel.helptext = h_select_sort;
9878 sortsel.rval = 0;
9880 if ((retval = os_sortdialog (&sortsel))) {
9881 *sort = sortsel.cursort;
9882 *rev = sortsel.reverse;
9885 return (retval);
9887 #endif
9889 /*----- String together the prompt ------*/
9890 tmp[1] = '\0';
9891 if(F_ON(F_USE_FK,ps_global))
9892 strncpy(prompt, _("Choose type of sort : "), sizeof(prompt));
9893 else
9894 strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "),
9895 sizeof(prompt));
9897 for(i = 0; state->sort_types[i] != EndofList; i++) {
9898 sorts[i].rval = i;
9899 p = sorts[i].label = sort_name(state->sort_types[i]);
9900 while(*(p+1) && islower((unsigned char)*p))
9901 p++;
9903 sorts[i].ch = tolower((unsigned char)(tmp[0] = *p));
9904 sorts[i].name = cpystr(tmp);
9906 if(mn_get_sort(state->msgmap) == state->sort_types[i])
9907 deefault = sorts[i].rval;
9910 sorts[i].ch = 'r';
9911 sorts[i].rval = 'r';
9912 sorts[i].name = cpystr("R");
9913 if(F_ON(F_USE_FK,ps_global))
9914 sorts[i].label = N_("Reverse");
9915 else
9916 sorts[i].label = "";
9918 sorts[++i].ch = -1;
9919 help = h_select_sort;
9921 if((F_ON(F_USE_FK,ps_global)
9922 && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x',
9923 help,RB_NORM)) != 'x'))
9925 (F_OFF(F_USE_FK,ps_global)
9926 && ((s = radio_buttons(prompt,ql,sorts,deefault,'x',
9927 help,RB_NORM)) != 'x'))){
9928 state->mangled_body = 1; /* signal screen's changed */
9929 if(s == 'r')
9930 *rev = !mn_get_revsort(state->msgmap);
9931 else
9932 *sort = state->sort_types[s];
9934 if(F_ON(F_SHOW_SORT, ps_global))
9935 ps_global->mangled_header = 1;
9937 else{
9938 retval = 0;
9939 cmd_cancelled("Sort");
9942 while(--i >= 0)
9943 fs_give((void **)&sorts[i].name);
9945 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9946 return(retval);
9950 /*---------------------------------------------------------------------
9951 Build list of folders in the given context for user selection
9953 Args: c -- pointer to pointer to folder's context context
9954 f -- folder prefix to display
9955 sublist -- whether or not to use 'f's contents as prefix
9956 lister -- function used to do the actual display
9958 Returns: malloc'd string containing sequence, else NULL if
9959 no messages in msgmap with local "selected" flag.
9960 ----*/
9962 display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int))
9964 int rc;
9965 CONTEXT_S *tc;
9966 void (*redraw)(void) = ps_global->redrawer;
9968 push_titlebar_state();
9969 tc = *c;
9970 if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0)
9971 *c = tc;
9973 ClearScreen();
9974 pop_titlebar_state();
9975 redraw_titlebar();
9976 if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
9977 (*ps_global->redrawer)();
9979 if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
9980 return(1);
9982 return(0);
9987 * Allow user to choose a single item from a list of strings.
9989 * Args list -- Array of strings to choose from, NULL terminated.
9990 * displist -- Array of strings to display instead of displaying list.
9991 * Indices correspond to the list array. Display the displist
9992 * but return the item from list if displist non-NULL.
9993 * title -- For conf_scroll_screen
9994 * pdesc -- For conf_scroll_screen
9995 * help -- For conf_scroll_screen
9996 * htitle -- For conf_scroll_screen
9998 * Returns an allocated copy of the chosen item or NULL.
10000 char *
10001 choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help,
10002 char *htitle, char *cursor_location)
10004 LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL;
10005 char **t, **dl;
10006 char *ret = NULL, *choice = NULL;
10008 /* build the LIST_SEL_S list */
10009 p = listhead = NULL;
10010 for(t = list, dl = displist; *t; t++, dl++){
10011 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
10012 memset(ls, 0, sizeof(*ls));
10013 ls->item = cpystr(*t);
10014 if(displist)
10015 ls->display_item = cpystr(*dl);
10017 if(cursor_location && (cursor_location == (*t)))
10018 starting_val = ls;
10020 if(p){
10021 p->next = ls;
10022 p = p->next;
10024 else
10025 listhead = p = ls;
10028 if(!listhead)
10029 return(ret);
10031 if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc,
10032 help, htitle, starting_val))
10033 for(p = listhead; !choice && p; p = p->next)
10034 if(p->selected)
10035 choice = p->item;
10037 if(choice)
10038 ret = cpystr(choice);
10040 free_list_sel(&listhead);
10042 return(ret);
10046 void
10047 free_list_sel(LIST_SEL_S **lsel)
10049 if(lsel && *lsel){
10050 free_list_sel(&(*lsel)->next);
10051 if((*lsel)->item)
10052 fs_give((void **) &(*lsel)->item);
10054 if((*lsel)->display_item)
10055 fs_give((void **) &(*lsel)->display_item);
10057 fs_give((void **) lsel);
10063 * file_lister - call pico library's file lister
10066 file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags)
10068 PICO pbf;
10069 int rv;
10070 void (*redraw)(void) = ps_global->redrawer;
10072 standard_picobuf_setup(&pbf);
10073 push_titlebar_state();
10074 if(!newmail)
10075 pbf.newmail = NULL;
10077 /* BUG: what about help command and text? */
10078 pbf.pine_anchor = title;
10080 rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags);
10081 standard_picobuf_teardown(&pbf);
10082 fix_windsize(ps_global);
10083 init_signals(); /* has it's own signal stuff */
10085 /* Restore display's titlebar and body */
10086 pop_titlebar_state();
10087 redraw_titlebar();
10088 if((ps_global->redrawer = redraw) != NULL)
10089 (*ps_global->redrawer)();
10091 return(rv);
10095 /*----------------------------------------------------------------------
10096 Print current folder index
10098 ---*/
10100 print_index(struct pine *state, MSGNO_S *msgmap, int agg)
10102 long i;
10103 ICE_S *ice;
10104 char buf[MAX_SCREEN_COLS+1];
10106 for(i = 1L; i <= mn_get_total(msgmap); i++){
10107 if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
10108 continue;
10110 if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0))
10111 continue;
10113 ice = build_header_line(state, state->mail_stream, msgmap, i, NULL);
10115 if(ice){
10117 * I don't understand why we'd want to mark the current message
10118 * instead of printing out the first character of the status
10119 * so I'm taking it out and including the first character of the
10120 * line instead. Hubert 2006-02-09
10122 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
10123 return(0);
10126 if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i),
10127 print_char)
10128 || !gf_puts(NEWLINE, print_char))
10129 return(0);
10133 return(1);
10136 #ifdef _WINDOWS
10139 * windows callback to get/set header mode state
10142 header_mode_callback(set, args)
10143 int set;
10144 long args;
10146 return(ps_global->full_header);
10151 * windows callback to get/set zoom mode state
10154 zoom_mode_callback(set, args)
10155 int set;
10156 long args;
10158 return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0);
10163 * windows callback to get/set zoom mode state
10166 any_selected_callback(set, args)
10167 int set;
10168 long args;
10170 return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0);
10178 flag_callback(set, flags)
10179 int set;
10180 long flags;
10182 MESSAGECACHE *mc;
10183 int newflags = 0;
10184 long msgno;
10185 int permflag = 0;
10187 switch (set) {
10188 case 1: /* Important */
10189 permflag = ps_global->mail_stream->perm_flagged;
10190 break;
10192 case 2: /* New */
10193 permflag = ps_global->mail_stream->perm_seen;
10194 break;
10196 case 3: /* Answered */
10197 permflag = ps_global->mail_stream->perm_answered;
10198 break;
10200 case 4: /* Deleted */
10201 permflag = ps_global->mail_stream->perm_deleted;
10202 break;
10206 if(!(any_messages(ps_global->msgmap, NULL, "to Flag")
10207 && can_set_flag(ps_global, "flag", permflag)))
10208 return(0);
10210 if(sp_io_error_on_stream(ps_global->mail_stream)){
10211 sp_set_io_error_on_stream(ps_global->mail_stream, 0);
10212 pine_mail_check(ps_global->mail_stream); /* forces write */
10213 return(0);
10216 msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
10217 if(msgno > 0L && ps_global->mail_stream
10218 && msgno <= ps_global->mail_stream->nmsgs
10219 && (mc = mail_elt(ps_global->mail_stream, msgno))
10220 && mc->valid){
10222 * NOTE: code below is *VERY* sensitive to the order of
10223 * the messages defined in resource.h for flag handling.
10224 * Don't change it unless you know what you're doing.
10226 if(set){
10227 char *flagstr;
10228 long mflag;
10230 switch(set){
10231 case 1 : /* Important */
10232 flagstr = "\\FLAGGED";
10233 mflag = (mc->flagged) ? 0L : ST_SET;
10234 break;
10236 case 2 : /* New */
10237 flagstr = "\\SEEN";
10238 mflag = (mc->seen) ? 0L : ST_SET;
10239 break;
10241 case 3 : /* Answered */
10242 flagstr = "\\ANSWERED";
10243 mflag = (mc->answered) ? 0L : ST_SET;
10244 break;
10246 case 4 : /* Deleted */
10247 flagstr = "\\DELETED";
10248 mflag = (mc->deleted) ? 0L : ST_SET;
10249 break;
10251 default : /* bogus */
10252 return(0);
10255 mail_flag(ps_global->mail_stream, long2string(msgno),
10256 flagstr, mflag);
10258 if(ps_global->redrawer)
10259 (*ps_global->redrawer)();
10261 else{
10262 /* Important */
10263 if(mc->flagged)
10264 newflags |= 0x0001;
10266 /* New */
10267 if(!mc->seen)
10268 newflags |= 0x0002;
10270 /* Answered */
10271 if(mc->answered)
10272 newflags |= 0x0004;
10274 /* Deleted */
10275 if(mc->deleted)
10276 newflags |= 0x0008;
10280 return(newflags);
10286 * BUG: Should teach this about keywords
10288 MPopup *
10289 flag_submenu(mc)
10290 MESSAGECACHE *mc;
10292 static MPopup flag_submenu[] = {
10293 {tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}},
10294 {tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}},
10295 {tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}},
10296 {tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}},
10297 {tTail}
10300 /* Important */
10301 flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal;
10303 /* New */
10304 flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked;
10306 /* Answered */
10307 flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal;
10309 /* Deleted */
10310 flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal;
10312 return(flag_submenu);
10315 #endif /* _WINDOWS */