* Addition of a link to the Apache License 2.0. This is available from
[alpine.git] / alpine / mailcmd.c
blob8c94aa48634e71f8e15a99913a46a1a9f33ae1e1
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: mailcmd.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2013-2020 Eduardo Chappa
8 * Copyright 2006-2009 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
20 mailcmd.c
21 The meat and pototoes of mail processing here:
22 - initial command processing and dispatch
23 - save message
24 - capture address off incoming mail
25 - jump to specific numbered message
26 - open (broach) a new folder
27 - search message headers (where is) command
28 ====*/
31 #include "headers.h"
32 #include "mailcmd.h"
33 #include "status.h"
34 #include "mailview.h"
35 #include "flagmaint.h"
36 #include "listsel.h"
37 #include "keymenu.h"
38 #include "alpine.h"
39 #include "mailpart.h"
40 #include "mailindx.h"
41 #include "folder.h"
42 #include "reply.h"
43 #include "help.h"
44 #include "titlebar.h"
45 #include "signal.h"
46 #include "radio.h"
47 #include "pipe.h"
48 #include "send.h"
49 #include "takeaddr.h"
50 #include "roleconf.h"
51 #include "smime.h"
52 #include "../pith/state.h"
53 #include "../pith/msgno.h"
54 #include "../pith/store.h"
55 #include "../pith/thread.h"
56 #include "../pith/flag.h"
57 #include "../pith/sort.h"
58 #include "../pith/maillist.h"
59 #include "../pith/save.h"
60 #include "../pith/pipe.h"
61 #include "../pith/news.h"
62 #include "../pith/util.h"
63 #include "../pith/sequence.h"
64 #include "../pith/keyword.h"
65 #include "../pith/stream.h"
66 #include "../pith/mailcmd.h"
67 #include "../pith/hist.h"
68 #include "../pith/list.h"
69 #include "../pith/icache.h"
70 #include "../pith/busy.h"
71 #include "../pith/mimedesc.h"
72 #include "../pith/pattern.h"
73 #include "../pith/tempfile.h"
74 #include "../pith/search.h"
75 #include "../pith/margin.h"
76 #ifdef _WINDOWS
77 #include "../pico/osdep/mswin.h"
78 #endif
81 * Internal Prototypes
83 int cmd_flag(struct pine *, MSGNO_S *, int);
84 int cmd_flag_prompt(struct pine *, struct flag_screen *, int);
85 void free_flag_table(struct flag_table **);
86 int cmd_reply(struct pine *, MSGNO_S *, int, ACTION_S *);
87 int cmd_forward(struct pine *, MSGNO_S *, int, ACTION_S *);
88 int cmd_bounce(struct pine *, MSGNO_S *, int, ACTION_S *);
89 int cmd_save(struct pine *, MAILSTREAM *, MSGNO_S *, int, CmdWhere);
90 void role_compose(struct pine *);
91 int cmd_expunge(struct pine *, MAILSTREAM *, MSGNO_S *, int);
92 int cmd_export(struct pine *, MSGNO_S *, int, int);
93 char *cmd_delete_action(struct pine *, MSGNO_S *, CmdWhere);
94 char *cmd_delete_view(struct pine *, MSGNO_S *);
95 char *cmd_delete_index(struct pine *, MSGNO_S *);
96 long get_level(int, UCS, SCROLL_S *);
97 long closest_jump_target(long, MAILSTREAM *, MSGNO_S *, int, CmdWhere, char *, size_t);
98 int update_folder_spec(char *, size_t, char *);
99 int cmd_print(struct pine *, MSGNO_S *, int, CmdWhere);
100 int cmd_pipe(struct pine *, MSGNO_S *, int);
101 STORE_S *list_mgmt_text(RFC2369_S *, long);
102 void list_mgmt_screen(STORE_S *);
103 int aggregate_select(struct pine *, MSGNO_S *, int, CmdWhere);
104 int select_by_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
105 int select_by_thrd_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
106 int select_by_date(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
107 int select_by_text(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
108 int select_by_gm_content(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **);
109 int select_by_size(MAILSTREAM *, SEARCHSET **);
110 SEARCHSET *visible_searchset(MAILSTREAM *, MSGNO_S *);
111 int select_by_status(MAILSTREAM *, SEARCHSET **);
112 int select_by_rule(MAILSTREAM *, SEARCHSET **);
113 int select_by_thread(MAILSTREAM *, MSGNO_S *, SEARCHSET **);
114 char *choose_a_rule(int);
115 int select_by_keyword(MAILSTREAM *, SEARCHSET **);
116 char *choose_a_keyword(void);
117 int select_sort(struct pine *, int, SortOrder *, int *);
118 int print_index(struct pine *, MSGNO_S *, int);
121 * List of Select options used by apply_* functions...
123 static char *sel_pmt1 = N_("ALTER message selection : ");
124 ESCKEY_S sel_opts1[] = {
125 /* TRANSLATORS: these are keymenu names for selecting. Broaden selection means
126 we will add more messages to the selection, Narrow selection means we will
127 remove some selections (like a logical AND instead of logical OR), and Flip
128 Selected means that all the messages that are currently selected become unselected,
129 and all the unselected messages become selected. */
130 {'a', 'a', "A", N_("unselect All")},
131 {'c', 'c', "C", NULL},
132 {'b', 'b', "B", N_("Broaden selctn")},
133 {'n', 'n', "N", N_("Narrow selctn")},
134 {'f', 'f', "F", N_("Flip selected")},
135 {'r', 'r', "R", N_("Replace selctn")},
136 {-1, 0, NULL, NULL}
140 #define SEL_OPTS_THREAD 9 /* index number of "tHread" */
141 #define SEL_OPTS_THREAD_CH 'h'
142 #define SEL_OPTS_XGMEXT 10 /* index number of "boX" */
143 #define SEL_OPTS_XGMEXT_CH 'g'
145 char *sel_pmt2 = "SELECT criteria : ";
146 static ESCKEY_S sel_opts2[] = {
147 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
148 means select the currently highlighted message; select by Number is by message
149 number; Status is by status of the message, for example the message might be
150 New or it might be Unseen or marked Important; Size has the Z upper case because
151 it is a Z command; Keyword is an alpine keyword that has been set by the user;
152 and Rule is an alpine rule */
153 {'a', 'a', "A", N_("select All")},
154 {'c', 'c', "C", N_("select Cur")},
155 {'n', 'n', "N", N_("Number")},
156 {'d', 'd', "D", N_("Date")},
157 {'t', 't', "T", N_("Text")},
158 {'s', 's', "S", N_("Status")},
159 {'z', 'z', "Z", N_("siZe")},
160 {'k', 'k', "K", N_("Keyword")},
161 {'r', 'r', "R", N_("Rule")},
162 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
163 {-1, 0, NULL, NULL}
167 static ESCKEY_S sel_opts3[] = {
168 /* TRANSLATORS: these are operations we can do on a set of selected messages.
169 Del is Delete; Undel is Undelete; TakeAddr means to Take some Addresses into
170 the address book; Save means to save the messages into another alpine folder;
171 Export means to copy the messages to a file outside of alpine, external to
172 alpine's world. */
173 {'d', 'd', "D", N_("Del")},
174 {'u', 'u', "U", N_("Undel")},
175 {'r', 'r', "R", N_("Reply")},
176 {'f', 'f', "F", N_("Forward")},
177 {'%', '%', "%", N_("Print")},
178 {'t', 't', "T", N_("TakeAddr")},
179 {'s', 's', "S", N_("Save")},
180 {'e', 'e', "E", N_("Export")},
181 { -1, 0, NULL, NULL},
182 { -1, 0, NULL, NULL},
183 { -1, 0, NULL, NULL},
184 { -1, 0, NULL, NULL},
185 { -1, 0, NULL, NULL},
186 { -1, 0, NULL, NULL},
187 { -1, 0, NULL, NULL},
188 {-1, 0, NULL, NULL}
191 static ESCKEY_S sel_opts4[] = {
192 {'a', 'a', "A", N_("select All")},
193 /* TRANSLATORS: select currently highlighted message Thread */
194 {'c', 'c', "C", N_("select Curthrd")},
195 {'n', 'n', "N", N_("Number")},
196 {'d', 'd', "D", N_("Date")},
197 {'t', 't', "T", N_("Text")},
198 {'s', 's', "S", N_("Status")},
199 {'z', 'z', "Z", N_("siZe")},
200 {'k', 'k', "K", N_("Keyword")},
201 {'r', 'r', "R", N_("Rule")},
202 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
203 {-1, 0, NULL, NULL}
206 static ESCKEY_S sel_opts5[] = {
207 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
208 means select the currently highlighted message; select by Number is by message
209 number; Status is by status of the message, for example the message might be
210 New or it might be Unseen or marked Important; Size has the Z upper case because
211 it is a Z command; Keyword is an alpine keyword that has been set by the user;
212 and Rule is an alpine rule */
213 {'a', 'a', "A", N_("select All")},
214 {'c', 'c', "C", N_("select Cur")},
215 {'n', 'n', "N", N_("Number")},
216 {'d', 'd', "D", N_("Date")},
217 {'t', 't', "T", N_("Text")},
218 {'s', 's', "S", N_("Status")},
219 {'z', 'z', "Z", N_("siZe")},
220 {'k', 'k', "K", N_("Keyword")},
221 {'r', 'r', "R", N_("Rule")},
222 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
223 {SEL_OPTS_XGMEXT_CH, 'g', "G", N_("GmSearch")},
224 {-1, 0, NULL, NULL},
225 {-1, 0, NULL, NULL},
226 {-1, 0, NULL, NULL},
227 {-1, 0, NULL, NULL},
228 {-1, 0, NULL, NULL}
231 static ESCKEY_S sel_opts6[] = {
232 {'a', 'a', "A", N_("select All")},
233 /* TRANSLATORS: select currently highlighted message Thread */
234 {'c', 'c', "C", N_("select Curthrd")},
235 {'n', 'n', "N", N_("Number")},
236 {'d', 'd', "D", N_("Date")},
237 {'t', 't', "T", N_("Text")},
238 {'s', 's', "S", N_("Status")},
239 {'z', 'z', "Z", N_("siZe")},
240 {'k', 'k', "K", N_("Keyword")},
241 {'r', 'r', "R", N_("Rule")},
242 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
243 {SEL_OPTS_XGMEXT_CH, 'g', "G", N_("Gmail")},
244 {-1, 0, NULL, NULL},
245 {-1, 0, NULL, NULL},
246 {-1, 0, NULL, NULL},
247 {-1, 0, NULL, NULL},
248 {-1, 0, NULL, NULL}
252 static char *sel_flag =
253 N_("Select New, Deleted, Answered, Forwarded, or Important messages ? ");
254 static char *sel_flag_not =
255 N_("Select NOT New, NOT Deleted, NOT Answered, NOT Forwarded or NOT Important msgs ? ");
256 static ESCKEY_S sel_flag_opt[] = {
257 /* TRANSLATORS: When selecting messages by message Status these are the
258 different types of Status you can select on. Is the message New, Recent,
259 and so on. Not means flip the meaning of the selection to the opposite
260 thing, so message is not New or not Important. */
261 {'n', 'n', "N", N_("New")},
262 {'*', '*', "*", N_("Important")},
263 {'d', 'd', "D", N_("Deleted")},
264 {'a', 'a', "A", N_("Answered")},
265 {'f', 'f', "F", N_("Forwarded")},
266 {-2, 0, NULL, NULL},
267 {'!', '!', "!", N_("Not")},
268 {-2, 0, NULL, NULL},
269 {'r', 'r', "R", N_("Recent")},
270 {'u', 'u', "U", N_("Unseen")},
271 {-1, 0, NULL, NULL}
275 static ESCKEY_S sel_date_opt[] = {
276 {0, 0, NULL, NULL},
277 /* TRANSLATORS: options when selecting messages by Date */
278 {ctrl('P'), 12, "^P", N_("Prev Day")},
279 {ctrl('N'), 13, "^N", N_("Next Day")},
280 {ctrl('X'), 11, "^X", N_("Cur Msg")},
281 {ctrl('W'), 14, "^W", N_("Toggle When")},
282 {KEY_UP, 12, "", ""},
283 {KEY_DOWN, 13, "", ""},
284 {-1, 0, NULL, NULL}
288 static char *sel_x_gm_ext =
289 N_("Search: ");
290 static char *sel_text =
291 N_("Select based on To, From, Cc, Recip, Partic, Subject fields or All msg text ? ");
292 static char *sel_text_not =
293 N_("Select based on NOT To, From, Cc, Recip, Partic, Subject or All msg text ? ");
294 static ESCKEY_S sel_text_opt[] = {
295 /* TRANSLATORS: Select messages based on the text contained in the From line, or
296 the Subject line, and so on. */
297 {'f', 'f', "F", N_("From")},
298 {'s', 's', "S", N_("Subject")},
299 {'t', 't', "T", N_("To")},
300 {'a', 'a', "A", N_("All Text")},
301 {'c', 'c', "C", N_("Cc")},
302 {'!', '!', "!", N_("Not")},
303 {'r', 'r', "R", N_("Recipient")},
304 {'p', 'p', "P", N_("Participant")},
305 {'b', 'b', "B", N_("Body")},
306 {'h', 'h', "H", N_("Header")},
307 {-1, 0, NULL, NULL}
310 static ESCKEY_S choose_action[] = {
311 {'c', 'c', "C", N_("Compose")},
312 {'r', 'r', "R", N_("Reply")},
313 {'f', 'f', "F", N_("Forward")},
314 {'b', 'b', "B", N_("Bounce")},
315 {-1, 0, NULL, NULL}
318 static char *select_num =
319 N_("Enter comma-delimited list of numbers (dash between ranges): ");
321 static char *select_size_larger_msg =
322 N_("Select messages with size larger than: ");
324 static char *select_size_smaller_msg =
325 N_("Select messages with size smaller than: ");
327 static char *sel_size_larger = N_("Larger");
328 static char *sel_size_smaller = N_("Smaller");
329 static ESCKEY_S sel_size_opt[] = {
330 {0, 0, NULL, NULL},
331 {ctrl('W'), 14, "^W", NULL},
332 {-1, 0, NULL, NULL}
335 static ESCKEY_S sel_key_opt[] = {
336 {0, 0, NULL, NULL},
337 {ctrl('T'), 14, "^T", N_("To List")},
338 {0, 0, NULL, NULL},
339 {'!', '!', "!", N_("Not")},
340 {-1, 0, NULL, NULL}
343 static ESCKEY_S flag_text_opt[] = {
344 /* TRANSLATORS: these are types of flags (markers) that the user can
345 set. For example, they can flag the message as an important message. */
346 {'n', 'n', "N", N_("New")},
347 {'*', '*', "*", N_("Important")},
348 {'d', 'd', "D", N_("Deleted")},
349 {'a', 'a', "A", N_("Answered")},
350 {'f', 'f', "F", N_("Forwarded")},
351 {'!', '!', "!", N_("Not")},
352 {ctrl('T'), 10, "^T", N_("To Flag Details")},
353 {-1, 0, NULL, NULL}
357 alpine_smime_confirm_save(char *email)
359 char prompt[128];
361 snprintf(prompt, sizeof(prompt), _("Save certificate for <%s>"),
362 email ? email : _("missing address"));
363 return want_to(prompt, 'n', 'x', NO_HELP, WT_NORM) == 'y';
366 int
367 alpine_get_password(char *prompt, char *pass, size_t len)
369 int flags = OE_PASSWD | OE_DISALLOW_HELP;
370 pass[0] = '\0';
371 return optionally_enter(pass,
372 -(ps_global->ttyo ? FOOTER_ROWS(ps_global) : 3),
373 0, len, prompt, NULL, NO_HELP, &flags);
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);
665 if(topthrd)
666 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
669 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
670 if(view_thread(state, stream, msgmap, 1)
671 && current_index_state)
672 msgmap->top_after_thrd = current_index_state->msg_at_top;
676 state->next_screen = SCREEN_FUN_NULL;
678 else
679 mn_set_cur(msgmap, i); /* put it back */
682 else if(THREADING()
683 && (thrd = fetch_thread(stream, mn_m2raw(msgmap, i)))
684 && thrd->next
685 && get_lflag(stream, NULL, thrd->rawno, MN_COLL)){
686 q_status_message(SM_ORDER, 0, 2,
687 _("Expand collapsed thread to see more messages"));
689 else
690 any_messages(NULL, "more", "in Zoomed Index");
693 else{
694 time_t now;
695 nfolder:
696 prompt[0] = '\0';
697 if(IS_NEWS(stream)
698 || (state->context_current->use & CNTXT_INCMNG)){
699 char nextfolder[MAXPATH];
701 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
702 nextfolder[sizeof(nextfolder)-1] = '\0';
703 if(next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
704 state->context_current, NULL, NULL))
705 strncpy(prompt, _(". Press TAB for next folder."),
706 sizeof(prompt));
707 else
708 strncpy(prompt, _(". No more folders to TAB to."),
709 sizeof(prompt));
711 prompt[sizeof(prompt)-1] = '\0';
714 any_messages(NULL, (mn_get_total(msgmap) > 0L) ? "more" : NULL,
715 prompt[0] ? prompt : NULL);
717 if(!IS_NEWS(stream)
718 && ((now = time(0)) > state->last_nextitem_forcechk)){
719 *force_mailchk = 1;
720 /* check at most once a second */
721 state->last_nextitem_forcechk = now;
725 break;
728 /*---------- Delete message ----------*/
729 case MC_DELETE :
730 (void) cmd_delete(state, msgmap, MCMD_NONE,
731 (in_index == View) ? cmd_delete_view : cmd_delete_index);
732 break;
735 /*---------- Undelete message ----------*/
736 case MC_UNDELETE :
737 (void) cmd_undelete(state, msgmap, MCMD_NONE);
738 update_titlebar_status();
739 break;
742 /*---------- Reply to message ----------*/
743 case MC_REPLY :
744 (void) cmd_reply(state, msgmap, MCMD_NONE, NULL);
745 break;
748 /*---------- Forward message ----------*/
749 case MC_FORWARD :
750 (void) cmd_forward(state, msgmap, MCMD_NONE, NULL);
751 break;
754 /*---------- Quit pine ------------*/
755 case MC_QUIT :
756 state->next_screen = quit_screen;
757 dprint((1,"MAIL_CMD: quit\n"));
758 break;
761 /*---------- Compose message ----------*/
762 case MC_COMPOSE :
763 state->prev_screen = (in_index == View) ? mail_view_screen
764 : mail_index_screen;
765 compose_screen(state);
766 state->mangled_screen = 1;
767 if (state->next_screen)
768 a_changed = TRUE;
769 break;
772 /*---------- Alt Compose message ----------*/
773 case MC_ROLE :
774 state->prev_screen = (in_index == View) ? mail_view_screen
775 : mail_index_screen;
776 role_compose(state);
777 if(state->next_screen)
778 a_changed = TRUE;
780 break;
783 /*--------- Folders menu ------------*/
784 case MC_FOLDERS :
785 state->start_in_context = 1;
787 /*--------- Top of Folders list menu ------------*/
788 case MC_COLLECTIONS :
789 state->next_screen = folder_screen;
790 dprint((2,"MAIL_CMD: going to folder/collection menu\n"));
791 break;
794 /*---------- Open specific new folder ----------*/
795 case MC_GOTO :
796 tc = (state->context_last && !NEWS_TEST(state->context_current))
797 ? state->context_last : state->context_current;
799 newfolder = broach_folder(question_line, 1, &notrealinbox, &tc);
800 if(newfolder){
801 visit_folder(state, newfolder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT);
802 a_changed = TRUE;
805 break;
808 /*------- Go to Index Screen ----------*/
809 case MC_INDEX :
810 state->next_screen = mail_index_screen;
811 break;
813 /*------- Skip to next interesting message -----------*/
814 case MC_TAB :
815 if(THRD_INDX()){
816 PINETHRD_S *thrd;
819 * If we're in the thread index, start looking after this
820 * thread. We don't want to match something in the current
821 * thread.
823 start = mn_get_cur(msgmap);
824 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
825 if(mn_get_revsort(msgmap)){
826 /* if reversed, top of thread is last one before next thread */
827 if(thrd && thrd->top)
828 start = mn_raw2m(msgmap, thrd->top);
830 else{
831 /* last msg of thread is at the ends of the branches/nexts */
832 while(thrd){
833 start = mn_raw2m(msgmap, thrd->rawno);
834 if(thrd->branch)
835 thrd = fetch_thread(stream, thrd->branch);
836 else if(thrd->next)
837 thrd = fetch_thread(stream, thrd->next);
838 else
839 thrd = NULL;
844 * Flags is 0 in this case because we want to not skip
845 * messages inside of threads so that we can find threads
846 * which have some unseen messages even though the top-level
847 * of the thread is already seen.
848 * If new_msgno ends up being a message which is not visible
849 * because it isn't at the top-level, the current message #
850 * will be adjusted below in adjust_cur.
852 flags = 0;
853 new_msgno = next_sorted_flagged((F_UNDEL
854 | F_UNSEEN
855 | ((F_ON(F_TAB_TO_NEW,state))
856 ? 0 : F_OR_FLAG)),
857 stream, start, &flags);
859 else if(THREADING() && sp_viewing_a_thread(stream)){
860 PINETHRD_S *thrd, *topthrd = NULL;
862 start = mn_get_cur(msgmap);
865 * Things are especially complicated when we're viewing_a_thread
866 * from the thread index. First we have to check within the
867 * current thread for a new message. If none is found, then
868 * we search in the next threads and offer to continue in
869 * them. Then we offer to go to the next folder.
871 flags = NSF_SKIP_CHID;
872 new_msgno = next_sorted_flagged((F_UNDEL
873 | F_UNSEEN
874 | ((F_ON(F_TAB_TO_NEW,state))
875 ? 0 : F_OR_FLAG)),
876 stream, start, &flags);
878 * If we found a match then we are done, that is another message
879 * in the current thread index. Otherwise, we have to look
880 * further.
882 if(!(flags & NSF_FLAG_MATCH)){
883 ret = 'n';
884 while(1){
886 flags = 0;
887 new_msgno = next_sorted_flagged((F_UNDEL
888 | F_UNSEEN
889 | ((F_ON(F_TAB_TO_NEW,
890 state))
891 ? 0 : F_OR_FLAG)),
892 stream, start, &flags);
894 * If we got a match, new_msgno is a message in
895 * a different thread from the one we are viewing.
897 if(flags & NSF_FLAG_MATCH){
898 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
899 if(thrd && thrd->top)
900 topthrd = fetch_thread(stream, thrd->top);
902 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
903 static ESCKEY_S next_opt[] = {
904 {'y', 'y', "Y", N_("Yes")},
905 {'n', 'n', "N", N_("No")},
906 {TAB, 'n', "Tab", N_("NextNew")},
907 {-1, 0, NULL, NULL}
910 if(in_index)
911 snprintf(prompt, sizeof(prompt), _("View thread number %s? "),
912 topthrd ? comatose(topthrd->thrdno) : "?");
913 else
914 snprintf(prompt, sizeof(prompt),
915 _("View message in thread number %s? "),
916 topthrd ? comatose(topthrd->thrdno) : "?");
918 prompt[sizeof(prompt)-1] = '\0';
920 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
921 next_opt, 'y', 'x', NO_HELP,
922 RB_NORM);
923 if(ret == 'x'){
924 cmd_cancelled(NULL);
925 goto get_out;
928 else
929 ret = 'y';
931 if(ret == 'y'){
932 if(unview_thread(state, stream, msgmap)){
933 state->next_screen = mail_index_screen;
934 state->view_skipped_index = 0;
935 state->mangled_screen = 1;
938 mn_set_cur(msgmap, new_msgno);
939 if(THRD_AUTO_VIEW()){
941 if(count_lflags_in_thread(stream, topthrd,
942 msgmap, MN_NONE) == 1){
943 if(view_thread(state, stream, msgmap, 1)){
944 if(current_index_state)
945 msgmap->top_after_thrd = current_index_state->msg_at_top;
947 state->view_skipped_index = 1;
948 command = MC_VIEW_TEXT;
949 goto view_text;
954 if(view_thread(state, stream, msgmap, 1) && current_index_state)
955 msgmap->top_after_thrd = current_index_state->msg_at_top;
957 state->next_screen = SCREEN_FUN_NULL;
958 break;
960 else if(ret == 'n' && topthrd){
962 * skip to end of this thread and look starting
963 * in the next thread.
965 if(mn_get_revsort(msgmap)){
967 * if reversed, top of thread is last one
968 * before next thread
970 start = mn_raw2m(msgmap, topthrd->rawno);
972 else{
974 * last msg of thread is at the ends of
975 * the branches/nexts
977 thrd = topthrd;
978 while(thrd){
979 start = mn_raw2m(msgmap, thrd->rawno);
980 if(thrd->branch)
981 thrd = fetch_thread(stream, thrd->branch);
982 else if(thrd->next)
983 thrd = fetch_thread(stream, thrd->next);
984 else
985 thrd = NULL;
989 else if(ret == 'n')
990 break;
992 else
993 break;
997 else{
999 start = mn_get_cur(msgmap);
1002 * If we are on a collapsed thread, start looking after the
1003 * collapsed part, unless we are viewing the message.
1005 if(THREADING() && in_index != View){
1006 PINETHRD_S *thrd;
1007 long rawno;
1008 int collapsed;
1010 rawno = mn_m2raw(msgmap, start);
1011 thrd = fetch_thread(stream, rawno);
1012 collapsed = thrd && thrd->next
1013 && get_lflag(stream, NULL, rawno, MN_COLL);
1015 if(collapsed){
1016 if(mn_get_revsort(msgmap)){
1017 if(thrd && thrd->top)
1018 start = mn_raw2m(msgmap, thrd->top);
1020 else{
1021 while(thrd){
1022 start = mn_raw2m(msgmap, thrd->rawno);
1023 if(thrd->branch)
1024 thrd = fetch_thread(stream, thrd->branch);
1025 else if(thrd->next)
1026 thrd = fetch_thread(stream, thrd->next);
1027 else
1028 thrd = NULL;
1035 new_msgno = next_sorted_flagged((F_UNDEL
1036 | F_UNSEEN
1037 | ((F_ON(F_TAB_TO_NEW,state))
1038 ? 0 : F_OR_FLAG)),
1039 stream, start, &flags);
1043 * If there weren't any unread messages left, OR there
1044 * aren't any messages at all, we may want to offer to
1045 * go on to the next folder...
1047 if(flags & NSF_FLAG_MATCH){
1048 mn_set_cur(msgmap, new_msgno);
1049 if(in_index != View)
1050 adjust_cur_to_visible(stream, msgmap);
1052 else{
1053 int in_inbox = sp_flagged(stream, SP_INBOX);
1055 if(state->context_current
1056 && ((NEWS_TEST(state->context_current)
1057 && context_isambig(state->cur_folder))
1058 || ((state->context_current->use & CNTXT_INCMNG)
1059 && (in_inbox
1060 || folder_index(state->cur_folder,
1061 state->context_current,
1062 FI_FOLDER) >= 0)))){
1063 char nextfolder[MAXPATH];
1064 MAILSTREAM *nextstream = NULL;
1065 long recent_cnt;
1066 int did_cancel = 0;
1068 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
1069 nextfolder[sizeof(nextfolder)-1] = '\0';
1070 while(1){
1071 if(!(next_folder(&nextstream, nextfolder, sizeof(nextfolder), nextfolder,
1072 state->context_current, &recent_cnt,
1073 F_ON(F_TAB_NO_CONFIRM,state)
1074 ? NULL : &did_cancel))){
1075 if(!in_inbox){
1076 static ESCKEY_S inbox_opt[] = {
1077 {'y', 'y', "Y", N_("Yes")},
1078 {'n', 'n', "N", N_("No")},
1079 {TAB, 'z', "Tab", N_("To Inbox")},
1080 {-1, 0, NULL, NULL}
1083 if(F_ON(F_RET_INBOX_NO_CONFIRM,state))
1084 ret = 'y';
1085 else{
1086 /* TRANSLATORS: this is a question, with some information followed
1087 by Return to INBOX? */
1088 if(state->context_current->use&CNTXT_INCMNG)
1089 snprintf(prompt, sizeof(prompt), _("No more incoming folders. Return to \"%s\"? "), state->inbox_name);
1090 else
1091 snprintf(prompt, sizeof(prompt), _("No more news groups. Return to \"%s\"? "), state->inbox_name);
1093 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1094 inbox_opt, 'y', 'x',
1095 NO_HELP, RB_NORM);
1099 * 'z' is a synonym for 'y'. It is not 'y'
1100 * so that it isn't displayed as a default
1101 * action with square-brackets around it
1102 * in the keymenu...
1104 if(ret == 'y' || ret == 'z'){
1105 visit_folder(state, state->inbox_name,
1106 state->context_current,
1107 NULL, DB_INBOXWOCNTXT);
1108 a_changed = TRUE;
1111 else if (did_cancel)
1112 cmd_cancelled(NULL);
1113 else{
1114 if(state->context_current->use&CNTXT_INCMNG)
1115 q_status_message(SM_ORDER, 0, 2, _("No more incoming folders"));
1116 else
1117 q_status_message(SM_ORDER, 0, 2, _("No more news groups"));
1120 break;
1124 #define CNTLEN 80
1125 char *front, type[80], cnt[CNTLEN], fbuf[MAX_SCREEN_COLS/2+1];
1126 int rbspace, avail, need, take_back;
1129 * View_next_
1130 * Incoming_folder_ or news_group_ or folder_ or group_
1131 * "foldername"
1132 * _(13 recent) or _(some recent) or nothing
1133 * ?_
1135 front = "View next";
1136 strncpy(type,
1137 (state->context_current->use & CNTXT_INCMNG)
1138 ? "Incoming folder" : "news group",
1139 sizeof(type));
1140 type[sizeof(type)-1] = '\0';
1141 snprintf(cnt, sizeof(cnt), " (%.*s %s)", CNTLEN-20,
1142 recent_cnt ? long2string(recent_cnt) : "some",
1143 F_ON(F_TAB_USES_UNSEEN, ps_global)
1144 ? "unseen" : "recent");
1145 cnt[sizeof(cnt)-1] = '\0';
1148 * Space reserved for radio_buttons call.
1149 * If we make this 3 then radio_buttons won't mess
1150 * with the prompt. If we make it 2, then we get
1151 * one more character to use but radio_buttons will
1152 * cut off the last character of our prompt, which is
1153 * ok because it is a space.
1155 rbspace = 2;
1156 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols
1157 : 80;
1158 need = strlen(front)+1 + strlen(type)+1 +
1159 + strlen(nextfolder)+2 + strlen(cnt) +
1160 2 + rbspace;
1161 if(avail < need){
1162 take_back = strlen(type);
1163 strncpy(type,
1164 (state->context_current->use & CNTXT_INCMNG)
1165 ? "folder" : "group", sizeof(type));
1166 take_back -= strlen(type);
1167 need -= take_back;
1168 if(avail < need){
1169 need -= strlen(cnt);
1170 cnt[0] = '\0';
1173 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1174 snprintf(prompt, sizeof(prompt), "%.*s %.*s \"%.*s\"%.*s? ",
1175 (MAX_SCREEN_COLS+1)/8, front,
1176 (MAX_SCREEN_COLS+1)/8, type,
1177 (MAX_SCREEN_COLS+1)/2,
1178 short_str(nextfolder, fbuf, sizeof(fbuf),
1179 strlen(nextfolder) -
1180 ((need>avail) ? (need-avail) : 0),
1181 MidDots),
1182 (MAX_SCREEN_COLS+1)/8, cnt);
1183 prompt[sizeof(prompt)-1] = '\0';
1187 * When help gets added, this'll have to become
1188 * a loop like the rest...
1190 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
1191 static ESCKEY_S next_opt[] = {
1192 {'y', 'y', "Y", N_("Yes")},
1193 {'n', 'n', "N", N_("No")},
1194 {TAB, 'n', "Tab", N_("NextNew")},
1195 {-1, 0, NULL, NULL}
1198 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1199 next_opt, 'y', 'x', NO_HELP,
1200 RB_NORM);
1201 if(ret == 'x'){
1202 cmd_cancelled(NULL);
1203 break;
1206 else
1207 ret = 'y';
1209 if(ret == 'y'){
1210 if(nextstream && sp_dead_stream(nextstream))
1211 nextstream = NULL;
1213 visit_folder(state, nextfolder,
1214 state->context_current, nextstream,
1215 DB_FROMTAB);
1216 /* visit_folder takes care of nextstream */
1217 nextstream = NULL;
1218 a_changed = TRUE;
1219 break;
1223 if(nextstream)
1224 pine_mail_close(nextstream);
1226 else
1227 any_messages(NULL,
1228 (mn_get_total(msgmap) > 0L)
1229 ? IS_NEWS(stream) ? "more undeleted" : "more new"
1230 : NULL,
1231 NULL);
1234 get_out:
1236 break;
1239 /*------- Zoom -----------*/
1240 case MC_ZOOM :
1242 * Right now the way zoom is implemented is sort of silly.
1243 * There are two per-message flags where just one and a
1244 * global "zoom mode" flag to suppress messages from the index
1245 * should suffice.
1247 if(any_messages(msgmap, NULL, "to Zoom on")){
1248 if(unzoom_index(state, stream, msgmap)){
1249 dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
1250 q_status_message(SM_ORDER,0,2, _("Index Zoom Mode is now off"));
1252 else if((i = zoom_index(state, stream, msgmap, MN_SLCT)) != 0){
1253 if(any_lflagged(msgmap, MN_HIDE)){
1254 dprint((4,"\n\n ---- Entering ZOOM mode ----\n"));
1255 q_status_message4(SM_ORDER, 0, 2,
1256 _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"),
1257 THRD_INDX() ? "" : comatose(i),
1258 THRD_INDX() ? "" : " ",
1259 THRD_INDX() ? _("threads") : _("message"),
1260 THRD_INDX() ? "" : plural(i));
1262 else
1263 q_status_message(SM_ORDER, 0, 2,
1264 _("All messages selected, so not entering Index Zoom Mode"));
1266 else
1267 any_messages(NULL, "selected", "to Zoom on");
1270 break;
1273 /*---------- print message on paper ----------*/
1274 case MC_PRINTMSG :
1275 if(any_messages(msgmap, NULL, "to print"))
1276 (void) cmd_print(state, msgmap, MCMD_NONE, in_index);
1278 break;
1281 /*---------- Take Address ----------*/
1282 case MC_TAKE :
1283 if(F_ON(F_ENABLE_ROLE_TAKE, state) ||
1284 any_messages(msgmap, NULL, "to Take address from"))
1285 (void) cmd_take_addr(state, msgmap, MCMD_NONE);
1287 break;
1290 /*---------- Save Message ----------*/
1291 case MC_SAVE :
1292 if(any_messages(msgmap, NULL, "to Save"))
1293 (void) cmd_save(state, stream, msgmap, MCMD_NONE, in_index);
1295 break;
1298 /*---------- Export message ----------*/
1299 case MC_EXPORT :
1300 if(any_messages(msgmap, NULL, "to Export")){
1301 (void) cmd_export(state, msgmap, question_line, MCMD_NONE);
1302 state->mangled_footer = 1;
1305 break;
1308 /*---------- Expunge ----------*/
1309 case MC_EXPUNGE :
1310 (void) cmd_expunge(state, stream, msgmap, MCMD_NONE);
1311 break;
1314 /*------- Unexclude -----------*/
1315 case MC_UNEXCLUDE :
1316 if(!(IS_NEWS(stream) && stream->rdonly)){
1317 q_status_message(SM_ORDER, 0, 3,
1318 _("Unexclude not available for mail folders"));
1320 else if(any_lflagged(msgmap, MN_EXLD)){
1321 SEARCHPGM *pgm;
1322 long i;
1323 int exbits;
1326 * Since excluded means "hidden deleted" and "killed",
1327 * the count should reflect the former.
1329 pgm = mail_newsearchpgm();
1330 pgm->deleted = 1;
1331 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
1332 for(i = 1L, del_count = 0L; i <= stream->nmsgs; i++)
1333 if((mc = mail_elt(stream, i)) && mc->searched
1334 && get_lflag(stream, NULL, i, MN_EXLD)
1335 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
1336 && (exbits & MSG_EX_FILTERED)))
1337 del_count++;
1339 if(del_count > 0L){
1340 state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1341 snprintf(prompt, sizeof(prompt), "UNexclude %ld message%s in %.*s", del_count,
1342 plural(del_count), MAX_SCREEN_COLS+1-40,
1343 pretty_fn(state->cur_folder));
1344 prompt[sizeof(prompt)-1] = '\0';
1345 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
1346 || (F_ON(F_AUTO_EXPUNGE, state)
1347 && (state->context_current
1348 && (state->context_current->use & CNTXT_INCMNG))
1349 && context_isambig(state->cur_folder))
1350 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
1351 long save_cur_rawno;
1352 int were_viewing_a_thread;
1354 save_cur_rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
1355 were_viewing_a_thread = (THREADING()
1356 && sp_viewing_a_thread(stream));
1358 if(msgno_include(stream, msgmap, MI_NONE)){
1359 clear_index_cache(stream, 0);
1361 if(stream && stream->spare)
1362 erase_threading_info(stream, msgmap);
1364 refresh_sort(stream, msgmap, SRT_NON);
1367 if(were_viewing_a_thread){
1368 if(save_cur_rawno > 0L)
1369 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1371 if(view_thread(state, stream, msgmap, 1) && current_index_state)
1372 msgmap->top_after_thrd = current_index_state->msg_at_top;
1375 if(save_cur_rawno > 0L)
1376 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1378 state->mangled_screen = 1;
1379 q_status_message2(SM_ORDER, 0, 4,
1380 "%s message%s UNexcluded",
1381 long2string(del_count),
1382 plural(del_count));
1384 if(in_index != View)
1385 adjust_cur_to_visible(stream, msgmap);
1387 else
1388 any_messages(NULL, NULL, "UNexcluded");
1390 else
1391 any_messages(NULL, "excluded", "to UNexclude");
1393 else
1394 any_messages(NULL, "excluded", "to UNexclude");
1396 break;
1399 /*------- Make Selection -----------*/
1400 case MC_SELECT :
1401 if(any_messages(msgmap, NULL, "to Select")){
1402 if(aggregate_select(state, msgmap, question_line, in_index) == 0
1403 && (in_index == MsgIndx || in_index == ThrdIndx)
1404 && F_ON(F_AUTO_ZOOM, state)
1405 && any_lflagged(msgmap, MN_SLCT) > 0L
1406 && !any_lflagged(msgmap, MN_HIDE))
1407 (void) zoom_index(state, stream, msgmap, MN_SLCT);
1410 break;
1413 /*------- Toggle Current Message Selection State -----------*/
1414 case MC_SELCUR :
1415 if(any_messages(msgmap, NULL, NULL)){
1416 if((select_by_current(state, msgmap, in_index)
1417 || (F_OFF(F_UNSELECT_WONT_ADVANCE, state)
1418 && !any_lflagged(msgmap, MN_HIDE)))
1419 && (i = mn_get_cur(msgmap)) < mn_get_total(msgmap)){
1420 /* advance current */
1421 mn_inc_cur(stream, msgmap,
1422 (in_index == View && THREADING()
1423 && sp_viewing_a_thread(stream))
1424 ? MH_THISTHD
1425 : (in_index == View)
1426 ? MH_ANYTHD : MH_NONE);
1430 break;
1433 /*------- Apply command -----------*/
1434 case MC_APPLY :
1435 if(any_messages(msgmap, NULL, NULL)){
1436 if(any_lflagged(msgmap, MN_SLCT) > 0L){
1437 if(apply_command(state, stream, msgmap, 0,
1438 AC_NONE, question_line)){
1439 if(F_ON(F_AUTO_UNSELECT, state)){
1440 agg_select_all(stream, msgmap, NULL, 0);
1441 unzoom_index(state, stream, msgmap);
1443 else if(F_ON(F_AUTO_UNZOOM, state))
1444 unzoom_index(state, stream, msgmap);
1447 else
1448 any_messages(NULL, NULL, "to Apply command to. Try \"Select\"");
1451 break;
1454 /*-------- Sort command -------*/
1455 case MC_SORT :
1457 int were_threading = THREADING();
1458 SortOrder sort = mn_get_sort(msgmap);
1459 int rev = mn_get_revsort(msgmap);
1461 dprint((1,"MAIL_CMD: sort\n"));
1462 if(select_sort(state, question_line, &sort, &rev)){
1463 /* $ command reinitializes threading collapsed/expanded info */
1464 if(SORT_IS_THREADED(msgmap) && !SEP_THRDINDX())
1465 erase_threading_info(stream, msgmap);
1467 if(ps_global && ps_global->ttyo){
1468 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
1469 ps_global->mangled_footer = 1;
1472 sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN);
1475 state->mangled_footer = 1;
1478 * We've changed whether we are threading or not so we need to
1479 * exit the index and come back in so that we switch between the
1480 * thread index and the regular index. Sort_folder will have
1481 * reset viewing_a_thread if necessary.
1483 if(SEP_THRDINDX()
1484 && ((!were_threading && THREADING())
1485 || (were_threading && !THREADING()))){
1486 state->next_screen = mail_index_screen;
1487 state->mangled_screen = 1;
1491 break;
1494 /*------- Toggle Full Headers -----------*/
1495 case MC_FULLHDR :
1496 state->full_header++;
1497 if(state->full_header == 1){
1498 if(!(state->quote_suppression_threshold
1499 && (state->some_quoting_was_suppressed || in_index != View)))
1500 state->full_header++;
1502 else if(state->full_header > 2)
1503 state->full_header = 0;
1505 switch(state->full_header){
1506 case 0:
1507 q_status_message(SM_ORDER, 0, 3,
1508 _("Display of full headers is now off."));
1509 break;
1511 case 1:
1512 q_status_message1(SM_ORDER, 0, 3,
1513 _("Quotes displayed, use %s to see full headers"),
1514 F_ON(F_USE_FK, state) ? "F9" : "H");
1515 break;
1517 case 2:
1518 q_status_message(SM_ORDER, 0, 3,
1519 _("Display of full headers is now on."));
1520 break;
1524 a_changed = TRUE;
1525 break;
1528 case MC_TOGGLE :
1529 a_changed = TRUE;
1530 break;
1533 #ifdef SMIME
1534 /*------- Try to decrypt message -----------*/
1535 case MC_DECRYPT:
1536 if(state->smime && state->smime->need_passphrase)
1537 smime_get_passphrase();
1539 a_changed = TRUE;
1540 break;
1542 case MC_SECURITY:
1543 smime_info_screen(state);
1544 break;
1545 #endif
1548 /*------- Bounce -----------*/
1549 case MC_BOUNCE :
1550 (void) cmd_bounce(state, msgmap, MCMD_NONE, NULL);
1551 break;
1554 /*------- Flag -----------*/
1555 case MC_FLAG :
1556 dprint((4, "\n - flag message -\n"));
1557 (void) cmd_flag(state, msgmap, MCMD_NONE);
1558 break;
1561 /*------- Pipe message -----------*/
1562 case MC_PIPE :
1563 (void) cmd_pipe(state, msgmap, MCMD_NONE);
1564 break;
1567 /*--------- Default, unknown command ----------*/
1568 default:
1569 alpine_panic("Unexpected command case");
1570 break;
1573 return((a_changed || mn_get_cur(msgmap) != old_msgno) ? 1 : 0);
1578 /*----------------------------------------------------------------------
1579 Map some of the special characters into sensible strings for human
1580 consumption.
1581 c is a UCS-4 character!
1582 ----*/
1583 char *
1584 pretty_command(UCS c)
1586 static char buf[10];
1587 char *s;
1589 buf[0] = '\0';
1590 s = buf;
1592 switch(c){
1593 case ' ' : s = "SPACE"; break;
1594 case '\033' : s = "ESC"; break;
1595 case '\177' : s = "DEL"; break;
1596 case ctrl('I') : s = "TAB"; break;
1597 case ctrl('J') : s = "LINEFEED"; break;
1598 case ctrl('M') : s = "RETURN"; break;
1599 case ctrl('Q') : s = "XON"; break;
1600 case ctrl('S') : s = "XOFF"; break;
1601 case KEY_UP : s = "Up Arrow"; break;
1602 case KEY_DOWN : s = "Down Arrow"; break;
1603 case KEY_RIGHT : s = "Right Arrow"; break;
1604 case KEY_LEFT : s = "Left Arrow"; break;
1605 case KEY_PGUP : s = "Prev Page"; break;
1606 case KEY_PGDN : s = "Next Page"; break;
1607 case KEY_HOME : s = "Home"; break;
1608 case KEY_END : s = "End"; break;
1609 case KEY_DEL : s = "Delete"; break; /* Not necessary DEL! */
1610 case KEY_JUNK : s = "Junk!"; break;
1611 case BADESC : s = "Bad Esc"; break;
1612 case NO_OP_IDLE : s = "NO_OP_IDLE"; break;
1613 case NO_OP_COMMAND : s = "NO_OP_COMMAND"; break;
1614 case KEY_RESIZE : s = "KEY_RESIZE"; break;
1615 case KEY_UTF8 : s = "KEY_UTF8"; break;
1616 case KEY_MOUSE : s = "KEY_MOUSE"; break;
1617 case KEY_SCRLUPL : s = "KEY_SCRLUPL"; break;
1618 case KEY_SCRLDNL : s = "KEY_SCRLDNL"; break;
1619 case KEY_SCRLTO : s = "KEY_SCRLTO"; break;
1620 case KEY_XTERM_MOUSE : s = "KEY_XTERM_MOUSE"; break;
1621 case KEY_DOUBLE_ESC : s = "KEY_DOUBLE_ESC"; break;
1622 case CTRL_KEY_UP : s = "Ctrl Up Arrow"; break;
1623 case CTRL_KEY_DOWN : s = "Ctrl Down Arrow"; break;
1624 case CTRL_KEY_RIGHT : s = "Ctrl Right Arrow"; break;
1625 case CTRL_KEY_LEFT : s = "Ctrl Left Arrow"; break;
1626 case PF1 :
1627 case PF2 :
1628 case PF3 :
1629 case PF4 :
1630 case PF5 :
1631 case PF6 :
1632 case PF7 :
1633 case PF8 :
1634 case PF9 :
1635 case PF10 :
1636 case PF11 :
1637 case PF12 :
1638 snprintf(s = buf, sizeof(buf), "F%ld", (long) (c - PF1 + 1));
1639 break;
1641 default:
1642 if(c < ' ' || (c >= 0x80 && c < 0xA0)){
1643 char d;
1644 int c1;
1646 c1 = (c >= 0x80);
1647 d = (c & 0x1f) + 'A' - 1;
1648 snprintf(s = buf, sizeof(buf), "%c%c", c1 ? '~' : '^', d);
1650 else{
1651 memset(buf, 0, sizeof(buf));
1652 utf8_put((unsigned char *) buf, (unsigned long) c);
1655 break;
1658 return(s);
1662 /*----------------------------------------------------------------------
1663 Complain about bogus input
1665 Args: ch -- input command to complain about
1666 help -- string indicating where to get help
1668 ----*/
1669 void
1670 bogus_command(UCS cmd, char *help)
1672 if(cmd == ctrl('Q') || cmd == ctrl('S'))
1673 q_status_message1(SM_ASYNC, 0, 2,
1674 "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.",
1675 pretty_command(cmd));
1676 else if(cmd == KEY_JUNK)
1677 q_status_message3(SM_ORDER, 0, 2,
1678 "Invalid key pressed.%s%s%s",
1679 (help) ? " Use " : "",
1680 (help) ? help : "",
1681 (help) ? " for help" : "");
1682 else
1683 q_status_message4(SM_ORDER, 0, 2,
1684 "Command \"%s\" not defined for this screen.%s%s%s",
1685 pretty_command(cmd),
1686 (help) ? " Use " : "",
1687 (help) ? help : "",
1688 (help) ? " for help" : "");
1692 void
1693 bogus_utf8_command(char *cmd, char *help)
1695 q_status_message4(SM_ORDER, 0, 2,
1696 "Command \"%s\" not defined for this screen.%s%s%s",
1697 cmd ? cmd : "?",
1698 (help) ? " Use " : "",
1699 (help) ? help : "",
1700 (help) ? " for help" : "");
1704 /*----------------------------------------------------------------------
1705 Execute FLAG message command
1707 Args: state -- Various satate info
1708 msgmap -- map of c-client to local message numbers
1710 Result: with side effect of "current" message FLAG flag set or UNset
1712 ----*/
1714 cmd_flag(struct pine *state, MSGNO_S *msgmap, int aopt)
1716 char *flagit, *seq, *screen_text[20], **exp, **p, *answer = NULL;
1717 char *keyword_array[2];
1718 int user_defined_flags = 0, mailbox_flags = 0;
1719 int directly_to_maint_screen = 0;
1720 long unflagged, flagged, flags, rawno;
1721 MESSAGECACHE *mc = NULL;
1722 KEYWORD_S *kw;
1723 int i, cnt, is_set, trouble = 0, rv = 0;
1724 size_t len;
1725 struct flag_table *fp, *ftbl = NULL;
1726 struct flag_screen flag_screen;
1727 static char *flag_screen_text1[] = {
1728 N_(" Set desired flags for current message below. An 'X' means set"),
1729 N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."),
1730 NULL
1733 static char *flag_screen_text2[] = {
1734 N_(" Set desired flags below for selected messages. A '?' means to"),
1735 N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"),
1736 N_(" to unset it. Use the \"Return\" key to toggle, and choose"),
1737 N_(" \"Exit\" when finished."),
1738 NULL
1741 static struct flag_table default_ftbl[] = {
1742 {N_("Important"), h_flag_important, F_FLAG, 0, 0, NULL, NULL},
1743 {N_("New"), h_flag_new, F_SEEN, 0, 0, NULL, NULL},
1744 {N_("Answered"), h_flag_answered, F_ANS, 0, 0, NULL, NULL},
1745 {N_("Forwarded"), h_flag_forwarded, F_FWD, 0, 0, NULL, NULL},
1746 {N_("Deleted"), h_flag_deleted, F_DEL, 0, 0, NULL, NULL},
1747 {NULL, NO_HELP, 0, 0, 0, NULL, NULL}
1750 /* Only check for dead stream for now. Should check permanent flags
1751 * eventually
1753 if(!(any_messages(msgmap, NULL, "to Flag") && can_set_flag(state, "flag", 1)))
1754 return rv;
1756 if(sp_io_error_on_stream(state->mail_stream)){
1757 sp_set_io_error_on_stream(state->mail_stream, 0);
1758 pine_mail_check(state->mail_stream); /* forces write */
1759 return rv;
1762 go_again:
1763 answer = NULL;
1764 user_defined_flags = 0;
1765 mailbox_flags = 0;
1766 mc = NULL;
1767 trouble = 0;
1768 ftbl = NULL;
1770 /* count how large ftbl will be */
1771 for(cnt = 0; default_ftbl[cnt].name; cnt++)
1774 /* add user flags */
1775 for(kw = ps_global->keywords; kw; kw = kw->next){
1776 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick)) || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1777 user_defined_flags++;
1778 cnt++;
1783 * Add mailbox flags that aren't user-defined flags.
1784 * Don't consider it if it matches either one of our defined
1785 * keywords or one of our defined nicknames for a keyword.
1787 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1788 char *q;
1790 q = stream_to_user_flag_name(state->mail_stream, i);
1791 if(q && q[0]){
1792 for(kw = ps_global->keywords; kw; kw = kw->next){
1793 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1794 break;
1798 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1799 mailbox_flags++;
1800 cnt++;
1804 cnt += (user_defined_flags ? 2 : 0) + (mailbox_flags ? 2 : 0);
1806 /* set up ftbl, first the system flags */
1807 ftbl = (struct flag_table *) fs_get((cnt+1) * sizeof(*ftbl));
1808 memset(ftbl, 0, (cnt+1) * sizeof(*ftbl));
1809 for(i = 0, fp = ftbl; default_ftbl[i].name; i++, fp++){
1810 fp->name = cpystr(default_ftbl[i].name);
1811 fp->help = default_ftbl[i].help;
1812 fp->flag = default_ftbl[i].flag;
1813 fp->set = default_ftbl[i].set;
1814 fp->ukn = default_ftbl[i].ukn;
1817 if(user_defined_flags){
1818 fp->flag = F_COMMENT;
1819 fp->name = cpystr("");
1820 fp++;
1821 fp->flag = F_COMMENT;
1822 len = strlen(_("User-defined Keywords from Setup/Config"));
1823 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1824 snprintf(fp->name, len+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config"));
1825 fp++;
1828 /* then the user-defined keywords */
1829 if(user_defined_flags)
1830 for(kw = ps_global->keywords; kw; kw = kw->next){
1831 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick))
1832 || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1833 fp->name = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
1834 fp->keyword = cpystr(kw->kw ? kw->kw : "");
1835 if(kw->nick && kw->kw){
1836 size_t l;
1838 l = strlen(kw->kw)+2;
1839 fp->comment = (char *) fs_get((l+1) * sizeof(char));
1840 snprintf(fp->comment, l+1, "(%.*s)", (int) strlen(kw->kw), kw->kw);
1841 fp->comment[l] = '\0';
1844 fp->help = h_flag_user_flag;
1845 fp->flag = F_KEYWORD;
1846 fp->set = 0;
1847 fp->ukn = 0;
1848 fp++;
1852 if(mailbox_flags){
1853 fp->flag = F_COMMENT;
1854 fp->name = cpystr("");
1855 fp++;
1856 fp->flag = F_COMMENT;
1857 len = strlen(_("Other keywords in the mailbox that are not user-defined"));
1858 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1859 snprintf(fp->name, len+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined"));
1860 fp++;
1863 /* then the extra mailbox-defined keywords */
1864 if(mailbox_flags)
1865 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1866 char *q;
1868 q = stream_to_user_flag_name(state->mail_stream, i);
1869 if(q && q[0]){
1870 for(kw = ps_global->keywords; kw; kw = kw->next){
1871 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1872 break;
1876 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1877 fp->name = cpystr(q);
1878 fp->keyword = cpystr(q);
1879 fp->help = h_flag_user_flag;
1880 fp->flag = F_KEYWORD;
1881 fp->set = 0;
1882 fp->ukn = 0;
1883 fp++;
1887 flag_screen.flag_table = &ftbl;
1888 flag_screen.explanation = screen_text;
1890 if(MCMD_ISAGG(aopt)){
1891 if(!pseudo_selected(ps_global->mail_stream, msgmap)){
1892 free_flag_table(&ftbl);
1893 return rv;
1896 exp = flag_screen_text2;
1897 for(fp = ftbl; fp->name; fp++){
1898 fp->set = CMD_FLAG_UNKN; /* set to unknown */
1899 fp->ukn = TRUE;
1902 else if(state->mail_stream
1903 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
1904 && rawno <= state->mail_stream->nmsgs
1905 && (mc = mail_elt(state->mail_stream, rawno))){
1906 exp = flag_screen_text1;
1907 for(fp = &ftbl[0]; fp->name; fp++){
1908 fp->ukn = 0;
1909 if(fp->flag == F_KEYWORD){
1910 /* see if this keyword is defined for this message */
1911 fp->set = CMD_FLAG_CLEAR;
1912 if(user_flag_is_set(state->mail_stream,
1913 rawno, fp->keyword))
1914 fp->set = CMD_FLAG_SET;
1916 else if(fp->flag == F_FWD){
1917 /* see if forwarded keyword is defined for this message */
1918 fp->set = CMD_FLAG_CLEAR;
1919 if(user_flag_is_set(state->mail_stream,
1920 rawno, FORWARDED_FLAG))
1921 fp->set = CMD_FLAG_SET;
1923 else if(fp->flag != F_COMMENT)
1924 fp->set = ((fp->flag == F_SEEN && !mc->seen)
1925 || (fp->flag == F_DEL && mc->deleted)
1926 || (fp->flag == F_FLAG && mc->flagged)
1927 || (fp->flag == F_ANS && mc->answered))
1928 ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
1931 else{
1932 q_status_message(SM_ORDER | SM_DING, 3, 4,
1933 _("Error accessing message data"));
1934 free_flag_table(&ftbl);
1935 return rv;
1938 if(directly_to_maint_screen)
1939 goto the_maint_screen;
1941 #ifdef _WINDOWS
1942 if (mswin_usedialog ()) {
1943 if (!os_flagmsgdialog (&ftbl[0])){
1944 free_flag_table(&ftbl);
1945 return rv;
1948 else
1949 #endif
1951 int use_maint_screen;
1952 int keyword_shortcut = 0;
1954 use_maint_screen = F_ON(F_FLAG_SCREEN_DFLT, ps_global);
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, preindex, 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;
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, 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, 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", 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", 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", dir, S_FILESEP, lfile);
3963 filename[sizeof(filename)-1] = '\0';
3964 while((ok = can_access(filename, ACCESS_EXISTS)) == 0 && errs == 0){
3965 char *ext;
3966 snprintf(filename, sizeof(filename), "%d", counter);
3967 if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + strlen(filename) + 2
3968 > sizeof(filename)){
3969 dprint((2,
3970 "FAILED Att Export: name too long: %s\n",
3971 dir, S_FILESEP, lfile));
3972 errs++;
3973 continue;
3975 if((ext = strrchr(lfile, '.')) != NULL)
3976 *ext = '\0';
3977 snprintf(filename, sizeof(filename), "%s%s%s%s%d%s%s",
3978 dir, S_FILESEP, lfile,
3979 ext ? "_" : "", counter++, ext ? "." : "", ext ? ext+1 : "");
3980 filename[sizeof(filename)-1] = '\0';
3983 if(write_attachment_to_file(state->mail_stream, rawno,
3984 a, GER_NONE, filename) == 1)
3985 saved++;
3986 else
3987 errs++;
3990 if(errs){
3991 if(saved)
3992 q_status_message1(SM_ORDER, 3, 3,
3993 "Errors saving some attachments, %s attachments saved",
3994 long2string((long) saved));
3995 else
3996 q_status_message(SM_ORDER, 3, 3,
3997 _("Problems saving attachments"));
3999 else{
4000 if(saved)
4001 q_status_message2(SM_ORDER, 0, 3,
4002 /* TRANSLATORS: Saved <how many> attachments to <directory name> */
4003 _("Saved %s attachments to %s"),
4004 long2string((long) saved), dir);
4005 else
4006 q_status_message(SM_ORDER, 3, 3, _("No attachments to save"));
4009 else if(mn_total_cur(msgmap) > 1L)
4010 q_status_message4(SM_ORDER,0,3,
4011 "%s message%s %s to file \"%s\"",
4012 long2string(count), plural(count),
4013 rflags & GER_OVER
4014 ? "overwritten"
4015 : rflags & GER_APPEND ? "appended" : "exported",
4016 filename);
4017 else
4018 q_status_message3(SM_ORDER,0,3,
4019 "Message %s %s to file \"%s\"",
4020 long2string(mn_get_cur(msgmap)),
4021 rflags & GER_OVER
4022 ? "overwritten"
4023 : rflags & GER_APPEND ? "appended" : "exported",
4024 filename);
4025 rv++;
4028 fini:
4029 if(MCMD_ISAGG(aopt))
4030 restore_selected(msgmap);
4032 return rv;
4037 * Ask user what file to export to. Export from srcstore to that file.
4039 * Args ps -- pine struct
4040 * srctext -- pointer to source text
4041 * srctype -- type of that source text
4042 * prompt_msg -- see get_export_filename
4043 * lister_msg -- "
4045 * Returns: != 0 : error
4046 * 0 : ok
4049 simple_export(struct pine *ps, void *srctext, SourceType srctype, char *prompt_msg, char *lister_msg)
4051 int r = 1, rflags = GER_NONE;
4052 char filename[MAXPATH+1], full_filename[MAXPATH+1];
4053 STORE_S *store = NULL;
4054 struct variable *vars = ps ? ps->vars : NULL;
4055 static HISTORY_S *history = NULL;
4056 static ESCKEY_S simple_export_opts[] = {
4057 {ctrl('T'), 10, "^T", N_("To Files")},
4058 {-1, 0, NULL, NULL},
4059 {-1, 0, NULL, NULL}};
4061 if(F_ON(F_ENABLE_TAB_COMPLETE,ps)){
4062 simple_export_opts[r].ch = ctrl('I');
4063 simple_export_opts[r].rval = 11;
4064 simple_export_opts[r].name = "TAB";
4065 simple_export_opts[r].label = N_("Complete");
4068 if(!srctext){
4069 q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
4070 r = -3;
4071 goto fini;
4074 simple_export_opts[++r].ch = -1;
4075 filename[0] = '\0';
4076 full_filename[0] = '\0';
4078 r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename),
4079 prompt_msg, lister_msg, simple_export_opts, &rflags,
4080 -FOOTER_ROWS(ps), GE_IS_EXPORT, &history);
4082 if(r < 0)
4083 goto fini;
4084 else if(!full_filename[0]){
4085 r = -1;
4086 goto fini;
4089 dprint((5, "Opening file \"%s\" for export\n",
4090 full_filename ? full_filename : "?"));
4092 if((store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
4093 char *pipe_err;
4094 gf_io_t pc, gc;
4096 gf_set_so_writec(&pc, store);
4097 gf_set_readc(&gc, srctext, (srctype == CharStar)
4098 ? strlen((char *)srctext)
4099 : 0L,
4100 srctype, 0);
4101 gf_filter_init();
4102 if((pipe_err = gf_pipe(gc, pc)) != NULL){
4103 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4104 /* TRANSLATORS: Problem saving to <filename>: <error text> */
4105 _("Problem saving to \"%s\": %s"),
4106 filename, pipe_err);
4107 r = -3;
4109 else
4110 r = 0;
4112 gf_clear_so_writec(store);
4113 if(so_give(&store)){
4114 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4115 _("Problem saving to \"%s\": %s"),
4116 filename, error_description(errno));
4117 r = -3;
4120 else{
4121 q_status_message2(SM_ORDER | SM_DING, 3, 4,
4122 _("Error opening file \"%s\" for export: %s"),
4123 full_filename, error_description(errno));
4124 r = -3;
4127 fini:
4128 switch(r){
4129 case 0:
4130 /* overloading full_filename */
4131 snprintf(full_filename, sizeof(full_filename), "%c%s",
4132 (prompt_msg && prompt_msg[0])
4133 ? (islower((unsigned char)prompt_msg[0])
4134 ? toupper((unsigned char)prompt_msg[0]) : prompt_msg[0])
4135 : 'T',
4136 (prompt_msg && prompt_msg[0]) ? prompt_msg+1 : "ext");
4137 full_filename[sizeof(full_filename)-1] = '\0';
4138 q_status_message3(SM_ORDER,0,2,"%s %s to \"%s\"",
4139 full_filename,
4140 rflags & GER_OVER
4141 ? "overwritten"
4142 : rflags & GER_APPEND ? "appended" : "exported",
4143 filename);
4144 break;
4146 case -1:
4147 cmd_cancelled("Export");
4148 break;
4150 case -2:
4151 q_status_message1(SM_ORDER, 0, 2,
4152 _("Can't export to file outside of %s"), VAR_OPER_DIR);
4153 break;
4156 ps->mangled_footer = 1;
4157 return(r);
4162 * Ask user what file to export to.
4164 * filename -- On input, this is the filename to start with. On exit,
4165 * this is the filename chosen. (but this isn't used)
4166 * deefault -- This is the default value if user hits return. The
4167 * prompt will have [deefault] added to it automatically.
4168 * full_filename -- This is the full filename on exit.
4169 * len -- Minimum length of _both_ filename and full_filename.
4170 * prompt_msg -- Message to insert in prompt.
4171 * lister_msg -- Message to insert in file_lister.
4172 * opts -- Key options.
4173 * There is a tangled relationship between the callers
4174 * and this routine as far as opts are concerned. Some
4175 * of the opts are handled here. In particular, r == 3,
4176 * r == 10, r == 11, and r == 13 are all handled here.
4177 * Don't use those values unless you want what happens
4178 * here. r == 12 and others are handled by the caller.
4179 * rflags -- Return flags
4180 * GER_OVER - overwrite of existing file
4181 * GER_APPEND - append of existing file
4182 * else file did not exist before
4184 * GER_ALLPARTS - AllParts toggle was turned on
4186 * qline -- Command line to prompt on.
4187 * flags -- Logically OR'd flags
4188 * GE_IS_EXPORT - The command was an Export command
4189 * so the prompt should include
4190 * EXPORT:.
4191 * GE_SEQ_SENSITIVE - The command that got us here is
4192 * sensitive to sequence number changes
4193 * caused by unsolicited expunges.
4194 * GE_NO_APPEND - We will not allow append to an
4195 * existing file, only removal of the
4196 * file if it exists.
4197 * GE_IS_IMPORT - We are selecting for reading.
4198 * No overwriting or checking for
4199 * existence at all. Don't use this
4200 * together with GE_NO_APPEND.
4201 * GE_ALLPARTS - Turn on AllParts toggle.
4202 * GE_BINARY - Turn on Binary toggle.
4204 * Returns: -1 cancelled
4205 * -2 prohibited by VAR_OPER_DIR
4206 * -3 other error, already reported here
4207 * 0 ok
4208 * 12 user chose 12 command from opts
4211 get_export_filename(struct pine *ps, char *filename, char *deefault,
4212 char *full_filename, size_t len, char *prompt_msg,
4213 char *lister_msg, ESCKEY_S *optsarg, int *rflags,
4214 int qline, int flags, HISTORY_S **history)
4216 char dir[MAXPATH+1], dir2[MAXPATH+1], orig_dir[MAXPATH+1];
4217 char precolon[MAXPATH+1], postcolon[MAXPATH+1];
4218 char filename2[MAXPATH+1], tmp[MAXPATH+1], *fn, *ill;
4219 int l, i, ku = -1, kp = -1, r, fatal, homedir = 0, was_abs_path=0, avail, ret = 0;
4220 int allparts = 0, binary = 0;
4221 char prompt_buf[400];
4222 char def[500];
4223 ESCKEY_S *opts = NULL;
4224 struct variable *vars = ps->vars;
4225 static HISTORY_S *dir_hist = NULL;
4226 static char *last;
4227 int pos, hist_len = 0;
4230 /* we will fake a history with the ps_global->VAR_HISTORY variable
4231 * We fake that we combine this variable into a history variable
4232 * by stacking VAR_HISTORY on top of dir_hist. We keep track of this
4233 * by looking at the variable pos.
4235 if(ps_global->VAR_HISTORY != NULL)
4236 for(hist_len = 0; ps_global->VAR_HISTORY[hist_len]
4237 && ps_global->VAR_HISTORY[hist_len][0]; hist_len++)
4240 pos = hist_len + items_in_hist(dir_hist);
4242 if(flags & GE_ALLPARTS || history || dir_hist){
4244 * Copy the opts and add one to the end of the list.
4246 for(i = 0; optsarg[i].ch != -1; i++)
4249 if(dir_hist || hist_len > 0)
4250 i += 2;
4252 if(history)
4253 i += dir_hist || hist_len > 0 ? 2 : 4;
4255 if(flags & GE_ALLPARTS)
4256 i++;
4258 if(flags & GE_BINARY)
4259 i++;
4261 opts = (ESCKEY_S *) fs_get((i+1) * sizeof(*opts));
4262 memset(opts, 0, (i+1) * sizeof(*opts));
4264 for(i = 0; optsarg[i].ch != -1; i++){
4265 opts[i].ch = optsarg[i].ch;
4266 opts[i].rval = optsarg[i].rval;
4267 opts[i].name = optsarg[i].name; /* no need to make a copy */
4268 opts[i].label = optsarg[i].label; /* " */
4271 if(flags & GE_ALLPARTS){
4272 allparts = i;
4273 opts[i].ch = ctrl('P');
4274 opts[i].rval = 13;
4275 opts[i].name = "^P";
4276 /* TRANSLATORS: Export all attachment parts */
4277 opts[i++].label = N_("AllParts");
4280 if(flags & GE_BINARY){
4281 binary = i;
4282 opts[i].ch = ctrl('R');
4283 opts[i].rval = 15;
4284 opts[i].name = "^R";
4285 opts[i++].label = N_("Binary");
4288 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4289 SIZEOF_20KBUF, filename);
4290 #ifndef _WINDOWS
4291 /* In the Windows operating system we always return the UTF8 encoded name */
4292 if(strcmp(tmp_20k_buf, filename)){
4293 opts[i].ch = ctrl('N');
4294 opts[i].rval = 40;
4295 opts[i].name = "^N";
4296 opts[i++].label = "Name UTF8";
4298 #else
4299 strncpy(filename, tmp_20k_buf, len);
4300 filename[len-1] = '\0';
4301 #endif /* _WINDOWS */
4303 if(dir_hist || hist_len > 0){
4304 opts[i].ch = ctrl('Y');
4305 opts[i].rval = 32;
4306 opts[i].name = "";
4307 kp = i;
4308 opts[i++].label = "";
4310 opts[i].ch = ctrl('V');
4311 opts[i].rval = 33;
4312 opts[i].name = "";
4313 opts[i++].label = "";
4316 if(history){
4317 opts[i].ch = KEY_UP;
4318 opts[i].rval = 30;
4319 opts[i].name = "";
4320 ku = i;
4321 opts[i++].label = "";
4323 opts[i].ch = KEY_DOWN;
4324 opts[i].rval = 31;
4325 opts[i].name = "";
4326 opts[i++].label = "";
4329 opts[i].ch = -1;
4331 if(history)
4332 init_hist(history, HISTSIZE);
4333 init_hist(&dir_hist, HISTSIZE); /* reset history to the end */
4335 else
4336 opts = optsarg;
4338 if(rflags)
4339 *rflags = GER_NONE;
4341 if(F_ON(F_USE_CURRENT_DIR, ps))
4342 dir[0] = '\0';
4343 else if(VAR_OPER_DIR){
4344 strncpy(dir, VAR_OPER_DIR, sizeof(dir));
4345 dir[sizeof(dir)-1] = '\0';
4347 #if defined(DOS) || defined(OS2)
4348 else if(VAR_FILE_DIR){
4349 strncpy(dir, VAR_FILE_DIR, sizeof(dir));
4350 dir[sizeof(dir)-1] = '\0';
4352 #endif
4353 else{
4354 dir[0] = '~';
4355 dir[1] = '\0';
4356 homedir=1;
4358 strncpy(orig_dir, dir, sizeof(orig_dir));
4359 orig_dir[sizeof(orig_dir)-1] = '\0';
4361 postcolon[0] = '\0';
4362 strncpy(precolon, dir, sizeof(precolon));
4363 precolon[sizeof(precolon)-1] = '\0';
4364 if(deefault){
4365 strncpy(def, deefault, sizeof(def)-1);
4366 def[sizeof(def)-1] = '\0';
4367 removing_leading_and_trailing_white_space(def);
4369 else
4370 def[0] = '\0';
4372 avail = MAX(20, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - MIN_OPT_ENT_WIDTH;
4374 /*---------- Prompt the user for the file name -------------*/
4375 while(1){
4376 int oeflags;
4377 char dirb[50], fileb[50];
4378 int l1, l2, l3, l4, l5, needed;
4379 char *p, p1[100], p2[100], *p3, p4[100], p5[100];
4381 snprintf(p1, sizeof(p1), "%sCopy ",
4382 (flags & GE_IS_EXPORT) ? "EXPORT: " :
4383 (flags & GE_IS_IMPORT) ? "IMPORT: " : "SAVE: ");
4384 p1[sizeof(p1)-1] = '\0';
4385 l1 = strlen(p1);
4387 strncpy(p2, prompt_msg ? prompt_msg : "", sizeof(p2)-1);
4388 p2[sizeof(p2)-1] = '\0';
4389 l2 = strlen(p2);
4391 if(rflags && *rflags & GER_ALLPARTS)
4392 p3 = " (and atts)";
4393 else
4394 p3 = "";
4396 l3 = strlen(p3);
4398 snprintf(p4, sizeof(p4), " %s file%s%s",
4399 (flags & GE_IS_IMPORT) ? "from" : "to",
4400 is_absolute_path(filename) ? "" : " in ",
4401 is_absolute_path(filename) ? "" :
4402 (!dir[0] ? "current directory"
4403 : (dir[0] == '~' && !dir[1]) ? "home directory"
4404 : short_str(dir,dirb,sizeof(dirb),30,FrontDots)));
4405 p4[sizeof(p4)-1] = '\0';
4406 l4 = strlen(p4);
4408 snprintf(p5, sizeof(p5), "%s%s%s: ",
4409 *def ? " [" : "",
4410 *def ? short_str(def,fileb,sizeof(fileb),40,EndDots) : "",
4411 *def ? "]" : "");
4412 p5[sizeof(p5)-1] = '\0';
4413 l5 = strlen(p5);
4415 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4416 snprintf(p4, sizeof(p4), " %s file%s%s",
4417 (flags & GE_IS_IMPORT) ? "from" : "to",
4418 is_absolute_path(filename) ? "" : " in ",
4419 is_absolute_path(filename) ? "" :
4420 (!dir[0] ? "current dir"
4421 : (dir[0] == '~' && !dir[1]) ? "home dir"
4422 : short_str(dir,dirb,sizeof(dirb),10,FrontDots)));
4423 p4[sizeof(p4)-1] = '\0';
4424 l4 = strlen(p4);
4427 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4428 snprintf(p5, sizeof(p5), "%s%s%s: ",
4429 *def ? " [" : "",
4430 *def ? short_str(def,fileb,sizeof(fileb),
4431 MAX(15,l5-5-needed),EndDots) : "",
4432 *def ? "]" : "");
4433 p5[sizeof(p5)-1] = '\0';
4434 l5 = strlen(p5);
4437 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l2 > 0){
4440 * 14 is about the shortest we can make this, because there are
4441 * fixed length strings of length 14 coming in here.
4443 p = short_str(prompt_msg, p2, sizeof(p2), MAX(14,l2-needed), FrontDots);
4444 if(p != p2){
4445 strncpy(p2, p, sizeof(p2)-1);
4446 p2[sizeof(p2)-1] = '\0';
4449 l2 = strlen(p2);
4452 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4453 strncpy(p1, "Copy ", sizeof(p1)-1);
4454 p1[sizeof(p1)-1] = '\0';
4455 l1 = strlen(p1);
4458 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4459 snprintf(p5, sizeof(p5), "%s%s%s: ",
4460 *def ? " [" : "",
4461 *def ? short_str(def,fileb, sizeof(fileb),
4462 MAX(10,l5-5-needed),EndDots) : "",
4463 *def ? "]" : "");
4464 p5[sizeof(p5)-1] = '\0';
4465 l5 = strlen(p5);
4468 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l3 > 0){
4469 if(needed <= l3 - strlen(" (+ atts)"))
4470 p3 = " (+ atts)";
4471 else if(needed <= l3 - strlen(" (atts)"))
4472 p3 = " (atts)";
4473 else if(needed <= l3 - strlen(" (+)"))
4474 p3 = " (+)";
4475 else if(needed <= l3 - strlen("+"))
4476 p3 = "+";
4477 else
4478 p3 = "";
4480 l3 = strlen(p3);
4483 snprintf(prompt_buf, sizeof(prompt_buf), "%s%s%s%s%s", p1, p2, p3, p4, p5);
4484 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4486 if(kp >= 0){
4487 if(items_in_hist(dir_hist) > 0 || hist_len > 0){ /* any directories */
4488 opts[kp].name = "^Y";
4489 opts[kp].label = "Prev Dir";
4490 opts[kp+1].name = "^V";
4491 opts[kp+1].label = "Next Dir";
4493 else{
4494 opts[kp].name = "";
4495 opts[kp].label = "";
4496 opts[kp+1].name = "";
4497 opts[kp+1].label = "";
4501 if(ku >= 0){
4502 if(items_in_hist(*history) > 0){
4503 opts[ku].name = HISTORY_UP_KEYNAME;
4504 opts[ku].label = HISTORY_KEYLABEL;
4505 opts[ku+1].name = HISTORY_DOWN_KEYNAME;
4506 opts[ku+1].label = HISTORY_KEYLABEL;
4508 else{
4509 opts[ku].name = "";
4510 opts[ku].label = "";
4511 opts[ku+1].name = "";
4512 opts[ku+1].label = "";
4516 oeflags = OE_APPEND_CURRENT |
4517 ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0);
4518 r = optionally_enter(filename, qline, 0, len, prompt_buf,
4519 opts, NO_HELP, &oeflags);
4521 dprint((2, "\n - export_filename = \"%s\", r = %d -\n", filename, r));
4522 /*--- Help ----*/
4523 if(r == 3){
4525 * Helps may not be right if you add another caller or change
4526 * things. Check it out.
4528 if(flags & GE_IS_IMPORT)
4529 helper(h_ge_import, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE);
4530 else if(flags & GE_ALLPARTS)
4531 helper(h_ge_allparts, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4532 else
4533 helper(h_ge_export, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4535 ps->mangled_screen = 1;
4537 continue;
4539 else if(r == 10 || r == 11){ /* Browser or File Completion */
4540 if(filename[0]=='~'){
4541 if(filename[1] == C_FILESEP && filename[2]!='\0'){
4542 precolon[0] = '~';
4543 precolon[1] = '\0';
4544 for(i=0; filename[i+2] != '\0' && i+2 < len-1; i++)
4545 filename[i] = filename[i+2];
4546 filename[i] = '\0';
4547 strncpy(dir, precolon, sizeof(dir)-1);
4548 dir[sizeof(dir)-1] = '\0';
4550 else if(filename[1]=='\0' ||
4551 (filename[1] == C_FILESEP && filename[2] == '\0')){
4552 precolon[0] = '~';
4553 precolon[1] = '\0';
4554 filename[0] = '\0';
4555 strncpy(dir, precolon, sizeof(dir)-1);
4556 dir[sizeof(dir)-1] = '\0';
4559 else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){
4560 if(homedir){
4561 precolon[0] = '~';
4562 precolon[1] = '\0';
4563 strncpy(dir, precolon, sizeof(dir)-1);
4564 dir[sizeof(dir)-1] = '\0';
4566 else{
4567 precolon[0] = '\0';
4568 dir[0] = '\0';
4571 l = MAXPATH;
4572 dir2[0] = '\0';
4573 strncpy(tmp, filename, sizeof(tmp)-1);
4574 tmp[sizeof(tmp)-1] = '\0';
4575 if(*tmp && is_absolute_path(tmp))
4576 fnexpand(tmp, sizeof(tmp));
4577 if(strncmp(tmp,postcolon, strlen(postcolon)))
4578 postcolon[0] = '\0';
4580 if(*tmp && (fn = last_cmpnt(tmp))){
4581 l -= fn - tmp;
4582 strncpy(filename2, fn, sizeof(filename2)-1);
4583 filename2[sizeof(filename2)-1] = '\0';
4584 if(is_absolute_path(tmp)){
4585 strncpy(dir2, tmp, MIN(fn - tmp, sizeof(dir2)-1));
4586 dir2[MIN(fn - tmp, sizeof(dir2)-1)] = '\0';
4587 #ifdef _WINDOWS
4588 if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){
4589 dir2[2] = '\\';
4590 dir2[3] = '\0';
4592 #endif
4593 strncpy(postcolon, dir2, sizeof(postcolon)-1);
4594 postcolon[sizeof(postcolon)-1] = '\0';
4595 precolon[0] = '\0';
4597 else{
4598 char *p = NULL;
4600 * Just building the directory name in dir2,
4601 * full_filename is overloaded.
4603 snprintf(full_filename, len, "%.*s", (int) MIN(fn-tmp,len-1), tmp);
4604 full_filename[len-1] = '\0';
4605 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4606 postcolon[sizeof(postcolon)-1] = '\0';
4607 build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH)
4608 : (dir[0] == '~' && !dir[1])
4609 ? ps->home_dir
4610 : dir,
4611 full_filename, sizeof(dir2));
4612 if(p)
4613 free(p);
4616 else{
4617 if(is_absolute_path(tmp)){
4618 strncpy(dir2, tmp, sizeof(dir2)-1);
4619 dir2[sizeof(dir2)-1] = '\0';
4620 #ifdef _WINDOWS
4621 if(dir2[2]=='\0' && dir2[1]==':'){
4622 dir2[2]='\\';
4623 dir2[3]='\0';
4624 strncpy(postcolon,dir2,sizeof(postcolon)-1);
4625 postcolon[sizeof(postcolon)-1] = '\0';
4627 #endif
4628 filename2[0] = '\0';
4629 precolon[0] = '\0';
4631 else{
4632 strncpy(filename2, tmp, sizeof(filename2)-1);
4633 filename2[sizeof(filename2)-1] = '\0';
4634 if(!dir[0]){
4635 if(getcwd(dir2, sizeof(dir2)) == NULL)
4636 alpine_panic(_("getcwd() call failed at get_export_filename"));
4638 else if(dir[0] == '~' && !dir[1]){
4639 strncpy(dir2, ps->home_dir, sizeof(dir2)-1);
4640 dir2[sizeof(dir2)-1] = '\0';
4642 else{
4643 strncpy(dir2, dir, sizeof(dir2)-1);
4644 dir2[sizeof(dir2)-1] = '\0';
4647 postcolon[0] = '\0';
4651 build_path(full_filename, dir2, filename2, len);
4652 if(!strcmp(full_filename, dir2))
4653 filename2[0] = '\0';
4654 if(full_filename[strlen(full_filename)-1] == C_FILESEP
4655 && isdir(full_filename,NULL,NULL)){
4656 if(strlen(full_filename) == 1)
4657 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4658 else if(filename2[0])
4659 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4660 postcolon[sizeof(postcolon)-1] = '\0';
4661 strncpy(dir2, full_filename, sizeof(dir2)-1);
4662 dir2[sizeof(dir2)-1] = '\0';
4663 filename2[0] = '\0';
4665 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4666 else if(full_filename[strlen(full_filename)-1] == C_FILESEP){
4667 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4668 postcolon[sizeof(postcolon)-1] = '\0';
4669 strncpy(dir2, full_filename, sizeof(dir2)-1);
4670 dir2[sizeof(dir2)-1] = '\0';
4671 filename2[0] = '\0';
4673 #endif
4674 if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1
4675 && strcmp(dir2+1, ":\\"))
4676 /* last condition to prevent stripping of '\\'
4677 in windows partition */
4678 dir2[strlen(dir2)-1] = '\0';
4680 if(r == 10){ /* File Browser */
4681 r = file_lister(lister_msg ? lister_msg : "EXPORT",
4682 dir2, sizeof(dir2), filename2, sizeof(filename2),
4683 TRUE,
4684 (flags & GE_IS_IMPORT) ? FB_READ : FB_SAVE);
4685 #ifdef _WINDOWS
4686 /* Windows has a special "feature" in which entering the file browser will
4687 change the working directory if the directory is changed at all (even
4688 clicking "Cancel" will change the working directory).
4690 if(F_ON(F_USE_CURRENT_DIR, ps))
4691 (void)getcwd(dir2,sizeof(dir2));
4692 #endif
4693 if(isdir(dir2,NULL,NULL)){
4694 strncpy(precolon, dir2, sizeof(precolon)-1);
4695 precolon[sizeof(precolon)-1] = '\0';
4697 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4698 postcolon[sizeof(postcolon)-1] = '\0';
4699 if(r == 1){
4700 build_path(full_filename, dir2, filename2, len);
4701 if(isdir(full_filename, NULL, NULL)){
4702 strncpy(dir, full_filename, sizeof(dir)-1);
4703 dir[sizeof(dir)-1] = '\0';
4704 filename[0] = '\0';
4706 else{
4707 fn = last_cmpnt(full_filename);
4708 strncpy(dir, full_filename,
4709 MIN(fn - full_filename, sizeof(dir)-1));
4710 dir[MIN(fn - full_filename, sizeof(dir)-1)] = '\0';
4711 if(fn - full_filename > 1)
4712 dir[fn - full_filename - 1] = '\0';
4715 if(!strcmp(dir, ps->home_dir)){
4716 dir[0] = '~';
4717 dir[1] = '\0';
4720 strncpy(filename, fn, len-1);
4721 filename[len-1] = '\0';
4724 else{ /* File Completion */
4725 if(!pico_fncomplete(dir2, filename2, sizeof(filename2)))
4726 Writechar(BELL, 0);
4727 strncat(postcolon, filename2,
4728 sizeof(postcolon)-1-strlen(postcolon));
4729 postcolon[sizeof(postcolon)-1] = '\0';
4731 was_abs_path = is_absolute_path(filename);
4733 if(!strcmp(dir, ps->home_dir)){
4734 dir[0] = '~';
4735 dir[1] = '\0';
4738 strncpy(filename, postcolon, len-1);
4739 filename[len-1] = '\0';
4740 strncpy(dir, precolon, sizeof(dir)-1);
4741 dir[sizeof(dir)-1] = '\0';
4743 if(filename[0] == '~' && !filename[1]){
4744 dir[0] = '~';
4745 dir[1] = '\0';
4746 filename[0] = '\0';
4749 continue;
4751 else if(r == 12){ /* Download, caller handles it */
4752 ret = r;
4753 goto done;
4755 else if(r == 13){ /* toggle AllParts bit */
4756 if(rflags){
4757 if(*rflags & GER_ALLPARTS){
4758 *rflags &= ~GER_ALLPARTS;
4759 opts[allparts].label = N_("AllParts");
4761 else{
4762 *rflags |= GER_ALLPARTS;
4763 /* opposite of All Parts, No All Parts */
4764 opts[allparts].label = N_("NoAllParts");
4768 continue;
4770 #if 0
4771 else if(r == 14){ /* List file names matching partial? */
4772 continue;
4774 #endif
4775 else if(r == 15){ /* toggle Binary bit */
4776 if(rflags){
4777 if(*rflags & GER_BINARY){
4778 *rflags &= ~GER_BINARY;
4779 opts[binary].label = N_("Binary");
4781 else{
4782 *rflags |= GER_BINARY;
4783 opts[binary].label = N_("No Binary");
4787 continue;
4789 else if(r == 1){ /* Cancel */
4790 ret = -1;
4791 goto done;
4793 else if(r == 4){
4794 continue;
4796 else if(r >= 30 && r <= 33){
4797 char *p = NULL;
4799 if(r == 30 || r == 31){
4800 if(history){
4801 if(r == 30)
4802 p = get_prev_hist(*history, filename, 0, NULL);
4803 else if (r == 31)
4804 p = get_next_hist(*history, filename, 0, NULL);
4808 if(r == 32 || r == 33){
4809 int nitems = items_in_hist(dir_hist);
4810 if(dir_hist || hist_len > 0){
4811 if(r == 32){
4812 if(pos > 0)
4813 p = hist_in_pos(--pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4814 else p = last;
4816 else if (r == 33){
4817 if(pos < hist_len + nitems)
4818 p = hist_in_pos(++pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4820 if(p == NULL || *p == '\0')
4821 p = orig_dir;
4824 last = p; /* save it! */
4826 if(p != NULL && *p != '\0'){
4827 if(r == 30 || r == 31){
4828 if((fn = last_cmpnt(p)) != NULL){
4829 strncpy(dir, p, MIN(fn - p, sizeof(dir)-1));
4830 dir[MIN(fn - p, sizeof(dir)-1)] = '\0';
4831 if(fn - p > 1)
4832 dir[fn - p - 1] = '\0';
4833 strncpy(filename, fn, len-1);
4834 filename[len-1] = '\0';
4836 } else { /* r == 32 || r == 33 */
4837 strncpy(dir, p, sizeof(dir)-1);
4838 dir[sizeof(dir)-1] = '\0';
4841 if(!strcmp(dir, ps->home_dir)){
4842 dir[0] = '~';
4843 dir[1] = '\0';
4846 else
4847 Writechar(BELL, 0);
4848 continue;
4850 #ifndef _WINDOWS
4851 else if(r == 40){
4852 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4853 SIZEOF_20KBUF, filename);
4854 strncpy(filename, tmp_20k_buf, len);
4855 filename[len-1] = '\0';
4856 continue;
4858 #endif /* _WINDOWS */
4859 else if(r != 0){
4860 Writechar(BELL, 0);
4861 continue;
4864 removing_leading_and_trailing_white_space(filename);
4866 if(!*filename){
4867 if(!*def){ /* Cancel */
4868 ret = -1;
4869 goto done;
4872 strncpy(filename, def, len-1);
4873 filename[len-1] = '\0';
4876 #if defined(DOS) || defined(OS2)
4877 if(is_absolute_path(filename)){
4878 fixpath(filename, len);
4880 #else
4881 if(filename[0] == '~'){
4882 if(fnexpand(filename, len) == NULL){
4883 char *p = strindex(filename, '/');
4884 if(p != NULL)
4885 *p = '\0';
4886 q_status_message1(SM_ORDER | SM_DING, 3, 3,
4887 _("Error expanding file name: \"%s\" unknown user"),
4888 filename);
4889 continue;
4892 #endif
4894 if(is_absolute_path(filename)){
4895 strncpy(full_filename, filename, len-1);
4896 full_filename[len-1] = '\0';
4898 else{
4899 if(!dir[0])
4900 build_path(full_filename, (char *)getcwd(dir,sizeof(dir)),
4901 filename, len);
4902 else if(dir[0] == '~' && !dir[1])
4903 build_path(full_filename, ps->home_dir, filename, len);
4904 else
4905 build_path(full_filename, dir, filename, len);
4908 if((ill = filter_filename(full_filename, &fatal,
4909 ps_global->restricted || ps_global->VAR_OPER_DIR)) != NULL){
4910 if(fatal){
4911 q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill);
4912 continue;
4914 else{
4915 /* BUG: we should beep when the key's pressed rather than bitch later */
4916 /* Warn and ask for confirmation. */
4917 snprintf(prompt_buf, sizeof(prompt_buf), "File name contains a '%s'. %s anyway",
4918 ill, (flags & GE_IS_EXPORT) ? "Export" : "Save");
4919 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4920 if(want_to(prompt_buf, 'n', 0, NO_HELP,
4921 ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y')
4922 continue;
4926 break; /* Must have got an OK file name */
4929 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){
4930 ret = -2;
4931 goto done;
4934 if(!can_access(full_filename, ACCESS_EXISTS)){
4935 int rbflags;
4936 static ESCKEY_S access_opts[] = {
4937 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4938 a file or append to the end of the file */
4939 {'o', 'o', "O", N_("Overwrite")},
4940 {'a', 'a', "A", N_("Append")},
4941 {-1, 0, NULL, NULL}};
4943 rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0);
4945 if(flags & GE_NO_APPEND){
4946 r = strlen(filename);
4947 snprintf(prompt_buf, sizeof(prompt_buf),
4948 /* TRANSLATORS: asking user whether to overwrite a file or not,
4949 File <filename> already exists. Overwrite it ? */
4950 _("File \"%s%s\" already exists. Overwrite it "),
4951 (r > 20) ? "..." : "",
4952 filename + ((r > 20) ? r - 20 : 0));
4953 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4954 if(want_to(prompt_buf, 'n', 'x', NO_HELP, rbflags) == 'y'){
4955 if(rflags)
4956 *rflags |= GER_OVER;
4958 if(our_unlink(full_filename) < 0){
4959 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4960 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4961 _("Cannot remove old %s: %s"),
4962 full_filename, error_description(errno));
4965 else{
4966 ret = -1;
4967 goto done;
4970 else if(!(flags & GE_IS_IMPORT)){
4971 r = strlen(filename);
4972 snprintf(prompt_buf, sizeof(prompt_buf),
4973 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4974 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4975 (r > 20) ? "..." : "",
4976 filename + ((r > 20) ? r - 20 : 0));
4977 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4978 switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global),
4979 access_opts, 'a', 'x', NO_HELP, rbflags)){
4980 case 'o' :
4981 if(rflags)
4982 *rflags |= GER_OVER;
4984 if(our_truncate(full_filename, (off_t)0) < 0)
4985 /* trouble truncating, but we'll give it a try anyway */
4986 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4987 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
4988 _("Warning: Cannot truncate old %s: %s"),
4989 full_filename, error_description(errno));
4990 break;
4992 case 'a' :
4993 if(rflags)
4994 *rflags |= GER_APPEND;
4996 break;
4998 case 'x' :
4999 default :
5000 ret = -1;
5001 goto done;
5006 done:
5007 if(history && ret == 0){
5008 save_hist(*history, full_filename, 0, NULL);
5009 strncpy(tmp, full_filename, MAXPATH);
5010 tmp[MAXPATH] = '\0';
5011 if((fn = strrchr(tmp, C_FILESEP)) != NULL)
5012 *fn = '\0';
5013 else
5014 tmp[0] = '\0';
5015 if(tmp[0])
5016 save_hist(dir_hist, tmp, 0, NULL);
5019 if(opts && opts != optsarg)
5020 fs_give((void **) &opts);
5022 return(ret);
5026 /*----------------------------------------------------------------------
5027 parse the config'd upload/download command
5029 Args: cmd -- buffer to return command fit for shellin'
5030 prefix --
5031 cfg_str --
5032 fname -- file name to build into the command
5034 Returns: pointer to cmd_str buffer or NULL on real bad error
5036 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
5037 cfg_str is written to standard out right before a successful
5038 return of this function. The call immediately following this
5039 function darn well better be the shell exec...
5040 ----*/
5041 char *
5042 build_updown_cmd(char *cmd, size_t cmdlen, char *prefix, char *cfg_str, char *fname)
5044 char *p;
5045 int fname_found = 0;
5047 if(prefix && *prefix){
5048 /* loop thru replacing all occurrences of _FILE_ */
5049 p = strncpy(cmd, prefix, cmdlen);
5050 cmd[cmdlen-1] = '\0';
5051 while((p = strstr(p, "_FILE_")))
5052 rplstr(p, cmdlen-(p-cmd), 6, fname);
5054 fputs(cmd, stdout);
5057 /* loop thru replacing all occurrences of _FILE_ */
5058 p = strncpy(cmd, cfg_str, cmdlen);
5059 cmd[cmdlen-1] = '\0';
5060 while((p = strstr(p, "_FILE_"))){
5061 rplstr(p, cmdlen-(p-cmd), 6, fname);
5062 fname_found = 1;
5065 if(!fname_found)
5066 snprintf(cmd+strlen(cmd), cmdlen-strlen(cmd), " %s", fname);
5068 cmd[cmdlen-1] = '\0';
5070 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
5071 cmd ? cmd : "?"));
5072 return(cmd);
5076 /*----------------------------------------------------------------------
5077 Write a berzerk format message delimiter using the given putc function
5079 Args: e -- envelope of message to write
5080 pc -- function to use
5082 Returns: TRUE if we could write it, FALSE if there was a problem
5084 NOTE: follows delimiter with OS-dependent newline
5085 ----*/
5087 bezerk_delimiter(ENVELOPE *env, MESSAGECACHE *mc, gf_io_t pc, int leading_newline)
5089 MESSAGECACHE telt;
5090 time_t when;
5091 char *p;
5093 /* write "[\n]From mailbox[@host] " */
5094 if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1)
5095 && gf_puts("From ", pc)
5096 && gf_puts((env && env->from) ? env->from->mailbox
5097 : "the-concourse-on-high", pc)
5098 && gf_puts((env && env->from && env->from->host) ? "@" : "", pc)
5099 && gf_puts((env && env->from && env->from->host) ? env->from->host
5100 : "", pc)
5101 && (*pc)(' ')))
5102 return(0);
5104 if(mc && mc->valid)
5105 when = mail_longdate(mc);
5106 else if(env && env->date && env->date[0]
5107 && mail_parse_date(&telt,env->date))
5108 when = mail_longdate(&telt);
5109 else
5110 when = time(0);
5112 p = ctime(&when);
5114 while(p && *p && *p != '\n') /* write date */
5115 if(!(*pc)(*p++))
5116 return(0);
5118 if(!gf_puts(NEWLINE, pc)) /* write terminating newline */
5119 return(0);
5121 return(1);
5125 /*----------------------------------------------------------------------
5126 Execute command to jump to a given message number
5128 Args: qline -- Line to ask question on
5130 Result: returns true if the use selected a new message, false otherwise
5132 ----*/
5133 long
5134 jump_to(MSGNO_S *msgmap, int qline, UCS first_num, SCROLL_S *sparms, CmdWhere in_index)
5136 char jump_num_string[80], *j, prompt[70];
5137 HelpType help;
5138 int rc;
5139 static ESCKEY_S jump_to_key[] = { {0, 0, NULL, NULL},
5140 /* TRANSLATORS: go to First Message */
5141 {ctrl('Y'), 10, "^Y", N_("First Msg")},
5142 {ctrl('V'), 11, "^V", N_("Last Msg")},
5143 {-1, 0, NULL, NULL} };
5145 dprint((4, "\n - jump_to -\n"));
5147 #ifdef DEBUG
5148 if(sparms && sparms->jump_is_debug)
5149 return(get_level(qline, first_num, sparms));
5150 #endif
5152 if(!any_messages(msgmap, NULL, "to Jump to"))
5153 return(0L);
5155 if(first_num && first_num < 0x80 && isdigit((unsigned char) first_num)){
5156 jump_num_string[0] = first_num;
5157 jump_num_string[1] = '\0';
5159 else
5160 jump_num_string[0] = '\0';
5162 if(mn_total_cur(msgmap) > 1L){
5163 snprintf(prompt, sizeof(prompt), "Unselect %s msgs in favor of number to be entered",
5164 comatose(mn_total_cur(msgmap)));
5165 prompt[sizeof(prompt)-1] = '\0';
5166 if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n')
5167 return(0L);
5170 snprintf(prompt, sizeof(prompt), "%s number to jump to : ", in_index == ThrdIndx
5171 ? "Thread"
5172 : "Message");
5173 prompt[sizeof(prompt)-1] = '\0';
5175 help = NO_HELP;
5176 while(1){
5177 int flags = OE_APPEND_CURRENT;
5179 rc = optionally_enter(jump_num_string, qline, 0,
5180 sizeof(jump_num_string), prompt,
5181 jump_to_key, help, &flags);
5182 if(rc == 3){
5183 help = help == NO_HELP
5184 ? (in_index == ThrdIndx ? h_oe_jump_thd : h_oe_jump)
5185 : NO_HELP;
5186 continue;
5188 else if(rc == 10 || rc == 11){
5189 char warning[100];
5190 long closest;
5192 closest = closest_jump_target(rc == 10 ? 1L
5193 : ((in_index == ThrdIndx)
5194 ? msgmap->max_thrdno
5195 : mn_get_total(msgmap)),
5196 ps_global->mail_stream,
5197 msgmap, 0,
5198 in_index, warning, sizeof(warning));
5199 /* ignore warning */
5200 return(closest);
5204 * If we take out the *jump_num_string nonempty test in this if
5205 * then the closest_jump_target routine will offer a jump to the
5206 * last message. However, it is slow because you have to wait for
5207 * the status message and it is annoying for people who hit J command
5208 * by mistake and just want to hit return to do nothing, like has
5209 * always worked. So the test is there for now. Hubert 2002-08-19
5211 * Jumping to first/last message is now possible through ^Y/^V
5212 * commands above. jpf 2002-08-21
5213 * (and through "end" hubert 2006-07-07)
5215 if(rc == 0 && *jump_num_string != '\0'){
5216 removing_leading_and_trailing_white_space(jump_num_string);
5217 for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++)
5220 if(*j != '\0'){
5221 if(!strucmp("end", j))
5222 return((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap));
5224 q_status_message(SM_ORDER | SM_DING, 2, 2,
5225 _("Invalid number entered. Use only digits 0-9"));
5226 jump_num_string[0] = '\0';
5228 else{
5229 char warning[100];
5230 long closest, jump_num;
5232 if(*jump_num_string)
5233 jump_num = atol(jump_num_string);
5234 else
5235 jump_num = -1L;
5237 warning[0] = '\0';
5238 closest = closest_jump_target(jump_num, ps_global->mail_stream,
5239 msgmap,
5240 *jump_num_string ? 0 : 1,
5241 in_index, warning, sizeof(warning));
5242 if(warning[0])
5243 q_status_message(SM_ORDER | SM_DING, 2, 2, warning);
5245 if(closest == jump_num)
5246 return(jump_num);
5248 if(closest == 0L)
5249 jump_num_string[0] = '\0';
5250 else
5251 strncpy(jump_num_string, long2string(closest),
5252 sizeof(jump_num_string));
5255 continue;
5258 if(rc != 4)
5259 break;
5262 return(0L);
5267 * cmd_delete_action - handle msgno advance and such after single message deletion
5269 char *
5270 cmd_delete_action(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
5272 int opts;
5273 long msgno;
5274 char *rv = NULL;
5276 msgno = mn_get_cur(msgmap);
5277 advance_cur_after_delete(state, state->mail_stream, msgmap, in_index);
5279 if(IS_NEWS(state->mail_stream)
5280 || ((state->context_current->use & CNTXT_INCMNG)
5281 && context_isambig(state->cur_folder))){
5283 opts = (NSF_TRUST_FLAGS | NSF_SKIP_CHID);
5284 if(in_index == View)
5285 opts &= ~NSF_SKIP_CHID;
5287 (void)next_sorted_flagged(F_UNDEL|F_UNSEEN, state->mail_stream, msgno, &opts);
5288 if(!(opts & NSF_FLAG_MATCH)){
5289 char nextfolder[MAXPATH];
5291 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
5292 nextfolder[sizeof(nextfolder)-1] = '\0';
5293 rv = next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
5294 state->context_current, NULL, NULL)
5295 ? ". Press TAB for next folder."
5296 : ". No more folders to TAB to.";
5300 return(rv);
5305 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5307 char *
5308 cmd_delete_index(struct pine *state, MSGNO_S *msgmap)
5310 return(cmd_delete_action(state, msgmap,MsgIndx));
5314 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5316 char *
5317 cmd_delete_view(struct pine *state, MSGNO_S *msgmap)
5319 return(cmd_delete_action(state, msgmap, View));
5323 void
5324 advance_cur_after_delete(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, CmdWhere in_index)
5326 long new_msgno, msgno;
5327 int opts;
5329 new_msgno = msgno = mn_get_cur(msgmap);
5330 opts = NSF_TRUST_FLAGS;
5332 if(F_ON(F_DEL_SKIPS_DEL, state)){
5334 if(THREADING() && sp_viewing_a_thread(stream))
5335 opts |= NSF_SKIP_CHID;
5337 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5339 else{
5340 mn_inc_cur(stream, msgmap,
5341 (in_index == View && THREADING()
5342 && sp_viewing_a_thread(stream))
5343 ? MH_THISTHD
5344 : (in_index == View)
5345 ? MH_ANYTHD : MH_NONE);
5346 new_msgno = mn_get_cur(msgmap);
5347 if(new_msgno != msgno)
5348 opts |= NSF_FLAG_MATCH;
5352 * Viewing_a_thread is the complicated case because we want to ignore
5353 * other threads at first and then look in other threads if we have to.
5354 * By ignoring other threads we also ignore collapsed partial threads
5355 * in our own thread.
5357 if(THREADING() && sp_viewing_a_thread(stream) && !(opts & NSF_FLAG_MATCH)){
5358 long rawno, orig_thrdno;
5359 PINETHRD_S *thrd, *topthrd = NULL;
5361 rawno = mn_m2raw(msgmap, msgno);
5362 thrd = fetch_thread(stream, rawno);
5363 if(thrd && thrd->top)
5364 topthrd = fetch_thread(stream, thrd->top);
5366 orig_thrdno = topthrd ? topthrd->thrdno : -1L;
5368 opts = NSF_TRUST_FLAGS;
5369 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5372 * If we got a match, new_msgno may be a message in
5373 * a different thread from the one we are viewing, or it could be
5374 * in a collapsed part of this thread.
5376 if(opts & NSF_FLAG_MATCH){
5377 int ret;
5378 char pmt[128];
5380 topthrd = NULL;
5381 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
5382 if(thrd && thrd->top)
5383 topthrd = fetch_thread(stream, thrd->top);
5386 * If this match is in the same thread we're already in
5387 * then we're done, else we have to ask the user and maybe
5388 * switch threads.
5390 if(!(orig_thrdno > 0L && topthrd
5391 && topthrd->thrdno == orig_thrdno)){
5393 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
5394 if(in_index == View)
5395 snprintf(pmt, sizeof(pmt),
5396 "View message in thread number %.10s",
5397 topthrd ? comatose(topthrd->thrdno) : "?");
5398 else
5399 snprintf(pmt, sizeof(pmt), "View thread number %.10s",
5400 topthrd ? comatose(topthrd->thrdno) : "?");
5402 ret = want_to(pmt, 'y', 'x', NO_HELP, WT_NORM);
5404 else
5405 ret = 'y';
5407 if(ret == 'y'){
5408 unview_thread(state, stream, msgmap);
5409 mn_set_cur(msgmap, new_msgno);
5410 if(THRD_AUTO_VIEW()
5411 && (count_lflags_in_thread(stream, topthrd, msgmap,
5412 MN_NONE) == 1)
5413 && view_thread(state, stream, msgmap, 1)){
5414 if(current_index_state)
5415 msgmap->top_after_thrd = current_index_state->msg_at_top;
5417 state->view_skipped_index = 1;
5418 state->next_screen = mail_view_screen;
5420 else{
5421 view_thread(state, stream, msgmap, 1);
5422 if(current_index_state)
5423 msgmap->top_after_thrd = current_index_state->msg_at_top;
5425 state->next_screen = SCREEN_FUN_NULL;
5428 else
5429 new_msgno = msgno; /* stick with original */
5434 mn_set_cur(msgmap, new_msgno);
5435 if(in_index != View)
5436 adjust_cur_to_visible(stream, msgmap);
5440 #ifdef DEBUG
5441 long
5442 get_level(int qline, UCS first_num, SCROLL_S *sparms)
5444 char debug_num_string[80], *j, prompt[70];
5445 HelpType help;
5446 int rc;
5447 long debug_num;
5449 if(first_num && first_num < 0x80 && isdigit((unsigned char)first_num)){
5450 debug_num_string[0] = first_num;
5451 debug_num_string[1] = '\0';
5452 debug_num = atol(debug_num_string);
5453 *(int *)(sparms->proc.data.p) = debug_num;
5454 q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s",
5455 comatose(debug_num));
5456 return(1L);
5458 else
5459 debug_num_string[0] = '\0';
5461 snprintf(prompt, sizeof(prompt), "Show debug <= this level (0-%d) : ", MAX(debug, 9));
5462 prompt[sizeof(prompt)-1] = '\0';
5464 help = NO_HELP;
5465 while(1){
5466 int flags = OE_APPEND_CURRENT;
5468 rc = optionally_enter(debug_num_string, qline, 0,
5469 sizeof(debug_num_string), prompt,
5470 NULL, help, &flags);
5471 if(rc == 3){
5472 help = help == NO_HELP ? h_oe_debuglevel : NO_HELP;
5473 continue;
5476 if(rc == 0){
5477 removing_leading_and_trailing_white_space(debug_num_string);
5478 for(j=debug_num_string; isdigit((unsigned char)*j); j++)
5481 if(*j != '\0'){
5482 q_status_message(SM_ORDER | SM_DING, 2, 2,
5483 _("Invalid number entered. Use only digits 0-9"));
5484 debug_num_string[0] = '\0';
5486 else{
5487 debug_num = atol(debug_num_string);
5488 if(debug_num < 0)
5489 q_status_message(SM_ORDER | SM_DING, 2, 2,
5490 _("Number should be >= 0"));
5491 else if(debug_num > MAX(debug,9))
5492 q_status_message1(SM_ORDER | SM_DING, 2, 2,
5493 _("Maximum is %s"), comatose(MAX(debug,9)));
5494 else{
5495 *(int *)(sparms->proc.data.p) = debug_num;
5496 q_status_message1(SM_ORDER, 0, 3,
5497 "Show debug <= level %s",
5498 comatose(debug_num));
5499 return(1L);
5503 continue;
5506 if(rc != 4)
5507 break;
5510 return(0L);
5512 #endif /* DEBUG */
5516 * Returns the message number closest to target that isn't hidden.
5517 * Make warning at least 100 chars.
5518 * A return of 0 means there is no message to jump to.
5520 long
5521 closest_jump_target(long int target, MAILSTREAM *stream, MSGNO_S *msgmap, int no_target, CmdWhere in_index, char *warning, size_t warninglen)
5523 long i, start, closest = 0L;
5524 char buf[80];
5525 long maxnum;
5527 warning[0] = '\0';
5528 maxnum = (in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap);
5530 if(no_target){
5531 target = maxnum;
5532 start = 1L;
5533 snprintf(warning, warninglen, "No %s number entered, jump to end? ",
5534 (in_index == ThrdIndx) ? "thread" : "message");
5535 warning[warninglen-1] = '\0';
5537 else if(target < 1L)
5538 start = 1L - target;
5539 else if(target > maxnum)
5540 start = target - maxnum;
5541 else
5542 start = 1L;
5544 if(target > 0L && target <= maxnum)
5545 if(in_index == ThrdIndx
5546 || !msgline_hidden(stream, msgmap, target, 0))
5547 return(target);
5549 for(i = start; target+i <= maxnum || target-i > 0L; i++){
5551 if(target+i > 0L && target+i <= maxnum &&
5552 (in_index == ThrdIndx
5553 || !msgline_hidden(stream, msgmap, target+i, 0))){
5554 closest = target+i;
5555 break;
5558 if(target-i > 0L && target-i <= maxnum &&
5559 (in_index == ThrdIndx
5560 || !msgline_hidden(stream, msgmap, target-i, 0))){
5561 closest = target-i;
5562 break;
5566 strncpy(buf, long2string(closest), sizeof(buf));
5567 buf[sizeof(buf)-1] = '\0';
5569 if(closest == 0L)
5570 strncpy(warning, "Nothing to jump to", warninglen);
5571 else if(target < 1L)
5572 snprintf(warning, warninglen, "%s number (%s) must be at least %s",
5573 (in_index == ThrdIndx) ? "Thread" : "Message",
5574 long2string(target), buf);
5575 else if(target > maxnum)
5576 snprintf(warning, warninglen, "%s number (%s) may be no more than %s",
5577 (in_index == ThrdIndx) ? "Thread" : "Message",
5578 long2string(target), buf);
5579 else if(!no_target)
5580 snprintf(warning, warninglen,
5581 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5582 long2string(target), buf);
5584 warning[warninglen-1] = '\0';
5586 return(closest);
5590 /*----------------------------------------------------------------------
5591 Prompt for folder name to open, expand the name and return it
5593 Args: qline -- Screen line to prompt on
5594 allow_list -- if 1, allow ^T to bring up collection lister
5596 Result: returns the folder name or NULL
5597 pine structure mangled_footer flag is set
5598 may call the collection lister in which case mangled screen will be set
5600 This prompts the user for the folder to open, possibly calling up
5601 the collection lister if the user types ^T.
5602 ----------------------------------------------------------------------*/
5603 char *
5604 broach_folder(int qline, int allow_list, int *notrealinbox, CONTEXT_S **context)
5606 HelpType help;
5607 static char newfolder[MAILTMPLEN];
5608 char expanded[MAXPATH+1],
5609 prompt[MAX_SCREEN_COLS+1],
5610 *last_folder, *p;
5611 unsigned char *f1, *f2, *f3;
5612 static HISTORY_S *history = NULL;
5613 CONTEXT_S *tc, *tc2;
5614 ESCKEY_S ekey[9];
5615 int rc, r, ku = -1, n, flags, last_rc = 0, inbox, done = 0;
5618 * the idea is to provide a clue for the context the file name
5619 * will be saved in (if a non-imap names is typed), and to
5620 * only show the previous if it was also in the same context
5622 help = NO_HELP;
5623 *expanded = '\0';
5624 *newfolder = '\0';
5625 last_folder = NULL;
5626 if(notrealinbox)
5627 (*notrealinbox) = 1;
5629 init_hist(&history, HISTSIZE);
5631 tc = broach_get_folder(context ? *context : NULL, &inbox, NULL);
5633 /* set up extra command option keys */
5634 rc = 0;
5635 ekey[rc].ch = (allow_list) ? ctrl('T') : 0 ;
5636 ekey[rc].rval = (allow_list) ? 2 : 0;
5637 ekey[rc].name = (allow_list) ? "^T" : "";
5638 ekey[rc++].label = (allow_list) ? N_("ToFldrs") : "";
5640 if(ps_global->context_list->next){
5641 ekey[rc].ch = ctrl('P');
5642 ekey[rc].rval = 10;
5643 ekey[rc].name = "^P";
5644 ekey[rc++].label = N_("Prev Collection");
5646 ekey[rc].ch = ctrl('N');
5647 ekey[rc].rval = 11;
5648 ekey[rc].name = "^N";
5649 ekey[rc++].label = N_("Next Collection");
5652 ekey[rc].ch = ctrl('W');
5653 ekey[rc].rval = 17;
5654 ekey[rc].name = "^W";
5655 ekey[rc++].label = N_("INBOX");
5657 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
5658 ekey[rc].ch = TAB;
5659 ekey[rc].rval = 12;
5660 ekey[rc].name = "TAB";
5661 ekey[rc++].label = N_("Complete");
5664 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
5665 ekey[rc].ch = ctrl('X');
5666 ekey[rc].rval = 14;
5667 ekey[rc].name = "^X";
5668 ekey[rc++].label = N_("ListMatches");
5671 if(ps_global->context_list->next && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5672 ekey[rc].ch = KEY_UP;
5673 ekey[rc].rval = 10;
5674 ekey[rc].name = "";
5675 ekey[rc++].label = "";
5677 ekey[rc].ch = KEY_DOWN;
5678 ekey[rc].rval = 11;
5679 ekey[rc].name = "";
5680 ekey[rc++].label = "";
5682 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5683 ekey[rc].ch = KEY_UP;
5684 ekey[rc].rval = 30;
5685 ekey[rc].name = "";
5686 ku = rc;
5687 ekey[rc++].label = "";
5689 ekey[rc].ch = KEY_DOWN;
5690 ekey[rc].rval = 31;
5691 ekey[rc].name = "";
5692 ekey[rc++].label = "";
5695 ekey[rc].ch = -1;
5697 while(!done) {
5699 * Figure out next default value for this context. The idea
5700 * is that in each context the last folder opened is cached.
5701 * It's up to pick it out and display it. This is fine
5702 * and dandy if we've currently got the inbox open, BUT
5703 * if not, make the inbox the default the first time thru.
5705 if(!inbox){
5706 last_folder = ps_global->inbox_name;
5707 inbox = 1; /* pretend we're in inbox from here on out */
5709 else
5710 last_folder = (ps_global->last_unambig_folder[0])
5711 ? ps_global->last_unambig_folder
5712 : ((tc->last_folder[0]) ? tc->last_folder : NULL);
5714 if(last_folder){ /* MAXPATH + 1 = sizeof(expanded) */
5715 unsigned char *fname = folder_name_decoded((unsigned char *)last_folder);
5716 snprintf(expanded, sizeof(expanded), " [%.*s]", MAXPATH+1-5,
5717 fname ? (char *) fname : last_folder);
5718 if(fname) fs_give((void **)&fname);
5720 else
5721 *expanded = '\0';
5723 expanded[sizeof(expanded)-1] = '\0';
5725 /* only show collection number if more than one available */
5726 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5727 snprintf(prompt, sizeof(prompt), "GOTO %s in <%s> %.*s%s: ",
5728 NEWS_TEST(tc) ? "news group" : "folder",
5729 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5730 *expanded ? " " : "");
5731 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5732 snprintf(prompt, sizeof(prompt), "GOTO folder %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5733 *expanded ? " " : "");
5735 prompt[sizeof(prompt)-1] = '\0';
5737 if(utf8_width(prompt) > MAXPROMPT){
5738 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5739 snprintf(prompt, sizeof(prompt), "GOTO <%s> %.*s%s: ",
5740 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5741 *expanded ? " " : "");
5742 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5743 snprintf(prompt, sizeof(prompt), "GOTO %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5744 *expanded ? " " : "");
5746 prompt[sizeof(prompt)-1] = '\0';
5748 if(utf8_width(prompt) > MAXPROMPT){
5749 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5750 snprintf(prompt, sizeof(prompt), "<%s> %.*s%s: ",
5751 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5752 *expanded ? " " : "");
5753 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5754 snprintf(prompt, sizeof(prompt), "%.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5755 *expanded ? " " : "");
5757 prompt[sizeof(prompt)-1] = '\0';
5761 if(ku >= 0){
5762 if(items_in_hist(history) > 1){
5763 ekey[ku].name = HISTORY_UP_KEYNAME;
5764 ekey[ku].label = HISTORY_KEYLABEL;
5765 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
5766 ekey[ku+1].label = HISTORY_KEYLABEL;
5768 else{
5769 ekey[ku].name = "";
5770 ekey[ku].label = "";
5771 ekey[ku+1].name = "";
5772 ekey[ku+1].label = "";
5776 /* is there any other way to do this? The point is that we
5777 * are trying to hide mutf7 from the user, and use the utf8
5778 * equivalent. So we create a variable f to take place of
5779 * newfolder, including content and size. f2 is copy of f1
5780 * that has to freed. Sigh!
5782 f3 = (unsigned char *) cpystr(newfolder);
5783 f1 = fs_get(sizeof(newfolder));
5784 f2 = folder_name_decoded(f3);
5785 if(f3) fs_give((void **)&f3);
5786 strncpy((char *)f1, (char *)f2, sizeof(newfolder));
5787 f1[sizeof(newfolder)-1] = '\0';
5788 if(f2) fs_give((void **)&f2);
5790 flags = OE_APPEND_CURRENT;
5791 rc = optionally_enter((char *) f1, qline, 0, sizeof(newfolder),
5792 (char *) prompt, ekey, help, &flags);
5794 f2 = folder_name_encoded(f1);
5795 strncpy(newfolder, (char *)f2, sizeof(newfolder));
5796 if(f1) fs_give((void **)&f1);
5797 if(f2) fs_give((void **)&f2);
5799 ps_global->mangled_footer = 1;
5801 switch(rc){
5802 case -1 : /* o_e says error! */
5803 q_status_message(SM_ORDER | SM_DING, 3, 3,
5804 _("Error reading folder name"));
5805 return(NULL);
5807 case 0 : /* o_e says normal entry */
5808 removing_trailing_white_space(newfolder);
5809 removing_leading_white_space(newfolder);
5811 if(*newfolder){
5812 char *name, *fullname = NULL;
5813 int exists, breakout = 0;
5815 save_hist(history, newfolder, 0, tc);
5817 if(!(name = folder_is_nick(newfolder, FOLDERS(tc),
5818 FN_WHOLE_NAME)))
5819 name = newfolder;
5821 if(update_folder_spec(expanded, sizeof(expanded), name)){
5822 strncpy(name = newfolder, expanded, sizeof(newfolder));
5823 newfolder[sizeof(newfolder)-1] = '\0';
5826 exists = folder_name_exists(tc, name, &fullname);
5828 if(fullname){
5829 strncpy(name = newfolder, fullname, sizeof(newfolder));
5830 newfolder[sizeof(newfolder)-1] = '\0';
5831 fs_give((void **) &fullname);
5832 breakout = TRUE;
5836 * if we know the things a folder, open it.
5837 * else if we know its a directory, visit it.
5838 * else we're not sure (it either doesn't really
5839 * exist or its unLISTable) so try opening it anyway
5841 if(exists & FEX_ISFILE){
5842 done++;
5843 break;
5845 else if((exists & FEX_ISDIR)){
5846 if(breakout){
5847 CONTEXT_S *fake_context;
5848 char tmp[MAILTMPLEN];
5849 size_t l;
5851 strncpy(tmp, name, sizeof(tmp));
5852 tmp[sizeof(tmp)-2-1] = '\0';
5853 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
5854 if(l < sizeof(tmp)){
5855 tmp[l] = tc->dir->delim;
5856 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
5859 else
5860 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
5862 tmp[sizeof(tmp)-1] = '\0';
5864 fake_context = new_context(tmp, 0);
5865 newfolder[0] = '\0';
5866 done = display_folder_list(&fake_context, newfolder,
5867 1, folders_for_goto);
5868 free_context(&fake_context);
5869 break;
5871 else if(!(tc->use & CNTXT_INCMNG)){
5872 done = display_folder_list(&tc, newfolder,
5873 1, folders_for_goto);
5874 break;
5877 else if((exists & FEX_ERROR)){
5878 q_status_message1(SM_ORDER, 0, 3,
5879 _("Problem accessing folder \"%s\""),
5880 newfolder);
5881 return(NULL);
5883 else{
5884 done++;
5885 break;
5888 if(exists == FEX_ERROR)
5889 q_status_message1(SM_ORDER, 0, 3,
5890 _("Problem accessing folder \"%s\""),
5891 newfolder);
5892 else if(tc->use & CNTXT_INCMNG)
5893 q_status_message1(SM_ORDER, 0, 3,
5894 _("Can't find Incoming Folder: %s"),
5895 newfolder);
5896 else if(context_isambig(newfolder))
5897 q_status_message2(SM_ORDER, 0, 3,
5898 _("Can't find folder \"%s\" in %s"),
5899 newfolder, (void *) tc->nickname);
5900 else
5901 q_status_message1(SM_ORDER, 0, 3,
5902 _("Can't find folder \"%s\""),
5903 newfolder);
5905 return(NULL);
5907 else if(last_folder){
5908 if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX
5909 && !strucmp(last_folder, ps_global->inbox_name)
5910 && tc == ((ps_global->context_list->use & CNTXT_INCMNG)
5911 ? ps_global->context_list->next : ps_global->context_list)){
5912 if(notrealinbox)
5913 (*notrealinbox) = 0;
5915 tc = ps_global->context_list;
5918 strncpy(newfolder, last_folder, sizeof(newfolder));
5919 newfolder[sizeof(newfolder)-1] = '\0';
5920 save_hist(history, newfolder, 0, tc);
5921 done++;
5922 break;
5924 /* fall thru like they cancelled */
5926 case 1 : /* o_e says user cancel */
5927 cmd_cancelled("Open folder");
5928 return(NULL);
5930 case 2 : /* o_e says user wants list */
5931 r = display_folder_list(&tc, newfolder, 0, folders_for_goto);
5932 if(r)
5933 done++;
5935 break;
5937 case 3 : /* o_e says user wants help */
5938 help = help == NO_HELP ? h_oe_broach : NO_HELP;
5939 break;
5941 case 4 : /* redraw */
5942 break;
5944 case 10 : /* Previous collection */
5945 tc2 = ps_global->context_list;
5946 while(tc2->next && tc2->next != tc)
5947 tc2 = tc2->next;
5949 tc = tc2;
5950 break;
5952 case 11 : /* Next collection */
5953 tc = (tc->next) ? tc->next : ps_global->context_list;
5954 break;
5956 case 12 : /* file name completion */
5957 if(!folder_complete(tc, newfolder, sizeof(newfolder), &n)){
5958 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
5959 r = display_folder_list(&tc, newfolder, 1,folders_for_goto);
5960 if(r)
5961 done++; /* bingo! */
5962 else
5963 rc = 0; /* burn last_rc */
5965 else
5966 Writechar(BELL, 0);
5969 break;
5971 case 14 : /* file name completion */
5972 r = display_folder_list(&tc, newfolder, 2, folders_for_goto);
5973 if(r)
5974 done++; /* bingo! */
5975 else
5976 rc = 0; /* burn last_rc */
5978 break;
5980 case 17 : /* GoTo INBOX */
5981 done++;
5982 strncpy(newfolder, ps_global->inbox_name, sizeof(newfolder)-1);
5983 newfolder[sizeof(newfolder)-1] = '\0';
5984 if(notrealinbox)
5985 (*notrealinbox) = 0;
5987 tc = ps_global->context_list;
5988 save_hist(history, newfolder, 0, tc);
5990 break;
5992 case 30 :
5993 if((p = get_prev_hist(history, newfolder, 0, tc)) != NULL){
5994 strncpy(newfolder, p, sizeof(newfolder));
5995 newfolder[sizeof(newfolder)-1] = '\0';
5996 if(history->hist[history->curindex])
5997 tc = history->hist[history->curindex]->cntxt;
5999 else
6000 Writechar(BELL, 0);
6002 break;
6004 case 31 :
6005 if((p = get_next_hist(history, newfolder, 0, tc)) != NULL){
6006 strncpy(newfolder, p, sizeof(newfolder));
6007 newfolder[sizeof(newfolder)-1] = '\0';
6008 if(history->hist[history->curindex])
6009 tc = history->hist[history->curindex]->cntxt;
6011 else
6012 Writechar(BELL, 0);
6014 break;
6016 default :
6017 alpine_panic("Unhandled case");
6018 break;
6021 last_rc = rc;
6024 dprint((2, "broach folder, name entered \"%s\"\n",
6025 newfolder ? newfolder : "?"));
6027 /*-- Just check that we can expand this. It gets done for real later --*/
6028 strncpy(expanded, newfolder, sizeof(expanded));
6029 expanded[sizeof(expanded)-1] = '\0';
6031 if(!expand_foldername(expanded, sizeof(expanded))) {
6032 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
6033 expanded ? expanded : "?"));
6034 return(NULL);
6037 *context = tc;
6038 return(newfolder);
6042 /*----------------------------------------------------------------------
6043 Check to see if user wants to reopen dead stream.
6045 Args: ps --
6046 reopenp --
6048 Result: 1 if the folder was successfully updatedn
6049 0 if not necessary
6051 ----*/
6053 ask_mailbox_reopen(struct pine *ps, int *reopenp)
6055 if(((ps->mail_stream->dtb
6056 && ((ps->mail_stream->dtb->flags & DR_NONEWMAIL)
6057 || (ps->mail_stream->rdonly
6058 && ps->mail_stream->dtb->flags & DR_NONEWMAILRONLY)))
6059 && (ps->reopen_rule == REOPEN_ASK_ASK_Y
6060 || ps->reopen_rule == REOPEN_ASK_ASK_N
6061 || ps->reopen_rule == REOPEN_ASK_NO_Y
6062 || ps->reopen_rule == REOPEN_ASK_NO_N))
6063 || ((ps->mail_stream->dtb
6064 && ps->mail_stream->rdonly
6065 && !(ps->mail_stream->dtb->flags & DR_LOCAL))
6066 && (ps->reopen_rule == REOPEN_YES_ASK_Y
6067 || ps->reopen_rule == REOPEN_YES_ASK_N
6068 || ps->reopen_rule == REOPEN_ASK_ASK_Y
6069 || ps->reopen_rule == REOPEN_ASK_ASK_N))){
6070 int deefault;
6072 switch(ps->reopen_rule){
6073 case REOPEN_YES_ASK_Y:
6074 case REOPEN_ASK_ASK_Y:
6075 case REOPEN_ASK_NO_Y:
6076 deefault = 'y';
6077 break;
6079 default:
6080 deefault = 'n';
6081 break;
6084 switch(want_to("Re-open folder to check for new messages", deefault,
6085 'x', h_reopen_folder, WT_NORM)){
6086 case 'y':
6087 (*reopenp)++;
6088 break;
6090 case 'x':
6091 return(-1);
6095 return(0);
6100 /*----------------------------------------------------------------------
6101 Check to see if user input is in form of old c-client mailbox speck
6103 Args: old --
6104 new --
6106 Result: 1 if the folder was successfully updatedn
6107 0 if not necessary
6109 ----*/
6111 update_folder_spec(char *new, size_t newlen, char *old)
6113 char *p, *orignew;
6114 int nntp = 0;
6116 orignew = new;
6117 if(*(p = old) == '*') /* old form? */
6118 old++;
6120 if(*old == '{') /* copy host spec */
6122 switch(*new = *old++){
6123 case '\0' :
6124 return(FALSE);
6126 case '/' :
6127 if(!struncmp(old, "nntp", 4))
6128 nntp++;
6130 break;
6132 default :
6133 break;
6135 while(*new++ != '}' && (new-orignew) < newlen-1);
6137 if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){
6139 * OK, some heuristics here. If it looks like a newsgroup
6140 * then we plunk it into the #news namespace else we
6141 * assume that they're trying to get at a #public folder...
6143 for(p = old;
6144 *p && (isalnum((unsigned char) *p) || strindex(".-", *p));
6145 p++)
6148 sstrncpy(&new, (*p && !nntp) ? "#public/" : "#news.", newlen-(new-orignew));
6149 strncpy(new, old, newlen-(new-orignew));
6150 return(TRUE);
6153 orignew[newlen-1] = '\0';
6155 return(FALSE);
6159 /*----------------------------------------------------------------------
6160 Open the requested folder in the requested context
6162 Args: state -- usual pine state struct
6163 newfolder -- folder to open
6164 new_context -- folder context might live in
6165 stream -- candidate for recycling
6167 Result: New folder open or not (if error), and we're set to
6168 enter the index screen.
6169 ----*/
6170 void
6171 visit_folder(struct pine *state, char *newfolder, CONTEXT_S *new_context,
6172 MAILSTREAM *stream, long unsigned int flags)
6174 dprint((9, "visit_folder(%s, %s)\n",
6175 newfolder ? newfolder : "?",
6176 (new_context && new_context->context)
6177 ? new_context->context : "(NULL)"));
6179 if(ps_global && ps_global->ttyo){
6180 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
6181 ps_global->mangled_footer = 1;
6184 if(do_broach_folder(newfolder, new_context, stream ? &stream : NULL,
6185 flags) >= 0
6186 || !sp_flagged(state->mail_stream, SP_LOCKED))
6187 state->next_screen = mail_index_screen;
6188 else
6189 state->next_screen = folder_screen;
6193 /*----------------------------------------------------------------------
6194 Move read messages from folder if listed in archive
6196 Args:
6198 ----*/
6200 read_msg_prompt(long int n, char *f)
6202 char buf[MAX_SCREEN_COLS+1];
6204 snprintf(buf, sizeof(buf), "Save the %ld read message%s in \"%s\"", n, plural(n), f);
6205 buf[sizeof(buf)-1] = '\0';
6206 return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y');
6210 /*----------------------------------------------------------------------
6211 Print current message[s] or folder index
6213 Args: state -- pointer to struct holding a bunch of pine state
6214 msgmap -- table mapping msg nums to c-client sequence nums
6215 aopt -- aggregate options
6216 in_index -- boolean indicating we're called from Index Screen
6218 Filters the original header and sends stuff to printer
6219 ---*/
6221 cmd_print(struct pine *state, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
6223 char prompt[250];
6224 long i, msgs, rawno;
6225 int next = 0, do_index = 0, rv = 0;
6226 ENVELOPE *e;
6227 BODY *b;
6228 MESSAGECACHE *mc;
6230 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
6231 return rv;
6233 msgs = mn_total_cur(msgmap);
6235 if((in_index != View) && F_ON(F_PRINT_INDEX, state)){
6236 char m[10];
6237 int ans;
6238 static ESCKEY_S prt_opts[] = {
6239 {'i', 'i', "I", N_("Index")},
6240 {'m', 'm', "M", NULL},
6241 {-1, 0, NULL, NULL}};
6243 if(in_index == ThrdIndx){
6244 /* TRANSLATORS: This is a question, Print Index ? */
6245 if(want_to(_("Print Index"), 'y', 'x', NO_HELP, WT_NORM) == 'y')
6246 ans = 'i';
6247 else
6248 ans = 'x';
6250 else{
6251 snprintf(m, sizeof(m), "Message%s", (msgs>1L) ? "s" : "");
6252 m[sizeof(m)-1] = '\0';
6253 prt_opts[1].label = m;
6254 snprintf(prompt, sizeof(prompt), "Print %sFolder Index or %s %s? ",
6255 (aopt & MCMD_AGG_2) ? "thread " : MCMD_ISAGG(aopt) ? "selected " : "",
6256 (aopt & MCMD_AGG_2) ? "thread" : MCMD_ISAGG(aopt) ? "selected" : "current", m);
6257 prompt[sizeof(prompt)-1] = '\0';
6259 ans = radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x',
6260 NO_HELP, RB_NORM|RB_SEQ_SENSITIVE);
6263 switch(ans){
6264 case 'x' :
6265 cmd_cancelled("Print");
6266 if(MCMD_ISAGG(aopt))
6267 restore_selected(msgmap);
6269 return rv;
6271 case 'i':
6272 do_index = 1;
6273 break;
6275 default :
6276 case 'm':
6277 break;
6281 if(do_index)
6282 snprintf(prompt, sizeof(prompt), "%sFolder Index",
6283 (aopt & MCMD_AGG_2) ? "Thread " : MCMD_ISAGG(aopt) ? "Selected " : "");
6284 else if(msgs > 1L)
6285 snprintf(prompt, sizeof(prompt), "%s messages", long2string(msgs));
6286 else
6287 snprintf(prompt, sizeof(prompt), "Message %s", long2string(mn_get_cur(msgmap)));
6289 prompt[sizeof(prompt)-1] = '\0';
6291 if(open_printer(prompt) < 0){
6292 if(MCMD_ISAGG(aopt))
6293 restore_selected(msgmap);
6295 return rv;
6298 if(do_index){
6299 TITLE_S *tc;
6301 tc = format_titlebar();
6303 /* Print titlebar... */
6304 print_text1("%s\n\n", tc ? tc->titlebar_line : "");
6305 /* then all the index members... */
6306 if(!print_index(state, msgmap, MCMD_ISAGG(aopt)))
6307 q_status_message(SM_ORDER | SM_DING, 3, 3,
6308 _("Error printing folder index"));
6309 else
6310 rv++;
6312 else{
6313 rv++;
6314 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){
6315 if(next && F_ON(F_AGG_PRINT_FF, state))
6316 if(!print_char(FORMFEED)){
6317 rv = 0;
6318 break;
6321 if(!(state->mail_stream
6322 && (rawno = mn_m2raw(msgmap, i)) > 0L
6323 && rawno <= state->mail_stream->nmsgs
6324 && (mc = mail_elt(state->mail_stream, rawno))
6325 && mc->valid))
6326 mc = NULL;
6328 if(!(e=pine_mail_fetchstructure(state->mail_stream,
6329 mn_m2raw(msgmap,i),
6330 &b))
6331 || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global)
6332 && !bezerk_delimiter(e, mc, print_char, next))
6333 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
6334 e, b, NULL, FM_NEW_MESS | FM_NOINDENT,
6335 print_char)){
6336 q_status_message(SM_ORDER | SM_DING, 3, 3,
6337 _("Error printing message"));
6338 rv = 0;
6339 break;
6344 close_printer();
6346 if(MCMD_ISAGG(aopt))
6347 restore_selected(msgmap);
6349 return rv;
6353 /*----------------------------------------------------------------------
6354 Pipe message text
6356 Args: state -- various pine state bits
6357 msgmap -- Message number mapping table
6358 aopt -- option flags
6360 Filters the original header and sends stuff to specified command
6361 ---*/
6363 cmd_pipe(struct pine *state, MSGNO_S *msgmap, int aopt)
6365 ENVELOPE *e;
6366 MESSAGECACHE *mc;
6367 BODY *b;
6368 PIPE_S *syspipe;
6369 char *resultfilename = NULL, prompt[80], *p;
6370 int done = 0, rv = 0;
6371 gf_io_t pc;
6372 int fourlabel = -1, j = 0, next = 0, ku;
6373 int pipe_rv; /* rv of proc to separate from close_system_pipe rv */
6374 long i, rawno;
6375 unsigned flagsforhist = 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6376 static HISTORY_S *history = NULL;
6377 int capture = 1, raw = 0, delimit = 0, newpipe = 0;
6378 char pipe_command[MAXPATH];
6379 ESCKEY_S pipe_opt[8];
6381 if(ps_global->restricted){
6382 q_status_message(SM_ORDER | SM_DING, 0, 4,
6383 "Alpine demo can't pipe messages");
6384 return rv;
6386 else if(!any_messages(msgmap, NULL, "to Pipe"))
6387 return rv;
6389 pipe_command[0] = '\0';
6390 init_hist(&history, HISTSIZE);
6391 flagsforhist = (raw ? 0x8 : 0) +
6392 (delimit ? 0x4 : 0) +
6393 (newpipe ? 0x2 : 0) +
6394 (capture ? 0x1 : 0);
6395 if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){
6396 strncpy(pipe_command, p, sizeof(pipe_command));
6397 pipe_command[sizeof(pipe_command)-1] = '\0';
6398 if(history->hist[history->curindex]){
6399 flagsforhist = history->hist[history->curindex]->flags;
6400 raw = (flagsforhist & 0x8) ? 1 : 0;
6401 delimit = (flagsforhist & 0x4) ? 1 : 0;
6402 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6403 capture = (flagsforhist & 0x1) ? 1 : 0;
6407 pipe_opt[j].ch = 0;
6408 pipe_opt[j].rval = 0;
6409 pipe_opt[j].name = "";
6410 pipe_opt[j++].label = "";
6412 pipe_opt[j].ch = ctrl('W');
6413 pipe_opt[j].rval = 10;
6414 pipe_opt[j].name = "^W";
6415 pipe_opt[j++].label = NULL;
6417 pipe_opt[j].ch = ctrl('Y');
6418 pipe_opt[j].rval = 11;
6419 pipe_opt[j].name = "^Y";
6420 pipe_opt[j++].label = NULL;
6422 pipe_opt[j].ch = ctrl('R');
6423 pipe_opt[j].rval = 12;
6424 pipe_opt[j].name = "^R";
6425 pipe_opt[j++].label = NULL;
6427 if(MCMD_ISAGG(aopt)){
6428 if(!pseudo_selected(state->mail_stream, msgmap))
6429 return rv;
6430 else{
6431 fourlabel = j;
6432 pipe_opt[j].ch = ctrl('T');
6433 pipe_opt[j].rval = 13;
6434 pipe_opt[j].name = "^T";
6435 pipe_opt[j++].label = NULL;
6439 pipe_opt[j].ch = KEY_UP;
6440 pipe_opt[j].rval = 30;
6441 pipe_opt[j].name = "";
6442 ku = j;
6443 pipe_opt[j++].label = "";
6445 pipe_opt[j].ch = KEY_DOWN;
6446 pipe_opt[j].rval = 31;
6447 pipe_opt[j].name = "";
6448 pipe_opt[j++].label = "";
6450 pipe_opt[j].ch = -1;
6452 while (!done) {
6453 int flags;
6455 snprintf(prompt, sizeof(prompt), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6456 raw ? "RAW " : "",
6457 MCMD_ISAGG(aopt) ? "s" : " ",
6458 MCMD_ISAGG(aopt) ? "" : comatose(mn_get_cur(msgmap)),
6459 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? "(" : "",
6460 capture ? "" : "uncaptured",
6461 (!capture && delimit) ? "," : "",
6462 delimit ? "delimited" : "",
6463 ((!capture || delimit) && newpipe && MCMD_ISAGG(aopt)) ? "," : "",
6464 (newpipe && MCMD_ISAGG(aopt)) ? "new pipe" : "",
6465 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? ") " : "");
6466 prompt[sizeof(prompt)-1] = '\0';
6467 pipe_opt[1].label = raw ? N_("Shown Text") : N_("Raw Text");
6468 pipe_opt[2].label = capture ? N_("Free Output") : N_("Capture Output");
6469 pipe_opt[3].label = delimit ? N_("No Delimiter") : N_("With Delimiter");
6470 if(fourlabel > 0)
6471 pipe_opt[fourlabel].label = newpipe ? N_("To Same Pipe") : N_("To Individual Pipes");
6475 * 2 is really 1 because there will be one real entry and
6476 * one entry of "" because of the get_prev_hist above.
6478 if(items_in_hist(history) > 2){
6479 pipe_opt[ku].name = HISTORY_UP_KEYNAME;
6480 pipe_opt[ku].label = HISTORY_KEYLABEL;
6481 pipe_opt[ku+1].name = HISTORY_DOWN_KEYNAME;
6482 pipe_opt[ku+1].label = HISTORY_KEYLABEL;
6484 else{
6485 pipe_opt[ku].name = "";
6486 pipe_opt[ku].label = "";
6487 pipe_opt[ku+1].name = "";
6488 pipe_opt[ku+1].label = "";
6491 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
6492 switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0,
6493 sizeof(pipe_command), prompt,
6494 pipe_opt, NO_HELP, &flags)){
6495 case -1 :
6496 q_status_message(SM_ORDER | SM_DING, 3, 4,
6497 _("Internal problem encountered"));
6498 done++;
6499 break;
6501 case 10 : /* flip raw bit */
6502 raw = !raw;
6503 break;
6505 case 11 : /* flip capture bit */
6506 capture = !capture;
6507 break;
6509 case 12 : /* flip delimit bit */
6510 delimit = !delimit;
6511 break;
6513 case 13 : /* flip newpipe bit */
6514 newpipe = !newpipe;
6515 break;
6517 case 30 :
6518 flagsforhist = (raw ? 0x8 : 0) +
6519 (delimit ? 0x4 : 0) +
6520 (newpipe ? 0x2 : 0) +
6521 (capture ? 0x1 : 0);
6522 if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6523 strncpy(pipe_command, p, sizeof(pipe_command));
6524 pipe_command[sizeof(pipe_command)-1] = '\0';
6525 if(history->hist[history->curindex]){
6526 flagsforhist = history->hist[history->curindex]->flags;
6527 raw = (flagsforhist & 0x8) ? 1 : 0;
6528 delimit = (flagsforhist & 0x4) ? 1 : 0;
6529 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6530 capture = (flagsforhist & 0x1) ? 1 : 0;
6533 else
6534 Writechar(BELL, 0);
6536 break;
6538 case 31 :
6539 flagsforhist = (raw ? 0x8 : 0) +
6540 (delimit ? 0x4 : 0) +
6541 (newpipe ? 0x2 : 0) +
6542 (capture ? 0x1 : 0);
6543 if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6544 strncpy(pipe_command, p, sizeof(pipe_command));
6545 pipe_command[sizeof(pipe_command)-1] = '\0';
6546 if(history->hist[history->curindex]){
6547 flagsforhist = history->hist[history->curindex]->flags;
6548 raw = (flagsforhist & 0x8) ? 1 : 0;
6549 delimit = (flagsforhist & 0x4) ? 1 : 0;
6550 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6551 capture = (flagsforhist & 0x1) ? 1 : 0;
6554 else
6555 Writechar(BELL, 0);
6557 break;
6559 case 0 :
6560 if(pipe_command[0]){
6562 flagsforhist = (raw ? 0x8 : 0) +
6563 (delimit ? 0x4 : 0) +
6564 (newpipe ? 0x2 : 0) +
6565 (capture ? 0x1 : 0);
6566 save_hist(history, pipe_command, flagsforhist, NULL);
6568 flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
6569 flags |= (raw ? PIPE_RAW : 0);
6570 if(!capture){
6571 #ifndef _WINDOWS
6572 ClearScreen();
6573 fflush(stdout);
6574 clear_cursor_pos();
6575 ps_global->mangled_screen = 1;
6576 ps_global->in_init_seq = 1;
6577 #endif
6578 flags |= PIPE_RESET;
6581 if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command,
6582 (flags & PIPE_RESET)
6583 ? NULL
6584 : &resultfilename,
6585 flags, &pc)))
6586 done++;
6588 for(i = mn_first_cur(msgmap);
6589 i > 0L && !done;
6590 i = mn_next_cur(msgmap)){
6591 e = pine_mail_fetchstructure(ps_global->mail_stream,
6592 mn_m2raw(msgmap, i), &b);
6593 if(!(state->mail_stream
6594 && (rawno = mn_m2raw(msgmap, i)) > 0L
6595 && rawno <= state->mail_stream->nmsgs
6596 && (mc = mail_elt(state->mail_stream, rawno))
6597 && mc->valid))
6598 mc = NULL;
6600 if((newpipe
6601 && !(syspipe = cmd_pipe_open(pipe_command,
6602 (flags & PIPE_RESET)
6603 ? NULL
6604 : &resultfilename,
6605 flags, &pc)))
6606 || (delimit && !bezerk_delimiter(e, mc, pc, next++)))
6607 done++;
6609 if(!done){
6610 if(raw){
6611 char *pipe_err;
6613 prime_raw_pipe_getc(ps_global->mail_stream,
6614 mn_m2raw(msgmap, i), -1L, 0L);
6615 gf_filter_init();
6616 gf_link_filter(gf_nvtnl_local, NULL);
6617 if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){
6618 q_status_message1(SM_ORDER|SM_DING,
6619 3, 3,
6620 _("Internal Error: %s"),
6621 pipe_err);
6622 done++;
6625 else if(!format_message(mn_m2raw(msgmap, i), e, b,
6626 NULL, FM_NEW_MESS | FM_NOWRAP, pc))
6627 done++;
6630 if(newpipe)
6631 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6632 done++;
6635 if(!capture)
6636 ps_global->in_init_seq = 0;
6638 if(!newpipe)
6639 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6640 done++;
6641 if(done) /* say we had a problem */
6642 q_status_message(SM_ORDER | SM_DING, 3, 3,
6643 _("Error piping message"));
6644 else if(resultfilename){
6645 rv++;
6646 /* only display if no error */
6647 display_output_file(resultfilename, "PIPE MESSAGE",
6648 NULL, DOF_EMPTY);
6649 fs_give((void **)&resultfilename);
6651 else{
6652 rv++;
6653 q_status_message(SM_ORDER, 0, 2, _("Pipe command completed"));
6656 done++;
6657 break;
6659 /* else fall thru as if cancelled */
6661 case 1 :
6662 cmd_cancelled("Pipe command");
6663 done++;
6664 break;
6666 case 3 :
6667 helper(h_common_pipe, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE);
6668 ps_global->mangled_screen = 1;
6669 break;
6671 case 2 : /* no place to escape to */
6672 case 4 : /* can't suspend */
6673 default :
6674 break;
6678 ps_global->mangled_footer = 1;
6679 if(MCMD_ISAGG(aopt))
6680 restore_selected(msgmap);
6682 return rv;
6686 /*----------------------------------------------------------------------
6687 Screen to offer list management commands contained in message
6689 Args: state -- pointer to struct holding a bunch of pine state
6690 msgmap -- table mapping msg nums to c-client sequence nums
6691 aopt -- aggregate options
6693 Result:
6695 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6696 ----*/
6697 void
6698 rfc2369_display(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno)
6700 int winner = 0;
6701 char *h, *hdrs[MLCMD_COUNT + 1];
6702 long index_no = mn_raw2m(msgmap, msgno);
6703 RFC2369_S data[MLCMD_COUNT];
6705 /* for each header field */
6706 if((h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))) != NULL){
6707 memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT);
6708 if(rfc2369_parse_fields(h, &data[0])){
6709 STORE_S *explain;
6711 if((explain = list_mgmt_text(data, index_no)) != NULL){
6712 list_mgmt_screen(explain);
6713 ps_global->mangled_screen = 1;
6714 so_give(&explain);
6715 winner++;
6719 fs_give((void **) &h);
6722 if(!winner)
6723 q_status_message1(SM_ORDER, 0, 3,
6724 "Message %s contains no list management information",
6725 comatose(index_no));
6729 STORE_S *
6730 list_mgmt_text(RFC2369_S *data, long int msgno)
6732 STORE_S *store;
6733 int i, j, n, fields = 0;
6734 static char *rfc2369_intro1 =
6735 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6736 static char *rfc2369_intro2[] = {
6737 N_(" has information associated with it "),
6738 N_("that explains how to participate in an email list. An "),
6739 N_("email list is represented by a single email address that "),
6740 N_("users sharing a common interest can send messages to (known "),
6741 N_("as posting) which are then redistributed to all members "),
6742 N_("of the list (sometimes after review by a moderator)."),
6743 N_("<P>List participation commands in this message include:"),
6744 NULL
6747 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6749 /* Insert introductory text */
6750 so_puts(store, rfc2369_intro1);
6752 so_puts(store, comatose(msgno));
6754 for(i = 0; rfc2369_intro2[i]; i++)
6755 so_puts(store, _(rfc2369_intro2[i]));
6757 so_puts(store, "<P>");
6758 for(i = 0; i < MLCMD_COUNT; i++)
6759 if(data[i].data[0].value
6760 || data[i].data[0].comment
6761 || data[i].data[0].error){
6762 if(!fields++)
6763 so_puts(store, "<UL>");
6765 so_puts(store, "<LI>");
6766 so_puts(store,
6767 (n = (data[i].data[1].value || data[i].data[1].comment))
6768 ? "Methods to "
6769 : "A method to ");
6771 so_puts(store, data[i].field.description);
6772 so_puts(store, ". ");
6774 if(n)
6775 so_puts(store, "<OL>");
6777 for(j = 0;
6778 j < MLCMD_MAXDATA
6779 && (data[i].data[j].comment
6780 || data[i].data[j].value
6781 || data[i].data[j].error);
6782 j++){
6784 so_puts(store, n ? "<P><LI>" : "<P>");
6786 if(data[i].data[j].comment){
6787 so_puts(store,
6788 _("With the provided comment:<P><BLOCKQUOTE>"));
6789 so_puts(store, data[i].data[j].comment);
6790 so_puts(store, "</BLOCKQUOTE><P>");
6793 if(data[i].data[j].value){
6794 if(i == MLCMD_POST
6795 && !strucmp(data[i].data[j].value, "NO")){
6796 so_puts(store,
6797 _("Posting is <EM>not</EM> allowed on this list"));
6799 else{
6800 so_puts(store, "Select <A HREF=\"");
6801 so_puts(store, data[i].data[j].value);
6802 so_puts(store, "\">HERE</A> to ");
6803 so_puts(store, (data[i].field.action)
6804 ? data[i].field.action
6805 : "try it");
6808 so_puts(store, ".");
6811 if(data[i].data[j].error){
6812 so_puts(store, "<P>Unfortunately, Alpine can not offer");
6813 so_puts(store, " to take direct action based upon it");
6814 so_puts(store, " because it was improperly formatted.");
6815 so_puts(store, " The unrecognized data associated with");
6816 so_puts(store, " the \"");
6817 so_puts(store, data[i].field.name);
6818 so_puts(store, "\" header field was:<P><BLOCKQUOTE>");
6819 so_puts(store, data[i].data[j].error);
6820 so_puts(store, "</BLOCKQUOTE>");
6823 so_puts(store, "<P>");
6826 if(n)
6827 so_puts(store, "</OL>");
6830 if(fields)
6831 so_puts(store, "</UL>");
6833 so_puts(store, "</BODY></HTML>");
6836 return(store);
6840 void
6841 list_mgmt_screen(STORE_S *html)
6843 int cmd = MC_NONE;
6844 long offset = 0L;
6845 char *error = NULL;
6846 STORE_S *store;
6847 HANDLE_S *handles = NULL;
6848 gf_io_t gc, pc;
6851 so_seek(html, 0L, 0);
6852 gf_set_so_readc(&gc, html);
6854 init_handles(&handles);
6856 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6857 gf_set_so_writec(&pc, store);
6858 gf_filter_init();
6860 gf_link_filter(gf_html2plain,
6861 gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols,
6862 non_messageview_margin(), &handles, NULL, 0));
6864 error = gf_pipe(gc, pc);
6866 gf_clear_so_writec(store);
6868 if(!error){
6869 SCROLL_S sargs;
6871 memset(&sargs, 0, sizeof(SCROLL_S));
6872 sargs.text.text = so_text(store);
6873 sargs.text.src = CharStar;
6874 sargs.text.desc = "list commands";
6875 sargs.text.handles = handles;
6876 if(offset){
6877 sargs.start.on = Offset;
6878 sargs.start.loc.offset = offset;
6881 sargs.bar.title = _("MAIL LIST COMMANDS");
6882 sargs.bar.style = MessageNumber;
6883 sargs.resize_exit = 1;
6884 sargs.help.text = h_special_list_commands;
6885 sargs.help.title = _("HELP FOR LIST COMMANDS");
6886 sargs.keys.menu = &listmgr_keymenu;
6887 setbitmap(sargs.keys.bitmap);
6888 if(!handles){
6889 clrbitn(LM_TRY_KEY, sargs.keys.bitmap);
6890 clrbitn(LM_PREV_KEY, sargs.keys.bitmap);
6891 clrbitn(LM_NEXT_KEY, sargs.keys.bitmap);
6894 cmd = scrolltool(&sargs);
6895 offset = sargs.start.loc.offset;
6898 so_give(&store);
6901 free_handles(&handles);
6902 gf_clear_so_readc(html);
6904 while(cmd == MC_RESIZE);
6908 /*----------------------------------------------------------------------
6909 Prompt the user for the type of select desired
6911 NOTE: any and all functions that successfully exit the second
6912 switch() statement below (currently "select_*() functions"),
6913 *MUST* update the folder's MESSAGECACHE element's "searched"
6914 bits to reflect the search result. Functions using
6915 mail_search() get this for free, the others must update 'em
6916 by hand.
6918 Returns -1 if canceled without changing selection
6919 0 if selection may have changed
6920 ----*/
6922 aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index)
6924 long i, diff, old_tot, msgno, raw;
6925 int p = 0, q = 0, rv = 0, narrow = 0, hidden, ret = -1;
6926 ESCKEY_S *sel_opts;
6927 MESSAGECACHE *mc;
6928 SEARCHSET *limitsrch = NULL;
6929 PINETHRD_S *thrd;
6930 extern MAILSTREAM *mm_search_stream;
6931 extern long mm_search_count;
6933 hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
6934 mm_search_stream = state->mail_stream;
6935 mm_search_count = 0L;
6937 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
6938 sel_opts = THRD_INDX() ? sel_opts6 : sel_opts5;
6939 else
6940 sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2;
6942 if(THREADING()){
6943 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH;
6945 else{
6946 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream)){
6947 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_XGMEXT_CH;
6948 sel_opts[SEL_OPTS_THREAD].rval = sel_opts[SEL_OPTS_XGMEXT].rval;
6949 sel_opts[SEL_OPTS_THREAD].name = sel_opts[SEL_OPTS_XGMEXT].name;
6950 sel_opts[SEL_OPTS_THREAD].label = sel_opts[SEL_OPTS_XGMEXT].label;
6951 sel_opts[SEL_OPTS_XGMEXT].ch = -1;
6953 else
6954 sel_opts[SEL_OPTS_THREAD].ch = -1;
6957 if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){
6958 if(THRD_INDX()){
6959 i = 0;
6960 thrd = fetch_thread(state->mail_stream,
6961 mn_m2raw(msgmap, mn_get_cur(msgmap)));
6962 /* check if whole thread is selected or not */
6963 if(thrd &&
6964 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT)
6966 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE))
6967 i = 1;
6969 sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd");
6971 else{
6972 i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap),
6973 MN_SLCT);
6974 sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur");
6977 sel_opts += 2; /* disable extra options */
6978 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
6979 q = double_radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6980 RB_NORM);
6981 else
6982 q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6983 RB_NORM);
6984 switch(q){
6985 case 'f' : /* flip selection */
6986 msgno = 0L;
6987 for(i = 1L; i <= mn_get_total(msgmap); i++){
6988 ret = 0;
6989 q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT);
6990 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q);
6991 if(hidden){
6992 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q);
6993 if(!msgno && q)
6994 mn_reset_cur(msgmap, msgno = i);
6998 return(ret);
7000 case 'n' : /* narrow selection */
7001 narrow++;
7002 case 'r' : /* replace selection */
7003 p = 1; /* set flag we want to replace */
7004 sel_opts -= 2; /* re-enable first two options */
7005 case 'b' : /* broaden selection */
7006 q = 0; /* offer criteria prompt */
7007 break;
7009 case 'c' : /* Un/Select Current */
7010 case 'a' : /* Unselect All */
7011 case 'x' : /* cancel */
7012 break;
7014 default :
7015 q_status_message(SM_ORDER | SM_DING, 3, 3,
7016 "Unsupported Select option");
7017 return(ret);
7021 if(!q){
7022 while(1){
7023 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
7024 q = double_radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP,
7025 RB_NORM);
7026 else
7027 q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP,
7028 RB_NORM|RB_RET_HELP);
7030 if(q == 3){
7031 helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE);
7032 ps_global->mangled_screen = 1;
7034 else
7035 break;
7039 /* When we are replacing the search, unselect all messages first, unless
7040 * we are cancelling, in whose case, leave the screen as is, because we
7041 * are cancelling!
7043 if(p == 1 && q != 'x'){
7044 msgno = any_lflagged(msgmap, MN_SLCT);
7045 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
7046 agg_select_all(state->mail_stream, msgmap, &diff,
7047 any_lflagged(msgmap, MN_SLCT) <= 0L);
7051 * The purpose of this is to add the appropriate searchset to the
7052 * search so that the search can be limited to only looking at what
7053 * it needs to look at. That is, if we are narrowing then we only need
7054 * to look at messages which are already selected, and if we are
7055 * broadening, then we only need to look at messages which are not
7056 * yet selected. This routine will work whether or not
7057 * limiting_searchset properly limits the search set. In particular,
7058 * the searchset returned by limiting_searchset may include messages
7059 * which really shouldn't be included. We do that because a too-large
7060 * searchset will break some IMAP servers. It is even possible that it
7061 * becomes inefficient to send the whole set. If the select function
7062 * frees limitsrch, it should be sure to set it to NULL so we won't
7063 * try freeing it again here.
7065 limitsrch = limiting_searchset(state->mail_stream, narrow);
7068 * NOTE: See note about MESSAGECACHE "searched" bits above!
7070 switch(q){
7071 case 'x': /* cancel */
7072 cmd_cancelled("Select command");
7073 return(ret);
7075 case 'c' : /* select/unselect current */
7076 (void) select_by_current(state, msgmap, in_index);
7077 ret = 0;
7078 return(ret);
7080 case 'a' : /* select/unselect all */
7081 msgno = any_lflagged(msgmap, MN_SLCT);
7082 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
7083 ret = 0;
7084 agg_select_all(state->mail_stream, msgmap, &diff,
7085 any_lflagged(msgmap, MN_SLCT) <= 0L);
7086 q_status_message4(SM_ORDER,0,2,
7087 "%s%s message%s %sselected",
7088 msgno ? "" : "All ", comatose(diff),
7089 plural(diff), msgno ? "UN" : "");
7090 return(ret);
7092 case 'n' : /* Select by Number */
7093 ret = 0;
7094 if(THRD_INDX())
7095 rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch);
7096 else
7097 rv = select_by_number(state->mail_stream, msgmap, &limitsrch);
7099 break;
7101 case 'd' : /* Select by Date */
7102 ret = 0;
7103 rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap),
7104 &limitsrch);
7105 break;
7107 case 't' : /* Text */
7108 ret = 0;
7109 rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap),
7110 &limitsrch);
7111 break;
7113 case 'z' : /* Size */
7114 ret = 0;
7115 rv = select_by_size(state->mail_stream, &limitsrch);
7116 break;
7118 case 's' : /* Status */
7119 ret = 0;
7120 rv = select_by_status(state->mail_stream, &limitsrch);
7121 break;
7123 case 'k' : /* Keyword */
7124 ret = 0;
7125 rv = select_by_keyword(state->mail_stream, &limitsrch);
7126 break;
7128 case 'r' : /* Rule */
7129 ret = 0;
7130 rv = select_by_rule(state->mail_stream, &limitsrch);
7131 break;
7133 case 'h' : /* Thread */
7134 ret = 0;
7135 rv = select_by_thread(state->mail_stream, msgmap, &limitsrch);
7136 break;
7138 case 'g' : /* X-GM-EXT-1 */
7139 ret = 0;
7140 rv = select_by_gm_content(state->mail_stream, msgmap, mn_get_cur(msgmap), &limitsrch);
7141 break;
7143 default :
7144 q_status_message(SM_ORDER | SM_DING, 3, 3,
7145 "Unsupported Select option");
7146 return(ret);
7149 if(limitsrch)
7150 mail_free_searchset(&limitsrch);
7152 if(rv) /* bad return value.. */
7153 return(ret); /* error already displayed */
7155 if(narrow) /* make sure something was selected */
7156 for(i = 1L; i <= mn_get_total(msgmap); i++)
7157 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7158 && raw <= state->mail_stream->nmsgs
7159 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7160 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
7161 break;
7162 else
7163 mm_search_count--;
7166 diff = 0L;
7167 if(mm_search_count){
7169 * loop thru all the messages, adjusting local flag bits
7170 * based on their "searched" bit...
7172 for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++)
7173 if(narrow){
7174 /* turning OFF selectedness if the "searched" bit isn't lit. */
7175 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7176 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7177 && raw <= state->mail_stream->nmsgs
7178 && (mc = mail_elt(state->mail_stream, raw))
7179 && !mc->searched){
7180 diff--;
7181 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
7182 if(hidden)
7183 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
7185 /* adjust current message in case we unselect and hide it */
7186 else if(msgno < mn_get_cur(msgmap)
7187 && (!THRD_INDX()
7188 || !get_lflag(state->mail_stream, msgmap,
7189 i, MN_CHID)))
7190 msgno = i;
7193 else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7194 && raw <= state->mail_stream->nmsgs
7195 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7196 /* turn ON selectedness if "searched" bit is lit. */
7197 if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7198 diff++;
7199 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
7200 if(hidden)
7201 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
7205 /* if we're zoomed and the current message was unselected */
7206 if(narrow && msgno
7207 && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE))
7208 mn_reset_cur(msgmap, msgno);
7211 if(!diff){
7212 if(narrow)
7213 q_status_message4(SM_ORDER, 3, 3,
7214 "%s. %s message%s remain%s selected.",
7215 mm_search_count
7216 ? "No change resulted"
7217 : "No messages in intersection",
7218 comatose(old_tot), plural(old_tot),
7219 (old_tot == 1L) ? "s" : "");
7220 else if(old_tot)
7221 q_status_message(SM_ORDER, 3, 3,
7222 _("No change resulted. Matching messages already selected."));
7223 else
7224 q_status_message1(SM_ORDER | SM_DING, 3, 3,
7225 _("Select failed. No %smessages selected."),
7226 old_tot ? _("additional ") : "");
7228 else if(old_tot){
7229 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
7230 "Select matched %ld message%s. %s %smessage%s %sselected.",
7231 (diff > 0) ? diff : old_tot + diff,
7232 plural((diff > 0) ? diff : old_tot + diff),
7233 comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7234 (diff > 0) ? "total " : "",
7235 plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7236 (diff > 0) ? "" : "UN");
7237 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
7238 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
7240 else
7241 q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"),
7242 comatose(diff), plural(diff));
7244 return(ret);
7248 /*----------------------------------------------------------------------
7249 Toggle the state of the current message
7251 Args: state -- pointer pine's state variables
7252 msgmap -- message collection to operate on
7253 in_index -- in the message index view
7254 Returns: TRUE if current marked selected, FALSE otw
7255 ----*/
7257 select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
7259 long cur;
7260 int all_selected = 0;
7261 unsigned long was, tot, rawno;
7262 PINETHRD_S *thrd;
7264 cur = mn_get_cur(msgmap);
7266 if(THRD_INDX()){
7267 thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur));
7268 if(!thrd)
7269 return 0;
7271 was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT);
7272 tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE);
7273 if(was == tot)
7274 all_selected++;
7276 if(all_selected){
7277 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0);
7278 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7279 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1);
7281 * See if there's anything left to zoom on. If so,
7282 * pick an adjacent one for highlighting, else make
7283 * sure nothing is left hidden...
7285 if(any_lflagged(msgmap, MN_SLCT)){
7286 mn_inc_cur(state->mail_stream, msgmap,
7287 (in_index == View && THREADING()
7288 && sp_viewing_a_thread(state->mail_stream))
7289 ? MH_THISTHD
7290 : (in_index == View)
7291 ? MH_ANYTHD : MH_NONE);
7292 if(mn_get_cur(msgmap) == cur)
7293 mn_dec_cur(state->mail_stream, msgmap,
7294 (in_index == View && THREADING()
7295 && sp_viewing_a_thread(state->mail_stream))
7296 ? MH_THISTHD
7297 : (in_index == View)
7298 ? MH_ANYTHD : MH_NONE);
7300 else /* clear all hidden flags */
7301 (void) unzoom_index(state, state->mail_stream, msgmap);
7304 else
7305 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1);
7307 q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected",
7308 comatose(all_selected ? was : tot-was),
7309 plural(all_selected ? was : tot-was),
7310 all_selected ? "UN" : "");
7312 /* collapsed thread */
7313 else if(THREADING()
7314 && ((rawno = mn_m2raw(msgmap, cur)) != 0L)
7315 && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL)
7316 && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){
7318 * This doesn't work quite the same as the colon command works, but
7319 * it is arguably doing the correct thing. The difference is
7320 * that aggregate_select will zoom after selecting back where it
7321 * was called from, but selecting a thread with colon won't zoom.
7322 * Maybe it makes sense to zoom after a select but not after a colon
7323 * command even though they are very similar.
7325 thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state));
7327 else{
7328 if((all_selected =
7329 get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */
7330 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0);
7331 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7332 set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1);
7334 * See if there's anything left to zoom on. If so,
7335 * pick an adjacent one for highlighting, else make
7336 * sure nothing is left hidden...
7338 if(any_lflagged(msgmap, MN_SLCT)){
7339 mn_inc_cur(state->mail_stream, msgmap,
7340 (in_index == View && THREADING()
7341 && sp_viewing_a_thread(state->mail_stream))
7342 ? MH_THISTHD
7343 : (in_index == View)
7344 ? MH_ANYTHD : MH_NONE);
7345 if(mn_get_cur(msgmap) == cur)
7346 mn_dec_cur(state->mail_stream, msgmap,
7347 (in_index == View && THREADING()
7348 && sp_viewing_a_thread(state->mail_stream))
7349 ? MH_THISTHD
7350 : (in_index == View)
7351 ? MH_ANYTHD : MH_NONE);
7353 else /* clear all hidden flags */
7354 (void) unzoom_index(state, state->mail_stream, msgmap);
7357 else
7358 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1);
7360 q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected",
7361 long2string(cur), all_selected ? "UN" : "");
7365 return(!all_selected);
7369 /*----------------------------------------------------------------------
7370 Prompt the user for the command to perform on selected messages
7372 Args: state -- pointer pine's state variables
7373 msgmap -- message collection to operate on
7374 q_line -- line on display to write prompts
7375 Returns: 1 if the selected messages are suitably commanded,
7376 0 if the choice to pick the command was declined
7378 ----*/
7380 apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
7381 UCS preloadkeystroke, int flags, int q_line)
7383 int i = 8, /* number of static entries in sel_opts3 */
7384 rv = 0,
7385 cmd,
7386 we_cancel = 0,
7387 agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG;
7388 char prompt[80];
7389 PAT_STATE pstate;
7392 * To do this "right", we really ought to have access to the keymenu
7393 * here and change the typed command into a real command by running
7394 * it through menu_command. Then the switch below would be against
7395 * results from menu_command. If we did that we'd also pass the
7396 * results of menu_command in as preloadkeystroke instead of passing
7397 * the keystroke itself. But we don't have the keymenu handy,
7398 * so we have to fake it. The only complication that we run into
7399 * is that KEY_DEL is an escape sequence so we change a typed
7400 * KEY_DEL esc seq into the letter D.
7403 if(!preloadkeystroke){
7404 if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
7405 sel_opts3[i].ch = '*';
7406 sel_opts3[i].rval = '*';
7407 sel_opts3[i].name = "*";
7408 sel_opts3[i++].label = N_("Flag");
7411 if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
7412 sel_opts3[i].ch = '|';
7413 sel_opts3[i].rval = '|';
7414 sel_opts3[i].name = "|";
7415 sel_opts3[i++].label = N_("Pipe");
7418 if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */
7419 sel_opts3[i].ch = 'b';
7420 sel_opts3[i].rval = 'b';
7421 sel_opts3[i].name = "B";
7422 sel_opts3[i++].label = N_("Bounce");
7425 if(flags & AC_FROM_THREAD){
7426 if(flags & (AC_COLL | AC_EXPN)){
7427 sel_opts3[i].ch = '/';
7428 sel_opts3[i].rval = '/';
7429 sel_opts3[i].name = "/";
7430 sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse")
7431 : N_("Expand");
7434 sel_opts3[i].ch = ';';
7435 sel_opts3[i].rval = ';';
7436 sel_opts3[i].name = ";";
7437 if(flags & AC_UNSEL)
7438 sel_opts3[i++].label = N_("UnSelect");
7439 else
7440 sel_opts3[i++].label = N_("Select");
7443 if(F_ON(F_ENABLE_PRYNT, state)){ /* this one is invisible */
7444 sel_opts3[i].ch = 'y';
7445 sel_opts3[i].rval = '%';
7446 sel_opts3[i].name = "";
7447 sel_opts3[i++].label = "";
7450 if(!is_imap_stream(stream) || LEVELUIDPLUS(stream)){ /* expunge selected messages */
7451 sel_opts3[i].ch = 'x';
7452 sel_opts3[i].rval = 'x';
7453 sel_opts3[i].name = "X";
7454 sel_opts3[i++].label = N_("Expunge");
7457 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7458 sel_opts3[i].ch = '#';
7459 sel_opts3[i].rval = '#';
7460 sel_opts3[i].name = "#";
7461 sel_opts3[i++].label = N_("Set Role");
7464 sel_opts3[i].ch = KEY_DEL; /* also invisible */
7465 sel_opts3[i].rval = 'd';
7466 sel_opts3[i].name = "";
7467 sel_opts3[i++].label = "";
7469 sel_opts3[i].ch = -1;
7471 snprintf(prompt, sizeof(prompt), "%s command : ",
7472 (flags & AC_FROM_THREAD) ? "THREAD" : "APPLY");
7473 prompt[sizeof(prompt)-1] = '\0';
7474 cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'c', NO_HELP,
7475 RB_SEQ_SENSITIVE);
7476 if(isupper(cmd))
7477 cmd = tolower(cmd);
7479 else{
7480 if(preloadkeystroke == KEY_DEL)
7481 cmd = 'd';
7482 else{
7483 if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke))
7484 cmd = tolower((int) preloadkeystroke);
7485 else
7486 cmd = (int) preloadkeystroke; /* shouldn't happen */
7490 switch(cmd){
7491 case 'd' : /* delete */
7492 we_cancel = busy_cue(NULL, NULL, 1);
7493 rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */
7494 if(we_cancel)
7495 cancel_busy_cue(0);
7496 break;
7498 case 'u' : /* undelete */
7499 we_cancel = busy_cue(NULL, NULL, 1);
7500 rv = cmd_undelete(state, msgmap, agg);
7501 if(we_cancel)
7502 cancel_busy_cue(0);
7503 break;
7505 case 'r' : /* reply */
7506 rv = cmd_reply(state, msgmap, agg, NULL);
7507 break;
7509 case 'f' : /* Forward */
7510 rv = cmd_forward(state, msgmap, agg, NULL);
7511 break;
7513 case '%' : /* print */
7514 rv = cmd_print(state, msgmap, agg, MsgIndx);
7515 break;
7517 case 't' : /* take address */
7518 rv = cmd_take_addr(state, msgmap, agg);
7519 break;
7521 case 's' : /* save */
7522 rv = cmd_save(state, stream, msgmap, agg, MsgIndx);
7523 break;
7525 case 'e' : /* export */
7526 rv = cmd_export(state, msgmap, q_line, agg);
7527 break;
7529 case '|' : /* pipe */
7530 rv = cmd_pipe(state, msgmap, agg);
7531 break;
7533 case '*' : /* flag */
7534 we_cancel = busy_cue(NULL, NULL, 1);
7535 rv = cmd_flag(state, msgmap, agg);
7536 if(we_cancel)
7537 cancel_busy_cue(0);
7538 break;
7540 case 'b' : /* bounce */
7541 rv = cmd_bounce(state, msgmap, agg, NULL);
7542 break;
7544 case '/' :
7545 collapse_or_expand(state, stream, msgmap,
7546 F_ON(F_SLASH_COLL_ENTIRE, ps_global)
7547 ? 0L
7548 : mn_get_cur(msgmap));
7549 break;
7551 case ':' :
7552 select_thread_stmp(state, stream, msgmap);
7553 break;
7555 case 'x' : /* Expunge */
7556 rv = cmd_expunge(state, stream, msgmap, agg);
7557 break;
7559 case 'c' : /* cancel */
7560 cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command"
7561 : "Apply command");
7562 break;
7564 case '#' :
7565 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7566 static ESCKEY_S choose_role[] = {
7567 {'r', 'r', "R", N_("Reply")},
7568 {'f', 'f', "F", N_("Forward")},
7569 {'b', 'b', "B", N_("Bounce")},
7570 {-1, 0, NULL, NULL}
7572 int action;
7573 ACTION_S *role = NULL;
7575 action = radio_buttons(_("Reply, Forward or Bounce using a role? "),
7576 -FOOTER_ROWS(state), choose_role,
7577 'r', 'x', h_role_aggregate, RB_NORM);
7578 if(action == 'r' || action == 'f' || action == 'b'){
7579 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
7581 redraw = state->redrawer;
7582 state->redrawer = NULL;
7583 prev_screen = state->prev_screen;
7584 role = NULL;
7585 state->next_screen = SCREEN_FUN_NULL;
7587 if(role_select_screen(state, &role,
7588 action == 'f' ? MC_FORWARD :
7589 action == 'r' ? MC_REPLY :
7590 action == 'b' ? MC_BOUNCE : 0) < 0){
7591 cmd_cancelled(action == 'f' ? _("Forward") :
7592 action == 'r' ? _("Reply") : _("Bounce"));
7593 state->next_screen = prev_screen;
7594 state->redrawer = redraw;
7595 state->mangled_screen = 1;
7597 else{
7598 if(role)
7599 role = combine_inherited_role(role);
7600 else{
7601 role = (ACTION_S *) fs_get(sizeof(*role));
7602 memset((void *) role, 0, sizeof(*role));
7603 role->nick = cpystr("Default Role");
7606 state->redrawer = NULL;
7607 switch(action){
7608 case 'r':
7609 (void) cmd_reply(state, msgmap, agg, role);
7610 break;
7612 case 'f':
7613 (void) cmd_forward(state, msgmap, agg, role);
7614 break;
7616 case 'b':
7617 (void) cmd_bounce(state, msgmap, agg, role);
7618 break;
7621 if(role)
7622 free_action(&role);
7624 if(redraw)
7625 (*redraw)();
7627 state->next_screen = prev_screen;
7628 state->redrawer = redraw;
7629 state->mangled_screen = 1;
7633 break;
7635 case 'z' : /* default */
7636 q_status_message(SM_INFO, 0, 2,
7637 "Cancelled, there is no default command");
7638 break;
7640 default:
7641 break;
7644 return(rv);
7649 * Select by message number ranges.
7650 * Sets searched bits in mail_elts
7652 * Args limitsrch -- limit search to this searchset
7654 * Returns 0 on success.
7657 select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
7659 int r, end, cur;
7660 long n1, n2, raw;
7661 char number1[16], number2[16], numbers[80], *p, *t;
7662 HelpType help;
7663 MESSAGECACHE *mc;
7665 numbers[0] = '\0';
7666 ps_global->mangled_footer = 1;
7667 help = NO_HELP;
7668 while(1){
7669 int flags = OE_APPEND_CURRENT;
7671 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7672 sizeof(numbers), _(select_num), NULL, help, &flags);
7673 if(r == 4)
7674 continue;
7676 if(r == 3){
7677 help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
7678 continue;
7681 for(t = p = numbers; *p ; p++) /* strip whitespace */
7682 if(!isspace((unsigned char)*p))
7683 *t++ = *p;
7685 *t = '\0';
7687 if(r == 1 || numbers[0] == '\0'){
7688 cmd_cancelled("Selection by number");
7689 return(1);
7691 else
7692 break;
7695 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7696 if((mc = mail_elt(stream, n1)) != NULL)
7697 mc->searched = 0; /* clear searched bits */
7699 for(p = numbers; *p ; p++){
7700 t = number1;
7701 while(*p && isdigit((unsigned char)*p))
7702 *t++ = *p++;
7704 *t = '\0';
7706 end = cur = 0;
7707 if(number1[0] == '\0'){
7708 if(*p == '-'){
7709 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7710 _("Invalid number range, missing number before \"-\": %s"),
7711 numbers);
7712 return(1);
7714 else if(!strucmp("end", p)){
7715 end = 1;
7716 p += strlen("end");
7718 else if(!strucmp("$", p)){
7719 end = 1;
7720 p++;
7722 else if(*p == '.'){
7723 cur = 1;
7724 p++;
7726 else{
7727 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7728 _("Invalid message number: %s"), numbers);
7729 return(1);
7733 if(end)
7734 n1 = mn_get_total(msgmap);
7735 else if(cur)
7736 n1 = mn_get_cur(msgmap);
7737 else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
7738 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7739 _("\"%s\" out of message number range"),
7740 long2string(n1));
7741 return(1);
7744 t = number2;
7745 if(*p == '-'){
7746 while(*++p && isdigit((unsigned char)*p))
7747 *t++ = *p;
7749 *t = '\0';
7751 end = cur = 0;
7752 if(number2[0] == '\0'){
7753 if(!strucmp("end", p)){
7754 end = 1;
7755 p += strlen("end");
7757 else if(!strucmp(p, "$")){
7758 end = 1;
7759 p++;
7761 else if(*p == '.'){
7762 cur = 1;
7763 p++;
7765 else{
7766 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7767 _("Invalid number range, missing number after \"-\": %s"),
7768 numbers);
7769 return(1);
7773 if(end)
7774 n2 = mn_get_total(msgmap);
7775 else if(cur)
7776 n2 = mn_get_cur(msgmap);
7777 else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){
7778 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7779 _("\"%s\" out of message number range"),
7780 long2string(n2));
7781 return(1);
7784 if(n2 <= n1){
7785 char t[20];
7787 strncpy(t, long2string(n1), sizeof(t));
7788 t[sizeof(t)-1] = '\0';
7789 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7790 _("Invalid reverse message number range: %s-%s"),
7791 t, long2string(n2));
7792 return(1);
7795 for(;n1 <= n2; n1++){
7796 raw = mn_m2raw(msgmap, n1);
7797 if(raw > 0L
7798 && (!(limitsrch && *limitsrch)
7799 || in_searchset(*limitsrch, (unsigned long) raw)))
7800 mm_searched(stream, raw);
7803 else{
7804 raw = mn_m2raw(msgmap, n1);
7805 if(raw > 0L
7806 && (!(limitsrch && *limitsrch)
7807 || in_searchset(*limitsrch, (unsigned long) raw)))
7808 mm_searched(stream, raw);
7811 if(*p == '\0')
7812 break;
7815 return(0);
7820 * Select by thread number ranges.
7821 * Sets searched bits in mail_elts
7823 * Args limitsrch -- limit search to this searchset
7825 * Returns 0 on success.
7828 select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset)
7830 int r, end, cur;
7831 long n1, n2;
7832 char number1[16], number2[16], numbers[80], *p, *t;
7833 HelpType help;
7834 PINETHRD_S *thrd = NULL, *th;
7835 MESSAGECACHE *mc;
7837 numbers[0] = '\0';
7838 ps_global->mangled_footer = 1;
7839 help = NO_HELP;
7840 while(1){
7841 int flags = OE_APPEND_CURRENT;
7843 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7844 sizeof(numbers), _(select_num), NULL, help, &flags);
7845 if(r == 4)
7846 continue;
7848 if(r == 3){
7849 help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP;
7850 continue;
7853 for(t = p = numbers; *p ; p++) /* strip whitespace */
7854 if(!isspace((unsigned char)*p))
7855 *t++ = *p;
7857 *t = '\0';
7859 if(r == 1 || numbers[0] == '\0'){
7860 cmd_cancelled("Selection by number");
7861 return(1);
7863 else
7864 break;
7867 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7868 if((mc = mail_elt(stream, n1)) != NULL)
7869 mc->searched = 0; /* clear searched bits */
7871 for(p = numbers; *p ; p++){
7872 t = number1;
7873 while(*p && isdigit((unsigned char)*p))
7874 *t++ = *p++;
7876 *t = '\0';
7878 end = cur = 0;
7879 if(number1[0] == '\0'){
7880 if(*p == '-'){
7881 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7882 _("Invalid number range, missing number before \"-\": %s"),
7883 numbers);
7884 return(1);
7886 else if(!strucmp("end", p)){
7887 end = 1;
7888 p += strlen("end");
7890 else if(!strucmp(p, "$")){
7891 end = 1;
7892 p++;
7894 else if(*p == '.'){
7895 cur = 1;
7896 p++;
7898 else{
7899 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7900 _("Invalid thread number: %s"), numbers);
7901 return(1);
7905 if(end)
7906 n1 = msgmap->max_thrdno;
7907 else if(cur){
7908 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7909 n1 = th->thrdno;
7911 else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){
7912 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7913 _("\"%s\" out of thread number range"),
7914 long2string(n1));
7915 return(1);
7918 t = number2;
7919 if(*p == '-'){
7921 while(*++p && isdigit((unsigned char)*p))
7922 *t++ = *p;
7924 *t = '\0';
7926 end = 0;
7927 if(number2[0] == '\0'){
7928 if(!strucmp("end", p)){
7929 end = 1;
7930 p += strlen("end");
7932 else if(!strucmp("$", p)){
7933 end = 1;
7934 p++;
7936 else if(*p == '.'){
7937 cur = 1;
7938 p++;
7940 else{
7941 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7942 _("Invalid number range, missing number after \"-\": %s"),
7943 numbers);
7944 return(1);
7948 if(end)
7949 n2 = msgmap->max_thrdno;
7950 else if(cur){
7951 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7952 n2 = th->thrdno;
7954 else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){
7955 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7956 _("\"%s\" out of thread number range"),
7957 long2string(n2));
7958 return(1);
7961 if(n2 <= n1){
7962 char t[20];
7964 strncpy(t, long2string(n1), sizeof(t));
7965 t[sizeof(t)-1] = '\0';
7966 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7967 _("Invalid reverse message number range: %s-%s"),
7968 t, long2string(n2));
7969 return(1);
7972 for(;n1 <= n2; n1++){
7973 thrd = find_thread_by_number(stream, msgmap, n1, thrd);
7975 if(thrd)
7976 set_search_bit_for_thread(stream, thrd, msgset);
7979 else{
7980 thrd = find_thread_by_number(stream, msgmap, n1, NULL);
7982 if(thrd)
7983 set_search_bit_for_thread(stream, thrd, msgset);
7986 if(*p == '\0')
7987 break;
7990 return(0);
7995 * Select by message dates.
7996 * Sets searched bits in mail_elts
7998 * Args limitsrch -- limit search to this searchset
8000 * Returns 0 on success.
8003 select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8005 int r, we_cancel = 0, when = 0;
8006 char date[100], defdate[100], prompt[128];
8007 time_t seldate = time(0);
8008 struct tm *seldate_tm;
8009 SEARCHPGM *pgm;
8010 HelpType help;
8011 static struct _tense {
8012 char *preamble,
8013 *range,
8014 *scope;
8015 } tense[] = {
8016 {"were ", "SENT SINCE", " (inclusive)"},
8017 {"were ", "SENT BEFORE", " (exclusive)"},
8018 {"were ", "SENT ON", "" },
8019 {"", "ARRIVED SINCE", " (inclusive)"},
8020 {"", "ARRIVED BEFORE", " (exclusive)"},
8021 {"", "ARRIVED ON", "" }
8024 date[0] = '\0';
8025 ps_global->mangled_footer = 1;
8026 help = NO_HELP;
8029 * If talking to an old server, default to SINCE instead of
8030 * SENTSINCE, which was added later.
8032 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8033 when = 3;
8035 while(1){
8036 int flags = OE_APPEND_CURRENT;
8038 seldate_tm = localtime(&seldate);
8039 snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday,
8040 month_abbrev(seldate_tm->tm_mon + 1),
8041 seldate_tm->tm_year + 1900);
8042 defdate[sizeof(defdate)-1] = '\0';
8043 snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ",
8044 tense[when].preamble, tense[when].range,
8045 tense[when].scope, defdate);
8046 prompt[sizeof(prompt)-1] = '\0';
8047 r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date),
8048 prompt, sel_date_opt, help, &flags);
8049 switch (r){
8050 case 1 :
8051 cmd_cancelled("Selection by date");
8052 return(1);
8054 case 3 :
8055 help = (help == NO_HELP) ? h_select_date : NO_HELP;
8056 continue;
8058 case 4 :
8059 continue;
8061 case 11 :
8063 MESSAGECACHE *mc;
8064 long rawno;
8066 if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L
8067 && rawno <= stream->nmsgs
8068 && (mc = mail_elt(stream, rawno))){
8070 /* cache not filled in yet? */
8071 if(mc->day == 0){
8072 char seq[20];
8074 if(stream->dtb && stream->dtb->flags & DR_NEWS){
8075 strncpy(seq,
8076 ulong2string(mail_uid(stream, rawno)),
8077 sizeof(seq));
8078 seq[sizeof(seq)-1] = '\0';
8079 mail_fetch_overview(stream, seq, NULL);
8081 else{
8082 strncpy(seq, long2string(rawno),
8083 sizeof(seq));
8084 seq[sizeof(seq)-1] = '\0';
8085 mail_fetch_fast(stream, seq, 0L);
8089 /* mail_date returns fixed field width date */
8090 mail_date(date, mc);
8091 date[11] = '\0';
8095 continue;
8097 case 12 : /* set default to PREVIOUS day */
8098 seldate -= 86400;
8099 continue;
8101 case 13 : /* set default to NEXT day */
8102 seldate += 86400;
8103 continue;
8105 case 14 :
8106 when = (when+1) % (sizeof(tense) / sizeof(struct _tense));
8107 continue;
8109 default:
8110 break;
8113 removing_leading_white_space(date);
8114 removing_trailing_white_space(date);
8115 if(!*date){
8116 strncpy(date, defdate, sizeof(date));
8117 date[sizeof(date)-1] = '\0';
8120 break;
8123 if((pgm = mail_newsearchpgm()) != NULL){
8124 MESSAGECACHE elt;
8125 short converted_date;
8127 if(mail_parse_date(&elt, (unsigned char *) date)){
8128 converted_date = mail_shortdate(elt.year, elt.month, elt.day);
8130 switch(when){
8131 case 0:
8132 pgm->sentsince = converted_date;
8133 break;
8134 case 1:
8135 pgm->sentbefore = converted_date;
8136 break;
8137 case 2:
8138 pgm->senton = converted_date;
8139 break;
8140 case 3:
8141 pgm->since = converted_date;
8142 break;
8143 case 4:
8144 pgm->before = converted_date;
8145 break;
8146 case 5:
8147 pgm->on = converted_date;
8148 break;
8151 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8153 if(ps_global && ps_global->ttyo){
8154 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8155 ps_global->mangled_footer = 1;
8158 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8160 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8162 if(we_cancel)
8163 cancel_busy_cue(0);
8165 /* we know this was freed in mail_search, let caller know */
8166 if(limitsrch)
8167 *limitsrch = NULL;
8169 else{
8170 mail_free_searchpgm(&pgm);
8171 q_status_message1(SM_ORDER, 3, 3,
8172 _("Invalid date entered: %s"), date);
8173 return(1);
8177 return(0);
8181 * Select by searching in message headers or body
8182 * using the x-gm-ext-1 capaility. This function
8183 * reads the input from the user and passes it to
8184 * the server directly. We need a x-gm-ext-1 variable
8185 * in the search pgm to carry this information to the
8186 * server.
8188 * Sets searched bits in mail_elts
8190 * Args limitsrch -- limit search to this searchset
8192 * Returns 0 on success.
8195 select_by_gm_content(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8197 int r, we_cancel = 0, rv;
8198 char tmp[128];
8199 char namehdr[MAILTMPLEN];
8200 ESCKEY_S ekey[8];
8201 HelpType help;
8203 ps_global->mangled_footer = 1;
8204 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8206 strncpy(tmp, sel_x_gm_ext, sizeof(tmp)-1);
8207 tmp[sizeof(tmp)-1] = '\0';
8209 namehdr[0] = '\0';
8211 help = NO_HELP;
8212 while (1){
8213 int flags = OE_APPEND_CURRENT;
8215 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8216 sizeof(namehdr), tmp, ekey, help, &flags);
8218 if(r == 4)
8219 continue;
8221 if(r == 3){
8222 help = (help == NO_HELP) ? h_select_by_gm_content : NO_HELP;
8223 continue;
8226 if (r == 1){
8227 cmd_cancelled("Selection by content");
8228 return(1);
8231 removing_leading_white_space(namehdr);
8233 if ((namehdr[0] != '\0')
8234 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8235 removing_trailing_white_space(namehdr);
8237 if (namehdr[0] != '\0')
8238 break;
8241 if(ps_global && ps_global->ttyo){
8242 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8243 ps_global->mangled_footer = 1;
8246 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8248 rv = agg_text_select(stream, msgmap, 'g', namehdr, 0, 0, NULL, "utf-8", limitsrch);
8249 if(we_cancel)
8250 cancel_busy_cue(0);
8252 return(rv);
8256 * Select by searching in message headers or body.
8257 * Sets searched bits in mail_elts
8259 * Args limitsrch -- limit search to this searchset
8261 * Returns 0 on success.
8264 select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8266 int r, ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
8267 int not = 0, me = 0;
8268 char sstring[80], tmp[128];
8269 char *p, *sval = NULL;
8270 char buftmp[MAILTMPLEN], namehdr[80];
8271 ESCKEY_S ekey[8];
8272 ENVELOPE *env = NULL;
8273 HelpType help;
8274 unsigned flagsforhist = 0;
8275 static HISTORY_S *history = NULL;
8276 static char *recip = "RECIPIENTS";
8277 static char *partic = "PARTICIPANTS";
8278 static char *match_me = N_("[Match_My_Addresses]");
8279 static char *dont_match_me = N_("[Don't_Match_My_Addresses]");
8281 ps_global->mangled_footer = 1;
8282 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8284 while(1){
8285 type = radio_buttons(not ? _(sel_text_not) : _(sel_text),
8286 -FOOTER_ROWS(ps_global), sel_text_opt,
8287 's', 'x', NO_HELP, RB_NORM|RB_RET_HELP);
8289 if(type == '!')
8290 not = !not;
8291 else if(type == 3){
8292 helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS",
8293 HLPD_SIMPLE);
8294 ps_global->mangled_screen = 1;
8296 else
8297 break;
8301 * prepare some friendly defaults...
8303 switch(type){
8304 case 't' : /* address fields, offer To or From */
8305 case 'f' :
8306 case 'c' :
8307 case 'r' :
8308 case 'p' :
8309 sval = (type == 't') ? "TO" :
8310 (type == 'f') ? "FROM" :
8311 (type == 'c') ? "CC" :
8312 (type == 'r') ? recip : partic;
8313 ekey[ekeyi].ch = ctrl('T');
8314 ekey[ekeyi].name = "^T";
8315 ekey[ekeyi].rval = 10;
8316 /* TRANSLATORS: use Current To Address */
8317 ekey[ekeyi++].label = N_("Cur To");
8318 ekey[ekeyi].ch = ctrl('R');
8319 ekey[ekeyi].name = "^R";
8320 ekey[ekeyi].rval = 11;
8321 /* TRANSLATORS: use Current From Address */
8322 ekey[ekeyi++].label = N_("Cur From");
8323 ekey[ekeyi].ch = ctrl('W');
8324 ekey[ekeyi].name = "^W";
8325 ekey[ekeyi].rval = 12;
8326 /* TRANSLATORS: use Current Cc Address */
8327 ekey[ekeyi++].label = N_("Cur Cc");
8328 ekey[ekeyi].ch = ctrl('Y');
8329 ekey[ekeyi].name = "^Y";
8330 ekey[ekeyi].rval = 13;
8331 /* TRANSLATORS: Match Me means match my address */
8332 ekey[ekeyi++].label = N_("Match Me");
8333 ekey[ekeyi].ch = 0;
8334 ekey[ekeyi].name = "";
8335 ekey[ekeyi].rval = 0;
8336 ekey[ekeyi++].label = "";
8337 break;
8339 case 's' :
8340 sval = "SUBJECT";
8341 ekey[ekeyi].ch = ctrl('X');
8342 ekey[ekeyi].name = "^X";
8343 ekey[ekeyi].rval = 14;
8344 /* TRANSLATORS: use Current Subject */
8345 ekey[ekeyi++].label = N_("Cur Subject");
8346 break;
8348 case 'a' :
8349 sval = "TEXT";
8350 break;
8352 case 'b' :
8353 sval = "BODYTEXT";
8354 break;
8356 case 'h' :
8357 strncpy(tmp, "Name of HEADER to match : ", sizeof(tmp)-1);
8358 tmp[sizeof(tmp)-1] = '\0';
8359 flags = OE_APPEND_CURRENT;
8360 namehdr[0] = '\0';
8361 r = 'x';
8362 while (r == 'x'){
8363 int done = 0;
8365 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8366 sizeof(namehdr), tmp, ekey, NO_HELP, &flags);
8367 if (r == 1){
8368 cmd_cancelled("Selection by text");
8369 return(1);
8371 removing_leading_white_space(namehdr);
8372 while(!done){
8373 while ((namehdr[0] != '\0') && /* remove trailing ":" */
8374 (namehdr[strlen(namehdr) - 1] == ':'))
8375 namehdr[strlen(namehdr) - 1] = '\0';
8376 if ((namehdr[0] != '\0')
8377 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8378 removing_trailing_white_space(namehdr);
8379 else
8380 done++;
8382 if (strchr(namehdr,' ') || strchr(namehdr,'\t') ||
8383 strchr(namehdr,':'))
8384 namehdr[0] = '\0';
8385 if (namehdr[0] == '\0')
8386 r = 'x';
8388 sval = namehdr;
8389 break;
8391 case 'x':
8392 break;
8394 default:
8395 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
8396 return(1);
8399 ekey[ekeyi].ch = KEY_UP;
8400 ekey[ekeyi].rval = 30;
8401 ekey[ekeyi].name = "";
8402 ku = ekeyi;
8403 ekey[ekeyi++].label = "";
8405 ekey[ekeyi].ch = KEY_DOWN;
8406 ekey[ekeyi].rval = 31;
8407 ekey[ekeyi].name = "";
8408 ekey[ekeyi++].label = "";
8410 ekey[ekeyi].ch = -1;
8412 if(type != 'x'){
8414 init_hist(&history, HISTSIZE);
8416 if(ekey[0].ch > -1 && msgno > 0L
8417 && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),
8418 NULL)))
8419 ekey[0].ch = -1;
8421 sstring[0] = '\0';
8422 help = NO_HELP;
8423 r = type;
8424 while(r != 'x'){
8425 if(not)
8426 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8427 snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval);
8428 else
8429 snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval);
8431 if(items_in_hist(history) > 0){
8432 ekey[ku].name = HISTORY_UP_KEYNAME;
8433 ekey[ku].label = HISTORY_KEYLABEL;
8434 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
8435 ekey[ku+1].label = HISTORY_KEYLABEL;
8437 else{
8438 ekey[ku].name = "";
8439 ekey[ku].label = "";
8440 ekey[ku+1].name = "";
8441 ekey[ku+1].label = "";
8444 flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
8445 r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
8446 79, tmp, ekey, help, &flags);
8448 if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me)))))
8449 me = 0;
8451 switch(r){
8452 case 3 :
8453 help = (help == NO_HELP)
8454 ? (not
8455 ? ((type == 'f') ? h_select_txt_not_from
8456 : (type == 't') ? h_select_txt_not_to
8457 : (type == 'c') ? h_select_txt_not_cc
8458 : (type == 's') ? h_select_txt_not_subj
8459 : (type == 'a') ? h_select_txt_not_all
8460 : (type == 'r') ? h_select_txt_not_recip
8461 : (type == 'p') ? h_select_txt_not_partic
8462 : (type == 'b') ? h_select_txt_not_body
8463 : NO_HELP)
8464 : ((type == 'f') ? h_select_txt_from
8465 : (type == 't') ? h_select_txt_to
8466 : (type == 'c') ? h_select_txt_cc
8467 : (type == 's') ? h_select_txt_subj
8468 : (type == 'a') ? h_select_txt_all
8469 : (type == 'r') ? h_select_txt_recip
8470 : (type == 'p') ? h_select_txt_partic
8471 : (type == 'b') ? h_select_txt_body
8472 : NO_HELP))
8473 : NO_HELP;
8475 case 4 :
8476 continue;
8478 case 10 : /* To: default */
8479 if(env && env->to && env->to->mailbox){
8480 snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox,
8481 env->to->host ? "@" : "",
8482 env->to->host ? env->to->host : "");
8483 sstring[sizeof(sstring)-1] = '\0';
8485 continue;
8487 case 11 : /* From: default */
8488 if(env && env->from && env->from->mailbox){
8489 snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox,
8490 env->from->host ? "@" : "",
8491 env->from->host ? env->from->host : "");
8492 sstring[sizeof(sstring)-1] = '\0';
8494 continue;
8496 case 12 : /* Cc: default */
8497 if(env && env->cc && env->cc->mailbox){
8498 snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox,
8499 env->cc->host ? "@" : "",
8500 env->cc->host ? env->cc->host : "");
8501 sstring[sizeof(sstring)-1] = '\0';
8503 continue;
8505 case 13 : /* Match my addresses */
8506 me++;
8507 snprintf(sstring, sizeof(sstring), "%s", not ? _(dont_match_me) : _(match_me));
8508 continue;
8510 case 14 : /* Subject: default */
8511 if(env && env->subject && env->subject[0]){
8512 char *q = NULL;
8514 q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
8515 SIZEOF_20KBUF, env->subject);
8516 snprintf(sstring, sizeof(sstring), "%s", q);
8517 sstring[sizeof(sstring)-1] = '\0';
8520 continue;
8522 case 30 :
8523 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8524 if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){
8525 strncpy(sstring, p, sizeof(sstring));
8526 sstring[sizeof(sstring)-1] = '\0';
8527 if(history->hist[history->curindex]){
8528 flagsforhist = history->hist[history->curindex]->flags;
8529 not = (flagsforhist & 0x1) ? 1 : 0;
8530 me = (flagsforhist & 0x2) ? 1 : 0;
8533 else
8534 Writechar(BELL, 0);
8536 continue;
8538 case 31 :
8539 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8540 if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){
8541 strncpy(sstring, p, sizeof(sstring));
8542 sstring[sizeof(sstring)-1] = '\0';
8543 if(history->hist[history->curindex]){
8544 flagsforhist = history->hist[history->curindex]->flags;
8545 not = (flagsforhist & 0x1) ? 1 : 0;
8546 me = (flagsforhist & 0x2) ? 1 : 0;
8549 else
8550 Writechar(BELL, 0);
8552 continue;
8554 default :
8555 break;
8558 if(r == 1 || sstring[0] == '\0')
8559 r = 'x';
8561 break;
8565 if(type == 'x' || r == 'x'){
8566 cmd_cancelled("Selection by text");
8567 return(1);
8570 if(ps_global && ps_global->ttyo){
8571 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8572 ps_global->mangled_footer = 1;
8575 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8577 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8578 save_hist(history, sstring, flagsforhist, NULL);
8580 rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch);
8581 if(we_cancel)
8582 cancel_busy_cue(0);
8584 return(rv);
8589 * Select by message size.
8590 * Sets searched bits in mail_elts
8592 * Args limitsrch -- limit search to this searchset
8594 * Returns 0 on success.
8597 select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch)
8599 int r, large = 1, we_cancel = 0;
8600 unsigned long n, mult = 1L, numerator = 0L, divisor = 1L;
8601 char size[16], numbers[80], *p, *t;
8602 HelpType help;
8603 SEARCHPGM *pgm;
8604 long flags = (SE_NOPREFETCH | SE_FREE);
8606 numbers[0] = '\0';
8607 ps_global->mangled_footer = 1;
8609 help = NO_HELP;
8610 while(1){
8611 int flgs = OE_APPEND_CURRENT;
8613 sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger;
8615 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
8616 sizeof(numbers), large ? _(select_size_larger_msg)
8617 : _(select_size_smaller_msg),
8618 sel_size_opt, help, &flgs);
8619 if(r == 4)
8620 continue;
8622 if(r == 14){
8623 large = 1 - large;
8624 continue;
8627 if(r == 3){
8628 help = (help == NO_HELP) ? (large ? h_select_by_larger_size
8629 : h_select_by_smaller_size)
8630 : NO_HELP;
8631 continue;
8634 for(t = p = numbers; *p ; p++) /* strip whitespace */
8635 if(!isspace((unsigned char)*p))
8636 *t++ = *p;
8638 *t = '\0';
8640 if(r == 1 || numbers[0] == '\0'){
8641 cmd_cancelled("Selection by size");
8642 return(1);
8644 else
8645 break;
8648 if(numbers[0] == '-'){
8649 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8650 _("Invalid size entered: %s"), numbers);
8651 return(1);
8654 t = size;
8655 p = numbers;
8657 while(*p && isdigit((unsigned char)*p))
8658 *t++ = *p++;
8660 *t = '\0';
8662 if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){
8663 size[0] = '0';
8664 size[1] = '\0';
8667 if(size[0] == '\0'){
8668 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8669 _("Invalid size entered: %s"), numbers);
8670 return(1);
8673 n = strtoul(size, (char **)NULL, 10);
8675 size[0] = '\0';
8676 if(*p == '.'){
8678 * We probably ought to just use atof() to convert 1.1 into a
8679 * double, but since we haven't used atof() anywhere else I'm
8680 * reluctant to use it because of portability concerns.
8682 p++;
8683 t = size;
8684 while(*p && isdigit((unsigned char)*p)){
8685 *t++ = *p++;
8686 divisor *= 10;
8689 *t = '\0';
8691 if(size[0])
8692 numerator = strtoul(size, (char **)NULL, 10);
8695 switch(*p){
8696 case 'g':
8697 case 'G':
8698 mult *= 1000;
8699 /* fall through */
8701 case 'm':
8702 case 'M':
8703 mult *= 1000;
8704 /* fall through */
8706 case 'k':
8707 case 'K':
8708 mult *= 1000;
8709 break;
8712 n = n * mult + (numerator * mult) / divisor;
8714 pgm = mail_newsearchpgm();
8715 if(large)
8716 pgm->larger = n;
8717 else
8718 pgm->smaller = n;
8720 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8721 flags |= SE_NOSERVER;
8723 if(ps_global && ps_global->ttyo){
8724 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8725 ps_global->mangled_footer = 1;
8728 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8730 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8731 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8732 /* we know this was freed in mail_search, let caller know */
8733 if(limitsrch)
8734 *limitsrch = NULL;
8736 if(we_cancel)
8737 cancel_busy_cue(0);
8739 return(0);
8744 * visible_searchset -- return c-client search set unEXLDed
8745 * sequence numbers
8747 SEARCHSET *
8748 visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap)
8750 long n, run;
8751 SEARCHSET *full_set = NULL, **set;
8754 * If we're talking to anything other than a server older than
8755 * imap 4rev1, build a searchset otherwise it'll choke.
8757 if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
8758 if(any_lflagged(msgmap, MN_EXLD)){
8759 for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
8760 if(get_lflag(stream, NULL, n, MN_EXLD)){
8761 if(run){ /* previous NOT excluded? */
8762 if(run > 1L)
8763 (*set)->last = n - 1L;
8765 set = &(*set)->next;
8766 run = 0L;
8769 else if(run++){ /* next in run */
8770 (*set)->last = n;
8772 else{ /* start of run */
8773 *set = mail_newsearchset();
8774 (*set)->first = n;
8777 else{
8778 full_set = mail_newsearchset();
8779 full_set->first = 1L;
8780 full_set->last = stream->nmsgs;
8784 return(full_set);
8789 * Select by message status bits.
8790 * Sets searched bits in mail_elts
8792 * Args limitsrch -- limit search to this searchset
8794 * Returns 0 on success.
8797 select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch)
8799 int s, not = 0, we_cancel = 0, rv;
8801 while(1){
8802 s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag),
8803 -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x',
8804 NO_HELP, RB_NORM|RB_RET_HELP);
8806 if(s == 'x'){
8807 cmd_cancelled("Selection by status");
8808 return(1);
8810 else if(s == 3){
8811 helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"),
8812 HLPD_SIMPLE);
8813 ps_global->mangled_screen = 1;
8815 else if(s == '!')
8816 not = !not;
8817 else
8818 break;
8821 if(ps_global && ps_global->ttyo){
8822 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8823 ps_global->mangled_footer = 1;
8826 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8827 rv = agg_flag_select(stream, not, s, limitsrch);
8828 if(we_cancel)
8829 cancel_busy_cue(0);
8831 return(rv);
8836 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8837 * Sets searched bits in mail_elts
8839 * Args limitsrch -- limit search to this searchset
8841 * Returns 0 on success.
8844 select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
8846 char rulenick[1000], *nick;
8847 PATGRP_S *patgrp;
8848 int r, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
8849 | ROLE_DO_INCOLS
8850 | ROLE_DO_ROLES
8851 | ROLE_DO_SCORES
8852 | ROLE_DO_OTHER
8853 | ROLE_DO_FILTER;
8855 rulenick[0] = '\0';
8856 ps_global->mangled_footer = 1;
8859 int oe_flags;
8861 oe_flags = OE_APPEND_CURRENT;
8862 r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0,
8863 sizeof(rulenick),
8864 not ? _("Rule to NOT match: ")
8865 : _("Rule to match: "),
8866 sel_key_opt, NO_HELP, &oe_flags);
8868 if(r == 14){
8869 /* select rulenick from a list */
8870 if((nick=choose_a_rule(rflags)) != NULL){
8871 strncpy(rulenick, nick, sizeof(rulenick)-1);
8872 rulenick[sizeof(rulenick)-1] = '\0';
8873 fs_give((void **) &nick);
8875 else
8876 r = 4;
8878 else if(r == '!')
8879 not = !not;
8881 if(r == 3){
8882 helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE);
8883 ps_global->mangled_screen = 1;
8885 else if(r == 1){
8886 cmd_cancelled("Selection by Rule");
8887 return(1);
8890 removing_leading_and_trailing_white_space(rulenick);
8892 }while(r == 3 || r == 4 || r == '!');
8896 * The approach of requiring a nickname instead of just allowing the
8897 * user to select from the list of rules has the drawback that a rule
8898 * may not have a nickname, or there may be more than one rule with
8899 * the same nickname. However, it has the benefit of allowing the user
8900 * to type in the nickname and, most importantly, allows us to set
8901 * up the ! (not). We could incorporate the ! into the selection
8902 * screen, but this is easier and also allows the typing of nicks.
8903 * User can just set up nicknames if they want to use this feature.
8905 patgrp = nick_to_patgrp(rulenick, rflags);
8907 if(patgrp){
8908 if(ps_global && ps_global->ttyo){
8909 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8910 ps_global->mangled_footer = 1;
8913 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8914 match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL,
8915 get_msg_score,
8916 (not ? MP_NOT : 0) | SE_NOPREFETCH);
8917 free_patgrp(&patgrp);
8918 if(we_cancel)
8919 cancel_busy_cue(0);
8922 if(limitsrch && *limitsrch){
8923 mail_free_searchset(limitsrch);
8924 *limitsrch = NULL;
8927 return(0);
8932 * Allow user to choose a rule from their list of rules.
8934 * Returns an allocated rule nickname on success, NULL otherwise.
8936 char *
8937 choose_a_rule(int rflags)
8939 char *choice = NULL;
8940 char **rule_list, **lp;
8941 int cnt = 0;
8942 PAT_S *pat;
8943 PAT_STATE pstate;
8945 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){
8946 q_status_message(SM_ORDER, 3, 3,
8947 _("No rules available. Use Setup/Rules to add some."));
8948 return(choice);
8952 * Build a list of rules to choose from.
8955 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8956 cnt++;
8958 if(cnt <= 0){
8959 q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
8960 return(choice);
8963 lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
8964 memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
8966 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8967 *lp++ = cpystr((pat->patgrp && pat->patgrp->nick)
8968 ? pat->patgrp->nick : "?");
8970 /* TRANSLATORS: SELECT A RULE is a screen title
8971 TRANSLATORS: Print something1 using something2.
8972 "rules" is something1 */
8973 choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"),
8974 _("rules"), h_select_rule_screen,
8975 _("HELP FOR SELECTING A RULE NICKNAME"), NULL);
8977 if(!choice)
8978 q_status_message(SM_ORDER, 1, 4, "No choice");
8980 free_list_array(&rule_list);
8982 return(choice);
8987 * Select by current thread.
8988 * Sets searched bits in mail_elts for this entire thread
8990 * Args limitsrch -- limit search to this searchset
8992 * Returns 0 on success.
8995 select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
8997 long n;
8998 PINETHRD_S *thrd = NULL;
8999 int ret = 1;
9000 MESSAGECACHE *mc;
9002 if(!stream)
9003 return(ret);
9005 for(n = 1L; n <= stream->nmsgs; n++)
9006 if((mc = mail_elt(stream, n)) != NULL)
9007 mc->searched = 0; /* clear searched bits */
9009 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
9010 if(thrd && thrd->top && thrd->top != thrd->rawno)
9011 thrd = fetch_thread(stream, thrd->top);
9014 * This doesn't unselect if the thread is already selected
9015 * (like select current does), it always selects.
9016 * There is no way to select ! this thread.
9018 if(thrd){
9019 set_search_bit_for_thread(stream, thrd, limitsrch);
9020 ret = 0;
9023 return(ret);
9028 * Select by message keywords.
9029 * Sets searched bits in mail_elts
9031 * Args limitsrch -- limit search to this searchset
9033 * Returns 0 on success.
9036 select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
9038 int r, not = 0, we_cancel = 0;
9039 char keyword[MAXUSERFLAG+1], *kword;
9040 char *error = NULL, *p, *prompt;
9041 HelpType help;
9042 SEARCHPGM *pgm;
9044 keyword[0] = '\0';
9045 ps_global->mangled_footer = 1;
9047 help = NO_HELP;
9049 int oe_flags;
9051 if(error){
9052 q_status_message(SM_ORDER, 3, 4, error);
9053 fs_give((void **) &error);
9056 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
9057 if(not)
9058 prompt = _("Keyword (or keyword initial) to NOT match: ");
9059 else
9060 prompt = _("Keyword (or keyword initial) to match: ");
9062 else{
9063 if(not)
9064 prompt = _("Keyword to NOT match: ");
9065 else
9066 prompt = _("Keyword to match: ");
9069 oe_flags = OE_APPEND_CURRENT;
9070 r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0,
9071 sizeof(keyword),
9072 prompt, sel_key_opt, help, &oe_flags);
9074 if(r == 14){
9075 /* select keyword from a list */
9076 if((kword=choose_a_keyword()) != NULL){
9077 strncpy(keyword, kword, sizeof(keyword)-1);
9078 keyword[sizeof(keyword)-1] = '\0';
9079 fs_give((void **) &kword);
9081 else
9082 r = 4;
9084 else if(r == '!')
9085 not = !not;
9087 if(r == 3)
9088 help = help == NO_HELP ? h_select_keyword : NO_HELP;
9089 else if(r == 1){
9090 cmd_cancelled("Selection by keyword");
9091 return(1);
9094 removing_leading_and_trailing_white_space(keyword);
9096 }while(r == 3 || r == 4 || r == '!' || keyword_check(keyword, &error));
9099 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
9100 p = initial_to_keyword(keyword);
9101 if(p != keyword){
9102 strncpy(keyword, p, sizeof(keyword)-1);
9103 keyword[sizeof(keyword)-1] = '\0';
9108 * We want to check the keyword, not the nickname of the keyword,
9109 * so convert it to the keyword if necessary.
9111 p = nick_to_keyword(keyword);
9112 if(p != keyword){
9113 strncpy(keyword, p, sizeof(keyword)-1);
9114 keyword[sizeof(keyword)-1] = '\0';
9117 pgm = mail_newsearchpgm();
9118 if(not){
9119 pgm->unkeyword = mail_newstringlist();
9120 pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword);
9121 pgm->unkeyword->text.size = strlen(keyword);
9123 else{
9124 pgm->keyword = mail_newstringlist();
9125 pgm->keyword->text.data = (unsigned char *) cpystr(keyword);
9126 pgm->keyword->text.size = strlen(keyword);
9129 if(ps_global && ps_global->ttyo){
9130 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9131 ps_global->mangled_footer = 1;
9134 we_cancel = busy_cue(_("Selecting"), NULL, 1);
9136 pgm->msgno = (limitsrch ? *limitsrch : NULL);
9137 pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE);
9138 /* we know this was freed in mail_search, let caller know */
9139 if(limitsrch)
9140 *limitsrch = NULL;
9142 if(we_cancel)
9143 cancel_busy_cue(0);
9145 return(0);
9150 * Allow user to choose a keyword from their list of keywords.
9152 * Returns an allocated keyword on success, NULL otherwise.
9154 char *
9155 choose_a_keyword(void)
9157 char *choice = NULL;
9158 char **keyword_list, **lp;
9159 int cnt;
9160 KEYWORD_S *kw;
9163 * Build a list of keywords to choose from.
9166 for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next)
9167 cnt++;
9169 if(cnt <= 0){
9170 q_status_message(SM_ORDER, 3, 4,
9171 _("No keywords defined, use \"keywords\" option in Setup/Config"));
9172 return(choice);
9175 lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list));
9176 memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list));
9178 for(kw = ps_global->keywords; kw; kw = kw->next)
9179 *lp++ = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9181 /* TRANSLATORS: SELECT A KEYWORD is a screen title
9182 TRANSLATORS: Print something1 using something2.
9183 "keywords" is something1 */
9184 choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"),
9185 _("keywords"), h_select_keyword_screen,
9186 _("HELP FOR SELECTING A KEYWORD"), NULL);
9188 if(!choice)
9189 q_status_message(SM_ORDER, 1, 4, "No choice");
9191 free_list_array(&keyword_list);
9193 return(choice);
9198 * Allow user to choose a list of keywords from their list of keywords.
9200 * Returns allocated list.
9202 char **
9203 choose_list_of_keywords(void)
9205 LIST_SEL_S *listhead, *ls, *p;
9206 char **ret = NULL;
9207 int cnt, i;
9208 KEYWORD_S *kw;
9211 * Build a list of keywords to choose from.
9214 p = listhead = NULL;
9215 for(kw = ps_global->keywords; kw; kw = kw->next){
9217 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9218 memset(ls, 0, sizeof(*ls));
9219 ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9221 if(p){
9222 p->next = ls;
9223 p = p->next;
9225 else
9226 listhead = p = ls;
9229 if(!listhead)
9230 return(ret);
9232 /* TRANSLATORS: SELECT KEYWORDS is a screen title
9233 Print something1 using something2.
9234 "keywords" is something1 */
9235 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9236 _("SELECT KEYWORDS"), _("keywords"),
9237 h_select_multkeyword_screen,
9238 _("HELP FOR SELECTING KEYWORDS"), NULL)){
9239 for(cnt = 0, p = listhead; p; p = p->next)
9240 if(p->selected)
9241 cnt++;
9243 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9244 memset(ret, 0, (cnt+1) * sizeof(*ret));
9245 for(i = 0, p = listhead; p; p = p->next)
9246 if(p->selected)
9247 ret[i++] = cpystr(p->item ? p->item : "");
9250 free_list_sel(&listhead);
9252 return(ret);
9257 * Allow user to choose a charset
9259 * Returns an allocated charset on success, NULL otherwise.
9261 char *
9262 choose_a_charset(int which_charsets)
9264 char *choice = NULL;
9265 char **charset_list, **lp;
9266 const CHARSET *cs;
9267 int cnt;
9270 * Build a list of charsets to choose from.
9273 for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){
9274 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9275 && ((which_charsets & CAC_ALL)
9276 || (which_charsets & CAC_POSTING
9277 && cs->flags & CF_POSTING)
9278 || (which_charsets & CAC_DISPLAY
9279 && cs->type != CT_2022
9280 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9281 cnt++;
9284 if(cnt <= 0){
9285 q_status_message(SM_ORDER, 3, 4,
9286 _("No charsets found? Enter charset manually."));
9287 return(choice);
9290 lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list));
9291 memset(charset_list, 0, (cnt+1) * sizeof(*charset_list));
9293 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9294 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9295 && ((which_charsets & CAC_ALL)
9296 || (which_charsets & CAC_POSTING
9297 && cs->flags & CF_POSTING)
9298 || (which_charsets & CAC_DISPLAY
9299 && cs->type != CT_2022
9300 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9301 *lp++ = cpystr(cs->name);
9304 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
9305 TRANSLATORS: Print something1 using something2.
9306 "character sets" is something1 */
9307 choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"),
9308 _("character sets"), h_select_charset_screen,
9309 _("HELP FOR SELECTING A CHARACTER SET"), NULL);
9311 if(!choice)
9312 q_status_message(SM_ORDER, 1, 4, "No choice");
9314 free_list_array(&charset_list);
9316 return(choice);
9321 * Allow user to choose a list of character sets and/or scripts
9323 * Returns allocated list.
9325 char **
9326 choose_list_of_charsets(void)
9328 LIST_SEL_S *listhead, *ls, *p;
9329 char **ret = NULL;
9330 int cnt, i, got_one;
9331 const CHARSET *cs;
9332 SCRIPT *s;
9333 char *q, *t;
9334 long width, limit;
9335 char buf[1024], *folded;
9338 * Build a list of charsets to choose from.
9341 p = listhead = NULL;
9343 /* this width is determined by select_from_list_screen() */
9344 width = ps_global->ttyo->screen_cols - 4;
9346 /* first comes a list of scripts (sets of character sets) */
9347 for(s = utf8_script(NIL); s && s->name; s++){
9349 limit = sizeof(buf)-1;
9350 q = buf;
9351 memset(q, 0, limit+1);
9353 if(s->name)
9354 sstrncpy(&q, s->name, limit);
9356 if(s->description){
9357 sstrncpy(&q, " (", limit-(q-buf));
9358 sstrncpy(&q, s->description, limit-(q-buf));
9359 sstrncpy(&q, ")", limit-(q-buf));
9362 /* add the list of charsets that are in this script */
9363 got_one = 0;
9364 for(cs = utf8_charset(NIL);
9365 cs && cs->name && (q-buf) < limit; cs++){
9366 if(cs->script & s->script){
9368 * Filter out some un-useful members of the list.
9369 * UTF-7 and UTF-8 weren't actually in the list at the
9370 * time this was written. Just making sure.
9372 if(!strucmp(cs->name, "ISO-2022-JP-2")
9373 || !strucmp(cs->name, "UTF-7")
9374 || !strucmp(cs->name, "UTF-8"))
9375 continue;
9377 if(got_one)
9378 sstrncpy(&q, " ", limit-(q-buf));
9379 else{
9380 got_one = 1;
9381 sstrncpy(&q, " {", limit-(q-buf));
9384 sstrncpy(&q, cs->name, limit-(q-buf));
9388 if(got_one)
9389 sstrncpy(&q, "}", limit-(q-buf));
9391 /* fold this line so that it can all be seen on the screen */
9392 folded = fold(buf, width, width, "", " ", FLD_NONE);
9393 if(folded){
9394 t = folded;
9395 while(t && *t && (q = strindex(t, '\n')) != NULL){
9396 *q = '\0';
9398 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9399 memset(ls, 0, sizeof(*ls));
9400 if(t == folded)
9401 ls->item = cpystr(s->name);
9402 else
9403 ls->flags = SFL_NOSELECT;
9405 ls->display_item = cpystr(t);
9407 t = q+1;
9409 if(p){
9410 p->next = ls;
9411 p = p->next;
9413 else{
9414 /* add a heading */
9415 listhead = (LIST_SEL_S *) fs_get(sizeof(*ls));
9416 memset(listhead, 0, sizeof(*listhead));
9417 listhead->flags = SFL_NOSELECT;
9418 listhead->display_item =
9419 cpystr(_("Scripts representing groups of related character sets"));
9420 listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls));
9421 memset(listhead->next, 0, sizeof(*listhead));
9422 listhead->next->flags = SFL_NOSELECT;
9423 listhead->next->display_item =
9424 cpystr(repeat_char(width, '-'));
9426 listhead->next->next = ls;
9427 p = ls;
9431 fs_give((void **) &folded);
9435 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9436 memset(ls, 0, sizeof(*ls));
9437 ls->flags = SFL_NOSELECT;
9438 if(p){
9439 p->next = ls;
9440 p = p->next;
9442 else
9443 listhead = p = ls;
9445 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9446 memset(ls, 0, sizeof(*ls));
9447 ls->flags = SFL_NOSELECT;
9448 ls->display_item =
9449 cpystr(_("Individual character sets, may be mixed with scripts"));
9450 p->next = ls;
9451 p = p->next;
9453 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9454 memset(ls, 0, sizeof(*ls));
9455 ls->flags = SFL_NOSELECT;
9456 ls->display_item =
9457 cpystr(repeat_char(width, '-'));
9458 p->next = ls;
9459 p = p->next;
9461 /* then comes a list of individual character sets */
9462 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9463 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9464 memset(ls, 0, sizeof(*ls));
9465 ls->item = cpystr(cs->name);
9467 if(p){
9468 p->next = ls;
9469 p = p->next;
9471 else
9472 listhead = p = ls;
9475 if(!listhead)
9476 return(ret);
9478 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9479 Print something1 using something2.
9480 "character sets" is something1 */
9481 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9482 _("SELECT CHARACTER SETS"), _("character sets"),
9483 h_select_multcharsets_screen,
9484 _("HELP FOR SELECTING CHARACTER SETS"), NULL)){
9485 for(cnt = 0, p = listhead; p; p = p->next)
9486 if(p->selected)
9487 cnt++;
9489 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9490 memset(ret, 0, (cnt+1) * sizeof(*ret));
9491 for(i = 0, p = listhead; p; p = p->next)
9492 if(p->selected)
9493 ret[i++] = cpystr(p->item ? p->item : "");
9496 free_list_sel(&listhead);
9498 return(ret);
9501 /* Report quota summary resources in an IMAP server */
9503 void
9504 cmd_quota (struct pine *state)
9506 QUOTALIST *imapquota;
9507 NETMBX mb;
9508 STORE_S *store;
9509 SCROLL_S sargs;
9511 if(!state->mail_stream || !is_imap_stream(state->mail_stream)){
9512 q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders");
9513 return;
9516 if (state->mail_stream
9517 && !sp_dead_stream(state->mail_stream)
9518 && state->mail_stream->mailbox
9519 && *state->mail_stream->mailbox
9520 && mail_valid_net_parse(state->mail_stream->mailbox, &mb))
9521 imap_getquotaroot(state->mail_stream, mb.mailbox);
9523 if(!state->quota) /* failed ? */
9524 return; /* go back... */
9526 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
9527 q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space.");
9528 return;
9531 so_puts(store, "Quota Report for ");
9532 so_puts(store, state->mail_stream->original_mailbox);
9533 so_puts(store, "\n\n");
9535 for (imapquota = state->quota; imapquota; imapquota = imapquota->next){
9537 so_puts(store, _("Resource : "));
9538 so_puts(store, imapquota->name);
9539 so_writec('\n', store);
9541 so_puts(store, _("Usage : "));
9542 so_puts(store, long2string(imapquota->usage));
9543 if(!strucmp(imapquota->name,"STORAGE"))
9544 so_puts(store, " KiB ");
9545 if(!strucmp(imapquota->name,"MESSAGE")){
9546 so_puts(store, _(" message"));
9547 if(imapquota->usage != 1)
9548 so_puts(store, _("s ")); /* plural */
9549 else
9550 so_puts(store, _(" "));
9552 so_writec('(', store);
9553 so_puts(store, long2string(100*imapquota->usage/imapquota->limit));
9554 so_puts(store, "%)\n");
9556 so_puts(store, _("Limit : "));
9557 so_puts(store, long2string(imapquota->limit));
9558 if(!strucmp(imapquota->name,"STORAGE"))
9559 so_puts(store, " KiB\n\n");
9560 if(!strucmp(imapquota->name,"MESSAGE")){
9561 so_puts(store, _(" message"));
9562 if(imapquota->usage != 1)
9563 so_puts(store, _("s\n\n")); /* plural */
9564 else
9565 so_puts(store, _("\n\n"));
9569 memset(&sargs, 0, sizeof(SCROLL_S));
9570 sargs.text.text = so_text(store);
9571 sargs.text.src = CharStar;
9572 sargs.text.desc = _("Quota Resources Summary");
9573 sargs.bar.title = _("QUOTA SUMMARY");
9574 sargs.proc.tool = NULL;
9575 sargs.help.text = h_quota_command;
9576 sargs.help.title = NULL;
9577 sargs.keys.menu = NULL;
9578 setbitmap(sargs.keys.bitmap);
9580 scrolltool(&sargs);
9581 so_give(&store);
9583 if (state->quota)
9584 mail_free_quotalist(&(state->quota));
9587 /*----------------------------------------------------------------------
9588 Prompt the user for the type of sort he desires
9590 Args: state -- pine state pointer
9591 q1 -- Line to prompt on
9593 Returns 0 if it was cancelled, 1 otherwise.
9594 ----*/
9596 select_sort(struct pine *state, int ql, SortOrder *sort, int *rev)
9598 char prompt[200], tmp[3], *p;
9599 int s, i;
9600 int deefault = 'a', retval = 1;
9601 HelpType help;
9602 ESCKEY_S sorts[14];
9604 #ifdef _WINDOWS
9605 DLG_SORTPARAM sortsel;
9607 if (mswin_usedialog ()) {
9609 sortsel.reverse = mn_get_revsort (state->msgmap);
9610 sortsel.cursort = mn_get_sort (state->msgmap);
9611 /* assumption here that HelpType is char ** */
9612 sortsel.helptext = h_select_sort;
9613 sortsel.rval = 0;
9615 if ((retval = os_sortdialog (&sortsel))) {
9616 *sort = sortsel.cursort;
9617 *rev = sortsel.reverse;
9620 return (retval);
9622 #endif
9624 /*----- String together the prompt ------*/
9625 tmp[1] = '\0';
9626 if(F_ON(F_USE_FK,ps_global))
9627 strncpy(prompt, _("Choose type of sort : "), sizeof(prompt));
9628 else
9629 strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "),
9630 sizeof(prompt));
9632 for(i = 0; state->sort_types[i] != EndofList; i++) {
9633 sorts[i].rval = i;
9634 p = sorts[i].label = sort_name(state->sort_types[i]);
9635 while(*(p+1) && islower((unsigned char)*p))
9636 p++;
9638 sorts[i].ch = tolower((unsigned char)(tmp[0] = *p));
9639 sorts[i].name = cpystr(tmp);
9641 if(mn_get_sort(state->msgmap) == state->sort_types[i])
9642 deefault = sorts[i].rval;
9645 sorts[i].ch = 'r';
9646 sorts[i].rval = 'r';
9647 sorts[i].name = cpystr("R");
9648 if(F_ON(F_USE_FK,ps_global))
9649 sorts[i].label = N_("Reverse");
9650 else
9651 sorts[i].label = "";
9653 sorts[++i].ch = -1;
9654 help = h_select_sort;
9656 if((F_ON(F_USE_FK,ps_global)
9657 && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x',
9658 help,RB_NORM)) != 'x'))
9660 (F_OFF(F_USE_FK,ps_global)
9661 && ((s = radio_buttons(prompt,ql,sorts,deefault,'x',
9662 help,RB_NORM)) != 'x'))){
9663 state->mangled_body = 1; /* signal screen's changed */
9664 if(s == 'r')
9665 *rev = !mn_get_revsort(state->msgmap);
9666 else
9667 *sort = state->sort_types[s];
9669 if(F_ON(F_SHOW_SORT, ps_global))
9670 ps_global->mangled_header = 1;
9672 else{
9673 retval = 0;
9674 cmd_cancelled("Sort");
9677 while(--i >= 0)
9678 fs_give((void **)&sorts[i].name);
9680 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9681 return(retval);
9685 /*---------------------------------------------------------------------
9686 Build list of folders in the given context for user selection
9688 Args: c -- pointer to pointer to folder's context context
9689 f -- folder prefix to display
9690 sublist -- whether or not to use 'f's contents as prefix
9691 lister -- function used to do the actual display
9693 Returns: malloc'd string containing sequence, else NULL if
9694 no messages in msgmap with local "selected" flag.
9695 ----*/
9697 display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int))
9699 int rc;
9700 CONTEXT_S *tc;
9701 void (*redraw)(void) = ps_global->redrawer;
9703 push_titlebar_state();
9704 tc = *c;
9705 if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0)
9706 *c = tc;
9708 ClearScreen();
9709 pop_titlebar_state();
9710 redraw_titlebar();
9711 if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
9712 (*ps_global->redrawer)();
9714 if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
9715 return(1);
9717 return(0);
9722 * Allow user to choose a single item from a list of strings.
9724 * Args list -- Array of strings to choose from, NULL terminated.
9725 * displist -- Array of strings to display instead of displaying list.
9726 * Indices correspond to the list array. Display the displist
9727 * but return the item from list if displist non-NULL.
9728 * title -- For conf_scroll_screen
9729 * pdesc -- For conf_scroll_screen
9730 * help -- For conf_scroll_screen
9731 * htitle -- For conf_scroll_screen
9733 * Returns an allocated copy of the chosen item or NULL.
9735 char *
9736 choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help,
9737 char *htitle, char *cursor_location)
9739 LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL;
9740 char **t, **dl;
9741 char *ret = NULL, *choice = NULL;
9743 /* build the LIST_SEL_S list */
9744 p = listhead = NULL;
9745 for(t = list, dl = displist; *t; t++, dl++){
9746 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9747 memset(ls, 0, sizeof(*ls));
9748 ls->item = cpystr(*t);
9749 if(displist)
9750 ls->display_item = cpystr(*dl);
9752 if(cursor_location && (cursor_location == (*t)))
9753 starting_val = ls;
9755 if(p){
9756 p->next = ls;
9757 p = p->next;
9759 else
9760 listhead = p = ls;
9763 if(!listhead)
9764 return(ret);
9766 if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc,
9767 help, htitle, starting_val))
9768 for(p = listhead; !choice && p; p = p->next)
9769 if(p->selected)
9770 choice = p->item;
9772 if(choice)
9773 ret = cpystr(choice);
9775 free_list_sel(&listhead);
9777 return(ret);
9781 void
9782 free_list_sel(LIST_SEL_S **lsel)
9784 if(lsel && *lsel){
9785 free_list_sel(&(*lsel)->next);
9786 if((*lsel)->item)
9787 fs_give((void **) &(*lsel)->item);
9789 if((*lsel)->display_item)
9790 fs_give((void **) &(*lsel)->display_item);
9792 fs_give((void **) lsel);
9798 * file_lister - call pico library's file lister
9801 file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags)
9803 PICO pbf;
9804 int rv;
9805 void (*redraw)(void) = ps_global->redrawer;
9807 standard_picobuf_setup(&pbf);
9808 push_titlebar_state();
9809 if(!newmail)
9810 pbf.newmail = NULL;
9812 /* BUG: what about help command and text? */
9813 pbf.pine_anchor = title;
9815 rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags);
9816 standard_picobuf_teardown(&pbf);
9817 fix_windsize(ps_global);
9818 init_signals(); /* has it's own signal stuff */
9820 /* Restore display's titlebar and body */
9821 pop_titlebar_state();
9822 redraw_titlebar();
9823 if((ps_global->redrawer = redraw) != NULL)
9824 (*ps_global->redrawer)();
9826 return(rv);
9830 /*----------------------------------------------------------------------
9831 Print current folder index
9833 ---*/
9835 print_index(struct pine *state, MSGNO_S *msgmap, int agg)
9837 long i;
9838 ICE_S *ice;
9839 char buf[MAX_SCREEN_COLS+1];
9841 for(i = 1L; i <= mn_get_total(msgmap); i++){
9842 if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
9843 continue;
9845 if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0))
9846 continue;
9848 ice = build_header_line(state, state->mail_stream, msgmap, i, NULL);
9850 if(ice){
9852 * I don't understand why we'd want to mark the current message
9853 * instead of printing out the first character of the status
9854 * so I'm taking it out and including the first character of the
9855 * line instead. Hubert 2006-02-09
9857 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
9858 return(0);
9861 if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i),
9862 print_char)
9863 || !gf_puts(NEWLINE, print_char))
9864 return(0);
9868 return(1);
9871 #ifdef _WINDOWS
9874 * windows callback to get/set header mode state
9877 header_mode_callback(set, args)
9878 int set;
9879 long args;
9881 return(ps_global->full_header);
9886 * windows callback to get/set zoom mode state
9889 zoom_mode_callback(set, args)
9890 int set;
9891 long args;
9893 return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0);
9898 * windows callback to get/set zoom mode state
9901 any_selected_callback(set, args)
9902 int set;
9903 long args;
9905 return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0);
9913 flag_callback(set, flags)
9914 int set;
9915 long flags;
9917 MESSAGECACHE *mc;
9918 int newflags = 0;
9919 long msgno;
9920 int permflag = 0;
9922 switch (set) {
9923 case 1: /* Important */
9924 permflag = ps_global->mail_stream->perm_flagged;
9925 break;
9927 case 2: /* New */
9928 permflag = ps_global->mail_stream->perm_seen;
9929 break;
9931 case 3: /* Answered */
9932 permflag = ps_global->mail_stream->perm_answered;
9933 break;
9935 case 4: /* Deleted */
9936 permflag = ps_global->mail_stream->perm_deleted;
9937 break;
9941 if(!(any_messages(ps_global->msgmap, NULL, "to Flag")
9942 && can_set_flag(ps_global, "flag", permflag)))
9943 return(0);
9945 if(sp_io_error_on_stream(ps_global->mail_stream)){
9946 sp_set_io_error_on_stream(ps_global->mail_stream, 0);
9947 pine_mail_check(ps_global->mail_stream); /* forces write */
9948 return(0);
9951 msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
9952 if(msgno > 0L && ps_global->mail_stream
9953 && msgno <= ps_global->mail_stream->nmsgs
9954 && (mc = mail_elt(ps_global->mail_stream, msgno))
9955 && mc->valid){
9957 * NOTE: code below is *VERY* sensitive to the order of
9958 * the messages defined in resource.h for flag handling.
9959 * Don't change it unless you know what you're doing.
9961 if(set){
9962 char *flagstr;
9963 long mflag;
9965 switch(set){
9966 case 1 : /* Important */
9967 flagstr = "\\FLAGGED";
9968 mflag = (mc->flagged) ? 0L : ST_SET;
9969 break;
9971 case 2 : /* New */
9972 flagstr = "\\SEEN";
9973 mflag = (mc->seen) ? 0L : ST_SET;
9974 break;
9976 case 3 : /* Answered */
9977 flagstr = "\\ANSWERED";
9978 mflag = (mc->answered) ? 0L : ST_SET;
9979 break;
9981 case 4 : /* Deleted */
9982 flagstr = "\\DELETED";
9983 mflag = (mc->deleted) ? 0L : ST_SET;
9984 break;
9986 default : /* bogus */
9987 return(0);
9990 mail_flag(ps_global->mail_stream, long2string(msgno),
9991 flagstr, mflag);
9993 if(ps_global->redrawer)
9994 (*ps_global->redrawer)();
9996 else{
9997 /* Important */
9998 if(mc->flagged)
9999 newflags |= 0x0001;
10001 /* New */
10002 if(!mc->seen)
10003 newflags |= 0x0002;
10005 /* Answered */
10006 if(mc->answered)
10007 newflags |= 0x0004;
10009 /* Deleted */
10010 if(mc->deleted)
10011 newflags |= 0x0008;
10015 return(newflags);
10021 * BUG: Should teach this about keywords
10023 MPopup *
10024 flag_submenu(mc)
10025 MESSAGECACHE *mc;
10027 static MPopup flag_submenu[] = {
10028 {tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}},
10029 {tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}},
10030 {tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}},
10031 {tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}},
10032 {tTail}
10035 /* Important */
10036 flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal;
10038 /* New */
10039 flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked;
10041 /* Answered */
10042 flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal;
10044 /* Deleted */
10045 flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal;
10047 return(flag_submenu);
10050 #endif /* _WINDOWS */