* Update configure script according to previous change to configure.ac
[alpine.git] / alpine / mailcmd.c
blobc0167b8715296b5d9fbfa9a4431984279c65f921
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);
111 int select_by_keyword(MAILSTREAM *, SEARCHSET **);
112 char *choose_a_keyword(void);
113 int select_sort(struct pine *, int, SortOrder *, int *);
114 int print_index(struct pine *, MSGNO_S *, int);
117 * List of Select options used by apply_* functions...
119 static char *sel_pmt1 = N_("ALTER message selection : ");
120 ESCKEY_S sel_opts1[] = {
121 /* TRANSLATORS: these are keymenu names for selecting. Broaden selection means
122 we will add more messages to the selection, Narrow selection means we will
123 remove some selections (like a logical AND instead of logical OR), and Flip
124 Selected means that all the messages that are currently selected become unselected,
125 and all the unselected messages become selected. */
126 {'a', 'a', "A", N_("unselect All")},
127 {'c', 'c', "C", NULL},
128 {'b', 'b', "B", N_("Broaden selctn")},
129 {'n', 'n', "N", N_("Narrow selctn")},
130 {'f', 'f', "F", N_("Flip selected")},
131 {'r', 'r', "R", N_("Replace selctn")},
132 {-1, 0, NULL, NULL}
136 #define SEL_OPTS_THREAD 9 /* index number of "tHread" */
137 #define SEL_OPTS_THREAD_CH 'h'
138 #define SEL_OPTS_XGMEXT 10 /* index number of "boX" */
139 #define SEL_OPTS_XGMEXT_CH 'g'
141 char *sel_pmt2 = "SELECT criteria : ";
142 static ESCKEY_S sel_opts2[] = {
143 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
144 means select the currently highlighted message; select by Number is by message
145 number; Status is by status of the message, for example the message might be
146 New or it might be Unseen or marked Important; Size has the Z upper case because
147 it is a Z command; Keyword is an alpine keyword that has been set by the user;
148 and Rule is an alpine rule */
149 {'a', 'a', "A", N_("select All")},
150 {'c', 'c', "C", N_("select Cur")},
151 {'n', 'n', "N", N_("Number")},
152 {'d', 'd', "D", N_("Date")},
153 {'t', 't', "T", N_("Text")},
154 {'s', 's', "S", N_("Status")},
155 {'z', 'z', "Z", N_("siZe")},
156 {'k', 'k', "K", N_("Keyword")},
157 {'r', 'r', "R", N_("Rule")},
158 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
159 {-1, 0, NULL, NULL}
163 static ESCKEY_S sel_opts3[] = {
164 /* TRANSLATORS: these are operations we can do on a set of selected messages.
165 Del is Delete; Undel is Undelete; TakeAddr means to Take some Addresses into
166 the address book; Save means to save the messages into another alpine folder;
167 Export means to copy the messages to a file outside of alpine, external to
168 alpine's world. */
169 {'d', 'd', "D", N_("Del")},
170 {'u', 'u', "U", N_("Undel")},
171 {'r', 'r', "R", N_("Reply")},
172 {'f', 'f', "F", N_("Forward")},
173 {'%', '%', "%", N_("Print")},
174 {'t', 't', "T", N_("TakeAddr")},
175 {'s', 's', "S", N_("Save")},
176 {'e', 'e', "E", N_("Export")},
177 { -1, 0, NULL, NULL},
178 { -1, 0, NULL, NULL},
179 { -1, 0, NULL, NULL},
180 { -1, 0, NULL, NULL},
181 { -1, 0, NULL, NULL},
182 { -1, 0, NULL, NULL},
183 { -1, 0, NULL, NULL},
184 {-1, 0, NULL, NULL}
187 static ESCKEY_S sel_opts4[] = {
188 {'a', 'a', "A", N_("select All")},
189 /* TRANSLATORS: select currently highlighted message Thread */
190 {'c', 'c', "C", N_("select Curthrd")},
191 {'n', 'n', "N", N_("Number")},
192 {'d', 'd', "D", N_("Date")},
193 {'t', 't', "T", N_("Text")},
194 {'s', 's', "S", N_("Status")},
195 {'z', 'z', "Z", N_("siZe")},
196 {'k', 'k', "K", N_("Keyword")},
197 {'r', 'r', "R", N_("Rule")},
198 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
199 {-1, 0, NULL, NULL}
202 static ESCKEY_S sel_opts5[] = {
203 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
204 means select the currently highlighted message; select by Number is by message
205 number; Status is by status of the message, for example the message might be
206 New or it might be Unseen or marked Important; Size has the Z upper case because
207 it is a Z command; Keyword is an alpine keyword that has been set by the user;
208 and Rule is an alpine rule */
209 {'a', 'a', "A", N_("select All")},
210 {'c', 'c', "C", N_("select Cur")},
211 {'n', 'n', "N", N_("Number")},
212 {'d', 'd', "D", N_("Date")},
213 {'t', 't', "T", N_("Text")},
214 {'s', 's', "S", N_("Status")},
215 {'z', 'z', "Z", N_("siZe")},
216 {'k', 'k', "K", N_("Keyword")},
217 {'r', 'r', "R", N_("Rule")},
218 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
219 {SEL_OPTS_XGMEXT_CH, 'g', "G", N_("GmSearch")},
220 {-1, 0, NULL, NULL},
221 {-1, 0, NULL, NULL},
222 {-1, 0, NULL, NULL},
223 {-1, 0, NULL, NULL},
224 {-1, 0, NULL, NULL}
227 static ESCKEY_S sel_opts6[] = {
228 {'a', 'a', "A", N_("select All")},
229 /* TRANSLATORS: select currently highlighted message Thread */
230 {'c', 'c', "C", N_("select Curthrd")},
231 {'n', 'n', "N", N_("Number")},
232 {'d', 'd', "D", N_("Date")},
233 {'t', 't', "T", N_("Text")},
234 {'s', 's', "S", N_("Status")},
235 {'z', 'z', "Z", N_("siZe")},
236 {'k', 'k', "K", N_("Keyword")},
237 {'r', 'r', "R", N_("Rule")},
238 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
239 {SEL_OPTS_XGMEXT_CH, 'g', "G", N_("Gmail")},
240 {-1, 0, NULL, NULL},
241 {-1, 0, NULL, NULL},
242 {-1, 0, NULL, NULL},
243 {-1, 0, NULL, NULL},
244 {-1, 0, NULL, NULL}
248 static char *sel_flag =
249 N_("Select New, Deleted, Answered, Forwarded, or Important messages ? ");
250 static char *sel_flag_not =
251 N_("Select NOT New, NOT Deleted, NOT Answered, NOT Forwarded or NOT Important msgs ? ");
252 static ESCKEY_S sel_flag_opt[] = {
253 /* TRANSLATORS: When selecting messages by message Status these are the
254 different types of Status you can select on. Is the message New, Recent,
255 and so on. Not means flip the meaning of the selection to the opposite
256 thing, so message is not New or not Important. */
257 {'n', 'n', "N", N_("New")},
258 {'*', '*', "*", N_("Important")},
259 {'d', 'd', "D", N_("Deleted")},
260 {'a', 'a', "A", N_("Answered")},
261 {'f', 'f', "F", N_("Forwarded")},
262 {-2, 0, NULL, NULL},
263 {'!', '!', "!", N_("Not")},
264 {-2, 0, NULL, NULL},
265 {'r', 'r', "R", N_("Recent")},
266 {'u', 'u', "U", N_("Unseen")},
267 {-1, 0, NULL, NULL}
271 static ESCKEY_S sel_date_opt[] = {
272 {0, 0, NULL, NULL},
273 /* TRANSLATORS: options when selecting messages by Date */
274 {ctrl('P'), 12, "^P", N_("Prev Day")},
275 {ctrl('N'), 13, "^N", N_("Next Day")},
276 {ctrl('X'), 11, "^X", N_("Cur Msg")},
277 {ctrl('W'), 14, "^W", N_("Toggle When")},
278 {KEY_UP, 12, "", ""},
279 {KEY_DOWN, 13, "", ""},
280 {-1, 0, NULL, NULL}
284 static char *sel_x_gm_ext =
285 N_("Search: ");
286 static char *sel_text =
287 N_("Select based on To, From, Cc, Recip, Partic, Subject fields or All msg text ? ");
288 static char *sel_text_not =
289 N_("Select based on NOT To, From, Cc, Recip, Partic, Subject or All msg text ? ");
290 static ESCKEY_S sel_text_opt[] = {
291 /* TRANSLATORS: Select messages based on the text contained in the From line, or
292 the Subject line, and so on. */
293 {'f', 'f', "F", N_("From")},
294 {'s', 's', "S", N_("Subject")},
295 {'t', 't', "T", N_("To")},
296 {'a', 'a', "A", N_("All Text")},
297 {'c', 'c', "C", N_("Cc")},
298 {'!', '!', "!", N_("Not")},
299 {'r', 'r', "R", N_("Recipient")},
300 {'p', 'p', "P", N_("Participant")},
301 {'b', 'b', "B", N_("Body")},
302 {'h', 'h', "H", N_("Header")},
303 {-1, 0, NULL, NULL}
306 static ESCKEY_S choose_action[] = {
307 {'c', 'c', "C", N_("Compose")},
308 {'r', 'r', "R", N_("Reply")},
309 {'f', 'f', "F", N_("Forward")},
310 {'b', 'b', "B", N_("Bounce")},
311 {-1, 0, NULL, NULL}
314 static char *select_num =
315 N_("Enter comma-delimited list of numbers (dash between ranges): ");
317 static char *select_size_larger_msg =
318 N_("Select messages with size larger than: ");
320 static char *select_size_smaller_msg =
321 N_("Select messages with size smaller than: ");
323 static char *sel_size_larger = N_("Larger");
324 static char *sel_size_smaller = N_("Smaller");
325 static ESCKEY_S sel_size_opt[] = {
326 {0, 0, NULL, NULL},
327 {ctrl('W'), 14, "^W", NULL},
328 {-1, 0, NULL, NULL}
331 static ESCKEY_S sel_key_opt[] = {
332 {0, 0, NULL, NULL},
333 {ctrl('T'), 14, "^T", N_("To List")},
334 {0, 0, NULL, NULL},
335 {'!', '!', "!", N_("Not")},
336 {-1, 0, NULL, NULL}
339 static ESCKEY_S flag_text_opt[] = {
340 /* TRANSLATORS: these are types of flags (markers) that the user can
341 set. For example, they can flag the message as an important message. */
342 {'n', 'n', "N", N_("New")},
343 {'*', '*', "*", N_("Important")},
344 {'d', 'd', "D", N_("Deleted")},
345 {'a', 'a', "A", N_("Answered")},
346 {'f', 'f', "F", N_("Forwarded")},
347 {'!', '!', "!", N_("Not")},
348 {ctrl('T'), 10, "^T", N_("To Flag Details")},
349 {-1, 0, NULL, NULL}
353 alpine_smime_confirm_save(char *email)
355 char prompt[128];
357 snprintf(prompt, sizeof(prompt), _("Save certificate for <%s>"),
358 email ? email : _("missing address"));
359 return want_to(prompt, 'n', 'x', NO_HELP, WT_NORM) == 'y';
362 int
363 alpine_get_password(char *prompt, char *pass, size_t len)
365 int flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD;
366 int rv;
367 flags |= OE_DISALLOW_HELP;
368 pass[0] = '\0';
369 rv = optionally_enter(pass,
370 -(ps_global->ttyo ? FOOTER_ROWS(ps_global) : 3),
371 0, len, prompt, NULL, NO_HELP, &flags);
372 if(rv == 1) ps_global->user_says_cancel = 1;
373 return rv;
377 smime_import_certificate(char *filename, char *full_filename, char *what, size_t len)
379 int r = 1;
380 static HISTORY_S *history = NULL;
381 static ESCKEY_S eopts[] = {
382 {ctrl('T'), 10, "^T", N_("To Files")},
383 {-1, 0, NULL, NULL},
384 {-1, 0, NULL, NULL}};
386 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
387 eopts[r].ch = ctrl('I');
388 eopts[r].rval = 11;
389 eopts[r].name = "TAB";
390 eopts[r].label = N_("Complete");
393 eopts[++r].ch = -1;
395 filename[0] = '\0';
396 full_filename[0] = '\0';
398 r = get_export_filename(ps_global, filename, NULL, full_filename,
399 len, what, "IMPORT", eopts, NULL,
400 -FOOTER_ROWS(ps_global), GE_IS_IMPORT, &history);
402 return r;
406 /*----------------------------------------------------------------------
407 The giant switch on the commands for index and viewing
409 Input: command -- The command char/code
410 in_index -- flag indicating command is from index
411 orig_command -- The original command typed before pre-processing
412 Output: force_mailchk -- Set to tell caller to force call to new_mail().
414 Result: Manifold
416 Returns 1 if the message number or attachment to show changed
417 ---*/
419 process_cmd(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
420 int command, CmdWhere in_index, int *force_mailchk)
422 int question_line, a_changed, flags = 0, ret, j;
423 int notrealinbox;
424 long new_msgno, del_count, old_msgno, i;
425 long start;
426 char *newfolder, prompt[MAX_SCREEN_COLS+1];
427 CONTEXT_S *tc;
428 MESSAGECACHE *mc;
429 #if defined(DOS) && !defined(_WINDOWS)
430 extern long coreleft();
431 #endif
433 dprint((4, "\n - process_cmd(cmd=%d) -\n", command));
435 question_line = -FOOTER_ROWS(state);
436 state->mangled_screen = 0;
437 state->mangled_footer = 0;
438 state->mangled_header = 0;
439 state->next_screen = SCREEN_FUN_NULL;
440 old_msgno = mn_get_cur(msgmap);
441 a_changed = FALSE;
442 *force_mailchk = 0;
444 switch (command) {
445 /*------------- Help --------*/
446 case MC_HELP :
448 * We're not using the h_mail_view portion of this right now because
449 * that call is being handled in scrolltool() before it gets
450 * here. Leave it in case we change how it works.
452 helper((in_index == MsgIndx)
453 ? h_mail_index
454 : (in_index == View)
455 ? h_mail_view
456 : h_mail_thread_index,
457 (in_index == MsgIndx)
458 ? _("HELP FOR MESSAGE INDEX")
459 : (in_index == View)
460 ? _("HELP FOR MESSAGE TEXT")
461 : _("HELP FOR THREAD INDEX"),
462 HLPD_NONE);
463 dprint((4,"MAIL_CMD: did help command\n"));
464 state->mangled_screen = 1;
465 break;
468 /*--------- Return to main menu ------------*/
469 case MC_MAIN :
470 state->next_screen = main_menu_screen;
471 dprint((2,"MAIL_CMD: going back to main menu\n"));
472 break;
475 /*------- View message text --------*/
476 case MC_VIEW_TEXT :
477 view_text:
478 if(any_messages(msgmap, NULL, "to View")){
479 state->next_screen = mail_view_screen;
482 break;
485 /*------- View attachment --------*/
486 case MC_VIEW_ATCH :
487 state->next_screen = attachment_screen;
488 dprint((2,"MAIL_CMD: going to attachment screen\n"));
489 break;
492 /*---------- Previous message ----------*/
493 case MC_PREVITEM :
494 if(any_messages(msgmap, NULL, NULL)){
495 if((i = mn_get_cur(msgmap)) > 1L){
496 mn_dec_cur(stream, msgmap,
497 (in_index == View && THREADING()
498 && sp_viewing_a_thread(stream))
499 ? MH_THISTHD
500 : (in_index == View)
501 ? MH_ANYTHD : MH_NONE);
502 if(i == mn_get_cur(msgmap)){
503 PINETHRD_S *thrd = NULL, *topthrd = NULL;
505 if(THRD_INDX_ENABLED()){
506 mn_dec_cur(stream, msgmap, MH_ANYTHD);
507 if(i == mn_get_cur(msgmap))
508 q_status_message1(SM_ORDER, 0, 2,
509 _("Already on first %s in Zoomed Index"),
510 THRD_INDX() ? _("thread") : _("message"));
511 else{
512 if(in_index == View
513 || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
514 ret = 'y';
515 else
516 ret = want_to(_("View previous thread"), 'y', 'x',
517 NO_HELP, WT_NORM);
519 if(ret == 'y'){
520 q_status_message(SM_ORDER, 0, 2,
521 _("Viewing previous thread"));
522 new_msgno = mn_get_cur(msgmap);
523 mn_set_cur(msgmap, i);
524 if(unview_thread(state, stream, msgmap)){
525 state->next_screen = mail_index_screen;
526 state->view_skipped_index = 0;
527 state->mangled_screen = 1;
530 mn_set_cur(msgmap, new_msgno);
531 if(THRD_AUTO_VIEW() && in_index == View){
533 thrd = fetch_thread(stream,
534 mn_m2raw(msgmap,
535 new_msgno));
536 if(count_lflags_in_thread(stream, thrd,
537 msgmap,
538 MN_NONE) == 1){
539 if(view_thread(state, stream, msgmap, 1)){
540 if(current_index_state)
541 msgmap->top_after_thrd = current_index_state->msg_at_top;
543 state->view_skipped_index = 1;
544 command = MC_VIEW_TEXT;
545 goto view_text;
550 j = 0;
551 if(THRD_AUTO_VIEW() && in_index != View){
552 thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
553 if(thrd && thrd->top)
554 topthrd = fetch_thread(stream, thrd->top);
556 if(topthrd)
557 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
560 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
561 if(view_thread(state, stream, msgmap, 1)
562 && current_index_state)
563 msgmap->top_after_thrd = current_index_state->msg_at_top;
567 state->next_screen = SCREEN_FUN_NULL;
569 else
570 mn_set_cur(msgmap, i); /* put it back */
573 else
574 q_status_message1(SM_ORDER, 0, 2,
575 _("Already on first %s in Zoomed Index"),
576 THRD_INDX() ? _("thread") : _("message"));
579 else{
580 time_t now;
582 if(!IS_NEWS(stream)
583 && ((now = time(0)) > state->last_nextitem_forcechk)){
584 *force_mailchk = 1;
585 /* check at most once a second */
586 state->last_nextitem_forcechk = now;
589 q_status_message1(SM_ORDER, 0, 1, _("Already on first %s"),
590 THRD_INDX() ? _("thread") : _("message"));
594 break;
597 /*---------- Next Message ----------*/
598 case MC_NEXTITEM :
599 if(mn_get_total(msgmap) > 0L
600 && ((i = mn_get_cur(msgmap)) < mn_get_total(msgmap))){
601 mn_inc_cur(stream, msgmap,
602 (in_index == View && THREADING()
603 && sp_viewing_a_thread(stream))
604 ? MH_THISTHD
605 : (in_index == View)
606 ? MH_ANYTHD : MH_NONE);
607 if(i == mn_get_cur(msgmap)){
608 PINETHRD_S *thrd, *topthrd;
610 if(THRD_INDX_ENABLED()){
611 if(!THRD_INDX())
612 mn_inc_cur(stream, msgmap, MH_ANYTHD);
614 if(i == mn_get_cur(msgmap)){
615 if(any_lflagged(msgmap, MN_HIDE))
616 any_messages(NULL, "more", "in Zoomed Index");
617 else
618 goto nfolder;
620 else{
621 if(in_index == View
622 || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
623 ret = 'y';
624 else
625 ret = want_to(_("View next thread"), 'y', 'x',
626 NO_HELP, WT_NORM);
628 if(ret == 'y'){
629 q_status_message(SM_ORDER, 0, 2,
630 _("Viewing next thread"));
631 new_msgno = mn_get_cur(msgmap);
632 mn_set_cur(msgmap, i);
633 if(unview_thread(state, stream, msgmap)){
634 state->next_screen = mail_index_screen;
635 state->view_skipped_index = 0;
636 state->mangled_screen = 1;
639 mn_set_cur(msgmap, new_msgno);
640 if(THRD_AUTO_VIEW() && in_index == View){
642 thrd = fetch_thread(stream,
643 mn_m2raw(msgmap,
644 new_msgno));
645 if(count_lflags_in_thread(stream, thrd,
646 msgmap,
647 MN_NONE) == 1){
648 if(view_thread(state, stream, msgmap, 1)){
649 if(current_index_state)
650 msgmap->top_after_thrd = current_index_state->msg_at_top;
652 state->view_skipped_index = 1;
653 command = MC_VIEW_TEXT;
654 goto view_text;
659 j = 0;
660 if(THRD_AUTO_VIEW() && in_index != View){
661 thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
662 if(thrd && thrd->top)
663 topthrd = fetch_thread(stream, thrd->top);
664 else
665 topthrd = NULL;
667 if(topthrd)
668 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
671 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
672 if(view_thread(state, stream, msgmap, 1)
673 && current_index_state)
674 msgmap->top_after_thrd = current_index_state->msg_at_top;
678 state->next_screen = SCREEN_FUN_NULL;
680 else
681 mn_set_cur(msgmap, i); /* put it back */
684 else if(THREADING()
685 && (thrd = fetch_thread(stream, mn_m2raw(msgmap, i)))
686 && thrd->next
687 && get_lflag(stream, NULL, thrd->rawno, MN_COLL)){
688 q_status_message(SM_ORDER, 0, 2,
689 _("Expand collapsed thread to see more messages"));
691 else
692 any_messages(NULL, "more", "in Zoomed Index");
695 else{
696 time_t now;
697 nfolder:
698 prompt[0] = '\0';
699 if(IS_NEWS(stream)
700 || (state->context_current->use & CNTXT_INCMNG)){
701 char nextfolder[MAXPATH];
703 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
704 nextfolder[sizeof(nextfolder)-1] = '\0';
705 if(next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
706 state->context_current, NULL, NULL))
707 strncpy(prompt, _(". Press TAB for next folder."),
708 sizeof(prompt));
709 else
710 strncpy(prompt, _(". No more folders to TAB to."),
711 sizeof(prompt));
713 prompt[sizeof(prompt)-1] = '\0';
716 any_messages(NULL, (mn_get_total(msgmap) > 0L) ? "more" : NULL,
717 prompt[0] ? prompt : NULL);
719 if(!IS_NEWS(stream)
720 && ((now = time(0)) > state->last_nextitem_forcechk)){
721 *force_mailchk = 1;
722 /* check at most once a second */
723 state->last_nextitem_forcechk = now;
727 break;
730 /*---------- Delete message ----------*/
731 case MC_DELETE :
732 (void) cmd_delete(state, msgmap, MCMD_NONE,
733 (in_index == View) ? cmd_delete_view : cmd_delete_index);
734 break;
737 /*---------- Undelete message ----------*/
738 case MC_UNDELETE :
739 (void) cmd_undelete(state, msgmap, MCMD_NONE);
740 update_titlebar_status();
741 break;
744 /*---------- Reply to message ----------*/
745 case MC_REPLY :
746 (void) cmd_reply(state, msgmap, MCMD_NONE, NULL);
747 break;
750 /*---------- Forward message ----------*/
751 case MC_FORWARD :
752 (void) cmd_forward(state, msgmap, MCMD_NONE, NULL);
753 break;
756 /*---------- Quit pine ------------*/
757 case MC_QUIT :
758 state->next_screen = quit_screen;
759 dprint((1,"MAIL_CMD: quit\n"));
760 break;
763 /*---------- Compose message ----------*/
764 case MC_COMPOSE :
765 state->prev_screen = (in_index == View) ? mail_view_screen
766 : mail_index_screen;
767 compose_screen(state);
768 state->mangled_screen = 1;
769 if (state->next_screen)
770 a_changed = TRUE;
771 break;
774 /*---------- Alt Compose message ----------*/
775 case MC_ROLE :
776 state->prev_screen = (in_index == View) ? mail_view_screen
777 : mail_index_screen;
778 role_compose(state);
779 if(state->next_screen)
780 a_changed = TRUE;
782 break;
785 /*--------- Folders menu ------------*/
786 case MC_FOLDERS :
787 state->start_in_context = 1;
789 /*--------- Top of Folders list menu ------------*/
790 case MC_COLLECTIONS :
791 state->next_screen = folder_screen;
792 dprint((2,"MAIL_CMD: going to folder/collection menu\n"));
793 break;
796 /*---------- Open specific new folder ----------*/
797 case MC_GOTO :
798 tc = (state->context_last && !NEWS_TEST(state->context_current))
799 ? state->context_last : state->context_current;
801 newfolder = broach_folder(question_line, 1, &notrealinbox, &tc);
802 if(newfolder){
803 visit_folder(state, newfolder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT);
804 a_changed = TRUE;
807 break;
810 /*------- Go to Index Screen ----------*/
811 case MC_INDEX :
812 state->next_screen = mail_index_screen;
813 break;
815 /*------- Skip to next interesting message -----------*/
816 case MC_TAB :
817 if(THRD_INDX()){
818 PINETHRD_S *thrd;
821 * If we're in the thread index, start looking after this
822 * thread. We don't want to match something in the current
823 * thread.
825 start = mn_get_cur(msgmap);
826 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
827 if(mn_get_revsort(msgmap)){
828 /* if reversed, top of thread is last one before next thread */
829 if(thrd && thrd->top)
830 start = mn_raw2m(msgmap, thrd->top);
832 else{
833 /* last msg of thread is at the ends of the branches/nexts */
834 while(thrd){
835 start = mn_raw2m(msgmap, thrd->rawno);
836 if(thrd->branch)
837 thrd = fetch_thread(stream, thrd->branch);
838 else if(thrd->next)
839 thrd = fetch_thread(stream, thrd->next);
840 else
841 thrd = NULL;
846 * Flags is 0 in this case because we want to not skip
847 * messages inside of threads so that we can find threads
848 * which have some unseen messages even though the top-level
849 * of the thread is already seen.
850 * If new_msgno ends up being a message which is not visible
851 * because it isn't at the top-level, the current message #
852 * will be adjusted below in adjust_cur.
854 flags = 0;
855 new_msgno = next_sorted_flagged((F_UNDEL
856 | F_UNSEEN
857 | ((F_ON(F_TAB_TO_NEW,state))
858 ? 0 : F_OR_FLAG)),
859 stream, start, &flags);
861 else if(THREADING() && sp_viewing_a_thread(stream)){
862 PINETHRD_S *thrd, *topthrd = NULL;
864 start = mn_get_cur(msgmap);
867 * Things are especially complicated when we're viewing_a_thread
868 * from the thread index. First we have to check within the
869 * current thread for a new message. If none is found, then
870 * we search in the next threads and offer to continue in
871 * them. Then we offer to go to the next folder.
873 flags = NSF_SKIP_CHID;
874 new_msgno = next_sorted_flagged((F_UNDEL
875 | F_UNSEEN
876 | ((F_ON(F_TAB_TO_NEW,state))
877 ? 0 : F_OR_FLAG)),
878 stream, start, &flags);
880 * If we found a match then we are done, that is another message
881 * in the current thread index. Otherwise, we have to look
882 * further.
884 if(!(flags & NSF_FLAG_MATCH)){
885 ret = 'n';
886 while(1){
888 flags = 0;
889 new_msgno = next_sorted_flagged((F_UNDEL
890 | F_UNSEEN
891 | ((F_ON(F_TAB_TO_NEW,
892 state))
893 ? 0 : F_OR_FLAG)),
894 stream, start, &flags);
896 * If we got a match, new_msgno is a message in
897 * a different thread from the one we are viewing.
899 if(flags & NSF_FLAG_MATCH){
900 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
901 if(thrd && thrd->top)
902 topthrd = fetch_thread(stream, thrd->top);
904 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
905 static ESCKEY_S next_opt[] = {
906 {'y', 'y', "Y", N_("Yes")},
907 {'n', 'n', "N", N_("No")},
908 {TAB, 'n', "Tab", N_("NextNew")},
909 {-1, 0, NULL, NULL}
912 if(in_index)
913 snprintf(prompt, sizeof(prompt), _("View thread number %s? "),
914 topthrd ? comatose(topthrd->thrdno) : "?");
915 else
916 snprintf(prompt, sizeof(prompt),
917 _("View message in thread number %s? "),
918 topthrd ? comatose(topthrd->thrdno) : "?");
920 prompt[sizeof(prompt)-1] = '\0';
922 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
923 next_opt, 'y', 'x', NO_HELP,
924 RB_NORM);
925 if(ret == 'x'){
926 cmd_cancelled(NULL);
927 goto get_out;
930 else
931 ret = 'y';
933 if(ret == 'y'){
934 if(unview_thread(state, stream, msgmap)){
935 state->next_screen = mail_index_screen;
936 state->view_skipped_index = 0;
937 state->mangled_screen = 1;
940 mn_set_cur(msgmap, new_msgno);
941 if(THRD_AUTO_VIEW()){
943 if(count_lflags_in_thread(stream, topthrd,
944 msgmap, MN_NONE) == 1){
945 if(view_thread(state, stream, msgmap, 1)){
946 if(current_index_state)
947 msgmap->top_after_thrd = current_index_state->msg_at_top;
949 state->view_skipped_index = 1;
950 command = MC_VIEW_TEXT;
951 goto view_text;
956 if(view_thread(state, stream, msgmap, 1) && current_index_state)
957 msgmap->top_after_thrd = current_index_state->msg_at_top;
959 state->next_screen = SCREEN_FUN_NULL;
960 break;
962 else if(ret == 'n' && topthrd){
964 * skip to end of this thread and look starting
965 * in the next thread.
967 if(mn_get_revsort(msgmap)){
969 * if reversed, top of thread is last one
970 * before next thread
972 start = mn_raw2m(msgmap, topthrd->rawno);
974 else{
976 * last msg of thread is at the ends of
977 * the branches/nexts
979 thrd = topthrd;
980 while(thrd){
981 start = mn_raw2m(msgmap, thrd->rawno);
982 if(thrd->branch)
983 thrd = fetch_thread(stream, thrd->branch);
984 else if(thrd->next)
985 thrd = fetch_thread(stream, thrd->next);
986 else
987 thrd = NULL;
991 else if(ret == 'n')
992 break;
994 else
995 break;
999 else{
1001 start = mn_get_cur(msgmap);
1004 * If we are on a collapsed thread, start looking after the
1005 * collapsed part, unless we are viewing the message.
1007 if(THREADING() && in_index != View){
1008 PINETHRD_S *thrd;
1009 long rawno;
1010 int collapsed;
1012 rawno = mn_m2raw(msgmap, start);
1013 thrd = fetch_thread(stream, rawno);
1014 collapsed = thrd && thrd->next
1015 && get_lflag(stream, NULL, rawno, MN_COLL);
1017 if(collapsed){
1018 if(mn_get_revsort(msgmap)){
1019 if(thrd && thrd->top)
1020 start = mn_raw2m(msgmap, thrd->top);
1022 else{
1023 while(thrd){
1024 start = mn_raw2m(msgmap, thrd->rawno);
1025 if(thrd->branch)
1026 thrd = fetch_thread(stream, thrd->branch);
1027 else if(thrd->next)
1028 thrd = fetch_thread(stream, thrd->next);
1029 else
1030 thrd = NULL;
1037 new_msgno = next_sorted_flagged((F_UNDEL
1038 | F_UNSEEN
1039 | ((F_ON(F_TAB_TO_NEW,state))
1040 ? 0 : F_OR_FLAG)),
1041 stream, start, &flags);
1045 * If there weren't any unread messages left, OR there
1046 * aren't any messages at all, we may want to offer to
1047 * go on to the next folder...
1049 if(flags & NSF_FLAG_MATCH){
1050 mn_set_cur(msgmap, new_msgno);
1051 if(in_index != View)
1052 adjust_cur_to_visible(stream, msgmap);
1054 else{
1055 int in_inbox = sp_flagged(stream, SP_INBOX);
1057 if(state->context_current
1058 && ((NEWS_TEST(state->context_current)
1059 && context_isambig(state->cur_folder))
1060 || ((state->context_current->use & CNTXT_INCMNG)
1061 && (in_inbox
1062 || folder_index(state->cur_folder,
1063 state->context_current,
1064 FI_FOLDER) >= 0)))){
1065 char nextfolder[MAXPATH];
1066 MAILSTREAM *nextstream = NULL;
1067 long recent_cnt;
1068 int did_cancel = 0;
1070 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
1071 nextfolder[sizeof(nextfolder)-1] = '\0';
1072 while(1){
1073 if(!(next_folder(&nextstream, nextfolder, sizeof(nextfolder), nextfolder,
1074 state->context_current, &recent_cnt,
1075 F_ON(F_TAB_NO_CONFIRM,state)
1076 ? NULL : &did_cancel))){
1077 if(!in_inbox){
1078 static ESCKEY_S inbox_opt[] = {
1079 {'y', 'y', "Y", N_("Yes")},
1080 {'n', 'n', "N", N_("No")},
1081 {TAB, 'z', "Tab", N_("To Inbox")},
1082 {-1, 0, NULL, NULL}
1085 if(F_ON(F_RET_INBOX_NO_CONFIRM,state))
1086 ret = 'y';
1087 else{
1088 /* TRANSLATORS: this is a question, with some information followed
1089 by Return to INBOX? */
1090 if(state->context_current->use&CNTXT_INCMNG)
1091 snprintf(prompt, sizeof(prompt), _("No more incoming folders. Return to \"%s\"? "), state->inbox_name);
1092 else
1093 snprintf(prompt, sizeof(prompt), _("No more news groups. Return to \"%s\"? "), state->inbox_name);
1095 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1096 inbox_opt, 'y', 'x',
1097 NO_HELP, RB_NORM);
1101 * 'z' is a synonym for 'y'. It is not 'y'
1102 * so that it isn't displayed as a default
1103 * action with square-brackets around it
1104 * in the keymenu...
1106 if(ret == 'y' || ret == 'z'){
1107 visit_folder(state, state->inbox_name,
1108 state->context_current,
1109 NULL, DB_INBOXWOCNTXT);
1110 a_changed = TRUE;
1113 else if (did_cancel)
1114 cmd_cancelled(NULL);
1115 else{
1116 if(state->context_current->use&CNTXT_INCMNG)
1117 q_status_message(SM_ORDER, 0, 2, _("No more incoming folders"));
1118 else
1119 q_status_message(SM_ORDER, 0, 2, _("No more news groups"));
1122 break;
1126 #define CNTLEN 80
1127 char *front, type[80], cnt[CNTLEN], fbuf[MAX_SCREEN_COLS/2+1];
1128 int rbspace, avail, need, take_back;
1131 * View_next_
1132 * Incoming_folder_ or news_group_ or folder_ or group_
1133 * "foldername"
1134 * _(13 recent) or _(some recent) or nothing
1135 * ?_
1137 front = "View next";
1138 strncpy(type,
1139 (state->context_current->use & CNTXT_INCMNG)
1140 ? "Incoming folder" : "news group",
1141 sizeof(type));
1142 type[sizeof(type)-1] = '\0';
1143 snprintf(cnt, sizeof(cnt), " (%.*s %s)", CNTLEN-20,
1144 recent_cnt ? long2string(recent_cnt) : "some",
1145 F_ON(F_TAB_USES_UNSEEN, ps_global)
1146 ? "unseen" : "recent");
1147 cnt[sizeof(cnt)-1] = '\0';
1150 * Space reserved for radio_buttons call.
1151 * If we make this 3 then radio_buttons won't mess
1152 * with the prompt. If we make it 2, then we get
1153 * one more character to use but radio_buttons will
1154 * cut off the last character of our prompt, which is
1155 * ok because it is a space.
1157 rbspace = 2;
1158 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols
1159 : 80;
1160 need = strlen(front)+1 + strlen(type)+1 +
1161 + strlen(nextfolder)+2 + strlen(cnt) +
1162 2 + rbspace;
1163 if(avail < need){
1164 take_back = strlen(type);
1165 strncpy(type,
1166 (state->context_current->use & CNTXT_INCMNG)
1167 ? "folder" : "group", sizeof(type));
1168 take_back -= strlen(type);
1169 need -= take_back;
1170 if(avail < need){
1171 need -= strlen(cnt);
1172 cnt[0] = '\0';
1175 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1176 snprintf(prompt, sizeof(prompt), "%.*s %.*s \"%.*s\"%.*s? ",
1177 (MAX_SCREEN_COLS+1)/8, front,
1178 (MAX_SCREEN_COLS+1)/8, type,
1179 (MAX_SCREEN_COLS+1)/2,
1180 short_str(nextfolder, fbuf, sizeof(fbuf),
1181 strlen(nextfolder) -
1182 ((need>avail) ? (need-avail) : 0),
1183 MidDots),
1184 (MAX_SCREEN_COLS+1)/8, cnt);
1185 prompt[sizeof(prompt)-1] = '\0';
1189 * When help gets added, this'll have to become
1190 * a loop like the rest...
1192 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
1193 static ESCKEY_S next_opt[] = {
1194 {'y', 'y', "Y", N_("Yes")},
1195 {'n', 'n', "N", N_("No")},
1196 {TAB, 'n', "Tab", N_("NextNew")},
1197 {-1, 0, NULL, NULL}
1200 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1201 next_opt, 'y', 'x', NO_HELP,
1202 RB_NORM);
1203 if(ret == 'x'){
1204 cmd_cancelled(NULL);
1205 break;
1208 else
1209 ret = 'y';
1211 if(ret == 'y'){
1212 if(nextstream && sp_dead_stream(nextstream))
1213 nextstream = NULL;
1215 visit_folder(state, nextfolder,
1216 state->context_current, nextstream,
1217 DB_FROMTAB);
1218 /* visit_folder takes care of nextstream */
1219 nextstream = NULL;
1220 a_changed = TRUE;
1221 break;
1225 if(nextstream)
1226 pine_mail_close(nextstream);
1228 else
1229 any_messages(NULL,
1230 (mn_get_total(msgmap) > 0L)
1231 ? IS_NEWS(stream) ? "more undeleted" : "more new"
1232 : NULL,
1233 NULL);
1236 get_out:
1238 break;
1241 /*------- Zoom -----------*/
1242 case MC_ZOOM :
1244 * Right now the way zoom is implemented is sort of silly.
1245 * There are two per-message flags where just one and a
1246 * global "zoom mode" flag to suppress messages from the index
1247 * should suffice.
1249 if(any_messages(msgmap, NULL, "to Zoom on")){
1250 if(unzoom_index(state, stream, msgmap)){
1251 dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
1252 q_status_message(SM_ORDER,0,2, _("Index Zoom Mode is now off"));
1254 else if((i = zoom_index(state, stream, msgmap, MN_SLCT)) != 0){
1255 if(any_lflagged(msgmap, MN_HIDE)){
1256 dprint((4,"\n\n ---- Entering ZOOM mode ----\n"));
1257 q_status_message4(SM_ORDER, 0, 2,
1258 _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"),
1259 THRD_INDX() ? "" : comatose(i),
1260 THRD_INDX() ? "" : " ",
1261 THRD_INDX() ? _("threads") : _("message"),
1262 THRD_INDX() ? "" : plural(i));
1264 else
1265 q_status_message(SM_ORDER, 0, 2,
1266 _("All messages selected, so not entering Index Zoom Mode"));
1268 else
1269 any_messages(NULL, "selected", "to Zoom on");
1272 break;
1275 /*---------- print message on paper ----------*/
1276 case MC_PRINTMSG :
1277 if(any_messages(msgmap, NULL, "to print"))
1278 (void) cmd_print(state, msgmap, MCMD_NONE, in_index);
1280 break;
1283 /*---------- Take Address ----------*/
1284 case MC_TAKE :
1285 if(F_ON(F_ENABLE_ROLE_TAKE, state) ||
1286 any_messages(msgmap, NULL, "to Take address from"))
1287 (void) cmd_take_addr(state, msgmap, MCMD_NONE);
1289 break;
1292 /*---------- Save Message ----------*/
1293 case MC_SAVE :
1294 if(any_messages(msgmap, NULL, "to Save"))
1295 (void) cmd_save(state, stream, msgmap, MCMD_NONE, in_index);
1297 break;
1300 /*---------- Export message ----------*/
1301 case MC_EXPORT :
1302 if(any_messages(msgmap, NULL, "to Export")){
1303 (void) cmd_export(state, msgmap, question_line, MCMD_NONE);
1304 state->mangled_footer = 1;
1307 break;
1310 /*---------- Expunge ----------*/
1311 case MC_EXPUNGE :
1312 (void) cmd_expunge(state, stream, msgmap, MCMD_NONE);
1313 break;
1316 /*------- Unexclude -----------*/
1317 case MC_UNEXCLUDE :
1318 if(!(IS_NEWS(stream) && stream->rdonly)){
1319 q_status_message(SM_ORDER, 0, 3,
1320 _("Unexclude not available for mail folders"));
1322 else if(any_lflagged(msgmap, MN_EXLD)){
1323 SEARCHPGM *pgm;
1324 long i;
1325 int exbits;
1328 * Since excluded means "hidden deleted" and "killed",
1329 * the count should reflect the former.
1331 pgm = mail_newsearchpgm();
1332 pgm->deleted = 1;
1333 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
1334 for(i = 1L, del_count = 0L; i <= stream->nmsgs; i++)
1335 if((mc = mail_elt(stream, i)) && mc->searched
1336 && get_lflag(stream, NULL, i, MN_EXLD)
1337 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
1338 && (exbits & MSG_EX_FILTERED)))
1339 del_count++;
1341 if(del_count > 0L){
1342 state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1343 snprintf(prompt, sizeof(prompt), "UNexclude %ld message%s in %.*s", del_count,
1344 plural(del_count), MAX_SCREEN_COLS+1-45,
1345 pretty_fn(state->cur_folder));
1346 prompt[sizeof(prompt)-1] = '\0';
1347 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
1348 || (F_ON(F_AUTO_EXPUNGE, state)
1349 && (state->context_current
1350 && (state->context_current->use & CNTXT_INCMNG))
1351 && context_isambig(state->cur_folder))
1352 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
1353 long save_cur_rawno;
1354 int were_viewing_a_thread;
1356 save_cur_rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
1357 were_viewing_a_thread = (THREADING()
1358 && sp_viewing_a_thread(stream));
1360 if(msgno_include(stream, msgmap, MI_NONE)){
1361 clear_index_cache(stream, 0);
1363 if(stream && stream->spare)
1364 erase_threading_info(stream, msgmap);
1366 refresh_sort(stream, msgmap, SRT_NON);
1369 if(were_viewing_a_thread){
1370 if(save_cur_rawno > 0L)
1371 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1373 if(view_thread(state, stream, msgmap, 1) && current_index_state)
1374 msgmap->top_after_thrd = current_index_state->msg_at_top;
1377 if(save_cur_rawno > 0L)
1378 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1380 state->mangled_screen = 1;
1381 q_status_message2(SM_ORDER, 0, 4,
1382 "%s message%s UNexcluded",
1383 long2string(del_count),
1384 plural(del_count));
1386 if(in_index != View)
1387 adjust_cur_to_visible(stream, msgmap);
1389 else
1390 any_messages(NULL, NULL, "UNexcluded");
1392 else
1393 any_messages(NULL, "excluded", "to UNexclude");
1395 else
1396 any_messages(NULL, "excluded", "to UNexclude");
1398 break;
1401 /*------- Make Selection -----------*/
1402 case MC_SELECT :
1403 if(any_messages(msgmap, NULL, "to Select")){
1404 if(aggregate_select(state, msgmap, question_line, in_index) == 0
1405 && (in_index == MsgIndx || in_index == ThrdIndx)
1406 && F_ON(F_AUTO_ZOOM, state)
1407 && any_lflagged(msgmap, MN_SLCT) > 0L
1408 && !any_lflagged(msgmap, MN_HIDE))
1409 (void) zoom_index(state, stream, msgmap, MN_SLCT);
1412 break;
1415 /*------- Toggle Current Message Selection State -----------*/
1416 case MC_SELCUR :
1417 if(any_messages(msgmap, NULL, NULL)){
1418 if((select_by_current(state, msgmap, in_index)
1419 || (F_OFF(F_UNSELECT_WONT_ADVANCE, state)
1420 && !any_lflagged(msgmap, MN_HIDE)))
1421 && (i = mn_get_cur(msgmap)) < mn_get_total(msgmap)){
1422 /* advance current */
1423 mn_inc_cur(stream, msgmap,
1424 (in_index == View && THREADING()
1425 && sp_viewing_a_thread(stream))
1426 ? MH_THISTHD
1427 : (in_index == View)
1428 ? MH_ANYTHD : MH_NONE);
1432 break;
1435 /*------- Apply command -----------*/
1436 case MC_APPLY :
1437 if(any_messages(msgmap, NULL, NULL)){
1438 if(any_lflagged(msgmap, MN_SLCT) > 0L){
1439 if(apply_command(state, stream, msgmap, 0,
1440 AC_NONE, question_line)){
1441 if(F_ON(F_AUTO_UNSELECT, state)){
1442 agg_select_all(stream, msgmap, NULL, 0);
1443 unzoom_index(state, stream, msgmap);
1445 else if(F_ON(F_AUTO_UNZOOM, state))
1446 unzoom_index(state, stream, msgmap);
1449 else
1450 any_messages(NULL, NULL, "to Apply command to. Try \"Select\"");
1453 break;
1456 /*-------- Sort command -------*/
1457 case MC_SORT :
1459 int were_threading = THREADING();
1460 SortOrder sort = mn_get_sort(msgmap);
1461 int rev = mn_get_revsort(msgmap);
1463 dprint((1,"MAIL_CMD: sort\n"));
1464 if(select_sort(state, question_line, &sort, &rev)){
1465 /* $ command reinitializes threading collapsed/expanded info */
1466 if(SORT_IS_THREADED(msgmap) && !SEP_THRDINDX())
1467 erase_threading_info(stream, msgmap);
1469 if(ps_global && ps_global->ttyo){
1470 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
1471 ps_global->mangled_footer = 1;
1474 sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN);
1477 state->mangled_footer = 1;
1480 * We've changed whether we are threading or not so we need to
1481 * exit the index and come back in so that we switch between the
1482 * thread index and the regular index. Sort_folder will have
1483 * reset viewing_a_thread if necessary.
1485 if(SEP_THRDINDX()
1486 && ((!were_threading && THREADING())
1487 || (were_threading && !THREADING()))){
1488 state->next_screen = mail_index_screen;
1489 state->mangled_screen = 1;
1493 break;
1496 /*------- Toggle Full Headers -----------*/
1497 case MC_FULLHDR :
1498 state->full_header++;
1499 if(state->full_header == 1){
1500 if(!(state->quote_suppression_threshold
1501 && (state->some_quoting_was_suppressed || in_index != View)))
1502 state->full_header++;
1504 else if(state->full_header > 2)
1505 state->full_header = 0;
1507 switch(state->full_header){
1508 case 0:
1509 q_status_message(SM_ORDER, 0, 3,
1510 _("Display of full headers is now off."));
1511 break;
1513 case 1:
1514 q_status_message1(SM_ORDER, 0, 3,
1515 _("Quotes displayed, use %s to see full headers"),
1516 F_ON(F_USE_FK, state) ? "F9" : "H");
1517 break;
1519 case 2:
1520 q_status_message(SM_ORDER, 0, 3,
1521 _("Display of full headers is now on."));
1522 break;
1526 a_changed = TRUE;
1527 break;
1530 case MC_TOGGLE :
1531 a_changed = TRUE;
1532 break;
1535 #ifdef SMIME
1536 /*------- Try to decrypt message -----------*/
1537 case MC_DECRYPT:
1538 if(state->smime && state->smime->need_passphrase)
1539 smime_get_passphrase();
1541 a_changed = TRUE;
1542 break;
1544 case MC_SECURITY:
1545 smime_info_screen(state);
1546 break;
1547 #endif
1550 /*------- Bounce -----------*/
1551 case MC_BOUNCE :
1552 (void) cmd_bounce(state, msgmap, MCMD_NONE, NULL);
1553 break;
1556 /*------- Flag -----------*/
1557 case MC_FLAG :
1558 dprint((4, "\n - flag message -\n"));
1559 (void) cmd_flag(state, msgmap, MCMD_NONE);
1560 break;
1563 /*------- Pipe message -----------*/
1564 case MC_PIPE :
1565 (void) cmd_pipe(state, msgmap, MCMD_NONE);
1566 break;
1569 /*--------- Default, unknown command ----------*/
1570 default:
1571 alpine_panic("Unexpected command case");
1572 break;
1575 return((a_changed || mn_get_cur(msgmap) != old_msgno) ? 1 : 0);
1580 /*----------------------------------------------------------------------
1581 Map some of the special characters into sensible strings for human
1582 consumption.
1583 c is a UCS-4 character!
1584 ----*/
1585 char *
1586 pretty_command(UCS c)
1588 static char buf[10];
1589 char *s;
1591 buf[0] = '\0';
1592 s = buf;
1594 switch(c){
1595 case ' ' : s = "SPACE"; break;
1596 case '\033' : s = "ESC"; break;
1597 case '\177' : s = "DEL"; break;
1598 case ctrl('I') : s = "TAB"; break;
1599 case ctrl('J') : s = "LINEFEED"; break;
1600 case ctrl('M') : s = "RETURN"; break;
1601 case ctrl('Q') : s = "XON"; break;
1602 case ctrl('S') : s = "XOFF"; break;
1603 case KEY_UP : s = "Up Arrow"; break;
1604 case KEY_DOWN : s = "Down Arrow"; break;
1605 case KEY_RIGHT : s = "Right Arrow"; break;
1606 case KEY_LEFT : s = "Left Arrow"; break;
1607 case KEY_PGUP : s = "Prev Page"; break;
1608 case KEY_PGDN : s = "Next Page"; break;
1609 case KEY_HOME : s = "Home"; break;
1610 case KEY_END : s = "End"; break;
1611 case KEY_DEL : s = "Delete"; break; /* Not necessary DEL! */
1612 case KEY_JUNK : s = "Junk!"; break;
1613 case BADESC : s = "Bad Esc"; break;
1614 case NO_OP_IDLE : s = "NO_OP_IDLE"; break;
1615 case NO_OP_COMMAND : s = "NO_OP_COMMAND"; break;
1616 case KEY_RESIZE : s = "KEY_RESIZE"; break;
1617 case KEY_UTF8 : s = "KEY_UTF8"; break;
1618 case KEY_MOUSE : s = "KEY_MOUSE"; break;
1619 case KEY_SCRLUPL : s = "KEY_SCRLUPL"; break;
1620 case KEY_SCRLDNL : s = "KEY_SCRLDNL"; break;
1621 case KEY_SCRLTO : s = "KEY_SCRLTO"; break;
1622 case KEY_XTERM_MOUSE : s = "KEY_XTERM_MOUSE"; break;
1623 case KEY_DOUBLE_ESC : s = "KEY_DOUBLE_ESC"; break;
1624 case CTRL_KEY_UP : s = "Ctrl Up Arrow"; break;
1625 case CTRL_KEY_DOWN : s = "Ctrl Down Arrow"; break;
1626 case CTRL_KEY_RIGHT : s = "Ctrl Right Arrow"; break;
1627 case CTRL_KEY_LEFT : s = "Ctrl Left Arrow"; break;
1628 case PF1 :
1629 case PF2 :
1630 case PF3 :
1631 case PF4 :
1632 case PF5 :
1633 case PF6 :
1634 case PF7 :
1635 case PF8 :
1636 case PF9 :
1637 case PF10 :
1638 case PF11 :
1639 case PF12 :
1640 snprintf(s = buf, sizeof(buf), "F%ld", (long) (c - PF1 + 1));
1641 break;
1643 default:
1644 if(c < ' ' || (c >= 0x80 && c < 0xA0)){
1645 char d;
1646 int c1;
1648 c1 = (c >= 0x80);
1649 d = (c & 0x1f) + 'A' - 1;
1650 snprintf(s = buf, sizeof(buf), "%c%c", c1 ? '~' : '^', d);
1652 else{
1653 memset(buf, 0, sizeof(buf));
1654 utf8_put((unsigned char *) buf, (unsigned long) c);
1657 break;
1660 return(s);
1664 /*----------------------------------------------------------------------
1665 Complain about bogus input
1667 Args: ch -- input command to complain about
1668 help -- string indicating where to get help
1670 ----*/
1671 void
1672 bogus_command(UCS cmd, char *help)
1674 if(cmd == ctrl('Q') || cmd == ctrl('S'))
1675 q_status_message1(SM_ASYNC, 0, 2,
1676 "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.",
1677 pretty_command(cmd));
1678 else if(cmd == KEY_JUNK)
1679 q_status_message3(SM_ORDER, 0, 2,
1680 "Invalid key pressed.%s%s%s",
1681 (help) ? " Use " : "",
1682 (help) ? help : "",
1683 (help) ? " for help" : "");
1684 else
1685 q_status_message4(SM_ORDER, 0, 2,
1686 "Command \"%s\" not defined for this screen.%s%s%s",
1687 pretty_command(cmd),
1688 (help) ? " Use " : "",
1689 (help) ? help : "",
1690 (help) ? " for help" : "");
1694 void
1695 bogus_utf8_command(char *cmd, char *help)
1697 q_status_message4(SM_ORDER, 0, 2,
1698 "Command \"%s\" not defined for this screen.%s%s%s",
1699 cmd ? cmd : "?",
1700 (help) ? " Use " : "",
1701 (help) ? help : "",
1702 (help) ? " for help" : "");
1706 /*----------------------------------------------------------------------
1707 Execute FLAG message command
1709 Args: state -- Various satate info
1710 msgmap -- map of c-client to local message numbers
1712 Result: with side effect of "current" message FLAG flag set or UNset
1714 ----*/
1716 cmd_flag(struct pine *state, MSGNO_S *msgmap, int aopt)
1718 char *flagit, *seq, *screen_text[20], **exp, **p, *answer = NULL;
1719 char *keyword_array[2];
1720 int user_defined_flags = 0, mailbox_flags = 0;
1721 int directly_to_maint_screen = 0;
1722 int use_maint_screen = F_ON(F_FLAG_SCREEN_DFLT, ps_global);
1723 long unflagged, flagged, flags, rawno;
1724 MESSAGECACHE *mc = NULL;
1725 KEYWORD_S *kw;
1726 int i, cnt, is_set, trouble = 0, rv = 0;
1727 size_t len;
1728 struct flag_table *fp, *ftbl = NULL;
1729 struct flag_screen flag_screen;
1730 static char *flag_screen_text1[] = {
1731 N_(" Set desired flags for current message below. An 'X' means set"),
1732 N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."),
1733 NULL
1736 static char *flag_screen_text2[] = {
1737 N_(" Set desired flags below for selected messages. A '?' means to"),
1738 N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"),
1739 N_(" to unset it. Use the \"Return\" key to toggle, and choose"),
1740 N_(" \"Exit\" when finished."),
1741 NULL
1744 static struct flag_table default_ftbl[] = {
1745 {N_("Important"), h_flag_important, F_FLAG, 0, 0, NULL, NULL},
1746 {N_("New"), h_flag_new, F_SEEN, 0, 0, NULL, NULL},
1747 {N_("Answered"), h_flag_answered, F_ANS, 0, 0, NULL, NULL},
1748 {N_("Forwarded"), h_flag_forwarded, F_FWD, 0, 0, NULL, NULL},
1749 {N_("Deleted"), h_flag_deleted, F_DEL, 0, 0, NULL, NULL},
1750 {NULL, NO_HELP, 0, 0, 0, NULL, NULL}
1753 /* Only check for dead stream for now. Should check permanent flags
1754 * eventually
1756 if(!(any_messages(msgmap, NULL, "to Flag") && can_set_flag(state, "flag", 1)))
1757 return rv;
1759 if(sp_io_error_on_stream(state->mail_stream)){
1760 sp_set_io_error_on_stream(state->mail_stream, 0);
1761 pine_mail_check(state->mail_stream); /* forces write */
1762 return rv;
1765 go_again:
1766 answer = NULL;
1767 user_defined_flags = 0;
1768 mailbox_flags = 0;
1769 mc = NULL;
1770 trouble = 0;
1771 ftbl = NULL;
1773 /* count how large ftbl will be */
1774 for(cnt = 0; default_ftbl[cnt].name; cnt++)
1777 /* add user flags */
1778 for(kw = ps_global->keywords; kw; kw = kw->next){
1779 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick)) || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1780 user_defined_flags++;
1781 cnt++;
1786 * Add mailbox flags that aren't user-defined flags.
1787 * Don't consider it if it matches either one of our defined
1788 * keywords or one of our defined nicknames for a keyword.
1790 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1791 char *q;
1793 q = stream_to_user_flag_name(state->mail_stream, i);
1794 if(q && q[0]){
1795 for(kw = ps_global->keywords; kw; kw = kw->next){
1796 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1797 break;
1801 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1802 mailbox_flags++;
1803 cnt++;
1807 cnt += (user_defined_flags ? 2 : 0) + (mailbox_flags ? 2 : 0);
1809 /* set up ftbl, first the system flags */
1810 ftbl = (struct flag_table *) fs_get((cnt+1) * sizeof(*ftbl));
1811 memset(ftbl, 0, (cnt+1) * sizeof(*ftbl));
1812 for(i = 0, fp = ftbl; default_ftbl[i].name; i++, fp++){
1813 fp->name = cpystr(default_ftbl[i].name);
1814 fp->help = default_ftbl[i].help;
1815 fp->flag = default_ftbl[i].flag;
1816 fp->set = default_ftbl[i].set;
1817 fp->ukn = default_ftbl[i].ukn;
1820 if(user_defined_flags){
1821 fp->flag = F_COMMENT;
1822 fp->name = cpystr("");
1823 fp++;
1824 fp->flag = F_COMMENT;
1825 len = strlen(_("User-defined Keywords from Setup/Config"));
1826 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1827 snprintf(fp->name, len+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config"));
1828 fp++;
1831 /* then the user-defined keywords */
1832 if(user_defined_flags)
1833 for(kw = ps_global->keywords; kw; kw = kw->next){
1834 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick))
1835 || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1836 fp->name = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
1837 fp->keyword = cpystr(kw->kw ? kw->kw : "");
1838 if(kw->nick && kw->kw){
1839 size_t l;
1841 l = strlen(kw->kw)+2;
1842 fp->comment = (char *) fs_get((l+1) * sizeof(char));
1843 snprintf(fp->comment, l+1, "(%.*s)", (int) strlen(kw->kw), kw->kw);
1844 fp->comment[l] = '\0';
1847 fp->help = h_flag_user_flag;
1848 fp->flag = F_KEYWORD;
1849 fp->set = 0;
1850 fp->ukn = 0;
1851 fp++;
1855 if(mailbox_flags){
1856 fp->flag = F_COMMENT;
1857 fp->name = cpystr("");
1858 fp++;
1859 fp->flag = F_COMMENT;
1860 len = strlen(_("Other keywords in the mailbox that are not user-defined"));
1861 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1862 snprintf(fp->name, len+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined"));
1863 fp++;
1866 /* then the extra mailbox-defined keywords */
1867 if(mailbox_flags)
1868 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1869 char *q;
1871 q = stream_to_user_flag_name(state->mail_stream, i);
1872 if(q && q[0]){
1873 for(kw = ps_global->keywords; kw; kw = kw->next){
1874 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1875 break;
1879 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1880 fp->name = cpystr(q);
1881 fp->keyword = cpystr(q);
1882 fp->help = h_flag_user_flag;
1883 fp->flag = F_KEYWORD;
1884 fp->set = 0;
1885 fp->ukn = 0;
1886 fp++;
1890 flag_screen.flag_table = &ftbl;
1891 flag_screen.explanation = screen_text;
1893 if(MCMD_ISAGG(aopt)){
1894 if(!pseudo_selected(ps_global->mail_stream, msgmap)){
1895 free_flag_table(&ftbl);
1896 return rv;
1899 exp = flag_screen_text2;
1900 for(fp = ftbl; fp->name; fp++){
1901 fp->set = CMD_FLAG_UNKN; /* set to unknown */
1902 fp->ukn = TRUE;
1905 else if(state->mail_stream
1906 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
1907 && rawno <= state->mail_stream->nmsgs
1908 && (mc = mail_elt(state->mail_stream, rawno))){
1909 exp = flag_screen_text1;
1910 for(fp = &ftbl[0]; fp->name; fp++){
1911 fp->ukn = 0;
1912 if(fp->flag == F_KEYWORD){
1913 /* see if this keyword is defined for this message */
1914 fp->set = CMD_FLAG_CLEAR;
1915 if(user_flag_is_set(state->mail_stream,
1916 rawno, fp->keyword))
1917 fp->set = CMD_FLAG_SET;
1919 else if(fp->flag == F_FWD){
1920 /* see if forwarded keyword is defined for this message */
1921 fp->set = CMD_FLAG_CLEAR;
1922 if(user_flag_is_set(state->mail_stream,
1923 rawno, FORWARDED_FLAG))
1924 fp->set = CMD_FLAG_SET;
1926 else if(fp->flag != F_COMMENT)
1927 fp->set = ((fp->flag == F_SEEN && !mc->seen)
1928 || (fp->flag == F_DEL && mc->deleted)
1929 || (fp->flag == F_FLAG && mc->flagged)
1930 || (fp->flag == F_ANS && mc->answered))
1931 ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
1934 else{
1935 q_status_message(SM_ORDER | SM_DING, 3, 4,
1936 _("Error accessing message data"));
1937 free_flag_table(&ftbl);
1938 return rv;
1941 if(directly_to_maint_screen)
1942 goto the_maint_screen;
1944 #ifdef _WINDOWS
1945 if (mswin_usedialog ()) {
1946 if (!os_flagmsgdialog (&ftbl[0])){
1947 free_flag_table(&ftbl);
1948 return rv;
1951 else
1952 #endif
1954 int keyword_shortcut = 0;
1956 if(!use_maint_screen){
1958 * We're going to call cmd_flag_prompt(). We need
1959 * to decide whether or not to offer the keyword setting
1960 * shortcut. We'll offer it if the user has the feature
1961 * enabled AND there are some possible keywords that could
1962 * be set.
1964 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global)){
1965 for(fp = &ftbl[0]; fp->name && !keyword_shortcut; fp++){
1966 if(fp->flag == F_KEYWORD){
1967 int first_char;
1968 ESCKEY_S *tp;
1970 first_char = (fp->name && fp->name[0])
1971 ? fp->name[0] : -2;
1972 if(isascii(first_char) && isupper(first_char))
1973 first_char = tolower((unsigned char) first_char);
1975 for(tp=flag_text_opt; tp->ch != -1; tp++)
1976 if(tp->ch == first_char)
1977 break;
1979 if(tp->ch == -1)
1980 keyword_shortcut++;
1985 use_maint_screen = !cmd_flag_prompt(state, &flag_screen,
1986 keyword_shortcut);
1989 the_maint_screen:
1990 if(use_maint_screen){
1991 for(p = &screen_text[0]; *exp; p++, exp++)
1992 *p = *exp;
1994 *p = NULL;
1996 directly_to_maint_screen = flag_maintenance_screen(state, &flag_screen);
2000 /* reacquire the elt pointer */
2001 mc = (state->mail_stream
2002 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
2003 && rawno <= state->mail_stream->nmsgs)
2004 ? mail_elt(state->mail_stream, rawno) : NULL;
2006 for(fp = ftbl; mc && fp->name; fp++){
2007 flags = -1;
2008 switch(fp->flag){
2009 case F_SEEN:
2010 if((!MCMD_ISAGG(aopt) && fp->set != !mc->seen)
2011 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2012 flagit = "\\SEEN";
2013 if(fp->set){
2014 flags = 0L;
2015 unflagged = F_SEEN;
2017 else{
2018 flags = ST_SET;
2019 unflagged = F_UNSEEN;
2023 break;
2025 case F_ANS:
2026 if((!MCMD_ISAGG(aopt) && fp->set != mc->answered)
2027 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2028 flagit = "\\ANSWERED";
2029 if(fp->set){
2030 flags = ST_SET;
2031 unflagged = F_UNANS;
2033 else{
2034 flags = 0L;
2035 unflagged = F_ANS;
2039 break;
2041 case F_DEL:
2042 if((!MCMD_ISAGG(aopt) && fp->set != mc->deleted)
2043 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2044 flagit = "\\DELETED";
2045 if(fp->set){
2046 flags = ST_SET;
2047 unflagged = F_UNDEL;
2049 else{
2050 flags = 0L;
2051 unflagged = F_DEL;
2055 break;
2057 case F_FLAG:
2058 if((!MCMD_ISAGG(aopt) && fp->set != mc->flagged)
2059 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2060 flagit = "\\FLAGGED";
2061 if(fp->set){
2062 flags = ST_SET;
2063 unflagged = F_UNFLAG;
2065 else{
2066 flags = 0L;
2067 unflagged = F_FLAG;
2071 break;
2073 case F_FWD :
2074 if(!MCMD_ISAGG(aopt)){
2075 /* see if forwarded is defined for this message */
2076 is_set = CMD_FLAG_CLEAR;
2077 if(user_flag_is_set(state->mail_stream,
2078 mn_m2raw(msgmap, mn_get_cur(msgmap)),
2079 FORWARDED_FLAG))
2080 is_set = CMD_FLAG_SET;
2083 if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2084 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2085 flagit = FORWARDED_FLAG;
2086 if(fp->set){
2087 flags = ST_SET;
2088 unflagged = F_UNFWD;
2090 else{
2091 flags = 0L;
2092 unflagged = F_FWD;
2096 break;
2098 case F_KEYWORD:
2099 if(!MCMD_ISAGG(aopt)){
2100 /* see if this keyword is defined for this message */
2101 is_set = CMD_FLAG_CLEAR;
2102 if(user_flag_is_set(state->mail_stream,
2103 mn_m2raw(msgmap, mn_get_cur(msgmap)),
2104 fp->keyword))
2105 is_set = CMD_FLAG_SET;
2108 if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2109 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2110 flagit = fp->keyword;
2111 keyword_array[0] = fp->keyword;
2112 keyword_array[1] = NULL;
2113 if(fp->set){
2114 flags = ST_SET;
2115 unflagged = F_UNKEYWORD;
2117 else{
2118 flags = 0L;
2119 unflagged = F_KEYWORD;
2123 break;
2125 default:
2126 break;
2129 flagged = 0L;
2130 if(flags >= 0L
2131 && (seq = currentf_sequence(state->mail_stream, msgmap,
2132 unflagged, &flagged, unflagged & F_DEL,
2133 (fp->flag == F_KEYWORD
2134 && unflagged == F_KEYWORD)
2135 ? keyword_array : NULL,
2136 (fp->flag == F_KEYWORD
2137 && unflagged == F_UNKEYWORD)
2138 ? keyword_array : NULL))){
2140 * For user keywords, we may have to create the flag in
2141 * the folder if it doesn't already exist and we are setting
2142 * it (as opposed to clearing it). Mail_flag will
2143 * do that for us, but it's failure isn't very friendly
2144 * error-wise. So we try to make it a little smoother.
2146 if(!(fp->flag == F_KEYWORD || fp->flag == F_FWD) || !fp->set
2147 || ((i=user_flag_index(state->mail_stream, flagit)) >= 0
2148 && i < NUSERFLAGS))
2149 mail_flag(state->mail_stream, seq, flagit, flags);
2150 else{
2151 /* trouble, see if we can add the user flag */
2152 if(state->mail_stream->kwd_create)
2153 mail_flag(state->mail_stream, seq, flagit, flags);
2154 else{
2155 trouble++;
2157 if(some_user_flags_defined(state->mail_stream))
2158 q_status_message(SM_ORDER, 3, 4,
2159 _("No more keywords allowed in this folder!"));
2160 else if(fp->flag == F_FWD)
2161 q_status_message(SM_ORDER, 3, 4,
2162 _("Cannot add keywords for this folder so cannot set Forwarded flag"));
2163 else
2164 q_status_message(SM_ORDER, 3, 4,
2165 _("Cannot add keywords for this folder"));
2169 fs_give((void **) &seq);
2170 if(flagged && !trouble){
2171 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%slagged%s%s%s%s%s message%s%s \"%s\"",
2172 (fp->set) ? "F" : "Unf",
2173 MCMD_ISAGG(aopt) ? " " : "",
2174 MCMD_ISAGG(aopt) ? long2string(flagged) : "",
2175 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2176 ? " (of " : "",
2177 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2178 ? comatose(mn_total_cur(msgmap)) : "",
2179 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2180 ? ")" : "",
2181 MCMD_ISAGG(aopt) ? plural(flagged) : " ",
2182 MCMD_ISAGG(aopt) ? "" : long2string(mn_get_cur(msgmap)),
2183 fp->name);
2184 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2185 q_status_message(SM_ORDER, 0, 2, answer = tmp_20k_buf);
2186 rv++;
2191 free_flag_table(&ftbl);
2193 if(directly_to_maint_screen)
2194 goto go_again;
2196 if(MCMD_ISAGG(aopt))
2197 restore_selected(msgmap);
2199 if(!answer)
2200 q_status_message(SM_ORDER, 0, 2, _("No flags changed."));
2202 return rv;
2206 /*----------------------------------------------------------------------
2207 Offer concise status line flag prompt
2209 Args: state -- Various satate info
2210 flags -- flags to offer setting
2212 Result: TRUE if flag to set specified in flags struct or FALSE otw
2214 ----*/
2216 cmd_flag_prompt(struct pine *state, struct flag_screen *flags, int allow_keyword_shortcuts)
2218 int r, setflag = 1, first_char;
2219 struct flag_table *fp;
2220 ESCKEY_S *ek;
2221 char *ftext, *ftext_not;
2222 static char *flag_text =
2223 N_("Flag New, Deleted, Answered, Forwarded or Important ? ");
2224 static char *flag_text_ak =
2225 N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? ");
2226 static char *flag_text_not =
2227 N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? ");
2228 static char *flag_text_ak_not =
2229 N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? ");
2231 if(allow_keyword_shortcuts){
2232 int cnt = 0;
2233 ESCKEY_S *dp, *sp, *tp;
2235 for(sp=flag_text_opt; sp->ch != -1; sp++)
2236 cnt++;
2238 for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++)
2239 if(fp->flag == F_KEYWORD)
2240 cnt++;
2242 /* set up an ESCKEY_S list which includes invisible keys for keywords */
2243 ek = (ESCKEY_S *) fs_get((cnt + 1) * sizeof(*ek));
2244 memset(ek, 0, (cnt+1) * sizeof(*ek));
2245 for(dp=ek, sp=flag_text_opt; sp->ch != -1; sp++, dp++)
2246 *dp = *sp;
2248 for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2249 if(fp->flag == F_KEYWORD){
2250 first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2251 if(isascii(first_char) && isupper(first_char))
2252 first_char = tolower((unsigned char) first_char);
2255 * Check to see if an earlier keyword in the list, or one of
2256 * the builtin system letters already uses this character.
2257 * If so, the first one wins.
2259 for(tp=ek; tp->ch != 0; tp++)
2260 if(tp->ch == first_char)
2261 break;
2263 if(tp->ch != 0)
2264 continue; /* skip it, already used that char */
2266 dp->ch = first_char;
2267 dp->rval = first_char;
2268 dp->name = "";
2269 dp->label = "";
2270 dp++;
2274 dp->ch = -1;
2275 ftext = _(flag_text_ak);
2276 ftext_not = _(flag_text_ak_not);
2278 else{
2279 ek = flag_text_opt;
2280 ftext = _(flag_text);
2281 ftext_not = _(flag_text_not);
2284 while(1){
2285 r = radio_buttons(setflag ? ftext : ftext_not,
2286 -FOOTER_ROWS(state), ek, '*', SEQ_EXCEPTION-1,
2287 NO_HELP, RB_NORM | RB_SEQ_SENSITIVE);
2289 * It is SEQ_EXCEPTION-1 just so that it is some value that isn't
2290 * being used otherwise. The keywords use up all the possible
2291 * letters, so a negative number is good, but it has to be different
2292 * from other negative return values.
2294 if(r == SEQ_EXCEPTION-1) /* ol'cancelrooney */
2295 return(TRUE);
2296 else if(r == 10) /* return and goto flag screen */
2297 return(FALSE);
2298 else if(r == '!') /* flip intention */
2299 setflag = !setflag;
2300 else
2301 break;
2304 for(fp = (flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2305 if(r == 'n' || r == '*' || r == 'd' || r == 'a' || r == 'f'){
2306 if((r == 'n' && fp->flag == F_SEEN)
2307 || (r == '*' && fp->flag == F_FLAG)
2308 || (r == 'd' && fp->flag == F_DEL)
2309 || (r == 'f' && fp->flag == F_FWD)
2310 || (r == 'a' && fp->flag == F_ANS)){
2311 fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2312 break;
2315 else if(allow_keyword_shortcuts && fp->flag == F_KEYWORD){
2316 first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2317 if(isascii(first_char) && isupper(first_char))
2318 first_char = tolower((unsigned char) first_char);
2320 if(r == first_char){
2321 fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2322 break;
2327 if(ek != flag_text_opt)
2328 fs_give((void **) &ek);
2330 return(TRUE);
2335 * (*ft) is an array of flag_table entries.
2337 void
2338 free_flag_table(struct flag_table **ft)
2340 struct flag_table *fp;
2342 if(ft && *ft){
2343 for(fp = (*ft); fp->name; fp++){
2344 if(fp->name)
2345 fs_give((void **) &fp->name);
2347 if(fp->keyword)
2348 fs_give((void **) &fp->keyword);
2350 if(fp->comment)
2351 fs_give((void **) &fp->comment);
2354 fs_give((void **) ft);
2359 /*----------------------------------------------------------------------
2360 Execute REPLY message command
2362 Args: state -- Various satate info
2363 msgmap -- map of c-client to local message numbers
2365 Result: reply sent or not
2367 ----*/
2369 cmd_reply(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2371 int rv = 0;
2373 if(any_messages(msgmap, NULL, "to Reply to")){
2374 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2375 return rv;
2377 rv = reply(state, role);
2379 if(MCMD_ISAGG(aopt))
2380 restore_selected(msgmap);
2382 state->mangled_screen = 1;
2385 return rv;
2389 /*----------------------------------------------------------------------
2390 Execute FORWARD message command
2392 Args: state -- Various satate info
2393 msgmap -- map of c-client to local message numbers
2395 Result: selected message[s] forwarded or not
2397 ----*/
2399 cmd_forward(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2401 int rv = 0;
2403 if(any_messages(msgmap, NULL, "to Forward")){
2404 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2405 return rv;
2407 rv = forward(state, role);
2409 if(MCMD_ISAGG(aopt))
2410 restore_selected(msgmap);
2412 state->mangled_screen = 1;
2415 return rv;
2419 /*----------------------------------------------------------------------
2420 Execute BOUNCE message command
2422 Args: state -- Various satate info
2423 msgmap -- map of c-client to local message numbers
2424 aopt -- aggregate options
2426 Result: selected message[s] bounced or not
2428 ----*/
2430 cmd_bounce(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2432 int rv = 0;
2434 if(any_messages(msgmap, NULL, "to Bounce")){
2435 long i;
2436 if(MCMD_ISAGG(aopt)){
2437 if(!pseudo_selected(state->mail_stream, msgmap))
2438 return rv;
2440 else if((i = any_lflagged(msgmap, MN_SLCT)) > 0
2441 && get_lflag(state->mail_stream, msgmap,
2442 mn_m2raw(msgmap, mn_get_cur(msgmap)), MN_SLCT) == 0)
2443 q_status_message(SM_ORDER | SM_DING, 3, 4,
2444 _("WARNING: non-selected message is being bounced!"));
2445 else if (i > 1L
2446 && get_lflag(state->mail_stream, msgmap,
2447 mn_m2raw(msgmap, mn_get_cur(msgmap)), MN_SLCT))
2448 q_status_message(SM_ORDER | SM_DING, 3, 4,
2449 _("WARNING: not bouncing all selected messages!"));
2451 rv = bounce(state, role);
2453 if(MCMD_ISAGG(aopt))
2454 restore_selected(msgmap);
2456 state->mangled_footer = 1;
2459 return rv;
2463 /*----------------------------------------------------------------------
2464 Execute save message command: prompt for folder and call function to save
2466 Args: screen_line -- Line on the screen to prompt on
2467 message -- The MESSAGECACHE entry of message to save
2469 Result: The folder lister can be called to make selection; mangled screen set
2471 This does the prompting for the folder name to save to, possibly calling
2472 up the folder display for selection of folder by user.
2473 ----*/
2475 cmd_save(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
2477 char newfolder[MAILTMPLEN], nmsgs[32], *nick;
2478 int we_cancel = 0, rv = 0, save_flags;
2479 long i, raw;
2480 CONTEXT_S *cntxt = NULL;
2481 ENVELOPE *e = NULL;
2482 SaveDel del = DontAsk;
2483 SavePreserveOrder pre = DontAskPreserve;
2485 dprint((4, "\n - saving message -\n"));
2487 if(MCMD_ISAGG(aopt) && !pseudo_selected(stream, msgmap))
2488 return rv;
2490 state->ugly_consider_advancing_bit = 0;
2491 if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM, state)
2492 && msgno_any_deletedparts(stream, msgmap)
2493 && want_to(_("Saved copy will NOT include entire message! Continue"),
2494 'y', 'n', NO_HELP, WT_FLUSH_IN | WT_SEQ_SENSITIVE) != 'y'){
2495 restore_selected(msgmap);
2496 cmd_cancelled("Save message");
2497 return rv;
2500 raw = mn_m2raw(msgmap, mn_get_cur(msgmap));
2502 if(mn_total_cur(msgmap) <= 1L){
2503 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld ", mn_get_cur(msgmap));
2504 nmsgs[sizeof(nmsgs)-1] = '\0';
2505 e = pine_mail_fetchstructure(stream, raw, NULL);
2506 if(!e) {
2507 q_status_message(SM_ORDER | SM_DING, 3, 4,
2508 _("Can't save message. Error accessing folder"));
2509 restore_selected(msgmap);
2510 return rv;
2513 else{
2514 snprintf(nmsgs, sizeof(nmsgs), "%s msgs ", comatose(mn_total_cur(msgmap)));
2515 nmsgs[sizeof(nmsgs)-1] = '\0';
2517 /* e is just used to get a default save folder from the first msg */
2518 e = pine_mail_fetchstructure(stream,
2519 mn_m2raw(msgmap, mn_first_cur(msgmap)),
2520 NULL);
2523 del = (!READONLY_FOLDER(stream) && F_OFF(F_SAVE_WONT_DELETE, ps_global))
2524 ? Del : NoDel;
2525 if(mn_total_cur(msgmap) > 1L)
2526 pre = F_OFF(F_AGG_SEQ_COPY, ps_global) ? Preserve : NoPreserve;
2527 else
2528 pre = DontAskPreserve;
2530 if(save_prompt(state, &cntxt, newfolder, sizeof(newfolder), nmsgs, e,
2531 raw, NULL, &del, &pre)){
2533 if(ps_global && ps_global->ttyo){
2534 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2535 ps_global->mangled_footer = 1;
2538 save_flags = SV_FIX_DELS;
2539 if(pre == RetPreserve)
2540 save_flags |= SV_PRESERVE;
2541 if(del == RetDel)
2542 save_flags |= SV_DELETE;
2543 if(ps_global->context_list == cntxt && !strucmp(newfolder, ps_global->inbox_name))
2544 save_flags |= SV_INBOXWOCNTXT;
2546 we_cancel = busy_cue(_("Saving"), NULL, 1);
2547 i = save(state, stream, cntxt, newfolder, msgmap, save_flags);
2548 if(we_cancel)
2549 cancel_busy_cue(0);
2551 if(i == mn_total_cur(msgmap)){
2552 rv++;
2553 if(mn_total_cur(msgmap) <= 1L){
2554 int need, avail = ps_global->ttyo->screen_cols - 2;
2555 int lennick, lenfldr;
2557 if(cntxt
2558 && ps_global->context_list->next
2559 && context_isambig(newfolder)){
2560 lennick = MIN(strlen(cntxt->nickname), 500);
2561 lenfldr = MIN(strlen(newfolder), 500);
2562 need = 27 + strlen(long2string(mn_get_cur(msgmap))) +
2563 lenfldr + lennick;
2564 if(need > avail){
2565 if(lennick > 10){
2566 need -= MIN(lennick-10, need-avail);
2567 lennick -= MIN(lennick-10, need-avail);
2570 if(need > avail && lenfldr > 10)
2571 lenfldr -= MIN(lenfldr-10, need-avail);
2574 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2575 "Message %s copied to \"%s\" in <%s>",
2576 long2string(mn_get_cur(msgmap)),
2577 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2578 lenfldr, MidDots),
2579 short_str(cntxt->nickname,
2580 (char *)(tmp_20k_buf+2000), 1000,
2581 lennick, EndDots));
2582 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2584 else if((nick=folder_is_target_of_nick(newfolder, cntxt)) != NULL){
2585 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2586 "Message %s copied to \"%s\"",
2587 long2string(mn_get_cur(msgmap)),
2588 nick);
2589 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2591 else{
2592 char *f = " folder";
2594 lenfldr = MIN(strlen(newfolder), 500);
2595 need = 28 + strlen(long2string(mn_get_cur(msgmap))) +
2596 lenfldr;
2597 if(need > avail){
2598 need -= strlen(f);
2599 f = "";
2600 if(need > avail && lenfldr > 10)
2601 lenfldr -= MIN(lenfldr-10, need-avail);
2604 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
2605 "Message %s copied to%s \"%s\"",
2606 long2string(mn_get_cur(msgmap)), f,
2607 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2608 lenfldr, MidDots));
2609 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2612 else{
2613 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s messages saved",
2614 comatose(mn_total_cur(msgmap)));
2615 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2618 if(del == RetDel){
2619 strncat(tmp_20k_buf, " and deleted", SIZEOF_20KBUF-strlen(tmp_20k_buf)-1);
2620 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2623 q_status_message(SM_ORDER, 0, 3, tmp_20k_buf);
2625 if(!MCMD_ISAGG(aopt) && F_ON(F_SAVE_ADVANCES, state)){
2626 if(sp_new_mail_count(stream))
2627 process_filter_patterns(stream, msgmap,
2628 sp_new_mail_count(stream));
2630 mn_inc_cur(stream, msgmap,
2631 (in_index == View && THREADING()
2632 && sp_viewing_a_thread(stream))
2633 ? MH_THISTHD
2634 : (in_index == View)
2635 ? MH_ANYTHD : MH_NONE);
2638 state->ugly_consider_advancing_bit = 1;
2642 if(MCMD_ISAGG(aopt)) /* straighten out fakes */
2643 restore_selected(msgmap);
2645 if(del == RetDel)
2646 update_titlebar_status(); /* make sure they see change */
2648 return rv;
2652 void
2653 role_compose(struct pine *state)
2655 int action;
2657 if(F_ON(F_ALT_ROLE_MENU, state) && mn_get_total(state->msgmap) > 0L){
2658 PAT_STATE pstate;
2660 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
2661 action = radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
2662 -FOOTER_ROWS(state), choose_action,
2663 'c', 'x', h_role_compose, RB_NORM);
2665 else{
2666 q_status_message(SM_ORDER, 0, 3,
2667 _("No roles available. Use Setup/Rules to add roles."));
2668 return;
2671 else
2672 action = 'c';
2674 if(action == 'c' || action == 'r' || action == 'f' || action == 'b'){
2675 ACTION_S *role = NULL;
2676 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
2678 redraw = state->redrawer;
2679 state->redrawer = NULL;
2680 prev_screen = state->prev_screen;
2681 role = NULL;
2682 state->next_screen = SCREEN_FUN_NULL;
2684 /* Setup role */
2685 if(role_select_screen(state, &role,
2686 action == 'f' ? MC_FORWARD :
2687 action == 'r' ? MC_REPLY :
2688 action == 'b' ? MC_BOUNCE :
2689 action == 'c' ? MC_COMPOSE : 0) < 0){
2690 cmd_cancelled(action == 'f' ? _("Forward") :
2691 action == 'r' ? _("Reply") :
2692 action == 'c' ? _("Composition") : _("Bounce"));
2693 state->next_screen = prev_screen;
2694 state->redrawer = redraw;
2695 state->mangled_screen = 1;
2697 else{
2699 * If default role was selected (NULL) we need to make
2700 * up a role which won't do anything, but will cause
2701 * compose_mail to think there's already a role so that
2702 * it won't try to confirm the default.
2704 if(role)
2705 role = combine_inherited_role(role);
2706 else{
2707 role = (ACTION_S *) fs_get(sizeof(*role));
2708 memset((void *) role, 0, sizeof(*role));
2709 role->nick = cpystr("Default Role");
2712 state->redrawer = NULL;
2713 switch(action){
2714 case 'c':
2715 compose_mail(NULL, NULL, role, NULL, NULL);
2716 break;
2718 case 'r':
2719 (void) reply(state, role);
2720 break;
2722 case 'f':
2723 (void) forward(state, role);
2724 break;
2726 case 'b':
2727 (void) bounce(state, role);
2728 break;
2731 if(role)
2732 free_action(&role);
2734 state->next_screen = prev_screen;
2735 state->redrawer = redraw;
2736 state->mangled_screen = 1;
2742 /*----------------------------------------------------------------------
2743 Do the dirty work of prompting the user for a folder name
2745 Args:
2746 nfldr should be a buffer at least MAILTMPLEN long
2747 dela -- a pointer to a SaveDel. If it is
2748 DontAsk on input, don't offer Delete prompt
2749 Del on input, offer Delete command with default of Delete
2750 NoDel NoDelete
2751 RetDel and RetNoDel are return values
2754 Result:
2756 ----*/
2758 save_prompt(struct pine *state, CONTEXT_S **cntxt, char *nfldr, size_t len_nfldr,
2759 char *nmsgs, ENVELOPE *env, long int rawmsgno, char *section,
2760 SaveDel *dela, SavePreserveOrder *prea)
2762 int rc, ku = -1, n, flags, last_rc = 0, saveable_count = 0, done = 0;
2763 int delindex = 0, preindex = 0, r;
2764 char prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN];
2765 char *buf = tmp_20k_buf;
2766 char shortbuf[200];
2767 char *folder;
2768 HelpType help;
2769 SaveDel del = DontAsk;
2770 SavePreserveOrder pre = DontAskPreserve;
2771 char *deltext = NULL;
2772 static HISTORY_S *history = NULL;
2773 CONTEXT_S *tc;
2774 ESCKEY_S ekey[10];
2776 if(!cntxt)
2777 alpine_panic("no context ptr in save_prompt");
2779 init_hist(&history, HISTSIZE);
2781 if(!(folder = save_get_default(state, env, rawmsgno, section, cntxt)))
2782 return(0); /* message expunged! */
2784 /* how many context's can be saved to... */
2785 for(tc = state->context_list; tc; tc = tc->next)
2786 if(!NEWS_TEST(tc))
2787 saveable_count++;
2789 /* set up extra command option keys */
2790 rc = 0;
2791 ekey[rc].ch = ctrl('T');
2792 ekey[rc].rval = 2;
2793 ekey[rc].name = "^T";
2794 /* TRANSLATORS: command means go to Folders list */
2795 ekey[rc++].label = N_("To Fldrs");
2797 if(saveable_count > 1){
2798 ekey[rc].ch = ctrl('P');
2799 ekey[rc].rval = 10;
2800 ekey[rc].name = "^P";
2801 ekey[rc++].label = N_("Prev Collection");
2803 ekey[rc].ch = ctrl('N');
2804 ekey[rc].rval = 11;
2805 ekey[rc].name = "^N";
2806 ekey[rc++].label = N_("Next Collection");
2809 if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
2810 ekey[rc].ch = TAB;
2811 ekey[rc].rval = 12;
2812 ekey[rc].name = "TAB";
2813 /* TRANSLATORS: command asks alpine to complete the name when tab is typed */
2814 ekey[rc++].label = N_("Complete");
2817 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
2818 ekey[rc].ch = ctrl('X');
2819 ekey[rc].rval = 14;
2820 ekey[rc].name = "^X";
2821 /* TRANSLATORS: list all the matches */
2822 ekey[rc++].label = N_("ListMatches");
2825 if(dela && (*dela == NoDel || *dela == Del)){
2826 ekey[rc].ch = ctrl('R');
2827 ekey[rc].rval = 15;
2828 ekey[rc].name = "^R";
2829 delindex = rc++;
2830 del = *dela;
2833 if(prea && (*prea == NoPreserve || *prea == Preserve)){
2834 ekey[rc].ch = ctrl('W');
2835 ekey[rc].rval = 16;
2836 ekey[rc].name = "^W";
2837 preindex = rc++;
2838 pre = *prea;
2841 if(saveable_count > 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2842 ekey[rc].ch = KEY_UP;
2843 ekey[rc].rval = 10;
2844 ekey[rc].name = "";
2845 ekey[rc++].label = "";
2847 ekey[rc].ch = KEY_DOWN;
2848 ekey[rc].rval = 11;
2849 ekey[rc].name = "";
2850 ekey[rc++].label = "";
2852 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2853 ekey[rc].ch = KEY_UP;
2854 ekey[rc].rval = 30;
2855 ekey[rc].name = "";
2856 ku = rc;
2857 ekey[rc++].label = "";
2859 ekey[rc].ch = KEY_DOWN;
2860 ekey[rc].rval = 31;
2861 ekey[rc].name = "";
2862 ekey[rc++].label = "";
2865 ekey[rc].ch = -1;
2867 *nfldr = '\0';
2868 help = NO_HELP;
2869 while(!done){
2870 /* only show collection number if more than one available */
2871 if(ps_global->context_list->next)
2872 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder in <%s> [%s] : ",
2873 deltext ? deltext : "",
2874 nmsgs,
2875 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2876 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2877 else
2878 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder [%s] : ",
2879 deltext ? deltext : "",
2880 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2882 prompt[sizeof(prompt)-1] = '\0';
2885 * If the prompt won't fit, try removing deltext.
2887 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && deltext){
2888 if(ps_global->context_list->next)
2889 snprintf(prompt, sizeof(prompt), "SAVE %sto folder in <%s> [%s] : ",
2890 nmsgs,
2891 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2892 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2893 else
2894 snprintf(prompt, sizeof(prompt), "SAVE %sto folder [%s] : ",
2895 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2897 prompt[sizeof(prompt)-1] = '\0';
2901 * If the prompt still won't fit, remove the extra info contained
2902 * in nmsgs.
2904 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && *nmsgs){
2905 if(ps_global->context_list->next)
2906 snprintf(prompt, sizeof(prompt), "SAVE to folder in <%s> [%s] : ",
2907 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2908 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2909 else
2910 snprintf(prompt, sizeof(prompt), "SAVE to folder [%s] : ",
2911 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2913 prompt[sizeof(prompt)-1] = '\0';
2916 if(del != DontAsk)
2917 ekey[delindex].label = (del == NoDel) ? "Delete" : "No Delete";
2919 if(pre != DontAskPreserve)
2920 ekey[preindex].label = (pre == NoPreserve) ? "Preserve Order" : "Any Order";
2922 if(ku >= 0){
2923 if(items_in_hist(history) > 1){
2924 ekey[ku].name = HISTORY_UP_KEYNAME;
2925 ekey[ku].label = HISTORY_KEYLABEL;
2926 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
2927 ekey[ku+1].label = HISTORY_KEYLABEL;
2929 else{
2930 ekey[ku].name = "";
2931 ekey[ku].label = "";
2932 ekey[ku+1].name = "";
2933 ekey[ku+1].label = "";
2937 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
2938 rc = optionally_enter(nfldr, -FOOTER_ROWS(state), 0, len_nfldr,
2939 prompt, ekey, help, &flags);
2941 switch(rc){
2942 case -1 :
2943 q_status_message(SM_ORDER | SM_DING, 3, 3,
2944 _("Error reading folder name"));
2945 done--;
2946 break;
2948 case 0 :
2949 removing_trailing_white_space(nfldr);
2950 removing_leading_white_space(nfldr);
2952 if(*nfldr || *folder){
2953 char *p, *name, *fullname = NULL;
2954 int exists, breakout = FALSE;
2956 if(!*nfldr){
2957 strncpy(nfldr, folder, len_nfldr-1);
2958 nfldr[len_nfldr-1] = '\0';
2961 save_hist(history, nfldr, 0, (void *) *cntxt);
2963 if(!(name = folder_is_nick(nfldr, FOLDERS(*cntxt), 0)))
2964 name = nfldr;
2966 if(update_folder_spec(expanded, sizeof(expanded), name)){
2967 strncpy(name = nfldr, expanded, len_nfldr-1);
2968 nfldr[len_nfldr-1] = '\0';
2971 exists = folder_name_exists(*cntxt, name, &fullname);
2973 if(exists == FEX_ERROR){
2974 q_status_message1(SM_ORDER, 0, 3,
2975 _("Problem accessing folder \"%s\""),
2976 nfldr);
2977 done--;
2979 else{
2980 if(fullname){
2981 strncpy(name = nfldr, fullname, len_nfldr-1);
2982 nfldr[len_nfldr-1] = '\0';
2983 fs_give((void **) &fullname);
2984 breakout = TRUE;
2987 if(exists & FEX_ISFILE){
2988 done++;
2990 else if((exists & FEX_ISDIR)){
2991 char tmp[MAILTMPLEN];
2993 tc = *cntxt;
2994 if(breakout){
2995 CONTEXT_S *fake_context;
2996 size_t l;
2998 strncpy(tmp, name, sizeof(tmp));
2999 tmp[sizeof(tmp)-2-1] = '\0';
3000 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
3001 if(l < sizeof(tmp)){
3002 tmp[l] = tc->dir->delim;
3003 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
3006 else
3007 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
3009 tmp[sizeof(tmp)-1] = '\0';
3011 fake_context = new_context(tmp, 0);
3012 nfldr[0] = '\0';
3013 done = display_folder_list(&fake_context, nfldr,
3014 1, folders_for_save);
3015 free_context(&fake_context);
3017 else if(tc->dir->delim
3018 && (p = strrindex(name, tc->dir->delim))
3019 && *(p+1) == '\0')
3020 done = display_folder_list(cntxt, nfldr,
3021 1, folders_for_save);
3022 else{
3023 q_status_message1(SM_ORDER, 3, 3,
3024 _("\"%s\" is a directory"), name);
3025 if(tc->dir->delim
3026 && !((p=strrindex(name, tc->dir->delim)) && *(p+1) == '\0')){
3027 strncpy(tmp, name, sizeof(tmp));
3028 tmp[sizeof(tmp)-1] = '\0';
3029 snprintf(nfldr, len_nfldr, "%s%c", tmp, tc->dir->delim);
3033 else{ /* Doesn't exist, create! */
3034 if((fullname = folder_as_breakout(*cntxt, name)) != NULL){
3035 strncpy(name = nfldr, fullname, len_nfldr-1);
3036 nfldr[len_nfldr-1] = '\0';
3037 fs_give((void **) &fullname);
3040 switch(create_for_save(*cntxt, name)){
3041 case 1 : /* success */
3042 done++;
3043 break;
3044 case 0 : /* error */
3045 case -1 : /* declined */
3046 done--;
3047 break;
3052 break;
3054 /* else fall thru like they cancelled */
3056 case 1 :
3057 cmd_cancelled("Save message");
3058 done--;
3059 break;
3061 case 2 :
3062 r = display_folder_list(cntxt, nfldr, 0, folders_for_save);
3064 if(r)
3065 done++;
3067 break;
3069 case 3 :
3070 helper(h_save, _("HELP FOR SAVE"), HLPD_SIMPLE);
3071 ps_global->mangled_screen = 1;
3072 break;
3074 case 4 : /* redraw */
3075 break;
3077 case 10 : /* previous collection */
3078 for(tc = (*cntxt)->prev; tc; tc = tc->prev)
3079 if(!NEWS_TEST(tc))
3080 break;
3082 if(!tc){
3083 CONTEXT_S *tc2;
3085 for(tc2 = (tc = (*cntxt))->next; tc2; tc2 = tc2->next)
3086 if(!NEWS_TEST(tc2))
3087 tc = tc2;
3090 *cntxt = tc;
3091 break;
3093 case 11 : /* next collection */
3094 tc = (*cntxt);
3097 if(((*cntxt) = (*cntxt)->next) == NULL)
3098 (*cntxt) = ps_global->context_list;
3099 while(NEWS_TEST(*cntxt) && (*cntxt) != tc);
3100 break;
3102 case 12 : /* file name completion */
3103 if(!folder_complete(*cntxt, nfldr, len_nfldr, &n)){
3104 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
3105 r = display_folder_list(cntxt, nfldr, 1, folders_for_save);
3106 if(r)
3107 done++; /* bingo! */
3108 else
3109 rc = 0; /* burn last_rc */
3111 else
3112 Writechar(BELL, 0);
3115 break;
3117 case 14 : /* file name completion */
3118 r = display_folder_list(cntxt, nfldr, 2, folders_for_save);
3119 if(r)
3120 done++; /* bingo! */
3121 else
3122 rc = 0; /* burn last_rc */
3124 break;
3126 case 15 : /* Delete / No Delete */
3127 del = (del == NoDel) ? Del : NoDel;
3128 deltext = (del == NoDel) ? " (no delete)" : " (and delete)";
3129 break;
3131 case 16 : /* Preserve Order or not */
3132 pre = (pre == NoPreserve) ? Preserve : NoPreserve;
3133 break;
3135 case 30 :
3136 if((p = get_prev_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3137 strncpy(nfldr, p, len_nfldr);
3138 nfldr[len_nfldr-1] = '\0';
3139 if(history->hist[history->curindex])
3140 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3142 else
3143 Writechar(BELL, 0);
3145 break;
3147 case 31 :
3148 if((p = get_next_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3149 strncpy(nfldr, p, len_nfldr);
3150 nfldr[len_nfldr-1] = '\0';
3151 if(history->hist[history->curindex])
3152 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3154 else
3155 Writechar(BELL, 0);
3157 break;
3159 default :
3160 alpine_panic("Unhandled case");
3161 break;
3164 last_rc = rc;
3167 ps_global->mangled_footer = 1;
3169 if(done < 0)
3170 return(0);
3172 if(*nfldr){
3173 strncpy(ps_global->last_save_folder, nfldr, sizeof(ps_global->last_save_folder)-1);
3174 ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0';
3175 if(*cntxt)
3176 ps_global->last_save_context = *cntxt;
3178 else{
3179 strncpy(nfldr, folder, len_nfldr-1);
3180 nfldr[len_nfldr-1] = '\0';
3183 /* nickname? Copy real name to nfldr */
3184 if(*cntxt
3185 && context_isambig(nfldr)
3186 && (p = folder_is_nick(nfldr, FOLDERS(*cntxt), 0))){
3187 strncpy(nfldr, p, len_nfldr-1);
3188 nfldr[len_nfldr-1] = '\0';
3191 if(dela && (*dela == NoDel || *dela == Del))
3192 *dela = (del == NoDel) ? RetNoDel : RetDel;
3194 if(prea && (*prea == NoPreserve || *prea == Preserve))
3195 *prea = (pre == NoPreserve) ? RetNoPreserve : RetPreserve;
3197 return(1);
3201 /*----------------------------------------------------------------------
3202 Prompt user before implicitly creating a folder for saving
3204 Args: context - context to create folder in
3205 folder - folder name to create
3207 Result: 1 on proceed, -1 on decline, 0 on error
3209 ----*/
3211 create_for_save_prompt(CONTEXT_S *context, char *folder, int sequence_sensitive)
3213 if(context && ps_global->context_list->next && context_isambig(folder)){
3214 if(context->use & CNTXT_INCMNG){
3215 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3216 _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
3217 folder, (strlen(folder) > 15) ? "..." : "");
3218 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
3219 return(0); /* error */
3222 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3223 _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
3224 folder, (strlen(folder) > 15) ? "..." : "",
3225 context->nickname,
3226 (strlen(context->nickname) > 15) ? "..." : "");
3228 else
3229 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
3230 _("Folder \"%.40s%s\" doesn't exist. Create"),
3231 folder, strlen(folder) > 40 ? "..." : "");
3233 if(want_to(tmp_20k_buf, 'y', 'n',
3234 NO_HELP, (sequence_sensitive) ? WT_SEQ_SENSITIVE : WT_NORM) != 'y'){
3235 cmd_cancelled("Save message");
3236 return(-1);
3239 return(1);
3244 /*----------------------------------------------------------------------
3245 Expunge messages from current folder
3247 Args: state -- pointer to struct holding a bunch of pine state
3248 msgmap -- table mapping msg nums to c-client sequence nums
3249 qline -- screen line to ask questions on
3250 agg -- boolean indicating we're to operate on aggregate set
3252 Result:
3253 ----*/
3255 cmd_expunge(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int agg)
3257 long del_count, prefilter_del_count = 0;
3258 int we_cancel = 0, rv = 0;
3259 char prompt[MAX_SCREEN_COLS+1];
3260 char *sequence;
3261 COLOR_PAIR *lastc = NULL;
3263 dprint((2, "\n - expunge -\n"));
3265 del_count = 0;
3267 sequence = MCMD_ISAGG(agg) ? selected_sequence(stream, msgmap, NULL, 0) : NULL;
3269 if(MCMD_ISAGG(agg)){
3270 long i;
3271 MESSAGECACHE *mc;
3272 for(i = 1L; i <= stream->nmsgs; i++){
3273 if((mc = mail_elt(stream, i)) != NULL
3274 && mc->sequence && mc->deleted)
3275 del_count++;
3277 if(del_count == 0){
3278 q_status_message(SM_ORDER, 0, 4,
3279 _("No selected messages are deleted"));
3280 return 0;
3282 } else {
3283 if(!any_messages(msgmap, NULL, "to Expunge"))
3284 return rv;
3287 if(IS_NEWS(stream) && stream->rdonly){
3288 if(!MCMD_ISAGG(agg))
3289 del_count = count_flagged(stream, F_DEL);
3290 if(del_count > 0L){
3291 state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3292 snprintf(prompt, sizeof(prompt), "Exclude %ld message%s from %.*s", del_count,
3293 plural(del_count), MAX_SCREEN_COLS+1-40,
3294 pretty_fn(state->cur_folder));
3295 prompt[sizeof(prompt)-1] = '\0';
3296 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3297 || (F_ON(F_AUTO_EXPUNGE, state)
3298 && (state->context_current
3299 && (state->context_current->use & CNTXT_INCMNG))
3300 && context_isambig(state->cur_folder))
3301 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
3303 if(F_ON(F_NEWS_CROSS_DELETE, state))
3304 cross_delete_crossposts(stream);
3306 msgno_exclude_deleted(stream, msgmap, sequence);
3307 clear_index_cache(stream, 0);
3310 * This is kind of surprising at first. For most sort
3311 * orders, if the whole set is sorted, then any subset
3312 * is also sorted. Not so for threaded sorts.
3314 if(SORT_IS_THREADED(msgmap))
3315 refresh_sort(stream, msgmap, SRT_NON);
3317 state->mangled_body = 1;
3318 state->mangled_header = 1;
3319 q_status_message2(SM_ORDER, 0, 4,
3320 "%s message%s excluded",
3321 long2string(del_count),
3322 plural(del_count));
3324 else
3325 any_messages(NULL, NULL, "Excluded");
3327 else
3328 any_messages(NULL, "deleted", "to Exclude");
3330 return del_count;
3332 else if(READONLY_FOLDER(stream)){
3333 q_status_message(SM_ORDER, 0, 4,
3334 _("Can't expunge. Folder is read-only"));
3335 return del_count;
3338 if(!MCMD_ISAGG(agg)){
3339 prefilter_del_count = count_flagged(stream, F_DEL|F_NOFILT);
3340 mail_expunge_prefilter(stream, MI_NONE);
3341 del_count = count_flagged(stream, F_DEL|F_NOFILT);
3344 if(del_count != 0){
3345 int ret;
3346 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3347 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3348 snprintf(prompt, sizeof(prompt), "Expunge %ld message%s from %.*s", del_count,
3349 plural(del_count), MAX_SCREEN_COLS+1-40,
3350 pretty_fn((char *) fname));
3351 if(fname) fs_give((void **)&fname);
3352 prompt[sizeof(prompt)-1] = '\0';
3353 state->mangled_footer = 1;
3355 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3356 || (F_ON(F_AUTO_EXPUNGE, state)
3357 && ((!strucmp(state->cur_folder,state->inbox_name))
3358 || (state->context_current->use & CNTXT_INCMNG))
3359 && context_isambig(state->cur_folder))
3360 || (ret=want_to(prompt, 'y', 0, NO_HELP, WT_NORM)) == 'y')
3361 ret = 'y';
3363 if(ret == 'x')
3364 cmd_cancelled("Expunge");
3366 if(ret != 'y')
3367 return 0;
3370 dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
3371 mn_get_total(msgmap), mn_get_cur(msgmap), del_count));
3373 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3374 state->VAR_TITLE_BACK_COLOR,
3375 PSC_REV|PSC_RET);
3377 PutLine0(0, 0, "**"); /* indicate delay */
3379 if(lastc){
3380 (void)pico_set_colorp(lastc, PSC_NONE);
3381 free_color_pair(&lastc);
3384 MoveCursor(state->ttyo->screen_rows -FOOTER_ROWS(state), 0);
3385 fflush(stdout);
3387 we_cancel = busy_cue(_("Expunging"), NULL, 1);
3389 if(cmd_expunge_work(stream, msgmap, sequence))
3390 state->mangled_body = 1;
3392 if(sequence)
3393 fs_give((void **)&sequence);
3395 if(we_cancel)
3396 cancel_busy_cue((sp_expunge_count(stream) > 0) ? 0 : -1);
3398 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3399 state->VAR_TITLE_BACK_COLOR,
3400 PSC_REV|PSC_RET);
3401 PutLine0(0, 0, " "); /* indicate delay's over */
3403 if(lastc){
3404 (void)pico_set_colorp(lastc, PSC_NONE);
3405 free_color_pair(&lastc);
3408 fflush(stdout);
3410 if(sp_expunge_count(stream) > 0){
3412 * This is kind of surprising at first. For most sort
3413 * orders, if the whole set is sorted, then any subset
3414 * is also sorted. Not so for threaded sorts.
3416 if(SORT_IS_THREADED(msgmap))
3417 refresh_sort(stream, msgmap, SRT_NON);
3419 else{
3420 if(del_count){
3421 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3422 q_status_message1(SM_ORDER, 0, 3,
3423 _("No messages expunged from folder \"%s\""),
3424 pretty_fn((char *) fname));
3425 if(fname) fs_give((void **)&fname);
3427 else if(!prefilter_del_count)
3428 q_status_message(SM_ORDER, 0, 3,
3429 _("No messages marked deleted. No messages expunged."));
3431 return del_count;
3435 /*----------------------------------------------------------------------
3436 Expunge_and_close callback to prompt user for confirmation
3438 Args: stream -- folder's stream
3439 folder -- name of folder containing folders
3440 deleted -- number of del'd msgs
3442 Result: 'y' to continue with expunge
3443 ----*/
3445 expunge_prompt(MAILSTREAM *stream, char *folder, long int deleted)
3447 long max_folder;
3448 int charcnt = 0;
3449 char prompt_b[MAX_SCREEN_COLS+1], temp[MAILTMPLEN+1], buff[MAX_SCREEN_COLS+1];
3450 char *short_folder_name;
3452 if(deleted == 1)
3453 charcnt = 1;
3454 else{
3455 snprintf(temp, sizeof(temp), "%ld", deleted);
3456 charcnt = strlen(temp)+1;
3459 max_folder = MAX(1,MAXPROMPT - (36+charcnt));
3460 strncpy(temp, folder, sizeof(temp));
3461 temp[sizeof(temp)-1] = '\0';
3462 short_folder_name = short_str(temp,buff,sizeof(buff),max_folder,FrontDots);
3464 if(IS_NEWS(stream))
3465 snprintf(prompt_b, sizeof(prompt_b),
3466 "Delete %s%ld message%s from \"%s\"",
3467 (deleted > 1L) ? "all " : "", deleted,
3468 plural(deleted), short_folder_name);
3469 else
3470 snprintf(prompt_b, sizeof(prompt_b),
3471 "Expunge the %ld deleted message%s from \"%s\"",
3472 deleted, deleted == 1 ? "" : "s",
3473 short_folder_name);
3475 return(want_to(prompt_b, 'y', 0, NO_HELP, WT_NORM));
3480 * This is used with multiple append saves. Call it once before
3481 * the series of appends with SSCP_INIT and once after all are
3482 * done with SSCP_END. In between, it is called automatically
3483 * from save_fetch_append or save_fetch_append_cb when we need
3484 * to ask the user if he or she wants to continue even though
3485 * announced message size doesn't match the actual message size.
3486 * As of 2008-02-29 the gmail IMAP server has these size mismatches
3487 * on a regular basis even though the data is ok.
3490 save_size_changed_prompt(long msgno, int flags)
3492 int ret;
3493 char prompt[100];
3494 static int remember_the_yes = 0;
3495 static int possible_corruption = 0;
3496 static ESCKEY_S save_size_opts[] = {
3497 {'y', 'y', "Y", "Yes"},
3498 {'n', 'n', "N", "No"},
3499 {'a', 'a', "A", "yes to All"},
3500 {-1, 0, NULL, NULL}
3503 if(F_ON(F_IGNORE_SIZE, ps_global))
3504 return 'y';
3506 if(flags & SSCP_INIT || flags & SSCP_END){
3507 if(flags & SSCP_END && possible_corruption)
3508 q_status_message(SM_ORDER, 3, 3, "There is possible data corruption, check the results");
3510 remember_the_yes = 0;
3511 possible_corruption = 0;
3512 ps_global->noshow_error = 0;
3513 ps_global->noshow_warn = 0;
3514 return(0);
3517 if(remember_the_yes){
3518 snprintf(prompt, sizeof(prompt),
3519 "Message to save shrank! (msg # %ld): Continuing", msgno);
3520 q_status_message(SM_ORDER, 0, 3, prompt);
3521 display_message('x');
3522 return(remember_the_yes);
3525 snprintf(prompt, sizeof(prompt),
3526 "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno);
3527 ret = radio_buttons(prompt, -FOOTER_ROWS(ps_global), save_size_opts,
3528 'n', 0, h_save_size_changed, RB_NORM|RB_NO_NEWMAIL);
3530 switch(ret){
3531 case 'a':
3532 remember_the_yes = 'y';
3533 possible_corruption++;
3534 return(remember_the_yes);
3536 case 'y':
3537 possible_corruption++;
3538 return('y');
3540 default:
3541 possible_corruption = 0;
3542 ps_global->noshow_error = 1;
3543 ps_global->noshow_warn = 1;
3544 break;
3547 return('n');
3551 /*----------------------------------------------------------------------
3552 Expunge_and_close callback that happens once the decision to expunge
3553 and close has been made and before expunging and closing begins
3556 Args: stream -- folder's stream
3557 folder -- name of folder containing folders
3558 deleted -- number of del'd msgs
3560 Result: 'y' to continue with expunge
3561 ----*/
3562 void
3563 expunge_and_close_begins(int flags, char *folder)
3565 if(!(flags & EC_NO_CLOSE)){
3566 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
3567 q_status_message1(SM_INFO, 0, 1, "Closing \"%.200s\"...", (char *) fname);
3568 flush_status_messages(1);
3569 if(fname) fs_give((void **)&fname);
3574 /*----------------------------------------------------------------------
3575 Export a message to a plain file in users home directory
3577 Args: state -- pointer to struct holding a bunch of pine state
3578 msgmap -- table mapping msg nums to c-client sequence nums
3579 qline -- screen line to ask questions on
3580 agg -- boolean indicating we're to operate on aggregate set
3582 Result:
3583 ----*/
3585 cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt)
3587 char filename[MAXPATH+1], full_filename[MAXPATH+1], *err;
3588 char nmsgs[80];
3589 int r, leading_nl, failure = 0, orig_errno = 0, rflags = GER_NONE;
3590 int flags = GE_IS_EXPORT | GE_SEQ_SENSITIVE, rv = 0;
3591 ENVELOPE *env;
3592 MESSAGECACHE *mc;
3593 BODY *b;
3594 long i, count = 0L, start_of_append = 0, rawno;
3595 gf_io_t pc;
3596 STORE_S *store;
3597 struct variable *vars = state ? ps_global->vars : NULL;
3598 ESCKEY_S export_opts[5];
3599 static HISTORY_S *history = NULL;
3601 if(ps_global->restricted){
3602 q_status_message(SM_ORDER, 0, 3,
3603 "Alpine demo can't export messages to files");
3604 return rv;
3607 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
3608 return rv;
3610 export_opts[i = 0].ch = ctrl('T');
3611 export_opts[i].rval = 10;
3612 export_opts[i].name = "^T";
3613 export_opts[i++].label = N_("To Files");
3615 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3616 if(ps_global->VAR_DOWNLOAD_CMD && ps_global->VAR_DOWNLOAD_CMD[0]){
3617 export_opts[i].ch = ctrl('V');
3618 export_opts[i].rval = 12;
3619 export_opts[i].name = "^V";
3620 /* TRANSLATORS: this is an abbreviation for Download Messages */
3621 export_opts[i++].label = N_("Downld Msg");
3623 #endif /* !(DOS || MAC) */
3625 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
3626 export_opts[i].ch = ctrl('I');
3627 export_opts[i].rval = 11;
3628 export_opts[i].name = "TAB";
3629 export_opts[i++].label = N_("Complete");
3632 #if 0
3633 /* Commented out since it's not yet support! */
3634 if(F_ON(F_ENABLE_SUB_LISTS,ps_global)){
3635 export_opts[i].ch = ctrl('X');
3636 export_opts[i].rval = 14;
3637 export_opts[i].name = "^X";
3638 export_opts[i++].label = N_("ListMatches");
3640 #endif
3643 * If message has attachments, add a toggle that will allow the user
3644 * to save all of the attachments to a single directory, using the
3645 * names provided with the attachments or part names. What we'll do is
3646 * export the message as usual, and then export the attachments into
3647 * a subdirectory that did not exist before. The subdir will be named
3648 * something based on the name of the file being saved to, but a
3649 * unique, new name.
3651 if(!MCMD_ISAGG(aopt)
3652 && state->mail_stream
3653 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3654 && rawno <= state->mail_stream->nmsgs
3655 && (env = pine_mail_fetchstructure(state->mail_stream, rawno, &b))
3656 && b
3657 && b->type == TYPEMULTIPART
3658 && b->subtype
3659 && strucmp(b->subtype, "ALTERNATIVE") != 0){
3660 PART *part;
3662 part = b->nested.part; /* 1st part */
3663 if(part && part->next)
3664 flags |= GE_ALLPARTS;
3667 export_opts[i].ch = -1;
3668 filename[0] = '\0';
3670 if(mn_total_cur(msgmap) <= 1L){
3671 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld", mn_get_cur(msgmap));
3672 nmsgs[sizeof(nmsgs)-1] = '\0';
3674 else{
3675 snprintf(nmsgs, sizeof(nmsgs), "%s messages", comatose(mn_total_cur(msgmap)));
3676 nmsgs[sizeof(nmsgs)-1] = '\0';
3679 r = get_export_filename(state, filename, NULL, full_filename,
3680 sizeof(filename), nmsgs, "EXPORT",
3681 export_opts, &rflags, qline, flags, &history);
3683 if(r < 0){
3684 switch(r){
3685 case -1:
3686 cmd_cancelled("Export message");
3687 break;
3689 case -2:
3690 q_status_message1(SM_ORDER, 0, 2,
3691 _("Can't export to file outside of %s"),
3692 VAR_OPER_DIR);
3693 break;
3696 goto fini;
3698 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3699 else if(r == 12){ /* Download */
3700 char cmd[MAXPATH], *tfp = NULL;
3701 int next = 0;
3702 PIPE_S *syspipe;
3703 STORE_S *so;
3704 gf_io_t pc;
3706 if(ps_global->restricted){
3707 q_status_message(SM_ORDER | SM_DING, 3, 3,
3708 "Download disallowed in restricted mode");
3709 goto fini;
3712 err = NULL;
3713 tfp = temp_nam(NULL, "pd");
3714 build_updown_cmd(cmd, sizeof(cmd), ps_global->VAR_DOWNLOAD_CMD_PREFIX,
3715 ps_global->VAR_DOWNLOAD_CMD, tfp);
3716 dprint((1, "Download cmd called: \"%s\"\n", cmd));
3717 if((so = so_get(FileStar, tfp, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){
3718 gf_set_so_writec(&pc, so);
3720 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
3721 if(!(state->mail_stream
3722 && (rawno = mn_m2raw(msgmap, i)) > 0L
3723 && rawno <= state->mail_stream->nmsgs
3724 && (mc = mail_elt(state->mail_stream, rawno))
3725 && mc->valid))
3726 mc = NULL;
3728 if(!(env = pine_mail_fetchstructure(state->mail_stream,
3729 mn_m2raw(msgmap, i), &b))
3730 || !bezerk_delimiter(env, mc, pc, next++)
3731 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
3732 env, b, NULL, FM_NEW_MESS | FM_NOWRAP, pc)){
3733 q_status_message(SM_ORDER | SM_DING, 3, 3,
3734 err = "Error writing tempfile for download");
3735 break;
3739 gf_clear_so_writec(so);
3740 if(so_give(&so)){ /* close file */
3741 if(!err)
3742 err = "Error writing tempfile for download";
3745 if(!err){
3746 if((syspipe = open_system_pipe(cmd, NULL, NULL,
3747 PIPE_USER | PIPE_RESET,
3748 0, pipe_callback, pipe_report_error)) != NULL)
3749 (void) close_system_pipe(&syspipe, NULL, pipe_callback);
3750 else
3751 q_status_message(SM_ORDER | SM_DING, 3, 3,
3752 err = _("Error running download command"));
3755 else
3756 q_status_message(SM_ORDER | SM_DING, 3, 3,
3757 err = "Error building temp file for download");
3759 if(tfp){
3760 our_unlink(tfp);
3761 fs_give((void **)&tfp);
3764 if(!err)
3765 q_status_message(SM_ORDER, 0, 3, _("Download Command Completed"));
3767 goto fini;
3769 #endif /* !(DOS || MAC) */
3772 if(rflags & GER_APPEND)
3773 leading_nl = 1;
3774 else
3775 leading_nl = 0;
3777 dprint((5, "Opening file \"%s\" for export\n",
3778 full_filename ? full_filename : "?"));
3780 if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE))){
3781 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3782 /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
3783 _("Error opening file \"%s\" to export message: %s"),
3784 full_filename, error_description(errno));
3785 goto fini;
3787 else
3788 gf_set_so_writec(&pc, store);
3790 err = NULL;
3791 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), count++){
3792 env = pine_mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i),
3793 &b);
3794 if(!env) {
3795 err = _("Can't export message. Error accessing mail folder");
3796 failure = 1;
3797 break;
3800 if(!(state->mail_stream
3801 && (rawno = mn_m2raw(msgmap, i)) > 0L
3802 && rawno <= state->mail_stream->nmsgs
3803 && (mc = mail_elt(state->mail_stream, rawno))
3804 && mc->valid))
3805 mc = NULL;
3807 start_of_append = so_tell(store);
3808 if(!bezerk_delimiter(env, mc, pc, leading_nl)
3809 || !format_message(mn_m2raw(msgmap, i), env, b, NULL,
3810 FM_NEW_MESS | FM_NOWRAP, pc)){
3811 orig_errno = errno; /* save in case things are really bad */
3812 failure = 1; /* pop out of here */
3813 break;
3816 leading_nl = 1;
3819 gf_clear_so_writec(store);
3820 if(so_give(&store)) /* release storage */
3821 failure++;
3823 if(failure){
3824 our_truncate(full_filename, (off_t)start_of_append);
3825 if(err){
3826 dprint((1, "FAILED Export: fetch(%ld): %s\n",
3827 i, err ? err : "?"));
3828 q_status_message(SM_ORDER | SM_DING, 3, 4, err);
3830 else{
3831 dprint((1, "FAILED Export: file \"%s\" : %s\n",
3832 full_filename ? full_filename : "?",
3833 error_description(orig_errno)));
3834 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3835 /* TRANSLATORS: Error exporting to <filename>: <error text> */
3836 _("Error exporting to \"%s\" : %s"),
3837 filename, error_description(orig_errno));
3840 else{
3841 if(rflags & GER_ALLPARTS && full_filename[0]){
3842 char dir[MAXPATH+1];
3843 char lfile[MAXPATH+1];
3844 int ok = 0, tries = 0, saved = 0, errs = 0, counter = 2;
3845 ATTACH_S *a;
3848 * Now we want to save all of the attachments to a subdirectory.
3849 * To make it easier for us and probably easier for the user, and
3850 * to prevent the user from shooting himself in the foot, we
3851 * make a new subdirectory so that we can't possibly step on
3852 * any existing files, and we don't need any interaction with the
3853 * user while saving.
3855 * We'll just use the directory name full_filename.d or if that
3856 * already exists and isn't empty, we'll try adding a suffix to
3857 * that until we get something to use.
3860 if(strlen(full_filename) + strlen(".d") + 1 > sizeof(dir)){
3861 q_status_message1(SM_ORDER | SM_DING, 3, 4,
3862 _("Can't save attachments, filename too long: %s"),
3863 full_filename);
3864 goto fini;
3867 ok = 0;
3868 snprintf(dir, sizeof(dir), "%.*s.d", MAXPATH-2, full_filename);
3869 dir[sizeof(dir)-1] = '\0';
3871 do {
3872 tries++;
3873 switch(r = is_writable_dir(dir)){
3874 case 0: /* exists and is a writable dir */
3876 * We could figure out if it is empty and use it in
3877 * that case, but that sounds like a lot of work, so
3878 * just fall through to default.
3881 default:
3882 if(strlen(full_filename) + strlen(".d") + 1 +
3883 1 + strlen(long2string((long) tries)) > sizeof(dir)){
3884 q_status_message(SM_ORDER | SM_DING, 3, 4,
3885 "Problem saving attachments");
3886 goto fini;
3889 snprintf(dir, sizeof(dir), "%.*s.d_%s", MAXPATH- (int) strlen(long2string((long) tries))-3, full_filename,
3890 long2string((long) tries));
3891 dir[sizeof(dir)-1] = '\0';
3892 break;
3894 case 3: /* doesn't exist, that's good! */
3895 /* make new directory */
3896 ok++;
3897 break;
3899 } while(!ok && tries < 1000);
3901 if(tries >= 1000){
3902 q_status_message(SM_ORDER | SM_DING, 3, 4,
3903 _("Problem saving attachments"));
3904 goto fini;
3907 /* create the new directory */
3908 if(our_mkdir(dir, 0700)){
3909 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3910 _("Problem saving attachments: %s: %s"), dir,
3911 error_description(errno));
3912 goto fini;
3915 if(!(state->mail_stream
3916 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3917 && rawno <= state->mail_stream->nmsgs
3918 && (env=pine_mail_fetchstructure(state->mail_stream,rawno,&b))
3919 && b)){
3920 q_status_message(SM_ORDER | SM_DING, 3, 4,
3921 _("Problem reading message"));
3922 goto fini;
3925 zero_atmts(state->atmts);
3926 describe_mime(b, "", 1, 1, 0, 0);
3928 a = state->atmts;
3929 if(a && a->description) /* skip main body part */
3930 a++;
3932 for(; a->description != NULL; a++){
3933 /* skip over these parts of the message */
3934 if(MIME_MSG_A(a) || MIME_DGST_A(a) || MIME_VCARD_A(a))
3935 continue;
3937 lfile[0] = '\0';
3938 (void) get_filename_parameter(lfile, sizeof(lfile), a->body, NULL);
3940 if(lfile[0] == '\0'){ /* MAXPATH + 1 = sizeof(lfile) */
3941 snprintf(lfile, sizeof(lfile), "part_%.*s", MAXPATH+1-6,
3942 a->number ? a->number : "?");
3943 lfile[sizeof(lfile)-1] = '\0';
3946 if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + 1
3947 > sizeof(filename)){
3948 dprint((2,
3949 "FAILED Att Export: name too long: %s\n",
3950 dir, S_FILESEP, lfile));
3951 errs++;
3952 continue;
3955 /* although files are being saved in a unique directory, there is
3956 * no guarantee that attachment names have unique names, so we have
3957 * to make sure that we are not constantly rewriting the same file name
3958 * over and over. In order to avoid this we test if the file already exists,
3959 * and if so, we write a counter name in the file name, just before the
3960 * extension of the file, and separate it with an underscore.
3962 snprintf(filename, sizeof(filename), "%.*s%.*s%.*s", (int) strlen(dir), dir,
3963 (int) strlen(S_FILESEP), S_FILESEP,
3964 MAXPATH - (int) strlen(dir) - (int) strlen(S_FILESEP), lfile);
3965 filename[sizeof(filename)-1] = '\0';
3966 while((ok = can_access(filename, ACCESS_EXISTS)) == 0 && errs == 0){
3967 char *ext, count[MAXPATH+1];
3968 unsigned long total;
3969 snprintf(count, sizeof(count), "%d", counter);
3970 if((ext = strrchr(lfile, '.')) != NULL)
3971 *ext = '\0';
3972 total = strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + strlen(count) + 3
3973 + (ext ? strlen(ext+1) : 0);
3974 if(total > sizeof(filename)){
3975 dprint((2,
3976 "FAILED Att Export: name too long: %s\n",
3977 dir, S_FILESEP, lfile));
3978 errs++;
3979 continue;
3981 snprintf(filename, sizeof(filename), "%.*s%.*s%.*s%.*s%.*d%.*s%.*s",
3982 (int) strlen(dir), dir, (int) strlen(S_FILESEP), S_FILESEP,
3983 (int) strlen(lfile), lfile,
3984 ext ? 1 : 0, ext ? "_" : "",
3985 (int) strlen(count), counter++,
3986 ext ? 1 : 0, ext ? "." : "",
3987 ext ? (int) (sizeof(filename) - total) : 0,
3988 ext ? ext+1 : "");
3989 filename[sizeof(filename)-1] = '\0';
3992 if(write_attachment_to_file(state->mail_stream, rawno,
3993 a, GER_NONE, filename) == 1)
3994 saved++;
3995 else
3996 errs++;
3999 if(errs){
4000 if(saved)
4001 q_status_message1(SM_ORDER, 3, 3,
4002 "Errors saving some attachments, %s attachments saved",
4003 long2string((long) saved));
4004 else
4005 q_status_message(SM_ORDER, 3, 3,
4006 _("Problems saving attachments"));
4008 else{
4009 if(saved)
4010 q_status_message2(SM_ORDER, 0, 3,
4011 /* TRANSLATORS: Saved <how many> attachments to <directory name> */
4012 _("Saved %s attachments to %s"),
4013 long2string((long) saved), dir);
4014 else
4015 q_status_message(SM_ORDER, 3, 3, _("No attachments to save"));
4018 else if(mn_total_cur(msgmap) > 1L)
4019 q_status_message4(SM_ORDER,0,3,
4020 "%s message%s %s to file \"%s\"",
4021 long2string(count), plural(count),
4022 rflags & GER_OVER
4023 ? "overwritten"
4024 : rflags & GER_APPEND ? "appended" : "exported",
4025 filename);
4026 else
4027 q_status_message3(SM_ORDER,0,3,
4028 "Message %s %s to file \"%s\"",
4029 long2string(mn_get_cur(msgmap)),
4030 rflags & GER_OVER
4031 ? "overwritten"
4032 : rflags & GER_APPEND ? "appended" : "exported",
4033 filename);
4034 rv++;
4037 fini:
4038 if(MCMD_ISAGG(aopt))
4039 restore_selected(msgmap);
4041 return rv;
4046 * Ask user what file to export to. Export from srcstore to that file.
4048 * Args ps -- pine struct
4049 * srctext -- pointer to source text
4050 * srctype -- type of that source text
4051 * prompt_msg -- see get_export_filename
4052 * lister_msg -- "
4054 * Returns: != 0 : error
4055 * 0 : ok
4058 simple_export(struct pine *ps, void *srctext, SourceType srctype, char *prompt_msg, char *lister_msg)
4060 int r = 1, rflags = GER_NONE;
4061 char filename[MAXPATH+1], full_filename[MAXPATH+1];
4062 STORE_S *store = NULL;
4063 struct variable *vars = ps ? ps->vars : NULL;
4064 static HISTORY_S *history = NULL;
4065 static ESCKEY_S simple_export_opts[] = {
4066 {ctrl('T'), 10, "^T", N_("To Files")},
4067 {-1, 0, NULL, NULL},
4068 {-1, 0, NULL, NULL}};
4070 if(F_ON(F_ENABLE_TAB_COMPLETE,ps)){
4071 simple_export_opts[r].ch = ctrl('I');
4072 simple_export_opts[r].rval = 11;
4073 simple_export_opts[r].name = "TAB";
4074 simple_export_opts[r].label = N_("Complete");
4077 if(!srctext){
4078 q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
4079 r = -3;
4080 goto fini;
4083 simple_export_opts[++r].ch = -1;
4084 filename[0] = '\0';
4085 full_filename[0] = '\0';
4087 r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename),
4088 prompt_msg, lister_msg, simple_export_opts, &rflags,
4089 -FOOTER_ROWS(ps), GE_IS_EXPORT, &history);
4091 if(r < 0)
4092 goto fini;
4093 else if(!full_filename[0]){
4094 r = -1;
4095 goto fini;
4098 dprint((5, "Opening file \"%s\" for export\n",
4099 full_filename ? full_filename : "?"));
4101 if((store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
4102 char *pipe_err;
4103 gf_io_t pc, gc;
4105 gf_set_so_writec(&pc, store);
4106 gf_set_readc(&gc, srctext, (srctype == CharStar)
4107 ? strlen((char *)srctext)
4108 : 0L,
4109 srctype, 0);
4110 gf_filter_init();
4111 if((pipe_err = gf_pipe(gc, pc)) != NULL){
4112 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4113 /* TRANSLATORS: Problem saving to <filename>: <error text> */
4114 _("Problem saving to \"%s\": %s"),
4115 filename, pipe_err);
4116 r = -3;
4118 else
4119 r = 0;
4121 gf_clear_so_writec(store);
4122 if(so_give(&store)){
4123 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4124 _("Problem saving to \"%s\": %s"),
4125 filename, error_description(errno));
4126 r = -3;
4129 else{
4130 q_status_message2(SM_ORDER | SM_DING, 3, 4,
4131 _("Error opening file \"%s\" for export: %s"),
4132 full_filename, error_description(errno));
4133 r = -3;
4136 fini:
4137 switch(r){
4138 case 0:
4139 /* overloading full_filename */
4140 snprintf(full_filename, sizeof(full_filename), "%c%s",
4141 (prompt_msg && prompt_msg[0])
4142 ? (islower((unsigned char)prompt_msg[0])
4143 ? toupper((unsigned char)prompt_msg[0]) : prompt_msg[0])
4144 : 'T',
4145 (prompt_msg && prompt_msg[0]) ? prompt_msg+1 : "ext");
4146 full_filename[sizeof(full_filename)-1] = '\0';
4147 q_status_message3(SM_ORDER,0,2,"%s %s to \"%s\"",
4148 full_filename,
4149 rflags & GER_OVER
4150 ? "overwritten"
4151 : rflags & GER_APPEND ? "appended" : "exported",
4152 filename);
4153 break;
4155 case -1:
4156 cmd_cancelled("Export");
4157 break;
4159 case -2:
4160 q_status_message1(SM_ORDER, 0, 2,
4161 _("Can't export to file outside of %s"), VAR_OPER_DIR);
4162 break;
4165 ps->mangled_footer = 1;
4166 return(r);
4171 * Ask user what file to export to.
4173 * filename -- On input, this is the filename to start with. On exit,
4174 * this is the filename chosen. (but this isn't used)
4175 * deefault -- This is the default value if user hits return. The
4176 * prompt will have [deefault] added to it automatically.
4177 * full_filename -- This is the full filename on exit.
4178 * len -- Minimum length of _both_ filename and full_filename.
4179 * prompt_msg -- Message to insert in prompt.
4180 * lister_msg -- Message to insert in file_lister.
4181 * opts -- Key options.
4182 * There is a tangled relationship between the callers
4183 * and this routine as far as opts are concerned. Some
4184 * of the opts are handled here. In particular, r == 3,
4185 * r == 10, r == 11, and r == 13 are all handled here.
4186 * Don't use those values unless you want what happens
4187 * here. r == 12 and others are handled by the caller.
4188 * rflags -- Return flags
4189 * GER_OVER - overwrite of existing file
4190 * GER_APPEND - append of existing file
4191 * else file did not exist before
4193 * GER_ALLPARTS - AllParts toggle was turned on
4195 * qline -- Command line to prompt on.
4196 * flags -- Logically OR'd flags
4197 * GE_IS_EXPORT - The command was an Export command
4198 * so the prompt should include
4199 * EXPORT:.
4200 * GE_SEQ_SENSITIVE - The command that got us here is
4201 * sensitive to sequence number changes
4202 * caused by unsolicited expunges.
4203 * GE_NO_APPEND - We will not allow append to an
4204 * existing file, only removal of the
4205 * file if it exists.
4206 * GE_IS_IMPORT - We are selecting for reading.
4207 * No overwriting or checking for
4208 * existence at all. Don't use this
4209 * together with GE_NO_APPEND.
4210 * GE_ALLPARTS - Turn on AllParts toggle.
4211 * GE_BINARY - Turn on Binary toggle.
4213 * Returns: -1 cancelled
4214 * -2 prohibited by VAR_OPER_DIR
4215 * -3 other error, already reported here
4216 * 0 ok
4217 * 12 user chose 12 command from opts
4220 get_export_filename(struct pine *ps, char *filename, char *deefault,
4221 char *full_filename, size_t len, char *prompt_msg,
4222 char *lister_msg, ESCKEY_S *optsarg, int *rflags,
4223 int qline, int flags, HISTORY_S **history)
4225 char dir[MAXPATH+1], dir2[MAXPATH+1], orig_dir[MAXPATH+1];
4226 char precolon[MAXPATH+1], postcolon[MAXPATH+1];
4227 char filename2[MAXPATH+1], tmp[MAXPATH+1], *fn, *ill;
4228 int l, i, ku = -1, kp = -1, r, fatal, homedir = 0, was_abs_path=0, avail, ret = 0;
4229 int allparts = 0, binary = 0;
4230 char prompt_buf[400];
4231 char def[500];
4232 ESCKEY_S *opts = NULL;
4233 struct variable *vars = ps->vars;
4234 static HISTORY_S *dir_hist = NULL;
4235 static char *last;
4236 int pos, hist_len = 0;
4239 /* we will fake a history with the ps_global->VAR_HISTORY variable
4240 * We fake that we combine this variable into a history variable
4241 * by stacking VAR_HISTORY on top of dir_hist. We keep track of this
4242 * by looking at the variable pos.
4244 if(ps_global->VAR_HISTORY != NULL)
4245 for(hist_len = 0; ps_global->VAR_HISTORY[hist_len]
4246 && ps_global->VAR_HISTORY[hist_len][0]; hist_len++)
4249 pos = hist_len + items_in_hist(dir_hist);
4251 if(flags & GE_ALLPARTS || history || dir_hist){
4253 * Copy the opts and add one to the end of the list.
4255 for(i = 0; optsarg[i].ch != -1; i++)
4258 if(dir_hist || hist_len > 0)
4259 i += 2;
4261 if(history)
4262 i += dir_hist || hist_len > 0 ? 2 : 4;
4264 if(flags & GE_ALLPARTS)
4265 i++;
4267 if(flags & GE_BINARY)
4268 i++;
4270 opts = (ESCKEY_S *) fs_get((i+1) * sizeof(*opts));
4271 memset(opts, 0, (i+1) * sizeof(*opts));
4273 for(i = 0; optsarg[i].ch != -1; i++){
4274 opts[i].ch = optsarg[i].ch;
4275 opts[i].rval = optsarg[i].rval;
4276 opts[i].name = optsarg[i].name; /* no need to make a copy */
4277 opts[i].label = optsarg[i].label; /* " */
4280 if(flags & GE_ALLPARTS){
4281 allparts = i;
4282 opts[i].ch = ctrl('P');
4283 opts[i].rval = 13;
4284 opts[i].name = "^P";
4285 /* TRANSLATORS: Export all attachment parts */
4286 opts[i++].label = N_("AllParts");
4289 if(flags & GE_BINARY){
4290 binary = i;
4291 opts[i].ch = ctrl('R');
4292 opts[i].rval = 15;
4293 opts[i].name = "^R";
4294 opts[i++].label = N_("Binary");
4297 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4298 SIZEOF_20KBUF, filename);
4299 #ifndef _WINDOWS
4300 /* In the Windows operating system we always return the UTF8 encoded name */
4301 if(strcmp(tmp_20k_buf, filename)){
4302 opts[i].ch = ctrl('N');
4303 opts[i].rval = 40;
4304 opts[i].name = "^N";
4305 opts[i++].label = "Name UTF8";
4307 #else
4308 strncpy(filename, tmp_20k_buf, len);
4309 filename[len-1] = '\0';
4310 #endif /* _WINDOWS */
4312 if(dir_hist || hist_len > 0){
4313 opts[i].ch = ctrl('Y');
4314 opts[i].rval = 32;
4315 opts[i].name = "";
4316 kp = i;
4317 opts[i++].label = "";
4319 opts[i].ch = ctrl('V');
4320 opts[i].rval = 33;
4321 opts[i].name = "";
4322 opts[i++].label = "";
4325 if(history){
4326 opts[i].ch = KEY_UP;
4327 opts[i].rval = 30;
4328 opts[i].name = "";
4329 ku = i;
4330 opts[i++].label = "";
4332 opts[i].ch = KEY_DOWN;
4333 opts[i].rval = 31;
4334 opts[i].name = "";
4335 opts[i++].label = "";
4338 opts[i].ch = -1;
4340 if(history)
4341 init_hist(history, HISTSIZE);
4342 init_hist(&dir_hist, HISTSIZE); /* reset history to the end */
4344 else
4345 opts = optsarg;
4347 if(rflags)
4348 *rflags = GER_NONE;
4350 if(F_ON(F_USE_CURRENT_DIR, ps))
4351 dir[0] = '\0';
4352 else if(VAR_OPER_DIR){
4353 strncpy(dir, VAR_OPER_DIR, sizeof(dir));
4354 dir[sizeof(dir)-1] = '\0';
4356 #if defined(DOS) || defined(OS2)
4357 else if(VAR_FILE_DIR){
4358 strncpy(dir, VAR_FILE_DIR, sizeof(dir));
4359 dir[sizeof(dir)-1] = '\0';
4361 #endif
4362 else{
4363 dir[0] = '~';
4364 dir[1] = '\0';
4365 homedir=1;
4367 strncpy(orig_dir, dir, sizeof(orig_dir));
4368 orig_dir[sizeof(orig_dir)-1] = '\0';
4370 postcolon[0] = '\0';
4371 strncpy(precolon, dir, sizeof(precolon));
4372 precolon[sizeof(precolon)-1] = '\0';
4373 if(deefault){
4374 strncpy(def, deefault, sizeof(def)-1);
4375 def[sizeof(def)-1] = '\0';
4376 removing_leading_and_trailing_white_space(def);
4378 else
4379 def[0] = '\0';
4381 avail = MAX(20, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - MIN_OPT_ENT_WIDTH;
4383 /*---------- Prompt the user for the file name -------------*/
4384 while(1){
4385 int oeflags;
4386 char dirb[50], fileb[50];
4387 int l1, l2, l3, l4, l5, needed;
4388 char *p, p1[100], p2[100], *p3, p4[100], p5[100];
4390 snprintf(p1, sizeof(p1), "%sCopy ",
4391 (flags & GE_IS_EXPORT) ? "EXPORT: " :
4392 (flags & GE_IS_IMPORT) ? "IMPORT: " : "SAVE: ");
4393 p1[sizeof(p1)-1] = '\0';
4394 l1 = strlen(p1);
4396 strncpy(p2, prompt_msg ? prompt_msg : "", sizeof(p2)-1);
4397 p2[sizeof(p2)-1] = '\0';
4398 l2 = strlen(p2);
4400 if(rflags && *rflags & GER_ALLPARTS)
4401 p3 = " (and atts)";
4402 else
4403 p3 = "";
4405 l3 = strlen(p3);
4407 snprintf(p4, sizeof(p4), " %s file%s%s",
4408 (flags & GE_IS_IMPORT) ? "from" : "to",
4409 is_absolute_path(filename) ? "" : " in ",
4410 is_absolute_path(filename) ? "" :
4411 (!dir[0] ? "current directory"
4412 : (dir[0] == '~' && !dir[1]) ? "home directory"
4413 : short_str(dir,dirb,sizeof(dirb),30,FrontDots)));
4414 p4[sizeof(p4)-1] = '\0';
4415 l4 = strlen(p4);
4417 snprintf(p5, sizeof(p5), "%s%s%s: ",
4418 *def ? " [" : "",
4419 *def ? short_str(def,fileb,sizeof(fileb),40,EndDots) : "",
4420 *def ? "]" : "");
4421 p5[sizeof(p5)-1] = '\0';
4422 l5 = strlen(p5);
4424 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4425 snprintf(p4, sizeof(p4), " %s file%s%s",
4426 (flags & GE_IS_IMPORT) ? "from" : "to",
4427 is_absolute_path(filename) ? "" : " in ",
4428 is_absolute_path(filename) ? "" :
4429 (!dir[0] ? "current dir"
4430 : (dir[0] == '~' && !dir[1]) ? "home dir"
4431 : short_str(dir,dirb,sizeof(dirb),10,FrontDots)));
4432 p4[sizeof(p4)-1] = '\0';
4433 l4 = strlen(p4);
4436 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4437 snprintf(p5, sizeof(p5), "%s%s%s: ",
4438 *def ? " [" : "",
4439 *def ? short_str(def,fileb,sizeof(fileb),
4440 MAX(15,l5-5-needed),EndDots) : "",
4441 *def ? "]" : "");
4442 p5[sizeof(p5)-1] = '\0';
4443 l5 = strlen(p5);
4446 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l2 > 0){
4449 * 14 is about the shortest we can make this, because there are
4450 * fixed length strings of length 14 coming in here.
4452 p = short_str(prompt_msg, p2, sizeof(p2), MAX(14,l2-needed), FrontDots);
4453 if(p != p2){
4454 strncpy(p2, p, sizeof(p2)-1);
4455 p2[sizeof(p2)-1] = '\0';
4458 l2 = strlen(p2);
4461 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4462 strncpy(p1, "Copy ", sizeof(p1)-1);
4463 p1[sizeof(p1)-1] = '\0';
4464 l1 = strlen(p1);
4467 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4468 snprintf(p5, sizeof(p5), "%s%s%s: ",
4469 *def ? " [" : "",
4470 *def ? short_str(def,fileb, sizeof(fileb),
4471 MAX(10,l5-5-needed),EndDots) : "",
4472 *def ? "]" : "");
4473 p5[sizeof(p5)-1] = '\0';
4474 l5 = strlen(p5);
4477 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l3 > 0){
4478 if(needed <= l3 - strlen(" (+ atts)"))
4479 p3 = " (+ atts)";
4480 else if(needed <= l3 - strlen(" (atts)"))
4481 p3 = " (atts)";
4482 else if(needed <= l3 - strlen(" (+)"))
4483 p3 = " (+)";
4484 else if(needed <= l3 - strlen("+"))
4485 p3 = "+";
4486 else
4487 p3 = "";
4489 l3 = strlen(p3);
4492 snprintf(prompt_buf, sizeof(prompt_buf), "%s%s%s%s%s", p1, p2, p3, p4, p5);
4493 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4495 if(kp >= 0){
4496 if(items_in_hist(dir_hist) > 0 || hist_len > 0){ /* any directories */
4497 opts[kp].name = "^Y";
4498 opts[kp].label = "Prev Dir";
4499 opts[kp+1].name = "^V";
4500 opts[kp+1].label = "Next Dir";
4502 else{
4503 opts[kp].name = "";
4504 opts[kp].label = "";
4505 opts[kp+1].name = "";
4506 opts[kp+1].label = "";
4510 if(ku >= 0){
4511 if(items_in_hist(*history) > 0){
4512 opts[ku].name = HISTORY_UP_KEYNAME;
4513 opts[ku].label = HISTORY_KEYLABEL;
4514 opts[ku+1].name = HISTORY_DOWN_KEYNAME;
4515 opts[ku+1].label = HISTORY_KEYLABEL;
4517 else{
4518 opts[ku].name = "";
4519 opts[ku].label = "";
4520 opts[ku+1].name = "";
4521 opts[ku+1].label = "";
4525 oeflags = OE_APPEND_CURRENT |
4526 ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0);
4527 r = optionally_enter(filename, qline, 0, len, prompt_buf,
4528 opts, NO_HELP, &oeflags);
4530 dprint((2, "\n - export_filename = \"%s\", r = %d -\n", filename, r));
4531 /*--- Help ----*/
4532 if(r == 3){
4534 * Helps may not be right if you add another caller or change
4535 * things. Check it out.
4537 if(flags & GE_IS_IMPORT)
4538 helper(h_ge_import, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE);
4539 else if(flags & GE_ALLPARTS)
4540 helper(h_ge_allparts, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4541 else
4542 helper(h_ge_export, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4544 ps->mangled_screen = 1;
4546 continue;
4548 else if(r == 10 || r == 11){ /* Browser or File Completion */
4549 if(filename[0]=='~'){
4550 if(filename[1] == C_FILESEP && filename[2]!='\0'){
4551 precolon[0] = '~';
4552 precolon[1] = '\0';
4553 for(i=0; filename[i+2] != '\0' && i+2 < len-1; i++)
4554 filename[i] = filename[i+2];
4555 filename[i] = '\0';
4556 strncpy(dir, precolon, sizeof(dir)-1);
4557 dir[sizeof(dir)-1] = '\0';
4559 else if(filename[1]=='\0' ||
4560 (filename[1] == C_FILESEP && filename[2] == '\0')){
4561 precolon[0] = '~';
4562 precolon[1] = '\0';
4563 filename[0] = '\0';
4564 strncpy(dir, precolon, sizeof(dir)-1);
4565 dir[sizeof(dir)-1] = '\0';
4568 else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){
4569 if(homedir){
4570 precolon[0] = '~';
4571 precolon[1] = '\0';
4572 strncpy(dir, precolon, sizeof(dir)-1);
4573 dir[sizeof(dir)-1] = '\0';
4575 else{
4576 precolon[0] = '\0';
4577 dir[0] = '\0';
4580 l = MAXPATH;
4581 dir2[0] = '\0';
4582 strncpy(tmp, filename, sizeof(tmp)-1);
4583 tmp[sizeof(tmp)-1] = '\0';
4584 if(*tmp && is_absolute_path(tmp))
4585 fnexpand(tmp, sizeof(tmp));
4586 if(strncmp(tmp,postcolon, strlen(postcolon)))
4587 postcolon[0] = '\0';
4589 if(*tmp && (fn = last_cmpnt(tmp))){
4590 l -= fn - tmp;
4591 strncpy(filename2, fn, sizeof(filename2)-1);
4592 filename2[sizeof(filename2)-1] = '\0';
4593 if(is_absolute_path(tmp)){
4594 strncpy(dir2, tmp, MIN(fn - tmp, sizeof(dir2)-1));
4595 dir2[MIN(fn - tmp, sizeof(dir2)-1)] = '\0';
4596 #ifdef _WINDOWS
4597 if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){
4598 dir2[2] = '\\';
4599 dir2[3] = '\0';
4601 #endif
4602 strncpy(postcolon, dir2, sizeof(postcolon)-1);
4603 postcolon[sizeof(postcolon)-1] = '\0';
4604 precolon[0] = '\0';
4606 else{
4607 char *p = NULL;
4609 * Just building the directory name in dir2,
4610 * full_filename is overloaded.
4612 snprintf(full_filename, len, "%.*s", (int) MIN(fn-tmp,len-1), tmp);
4613 full_filename[len-1] = '\0';
4614 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4615 postcolon[sizeof(postcolon)-1] = '\0';
4616 build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH)
4617 : (dir[0] == '~' && !dir[1])
4618 ? ps->home_dir
4619 : dir,
4620 full_filename, sizeof(dir2));
4621 if(p)
4622 free(p);
4625 else{
4626 if(is_absolute_path(tmp)){
4627 strncpy(dir2, tmp, sizeof(dir2)-1);
4628 dir2[sizeof(dir2)-1] = '\0';
4629 #ifdef _WINDOWS
4630 if(dir2[2]=='\0' && dir2[1]==':'){
4631 dir2[2]='\\';
4632 dir2[3]='\0';
4633 strncpy(postcolon,dir2,sizeof(postcolon)-1);
4634 postcolon[sizeof(postcolon)-1] = '\0';
4636 #endif
4637 filename2[0] = '\0';
4638 precolon[0] = '\0';
4640 else{
4641 strncpy(filename2, tmp, sizeof(filename2)-1);
4642 filename2[sizeof(filename2)-1] = '\0';
4643 if(!dir[0]){
4644 if(getcwd(dir2, sizeof(dir2)) == NULL)
4645 alpine_panic(_("getcwd() call failed at get_export_filename"));
4647 else if(dir[0] == '~' && !dir[1]){
4648 strncpy(dir2, ps->home_dir, sizeof(dir2)-1);
4649 dir2[sizeof(dir2)-1] = '\0';
4651 else{
4652 strncpy(dir2, dir, sizeof(dir2)-1);
4653 dir2[sizeof(dir2)-1] = '\0';
4656 postcolon[0] = '\0';
4660 build_path(full_filename, dir2, filename2, len);
4661 if(!strcmp(full_filename, dir2))
4662 filename2[0] = '\0';
4663 if(full_filename[strlen(full_filename)-1] == C_FILESEP
4664 && isdir(full_filename,NULL,NULL)){
4665 if(strlen(full_filename) == 1)
4666 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4667 else if(filename2[0])
4668 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4669 postcolon[sizeof(postcolon)-1] = '\0';
4670 strncpy(dir2, full_filename, sizeof(dir2)-1);
4671 dir2[sizeof(dir2)-1] = '\0';
4672 filename2[0] = '\0';
4674 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4675 else if(full_filename[strlen(full_filename)-1] == C_FILESEP){
4676 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4677 postcolon[sizeof(postcolon)-1] = '\0';
4678 strncpy(dir2, full_filename, sizeof(dir2)-1);
4679 dir2[sizeof(dir2)-1] = '\0';
4680 filename2[0] = '\0';
4682 #endif
4683 if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1
4684 && strcmp(dir2+1, ":\\"))
4685 /* last condition to prevent stripping of '\\'
4686 in windows partition */
4687 dir2[strlen(dir2)-1] = '\0';
4689 if(r == 10){ /* File Browser */
4690 r = file_lister(lister_msg ? lister_msg : "EXPORT",
4691 dir2, sizeof(dir2), filename2, sizeof(filename2),
4692 TRUE,
4693 (flags & GE_IS_IMPORT) ? FB_READ : FB_SAVE);
4694 #ifdef _WINDOWS
4695 /* Windows has a special "feature" in which entering the file browser will
4696 change the working directory if the directory is changed at all (even
4697 clicking "Cancel" will change the working directory).
4699 if(F_ON(F_USE_CURRENT_DIR, ps))
4700 (void)getcwd(dir2,sizeof(dir2));
4701 #endif
4702 if(isdir(dir2,NULL,NULL)){
4703 strncpy(precolon, dir2, sizeof(precolon)-1);
4704 precolon[sizeof(precolon)-1] = '\0';
4706 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4707 postcolon[sizeof(postcolon)-1] = '\0';
4708 if(r == 1){
4709 build_path(full_filename, dir2, filename2, len);
4710 if(isdir(full_filename, NULL, NULL)){
4711 strncpy(dir, full_filename, sizeof(dir)-1);
4712 dir[sizeof(dir)-1] = '\0';
4713 filename[0] = '\0';
4715 else{
4716 fn = last_cmpnt(full_filename);
4717 strncpy(dir, full_filename,
4718 MIN(fn - full_filename, sizeof(dir)-1));
4719 dir[MIN(fn - full_filename, sizeof(dir)-1)] = '\0';
4720 if(fn - full_filename > 1)
4721 dir[fn - full_filename - 1] = '\0';
4724 if(!strcmp(dir, ps->home_dir)){
4725 dir[0] = '~';
4726 dir[1] = '\0';
4729 strncpy(filename, fn, len-1);
4730 filename[len-1] = '\0';
4733 else{ /* File Completion */
4734 if(!pico_fncomplete(dir2, filename2, sizeof(filename2)))
4735 Writechar(BELL, 0);
4736 strncat(postcolon, filename2,
4737 sizeof(postcolon)-1-strlen(postcolon));
4738 postcolon[sizeof(postcolon)-1] = '\0';
4740 was_abs_path = is_absolute_path(filename);
4742 if(!strcmp(dir, ps->home_dir)){
4743 dir[0] = '~';
4744 dir[1] = '\0';
4747 strncpy(filename, postcolon, len-1);
4748 filename[len-1] = '\0';
4749 strncpy(dir, precolon, sizeof(dir)-1);
4750 dir[sizeof(dir)-1] = '\0';
4752 if(filename[0] == '~' && !filename[1]){
4753 dir[0] = '~';
4754 dir[1] = '\0';
4755 filename[0] = '\0';
4758 continue;
4760 else if(r == 12){ /* Download, caller handles it */
4761 ret = r;
4762 goto done;
4764 else if(r == 13){ /* toggle AllParts bit */
4765 if(rflags){
4766 if(*rflags & GER_ALLPARTS){
4767 *rflags &= ~GER_ALLPARTS;
4768 opts[allparts].label = N_("AllParts");
4770 else{
4771 *rflags |= GER_ALLPARTS;
4772 /* opposite of All Parts, No All Parts */
4773 opts[allparts].label = N_("NoAllParts");
4777 continue;
4779 #if 0
4780 else if(r == 14){ /* List file names matching partial? */
4781 continue;
4783 #endif
4784 else if(r == 15){ /* toggle Binary bit */
4785 if(rflags){
4786 if(*rflags & GER_BINARY){
4787 *rflags &= ~GER_BINARY;
4788 opts[binary].label = N_("Binary");
4790 else{
4791 *rflags |= GER_BINARY;
4792 opts[binary].label = N_("No Binary");
4796 continue;
4798 else if(r == 1){ /* Cancel */
4799 ret = -1;
4800 goto done;
4802 else if(r == 4){
4803 continue;
4805 else if(r >= 30 && r <= 33){
4806 char *p = NULL;
4808 if(r == 30 || r == 31){
4809 if(history){
4810 if(r == 30)
4811 p = get_prev_hist(*history, filename, 0, NULL);
4812 else if (r == 31)
4813 p = get_next_hist(*history, filename, 0, NULL);
4817 if(r == 32 || r == 33){
4818 int nitems = items_in_hist(dir_hist);
4819 if(dir_hist || hist_len > 0){
4820 if(r == 32){
4821 if(pos > 0)
4822 p = hist_in_pos(--pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4823 else p = last;
4825 else if (r == 33){
4826 if(pos < hist_len + nitems)
4827 p = hist_in_pos(++pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4829 if(p == NULL || *p == '\0')
4830 p = orig_dir;
4833 last = p; /* save it! */
4835 if(p != NULL && *p != '\0'){
4836 if(r == 30 || r == 31){
4837 if((fn = last_cmpnt(p)) != NULL){
4838 strncpy(dir, p, MIN(fn - p, sizeof(dir)-1));
4839 dir[MIN(fn - p, sizeof(dir)-1)] = '\0';
4840 if(fn - p > 1)
4841 dir[fn - p - 1] = '\0';
4842 strncpy(filename, fn, len-1);
4843 filename[len-1] = '\0';
4845 } else { /* r == 32 || r == 33 */
4846 strncpy(dir, p, sizeof(dir)-1);
4847 dir[sizeof(dir)-1] = '\0';
4850 if(!strcmp(dir, ps->home_dir)){
4851 dir[0] = '~';
4852 dir[1] = '\0';
4855 else
4856 Writechar(BELL, 0);
4857 continue;
4859 #ifndef _WINDOWS
4860 else if(r == 40){
4861 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4862 SIZEOF_20KBUF, filename);
4863 strncpy(filename, tmp_20k_buf, len);
4864 filename[len-1] = '\0';
4865 continue;
4867 #endif /* _WINDOWS */
4868 else if(r != 0){
4869 Writechar(BELL, 0);
4870 continue;
4873 removing_leading_and_trailing_white_space(filename);
4875 if(!*filename){
4876 if(!*def){ /* Cancel */
4877 ret = -1;
4878 goto done;
4881 strncpy(filename, def, len-1);
4882 filename[len-1] = '\0';
4885 #if defined(DOS) || defined(OS2)
4886 if(is_absolute_path(filename)){
4887 fixpath(filename, len);
4889 #else
4890 if(filename[0] == '~'){
4891 if(fnexpand(filename, len) == NULL){
4892 char *p = strindex(filename, '/');
4893 if(p != NULL)
4894 *p = '\0';
4895 q_status_message1(SM_ORDER | SM_DING, 3, 3,
4896 _("Error expanding file name: \"%s\" unknown user"),
4897 filename);
4898 continue;
4901 #endif
4903 if(is_absolute_path(filename)){
4904 strncpy(full_filename, filename, len-1);
4905 full_filename[len-1] = '\0';
4907 else{
4908 if(!dir[0])
4909 build_path(full_filename, (char *)getcwd(dir,sizeof(dir)),
4910 filename, len);
4911 else if(dir[0] == '~' && !dir[1])
4912 build_path(full_filename, ps->home_dir, filename, len);
4913 else
4914 build_path(full_filename, dir, filename, len);
4917 if((ill = filter_filename(full_filename, &fatal,
4918 ps_global->restricted || ps_global->VAR_OPER_DIR)) != NULL){
4919 if(fatal){
4920 q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill);
4921 continue;
4923 else{
4924 /* BUG: we should beep when the key's pressed rather than bitch later */
4925 /* Warn and ask for confirmation. */
4926 snprintf(prompt_buf, sizeof(prompt_buf), "File name contains a '%s'. %s anyway",
4927 ill, (flags & GE_IS_EXPORT) ? "Export" : "Save");
4928 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4929 if(want_to(prompt_buf, 'n', 0, NO_HELP,
4930 ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y')
4931 continue;
4935 break; /* Must have got an OK file name */
4938 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){
4939 ret = -2;
4940 goto done;
4943 if(!can_access(full_filename, ACCESS_EXISTS)){
4944 int rbflags;
4945 static ESCKEY_S access_opts[] = {
4946 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4947 a file or append to the end of the file */
4948 {'o', 'o', "O", N_("Overwrite")},
4949 {'a', 'a', "A", N_("Append")},
4950 {-1, 0, NULL, NULL}};
4952 rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0);
4954 if(flags & GE_NO_APPEND){
4955 r = strlen(filename);
4956 snprintf(prompt_buf, sizeof(prompt_buf),
4957 /* TRANSLATORS: asking user whether to overwrite a file or not,
4958 File <filename> already exists. Overwrite it ? */
4959 _("File \"%s%s\" already exists. Overwrite it "),
4960 (r > 20) ? "..." : "",
4961 filename + ((r > 20) ? r - 20 : 0));
4962 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4963 if(want_to(prompt_buf, 'n', 'x', NO_HELP, rbflags) == 'y'){
4964 if(rflags)
4965 *rflags |= GER_OVER;
4967 if(our_unlink(full_filename) < 0){
4968 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4969 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4970 _("Cannot remove old %s: %s"),
4971 full_filename, error_description(errno));
4974 else{
4975 ret = -1;
4976 goto done;
4979 else if(!(flags & GE_IS_IMPORT)){
4980 r = strlen(filename);
4981 snprintf(prompt_buf, sizeof(prompt_buf),
4982 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4983 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4984 (r > 20) ? "..." : "",
4985 filename + ((r > 20) ? r - 20 : 0));
4986 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4987 switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global),
4988 access_opts, 'a', 'x', NO_HELP, rbflags)){
4989 case 'o' :
4990 if(rflags)
4991 *rflags |= GER_OVER;
4993 if(our_truncate(full_filename, (off_t)0) < 0)
4994 /* trouble truncating, but we'll give it a try anyway */
4995 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4996 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
4997 _("Warning: Cannot truncate old %s: %s"),
4998 full_filename, error_description(errno));
4999 break;
5001 case 'a' :
5002 if(rflags)
5003 *rflags |= GER_APPEND;
5005 break;
5007 case 'x' :
5008 default :
5009 ret = -1;
5010 goto done;
5015 done:
5016 if(history && ret == 0){
5017 save_hist(*history, full_filename, 0, NULL);
5018 strncpy(tmp, full_filename, MAXPATH);
5019 tmp[MAXPATH] = '\0';
5020 if((fn = strrchr(tmp, C_FILESEP)) != NULL)
5021 *fn = '\0';
5022 else
5023 tmp[0] = '\0';
5024 if(tmp[0])
5025 save_hist(dir_hist, tmp, 0, NULL);
5028 if(opts && opts != optsarg)
5029 fs_give((void **) &opts);
5031 return(ret);
5035 /*----------------------------------------------------------------------
5036 parse the config'd upload/download command
5038 Args: cmd -- buffer to return command fit for shellin'
5039 prefix --
5040 cfg_str --
5041 fname -- file name to build into the command
5043 Returns: pointer to cmd_str buffer or NULL on real bad error
5045 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
5046 cfg_str is written to standard out right before a successful
5047 return of this function. The call immediately following this
5048 function darn well better be the shell exec...
5049 ----*/
5050 char *
5051 build_updown_cmd(char *cmd, size_t cmdlen, char *prefix, char *cfg_str, char *fname)
5053 char *p;
5054 int fname_found = 0;
5056 if(prefix && *prefix){
5057 /* loop thru replacing all occurrences of _FILE_ */
5058 p = strncpy(cmd, prefix, cmdlen);
5059 cmd[cmdlen-1] = '\0';
5060 while((p = strstr(p, "_FILE_")))
5061 rplstr(p, cmdlen-(p-cmd), 6, fname);
5063 fputs(cmd, stdout);
5066 /* loop thru replacing all occurrences of _FILE_ */
5067 p = strncpy(cmd, cfg_str, cmdlen);
5068 cmd[cmdlen-1] = '\0';
5069 while((p = strstr(p, "_FILE_"))){
5070 rplstr(p, cmdlen-(p-cmd), 6, fname);
5071 fname_found = 1;
5074 if(!fname_found)
5075 snprintf(cmd+strlen(cmd), cmdlen-strlen(cmd), " %s", fname);
5077 cmd[cmdlen-1] = '\0';
5079 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
5080 cmd ? cmd : "?"));
5081 return(cmd);
5085 /*----------------------------------------------------------------------
5086 Write a berzerk format message delimiter using the given putc function
5088 Args: e -- envelope of message to write
5089 pc -- function to use
5091 Returns: TRUE if we could write it, FALSE if there was a problem
5093 NOTE: follows delimiter with OS-dependent newline
5094 ----*/
5096 bezerk_delimiter(ENVELOPE *env, MESSAGECACHE *mc, gf_io_t pc, int leading_newline)
5098 MESSAGECACHE telt;
5099 time_t when;
5100 char *p;
5102 /* write "[\n]From mailbox[@host] " */
5103 if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1)
5104 && gf_puts("From ", pc)
5105 && gf_puts((env && env->from) ? env->from->mailbox
5106 : "the-concourse-on-high", pc)
5107 && gf_puts((env && env->from && env->from->host) ? "@" : "", pc)
5108 && gf_puts((env && env->from && env->from->host) ? env->from->host
5109 : "", pc)
5110 && (*pc)(' ')))
5111 return(0);
5113 if(mc && mc->valid)
5114 when = mail_longdate(mc);
5115 else if(env && env->date && env->date[0]
5116 && mail_parse_date(&telt,env->date))
5117 when = mail_longdate(&telt);
5118 else
5119 when = time(0);
5121 p = ctime(&when);
5123 while(p && *p && *p != '\n') /* write date */
5124 if(!(*pc)(*p++))
5125 return(0);
5127 if(!gf_puts(NEWLINE, pc)) /* write terminating newline */
5128 return(0);
5130 return(1);
5134 /*----------------------------------------------------------------------
5135 Execute command to jump to a given message number
5137 Args: qline -- Line to ask question on
5139 Result: returns true if the use selected a new message, false otherwise
5141 ----*/
5142 long
5143 jump_to(MSGNO_S *msgmap, int qline, UCS first_num, SCROLL_S *sparms, CmdWhere in_index)
5145 char jump_num_string[80], *j, prompt[70];
5146 HelpType help;
5147 int rc;
5148 static ESCKEY_S jump_to_key[] = { {0, 0, NULL, NULL},
5149 /* TRANSLATORS: go to First Message */
5150 {ctrl('Y'), 10, "^Y", N_("First Msg")},
5151 {ctrl('V'), 11, "^V", N_("Last Msg")},
5152 {-1, 0, NULL, NULL} };
5154 dprint((4, "\n - jump_to -\n"));
5156 #ifdef DEBUG
5157 if(sparms && sparms->jump_is_debug)
5158 return(get_level(qline, first_num, sparms));
5159 #endif
5161 if(!any_messages(msgmap, NULL, "to Jump to"))
5162 return(0L);
5164 if(first_num && first_num < 0x80 && isdigit((unsigned char) first_num)){
5165 jump_num_string[0] = first_num;
5166 jump_num_string[1] = '\0';
5168 else
5169 jump_num_string[0] = '\0';
5171 if(mn_total_cur(msgmap) > 1L){
5172 snprintf(prompt, sizeof(prompt), "Unselect %s msgs in favor of number to be entered",
5173 comatose(mn_total_cur(msgmap)));
5174 prompt[sizeof(prompt)-1] = '\0';
5175 if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n')
5176 return(0L);
5179 snprintf(prompt, sizeof(prompt), "%s number to jump to : ", in_index == ThrdIndx
5180 ? "Thread"
5181 : "Message");
5182 prompt[sizeof(prompt)-1] = '\0';
5184 help = NO_HELP;
5185 while(1){
5186 int flags = OE_APPEND_CURRENT;
5188 rc = optionally_enter(jump_num_string, qline, 0,
5189 sizeof(jump_num_string), prompt,
5190 jump_to_key, help, &flags);
5191 if(rc == 3){
5192 help = help == NO_HELP
5193 ? (in_index == ThrdIndx ? h_oe_jump_thd : h_oe_jump)
5194 : NO_HELP;
5195 continue;
5197 else if(rc == 10 || rc == 11){
5198 char warning[100];
5199 long closest;
5201 closest = closest_jump_target(rc == 10 ? 1L
5202 : ((in_index == ThrdIndx)
5203 ? msgmap->max_thrdno
5204 : mn_get_total(msgmap)),
5205 ps_global->mail_stream,
5206 msgmap, 0,
5207 in_index, warning, sizeof(warning));
5208 /* ignore warning */
5209 return(closest);
5213 * If we take out the *jump_num_string nonempty test in this if
5214 * then the closest_jump_target routine will offer a jump to the
5215 * last message. However, it is slow because you have to wait for
5216 * the status message and it is annoying for people who hit J command
5217 * by mistake and just want to hit return to do nothing, like has
5218 * always worked. So the test is there for now. Hubert 2002-08-19
5220 * Jumping to first/last message is now possible through ^Y/^V
5221 * commands above. jpf 2002-08-21
5222 * (and through "end" hubert 2006-07-07)
5224 if(rc == 0 && *jump_num_string != '\0'){
5225 removing_leading_and_trailing_white_space(jump_num_string);
5226 for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++)
5229 if(*j != '\0'){
5230 if(!strucmp("end", j))
5231 return((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap));
5233 q_status_message(SM_ORDER | SM_DING, 2, 2,
5234 _("Invalid number entered. Use only digits 0-9"));
5235 jump_num_string[0] = '\0';
5237 else{
5238 char warning[100];
5239 long closest, jump_num;
5241 if(*jump_num_string)
5242 jump_num = atol(jump_num_string);
5243 else
5244 jump_num = -1L;
5246 warning[0] = '\0';
5247 closest = closest_jump_target(jump_num, ps_global->mail_stream,
5248 msgmap,
5249 *jump_num_string ? 0 : 1,
5250 in_index, warning, sizeof(warning));
5251 if(warning[0])
5252 q_status_message(SM_ORDER | SM_DING, 2, 2, warning);
5254 if(closest == jump_num)
5255 return(jump_num);
5257 if(closest == 0L)
5258 jump_num_string[0] = '\0';
5259 else
5260 strncpy(jump_num_string, long2string(closest),
5261 sizeof(jump_num_string));
5264 continue;
5267 if(rc != 4)
5268 break;
5271 return(0L);
5276 * cmd_delete_action - handle msgno advance and such after single message deletion
5278 char *
5279 cmd_delete_action(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
5281 int opts;
5282 long msgno;
5283 char *rv = NULL;
5285 msgno = mn_get_cur(msgmap);
5286 advance_cur_after_delete(state, state->mail_stream, msgmap, in_index);
5288 if(IS_NEWS(state->mail_stream)
5289 || ((state->context_current->use & CNTXT_INCMNG)
5290 && context_isambig(state->cur_folder))){
5292 opts = (NSF_TRUST_FLAGS | NSF_SKIP_CHID);
5293 if(in_index == View)
5294 opts &= ~NSF_SKIP_CHID;
5296 (void)next_sorted_flagged(F_UNDEL|F_UNSEEN, state->mail_stream, msgno, &opts);
5297 if(!(opts & NSF_FLAG_MATCH)){
5298 char nextfolder[MAXPATH];
5300 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
5301 nextfolder[sizeof(nextfolder)-1] = '\0';
5302 rv = next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
5303 state->context_current, NULL, NULL)
5304 ? ". Press TAB for next folder."
5305 : ". No more folders to TAB to.";
5309 return(rv);
5314 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5316 char *
5317 cmd_delete_index(struct pine *state, MSGNO_S *msgmap)
5319 return(cmd_delete_action(state, msgmap,MsgIndx));
5323 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5325 char *
5326 cmd_delete_view(struct pine *state, MSGNO_S *msgmap)
5328 return(cmd_delete_action(state, msgmap, View));
5332 void
5333 advance_cur_after_delete(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, CmdWhere in_index)
5335 long new_msgno, msgno;
5336 int opts;
5338 new_msgno = msgno = mn_get_cur(msgmap);
5339 opts = NSF_TRUST_FLAGS;
5341 if(F_ON(F_DEL_SKIPS_DEL, state)){
5343 if(THREADING() && sp_viewing_a_thread(stream))
5344 opts |= NSF_SKIP_CHID;
5346 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5348 else{
5349 mn_inc_cur(stream, msgmap,
5350 (in_index == View && THREADING()
5351 && sp_viewing_a_thread(stream))
5352 ? MH_THISTHD
5353 : (in_index == View)
5354 ? MH_ANYTHD : MH_NONE);
5355 new_msgno = mn_get_cur(msgmap);
5356 if(new_msgno != msgno)
5357 opts |= NSF_FLAG_MATCH;
5361 * Viewing_a_thread is the complicated case because we want to ignore
5362 * other threads at first and then look in other threads if we have to.
5363 * By ignoring other threads we also ignore collapsed partial threads
5364 * in our own thread.
5366 if(THREADING() && sp_viewing_a_thread(stream) && !(opts & NSF_FLAG_MATCH)){
5367 long rawno, orig_thrdno;
5368 PINETHRD_S *thrd, *topthrd = NULL;
5370 rawno = mn_m2raw(msgmap, msgno);
5371 thrd = fetch_thread(stream, rawno);
5372 if(thrd && thrd->top)
5373 topthrd = fetch_thread(stream, thrd->top);
5375 orig_thrdno = topthrd ? topthrd->thrdno : -1L;
5377 opts = NSF_TRUST_FLAGS;
5378 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5381 * If we got a match, new_msgno may be a message in
5382 * a different thread from the one we are viewing, or it could be
5383 * in a collapsed part of this thread.
5385 if(opts & NSF_FLAG_MATCH){
5386 int ret;
5387 char pmt[128];
5389 topthrd = NULL;
5390 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
5391 if(thrd && thrd->top)
5392 topthrd = fetch_thread(stream, thrd->top);
5395 * If this match is in the same thread we're already in
5396 * then we're done, else we have to ask the user and maybe
5397 * switch threads.
5399 if(!(orig_thrdno > 0L && topthrd
5400 && topthrd->thrdno == orig_thrdno)){
5402 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
5403 if(in_index == View)
5404 snprintf(pmt, sizeof(pmt),
5405 "View message in thread number %.10s",
5406 topthrd ? comatose(topthrd->thrdno) : "?");
5407 else
5408 snprintf(pmt, sizeof(pmt), "View thread number %.10s",
5409 topthrd ? comatose(topthrd->thrdno) : "?");
5411 ret = want_to(pmt, 'y', 'x', NO_HELP, WT_NORM);
5413 else
5414 ret = 'y';
5416 if(ret == 'y'){
5417 unview_thread(state, stream, msgmap);
5418 mn_set_cur(msgmap, new_msgno);
5419 if(THRD_AUTO_VIEW()
5420 && (count_lflags_in_thread(stream, topthrd, msgmap,
5421 MN_NONE) == 1)
5422 && view_thread(state, stream, msgmap, 1)){
5423 if(current_index_state)
5424 msgmap->top_after_thrd = current_index_state->msg_at_top;
5426 state->view_skipped_index = 1;
5427 state->next_screen = mail_view_screen;
5429 else{
5430 view_thread(state, stream, msgmap, 1);
5431 if(current_index_state)
5432 msgmap->top_after_thrd = current_index_state->msg_at_top;
5434 state->next_screen = SCREEN_FUN_NULL;
5437 else
5438 new_msgno = msgno; /* stick with original */
5443 mn_set_cur(msgmap, new_msgno);
5444 if(in_index != View)
5445 adjust_cur_to_visible(stream, msgmap);
5449 #ifdef DEBUG
5450 long
5451 get_level(int qline, UCS first_num, SCROLL_S *sparms)
5453 char debug_num_string[80], *j, prompt[70];
5454 HelpType help;
5455 int rc;
5456 long debug_num;
5458 if(first_num && first_num < 0x80 && isdigit((unsigned char)first_num)){
5459 debug_num_string[0] = first_num;
5460 debug_num_string[1] = '\0';
5461 debug_num = atol(debug_num_string);
5462 *(int *)(sparms->proc.data.p) = debug_num;
5463 q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s",
5464 comatose(debug_num));
5465 return(1L);
5467 else
5468 debug_num_string[0] = '\0';
5470 snprintf(prompt, sizeof(prompt), "Show debug <= this level (0-%d) : ", MAX(debug, 9));
5471 prompt[sizeof(prompt)-1] = '\0';
5473 help = NO_HELP;
5474 while(1){
5475 int flags = OE_APPEND_CURRENT;
5477 rc = optionally_enter(debug_num_string, qline, 0,
5478 sizeof(debug_num_string), prompt,
5479 NULL, help, &flags);
5480 if(rc == 3){
5481 help = help == NO_HELP ? h_oe_debuglevel : NO_HELP;
5482 continue;
5485 if(rc == 0){
5486 removing_leading_and_trailing_white_space(debug_num_string);
5487 for(j=debug_num_string; isdigit((unsigned char)*j); j++)
5490 if(*j != '\0'){
5491 q_status_message(SM_ORDER | SM_DING, 2, 2,
5492 _("Invalid number entered. Use only digits 0-9"));
5493 debug_num_string[0] = '\0';
5495 else{
5496 debug_num = atol(debug_num_string);
5497 if(debug_num < 0)
5498 q_status_message(SM_ORDER | SM_DING, 2, 2,
5499 _("Number should be >= 0"));
5500 else if(debug_num > MAX(debug,9))
5501 q_status_message1(SM_ORDER | SM_DING, 2, 2,
5502 _("Maximum is %s"), comatose(MAX(debug,9)));
5503 else{
5504 *(int *)(sparms->proc.data.p) = debug_num;
5505 q_status_message1(SM_ORDER, 0, 3,
5506 "Show debug <= level %s",
5507 comatose(debug_num));
5508 return(1L);
5512 continue;
5515 if(rc != 4)
5516 break;
5519 return(0L);
5521 #endif /* DEBUG */
5525 * Returns the message number closest to target that isn't hidden.
5526 * Make warning at least 100 chars.
5527 * A return of 0 means there is no message to jump to.
5529 long
5530 closest_jump_target(long int target, MAILSTREAM *stream, MSGNO_S *msgmap, int no_target, CmdWhere in_index, char *warning, size_t warninglen)
5532 long i, start, closest = 0L;
5533 char buf[80];
5534 long maxnum;
5536 warning[0] = '\0';
5537 maxnum = (in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap);
5539 if(no_target){
5540 target = maxnum;
5541 start = 1L;
5542 snprintf(warning, warninglen, "No %s number entered, jump to end? ",
5543 (in_index == ThrdIndx) ? "thread" : "message");
5544 warning[warninglen-1] = '\0';
5546 else if(target < 1L)
5547 start = 1L - target;
5548 else if(target > maxnum)
5549 start = target - maxnum;
5550 else
5551 start = 1L;
5553 if(target > 0L && target <= maxnum)
5554 if(in_index == ThrdIndx
5555 || !msgline_hidden(stream, msgmap, target, 0))
5556 return(target);
5558 for(i = start; target+i <= maxnum || target-i > 0L; i++){
5560 if(target+i > 0L && target+i <= maxnum &&
5561 (in_index == ThrdIndx
5562 || !msgline_hidden(stream, msgmap, target+i, 0))){
5563 closest = target+i;
5564 break;
5567 if(target-i > 0L && target-i <= maxnum &&
5568 (in_index == ThrdIndx
5569 || !msgline_hidden(stream, msgmap, target-i, 0))){
5570 closest = target-i;
5571 break;
5575 strncpy(buf, long2string(closest), sizeof(buf));
5576 buf[sizeof(buf)-1] = '\0';
5578 if(closest == 0L)
5579 strncpy(warning, "Nothing to jump to", warninglen);
5580 else if(target < 1L)
5581 snprintf(warning, warninglen, "%s number (%s) must be at least %s",
5582 (in_index == ThrdIndx) ? "Thread" : "Message",
5583 long2string(target), buf);
5584 else if(target > maxnum)
5585 snprintf(warning, warninglen, "%s number (%s) may be no more than %s",
5586 (in_index == ThrdIndx) ? "Thread" : "Message",
5587 long2string(target), buf);
5588 else if(!no_target)
5589 snprintf(warning, warninglen,
5590 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5591 long2string(target), buf);
5593 warning[warninglen-1] = '\0';
5595 return(closest);
5599 /*----------------------------------------------------------------------
5600 Prompt for folder name to open, expand the name and return it
5602 Args: qline -- Screen line to prompt on
5603 allow_list -- if 1, allow ^T to bring up collection lister
5605 Result: returns the folder name or NULL
5606 pine structure mangled_footer flag is set
5607 may call the collection lister in which case mangled screen will be set
5609 This prompts the user for the folder to open, possibly calling up
5610 the collection lister if the user types ^T.
5611 ----------------------------------------------------------------------*/
5612 char *
5613 broach_folder(int qline, int allow_list, int *notrealinbox, CONTEXT_S **context)
5615 HelpType help;
5616 static char newfolder[MAILTMPLEN];
5617 char expanded[MAXPATH+1],
5618 prompt[MAX_SCREEN_COLS+1],
5619 *last_folder, *p;
5620 unsigned char *f1, *f2, *f3;
5621 static HISTORY_S *history = NULL;
5622 CONTEXT_S *tc, *tc2;
5623 ESCKEY_S ekey[9];
5624 int rc, r, ku = -1, n, flags, last_rc = 0, inbox, done = 0;
5627 * the idea is to provide a clue for the context the file name
5628 * will be saved in (if a non-imap names is typed), and to
5629 * only show the previous if it was also in the same context
5631 help = NO_HELP;
5632 *expanded = '\0';
5633 *newfolder = '\0';
5634 last_folder = NULL;
5635 if(notrealinbox)
5636 (*notrealinbox) = 1;
5638 init_hist(&history, HISTSIZE);
5640 tc = broach_get_folder(context ? *context : NULL, &inbox, NULL);
5642 /* set up extra command option keys */
5643 rc = 0;
5644 ekey[rc].ch = (allow_list) ? ctrl('T') : 0 ;
5645 ekey[rc].rval = (allow_list) ? 2 : 0;
5646 ekey[rc].name = (allow_list) ? "^T" : "";
5647 ekey[rc++].label = (allow_list) ? N_("ToFldrs") : "";
5649 if(ps_global->context_list->next){
5650 ekey[rc].ch = ctrl('P');
5651 ekey[rc].rval = 10;
5652 ekey[rc].name = "^P";
5653 ekey[rc++].label = N_("Prev Collection");
5655 ekey[rc].ch = ctrl('N');
5656 ekey[rc].rval = 11;
5657 ekey[rc].name = "^N";
5658 ekey[rc++].label = N_("Next Collection");
5661 ekey[rc].ch = ctrl('W');
5662 ekey[rc].rval = 17;
5663 ekey[rc].name = "^W";
5664 ekey[rc++].label = N_("INBOX");
5666 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
5667 ekey[rc].ch = TAB;
5668 ekey[rc].rval = 12;
5669 ekey[rc].name = "TAB";
5670 ekey[rc++].label = N_("Complete");
5673 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
5674 ekey[rc].ch = ctrl('X');
5675 ekey[rc].rval = 14;
5676 ekey[rc].name = "^X";
5677 ekey[rc++].label = N_("ListMatches");
5680 if(ps_global->context_list->next && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5681 ekey[rc].ch = KEY_UP;
5682 ekey[rc].rval = 10;
5683 ekey[rc].name = "";
5684 ekey[rc++].label = "";
5686 ekey[rc].ch = KEY_DOWN;
5687 ekey[rc].rval = 11;
5688 ekey[rc].name = "";
5689 ekey[rc++].label = "";
5691 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5692 ekey[rc].ch = KEY_UP;
5693 ekey[rc].rval = 30;
5694 ekey[rc].name = "";
5695 ku = rc;
5696 ekey[rc++].label = "";
5698 ekey[rc].ch = KEY_DOWN;
5699 ekey[rc].rval = 31;
5700 ekey[rc].name = "";
5701 ekey[rc++].label = "";
5704 ekey[rc].ch = -1;
5706 while(!done) {
5708 * Figure out next default value for this context. The idea
5709 * is that in each context the last folder opened is cached.
5710 * It's up to pick it out and display it. This is fine
5711 * and dandy if we've currently got the inbox open, BUT
5712 * if not, make the inbox the default the first time thru.
5714 if(!inbox){
5715 last_folder = ps_global->inbox_name;
5716 inbox = 1; /* pretend we're in inbox from here on out */
5718 else
5719 last_folder = (ps_global->last_unambig_folder[0])
5720 ? ps_global->last_unambig_folder
5721 : ((tc->last_folder[0]) ? tc->last_folder : NULL);
5723 if(last_folder){ /* MAXPATH + 1 = sizeof(expanded) */
5724 unsigned char *fname = folder_name_decoded((unsigned char *)last_folder);
5725 snprintf(expanded, sizeof(expanded), " [%.*s]", MAXPATH+1-5,
5726 fname ? (char *) fname : last_folder);
5727 if(fname) fs_give((void **)&fname);
5729 else
5730 *expanded = '\0';
5732 expanded[sizeof(expanded)-1] = '\0';
5734 /* only show collection number if more than one available */
5735 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5736 snprintf(prompt, sizeof(prompt), "GOTO %s in <%s> %.*s%s: ",
5737 NEWS_TEST(tc) ? "news group" : "folder",
5738 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5739 *expanded ? " " : "");
5740 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5741 snprintf(prompt, sizeof(prompt), "GOTO folder %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5742 *expanded ? " " : "");
5744 prompt[sizeof(prompt)-1] = '\0';
5746 if(utf8_width(prompt) > MAXPROMPT){
5747 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5748 snprintf(prompt, sizeof(prompt), "GOTO <%s> %.*s%s: ",
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 %.*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), "<%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), "%.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5764 *expanded ? " " : "");
5766 prompt[sizeof(prompt)-1] = '\0';
5770 if(ku >= 0){
5771 if(items_in_hist(history) > 1){
5772 ekey[ku].name = HISTORY_UP_KEYNAME;
5773 ekey[ku].label = HISTORY_KEYLABEL;
5774 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
5775 ekey[ku+1].label = HISTORY_KEYLABEL;
5777 else{
5778 ekey[ku].name = "";
5779 ekey[ku].label = "";
5780 ekey[ku+1].name = "";
5781 ekey[ku+1].label = "";
5785 /* is there any other way to do this? The point is that we
5786 * are trying to hide mutf7 from the user, and use the utf8
5787 * equivalent. So we create a variable f to take place of
5788 * newfolder, including content and size. f2 is copy of f1
5789 * that has to freed. Sigh!
5791 f3 = (unsigned char *) cpystr(newfolder);
5792 f1 = fs_get(sizeof(newfolder));
5793 f2 = folder_name_decoded(f3);
5794 if(f3) fs_give((void **)&f3);
5795 strncpy((char *)f1, (char *)f2, sizeof(newfolder));
5796 f1[sizeof(newfolder)-1] = '\0';
5797 if(f2) fs_give((void **)&f2);
5799 flags = OE_APPEND_CURRENT;
5800 rc = optionally_enter((char *) f1, qline, 0, sizeof(newfolder),
5801 (char *) prompt, ekey, help, &flags);
5803 f2 = folder_name_encoded(f1);
5804 strncpy(newfolder, (char *)f2, sizeof(newfolder));
5805 if(f1) fs_give((void **)&f1);
5806 if(f2) fs_give((void **)&f2);
5808 ps_global->mangled_footer = 1;
5810 switch(rc){
5811 case -1 : /* o_e says error! */
5812 q_status_message(SM_ORDER | SM_DING, 3, 3,
5813 _("Error reading folder name"));
5814 return(NULL);
5816 case 0 : /* o_e says normal entry */
5817 removing_trailing_white_space(newfolder);
5818 removing_leading_white_space(newfolder);
5820 if(*newfolder){
5821 char *name, *fullname = NULL;
5822 int exists, breakout = 0;
5824 save_hist(history, newfolder, 0, tc);
5826 if(!(name = folder_is_nick(newfolder, FOLDERS(tc),
5827 FN_WHOLE_NAME)))
5828 name = newfolder;
5830 if(update_folder_spec(expanded, sizeof(expanded), name)){
5831 strncpy(name = newfolder, expanded, sizeof(newfolder));
5832 newfolder[sizeof(newfolder)-1] = '\0';
5835 exists = folder_name_exists(tc, name, &fullname);
5837 if(fullname){
5838 strncpy(name = newfolder, fullname, sizeof(newfolder));
5839 newfolder[sizeof(newfolder)-1] = '\0';
5840 fs_give((void **) &fullname);
5841 breakout = TRUE;
5845 * if we know the things a folder, open it.
5846 * else if we know its a directory, visit it.
5847 * else we're not sure (it either doesn't really
5848 * exist or its unLISTable) so try opening it anyway
5850 if(exists & FEX_ISFILE){
5851 done++;
5852 break;
5854 else if((exists & FEX_ISDIR)){
5855 if(breakout){
5856 CONTEXT_S *fake_context;
5857 char tmp[MAILTMPLEN];
5858 size_t l;
5860 strncpy(tmp, name, sizeof(tmp));
5861 tmp[sizeof(tmp)-2-1] = '\0';
5862 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
5863 if(l < sizeof(tmp)){
5864 tmp[l] = tc->dir->delim;
5865 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
5868 else
5869 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
5871 tmp[sizeof(tmp)-1] = '\0';
5873 fake_context = new_context(tmp, 0);
5874 newfolder[0] = '\0';
5875 done = display_folder_list(&fake_context, newfolder,
5876 1, folders_for_goto);
5877 free_context(&fake_context);
5878 break;
5880 else if(!(tc->use & CNTXT_INCMNG)){
5881 done = display_folder_list(&tc, newfolder,
5882 1, folders_for_goto);
5883 break;
5886 else if((exists & FEX_ERROR)){
5887 q_status_message1(SM_ORDER, 0, 3,
5888 _("Problem accessing folder \"%s\""),
5889 newfolder);
5890 return(NULL);
5892 else{
5893 done++;
5894 break;
5897 if(exists == FEX_ERROR)
5898 q_status_message1(SM_ORDER, 0, 3,
5899 _("Problem accessing folder \"%s\""),
5900 newfolder);
5901 else if(tc->use & CNTXT_INCMNG)
5902 q_status_message1(SM_ORDER, 0, 3,
5903 _("Can't find Incoming Folder: %s"),
5904 newfolder);
5905 else if(context_isambig(newfolder))
5906 q_status_message2(SM_ORDER, 0, 3,
5907 _("Can't find folder \"%s\" in %s"),
5908 newfolder, (void *) tc->nickname);
5909 else
5910 q_status_message1(SM_ORDER, 0, 3,
5911 _("Can't find folder \"%s\""),
5912 newfolder);
5914 return(NULL);
5916 else if(last_folder){
5917 if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX
5918 && !strucmp(last_folder, ps_global->inbox_name)
5919 && tc == ((ps_global->context_list->use & CNTXT_INCMNG)
5920 ? ps_global->context_list->next : ps_global->context_list)){
5921 if(notrealinbox)
5922 (*notrealinbox) = 0;
5924 tc = ps_global->context_list;
5927 strncpy(newfolder, last_folder, sizeof(newfolder));
5928 newfolder[sizeof(newfolder)-1] = '\0';
5929 save_hist(history, newfolder, 0, tc);
5930 done++;
5931 break;
5933 /* fall thru like they cancelled */
5935 case 1 : /* o_e says user cancel */
5936 cmd_cancelled("Open folder");
5937 return(NULL);
5939 case 2 : /* o_e says user wants list */
5940 r = display_folder_list(&tc, newfolder, 0, folders_for_goto);
5941 if(r)
5942 done++;
5944 break;
5946 case 3 : /* o_e says user wants help */
5947 help = help == NO_HELP ? h_oe_broach : NO_HELP;
5948 break;
5950 case 4 : /* redraw */
5951 break;
5953 case 10 : /* Previous collection */
5954 tc2 = ps_global->context_list;
5955 while(tc2->next && tc2->next != tc)
5956 tc2 = tc2->next;
5958 tc = tc2;
5959 break;
5961 case 11 : /* Next collection */
5962 tc = (tc->next) ? tc->next : ps_global->context_list;
5963 break;
5965 case 12 : /* file name completion */
5966 if(!folder_complete(tc, newfolder, sizeof(newfolder), &n)){
5967 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
5968 r = display_folder_list(&tc, newfolder, 1,folders_for_goto);
5969 if(r)
5970 done++; /* bingo! */
5971 else
5972 rc = 0; /* burn last_rc */
5974 else
5975 Writechar(BELL, 0);
5978 break;
5980 case 14 : /* file name completion */
5981 r = display_folder_list(&tc, newfolder, 2, folders_for_goto);
5982 if(r)
5983 done++; /* bingo! */
5984 else
5985 rc = 0; /* burn last_rc */
5987 break;
5989 case 17 : /* GoTo INBOX */
5990 done++;
5991 strncpy(newfolder, ps_global->inbox_name, sizeof(newfolder)-1);
5992 newfolder[sizeof(newfolder)-1] = '\0';
5993 if(notrealinbox)
5994 (*notrealinbox) = 0;
5996 tc = ps_global->context_list;
5997 save_hist(history, newfolder, 0, tc);
5999 break;
6001 case 30 :
6002 if((p = get_prev_hist(history, newfolder, 0, tc)) != NULL){
6003 strncpy(newfolder, p, sizeof(newfolder));
6004 newfolder[sizeof(newfolder)-1] = '\0';
6005 if(history->hist[history->curindex])
6006 tc = history->hist[history->curindex]->cntxt;
6008 else
6009 Writechar(BELL, 0);
6011 break;
6013 case 31 :
6014 if((p = get_next_hist(history, newfolder, 0, tc)) != NULL){
6015 strncpy(newfolder, p, sizeof(newfolder));
6016 newfolder[sizeof(newfolder)-1] = '\0';
6017 if(history->hist[history->curindex])
6018 tc = history->hist[history->curindex]->cntxt;
6020 else
6021 Writechar(BELL, 0);
6023 break;
6025 default :
6026 alpine_panic("Unhandled case");
6027 break;
6030 last_rc = rc;
6033 dprint((2, "broach folder, name entered \"%s\"\n",
6034 newfolder ? newfolder : "?"));
6036 /*-- Just check that we can expand this. It gets done for real later --*/
6037 strncpy(expanded, newfolder, sizeof(expanded));
6038 expanded[sizeof(expanded)-1] = '\0';
6040 if(!expand_foldername(expanded, sizeof(expanded))) {
6041 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
6042 expanded ? expanded : "?"));
6043 return(NULL);
6046 *context = tc;
6047 return(newfolder);
6051 /*----------------------------------------------------------------------
6052 Check to see if user wants to reopen dead stream.
6054 Args: ps --
6055 reopenp --
6057 Result: 1 if the folder was successfully updatedn
6058 0 if not necessary
6060 ----*/
6062 ask_mailbox_reopen(struct pine *ps, int *reopenp)
6064 if(((ps->mail_stream->dtb
6065 && ((ps->mail_stream->dtb->flags & DR_NONEWMAIL)
6066 || (ps->mail_stream->rdonly
6067 && ps->mail_stream->dtb->flags & DR_NONEWMAILRONLY)))
6068 && (ps->reopen_rule == REOPEN_ASK_ASK_Y
6069 || ps->reopen_rule == REOPEN_ASK_ASK_N
6070 || ps->reopen_rule == REOPEN_ASK_NO_Y
6071 || ps->reopen_rule == REOPEN_ASK_NO_N))
6072 || ((ps->mail_stream->dtb
6073 && ps->mail_stream->rdonly
6074 && !(ps->mail_stream->dtb->flags & DR_LOCAL))
6075 && (ps->reopen_rule == REOPEN_YES_ASK_Y
6076 || ps->reopen_rule == REOPEN_YES_ASK_N
6077 || ps->reopen_rule == REOPEN_ASK_ASK_Y
6078 || ps->reopen_rule == REOPEN_ASK_ASK_N))){
6079 int deefault;
6081 switch(ps->reopen_rule){
6082 case REOPEN_YES_ASK_Y:
6083 case REOPEN_ASK_ASK_Y:
6084 case REOPEN_ASK_NO_Y:
6085 deefault = 'y';
6086 break;
6088 default:
6089 deefault = 'n';
6090 break;
6093 switch(want_to("Re-open folder to check for new messages", deefault,
6094 'x', h_reopen_folder, WT_NORM)){
6095 case 'y':
6096 (*reopenp)++;
6097 break;
6099 case 'x':
6100 return(-1);
6104 return(0);
6109 /*----------------------------------------------------------------------
6110 Check to see if user input is in form of old c-client mailbox speck
6112 Args: old --
6113 new --
6115 Result: 1 if the folder was successfully updatedn
6116 0 if not necessary
6118 ----*/
6120 update_folder_spec(char *new, size_t newlen, char *old)
6122 char *p, *orignew;
6123 int nntp = 0;
6125 orignew = new;
6126 if(*(p = old) == '*') /* old form? */
6127 old++;
6129 if(*old == '{') /* copy host spec */
6131 switch(*new = *old++){
6132 case '\0' :
6133 return(FALSE);
6135 case '/' :
6136 if(!struncmp(old, "nntp", 4))
6137 nntp++;
6139 break;
6141 default :
6142 break;
6144 while(*new++ != '}' && (new-orignew) < newlen-1);
6146 if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){
6148 * OK, some heuristics here. If it looks like a newsgroup
6149 * then we plunk it into the #news namespace else we
6150 * assume that they're trying to get at a #public folder...
6152 for(p = old;
6153 *p && (isalnum((unsigned char) *p) || strindex(".-", *p));
6154 p++)
6157 sstrncpy(&new, (*p && !nntp) ? "#public/" : "#news.", newlen-(new-orignew));
6158 strncpy(new, old, newlen-(new-orignew));
6159 return(TRUE);
6162 orignew[newlen-1] = '\0';
6164 return(FALSE);
6168 /*----------------------------------------------------------------------
6169 Open the requested folder in the requested context
6171 Args: state -- usual pine state struct
6172 newfolder -- folder to open
6173 new_context -- folder context might live in
6174 stream -- candidate for recycling
6176 Result: New folder open or not (if error), and we're set to
6177 enter the index screen.
6178 ----*/
6179 void
6180 visit_folder(struct pine *state, char *newfolder, CONTEXT_S *new_context,
6181 MAILSTREAM *stream, long unsigned int flags)
6183 dprint((9, "visit_folder(%s, %s)\n",
6184 newfolder ? newfolder : "?",
6185 (new_context && new_context->context)
6186 ? new_context->context : "(NULL)"));
6188 if(ps_global && ps_global->ttyo){
6189 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
6190 ps_global->mangled_footer = 1;
6193 if(do_broach_folder(newfolder, new_context, stream ? &stream : NULL,
6194 flags) >= 0
6195 || !sp_flagged(state->mail_stream, SP_LOCKED))
6196 state->next_screen = mail_index_screen;
6197 else
6198 state->next_screen = folder_screen;
6202 /*----------------------------------------------------------------------
6203 Move read messages from folder if listed in archive
6205 Args:
6207 ----*/
6209 read_msg_prompt(long int n, char *f)
6211 char buf[MAX_SCREEN_COLS+1];
6213 snprintf(buf, sizeof(buf), "Save the %ld read message%s in \"%s\"", n, plural(n), f);
6214 buf[sizeof(buf)-1] = '\0';
6215 return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y');
6219 /*----------------------------------------------------------------------
6220 Print current message[s] or folder index
6222 Args: state -- pointer to struct holding a bunch of pine state
6223 msgmap -- table mapping msg nums to c-client sequence nums
6224 aopt -- aggregate options
6225 in_index -- boolean indicating we're called from Index Screen
6227 Filters the original header and sends stuff to printer
6228 ---*/
6230 cmd_print(struct pine *state, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
6232 char prompt[250];
6233 long i, msgs, rawno;
6234 int next = 0, do_index = 0, rv = 0;
6235 ENVELOPE *e;
6236 BODY *b;
6237 MESSAGECACHE *mc;
6239 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
6240 return rv;
6242 msgs = mn_total_cur(msgmap);
6244 if((in_index != View) && F_ON(F_PRINT_INDEX, state)){
6245 char m[10];
6246 int ans;
6247 static ESCKEY_S prt_opts[] = {
6248 {'i', 'i', "I", N_("Index")},
6249 {'m', 'm', "M", NULL},
6250 {-1, 0, NULL, NULL}};
6252 if(in_index == ThrdIndx){
6253 /* TRANSLATORS: This is a question, Print Index ? */
6254 if(want_to(_("Print Index"), 'y', 'x', NO_HELP, WT_NORM) == 'y')
6255 ans = 'i';
6256 else
6257 ans = 'x';
6259 else{
6260 snprintf(m, sizeof(m), "Message%s", (msgs>1L) ? "s" : "");
6261 m[sizeof(m)-1] = '\0';
6262 prt_opts[1].label = m;
6263 snprintf(prompt, sizeof(prompt), "Print %sFolder Index or %s %s? ",
6264 (aopt & MCMD_AGG_2) ? "thread " : MCMD_ISAGG(aopt) ? "selected " : "",
6265 (aopt & MCMD_AGG_2) ? "thread" : MCMD_ISAGG(aopt) ? "selected" : "current", m);
6266 prompt[sizeof(prompt)-1] = '\0';
6268 ans = radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x',
6269 NO_HELP, RB_NORM|RB_SEQ_SENSITIVE);
6272 switch(ans){
6273 case 'x' :
6274 cmd_cancelled("Print");
6275 if(MCMD_ISAGG(aopt))
6276 restore_selected(msgmap);
6278 return rv;
6280 case 'i':
6281 do_index = 1;
6282 break;
6284 default :
6285 case 'm':
6286 break;
6290 if(do_index)
6291 snprintf(prompt, sizeof(prompt), "%sFolder Index",
6292 (aopt & MCMD_AGG_2) ? "Thread " : MCMD_ISAGG(aopt) ? "Selected " : "");
6293 else if(msgs > 1L)
6294 snprintf(prompt, sizeof(prompt), "%s messages", long2string(msgs));
6295 else
6296 snprintf(prompt, sizeof(prompt), "Message %s", long2string(mn_get_cur(msgmap)));
6298 prompt[sizeof(prompt)-1] = '\0';
6300 if(open_printer(prompt) < 0){
6301 if(MCMD_ISAGG(aopt))
6302 restore_selected(msgmap);
6304 return rv;
6307 if(do_index){
6308 TITLE_S *tc;
6310 tc = format_titlebar();
6312 /* Print titlebar... */
6313 print_text1("%s\n\n", tc ? tc->titlebar_line : "");
6314 /* then all the index members... */
6315 if(!print_index(state, msgmap, MCMD_ISAGG(aopt)))
6316 q_status_message(SM_ORDER | SM_DING, 3, 3,
6317 _("Error printing folder index"));
6318 else
6319 rv++;
6321 else{
6322 rv++;
6323 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){
6324 if(next && F_ON(F_AGG_PRINT_FF, state))
6325 if(!print_char(FORMFEED)){
6326 rv = 0;
6327 break;
6330 if(!(state->mail_stream
6331 && (rawno = mn_m2raw(msgmap, i)) > 0L
6332 && rawno <= state->mail_stream->nmsgs
6333 && (mc = mail_elt(state->mail_stream, rawno))
6334 && mc->valid))
6335 mc = NULL;
6337 if(!(e=pine_mail_fetchstructure(state->mail_stream,
6338 mn_m2raw(msgmap,i),
6339 &b))
6340 || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global)
6341 && !bezerk_delimiter(e, mc, print_char, next))
6342 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
6343 e, b, NULL, FM_NEW_MESS | FM_NOINDENT,
6344 print_char)){
6345 q_status_message(SM_ORDER | SM_DING, 3, 3,
6346 _("Error printing message"));
6347 rv = 0;
6348 break;
6353 close_printer();
6355 if(MCMD_ISAGG(aopt))
6356 restore_selected(msgmap);
6358 return rv;
6362 /*----------------------------------------------------------------------
6363 Pipe message text
6365 Args: state -- various pine state bits
6366 msgmap -- Message number mapping table
6367 aopt -- option flags
6369 Filters the original header and sends stuff to specified command
6370 ---*/
6372 cmd_pipe(struct pine *state, MSGNO_S *msgmap, int aopt)
6374 ENVELOPE *e;
6375 MESSAGECACHE *mc;
6376 BODY *b;
6377 PIPE_S *syspipe;
6378 char *resultfilename = NULL, prompt[80], *p;
6379 int done = 0, rv = 0;
6380 gf_io_t pc;
6381 int fourlabel = -1, j = 0, next = 0, ku;
6382 int pipe_rv; /* rv of proc to separate from close_system_pipe rv */
6383 long i, rawno;
6384 unsigned flagsforhist = 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6385 static HISTORY_S *history = NULL;
6386 int capture = 1, raw = 0, delimit = 0, newpipe = 0;
6387 char pipe_command[MAXPATH];
6388 ESCKEY_S pipe_opt[8];
6390 if(ps_global->restricted){
6391 q_status_message(SM_ORDER | SM_DING, 0, 4,
6392 "Alpine demo can't pipe messages");
6393 return rv;
6395 else if(!any_messages(msgmap, NULL, "to Pipe"))
6396 return rv;
6398 pipe_command[0] = '\0';
6399 init_hist(&history, HISTSIZE);
6400 flagsforhist = (raw ? 0x8 : 0) +
6401 (delimit ? 0x4 : 0) +
6402 (newpipe ? 0x2 : 0) +
6403 (capture ? 0x1 : 0);
6404 if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){
6405 strncpy(pipe_command, p, sizeof(pipe_command));
6406 pipe_command[sizeof(pipe_command)-1] = '\0';
6407 if(history->hist[history->curindex]){
6408 flagsforhist = history->hist[history->curindex]->flags;
6409 raw = (flagsforhist & 0x8) ? 1 : 0;
6410 delimit = (flagsforhist & 0x4) ? 1 : 0;
6411 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6412 capture = (flagsforhist & 0x1) ? 1 : 0;
6416 pipe_opt[j].ch = 0;
6417 pipe_opt[j].rval = 0;
6418 pipe_opt[j].name = "";
6419 pipe_opt[j++].label = "";
6421 pipe_opt[j].ch = ctrl('W');
6422 pipe_opt[j].rval = 10;
6423 pipe_opt[j].name = "^W";
6424 pipe_opt[j++].label = NULL;
6426 pipe_opt[j].ch = ctrl('Y');
6427 pipe_opt[j].rval = 11;
6428 pipe_opt[j].name = "^Y";
6429 pipe_opt[j++].label = NULL;
6431 pipe_opt[j].ch = ctrl('R');
6432 pipe_opt[j].rval = 12;
6433 pipe_opt[j].name = "^R";
6434 pipe_opt[j++].label = NULL;
6436 if(MCMD_ISAGG(aopt)){
6437 if(!pseudo_selected(state->mail_stream, msgmap))
6438 return rv;
6439 else{
6440 fourlabel = j;
6441 pipe_opt[j].ch = ctrl('T');
6442 pipe_opt[j].rval = 13;
6443 pipe_opt[j].name = "^T";
6444 pipe_opt[j++].label = NULL;
6448 pipe_opt[j].ch = KEY_UP;
6449 pipe_opt[j].rval = 30;
6450 pipe_opt[j].name = "";
6451 ku = j;
6452 pipe_opt[j++].label = "";
6454 pipe_opt[j].ch = KEY_DOWN;
6455 pipe_opt[j].rval = 31;
6456 pipe_opt[j].name = "";
6457 pipe_opt[j++].label = "";
6459 pipe_opt[j].ch = -1;
6461 while (!done) {
6462 int flags;
6464 snprintf(prompt, sizeof(prompt), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6465 raw ? "RAW " : "",
6466 MCMD_ISAGG(aopt) ? "s" : " ",
6467 MCMD_ISAGG(aopt) ? "" : comatose(mn_get_cur(msgmap)),
6468 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? "(" : "",
6469 capture ? "" : "uncaptured",
6470 (!capture && delimit) ? "," : "",
6471 delimit ? "delimited" : "",
6472 ((!capture || delimit) && newpipe && MCMD_ISAGG(aopt)) ? "," : "",
6473 (newpipe && MCMD_ISAGG(aopt)) ? "new pipe" : "",
6474 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? ") " : "");
6475 prompt[sizeof(prompt)-1] = '\0';
6476 pipe_opt[1].label = raw ? N_("Shown Text") : N_("Raw Text");
6477 pipe_opt[2].label = capture ? N_("Free Output") : N_("Capture Output");
6478 pipe_opt[3].label = delimit ? N_("No Delimiter") : N_("With Delimiter");
6479 if(fourlabel > 0)
6480 pipe_opt[fourlabel].label = newpipe ? N_("To Same Pipe") : N_("To Individual Pipes");
6484 * 2 is really 1 because there will be one real entry and
6485 * one entry of "" because of the get_prev_hist above.
6487 if(items_in_hist(history) > 2){
6488 pipe_opt[ku].name = HISTORY_UP_KEYNAME;
6489 pipe_opt[ku].label = HISTORY_KEYLABEL;
6490 pipe_opt[ku+1].name = HISTORY_DOWN_KEYNAME;
6491 pipe_opt[ku+1].label = HISTORY_KEYLABEL;
6493 else{
6494 pipe_opt[ku].name = "";
6495 pipe_opt[ku].label = "";
6496 pipe_opt[ku+1].name = "";
6497 pipe_opt[ku+1].label = "";
6500 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
6501 switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0,
6502 sizeof(pipe_command), prompt,
6503 pipe_opt, NO_HELP, &flags)){
6504 case -1 :
6505 q_status_message(SM_ORDER | SM_DING, 3, 4,
6506 _("Internal problem encountered"));
6507 done++;
6508 break;
6510 case 10 : /* flip raw bit */
6511 raw = !raw;
6512 break;
6514 case 11 : /* flip capture bit */
6515 capture = !capture;
6516 break;
6518 case 12 : /* flip delimit bit */
6519 delimit = !delimit;
6520 break;
6522 case 13 : /* flip newpipe bit */
6523 newpipe = !newpipe;
6524 break;
6526 case 30 :
6527 flagsforhist = (raw ? 0x8 : 0) +
6528 (delimit ? 0x4 : 0) +
6529 (newpipe ? 0x2 : 0) +
6530 (capture ? 0x1 : 0);
6531 if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6532 strncpy(pipe_command, p, sizeof(pipe_command));
6533 pipe_command[sizeof(pipe_command)-1] = '\0';
6534 if(history->hist[history->curindex]){
6535 flagsforhist = history->hist[history->curindex]->flags;
6536 raw = (flagsforhist & 0x8) ? 1 : 0;
6537 delimit = (flagsforhist & 0x4) ? 1 : 0;
6538 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6539 capture = (flagsforhist & 0x1) ? 1 : 0;
6542 else
6543 Writechar(BELL, 0);
6545 break;
6547 case 31 :
6548 flagsforhist = (raw ? 0x8 : 0) +
6549 (delimit ? 0x4 : 0) +
6550 (newpipe ? 0x2 : 0) +
6551 (capture ? 0x1 : 0);
6552 if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6553 strncpy(pipe_command, p, sizeof(pipe_command));
6554 pipe_command[sizeof(pipe_command)-1] = '\0';
6555 if(history->hist[history->curindex]){
6556 flagsforhist = history->hist[history->curindex]->flags;
6557 raw = (flagsforhist & 0x8) ? 1 : 0;
6558 delimit = (flagsforhist & 0x4) ? 1 : 0;
6559 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6560 capture = (flagsforhist & 0x1) ? 1 : 0;
6563 else
6564 Writechar(BELL, 0);
6566 break;
6568 case 0 :
6569 if(pipe_command[0]){
6571 flagsforhist = (raw ? 0x8 : 0) +
6572 (delimit ? 0x4 : 0) +
6573 (newpipe ? 0x2 : 0) +
6574 (capture ? 0x1 : 0);
6575 save_hist(history, pipe_command, flagsforhist, NULL);
6577 flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
6578 flags |= (raw ? PIPE_RAW : 0);
6579 if(!capture){
6580 #ifndef _WINDOWS
6581 ClearScreen();
6582 fflush(stdout);
6583 clear_cursor_pos();
6584 ps_global->mangled_screen = 1;
6585 ps_global->in_init_seq = 1;
6586 #endif
6587 flags |= PIPE_RESET;
6590 if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command,
6591 (flags & PIPE_RESET)
6592 ? NULL
6593 : &resultfilename,
6594 flags, &pc)))
6595 done++;
6597 for(i = mn_first_cur(msgmap);
6598 i > 0L && !done;
6599 i = mn_next_cur(msgmap)){
6600 e = pine_mail_fetchstructure(ps_global->mail_stream,
6601 mn_m2raw(msgmap, i), &b);
6602 if(!(state->mail_stream
6603 && (rawno = mn_m2raw(msgmap, i)) > 0L
6604 && rawno <= state->mail_stream->nmsgs
6605 && (mc = mail_elt(state->mail_stream, rawno))
6606 && mc->valid))
6607 mc = NULL;
6609 if((newpipe
6610 && !(syspipe = cmd_pipe_open(pipe_command,
6611 (flags & PIPE_RESET)
6612 ? NULL
6613 : &resultfilename,
6614 flags, &pc)))
6615 || (delimit && !bezerk_delimiter(e, mc, pc, next++)))
6616 done++;
6618 if(!done){
6619 if(raw){
6620 char *pipe_err;
6622 prime_raw_pipe_getc(ps_global->mail_stream,
6623 mn_m2raw(msgmap, i), -1L, 0L);
6624 gf_filter_init();
6625 gf_link_filter(gf_nvtnl_local, NULL);
6626 if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){
6627 q_status_message1(SM_ORDER|SM_DING,
6628 3, 3,
6629 _("Internal Error: %s"),
6630 pipe_err);
6631 done++;
6634 else if(!format_message(mn_m2raw(msgmap, i), e, b,
6635 NULL, FM_NEW_MESS | FM_NOWRAP, pc))
6636 done++;
6639 if(newpipe)
6640 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6641 done++;
6644 if(!capture)
6645 ps_global->in_init_seq = 0;
6647 if(!newpipe)
6648 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6649 done++;
6650 if(done) /* say we had a problem */
6651 q_status_message(SM_ORDER | SM_DING, 3, 3,
6652 _("Error piping message"));
6653 else if(resultfilename){
6654 rv++;
6655 /* only display if no error */
6656 display_output_file(resultfilename, "PIPE MESSAGE",
6657 NULL, DOF_EMPTY);
6658 fs_give((void **)&resultfilename);
6660 else{
6661 rv++;
6662 q_status_message(SM_ORDER, 0, 2, _("Pipe command completed"));
6665 done++;
6666 break;
6668 /* else fall thru as if cancelled */
6670 case 1 :
6671 cmd_cancelled("Pipe command");
6672 done++;
6673 break;
6675 case 3 :
6676 helper(h_common_pipe, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE);
6677 ps_global->mangled_screen = 1;
6678 break;
6680 case 2 : /* no place to escape to */
6681 case 4 : /* can't suspend */
6682 default :
6683 break;
6687 ps_global->mangled_footer = 1;
6688 if(MCMD_ISAGG(aopt))
6689 restore_selected(msgmap);
6691 return rv;
6695 /*----------------------------------------------------------------------
6696 Screen to offer list management commands contained in message
6698 Args: state -- pointer to struct holding a bunch of pine state
6699 msgmap -- table mapping msg nums to c-client sequence nums
6700 aopt -- aggregate options
6702 Result:
6704 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6705 ----*/
6706 void
6707 rfc2369_display(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno)
6709 int winner = 0;
6710 char *h, *hdrs[MLCMD_COUNT + 1];
6711 long index_no = mn_raw2m(msgmap, msgno);
6712 RFC2369_S data[MLCMD_COUNT];
6714 /* for each header field */
6715 if((h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))) != NULL){
6716 memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT);
6717 if(rfc2369_parse_fields(h, &data[0])){
6718 STORE_S *explain;
6720 if((explain = list_mgmt_text(data, index_no)) != NULL){
6721 list_mgmt_screen(explain);
6722 ps_global->mangled_screen = 1;
6723 so_give(&explain);
6724 winner++;
6728 fs_give((void **) &h);
6731 if(!winner)
6732 q_status_message1(SM_ORDER, 0, 3,
6733 "Message %s contains no list management information",
6734 comatose(index_no));
6738 STORE_S *
6739 list_mgmt_text(RFC2369_S *data, long int msgno)
6741 STORE_S *store;
6742 int i, j, n, fields = 0;
6743 static char *rfc2369_intro1 =
6744 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6745 static char *rfc2369_intro2[] = {
6746 N_(" has information associated with it "),
6747 N_("that explains how to participate in an email list. An "),
6748 N_("email list is represented by a single email address that "),
6749 N_("users sharing a common interest can send messages to (known "),
6750 N_("as posting) which are then redistributed to all members "),
6751 N_("of the list (sometimes after review by a moderator)."),
6752 N_("<P>List participation commands in this message include:"),
6753 NULL
6756 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6758 /* Insert introductory text */
6759 so_puts(store, rfc2369_intro1);
6761 so_puts(store, comatose(msgno));
6763 for(i = 0; rfc2369_intro2[i]; i++)
6764 so_puts(store, _(rfc2369_intro2[i]));
6766 so_puts(store, "<P>");
6767 for(i = 0; i < MLCMD_COUNT; i++)
6768 if(data[i].data[0].value
6769 || data[i].data[0].comment
6770 || data[i].data[0].error){
6771 if(!fields++)
6772 so_puts(store, "<UL>");
6774 so_puts(store, "<LI>");
6775 so_puts(store,
6776 (n = (data[i].data[1].value || data[i].data[1].comment))
6777 ? "Methods to "
6778 : "A method to ");
6780 so_puts(store, data[i].field.description);
6781 so_puts(store, ". ");
6783 if(n)
6784 so_puts(store, "<OL>");
6786 for(j = 0;
6787 j < MLCMD_MAXDATA
6788 && (data[i].data[j].comment
6789 || data[i].data[j].value
6790 || data[i].data[j].error);
6791 j++){
6793 so_puts(store, n ? "<P><LI>" : "<P>");
6795 if(data[i].data[j].comment){
6796 so_puts(store,
6797 _("With the provided comment:<P><BLOCKQUOTE>"));
6798 so_puts(store, data[i].data[j].comment);
6799 so_puts(store, "</BLOCKQUOTE><P>");
6802 if(data[i].data[j].value){
6803 if(i == MLCMD_POST
6804 && !strucmp(data[i].data[j].value, "NO")){
6805 so_puts(store,
6806 _("Posting is <EM>not</EM> allowed on this list"));
6808 else{
6809 so_puts(store, "Select <A HREF=\"");
6810 so_puts(store, data[i].data[j].value);
6811 so_puts(store, "\">HERE</A> to ");
6812 so_puts(store, (data[i].field.action)
6813 ? data[i].field.action
6814 : "try it");
6817 so_puts(store, ".");
6820 if(data[i].data[j].error){
6821 so_puts(store, "<P>Unfortunately, Alpine can not offer");
6822 so_puts(store, " to take direct action based upon it");
6823 so_puts(store, " because it was improperly formatted.");
6824 so_puts(store, " The unrecognized data associated with");
6825 so_puts(store, " the \"");
6826 so_puts(store, data[i].field.name);
6827 so_puts(store, "\" header field was:<P><BLOCKQUOTE>");
6828 so_puts(store, data[i].data[j].error);
6829 so_puts(store, "</BLOCKQUOTE>");
6832 so_puts(store, "<P>");
6835 if(n)
6836 so_puts(store, "</OL>");
6839 if(fields)
6840 so_puts(store, "</UL>");
6842 so_puts(store, "</BODY></HTML>");
6845 return(store);
6849 void
6850 list_mgmt_screen(STORE_S *html)
6852 int cmd = MC_NONE;
6853 long offset = 0L;
6854 char *error = NULL;
6855 STORE_S *store;
6856 HANDLE_S *handles = NULL;
6857 gf_io_t gc, pc;
6860 so_seek(html, 0L, 0);
6861 gf_set_so_readc(&gc, html);
6863 init_handles(&handles);
6865 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6866 gf_set_so_writec(&pc, store);
6867 gf_filter_init();
6869 gf_link_filter(gf_html2plain,
6870 gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols,
6871 non_messageview_margin(), &handles, NULL, 0));
6873 error = gf_pipe(gc, pc);
6875 gf_clear_so_writec(store);
6877 if(!error){
6878 SCROLL_S sargs;
6880 memset(&sargs, 0, sizeof(SCROLL_S));
6881 sargs.text.text = so_text(store);
6882 sargs.text.src = CharStar;
6883 sargs.text.desc = "list commands";
6884 sargs.text.handles = handles;
6885 if(offset){
6886 sargs.start.on = Offset;
6887 sargs.start.loc.offset = offset;
6890 sargs.bar.title = _("MAIL LIST COMMANDS");
6891 sargs.bar.style = MessageNumber;
6892 sargs.resize_exit = 1;
6893 sargs.help.text = h_special_list_commands;
6894 sargs.help.title = _("HELP FOR LIST COMMANDS");
6895 sargs.keys.menu = &listmgr_keymenu;
6896 setbitmap(sargs.keys.bitmap);
6897 if(!handles){
6898 clrbitn(LM_TRY_KEY, sargs.keys.bitmap);
6899 clrbitn(LM_PREV_KEY, sargs.keys.bitmap);
6900 clrbitn(LM_NEXT_KEY, sargs.keys.bitmap);
6903 cmd = scrolltool(&sargs);
6904 offset = sargs.start.loc.offset;
6907 so_give(&store);
6910 free_handles(&handles);
6911 gf_clear_so_readc(html);
6913 while(cmd == MC_RESIZE);
6917 /*----------------------------------------------------------------------
6918 Prompt the user for the type of select desired
6920 NOTE: any and all functions that successfully exit the second
6921 switch() statement below (currently "select_*() functions"),
6922 *MUST* update the folder's MESSAGECACHE element's "searched"
6923 bits to reflect the search result. Functions using
6924 mail_search() get this for free, the others must update 'em
6925 by hand.
6927 Returns -1 if canceled without changing selection
6928 0 if selection may have changed
6929 ----*/
6931 aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index)
6933 long i, diff, old_tot, msgno, raw;
6934 int p = 0, q = 0, rv = 0, narrow = 0, hidden, ret = -1;
6935 ESCKEY_S *sel_opts;
6936 MESSAGECACHE *mc;
6937 SEARCHSET *limitsrch = NULL;
6938 PINETHRD_S *thrd;
6939 extern MAILSTREAM *mm_search_stream;
6940 extern long mm_search_count;
6942 hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
6943 mm_search_stream = state->mail_stream;
6944 mm_search_count = 0L;
6946 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
6947 sel_opts = THRD_INDX() ? sel_opts6 : sel_opts5;
6948 else
6949 sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2;
6951 if(THREADING()){
6952 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH;
6954 else{
6955 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream)){
6956 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_XGMEXT_CH;
6957 sel_opts[SEL_OPTS_THREAD].rval = sel_opts[SEL_OPTS_XGMEXT].rval;
6958 sel_opts[SEL_OPTS_THREAD].name = sel_opts[SEL_OPTS_XGMEXT].name;
6959 sel_opts[SEL_OPTS_THREAD].label = sel_opts[SEL_OPTS_XGMEXT].label;
6960 sel_opts[SEL_OPTS_XGMEXT].ch = -1;
6962 else
6963 sel_opts[SEL_OPTS_THREAD].ch = -1;
6966 if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){
6967 if(THRD_INDX()){
6968 i = 0;
6969 thrd = fetch_thread(state->mail_stream,
6970 mn_m2raw(msgmap, mn_get_cur(msgmap)));
6971 /* check if whole thread is selected or not */
6972 if(thrd &&
6973 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT)
6975 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE))
6976 i = 1;
6978 sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd");
6980 else{
6981 i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap),
6982 MN_SLCT);
6983 sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur");
6986 sel_opts += 2; /* disable extra options */
6987 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
6988 q = double_radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6989 RB_NORM);
6990 else
6991 q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6992 RB_NORM);
6993 switch(q){
6994 case 'f' : /* flip selection */
6995 msgno = 0L;
6996 for(i = 1L; i <= mn_get_total(msgmap); i++){
6997 ret = 0;
6998 q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT);
6999 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q);
7000 if(hidden){
7001 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q);
7002 if(!msgno && q)
7003 mn_reset_cur(msgmap, msgno = i);
7007 return(ret);
7009 case 'n' : /* narrow selection */
7010 narrow++;
7011 q = 0;
7012 break;
7013 case 'r' : /* replace selection */
7014 p = 1; /* set flag we want to replace */
7015 sel_opts -= 2; /* re-enable first two options */
7016 case 'b' : /* broaden selection */
7017 q = 0; /* offer criteria prompt */
7018 break;
7020 case 'c' : /* Un/Select Current */
7021 case 'a' : /* Unselect All */
7022 case 'x' : /* cancel */
7023 break;
7025 default :
7026 q_status_message(SM_ORDER | SM_DING, 3, 3,
7027 "Unsupported Select option");
7028 return(ret);
7032 if(!q){
7033 while(1){
7034 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
7035 q = double_radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP,
7036 RB_NORM);
7037 else
7038 q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP,
7039 RB_NORM|RB_RET_HELP);
7041 if(q == 3){
7042 helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE);
7043 ps_global->mangled_screen = 1;
7045 else
7046 break;
7050 /* When we are replacing the search, unselect all messages first, unless
7051 * we are cancelling, in whose case, leave the screen as is, because we
7052 * are cancelling!
7054 if(p == 1 && q != 'x'){
7055 msgno = any_lflagged(msgmap, MN_SLCT);
7056 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
7057 agg_select_all(state->mail_stream, msgmap, &diff,
7058 any_lflagged(msgmap, MN_SLCT) <= 0L);
7062 * The purpose of this is to add the appropriate searchset to the
7063 * search so that the search can be limited to only looking at what
7064 * it needs to look at. That is, if we are narrowing then we only need
7065 * to look at messages which are already selected, and if we are
7066 * broadening, then we only need to look at messages which are not
7067 * yet selected. This routine will work whether or not
7068 * limiting_searchset properly limits the search set. In particular,
7069 * the searchset returned by limiting_searchset may include messages
7070 * which really shouldn't be included. We do that because a too-large
7071 * searchset will break some IMAP servers. It is even possible that it
7072 * becomes inefficient to send the whole set. If the select function
7073 * frees limitsrch, it should be sure to set it to NULL so we won't
7074 * try freeing it again here.
7076 limitsrch = limiting_searchset(state->mail_stream, narrow);
7079 * NOTE: See note about MESSAGECACHE "searched" bits above!
7081 switch(q){
7082 case 'x': /* cancel */
7083 cmd_cancelled("Select command");
7084 return(ret);
7086 case 'c' : /* select/unselect current */
7087 (void) select_by_current(state, msgmap, in_index);
7088 ret = 0;
7089 return(ret);
7091 case 'a' : /* select/unselect all */
7092 msgno = any_lflagged(msgmap, MN_SLCT);
7093 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
7094 ret = 0;
7095 agg_select_all(state->mail_stream, msgmap, &diff,
7096 any_lflagged(msgmap, MN_SLCT) <= 0L);
7097 q_status_message4(SM_ORDER,0,2,
7098 "%s%s message%s %sselected",
7099 msgno ? "" : "All ", comatose(diff),
7100 plural(diff), msgno ? "UN" : "");
7101 return(ret);
7103 case 'n' : /* Select by Number */
7104 ret = 0;
7105 if(THRD_INDX())
7106 rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch);
7107 else
7108 rv = select_by_number(state->mail_stream, msgmap, &limitsrch);
7110 break;
7112 case 'd' : /* Select by Date */
7113 ret = 0;
7114 rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap),
7115 &limitsrch);
7116 break;
7118 case 't' : /* Text */
7119 ret = 0;
7120 rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap),
7121 &limitsrch);
7122 break;
7124 case 'z' : /* Size */
7125 ret = 0;
7126 rv = select_by_size(state->mail_stream, &limitsrch);
7127 break;
7129 case 's' : /* Status */
7130 ret = 0;
7131 rv = select_by_status(state->mail_stream, &limitsrch);
7132 break;
7134 case 'k' : /* Keyword */
7135 ret = 0;
7136 rv = select_by_keyword(state->mail_stream, &limitsrch);
7137 break;
7139 case 'r' : /* Rule */
7140 ret = 0;
7141 rv = select_by_rule(state->mail_stream, &limitsrch);
7142 break;
7144 case 'h' : /* Thread */
7145 ret = 0;
7146 rv = select_by_thread(state->mail_stream, msgmap, &limitsrch);
7147 break;
7149 case 'g' : /* X-GM-EXT-1 */
7150 ret = 0;
7151 rv = select_by_gm_content(state->mail_stream, msgmap, mn_get_cur(msgmap), &limitsrch);
7152 break;
7154 default :
7155 q_status_message(SM_ORDER | SM_DING, 3, 3,
7156 "Unsupported Select option");
7157 return(ret);
7160 if(limitsrch)
7161 mail_free_searchset(&limitsrch);
7163 if(rv) /* bad return value.. */
7164 return(ret); /* error already displayed */
7166 if(narrow) /* make sure something was selected */
7167 for(i = 1L; i <= mn_get_total(msgmap); i++)
7168 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7169 && raw <= state->mail_stream->nmsgs
7170 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7171 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
7172 break;
7173 else
7174 mm_search_count--;
7177 diff = 0L;
7178 if(mm_search_count){
7180 * loop thru all the messages, adjusting local flag bits
7181 * based on their "searched" bit...
7183 for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++)
7184 if(narrow){
7185 /* turning OFF selectedness if the "searched" bit isn't lit. */
7186 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7187 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7188 && raw <= state->mail_stream->nmsgs
7189 && (mc = mail_elt(state->mail_stream, raw))
7190 && !mc->searched){
7191 diff--;
7192 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
7193 if(hidden)
7194 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
7196 /* adjust current message in case we unselect and hide it */
7197 else if(msgno < mn_get_cur(msgmap)
7198 && (!THRD_INDX()
7199 || !get_lflag(state->mail_stream, msgmap,
7200 i, MN_CHID)))
7201 msgno = i;
7204 else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7205 && raw <= state->mail_stream->nmsgs
7206 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7207 /* turn ON selectedness if "searched" bit is lit. */
7208 if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7209 diff++;
7210 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
7211 if(hidden)
7212 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
7216 /* if we're zoomed and the current message was unselected */
7217 if(narrow && msgno
7218 && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE))
7219 mn_reset_cur(msgmap, msgno);
7222 if(!diff){
7223 if(narrow)
7224 q_status_message4(SM_ORDER, 3, 3,
7225 "%s. %s message%s remain%s selected.",
7226 mm_search_count
7227 ? "No change resulted"
7228 : "No messages in intersection",
7229 comatose(old_tot), plural(old_tot),
7230 (old_tot == 1L) ? "s" : "");
7231 else if(old_tot)
7232 q_status_message(SM_ORDER, 3, 3,
7233 _("No change resulted. Matching messages already selected."));
7234 else
7235 q_status_message1(SM_ORDER | SM_DING, 3, 3,
7236 _("Select failed. No %smessages selected."),
7237 old_tot ? _("additional ") : "");
7239 else if(old_tot){
7240 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
7241 "Select matched %ld message%s. %s %smessage%s %sselected.",
7242 (diff > 0) ? diff : old_tot + diff,
7243 plural((diff > 0) ? diff : old_tot + diff),
7244 comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7245 (diff > 0) ? "total " : "",
7246 plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7247 (diff > 0) ? "" : "UN");
7248 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
7249 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
7251 else
7252 q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"),
7253 comatose(diff), plural(diff));
7255 return(ret);
7259 /*----------------------------------------------------------------------
7260 Toggle the state of the current message
7262 Args: state -- pointer pine's state variables
7263 msgmap -- message collection to operate on
7264 in_index -- in the message index view
7265 Returns: TRUE if current marked selected, FALSE otw
7266 ----*/
7268 select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
7270 long cur;
7271 int all_selected = 0;
7272 unsigned long was, tot, rawno;
7273 PINETHRD_S *thrd;
7275 cur = mn_get_cur(msgmap);
7277 if(THRD_INDX()){
7278 thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur));
7279 if(!thrd)
7280 return 0;
7282 was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT);
7283 tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE);
7284 if(was == tot)
7285 all_selected++;
7287 if(all_selected){
7288 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0);
7289 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7290 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1);
7292 * See if there's anything left to zoom on. If so,
7293 * pick an adjacent one for highlighting, else make
7294 * sure nothing is left hidden...
7296 if(any_lflagged(msgmap, MN_SLCT)){
7297 mn_inc_cur(state->mail_stream, msgmap,
7298 (in_index == View && THREADING()
7299 && sp_viewing_a_thread(state->mail_stream))
7300 ? MH_THISTHD
7301 : (in_index == View)
7302 ? MH_ANYTHD : MH_NONE);
7303 if(mn_get_cur(msgmap) == cur)
7304 mn_dec_cur(state->mail_stream, msgmap,
7305 (in_index == View && THREADING()
7306 && sp_viewing_a_thread(state->mail_stream))
7307 ? MH_THISTHD
7308 : (in_index == View)
7309 ? MH_ANYTHD : MH_NONE);
7311 else /* clear all hidden flags */
7312 (void) unzoom_index(state, state->mail_stream, msgmap);
7315 else
7316 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1);
7318 q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected",
7319 comatose(all_selected ? was : tot-was),
7320 plural(all_selected ? was : tot-was),
7321 all_selected ? "UN" : "");
7323 /* collapsed thread */
7324 else if(THREADING()
7325 && ((rawno = mn_m2raw(msgmap, cur)) != 0L)
7326 && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL)
7327 && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){
7329 * This doesn't work quite the same as the colon command works, but
7330 * it is arguably doing the correct thing. The difference is
7331 * that aggregate_select will zoom after selecting back where it
7332 * was called from, but selecting a thread with colon won't zoom.
7333 * Maybe it makes sense to zoom after a select but not after a colon
7334 * command even though they are very similar.
7336 thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state));
7338 else{
7339 if((all_selected =
7340 get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */
7341 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0);
7342 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7343 set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1);
7345 * See if there's anything left to zoom on. If so,
7346 * pick an adjacent one for highlighting, else make
7347 * sure nothing is left hidden...
7349 if(any_lflagged(msgmap, MN_SLCT)){
7350 mn_inc_cur(state->mail_stream, msgmap,
7351 (in_index == View && THREADING()
7352 && sp_viewing_a_thread(state->mail_stream))
7353 ? MH_THISTHD
7354 : (in_index == View)
7355 ? MH_ANYTHD : MH_NONE);
7356 if(mn_get_cur(msgmap) == cur)
7357 mn_dec_cur(state->mail_stream, msgmap,
7358 (in_index == View && THREADING()
7359 && sp_viewing_a_thread(state->mail_stream))
7360 ? MH_THISTHD
7361 : (in_index == View)
7362 ? MH_ANYTHD : MH_NONE);
7364 else /* clear all hidden flags */
7365 (void) unzoom_index(state, state->mail_stream, msgmap);
7368 else
7369 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1);
7371 q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected",
7372 long2string(cur), all_selected ? "UN" : "");
7376 return(!all_selected);
7380 /*----------------------------------------------------------------------
7381 Prompt the user for the command to perform on selected messages
7383 Args: state -- pointer pine's state variables
7384 msgmap -- message collection to operate on
7385 q_line -- line on display to write prompts
7386 Returns: 1 if the selected messages are suitably commanded,
7387 0 if the choice to pick the command was declined
7389 ----*/
7391 apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
7392 UCS preloadkeystroke, int flags, int q_line)
7394 int i = 8, /* number of static entries in sel_opts3 */
7395 rv = 0,
7396 cmd,
7397 we_cancel = 0,
7398 agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG;
7399 char prompt[80];
7400 PAT_STATE pstate;
7403 * To do this "right", we really ought to have access to the keymenu
7404 * here and change the typed command into a real command by running
7405 * it through menu_command. Then the switch below would be against
7406 * results from menu_command. If we did that we'd also pass the
7407 * results of menu_command in as preloadkeystroke instead of passing
7408 * the keystroke itself. But we don't have the keymenu handy,
7409 * so we have to fake it. The only complication that we run into
7410 * is that KEY_DEL is an escape sequence so we change a typed
7411 * KEY_DEL esc seq into the letter D.
7414 if(!preloadkeystroke){
7415 if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
7416 sel_opts3[i].ch = '*';
7417 sel_opts3[i].rval = '*';
7418 sel_opts3[i].name = "*";
7419 sel_opts3[i++].label = N_("Flag");
7422 if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
7423 sel_opts3[i].ch = '|';
7424 sel_opts3[i].rval = '|';
7425 sel_opts3[i].name = "|";
7426 sel_opts3[i++].label = N_("Pipe");
7429 if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */
7430 sel_opts3[i].ch = 'b';
7431 sel_opts3[i].rval = 'b';
7432 sel_opts3[i].name = "B";
7433 sel_opts3[i++].label = N_("Bounce");
7436 if(flags & AC_FROM_THREAD){
7437 if(flags & (AC_COLL | AC_EXPN)){
7438 sel_opts3[i].ch = '/';
7439 sel_opts3[i].rval = '/';
7440 sel_opts3[i].name = "/";
7441 sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse")
7442 : N_("Expand");
7445 sel_opts3[i].ch = ';';
7446 sel_opts3[i].rval = ';';
7447 sel_opts3[i].name = ";";
7448 if(flags & AC_UNSEL)
7449 sel_opts3[i++].label = N_("UnSelect");
7450 else
7451 sel_opts3[i++].label = N_("Select");
7454 if(F_ON(F_ENABLE_PRYNT, state)){ /* this one is invisible */
7455 sel_opts3[i].ch = 'y';
7456 sel_opts3[i].rval = '%';
7457 sel_opts3[i].name = "";
7458 sel_opts3[i++].label = "";
7461 if(!is_imap_stream(stream) || LEVELUIDPLUS(stream)){ /* expunge selected messages */
7462 sel_opts3[i].ch = 'x';
7463 sel_opts3[i].rval = 'x';
7464 sel_opts3[i].name = "X";
7465 sel_opts3[i++].label = N_("Expunge");
7468 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7469 sel_opts3[i].ch = '#';
7470 sel_opts3[i].rval = '#';
7471 sel_opts3[i].name = "#";
7472 sel_opts3[i++].label = N_("Set Role");
7475 sel_opts3[i].ch = KEY_DEL; /* also invisible */
7476 sel_opts3[i].rval = 'd';
7477 sel_opts3[i].name = "";
7478 sel_opts3[i++].label = "";
7480 sel_opts3[i].ch = -1;
7482 snprintf(prompt, sizeof(prompt), "%s command : ",
7483 (flags & AC_FROM_THREAD) ? "THREAD" : "APPLY");
7484 prompt[sizeof(prompt)-1] = '\0';
7485 cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'c', NO_HELP,
7486 RB_SEQ_SENSITIVE);
7487 if(isupper(cmd))
7488 cmd = tolower(cmd);
7490 else{
7491 if(preloadkeystroke == KEY_DEL)
7492 cmd = 'd';
7493 else{
7494 if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke))
7495 cmd = tolower((int) preloadkeystroke);
7496 else
7497 cmd = (int) preloadkeystroke; /* shouldn't happen */
7501 switch(cmd){
7502 case 'd' : /* delete */
7503 we_cancel = busy_cue(NULL, NULL, 1);
7504 rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */
7505 if(we_cancel)
7506 cancel_busy_cue(0);
7507 break;
7509 case 'u' : /* undelete */
7510 we_cancel = busy_cue(NULL, NULL, 1);
7511 rv = cmd_undelete(state, msgmap, agg);
7512 if(we_cancel)
7513 cancel_busy_cue(0);
7514 break;
7516 case 'r' : /* reply */
7517 rv = cmd_reply(state, msgmap, agg, NULL);
7518 break;
7520 case 'f' : /* Forward */
7521 rv = cmd_forward(state, msgmap, agg, NULL);
7522 break;
7524 case '%' : /* print */
7525 rv = cmd_print(state, msgmap, agg, MsgIndx);
7526 break;
7528 case 't' : /* take address */
7529 rv = cmd_take_addr(state, msgmap, agg);
7530 break;
7532 case 's' : /* save */
7533 rv = cmd_save(state, stream, msgmap, agg, MsgIndx);
7534 break;
7536 case 'e' : /* export */
7537 rv = cmd_export(state, msgmap, q_line, agg);
7538 break;
7540 case '|' : /* pipe */
7541 rv = cmd_pipe(state, msgmap, agg);
7542 break;
7544 case '*' : /* flag */
7545 we_cancel = busy_cue(NULL, NULL, 1);
7546 rv = cmd_flag(state, msgmap, agg);
7547 if(we_cancel)
7548 cancel_busy_cue(0);
7549 break;
7551 case 'b' : /* bounce */
7552 rv = cmd_bounce(state, msgmap, agg, NULL);
7553 break;
7555 case '/' :
7556 collapse_or_expand(state, stream, msgmap,
7557 F_ON(F_SLASH_COLL_ENTIRE, ps_global)
7558 ? 0L
7559 : mn_get_cur(msgmap));
7560 break;
7562 case ':' :
7563 select_thread_stmp(state, stream, msgmap);
7564 break;
7566 case 'x' : /* Expunge */
7567 rv = cmd_expunge(state, stream, msgmap, agg);
7568 break;
7570 case 'c' : /* cancel */
7571 cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command"
7572 : "Apply command");
7573 break;
7575 case '#' :
7576 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7577 static ESCKEY_S choose_role[] = {
7578 {'r', 'r', "R", N_("Reply")},
7579 {'f', 'f', "F", N_("Forward")},
7580 {'b', 'b', "B", N_("Bounce")},
7581 {-1, 0, NULL, NULL}
7583 int action;
7584 ACTION_S *role = NULL;
7586 action = radio_buttons(_("Reply, Forward or Bounce using a role? "),
7587 -FOOTER_ROWS(state), choose_role,
7588 'r', 'x', h_role_aggregate, RB_NORM);
7589 if(action == 'r' || action == 'f' || action == 'b'){
7590 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
7592 redraw = state->redrawer;
7593 state->redrawer = NULL;
7594 prev_screen = state->prev_screen;
7595 role = NULL;
7596 state->next_screen = SCREEN_FUN_NULL;
7598 if(role_select_screen(state, &role,
7599 action == 'f' ? MC_FORWARD :
7600 action == 'r' ? MC_REPLY :
7601 action == 'b' ? MC_BOUNCE : 0) < 0){
7602 cmd_cancelled(action == 'f' ? _("Forward") :
7603 action == 'r' ? _("Reply") : _("Bounce"));
7604 state->next_screen = prev_screen;
7605 state->redrawer = redraw;
7606 state->mangled_screen = 1;
7608 else{
7609 if(role)
7610 role = combine_inherited_role(role);
7611 else{
7612 role = (ACTION_S *) fs_get(sizeof(*role));
7613 memset((void *) role, 0, sizeof(*role));
7614 role->nick = cpystr("Default Role");
7617 state->redrawer = NULL;
7618 switch(action){
7619 case 'r':
7620 (void) cmd_reply(state, msgmap, agg, role);
7621 break;
7623 case 'f':
7624 (void) cmd_forward(state, msgmap, agg, role);
7625 break;
7627 case 'b':
7628 (void) cmd_bounce(state, msgmap, agg, role);
7629 break;
7632 if(role)
7633 free_action(&role);
7635 if(redraw)
7636 (*redraw)();
7638 state->next_screen = prev_screen;
7639 state->redrawer = redraw;
7640 state->mangled_screen = 1;
7644 break;
7646 case 'z' : /* default */
7647 q_status_message(SM_INFO, 0, 2,
7648 "Cancelled, there is no default command");
7649 break;
7651 default:
7652 break;
7655 return(rv);
7660 * Select by message number ranges.
7661 * Sets searched bits in mail_elts
7663 * Args limitsrch -- limit search to this searchset
7665 * Returns 0 on success.
7668 select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
7670 int r, end, cur;
7671 long n1, n2, raw;
7672 char number1[16], number2[16], numbers[80], *p, *t;
7673 HelpType help;
7674 MESSAGECACHE *mc;
7676 numbers[0] = '\0';
7677 ps_global->mangled_footer = 1;
7678 help = NO_HELP;
7679 while(1){
7680 int flags = OE_APPEND_CURRENT;
7682 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7683 sizeof(numbers), _(select_num), NULL, help, &flags);
7684 if(r == 4)
7685 continue;
7687 if(r == 3){
7688 help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
7689 continue;
7692 for(t = p = numbers; *p ; p++) /* strip whitespace */
7693 if(!isspace((unsigned char)*p))
7694 *t++ = *p;
7696 *t = '\0';
7698 if(r == 1 || numbers[0] == '\0'){
7699 cmd_cancelled("Selection by number");
7700 return(1);
7702 else
7703 break;
7706 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7707 if((mc = mail_elt(stream, n1)) != NULL)
7708 mc->searched = 0; /* clear searched bits */
7710 for(p = numbers; *p ; p++){
7711 t = number1;
7712 while(*p && isdigit((unsigned char)*p))
7713 *t++ = *p++;
7715 *t = '\0';
7717 end = cur = 0;
7718 if(number1[0] == '\0'){
7719 if(*p == '-'){
7720 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7721 _("Invalid number range, missing number before \"-\": %s"),
7722 numbers);
7723 return(1);
7725 else if(!strucmp("end", p)){
7726 end = 1;
7727 p += strlen("end");
7729 else if(!strucmp("$", p)){
7730 end = 1;
7731 p++;
7733 else if(*p == '.'){
7734 cur = 1;
7735 p++;
7737 else{
7738 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7739 _("Invalid message number: %s"), numbers);
7740 return(1);
7744 if(end)
7745 n1 = mn_get_total(msgmap);
7746 else if(cur)
7747 n1 = mn_get_cur(msgmap);
7748 else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
7749 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7750 _("\"%s\" out of message number range"),
7751 long2string(n1));
7752 return(1);
7755 t = number2;
7756 if(*p == '-'){
7757 while(*++p && isdigit((unsigned char)*p))
7758 *t++ = *p;
7760 *t = '\0';
7762 end = cur = 0;
7763 if(number2[0] == '\0'){
7764 if(!strucmp("end", p)){
7765 end = 1;
7766 p += strlen("end");
7768 else if(!strucmp(p, "$")){
7769 end = 1;
7770 p++;
7772 else if(*p == '.'){
7773 cur = 1;
7774 p++;
7776 else{
7777 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7778 _("Invalid number range, missing number after \"-\": %s"),
7779 numbers);
7780 return(1);
7784 if(end)
7785 n2 = mn_get_total(msgmap);
7786 else if(cur)
7787 n2 = mn_get_cur(msgmap);
7788 else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){
7789 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7790 _("\"%s\" out of message number range"),
7791 long2string(n2));
7792 return(1);
7795 if(n2 <= n1){
7796 char t[20];
7798 strncpy(t, long2string(n1), sizeof(t));
7799 t[sizeof(t)-1] = '\0';
7800 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7801 _("Invalid reverse message number range: %s-%s"),
7802 t, long2string(n2));
7803 return(1);
7806 for(;n1 <= n2; n1++){
7807 raw = mn_m2raw(msgmap, n1);
7808 if(raw > 0L
7809 && (!(limitsrch && *limitsrch)
7810 || in_searchset(*limitsrch, (unsigned long) raw)))
7811 mm_searched(stream, raw);
7814 else{
7815 raw = mn_m2raw(msgmap, n1);
7816 if(raw > 0L
7817 && (!(limitsrch && *limitsrch)
7818 || in_searchset(*limitsrch, (unsigned long) raw)))
7819 mm_searched(stream, raw);
7822 if(*p == '\0')
7823 break;
7826 return(0);
7831 * Select by thread number ranges.
7832 * Sets searched bits in mail_elts
7834 * Args limitsrch -- limit search to this searchset
7836 * Returns 0 on success.
7839 select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset)
7841 int r, end, cur;
7842 long n1, n2;
7843 char number1[16], number2[16], numbers[80], *p, *t;
7844 HelpType help;
7845 PINETHRD_S *thrd = NULL, *th;
7846 MESSAGECACHE *mc;
7848 numbers[0] = '\0';
7849 ps_global->mangled_footer = 1;
7850 help = NO_HELP;
7851 while(1){
7852 int flags = OE_APPEND_CURRENT;
7854 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7855 sizeof(numbers), _(select_num), NULL, help, &flags);
7856 if(r == 4)
7857 continue;
7859 if(r == 3){
7860 help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP;
7861 continue;
7864 for(t = p = numbers; *p ; p++) /* strip whitespace */
7865 if(!isspace((unsigned char)*p))
7866 *t++ = *p;
7868 *t = '\0';
7870 if(r == 1 || numbers[0] == '\0'){
7871 cmd_cancelled("Selection by number");
7872 return(1);
7874 else
7875 break;
7878 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7879 if((mc = mail_elt(stream, n1)) != NULL)
7880 mc->searched = 0; /* clear searched bits */
7882 for(p = numbers; *p ; p++){
7883 t = number1;
7884 while(*p && isdigit((unsigned char)*p))
7885 *t++ = *p++;
7887 *t = '\0';
7889 end = cur = 0;
7890 if(number1[0] == '\0'){
7891 if(*p == '-'){
7892 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7893 _("Invalid number range, missing number before \"-\": %s"),
7894 numbers);
7895 return(1);
7897 else if(!strucmp("end", p)){
7898 end = 1;
7899 p += strlen("end");
7901 else if(!strucmp(p, "$")){
7902 end = 1;
7903 p++;
7905 else if(*p == '.'){
7906 cur = 1;
7907 p++;
7909 else{
7910 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7911 _("Invalid thread number: %s"), numbers);
7912 return(1);
7916 if(end)
7917 n1 = msgmap->max_thrdno;
7918 else if(cur){
7919 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7920 n1 = th->thrdno;
7922 else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){
7923 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7924 _("\"%s\" out of thread number range"),
7925 long2string(n1));
7926 return(1);
7929 t = number2;
7930 if(*p == '-'){
7932 while(*++p && isdigit((unsigned char)*p))
7933 *t++ = *p;
7935 *t = '\0';
7937 end = 0;
7938 if(number2[0] == '\0'){
7939 if(!strucmp("end", p)){
7940 end = 1;
7941 p += strlen("end");
7943 else if(!strucmp("$", p)){
7944 end = 1;
7945 p++;
7947 else if(*p == '.'){
7948 cur = 1;
7949 p++;
7951 else{
7952 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7953 _("Invalid number range, missing number after \"-\": %s"),
7954 numbers);
7955 return(1);
7959 if(end)
7960 n2 = msgmap->max_thrdno;
7961 else if(cur){
7962 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7963 n2 = th->thrdno;
7965 else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){
7966 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7967 _("\"%s\" out of thread number range"),
7968 long2string(n2));
7969 return(1);
7972 if(n2 <= n1){
7973 char t[20];
7975 strncpy(t, long2string(n1), sizeof(t));
7976 t[sizeof(t)-1] = '\0';
7977 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7978 _("Invalid reverse message number range: %s-%s"),
7979 t, long2string(n2));
7980 return(1);
7983 for(;n1 <= n2; n1++){
7984 thrd = find_thread_by_number(stream, msgmap, n1, thrd);
7986 if(thrd)
7987 set_search_bit_for_thread(stream, thrd, msgset);
7990 else{
7991 thrd = find_thread_by_number(stream, msgmap, n1, NULL);
7993 if(thrd)
7994 set_search_bit_for_thread(stream, thrd, msgset);
7997 if(*p == '\0')
7998 break;
8001 return(0);
8006 * Select by message dates.
8007 * Sets searched bits in mail_elts
8009 * Args limitsrch -- limit search to this searchset
8011 * Returns 0 on success.
8014 select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8016 int r, we_cancel = 0, when = 0;
8017 char date[100], defdate[100], prompt[128];
8018 time_t seldate = time(0);
8019 struct tm *seldate_tm;
8020 SEARCHPGM *pgm;
8021 HelpType help;
8022 static struct _tense {
8023 char *preamble,
8024 *range,
8025 *scope;
8026 } tense[] = {
8027 {"were ", "SENT SINCE", " (inclusive)"},
8028 {"were ", "SENT BEFORE", " (exclusive)"},
8029 {"were ", "SENT ON", "" },
8030 {"", "ARRIVED SINCE", " (inclusive)"},
8031 {"", "ARRIVED BEFORE", " (exclusive)"},
8032 {"", "ARRIVED ON", "" }
8035 date[0] = '\0';
8036 ps_global->mangled_footer = 1;
8037 help = NO_HELP;
8040 * If talking to an old server, default to SINCE instead of
8041 * SENTSINCE, which was added later.
8043 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8044 when = 3;
8046 while(1){
8047 int flags = OE_APPEND_CURRENT;
8049 seldate_tm = localtime(&seldate);
8050 snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday,
8051 month_abbrev(seldate_tm->tm_mon + 1),
8052 seldate_tm->tm_year + 1900);
8053 defdate[sizeof(defdate)-1] = '\0';
8054 snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ",
8055 tense[when].preamble, tense[when].range,
8056 tense[when].scope, defdate);
8057 prompt[sizeof(prompt)-1] = '\0';
8058 r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date),
8059 prompt, sel_date_opt, help, &flags);
8060 switch (r){
8061 case 1 :
8062 cmd_cancelled("Selection by date");
8063 return(1);
8065 case 3 :
8066 help = (help == NO_HELP) ? h_select_date : NO_HELP;
8067 continue;
8069 case 4 :
8070 continue;
8072 case 11 :
8074 MESSAGECACHE *mc;
8075 long rawno;
8077 if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L
8078 && rawno <= stream->nmsgs
8079 && (mc = mail_elt(stream, rawno))){
8081 /* cache not filled in yet? */
8082 if(mc->day == 0){
8083 char seq[20];
8085 if(stream->dtb && stream->dtb->flags & DR_NEWS){
8086 strncpy(seq,
8087 ulong2string(mail_uid(stream, rawno)),
8088 sizeof(seq));
8089 seq[sizeof(seq)-1] = '\0';
8090 mail_fetch_overview(stream, seq, NULL);
8092 else{
8093 strncpy(seq, long2string(rawno),
8094 sizeof(seq));
8095 seq[sizeof(seq)-1] = '\0';
8096 mail_fetch_fast(stream, seq, 0L);
8100 /* mail_date returns fixed field width date */
8101 mail_date(date, mc);
8102 date[11] = '\0';
8106 continue;
8108 case 12 : /* set default to PREVIOUS day */
8109 seldate -= 86400;
8110 continue;
8112 case 13 : /* set default to NEXT day */
8113 seldate += 86400;
8114 continue;
8116 case 14 :
8117 when = (when+1) % (sizeof(tense) / sizeof(struct _tense));
8118 continue;
8120 default:
8121 break;
8124 removing_leading_white_space(date);
8125 removing_trailing_white_space(date);
8126 if(!*date){
8127 strncpy(date, defdate, sizeof(date));
8128 date[sizeof(date)-1] = '\0';
8131 break;
8134 if((pgm = mail_newsearchpgm()) != NULL){
8135 MESSAGECACHE elt;
8136 short converted_date;
8138 if(mail_parse_date(&elt, (unsigned char *) date)){
8139 converted_date = mail_shortdate(elt.year, elt.month, elt.day);
8141 switch(when){
8142 case 0:
8143 pgm->sentsince = converted_date;
8144 break;
8145 case 1:
8146 pgm->sentbefore = converted_date;
8147 break;
8148 case 2:
8149 pgm->senton = converted_date;
8150 break;
8151 case 3:
8152 pgm->since = converted_date;
8153 break;
8154 case 4:
8155 pgm->before = converted_date;
8156 break;
8157 case 5:
8158 pgm->on = converted_date;
8159 break;
8162 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8164 if(ps_global && ps_global->ttyo){
8165 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8166 ps_global->mangled_footer = 1;
8169 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8171 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8173 if(we_cancel)
8174 cancel_busy_cue(0);
8176 /* we know this was freed in mail_search, let caller know */
8177 if(limitsrch)
8178 *limitsrch = NULL;
8180 else{
8181 mail_free_searchpgm(&pgm);
8182 q_status_message1(SM_ORDER, 3, 3,
8183 _("Invalid date entered: %s"), date);
8184 return(1);
8188 return(0);
8192 * Select by searching in message headers or body
8193 * using the x-gm-ext-1 capaility. This function
8194 * reads the input from the user and passes it to
8195 * the server directly. We need a x-gm-ext-1 variable
8196 * in the search pgm to carry this information to the
8197 * server.
8199 * Sets searched bits in mail_elts
8201 * Args limitsrch -- limit search to this searchset
8203 * Returns 0 on success.
8206 select_by_gm_content(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8208 int r, we_cancel = 0, rv;
8209 char tmp[128];
8210 char namehdr[MAILTMPLEN];
8211 ESCKEY_S ekey[8];
8212 HelpType help;
8214 ps_global->mangled_footer = 1;
8215 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8217 strncpy(tmp, sel_x_gm_ext, sizeof(tmp)-1);
8218 tmp[sizeof(tmp)-1] = '\0';
8220 namehdr[0] = '\0';
8222 help = NO_HELP;
8223 while (1){
8224 int flags = OE_APPEND_CURRENT;
8226 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8227 sizeof(namehdr), tmp, ekey, help, &flags);
8229 if(r == 4)
8230 continue;
8232 if(r == 3){
8233 help = (help == NO_HELP) ? h_select_by_gm_content : NO_HELP;
8234 continue;
8237 if (r == 1){
8238 cmd_cancelled("Selection by content");
8239 return(1);
8242 removing_leading_white_space(namehdr);
8244 if ((namehdr[0] != '\0')
8245 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8246 removing_trailing_white_space(namehdr);
8248 if (namehdr[0] != '\0')
8249 break;
8252 if(ps_global && ps_global->ttyo){
8253 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8254 ps_global->mangled_footer = 1;
8257 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8259 rv = agg_text_select(stream, msgmap, 'g', namehdr, 0, 0, NULL, "utf-8", limitsrch);
8260 if(we_cancel)
8261 cancel_busy_cue(0);
8263 return(rv);
8267 * Select by searching in message headers or body.
8268 * Sets searched bits in mail_elts
8270 * Args limitsrch -- limit search to this searchset
8272 * Returns 0 on success.
8275 select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8277 int r = '\0', ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
8278 int not = 0, me = 0;
8279 char sstring[512], tmp[128];
8280 char *p, *sval = NULL;
8281 char namehdr[80];
8282 ESCKEY_S ekey[8];
8283 ENVELOPE *env = NULL;
8284 HelpType help;
8285 unsigned flagsforhist = 0;
8286 static HISTORY_S *history = NULL;
8287 static char *recip = "RECIPIENTS";
8288 static char *partic = "PARTICIPANTS";
8289 static char *match_me = N_("[Match_My_Addresses]");
8290 static char *dont_match_me = N_("[Don't_Match_My_Addresses]");
8292 ps_global->mangled_footer = 1;
8293 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8295 while(1){
8296 type = radio_buttons(not ? _(sel_text_not) : _(sel_text),
8297 -FOOTER_ROWS(ps_global), sel_text_opt,
8298 's', 'x', NO_HELP, RB_NORM|RB_RET_HELP);
8300 if(type == '!')
8301 not = !not;
8302 else if(type == 3){
8303 helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS",
8304 HLPD_SIMPLE);
8305 ps_global->mangled_screen = 1;
8307 else
8308 break;
8312 * prepare some friendly defaults...
8314 switch(type){
8315 case 't' : /* address fields, offer To or From */
8316 case 'f' :
8317 case 'c' :
8318 case 'r' :
8319 case 'p' :
8320 sval = (type == 't') ? "TO" :
8321 (type == 'f') ? "FROM" :
8322 (type == 'c') ? "CC" :
8323 (type == 'r') ? recip : partic;
8324 ekey[ekeyi].ch = ctrl('T');
8325 ekey[ekeyi].name = "^T";
8326 ekey[ekeyi].rval = 10;
8327 /* TRANSLATORS: use Current To Address */
8328 ekey[ekeyi++].label = N_("Cur To");
8329 ekey[ekeyi].ch = ctrl('R');
8330 ekey[ekeyi].name = "^R";
8331 ekey[ekeyi].rval = 11;
8332 /* TRANSLATORS: use Current From Address */
8333 ekey[ekeyi++].label = N_("Cur From");
8334 ekey[ekeyi].ch = ctrl('W');
8335 ekey[ekeyi].name = "^W";
8336 ekey[ekeyi].rval = 12;
8337 /* TRANSLATORS: use Current Cc Address */
8338 ekey[ekeyi++].label = N_("Cur Cc");
8339 ekey[ekeyi].ch = ctrl('Y');
8340 ekey[ekeyi].name = "^Y";
8341 ekey[ekeyi].rval = 13;
8342 /* TRANSLATORS: Match Me means match my address */
8343 ekey[ekeyi++].label = N_("Match Me");
8344 ekey[ekeyi].ch = 0;
8345 ekey[ekeyi].name = "";
8346 ekey[ekeyi].rval = 0;
8347 ekey[ekeyi++].label = "";
8348 break;
8350 case 's' :
8351 sval = "SUBJECT";
8352 ekey[ekeyi].ch = ctrl('X');
8353 ekey[ekeyi].name = "^X";
8354 ekey[ekeyi].rval = 14;
8355 /* TRANSLATORS: use Current Subject */
8356 ekey[ekeyi++].label = N_("Cur Subject");
8357 break;
8359 case 'a' :
8360 sval = "TEXT";
8361 break;
8363 case 'b' :
8364 sval = "BODYTEXT";
8365 break;
8367 case 'h' :
8368 strncpy(tmp, "Name of HEADER to match : ", sizeof(tmp)-1);
8369 tmp[sizeof(tmp)-1] = '\0';
8370 flags = OE_APPEND_CURRENT;
8371 namehdr[0] = '\0';
8372 r = 'x';
8373 while (r == 'x'){
8374 int done = 0;
8376 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8377 sizeof(namehdr), tmp, ekey, NO_HELP, &flags);
8378 if (r == 1){
8379 cmd_cancelled("Selection by text");
8380 return(1);
8382 removing_leading_white_space(namehdr);
8383 while(!done){
8384 while ((namehdr[0] != '\0') && /* remove trailing ":" */
8385 (namehdr[strlen(namehdr) - 1] == ':'))
8386 namehdr[strlen(namehdr) - 1] = '\0';
8387 if ((namehdr[0] != '\0')
8388 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8389 removing_trailing_white_space(namehdr);
8390 else
8391 done++;
8393 if (strchr(namehdr,' ') || strchr(namehdr,'\t') ||
8394 strchr(namehdr,':'))
8395 namehdr[0] = '\0';
8396 if (namehdr[0] == '\0')
8397 r = 'x';
8399 sval = namehdr;
8400 break;
8402 case 'x':
8403 break;
8405 default:
8406 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
8407 return(1);
8410 ekey[ekeyi].ch = KEY_UP;
8411 ekey[ekeyi].rval = 30;
8412 ekey[ekeyi].name = "";
8413 ku = ekeyi;
8414 ekey[ekeyi++].label = "";
8416 ekey[ekeyi].ch = KEY_DOWN;
8417 ekey[ekeyi].rval = 31;
8418 ekey[ekeyi].name = "";
8419 ekey[ekeyi++].label = "";
8421 ekey[ekeyi].ch = -1;
8423 if(type != 'x'){
8425 init_hist(&history, HISTSIZE);
8427 if(ekey[0].ch > -1 && msgno > 0L
8428 && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),
8429 NULL)))
8430 ekey[0].ch = -1;
8432 sstring[0] = '\0';
8433 help = NO_HELP;
8434 r = type;
8435 while(r != 'x'){
8436 if(not)
8437 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8438 snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval);
8439 else
8440 snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval);
8442 if(items_in_hist(history) > 0){
8443 ekey[ku].name = HISTORY_UP_KEYNAME;
8444 ekey[ku].label = HISTORY_KEYLABEL;
8445 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
8446 ekey[ku+1].label = HISTORY_KEYLABEL;
8448 else{
8449 ekey[ku].name = "";
8450 ekey[ku].label = "";
8451 ekey[ku+1].name = "";
8452 ekey[ku+1].label = "";
8455 flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
8456 r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
8457 sizeof(sstring), tmp, ekey, help, &flags);
8459 if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me)))))
8460 me = 0;
8462 switch(r){
8463 case 3 :
8464 help = (help == NO_HELP)
8465 ? (not
8466 ? ((type == 'f') ? h_select_txt_not_from
8467 : (type == 't') ? h_select_txt_not_to
8468 : (type == 'c') ? h_select_txt_not_cc
8469 : (type == 's') ? h_select_txt_not_subj
8470 : (type == 'a') ? h_select_txt_not_all
8471 : (type == 'r') ? h_select_txt_not_recip
8472 : (type == 'p') ? h_select_txt_not_partic
8473 : (type == 'b') ? h_select_txt_not_body
8474 : NO_HELP)
8475 : ((type == 'f') ? h_select_txt_from
8476 : (type == 't') ? h_select_txt_to
8477 : (type == 'c') ? h_select_txt_cc
8478 : (type == 's') ? h_select_txt_subj
8479 : (type == 'a') ? h_select_txt_all
8480 : (type == 'r') ? h_select_txt_recip
8481 : (type == 'p') ? h_select_txt_partic
8482 : (type == 'b') ? h_select_txt_body
8483 : NO_HELP))
8484 : NO_HELP;
8486 case 4 :
8487 continue;
8489 case 10 : /* To: default */
8490 if(env && env->to && env->to->mailbox){
8491 snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox,
8492 env->to->host ? "@" : "",
8493 env->to->host ? env->to->host : "");
8494 sstring[sizeof(sstring)-1] = '\0';
8496 continue;
8498 case 11 : /* From: default */
8499 if(env && env->from && env->from->mailbox){
8500 snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox,
8501 env->from->host ? "@" : "",
8502 env->from->host ? env->from->host : "");
8503 sstring[sizeof(sstring)-1] = '\0';
8505 continue;
8507 case 12 : /* Cc: default */
8508 if(env && env->cc && env->cc->mailbox){
8509 snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox,
8510 env->cc->host ? "@" : "",
8511 env->cc->host ? env->cc->host : "");
8512 sstring[sizeof(sstring)-1] = '\0';
8514 continue;
8516 case 13 : /* Match my addresses */
8517 me++;
8518 snprintf(sstring, sizeof(sstring), "%s", not ? _(dont_match_me) : _(match_me));
8519 continue;
8521 case 14 : /* Subject: default */
8522 if(env && env->subject && env->subject[0]){
8523 char *q = NULL;
8525 q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
8526 SIZEOF_20KBUF, env->subject);
8527 snprintf(sstring, sizeof(sstring), "%s", q);
8528 sstring[sizeof(sstring)-1] = '\0';
8531 continue;
8533 case 30 :
8534 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8535 if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){
8536 strncpy(sstring, p, sizeof(sstring));
8537 sstring[sizeof(sstring)-1] = '\0';
8538 if(history->hist[history->curindex]){
8539 flagsforhist = history->hist[history->curindex]->flags;
8540 not = (flagsforhist & 0x1) ? 1 : 0;
8541 me = (flagsforhist & 0x2) ? 1 : 0;
8544 else
8545 Writechar(BELL, 0);
8547 continue;
8549 case 31 :
8550 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8551 if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){
8552 strncpy(sstring, p, sizeof(sstring));
8553 sstring[sizeof(sstring)-1] = '\0';
8554 if(history->hist[history->curindex]){
8555 flagsforhist = history->hist[history->curindex]->flags;
8556 not = (flagsforhist & 0x1) ? 1 : 0;
8557 me = (flagsforhist & 0x2) ? 1 : 0;
8560 else
8561 Writechar(BELL, 0);
8563 continue;
8565 default :
8566 break;
8569 if(r == 1 || sstring[0] == '\0')
8570 r = 'x';
8572 break;
8576 if(type == 'x' || r == 'x'){
8577 cmd_cancelled("Selection by text");
8578 return(1);
8581 if(ps_global && ps_global->ttyo){
8582 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8583 ps_global->mangled_footer = 1;
8586 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8588 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8589 save_hist(history, sstring, flagsforhist, NULL);
8591 rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch);
8592 if(we_cancel)
8593 cancel_busy_cue(0);
8595 return(rv);
8600 * Select by message size.
8601 * Sets searched bits in mail_elts
8603 * Args limitsrch -- limit search to this searchset
8605 * Returns 0 on success.
8608 select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch)
8610 int r, large = 1, we_cancel = 0;
8611 unsigned long n, mult = 1L, numerator = 0L, divisor = 1L;
8612 char size[16], numbers[80], *p, *t;
8613 HelpType help;
8614 SEARCHPGM *pgm;
8615 long flags = (SE_NOPREFETCH | SE_FREE);
8617 numbers[0] = '\0';
8618 ps_global->mangled_footer = 1;
8620 help = NO_HELP;
8621 while(1){
8622 int flgs = OE_APPEND_CURRENT;
8624 sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger;
8626 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
8627 sizeof(numbers), large ? _(select_size_larger_msg)
8628 : _(select_size_smaller_msg),
8629 sel_size_opt, help, &flgs);
8630 if(r == 4)
8631 continue;
8633 if(r == 14){
8634 large = 1 - large;
8635 continue;
8638 if(r == 3){
8639 help = (help == NO_HELP) ? (large ? h_select_by_larger_size
8640 : h_select_by_smaller_size)
8641 : NO_HELP;
8642 continue;
8645 for(t = p = numbers; *p ; p++) /* strip whitespace */
8646 if(!isspace((unsigned char)*p))
8647 *t++ = *p;
8649 *t = '\0';
8651 if(r == 1 || numbers[0] == '\0'){
8652 cmd_cancelled("Selection by size");
8653 return(1);
8655 else
8656 break;
8659 if(numbers[0] == '-'){
8660 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8661 _("Invalid size entered: %s"), numbers);
8662 return(1);
8665 t = size;
8666 p = numbers;
8668 while(*p && isdigit((unsigned char)*p))
8669 *t++ = *p++;
8671 *t = '\0';
8673 if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){
8674 size[0] = '0';
8675 size[1] = '\0';
8678 if(size[0] == '\0'){
8679 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8680 _("Invalid size entered: %s"), numbers);
8681 return(1);
8684 n = strtoul(size, (char **)NULL, 10);
8686 size[0] = '\0';
8687 if(*p == '.'){
8689 * We probably ought to just use atof() to convert 1.1 into a
8690 * double, but since we haven't used atof() anywhere else I'm
8691 * reluctant to use it because of portability concerns.
8693 p++;
8694 t = size;
8695 while(*p && isdigit((unsigned char)*p)){
8696 *t++ = *p++;
8697 divisor *= 10;
8700 *t = '\0';
8702 if(size[0])
8703 numerator = strtoul(size, (char **)NULL, 10);
8706 switch(*p){
8707 case 'g':
8708 case 'G':
8709 mult *= 1000;
8710 /* fall through */
8712 case 'm':
8713 case 'M':
8714 mult *= 1000;
8715 /* fall through */
8717 case 'k':
8718 case 'K':
8719 mult *= 1000;
8720 break;
8723 n = n * mult + (numerator * mult) / divisor;
8725 pgm = mail_newsearchpgm();
8726 if(large)
8727 pgm->larger = n;
8728 else
8729 pgm->smaller = n;
8731 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8732 flags |= SE_NOSERVER;
8734 if(ps_global && ps_global->ttyo){
8735 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8736 ps_global->mangled_footer = 1;
8739 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8741 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8742 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8743 /* we know this was freed in mail_search, let caller know */
8744 if(limitsrch)
8745 *limitsrch = NULL;
8747 if(we_cancel)
8748 cancel_busy_cue(0);
8750 return(0);
8755 * visible_searchset -- return c-client search set unEXLDed
8756 * sequence numbers
8758 SEARCHSET *
8759 visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap)
8761 long n, run;
8762 SEARCHSET *full_set = NULL, **set;
8765 * If we're talking to anything other than a server older than
8766 * imap 4rev1, build a searchset otherwise it'll choke.
8768 if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
8769 if(any_lflagged(msgmap, MN_EXLD)){
8770 for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
8771 if(get_lflag(stream, NULL, n, MN_EXLD)){
8772 if(run){ /* previous NOT excluded? */
8773 if(run > 1L)
8774 (*set)->last = n - 1L;
8776 set = &(*set)->next;
8777 run = 0L;
8780 else if(run++){ /* next in run */
8781 (*set)->last = n;
8783 else{ /* start of run */
8784 *set = mail_newsearchset();
8785 (*set)->first = n;
8788 else{
8789 full_set = mail_newsearchset();
8790 full_set->first = 1L;
8791 full_set->last = stream->nmsgs;
8795 return(full_set);
8800 * Select by message status bits.
8801 * Sets searched bits in mail_elts
8803 * Args limitsrch -- limit search to this searchset
8805 * Returns 0 on success.
8808 select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch)
8810 int s, not = 0, we_cancel = 0, rv;
8812 while(1){
8813 s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag),
8814 -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x',
8815 NO_HELP, RB_NORM|RB_RET_HELP);
8817 if(s == 'x'){
8818 cmd_cancelled("Selection by status");
8819 return(1);
8821 else if(s == 3){
8822 helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"),
8823 HLPD_SIMPLE);
8824 ps_global->mangled_screen = 1;
8826 else if(s == '!')
8827 not = !not;
8828 else
8829 break;
8832 if(ps_global && ps_global->ttyo){
8833 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8834 ps_global->mangled_footer = 1;
8837 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8838 rv = agg_flag_select(stream, not, s, limitsrch);
8839 if(we_cancel)
8840 cancel_busy_cue(0);
8842 return(rv);
8847 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8848 * Sets searched bits in mail_elts
8850 * Args limitsrch -- limit search to this searchset
8852 * Returns 0 on success.
8855 select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
8857 char rulenick[1000], *nick;
8858 PATGRP_S *patgrp;
8859 int r, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
8860 | ROLE_DO_INCOLS
8861 | ROLE_DO_ROLES
8862 | ROLE_DO_SCORES
8863 | ROLE_DO_OTHER
8864 | ROLE_DO_FILTER;
8866 rulenick[0] = '\0';
8867 ps_global->mangled_footer = 1;
8870 int oe_flags;
8872 oe_flags = OE_APPEND_CURRENT;
8873 r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0,
8874 sizeof(rulenick),
8875 not ? _("Rule to NOT match: ")
8876 : _("Rule to match: "),
8877 sel_key_opt, NO_HELP, &oe_flags);
8879 if(r == 14){
8880 /* select rulenick from a list */
8881 if((nick=choose_a_rule(rflags)) != NULL){
8882 strncpy(rulenick, nick, sizeof(rulenick)-1);
8883 rulenick[sizeof(rulenick)-1] = '\0';
8884 fs_give((void **) &nick);
8886 else
8887 r = 4;
8889 else if(r == '!')
8890 not = !not;
8892 if(r == 3){
8893 helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE);
8894 ps_global->mangled_screen = 1;
8896 else if(r == 1){
8897 cmd_cancelled("Selection by Rule");
8898 return(1);
8901 removing_leading_and_trailing_white_space(rulenick);
8903 }while(r == 3 || r == 4 || r == '!');
8907 * The approach of requiring a nickname instead of just allowing the
8908 * user to select from the list of rules has the drawback that a rule
8909 * may not have a nickname, or there may be more than one rule with
8910 * the same nickname. However, it has the benefit of allowing the user
8911 * to type in the nickname and, most importantly, allows us to set
8912 * up the ! (not). We could incorporate the ! into the selection
8913 * screen, but this is easier and also allows the typing of nicks.
8914 * User can just set up nicknames if they want to use this feature.
8916 patgrp = nick_to_patgrp(rulenick, rflags);
8918 if(patgrp){
8919 if(ps_global && ps_global->ttyo){
8920 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8921 ps_global->mangled_footer = 1;
8924 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8925 match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL,
8926 get_msg_score,
8927 (not ? MP_NOT : 0) | SE_NOPREFETCH);
8928 free_patgrp(&patgrp);
8929 if(we_cancel)
8930 cancel_busy_cue(0);
8933 if(limitsrch && *limitsrch){
8934 mail_free_searchset(limitsrch);
8935 *limitsrch = NULL;
8938 return(0);
8943 * Allow user to choose a rule from their list of rules.
8945 * Returns an allocated rule nickname on success, NULL otherwise.
8947 char *
8948 choose_a_rule(int rflags)
8950 char *choice = NULL;
8951 char **rule_list, **lp;
8952 int cnt = 0;
8953 PAT_S *pat;
8954 PAT_STATE pstate;
8956 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){
8957 q_status_message(SM_ORDER, 3, 3,
8958 _("No rules available. Use Setup/Rules to add some."));
8959 return(choice);
8963 * Build a list of rules to choose from.
8966 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8967 cnt++;
8969 if(cnt <= 0){
8970 q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
8971 return(choice);
8974 lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
8975 memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
8977 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8978 *lp++ = cpystr((pat->patgrp && pat->patgrp->nick)
8979 ? pat->patgrp->nick : "?");
8981 /* TRANSLATORS: SELECT A RULE is a screen title
8982 TRANSLATORS: Print something1 using something2.
8983 "rules" is something1 */
8984 choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"),
8985 _("rules"), h_select_rule_screen,
8986 _("HELP FOR SELECTING A RULE NICKNAME"), NULL);
8988 if(!choice)
8989 q_status_message(SM_ORDER, 1, 4, "No choice");
8991 free_list_array(&rule_list);
8993 return(choice);
8998 * Select by current thread.
8999 * Sets searched bits in mail_elts for this entire thread
9001 * Args limitsrch -- limit search to this searchset
9003 * Returns 0 on success.
9006 select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
9008 long n;
9009 PINETHRD_S *thrd = NULL;
9010 int ret = 1;
9011 MESSAGECACHE *mc;
9013 if(!stream)
9014 return(ret);
9016 for(n = 1L; n <= stream->nmsgs; n++)
9017 if((mc = mail_elt(stream, n)) != NULL)
9018 mc->searched = 0; /* clear searched bits */
9020 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
9021 if(thrd && thrd->top && thrd->top != thrd->rawno)
9022 thrd = fetch_thread(stream, thrd->top);
9025 * This doesn't unselect if the thread is already selected
9026 * (like select current does), it always selects.
9027 * There is no way to select ! this thread.
9029 if(thrd){
9030 set_search_bit_for_thread(stream, thrd, limitsrch);
9031 ret = 0;
9034 return(ret);
9039 * Select by message keywords.
9040 * Sets searched bits in mail_elts
9042 * Args limitsrch -- limit search to this searchset
9044 * Returns 0 on success.
9047 select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
9049 int r, not = 0, we_cancel = 0;
9050 char keyword[MAXUSERFLAG+1], *kword;
9051 char *error = NULL, *p, *prompt;
9052 HelpType help;
9053 SEARCHPGM *pgm;
9055 keyword[0] = '\0';
9056 ps_global->mangled_footer = 1;
9058 help = NO_HELP;
9060 int oe_flags;
9062 if(error){
9063 q_status_message(SM_ORDER, 3, 4, error);
9064 fs_give((void **) &error);
9067 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
9068 if(not)
9069 prompt = _("Keyword (or keyword initial) to NOT match: ");
9070 else
9071 prompt = _("Keyword (or keyword initial) to match: ");
9073 else{
9074 if(not)
9075 prompt = _("Keyword to NOT match: ");
9076 else
9077 prompt = _("Keyword to match: ");
9080 oe_flags = OE_APPEND_CURRENT;
9081 r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0,
9082 sizeof(keyword),
9083 prompt, sel_key_opt, help, &oe_flags);
9085 if(r == 14){
9086 /* select keyword from a list */
9087 if((kword=choose_a_keyword()) != NULL){
9088 strncpy(keyword, kword, sizeof(keyword)-1);
9089 keyword[sizeof(keyword)-1] = '\0';
9090 fs_give((void **) &kword);
9092 else
9093 r = 4;
9095 else if(r == '!')
9096 not = !not;
9098 if(r == 3)
9099 help = help == NO_HELP ? h_select_keyword : NO_HELP;
9100 else if(r == 1){
9101 cmd_cancelled("Selection by keyword");
9102 return(1);
9105 removing_leading_and_trailing_white_space(keyword);
9107 }while(r == 3 || r == 4 || r == '!' || keyword_check(keyword, &error));
9110 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
9111 p = initial_to_keyword(keyword);
9112 if(p != keyword){
9113 strncpy(keyword, p, sizeof(keyword)-1);
9114 keyword[sizeof(keyword)-1] = '\0';
9119 * We want to check the keyword, not the nickname of the keyword,
9120 * so convert it to the keyword if necessary.
9122 p = nick_to_keyword(keyword);
9123 if(p != keyword){
9124 strncpy(keyword, p, sizeof(keyword)-1);
9125 keyword[sizeof(keyword)-1] = '\0';
9128 pgm = mail_newsearchpgm();
9129 if(not){
9130 pgm->unkeyword = mail_newstringlist();
9131 pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword);
9132 pgm->unkeyword->text.size = strlen(keyword);
9134 else{
9135 pgm->keyword = mail_newstringlist();
9136 pgm->keyword->text.data = (unsigned char *) cpystr(keyword);
9137 pgm->keyword->text.size = strlen(keyword);
9140 if(ps_global && ps_global->ttyo){
9141 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9142 ps_global->mangled_footer = 1;
9145 we_cancel = busy_cue(_("Selecting"), NULL, 1);
9147 pgm->msgno = (limitsrch ? *limitsrch : NULL);
9148 pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE);
9149 /* we know this was freed in mail_search, let caller know */
9150 if(limitsrch)
9151 *limitsrch = NULL;
9153 if(we_cancel)
9154 cancel_busy_cue(0);
9156 return(0);
9161 * Allow user to choose a keyword from their list of keywords.
9163 * Returns an allocated keyword on success, NULL otherwise.
9165 char *
9166 choose_a_keyword(void)
9168 char *choice = NULL;
9169 char **keyword_list, **lp;
9170 int cnt;
9171 KEYWORD_S *kw;
9174 * Build a list of keywords to choose from.
9177 for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next)
9178 cnt++;
9180 if(cnt <= 0){
9181 q_status_message(SM_ORDER, 3, 4,
9182 _("No keywords defined, use \"keywords\" option in Setup/Config"));
9183 return(choice);
9186 lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list));
9187 memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list));
9189 for(kw = ps_global->keywords; kw; kw = kw->next)
9190 *lp++ = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9192 /* TRANSLATORS: SELECT A KEYWORD is a screen title
9193 TRANSLATORS: Print something1 using something2.
9194 "keywords" is something1 */
9195 choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"),
9196 _("keywords"), h_select_keyword_screen,
9197 _("HELP FOR SELECTING A KEYWORD"), NULL);
9199 if(!choice)
9200 q_status_message(SM_ORDER, 1, 4, "No choice");
9202 free_list_array(&keyword_list);
9204 return(choice);
9209 * Allow user to choose a list of keywords from their list of keywords.
9211 * Returns allocated list.
9213 char **
9214 choose_list_of_keywords(void)
9216 LIST_SEL_S *listhead, *ls, *p;
9217 char **ret = NULL;
9218 int cnt, i;
9219 KEYWORD_S *kw;
9222 * Build a list of keywords to choose from.
9225 p = listhead = NULL;
9226 for(kw = ps_global->keywords; kw; kw = kw->next){
9228 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9229 memset(ls, 0, sizeof(*ls));
9230 ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9232 if(p){
9233 p->next = ls;
9234 p = p->next;
9236 else
9237 listhead = p = ls;
9240 if(!listhead)
9241 return(ret);
9243 /* TRANSLATORS: SELECT KEYWORDS is a screen title
9244 Print something1 using something2.
9245 "keywords" is something1 */
9246 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9247 _("SELECT KEYWORDS"), _("keywords"),
9248 h_select_multkeyword_screen,
9249 _("HELP FOR SELECTING KEYWORDS"), NULL)){
9250 for(cnt = 0, p = listhead; p; p = p->next)
9251 if(p->selected)
9252 cnt++;
9254 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9255 memset(ret, 0, (cnt+1) * sizeof(*ret));
9256 for(i = 0, p = listhead; p; p = p->next)
9257 if(p->selected)
9258 ret[i++] = cpystr(p->item ? p->item : "");
9261 free_list_sel(&listhead);
9263 return(ret);
9268 * Allow user to choose a charset
9270 * Returns an allocated charset on success, NULL otherwise.
9272 char *
9273 choose_a_charset(int which_charsets)
9275 char *choice = NULL;
9276 char **charset_list, **lp;
9277 const CHARSET *cs;
9278 int cnt;
9281 * Build a list of charsets to choose from.
9284 for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){
9285 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9286 && ((which_charsets & CAC_ALL)
9287 || (which_charsets & CAC_POSTING
9288 && cs->flags & CF_POSTING)
9289 || (which_charsets & CAC_DISPLAY
9290 && cs->type != CT_2022
9291 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9292 cnt++;
9295 if(cnt <= 0){
9296 q_status_message(SM_ORDER, 3, 4,
9297 _("No charsets found? Enter charset manually."));
9298 return(choice);
9301 lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list));
9302 memset(charset_list, 0, (cnt+1) * sizeof(*charset_list));
9304 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9305 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9306 && ((which_charsets & CAC_ALL)
9307 || (which_charsets & CAC_POSTING
9308 && cs->flags & CF_POSTING)
9309 || (which_charsets & CAC_DISPLAY
9310 && cs->type != CT_2022
9311 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9312 *lp++ = cpystr(cs->name);
9315 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
9316 TRANSLATORS: Print something1 using something2.
9317 "character sets" is something1 */
9318 choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"),
9319 _("character sets"), h_select_charset_screen,
9320 _("HELP FOR SELECTING A CHARACTER SET"), NULL);
9322 if(!choice)
9323 q_status_message(SM_ORDER, 1, 4, "No choice");
9325 free_list_array(&charset_list);
9327 return(choice);
9332 * Allow user to choose a list of character sets and/or scripts
9334 * Returns allocated list.
9336 char **
9337 choose_list_of_charsets(void)
9339 LIST_SEL_S *listhead, *ls, *p;
9340 char **ret = NULL;
9341 int cnt, i, got_one;
9342 const CHARSET *cs;
9343 SCRIPT *s;
9344 char *q, *t;
9345 long width, limit;
9346 char buf[1024], *folded;
9349 * Build a list of charsets to choose from.
9352 p = listhead = NULL;
9354 /* this width is determined by select_from_list_screen() */
9355 width = ps_global->ttyo->screen_cols - 4;
9357 /* first comes a list of scripts (sets of character sets) */
9358 for(s = utf8_script(NIL); s && s->name; s++){
9360 limit = sizeof(buf)-1;
9361 q = buf;
9362 memset(q, 0, limit+1);
9364 if(s->name)
9365 sstrncpy(&q, s->name, limit);
9367 if(s->description){
9368 sstrncpy(&q, " (", limit-(q-buf));
9369 sstrncpy(&q, s->description, limit-(q-buf));
9370 sstrncpy(&q, ")", limit-(q-buf));
9373 /* add the list of charsets that are in this script */
9374 got_one = 0;
9375 for(cs = utf8_charset(NIL);
9376 cs && cs->name && (q-buf) < limit; cs++){
9377 if(cs->script & s->script){
9379 * Filter out some un-useful members of the list.
9380 * UTF-7 and UTF-8 weren't actually in the list at the
9381 * time this was written. Just making sure.
9383 if(!strucmp(cs->name, "ISO-2022-JP-2")
9384 || !strucmp(cs->name, "UTF-7")
9385 || !strucmp(cs->name, "UTF-8"))
9386 continue;
9388 if(got_one)
9389 sstrncpy(&q, " ", limit-(q-buf));
9390 else{
9391 got_one = 1;
9392 sstrncpy(&q, " {", limit-(q-buf));
9395 sstrncpy(&q, cs->name, limit-(q-buf));
9399 if(got_one)
9400 sstrncpy(&q, "}", limit-(q-buf));
9402 /* fold this line so that it can all be seen on the screen */
9403 folded = fold(buf, width, width, "", " ", FLD_NONE);
9404 if(folded){
9405 t = folded;
9406 while(t && *t && (q = strindex(t, '\n')) != NULL){
9407 *q = '\0';
9409 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9410 memset(ls, 0, sizeof(*ls));
9411 if(t == folded)
9412 ls->item = cpystr(s->name);
9413 else
9414 ls->flags = SFL_NOSELECT;
9416 ls->display_item = cpystr(t);
9418 t = q+1;
9420 if(p){
9421 p->next = ls;
9422 p = p->next;
9424 else{
9425 /* add a heading */
9426 listhead = (LIST_SEL_S *) fs_get(sizeof(*ls));
9427 memset(listhead, 0, sizeof(*listhead));
9428 listhead->flags = SFL_NOSELECT;
9429 listhead->display_item =
9430 cpystr(_("Scripts representing groups of related character sets"));
9431 listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls));
9432 memset(listhead->next, 0, sizeof(*listhead));
9433 listhead->next->flags = SFL_NOSELECT;
9434 listhead->next->display_item =
9435 cpystr(repeat_char(width, '-'));
9437 listhead->next->next = ls;
9438 p = ls;
9442 fs_give((void **) &folded);
9446 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9447 memset(ls, 0, sizeof(*ls));
9448 ls->flags = SFL_NOSELECT;
9449 if(p){
9450 p->next = ls;
9451 p = p->next;
9453 else
9454 listhead = p = ls;
9456 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9457 memset(ls, 0, sizeof(*ls));
9458 ls->flags = SFL_NOSELECT;
9459 ls->display_item =
9460 cpystr(_("Individual character sets, may be mixed with scripts"));
9461 p->next = ls;
9462 p = p->next;
9464 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9465 memset(ls, 0, sizeof(*ls));
9466 ls->flags = SFL_NOSELECT;
9467 ls->display_item =
9468 cpystr(repeat_char(width, '-'));
9469 p->next = ls;
9470 p = p->next;
9472 /* then comes a list of individual character sets */
9473 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9474 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9475 memset(ls, 0, sizeof(*ls));
9476 ls->item = cpystr(cs->name);
9478 if(p){
9479 p->next = ls;
9480 p = p->next;
9482 else
9483 listhead = p = ls;
9486 if(!listhead)
9487 return(ret);
9489 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9490 Print something1 using something2.
9491 "character sets" is something1 */
9492 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9493 _("SELECT CHARACTER SETS"), _("character sets"),
9494 h_select_multcharsets_screen,
9495 _("HELP FOR SELECTING CHARACTER SETS"), NULL)){
9496 for(cnt = 0, p = listhead; p; p = p->next)
9497 if(p->selected)
9498 cnt++;
9500 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9501 memset(ret, 0, (cnt+1) * sizeof(*ret));
9502 for(i = 0, p = listhead; p; p = p->next)
9503 if(p->selected)
9504 ret[i++] = cpystr(p->item ? p->item : "");
9507 free_list_sel(&listhead);
9509 return(ret);
9512 /* Report quota summary resources in an IMAP server */
9514 void
9515 cmd_quota (struct pine *state)
9517 QUOTALIST *imapquota;
9518 NETMBX mb;
9519 STORE_S *store;
9520 SCROLL_S sargs;
9522 if(!state->mail_stream || !is_imap_stream(state->mail_stream)){
9523 q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders");
9524 return;
9527 if (state->mail_stream
9528 && !sp_dead_stream(state->mail_stream)
9529 && state->mail_stream->mailbox
9530 && *state->mail_stream->mailbox
9531 && mail_valid_net_parse(state->mail_stream->mailbox, &mb))
9532 imap_getquotaroot(state->mail_stream, mb.mailbox);
9534 if(!state->quota) /* failed ? */
9535 return; /* go back... */
9537 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
9538 q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space.");
9539 return;
9542 so_puts(store, "Quota Report for ");
9543 so_puts(store, state->mail_stream->original_mailbox);
9544 so_puts(store, "\n\n");
9546 for (imapquota = state->quota; imapquota; imapquota = imapquota->next){
9548 so_puts(store, _("Resource : "));
9549 so_puts(store, imapquota->name);
9550 so_writec('\n', store);
9552 so_puts(store, _("Usage : "));
9553 so_puts(store, long2string(imapquota->usage));
9554 if(!strucmp(imapquota->name,"STORAGE"))
9555 so_puts(store, " KiB ");
9556 if(!strucmp(imapquota->name,"MESSAGE")){
9557 so_puts(store, _(" message"));
9558 if(imapquota->usage != 1)
9559 so_puts(store, _("s ")); /* plural */
9560 else
9561 so_puts(store, _(" "));
9563 so_writec('(', store);
9564 so_puts(store, long2string(100*imapquota->usage/imapquota->limit));
9565 so_puts(store, "%)\n");
9567 so_puts(store, _("Limit : "));
9568 so_puts(store, long2string(imapquota->limit));
9569 if(!strucmp(imapquota->name,"STORAGE"))
9570 so_puts(store, " KiB\n\n");
9571 if(!strucmp(imapquota->name,"MESSAGE")){
9572 so_puts(store, _(" message"));
9573 if(imapquota->usage != 1)
9574 so_puts(store, _("s\n\n")); /* plural */
9575 else
9576 so_puts(store, _("\n\n"));
9580 memset(&sargs, 0, sizeof(SCROLL_S));
9581 sargs.text.text = so_text(store);
9582 sargs.text.src = CharStar;
9583 sargs.text.desc = _("Quota Resources Summary");
9584 sargs.bar.title = _("QUOTA SUMMARY");
9585 sargs.proc.tool = NULL;
9586 sargs.help.text = h_quota_command;
9587 sargs.help.title = NULL;
9588 sargs.keys.menu = NULL;
9589 setbitmap(sargs.keys.bitmap);
9591 scrolltool(&sargs);
9592 so_give(&store);
9594 if (state->quota)
9595 mail_free_quotalist(&(state->quota));
9598 /*----------------------------------------------------------------------
9599 Prompt the user for the type of sort he desires
9601 Args: state -- pine state pointer
9602 q1 -- Line to prompt on
9604 Returns 0 if it was cancelled, 1 otherwise.
9605 ----*/
9607 select_sort(struct pine *state, int ql, SortOrder *sort, int *rev)
9609 char prompt[200], tmp[3], *p;
9610 int s, i;
9611 int deefault = 'a', retval = 1;
9612 HelpType help;
9613 ESCKEY_S sorts[14];
9615 #ifdef _WINDOWS
9616 DLG_SORTPARAM sortsel;
9618 if (mswin_usedialog ()) {
9620 sortsel.reverse = mn_get_revsort (state->msgmap);
9621 sortsel.cursort = mn_get_sort (state->msgmap);
9622 /* assumption here that HelpType is char ** */
9623 sortsel.helptext = h_select_sort;
9624 sortsel.rval = 0;
9626 if ((retval = os_sortdialog (&sortsel))) {
9627 *sort = sortsel.cursort;
9628 *rev = sortsel.reverse;
9631 return (retval);
9633 #endif
9635 /*----- String together the prompt ------*/
9636 tmp[1] = '\0';
9637 if(F_ON(F_USE_FK,ps_global))
9638 strncpy(prompt, _("Choose type of sort : "), sizeof(prompt));
9639 else
9640 strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "),
9641 sizeof(prompt));
9643 for(i = 0; state->sort_types[i] != EndofList; i++) {
9644 sorts[i].rval = i;
9645 p = sorts[i].label = sort_name(state->sort_types[i]);
9646 while(*(p+1) && islower((unsigned char)*p))
9647 p++;
9649 sorts[i].ch = tolower((unsigned char)(tmp[0] = *p));
9650 sorts[i].name = cpystr(tmp);
9652 if(mn_get_sort(state->msgmap) == state->sort_types[i])
9653 deefault = sorts[i].rval;
9656 sorts[i].ch = 'r';
9657 sorts[i].rval = 'r';
9658 sorts[i].name = cpystr("R");
9659 if(F_ON(F_USE_FK,ps_global))
9660 sorts[i].label = N_("Reverse");
9661 else
9662 sorts[i].label = "";
9664 sorts[++i].ch = -1;
9665 help = h_select_sort;
9667 if((F_ON(F_USE_FK,ps_global)
9668 && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x',
9669 help,RB_NORM)) != 'x'))
9671 (F_OFF(F_USE_FK,ps_global)
9672 && ((s = radio_buttons(prompt,ql,sorts,deefault,'x',
9673 help,RB_NORM)) != 'x'))){
9674 state->mangled_body = 1; /* signal screen's changed */
9675 if(s == 'r')
9676 *rev = !mn_get_revsort(state->msgmap);
9677 else
9678 *sort = state->sort_types[s];
9680 if(F_ON(F_SHOW_SORT, ps_global))
9681 ps_global->mangled_header = 1;
9683 else{
9684 retval = 0;
9685 cmd_cancelled("Sort");
9688 while(--i >= 0)
9689 fs_give((void **)&sorts[i].name);
9691 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9692 return(retval);
9696 /*---------------------------------------------------------------------
9697 Build list of folders in the given context for user selection
9699 Args: c -- pointer to pointer to folder's context context
9700 f -- folder prefix to display
9701 sublist -- whether or not to use 'f's contents as prefix
9702 lister -- function used to do the actual display
9704 Returns: malloc'd string containing sequence, else NULL if
9705 no messages in msgmap with local "selected" flag.
9706 ----*/
9708 display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int))
9710 int rc;
9711 CONTEXT_S *tc;
9712 void (*redraw)(void) = ps_global->redrawer;
9714 push_titlebar_state();
9715 tc = *c;
9716 if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0)
9717 *c = tc;
9719 ClearScreen();
9720 pop_titlebar_state();
9721 redraw_titlebar();
9722 if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
9723 (*ps_global->redrawer)();
9725 if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
9726 return(1);
9728 return(0);
9733 * Allow user to choose a single item from a list of strings.
9735 * Args list -- Array of strings to choose from, NULL terminated.
9736 * displist -- Array of strings to display instead of displaying list.
9737 * Indices correspond to the list array. Display the displist
9738 * but return the item from list if displist non-NULL.
9739 * title -- For conf_scroll_screen
9740 * pdesc -- For conf_scroll_screen
9741 * help -- For conf_scroll_screen
9742 * htitle -- For conf_scroll_screen
9744 * Returns an allocated copy of the chosen item or NULL.
9746 char *
9747 choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help,
9748 char *htitle, char *cursor_location)
9750 LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL;
9751 char **t, **dl;
9752 char *ret = NULL, *choice = NULL;
9754 /* build the LIST_SEL_S list */
9755 p = listhead = NULL;
9756 for(t = list, dl = displist; *t; t++, dl++){
9757 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9758 memset(ls, 0, sizeof(*ls));
9759 ls->item = cpystr(*t);
9760 if(displist)
9761 ls->display_item = cpystr(*dl);
9763 if(cursor_location && (cursor_location == (*t)))
9764 starting_val = ls;
9766 if(p){
9767 p->next = ls;
9768 p = p->next;
9770 else
9771 listhead = p = ls;
9774 if(!listhead)
9775 return(ret);
9777 if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc,
9778 help, htitle, starting_val))
9779 for(p = listhead; !choice && p; p = p->next)
9780 if(p->selected)
9781 choice = p->item;
9783 if(choice)
9784 ret = cpystr(choice);
9786 free_list_sel(&listhead);
9788 return(ret);
9792 void
9793 free_list_sel(LIST_SEL_S **lsel)
9795 if(lsel && *lsel){
9796 free_list_sel(&(*lsel)->next);
9797 if((*lsel)->item)
9798 fs_give((void **) &(*lsel)->item);
9800 if((*lsel)->display_item)
9801 fs_give((void **) &(*lsel)->display_item);
9803 fs_give((void **) lsel);
9809 * file_lister - call pico library's file lister
9812 file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags)
9814 PICO pbf;
9815 int rv;
9816 void (*redraw)(void) = ps_global->redrawer;
9818 standard_picobuf_setup(&pbf);
9819 push_titlebar_state();
9820 if(!newmail)
9821 pbf.newmail = NULL;
9823 /* BUG: what about help command and text? */
9824 pbf.pine_anchor = title;
9826 rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags);
9827 standard_picobuf_teardown(&pbf);
9828 fix_windsize(ps_global);
9829 init_signals(); /* has it's own signal stuff */
9831 /* Restore display's titlebar and body */
9832 pop_titlebar_state();
9833 redraw_titlebar();
9834 if((ps_global->redrawer = redraw) != NULL)
9835 (*ps_global->redrawer)();
9837 return(rv);
9841 /*----------------------------------------------------------------------
9842 Print current folder index
9844 ---*/
9846 print_index(struct pine *state, MSGNO_S *msgmap, int agg)
9848 long i;
9849 ICE_S *ice;
9850 char buf[MAX_SCREEN_COLS+1];
9852 for(i = 1L; i <= mn_get_total(msgmap); i++){
9853 if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
9854 continue;
9856 if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0))
9857 continue;
9859 ice = build_header_line(state, state->mail_stream, msgmap, i, NULL);
9861 if(ice){
9863 * I don't understand why we'd want to mark the current message
9864 * instead of printing out the first character of the status
9865 * so I'm taking it out and including the first character of the
9866 * line instead. Hubert 2006-02-09
9868 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
9869 return(0);
9872 if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i),
9873 print_char)
9874 || !gf_puts(NEWLINE, print_char))
9875 return(0);
9879 return(1);
9882 #ifdef _WINDOWS
9885 * windows callback to get/set header mode state
9888 header_mode_callback(set, args)
9889 int set;
9890 long args;
9892 return(ps_global->full_header);
9897 * windows callback to get/set zoom mode state
9900 zoom_mode_callback(set, args)
9901 int set;
9902 long args;
9904 return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0);
9909 * windows callback to get/set zoom mode state
9912 any_selected_callback(set, args)
9913 int set;
9914 long args;
9916 return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0);
9924 flag_callback(set, flags)
9925 int set;
9926 long flags;
9928 MESSAGECACHE *mc;
9929 int newflags = 0;
9930 long msgno;
9931 int permflag = 0;
9933 switch (set) {
9934 case 1: /* Important */
9935 permflag = ps_global->mail_stream->perm_flagged;
9936 break;
9938 case 2: /* New */
9939 permflag = ps_global->mail_stream->perm_seen;
9940 break;
9942 case 3: /* Answered */
9943 permflag = ps_global->mail_stream->perm_answered;
9944 break;
9946 case 4: /* Deleted */
9947 permflag = ps_global->mail_stream->perm_deleted;
9948 break;
9952 if(!(any_messages(ps_global->msgmap, NULL, "to Flag")
9953 && can_set_flag(ps_global, "flag", permflag)))
9954 return(0);
9956 if(sp_io_error_on_stream(ps_global->mail_stream)){
9957 sp_set_io_error_on_stream(ps_global->mail_stream, 0);
9958 pine_mail_check(ps_global->mail_stream); /* forces write */
9959 return(0);
9962 msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
9963 if(msgno > 0L && ps_global->mail_stream
9964 && msgno <= ps_global->mail_stream->nmsgs
9965 && (mc = mail_elt(ps_global->mail_stream, msgno))
9966 && mc->valid){
9968 * NOTE: code below is *VERY* sensitive to the order of
9969 * the messages defined in resource.h for flag handling.
9970 * Don't change it unless you know what you're doing.
9972 if(set){
9973 char *flagstr;
9974 long mflag;
9976 switch(set){
9977 case 1 : /* Important */
9978 flagstr = "\\FLAGGED";
9979 mflag = (mc->flagged) ? 0L : ST_SET;
9980 break;
9982 case 2 : /* New */
9983 flagstr = "\\SEEN";
9984 mflag = (mc->seen) ? 0L : ST_SET;
9985 break;
9987 case 3 : /* Answered */
9988 flagstr = "\\ANSWERED";
9989 mflag = (mc->answered) ? 0L : ST_SET;
9990 break;
9992 case 4 : /* Deleted */
9993 flagstr = "\\DELETED";
9994 mflag = (mc->deleted) ? 0L : ST_SET;
9995 break;
9997 default : /* bogus */
9998 return(0);
10001 mail_flag(ps_global->mail_stream, long2string(msgno),
10002 flagstr, mflag);
10004 if(ps_global->redrawer)
10005 (*ps_global->redrawer)();
10007 else{
10008 /* Important */
10009 if(mc->flagged)
10010 newflags |= 0x0001;
10012 /* New */
10013 if(!mc->seen)
10014 newflags |= 0x0002;
10016 /* Answered */
10017 if(mc->answered)
10018 newflags |= 0x0004;
10020 /* Deleted */
10021 if(mc->deleted)
10022 newflags |= 0x0008;
10026 return(newflags);
10032 * BUG: Should teach this about keywords
10034 MPopup *
10035 flag_submenu(mc)
10036 MESSAGECACHE *mc;
10038 static MPopup flag_submenu[] = {
10039 {tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}},
10040 {tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}},
10041 {tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}},
10042 {tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}},
10043 {tTail}
10046 /* Important */
10047 flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal;
10049 /* New */
10050 flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked;
10052 /* Answered */
10053 flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal;
10055 /* Deleted */
10056 flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal;
10058 return(flag_submenu);
10061 #endif /* _WINDOWS */