* Width of characters is not always determined correctly when wcwidth
[alpine.git] / alpine / mailcmd.c
blobcf61c003be57fb98e3514dce09ab46922d84562c
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-2018 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 currrently 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 currrently 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 messags 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 /* reaquire 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);
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 incase 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> attachements 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 if(strcmp(tmp_20k_buf, filename)){
4290 opts[i].ch = ctrl('N');
4291 opts[i].rval = 40;
4292 opts[i].name = "^N";
4293 opts[i++].label = "Name UTF8";
4296 if(dir_hist || hist_len > 0){
4297 opts[i].ch = ctrl('Y');
4298 opts[i].rval = 32;
4299 opts[i].name = "";
4300 kp = i;
4301 opts[i++].label = "";
4303 opts[i].ch = ctrl('V');
4304 opts[i].rval = 33;
4305 opts[i].name = "";
4306 opts[i++].label = "";
4309 if(history){
4310 opts[i].ch = KEY_UP;
4311 opts[i].rval = 30;
4312 opts[i].name = "";
4313 ku = i;
4314 opts[i++].label = "";
4316 opts[i].ch = KEY_DOWN;
4317 opts[i].rval = 31;
4318 opts[i].name = "";
4319 opts[i++].label = "";
4322 opts[i].ch = -1;
4324 if(history)
4325 init_hist(history, HISTSIZE);
4326 init_hist(&dir_hist, HISTSIZE); /* reset history to the end */
4328 else
4329 opts = optsarg;
4331 if(rflags)
4332 *rflags = GER_NONE;
4334 if(F_ON(F_USE_CURRENT_DIR, ps))
4335 dir[0] = '\0';
4336 else if(VAR_OPER_DIR){
4337 strncpy(dir, VAR_OPER_DIR, sizeof(dir));
4338 dir[sizeof(dir)-1] = '\0';
4340 #if defined(DOS) || defined(OS2)
4341 else if(VAR_FILE_DIR){
4342 strncpy(dir, VAR_FILE_DIR, sizeof(dir));
4343 dir[sizeof(dir)-1] = '\0';
4345 #endif
4346 else{
4347 dir[0] = '~';
4348 dir[1] = '\0';
4349 homedir=1;
4351 strncpy(orig_dir, dir, sizeof(orig_dir));
4352 orig_dir[sizeof(orig_dir)-1] = '\0';
4354 postcolon[0] = '\0';
4355 strncpy(precolon, dir, sizeof(precolon));
4356 precolon[sizeof(precolon)-1] = '\0';
4357 if(deefault){
4358 strncpy(def, deefault, sizeof(def)-1);
4359 def[sizeof(def)-1] = '\0';
4360 removing_leading_and_trailing_white_space(def);
4362 else
4363 def[0] = '\0';
4365 avail = MAX(20, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - MIN_OPT_ENT_WIDTH;
4367 /*---------- Prompt the user for the file name -------------*/
4368 while(1){
4369 int oeflags;
4370 char dirb[50], fileb[50];
4371 int l1, l2, l3, l4, l5, needed;
4372 char *p, p1[100], p2[100], *p3, p4[100], p5[100];
4374 snprintf(p1, sizeof(p1), "%sCopy ",
4375 (flags & GE_IS_EXPORT) ? "EXPORT: " :
4376 (flags & GE_IS_IMPORT) ? "IMPORT: " : "SAVE: ");
4377 p1[sizeof(p1)-1] = '\0';
4378 l1 = strlen(p1);
4380 strncpy(p2, prompt_msg ? prompt_msg : "", sizeof(p2)-1);
4381 p2[sizeof(p2)-1] = '\0';
4382 l2 = strlen(p2);
4384 if(rflags && *rflags & GER_ALLPARTS)
4385 p3 = " (and atts)";
4386 else
4387 p3 = "";
4389 l3 = strlen(p3);
4391 snprintf(p4, sizeof(p4), " %s file%s%s",
4392 (flags & GE_IS_IMPORT) ? "from" : "to",
4393 is_absolute_path(filename) ? "" : " in ",
4394 is_absolute_path(filename) ? "" :
4395 (!dir[0] ? "current directory"
4396 : (dir[0] == '~' && !dir[1]) ? "home directory"
4397 : short_str(dir,dirb,sizeof(dirb),30,FrontDots)));
4398 p4[sizeof(p4)-1] = '\0';
4399 l4 = strlen(p4);
4401 snprintf(p5, sizeof(p5), "%s%s%s: ",
4402 *def ? " [" : "",
4403 *def ? short_str(def,fileb,sizeof(fileb),40,EndDots) : "",
4404 *def ? "]" : "");
4405 p5[sizeof(p5)-1] = '\0';
4406 l5 = strlen(p5);
4408 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4409 snprintf(p4, sizeof(p4), " %s file%s%s",
4410 (flags & GE_IS_IMPORT) ? "from" : "to",
4411 is_absolute_path(filename) ? "" : " in ",
4412 is_absolute_path(filename) ? "" :
4413 (!dir[0] ? "current dir"
4414 : (dir[0] == '~' && !dir[1]) ? "home dir"
4415 : short_str(dir,dirb,sizeof(dirb),10,FrontDots)));
4416 p4[sizeof(p4)-1] = '\0';
4417 l4 = strlen(p4);
4420 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4421 snprintf(p5, sizeof(p5), "%s%s%s: ",
4422 *def ? " [" : "",
4423 *def ? short_str(def,fileb,sizeof(fileb),
4424 MAX(15,l5-5-needed),EndDots) : "",
4425 *def ? "]" : "");
4426 p5[sizeof(p5)-1] = '\0';
4427 l5 = strlen(p5);
4430 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l2 > 0){
4433 * 14 is about the shortest we can make this, because there are
4434 * fixed length strings of length 14 coming in here.
4436 p = short_str(prompt_msg, p2, sizeof(p2), MAX(14,l2-needed), FrontDots);
4437 if(p != p2){
4438 strncpy(p2, p, sizeof(p2)-1);
4439 p2[sizeof(p2)-1] = '\0';
4442 l2 = strlen(p2);
4445 if((needed = l1+l2+l3+l4+l5-avail) > 0){
4446 strncpy(p1, "Copy ", sizeof(p1)-1);
4447 p1[sizeof(p1)-1] = '\0';
4448 l1 = strlen(p1);
4451 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){
4452 snprintf(p5, sizeof(p5), "%s%s%s: ",
4453 *def ? " [" : "",
4454 *def ? short_str(def,fileb, sizeof(fileb),
4455 MAX(10,l5-5-needed),EndDots) : "",
4456 *def ? "]" : "");
4457 p5[sizeof(p5)-1] = '\0';
4458 l5 = strlen(p5);
4461 if((needed = l1+l2+l3+l4+l5-avail) > 0 && l3 > 0){
4462 if(needed <= l3 - strlen(" (+ atts)"))
4463 p3 = " (+ atts)";
4464 else if(needed <= l3 - strlen(" (atts)"))
4465 p3 = " (atts)";
4466 else if(needed <= l3 - strlen(" (+)"))
4467 p3 = " (+)";
4468 else if(needed <= l3 - strlen("+"))
4469 p3 = "+";
4470 else
4471 p3 = "";
4473 l3 = strlen(p3);
4476 snprintf(prompt_buf, sizeof(prompt_buf), "%s%s%s%s%s", p1, p2, p3, p4, p5);
4477 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4479 if(kp >= 0){
4480 if(items_in_hist(dir_hist) > 0 || hist_len > 0){ /* any directories */
4481 opts[kp].name = "^Y";
4482 opts[kp].label = "Prev Dir";
4483 opts[kp+1].name = "^V";
4484 opts[kp+1].label = "Next Dir";
4486 else{
4487 opts[kp].name = "";
4488 opts[kp].label = "";
4489 opts[kp+1].name = "";
4490 opts[kp+1].label = "";
4494 if(ku >= 0){
4495 if(items_in_hist(*history) > 0){
4496 opts[ku].name = HISTORY_UP_KEYNAME;
4497 opts[ku].label = HISTORY_KEYLABEL;
4498 opts[ku+1].name = HISTORY_DOWN_KEYNAME;
4499 opts[ku+1].label = HISTORY_KEYLABEL;
4501 else{
4502 opts[ku].name = "";
4503 opts[ku].label = "";
4504 opts[ku+1].name = "";
4505 opts[ku+1].label = "";
4509 oeflags = OE_APPEND_CURRENT |
4510 ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0);
4511 r = optionally_enter(filename, qline, 0, len, prompt_buf,
4512 opts, NO_HELP, &oeflags);
4514 dprint((2, "\n - export_filename = \"%s\", r = %d -\n", filename, r));
4515 /*--- Help ----*/
4516 if(r == 3){
4518 * Helps may not be right if you add another caller or change
4519 * things. Check it out.
4521 if(flags & GE_IS_IMPORT)
4522 helper(h_ge_import, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE);
4523 else if(flags & GE_ALLPARTS)
4524 helper(h_ge_allparts, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4525 else
4526 helper(h_ge_export, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE);
4528 ps->mangled_screen = 1;
4530 continue;
4532 else if(r == 10 || r == 11){ /* Browser or File Completion */
4533 if(filename[0]=='~'){
4534 if(filename[1] == C_FILESEP && filename[2]!='\0'){
4535 precolon[0] = '~';
4536 precolon[1] = '\0';
4537 for(i=0; filename[i+2] != '\0' && i+2 < len-1; i++)
4538 filename[i] = filename[i+2];
4539 filename[i] = '\0';
4540 strncpy(dir, precolon, sizeof(dir)-1);
4541 dir[sizeof(dir)-1] = '\0';
4543 else if(filename[1]=='\0' ||
4544 (filename[1] == C_FILESEP && filename[2] == '\0')){
4545 precolon[0] = '~';
4546 precolon[1] = '\0';
4547 filename[0] = '\0';
4548 strncpy(dir, precolon, sizeof(dir)-1);
4549 dir[sizeof(dir)-1] = '\0';
4552 else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){
4553 if(homedir){
4554 precolon[0] = '~';
4555 precolon[1] = '\0';
4556 strncpy(dir, precolon, sizeof(dir)-1);
4557 dir[sizeof(dir)-1] = '\0';
4559 else{
4560 precolon[0] = '\0';
4561 dir[0] = '\0';
4564 l = MAXPATH;
4565 dir2[0] = '\0';
4566 strncpy(tmp, filename, sizeof(tmp)-1);
4567 tmp[sizeof(tmp)-1] = '\0';
4568 if(*tmp && is_absolute_path(tmp))
4569 fnexpand(tmp, sizeof(tmp));
4570 if(strncmp(tmp,postcolon, strlen(postcolon)))
4571 postcolon[0] = '\0';
4573 if(*tmp && (fn = last_cmpnt(tmp))){
4574 l -= fn - tmp;
4575 strncpy(filename2, fn, sizeof(filename2)-1);
4576 filename2[sizeof(filename2)-1] = '\0';
4577 if(is_absolute_path(tmp)){
4578 strncpy(dir2, tmp, MIN(fn - tmp, sizeof(dir2)-1));
4579 dir2[MIN(fn - tmp, sizeof(dir2)-1)] = '\0';
4580 #ifdef _WINDOWS
4581 if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){
4582 dir2[2] = '\\';
4583 dir2[3] = '\0';
4585 #endif
4586 strncpy(postcolon, dir2, sizeof(postcolon)-1);
4587 postcolon[sizeof(postcolon)-1] = '\0';
4588 precolon[0] = '\0';
4590 else{
4591 char *p = NULL;
4593 * Just building the directory name in dir2,
4594 * full_filename is overloaded.
4596 snprintf(full_filename, len, "%.*s", (int) MIN(fn-tmp,len-1), tmp);
4597 full_filename[len-1] = '\0';
4598 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4599 postcolon[sizeof(postcolon)-1] = '\0';
4600 build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH)
4601 : (dir[0] == '~' && !dir[1])
4602 ? ps->home_dir
4603 : dir,
4604 full_filename, sizeof(dir2));
4605 if(p)
4606 free(p);
4609 else{
4610 if(is_absolute_path(tmp)){
4611 strncpy(dir2, tmp, sizeof(dir2)-1);
4612 dir2[sizeof(dir2)-1] = '\0';
4613 #ifdef _WINDOWS
4614 if(dir2[2]=='\0' && dir2[1]==':'){
4615 dir2[2]='\\';
4616 dir2[3]='\0';
4617 strncpy(postcolon,dir2,sizeof(postcolon)-1);
4618 postcolon[sizeof(postcolon)-1] = '\0';
4620 #endif
4621 filename2[0] = '\0';
4622 precolon[0] = '\0';
4624 else{
4625 strncpy(filename2, tmp, sizeof(filename2)-1);
4626 filename2[sizeof(filename2)-1] = '\0';
4627 if(!dir[0]){
4628 if(getcwd(dir2, sizeof(dir2)) == NULL)
4629 alpine_panic(_("getcwd() call failed at get_export_filename"));
4631 else if(dir[0] == '~' && !dir[1]){
4632 strncpy(dir2, ps->home_dir, sizeof(dir2)-1);
4633 dir2[sizeof(dir2)-1] = '\0';
4635 else{
4636 strncpy(dir2, dir, sizeof(dir2)-1);
4637 dir2[sizeof(dir2)-1] = '\0';
4640 postcolon[0] = '\0';
4644 build_path(full_filename, dir2, filename2, len);
4645 if(!strcmp(full_filename, dir2))
4646 filename2[0] = '\0';
4647 if(full_filename[strlen(full_filename)-1] == C_FILESEP
4648 && isdir(full_filename,NULL,NULL)){
4649 if(strlen(full_filename) == 1)
4650 strncpy(postcolon, full_filename, sizeof(postcolon)-1);
4651 else if(filename2[0])
4652 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4653 postcolon[sizeof(postcolon)-1] = '\0';
4654 strncpy(dir2, full_filename, sizeof(dir2)-1);
4655 dir2[sizeof(dir2)-1] = '\0';
4656 filename2[0] = '\0';
4658 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4659 else if(full_filename[strlen(full_filename)-1] == C_FILESEP){
4660 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4661 postcolon[sizeof(postcolon)-1] = '\0';
4662 strncpy(dir2, full_filename, sizeof(dir2)-1);
4663 dir2[sizeof(dir2)-1] = '\0';
4664 filename2[0] = '\0';
4666 #endif
4667 if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1
4668 && strcmp(dir2+1, ":\\"))
4669 /* last condition to prevent stripping of '\\'
4670 in windows partition */
4671 dir2[strlen(dir2)-1] = '\0';
4673 if(r == 10){ /* File Browser */
4674 r = file_lister(lister_msg ? lister_msg : "EXPORT",
4675 dir2, sizeof(dir2), filename2, sizeof(filename2),
4676 TRUE,
4677 (flags & GE_IS_IMPORT) ? FB_READ : FB_SAVE);
4678 #ifdef _WINDOWS
4679 /* Windows has a special "feature" in which entering the file browser will
4680 change the working directory if the directory is changed at all (even
4681 clicking "Cancel" will change the working directory).
4683 if(F_ON(F_USE_CURRENT_DIR, ps))
4684 (void)getcwd(dir2,sizeof(dir2));
4685 #endif
4686 if(isdir(dir2,NULL,NULL)){
4687 strncpy(precolon, dir2, sizeof(precolon)-1);
4688 precolon[sizeof(precolon)-1] = '\0';
4690 strncpy(postcolon, filename2, sizeof(postcolon)-1);
4691 postcolon[sizeof(postcolon)-1] = '\0';
4692 if(r == 1){
4693 build_path(full_filename, dir2, filename2, len);
4694 if(isdir(full_filename, NULL, NULL)){
4695 strncpy(dir, full_filename, sizeof(dir)-1);
4696 dir[sizeof(dir)-1] = '\0';
4697 filename[0] = '\0';
4699 else{
4700 fn = last_cmpnt(full_filename);
4701 strncpy(dir, full_filename,
4702 MIN(fn - full_filename, sizeof(dir)-1));
4703 dir[MIN(fn - full_filename, sizeof(dir)-1)] = '\0';
4704 if(fn - full_filename > 1)
4705 dir[fn - full_filename - 1] = '\0';
4708 if(!strcmp(dir, ps->home_dir)){
4709 dir[0] = '~';
4710 dir[1] = '\0';
4713 strncpy(filename, fn, len-1);
4714 filename[len-1] = '\0';
4717 else{ /* File Completion */
4718 if(!pico_fncomplete(dir2, filename2, sizeof(filename2)))
4719 Writechar(BELL, 0);
4720 strncat(postcolon, filename2,
4721 sizeof(postcolon)-1-strlen(postcolon));
4722 postcolon[sizeof(postcolon)-1] = '\0';
4724 was_abs_path = is_absolute_path(filename);
4726 if(!strcmp(dir, ps->home_dir)){
4727 dir[0] = '~';
4728 dir[1] = '\0';
4731 strncpy(filename, postcolon, len-1);
4732 filename[len-1] = '\0';
4733 strncpy(dir, precolon, sizeof(dir)-1);
4734 dir[sizeof(dir)-1] = '\0';
4736 if(filename[0] == '~' && !filename[1]){
4737 dir[0] = '~';
4738 dir[1] = '\0';
4739 filename[0] = '\0';
4742 continue;
4744 else if(r == 12){ /* Download, caller handles it */
4745 ret = r;
4746 goto done;
4748 else if(r == 13){ /* toggle AllParts bit */
4749 if(rflags){
4750 if(*rflags & GER_ALLPARTS){
4751 *rflags &= ~GER_ALLPARTS;
4752 opts[allparts].label = N_("AllParts");
4754 else{
4755 *rflags |= GER_ALLPARTS;
4756 /* opposite of All Parts, No All Parts */
4757 opts[allparts].label = N_("NoAllParts");
4761 continue;
4763 #if 0
4764 else if(r == 14){ /* List file names matching partial? */
4765 continue;
4767 #endif
4768 else if(r == 15){ /* toggle Binary bit */
4769 if(rflags){
4770 if(*rflags & GER_BINARY){
4771 *rflags &= ~GER_BINARY;
4772 opts[binary].label = N_("Binary");
4774 else{
4775 *rflags |= GER_BINARY;
4776 opts[binary].label = N_("No Binary");
4780 continue;
4782 else if(r == 1){ /* Cancel */
4783 ret = -1;
4784 goto done;
4786 else if(r == 4){
4787 continue;
4789 else if(r >= 30 && r <= 33){
4790 char *p = NULL;
4792 if(r == 30 || r == 31){
4793 if(history){
4794 if(r == 30)
4795 p = get_prev_hist(*history, filename, 0, NULL);
4796 else if (r == 31)
4797 p = get_next_hist(*history, filename, 0, NULL);
4801 if(r == 32 || r == 33){
4802 int nitems = items_in_hist(dir_hist);
4803 if(dir_hist || hist_len > 0){
4804 if(r == 32){
4805 if(pos > 0)
4806 p = hist_in_pos(--pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4807 else p = last;
4809 else if (r == 33){
4810 if(pos < hist_len + nitems)
4811 p = hist_in_pos(++pos, ps_global->VAR_HISTORY, hist_len, dir_hist, nitems);
4813 if(p == NULL || *p == '\0')
4814 p = orig_dir;
4817 last = p; /* save it! */
4819 if(p != NULL && *p != '\0'){
4820 if(r == 30 || r == 31){
4821 if((fn = last_cmpnt(p)) != NULL){
4822 strncpy(dir, p, MIN(fn - p, sizeof(dir)-1));
4823 dir[MIN(fn - p, sizeof(dir)-1)] = '\0';
4824 if(fn - p > 1)
4825 dir[fn - p - 1] = '\0';
4826 strncpy(filename, fn, len-1);
4827 filename[len-1] = '\0';
4829 } else { /* r == 32 || r == 33 */
4830 strncpy(dir, p, sizeof(dir)-1);
4831 dir[sizeof(dir)-1] = '\0';
4834 if(!strcmp(dir, ps->home_dir)){
4835 dir[0] = '~';
4836 dir[1] = '\0';
4839 else
4840 Writechar(BELL, 0);
4841 continue;
4843 else if(r == 40){
4844 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
4845 SIZEOF_20KBUF, filename);
4846 strncpy(filename, tmp_20k_buf, len);
4847 filename[len-1] = '\0';
4848 continue;
4850 else if(r != 0){
4851 Writechar(BELL, 0);
4852 continue;
4855 removing_leading_and_trailing_white_space(filename);
4857 if(!*filename){
4858 if(!*def){ /* Cancel */
4859 ret = -1;
4860 goto done;
4863 strncpy(filename, def, len-1);
4864 filename[len-1] = '\0';
4867 #if defined(DOS) || defined(OS2)
4868 if(is_absolute_path(filename)){
4869 fixpath(filename, len);
4871 #else
4872 if(filename[0] == '~'){
4873 if(fnexpand(filename, len) == NULL){
4874 char *p = strindex(filename, '/');
4875 if(p != NULL)
4876 *p = '\0';
4877 q_status_message1(SM_ORDER | SM_DING, 3, 3,
4878 _("Error expanding file name: \"%s\" unknown user"),
4879 filename);
4880 continue;
4883 #endif
4885 if(is_absolute_path(filename)){
4886 strncpy(full_filename, filename, len-1);
4887 full_filename[len-1] = '\0';
4889 else{
4890 if(!dir[0])
4891 build_path(full_filename, (char *)getcwd(dir,sizeof(dir)),
4892 filename, len);
4893 else if(dir[0] == '~' && !dir[1])
4894 build_path(full_filename, ps->home_dir, filename, len);
4895 else
4896 build_path(full_filename, dir, filename, len);
4899 if((ill = filter_filename(full_filename, &fatal,
4900 ps_global->restricted || ps_global->VAR_OPER_DIR)) != NULL){
4901 if(fatal){
4902 q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill);
4903 continue;
4905 else{
4906 /* BUG: we should beep when the key's pressed rather than bitch later */
4907 /* Warn and ask for confirmation. */
4908 snprintf(prompt_buf, sizeof(prompt_buf), "File name contains a '%s'. %s anyway",
4909 ill, (flags & GE_IS_EXPORT) ? "Export" : "Save");
4910 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4911 if(want_to(prompt_buf, 'n', 0, NO_HELP,
4912 ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y')
4913 continue;
4917 break; /* Must have got an OK file name */
4920 if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){
4921 ret = -2;
4922 goto done;
4925 if(!can_access(full_filename, ACCESS_EXISTS)){
4926 int rbflags;
4927 static ESCKEY_S access_opts[] = {
4928 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4929 a file or append to the end of the file */
4930 {'o', 'o', "O", N_("Overwrite")},
4931 {'a', 'a', "A", N_("Append")},
4932 {-1, 0, NULL, NULL}};
4934 rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0);
4936 if(flags & GE_NO_APPEND){
4937 r = strlen(filename);
4938 snprintf(prompt_buf, sizeof(prompt_buf),
4939 /* TRANSLATORS: asking user whether to overwrite a file or not,
4940 File <filename> already exists. Overwrite it ? */
4941 _("File \"%s%s\" already exists. Overwrite it "),
4942 (r > 20) ? "..." : "",
4943 filename + ((r > 20) ? r - 20 : 0));
4944 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4945 if(want_to(prompt_buf, 'n', 'x', NO_HELP, rbflags) == 'y'){
4946 if(rflags)
4947 *rflags |= GER_OVER;
4949 if(our_unlink(full_filename) < 0){
4950 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4951 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4952 _("Cannot remove old %s: %s"),
4953 full_filename, error_description(errno));
4956 else{
4957 ret = -1;
4958 goto done;
4961 else if(!(flags & GE_IS_IMPORT)){
4962 r = strlen(filename);
4963 snprintf(prompt_buf, sizeof(prompt_buf),
4964 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4965 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4966 (r > 20) ? "..." : "",
4967 filename + ((r > 20) ? r - 20 : 0));
4968 prompt_buf[sizeof(prompt_buf)-1] = '\0';
4969 switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global),
4970 access_opts, 'a', 'x', NO_HELP, rbflags)){
4971 case 'o' :
4972 if(rflags)
4973 *rflags |= GER_OVER;
4975 if(our_truncate(full_filename, (off_t)0) < 0)
4976 /* trouble truncating, but we'll give it a try anyway */
4977 q_status_message2(SM_ORDER | SM_DING, 3, 5,
4978 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
4979 _("Warning: Cannot truncate old %s: %s"),
4980 full_filename, error_description(errno));
4981 break;
4983 case 'a' :
4984 if(rflags)
4985 *rflags |= GER_APPEND;
4987 break;
4989 case 'x' :
4990 default :
4991 ret = -1;
4992 goto done;
4997 done:
4998 if(history && ret == 0){
4999 save_hist(*history, full_filename, 0, NULL);
5000 strncpy(tmp, full_filename, MAXPATH);
5001 tmp[MAXPATH] = '\0';
5002 if((fn = strrchr(tmp, C_FILESEP)) != NULL)
5003 *fn = '\0';
5004 else
5005 tmp[0] = '\0';
5006 if(tmp[0])
5007 save_hist(dir_hist, tmp, 0, NULL);
5010 if(opts && opts != optsarg)
5011 fs_give((void **) &opts);
5013 return(ret);
5017 /*----------------------------------------------------------------------
5018 parse the config'd upload/download command
5020 Args: cmd -- buffer to return command fit for shellin'
5021 prefix --
5022 cfg_str --
5023 fname -- file name to build into the command
5025 Returns: pointer to cmd_str buffer or NULL on real bad error
5027 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
5028 cfg_str is written to standard out right before a successful
5029 return of this function. The call immediately following this
5030 function darn well better be the shell exec...
5031 ----*/
5032 char *
5033 build_updown_cmd(char *cmd, size_t cmdlen, char *prefix, char *cfg_str, char *fname)
5035 char *p;
5036 int fname_found = 0;
5038 if(prefix && *prefix){
5039 /* loop thru replacing all occurances of _FILE_ */
5040 p = strncpy(cmd, prefix, cmdlen);
5041 cmd[cmdlen-1] = '\0';
5042 while((p = strstr(p, "_FILE_")))
5043 rplstr(p, cmdlen-(p-cmd), 6, fname);
5045 fputs(cmd, stdout);
5048 /* loop thru replacing all occurances of _FILE_ */
5049 p = strncpy(cmd, cfg_str, cmdlen);
5050 cmd[cmdlen-1] = '\0';
5051 while((p = strstr(p, "_FILE_"))){
5052 rplstr(p, cmdlen-(p-cmd), 6, fname);
5053 fname_found = 1;
5056 if(!fname_found)
5057 snprintf(cmd+strlen(cmd), cmdlen-strlen(cmd), " %s", fname);
5059 cmd[cmdlen-1] = '\0';
5061 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
5062 cmd ? cmd : "?"));
5063 return(cmd);
5067 /*----------------------------------------------------------------------
5068 Write a berzerk format message delimiter using the given putc function
5070 Args: e -- envelope of message to write
5071 pc -- function to use
5073 Returns: TRUE if we could write it, FALSE if there was a problem
5075 NOTE: follows delimiter with OS-dependent newline
5076 ----*/
5078 bezerk_delimiter(ENVELOPE *env, MESSAGECACHE *mc, gf_io_t pc, int leading_newline)
5080 MESSAGECACHE telt;
5081 time_t when;
5082 char *p;
5084 /* write "[\n]From mailbox[@host] " */
5085 if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1)
5086 && gf_puts("From ", pc)
5087 && gf_puts((env && env->from) ? env->from->mailbox
5088 : "the-concourse-on-high", pc)
5089 && gf_puts((env && env->from && env->from->host) ? "@" : "", pc)
5090 && gf_puts((env && env->from && env->from->host) ? env->from->host
5091 : "", pc)
5092 && (*pc)(' ')))
5093 return(0);
5095 if(mc && mc->valid)
5096 when = mail_longdate(mc);
5097 else if(env && env->date && env->date[0]
5098 && mail_parse_date(&telt,env->date))
5099 when = mail_longdate(&telt);
5100 else
5101 when = time(0);
5103 p = ctime(&when);
5105 while(p && *p && *p != '\n') /* write date */
5106 if(!(*pc)(*p++))
5107 return(0);
5109 if(!gf_puts(NEWLINE, pc)) /* write terminating newline */
5110 return(0);
5112 return(1);
5116 /*----------------------------------------------------------------------
5117 Execute command to jump to a given message number
5119 Args: qline -- Line to ask question on
5121 Result: returns true if the use selected a new message, false otherwise
5123 ----*/
5124 long
5125 jump_to(MSGNO_S *msgmap, int qline, UCS first_num, SCROLL_S *sparms, CmdWhere in_index)
5127 char jump_num_string[80], *j, prompt[70];
5128 HelpType help;
5129 int rc;
5130 static ESCKEY_S jump_to_key[] = { {0, 0, NULL, NULL},
5131 /* TRANSLATORS: go to First Message */
5132 {ctrl('Y'), 10, "^Y", N_("First Msg")},
5133 {ctrl('V'), 11, "^V", N_("Last Msg")},
5134 {-1, 0, NULL, NULL} };
5136 dprint((4, "\n - jump_to -\n"));
5138 #ifdef DEBUG
5139 if(sparms && sparms->jump_is_debug)
5140 return(get_level(qline, first_num, sparms));
5141 #endif
5143 if(!any_messages(msgmap, NULL, "to Jump to"))
5144 return(0L);
5146 if(first_num && first_num < 0x80 && isdigit((unsigned char) first_num)){
5147 jump_num_string[0] = first_num;
5148 jump_num_string[1] = '\0';
5150 else
5151 jump_num_string[0] = '\0';
5153 if(mn_total_cur(msgmap) > 1L){
5154 snprintf(prompt, sizeof(prompt), "Unselect %s msgs in favor of number to be entered",
5155 comatose(mn_total_cur(msgmap)));
5156 prompt[sizeof(prompt)-1] = '\0';
5157 if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n')
5158 return(0L);
5161 snprintf(prompt, sizeof(prompt), "%s number to jump to : ", in_index == ThrdIndx
5162 ? "Thread"
5163 : "Message");
5164 prompt[sizeof(prompt)-1] = '\0';
5166 help = NO_HELP;
5167 while(1){
5168 int flags = OE_APPEND_CURRENT;
5170 rc = optionally_enter(jump_num_string, qline, 0,
5171 sizeof(jump_num_string), prompt,
5172 jump_to_key, help, &flags);
5173 if(rc == 3){
5174 help = help == NO_HELP
5175 ? (in_index == ThrdIndx ? h_oe_jump_thd : h_oe_jump)
5176 : NO_HELP;
5177 continue;
5179 else if(rc == 10 || rc == 11){
5180 char warning[100];
5181 long closest;
5183 closest = closest_jump_target(rc == 10 ? 1L
5184 : ((in_index == ThrdIndx)
5185 ? msgmap->max_thrdno
5186 : mn_get_total(msgmap)),
5187 ps_global->mail_stream,
5188 msgmap, 0,
5189 in_index, warning, sizeof(warning));
5190 /* ignore warning */
5191 return(closest);
5195 * If we take out the *jump_num_string nonempty test in this if
5196 * then the closest_jump_target routine will offer a jump to the
5197 * last message. However, it is slow because you have to wait for
5198 * the status message and it is annoying for people who hit J command
5199 * by mistake and just want to hit return to do nothing, like has
5200 * always worked. So the test is there for now. Hubert 2002-08-19
5202 * Jumping to first/last message is now possible through ^Y/^V
5203 * commands above. jpf 2002-08-21
5204 * (and through "end" hubert 2006-07-07)
5206 if(rc == 0 && *jump_num_string != '\0'){
5207 removing_leading_and_trailing_white_space(jump_num_string);
5208 for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++)
5211 if(*j != '\0'){
5212 if(!strucmp("end", j))
5213 return((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap));
5215 q_status_message(SM_ORDER | SM_DING, 2, 2,
5216 _("Invalid number entered. Use only digits 0-9"));
5217 jump_num_string[0] = '\0';
5219 else{
5220 char warning[100];
5221 long closest, jump_num;
5223 if(*jump_num_string)
5224 jump_num = atol(jump_num_string);
5225 else
5226 jump_num = -1L;
5228 warning[0] = '\0';
5229 closest = closest_jump_target(jump_num, ps_global->mail_stream,
5230 msgmap,
5231 *jump_num_string ? 0 : 1,
5232 in_index, warning, sizeof(warning));
5233 if(warning[0])
5234 q_status_message(SM_ORDER | SM_DING, 2, 2, warning);
5236 if(closest == jump_num)
5237 return(jump_num);
5239 if(closest == 0L)
5240 jump_num_string[0] = '\0';
5241 else
5242 strncpy(jump_num_string, long2string(closest),
5243 sizeof(jump_num_string));
5246 continue;
5249 if(rc != 4)
5250 break;
5253 return(0L);
5258 * cmd_delete_action - handle msgno advance and such after single message deletion
5260 char *
5261 cmd_delete_action(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
5263 int opts;
5264 long msgno;
5265 char *rv = NULL;
5267 msgno = mn_get_cur(msgmap);
5268 advance_cur_after_delete(state, state->mail_stream, msgmap, in_index);
5270 if(IS_NEWS(state->mail_stream)
5271 || ((state->context_current->use & CNTXT_INCMNG)
5272 && context_isambig(state->cur_folder))){
5274 opts = (NSF_TRUST_FLAGS | NSF_SKIP_CHID);
5275 if(in_index == View)
5276 opts &= ~NSF_SKIP_CHID;
5278 (void)next_sorted_flagged(F_UNDEL|F_UNSEEN, state->mail_stream, msgno, &opts);
5279 if(!(opts & NSF_FLAG_MATCH)){
5280 char nextfolder[MAXPATH];
5282 strncpy(nextfolder, state->cur_folder, sizeof(nextfolder));
5283 nextfolder[sizeof(nextfolder)-1] = '\0';
5284 rv = next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder,
5285 state->context_current, NULL, NULL)
5286 ? ". Press TAB for next folder."
5287 : ". No more folders to TAB to.";
5291 return(rv);
5296 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5298 char *
5299 cmd_delete_index(struct pine *state, MSGNO_S *msgmap)
5301 return(cmd_delete_action(state, msgmap,MsgIndx));
5305 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5307 char *
5308 cmd_delete_view(struct pine *state, MSGNO_S *msgmap)
5310 return(cmd_delete_action(state, msgmap, View));
5314 void
5315 advance_cur_after_delete(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, CmdWhere in_index)
5317 long new_msgno, msgno;
5318 int opts;
5320 new_msgno = msgno = mn_get_cur(msgmap);
5321 opts = NSF_TRUST_FLAGS;
5323 if(F_ON(F_DEL_SKIPS_DEL, state)){
5325 if(THREADING() && sp_viewing_a_thread(stream))
5326 opts |= NSF_SKIP_CHID;
5328 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5330 else{
5331 mn_inc_cur(stream, msgmap,
5332 (in_index == View && THREADING()
5333 && sp_viewing_a_thread(stream))
5334 ? MH_THISTHD
5335 : (in_index == View)
5336 ? MH_ANYTHD : MH_NONE);
5337 new_msgno = mn_get_cur(msgmap);
5338 if(new_msgno != msgno)
5339 opts |= NSF_FLAG_MATCH;
5343 * Viewing_a_thread is the complicated case because we want to ignore
5344 * other threads at first and then look in other threads if we have to.
5345 * By ignoring other threads we also ignore collapsed partial threads
5346 * in our own thread.
5348 if(THREADING() && sp_viewing_a_thread(stream) && !(opts & NSF_FLAG_MATCH)){
5349 long rawno, orig_thrdno;
5350 PINETHRD_S *thrd, *topthrd = NULL;
5352 rawno = mn_m2raw(msgmap, msgno);
5353 thrd = fetch_thread(stream, rawno);
5354 if(thrd && thrd->top)
5355 topthrd = fetch_thread(stream, thrd->top);
5357 orig_thrdno = topthrd ? topthrd->thrdno : -1L;
5359 opts = NSF_TRUST_FLAGS;
5360 new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts);
5363 * If we got a match, new_msgno may be a message in
5364 * a different thread from the one we are viewing, or it could be
5365 * in a collapsed part of this thread.
5367 if(opts & NSF_FLAG_MATCH){
5368 int ret;
5369 char pmt[128];
5371 topthrd = NULL;
5372 thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno));
5373 if(thrd && thrd->top)
5374 topthrd = fetch_thread(stream, thrd->top);
5377 * If this match is in the same thread we're already in
5378 * then we're done, else we have to ask the user and maybe
5379 * switch threads.
5381 if(!(orig_thrdno > 0L && topthrd
5382 && topthrd->thrdno == orig_thrdno)){
5384 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){
5385 if(in_index == View)
5386 snprintf(pmt, sizeof(pmt),
5387 "View message in thread number %.10s",
5388 topthrd ? comatose(topthrd->thrdno) : "?");
5389 else
5390 snprintf(pmt, sizeof(pmt), "View thread number %.10s",
5391 topthrd ? comatose(topthrd->thrdno) : "?");
5393 ret = want_to(pmt, 'y', 'x', NO_HELP, WT_NORM);
5395 else
5396 ret = 'y';
5398 if(ret == 'y'){
5399 unview_thread(state, stream, msgmap);
5400 mn_set_cur(msgmap, new_msgno);
5401 if(THRD_AUTO_VIEW()
5402 && (count_lflags_in_thread(stream, topthrd, msgmap,
5403 MN_NONE) == 1)
5404 && view_thread(state, stream, msgmap, 1)){
5405 if(current_index_state)
5406 msgmap->top_after_thrd = current_index_state->msg_at_top;
5408 state->view_skipped_index = 1;
5409 state->next_screen = mail_view_screen;
5411 else{
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->next_screen = SCREEN_FUN_NULL;
5419 else
5420 new_msgno = msgno; /* stick with original */
5425 mn_set_cur(msgmap, new_msgno);
5426 if(in_index != View)
5427 adjust_cur_to_visible(stream, msgmap);
5431 #ifdef DEBUG
5432 long
5433 get_level(int qline, UCS first_num, SCROLL_S *sparms)
5435 char debug_num_string[80], *j, prompt[70];
5436 HelpType help;
5437 int rc;
5438 long debug_num;
5440 if(first_num && first_num < 0x80 && isdigit((unsigned char)first_num)){
5441 debug_num_string[0] = first_num;
5442 debug_num_string[1] = '\0';
5443 debug_num = atol(debug_num_string);
5444 *(int *)(sparms->proc.data.p) = debug_num;
5445 q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s",
5446 comatose(debug_num));
5447 return(1L);
5449 else
5450 debug_num_string[0] = '\0';
5452 snprintf(prompt, sizeof(prompt), "Show debug <= this level (0-%d) : ", MAX(debug, 9));
5453 prompt[sizeof(prompt)-1] = '\0';
5455 help = NO_HELP;
5456 while(1){
5457 int flags = OE_APPEND_CURRENT;
5459 rc = optionally_enter(debug_num_string, qline, 0,
5460 sizeof(debug_num_string), prompt,
5461 NULL, help, &flags);
5462 if(rc == 3){
5463 help = help == NO_HELP ? h_oe_debuglevel : NO_HELP;
5464 continue;
5467 if(rc == 0){
5468 removing_leading_and_trailing_white_space(debug_num_string);
5469 for(j=debug_num_string; isdigit((unsigned char)*j); j++)
5472 if(*j != '\0'){
5473 q_status_message(SM_ORDER | SM_DING, 2, 2,
5474 _("Invalid number entered. Use only digits 0-9"));
5475 debug_num_string[0] = '\0';
5477 else{
5478 debug_num = atol(debug_num_string);
5479 if(debug_num < 0)
5480 q_status_message(SM_ORDER | SM_DING, 2, 2,
5481 _("Number should be >= 0"));
5482 else if(debug_num > MAX(debug,9))
5483 q_status_message1(SM_ORDER | SM_DING, 2, 2,
5484 _("Maximum is %s"), comatose(MAX(debug,9)));
5485 else{
5486 *(int *)(sparms->proc.data.p) = debug_num;
5487 q_status_message1(SM_ORDER, 0, 3,
5488 "Show debug <= level %s",
5489 comatose(debug_num));
5490 return(1L);
5494 continue;
5497 if(rc != 4)
5498 break;
5501 return(0L);
5503 #endif /* DEBUG */
5507 * Returns the message number closest to target that isn't hidden.
5508 * Make warning at least 100 chars.
5509 * A return of 0 means there is no message to jump to.
5511 long
5512 closest_jump_target(long int target, MAILSTREAM *stream, MSGNO_S *msgmap, int no_target, CmdWhere in_index, char *warning, size_t warninglen)
5514 long i, start, closest = 0L;
5515 char buf[80];
5516 long maxnum;
5518 warning[0] = '\0';
5519 maxnum = (in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap);
5521 if(no_target){
5522 target = maxnum;
5523 start = 1L;
5524 snprintf(warning, warninglen, "No %s number entered, jump to end? ",
5525 (in_index == ThrdIndx) ? "thread" : "message");
5526 warning[warninglen-1] = '\0';
5528 else if(target < 1L)
5529 start = 1L - target;
5530 else if(target > maxnum)
5531 start = target - maxnum;
5532 else
5533 start = 1L;
5535 if(target > 0L && target <= maxnum)
5536 if(in_index == ThrdIndx
5537 || !msgline_hidden(stream, msgmap, target, 0))
5538 return(target);
5540 for(i = start; target+i <= maxnum || target-i > 0L; i++){
5542 if(target+i > 0L && target+i <= maxnum &&
5543 (in_index == ThrdIndx
5544 || !msgline_hidden(stream, msgmap, target+i, 0))){
5545 closest = target+i;
5546 break;
5549 if(target-i > 0L && target-i <= maxnum &&
5550 (in_index == ThrdIndx
5551 || !msgline_hidden(stream, msgmap, target-i, 0))){
5552 closest = target-i;
5553 break;
5557 strncpy(buf, long2string(closest), sizeof(buf));
5558 buf[sizeof(buf)-1] = '\0';
5560 if(closest == 0L)
5561 strncpy(warning, "Nothing to jump to", warninglen);
5562 else if(target < 1L)
5563 snprintf(warning, warninglen, "%s number (%s) must be at least %s",
5564 (in_index == ThrdIndx) ? "Thread" : "Message",
5565 long2string(target), buf);
5566 else if(target > maxnum)
5567 snprintf(warning, warninglen, "%s number (%s) may be no more than %s",
5568 (in_index == ThrdIndx) ? "Thread" : "Message",
5569 long2string(target), buf);
5570 else if(!no_target)
5571 snprintf(warning, warninglen,
5572 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5573 long2string(target), buf);
5575 warning[warninglen-1] = '\0';
5577 return(closest);
5581 /*----------------------------------------------------------------------
5582 Prompt for folder name to open, expand the name and return it
5584 Args: qline -- Screen line to prompt on
5585 allow_list -- if 1, allow ^T to bring up collection lister
5587 Result: returns the folder name or NULL
5588 pine structure mangled_footer flag is set
5589 may call the collection lister in which case mangled screen will be set
5591 This prompts the user for the folder to open, possibly calling up
5592 the collection lister if the user types ^T.
5593 ----------------------------------------------------------------------*/
5594 char *
5595 broach_folder(int qline, int allow_list, int *notrealinbox, CONTEXT_S **context)
5597 HelpType help;
5598 static char newfolder[MAILTMPLEN];
5599 char expanded[MAXPATH+1],
5600 prompt[MAX_SCREEN_COLS+1],
5601 *last_folder, *p;
5602 unsigned char *f1, *f2, *f3;
5603 static HISTORY_S *history = NULL;
5604 CONTEXT_S *tc, *tc2;
5605 ESCKEY_S ekey[9];
5606 int rc, r, ku = -1, n, flags, last_rc = 0, inbox, done = 0;
5609 * the idea is to provide a clue for the context the file name
5610 * will be saved in (if a non-imap names is typed), and to
5611 * only show the previous if it was also in the same context
5613 help = NO_HELP;
5614 *expanded = '\0';
5615 *newfolder = '\0';
5616 last_folder = NULL;
5617 if(notrealinbox)
5618 (*notrealinbox) = 1;
5620 init_hist(&history, HISTSIZE);
5622 tc = broach_get_folder(context ? *context : NULL, &inbox, NULL);
5624 /* set up extra command option keys */
5625 rc = 0;
5626 ekey[rc].ch = (allow_list) ? ctrl('T') : 0 ;
5627 ekey[rc].rval = (allow_list) ? 2 : 0;
5628 ekey[rc].name = (allow_list) ? "^T" : "";
5629 ekey[rc++].label = (allow_list) ? N_("ToFldrs") : "";
5631 if(ps_global->context_list->next){
5632 ekey[rc].ch = ctrl('P');
5633 ekey[rc].rval = 10;
5634 ekey[rc].name = "^P";
5635 ekey[rc++].label = N_("Prev Collection");
5637 ekey[rc].ch = ctrl('N');
5638 ekey[rc].rval = 11;
5639 ekey[rc].name = "^N";
5640 ekey[rc++].label = N_("Next Collection");
5643 ekey[rc].ch = ctrl('W');
5644 ekey[rc].rval = 17;
5645 ekey[rc].name = "^W";
5646 ekey[rc++].label = N_("INBOX");
5648 if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){
5649 ekey[rc].ch = TAB;
5650 ekey[rc].rval = 12;
5651 ekey[rc].name = "TAB";
5652 ekey[rc++].label = N_("Complete");
5655 if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){
5656 ekey[rc].ch = ctrl('X');
5657 ekey[rc].rval = 14;
5658 ekey[rc].name = "^X";
5659 ekey[rc++].label = N_("ListMatches");
5662 if(ps_global->context_list->next && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5663 ekey[rc].ch = KEY_UP;
5664 ekey[rc].rval = 10;
5665 ekey[rc].name = "";
5666 ekey[rc++].label = "";
5668 ekey[rc].ch = KEY_DOWN;
5669 ekey[rc].rval = 11;
5670 ekey[rc].name = "";
5671 ekey[rc++].label = "";
5673 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){
5674 ekey[rc].ch = KEY_UP;
5675 ekey[rc].rval = 30;
5676 ekey[rc].name = "";
5677 ku = rc;
5678 ekey[rc++].label = "";
5680 ekey[rc].ch = KEY_DOWN;
5681 ekey[rc].rval = 31;
5682 ekey[rc].name = "";
5683 ekey[rc++].label = "";
5686 ekey[rc].ch = -1;
5688 while(!done) {
5690 * Figure out next default value for this context. The idea
5691 * is that in each context the last folder opened is cached.
5692 * It's up to pick it out and display it. This is fine
5693 * and dandy if we've currently got the inbox open, BUT
5694 * if not, make the inbox the default the first time thru.
5696 if(!inbox){
5697 last_folder = ps_global->inbox_name;
5698 inbox = 1; /* pretend we're in inbox from here on out */
5700 else
5701 last_folder = (ps_global->last_unambig_folder[0])
5702 ? ps_global->last_unambig_folder
5703 : ((tc->last_folder[0]) ? tc->last_folder : NULL);
5705 if(last_folder){ /* MAXPATH + 1 = sizeof(expanded) */
5706 unsigned char *fname = folder_name_decoded((unsigned char *)last_folder);
5707 snprintf(expanded, sizeof(expanded), " [%.*s]", MAXPATH+1-5,
5708 fname ? (char *) fname : last_folder);
5709 if(fname) fs_give((void **)&fname);
5711 else
5712 *expanded = '\0';
5714 expanded[sizeof(expanded)-1] = '\0';
5716 /* only show collection number if more than one available */
5717 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5718 snprintf(prompt, sizeof(prompt), "GOTO %s in <%s> %.*s%s: ",
5719 NEWS_TEST(tc) ? "news group" : "folder",
5720 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5721 *expanded ? " " : "");
5722 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5723 snprintf(prompt, sizeof(prompt), "GOTO folder %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5724 *expanded ? " " : "");
5726 prompt[sizeof(prompt)-1] = '\0';
5728 if(utf8_width(prompt) > MAXPROMPT){
5729 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5730 snprintf(prompt, sizeof(prompt), "GOTO <%s> %.*s%s: ",
5731 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5732 *expanded ? " " : "");
5733 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5734 snprintf(prompt, sizeof(prompt), "GOTO %.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5735 *expanded ? " " : "");
5737 prompt[sizeof(prompt)-1] = '\0';
5739 if(utf8_width(prompt) > MAXPROMPT){
5740 if(ps_global->context_list->next) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5741 snprintf(prompt, sizeof(prompt), "<%s> %.*s%s: ",
5742 tc->nickname, MAX_SCREEN_COLS+1-50, expanded,
5743 *expanded ? " " : "");
5744 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5745 snprintf(prompt, sizeof(prompt), "%.*s%s: ", MAX_SCREEN_COLS+1-20, expanded,
5746 *expanded ? " " : "");
5748 prompt[sizeof(prompt)-1] = '\0';
5752 if(ku >= 0){
5753 if(items_in_hist(history) > 1){
5754 ekey[ku].name = HISTORY_UP_KEYNAME;
5755 ekey[ku].label = HISTORY_KEYLABEL;
5756 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
5757 ekey[ku+1].label = HISTORY_KEYLABEL;
5759 else{
5760 ekey[ku].name = "";
5761 ekey[ku].label = "";
5762 ekey[ku+1].name = "";
5763 ekey[ku+1].label = "";
5767 /* is there any other way to do this? The point is that we
5768 * are trying to hide mutf7 from the user, and use the utf8
5769 * equivalent. So we create a variable f to take place of
5770 * newfolder, including content and size. f2 is copy of f1
5771 * that has to freed. Sigh!
5773 f3 = (unsigned char *) cpystr(newfolder);
5774 f1 = fs_get(sizeof(newfolder));
5775 f2 = folder_name_decoded(f3);
5776 if(f3) fs_give((void **)&f3);
5777 strncpy((char *)f1, (char *)f2, sizeof(newfolder));
5778 f1[sizeof(newfolder)-1] = '\0';
5779 if(f2) fs_give((void **)&f2);
5781 flags = OE_APPEND_CURRENT;
5782 rc = optionally_enter((char *) f1, qline, 0, sizeof(newfolder),
5783 (char *) prompt, ekey, help, &flags);
5785 f2 = folder_name_encoded(f1);
5786 strncpy(newfolder, (char *)f2, sizeof(newfolder));
5787 if(f1) fs_give((void **)&f1);
5788 if(f2) fs_give((void **)&f2);
5790 ps_global->mangled_footer = 1;
5792 switch(rc){
5793 case -1 : /* o_e says error! */
5794 q_status_message(SM_ORDER | SM_DING, 3, 3,
5795 _("Error reading folder name"));
5796 return(NULL);
5798 case 0 : /* o_e says normal entry */
5799 removing_trailing_white_space(newfolder);
5800 removing_leading_white_space(newfolder);
5802 if(*newfolder){
5803 char *name, *fullname = NULL;
5804 int exists, breakout = 0;
5806 save_hist(history, newfolder, 0, tc);
5808 if(!(name = folder_is_nick(newfolder, FOLDERS(tc),
5809 FN_WHOLE_NAME)))
5810 name = newfolder;
5812 if(update_folder_spec(expanded, sizeof(expanded), name)){
5813 strncpy(name = newfolder, expanded, sizeof(newfolder));
5814 newfolder[sizeof(newfolder)-1] = '\0';
5817 exists = folder_name_exists(tc, name, &fullname);
5819 if(fullname){
5820 strncpy(name = newfolder, fullname, sizeof(newfolder));
5821 newfolder[sizeof(newfolder)-1] = '\0';
5822 fs_give((void **) &fullname);
5823 breakout = TRUE;
5827 * if we know the things a folder, open it.
5828 * else if we know its a directory, visit it.
5829 * else we're not sure (it either doesn't really
5830 * exist or its unLISTable) so try opening it anyway
5832 if(exists & FEX_ISFILE){
5833 done++;
5834 break;
5836 else if((exists & FEX_ISDIR)){
5837 if(breakout){
5838 CONTEXT_S *fake_context;
5839 char tmp[MAILTMPLEN];
5840 size_t l;
5842 strncpy(tmp, name, sizeof(tmp));
5843 tmp[sizeof(tmp)-2-1] = '\0';
5844 if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){
5845 if(l < sizeof(tmp)){
5846 tmp[l] = tc->dir->delim;
5847 strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1));
5850 else
5851 strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1);
5853 tmp[sizeof(tmp)-1] = '\0';
5855 fake_context = new_context(tmp, 0);
5856 newfolder[0] = '\0';
5857 done = display_folder_list(&fake_context, newfolder,
5858 1, folders_for_goto);
5859 free_context(&fake_context);
5860 break;
5862 else if(!(tc->use & CNTXT_INCMNG)){
5863 done = display_folder_list(&tc, newfolder,
5864 1, folders_for_goto);
5865 break;
5868 else if((exists & FEX_ERROR)){
5869 q_status_message1(SM_ORDER, 0, 3,
5870 _("Problem accessing folder \"%s\""),
5871 newfolder);
5872 return(NULL);
5874 else{
5875 done++;
5876 break;
5879 if(exists == FEX_ERROR)
5880 q_status_message1(SM_ORDER, 0, 3,
5881 _("Problem accessing folder \"%s\""),
5882 newfolder);
5883 else if(tc->use & CNTXT_INCMNG)
5884 q_status_message1(SM_ORDER, 0, 3,
5885 _("Can't find Incoming Folder: %s"),
5886 newfolder);
5887 else if(context_isambig(newfolder))
5888 q_status_message2(SM_ORDER, 0, 3,
5889 _("Can't find folder \"%s\" in %s"),
5890 newfolder, (void *) tc->nickname);
5891 else
5892 q_status_message1(SM_ORDER, 0, 3,
5893 _("Can't find folder \"%s\""),
5894 newfolder);
5896 return(NULL);
5898 else if(last_folder){
5899 if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX
5900 && !strucmp(last_folder, ps_global->inbox_name)
5901 && tc == ((ps_global->context_list->use & CNTXT_INCMNG)
5902 ? ps_global->context_list->next : ps_global->context_list)){
5903 if(notrealinbox)
5904 (*notrealinbox) = 0;
5906 tc = ps_global->context_list;
5909 strncpy(newfolder, last_folder, sizeof(newfolder));
5910 newfolder[sizeof(newfolder)-1] = '\0';
5911 save_hist(history, newfolder, 0, tc);
5912 done++;
5913 break;
5915 /* fall thru like they cancelled */
5917 case 1 : /* o_e says user cancel */
5918 cmd_cancelled("Open folder");
5919 return(NULL);
5921 case 2 : /* o_e says user wants list */
5922 r = display_folder_list(&tc, newfolder, 0, folders_for_goto);
5923 if(r)
5924 done++;
5926 break;
5928 case 3 : /* o_e says user wants help */
5929 help = help == NO_HELP ? h_oe_broach : NO_HELP;
5930 break;
5932 case 4 : /* redraw */
5933 break;
5935 case 10 : /* Previous collection */
5936 tc2 = ps_global->context_list;
5937 while(tc2->next && tc2->next != tc)
5938 tc2 = tc2->next;
5940 tc = tc2;
5941 break;
5943 case 11 : /* Next collection */
5944 tc = (tc->next) ? tc->next : ps_global->context_list;
5945 break;
5947 case 12 : /* file name completion */
5948 if(!folder_complete(tc, newfolder, sizeof(newfolder), &n)){
5949 if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){
5950 r = display_folder_list(&tc, newfolder, 1,folders_for_goto);
5951 if(r)
5952 done++; /* bingo! */
5953 else
5954 rc = 0; /* burn last_rc */
5956 else
5957 Writechar(BELL, 0);
5960 break;
5962 case 14 : /* file name completion */
5963 r = display_folder_list(&tc, newfolder, 2, folders_for_goto);
5964 if(r)
5965 done++; /* bingo! */
5966 else
5967 rc = 0; /* burn last_rc */
5969 break;
5971 case 17 : /* GoTo INBOX */
5972 done++;
5973 strncpy(newfolder, ps_global->inbox_name, sizeof(newfolder)-1);
5974 newfolder[sizeof(newfolder)-1] = '\0';
5975 if(notrealinbox)
5976 (*notrealinbox) = 0;
5978 tc = ps_global->context_list;
5979 save_hist(history, newfolder, 0, tc);
5981 break;
5983 case 30 :
5984 if((p = get_prev_hist(history, newfolder, 0, tc)) != NULL){
5985 strncpy(newfolder, p, sizeof(newfolder));
5986 newfolder[sizeof(newfolder)-1] = '\0';
5987 if(history->hist[history->curindex])
5988 tc = history->hist[history->curindex]->cntxt;
5990 else
5991 Writechar(BELL, 0);
5993 break;
5995 case 31 :
5996 if((p = get_next_hist(history, newfolder, 0, tc)) != NULL){
5997 strncpy(newfolder, p, sizeof(newfolder));
5998 newfolder[sizeof(newfolder)-1] = '\0';
5999 if(history->hist[history->curindex])
6000 tc = history->hist[history->curindex]->cntxt;
6002 else
6003 Writechar(BELL, 0);
6005 break;
6007 default :
6008 alpine_panic("Unhandled case");
6009 break;
6012 last_rc = rc;
6015 dprint((2, "broach folder, name entered \"%s\"\n",
6016 newfolder ? newfolder : "?"));
6018 /*-- Just check that we can expand this. It gets done for real later --*/
6019 strncpy(expanded, newfolder, sizeof(expanded));
6020 expanded[sizeof(expanded)-1] = '\0';
6022 if(!expand_foldername(expanded, sizeof(expanded))) {
6023 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
6024 expanded ? expanded : "?"));
6025 return(NULL);
6028 *context = tc;
6029 return(newfolder);
6033 /*----------------------------------------------------------------------
6034 Check to see if user wants to reopen dead stream.
6036 Args: ps --
6037 reopenp --
6039 Result: 1 if the folder was successfully updatedn
6040 0 if not necessary
6042 ----*/
6044 ask_mailbox_reopen(struct pine *ps, int *reopenp)
6046 if(((ps->mail_stream->dtb
6047 && ((ps->mail_stream->dtb->flags & DR_NONEWMAIL)
6048 || (ps->mail_stream->rdonly
6049 && ps->mail_stream->dtb->flags & DR_NONEWMAILRONLY)))
6050 && (ps->reopen_rule == REOPEN_ASK_ASK_Y
6051 || ps->reopen_rule == REOPEN_ASK_ASK_N
6052 || ps->reopen_rule == REOPEN_ASK_NO_Y
6053 || ps->reopen_rule == REOPEN_ASK_NO_N))
6054 || ((ps->mail_stream->dtb
6055 && ps->mail_stream->rdonly
6056 && !(ps->mail_stream->dtb->flags & DR_LOCAL))
6057 && (ps->reopen_rule == REOPEN_YES_ASK_Y
6058 || ps->reopen_rule == REOPEN_YES_ASK_N
6059 || ps->reopen_rule == REOPEN_ASK_ASK_Y
6060 || ps->reopen_rule == REOPEN_ASK_ASK_N))){
6061 int deefault;
6063 switch(ps->reopen_rule){
6064 case REOPEN_YES_ASK_Y:
6065 case REOPEN_ASK_ASK_Y:
6066 case REOPEN_ASK_NO_Y:
6067 deefault = 'y';
6068 break;
6070 default:
6071 deefault = 'n';
6072 break;
6075 switch(want_to("Re-open folder to check for new messages", deefault,
6076 'x', h_reopen_folder, WT_NORM)){
6077 case 'y':
6078 (*reopenp)++;
6079 break;
6081 case 'x':
6082 return(-1);
6086 return(0);
6091 /*----------------------------------------------------------------------
6092 Check to see if user input is in form of old c-client mailbox speck
6094 Args: old --
6095 new --
6097 Result: 1 if the folder was successfully updatedn
6098 0 if not necessary
6100 ----*/
6102 update_folder_spec(char *new, size_t newlen, char *old)
6104 char *p, *orignew;
6105 int nntp = 0;
6107 orignew = new;
6108 if(*(p = old) == '*') /* old form? */
6109 old++;
6111 if(*old == '{') /* copy host spec */
6113 switch(*new = *old++){
6114 case '\0' :
6115 return(FALSE);
6117 case '/' :
6118 if(!struncmp(old, "nntp", 4))
6119 nntp++;
6121 break;
6123 default :
6124 break;
6126 while(*new++ != '}' && (new-orignew) < newlen-1);
6128 if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){
6130 * OK, some heuristics here. If it looks like a newsgroup
6131 * then we plunk it into the #news namespace else we
6132 * assume that they're trying to get at a #public folder...
6134 for(p = old;
6135 *p && (isalnum((unsigned char) *p) || strindex(".-", *p));
6136 p++)
6139 sstrncpy(&new, (*p && !nntp) ? "#public/" : "#news.", newlen-(new-orignew));
6140 strncpy(new, old, newlen-(new-orignew));
6141 return(TRUE);
6144 orignew[newlen-1] = '\0';
6146 return(FALSE);
6150 /*----------------------------------------------------------------------
6151 Open the requested folder in the requested context
6153 Args: state -- usual pine state struct
6154 newfolder -- folder to open
6155 new_context -- folder context might live in
6156 stream -- candidate for recycling
6158 Result: New folder open or not (if error), and we're set to
6159 enter the index screen.
6160 ----*/
6161 void
6162 visit_folder(struct pine *state, char *newfolder, CONTEXT_S *new_context,
6163 MAILSTREAM *stream, long unsigned int flags)
6165 dprint((9, "visit_folder(%s, %s)\n",
6166 newfolder ? newfolder : "?",
6167 (new_context && new_context->context)
6168 ? new_context->context : "(NULL)"));
6170 if(ps_global && ps_global->ttyo){
6171 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
6172 ps_global->mangled_footer = 1;
6175 if(do_broach_folder(newfolder, new_context, stream ? &stream : NULL,
6176 flags) >= 0
6177 || !sp_flagged(state->mail_stream, SP_LOCKED))
6178 state->next_screen = mail_index_screen;
6179 else
6180 state->next_screen = folder_screen;
6184 /*----------------------------------------------------------------------
6185 Move read messages from folder if listed in archive
6187 Args:
6189 ----*/
6191 read_msg_prompt(long int n, char *f)
6193 char buf[MAX_SCREEN_COLS+1];
6195 snprintf(buf, sizeof(buf), "Save the %ld read message%s in \"%s\"", n, plural(n), f);
6196 buf[sizeof(buf)-1] = '\0';
6197 return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y');
6201 /*----------------------------------------------------------------------
6202 Print current message[s] or folder index
6204 Args: state -- pointer to struct holding a bunch of pine state
6205 msgmap -- table mapping msg nums to c-client sequence nums
6206 aopt -- aggregate options
6207 in_index -- boolean indicating we're called from Index Screen
6209 Filters the original header and sends stuff to printer
6210 ---*/
6212 cmd_print(struct pine *state, MSGNO_S *msgmap, int aopt, CmdWhere in_index)
6214 char prompt[250];
6215 long i, msgs, rawno;
6216 int next = 0, do_index = 0, rv = 0;
6217 ENVELOPE *e;
6218 BODY *b;
6219 MESSAGECACHE *mc;
6221 if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap))
6222 return rv;
6224 msgs = mn_total_cur(msgmap);
6226 if((in_index != View) && F_ON(F_PRINT_INDEX, state)){
6227 char m[10];
6228 int ans;
6229 static ESCKEY_S prt_opts[] = {
6230 {'i', 'i', "I", N_("Index")},
6231 {'m', 'm', "M", NULL},
6232 {-1, 0, NULL, NULL}};
6234 if(in_index == ThrdIndx){
6235 /* TRANSLATORS: This is a question, Print Index ? */
6236 if(want_to(_("Print Index"), 'y', 'x', NO_HELP, WT_NORM) == 'y')
6237 ans = 'i';
6238 else
6239 ans = 'x';
6241 else{
6242 snprintf(m, sizeof(m), "Message%s", (msgs>1L) ? "s" : "");
6243 m[sizeof(m)-1] = '\0';
6244 prt_opts[1].label = m;
6245 snprintf(prompt, sizeof(prompt), "Print %sFolder Index or %s %s? ",
6246 (aopt & MCMD_AGG_2) ? "thread " : MCMD_ISAGG(aopt) ? "selected " : "",
6247 (aopt & MCMD_AGG_2) ? "thread" : MCMD_ISAGG(aopt) ? "selected" : "current", m);
6248 prompt[sizeof(prompt)-1] = '\0';
6250 ans = radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x',
6251 NO_HELP, RB_NORM|RB_SEQ_SENSITIVE);
6254 switch(ans){
6255 case 'x' :
6256 cmd_cancelled("Print");
6257 if(MCMD_ISAGG(aopt))
6258 restore_selected(msgmap);
6260 return rv;
6262 case 'i':
6263 do_index = 1;
6264 break;
6266 default :
6267 case 'm':
6268 break;
6272 if(do_index)
6273 snprintf(prompt, sizeof(prompt), "%sFolder Index",
6274 (aopt & MCMD_AGG_2) ? "Thread " : MCMD_ISAGG(aopt) ? "Selected " : "");
6275 else if(msgs > 1L)
6276 snprintf(prompt, sizeof(prompt), "%s messages", long2string(msgs));
6277 else
6278 snprintf(prompt, sizeof(prompt), "Message %s", long2string(mn_get_cur(msgmap)));
6280 prompt[sizeof(prompt)-1] = '\0';
6282 if(open_printer(prompt) < 0){
6283 if(MCMD_ISAGG(aopt))
6284 restore_selected(msgmap);
6286 return rv;
6289 if(do_index){
6290 TITLE_S *tc;
6292 tc = format_titlebar();
6294 /* Print titlebar... */
6295 print_text1("%s\n\n", tc ? tc->titlebar_line : "");
6296 /* then all the index members... */
6297 if(!print_index(state, msgmap, MCMD_ISAGG(aopt)))
6298 q_status_message(SM_ORDER | SM_DING, 3, 3,
6299 _("Error printing folder index"));
6300 else
6301 rv++;
6303 else{
6304 rv++;
6305 for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){
6306 if(next && F_ON(F_AGG_PRINT_FF, state))
6307 if(!print_char(FORMFEED)){
6308 rv = 0;
6309 break;
6312 if(!(state->mail_stream
6313 && (rawno = mn_m2raw(msgmap, i)) > 0L
6314 && rawno <= state->mail_stream->nmsgs
6315 && (mc = mail_elt(state->mail_stream, rawno))
6316 && mc->valid))
6317 mc = NULL;
6319 if(!(e=pine_mail_fetchstructure(state->mail_stream,
6320 mn_m2raw(msgmap,i),
6321 &b))
6322 || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global)
6323 && !bezerk_delimiter(e, mc, print_char, next))
6324 || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)),
6325 e, b, NULL, FM_NEW_MESS | FM_NOINDENT,
6326 print_char)){
6327 q_status_message(SM_ORDER | SM_DING, 3, 3,
6328 _("Error printing message"));
6329 rv = 0;
6330 break;
6335 close_printer();
6337 if(MCMD_ISAGG(aopt))
6338 restore_selected(msgmap);
6340 return rv;
6344 /*----------------------------------------------------------------------
6345 Pipe message text
6347 Args: state -- various pine state bits
6348 msgmap -- Message number mapping table
6349 aopt -- option flags
6351 Filters the original header and sends stuff to specified command
6352 ---*/
6354 cmd_pipe(struct pine *state, MSGNO_S *msgmap, int aopt)
6356 ENVELOPE *e;
6357 MESSAGECACHE *mc;
6358 BODY *b;
6359 PIPE_S *syspipe;
6360 char *resultfilename = NULL, prompt[80], *p;
6361 int done = 0, rv = 0;
6362 gf_io_t pc;
6363 int fourlabel = -1, j = 0, next = 0, ku;
6364 int pipe_rv; /* rv of proc to separate from close_system_pipe rv */
6365 long i, rawno;
6366 unsigned flagsforhist = 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6367 static HISTORY_S *history = NULL;
6368 int capture = 1, raw = 0, delimit = 0, newpipe = 0;
6369 char pipe_command[MAXPATH];
6370 ESCKEY_S pipe_opt[8];
6372 if(ps_global->restricted){
6373 q_status_message(SM_ORDER | SM_DING, 0, 4,
6374 "Alpine demo can't pipe messages");
6375 return rv;
6377 else if(!any_messages(msgmap, NULL, "to Pipe"))
6378 return rv;
6380 pipe_command[0] = '\0';
6381 init_hist(&history, HISTSIZE);
6382 flagsforhist = (raw ? 0x8 : 0) +
6383 (delimit ? 0x4 : 0) +
6384 (newpipe ? 0x2 : 0) +
6385 (capture ? 0x1 : 0);
6386 if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){
6387 strncpy(pipe_command, p, sizeof(pipe_command));
6388 pipe_command[sizeof(pipe_command)-1] = '\0';
6389 if(history->hist[history->curindex]){
6390 flagsforhist = history->hist[history->curindex]->flags;
6391 raw = (flagsforhist & 0x8) ? 1 : 0;
6392 delimit = (flagsforhist & 0x4) ? 1 : 0;
6393 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6394 capture = (flagsforhist & 0x1) ? 1 : 0;
6398 pipe_opt[j].ch = 0;
6399 pipe_opt[j].rval = 0;
6400 pipe_opt[j].name = "";
6401 pipe_opt[j++].label = "";
6403 pipe_opt[j].ch = ctrl('W');
6404 pipe_opt[j].rval = 10;
6405 pipe_opt[j].name = "^W";
6406 pipe_opt[j++].label = NULL;
6408 pipe_opt[j].ch = ctrl('Y');
6409 pipe_opt[j].rval = 11;
6410 pipe_opt[j].name = "^Y";
6411 pipe_opt[j++].label = NULL;
6413 pipe_opt[j].ch = ctrl('R');
6414 pipe_opt[j].rval = 12;
6415 pipe_opt[j].name = "^R";
6416 pipe_opt[j++].label = NULL;
6418 if(MCMD_ISAGG(aopt)){
6419 if(!pseudo_selected(state->mail_stream, msgmap))
6420 return rv;
6421 else{
6422 fourlabel = j;
6423 pipe_opt[j].ch = ctrl('T');
6424 pipe_opt[j].rval = 13;
6425 pipe_opt[j].name = "^T";
6426 pipe_opt[j++].label = NULL;
6430 pipe_opt[j].ch = KEY_UP;
6431 pipe_opt[j].rval = 30;
6432 pipe_opt[j].name = "";
6433 ku = j;
6434 pipe_opt[j++].label = "";
6436 pipe_opt[j].ch = KEY_DOWN;
6437 pipe_opt[j].rval = 31;
6438 pipe_opt[j].name = "";
6439 pipe_opt[j++].label = "";
6441 pipe_opt[j].ch = -1;
6443 while (!done) {
6444 int flags;
6446 snprintf(prompt, sizeof(prompt), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6447 raw ? "RAW " : "",
6448 MCMD_ISAGG(aopt) ? "s" : " ",
6449 MCMD_ISAGG(aopt) ? "" : comatose(mn_get_cur(msgmap)),
6450 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? "(" : "",
6451 capture ? "" : "uncaptured",
6452 (!capture && delimit) ? "," : "",
6453 delimit ? "delimited" : "",
6454 ((!capture || delimit) && newpipe && MCMD_ISAGG(aopt)) ? "," : "",
6455 (newpipe && MCMD_ISAGG(aopt)) ? "new pipe" : "",
6456 (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? ") " : "");
6457 prompt[sizeof(prompt)-1] = '\0';
6458 pipe_opt[1].label = raw ? N_("Shown Text") : N_("Raw Text");
6459 pipe_opt[2].label = capture ? N_("Free Output") : N_("Capture Output");
6460 pipe_opt[3].label = delimit ? N_("No Delimiter") : N_("With Delimiter");
6461 if(fourlabel > 0)
6462 pipe_opt[fourlabel].label = newpipe ? N_("To Same Pipe") : N_("To Individual Pipes");
6466 * 2 is really 1 because there will be one real entry and
6467 * one entry of "" because of the get_prev_hist above.
6469 if(items_in_hist(history) > 2){
6470 pipe_opt[ku].name = HISTORY_UP_KEYNAME;
6471 pipe_opt[ku].label = HISTORY_KEYLABEL;
6472 pipe_opt[ku+1].name = HISTORY_DOWN_KEYNAME;
6473 pipe_opt[ku+1].label = HISTORY_KEYLABEL;
6475 else{
6476 pipe_opt[ku].name = "";
6477 pipe_opt[ku].label = "";
6478 pipe_opt[ku+1].name = "";
6479 pipe_opt[ku+1].label = "";
6482 flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE;
6483 switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0,
6484 sizeof(pipe_command), prompt,
6485 pipe_opt, NO_HELP, &flags)){
6486 case -1 :
6487 q_status_message(SM_ORDER | SM_DING, 3, 4,
6488 _("Internal problem encountered"));
6489 done++;
6490 break;
6492 case 10 : /* flip raw bit */
6493 raw = !raw;
6494 break;
6496 case 11 : /* flip capture bit */
6497 capture = !capture;
6498 break;
6500 case 12 : /* flip delimit bit */
6501 delimit = !delimit;
6502 break;
6504 case 13 : /* flip newpipe bit */
6505 newpipe = !newpipe;
6506 break;
6508 case 30 :
6509 flagsforhist = (raw ? 0x8 : 0) +
6510 (delimit ? 0x4 : 0) +
6511 (newpipe ? 0x2 : 0) +
6512 (capture ? 0x1 : 0);
6513 if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6514 strncpy(pipe_command, p, sizeof(pipe_command));
6515 pipe_command[sizeof(pipe_command)-1] = '\0';
6516 if(history->hist[history->curindex]){
6517 flagsforhist = history->hist[history->curindex]->flags;
6518 raw = (flagsforhist & 0x8) ? 1 : 0;
6519 delimit = (flagsforhist & 0x4) ? 1 : 0;
6520 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6521 capture = (flagsforhist & 0x1) ? 1 : 0;
6524 else
6525 Writechar(BELL, 0);
6527 break;
6529 case 31 :
6530 flagsforhist = (raw ? 0x8 : 0) +
6531 (delimit ? 0x4 : 0) +
6532 (newpipe ? 0x2 : 0) +
6533 (capture ? 0x1 : 0);
6534 if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){
6535 strncpy(pipe_command, p, sizeof(pipe_command));
6536 pipe_command[sizeof(pipe_command)-1] = '\0';
6537 if(history->hist[history->curindex]){
6538 flagsforhist = history->hist[history->curindex]->flags;
6539 raw = (flagsforhist & 0x8) ? 1 : 0;
6540 delimit = (flagsforhist & 0x4) ? 1 : 0;
6541 newpipe = (flagsforhist & 0x2) ? 1 : 0;
6542 capture = (flagsforhist & 0x1) ? 1 : 0;
6545 else
6546 Writechar(BELL, 0);
6548 break;
6550 case 0 :
6551 if(pipe_command[0]){
6553 flagsforhist = (raw ? 0x8 : 0) +
6554 (delimit ? 0x4 : 0) +
6555 (newpipe ? 0x2 : 0) +
6556 (capture ? 0x1 : 0);
6557 save_hist(history, pipe_command, flagsforhist, NULL);
6559 flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR;
6560 flags |= (raw ? PIPE_RAW : 0);
6561 if(!capture){
6562 #ifndef _WINDOWS
6563 ClearScreen();
6564 fflush(stdout);
6565 clear_cursor_pos();
6566 ps_global->mangled_screen = 1;
6567 ps_global->in_init_seq = 1;
6568 #endif
6569 flags |= PIPE_RESET;
6572 if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command,
6573 (flags & PIPE_RESET)
6574 ? NULL
6575 : &resultfilename,
6576 flags, &pc)))
6577 done++;
6579 for(i = mn_first_cur(msgmap);
6580 i > 0L && !done;
6581 i = mn_next_cur(msgmap)){
6582 e = pine_mail_fetchstructure(ps_global->mail_stream,
6583 mn_m2raw(msgmap, i), &b);
6584 if(!(state->mail_stream
6585 && (rawno = mn_m2raw(msgmap, i)) > 0L
6586 && rawno <= state->mail_stream->nmsgs
6587 && (mc = mail_elt(state->mail_stream, rawno))
6588 && mc->valid))
6589 mc = NULL;
6591 if((newpipe
6592 && !(syspipe = cmd_pipe_open(pipe_command,
6593 (flags & PIPE_RESET)
6594 ? NULL
6595 : &resultfilename,
6596 flags, &pc)))
6597 || (delimit && !bezerk_delimiter(e, mc, pc, next++)))
6598 done++;
6600 if(!done){
6601 if(raw){
6602 char *pipe_err;
6604 prime_raw_pipe_getc(ps_global->mail_stream,
6605 mn_m2raw(msgmap, i), -1L, 0L);
6606 gf_filter_init();
6607 gf_link_filter(gf_nvtnl_local, NULL);
6608 if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){
6609 q_status_message1(SM_ORDER|SM_DING,
6610 3, 3,
6611 _("Internal Error: %s"),
6612 pipe_err);
6613 done++;
6616 else if(!format_message(mn_m2raw(msgmap, i), e, b,
6617 NULL, FM_NEW_MESS | FM_NOWRAP, pc))
6618 done++;
6621 if(newpipe)
6622 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6623 done++;
6626 if(!capture)
6627 ps_global->in_init_seq = 0;
6629 if(!newpipe)
6630 if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1)
6631 done++;
6632 if(done) /* say we had a problem */
6633 q_status_message(SM_ORDER | SM_DING, 3, 3,
6634 _("Error piping message"));
6635 else if(resultfilename){
6636 rv++;
6637 /* only display if no error */
6638 display_output_file(resultfilename, "PIPE MESSAGE",
6639 NULL, DOF_EMPTY);
6640 fs_give((void **)&resultfilename);
6642 else{
6643 rv++;
6644 q_status_message(SM_ORDER, 0, 2, _("Pipe command completed"));
6647 done++;
6648 break;
6650 /* else fall thru as if cancelled */
6652 case 1 :
6653 cmd_cancelled("Pipe command");
6654 done++;
6655 break;
6657 case 3 :
6658 helper(h_common_pipe, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE);
6659 ps_global->mangled_screen = 1;
6660 break;
6662 case 2 : /* no place to escape to */
6663 case 4 : /* can't suspend */
6664 default :
6665 break;
6669 ps_global->mangled_footer = 1;
6670 if(MCMD_ISAGG(aopt))
6671 restore_selected(msgmap);
6673 return rv;
6677 /*----------------------------------------------------------------------
6678 Screen to offer list management commands contained in message
6680 Args: state -- pointer to struct holding a bunch of pine state
6681 msgmap -- table mapping msg nums to c-client sequence nums
6682 aopt -- aggregate options
6684 Result:
6686 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6687 ----*/
6688 void
6689 rfc2369_display(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno)
6691 int winner = 0;
6692 char *h, *hdrs[MLCMD_COUNT + 1];
6693 long index_no = mn_raw2m(msgmap, msgno);
6694 RFC2369_S data[MLCMD_COUNT];
6696 /* for each header field */
6697 if((h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))) != NULL){
6698 memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT);
6699 if(rfc2369_parse_fields(h, &data[0])){
6700 STORE_S *explain;
6702 if((explain = list_mgmt_text(data, index_no)) != NULL){
6703 list_mgmt_screen(explain);
6704 ps_global->mangled_screen = 1;
6705 so_give(&explain);
6706 winner++;
6710 fs_give((void **) &h);
6713 if(!winner)
6714 q_status_message1(SM_ORDER, 0, 3,
6715 "Message %s contains no list management information",
6716 comatose(index_no));
6720 STORE_S *
6721 list_mgmt_text(RFC2369_S *data, long int msgno)
6723 STORE_S *store;
6724 int i, j, n, fields = 0;
6725 static char *rfc2369_intro1 =
6726 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6727 static char *rfc2369_intro2[] = {
6728 N_(" has information associated with it "),
6729 N_("that explains how to participate in an email list. An "),
6730 N_("email list is represented by a single email address that "),
6731 N_("users sharing a common interest can send messages to (known "),
6732 N_("as posting) which are then redistributed to all members "),
6733 N_("of the list (sometimes after review by a moderator)."),
6734 N_("<P>List participation commands in this message include:"),
6735 NULL
6738 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6740 /* Insert introductory text */
6741 so_puts(store, rfc2369_intro1);
6743 so_puts(store, comatose(msgno));
6745 for(i = 0; rfc2369_intro2[i]; i++)
6746 so_puts(store, _(rfc2369_intro2[i]));
6748 so_puts(store, "<P>");
6749 for(i = 0; i < MLCMD_COUNT; i++)
6750 if(data[i].data[0].value
6751 || data[i].data[0].comment
6752 || data[i].data[0].error){
6753 if(!fields++)
6754 so_puts(store, "<UL>");
6756 so_puts(store, "<LI>");
6757 so_puts(store,
6758 (n = (data[i].data[1].value || data[i].data[1].comment))
6759 ? "Methods to "
6760 : "A method to ");
6762 so_puts(store, data[i].field.description);
6763 so_puts(store, ". ");
6765 if(n)
6766 so_puts(store, "<OL>");
6768 for(j = 0;
6769 j < MLCMD_MAXDATA
6770 && (data[i].data[j].comment
6771 || data[i].data[j].value
6772 || data[i].data[j].error);
6773 j++){
6775 so_puts(store, n ? "<P><LI>" : "<P>");
6777 if(data[i].data[j].comment){
6778 so_puts(store,
6779 _("With the provided comment:<P><BLOCKQUOTE>"));
6780 so_puts(store, data[i].data[j].comment);
6781 so_puts(store, "</BLOCKQUOTE><P>");
6784 if(data[i].data[j].value){
6785 if(i == MLCMD_POST
6786 && !strucmp(data[i].data[j].value, "NO")){
6787 so_puts(store,
6788 _("Posting is <EM>not</EM> allowed on this list"));
6790 else{
6791 so_puts(store, "Select <A HREF=\"");
6792 so_puts(store, data[i].data[j].value);
6793 so_puts(store, "\">HERE</A> to ");
6794 so_puts(store, (data[i].field.action)
6795 ? data[i].field.action
6796 : "try it");
6799 so_puts(store, ".");
6802 if(data[i].data[j].error){
6803 so_puts(store, "<P>Unfortunately, Alpine can not offer");
6804 so_puts(store, " to take direct action based upon it");
6805 so_puts(store, " because it was improperly formatted.");
6806 so_puts(store, " The unrecognized data associated with");
6807 so_puts(store, " the \"");
6808 so_puts(store, data[i].field.name);
6809 so_puts(store, "\" header field was:<P><BLOCKQUOTE>");
6810 so_puts(store, data[i].data[j].error);
6811 so_puts(store, "</BLOCKQUOTE>");
6814 so_puts(store, "<P>");
6817 if(n)
6818 so_puts(store, "</OL>");
6821 if(fields)
6822 so_puts(store, "</UL>");
6824 so_puts(store, "</BODY></HTML>");
6827 return(store);
6831 void
6832 list_mgmt_screen(STORE_S *html)
6834 int cmd = MC_NONE;
6835 long offset = 0L;
6836 char *error = NULL;
6837 STORE_S *store;
6838 HANDLE_S *handles = NULL;
6839 gf_io_t gc, pc;
6842 so_seek(html, 0L, 0);
6843 gf_set_so_readc(&gc, html);
6845 init_handles(&handles);
6847 if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){
6848 gf_set_so_writec(&pc, store);
6849 gf_filter_init();
6851 gf_link_filter(gf_html2plain,
6852 gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols,
6853 non_messageview_margin(), &handles, NULL, 0));
6855 error = gf_pipe(gc, pc);
6857 gf_clear_so_writec(store);
6859 if(!error){
6860 SCROLL_S sargs;
6862 memset(&sargs, 0, sizeof(SCROLL_S));
6863 sargs.text.text = so_text(store);
6864 sargs.text.src = CharStar;
6865 sargs.text.desc = "list commands";
6866 sargs.text.handles = handles;
6867 if(offset){
6868 sargs.start.on = Offset;
6869 sargs.start.loc.offset = offset;
6872 sargs.bar.title = _("MAIL LIST COMMANDS");
6873 sargs.bar.style = MessageNumber;
6874 sargs.resize_exit = 1;
6875 sargs.help.text = h_special_list_commands;
6876 sargs.help.title = _("HELP FOR LIST COMMANDS");
6877 sargs.keys.menu = &listmgr_keymenu;
6878 setbitmap(sargs.keys.bitmap);
6879 if(!handles){
6880 clrbitn(LM_TRY_KEY, sargs.keys.bitmap);
6881 clrbitn(LM_PREV_KEY, sargs.keys.bitmap);
6882 clrbitn(LM_NEXT_KEY, sargs.keys.bitmap);
6885 cmd = scrolltool(&sargs);
6886 offset = sargs.start.loc.offset;
6889 so_give(&store);
6892 free_handles(&handles);
6893 gf_clear_so_readc(html);
6895 while(cmd == MC_RESIZE);
6899 /*----------------------------------------------------------------------
6900 Prompt the user for the type of select desired
6902 NOTE: any and all functions that successfully exit the second
6903 switch() statement below (currently "select_*() functions"),
6904 *MUST* update the folder's MESSAGECACHE element's "searched"
6905 bits to reflect the search result. Functions using
6906 mail_search() get this for free, the others must update 'em
6907 by hand.
6909 Returns -1 if canceled without changing selection
6910 0 if selection may have changed
6911 ----*/
6913 aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index)
6915 long i, diff, old_tot, msgno, raw;
6916 int q = 0, rv = 0, narrow = 0, hidden, ret = -1;
6917 ESCKEY_S *sel_opts;
6918 MESSAGECACHE *mc;
6919 SEARCHSET *limitsrch = NULL;
6920 PINETHRD_S *thrd;
6921 extern MAILSTREAM *mm_search_stream;
6922 extern long mm_search_count;
6924 hidden = any_lflagged(msgmap, MN_HIDE) > 0L;
6925 mm_search_stream = state->mail_stream;
6926 mm_search_count = 0L;
6928 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
6929 sel_opts = THRD_INDX() ? sel_opts6 : sel_opts5;
6930 else
6931 sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2;
6933 if(THREADING()){
6934 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH;
6936 else{
6937 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream)){
6938 sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_XGMEXT_CH;
6939 sel_opts[SEL_OPTS_THREAD].rval = sel_opts[SEL_OPTS_XGMEXT].rval;
6940 sel_opts[SEL_OPTS_THREAD].name = sel_opts[SEL_OPTS_XGMEXT].name;
6941 sel_opts[SEL_OPTS_THREAD].label = sel_opts[SEL_OPTS_XGMEXT].label;
6942 sel_opts[SEL_OPTS_XGMEXT].ch = -1;
6944 else
6945 sel_opts[SEL_OPTS_THREAD].ch = -1;
6948 if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){
6949 if(THRD_INDX()){
6950 i = 0;
6951 thrd = fetch_thread(state->mail_stream,
6952 mn_m2raw(msgmap, mn_get_cur(msgmap)));
6953 /* check if whole thread is selected or not */
6954 if(thrd &&
6955 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT)
6957 count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE))
6958 i = 1;
6960 sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd");
6962 else{
6963 i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap),
6964 MN_SLCT);
6965 sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur");
6968 sel_opts += 2; /* disable extra options */
6969 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
6970 q = double_radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6971 RB_NORM);
6972 else
6973 q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP,
6974 RB_NORM);
6975 switch(q){
6976 case 'f' : /* flip selection */
6977 msgno = 0L;
6978 for(i = 1L; i <= mn_get_total(msgmap); i++){
6979 ret = 0;
6980 q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT);
6981 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q);
6982 if(hidden){
6983 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q);
6984 if(!msgno && q)
6985 mn_reset_cur(msgmap, msgno = i);
6989 return(ret);
6991 case 'n' : /* narrow selection */
6992 narrow++;
6993 case 'b' : /* broaden selection */
6994 q = 0; /* offer criteria prompt */
6995 break;
6997 case 'c' : /* Un/Select Current */
6998 case 'a' : /* Unselect All */
6999 case 'x' : /* cancel */
7000 break;
7002 default :
7003 q_status_message(SM_ORDER | SM_DING, 3, 3,
7004 "Unsupported Select option");
7005 return(ret);
7009 if(!q){
7010 while(1){
7011 if(is_imap_stream(state->mail_stream) && XGMEXT1(state->mail_stream))
7012 q = double_radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP,
7013 RB_NORM);
7014 else
7015 q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', NO_HELP,
7016 RB_NORM|RB_RET_HELP);
7018 if(q == 3){
7019 helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE);
7020 ps_global->mangled_screen = 1;
7022 else
7023 break;
7028 * The purpose of this is to add the appropriate searchset to the
7029 * search so that the search can be limited to only looking at what
7030 * it needs to look at. That is, if we are narrowing then we only need
7031 * to look at messages which are already selected, and if we are
7032 * broadening, then we only need to look at messages which are not
7033 * yet selected. This routine will work whether or not
7034 * limiting_searchset properly limits the search set. In particular,
7035 * the searchset returned by limiting_searchset may include messages
7036 * which really shouldn't be included. We do that because a too-large
7037 * searchset will break some IMAP servers. It is even possible that it
7038 * becomes inefficient to send the whole set. If the select function
7039 * frees limitsrch, it should be sure to set it to NULL so we won't
7040 * try freeing it again here.
7042 limitsrch = limiting_searchset(state->mail_stream, narrow);
7045 * NOTE: See note about MESSAGECACHE "searched" bits above!
7047 switch(q){
7048 case 'x': /* cancel */
7049 cmd_cancelled("Select command");
7050 return(ret);
7052 case 'c' : /* select/unselect current */
7053 (void) select_by_current(state, msgmap, in_index);
7054 ret = 0;
7055 return(ret);
7057 case 'a' : /* select/unselect all */
7058 msgno = any_lflagged(msgmap, MN_SLCT);
7059 diff = (!msgno) ? mn_get_total(msgmap) : 0L;
7060 ret = 0;
7061 agg_select_all(state->mail_stream, msgmap, &diff,
7062 any_lflagged(msgmap, MN_SLCT) <= 0L);
7063 q_status_message4(SM_ORDER,0,2,
7064 "%s%s message%s %sselected",
7065 msgno ? "" : "All ", comatose(diff),
7066 plural(diff), msgno ? "UN" : "");
7067 return(ret);
7069 case 'n' : /* Select by Number */
7070 ret = 0;
7071 if(THRD_INDX())
7072 rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch);
7073 else
7074 rv = select_by_number(state->mail_stream, msgmap, &limitsrch);
7076 break;
7078 case 'd' : /* Select by Date */
7079 ret = 0;
7080 rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap),
7081 &limitsrch);
7082 break;
7084 case 't' : /* Text */
7085 ret = 0;
7086 rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap),
7087 &limitsrch);
7088 break;
7090 case 'z' : /* Size */
7091 ret = 0;
7092 rv = select_by_size(state->mail_stream, &limitsrch);
7093 break;
7095 case 's' : /* Status */
7096 ret = 0;
7097 rv = select_by_status(state->mail_stream, &limitsrch);
7098 break;
7100 case 'k' : /* Keyword */
7101 ret = 0;
7102 rv = select_by_keyword(state->mail_stream, &limitsrch);
7103 break;
7105 case 'r' : /* Rule */
7106 ret = 0;
7107 rv = select_by_rule(state->mail_stream, &limitsrch);
7108 break;
7110 case 'h' : /* Thread */
7111 ret = 0;
7112 rv = select_by_thread(state->mail_stream, msgmap, &limitsrch);
7113 break;
7115 case 'g' : /* X-GM-EXT-1 */
7116 ret = 0;
7117 rv = select_by_gm_content(state->mail_stream, msgmap, mn_get_cur(msgmap), &limitsrch);
7118 break;
7120 default :
7121 q_status_message(SM_ORDER | SM_DING, 3, 3,
7122 "Unsupported Select option");
7123 return(ret);
7126 if(limitsrch)
7127 mail_free_searchset(&limitsrch);
7129 if(rv) /* bad return value.. */
7130 return(ret); /* error already displayed */
7132 if(narrow) /* make sure something was selected */
7133 for(i = 1L; i <= mn_get_total(msgmap); i++)
7134 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7135 && raw <= state->mail_stream->nmsgs
7136 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7137 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
7138 break;
7139 else
7140 mm_search_count--;
7143 diff = 0L;
7144 if(mm_search_count){
7146 * loop thru all the messages, adjusting local flag bits
7147 * based on their "searched" bit...
7149 for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++)
7150 if(narrow){
7151 /* turning OFF selectedness if the "searched" bit isn't lit. */
7152 if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7153 if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7154 && raw <= state->mail_stream->nmsgs
7155 && (mc = mail_elt(state->mail_stream, raw))
7156 && !mc->searched){
7157 diff--;
7158 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0);
7159 if(hidden)
7160 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1);
7162 /* adjust current message in case we unselect and hide it */
7163 else if(msgno < mn_get_cur(msgmap)
7164 && (!THRD_INDX()
7165 || !get_lflag(state->mail_stream, msgmap,
7166 i, MN_CHID)))
7167 msgno = i;
7170 else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream
7171 && raw <= state->mail_stream->nmsgs
7172 && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){
7173 /* turn ON selectedness if "searched" bit is lit. */
7174 if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){
7175 diff++;
7176 set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1);
7177 if(hidden)
7178 set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0);
7182 /* if we're zoomed and the current message was unselected */
7183 if(narrow && msgno
7184 && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE))
7185 mn_reset_cur(msgmap, msgno);
7188 if(!diff){
7189 if(narrow)
7190 q_status_message4(SM_ORDER, 3, 3,
7191 "%s. %s message%s remain%s selected.",
7192 mm_search_count
7193 ? "No change resulted"
7194 : "No messages in intersection",
7195 comatose(old_tot), plural(old_tot),
7196 (old_tot == 1L) ? "s" : "");
7197 else if(old_tot)
7198 q_status_message(SM_ORDER, 3, 3,
7199 _("No change resulted. Matching messages already selected."));
7200 else
7201 q_status_message1(SM_ORDER | SM_DING, 3, 3,
7202 _("Select failed. No %smessages selected."),
7203 old_tot ? _("additional ") : "");
7205 else if(old_tot){
7206 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
7207 "Select matched %ld message%s. %s %smessage%s %sselected.",
7208 (diff > 0) ? diff : old_tot + diff,
7209 plural((diff > 0) ? diff : old_tot + diff),
7210 comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7211 (diff > 0) ? "total " : "",
7212 plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff),
7213 (diff > 0) ? "" : "UN");
7214 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
7215 q_status_message(SM_ORDER, 3, 3, tmp_20k_buf);
7217 else
7218 q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"),
7219 comatose(diff), plural(diff));
7221 return(ret);
7225 /*----------------------------------------------------------------------
7226 Toggle the state of the current message
7228 Args: state -- pointer pine's state variables
7229 msgmap -- message collection to operate on
7230 in_index -- in the message index view
7231 Returns: TRUE if current marked selected, FALSE otw
7232 ----*/
7234 select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index)
7236 long cur;
7237 int all_selected = 0;
7238 unsigned long was, tot, rawno;
7239 PINETHRD_S *thrd;
7241 cur = mn_get_cur(msgmap);
7243 if(THRD_INDX()){
7244 thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur));
7245 if(!thrd)
7246 return 0;
7248 was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT);
7249 tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE);
7250 if(was == tot)
7251 all_selected++;
7253 if(all_selected){
7254 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0);
7255 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7256 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1);
7258 * See if there's anything left to zoom on. If so,
7259 * pick an adjacent one for highlighting, else make
7260 * sure nothing is left hidden...
7262 if(any_lflagged(msgmap, MN_SLCT)){
7263 mn_inc_cur(state->mail_stream, msgmap,
7264 (in_index == View && THREADING()
7265 && sp_viewing_a_thread(state->mail_stream))
7266 ? MH_THISTHD
7267 : (in_index == View)
7268 ? MH_ANYTHD : MH_NONE);
7269 if(mn_get_cur(msgmap) == cur)
7270 mn_dec_cur(state->mail_stream, msgmap,
7271 (in_index == View && THREADING()
7272 && sp_viewing_a_thread(state->mail_stream))
7273 ? MH_THISTHD
7274 : (in_index == View)
7275 ? MH_ANYTHD : MH_NONE);
7277 else /* clear all hidden flags */
7278 (void) unzoom_index(state, state->mail_stream, msgmap);
7281 else
7282 set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1);
7284 q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected",
7285 comatose(all_selected ? was : tot-was),
7286 plural(all_selected ? was : tot-was),
7287 all_selected ? "UN" : "");
7289 /* collapsed thread */
7290 else if(THREADING()
7291 && ((rawno = mn_m2raw(msgmap, cur)) != 0L)
7292 && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL)
7293 && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){
7295 * This doesn't work quite the same as the colon command works, but
7296 * it is arguably doing the correct thing. The difference is
7297 * that aggregate_select will zoom after selecting back where it
7298 * was called from, but selecting a thread with colon won't zoom.
7299 * Maybe it makes sense to zoom after a select but not after a colon
7300 * command even though they are very similar.
7302 thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state));
7304 else{
7305 if((all_selected =
7306 get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */
7307 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0);
7308 if(any_lflagged(msgmap, MN_HIDE) > 0L){
7309 set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1);
7311 * See if there's anything left to zoom on. If so,
7312 * pick an adjacent one for highlighting, else make
7313 * sure nothing is left hidden...
7315 if(any_lflagged(msgmap, MN_SLCT)){
7316 mn_inc_cur(state->mail_stream, msgmap,
7317 (in_index == View && THREADING()
7318 && sp_viewing_a_thread(state->mail_stream))
7319 ? MH_THISTHD
7320 : (in_index == View)
7321 ? MH_ANYTHD : MH_NONE);
7322 if(mn_get_cur(msgmap) == cur)
7323 mn_dec_cur(state->mail_stream, msgmap,
7324 (in_index == View && THREADING()
7325 && sp_viewing_a_thread(state->mail_stream))
7326 ? MH_THISTHD
7327 : (in_index == View)
7328 ? MH_ANYTHD : MH_NONE);
7330 else /* clear all hidden flags */
7331 (void) unzoom_index(state, state->mail_stream, msgmap);
7334 else
7335 set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1);
7337 q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected",
7338 long2string(cur), all_selected ? "UN" : "");
7342 return(!all_selected);
7346 /*----------------------------------------------------------------------
7347 Prompt the user for the command to perform on selected messages
7349 Args: state -- pointer pine's state variables
7350 msgmap -- message collection to operate on
7351 q_line -- line on display to write prompts
7352 Returns: 1 if the selected messages are suitably commanded,
7353 0 if the choice to pick the command was declined
7355 ----*/
7357 apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap,
7358 UCS preloadkeystroke, int flags, int q_line)
7360 int i = 8, /* number of static entries in sel_opts3 */
7361 rv = 0,
7362 cmd,
7363 we_cancel = 0,
7364 agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG;
7365 char prompt[80];
7366 PAT_STATE pstate;
7369 * To do this "right", we really ought to have access to the keymenu
7370 * here and change the typed command into a real command by running
7371 * it through menu_command. Then the switch below would be against
7372 * results from menu_command. If we did that we'd also pass the
7373 * results of menu_command in as preloadkeystroke instead of passing
7374 * the keystroke itself. But we don't have the keymenu handy,
7375 * so we have to fake it. The only complication that we run into
7376 * is that KEY_DEL is an escape sequence so we change a typed
7377 * KEY_DEL esc seq into the letter D.
7380 if(!preloadkeystroke){
7381 if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */
7382 sel_opts3[i].ch = '*';
7383 sel_opts3[i].rval = '*';
7384 sel_opts3[i].name = "*";
7385 sel_opts3[i++].label = N_("Flag");
7388 if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */
7389 sel_opts3[i].ch = '|';
7390 sel_opts3[i].rval = '|';
7391 sel_opts3[i].name = "|";
7392 sel_opts3[i++].label = N_("Pipe");
7395 if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */
7396 sel_opts3[i].ch = 'b';
7397 sel_opts3[i].rval = 'b';
7398 sel_opts3[i].name = "B";
7399 sel_opts3[i++].label = N_("Bounce");
7402 if(flags & AC_FROM_THREAD){
7403 if(flags & (AC_COLL | AC_EXPN)){
7404 sel_opts3[i].ch = '/';
7405 sel_opts3[i].rval = '/';
7406 sel_opts3[i].name = "/";
7407 sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse")
7408 : N_("Expand");
7411 sel_opts3[i].ch = ';';
7412 sel_opts3[i].rval = ';';
7413 sel_opts3[i].name = ";";
7414 if(flags & AC_UNSEL)
7415 sel_opts3[i++].label = N_("UnSelect");
7416 else
7417 sel_opts3[i++].label = N_("Select");
7420 if(F_ON(F_ENABLE_PRYNT, state)){ /* this one is invisible */
7421 sel_opts3[i].ch = 'y';
7422 sel_opts3[i].rval = '%';
7423 sel_opts3[i].name = "";
7424 sel_opts3[i++].label = "";
7427 if(!is_imap_stream(stream) || LEVELUIDPLUS(stream)){ /* expunge selected messages */
7428 sel_opts3[i].ch = 'x';
7429 sel_opts3[i].rval = 'x';
7430 sel_opts3[i].name = "X";
7431 sel_opts3[i++].label = N_("Expunge");
7434 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7435 sel_opts3[i].ch = '#';
7436 sel_opts3[i].rval = '#';
7437 sel_opts3[i].name = "#";
7438 sel_opts3[i++].label = N_("Set Role");
7441 sel_opts3[i].ch = KEY_DEL; /* also invisible */
7442 sel_opts3[i].rval = 'd';
7443 sel_opts3[i].name = "";
7444 sel_opts3[i++].label = "";
7446 sel_opts3[i].ch = -1;
7448 snprintf(prompt, sizeof(prompt), "%s command : ",
7449 (flags & AC_FROM_THREAD) ? "THREAD" : "APPLY");
7450 prompt[sizeof(prompt)-1] = '\0';
7451 cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'c', NO_HELP,
7452 RB_SEQ_SENSITIVE);
7453 if(isupper(cmd))
7454 cmd = tolower(cmd);
7456 else{
7457 if(preloadkeystroke == KEY_DEL)
7458 cmd = 'd';
7459 else{
7460 if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke))
7461 cmd = tolower((int) preloadkeystroke);
7462 else
7463 cmd = (int) preloadkeystroke; /* shouldn't happen */
7467 switch(cmd){
7468 case 'd' : /* delete */
7469 we_cancel = busy_cue(NULL, NULL, 1);
7470 rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */
7471 if(we_cancel)
7472 cancel_busy_cue(0);
7473 break;
7475 case 'u' : /* undelete */
7476 we_cancel = busy_cue(NULL, NULL, 1);
7477 rv = cmd_undelete(state, msgmap, agg);
7478 if(we_cancel)
7479 cancel_busy_cue(0);
7480 break;
7482 case 'r' : /* reply */
7483 rv = cmd_reply(state, msgmap, agg, NULL);
7484 break;
7486 case 'f' : /* Forward */
7487 rv = cmd_forward(state, msgmap, agg, NULL);
7488 break;
7490 case '%' : /* print */
7491 rv = cmd_print(state, msgmap, agg, MsgIndx);
7492 break;
7494 case 't' : /* take address */
7495 rv = cmd_take_addr(state, msgmap, agg);
7496 break;
7498 case 's' : /* save */
7499 rv = cmd_save(state, stream, msgmap, agg, MsgIndx);
7500 break;
7502 case 'e' : /* export */
7503 rv = cmd_export(state, msgmap, q_line, agg);
7504 break;
7506 case '|' : /* pipe */
7507 rv = cmd_pipe(state, msgmap, agg);
7508 break;
7510 case '*' : /* flag */
7511 we_cancel = busy_cue(NULL, NULL, 1);
7512 rv = cmd_flag(state, msgmap, agg);
7513 if(we_cancel)
7514 cancel_busy_cue(0);
7515 break;
7517 case 'b' : /* bounce */
7518 rv = cmd_bounce(state, msgmap, agg, NULL);
7519 break;
7521 case '/' :
7522 collapse_or_expand(state, stream, msgmap,
7523 F_ON(F_SLASH_COLL_ENTIRE, ps_global)
7524 ? 0L
7525 : mn_get_cur(msgmap));
7526 break;
7528 case ':' :
7529 select_thread_stmp(state, stream, msgmap);
7530 break;
7532 case 'x' : /* Expunge */
7533 rv = cmd_expunge(state, stream, msgmap, agg);
7534 break;
7536 case 'c' : /* cancel */
7537 cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command"
7538 : "Apply command");
7539 break;
7541 case '#' :
7542 if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){
7543 static ESCKEY_S choose_role[] = {
7544 {'r', 'r', "R", N_("Reply")},
7545 {'f', 'f', "F", N_("Forward")},
7546 {'b', 'b', "B", N_("Bounce")},
7547 {-1, 0, NULL, NULL}
7549 int action;
7550 ACTION_S *role = NULL;
7552 action = radio_buttons(_("Reply, Forward or Bounce using a role? "),
7553 -FOOTER_ROWS(state), choose_role,
7554 'r', 'x', h_role_aggregate, RB_NORM);
7555 if(action == 'r' || action == 'f' || action == 'b'){
7556 void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL;
7558 redraw = state->redrawer;
7559 state->redrawer = NULL;
7560 prev_screen = state->prev_screen;
7561 role = NULL;
7562 state->next_screen = SCREEN_FUN_NULL;
7564 if(role_select_screen(state, &role,
7565 action == 'f' ? MC_FORWARD :
7566 action == 'r' ? MC_REPLY :
7567 action == 'b' ? MC_BOUNCE : 0) < 0){
7568 cmd_cancelled(action == 'f' ? _("Forward") :
7569 action == 'r' ? _("Reply") : _("Bounce"));
7570 state->next_screen = prev_screen;
7571 state->redrawer = redraw;
7572 state->mangled_screen = 1;
7574 else{
7575 if(role)
7576 role = combine_inherited_role(role);
7577 else{
7578 role = (ACTION_S *) fs_get(sizeof(*role));
7579 memset((void *) role, 0, sizeof(*role));
7580 role->nick = cpystr("Default Role");
7583 state->redrawer = NULL;
7584 switch(action){
7585 case 'r':
7586 (void) cmd_reply(state, msgmap, agg, role);
7587 break;
7589 case 'f':
7590 (void) cmd_forward(state, msgmap, agg, role);
7591 break;
7593 case 'b':
7594 (void) cmd_bounce(state, msgmap, agg, role);
7595 break;
7598 if(role)
7599 free_action(&role);
7601 if(redraw)
7602 (*redraw)();
7604 state->next_screen = prev_screen;
7605 state->redrawer = redraw;
7606 state->mangled_screen = 1;
7610 break;
7612 case 'z' : /* default */
7613 q_status_message(SM_INFO, 0, 2,
7614 "Cancelled, there is no default command");
7615 break;
7617 default:
7618 break;
7621 return(rv);
7626 * Select by message number ranges.
7627 * Sets searched bits in mail_elts
7629 * Args limitsrch -- limit search to this searchset
7631 * Returns 0 on success.
7634 select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
7636 int r, end, cur;
7637 long n1, n2, raw;
7638 char number1[16], number2[16], numbers[80], *p, *t;
7639 HelpType help;
7640 MESSAGECACHE *mc;
7642 numbers[0] = '\0';
7643 ps_global->mangled_footer = 1;
7644 help = NO_HELP;
7645 while(1){
7646 int flags = OE_APPEND_CURRENT;
7648 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7649 sizeof(numbers), _(select_num), NULL, help, &flags);
7650 if(r == 4)
7651 continue;
7653 if(r == 3){
7654 help = (help == NO_HELP) ? h_select_by_num : NO_HELP;
7655 continue;
7658 for(t = p = numbers; *p ; p++) /* strip whitespace */
7659 if(!isspace((unsigned char)*p))
7660 *t++ = *p;
7662 *t = '\0';
7664 if(r == 1 || numbers[0] == '\0'){
7665 cmd_cancelled("Selection by number");
7666 return(1);
7668 else
7669 break;
7672 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7673 if((mc = mail_elt(stream, n1)) != NULL)
7674 mc->searched = 0; /* clear searched bits */
7676 for(p = numbers; *p ; p++){
7677 t = number1;
7678 while(*p && isdigit((unsigned char)*p))
7679 *t++ = *p++;
7681 *t = '\0';
7683 end = cur = 0;
7684 if(number1[0] == '\0'){
7685 if(*p == '-'){
7686 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7687 _("Invalid number range, missing number before \"-\": %s"),
7688 numbers);
7689 return(1);
7691 else if(!strucmp("end", p)){
7692 end = 1;
7693 p += strlen("end");
7695 else if(!strucmp("$", p)){
7696 end = 1;
7697 p++;
7699 else if(*p == '.'){
7700 cur = 1;
7701 p++;
7703 else{
7704 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7705 _("Invalid message number: %s"), numbers);
7706 return(1);
7710 if(end)
7711 n1 = mn_get_total(msgmap);
7712 else if(cur)
7713 n1 = mn_get_cur(msgmap);
7714 else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){
7715 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7716 _("\"%s\" out of message number range"),
7717 long2string(n1));
7718 return(1);
7721 t = number2;
7722 if(*p == '-'){
7723 while(*++p && isdigit((unsigned char)*p))
7724 *t++ = *p;
7726 *t = '\0';
7728 end = cur = 0;
7729 if(number2[0] == '\0'){
7730 if(!strucmp("end", p)){
7731 end = 1;
7732 p += strlen("end");
7734 else if(!strucmp(p, "$")){
7735 end = 1;
7736 p++;
7738 else if(*p == '.'){
7739 cur = 1;
7740 p++;
7742 else{
7743 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7744 _("Invalid number range, missing number after \"-\": %s"),
7745 numbers);
7746 return(1);
7750 if(end)
7751 n2 = mn_get_total(msgmap);
7752 else if(cur)
7753 n2 = mn_get_cur(msgmap);
7754 else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){
7755 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7756 _("\"%s\" out of message number range"),
7757 long2string(n2));
7758 return(1);
7761 if(n2 <= n1){
7762 char t[20];
7764 strncpy(t, long2string(n1), sizeof(t));
7765 t[sizeof(t)-1] = '\0';
7766 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7767 _("Invalid reverse message number range: %s-%s"),
7768 t, long2string(n2));
7769 return(1);
7772 for(;n1 <= n2; n1++){
7773 raw = mn_m2raw(msgmap, n1);
7774 if(raw > 0L
7775 && (!(limitsrch && *limitsrch)
7776 || in_searchset(*limitsrch, (unsigned long) raw)))
7777 mm_searched(stream, raw);
7780 else{
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 if(*p == '\0')
7789 break;
7792 return(0);
7797 * Select by thread number ranges.
7798 * Sets searched bits in mail_elts
7800 * Args limitsrch -- limit search to this searchset
7802 * Returns 0 on success.
7805 select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset)
7807 int r, end, cur;
7808 long n1, n2;
7809 char number1[16], number2[16], numbers[80], *p, *t;
7810 HelpType help;
7811 PINETHRD_S *thrd = NULL, *th;
7812 MESSAGECACHE *mc;
7814 numbers[0] = '\0';
7815 ps_global->mangled_footer = 1;
7816 help = NO_HELP;
7817 while(1){
7818 int flags = OE_APPEND_CURRENT;
7820 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
7821 sizeof(numbers), _(select_num), NULL, help, &flags);
7822 if(r == 4)
7823 continue;
7825 if(r == 3){
7826 help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP;
7827 continue;
7830 for(t = p = numbers; *p ; p++) /* strip whitespace */
7831 if(!isspace((unsigned char)*p))
7832 *t++ = *p;
7834 *t = '\0';
7836 if(r == 1 || numbers[0] == '\0'){
7837 cmd_cancelled("Selection by number");
7838 return(1);
7840 else
7841 break;
7844 for(n1 = 1; n1 <= stream->nmsgs; n1++)
7845 if((mc = mail_elt(stream, n1)) != NULL)
7846 mc->searched = 0; /* clear searched bits */
7848 for(p = numbers; *p ; p++){
7849 t = number1;
7850 while(*p && isdigit((unsigned char)*p))
7851 *t++ = *p++;
7853 *t = '\0';
7855 end = cur = 0;
7856 if(number1[0] == '\0'){
7857 if(*p == '-'){
7858 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7859 _("Invalid number range, missing number before \"-\": %s"),
7860 numbers);
7861 return(1);
7863 else if(!strucmp("end", p)){
7864 end = 1;
7865 p += strlen("end");
7867 else if(!strucmp(p, "$")){
7868 end = 1;
7869 p++;
7871 else if(*p == '.'){
7872 cur = 1;
7873 p++;
7875 else{
7876 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7877 _("Invalid thread number: %s"), numbers);
7878 return(1);
7882 if(end)
7883 n1 = msgmap->max_thrdno;
7884 else if(cur){
7885 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7886 n1 = th->thrdno;
7888 else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){
7889 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7890 _("\"%s\" out of thread number range"),
7891 long2string(n1));
7892 return(1);
7895 t = number2;
7896 if(*p == '-'){
7898 while(*++p && isdigit((unsigned char)*p))
7899 *t++ = *p;
7901 *t = '\0';
7903 end = 0;
7904 if(number2[0] == '\0'){
7905 if(!strucmp("end", p)){
7906 end = 1;
7907 p += strlen("end");
7909 else if(!strucmp("$", p)){
7910 end = 1;
7911 p++;
7913 else if(*p == '.'){
7914 cur = 1;
7915 p++;
7917 else{
7918 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7919 _("Invalid number range, missing number after \"-\": %s"),
7920 numbers);
7921 return(1);
7925 if(end)
7926 n2 = msgmap->max_thrdno;
7927 else if(cur){
7928 th = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
7929 n2 = th->thrdno;
7931 else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){
7932 q_status_message1(SM_ORDER | SM_DING, 0, 2,
7933 _("\"%s\" out of thread number range"),
7934 long2string(n2));
7935 return(1);
7938 if(n2 <= n1){
7939 char t[20];
7941 strncpy(t, long2string(n1), sizeof(t));
7942 t[sizeof(t)-1] = '\0';
7943 q_status_message2(SM_ORDER | SM_DING, 0, 2,
7944 _("Invalid reverse message number range: %s-%s"),
7945 t, long2string(n2));
7946 return(1);
7949 for(;n1 <= n2; n1++){
7950 thrd = find_thread_by_number(stream, msgmap, n1, thrd);
7952 if(thrd)
7953 set_search_bit_for_thread(stream, thrd, msgset);
7956 else{
7957 thrd = find_thread_by_number(stream, msgmap, n1, NULL);
7959 if(thrd)
7960 set_search_bit_for_thread(stream, thrd, msgset);
7963 if(*p == '\0')
7964 break;
7967 return(0);
7972 * Select by message dates.
7973 * Sets searched bits in mail_elts
7975 * Args limitsrch -- limit search to this searchset
7977 * Returns 0 on success.
7980 select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
7982 int r, we_cancel = 0, when = 0;
7983 char date[100], defdate[100], prompt[128];
7984 time_t seldate = time(0);
7985 struct tm *seldate_tm;
7986 SEARCHPGM *pgm;
7987 HelpType help;
7988 static struct _tense {
7989 char *preamble,
7990 *range,
7991 *scope;
7992 } tense[] = {
7993 {"were ", "SENT SINCE", " (inclusive)"},
7994 {"were ", "SENT BEFORE", " (exclusive)"},
7995 {"were ", "SENT ON", "" },
7996 {"", "ARRIVED SINCE", " (inclusive)"},
7997 {"", "ARRIVED BEFORE", " (exclusive)"},
7998 {"", "ARRIVED ON", "" }
8001 date[0] = '\0';
8002 ps_global->mangled_footer = 1;
8003 help = NO_HELP;
8006 * If talking to an old server, default to SINCE instead of
8007 * SENTSINCE, which was added later.
8009 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8010 when = 3;
8012 while(1){
8013 int flags = OE_APPEND_CURRENT;
8015 seldate_tm = localtime(&seldate);
8016 snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday,
8017 month_abbrev(seldate_tm->tm_mon + 1),
8018 seldate_tm->tm_year + 1900);
8019 defdate[sizeof(defdate)-1] = '\0';
8020 snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ",
8021 tense[when].preamble, tense[when].range,
8022 tense[when].scope, defdate);
8023 prompt[sizeof(prompt)-1] = '\0';
8024 r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date),
8025 prompt, sel_date_opt, help, &flags);
8026 switch (r){
8027 case 1 :
8028 cmd_cancelled("Selection by date");
8029 return(1);
8031 case 3 :
8032 help = (help == NO_HELP) ? h_select_date : NO_HELP;
8033 continue;
8035 case 4 :
8036 continue;
8038 case 11 :
8040 MESSAGECACHE *mc;
8041 long rawno;
8043 if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L
8044 && rawno <= stream->nmsgs
8045 && (mc = mail_elt(stream, rawno))){
8047 /* cache not filled in yet? */
8048 if(mc->day == 0){
8049 char seq[20];
8051 if(stream->dtb && stream->dtb->flags & DR_NEWS){
8052 strncpy(seq,
8053 ulong2string(mail_uid(stream, rawno)),
8054 sizeof(seq));
8055 seq[sizeof(seq)-1] = '\0';
8056 mail_fetch_overview(stream, seq, NULL);
8058 else{
8059 strncpy(seq, long2string(rawno),
8060 sizeof(seq));
8061 seq[sizeof(seq)-1] = '\0';
8062 mail_fetch_fast(stream, seq, 0L);
8066 /* mail_date returns fixed field width date */
8067 mail_date(date, mc);
8068 date[11] = '\0';
8072 continue;
8074 case 12 : /* set default to PREVIOUS day */
8075 seldate -= 86400;
8076 continue;
8078 case 13 : /* set default to NEXT day */
8079 seldate += 86400;
8080 continue;
8082 case 14 :
8083 when = (when+1) % (sizeof(tense) / sizeof(struct _tense));
8084 continue;
8086 default:
8087 break;
8090 removing_leading_white_space(date);
8091 removing_trailing_white_space(date);
8092 if(!*date){
8093 strncpy(date, defdate, sizeof(date));
8094 date[sizeof(date)-1] = '\0';
8097 break;
8100 if((pgm = mail_newsearchpgm()) != NULL){
8101 MESSAGECACHE elt;
8102 short converted_date;
8104 if(mail_parse_date(&elt, (unsigned char *) date)){
8105 converted_date = mail_shortdate(elt.year, elt.month, elt.day);
8107 switch(when){
8108 case 0:
8109 pgm->sentsince = converted_date;
8110 break;
8111 case 1:
8112 pgm->sentbefore = converted_date;
8113 break;
8114 case 2:
8115 pgm->senton = converted_date;
8116 break;
8117 case 3:
8118 pgm->since = converted_date;
8119 break;
8120 case 4:
8121 pgm->before = converted_date;
8122 break;
8123 case 5:
8124 pgm->on = converted_date;
8125 break;
8128 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8130 if(ps_global && ps_global->ttyo){
8131 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8132 ps_global->mangled_footer = 1;
8135 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8137 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8139 if(we_cancel)
8140 cancel_busy_cue(0);
8142 /* we know this was freed in mail_search, let caller know */
8143 if(limitsrch)
8144 *limitsrch = NULL;
8146 else{
8147 mail_free_searchpgm(&pgm);
8148 q_status_message1(SM_ORDER, 3, 3,
8149 _("Invalid date entered: %s"), date);
8150 return(1);
8154 return(0);
8158 * Select by searching in message headers or body
8159 * using the x-gm-ext-1 capaility. This function
8160 * reads the input from the user and passes it to
8161 * the server directly. We need a x-gm-ext-1 variable
8162 * in the search pgm to carry this information to the
8163 * server.
8165 * Sets searched bits in mail_elts
8167 * Args limitsrch -- limit search to this searchset
8169 * Returns 0 on success.
8172 select_by_gm_content(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8174 int r, we_cancel = 0, rv;
8175 char tmp[128];
8176 char namehdr[MAILTMPLEN];
8177 ESCKEY_S ekey[8];
8178 HelpType help;
8180 ps_global->mangled_footer = 1;
8181 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8183 strncpy(tmp, sel_x_gm_ext, sizeof(tmp)-1);
8184 tmp[sizeof(tmp)-1] = '\0';
8186 namehdr[0] = '\0';
8188 help = NO_HELP;
8189 while (1){
8190 int flags = OE_APPEND_CURRENT;
8192 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8193 sizeof(namehdr), tmp, ekey, help, &flags);
8195 if(r == 4)
8196 continue;
8198 if(r == 3){
8199 help = (help == NO_HELP) ? h_select_by_gm_content : NO_HELP;
8200 continue;
8203 if (r == 1){
8204 cmd_cancelled("Selection by content");
8205 return(1);
8208 removing_leading_white_space(namehdr);
8210 if ((namehdr[0] != '\0')
8211 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8212 removing_trailing_white_space(namehdr);
8214 if (namehdr[0] != '\0')
8215 break;
8218 if(ps_global && ps_global->ttyo){
8219 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8220 ps_global->mangled_footer = 1;
8223 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8225 rv = agg_text_select(stream, msgmap, 'g', namehdr, 0, 0, NULL, "utf-8", limitsrch);
8226 if(we_cancel)
8227 cancel_busy_cue(0);
8229 return(rv);
8233 * Select by searching in message headers or body.
8234 * Sets searched bits in mail_elts
8236 * Args limitsrch -- limit search to this searchset
8238 * Returns 0 on success.
8241 select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch)
8243 int r, ku, type, we_cancel = 0, flags, rv, ekeyi = 0;
8244 int not = 0, me = 0;
8245 char sstring[80], savedsstring[80], tmp[128];
8246 char *p, *sval = NULL;
8247 char buftmp[MAILTMPLEN], namehdr[80];
8248 ESCKEY_S ekey[8];
8249 ENVELOPE *env = NULL;
8250 HelpType help;
8251 unsigned flagsforhist = 0;
8252 static HISTORY_S *history = NULL;
8253 static char *recip = "RECIPIENTS";
8254 static char *partic = "PARTICIPANTS";
8255 static char *match_me = N_("[Match_My_Addresses]");
8256 static char *dont_match_me = N_("[Don't_Match_My_Addresses]");
8258 ps_global->mangled_footer = 1;
8259 savedsstring[0] = '\0';
8260 ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1;
8262 while(1){
8263 type = radio_buttons(not ? _(sel_text_not) : _(sel_text),
8264 -FOOTER_ROWS(ps_global), sel_text_opt,
8265 's', 'x', NO_HELP, RB_NORM|RB_RET_HELP);
8267 if(type == '!')
8268 not = !not;
8269 else if(type == 3){
8270 helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS",
8271 HLPD_SIMPLE);
8272 ps_global->mangled_screen = 1;
8274 else
8275 break;
8279 * prepare some friendly defaults...
8281 switch(type){
8282 case 't' : /* address fields, offer To or From */
8283 case 'f' :
8284 case 'c' :
8285 case 'r' :
8286 case 'p' :
8287 sval = (type == 't') ? "TO" :
8288 (type == 'f') ? "FROM" :
8289 (type == 'c') ? "CC" :
8290 (type == 'r') ? recip : partic;
8291 ekey[ekeyi].ch = ctrl('T');
8292 ekey[ekeyi].name = "^T";
8293 ekey[ekeyi].rval = 10;
8294 /* TRANSLATORS: use Current To Address */
8295 ekey[ekeyi++].label = N_("Cur To");
8296 ekey[ekeyi].ch = ctrl('R');
8297 ekey[ekeyi].name = "^R";
8298 ekey[ekeyi].rval = 11;
8299 /* TRANSLATORS: use Current From Address */
8300 ekey[ekeyi++].label = N_("Cur From");
8301 ekey[ekeyi].ch = ctrl('W');
8302 ekey[ekeyi].name = "^W";
8303 ekey[ekeyi].rval = 12;
8304 /* TRANSLATORS: use Current Cc Address */
8305 ekey[ekeyi++].label = N_("Cur Cc");
8306 ekey[ekeyi].ch = ctrl('Y');
8307 ekey[ekeyi].name = "^Y";
8308 ekey[ekeyi].rval = 13;
8309 /* TRANSLATORS: Match Me means match my address */
8310 ekey[ekeyi++].label = N_("Match Me");
8311 ekey[ekeyi].ch = 0;
8312 ekey[ekeyi].name = "";
8313 ekey[ekeyi].rval = 0;
8314 ekey[ekeyi++].label = "";
8315 break;
8317 case 's' :
8318 sval = "SUBJECT";
8319 ekey[ekeyi].ch = ctrl('X');
8320 ekey[ekeyi].name = "^X";
8321 ekey[ekeyi].rval = 14;
8322 /* TRANSLATORS: use Current Subject */
8323 ekey[ekeyi++].label = N_("Cur Subject");
8324 break;
8326 case 'a' :
8327 sval = "TEXT";
8328 break;
8330 case 'b' :
8331 sval = "BODYTEXT";
8332 break;
8334 case 'h' :
8335 strncpy(tmp, "Name of HEADER to match : ", sizeof(tmp)-1);
8336 tmp[sizeof(tmp)-1] = '\0';
8337 flags = OE_APPEND_CURRENT;
8338 namehdr[0] = '\0';
8339 r = 'x';
8340 while (r == 'x'){
8341 int done = 0;
8343 r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0,
8344 sizeof(namehdr), tmp, ekey, NO_HELP, &flags);
8345 if (r == 1){
8346 cmd_cancelled("Selection by text");
8347 return(1);
8349 removing_leading_white_space(namehdr);
8350 while(!done){
8351 while ((namehdr[0] != '\0') && /* remove trailing ":" */
8352 (namehdr[strlen(namehdr) - 1] == ':'))
8353 namehdr[strlen(namehdr) - 1] = '\0';
8354 if ((namehdr[0] != '\0')
8355 && isspace((unsigned char) namehdr[strlen(namehdr) - 1]))
8356 removing_trailing_white_space(namehdr);
8357 else
8358 done++;
8360 if (strchr(namehdr,' ') || strchr(namehdr,'\t') ||
8361 strchr(namehdr,':'))
8362 namehdr[0] = '\0';
8363 if (namehdr[0] == '\0')
8364 r = 'x';
8366 sval = namehdr;
8367 break;
8369 case 'x':
8370 break;
8372 default:
8373 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
8374 return(1);
8377 ekey[ekeyi].ch = KEY_UP;
8378 ekey[ekeyi].rval = 30;
8379 ekey[ekeyi].name = "";
8380 ku = ekeyi;
8381 ekey[ekeyi++].label = "";
8383 ekey[ekeyi].ch = KEY_DOWN;
8384 ekey[ekeyi].rval = 31;
8385 ekey[ekeyi].name = "";
8386 ekey[ekeyi++].label = "";
8388 ekey[ekeyi].ch = -1;
8390 if(type != 'x'){
8392 init_hist(&history, HISTSIZE);
8394 if(ekey[0].ch > -1 && msgno > 0L
8395 && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno),
8396 NULL)))
8397 ekey[0].ch = -1;
8399 sstring[0] = '\0';
8400 help = NO_HELP;
8401 r = type;
8402 while(r != 'x'){
8403 if(not)
8404 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8405 snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval);
8406 else
8407 snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval);
8409 if(items_in_hist(history) > 0){
8410 ekey[ku].name = HISTORY_UP_KEYNAME;
8411 ekey[ku].label = HISTORY_KEYLABEL;
8412 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
8413 ekey[ku+1].label = HISTORY_KEYLABEL;
8415 else{
8416 ekey[ku].name = "";
8417 ekey[ku].label = "";
8418 ekey[ku+1].name = "";
8419 ekey[ku+1].label = "";
8422 flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
8423 r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
8424 79, tmp, ekey, help, &flags);
8426 if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me)))))
8427 me = 0;
8429 switch(r){
8430 case 3 :
8431 help = (help == NO_HELP)
8432 ? (not
8433 ? ((type == 'f') ? h_select_txt_not_from
8434 : (type == 't') ? h_select_txt_not_to
8435 : (type == 'c') ? h_select_txt_not_cc
8436 : (type == 's') ? h_select_txt_not_subj
8437 : (type == 'a') ? h_select_txt_not_all
8438 : (type == 'r') ? h_select_txt_not_recip
8439 : (type == 'p') ? h_select_txt_not_partic
8440 : (type == 'b') ? h_select_txt_not_body
8441 : NO_HELP)
8442 : ((type == 'f') ? h_select_txt_from
8443 : (type == 't') ? h_select_txt_to
8444 : (type == 'c') ? h_select_txt_cc
8445 : (type == 's') ? h_select_txt_subj
8446 : (type == 'a') ? h_select_txt_all
8447 : (type == 'r') ? h_select_txt_recip
8448 : (type == 'p') ? h_select_txt_partic
8449 : (type == 'b') ? h_select_txt_body
8450 : NO_HELP))
8451 : NO_HELP;
8453 case 4 :
8454 continue;
8456 case 10 : /* To: default */
8457 if(env && env->to && env->to->mailbox){
8458 snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox,
8459 env->to->host ? "@" : "",
8460 env->to->host ? env->to->host : "");
8461 sstring[sizeof(sstring)-1] = '\0';
8463 continue;
8465 case 11 : /* From: default */
8466 if(env && env->from && env->from->mailbox){
8467 snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox,
8468 env->from->host ? "@" : "",
8469 env->from->host ? env->from->host : "");
8470 sstring[sizeof(sstring)-1] = '\0';
8472 continue;
8474 case 12 : /* Cc: default */
8475 if(env && env->cc && env->cc->mailbox){
8476 snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox,
8477 env->cc->host ? "@" : "",
8478 env->cc->host ? env->cc->host : "");
8479 sstring[sizeof(sstring)-1] = '\0';
8481 continue;
8483 case 13 : /* Match my addresses */
8484 me++;
8485 snprintf(sstring, sizeof(sstring), "%s", not ? _(dont_match_me) : _(match_me));
8486 continue;
8488 case 14 : /* Subject: default */
8489 if(env && env->subject && env->subject[0]){
8490 char *q = NULL;
8492 snprintf(buftmp, sizeof(buftmp), "%.75s", env->subject);
8493 buftmp[sizeof(buftmp)-1] = '\0';
8494 q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
8495 SIZEOF_20KBUF, buftmp);
8496 if(q != env->subject){
8497 snprintf(savedsstring, sizeof(savedsstring), "%.70s", q);
8498 savedsstring[sizeof(savedsstring)-1] = '\0';
8501 snprintf(sstring, sizeof(sstring), "%s", q);
8502 sstring[sizeof(sstring)-1] = '\0';
8505 continue;
8507 case 30 :
8508 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8509 if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){
8510 strncpy(sstring, p, sizeof(sstring));
8511 sstring[sizeof(sstring)-1] = '\0';
8512 if(history->hist[history->curindex]){
8513 flagsforhist = history->hist[history->curindex]->flags;
8514 not = (flagsforhist & 0x1) ? 1 : 0;
8515 me = (flagsforhist & 0x2) ? 1 : 0;
8518 else
8519 Writechar(BELL, 0);
8521 continue;
8523 case 31 :
8524 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8525 if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){
8526 strncpy(sstring, p, sizeof(sstring));
8527 sstring[sizeof(sstring)-1] = '\0';
8528 if(history->hist[history->curindex]){
8529 flagsforhist = history->hist[history->curindex]->flags;
8530 not = (flagsforhist & 0x1) ? 1 : 0;
8531 me = (flagsforhist & 0x2) ? 1 : 0;
8534 else
8535 Writechar(BELL, 0);
8537 continue;
8539 default :
8540 break;
8543 if(r == 1 || sstring[0] == '\0')
8544 r = 'x';
8546 break;
8550 if(type == 'x' || r == 'x'){
8551 cmd_cancelled("Selection by text");
8552 return(1);
8555 if(ps_global && ps_global->ttyo){
8556 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8557 ps_global->mangled_footer = 1;
8560 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8562 flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0);
8563 save_hist(history, sstring, flagsforhist, NULL);
8565 rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch);
8566 if(we_cancel)
8567 cancel_busy_cue(0);
8569 return(rv);
8574 * Select by message size.
8575 * Sets searched bits in mail_elts
8577 * Args limitsrch -- limit search to this searchset
8579 * Returns 0 on success.
8582 select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch)
8584 int r, large = 1, we_cancel = 0;
8585 unsigned long n, mult = 1L, numerator = 0L, divisor = 1L;
8586 char size[16], numbers[80], *p, *t;
8587 HelpType help;
8588 SEARCHPGM *pgm;
8589 long flags = (SE_NOPREFETCH | SE_FREE);
8591 numbers[0] = '\0';
8592 ps_global->mangled_footer = 1;
8594 help = NO_HELP;
8595 while(1){
8596 int flgs = OE_APPEND_CURRENT;
8598 sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger;
8600 r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0,
8601 sizeof(numbers), large ? _(select_size_larger_msg)
8602 : _(select_size_smaller_msg),
8603 sel_size_opt, help, &flgs);
8604 if(r == 4)
8605 continue;
8607 if(r == 14){
8608 large = 1 - large;
8609 continue;
8612 if(r == 3){
8613 help = (help == NO_HELP) ? (large ? h_select_by_larger_size
8614 : h_select_by_smaller_size)
8615 : NO_HELP;
8616 continue;
8619 for(t = p = numbers; *p ; p++) /* strip whitespace */
8620 if(!isspace((unsigned char)*p))
8621 *t++ = *p;
8623 *t = '\0';
8625 if(r == 1 || numbers[0] == '\0'){
8626 cmd_cancelled("Selection by size");
8627 return(1);
8629 else
8630 break;
8633 if(numbers[0] == '-'){
8634 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8635 _("Invalid size entered: %s"), numbers);
8636 return(1);
8639 t = size;
8640 p = numbers;
8642 while(*p && isdigit((unsigned char)*p))
8643 *t++ = *p++;
8645 *t = '\0';
8647 if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){
8648 size[0] = '0';
8649 size[1] = '\0';
8652 if(size[0] == '\0'){
8653 q_status_message1(SM_ORDER | SM_DING, 0, 2,
8654 _("Invalid size entered: %s"), numbers);
8655 return(1);
8658 n = strtoul(size, (char **)NULL, 10);
8660 size[0] = '\0';
8661 if(*p == '.'){
8663 * We probably ought to just use atof() to convert 1.1 into a
8664 * double, but since we haven't used atof() anywhere else I'm
8665 * reluctant to use it because of portability concerns.
8667 p++;
8668 t = size;
8669 while(*p && isdigit((unsigned char)*p)){
8670 *t++ = *p++;
8671 divisor *= 10;
8674 *t = '\0';
8676 if(size[0])
8677 numerator = strtoul(size, (char **)NULL, 10);
8680 switch(*p){
8681 case 'g':
8682 case 'G':
8683 mult *= 1000;
8684 /* fall through */
8686 case 'm':
8687 case 'M':
8688 mult *= 1000;
8689 /* fall through */
8691 case 'k':
8692 case 'K':
8693 mult *= 1000;
8694 break;
8697 n = n * mult + (numerator * mult) / divisor;
8699 pgm = mail_newsearchpgm();
8700 if(large)
8701 pgm->larger = n;
8702 else
8703 pgm->smaller = n;
8705 if(is_imap_stream(stream) && !modern_imap_stream(stream))
8706 flags |= SE_NOSERVER;
8708 if(ps_global && ps_global->ttyo){
8709 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8710 ps_global->mangled_footer = 1;
8713 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8715 pgm->msgno = (limitsrch ? *limitsrch : NULL);
8716 pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE);
8717 /* we know this was freed in mail_search, let caller know */
8718 if(limitsrch)
8719 *limitsrch = NULL;
8721 if(we_cancel)
8722 cancel_busy_cue(0);
8724 return(0);
8729 * visible_searchset -- return c-client search set unEXLDed
8730 * sequence numbers
8732 SEARCHSET *
8733 visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap)
8735 long n, run;
8736 SEARCHSET *full_set = NULL, **set;
8739 * If we're talking to anything other than a server older than
8740 * imap 4rev1, build a searchset otherwise it'll choke.
8742 if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
8743 if(any_lflagged(msgmap, MN_EXLD)){
8744 for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
8745 if(get_lflag(stream, NULL, n, MN_EXLD)){
8746 if(run){ /* previous NOT excluded? */
8747 if(run > 1L)
8748 (*set)->last = n - 1L;
8750 set = &(*set)->next;
8751 run = 0L;
8754 else if(run++){ /* next in run */
8755 (*set)->last = n;
8757 else{ /* start of run */
8758 *set = mail_newsearchset();
8759 (*set)->first = n;
8762 else{
8763 full_set = mail_newsearchset();
8764 full_set->first = 1L;
8765 full_set->last = stream->nmsgs;
8769 return(full_set);
8774 * Select by message status bits.
8775 * Sets searched bits in mail_elts
8777 * Args limitsrch -- limit search to this searchset
8779 * Returns 0 on success.
8782 select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch)
8784 int s, not = 0, we_cancel = 0, rv;
8786 while(1){
8787 s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag),
8788 -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x',
8789 NO_HELP, RB_NORM|RB_RET_HELP);
8791 if(s == 'x'){
8792 cmd_cancelled("Selection by status");
8793 return(1);
8795 else if(s == 3){
8796 helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"),
8797 HLPD_SIMPLE);
8798 ps_global->mangled_screen = 1;
8800 else if(s == '!')
8801 not = !not;
8802 else
8803 break;
8806 if(ps_global && ps_global->ttyo){
8807 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8808 ps_global->mangled_footer = 1;
8811 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8812 rv = agg_flag_select(stream, not, s, limitsrch);
8813 if(we_cancel)
8814 cancel_busy_cue(0);
8816 return(rv);
8821 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8822 * Sets searched bits in mail_elts
8824 * Args limitsrch -- limit search to this searchset
8826 * Returns 0 on success.
8829 select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch)
8831 char rulenick[1000], *nick;
8832 PATGRP_S *patgrp;
8833 int r, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH
8834 | ROLE_DO_INCOLS
8835 | ROLE_DO_ROLES
8836 | ROLE_DO_SCORES
8837 | ROLE_DO_OTHER
8838 | ROLE_DO_FILTER;
8840 rulenick[0] = '\0';
8841 ps_global->mangled_footer = 1;
8844 int oe_flags;
8846 oe_flags = OE_APPEND_CURRENT;
8847 r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0,
8848 sizeof(rulenick),
8849 not ? _("Rule to NOT match: ")
8850 : _("Rule to match: "),
8851 sel_key_opt, NO_HELP, &oe_flags);
8853 if(r == 14){
8854 /* select rulenick from a list */
8855 if((nick=choose_a_rule(rflags)) != NULL){
8856 strncpy(rulenick, nick, sizeof(rulenick)-1);
8857 rulenick[sizeof(rulenick)-1] = '\0';
8858 fs_give((void **) &nick);
8860 else
8861 r = 4;
8863 else if(r == '!')
8864 not = !not;
8866 if(r == 3){
8867 helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE);
8868 ps_global->mangled_screen = 1;
8870 else if(r == 1){
8871 cmd_cancelled("Selection by Rule");
8872 return(1);
8875 removing_leading_and_trailing_white_space(rulenick);
8877 }while(r == 3 || r == 4 || r == '!');
8881 * The approach of requiring a nickname instead of just allowing the
8882 * user to select from the list of rules has the drawback that a rule
8883 * may not have a nickname, or there may be more than one rule with
8884 * the same nickname. However, it has the benefit of allowing the user
8885 * to type in the nickname and, most importantly, allows us to set
8886 * up the ! (not). We could incorporate the ! into the selection
8887 * screen, but this is easier and also allows the typing of nicks.
8888 * User can just set up nicknames if they want to use this feature.
8890 patgrp = nick_to_patgrp(rulenick, rflags);
8892 if(patgrp){
8893 if(ps_global && ps_global->ttyo){
8894 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
8895 ps_global->mangled_footer = 1;
8898 we_cancel = busy_cue(_("Selecting"), NULL, 1);
8899 match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL,
8900 get_msg_score,
8901 (not ? MP_NOT : 0) | SE_NOPREFETCH);
8902 free_patgrp(&patgrp);
8903 if(we_cancel)
8904 cancel_busy_cue(0);
8907 if(limitsrch && *limitsrch){
8908 mail_free_searchset(limitsrch);
8909 *limitsrch = NULL;
8912 return(0);
8917 * Allow user to choose a rule from their list of rules.
8919 * Returns an allocated rule nickname on success, NULL otherwise.
8921 char *
8922 choose_a_rule(int rflags)
8924 char *choice = NULL;
8925 char **rule_list, **lp;
8926 int cnt = 0;
8927 PAT_S *pat;
8928 PAT_STATE pstate;
8930 if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){
8931 q_status_message(SM_ORDER, 3, 3,
8932 _("No rules available. Use Setup/Rules to add some."));
8933 return(choice);
8937 * Build a list of rules to choose from.
8940 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8941 cnt++;
8943 if(cnt <= 0){
8944 q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules"));
8945 return(choice);
8948 lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list));
8949 memset(rule_list, 0, (cnt+1) * sizeof(*rule_list));
8951 for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate))
8952 *lp++ = cpystr((pat->patgrp && pat->patgrp->nick)
8953 ? pat->patgrp->nick : "?");
8955 /* TRANSLATORS: SELECT A RULE is a screen title
8956 TRANSLATORS: Print something1 using something2.
8957 "rules" is something1 */
8958 choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"),
8959 _("rules"), h_select_rule_screen,
8960 _("HELP FOR SELECTING A RULE NICKNAME"), NULL);
8962 if(!choice)
8963 q_status_message(SM_ORDER, 1, 4, "No choice");
8965 free_list_array(&rule_list);
8967 return(choice);
8972 * Select by current thread.
8973 * Sets searched bits in mail_elts for this entire thread
8975 * Args limitsrch -- limit search to this searchset
8977 * Returns 0 on success.
8980 select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch)
8982 long n;
8983 PINETHRD_S *thrd = NULL;
8984 int ret = 1;
8985 MESSAGECACHE *mc;
8987 if(!stream)
8988 return(ret);
8990 for(n = 1L; n <= stream->nmsgs; n++)
8991 if((mc = mail_elt(stream, n)) != NULL)
8992 mc->searched = 0; /* clear searched bits */
8994 thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap)));
8995 if(thrd && thrd->top && thrd->top != thrd->rawno)
8996 thrd = fetch_thread(stream, thrd->top);
8999 * This doesn't unselect if the thread is already selected
9000 * (like select current does), it always selects.
9001 * There is no way to select ! this thread.
9003 if(thrd){
9004 set_search_bit_for_thread(stream, thrd, limitsrch);
9005 ret = 0;
9008 return(ret);
9013 * Select by message keywords.
9014 * Sets searched bits in mail_elts
9016 * Args limitsrch -- limit search to this searchset
9018 * Returns 0 on success.
9021 select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch)
9023 int r, not = 0, we_cancel = 0;
9024 char keyword[MAXUSERFLAG+1], *kword;
9025 char *error = NULL, *p, *prompt;
9026 HelpType help;
9027 SEARCHPGM *pgm;
9029 keyword[0] = '\0';
9030 ps_global->mangled_footer = 1;
9032 help = NO_HELP;
9034 int oe_flags;
9036 if(error){
9037 q_status_message(SM_ORDER, 3, 4, error);
9038 fs_give((void **) &error);
9041 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
9042 if(not)
9043 prompt = _("Keyword (or keyword initial) to NOT match: ");
9044 else
9045 prompt = _("Keyword (or keyword initial) to match: ");
9047 else{
9048 if(not)
9049 prompt = _("Keyword to NOT match: ");
9050 else
9051 prompt = _("Keyword to match: ");
9054 oe_flags = OE_APPEND_CURRENT;
9055 r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0,
9056 sizeof(keyword),
9057 prompt, sel_key_opt, help, &oe_flags);
9059 if(r == 14){
9060 /* select keyword from a list */
9061 if((kword=choose_a_keyword()) != NULL){
9062 strncpy(keyword, kword, sizeof(keyword)-1);
9063 keyword[sizeof(keyword)-1] = '\0';
9064 fs_give((void **) &kword);
9066 else
9067 r = 4;
9069 else if(r == '!')
9070 not = !not;
9072 if(r == 3)
9073 help = help == NO_HELP ? h_select_keyword : NO_HELP;
9074 else if(r == 1){
9075 cmd_cancelled("Selection by keyword");
9076 return(1);
9079 removing_leading_and_trailing_white_space(keyword);
9081 }while(r == 3 || r == 4 || r == '!' || keyword_check(keyword, &error));
9084 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){
9085 p = initial_to_keyword(keyword);
9086 if(p != keyword){
9087 strncpy(keyword, p, sizeof(keyword)-1);
9088 keyword[sizeof(keyword)-1] = '\0';
9093 * We want to check the keyword, not the nickname of the keyword,
9094 * so convert it to the keyword if necessary.
9096 p = nick_to_keyword(keyword);
9097 if(p != keyword){
9098 strncpy(keyword, p, sizeof(keyword)-1);
9099 keyword[sizeof(keyword)-1] = '\0';
9102 pgm = mail_newsearchpgm();
9103 if(not){
9104 pgm->unkeyword = mail_newstringlist();
9105 pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword);
9106 pgm->unkeyword->text.size = strlen(keyword);
9108 else{
9109 pgm->keyword = mail_newstringlist();
9110 pgm->keyword->text.data = (unsigned char *) cpystr(keyword);
9111 pgm->keyword->text.size = strlen(keyword);
9114 if(ps_global && ps_global->ttyo){
9115 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9116 ps_global->mangled_footer = 1;
9119 we_cancel = busy_cue(_("Selecting"), NULL, 1);
9121 pgm->msgno = (limitsrch ? *limitsrch : NULL);
9122 pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE);
9123 /* we know this was freed in mail_search, let caller know */
9124 if(limitsrch)
9125 *limitsrch = NULL;
9127 if(we_cancel)
9128 cancel_busy_cue(0);
9130 return(0);
9135 * Allow user to choose a keyword from their list of keywords.
9137 * Returns an allocated keyword on success, NULL otherwise.
9139 char *
9140 choose_a_keyword(void)
9142 char *choice = NULL;
9143 char **keyword_list, **lp;
9144 int cnt;
9145 KEYWORD_S *kw;
9148 * Build a list of keywords to choose from.
9151 for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next)
9152 cnt++;
9154 if(cnt <= 0){
9155 q_status_message(SM_ORDER, 3, 4,
9156 _("No keywords defined, use \"keywords\" option in Setup/Config"));
9157 return(choice);
9160 lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list));
9161 memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list));
9163 for(kw = ps_global->keywords; kw; kw = kw->next)
9164 *lp++ = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9166 /* TRANSLATORS: SELECT A KEYWORD is a screen title
9167 TRANSLATORS: Print something1 using something2.
9168 "keywords" is something1 */
9169 choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"),
9170 _("keywords"), h_select_keyword_screen,
9171 _("HELP FOR SELECTING A KEYWORD"), NULL);
9173 if(!choice)
9174 q_status_message(SM_ORDER, 1, 4, "No choice");
9176 free_list_array(&keyword_list);
9178 return(choice);
9183 * Allow user to choose a list of keywords from their list of keywords.
9185 * Returns allocated list.
9187 char **
9188 choose_list_of_keywords(void)
9190 LIST_SEL_S *listhead, *ls, *p;
9191 char **ret = NULL;
9192 int cnt, i;
9193 KEYWORD_S *kw;
9196 * Build a list of keywords to choose from.
9199 p = listhead = NULL;
9200 for(kw = ps_global->keywords; kw; kw = kw->next){
9202 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9203 memset(ls, 0, sizeof(*ls));
9204 ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : "");
9206 if(p){
9207 p->next = ls;
9208 p = p->next;
9210 else
9211 listhead = p = ls;
9214 if(!listhead)
9215 return(ret);
9217 /* TRANSLATORS: SELECT KEYWORDS is a screen title
9218 Print something1 using something2.
9219 "keywords" is something1 */
9220 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9221 _("SELECT KEYWORDS"), _("keywords"),
9222 h_select_multkeyword_screen,
9223 _("HELP FOR SELECTING KEYWORDS"), NULL)){
9224 for(cnt = 0, p = listhead; p; p = p->next)
9225 if(p->selected)
9226 cnt++;
9228 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9229 memset(ret, 0, (cnt+1) * sizeof(*ret));
9230 for(i = 0, p = listhead; p; p = p->next)
9231 if(p->selected)
9232 ret[i++] = cpystr(p->item ? p->item : "");
9235 free_list_sel(&listhead);
9237 return(ret);
9242 * Allow user to choose a charset
9244 * Returns an allocated charset on success, NULL otherwise.
9246 char *
9247 choose_a_charset(int which_charsets)
9249 char *choice = NULL;
9250 char **charset_list, **lp;
9251 const CHARSET *cs;
9252 int cnt;
9255 * Build a list of charsets to choose from.
9258 for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){
9259 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9260 && ((which_charsets & CAC_ALL)
9261 || (which_charsets & CAC_POSTING
9262 && cs->flags & CF_POSTING)
9263 || (which_charsets & CAC_DISPLAY
9264 && cs->type != CT_2022
9265 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9266 cnt++;
9269 if(cnt <= 0){
9270 q_status_message(SM_ORDER, 3, 4,
9271 _("No charsets found? Enter charset manually."));
9272 return(choice);
9275 lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list));
9276 memset(charset_list, 0, (cnt+1) * sizeof(*charset_list));
9278 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9279 if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL))
9280 && ((which_charsets & CAC_ALL)
9281 || (which_charsets & CAC_POSTING
9282 && cs->flags & CF_POSTING)
9283 || (which_charsets & CAC_DISPLAY
9284 && cs->type != CT_2022
9285 && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY))))
9286 *lp++ = cpystr(cs->name);
9289 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
9290 TRANSLATORS: Print something1 using something2.
9291 "character sets" is something1 */
9292 choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"),
9293 _("character sets"), h_select_charset_screen,
9294 _("HELP FOR SELECTING A CHARACTER SET"), NULL);
9296 if(!choice)
9297 q_status_message(SM_ORDER, 1, 4, "No choice");
9299 free_list_array(&charset_list);
9301 return(choice);
9306 * Allow user to choose a list of character sets and/or scripts
9308 * Returns allocated list.
9310 char **
9311 choose_list_of_charsets(void)
9313 LIST_SEL_S *listhead, *ls, *p;
9314 char **ret = NULL;
9315 int cnt, i, got_one;
9316 const CHARSET *cs;
9317 SCRIPT *s;
9318 char *q, *t;
9319 long width, limit;
9320 char buf[1024], *folded;
9323 * Build a list of charsets to choose from.
9326 p = listhead = NULL;
9328 /* this width is determined by select_from_list_screen() */
9329 width = ps_global->ttyo->screen_cols - 4;
9331 /* first comes a list of scripts (sets of character sets) */
9332 for(s = utf8_script(NIL); s && s->name; s++){
9334 limit = sizeof(buf)-1;
9335 q = buf;
9336 memset(q, 0, limit+1);
9338 if(s->name)
9339 sstrncpy(&q, s->name, limit);
9341 if(s->description){
9342 sstrncpy(&q, " (", limit-(q-buf));
9343 sstrncpy(&q, s->description, limit-(q-buf));
9344 sstrncpy(&q, ")", limit-(q-buf));
9347 /* add the list of charsets that are in this script */
9348 got_one = 0;
9349 for(cs = utf8_charset(NIL);
9350 cs && cs->name && (q-buf) < limit; cs++){
9351 if(cs->script & s->script){
9353 * Filter out some un-useful members of the list.
9354 * UTF-7 and UTF-8 weren't actually in the list at the
9355 * time this was written. Just making sure.
9357 if(!strucmp(cs->name, "ISO-2022-JP-2")
9358 || !strucmp(cs->name, "UTF-7")
9359 || !strucmp(cs->name, "UTF-8"))
9360 continue;
9362 if(got_one)
9363 sstrncpy(&q, " ", limit-(q-buf));
9364 else{
9365 got_one = 1;
9366 sstrncpy(&q, " {", limit-(q-buf));
9369 sstrncpy(&q, cs->name, limit-(q-buf));
9373 if(got_one)
9374 sstrncpy(&q, "}", limit-(q-buf));
9376 /* fold this line so that it can all be seen on the screen */
9377 folded = fold(buf, width, width, "", " ", FLD_NONE);
9378 if(folded){
9379 t = folded;
9380 while(t && *t && (q = strindex(t, '\n')) != NULL){
9381 *q = '\0';
9383 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9384 memset(ls, 0, sizeof(*ls));
9385 if(t == folded)
9386 ls->item = cpystr(s->name);
9387 else
9388 ls->flags = SFL_NOSELECT;
9390 ls->display_item = cpystr(t);
9392 t = q+1;
9394 if(p){
9395 p->next = ls;
9396 p = p->next;
9398 else{
9399 /* add a heading */
9400 listhead = (LIST_SEL_S *) fs_get(sizeof(*ls));
9401 memset(listhead, 0, sizeof(*listhead));
9402 listhead->flags = SFL_NOSELECT;
9403 listhead->display_item =
9404 cpystr(_("Scripts representing groups of related character sets"));
9405 listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls));
9406 memset(listhead->next, 0, sizeof(*listhead));
9407 listhead->next->flags = SFL_NOSELECT;
9408 listhead->next->display_item =
9409 cpystr(repeat_char(width, '-'));
9411 listhead->next->next = ls;
9412 p = ls;
9416 fs_give((void **) &folded);
9420 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9421 memset(ls, 0, sizeof(*ls));
9422 ls->flags = SFL_NOSELECT;
9423 if(p){
9424 p->next = ls;
9425 p = p->next;
9427 else
9428 listhead = p = ls;
9430 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9431 memset(ls, 0, sizeof(*ls));
9432 ls->flags = SFL_NOSELECT;
9433 ls->display_item =
9434 cpystr(_("Individual character sets, may be mixed with scripts"));
9435 p->next = ls;
9436 p = p->next;
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(repeat_char(width, '-'));
9443 p->next = ls;
9444 p = p->next;
9446 /* then comes a list of individual character sets */
9447 for(cs = utf8_charset(NIL); cs && cs->name; cs++){
9448 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9449 memset(ls, 0, sizeof(*ls));
9450 ls->item = cpystr(cs->name);
9452 if(p){
9453 p->next = ls;
9454 p = p->next;
9456 else
9457 listhead = p = ls;
9460 if(!listhead)
9461 return(ret);
9463 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9464 Print something1 using something2.
9465 "character sets" is something1 */
9466 if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE,
9467 _("SELECT CHARACTER SETS"), _("character sets"),
9468 h_select_multcharsets_screen,
9469 _("HELP FOR SELECTING CHARACTER SETS"), NULL)){
9470 for(cnt = 0, p = listhead; p; p = p->next)
9471 if(p->selected)
9472 cnt++;
9474 ret = (char **) fs_get((cnt+1) * sizeof(*ret));
9475 memset(ret, 0, (cnt+1) * sizeof(*ret));
9476 for(i = 0, p = listhead; p; p = p->next)
9477 if(p->selected)
9478 ret[i++] = cpystr(p->item ? p->item : "");
9481 free_list_sel(&listhead);
9483 return(ret);
9486 /* Report quota summary resources in an IMAP server */
9488 void
9489 cmd_quota (struct pine *state)
9491 QUOTALIST *imapquota;
9492 NETMBX mb;
9493 STORE_S *store;
9494 SCROLL_S sargs;
9496 if(!state->mail_stream || !is_imap_stream(state->mail_stream)){
9497 q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders");
9498 return;
9501 if (state->mail_stream
9502 && !sp_dead_stream(state->mail_stream)
9503 && state->mail_stream->mailbox
9504 && *state->mail_stream->mailbox
9505 && mail_valid_net_parse(state->mail_stream->mailbox, &mb))
9506 imap_getquotaroot(state->mail_stream, mb.mailbox);
9508 if(!state->quota) /* failed ? */
9509 return; /* go back... */
9511 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
9512 q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space.");
9513 return;
9516 so_puts(store, "Quota Report for ");
9517 so_puts(store, state->mail_stream->original_mailbox);
9518 so_puts(store, "\n\n");
9520 for (imapquota = state->quota; imapquota; imapquota = imapquota->next){
9522 so_puts(store, _("Resource : "));
9523 so_puts(store, imapquota->name);
9524 so_writec('\n', store);
9526 so_puts(store, _("Usage : "));
9527 so_puts(store, long2string(imapquota->usage));
9528 if(!strucmp(imapquota->name,"STORAGE"))
9529 so_puts(store, " KiB ");
9530 if(!strucmp(imapquota->name,"MESSAGE")){
9531 so_puts(store, _(" message"));
9532 if(imapquota->usage != 1)
9533 so_puts(store, _("s ")); /* plural */
9534 else
9535 so_puts(store, _(" "));
9537 so_writec('(', store);
9538 so_puts(store, long2string(100*imapquota->usage/imapquota->limit));
9539 so_puts(store, "%)\n");
9541 so_puts(store, _("Limit : "));
9542 so_puts(store, long2string(imapquota->limit));
9543 if(!strucmp(imapquota->name,"STORAGE"))
9544 so_puts(store, " KiB\n\n");
9545 if(!strucmp(imapquota->name,"MESSAGE")){
9546 so_puts(store, _(" message"));
9547 if(imapquota->usage != 1)
9548 so_puts(store, _("s\n\n")); /* plural */
9549 else
9550 so_puts(store, _("\n\n"));
9554 memset(&sargs, 0, sizeof(SCROLL_S));
9555 sargs.text.text = so_text(store);
9556 sargs.text.src = CharStar;
9557 sargs.text.desc = _("Quota Resources Summary");
9558 sargs.bar.title = _("QUOTA SUMMARY");
9559 sargs.proc.tool = NULL;
9560 sargs.help.text = h_quota_command;
9561 sargs.help.title = NULL;
9562 sargs.keys.menu = NULL;
9563 setbitmap(sargs.keys.bitmap);
9565 scrolltool(&sargs);
9566 so_give(&store);
9568 if (state->quota)
9569 mail_free_quotalist(&(state->quota));
9572 /*----------------------------------------------------------------------
9573 Prompt the user for the type of sort he desires
9575 Args: state -- pine state pointer
9576 q1 -- Line to prompt on
9578 Returns 0 if it was cancelled, 1 otherwise.
9579 ----*/
9581 select_sort(struct pine *state, int ql, SortOrder *sort, int *rev)
9583 char prompt[200], tmp[3], *p;
9584 int s, i;
9585 int deefault = 'a', retval = 1;
9586 HelpType help;
9587 ESCKEY_S sorts[14];
9589 #ifdef _WINDOWS
9590 DLG_SORTPARAM sortsel;
9592 if (mswin_usedialog ()) {
9594 sortsel.reverse = mn_get_revsort (state->msgmap);
9595 sortsel.cursort = mn_get_sort (state->msgmap);
9596 /* assumption here that HelpType is char ** */
9597 sortsel.helptext = h_select_sort;
9598 sortsel.rval = 0;
9600 if ((retval = os_sortdialog (&sortsel))) {
9601 *sort = sortsel.cursort;
9602 *rev = sortsel.reverse;
9605 return (retval);
9607 #endif
9609 /*----- String together the prompt ------*/
9610 tmp[1] = '\0';
9611 if(F_ON(F_USE_FK,ps_global))
9612 strncpy(prompt, _("Choose type of sort : "), sizeof(prompt));
9613 else
9614 strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "),
9615 sizeof(prompt));
9617 for(i = 0; state->sort_types[i] != EndofList; i++) {
9618 sorts[i].rval = i;
9619 p = sorts[i].label = sort_name(state->sort_types[i]);
9620 while(*(p+1) && islower((unsigned char)*p))
9621 p++;
9623 sorts[i].ch = tolower((unsigned char)(tmp[0] = *p));
9624 sorts[i].name = cpystr(tmp);
9626 if(mn_get_sort(state->msgmap) == state->sort_types[i])
9627 deefault = sorts[i].rval;
9630 sorts[i].ch = 'r';
9631 sorts[i].rval = 'r';
9632 sorts[i].name = cpystr("R");
9633 if(F_ON(F_USE_FK,ps_global))
9634 sorts[i].label = N_("Reverse");
9635 else
9636 sorts[i].label = "";
9638 sorts[++i].ch = -1;
9639 help = h_select_sort;
9641 if((F_ON(F_USE_FK,ps_global)
9642 && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x',
9643 help,RB_NORM)) != 'x'))
9645 (F_OFF(F_USE_FK,ps_global)
9646 && ((s = radio_buttons(prompt,ql,sorts,deefault,'x',
9647 help,RB_NORM)) != 'x'))){
9648 state->mangled_body = 1; /* signal screen's changed */
9649 if(s == 'r')
9650 *rev = !mn_get_revsort(state->msgmap);
9651 else
9652 *sort = state->sort_types[s];
9654 if(F_ON(F_SHOW_SORT, ps_global))
9655 ps_global->mangled_header = 1;
9657 else{
9658 retval = 0;
9659 cmd_cancelled("Sort");
9662 while(--i >= 0)
9663 fs_give((void **)&sorts[i].name);
9665 blank_keymenu(ps_global->ttyo->screen_rows - 2, 0);
9666 return(retval);
9670 /*---------------------------------------------------------------------
9671 Build list of folders in the given context for user selection
9673 Args: c -- pointer to pointer to folder's context context
9674 f -- folder prefix to display
9675 sublist -- whether or not to use 'f's contents as prefix
9676 lister -- function used to do the actual display
9678 Returns: malloc'd string containing sequence, else NULL if
9679 no messages in msgmap with local "selected" flag.
9680 ----*/
9682 display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int))
9684 int rc;
9685 CONTEXT_S *tc;
9686 void (*redraw)(void) = ps_global->redrawer;
9688 push_titlebar_state();
9689 tc = *c;
9690 if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0)
9691 *c = tc;
9693 ClearScreen();
9694 pop_titlebar_state();
9695 redraw_titlebar();
9696 if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */
9697 (*ps_global->redrawer)();
9699 if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global))
9700 return(1);
9702 return(0);
9707 * Allow user to choose a single item from a list of strings.
9709 * Args list -- Array of strings to choose from, NULL terminated.
9710 * displist -- Array of strings to display instead of displaying list.
9711 * Indices correspond to the list array. Display the displist
9712 * but return the item from list if displist non-NULL.
9713 * title -- For conf_scroll_screen
9714 * pdesc -- For conf_scroll_screen
9715 * help -- For conf_scroll_screen
9716 * htitle -- For conf_scroll_screen
9718 * Returns an allocated copy of the chosen item or NULL.
9720 char *
9721 choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help,
9722 char *htitle, char *cursor_location)
9724 LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL;
9725 char **t, **dl;
9726 char *ret = NULL, *choice = NULL;
9728 /* build the LIST_SEL_S list */
9729 p = listhead = NULL;
9730 for(t = list, dl = displist; *t; t++, dl++){
9731 ls = (LIST_SEL_S *) fs_get(sizeof(*ls));
9732 memset(ls, 0, sizeof(*ls));
9733 ls->item = cpystr(*t);
9734 if(displist)
9735 ls->display_item = cpystr(*dl);
9737 if(cursor_location && (cursor_location == (*t)))
9738 starting_val = ls;
9740 if(p){
9741 p->next = ls;
9742 p = p->next;
9744 else
9745 listhead = p = ls;
9748 if(!listhead)
9749 return(ret);
9751 if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc,
9752 help, htitle, starting_val))
9753 for(p = listhead; !choice && p; p = p->next)
9754 if(p->selected)
9755 choice = p->item;
9757 if(choice)
9758 ret = cpystr(choice);
9760 free_list_sel(&listhead);
9762 return(ret);
9766 void
9767 free_list_sel(LIST_SEL_S **lsel)
9769 if(lsel && *lsel){
9770 free_list_sel(&(*lsel)->next);
9771 if((*lsel)->item)
9772 fs_give((void **) &(*lsel)->item);
9774 if((*lsel)->display_item)
9775 fs_give((void **) &(*lsel)->display_item);
9777 fs_give((void **) lsel);
9783 * file_lister - call pico library's file lister
9786 file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags)
9788 PICO pbf;
9789 int rv;
9790 void (*redraw)(void) = ps_global->redrawer;
9792 standard_picobuf_setup(&pbf);
9793 push_titlebar_state();
9794 if(!newmail)
9795 pbf.newmail = NULL;
9797 /* BUG: what about help command and text? */
9798 pbf.pine_anchor = title;
9800 rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags);
9801 standard_picobuf_teardown(&pbf);
9802 fix_windsize(ps_global);
9803 init_signals(); /* has it's own signal stuff */
9805 /* Restore display's titlebar and body */
9806 pop_titlebar_state();
9807 redraw_titlebar();
9808 if((ps_global->redrawer = redraw) != NULL)
9809 (*ps_global->redrawer)();
9811 return(rv);
9815 /*----------------------------------------------------------------------
9816 Print current folder index
9818 ---*/
9820 print_index(struct pine *state, MSGNO_S *msgmap, int agg)
9822 long i;
9823 ICE_S *ice;
9824 char buf[MAX_SCREEN_COLS+1];
9826 for(i = 1L; i <= mn_get_total(msgmap); i++){
9827 if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT))
9828 continue;
9830 if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0))
9831 continue;
9833 ice = build_header_line(state, state->mail_stream, msgmap, i, NULL);
9835 if(ice){
9837 * I don't understand why we'd want to mark the current message
9838 * instead of printing out the first character of the status
9839 * so I'm taking it out and including the first character of the
9840 * line instead. Hubert 2006-02-09
9842 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
9843 return(0);
9846 if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i),
9847 print_char)
9848 || !gf_puts(NEWLINE, print_char))
9849 return(0);
9853 return(1);
9856 #ifdef _WINDOWS
9859 * windows callback to get/set header mode state
9862 header_mode_callback(set, args)
9863 int set;
9864 long args;
9866 return(ps_global->full_header);
9871 * windows callback to get/set zoom mode state
9874 zoom_mode_callback(set, args)
9875 int set;
9876 long args;
9878 return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0);
9883 * windows callback to get/set zoom mode state
9886 any_selected_callback(set, args)
9887 int set;
9888 long args;
9890 return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0);
9898 flag_callback(set, flags)
9899 int set;
9900 long flags;
9902 MESSAGECACHE *mc;
9903 int newflags = 0;
9904 long msgno;
9905 int permflag = 0;
9907 switch (set) {
9908 case 1: /* Important */
9909 permflag = ps_global->mail_stream->perm_flagged;
9910 break;
9912 case 2: /* New */
9913 permflag = ps_global->mail_stream->perm_seen;
9914 break;
9916 case 3: /* Answered */
9917 permflag = ps_global->mail_stream->perm_answered;
9918 break;
9920 case 4: /* Deleted */
9921 permflag = ps_global->mail_stream->perm_deleted;
9922 break;
9926 if(!(any_messages(ps_global->msgmap, NULL, "to Flag")
9927 && can_set_flag(ps_global, "flag", permflag)))
9928 return(0);
9930 if(sp_io_error_on_stream(ps_global->mail_stream)){
9931 sp_set_io_error_on_stream(ps_global->mail_stream, 0);
9932 pine_mail_check(ps_global->mail_stream); /* forces write */
9933 return(0);
9936 msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap));
9937 if(msgno > 0L && ps_global->mail_stream
9938 && msgno <= ps_global->mail_stream->nmsgs
9939 && (mc = mail_elt(ps_global->mail_stream, msgno))
9940 && mc->valid){
9942 * NOTE: code below is *VERY* sensitive to the order of
9943 * the messages defined in resource.h for flag handling.
9944 * Don't change it unless you know what you're doing.
9946 if(set){
9947 char *flagstr;
9948 long mflag;
9950 switch(set){
9951 case 1 : /* Important */
9952 flagstr = "\\FLAGGED";
9953 mflag = (mc->flagged) ? 0L : ST_SET;
9954 break;
9956 case 2 : /* New */
9957 flagstr = "\\SEEN";
9958 mflag = (mc->seen) ? 0L : ST_SET;
9959 break;
9961 case 3 : /* Answered */
9962 flagstr = "\\ANSWERED";
9963 mflag = (mc->answered) ? 0L : ST_SET;
9964 break;
9966 case 4 : /* Deleted */
9967 flagstr = "\\DELETED";
9968 mflag = (mc->deleted) ? 0L : ST_SET;
9969 break;
9971 default : /* bogus */
9972 return(0);
9975 mail_flag(ps_global->mail_stream, long2string(msgno),
9976 flagstr, mflag);
9978 if(ps_global->redrawer)
9979 (*ps_global->redrawer)();
9981 else{
9982 /* Important */
9983 if(mc->flagged)
9984 newflags |= 0x0001;
9986 /* New */
9987 if(!mc->seen)
9988 newflags |= 0x0002;
9990 /* Answered */
9991 if(mc->answered)
9992 newflags |= 0x0004;
9994 /* Deleted */
9995 if(mc->deleted)
9996 newflags |= 0x0008;
10000 return(newflags);
10006 * BUG: Should teach this about keywords
10008 MPopup *
10009 flag_submenu(mc)
10010 MESSAGECACHE *mc;
10012 static MPopup flag_submenu[] = {
10013 {tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}},
10014 {tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}},
10015 {tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}},
10016 {tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}},
10017 {tTail}
10020 /* Important */
10021 flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal;
10023 /* New */
10024 flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked;
10026 /* Answered */
10027 flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal;
10029 /* Deleted */
10030 flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal;
10032 return(flag_submenu);
10035 #endif /* _WINDOWS */