* Implement a different way to delete a password from the cache.
[alpine.git] / alpine / mailcmd.c
bloba144b2d22aa2b6b560357f09aa6da49df034616a
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_o_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_o_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_o_t pc;
4114 gf_i_t gc;
4116 gf_set_so_writec(&pc, store);
4117 gf_set_readc(&gc, srctext, (srctype == CharStar)
4118 ? strlen((char *)srctext)
4119 : 0L,
4120 srctype, 0);
4121 gf_filter_init();
4122 if((pipe_err = gf_pipe(gc, pc)) != NULL){
4123 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4124 /* TRANSLATORS: Problem saving to <filename>: <error text> */
4125 _("Problem saving to \"%s\": %s"),
4126 filename, pipe_err);
4127 r = -3;
4129 else
4130 r = 0;
4132 gf_clear_so_writec(store);
4133 if(so_give(&store)){
4134 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4135 _("Problem saving to \"%s\": %s"),
4136 filename, error_description(errno));
4137 r = -3;
4140 else{
4141 q_status_message2(SM_ORDER | SM_DING, 3, 4,
4142 _("Error opening file \"%s\" for export: %s"),
4143 full_filename, error_description(errno));
4144 r = -3;
4147 fini:
4148 switch(r){
4149 case 0:
4150 /* overloading full_filename */
4151 snprintf(full_filename, sizeof(full_filename), "%c%s",
4152 (prompt_msg && prompt_msg[0])
4153 ? (islower((unsigned char)prompt_msg[0])
4154 ? toupper((unsigned char)prompt_msg[0]) : prompt_msg[0])
4155 : 'T',
4156 (prompt_msg && prompt_msg[0]) ? prompt_msg+1 : "ext");
4157 full_filename[sizeof(full_filename)-1] = '\0';
4158 q_status_message3(SM_ORDER,0,2,"%s %s to \"%s\"",
4159 full_filename,
4160 rflags & GER_OVER
4161 ? "overwritten"
4162 : rflags & GER_APPEND ? "appended" : "exported",
4163 filename);
4164 break;
4166 case -1:
4167 cmd_cancelled("Export");
4168 break;
4170 case -2:
4171 q_status_message1(SM_ORDER, 0, 2,
4172 _("Can't export to file outside of %s"), VAR_OPER_DIR);
4173 break;
4176 ps->mangled_footer = 1;
4177 return(r);
4182 * Ask user what file to export to.
4184 * filename -- On input, this is the filename to start with. On exit,
4185 * this is the filename chosen. (but this isn't used)
4186 * deefault -- This is the default value if user hits return. The
4187 * prompt will have [deefault] added to it automatically.
4188 * full_filename -- This is the full filename on exit.
4189 * len -- Minimum length of _both_ filename and full_filename.
4190 * prompt_msg -- Message to insert in prompt.
4191 * lister_msg -- Message to insert in file_lister.
4192 * opts -- Key options.
4193 * There is a tangled relationship between the callers
4194 * and this routine as far as opts are concerned. Some
4195 * of the opts are handled here. In particular, r == 3,
4196 * r == 10, r == 11, and r == 13 are all handled here.
4197 * Don't use those values unless you want what happens
4198 * here. r == 12 and others are handled by the caller.
4199 * rflags -- Return flags
4200 * GER_OVER - overwrite of existing file
4201 * GER_APPEND - append of existing file
4202 * else file did not exist before
4204 * GER_ALLPARTS - AllParts toggle was turned on
4206 * qline -- Command line to prompt on.
4207 * flags -- Logically OR'd flags
4208 * GE_IS_EXPORT - The command was an Export command
4209 * so the prompt should include
4210 * EXPORT:.
4211 * GE_SEQ_SENSITIVE - The command that got us here is
4212 * sensitive to sequence number changes
4213 * caused by unsolicited expunges.
4214 * GE_NO_APPEND - We will not allow append to an
4215 * existing file, only removal of the
4216 * file if it exists.
4217 * GE_IS_IMPORT - We are selecting for reading.
4218 * No overwriting or checking for
4219 * existence at all. Don't use this
4220 * together with GE_NO_APPEND.
4221 * GE_ALLPARTS - Turn on AllParts toggle.
4222 * GE_BINARY - Turn on Binary toggle.
4224 * Returns: -1 cancelled
4225 * -2 prohibited by VAR_OPER_DIR
4226 * -3 other error, already reported here
4227 * 0 ok
4228 * 12 user chose 12 command from opts
4231 get_export_filename(struct pine *ps, char *filename, char *deefault,
4232 char *full_filename, size_t len, char *prompt_msg,
4233 char *lister_msg, ESCKEY_S *optsarg, int *rflags,
4234 int qline, int flags, HISTORY_S **history)
4236 char dir[MAXPATH+1], dir2[MAXPATH+1], orig_dir[MAXPATH+1];
4237 char precolon[MAXPATH+1], postcolon[MAXPATH+1];
4238 char filename2[MAXPATH+1], tmp[MAXPATH+1], *fn, *ill;
4239 int l, i, ku = -1, kp = -1, r, fatal, homedir = 0, was_abs_path=0, avail, ret = 0;
4240 int allparts = 0, binary = 0;
4241 char prompt_buf[400];
4242 char def[500];
4243 ESCKEY_S *opts = NULL;
4244 struct variable *vars = ps->vars;
4245 static HISTORY_S *dir_hist = NULL;
4246 static char *last;
4247 int pos, hist_len = 0;
4250 /* we will fake a history with the ps_global->VAR_HISTORY variable
4251 * We fake that we combine this variable into a history variable
4252 * by stacking VAR_HISTORY on top of dir_hist. We keep track of this
4253 * by looking at the variable pos.
4255 if(ps_global->VAR_HISTORY != NULL)
4256 for(hist_len = 0; ps_global->VAR_HISTORY[hist_len]
4257 && ps_global->VAR_HISTORY[hist_len][0]; hist_len++)
4260 pos = hist_len + items_in_hist(dir_hist);
4262 if(flags & GE_ALLPARTS || history || dir_hist){
4264 * Copy the opts and add one to the end of the list.
4266 for(i = 0; optsarg[i].ch != -1; i++)
4269 if(dir_hist || hist_len > 0)
4270 i += 2;
4272 if(history)
4273 i += dir_hist || hist_len > 0 ? 2 : 4;
4275 if(flags & GE_ALLPARTS)
4276 i++;
4278 if(flags & GE_BINARY)
4279 i++;
4281 opts = (ESCKEY_S *) fs_get((i+1) * sizeof(*opts));
4282 memset(opts, 0, (i+1) * sizeof(*opts));
4284 for(i = 0; optsarg[i].ch != -1; i++){
4285 opts[i].ch = optsarg[i].ch;
4286 opts[i].rval = optsarg[i].rval;
4287 opts[i].name = optsarg[i].name; /* no need to make a copy */
4288 opts[i].label = optsarg[i].label; /* " */
4291 if(flags & GE_ALLPARTS){
4292 allparts = i;
4293 opts[i].ch = ctrl('P');
4294 opts[i].rval = 13;
4295 opts[i].name = "^P";
4296 /* TRANSLATORS: Export all attachment parts */
4297 opts[i++].label = N_("AllParts");
4300 if(flags & GE_BINARY){
4301 binary = i;
4302 opts[i].ch = ctrl('R');
4303 opts[i].rval = 15;
4304 opts[i].name = "^R";
4305 opts[i++].label = N_("Binary");
4308 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4309 SIZEOF_20KBUF, filename);
4310 #ifndef _WINDOWS
4311 /* In the Windows operating system we always return the UTF8 encoded name */
4312 if(strcmp(tmp_20k_buf, filename)){
4313 opts[i].ch = ctrl('N');
4314 opts[i].rval = 40;
4315 opts[i].name = "^N";
4316 opts[i++].label = "Name UTF8";
4318 #else
4319 strncpy(filename, tmp_20k_buf, len);
4320 filename[len-1] = '\0';
4321 #endif /* _WINDOWS */
4323 if(dir_hist || hist_len > 0){
4324 opts[i].ch = ctrl('Y');
4325 opts[i].rval = 32;
4326 opts[i].name = "";
4327 kp = i;
4328 opts[i++].label = "";
4330 opts[i].ch = ctrl('V');
4331 opts[i].rval = 33;
4332 opts[i].name = "";
4333 opts[i++].label = "";
4336 if(history){
4337 opts[i].ch = KEY_UP;
4338 opts[i].rval = 30;
4339 opts[i].name = "";
4340 ku = i;
4341 opts[i++].label = "";
4343 opts[i].ch = KEY_DOWN;
4344 opts[i].rval = 31;
4345 opts[i].name = "";
4346 opts[i++].label = "";
4349 opts[i].ch = -1;
4351 if(history)
4352 init_hist(history, HISTSIZE);
4353 init_hist(&dir_hist, HISTSIZE); /* reset history to the end */
4355 else
4356 opts = optsarg;
4358 if(rflags)
4359 *rflags = GER_NONE;
4361 if(F_ON(F_USE_CURRENT_DIR, ps))
4362 dir[0] = '\0';
4363 else if(VAR_OPER_DIR){
4364 strncpy(dir, VAR_OPER_DIR, sizeof(dir));
4365 dir[sizeof(dir)-1] = '\0';
4367 #if defined(DOS) || defined(OS2)
4368 else if(VAR_FILE_DIR){
4369 strncpy(dir, VAR_FILE_DIR, sizeof(dir));
4370 dir[sizeof(dir)-1] = '\0';
4372 #endif
4373 else{
4374 dir[0] = '~';
4375 dir[1] = '\0';
4376 homedir=1;
4378 strncpy(orig_dir, dir, sizeof(orig_dir));
4379 orig_dir[sizeof(orig_dir)-1] = '\0';
4381 postcolon[0] = '\0';
4382 strncpy(precolon, dir, sizeof(precolon));
4383 precolon[sizeof(precolon)-1] = '\0';
4384 if(deefault){
4385 strncpy(def, deefault, sizeof(def)-1);
4386 def[sizeof(def)-1] = '\0';
4387 removing_leading_and_trailing_white_space(def);
4389 else
4390 def[0] = '\0';
4392 avail = MAX(20, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - MIN_OPT_ENT_WIDTH;
4394 /*---------- Prompt the user for the file name -------------*/
4395 while(1){
4396 int oeflags;
4397 char dirb[50], fileb[50];
4398 int l1, l2, l3, l4, l5, needed;
4399 char *p, p1[100], p2[100], *p3, p4[100], p5[100];
4401 snprintf(p1, sizeof(p1), "%sCopy ",
4402 (flags & GE_IS_EXPORT) ? "EXPORT: " :
4403 (flags & GE_IS_IMPORT) ? "IMPORT: " : "SAVE: ");
4404 p1[sizeof(p1)-1] = '\0';
4405 l1 = strlen(p1);
4407 strncpy(p2, prompt_msg ? prompt_msg : "", sizeof(p2)-1);
4408 p2[sizeof(p2)-1] = '\0';
4409 l2 = strlen(p2);
4411 if(rflags && *rflags & GER_ALLPARTS)
4412 p3 = " (and atts)";
4413 else
4414 p3 = "";
4416 l3 = strlen(p3);
4418 snprintf(p4, sizeof(p4), " %s file%s%s",
4419 (flags & GE_IS_IMPORT) ? "from" : "to",
4420 is_absolute_path(filename) ? "" : " in ",
4421 is_absolute_path(filename) ? "" :
4422 (!dir[0] ? "current directory"
4423 : (dir[0] == '~' && !dir[1]) ? "home directory"
4424 : short_str(dir,dirb,sizeof(dirb),30,FrontDots)));
4425 p4[sizeof(p4)-1] = '\0';
4426 l4 = strlen(p4);
4428 snprintf(p5, sizeof(p5), "%s%s%s: ",
4429 *def ? " [" : "",
4430 *def ? short_str(def,fileb,sizeof(fileb),40,EndDots) : "",
4431 *def ? "]" : "");
4432 p5[sizeof(p5)-1] = '\0';
4433 l5 = strlen(p5);
4435 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4436 snprintf(p4, sizeof(p4), " %s file%s%s",
4437 (flags & GE_IS_IMPORT) ? "from" : "to",
4438 is_absolute_path(filename) ? "" : " in ",
4439 is_absolute_path(filename) ? "" :
4440 (!dir[0] ? "current dir"
4441 : (dir[0] == '~' && !dir[1]) ? "home dir"
4442 : short_str(dir,dirb,sizeof(dirb),10,FrontDots)));
4443 p4[sizeof(p4)-1] = '\0';
4444 l4 = strlen(p4);
4447 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4448 snprintf(p5, sizeof(p5), "%s%s%s: ",
4449 *def ? " [" : "",
4450 *def ? short_str(def,fileb,sizeof(fileb),
4451 MAX(15,l5-5-needed),EndDots) : "",
4452 *def ? "]" : "");
4453 p5[sizeof(p5)-1] = '\0';
4454 l5 = strlen(p5);
4457 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l2 > 0){
4460 * 14 is about the shortest we can make this, because there are
4461 * fixed length strings of length 14 coming in here.
4463 p = short_str(prompt_msg, p2, sizeof(p2), MAX(14,l2-needed), FrontDots);
4464 if(p != p2){
4465 strncpy(p2, p, sizeof(p2)-1);
4466 p2[sizeof(p2)-1] = '\0';
4469 l2 = strlen(p2);
4472 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4473 strncpy(p1, "Copy ", sizeof(p1)-1);
4474 p1[sizeof(p1)-1] = '\0';
4475 l1 = strlen(p1);
4478 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4479 snprintf(p5, sizeof(p5), "%s%s%s: ",
4480 *def ? " [" : "",
4481 *def ? short_str(def,fileb, sizeof(fileb),
4482 MAX(10,l5-5-needed),EndDots) : "",
4483 *def ? "]" : "");
4484 p5[sizeof(p5)-1] = '\0';
4485 l5 = strlen(p5);
4488 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l3 > 0){
4489 if(needed <= l3 - strlen(" (+ atts)"))
4490 p3 = " (+ atts)";
4491 else if(needed <= l3 - strlen(" (atts)"))
4492 p3 = " (atts)";
4493 else if(needed <= l3 - strlen(" (+)"))
4494 p3 = " (+)";
4495 else if(needed <= l3 - strlen("+"))
4496 p3 = "+";
4497 else
4498 p3 = "";
4500 l3 = strlen(p3);
4503 snprintf(prompt_buf, sizeof(prompt_buf), "%s%s%s%s%s", p1, p2, p3, p4, p5);
4504 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4506 if(kp >= 0){
4507 if(items_in_hist(dir_hist) > 0 || hist_len > 0){ /* any directories */
4508 opts[kp].name = "^Y";
4509 opts[kp].label = "Prev Dir";
4510 opts[kp+1].name = "^V";
4511 opts[kp+1].label = "Next Dir";
4513 else{
4514 opts[kp].name = "";
4515 opts[kp].label = "";
4516 opts[kp+1].name = "";
4517 opts[kp+1].label = "";
4521 if(ku >= 0){
4522 if(items_in_hist(*history) > 0){
4523 opts[ku].name = HISTORY_UP_KEYNAME;
4524 opts[ku].label = HISTORY_KEYLABEL;
4525 opts[ku+1].name = HISTORY_DOWN_KEYNAME;
4526 opts[ku+1].label = HISTORY_KEYLABEL;
4528 else{
4529 opts[ku].name = "";
4530 opts[ku].label = "";
4531 opts[ku+1].name = "";
4532 opts[ku+1].label = "";
4536 oeflags = OE_APPEND_CURRENT |
4537 ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0);
4538 r = optionally_enter(filename, qline, 0, len, prompt_buf,
4539 opts, NO_HELP, &oeflags);
4541 dprint((2, "\n - export_filename = \"%s\", r = %d -\n", filename, r));
4542 /*--- Help ----*/
4543 if(r == 3){
4545 * Helps may not be right if you add another caller or change
4546 * things. Check it out.
4548 if(flags & GE_IS_IMPORT)
4549 helper(h_ge_import, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE);
4550 else if(flags & GE_ALLPARTS)
4551 helper(h_ge_allparts, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4552 else
4553 helper(h_ge_export, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4555 ps->mangled_screen = 1;
4557 continue;
4559 else if(r == 10 || r == 11){ /* Browser or File Completion */
4560 if(filename[0]=='~'){
4561 if(filename[1] == C_FILESEP && filename[2]!='\0'){
4562 precolon[0] = '~';
4563 precolon[1] = '\0';
4564 for(i=0; filename[i+2] != '\0' && i+2 < len-1; i++)
4565 filename[i] = filename[i+2];
4566 filename[i] = '\0';
4567 strncpy(dir, precolon, sizeof(dir)-1);
4568 dir[sizeof(dir)-1] = '\0';
4570 else if(filename[1]=='\0' ||
4571 (filename[1] == C_FILESEP && filename[2] == '\0')){
4572 precolon[0] = '~';
4573 precolon[1] = '\0';
4574 filename[0] = '\0';
4575 strncpy(dir, precolon, sizeof(dir)-1);
4576 dir[sizeof(dir)-1] = '\0';
4579 else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){
4580 if(homedir){
4581 precolon[0] = '~';
4582 precolon[1] = '\0';
4583 strncpy(dir, precolon, sizeof(dir)-1);
4584 dir[sizeof(dir)-1] = '\0';
4586 else{
4587 precolon[0] = '\0';
4588 dir[0] = '\0';
4591 l = MAXPATH;
4592 dir2[0] = '\0';
4593 strncpy(tmp, filename, sizeof(tmp)-1);
4594 tmp[sizeof(tmp)-1] = '\0';
4595 if(*tmp && is_absolute_path(tmp))
4596 fnexpand(tmp, sizeof(tmp));
4597 if(strncmp(tmp,postcolon, strlen(postcolon)))
4598 postcolon[0] = '\0';
4600 if(*tmp && (fn = last_cmpnt(tmp))){
4601 l -= fn - tmp;
4602 strncpy(filename2, fn, sizeof(filename2)-1);
4603 filename2[sizeof(filename2)-1] = '\0';
4604 if(is_absolute_path(tmp)){
4605 strncpy(dir2, tmp, MIN(fn - tmp, sizeof(dir2)-1));
4606 dir2[MIN(fn - tmp, sizeof(dir2)-1)] = '\0';
4607 #ifdef _WINDOWS
4608 if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){
4609 dir2[2] = '\\';
4610 dir2[3] = '\0';
4612 #endif
4613 strncpy(postcolon, dir2, sizeof(postcolon)-1);
4614 postcolon[sizeof(postcolon)-1] = '\0';
4615 precolon[0] = '\0';
4617 else{
4618 char *p = NULL;
4620 * Just building the directory name in dir2,
4621 * full_filename is overloaded.
4623 snprintf(full_filename, len, "%.*s", (int) MIN(fn-tmp,len-1), tmp);
4624 full_filename[len-1] = '\0';
4625 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4626 postcolon[sizeof(postcolon)-1] = '\0';
4627 build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH)
4628 : (dir[0] == '~' && !dir[1])
4629 ? ps->home_dir
4630 : dir,
4631 full_filename, sizeof(dir2));
4632 if(p)
4633 free(p);
4636 else{
4637 if(is_absolute_path(tmp)){
4638 strncpy(dir2, tmp, sizeof(dir2)-1);
4639 dir2[sizeof(dir2)-1] = '\0';
4640 #ifdef _WINDOWS
4641 if(dir2[2]=='\0' && dir2[1]==':'){
4642 dir2[2]='\\';
4643 dir2[3]='\0';
4644 strncpy(postcolon,dir2,sizeof(postcolon)-1);
4645 postcolon[sizeof(postcolon)-1] = '\0';
4647 #endif
4648 filename2[0] = '\0';
4649 precolon[0] = '\0';
4651 else{
4652 strncpy(filename2, tmp, sizeof(filename2)-1);
4653 filename2[sizeof(filename2)-1] = '\0';
4654 if(!dir[0]){
4655 if(getcwd(dir2, sizeof(dir2)) == NULL)
4656 alpine_panic(_("getcwd() call failed at get_export_filename"));
4658 else if(dir[0] == '~' && !dir[1]){
4659 strncpy(dir2, ps->home_dir, sizeof(dir2)-1);
4660 dir2[sizeof(dir2)-1] = '\0';
4662 else{
4663 strncpy(dir2, dir, sizeof(dir2)-1);
4664 dir2[sizeof(dir2)-1] = '\0';
4667 postcolon[0] = '\0';
4671 build_path(full_filename, dir2, filename2, len);
4672 if(!strcmp(full_filename, dir2))
4673 filename2[0] = '\0';
4674 if(full_filename[strlen(full_filename)-1] == C_FILESEP
4675 && isdir(full_filename,NULL,NULL)){
4676 if(strlen(full_filename) == 1)
4677 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4678 else if(filename2[0])
4679 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4680 postcolon[sizeof(postcolon)-1] = '\0';
4681 strncpy(dir2, full_filename, sizeof(dir2)-1);
4682 dir2[sizeof(dir2)-1] = '\0';
4683 filename2[0] = '\0';
4685 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4686 else if(full_filename[strlen(full_filename)-1] == C_FILESEP){
4687 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4688 postcolon[sizeof(postcolon)-1] = '\0';
4689 strncpy(dir2, full_filename, sizeof(dir2)-1);
4690 dir2[sizeof(dir2)-1] = '\0';
4691 filename2[0] = '\0';
4693 #endif
4694 if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1
4695 && strcmp(dir2+1, ":\\"))
4696 /* last condition to prevent stripping of '\\'
4697 in windows partition */
4698 dir2[strlen(dir2)-1] = '\0';
4700 if(r == 10){ /* File Browser */
4701 r = file_lister(lister_msg ? lister_msg : "EXPORT",
4702 dir2, sizeof(dir2), filename2, sizeof(filename2),
4703 TRUE,
4704 (flags & GE_IS_IMPORT) ? FB_READ : FB_SAVE);
4705 #ifdef _WINDOWS
4706 /* Windows has a special "feature" in which entering the file browser will
4707 change the working directory if the directory is changed at all (even
4708 clicking "Cancel" will change the working directory).
4710 if(F_ON(F_USE_CURRENT_DIR, ps))
4711 (void)getcwd(dir2,sizeof(dir2));
4712 #endif
4713 if(isdir(dir2,NULL,NULL)){
4714 strncpy(precolon, dir2, sizeof(precolon)-1);
4715 precolon[sizeof(precolon)-1] = '\0';
4717 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4718 postcolon[sizeof(postcolon)-1] = '\0';
4719 if(r == 1){
4720 build_path(full_filename, dir2, filename2, len);
4721 if(isdir(full_filename, NULL, NULL)){
4722 strncpy(dir, full_filename, sizeof(dir)-1);
4723 dir[sizeof(dir)-1] = '\0';
4724 filename[0] = '\0';
4726 else{
4727 fn = last_cmpnt(full_filename);
4728 strncpy(dir, full_filename,
4729 MIN(fn - full_filename, sizeof(dir)-1));
4730 dir[MIN(fn - full_filename, sizeof(dir)-1)] = '\0';
4731 if(fn - full_filename > 1)
4732 dir[fn - full_filename - 1] = '\0';
4735 if(!strcmp(dir, ps->home_dir)){
4736 dir[0] = '~';
4737 dir[1] = '\0';
4740 strncpy(filename, fn, len-1);
4741 filename[len-1] = '\0';
4744 else{ /* File Completion */
4745 if(!pico_fncomplete(dir2, filename2, sizeof(filename2)))
4746 Writechar(BELL, 0);
4747 strncat(postcolon, filename2,
4748 sizeof(postcolon)-1-strlen(postcolon));
4749 postcolon[sizeof(postcolon)-1] = '\0';
4751 was_abs_path = is_absolute_path(filename);
4753 if(!strcmp(dir, ps->home_dir)){
4754 dir[0] = '~';
4755 dir[1] = '\0';
4758 strncpy(filename, postcolon, len-1);
4759 filename[len-1] = '\0';
4760 strncpy(dir, precolon, sizeof(dir)-1);
4761 dir[sizeof(dir)-1] = '\0';
4763 if(filename[0] == '~' && !filename[1]){
4764 dir[0] = '~';
4765 dir[1] = '\0';
4766 filename[0] = '\0';
4769 continue;
4771 else if(r == 12){ /* Download, caller handles it */
4772 ret = r;
4773 goto done;
4775 else if(r == 13){ /* toggle AllParts bit */
4776 if(rflags){
4777 if(*rflags & GER_ALLPARTS){
4778 *rflags &= ~GER_ALLPARTS;
4779 opts[allparts].label = N_("AllParts");
4781 else{
4782 *rflags |= GER_ALLPARTS;
4783 /* opposite of All Parts, No All Parts */
4784 opts[allparts].label = N_("NoAllParts");
4788 continue;
4790 #if 0
4791 else if(r == 14){ /* List file names matching partial? */
4792 continue;
4794 #endif
4795 else if(r == 15){ /* toggle Binary bit */
4796 if(rflags){
4797 if(*rflags & GER_BINARY){
4798 *rflags &= ~GER_BINARY;
4799 opts[binary].label = N_("Binary");
4801 else{
4802 *rflags |= GER_BINARY;
4803 opts[binary].label = N_("No Binary");
4807 continue;
4809 else if(r == 1){ /* Cancel */
4810 ret = -1;
4811 goto done;
4813 else if(r == 4){
4814 continue;
4816 else if(r >= 30 && r <= 33){
4817 char *p = NULL;
4819 if(r == 30 || r == 31){
4820 if(history){
4821 if(r == 30)
4822 p = get_prev_hist(*history, filename, 0, NULL);
4823 else if (r == 31)
4824 p = get_next_hist(*history, filename, 0, NULL);
4828 if(r == 32 || r == 33){
4829 int nitems = items_in_hist(dir_hist);
4830 if(dir_hist || hist_len > 0){
4831 if(r == 32){
4832 if(pos > 0)
4833 p = hist_in_pos(--pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4834 else p = last;
4836 else if (r == 33){
4837 if(pos < hist_len + nitems)
4838 p = hist_in_pos(++pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4840 if(p == NULL || *p == '\0')
4841 p = orig_dir;
4844 last = p; /* save it! */
4846 if(p != NULL && *p != '\0'){
4847 if(r == 30 || r == 31){
4848 if((fn = last_cmpnt(p)) != NULL){
4849 strncpy(dir, p, MIN(fn - p, sizeof(dir)-1));
4850 dir[MIN(fn - p, sizeof(dir)-1)] = '\0';
4851 if(fn - p > 1)
4852 dir[fn - p - 1] = '\0';
4853 strncpy(filename, fn, len-1);
4854 filename[len-1] = '\0';
4856 } else { /* r == 32 || r == 33 */
4857 strncpy(dir, p, sizeof(dir)-1);
4858 dir[sizeof(dir)-1] = '\0';
4861 if(!strcmp(dir, ps->home_dir)){
4862 dir[0] = '~';
4863 dir[1] = '\0';
4866 else
4867 Writechar(BELL, 0);
4868 continue;
4870 #ifndef _WINDOWS
4871 else if(r == 40){
4872 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4873 SIZEOF_20KBUF, filename);
4874 strncpy(filename, tmp_20k_buf, len);
4875 filename[len-1] = '\0';
4876 continue;
4878 #endif /* _WINDOWS */
4879 else if(r != 0){
4880 Writechar(BELL, 0);
4881 continue;
4884 removing_leading_and_trailing_white_space(filename);
4886 if(!*filename){
4887 if(!*def){ /* Cancel */
4888 ret = -1;
4889 goto done;
4892 strncpy(filename, def, len-1);
4893 filename[len-1] = '\0';
4896 #if defined(DOS) || defined(OS2)
4897 if(is_absolute_path(filename)){
4898 fixpath(filename, len);
4900 #else
4901 if(filename[0] == '~'){
4902 if(fnexpand(filename, len) == NULL){
4903 char *p = strindex(filename, '/');
4904 if(p != NULL)
4905 *p = '\0';
4906 q_status_message1(SM_ORDER | SM_DING, 3, 3,
4907 _("Error expanding file name: \"%s\" unknown user"),
4908 filename);
4909 continue;
4912 #endif
4914 if(is_absolute_path(filename)){
4915 strncpy(full_filename, filename, len-1);
4916 full_filename[len-1] = '\0';
4918 else{
4919 if(!dir[0])
4920 build_path(full_filename, (char *)getcwd(dir,sizeof(dir)),
4921 filename, len);
4922 else if(dir[0] == '~' && !dir[1])
4923 build_path(full_filename, ps->home_dir, filename, len);
4924 else
4925 build_path(full_filename, dir, filename, len);
4928 if((ill = filter_filename(full_filename, &fatal,
4929 ps_global->restricted || ps_global->VAR_OPER_DIR)) != NULL){
4930 if(fatal){
4931 q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill);
4932 continue;
4934 else{
4935 /* BUG: we should beep when the key's pressed rather than bitch later */
4936 /* Warn and ask for confirmation. */
4937 snprintf(prompt_buf, sizeof(prompt_buf), "File name contains a '%s'. %s anyway",
4938 ill, (flags & GE_IS_EXPORT) ? "Export" : "Save");
4939 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4940 if(want_to(prompt_buf, 'n', 0, NO_HELP,
4941 ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y')
4942 continue;
4946 break; /* Must have got an OK file name */
4949 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){
4950 ret = -2;
4951 goto done;
4954 if(!can_access(full_filename, ACCESS_EXISTS)){
4955 int rbflags;
4956 static ESCKEY_S access_opts[] = {
4957 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4958 a file or append to the end of the file */
4959 {'o', 'o', "O", N_("Overwrite")},
4960 {'a', 'a', "A", N_("Append")},
4961 {-1, 0, NULL, NULL}};
4963 rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0);
4965 if(flags & GE_NO_APPEND){
4966 r = strlen(filename);
4967 snprintf(prompt_buf, sizeof(prompt_buf),
4968 /* TRANSLATORS: asking user whether to overwrite a file or not,
4969 File <filename> already exists. Overwrite it ? */
4970 _("File \"%s%s\" already exists. Overwrite it "),
4971 (r > 20) ? "..." : "",
4972 filename + ((r > 20) ? r - 20 : 0));
4973 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4974 if(want_to(prompt_buf, 'n', 'x', NO_HELP, rbflags) == 'y'){
4975 if(rflags)
4976 *rflags |= GER_OVER;
4978 if(our_unlink(full_filename) < 0){
4979 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4980 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4981 _("Cannot remove old %s: %s"),
4982 full_filename, error_description(errno));
4985 else{
4986 ret = -1;
4987 goto done;
4990 else if(!(flags & GE_IS_IMPORT)){
4991 r = strlen(filename);
4992 snprintf(prompt_buf, sizeof(prompt_buf),
4993 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4994 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4995 (r > 20) ? "..." : "",
4996 filename + ((r > 20) ? r - 20 : 0));
4997 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4998 switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global),
4999 access_opts, 'a', 'x', NO_HELP, rbflags)){
5000 case 'o' :
5001 if(rflags)
5002 *rflags |= GER_OVER;
5004 if(our_truncate(full_filename, (off_t)0) < 0)
5005 /* trouble truncating, but we'll give it a try anyway */
5006 q_status_message2(SM_ORDER | SM_DING, 3, 5,
5007 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
5008 _("Warning: Cannot truncate old %s: %s"),
5009 full_filename, error_description(errno));
5010 break;
5012 case 'a' :
5013 if(rflags)
5014 *rflags |= GER_APPEND;
5016 break;
5018 case 'x' :
5019 default :
5020 ret = -1;
5021 goto done;
5026 done:
5027 if(history && ret == 0){
5028 save_hist(*history, full_filename, 0, NULL);
5029 strncpy(tmp, full_filename, MAXPATH);
5030 tmp[MAXPATH] = '\0';
5031 if((fn = strrchr(tmp, C_FILESEP)) != NULL)
5032 *fn = '\0';
5033 else
5034 tmp[0] = '\0';
5035 if(tmp[0])
5036 save_hist(dir_hist, tmp, 0, NULL);
5039 if(opts && opts != optsarg)
5040 fs_give((void **) &opts);
5042 return(ret);
5046 /*----------------------------------------------------------------------
5047 parse the config'd upload/download command
5049 Args: cmd -- buffer to return command fit for shellin'
5050 prefix --
5051 cfg_str --
5052 fname -- file name to build into the command
5054 Returns: pointer to cmd_str buffer or NULL on real bad error
5056 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
5057 cfg_str is written to standard out right before a successful
5058 return of this function. The call immediately following this
5059 function darn well better be the shell exec...
5060 ----*/
5061 char *
5062 build_updown_cmd(char *cmd, size_t cmdlen, char *prefix, char *cfg_str, char *fname)
5064 char *p;
5065 int fname_found = 0;
5067 if(prefix && *prefix){
5068 /* loop thru replacing all occurrences of _FILE_ */
5069 p = strncpy(cmd, prefix, cmdlen);
5070 cmd[cmdlen-1] = '\0';
5071 while((p = strstr(p, "_FILE_")))
5072 rplstr(p, cmdlen-(p-cmd), 6, fname);
5074 fputs(cmd, stdout);
5077 /* loop thru replacing all occurrences of _FILE_ */
5078 p = strncpy(cmd, cfg_str, cmdlen);
5079 cmd[cmdlen-1] = '\0';
5080 while((p = strstr(p, "_FILE_"))){
5081 rplstr(p, cmdlen-(p-cmd), 6, fname);
5082 fname_found = 1;
5085 if(!fname_found)
5086 snprintf(cmd+strlen(cmd), cmdlen-strlen(cmd), " %s", fname);
5088 cmd[cmdlen-1] = '\0';
5090 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
5091 cmd ? cmd : "?"));
5092 return(cmd);
5096 /*----------------------------------------------------------------------
5097 Write a berzerk format message delimiter using the given putc function
5099 Args: e -- envelope of message to write
5100 pc -- function to use
5102 Returns: TRUE if we could write it, FALSE if there was a problem
5104 NOTE: follows delimiter with OS-dependent newline
5105 ----*/
5107 bezerk_delimiter(ENVELOPE *env, MESSAGECACHE *mc, gf_o_t pc, int leading_newline)
5109 MESSAGECACHE telt;
5110 time_t when;
5111 char *p;
5113 /* write "[\n]From mailbox[@host] " */
5114 if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1)
5115 && gf_puts("From ", pc)
5116 && gf_puts((env && env->from) ? env->from->mailbox
5117 : "the-concourse-on-high", pc)
5118 && gf_puts((env && env->from && env->from->host) ? "@" : "", pc)
5119 && gf_puts((env && env->from && env->from->host) ? env->from->host
5120 : "", pc)
5121 && (*pc)(' ')))
5122 return(0);
5124 if(mc && mc->valid)
5125 when = mail_longdate(mc);
5126 else if(env && env->date && env->date[0]
5127 && mail_parse_date(&telt,env->date))
5128 when = mail_longdate(&telt);
5129 else
5130 when = time(0);
5132 p = ctime(&when);
5134 while(p && *p && *p != '\n') /* write date */
5135 if(!(*pc)(*p++))
5136 return(0);
5138 if(!gf_puts(NEWLINE, pc)) /* write terminating newline */
5139 return(0);
5141 return(1);
5145 /*----------------------------------------------------------------------
5146 Execute command to jump to a given message number
5148 Args: qline -- Line to ask question on
5150 Result: returns true if the use selected a new message, false otherwise
5152 ----*/
5153 long
5154 jump_to(MSGNO_S *msgmap, int qline, UCS first_num, SCROLL_S *sparms, CmdWhere in_index)
5156 char jump_num_string[80], *j, prompt[70];
5157 HelpType help;
5158 int rc;
5159 static ESCKEY_S jump_to_key[] = { {0, 0, NULL, NULL},
5160 /* TRANSLATORS: go to First Message */
5161 {ctrl('Y'), 10, "^Y", N_("First Msg")},
5162 {ctrl('V'), 11, "^V", N_("Last Msg")},
5163 {-1, 0, NULL, NULL} };
5165 dprint((4, "\n - jump_to -\n"));
5167 #ifdef DEBUG
5168 if(sparms && sparms->jump_is_debug)
5169 return(get_level(qline, first_num, sparms));
5170 #endif
5172 if(!any_messages(msgmap, NULL, "to Jump to"))
5173 return(0L);
5175 if(first_num && first_num < 0x80 && isdigit((unsigned char) first_num)){
5176 jump_num_string[0] = first_num;
5177 jump_num_string[1] = '\0';
5179 else
5180 jump_num_string[0] = '\0';
5182 if(mn_total_cur(msgmap) > 1L){
5183 snprintf(prompt, sizeof(prompt), "Unselect %s msgs in favor of number to be entered",
5184 comatose(mn_total_cur(msgmap)));
5185 prompt[sizeof(prompt)-1] = '\0';
5186 if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n')
5187 return(0L);
5190 snprintf(prompt, sizeof(prompt), "%s number to jump to : ", in_index == ThrdIndx
5191 ? "Thread"
5192 : "Message");
5193 prompt[sizeof(prompt)-1] = '\0';
5195 help = NO_HELP;
5196 while(1){
5197 int flags = OE_APPEND_CURRENT;
5199 rc = optionally_enter(jump_num_string, qline, 0,
5200 sizeof(jump_num_string), prompt,
5201 jump_to_key, help, &flags);
5202 if(rc == 3){
5203 help = help == NO_HELP
5204 ? (in_index == ThrdIndx ? h_oe_jump_thd : h_oe_jump)
5205 : NO_HELP;
5206 continue;
5208 else if(rc == 10 || rc == 11){
5209 char warning[100];
5210 long closest;
5212 closest = closest_jump_target(rc == 10 ? 1L
5213 : ((in_index == ThrdIndx)
5214 ? msgmap->max_thrdno
5215 : mn_get_total(msgmap)),
5216 ps_global->mail_stream,
5217 msgmap, 0,
5218 in_index, warning, sizeof(warning));
5219 /* ignore warning */
5220 return(closest);
5224 * If we take out the *jump_num_string nonempty test in this if
5225 * then the closest_jump_target routine will offer a jump to the
5226 * last message. However, it is slow because you have to wait for
5227 * the status message and it is annoying for people who hit J command
5228 * by mistake and just want to hit return to do nothing, like has
5229 * always worked. So the test is there for now. Hubert 2002-08-19
5231 * Jumping to first/last message is now possible through ^Y/^V
5232 * commands above. jpf 2002-08-21
5233 * (and through "end" hubert 2006-07-07)
5235 if(rc == 0 && *jump_num_string != '\0'){
5236 removing_leading_and_trailing_white_space(jump_num_string);
5237 for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++)
5240 if(*j != '\0'){
5241 if(!strucmp("end", j))
5242 return((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap));
5244 q_status_message(SM_ORDER | SM_DING, 2, 2,
5245 _("Invalid number entered. Use only digits 0-9"));
5246 jump_num_string[0] = '\0';
5248 else{
5249 char warning[100];
5250 long closest, jump_num;
5252 if(*jump_num_string)
5253 jump_num = atol(jump_num_string);
5254 else
5255 jump_num = -1L;
5257 warning[0] = '\0';
5258 closest = closest_jump_target(jump_num, ps_global->mail_stream,
5259 msgmap,
5260 *jump_num_string ? 0 : 1,
5261 in_index, warning, sizeof(warning));
5262 if(warning[0])
5263 q_status_message(SM_ORDER | SM_DING, 2, 2, warning);
5265 if(closest == jump_num)
5266 return(jump_num);
5268 if(closest == 0L)
5269 jump_num_string[0] = '\0';
5270 else
5271 strncpy(jump_num_string, long2string(closest),
5272 sizeof(jump_num_string));
5275 continue;
5278 if(rc != 4)
5279 break;
5282 return(0L);
5287 * cmd_delete_action - handle msgno advance and such after single message deletion
5289 char *
5290 cmd_delete_action(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
5292 int opts;
5293 long msgno;
5294 char *rv = NULL;
5296 msgno = mn_get_cur(msgmap);
5297 advance_cur_after_delete(state, state->mail_stream, msgmap, in_index);
5299 if(IS_NEWS(state->mail_stream)
5300 || ((state->context_current->use & CNTXT_INCMNG)
5301 && context_isambig(state->cur_folder))){
5303 opts = (NSF_TRUST_FLAGS | NSF_SKIP_CHID);
5304 if(in_index == View)
5305 opts &= ~NSF_SKIP_CHID;
5307 (void)next_sorted_flagged(F_UNDEL|F_UNSEEN, state->mail_stream, msgno, &opts);
5308 if(!(opts & NSF_FLAG_MATCH)){
5309 char nextfolder[MAXPATH];
5311 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
5312 nextfolder[sizeof(nextfolder)-1] = '\0';
5313 rv = next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
5314 state->context_current, NULL, NULL)
5315 ? ". Press TAB for next folder."
5316 : ". No more folders to TAB to.";
5320 return(rv);
5325 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5327 char *
5328 cmd_delete_index(struct pine *state, MSGNO_S *msgmap)
5330 return(cmd_delete_action(state, msgmap,MsgIndx));
5334 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5336 char *
5337 cmd_delete_view(struct pine *state, MSGNO_S *msgmap)
5339 return(cmd_delete_action(state, msgmap, View));
5343 void
5344 advance_cur_after_delete(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, CmdWhere in_index)
5346 long new_msgno, msgno;
5347 int opts;
5349 new_msgno = msgno = mn_get_cur(msgmap);
5350 opts = NSF_TRUST_FLAGS;
5352 if(F_ON(F_DEL_SKIPS_DEL, state)){
5354 if(THREADING() && sp_viewing_a_thread(stream))
5355 opts |= NSF_SKIP_CHID;
5357 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5359 else{
5360 mn_inc_cur(stream, msgmap,
5361 (in_index == View && THREADING()
5362 && sp_viewing_a_thread(stream))
5363 ? MH_THISTHD
5364 : (in_index == View)
5365 ? MH_ANYTHD : MH_NONE);
5366 new_msgno = mn_get_cur(msgmap);
5367 if(new_msgno != msgno)
5368 opts |= NSF_FLAG_MATCH;
5372 * Viewing_a_thread is the complicated case because we want to ignore
5373 * other threads at first and then look in other threads if we have to.
5374 * By ignoring other threads we also ignore collapsed partial threads
5375 * in our own thread.
5377 if(THREADING() && sp_viewing_a_thread(stream) && !(opts & NSF_FLAG_MATCH)){
5378 long rawno, orig_thrdno;
5379 PINETHRD_S *thrd, *topthrd = NULL;
5381 rawno = mn_m2raw(msgmap, msgno);
5382 thrd = fetch_thread(stream, rawno);
5383 if(thrd && thrd->top)
5384 topthrd = fetch_thread(stream, thrd->top);
5386 orig_thrdno = topthrd ? topthrd->thrdno : -1L;
5388 opts = NSF_TRUST_FLAGS;
5389 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5392 * If we got a match, new_msgno may be a message in
5393 * a different thread from the one we are viewing, or it could be
5394 * in a collapsed part of this thread.
5396 if(opts & NSF_FLAG_MATCH){
5397 int ret;
5398 char pmt[128];
5400 topthrd = NULL;
5401 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
5402 if(thrd && thrd->top)
5403 topthrd = fetch_thread(stream, thrd->top);
5406 * If this match is in the same thread we're already in
5407 * then we're done, else we have to ask the user and maybe
5408 * switch threads.
5410 if(!(orig_thrdno > 0L && topthrd
5411 && topthrd->thrdno == orig_thrdno)){
5413 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
5414 if(in_index == View)
5415 snprintf(pmt, sizeof(pmt),
5416 "View message in thread number %.10s",
5417 topthrd ? comatose(topthrd->thrdno) : "?");
5418 else
5419 snprintf(pmt, sizeof(pmt), "View thread number %.10s",
5420 topthrd ? comatose(topthrd->thrdno) : "?");
5422 ret = want_to(pmt, 'y', 'x', NO_HELP, WT_NORM);
5424 else
5425 ret = 'y';
5427 if(ret == 'y'){
5428 unview_thread(state, stream, msgmap);
5429 mn_set_cur(msgmap, new_msgno);
5430 if(THRD_AUTO_VIEW()
5431 && (count_lflags_in_thread(stream, topthrd, msgmap,
5432 MN_NONE) == 1)
5433 && view_thread(state, stream, msgmap, 1)){
5434 if(current_index_state)
5435 msgmap->top_after_thrd = current_index_state->msg_at_top;
5437 state->view_skipped_index = 1;
5438 state->next_screen = mail_view_screen;
5440 else{
5441 view_thread(state, stream, msgmap, 1);
5442 if(current_index_state)
5443 msgmap->top_after_thrd = current_index_state->msg_at_top;
5445 state->next_screen = SCREEN_FUN_NULL;
5448 else
5449 new_msgno = msgno; /* stick with original */
5454 mn_set_cur(msgmap, new_msgno);
5455 if(in_index != View)
5456 adjust_cur_to_visible(stream, msgmap);
5460 #ifdef DEBUG
5461 long
5462 get_level(int qline, UCS first_num, SCROLL_S *sparms)
5464 char debug_num_string[80], *j, prompt[70];
5465 HelpType help;
5466 int rc;
5467 long debug_num;
5469 if(first_num && first_num < 0x80 && isdigit((unsigned char)first_num)){
5470 debug_num_string[0] = first_num;
5471 debug_num_string[1] = '\0';
5472 debug_num = atol(debug_num_string);
5473 *(int *)(sparms->proc.data.p) = debug_num;
5474 q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s",
5475 comatose(debug_num));
5476 return(1L);
5478 else
5479 debug_num_string[0] = '\0';
5481 snprintf(prompt, sizeof(prompt), "Show debug <= this level (0-%d) : ", MAX(debug, 9));
5482 prompt[sizeof(prompt)-1] = '\0';
5484 help = NO_HELP;
5485 while(1){
5486 int flags = OE_APPEND_CURRENT;
5488 rc = optionally_enter(debug_num_string, qline, 0,
5489 sizeof(debug_num_string), prompt,
5490 NULL, help, &flags);
5491 if(rc == 3){
5492 help = help == NO_HELP ? h_oe_debuglevel : NO_HELP;
5493 continue;
5496 if(rc == 0){
5497 removing_leading_and_trailing_white_space(debug_num_string);
5498 for(j=debug_num_string; isdigit((unsigned char)*j); j++)
5501 if(*j != '\0'){
5502 q_status_message(SM_ORDER | SM_DING, 2, 2,
5503 _("Invalid number entered. Use only digits 0-9"));
5504 debug_num_string[0] = '\0';
5506 else{
5507 debug_num = atol(debug_num_string);
5508 if(debug_num < 0)
5509 q_status_message(SM_ORDER | SM_DING, 2, 2,
5510 _("Number should be >= 0"));
5511 else if(debug_num > MAX(debug,9))
5512 q_status_message1(SM_ORDER | SM_DING, 2, 2,
5513 _("Maximum is %s"), comatose(MAX(debug,9)));
5514 else{
5515 *(int *)(sparms->proc.data.p) = debug_num;
5516 q_status_message1(SM_ORDER, 0, 3,
5517 "Show debug <= level %s",
5518 comatose(debug_num));
5519 return(1L);
5523 continue;
5526 if(rc != 4)
5527 break;
5530 return(0L);
5532 #endif /* DEBUG */
5536 * Returns the message number closest to target that isn't hidden.
5537 * Make warning at least 100 chars.
5538 * A return of 0 means there is no message to jump to.
5540 long
5541 closest_jump_target(long int target, MAILSTREAM *stream, MSGNO_S *msgmap, int no_target, CmdWhere in_index, char *warning, size_t warninglen)
5543 long i, start, closest = 0L;
5544 char buf[80];
5545 long maxnum;
5547 warning[0] = '\0';
5548 maxnum = (in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap);
5550 if(no_target){
5551 target = maxnum;
5552 start = 1L;
5553 snprintf(warning, warninglen, "No %s number entered, jump to end? ",
5554 (in_index == ThrdIndx) ? "thread" : "message");
5555 warning[warninglen-1] = '\0';
5557 else if(target < 1L)
5558 start = 1L - target;
5559 else if(target > maxnum)
5560 start = target - maxnum;
5561 else
5562 start = 1L;
5564 if(target > 0L && target <= maxnum)
5565 if(in_index == ThrdIndx
5566 || !msgline_hidden(stream, msgmap, target, 0))
5567 return(target);
5569 for(i = start; target+i <= maxnum || target-i > 0L; i++){
5571 if(target+i > 0L && target+i <= maxnum &&
5572 (in_index == ThrdIndx
5573 || !msgline_hidden(stream, msgmap, target+i, 0))){
5574 closest = target+i;
5575 break;
5578 if(target-i > 0L && target-i <= maxnum &&
5579 (in_index == ThrdIndx
5580 || !msgline_hidden(stream, msgmap, target-i, 0))){
5581 closest = target-i;
5582 break;
5586 strncpy(buf, long2string(closest), sizeof(buf));
5587 buf[sizeof(buf)-1] = '\0';
5589 if(closest == 0L)
5590 strncpy(warning, "Nothing to jump to", warninglen);
5591 else if(target < 1L)
5592 snprintf(warning, warninglen, "%s number (%s) must be at least %s",
5593 (in_index == ThrdIndx) ? "Thread" : "Message",
5594 long2string(target), buf);
5595 else if(target > maxnum)
5596 snprintf(warning, warninglen, "%s number (%s) may be no more than %s",
5597 (in_index == ThrdIndx) ? "Thread" : "Message",
5598 long2string(target), buf);
5599 else if(!no_target)
5600 snprintf(warning, warninglen,
5601 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5602 long2string(target), buf);
5604 warning[warninglen-1] = '\0';
5606 return(closest);
5610 /*----------------------------------------------------------------------
5611 Prompt for folder name to open, expand the name and return it
5613 Args: qline -- Screen line to prompt on
5614 allow_list -- if 1, allow ^T to bring up collection lister
5616 Result: returns the folder name or NULL
5617 pine structure mangled_footer flag is set
5618 may call the collection lister in which case mangled screen will be set
5620 This prompts the user for the folder to open, possibly calling up
5621 the collection lister if the user types ^T.
5622 ----------------------------------------------------------------------*/
5623 char *
5624 broach_folder(int qline, int allow_list, int *notrealinbox, CONTEXT_S **context)
5626 HelpType help;
5627 static char newfolder[MAILTMPLEN];
5628 char expanded[MAXPATH+1],
5629 prompt[MAX_SCREEN_COLS+1],
5630 *last_folder, *p;
5631 unsigned char *f1, *f2, *f3;
5632 static HISTORY_S *history = NULL;
5633 CONTEXT_S *tc, *tc2;
5634 ESCKEY_S ekey[9];
5635 int rc, r, ku = -1, n, flags, last_rc = 0, inbox, done = 0;
5638 * the idea is to provide a clue for the context the file name
5639 * will be saved in (if a non-imap names is typed), and to
5640 * only show the previous if it was also in the same context
5642 help = NO_HELP;
5643 *expanded = '\0';
5644 *newfolder = '\0';
5645 last_folder = NULL;
5646 if(notrealinbox)
5647 (*notrealinbox) = 1;
5649 init_hist(&history, HISTSIZE);
5651 tc = broach_get_folder(context ? *context : NULL, &inbox, NULL);
5653 /* set up extra command option keys */
5654 rc = 0;
5655 ekey[rc].ch = (allow_list) ? ctrl('T') : 0 ;
5656 ekey[rc].rval = (allow_list) ? 2 : 0;
5657 ekey[rc].name = (allow_list) ? "^T" : "";
5658 ekey[rc++].label = (allow_list) ? N_("ToFldrs") : "";
5660 if(ps_global->context_list->next){
5661 ekey[rc].ch = ctrl('P');
5662 ekey[rc].rval = 10;
5663 ekey[rc].name = "^P";
5664 ekey[rc++].label = N_("Prev Collection");
5666 ekey[rc].ch = ctrl('N');
5667 ekey[rc].rval = 11;
5668 ekey[rc].name = "^N";
5669 ekey[rc++].label = N_("Next Collection");
5672 ekey[rc].ch = ctrl('W');
5673 ekey[rc].rval = 17;
5674 ekey[rc].name = "^W";
5675 ekey[rc++].label = N_("INBOX");
5677 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
5678 ekey[rc].ch = TAB;
5679 ekey[rc].rval = 12;
5680 ekey[rc].name = "TAB";
5681 ekey[rc++].label = N_("Complete");
5684 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
5685 ekey[rc].ch = ctrl('X');
5686 ekey[rc].rval = 14;
5687 ekey[rc].name = "^X";
5688 ekey[rc++].label = N_("ListMatches");
5691 if(ps_global->context_list->next && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5692 ekey[rc].ch = KEY_UP;
5693 ekey[rc].rval = 10;
5694 ekey[rc].name = "";
5695 ekey[rc++].label = "";
5697 ekey[rc].ch = KEY_DOWN;
5698 ekey[rc].rval = 11;
5699 ekey[rc].name = "";
5700 ekey[rc++].label = "";
5702 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5703 ekey[rc].ch = KEY_UP;
5704 ekey[rc].rval = 30;
5705 ekey[rc].name = "";
5706 ku = rc;
5707 ekey[rc++].label = "";
5709 ekey[rc].ch = KEY_DOWN;
5710 ekey[rc].rval = 31;
5711 ekey[rc].name = "";
5712 ekey[rc++].label = "";
5715 ekey[rc].ch = -1;
5717 while(!done) {
5719 * Figure out next default value for this context. The idea
5720 * is that in each context the last folder opened is cached.
5721 * It's up to pick it out and display it. This is fine
5722 * and dandy if we've currently got the inbox open, BUT
5723 * if not, make the inbox the default the first time thru.
5725 if(!inbox){
5726 last_folder = ps_global->inbox_name;
5727 inbox = 1; /* pretend we're in inbox from here on out */
5729 else
5730 last_folder = (ps_global->last_unambig_folder[0])
5731 ? ps_global->last_unambig_folder
5732 : ((tc->last_folder[0]) ? tc->last_folder : NULL);
5734 if(last_folder){ /* MAXPATH + 1 = sizeof(expanded) */
5735 unsigned char *fname = folder_name_decoded((unsigned char *)last_folder);
5736 snprintf(expanded, sizeof(expanded), " [%.*s]", MAXPATH+1-5,
5737 fname ? (char *) fname : last_folder);
5738 if(fname) fs_give((void **)&fname);
5740 else
5741 *expanded = '\0';
5743 expanded[sizeof(expanded)-1] = '\0';
5745 /* only show collection number if more than one available */
5746 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5747 snprintf(prompt, sizeof(prompt), "GOTO %s in <%s> %.*s%s: ",
5748 NEWS_TEST(tc) ? "news group" : "folder",
5749 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5750 *expanded ? " " : "");
5751 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5752 snprintf(prompt, sizeof(prompt), "GOTO folder %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5753 *expanded ? " " : "");
5755 prompt[sizeof(prompt)-1] = '\0';
5757 if(utf8_width(prompt) > MAXPROMPT){
5758 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5759 snprintf(prompt, sizeof(prompt), "GOTO <%s> %.*s%s: ",
5760 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5761 *expanded ? " " : "");
5762 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5763 snprintf(prompt, sizeof(prompt), "GOTO %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5764 *expanded ? " " : "");
5766 prompt[sizeof(prompt)-1] = '\0';
5768 if(utf8_width(prompt) > MAXPROMPT){
5769 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5770 snprintf(prompt, sizeof(prompt), "<%s> %.*s%s: ",
5771 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5772 *expanded ? " " : "");
5773 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5774 snprintf(prompt, sizeof(prompt), "%.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5775 *expanded ? " " : "");
5777 prompt[sizeof(prompt)-1] = '\0';
5781 if(ku >= 0){
5782 if(items_in_hist(history) > 1){
5783 ekey[ku].name = HISTORY_UP_KEYNAME;
5784 ekey[ku].label = HISTORY_KEYLABEL;
5785 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
5786 ekey[ku+1].label = HISTORY_KEYLABEL;
5788 else{
5789 ekey[ku].name = "";
5790 ekey[ku].label = "";
5791 ekey[ku+1].name = "";
5792 ekey[ku+1].label = "";
5796 /* is there any other way to do this? The point is that we
5797 * are trying to hide mutf7 from the user, and use the utf8
5798 * equivalent. So we create a variable f to take place of
5799 * newfolder, including content and size. f2 is copy of f1
5800 * that has to freed. Sigh!
5802 f3 = (unsigned char *) cpystr(newfolder);
5803 f1 = fs_get(sizeof(newfolder));
5804 f2 = folder_name_decoded(f3);
5805 if(f3) fs_give((void **)&f3);
5806 strncpy((char *)f1, (char *)f2, sizeof(newfolder));
5807 f1[sizeof(newfolder)-1] = '\0';
5808 if(f2) fs_give((void **)&f2);
5810 flags = OE_APPEND_CURRENT;
5811 rc = optionally_enter((char *) f1, qline, 0, sizeof(newfolder),
5812 (char *) prompt, ekey, help, &flags);
5814 f2 = folder_name_encoded(f1);
5815 strncpy(newfolder, (char *)f2, sizeof(newfolder));
5816 if(f1) fs_give((void **)&f1);
5817 if(f2) fs_give((void **)&f2);
5819 ps_global->mangled_footer = 1;
5821 switch(rc){
5822 case -1 : /* o_e says error! */
5823 q_status_message(SM_ORDER | SM_DING, 3, 3,
5824 _("Error reading folder name"));
5825 return(NULL);
5827 case 0 : /* o_e says normal entry */
5828 removing_trailing_white_space(newfolder);
5829 removing_leading_white_space(newfolder);
5831 if(*newfolder){
5832 char *name, *fullname = NULL;
5833 int exists, breakout = 0;
5835 save_hist(history, newfolder, 0, tc);
5837 if(!(name = folder_is_nick(newfolder, FOLDERS(tc),
5838 FN_WHOLE_NAME)))
5839 name = newfolder;
5841 if(update_folder_spec(expanded, sizeof(expanded), name)){
5842 strncpy(name = newfolder, expanded, sizeof(newfolder));
5843 newfolder[sizeof(newfolder)-1] = '\0';
5846 exists = folder_name_exists(tc, name, &fullname);
5848 if(fullname){
5849 strncpy(name = newfolder, fullname, sizeof(newfolder));
5850 newfolder[sizeof(newfolder)-1] = '\0';
5851 fs_give((void **) &fullname);
5852 breakout = TRUE;
5856 * if we know the things a folder, open it.
5857 * else if we know its a directory, visit it.
5858 * else we're not sure (it either doesn't really
5859 * exist or its unLISTable) so try opening it anyway
5861 if(exists & FEX_ISFILE){
5862 done++;
5863 break;
5865 else if((exists & FEX_ISDIR)){
5866 if(breakout){
5867 CONTEXT_S *fake_context;
5868 char tmp[MAILTMPLEN];
5869 size_t l;
5871 strncpy(tmp, name, sizeof(tmp));
5872 tmp[sizeof(tmp)-2-1] = '\0';
5873 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
5874 if(l < sizeof(tmp)){
5875 tmp[l] = tc->dir->delim;
5876 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
5879 else
5880 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
5882 tmp[sizeof(tmp)-1] = '\0';
5884 fake_context = new_context(tmp, 0);
5885 newfolder[0] = '\0';
5886 done = display_folder_list(&fake_context, newfolder,
5887 1, folders_for_goto);
5888 free_context(&fake_context);
5889 break;
5891 else if(!(tc->use & CNTXT_INCMNG)){
5892 done = display_folder_list(&tc, newfolder,
5893 1, folders_for_goto);
5894 break;
5897 else if((exists & FEX_ERROR)){
5898 q_status_message1(SM_ORDER, 0, 3,
5899 _("Problem accessing folder \"%s\""),
5900 newfolder);
5901 return(NULL);
5903 else{
5904 done++;
5905 break;
5908 if(exists == FEX_ERROR)
5909 q_status_message1(SM_ORDER, 0, 3,
5910 _("Problem accessing folder \"%s\""),
5911 newfolder);
5912 else if(tc->use & CNTXT_INCMNG)
5913 q_status_message1(SM_ORDER, 0, 3,
5914 _("Can't find Incoming Folder: %s"),
5915 newfolder);
5916 else if(context_isambig(newfolder))
5917 q_status_message2(SM_ORDER, 0, 3,
5918 _("Can't find folder \"%s\" in %s"),
5919 newfolder, (void *) tc->nickname);
5920 else
5921 q_status_message1(SM_ORDER, 0, 3,
5922 _("Can't find folder \"%s\""),
5923 newfolder);
5925 return(NULL);
5927 else if(last_folder){
5928 if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX
5929 && !strucmp(last_folder, ps_global->inbox_name)
5930 && tc == ((ps_global->context_list->use & CNTXT_INCMNG)
5931 ? ps_global->context_list->next : ps_global->context_list)){
5932 if(notrealinbox)
5933 (*notrealinbox) = 0;
5935 tc = ps_global->context_list;
5938 strncpy(newfolder, last_folder, sizeof(newfolder));
5939 newfolder[sizeof(newfolder)-1] = '\0';
5940 save_hist(history, newfolder, 0, tc);
5941 done++;
5942 break;
5944 /* fall thru like they cancelled */
5946 case 1 : /* o_e says user cancel */
5947 cmd_cancelled("Open folder");
5948 return(NULL);
5950 case 2 : /* o_e says user wants list */
5951 r = display_folder_list(&tc, newfolder, 0, folders_for_goto);
5952 if(r)
5953 done++;
5955 break;
5957 case 3 : /* o_e says user wants help */
5958 help = help == NO_HELP ? h_oe_broach : NO_HELP;
5959 break;
5961 case 4 : /* redraw */
5962 break;
5964 case 10 : /* Previous collection */
5965 tc2 = ps_global->context_list;
5966 while(tc2->next && tc2->next != tc)
5967 tc2 = tc2->next;
5969 tc = tc2;
5970 break;
5972 case 11 : /* Next collection */
5973 tc = (tc->next) ? tc->next : ps_global->context_list;
5974 break;
5976 case 12 : /* file name completion */
5977 if(!folder_complete(tc, newfolder, sizeof(newfolder), &n)){
5978 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
5979 r = display_folder_list(&tc, newfolder, 1,folders_for_goto);
5980 if(r)
5981 done++; /* bingo! */
5982 else
5983 rc = 0; /* burn last_rc */
5985 else
5986 Writechar(BELL, 0);
5989 break;
5991 case 14 : /* file name completion */
5992 r = display_folder_list(&tc, newfolder, 2, folders_for_goto);
5993 if(r)
5994 done++; /* bingo! */
5995 else
5996 rc = 0; /* burn last_rc */
5998 break;
6000 case 17 : /* GoTo INBOX */
6001 done++;
6002 strncpy(newfolder, ps_global->inbox_name, sizeof(newfolder)-1);
6003 newfolder[sizeof(newfolder)-1] = '\0';
6004 if(notrealinbox)
6005 (*notrealinbox) = 0;
6007 tc = ps_global->context_list;
6008 save_hist(history, newfolder, 0, tc);
6010 break;
6012 case 30 :
6013 if((p = get_prev_hist(history, newfolder, 0, tc)) != NULL){
6014 strncpy(newfolder, p, sizeof(newfolder));
6015 newfolder[sizeof(newfolder)-1] = '\0';
6016 if(history->hist[history->curindex])
6017 tc = history->hist[history->curindex]->cntxt;
6019 else
6020 Writechar(BELL, 0);
6022 break;
6024 case 31 :
6025 if((p = get_next_hist(history, newfolder, 0, tc)) != NULL){
6026 strncpy(newfolder, p, sizeof(newfolder));
6027 newfolder[sizeof(newfolder)-1] = '\0';
6028 if(history->hist[history->curindex])
6029 tc = history->hist[history->curindex]->cntxt;
6031 else
6032 Writechar(BELL, 0);
6034 break;
6036 default :
6037 alpine_panic("Unhandled case");
6038 break;
6041 last_rc = rc;
6044 dprint((2, "broach folder, name entered \"%s\"\n",
6045 newfolder ? newfolder : "?"));
6047 /*-- Just check that we can expand this. It gets done for real later --*/
6048 strncpy(expanded, newfolder, sizeof(expanded));
6049 expanded[sizeof(expanded)-1] = '\0';
6051 if(!expand_foldername(expanded, sizeof(expanded))) {
6052 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
6053 expanded ? expanded : "?"));
6054 return(NULL);
6057 *context = tc;
6058 return(newfolder);
6062 /*----------------------------------------------------------------------
6063 Check to see if user wants to reopen dead stream.
6065 Args: ps --
6066 reopenp --
6068 Result: 1 if the folder was successfully updatedn
6069 0 if not necessary
6071 ----*/
6073 ask_mailbox_reopen(struct pine *ps, int *reopenp)
6075 if(((ps->mail_stream->dtb
6076 && ((ps->mail_stream->dtb->flags & DR_NONEWMAIL)
6077 || (ps->mail_stream->rdonly
6078 && ps->mail_stream->dtb->flags & DR_NONEWMAILRONLY)))
6079 && (ps->reopen_rule == REOPEN_ASK_ASK_Y
6080 || ps->reopen_rule == REOPEN_ASK_ASK_N
6081 || ps->reopen_rule == REOPEN_ASK_NO_Y
6082 || ps->reopen_rule == REOPEN_ASK_NO_N))
6083 || ((ps->mail_stream->dtb
6084 && ps->mail_stream->rdonly
6085 && !(ps->mail_stream->dtb->flags & DR_LOCAL))
6086 && (ps->reopen_rule == REOPEN_YES_ASK_Y
6087 || ps->reopen_rule == REOPEN_YES_ASK_N
6088 || ps->reopen_rule == REOPEN_ASK_ASK_Y
6089 || ps->reopen_rule == REOPEN_ASK_ASK_N))){
6090 int deefault;
6092 switch(ps->reopen_rule){
6093 case REOPEN_YES_ASK_Y:
6094 case REOPEN_ASK_ASK_Y:
6095 case REOPEN_ASK_NO_Y:
6096 deefault = 'y';
6097 break;
6099 default:
6100 deefault = 'n';
6101 break;
6104 switch(want_to("Re-open folder to check for new messages", deefault,
6105 'x', h_reopen_folder, WT_NORM)){
6106 case 'y':
6107 (*reopenp)++;
6108 break;
6110 case 'x':
6111 return(-1);
6115 return(0);
6120 /*----------------------------------------------------------------------
6121 Check to see if user input is in form of old c-client mailbox speck
6123 Args: old --
6124 new --
6126 Result: 1 if the folder was successfully updatedn
6127 0 if not necessary
6129 ----*/
6131 update_folder_spec(char *new, size_t newlen, char *old)
6133 char *p, *orignew;
6134 int nntp = 0;
6136 orignew = new;
6137 if(*(p = old) == '*') /* old form? */
6138 old++;
6140 if(*old == '{') /* copy host spec */
6142 switch(*new = *old++){
6143 case '\0' :
6144 return(FALSE);
6146 case '/' :
6147 if(!struncmp(old, "nntp", 4))
6148 nntp++;
6150 break;
6152 default :
6153 break;
6155 while(*new++ != '}' && (new-orignew) < newlen-1);
6157 if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){
6159 * OK, some heuristics here. If it looks like a newsgroup
6160 * then we plunk it into the #news namespace else we
6161 * assume that they're trying to get at a #public folder...
6163 for(p = old;
6164 *p && (isalnum((unsigned char) *p) || strindex(".-", *p));
6165 p++)
6168 sstrncpy(&new, (*p && !nntp) ? "#public/" : "#news.", newlen-(new-orignew));
6169 strncpy(new, old, newlen-(new-orignew));
6170 return(TRUE);
6173 orignew[newlen-1] = '\0';
6175 return(FALSE);
6179 /*----------------------------------------------------------------------
6180 Open the requested folder in the requested context
6182 Args: state -- usual pine state struct
6183 newfolder -- folder to open
6184 new_context -- folder context might live in
6185 stream -- candidate for recycling
6187 Result: New folder open or not (if error), and we're set to
6188 enter the index screen.
6189 ----*/
6190 void
6191 visit_folder(struct pine *state, char *newfolder, CONTEXT_S *new_context,
6192 MAILSTREAM *stream, long unsigned int flags)
6194 dprint((9, "visit_folder(%s, %s)\n",
6195 newfolder ? newfolder : "?",
6196 (new_context && new_context->context)
6197 ? new_context->context : "(NULL)"));
6199 if(ps_global && ps_global->ttyo){
6200 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
6201 ps_global->mangled_footer = 1;
6204 if(do_broach_folder(newfolder, new_context, stream ? &stream : NULL,
6205 flags) >= 0
6206 || !sp_flagged(state->mail_stream, SP_LOCKED))
6207 state->next_screen = mail_index_screen;
6208 else
6209 state->next_screen = folder_screen;
6213 /*----------------------------------------------------------------------
6214 Move read messages from folder if listed in archive
6216 Args:
6218 ----*/
6220 read_msg_prompt(long int n, char *f)
6222 char buf[MAX_SCREEN_COLS+1];
6224 snprintf(buf, sizeof(buf), "Save the %ld read message%s in \"%s\"", n, plural(n), f);
6225 buf[sizeof(buf)-1] = '\0';
6226 return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y');
6230 /*----------------------------------------------------------------------
6231 Print current message[s] or folder index
6233 Args: state -- pointer to struct holding a bunch of pine state
6234 msgmap -- table mapping msg nums to c-client sequence nums
6235 aopt -- aggregate options
6236 in_index -- boolean indicating we're called from Index Screen
6238 Filters the original header and sends stuff to printer
6239 ---*/
6241 cmd_print(struct pine *state, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
6243 char prompt[250];
6244 long i, msgs, rawno;
6245 int next = 0, do_index = 0, rv = 0;
6246 ENVELOPE *e;
6247 BODY *b;
6248 MESSAGECACHE *mc;
6250 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
6251 return rv;
6253 msgs = mn_total_cur(msgmap);
6255 if((in_index != View) && F_ON(F_PRINT_INDEX, state)){
6256 char m[10];
6257 int ans;
6258 static ESCKEY_S prt_opts[] = {
6259 {'i', 'i', "I", N_("Index")},
6260 {'m', 'm', "M", NULL},
6261 {-1, 0, NULL, NULL}};
6263 if(in_index == ThrdIndx){
6264 /* TRANSLATORS: This is a question, Print Index ? */
6265 if(want_to(_("Print Index"), 'y', 'x', NO_HELP, WT_NORM) == 'y')
6266 ans = 'i';
6267 else
6268 ans = 'x';
6270 else{
6271 snprintf(m, sizeof(m), "Message%s", (msgs>1L) ? "s" : "");
6272 m[sizeof(m)-1] = '\0';
6273 prt_opts[1].label = m;
6274 snprintf(prompt, sizeof(prompt), "Print %sFolder Index or %s %s? ",
6275 (aopt & MCMD_AGG_2) ? "thread " : MCMD_ISAGG(aopt) ? "selected " : "",
6276 (aopt & MCMD_AGG_2) ? "thread" : MCMD_ISAGG(aopt) ? "selected" : "current", m);
6277 prompt[sizeof(prompt)-1] = '\0';
6279 ans = radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x',
6280 NO_HELP, RB_NORM|RB_SEQ_SENSITIVE);
6283 switch(ans){
6284 case 'x' :
6285 cmd_cancelled("Print");
6286 if(MCMD_ISAGG(aopt))
6287 restore_selected(msgmap);
6289 return rv;
6291 case 'i':
6292 do_index = 1;
6293 break;
6295 default :
6296 case 'm':
6297 break;
6301 if(do_index)
6302 snprintf(prompt, sizeof(prompt), "%sFolder Index",
6303 (aopt & MCMD_AGG_2) ? "Thread " : MCMD_ISAGG(aopt) ? "Selected " : "");
6304 else if(msgs > 1L)
6305 snprintf(prompt, sizeof(prompt), "%s messages", long2string(msgs));
6306 else
6307 snprintf(prompt, sizeof(prompt), "Message %s", long2string(mn_get_cur(msgmap)));
6309 prompt[sizeof(prompt)-1] = '\0';
6311 if(open_printer(prompt) < 0){
6312 if(MCMD_ISAGG(aopt))
6313 restore_selected(msgmap);
6315 return rv;
6318 if(do_index){
6319 TITLE_S *tc;
6321 tc = format_titlebar();
6323 /* Print titlebar... */
6324 print_text1("%s\n\n", tc ? tc->titlebar_line : "");
6325 /* then all the index members... */
6326 if(!print_index(state, msgmap, MCMD_ISAGG(aopt)))
6327 q_status_message(SM_ORDER | SM_DING, 3, 3,
6328 _("Error printing folder index"));
6329 else
6330 rv++;
6332 else{
6333 rv++;
6334 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){
6335 if(next && F_ON(F_AGG_PRINT_FF, state))
6336 if(!print_char(FORMFEED)){
6337 rv = 0;
6338 break;
6341 if(!(state->mail_stream
6342 && (rawno = mn_m2raw(msgmap, i)) > 0L
6343 && rawno <= state->mail_stream->nmsgs
6344 && (mc = mail_elt(state->mail_stream, rawno))
6345 && mc->valid))
6346 mc = NULL;
6348 if(!(e=pine_mail_fetchstructure(state->mail_stream,
6349 mn_m2raw(msgmap,i),
6350 &b))
6351 || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global)
6352 && !bezerk_delimiter(e, mc, print_char, next))
6353 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
6354 e, b, NULL, FM_NEW_MESS | FM_NOINDENT,
6355 print_char)){
6356 q_status_message(SM_ORDER | SM_DING, 3, 3,
6357 _("Error printing message"));
6358 rv = 0;
6359 break;
6364 close_printer();
6366 if(MCMD_ISAGG(aopt))
6367 restore_selected(msgmap);
6369 return rv;
6373 /*----------------------------------------------------------------------
6374 Pipe message text
6376 Args: state -- various pine state bits
6377 msgmap -- Message number mapping table
6378 aopt -- option flags
6380 Filters the original header and sends stuff to specified command
6381 ---*/
6383 cmd_pipe(struct pine *state, MSGNO_S *msgmap, int aopt)
6385 ENVELOPE *e;
6386 MESSAGECACHE *mc;
6387 BODY *b;
6388 PIPE_S *syspipe;
6389 char *resultfilename = NULL, prompt[80], *p;
6390 int done = 0, rv = 0;
6391 gf_o_t pc;
6392 int fourlabel = -1, j = 0, next = 0, ku;
6393 int pipe_rv; /* rv of proc to separate from close_system_pipe rv */
6394 long i, rawno;
6395 unsigned flagsforhist = 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6396 static HISTORY_S *history = NULL;
6397 int capture = 1, raw = 0, delimit = 0, newpipe = 0;
6398 char pipe_command[MAXPATH];
6399 ESCKEY_S pipe_opt[8];
6401 if(ps_global->restricted){
6402 q_status_message(SM_ORDER | SM_DING, 0, 4,
6403 "Alpine demo can't pipe messages");
6404 return rv;
6406 else if(!any_messages(msgmap, NULL, "to Pipe"))
6407 return rv;
6409 pipe_command[0] = '\0';
6410 init_hist(&history, HISTSIZE);
6411 flagsforhist = (raw ? 0x8 : 0) +
6412 (delimit ? 0x4 : 0) +
6413 (newpipe ? 0x2 : 0) +
6414 (capture ? 0x1 : 0);
6415 if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){
6416 strncpy(pipe_command, p, sizeof(pipe_command));
6417 pipe_command[sizeof(pipe_command)-1] = '\0';
6418 if(history->hist[history->curindex]){
6419 flagsforhist = history->hist[history->curindex]->flags;
6420 raw = (flagsforhist & 0x8) ? 1 : 0;
6421 delimit = (flagsforhist & 0x4) ? 1 : 0;
6422 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6423 capture = (flagsforhist & 0x1) ? 1 : 0;
6427 pipe_opt[j].ch = 0;
6428 pipe_opt[j].rval = 0;
6429 pipe_opt[j].name = "";
6430 pipe_opt[j++].label = "";
6432 pipe_opt[j].ch = ctrl('W');
6433 pipe_opt[j].rval = 10;
6434 pipe_opt[j].name = "^W";
6435 pipe_opt[j++].label = NULL;
6437 pipe_opt[j].ch = ctrl('Y');
6438 pipe_opt[j].rval = 11;
6439 pipe_opt[j].name = "^Y";
6440 pipe_opt[j++].label = NULL;
6442 pipe_opt[j].ch = ctrl('R');
6443 pipe_opt[j].rval = 12;
6444 pipe_opt[j].name = "^R";
6445 pipe_opt[j++].label = NULL;
6447 if(MCMD_ISAGG(aopt)){
6448 if(!pseudo_selected(state->mail_stream, msgmap))
6449 return rv;
6450 else{
6451 fourlabel = j;
6452 pipe_opt[j].ch = ctrl('T');
6453 pipe_opt[j].rval = 13;
6454 pipe_opt[j].name = "^T";
6455 pipe_opt[j++].label = NULL;
6459 pipe_opt[j].ch = KEY_UP;
6460 pipe_opt[j].rval = 30;
6461 pipe_opt[j].name = "";
6462 ku = j;
6463 pipe_opt[j++].label = "";
6465 pipe_opt[j].ch = KEY_DOWN;
6466 pipe_opt[j].rval = 31;
6467 pipe_opt[j].name = "";
6468 pipe_opt[j++].label = "";
6470 pipe_opt[j].ch = -1;
6472 while (!done) {
6473 int flags;
6475 snprintf(prompt, sizeof(prompt), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6476 raw ? "RAW " : "",
6477 MCMD_ISAGG(aopt) ? "s" : " ",
6478 MCMD_ISAGG(aopt) ? "" : comatose(mn_get_cur(msgmap)),
6479 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? "(" : "",
6480 capture ? "" : "uncaptured",
6481 (!capture && delimit) ? "," : "",
6482 delimit ? "delimited" : "",
6483 ((!capture || delimit) && newpipe && MCMD_ISAGG(aopt)) ? "," : "",
6484 (newpipe && MCMD_ISAGG(aopt)) ? "new pipe" : "",
6485 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? ") " : "");
6486 prompt[sizeof(prompt)-1] = '\0';
6487 pipe_opt[1].label = raw ? N_("Shown Text") : N_("Raw Text");
6488 pipe_opt[2].label = capture ? N_("Free Output") : N_("Capture Output");
6489 pipe_opt[3].label = delimit ? N_("No Delimiter") : N_("With Delimiter");
6490 if(fourlabel > 0)
6491 pipe_opt[fourlabel].label = newpipe ? N_("To Same Pipe") : N_("To Individual Pipes");
6495 * 2 is really 1 because there will be one real entry and
6496 * one entry of "" because of the get_prev_hist above.
6498 if(items_in_hist(history) > 2){
6499 pipe_opt[ku].name = HISTORY_UP_KEYNAME;
6500 pipe_opt[ku].label = HISTORY_KEYLABEL;
6501 pipe_opt[ku+1].name = HISTORY_DOWN_KEYNAME;
6502 pipe_opt[ku+1].label = HISTORY_KEYLABEL;
6504 else{
6505 pipe_opt[ku].name = "";
6506 pipe_opt[ku].label = "";
6507 pipe_opt[ku+1].name = "";
6508 pipe_opt[ku+1].label = "";
6511 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
6512 switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0,
6513 sizeof(pipe_command), prompt,
6514 pipe_opt, NO_HELP, &flags)){
6515 case -1 :
6516 q_status_message(SM_ORDER | SM_DING, 3, 4,
6517 _("Internal problem encountered"));
6518 done++;
6519 break;
6521 case 10 : /* flip raw bit */
6522 raw = !raw;
6523 break;
6525 case 11 : /* flip capture bit */
6526 capture = !capture;
6527 break;
6529 case 12 : /* flip delimit bit */
6530 delimit = !delimit;
6531 break;
6533 case 13 : /* flip newpipe bit */
6534 newpipe = !newpipe;
6535 break;
6537 case 30 :
6538 flagsforhist = (raw ? 0x8 : 0) +
6539 (delimit ? 0x4 : 0) +
6540 (newpipe ? 0x2 : 0) +
6541 (capture ? 0x1 : 0);
6542 if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6543 strncpy(pipe_command, p, sizeof(pipe_command));
6544 pipe_command[sizeof(pipe_command)-1] = '\0';
6545 if(history->hist[history->curindex]){
6546 flagsforhist = history->hist[history->curindex]->flags;
6547 raw = (flagsforhist & 0x8) ? 1 : 0;
6548 delimit = (flagsforhist & 0x4) ? 1 : 0;
6549 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6550 capture = (flagsforhist & 0x1) ? 1 : 0;
6553 else
6554 Writechar(BELL, 0);
6556 break;
6558 case 31 :
6559 flagsforhist = (raw ? 0x8 : 0) +
6560 (delimit ? 0x4 : 0) +
6561 (newpipe ? 0x2 : 0) +
6562 (capture ? 0x1 : 0);
6563 if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6564 strncpy(pipe_command, p, sizeof(pipe_command));
6565 pipe_command[sizeof(pipe_command)-1] = '\0';
6566 if(history->hist[history->curindex]){
6567 flagsforhist = history->hist[history->curindex]->flags;
6568 raw = (flagsforhist & 0x8) ? 1 : 0;
6569 delimit = (flagsforhist & 0x4) ? 1 : 0;
6570 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6571 capture = (flagsforhist & 0x1) ? 1 : 0;
6574 else
6575 Writechar(BELL, 0);
6577 break;
6579 case 0 :
6580 if(pipe_command[0]){
6582 flagsforhist = (raw ? 0x8 : 0) +
6583 (delimit ? 0x4 : 0) +
6584 (newpipe ? 0x2 : 0) +
6585 (capture ? 0x1 : 0);
6586 save_hist(history, pipe_command, flagsforhist, NULL);
6588 flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
6589 flags |= (raw ? PIPE_RAW : 0);
6590 if(!capture){
6591 #ifndef _WINDOWS
6592 ClearScreen();
6593 fflush(stdout);
6594 clear_cursor_pos();
6595 ps_global->mangled_screen = 1;
6596 ps_global->in_init_seq = 1;
6597 #endif
6598 flags |= PIPE_RESET;
6601 if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command,
6602 (flags & PIPE_RESET)
6603 ? NULL
6604 : &resultfilename,
6605 flags, &pc)))
6606 done++;
6608 for(i = mn_first_cur(msgmap);
6609 i > 0L && !done;
6610 i = mn_next_cur(msgmap)){
6611 e = pine_mail_fetchstructure(ps_global->mail_stream,
6612 mn_m2raw(msgmap, i), &b);
6613 if(!(state->mail_stream
6614 && (rawno = mn_m2raw(msgmap, i)) > 0L
6615 && rawno <= state->mail_stream->nmsgs
6616 && (mc = mail_elt(state->mail_stream, rawno))
6617 && mc->valid))
6618 mc = NULL;
6620 if((newpipe
6621 && !(syspipe = cmd_pipe_open(pipe_command,
6622 (flags & PIPE_RESET)
6623 ? NULL
6624 : &resultfilename,
6625 flags, &pc)))
6626 || (delimit && !bezerk_delimiter(e, mc, pc, next++)))
6627 done++;
6629 if(!done){
6630 if(raw){
6631 char *pipe_err;
6633 prime_raw_pipe_getc(ps_global->mail_stream,
6634 mn_m2raw(msgmap, i), -1L, 0L);
6635 gf_filter_init();
6636 gf_link_filter(gf_nvtnl_local, NULL);
6637 if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){
6638 q_status_message1(SM_ORDER|SM_DING,
6639 3, 3,
6640 _("Internal Error: %s"),
6641 pipe_err);
6642 done++;
6645 else if(!format_message(mn_m2raw(msgmap, i), e, b,
6646 NULL, FM_NEW_MESS | FM_NOWRAP, pc))
6647 done++;
6650 if(newpipe)
6651 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6652 done++;
6655 if(!capture)
6656 ps_global->in_init_seq = 0;
6658 if(!newpipe)
6659 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6660 done++;
6661 if(done) /* say we had a problem */
6662 q_status_message(SM_ORDER | SM_DING, 3, 3,
6663 _("Error piping message"));
6664 else if(resultfilename){
6665 rv++;
6666 /* only display if no error */
6667 display_output_file(resultfilename, "PIPE MESSAGE",
6668 NULL, DOF_EMPTY);
6669 fs_give((void **)&resultfilename);
6671 else{
6672 rv++;
6673 q_status_message(SM_ORDER, 0, 2, _("Pipe command completed"));
6676 done++;
6677 break;
6679 /* else fall thru as if cancelled */
6681 case 1 :
6682 cmd_cancelled("Pipe command");
6683 done++;
6684 break;
6686 case 3 :
6687 helper(h_common_pipe, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE);
6688 ps_global->mangled_screen = 1;
6689 break;
6691 case 2 : /* no place to escape to */
6692 case 4 : /* can't suspend */
6693 default :
6694 break;
6698 ps_global->mangled_footer = 1;
6699 if(MCMD_ISAGG(aopt))
6700 restore_selected(msgmap);
6702 return rv;
6706 /*----------------------------------------------------------------------
6707 Screen to offer list management commands contained in message
6709 Args: state -- pointer to struct holding a bunch of pine state
6710 msgmap -- table mapping msg nums to c-client sequence nums
6711 aopt -- aggregate options
6713 Result:
6715 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6716 ----*/
6717 void
6718 rfc2369_display(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno)
6720 int winner = 0;
6721 char *h, *hdrs[MLCMD_COUNT + 1];
6722 long index_no = mn_raw2m(msgmap, msgno);
6723 RFC2369_S data[MLCMD_COUNT];
6725 /* for each header field */
6726 if((h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))) != NULL){
6727 memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT);
6728 if(rfc2369_parse_fields(h, &data[0])){
6729 STORE_S *explain;
6731 if((explain = list_mgmt_text(data, index_no)) != NULL){
6732 list_mgmt_screen(explain);
6733 ps_global->mangled_screen = 1;
6734 so_give(&explain);
6735 winner++;
6739 fs_give((void **) &h);
6742 if(!winner)
6743 q_status_message1(SM_ORDER, 0, 3,
6744 "Message %s contains no list management information",
6745 comatose(index_no));
6749 STORE_S *
6750 list_mgmt_text(RFC2369_S *data, long int msgno)
6752 STORE_S *store;
6753 int i, j, n, fields = 0;
6754 static char *rfc2369_intro1 =
6755 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6756 static char *rfc2369_intro2[] = {
6757 N_(" has information associated with it "),
6758 N_("that explains how to participate in an email list. An "),
6759 N_("email list is represented by a single email address that "),
6760 N_("users sharing a common interest can send messages to (known "),
6761 N_("as posting) which are then redistributed to all members "),
6762 N_("of the list (sometimes after review by a moderator)."),
6763 N_("<P>List participation commands in this message include:"),
6764 NULL
6767 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6769 /* Insert introductory text */
6770 so_puts(store, rfc2369_intro1);
6772 so_puts(store, comatose(msgno));
6774 for(i = 0; rfc2369_intro2[i]; i++)
6775 so_puts(store, _(rfc2369_intro2[i]));
6777 so_puts(store, "<P>");
6778 for(i = 0; i < MLCMD_COUNT; i++)
6779 if(data[i].data[0].value
6780 || data[i].data[0].comment
6781 || data[i].data[0].error){
6782 if(!fields++)
6783 so_puts(store, "<UL>");
6785 so_puts(store, "<LI>");
6786 so_puts(store,
6787 (n = (data[i].data[1].value || data[i].data[1].comment))
6788 ? "Methods to "
6789 : "A method to ");
6791 so_puts(store, data[i].field.description);
6792 so_puts(store, ". ");
6794 if(n)
6795 so_puts(store, "<OL>");
6797 for(j = 0;
6798 j < MLCMD_MAXDATA
6799 && (data[i].data[j].comment
6800 || data[i].data[j].value
6801 || data[i].data[j].error);
6802 j++){
6804 so_puts(store, n ? "<P><LI>" : "<P>");
6806 if(data[i].data[j].comment){
6807 so_puts(store,
6808 _("With the provided comment:<P><BLOCKQUOTE>"));
6809 so_puts(store, data[i].data[j].comment);
6810 so_puts(store, "</BLOCKQUOTE><P>");
6813 if(data[i].data[j].value){
6814 if(i == MLCMD_POST
6815 && !strucmp(data[i].data[j].value, "NO")){
6816 so_puts(store,
6817 _("Posting is <EM>not</EM> allowed on this list"));
6819 else{
6820 so_puts(store, "Select <A HREF=\"");
6821 so_puts(store, data[i].data[j].value);
6822 so_puts(store, "\">HERE</A> ");
6823 if(!struncmp(data[i].data[j].value, "http", 4)) {
6824 so_puts(store, "(web link) ");
6826 else if(!struncmp(data[i].data[j].value, "mailto", 5)) {
6827 so_puts(store, " (email link) ");
6828 } so_puts(store, "to ");
6829 so_puts(store, (data[i].field.action)
6830 ? data[i].field.action
6831 : "try it");
6834 so_puts(store, ".");
6837 if(data[i].data[j].error){
6838 so_puts(store, "<P>Unfortunately, Alpine can not offer");
6839 so_puts(store, " to take direct action based upon it");
6840 so_puts(store, " because it was improperly formatted.");
6841 so_puts(store, " The unrecognized data associated with");
6842 so_puts(store, " the \"");
6843 so_puts(store, data[i].field.name);
6844 so_puts(store, "\" header field was:<P><BLOCKQUOTE>");
6845 so_puts(store, data[i].data[j].error);
6846 so_puts(store, "</BLOCKQUOTE>");
6849 so_puts(store, "<P>");
6852 if(n)
6853 so_puts(store, "</OL>");
6856 if(fields)
6857 so_puts(store, "</UL>");
6859 so_puts(store, "</BODY></HTML>");
6862 return(store);
6866 void
6867 list_mgmt_screen(STORE_S *html)
6869 int cmd = MC_NONE;
6870 long offset = 0L;
6871 char *error = NULL;
6872 STORE_S *store;
6873 HANDLE_S *handles = NULL;
6874 gf_i_t gc;
6875 gf_o_t pc;
6878 so_seek(html, 0L, 0);
6879 gf_set_so_readc(&gc, html);
6881 init_handles(&handles);
6883 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6884 gf_set_so_writec(&pc, store);
6885 gf_filter_init();
6887 gf_link_filter(gf_html2plain,
6888 gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols,
6889 non_messageview_margin(), &handles, NULL, 0));
6891 error = gf_pipe(gc, pc);
6893 gf_clear_so_writec(store);
6895 if(!error){
6896 SCROLL_S sargs;
6898 memset(&sargs, 0, sizeof(SCROLL_S));
6899 sargs.text.text = so_text(store);
6900 sargs.text.src = CharStar;
6901 sargs.text.desc = "list commands";
6902 sargs.text.handles = handles;
6903 if(offset){
6904 sargs.start.on = Offset;
6905 sargs.start.loc.offset = offset;
6908 sargs.bar.title = _("MAIL LIST COMMANDS");
6909 sargs.bar.style = MessageNumber;
6910 sargs.resize_exit = 1;
6911 sargs.help.text = h_special_list_commands;
6912 sargs.help.title = _("HELP FOR LIST COMMANDS");
6913 sargs.keys.menu = &listmgr_keymenu;
6914 setbitmap(sargs.keys.bitmap);
6915 if(!handles){
6916 clrbitn(LM_TRY_KEY, sargs.keys.bitmap);
6917 clrbitn(LM_PREV_KEY, sargs.keys.bitmap);
6918 clrbitn(LM_NEXT_KEY, sargs.keys.bitmap);
6921 cmd = scrolltool(&sargs);
6922 offset = sargs.start.loc.offset;
6925 so_give(&store);
6928 free_handles(&handles);
6929 gf_clear_so_readc(html);
6931 while(cmd == MC_RESIZE);
6935 /*----------------------------------------------------------------------
6936 Prompt the user for the type of select desired
6938 NOTE: any and all functions that successfully exit the second
6939 switch() statement below (currently "select_*() functions"),
6940 *MUST* update the folder's MESSAGECACHE element's "searched"
6941 bits to reflect the search result. Functions using
6942 mail_search() get this for free, the others must update 'em
6943 by hand.
6945 Returns -1 if canceled without changing selection
6946 0 if selection may have changed
6947 ----*/
6949 aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index)
6951 long i, diff, old_tot, msgno, raw;
6952 int p = 0, q = 0, rv = 0, narrow = 0, hidden, ret = -1;
6953 ESCKEY_S *sel_opts;
6954 MESSAGECACHE *mc;
6955 SEARCHSET *limitsrch = NULL;
6956 PINETHRD_S *thrd;
6957 extern MAILSTREAM *mm_search_stream;
6958 extern long mm_search_count;
6960 hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
6961 mm_search_stream = state->mail_stream;
6962 mm_search_count = 0L;
6964 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
6965 sel_opts = THRD_INDX() ? sel_opts6 : sel_opts5;
6966 else
6967 sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2;
6969 if(THREADING()){
6970 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH;
6972 else{
6973 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream)){
6974 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_XGMEXT_CH;
6975 sel_opts[SEL_OPTS_THREAD].rval = sel_opts[SEL_OPTS_XGMEXT].rval;
6976 sel_opts[SEL_OPTS_THREAD].name = sel_opts[SEL_OPTS_XGMEXT].name;
6977 sel_opts[SEL_OPTS_THREAD].label = sel_opts[SEL_OPTS_XGMEXT].label;
6978 sel_opts[SEL_OPTS_XGMEXT].ch = -1;
6980 else
6981 sel_opts[SEL_OPTS_THREAD].ch = -1;
6984 if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){
6985 if(THRD_INDX()){
6986 i = 0;
6987 thrd = fetch_thread(state->mail_stream,
6988 mn_m2raw(msgmap, mn_get_cur(msgmap)));
6989 /* check if whole thread is selected or not */
6990 if(thrd &&
6991 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT)
6993 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE))
6994 i = 1;
6996 sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd");
6998 else{
6999 i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap),
7000 MN_SLCT);
7001 sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur");
7004 sel_opts += 2; /* disable extra options */
7005 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
7006 q = double_radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
7007 RB_NORM);
7008 else
7009 q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
7010 RB_NORM);
7011 switch(q){
7012 case 'f' : /* flip selection */
7013 msgno = 0L;
7014 for(i = 1L; i <= mn_get_total(msgmap); i++){
7015 ret = 0;
7016 q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT);
7017 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q);
7018 if(hidden){
7019 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q);
7020 if(!msgno && q)
7021 mn_reset_cur(msgmap, msgno = i);
7025 return(ret);
7027 case 'n' : /* narrow selection */
7028 narrow++;
7029 q = 0;
7030 break;
7031 case 'r' : /* replace selection */
7032 p = 1; /* set flag we want to replace */
7033 sel_opts -= 2; /* re-enable first two options */
7034 case 'b' : /* broaden selection */
7035 q = 0; /* offer criteria prompt */
7036 break;
7038 case 'c' : /* Un/Select Current */
7039 case 'a' : /* Unselect All */
7040 case 'x' : /* cancel */
7041 break;
7043 default :
7044 q_status_message(SM_ORDER | SM_DING, 3, 3,
7045 "Unsupported Select option");
7046 return(ret);
7050 if(!q){
7051 while(1){
7052 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
7053 q = double_radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP,
7054 RB_NORM);
7055 else
7056 q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP,
7057 RB_NORM|RB_RET_HELP);
7059 if(q == 3){
7060 helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE);
7061 ps_global->mangled_screen = 1;
7063 else
7064 break;
7068 /* When we are replacing the search, unselect all messages first, unless
7069 * we are cancelling, in whose case, leave the screen as is, because we
7070 * are cancelling!
7072 if(p == 1 && q != 'x'){
7073 msgno = any_lflagged(msgmap, MN_SLCT);
7074 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
7075 agg_select_all(state->mail_stream, msgmap, &diff,
7076 any_lflagged(msgmap, MN_SLCT) <= 0L);
7080 * The purpose of this is to add the appropriate searchset to the
7081 * search so that the search can be limited to only looking at what
7082 * it needs to look at. That is, if we are narrowing then we only need
7083 * to look at messages which are already selected, and if we are
7084 * broadening, then we only need to look at messages which are not
7085 * yet selected. This routine will work whether or not
7086 * limiting_searchset properly limits the search set. In particular,
7087 * the searchset returned by limiting_searchset may include messages
7088 * which really shouldn't be included. We do that because a too-large
7089 * searchset will break some IMAP servers. It is even possible that it
7090 * becomes inefficient to send the whole set. If the select function
7091 * frees limitsrch, it should be sure to set it to NULL so we won't
7092 * try freeing it again here.
7094 limitsrch = limiting_searchset(state->mail_stream, narrow);
7097 * NOTE: See note about MESSAGECACHE "searched" bits above!
7099 switch(q){
7100 case 'x': /* cancel */
7101 cmd_cancelled("Select command");
7102 return(ret);
7104 case 'c' : /* select/unselect current */
7105 (void) select_by_current(state, msgmap, in_index);
7106 ret = 0;
7107 return(ret);
7109 case 'a' : /* select/unselect all */
7110 msgno = any_lflagged(msgmap, MN_SLCT);
7111 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
7112 ret = 0;
7113 agg_select_all(state->mail_stream, msgmap, &diff,
7114 any_lflagged(msgmap, MN_SLCT) <= 0L);
7115 q_status_message4(SM_ORDER,0,2,
7116 "%s%s message%s %sselected",
7117 msgno ? "" : "All ", comatose(diff),
7118 plural(diff), msgno ? "UN" : "");
7119 return(ret);
7121 case 'n' : /* Select by Number */
7122 ret = 0;
7123 if(THRD_INDX())
7124 rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch);
7125 else
7126 rv = select_by_number(state->mail_stream, msgmap, &limitsrch);
7128 break;
7130 case 'd' : /* Select by Date */
7131 ret = 0;
7132 rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap),
7133 &limitsrch);
7134 break;
7136 case 't' : /* Text */
7137 ret = 0;
7138 rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap),
7139 &limitsrch);
7140 break;
7142 case 'z' : /* Size */
7143 ret = 0;
7144 rv = select_by_size(state->mail_stream, &limitsrch);
7145 break;
7147 case 's' : /* Status */
7148 ret = 0;
7149 rv = select_by_status(state->mail_stream, &limitsrch);
7150 break;
7152 case 'k' : /* Keyword */
7153 ret = 0;
7154 rv = select_by_keyword(state->mail_stream, &limitsrch);
7155 break;
7157 case 'r' : /* Rule */
7158 ret = 0;
7159 rv = select_by_rule(state->mail_stream, &limitsrch);
7160 break;
7162 case 'h' : /* Thread */
7163 ret = 0;
7164 rv = select_by_thread(state->mail_stream, msgmap, &limitsrch);
7165 break;
7167 case 'g' : /* X-GM-EXT-1 */
7168 ret = 0;
7169 rv = select_by_gm_content(state->mail_stream, msgmap, mn_get_cur(msgmap), &limitsrch);
7170 break;
7172 default :
7173 q_status_message(SM_ORDER | SM_DING, 3, 3,
7174 "Unsupported Select option");
7175 return(ret);
7178 if(limitsrch)
7179 mail_free_searchset(&limitsrch);
7181 if(rv) /* bad return value.. */
7182 return(ret); /* error already displayed */
7184 if(narrow) /* make sure something was selected */
7185 for(i = 1L; i <= mn_get_total(msgmap); i++)
7186 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7187 && raw <= state->mail_stream->nmsgs
7188 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7189 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
7190 break;
7191 else
7192 mm_search_count--;
7195 diff = 0L;
7196 if(mm_search_count){
7198 * loop thru all the messages, adjusting local flag bits
7199 * based on their "searched" bit...
7201 for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++)
7202 if(narrow){
7203 /* turning OFF selectedness if the "searched" bit isn't lit. */
7204 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7205 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7206 && raw <= state->mail_stream->nmsgs
7207 && (mc = mail_elt(state->mail_stream, raw))
7208 && !mc->searched){
7209 diff--;
7210 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
7211 if(hidden)
7212 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
7214 /* adjust current message in case we unselect and hide it */
7215 else if(msgno < mn_get_cur(msgmap)
7216 && (!THRD_INDX()
7217 || !get_lflag(state->mail_stream, msgmap,
7218 i, MN_CHID)))
7219 msgno = i;
7222 else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7223 && raw <= state->mail_stream->nmsgs
7224 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7225 /* turn ON selectedness if "searched" bit is lit. */
7226 if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7227 diff++;
7228 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
7229 if(hidden)
7230 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
7234 /* if we're zoomed and the current message was unselected */
7235 if(narrow && msgno
7236 && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE))
7237 mn_reset_cur(msgmap, msgno);
7240 if(!diff){
7241 if(narrow)
7242 q_status_message4(SM_ORDER, 3, 3,
7243 "%s. %s message%s remain%s selected.",
7244 mm_search_count
7245 ? "No change resulted"
7246 : "No messages in intersection",
7247 comatose(old_tot), plural(old_tot),
7248 (old_tot == 1L) ? "s" : "");
7249 else if(old_tot)
7250 q_status_message(SM_ORDER, 3, 3,
7251 _("No change resulted. Matching messages already selected."));
7252 else
7253 q_status_message1(SM_ORDER | SM_DING, 3, 3,
7254 _("Select failed. No %smessages selected."),
7255 old_tot ? _("additional ") : "");
7257 else if(old_tot){
7258 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
7259 "Select matched %ld message%s. %s %smessage%s %sselected.",
7260 (diff > 0) ? diff : old_tot + diff,
7261 plural((diff > 0) ? diff : old_tot + diff),
7262 comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7263 (diff > 0) ? "total " : "",
7264 plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7265 (diff > 0) ? "" : "UN");
7266 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
7267 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
7269 else
7270 q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"),
7271 comatose(diff), plural(diff));
7273 return(ret);
7277 /*----------------------------------------------------------------------
7278 Toggle the state of the current message
7280 Args: state -- pointer pine's state variables
7281 msgmap -- message collection to operate on
7282 in_index -- in the message index view
7283 Returns: TRUE if current marked selected, FALSE otw
7284 ----*/
7286 select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
7288 long cur;
7289 int all_selected = 0;
7290 unsigned long was, tot, rawno;
7291 PINETHRD_S *thrd;
7293 cur = mn_get_cur(msgmap);
7295 if(THRD_INDX()){
7296 thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur));
7297 if(!thrd)
7298 return 0;
7300 was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT);
7301 tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE);
7302 if(was == tot)
7303 all_selected++;
7305 if(all_selected){
7306 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0);
7307 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7308 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1);
7310 * See if there's anything left to zoom on. If so,
7311 * pick an adjacent one for highlighting, else make
7312 * sure nothing is left hidden...
7314 if(any_lflagged(msgmap, MN_SLCT)){
7315 mn_inc_cur(state->mail_stream, msgmap,
7316 (in_index == View && THREADING()
7317 && sp_viewing_a_thread(state->mail_stream))
7318 ? MH_THISTHD
7319 : (in_index == View)
7320 ? MH_ANYTHD : MH_NONE);
7321 if(mn_get_cur(msgmap) == cur)
7322 mn_dec_cur(state->mail_stream, msgmap,
7323 (in_index == View && THREADING()
7324 && sp_viewing_a_thread(state->mail_stream))
7325 ? MH_THISTHD
7326 : (in_index == View)
7327 ? MH_ANYTHD : MH_NONE);
7329 else /* clear all hidden flags */
7330 (void) unzoom_index(state, state->mail_stream, msgmap);
7333 else
7334 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1);
7336 q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected",
7337 comatose(all_selected ? was : tot-was),
7338 plural(all_selected ? was : tot-was),
7339 all_selected ? "UN" : "");
7341 /* collapsed thread */
7342 else if(THREADING()
7343 && ((rawno = mn_m2raw(msgmap, cur)) != 0L)
7344 && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL)
7345 && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){
7347 * This doesn't work quite the same as the colon command works, but
7348 * it is arguably doing the correct thing. The difference is
7349 * that aggregate_select will zoom after selecting back where it
7350 * was called from, but selecting a thread with colon won't zoom.
7351 * Maybe it makes sense to zoom after a select but not after a colon
7352 * command even though they are very similar.
7354 thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state));
7356 else{
7357 if((all_selected =
7358 get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */
7359 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0);
7360 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7361 set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1);
7363 * See if there's anything left to zoom on. If so,
7364 * pick an adjacent one for highlighting, else make
7365 * sure nothing is left hidden...
7367 if(any_lflagged(msgmap, MN_SLCT)){
7368 mn_inc_cur(state->mail_stream, msgmap,
7369 (in_index == View && THREADING()
7370 && sp_viewing_a_thread(state->mail_stream))
7371 ? MH_THISTHD
7372 : (in_index == View)
7373 ? MH_ANYTHD : MH_NONE);
7374 if(mn_get_cur(msgmap) == cur)
7375 mn_dec_cur(state->mail_stream, msgmap,
7376 (in_index == View && THREADING()
7377 && sp_viewing_a_thread(state->mail_stream))
7378 ? MH_THISTHD
7379 : (in_index == View)
7380 ? MH_ANYTHD : MH_NONE);
7382 else /* clear all hidden flags */
7383 (void) unzoom_index(state, state->mail_stream, msgmap);
7386 else
7387 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1);
7389 q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected",
7390 long2string(cur), all_selected ? "UN" : "");
7394 return(!all_selected);
7398 /*----------------------------------------------------------------------
7399 Prompt the user for the command to perform on selected messages
7401 Args: state -- pointer pine's state variables
7402 msgmap -- message collection to operate on
7403 q_line -- line on display to write prompts
7404 Returns: 1 if the selected messages are suitably commanded,
7405 0 if the choice to pick the command was declined
7407 ----*/
7409 apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
7410 UCS preloadkeystroke, int flags, int q_line)
7412 int i = 8, /* number of static entries in sel_opts3 */
7413 rv = 0,
7414 cmd,
7415 we_cancel = 0,
7416 agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG;
7417 char prompt[80];
7418 PAT_STATE pstate;
7421 * To do this "right", we really ought to have access to the keymenu
7422 * here and change the typed command into a real command by running
7423 * it through menu_command. Then the switch below would be against
7424 * results from menu_command. If we did that we'd also pass the
7425 * results of menu_command in as preloadkeystroke instead of passing
7426 * the keystroke itself. But we don't have the keymenu handy,
7427 * so we have to fake it. The only complication that we run into
7428 * is that KEY_DEL is an escape sequence so we change a typed
7429 * KEY_DEL esc seq into the letter D.
7432 if(!preloadkeystroke){
7433 if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
7434 sel_opts3[i].ch = '*';
7435 sel_opts3[i].rval = '*';
7436 sel_opts3[i].name = "*";
7437 sel_opts3[i++].label = N_("Flag");
7440 if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
7441 sel_opts3[i].ch = '|';
7442 sel_opts3[i].rval = '|';
7443 sel_opts3[i].name = "|";
7444 sel_opts3[i++].label = N_("Pipe");
7447 if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */
7448 sel_opts3[i].ch = 'b';
7449 sel_opts3[i].rval = 'b';
7450 sel_opts3[i].name = "B";
7451 sel_opts3[i++].label = N_("Bounce");
7454 if(flags & AC_FROM_THREAD){
7455 if(flags & (AC_COLL | AC_EXPN)){
7456 sel_opts3[i].ch = '/';
7457 sel_opts3[i].rval = '/';
7458 sel_opts3[i].name = "/";
7459 sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse")
7460 : N_("Expand");
7463 sel_opts3[i].ch = ';';
7464 sel_opts3[i].rval = ';';
7465 sel_opts3[i].name = ";";
7466 if(flags & AC_UNSEL)
7467 sel_opts3[i++].label = N_("UnSelect");
7468 else
7469 sel_opts3[i++].label = N_("Select");
7472 if(F_ON(F_ENABLE_PRYNT, state)){ /* this one is invisible */
7473 sel_opts3[i].ch = 'y';
7474 sel_opts3[i].rval = '%';
7475 sel_opts3[i].name = "";
7476 sel_opts3[i++].label = "";
7479 if(!is_imap_stream(stream) || LEVELUIDPLUS(stream)){ /* expunge selected messages */
7480 sel_opts3[i].ch = 'x';
7481 sel_opts3[i].rval = 'x';
7482 sel_opts3[i].name = "X";
7483 sel_opts3[i++].label = N_("Expunge");
7486 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7487 sel_opts3[i].ch = '#';
7488 sel_opts3[i].rval = '#';
7489 sel_opts3[i].name = "#";
7490 sel_opts3[i++].label = N_("Set Role");
7493 sel_opts3[i].ch = KEY_DEL; /* also invisible */
7494 sel_opts3[i].rval = 'd';
7495 sel_opts3[i].name = "";
7496 sel_opts3[i++].label = "";
7498 sel_opts3[i].ch = -1;
7500 snprintf(prompt, sizeof(prompt), "%s command : ",
7501 (flags & AC_FROM_THREAD) ? "THREAD" : "APPLY");
7502 prompt[sizeof(prompt)-1] = '\0';
7503 cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'c', NO_HELP,
7504 RB_SEQ_SENSITIVE);
7505 if(isupper(cmd))
7506 cmd = tolower(cmd);
7508 else{
7509 if(preloadkeystroke == KEY_DEL)
7510 cmd = 'd';
7511 else{
7512 if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke))
7513 cmd = tolower((int) preloadkeystroke);
7514 else
7515 cmd = (int) preloadkeystroke; /* shouldn't happen */
7519 switch(cmd){
7520 case 'd' : /* delete */
7521 we_cancel = busy_cue(NULL, NULL, 1);
7522 rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */
7523 if(we_cancel)
7524 cancel_busy_cue(0);
7525 break;
7527 case 'u' : /* undelete */
7528 we_cancel = busy_cue(NULL, NULL, 1);
7529 rv = cmd_undelete(state, msgmap, agg);
7530 if(we_cancel)
7531 cancel_busy_cue(0);
7532 break;
7534 case 'r' : /* reply */
7535 rv = cmd_reply(state, msgmap, agg, NULL);
7536 break;
7538 case 'f' : /* Forward */
7539 rv = cmd_forward(state, msgmap, agg, NULL);
7540 break;
7542 case '%' : /* print */
7543 rv = cmd_print(state, msgmap, agg, MsgIndx);
7544 break;
7546 case 't' : /* take address */
7547 rv = cmd_take_addr(state, msgmap, agg);
7548 break;
7550 case 's' : /* save */
7551 rv = cmd_save(state, stream, msgmap, agg, MsgIndx);
7552 break;
7554 case 'e' : /* export */
7555 rv = cmd_export(state, msgmap, q_line, agg);
7556 break;
7558 case '|' : /* pipe */
7559 rv = cmd_pipe(state, msgmap, agg);
7560 break;
7562 case '*' : /* flag */
7563 we_cancel = busy_cue(NULL, NULL, 1);
7564 rv = cmd_flag(state, msgmap, agg);
7565 if(we_cancel)
7566 cancel_busy_cue(0);
7567 break;
7569 case 'b' : /* bounce */
7570 rv = cmd_bounce(state, msgmap, agg, NULL);
7571 break;
7573 case '/' :
7574 collapse_or_expand(state, stream, msgmap,
7575 F_ON(F_SLASH_COLL_ENTIRE, ps_global)
7576 ? 0L
7577 : mn_get_cur(msgmap));
7578 break;
7580 case ':' :
7581 select_thread_stmp(state, stream, msgmap);
7582 break;
7584 case 'x' : /* Expunge */
7585 rv = cmd_expunge(state, stream, msgmap, agg);
7586 break;
7588 case 'c' : /* cancel */
7589 cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command"
7590 : "Apply command");
7591 break;
7593 case '#' :
7594 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7595 static ESCKEY_S choose_role[] = {
7596 {'r', 'r', "R", N_("Reply")},
7597 {'f', 'f', "F", N_("Forward")},
7598 {'b', 'b', "B", N_("Bounce")},
7599 {-1, 0, NULL, NULL}
7601 int action;
7602 ACTION_S *role = NULL;
7604 action = radio_buttons(_("Reply, Forward or Bounce using a role? "),
7605 -FOOTER_ROWS(state), choose_role,
7606 'r', 'x', h_role_aggregate, RB_NORM);
7607 if(action == 'r' || action == 'f' || action == 'b'){
7608 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
7610 redraw = state->redrawer;
7611 state->redrawer = NULL;
7612 prev_screen = state->prev_screen;
7613 role = NULL;
7614 state->next_screen = SCREEN_FUN_NULL;
7616 if(role_select_screen(state, &role,
7617 action == 'f' ? MC_FORWARD :
7618 action == 'r' ? MC_REPLY :
7619 action == 'b' ? MC_BOUNCE : 0) < 0){
7620 cmd_cancelled(action == 'f' ? _("Forward") :
7621 action == 'r' ? _("Reply") : _("Bounce"));
7622 state->next_screen = prev_screen;
7623 state->redrawer = redraw;
7624 state->mangled_screen = 1;
7626 else{
7627 if(role)
7628 role = combine_inherited_role(role);
7629 else{
7630 role = (ACTION_S *) fs_get(sizeof(*role));
7631 memset((void *) role, 0, sizeof(*role));
7632 role->nick = cpystr("Default Role");
7635 state->redrawer = NULL;
7636 switch(action){
7637 case 'r':
7638 (void) cmd_reply(state, msgmap, agg, role);
7639 break;
7641 case 'f':
7642 (void) cmd_forward(state, msgmap, agg, role);
7643 break;
7645 case 'b':
7646 (void) cmd_bounce(state, msgmap, agg, role);
7647 break;
7650 if(role)
7651 free_action(&role);
7653 if(redraw)
7654 (*redraw)();
7656 state->next_screen = prev_screen;
7657 state->redrawer = redraw;
7658 state->mangled_screen = 1;
7662 break;
7664 case 'z' : /* default */
7665 q_status_message(SM_INFO, 0, 2,
7666 "Cancelled, there is no default command");
7667 break;
7669 default:
7670 break;
7673 return(rv);
7678 * Select by message number ranges.
7679 * Sets searched bits in mail_elts
7681 * Args limitsrch -- limit search to this searchset
7683 * Returns 0 on success.
7686 select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
7688 int r, end, cur;
7689 long n1, n2, raw;
7690 char number1[16], number2[16], numbers[80], *p, *t;
7691 HelpType help;
7692 MESSAGECACHE *mc;
7694 numbers[0] = '\0';
7695 ps_global->mangled_footer = 1;
7696 help = NO_HELP;
7697 while(1){
7698 int flags = OE_APPEND_CURRENT;
7700 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7701 sizeof(numbers), _(select_num), NULL, help, &flags);
7702 if(r == 4)
7703 continue;
7705 if(r == 3){
7706 help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
7707 continue;
7710 for(t = p = numbers; *p ; p++) /* strip whitespace */
7711 if(!isspace((unsigned char)*p))
7712 *t++ = *p;
7714 *t = '\0';
7716 if(r == 1 || numbers[0] == '\0'){
7717 cmd_cancelled("Selection by number");
7718 return(1);
7720 else
7721 break;
7724 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7725 if((mc = mail_elt(stream, n1)) != NULL)
7726 mc->searched = 0; /* clear searched bits */
7728 for(p = numbers; *p ; p++){
7729 t = number1;
7730 while(*p && isdigit((unsigned char)*p))
7731 *t++ = *p++;
7733 *t = '\0';
7735 end = cur = 0;
7736 if(number1[0] == '\0'){
7737 if(*p == '-'){
7738 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7739 _("Invalid number range, missing number before \"-\": %s"),
7740 numbers);
7741 return(1);
7743 else if(!strucmp("end", p)){
7744 end = 1;
7745 p += strlen("end");
7747 else if(!strucmp("$", p)){
7748 end = 1;
7749 p++;
7751 else if(*p == '.'){
7752 cur = 1;
7753 p++;
7755 else{
7756 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7757 _("Invalid message number: %s"), numbers);
7758 return(1);
7762 if(end)
7763 n1 = mn_get_total(msgmap);
7764 else if(cur)
7765 n1 = mn_get_cur(msgmap);
7766 else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
7767 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7768 _("\"%s\" out of message number range"),
7769 long2string(n1));
7770 return(1);
7773 t = number2;
7774 if(*p == '-'){
7775 while(*++p && isdigit((unsigned char)*p))
7776 *t++ = *p;
7778 *t = '\0';
7780 end = cur = 0;
7781 if(number2[0] == '\0'){
7782 if(!strucmp("end", p)){
7783 end = 1;
7784 p += strlen("end");
7786 else if(!strucmp(p, "$")){
7787 end = 1;
7788 p++;
7790 else if(*p == '.'){
7791 cur = 1;
7792 p++;
7794 else{
7795 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7796 _("Invalid number range, missing number after \"-\": %s"),
7797 numbers);
7798 return(1);
7802 if(end)
7803 n2 = mn_get_total(msgmap);
7804 else if(cur)
7805 n2 = mn_get_cur(msgmap);
7806 else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){
7807 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7808 _("\"%s\" out of message number range"),
7809 long2string(n2));
7810 return(1);
7813 if(n2 <= n1){
7814 char t[20];
7816 strncpy(t, long2string(n1), sizeof(t));
7817 t[sizeof(t)-1] = '\0';
7818 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7819 _("Invalid reverse message number range: %s-%s"),
7820 t, long2string(n2));
7821 return(1);
7824 for(;n1 <= n2; n1++){
7825 raw = mn_m2raw(msgmap, n1);
7826 if(raw > 0L
7827 && (!(limitsrch && *limitsrch)
7828 || in_searchset(*limitsrch, (unsigned long) raw)))
7829 mm_searched(stream, raw);
7832 else{
7833 raw = mn_m2raw(msgmap, n1);
7834 if(raw > 0L
7835 && (!(limitsrch && *limitsrch)
7836 || in_searchset(*limitsrch, (unsigned long) raw)))
7837 mm_searched(stream, raw);
7840 if(*p == '\0')
7841 break;
7844 return(0);
7849 * Select by thread number ranges.
7850 * Sets searched bits in mail_elts
7852 * Args limitsrch -- limit search to this searchset
7854 * Returns 0 on success.
7857 select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset)
7859 int r, end, cur;
7860 long n1, n2;
7861 char number1[16], number2[16], numbers[80], *p, *t;
7862 HelpType help;
7863 PINETHRD_S *thrd = NULL, *th;
7864 MESSAGECACHE *mc;
7866 numbers[0] = '\0';
7867 ps_global->mangled_footer = 1;
7868 help = NO_HELP;
7869 while(1){
7870 int flags = OE_APPEND_CURRENT;
7872 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7873 sizeof(numbers), _(select_num), NULL, help, &flags);
7874 if(r == 4)
7875 continue;
7877 if(r == 3){
7878 help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP;
7879 continue;
7882 for(t = p = numbers; *p ; p++) /* strip whitespace */
7883 if(!isspace((unsigned char)*p))
7884 *t++ = *p;
7886 *t = '\0';
7888 if(r == 1 || numbers[0] == '\0'){
7889 cmd_cancelled("Selection by number");
7890 return(1);
7892 else
7893 break;
7896 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7897 if((mc = mail_elt(stream, n1)) != NULL)
7898 mc->searched = 0; /* clear searched bits */
7900 for(p = numbers; *p ; p++){
7901 t = number1;
7902 while(*p && isdigit((unsigned char)*p))
7903 *t++ = *p++;
7905 *t = '\0';
7907 end = cur = 0;
7908 if(number1[0] == '\0'){
7909 if(*p == '-'){
7910 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7911 _("Invalid number range, missing number before \"-\": %s"),
7912 numbers);
7913 return(1);
7915 else if(!strucmp("end", p)){
7916 end = 1;
7917 p += strlen("end");
7919 else if(!strucmp(p, "$")){
7920 end = 1;
7921 p++;
7923 else if(*p == '.'){
7924 cur = 1;
7925 p++;
7927 else{
7928 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7929 _("Invalid thread number: %s"), numbers);
7930 return(1);
7934 if(end)
7935 n1 = msgmap->max_thrdno;
7936 else if(cur){
7937 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7938 n1 = th->thrdno;
7940 else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){
7941 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7942 _("\"%s\" out of thread number range"),
7943 long2string(n1));
7944 return(1);
7947 t = number2;
7948 if(*p == '-'){
7950 while(*++p && isdigit((unsigned char)*p))
7951 *t++ = *p;
7953 *t = '\0';
7955 end = 0;
7956 if(number2[0] == '\0'){
7957 if(!strucmp("end", p)){
7958 end = 1;
7959 p += strlen("end");
7961 else if(!strucmp("$", p)){
7962 end = 1;
7963 p++;
7965 else if(*p == '.'){
7966 cur = 1;
7967 p++;
7969 else{
7970 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7971 _("Invalid number range, missing number after \"-\": %s"),
7972 numbers);
7973 return(1);
7977 if(end)
7978 n2 = msgmap->max_thrdno;
7979 else if(cur){
7980 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7981 n2 = th->thrdno;
7983 else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){
7984 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7985 _("\"%s\" out of thread number range"),
7986 long2string(n2));
7987 return(1);
7990 if(n2 <= n1){
7991 char t[20];
7993 strncpy(t, long2string(n1), sizeof(t));
7994 t[sizeof(t)-1] = '\0';
7995 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7996 _("Invalid reverse message number range: %s-%s"),
7997 t, long2string(n2));
7998 return(1);
8001 for(;n1 <= n2; n1++){
8002 thrd = find_thread_by_number(stream, msgmap, n1, thrd);
8004 if(thrd)
8005 set_search_bit_for_thread(stream, thrd, msgset);
8008 else{
8009 thrd = find_thread_by_number(stream, msgmap, n1, NULL);
8011 if(thrd)
8012 set_search_bit_for_thread(stream, thrd, msgset);
8015 if(*p == '\0')
8016 break;
8019 return(0);
8024 * Select by message dates.
8025 * Sets searched bits in mail_elts
8027 * Args limitsrch -- limit search to this searchset
8029 * Returns 0 on success.
8032 select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8034 int r, we_cancel = 0, when = 0;
8035 char date[100], defdate[100], prompt[128];
8036 time_t seldate = time(0);
8037 struct tm *seldate_tm;
8038 SEARCHPGM *pgm;
8039 HelpType help;
8040 static struct _tense {
8041 char *preamble,
8042 *range,
8043 *scope;
8044 } tense[] = {
8045 {"were ", "SENT SINCE", " (inclusive)"},
8046 {"were ", "SENT BEFORE", " (exclusive)"},
8047 {"were ", "SENT ON", "" },
8048 {"", "ARRIVED SINCE", " (inclusive)"},
8049 {"", "ARRIVED BEFORE", " (exclusive)"},
8050 {"", "ARRIVED ON", "" }
8053 date[0] = '\0';
8054 ps_global->mangled_footer = 1;
8055 help = NO_HELP;
8058 * If talking to an old server, default to SINCE instead of
8059 * SENTSINCE, which was added later.
8061 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8062 when = 3;
8064 while(1){
8065 int flags = OE_APPEND_CURRENT;
8067 seldate_tm = localtime(&seldate);
8068 snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday,
8069 month_abbrev(seldate_tm->tm_mon + 1),
8070 seldate_tm->tm_year + 1900);
8071 defdate[sizeof(defdate)-1] = '\0';
8072 snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ",
8073 tense[when].preamble, tense[when].range,
8074 tense[when].scope, defdate);
8075 prompt[sizeof(prompt)-1] = '\0';
8076 r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date),
8077 prompt, sel_date_opt, help, &flags);
8078 switch (r){
8079 case 1 :
8080 cmd_cancelled("Selection by date");
8081 return(1);
8083 case 3 :
8084 help = (help == NO_HELP) ? h_select_date : NO_HELP;
8085 continue;
8087 case 4 :
8088 continue;
8090 case 11 :
8092 MESSAGECACHE *mc;
8093 long rawno;
8095 if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L
8096 && rawno <= stream->nmsgs
8097 && (mc = mail_elt(stream, rawno))){
8099 /* cache not filled in yet? */
8100 if(mc->day == 0){
8101 char seq[20];
8103 if(stream->dtb && stream->dtb->flags & DR_NEWS){
8104 strncpy(seq,
8105 ulong2string(mail_uid(stream, rawno)),
8106 sizeof(seq));
8107 seq[sizeof(seq)-1] = '\0';
8108 mail_fetch_overview(stream, seq, NULL);
8110 else{
8111 strncpy(seq, long2string(rawno),
8112 sizeof(seq));
8113 seq[sizeof(seq)-1] = '\0';
8114 mail_fetch_fast(stream, seq, 0L);
8118 /* mail_date returns fixed field width date */
8119 mail_date(date, mc);
8120 date[11] = '\0';
8124 continue;
8126 case 12 : /* set default to PREVIOUS day */
8127 seldate -= 86400;
8128 continue;
8130 case 13 : /* set default to NEXT day */
8131 seldate += 86400;
8132 continue;
8134 case 14 :
8135 when = (when+1) % (sizeof(tense) / sizeof(struct _tense));
8136 continue;
8138 default:
8139 break;
8142 removing_leading_white_space(date);
8143 removing_trailing_white_space(date);
8144 if(!*date){
8145 strncpy(date, defdate, sizeof(date));
8146 date[sizeof(date)-1] = '\0';
8149 break;
8152 if((pgm = mail_newsearchpgm()) != NULL){
8153 MESSAGECACHE elt;
8154 short converted_date;
8156 if(mail_parse_date(&elt, (unsigned char *) date)){
8157 converted_date = mail_shortdate(elt.year, elt.month, elt.day);
8159 switch(when){
8160 case 0:
8161 pgm->sentsince = converted_date;
8162 break;
8163 case 1:
8164 pgm->sentbefore = converted_date;
8165 break;
8166 case 2:
8167 pgm->senton = converted_date;
8168 break;
8169 case 3:
8170 pgm->since = converted_date;
8171 break;
8172 case 4:
8173 pgm->before = converted_date;
8174 break;
8175 case 5:
8176 pgm->on = converted_date;
8177 break;
8180 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8182 if(ps_global && ps_global->ttyo){
8183 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8184 ps_global->mangled_footer = 1;
8187 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8189 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8191 if(we_cancel)
8192 cancel_busy_cue(0);
8194 /* we know this was freed in mail_search, let caller know */
8195 if(limitsrch)
8196 *limitsrch = NULL;
8198 else{
8199 mail_free_searchpgm(&pgm);
8200 q_status_message1(SM_ORDER, 3, 3,
8201 _("Invalid date entered: %s"), date);
8202 return(1);
8206 return(0);
8210 * Select by searching in message headers or body
8211 * using the x-gm-ext-1 capaility. This function
8212 * reads the input from the user and passes it to
8213 * the server directly. We need a x-gm-ext-1 variable
8214 * in the search pgm to carry this information to the
8215 * server.
8217 * Sets searched bits in mail_elts
8219 * Args limitsrch -- limit search to this searchset
8221 * Returns 0 on success.
8224 select_by_gm_content(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8226 int r, we_cancel = 0, rv;
8227 char tmp[128];
8228 char namehdr[MAILTMPLEN];
8229 ESCKEY_S ekey[8];
8230 HelpType help;
8232 ps_global->mangled_footer = 1;
8233 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8235 strncpy(tmp, sel_x_gm_ext, sizeof(tmp)-1);
8236 tmp[sizeof(tmp)-1] = '\0';
8238 namehdr[0] = '\0';
8240 help = NO_HELP;
8241 while (1){
8242 int flags = OE_APPEND_CURRENT;
8244 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8245 sizeof(namehdr), tmp, ekey, help, &flags);
8247 if(r == 4)
8248 continue;
8250 if(r == 3){
8251 help = (help == NO_HELP) ? h_select_by_gm_content : NO_HELP;
8252 continue;
8255 if (r == 1){
8256 cmd_cancelled("Selection by content");
8257 return(1);
8260 removing_leading_white_space(namehdr);
8262 if ((namehdr[0] != '\0')
8263 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8264 removing_trailing_white_space(namehdr);
8266 if (namehdr[0] != '\0')
8267 break;
8270 if(ps_global && ps_global->ttyo){
8271 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8272 ps_global->mangled_footer = 1;
8275 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8277 rv = agg_text_select(stream, msgmap, 'g', namehdr, 0, 0, NULL, "utf-8", limitsrch);
8278 if(we_cancel)
8279 cancel_busy_cue(0);
8281 return(rv);
8285 * Select by searching in message headers or body.
8286 * Sets searched bits in mail_elts
8288 * Args limitsrch -- limit search to this searchset
8290 * Returns 0 on success.
8293 select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8295 int r = '\0', ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
8296 int not = 0, me = 0;
8297 char sstring[512], tmp[128];
8298 char *p, *sval = NULL;
8299 char namehdr[80];
8300 ESCKEY_S ekey[8];
8301 ENVELOPE *env = NULL;
8302 HelpType help;
8303 unsigned flagsforhist = 0;
8304 static HISTORY_S *history = NULL;
8305 static char *recip = "RECIPIENTS";
8306 static char *partic = "PARTICIPANTS";
8307 static char *match_me = N_("[Match_My_Addresses]");
8308 static char *dont_match_me = N_("[Don't_Match_My_Addresses]");
8310 ps_global->mangled_footer = 1;
8311 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8313 while(1){
8314 type = radio_buttons(not ? _(sel_text_not) : _(sel_text),
8315 -FOOTER_ROWS(ps_global), sel_text_opt,
8316 's', 'x', NO_HELP, RB_NORM|RB_RET_HELP);
8318 if(type == '!')
8319 not = !not;
8320 else if(type == 3){
8321 helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS",
8322 HLPD_SIMPLE);
8323 ps_global->mangled_screen = 1;
8325 else
8326 break;
8330 * prepare some friendly defaults...
8332 switch(type){
8333 case 't' : /* address fields, offer To or From */
8334 case 'f' :
8335 case 'c' :
8336 case 'r' :
8337 case 'p' :
8338 sval = (type == 't') ? "TO" :
8339 (type == 'f') ? "FROM" :
8340 (type == 'c') ? "CC" :
8341 (type == 'r') ? recip : partic;
8342 ekey[ekeyi].ch = ctrl('T');
8343 ekey[ekeyi].name = "^T";
8344 ekey[ekeyi].rval = 10;
8345 /* TRANSLATORS: use Current To Address */
8346 ekey[ekeyi++].label = N_("Cur To");
8347 ekey[ekeyi].ch = ctrl('R');
8348 ekey[ekeyi].name = "^R";
8349 ekey[ekeyi].rval = 11;
8350 /* TRANSLATORS: use Current From Address */
8351 ekey[ekeyi++].label = N_("Cur From");
8352 ekey[ekeyi].ch = ctrl('W');
8353 ekey[ekeyi].name = "^W";
8354 ekey[ekeyi].rval = 12;
8355 /* TRANSLATORS: use Current Cc Address */
8356 ekey[ekeyi++].label = N_("Cur Cc");
8357 ekey[ekeyi].ch = ctrl('Y');
8358 ekey[ekeyi].name = "^Y";
8359 ekey[ekeyi].rval = 13;
8360 /* TRANSLATORS: Match Me means match my address */
8361 ekey[ekeyi++].label = N_("Match Me");
8362 ekey[ekeyi].ch = 0;
8363 ekey[ekeyi].name = "";
8364 ekey[ekeyi].rval = 0;
8365 ekey[ekeyi++].label = "";
8366 break;
8368 case 's' :
8369 sval = "SUBJECT";
8370 ekey[ekeyi].ch = ctrl('X');
8371 ekey[ekeyi].name = "^X";
8372 ekey[ekeyi].rval = 14;
8373 /* TRANSLATORS: use Current Subject */
8374 ekey[ekeyi++].label = N_("Cur Subject");
8375 break;
8377 case 'a' :
8378 sval = "TEXT";
8379 break;
8381 case 'b' :
8382 sval = "BODYTEXT";
8383 break;
8385 case 'h' :
8386 strncpy(tmp, "Name of HEADER to match : ", sizeof(tmp)-1);
8387 tmp[sizeof(tmp)-1] = '\0';
8388 flags = OE_APPEND_CURRENT;
8389 namehdr[0] = '\0';
8390 r = 'x';
8391 while (r == 'x'){
8392 int done = 0;
8394 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8395 sizeof(namehdr), tmp, ekey, NO_HELP, &flags);
8396 if (r == 1){
8397 cmd_cancelled("Selection by text");
8398 return(1);
8400 removing_leading_white_space(namehdr);
8401 while(!done){
8402 while ((namehdr[0] != '\0') && /* remove trailing ":" */
8403 (namehdr[strlen(namehdr) - 1] == ':'))
8404 namehdr[strlen(namehdr) - 1] = '\0';
8405 if ((namehdr[0] != '\0')
8406 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8407 removing_trailing_white_space(namehdr);
8408 else
8409 done++;
8411 if (strchr(namehdr,' ') || strchr(namehdr,'\t') ||
8412 strchr(namehdr,':'))
8413 namehdr[0] = '\0';
8414 if (namehdr[0] == '\0')
8415 r = 'x';
8417 sval = namehdr;
8418 break;
8420 case 'x':
8421 break;
8423 default:
8424 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
8425 return(1);
8428 ekey[ekeyi].ch = KEY_UP;
8429 ekey[ekeyi].rval = 30;
8430 ekey[ekeyi].name = "";
8431 ku = ekeyi;
8432 ekey[ekeyi++].label = "";
8434 ekey[ekeyi].ch = KEY_DOWN;
8435 ekey[ekeyi].rval = 31;
8436 ekey[ekeyi].name = "";
8437 ekey[ekeyi++].label = "";
8439 ekey[ekeyi].ch = -1;
8441 if(type != 'x'){
8443 init_hist(&history, HISTSIZE);
8445 if(ekey[0].ch > -1 && msgno > 0L
8446 && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),
8447 NULL)))
8448 ekey[0].ch = -1;
8450 sstring[0] = '\0';
8451 help = NO_HELP;
8452 r = type;
8453 while(r != 'x'){
8454 if(not)
8455 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8456 snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval);
8457 else
8458 snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval);
8460 if(items_in_hist(history) > 0){
8461 ekey[ku].name = HISTORY_UP_KEYNAME;
8462 ekey[ku].label = HISTORY_KEYLABEL;
8463 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
8464 ekey[ku+1].label = HISTORY_KEYLABEL;
8466 else{
8467 ekey[ku].name = "";
8468 ekey[ku].label = "";
8469 ekey[ku+1].name = "";
8470 ekey[ku+1].label = "";
8473 flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
8474 r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
8475 sizeof(sstring), tmp, ekey, help, &flags);
8477 if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me)))))
8478 me = 0;
8480 switch(r){
8481 case 3 :
8482 help = (help == NO_HELP)
8483 ? (not
8484 ? ((type == 'f') ? h_select_txt_not_from
8485 : (type == 't') ? h_select_txt_not_to
8486 : (type == 'c') ? h_select_txt_not_cc
8487 : (type == 's') ? h_select_txt_not_subj
8488 : (type == 'a') ? h_select_txt_not_all
8489 : (type == 'r') ? h_select_txt_not_recip
8490 : (type == 'p') ? h_select_txt_not_partic
8491 : (type == 'b') ? h_select_txt_not_body
8492 : NO_HELP)
8493 : ((type == 'f') ? h_select_txt_from
8494 : (type == 't') ? h_select_txt_to
8495 : (type == 'c') ? h_select_txt_cc
8496 : (type == 's') ? h_select_txt_subj
8497 : (type == 'a') ? h_select_txt_all
8498 : (type == 'r') ? h_select_txt_recip
8499 : (type == 'p') ? h_select_txt_partic
8500 : (type == 'b') ? h_select_txt_body
8501 : NO_HELP))
8502 : NO_HELP;
8504 case 4 :
8505 continue;
8507 case 10 : /* To: default */
8508 if(env && env->to && env->to->mailbox){
8509 snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox,
8510 env->to->host ? "@" : "",
8511 env->to->host ? env->to->host : "");
8512 sstring[sizeof(sstring)-1] = '\0';
8514 continue;
8516 case 11 : /* From: default */
8517 if(env && env->from && env->from->mailbox){
8518 snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox,
8519 env->from->host ? "@" : "",
8520 env->from->host ? env->from->host : "");
8521 sstring[sizeof(sstring)-1] = '\0';
8523 continue;
8525 case 12 : /* Cc: default */
8526 if(env && env->cc && env->cc->mailbox){
8527 snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox,
8528 env->cc->host ? "@" : "",
8529 env->cc->host ? env->cc->host : "");
8530 sstring[sizeof(sstring)-1] = '\0';
8532 continue;
8534 case 13 : /* Match my addresses */
8535 me++;
8536 snprintf(sstring, sizeof(sstring), "%s", not ? _(dont_match_me) : _(match_me));
8537 continue;
8539 case 14 : /* Subject: default */
8540 if(env && env->subject && env->subject[0]){
8541 char *q = NULL;
8543 q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
8544 SIZEOF_20KBUF, env->subject);
8545 snprintf(sstring, sizeof(sstring), "%s", q);
8546 sstring[sizeof(sstring)-1] = '\0';
8549 continue;
8551 case 30 :
8552 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8553 if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){
8554 strncpy(sstring, p, sizeof(sstring));
8555 sstring[sizeof(sstring)-1] = '\0';
8556 if(history->hist[history->curindex]){
8557 flagsforhist = history->hist[history->curindex]->flags;
8558 not = (flagsforhist & 0x1) ? 1 : 0;
8559 me = (flagsforhist & 0x2) ? 1 : 0;
8562 else
8563 Writechar(BELL, 0);
8565 continue;
8567 case 31 :
8568 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8569 if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){
8570 strncpy(sstring, p, sizeof(sstring));
8571 sstring[sizeof(sstring)-1] = '\0';
8572 if(history->hist[history->curindex]){
8573 flagsforhist = history->hist[history->curindex]->flags;
8574 not = (flagsforhist & 0x1) ? 1 : 0;
8575 me = (flagsforhist & 0x2) ? 1 : 0;
8578 else
8579 Writechar(BELL, 0);
8581 continue;
8583 default :
8584 break;
8587 if(r == 1 || sstring[0] == '\0')
8588 r = 'x';
8590 break;
8594 if(type == 'x' || r == 'x'){
8595 cmd_cancelled("Selection by text");
8596 return(1);
8599 if(ps_global && ps_global->ttyo){
8600 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8601 ps_global->mangled_footer = 1;
8604 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8606 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8607 save_hist(history, sstring, flagsforhist, NULL);
8609 rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch);
8610 if(we_cancel)
8611 cancel_busy_cue(0);
8613 return(rv);
8618 * Select by message size.
8619 * Sets searched bits in mail_elts
8621 * Args limitsrch -- limit search to this searchset
8623 * Returns 0 on success.
8626 select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch)
8628 int r, large = 1, we_cancel = 0;
8629 unsigned long n, mult = 1L, numerator = 0L, divisor = 1L;
8630 char size[16], numbers[80], *p, *t;
8631 HelpType help;
8632 SEARCHPGM *pgm;
8633 long flags = (SE_NOPREFETCH | SE_FREE);
8635 numbers[0] = '\0';
8636 ps_global->mangled_footer = 1;
8638 help = NO_HELP;
8639 while(1){
8640 int flgs = OE_APPEND_CURRENT;
8642 sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger;
8644 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
8645 sizeof(numbers), large ? _(select_size_larger_msg)
8646 : _(select_size_smaller_msg),
8647 sel_size_opt, help, &flgs);
8648 if(r == 4)
8649 continue;
8651 if(r == 14){
8652 large = 1 - large;
8653 continue;
8656 if(r == 3){
8657 help = (help == NO_HELP) ? (large ? h_select_by_larger_size
8658 : h_select_by_smaller_size)
8659 : NO_HELP;
8660 continue;
8663 for(t = p = numbers; *p ; p++) /* strip whitespace */
8664 if(!isspace((unsigned char)*p))
8665 *t++ = *p;
8667 *t = '\0';
8669 if(r == 1 || numbers[0] == '\0'){
8670 cmd_cancelled("Selection by size");
8671 return(1);
8673 else
8674 break;
8677 if(numbers[0] == '-'){
8678 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8679 _("Invalid size entered: %s"), numbers);
8680 return(1);
8683 t = size;
8684 p = numbers;
8686 while(*p && isdigit((unsigned char)*p))
8687 *t++ = *p++;
8689 *t = '\0';
8691 if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){
8692 size[0] = '0';
8693 size[1] = '\0';
8696 if(size[0] == '\0'){
8697 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8698 _("Invalid size entered: %s"), numbers);
8699 return(1);
8702 n = strtoul(size, (char **)NULL, 10);
8704 size[0] = '\0';
8705 if(*p == '.'){
8707 * We probably ought to just use atof() to convert 1.1 into a
8708 * double, but since we haven't used atof() anywhere else I'm
8709 * reluctant to use it because of portability concerns.
8711 p++;
8712 t = size;
8713 while(*p && isdigit((unsigned char)*p)){
8714 *t++ = *p++;
8715 divisor *= 10;
8718 *t = '\0';
8720 if(size[0])
8721 numerator = strtoul(size, (char **)NULL, 10);
8724 switch(*p){
8725 case 'g':
8726 case 'G':
8727 mult *= 1000;
8728 /* fall through */
8730 case 'm':
8731 case 'M':
8732 mult *= 1000;
8733 /* fall through */
8735 case 'k':
8736 case 'K':
8737 mult *= 1000;
8738 break;
8741 n = n * mult + (numerator * mult) / divisor;
8743 pgm = mail_newsearchpgm();
8744 if(large)
8745 pgm->larger = n;
8746 else
8747 pgm->smaller = n;
8749 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8750 flags |= SE_NOSERVER;
8752 if(ps_global && ps_global->ttyo){
8753 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8754 ps_global->mangled_footer = 1;
8757 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8759 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8760 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8761 /* we know this was freed in mail_search, let caller know */
8762 if(limitsrch)
8763 *limitsrch = NULL;
8765 if(we_cancel)
8766 cancel_busy_cue(0);
8768 return(0);
8773 * visible_searchset -- return c-client search set unEXLDed
8774 * sequence numbers
8776 SEARCHSET *
8777 visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap)
8779 long n, run;
8780 SEARCHSET *full_set = NULL, **set;
8783 * If we're talking to anything other than a server older than
8784 * imap 4rev1, build a searchset otherwise it'll choke.
8786 if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
8787 if(any_lflagged(msgmap, MN_EXLD)){
8788 for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
8789 if(get_lflag(stream, NULL, n, MN_EXLD)){
8790 if(run){ /* previous NOT excluded? */
8791 if(run > 1L)
8792 (*set)->last = n - 1L;
8794 set = &(*set)->next;
8795 run = 0L;
8798 else if(run++){ /* next in run */
8799 (*set)->last = n;
8801 else{ /* start of run */
8802 *set = mail_newsearchset();
8803 (*set)->first = n;
8806 else{
8807 full_set = mail_newsearchset();
8808 full_set->first = 1L;
8809 full_set->last = stream->nmsgs;
8813 return(full_set);
8818 * Select by message status bits.
8819 * Sets searched bits in mail_elts
8821 * Args limitsrch -- limit search to this searchset
8823 * Returns 0 on success.
8826 select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch)
8828 int s, not = 0, we_cancel = 0, rv;
8830 while(1){
8831 s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag),
8832 -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x',
8833 NO_HELP, RB_NORM|RB_RET_HELP);
8835 if(s == 'x'){
8836 cmd_cancelled("Selection by status");
8837 return(1);
8839 else if(s == 3){
8840 helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"),
8841 HLPD_SIMPLE);
8842 ps_global->mangled_screen = 1;
8844 else if(s == '!')
8845 not = !not;
8846 else
8847 break;
8850 if(ps_global && ps_global->ttyo){
8851 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8852 ps_global->mangled_footer = 1;
8855 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8856 rv = agg_flag_select(stream, not, s, limitsrch);
8857 if(we_cancel)
8858 cancel_busy_cue(0);
8860 return(rv);
8865 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8866 * Sets searched bits in mail_elts
8868 * Args limitsrch -- limit search to this searchset
8870 * Returns 0 on success.
8873 select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
8875 char rulenick[1000], *nick;
8876 PATGRP_S *patgrp;
8877 int r, last_r = 0, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
8878 | ROLE_DO_INCOLS
8879 | ROLE_DO_ROLES
8880 | ROLE_DO_SCORES
8881 | ROLE_DO_OTHER
8882 | ROLE_DO_FILTER;
8884 rulenick[0] = '\0';
8885 ps_global->mangled_footer = 1;
8887 if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
8888 sel_rule_opt[2].ch = TAB;
8889 sel_rule_opt[2].rval = 15;
8890 sel_rule_opt[2].name = "TAB";
8891 sel_rule_opt[2].label = N_("Complete");
8893 else{
8894 memset(&sel_rule_opt[2], 0, sizeof(sel_rule_opt[2]));
8898 int oe_flags;
8900 oe_flags = OE_APPEND_CURRENT;
8901 r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0,
8902 sizeof(rulenick),
8903 not ? _("Rule to NOT match: ")
8904 : _("Rule to match: "),
8905 sel_rule_opt, NO_HELP, &oe_flags);
8907 if(r == 14){
8908 /* select rulenick from a list */
8909 if((nick=choose_a_rule(rflags, NULL)) != NULL){
8910 strncpy(rulenick, nick, sizeof(rulenick)-1);
8911 rulenick[sizeof(rulenick)-1] = '\0';
8912 fs_give((void **) &nick);
8914 else
8915 r = 4;
8917 else if(r == 15){
8918 int n = rulenick_complete(rflags, rulenick, sizeof(rulenick));
8920 if(n > 1 && last_r == 15 && !(oe_flags & OE_USER_MODIFIED)){
8921 /* double tab with multiple completions: select from list */
8922 if((nick=choose_a_rule(rflags, rulenick)) != NULL){
8923 strncpy(rulenick, nick, sizeof(rulenick)-1);
8924 rulenick[sizeof(rulenick)-1] = '\0';
8925 fs_give((void **) &nick);
8926 r = 0;
8928 else
8929 r = 4;
8931 else if(n != 1)
8932 Writechar(BELL, 0);
8934 else if(r == '!')
8935 not = !not;
8937 if(r == 3){
8938 helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE);
8939 ps_global->mangled_screen = 1;
8941 else if(r == 1){
8942 cmd_cancelled("Selection by Rule");
8943 return(1);
8946 removing_leading_and_trailing_white_space(rulenick);
8947 last_r = r;
8949 }while(r == 3 || r == 4 || r == 15 || r == '!');
8953 * The approach of requiring a nickname instead of just allowing the
8954 * user to select from the list of rules has the drawback that a rule
8955 * may not have a nickname, or there may be more than one rule with
8956 * the same nickname. However, it has the benefit of allowing the user
8957 * to type in the nickname and, most importantly, allows us to set
8958 * up the ! (not). We could incorporate the ! into the selection
8959 * screen, but this is easier and also allows the typing of nicks.
8960 * User can just set up nicknames if they want to use this feature.
8962 patgrp = nick_to_patgrp(rulenick, rflags);
8964 if(patgrp){
8965 if(ps_global && ps_global->ttyo){
8966 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8967 ps_global->mangled_footer = 1;
8970 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8971 match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL,
8972 get_msg_score,
8973 (not ? MP_NOT : 0) | SE_NOPREFETCH);
8974 free_patgrp(&patgrp);
8975 if(we_cancel)
8976 cancel_busy_cue(0);
8979 if(limitsrch && *limitsrch){
8980 mail_free_searchset(limitsrch);
8981 *limitsrch = NULL;
8984 return(0);
8989 * Allow user to choose a rule from their list of rules.
8991 * Args rflags -- Pattern types to choose from
8992 * prefix -- Only list rules matching the given prefix
8994 * Returns an allocated rule nickname on success, NULL otherwise.
8996 char *
8997 choose_a_rule(int rflags, char *prefix)
8999 char *choice = NULL;
9000 char **rule_list, **lp;
9001 int cnt = 0;
9002 int prefix_length = 0;
9003 PAT_S *pat;
9004 PAT_STATE pstate;
9005 void (*redraw)(void) = ps_global->redrawer;
9007 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){
9008 q_status_message(SM_ORDER, 3, 3,
9009 _("No rules available. Use Setup/Rules to add some."));
9010 return(choice);
9014 * Build a list of rules to choose from.
9017 if(prefix)
9018 prefix_length = strlen(prefix);
9020 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)) {
9021 if(!pat->patgrp || !pat->patgrp->nick)
9022 continue;
9023 if(!prefix || strncmp(pat->patgrp->nick, prefix, prefix_length) == 0)
9024 cnt++;
9027 if(cnt <= 0){
9028 q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
9029 return(choice);
9032 lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
9033 memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
9035 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)) {
9036 if(!pat->patgrp || !pat->patgrp->nick)
9037 continue;
9038 if(!prefix || strncmp(pat->patgrp->nick, prefix, prefix_length) == 0)
9039 *lp++ = cpystr(pat->patgrp->nick);
9042 /* TRANSLATORS: SELECT A RULE is a screen title
9043 TRANSLATORS: Print something1 using something2.
9044 "rules" is something1 */
9045 choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"),
9046 _("rules"), h_select_rule_screen,
9047 _("HELP FOR SELECTING A RULE NICKNAME"), NULL);
9049 if(!choice){
9050 q_status_message(SM_ORDER, 1, 4, "No choice");
9051 ps_global->redrawer = redraw;
9054 free_list_array(&rule_list);
9056 return(choice);
9061 * Complete a partial rule name against the user's list of rules.
9063 * Args rflags -- Pattern types to choose from
9064 * nick -- Current rule nickname to complete
9065 * nick_size -- Maximum length of the nick array to store completion in
9067 * Returns the number of valid completions, i.e.:
9069 * 0 if there are no valid completions. The value of the nick argument has not
9070 * been changed.
9071 * 1 if there is only a single valid completion. The value of the nick argument
9072 * has been replaced with the full text of that completion.
9073 * >1 if there is more than one valid completion. The value of the nick argument
9074 * has been replaced with the longest substring common to all the appropriate
9075 * completions.
9078 rulenick_complete(int rflags, char *nick, int nick_size)
9080 char *candidate = NULL;
9081 int cnt = 0;
9082 int common_prefix_length = 0;
9083 int nick_length;
9084 PAT_S *pat;
9085 PAT_STATE pstate;
9087 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate)))
9088 return 0;
9090 nick_length = strlen(nick);
9092 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){
9093 if(!pat->patgrp || !pat->patgrp->nick)
9094 continue;
9095 if(strncmp(pat->patgrp->nick, nick, nick_length) == 0){
9096 /* This is a candidate for completion. */
9097 cnt++;
9098 if(!candidate){
9099 /* This is the first candidate. Keep it as a future reference to
9100 * compare against to find the longest common prefix length of
9101 * all the matches. */
9102 candidate = pat->patgrp->nick;
9103 common_prefix_length = strlen(pat->patgrp->nick);
9105 else{
9106 /* Find the common prefix length between the first candidate and
9107 * this one. */
9108 int i;
9109 for(i = 0; i < common_prefix_length; i++){
9110 if(pat->patgrp->nick[i] != candidate[i]){
9111 /* In the event that we ended up in the middle of a
9112 * UTF-8 code point, backtrack to the byte before the
9113 * start of this code point. */
9114 while(i > 0 && (candidate[i] & 0xC0) == 0x80)
9115 i--;
9116 common_prefix_length = i;
9117 break;
9124 if(cnt > 0){
9125 int length = MIN(nick_size, common_prefix_length);
9126 strncpy(nick + nick_length, candidate + nick_length, length - nick_length);
9127 nick[length] = '\0';
9130 return cnt;
9135 * Select by current thread.
9136 * Sets searched bits in mail_elts for this entire thread
9138 * Args limitsrch -- limit search to this searchset
9140 * Returns 0 on success.
9143 select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
9145 long n;
9146 PINETHRD_S *thrd = NULL;
9147 int ret = 1;
9148 MESSAGECACHE *mc;
9150 if(!stream)
9151 return(ret);
9153 for(n = 1L; n <= stream->nmsgs; n++)
9154 if((mc = mail_elt(stream, n)) != NULL)
9155 mc->searched = 0; /* clear searched bits */
9157 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
9158 if(thrd && thrd->top && thrd->top != thrd->rawno)
9159 thrd = fetch_thread(stream, thrd->top);
9162 * This doesn't unselect if the thread is already selected
9163 * (like select current does), it always selects.
9164 * There is no way to select ! this thread.
9166 if(thrd){
9167 set_search_bit_for_thread(stream, thrd, limitsrch);
9168 ret = 0;
9171 return(ret);
9176 * Select by message keywords.
9177 * Sets searched bits in mail_elts
9179 * Args limitsrch -- limit search to this searchset
9181 * Returns 0 on success.
9184 select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
9186 int r, last_r = 0, not = 0, we_cancel = 0;
9187 char keyword[MAXUSERFLAG+1], *kword;
9188 char *error = NULL, *p, *prompt;
9189 HelpType help;
9190 SEARCHPGM *pgm;
9192 keyword[0] = '\0';
9193 ps_global->mangled_footer = 1;
9195 if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
9196 sel_key_opt[2].ch = TAB;
9197 sel_key_opt[2].rval = 15;
9198 sel_key_opt[2].name = "TAB";
9199 sel_key_opt[2].label = N_("Complete");
9201 else{
9202 memset(&sel_key_opt[2], 0, sizeof(sel_key_opt[2]));
9205 help = NO_HELP;
9207 int oe_flags;
9209 if(error){
9210 q_status_message(SM_ORDER, 3, 4, error);
9211 fs_give((void **) &error);
9214 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
9215 if(not)
9216 prompt = _("Keyword (or keyword initial) to NOT match: ");
9217 else
9218 prompt = _("Keyword (or keyword initial) to match: ");
9220 else{
9221 if(not)
9222 prompt = _("Keyword to NOT match: ");
9223 else
9224 prompt = _("Keyword to match: ");
9227 oe_flags = OE_APPEND_CURRENT;
9228 r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0,
9229 sizeof(keyword),
9230 prompt, sel_key_opt, help, &oe_flags);
9232 if(r == 14){
9233 /* select keyword from a list */
9234 if((kword=choose_a_keyword(NULL)) != NULL){
9235 strncpy(keyword, kword, sizeof(keyword)-1);
9236 keyword[sizeof(keyword)-1] = '\0';
9237 fs_give((void **) &kword);
9239 else
9240 r = 4;
9242 else if(r == 15){
9243 int n = keyword_complete(keyword, sizeof(keyword));
9245 if(n > 1 && last_r == 15 && !(oe_flags & OE_USER_MODIFIED)){
9246 /* double tab with multiple completions: select from list */
9247 if((kword=choose_a_keyword(keyword)) != NULL){
9248 strncpy(keyword, kword, sizeof(keyword)-1);
9249 keyword[sizeof(keyword)-1] = '\0';
9250 fs_give((void **) &kword);
9251 r = 0;
9253 else
9254 r = 4;
9256 else if(n != 1)
9257 Writechar(BELL, 0);
9259 else if(r == '!')
9260 not = !not;
9262 if(r == 3)
9263 help = help == NO_HELP ? h_select_keyword : NO_HELP;
9264 else if(r == 1){
9265 cmd_cancelled("Selection by keyword");
9266 return(1);
9269 removing_leading_and_trailing_white_space(keyword);
9270 last_r = r;
9272 }while(r == 3 || r == 4 || r == 15 || r == '!' || keyword_check(keyword, &error));
9275 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
9276 p = initial_to_keyword(keyword);
9277 if(p != keyword){
9278 strncpy(keyword, p, sizeof(keyword)-1);
9279 keyword[sizeof(keyword)-1] = '\0';
9284 * We want to check the keyword, not the nickname of the keyword,
9285 * so convert it to the keyword if necessary.
9287 p = nick_to_keyword(keyword);
9288 if(p != keyword){
9289 strncpy(keyword, p, sizeof(keyword)-1);
9290 keyword[sizeof(keyword)-1] = '\0';
9293 pgm = mail_newsearchpgm();
9294 if(not){
9295 pgm->unkeyword = mail_newstringlist();
9296 pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword);
9297 pgm->unkeyword->text.size = strlen(keyword);
9299 else{
9300 pgm->keyword = mail_newstringlist();
9301 pgm->keyword->text.data = (unsigned char *) cpystr(keyword);
9302 pgm->keyword->text.size = strlen(keyword);
9305 if(ps_global && ps_global->ttyo){
9306 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9307 ps_global->mangled_footer = 1;
9310 we_cancel = busy_cue(_("Selecting"), NULL, 1);
9312 pgm->msgno = (limitsrch ? *limitsrch : NULL);
9313 pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE);
9314 /* we know this was freed in mail_search, let caller know */
9315 if(limitsrch)
9316 *limitsrch = NULL;
9318 if(we_cancel)
9319 cancel_busy_cue(0);
9321 return(0);
9326 * Allow user to choose a keyword from their list of keywords.
9328 * Args prefix -- Only list keywords matching the given prefix
9330 * Returns an allocated keyword on success, NULL otherwise.
9332 char *
9333 choose_a_keyword(char *prefix)
9335 char *choice = NULL;
9336 char **keyword_list, **lp;
9337 int cnt;
9338 int prefix_length = 0;
9339 KEYWORD_S *kw;
9340 void (*redraw)(void) = ps_global->redrawer;
9343 * Build a list of keywords to choose from.
9346 if(prefix)
9347 prefix_length = strlen(prefix);
9349 for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next){
9350 char *kw_name = kw->nick ? kw->nick : kw->kw;
9351 if(!prefix || kw_name && strncmp(kw_name, prefix, prefix_length) == 0)
9352 cnt++;
9355 if(cnt <= 0){
9356 q_status_message(SM_ORDER, 3, 4,
9357 _("No keywords defined, use \"keywords\" option in Setup/Config"));
9358 return(choice);
9361 lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list));
9362 memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list));
9364 for(kw = ps_global->keywords; kw; kw = kw->next){
9365 char *kw_name = kw->nick ? kw->nick : kw->kw;
9366 if(!kw_name)
9367 continue;
9368 if(!prefix || kw_name && strncmp(kw_name, prefix, prefix_length) == 0)
9369 *lp++ = cpystr(kw_name);
9372 /* TRANSLATORS: SELECT A KEYWORD is a screen title
9373 TRANSLATORS: Print something1 using something2.
9374 "keywords" is something1 */
9375 choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"),
9376 _("keywords"), h_select_keyword_screen,
9377 _("HELP FOR SELECTING A KEYWORD"), NULL);
9379 if(!choice){
9380 q_status_message(SM_ORDER, 1, 4, "No choice");
9381 ps_global->redrawer = redraw;
9384 free_list_array(&keyword_list);
9386 return(choice);
9391 * Complete a partial keyword name against the user's list of keywords.
9393 * Args keyword -- Current keyword to complete
9394 * keyword_size -- Maximum length of the keyword array to store
9395 * completion in
9397 * Returns the number of valid completions, i.e.:
9399 * 0 if there are no valid completions. The value of the keyword argument has
9400 * not been changed.
9401 * 1 if there is only a single valid completion. The value of the keyword
9402 * argument has been replaced with the full text of that completion.
9403 * >1 if there is more than one valid completion. The value of the keyword
9404 * argument has been replaced with the longest substring common to all the
9405 * appropriate completions.
9408 keyword_complete(char *keyword, int keyword_size)
9410 char *candidate = NULL;
9411 int cnt = 0;
9412 int common_prefix_length = 0;
9413 int keyword_length;
9414 KEYWORD_S *kw;
9416 keyword_length = strlen(keyword);
9418 for(kw = ps_global->keywords; kw; kw = kw->next){
9419 char *kw_name = kw->nick ? kw->nick : kw->kw;
9420 if(!kw_name)
9421 continue;
9422 if(strncmp(kw_name, keyword, keyword_length) == 0){
9423 /* This is a candidate for completion. */
9424 cnt++;
9425 if(!candidate){
9426 /* This is the first candidate. Keep it as a future reference to
9427 * compare against to find the longest common prefix length of
9428 * all the matches. */
9429 candidate = kw_name;
9430 common_prefix_length = strlen(candidate);
9432 else{
9433 /* Find the common prefix length between the first candidate and
9434 * this one. */
9435 int i;
9436 for(i = 0; i < common_prefix_length; i++){
9437 if(kw_name[i] != candidate[i]){
9438 /* In the event that we ended up in the middle of a
9439 * UTF-8 code point, backtrack to the byte before the
9440 * start of this code point. */
9441 while(i > 0 && (candidate[i] & 0xC0) == 0x80)
9442 i--;
9443 common_prefix_length = i;
9444 break;
9451 if(cnt > 0){
9452 int length = MIN(keyword_size, common_prefix_length);
9453 strncpy(keyword + keyword_length, candidate + keyword_length, length - keyword_length);
9454 keyword[length] = '\0';
9457 return cnt;
9462 * Allow user to choose a list of keywords from their list of keywords.
9464 * Returns allocated list.
9466 char **
9467 choose_list_of_keywords(void)
9469 LIST_SEL_S *listhead, *ls, *p;
9470 char **ret = NULL;
9471 int cnt, i;
9472 KEYWORD_S *kw;
9475 * Build a list of keywords to choose from.
9478 p = listhead = NULL;
9479 for(kw = ps_global->keywords; kw; kw = kw->next){
9481 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9482 memset(ls, 0, sizeof(*ls));
9483 ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9485 if(p){
9486 p->next = ls;
9487 p = p->next;
9489 else
9490 listhead = p = ls;
9493 if(!listhead)
9494 return(ret);
9496 /* TRANSLATORS: SELECT KEYWORDS is a screen title
9497 Print something1 using something2.
9498 "keywords" is something1 */
9499 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9500 _("SELECT KEYWORDS"), _("keywords"),
9501 h_select_multkeyword_screen,
9502 _("HELP FOR SELECTING KEYWORDS"), NULL)){
9503 for(cnt = 0, p = listhead; p; p = p->next)
9504 if(p->selected)
9505 cnt++;
9507 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9508 memset(ret, 0, (cnt+1) * sizeof(*ret));
9509 for(i = 0, p = listhead; p; p = p->next)
9510 if(p->selected)
9511 ret[i++] = cpystr(p->item ? p->item : "");
9514 free_list_sel(&listhead);
9516 return(ret);
9521 * Allow user to choose a charset
9523 * Returns an allocated charset on success, NULL otherwise.
9525 char *
9526 choose_a_charset(int which_charsets)
9528 char *choice = NULL;
9529 char **charset_list, **lp;
9530 const CHARSET *cs;
9531 int cnt;
9532 void (*redraw)(void) = ps_global->redrawer;
9535 * Build a list of charsets to choose from.
9538 for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){
9539 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9540 && ((which_charsets & CAC_ALL)
9541 || (which_charsets & CAC_POSTING
9542 && cs->flags & CF_POSTING)
9543 || (which_charsets & CAC_DISPLAY
9544 && cs->type != CT_2022
9545 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9546 cnt++;
9549 if(cnt <= 0){
9550 q_status_message(SM_ORDER, 3, 4,
9551 _("No charsets found? Enter charset manually."));
9552 return(choice);
9555 lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list));
9556 memset(charset_list, 0, (cnt+1) * sizeof(*charset_list));
9558 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9559 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9560 && ((which_charsets & CAC_ALL)
9561 || (which_charsets & CAC_POSTING
9562 && cs->flags & CF_POSTING)
9563 || (which_charsets & CAC_DISPLAY
9564 && cs->type != CT_2022
9565 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9566 *lp++ = cpystr(cs->name);
9569 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
9570 TRANSLATORS: Print something1 using something2.
9571 "character sets" is something1 */
9572 choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"),
9573 _("character sets"), h_select_charset_screen,
9574 _("HELP FOR SELECTING A CHARACTER SET"), NULL);
9576 if(!choice){
9577 q_status_message(SM_ORDER, 1, 4, "No choice");
9578 ps_global->redrawer = redraw;
9581 free_list_array(&charset_list);
9583 return(choice);
9588 * Allow user to choose a list of character sets and/or scripts
9590 * Returns allocated list.
9592 char **
9593 choose_list_of_charsets(void)
9595 LIST_SEL_S *listhead, *ls, *p;
9596 char **ret = NULL;
9597 int cnt, i, got_one;
9598 const CHARSET *cs;
9599 SCRIPT *s;
9600 char *q, *t;
9601 long width, limit;
9602 char buf[1024], *folded;
9605 * Build a list of charsets to choose from.
9608 p = listhead = NULL;
9610 /* this width is determined by select_from_list_screen() */
9611 width = ps_global->ttyo->screen_cols - 4;
9613 /* first comes a list of scripts (sets of character sets) */
9614 for(s = utf8_script(NIL); s && s->name; s++){
9616 limit = sizeof(buf)-1;
9617 q = buf;
9618 memset(q, 0, limit+1);
9620 if(s->name)
9621 sstrncpy(&q, s->name, limit);
9623 if(s->description){
9624 sstrncpy(&q, " (", limit-(q-buf));
9625 sstrncpy(&q, s->description, limit-(q-buf));
9626 sstrncpy(&q, ")", limit-(q-buf));
9629 /* add the list of charsets that are in this script */
9630 got_one = 0;
9631 for(cs = utf8_charset(NIL);
9632 cs && cs->name && (q-buf) < limit; cs++){
9633 if(cs->script & s->script){
9635 * Filter out some un-useful members of the list.
9636 * UTF-7 and UTF-8 weren't actually in the list at the
9637 * time this was written. Just making sure.
9639 if(!strucmp(cs->name, "ISO-2022-JP-2")
9640 || !strucmp(cs->name, "UTF-7")
9641 || !strucmp(cs->name, "UTF-8"))
9642 continue;
9644 if(got_one)
9645 sstrncpy(&q, " ", limit-(q-buf));
9646 else{
9647 got_one = 1;
9648 sstrncpy(&q, " {", limit-(q-buf));
9651 sstrncpy(&q, cs->name, limit-(q-buf));
9655 if(got_one)
9656 sstrncpy(&q, "}", limit-(q-buf));
9658 /* fold this line so that it can all be seen on the screen */
9659 folded = fold(buf, width, width, "", " ", FLD_NONE);
9660 if(folded){
9661 t = folded;
9662 while(t && *t && (q = strindex(t, '\n')) != NULL){
9663 *q = '\0';
9665 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9666 memset(ls, 0, sizeof(*ls));
9667 if(t == folded)
9668 ls->item = cpystr(s->name);
9669 else
9670 ls->flags = SFL_NOSELECT;
9672 ls->display_item = cpystr(t);
9674 t = q+1;
9676 if(p){
9677 p->next = ls;
9678 p = p->next;
9680 else{
9681 /* add a heading */
9682 listhead = (LIST_SEL_S *) fs_get(sizeof(*ls));
9683 memset(listhead, 0, sizeof(*listhead));
9684 listhead->flags = SFL_NOSELECT;
9685 listhead->display_item =
9686 cpystr(_("Scripts representing groups of related character sets"));
9687 listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls));
9688 memset(listhead->next, 0, sizeof(*listhead));
9689 listhead->next->flags = SFL_NOSELECT;
9690 listhead->next->display_item =
9691 cpystr(repeat_char(width, '-'));
9693 listhead->next->next = ls;
9694 p = ls;
9698 fs_give((void **) &folded);
9702 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9703 memset(ls, 0, sizeof(*ls));
9704 ls->flags = SFL_NOSELECT;
9705 if(p){
9706 p->next = ls;
9707 p = p->next;
9709 else
9710 listhead = p = ls;
9712 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9713 memset(ls, 0, sizeof(*ls));
9714 ls->flags = SFL_NOSELECT;
9715 ls->display_item =
9716 cpystr(_("Individual character sets, may be mixed with scripts"));
9717 p->next = ls;
9718 p = p->next;
9720 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9721 memset(ls, 0, sizeof(*ls));
9722 ls->flags = SFL_NOSELECT;
9723 ls->display_item =
9724 cpystr(repeat_char(width, '-'));
9725 p->next = ls;
9726 p = p->next;
9728 /* then comes a list of individual character sets */
9729 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9730 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9731 memset(ls, 0, sizeof(*ls));
9732 ls->item = cpystr(cs->name);
9734 if(p){
9735 p->next = ls;
9736 p = p->next;
9738 else
9739 listhead = p = ls;
9742 if(!listhead)
9743 return(ret);
9745 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9746 Print something1 using something2.
9747 "character sets" is something1 */
9748 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9749 _("SELECT CHARACTER SETS"), _("character sets"),
9750 h_select_multcharsets_screen,
9751 _("HELP FOR SELECTING CHARACTER SETS"), NULL)){
9752 for(cnt = 0, p = listhead; p; p = p->next)
9753 if(p->selected)
9754 cnt++;
9756 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9757 memset(ret, 0, (cnt+1) * sizeof(*ret));
9758 for(i = 0, p = listhead; p; p = p->next)
9759 if(p->selected)
9760 ret[i++] = cpystr(p->item ? p->item : "");
9763 free_list_sel(&listhead);
9765 return(ret);
9768 /* Report quota summary resources in an IMAP server */
9770 void
9771 cmd_quota (struct pine *state)
9773 QUOTALIST *imapquota;
9774 NETMBX mb;
9775 STORE_S *store;
9776 SCROLL_S sargs;
9778 if(!state->mail_stream || !is_imap_stream(state->mail_stream)){
9779 q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders");
9780 return;
9783 if (state->mail_stream
9784 && !sp_dead_stream(state->mail_stream)
9785 && state->mail_stream->mailbox
9786 && *state->mail_stream->mailbox
9787 && mail_valid_net_parse(state->mail_stream->mailbox, &mb))
9788 imap_getquotaroot(state->mail_stream, mb.mailbox);
9790 if(!state->quota) /* failed ? */
9791 return; /* go back... */
9793 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
9794 q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space.");
9795 return;
9798 so_puts(store, "Quota Report for ");
9799 so_puts(store, state->mail_stream->original_mailbox);
9800 so_puts(store, "\n\n");
9802 for (imapquota = state->quota; imapquota; imapquota = imapquota->next){
9804 so_puts(store, _("Resource : "));
9805 so_puts(store, imapquota->name);
9806 so_writec('\n', store);
9808 so_puts(store, _("Usage : "));
9809 so_puts(store, long2string(imapquota->usage));
9810 if(!strucmp(imapquota->name,"STORAGE"))
9811 so_puts(store, " KiB ");
9812 if(!strucmp(imapquota->name,"MESSAGE")){
9813 so_puts(store, _(" message"));
9814 if(imapquota->usage != 1)
9815 so_puts(store, _("s ")); /* plural */
9816 else
9817 so_puts(store, _(" "));
9819 so_writec('(', store);
9820 so_puts(store, long2string(100*imapquota->usage/imapquota->limit));
9821 so_puts(store, "%)\n");
9823 so_puts(store, _("Limit : "));
9824 so_puts(store, long2string(imapquota->limit));
9825 if(!strucmp(imapquota->name,"STORAGE"))
9826 so_puts(store, " KiB\n\n");
9827 if(!strucmp(imapquota->name,"MESSAGE")){
9828 so_puts(store, _(" message"));
9829 if(imapquota->usage != 1)
9830 so_puts(store, _("s\n\n")); /* plural */
9831 else
9832 so_puts(store, _("\n\n"));
9836 memset(&sargs, 0, sizeof(SCROLL_S));
9837 sargs.text.text = so_text(store);
9838 sargs.text.src = CharStar;
9839 sargs.text.desc = _("Quota Resources Summary");
9840 sargs.bar.title = _("QUOTA SUMMARY");
9841 sargs.proc.tool = NULL;
9842 sargs.help.text = h_quota_command;
9843 sargs.help.title = NULL;
9844 sargs.keys.menu = NULL;
9845 setbitmap(sargs.keys.bitmap);
9847 scrolltool(&sargs);
9848 so_give(&store);
9850 if (state->quota)
9851 mail_free_quotalist(&(state->quota));
9854 /*----------------------------------------------------------------------
9855 Prompt the user for the type of sort he desires
9857 Args: state -- pine state pointer
9858 q1 -- Line to prompt on
9860 Returns 0 if it was cancelled, 1 otherwise.
9861 ----*/
9863 select_sort(struct pine *state, int ql, SortOrder *sort, int *rev)
9865 char prompt[200], tmp[3], *p;
9866 int s, i;
9867 int deefault = 'a', retval = 1;
9868 HelpType help;
9869 ESCKEY_S sorts[14];
9871 #ifdef _WINDOWS
9872 DLG_SORTPARAM sortsel;
9874 if (mswin_usedialog ()) {
9876 sortsel.reverse = mn_get_revsort (state->msgmap);
9877 sortsel.cursort = mn_get_sort (state->msgmap);
9878 /* assumption here that HelpType is char ** */
9879 sortsel.helptext = h_select_sort;
9880 sortsel.rval = 0;
9882 if ((retval = os_sortdialog (&sortsel))) {
9883 *sort = sortsel.cursort;
9884 *rev = sortsel.reverse;
9887 return (retval);
9889 #endif
9891 /*----- String together the prompt ------*/
9892 tmp[1] = '\0';
9893 if(F_ON(F_USE_FK,ps_global))
9894 strncpy(prompt, _("Choose type of sort : "), sizeof(prompt));
9895 else
9896 strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "),
9897 sizeof(prompt));
9899 for(i = 0; state->sort_types[i] != EndofList; i++) {
9900 sorts[i].rval = i;
9901 p = sorts[i].label = sort_name(state->sort_types[i]);
9902 while(*(p+1) && islower((unsigned char)*p))
9903 p++;
9905 sorts[i].ch = tolower((unsigned char)(tmp[0] = *p));
9906 sorts[i].name = cpystr(tmp);
9908 if(mn_get_sort(state->msgmap) == state->sort_types[i])
9909 deefault = sorts[i].rval;
9912 sorts[i].ch = 'r';
9913 sorts[i].rval = 'r';
9914 sorts[i].name = cpystr("R");
9915 if(F_ON(F_USE_FK,ps_global))
9916 sorts[i].label = N_("Reverse");
9917 else
9918 sorts[i].label = "";
9920 sorts[++i].ch = -1;
9921 help = h_select_sort;
9923 if((F_ON(F_USE_FK,ps_global)
9924 && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x',
9925 help,RB_NORM)) != 'x'))
9927 (F_OFF(F_USE_FK,ps_global)
9928 && ((s = radio_buttons(prompt,ql,sorts,deefault,'x',
9929 help,RB_NORM)) != 'x'))){
9930 state->mangled_body = 1; /* signal screen's changed */
9931 if(s == 'r')
9932 *rev = !mn_get_revsort(state->msgmap);
9933 else
9934 *sort = state->sort_types[s];
9936 if(F_ON(F_SHOW_SORT, ps_global))
9937 ps_global->mangled_header = 1;
9939 else{
9940 retval = 0;
9941 cmd_cancelled("Sort");
9944 while(--i >= 0)
9945 fs_give((void **)&sorts[i].name);
9947 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9948 return(retval);
9952 /*---------------------------------------------------------------------
9953 Build list of folders in the given context for user selection
9955 Args: c -- pointer to pointer to folder's context context
9956 f -- folder prefix to display
9957 sublist -- whether or not to use 'f's contents as prefix
9958 lister -- function used to do the actual display
9960 Returns: malloc'd string containing sequence, else NULL if
9961 no messages in msgmap with local "selected" flag.
9962 ----*/
9964 display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int))
9966 int rc;
9967 CONTEXT_S *tc;
9968 void (*redraw)(void) = ps_global->redrawer;
9970 push_titlebar_state();
9971 tc = *c;
9972 if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0)
9973 *c = tc;
9975 ClearScreen();
9976 pop_titlebar_state();
9977 redraw_titlebar();
9978 if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
9979 (*ps_global->redrawer)();
9981 if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
9982 return(1);
9984 return(0);
9989 * Allow user to choose a single item from a list of strings.
9991 * Args list -- Array of strings to choose from, NULL terminated.
9992 * displist -- Array of strings to display instead of displaying list.
9993 * Indices correspond to the list array. Display the displist
9994 * but return the item from list if displist non-NULL.
9995 * title -- For conf_scroll_screen
9996 * pdesc -- For conf_scroll_screen
9997 * help -- For conf_scroll_screen
9998 * htitle -- For conf_scroll_screen
10000 * Returns an allocated copy of the chosen item or NULL.
10002 char *
10003 choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help,
10004 char *htitle, char *cursor_location)
10006 LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL;
10007 char **t, **dl;
10008 char *ret = NULL, *choice = NULL;
10010 /* build the LIST_SEL_S list */
10011 p = listhead = NULL;
10012 for(t = list, dl = displist; *t; t++, dl++){
10013 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
10014 memset(ls, 0, sizeof(*ls));
10015 ls->item = cpystr(*t);
10016 if(displist)
10017 ls->display_item = cpystr(*dl);
10019 if(cursor_location && (cursor_location == (*t)))
10020 starting_val = ls;
10022 if(p){
10023 p->next = ls;
10024 p = p->next;
10026 else
10027 listhead = p = ls;
10030 if(!listhead)
10031 return(ret);
10033 if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc,
10034 help, htitle, starting_val))
10035 for(p = listhead; !choice && p; p = p->next)
10036 if(p->selected)
10037 choice = p->item;
10039 if(choice)
10040 ret = cpystr(choice);
10042 free_list_sel(&listhead);
10044 return(ret);
10048 void
10049 free_list_sel(LIST_SEL_S **lsel)
10051 if(lsel && *lsel){
10052 free_list_sel(&(*lsel)->next);
10053 if((*lsel)->item)
10054 fs_give((void **) &(*lsel)->item);
10056 if((*lsel)->display_item)
10057 fs_give((void **) &(*lsel)->display_item);
10059 fs_give((void **) lsel);
10065 * file_lister - call pico library's file lister
10068 file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags)
10070 PICO pbf;
10071 int rv;
10072 void (*redraw)(void) = ps_global->redrawer;
10074 standard_picobuf_setup(&pbf);
10075 push_titlebar_state();
10076 if(!newmail)
10077 pbf.newmail = NULL;
10079 /* BUG: what about help command and text? */
10080 pbf.pine_anchor = title;
10082 rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags);
10083 standard_picobuf_teardown(&pbf);
10084 fix_windsize(ps_global);
10085 init_signals(); /* has it's own signal stuff */
10087 /* Restore display's titlebar and body */
10088 pop_titlebar_state();
10089 redraw_titlebar();
10090 if((ps_global->redrawer = redraw) != NULL)
10091 (*ps_global->redrawer)();
10093 return(rv);
10097 /*----------------------------------------------------------------------
10098 Print current folder index
10100 ---*/
10102 print_index(struct pine *state, MSGNO_S *msgmap, int agg)
10104 long i;
10105 ICE_S *ice;
10106 char buf[MAX_SCREEN_COLS+1];
10108 for(i = 1L; i <= mn_get_total(msgmap); i++){
10109 if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
10110 continue;
10112 if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0))
10113 continue;
10115 ice = build_header_line(state, state->mail_stream, msgmap, i, NULL);
10117 if(ice){
10119 * I don't understand why we'd want to mark the current message
10120 * instead of printing out the first character of the status
10121 * so I'm taking it out and including the first character of the
10122 * line instead. Hubert 2006-02-09
10124 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
10125 return(0);
10128 if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i),
10129 print_char)
10130 || !gf_puts(NEWLINE, print_char))
10131 return(0);
10135 return(1);
10138 #ifdef _WINDOWS
10141 * windows callback to get/set header mode state
10144 header_mode_callback(set, args)
10145 int set;
10146 long args;
10148 return(ps_global->full_header);
10153 * windows callback to get/set zoom mode state
10156 zoom_mode_callback(set, args)
10157 int set;
10158 long args;
10160 return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0);
10165 * windows callback to get/set zoom mode state
10168 any_selected_callback(set, args)
10169 int set;
10170 long args;
10172 return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0);
10180 flag_callback(set, flags)
10181 int set;
10182 long flags;
10184 MESSAGECACHE *mc;
10185 int newflags = 0;
10186 long msgno;
10187 int permflag = 0;
10189 switch (set) {
10190 case 1: /* Important */
10191 permflag = ps_global->mail_stream->perm_flagged;
10192 break;
10194 case 2: /* New */
10195 permflag = ps_global->mail_stream->perm_seen;
10196 break;
10198 case 3: /* Answered */
10199 permflag = ps_global->mail_stream->perm_answered;
10200 break;
10202 case 4: /* Deleted */
10203 permflag = ps_global->mail_stream->perm_deleted;
10204 break;
10208 if(!(any_messages(ps_global->msgmap, NULL, "to Flag")
10209 && can_set_flag(ps_global, "flag", permflag)))
10210 return(0);
10212 if(sp_io_error_on_stream(ps_global->mail_stream)){
10213 sp_set_io_error_on_stream(ps_global->mail_stream, 0);
10214 pine_mail_check(ps_global->mail_stream); /* forces write */
10215 return(0);
10218 msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
10219 if(msgno > 0L && ps_global->mail_stream
10220 && msgno <= ps_global->mail_stream->nmsgs
10221 && (mc = mail_elt(ps_global->mail_stream, msgno))
10222 && mc->valid){
10224 * NOTE: code below is *VERY* sensitive to the order of
10225 * the messages defined in resource.h for flag handling.
10226 * Don't change it unless you know what you're doing.
10228 if(set){
10229 char *flagstr;
10230 long mflag;
10232 switch(set){
10233 case 1 : /* Important */
10234 flagstr = "\\FLAGGED";
10235 mflag = (mc->flagged) ? 0L : ST_SET;
10236 break;
10238 case 2 : /* New */
10239 flagstr = "\\SEEN";
10240 mflag = (mc->seen) ? 0L : ST_SET;
10241 break;
10243 case 3 : /* Answered */
10244 flagstr = "\\ANSWERED";
10245 mflag = (mc->answered) ? 0L : ST_SET;
10246 break;
10248 case 4 : /* Deleted */
10249 flagstr = "\\DELETED";
10250 mflag = (mc->deleted) ? 0L : ST_SET;
10251 break;
10253 default : /* bogus */
10254 return(0);
10257 mail_flag(ps_global->mail_stream, long2string(msgno),
10258 flagstr, mflag);
10260 if(ps_global->redrawer)
10261 (*ps_global->redrawer)();
10263 else{
10264 /* Important */
10265 if(mc->flagged)
10266 newflags |= 0x0001;
10268 /* New */
10269 if(!mc->seen)
10270 newflags |= 0x0002;
10272 /* Answered */
10273 if(mc->answered)
10274 newflags |= 0x0004;
10276 /* Deleted */
10277 if(mc->deleted)
10278 newflags |= 0x0008;
10282 return(newflags);
10288 * BUG: Should teach this about keywords
10290 MPopup *
10291 flag_submenu(mc)
10292 MESSAGECACHE *mc;
10294 static MPopup flag_submenu[] = {
10295 {tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}},
10296 {tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}},
10297 {tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}},
10298 {tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}},
10299 {tTail}
10302 /* Important */
10303 flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal;
10305 /* New */
10306 flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked;
10308 /* Answered */
10309 flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal;
10311 /* Deleted */
10312 flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal;
10314 return(flag_submenu);
10317 #endif /* _WINDOWS */