* Avoid error messages or tcp timeouts when cancelling imap authentication.
[alpine.git] / alpine / mailcmd.c
blob794e335a8b5bba1a83a969d68a12b0fff042032f
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 {-1, 0, NULL, NULL}
139 #define SEL_OPTS_THREAD 9 /* index number of "tHread" */
140 #define SEL_OPTS_THREAD_CH 'h'
141 #define SEL_OPTS_XGMEXT 10 /* index number of "boX" */
142 #define SEL_OPTS_XGMEXT_CH 'g'
144 char *sel_pmt2 = "SELECT criteria : ";
145 static ESCKEY_S sel_opts2[] = {
146 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
147 means select the currently highlighted message; select by Number is by message
148 number; Status is by status of the message, for example the message might be
149 New or it might be Unseen or marked Important; Size has the Z upper case because
150 it is a Z command; Keyword is an alpine keyword that has been set by the user;
151 and Rule is an alpine rule */
152 {'a', 'a', "A", N_("select All")},
153 {'c', 'c', "C", N_("select Cur")},
154 {'n', 'n', "N", N_("Number")},
155 {'d', 'd', "D", N_("Date")},
156 {'t', 't', "T", N_("Text")},
157 {'s', 's', "S", N_("Status")},
158 {'z', 'z', "Z", N_("siZe")},
159 {'k', 'k', "K", N_("Keyword")},
160 {'r', 'r', "R", N_("Rule")},
161 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
162 {-1, 0, NULL, NULL}
166 static ESCKEY_S sel_opts3[] = {
167 /* TRANSLATORS: these are operations we can do on a set of selected messages.
168 Del is Delete; Undel is Undelete; TakeAddr means to Take some Addresses into
169 the address book; Save means to save the messages into another alpine folder;
170 Export means to copy the messages to a file outside of alpine, external to
171 alpine's world. */
172 {'d', 'd', "D", N_("Del")},
173 {'u', 'u', "U", N_("Undel")},
174 {'r', 'r', "R", N_("Reply")},
175 {'f', 'f', "F", N_("Forward")},
176 {'%', '%', "%", N_("Print")},
177 {'t', 't', "T", N_("TakeAddr")},
178 {'s', 's', "S", N_("Save")},
179 {'e', 'e', "E", N_("Export")},
180 { -1, 0, NULL, NULL},
181 { -1, 0, NULL, NULL},
182 { -1, 0, NULL, NULL},
183 { -1, 0, NULL, NULL},
184 { -1, 0, NULL, NULL},
185 { -1, 0, NULL, NULL},
186 { -1, 0, NULL, NULL},
187 {-1, 0, NULL, NULL}
190 static ESCKEY_S sel_opts4[] = {
191 {'a', 'a', "A", N_("select All")},
192 /* TRANSLATORS: select currently highlighted message Thread */
193 {'c', 'c', "C", N_("select Curthrd")},
194 {'n', 'n', "N", N_("Number")},
195 {'d', 'd', "D", N_("Date")},
196 {'t', 't', "T", N_("Text")},
197 {'s', 's', "S", N_("Status")},
198 {'z', 'z', "Z", N_("siZe")},
199 {'k', 'k', "K", N_("Keyword")},
200 {'r', 'r', "R", N_("Rule")},
201 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
202 {-1, 0, NULL, NULL}
205 static ESCKEY_S sel_opts5[] = {
206 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
207 means select the currently highlighted message; select by Number is by message
208 number; Status is by status of the message, for example the message might be
209 New or it might be Unseen or marked Important; Size has the Z upper case because
210 it is a Z command; Keyword is an alpine keyword that has been set by the user;
211 and Rule is an alpine rule */
212 {'a', 'a', "A", N_("select All")},
213 {'c', 'c', "C", N_("select Cur")},
214 {'n', 'n', "N", N_("Number")},
215 {'d', 'd', "D", N_("Date")},
216 {'t', 't', "T", N_("Text")},
217 {'s', 's', "S", N_("Status")},
218 {'z', 'z', "Z", N_("siZe")},
219 {'k', 'k', "K", N_("Keyword")},
220 {'r', 'r', "R", N_("Rule")},
221 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
222 {SEL_OPTS_XGMEXT_CH, 'g', "G", N_("GmSearch")},
223 {-1, 0, NULL, NULL},
224 {-1, 0, NULL, NULL},
225 {-1, 0, NULL, NULL},
226 {-1, 0, NULL, NULL},
227 {-1, 0, NULL, NULL}
230 static ESCKEY_S sel_opts6[] = {
231 {'a', 'a', "A", N_("select All")},
232 /* TRANSLATORS: select currently highlighted message Thread */
233 {'c', 'c', "C", N_("select Curthrd")},
234 {'n', 'n', "N", N_("Number")},
235 {'d', 'd', "D", N_("Date")},
236 {'t', 't', "T", N_("Text")},
237 {'s', 's', "S", N_("Status")},
238 {'z', 'z', "Z", N_("siZe")},
239 {'k', 'k', "K", N_("Keyword")},
240 {'r', 'r', "R", N_("Rule")},
241 {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")},
242 {SEL_OPTS_XGMEXT_CH, 'g', "G", N_("Gmail")},
243 {-1, 0, NULL, NULL},
244 {-1, 0, NULL, NULL},
245 {-1, 0, NULL, NULL},
246 {-1, 0, NULL, NULL},
247 {-1, 0, NULL, NULL}
251 static char *sel_flag =
252 N_("Select New, Deleted, Answered, Forwarded, or Important messages ? ");
253 static char *sel_flag_not =
254 N_("Select NOT New, NOT Deleted, NOT Answered, NOT Forwarded or NOT Important msgs ? ");
255 static ESCKEY_S sel_flag_opt[] = {
256 /* TRANSLATORS: When selecting messages by message Status these are the
257 different types of Status you can select on. Is the message New, Recent,
258 and so on. Not means flip the meaning of the selection to the opposite
259 thing, so message is not New or not Important. */
260 {'n', 'n', "N", N_("New")},
261 {'*', '*', "*", N_("Important")},
262 {'d', 'd', "D", N_("Deleted")},
263 {'a', 'a', "A", N_("Answered")},
264 {'f', 'f', "F", N_("Forwarded")},
265 {-2, 0, NULL, NULL},
266 {'!', '!', "!", N_("Not")},
267 {-2, 0, NULL, NULL},
268 {'r', 'r', "R", N_("Recent")},
269 {'u', 'u', "U", N_("Unseen")},
270 {-1, 0, NULL, NULL}
274 static ESCKEY_S sel_date_opt[] = {
275 {0, 0, NULL, NULL},
276 /* TRANSLATORS: options when selecting messages by Date */
277 {ctrl('P'), 12, "^P", N_("Prev Day")},
278 {ctrl('N'), 13, "^N", N_("Next Day")},
279 {ctrl('X'), 11, "^X", N_("Cur Msg")},
280 {ctrl('W'), 14, "^W", N_("Toggle When")},
281 {KEY_UP, 12, "", ""},
282 {KEY_DOWN, 13, "", ""},
283 {-1, 0, NULL, NULL}
287 static char *sel_x_gm_ext =
288 N_("Search: ");
289 static char *sel_text =
290 N_("Select based on To, From, Cc, Recip, Partic, Subject fields or All msg text ? ");
291 static char *sel_text_not =
292 N_("Select based on NOT To, From, Cc, Recip, Partic, Subject or All msg text ? ");
293 static ESCKEY_S sel_text_opt[] = {
294 /* TRANSLATORS: Select messages based on the text contained in the From line, or
295 the Subject line, and so on. */
296 {'f', 'f', "F", N_("From")},
297 {'s', 's', "S", N_("Subject")},
298 {'t', 't', "T", N_("To")},
299 {'a', 'a', "A", N_("All Text")},
300 {'c', 'c', "C", N_("Cc")},
301 {'!', '!', "!", N_("Not")},
302 {'r', 'r', "R", N_("Recipient")},
303 {'p', 'p', "P", N_("Participant")},
304 {'b', 'b', "B", N_("Body")},
305 {'h', 'h', "H", N_("Header")},
306 {-1, 0, NULL, NULL}
309 static ESCKEY_S choose_action[] = {
310 {'c', 'c', "C", N_("Compose")},
311 {'r', 'r', "R", N_("Reply")},
312 {'f', 'f', "F", N_("Forward")},
313 {'b', 'b', "B", N_("Bounce")},
314 {-1, 0, NULL, NULL}
317 static char *select_num =
318 N_("Enter comma-delimited list of numbers (dash between ranges): ");
320 static char *select_size_larger_msg =
321 N_("Select messages with size larger than: ");
323 static char *select_size_smaller_msg =
324 N_("Select messages with size smaller than: ");
326 static char *sel_size_larger = N_("Larger");
327 static char *sel_size_smaller = N_("Smaller");
328 static ESCKEY_S sel_size_opt[] = {
329 {0, 0, NULL, NULL},
330 {ctrl('W'), 14, "^W", NULL},
331 {-1, 0, NULL, NULL}
334 static ESCKEY_S sel_key_opt[] = {
335 {0, 0, NULL, NULL},
336 {ctrl('T'), 14, "^T", N_("To List")},
337 {0, 0, NULL, NULL},
338 {'!', '!', "!", N_("Not")},
339 {-1, 0, NULL, NULL}
342 static ESCKEY_S flag_text_opt[] = {
343 /* TRANSLATORS: these are types of flags (markers) that the user can
344 set. For example, they can flag the message as an important message. */
345 {'n', 'n', "N", N_("New")},
346 {'*', '*', "*", N_("Important")},
347 {'d', 'd', "D", N_("Deleted")},
348 {'a', 'a', "A", N_("Answered")},
349 {'f', 'f', "F", N_("Forwarded")},
350 {'!', '!', "!", N_("Not")},
351 {ctrl('T'), 10, "^T", N_("To Flag Details")},
352 {-1, 0, NULL, NULL}
356 alpine_smime_confirm_save(char *email)
358 char prompt[128];
360 snprintf(prompt, sizeof(prompt), _("Save certificate for <%s>"),
361 email ? email : _("missing address"));
362 return want_to(prompt, 'n', 'x', NO_HELP, WT_NORM) == 'y';
365 int
366 alpine_get_password(char *prompt, char *pass, size_t len)
368 int flags = OE_PASSWD | OE_DISALLOW_HELP;
369 pass[0] = '\0';
370 return optionally_enter(pass,
371 -(ps_global->ttyo ? FOOTER_ROWS(ps_global) : 3),
372 0, len, prompt, NULL, NO_HELP, &flags);
376 smime_import_certificate(char *filename, char *full_filename, char *what, size_t len)
378 int r = 1;
379 static HISTORY_S *history = NULL;
380 static ESCKEY_S eopts[] = {
381 {ctrl('T'), 10, "^T", N_("To Files")},
382 {-1, 0, NULL, NULL},
383 {-1, 0, NULL, NULL}};
385 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
386 eopts[r].ch = ctrl('I');
387 eopts[r].rval = 11;
388 eopts[r].name = "TAB";
389 eopts[r].label = N_("Complete");
392 eopts[++r].ch = -1;
394 filename[0] = '\0';
395 full_filename[0] = '\0';
397 r = get_export_filename(ps_global, filename, NULL, full_filename,
398 len, what, "IMPORT", eopts, NULL,
399 -FOOTER_ROWS(ps_global), GE_IS_IMPORT, &history);
401 return r;
405 /*----------------------------------------------------------------------
406 The giant switch on the commands for index and viewing
408 Input: command -- The command char/code
409 in_index -- flag indicating command is from index
410 orig_command -- The original command typed before pre-processing
411 Output: force_mailchk -- Set to tell caller to force call to new_mail().
413 Result: Manifold
415 Returns 1 if the message number or attachment to show changed
416 ---*/
418 process_cmd(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
419 int command, CmdWhere in_index, int *force_mailchk)
421 int question_line, a_changed, flags = 0, ret, j;
422 int notrealinbox;
423 long new_msgno, del_count, old_msgno, i;
424 long start;
425 char *newfolder, prompt[MAX_SCREEN_COLS+1];
426 CONTEXT_S *tc;
427 MESSAGECACHE *mc;
428 #if defined(DOS) && !defined(_WINDOWS)
429 extern long coreleft();
430 #endif
432 dprint((4, "\n - process_cmd(cmd=%d) -\n", command));
434 question_line = -FOOTER_ROWS(state);
435 state->mangled_screen = 0;
436 state->mangled_footer = 0;
437 state->mangled_header = 0;
438 state->next_screen = SCREEN_FUN_NULL;
439 old_msgno = mn_get_cur(msgmap);
440 a_changed = FALSE;
441 *force_mailchk = 0;
443 switch (command) {
444 /*------------- Help --------*/
445 case MC_HELP :
447 * We're not using the h_mail_view portion of this right now because
448 * that call is being handled in scrolltool() before it gets
449 * here. Leave it in case we change how it works.
451 helper((in_index == MsgIndx)
452 ? h_mail_index
453 : (in_index == View)
454 ? h_mail_view
455 : h_mail_thread_index,
456 (in_index == MsgIndx)
457 ? _("HELP FOR MESSAGE INDEX")
458 : (in_index == View)
459 ? _("HELP FOR MESSAGE TEXT")
460 : _("HELP FOR THREAD INDEX"),
461 HLPD_NONE);
462 dprint((4,"MAIL_CMD: did help command\n"));
463 state->mangled_screen = 1;
464 break;
467 /*--------- Return to main menu ------------*/
468 case MC_MAIN :
469 state->next_screen = main_menu_screen;
470 dprint((2,"MAIL_CMD: going back to main menu\n"));
471 break;
474 /*------- View message text --------*/
475 case MC_VIEW_TEXT :
476 view_text:
477 if(any_messages(msgmap, NULL, "to View")){
478 state->next_screen = mail_view_screen;
481 break;
484 /*------- View attachment --------*/
485 case MC_VIEW_ATCH :
486 state->next_screen = attachment_screen;
487 dprint((2,"MAIL_CMD: going to attachment screen\n"));
488 break;
491 /*---------- Previous message ----------*/
492 case MC_PREVITEM :
493 if(any_messages(msgmap, NULL, NULL)){
494 if((i = mn_get_cur(msgmap)) > 1L){
495 mn_dec_cur(stream, msgmap,
496 (in_index == View && THREADING()
497 && sp_viewing_a_thread(stream))
498 ? MH_THISTHD
499 : (in_index == View)
500 ? MH_ANYTHD : MH_NONE);
501 if(i == mn_get_cur(msgmap)){
502 PINETHRD_S *thrd = NULL, *topthrd = NULL;
504 if(THRD_INDX_ENABLED()){
505 mn_dec_cur(stream, msgmap, MH_ANYTHD);
506 if(i == mn_get_cur(msgmap))
507 q_status_message1(SM_ORDER, 0, 2,
508 _("Already on first %s in Zoomed Index"),
509 THRD_INDX() ? _("thread") : _("message"));
510 else{
511 if(in_index == View
512 || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
513 ret = 'y';
514 else
515 ret = want_to(_("View previous thread"), 'y', 'x',
516 NO_HELP, WT_NORM);
518 if(ret == 'y'){
519 q_status_message(SM_ORDER, 0, 2,
520 _("Viewing previous thread"));
521 new_msgno = mn_get_cur(msgmap);
522 mn_set_cur(msgmap, i);
523 if(unview_thread(state, stream, msgmap)){
524 state->next_screen = mail_index_screen;
525 state->view_skipped_index = 0;
526 state->mangled_screen = 1;
529 mn_set_cur(msgmap, new_msgno);
530 if(THRD_AUTO_VIEW() && in_index == View){
532 thrd = fetch_thread(stream,
533 mn_m2raw(msgmap,
534 new_msgno));
535 if(count_lflags_in_thread(stream, thrd,
536 msgmap,
537 MN_NONE) == 1){
538 if(view_thread(state, stream, msgmap, 1)){
539 if(current_index_state)
540 msgmap->top_after_thrd = current_index_state->msg_at_top;
542 state->view_skipped_index = 1;
543 command = MC_VIEW_TEXT;
544 goto view_text;
549 j = 0;
550 if(THRD_AUTO_VIEW() && in_index != View){
551 thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
552 if(thrd && thrd->top)
553 topthrd = fetch_thread(stream, thrd->top);
555 if(topthrd)
556 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
559 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
560 if(view_thread(state, stream, msgmap, 1)
561 && current_index_state)
562 msgmap->top_after_thrd = current_index_state->msg_at_top;
566 state->next_screen = SCREEN_FUN_NULL;
568 else
569 mn_set_cur(msgmap, i); /* put it back */
572 else
573 q_status_message1(SM_ORDER, 0, 2,
574 _("Already on first %s in Zoomed Index"),
575 THRD_INDX() ? _("thread") : _("message"));
578 else{
579 time_t now;
581 if(!IS_NEWS(stream)
582 && ((now = time(0)) > state->last_nextitem_forcechk)){
583 *force_mailchk = 1;
584 /* check at most once a second */
585 state->last_nextitem_forcechk = now;
588 q_status_message1(SM_ORDER, 0, 1, _("Already on first %s"),
589 THRD_INDX() ? _("thread") : _("message"));
593 break;
596 /*---------- Next Message ----------*/
597 case MC_NEXTITEM :
598 if(mn_get_total(msgmap) > 0L
599 && ((i = mn_get_cur(msgmap)) < mn_get_total(msgmap))){
600 mn_inc_cur(stream, msgmap,
601 (in_index == View && THREADING()
602 && sp_viewing_a_thread(stream))
603 ? MH_THISTHD
604 : (in_index == View)
605 ? MH_ANYTHD : MH_NONE);
606 if(i == mn_get_cur(msgmap)){
607 PINETHRD_S *thrd, *topthrd;
609 if(THRD_INDX_ENABLED()){
610 if(!THRD_INDX())
611 mn_inc_cur(stream, msgmap, MH_ANYTHD);
613 if(i == mn_get_cur(msgmap)){
614 if(any_lflagged(msgmap, MN_HIDE))
615 any_messages(NULL, "more", "in Zoomed Index");
616 else
617 goto nfolder;
619 else{
620 if(in_index == View
621 || F_ON(F_NEXT_THRD_WO_CONFIRM, state))
622 ret = 'y';
623 else
624 ret = want_to(_("View next thread"), 'y', 'x',
625 NO_HELP, WT_NORM);
627 if(ret == 'y'){
628 q_status_message(SM_ORDER, 0, 2,
629 _("Viewing next thread"));
630 new_msgno = mn_get_cur(msgmap);
631 mn_set_cur(msgmap, i);
632 if(unview_thread(state, stream, msgmap)){
633 state->next_screen = mail_index_screen;
634 state->view_skipped_index = 0;
635 state->mangled_screen = 1;
638 mn_set_cur(msgmap, new_msgno);
639 if(THRD_AUTO_VIEW() && in_index == View){
641 thrd = fetch_thread(stream,
642 mn_m2raw(msgmap,
643 new_msgno));
644 if(count_lflags_in_thread(stream, thrd,
645 msgmap,
646 MN_NONE) == 1){
647 if(view_thread(state, stream, msgmap, 1)){
648 if(current_index_state)
649 msgmap->top_after_thrd = current_index_state->msg_at_top;
651 state->view_skipped_index = 1;
652 command = MC_VIEW_TEXT;
653 goto view_text;
658 j = 0;
659 if(THRD_AUTO_VIEW() && in_index != View){
660 thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno));
661 if(thrd && thrd->top)
662 topthrd = fetch_thread(stream, thrd->top);
664 if(topthrd)
665 j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE);
668 if(!THRD_AUTO_VIEW() || in_index == View || j != 1){
669 if(view_thread(state, stream, msgmap, 1)
670 && current_index_state)
671 msgmap->top_after_thrd = current_index_state->msg_at_top;
675 state->next_screen = SCREEN_FUN_NULL;
677 else
678 mn_set_cur(msgmap, i); /* put it back */
681 else if(THREADING()
682 && (thrd = fetch_thread(stream, mn_m2raw(msgmap, i)))
683 && thrd->next
684 && get_lflag(stream, NULL, thrd->rawno, MN_COLL)){
685 q_status_message(SM_ORDER, 0, 2,
686 _("Expand collapsed thread to see more messages"));
688 else
689 any_messages(NULL, "more", "in Zoomed Index");
692 else{
693 time_t now;
694 nfolder:
695 prompt[0] = '\0';
696 if(IS_NEWS(stream)
697 || (state->context_current->use & CNTXT_INCMNG)){
698 char nextfolder[MAXPATH];
700 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
701 nextfolder[sizeof(nextfolder)-1] = '\0';
702 if(next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
703 state->context_current, NULL, NULL))
704 strncpy(prompt, _(". Press TAB for next folder."),
705 sizeof(prompt));
706 else
707 strncpy(prompt, _(". No more folders to TAB to."),
708 sizeof(prompt));
710 prompt[sizeof(prompt)-1] = '\0';
713 any_messages(NULL, (mn_get_total(msgmap) > 0L) ? "more" : NULL,
714 prompt[0] ? prompt : NULL);
716 if(!IS_NEWS(stream)
717 && ((now = time(0)) > state->last_nextitem_forcechk)){
718 *force_mailchk = 1;
719 /* check at most once a second */
720 state->last_nextitem_forcechk = now;
724 break;
727 /*---------- Delete message ----------*/
728 case MC_DELETE :
729 (void) cmd_delete(state, msgmap, MCMD_NONE,
730 (in_index == View) ? cmd_delete_view : cmd_delete_index);
731 break;
734 /*---------- Undelete message ----------*/
735 case MC_UNDELETE :
736 (void) cmd_undelete(state, msgmap, MCMD_NONE);
737 update_titlebar_status();
738 break;
741 /*---------- Reply to message ----------*/
742 case MC_REPLY :
743 (void) cmd_reply(state, msgmap, MCMD_NONE, NULL);
744 break;
747 /*---------- Forward message ----------*/
748 case MC_FORWARD :
749 (void) cmd_forward(state, msgmap, MCMD_NONE, NULL);
750 break;
753 /*---------- Quit pine ------------*/
754 case MC_QUIT :
755 state->next_screen = quit_screen;
756 dprint((1,"MAIL_CMD: quit\n"));
757 break;
760 /*---------- Compose message ----------*/
761 case MC_COMPOSE :
762 state->prev_screen = (in_index == View) ? mail_view_screen
763 : mail_index_screen;
764 compose_screen(state);
765 state->mangled_screen = 1;
766 if (state->next_screen)
767 a_changed = TRUE;
768 break;
771 /*---------- Alt Compose message ----------*/
772 case MC_ROLE :
773 state->prev_screen = (in_index == View) ? mail_view_screen
774 : mail_index_screen;
775 role_compose(state);
776 if(state->next_screen)
777 a_changed = TRUE;
779 break;
782 /*--------- Folders menu ------------*/
783 case MC_FOLDERS :
784 state->start_in_context = 1;
786 /*--------- Top of Folders list menu ------------*/
787 case MC_COLLECTIONS :
788 state->next_screen = folder_screen;
789 dprint((2,"MAIL_CMD: going to folder/collection menu\n"));
790 break;
793 /*---------- Open specific new folder ----------*/
794 case MC_GOTO :
795 tc = (state->context_last && !NEWS_TEST(state->context_current))
796 ? state->context_last : state->context_current;
798 newfolder = broach_folder(question_line, 1, &notrealinbox, &tc);
799 if(newfolder){
800 visit_folder(state, newfolder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT);
801 a_changed = TRUE;
804 break;
807 /*------- Go to Index Screen ----------*/
808 case MC_INDEX :
809 state->next_screen = mail_index_screen;
810 break;
812 /*------- Skip to next interesting message -----------*/
813 case MC_TAB :
814 if(THRD_INDX()){
815 PINETHRD_S *thrd;
818 * If we're in the thread index, start looking after this
819 * thread. We don't want to match something in the current
820 * thread.
822 start = mn_get_cur(msgmap);
823 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
824 if(mn_get_revsort(msgmap)){
825 /* if reversed, top of thread is last one before next thread */
826 if(thrd && thrd->top)
827 start = mn_raw2m(msgmap, thrd->top);
829 else{
830 /* last msg of thread is at the ends of the branches/nexts */
831 while(thrd){
832 start = mn_raw2m(msgmap, thrd->rawno);
833 if(thrd->branch)
834 thrd = fetch_thread(stream, thrd->branch);
835 else if(thrd->next)
836 thrd = fetch_thread(stream, thrd->next);
837 else
838 thrd = NULL;
843 * Flags is 0 in this case because we want to not skip
844 * messages inside of threads so that we can find threads
845 * which have some unseen messages even though the top-level
846 * of the thread is already seen.
847 * If new_msgno ends up being a message which is not visible
848 * because it isn't at the top-level, the current message #
849 * will be adjusted below in adjust_cur.
851 flags = 0;
852 new_msgno = next_sorted_flagged((F_UNDEL
853 | F_UNSEEN
854 | ((F_ON(F_TAB_TO_NEW,state))
855 ? 0 : F_OR_FLAG)),
856 stream, start, &flags);
858 else if(THREADING() && sp_viewing_a_thread(stream)){
859 PINETHRD_S *thrd, *topthrd = NULL;
861 start = mn_get_cur(msgmap);
864 * Things are especially complicated when we're viewing_a_thread
865 * from the thread index. First we have to check within the
866 * current thread for a new message. If none is found, then
867 * we search in the next threads and offer to continue in
868 * them. Then we offer to go to the next folder.
870 flags = NSF_SKIP_CHID;
871 new_msgno = next_sorted_flagged((F_UNDEL
872 | F_UNSEEN
873 | ((F_ON(F_TAB_TO_NEW,state))
874 ? 0 : F_OR_FLAG)),
875 stream, start, &flags);
877 * If we found a match then we are done, that is another message
878 * in the current thread index. Otherwise, we have to look
879 * further.
881 if(!(flags & NSF_FLAG_MATCH)){
882 ret = 'n';
883 while(1){
885 flags = 0;
886 new_msgno = next_sorted_flagged((F_UNDEL
887 | F_UNSEEN
888 | ((F_ON(F_TAB_TO_NEW,
889 state))
890 ? 0 : F_OR_FLAG)),
891 stream, start, &flags);
893 * If we got a match, new_msgno is a message in
894 * a different thread from the one we are viewing.
896 if(flags & NSF_FLAG_MATCH){
897 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
898 if(thrd && thrd->top)
899 topthrd = fetch_thread(stream, thrd->top);
901 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
902 static ESCKEY_S next_opt[] = {
903 {'y', 'y', "Y", N_("Yes")},
904 {'n', 'n', "N", N_("No")},
905 {TAB, 'n', "Tab", N_("NextNew")},
906 {-1, 0, NULL, NULL}
909 if(in_index)
910 snprintf(prompt, sizeof(prompt), _("View thread number %s? "),
911 topthrd ? comatose(topthrd->thrdno) : "?");
912 else
913 snprintf(prompt, sizeof(prompt),
914 _("View message in thread number %s? "),
915 topthrd ? comatose(topthrd->thrdno) : "?");
917 prompt[sizeof(prompt)-1] = '\0';
919 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
920 next_opt, 'y', 'x', NO_HELP,
921 RB_NORM);
922 if(ret == 'x'){
923 cmd_cancelled(NULL);
924 goto get_out;
927 else
928 ret = 'y';
930 if(ret == 'y'){
931 if(unview_thread(state, stream, msgmap)){
932 state->next_screen = mail_index_screen;
933 state->view_skipped_index = 0;
934 state->mangled_screen = 1;
937 mn_set_cur(msgmap, new_msgno);
938 if(THRD_AUTO_VIEW()){
940 if(count_lflags_in_thread(stream, topthrd,
941 msgmap, MN_NONE) == 1){
942 if(view_thread(state, stream, msgmap, 1)){
943 if(current_index_state)
944 msgmap->top_after_thrd = current_index_state->msg_at_top;
946 state->view_skipped_index = 1;
947 command = MC_VIEW_TEXT;
948 goto view_text;
953 if(view_thread(state, stream, msgmap, 1) && current_index_state)
954 msgmap->top_after_thrd = current_index_state->msg_at_top;
956 state->next_screen = SCREEN_FUN_NULL;
957 break;
959 else if(ret == 'n' && topthrd){
961 * skip to end of this thread and look starting
962 * in the next thread.
964 if(mn_get_revsort(msgmap)){
966 * if reversed, top of thread is last one
967 * before next thread
969 start = mn_raw2m(msgmap, topthrd->rawno);
971 else{
973 * last msg of thread is at the ends of
974 * the branches/nexts
976 thrd = topthrd;
977 while(thrd){
978 start = mn_raw2m(msgmap, thrd->rawno);
979 if(thrd->branch)
980 thrd = fetch_thread(stream, thrd->branch);
981 else if(thrd->next)
982 thrd = fetch_thread(stream, thrd->next);
983 else
984 thrd = NULL;
988 else if(ret == 'n')
989 break;
991 else
992 break;
996 else{
998 start = mn_get_cur(msgmap);
1001 * If we are on a collapsed thread, start looking after the
1002 * collapsed part, unless we are viewing the message.
1004 if(THREADING() && in_index != View){
1005 PINETHRD_S *thrd;
1006 long rawno;
1007 int collapsed;
1009 rawno = mn_m2raw(msgmap, start);
1010 thrd = fetch_thread(stream, rawno);
1011 collapsed = thrd && thrd->next
1012 && get_lflag(stream, NULL, rawno, MN_COLL);
1014 if(collapsed){
1015 if(mn_get_revsort(msgmap)){
1016 if(thrd && thrd->top)
1017 start = mn_raw2m(msgmap, thrd->top);
1019 else{
1020 while(thrd){
1021 start = mn_raw2m(msgmap, thrd->rawno);
1022 if(thrd->branch)
1023 thrd = fetch_thread(stream, thrd->branch);
1024 else if(thrd->next)
1025 thrd = fetch_thread(stream, thrd->next);
1026 else
1027 thrd = NULL;
1034 new_msgno = next_sorted_flagged((F_UNDEL
1035 | F_UNSEEN
1036 | ((F_ON(F_TAB_TO_NEW,state))
1037 ? 0 : F_OR_FLAG)),
1038 stream, start, &flags);
1042 * If there weren't any unread messages left, OR there
1043 * aren't any messages at all, we may want to offer to
1044 * go on to the next folder...
1046 if(flags & NSF_FLAG_MATCH){
1047 mn_set_cur(msgmap, new_msgno);
1048 if(in_index != View)
1049 adjust_cur_to_visible(stream, msgmap);
1051 else{
1052 int in_inbox = sp_flagged(stream, SP_INBOX);
1054 if(state->context_current
1055 && ((NEWS_TEST(state->context_current)
1056 && context_isambig(state->cur_folder))
1057 || ((state->context_current->use & CNTXT_INCMNG)
1058 && (in_inbox
1059 || folder_index(state->cur_folder,
1060 state->context_current,
1061 FI_FOLDER) >= 0)))){
1062 char nextfolder[MAXPATH];
1063 MAILSTREAM *nextstream = NULL;
1064 long recent_cnt;
1065 int did_cancel = 0;
1067 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
1068 nextfolder[sizeof(nextfolder)-1] = '\0';
1069 while(1){
1070 if(!(next_folder(&nextstream, nextfolder, sizeof(nextfolder), nextfolder,
1071 state->context_current, &recent_cnt,
1072 F_ON(F_TAB_NO_CONFIRM,state)
1073 ? NULL : &did_cancel))){
1074 if(!in_inbox){
1075 static ESCKEY_S inbox_opt[] = {
1076 {'y', 'y', "Y", N_("Yes")},
1077 {'n', 'n', "N", N_("No")},
1078 {TAB, 'z', "Tab", N_("To Inbox")},
1079 {-1, 0, NULL, NULL}
1082 if(F_ON(F_RET_INBOX_NO_CONFIRM,state))
1083 ret = 'y';
1084 else{
1085 /* TRANSLATORS: this is a question, with some information followed
1086 by Return to INBOX? */
1087 if(state->context_current->use&CNTXT_INCMNG)
1088 snprintf(prompt, sizeof(prompt), _("No more incoming folders. Return to \"%s\"? "), state->inbox_name);
1089 else
1090 snprintf(prompt, sizeof(prompt), _("No more news groups. Return to \"%s\"? "), state->inbox_name);
1092 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1093 inbox_opt, 'y', 'x',
1094 NO_HELP, RB_NORM);
1098 * 'z' is a synonym for 'y'. It is not 'y'
1099 * so that it isn't displayed as a default
1100 * action with square-brackets around it
1101 * in the keymenu...
1103 if(ret == 'y' || ret == 'z'){
1104 visit_folder(state, state->inbox_name,
1105 state->context_current,
1106 NULL, DB_INBOXWOCNTXT);
1107 a_changed = TRUE;
1110 else if (did_cancel)
1111 cmd_cancelled(NULL);
1112 else{
1113 if(state->context_current->use&CNTXT_INCMNG)
1114 q_status_message(SM_ORDER, 0, 2, _("No more incoming folders"));
1115 else
1116 q_status_message(SM_ORDER, 0, 2, _("No more news groups"));
1119 break;
1123 #define CNTLEN 80
1124 char *front, type[80], cnt[CNTLEN], fbuf[MAX_SCREEN_COLS/2+1];
1125 int rbspace, avail, need, take_back;
1128 * View_next_
1129 * Incoming_folder_ or news_group_ or folder_ or group_
1130 * "foldername"
1131 * _(13 recent) or _(some recent) or nothing
1132 * ?_
1134 front = "View next";
1135 strncpy(type,
1136 (state->context_current->use & CNTXT_INCMNG)
1137 ? "Incoming folder" : "news group",
1138 sizeof(type));
1139 type[sizeof(type)-1] = '\0';
1140 snprintf(cnt, sizeof(cnt), " (%.*s %s)", CNTLEN-20,
1141 recent_cnt ? long2string(recent_cnt) : "some",
1142 F_ON(F_TAB_USES_UNSEEN, ps_global)
1143 ? "unseen" : "recent");
1144 cnt[sizeof(cnt)-1] = '\0';
1147 * Space reserved for radio_buttons call.
1148 * If we make this 3 then radio_buttons won't mess
1149 * with the prompt. If we make it 2, then we get
1150 * one more character to use but radio_buttons will
1151 * cut off the last character of our prompt, which is
1152 * ok because it is a space.
1154 rbspace = 2;
1155 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols
1156 : 80;
1157 need = strlen(front)+1 + strlen(type)+1 +
1158 + strlen(nextfolder)+2 + strlen(cnt) +
1159 2 + rbspace;
1160 if(avail < need){
1161 take_back = strlen(type);
1162 strncpy(type,
1163 (state->context_current->use & CNTXT_INCMNG)
1164 ? "folder" : "group", sizeof(type));
1165 take_back -= strlen(type);
1166 need -= take_back;
1167 if(avail < need){
1168 need -= strlen(cnt);
1169 cnt[0] = '\0';
1172 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1173 snprintf(prompt, sizeof(prompt), "%.*s %.*s \"%.*s\"%.*s? ",
1174 (MAX_SCREEN_COLS+1)/8, front,
1175 (MAX_SCREEN_COLS+1)/8, type,
1176 (MAX_SCREEN_COLS+1)/2,
1177 short_str(nextfolder, fbuf, sizeof(fbuf),
1178 strlen(nextfolder) -
1179 ((need>avail) ? (need-avail) : 0),
1180 MidDots),
1181 (MAX_SCREEN_COLS+1)/8, cnt);
1182 prompt[sizeof(prompt)-1] = '\0';
1186 * When help gets added, this'll have to become
1187 * a loop like the rest...
1189 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
1190 static ESCKEY_S next_opt[] = {
1191 {'y', 'y', "Y", N_("Yes")},
1192 {'n', 'n', "N", N_("No")},
1193 {TAB, 'n', "Tab", N_("NextNew")},
1194 {-1, 0, NULL, NULL}
1197 ret = radio_buttons(prompt, -FOOTER_ROWS(state),
1198 next_opt, 'y', 'x', NO_HELP,
1199 RB_NORM);
1200 if(ret == 'x'){
1201 cmd_cancelled(NULL);
1202 break;
1205 else
1206 ret = 'y';
1208 if(ret == 'y'){
1209 if(nextstream && sp_dead_stream(nextstream))
1210 nextstream = NULL;
1212 visit_folder(state, nextfolder,
1213 state->context_current, nextstream,
1214 DB_FROMTAB);
1215 /* visit_folder takes care of nextstream */
1216 nextstream = NULL;
1217 a_changed = TRUE;
1218 break;
1222 if(nextstream)
1223 pine_mail_close(nextstream);
1225 else
1226 any_messages(NULL,
1227 (mn_get_total(msgmap) > 0L)
1228 ? IS_NEWS(stream) ? "more undeleted" : "more new"
1229 : NULL,
1230 NULL);
1233 get_out:
1235 break;
1238 /*------- Zoom -----------*/
1239 case MC_ZOOM :
1241 * Right now the way zoom is implemented is sort of silly.
1242 * There are two per-message flags where just one and a
1243 * global "zoom mode" flag to suppress messages from the index
1244 * should suffice.
1246 if(any_messages(msgmap, NULL, "to Zoom on")){
1247 if(unzoom_index(state, stream, msgmap)){
1248 dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
1249 q_status_message(SM_ORDER,0,2, _("Index Zoom Mode is now off"));
1251 else if((i = zoom_index(state, stream, msgmap, MN_SLCT)) != 0){
1252 if(any_lflagged(msgmap, MN_HIDE)){
1253 dprint((4,"\n\n ---- Entering ZOOM mode ----\n"));
1254 q_status_message4(SM_ORDER, 0, 2,
1255 _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"),
1256 THRD_INDX() ? "" : comatose(i),
1257 THRD_INDX() ? "" : " ",
1258 THRD_INDX() ? _("threads") : _("message"),
1259 THRD_INDX() ? "" : plural(i));
1261 else
1262 q_status_message(SM_ORDER, 0, 2,
1263 _("All messages selected, so not entering Index Zoom Mode"));
1265 else
1266 any_messages(NULL, "selected", "to Zoom on");
1269 break;
1272 /*---------- print message on paper ----------*/
1273 case MC_PRINTMSG :
1274 if(any_messages(msgmap, NULL, "to print"))
1275 (void) cmd_print(state, msgmap, MCMD_NONE, in_index);
1277 break;
1280 /*---------- Take Address ----------*/
1281 case MC_TAKE :
1282 if(F_ON(F_ENABLE_ROLE_TAKE, state) ||
1283 any_messages(msgmap, NULL, "to Take address from"))
1284 (void) cmd_take_addr(state, msgmap, MCMD_NONE);
1286 break;
1289 /*---------- Save Message ----------*/
1290 case MC_SAVE :
1291 if(any_messages(msgmap, NULL, "to Save"))
1292 (void) cmd_save(state, stream, msgmap, MCMD_NONE, in_index);
1294 break;
1297 /*---------- Export message ----------*/
1298 case MC_EXPORT :
1299 if(any_messages(msgmap, NULL, "to Export")){
1300 (void) cmd_export(state, msgmap, question_line, MCMD_NONE);
1301 state->mangled_footer = 1;
1304 break;
1307 /*---------- Expunge ----------*/
1308 case MC_EXPUNGE :
1309 (void) cmd_expunge(state, stream, msgmap, MCMD_NONE);
1310 break;
1313 /*------- Unexclude -----------*/
1314 case MC_UNEXCLUDE :
1315 if(!(IS_NEWS(stream) && stream->rdonly)){
1316 q_status_message(SM_ORDER, 0, 3,
1317 _("Unexclude not available for mail folders"));
1319 else if(any_lflagged(msgmap, MN_EXLD)){
1320 SEARCHPGM *pgm;
1321 long i;
1322 int exbits;
1325 * Since excluded means "hidden deleted" and "killed",
1326 * the count should reflect the former.
1328 pgm = mail_newsearchpgm();
1329 pgm->deleted = 1;
1330 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
1331 for(i = 1L, del_count = 0L; i <= stream->nmsgs; i++)
1332 if((mc = mail_elt(stream, i)) && mc->searched
1333 && get_lflag(stream, NULL, i, MN_EXLD)
1334 && !(msgno_exceptions(stream, i, "0", &exbits, FALSE)
1335 && (exbits & MSG_EX_FILTERED)))
1336 del_count++;
1338 if(del_count > 0L){
1339 state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1340 snprintf(prompt, sizeof(prompt), "UNexclude %ld message%s in %.*s", del_count,
1341 plural(del_count), MAX_SCREEN_COLS+1-40,
1342 pretty_fn(state->cur_folder));
1343 prompt[sizeof(prompt)-1] = '\0';
1344 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
1345 || (F_ON(F_AUTO_EXPUNGE, state)
1346 && (state->context_current
1347 && (state->context_current->use & CNTXT_INCMNG))
1348 && context_isambig(state->cur_folder))
1349 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
1350 long save_cur_rawno;
1351 int were_viewing_a_thread;
1353 save_cur_rawno = mn_m2raw(msgmap, mn_get_cur(msgmap));
1354 were_viewing_a_thread = (THREADING()
1355 && sp_viewing_a_thread(stream));
1357 if(msgno_include(stream, msgmap, MI_NONE)){
1358 clear_index_cache(stream, 0);
1360 if(stream && stream->spare)
1361 erase_threading_info(stream, msgmap);
1363 refresh_sort(stream, msgmap, SRT_NON);
1366 if(were_viewing_a_thread){
1367 if(save_cur_rawno > 0L)
1368 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1370 if(view_thread(state, stream, msgmap, 1) && current_index_state)
1371 msgmap->top_after_thrd = current_index_state->msg_at_top;
1374 if(save_cur_rawno > 0L)
1375 mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno));
1377 state->mangled_screen = 1;
1378 q_status_message2(SM_ORDER, 0, 4,
1379 "%s message%s UNexcluded",
1380 long2string(del_count),
1381 plural(del_count));
1383 if(in_index != View)
1384 adjust_cur_to_visible(stream, msgmap);
1386 else
1387 any_messages(NULL, NULL, "UNexcluded");
1389 else
1390 any_messages(NULL, "excluded", "to UNexclude");
1392 else
1393 any_messages(NULL, "excluded", "to UNexclude");
1395 break;
1398 /*------- Make Selection -----------*/
1399 case MC_SELECT :
1400 if(any_messages(msgmap, NULL, "to Select")){
1401 if(aggregate_select(state, msgmap, question_line, in_index) == 0
1402 && (in_index == MsgIndx || in_index == ThrdIndx)
1403 && F_ON(F_AUTO_ZOOM, state)
1404 && any_lflagged(msgmap, MN_SLCT) > 0L
1405 && !any_lflagged(msgmap, MN_HIDE))
1406 (void) zoom_index(state, stream, msgmap, MN_SLCT);
1409 break;
1412 /*------- Toggle Current Message Selection State -----------*/
1413 case MC_SELCUR :
1414 if(any_messages(msgmap, NULL, NULL)){
1415 if((select_by_current(state, msgmap, in_index)
1416 || (F_OFF(F_UNSELECT_WONT_ADVANCE, state)
1417 && !any_lflagged(msgmap, MN_HIDE)))
1418 && (i = mn_get_cur(msgmap)) < mn_get_total(msgmap)){
1419 /* advance current */
1420 mn_inc_cur(stream, msgmap,
1421 (in_index == View && THREADING()
1422 && sp_viewing_a_thread(stream))
1423 ? MH_THISTHD
1424 : (in_index == View)
1425 ? MH_ANYTHD : MH_NONE);
1429 break;
1432 /*------- Apply command -----------*/
1433 case MC_APPLY :
1434 if(any_messages(msgmap, NULL, NULL)){
1435 if(any_lflagged(msgmap, MN_SLCT) > 0L){
1436 if(apply_command(state, stream, msgmap, 0,
1437 AC_NONE, question_line)){
1438 if(F_ON(F_AUTO_UNSELECT, state)){
1439 agg_select_all(stream, msgmap, NULL, 0);
1440 unzoom_index(state, stream, msgmap);
1442 else if(F_ON(F_AUTO_UNZOOM, state))
1443 unzoom_index(state, stream, msgmap);
1446 else
1447 any_messages(NULL, NULL, "to Apply command to. Try \"Select\"");
1450 break;
1453 /*-------- Sort command -------*/
1454 case MC_SORT :
1456 int were_threading = THREADING();
1457 SortOrder sort = mn_get_sort(msgmap);
1458 int rev = mn_get_revsort(msgmap);
1460 dprint((1,"MAIL_CMD: sort\n"));
1461 if(select_sort(state, question_line, &sort, &rev)){
1462 /* $ command reinitializes threading collapsed/expanded info */
1463 if(SORT_IS_THREADED(msgmap) && !SEP_THRDINDX())
1464 erase_threading_info(stream, msgmap);
1466 if(ps_global && ps_global->ttyo){
1467 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
1468 ps_global->mangled_footer = 1;
1471 sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN);
1474 state->mangled_footer = 1;
1477 * We've changed whether we are threading or not so we need to
1478 * exit the index and come back in so that we switch between the
1479 * thread index and the regular index. Sort_folder will have
1480 * reset viewing_a_thread if necessary.
1482 if(SEP_THRDINDX()
1483 && ((!were_threading && THREADING())
1484 || (were_threading && !THREADING()))){
1485 state->next_screen = mail_index_screen;
1486 state->mangled_screen = 1;
1490 break;
1493 /*------- Toggle Full Headers -----------*/
1494 case MC_FULLHDR :
1495 state->full_header++;
1496 if(state->full_header == 1){
1497 if(!(state->quote_suppression_threshold
1498 && (state->some_quoting_was_suppressed || in_index != View)))
1499 state->full_header++;
1501 else if(state->full_header > 2)
1502 state->full_header = 0;
1504 switch(state->full_header){
1505 case 0:
1506 q_status_message(SM_ORDER, 0, 3,
1507 _("Display of full headers is now off."));
1508 break;
1510 case 1:
1511 q_status_message1(SM_ORDER, 0, 3,
1512 _("Quotes displayed, use %s to see full headers"),
1513 F_ON(F_USE_FK, state) ? "F9" : "H");
1514 break;
1516 case 2:
1517 q_status_message(SM_ORDER, 0, 3,
1518 _("Display of full headers is now on."));
1519 break;
1523 a_changed = TRUE;
1524 break;
1527 case MC_TOGGLE :
1528 a_changed = TRUE;
1529 break;
1532 #ifdef SMIME
1533 /*------- Try to decrypt message -----------*/
1534 case MC_DECRYPT:
1535 if(state->smime && state->smime->need_passphrase)
1536 smime_get_passphrase();
1538 a_changed = TRUE;
1539 break;
1541 case MC_SECURITY:
1542 smime_info_screen(state);
1543 break;
1544 #endif
1547 /*------- Bounce -----------*/
1548 case MC_BOUNCE :
1549 (void) cmd_bounce(state, msgmap, MCMD_NONE, NULL);
1550 break;
1553 /*------- Flag -----------*/
1554 case MC_FLAG :
1555 dprint((4, "\n - flag message -\n"));
1556 (void) cmd_flag(state, msgmap, MCMD_NONE);
1557 break;
1560 /*------- Pipe message -----------*/
1561 case MC_PIPE :
1562 (void) cmd_pipe(state, msgmap, MCMD_NONE);
1563 break;
1566 /*--------- Default, unknown command ----------*/
1567 default:
1568 alpine_panic("Unexpected command case");
1569 break;
1572 return((a_changed || mn_get_cur(msgmap) != old_msgno) ? 1 : 0);
1577 /*----------------------------------------------------------------------
1578 Map some of the special characters into sensible strings for human
1579 consumption.
1580 c is a UCS-4 character!
1581 ----*/
1582 char *
1583 pretty_command(UCS c)
1585 static char buf[10];
1586 char *s;
1588 buf[0] = '\0';
1589 s = buf;
1591 switch(c){
1592 case ' ' : s = "SPACE"; break;
1593 case '\033' : s = "ESC"; break;
1594 case '\177' : s = "DEL"; break;
1595 case ctrl('I') : s = "TAB"; break;
1596 case ctrl('J') : s = "LINEFEED"; break;
1597 case ctrl('M') : s = "RETURN"; break;
1598 case ctrl('Q') : s = "XON"; break;
1599 case ctrl('S') : s = "XOFF"; break;
1600 case KEY_UP : s = "Up Arrow"; break;
1601 case KEY_DOWN : s = "Down Arrow"; break;
1602 case KEY_RIGHT : s = "Right Arrow"; break;
1603 case KEY_LEFT : s = "Left Arrow"; break;
1604 case KEY_PGUP : s = "Prev Page"; break;
1605 case KEY_PGDN : s = "Next Page"; break;
1606 case KEY_HOME : s = "Home"; break;
1607 case KEY_END : s = "End"; break;
1608 case KEY_DEL : s = "Delete"; break; /* Not necessary DEL! */
1609 case KEY_JUNK : s = "Junk!"; break;
1610 case BADESC : s = "Bad Esc"; break;
1611 case NO_OP_IDLE : s = "NO_OP_IDLE"; break;
1612 case NO_OP_COMMAND : s = "NO_OP_COMMAND"; break;
1613 case KEY_RESIZE : s = "KEY_RESIZE"; break;
1614 case KEY_UTF8 : s = "KEY_UTF8"; break;
1615 case KEY_MOUSE : s = "KEY_MOUSE"; break;
1616 case KEY_SCRLUPL : s = "KEY_SCRLUPL"; break;
1617 case KEY_SCRLDNL : s = "KEY_SCRLDNL"; break;
1618 case KEY_SCRLTO : s = "KEY_SCRLTO"; break;
1619 case KEY_XTERM_MOUSE : s = "KEY_XTERM_MOUSE"; break;
1620 case KEY_DOUBLE_ESC : s = "KEY_DOUBLE_ESC"; break;
1621 case CTRL_KEY_UP : s = "Ctrl Up Arrow"; break;
1622 case CTRL_KEY_DOWN : s = "Ctrl Down Arrow"; break;
1623 case CTRL_KEY_RIGHT : s = "Ctrl Right Arrow"; break;
1624 case CTRL_KEY_LEFT : s = "Ctrl Left Arrow"; break;
1625 case PF1 :
1626 case PF2 :
1627 case PF3 :
1628 case PF4 :
1629 case PF5 :
1630 case PF6 :
1631 case PF7 :
1632 case PF8 :
1633 case PF9 :
1634 case PF10 :
1635 case PF11 :
1636 case PF12 :
1637 snprintf(s = buf, sizeof(buf), "F%ld", (long) (c - PF1 + 1));
1638 break;
1640 default:
1641 if(c < ' ' || (c >= 0x80 && c < 0xA0)){
1642 char d;
1643 int c1;
1645 c1 = (c >= 0x80);
1646 d = (c & 0x1f) + 'A' - 1;
1647 snprintf(s = buf, sizeof(buf), "%c%c", c1 ? '~' : '^', d);
1649 else{
1650 memset(buf, 0, sizeof(buf));
1651 utf8_put((unsigned char *) buf, (unsigned long) c);
1654 break;
1657 return(s);
1661 /*----------------------------------------------------------------------
1662 Complain about bogus input
1664 Args: ch -- input command to complain about
1665 help -- string indicating where to get help
1667 ----*/
1668 void
1669 bogus_command(UCS cmd, char *help)
1671 if(cmd == ctrl('Q') || cmd == ctrl('S'))
1672 q_status_message1(SM_ASYNC, 0, 2,
1673 "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.",
1674 pretty_command(cmd));
1675 else if(cmd == KEY_JUNK)
1676 q_status_message3(SM_ORDER, 0, 2,
1677 "Invalid key pressed.%s%s%s",
1678 (help) ? " Use " : "",
1679 (help) ? help : "",
1680 (help) ? " for help" : "");
1681 else
1682 q_status_message4(SM_ORDER, 0, 2,
1683 "Command \"%s\" not defined for this screen.%s%s%s",
1684 pretty_command(cmd),
1685 (help) ? " Use " : "",
1686 (help) ? help : "",
1687 (help) ? " for help" : "");
1691 void
1692 bogus_utf8_command(char *cmd, char *help)
1694 q_status_message4(SM_ORDER, 0, 2,
1695 "Command \"%s\" not defined for this screen.%s%s%s",
1696 cmd ? cmd : "?",
1697 (help) ? " Use " : "",
1698 (help) ? help : "",
1699 (help) ? " for help" : "");
1703 /*----------------------------------------------------------------------
1704 Execute FLAG message command
1706 Args: state -- Various satate info
1707 msgmap -- map of c-client to local message numbers
1709 Result: with side effect of "current" message FLAG flag set or UNset
1711 ----*/
1713 cmd_flag(struct pine *state, MSGNO_S *msgmap, int aopt)
1715 char *flagit, *seq, *screen_text[20], **exp, **p, *answer = NULL;
1716 char *keyword_array[2];
1717 int user_defined_flags = 0, mailbox_flags = 0;
1718 int directly_to_maint_screen = 0;
1719 long unflagged, flagged, flags, rawno;
1720 MESSAGECACHE *mc = NULL;
1721 KEYWORD_S *kw;
1722 int i, cnt, is_set, trouble = 0, rv = 0;
1723 size_t len;
1724 struct flag_table *fp, *ftbl = NULL;
1725 struct flag_screen flag_screen;
1726 static char *flag_screen_text1[] = {
1727 N_(" Set desired flags for current message below. An 'X' means set"),
1728 N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."),
1729 NULL
1732 static char *flag_screen_text2[] = {
1733 N_(" Set desired flags below for selected messages. A '?' means to"),
1734 N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"),
1735 N_(" to unset it. Use the \"Return\" key to toggle, and choose"),
1736 N_(" \"Exit\" when finished."),
1737 NULL
1740 static struct flag_table default_ftbl[] = {
1741 {N_("Important"), h_flag_important, F_FLAG, 0, 0, NULL, NULL},
1742 {N_("New"), h_flag_new, F_SEEN, 0, 0, NULL, NULL},
1743 {N_("Answered"), h_flag_answered, F_ANS, 0, 0, NULL, NULL},
1744 {N_("Forwarded"), h_flag_forwarded, F_FWD, 0, 0, NULL, NULL},
1745 {N_("Deleted"), h_flag_deleted, F_DEL, 0, 0, NULL, NULL},
1746 {NULL, NO_HELP, 0, 0, 0, NULL, NULL}
1749 /* Only check for dead stream for now. Should check permanent flags
1750 * eventually
1752 if(!(any_messages(msgmap, NULL, "to Flag") && can_set_flag(state, "flag", 1)))
1753 return rv;
1755 if(sp_io_error_on_stream(state->mail_stream)){
1756 sp_set_io_error_on_stream(state->mail_stream, 0);
1757 pine_mail_check(state->mail_stream); /* forces write */
1758 return rv;
1761 go_again:
1762 answer = NULL;
1763 user_defined_flags = 0;
1764 mailbox_flags = 0;
1765 mc = NULL;
1766 trouble = 0;
1767 ftbl = NULL;
1769 /* count how large ftbl will be */
1770 for(cnt = 0; default_ftbl[cnt].name; cnt++)
1773 /* add user flags */
1774 for(kw = ps_global->keywords; kw; kw = kw->next){
1775 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick)) || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1776 user_defined_flags++;
1777 cnt++;
1782 * Add mailbox flags that aren't user-defined flags.
1783 * Don't consider it if it matches either one of our defined
1784 * keywords or one of our defined nicknames for a keyword.
1786 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1787 char *q;
1789 q = stream_to_user_flag_name(state->mail_stream, i);
1790 if(q && q[0]){
1791 for(kw = ps_global->keywords; kw; kw = kw->next){
1792 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1793 break;
1797 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1798 mailbox_flags++;
1799 cnt++;
1803 cnt += (user_defined_flags ? 2 : 0) + (mailbox_flags ? 2 : 0);
1805 /* set up ftbl, first the system flags */
1806 ftbl = (struct flag_table *) fs_get((cnt+1) * sizeof(*ftbl));
1807 memset(ftbl, 0, (cnt+1) * sizeof(*ftbl));
1808 for(i = 0, fp = ftbl; default_ftbl[i].name; i++, fp++){
1809 fp->name = cpystr(default_ftbl[i].name);
1810 fp->help = default_ftbl[i].help;
1811 fp->flag = default_ftbl[i].flag;
1812 fp->set = default_ftbl[i].set;
1813 fp->ukn = default_ftbl[i].ukn;
1816 if(user_defined_flags){
1817 fp->flag = F_COMMENT;
1818 fp->name = cpystr("");
1819 fp++;
1820 fp->flag = F_COMMENT;
1821 len = strlen(_("User-defined Keywords from Setup/Config"));
1822 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1823 snprintf(fp->name, len+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config"));
1824 fp++;
1827 /* then the user-defined keywords */
1828 if(user_defined_flags)
1829 for(kw = ps_global->keywords; kw; kw = kw->next){
1830 if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick))
1831 || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){
1832 fp->name = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
1833 fp->keyword = cpystr(kw->kw ? kw->kw : "");
1834 if(kw->nick && kw->kw){
1835 size_t l;
1837 l = strlen(kw->kw)+2;
1838 fp->comment = (char *) fs_get((l+1) * sizeof(char));
1839 snprintf(fp->comment, l+1, "(%.*s)", (int) strlen(kw->kw), kw->kw);
1840 fp->comment[l] = '\0';
1843 fp->help = h_flag_user_flag;
1844 fp->flag = F_KEYWORD;
1845 fp->set = 0;
1846 fp->ukn = 0;
1847 fp++;
1851 if(mailbox_flags){
1852 fp->flag = F_COMMENT;
1853 fp->name = cpystr("");
1854 fp++;
1855 fp->flag = F_COMMENT;
1856 len = strlen(_("Other keywords in the mailbox that are not user-defined"));
1857 fp->name = (char *) fs_get((len+6+6+1) * sizeof(char));
1858 snprintf(fp->name, len+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined"));
1859 fp++;
1862 /* then the extra mailbox-defined keywords */
1863 if(mailbox_flags)
1864 for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){
1865 char *q;
1867 q = stream_to_user_flag_name(state->mail_stream, i);
1868 if(q && q[0]){
1869 for(kw = ps_global->keywords; kw; kw = kw->next){
1870 if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q)))
1871 break;
1875 if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){
1876 fp->name = cpystr(q);
1877 fp->keyword = cpystr(q);
1878 fp->help = h_flag_user_flag;
1879 fp->flag = F_KEYWORD;
1880 fp->set = 0;
1881 fp->ukn = 0;
1882 fp++;
1886 flag_screen.flag_table = &ftbl;
1887 flag_screen.explanation = screen_text;
1889 if(MCMD_ISAGG(aopt)){
1890 if(!pseudo_selected(ps_global->mail_stream, msgmap)){
1891 free_flag_table(&ftbl);
1892 return rv;
1895 exp = flag_screen_text2;
1896 for(fp = ftbl; fp->name; fp++){
1897 fp->set = CMD_FLAG_UNKN; /* set to unknown */
1898 fp->ukn = TRUE;
1901 else if(state->mail_stream
1902 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
1903 && rawno <= state->mail_stream->nmsgs
1904 && (mc = mail_elt(state->mail_stream, rawno))){
1905 exp = flag_screen_text1;
1906 for(fp = &ftbl[0]; fp->name; fp++){
1907 fp->ukn = 0;
1908 if(fp->flag == F_KEYWORD){
1909 /* see if this keyword is defined for this message */
1910 fp->set = CMD_FLAG_CLEAR;
1911 if(user_flag_is_set(state->mail_stream,
1912 rawno, fp->keyword))
1913 fp->set = CMD_FLAG_SET;
1915 else if(fp->flag == F_FWD){
1916 /* see if forwarded keyword is defined for this message */
1917 fp->set = CMD_FLAG_CLEAR;
1918 if(user_flag_is_set(state->mail_stream,
1919 rawno, FORWARDED_FLAG))
1920 fp->set = CMD_FLAG_SET;
1922 else if(fp->flag != F_COMMENT)
1923 fp->set = ((fp->flag == F_SEEN && !mc->seen)
1924 || (fp->flag == F_DEL && mc->deleted)
1925 || (fp->flag == F_FLAG && mc->flagged)
1926 || (fp->flag == F_ANS && mc->answered))
1927 ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
1930 else{
1931 q_status_message(SM_ORDER | SM_DING, 3, 4,
1932 _("Error accessing message data"));
1933 free_flag_table(&ftbl);
1934 return rv;
1937 if(directly_to_maint_screen)
1938 goto the_maint_screen;
1940 #ifdef _WINDOWS
1941 if (mswin_usedialog ()) {
1942 if (!os_flagmsgdialog (&ftbl[0])){
1943 free_flag_table(&ftbl);
1944 return rv;
1947 else
1948 #endif
1950 int use_maint_screen;
1951 int keyword_shortcut = 0;
1953 use_maint_screen = F_ON(F_FLAG_SCREEN_DFLT, ps_global);
1955 if(!use_maint_screen){
1957 * We're going to call cmd_flag_prompt(). We need
1958 * to decide whether or not to offer the keyword setting
1959 * shortcut. We'll offer it if the user has the feature
1960 * enabled AND there are some possible keywords that could
1961 * be set.
1963 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global)){
1964 for(fp = &ftbl[0]; fp->name && !keyword_shortcut; fp++){
1965 if(fp->flag == F_KEYWORD){
1966 int first_char;
1967 ESCKEY_S *tp;
1969 first_char = (fp->name && fp->name[0])
1970 ? fp->name[0] : -2;
1971 if(isascii(first_char) && isupper(first_char))
1972 first_char = tolower((unsigned char) first_char);
1974 for(tp=flag_text_opt; tp->ch != -1; tp++)
1975 if(tp->ch == first_char)
1976 break;
1978 if(tp->ch == -1)
1979 keyword_shortcut++;
1984 use_maint_screen = !cmd_flag_prompt(state, &flag_screen,
1985 keyword_shortcut);
1988 the_maint_screen:
1989 if(use_maint_screen){
1990 for(p = &screen_text[0]; *exp; p++, exp++)
1991 *p = *exp;
1993 *p = NULL;
1995 directly_to_maint_screen = flag_maintenance_screen(state, &flag_screen);
1999 /* reacquire the elt pointer */
2000 mc = (state->mail_stream
2001 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
2002 && rawno <= state->mail_stream->nmsgs)
2003 ? mail_elt(state->mail_stream, rawno) : NULL;
2005 for(fp = ftbl; mc && fp->name; fp++){
2006 flags = -1;
2007 switch(fp->flag){
2008 case F_SEEN:
2009 if((!MCMD_ISAGG(aopt) && fp->set != !mc->seen)
2010 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2011 flagit = "\\SEEN";
2012 if(fp->set){
2013 flags = 0L;
2014 unflagged = F_SEEN;
2016 else{
2017 flags = ST_SET;
2018 unflagged = F_UNSEEN;
2022 break;
2024 case F_ANS:
2025 if((!MCMD_ISAGG(aopt) && fp->set != mc->answered)
2026 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2027 flagit = "\\ANSWERED";
2028 if(fp->set){
2029 flags = ST_SET;
2030 unflagged = F_UNANS;
2032 else{
2033 flags = 0L;
2034 unflagged = F_ANS;
2038 break;
2040 case F_DEL:
2041 if((!MCMD_ISAGG(aopt) && fp->set != mc->deleted)
2042 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2043 flagit = "\\DELETED";
2044 if(fp->set){
2045 flags = ST_SET;
2046 unflagged = F_UNDEL;
2048 else{
2049 flags = 0L;
2050 unflagged = F_DEL;
2054 break;
2056 case F_FLAG:
2057 if((!MCMD_ISAGG(aopt) && fp->set != mc->flagged)
2058 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2059 flagit = "\\FLAGGED";
2060 if(fp->set){
2061 flags = ST_SET;
2062 unflagged = F_UNFLAG;
2064 else{
2065 flags = 0L;
2066 unflagged = F_FLAG;
2070 break;
2072 case F_FWD :
2073 if(!MCMD_ISAGG(aopt)){
2074 /* see if forwarded is defined for this message */
2075 is_set = CMD_FLAG_CLEAR;
2076 if(user_flag_is_set(state->mail_stream,
2077 mn_m2raw(msgmap, mn_get_cur(msgmap)),
2078 FORWARDED_FLAG))
2079 is_set = CMD_FLAG_SET;
2082 if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2083 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2084 flagit = FORWARDED_FLAG;
2085 if(fp->set){
2086 flags = ST_SET;
2087 unflagged = F_UNFWD;
2089 else{
2090 flags = 0L;
2091 unflagged = F_FWD;
2095 break;
2097 case F_KEYWORD:
2098 if(!MCMD_ISAGG(aopt)){
2099 /* see if this keyword is defined for this message */
2100 is_set = CMD_FLAG_CLEAR;
2101 if(user_flag_is_set(state->mail_stream,
2102 mn_m2raw(msgmap, mn_get_cur(msgmap)),
2103 fp->keyword))
2104 is_set = CMD_FLAG_SET;
2107 if((!MCMD_ISAGG(aopt) && fp->set != is_set)
2108 || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){
2109 flagit = fp->keyword;
2110 keyword_array[0] = fp->keyword;
2111 keyword_array[1] = NULL;
2112 if(fp->set){
2113 flags = ST_SET;
2114 unflagged = F_UNKEYWORD;
2116 else{
2117 flags = 0L;
2118 unflagged = F_KEYWORD;
2122 break;
2124 default:
2125 break;
2128 flagged = 0L;
2129 if(flags >= 0L
2130 && (seq = currentf_sequence(state->mail_stream, msgmap,
2131 unflagged, &flagged, unflagged & F_DEL,
2132 (fp->flag == F_KEYWORD
2133 && unflagged == F_KEYWORD)
2134 ? keyword_array : NULL,
2135 (fp->flag == F_KEYWORD
2136 && unflagged == F_UNKEYWORD)
2137 ? keyword_array : NULL))){
2139 * For user keywords, we may have to create the flag in
2140 * the folder if it doesn't already exist and we are setting
2141 * it (as opposed to clearing it). Mail_flag will
2142 * do that for us, but it's failure isn't very friendly
2143 * error-wise. So we try to make it a little smoother.
2145 if(!(fp->flag == F_KEYWORD || fp->flag == F_FWD) || !fp->set
2146 || ((i=user_flag_index(state->mail_stream, flagit)) >= 0
2147 && i < NUSERFLAGS))
2148 mail_flag(state->mail_stream, seq, flagit, flags);
2149 else{
2150 /* trouble, see if we can add the user flag */
2151 if(state->mail_stream->kwd_create)
2152 mail_flag(state->mail_stream, seq, flagit, flags);
2153 else{
2154 trouble++;
2156 if(some_user_flags_defined(state->mail_stream))
2157 q_status_message(SM_ORDER, 3, 4,
2158 _("No more keywords allowed in this folder!"));
2159 else if(fp->flag == F_FWD)
2160 q_status_message(SM_ORDER, 3, 4,
2161 _("Cannot add keywords for this folder so cannot set Forwarded flag"));
2162 else
2163 q_status_message(SM_ORDER, 3, 4,
2164 _("Cannot add keywords for this folder"));
2168 fs_give((void **) &seq);
2169 if(flagged && !trouble){
2170 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%slagged%s%s%s%s%s message%s%s \"%s\"",
2171 (fp->set) ? "F" : "Unf",
2172 MCMD_ISAGG(aopt) ? " " : "",
2173 MCMD_ISAGG(aopt) ? long2string(flagged) : "",
2174 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2175 ? " (of " : "",
2176 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2177 ? comatose(mn_total_cur(msgmap)) : "",
2178 (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap))
2179 ? ")" : "",
2180 MCMD_ISAGG(aopt) ? plural(flagged) : " ",
2181 MCMD_ISAGG(aopt) ? "" : long2string(mn_get_cur(msgmap)),
2182 fp->name);
2183 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2184 q_status_message(SM_ORDER, 0, 2, answer = tmp_20k_buf);
2185 rv++;
2190 free_flag_table(&ftbl);
2192 if(directly_to_maint_screen)
2193 goto go_again;
2195 if(MCMD_ISAGG(aopt))
2196 restore_selected(msgmap);
2198 if(!answer)
2199 q_status_message(SM_ORDER, 0, 2, _("No flags changed."));
2201 return rv;
2205 /*----------------------------------------------------------------------
2206 Offer concise status line flag prompt
2208 Args: state -- Various satate info
2209 flags -- flags to offer setting
2211 Result: TRUE if flag to set specified in flags struct or FALSE otw
2213 ----*/
2215 cmd_flag_prompt(struct pine *state, struct flag_screen *flags, int allow_keyword_shortcuts)
2217 int r, setflag = 1, first_char;
2218 struct flag_table *fp;
2219 ESCKEY_S *ek;
2220 char *ftext, *ftext_not;
2221 static char *flag_text =
2222 N_("Flag New, Deleted, Answered, Forwarded or Important ? ");
2223 static char *flag_text_ak =
2224 N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? ");
2225 static char *flag_text_not =
2226 N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? ");
2227 static char *flag_text_ak_not =
2228 N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? ");
2230 if(allow_keyword_shortcuts){
2231 int cnt = 0;
2232 ESCKEY_S *dp, *sp, *tp;
2234 for(sp=flag_text_opt; sp->ch != -1; sp++)
2235 cnt++;
2237 for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++)
2238 if(fp->flag == F_KEYWORD)
2239 cnt++;
2241 /* set up an ESCKEY_S list which includes invisible keys for keywords */
2242 ek = (ESCKEY_S *) fs_get((cnt + 1) * sizeof(*ek));
2243 memset(ek, 0, (cnt+1) * sizeof(*ek));
2244 for(dp=ek, sp=flag_text_opt; sp->ch != -1; sp++, dp++)
2245 *dp = *sp;
2247 for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2248 if(fp->flag == F_KEYWORD){
2249 first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2250 if(isascii(first_char) && isupper(first_char))
2251 first_char = tolower((unsigned char) first_char);
2254 * Check to see if an earlier keyword in the list, or one of
2255 * the builtin system letters already uses this character.
2256 * If so, the first one wins.
2258 for(tp=ek; tp->ch != 0; tp++)
2259 if(tp->ch == first_char)
2260 break;
2262 if(tp->ch != 0)
2263 continue; /* skip it, already used that char */
2265 dp->ch = first_char;
2266 dp->rval = first_char;
2267 dp->name = "";
2268 dp->label = "";
2269 dp++;
2273 dp->ch = -1;
2274 ftext = _(flag_text_ak);
2275 ftext_not = _(flag_text_ak_not);
2277 else{
2278 ek = flag_text_opt;
2279 ftext = _(flag_text);
2280 ftext_not = _(flag_text_not);
2283 while(1){
2284 r = radio_buttons(setflag ? ftext : ftext_not,
2285 -FOOTER_ROWS(state), ek, '*', SEQ_EXCEPTION-1,
2286 NO_HELP, RB_NORM | RB_SEQ_SENSITIVE);
2288 * It is SEQ_EXCEPTION-1 just so that it is some value that isn't
2289 * being used otherwise. The keywords use up all the possible
2290 * letters, so a negative number is good, but it has to be different
2291 * from other negative return values.
2293 if(r == SEQ_EXCEPTION-1) /* ol'cancelrooney */
2294 return(TRUE);
2295 else if(r == 10) /* return and goto flag screen */
2296 return(FALSE);
2297 else if(r == '!') /* flip intention */
2298 setflag = !setflag;
2299 else
2300 break;
2303 for(fp = (flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){
2304 if(r == 'n' || r == '*' || r == 'd' || r == 'a' || r == 'f'){
2305 if((r == 'n' && fp->flag == F_SEEN)
2306 || (r == '*' && fp->flag == F_FLAG)
2307 || (r == 'd' && fp->flag == F_DEL)
2308 || (r == 'f' && fp->flag == F_FWD)
2309 || (r == 'a' && fp->flag == F_ANS)){
2310 fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2311 break;
2314 else if(allow_keyword_shortcuts && fp->flag == F_KEYWORD){
2315 first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2;
2316 if(isascii(first_char) && isupper(first_char))
2317 first_char = tolower((unsigned char) first_char);
2319 if(r == first_char){
2320 fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR;
2321 break;
2326 if(ek != flag_text_opt)
2327 fs_give((void **) &ek);
2329 return(TRUE);
2334 * (*ft) is an array of flag_table entries.
2336 void
2337 free_flag_table(struct flag_table **ft)
2339 struct flag_table *fp;
2341 if(ft && *ft){
2342 for(fp = (*ft); fp->name; fp++){
2343 if(fp->name)
2344 fs_give((void **) &fp->name);
2346 if(fp->keyword)
2347 fs_give((void **) &fp->keyword);
2349 if(fp->comment)
2350 fs_give((void **) &fp->comment);
2353 fs_give((void **) ft);
2358 /*----------------------------------------------------------------------
2359 Execute REPLY message command
2361 Args: state -- Various satate info
2362 msgmap -- map of c-client to local message numbers
2364 Result: reply sent or not
2366 ----*/
2368 cmd_reply(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2370 int rv = 0;
2372 if(any_messages(msgmap, NULL, "to Reply to")){
2373 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2374 return rv;
2376 rv = reply(state, role);
2378 if(MCMD_ISAGG(aopt))
2379 restore_selected(msgmap);
2381 state->mangled_screen = 1;
2384 return rv;
2388 /*----------------------------------------------------------------------
2389 Execute FORWARD message command
2391 Args: state -- Various satate info
2392 msgmap -- map of c-client to local message numbers
2394 Result: selected message[s] forwarded or not
2396 ----*/
2398 cmd_forward(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2400 int rv = 0;
2402 if(any_messages(msgmap, NULL, "to Forward")){
2403 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
2404 return rv;
2406 rv = forward(state, role);
2408 if(MCMD_ISAGG(aopt))
2409 restore_selected(msgmap);
2411 state->mangled_screen = 1;
2414 return rv;
2418 /*----------------------------------------------------------------------
2419 Execute BOUNCE message command
2421 Args: state -- Various satate info
2422 msgmap -- map of c-client to local message numbers
2423 aopt -- aggregate options
2425 Result: selected message[s] bounced or not
2427 ----*/
2429 cmd_bounce(struct pine *state, MSGNO_S *msgmap, int aopt, ACTION_S *role)
2431 int rv = 0;
2433 if(any_messages(msgmap, NULL, "to Bounce")){
2434 long i;
2435 if(MCMD_ISAGG(aopt)){
2436 if(!pseudo_selected(state->mail_stream, msgmap))
2437 return rv;
2439 else if((i = any_lflagged(msgmap, MN_SLCT)) > 0
2440 && get_lflag(state->mail_stream, msgmap,
2441 mn_m2raw(msgmap, mn_get_cur(msgmap)), MN_SLCT) == 0)
2442 q_status_message(SM_ORDER | SM_DING, 3, 4,
2443 _("WARNING: non-selected message is being bounced!"));
2444 else if (i > 1L
2445 && get_lflag(state->mail_stream, msgmap,
2446 mn_m2raw(msgmap, mn_get_cur(msgmap)), MN_SLCT))
2447 q_status_message(SM_ORDER | SM_DING, 3, 4,
2448 _("WARNING: not bouncing all selected messages!"));
2450 rv = bounce(state, role);
2452 if(MCMD_ISAGG(aopt))
2453 restore_selected(msgmap);
2455 state->mangled_footer = 1;
2458 return rv;
2462 /*----------------------------------------------------------------------
2463 Execute save message command: prompt for folder and call function to save
2465 Args: screen_line -- Line on the screen to prompt on
2466 message -- The MESSAGECACHE entry of message to save
2468 Result: The folder lister can be called to make selection; mangled screen set
2470 This does the prompting for the folder name to save to, possibly calling
2471 up the folder display for selection of folder by user.
2472 ----*/
2474 cmd_save(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
2476 char newfolder[MAILTMPLEN], nmsgs[32], *nick;
2477 int we_cancel = 0, rv = 0, save_flags;
2478 long i, raw;
2479 CONTEXT_S *cntxt = NULL;
2480 ENVELOPE *e = NULL;
2481 SaveDel del = DontAsk;
2482 SavePreserveOrder pre = DontAskPreserve;
2484 dprint((4, "\n - saving message -\n"));
2486 if(MCMD_ISAGG(aopt) && !pseudo_selected(stream, msgmap))
2487 return rv;
2489 state->ugly_consider_advancing_bit = 0;
2490 if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM, state)
2491 && msgno_any_deletedparts(stream, msgmap)
2492 && want_to(_("Saved copy will NOT include entire message! Continue"),
2493 'y', 'n', NO_HELP, WT_FLUSH_IN | WT_SEQ_SENSITIVE) != 'y'){
2494 restore_selected(msgmap);
2495 cmd_cancelled("Save message");
2496 return rv;
2499 raw = mn_m2raw(msgmap, mn_get_cur(msgmap));
2501 if(mn_total_cur(msgmap) <= 1L){
2502 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld ", mn_get_cur(msgmap));
2503 nmsgs[sizeof(nmsgs)-1] = '\0';
2504 e = pine_mail_fetchstructure(stream, raw, NULL);
2505 if(!e) {
2506 q_status_message(SM_ORDER | SM_DING, 3, 4,
2507 _("Can't save message. Error accessing folder"));
2508 restore_selected(msgmap);
2509 return rv;
2512 else{
2513 snprintf(nmsgs, sizeof(nmsgs), "%s msgs ", comatose(mn_total_cur(msgmap)));
2514 nmsgs[sizeof(nmsgs)-1] = '\0';
2516 /* e is just used to get a default save folder from the first msg */
2517 e = pine_mail_fetchstructure(stream,
2518 mn_m2raw(msgmap, mn_first_cur(msgmap)),
2519 NULL);
2522 del = (!READONLY_FOLDER(stream) && F_OFF(F_SAVE_WONT_DELETE, ps_global))
2523 ? Del : NoDel;
2524 if(mn_total_cur(msgmap) > 1L)
2525 pre = F_OFF(F_AGG_SEQ_COPY, ps_global) ? Preserve : NoPreserve;
2526 else
2527 pre = DontAskPreserve;
2529 if(save_prompt(state, &cntxt, newfolder, sizeof(newfolder), nmsgs, e,
2530 raw, NULL, &del, &pre)){
2532 if(ps_global && ps_global->ttyo){
2533 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
2534 ps_global->mangled_footer = 1;
2537 save_flags = SV_FIX_DELS;
2538 if(pre == RetPreserve)
2539 save_flags |= SV_PRESERVE;
2540 if(del == RetDel)
2541 save_flags |= SV_DELETE;
2542 if(ps_global->context_list == cntxt && !strucmp(newfolder, ps_global->inbox_name))
2543 save_flags |= SV_INBOXWOCNTXT;
2545 we_cancel = busy_cue(_("Saving"), NULL, 1);
2546 i = save(state, stream, cntxt, newfolder, msgmap, save_flags);
2547 if(we_cancel)
2548 cancel_busy_cue(0);
2550 if(i == mn_total_cur(msgmap)){
2551 rv++;
2552 if(mn_total_cur(msgmap) <= 1L){
2553 int need, avail = ps_global->ttyo->screen_cols - 2;
2554 int lennick, lenfldr;
2556 if(cntxt
2557 && ps_global->context_list->next
2558 && context_isambig(newfolder)){
2559 lennick = MIN(strlen(cntxt->nickname), 500);
2560 lenfldr = MIN(strlen(newfolder), 500);
2561 need = 27 + strlen(long2string(mn_get_cur(msgmap))) +
2562 lenfldr + lennick;
2563 if(need > avail){
2564 if(lennick > 10){
2565 need -= MIN(lennick-10, need-avail);
2566 lennick -= MIN(lennick-10, need-avail);
2569 if(need > avail && lenfldr > 10)
2570 lenfldr -= MIN(lenfldr-10, need-avail);
2573 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2574 "Message %s copied to \"%s\" in <%s>",
2575 long2string(mn_get_cur(msgmap)),
2576 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2577 lenfldr, MidDots),
2578 short_str(cntxt->nickname,
2579 (char *)(tmp_20k_buf+2000), 1000,
2580 lennick, EndDots));
2581 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2583 else if((nick=folder_is_target_of_nick(newfolder, cntxt)) != NULL){
2584 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
2585 "Message %s copied to \"%s\"",
2586 long2string(mn_get_cur(msgmap)),
2587 nick);
2588 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2590 else{
2591 char *f = " folder";
2593 lenfldr = MIN(strlen(newfolder), 500);
2594 need = 28 + strlen(long2string(mn_get_cur(msgmap))) +
2595 lenfldr;
2596 if(need > avail){
2597 need -= strlen(f);
2598 f = "";
2599 if(need > avail && lenfldr > 10)
2600 lenfldr -= MIN(lenfldr-10, need-avail);
2603 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
2604 "Message %s copied to%s \"%s\"",
2605 long2string(mn_get_cur(msgmap)), f,
2606 short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000,
2607 lenfldr, MidDots));
2608 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2611 else{
2612 snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s messages saved",
2613 comatose(mn_total_cur(msgmap)));
2614 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2617 if(del == RetDel){
2618 strncat(tmp_20k_buf, " and deleted", SIZEOF_20KBUF-strlen(tmp_20k_buf)-1);
2619 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
2622 q_status_message(SM_ORDER, 0, 3, tmp_20k_buf);
2624 if(!MCMD_ISAGG(aopt) && F_ON(F_SAVE_ADVANCES, state)){
2625 if(sp_new_mail_count(stream))
2626 process_filter_patterns(stream, msgmap,
2627 sp_new_mail_count(stream));
2629 mn_inc_cur(stream, msgmap,
2630 (in_index == View && THREADING()
2631 && sp_viewing_a_thread(stream))
2632 ? MH_THISTHD
2633 : (in_index == View)
2634 ? MH_ANYTHD : MH_NONE);
2637 state->ugly_consider_advancing_bit = 1;
2641 if(MCMD_ISAGG(aopt)) /* straighten out fakes */
2642 restore_selected(msgmap);
2644 if(del == RetDel)
2645 update_titlebar_status(); /* make sure they see change */
2647 return rv;
2651 void
2652 role_compose(struct pine *state)
2654 int action;
2656 if(F_ON(F_ALT_ROLE_MENU, state) && mn_get_total(state->msgmap) > 0L){
2657 PAT_STATE pstate;
2659 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
2660 action = radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
2661 -FOOTER_ROWS(state), choose_action,
2662 'c', 'x', h_role_compose, RB_NORM);
2664 else{
2665 q_status_message(SM_ORDER, 0, 3,
2666 _("No roles available. Use Setup/Rules to add roles."));
2667 return;
2670 else
2671 action = 'c';
2673 if(action == 'c' || action == 'r' || action == 'f' || action == 'b'){
2674 ACTION_S *role = NULL;
2675 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
2677 redraw = state->redrawer;
2678 state->redrawer = NULL;
2679 prev_screen = state->prev_screen;
2680 role = NULL;
2681 state->next_screen = SCREEN_FUN_NULL;
2683 /* Setup role */
2684 if(role_select_screen(state, &role,
2685 action == 'f' ? MC_FORWARD :
2686 action == 'r' ? MC_REPLY :
2687 action == 'b' ? MC_BOUNCE :
2688 action == 'c' ? MC_COMPOSE : 0) < 0){
2689 cmd_cancelled(action == 'f' ? _("Forward") :
2690 action == 'r' ? _("Reply") :
2691 action == 'c' ? _("Composition") : _("Bounce"));
2692 state->next_screen = prev_screen;
2693 state->redrawer = redraw;
2694 state->mangled_screen = 1;
2696 else{
2698 * If default role was selected (NULL) we need to make
2699 * up a role which won't do anything, but will cause
2700 * compose_mail to think there's already a role so that
2701 * it won't try to confirm the default.
2703 if(role)
2704 role = combine_inherited_role(role);
2705 else{
2706 role = (ACTION_S *) fs_get(sizeof(*role));
2707 memset((void *) role, 0, sizeof(*role));
2708 role->nick = cpystr("Default Role");
2711 state->redrawer = NULL;
2712 switch(action){
2713 case 'c':
2714 compose_mail(NULL, NULL, role, NULL, NULL);
2715 break;
2717 case 'r':
2718 (void) reply(state, role);
2719 break;
2721 case 'f':
2722 (void) forward(state, role);
2723 break;
2725 case 'b':
2726 (void) bounce(state, role);
2727 break;
2730 if(role)
2731 free_action(&role);
2733 state->next_screen = prev_screen;
2734 state->redrawer = redraw;
2735 state->mangled_screen = 1;
2741 /*----------------------------------------------------------------------
2742 Do the dirty work of prompting the user for a folder name
2744 Args:
2745 nfldr should be a buffer at least MAILTMPLEN long
2746 dela -- a pointer to a SaveDel. If it is
2747 DontAsk on input, don't offer Delete prompt
2748 Del on input, offer Delete command with default of Delete
2749 NoDel NoDelete
2750 RetDel and RetNoDel are return values
2753 Result:
2755 ----*/
2757 save_prompt(struct pine *state, CONTEXT_S **cntxt, char *nfldr, size_t len_nfldr,
2758 char *nmsgs, ENVELOPE *env, long int rawmsgno, char *section,
2759 SaveDel *dela, SavePreserveOrder *prea)
2761 int rc, ku = -1, n, flags, last_rc = 0, saveable_count = 0, done = 0;
2762 int delindex, preindex, r;
2763 char prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN];
2764 char *buf = tmp_20k_buf;
2765 char shortbuf[200];
2766 char *folder;
2767 HelpType help;
2768 SaveDel del = DontAsk;
2769 SavePreserveOrder pre = DontAskPreserve;
2770 char *deltext = NULL;
2771 static HISTORY_S *history = NULL;
2772 CONTEXT_S *tc;
2773 ESCKEY_S ekey[10];
2775 if(!cntxt)
2776 alpine_panic("no context ptr in save_prompt");
2778 init_hist(&history, HISTSIZE);
2780 if(!(folder = save_get_default(state, env, rawmsgno, section, cntxt)))
2781 return(0); /* message expunged! */
2783 /* how many context's can be saved to... */
2784 for(tc = state->context_list; tc; tc = tc->next)
2785 if(!NEWS_TEST(tc))
2786 saveable_count++;
2788 /* set up extra command option keys */
2789 rc = 0;
2790 ekey[rc].ch = ctrl('T');
2791 ekey[rc].rval = 2;
2792 ekey[rc].name = "^T";
2793 /* TRANSLATORS: command means go to Folders list */
2794 ekey[rc++].label = N_("To Fldrs");
2796 if(saveable_count > 1){
2797 ekey[rc].ch = ctrl('P');
2798 ekey[rc].rval = 10;
2799 ekey[rc].name = "^P";
2800 ekey[rc++].label = N_("Prev Collection");
2802 ekey[rc].ch = ctrl('N');
2803 ekey[rc].rval = 11;
2804 ekey[rc].name = "^N";
2805 ekey[rc++].label = N_("Next Collection");
2808 if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){
2809 ekey[rc].ch = TAB;
2810 ekey[rc].rval = 12;
2811 ekey[rc].name = "TAB";
2812 /* TRANSLATORS: command asks alpine to complete the name when tab is typed */
2813 ekey[rc++].label = N_("Complete");
2816 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
2817 ekey[rc].ch = ctrl('X');
2818 ekey[rc].rval = 14;
2819 ekey[rc].name = "^X";
2820 /* TRANSLATORS: list all the matches */
2821 ekey[rc++].label = N_("ListMatches");
2824 if(dela && (*dela == NoDel || *dela == Del)){
2825 ekey[rc].ch = ctrl('R');
2826 ekey[rc].rval = 15;
2827 ekey[rc].name = "^R";
2828 delindex = rc++;
2829 del = *dela;
2832 if(prea && (*prea == NoPreserve || *prea == Preserve)){
2833 ekey[rc].ch = ctrl('W');
2834 ekey[rc].rval = 16;
2835 ekey[rc].name = "^W";
2836 preindex = rc++;
2837 pre = *prea;
2840 if(saveable_count > 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2841 ekey[rc].ch = KEY_UP;
2842 ekey[rc].rval = 10;
2843 ekey[rc].name = "";
2844 ekey[rc++].label = "";
2846 ekey[rc].ch = KEY_DOWN;
2847 ekey[rc].rval = 11;
2848 ekey[rc].name = "";
2849 ekey[rc++].label = "";
2851 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
2852 ekey[rc].ch = KEY_UP;
2853 ekey[rc].rval = 30;
2854 ekey[rc].name = "";
2855 ku = rc;
2856 ekey[rc++].label = "";
2858 ekey[rc].ch = KEY_DOWN;
2859 ekey[rc].rval = 31;
2860 ekey[rc].name = "";
2861 ekey[rc++].label = "";
2864 ekey[rc].ch = -1;
2866 *nfldr = '\0';
2867 help = NO_HELP;
2868 while(!done){
2869 /* only show collection number if more than one available */
2870 if(ps_global->context_list->next)
2871 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder in <%s> [%s] : ",
2872 deltext ? deltext : "",
2873 nmsgs,
2874 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2875 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2876 else
2877 snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder [%s] : ",
2878 deltext ? deltext : "",
2879 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2881 prompt[sizeof(prompt)-1] = '\0';
2884 * If the prompt won't fit, try removing deltext.
2886 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && deltext){
2887 if(ps_global->context_list->next)
2888 snprintf(prompt, sizeof(prompt), "SAVE %sto folder in <%s> [%s] : ",
2889 nmsgs,
2890 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2891 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2892 else
2893 snprintf(prompt, sizeof(prompt), "SAVE %sto folder [%s] : ",
2894 nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40));
2896 prompt[sizeof(prompt)-1] = '\0';
2900 * If the prompt still won't fit, remove the extra info contained
2901 * in nmsgs.
2903 if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && *nmsgs){
2904 if(ps_global->context_list->next)
2905 snprintf(prompt, sizeof(prompt), "SAVE to folder in <%s> [%s] : ",
2906 short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots),
2907 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2908 else
2909 snprintf(prompt, sizeof(prompt), "SAVE to folder [%s] : ",
2910 strsquish(buf, SIZEOF_20KBUF, folder, 25));
2912 prompt[sizeof(prompt)-1] = '\0';
2915 if(del != DontAsk)
2916 ekey[delindex].label = (del == NoDel) ? "Delete" : "No Delete";
2918 if(pre != DontAskPreserve)
2919 ekey[preindex].label = (pre == NoPreserve) ? "Preserve Order" : "Any Order";
2921 if(ku >= 0){
2922 if(items_in_hist(history) > 1){
2923 ekey[ku].name = HISTORY_UP_KEYNAME;
2924 ekey[ku].label = HISTORY_KEYLABEL;
2925 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
2926 ekey[ku+1].label = HISTORY_KEYLABEL;
2928 else{
2929 ekey[ku].name = "";
2930 ekey[ku].label = "";
2931 ekey[ku+1].name = "";
2932 ekey[ku+1].label = "";
2936 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
2937 rc = optionally_enter(nfldr, -FOOTER_ROWS(state), 0, len_nfldr,
2938 prompt, ekey, help, &flags);
2940 switch(rc){
2941 case -1 :
2942 q_status_message(SM_ORDER | SM_DING, 3, 3,
2943 _("Error reading folder name"));
2944 done--;
2945 break;
2947 case 0 :
2948 removing_trailing_white_space(nfldr);
2949 removing_leading_white_space(nfldr);
2951 if(*nfldr || *folder){
2952 char *p, *name, *fullname = NULL;
2953 int exists, breakout = FALSE;
2955 if(!*nfldr){
2956 strncpy(nfldr, folder, len_nfldr-1);
2957 nfldr[len_nfldr-1] = '\0';
2960 save_hist(history, nfldr, 0, (void *) *cntxt);
2962 if(!(name = folder_is_nick(nfldr, FOLDERS(*cntxt), 0)))
2963 name = nfldr;
2965 if(update_folder_spec(expanded, sizeof(expanded), name)){
2966 strncpy(name = nfldr, expanded, len_nfldr-1);
2967 nfldr[len_nfldr-1] = '\0';
2970 exists = folder_name_exists(*cntxt, name, &fullname);
2972 if(exists == FEX_ERROR){
2973 q_status_message1(SM_ORDER, 0, 3,
2974 _("Problem accessing folder \"%s\""),
2975 nfldr);
2976 done--;
2978 else{
2979 if(fullname){
2980 strncpy(name = nfldr, fullname, len_nfldr-1);
2981 nfldr[len_nfldr-1] = '\0';
2982 fs_give((void **) &fullname);
2983 breakout = TRUE;
2986 if(exists & FEX_ISFILE){
2987 done++;
2989 else if((exists & FEX_ISDIR)){
2990 char tmp[MAILTMPLEN];
2992 tc = *cntxt;
2993 if(breakout){
2994 CONTEXT_S *fake_context;
2995 size_t l;
2997 strncpy(tmp, name, sizeof(tmp));
2998 tmp[sizeof(tmp)-2-1] = '\0';
2999 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
3000 if(l < sizeof(tmp)){
3001 tmp[l] = tc->dir->delim;
3002 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
3005 else
3006 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
3008 tmp[sizeof(tmp)-1] = '\0';
3010 fake_context = new_context(tmp, 0);
3011 nfldr[0] = '\0';
3012 done = display_folder_list(&fake_context, nfldr,
3013 1, folders_for_save);
3014 free_context(&fake_context);
3016 else if(tc->dir->delim
3017 && (p = strrindex(name, tc->dir->delim))
3018 && *(p+1) == '\0')
3019 done = display_folder_list(cntxt, nfldr,
3020 1, folders_for_save);
3021 else{
3022 q_status_message1(SM_ORDER, 3, 3,
3023 _("\"%s\" is a directory"), name);
3024 if(tc->dir->delim
3025 && !((p=strrindex(name, tc->dir->delim)) && *(p+1) == '\0')){
3026 strncpy(tmp, name, sizeof(tmp));
3027 tmp[sizeof(tmp)-1] = '\0';
3028 snprintf(nfldr, len_nfldr, "%s%c", tmp, tc->dir->delim);
3032 else{ /* Doesn't exist, create! */
3033 if((fullname = folder_as_breakout(*cntxt, name)) != NULL){
3034 strncpy(name = nfldr, fullname, len_nfldr-1);
3035 nfldr[len_nfldr-1] = '\0';
3036 fs_give((void **) &fullname);
3039 switch(create_for_save(*cntxt, name)){
3040 case 1 : /* success */
3041 done++;
3042 break;
3043 case 0 : /* error */
3044 case -1 : /* declined */
3045 done--;
3046 break;
3051 break;
3053 /* else fall thru like they cancelled */
3055 case 1 :
3056 cmd_cancelled("Save message");
3057 done--;
3058 break;
3060 case 2 :
3061 r = display_folder_list(cntxt, nfldr, 0, folders_for_save);
3063 if(r)
3064 done++;
3066 break;
3068 case 3 :
3069 helper(h_save, _("HELP FOR SAVE"), HLPD_SIMPLE);
3070 ps_global->mangled_screen = 1;
3071 break;
3073 case 4 : /* redraw */
3074 break;
3076 case 10 : /* previous collection */
3077 for(tc = (*cntxt)->prev; tc; tc = tc->prev)
3078 if(!NEWS_TEST(tc))
3079 break;
3081 if(!tc){
3082 CONTEXT_S *tc2;
3084 for(tc2 = (tc = (*cntxt))->next; tc2; tc2 = tc2->next)
3085 if(!NEWS_TEST(tc2))
3086 tc = tc2;
3089 *cntxt = tc;
3090 break;
3092 case 11 : /* next collection */
3093 tc = (*cntxt);
3096 if(((*cntxt) = (*cntxt)->next) == NULL)
3097 (*cntxt) = ps_global->context_list;
3098 while(NEWS_TEST(*cntxt) && (*cntxt) != tc);
3099 break;
3101 case 12 : /* file name completion */
3102 if(!folder_complete(*cntxt, nfldr, len_nfldr, &n)){
3103 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
3104 r = display_folder_list(cntxt, nfldr, 1, folders_for_save);
3105 if(r)
3106 done++; /* bingo! */
3107 else
3108 rc = 0; /* burn last_rc */
3110 else
3111 Writechar(BELL, 0);
3114 break;
3116 case 14 : /* file name completion */
3117 r = display_folder_list(cntxt, nfldr, 2, folders_for_save);
3118 if(r)
3119 done++; /* bingo! */
3120 else
3121 rc = 0; /* burn last_rc */
3123 break;
3125 case 15 : /* Delete / No Delete */
3126 del = (del == NoDel) ? Del : NoDel;
3127 deltext = (del == NoDel) ? " (no delete)" : " (and delete)";
3128 break;
3130 case 16 : /* Preserve Order or not */
3131 pre = (pre == NoPreserve) ? Preserve : NoPreserve;
3132 break;
3134 case 30 :
3135 if((p = get_prev_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3136 strncpy(nfldr, p, len_nfldr);
3137 nfldr[len_nfldr-1] = '\0';
3138 if(history->hist[history->curindex])
3139 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3141 else
3142 Writechar(BELL, 0);
3144 break;
3146 case 31 :
3147 if((p = get_next_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){
3148 strncpy(nfldr, p, len_nfldr);
3149 nfldr[len_nfldr-1] = '\0';
3150 if(history->hist[history->curindex])
3151 *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt;
3153 else
3154 Writechar(BELL, 0);
3156 break;
3158 default :
3159 alpine_panic("Unhandled case");
3160 break;
3163 last_rc = rc;
3166 ps_global->mangled_footer = 1;
3168 if(done < 0)
3169 return(0);
3171 if(*nfldr){
3172 strncpy(ps_global->last_save_folder, nfldr, sizeof(ps_global->last_save_folder)-1);
3173 ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0';
3174 if(*cntxt)
3175 ps_global->last_save_context = *cntxt;
3177 else{
3178 strncpy(nfldr, folder, len_nfldr-1);
3179 nfldr[len_nfldr-1] = '\0';
3182 /* nickname? Copy real name to nfldr */
3183 if(*cntxt
3184 && context_isambig(nfldr)
3185 && (p = folder_is_nick(nfldr, FOLDERS(*cntxt), 0))){
3186 strncpy(nfldr, p, len_nfldr-1);
3187 nfldr[len_nfldr-1] = '\0';
3190 if(dela && (*dela == NoDel || *dela == Del))
3191 *dela = (del == NoDel) ? RetNoDel : RetDel;
3193 if(prea && (*prea == NoPreserve || *prea == Preserve))
3194 *prea = (pre == NoPreserve) ? RetNoPreserve : RetPreserve;
3196 return(1);
3200 /*----------------------------------------------------------------------
3201 Prompt user before implicitly creating a folder for saving
3203 Args: context - context to create folder in
3204 folder - folder name to create
3206 Result: 1 on proceed, -1 on decline, 0 on error
3208 ----*/
3210 create_for_save_prompt(CONTEXT_S *context, char *folder, int sequence_sensitive)
3212 if(context && ps_global->context_list->next && context_isambig(folder)){
3213 if(context->use & CNTXT_INCMNG){
3214 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3215 _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
3216 folder, (strlen(folder) > 15) ? "..." : "");
3217 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
3218 return(0); /* error */
3221 snprintf(tmp_20k_buf,SIZEOF_20KBUF,
3222 _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
3223 folder, (strlen(folder) > 15) ? "..." : "",
3224 context->nickname,
3225 (strlen(context->nickname) > 15) ? "..." : "");
3227 else
3228 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
3229 _("Folder \"%.40s%s\" doesn't exist. Create"),
3230 folder, strlen(folder) > 40 ? "..." : "");
3232 if(want_to(tmp_20k_buf, 'y', 'n',
3233 NO_HELP, (sequence_sensitive) ? WT_SEQ_SENSITIVE : WT_NORM) != 'y'){
3234 cmd_cancelled("Save message");
3235 return(-1);
3238 return(1);
3243 /*----------------------------------------------------------------------
3244 Expunge messages from current folder
3246 Args: state -- pointer to struct holding a bunch of pine state
3247 msgmap -- table mapping msg nums to c-client sequence nums
3248 qline -- screen line to ask questions on
3249 agg -- boolean indicating we're to operate on aggregate set
3251 Result:
3252 ----*/
3254 cmd_expunge(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int agg)
3256 long del_count, prefilter_del_count;
3257 int we_cancel = 0, rv = 0;
3258 char prompt[MAX_SCREEN_COLS+1];
3259 char *sequence;
3260 COLOR_PAIR *lastc = NULL;
3262 dprint((2, "\n - expunge -\n"));
3264 del_count = 0;
3266 sequence = MCMD_ISAGG(agg) ? selected_sequence(stream, msgmap, NULL, 0) : NULL;
3268 if(MCMD_ISAGG(agg)){
3269 long i;
3270 MESSAGECACHE *mc;
3271 for(i = 1L; i <= stream->nmsgs; i++){
3272 if((mc = mail_elt(stream, i)) != NULL
3273 && mc->sequence && mc->deleted)
3274 del_count++;
3276 if(del_count == 0){
3277 q_status_message(SM_ORDER, 0, 4,
3278 _("No selected messages are deleted"));
3279 return 0;
3281 } else {
3282 if(!any_messages(msgmap, NULL, "to Expunge"))
3283 return rv;
3286 if(IS_NEWS(stream) && stream->rdonly){
3287 if(!MCMD_ISAGG(agg))
3288 del_count = count_flagged(stream, F_DEL);
3289 if(del_count > 0L){
3290 state->mangled_footer = 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3291 snprintf(prompt, sizeof(prompt), "Exclude %ld message%s from %.*s", del_count,
3292 plural(del_count), MAX_SCREEN_COLS+1-40,
3293 pretty_fn(state->cur_folder));
3294 prompt[sizeof(prompt)-1] = '\0';
3295 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3296 || (F_ON(F_AUTO_EXPUNGE, state)
3297 && (state->context_current
3298 && (state->context_current->use & CNTXT_INCMNG))
3299 && context_isambig(state->cur_folder))
3300 || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){
3302 if(F_ON(F_NEWS_CROSS_DELETE, state))
3303 cross_delete_crossposts(stream);
3305 msgno_exclude_deleted(stream, msgmap, sequence);
3306 clear_index_cache(stream, 0);
3309 * This is kind of surprising at first. For most sort
3310 * orders, if the whole set is sorted, then any subset
3311 * is also sorted. Not so for threaded sorts.
3313 if(SORT_IS_THREADED(msgmap))
3314 refresh_sort(stream, msgmap, SRT_NON);
3316 state->mangled_body = 1;
3317 state->mangled_header = 1;
3318 q_status_message2(SM_ORDER, 0, 4,
3319 "%s message%s excluded",
3320 long2string(del_count),
3321 plural(del_count));
3323 else
3324 any_messages(NULL, NULL, "Excluded");
3326 else
3327 any_messages(NULL, "deleted", "to Exclude");
3329 return del_count;
3331 else if(READONLY_FOLDER(stream)){
3332 q_status_message(SM_ORDER, 0, 4,
3333 _("Can't expunge. Folder is read-only"));
3334 return del_count;
3337 if(!MCMD_ISAGG(agg)){
3338 prefilter_del_count = count_flagged(stream, F_DEL|F_NOFILT);
3339 mail_expunge_prefilter(stream, MI_NONE);
3340 del_count = count_flagged(stream, F_DEL|F_NOFILT);
3343 if(del_count != 0){
3344 int ret;
3345 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3346 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3347 snprintf(prompt, sizeof(prompt), "Expunge %ld message%s from %.*s", del_count,
3348 plural(del_count), MAX_SCREEN_COLS+1-40,
3349 pretty_fn((char *) fname));
3350 if(fname) fs_give((void **)&fname);
3351 prompt[sizeof(prompt)-1] = '\0';
3352 state->mangled_footer = 1;
3354 if(F_ON(F_FULL_AUTO_EXPUNGE, state)
3355 || (F_ON(F_AUTO_EXPUNGE, state)
3356 && ((!strucmp(state->cur_folder,state->inbox_name))
3357 || (state->context_current->use & CNTXT_INCMNG))
3358 && context_isambig(state->cur_folder))
3359 || (ret=want_to(prompt, 'y', 0, NO_HELP, WT_NORM)) == 'y')
3360 ret = 'y';
3362 if(ret == 'x')
3363 cmd_cancelled("Expunge");
3365 if(ret != 'y')
3366 return 0;
3369 dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
3370 mn_get_total(msgmap), mn_get_cur(msgmap), del_count));
3372 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3373 state->VAR_TITLE_BACK_COLOR,
3374 PSC_REV|PSC_RET);
3376 PutLine0(0, 0, "**"); /* indicate delay */
3378 if(lastc){
3379 (void)pico_set_colorp(lastc, PSC_NONE);
3380 free_color_pair(&lastc);
3383 MoveCursor(state->ttyo->screen_rows -FOOTER_ROWS(state), 0);
3384 fflush(stdout);
3386 we_cancel = busy_cue(_("Expunging"), NULL, 1);
3388 if(cmd_expunge_work(stream, msgmap, sequence))
3389 state->mangled_body = 1;
3391 if(sequence)
3392 fs_give((void **)&sequence);
3394 if(we_cancel)
3395 cancel_busy_cue((sp_expunge_count(stream) > 0) ? 0 : -1);
3397 lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR,
3398 state->VAR_TITLE_BACK_COLOR,
3399 PSC_REV|PSC_RET);
3400 PutLine0(0, 0, " "); /* indicate delay's over */
3402 if(lastc){
3403 (void)pico_set_colorp(lastc, PSC_NONE);
3404 free_color_pair(&lastc);
3407 fflush(stdout);
3409 if(sp_expunge_count(stream) > 0){
3411 * This is kind of surprising at first. For most sort
3412 * orders, if the whole set is sorted, then any subset
3413 * is also sorted. Not so for threaded sorts.
3415 if(SORT_IS_THREADED(msgmap))
3416 refresh_sort(stream, msgmap, SRT_NON);
3418 else{
3419 if(del_count){
3420 unsigned char *fname = folder_name_decoded((unsigned char *)state->cur_folder);
3421 q_status_message1(SM_ORDER, 0, 3,
3422 _("No messages expunged from folder \"%s\""),
3423 pretty_fn((char *) fname));
3424 if(fname) fs_give((void **)&fname);
3426 else if(!prefilter_del_count)
3427 q_status_message(SM_ORDER, 0, 3,
3428 _("No messages marked deleted. No messages expunged."));
3430 return del_count;
3434 /*----------------------------------------------------------------------
3435 Expunge_and_close callback to prompt user for confirmation
3437 Args: stream -- folder's stream
3438 folder -- name of folder containing folders
3439 deleted -- number of del'd msgs
3441 Result: 'y' to continue with expunge
3442 ----*/
3444 expunge_prompt(MAILSTREAM *stream, char *folder, long int deleted)
3446 long max_folder;
3447 int charcnt = 0;
3448 char prompt_b[MAX_SCREEN_COLS+1], temp[MAILTMPLEN+1], buff[MAX_SCREEN_COLS+1];
3449 char *short_folder_name;
3451 if(deleted == 1)
3452 charcnt = 1;
3453 else{
3454 snprintf(temp, sizeof(temp), "%ld", deleted);
3455 charcnt = strlen(temp)+1;
3458 max_folder = MAX(1,MAXPROMPT - (36+charcnt));
3459 strncpy(temp, folder, sizeof(temp));
3460 temp[sizeof(temp)-1] = '\0';
3461 short_folder_name = short_str(temp,buff,sizeof(buff),max_folder,FrontDots);
3463 if(IS_NEWS(stream))
3464 snprintf(prompt_b, sizeof(prompt_b),
3465 "Delete %s%ld message%s from \"%s\"",
3466 (deleted > 1L) ? "all " : "", deleted,
3467 plural(deleted), short_folder_name);
3468 else
3469 snprintf(prompt_b, sizeof(prompt_b),
3470 "Expunge the %ld deleted message%s from \"%s\"",
3471 deleted, deleted == 1 ? "" : "s",
3472 short_folder_name);
3474 return(want_to(prompt_b, 'y', 0, NO_HELP, WT_NORM));
3479 * This is used with multiple append saves. Call it once before
3480 * the series of appends with SSCP_INIT and once after all are
3481 * done with SSCP_END. In between, it is called automatically
3482 * from save_fetch_append or save_fetch_append_cb when we need
3483 * to ask the user if he or she wants to continue even though
3484 * announced message size doesn't match the actual message size.
3485 * As of 2008-02-29 the gmail IMAP server has these size mismatches
3486 * on a regular basis even though the data is ok.
3489 save_size_changed_prompt(long msgno, int flags)
3491 int ret;
3492 char prompt[100];
3493 static int remember_the_yes = 0;
3494 static int possible_corruption = 0;
3495 static ESCKEY_S save_size_opts[] = {
3496 {'y', 'y', "Y", "Yes"},
3497 {'n', 'n', "N", "No"},
3498 {'a', 'a', "A", "yes to All"},
3499 {-1, 0, NULL, NULL}
3502 if(F_ON(F_IGNORE_SIZE, ps_global))
3503 return 'y';
3505 if(flags & SSCP_INIT || flags & SSCP_END){
3506 if(flags & SSCP_END && possible_corruption)
3507 q_status_message(SM_ORDER, 3, 3, "There is possible data corruption, check the results");
3509 remember_the_yes = 0;
3510 possible_corruption = 0;
3511 ps_global->noshow_error = 0;
3512 ps_global->noshow_warn = 0;
3513 return(0);
3516 if(remember_the_yes){
3517 snprintf(prompt, sizeof(prompt),
3518 "Message to save shrank! (msg # %ld): Continuing", msgno);
3519 q_status_message(SM_ORDER, 0, 3, prompt);
3520 display_message('x');
3521 return(remember_the_yes);
3524 snprintf(prompt, sizeof(prompt),
3525 "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno);
3526 ret = radio_buttons(prompt, -FOOTER_ROWS(ps_global), save_size_opts,
3527 'n', 0, h_save_size_changed, RB_NORM|RB_NO_NEWMAIL);
3529 switch(ret){
3530 case 'a':
3531 remember_the_yes = 'y';
3532 possible_corruption++;
3533 return(remember_the_yes);
3535 case 'y':
3536 possible_corruption++;
3537 return('y');
3539 default:
3540 possible_corruption = 0;
3541 ps_global->noshow_error = 1;
3542 ps_global->noshow_warn = 1;
3543 break;
3546 return('n');
3550 /*----------------------------------------------------------------------
3551 Expunge_and_close callback that happens once the decision to expunge
3552 and close has been made and before expunging and closing begins
3555 Args: stream -- folder's stream
3556 folder -- name of folder containing folders
3557 deleted -- number of del'd msgs
3559 Result: 'y' to continue with expunge
3560 ----*/
3561 void
3562 expunge_and_close_begins(int flags, char *folder)
3564 if(!(flags & EC_NO_CLOSE)){
3565 unsigned char *fname = folder_name_decoded((unsigned char *)folder);
3566 q_status_message1(SM_INFO, 0, 1, "Closing \"%.200s\"...", (char *) fname);
3567 flush_status_messages(1);
3568 if(fname) fs_give((void **)&fname);
3573 /*----------------------------------------------------------------------
3574 Export a message to a plain file in users home directory
3576 Args: state -- pointer to struct holding a bunch of pine state
3577 msgmap -- table mapping msg nums to c-client sequence nums
3578 qline -- screen line to ask questions on
3579 agg -- boolean indicating we're to operate on aggregate set
3581 Result:
3582 ----*/
3584 cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt)
3586 char filename[MAXPATH+1], full_filename[MAXPATH+1], *err;
3587 char nmsgs[80];
3588 int r, leading_nl, failure = 0, orig_errno, rflags = GER_NONE;
3589 int flags = GE_IS_EXPORT | GE_SEQ_SENSITIVE, rv = 0;
3590 ENVELOPE *env;
3591 MESSAGECACHE *mc;
3592 BODY *b;
3593 long i, count = 0L, start_of_append, rawno;
3594 gf_io_t pc;
3595 STORE_S *store;
3596 struct variable *vars = state ? ps_global->vars : NULL;
3597 ESCKEY_S export_opts[5];
3598 static HISTORY_S *history = NULL;
3600 if(ps_global->restricted){
3601 q_status_message(SM_ORDER, 0, 3,
3602 "Alpine demo can't export messages to files");
3603 return rv;
3606 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
3607 return rv;
3609 export_opts[i = 0].ch = ctrl('T');
3610 export_opts[i].rval = 10;
3611 export_opts[i].name = "^T";
3612 export_opts[i++].label = N_("To Files");
3614 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3615 if(ps_global->VAR_DOWNLOAD_CMD && ps_global->VAR_DOWNLOAD_CMD[0]){
3616 export_opts[i].ch = ctrl('V');
3617 export_opts[i].rval = 12;
3618 export_opts[i].name = "^V";
3619 /* TRANSLATORS: this is an abbreviation for Download Messages */
3620 export_opts[i++].label = N_("Downld Msg");
3622 #endif /* !(DOS || MAC) */
3624 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
3625 export_opts[i].ch = ctrl('I');
3626 export_opts[i].rval = 11;
3627 export_opts[i].name = "TAB";
3628 export_opts[i++].label = N_("Complete");
3631 #if 0
3632 /* Commented out since it's not yet support! */
3633 if(F_ON(F_ENABLE_SUB_LISTS,ps_global)){
3634 export_opts[i].ch = ctrl('X');
3635 export_opts[i].rval = 14;
3636 export_opts[i].name = "^X";
3637 export_opts[i++].label = N_("ListMatches");
3639 #endif
3642 * If message has attachments, add a toggle that will allow the user
3643 * to save all of the attachments to a single directory, using the
3644 * names provided with the attachments or part names. What we'll do is
3645 * export the message as usual, and then export the attachments into
3646 * a subdirectory that did not exist before. The subdir will be named
3647 * something based on the name of the file being saved to, but a
3648 * unique, new name.
3650 if(!MCMD_ISAGG(aopt)
3651 && state->mail_stream
3652 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3653 && rawno <= state->mail_stream->nmsgs
3654 && (env = pine_mail_fetchstructure(state->mail_stream, rawno, &b))
3655 && b
3656 && b->type == TYPEMULTIPART
3657 && b->subtype
3658 && strucmp(b->subtype, "ALTERNATIVE") != 0){
3659 PART *part;
3661 part = b->nested.part; /* 1st part */
3662 if(part && part->next)
3663 flags |= GE_ALLPARTS;
3666 export_opts[i].ch = -1;
3667 filename[0] = '\0';
3669 if(mn_total_cur(msgmap) <= 1L){
3670 snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld", mn_get_cur(msgmap));
3671 nmsgs[sizeof(nmsgs)-1] = '\0';
3673 else{
3674 snprintf(nmsgs, sizeof(nmsgs), "%s messages", comatose(mn_total_cur(msgmap)));
3675 nmsgs[sizeof(nmsgs)-1] = '\0';
3678 r = get_export_filename(state, filename, NULL, full_filename,
3679 sizeof(filename), nmsgs, "EXPORT",
3680 export_opts, &rflags, qline, flags, &history);
3682 if(r < 0){
3683 switch(r){
3684 case -1:
3685 cmd_cancelled("Export message");
3686 break;
3688 case -2:
3689 q_status_message1(SM_ORDER, 0, 2,
3690 _("Can't export to file outside of %s"),
3691 VAR_OPER_DIR);
3692 break;
3695 goto fini;
3697 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3698 else if(r == 12){ /* Download */
3699 char cmd[MAXPATH], *tfp = NULL;
3700 int next = 0;
3701 PIPE_S *syspipe;
3702 STORE_S *so;
3703 gf_io_t pc;
3705 if(ps_global->restricted){
3706 q_status_message(SM_ORDER | SM_DING, 3, 3,
3707 "Download disallowed in restricted mode");
3708 goto fini;
3711 err = NULL;
3712 tfp = temp_nam(NULL, "pd");
3713 build_updown_cmd(cmd, sizeof(cmd), ps_global->VAR_DOWNLOAD_CMD_PREFIX,
3714 ps_global->VAR_DOWNLOAD_CMD, tfp);
3715 dprint((1, "Download cmd called: \"%s\"\n", cmd));
3716 if((so = so_get(FileStar, tfp, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){
3717 gf_set_so_writec(&pc, so);
3719 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
3720 if(!(state->mail_stream
3721 && (rawno = mn_m2raw(msgmap, i)) > 0L
3722 && rawno <= state->mail_stream->nmsgs
3723 && (mc = mail_elt(state->mail_stream, rawno))
3724 && mc->valid))
3725 mc = NULL;
3727 if(!(env = pine_mail_fetchstructure(state->mail_stream,
3728 mn_m2raw(msgmap, i), &b))
3729 || !bezerk_delimiter(env, mc, pc, next++)
3730 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
3731 env, b, NULL, FM_NEW_MESS | FM_NOWRAP, pc)){
3732 q_status_message(SM_ORDER | SM_DING, 3, 3,
3733 err = "Error writing tempfile for download");
3734 break;
3738 gf_clear_so_writec(so);
3739 if(so_give(&so)){ /* close file */
3740 if(!err)
3741 err = "Error writing tempfile for download";
3744 if(!err){
3745 if((syspipe = open_system_pipe(cmd, NULL, NULL,
3746 PIPE_USER | PIPE_RESET,
3747 0, pipe_callback, pipe_report_error)) != NULL)
3748 (void) close_system_pipe(&syspipe, NULL, pipe_callback);
3749 else
3750 q_status_message(SM_ORDER | SM_DING, 3, 3,
3751 err = _("Error running download command"));
3754 else
3755 q_status_message(SM_ORDER | SM_DING, 3, 3,
3756 err = "Error building temp file for download");
3758 if(tfp){
3759 our_unlink(tfp);
3760 fs_give((void **)&tfp);
3763 if(!err)
3764 q_status_message(SM_ORDER, 0, 3, _("Download Command Completed"));
3766 goto fini;
3768 #endif /* !(DOS || MAC) */
3771 if(rflags & GER_APPEND)
3772 leading_nl = 1;
3773 else
3774 leading_nl = 0;
3776 dprint((5, "Opening file \"%s\" for export\n",
3777 full_filename ? full_filename : "?"));
3779 if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE))){
3780 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3781 /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
3782 _("Error opening file \"%s\" to export message: %s"),
3783 full_filename, error_description(errno));
3784 goto fini;
3786 else
3787 gf_set_so_writec(&pc, store);
3789 err = NULL;
3790 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), count++){
3791 env = pine_mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i),
3792 &b);
3793 if(!env) {
3794 err = _("Can't export message. Error accessing mail folder");
3795 failure = 1;
3796 break;
3799 if(!(state->mail_stream
3800 && (rawno = mn_m2raw(msgmap, i)) > 0L
3801 && rawno <= state->mail_stream->nmsgs
3802 && (mc = mail_elt(state->mail_stream, rawno))
3803 && mc->valid))
3804 mc = NULL;
3806 start_of_append = so_tell(store);
3807 if(!bezerk_delimiter(env, mc, pc, leading_nl)
3808 || !format_message(mn_m2raw(msgmap, i), env, b, NULL,
3809 FM_NEW_MESS | FM_NOWRAP, pc)){
3810 orig_errno = errno; /* save in case things are really bad */
3811 failure = 1; /* pop out of here */
3812 break;
3815 leading_nl = 1;
3818 gf_clear_so_writec(store);
3819 if(so_give(&store)) /* release storage */
3820 failure++;
3822 if(failure){
3823 our_truncate(full_filename, (off_t)start_of_append);
3824 if(err){
3825 dprint((1, "FAILED Export: fetch(%ld): %s\n",
3826 i, err ? err : "?"));
3827 q_status_message(SM_ORDER | SM_DING, 3, 4, err);
3829 else{
3830 dprint((1, "FAILED Export: file \"%s\" : %s\n",
3831 full_filename ? full_filename : "?",
3832 error_description(orig_errno)));
3833 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3834 /* TRANSLATORS: Error exporting to <filename>: <error text> */
3835 _("Error exporting to \"%s\" : %s"),
3836 filename, error_description(orig_errno));
3839 else{
3840 if(rflags & GER_ALLPARTS && full_filename[0]){
3841 char dir[MAXPATH+1];
3842 char lfile[MAXPATH+1];
3843 int ok = 0, tries = 0, saved = 0, errs = 0, counter = 2;
3844 ATTACH_S *a;
3847 * Now we want to save all of the attachments to a subdirectory.
3848 * To make it easier for us and probably easier for the user, and
3849 * to prevent the user from shooting himself in the foot, we
3850 * make a new subdirectory so that we can't possibly step on
3851 * any existing files, and we don't need any interaction with the
3852 * user while saving.
3854 * We'll just use the directory name full_filename.d or if that
3855 * already exists and isn't empty, we'll try adding a suffix to
3856 * that until we get something to use.
3859 if(strlen(full_filename) + strlen(".d") + 1 > sizeof(dir)){
3860 q_status_message1(SM_ORDER | SM_DING, 3, 4,
3861 _("Can't save attachments, filename too long: %s"),
3862 full_filename);
3863 goto fini;
3866 ok = 0;
3867 snprintf(dir, sizeof(dir), "%s.d", full_filename);
3868 dir[sizeof(dir)-1] = '\0';
3870 do {
3871 tries++;
3872 switch(r = is_writable_dir(dir)){
3873 case 0: /* exists and is a writable dir */
3875 * We could figure out if it is empty and use it in
3876 * that case, but that sounds like a lot of work, so
3877 * just fall through to default.
3880 default:
3881 if(strlen(full_filename) + strlen(".d") + 1 +
3882 1 + strlen(long2string((long) tries)) > sizeof(dir)){
3883 q_status_message(SM_ORDER | SM_DING, 3, 4,
3884 "Problem saving attachments");
3885 goto fini;
3888 snprintf(dir, sizeof(dir), "%s.d_%s", full_filename,
3889 long2string((long) tries));
3890 dir[sizeof(dir)-1] = '\0';
3891 break;
3893 case 3: /* doesn't exist, that's good! */
3894 /* make new directory */
3895 ok++;
3896 break;
3898 } while(!ok && tries < 1000);
3900 if(tries >= 1000){
3901 q_status_message(SM_ORDER | SM_DING, 3, 4,
3902 _("Problem saving attachments"));
3903 goto fini;
3906 /* create the new directory */
3907 if(our_mkdir(dir, 0700)){
3908 q_status_message2(SM_ORDER | SM_DING, 3, 4,
3909 _("Problem saving attachments: %s: %s"), dir,
3910 error_description(errno));
3911 goto fini;
3914 if(!(state->mail_stream
3915 && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L
3916 && rawno <= state->mail_stream->nmsgs
3917 && (env=pine_mail_fetchstructure(state->mail_stream,rawno,&b))
3918 && b)){
3919 q_status_message(SM_ORDER | SM_DING, 3, 4,
3920 _("Problem reading message"));
3921 goto fini;
3924 zero_atmts(state->atmts);
3925 describe_mime(b, "", 1, 1, 0, 0);
3927 a = state->atmts;
3928 if(a && a->description) /* skip main body part */
3929 a++;
3931 for(; a->description != NULL; a++){
3932 /* skip over these parts of the message */
3933 if(MIME_MSG_A(a) || MIME_DGST_A(a) || MIME_VCARD_A(a))
3934 continue;
3936 lfile[0] = '\0';
3937 (void) get_filename_parameter(lfile, sizeof(lfile), a->body, NULL);
3939 if(lfile[0] == '\0'){ /* MAXPATH + 1 = sizeof(lfile) */
3940 snprintf(lfile, sizeof(lfile), "part_%.*s", MAXPATH+1-6,
3941 a->number ? a->number : "?");
3942 lfile[sizeof(lfile)-1] = '\0';
3945 if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + 1
3946 > sizeof(filename)){
3947 dprint((2,
3948 "FAILED Att Export: name too long: %s\n",
3949 dir, S_FILESEP, lfile));
3950 errs++;
3951 continue;
3954 /* although files are being saved in a unique directory, there is
3955 * no guarantee that attachment names have unique names, so we have
3956 * to make sure that we are not constantly rewriting the same file name
3957 * over and over. In order to avoid this we test if the file already exists,
3958 * and if so, we write a counter name in the file name, just before the
3959 * extension of the file, and separate it with an underscore.
3961 snprintf(filename, sizeof(filename), "%s%s%s", dir, S_FILESEP, lfile);
3962 filename[sizeof(filename)-1] = '\0';
3963 while((ok = can_access(filename, ACCESS_EXISTS)) == 0 && errs == 0){
3964 char *ext;
3965 snprintf(filename, sizeof(filename), "%d", counter);
3966 if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + strlen(filename) + 2
3967 > sizeof(filename)){
3968 dprint((2,
3969 "FAILED Att Export: name too long: %s\n",
3970 dir, S_FILESEP, lfile));
3971 errs++;
3972 continue;
3974 if((ext = strrchr(lfile, '.')) != NULL)
3975 *ext = '\0';
3976 snprintf(filename, sizeof(filename), "%s%s%s%s%d%s%s",
3977 dir, S_FILESEP, lfile,
3978 ext ? "_" : "", counter++, ext ? "." : "", ext ? ext+1 : "");
3979 filename[sizeof(filename)-1] = '\0';
3982 if(write_attachment_to_file(state->mail_stream, rawno,
3983 a, GER_NONE, filename) == 1)
3984 saved++;
3985 else
3986 errs++;
3989 if(errs){
3990 if(saved)
3991 q_status_message1(SM_ORDER, 3, 3,
3992 "Errors saving some attachments, %s attachments saved",
3993 long2string((long) saved));
3994 else
3995 q_status_message(SM_ORDER, 3, 3,
3996 _("Problems saving attachments"));
3998 else{
3999 if(saved)
4000 q_status_message2(SM_ORDER, 0, 3,
4001 /* TRANSLATORS: Saved <how many> attachments to <directory name> */
4002 _("Saved %s attachments to %s"),
4003 long2string((long) saved), dir);
4004 else
4005 q_status_message(SM_ORDER, 3, 3, _("No attachments to save"));
4008 else if(mn_total_cur(msgmap) > 1L)
4009 q_status_message4(SM_ORDER,0,3,
4010 "%s message%s %s to file \"%s\"",
4011 long2string(count), plural(count),
4012 rflags & GER_OVER
4013 ? "overwritten"
4014 : rflags & GER_APPEND ? "appended" : "exported",
4015 filename);
4016 else
4017 q_status_message3(SM_ORDER,0,3,
4018 "Message %s %s to file \"%s\"",
4019 long2string(mn_get_cur(msgmap)),
4020 rflags & GER_OVER
4021 ? "overwritten"
4022 : rflags & GER_APPEND ? "appended" : "exported",
4023 filename);
4024 rv++;
4027 fini:
4028 if(MCMD_ISAGG(aopt))
4029 restore_selected(msgmap);
4031 return rv;
4036 * Ask user what file to export to. Export from srcstore to that file.
4038 * Args ps -- pine struct
4039 * srctext -- pointer to source text
4040 * srctype -- type of that source text
4041 * prompt_msg -- see get_export_filename
4042 * lister_msg -- "
4044 * Returns: != 0 : error
4045 * 0 : ok
4048 simple_export(struct pine *ps, void *srctext, SourceType srctype, char *prompt_msg, char *lister_msg)
4050 int r = 1, rflags = GER_NONE;
4051 char filename[MAXPATH+1], full_filename[MAXPATH+1];
4052 STORE_S *store = NULL;
4053 struct variable *vars = ps ? ps->vars : NULL;
4054 static HISTORY_S *history = NULL;
4055 static ESCKEY_S simple_export_opts[] = {
4056 {ctrl('T'), 10, "^T", N_("To Files")},
4057 {-1, 0, NULL, NULL},
4058 {-1, 0, NULL, NULL}};
4060 if(F_ON(F_ENABLE_TAB_COMPLETE,ps)){
4061 simple_export_opts[r].ch = ctrl('I');
4062 simple_export_opts[r].rval = 11;
4063 simple_export_opts[r].name = "TAB";
4064 simple_export_opts[r].label = N_("Complete");
4067 if(!srctext){
4068 q_status_message(SM_ORDER, 0, 2, _("Error allocating space"));
4069 r = -3;
4070 goto fini;
4073 simple_export_opts[++r].ch = -1;
4074 filename[0] = '\0';
4075 full_filename[0] = '\0';
4077 r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename),
4078 prompt_msg, lister_msg, simple_export_opts, &rflags,
4079 -FOOTER_ROWS(ps), GE_IS_EXPORT, &history);
4081 if(r < 0)
4082 goto fini;
4083 else if(!full_filename[0]){
4084 r = -1;
4085 goto fini;
4088 dprint((5, "Opening file \"%s\" for export\n",
4089 full_filename ? full_filename : "?"));
4091 if((store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){
4092 char *pipe_err;
4093 gf_io_t pc, gc;
4095 gf_set_so_writec(&pc, store);
4096 gf_set_readc(&gc, srctext, (srctype == CharStar)
4097 ? strlen((char *)srctext)
4098 : 0L,
4099 srctype, 0);
4100 gf_filter_init();
4101 if((pipe_err = gf_pipe(gc, pc)) != NULL){
4102 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4103 /* TRANSLATORS: Problem saving to <filename>: <error text> */
4104 _("Problem saving to \"%s\": %s"),
4105 filename, pipe_err);
4106 r = -3;
4108 else
4109 r = 0;
4111 gf_clear_so_writec(store);
4112 if(so_give(&store)){
4113 q_status_message2(SM_ORDER | SM_DING, 3, 3,
4114 _("Problem saving to \"%s\": %s"),
4115 filename, error_description(errno));
4116 r = -3;
4119 else{
4120 q_status_message2(SM_ORDER | SM_DING, 3, 4,
4121 _("Error opening file \"%s\" for export: %s"),
4122 full_filename, error_description(errno));
4123 r = -3;
4126 fini:
4127 switch(r){
4128 case 0:
4129 /* overloading full_filename */
4130 snprintf(full_filename, sizeof(full_filename), "%c%s",
4131 (prompt_msg && prompt_msg[0])
4132 ? (islower((unsigned char)prompt_msg[0])
4133 ? toupper((unsigned char)prompt_msg[0]) : prompt_msg[0])
4134 : 'T',
4135 (prompt_msg && prompt_msg[0]) ? prompt_msg+1 : "ext");
4136 full_filename[sizeof(full_filename)-1] = '\0';
4137 q_status_message3(SM_ORDER,0,2,"%s %s to \"%s\"",
4138 full_filename,
4139 rflags & GER_OVER
4140 ? "overwritten"
4141 : rflags & GER_APPEND ? "appended" : "exported",
4142 filename);
4143 break;
4145 case -1:
4146 cmd_cancelled("Export");
4147 break;
4149 case -2:
4150 q_status_message1(SM_ORDER, 0, 2,
4151 _("Can't export to file outside of %s"), VAR_OPER_DIR);
4152 break;
4155 ps->mangled_footer = 1;
4156 return(r);
4161 * Ask user what file to export to.
4163 * filename -- On input, this is the filename to start with. On exit,
4164 * this is the filename chosen. (but this isn't used)
4165 * deefault -- This is the default value if user hits return. The
4166 * prompt will have [deefault] added to it automatically.
4167 * full_filename -- This is the full filename on exit.
4168 * len -- Minimum length of _both_ filename and full_filename.
4169 * prompt_msg -- Message to insert in prompt.
4170 * lister_msg -- Message to insert in file_lister.
4171 * opts -- Key options.
4172 * There is a tangled relationship between the callers
4173 * and this routine as far as opts are concerned. Some
4174 * of the opts are handled here. In particular, r == 3,
4175 * r == 10, r == 11, and r == 13 are all handled here.
4176 * Don't use those values unless you want what happens
4177 * here. r == 12 and others are handled by the caller.
4178 * rflags -- Return flags
4179 * GER_OVER - overwrite of existing file
4180 * GER_APPEND - append of existing file
4181 * else file did not exist before
4183 * GER_ALLPARTS - AllParts toggle was turned on
4185 * qline -- Command line to prompt on.
4186 * flags -- Logically OR'd flags
4187 * GE_IS_EXPORT - The command was an Export command
4188 * so the prompt should include
4189 * EXPORT:.
4190 * GE_SEQ_SENSITIVE - The command that got us here is
4191 * sensitive to sequence number changes
4192 * caused by unsolicited expunges.
4193 * GE_NO_APPEND - We will not allow append to an
4194 * existing file, only removal of the
4195 * file if it exists.
4196 * GE_IS_IMPORT - We are selecting for reading.
4197 * No overwriting or checking for
4198 * existence at all. Don't use this
4199 * together with GE_NO_APPEND.
4200 * GE_ALLPARTS - Turn on AllParts toggle.
4201 * GE_BINARY - Turn on Binary toggle.
4203 * Returns: -1 cancelled
4204 * -2 prohibited by VAR_OPER_DIR
4205 * -3 other error, already reported here
4206 * 0 ok
4207 * 12 user chose 12 command from opts
4210 get_export_filename(struct pine *ps, char *filename, char *deefault,
4211 char *full_filename, size_t len, char *prompt_msg,
4212 char *lister_msg, ESCKEY_S *optsarg, int *rflags,
4213 int qline, int flags, HISTORY_S **history)
4215 char dir[MAXPATH+1], dir2[MAXPATH+1], orig_dir[MAXPATH+1];
4216 char precolon[MAXPATH+1], postcolon[MAXPATH+1];
4217 char filename2[MAXPATH+1], tmp[MAXPATH+1], *fn, *ill;
4218 int l, i, ku = -1, kp = -1, r, fatal, homedir = 0, was_abs_path=0, avail, ret = 0;
4219 int allparts = 0, binary = 0;
4220 char prompt_buf[400];
4221 char def[500];
4222 ESCKEY_S *opts = NULL;
4223 struct variable *vars = ps->vars;
4224 static HISTORY_S *dir_hist = NULL;
4225 static char *last;
4226 int pos, hist_len = 0;
4229 /* we will fake a history with the ps_global->VAR_HISTORY variable
4230 * We fake that we combine this variable into a history variable
4231 * by stacking VAR_HISTORY on top of dir_hist. We keep track of this
4232 * by looking at the variable pos.
4234 if(ps_global->VAR_HISTORY != NULL)
4235 for(hist_len = 0; ps_global->VAR_HISTORY[hist_len]
4236 && ps_global->VAR_HISTORY[hist_len][0]; hist_len++)
4239 pos = hist_len + items_in_hist(dir_hist);
4241 if(flags & GE_ALLPARTS || history || dir_hist){
4243 * Copy the opts and add one to the end of the list.
4245 for(i = 0; optsarg[i].ch != -1; i++)
4248 if(dir_hist || hist_len > 0)
4249 i += 2;
4251 if(history)
4252 i += dir_hist || hist_len > 0 ? 2 : 4;
4254 if(flags & GE_ALLPARTS)
4255 i++;
4257 if(flags & GE_BINARY)
4258 i++;
4260 opts = (ESCKEY_S *) fs_get((i+1) * sizeof(*opts));
4261 memset(opts, 0, (i+1) * sizeof(*opts));
4263 for(i = 0; optsarg[i].ch != -1; i++){
4264 opts[i].ch = optsarg[i].ch;
4265 opts[i].rval = optsarg[i].rval;
4266 opts[i].name = optsarg[i].name; /* no need to make a copy */
4267 opts[i].label = optsarg[i].label; /* " */
4270 if(flags & GE_ALLPARTS){
4271 allparts = i;
4272 opts[i].ch = ctrl('P');
4273 opts[i].rval = 13;
4274 opts[i].name = "^P";
4275 /* TRANSLATORS: Export all attachment parts */
4276 opts[i++].label = N_("AllParts");
4279 if(flags & GE_BINARY){
4280 binary = i;
4281 opts[i].ch = ctrl('R');
4282 opts[i].rval = 15;
4283 opts[i].name = "^R";
4284 opts[i++].label = N_("Binary");
4287 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4288 SIZEOF_20KBUF, filename);
4289 #ifndef _WINDOWS
4290 /* In the Windows operating system we always return the UTF8 encoded name */
4291 if(strcmp(tmp_20k_buf, filename)){
4292 opts[i].ch = ctrl('N');
4293 opts[i].rval = 40;
4294 opts[i].name = "^N";
4295 opts[i++].label = "Name UTF8";
4297 #else
4298 strncpy(filename, tmp_20k_buf, len);
4299 filename[len-1] = '\0';
4300 #endif /* _WINDOWS */
4302 if(dir_hist || hist_len > 0){
4303 opts[i].ch = ctrl('Y');
4304 opts[i].rval = 32;
4305 opts[i].name = "";
4306 kp = i;
4307 opts[i++].label = "";
4309 opts[i].ch = ctrl('V');
4310 opts[i].rval = 33;
4311 opts[i].name = "";
4312 opts[i++].label = "";
4315 if(history){
4316 opts[i].ch = KEY_UP;
4317 opts[i].rval = 30;
4318 opts[i].name = "";
4319 ku = i;
4320 opts[i++].label = "";
4322 opts[i].ch = KEY_DOWN;
4323 opts[i].rval = 31;
4324 opts[i].name = "";
4325 opts[i++].label = "";
4328 opts[i].ch = -1;
4330 if(history)
4331 init_hist(history, HISTSIZE);
4332 init_hist(&dir_hist, HISTSIZE); /* reset history to the end */
4334 else
4335 opts = optsarg;
4337 if(rflags)
4338 *rflags = GER_NONE;
4340 if(F_ON(F_USE_CURRENT_DIR, ps))
4341 dir[0] = '\0';
4342 else if(VAR_OPER_DIR){
4343 strncpy(dir, VAR_OPER_DIR, sizeof(dir));
4344 dir[sizeof(dir)-1] = '\0';
4346 #if defined(DOS) || defined(OS2)
4347 else if(VAR_FILE_DIR){
4348 strncpy(dir, VAR_FILE_DIR, sizeof(dir));
4349 dir[sizeof(dir)-1] = '\0';
4351 #endif
4352 else{
4353 dir[0] = '~';
4354 dir[1] = '\0';
4355 homedir=1;
4357 strncpy(orig_dir, dir, sizeof(orig_dir));
4358 orig_dir[sizeof(orig_dir)-1] = '\0';
4360 postcolon[0] = '\0';
4361 strncpy(precolon, dir, sizeof(precolon));
4362 precolon[sizeof(precolon)-1] = '\0';
4363 if(deefault){
4364 strncpy(def, deefault, sizeof(def)-1);
4365 def[sizeof(def)-1] = '\0';
4366 removing_leading_and_trailing_white_space(def);
4368 else
4369 def[0] = '\0';
4371 avail = MAX(20, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - MIN_OPT_ENT_WIDTH;
4373 /*---------- Prompt the user for the file name -------------*/
4374 while(1){
4375 int oeflags;
4376 char dirb[50], fileb[50];
4377 int l1, l2, l3, l4, l5, needed;
4378 char *p, p1[100], p2[100], *p3, p4[100], p5[100];
4380 snprintf(p1, sizeof(p1), "%sCopy ",
4381 (flags & GE_IS_EXPORT) ? "EXPORT: " :
4382 (flags & GE_IS_IMPORT) ? "IMPORT: " : "SAVE: ");
4383 p1[sizeof(p1)-1] = '\0';
4384 l1 = strlen(p1);
4386 strncpy(p2, prompt_msg ? prompt_msg : "", sizeof(p2)-1);
4387 p2[sizeof(p2)-1] = '\0';
4388 l2 = strlen(p2);
4390 if(rflags && *rflags & GER_ALLPARTS)
4391 p3 = " (and atts)";
4392 else
4393 p3 = "";
4395 l3 = strlen(p3);
4397 snprintf(p4, sizeof(p4), " %s file%s%s",
4398 (flags & GE_IS_IMPORT) ? "from" : "to",
4399 is_absolute_path(filename) ? "" : " in ",
4400 is_absolute_path(filename) ? "" :
4401 (!dir[0] ? "current directory"
4402 : (dir[0] == '~' && !dir[1]) ? "home directory"
4403 : short_str(dir,dirb,sizeof(dirb),30,FrontDots)));
4404 p4[sizeof(p4)-1] = '\0';
4405 l4 = strlen(p4);
4407 snprintf(p5, sizeof(p5), "%s%s%s: ",
4408 *def ? " [" : "",
4409 *def ? short_str(def,fileb,sizeof(fileb),40,EndDots) : "",
4410 *def ? "]" : "");
4411 p5[sizeof(p5)-1] = '\0';
4412 l5 = strlen(p5);
4414 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4415 snprintf(p4, sizeof(p4), " %s file%s%s",
4416 (flags & GE_IS_IMPORT) ? "from" : "to",
4417 is_absolute_path(filename) ? "" : " in ",
4418 is_absolute_path(filename) ? "" :
4419 (!dir[0] ? "current dir"
4420 : (dir[0] == '~' && !dir[1]) ? "home dir"
4421 : short_str(dir,dirb,sizeof(dirb),10,FrontDots)));
4422 p4[sizeof(p4)-1] = '\0';
4423 l4 = strlen(p4);
4426 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4427 snprintf(p5, sizeof(p5), "%s%s%s: ",
4428 *def ? " [" : "",
4429 *def ? short_str(def,fileb,sizeof(fileb),
4430 MAX(15,l5-5-needed),EndDots) : "",
4431 *def ? "]" : "");
4432 p5[sizeof(p5)-1] = '\0';
4433 l5 = strlen(p5);
4436 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l2 > 0){
4439 * 14 is about the shortest we can make this, because there are
4440 * fixed length strings of length 14 coming in here.
4442 p = short_str(prompt_msg, p2, sizeof(p2), MAX(14,l2-needed), FrontDots);
4443 if(p != p2){
4444 strncpy(p2, p, sizeof(p2)-1);
4445 p2[sizeof(p2)-1] = '\0';
4448 l2 = strlen(p2);
4451 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4452 strncpy(p1, "Copy ", sizeof(p1)-1);
4453 p1[sizeof(p1)-1] = '\0';
4454 l1 = strlen(p1);
4457 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4458 snprintf(p5, sizeof(p5), "%s%s%s: ",
4459 *def ? " [" : "",
4460 *def ? short_str(def,fileb, sizeof(fileb),
4461 MAX(10,l5-5-needed),EndDots) : "",
4462 *def ? "]" : "");
4463 p5[sizeof(p5)-1] = '\0';
4464 l5 = strlen(p5);
4467 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l3 > 0){
4468 if(needed <= l3 - strlen(" (+ atts)"))
4469 p3 = " (+ atts)";
4470 else if(needed <= l3 - strlen(" (atts)"))
4471 p3 = " (atts)";
4472 else if(needed <= l3 - strlen(" (+)"))
4473 p3 = " (+)";
4474 else if(needed <= l3 - strlen("+"))
4475 p3 = "+";
4476 else
4477 p3 = "";
4479 l3 = strlen(p3);
4482 snprintf(prompt_buf, sizeof(prompt_buf), "%s%s%s%s%s", p1, p2, p3, p4, p5);
4483 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4485 if(kp >= 0){
4486 if(items_in_hist(dir_hist) > 0 || hist_len > 0){ /* any directories */
4487 opts[kp].name = "^Y";
4488 opts[kp].label = "Prev Dir";
4489 opts[kp+1].name = "^V";
4490 opts[kp+1].label = "Next Dir";
4492 else{
4493 opts[kp].name = "";
4494 opts[kp].label = "";
4495 opts[kp+1].name = "";
4496 opts[kp+1].label = "";
4500 if(ku >= 0){
4501 if(items_in_hist(*history) > 0){
4502 opts[ku].name = HISTORY_UP_KEYNAME;
4503 opts[ku].label = HISTORY_KEYLABEL;
4504 opts[ku+1].name = HISTORY_DOWN_KEYNAME;
4505 opts[ku+1].label = HISTORY_KEYLABEL;
4507 else{
4508 opts[ku].name = "";
4509 opts[ku].label = "";
4510 opts[ku+1].name = "";
4511 opts[ku+1].label = "";
4515 oeflags = OE_APPEND_CURRENT |
4516 ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0);
4517 r = optionally_enter(filename, qline, 0, len, prompt_buf,
4518 opts, NO_HELP, &oeflags);
4520 dprint((2, "\n - export_filename = \"%s\", r = %d -\n", filename, r));
4521 /*--- Help ----*/
4522 if(r == 3){
4524 * Helps may not be right if you add another caller or change
4525 * things. Check it out.
4527 if(flags & GE_IS_IMPORT)
4528 helper(h_ge_import, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE);
4529 else if(flags & GE_ALLPARTS)
4530 helper(h_ge_allparts, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4531 else
4532 helper(h_ge_export, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4534 ps->mangled_screen = 1;
4536 continue;
4538 else if(r == 10 || r == 11){ /* Browser or File Completion */
4539 if(filename[0]=='~'){
4540 if(filename[1] == C_FILESEP && filename[2]!='\0'){
4541 precolon[0] = '~';
4542 precolon[1] = '\0';
4543 for(i=0; filename[i+2] != '\0' && i+2 < len-1; i++)
4544 filename[i] = filename[i+2];
4545 filename[i] = '\0';
4546 strncpy(dir, precolon, sizeof(dir)-1);
4547 dir[sizeof(dir)-1] = '\0';
4549 else if(filename[1]=='\0' ||
4550 (filename[1] == C_FILESEP && filename[2] == '\0')){
4551 precolon[0] = '~';
4552 precolon[1] = '\0';
4553 filename[0] = '\0';
4554 strncpy(dir, precolon, sizeof(dir)-1);
4555 dir[sizeof(dir)-1] = '\0';
4558 else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){
4559 if(homedir){
4560 precolon[0] = '~';
4561 precolon[1] = '\0';
4562 strncpy(dir, precolon, sizeof(dir)-1);
4563 dir[sizeof(dir)-1] = '\0';
4565 else{
4566 precolon[0] = '\0';
4567 dir[0] = '\0';
4570 l = MAXPATH;
4571 dir2[0] = '\0';
4572 strncpy(tmp, filename, sizeof(tmp)-1);
4573 tmp[sizeof(tmp)-1] = '\0';
4574 if(*tmp && is_absolute_path(tmp))
4575 fnexpand(tmp, sizeof(tmp));
4576 if(strncmp(tmp,postcolon, strlen(postcolon)))
4577 postcolon[0] = '\0';
4579 if(*tmp && (fn = last_cmpnt(tmp))){
4580 l -= fn - tmp;
4581 strncpy(filename2, fn, sizeof(filename2)-1);
4582 filename2[sizeof(filename2)-1] = '\0';
4583 if(is_absolute_path(tmp)){
4584 strncpy(dir2, tmp, MIN(fn - tmp, sizeof(dir2)-1));
4585 dir2[MIN(fn - tmp, sizeof(dir2)-1)] = '\0';
4586 #ifdef _WINDOWS
4587 if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){
4588 dir2[2] = '\\';
4589 dir2[3] = '\0';
4591 #endif
4592 strncpy(postcolon, dir2, sizeof(postcolon)-1);
4593 postcolon[sizeof(postcolon)-1] = '\0';
4594 precolon[0] = '\0';
4596 else{
4597 char *p = NULL;
4599 * Just building the directory name in dir2,
4600 * full_filename is overloaded.
4602 snprintf(full_filename, len, "%.*s", (int) MIN(fn-tmp,len-1), tmp);
4603 full_filename[len-1] = '\0';
4604 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4605 postcolon[sizeof(postcolon)-1] = '\0';
4606 build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH)
4607 : (dir[0] == '~' && !dir[1])
4608 ? ps->home_dir
4609 : dir,
4610 full_filename, sizeof(dir2));
4611 if(p)
4612 free(p);
4615 else{
4616 if(is_absolute_path(tmp)){
4617 strncpy(dir2, tmp, sizeof(dir2)-1);
4618 dir2[sizeof(dir2)-1] = '\0';
4619 #ifdef _WINDOWS
4620 if(dir2[2]=='\0' && dir2[1]==':'){
4621 dir2[2]='\\';
4622 dir2[3]='\0';
4623 strncpy(postcolon,dir2,sizeof(postcolon)-1);
4624 postcolon[sizeof(postcolon)-1] = '\0';
4626 #endif
4627 filename2[0] = '\0';
4628 precolon[0] = '\0';
4630 else{
4631 strncpy(filename2, tmp, sizeof(filename2)-1);
4632 filename2[sizeof(filename2)-1] = '\0';
4633 if(!dir[0]){
4634 if(getcwd(dir2, sizeof(dir2)) == NULL)
4635 alpine_panic(_("getcwd() call failed at get_export_filename"));
4637 else if(dir[0] == '~' && !dir[1]){
4638 strncpy(dir2, ps->home_dir, sizeof(dir2)-1);
4639 dir2[sizeof(dir2)-1] = '\0';
4641 else{
4642 strncpy(dir2, dir, sizeof(dir2)-1);
4643 dir2[sizeof(dir2)-1] = '\0';
4646 postcolon[0] = '\0';
4650 build_path(full_filename, dir2, filename2, len);
4651 if(!strcmp(full_filename, dir2))
4652 filename2[0] = '\0';
4653 if(full_filename[strlen(full_filename)-1] == C_FILESEP
4654 && isdir(full_filename,NULL,NULL)){
4655 if(strlen(full_filename) == 1)
4656 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4657 else if(filename2[0])
4658 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4659 postcolon[sizeof(postcolon)-1] = '\0';
4660 strncpy(dir2, full_filename, sizeof(dir2)-1);
4661 dir2[sizeof(dir2)-1] = '\0';
4662 filename2[0] = '\0';
4664 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4665 else if(full_filename[strlen(full_filename)-1] == C_FILESEP){
4666 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4667 postcolon[sizeof(postcolon)-1] = '\0';
4668 strncpy(dir2, full_filename, sizeof(dir2)-1);
4669 dir2[sizeof(dir2)-1] = '\0';
4670 filename2[0] = '\0';
4672 #endif
4673 if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1
4674 && strcmp(dir2+1, ":\\"))
4675 /* last condition to prevent stripping of '\\'
4676 in windows partition */
4677 dir2[strlen(dir2)-1] = '\0';
4679 if(r == 10){ /* File Browser */
4680 r = file_lister(lister_msg ? lister_msg : "EXPORT",
4681 dir2, sizeof(dir2), filename2, sizeof(filename2),
4682 TRUE,
4683 (flags & GE_IS_IMPORT) ? FB_READ : FB_SAVE);
4684 #ifdef _WINDOWS
4685 /* Windows has a special "feature" in which entering the file browser will
4686 change the working directory if the directory is changed at all (even
4687 clicking "Cancel" will change the working directory).
4689 if(F_ON(F_USE_CURRENT_DIR, ps))
4690 (void)getcwd(dir2,sizeof(dir2));
4691 #endif
4692 if(isdir(dir2,NULL,NULL)){
4693 strncpy(precolon, dir2, sizeof(precolon)-1);
4694 precolon[sizeof(precolon)-1] = '\0';
4696 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4697 postcolon[sizeof(postcolon)-1] = '\0';
4698 if(r == 1){
4699 build_path(full_filename, dir2, filename2, len);
4700 if(isdir(full_filename, NULL, NULL)){
4701 strncpy(dir, full_filename, sizeof(dir)-1);
4702 dir[sizeof(dir)-1] = '\0';
4703 filename[0] = '\0';
4705 else{
4706 fn = last_cmpnt(full_filename);
4707 strncpy(dir, full_filename,
4708 MIN(fn - full_filename, sizeof(dir)-1));
4709 dir[MIN(fn - full_filename, sizeof(dir)-1)] = '\0';
4710 if(fn - full_filename > 1)
4711 dir[fn - full_filename - 1] = '\0';
4714 if(!strcmp(dir, ps->home_dir)){
4715 dir[0] = '~';
4716 dir[1] = '\0';
4719 strncpy(filename, fn, len-1);
4720 filename[len-1] = '\0';
4723 else{ /* File Completion */
4724 if(!pico_fncomplete(dir2, filename2, sizeof(filename2)))
4725 Writechar(BELL, 0);
4726 strncat(postcolon, filename2,
4727 sizeof(postcolon)-1-strlen(postcolon));
4728 postcolon[sizeof(postcolon)-1] = '\0';
4730 was_abs_path = is_absolute_path(filename);
4732 if(!strcmp(dir, ps->home_dir)){
4733 dir[0] = '~';
4734 dir[1] = '\0';
4737 strncpy(filename, postcolon, len-1);
4738 filename[len-1] = '\0';
4739 strncpy(dir, precolon, sizeof(dir)-1);
4740 dir[sizeof(dir)-1] = '\0';
4742 if(filename[0] == '~' && !filename[1]){
4743 dir[0] = '~';
4744 dir[1] = '\0';
4745 filename[0] = '\0';
4748 continue;
4750 else if(r == 12){ /* Download, caller handles it */
4751 ret = r;
4752 goto done;
4754 else if(r == 13){ /* toggle AllParts bit */
4755 if(rflags){
4756 if(*rflags & GER_ALLPARTS){
4757 *rflags &= ~GER_ALLPARTS;
4758 opts[allparts].label = N_("AllParts");
4760 else{
4761 *rflags |= GER_ALLPARTS;
4762 /* opposite of All Parts, No All Parts */
4763 opts[allparts].label = N_("NoAllParts");
4767 continue;
4769 #if 0
4770 else if(r == 14){ /* List file names matching partial? */
4771 continue;
4773 #endif
4774 else if(r == 15){ /* toggle Binary bit */
4775 if(rflags){
4776 if(*rflags & GER_BINARY){
4777 *rflags &= ~GER_BINARY;
4778 opts[binary].label = N_("Binary");
4780 else{
4781 *rflags |= GER_BINARY;
4782 opts[binary].label = N_("No Binary");
4786 continue;
4788 else if(r == 1){ /* Cancel */
4789 ret = -1;
4790 goto done;
4792 else if(r == 4){
4793 continue;
4795 else if(r >= 30 && r <= 33){
4796 char *p = NULL;
4798 if(r == 30 || r == 31){
4799 if(history){
4800 if(r == 30)
4801 p = get_prev_hist(*history, filename, 0, NULL);
4802 else if (r == 31)
4803 p = get_next_hist(*history, filename, 0, NULL);
4807 if(r == 32 || r == 33){
4808 int nitems = items_in_hist(dir_hist);
4809 if(dir_hist || hist_len > 0){
4810 if(r == 32){
4811 if(pos > 0)
4812 p = hist_in_pos(--pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4813 else p = last;
4815 else if (r == 33){
4816 if(pos < hist_len + nitems)
4817 p = hist_in_pos(++pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4819 if(p == NULL || *p == '\0')
4820 p = orig_dir;
4823 last = p; /* save it! */
4825 if(p != NULL && *p != '\0'){
4826 if(r == 30 || r == 31){
4827 if((fn = last_cmpnt(p)) != NULL){
4828 strncpy(dir, p, MIN(fn - p, sizeof(dir)-1));
4829 dir[MIN(fn - p, sizeof(dir)-1)] = '\0';
4830 if(fn - p > 1)
4831 dir[fn - p - 1] = '\0';
4832 strncpy(filename, fn, len-1);
4833 filename[len-1] = '\0';
4835 } else { /* r == 32 || r == 33 */
4836 strncpy(dir, p, sizeof(dir)-1);
4837 dir[sizeof(dir)-1] = '\0';
4840 if(!strcmp(dir, ps->home_dir)){
4841 dir[0] = '~';
4842 dir[1] = '\0';
4845 else
4846 Writechar(BELL, 0);
4847 continue;
4849 #ifndef _WINDOWS
4850 else if(r == 40){
4851 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4852 SIZEOF_20KBUF, filename);
4853 strncpy(filename, tmp_20k_buf, len);
4854 filename[len-1] = '\0';
4855 continue;
4857 #endif /* _WINDOWS */
4858 else if(r != 0){
4859 Writechar(BELL, 0);
4860 continue;
4863 removing_leading_and_trailing_white_space(filename);
4865 if(!*filename){
4866 if(!*def){ /* Cancel */
4867 ret = -1;
4868 goto done;
4871 strncpy(filename, def, len-1);
4872 filename[len-1] = '\0';
4875 #if defined(DOS) || defined(OS2)
4876 if(is_absolute_path(filename)){
4877 fixpath(filename, len);
4879 #else
4880 if(filename[0] == '~'){
4881 if(fnexpand(filename, len) == NULL){
4882 char *p = strindex(filename, '/');
4883 if(p != NULL)
4884 *p = '\0';
4885 q_status_message1(SM_ORDER | SM_DING, 3, 3,
4886 _("Error expanding file name: \"%s\" unknown user"),
4887 filename);
4888 continue;
4891 #endif
4893 if(is_absolute_path(filename)){
4894 strncpy(full_filename, filename, len-1);
4895 full_filename[len-1] = '\0';
4897 else{
4898 if(!dir[0])
4899 build_path(full_filename, (char *)getcwd(dir,sizeof(dir)),
4900 filename, len);
4901 else if(dir[0] == '~' && !dir[1])
4902 build_path(full_filename, ps->home_dir, filename, len);
4903 else
4904 build_path(full_filename, dir, filename, len);
4907 if((ill = filter_filename(full_filename, &fatal,
4908 ps_global->restricted || ps_global->VAR_OPER_DIR)) != NULL){
4909 if(fatal){
4910 q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill);
4911 continue;
4913 else{
4914 /* BUG: we should beep when the key's pressed rather than bitch later */
4915 /* Warn and ask for confirmation. */
4916 snprintf(prompt_buf, sizeof(prompt_buf), "File name contains a '%s'. %s anyway",
4917 ill, (flags & GE_IS_EXPORT) ? "Export" : "Save");
4918 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4919 if(want_to(prompt_buf, 'n', 0, NO_HELP,
4920 ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y')
4921 continue;
4925 break; /* Must have got an OK file name */
4928 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){
4929 ret = -2;
4930 goto done;
4933 if(!can_access(full_filename, ACCESS_EXISTS)){
4934 int rbflags;
4935 static ESCKEY_S access_opts[] = {
4936 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4937 a file or append to the end of the file */
4938 {'o', 'o', "O", N_("Overwrite")},
4939 {'a', 'a', "A", N_("Append")},
4940 {-1, 0, NULL, NULL}};
4942 rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0);
4944 if(flags & GE_NO_APPEND){
4945 r = strlen(filename);
4946 snprintf(prompt_buf, sizeof(prompt_buf),
4947 /* TRANSLATORS: asking user whether to overwrite a file or not,
4948 File <filename> already exists. Overwrite it ? */
4949 _("File \"%s%s\" already exists. Overwrite it "),
4950 (r > 20) ? "..." : "",
4951 filename + ((r > 20) ? r - 20 : 0));
4952 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4953 if(want_to(prompt_buf, 'n', 'x', NO_HELP, rbflags) == 'y'){
4954 if(rflags)
4955 *rflags |= GER_OVER;
4957 if(our_unlink(full_filename) < 0){
4958 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4959 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4960 _("Cannot remove old %s: %s"),
4961 full_filename, error_description(errno));
4964 else{
4965 ret = -1;
4966 goto done;
4969 else if(!(flags & GE_IS_IMPORT)){
4970 r = strlen(filename);
4971 snprintf(prompt_buf, sizeof(prompt_buf),
4972 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4973 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4974 (r > 20) ? "..." : "",
4975 filename + ((r > 20) ? r - 20 : 0));
4976 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4977 switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global),
4978 access_opts, 'a', 'x', NO_HELP, rbflags)){
4979 case 'o' :
4980 if(rflags)
4981 *rflags |= GER_OVER;
4983 if(our_truncate(full_filename, (off_t)0) < 0)
4984 /* trouble truncating, but we'll give it a try anyway */
4985 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4986 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
4987 _("Warning: Cannot truncate old %s: %s"),
4988 full_filename, error_description(errno));
4989 break;
4991 case 'a' :
4992 if(rflags)
4993 *rflags |= GER_APPEND;
4995 break;
4997 case 'x' :
4998 default :
4999 ret = -1;
5000 goto done;
5005 done:
5006 if(history && ret == 0){
5007 save_hist(*history, full_filename, 0, NULL);
5008 strncpy(tmp, full_filename, MAXPATH);
5009 tmp[MAXPATH] = '\0';
5010 if((fn = strrchr(tmp, C_FILESEP)) != NULL)
5011 *fn = '\0';
5012 else
5013 tmp[0] = '\0';
5014 if(tmp[0])
5015 save_hist(dir_hist, tmp, 0, NULL);
5018 if(opts && opts != optsarg)
5019 fs_give((void **) &opts);
5021 return(ret);
5025 /*----------------------------------------------------------------------
5026 parse the config'd upload/download command
5028 Args: cmd -- buffer to return command fit for shellin'
5029 prefix --
5030 cfg_str --
5031 fname -- file name to build into the command
5033 Returns: pointer to cmd_str buffer or NULL on real bad error
5035 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
5036 cfg_str is written to standard out right before a successful
5037 return of this function. The call immediately following this
5038 function darn well better be the shell exec...
5039 ----*/
5040 char *
5041 build_updown_cmd(char *cmd, size_t cmdlen, char *prefix, char *cfg_str, char *fname)
5043 char *p;
5044 int fname_found = 0;
5046 if(prefix && *prefix){
5047 /* loop thru replacing all occurrences of _FILE_ */
5048 p = strncpy(cmd, prefix, cmdlen);
5049 cmd[cmdlen-1] = '\0';
5050 while((p = strstr(p, "_FILE_")))
5051 rplstr(p, cmdlen-(p-cmd), 6, fname);
5053 fputs(cmd, stdout);
5056 /* loop thru replacing all occurrences of _FILE_ */
5057 p = strncpy(cmd, cfg_str, cmdlen);
5058 cmd[cmdlen-1] = '\0';
5059 while((p = strstr(p, "_FILE_"))){
5060 rplstr(p, cmdlen-(p-cmd), 6, fname);
5061 fname_found = 1;
5064 if(!fname_found)
5065 snprintf(cmd+strlen(cmd), cmdlen-strlen(cmd), " %s", fname);
5067 cmd[cmdlen-1] = '\0';
5069 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
5070 cmd ? cmd : "?"));
5071 return(cmd);
5075 /*----------------------------------------------------------------------
5076 Write a berzerk format message delimiter using the given putc function
5078 Args: e -- envelope of message to write
5079 pc -- function to use
5081 Returns: TRUE if we could write it, FALSE if there was a problem
5083 NOTE: follows delimiter with OS-dependent newline
5084 ----*/
5086 bezerk_delimiter(ENVELOPE *env, MESSAGECACHE *mc, gf_io_t pc, int leading_newline)
5088 MESSAGECACHE telt;
5089 time_t when;
5090 char *p;
5092 /* write "[\n]From mailbox[@host] " */
5093 if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1)
5094 && gf_puts("From ", pc)
5095 && gf_puts((env && env->from) ? env->from->mailbox
5096 : "the-concourse-on-high", pc)
5097 && gf_puts((env && env->from && env->from->host) ? "@" : "", pc)
5098 && gf_puts((env && env->from && env->from->host) ? env->from->host
5099 : "", pc)
5100 && (*pc)(' ')))
5101 return(0);
5103 if(mc && mc->valid)
5104 when = mail_longdate(mc);
5105 else if(env && env->date && env->date[0]
5106 && mail_parse_date(&telt,env->date))
5107 when = mail_longdate(&telt);
5108 else
5109 when = time(0);
5111 p = ctime(&when);
5113 while(p && *p && *p != '\n') /* write date */
5114 if(!(*pc)(*p++))
5115 return(0);
5117 if(!gf_puts(NEWLINE, pc)) /* write terminating newline */
5118 return(0);
5120 return(1);
5124 /*----------------------------------------------------------------------
5125 Execute command to jump to a given message number
5127 Args: qline -- Line to ask question on
5129 Result: returns true if the use selected a new message, false otherwise
5131 ----*/
5132 long
5133 jump_to(MSGNO_S *msgmap, int qline, UCS first_num, SCROLL_S *sparms, CmdWhere in_index)
5135 char jump_num_string[80], *j, prompt[70];
5136 HelpType help;
5137 int rc;
5138 static ESCKEY_S jump_to_key[] = { {0, 0, NULL, NULL},
5139 /* TRANSLATORS: go to First Message */
5140 {ctrl('Y'), 10, "^Y", N_("First Msg")},
5141 {ctrl('V'), 11, "^V", N_("Last Msg")},
5142 {-1, 0, NULL, NULL} };
5144 dprint((4, "\n - jump_to -\n"));
5146 #ifdef DEBUG
5147 if(sparms && sparms->jump_is_debug)
5148 return(get_level(qline, first_num, sparms));
5149 #endif
5151 if(!any_messages(msgmap, NULL, "to Jump to"))
5152 return(0L);
5154 if(first_num && first_num < 0x80 && isdigit((unsigned char) first_num)){
5155 jump_num_string[0] = first_num;
5156 jump_num_string[1] = '\0';
5158 else
5159 jump_num_string[0] = '\0';
5161 if(mn_total_cur(msgmap) > 1L){
5162 snprintf(prompt, sizeof(prompt), "Unselect %s msgs in favor of number to be entered",
5163 comatose(mn_total_cur(msgmap)));
5164 prompt[sizeof(prompt)-1] = '\0';
5165 if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n')
5166 return(0L);
5169 snprintf(prompt, sizeof(prompt), "%s number to jump to : ", in_index == ThrdIndx
5170 ? "Thread"
5171 : "Message");
5172 prompt[sizeof(prompt)-1] = '\0';
5174 help = NO_HELP;
5175 while(1){
5176 int flags = OE_APPEND_CURRENT;
5178 rc = optionally_enter(jump_num_string, qline, 0,
5179 sizeof(jump_num_string), prompt,
5180 jump_to_key, help, &flags);
5181 if(rc == 3){
5182 help = help == NO_HELP
5183 ? (in_index == ThrdIndx ? h_oe_jump_thd : h_oe_jump)
5184 : NO_HELP;
5185 continue;
5187 else if(rc == 10 || rc == 11){
5188 char warning[100];
5189 long closest;
5191 closest = closest_jump_target(rc == 10 ? 1L
5192 : ((in_index == ThrdIndx)
5193 ? msgmap->max_thrdno
5194 : mn_get_total(msgmap)),
5195 ps_global->mail_stream,
5196 msgmap, 0,
5197 in_index, warning, sizeof(warning));
5198 /* ignore warning */
5199 return(closest);
5203 * If we take out the *jump_num_string nonempty test in this if
5204 * then the closest_jump_target routine will offer a jump to the
5205 * last message. However, it is slow because you have to wait for
5206 * the status message and it is annoying for people who hit J command
5207 * by mistake and just want to hit return to do nothing, like has
5208 * always worked. So the test is there for now. Hubert 2002-08-19
5210 * Jumping to first/last message is now possible through ^Y/^V
5211 * commands above. jpf 2002-08-21
5212 * (and through "end" hubert 2006-07-07)
5214 if(rc == 0 && *jump_num_string != '\0'){
5215 removing_leading_and_trailing_white_space(jump_num_string);
5216 for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++)
5219 if(*j != '\0'){
5220 if(!strucmp("end", j))
5221 return((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap));
5223 q_status_message(SM_ORDER | SM_DING, 2, 2,
5224 _("Invalid number entered. Use only digits 0-9"));
5225 jump_num_string[0] = '\0';
5227 else{
5228 char warning[100];
5229 long closest, jump_num;
5231 if(*jump_num_string)
5232 jump_num = atol(jump_num_string);
5233 else
5234 jump_num = -1L;
5236 warning[0] = '\0';
5237 closest = closest_jump_target(jump_num, ps_global->mail_stream,
5238 msgmap,
5239 *jump_num_string ? 0 : 1,
5240 in_index, warning, sizeof(warning));
5241 if(warning[0])
5242 q_status_message(SM_ORDER | SM_DING, 2, 2, warning);
5244 if(closest == jump_num)
5245 return(jump_num);
5247 if(closest == 0L)
5248 jump_num_string[0] = '\0';
5249 else
5250 strncpy(jump_num_string, long2string(closest),
5251 sizeof(jump_num_string));
5254 continue;
5257 if(rc != 4)
5258 break;
5261 return(0L);
5266 * cmd_delete_action - handle msgno advance and such after single message deletion
5268 char *
5269 cmd_delete_action(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
5271 int opts;
5272 long msgno;
5273 char *rv = NULL;
5275 msgno = mn_get_cur(msgmap);
5276 advance_cur_after_delete(state, state->mail_stream, msgmap, in_index);
5278 if(IS_NEWS(state->mail_stream)
5279 || ((state->context_current->use & CNTXT_INCMNG)
5280 && context_isambig(state->cur_folder))){
5282 opts = (NSF_TRUST_FLAGS | NSF_SKIP_CHID);
5283 if(in_index == View)
5284 opts &= ~NSF_SKIP_CHID;
5286 (void)next_sorted_flagged(F_UNDEL|F_UNSEEN, state->mail_stream, msgno, &opts);
5287 if(!(opts & NSF_FLAG_MATCH)){
5288 char nextfolder[MAXPATH];
5290 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
5291 nextfolder[sizeof(nextfolder)-1] = '\0';
5292 rv = next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
5293 state->context_current, NULL, NULL)
5294 ? ". Press TAB for next folder."
5295 : ". No more folders to TAB to.";
5299 return(rv);
5304 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5306 char *
5307 cmd_delete_index(struct pine *state, MSGNO_S *msgmap)
5309 return(cmd_delete_action(state, msgmap,MsgIndx));
5313 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5315 char *
5316 cmd_delete_view(struct pine *state, MSGNO_S *msgmap)
5318 return(cmd_delete_action(state, msgmap, View));
5322 void
5323 advance_cur_after_delete(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, CmdWhere in_index)
5325 long new_msgno, msgno;
5326 int opts;
5328 new_msgno = msgno = mn_get_cur(msgmap);
5329 opts = NSF_TRUST_FLAGS;
5331 if(F_ON(F_DEL_SKIPS_DEL, state)){
5333 if(THREADING() && sp_viewing_a_thread(stream))
5334 opts |= NSF_SKIP_CHID;
5336 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5338 else{
5339 mn_inc_cur(stream, msgmap,
5340 (in_index == View && THREADING()
5341 && sp_viewing_a_thread(stream))
5342 ? MH_THISTHD
5343 : (in_index == View)
5344 ? MH_ANYTHD : MH_NONE);
5345 new_msgno = mn_get_cur(msgmap);
5346 if(new_msgno != msgno)
5347 opts |= NSF_FLAG_MATCH;
5351 * Viewing_a_thread is the complicated case because we want to ignore
5352 * other threads at first and then look in other threads if we have to.
5353 * By ignoring other threads we also ignore collapsed partial threads
5354 * in our own thread.
5356 if(THREADING() && sp_viewing_a_thread(stream) && !(opts & NSF_FLAG_MATCH)){
5357 long rawno, orig_thrdno;
5358 PINETHRD_S *thrd, *topthrd = NULL;
5360 rawno = mn_m2raw(msgmap, msgno);
5361 thrd = fetch_thread(stream, rawno);
5362 if(thrd && thrd->top)
5363 topthrd = fetch_thread(stream, thrd->top);
5365 orig_thrdno = topthrd ? topthrd->thrdno : -1L;
5367 opts = NSF_TRUST_FLAGS;
5368 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5371 * If we got a match, new_msgno may be a message in
5372 * a different thread from the one we are viewing, or it could be
5373 * in a collapsed part of this thread.
5375 if(opts & NSF_FLAG_MATCH){
5376 int ret;
5377 char pmt[128];
5379 topthrd = NULL;
5380 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
5381 if(thrd && thrd->top)
5382 topthrd = fetch_thread(stream, thrd->top);
5385 * If this match is in the same thread we're already in
5386 * then we're done, else we have to ask the user and maybe
5387 * switch threads.
5389 if(!(orig_thrdno > 0L && topthrd
5390 && topthrd->thrdno == orig_thrdno)){
5392 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
5393 if(in_index == View)
5394 snprintf(pmt, sizeof(pmt),
5395 "View message in thread number %.10s",
5396 topthrd ? comatose(topthrd->thrdno) : "?");
5397 else
5398 snprintf(pmt, sizeof(pmt), "View thread number %.10s",
5399 topthrd ? comatose(topthrd->thrdno) : "?");
5401 ret = want_to(pmt, 'y', 'x', NO_HELP, WT_NORM);
5403 else
5404 ret = 'y';
5406 if(ret == 'y'){
5407 unview_thread(state, stream, msgmap);
5408 mn_set_cur(msgmap, new_msgno);
5409 if(THRD_AUTO_VIEW()
5410 && (count_lflags_in_thread(stream, topthrd, msgmap,
5411 MN_NONE) == 1)
5412 && view_thread(state, stream, msgmap, 1)){
5413 if(current_index_state)
5414 msgmap->top_after_thrd = current_index_state->msg_at_top;
5416 state->view_skipped_index = 1;
5417 state->next_screen = mail_view_screen;
5419 else{
5420 view_thread(state, stream, msgmap, 1);
5421 if(current_index_state)
5422 msgmap->top_after_thrd = current_index_state->msg_at_top;
5424 state->next_screen = SCREEN_FUN_NULL;
5427 else
5428 new_msgno = msgno; /* stick with original */
5433 mn_set_cur(msgmap, new_msgno);
5434 if(in_index != View)
5435 adjust_cur_to_visible(stream, msgmap);
5439 #ifdef DEBUG
5440 long
5441 get_level(int qline, UCS first_num, SCROLL_S *sparms)
5443 char debug_num_string[80], *j, prompt[70];
5444 HelpType help;
5445 int rc;
5446 long debug_num;
5448 if(first_num && first_num < 0x80 && isdigit((unsigned char)first_num)){
5449 debug_num_string[0] = first_num;
5450 debug_num_string[1] = '\0';
5451 debug_num = atol(debug_num_string);
5452 *(int *)(sparms->proc.data.p) = debug_num;
5453 q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s",
5454 comatose(debug_num));
5455 return(1L);
5457 else
5458 debug_num_string[0] = '\0';
5460 snprintf(prompt, sizeof(prompt), "Show debug <= this level (0-%d) : ", MAX(debug, 9));
5461 prompt[sizeof(prompt)-1] = '\0';
5463 help = NO_HELP;
5464 while(1){
5465 int flags = OE_APPEND_CURRENT;
5467 rc = optionally_enter(debug_num_string, qline, 0,
5468 sizeof(debug_num_string), prompt,
5469 NULL, help, &flags);
5470 if(rc == 3){
5471 help = help == NO_HELP ? h_oe_debuglevel : NO_HELP;
5472 continue;
5475 if(rc == 0){
5476 removing_leading_and_trailing_white_space(debug_num_string);
5477 for(j=debug_num_string; isdigit((unsigned char)*j); j++)
5480 if(*j != '\0'){
5481 q_status_message(SM_ORDER | SM_DING, 2, 2,
5482 _("Invalid number entered. Use only digits 0-9"));
5483 debug_num_string[0] = '\0';
5485 else{
5486 debug_num = atol(debug_num_string);
5487 if(debug_num < 0)
5488 q_status_message(SM_ORDER | SM_DING, 2, 2,
5489 _("Number should be >= 0"));
5490 else if(debug_num > MAX(debug,9))
5491 q_status_message1(SM_ORDER | SM_DING, 2, 2,
5492 _("Maximum is %s"), comatose(MAX(debug,9)));
5493 else{
5494 *(int *)(sparms->proc.data.p) = debug_num;
5495 q_status_message1(SM_ORDER, 0, 3,
5496 "Show debug <= level %s",
5497 comatose(debug_num));
5498 return(1L);
5502 continue;
5505 if(rc != 4)
5506 break;
5509 return(0L);
5511 #endif /* DEBUG */
5515 * Returns the message number closest to target that isn't hidden.
5516 * Make warning at least 100 chars.
5517 * A return of 0 means there is no message to jump to.
5519 long
5520 closest_jump_target(long int target, MAILSTREAM *stream, MSGNO_S *msgmap, int no_target, CmdWhere in_index, char *warning, size_t warninglen)
5522 long i, start, closest = 0L;
5523 char buf[80];
5524 long maxnum;
5526 warning[0] = '\0';
5527 maxnum = (in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap);
5529 if(no_target){
5530 target = maxnum;
5531 start = 1L;
5532 snprintf(warning, warninglen, "No %s number entered, jump to end? ",
5533 (in_index == ThrdIndx) ? "thread" : "message");
5534 warning[warninglen-1] = '\0';
5536 else if(target < 1L)
5537 start = 1L - target;
5538 else if(target > maxnum)
5539 start = target - maxnum;
5540 else
5541 start = 1L;
5543 if(target > 0L && target <= maxnum)
5544 if(in_index == ThrdIndx
5545 || !msgline_hidden(stream, msgmap, target, 0))
5546 return(target);
5548 for(i = start; target+i <= maxnum || target-i > 0L; i++){
5550 if(target+i > 0L && target+i <= maxnum &&
5551 (in_index == ThrdIndx
5552 || !msgline_hidden(stream, msgmap, target+i, 0))){
5553 closest = target+i;
5554 break;
5557 if(target-i > 0L && target-i <= maxnum &&
5558 (in_index == ThrdIndx
5559 || !msgline_hidden(stream, msgmap, target-i, 0))){
5560 closest = target-i;
5561 break;
5565 strncpy(buf, long2string(closest), sizeof(buf));
5566 buf[sizeof(buf)-1] = '\0';
5568 if(closest == 0L)
5569 strncpy(warning, "Nothing to jump to", warninglen);
5570 else if(target < 1L)
5571 snprintf(warning, warninglen, "%s number (%s) must be at least %s",
5572 (in_index == ThrdIndx) ? "Thread" : "Message",
5573 long2string(target), buf);
5574 else if(target > maxnum)
5575 snprintf(warning, warninglen, "%s number (%s) may be no more than %s",
5576 (in_index == ThrdIndx) ? "Thread" : "Message",
5577 long2string(target), buf);
5578 else if(!no_target)
5579 snprintf(warning, warninglen,
5580 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5581 long2string(target), buf);
5583 warning[warninglen-1] = '\0';
5585 return(closest);
5589 /*----------------------------------------------------------------------
5590 Prompt for folder name to open, expand the name and return it
5592 Args: qline -- Screen line to prompt on
5593 allow_list -- if 1, allow ^T to bring up collection lister
5595 Result: returns the folder name or NULL
5596 pine structure mangled_footer flag is set
5597 may call the collection lister in which case mangled screen will be set
5599 This prompts the user for the folder to open, possibly calling up
5600 the collection lister if the user types ^T.
5601 ----------------------------------------------------------------------*/
5602 char *
5603 broach_folder(int qline, int allow_list, int *notrealinbox, CONTEXT_S **context)
5605 HelpType help;
5606 static char newfolder[MAILTMPLEN];
5607 char expanded[MAXPATH+1],
5608 prompt[MAX_SCREEN_COLS+1],
5609 *last_folder, *p;
5610 unsigned char *f1, *f2, *f3;
5611 static HISTORY_S *history = NULL;
5612 CONTEXT_S *tc, *tc2;
5613 ESCKEY_S ekey[9];
5614 int rc, r, ku = -1, n, flags, last_rc = 0, inbox, done = 0;
5617 * the idea is to provide a clue for the context the file name
5618 * will be saved in (if a non-imap names is typed), and to
5619 * only show the previous if it was also in the same context
5621 help = NO_HELP;
5622 *expanded = '\0';
5623 *newfolder = '\0';
5624 last_folder = NULL;
5625 if(notrealinbox)
5626 (*notrealinbox) = 1;
5628 init_hist(&history, HISTSIZE);
5630 tc = broach_get_folder(context ? *context : NULL, &inbox, NULL);
5632 /* set up extra command option keys */
5633 rc = 0;
5634 ekey[rc].ch = (allow_list) ? ctrl('T') : 0 ;
5635 ekey[rc].rval = (allow_list) ? 2 : 0;
5636 ekey[rc].name = (allow_list) ? "^T" : "";
5637 ekey[rc++].label = (allow_list) ? N_("ToFldrs") : "";
5639 if(ps_global->context_list->next){
5640 ekey[rc].ch = ctrl('P');
5641 ekey[rc].rval = 10;
5642 ekey[rc].name = "^P";
5643 ekey[rc++].label = N_("Prev Collection");
5645 ekey[rc].ch = ctrl('N');
5646 ekey[rc].rval = 11;
5647 ekey[rc].name = "^N";
5648 ekey[rc++].label = N_("Next Collection");
5651 ekey[rc].ch = ctrl('W');
5652 ekey[rc].rval = 17;
5653 ekey[rc].name = "^W";
5654 ekey[rc++].label = N_("INBOX");
5656 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
5657 ekey[rc].ch = TAB;
5658 ekey[rc].rval = 12;
5659 ekey[rc].name = "TAB";
5660 ekey[rc++].label = N_("Complete");
5663 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
5664 ekey[rc].ch = ctrl('X');
5665 ekey[rc].rval = 14;
5666 ekey[rc].name = "^X";
5667 ekey[rc++].label = N_("ListMatches");
5670 if(ps_global->context_list->next && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5671 ekey[rc].ch = KEY_UP;
5672 ekey[rc].rval = 10;
5673 ekey[rc].name = "";
5674 ekey[rc++].label = "";
5676 ekey[rc].ch = KEY_DOWN;
5677 ekey[rc].rval = 11;
5678 ekey[rc].name = "";
5679 ekey[rc++].label = "";
5681 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5682 ekey[rc].ch = KEY_UP;
5683 ekey[rc].rval = 30;
5684 ekey[rc].name = "";
5685 ku = rc;
5686 ekey[rc++].label = "";
5688 ekey[rc].ch = KEY_DOWN;
5689 ekey[rc].rval = 31;
5690 ekey[rc].name = "";
5691 ekey[rc++].label = "";
5694 ekey[rc].ch = -1;
5696 while(!done) {
5698 * Figure out next default value for this context. The idea
5699 * is that in each context the last folder opened is cached.
5700 * It's up to pick it out and display it. This is fine
5701 * and dandy if we've currently got the inbox open, BUT
5702 * if not, make the inbox the default the first time thru.
5704 if(!inbox){
5705 last_folder = ps_global->inbox_name;
5706 inbox = 1; /* pretend we're in inbox from here on out */
5708 else
5709 last_folder = (ps_global->last_unambig_folder[0])
5710 ? ps_global->last_unambig_folder
5711 : ((tc->last_folder[0]) ? tc->last_folder : NULL);
5713 if(last_folder){ /* MAXPATH + 1 = sizeof(expanded) */
5714 unsigned char *fname = folder_name_decoded((unsigned char *)last_folder);
5715 snprintf(expanded, sizeof(expanded), " [%.*s]", MAXPATH+1-5,
5716 fname ? (char *) fname : last_folder);
5717 if(fname) fs_give((void **)&fname);
5719 else
5720 *expanded = '\0';
5722 expanded[sizeof(expanded)-1] = '\0';
5724 /* only show collection number if more than one available */
5725 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5726 snprintf(prompt, sizeof(prompt), "GOTO %s in <%s> %.*s%s: ",
5727 NEWS_TEST(tc) ? "news group" : "folder",
5728 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5729 *expanded ? " " : "");
5730 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5731 snprintf(prompt, sizeof(prompt), "GOTO folder %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5732 *expanded ? " " : "");
5734 prompt[sizeof(prompt)-1] = '\0';
5736 if(utf8_width(prompt) > MAXPROMPT){
5737 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5738 snprintf(prompt, sizeof(prompt), "GOTO <%s> %.*s%s: ",
5739 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5740 *expanded ? " " : "");
5741 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5742 snprintf(prompt, sizeof(prompt), "GOTO %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5743 *expanded ? " " : "");
5745 prompt[sizeof(prompt)-1] = '\0';
5747 if(utf8_width(prompt) > MAXPROMPT){
5748 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5749 snprintf(prompt, sizeof(prompt), "<%s> %.*s%s: ",
5750 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5751 *expanded ? " " : "");
5752 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5753 snprintf(prompt, sizeof(prompt), "%.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5754 *expanded ? " " : "");
5756 prompt[sizeof(prompt)-1] = '\0';
5760 if(ku >= 0){
5761 if(items_in_hist(history) > 1){
5762 ekey[ku].name = HISTORY_UP_KEYNAME;
5763 ekey[ku].label = HISTORY_KEYLABEL;
5764 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
5765 ekey[ku+1].label = HISTORY_KEYLABEL;
5767 else{
5768 ekey[ku].name = "";
5769 ekey[ku].label = "";
5770 ekey[ku+1].name = "";
5771 ekey[ku+1].label = "";
5775 /* is there any other way to do this? The point is that we
5776 * are trying to hide mutf7 from the user, and use the utf8
5777 * equivalent. So we create a variable f to take place of
5778 * newfolder, including content and size. f2 is copy of f1
5779 * that has to freed. Sigh!
5781 f3 = (unsigned char *) cpystr(newfolder);
5782 f1 = fs_get(sizeof(newfolder));
5783 f2 = folder_name_decoded(f3);
5784 if(f3) fs_give((void **)&f3);
5785 strncpy((char *)f1, (char *)f2, sizeof(newfolder));
5786 f1[sizeof(newfolder)-1] = '\0';
5787 if(f2) fs_give((void **)&f2);
5789 flags = OE_APPEND_CURRENT;
5790 rc = optionally_enter((char *) f1, qline, 0, sizeof(newfolder),
5791 (char *) prompt, ekey, help, &flags);
5793 f2 = folder_name_encoded(f1);
5794 strncpy(newfolder, (char *)f2, sizeof(newfolder));
5795 if(f1) fs_give((void **)&f1);
5796 if(f2) fs_give((void **)&f2);
5798 ps_global->mangled_footer = 1;
5800 switch(rc){
5801 case -1 : /* o_e says error! */
5802 q_status_message(SM_ORDER | SM_DING, 3, 3,
5803 _("Error reading folder name"));
5804 return(NULL);
5806 case 0 : /* o_e says normal entry */
5807 removing_trailing_white_space(newfolder);
5808 removing_leading_white_space(newfolder);
5810 if(*newfolder){
5811 char *name, *fullname = NULL;
5812 int exists, breakout = 0;
5814 save_hist(history, newfolder, 0, tc);
5816 if(!(name = folder_is_nick(newfolder, FOLDERS(tc),
5817 FN_WHOLE_NAME)))
5818 name = newfolder;
5820 if(update_folder_spec(expanded, sizeof(expanded), name)){
5821 strncpy(name = newfolder, expanded, sizeof(newfolder));
5822 newfolder[sizeof(newfolder)-1] = '\0';
5825 exists = folder_name_exists(tc, name, &fullname);
5827 if(fullname){
5828 strncpy(name = newfolder, fullname, sizeof(newfolder));
5829 newfolder[sizeof(newfolder)-1] = '\0';
5830 fs_give((void **) &fullname);
5831 breakout = TRUE;
5835 * if we know the things a folder, open it.
5836 * else if we know its a directory, visit it.
5837 * else we're not sure (it either doesn't really
5838 * exist or its unLISTable) so try opening it anyway
5840 if(exists & FEX_ISFILE){
5841 done++;
5842 break;
5844 else if((exists & FEX_ISDIR)){
5845 if(breakout){
5846 CONTEXT_S *fake_context;
5847 char tmp[MAILTMPLEN];
5848 size_t l;
5850 strncpy(tmp, name, sizeof(tmp));
5851 tmp[sizeof(tmp)-2-1] = '\0';
5852 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
5853 if(l < sizeof(tmp)){
5854 tmp[l] = tc->dir->delim;
5855 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
5858 else
5859 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
5861 tmp[sizeof(tmp)-1] = '\0';
5863 fake_context = new_context(tmp, 0);
5864 newfolder[0] = '\0';
5865 done = display_folder_list(&fake_context, newfolder,
5866 1, folders_for_goto);
5867 free_context(&fake_context);
5868 break;
5870 else if(!(tc->use & CNTXT_INCMNG)){
5871 done = display_folder_list(&tc, newfolder,
5872 1, folders_for_goto);
5873 break;
5876 else if((exists & FEX_ERROR)){
5877 q_status_message1(SM_ORDER, 0, 3,
5878 _("Problem accessing folder \"%s\""),
5879 newfolder);
5880 return(NULL);
5882 else{
5883 done++;
5884 break;
5887 if(exists == FEX_ERROR)
5888 q_status_message1(SM_ORDER, 0, 3,
5889 _("Problem accessing folder \"%s\""),
5890 newfolder);
5891 else if(tc->use & CNTXT_INCMNG)
5892 q_status_message1(SM_ORDER, 0, 3,
5893 _("Can't find Incoming Folder: %s"),
5894 newfolder);
5895 else if(context_isambig(newfolder))
5896 q_status_message2(SM_ORDER, 0, 3,
5897 _("Can't find folder \"%s\" in %s"),
5898 newfolder, (void *) tc->nickname);
5899 else
5900 q_status_message1(SM_ORDER, 0, 3,
5901 _("Can't find folder \"%s\""),
5902 newfolder);
5904 return(NULL);
5906 else if(last_folder){
5907 if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX
5908 && !strucmp(last_folder, ps_global->inbox_name)
5909 && tc == ((ps_global->context_list->use & CNTXT_INCMNG)
5910 ? ps_global->context_list->next : ps_global->context_list)){
5911 if(notrealinbox)
5912 (*notrealinbox) = 0;
5914 tc = ps_global->context_list;
5917 strncpy(newfolder, last_folder, sizeof(newfolder));
5918 newfolder[sizeof(newfolder)-1] = '\0';
5919 save_hist(history, newfolder, 0, tc);
5920 done++;
5921 break;
5923 /* fall thru like they cancelled */
5925 case 1 : /* o_e says user cancel */
5926 cmd_cancelled("Open folder");
5927 return(NULL);
5929 case 2 : /* o_e says user wants list */
5930 r = display_folder_list(&tc, newfolder, 0, folders_for_goto);
5931 if(r)
5932 done++;
5934 break;
5936 case 3 : /* o_e says user wants help */
5937 help = help == NO_HELP ? h_oe_broach : NO_HELP;
5938 break;
5940 case 4 : /* redraw */
5941 break;
5943 case 10 : /* Previous collection */
5944 tc2 = ps_global->context_list;
5945 while(tc2->next && tc2->next != tc)
5946 tc2 = tc2->next;
5948 tc = tc2;
5949 break;
5951 case 11 : /* Next collection */
5952 tc = (tc->next) ? tc->next : ps_global->context_list;
5953 break;
5955 case 12 : /* file name completion */
5956 if(!folder_complete(tc, newfolder, sizeof(newfolder), &n)){
5957 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
5958 r = display_folder_list(&tc, newfolder, 1,folders_for_goto);
5959 if(r)
5960 done++; /* bingo! */
5961 else
5962 rc = 0; /* burn last_rc */
5964 else
5965 Writechar(BELL, 0);
5968 break;
5970 case 14 : /* file name completion */
5971 r = display_folder_list(&tc, newfolder, 2, folders_for_goto);
5972 if(r)
5973 done++; /* bingo! */
5974 else
5975 rc = 0; /* burn last_rc */
5977 break;
5979 case 17 : /* GoTo INBOX */
5980 done++;
5981 strncpy(newfolder, ps_global->inbox_name, sizeof(newfolder)-1);
5982 newfolder[sizeof(newfolder)-1] = '\0';
5983 if(notrealinbox)
5984 (*notrealinbox) = 0;
5986 tc = ps_global->context_list;
5987 save_hist(history, newfolder, 0, tc);
5989 break;
5991 case 30 :
5992 if((p = get_prev_hist(history, newfolder, 0, tc)) != NULL){
5993 strncpy(newfolder, p, sizeof(newfolder));
5994 newfolder[sizeof(newfolder)-1] = '\0';
5995 if(history->hist[history->curindex])
5996 tc = history->hist[history->curindex]->cntxt;
5998 else
5999 Writechar(BELL, 0);
6001 break;
6003 case 31 :
6004 if((p = get_next_hist(history, newfolder, 0, tc)) != NULL){
6005 strncpy(newfolder, p, sizeof(newfolder));
6006 newfolder[sizeof(newfolder)-1] = '\0';
6007 if(history->hist[history->curindex])
6008 tc = history->hist[history->curindex]->cntxt;
6010 else
6011 Writechar(BELL, 0);
6013 break;
6015 default :
6016 alpine_panic("Unhandled case");
6017 break;
6020 last_rc = rc;
6023 dprint((2, "broach folder, name entered \"%s\"\n",
6024 newfolder ? newfolder : "?"));
6026 /*-- Just check that we can expand this. It gets done for real later --*/
6027 strncpy(expanded, newfolder, sizeof(expanded));
6028 expanded[sizeof(expanded)-1] = '\0';
6030 if(!expand_foldername(expanded, sizeof(expanded))) {
6031 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
6032 expanded ? expanded : "?"));
6033 return(NULL);
6036 *context = tc;
6037 return(newfolder);
6041 /*----------------------------------------------------------------------
6042 Check to see if user wants to reopen dead stream.
6044 Args: ps --
6045 reopenp --
6047 Result: 1 if the folder was successfully updatedn
6048 0 if not necessary
6050 ----*/
6052 ask_mailbox_reopen(struct pine *ps, int *reopenp)
6054 if(((ps->mail_stream->dtb
6055 && ((ps->mail_stream->dtb->flags & DR_NONEWMAIL)
6056 || (ps->mail_stream->rdonly
6057 && ps->mail_stream->dtb->flags & DR_NONEWMAILRONLY)))
6058 && (ps->reopen_rule == REOPEN_ASK_ASK_Y
6059 || ps->reopen_rule == REOPEN_ASK_ASK_N
6060 || ps->reopen_rule == REOPEN_ASK_NO_Y
6061 || ps->reopen_rule == REOPEN_ASK_NO_N))
6062 || ((ps->mail_stream->dtb
6063 && ps->mail_stream->rdonly
6064 && !(ps->mail_stream->dtb->flags & DR_LOCAL))
6065 && (ps->reopen_rule == REOPEN_YES_ASK_Y
6066 || ps->reopen_rule == REOPEN_YES_ASK_N
6067 || ps->reopen_rule == REOPEN_ASK_ASK_Y
6068 || ps->reopen_rule == REOPEN_ASK_ASK_N))){
6069 int deefault;
6071 switch(ps->reopen_rule){
6072 case REOPEN_YES_ASK_Y:
6073 case REOPEN_ASK_ASK_Y:
6074 case REOPEN_ASK_NO_Y:
6075 deefault = 'y';
6076 break;
6078 default:
6079 deefault = 'n';
6080 break;
6083 switch(want_to("Re-open folder to check for new messages", deefault,
6084 'x', h_reopen_folder, WT_NORM)){
6085 case 'y':
6086 (*reopenp)++;
6087 break;
6089 case 'x':
6090 return(-1);
6094 return(0);
6099 /*----------------------------------------------------------------------
6100 Check to see if user input is in form of old c-client mailbox speck
6102 Args: old --
6103 new --
6105 Result: 1 if the folder was successfully updatedn
6106 0 if not necessary
6108 ----*/
6110 update_folder_spec(char *new, size_t newlen, char *old)
6112 char *p, *orignew;
6113 int nntp = 0;
6115 orignew = new;
6116 if(*(p = old) == '*') /* old form? */
6117 old++;
6119 if(*old == '{') /* copy host spec */
6121 switch(*new = *old++){
6122 case '\0' :
6123 return(FALSE);
6125 case '/' :
6126 if(!struncmp(old, "nntp", 4))
6127 nntp++;
6129 break;
6131 default :
6132 break;
6134 while(*new++ != '}' && (new-orignew) < newlen-1);
6136 if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){
6138 * OK, some heuristics here. If it looks like a newsgroup
6139 * then we plunk it into the #news namespace else we
6140 * assume that they're trying to get at a #public folder...
6142 for(p = old;
6143 *p && (isalnum((unsigned char) *p) || strindex(".-", *p));
6144 p++)
6147 sstrncpy(&new, (*p && !nntp) ? "#public/" : "#news.", newlen-(new-orignew));
6148 strncpy(new, old, newlen-(new-orignew));
6149 return(TRUE);
6152 orignew[newlen-1] = '\0';
6154 return(FALSE);
6158 /*----------------------------------------------------------------------
6159 Open the requested folder in the requested context
6161 Args: state -- usual pine state struct
6162 newfolder -- folder to open
6163 new_context -- folder context might live in
6164 stream -- candidate for recycling
6166 Result: New folder open or not (if error), and we're set to
6167 enter the index screen.
6168 ----*/
6169 void
6170 visit_folder(struct pine *state, char *newfolder, CONTEXT_S *new_context,
6171 MAILSTREAM *stream, long unsigned int flags)
6173 dprint((9, "visit_folder(%s, %s)\n",
6174 newfolder ? newfolder : "?",
6175 (new_context && new_context->context)
6176 ? new_context->context : "(NULL)"));
6178 if(ps_global && ps_global->ttyo){
6179 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
6180 ps_global->mangled_footer = 1;
6183 if(do_broach_folder(newfolder, new_context, stream ? &stream : NULL,
6184 flags) >= 0
6185 || !sp_flagged(state->mail_stream, SP_LOCKED))
6186 state->next_screen = mail_index_screen;
6187 else
6188 state->next_screen = folder_screen;
6192 /*----------------------------------------------------------------------
6193 Move read messages from folder if listed in archive
6195 Args:
6197 ----*/
6199 read_msg_prompt(long int n, char *f)
6201 char buf[MAX_SCREEN_COLS+1];
6203 snprintf(buf, sizeof(buf), "Save the %ld read message%s in \"%s\"", n, plural(n), f);
6204 buf[sizeof(buf)-1] = '\0';
6205 return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y');
6209 /*----------------------------------------------------------------------
6210 Print current message[s] or folder index
6212 Args: state -- pointer to struct holding a bunch of pine state
6213 msgmap -- table mapping msg nums to c-client sequence nums
6214 aopt -- aggregate options
6215 in_index -- boolean indicating we're called from Index Screen
6217 Filters the original header and sends stuff to printer
6218 ---*/
6220 cmd_print(struct pine *state, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
6222 char prompt[250];
6223 long i, msgs, rawno;
6224 int next = 0, do_index = 0, rv = 0;
6225 ENVELOPE *e;
6226 BODY *b;
6227 MESSAGECACHE *mc;
6229 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
6230 return rv;
6232 msgs = mn_total_cur(msgmap);
6234 if((in_index != View) && F_ON(F_PRINT_INDEX, state)){
6235 char m[10];
6236 int ans;
6237 static ESCKEY_S prt_opts[] = {
6238 {'i', 'i', "I", N_("Index")},
6239 {'m', 'm', "M", NULL},
6240 {-1, 0, NULL, NULL}};
6242 if(in_index == ThrdIndx){
6243 /* TRANSLATORS: This is a question, Print Index ? */
6244 if(want_to(_("Print Index"), 'y', 'x', NO_HELP, WT_NORM) == 'y')
6245 ans = 'i';
6246 else
6247 ans = 'x';
6249 else{
6250 snprintf(m, sizeof(m), "Message%s", (msgs>1L) ? "s" : "");
6251 m[sizeof(m)-1] = '\0';
6252 prt_opts[1].label = m;
6253 snprintf(prompt, sizeof(prompt), "Print %sFolder Index or %s %s? ",
6254 (aopt & MCMD_AGG_2) ? "thread " : MCMD_ISAGG(aopt) ? "selected " : "",
6255 (aopt & MCMD_AGG_2) ? "thread" : MCMD_ISAGG(aopt) ? "selected" : "current", m);
6256 prompt[sizeof(prompt)-1] = '\0';
6258 ans = radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x',
6259 NO_HELP, RB_NORM|RB_SEQ_SENSITIVE);
6262 switch(ans){
6263 case 'x' :
6264 cmd_cancelled("Print");
6265 if(MCMD_ISAGG(aopt))
6266 restore_selected(msgmap);
6268 return rv;
6270 case 'i':
6271 do_index = 1;
6272 break;
6274 default :
6275 case 'm':
6276 break;
6280 if(do_index)
6281 snprintf(prompt, sizeof(prompt), "%sFolder Index",
6282 (aopt & MCMD_AGG_2) ? "Thread " : MCMD_ISAGG(aopt) ? "Selected " : "");
6283 else if(msgs > 1L)
6284 snprintf(prompt, sizeof(prompt), "%s messages", long2string(msgs));
6285 else
6286 snprintf(prompt, sizeof(prompt), "Message %s", long2string(mn_get_cur(msgmap)));
6288 prompt[sizeof(prompt)-1] = '\0';
6290 if(open_printer(prompt) < 0){
6291 if(MCMD_ISAGG(aopt))
6292 restore_selected(msgmap);
6294 return rv;
6297 if(do_index){
6298 TITLE_S *tc;
6300 tc = format_titlebar();
6302 /* Print titlebar... */
6303 print_text1("%s\n\n", tc ? tc->titlebar_line : "");
6304 /* then all the index members... */
6305 if(!print_index(state, msgmap, MCMD_ISAGG(aopt)))
6306 q_status_message(SM_ORDER | SM_DING, 3, 3,
6307 _("Error printing folder index"));
6308 else
6309 rv++;
6311 else{
6312 rv++;
6313 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){
6314 if(next && F_ON(F_AGG_PRINT_FF, state))
6315 if(!print_char(FORMFEED)){
6316 rv = 0;
6317 break;
6320 if(!(state->mail_stream
6321 && (rawno = mn_m2raw(msgmap, i)) > 0L
6322 && rawno <= state->mail_stream->nmsgs
6323 && (mc = mail_elt(state->mail_stream, rawno))
6324 && mc->valid))
6325 mc = NULL;
6327 if(!(e=pine_mail_fetchstructure(state->mail_stream,
6328 mn_m2raw(msgmap,i),
6329 &b))
6330 || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global)
6331 && !bezerk_delimiter(e, mc, print_char, next))
6332 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
6333 e, b, NULL, FM_NEW_MESS | FM_NOINDENT,
6334 print_char)){
6335 q_status_message(SM_ORDER | SM_DING, 3, 3,
6336 _("Error printing message"));
6337 rv = 0;
6338 break;
6343 close_printer();
6345 if(MCMD_ISAGG(aopt))
6346 restore_selected(msgmap);
6348 return rv;
6352 /*----------------------------------------------------------------------
6353 Pipe message text
6355 Args: state -- various pine state bits
6356 msgmap -- Message number mapping table
6357 aopt -- option flags
6359 Filters the original header and sends stuff to specified command
6360 ---*/
6362 cmd_pipe(struct pine *state, MSGNO_S *msgmap, int aopt)
6364 ENVELOPE *e;
6365 MESSAGECACHE *mc;
6366 BODY *b;
6367 PIPE_S *syspipe;
6368 char *resultfilename = NULL, prompt[80], *p;
6369 int done = 0, rv = 0;
6370 gf_io_t pc;
6371 int fourlabel = -1, j = 0, next = 0, ku;
6372 int pipe_rv; /* rv of proc to separate from close_system_pipe rv */
6373 long i, rawno;
6374 unsigned flagsforhist = 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6375 static HISTORY_S *history = NULL;
6376 int capture = 1, raw = 0, delimit = 0, newpipe = 0;
6377 char pipe_command[MAXPATH];
6378 ESCKEY_S pipe_opt[8];
6380 if(ps_global->restricted){
6381 q_status_message(SM_ORDER | SM_DING, 0, 4,
6382 "Alpine demo can't pipe messages");
6383 return rv;
6385 else if(!any_messages(msgmap, NULL, "to Pipe"))
6386 return rv;
6388 pipe_command[0] = '\0';
6389 init_hist(&history, HISTSIZE);
6390 flagsforhist = (raw ? 0x8 : 0) +
6391 (delimit ? 0x4 : 0) +
6392 (newpipe ? 0x2 : 0) +
6393 (capture ? 0x1 : 0);
6394 if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){
6395 strncpy(pipe_command, p, sizeof(pipe_command));
6396 pipe_command[sizeof(pipe_command)-1] = '\0';
6397 if(history->hist[history->curindex]){
6398 flagsforhist = history->hist[history->curindex]->flags;
6399 raw = (flagsforhist & 0x8) ? 1 : 0;
6400 delimit = (flagsforhist & 0x4) ? 1 : 0;
6401 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6402 capture = (flagsforhist & 0x1) ? 1 : 0;
6406 pipe_opt[j].ch = 0;
6407 pipe_opt[j].rval = 0;
6408 pipe_opt[j].name = "";
6409 pipe_opt[j++].label = "";
6411 pipe_opt[j].ch = ctrl('W');
6412 pipe_opt[j].rval = 10;
6413 pipe_opt[j].name = "^W";
6414 pipe_opt[j++].label = NULL;
6416 pipe_opt[j].ch = ctrl('Y');
6417 pipe_opt[j].rval = 11;
6418 pipe_opt[j].name = "^Y";
6419 pipe_opt[j++].label = NULL;
6421 pipe_opt[j].ch = ctrl('R');
6422 pipe_opt[j].rval = 12;
6423 pipe_opt[j].name = "^R";
6424 pipe_opt[j++].label = NULL;
6426 if(MCMD_ISAGG(aopt)){
6427 if(!pseudo_selected(state->mail_stream, msgmap))
6428 return rv;
6429 else{
6430 fourlabel = j;
6431 pipe_opt[j].ch = ctrl('T');
6432 pipe_opt[j].rval = 13;
6433 pipe_opt[j].name = "^T";
6434 pipe_opt[j++].label = NULL;
6438 pipe_opt[j].ch = KEY_UP;
6439 pipe_opt[j].rval = 30;
6440 pipe_opt[j].name = "";
6441 ku = j;
6442 pipe_opt[j++].label = "";
6444 pipe_opt[j].ch = KEY_DOWN;
6445 pipe_opt[j].rval = 31;
6446 pipe_opt[j].name = "";
6447 pipe_opt[j++].label = "";
6449 pipe_opt[j].ch = -1;
6451 while (!done) {
6452 int flags;
6454 snprintf(prompt, sizeof(prompt), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6455 raw ? "RAW " : "",
6456 MCMD_ISAGG(aopt) ? "s" : " ",
6457 MCMD_ISAGG(aopt) ? "" : comatose(mn_get_cur(msgmap)),
6458 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? "(" : "",
6459 capture ? "" : "uncaptured",
6460 (!capture && delimit) ? "," : "",
6461 delimit ? "delimited" : "",
6462 ((!capture || delimit) && newpipe && MCMD_ISAGG(aopt)) ? "," : "",
6463 (newpipe && MCMD_ISAGG(aopt)) ? "new pipe" : "",
6464 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? ") " : "");
6465 prompt[sizeof(prompt)-1] = '\0';
6466 pipe_opt[1].label = raw ? N_("Shown Text") : N_("Raw Text");
6467 pipe_opt[2].label = capture ? N_("Free Output") : N_("Capture Output");
6468 pipe_opt[3].label = delimit ? N_("No Delimiter") : N_("With Delimiter");
6469 if(fourlabel > 0)
6470 pipe_opt[fourlabel].label = newpipe ? N_("To Same Pipe") : N_("To Individual Pipes");
6474 * 2 is really 1 because there will be one real entry and
6475 * one entry of "" because of the get_prev_hist above.
6477 if(items_in_hist(history) > 2){
6478 pipe_opt[ku].name = HISTORY_UP_KEYNAME;
6479 pipe_opt[ku].label = HISTORY_KEYLABEL;
6480 pipe_opt[ku+1].name = HISTORY_DOWN_KEYNAME;
6481 pipe_opt[ku+1].label = HISTORY_KEYLABEL;
6483 else{
6484 pipe_opt[ku].name = "";
6485 pipe_opt[ku].label = "";
6486 pipe_opt[ku+1].name = "";
6487 pipe_opt[ku+1].label = "";
6490 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
6491 switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0,
6492 sizeof(pipe_command), prompt,
6493 pipe_opt, NO_HELP, &flags)){
6494 case -1 :
6495 q_status_message(SM_ORDER | SM_DING, 3, 4,
6496 _("Internal problem encountered"));
6497 done++;
6498 break;
6500 case 10 : /* flip raw bit */
6501 raw = !raw;
6502 break;
6504 case 11 : /* flip capture bit */
6505 capture = !capture;
6506 break;
6508 case 12 : /* flip delimit bit */
6509 delimit = !delimit;
6510 break;
6512 case 13 : /* flip newpipe bit */
6513 newpipe = !newpipe;
6514 break;
6516 case 30 :
6517 flagsforhist = (raw ? 0x8 : 0) +
6518 (delimit ? 0x4 : 0) +
6519 (newpipe ? 0x2 : 0) +
6520 (capture ? 0x1 : 0);
6521 if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6522 strncpy(pipe_command, p, sizeof(pipe_command));
6523 pipe_command[sizeof(pipe_command)-1] = '\0';
6524 if(history->hist[history->curindex]){
6525 flagsforhist = history->hist[history->curindex]->flags;
6526 raw = (flagsforhist & 0x8) ? 1 : 0;
6527 delimit = (flagsforhist & 0x4) ? 1 : 0;
6528 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6529 capture = (flagsforhist & 0x1) ? 1 : 0;
6532 else
6533 Writechar(BELL, 0);
6535 break;
6537 case 31 :
6538 flagsforhist = (raw ? 0x8 : 0) +
6539 (delimit ? 0x4 : 0) +
6540 (newpipe ? 0x2 : 0) +
6541 (capture ? 0x1 : 0);
6542 if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6543 strncpy(pipe_command, p, sizeof(pipe_command));
6544 pipe_command[sizeof(pipe_command)-1] = '\0';
6545 if(history->hist[history->curindex]){
6546 flagsforhist = history->hist[history->curindex]->flags;
6547 raw = (flagsforhist & 0x8) ? 1 : 0;
6548 delimit = (flagsforhist & 0x4) ? 1 : 0;
6549 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6550 capture = (flagsforhist & 0x1) ? 1 : 0;
6553 else
6554 Writechar(BELL, 0);
6556 break;
6558 case 0 :
6559 if(pipe_command[0]){
6561 flagsforhist = (raw ? 0x8 : 0) +
6562 (delimit ? 0x4 : 0) +
6563 (newpipe ? 0x2 : 0) +
6564 (capture ? 0x1 : 0);
6565 save_hist(history, pipe_command, flagsforhist, NULL);
6567 flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
6568 flags |= (raw ? PIPE_RAW : 0);
6569 if(!capture){
6570 #ifndef _WINDOWS
6571 ClearScreen();
6572 fflush(stdout);
6573 clear_cursor_pos();
6574 ps_global->mangled_screen = 1;
6575 ps_global->in_init_seq = 1;
6576 #endif
6577 flags |= PIPE_RESET;
6580 if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command,
6581 (flags & PIPE_RESET)
6582 ? NULL
6583 : &resultfilename,
6584 flags, &pc)))
6585 done++;
6587 for(i = mn_first_cur(msgmap);
6588 i > 0L && !done;
6589 i = mn_next_cur(msgmap)){
6590 e = pine_mail_fetchstructure(ps_global->mail_stream,
6591 mn_m2raw(msgmap, i), &b);
6592 if(!(state->mail_stream
6593 && (rawno = mn_m2raw(msgmap, i)) > 0L
6594 && rawno <= state->mail_stream->nmsgs
6595 && (mc = mail_elt(state->mail_stream, rawno))
6596 && mc->valid))
6597 mc = NULL;
6599 if((newpipe
6600 && !(syspipe = cmd_pipe_open(pipe_command,
6601 (flags & PIPE_RESET)
6602 ? NULL
6603 : &resultfilename,
6604 flags, &pc)))
6605 || (delimit && !bezerk_delimiter(e, mc, pc, next++)))
6606 done++;
6608 if(!done){
6609 if(raw){
6610 char *pipe_err;
6612 prime_raw_pipe_getc(ps_global->mail_stream,
6613 mn_m2raw(msgmap, i), -1L, 0L);
6614 gf_filter_init();
6615 gf_link_filter(gf_nvtnl_local, NULL);
6616 if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){
6617 q_status_message1(SM_ORDER|SM_DING,
6618 3, 3,
6619 _("Internal Error: %s"),
6620 pipe_err);
6621 done++;
6624 else if(!format_message(mn_m2raw(msgmap, i), e, b,
6625 NULL, FM_NEW_MESS | FM_NOWRAP, pc))
6626 done++;
6629 if(newpipe)
6630 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6631 done++;
6634 if(!capture)
6635 ps_global->in_init_seq = 0;
6637 if(!newpipe)
6638 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6639 done++;
6640 if(done) /* say we had a problem */
6641 q_status_message(SM_ORDER | SM_DING, 3, 3,
6642 _("Error piping message"));
6643 else if(resultfilename){
6644 rv++;
6645 /* only display if no error */
6646 display_output_file(resultfilename, "PIPE MESSAGE",
6647 NULL, DOF_EMPTY);
6648 fs_give((void **)&resultfilename);
6650 else{
6651 rv++;
6652 q_status_message(SM_ORDER, 0, 2, _("Pipe command completed"));
6655 done++;
6656 break;
6658 /* else fall thru as if cancelled */
6660 case 1 :
6661 cmd_cancelled("Pipe command");
6662 done++;
6663 break;
6665 case 3 :
6666 helper(h_common_pipe, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE);
6667 ps_global->mangled_screen = 1;
6668 break;
6670 case 2 : /* no place to escape to */
6671 case 4 : /* can't suspend */
6672 default :
6673 break;
6677 ps_global->mangled_footer = 1;
6678 if(MCMD_ISAGG(aopt))
6679 restore_selected(msgmap);
6681 return rv;
6685 /*----------------------------------------------------------------------
6686 Screen to offer list management commands contained in message
6688 Args: state -- pointer to struct holding a bunch of pine state
6689 msgmap -- table mapping msg nums to c-client sequence nums
6690 aopt -- aggregate options
6692 Result:
6694 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6695 ----*/
6696 void
6697 rfc2369_display(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno)
6699 int winner = 0;
6700 char *h, *hdrs[MLCMD_COUNT + 1];
6701 long index_no = mn_raw2m(msgmap, msgno);
6702 RFC2369_S data[MLCMD_COUNT];
6704 /* for each header field */
6705 if((h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))) != NULL){
6706 memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT);
6707 if(rfc2369_parse_fields(h, &data[0])){
6708 STORE_S *explain;
6710 if((explain = list_mgmt_text(data, index_no)) != NULL){
6711 list_mgmt_screen(explain);
6712 ps_global->mangled_screen = 1;
6713 so_give(&explain);
6714 winner++;
6718 fs_give((void **) &h);
6721 if(!winner)
6722 q_status_message1(SM_ORDER, 0, 3,
6723 "Message %s contains no list management information",
6724 comatose(index_no));
6728 STORE_S *
6729 list_mgmt_text(RFC2369_S *data, long int msgno)
6731 STORE_S *store;
6732 int i, j, n, fields = 0;
6733 static char *rfc2369_intro1 =
6734 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6735 static char *rfc2369_intro2[] = {
6736 N_(" has information associated with it "),
6737 N_("that explains how to participate in an email list. An "),
6738 N_("email list is represented by a single email address that "),
6739 N_("users sharing a common interest can send messages to (known "),
6740 N_("as posting) which are then redistributed to all members "),
6741 N_("of the list (sometimes after review by a moderator)."),
6742 N_("<P>List participation commands in this message include:"),
6743 NULL
6746 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6748 /* Insert introductory text */
6749 so_puts(store, rfc2369_intro1);
6751 so_puts(store, comatose(msgno));
6753 for(i = 0; rfc2369_intro2[i]; i++)
6754 so_puts(store, _(rfc2369_intro2[i]));
6756 so_puts(store, "<P>");
6757 for(i = 0; i < MLCMD_COUNT; i++)
6758 if(data[i].data[0].value
6759 || data[i].data[0].comment
6760 || data[i].data[0].error){
6761 if(!fields++)
6762 so_puts(store, "<UL>");
6764 so_puts(store, "<LI>");
6765 so_puts(store,
6766 (n = (data[i].data[1].value || data[i].data[1].comment))
6767 ? "Methods to "
6768 : "A method to ");
6770 so_puts(store, data[i].field.description);
6771 so_puts(store, ". ");
6773 if(n)
6774 so_puts(store, "<OL>");
6776 for(j = 0;
6777 j < MLCMD_MAXDATA
6778 && (data[i].data[j].comment
6779 || data[i].data[j].value
6780 || data[i].data[j].error);
6781 j++){
6783 so_puts(store, n ? "<P><LI>" : "<P>");
6785 if(data[i].data[j].comment){
6786 so_puts(store,
6787 _("With the provided comment:<P><BLOCKQUOTE>"));
6788 so_puts(store, data[i].data[j].comment);
6789 so_puts(store, "</BLOCKQUOTE><P>");
6792 if(data[i].data[j].value){
6793 if(i == MLCMD_POST
6794 && !strucmp(data[i].data[j].value, "NO")){
6795 so_puts(store,
6796 _("Posting is <EM>not</EM> allowed on this list"));
6798 else{
6799 so_puts(store, "Select <A HREF=\"");
6800 so_puts(store, data[i].data[j].value);
6801 so_puts(store, "\">HERE</A> to ");
6802 so_puts(store, (data[i].field.action)
6803 ? data[i].field.action
6804 : "try it");
6807 so_puts(store, ".");
6810 if(data[i].data[j].error){
6811 so_puts(store, "<P>Unfortunately, Alpine can not offer");
6812 so_puts(store, " to take direct action based upon it");
6813 so_puts(store, " because it was improperly formatted.");
6814 so_puts(store, " The unrecognized data associated with");
6815 so_puts(store, " the \"");
6816 so_puts(store, data[i].field.name);
6817 so_puts(store, "\" header field was:<P><BLOCKQUOTE>");
6818 so_puts(store, data[i].data[j].error);
6819 so_puts(store, "</BLOCKQUOTE>");
6822 so_puts(store, "<P>");
6825 if(n)
6826 so_puts(store, "</OL>");
6829 if(fields)
6830 so_puts(store, "</UL>");
6832 so_puts(store, "</BODY></HTML>");
6835 return(store);
6839 void
6840 list_mgmt_screen(STORE_S *html)
6842 int cmd = MC_NONE;
6843 long offset = 0L;
6844 char *error = NULL;
6845 STORE_S *store;
6846 HANDLE_S *handles = NULL;
6847 gf_io_t gc, pc;
6850 so_seek(html, 0L, 0);
6851 gf_set_so_readc(&gc, html);
6853 init_handles(&handles);
6855 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6856 gf_set_so_writec(&pc, store);
6857 gf_filter_init();
6859 gf_link_filter(gf_html2plain,
6860 gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols,
6861 non_messageview_margin(), &handles, NULL, 0));
6863 error = gf_pipe(gc, pc);
6865 gf_clear_so_writec(store);
6867 if(!error){
6868 SCROLL_S sargs;
6870 memset(&sargs, 0, sizeof(SCROLL_S));
6871 sargs.text.text = so_text(store);
6872 sargs.text.src = CharStar;
6873 sargs.text.desc = "list commands";
6874 sargs.text.handles = handles;
6875 if(offset){
6876 sargs.start.on = Offset;
6877 sargs.start.loc.offset = offset;
6880 sargs.bar.title = _("MAIL LIST COMMANDS");
6881 sargs.bar.style = MessageNumber;
6882 sargs.resize_exit = 1;
6883 sargs.help.text = h_special_list_commands;
6884 sargs.help.title = _("HELP FOR LIST COMMANDS");
6885 sargs.keys.menu = &listmgr_keymenu;
6886 setbitmap(sargs.keys.bitmap);
6887 if(!handles){
6888 clrbitn(LM_TRY_KEY, sargs.keys.bitmap);
6889 clrbitn(LM_PREV_KEY, sargs.keys.bitmap);
6890 clrbitn(LM_NEXT_KEY, sargs.keys.bitmap);
6893 cmd = scrolltool(&sargs);
6894 offset = sargs.start.loc.offset;
6897 so_give(&store);
6900 free_handles(&handles);
6901 gf_clear_so_readc(html);
6903 while(cmd == MC_RESIZE);
6907 /*----------------------------------------------------------------------
6908 Prompt the user for the type of select desired
6910 NOTE: any and all functions that successfully exit the second
6911 switch() statement below (currently "select_*() functions"),
6912 *MUST* update the folder's MESSAGECACHE element's "searched"
6913 bits to reflect the search result. Functions using
6914 mail_search() get this for free, the others must update 'em
6915 by hand.
6917 Returns -1 if canceled without changing selection
6918 0 if selection may have changed
6919 ----*/
6921 aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index)
6923 long i, diff, old_tot, msgno, raw;
6924 int q = 0, rv = 0, narrow = 0, hidden, ret = -1;
6925 ESCKEY_S *sel_opts;
6926 MESSAGECACHE *mc;
6927 SEARCHSET *limitsrch = NULL;
6928 PINETHRD_S *thrd;
6929 extern MAILSTREAM *mm_search_stream;
6930 extern long mm_search_count;
6932 hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
6933 mm_search_stream = state->mail_stream;
6934 mm_search_count = 0L;
6936 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
6937 sel_opts = THRD_INDX() ? sel_opts6 : sel_opts5;
6938 else
6939 sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2;
6941 if(THREADING()){
6942 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH;
6944 else{
6945 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream)){
6946 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_XGMEXT_CH;
6947 sel_opts[SEL_OPTS_THREAD].rval = sel_opts[SEL_OPTS_XGMEXT].rval;
6948 sel_opts[SEL_OPTS_THREAD].name = sel_opts[SEL_OPTS_XGMEXT].name;
6949 sel_opts[SEL_OPTS_THREAD].label = sel_opts[SEL_OPTS_XGMEXT].label;
6950 sel_opts[SEL_OPTS_XGMEXT].ch = -1;
6952 else
6953 sel_opts[SEL_OPTS_THREAD].ch = -1;
6956 if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){
6957 if(THRD_INDX()){
6958 i = 0;
6959 thrd = fetch_thread(state->mail_stream,
6960 mn_m2raw(msgmap, mn_get_cur(msgmap)));
6961 /* check if whole thread is selected or not */
6962 if(thrd &&
6963 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT)
6965 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE))
6966 i = 1;
6968 sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd");
6970 else{
6971 i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap),
6972 MN_SLCT);
6973 sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur");
6976 sel_opts += 2; /* disable extra options */
6977 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
6978 q = double_radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6979 RB_NORM);
6980 else
6981 q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6982 RB_NORM);
6983 switch(q){
6984 case 'f' : /* flip selection */
6985 msgno = 0L;
6986 for(i = 1L; i <= mn_get_total(msgmap); i++){
6987 ret = 0;
6988 q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT);
6989 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q);
6990 if(hidden){
6991 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q);
6992 if(!msgno && q)
6993 mn_reset_cur(msgmap, msgno = i);
6997 return(ret);
6999 case 'n' : /* narrow selection */
7000 narrow++;
7001 case 'b' : /* broaden selection */
7002 q = 0; /* offer criteria prompt */
7003 break;
7005 case 'c' : /* Un/Select Current */
7006 case 'a' : /* Unselect All */
7007 case 'x' : /* cancel */
7008 break;
7010 default :
7011 q_status_message(SM_ORDER | SM_DING, 3, 3,
7012 "Unsupported Select option");
7013 return(ret);
7017 if(!q){
7018 while(1){
7019 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
7020 q = double_radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP,
7021 RB_NORM);
7022 else
7023 q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP,
7024 RB_NORM|RB_RET_HELP);
7026 if(q == 3){
7027 helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE);
7028 ps_global->mangled_screen = 1;
7030 else
7031 break;
7036 * The purpose of this is to add the appropriate searchset to the
7037 * search so that the search can be limited to only looking at what
7038 * it needs to look at. That is, if we are narrowing then we only need
7039 * to look at messages which are already selected, and if we are
7040 * broadening, then we only need to look at messages which are not
7041 * yet selected. This routine will work whether or not
7042 * limiting_searchset properly limits the search set. In particular,
7043 * the searchset returned by limiting_searchset may include messages
7044 * which really shouldn't be included. We do that because a too-large
7045 * searchset will break some IMAP servers. It is even possible that it
7046 * becomes inefficient to send the whole set. If the select function
7047 * frees limitsrch, it should be sure to set it to NULL so we won't
7048 * try freeing it again here.
7050 limitsrch = limiting_searchset(state->mail_stream, narrow);
7053 * NOTE: See note about MESSAGECACHE "searched" bits above!
7055 switch(q){
7056 case 'x': /* cancel */
7057 cmd_cancelled("Select command");
7058 return(ret);
7060 case 'c' : /* select/unselect current */
7061 (void) select_by_current(state, msgmap, in_index);
7062 ret = 0;
7063 return(ret);
7065 case 'a' : /* select/unselect all */
7066 msgno = any_lflagged(msgmap, MN_SLCT);
7067 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
7068 ret = 0;
7069 agg_select_all(state->mail_stream, msgmap, &diff,
7070 any_lflagged(msgmap, MN_SLCT) <= 0L);
7071 q_status_message4(SM_ORDER,0,2,
7072 "%s%s message%s %sselected",
7073 msgno ? "" : "All ", comatose(diff),
7074 plural(diff), msgno ? "UN" : "");
7075 return(ret);
7077 case 'n' : /* Select by Number */
7078 ret = 0;
7079 if(THRD_INDX())
7080 rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch);
7081 else
7082 rv = select_by_number(state->mail_stream, msgmap, &limitsrch);
7084 break;
7086 case 'd' : /* Select by Date */
7087 ret = 0;
7088 rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap),
7089 &limitsrch);
7090 break;
7092 case 't' : /* Text */
7093 ret = 0;
7094 rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap),
7095 &limitsrch);
7096 break;
7098 case 'z' : /* Size */
7099 ret = 0;
7100 rv = select_by_size(state->mail_stream, &limitsrch);
7101 break;
7103 case 's' : /* Status */
7104 ret = 0;
7105 rv = select_by_status(state->mail_stream, &limitsrch);
7106 break;
7108 case 'k' : /* Keyword */
7109 ret = 0;
7110 rv = select_by_keyword(state->mail_stream, &limitsrch);
7111 break;
7113 case 'r' : /* Rule */
7114 ret = 0;
7115 rv = select_by_rule(state->mail_stream, &limitsrch);
7116 break;
7118 case 'h' : /* Thread */
7119 ret = 0;
7120 rv = select_by_thread(state->mail_stream, msgmap, &limitsrch);
7121 break;
7123 case 'g' : /* X-GM-EXT-1 */
7124 ret = 0;
7125 rv = select_by_gm_content(state->mail_stream, msgmap, mn_get_cur(msgmap), &limitsrch);
7126 break;
7128 default :
7129 q_status_message(SM_ORDER | SM_DING, 3, 3,
7130 "Unsupported Select option");
7131 return(ret);
7134 if(limitsrch)
7135 mail_free_searchset(&limitsrch);
7137 if(rv) /* bad return value.. */
7138 return(ret); /* error already displayed */
7140 if(narrow) /* make sure something was selected */
7141 for(i = 1L; i <= mn_get_total(msgmap); i++)
7142 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7143 && raw <= state->mail_stream->nmsgs
7144 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7145 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
7146 break;
7147 else
7148 mm_search_count--;
7151 diff = 0L;
7152 if(mm_search_count){
7154 * loop thru all the messages, adjusting local flag bits
7155 * based on their "searched" bit...
7157 for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++)
7158 if(narrow){
7159 /* turning OFF selectedness if the "searched" bit isn't lit. */
7160 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7161 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7162 && raw <= state->mail_stream->nmsgs
7163 && (mc = mail_elt(state->mail_stream, raw))
7164 && !mc->searched){
7165 diff--;
7166 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
7167 if(hidden)
7168 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
7170 /* adjust current message in case we unselect and hide it */
7171 else if(msgno < mn_get_cur(msgmap)
7172 && (!THRD_INDX()
7173 || !get_lflag(state->mail_stream, msgmap,
7174 i, MN_CHID)))
7175 msgno = i;
7178 else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7179 && raw <= state->mail_stream->nmsgs
7180 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7181 /* turn ON selectedness if "searched" bit is lit. */
7182 if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7183 diff++;
7184 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
7185 if(hidden)
7186 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
7190 /* if we're zoomed and the current message was unselected */
7191 if(narrow && msgno
7192 && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE))
7193 mn_reset_cur(msgmap, msgno);
7196 if(!diff){
7197 if(narrow)
7198 q_status_message4(SM_ORDER, 3, 3,
7199 "%s. %s message%s remain%s selected.",
7200 mm_search_count
7201 ? "No change resulted"
7202 : "No messages in intersection",
7203 comatose(old_tot), plural(old_tot),
7204 (old_tot == 1L) ? "s" : "");
7205 else if(old_tot)
7206 q_status_message(SM_ORDER, 3, 3,
7207 _("No change resulted. Matching messages already selected."));
7208 else
7209 q_status_message1(SM_ORDER | SM_DING, 3, 3,
7210 _("Select failed. No %smessages selected."),
7211 old_tot ? _("additional ") : "");
7213 else if(old_tot){
7214 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
7215 "Select matched %ld message%s. %s %smessage%s %sselected.",
7216 (diff > 0) ? diff : old_tot + diff,
7217 plural((diff > 0) ? diff : old_tot + diff),
7218 comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7219 (diff > 0) ? "total " : "",
7220 plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7221 (diff > 0) ? "" : "UN");
7222 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
7223 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
7225 else
7226 q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"),
7227 comatose(diff), plural(diff));
7229 return(ret);
7233 /*----------------------------------------------------------------------
7234 Toggle the state of the current message
7236 Args: state -- pointer pine's state variables
7237 msgmap -- message collection to operate on
7238 in_index -- in the message index view
7239 Returns: TRUE if current marked selected, FALSE otw
7240 ----*/
7242 select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
7244 long cur;
7245 int all_selected = 0;
7246 unsigned long was, tot, rawno;
7247 PINETHRD_S *thrd;
7249 cur = mn_get_cur(msgmap);
7251 if(THRD_INDX()){
7252 thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur));
7253 if(!thrd)
7254 return 0;
7256 was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT);
7257 tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE);
7258 if(was == tot)
7259 all_selected++;
7261 if(all_selected){
7262 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0);
7263 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7264 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1);
7266 * See if there's anything left to zoom on. If so,
7267 * pick an adjacent one for highlighting, else make
7268 * sure nothing is left hidden...
7270 if(any_lflagged(msgmap, MN_SLCT)){
7271 mn_inc_cur(state->mail_stream, msgmap,
7272 (in_index == View && THREADING()
7273 && sp_viewing_a_thread(state->mail_stream))
7274 ? MH_THISTHD
7275 : (in_index == View)
7276 ? MH_ANYTHD : MH_NONE);
7277 if(mn_get_cur(msgmap) == cur)
7278 mn_dec_cur(state->mail_stream, msgmap,
7279 (in_index == View && THREADING()
7280 && sp_viewing_a_thread(state->mail_stream))
7281 ? MH_THISTHD
7282 : (in_index == View)
7283 ? MH_ANYTHD : MH_NONE);
7285 else /* clear all hidden flags */
7286 (void) unzoom_index(state, state->mail_stream, msgmap);
7289 else
7290 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1);
7292 q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected",
7293 comatose(all_selected ? was : tot-was),
7294 plural(all_selected ? was : tot-was),
7295 all_selected ? "UN" : "");
7297 /* collapsed thread */
7298 else if(THREADING()
7299 && ((rawno = mn_m2raw(msgmap, cur)) != 0L)
7300 && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL)
7301 && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){
7303 * This doesn't work quite the same as the colon command works, but
7304 * it is arguably doing the correct thing. The difference is
7305 * that aggregate_select will zoom after selecting back where it
7306 * was called from, but selecting a thread with colon won't zoom.
7307 * Maybe it makes sense to zoom after a select but not after a colon
7308 * command even though they are very similar.
7310 thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state));
7312 else{
7313 if((all_selected =
7314 get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */
7315 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0);
7316 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7317 set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1);
7319 * See if there's anything left to zoom on. If so,
7320 * pick an adjacent one for highlighting, else make
7321 * sure nothing is left hidden...
7323 if(any_lflagged(msgmap, MN_SLCT)){
7324 mn_inc_cur(state->mail_stream, msgmap,
7325 (in_index == View && THREADING()
7326 && sp_viewing_a_thread(state->mail_stream))
7327 ? MH_THISTHD
7328 : (in_index == View)
7329 ? MH_ANYTHD : MH_NONE);
7330 if(mn_get_cur(msgmap) == cur)
7331 mn_dec_cur(state->mail_stream, msgmap,
7332 (in_index == View && THREADING()
7333 && sp_viewing_a_thread(state->mail_stream))
7334 ? MH_THISTHD
7335 : (in_index == View)
7336 ? MH_ANYTHD : MH_NONE);
7338 else /* clear all hidden flags */
7339 (void) unzoom_index(state, state->mail_stream, msgmap);
7342 else
7343 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1);
7345 q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected",
7346 long2string(cur), all_selected ? "UN" : "");
7350 return(!all_selected);
7354 /*----------------------------------------------------------------------
7355 Prompt the user for the command to perform on selected messages
7357 Args: state -- pointer pine's state variables
7358 msgmap -- message collection to operate on
7359 q_line -- line on display to write prompts
7360 Returns: 1 if the selected messages are suitably commanded,
7361 0 if the choice to pick the command was declined
7363 ----*/
7365 apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
7366 UCS preloadkeystroke, int flags, int q_line)
7368 int i = 8, /* number of static entries in sel_opts3 */
7369 rv = 0,
7370 cmd,
7371 we_cancel = 0,
7372 agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG;
7373 char prompt[80];
7374 PAT_STATE pstate;
7377 * To do this "right", we really ought to have access to the keymenu
7378 * here and change the typed command into a real command by running
7379 * it through menu_command. Then the switch below would be against
7380 * results from menu_command. If we did that we'd also pass the
7381 * results of menu_command in as preloadkeystroke instead of passing
7382 * the keystroke itself. But we don't have the keymenu handy,
7383 * so we have to fake it. The only complication that we run into
7384 * is that KEY_DEL is an escape sequence so we change a typed
7385 * KEY_DEL esc seq into the letter D.
7388 if(!preloadkeystroke){
7389 if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
7390 sel_opts3[i].ch = '*';
7391 sel_opts3[i].rval = '*';
7392 sel_opts3[i].name = "*";
7393 sel_opts3[i++].label = N_("Flag");
7396 if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
7397 sel_opts3[i].ch = '|';
7398 sel_opts3[i].rval = '|';
7399 sel_opts3[i].name = "|";
7400 sel_opts3[i++].label = N_("Pipe");
7403 if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */
7404 sel_opts3[i].ch = 'b';
7405 sel_opts3[i].rval = 'b';
7406 sel_opts3[i].name = "B";
7407 sel_opts3[i++].label = N_("Bounce");
7410 if(flags & AC_FROM_THREAD){
7411 if(flags & (AC_COLL | AC_EXPN)){
7412 sel_opts3[i].ch = '/';
7413 sel_opts3[i].rval = '/';
7414 sel_opts3[i].name = "/";
7415 sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse")
7416 : N_("Expand");
7419 sel_opts3[i].ch = ';';
7420 sel_opts3[i].rval = ';';
7421 sel_opts3[i].name = ";";
7422 if(flags & AC_UNSEL)
7423 sel_opts3[i++].label = N_("UnSelect");
7424 else
7425 sel_opts3[i++].label = N_("Select");
7428 if(F_ON(F_ENABLE_PRYNT, state)){ /* this one is invisible */
7429 sel_opts3[i].ch = 'y';
7430 sel_opts3[i].rval = '%';
7431 sel_opts3[i].name = "";
7432 sel_opts3[i++].label = "";
7435 if(!is_imap_stream(stream) || LEVELUIDPLUS(stream)){ /* expunge selected messages */
7436 sel_opts3[i].ch = 'x';
7437 sel_opts3[i].rval = 'x';
7438 sel_opts3[i].name = "X";
7439 sel_opts3[i++].label = N_("Expunge");
7442 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7443 sel_opts3[i].ch = '#';
7444 sel_opts3[i].rval = '#';
7445 sel_opts3[i].name = "#";
7446 sel_opts3[i++].label = N_("Set Role");
7449 sel_opts3[i].ch = KEY_DEL; /* also invisible */
7450 sel_opts3[i].rval = 'd';
7451 sel_opts3[i].name = "";
7452 sel_opts3[i++].label = "";
7454 sel_opts3[i].ch = -1;
7456 snprintf(prompt, sizeof(prompt), "%s command : ",
7457 (flags & AC_FROM_THREAD) ? "THREAD" : "APPLY");
7458 prompt[sizeof(prompt)-1] = '\0';
7459 cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'c', NO_HELP,
7460 RB_SEQ_SENSITIVE);
7461 if(isupper(cmd))
7462 cmd = tolower(cmd);
7464 else{
7465 if(preloadkeystroke == KEY_DEL)
7466 cmd = 'd';
7467 else{
7468 if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke))
7469 cmd = tolower((int) preloadkeystroke);
7470 else
7471 cmd = (int) preloadkeystroke; /* shouldn't happen */
7475 switch(cmd){
7476 case 'd' : /* delete */
7477 we_cancel = busy_cue(NULL, NULL, 1);
7478 rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */
7479 if(we_cancel)
7480 cancel_busy_cue(0);
7481 break;
7483 case 'u' : /* undelete */
7484 we_cancel = busy_cue(NULL, NULL, 1);
7485 rv = cmd_undelete(state, msgmap, agg);
7486 if(we_cancel)
7487 cancel_busy_cue(0);
7488 break;
7490 case 'r' : /* reply */
7491 rv = cmd_reply(state, msgmap, agg, NULL);
7492 break;
7494 case 'f' : /* Forward */
7495 rv = cmd_forward(state, msgmap, agg, NULL);
7496 break;
7498 case '%' : /* print */
7499 rv = cmd_print(state, msgmap, agg, MsgIndx);
7500 break;
7502 case 't' : /* take address */
7503 rv = cmd_take_addr(state, msgmap, agg);
7504 break;
7506 case 's' : /* save */
7507 rv = cmd_save(state, stream, msgmap, agg, MsgIndx);
7508 break;
7510 case 'e' : /* export */
7511 rv = cmd_export(state, msgmap, q_line, agg);
7512 break;
7514 case '|' : /* pipe */
7515 rv = cmd_pipe(state, msgmap, agg);
7516 break;
7518 case '*' : /* flag */
7519 we_cancel = busy_cue(NULL, NULL, 1);
7520 rv = cmd_flag(state, msgmap, agg);
7521 if(we_cancel)
7522 cancel_busy_cue(0);
7523 break;
7525 case 'b' : /* bounce */
7526 rv = cmd_bounce(state, msgmap, agg, NULL);
7527 break;
7529 case '/' :
7530 collapse_or_expand(state, stream, msgmap,
7531 F_ON(F_SLASH_COLL_ENTIRE, ps_global)
7532 ? 0L
7533 : mn_get_cur(msgmap));
7534 break;
7536 case ':' :
7537 select_thread_stmp(state, stream, msgmap);
7538 break;
7540 case 'x' : /* Expunge */
7541 rv = cmd_expunge(state, stream, msgmap, agg);
7542 break;
7544 case 'c' : /* cancel */
7545 cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command"
7546 : "Apply command");
7547 break;
7549 case '#' :
7550 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7551 static ESCKEY_S choose_role[] = {
7552 {'r', 'r', "R", N_("Reply")},
7553 {'f', 'f', "F", N_("Forward")},
7554 {'b', 'b', "B", N_("Bounce")},
7555 {-1, 0, NULL, NULL}
7557 int action;
7558 ACTION_S *role = NULL;
7560 action = radio_buttons(_("Reply, Forward or Bounce using a role? "),
7561 -FOOTER_ROWS(state), choose_role,
7562 'r', 'x', h_role_aggregate, RB_NORM);
7563 if(action == 'r' || action == 'f' || action == 'b'){
7564 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
7566 redraw = state->redrawer;
7567 state->redrawer = NULL;
7568 prev_screen = state->prev_screen;
7569 role = NULL;
7570 state->next_screen = SCREEN_FUN_NULL;
7572 if(role_select_screen(state, &role,
7573 action == 'f' ? MC_FORWARD :
7574 action == 'r' ? MC_REPLY :
7575 action == 'b' ? MC_BOUNCE : 0) < 0){
7576 cmd_cancelled(action == 'f' ? _("Forward") :
7577 action == 'r' ? _("Reply") : _("Bounce"));
7578 state->next_screen = prev_screen;
7579 state->redrawer = redraw;
7580 state->mangled_screen = 1;
7582 else{
7583 if(role)
7584 role = combine_inherited_role(role);
7585 else{
7586 role = (ACTION_S *) fs_get(sizeof(*role));
7587 memset((void *) role, 0, sizeof(*role));
7588 role->nick = cpystr("Default Role");
7591 state->redrawer = NULL;
7592 switch(action){
7593 case 'r':
7594 (void) cmd_reply(state, msgmap, agg, role);
7595 break;
7597 case 'f':
7598 (void) cmd_forward(state, msgmap, agg, role);
7599 break;
7601 case 'b':
7602 (void) cmd_bounce(state, msgmap, agg, role);
7603 break;
7606 if(role)
7607 free_action(&role);
7609 if(redraw)
7610 (*redraw)();
7612 state->next_screen = prev_screen;
7613 state->redrawer = redraw;
7614 state->mangled_screen = 1;
7618 break;
7620 case 'z' : /* default */
7621 q_status_message(SM_INFO, 0, 2,
7622 "Cancelled, there is no default command");
7623 break;
7625 default:
7626 break;
7629 return(rv);
7634 * Select by message number ranges.
7635 * Sets searched bits in mail_elts
7637 * Args limitsrch -- limit search to this searchset
7639 * Returns 0 on success.
7642 select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
7644 int r, end, cur;
7645 long n1, n2, raw;
7646 char number1[16], number2[16], numbers[80], *p, *t;
7647 HelpType help;
7648 MESSAGECACHE *mc;
7650 numbers[0] = '\0';
7651 ps_global->mangled_footer = 1;
7652 help = NO_HELP;
7653 while(1){
7654 int flags = OE_APPEND_CURRENT;
7656 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7657 sizeof(numbers), _(select_num), NULL, help, &flags);
7658 if(r == 4)
7659 continue;
7661 if(r == 3){
7662 help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
7663 continue;
7666 for(t = p = numbers; *p ; p++) /* strip whitespace */
7667 if(!isspace((unsigned char)*p))
7668 *t++ = *p;
7670 *t = '\0';
7672 if(r == 1 || numbers[0] == '\0'){
7673 cmd_cancelled("Selection by number");
7674 return(1);
7676 else
7677 break;
7680 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7681 if((mc = mail_elt(stream, n1)) != NULL)
7682 mc->searched = 0; /* clear searched bits */
7684 for(p = numbers; *p ; p++){
7685 t = number1;
7686 while(*p && isdigit((unsigned char)*p))
7687 *t++ = *p++;
7689 *t = '\0';
7691 end = cur = 0;
7692 if(number1[0] == '\0'){
7693 if(*p == '-'){
7694 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7695 _("Invalid number range, missing number before \"-\": %s"),
7696 numbers);
7697 return(1);
7699 else if(!strucmp("end", p)){
7700 end = 1;
7701 p += strlen("end");
7703 else if(!strucmp("$", p)){
7704 end = 1;
7705 p++;
7707 else if(*p == '.'){
7708 cur = 1;
7709 p++;
7711 else{
7712 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7713 _("Invalid message number: %s"), numbers);
7714 return(1);
7718 if(end)
7719 n1 = mn_get_total(msgmap);
7720 else if(cur)
7721 n1 = mn_get_cur(msgmap);
7722 else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
7723 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7724 _("\"%s\" out of message number range"),
7725 long2string(n1));
7726 return(1);
7729 t = number2;
7730 if(*p == '-'){
7731 while(*++p && isdigit((unsigned char)*p))
7732 *t++ = *p;
7734 *t = '\0';
7736 end = cur = 0;
7737 if(number2[0] == '\0'){
7738 if(!strucmp("end", p)){
7739 end = 1;
7740 p += strlen("end");
7742 else if(!strucmp(p, "$")){
7743 end = 1;
7744 p++;
7746 else if(*p == '.'){
7747 cur = 1;
7748 p++;
7750 else{
7751 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7752 _("Invalid number range, missing number after \"-\": %s"),
7753 numbers);
7754 return(1);
7758 if(end)
7759 n2 = mn_get_total(msgmap);
7760 else if(cur)
7761 n2 = mn_get_cur(msgmap);
7762 else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){
7763 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7764 _("\"%s\" out of message number range"),
7765 long2string(n2));
7766 return(1);
7769 if(n2 <= n1){
7770 char t[20];
7772 strncpy(t, long2string(n1), sizeof(t));
7773 t[sizeof(t)-1] = '\0';
7774 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7775 _("Invalid reverse message number range: %s-%s"),
7776 t, long2string(n2));
7777 return(1);
7780 for(;n1 <= n2; n1++){
7781 raw = mn_m2raw(msgmap, n1);
7782 if(raw > 0L
7783 && (!(limitsrch && *limitsrch)
7784 || in_searchset(*limitsrch, (unsigned long) raw)))
7785 mm_searched(stream, raw);
7788 else{
7789 raw = mn_m2raw(msgmap, n1);
7790 if(raw > 0L
7791 && (!(limitsrch && *limitsrch)
7792 || in_searchset(*limitsrch, (unsigned long) raw)))
7793 mm_searched(stream, raw);
7796 if(*p == '\0')
7797 break;
7800 return(0);
7805 * Select by thread number ranges.
7806 * Sets searched bits in mail_elts
7808 * Args limitsrch -- limit search to this searchset
7810 * Returns 0 on success.
7813 select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset)
7815 int r, end, cur;
7816 long n1, n2;
7817 char number1[16], number2[16], numbers[80], *p, *t;
7818 HelpType help;
7819 PINETHRD_S *thrd = NULL, *th;
7820 MESSAGECACHE *mc;
7822 numbers[0] = '\0';
7823 ps_global->mangled_footer = 1;
7824 help = NO_HELP;
7825 while(1){
7826 int flags = OE_APPEND_CURRENT;
7828 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7829 sizeof(numbers), _(select_num), NULL, help, &flags);
7830 if(r == 4)
7831 continue;
7833 if(r == 3){
7834 help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP;
7835 continue;
7838 for(t = p = numbers; *p ; p++) /* strip whitespace */
7839 if(!isspace((unsigned char)*p))
7840 *t++ = *p;
7842 *t = '\0';
7844 if(r == 1 || numbers[0] == '\0'){
7845 cmd_cancelled("Selection by number");
7846 return(1);
7848 else
7849 break;
7852 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7853 if((mc = mail_elt(stream, n1)) != NULL)
7854 mc->searched = 0; /* clear searched bits */
7856 for(p = numbers; *p ; p++){
7857 t = number1;
7858 while(*p && isdigit((unsigned char)*p))
7859 *t++ = *p++;
7861 *t = '\0';
7863 end = cur = 0;
7864 if(number1[0] == '\0'){
7865 if(*p == '-'){
7866 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7867 _("Invalid number range, missing number before \"-\": %s"),
7868 numbers);
7869 return(1);
7871 else if(!strucmp("end", p)){
7872 end = 1;
7873 p += strlen("end");
7875 else if(!strucmp(p, "$")){
7876 end = 1;
7877 p++;
7879 else if(*p == '.'){
7880 cur = 1;
7881 p++;
7883 else{
7884 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7885 _("Invalid thread number: %s"), numbers);
7886 return(1);
7890 if(end)
7891 n1 = msgmap->max_thrdno;
7892 else if(cur){
7893 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7894 n1 = th->thrdno;
7896 else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){
7897 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7898 _("\"%s\" out of thread number range"),
7899 long2string(n1));
7900 return(1);
7903 t = number2;
7904 if(*p == '-'){
7906 while(*++p && isdigit((unsigned char)*p))
7907 *t++ = *p;
7909 *t = '\0';
7911 end = 0;
7912 if(number2[0] == '\0'){
7913 if(!strucmp("end", p)){
7914 end = 1;
7915 p += strlen("end");
7917 else if(!strucmp("$", p)){
7918 end = 1;
7919 p++;
7921 else if(*p == '.'){
7922 cur = 1;
7923 p++;
7925 else{
7926 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7927 _("Invalid number range, missing number after \"-\": %s"),
7928 numbers);
7929 return(1);
7933 if(end)
7934 n2 = msgmap->max_thrdno;
7935 else if(cur){
7936 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7937 n2 = th->thrdno;
7939 else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){
7940 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7941 _("\"%s\" out of thread number range"),
7942 long2string(n2));
7943 return(1);
7946 if(n2 <= n1){
7947 char t[20];
7949 strncpy(t, long2string(n1), sizeof(t));
7950 t[sizeof(t)-1] = '\0';
7951 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7952 _("Invalid reverse message number range: %s-%s"),
7953 t, long2string(n2));
7954 return(1);
7957 for(;n1 <= n2; n1++){
7958 thrd = find_thread_by_number(stream, msgmap, n1, thrd);
7960 if(thrd)
7961 set_search_bit_for_thread(stream, thrd, msgset);
7964 else{
7965 thrd = find_thread_by_number(stream, msgmap, n1, NULL);
7967 if(thrd)
7968 set_search_bit_for_thread(stream, thrd, msgset);
7971 if(*p == '\0')
7972 break;
7975 return(0);
7980 * Select by message dates.
7981 * Sets searched bits in mail_elts
7983 * Args limitsrch -- limit search to this searchset
7985 * Returns 0 on success.
7988 select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
7990 int r, we_cancel = 0, when = 0;
7991 char date[100], defdate[100], prompt[128];
7992 time_t seldate = time(0);
7993 struct tm *seldate_tm;
7994 SEARCHPGM *pgm;
7995 HelpType help;
7996 static struct _tense {
7997 char *preamble,
7998 *range,
7999 *scope;
8000 } tense[] = {
8001 {"were ", "SENT SINCE", " (inclusive)"},
8002 {"were ", "SENT BEFORE", " (exclusive)"},
8003 {"were ", "SENT ON", "" },
8004 {"", "ARRIVED SINCE", " (inclusive)"},
8005 {"", "ARRIVED BEFORE", " (exclusive)"},
8006 {"", "ARRIVED ON", "" }
8009 date[0] = '\0';
8010 ps_global->mangled_footer = 1;
8011 help = NO_HELP;
8014 * If talking to an old server, default to SINCE instead of
8015 * SENTSINCE, which was added later.
8017 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8018 when = 3;
8020 while(1){
8021 int flags = OE_APPEND_CURRENT;
8023 seldate_tm = localtime(&seldate);
8024 snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday,
8025 month_abbrev(seldate_tm->tm_mon + 1),
8026 seldate_tm->tm_year + 1900);
8027 defdate[sizeof(defdate)-1] = '\0';
8028 snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ",
8029 tense[when].preamble, tense[when].range,
8030 tense[when].scope, defdate);
8031 prompt[sizeof(prompt)-1] = '\0';
8032 r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date),
8033 prompt, sel_date_opt, help, &flags);
8034 switch (r){
8035 case 1 :
8036 cmd_cancelled("Selection by date");
8037 return(1);
8039 case 3 :
8040 help = (help == NO_HELP) ? h_select_date : NO_HELP;
8041 continue;
8043 case 4 :
8044 continue;
8046 case 11 :
8048 MESSAGECACHE *mc;
8049 long rawno;
8051 if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L
8052 && rawno <= stream->nmsgs
8053 && (mc = mail_elt(stream, rawno))){
8055 /* cache not filled in yet? */
8056 if(mc->day == 0){
8057 char seq[20];
8059 if(stream->dtb && stream->dtb->flags & DR_NEWS){
8060 strncpy(seq,
8061 ulong2string(mail_uid(stream, rawno)),
8062 sizeof(seq));
8063 seq[sizeof(seq)-1] = '\0';
8064 mail_fetch_overview(stream, seq, NULL);
8066 else{
8067 strncpy(seq, long2string(rawno),
8068 sizeof(seq));
8069 seq[sizeof(seq)-1] = '\0';
8070 mail_fetch_fast(stream, seq, 0L);
8074 /* mail_date returns fixed field width date */
8075 mail_date(date, mc);
8076 date[11] = '\0';
8080 continue;
8082 case 12 : /* set default to PREVIOUS day */
8083 seldate -= 86400;
8084 continue;
8086 case 13 : /* set default to NEXT day */
8087 seldate += 86400;
8088 continue;
8090 case 14 :
8091 when = (when+1) % (sizeof(tense) / sizeof(struct _tense));
8092 continue;
8094 default:
8095 break;
8098 removing_leading_white_space(date);
8099 removing_trailing_white_space(date);
8100 if(!*date){
8101 strncpy(date, defdate, sizeof(date));
8102 date[sizeof(date)-1] = '\0';
8105 break;
8108 if((pgm = mail_newsearchpgm()) != NULL){
8109 MESSAGECACHE elt;
8110 short converted_date;
8112 if(mail_parse_date(&elt, (unsigned char *) date)){
8113 converted_date = mail_shortdate(elt.year, elt.month, elt.day);
8115 switch(when){
8116 case 0:
8117 pgm->sentsince = converted_date;
8118 break;
8119 case 1:
8120 pgm->sentbefore = converted_date;
8121 break;
8122 case 2:
8123 pgm->senton = converted_date;
8124 break;
8125 case 3:
8126 pgm->since = converted_date;
8127 break;
8128 case 4:
8129 pgm->before = converted_date;
8130 break;
8131 case 5:
8132 pgm->on = converted_date;
8133 break;
8136 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8138 if(ps_global && ps_global->ttyo){
8139 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8140 ps_global->mangled_footer = 1;
8143 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8145 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8147 if(we_cancel)
8148 cancel_busy_cue(0);
8150 /* we know this was freed in mail_search, let caller know */
8151 if(limitsrch)
8152 *limitsrch = NULL;
8154 else{
8155 mail_free_searchpgm(&pgm);
8156 q_status_message1(SM_ORDER, 3, 3,
8157 _("Invalid date entered: %s"), date);
8158 return(1);
8162 return(0);
8166 * Select by searching in message headers or body
8167 * using the x-gm-ext-1 capaility. This function
8168 * reads the input from the user and passes it to
8169 * the server directly. We need a x-gm-ext-1 variable
8170 * in the search pgm to carry this information to the
8171 * server.
8173 * Sets searched bits in mail_elts
8175 * Args limitsrch -- limit search to this searchset
8177 * Returns 0 on success.
8180 select_by_gm_content(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8182 int r, we_cancel = 0, rv;
8183 char tmp[128];
8184 char namehdr[MAILTMPLEN];
8185 ESCKEY_S ekey[8];
8186 HelpType help;
8188 ps_global->mangled_footer = 1;
8189 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8191 strncpy(tmp, sel_x_gm_ext, sizeof(tmp)-1);
8192 tmp[sizeof(tmp)-1] = '\0';
8194 namehdr[0] = '\0';
8196 help = NO_HELP;
8197 while (1){
8198 int flags = OE_APPEND_CURRENT;
8200 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8201 sizeof(namehdr), tmp, ekey, help, &flags);
8203 if(r == 4)
8204 continue;
8206 if(r == 3){
8207 help = (help == NO_HELP) ? h_select_by_gm_content : NO_HELP;
8208 continue;
8211 if (r == 1){
8212 cmd_cancelled("Selection by content");
8213 return(1);
8216 removing_leading_white_space(namehdr);
8218 if ((namehdr[0] != '\0')
8219 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8220 removing_trailing_white_space(namehdr);
8222 if (namehdr[0] != '\0')
8223 break;
8226 if(ps_global && ps_global->ttyo){
8227 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8228 ps_global->mangled_footer = 1;
8231 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8233 rv = agg_text_select(stream, msgmap, 'g', namehdr, 0, 0, NULL, "utf-8", limitsrch);
8234 if(we_cancel)
8235 cancel_busy_cue(0);
8237 return(rv);
8241 * Select by searching in message headers or body.
8242 * Sets searched bits in mail_elts
8244 * Args limitsrch -- limit search to this searchset
8246 * Returns 0 on success.
8249 select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8251 int r, ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
8252 int not = 0, me = 0;
8253 char sstring[80], savedsstring[80], tmp[128];
8254 char *p, *sval = NULL;
8255 char buftmp[MAILTMPLEN], namehdr[80];
8256 ESCKEY_S ekey[8];
8257 ENVELOPE *env = NULL;
8258 HelpType help;
8259 unsigned flagsforhist = 0;
8260 static HISTORY_S *history = NULL;
8261 static char *recip = "RECIPIENTS";
8262 static char *partic = "PARTICIPANTS";
8263 static char *match_me = N_("[Match_My_Addresses]");
8264 static char *dont_match_me = N_("[Don't_Match_My_Addresses]");
8266 ps_global->mangled_footer = 1;
8267 savedsstring[0] = '\0';
8268 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8270 while(1){
8271 type = radio_buttons(not ? _(sel_text_not) : _(sel_text),
8272 -FOOTER_ROWS(ps_global), sel_text_opt,
8273 's', 'x', NO_HELP, RB_NORM|RB_RET_HELP);
8275 if(type == '!')
8276 not = !not;
8277 else if(type == 3){
8278 helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS",
8279 HLPD_SIMPLE);
8280 ps_global->mangled_screen = 1;
8282 else
8283 break;
8287 * prepare some friendly defaults...
8289 switch(type){
8290 case 't' : /* address fields, offer To or From */
8291 case 'f' :
8292 case 'c' :
8293 case 'r' :
8294 case 'p' :
8295 sval = (type == 't') ? "TO" :
8296 (type == 'f') ? "FROM" :
8297 (type == 'c') ? "CC" :
8298 (type == 'r') ? recip : partic;
8299 ekey[ekeyi].ch = ctrl('T');
8300 ekey[ekeyi].name = "^T";
8301 ekey[ekeyi].rval = 10;
8302 /* TRANSLATORS: use Current To Address */
8303 ekey[ekeyi++].label = N_("Cur To");
8304 ekey[ekeyi].ch = ctrl('R');
8305 ekey[ekeyi].name = "^R";
8306 ekey[ekeyi].rval = 11;
8307 /* TRANSLATORS: use Current From Address */
8308 ekey[ekeyi++].label = N_("Cur From");
8309 ekey[ekeyi].ch = ctrl('W');
8310 ekey[ekeyi].name = "^W";
8311 ekey[ekeyi].rval = 12;
8312 /* TRANSLATORS: use Current Cc Address */
8313 ekey[ekeyi++].label = N_("Cur Cc");
8314 ekey[ekeyi].ch = ctrl('Y');
8315 ekey[ekeyi].name = "^Y";
8316 ekey[ekeyi].rval = 13;
8317 /* TRANSLATORS: Match Me means match my address */
8318 ekey[ekeyi++].label = N_("Match Me");
8319 ekey[ekeyi].ch = 0;
8320 ekey[ekeyi].name = "";
8321 ekey[ekeyi].rval = 0;
8322 ekey[ekeyi++].label = "";
8323 break;
8325 case 's' :
8326 sval = "SUBJECT";
8327 ekey[ekeyi].ch = ctrl('X');
8328 ekey[ekeyi].name = "^X";
8329 ekey[ekeyi].rval = 14;
8330 /* TRANSLATORS: use Current Subject */
8331 ekey[ekeyi++].label = N_("Cur Subject");
8332 break;
8334 case 'a' :
8335 sval = "TEXT";
8336 break;
8338 case 'b' :
8339 sval = "BODYTEXT";
8340 break;
8342 case 'h' :
8343 strncpy(tmp, "Name of HEADER to match : ", sizeof(tmp)-1);
8344 tmp[sizeof(tmp)-1] = '\0';
8345 flags = OE_APPEND_CURRENT;
8346 namehdr[0] = '\0';
8347 r = 'x';
8348 while (r == 'x'){
8349 int done = 0;
8351 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8352 sizeof(namehdr), tmp, ekey, NO_HELP, &flags);
8353 if (r == 1){
8354 cmd_cancelled("Selection by text");
8355 return(1);
8357 removing_leading_white_space(namehdr);
8358 while(!done){
8359 while ((namehdr[0] != '\0') && /* remove trailing ":" */
8360 (namehdr[strlen(namehdr) - 1] == ':'))
8361 namehdr[strlen(namehdr) - 1] = '\0';
8362 if ((namehdr[0] != '\0')
8363 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8364 removing_trailing_white_space(namehdr);
8365 else
8366 done++;
8368 if (strchr(namehdr,' ') || strchr(namehdr,'\t') ||
8369 strchr(namehdr,':'))
8370 namehdr[0] = '\0';
8371 if (namehdr[0] == '\0')
8372 r = 'x';
8374 sval = namehdr;
8375 break;
8377 case 'x':
8378 break;
8380 default:
8381 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
8382 return(1);
8385 ekey[ekeyi].ch = KEY_UP;
8386 ekey[ekeyi].rval = 30;
8387 ekey[ekeyi].name = "";
8388 ku = ekeyi;
8389 ekey[ekeyi++].label = "";
8391 ekey[ekeyi].ch = KEY_DOWN;
8392 ekey[ekeyi].rval = 31;
8393 ekey[ekeyi].name = "";
8394 ekey[ekeyi++].label = "";
8396 ekey[ekeyi].ch = -1;
8398 if(type != 'x'){
8400 init_hist(&history, HISTSIZE);
8402 if(ekey[0].ch > -1 && msgno > 0L
8403 && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),
8404 NULL)))
8405 ekey[0].ch = -1;
8407 sstring[0] = '\0';
8408 help = NO_HELP;
8409 r = type;
8410 while(r != 'x'){
8411 if(not)
8412 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8413 snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval);
8414 else
8415 snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval);
8417 if(items_in_hist(history) > 0){
8418 ekey[ku].name = HISTORY_UP_KEYNAME;
8419 ekey[ku].label = HISTORY_KEYLABEL;
8420 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
8421 ekey[ku+1].label = HISTORY_KEYLABEL;
8423 else{
8424 ekey[ku].name = "";
8425 ekey[ku].label = "";
8426 ekey[ku+1].name = "";
8427 ekey[ku+1].label = "";
8430 flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
8431 r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
8432 79, tmp, ekey, help, &flags);
8434 if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me)))))
8435 me = 0;
8437 switch(r){
8438 case 3 :
8439 help = (help == NO_HELP)
8440 ? (not
8441 ? ((type == 'f') ? h_select_txt_not_from
8442 : (type == 't') ? h_select_txt_not_to
8443 : (type == 'c') ? h_select_txt_not_cc
8444 : (type == 's') ? h_select_txt_not_subj
8445 : (type == 'a') ? h_select_txt_not_all
8446 : (type == 'r') ? h_select_txt_not_recip
8447 : (type == 'p') ? h_select_txt_not_partic
8448 : (type == 'b') ? h_select_txt_not_body
8449 : NO_HELP)
8450 : ((type == 'f') ? h_select_txt_from
8451 : (type == 't') ? h_select_txt_to
8452 : (type == 'c') ? h_select_txt_cc
8453 : (type == 's') ? h_select_txt_subj
8454 : (type == 'a') ? h_select_txt_all
8455 : (type == 'r') ? h_select_txt_recip
8456 : (type == 'p') ? h_select_txt_partic
8457 : (type == 'b') ? h_select_txt_body
8458 : NO_HELP))
8459 : NO_HELP;
8461 case 4 :
8462 continue;
8464 case 10 : /* To: default */
8465 if(env && env->to && env->to->mailbox){
8466 snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox,
8467 env->to->host ? "@" : "",
8468 env->to->host ? env->to->host : "");
8469 sstring[sizeof(sstring)-1] = '\0';
8471 continue;
8473 case 11 : /* From: default */
8474 if(env && env->from && env->from->mailbox){
8475 snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox,
8476 env->from->host ? "@" : "",
8477 env->from->host ? env->from->host : "");
8478 sstring[sizeof(sstring)-1] = '\0';
8480 continue;
8482 case 12 : /* Cc: default */
8483 if(env && env->cc && env->cc->mailbox){
8484 snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox,
8485 env->cc->host ? "@" : "",
8486 env->cc->host ? env->cc->host : "");
8487 sstring[sizeof(sstring)-1] = '\0';
8489 continue;
8491 case 13 : /* Match my addresses */
8492 me++;
8493 snprintf(sstring, sizeof(sstring), "%s", not ? _(dont_match_me) : _(match_me));
8494 continue;
8496 case 14 : /* Subject: default */
8497 if(env && env->subject && env->subject[0]){
8498 char *q = NULL;
8500 snprintf(buftmp, sizeof(buftmp), "%.75s", env->subject);
8501 buftmp[sizeof(buftmp)-1] = '\0';
8502 q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
8503 SIZEOF_20KBUF, buftmp);
8504 if(q != env->subject){
8505 snprintf(savedsstring, sizeof(savedsstring), "%.70s", q);
8506 savedsstring[sizeof(savedsstring)-1] = '\0';
8509 snprintf(sstring, sizeof(sstring), "%s", q);
8510 sstring[sizeof(sstring)-1] = '\0';
8513 continue;
8515 case 30 :
8516 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8517 if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){
8518 strncpy(sstring, p, sizeof(sstring));
8519 sstring[sizeof(sstring)-1] = '\0';
8520 if(history->hist[history->curindex]){
8521 flagsforhist = history->hist[history->curindex]->flags;
8522 not = (flagsforhist & 0x1) ? 1 : 0;
8523 me = (flagsforhist & 0x2) ? 1 : 0;
8526 else
8527 Writechar(BELL, 0);
8529 continue;
8531 case 31 :
8532 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8533 if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){
8534 strncpy(sstring, p, sizeof(sstring));
8535 sstring[sizeof(sstring)-1] = '\0';
8536 if(history->hist[history->curindex]){
8537 flagsforhist = history->hist[history->curindex]->flags;
8538 not = (flagsforhist & 0x1) ? 1 : 0;
8539 me = (flagsforhist & 0x2) ? 1 : 0;
8542 else
8543 Writechar(BELL, 0);
8545 continue;
8547 default :
8548 break;
8551 if(r == 1 || sstring[0] == '\0')
8552 r = 'x';
8554 break;
8558 if(type == 'x' || r == 'x'){
8559 cmd_cancelled("Selection by text");
8560 return(1);
8563 if(ps_global && ps_global->ttyo){
8564 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8565 ps_global->mangled_footer = 1;
8568 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8570 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8571 save_hist(history, sstring, flagsforhist, NULL);
8573 rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch);
8574 if(we_cancel)
8575 cancel_busy_cue(0);
8577 return(rv);
8582 * Select by message size.
8583 * Sets searched bits in mail_elts
8585 * Args limitsrch -- limit search to this searchset
8587 * Returns 0 on success.
8590 select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch)
8592 int r, large = 1, we_cancel = 0;
8593 unsigned long n, mult = 1L, numerator = 0L, divisor = 1L;
8594 char size[16], numbers[80], *p, *t;
8595 HelpType help;
8596 SEARCHPGM *pgm;
8597 long flags = (SE_NOPREFETCH | SE_FREE);
8599 numbers[0] = '\0';
8600 ps_global->mangled_footer = 1;
8602 help = NO_HELP;
8603 while(1){
8604 int flgs = OE_APPEND_CURRENT;
8606 sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger;
8608 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
8609 sizeof(numbers), large ? _(select_size_larger_msg)
8610 : _(select_size_smaller_msg),
8611 sel_size_opt, help, &flgs);
8612 if(r == 4)
8613 continue;
8615 if(r == 14){
8616 large = 1 - large;
8617 continue;
8620 if(r == 3){
8621 help = (help == NO_HELP) ? (large ? h_select_by_larger_size
8622 : h_select_by_smaller_size)
8623 : NO_HELP;
8624 continue;
8627 for(t = p = numbers; *p ; p++) /* strip whitespace */
8628 if(!isspace((unsigned char)*p))
8629 *t++ = *p;
8631 *t = '\0';
8633 if(r == 1 || numbers[0] == '\0'){
8634 cmd_cancelled("Selection by size");
8635 return(1);
8637 else
8638 break;
8641 if(numbers[0] == '-'){
8642 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8643 _("Invalid size entered: %s"), numbers);
8644 return(1);
8647 t = size;
8648 p = numbers;
8650 while(*p && isdigit((unsigned char)*p))
8651 *t++ = *p++;
8653 *t = '\0';
8655 if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){
8656 size[0] = '0';
8657 size[1] = '\0';
8660 if(size[0] == '\0'){
8661 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8662 _("Invalid size entered: %s"), numbers);
8663 return(1);
8666 n = strtoul(size, (char **)NULL, 10);
8668 size[0] = '\0';
8669 if(*p == '.'){
8671 * We probably ought to just use atof() to convert 1.1 into a
8672 * double, but since we haven't used atof() anywhere else I'm
8673 * reluctant to use it because of portability concerns.
8675 p++;
8676 t = size;
8677 while(*p && isdigit((unsigned char)*p)){
8678 *t++ = *p++;
8679 divisor *= 10;
8682 *t = '\0';
8684 if(size[0])
8685 numerator = strtoul(size, (char **)NULL, 10);
8688 switch(*p){
8689 case 'g':
8690 case 'G':
8691 mult *= 1000;
8692 /* fall through */
8694 case 'm':
8695 case 'M':
8696 mult *= 1000;
8697 /* fall through */
8699 case 'k':
8700 case 'K':
8701 mult *= 1000;
8702 break;
8705 n = n * mult + (numerator * mult) / divisor;
8707 pgm = mail_newsearchpgm();
8708 if(large)
8709 pgm->larger = n;
8710 else
8711 pgm->smaller = n;
8713 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8714 flags |= SE_NOSERVER;
8716 if(ps_global && ps_global->ttyo){
8717 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8718 ps_global->mangled_footer = 1;
8721 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8723 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8724 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8725 /* we know this was freed in mail_search, let caller know */
8726 if(limitsrch)
8727 *limitsrch = NULL;
8729 if(we_cancel)
8730 cancel_busy_cue(0);
8732 return(0);
8737 * visible_searchset -- return c-client search set unEXLDed
8738 * sequence numbers
8740 SEARCHSET *
8741 visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap)
8743 long n, run;
8744 SEARCHSET *full_set = NULL, **set;
8747 * If we're talking to anything other than a server older than
8748 * imap 4rev1, build a searchset otherwise it'll choke.
8750 if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
8751 if(any_lflagged(msgmap, MN_EXLD)){
8752 for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
8753 if(get_lflag(stream, NULL, n, MN_EXLD)){
8754 if(run){ /* previous NOT excluded? */
8755 if(run > 1L)
8756 (*set)->last = n - 1L;
8758 set = &(*set)->next;
8759 run = 0L;
8762 else if(run++){ /* next in run */
8763 (*set)->last = n;
8765 else{ /* start of run */
8766 *set = mail_newsearchset();
8767 (*set)->first = n;
8770 else{
8771 full_set = mail_newsearchset();
8772 full_set->first = 1L;
8773 full_set->last = stream->nmsgs;
8777 return(full_set);
8782 * Select by message status bits.
8783 * Sets searched bits in mail_elts
8785 * Args limitsrch -- limit search to this searchset
8787 * Returns 0 on success.
8790 select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch)
8792 int s, not = 0, we_cancel = 0, rv;
8794 while(1){
8795 s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag),
8796 -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x',
8797 NO_HELP, RB_NORM|RB_RET_HELP);
8799 if(s == 'x'){
8800 cmd_cancelled("Selection by status");
8801 return(1);
8803 else if(s == 3){
8804 helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"),
8805 HLPD_SIMPLE);
8806 ps_global->mangled_screen = 1;
8808 else if(s == '!')
8809 not = !not;
8810 else
8811 break;
8814 if(ps_global && ps_global->ttyo){
8815 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8816 ps_global->mangled_footer = 1;
8819 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8820 rv = agg_flag_select(stream, not, s, limitsrch);
8821 if(we_cancel)
8822 cancel_busy_cue(0);
8824 return(rv);
8829 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8830 * Sets searched bits in mail_elts
8832 * Args limitsrch -- limit search to this searchset
8834 * Returns 0 on success.
8837 select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
8839 char rulenick[1000], *nick;
8840 PATGRP_S *patgrp;
8841 int r, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
8842 | ROLE_DO_INCOLS
8843 | ROLE_DO_ROLES
8844 | ROLE_DO_SCORES
8845 | ROLE_DO_OTHER
8846 | ROLE_DO_FILTER;
8848 rulenick[0] = '\0';
8849 ps_global->mangled_footer = 1;
8852 int oe_flags;
8854 oe_flags = OE_APPEND_CURRENT;
8855 r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0,
8856 sizeof(rulenick),
8857 not ? _("Rule to NOT match: ")
8858 : _("Rule to match: "),
8859 sel_key_opt, NO_HELP, &oe_flags);
8861 if(r == 14){
8862 /* select rulenick from a list */
8863 if((nick=choose_a_rule(rflags)) != NULL){
8864 strncpy(rulenick, nick, sizeof(rulenick)-1);
8865 rulenick[sizeof(rulenick)-1] = '\0';
8866 fs_give((void **) &nick);
8868 else
8869 r = 4;
8871 else if(r == '!')
8872 not = !not;
8874 if(r == 3){
8875 helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE);
8876 ps_global->mangled_screen = 1;
8878 else if(r == 1){
8879 cmd_cancelled("Selection by Rule");
8880 return(1);
8883 removing_leading_and_trailing_white_space(rulenick);
8885 }while(r == 3 || r == 4 || r == '!');
8889 * The approach of requiring a nickname instead of just allowing the
8890 * user to select from the list of rules has the drawback that a rule
8891 * may not have a nickname, or there may be more than one rule with
8892 * the same nickname. However, it has the benefit of allowing the user
8893 * to type in the nickname and, most importantly, allows us to set
8894 * up the ! (not). We could incorporate the ! into the selection
8895 * screen, but this is easier and also allows the typing of nicks.
8896 * User can just set up nicknames if they want to use this feature.
8898 patgrp = nick_to_patgrp(rulenick, rflags);
8900 if(patgrp){
8901 if(ps_global && ps_global->ttyo){
8902 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8903 ps_global->mangled_footer = 1;
8906 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8907 match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL,
8908 get_msg_score,
8909 (not ? MP_NOT : 0) | SE_NOPREFETCH);
8910 free_patgrp(&patgrp);
8911 if(we_cancel)
8912 cancel_busy_cue(0);
8915 if(limitsrch && *limitsrch){
8916 mail_free_searchset(limitsrch);
8917 *limitsrch = NULL;
8920 return(0);
8925 * Allow user to choose a rule from their list of rules.
8927 * Returns an allocated rule nickname on success, NULL otherwise.
8929 char *
8930 choose_a_rule(int rflags)
8932 char *choice = NULL;
8933 char **rule_list, **lp;
8934 int cnt = 0;
8935 PAT_S *pat;
8936 PAT_STATE pstate;
8938 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){
8939 q_status_message(SM_ORDER, 3, 3,
8940 _("No rules available. Use Setup/Rules to add some."));
8941 return(choice);
8945 * Build a list of rules to choose from.
8948 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8949 cnt++;
8951 if(cnt <= 0){
8952 q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
8953 return(choice);
8956 lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
8957 memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
8959 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8960 *lp++ = cpystr((pat->patgrp && pat->patgrp->nick)
8961 ? pat->patgrp->nick : "?");
8963 /* TRANSLATORS: SELECT A RULE is a screen title
8964 TRANSLATORS: Print something1 using something2.
8965 "rules" is something1 */
8966 choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"),
8967 _("rules"), h_select_rule_screen,
8968 _("HELP FOR SELECTING A RULE NICKNAME"), NULL);
8970 if(!choice)
8971 q_status_message(SM_ORDER, 1, 4, "No choice");
8973 free_list_array(&rule_list);
8975 return(choice);
8980 * Select by current thread.
8981 * Sets searched bits in mail_elts for this entire thread
8983 * Args limitsrch -- limit search to this searchset
8985 * Returns 0 on success.
8988 select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
8990 long n;
8991 PINETHRD_S *thrd = NULL;
8992 int ret = 1;
8993 MESSAGECACHE *mc;
8995 if(!stream)
8996 return(ret);
8998 for(n = 1L; n <= stream->nmsgs; n++)
8999 if((mc = mail_elt(stream, n)) != NULL)
9000 mc->searched = 0; /* clear searched bits */
9002 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
9003 if(thrd && thrd->top && thrd->top != thrd->rawno)
9004 thrd = fetch_thread(stream, thrd->top);
9007 * This doesn't unselect if the thread is already selected
9008 * (like select current does), it always selects.
9009 * There is no way to select ! this thread.
9011 if(thrd){
9012 set_search_bit_for_thread(stream, thrd, limitsrch);
9013 ret = 0;
9016 return(ret);
9021 * Select by message keywords.
9022 * Sets searched bits in mail_elts
9024 * Args limitsrch -- limit search to this searchset
9026 * Returns 0 on success.
9029 select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
9031 int r, not = 0, we_cancel = 0;
9032 char keyword[MAXUSERFLAG+1], *kword;
9033 char *error = NULL, *p, *prompt;
9034 HelpType help;
9035 SEARCHPGM *pgm;
9037 keyword[0] = '\0';
9038 ps_global->mangled_footer = 1;
9040 help = NO_HELP;
9042 int oe_flags;
9044 if(error){
9045 q_status_message(SM_ORDER, 3, 4, error);
9046 fs_give((void **) &error);
9049 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
9050 if(not)
9051 prompt = _("Keyword (or keyword initial) to NOT match: ");
9052 else
9053 prompt = _("Keyword (or keyword initial) to match: ");
9055 else{
9056 if(not)
9057 prompt = _("Keyword to NOT match: ");
9058 else
9059 prompt = _("Keyword to match: ");
9062 oe_flags = OE_APPEND_CURRENT;
9063 r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0,
9064 sizeof(keyword),
9065 prompt, sel_key_opt, help, &oe_flags);
9067 if(r == 14){
9068 /* select keyword from a list */
9069 if((kword=choose_a_keyword()) != NULL){
9070 strncpy(keyword, kword, sizeof(keyword)-1);
9071 keyword[sizeof(keyword)-1] = '\0';
9072 fs_give((void **) &kword);
9074 else
9075 r = 4;
9077 else if(r == '!')
9078 not = !not;
9080 if(r == 3)
9081 help = help == NO_HELP ? h_select_keyword : NO_HELP;
9082 else if(r == 1){
9083 cmd_cancelled("Selection by keyword");
9084 return(1);
9087 removing_leading_and_trailing_white_space(keyword);
9089 }while(r == 3 || r == 4 || r == '!' || keyword_check(keyword, &error));
9092 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
9093 p = initial_to_keyword(keyword);
9094 if(p != keyword){
9095 strncpy(keyword, p, sizeof(keyword)-1);
9096 keyword[sizeof(keyword)-1] = '\0';
9101 * We want to check the keyword, not the nickname of the keyword,
9102 * so convert it to the keyword if necessary.
9104 p = nick_to_keyword(keyword);
9105 if(p != keyword){
9106 strncpy(keyword, p, sizeof(keyword)-1);
9107 keyword[sizeof(keyword)-1] = '\0';
9110 pgm = mail_newsearchpgm();
9111 if(not){
9112 pgm->unkeyword = mail_newstringlist();
9113 pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword);
9114 pgm->unkeyword->text.size = strlen(keyword);
9116 else{
9117 pgm->keyword = mail_newstringlist();
9118 pgm->keyword->text.data = (unsigned char *) cpystr(keyword);
9119 pgm->keyword->text.size = strlen(keyword);
9122 if(ps_global && ps_global->ttyo){
9123 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9124 ps_global->mangled_footer = 1;
9127 we_cancel = busy_cue(_("Selecting"), NULL, 1);
9129 pgm->msgno = (limitsrch ? *limitsrch : NULL);
9130 pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE);
9131 /* we know this was freed in mail_search, let caller know */
9132 if(limitsrch)
9133 *limitsrch = NULL;
9135 if(we_cancel)
9136 cancel_busy_cue(0);
9138 return(0);
9143 * Allow user to choose a keyword from their list of keywords.
9145 * Returns an allocated keyword on success, NULL otherwise.
9147 char *
9148 choose_a_keyword(void)
9150 char *choice = NULL;
9151 char **keyword_list, **lp;
9152 int cnt;
9153 KEYWORD_S *kw;
9156 * Build a list of keywords to choose from.
9159 for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next)
9160 cnt++;
9162 if(cnt <= 0){
9163 q_status_message(SM_ORDER, 3, 4,
9164 _("No keywords defined, use \"keywords\" option in Setup/Config"));
9165 return(choice);
9168 lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list));
9169 memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list));
9171 for(kw = ps_global->keywords; kw; kw = kw->next)
9172 *lp++ = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9174 /* TRANSLATORS: SELECT A KEYWORD is a screen title
9175 TRANSLATORS: Print something1 using something2.
9176 "keywords" is something1 */
9177 choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"),
9178 _("keywords"), h_select_keyword_screen,
9179 _("HELP FOR SELECTING A KEYWORD"), NULL);
9181 if(!choice)
9182 q_status_message(SM_ORDER, 1, 4, "No choice");
9184 free_list_array(&keyword_list);
9186 return(choice);
9191 * Allow user to choose a list of keywords from their list of keywords.
9193 * Returns allocated list.
9195 char **
9196 choose_list_of_keywords(void)
9198 LIST_SEL_S *listhead, *ls, *p;
9199 char **ret = NULL;
9200 int cnt, i;
9201 KEYWORD_S *kw;
9204 * Build a list of keywords to choose from.
9207 p = listhead = NULL;
9208 for(kw = ps_global->keywords; kw; kw = kw->next){
9210 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9211 memset(ls, 0, sizeof(*ls));
9212 ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9214 if(p){
9215 p->next = ls;
9216 p = p->next;
9218 else
9219 listhead = p = ls;
9222 if(!listhead)
9223 return(ret);
9225 /* TRANSLATORS: SELECT KEYWORDS is a screen title
9226 Print something1 using something2.
9227 "keywords" is something1 */
9228 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9229 _("SELECT KEYWORDS"), _("keywords"),
9230 h_select_multkeyword_screen,
9231 _("HELP FOR SELECTING KEYWORDS"), NULL)){
9232 for(cnt = 0, p = listhead; p; p = p->next)
9233 if(p->selected)
9234 cnt++;
9236 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9237 memset(ret, 0, (cnt+1) * sizeof(*ret));
9238 for(i = 0, p = listhead; p; p = p->next)
9239 if(p->selected)
9240 ret[i++] = cpystr(p->item ? p->item : "");
9243 free_list_sel(&listhead);
9245 return(ret);
9250 * Allow user to choose a charset
9252 * Returns an allocated charset on success, NULL otherwise.
9254 char *
9255 choose_a_charset(int which_charsets)
9257 char *choice = NULL;
9258 char **charset_list, **lp;
9259 const CHARSET *cs;
9260 int cnt;
9263 * Build a list of charsets to choose from.
9266 for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){
9267 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9268 && ((which_charsets & CAC_ALL)
9269 || (which_charsets & CAC_POSTING
9270 && cs->flags & CF_POSTING)
9271 || (which_charsets & CAC_DISPLAY
9272 && cs->type != CT_2022
9273 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9274 cnt++;
9277 if(cnt <= 0){
9278 q_status_message(SM_ORDER, 3, 4,
9279 _("No charsets found? Enter charset manually."));
9280 return(choice);
9283 lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list));
9284 memset(charset_list, 0, (cnt+1) * sizeof(*charset_list));
9286 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9287 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9288 && ((which_charsets & CAC_ALL)
9289 || (which_charsets & CAC_POSTING
9290 && cs->flags & CF_POSTING)
9291 || (which_charsets & CAC_DISPLAY
9292 && cs->type != CT_2022
9293 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9294 *lp++ = cpystr(cs->name);
9297 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
9298 TRANSLATORS: Print something1 using something2.
9299 "character sets" is something1 */
9300 choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"),
9301 _("character sets"), h_select_charset_screen,
9302 _("HELP FOR SELECTING A CHARACTER SET"), NULL);
9304 if(!choice)
9305 q_status_message(SM_ORDER, 1, 4, "No choice");
9307 free_list_array(&charset_list);
9309 return(choice);
9314 * Allow user to choose a list of character sets and/or scripts
9316 * Returns allocated list.
9318 char **
9319 choose_list_of_charsets(void)
9321 LIST_SEL_S *listhead, *ls, *p;
9322 char **ret = NULL;
9323 int cnt, i, got_one;
9324 const CHARSET *cs;
9325 SCRIPT *s;
9326 char *q, *t;
9327 long width, limit;
9328 char buf[1024], *folded;
9331 * Build a list of charsets to choose from.
9334 p = listhead = NULL;
9336 /* this width is determined by select_from_list_screen() */
9337 width = ps_global->ttyo->screen_cols - 4;
9339 /* first comes a list of scripts (sets of character sets) */
9340 for(s = utf8_script(NIL); s && s->name; s++){
9342 limit = sizeof(buf)-1;
9343 q = buf;
9344 memset(q, 0, limit+1);
9346 if(s->name)
9347 sstrncpy(&q, s->name, limit);
9349 if(s->description){
9350 sstrncpy(&q, " (", limit-(q-buf));
9351 sstrncpy(&q, s->description, limit-(q-buf));
9352 sstrncpy(&q, ")", limit-(q-buf));
9355 /* add the list of charsets that are in this script */
9356 got_one = 0;
9357 for(cs = utf8_charset(NIL);
9358 cs && cs->name && (q-buf) < limit; cs++){
9359 if(cs->script & s->script){
9361 * Filter out some un-useful members of the list.
9362 * UTF-7 and UTF-8 weren't actually in the list at the
9363 * time this was written. Just making sure.
9365 if(!strucmp(cs->name, "ISO-2022-JP-2")
9366 || !strucmp(cs->name, "UTF-7")
9367 || !strucmp(cs->name, "UTF-8"))
9368 continue;
9370 if(got_one)
9371 sstrncpy(&q, " ", limit-(q-buf));
9372 else{
9373 got_one = 1;
9374 sstrncpy(&q, " {", limit-(q-buf));
9377 sstrncpy(&q, cs->name, limit-(q-buf));
9381 if(got_one)
9382 sstrncpy(&q, "}", limit-(q-buf));
9384 /* fold this line so that it can all be seen on the screen */
9385 folded = fold(buf, width, width, "", " ", FLD_NONE);
9386 if(folded){
9387 t = folded;
9388 while(t && *t && (q = strindex(t, '\n')) != NULL){
9389 *q = '\0';
9391 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9392 memset(ls, 0, sizeof(*ls));
9393 if(t == folded)
9394 ls->item = cpystr(s->name);
9395 else
9396 ls->flags = SFL_NOSELECT;
9398 ls->display_item = cpystr(t);
9400 t = q+1;
9402 if(p){
9403 p->next = ls;
9404 p = p->next;
9406 else{
9407 /* add a heading */
9408 listhead = (LIST_SEL_S *) fs_get(sizeof(*ls));
9409 memset(listhead, 0, sizeof(*listhead));
9410 listhead->flags = SFL_NOSELECT;
9411 listhead->display_item =
9412 cpystr(_("Scripts representing groups of related character sets"));
9413 listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls));
9414 memset(listhead->next, 0, sizeof(*listhead));
9415 listhead->next->flags = SFL_NOSELECT;
9416 listhead->next->display_item =
9417 cpystr(repeat_char(width, '-'));
9419 listhead->next->next = ls;
9420 p = ls;
9424 fs_give((void **) &folded);
9428 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9429 memset(ls, 0, sizeof(*ls));
9430 ls->flags = SFL_NOSELECT;
9431 if(p){
9432 p->next = ls;
9433 p = p->next;
9435 else
9436 listhead = p = ls;
9438 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9439 memset(ls, 0, sizeof(*ls));
9440 ls->flags = SFL_NOSELECT;
9441 ls->display_item =
9442 cpystr(_("Individual character sets, may be mixed with scripts"));
9443 p->next = ls;
9444 p = p->next;
9446 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9447 memset(ls, 0, sizeof(*ls));
9448 ls->flags = SFL_NOSELECT;
9449 ls->display_item =
9450 cpystr(repeat_char(width, '-'));
9451 p->next = ls;
9452 p = p->next;
9454 /* then comes a list of individual character sets */
9455 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9456 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9457 memset(ls, 0, sizeof(*ls));
9458 ls->item = cpystr(cs->name);
9460 if(p){
9461 p->next = ls;
9462 p = p->next;
9464 else
9465 listhead = p = ls;
9468 if(!listhead)
9469 return(ret);
9471 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9472 Print something1 using something2.
9473 "character sets" is something1 */
9474 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9475 _("SELECT CHARACTER SETS"), _("character sets"),
9476 h_select_multcharsets_screen,
9477 _("HELP FOR SELECTING CHARACTER SETS"), NULL)){
9478 for(cnt = 0, p = listhead; p; p = p->next)
9479 if(p->selected)
9480 cnt++;
9482 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9483 memset(ret, 0, (cnt+1) * sizeof(*ret));
9484 for(i = 0, p = listhead; p; p = p->next)
9485 if(p->selected)
9486 ret[i++] = cpystr(p->item ? p->item : "");
9489 free_list_sel(&listhead);
9491 return(ret);
9494 /* Report quota summary resources in an IMAP server */
9496 void
9497 cmd_quota (struct pine *state)
9499 QUOTALIST *imapquota;
9500 NETMBX mb;
9501 STORE_S *store;
9502 SCROLL_S sargs;
9504 if(!state->mail_stream || !is_imap_stream(state->mail_stream)){
9505 q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders");
9506 return;
9509 if (state->mail_stream
9510 && !sp_dead_stream(state->mail_stream)
9511 && state->mail_stream->mailbox
9512 && *state->mail_stream->mailbox
9513 && mail_valid_net_parse(state->mail_stream->mailbox, &mb))
9514 imap_getquotaroot(state->mail_stream, mb.mailbox);
9516 if(!state->quota) /* failed ? */
9517 return; /* go back... */
9519 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
9520 q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space.");
9521 return;
9524 so_puts(store, "Quota Report for ");
9525 so_puts(store, state->mail_stream->original_mailbox);
9526 so_puts(store, "\n\n");
9528 for (imapquota = state->quota; imapquota; imapquota = imapquota->next){
9530 so_puts(store, _("Resource : "));
9531 so_puts(store, imapquota->name);
9532 so_writec('\n', store);
9534 so_puts(store, _("Usage : "));
9535 so_puts(store, long2string(imapquota->usage));
9536 if(!strucmp(imapquota->name,"STORAGE"))
9537 so_puts(store, " KiB ");
9538 if(!strucmp(imapquota->name,"MESSAGE")){
9539 so_puts(store, _(" message"));
9540 if(imapquota->usage != 1)
9541 so_puts(store, _("s ")); /* plural */
9542 else
9543 so_puts(store, _(" "));
9545 so_writec('(', store);
9546 so_puts(store, long2string(100*imapquota->usage/imapquota->limit));
9547 so_puts(store, "%)\n");
9549 so_puts(store, _("Limit : "));
9550 so_puts(store, long2string(imapquota->limit));
9551 if(!strucmp(imapquota->name,"STORAGE"))
9552 so_puts(store, " KiB\n\n");
9553 if(!strucmp(imapquota->name,"MESSAGE")){
9554 so_puts(store, _(" message"));
9555 if(imapquota->usage != 1)
9556 so_puts(store, _("s\n\n")); /* plural */
9557 else
9558 so_puts(store, _("\n\n"));
9562 memset(&sargs, 0, sizeof(SCROLL_S));
9563 sargs.text.text = so_text(store);
9564 sargs.text.src = CharStar;
9565 sargs.text.desc = _("Quota Resources Summary");
9566 sargs.bar.title = _("QUOTA SUMMARY");
9567 sargs.proc.tool = NULL;
9568 sargs.help.text = h_quota_command;
9569 sargs.help.title = NULL;
9570 sargs.keys.menu = NULL;
9571 setbitmap(sargs.keys.bitmap);
9573 scrolltool(&sargs);
9574 so_give(&store);
9576 if (state->quota)
9577 mail_free_quotalist(&(state->quota));
9580 /*----------------------------------------------------------------------
9581 Prompt the user for the type of sort he desires
9583 Args: state -- pine state pointer
9584 q1 -- Line to prompt on
9586 Returns 0 if it was cancelled, 1 otherwise.
9587 ----*/
9589 select_sort(struct pine *state, int ql, SortOrder *sort, int *rev)
9591 char prompt[200], tmp[3], *p;
9592 int s, i;
9593 int deefault = 'a', retval = 1;
9594 HelpType help;
9595 ESCKEY_S sorts[14];
9597 #ifdef _WINDOWS
9598 DLG_SORTPARAM sortsel;
9600 if (mswin_usedialog ()) {
9602 sortsel.reverse = mn_get_revsort (state->msgmap);
9603 sortsel.cursort = mn_get_sort (state->msgmap);
9604 /* assumption here that HelpType is char ** */
9605 sortsel.helptext = h_select_sort;
9606 sortsel.rval = 0;
9608 if ((retval = os_sortdialog (&sortsel))) {
9609 *sort = sortsel.cursort;
9610 *rev = sortsel.reverse;
9613 return (retval);
9615 #endif
9617 /*----- String together the prompt ------*/
9618 tmp[1] = '\0';
9619 if(F_ON(F_USE_FK,ps_global))
9620 strncpy(prompt, _("Choose type of sort : "), sizeof(prompt));
9621 else
9622 strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "),
9623 sizeof(prompt));
9625 for(i = 0; state->sort_types[i] != EndofList; i++) {
9626 sorts[i].rval = i;
9627 p = sorts[i].label = sort_name(state->sort_types[i]);
9628 while(*(p+1) && islower((unsigned char)*p))
9629 p++;
9631 sorts[i].ch = tolower((unsigned char)(tmp[0] = *p));
9632 sorts[i].name = cpystr(tmp);
9634 if(mn_get_sort(state->msgmap) == state->sort_types[i])
9635 deefault = sorts[i].rval;
9638 sorts[i].ch = 'r';
9639 sorts[i].rval = 'r';
9640 sorts[i].name = cpystr("R");
9641 if(F_ON(F_USE_FK,ps_global))
9642 sorts[i].label = N_("Reverse");
9643 else
9644 sorts[i].label = "";
9646 sorts[++i].ch = -1;
9647 help = h_select_sort;
9649 if((F_ON(F_USE_FK,ps_global)
9650 && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x',
9651 help,RB_NORM)) != 'x'))
9653 (F_OFF(F_USE_FK,ps_global)
9654 && ((s = radio_buttons(prompt,ql,sorts,deefault,'x',
9655 help,RB_NORM)) != 'x'))){
9656 state->mangled_body = 1; /* signal screen's changed */
9657 if(s == 'r')
9658 *rev = !mn_get_revsort(state->msgmap);
9659 else
9660 *sort = state->sort_types[s];
9662 if(F_ON(F_SHOW_SORT, ps_global))
9663 ps_global->mangled_header = 1;
9665 else{
9666 retval = 0;
9667 cmd_cancelled("Sort");
9670 while(--i >= 0)
9671 fs_give((void **)&sorts[i].name);
9673 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9674 return(retval);
9678 /*---------------------------------------------------------------------
9679 Build list of folders in the given context for user selection
9681 Args: c -- pointer to pointer to folder's context context
9682 f -- folder prefix to display
9683 sublist -- whether or not to use 'f's contents as prefix
9684 lister -- function used to do the actual display
9686 Returns: malloc'd string containing sequence, else NULL if
9687 no messages in msgmap with local "selected" flag.
9688 ----*/
9690 display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int))
9692 int rc;
9693 CONTEXT_S *tc;
9694 void (*redraw)(void) = ps_global->redrawer;
9696 push_titlebar_state();
9697 tc = *c;
9698 if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0)
9699 *c = tc;
9701 ClearScreen();
9702 pop_titlebar_state();
9703 redraw_titlebar();
9704 if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
9705 (*ps_global->redrawer)();
9707 if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
9708 return(1);
9710 return(0);
9715 * Allow user to choose a single item from a list of strings.
9717 * Args list -- Array of strings to choose from, NULL terminated.
9718 * displist -- Array of strings to display instead of displaying list.
9719 * Indices correspond to the list array. Display the displist
9720 * but return the item from list if displist non-NULL.
9721 * title -- For conf_scroll_screen
9722 * pdesc -- For conf_scroll_screen
9723 * help -- For conf_scroll_screen
9724 * htitle -- For conf_scroll_screen
9726 * Returns an allocated copy of the chosen item or NULL.
9728 char *
9729 choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help,
9730 char *htitle, char *cursor_location)
9732 LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL;
9733 char **t, **dl;
9734 char *ret = NULL, *choice = NULL;
9736 /* build the LIST_SEL_S list */
9737 p = listhead = NULL;
9738 for(t = list, dl = displist; *t; t++, dl++){
9739 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9740 memset(ls, 0, sizeof(*ls));
9741 ls->item = cpystr(*t);
9742 if(displist)
9743 ls->display_item = cpystr(*dl);
9745 if(cursor_location && (cursor_location == (*t)))
9746 starting_val = ls;
9748 if(p){
9749 p->next = ls;
9750 p = p->next;
9752 else
9753 listhead = p = ls;
9756 if(!listhead)
9757 return(ret);
9759 if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc,
9760 help, htitle, starting_val))
9761 for(p = listhead; !choice && p; p = p->next)
9762 if(p->selected)
9763 choice = p->item;
9765 if(choice)
9766 ret = cpystr(choice);
9768 free_list_sel(&listhead);
9770 return(ret);
9774 void
9775 free_list_sel(LIST_SEL_S **lsel)
9777 if(lsel && *lsel){
9778 free_list_sel(&(*lsel)->next);
9779 if((*lsel)->item)
9780 fs_give((void **) &(*lsel)->item);
9782 if((*lsel)->display_item)
9783 fs_give((void **) &(*lsel)->display_item);
9785 fs_give((void **) lsel);
9791 * file_lister - call pico library's file lister
9794 file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags)
9796 PICO pbf;
9797 int rv;
9798 void (*redraw)(void) = ps_global->redrawer;
9800 standard_picobuf_setup(&pbf);
9801 push_titlebar_state();
9802 if(!newmail)
9803 pbf.newmail = NULL;
9805 /* BUG: what about help command and text? */
9806 pbf.pine_anchor = title;
9808 rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags);
9809 standard_picobuf_teardown(&pbf);
9810 fix_windsize(ps_global);
9811 init_signals(); /* has it's own signal stuff */
9813 /* Restore display's titlebar and body */
9814 pop_titlebar_state();
9815 redraw_titlebar();
9816 if((ps_global->redrawer = redraw) != NULL)
9817 (*ps_global->redrawer)();
9819 return(rv);
9823 /*----------------------------------------------------------------------
9824 Print current folder index
9826 ---*/
9828 print_index(struct pine *state, MSGNO_S *msgmap, int agg)
9830 long i;
9831 ICE_S *ice;
9832 char buf[MAX_SCREEN_COLS+1];
9834 for(i = 1L; i <= mn_get_total(msgmap); i++){
9835 if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
9836 continue;
9838 if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0))
9839 continue;
9841 ice = build_header_line(state, state->mail_stream, msgmap, i, NULL);
9843 if(ice){
9845 * I don't understand why we'd want to mark the current message
9846 * instead of printing out the first character of the status
9847 * so I'm taking it out and including the first character of the
9848 * line instead. Hubert 2006-02-09
9850 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
9851 return(0);
9854 if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i),
9855 print_char)
9856 || !gf_puts(NEWLINE, print_char))
9857 return(0);
9861 return(1);
9864 #ifdef _WINDOWS
9867 * windows callback to get/set header mode state
9870 header_mode_callback(set, args)
9871 int set;
9872 long args;
9874 return(ps_global->full_header);
9879 * windows callback to get/set zoom mode state
9882 zoom_mode_callback(set, args)
9883 int set;
9884 long args;
9886 return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0);
9891 * windows callback to get/set zoom mode state
9894 any_selected_callback(set, args)
9895 int set;
9896 long args;
9898 return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0);
9906 flag_callback(set, flags)
9907 int set;
9908 long flags;
9910 MESSAGECACHE *mc;
9911 int newflags = 0;
9912 long msgno;
9913 int permflag = 0;
9915 switch (set) {
9916 case 1: /* Important */
9917 permflag = ps_global->mail_stream->perm_flagged;
9918 break;
9920 case 2: /* New */
9921 permflag = ps_global->mail_stream->perm_seen;
9922 break;
9924 case 3: /* Answered */
9925 permflag = ps_global->mail_stream->perm_answered;
9926 break;
9928 case 4: /* Deleted */
9929 permflag = ps_global->mail_stream->perm_deleted;
9930 break;
9934 if(!(any_messages(ps_global->msgmap, NULL, "to Flag")
9935 && can_set_flag(ps_global, "flag", permflag)))
9936 return(0);
9938 if(sp_io_error_on_stream(ps_global->mail_stream)){
9939 sp_set_io_error_on_stream(ps_global->mail_stream, 0);
9940 pine_mail_check(ps_global->mail_stream); /* forces write */
9941 return(0);
9944 msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
9945 if(msgno > 0L && ps_global->mail_stream
9946 && msgno <= ps_global->mail_stream->nmsgs
9947 && (mc = mail_elt(ps_global->mail_stream, msgno))
9948 && mc->valid){
9950 * NOTE: code below is *VERY* sensitive to the order of
9951 * the messages defined in resource.h for flag handling.
9952 * Don't change it unless you know what you're doing.
9954 if(set){
9955 char *flagstr;
9956 long mflag;
9958 switch(set){
9959 case 1 : /* Important */
9960 flagstr = "\\FLAGGED";
9961 mflag = (mc->flagged) ? 0L : ST_SET;
9962 break;
9964 case 2 : /* New */
9965 flagstr = "\\SEEN";
9966 mflag = (mc->seen) ? 0L : ST_SET;
9967 break;
9969 case 3 : /* Answered */
9970 flagstr = "\\ANSWERED";
9971 mflag = (mc->answered) ? 0L : ST_SET;
9972 break;
9974 case 4 : /* Deleted */
9975 flagstr = "\\DELETED";
9976 mflag = (mc->deleted) ? 0L : ST_SET;
9977 break;
9979 default : /* bogus */
9980 return(0);
9983 mail_flag(ps_global->mail_stream, long2string(msgno),
9984 flagstr, mflag);
9986 if(ps_global->redrawer)
9987 (*ps_global->redrawer)();
9989 else{
9990 /* Important */
9991 if(mc->flagged)
9992 newflags |= 0x0001;
9994 /* New */
9995 if(!mc->seen)
9996 newflags |= 0x0002;
9998 /* Answered */
9999 if(mc->answered)
10000 newflags |= 0x0004;
10002 /* Deleted */
10003 if(mc->deleted)
10004 newflags |= 0x0008;
10008 return(newflags);
10014 * BUG: Should teach this about keywords
10016 MPopup *
10017 flag_submenu(mc)
10018 MESSAGECACHE *mc;
10020 static MPopup flag_submenu[] = {
10021 {tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}},
10022 {tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}},
10023 {tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}},
10024 {tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}},
10025 {tTail}
10028 /* Important */
10029 flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal;
10031 /* New */
10032 flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked;
10034 /* Answered */
10035 flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal;
10037 /* Deleted */
10038 flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal;
10040 return(flag_submenu);
10043 #endif /* _WINDOWS */