1 #if !defined(lint) && !defined(DOS)
2 static char rcsid
[] = "$Id: mailcmd.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
6 * ========================================================================
7 * Copyright 2013-2020 Eduardo Chappa
8 * Copyright 2006-2009 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
21 The meat and pototoes of mail processing here:
22 - initial command processing and dispatch
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
35 #include "flagmaint.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"
77 #include "../pico/osdep/mswin.h"
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")},
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")},
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
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
},
190 static ESCKEY_S sel_opts4
[] = {
191 {'a', 'a', "A", N_("select All")},
192 /* TRANSLATORS: select currently highlighted message Thread */
193 {'c', 'c', "C", N_("select Curthrd")},
194 {'n', 'n', "N", N_("Number")},
195 {'d', 'd', "D", N_("Date")},
196 {'t', 't', "T", N_("Text")},
197 {'s', 's', "S", N_("Status")},
198 {'z', 'z', "Z", N_("siZe")},
199 {'k', 'k', "K", N_("Keyword")},
200 {'r', 'r', "R", N_("Rule")},
201 {SEL_OPTS_THREAD_CH
, 'h', "H", N_("tHread")},
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")},
230 static ESCKEY_S sel_opts6
[] = {
231 {'a', 'a', "A", N_("select All")},
232 /* TRANSLATORS: select currently highlighted message Thread */
233 {'c', 'c', "C", N_("select Curthrd")},
234 {'n', 'n', "N", N_("Number")},
235 {'d', 'd', "D", N_("Date")},
236 {'t', 't', "T", N_("Text")},
237 {'s', 's', "S", N_("Status")},
238 {'z', 'z', "Z", N_("siZe")},
239 {'k', 'k', "K", N_("Keyword")},
240 {'r', 'r', "R", N_("Rule")},
241 {SEL_OPTS_THREAD_CH
, 'h', "H", N_("tHread")},
242 {SEL_OPTS_XGMEXT_CH
, 'g', "G", N_("Gmail")},
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")},
266 {'!', '!', "!", N_("Not")},
268 {'r', 'r', "R", N_("Recent")},
269 {'u', 'u', "U", N_("Unseen")},
274 static ESCKEY_S sel_date_opt
[] = {
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, "", ""},
287 static char *sel_x_gm_ext
=
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")},
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")},
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
[] = {
330 {ctrl('W'), 14, "^W", NULL
},
334 static ESCKEY_S sel_key_opt
[] = {
336 {ctrl('T'), 14, "^T", N_("To List")},
338 {'!', '!', "!", N_("Not")},
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")},
356 alpine_smime_confirm_save(char *email
)
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';
366 alpine_get_password(char *prompt
, char *pass
, size_t len
)
368 int flags
= OE_PASSWD
| OE_DISALLOW_HELP
;
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
)
379 static HISTORY_S
*history
= NULL
;
380 static ESCKEY_S eopts
[] = {
381 {ctrl('T'), 10, "^T", N_("To Files")},
383 {-1, 0, NULL
, NULL
}};
385 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps_global
)){
386 eopts
[r
].ch
= ctrl('I');
388 eopts
[r
].name
= "TAB";
389 eopts
[r
].label
= N_("Complete");
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
);
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().
415 Returns 1 if the message number or attachment to show changed
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
;
423 long new_msgno
, del_count
, old_msgno
, i
;
425 char *newfolder
, prompt
[MAX_SCREEN_COLS
+1];
428 #if defined(DOS) && !defined(_WINDOWS)
429 extern long coreleft();
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
);
444 /*------------- 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
)
455 : h_mail_thread_index
,
456 (in_index
== MsgIndx
)
457 ? _("HELP FOR MESSAGE INDEX")
459 ? _("HELP FOR MESSAGE TEXT")
460 : _("HELP FOR THREAD INDEX"),
462 dprint((4,"MAIL_CMD: did help command\n"));
463 state
->mangled_screen
= 1;
467 /*--------- Return to main menu ------------*/
469 state
->next_screen
= main_menu_screen
;
470 dprint((2,"MAIL_CMD: going back to main menu\n"));
474 /*------- View message text --------*/
477 if(any_messages(msgmap
, NULL
, "to View")){
478 state
->next_screen
= mail_view_screen
;
484 /*------- View attachment --------*/
486 state
->next_screen
= attachment_screen
;
487 dprint((2,"MAIL_CMD: going to attachment screen\n"));
491 /*---------- Previous message ----------*/
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
))
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"));
512 || F_ON(F_NEXT_THRD_WO_CONFIRM
, state
))
515 ret
= want_to(_("View previous thread"), 'y', 'x',
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
,
535 if(count_lflags_in_thread(stream
, thrd
,
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
;
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
);
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
;
569 mn_set_cur(msgmap
, i
); /* put it back */
573 q_status_message1(SM_ORDER
, 0, 2,
574 _("Already on first %s in Zoomed Index"),
575 THRD_INDX() ? _("thread") : _("message"));
582 && ((now
= time(0)) > state
->last_nextitem_forcechk
)){
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"));
596 /*---------- Next Message ----------*/
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
))
605 ? MH_ANYTHD
: MH_NONE
);
606 if(i
== mn_get_cur(msgmap
)){
607 PINETHRD_S
*thrd
, *topthrd
;
609 if(THRD_INDX_ENABLED()){
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");
621 || F_ON(F_NEXT_THRD_WO_CONFIRM
, state
))
624 ret
= want_to(_("View next thread"), 'y', 'x',
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
,
644 if(count_lflags_in_thread(stream
, thrd
,
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
;
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
);
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
;
678 mn_set_cur(msgmap
, i
); /* put it back */
682 && (thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, i
)))
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"));
689 any_messages(NULL
, "more", "in Zoomed Index");
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."),
707 strncpy(prompt
, _(". No more folders to TAB to."),
710 prompt
[sizeof(prompt
)-1] = '\0';
713 any_messages(NULL
, (mn_get_total(msgmap
) > 0L) ? "more" : NULL
,
714 prompt
[0] ? prompt
: NULL
);
717 && ((now
= time(0)) > state
->last_nextitem_forcechk
)){
719 /* check at most once a second */
720 state
->last_nextitem_forcechk
= now
;
727 /*---------- Delete message ----------*/
729 (void) cmd_delete(state
, msgmap
, MCMD_NONE
,
730 (in_index
== View
) ? cmd_delete_view
: cmd_delete_index
);
734 /*---------- Undelete message ----------*/
736 (void) cmd_undelete(state
, msgmap
, MCMD_NONE
);
737 update_titlebar_status();
741 /*---------- Reply to message ----------*/
743 (void) cmd_reply(state
, msgmap
, MCMD_NONE
, NULL
);
747 /*---------- Forward message ----------*/
749 (void) cmd_forward(state
, msgmap
, MCMD_NONE
, NULL
);
753 /*---------- Quit pine ------------*/
755 state
->next_screen
= quit_screen
;
756 dprint((1,"MAIL_CMD: quit\n"));
760 /*---------- Compose message ----------*/
762 state
->prev_screen
= (in_index
== View
) ? mail_view_screen
764 compose_screen(state
);
765 state
->mangled_screen
= 1;
766 if (state
->next_screen
)
771 /*---------- Alt Compose message ----------*/
773 state
->prev_screen
= (in_index
== View
) ? mail_view_screen
776 if(state
->next_screen
)
782 /*--------- Folders menu ------------*/
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"));
793 /*---------- Open specific new folder ----------*/
795 tc
= (state
->context_last
&& !NEWS_TEST(state
->context_current
))
796 ? state
->context_last
: state
->context_current
;
798 newfolder
= broach_folder(question_line
, 1, ¬realinbox
, &tc
);
800 visit_folder(state
, newfolder
, tc
, NULL
, notrealinbox
? 0L : DB_INBOXWOCNTXT
);
807 /*------- Go to Index Screen ----------*/
809 state
->next_screen
= mail_index_screen
;
812 /*------- Skip to next interesting message -----------*/
818 * If we're in the thread index, start looking after this
819 * thread. We don't want to match something in the current
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
);
830 /* last msg of thread is at the ends of the branches/nexts */
832 start
= mn_raw2m(msgmap
, thrd
->rawno
);
834 thrd
= fetch_thread(stream
, thrd
->branch
);
836 thrd
= fetch_thread(stream
, thrd
->next
);
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.
852 new_msgno
= next_sorted_flagged((F_UNDEL
854 | ((F_ON(F_TAB_TO_NEW
,state
))
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
873 | ((F_ON(F_TAB_TO_NEW
,state
))
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
881 if(!(flags
& NSF_FLAG_MATCH
)){
886 new_msgno
= next_sorted_flagged((F_UNDEL
888 | ((F_ON(F_TAB_TO_NEW
,
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")},
910 snprintf(prompt
, sizeof(prompt
), _("View thread number %s? "),
911 topthrd
? comatose(topthrd
->thrdno
) : "?");
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
,
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
;
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
;
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
969 start
= mn_raw2m(msgmap
, topthrd
->rawno
);
973 * last msg of thread is at the ends of
978 start
= mn_raw2m(msgmap
, thrd
->rawno
);
980 thrd
= fetch_thread(stream
, thrd
->branch
);
982 thrd
= fetch_thread(stream
, thrd
->next
);
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
){
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
);
1015 if(mn_get_revsort(msgmap
)){
1016 if(thrd
&& thrd
->top
)
1017 start
= mn_raw2m(msgmap
, thrd
->top
);
1021 start
= mn_raw2m(msgmap
, thrd
->rawno
);
1023 thrd
= fetch_thread(stream
, thrd
->branch
);
1025 thrd
= fetch_thread(stream
, thrd
->next
);
1034 new_msgno
= next_sorted_flagged((F_UNDEL
1036 | ((F_ON(F_TAB_TO_NEW
,state
))
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
);
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
)
1059 || folder_index(state
->cur_folder
,
1060 state
->context_current
,
1061 FI_FOLDER
) >= 0)))){
1062 char nextfolder
[MAXPATH
];
1063 MAILSTREAM
*nextstream
= NULL
;
1067 strncpy(nextfolder
, state
->cur_folder
, sizeof(nextfolder
));
1068 nextfolder
[sizeof(nextfolder
)-1] = '\0';
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
))){
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")},
1082 if(F_ON(F_RET_INBOX_NO_CONFIRM
,state
))
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
);
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',
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
1103 if(ret
== 'y' || ret
== 'z'){
1104 visit_folder(state
, state
->inbox_name
,
1105 state
->context_current
,
1106 NULL
, DB_INBOXWOCNTXT
);
1110 else if (did_cancel
)
1111 cmd_cancelled(NULL
);
1113 if(state
->context_current
->use
&CNTXT_INCMNG
)
1114 q_status_message(SM_ORDER
, 0, 2, _("No more incoming folders"));
1116 q_status_message(SM_ORDER
, 0, 2, _("No more news groups"));
1124 char *front
, type
[80], cnt
[CNTLEN
], fbuf
[MAX_SCREEN_COLS
/2+1];
1125 int rbspace
, avail
, need
, take_back
;
1129 * Incoming_folder_ or news_group_ or folder_ or group_
1131 * _(13 recent) or _(some recent) or nothing
1134 front
= "View next";
1136 (state
->context_current
->use
& CNTXT_INCMNG
)
1137 ? "Incoming folder" : "news group",
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.
1155 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
1157 need
= strlen(front
)+1 + strlen(type
)+1 +
1158 + strlen(nextfolder
)+2 + strlen(cnt
) +
1161 take_back
= strlen(type
);
1163 (state
->context_current
->use
& CNTXT_INCMNG
)
1164 ? "folder" : "group", sizeof(type
));
1165 take_back
-= strlen(type
);
1168 need
-= strlen(cnt
);
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),
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")},
1197 ret
= radio_buttons(prompt
, -FOOTER_ROWS(state
),
1198 next_opt
, 'y', 'x', NO_HELP
,
1201 cmd_cancelled(NULL
);
1209 if(nextstream
&& sp_dead_stream(nextstream
))
1212 visit_folder(state
, nextfolder
,
1213 state
->context_current
, nextstream
,
1215 /* visit_folder takes care of nextstream */
1223 pine_mail_close(nextstream
);
1227 (mn_get_total(msgmap
) > 0L)
1228 ? IS_NEWS(stream
) ? "more undeleted" : "more new"
1238 /*------- Zoom -----------*/
1241 * Right now the way zoom is implemented is sort of silly.
1242 * There are two per-message flags where just one and a
1243 * global "zoom mode" flag to suppress messages from the index
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
));
1262 q_status_message(SM_ORDER
, 0, 2,
1263 _("All messages selected, so not entering Index Zoom Mode"));
1266 any_messages(NULL
, "selected", "to Zoom on");
1272 /*---------- print message on paper ----------*/
1274 if(any_messages(msgmap
, NULL
, "to print"))
1275 (void) cmd_print(state
, msgmap
, MCMD_NONE
, in_index
);
1280 /*---------- Take Address ----------*/
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
);
1289 /*---------- Save Message ----------*/
1291 if(any_messages(msgmap
, NULL
, "to Save"))
1292 (void) cmd_save(state
, stream
, msgmap
, MCMD_NONE
, in_index
);
1297 /*---------- Export message ----------*/
1299 if(any_messages(msgmap
, NULL
, "to Export")){
1300 (void) cmd_export(state
, msgmap
, question_line
, MCMD_NONE
);
1301 state
->mangled_footer
= 1;
1307 /*---------- Expunge ----------*/
1309 (void) cmd_expunge(state
, stream
, msgmap
, MCMD_NONE
);
1313 /*------- 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
)){
1325 * Since excluded means "hidden deleted" and "killed",
1326 * the count should reflect the former.
1328 pgm
= mail_newsearchpgm();
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
)))
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
),
1383 if(in_index
!= View
)
1384 adjust_cur_to_visible(stream
, msgmap
);
1387 any_messages(NULL
, NULL
, "UNexcluded");
1390 any_messages(NULL
, "excluded", "to UNexclude");
1393 any_messages(NULL
, "excluded", "to UNexclude");
1398 /*------- Make Selection -----------*/
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
);
1412 /*------- Toggle Current Message Selection State -----------*/
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
))
1424 : (in_index
== View
)
1425 ? MH_ANYTHD
: MH_NONE
);
1432 /*------- Apply command -----------*/
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
);
1447 any_messages(NULL
, NULL
, "to Apply command to. Try \"Select\"");
1453 /*-------- Sort command -------*/
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.
1483 && ((!were_threading
&& THREADING())
1484 || (were_threading
&& !THREADING()))){
1485 state
->next_screen
= mail_index_screen
;
1486 state
->mangled_screen
= 1;
1493 /*------- Toggle Full Headers -----------*/
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
){
1506 q_status_message(SM_ORDER
, 0, 3,
1507 _("Display of full headers is now off."));
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");
1517 q_status_message(SM_ORDER
, 0, 3,
1518 _("Display of full headers is now on."));
1533 /*------- Try to decrypt message -----------*/
1535 if(state
->smime
&& state
->smime
->need_passphrase
)
1536 smime_get_passphrase();
1542 smime_info_screen(state
);
1547 /*------- Bounce -----------*/
1549 (void) cmd_bounce(state
, msgmap
, MCMD_NONE
, NULL
);
1553 /*------- Flag -----------*/
1555 dprint((4, "\n - flag message -\n"));
1556 (void) cmd_flag(state
, msgmap
, MCMD_NONE
);
1560 /*------- Pipe message -----------*/
1562 (void) cmd_pipe(state
, msgmap
, MCMD_NONE
);
1566 /*--------- Default, unknown command ----------*/
1568 alpine_panic("Unexpected command case");
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
1580 c is a UCS-4 character!
1583 pretty_command(UCS c
)
1585 static char buf
[10];
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;
1637 snprintf(s
= buf
, sizeof(buf
), "F%ld", (long) (c
- PF1
+ 1));
1641 if(c
< ' ' || (c
>= 0x80 && c
< 0xA0)){
1646 d
= (c
& 0x1f) + 'A' - 1;
1647 snprintf(s
= buf
, sizeof(buf
), "%c%c", c1
? '~' : '^', d
);
1650 memset(buf
, 0, sizeof(buf
));
1651 utf8_put((unsigned char *) buf
, (unsigned long) c
);
1661 /*----------------------------------------------------------------------
1662 Complain about bogus input
1664 Args: ch -- input command to complain about
1665 help -- string indicating where to get help
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 " : "",
1680 (help
) ? " for help" : "");
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 " : "",
1687 (help
) ? " for help" : "");
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",
1697 (help
) ? " Use " : "",
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
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
;
1722 int i
, cnt
, is_set
, trouble
= 0, rv
= 0;
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."),
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."),
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
1752 if(!(any_messages(msgmap
, NULL
, "to Flag") && can_set_flag(state
, "flag", 1)))
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 */
1763 user_defined_flags
= 0;
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
++;
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
++){
1789 q
= stream_to_user_flag_name(state
->mail_stream
, i
);
1791 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1792 if((kw
->nick
&& !strucmp(kw
->nick
, q
)) || (kw
->kw
&& !strucmp(kw
->kw
, q
)))
1797 if(!kw
&& !(q
&& !strucmp(FORWARDED_FLAG
, q
))){
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("");
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"));
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
){
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
;
1852 fp
->flag
= F_COMMENT
;
1853 fp
->name
= cpystr("");
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"));
1862 /* then the extra mailbox-defined keywords */
1864 for(i
= 0; stream_to_user_flag_name(state
->mail_stream
, i
); i
++){
1867 q
= stream_to_user_flag_name(state
->mail_stream
, i
);
1869 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1870 if((kw
->nick
&& !strucmp(kw
->nick
, q
)) || (kw
->kw
&& !strucmp(kw
->kw
, q
)))
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
;
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
);
1895 exp
= flag_screen_text2
;
1896 for(fp
= ftbl
; fp
->name
; fp
++){
1897 fp
->set
= CMD_FLAG_UNKN
; /* set to unknown */
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
++){
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
;
1931 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
1932 _("Error accessing message data"));
1933 free_flag_table(&ftbl
);
1937 if(directly_to_maint_screen
)
1938 goto the_maint_screen
;
1941 if (mswin_usedialog ()) {
1942 if (!os_flagmsgdialog (&ftbl
[0])){
1943 free_flag_table(&ftbl
);
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
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
){
1969 first_char
= (fp
->name
&& fp
->name
[0])
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
)
1984 use_maint_screen
= !cmd_flag_prompt(state
, &flag_screen
,
1989 if(use_maint_screen
){
1990 for(p
= &screen_text
[0]; *exp
; p
++, exp
++)
1995 directly_to_maint_screen
= flag_maintenance_screen(state
, &flag_screen
);
1999 /* reacquire the elt pointer */
2000 mc
= (state
->mail_stream
2001 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
2002 && rawno
<= state
->mail_stream
->nmsgs
)
2003 ? mail_elt(state
->mail_stream
, rawno
) : NULL
;
2005 for(fp
= ftbl
; mc
&& fp
->name
; fp
++){
2009 if((!MCMD_ISAGG(aopt
) && fp
->set
!= !mc
->seen
)
2010 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2018 unflagged
= F_UNSEEN
;
2025 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->answered
)
2026 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2027 flagit
= "\\ANSWERED";
2030 unflagged
= F_UNANS
;
2041 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->deleted
)
2042 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2043 flagit
= "\\DELETED";
2046 unflagged
= F_UNDEL
;
2057 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->flagged
)
2058 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2059 flagit
= "\\FLAGGED";
2062 unflagged
= F_UNFLAG
;
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
)),
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
;
2087 unflagged
= F_UNFWD
;
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
)),
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
;
2114 unflagged
= F_UNKEYWORD
;
2118 unflagged
= F_KEYWORD
;
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
2148 mail_flag(state
->mail_stream
, seq
, flagit
, flags
);
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
);
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"));
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
))
2176 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2177 ? comatose(mn_total_cur(msgmap
)) : "",
2178 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2180 MCMD_ISAGG(aopt
) ? plural(flagged
) : " ",
2181 MCMD_ISAGG(aopt
) ? "" : long2string(mn_get_cur(msgmap
)),
2183 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2184 q_status_message(SM_ORDER
, 0, 2, answer
= tmp_20k_buf
);
2190 free_flag_table(&ftbl
);
2192 if(directly_to_maint_screen
)
2195 if(MCMD_ISAGG(aopt
))
2196 restore_selected(msgmap
);
2199 q_status_message(SM_ORDER
, 0, 2, _("No flags changed."));
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
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
;
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
){
2232 ESCKEY_S
*dp
, *sp
, *tp
;
2234 for(sp
=flag_text_opt
; sp
->ch
!= -1; sp
++)
2237 for(fp
=(flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++)
2238 if(fp
->flag
== F_KEYWORD
)
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
++)
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
)
2263 continue; /* skip it, already used that char */
2265 dp
->ch
= first_char
;
2266 dp
->rval
= first_char
;
2274 ftext
= _(flag_text_ak
);
2275 ftext_not
= _(flag_text_ak_not
);
2279 ftext
= _(flag_text
);
2280 ftext_not
= _(flag_text_not
);
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 */
2295 else if(r
== 10) /* return and goto flag screen */
2297 else if(r
== '!') /* flip intention */
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
;
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
;
2326 if(ek
!= flag_text_opt
)
2327 fs_give((void **) &ek
);
2334 * (*ft) is an array of flag_table entries.
2337 free_flag_table(struct flag_table
**ft
)
2339 struct flag_table
*fp
;
2342 for(fp
= (*ft
); fp
->name
; fp
++){
2344 fs_give((void **) &fp
->name
);
2347 fs_give((void **) &fp
->keyword
);
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
2368 cmd_reply(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, ACTION_S
*role
)
2372 if(any_messages(msgmap
, NULL
, "to Reply to")){
2373 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
2376 rv
= reply(state
, role
);
2378 if(MCMD_ISAGG(aopt
))
2379 restore_selected(msgmap
);
2381 state
->mangled_screen
= 1;
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
2398 cmd_forward(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, ACTION_S
*role
)
2402 if(any_messages(msgmap
, NULL
, "to Forward")){
2403 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
2406 rv
= forward(state
, role
);
2408 if(MCMD_ISAGG(aopt
))
2409 restore_selected(msgmap
);
2411 state
->mangled_screen
= 1;
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
2429 cmd_bounce(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, ACTION_S
*role
)
2433 if(any_messages(msgmap
, NULL
, "to Bounce")){
2435 if(MCMD_ISAGG(aopt
)){
2436 if(!pseudo_selected(state
->mail_stream
, msgmap
))
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!"));
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;
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.
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
;
2479 CONTEXT_S
*cntxt
= 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
))
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");
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
);
2506 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
2507 _("Can't save message. Error accessing folder"));
2508 restore_selected(msgmap
);
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
)),
2522 del
= (!READONLY_FOLDER(stream
) && F_OFF(F_SAVE_WONT_DELETE
, ps_global
))
2524 if(mn_total_cur(msgmap
) > 1L)
2525 pre
= F_OFF(F_AGG_SEQ_COPY
, ps_global
) ? Preserve
: NoPreserve
;
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
;
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
);
2550 if(i
== mn_total_cur(msgmap
)){
2552 if(mn_total_cur(msgmap
) <= 1L){
2553 int need
, avail
= ps_global
->ttyo
->screen_cols
- 2;
2554 int lennick
, lenfldr
;
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
))) +
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,
2578 short_str(cntxt
->nickname
,
2579 (char *)(tmp_20k_buf
+2000), 1000,
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
)),
2588 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2591 char *f
= " folder";
2593 lenfldr
= MIN(strlen(newfolder
), 500);
2594 need
= 28 + strlen(long2string(mn_get_cur(msgmap
))) +
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,
2608 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2612 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%s messages saved",
2613 comatose(mn_total_cur(msgmap
)));
2614 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
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
))
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
);
2645 update_titlebar_status(); /* make sure they see change */
2652 role_compose(struct pine
*state
)
2656 if(F_ON(F_ALT_ROLE_MENU
, state
) && mn_get_total(state
->msgmap
) > 0L){
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
);
2665 q_status_message(SM_ORDER
, 0, 3,
2666 _("No roles available. Use Setup/Rules to add roles."));
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
;
2681 state
->next_screen
= SCREEN_FUN_NULL
;
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;
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.
2704 role
= combine_inherited_role(role
);
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
;
2714 compose_mail(NULL
, NULL
, role
, NULL
, NULL
);
2718 (void) reply(state
, role
);
2722 (void) forward(state
, role
);
2726 (void) bounce(state
, 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
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
2750 RetDel and RetNoDel are return values
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
;
2768 SaveDel del
= DontAsk
;
2769 SavePreserveOrder pre
= DontAskPreserve
;
2770 char *deltext
= NULL
;
2771 static HISTORY_S
*history
= NULL
;
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
)
2788 /* set up extra command option keys */
2790 ekey
[rc
].ch
= ctrl('T');
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');
2799 ekey
[rc
].name
= "^P";
2800 ekey
[rc
++].label
= N_("Prev Collection");
2802 ekey
[rc
].ch
= ctrl('N');
2804 ekey
[rc
].name
= "^N";
2805 ekey
[rc
++].label
= N_("Next Collection");
2808 if(F_ON(F_ENABLE_TAB_COMPLETE
, ps_global
)){
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');
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');
2827 ekey
[rc
].name
= "^R";
2832 if(prea
&& (*prea
== NoPreserve
|| *prea
== Preserve
)){
2833 ekey
[rc
].ch
= ctrl('W');
2835 ekey
[rc
].name
= "^W";
2840 if(saveable_count
> 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
2841 ekey
[rc
].ch
= KEY_UP
;
2844 ekey
[rc
++].label
= "";
2846 ekey
[rc
].ch
= KEY_DOWN
;
2849 ekey
[rc
++].label
= "";
2851 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
2852 ekey
[rc
].ch
= KEY_UP
;
2856 ekey
[rc
++].label
= "";
2858 ekey
[rc
].ch
= KEY_DOWN
;
2861 ekey
[rc
++].label
= "";
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
: "",
2874 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2875 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
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] : ",
2890 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2891 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
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
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));
2909 snprintf(prompt
, sizeof(prompt
), "SAVE to folder [%s] : ",
2910 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2912 prompt
[sizeof(prompt
)-1] = '\0';
2916 ekey
[delindex
].label
= (del
== NoDel
) ? "Delete" : "No Delete";
2918 if(pre
!= DontAskPreserve
)
2919 ekey
[preindex
].label
= (pre
== NoPreserve
) ? "Preserve Order" : "Any Order";
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
;
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
);
2942 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
2943 _("Error reading folder name"));
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
;
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)))
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\""),
2980 strncpy(name
= nfldr
, fullname
, len_nfldr
-1);
2981 nfldr
[len_nfldr
-1] = '\0';
2982 fs_give((void **) &fullname
);
2986 if(exists
& FEX_ISFILE
){
2989 else if((exists
& FEX_ISDIR
)){
2990 char tmp
[MAILTMPLEN
];
2994 CONTEXT_S
*fake_context
;
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));
3006 strncat(tmp
, "[]", sizeof(tmp
)-strlen(tmp
)-1);
3008 tmp
[sizeof(tmp
)-1] = '\0';
3010 fake_context
= new_context(tmp
, 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
))
3019 done
= display_folder_list(cntxt
, nfldr
,
3020 1, folders_for_save
);
3022 q_status_message1(SM_ORDER
, 3, 3,
3023 _("\"%s\" is a directory"), name
);
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 */
3043 case 0 : /* error */
3044 case -1 : /* declined */
3053 /* else fall thru like they cancelled */
3056 cmd_cancelled("Save message");
3061 r
= display_folder_list(cntxt
, nfldr
, 0, folders_for_save
);
3069 helper(h_save
, _("HELP FOR SAVE"), HLPD_SIMPLE
);
3070 ps_global
->mangled_screen
= 1;
3073 case 4 : /* redraw */
3076 case 10 : /* previous collection */
3077 for(tc
= (*cntxt
)->prev
; tc
; tc
= tc
->prev
)
3084 for(tc2
= (tc
= (*cntxt
))->next
; tc2
; tc2
= tc2
->next
)
3092 case 11 : /* next collection */
3096 if(((*cntxt
) = (*cntxt
)->next
) == NULL
)
3097 (*cntxt
) = ps_global
->context_list
;
3098 while(NEWS_TEST(*cntxt
) && (*cntxt
) != tc
);
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
);
3106 done
++; /* bingo! */
3108 rc
= 0; /* burn last_rc */
3116 case 14 : /* file name completion */
3117 r
= display_folder_list(cntxt
, nfldr
, 2, folders_for_save
);
3119 done
++; /* bingo! */
3121 rc
= 0; /* burn last_rc */
3125 case 15 : /* Delete / No Delete */
3126 del
= (del
== NoDel
) ? Del
: NoDel
;
3127 deltext
= (del
== NoDel
) ? " (no delete)" : " (and delete)";
3130 case 16 : /* Preserve Order or not */
3131 pre
= (pre
== NoPreserve
) ? Preserve
: NoPreserve
;
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
;
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
;
3159 alpine_panic("Unhandled case");
3166 ps_global
->mangled_footer
= 1;
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';
3175 ps_global
->last_save_context
= *cntxt
;
3178 strncpy(nfldr
, folder
, len_nfldr
-1);
3179 nfldr
[len_nfldr
-1] = '\0';
3182 /* nickname? Copy real name to nfldr */
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
;
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
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) ? "..." : "",
3225 (strlen(context
->nickname
) > 15) ? "..." : "");
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");
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
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];
3260 COLOR_PAIR
*lastc
= NULL
;
3262 dprint((2, "\n - expunge -\n"));
3266 sequence
= MCMD_ISAGG(agg
) ? selected_sequence(stream
, msgmap
, NULL
, 0) : NULL
;
3268 if(MCMD_ISAGG(agg
)){
3271 for(i
= 1L; i
<= stream
->nmsgs
; i
++){
3272 if((mc
= mail_elt(stream
, i
)) != NULL
3273 && mc
->sequence
&& mc
->deleted
)
3277 q_status_message(SM_ORDER
, 0, 4,
3278 _("No selected messages are deleted"));
3282 if(!any_messages(msgmap
, NULL
, "to Expunge"))
3286 if(IS_NEWS(stream
) && stream
->rdonly
){
3287 if(!MCMD_ISAGG(agg
))
3288 del_count
= count_flagged(stream
, F_DEL
);
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
),
3324 any_messages(NULL
, NULL
, "Excluded");
3327 any_messages(NULL
, "deleted", "to Exclude");
3331 else if(READONLY_FOLDER(stream
)){
3332 q_status_message(SM_ORDER
, 0, 4,
3333 _("Can't expunge. Folder is read-only"));
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
);
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')
3363 cmd_cancelled("Expunge");
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
,
3376 PutLine0(0, 0, "**"); /* indicate delay */
3379 (void)pico_set_colorp(lastc
, PSC_NONE
);
3380 free_color_pair(&lastc
);
3383 MoveCursor(state
->ttyo
->screen_rows
-FOOTER_ROWS(state
), 0);
3386 we_cancel
= busy_cue(_("Expunging"), NULL
, 1);
3388 if(cmd_expunge_work(stream
, msgmap
, sequence
))
3389 state
->mangled_body
= 1;
3392 fs_give((void **)&sequence
);
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
,
3400 PutLine0(0, 0, " "); /* indicate delay's over */
3403 (void)pico_set_colorp(lastc
, PSC_NONE
);
3404 free_color_pair(&lastc
);
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
);
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."));
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
3444 expunge_prompt(MAILSTREAM
*stream
, char *folder
, long int deleted
)
3448 char prompt_b
[MAX_SCREEN_COLS
+1], temp
[MAILTMPLEN
+1], buff
[MAX_SCREEN_COLS
+1];
3449 char *short_folder_name
;
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
);
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
);
3469 snprintf(prompt_b
, sizeof(prompt_b
),
3470 "Expunge the %ld deleted message%s from \"%s\"",
3471 deleted
, deleted
== 1 ? "" : "s",
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
)
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"},
3502 if(F_ON(F_IGNORE_SIZE
, ps_global
))
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;
3516 if(remember_the_yes
){
3517 snprintf(prompt
, sizeof(prompt
),
3518 "Message to save shrank! (msg # %ld): Continuing", msgno
);
3519 q_status_message(SM_ORDER
, 0, 3, prompt
);
3520 display_message('x');
3521 return(remember_the_yes
);
3524 snprintf(prompt
, sizeof(prompt
),
3525 "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno
);
3526 ret
= radio_buttons(prompt
, -FOOTER_ROWS(ps_global
), save_size_opts
,
3527 'n', 0, h_save_size_changed
, RB_NORM
|RB_NO_NEWMAIL
);
3531 remember_the_yes
= 'y';
3532 possible_corruption
++;
3533 return(remember_the_yes
);
3536 possible_corruption
++;
3540 possible_corruption
= 0;
3541 ps_global
->noshow_error
= 1;
3542 ps_global
->noshow_warn
= 1;
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
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
3584 cmd_export(struct pine
*state
, MSGNO_S
*msgmap
, int qline
, int aopt
)
3586 char filename
[MAXPATH
+1], full_filename
[MAXPATH
+1], *err
;
3588 int r
, leading_nl
, failure
= 0, orig_errno
, rflags
= GER_NONE
;
3589 int flags
= GE_IS_EXPORT
| GE_SEQ_SENSITIVE
, rv
= 0;
3593 long i
, count
= 0L, start_of_append
, rawno
;
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");
3606 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
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");
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");
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
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
))
3656 && b
->type
== TYPEMULTIPART
3658 && strucmp(b
->subtype
, "ALTERNATIVE") != 0){
3661 part
= b
->nested
.part
; /* 1st part */
3662 if(part
&& part
->next
)
3663 flags
|= GE_ALLPARTS
;
3666 export_opts
[i
].ch
= -1;
3669 if(mn_total_cur(msgmap
) <= 1L){
3670 snprintf(nmsgs
, sizeof(nmsgs
), "Msg #%ld", mn_get_cur(msgmap
));
3671 nmsgs
[sizeof(nmsgs
)-1] = '\0';
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
);
3685 cmd_cancelled("Export message");
3689 q_status_message1(SM_ORDER
, 0, 2,
3690 _("Can't export to file outside of %s"),
3697 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3698 else if(r
== 12){ /* Download */
3699 char cmd
[MAXPATH
], *tfp
= NULL
;
3705 if(ps_global
->restricted
){
3706 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3707 "Download disallowed in restricted mode");
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
))
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");
3738 gf_clear_so_writec(so
);
3739 if(so_give(&so
)){ /* close file */
3741 err
= "Error writing tempfile for download";
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
);
3750 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3751 err
= _("Error running download command"));
3755 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3756 err
= "Error building temp file for download");
3760 fs_give((void **)&tfp
);
3764 q_status_message(SM_ORDER
, 0, 3, _("Download Command Completed"));
3768 #endif /* !(DOS || MAC) */
3771 if(rflags
& GER_APPEND
)
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
));
3787 gf_set_so_writec(&pc
, store
);
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
),
3794 err
= _("Can't export message. Error accessing mail folder");
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
))
3806 start_of_append
= so_tell(store
);
3807 if(!bezerk_delimiter(env
, mc
, pc
, leading_nl
)
3808 || !format_message(mn_m2raw(msgmap
, i
), env
, b
, NULL
,
3809 FM_NEW_MESS
| FM_NOWRAP
, pc
)){
3810 orig_errno
= errno
; /* save in case things are really bad */
3811 failure
= 1; /* pop out of here */
3818 gf_clear_so_writec(store
);
3819 if(so_give(&store
)) /* release storage */
3823 our_truncate(full_filename
, (off_t
)start_of_append
);
3825 dprint((1, "FAILED Export: fetch(%ld): %s\n",
3826 i
, err
? err
: "?"));
3827 q_status_message(SM_ORDER
| SM_DING
, 3, 4, err
);
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
));
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;
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"),
3867 snprintf(dir
, sizeof(dir
), "%s.d", full_filename
);
3868 dir
[sizeof(dir
)-1] = '\0';
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.
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");
3888 snprintf(dir
, sizeof(dir
), "%s.d_%s", full_filename
,
3889 long2string((long) tries
));
3890 dir
[sizeof(dir
)-1] = '\0';
3893 case 3: /* doesn't exist, that's good! */
3894 /* make new directory */
3898 } while(!ok
&& tries
< 1000);
3901 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3902 _("Problem saving attachments"));
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
));
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
))
3919 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3920 _("Problem reading message"));
3924 zero_atmts(state
->atmts
);
3925 describe_mime(b
, "", 1, 1, 0, 0);
3928 if(a
&& a
->description
) /* skip main body part */
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
))
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
)){
3948 "FAILED Att Export: name too long: %s\n",
3949 dir
, S_FILESEP
, lfile
));
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){
3965 snprintf(filename
, sizeof(filename
), "%d", counter
);
3966 if(strlen(dir
) + strlen(S_FILESEP
) + strlen(lfile
) + strlen(filename
) + 2
3967 > sizeof(filename
)){
3969 "FAILED Att Export: name too long: %s\n",
3970 dir
, S_FILESEP
, lfile
));
3974 if((ext
= strrchr(lfile
, '.')) != NULL
)
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)
3991 q_status_message1(SM_ORDER
, 3, 3,
3992 "Errors saving some attachments, %s attachments saved",
3993 long2string((long) saved
));
3995 q_status_message(SM_ORDER
, 3, 3,
3996 _("Problems saving attachments"));
4000 q_status_message2(SM_ORDER
, 0, 3,
4001 /* TRANSLATORS: Saved <how many> attachments to <directory name> */
4002 _("Saved %s attachments to %s"),
4003 long2string((long) saved
), dir
);
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
),
4014 : rflags
& GER_APPEND
? "appended" : "exported",
4017 q_status_message3(SM_ORDER
,0,3,
4018 "Message %s %s to file \"%s\"",
4019 long2string(mn_get_cur(msgmap
)),
4022 : rflags
& GER_APPEND
? "appended" : "exported",
4028 if(MCMD_ISAGG(aopt
))
4029 restore_selected(msgmap
);
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
4044 * Returns: != 0 : error
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");
4068 q_status_message(SM_ORDER
, 0, 2, _("Error allocating space"));
4073 simple_export_opts
[++r
].ch
= -1;
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
);
4083 else if(!full_filename
[0]){
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
){
4095 gf_set_so_writec(&pc
, store
);
4096 gf_set_readc(&gc
, srctext
, (srctype
== CharStar
)
4097 ? strlen((char *)srctext
)
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
);
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
));
4120 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
4121 _("Error opening file \"%s\" for export: %s"),
4122 full_filename
, error_description(errno
));
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])
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\"",
4141 : rflags
& GER_APPEND
? "appended" : "exported",
4146 cmd_cancelled("Export");
4150 q_status_message1(SM_ORDER
, 0, 2,
4151 _("Can't export to file outside of %s"), VAR_OPER_DIR
);
4155 ps
->mangled_footer
= 1;
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
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
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];
4222 ESCKEY_S
*opts
= NULL
;
4223 struct variable
*vars
= ps
->vars
;
4224 static HISTORY_S
*dir_hist
= NULL
;
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)
4252 i
+= dir_hist
|| hist_len
> 0 ? 2 : 4;
4254 if(flags
& GE_ALLPARTS
)
4257 if(flags
& GE_BINARY
)
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
){
4272 opts
[i
].ch
= ctrl('P');
4274 opts
[i
].name
= "^P";
4275 /* TRANSLATORS: Export all attachment parts */
4276 opts
[i
++].label
= N_("AllParts");
4279 if(flags
& GE_BINARY
){
4281 opts
[i
].ch
= ctrl('R');
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
);
4290 /* In the Windows operating system we always return the UTF8 encoded name */
4291 if(strcmp(tmp_20k_buf
, filename
)){
4292 opts
[i
].ch
= ctrl('N');
4294 opts
[i
].name
= "^N";
4295 opts
[i
++].label
= "Name UTF8";
4298 strncpy(filename
, tmp_20k_buf
, len
);
4299 filename
[len
-1] = '\0';
4300 #endif /* _WINDOWS */
4302 if(dir_hist
|| hist_len
> 0){
4303 opts
[i
].ch
= ctrl('Y');
4307 opts
[i
++].label
= "";
4309 opts
[i
].ch
= ctrl('V');
4312 opts
[i
++].label
= "";
4316 opts
[i
].ch
= KEY_UP
;
4320 opts
[i
++].label
= "";
4322 opts
[i
].ch
= KEY_DOWN
;
4325 opts
[i
++].label
= "";
4331 init_hist(history
, HISTSIZE
);
4332 init_hist(&dir_hist
, HISTSIZE
); /* reset history to the end */
4340 if(F_ON(F_USE_CURRENT_DIR
, ps
))
4342 else if(VAR_OPER_DIR
){
4343 strncpy(dir
, VAR_OPER_DIR
, sizeof(dir
));
4344 dir
[sizeof(dir
)-1] = '\0';
4346 #if defined(DOS) || defined(OS2)
4347 else if(VAR_FILE_DIR
){
4348 strncpy(dir
, VAR_FILE_DIR
, sizeof(dir
));
4349 dir
[sizeof(dir
)-1] = '\0';
4357 strncpy(orig_dir
, dir
, sizeof(orig_dir
));
4358 orig_dir
[sizeof(orig_dir
)-1] = '\0';
4360 postcolon
[0] = '\0';
4361 strncpy(precolon
, dir
, sizeof(precolon
));
4362 precolon
[sizeof(precolon
)-1] = '\0';
4364 strncpy(def
, deefault
, sizeof(def
)-1);
4365 def
[sizeof(def
)-1] = '\0';
4366 removing_leading_and_trailing_white_space(def
);
4371 avail
= MAX(20, ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80) - MIN_OPT_ENT_WIDTH
;
4373 /*---------- Prompt the user for the file name -------------*/
4376 char dirb
[50], fileb
[50];
4377 int l1
, l2
, l3
, l4
, l5
, needed
;
4378 char *p
, p1
[100], p2
[100], *p3
, p4
[100], p5
[100];
4380 snprintf(p1
, sizeof(p1
), "%sCopy ",
4381 (flags
& GE_IS_EXPORT
) ? "EXPORT: " :
4382 (flags
& GE_IS_IMPORT
) ? "IMPORT: " : "SAVE: ");
4383 p1
[sizeof(p1
)-1] = '\0';
4386 strncpy(p2
, prompt_msg
? prompt_msg
: "", sizeof(p2
)-1);
4387 p2
[sizeof(p2
)-1] = '\0';
4390 if(rflags
&& *rflags
& GER_ALLPARTS
)
4397 snprintf(p4
, sizeof(p4
), " %s file%s%s",
4398 (flags
& GE_IS_IMPORT
) ? "from" : "to",
4399 is_absolute_path(filename
) ? "" : " in ",
4400 is_absolute_path(filename
) ? "" :
4401 (!dir
[0] ? "current directory"
4402 : (dir
[0] == '~' && !dir
[1]) ? "home directory"
4403 : short_str(dir
,dirb
,sizeof(dirb
),30,FrontDots
)));
4404 p4
[sizeof(p4
)-1] = '\0';
4407 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4409 *def
? short_str(def
,fileb
,sizeof(fileb
),40,EndDots
) : "",
4411 p5
[sizeof(p5
)-1] = '\0';
4414 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0){
4415 snprintf(p4
, sizeof(p4
), " %s file%s%s",
4416 (flags
& GE_IS_IMPORT
) ? "from" : "to",
4417 is_absolute_path(filename
) ? "" : " in ",
4418 is_absolute_path(filename
) ? "" :
4419 (!dir
[0] ? "current dir"
4420 : (dir
[0] == '~' && !dir
[1]) ? "home dir"
4421 : short_str(dir
,dirb
,sizeof(dirb
),10,FrontDots
)));
4422 p4
[sizeof(p4
)-1] = '\0';
4426 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l5
> 0){
4427 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4429 *def
? short_str(def
,fileb
,sizeof(fileb
),
4430 MAX(15,l5
-5-needed
),EndDots
) : "",
4432 p5
[sizeof(p5
)-1] = '\0';
4436 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l2
> 0){
4439 * 14 is about the shortest we can make this, because there are
4440 * fixed length strings of length 14 coming in here.
4442 p
= short_str(prompt_msg
, p2
, sizeof(p2
), MAX(14,l2
-needed
), FrontDots
);
4444 strncpy(p2
, p
, sizeof(p2
)-1);
4445 p2
[sizeof(p2
)-1] = '\0';
4451 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0){
4452 strncpy(p1
, "Copy ", sizeof(p1
)-1);
4453 p1
[sizeof(p1
)-1] = '\0';
4457 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l5
> 0){
4458 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4460 *def
? short_str(def
,fileb
, sizeof(fileb
),
4461 MAX(10,l5
-5-needed
),EndDots
) : "",
4463 p5
[sizeof(p5
)-1] = '\0';
4467 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l3
> 0){
4468 if(needed
<= l3
- strlen(" (+ atts)"))
4470 else if(needed
<= l3
- strlen(" (atts)"))
4472 else if(needed
<= l3
- strlen(" (+)"))
4474 else if(needed
<= l3
- strlen("+"))
4482 snprintf(prompt_buf
, sizeof(prompt_buf
), "%s%s%s%s%s", p1
, p2
, p3
, p4
, p5
);
4483 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4486 if(items_in_hist(dir_hist
) > 0 || hist_len
> 0){ /* any directories */
4487 opts
[kp
].name
= "^Y";
4488 opts
[kp
].label
= "Prev Dir";
4489 opts
[kp
+1].name
= "^V";
4490 opts
[kp
+1].label
= "Next Dir";
4494 opts
[kp
].label
= "";
4495 opts
[kp
+1].name
= "";
4496 opts
[kp
+1].label
= "";
4501 if(items_in_hist(*history
) > 0){
4502 opts
[ku
].name
= HISTORY_UP_KEYNAME
;
4503 opts
[ku
].label
= HISTORY_KEYLABEL
;
4504 opts
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
4505 opts
[ku
+1].label
= HISTORY_KEYLABEL
;
4509 opts
[ku
].label
= "";
4510 opts
[ku
+1].name
= "";
4511 opts
[ku
+1].label
= "";
4515 oeflags
= OE_APPEND_CURRENT
|
4516 ((flags
& GE_SEQ_SENSITIVE
) ? OE_SEQ_SENSITIVE
: 0);
4517 r
= optionally_enter(filename
, qline
, 0, len
, prompt_buf
,
4518 opts
, NO_HELP
, &oeflags
);
4520 dprint((2, "\n - export_filename = \"%s\", r = %d -\n", filename
, r
));
4524 * Helps may not be right if you add another caller or change
4525 * things. Check it out.
4527 if(flags
& GE_IS_IMPORT
)
4528 helper(h_ge_import
, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE
);
4529 else if(flags
& GE_ALLPARTS
)
4530 helper(h_ge_allparts
, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE
);
4532 helper(h_ge_export
, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE
);
4534 ps
->mangled_screen
= 1;
4538 else if(r
== 10 || r
== 11){ /* Browser or File Completion */
4539 if(filename
[0]=='~'){
4540 if(filename
[1] == C_FILESEP
&& filename
[2]!='\0'){
4543 for(i
=0; filename
[i
+2] != '\0' && i
+2 < len
-1; i
++)
4544 filename
[i
] = filename
[i
+2];
4546 strncpy(dir
, precolon
, sizeof(dir
)-1);
4547 dir
[sizeof(dir
)-1] = '\0';
4549 else if(filename
[1]=='\0' ||
4550 (filename
[1] == C_FILESEP
&& filename
[2] == '\0')){
4554 strncpy(dir
, precolon
, sizeof(dir
)-1);
4555 dir
[sizeof(dir
)-1] = '\0';
4558 else if(!dir
[0] && !is_absolute_path(filename
) && was_abs_path
){
4562 strncpy(dir
, precolon
, sizeof(dir
)-1);
4563 dir
[sizeof(dir
)-1] = '\0';
4572 strncpy(tmp
, filename
, sizeof(tmp
)-1);
4573 tmp
[sizeof(tmp
)-1] = '\0';
4574 if(*tmp
&& is_absolute_path(tmp
))
4575 fnexpand(tmp
, sizeof(tmp
));
4576 if(strncmp(tmp
,postcolon
, strlen(postcolon
)))
4577 postcolon
[0] = '\0';
4579 if(*tmp
&& (fn
= last_cmpnt(tmp
))){
4581 strncpy(filename2
, fn
, sizeof(filename2
)-1);
4582 filename2
[sizeof(filename2
)-1] = '\0';
4583 if(is_absolute_path(tmp
)){
4584 strncpy(dir2
, tmp
, MIN(fn
- tmp
, sizeof(dir2
)-1));
4585 dir2
[MIN(fn
- tmp
, sizeof(dir2
)-1)] = '\0';
4587 if(tmp
[1]==':' && tmp
[2]=='\\' && dir2
[2]=='\0'){
4592 strncpy(postcolon
, dir2
, sizeof(postcolon
)-1);
4593 postcolon
[sizeof(postcolon
)-1] = '\0';
4599 * Just building the directory name in dir2,
4600 * full_filename is overloaded.
4602 snprintf(full_filename
, len
, "%.*s", (int) MIN(fn
-tmp
,len
-1), tmp
);
4603 full_filename
[len
-1] = '\0';
4604 strncpy(postcolon
, full_filename
, sizeof(postcolon
)-1);
4605 postcolon
[sizeof(postcolon
)-1] = '\0';
4606 build_path(dir2
, !dir
[0] ? p
= (char *)getcwd(NULL
,MAXPATH
)
4607 : (dir
[0] == '~' && !dir
[1])
4610 full_filename
, sizeof(dir2
));
4616 if(is_absolute_path(tmp
)){
4617 strncpy(dir2
, tmp
, sizeof(dir2
)-1);
4618 dir2
[sizeof(dir2
)-1] = '\0';
4620 if(dir2
[2]=='\0' && dir2
[1]==':'){
4623 strncpy(postcolon
,dir2
,sizeof(postcolon
)-1);
4624 postcolon
[sizeof(postcolon
)-1] = '\0';
4627 filename2
[0] = '\0';
4631 strncpy(filename2
, tmp
, sizeof(filename2
)-1);
4632 filename2
[sizeof(filename2
)-1] = '\0';
4634 if(getcwd(dir2
, sizeof(dir2
)) == NULL
)
4635 alpine_panic(_("getcwd() call failed at get_export_filename"));
4637 else if(dir
[0] == '~' && !dir
[1]){
4638 strncpy(dir2
, ps
->home_dir
, sizeof(dir2
)-1);
4639 dir2
[sizeof(dir2
)-1] = '\0';
4642 strncpy(dir2
, dir
, sizeof(dir2
)-1);
4643 dir2
[sizeof(dir2
)-1] = '\0';
4646 postcolon
[0] = '\0';
4650 build_path(full_filename
, dir2
, filename2
, len
);
4651 if(!strcmp(full_filename
, dir2
))
4652 filename2
[0] = '\0';
4653 if(full_filename
[strlen(full_filename
)-1] == C_FILESEP
4654 && isdir(full_filename
,NULL
,NULL
)){
4655 if(strlen(full_filename
) == 1)
4656 strncpy(postcolon
, full_filename
, sizeof(postcolon
)-1);
4657 else if(filename2
[0])
4658 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4659 postcolon
[sizeof(postcolon
)-1] = '\0';
4660 strncpy(dir2
, full_filename
, sizeof(dir2
)-1);
4661 dir2
[sizeof(dir2
)-1] = '\0';
4662 filename2
[0] = '\0';
4664 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4665 else if(full_filename
[strlen(full_filename
)-1] == C_FILESEP
){
4666 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4667 postcolon
[sizeof(postcolon
)-1] = '\0';
4668 strncpy(dir2
, full_filename
, sizeof(dir2
)-1);
4669 dir2
[sizeof(dir2
)-1] = '\0';
4670 filename2
[0] = '\0';
4673 if(dir2
[strlen(dir2
)-1] == C_FILESEP
&& strlen(dir2
)!=1
4674 && strcmp(dir2
+1, ":\\"))
4675 /* last condition to prevent stripping of '\\'
4676 in windows partition */
4677 dir2
[strlen(dir2
)-1] = '\0';
4679 if(r
== 10){ /* File Browser */
4680 r
= file_lister(lister_msg
? lister_msg
: "EXPORT",
4681 dir2
, sizeof(dir2
), filename2
, sizeof(filename2
),
4683 (flags
& GE_IS_IMPORT
) ? FB_READ
: FB_SAVE
);
4685 /* Windows has a special "feature" in which entering the file browser will
4686 change the working directory if the directory is changed at all (even
4687 clicking "Cancel" will change the working directory).
4689 if(F_ON(F_USE_CURRENT_DIR
, ps
))
4690 (void)getcwd(dir2
,sizeof(dir2
));
4692 if(isdir(dir2
,NULL
,NULL
)){
4693 strncpy(precolon
, dir2
, sizeof(precolon
)-1);
4694 precolon
[sizeof(precolon
)-1] = '\0';
4696 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4697 postcolon
[sizeof(postcolon
)-1] = '\0';
4699 build_path(full_filename
, dir2
, filename2
, len
);
4700 if(isdir(full_filename
, NULL
, NULL
)){
4701 strncpy(dir
, full_filename
, sizeof(dir
)-1);
4702 dir
[sizeof(dir
)-1] = '\0';
4706 fn
= last_cmpnt(full_filename
);
4707 strncpy(dir
, full_filename
,
4708 MIN(fn
- full_filename
, sizeof(dir
)-1));
4709 dir
[MIN(fn
- full_filename
, sizeof(dir
)-1)] = '\0';
4710 if(fn
- full_filename
> 1)
4711 dir
[fn
- full_filename
- 1] = '\0';
4714 if(!strcmp(dir
, ps
->home_dir
)){
4719 strncpy(filename
, fn
, len
-1);
4720 filename
[len
-1] = '\0';
4723 else{ /* File Completion */
4724 if(!pico_fncomplete(dir2
, filename2
, sizeof(filename2
)))
4726 strncat(postcolon
, filename2
,
4727 sizeof(postcolon
)-1-strlen(postcolon
));
4728 postcolon
[sizeof(postcolon
)-1] = '\0';
4730 was_abs_path
= is_absolute_path(filename
);
4732 if(!strcmp(dir
, ps
->home_dir
)){
4737 strncpy(filename
, postcolon
, len
-1);
4738 filename
[len
-1] = '\0';
4739 strncpy(dir
, precolon
, sizeof(dir
)-1);
4740 dir
[sizeof(dir
)-1] = '\0';
4742 if(filename
[0] == '~' && !filename
[1]){
4750 else if(r
== 12){ /* Download, caller handles it */
4754 else if(r
== 13){ /* toggle AllParts bit */
4756 if(*rflags
& GER_ALLPARTS
){
4757 *rflags
&= ~GER_ALLPARTS
;
4758 opts
[allparts
].label
= N_("AllParts");
4761 *rflags
|= GER_ALLPARTS
;
4762 /* opposite of All Parts, No All Parts */
4763 opts
[allparts
].label
= N_("NoAllParts");
4770 else if(r
== 14){ /* List file names matching partial? */
4774 else if(r
== 15){ /* toggle Binary bit */
4776 if(*rflags
& GER_BINARY
){
4777 *rflags
&= ~GER_BINARY
;
4778 opts
[binary
].label
= N_("Binary");
4781 *rflags
|= GER_BINARY
;
4782 opts
[binary
].label
= N_("No Binary");
4788 else if(r
== 1){ /* Cancel */
4795 else if(r
>= 30 && r
<= 33){
4798 if(r
== 30 || r
== 31){
4801 p
= get_prev_hist(*history
, filename
, 0, NULL
);
4803 p
= get_next_hist(*history
, filename
, 0, NULL
);
4807 if(r
== 32 || r
== 33){
4808 int nitems
= items_in_hist(dir_hist
);
4809 if(dir_hist
|| hist_len
> 0){
4812 p
= hist_in_pos(--pos
, ps_global
->VAR_HISTORY
, hist_len
, dir_hist
, nitems
);
4816 if(pos
< hist_len
+ nitems
)
4817 p
= hist_in_pos(++pos
, ps_global
->VAR_HISTORY
, hist_len
, dir_hist
, nitems
);
4819 if(p
== NULL
|| *p
== '\0')
4823 last
= p
; /* save it! */
4825 if(p
!= NULL
&& *p
!= '\0'){
4826 if(r
== 30 || r
== 31){
4827 if((fn
= last_cmpnt(p
)) != NULL
){
4828 strncpy(dir
, p
, MIN(fn
- p
, sizeof(dir
)-1));
4829 dir
[MIN(fn
- p
, sizeof(dir
)-1)] = '\0';
4831 dir
[fn
- p
- 1] = '\0';
4832 strncpy(filename
, fn
, len
-1);
4833 filename
[len
-1] = '\0';
4835 } else { /* r == 32 || r == 33 */
4836 strncpy(dir
, p
, sizeof(dir
)-1);
4837 dir
[sizeof(dir
)-1] = '\0';
4840 if(!strcmp(dir
, ps
->home_dir
)){
4851 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf
,
4852 SIZEOF_20KBUF
, filename
);
4853 strncpy(filename
, tmp_20k_buf
, len
);
4854 filename
[len
-1] = '\0';
4857 #endif /* _WINDOWS */
4863 removing_leading_and_trailing_white_space(filename
);
4866 if(!*def
){ /* Cancel */
4871 strncpy(filename
, def
, len
-1);
4872 filename
[len
-1] = '\0';
4875 #if defined(DOS) || defined(OS2)
4876 if(is_absolute_path(filename
)){
4877 fixpath(filename
, len
);
4880 if(filename
[0] == '~'){
4881 if(fnexpand(filename
, len
) == NULL
){
4882 char *p
= strindex(filename
, '/');
4885 q_status_message1(SM_ORDER
| SM_DING
, 3, 3,
4886 _("Error expanding file name: \"%s\" unknown user"),
4893 if(is_absolute_path(filename
)){
4894 strncpy(full_filename
, filename
, len
-1);
4895 full_filename
[len
-1] = '\0';
4899 build_path(full_filename
, (char *)getcwd(dir
,sizeof(dir
)),
4901 else if(dir
[0] == '~' && !dir
[1])
4902 build_path(full_filename
, ps
->home_dir
, filename
, len
);
4904 build_path(full_filename
, dir
, filename
, len
);
4907 if((ill
= filter_filename(full_filename
, &fatal
,
4908 ps_global
->restricted
|| ps_global
->VAR_OPER_DIR
)) != NULL
){
4910 q_status_message1(SM_ORDER
| SM_DING
, 3, 3, "%s", ill
);
4914 /* BUG: we should beep when the key's pressed rather than bitch later */
4915 /* Warn and ask for confirmation. */
4916 snprintf(prompt_buf
, sizeof(prompt_buf
), "File name contains a '%s'. %s anyway",
4917 ill
, (flags
& GE_IS_EXPORT
) ? "Export" : "Save");
4918 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4919 if(want_to(prompt_buf
, 'n', 0, NO_HELP
,
4920 ((flags
& GE_SEQ_SENSITIVE
) ? RB_SEQ_SENSITIVE
: 0)) != 'y')
4925 break; /* Must have got an OK file name */
4928 if(VAR_OPER_DIR
&& !in_dir(VAR_OPER_DIR
, full_filename
)){
4933 if(!can_access(full_filename
, ACCESS_EXISTS
)){
4935 static ESCKEY_S access_opts
[] = {
4936 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4937 a file or append to the end of the file */
4938 {'o', 'o', "O", N_("Overwrite")},
4939 {'a', 'a', "A", N_("Append")},
4940 {-1, 0, NULL
, NULL
}};
4942 rbflags
= RB_NORM
| ((flags
& GE_SEQ_SENSITIVE
) ? RB_SEQ_SENSITIVE
: 0);
4944 if(flags
& GE_NO_APPEND
){
4945 r
= strlen(filename
);
4946 snprintf(prompt_buf
, sizeof(prompt_buf
),
4947 /* TRANSLATORS: asking user whether to overwrite a file or not,
4948 File <filename> already exists. Overwrite it ? */
4949 _("File \"%s%s\" already exists. Overwrite it "),
4950 (r
> 20) ? "..." : "",
4951 filename
+ ((r
> 20) ? r
- 20 : 0));
4952 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4953 if(want_to(prompt_buf
, 'n', 'x', NO_HELP
, rbflags
) == 'y'){
4955 *rflags
|= GER_OVER
;
4957 if(our_unlink(full_filename
) < 0){
4958 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
4959 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4960 _("Cannot remove old %s: %s"),
4961 full_filename
, error_description(errno
));
4969 else if(!(flags
& GE_IS_IMPORT
)){
4970 r
= strlen(filename
);
4971 snprintf(prompt_buf
, sizeof(prompt_buf
),
4972 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4973 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4974 (r
> 20) ? "..." : "",
4975 filename
+ ((r
> 20) ? r
- 20 : 0));
4976 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4977 switch(radio_buttons(prompt_buf
, -FOOTER_ROWS(ps_global
),
4978 access_opts
, 'a', 'x', NO_HELP
, rbflags
)){
4981 *rflags
|= GER_OVER
;
4983 if(our_truncate(full_filename
, (off_t
)0) < 0)
4984 /* trouble truncating, but we'll give it a try anyway */
4985 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
4986 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
4987 _("Warning: Cannot truncate old %s: %s"),
4988 full_filename
, error_description(errno
));
4993 *rflags
|= GER_APPEND
;
5006 if(history
&& ret
== 0){
5007 save_hist(*history
, full_filename
, 0, NULL
);
5008 strncpy(tmp
, full_filename
, MAXPATH
);
5009 tmp
[MAXPATH
] = '\0';
5010 if((fn
= strrchr(tmp
, C_FILESEP
)) != NULL
)
5015 save_hist(dir_hist
, tmp
, 0, NULL
);
5018 if(opts
&& opts
!= optsarg
)
5019 fs_give((void **) &opts
);
5025 /*----------------------------------------------------------------------
5026 parse the config'd upload/download command
5028 Args: cmd -- buffer to return command fit for shellin'
5031 fname -- file name to build into the command
5033 Returns: pointer to cmd_str buffer or NULL on real bad error
5035 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
5036 cfg_str is written to standard out right before a successful
5037 return of this function. The call immediately following this
5038 function darn well better be the shell exec...
5041 build_updown_cmd(char *cmd
, size_t cmdlen
, char *prefix
, char *cfg_str
, char *fname
)
5044 int fname_found
= 0;
5046 if(prefix
&& *prefix
){
5047 /* loop thru replacing all occurrences of _FILE_ */
5048 p
= strncpy(cmd
, prefix
, cmdlen
);
5049 cmd
[cmdlen
-1] = '\0';
5050 while((p
= strstr(p
, "_FILE_")))
5051 rplstr(p
, cmdlen
-(p
-cmd
), 6, fname
);
5056 /* loop thru replacing all occurrences of _FILE_ */
5057 p
= strncpy(cmd
, cfg_str
, cmdlen
);
5058 cmd
[cmdlen
-1] = '\0';
5059 while((p
= strstr(p
, "_FILE_"))){
5060 rplstr(p
, cmdlen
-(p
-cmd
), 6, fname
);
5065 snprintf(cmd
+strlen(cmd
), cmdlen
-strlen(cmd
), " %s", fname
);
5067 cmd
[cmdlen
-1] = '\0';
5069 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
5075 /*----------------------------------------------------------------------
5076 Write a berzerk format message delimiter using the given putc function
5078 Args: e -- envelope of message to write
5079 pc -- function to use
5081 Returns: TRUE if we could write it, FALSE if there was a problem
5083 NOTE: follows delimiter with OS-dependent newline
5086 bezerk_delimiter(ENVELOPE
*env
, MESSAGECACHE
*mc
, gf_io_t pc
, int leading_newline
)
5092 /* write "[\n]From mailbox[@host] " */
5093 if(!((leading_newline
? gf_puts(NEWLINE
, pc
) : 1)
5094 && gf_puts("From ", pc
)
5095 && gf_puts((env
&& env
->from
) ? env
->from
->mailbox
5096 : "the-concourse-on-high", pc
)
5097 && gf_puts((env
&& env
->from
&& env
->from
->host
) ? "@" : "", pc
)
5098 && gf_puts((env
&& env
->from
&& env
->from
->host
) ? env
->from
->host
5104 when
= mail_longdate(mc
);
5105 else if(env
&& env
->date
&& env
->date
[0]
5106 && mail_parse_date(&telt
,env
->date
))
5107 when
= mail_longdate(&telt
);
5113 while(p
&& *p
&& *p
!= '\n') /* write date */
5117 if(!gf_puts(NEWLINE
, pc
)) /* write terminating newline */
5124 /*----------------------------------------------------------------------
5125 Execute command to jump to a given message number
5127 Args: qline -- Line to ask question on
5129 Result: returns true if the use selected a new message, false otherwise
5133 jump_to(MSGNO_S
*msgmap
, int qline
, UCS first_num
, SCROLL_S
*sparms
, CmdWhere in_index
)
5135 char jump_num_string
[80], *j
, prompt
[70];
5138 static ESCKEY_S jump_to_key
[] = { {0, 0, NULL
, NULL
},
5139 /* TRANSLATORS: go to First Message */
5140 {ctrl('Y'), 10, "^Y", N_("First Msg")},
5141 {ctrl('V'), 11, "^V", N_("Last Msg")},
5142 {-1, 0, NULL
, NULL
} };
5144 dprint((4, "\n - jump_to -\n"));
5147 if(sparms
&& sparms
->jump_is_debug
)
5148 return(get_level(qline
, first_num
, sparms
));
5151 if(!any_messages(msgmap
, NULL
, "to Jump to"))
5154 if(first_num
&& first_num
< 0x80 && isdigit((unsigned char) first_num
)){
5155 jump_num_string
[0] = first_num
;
5156 jump_num_string
[1] = '\0';
5159 jump_num_string
[0] = '\0';
5161 if(mn_total_cur(msgmap
) > 1L){
5162 snprintf(prompt
, sizeof(prompt
), "Unselect %s msgs in favor of number to be entered",
5163 comatose(mn_total_cur(msgmap
)));
5164 prompt
[sizeof(prompt
)-1] = '\0';
5165 if((rc
= want_to(prompt
, 'n', 0, NO_HELP
, WT_NORM
)) == 'n')
5169 snprintf(prompt
, sizeof(prompt
), "%s number to jump to : ", in_index
== ThrdIndx
5172 prompt
[sizeof(prompt
)-1] = '\0';
5176 int flags
= OE_APPEND_CURRENT
;
5178 rc
= optionally_enter(jump_num_string
, qline
, 0,
5179 sizeof(jump_num_string
), prompt
,
5180 jump_to_key
, help
, &flags
);
5182 help
= help
== NO_HELP
5183 ? (in_index
== ThrdIndx
? h_oe_jump_thd
: h_oe_jump
)
5187 else if(rc
== 10 || rc
== 11){
5191 closest
= closest_jump_target(rc
== 10 ? 1L
5192 : ((in_index
== ThrdIndx
)
5193 ? msgmap
->max_thrdno
5194 : mn_get_total(msgmap
)),
5195 ps_global
->mail_stream
,
5197 in_index
, warning
, sizeof(warning
));
5198 /* ignore warning */
5203 * If we take out the *jump_num_string nonempty test in this if
5204 * then the closest_jump_target routine will offer a jump to the
5205 * last message. However, it is slow because you have to wait for
5206 * the status message and it is annoying for people who hit J command
5207 * by mistake and just want to hit return to do nothing, like has
5208 * always worked. So the test is there for now. Hubert 2002-08-19
5210 * Jumping to first/last message is now possible through ^Y/^V
5211 * commands above. jpf 2002-08-21
5212 * (and through "end" hubert 2006-07-07)
5214 if(rc
== 0 && *jump_num_string
!= '\0'){
5215 removing_leading_and_trailing_white_space(jump_num_string
);
5216 for(j
=jump_num_string
; isdigit((unsigned char)*j
) || *j
=='-'; j
++)
5220 if(!strucmp("end", j
))
5221 return((in_index
== ThrdIndx
) ? msgmap
->max_thrdno
: mn_get_total(msgmap
));
5223 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5224 _("Invalid number entered. Use only digits 0-9"));
5225 jump_num_string
[0] = '\0';
5229 long closest
, jump_num
;
5231 if(*jump_num_string
)
5232 jump_num
= atol(jump_num_string
);
5237 closest
= closest_jump_target(jump_num
, ps_global
->mail_stream
,
5239 *jump_num_string
? 0 : 1,
5240 in_index
, warning
, sizeof(warning
));
5242 q_status_message(SM_ORDER
| SM_DING
, 2, 2, warning
);
5244 if(closest
== jump_num
)
5248 jump_num_string
[0] = '\0';
5250 strncpy(jump_num_string
, long2string(closest
),
5251 sizeof(jump_num_string
));
5266 * cmd_delete_action - handle msgno advance and such after single message deletion
5269 cmd_delete_action(struct pine
*state
, MSGNO_S
*msgmap
, CmdWhere in_index
)
5275 msgno
= mn_get_cur(msgmap
);
5276 advance_cur_after_delete(state
, state
->mail_stream
, msgmap
, in_index
);
5278 if(IS_NEWS(state
->mail_stream
)
5279 || ((state
->context_current
->use
& CNTXT_INCMNG
)
5280 && context_isambig(state
->cur_folder
))){
5282 opts
= (NSF_TRUST_FLAGS
| NSF_SKIP_CHID
);
5283 if(in_index
== View
)
5284 opts
&= ~NSF_SKIP_CHID
;
5286 (void)next_sorted_flagged(F_UNDEL
|F_UNSEEN
, state
->mail_stream
, msgno
, &opts
);
5287 if(!(opts
& NSF_FLAG_MATCH
)){
5288 char nextfolder
[MAXPATH
];
5290 strncpy(nextfolder
, state
->cur_folder
, sizeof(nextfolder
));
5291 nextfolder
[sizeof(nextfolder
)-1] = '\0';
5292 rv
= next_folder(NULL
, nextfolder
, sizeof(nextfolder
), nextfolder
,
5293 state
->context_current
, NULL
, NULL
)
5294 ? ". Press TAB for next folder."
5295 : ". No more folders to TAB to.";
5304 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5307 cmd_delete_index(struct pine
*state
, MSGNO_S
*msgmap
)
5309 return(cmd_delete_action(state
, msgmap
,MsgIndx
));
5313 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5316 cmd_delete_view(struct pine
*state
, MSGNO_S
*msgmap
)
5318 return(cmd_delete_action(state
, msgmap
, View
));
5323 advance_cur_after_delete(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, CmdWhere in_index
)
5325 long new_msgno
, msgno
;
5328 new_msgno
= msgno
= mn_get_cur(msgmap
);
5329 opts
= NSF_TRUST_FLAGS
;
5331 if(F_ON(F_DEL_SKIPS_DEL
, state
)){
5333 if(THREADING() && sp_viewing_a_thread(stream
))
5334 opts
|= NSF_SKIP_CHID
;
5336 new_msgno
= next_sorted_flagged(F_UNDEL
, stream
, msgno
, &opts
);
5339 mn_inc_cur(stream
, msgmap
,
5340 (in_index
== View
&& THREADING()
5341 && sp_viewing_a_thread(stream
))
5343 : (in_index
== View
)
5344 ? MH_ANYTHD
: MH_NONE
);
5345 new_msgno
= mn_get_cur(msgmap
);
5346 if(new_msgno
!= msgno
)
5347 opts
|= NSF_FLAG_MATCH
;
5351 * Viewing_a_thread is the complicated case because we want to ignore
5352 * other threads at first and then look in other threads if we have to.
5353 * By ignoring other threads we also ignore collapsed partial threads
5354 * in our own thread.
5356 if(THREADING() && sp_viewing_a_thread(stream
) && !(opts
& NSF_FLAG_MATCH
)){
5357 long rawno
, orig_thrdno
;
5358 PINETHRD_S
*thrd
, *topthrd
= NULL
;
5360 rawno
= mn_m2raw(msgmap
, msgno
);
5361 thrd
= fetch_thread(stream
, rawno
);
5362 if(thrd
&& thrd
->top
)
5363 topthrd
= fetch_thread(stream
, thrd
->top
);
5365 orig_thrdno
= topthrd
? topthrd
->thrdno
: -1L;
5367 opts
= NSF_TRUST_FLAGS
;
5368 new_msgno
= next_sorted_flagged(F_UNDEL
, stream
, msgno
, &opts
);
5371 * If we got a match, new_msgno may be a message in
5372 * a different thread from the one we are viewing, or it could be
5373 * in a collapsed part of this thread.
5375 if(opts
& NSF_FLAG_MATCH
){
5380 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
,new_msgno
));
5381 if(thrd
&& thrd
->top
)
5382 topthrd
= fetch_thread(stream
, thrd
->top
);
5385 * If this match is in the same thread we're already in
5386 * then we're done, else we have to ask the user and maybe
5389 if(!(orig_thrdno
> 0L && topthrd
5390 && topthrd
->thrdno
== orig_thrdno
)){
5392 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD
, state
)){
5393 if(in_index
== View
)
5394 snprintf(pmt
, sizeof(pmt
),
5395 "View message in thread number %.10s",
5396 topthrd
? comatose(topthrd
->thrdno
) : "?");
5398 snprintf(pmt
, sizeof(pmt
), "View thread number %.10s",
5399 topthrd
? comatose(topthrd
->thrdno
) : "?");
5401 ret
= want_to(pmt
, 'y', 'x', NO_HELP
, WT_NORM
);
5407 unview_thread(state
, stream
, msgmap
);
5408 mn_set_cur(msgmap
, new_msgno
);
5410 && (count_lflags_in_thread(stream
, topthrd
, msgmap
,
5412 && view_thread(state
, stream
, msgmap
, 1)){
5413 if(current_index_state
)
5414 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
5416 state
->view_skipped_index
= 1;
5417 state
->next_screen
= mail_view_screen
;
5420 view_thread(state
, stream
, msgmap
, 1);
5421 if(current_index_state
)
5422 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
5424 state
->next_screen
= SCREEN_FUN_NULL
;
5428 new_msgno
= msgno
; /* stick with original */
5433 mn_set_cur(msgmap
, new_msgno
);
5434 if(in_index
!= View
)
5435 adjust_cur_to_visible(stream
, msgmap
);
5441 get_level(int qline
, UCS first_num
, SCROLL_S
*sparms
)
5443 char debug_num_string
[80], *j
, prompt
[70];
5448 if(first_num
&& first_num
< 0x80 && isdigit((unsigned char)first_num
)){
5449 debug_num_string
[0] = first_num
;
5450 debug_num_string
[1] = '\0';
5451 debug_num
= atol(debug_num_string
);
5452 *(int *)(sparms
->proc
.data
.p
) = debug_num
;
5453 q_status_message1(SM_ORDER
, 0, 3, "Show debug <= level %s",
5454 comatose(debug_num
));
5458 debug_num_string
[0] = '\0';
5460 snprintf(prompt
, sizeof(prompt
), "Show debug <= this level (0-%d) : ", MAX(debug
, 9));
5461 prompt
[sizeof(prompt
)-1] = '\0';
5465 int flags
= OE_APPEND_CURRENT
;
5467 rc
= optionally_enter(debug_num_string
, qline
, 0,
5468 sizeof(debug_num_string
), prompt
,
5469 NULL
, help
, &flags
);
5471 help
= help
== NO_HELP
? h_oe_debuglevel
: NO_HELP
;
5476 removing_leading_and_trailing_white_space(debug_num_string
);
5477 for(j
=debug_num_string
; isdigit((unsigned char)*j
); j
++)
5481 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5482 _("Invalid number entered. Use only digits 0-9"));
5483 debug_num_string
[0] = '\0';
5486 debug_num
= atol(debug_num_string
);
5488 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5489 _("Number should be >= 0"));
5490 else if(debug_num
> MAX(debug
,9))
5491 q_status_message1(SM_ORDER
| SM_DING
, 2, 2,
5492 _("Maximum is %s"), comatose(MAX(debug
,9)));
5494 *(int *)(sparms
->proc
.data
.p
) = debug_num
;
5495 q_status_message1(SM_ORDER
, 0, 3,
5496 "Show debug <= level %s",
5497 comatose(debug_num
));
5515 * Returns the message number closest to target that isn't hidden.
5516 * Make warning at least 100 chars.
5517 * A return of 0 means there is no message to jump to.
5520 closest_jump_target(long int target
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int no_target
, CmdWhere in_index
, char *warning
, size_t warninglen
)
5522 long i
, start
, closest
= 0L;
5527 maxnum
= (in_index
== ThrdIndx
) ? msgmap
->max_thrdno
: mn_get_total(msgmap
);
5532 snprintf(warning
, warninglen
, "No %s number entered, jump to end? ",
5533 (in_index
== ThrdIndx
) ? "thread" : "message");
5534 warning
[warninglen
-1] = '\0';
5536 else if(target
< 1L)
5537 start
= 1L - target
;
5538 else if(target
> maxnum
)
5539 start
= target
- maxnum
;
5543 if(target
> 0L && target
<= maxnum
)
5544 if(in_index
== ThrdIndx
5545 || !msgline_hidden(stream
, msgmap
, target
, 0))
5548 for(i
= start
; target
+i
<= maxnum
|| target
-i
> 0L; i
++){
5550 if(target
+i
> 0L && target
+i
<= maxnum
&&
5551 (in_index
== ThrdIndx
5552 || !msgline_hidden(stream
, msgmap
, target
+i
, 0))){
5557 if(target
-i
> 0L && target
-i
<= maxnum
&&
5558 (in_index
== ThrdIndx
5559 || !msgline_hidden(stream
, msgmap
, target
-i
, 0))){
5565 strncpy(buf
, long2string(closest
), sizeof(buf
));
5566 buf
[sizeof(buf
)-1] = '\0';
5569 strncpy(warning
, "Nothing to jump to", warninglen
);
5570 else if(target
< 1L)
5571 snprintf(warning
, warninglen
, "%s number (%s) must be at least %s",
5572 (in_index
== ThrdIndx
) ? "Thread" : "Message",
5573 long2string(target
), buf
);
5574 else if(target
> maxnum
)
5575 snprintf(warning
, warninglen
, "%s number (%s) may be no more than %s",
5576 (in_index
== ThrdIndx
) ? "Thread" : "Message",
5577 long2string(target
), buf
);
5579 snprintf(warning
, warninglen
,
5580 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5581 long2string(target
), buf
);
5583 warning
[warninglen
-1] = '\0';
5589 /*----------------------------------------------------------------------
5590 Prompt for folder name to open, expand the name and return it
5592 Args: qline -- Screen line to prompt on
5593 allow_list -- if 1, allow ^T to bring up collection lister
5595 Result: returns the folder name or NULL
5596 pine structure mangled_footer flag is set
5597 may call the collection lister in which case mangled screen will be set
5599 This prompts the user for the folder to open, possibly calling up
5600 the collection lister if the user types ^T.
5601 ----------------------------------------------------------------------*/
5603 broach_folder(int qline
, int allow_list
, int *notrealinbox
, CONTEXT_S
**context
)
5606 static char newfolder
[MAILTMPLEN
];
5607 char expanded
[MAXPATH
+1],
5608 prompt
[MAX_SCREEN_COLS
+1],
5610 unsigned char *f1
, *f2
, *f3
;
5611 static HISTORY_S
*history
= NULL
;
5612 CONTEXT_S
*tc
, *tc2
;
5614 int rc
, r
, ku
= -1, n
, flags
, last_rc
= 0, inbox
, done
= 0;
5617 * the idea is to provide a clue for the context the file name
5618 * will be saved in (if a non-imap names is typed), and to
5619 * only show the previous if it was also in the same context
5626 (*notrealinbox
) = 1;
5628 init_hist(&history
, HISTSIZE
);
5630 tc
= broach_get_folder(context
? *context
: NULL
, &inbox
, NULL
);
5632 /* set up extra command option keys */
5634 ekey
[rc
].ch
= (allow_list
) ? ctrl('T') : 0 ;
5635 ekey
[rc
].rval
= (allow_list
) ? 2 : 0;
5636 ekey
[rc
].name
= (allow_list
) ? "^T" : "";
5637 ekey
[rc
++].label
= (allow_list
) ? N_("ToFldrs") : "";
5639 if(ps_global
->context_list
->next
){
5640 ekey
[rc
].ch
= ctrl('P');
5642 ekey
[rc
].name
= "^P";
5643 ekey
[rc
++].label
= N_("Prev Collection");
5645 ekey
[rc
].ch
= ctrl('N');
5647 ekey
[rc
].name
= "^N";
5648 ekey
[rc
++].label
= N_("Next Collection");
5651 ekey
[rc
].ch
= ctrl('W');
5653 ekey
[rc
].name
= "^W";
5654 ekey
[rc
++].label
= N_("INBOX");
5656 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps_global
)){
5659 ekey
[rc
].name
= "TAB";
5660 ekey
[rc
++].label
= N_("Complete");
5663 if(F_ON(F_ENABLE_SUB_LISTS
, ps_global
)){
5664 ekey
[rc
].ch
= ctrl('X');
5666 ekey
[rc
].name
= "^X";
5667 ekey
[rc
++].label
= N_("ListMatches");
5670 if(ps_global
->context_list
->next
&& F_ON(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
5671 ekey
[rc
].ch
= KEY_UP
;
5674 ekey
[rc
++].label
= "";
5676 ekey
[rc
].ch
= KEY_DOWN
;
5679 ekey
[rc
++].label
= "";
5681 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
5682 ekey
[rc
].ch
= KEY_UP
;
5686 ekey
[rc
++].label
= "";
5688 ekey
[rc
].ch
= KEY_DOWN
;
5691 ekey
[rc
++].label
= "";
5698 * Figure out next default value for this context. The idea
5699 * is that in each context the last folder opened is cached.
5700 * It's up to pick it out and display it. This is fine
5701 * and dandy if we've currently got the inbox open, BUT
5702 * if not, make the inbox the default the first time thru.
5705 last_folder
= ps_global
->inbox_name
;
5706 inbox
= 1; /* pretend we're in inbox from here on out */
5709 last_folder
= (ps_global
->last_unambig_folder
[0])
5710 ? ps_global
->last_unambig_folder
5711 : ((tc
->last_folder
[0]) ? tc
->last_folder
: NULL
);
5713 if(last_folder
){ /* MAXPATH + 1 = sizeof(expanded) */
5714 unsigned char *fname
= folder_name_decoded((unsigned char *)last_folder
);
5715 snprintf(expanded
, sizeof(expanded
), " [%.*s]", MAXPATH
+1-5,
5716 fname
? (char *) fname
: last_folder
);
5717 if(fname
) fs_give((void **)&fname
);
5722 expanded
[sizeof(expanded
)-1] = '\0';
5724 /* only show collection number if more than one available */
5725 if(ps_global
->context_list
->next
) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5726 snprintf(prompt
, sizeof(prompt
), "GOTO %s in <%s> %.*s%s: ",
5727 NEWS_TEST(tc
) ? "news group" : "folder",
5728 tc
->nickname
, MAX_SCREEN_COLS
+1-50, expanded
,
5729 *expanded
? " " : "");
5730 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5731 snprintf(prompt
, sizeof(prompt
), "GOTO folder %.*s%s: ", MAX_SCREEN_COLS
+1-20, expanded
,
5732 *expanded
? " " : "");
5734 prompt
[sizeof(prompt
)-1] = '\0';
5736 if(utf8_width(prompt
) > MAXPROMPT
){
5737 if(ps_global
->context_list
->next
) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5738 snprintf(prompt
, sizeof(prompt
), "GOTO <%s> %.*s%s: ",
5739 tc
->nickname
, MAX_SCREEN_COLS
+1-50, expanded
,
5740 *expanded
? " " : "");
5741 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5742 snprintf(prompt
, sizeof(prompt
), "GOTO %.*s%s: ", MAX_SCREEN_COLS
+1-20, expanded
,
5743 *expanded
? " " : "");
5745 prompt
[sizeof(prompt
)-1] = '\0';
5747 if(utf8_width(prompt
) > MAXPROMPT
){
5748 if(ps_global
->context_list
->next
) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5749 snprintf(prompt
, sizeof(prompt
), "<%s> %.*s%s: ",
5750 tc
->nickname
, MAX_SCREEN_COLS
+1-50, expanded
,
5751 *expanded
? " " : "");
5752 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5753 snprintf(prompt
, sizeof(prompt
), "%.*s%s: ", MAX_SCREEN_COLS
+1-20, expanded
,
5754 *expanded
? " " : "");
5756 prompt
[sizeof(prompt
)-1] = '\0';
5761 if(items_in_hist(history
) > 1){
5762 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
5763 ekey
[ku
].label
= HISTORY_KEYLABEL
;
5764 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
5765 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
5769 ekey
[ku
].label
= "";
5770 ekey
[ku
+1].name
= "";
5771 ekey
[ku
+1].label
= "";
5775 /* is there any other way to do this? The point is that we
5776 * are trying to hide mutf7 from the user, and use the utf8
5777 * equivalent. So we create a variable f to take place of
5778 * newfolder, including content and size. f2 is copy of f1
5779 * that has to freed. Sigh!
5781 f3
= (unsigned char *) cpystr(newfolder
);
5782 f1
= fs_get(sizeof(newfolder
));
5783 f2
= folder_name_decoded(f3
);
5784 if(f3
) fs_give((void **)&f3
);
5785 strncpy((char *)f1
, (char *)f2
, sizeof(newfolder
));
5786 f1
[sizeof(newfolder
)-1] = '\0';
5787 if(f2
) fs_give((void **)&f2
);
5789 flags
= OE_APPEND_CURRENT
;
5790 rc
= optionally_enter((char *) f1
, qline
, 0, sizeof(newfolder
),
5791 (char *) prompt
, ekey
, help
, &flags
);
5793 f2
= folder_name_encoded(f1
);
5794 strncpy(newfolder
, (char *)f2
, sizeof(newfolder
));
5795 if(f1
) fs_give((void **)&f1
);
5796 if(f2
) fs_give((void **)&f2
);
5798 ps_global
->mangled_footer
= 1;
5801 case -1 : /* o_e says error! */
5802 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
5803 _("Error reading folder name"));
5806 case 0 : /* o_e says normal entry */
5807 removing_trailing_white_space(newfolder
);
5808 removing_leading_white_space(newfolder
);
5811 char *name
, *fullname
= NULL
;
5812 int exists
, breakout
= 0;
5814 save_hist(history
, newfolder
, 0, tc
);
5816 if(!(name
= folder_is_nick(newfolder
, FOLDERS(tc
),
5820 if(update_folder_spec(expanded
, sizeof(expanded
), name
)){
5821 strncpy(name
= newfolder
, expanded
, sizeof(newfolder
));
5822 newfolder
[sizeof(newfolder
)-1] = '\0';
5825 exists
= folder_name_exists(tc
, name
, &fullname
);
5828 strncpy(name
= newfolder
, fullname
, sizeof(newfolder
));
5829 newfolder
[sizeof(newfolder
)-1] = '\0';
5830 fs_give((void **) &fullname
);
5835 * if we know the things a folder, open it.
5836 * else if we know its a directory, visit it.
5837 * else we're not sure (it either doesn't really
5838 * exist or its unLISTable) so try opening it anyway
5840 if(exists
& FEX_ISFILE
){
5844 else if((exists
& FEX_ISDIR
)){
5846 CONTEXT_S
*fake_context
;
5847 char tmp
[MAILTMPLEN
];
5850 strncpy(tmp
, name
, sizeof(tmp
));
5851 tmp
[sizeof(tmp
)-2-1] = '\0';
5852 if(tmp
[(l
= strlen(tmp
)) - 1] != tc
->dir
->delim
){
5853 if(l
< sizeof(tmp
)){
5854 tmp
[l
] = tc
->dir
->delim
;
5855 strncpy(&tmp
[l
+1], "[]", sizeof(tmp
)-(l
+1));
5859 strncat(tmp
, "[]", sizeof(tmp
)-strlen(tmp
)-1);
5861 tmp
[sizeof(tmp
)-1] = '\0';
5863 fake_context
= new_context(tmp
, 0);
5864 newfolder
[0] = '\0';
5865 done
= display_folder_list(&fake_context
, newfolder
,
5866 1, folders_for_goto
);
5867 free_context(&fake_context
);
5870 else if(!(tc
->use
& CNTXT_INCMNG
)){
5871 done
= display_folder_list(&tc
, newfolder
,
5872 1, folders_for_goto
);
5876 else if((exists
& FEX_ERROR
)){
5877 q_status_message1(SM_ORDER
, 0, 3,
5878 _("Problem accessing folder \"%s\""),
5887 if(exists
== FEX_ERROR
)
5888 q_status_message1(SM_ORDER
, 0, 3,
5889 _("Problem accessing folder \"%s\""),
5891 else if(tc
->use
& CNTXT_INCMNG
)
5892 q_status_message1(SM_ORDER
, 0, 3,
5893 _("Can't find Incoming Folder: %s"),
5895 else if(context_isambig(newfolder
))
5896 q_status_message2(SM_ORDER
, 0, 3,
5897 _("Can't find folder \"%s\" in %s"),
5898 newfolder
, (void *) tc
->nickname
);
5900 q_status_message1(SM_ORDER
, 0, 3,
5901 _("Can't find folder \"%s\""),
5906 else if(last_folder
){
5907 if(ps_global
->goto_default_rule
== GOTO_FIRST_CLCTN_DEF_INBOX
5908 && !strucmp(last_folder
, ps_global
->inbox_name
)
5909 && tc
== ((ps_global
->context_list
->use
& CNTXT_INCMNG
)
5910 ? ps_global
->context_list
->next
: ps_global
->context_list
)){
5912 (*notrealinbox
) = 0;
5914 tc
= ps_global
->context_list
;
5917 strncpy(newfolder
, last_folder
, sizeof(newfolder
));
5918 newfolder
[sizeof(newfolder
)-1] = '\0';
5919 save_hist(history
, newfolder
, 0, tc
);
5923 /* fall thru like they cancelled */
5925 case 1 : /* o_e says user cancel */
5926 cmd_cancelled("Open folder");
5929 case 2 : /* o_e says user wants list */
5930 r
= display_folder_list(&tc
, newfolder
, 0, folders_for_goto
);
5936 case 3 : /* o_e says user wants help */
5937 help
= help
== NO_HELP
? h_oe_broach
: NO_HELP
;
5940 case 4 : /* redraw */
5943 case 10 : /* Previous collection */
5944 tc2
= ps_global
->context_list
;
5945 while(tc2
->next
&& tc2
->next
!= tc
)
5951 case 11 : /* Next collection */
5952 tc
= (tc
->next
) ? tc
->next
: ps_global
->context_list
;
5955 case 12 : /* file name completion */
5956 if(!folder_complete(tc
, newfolder
, sizeof(newfolder
), &n
)){
5957 if(n
&& last_rc
== 12 && !(flags
& OE_USER_MODIFIED
)){
5958 r
= display_folder_list(&tc
, newfolder
, 1,folders_for_goto
);
5960 done
++; /* bingo! */
5962 rc
= 0; /* burn last_rc */
5970 case 14 : /* file name completion */
5971 r
= display_folder_list(&tc
, newfolder
, 2, folders_for_goto
);
5973 done
++; /* bingo! */
5975 rc
= 0; /* burn last_rc */
5979 case 17 : /* GoTo INBOX */
5981 strncpy(newfolder
, ps_global
->inbox_name
, sizeof(newfolder
)-1);
5982 newfolder
[sizeof(newfolder
)-1] = '\0';
5984 (*notrealinbox
) = 0;
5986 tc
= ps_global
->context_list
;
5987 save_hist(history
, newfolder
, 0, tc
);
5992 if((p
= get_prev_hist(history
, newfolder
, 0, tc
)) != NULL
){
5993 strncpy(newfolder
, p
, sizeof(newfolder
));
5994 newfolder
[sizeof(newfolder
)-1] = '\0';
5995 if(history
->hist
[history
->curindex
])
5996 tc
= history
->hist
[history
->curindex
]->cntxt
;
6004 if((p
= get_next_hist(history
, newfolder
, 0, tc
)) != NULL
){
6005 strncpy(newfolder
, p
, sizeof(newfolder
));
6006 newfolder
[sizeof(newfolder
)-1] = '\0';
6007 if(history
->hist
[history
->curindex
])
6008 tc
= history
->hist
[history
->curindex
]->cntxt
;
6016 alpine_panic("Unhandled case");
6023 dprint((2, "broach folder, name entered \"%s\"\n",
6024 newfolder
? newfolder
: "?"));
6026 /*-- Just check that we can expand this. It gets done for real later --*/
6027 strncpy(expanded
, newfolder
, sizeof(expanded
));
6028 expanded
[sizeof(expanded
)-1] = '\0';
6030 if(!expand_foldername(expanded
, sizeof(expanded
))) {
6031 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
6032 expanded
? expanded
: "?"));
6041 /*----------------------------------------------------------------------
6042 Check to see if user wants to reopen dead stream.
6047 Result: 1 if the folder was successfully updatedn
6052 ask_mailbox_reopen(struct pine
*ps
, int *reopenp
)
6054 if(((ps
->mail_stream
->dtb
6055 && ((ps
->mail_stream
->dtb
->flags
& DR_NONEWMAIL
)
6056 || (ps
->mail_stream
->rdonly
6057 && ps
->mail_stream
->dtb
->flags
& DR_NONEWMAILRONLY
)))
6058 && (ps
->reopen_rule
== REOPEN_ASK_ASK_Y
6059 || ps
->reopen_rule
== REOPEN_ASK_ASK_N
6060 || ps
->reopen_rule
== REOPEN_ASK_NO_Y
6061 || ps
->reopen_rule
== REOPEN_ASK_NO_N
))
6062 || ((ps
->mail_stream
->dtb
6063 && ps
->mail_stream
->rdonly
6064 && !(ps
->mail_stream
->dtb
->flags
& DR_LOCAL
))
6065 && (ps
->reopen_rule
== REOPEN_YES_ASK_Y
6066 || ps
->reopen_rule
== REOPEN_YES_ASK_N
6067 || ps
->reopen_rule
== REOPEN_ASK_ASK_Y
6068 || ps
->reopen_rule
== REOPEN_ASK_ASK_N
))){
6071 switch(ps
->reopen_rule
){
6072 case REOPEN_YES_ASK_Y
:
6073 case REOPEN_ASK_ASK_Y
:
6074 case REOPEN_ASK_NO_Y
:
6083 switch(want_to("Re-open folder to check for new messages", deefault
,
6084 'x', h_reopen_folder
, WT_NORM
)){
6099 /*----------------------------------------------------------------------
6100 Check to see if user input is in form of old c-client mailbox speck
6105 Result: 1 if the folder was successfully updatedn
6110 update_folder_spec(char *new, size_t newlen
, char *old
)
6116 if(*(p
= old
) == '*') /* old form? */
6119 if(*old
== '{') /* copy host spec */
6121 switch(*new = *old
++){
6126 if(!struncmp(old
, "nntp", 4))
6134 while(*new++ != '}' && (new-orignew
) < newlen
-1);
6136 if((*p
== '*' && *old
) || ((*old
== '*') ? *++old
: 0)){
6138 * OK, some heuristics here. If it looks like a newsgroup
6139 * then we plunk it into the #news namespace else we
6140 * assume that they're trying to get at a #public folder...
6143 *p
&& (isalnum((unsigned char) *p
) || strindex(".-", *p
));
6147 sstrncpy(&new, (*p
&& !nntp
) ? "#public/" : "#news.", newlen
-(new-orignew
));
6148 strncpy(new, old
, newlen
-(new-orignew
));
6152 orignew
[newlen
-1] = '\0';
6158 /*----------------------------------------------------------------------
6159 Open the requested folder in the requested context
6161 Args: state -- usual pine state struct
6162 newfolder -- folder to open
6163 new_context -- folder context might live in
6164 stream -- candidate for recycling
6166 Result: New folder open or not (if error), and we're set to
6167 enter the index screen.
6170 visit_folder(struct pine
*state
, char *newfolder
, CONTEXT_S
*new_context
,
6171 MAILSTREAM
*stream
, long unsigned int flags
)
6173 dprint((9, "visit_folder(%s, %s)\n",
6174 newfolder
? newfolder
: "?",
6175 (new_context
&& new_context
->context
)
6176 ? new_context
->context
: "(NULL)"));
6178 if(ps_global
&& ps_global
->ttyo
){
6179 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
6180 ps_global
->mangled_footer
= 1;
6183 if(do_broach_folder(newfolder
, new_context
, stream
? &stream
: NULL
,
6185 || !sp_flagged(state
->mail_stream
, SP_LOCKED
))
6186 state
->next_screen
= mail_index_screen
;
6188 state
->next_screen
= folder_screen
;
6192 /*----------------------------------------------------------------------
6193 Move read messages from folder if listed in archive
6199 read_msg_prompt(long int n
, char *f
)
6201 char buf
[MAX_SCREEN_COLS
+1];
6203 snprintf(buf
, sizeof(buf
), "Save the %ld read message%s in \"%s\"", n
, plural(n
), f
);
6204 buf
[sizeof(buf
)-1] = '\0';
6205 return(want_to(buf
, 'y', 0, NO_HELP
, WT_NORM
) == 'y');
6209 /*----------------------------------------------------------------------
6210 Print current message[s] or folder index
6212 Args: state -- pointer to struct holding a bunch of pine state
6213 msgmap -- table mapping msg nums to c-client sequence nums
6214 aopt -- aggregate options
6215 in_index -- boolean indicating we're called from Index Screen
6217 Filters the original header and sends stuff to printer
6220 cmd_print(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, CmdWhere in_index
)
6223 long i
, msgs
, rawno
;
6224 int next
= 0, do_index
= 0, rv
= 0;
6229 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
6232 msgs
= mn_total_cur(msgmap
);
6234 if((in_index
!= View
) && F_ON(F_PRINT_INDEX
, state
)){
6237 static ESCKEY_S prt_opts
[] = {
6238 {'i', 'i', "I", N_("Index")},
6239 {'m', 'm', "M", NULL
},
6240 {-1, 0, NULL
, NULL
}};
6242 if(in_index
== ThrdIndx
){
6243 /* TRANSLATORS: This is a question, Print Index ? */
6244 if(want_to(_("Print Index"), 'y', 'x', NO_HELP
, WT_NORM
) == 'y')
6250 snprintf(m
, sizeof(m
), "Message%s", (msgs
>1L) ? "s" : "");
6251 m
[sizeof(m
)-1] = '\0';
6252 prt_opts
[1].label
= m
;
6253 snprintf(prompt
, sizeof(prompt
), "Print %sFolder Index or %s %s? ",
6254 (aopt
& MCMD_AGG_2
) ? "thread " : MCMD_ISAGG(aopt
) ? "selected " : "",
6255 (aopt
& MCMD_AGG_2
) ? "thread" : MCMD_ISAGG(aopt
) ? "selected" : "current", m
);
6256 prompt
[sizeof(prompt
)-1] = '\0';
6258 ans
= radio_buttons(prompt
, -FOOTER_ROWS(state
), prt_opts
, 'm', 'x',
6259 NO_HELP
, RB_NORM
|RB_SEQ_SENSITIVE
);
6264 cmd_cancelled("Print");
6265 if(MCMD_ISAGG(aopt
))
6266 restore_selected(msgmap
);
6281 snprintf(prompt
, sizeof(prompt
), "%sFolder Index",
6282 (aopt
& MCMD_AGG_2
) ? "Thread " : MCMD_ISAGG(aopt
) ? "Selected " : "");
6284 snprintf(prompt
, sizeof(prompt
), "%s messages", long2string(msgs
));
6286 snprintf(prompt
, sizeof(prompt
), "Message %s", long2string(mn_get_cur(msgmap
)));
6288 prompt
[sizeof(prompt
)-1] = '\0';
6290 if(open_printer(prompt
) < 0){
6291 if(MCMD_ISAGG(aopt
))
6292 restore_selected(msgmap
);
6300 tc
= format_titlebar();
6302 /* Print titlebar... */
6303 print_text1("%s\n\n", tc
? tc
->titlebar_line
: "");
6304 /* then all the index members... */
6305 if(!print_index(state
, msgmap
, MCMD_ISAGG(aopt
)))
6306 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6307 _("Error printing folder index"));
6313 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
), next
++){
6314 if(next
&& F_ON(F_AGG_PRINT_FF
, state
))
6315 if(!print_char(FORMFEED
)){
6320 if(!(state
->mail_stream
6321 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
6322 && rawno
<= state
->mail_stream
->nmsgs
6323 && (mc
= mail_elt(state
->mail_stream
, rawno
))
6327 if(!(e
=pine_mail_fetchstructure(state
->mail_stream
,
6330 || (F_ON(F_FROM_DELIM_IN_PRINT
, ps_global
)
6331 && !bezerk_delimiter(e
, mc
, print_char
, next
))
6332 || !format_message(mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
6333 e
, b
, NULL
, FM_NEW_MESS
| FM_NOINDENT
,
6335 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6336 _("Error printing message"));
6345 if(MCMD_ISAGG(aopt
))
6346 restore_selected(msgmap
);
6352 /*----------------------------------------------------------------------
6355 Args: state -- various pine state bits
6356 msgmap -- Message number mapping table
6357 aopt -- option flags
6359 Filters the original header and sends stuff to specified command
6362 cmd_pipe(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
6368 char *resultfilename
= NULL
, prompt
[80], *p
;
6369 int done
= 0, rv
= 0;
6371 int fourlabel
= -1, j
= 0, next
= 0, ku
;
6372 int pipe_rv
; /* rv of proc to separate from close_system_pipe rv */
6374 unsigned flagsforhist
= 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6375 static HISTORY_S
*history
= NULL
;
6376 int capture
= 1, raw
= 0, delimit
= 0, newpipe
= 0;
6377 char pipe_command
[MAXPATH
];
6378 ESCKEY_S pipe_opt
[8];
6380 if(ps_global
->restricted
){
6381 q_status_message(SM_ORDER
| SM_DING
, 0, 4,
6382 "Alpine demo can't pipe messages");
6385 else if(!any_messages(msgmap
, NULL
, "to Pipe"))
6388 pipe_command
[0] = '\0';
6389 init_hist(&history
, HISTSIZE
);
6390 flagsforhist
= (raw
? 0x8 : 0) +
6391 (delimit
? 0x4 : 0) +
6392 (newpipe
? 0x2 : 0) +
6393 (capture
? 0x1 : 0);
6394 if((p
= get_prev_hist(history
, "", flagsforhist
, NULL
)) != NULL
){
6395 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6396 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6397 if(history
->hist
[history
->curindex
]){
6398 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6399 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6400 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6401 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6402 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6407 pipe_opt
[j
].rval
= 0;
6408 pipe_opt
[j
].name
= "";
6409 pipe_opt
[j
++].label
= "";
6411 pipe_opt
[j
].ch
= ctrl('W');
6412 pipe_opt
[j
].rval
= 10;
6413 pipe_opt
[j
].name
= "^W";
6414 pipe_opt
[j
++].label
= NULL
;
6416 pipe_opt
[j
].ch
= ctrl('Y');
6417 pipe_opt
[j
].rval
= 11;
6418 pipe_opt
[j
].name
= "^Y";
6419 pipe_opt
[j
++].label
= NULL
;
6421 pipe_opt
[j
].ch
= ctrl('R');
6422 pipe_opt
[j
].rval
= 12;
6423 pipe_opt
[j
].name
= "^R";
6424 pipe_opt
[j
++].label
= NULL
;
6426 if(MCMD_ISAGG(aopt
)){
6427 if(!pseudo_selected(state
->mail_stream
, msgmap
))
6431 pipe_opt
[j
].ch
= ctrl('T');
6432 pipe_opt
[j
].rval
= 13;
6433 pipe_opt
[j
].name
= "^T";
6434 pipe_opt
[j
++].label
= NULL
;
6438 pipe_opt
[j
].ch
= KEY_UP
;
6439 pipe_opt
[j
].rval
= 30;
6440 pipe_opt
[j
].name
= "";
6442 pipe_opt
[j
++].label
= "";
6444 pipe_opt
[j
].ch
= KEY_DOWN
;
6445 pipe_opt
[j
].rval
= 31;
6446 pipe_opt
[j
].name
= "";
6447 pipe_opt
[j
++].label
= "";
6449 pipe_opt
[j
].ch
= -1;
6454 snprintf(prompt
, sizeof(prompt
), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6456 MCMD_ISAGG(aopt
) ? "s" : " ",
6457 MCMD_ISAGG(aopt
) ? "" : comatose(mn_get_cur(msgmap
)),
6458 (!capture
|| delimit
|| (newpipe
&& MCMD_ISAGG(aopt
))) ? "(" : "",
6459 capture
? "" : "uncaptured",
6460 (!capture
&& delimit
) ? "," : "",
6461 delimit
? "delimited" : "",
6462 ((!capture
|| delimit
) && newpipe
&& MCMD_ISAGG(aopt
)) ? "," : "",
6463 (newpipe
&& MCMD_ISAGG(aopt
)) ? "new pipe" : "",
6464 (!capture
|| delimit
|| (newpipe
&& MCMD_ISAGG(aopt
))) ? ") " : "");
6465 prompt
[sizeof(prompt
)-1] = '\0';
6466 pipe_opt
[1].label
= raw
? N_("Shown Text") : N_("Raw Text");
6467 pipe_opt
[2].label
= capture
? N_("Free Output") : N_("Capture Output");
6468 pipe_opt
[3].label
= delimit
? N_("No Delimiter") : N_("With Delimiter");
6470 pipe_opt
[fourlabel
].label
= newpipe
? N_("To Same Pipe") : N_("To Individual Pipes");
6474 * 2 is really 1 because there will be one real entry and
6475 * one entry of "" because of the get_prev_hist above.
6477 if(items_in_hist(history
) > 2){
6478 pipe_opt
[ku
].name
= HISTORY_UP_KEYNAME
;
6479 pipe_opt
[ku
].label
= HISTORY_KEYLABEL
;
6480 pipe_opt
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
6481 pipe_opt
[ku
+1].label
= HISTORY_KEYLABEL
;
6484 pipe_opt
[ku
].name
= "";
6485 pipe_opt
[ku
].label
= "";
6486 pipe_opt
[ku
+1].name
= "";
6487 pipe_opt
[ku
+1].label
= "";
6490 flags
= OE_APPEND_CURRENT
| OE_SEQ_SENSITIVE
;
6491 switch(optionally_enter(pipe_command
, -FOOTER_ROWS(state
), 0,
6492 sizeof(pipe_command
), prompt
,
6493 pipe_opt
, NO_HELP
, &flags
)){
6495 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
6496 _("Internal problem encountered"));
6500 case 10 : /* flip raw bit */
6504 case 11 : /* flip capture bit */
6508 case 12 : /* flip delimit bit */
6512 case 13 : /* flip newpipe bit */
6517 flagsforhist
= (raw
? 0x8 : 0) +
6518 (delimit
? 0x4 : 0) +
6519 (newpipe
? 0x2 : 0) +
6520 (capture
? 0x1 : 0);
6521 if((p
= get_prev_hist(history
, pipe_command
, flagsforhist
, NULL
)) != NULL
){
6522 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6523 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6524 if(history
->hist
[history
->curindex
]){
6525 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6526 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6527 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6528 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6529 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6538 flagsforhist
= (raw
? 0x8 : 0) +
6539 (delimit
? 0x4 : 0) +
6540 (newpipe
? 0x2 : 0) +
6541 (capture
? 0x1 : 0);
6542 if((p
= get_next_hist(history
, pipe_command
, flagsforhist
, NULL
)) != NULL
){
6543 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6544 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6545 if(history
->hist
[history
->curindex
]){
6546 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6547 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6548 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6549 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6550 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6559 if(pipe_command
[0]){
6561 flagsforhist
= (raw
? 0x8 : 0) +
6562 (delimit
? 0x4 : 0) +
6563 (newpipe
? 0x2 : 0) +
6564 (capture
? 0x1 : 0);
6565 save_hist(history
, pipe_command
, flagsforhist
, NULL
);
6567 flags
= PIPE_USER
| PIPE_WRITE
| PIPE_STDERR
;
6568 flags
|= (raw
? PIPE_RAW
: 0);
6574 ps_global
->mangled_screen
= 1;
6575 ps_global
->in_init_seq
= 1;
6577 flags
|= PIPE_RESET
;
6580 if(!newpipe
&& !(syspipe
= cmd_pipe_open(pipe_command
,
6581 (flags
& PIPE_RESET
)
6587 for(i
= mn_first_cur(msgmap
);
6589 i
= mn_next_cur(msgmap
)){
6590 e
= pine_mail_fetchstructure(ps_global
->mail_stream
,
6591 mn_m2raw(msgmap
, i
), &b
);
6592 if(!(state
->mail_stream
6593 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
6594 && rawno
<= state
->mail_stream
->nmsgs
6595 && (mc
= mail_elt(state
->mail_stream
, rawno
))
6600 && !(syspipe
= cmd_pipe_open(pipe_command
,
6601 (flags
& PIPE_RESET
)
6605 || (delimit
&& !bezerk_delimiter(e
, mc
, pc
, next
++)))
6612 prime_raw_pipe_getc(ps_global
->mail_stream
,
6613 mn_m2raw(msgmap
, i
), -1L, 0L);
6615 gf_link_filter(gf_nvtnl_local
, NULL
);
6616 if((pipe_err
= gf_pipe(raw_pipe_getc
, pc
)) != NULL
){
6617 q_status_message1(SM_ORDER
|SM_DING
,
6619 _("Internal Error: %s"),
6624 else if(!format_message(mn_m2raw(msgmap
, i
), e
, b
,
6625 NULL
, FM_NEW_MESS
| FM_NOWRAP
, pc
))
6630 if(close_system_pipe(&syspipe
, &pipe_rv
, pipe_callback
) == -1)
6635 ps_global
->in_init_seq
= 0;
6638 if(close_system_pipe(&syspipe
, &pipe_rv
, pipe_callback
) == -1)
6640 if(done
) /* say we had a problem */
6641 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6642 _("Error piping message"));
6643 else if(resultfilename
){
6645 /* only display if no error */
6646 display_output_file(resultfilename
, "PIPE MESSAGE",
6648 fs_give((void **)&resultfilename
);
6652 q_status_message(SM_ORDER
, 0, 2, _("Pipe command completed"));
6658 /* else fall thru as if cancelled */
6661 cmd_cancelled("Pipe command");
6666 helper(h_common_pipe
, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE
);
6667 ps_global
->mangled_screen
= 1;
6670 case 2 : /* no place to escape to */
6671 case 4 : /* can't suspend */
6677 ps_global
->mangled_footer
= 1;
6678 if(MCMD_ISAGG(aopt
))
6679 restore_selected(msgmap
);
6685 /*----------------------------------------------------------------------
6686 Screen to offer list management commands contained in message
6688 Args: state -- pointer to struct holding a bunch of pine state
6689 msgmap -- table mapping msg nums to c-client sequence nums
6690 aopt -- aggregate options
6694 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6697 rfc2369_display(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
)
6700 char *h
, *hdrs
[MLCMD_COUNT
+ 1];
6701 long index_no
= mn_raw2m(msgmap
, msgno
);
6702 RFC2369_S data
[MLCMD_COUNT
];
6704 /* for each header field */
6705 if((h
= pine_fetchheader_lines(stream
, msgno
, NULL
, rfc2369_hdrs(hdrs
))) != NULL
){
6706 memset(&data
[0], 0, sizeof(RFC2369_S
) * MLCMD_COUNT
);
6707 if(rfc2369_parse_fields(h
, &data
[0])){
6710 if((explain
= list_mgmt_text(data
, index_no
)) != NULL
){
6711 list_mgmt_screen(explain
);
6712 ps_global
->mangled_screen
= 1;
6718 fs_give((void **) &h
);
6722 q_status_message1(SM_ORDER
, 0, 3,
6723 "Message %s contains no list management information",
6724 comatose(index_no
));
6729 list_mgmt_text(RFC2369_S
*data
, long int msgno
)
6732 int i
, j
, n
, fields
= 0;
6733 static char *rfc2369_intro1
=
6734 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6735 static char *rfc2369_intro2
[] = {
6736 N_(" has information associated with it "),
6737 N_("that explains how to participate in an email list. An "),
6738 N_("email list is represented by a single email address that "),
6739 N_("users sharing a common interest can send messages to (known "),
6740 N_("as posting) which are then redistributed to all members "),
6741 N_("of the list (sometimes after review by a moderator)."),
6742 N_("<P>List participation commands in this message include:"),
6746 if((store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) != NULL
){
6748 /* Insert introductory text */
6749 so_puts(store
, rfc2369_intro1
);
6751 so_puts(store
, comatose(msgno
));
6753 for(i
= 0; rfc2369_intro2
[i
]; i
++)
6754 so_puts(store
, _(rfc2369_intro2
[i
]));
6756 so_puts(store
, "<P>");
6757 for(i
= 0; i
< MLCMD_COUNT
; i
++)
6758 if(data
[i
].data
[0].value
6759 || data
[i
].data
[0].comment
6760 || data
[i
].data
[0].error
){
6762 so_puts(store
, "<UL>");
6764 so_puts(store
, "<LI>");
6766 (n
= (data
[i
].data
[1].value
|| data
[i
].data
[1].comment
))
6770 so_puts(store
, data
[i
].field
.description
);
6771 so_puts(store
, ". ");
6774 so_puts(store
, "<OL>");
6778 && (data
[i
].data
[j
].comment
6779 || data
[i
].data
[j
].value
6780 || data
[i
].data
[j
].error
);
6783 so_puts(store
, n
? "<P><LI>" : "<P>");
6785 if(data
[i
].data
[j
].comment
){
6787 _("With the provided comment:<P><BLOCKQUOTE>"));
6788 so_puts(store
, data
[i
].data
[j
].comment
);
6789 so_puts(store
, "</BLOCKQUOTE><P>");
6792 if(data
[i
].data
[j
].value
){
6794 && !strucmp(data
[i
].data
[j
].value
, "NO")){
6796 _("Posting is <EM>not</EM> allowed on this list"));
6799 so_puts(store
, "Select <A HREF=\"");
6800 so_puts(store
, data
[i
].data
[j
].value
);
6801 so_puts(store
, "\">HERE</A> to ");
6802 so_puts(store
, (data
[i
].field
.action
)
6803 ? data
[i
].field
.action
6807 so_puts(store
, ".");
6810 if(data
[i
].data
[j
].error
){
6811 so_puts(store
, "<P>Unfortunately, Alpine can not offer");
6812 so_puts(store
, " to take direct action based upon it");
6813 so_puts(store
, " because it was improperly formatted.");
6814 so_puts(store
, " The unrecognized data associated with");
6815 so_puts(store
, " the \"");
6816 so_puts(store
, data
[i
].field
.name
);
6817 so_puts(store
, "\" header field was:<P><BLOCKQUOTE>");
6818 so_puts(store
, data
[i
].data
[j
].error
);
6819 so_puts(store
, "</BLOCKQUOTE>");
6822 so_puts(store
, "<P>");
6826 so_puts(store
, "</OL>");
6830 so_puts(store
, "</UL>");
6832 so_puts(store
, "</BODY></HTML>");
6840 list_mgmt_screen(STORE_S
*html
)
6846 HANDLE_S
*handles
= NULL
;
6850 so_seek(html
, 0L, 0);
6851 gf_set_so_readc(&gc
, html
);
6853 init_handles(&handles
);
6855 if((store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) != NULL
){
6856 gf_set_so_writec(&pc
, store
);
6859 gf_link_filter(gf_html2plain
,
6860 gf_html2plain_opt(NULL
, ps_global
->ttyo
->screen_cols
,
6861 non_messageview_margin(), &handles
, NULL
, 0));
6863 error
= gf_pipe(gc
, pc
);
6865 gf_clear_so_writec(store
);
6870 memset(&sargs
, 0, sizeof(SCROLL_S
));
6871 sargs
.text
.text
= so_text(store
);
6872 sargs
.text
.src
= CharStar
;
6873 sargs
.text
.desc
= "list commands";
6874 sargs
.text
.handles
= handles
;
6876 sargs
.start
.on
= Offset
;
6877 sargs
.start
.loc
.offset
= offset
;
6880 sargs
.bar
.title
= _("MAIL LIST COMMANDS");
6881 sargs
.bar
.style
= MessageNumber
;
6882 sargs
.resize_exit
= 1;
6883 sargs
.help
.text
= h_special_list_commands
;
6884 sargs
.help
.title
= _("HELP FOR LIST COMMANDS");
6885 sargs
.keys
.menu
= &listmgr_keymenu
;
6886 setbitmap(sargs
.keys
.bitmap
);
6888 clrbitn(LM_TRY_KEY
, sargs
.keys
.bitmap
);
6889 clrbitn(LM_PREV_KEY
, sargs
.keys
.bitmap
);
6890 clrbitn(LM_NEXT_KEY
, sargs
.keys
.bitmap
);
6893 cmd
= scrolltool(&sargs
);
6894 offset
= sargs
.start
.loc
.offset
;
6900 free_handles(&handles
);
6901 gf_clear_so_readc(html
);
6903 while(cmd
== MC_RESIZE
);
6907 /*----------------------------------------------------------------------
6908 Prompt the user for the type of select desired
6910 NOTE: any and all functions that successfully exit the second
6911 switch() statement below (currently "select_*() functions"),
6912 *MUST* update the folder's MESSAGECACHE element's "searched"
6913 bits to reflect the search result. Functions using
6914 mail_search() get this for free, the others must update 'em
6917 Returns -1 if canceled without changing selection
6918 0 if selection may have changed
6921 aggregate_select(struct pine
*state
, MSGNO_S
*msgmap
, int q_line
, CmdWhere in_index
)
6923 long i
, diff
, old_tot
, msgno
, raw
;
6924 int q
= 0, rv
= 0, narrow
= 0, hidden
, ret
= -1;
6927 SEARCHSET
*limitsrch
= NULL
;
6929 extern MAILSTREAM
*mm_search_stream
;
6930 extern long mm_search_count
;
6932 hidden
= any_lflagged(msgmap
, MN_HIDE
) > 0L;
6933 mm_search_stream
= state
->mail_stream
;
6934 mm_search_count
= 0L;
6936 if(is_imap_stream(state
->mail_stream
) && XGMEXT1(state
->mail_stream
))
6937 sel_opts
= THRD_INDX() ? sel_opts6
: sel_opts5
;
6939 sel_opts
= THRD_INDX() ? sel_opts4
: sel_opts2
;
6942 sel_opts
[SEL_OPTS_THREAD
].ch
= SEL_OPTS_THREAD_CH
;
6945 if(is_imap_stream(state
->mail_stream
) && XGMEXT1(state
->mail_stream
)){
6946 sel_opts
[SEL_OPTS_THREAD
].ch
= SEL_OPTS_XGMEXT_CH
;
6947 sel_opts
[SEL_OPTS_THREAD
].rval
= sel_opts
[SEL_OPTS_XGMEXT
].rval
;
6948 sel_opts
[SEL_OPTS_THREAD
].name
= sel_opts
[SEL_OPTS_XGMEXT
].name
;
6949 sel_opts
[SEL_OPTS_THREAD
].label
= sel_opts
[SEL_OPTS_XGMEXT
].label
;
6950 sel_opts
[SEL_OPTS_XGMEXT
].ch
= -1;
6953 sel_opts
[SEL_OPTS_THREAD
].ch
= -1;
6956 if((old_tot
= any_lflagged(msgmap
, MN_SLCT
)) != 0){
6959 thrd
= fetch_thread(state
->mail_stream
,
6960 mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
6961 /* check if whole thread is selected or not */
6963 count_lflags_in_thread(state
->mail_stream
,thrd
,msgmap
,MN_SLCT
)
6965 count_lflags_in_thread(state
->mail_stream
,thrd
,msgmap
,MN_NONE
))
6968 sel_opts1
[1].label
= i
? N_("unselect Curthrd") : N_("select Curthrd");
6971 i
= get_lflag(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
6973 sel_opts1
[1].label
= i
? N_("unselect Cur") : N_("select Cur");
6976 sel_opts
+= 2; /* disable extra options */
6977 if(is_imap_stream(state
->mail_stream
) && XGMEXT1(state
->mail_stream
))
6978 q
= double_radio_buttons(_(sel_pmt1
), q_line
, sel_opts1
, 'c', 'x', NO_HELP
,
6981 q
= radio_buttons(_(sel_pmt1
), q_line
, sel_opts1
, 'c', 'x', NO_HELP
,
6984 case 'f' : /* flip selection */
6986 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++){
6988 q
= !get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
);
6989 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, q
);
6991 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, !q
);
6993 mn_reset_cur(msgmap
, msgno
= i
);
6999 case 'n' : /* narrow selection */
7001 case 'b' : /* broaden selection */
7002 q
= 0; /* offer criteria prompt */
7005 case 'c' : /* Un/Select Current */
7006 case 'a' : /* Unselect All */
7007 case 'x' : /* cancel */
7011 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
7012 "Unsupported Select option");
7019 if(is_imap_stream(state
->mail_stream
) && XGMEXT1(state
->mail_stream
))
7020 q
= double_radio_buttons(sel_pmt2
, q_line
, sel_opts
, 'c', 'x', NO_HELP
,
7023 q
= radio_buttons(sel_pmt2
, q_line
, sel_opts
, 'c', 'x', NO_HELP
,
7024 RB_NORM
|RB_RET_HELP
);
7027 helper(h_index_cmd_select
, _("HELP FOR SELECT"), HLPD_SIMPLE
);
7028 ps_global
->mangled_screen
= 1;
7036 * The purpose of this is to add the appropriate searchset to the
7037 * search so that the search can be limited to only looking at what
7038 * it needs to look at. That is, if we are narrowing then we only need
7039 * to look at messages which are already selected, and if we are
7040 * broadening, then we only need to look at messages which are not
7041 * yet selected. This routine will work whether or not
7042 * limiting_searchset properly limits the search set. In particular,
7043 * the searchset returned by limiting_searchset may include messages
7044 * which really shouldn't be included. We do that because a too-large
7045 * searchset will break some IMAP servers. It is even possible that it
7046 * becomes inefficient to send the whole set. If the select function
7047 * frees limitsrch, it should be sure to set it to NULL so we won't
7048 * try freeing it again here.
7050 limitsrch
= limiting_searchset(state
->mail_stream
, narrow
);
7053 * NOTE: See note about MESSAGECACHE "searched" bits above!
7056 case 'x': /* cancel */
7057 cmd_cancelled("Select command");
7060 case 'c' : /* select/unselect current */
7061 (void) select_by_current(state
, msgmap
, in_index
);
7065 case 'a' : /* select/unselect all */
7066 msgno
= any_lflagged(msgmap
, MN_SLCT
);
7067 diff
= (!msgno
) ? mn_get_total(msgmap
) : 0L;
7069 agg_select_all(state
->mail_stream
, msgmap
, &diff
,
7070 any_lflagged(msgmap
, MN_SLCT
) <= 0L);
7071 q_status_message4(SM_ORDER
,0,2,
7072 "%s%s message%s %sselected",
7073 msgno
? "" : "All ", comatose(diff
),
7074 plural(diff
), msgno
? "UN" : "");
7077 case 'n' : /* Select by Number */
7080 rv
= select_by_thrd_number(state
->mail_stream
, msgmap
, &limitsrch
);
7082 rv
= select_by_number(state
->mail_stream
, msgmap
, &limitsrch
);
7086 case 'd' : /* Select by Date */
7088 rv
= select_by_date(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
7092 case 't' : /* Text */
7094 rv
= select_by_text(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
7098 case 'z' : /* Size */
7100 rv
= select_by_size(state
->mail_stream
, &limitsrch
);
7103 case 's' : /* Status */
7105 rv
= select_by_status(state
->mail_stream
, &limitsrch
);
7108 case 'k' : /* Keyword */
7110 rv
= select_by_keyword(state
->mail_stream
, &limitsrch
);
7113 case 'r' : /* Rule */
7115 rv
= select_by_rule(state
->mail_stream
, &limitsrch
);
7118 case 'h' : /* Thread */
7120 rv
= select_by_thread(state
->mail_stream
, msgmap
, &limitsrch
);
7123 case 'g' : /* X-GM-EXT-1 */
7125 rv
= select_by_gm_content(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
), &limitsrch
);
7129 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
7130 "Unsupported Select option");
7135 mail_free_searchset(&limitsrch
);
7137 if(rv
) /* bad return value.. */
7138 return(ret
); /* error already displayed */
7140 if(narrow
) /* make sure something was selected */
7141 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++)
7142 if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
7143 && raw
<= state
->mail_stream
->nmsgs
7144 && (mc
= mail_elt(state
->mail_stream
, raw
)) && mc
->searched
){
7145 if(get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
))
7152 if(mm_search_count
){
7154 * loop thru all the messages, adjusting local flag bits
7155 * based on their "searched" bit...
7157 for(i
= 1L, msgno
= 0L; i
<= mn_get_total(msgmap
); i
++)
7159 /* turning OFF selectedness if the "searched" bit isn't lit. */
7160 if(get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
)){
7161 if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
7162 && raw
<= state
->mail_stream
->nmsgs
7163 && (mc
= mail_elt(state
->mail_stream
, raw
))
7166 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, 0);
7168 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, 1);
7170 /* adjust current message in case we unselect and hide it */
7171 else if(msgno
< mn_get_cur(msgmap
)
7173 || !get_lflag(state
->mail_stream
, msgmap
,
7178 else if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
7179 && raw
<= state
->mail_stream
->nmsgs
7180 && (mc
= mail_elt(state
->mail_stream
, raw
)) && mc
->searched
){
7181 /* turn ON selectedness if "searched" bit is lit. */
7182 if(!get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
)){
7184 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, 1);
7186 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, 0);
7190 /* if we're zoomed and the current message was unselected */
7192 && get_lflag(state
->mail_stream
,msgmap
,mn_get_cur(msgmap
),MN_HIDE
))
7193 mn_reset_cur(msgmap
, msgno
);
7198 q_status_message4(SM_ORDER
, 3, 3,
7199 "%s. %s message%s remain%s selected.",
7201 ? "No change resulted"
7202 : "No messages in intersection",
7203 comatose(old_tot
), plural(old_tot
),
7204 (old_tot
== 1L) ? "s" : "");
7206 q_status_message(SM_ORDER
, 3, 3,
7207 _("No change resulted. Matching messages already selected."));
7209 q_status_message1(SM_ORDER
| SM_DING
, 3, 3,
7210 _("Select failed. No %smessages selected."),
7211 old_tot
? _("additional ") : "");
7214 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
7215 "Select matched %ld message%s. %s %smessage%s %sselected.",
7216 (diff
> 0) ? diff
: old_tot
+ diff
,
7217 plural((diff
> 0) ? diff
: old_tot
+ diff
),
7218 comatose((diff
> 0) ? any_lflagged(msgmap
, MN_SLCT
) : -diff
),
7219 (diff
> 0) ? "total " : "",
7220 plural((diff
> 0) ? any_lflagged(msgmap
, MN_SLCT
) : -diff
),
7221 (diff
> 0) ? "" : "UN");
7222 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
7223 q_status_message(SM_ORDER
, 3, 3, tmp_20k_buf
);
7226 q_status_message2(SM_ORDER
, 3, 3, _("Select matched %s message%s!"),
7227 comatose(diff
), plural(diff
));
7233 /*----------------------------------------------------------------------
7234 Toggle the state of the current message
7236 Args: state -- pointer pine's state variables
7237 msgmap -- message collection to operate on
7238 in_index -- in the message index view
7239 Returns: TRUE if current marked selected, FALSE otw
7242 select_by_current(struct pine
*state
, MSGNO_S
*msgmap
, CmdWhere in_index
)
7245 int all_selected
= 0;
7246 unsigned long was
, tot
, rawno
;
7249 cur
= mn_get_cur(msgmap
);
7252 thrd
= fetch_thread(state
->mail_stream
, mn_m2raw(msgmap
, cur
));
7256 was
= count_lflags_in_thread(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
);
7257 tot
= count_lflags_in_thread(state
->mail_stream
, thrd
, msgmap
, MN_NONE
);
7262 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
, 0);
7263 if(any_lflagged(msgmap
, MN_HIDE
) > 0L){
7264 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_HIDE
, 1);
7266 * See if there's anything left to zoom on. If so,
7267 * pick an adjacent one for highlighting, else make
7268 * sure nothing is left hidden...
7270 if(any_lflagged(msgmap
, MN_SLCT
)){
7271 mn_inc_cur(state
->mail_stream
, msgmap
,
7272 (in_index
== View
&& THREADING()
7273 && sp_viewing_a_thread(state
->mail_stream
))
7275 : (in_index
== View
)
7276 ? MH_ANYTHD
: MH_NONE
);
7277 if(mn_get_cur(msgmap
) == cur
)
7278 mn_dec_cur(state
->mail_stream
, msgmap
,
7279 (in_index
== View
&& THREADING()
7280 && sp_viewing_a_thread(state
->mail_stream
))
7282 : (in_index
== View
)
7283 ? MH_ANYTHD
: MH_NONE
);
7285 else /* clear all hidden flags */
7286 (void) unzoom_index(state
, state
->mail_stream
, msgmap
);
7290 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
, 1);
7292 q_status_message3(SM_ORDER
, 0, 2, "%s message%s %sselected",
7293 comatose(all_selected
? was
: tot
-was
),
7294 plural(all_selected
? was
: tot
-was
),
7295 all_selected
? "UN" : "");
7297 /* collapsed thread */
7299 && ((rawno
= mn_m2raw(msgmap
, cur
)) != 0L)
7300 && ((thrd
= fetch_thread(state
->mail_stream
, rawno
)) != NULL
)
7301 && (thrd
&& thrd
->next
&& get_lflag(state
->mail_stream
, NULL
, rawno
, MN_COLL
))){
7303 * This doesn't work quite the same as the colon command works, but
7304 * it is arguably doing the correct thing. The difference is
7305 * that aggregate_select will zoom after selecting back where it
7306 * was called from, but selecting a thread with colon won't zoom.
7307 * Maybe it makes sense to zoom after a select but not after a colon
7308 * command even though they are very similar.
7310 thread_command(state
, state
->mail_stream
, msgmap
, ':', -FOOTER_ROWS(state
));
7314 get_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
)) != 0){ /* set? */
7315 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
, 0);
7316 if(any_lflagged(msgmap
, MN_HIDE
) > 0L){
7317 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_HIDE
, 1);
7319 * See if there's anything left to zoom on. If so,
7320 * pick an adjacent one for highlighting, else make
7321 * sure nothing is left hidden...
7323 if(any_lflagged(msgmap
, MN_SLCT
)){
7324 mn_inc_cur(state
->mail_stream
, msgmap
,
7325 (in_index
== View
&& THREADING()
7326 && sp_viewing_a_thread(state
->mail_stream
))
7328 : (in_index
== View
)
7329 ? MH_ANYTHD
: MH_NONE
);
7330 if(mn_get_cur(msgmap
) == cur
)
7331 mn_dec_cur(state
->mail_stream
, msgmap
,
7332 (in_index
== View
&& THREADING()
7333 && sp_viewing_a_thread(state
->mail_stream
))
7335 : (in_index
== View
)
7336 ? MH_ANYTHD
: MH_NONE
);
7338 else /* clear all hidden flags */
7339 (void) unzoom_index(state
, state
->mail_stream
, msgmap
);
7343 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
, 1);
7345 q_status_message2(SM_ORDER
, 0, 2, "Message %s %sselected",
7346 long2string(cur
), all_selected
? "UN" : "");
7350 return(!all_selected
);
7354 /*----------------------------------------------------------------------
7355 Prompt the user for the command to perform on selected messages
7357 Args: state -- pointer pine's state variables
7358 msgmap -- message collection to operate on
7359 q_line -- line on display to write prompts
7360 Returns: 1 if the selected messages are suitably commanded,
7361 0 if the choice to pick the command was declined
7365 apply_command(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
,
7366 UCS preloadkeystroke
, int flags
, int q_line
)
7368 int i
= 8, /* number of static entries in sel_opts3 */
7372 agg
= (flags
& AC_FROM_THREAD
) ? MCMD_AGG_2
: MCMD_AGG
;
7377 * To do this "right", we really ought to have access to the keymenu
7378 * here and change the typed command into a real command by running
7379 * it through menu_command. Then the switch below would be against
7380 * results from menu_command. If we did that we'd also pass the
7381 * results of menu_command in as preloadkeystroke instead of passing
7382 * the keystroke itself. But we don't have the keymenu handy,
7383 * so we have to fake it. The only complication that we run into
7384 * is that KEY_DEL is an escape sequence so we change a typed
7385 * KEY_DEL esc seq into the letter D.
7388 if(!preloadkeystroke
){
7389 if(F_ON(F_ENABLE_FLAG
,state
)){ /* flag? */
7390 sel_opts3
[i
].ch
= '*';
7391 sel_opts3
[i
].rval
= '*';
7392 sel_opts3
[i
].name
= "*";
7393 sel_opts3
[i
++].label
= N_("Flag");
7396 if(F_ON(F_ENABLE_PIPE
,state
)){ /* pipe? */
7397 sel_opts3
[i
].ch
= '|';
7398 sel_opts3
[i
].rval
= '|';
7399 sel_opts3
[i
].name
= "|";
7400 sel_opts3
[i
++].label
= N_("Pipe");
7403 if(F_ON(F_ENABLE_BOUNCE
,state
)){ /* bounce? */
7404 sel_opts3
[i
].ch
= 'b';
7405 sel_opts3
[i
].rval
= 'b';
7406 sel_opts3
[i
].name
= "B";
7407 sel_opts3
[i
++].label
= N_("Bounce");
7410 if(flags
& AC_FROM_THREAD
){
7411 if(flags
& (AC_COLL
| AC_EXPN
)){
7412 sel_opts3
[i
].ch
= '/';
7413 sel_opts3
[i
].rval
= '/';
7414 sel_opts3
[i
].name
= "/";
7415 sel_opts3
[i
++].label
= (flags
& AC_COLL
) ? N_("Collapse")
7419 sel_opts3
[i
].ch
= ';';
7420 sel_opts3
[i
].rval
= ';';
7421 sel_opts3
[i
].name
= ";";
7422 if(flags
& AC_UNSEL
)
7423 sel_opts3
[i
++].label
= N_("UnSelect");
7425 sel_opts3
[i
++].label
= N_("Select");
7428 if(F_ON(F_ENABLE_PRYNT
, state
)){ /* this one is invisible */
7429 sel_opts3
[i
].ch
= 'y';
7430 sel_opts3
[i
].rval
= '%';
7431 sel_opts3
[i
].name
= "";
7432 sel_opts3
[i
++].label
= "";
7435 if(!is_imap_stream(stream
) || LEVELUIDPLUS(stream
)){ /* expunge selected messages */
7436 sel_opts3
[i
].ch
= 'x';
7437 sel_opts3
[i
].rval
= 'x';
7438 sel_opts3
[i
].name
= "X";
7439 sel_opts3
[i
++].label
= N_("Expunge");
7442 if(nonempty_patterns(ROLE_DO_ROLES
, &pstate
) && first_pattern(&pstate
)){
7443 sel_opts3
[i
].ch
= '#';
7444 sel_opts3
[i
].rval
= '#';
7445 sel_opts3
[i
].name
= "#";
7446 sel_opts3
[i
++].label
= N_("Set Role");
7449 sel_opts3
[i
].ch
= KEY_DEL
; /* also invisible */
7450 sel_opts3
[i
].rval
= 'd';
7451 sel_opts3
[i
].name
= "";
7452 sel_opts3
[i
++].label
= "";
7454 sel_opts3
[i
].ch
= -1;
7456 snprintf(prompt
, sizeof(prompt
), "%s command : ",
7457 (flags
& AC_FROM_THREAD
) ? "THREAD" : "APPLY");
7458 prompt
[sizeof(prompt
)-1] = '\0';
7459 cmd
= double_radio_buttons(prompt
, q_line
, sel_opts3
, 'z', 'c', NO_HELP
,
7465 if(preloadkeystroke
== KEY_DEL
)
7468 if(preloadkeystroke
< 0x80 && isupper((int) preloadkeystroke
))
7469 cmd
= tolower((int) preloadkeystroke
);
7471 cmd
= (int) preloadkeystroke
; /* shouldn't happen */
7476 case 'd' : /* delete */
7477 we_cancel
= busy_cue(NULL
, NULL
, 1);
7478 rv
= cmd_delete(state
, msgmap
, agg
, NULL
); /* don't advance or offer "TAB" */
7483 case 'u' : /* undelete */
7484 we_cancel
= busy_cue(NULL
, NULL
, 1);
7485 rv
= cmd_undelete(state
, msgmap
, agg
);
7490 case 'r' : /* reply */
7491 rv
= cmd_reply(state
, msgmap
, agg
, NULL
);
7494 case 'f' : /* Forward */
7495 rv
= cmd_forward(state
, msgmap
, agg
, NULL
);
7498 case '%' : /* print */
7499 rv
= cmd_print(state
, msgmap
, agg
, MsgIndx
);
7502 case 't' : /* take address */
7503 rv
= cmd_take_addr(state
, msgmap
, agg
);
7506 case 's' : /* save */
7507 rv
= cmd_save(state
, stream
, msgmap
, agg
, MsgIndx
);
7510 case 'e' : /* export */
7511 rv
= cmd_export(state
, msgmap
, q_line
, agg
);
7514 case '|' : /* pipe */
7515 rv
= cmd_pipe(state
, msgmap
, agg
);
7518 case '*' : /* flag */
7519 we_cancel
= busy_cue(NULL
, NULL
, 1);
7520 rv
= cmd_flag(state
, msgmap
, agg
);
7525 case 'b' : /* bounce */
7526 rv
= cmd_bounce(state
, msgmap
, agg
, NULL
);
7530 collapse_or_expand(state
, stream
, msgmap
,
7531 F_ON(F_SLASH_COLL_ENTIRE
, ps_global
)
7533 : mn_get_cur(msgmap
));
7537 select_thread_stmp(state
, stream
, msgmap
);
7540 case 'x' : /* Expunge */
7541 rv
= cmd_expunge(state
, stream
, msgmap
, agg
);
7544 case 'c' : /* cancel */
7545 cmd_cancelled((flags
& AC_FROM_THREAD
) ? "Thread command"
7550 if(nonempty_patterns(ROLE_DO_ROLES
, &pstate
) && first_pattern(&pstate
)){
7551 static ESCKEY_S choose_role
[] = {
7552 {'r', 'r', "R", N_("Reply")},
7553 {'f', 'f', "F", N_("Forward")},
7554 {'b', 'b', "B", N_("Bounce")},
7558 ACTION_S
*role
= NULL
;
7560 action
= radio_buttons(_("Reply, Forward or Bounce using a role? "),
7561 -FOOTER_ROWS(state
), choose_role
,
7562 'r', 'x', h_role_aggregate
, RB_NORM
);
7563 if(action
== 'r' || action
== 'f' || action
== 'b'){
7564 void (*prev_screen
)(struct pine
*) = NULL
, (*redraw
)(void) = NULL
;
7566 redraw
= state
->redrawer
;
7567 state
->redrawer
= NULL
;
7568 prev_screen
= state
->prev_screen
;
7570 state
->next_screen
= SCREEN_FUN_NULL
;
7572 if(role_select_screen(state
, &role
,
7573 action
== 'f' ? MC_FORWARD
:
7574 action
== 'r' ? MC_REPLY
:
7575 action
== 'b' ? MC_BOUNCE
: 0) < 0){
7576 cmd_cancelled(action
== 'f' ? _("Forward") :
7577 action
== 'r' ? _("Reply") : _("Bounce"));
7578 state
->next_screen
= prev_screen
;
7579 state
->redrawer
= redraw
;
7580 state
->mangled_screen
= 1;
7584 role
= combine_inherited_role(role
);
7586 role
= (ACTION_S
*) fs_get(sizeof(*role
));
7587 memset((void *) role
, 0, sizeof(*role
));
7588 role
->nick
= cpystr("Default Role");
7591 state
->redrawer
= NULL
;
7594 (void) cmd_reply(state
, msgmap
, agg
, role
);
7598 (void) cmd_forward(state
, msgmap
, agg
, role
);
7602 (void) cmd_bounce(state
, msgmap
, agg
, role
);
7612 state
->next_screen
= prev_screen
;
7613 state
->redrawer
= redraw
;
7614 state
->mangled_screen
= 1;
7620 case 'z' : /* default */
7621 q_status_message(SM_INFO
, 0, 2,
7622 "Cancelled, there is no default command");
7634 * Select by message number ranges.
7635 * Sets searched bits in mail_elts
7637 * Args limitsrch -- limit search to this searchset
7639 * Returns 0 on success.
7642 select_by_number(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**limitsrch
)
7646 char number1
[16], number2
[16], numbers
[80], *p
, *t
;
7651 ps_global
->mangled_footer
= 1;
7654 int flags
= OE_APPEND_CURRENT
;
7656 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
7657 sizeof(numbers
), _(select_num
), NULL
, help
, &flags
);
7662 help
= (help
== NO_HELP
) ? h_select_by_num
: NO_HELP
;
7666 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
7667 if(!isspace((unsigned char)*p
))
7672 if(r
== 1 || numbers
[0] == '\0'){
7673 cmd_cancelled("Selection by number");
7680 for(n1
= 1; n1
<= stream
->nmsgs
; n1
++)
7681 if((mc
= mail_elt(stream
, n1
)) != NULL
)
7682 mc
->searched
= 0; /* clear searched bits */
7684 for(p
= numbers
; *p
; p
++){
7686 while(*p
&& isdigit((unsigned char)*p
))
7692 if(number1
[0] == '\0'){
7694 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7695 _("Invalid number range, missing number before \"-\": %s"),
7699 else if(!strucmp("end", p
)){
7703 else if(!strucmp("$", p
)){
7712 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7713 _("Invalid message number: %s"), numbers
);
7719 n1
= mn_get_total(msgmap
);
7721 n1
= mn_get_cur(msgmap
);
7722 else if((n1
= atol(number1
)) < 1L || n1
> mn_get_total(msgmap
)){
7723 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7724 _("\"%s\" out of message number range"),
7731 while(*++p
&& isdigit((unsigned char)*p
))
7737 if(number2
[0] == '\0'){
7738 if(!strucmp("end", p
)){
7742 else if(!strucmp(p
, "$")){
7751 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7752 _("Invalid number range, missing number after \"-\": %s"),
7759 n2
= mn_get_total(msgmap
);
7761 n2
= mn_get_cur(msgmap
);
7762 else if((n2
= atol(number2
)) < 1L || n2
> mn_get_total(msgmap
)){
7763 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7764 _("\"%s\" out of message number range"),
7772 strncpy(t
, long2string(n1
), sizeof(t
));
7773 t
[sizeof(t
)-1] = '\0';
7774 q_status_message2(SM_ORDER
| SM_DING
, 0, 2,
7775 _("Invalid reverse message number range: %s-%s"),
7776 t
, long2string(n2
));
7780 for(;n1
<= n2
; n1
++){
7781 raw
= mn_m2raw(msgmap
, n1
);
7783 && (!(limitsrch
&& *limitsrch
)
7784 || in_searchset(*limitsrch
, (unsigned long) raw
)))
7785 mm_searched(stream
, raw
);
7789 raw
= mn_m2raw(msgmap
, n1
);
7791 && (!(limitsrch
&& *limitsrch
)
7792 || in_searchset(*limitsrch
, (unsigned long) raw
)))
7793 mm_searched(stream
, raw
);
7805 * Select by thread number ranges.
7806 * Sets searched bits in mail_elts
7808 * Args limitsrch -- limit search to this searchset
7810 * Returns 0 on success.
7813 select_by_thrd_number(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**msgset
)
7817 char number1
[16], number2
[16], numbers
[80], *p
, *t
;
7819 PINETHRD_S
*thrd
= NULL
, *th
;
7823 ps_global
->mangled_footer
= 1;
7826 int flags
= OE_APPEND_CURRENT
;
7828 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
7829 sizeof(numbers
), _(select_num
), NULL
, help
, &flags
);
7834 help
= (help
== NO_HELP
) ? h_select_by_thrdnum
: NO_HELP
;
7838 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
7839 if(!isspace((unsigned char)*p
))
7844 if(r
== 1 || numbers
[0] == '\0'){
7845 cmd_cancelled("Selection by number");
7852 for(n1
= 1; n1
<= stream
->nmsgs
; n1
++)
7853 if((mc
= mail_elt(stream
, n1
)) != NULL
)
7854 mc
->searched
= 0; /* clear searched bits */
7856 for(p
= numbers
; *p
; p
++){
7858 while(*p
&& isdigit((unsigned char)*p
))
7864 if(number1
[0] == '\0'){
7866 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7867 _("Invalid number range, missing number before \"-\": %s"),
7871 else if(!strucmp("end", p
)){
7875 else if(!strucmp(p
, "$")){
7884 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7885 _("Invalid thread number: %s"), numbers
);
7891 n1
= msgmap
->max_thrdno
;
7893 th
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
7896 else if((n1
= atol(number1
)) < 1L || n1
> msgmap
->max_thrdno
){
7897 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7898 _("\"%s\" out of thread number range"),
7906 while(*++p
&& isdigit((unsigned char)*p
))
7912 if(number2
[0] == '\0'){
7913 if(!strucmp("end", p
)){
7917 else if(!strucmp("$", p
)){
7926 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7927 _("Invalid number range, missing number after \"-\": %s"),
7934 n2
= msgmap
->max_thrdno
;
7936 th
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
7939 else if((n2
= atol(number2
)) < 1L || n2
> msgmap
->max_thrdno
){
7940 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7941 _("\"%s\" out of thread number range"),
7949 strncpy(t
, long2string(n1
), sizeof(t
));
7950 t
[sizeof(t
)-1] = '\0';
7951 q_status_message2(SM_ORDER
| SM_DING
, 0, 2,
7952 _("Invalid reverse message number range: %s-%s"),
7953 t
, long2string(n2
));
7957 for(;n1
<= n2
; n1
++){
7958 thrd
= find_thread_by_number(stream
, msgmap
, n1
, thrd
);
7961 set_search_bit_for_thread(stream
, thrd
, msgset
);
7965 thrd
= find_thread_by_number(stream
, msgmap
, n1
, NULL
);
7968 set_search_bit_for_thread(stream
, thrd
, msgset
);
7980 * Select by message dates.
7981 * Sets searched bits in mail_elts
7983 * Args limitsrch -- limit search to this searchset
7985 * Returns 0 on success.
7988 select_by_date(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
, SEARCHSET
**limitsrch
)
7990 int r
, we_cancel
= 0, when
= 0;
7991 char date
[100], defdate
[100], prompt
[128];
7992 time_t seldate
= time(0);
7993 struct tm
*seldate_tm
;
7996 static struct _tense
{
8001 {"were ", "SENT SINCE", " (inclusive)"},
8002 {"were ", "SENT BEFORE", " (exclusive)"},
8003 {"were ", "SENT ON", "" },
8004 {"", "ARRIVED SINCE", " (inclusive)"},
8005 {"", "ARRIVED BEFORE", " (exclusive)"},
8006 {"", "ARRIVED ON", "" }
8010 ps_global
->mangled_footer
= 1;
8014 * If talking to an old server, default to SINCE instead of
8015 * SENTSINCE, which was added later.
8017 if(is_imap_stream(stream
) && !modern_imap_stream(stream
))
8021 int flags
= OE_APPEND_CURRENT
;
8023 seldate_tm
= localtime(&seldate
);
8024 snprintf(defdate
, sizeof(defdate
), "%.2d-%.4s-%.4d", seldate_tm
->tm_mday
,
8025 month_abbrev(seldate_tm
->tm_mon
+ 1),
8026 seldate_tm
->tm_year
+ 1900);
8027 defdate
[sizeof(defdate
)-1] = '\0';
8028 snprintf(prompt
,sizeof(prompt
),"Select messages which %s%s%s [%s]: ",
8029 tense
[when
].preamble
, tense
[when
].range
,
8030 tense
[when
].scope
, defdate
);
8031 prompt
[sizeof(prompt
)-1] = '\0';
8032 r
= optionally_enter(date
,-FOOTER_ROWS(ps_global
), 0, sizeof(date
),
8033 prompt
, sel_date_opt
, help
, &flags
);
8036 cmd_cancelled("Selection by date");
8040 help
= (help
== NO_HELP
) ? h_select_date
: NO_HELP
;
8051 if(stream
&& (rawno
= mn_m2raw(msgmap
, msgno
)) > 0L
8052 && rawno
<= stream
->nmsgs
8053 && (mc
= mail_elt(stream
, rawno
))){
8055 /* cache not filled in yet? */
8059 if(stream
->dtb
&& stream
->dtb
->flags
& DR_NEWS
){
8061 ulong2string(mail_uid(stream
, rawno
)),
8063 seq
[sizeof(seq
)-1] = '\0';
8064 mail_fetch_overview(stream
, seq
, NULL
);
8067 strncpy(seq
, long2string(rawno
),
8069 seq
[sizeof(seq
)-1] = '\0';
8070 mail_fetch_fast(stream
, seq
, 0L);
8074 /* mail_date returns fixed field width date */
8075 mail_date(date
, mc
);
8082 case 12 : /* set default to PREVIOUS day */
8086 case 13 : /* set default to NEXT day */
8091 when
= (when
+1) % (sizeof(tense
) / sizeof(struct _tense
));
8098 removing_leading_white_space(date
);
8099 removing_trailing_white_space(date
);
8101 strncpy(date
, defdate
, sizeof(date
));
8102 date
[sizeof(date
)-1] = '\0';
8108 if((pgm
= mail_newsearchpgm()) != NULL
){
8110 short converted_date
;
8112 if(mail_parse_date(&elt
, (unsigned char *) date
)){
8113 converted_date
= mail_shortdate(elt
.year
, elt
.month
, elt
.day
);
8117 pgm
->sentsince
= converted_date
;
8120 pgm
->sentbefore
= converted_date
;
8123 pgm
->senton
= converted_date
;
8126 pgm
->since
= converted_date
;
8129 pgm
->before
= converted_date
;
8132 pgm
->on
= converted_date
;
8136 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
8138 if(ps_global
&& ps_global
->ttyo
){
8139 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8140 ps_global
->mangled_footer
= 1;
8143 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8145 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
8150 /* we know this was freed in mail_search, let caller know */
8155 mail_free_searchpgm(&pgm
);
8156 q_status_message1(SM_ORDER
, 3, 3,
8157 _("Invalid date entered: %s"), date
);
8166 * Select by searching in message headers or body
8167 * using the x-gm-ext-1 capaility. This function
8168 * reads the input from the user and passes it to
8169 * the server directly. We need a x-gm-ext-1 variable
8170 * in the search pgm to carry this information to the
8173 * Sets searched bits in mail_elts
8175 * Args limitsrch -- limit search to this searchset
8177 * Returns 0 on success.
8180 select_by_gm_content(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
, SEARCHSET
**limitsrch
)
8182 int r
, we_cancel
= 0, rv
;
8184 char namehdr
[MAILTMPLEN
];
8188 ps_global
->mangled_footer
= 1;
8189 ekey
[0].ch
= ekey
[1].ch
= ekey
[2].ch
= ekey
[3].ch
= -1;
8191 strncpy(tmp
, sel_x_gm_ext
, sizeof(tmp
)-1);
8192 tmp
[sizeof(tmp
)-1] = '\0';
8198 int flags
= OE_APPEND_CURRENT
;
8200 r
= optionally_enter(namehdr
, -FOOTER_ROWS(ps_global
), 0,
8201 sizeof(namehdr
), tmp
, ekey
, help
, &flags
);
8207 help
= (help
== NO_HELP
) ? h_select_by_gm_content
: NO_HELP
;
8212 cmd_cancelled("Selection by content");
8216 removing_leading_white_space(namehdr
);
8218 if ((namehdr
[0] != '\0')
8219 && isspace((unsigned char) namehdr
[strlen(namehdr
) - 1]))
8220 removing_trailing_white_space(namehdr
);
8222 if (namehdr
[0] != '\0')
8226 if(ps_global
&& ps_global
->ttyo
){
8227 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8228 ps_global
->mangled_footer
= 1;
8231 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8233 rv
= agg_text_select(stream
, msgmap
, 'g', namehdr
, 0, 0, NULL
, "utf-8", limitsrch
);
8241 * Select by searching in message headers or body.
8242 * Sets searched bits in mail_elts
8244 * Args limitsrch -- limit search to this searchset
8246 * Returns 0 on success.
8249 select_by_text(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
, SEARCHSET
**limitsrch
)
8251 int r
, ku
, type
, we_cancel
= 0, flags
, rv
, ekeyi
= 0;
8252 int not = 0, me
= 0;
8253 char sstring
[80], savedsstring
[80], tmp
[128];
8254 char *p
, *sval
= NULL
;
8255 char buftmp
[MAILTMPLEN
], namehdr
[80];
8257 ENVELOPE
*env
= NULL
;
8259 unsigned flagsforhist
= 0;
8260 static HISTORY_S
*history
= NULL
;
8261 static char *recip
= "RECIPIENTS";
8262 static char *partic
= "PARTICIPANTS";
8263 static char *match_me
= N_("[Match_My_Addresses]");
8264 static char *dont_match_me
= N_("[Don't_Match_My_Addresses]");
8266 ps_global
->mangled_footer
= 1;
8267 savedsstring
[0] = '\0';
8268 ekey
[0].ch
= ekey
[1].ch
= ekey
[2].ch
= ekey
[3].ch
= -1;
8271 type
= radio_buttons(not ? _(sel_text_not
) : _(sel_text
),
8272 -FOOTER_ROWS(ps_global
), sel_text_opt
,
8273 's', 'x', NO_HELP
, RB_NORM
|RB_RET_HELP
);
8278 helper(h_select_text
, "HELP FOR SELECT BASED ON CONTENTS",
8280 ps_global
->mangled_screen
= 1;
8287 * prepare some friendly defaults...
8290 case 't' : /* address fields, offer To or From */
8295 sval
= (type
== 't') ? "TO" :
8296 (type
== 'f') ? "FROM" :
8297 (type
== 'c') ? "CC" :
8298 (type
== 'r') ? recip
: partic
;
8299 ekey
[ekeyi
].ch
= ctrl('T');
8300 ekey
[ekeyi
].name
= "^T";
8301 ekey
[ekeyi
].rval
= 10;
8302 /* TRANSLATORS: use Current To Address */
8303 ekey
[ekeyi
++].label
= N_("Cur To");
8304 ekey
[ekeyi
].ch
= ctrl('R');
8305 ekey
[ekeyi
].name
= "^R";
8306 ekey
[ekeyi
].rval
= 11;
8307 /* TRANSLATORS: use Current From Address */
8308 ekey
[ekeyi
++].label
= N_("Cur From");
8309 ekey
[ekeyi
].ch
= ctrl('W');
8310 ekey
[ekeyi
].name
= "^W";
8311 ekey
[ekeyi
].rval
= 12;
8312 /* TRANSLATORS: use Current Cc Address */
8313 ekey
[ekeyi
++].label
= N_("Cur Cc");
8314 ekey
[ekeyi
].ch
= ctrl('Y');
8315 ekey
[ekeyi
].name
= "^Y";
8316 ekey
[ekeyi
].rval
= 13;
8317 /* TRANSLATORS: Match Me means match my address */
8318 ekey
[ekeyi
++].label
= N_("Match Me");
8320 ekey
[ekeyi
].name
= "";
8321 ekey
[ekeyi
].rval
= 0;
8322 ekey
[ekeyi
++].label
= "";
8327 ekey
[ekeyi
].ch
= ctrl('X');
8328 ekey
[ekeyi
].name
= "^X";
8329 ekey
[ekeyi
].rval
= 14;
8330 /* TRANSLATORS: use Current Subject */
8331 ekey
[ekeyi
++].label
= N_("Cur Subject");
8343 strncpy(tmp
, "Name of HEADER to match : ", sizeof(tmp
)-1);
8344 tmp
[sizeof(tmp
)-1] = '\0';
8345 flags
= OE_APPEND_CURRENT
;
8351 r
= optionally_enter(namehdr
, -FOOTER_ROWS(ps_global
), 0,
8352 sizeof(namehdr
), tmp
, ekey
, NO_HELP
, &flags
);
8354 cmd_cancelled("Selection by text");
8357 removing_leading_white_space(namehdr
);
8359 while ((namehdr
[0] != '\0') && /* remove trailing ":" */
8360 (namehdr
[strlen(namehdr
) - 1] == ':'))
8361 namehdr
[strlen(namehdr
) - 1] = '\0';
8362 if ((namehdr
[0] != '\0')
8363 && isspace((unsigned char) namehdr
[strlen(namehdr
) - 1]))
8364 removing_trailing_white_space(namehdr
);
8368 if (strchr(namehdr
,' ') || strchr(namehdr
,'\t') ||
8369 strchr(namehdr
,':'))
8371 if (namehdr
[0] == '\0')
8381 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
8385 ekey
[ekeyi
].ch
= KEY_UP
;
8386 ekey
[ekeyi
].rval
= 30;
8387 ekey
[ekeyi
].name
= "";
8389 ekey
[ekeyi
++].label
= "";
8391 ekey
[ekeyi
].ch
= KEY_DOWN
;
8392 ekey
[ekeyi
].rval
= 31;
8393 ekey
[ekeyi
].name
= "";
8394 ekey
[ekeyi
++].label
= "";
8396 ekey
[ekeyi
].ch
= -1;
8400 init_hist(&history
, HISTSIZE
);
8402 if(ekey
[0].ch
> -1 && msgno
> 0L
8403 && !(env
=pine_mail_fetchstructure(stream
,mn_m2raw(msgmap
,msgno
),
8412 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8413 snprintf(tmp
, sizeof(tmp
), "String in message %s to NOT match : ", sval
);
8415 snprintf(tmp
, sizeof(tmp
), "String in message %s to match : ", sval
);
8417 if(items_in_hist(history
) > 0){
8418 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
8419 ekey
[ku
].label
= HISTORY_KEYLABEL
;
8420 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
8421 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
8425 ekey
[ku
].label
= "";
8426 ekey
[ku
+1].name
= "";
8427 ekey
[ku
+1].label
= "";
8430 flags
= OE_APPEND_CURRENT
| OE_KEEP_TRAILING_SPACE
;
8431 r
= optionally_enter(sstring
, -FOOTER_ROWS(ps_global
), 0,
8432 79, tmp
, ekey
, help
, &flags
);
8434 if(me
&& r
== 0 && ((!not && strcmp(sstring
, _(match_me
))) || (not && strcmp(sstring
, _(dont_match_me
)))))
8439 help
= (help
== NO_HELP
)
8441 ? ((type
== 'f') ? h_select_txt_not_from
8442 : (type
== 't') ? h_select_txt_not_to
8443 : (type
== 'c') ? h_select_txt_not_cc
8444 : (type
== 's') ? h_select_txt_not_subj
8445 : (type
== 'a') ? h_select_txt_not_all
8446 : (type
== 'r') ? h_select_txt_not_recip
8447 : (type
== 'p') ? h_select_txt_not_partic
8448 : (type
== 'b') ? h_select_txt_not_body
8450 : ((type
== 'f') ? h_select_txt_from
8451 : (type
== 't') ? h_select_txt_to
8452 : (type
== 'c') ? h_select_txt_cc
8453 : (type
== 's') ? h_select_txt_subj
8454 : (type
== 'a') ? h_select_txt_all
8455 : (type
== 'r') ? h_select_txt_recip
8456 : (type
== 'p') ? h_select_txt_partic
8457 : (type
== 'b') ? h_select_txt_body
8464 case 10 : /* To: default */
8465 if(env
&& env
->to
&& env
->to
->mailbox
){
8466 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->to
->mailbox
,
8467 env
->to
->host
? "@" : "",
8468 env
->to
->host
? env
->to
->host
: "");
8469 sstring
[sizeof(sstring
)-1] = '\0';
8473 case 11 : /* From: default */
8474 if(env
&& env
->from
&& env
->from
->mailbox
){
8475 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->from
->mailbox
,
8476 env
->from
->host
? "@" : "",
8477 env
->from
->host
? env
->from
->host
: "");
8478 sstring
[sizeof(sstring
)-1] = '\0';
8482 case 12 : /* Cc: default */
8483 if(env
&& env
->cc
&& env
->cc
->mailbox
){
8484 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->cc
->mailbox
,
8485 env
->cc
->host
? "@" : "",
8486 env
->cc
->host
? env
->cc
->host
: "");
8487 sstring
[sizeof(sstring
)-1] = '\0';
8491 case 13 : /* Match my addresses */
8493 snprintf(sstring
, sizeof(sstring
), "%s", not ? _(dont_match_me
) : _(match_me
));
8496 case 14 : /* Subject: default */
8497 if(env
&& env
->subject
&& env
->subject
[0]){
8500 snprintf(buftmp
, sizeof(buftmp
), "%.75s", env
->subject
);
8501 buftmp
[sizeof(buftmp
)-1] = '\0';
8502 q
= (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf
,
8503 SIZEOF_20KBUF
, buftmp
);
8504 if(q
!= env
->subject
){
8505 snprintf(savedsstring
, sizeof(savedsstring
), "%.70s", q
);
8506 savedsstring
[sizeof(savedsstring
)-1] = '\0';
8509 snprintf(sstring
, sizeof(sstring
), "%s", q
);
8510 sstring
[sizeof(sstring
)-1] = '\0';
8516 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8517 if((p
= get_prev_hist(history
, sstring
, flagsforhist
, NULL
)) != NULL
){
8518 strncpy(sstring
, p
, sizeof(sstring
));
8519 sstring
[sizeof(sstring
)-1] = '\0';
8520 if(history
->hist
[history
->curindex
]){
8521 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
8522 not = (flagsforhist
& 0x1) ? 1 : 0;
8523 me
= (flagsforhist
& 0x2) ? 1 : 0;
8532 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8533 if((p
= get_next_hist(history
, sstring
, flagsforhist
, NULL
)) != NULL
){
8534 strncpy(sstring
, p
, sizeof(sstring
));
8535 sstring
[sizeof(sstring
)-1] = '\0';
8536 if(history
->hist
[history
->curindex
]){
8537 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
8538 not = (flagsforhist
& 0x1) ? 1 : 0;
8539 me
= (flagsforhist
& 0x2) ? 1 : 0;
8551 if(r
== 1 || sstring
[0] == '\0')
8558 if(type
== 'x' || r
== 'x'){
8559 cmd_cancelled("Selection by text");
8563 if(ps_global
&& ps_global
->ttyo
){
8564 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8565 ps_global
->mangled_footer
= 1;
8568 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8570 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8571 save_hist(history
, sstring
, flagsforhist
, NULL
);
8573 rv
= agg_text_select(stream
, msgmap
, type
, namehdr
, not, me
, sstring
, "utf-8", limitsrch
);
8582 * Select by message size.
8583 * Sets searched bits in mail_elts
8585 * Args limitsrch -- limit search to this searchset
8587 * Returns 0 on success.
8590 select_by_size(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8592 int r
, large
= 1, we_cancel
= 0;
8593 unsigned long n
, mult
= 1L, numerator
= 0L, divisor
= 1L;
8594 char size
[16], numbers
[80], *p
, *t
;
8597 long flags
= (SE_NOPREFETCH
| SE_FREE
);
8600 ps_global
->mangled_footer
= 1;
8604 int flgs
= OE_APPEND_CURRENT
;
8606 sel_size_opt
[1].label
= large
? sel_size_smaller
: sel_size_larger
;
8608 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
8609 sizeof(numbers
), large
? _(select_size_larger_msg
)
8610 : _(select_size_smaller_msg
),
8611 sel_size_opt
, help
, &flgs
);
8621 help
= (help
== NO_HELP
) ? (large
? h_select_by_larger_size
8622 : h_select_by_smaller_size
)
8627 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
8628 if(!isspace((unsigned char)*p
))
8633 if(r
== 1 || numbers
[0] == '\0'){
8634 cmd_cancelled("Selection by size");
8641 if(numbers
[0] == '-'){
8642 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
8643 _("Invalid size entered: %s"), numbers
);
8650 while(*p
&& isdigit((unsigned char)*p
))
8655 if(size
[0] == '\0' && *p
== '.' && isdigit(*(p
+1))){
8660 if(size
[0] == '\0'){
8661 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
8662 _("Invalid size entered: %s"), numbers
);
8666 n
= strtoul(size
, (char **)NULL
, 10);
8671 * We probably ought to just use atof() to convert 1.1 into a
8672 * double, but since we haven't used atof() anywhere else I'm
8673 * reluctant to use it because of portability concerns.
8677 while(*p
&& isdigit((unsigned char)*p
)){
8685 numerator
= strtoul(size
, (char **)NULL
, 10);
8705 n
= n
* mult
+ (numerator
* mult
) / divisor
;
8707 pgm
= mail_newsearchpgm();
8713 if(is_imap_stream(stream
) && !modern_imap_stream(stream
))
8714 flags
|= SE_NOSERVER
;
8716 if(ps_global
&& ps_global
->ttyo
){
8717 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8718 ps_global
->mangled_footer
= 1;
8721 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8723 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
8724 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
8725 /* we know this was freed in mail_search, let caller know */
8737 * visible_searchset -- return c-client search set unEXLDed
8741 visible_searchset(MAILSTREAM
*stream
, MSGNO_S
*msgmap
)
8744 SEARCHSET
*full_set
= NULL
, **set
;
8747 * If we're talking to anything other than a server older than
8748 * imap 4rev1, build a searchset otherwise it'll choke.
8750 if(!(is_imap_stream(stream
) && !modern_imap_stream(stream
))){
8751 if(any_lflagged(msgmap
, MN_EXLD
)){
8752 for(n
= 1L, set
= &full_set
, run
= 0L; n
<= stream
->nmsgs
; n
++)
8753 if(get_lflag(stream
, NULL
, n
, MN_EXLD
)){
8754 if(run
){ /* previous NOT excluded? */
8756 (*set
)->last
= n
- 1L;
8758 set
= &(*set
)->next
;
8762 else if(run
++){ /* next in run */
8765 else{ /* start of run */
8766 *set
= mail_newsearchset();
8771 full_set
= mail_newsearchset();
8772 full_set
->first
= 1L;
8773 full_set
->last
= stream
->nmsgs
;
8782 * Select by message status bits.
8783 * Sets searched bits in mail_elts
8785 * Args limitsrch -- limit search to this searchset
8787 * Returns 0 on success.
8790 select_by_status(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8792 int s
, not = 0, we_cancel
= 0, rv
;
8795 s
= radio_buttons((not) ? _(sel_flag_not
) : _(sel_flag
),
8796 -FOOTER_ROWS(ps_global
), sel_flag_opt
, '*', 'x',
8797 NO_HELP
, RB_NORM
|RB_RET_HELP
);
8800 cmd_cancelled("Selection by status");
8804 helper(h_select_status
, _("HELP FOR SELECT BASED ON STATUS"),
8806 ps_global
->mangled_screen
= 1;
8814 if(ps_global
&& ps_global
->ttyo
){
8815 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8816 ps_global
->mangled_footer
= 1;
8819 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8820 rv
= agg_flag_select(stream
, not, s
, limitsrch
);
8829 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8830 * Sets searched bits in mail_elts
8832 * Args limitsrch -- limit search to this searchset
8834 * Returns 0 on success.
8837 select_by_rule(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8839 char rulenick
[1000], *nick
;
8841 int r
, not = 0, we_cancel
= 0, rflags
= ROLE_DO_SRCH
8849 ps_global
->mangled_footer
= 1;
8854 oe_flags
= OE_APPEND_CURRENT
;
8855 r
= optionally_enter(rulenick
, -FOOTER_ROWS(ps_global
), 0,
8857 not ? _("Rule to NOT match: ")
8858 : _("Rule to match: "),
8859 sel_key_opt
, NO_HELP
, &oe_flags
);
8862 /* select rulenick from a list */
8863 if((nick
=choose_a_rule(rflags
)) != NULL
){
8864 strncpy(rulenick
, nick
, sizeof(rulenick
)-1);
8865 rulenick
[sizeof(rulenick
)-1] = '\0';
8866 fs_give((void **) &nick
);
8875 helper(h_select_rule
, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE
);
8876 ps_global
->mangled_screen
= 1;
8879 cmd_cancelled("Selection by Rule");
8883 removing_leading_and_trailing_white_space(rulenick
);
8885 }while(r
== 3 || r
== 4 || r
== '!');
8889 * The approach of requiring a nickname instead of just allowing the
8890 * user to select from the list of rules has the drawback that a rule
8891 * may not have a nickname, or there may be more than one rule with
8892 * the same nickname. However, it has the benefit of allowing the user
8893 * to type in the nickname and, most importantly, allows us to set
8894 * up the ! (not). We could incorporate the ! into the selection
8895 * screen, but this is easier and also allows the typing of nicks.
8896 * User can just set up nicknames if they want to use this feature.
8898 patgrp
= nick_to_patgrp(rulenick
, rflags
);
8901 if(ps_global
&& ps_global
->ttyo
){
8902 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8903 ps_global
->mangled_footer
= 1;
8906 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8907 match_pattern(patgrp
, stream
, limitsrch
? *limitsrch
: 0, NULL
,
8909 (not ? MP_NOT
: 0) | SE_NOPREFETCH
);
8910 free_patgrp(&patgrp
);
8915 if(limitsrch
&& *limitsrch
){
8916 mail_free_searchset(limitsrch
);
8925 * Allow user to choose a rule from their list of rules.
8927 * Returns an allocated rule nickname on success, NULL otherwise.
8930 choose_a_rule(int rflags
)
8932 char *choice
= NULL
;
8933 char **rule_list
, **lp
;
8938 if(!(nonempty_patterns(rflags
, &pstate
) && first_pattern(&pstate
))){
8939 q_status_message(SM_ORDER
, 3, 3,
8940 _("No rules available. Use Setup/Rules to add some."));
8945 * Build a list of rules to choose from.
8948 for(pat
= first_pattern(&pstate
); pat
; pat
= next_pattern(&pstate
))
8952 q_status_message(SM_ORDER
, 3, 4, _("No rules defined, use Setup/Rules"));
8956 lp
= rule_list
= (char **) fs_get((cnt
+ 1) * sizeof(*rule_list
));
8957 memset(rule_list
, 0, (cnt
+1) * sizeof(*rule_list
));
8959 for(pat
= first_pattern(&pstate
); pat
; pat
= next_pattern(&pstate
))
8960 *lp
++ = cpystr((pat
->patgrp
&& pat
->patgrp
->nick
)
8961 ? pat
->patgrp
->nick
: "?");
8963 /* TRANSLATORS: SELECT A RULE is a screen title
8964 TRANSLATORS: Print something1 using something2.
8965 "rules" is something1 */
8966 choice
= choose_item_from_list(rule_list
, NULL
, _("SELECT A RULE"),
8967 _("rules"), h_select_rule_screen
,
8968 _("HELP FOR SELECTING A RULE NICKNAME"), NULL
);
8971 q_status_message(SM_ORDER
, 1, 4, "No choice");
8973 free_list_array(&rule_list
);
8980 * Select by current thread.
8981 * Sets searched bits in mail_elts for this entire thread
8983 * Args limitsrch -- limit search to this searchset
8985 * Returns 0 on success.
8988 select_by_thread(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**limitsrch
)
8991 PINETHRD_S
*thrd
= NULL
;
8998 for(n
= 1L; n
<= stream
->nmsgs
; n
++)
8999 if((mc
= mail_elt(stream
, n
)) != NULL
)
9000 mc
->searched
= 0; /* clear searched bits */
9002 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
9003 if(thrd
&& thrd
->top
&& thrd
->top
!= thrd
->rawno
)
9004 thrd
= fetch_thread(stream
, thrd
->top
);
9007 * This doesn't unselect if the thread is already selected
9008 * (like select current does), it always selects.
9009 * There is no way to select ! this thread.
9012 set_search_bit_for_thread(stream
, thrd
, limitsrch
);
9021 * Select by message keywords.
9022 * Sets searched bits in mail_elts
9024 * Args limitsrch -- limit search to this searchset
9026 * Returns 0 on success.
9029 select_by_keyword(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
9031 int r
, not = 0, we_cancel
= 0;
9032 char keyword
[MAXUSERFLAG
+1], *kword
;
9033 char *error
= NULL
, *p
, *prompt
;
9038 ps_global
->mangled_footer
= 1;
9045 q_status_message(SM_ORDER
, 3, 4, error
);
9046 fs_give((void **) &error
);
9049 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
) && ps_global
->keywords
){
9051 prompt
= _("Keyword (or keyword initial) to NOT match: ");
9053 prompt
= _("Keyword (or keyword initial) to match: ");
9057 prompt
= _("Keyword to NOT match: ");
9059 prompt
= _("Keyword to match: ");
9062 oe_flags
= OE_APPEND_CURRENT
;
9063 r
= optionally_enter(keyword
, -FOOTER_ROWS(ps_global
), 0,
9065 prompt
, sel_key_opt
, help
, &oe_flags
);
9068 /* select keyword from a list */
9069 if((kword
=choose_a_keyword()) != NULL
){
9070 strncpy(keyword
, kword
, sizeof(keyword
)-1);
9071 keyword
[sizeof(keyword
)-1] = '\0';
9072 fs_give((void **) &kword
);
9081 help
= help
== NO_HELP
? h_select_keyword
: NO_HELP
;
9083 cmd_cancelled("Selection by keyword");
9087 removing_leading_and_trailing_white_space(keyword
);
9089 }while(r
== 3 || r
== 4 || r
== '!' || keyword_check(keyword
, &error
));
9092 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
) && ps_global
->keywords
){
9093 p
= initial_to_keyword(keyword
);
9095 strncpy(keyword
, p
, sizeof(keyword
)-1);
9096 keyword
[sizeof(keyword
)-1] = '\0';
9101 * We want to check the keyword, not the nickname of the keyword,
9102 * so convert it to the keyword if necessary.
9104 p
= nick_to_keyword(keyword
);
9106 strncpy(keyword
, p
, sizeof(keyword
)-1);
9107 keyword
[sizeof(keyword
)-1] = '\0';
9110 pgm
= mail_newsearchpgm();
9112 pgm
->unkeyword
= mail_newstringlist();
9113 pgm
->unkeyword
->text
.data
= (unsigned char *) cpystr(keyword
);
9114 pgm
->unkeyword
->text
.size
= strlen(keyword
);
9117 pgm
->keyword
= mail_newstringlist();
9118 pgm
->keyword
->text
.data
= (unsigned char *) cpystr(keyword
);
9119 pgm
->keyword
->text
.size
= strlen(keyword
);
9122 if(ps_global
&& ps_global
->ttyo
){
9123 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
9124 ps_global
->mangled_footer
= 1;
9127 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
9129 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
9130 pine_mail_search_full(stream
, "UTF-8", pgm
, SE_NOPREFETCH
| SE_FREE
);
9131 /* we know this was freed in mail_search, let caller know */
9143 * Allow user to choose a keyword from their list of keywords.
9145 * Returns an allocated keyword on success, NULL otherwise.
9148 choose_a_keyword(void)
9150 char *choice
= NULL
;
9151 char **keyword_list
, **lp
;
9156 * Build a list of keywords to choose from.
9159 for(cnt
= 0, kw
= ps_global
->keywords
; kw
; kw
= kw
->next
)
9163 q_status_message(SM_ORDER
, 3, 4,
9164 _("No keywords defined, use \"keywords\" option in Setup/Config"));
9168 lp
= keyword_list
= (char **) fs_get((cnt
+ 1) * sizeof(*keyword_list
));
9169 memset(keyword_list
, 0, (cnt
+1) * sizeof(*keyword_list
));
9171 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
)
9172 *lp
++ = cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
9174 /* TRANSLATORS: SELECT A KEYWORD is a screen title
9175 TRANSLATORS: Print something1 using something2.
9176 "keywords" is something1 */
9177 choice
= choose_item_from_list(keyword_list
, NULL
, _("SELECT A KEYWORD"),
9178 _("keywords"), h_select_keyword_screen
,
9179 _("HELP FOR SELECTING A KEYWORD"), NULL
);
9182 q_status_message(SM_ORDER
, 1, 4, "No choice");
9184 free_list_array(&keyword_list
);
9191 * Allow user to choose a list of keywords from their list of keywords.
9193 * Returns allocated list.
9196 choose_list_of_keywords(void)
9198 LIST_SEL_S
*listhead
, *ls
, *p
;
9204 * Build a list of keywords to choose from.
9207 p
= listhead
= NULL
;
9208 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
9210 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9211 memset(ls
, 0, sizeof(*ls
));
9212 ls
->item
= cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
9225 /* TRANSLATORS: SELECT KEYWORDS is a screen title
9226 Print something1 using something2.
9227 "keywords" is something1 */
9228 if(!select_from_list_screen(listhead
, SFL_ALLOW_LISTMODE
,
9229 _("SELECT KEYWORDS"), _("keywords"),
9230 h_select_multkeyword_screen
,
9231 _("HELP FOR SELECTING KEYWORDS"), NULL
)){
9232 for(cnt
= 0, p
= listhead
; p
; p
= p
->next
)
9236 ret
= (char **) fs_get((cnt
+1) * sizeof(*ret
));
9237 memset(ret
, 0, (cnt
+1) * sizeof(*ret
));
9238 for(i
= 0, p
= listhead
; p
; p
= p
->next
)
9240 ret
[i
++] = cpystr(p
->item
? p
->item
: "");
9243 free_list_sel(&listhead
);
9250 * Allow user to choose a charset
9252 * Returns an allocated charset on success, NULL otherwise.
9255 choose_a_charset(int which_charsets
)
9257 char *choice
= NULL
;
9258 char **charset_list
, **lp
;
9263 * Build a list of charsets to choose from.
9266 for(cnt
= 0, cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
9267 if(!(cs
->flags
& (CF_UNSUPRT
|CF_NOEMAIL
))
9268 && ((which_charsets
& CAC_ALL
)
9269 || (which_charsets
& CAC_POSTING
9270 && cs
->flags
& CF_POSTING
)
9271 || (which_charsets
& CAC_DISPLAY
9272 && cs
->type
!= CT_2022
9273 && (cs
->flags
& (CF_PRIMARY
|CF_DISPLAY
)) == (CF_PRIMARY
|CF_DISPLAY
))))
9278 q_status_message(SM_ORDER
, 3, 4,
9279 _("No charsets found? Enter charset manually."));
9283 lp
= charset_list
= (char **) fs_get((cnt
+ 1) * sizeof(*charset_list
));
9284 memset(charset_list
, 0, (cnt
+1) * sizeof(*charset_list
));
9286 for(cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
9287 if(!(cs
->flags
& (CF_UNSUPRT
|CF_NOEMAIL
))
9288 && ((which_charsets
& CAC_ALL
)
9289 || (which_charsets
& CAC_POSTING
9290 && cs
->flags
& CF_POSTING
)
9291 || (which_charsets
& CAC_DISPLAY
9292 && cs
->type
!= CT_2022
9293 && (cs
->flags
& (CF_PRIMARY
|CF_DISPLAY
)) == (CF_PRIMARY
|CF_DISPLAY
))))
9294 *lp
++ = cpystr(cs
->name
);
9297 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
9298 TRANSLATORS: Print something1 using something2.
9299 "character sets" is something1 */
9300 choice
= choose_item_from_list(charset_list
, NULL
, _("SELECT A CHARACTER SET"),
9301 _("character sets"), h_select_charset_screen
,
9302 _("HELP FOR SELECTING A CHARACTER SET"), NULL
);
9305 q_status_message(SM_ORDER
, 1, 4, "No choice");
9307 free_list_array(&charset_list
);
9314 * Allow user to choose a list of character sets and/or scripts
9316 * Returns allocated list.
9319 choose_list_of_charsets(void)
9321 LIST_SEL_S
*listhead
, *ls
, *p
;
9323 int cnt
, i
, got_one
;
9328 char buf
[1024], *folded
;
9331 * Build a list of charsets to choose from.
9334 p
= listhead
= NULL
;
9336 /* this width is determined by select_from_list_screen() */
9337 width
= ps_global
->ttyo
->screen_cols
- 4;
9339 /* first comes a list of scripts (sets of character sets) */
9340 for(s
= utf8_script(NIL
); s
&& s
->name
; s
++){
9342 limit
= sizeof(buf
)-1;
9344 memset(q
, 0, limit
+1);
9347 sstrncpy(&q
, s
->name
, limit
);
9350 sstrncpy(&q
, " (", limit
-(q
-buf
));
9351 sstrncpy(&q
, s
->description
, limit
-(q
-buf
));
9352 sstrncpy(&q
, ")", limit
-(q
-buf
));
9355 /* add the list of charsets that are in this script */
9357 for(cs
= utf8_charset(NIL
);
9358 cs
&& cs
->name
&& (q
-buf
) < limit
; cs
++){
9359 if(cs
->script
& s
->script
){
9361 * Filter out some un-useful members of the list.
9362 * UTF-7 and UTF-8 weren't actually in the list at the
9363 * time this was written. Just making sure.
9365 if(!strucmp(cs
->name
, "ISO-2022-JP-2")
9366 || !strucmp(cs
->name
, "UTF-7")
9367 || !strucmp(cs
->name
, "UTF-8"))
9371 sstrncpy(&q
, " ", limit
-(q
-buf
));
9374 sstrncpy(&q
, " {", limit
-(q
-buf
));
9377 sstrncpy(&q
, cs
->name
, limit
-(q
-buf
));
9382 sstrncpy(&q
, "}", limit
-(q
-buf
));
9384 /* fold this line so that it can all be seen on the screen */
9385 folded
= fold(buf
, width
, width
, "", " ", FLD_NONE
);
9388 while(t
&& *t
&& (q
= strindex(t
, '\n')) != NULL
){
9391 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9392 memset(ls
, 0, sizeof(*ls
));
9394 ls
->item
= cpystr(s
->name
);
9396 ls
->flags
= SFL_NOSELECT
;
9398 ls
->display_item
= cpystr(t
);
9408 listhead
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9409 memset(listhead
, 0, sizeof(*listhead
));
9410 listhead
->flags
= SFL_NOSELECT
;
9411 listhead
->display_item
=
9412 cpystr(_("Scripts representing groups of related character sets"));
9413 listhead
->next
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9414 memset(listhead
->next
, 0, sizeof(*listhead
));
9415 listhead
->next
->flags
= SFL_NOSELECT
;
9416 listhead
->next
->display_item
=
9417 cpystr(repeat_char(width
, '-'));
9419 listhead
->next
->next
= ls
;
9424 fs_give((void **) &folded
);
9428 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9429 memset(ls
, 0, sizeof(*ls
));
9430 ls
->flags
= SFL_NOSELECT
;
9438 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9439 memset(ls
, 0, sizeof(*ls
));
9440 ls
->flags
= SFL_NOSELECT
;
9442 cpystr(_("Individual character sets, may be mixed with scripts"));
9446 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9447 memset(ls
, 0, sizeof(*ls
));
9448 ls
->flags
= SFL_NOSELECT
;
9450 cpystr(repeat_char(width
, '-'));
9454 /* then comes a list of individual character sets */
9455 for(cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
9456 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9457 memset(ls
, 0, sizeof(*ls
));
9458 ls
->item
= cpystr(cs
->name
);
9471 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9472 Print something1 using something2.
9473 "character sets" is something1 */
9474 if(!select_from_list_screen(listhead
, SFL_ALLOW_LISTMODE
,
9475 _("SELECT CHARACTER SETS"), _("character sets"),
9476 h_select_multcharsets_screen
,
9477 _("HELP FOR SELECTING CHARACTER SETS"), NULL
)){
9478 for(cnt
= 0, p
= listhead
; p
; p
= p
->next
)
9482 ret
= (char **) fs_get((cnt
+1) * sizeof(*ret
));
9483 memset(ret
, 0, (cnt
+1) * sizeof(*ret
));
9484 for(i
= 0, p
= listhead
; p
; p
= p
->next
)
9486 ret
[i
++] = cpystr(p
->item
? p
->item
: "");
9489 free_list_sel(&listhead
);
9494 /* Report quota summary resources in an IMAP server */
9497 cmd_quota (struct pine
*state
)
9499 QUOTALIST
*imapquota
;
9504 if(!state
->mail_stream
|| !is_imap_stream(state
->mail_stream
)){
9505 q_status_message(SM_ORDER
, 1, 5, "Quota only available for IMAP folders");
9509 if (state
->mail_stream
9510 && !sp_dead_stream(state
->mail_stream
)
9511 && state
->mail_stream
->mailbox
9512 && *state
->mail_stream
->mailbox
9513 && mail_valid_net_parse(state
->mail_stream
->mailbox
, &mb
))
9514 imap_getquotaroot(state
->mail_stream
, mb
.mailbox
);
9516 if(!state
->quota
) /* failed ? */
9517 return; /* go back... */
9519 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
9520 q_status_message(SM_ORDER
| SM_DING
, 3, 3, "Error allocating space.");
9524 so_puts(store
, "Quota Report for ");
9525 so_puts(store
, state
->mail_stream
->original_mailbox
);
9526 so_puts(store
, "\n\n");
9528 for (imapquota
= state
->quota
; imapquota
; imapquota
= imapquota
->next
){
9530 so_puts(store
, _("Resource : "));
9531 so_puts(store
, imapquota
->name
);
9532 so_writec('\n', store
);
9534 so_puts(store
, _("Usage : "));
9535 so_puts(store
, long2string(imapquota
->usage
));
9536 if(!strucmp(imapquota
->name
,"STORAGE"))
9537 so_puts(store
, " KiB ");
9538 if(!strucmp(imapquota
->name
,"MESSAGE")){
9539 so_puts(store
, _(" message"));
9540 if(imapquota
->usage
!= 1)
9541 so_puts(store
, _("s ")); /* plural */
9543 so_puts(store
, _(" "));
9545 so_writec('(', store
);
9546 so_puts(store
, long2string(100*imapquota
->usage
/imapquota
->limit
));
9547 so_puts(store
, "%)\n");
9549 so_puts(store
, _("Limit : "));
9550 so_puts(store
, long2string(imapquota
->limit
));
9551 if(!strucmp(imapquota
->name
,"STORAGE"))
9552 so_puts(store
, " KiB\n\n");
9553 if(!strucmp(imapquota
->name
,"MESSAGE")){
9554 so_puts(store
, _(" message"));
9555 if(imapquota
->usage
!= 1)
9556 so_puts(store
, _("s\n\n")); /* plural */
9558 so_puts(store
, _("\n\n"));
9562 memset(&sargs
, 0, sizeof(SCROLL_S
));
9563 sargs
.text
.text
= so_text(store
);
9564 sargs
.text
.src
= CharStar
;
9565 sargs
.text
.desc
= _("Quota Resources Summary");
9566 sargs
.bar
.title
= _("QUOTA SUMMARY");
9567 sargs
.proc
.tool
= NULL
;
9568 sargs
.help
.text
= h_quota_command
;
9569 sargs
.help
.title
= NULL
;
9570 sargs
.keys
.menu
= NULL
;
9571 setbitmap(sargs
.keys
.bitmap
);
9577 mail_free_quotalist(&(state
->quota
));
9580 /*----------------------------------------------------------------------
9581 Prompt the user for the type of sort he desires
9583 Args: state -- pine state pointer
9584 q1 -- Line to prompt on
9586 Returns 0 if it was cancelled, 1 otherwise.
9589 select_sort(struct pine
*state
, int ql
, SortOrder
*sort
, int *rev
)
9591 char prompt
[200], tmp
[3], *p
;
9593 int deefault
= 'a', retval
= 1;
9598 DLG_SORTPARAM sortsel
;
9600 if (mswin_usedialog ()) {
9602 sortsel
.reverse
= mn_get_revsort (state
->msgmap
);
9603 sortsel
.cursort
= mn_get_sort (state
->msgmap
);
9604 /* assumption here that HelpType is char ** */
9605 sortsel
.helptext
= h_select_sort
;
9608 if ((retval
= os_sortdialog (&sortsel
))) {
9609 *sort
= sortsel
.cursort
;
9610 *rev
= sortsel
.reverse
;
9617 /*----- String together the prompt ------*/
9619 if(F_ON(F_USE_FK
,ps_global
))
9620 strncpy(prompt
, _("Choose type of sort : "), sizeof(prompt
));
9622 strncpy(prompt
, _("Choose type of sort, or 'R' to reverse current sort : "),
9625 for(i
= 0; state
->sort_types
[i
] != EndofList
; i
++) {
9627 p
= sorts
[i
].label
= sort_name(state
->sort_types
[i
]);
9628 while(*(p
+1) && islower((unsigned char)*p
))
9631 sorts
[i
].ch
= tolower((unsigned char)(tmp
[0] = *p
));
9632 sorts
[i
].name
= cpystr(tmp
);
9634 if(mn_get_sort(state
->msgmap
) == state
->sort_types
[i
])
9635 deefault
= sorts
[i
].rval
;
9639 sorts
[i
].rval
= 'r';
9640 sorts
[i
].name
= cpystr("R");
9641 if(F_ON(F_USE_FK
,ps_global
))
9642 sorts
[i
].label
= N_("Reverse");
9644 sorts
[i
].label
= "";
9647 help
= h_select_sort
;
9649 if((F_ON(F_USE_FK
,ps_global
)
9650 && ((s
= double_radio_buttons(prompt
,ql
,sorts
,deefault
,'x',
9651 help
,RB_NORM
)) != 'x'))
9653 (F_OFF(F_USE_FK
,ps_global
)
9654 && ((s
= radio_buttons(prompt
,ql
,sorts
,deefault
,'x',
9655 help
,RB_NORM
)) != 'x'))){
9656 state
->mangled_body
= 1; /* signal screen's changed */
9658 *rev
= !mn_get_revsort(state
->msgmap
);
9660 *sort
= state
->sort_types
[s
];
9662 if(F_ON(F_SHOW_SORT
, ps_global
))
9663 ps_global
->mangled_header
= 1;
9667 cmd_cancelled("Sort");
9671 fs_give((void **)&sorts
[i
].name
);
9673 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
9678 /*---------------------------------------------------------------------
9679 Build list of folders in the given context for user selection
9681 Args: c -- pointer to pointer to folder's context context
9682 f -- folder prefix to display
9683 sublist -- whether or not to use 'f's contents as prefix
9684 lister -- function used to do the actual display
9686 Returns: malloc'd string containing sequence, else NULL if
9687 no messages in msgmap with local "selected" flag.
9690 display_folder_list(CONTEXT_S
**c
, char *f
, int sublist
, int (*lister
) (struct pine
*, CONTEXT_S
**, char *, int))
9694 void (*redraw
)(void) = ps_global
->redrawer
;
9696 push_titlebar_state();
9698 if((rc
= (*lister
)(ps_global
, &tc
, f
, sublist
)) != 0)
9702 pop_titlebar_state();
9704 if((ps_global
->redrawer
= redraw
) != NULL
) /* reset old value, and test */
9705 (*ps_global
->redrawer
)();
9707 if(rc
== 1 && F_ON(F_SELECT_WO_CONFIRM
, ps_global
))
9715 * Allow user to choose a single item from a list of strings.
9717 * Args list -- Array of strings to choose from, NULL terminated.
9718 * displist -- Array of strings to display instead of displaying list.
9719 * Indices correspond to the list array. Display the displist
9720 * but return the item from list if displist non-NULL.
9721 * title -- For conf_scroll_screen
9722 * pdesc -- For conf_scroll_screen
9723 * help -- For conf_scroll_screen
9724 * htitle -- For conf_scroll_screen
9726 * Returns an allocated copy of the chosen item or NULL.
9729 choose_item_from_list(char **list
, char **displist
, char *title
, char *pdesc
, HelpType help
,
9730 char *htitle
, char *cursor_location
)
9732 LIST_SEL_S
*listhead
, *ls
, *p
, *starting_val
= NULL
;
9734 char *ret
= NULL
, *choice
= NULL
;
9736 /* build the LIST_SEL_S list */
9737 p
= listhead
= NULL
;
9738 for(t
= list
, dl
= displist
; *t
; t
++, dl
++){
9739 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9740 memset(ls
, 0, sizeof(*ls
));
9741 ls
->item
= cpystr(*t
);
9743 ls
->display_item
= cpystr(*dl
);
9745 if(cursor_location
&& (cursor_location
== (*t
)))
9759 if(!select_from_list_screen(listhead
, SFL_NONE
, title
, pdesc
,
9760 help
, htitle
, starting_val
))
9761 for(p
= listhead
; !choice
&& p
; p
= p
->next
)
9766 ret
= cpystr(choice
);
9768 free_list_sel(&listhead
);
9775 free_list_sel(LIST_SEL_S
**lsel
)
9778 free_list_sel(&(*lsel
)->next
);
9780 fs_give((void **) &(*lsel
)->item
);
9782 if((*lsel
)->display_item
)
9783 fs_give((void **) &(*lsel
)->display_item
);
9785 fs_give((void **) lsel
);
9791 * file_lister - call pico library's file lister
9794 file_lister(char *title
, char *path
, size_t pathlen
, char *file
, size_t filelen
, int newmail
, int flags
)
9798 void (*redraw
)(void) = ps_global
->redrawer
;
9800 standard_picobuf_setup(&pbf
);
9801 push_titlebar_state();
9805 /* BUG: what about help command and text? */
9806 pbf
.pine_anchor
= title
;
9808 rv
= pico_file_browse(&pbf
, path
, pathlen
, file
, filelen
, NULL
, 0, flags
);
9809 standard_picobuf_teardown(&pbf
);
9810 fix_windsize(ps_global
);
9811 init_signals(); /* has it's own signal stuff */
9813 /* Restore display's titlebar and body */
9814 pop_titlebar_state();
9816 if((ps_global
->redrawer
= redraw
) != NULL
)
9817 (*ps_global
->redrawer
)();
9823 /*----------------------------------------------------------------------
9824 Print current folder index
9828 print_index(struct pine
*state
, MSGNO_S
*msgmap
, int agg
)
9832 char buf
[MAX_SCREEN_COLS
+1];
9834 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++){
9835 if(agg
&& !get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
))
9838 if(!agg
&& msgline_hidden(state
->mail_stream
, msgmap
, i
, 0))
9841 ice
= build_header_line(state
, state
->mail_stream
, msgmap
, i
, NULL
);
9845 * I don't understand why we'd want to mark the current message
9846 * instead of printing out the first character of the status
9847 * so I'm taking it out and including the first character of the
9848 * line instead. Hubert 2006-02-09
9850 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
9854 if(!gf_puts(simple_index_line(buf
,sizeof(buf
),ice
,i
),
9856 || !gf_puts(NEWLINE
, print_char
))
9867 * windows callback to get/set header mode state
9870 header_mode_callback(set
, args
)
9874 return(ps_global
->full_header
);
9879 * windows callback to get/set zoom mode state
9882 zoom_mode_callback(set
, args
)
9886 return(any_lflagged(ps_global
->msgmap
, MN_HIDE
) != 0);
9891 * windows callback to get/set zoom mode state
9894 any_selected_callback(set
, args
)
9898 return(any_lflagged(ps_global
->msgmap
, MN_SLCT
) != 0);
9906 flag_callback(set
, flags
)
9916 case 1: /* Important */
9917 permflag
= ps_global
->mail_stream
->perm_flagged
;
9921 permflag
= ps_global
->mail_stream
->perm_seen
;
9924 case 3: /* Answered */
9925 permflag
= ps_global
->mail_stream
->perm_answered
;
9928 case 4: /* Deleted */
9929 permflag
= ps_global
->mail_stream
->perm_deleted
;
9934 if(!(any_messages(ps_global
->msgmap
, NULL
, "to Flag")
9935 && can_set_flag(ps_global
, "flag", permflag
)))
9938 if(sp_io_error_on_stream(ps_global
->mail_stream
)){
9939 sp_set_io_error_on_stream(ps_global
->mail_stream
, 0);
9940 pine_mail_check(ps_global
->mail_stream
); /* forces write */
9944 msgno
= mn_m2raw(ps_global
->msgmap
, mn_get_cur(ps_global
->msgmap
));
9945 if(msgno
> 0L && ps_global
->mail_stream
9946 && msgno
<= ps_global
->mail_stream
->nmsgs
9947 && (mc
= mail_elt(ps_global
->mail_stream
, msgno
))
9950 * NOTE: code below is *VERY* sensitive to the order of
9951 * the messages defined in resource.h for flag handling.
9952 * Don't change it unless you know what you're doing.
9959 case 1 : /* Important */
9960 flagstr
= "\\FLAGGED";
9961 mflag
= (mc
->flagged
) ? 0L : ST_SET
;
9966 mflag
= (mc
->seen
) ? 0L : ST_SET
;
9969 case 3 : /* Answered */
9970 flagstr
= "\\ANSWERED";
9971 mflag
= (mc
->answered
) ? 0L : ST_SET
;
9974 case 4 : /* Deleted */
9975 flagstr
= "\\DELETED";
9976 mflag
= (mc
->deleted
) ? 0L : ST_SET
;
9979 default : /* bogus */
9983 mail_flag(ps_global
->mail_stream
, long2string(msgno
),
9986 if(ps_global
->redrawer
)
9987 (*ps_global
->redrawer
)();
10000 newflags
|= 0x0004;
10004 newflags
|= 0x0008;
10014 * BUG: Should teach this about keywords
10020 static MPopup flag_submenu
[] = {
10021 {tMessage
, {N_("Important"), lNormal
}, {IDM_MI_FLAGIMPORTANT
}},
10022 {tMessage
, {N_("New"), lNormal
}, {IDM_MI_FLAGNEW
}},
10023 {tMessage
, {N_("Answered"), lNormal
}, {IDM_MI_FLAGANSWERED
}},
10024 {tMessage
, {N_("Deleted"), lNormal
}, {IDM_MI_FLAGDELETED
}},
10029 flag_submenu
[0].label
.style
= (mc
&& mc
->flagged
) ? lChecked
: lNormal
;
10032 flag_submenu
[1].label
.style
= (mc
&& mc
->seen
) ? lNormal
: lChecked
;
10035 flag_submenu
[2].label
.style
= (mc
&& mc
->answered
) ? lChecked
: lNormal
;
10038 flag_submenu
[3].label
.style
= (mc
&& mc
->deleted
) ? lChecked
: lNormal
;
10040 return(flag_submenu
);
10043 #endif /* _WINDOWS */