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-2015 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);
87 int cmd_forward(struct pine
*, MSGNO_S
*, int);
88 int cmd_bounce(struct pine
*, MSGNO_S
*, int);
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_size(MAILSTREAM
*, SEARCHSET
**);
109 SEARCHSET
*visible_searchset(MAILSTREAM
*, MSGNO_S
*);
110 int select_by_status(MAILSTREAM
*, SEARCHSET
**);
111 int select_by_rule(MAILSTREAM
*, SEARCHSET
**);
112 int select_by_thread(MAILSTREAM
*, MSGNO_S
*, SEARCHSET
**);
113 char *choose_a_rule(int);
114 int select_by_keyword(MAILSTREAM
*, SEARCHSET
**);
115 char *choose_a_keyword(void);
116 int select_sort(struct pine
*, int, SortOrder
*, int *);
117 int print_index(struct pine
*, MSGNO_S
*, int);
120 * List of Select options used by apply_* functions...
122 static char *sel_pmt1
= N_("ALTER message selection : ");
123 ESCKEY_S sel_opts1
[] = {
124 /* TRANSLATORS: these are keymenu names for selecting. Broaden selection means
125 we will add more messages to the selection, Narrow selection means we will
126 remove some selections (like a logical AND instead of logical OR), and Flip
127 Selected means that all the messages that are currently selected become unselected,
128 and all the unselected messages become selected. */
129 {'a', 'a', "A", N_("unselect All")},
130 {'c', 'c', "C", NULL
},
131 {'b', 'b', "B", N_("Broaden selctn")},
132 {'n', 'n', "N", N_("Narrow selctn")},
133 {'f', 'f', "F", N_("Flip selected")},
138 #define SEL_OPTS_THREAD 9 /* index number of "tHread" */
139 #define SEL_OPTS_THREAD_CH 'h'
141 char *sel_pmt2
= "SELECT criteria : ";
142 static ESCKEY_S sel_opts2
[] = {
143 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
144 means select the currently highlighted message; select by Number is by message
145 number; Status is by status of the message, for example the message might be
146 New or it might be Unseen or marked Important; Size has the Z upper case because
147 it is a Z command; Keyword is an alpine keyword that has been set by the user;
148 and Rule is an alpine rule */
149 {'a', 'a', "A", N_("select All")},
150 {'c', 'c', "C", N_("select Cur")},
151 {'n', 'n', "N", N_("Number")},
152 {'d', 'd', "D", N_("Date")},
153 {'t', 't', "T", N_("Text")},
154 {'s', 's', "S", N_("Status")},
155 {'z', 'z', "Z", N_("siZe")},
156 {'k', 'k', "K", N_("Keyword")},
157 {'r', 'r', "R", N_("Rule")},
158 {SEL_OPTS_THREAD_CH
, 'h', "H", N_("tHread")},
163 static ESCKEY_S sel_opts3
[] = {
164 /* TRANSLATORS: these are operations we can do on a set of selected messages.
165 Del is Delete; Undel is Undelete; TakeAddr means to Take some Addresses into
166 the address book; Save means to save the messages into another alpine folder;
167 Export means to copy the messages to a file outside of alpine, external to
169 {'d', 'd', "D", N_("Del")},
170 {'u', 'u', "U", N_("Undel")},
171 {'r', 'r', "R", N_("Reply")},
172 {'f', 'f', "F", N_("Forward")},
173 {'%', '%', "%", N_("Print")},
174 {'t', 't', "T", N_("TakeAddr")},
175 {'s', 's', "S", N_("Save")},
176 {'e', 'e', "E", N_("Export")},
177 { -1, 0, NULL
, NULL
},
178 { -1, 0, NULL
, NULL
},
179 { -1, 0, NULL
, NULL
},
180 { -1, 0, NULL
, NULL
},
181 { -1, 0, NULL
, NULL
},
182 { -1, 0, NULL
, NULL
},
183 { -1, 0, NULL
, NULL
},
187 static ESCKEY_S sel_opts4
[] = {
188 {'a', 'a', "A", N_("select All")},
189 /* TRANSLATORS: select currrently highlighted message Thread */
190 {'c', 'c', "C", N_("select Curthrd")},
191 {'n', 'n', "N", N_("Number")},
192 {'d', 'd', "D", N_("Date")},
193 {'t', 't', "T", N_("Text")},
194 {'s', 's', "S", N_("Status")},
195 {'z', 'z', "Z", N_("siZe")},
196 {'k', 'k', "K", N_("Keyword")},
197 {'r', 'r', "R", N_("Rule")},
198 {SEL_OPTS_THREAD_CH
, 'h', "H", N_("tHread")},
203 static char *sel_flag
=
204 N_("Select New, Deleted, Answered, Forwarded, or Important messages ? ");
205 static char *sel_flag_not
=
206 N_("Select NOT New, NOT Deleted, NOT Answered, NOT Forwarded or NOT Important msgs ? ");
207 static ESCKEY_S sel_flag_opt
[] = {
208 /* TRANSLATORS: When selecting messages by message Status these are the
209 different types of Status you can select on. Is the message New, Recent,
210 and so on. Not means flip the meaning of the selection to the opposite
211 thing, so message is not New or not Important. */
212 {'n', 'n', "N", N_("New")},
213 {'*', '*', "*", N_("Important")},
214 {'d', 'd', "D", N_("Deleted")},
215 {'a', 'a', "A", N_("Answered")},
216 {'f', 'f', "F", N_("Forwarded")},
218 {'!', '!', "!", N_("Not")},
220 {'r', 'r', "R", N_("Recent")},
221 {'u', 'u', "U", N_("Unseen")},
226 static ESCKEY_S sel_date_opt
[] = {
228 /* TRANSLATORS: options when selecting messages by Date */
229 {ctrl('P'), 12, "^P", N_("Prev Day")},
230 {ctrl('N'), 13, "^N", N_("Next Day")},
231 {ctrl('X'), 11, "^X", N_("Cur Msg")},
232 {ctrl('W'), 14, "^W", N_("Toggle When")},
233 {KEY_UP
, 12, "", ""},
234 {KEY_DOWN
, 13, "", ""},
239 static char *sel_text
=
240 N_("Select based on To, From, Cc, Recip, Partic, Subject fields or All msg text ? ");
241 static char *sel_text_not
=
242 N_("Select based on NOT To, From, Cc, Recip, Partic, Subject or All msg text ? ");
243 static ESCKEY_S sel_text_opt
[] = {
244 /* TRANSLATORS: Select messages based on the text contained in the From line, or
245 the Subject line, and so on. */
246 {'f', 'f', "F", N_("From")},
247 {'s', 's', "S", N_("Subject")},
248 {'t', 't', "T", N_("To")},
249 {'a', 'a', "A", N_("All Text")},
250 {'c', 'c', "C", N_("Cc")},
251 {'!', '!', "!", N_("Not")},
252 {'r', 'r', "R", N_("Recipient")},
253 {'p', 'p', "P", N_("Participant")},
254 {'b', 'b', "B", N_("Body")},
255 {'h', 'h', "H", N_("Header")},
259 static ESCKEY_S choose_action
[] = {
260 {'c', 'c', "C", N_("Compose")},
261 {'r', 'r', "R", N_("Reply")},
262 {'f', 'f', "F", N_("Forward")},
263 {'b', 'b', "B", N_("Bounce")},
267 static char *select_num
=
268 N_("Enter comma-delimited list of numbers (dash between ranges): ");
270 static char *select_size_larger_msg
=
271 N_("Select messages with size larger than: ");
273 static char *select_size_smaller_msg
=
274 N_("Select messages with size smaller than: ");
276 static char *sel_size_larger
= N_("Larger");
277 static char *sel_size_smaller
= N_("Smaller");
278 static ESCKEY_S sel_size_opt
[] = {
280 {ctrl('W'), 14, "^W", NULL
},
284 static ESCKEY_S sel_key_opt
[] = {
286 {ctrl('T'), 14, "^T", N_("To List")},
288 {'!', '!', "!", N_("Not")},
292 static ESCKEY_S flag_text_opt
[] = {
293 /* TRANSLATORS: these are types of flags (markers) that the user can
294 set. For example, they can flag the message as an important message. */
295 {'n', 'n', "N", N_("New")},
296 {'*', '*', "*", N_("Important")},
297 {'d', 'd', "D", N_("Deleted")},
298 {'a', 'a', "A", N_("Answered")},
299 {'f', 'f', "F", N_("Forwarded")},
300 {'!', '!', "!", N_("Not")},
301 {ctrl('T'), 10, "^T", N_("To Flag Details")},
306 alpine_get_password(char *prompt
, char *pass
, size_t len
)
308 int flags
= OE_PASSWD
| OE_DISALLOW_HELP
;
310 return optionally_enter(pass
,
311 -(ps_global
->ttyo
? FOOTER_ROWS(ps_global
) : 3),
312 0, len
, prompt
, NULL
, NO_HELP
, &flags
);
315 int smime_import_certificate(char *filename
, char *full_filename
, size_t len
)
318 static HISTORY_S
*history
= NULL
;
319 static ESCKEY_S eopts
[] = {
320 {ctrl('T'), 10, "^T", N_("To Files")},
322 {-1, 0, NULL
, NULL
}};
324 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps_global
)){
325 eopts
[r
].ch
= ctrl('I');
327 eopts
[r
].name
= "TAB";
328 eopts
[r
].label
= N_("Complete");
334 full_filename
[0] = '\0';
336 r
= get_export_filename(ps_global
, filename
, NULL
, full_filename
,
337 len
, "certificate", "IMPORT", eopts
, NULL
,
338 -FOOTER_ROWS(ps_global
), GE_IS_IMPORT
, &history
);
344 /*----------------------------------------------------------------------
345 The giant switch on the commands for index and viewing
347 Input: command -- The command char/code
348 in_index -- flag indicating command is from index
349 orig_command -- The original command typed before pre-processing
350 Output: force_mailchk -- Set to tell caller to force call to new_mail().
354 Returns 1 if the message number or attachment to show changed
357 process_cmd(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
,
358 int command
, CmdWhere in_index
, int *force_mailchk
)
360 int question_line
, a_changed
, flags
= 0, ret
, j
;
362 long new_msgno
, del_count
, old_msgno
, i
;
364 char *newfolder
, prompt
[MAX_SCREEN_COLS
+1];
367 #if defined(DOS) && !defined(_WINDOWS)
368 extern long coreleft();
371 dprint((4, "\n - process_cmd(cmd=%d) -\n", command
));
373 question_line
= -FOOTER_ROWS(state
);
374 state
->mangled_screen
= 0;
375 state
->mangled_footer
= 0;
376 state
->mangled_header
= 0;
377 state
->next_screen
= SCREEN_FUN_NULL
;
378 old_msgno
= mn_get_cur(msgmap
);
383 /*------------- Help --------*/
386 * We're not using the h_mail_view portion of this right now because
387 * that call is being handled in scrolltool() before it gets
388 * here. Leave it in case we change how it works.
390 helper((in_index
== MsgIndx
)
394 : h_mail_thread_index
,
395 (in_index
== MsgIndx
)
396 ? _("HELP FOR MESSAGE INDEX")
398 ? _("HELP FOR MESSAGE TEXT")
399 : _("HELP FOR THREAD INDEX"),
401 dprint((4,"MAIL_CMD: did help command\n"));
402 state
->mangled_screen
= 1;
406 /*--------- Return to main menu ------------*/
408 state
->next_screen
= main_menu_screen
;
409 dprint((2,"MAIL_CMD: going back to main menu\n"));
413 /*------- View message text --------*/
416 if(any_messages(msgmap
, NULL
, "to View")){
417 state
->next_screen
= mail_view_screen
;
423 /*------- View attachment --------*/
425 state
->next_screen
= attachment_screen
;
426 dprint((2,"MAIL_CMD: going to attachment screen\n"));
430 /*---------- Previous message ----------*/
432 if(any_messages(msgmap
, NULL
, NULL
)){
433 if((i
= mn_get_cur(msgmap
)) > 1L){
434 mn_dec_cur(stream
, msgmap
,
435 (in_index
== View
&& THREADING()
436 && sp_viewing_a_thread(stream
))
439 ? MH_ANYTHD
: MH_NONE
);
440 if(i
== mn_get_cur(msgmap
)){
441 PINETHRD_S
*thrd
= NULL
, *topthrd
= NULL
;
443 if(THRD_INDX_ENABLED()){
444 mn_dec_cur(stream
, msgmap
, MH_ANYTHD
);
445 if(i
== mn_get_cur(msgmap
))
446 q_status_message1(SM_ORDER
, 0, 2,
447 _("Already on first %s in Zoomed Index"),
448 THRD_INDX() ? _("thread") : _("message"));
451 || F_ON(F_NEXT_THRD_WO_CONFIRM
, state
))
454 ret
= want_to(_("View previous thread"), 'y', 'x',
458 q_status_message(SM_ORDER
, 0, 2,
459 _("Viewing previous thread"));
460 new_msgno
= mn_get_cur(msgmap
);
461 mn_set_cur(msgmap
, i
);
462 if(unview_thread(state
, stream
, msgmap
)){
463 state
->next_screen
= mail_index_screen
;
464 state
->view_skipped_index
= 0;
465 state
->mangled_screen
= 1;
468 mn_set_cur(msgmap
, new_msgno
);
469 if(THRD_AUTO_VIEW() && in_index
== View
){
471 thrd
= fetch_thread(stream
,
474 if(count_lflags_in_thread(stream
, thrd
,
477 if(view_thread(state
, stream
, msgmap
, 1)){
478 if(current_index_state
)
479 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
481 state
->view_skipped_index
= 1;
482 command
= MC_VIEW_TEXT
;
489 if(THRD_AUTO_VIEW() && in_index
!= View
){
490 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, new_msgno
));
491 if(thrd
&& thrd
->top
)
492 topthrd
= fetch_thread(stream
, thrd
->top
);
495 j
= count_lflags_in_thread(stream
, topthrd
, msgmap
, MN_NONE
);
498 if(!THRD_AUTO_VIEW() || in_index
== View
|| j
!= 1){
499 if(view_thread(state
, stream
, msgmap
, 1)
500 && current_index_state
)
501 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
505 state
->next_screen
= SCREEN_FUN_NULL
;
508 mn_set_cur(msgmap
, i
); /* put it back */
512 q_status_message1(SM_ORDER
, 0, 2,
513 _("Already on first %s in Zoomed Index"),
514 THRD_INDX() ? _("thread") : _("message"));
521 && ((now
= time(0)) > state
->last_nextitem_forcechk
)){
523 /* check at most once a second */
524 state
->last_nextitem_forcechk
= now
;
527 q_status_message1(SM_ORDER
, 0, 1, _("Already on first %s"),
528 THRD_INDX() ? _("thread") : _("message"));
535 /*---------- Next Message ----------*/
537 if(mn_get_total(msgmap
) > 0L
538 && ((i
= mn_get_cur(msgmap
)) < mn_get_total(msgmap
))){
539 mn_inc_cur(stream
, msgmap
,
540 (in_index
== View
&& THREADING()
541 && sp_viewing_a_thread(stream
))
544 ? MH_ANYTHD
: MH_NONE
);
545 if(i
== mn_get_cur(msgmap
)){
546 PINETHRD_S
*thrd
, *topthrd
;
548 if(THRD_INDX_ENABLED()){
550 mn_inc_cur(stream
, msgmap
, MH_ANYTHD
);
552 if(i
== mn_get_cur(msgmap
)){
553 if(any_lflagged(msgmap
, MN_HIDE
))
554 any_messages(NULL
, "more", "in Zoomed Index");
560 || F_ON(F_NEXT_THRD_WO_CONFIRM
, state
))
563 ret
= want_to(_("View next thread"), 'y', 'x',
567 q_status_message(SM_ORDER
, 0, 2,
568 _("Viewing next thread"));
569 new_msgno
= mn_get_cur(msgmap
);
570 mn_set_cur(msgmap
, i
);
571 if(unview_thread(state
, stream
, msgmap
)){
572 state
->next_screen
= mail_index_screen
;
573 state
->view_skipped_index
= 0;
574 state
->mangled_screen
= 1;
577 mn_set_cur(msgmap
, new_msgno
);
578 if(THRD_AUTO_VIEW() && in_index
== View
){
580 thrd
= fetch_thread(stream
,
583 if(count_lflags_in_thread(stream
, thrd
,
586 if(view_thread(state
, stream
, msgmap
, 1)){
587 if(current_index_state
)
588 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
590 state
->view_skipped_index
= 1;
591 command
= MC_VIEW_TEXT
;
598 if(THRD_AUTO_VIEW() && in_index
!= View
){
599 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, new_msgno
));
600 if(thrd
&& thrd
->top
)
601 topthrd
= fetch_thread(stream
, thrd
->top
);
604 j
= count_lflags_in_thread(stream
, topthrd
, msgmap
, MN_NONE
);
607 if(!THRD_AUTO_VIEW() || in_index
== View
|| j
!= 1){
608 if(view_thread(state
, stream
, msgmap
, 1)
609 && current_index_state
)
610 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
614 state
->next_screen
= SCREEN_FUN_NULL
;
617 mn_set_cur(msgmap
, i
); /* put it back */
621 && (thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, i
)))
623 && get_lflag(stream
, NULL
, thrd
->rawno
, MN_COLL
)){
624 q_status_message(SM_ORDER
, 0, 2,
625 _("Expand collapsed thread to see more messages"));
628 any_messages(NULL
, "more", "in Zoomed Index");
636 || (state
->context_current
->use
& CNTXT_INCMNG
)){
637 char nextfolder
[MAXPATH
];
639 strncpy(nextfolder
, state
->cur_folder
, sizeof(nextfolder
));
640 nextfolder
[sizeof(nextfolder
)-1] = '\0';
641 if(next_folder(NULL
, nextfolder
, sizeof(nextfolder
), nextfolder
,
642 state
->context_current
, NULL
, NULL
))
643 strncpy(prompt
, _(". Press TAB for next folder."),
646 strncpy(prompt
, _(". No more folders to TAB to."),
649 prompt
[sizeof(prompt
)-1] = '\0';
652 any_messages(NULL
, (mn_get_total(msgmap
) > 0L) ? "more" : NULL
,
653 prompt
[0] ? prompt
: NULL
);
656 && ((now
= time(0)) > state
->last_nextitem_forcechk
)){
658 /* check at most once a second */
659 state
->last_nextitem_forcechk
= now
;
666 /*---------- Delete message ----------*/
668 (void) cmd_delete(state
, msgmap
, MCMD_NONE
,
669 (in_index
== View
) ? cmd_delete_view
: cmd_delete_index
);
673 /*---------- Undelete message ----------*/
675 (void) cmd_undelete(state
, msgmap
, MCMD_NONE
);
676 update_titlebar_status();
680 /*---------- Reply to message ----------*/
682 (void) cmd_reply(state
, msgmap
, MCMD_NONE
);
686 /*---------- Forward message ----------*/
688 (void) cmd_forward(state
, msgmap
, MCMD_NONE
);
692 /*---------- Quit pine ------------*/
694 state
->next_screen
= quit_screen
;
695 dprint((1,"MAIL_CMD: quit\n"));
699 /*---------- Compose message ----------*/
701 state
->prev_screen
= (in_index
== View
) ? mail_view_screen
703 compose_screen(state
);
704 state
->mangled_screen
= 1;
705 if (state
->next_screen
)
710 /*---------- Alt Compose message ----------*/
712 state
->prev_screen
= (in_index
== View
) ? mail_view_screen
715 if(state
->next_screen
)
721 /*--------- Folders menu ------------*/
723 state
->start_in_context
= 1;
725 /*--------- Top of Folders list menu ------------*/
726 case MC_COLLECTIONS
:
727 state
->next_screen
= folder_screen
;
728 dprint((2,"MAIL_CMD: going to folder/collection menu\n"));
732 /*---------- Open specific new folder ----------*/
734 tc
= (state
->context_last
&& !NEWS_TEST(state
->context_current
))
735 ? state
->context_last
: state
->context_current
;
737 newfolder
= broach_folder(question_line
, 1, ¬realinbox
, &tc
);
739 visit_folder(state
, newfolder
, tc
, NULL
, notrealinbox
? 0L : DB_INBOXWOCNTXT
);
746 /*------- Go to Index Screen ----------*/
748 state
->next_screen
= mail_index_screen
;
751 /*------- Skip to next interesting message -----------*/
757 * If we're in the thread index, start looking after this
758 * thread. We don't want to match something in the current
761 start
= mn_get_cur(msgmap
);
762 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
763 if(mn_get_revsort(msgmap
)){
764 /* if reversed, top of thread is last one before next thread */
765 if(thrd
&& thrd
->top
)
766 start
= mn_raw2m(msgmap
, thrd
->top
);
769 /* last msg of thread is at the ends of the branches/nexts */
771 start
= mn_raw2m(msgmap
, thrd
->rawno
);
773 thrd
= fetch_thread(stream
, thrd
->branch
);
775 thrd
= fetch_thread(stream
, thrd
->next
);
782 * Flags is 0 in this case because we want to not skip
783 * messages inside of threads so that we can find threads
784 * which have some unseen messages even though the top-level
785 * of the thread is already seen.
786 * If new_msgno ends up being a message which is not visible
787 * because it isn't at the top-level, the current message #
788 * will be adjusted below in adjust_cur.
791 new_msgno
= next_sorted_flagged((F_UNDEL
793 | ((F_ON(F_TAB_TO_NEW
,state
))
795 stream
, start
, &flags
);
797 else if(THREADING() && sp_viewing_a_thread(stream
)){
798 PINETHRD_S
*thrd
, *topthrd
= NULL
;
800 start
= mn_get_cur(msgmap
);
803 * Things are especially complicated when we're viewing_a_thread
804 * from the thread index. First we have to check within the
805 * current thread for a new message. If none is found, then
806 * we search in the next threads and offer to continue in
807 * them. Then we offer to go to the next folder.
809 flags
= NSF_SKIP_CHID
;
810 new_msgno
= next_sorted_flagged((F_UNDEL
812 | ((F_ON(F_TAB_TO_NEW
,state
))
814 stream
, start
, &flags
);
816 * If we found a match then we are done, that is another message
817 * in the current thread index. Otherwise, we have to look
820 if(!(flags
& NSF_FLAG_MATCH
)){
825 new_msgno
= next_sorted_flagged((F_UNDEL
827 | ((F_ON(F_TAB_TO_NEW
,
830 stream
, start
, &flags
);
832 * If we got a match, new_msgno is a message in
833 * a different thread from the one we are viewing.
835 if(flags
& NSF_FLAG_MATCH
){
836 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
,new_msgno
));
837 if(thrd
&& thrd
->top
)
838 topthrd
= fetch_thread(stream
, thrd
->top
);
840 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD
, state
)){
841 static ESCKEY_S next_opt
[] = {
842 {'y', 'y', "Y", N_("Yes")},
843 {'n', 'n', "N", N_("No")},
844 {TAB
, 'n', "Tab", N_("NextNew")},
849 snprintf(prompt
, sizeof(prompt
), _("View thread number %s? "),
850 topthrd
? comatose(topthrd
->thrdno
) : "?");
852 snprintf(prompt
, sizeof(prompt
),
853 _("View message in thread number %s? "),
854 topthrd
? comatose(topthrd
->thrdno
) : "?");
856 prompt
[sizeof(prompt
)-1] = '\0';
858 ret
= radio_buttons(prompt
, -FOOTER_ROWS(state
),
859 next_opt
, 'y', 'x', NO_HELP
,
870 if(unview_thread(state
, stream
, msgmap
)){
871 state
->next_screen
= mail_index_screen
;
872 state
->view_skipped_index
= 0;
873 state
->mangled_screen
= 1;
876 mn_set_cur(msgmap
, new_msgno
);
877 if(THRD_AUTO_VIEW()){
879 if(count_lflags_in_thread(stream
, topthrd
,
880 msgmap
, MN_NONE
) == 1){
881 if(view_thread(state
, stream
, msgmap
, 1)){
882 if(current_index_state
)
883 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
885 state
->view_skipped_index
= 1;
886 command
= MC_VIEW_TEXT
;
892 if(view_thread(state
, stream
, msgmap
, 1) && current_index_state
)
893 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
895 state
->next_screen
= SCREEN_FUN_NULL
;
898 else if(ret
== 'n' && topthrd
){
900 * skip to end of this thread and look starting
901 * in the next thread.
903 if(mn_get_revsort(msgmap
)){
905 * if reversed, top of thread is last one
908 start
= mn_raw2m(msgmap
, topthrd
->rawno
);
912 * last msg of thread is at the ends of
917 start
= mn_raw2m(msgmap
, thrd
->rawno
);
919 thrd
= fetch_thread(stream
, thrd
->branch
);
921 thrd
= fetch_thread(stream
, thrd
->next
);
937 start
= mn_get_cur(msgmap
);
940 * If we are on a collapsed thread, start looking after the
941 * collapsed part, unless we are viewing the message.
943 if(THREADING() && in_index
!= View
){
948 rawno
= mn_m2raw(msgmap
, start
);
949 thrd
= fetch_thread(stream
, rawno
);
950 collapsed
= thrd
&& thrd
->next
951 && get_lflag(stream
, NULL
, rawno
, MN_COLL
);
954 if(mn_get_revsort(msgmap
)){
955 if(thrd
&& thrd
->top
)
956 start
= mn_raw2m(msgmap
, thrd
->top
);
960 start
= mn_raw2m(msgmap
, thrd
->rawno
);
962 thrd
= fetch_thread(stream
, thrd
->branch
);
964 thrd
= fetch_thread(stream
, thrd
->next
);
973 new_msgno
= next_sorted_flagged((F_UNDEL
975 | ((F_ON(F_TAB_TO_NEW
,state
))
977 stream
, start
, &flags
);
981 * If there weren't any unread messages left, OR there
982 * aren't any messages at all, we may want to offer to
983 * go on to the next folder...
985 if(flags
& NSF_FLAG_MATCH
){
986 mn_set_cur(msgmap
, new_msgno
);
988 adjust_cur_to_visible(stream
, msgmap
);
991 int in_inbox
= sp_flagged(stream
, SP_INBOX
);
993 if(state
->context_current
994 && ((NEWS_TEST(state
->context_current
)
995 && context_isambig(state
->cur_folder
))
996 || ((state
->context_current
->use
& CNTXT_INCMNG
)
998 || folder_index(state
->cur_folder
,
999 state
->context_current
,
1000 FI_FOLDER
) >= 0)))){
1001 char nextfolder
[MAXPATH
];
1002 MAILSTREAM
*nextstream
= NULL
;
1006 strncpy(nextfolder
, state
->cur_folder
, sizeof(nextfolder
));
1007 nextfolder
[sizeof(nextfolder
)-1] = '\0';
1009 if(!(next_folder(&nextstream
, nextfolder
, sizeof(nextfolder
), nextfolder
,
1010 state
->context_current
, &recent_cnt
,
1011 F_ON(F_TAB_NO_CONFIRM
,state
)
1012 ? NULL
: &did_cancel
))){
1014 static ESCKEY_S inbox_opt
[] = {
1015 {'y', 'y', "Y", N_("Yes")},
1016 {'n', 'n', "N", N_("No")},
1017 {TAB
, 'z', "Tab", N_("To Inbox")},
1021 if(F_ON(F_RET_INBOX_NO_CONFIRM
,state
))
1024 /* TRANSLATORS: this is a question, with some information followed
1025 by Return to INBOX? */
1026 if(state
->context_current
->use
&CNTXT_INCMNG
)
1027 snprintf(prompt
, sizeof(prompt
), _("No more incoming folders. Return to \"%s\"? "), state
->inbox_name
);
1029 snprintf(prompt
, sizeof(prompt
), _("No more news groups. Return to \"%s\"? "), state
->inbox_name
);
1031 ret
= radio_buttons(prompt
, -FOOTER_ROWS(state
),
1032 inbox_opt
, 'y', 'x',
1037 * 'z' is a synonym for 'y'. It is not 'y'
1038 * so that it isn't displayed as a default
1039 * action with square-brackets around it
1042 if(ret
== 'y' || ret
== 'z'){
1043 visit_folder(state
, state
->inbox_name
,
1044 state
->context_current
,
1045 NULL
, DB_INBOXWOCNTXT
);
1049 else if (did_cancel
)
1050 cmd_cancelled(NULL
);
1052 if(state
->context_current
->use
&CNTXT_INCMNG
)
1053 q_status_message(SM_ORDER
, 0, 2, _("No more incoming folders"));
1055 q_status_message(SM_ORDER
, 0, 2, _("No more news groups"));
1061 {char *front
, type
[80], cnt
[80], fbuf
[MAX_SCREEN_COLS
/2+1];
1062 int rbspace
, avail
, need
, take_back
;
1066 * Incoming_folder_ or news_group_ or folder_ or group_
1068 * _(13 recent) or _(some recent) or nothing
1071 front
= "View next";
1073 (state
->context_current
->use
& CNTXT_INCMNG
)
1074 ? "Incoming folder" : "news group",
1076 type
[sizeof(type
)-1] = '\0';
1077 snprintf(cnt
, sizeof(cnt
), " (%.*s %s)", sizeof(cnt
)-20,
1078 recent_cnt
? long2string(recent_cnt
) : "some",
1079 F_ON(F_TAB_USES_UNSEEN
, ps_global
)
1080 ? "unseen" : "recent");
1081 cnt
[sizeof(cnt
)-1] = '\0';
1084 * Space reserved for radio_buttons call.
1085 * If we make this 3 then radio_buttons won't mess
1086 * with the prompt. If we make it 2, then we get
1087 * one more character to use but radio_buttons will
1088 * cut off the last character of our prompt, which is
1089 * ok because it is a space.
1092 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
1094 need
= strlen(front
)+1 + strlen(type
)+1 +
1095 + strlen(nextfolder
)+2 + strlen(cnt
) +
1098 take_back
= strlen(type
);
1100 (state
->context_current
->use
& CNTXT_INCMNG
)
1101 ? "folder" : "group", sizeof(type
));
1102 take_back
-= strlen(type
);
1105 need
-= strlen(cnt
);
1110 snprintf(prompt
, sizeof(prompt
), "%.*s %.*s \"%.*s\"%.*s? ",
1111 sizeof(prompt
)/8, front
,
1112 sizeof(prompt
)/8, type
,
1114 short_str(nextfolder
, fbuf
, sizeof(fbuf
),
1115 strlen(nextfolder
) -
1116 ((need
>avail
) ? (need
-avail
) : 0),
1118 sizeof(prompt
)/8, cnt
);
1119 prompt
[sizeof(prompt
)-1] = '\0';
1123 * When help gets added, this'll have to become
1124 * a loop like the rest...
1126 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD
, state
)){
1127 static ESCKEY_S next_opt
[] = {
1128 {'y', 'y', "Y", N_("Yes")},
1129 {'n', 'n', "N", N_("No")},
1130 {TAB
, 'n', "Tab", N_("NextNew")},
1134 ret
= radio_buttons(prompt
, -FOOTER_ROWS(state
),
1135 next_opt
, 'y', 'x', NO_HELP
,
1138 cmd_cancelled(NULL
);
1146 if(nextstream
&& sp_dead_stream(nextstream
))
1149 visit_folder(state
, nextfolder
,
1150 state
->context_current
, nextstream
,
1152 /* visit_folder takes care of nextstream */
1160 pine_mail_close(nextstream
);
1164 (mn_get_total(msgmap
) > 0L)
1165 ? IS_NEWS(stream
) ? "more undeleted" : "more new"
1175 /*------- Zoom -----------*/
1178 * Right now the way zoom is implemented is sort of silly.
1179 * There are two per-message flags where just one and a
1180 * global "zoom mode" flag to suppress messags from the index
1183 if(any_messages(msgmap
, NULL
, "to Zoom on")){
1184 if(unzoom_index(state
, stream
, msgmap
)){
1185 dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
1186 q_status_message(SM_ORDER
,0,2, _("Index Zoom Mode is now off"));
1188 else if((i
= zoom_index(state
, stream
, msgmap
, MN_SLCT
)) != 0){
1189 if(any_lflagged(msgmap
, MN_HIDE
)){
1190 dprint((4,"\n\n ---- Entering ZOOM mode ----\n"));
1191 q_status_message4(SM_ORDER
, 0, 2,
1192 _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"),
1193 THRD_INDX() ? "" : comatose(i
),
1194 THRD_INDX() ? "" : " ",
1195 THRD_INDX() ? _("threads") : _("message"),
1196 THRD_INDX() ? "" : plural(i
));
1199 q_status_message(SM_ORDER
, 0, 2,
1200 _("All messages selected, so not entering Index Zoom Mode"));
1203 any_messages(NULL
, "selected", "to Zoom on");
1209 /*---------- print message on paper ----------*/
1211 if(any_messages(msgmap
, NULL
, "to print"))
1212 (void) cmd_print(state
, msgmap
, MCMD_NONE
, in_index
);
1217 /*---------- Take Address ----------*/
1219 if(F_ON(F_ENABLE_ROLE_TAKE
, state
) ||
1220 any_messages(msgmap
, NULL
, "to Take address from"))
1221 (void) cmd_take_addr(state
, msgmap
, MCMD_NONE
);
1226 /*---------- Save Message ----------*/
1228 if(any_messages(msgmap
, NULL
, "to Save"))
1229 (void) cmd_save(state
, stream
, msgmap
, MCMD_NONE
, in_index
);
1234 /*---------- Export message ----------*/
1236 if(any_messages(msgmap
, NULL
, "to Export")){
1237 (void) cmd_export(state
, msgmap
, question_line
, MCMD_NONE
);
1238 state
->mangled_footer
= 1;
1244 /*---------- Expunge ----------*/
1246 (void) cmd_expunge(state
, stream
, msgmap
, MCMD_NONE
);
1250 /*------- Unexclude -----------*/
1252 if(!(IS_NEWS(stream
) && stream
->rdonly
)){
1253 q_status_message(SM_ORDER
, 0, 3,
1254 _("Unexclude not available for mail folders"));
1256 else if(any_lflagged(msgmap
, MN_EXLD
)){
1262 * Since excluded means "hidden deleted" and "killed",
1263 * the count should reflect the former.
1265 pgm
= mail_newsearchpgm();
1267 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
1268 for(i
= 1L, del_count
= 0L; i
<= stream
->nmsgs
; i
++)
1269 if((mc
= mail_elt(stream
, i
)) && mc
->searched
1270 && get_lflag(stream
, NULL
, i
, MN_EXLD
)
1271 && !(msgno_exceptions(stream
, i
, "0", &exbits
, FALSE
)
1272 && (exbits
& MSG_EX_FILTERED
)))
1276 state
->mangled_footer
= 1;
1277 snprintf(prompt
, sizeof(prompt
), "UNexclude %ld message%s in %.*s", del_count
,
1278 plural(del_count
), sizeof(prompt
)-40,
1279 pretty_fn(state
->cur_folder
));
1280 prompt
[sizeof(prompt
)-1] = '\0';
1281 if(F_ON(F_FULL_AUTO_EXPUNGE
, state
)
1282 || (F_ON(F_AUTO_EXPUNGE
, state
)
1283 && (state
->context_current
1284 && (state
->context_current
->use
& CNTXT_INCMNG
))
1285 && context_isambig(state
->cur_folder
))
1286 || want_to(prompt
, 'y', 0, NO_HELP
, WT_NORM
) == 'y'){
1287 long save_cur_rawno
;
1288 int were_viewing_a_thread
;
1290 save_cur_rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
1291 were_viewing_a_thread
= (THREADING()
1292 && sp_viewing_a_thread(stream
));
1294 if(msgno_include(stream
, msgmap
, MI_NONE
)){
1295 clear_index_cache(stream
, 0);
1297 if(stream
&& stream
->spare
)
1298 erase_threading_info(stream
, msgmap
);
1300 refresh_sort(stream
, msgmap
, SRT_NON
);
1303 if(were_viewing_a_thread
){
1304 if(save_cur_rawno
> 0L)
1305 mn_set_cur(msgmap
, mn_raw2m(msgmap
,save_cur_rawno
));
1307 if(view_thread(state
, stream
, msgmap
, 1) && current_index_state
)
1308 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
1311 if(save_cur_rawno
> 0L)
1312 mn_set_cur(msgmap
, mn_raw2m(msgmap
,save_cur_rawno
));
1314 state
->mangled_screen
= 1;
1315 q_status_message2(SM_ORDER
, 0, 4,
1316 "%s message%s UNexcluded",
1317 long2string(del_count
),
1320 if(in_index
!= View
)
1321 adjust_cur_to_visible(stream
, msgmap
);
1324 any_messages(NULL
, NULL
, "UNexcluded");
1327 any_messages(NULL
, "excluded", "to UNexclude");
1330 any_messages(NULL
, "excluded", "to UNexclude");
1335 /*------- Make Selection -----------*/
1337 if(any_messages(msgmap
, NULL
, "to Select")){
1338 if(aggregate_select(state
, msgmap
, question_line
, in_index
) == 0
1339 && (in_index
== MsgIndx
|| in_index
== ThrdIndx
)
1340 && F_ON(F_AUTO_ZOOM
, state
)
1341 && any_lflagged(msgmap
, MN_SLCT
) > 0L
1342 && !any_lflagged(msgmap
, MN_HIDE
))
1343 (void) zoom_index(state
, stream
, msgmap
, MN_SLCT
);
1349 /*------- Toggle Current Message Selection State -----------*/
1351 if(any_messages(msgmap
, NULL
, NULL
)){
1352 if((select_by_current(state
, msgmap
, in_index
)
1353 || (F_OFF(F_UNSELECT_WONT_ADVANCE
, state
)
1354 && !any_lflagged(msgmap
, MN_HIDE
)))
1355 && (i
= mn_get_cur(msgmap
)) < mn_get_total(msgmap
)){
1356 /* advance current */
1357 mn_inc_cur(stream
, msgmap
,
1358 (in_index
== View
&& THREADING()
1359 && sp_viewing_a_thread(stream
))
1361 : (in_index
== View
)
1362 ? MH_ANYTHD
: MH_NONE
);
1369 /*------- Apply command -----------*/
1371 if(any_messages(msgmap
, NULL
, NULL
)){
1372 if(any_lflagged(msgmap
, MN_SLCT
) > 0L){
1373 if(apply_command(state
, stream
, msgmap
, 0,
1374 AC_NONE
, question_line
)){
1375 if(F_ON(F_AUTO_UNSELECT
, state
)){
1376 agg_select_all(stream
, msgmap
, NULL
, 0);
1377 unzoom_index(state
, stream
, msgmap
);
1379 else if(F_ON(F_AUTO_UNZOOM
, state
))
1380 unzoom_index(state
, stream
, msgmap
);
1384 any_messages(NULL
, NULL
, "to Apply command to. Try \"Select\"");
1390 /*-------- Sort command -------*/
1393 int were_threading
= THREADING();
1394 SortOrder sort
= mn_get_sort(msgmap
);
1395 int rev
= mn_get_revsort(msgmap
);
1397 dprint((1,"MAIL_CMD: sort\n"));
1398 if(select_sort(state
, question_line
, &sort
, &rev
)){
1399 /* $ command reinitializes threading collapsed/expanded info */
1400 if(SORT_IS_THREADED(msgmap
) && !SEP_THRDINDX())
1401 erase_threading_info(stream
, msgmap
);
1403 if(ps_global
&& ps_global
->ttyo
){
1404 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
1405 ps_global
->mangled_footer
= 1;
1408 sort_folder(stream
, msgmap
, sort
, rev
, SRT_VRB
|SRT_MAN
);
1411 state
->mangled_footer
= 1;
1414 * We've changed whether we are threading or not so we need to
1415 * exit the index and come back in so that we switch between the
1416 * thread index and the regular index. Sort_folder will have
1417 * reset viewing_a_thread if necessary.
1420 && ((!were_threading
&& THREADING())
1421 || (were_threading
&& !THREADING()))){
1422 state
->next_screen
= mail_index_screen
;
1423 state
->mangled_screen
= 1;
1430 /*------- Toggle Full Headers -----------*/
1432 state
->full_header
++;
1433 if(state
->full_header
== 1){
1434 if(!(state
->quote_suppression_threshold
1435 && (state
->some_quoting_was_suppressed
|| in_index
!= View
)))
1436 state
->full_header
++;
1438 else if(state
->full_header
> 2)
1439 state
->full_header
= 0;
1441 switch(state
->full_header
){
1443 q_status_message(SM_ORDER
, 0, 3,
1444 _("Display of full headers is now off."));
1448 q_status_message1(SM_ORDER
, 0, 3,
1449 _("Quotes displayed, use %s to see full headers"),
1450 F_ON(F_USE_FK
, state
) ? "F9" : "H");
1454 q_status_message(SM_ORDER
, 0, 3,
1455 _("Display of full headers is now on."));
1470 /*------- Try to decrypt message -----------*/
1472 if(state
->smime
&& state
->smime
->need_passphrase
)
1473 smime_get_passphrase();
1479 smime_info_screen(state
);
1484 /*------- Bounce -----------*/
1486 (void) cmd_bounce(state
, msgmap
, MCMD_NONE
);
1490 /*------- Flag -----------*/
1492 dprint((4, "\n - flag message -\n"));
1493 (void) cmd_flag(state
, msgmap
, MCMD_NONE
);
1497 /*------- Pipe message -----------*/
1499 (void) cmd_pipe(state
, msgmap
, MCMD_NONE
);
1503 /*--------- Default, unknown command ----------*/
1505 alpine_panic("Unexpected command case");
1509 return((a_changed
|| mn_get_cur(msgmap
) != old_msgno
) ? 1 : 0);
1514 /*----------------------------------------------------------------------
1515 Map some of the special characters into sensible strings for human
1517 c is a UCS-4 character!
1520 pretty_command(UCS c
)
1522 static char buf
[10];
1529 case ' ' : s
= "SPACE"; break;
1530 case '\033' : s
= "ESC"; break;
1531 case '\177' : s
= "DEL"; break;
1532 case ctrl('I') : s
= "TAB"; break;
1533 case ctrl('J') : s
= "LINEFEED"; break;
1534 case ctrl('M') : s
= "RETURN"; break;
1535 case ctrl('Q') : s
= "XON"; break;
1536 case ctrl('S') : s
= "XOFF"; break;
1537 case KEY_UP
: s
= "Up Arrow"; break;
1538 case KEY_DOWN
: s
= "Down Arrow"; break;
1539 case KEY_RIGHT
: s
= "Right Arrow"; break;
1540 case KEY_LEFT
: s
= "Left Arrow"; break;
1541 case KEY_PGUP
: s
= "Prev Page"; break;
1542 case KEY_PGDN
: s
= "Next Page"; break;
1543 case KEY_HOME
: s
= "Home"; break;
1544 case KEY_END
: s
= "End"; break;
1545 case KEY_DEL
: s
= "Delete"; break; /* Not necessary DEL! */
1546 case KEY_JUNK
: s
= "Junk!"; break;
1547 case BADESC
: s
= "Bad Esc"; break;
1548 case NO_OP_IDLE
: s
= "NO_OP_IDLE"; break;
1549 case NO_OP_COMMAND
: s
= "NO_OP_COMMAND"; break;
1550 case KEY_RESIZE
: s
= "KEY_RESIZE"; break;
1551 case KEY_UTF8
: s
= "KEY_UTF8"; break;
1552 case KEY_MOUSE
: s
= "KEY_MOUSE"; break;
1553 case KEY_SCRLUPL
: s
= "KEY_SCRLUPL"; break;
1554 case KEY_SCRLDNL
: s
= "KEY_SCRLDNL"; break;
1555 case KEY_SCRLTO
: s
= "KEY_SCRLTO"; break;
1556 case KEY_XTERM_MOUSE
: s
= "KEY_XTERM_MOUSE"; break;
1557 case KEY_DOUBLE_ESC
: s
= "KEY_DOUBLE_ESC"; break;
1558 case CTRL_KEY_UP
: s
= "Ctrl Up Arrow"; break;
1559 case CTRL_KEY_DOWN
: s
= "Ctrl Down Arrow"; break;
1560 case CTRL_KEY_RIGHT
: s
= "Ctrl Right Arrow"; break;
1561 case CTRL_KEY_LEFT
: s
= "Ctrl Left Arrow"; break;
1574 snprintf(s
= buf
, sizeof(buf
), "F%ld", (long) (c
- PF1
+ 1));
1578 if(c
< ' ' || (c
>= 0x80 && c
< 0xA0)){
1583 d
= (c
& 0x1f) + 'A' - 1;
1584 snprintf(s
= buf
, sizeof(buf
), "%c%c", c1
? '~' : '^', d
);
1587 memset(buf
, 0, sizeof(buf
));
1588 utf8_put((unsigned char *) buf
, (unsigned long) c
);
1598 /*----------------------------------------------------------------------
1599 Complain about bogus input
1601 Args: ch -- input command to complain about
1602 help -- string indicating where to get help
1606 bogus_command(UCS cmd
, char *help
)
1608 if(cmd
== ctrl('Q') || cmd
== ctrl('S'))
1609 q_status_message1(SM_ASYNC
, 0, 2,
1610 "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.",
1611 pretty_command(cmd
));
1612 else if(cmd
== KEY_JUNK
)
1613 q_status_message3(SM_ORDER
, 0, 2,
1614 "Invalid key pressed.%s%s%s",
1615 (help
) ? " Use " : "",
1617 (help
) ? " for help" : "");
1619 q_status_message4(SM_ORDER
, 0, 2,
1620 "Command \"%s\" not defined for this screen.%s%s%s",
1621 pretty_command(cmd
),
1622 (help
) ? " Use " : "",
1624 (help
) ? " for help" : "");
1629 bogus_utf8_command(char *cmd
, char *help
)
1631 q_status_message4(SM_ORDER
, 0, 2,
1632 "Command \"%s\" not defined for this screen.%s%s%s",
1634 (help
) ? " Use " : "",
1636 (help
) ? " for help" : "");
1640 /*----------------------------------------------------------------------
1641 Execute FLAG message command
1643 Args: state -- Various satate info
1644 msgmap -- map of c-client to local message numbers
1646 Result: with side effect of "current" message FLAG flag set or UNset
1650 cmd_flag(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
1652 char *flagit
, *seq
, *screen_text
[20], **exp
, **p
, *answer
= NULL
;
1653 char *keyword_array
[2];
1654 int user_defined_flags
= 0, mailbox_flags
= 0;
1655 int directly_to_maint_screen
= 0;
1656 long unflagged
, flagged
, flags
, rawno
;
1657 MESSAGECACHE
*mc
= NULL
;
1659 int i
, cnt
, is_set
, trouble
= 0, rv
= 0;
1661 struct flag_table
*fp
, *ftbl
= NULL
;
1662 struct flag_screen flag_screen
;
1663 static char *flag_screen_text1
[] = {
1664 N_(" Set desired flags for current message below. An 'X' means set"),
1665 N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."),
1669 static char *flag_screen_text2
[] = {
1670 N_(" Set desired flags below for selected messages. A '?' means to"),
1671 N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"),
1672 N_(" to unset it. Use the \"Return\" key to toggle, and choose"),
1673 N_(" \"Exit\" when finished."),
1677 static struct flag_table default_ftbl
[] = {
1678 {N_("Important"), h_flag_important
, F_FLAG
, 0, 0, NULL
, NULL
},
1679 {N_("New"), h_flag_new
, F_SEEN
, 0, 0, NULL
, NULL
},
1680 {N_("Answered"), h_flag_answered
, F_ANS
, 0, 0, NULL
, NULL
},
1681 {N_("Forwarded"), h_flag_forwarded
, F_FWD
, 0, 0, NULL
, NULL
},
1682 {N_("Deleted"), h_flag_deleted
, F_DEL
, 0, 0, NULL
, NULL
},
1683 {NULL
, NO_HELP
, 0, 0, 0, NULL
, NULL
}
1686 /* Only check for dead stream for now. Should check permanent flags
1689 if(!(any_messages(msgmap
, NULL
, "to Flag") && can_set_flag(state
, "flag", 1)))
1692 if(sp_io_error_on_stream(state
->mail_stream
)){
1693 sp_set_io_error_on_stream(state
->mail_stream
, 0);
1694 pine_mail_check(state
->mail_stream
); /* forces write */
1700 user_defined_flags
= 0;
1706 /* count how large ftbl will be */
1707 for(cnt
= 0; default_ftbl
[cnt
].name
; cnt
++)
1710 /* add user flags */
1711 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1712 if(!((kw
->nick
&& !strucmp(FORWARDED_FLAG
, kw
->nick
)) || (kw
->kw
&& !strucmp(FORWARDED_FLAG
, kw
->kw
)))){
1713 user_defined_flags
++;
1719 * Add mailbox flags that aren't user-defined flags.
1720 * Don't consider it if it matches either one of our defined
1721 * keywords or one of our defined nicknames for a keyword.
1723 for(i
= 0; stream_to_user_flag_name(state
->mail_stream
, i
); i
++){
1726 q
= stream_to_user_flag_name(state
->mail_stream
, i
);
1728 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1729 if((kw
->nick
&& !strucmp(kw
->nick
, q
)) || (kw
->kw
&& !strucmp(kw
->kw
, q
)))
1734 if(!kw
&& !(q
&& !strucmp(FORWARDED_FLAG
, q
))){
1740 cnt
+= (user_defined_flags
? 2 : 0) + (mailbox_flags
? 2 : 0);
1742 /* set up ftbl, first the system flags */
1743 ftbl
= (struct flag_table
*) fs_get((cnt
+1) * sizeof(*ftbl
));
1744 memset(ftbl
, 0, (cnt
+1) * sizeof(*ftbl
));
1745 for(i
= 0, fp
= ftbl
; default_ftbl
[i
].name
; i
++, fp
++){
1746 fp
->name
= cpystr(default_ftbl
[i
].name
);
1747 fp
->help
= default_ftbl
[i
].help
;
1748 fp
->flag
= default_ftbl
[i
].flag
;
1749 fp
->set
= default_ftbl
[i
].set
;
1750 fp
->ukn
= default_ftbl
[i
].ukn
;
1753 if(user_defined_flags
){
1754 fp
->flag
= F_COMMENT
;
1755 fp
->name
= cpystr("");
1757 fp
->flag
= F_COMMENT
;
1758 len
= strlen(_("User-defined Keywords from Setup/Config"));
1759 fp
->name
= (char *) fs_get((len
+6+6+1) * sizeof(char));
1760 snprintf(fp
->name
, len
+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config"));
1764 /* then the user-defined keywords */
1765 if(user_defined_flags
)
1766 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1767 if(!((kw
->nick
&& !strucmp(FORWARDED_FLAG
, kw
->nick
))
1768 || (kw
->kw
&& !strucmp(FORWARDED_FLAG
, kw
->kw
)))){
1769 fp
->name
= cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
1770 fp
->keyword
= cpystr(kw
->kw
? kw
->kw
: "");
1771 if(kw
->nick
&& kw
->kw
){
1774 l
= strlen(kw
->kw
)+2;
1775 fp
->comment
= (char *) fs_get((l
+1) * sizeof(char));
1776 snprintf(fp
->comment
, l
+1, "(%.*s)", strlen(kw
->kw
), kw
->kw
);
1777 fp
->comment
[l
] = '\0';
1780 fp
->help
= h_flag_user_flag
;
1781 fp
->flag
= F_KEYWORD
;
1789 fp
->flag
= F_COMMENT
;
1790 fp
->name
= cpystr("");
1792 fp
->flag
= F_COMMENT
;
1793 len
= strlen(_("Other keywords in the mailbox that are not user-defined"));
1794 fp
->name
= (char *) fs_get((len
+6+6+1) * sizeof(char));
1795 snprintf(fp
->name
, len
+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined"));
1799 /* then the extra mailbox-defined keywords */
1801 for(i
= 0; stream_to_user_flag_name(state
->mail_stream
, i
); i
++){
1804 q
= stream_to_user_flag_name(state
->mail_stream
, i
);
1806 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1807 if((kw
->nick
&& !strucmp(kw
->nick
, q
)) || (kw
->kw
&& !strucmp(kw
->kw
, q
)))
1812 if(!kw
&& !(q
&& !strucmp(FORWARDED_FLAG
, q
))){
1813 fp
->name
= cpystr(q
);
1814 fp
->keyword
= cpystr(q
);
1815 fp
->help
= h_flag_user_flag
;
1816 fp
->flag
= F_KEYWORD
;
1823 flag_screen
.flag_table
= &ftbl
;
1824 flag_screen
.explanation
= screen_text
;
1826 if(MCMD_ISAGG(aopt
)){
1827 if(!pseudo_selected(ps_global
->mail_stream
, msgmap
)){
1828 free_flag_table(&ftbl
);
1832 exp
= flag_screen_text2
;
1833 for(fp
= ftbl
; fp
->name
; fp
++){
1834 fp
->set
= CMD_FLAG_UNKN
; /* set to unknown */
1838 else if(state
->mail_stream
1839 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
1840 && rawno
<= state
->mail_stream
->nmsgs
1841 && (mc
= mail_elt(state
->mail_stream
, rawno
))){
1842 exp
= flag_screen_text1
;
1843 for(fp
= &ftbl
[0]; fp
->name
; fp
++){
1845 if(fp
->flag
== F_KEYWORD
){
1846 /* see if this keyword is defined for this message */
1847 fp
->set
= CMD_FLAG_CLEAR
;
1848 if(user_flag_is_set(state
->mail_stream
,
1849 rawno
, fp
->keyword
))
1850 fp
->set
= CMD_FLAG_SET
;
1852 else if(fp
->flag
== F_FWD
){
1853 /* see if forwarded keyword is defined for this message */
1854 fp
->set
= CMD_FLAG_CLEAR
;
1855 if(user_flag_is_set(state
->mail_stream
,
1856 rawno
, FORWARDED_FLAG
))
1857 fp
->set
= CMD_FLAG_SET
;
1859 else if(fp
->flag
!= F_COMMENT
)
1860 fp
->set
= ((fp
->flag
== F_SEEN
&& !mc
->seen
)
1861 || (fp
->flag
== F_DEL
&& mc
->deleted
)
1862 || (fp
->flag
== F_FLAG
&& mc
->flagged
)
1863 || (fp
->flag
== F_ANS
&& mc
->answered
))
1864 ? CMD_FLAG_SET
: CMD_FLAG_CLEAR
;
1868 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
1869 _("Error accessing message data"));
1870 free_flag_table(&ftbl
);
1874 if(directly_to_maint_screen
)
1875 goto the_maint_screen
;
1878 if (mswin_usedialog ()) {
1879 if (!os_flagmsgdialog (&ftbl
[0])){
1880 free_flag_table(&ftbl
);
1887 int use_maint_screen
;
1888 int keyword_shortcut
= 0;
1890 use_maint_screen
= F_ON(F_FLAG_SCREEN_DFLT
, ps_global
);
1892 if(!use_maint_screen
){
1894 * We're going to call cmd_flag_prompt(). We need
1895 * to decide whether or not to offer the keyword setting
1896 * shortcut. We'll offer it if the user has the feature
1897 * enabled AND there are some possible keywords that could
1900 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
)){
1901 for(fp
= &ftbl
[0]; fp
->name
&& !keyword_shortcut
; fp
++){
1902 if(fp
->flag
== F_KEYWORD
){
1906 first_char
= (fp
->name
&& fp
->name
[0])
1908 if(isascii(first_char
) && isupper(first_char
))
1909 first_char
= tolower((unsigned char) first_char
);
1911 for(tp
=flag_text_opt
; tp
->ch
!= -1; tp
++)
1912 if(tp
->ch
== first_char
)
1921 use_maint_screen
= !cmd_flag_prompt(state
, &flag_screen
,
1926 if(use_maint_screen
){
1927 for(p
= &screen_text
[0]; *exp
; p
++, exp
++)
1932 directly_to_maint_screen
= flag_maintenance_screen(state
, &flag_screen
);
1936 /* reaquire the elt pointer */
1937 mc
= (state
->mail_stream
1938 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
1939 && rawno
<= state
->mail_stream
->nmsgs
)
1940 ? mail_elt(state
->mail_stream
, rawno
) : NULL
;
1942 for(fp
= ftbl
; mc
&& fp
->name
; fp
++){
1946 if((!MCMD_ISAGG(aopt
) && fp
->set
!= !mc
->seen
)
1947 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
1955 unflagged
= F_UNSEEN
;
1962 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->answered
)
1963 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
1964 flagit
= "\\ANSWERED";
1967 unflagged
= F_UNANS
;
1978 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->deleted
)
1979 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
1980 flagit
= "\\DELETED";
1983 unflagged
= F_UNDEL
;
1994 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->flagged
)
1995 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
1996 flagit
= "\\FLAGGED";
1999 unflagged
= F_UNFLAG
;
2010 if(!MCMD_ISAGG(aopt
)){
2011 /* see if forwarded is defined for this message */
2012 is_set
= CMD_FLAG_CLEAR
;
2013 if(user_flag_is_set(state
->mail_stream
,
2014 mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
2016 is_set
= CMD_FLAG_SET
;
2019 if((!MCMD_ISAGG(aopt
) && fp
->set
!= is_set
)
2020 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2021 flagit
= FORWARDED_FLAG
;
2024 unflagged
= F_UNFWD
;
2035 if(!MCMD_ISAGG(aopt
)){
2036 /* see if this keyword is defined for this message */
2037 is_set
= CMD_FLAG_CLEAR
;
2038 if(user_flag_is_set(state
->mail_stream
,
2039 mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
2041 is_set
= CMD_FLAG_SET
;
2044 if((!MCMD_ISAGG(aopt
) && fp
->set
!= is_set
)
2045 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2046 flagit
= fp
->keyword
;
2047 keyword_array
[0] = fp
->keyword
;
2048 keyword_array
[1] = NULL
;
2051 unflagged
= F_UNKEYWORD
;
2055 unflagged
= F_KEYWORD
;
2067 && (seq
= currentf_sequence(state
->mail_stream
, msgmap
,
2068 unflagged
, &flagged
, unflagged
& F_DEL
,
2069 (fp
->flag
== F_KEYWORD
2070 && unflagged
== F_KEYWORD
)
2071 ? keyword_array
: NULL
,
2072 (fp
->flag
== F_KEYWORD
2073 && unflagged
== F_UNKEYWORD
)
2074 ? keyword_array
: NULL
))){
2076 * For user keywords, we may have to create the flag in
2077 * the folder if it doesn't already exist and we are setting
2078 * it (as opposed to clearing it). Mail_flag will
2079 * do that for us, but it's failure isn't very friendly
2080 * error-wise. So we try to make it a little smoother.
2082 if(!(fp
->flag
== F_KEYWORD
|| fp
->flag
== F_FWD
) || !fp
->set
2083 || ((i
=user_flag_index(state
->mail_stream
, flagit
)) >= 0
2085 mail_flag(state
->mail_stream
, seq
, flagit
, flags
);
2087 /* trouble, see if we can add the user flag */
2088 if(state
->mail_stream
->kwd_create
)
2089 mail_flag(state
->mail_stream
, seq
, flagit
, flags
);
2093 if(some_user_flags_defined(state
->mail_stream
))
2094 q_status_message(SM_ORDER
, 3, 4,
2095 _("No more keywords allowed in this folder!"));
2096 else if(fp
->flag
== F_FWD
)
2097 q_status_message(SM_ORDER
, 3, 4,
2098 _("Cannot add keywords for this folder so cannot set Forwarded flag"));
2100 q_status_message(SM_ORDER
, 3, 4,
2101 _("Cannot add keywords for this folder"));
2105 fs_give((void **) &seq
);
2106 if(flagged
&& !trouble
){
2107 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%slagged%s%s%s%s%s message%s%s \"%s\"",
2108 (fp
->set
) ? "F" : "Unf",
2109 MCMD_ISAGG(aopt
) ? " " : "",
2110 MCMD_ISAGG(aopt
) ? long2string(flagged
) : "",
2111 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2113 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2114 ? comatose(mn_total_cur(msgmap
)) : "",
2115 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2117 MCMD_ISAGG(aopt
) ? plural(flagged
) : " ",
2118 MCMD_ISAGG(aopt
) ? "" : long2string(mn_get_cur(msgmap
)),
2120 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2121 q_status_message(SM_ORDER
, 0, 2, answer
= tmp_20k_buf
);
2127 free_flag_table(&ftbl
);
2129 if(directly_to_maint_screen
)
2132 if(MCMD_ISAGG(aopt
))
2133 restore_selected(msgmap
);
2136 q_status_message(SM_ORDER
, 0, 2, _("No flags changed."));
2142 /*----------------------------------------------------------------------
2143 Offer concise status line flag prompt
2145 Args: state -- Various satate info
2146 flags -- flags to offer setting
2148 Result: TRUE if flag to set specified in flags struct or FALSE otw
2152 cmd_flag_prompt(struct pine
*state
, struct flag_screen
*flags
, int allow_keyword_shortcuts
)
2154 int r
, setflag
= 1, first_char
;
2155 struct flag_table
*fp
;
2157 char *ftext
, *ftext_not
;
2158 static char *flag_text
=
2159 N_("Flag New, Deleted, Answered, Forwarded or Important ? ");
2160 static char *flag_text_ak
=
2161 N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? ");
2162 static char *flag_text_not
=
2163 N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? ");
2164 static char *flag_text_ak_not
=
2165 N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? ");
2167 if(allow_keyword_shortcuts
){
2169 ESCKEY_S
*dp
, *sp
, *tp
;
2171 for(sp
=flag_text_opt
; sp
->ch
!= -1; sp
++)
2174 for(fp
=(flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++)
2175 if(fp
->flag
== F_KEYWORD
)
2178 /* set up an ESCKEY_S list which includes invisible keys for keywords */
2179 ek
= (ESCKEY_S
*) fs_get((cnt
+ 1) * sizeof(*ek
));
2180 memset(ek
, 0, (cnt
+1) * sizeof(*ek
));
2181 for(dp
=ek
, sp
=flag_text_opt
; sp
->ch
!= -1; sp
++, dp
++)
2184 for(fp
=(flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++){
2185 if(fp
->flag
== F_KEYWORD
){
2186 first_char
= (fp
->name
&& fp
->name
[0]) ? fp
->name
[0] : -2;
2187 if(isascii(first_char
) && isupper(first_char
))
2188 first_char
= tolower((unsigned char) first_char
);
2191 * Check to see if an earlier keyword in the list, or one of
2192 * the builtin system letters already uses this character.
2193 * If so, the first one wins.
2195 for(tp
=ek
; tp
->ch
!= 0; tp
++)
2196 if(tp
->ch
== first_char
)
2200 continue; /* skip it, already used that char */
2202 dp
->ch
= first_char
;
2203 dp
->rval
= first_char
;
2211 ftext
= _(flag_text_ak
);
2212 ftext_not
= _(flag_text_ak_not
);
2216 ftext
= _(flag_text
);
2217 ftext_not
= _(flag_text_not
);
2221 r
= radio_buttons(setflag
? ftext
: ftext_not
,
2222 -FOOTER_ROWS(state
), ek
, '*', SEQ_EXCEPTION
-1,
2223 NO_HELP
, RB_NORM
| RB_SEQ_SENSITIVE
);
2225 * It is SEQ_EXCEPTION-1 just so that it is some value that isn't
2226 * being used otherwise. The keywords use up all the possible
2227 * letters, so a negative number is good, but it has to be different
2228 * from other negative return values.
2230 if(r
== SEQ_EXCEPTION
-1) /* ol'cancelrooney */
2232 else if(r
== 10) /* return and goto flag screen */
2234 else if(r
== '!') /* flip intention */
2240 for(fp
= (flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++){
2241 if(r
== 'n' || r
== '*' || r
== 'd' || r
== 'a' || r
== 'f'){
2242 if((r
== 'n' && fp
->flag
== F_SEEN
)
2243 || (r
== '*' && fp
->flag
== F_FLAG
)
2244 || (r
== 'd' && fp
->flag
== F_DEL
)
2245 || (r
== 'f' && fp
->flag
== F_FWD
)
2246 || (r
== 'a' && fp
->flag
== F_ANS
)){
2247 fp
->set
= setflag
? CMD_FLAG_SET
: CMD_FLAG_CLEAR
;
2251 else if(allow_keyword_shortcuts
&& fp
->flag
== F_KEYWORD
){
2252 first_char
= (fp
->name
&& fp
->name
[0]) ? fp
->name
[0] : -2;
2253 if(isascii(first_char
) && isupper(first_char
))
2254 first_char
= tolower((unsigned char) first_char
);
2256 if(r
== first_char
){
2257 fp
->set
= setflag
? CMD_FLAG_SET
: CMD_FLAG_CLEAR
;
2263 if(ek
!= flag_text_opt
)
2264 fs_give((void **) &ek
);
2271 * (*ft) is an array of flag_table entries.
2274 free_flag_table(struct flag_table
**ft
)
2276 struct flag_table
*fp
;
2279 for(fp
= (*ft
); fp
->name
; fp
++){
2281 fs_give((void **) &fp
->name
);
2284 fs_give((void **) &fp
->keyword
);
2287 fs_give((void **) &fp
->comment
);
2290 fs_give((void **) ft
);
2295 /*----------------------------------------------------------------------
2296 Execute REPLY message command
2298 Args: state -- Various satate info
2299 msgmap -- map of c-client to local message numbers
2301 Result: reply sent or not
2305 cmd_reply(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
2309 if(any_messages(msgmap
, NULL
, "to Reply to")){
2310 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
2313 rv
= reply(state
, NULL
);
2315 if(MCMD_ISAGG(aopt
))
2316 restore_selected(msgmap
);
2318 state
->mangled_screen
= 1;
2325 /*----------------------------------------------------------------------
2326 Execute FORWARD message command
2328 Args: state -- Various satate info
2329 msgmap -- map of c-client to local message numbers
2331 Result: selected message[s] forwarded or not
2335 cmd_forward(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
2339 if(any_messages(msgmap
, NULL
, "to Forward")){
2340 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
2343 rv
= forward(state
, NULL
);
2345 if(MCMD_ISAGG(aopt
))
2346 restore_selected(msgmap
);
2348 state
->mangled_screen
= 1;
2355 /*----------------------------------------------------------------------
2356 Execute BOUNCE message command
2358 Args: state -- Various satate info
2359 msgmap -- map of c-client to local message numbers
2360 aopt -- aggregate options
2362 Result: selected message[s] bounced or not
2366 cmd_bounce(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
2369 ACTION_S
*role
= NULL
;
2371 if(any_messages(msgmap
, NULL
, "to Bounce")){
2372 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
2375 if(MCMD_ISAGG(aopt
)){ /* check for possible role */
2379 if(nonempty_patterns(ROLE_DO_ROLES
, &pstate
) && first_pattern(&pstate
)){
2380 static ESCKEY_S yesno_opts
[] = {
2381 {'y', 'y', "Y", N_("Yes")},
2382 {'n', 'n', "N", N_("No")},
2386 action
= radio_buttons(_("Bounce messages using a role? "),
2387 -FOOTER_ROWS(state
), yesno_opts
,
2388 'y', 'x', h_role_compose
, RB_NORM
);
2390 void (*prev_screen
)(struct pine
*) = NULL
, (*redraw
)(void) = NULL
;
2392 redraw
= state
->redrawer
;
2393 state
->redrawer
= NULL
;
2394 prev_screen
= state
->prev_screen
;
2396 state
->next_screen
= SCREEN_FUN_NULL
;
2398 if(role_select_screen(state
, &role
, MC_BOUNCE
) < 0)
2399 cmd_cancelled(_("Bounce"));
2402 role
= combine_inherited_role(role
);
2404 role
= (ACTION_S
*) fs_get(sizeof(*role
));
2405 memset((void *) role
, 0, sizeof(*role
));
2406 role
->nick
= cpystr("Default Role");
2413 state
->next_screen
= prev_screen
;
2414 state
->redrawer
= redraw
;
2415 state
->mangled_screen
= 1;
2420 rv
= bounce(state
, role
);
2425 if(MCMD_ISAGG(aopt
))
2426 restore_selected(msgmap
);
2428 state
->mangled_footer
= 1;
2435 /*----------------------------------------------------------------------
2436 Execute save message command: prompt for folder and call function to save
2438 Args: screen_line -- Line on the screen to prompt on
2439 message -- The MESSAGECACHE entry of message to save
2441 Result: The folder lister can be called to make selection; mangled screen set
2443 This does the prompting for the folder name to save to, possibly calling
2444 up the folder display for selection of folder by user.
2447 cmd_save(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int aopt
, CmdWhere in_index
)
2449 char newfolder
[MAILTMPLEN
], nmsgs
[32], *nick
;
2450 int we_cancel
= 0, rv
= 0, save_flags
;
2452 CONTEXT_S
*cntxt
= NULL
;
2454 SaveDel del
= DontAsk
;
2455 SavePreserveOrder pre
= DontAskPreserve
;
2457 dprint((4, "\n - saving message -\n"));
2459 if(MCMD_ISAGG(aopt
) && !pseudo_selected(stream
, msgmap
))
2462 state
->ugly_consider_advancing_bit
= 0;
2463 if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM
, state
)
2464 && msgno_any_deletedparts(stream
, msgmap
)
2465 && want_to(_("Saved copy will NOT include entire message! Continue"),
2466 'y', 'n', NO_HELP
, WT_FLUSH_IN
| WT_SEQ_SENSITIVE
) != 'y'){
2467 restore_selected(msgmap
);
2468 cmd_cancelled("Save message");
2472 raw
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
2474 if(mn_total_cur(msgmap
) <= 1L){
2475 snprintf(nmsgs
, sizeof(nmsgs
), "Msg #%ld ", mn_get_cur(msgmap
));
2476 nmsgs
[sizeof(nmsgs
)-1] = '\0';
2477 e
= pine_mail_fetchstructure(stream
, raw
, NULL
);
2479 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
2480 _("Can't save message. Error accessing folder"));
2481 restore_selected(msgmap
);
2486 snprintf(nmsgs
, sizeof(nmsgs
), "%s msgs ", comatose(mn_total_cur(msgmap
)));
2487 nmsgs
[sizeof(nmsgs
)-1] = '\0';
2489 /* e is just used to get a default save folder from the first msg */
2490 e
= pine_mail_fetchstructure(stream
,
2491 mn_m2raw(msgmap
, mn_first_cur(msgmap
)),
2495 del
= (!READONLY_FOLDER(stream
) && F_OFF(F_SAVE_WONT_DELETE
, ps_global
))
2497 if(mn_total_cur(msgmap
) > 1L)
2498 pre
= F_OFF(F_AGG_SEQ_COPY
, ps_global
) ? Preserve
: NoPreserve
;
2500 pre
= DontAskPreserve
;
2502 if(save_prompt(state
, &cntxt
, newfolder
, sizeof(newfolder
), nmsgs
, e
,
2503 raw
, NULL
, &del
, &pre
)){
2505 if(ps_global
&& ps_global
->ttyo
){
2506 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
2507 ps_global
->mangled_footer
= 1;
2510 save_flags
= SV_FIX_DELS
;
2511 if(pre
== RetPreserve
)
2512 save_flags
|= SV_PRESERVE
;
2514 save_flags
|= SV_DELETE
;
2515 if(ps_global
->context_list
== cntxt
&& !strucmp(newfolder
, ps_global
->inbox_name
))
2516 save_flags
|= SV_INBOXWOCNTXT
;
2518 we_cancel
= busy_cue(_("Saving"), NULL
, 1);
2519 i
= save(state
, stream
, cntxt
, newfolder
, msgmap
, save_flags
);
2523 if(i
== mn_total_cur(msgmap
)){
2525 if(mn_total_cur(msgmap
) <= 1L){
2526 int need
, avail
= ps_global
->ttyo
->screen_cols
- 2;
2527 int lennick
, lenfldr
;
2530 && ps_global
->context_list
->next
2531 && context_isambig(newfolder
)){
2532 lennick
= MIN(strlen(cntxt
->nickname
), 500);
2533 lenfldr
= MIN(strlen(newfolder
), 500);
2534 need
= 27 + strlen(long2string(mn_get_cur(msgmap
))) +
2538 need
-= MIN(lennick
-10, need
-avail
);
2539 lennick
-= MIN(lennick
-10, need
-avail
);
2542 if(need
> avail
&& lenfldr
> 10)
2543 lenfldr
-= MIN(lenfldr
-10, need
-avail
);
2546 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
2547 "Message %s copied to \"%s\" in <%s>",
2548 long2string(mn_get_cur(msgmap
)),
2549 short_str(newfolder
, (char *)(tmp_20k_buf
+1000), 1000,
2551 short_str(cntxt
->nickname
,
2552 (char *)(tmp_20k_buf
+2000), 1000,
2554 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2556 else if((nick
=folder_is_target_of_nick(newfolder
, cntxt
)) != NULL
){
2557 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
2558 "Message %s copied to \"%s\"",
2559 long2string(mn_get_cur(msgmap
)),
2561 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2564 char *f
= " folder";
2566 lenfldr
= MIN(strlen(newfolder
), 500);
2567 need
= 28 + strlen(long2string(mn_get_cur(msgmap
))) +
2572 if(need
> avail
&& lenfldr
> 10)
2573 lenfldr
-= MIN(lenfldr
-10, need
-avail
);
2576 snprintf(tmp_20k_buf
,SIZEOF_20KBUF
,
2577 "Message %s copied to%s \"%s\"",
2578 long2string(mn_get_cur(msgmap
)), f
,
2579 short_str(newfolder
, (char *)(tmp_20k_buf
+1000), 1000,
2581 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2585 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%s messages saved",
2586 comatose(mn_total_cur(msgmap
)));
2587 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2591 strncat(tmp_20k_buf
, " and deleted", SIZEOF_20KBUF
-strlen(tmp_20k_buf
)-1);
2592 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2595 q_status_message(SM_ORDER
, 0, 3, tmp_20k_buf
);
2597 if(!MCMD_ISAGG(aopt
) && F_ON(F_SAVE_ADVANCES
, state
)){
2598 if(sp_new_mail_count(stream
))
2599 process_filter_patterns(stream
, msgmap
,
2600 sp_new_mail_count(stream
));
2602 mn_inc_cur(stream
, msgmap
,
2603 (in_index
== View
&& THREADING()
2604 && sp_viewing_a_thread(stream
))
2606 : (in_index
== View
)
2607 ? MH_ANYTHD
: MH_NONE
);
2610 state
->ugly_consider_advancing_bit
= 1;
2614 if(MCMD_ISAGG(aopt
)) /* straighten out fakes */
2615 restore_selected(msgmap
);
2618 update_titlebar_status(); /* make sure they see change */
2625 role_compose(struct pine
*state
)
2629 if(F_ON(F_ALT_ROLE_MENU
, state
) && mn_get_total(state
->msgmap
) > 0L){
2632 if(nonempty_patterns(ROLE_DO_ROLES
, &pstate
) && first_pattern(&pstate
)){
2633 action
= radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
2634 -FOOTER_ROWS(state
), choose_action
,
2635 'c', 'x', h_role_compose
, RB_NORM
);
2638 q_status_message(SM_ORDER
, 0, 3,
2639 _("No roles available. Use Setup/Rules to add roles."));
2646 if(action
== 'c' || action
== 'r' || action
== 'f' || action
== 'b'){
2647 ACTION_S
*role
= NULL
;
2648 void (*prev_screen
)(struct pine
*) = NULL
, (*redraw
)(void) = NULL
;
2650 redraw
= state
->redrawer
;
2651 state
->redrawer
= NULL
;
2652 prev_screen
= state
->prev_screen
;
2654 state
->next_screen
= SCREEN_FUN_NULL
;
2657 if(role_select_screen(state
, &role
,
2658 action
== 'f' ? MC_FORWARD
:
2659 action
== 'r' ? MC_REPLY
:
2660 action
== 'b' ? MC_BOUNCE
:
2661 action
== 'c' ? MC_COMPOSE
: 0) < 0){
2662 cmd_cancelled(action
== 'f' ? _("Forward") :
2663 action
== 'r' ? _("Reply") :
2664 action
== 'c' ? _("Composition") : _("Bounce"));
2665 state
->next_screen
= prev_screen
;
2666 state
->redrawer
= redraw
;
2667 state
->mangled_screen
= 1;
2671 * If default role was selected (NULL) we need to make
2672 * up a role which won't do anything, but will cause
2673 * compose_mail to think there's already a role so that
2674 * it won't try to confirm the default.
2677 role
= combine_inherited_role(role
);
2679 role
= (ACTION_S
*) fs_get(sizeof(*role
));
2680 memset((void *) role
, 0, sizeof(*role
));
2681 role
->nick
= cpystr("Default Role");
2684 state
->redrawer
= NULL
;
2687 compose_mail(NULL
, NULL
, role
, NULL
, NULL
);
2691 (void) reply(state
, role
);
2695 (void) forward(state
, role
);
2699 (void) bounce(state
, role
);
2706 state
->next_screen
= prev_screen
;
2707 state
->redrawer
= redraw
;
2708 state
->mangled_screen
= 1;
2714 /*----------------------------------------------------------------------
2715 Do the dirty work of prompting the user for a folder name
2718 nfldr should be a buffer at least MAILTMPLEN long
2719 dela -- a pointer to a SaveDel. If it is
2720 DontAsk on input, don't offer Delete prompt
2721 Del on input, offer Delete command with default of Delete
2723 RetDel and RetNoDel are return values
2730 save_prompt(struct pine
*state
, CONTEXT_S
**cntxt
, char *nfldr
, size_t len_nfldr
,
2731 char *nmsgs
, ENVELOPE
*env
, long int rawmsgno
, char *section
,
2732 SaveDel
*dela
, SavePreserveOrder
*prea
)
2734 int rc
, ku
= -1, n
, flags
, last_rc
= 0, saveable_count
= 0, done
= 0;
2735 int delindex
, preindex
, r
;
2736 char prompt
[6*MAX_SCREEN_COLS
+1], *p
, expanded
[MAILTMPLEN
];
2737 char *buf
= tmp_20k_buf
;
2741 SaveDel del
= DontAsk
;
2742 SavePreserveOrder pre
= DontAskPreserve
;
2743 char *deltext
= NULL
;
2744 static HISTORY_S
*history
= NULL
;
2749 alpine_panic("no context ptr in save_prompt");
2751 init_hist(&history
, HISTSIZE
);
2753 if(!(folder
= save_get_default(state
, env
, rawmsgno
, section
, cntxt
)))
2754 return(0); /* message expunged! */
2756 /* how many context's can be saved to... */
2757 for(tc
= state
->context_list
; tc
; tc
= tc
->next
)
2761 /* set up extra command option keys */
2763 ekey
[rc
].ch
= ctrl('T');
2765 ekey
[rc
].name
= "^T";
2766 /* TRANSLATORS: command means go to Folders list */
2767 ekey
[rc
++].label
= N_("To Fldrs");
2769 if(saveable_count
> 1){
2770 ekey
[rc
].ch
= ctrl('P');
2772 ekey
[rc
].name
= "^P";
2773 ekey
[rc
++].label
= N_("Prev Collection");
2775 ekey
[rc
].ch
= ctrl('N');
2777 ekey
[rc
].name
= "^N";
2778 ekey
[rc
++].label
= N_("Next Collection");
2781 if(F_ON(F_ENABLE_TAB_COMPLETE
, ps_global
)){
2784 ekey
[rc
].name
= "TAB";
2785 /* TRANSLATORS: command asks alpine to complete the name when tab is typed */
2786 ekey
[rc
++].label
= N_("Complete");
2789 if(F_ON(F_ENABLE_SUB_LISTS
, ps_global
)){
2790 ekey
[rc
].ch
= ctrl('X');
2792 ekey
[rc
].name
= "^X";
2793 /* TRANSLATORS: list all the matches */
2794 ekey
[rc
++].label
= N_("ListMatches");
2797 if(dela
&& (*dela
== NoDel
|| *dela
== Del
)){
2798 ekey
[rc
].ch
= ctrl('R');
2800 ekey
[rc
].name
= "^R";
2805 if(prea
&& (*prea
== NoPreserve
|| *prea
== Preserve
)){
2806 ekey
[rc
].ch
= ctrl('W');
2808 ekey
[rc
].name
= "^W";
2813 if(saveable_count
> 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
2814 ekey
[rc
].ch
= KEY_UP
;
2817 ekey
[rc
++].label
= "";
2819 ekey
[rc
].ch
= KEY_DOWN
;
2822 ekey
[rc
++].label
= "";
2824 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
2825 ekey
[rc
].ch
= KEY_UP
;
2829 ekey
[rc
++].label
= "";
2831 ekey
[rc
].ch
= KEY_DOWN
;
2834 ekey
[rc
++].label
= "";
2842 /* only show collection number if more than one available */
2843 if(ps_global
->context_list
->next
)
2844 snprintf(prompt
, sizeof(prompt
), "SAVE%s %sto folder in <%s> [%s] : ",
2845 deltext
? deltext
: "",
2847 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2848 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2850 snprintf(prompt
, sizeof(prompt
), "SAVE%s %sto folder [%s] : ",
2851 deltext
? deltext
: "",
2852 nmsgs
, strsquish(buf
, SIZEOF_20KBUF
, folder
, 40));
2854 prompt
[sizeof(prompt
)-1] = '\0';
2857 * If the prompt won't fit, try removing deltext.
2859 if(state
->ttyo
->screen_cols
< strlen(prompt
) + MIN_OPT_ENT_WIDTH
&& deltext
){
2860 if(ps_global
->context_list
->next
)
2861 snprintf(prompt
, sizeof(prompt
), "SAVE %sto folder in <%s> [%s] : ",
2863 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2864 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2866 snprintf(prompt
, sizeof(prompt
), "SAVE %sto folder [%s] : ",
2867 nmsgs
, strsquish(buf
, SIZEOF_20KBUF
, folder
, 40));
2869 prompt
[sizeof(prompt
)-1] = '\0';
2873 * If the prompt still won't fit, remove the extra info contained
2876 if(state
->ttyo
->screen_cols
< strlen(prompt
) + MIN_OPT_ENT_WIDTH
&& *nmsgs
){
2877 if(ps_global
->context_list
->next
)
2878 snprintf(prompt
, sizeof(prompt
), "SAVE to folder in <%s> [%s] : ",
2879 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2880 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2882 snprintf(prompt
, sizeof(prompt
), "SAVE to folder [%s] : ",
2883 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2885 prompt
[sizeof(prompt
)-1] = '\0';
2889 ekey
[delindex
].label
= (del
== NoDel
) ? "Delete" : "No Delete";
2891 if(pre
!= DontAskPreserve
)
2892 ekey
[preindex
].label
= (pre
== NoPreserve
) ? "Preserve Order" : "Any Order";
2895 if(items_in_hist(history
) > 1){
2896 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
2897 ekey
[ku
].label
= HISTORY_KEYLABEL
;
2898 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
2899 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
2903 ekey
[ku
].label
= "";
2904 ekey
[ku
+1].name
= "";
2905 ekey
[ku
+1].label
= "";
2909 flags
= OE_APPEND_CURRENT
| OE_SEQ_SENSITIVE
;
2910 rc
= optionally_enter(nfldr
, -FOOTER_ROWS(state
), 0, len_nfldr
,
2911 prompt
, ekey
, help
, &flags
);
2915 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
2916 _("Error reading folder name"));
2921 removing_trailing_white_space(nfldr
);
2922 removing_leading_white_space(nfldr
);
2924 if(*nfldr
|| *folder
){
2925 char *p
, *name
, *fullname
= NULL
;
2926 int exists
, breakout
= FALSE
;
2929 strncpy(nfldr
, folder
, len_nfldr
-1);
2930 nfldr
[len_nfldr
-1] = '\0';
2933 save_hist(history
, nfldr
, 0, (void *) *cntxt
);
2935 if(!(name
= folder_is_nick(nfldr
, FOLDERS(*cntxt
), 0)))
2938 if(update_folder_spec(expanded
, sizeof(expanded
), name
)){
2939 strncpy(name
= nfldr
, expanded
, len_nfldr
-1);
2940 nfldr
[len_nfldr
-1] = '\0';
2943 exists
= folder_name_exists(*cntxt
, name
, &fullname
);
2945 if(exists
== FEX_ERROR
){
2946 q_status_message1(SM_ORDER
, 0, 3,
2947 _("Problem accessing folder \"%s\""),
2953 strncpy(name
= nfldr
, fullname
, len_nfldr
-1);
2954 nfldr
[len_nfldr
-1] = '\0';
2955 fs_give((void **) &fullname
);
2959 if(exists
& FEX_ISFILE
){
2962 else if((exists
& FEX_ISDIR
)){
2963 char tmp
[MAILTMPLEN
];
2967 CONTEXT_S
*fake_context
;
2970 strncpy(tmp
, name
, sizeof(tmp
));
2971 tmp
[sizeof(tmp
)-2-1] = '\0';
2972 if(tmp
[(l
= strlen(tmp
)) - 1] != tc
->dir
->delim
){
2973 if(l
< sizeof(tmp
)){
2974 tmp
[l
] = tc
->dir
->delim
;
2975 strncpy(&tmp
[l
+1], "[]", sizeof(tmp
)-(l
+1));
2979 strncat(tmp
, "[]", sizeof(tmp
)-strlen(tmp
)-1);
2981 tmp
[sizeof(tmp
)-1] = '\0';
2983 fake_context
= new_context(tmp
, 0);
2985 done
= display_folder_list(&fake_context
, nfldr
,
2986 1, folders_for_save
);
2987 free_context(&fake_context
);
2989 else if(tc
->dir
->delim
2990 && (p
= strrindex(name
, tc
->dir
->delim
))
2992 done
= display_folder_list(cntxt
, nfldr
,
2993 1, folders_for_save
);
2995 q_status_message1(SM_ORDER
, 3, 3,
2996 _("\"%s\" is a directory"), name
);
2998 && !((p
=strrindex(name
, tc
->dir
->delim
)) && *(p
+1) == '\0')){
2999 strncpy(tmp
, name
, sizeof(tmp
));
3000 tmp
[sizeof(tmp
)-1] = '\0';
3001 snprintf(nfldr
, len_nfldr
, "%s%c", tmp
, tc
->dir
->delim
);
3005 else{ /* Doesn't exist, create! */
3006 if((fullname
= folder_as_breakout(*cntxt
, name
)) != NULL
){
3007 strncpy(name
= nfldr
, fullname
, len_nfldr
-1);
3008 nfldr
[len_nfldr
-1] = '\0';
3009 fs_give((void **) &fullname
);
3012 switch(create_for_save(*cntxt
, name
)){
3013 case 1 : /* success */
3016 case 0 : /* error */
3017 case -1 : /* declined */
3026 /* else fall thru like they cancelled */
3029 cmd_cancelled("Save message");
3034 r
= display_folder_list(cntxt
, nfldr
, 0, folders_for_save
);
3042 helper(h_save
, _("HELP FOR SAVE"), HLPD_SIMPLE
);
3043 ps_global
->mangled_screen
= 1;
3046 case 4 : /* redraw */
3049 case 10 : /* previous collection */
3050 for(tc
= (*cntxt
)->prev
; tc
; tc
= tc
->prev
)
3057 for(tc2
= (tc
= (*cntxt
))->next
; tc2
; tc2
= tc2
->next
)
3065 case 11 : /* next collection */
3069 if(((*cntxt
) = (*cntxt
)->next
) == NULL
)
3070 (*cntxt
) = ps_global
->context_list
;
3071 while(NEWS_TEST(*cntxt
) && (*cntxt
) != tc
);
3074 case 12 : /* file name completion */
3075 if(!folder_complete(*cntxt
, nfldr
, len_nfldr
, &n
)){
3076 if(n
&& last_rc
== 12 && !(flags
& OE_USER_MODIFIED
)){
3077 r
= display_folder_list(cntxt
, nfldr
, 1, folders_for_save
);
3079 done
++; /* bingo! */
3081 rc
= 0; /* burn last_rc */
3089 case 14 : /* file name completion */
3090 r
= display_folder_list(cntxt
, nfldr
, 2, folders_for_save
);
3092 done
++; /* bingo! */
3094 rc
= 0; /* burn last_rc */
3098 case 15 : /* Delete / No Delete */
3099 del
= (del
== NoDel
) ? Del
: NoDel
;
3100 deltext
= (del
== NoDel
) ? " (no delete)" : " (and delete)";
3103 case 16 : /* Preserve Order or not */
3104 pre
= (pre
== NoPreserve
) ? Preserve
: NoPreserve
;
3108 if((p
= get_prev_hist(history
, nfldr
, 0, (void *) *cntxt
)) != NULL
){
3109 strncpy(nfldr
, p
, len_nfldr
);
3110 nfldr
[len_nfldr
-1] = '\0';
3111 if(history
->hist
[history
->curindex
])
3112 *cntxt
= (CONTEXT_S
*) history
->hist
[history
->curindex
]->cntxt
;
3120 if((p
= get_next_hist(history
, nfldr
, 0, (void *) *cntxt
)) != NULL
){
3121 strncpy(nfldr
, p
, len_nfldr
);
3122 nfldr
[len_nfldr
-1] = '\0';
3123 if(history
->hist
[history
->curindex
])
3124 *cntxt
= (CONTEXT_S
*) history
->hist
[history
->curindex
]->cntxt
;
3132 alpine_panic("Unhandled case");
3139 ps_global
->mangled_footer
= 1;
3145 strncpy(ps_global
->last_save_folder
, nfldr
, sizeof(ps_global
->last_save_folder
)-1);
3146 ps_global
->last_save_folder
[sizeof(ps_global
->last_save_folder
)-1] = '\0';
3148 ps_global
->last_save_context
= *cntxt
;
3151 strncpy(nfldr
, folder
, len_nfldr
-1);
3152 nfldr
[len_nfldr
-1] = '\0';
3155 /* nickname? Copy real name to nfldr */
3157 && context_isambig(nfldr
)
3158 && (p
= folder_is_nick(nfldr
, FOLDERS(*cntxt
), 0))){
3159 strncpy(nfldr
, p
, len_nfldr
-1);
3160 nfldr
[len_nfldr
-1] = '\0';
3163 if(dela
&& (*dela
== NoDel
|| *dela
== Del
))
3164 *dela
= (del
== NoDel
) ? RetNoDel
: RetDel
;
3166 if(prea
&& (*prea
== NoPreserve
|| *prea
== Preserve
))
3167 *prea
= (pre
== NoPreserve
) ? RetNoPreserve
: RetPreserve
;
3173 /*----------------------------------------------------------------------
3174 Prompt user before implicitly creating a folder for saving
3176 Args: context - context to create folder in
3177 folder - folder name to create
3179 Result: 1 on proceed, -1 on decline, 0 on error
3183 create_for_save_prompt(CONTEXT_S
*context
, char *folder
, int sequence_sensitive
)
3185 if(context
&& ps_global
->context_list
->next
&& context_isambig(folder
)){
3186 if(context
->use
& CNTXT_INCMNG
){
3187 snprintf(tmp_20k_buf
,SIZEOF_20KBUF
,
3188 _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
3189 folder
, (strlen(folder
) > 15) ? "..." : "");
3190 q_status_message(SM_ORDER
, 3, 3, tmp_20k_buf
);
3191 return(0); /* error */
3194 snprintf(tmp_20k_buf
,SIZEOF_20KBUF
,
3195 _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
3196 folder
, (strlen(folder
) > 15) ? "..." : "",
3198 (strlen(context
->nickname
) > 15) ? "..." : "");
3201 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
3202 _("Folder \"%.40s%s\" doesn't exist. Create"),
3203 folder
, strlen(folder
) > 40 ? "..." : "");
3205 if(want_to(tmp_20k_buf
, 'y', 'n',
3206 NO_HELP
, (sequence_sensitive
) ? WT_SEQ_SENSITIVE
: WT_NORM
) != 'y'){
3207 cmd_cancelled("Save message");
3216 /*----------------------------------------------------------------------
3217 Expunge messages from current folder
3219 Args: state -- pointer to struct holding a bunch of pine state
3220 msgmap -- table mapping msg nums to c-client sequence nums
3221 qline -- screen line to ask questions on
3222 agg -- boolean indicating we're to operate on aggregate set
3227 cmd_expunge(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int agg
)
3229 long del_count
, prefilter_del_count
;
3230 int we_cancel
= 0, rv
= 0;
3231 char prompt
[MAX_SCREEN_COLS
+1];
3233 COLOR_PAIR
*lastc
= NULL
;
3235 dprint((2, "\n - expunge -\n"));
3239 sequence
= MCMD_ISAGG(agg
) ? selected_sequence(stream
, msgmap
, NULL
, 0) : NULL
;
3241 if(MCMD_ISAGG(agg
)){
3244 for(i
= 1L; i
<= stream
->nmsgs
; i
++){
3245 if((mc
= mail_elt(stream
, i
)) != NULL
3246 && mc
->sequence
&& mc
->deleted
)
3250 q_status_message(SM_ORDER
, 0, 4,
3251 _("No selected messages are deleted"));
3255 if(!any_messages(msgmap
, NULL
, "to Expunge"))
3259 if(IS_NEWS(stream
) && stream
->rdonly
){
3260 if(!MCMD_ISAGG(agg
))
3261 del_count
= count_flagged(stream
, F_DEL
);
3263 state
->mangled_footer
= 1;
3264 snprintf(prompt
, sizeof(prompt
), "Exclude %ld message%s from %.*s", del_count
,
3265 plural(del_count
), sizeof(prompt
)-40,
3266 pretty_fn(state
->cur_folder
));
3267 prompt
[sizeof(prompt
)-1] = '\0';
3268 if(F_ON(F_FULL_AUTO_EXPUNGE
, state
)
3269 || (F_ON(F_AUTO_EXPUNGE
, state
)
3270 && (state
->context_current
3271 && (state
->context_current
->use
& CNTXT_INCMNG
))
3272 && context_isambig(state
->cur_folder
))
3273 || want_to(prompt
, 'y', 0, NO_HELP
, WT_NORM
) == 'y'){
3275 if(F_ON(F_NEWS_CROSS_DELETE
, state
))
3276 cross_delete_crossposts(stream
);
3278 msgno_exclude_deleted(stream
, msgmap
, sequence
);
3279 clear_index_cache(stream
, 0);
3282 * This is kind of surprising at first. For most sort
3283 * orders, if the whole set is sorted, then any subset
3284 * is also sorted. Not so for threaded sorts.
3286 if(SORT_IS_THREADED(msgmap
))
3287 refresh_sort(stream
, msgmap
, SRT_NON
);
3289 state
->mangled_body
= 1;
3290 state
->mangled_header
= 1;
3291 q_status_message2(SM_ORDER
, 0, 4,
3292 "%s message%s excluded",
3293 long2string(del_count
),
3297 any_messages(NULL
, NULL
, "Excluded");
3300 any_messages(NULL
, "deleted", "to Exclude");
3304 else if(READONLY_FOLDER(stream
)){
3305 q_status_message(SM_ORDER
, 0, 4,
3306 _("Can't expunge. Folder is read-only"));
3310 if(!MCMD_ISAGG(agg
)){
3311 prefilter_del_count
= count_flagged(stream
, F_DEL
|F_NOFILT
);
3312 mail_expunge_prefilter(stream
, MI_NONE
);
3313 del_count
= count_flagged(stream
, F_DEL
|F_NOFILT
);
3318 unsigned char *fname
= folder_name_decoded((unsigned char *)state
->cur_folder
);
3320 snprintf(prompt
, sizeof(prompt
), "Expunge %ld message%s from %.*s", del_count
,
3321 plural(del_count
), sizeof(prompt
)-40,
3322 pretty_fn((char *) fname
));
3323 if(fname
) fs_give((void **)&fname
);
3324 prompt
[sizeof(prompt
)-1] = '\0';
3325 state
->mangled_footer
= 1;
3327 if(F_ON(F_FULL_AUTO_EXPUNGE
, state
)
3328 || (F_ON(F_AUTO_EXPUNGE
, state
)
3329 && ((!strucmp(state
->cur_folder
,state
->inbox_name
))
3330 || (state
->context_current
->use
& CNTXT_INCMNG
))
3331 && context_isambig(state
->cur_folder
))
3332 || (ret
=want_to(prompt
, 'y', 0, NO_HELP
, WT_NORM
)) == 'y')
3336 cmd_cancelled("Expunge");
3342 dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
3343 mn_get_total(msgmap
), mn_get_cur(msgmap
), del_count
));
3345 lastc
= pico_set_colors(state
->VAR_TITLE_FORE_COLOR
,
3346 state
->VAR_TITLE_BACK_COLOR
,
3349 PutLine0(0, 0, "**"); /* indicate delay */
3352 (void)pico_set_colorp(lastc
, PSC_NONE
);
3353 free_color_pair(&lastc
);
3356 MoveCursor(state
->ttyo
->screen_rows
-FOOTER_ROWS(state
), 0);
3359 we_cancel
= busy_cue(_("Expunging"), NULL
, 1);
3361 if(cmd_expunge_work(stream
, msgmap
, sequence
))
3362 state
->mangled_body
= 1;
3365 fs_give((void **)&sequence
);
3368 cancel_busy_cue((sp_expunge_count(stream
) > 0) ? 0 : -1);
3370 lastc
= pico_set_colors(state
->VAR_TITLE_FORE_COLOR
,
3371 state
->VAR_TITLE_BACK_COLOR
,
3373 PutLine0(0, 0, " "); /* indicate delay's over */
3376 (void)pico_set_colorp(lastc
, PSC_NONE
);
3377 free_color_pair(&lastc
);
3382 if(sp_expunge_count(stream
) > 0){
3384 * This is kind of surprising at first. For most sort
3385 * orders, if the whole set is sorted, then any subset
3386 * is also sorted. Not so for threaded sorts.
3388 if(SORT_IS_THREADED(msgmap
))
3389 refresh_sort(stream
, msgmap
, SRT_NON
);
3393 unsigned char *fname
= folder_name_decoded((unsigned char *)state
->cur_folder
);
3394 q_status_message1(SM_ORDER
, 0, 3,
3395 _("No messages expunged from folder \"%s\""),
3396 pretty_fn((char *) fname
));
3397 if(fname
) fs_give((void **)&fname
);
3399 else if(!prefilter_del_count
)
3400 q_status_message(SM_ORDER
, 0, 3,
3401 _("No messages marked deleted. No messages expunged."));
3407 /*----------------------------------------------------------------------
3408 Expunge_and_close callback to prompt user for confirmation
3410 Args: stream -- folder's stream
3411 folder -- name of folder containing folders
3412 deleted -- number of del'd msgs
3414 Result: 'y' to continue with expunge
3417 expunge_prompt(MAILSTREAM
*stream
, char *folder
, long int deleted
)
3421 char prompt_b
[MAX_SCREEN_COLS
+1], temp
[MAILTMPLEN
+1], buff
[MAX_SCREEN_COLS
+1];
3422 char *short_folder_name
;
3427 snprintf(temp
, sizeof(temp
), "%ld", deleted
);
3428 charcnt
= strlen(temp
)+1;
3431 max_folder
= MAX(1,MAXPROMPT
- (36+charcnt
));
3432 strncpy(temp
, folder
, sizeof(temp
));
3433 temp
[sizeof(temp
)-1] = '\0';
3434 short_folder_name
= short_str(temp
,buff
,sizeof(buff
),max_folder
,FrontDots
);
3437 snprintf(prompt_b
, sizeof(prompt_b
),
3438 "Delete %s%ld message%s from \"%s\"",
3439 (deleted
> 1L) ? "all " : "", deleted
,
3440 plural(deleted
), short_folder_name
);
3442 snprintf(prompt_b
, sizeof(prompt_b
),
3443 "Expunge the %ld deleted message%s from \"%s\"",
3444 deleted
, deleted
== 1 ? "" : "s",
3447 return(want_to(prompt_b
, 'y', 0, NO_HELP
, WT_NORM
));
3452 * This is used with multiple append saves. Call it once before
3453 * the series of appends with SSCP_INIT and once after all are
3454 * done with SSCP_END. In between, it is called automatically
3455 * from save_fetch_append or save_fetch_append_cb when we need
3456 * to ask the user if he or she wants to continue even though
3457 * announced message size doesn't match the actual message size.
3458 * As of 2008-02-29 the gmail IMAP server has these size mismatches
3459 * on a regular basis even though the data is ok.
3462 save_size_changed_prompt(long msgno
, int flags
)
3466 static int remember_the_yes
= 0;
3467 static int possible_corruption
= 0;
3468 static ESCKEY_S save_size_opts
[] = {
3469 {'y', 'y', "Y", "Yes"},
3470 {'n', 'n', "N", "No"},
3471 {'a', 'a', "A", "yes to All"},
3475 if(flags
& SSCP_INIT
|| flags
& SSCP_END
){
3476 if(flags
& SSCP_END
&& possible_corruption
)
3477 q_status_message(SM_ORDER
, 3, 3, "There is possible data corruption, check the results");
3479 remember_the_yes
= 0;
3480 possible_corruption
= 0;
3481 ps_global
->noshow_error
= 0;
3482 ps_global
->noshow_warn
= 0;
3486 if(remember_the_yes
){
3487 snprintf(prompt
, sizeof(prompt
),
3488 "Message to save shrank! (msg # %ld): Continuing", msgno
);
3489 q_status_message(SM_ORDER
, 0, 3, prompt
);
3490 display_message('x');
3491 return(remember_the_yes
);
3494 snprintf(prompt
, sizeof(prompt
),
3495 "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno
);
3496 ret
= radio_buttons(prompt
, -FOOTER_ROWS(ps_global
), save_size_opts
,
3497 'n', 0, h_save_size_changed
, RB_NORM
);
3501 remember_the_yes
= 'y';
3502 possible_corruption
++;
3503 return(remember_the_yes
);
3506 possible_corruption
++;
3510 possible_corruption
= 0;
3511 ps_global
->noshow_error
= 1;
3512 ps_global
->noshow_warn
= 1;
3520 /*----------------------------------------------------------------------
3521 Expunge_and_close callback that happens once the decision to expunge
3522 and close has been made and before expunging and closing begins
3525 Args: stream -- folder's stream
3526 folder -- name of folder containing folders
3527 deleted -- number of del'd msgs
3529 Result: 'y' to continue with expunge
3532 expunge_and_close_begins(int flags
, char *folder
)
3534 if(!(flags
& EC_NO_CLOSE
)){
3535 unsigned char *fname
= folder_name_decoded((unsigned char *)folder
);
3536 q_status_message1(SM_INFO
, 0, 1, "Closing \"%.200s\"...", (char *) fname
);
3537 flush_status_messages(1);
3538 if(fname
) fs_give((void **)&fname
);
3543 /*----------------------------------------------------------------------
3544 Export a message to a plain file in users home directory
3546 Args: state -- pointer to struct holding a bunch of pine state
3547 msgmap -- table mapping msg nums to c-client sequence nums
3548 qline -- screen line to ask questions on
3549 agg -- boolean indicating we're to operate on aggregate set
3554 cmd_export(struct pine
*state
, MSGNO_S
*msgmap
, int qline
, int aopt
)
3556 char filename
[MAXPATH
+1], full_filename
[MAXPATH
+1], *err
;
3558 int r
, leading_nl
, failure
= 0, orig_errno
, rflags
= GER_NONE
;
3559 int flags
= GE_IS_EXPORT
| GE_SEQ_SENSITIVE
, rv
= 0;
3563 long i
, count
= 0L, start_of_append
, rawno
;
3566 struct variable
*vars
= ps_global
->vars
;
3567 ESCKEY_S export_opts
[5];
3568 static HISTORY_S
*history
= NULL
;
3570 if(ps_global
->restricted
){
3571 q_status_message(SM_ORDER
, 0, 3,
3572 "Alpine demo can't export messages to files");
3576 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
3579 export_opts
[i
= 0].ch
= ctrl('T');
3580 export_opts
[i
].rval
= 10;
3581 export_opts
[i
].name
= "^T";
3582 export_opts
[i
++].label
= N_("To Files");
3584 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3585 if(ps_global
->VAR_DOWNLOAD_CMD
&& ps_global
->VAR_DOWNLOAD_CMD
[0]){
3586 export_opts
[i
].ch
= ctrl('V');
3587 export_opts
[i
].rval
= 12;
3588 export_opts
[i
].name
= "^V";
3589 /* TRANSLATORS: this is an abbreviation for Download Messages */
3590 export_opts
[i
++].label
= N_("Downld Msg");
3592 #endif /* !(DOS || MAC) */
3594 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps_global
)){
3595 export_opts
[i
].ch
= ctrl('I');
3596 export_opts
[i
].rval
= 11;
3597 export_opts
[i
].name
= "TAB";
3598 export_opts
[i
++].label
= N_("Complete");
3602 /* Commented out since it's not yet support! */
3603 if(F_ON(F_ENABLE_SUB_LISTS
,ps_global
)){
3604 export_opts
[i
].ch
= ctrl('X');
3605 export_opts
[i
].rval
= 14;
3606 export_opts
[i
].name
= "^X";
3607 export_opts
[i
++].label
= N_("ListMatches");
3612 * If message has attachments, add a toggle that will allow the user
3613 * to save all of the attachments to a single directory, using the
3614 * names provided with the attachments or part names. What we'll do is
3615 * export the message as usual, and then export the attachments into
3616 * a subdirectory that did not exist before. The subdir will be named
3617 * something based on the name of the file being saved to, but a
3620 if(!MCMD_ISAGG(aopt
)
3621 && state
->mail_stream
3622 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
3623 && rawno
<= state
->mail_stream
->nmsgs
3624 && (env
= pine_mail_fetchstructure(state
->mail_stream
, rawno
, &b
))
3626 && b
->type
== TYPEMULTIPART
3628 && strucmp(b
->subtype
, "ALTERNATIVE") != 0){
3631 part
= b
->nested
.part
; /* 1st part */
3632 if(part
&& part
->next
)
3633 flags
|= GE_ALLPARTS
;
3636 export_opts
[i
].ch
= -1;
3639 if(mn_total_cur(msgmap
) <= 1L){
3640 snprintf(nmsgs
, sizeof(nmsgs
), "Msg #%ld", mn_get_cur(msgmap
));
3641 nmsgs
[sizeof(nmsgs
)-1] = '\0';
3644 snprintf(nmsgs
, sizeof(nmsgs
), "%s messages", comatose(mn_total_cur(msgmap
)));
3645 nmsgs
[sizeof(nmsgs
)-1] = '\0';
3648 r
= get_export_filename(state
, filename
, NULL
, full_filename
,
3649 sizeof(filename
), nmsgs
, "EXPORT",
3650 export_opts
, &rflags
, qline
, flags
, &history
);
3655 cmd_cancelled("Export message");
3659 q_status_message1(SM_ORDER
, 0, 2,
3660 _("Can't export to file outside of %s"),
3667 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3668 else if(r
== 12){ /* Download */
3669 char cmd
[MAXPATH
], *tfp
= NULL
;
3675 if(ps_global
->restricted
){
3676 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3677 "Download disallowed in restricted mode");
3682 tfp
= temp_nam(NULL
, "pd");
3683 build_updown_cmd(cmd
, sizeof(cmd
), ps_global
->VAR_DOWNLOAD_CMD_PREFIX
,
3684 ps_global
->VAR_DOWNLOAD_CMD
, tfp
);
3685 dprint((1, "Download cmd called: \"%s\"\n", cmd
));
3686 if((so
= so_get(FileStar
, tfp
, WRITE_ACCESS
|OWNER_ONLY
|WRITE_TO_LOCALE
)) != NULL
){
3687 gf_set_so_writec(&pc
, so
);
3689 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
)){
3690 if(!(state
->mail_stream
3691 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
3692 && rawno
<= state
->mail_stream
->nmsgs
3693 && (mc
= mail_elt(state
->mail_stream
, rawno
))
3697 if(!(env
= pine_mail_fetchstructure(state
->mail_stream
,
3698 mn_m2raw(msgmap
, i
), &b
))
3699 || !bezerk_delimiter(env
, mc
, pc
, next
++)
3700 || !format_message(mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
3701 env
, b
, NULL
, FM_NEW_MESS
| FM_NOWRAP
, pc
)){
3702 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3703 err
= "Error writing tempfile for download");
3708 gf_clear_so_writec(so
);
3709 if(so_give(&so
)){ /* close file */
3711 err
= "Error writing tempfile for download";
3715 if((syspipe
= open_system_pipe(cmd
, NULL
, NULL
,
3716 PIPE_USER
| PIPE_RESET
,
3717 0, pipe_callback
, pipe_report_error
)) != NULL
)
3718 (void) close_system_pipe(&syspipe
, NULL
, pipe_callback
);
3720 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3721 err
= _("Error running download command"));
3725 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3726 err
= "Error building temp file for download");
3730 fs_give((void **)&tfp
);
3734 q_status_message(SM_ORDER
, 0, 3, _("Download Command Completed"));
3738 #endif /* !(DOS || MAC) */
3741 if(rflags
& GER_APPEND
)
3746 dprint((5, "Opening file \"%s\" for export\n",
3747 full_filename
? full_filename
: "?"));
3749 if(!(store
= so_get(FileStar
, full_filename
, WRITE_ACCESS
|WRITE_TO_LOCALE
))){
3750 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
3751 /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
3752 _("Error opening file \"%s\" to export message: %s"),
3753 full_filename
, error_description(errno
));
3757 gf_set_so_writec(&pc
, store
);
3760 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
), count
++){
3761 env
= pine_mail_fetchstructure(state
->mail_stream
, mn_m2raw(msgmap
, i
),
3764 err
= _("Can't export message. Error accessing mail folder");
3769 if(!(state
->mail_stream
3770 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
3771 && rawno
<= state
->mail_stream
->nmsgs
3772 && (mc
= mail_elt(state
->mail_stream
, rawno
))
3776 start_of_append
= so_tell(store
);
3777 if(!bezerk_delimiter(env
, mc
, pc
, leading_nl
)
3778 || !format_message(mn_m2raw(msgmap
, i
), env
, b
, NULL
,
3779 FM_NEW_MESS
| FM_NOWRAP
, pc
)){
3780 orig_errno
= errno
; /* save incase things are really bad */
3781 failure
= 1; /* pop out of here */
3788 gf_clear_so_writec(store
);
3789 if(so_give(&store
)) /* release storage */
3793 our_truncate(full_filename
, (off_t
)start_of_append
);
3795 dprint((1, "FAILED Export: fetch(%ld): %s\n",
3796 i
, err
? err
: "?"));
3797 q_status_message(SM_ORDER
| SM_DING
, 3, 4, err
);
3800 dprint((1, "FAILED Export: file \"%s\" : %s\n",
3801 full_filename
? full_filename
: "?",
3802 error_description(orig_errno
)));
3803 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
3804 /* TRANSLATORS: Error exporting to <filename>: <error text> */
3805 _("Error exporting to \"%s\" : %s"),
3806 filename
, error_description(orig_errno
));
3810 if(rflags
& GER_ALLPARTS
&& full_filename
[0]){
3811 char dir
[MAXPATH
+1];
3812 char lfile
[MAXPATH
+1];
3813 int ok
= 0, tries
= 0, saved
= 0, errs
= 0;
3817 * Now we want to save all of the attachments to a subdirectory.
3818 * To make it easier for us and probably easier for the user, and
3819 * to prevent the user from shooting himself in the foot, we
3820 * make a new subdirectory so that we can't possibly step on
3821 * any existing files, and we don't need any interaction with the
3822 * user while saving.
3824 * We'll just use the directory name full_filename.d or if that
3825 * already exists and isn't empty, we'll try adding a suffix to
3826 * that until we get something to use.
3829 if(strlen(full_filename
) + strlen(".d") + 1 > sizeof(dir
)){
3830 q_status_message1(SM_ORDER
| SM_DING
, 3, 4,
3831 _("Can't save attachments, filename too long: %s"),
3837 snprintf(dir
, sizeof(dir
), "%s.d", full_filename
);
3838 dir
[sizeof(dir
)-1] = '\0';
3842 switch(r
= is_writable_dir(dir
)){
3843 case 0: /* exists and is a writable dir */
3845 * We could figure out if it is empty and use it in
3846 * that case, but that sounds like a lot of work, so
3847 * just fall through to default.
3851 if(strlen(full_filename
) + strlen(".d") + 1 +
3852 1 + strlen(long2string((long) tries
)) > sizeof(dir
)){
3853 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3854 "Problem saving attachments");
3858 snprintf(dir
, sizeof(dir
), "%s.d_%s", full_filename
,
3859 long2string((long) tries
));
3860 dir
[sizeof(dir
)-1] = '\0';
3863 case 3: /* doesn't exist, that's good! */
3864 /* make new directory */
3868 } while(!ok
&& tries
< 1000);
3871 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3872 _("Problem saving attachments"));
3876 /* create the new directory */
3877 if(our_mkdir(dir
, 0700)){
3878 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
3879 _("Problem saving attachments: %s: %s"), dir
,
3880 error_description(errno
));
3884 if(!(state
->mail_stream
3885 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
3886 && rawno
<= state
->mail_stream
->nmsgs
3887 && (env
=pine_mail_fetchstructure(state
->mail_stream
,rawno
,&b
))
3889 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3890 _("Problem reading message"));
3894 zero_atmts(state
->atmts
);
3895 describe_mime(b
, "", 1, 1, 0, 0);
3898 if(a
&& a
->description
) /* skip main body part */
3901 for(; a
->description
!= NULL
; a
++){
3902 /* skip over these parts of the message */
3903 if(MIME_MSG_A(a
) || MIME_DGST_A(a
) || MIME_VCARD_A(a
))
3907 (void) get_filename_parameter(lfile
, sizeof(lfile
), a
->body
, NULL
);
3909 if(lfile
[0] == '\0'){
3910 snprintf(lfile
, sizeof(lfile
), "part_%.*s", sizeof(lfile
)-6,
3911 a
->number
? a
->number
: "?");
3912 lfile
[sizeof(lfile
)-1] = '\0';
3915 if(strlen(dir
) + strlen(S_FILESEP
) + strlen(lfile
) + 1
3916 > sizeof(filename
)){
3918 "FAILED Att Export: name too long: %s\n",
3919 dir
, S_FILESEP
, lfile
));
3924 snprintf(filename
, sizeof(filename
), "%s%s%s", dir
, S_FILESEP
, lfile
);
3925 filename
[sizeof(filename
)-1] = '\0';
3927 if(write_attachment_to_file(state
->mail_stream
, rawno
,
3928 a
, GER_NONE
, filename
) == 1)
3936 q_status_message1(SM_ORDER
, 3, 3,
3937 "Errors saving some attachments, %s attachments saved",
3938 long2string((long) saved
));
3940 q_status_message(SM_ORDER
, 3, 3,
3941 _("Problems saving attachments"));
3945 q_status_message2(SM_ORDER
, 0, 3,
3946 /* TRANSLATORS: Saved <how many> attachements to <directory name> */
3947 _("Saved %s attachments to %s"),
3948 long2string((long) saved
), dir
);
3950 q_status_message(SM_ORDER
, 3, 3, _("No attachments to save"));
3953 else if(mn_total_cur(msgmap
) > 1L)
3954 q_status_message4(SM_ORDER
,0,3,
3955 "%s message%s %s to file \"%s\"",
3956 long2string(count
), plural(count
),
3959 : rflags
& GER_APPEND
? "appended" : "exported",
3962 q_status_message3(SM_ORDER
,0,3,
3963 "Message %s %s to file \"%s\"",
3964 long2string(mn_get_cur(msgmap
)),
3967 : rflags
& GER_APPEND
? "appended" : "exported",
3973 if(MCMD_ISAGG(aopt
))
3974 restore_selected(msgmap
);
3981 * Ask user what file to export to. Export from srcstore to that file.
3983 * Args ps -- pine struct
3984 * srctext -- pointer to source text
3985 * srctype -- type of that source text
3986 * prompt_msg -- see get_export_filename
3989 * Returns: != 0 : error
3993 simple_export(struct pine
*ps
, void *srctext
, SourceType srctype
, char *prompt_msg
, char *lister_msg
)
3995 int r
= 1, rflags
= GER_NONE
;
3996 char filename
[MAXPATH
+1], full_filename
[MAXPATH
+1];
3997 STORE_S
*store
= NULL
;
3998 struct variable
*vars
= ps
->vars
;
3999 static HISTORY_S
*history
= NULL
;
4000 static ESCKEY_S simple_export_opts
[] = {
4001 {ctrl('T'), 10, "^T", N_("To Files")},
4002 {-1, 0, NULL
, NULL
},
4003 {-1, 0, NULL
, NULL
}};
4005 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps
)){
4006 simple_export_opts
[r
].ch
= ctrl('I');
4007 simple_export_opts
[r
].rval
= 11;
4008 simple_export_opts
[r
].name
= "TAB";
4009 simple_export_opts
[r
].label
= N_("Complete");
4013 q_status_message(SM_ORDER
, 0, 2, _("Error allocating space"));
4018 simple_export_opts
[++r
].ch
= -1;
4020 full_filename
[0] = '\0';
4022 r
= get_export_filename(ps
, filename
, NULL
, full_filename
, sizeof(filename
),
4023 prompt_msg
, lister_msg
, simple_export_opts
, &rflags
,
4024 -FOOTER_ROWS(ps
), GE_IS_EXPORT
, &history
);
4028 else if(!full_filename
[0]){
4033 dprint((5, "Opening file \"%s\" for export\n",
4034 full_filename
? full_filename
: "?"));
4036 if((store
= so_get(FileStar
, full_filename
, WRITE_ACCESS
|WRITE_TO_LOCALE
)) != NULL
){
4040 gf_set_so_writec(&pc
, store
);
4041 gf_set_readc(&gc
, srctext
, (srctype
== CharStar
)
4042 ? strlen((char *)srctext
)
4046 if((pipe_err
= gf_pipe(gc
, pc
)) != NULL
){
4047 q_status_message2(SM_ORDER
| SM_DING
, 3, 3,
4048 /* TRANSLATORS: Problem saving to <filename>: <error text> */
4049 _("Problem saving to \"%s\": %s"),
4050 filename
, pipe_err
);
4056 gf_clear_so_writec(store
);
4057 if(so_give(&store
)){
4058 q_status_message2(SM_ORDER
| SM_DING
, 3, 3,
4059 _("Problem saving to \"%s\": %s"),
4060 filename
, error_description(errno
));
4065 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
4066 _("Error opening file \"%s\" for export: %s"),
4067 full_filename
, error_description(errno
));
4074 /* overloading full_filename */
4075 snprintf(full_filename
, sizeof(full_filename
), "%c%s",
4076 (prompt_msg
&& prompt_msg
[0])
4077 ? (islower((unsigned char)prompt_msg
[0])
4078 ? toupper((unsigned char)prompt_msg
[0]) : prompt_msg
[0])
4080 (prompt_msg
&& prompt_msg
[0]) ? prompt_msg
+1 : "ext");
4081 full_filename
[sizeof(full_filename
)-1] = '\0';
4082 q_status_message3(SM_ORDER
,0,2,"%s %s to \"%s\"",
4086 : rflags
& GER_APPEND
? "appended" : "exported",
4091 cmd_cancelled("Export");
4095 q_status_message1(SM_ORDER
, 0, 2,
4096 _("Can't export to file outside of %s"), VAR_OPER_DIR
);
4100 ps
->mangled_footer
= 1;
4106 * Ask user what file to export to.
4108 * filename -- On input, this is the filename to start with. On exit,
4109 * this is the filename chosen. (but this isn't used)
4110 * deefault -- This is the default value if user hits return. The
4111 * prompt will have [deefault] added to it automatically.
4112 * full_filename -- This is the full filename on exit.
4113 * len -- Minimum length of _both_ filename and full_filename.
4114 * prompt_msg -- Message to insert in prompt.
4115 * lister_msg -- Message to insert in file_lister.
4116 * opts -- Key options.
4117 * There is a tangled relationship between the callers
4118 * and this routine as far as opts are concerned. Some
4119 * of the opts are handled here. In particular, r == 3,
4120 * r == 10, r == 11, and r == 13 are all handled here.
4121 * Don't use those values unless you want what happens
4122 * here. r == 12 and others are handled by the caller.
4123 * rflags -- Return flags
4124 * GER_OVER - overwrite of existing file
4125 * GER_APPEND - append of existing file
4126 * else file did not exist before
4128 * GER_ALLPARTS - AllParts toggle was turned on
4130 * qline -- Command line to prompt on.
4131 * flags -- Logically OR'd flags
4132 * GE_IS_EXPORT - The command was an Export command
4133 * so the prompt should include
4135 * GE_SEQ_SENSITIVE - The command that got us here is
4136 * sensitive to sequence number changes
4137 * caused by unsolicited expunges.
4138 * GE_NO_APPEND - We will not allow append to an
4139 * existing file, only removal of the
4140 * file if it exists.
4141 * GE_IS_IMPORT - We are selecting for reading.
4142 * No overwriting or checking for
4143 * existence at all. Don't use this
4144 * together with GE_NO_APPEND.
4145 * GE_ALLPARTS - Turn on AllParts toggle.
4146 * GE_BINARY - Turn on Binary toggle.
4148 * Returns: -1 cancelled
4149 * -2 prohibited by VAR_OPER_DIR
4150 * -3 other error, already reported here
4152 * 12 user chose 12 command from opts
4155 get_export_filename(struct pine
*ps
, char *filename
, char *deefault
,
4156 char *full_filename
, size_t len
, char *prompt_msg
,
4157 char *lister_msg
, ESCKEY_S
*optsarg
, int *rflags
,
4158 int qline
, int flags
, HISTORY_S
**history
)
4160 char dir
[MAXPATH
+1], dir2
[MAXPATH
+1];
4161 char precolon
[MAXPATH
+1], postcolon
[MAXPATH
+1];
4162 char filename2
[MAXPATH
+1], tmp
[MAXPATH
+1], *fn
, *ill
;
4163 int l
, i
, ku
= -1, r
, fatal
, homedir
= 0, was_abs_path
=0, avail
, ret
= 0;
4164 int allparts
= 0, binary
= 0;
4165 char prompt_buf
[400];
4167 ESCKEY_S
*opts
= NULL
;
4168 struct variable
*vars
= ps
->vars
;
4170 if(flags
& GE_ALLPARTS
|| history
){
4172 * Copy the opts and add one to the end of the list.
4174 for(i
= 0; optsarg
[i
].ch
!= -1; i
++)
4180 if(flags
& GE_ALLPARTS
)
4183 if(flags
& GE_BINARY
)
4186 opts
= (ESCKEY_S
*) fs_get((i
+1) * sizeof(*opts
));
4187 memset(opts
, 0, (i
+1) * sizeof(*opts
));
4189 for(i
= 0; optsarg
[i
].ch
!= -1; i
++){
4190 opts
[i
].ch
= optsarg
[i
].ch
;
4191 opts
[i
].rval
= optsarg
[i
].rval
;
4192 opts
[i
].name
= optsarg
[i
].name
; /* no need to make a copy */
4193 opts
[i
].label
= optsarg
[i
].label
; /* " */
4196 if(flags
& GE_ALLPARTS
){
4198 opts
[i
].ch
= ctrl('P');
4200 opts
[i
].name
= "^P";
4201 /* TRANSLATORS: Export all attachment parts */
4202 opts
[i
++].label
= N_("AllParts");
4205 if(flags
& GE_BINARY
){
4207 opts
[i
].ch
= ctrl('R');
4209 opts
[i
].name
= "^R";
4210 opts
[i
++].label
= N_("Binary");
4214 opts
[i
].ch
= KEY_UP
;
4218 opts
[i
++].label
= "";
4220 opts
[i
].ch
= KEY_DOWN
;
4223 opts
[i
++].label
= "";
4229 init_hist(history
, HISTSIZE
);
4237 if(F_ON(F_USE_CURRENT_DIR
, ps
))
4239 else if(VAR_OPER_DIR
){
4240 strncpy(dir
, VAR_OPER_DIR
, sizeof(dir
));
4241 dir
[sizeof(dir
)-1] = '\0';
4243 #if defined(DOS) || defined(OS2)
4244 else if(VAR_FILE_DIR
){
4245 strncpy(dir
, VAR_FILE_DIR
, sizeof(dir
));
4246 dir
[sizeof(dir
)-1] = '\0';
4255 postcolon
[0] = '\0';
4256 strncpy(precolon
, dir
, sizeof(precolon
));
4257 precolon
[sizeof(precolon
)-1] = '\0';
4259 strncpy(def
, deefault
, sizeof(def
)-1);
4260 def
[sizeof(def
)-1] = '\0';
4261 removing_leading_and_trailing_white_space(def
);
4266 avail
= MAX(20, ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80) - MIN_OPT_ENT_WIDTH
;
4268 /*---------- Prompt the user for the file name -------------*/
4271 char dirb
[50], fileb
[50];
4272 int l1
, l2
, l3
, l4
, l5
, needed
;
4273 char *p
, p1
[100], p2
[100], *p3
, p4
[100], p5
[100];
4275 snprintf(p1
, sizeof(p1
), "%sCopy ",
4276 (flags
& GE_IS_EXPORT
) ? "EXPORT: " :
4277 (flags
& GE_IS_IMPORT
) ? "IMPORT: " : "SAVE: ");
4278 p1
[sizeof(p1
)-1] = '\0';
4281 strncpy(p2
, prompt_msg
? prompt_msg
: "", sizeof(p2
)-1);
4282 p2
[sizeof(p2
)-1] = '\0';
4285 if(rflags
&& *rflags
& GER_ALLPARTS
)
4292 snprintf(p4
, sizeof(p4
), " %s file%s%s",
4293 (flags
& GE_IS_IMPORT
) ? "from" : "to",
4294 is_absolute_path(filename
) ? "" : " in ",
4295 is_absolute_path(filename
) ? "" :
4296 (!dir
[0] ? "current directory"
4297 : (dir
[0] == '~' && !dir
[1]) ? "home directory"
4298 : short_str(dir
,dirb
,sizeof(dirb
),30,FrontDots
)));
4299 p4
[sizeof(p4
)-1] = '\0';
4302 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4304 *def
? short_str(def
,fileb
,sizeof(fileb
),40,EndDots
) : "",
4306 p5
[sizeof(p5
)-1] = '\0';
4309 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0){
4310 snprintf(p4
, sizeof(p4
), " %s file%s%s",
4311 (flags
& GE_IS_IMPORT
) ? "from" : "to",
4312 is_absolute_path(filename
) ? "" : " in ",
4313 is_absolute_path(filename
) ? "" :
4314 (!dir
[0] ? "current dir"
4315 : (dir
[0] == '~' && !dir
[1]) ? "home dir"
4316 : short_str(dir
,dirb
,sizeof(dirb
),10,FrontDots
)));
4317 p4
[sizeof(p4
)-1] = '\0';
4321 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l5
> 0){
4322 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4324 *def
? short_str(def
,fileb
,sizeof(fileb
),
4325 MAX(15,l5
-5-needed
),EndDots
) : "",
4327 p5
[sizeof(p5
)-1] = '\0';
4331 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l2
> 0){
4334 * 14 is about the shortest we can make this, because there are
4335 * fixed length strings of length 14 coming in here.
4337 p
= short_str(prompt_msg
, p2
, sizeof(p2
), MAX(14,l2
-needed
), FrontDots
);
4339 strncpy(p2
, p
, sizeof(p2
)-1);
4340 p2
[sizeof(p2
)-1] = '\0';
4346 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0){
4347 strncpy(p1
, "Copy ", sizeof(p1
)-1);
4348 p1
[sizeof(p1
)-1] = '\0';
4352 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l5
> 0){
4353 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4355 *def
? short_str(def
,fileb
, sizeof(fileb
),
4356 MAX(10,l5
-5-needed
),EndDots
) : "",
4358 p5
[sizeof(p5
)-1] = '\0';
4362 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l3
> 0){
4363 if(needed
<= l3
- strlen(" (+ atts)"))
4365 else if(needed
<= l3
- strlen(" (atts)"))
4367 else if(needed
<= l3
- strlen(" (+)"))
4369 else if(needed
<= l3
- strlen("+"))
4377 snprintf(prompt_buf
, sizeof(prompt_buf
), "%s%s%s%s%s", p1
, p2
, p3
, p4
, p5
);
4378 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4381 if(items_in_hist(*history
) > 0){
4382 opts
[ku
].name
= HISTORY_UP_KEYNAME
;
4383 opts
[ku
].label
= HISTORY_KEYLABEL
;
4384 opts
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
4385 opts
[ku
+1].label
= HISTORY_KEYLABEL
;
4389 opts
[ku
].label
= "";
4390 opts
[ku
+1].name
= "";
4391 opts
[ku
+1].label
= "";
4395 oeflags
= OE_APPEND_CURRENT
|
4396 ((flags
& GE_SEQ_SENSITIVE
) ? OE_SEQ_SENSITIVE
: 0);
4397 r
= optionally_enter(filename
, qline
, 0, len
, prompt_buf
,
4398 opts
, NO_HELP
, &oeflags
);
4403 * Helps may not be right if you add another caller or change
4404 * things. Check it out.
4406 if(flags
& GE_IS_IMPORT
)
4407 helper(h_ge_import
, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE
);
4408 else if(flags
& GE_ALLPARTS
)
4409 helper(h_ge_allparts
, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE
);
4411 helper(h_ge_export
, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE
);
4413 ps
->mangled_screen
= 1;
4417 else if(r
== 10 || r
== 11){ /* Browser or File Completion */
4418 if(filename
[0]=='~'){
4419 if(filename
[1] == C_FILESEP
&& filename
[2]!='\0'){
4422 for(i
=0; filename
[i
+2] != '\0' && i
+2 < len
-1; i
++)
4423 filename
[i
] = filename
[i
+2];
4425 strncpy(dir
, precolon
, sizeof(dir
)-1);
4426 dir
[sizeof(dir
)-1] = '\0';
4428 else if(filename
[1]=='\0' ||
4429 (filename
[1] == C_FILESEP
&& filename
[2] == '\0')){
4433 strncpy(dir
, precolon
, sizeof(dir
)-1);
4434 dir
[sizeof(dir
)-1] = '\0';
4437 else if(!dir
[0] && !is_absolute_path(filename
) && was_abs_path
){
4441 strncpy(dir
, precolon
, sizeof(dir
)-1);
4442 dir
[sizeof(dir
)-1] = '\0';
4451 strncpy(tmp
, filename
, sizeof(tmp
)-1);
4452 tmp
[sizeof(tmp
)-1] = '\0';
4453 if(*tmp
&& is_absolute_path(tmp
))
4454 fnexpand(tmp
, sizeof(tmp
));
4455 if(strncmp(tmp
,postcolon
, strlen(postcolon
)))
4456 postcolon
[0] = '\0';
4458 if(*tmp
&& (fn
= last_cmpnt(tmp
))){
4460 strncpy(filename2
, fn
, sizeof(filename2
)-1);
4461 filename2
[sizeof(filename2
)-1] = '\0';
4462 if(is_absolute_path(tmp
)){
4463 strncpy(dir2
, tmp
, MIN(fn
- tmp
, sizeof(dir2
)-1));
4464 dir2
[MIN(fn
- tmp
, sizeof(dir2
)-1)] = '\0';
4466 if(tmp
[1]==':' && tmp
[2]=='\\' && dir2
[2]=='\0'){
4471 strncpy(postcolon
, dir2
, sizeof(postcolon
)-1);
4472 postcolon
[sizeof(postcolon
)-1] = '\0';
4478 * Just building the directory name in dir2,
4479 * full_filename is overloaded.
4481 snprintf(full_filename
, len
, "%.*s", MIN(fn
-tmp
,len
-1), tmp
);
4482 full_filename
[len
-1] = '\0';
4483 strncpy(postcolon
, full_filename
, sizeof(postcolon
)-1);
4484 postcolon
[sizeof(postcolon
)-1] = '\0';
4485 build_path(dir2
, !dir
[0] ? p
= (char *)getcwd(NULL
,MAXPATH
)
4486 : (dir
[0] == '~' && !dir
[1])
4489 full_filename
, sizeof(dir2
));
4495 if(is_absolute_path(tmp
)){
4496 strncpy(dir2
, tmp
, sizeof(dir2
)-1);
4497 dir2
[sizeof(dir2
)-1] = '\0';
4499 if(dir2
[2]=='\0' && dir2
[1]==':'){
4502 strncpy(postcolon
,dir2
,sizeof(postcolon
)-1);
4503 postcolon
[sizeof(postcolon
)-1] = '\0';
4506 filename2
[0] = '\0';
4510 strncpy(filename2
, tmp
, sizeof(filename2
)-1);
4511 filename2
[sizeof(filename2
)-1] = '\0';
4513 (void)getcwd(dir2
, sizeof(dir2
));
4514 else if(dir
[0] == '~' && !dir
[1]){
4515 strncpy(dir2
, ps
->home_dir
, sizeof(dir2
)-1);
4516 dir2
[sizeof(dir2
)-1] = '\0';
4519 strncpy(dir2
, dir
, sizeof(dir2
)-1);
4520 dir2
[sizeof(dir2
)-1] = '\0';
4523 postcolon
[0] = '\0';
4527 build_path(full_filename
, dir2
, filename2
, len
);
4528 if(!strcmp(full_filename
, dir2
))
4529 filename2
[0] = '\0';
4530 if(full_filename
[strlen(full_filename
)-1] == C_FILESEP
4531 && isdir(full_filename
,NULL
,NULL
)){
4532 if(strlen(full_filename
) == 1)
4533 strncpy(postcolon
, full_filename
, sizeof(postcolon
)-1);
4534 else if(filename2
[0])
4535 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4536 postcolon
[sizeof(postcolon
)-1] = '\0';
4537 strncpy(dir2
, full_filename
, sizeof(dir2
)-1);
4538 dir2
[sizeof(dir2
)-1] = '\0';
4539 filename2
[0] = '\0';
4541 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4542 else if(full_filename
[strlen(full_filename
)-1] == C_FILESEP
){
4543 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4544 postcolon
[sizeof(postcolon
)-1] = '\0';
4545 strncpy(dir2
, full_filename
, sizeof(dir2
)-1);
4546 dir2
[sizeof(dir2
)-1] = '\0';
4547 filename2
[0] = '\0';
4550 if(dir2
[strlen(dir2
)-1] == C_FILESEP
&& strlen(dir2
)!=1
4551 && strcmp(dir2
+1, ":\\"))
4552 /* last condition to prevent stripping of '\\'
4553 in windows partition */
4554 dir2
[strlen(dir2
)-1] = '\0';
4556 if(r
== 10){ /* File Browser */
4557 r
= file_lister(lister_msg
? lister_msg
: "EXPORT",
4558 dir2
, sizeof(dir2
), filename2
, sizeof(filename2
),
4560 (flags
& GE_IS_IMPORT
) ? FB_READ
: FB_SAVE
);
4562 /* Windows has a special "feature" in which entering the file browser will
4563 change the working directory if the directory is changed at all (even
4564 clicking "Cancel" will change the working directory).
4566 if(F_ON(F_USE_CURRENT_DIR
, ps
))
4567 (void)getcwd(dir2
,sizeof(dir2
));
4569 if(isdir(dir2
,NULL
,NULL
)){
4570 strncpy(precolon
, dir2
, sizeof(precolon
)-1);
4571 precolon
[sizeof(precolon
)-1] = '\0';
4573 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4574 postcolon
[sizeof(postcolon
)-1] = '\0';
4576 build_path(full_filename
, dir2
, filename2
, len
);
4577 if(isdir(full_filename
, NULL
, NULL
)){
4578 strncpy(dir
, full_filename
, sizeof(dir
)-1);
4579 dir
[sizeof(dir
)-1] = '\0';
4583 fn
= last_cmpnt(full_filename
);
4584 strncpy(dir
, full_filename
,
4585 MIN(fn
- full_filename
, sizeof(dir
)-1));
4586 dir
[MIN(fn
- full_filename
, sizeof(dir
)-1)] = '\0';
4587 if(fn
- full_filename
> 1)
4588 dir
[fn
- full_filename
- 1] = '\0';
4591 if(!strcmp(dir
, ps
->home_dir
)){
4596 strncpy(filename
, fn
, len
-1);
4597 filename
[len
-1] = '\0';
4600 else{ /* File Completion */
4601 if(!pico_fncomplete(dir2
, filename2
, sizeof(filename2
)))
4603 strncat(postcolon
, filename2
,
4604 sizeof(postcolon
)-1-strlen(postcolon
));
4605 postcolon
[sizeof(postcolon
)-1] = '\0';
4607 was_abs_path
= is_absolute_path(filename
);
4609 if(!strcmp(dir
, ps
->home_dir
)){
4614 strncpy(filename
, postcolon
, len
-1);
4615 filename
[len
-1] = '\0';
4616 strncpy(dir
, precolon
, sizeof(dir
)-1);
4617 dir
[sizeof(dir
)-1] = '\0';
4619 if(filename
[0] == '~' && !filename
[1]){
4627 else if(r
== 12){ /* Download, caller handles it */
4631 else if(r
== 13){ /* toggle AllParts bit */
4633 if(*rflags
& GER_ALLPARTS
){
4634 *rflags
&= ~GER_ALLPARTS
;
4635 opts
[allparts
].label
= N_("AllParts");
4638 *rflags
|= GER_ALLPARTS
;
4639 /* opposite of All Parts, No All Parts */
4640 opts
[allparts
].label
= N_("NoAllParts");
4647 else if(r
== 14){ /* List file names matching partial? */
4651 else if(r
== 15){ /* toggle Binary bit */
4653 if(*rflags
& GER_BINARY
){
4654 *rflags
&= ~GER_BINARY
;
4655 opts
[binary
].label
= N_("Binary");
4658 *rflags
|= GER_BINARY
;
4659 opts
[binary
].label
= N_("No Binary");
4665 else if(r
== 1){ /* Cancel */
4672 else if(r
== 30 || r
== 31){
4677 p
= get_prev_hist(*history
, filename
, 0, NULL
);
4679 p
= get_next_hist(*history
, filename
, 0, NULL
);
4684 strncpy(dir
, p
, MIN(fn
- p
, sizeof(dir
)-1));
4685 dir
[MIN(fn
- p
, sizeof(dir
)-1)] = '\0';
4687 dir
[fn
- p
- 1] = '\0';
4689 if(!strcmp(dir
, ps
->home_dir
)){
4694 strncpy(filename
, fn
, len
-1);
4695 filename
[len
-1] = '\0';
4707 removing_leading_and_trailing_white_space(filename
);
4710 if(!*def
){ /* Cancel */
4715 strncpy(filename
, def
, len
-1);
4716 filename
[len
-1] = '\0';
4719 #if defined(DOS) || defined(OS2)
4720 if(is_absolute_path(filename
)){
4721 fixpath(filename
, len
);
4724 if(filename
[0] == '~'){
4725 if(fnexpand(filename
, len
) == NULL
){
4726 char *p
= strindex(filename
, '/');
4729 q_status_message1(SM_ORDER
| SM_DING
, 3, 3,
4730 _("Error expanding file name: \"%s\" unknown user"),
4737 if(is_absolute_path(filename
)){
4738 strncpy(full_filename
, filename
, len
-1);
4739 full_filename
[len
-1] = '\0';
4743 build_path(full_filename
, (char *)getcwd(dir
,sizeof(dir
)),
4745 else if(dir
[0] == '~' && !dir
[1])
4746 build_path(full_filename
, ps
->home_dir
, filename
, len
);
4748 build_path(full_filename
, dir
, filename
, len
);
4751 if((ill
= filter_filename(full_filename
, &fatal
,
4752 ps_global
->restricted
|| ps_global
->VAR_OPER_DIR
)) != NULL
){
4754 q_status_message1(SM_ORDER
| SM_DING
, 3, 3, "%s", ill
);
4758 /* BUG: we should beep when the key's pressed rather than bitch later */
4759 /* Warn and ask for confirmation. */
4760 snprintf(prompt_buf
, sizeof(prompt_buf
), "File name contains a '%s'. %s anyway",
4761 ill
, (flags
& GE_IS_EXPORT
) ? "Export" : "Save");
4762 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4763 if(want_to(prompt_buf
, 'n', 0, NO_HELP
,
4764 ((flags
& GE_SEQ_SENSITIVE
) ? RB_SEQ_SENSITIVE
: 0)) != 'y')
4769 break; /* Must have got an OK file name */
4772 if(VAR_OPER_DIR
&& !in_dir(VAR_OPER_DIR
, full_filename
)){
4777 if(!can_access(full_filename
, ACCESS_EXISTS
)){
4779 static ESCKEY_S access_opts
[] = {
4780 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4781 a file or append to the end of the file */
4782 {'o', 'o', "O", N_("Overwrite")},
4783 {'a', 'a', "A", N_("Append")},
4784 {-1, 0, NULL
, NULL
}};
4786 rbflags
= RB_NORM
| ((flags
& GE_SEQ_SENSITIVE
) ? RB_SEQ_SENSITIVE
: 0);
4788 if(flags
& GE_NO_APPEND
){
4789 r
= strlen(filename
);
4790 snprintf(prompt_buf
, sizeof(prompt_buf
),
4791 /* TRANSLATORS: asking user whether to overwrite a file or not,
4792 File <filename> already exists. Overwrite it ? */
4793 _("File \"%s%s\" already exists. Overwrite it "),
4794 (r
> 20) ? "..." : "",
4795 filename
+ ((r
> 20) ? r
- 20 : 0));
4796 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4797 if(want_to(prompt_buf
, 'n', 'x', NO_HELP
, rbflags
) == 'y'){
4799 *rflags
|= GER_OVER
;
4801 if(our_unlink(full_filename
) < 0){
4802 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
4803 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4804 _("Cannot remove old %s: %s"),
4805 full_filename
, error_description(errno
));
4813 else if(!(flags
& GE_IS_IMPORT
)){
4814 r
= strlen(filename
);
4815 snprintf(prompt_buf
, sizeof(prompt_buf
),
4816 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4817 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4818 (r
> 20) ? "..." : "",
4819 filename
+ ((r
> 20) ? r
- 20 : 0));
4820 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4821 switch(radio_buttons(prompt_buf
, -FOOTER_ROWS(ps_global
),
4822 access_opts
, 'a', 'x', NO_HELP
, rbflags
)){
4825 *rflags
|= GER_OVER
;
4827 if(our_truncate(full_filename
, (off_t
)0) < 0)
4828 /* trouble truncating, but we'll give it a try anyway */
4829 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
4830 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
4831 _("Warning: Cannot truncate old %s: %s"),
4832 full_filename
, error_description(errno
));
4837 *rflags
|= GER_APPEND
;
4850 if(history
&& ret
== 0)
4851 save_hist(*history
, full_filename
, 0, NULL
);
4853 if(opts
&& opts
!= optsarg
)
4854 fs_give((void **) &opts
);
4860 /*----------------------------------------------------------------------
4861 parse the config'd upload/download command
4863 Args: cmd -- buffer to return command fit for shellin'
4866 fname -- file name to build into the command
4868 Returns: pointer to cmd_str buffer or NULL on real bad error
4870 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
4871 cfg_str is written to standard out right before a successful
4872 return of this function. The call immediately following this
4873 function darn well better be the shell exec...
4876 build_updown_cmd(char *cmd
, size_t cmdlen
, char *prefix
, char *cfg_str
, char *fname
)
4879 int fname_found
= 0;
4881 if(prefix
&& *prefix
){
4882 /* loop thru replacing all occurances of _FILE_ */
4883 p
= strncpy(cmd
, prefix
, cmdlen
);
4884 cmd
[cmdlen
-1] = '\0';
4885 while((p
= strstr(p
, "_FILE_")))
4886 rplstr(p
, cmdlen
-(p
-cmd
), 6, fname
);
4891 /* loop thru replacing all occurances of _FILE_ */
4892 p
= strncpy(cmd
, cfg_str
, cmdlen
);
4893 cmd
[cmdlen
-1] = '\0';
4894 while((p
= strstr(p
, "_FILE_"))){
4895 rplstr(p
, cmdlen
-(p
-cmd
), 6, fname
);
4900 snprintf(cmd
+strlen(cmd
), cmdlen
-strlen(cmd
), " %s", fname
);
4902 cmd
[cmdlen
-1] = '\0';
4904 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
4910 /*----------------------------------------------------------------------
4911 Write a berzerk format message delimiter using the given putc function
4913 Args: e -- envelope of message to write
4914 pc -- function to use
4916 Returns: TRUE if we could write it, FALSE if there was a problem
4918 NOTE: follows delimiter with OS-dependent newline
4921 bezerk_delimiter(ENVELOPE
*env
, MESSAGECACHE
*mc
, gf_io_t pc
, int leading_newline
)
4927 /* write "[\n]From mailbox[@host] " */
4928 if(!((leading_newline
? gf_puts(NEWLINE
, pc
) : 1)
4929 && gf_puts("From ", pc
)
4930 && gf_puts((env
&& env
->from
) ? env
->from
->mailbox
4931 : "the-concourse-on-high", pc
)
4932 && gf_puts((env
&& env
->from
&& env
->from
->host
) ? "@" : "", pc
)
4933 && gf_puts((env
&& env
->from
&& env
->from
->host
) ? env
->from
->host
4939 when
= mail_longdate(mc
);
4940 else if(env
&& env
->date
&& env
->date
[0]
4941 && mail_parse_date(&telt
,env
->date
))
4942 when
= mail_longdate(&telt
);
4948 while(p
&& *p
&& *p
!= '\n') /* write date */
4952 if(!gf_puts(NEWLINE
, pc
)) /* write terminating newline */
4959 /*----------------------------------------------------------------------
4960 Execute command to jump to a given message number
4962 Args: qline -- Line to ask question on
4964 Result: returns true if the use selected a new message, false otherwise
4968 jump_to(MSGNO_S
*msgmap
, int qline
, UCS first_num
, SCROLL_S
*sparms
, CmdWhere in_index
)
4970 char jump_num_string
[80], *j
, prompt
[70];
4973 static ESCKEY_S jump_to_key
[] = { {0, 0, NULL
, NULL
},
4974 /* TRANSLATORS: go to First Message */
4975 {ctrl('Y'), 10, "^Y", N_("First Msg")},
4976 {ctrl('V'), 11, "^V", N_("Last Msg")},
4977 {-1, 0, NULL
, NULL
} };
4979 dprint((4, "\n - jump_to -\n"));
4982 if(sparms
&& sparms
->jump_is_debug
)
4983 return(get_level(qline
, first_num
, sparms
));
4986 if(!any_messages(msgmap
, NULL
, "to Jump to"))
4989 if(first_num
&& first_num
< 0x80 && isdigit((unsigned char) first_num
)){
4990 jump_num_string
[0] = first_num
;
4991 jump_num_string
[1] = '\0';
4994 jump_num_string
[0] = '\0';
4996 if(mn_total_cur(msgmap
) > 1L){
4997 snprintf(prompt
, sizeof(prompt
), "Unselect %s msgs in favor of number to be entered",
4998 comatose(mn_total_cur(msgmap
)));
4999 prompt
[sizeof(prompt
)-1] = '\0';
5000 if((rc
= want_to(prompt
, 'n', 0, NO_HELP
, WT_NORM
)) == 'n')
5004 snprintf(prompt
, sizeof(prompt
), "%s number to jump to : ", in_index
== ThrdIndx
5007 prompt
[sizeof(prompt
)-1] = '\0';
5011 int flags
= OE_APPEND_CURRENT
;
5013 rc
= optionally_enter(jump_num_string
, qline
, 0,
5014 sizeof(jump_num_string
), prompt
,
5015 jump_to_key
, help
, &flags
);
5017 help
= help
== NO_HELP
5018 ? (in_index
== ThrdIndx
? h_oe_jump_thd
: h_oe_jump
)
5022 else if(rc
== 10 || rc
== 11){
5026 closest
= closest_jump_target(rc
== 10 ? 1L
5027 : ((in_index
== ThrdIndx
)
5028 ? msgmap
->max_thrdno
5029 : mn_get_total(msgmap
)),
5030 ps_global
->mail_stream
,
5032 in_index
, warning
, sizeof(warning
));
5033 /* ignore warning */
5038 * If we take out the *jump_num_string nonempty test in this if
5039 * then the closest_jump_target routine will offer a jump to the
5040 * last message. However, it is slow because you have to wait for
5041 * the status message and it is annoying for people who hit J command
5042 * by mistake and just want to hit return to do nothing, like has
5043 * always worked. So the test is there for now. Hubert 2002-08-19
5045 * Jumping to first/last message is now possible through ^Y/^V
5046 * commands above. jpf 2002-08-21
5047 * (and through "end" hubert 2006-07-07)
5049 if(rc
== 0 && *jump_num_string
!= '\0'){
5050 removing_leading_and_trailing_white_space(jump_num_string
);
5051 for(j
=jump_num_string
; isdigit((unsigned char)*j
) || *j
=='-'; j
++)
5055 if(!strucmp("end", j
))
5056 return((in_index
== ThrdIndx
) ? msgmap
->max_thrdno
: mn_get_total(msgmap
));
5058 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5059 _("Invalid number entered. Use only digits 0-9"));
5060 jump_num_string
[0] = '\0';
5064 long closest
, jump_num
;
5066 if(*jump_num_string
)
5067 jump_num
= atol(jump_num_string
);
5072 closest
= closest_jump_target(jump_num
, ps_global
->mail_stream
,
5074 *jump_num_string
? 0 : 1,
5075 in_index
, warning
, sizeof(warning
));
5077 q_status_message(SM_ORDER
| SM_DING
, 2, 2, warning
);
5079 if(closest
== jump_num
)
5083 jump_num_string
[0] = '\0';
5085 strncpy(jump_num_string
, long2string(closest
),
5086 sizeof(jump_num_string
));
5101 * cmd_delete_action - handle msgno advance and such after single message deletion
5104 cmd_delete_action(struct pine
*state
, MSGNO_S
*msgmap
, CmdWhere in_index
)
5110 msgno
= mn_get_cur(msgmap
);
5111 advance_cur_after_delete(state
, state
->mail_stream
, msgmap
, in_index
);
5113 if(IS_NEWS(state
->mail_stream
)
5114 || ((state
->context_current
->use
& CNTXT_INCMNG
)
5115 && context_isambig(state
->cur_folder
))){
5117 opts
= (NSF_TRUST_FLAGS
| NSF_SKIP_CHID
);
5118 if(in_index
== View
)
5119 opts
&= ~NSF_SKIP_CHID
;
5121 (void)next_sorted_flagged(F_UNDEL
|F_UNSEEN
, state
->mail_stream
, msgno
, &opts
);
5122 if(!(opts
& NSF_FLAG_MATCH
)){
5123 char nextfolder
[MAXPATH
];
5125 strncpy(nextfolder
, state
->cur_folder
, sizeof(nextfolder
));
5126 nextfolder
[sizeof(nextfolder
)-1] = '\0';
5127 rv
= next_folder(NULL
, nextfolder
, sizeof(nextfolder
), nextfolder
,
5128 state
->context_current
, NULL
, NULL
)
5129 ? ". Press TAB for next folder."
5130 : ". No more folders to TAB to.";
5139 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5142 cmd_delete_index(struct pine
*state
, MSGNO_S
*msgmap
)
5144 return(cmd_delete_action(state
, msgmap
,MsgIndx
));
5148 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5151 cmd_delete_view(struct pine
*state
, MSGNO_S
*msgmap
)
5153 return(cmd_delete_action(state
, msgmap
, View
));
5158 advance_cur_after_delete(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, CmdWhere in_index
)
5160 long new_msgno
, msgno
;
5163 new_msgno
= msgno
= mn_get_cur(msgmap
);
5164 opts
= NSF_TRUST_FLAGS
;
5166 if(F_ON(F_DEL_SKIPS_DEL
, state
)){
5168 if(THREADING() && sp_viewing_a_thread(stream
))
5169 opts
|= NSF_SKIP_CHID
;
5171 new_msgno
= next_sorted_flagged(F_UNDEL
, stream
, msgno
, &opts
);
5174 mn_inc_cur(stream
, msgmap
,
5175 (in_index
== View
&& THREADING()
5176 && sp_viewing_a_thread(stream
))
5178 : (in_index
== View
)
5179 ? MH_ANYTHD
: MH_NONE
);
5180 new_msgno
= mn_get_cur(msgmap
);
5181 if(new_msgno
!= msgno
)
5182 opts
|= NSF_FLAG_MATCH
;
5186 * Viewing_a_thread is the complicated case because we want to ignore
5187 * other threads at first and then look in other threads if we have to.
5188 * By ignoring other threads we also ignore collapsed partial threads
5189 * in our own thread.
5191 if(THREADING() && sp_viewing_a_thread(stream
) && !(opts
& NSF_FLAG_MATCH
)){
5192 long rawno
, orig_thrdno
;
5193 PINETHRD_S
*thrd
, *topthrd
= NULL
;
5195 rawno
= mn_m2raw(msgmap
, msgno
);
5196 thrd
= fetch_thread(stream
, rawno
);
5197 if(thrd
&& thrd
->top
)
5198 topthrd
= fetch_thread(stream
, thrd
->top
);
5200 orig_thrdno
= topthrd
? topthrd
->thrdno
: -1L;
5202 opts
= NSF_TRUST_FLAGS
;
5203 new_msgno
= next_sorted_flagged(F_UNDEL
, stream
, msgno
, &opts
);
5206 * If we got a match, new_msgno may be a message in
5207 * a different thread from the one we are viewing, or it could be
5208 * in a collapsed part of this thread.
5210 if(opts
& NSF_FLAG_MATCH
){
5215 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
,new_msgno
));
5216 if(thrd
&& thrd
->top
)
5217 topthrd
= fetch_thread(stream
, thrd
->top
);
5220 * If this match is in the same thread we're already in
5221 * then we're done, else we have to ask the user and maybe
5224 if(!(orig_thrdno
> 0L && topthrd
5225 && topthrd
->thrdno
== orig_thrdno
)){
5227 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD
, state
)){
5228 if(in_index
== View
)
5229 snprintf(pmt
, sizeof(pmt
),
5230 "View message in thread number %.10s",
5231 topthrd
? comatose(topthrd
->thrdno
) : "?");
5233 snprintf(pmt
, sizeof(pmt
), "View thread number %.10s",
5234 topthrd
? comatose(topthrd
->thrdno
) : "?");
5236 ret
= want_to(pmt
, 'y', 'x', NO_HELP
, WT_NORM
);
5242 unview_thread(state
, stream
, msgmap
);
5243 mn_set_cur(msgmap
, new_msgno
);
5245 && (count_lflags_in_thread(stream
, topthrd
, msgmap
,
5247 && view_thread(state
, stream
, msgmap
, 1)){
5248 if(current_index_state
)
5249 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
5251 state
->view_skipped_index
= 1;
5252 state
->next_screen
= mail_view_screen
;
5255 view_thread(state
, stream
, msgmap
, 1);
5256 if(current_index_state
)
5257 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
5259 state
->next_screen
= SCREEN_FUN_NULL
;
5263 new_msgno
= msgno
; /* stick with original */
5268 mn_set_cur(msgmap
, new_msgno
);
5269 if(in_index
!= View
)
5270 adjust_cur_to_visible(stream
, msgmap
);
5276 get_level(int qline
, UCS first_num
, SCROLL_S
*sparms
)
5278 char debug_num_string
[80], *j
, prompt
[70];
5283 if(first_num
&& first_num
< 0x80 && isdigit((unsigned char)first_num
)){
5284 debug_num_string
[0] = first_num
;
5285 debug_num_string
[1] = '\0';
5286 debug_num
= atol(debug_num_string
);
5287 *(int *)(sparms
->proc
.data
.p
) = debug_num
;
5288 q_status_message1(SM_ORDER
, 0, 3, "Show debug <= level %s",
5289 comatose(debug_num
));
5293 debug_num_string
[0] = '\0';
5295 snprintf(prompt
, sizeof(prompt
), "Show debug <= this level (0-%d) : ", MAX(debug
, 9));
5296 prompt
[sizeof(prompt
)-1] = '\0';
5300 int flags
= OE_APPEND_CURRENT
;
5302 rc
= optionally_enter(debug_num_string
, qline
, 0,
5303 sizeof(debug_num_string
), prompt
,
5304 NULL
, help
, &flags
);
5306 help
= help
== NO_HELP
? h_oe_debuglevel
: NO_HELP
;
5311 removing_leading_and_trailing_white_space(debug_num_string
);
5312 for(j
=debug_num_string
; isdigit((unsigned char)*j
); j
++)
5316 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5317 _("Invalid number entered. Use only digits 0-9"));
5318 debug_num_string
[0] = '\0';
5321 debug_num
= atol(debug_num_string
);
5323 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5324 _("Number should be >= 0"));
5325 else if(debug_num
> MAX(debug
,9))
5326 q_status_message1(SM_ORDER
| SM_DING
, 2, 2,
5327 _("Maximum is %s"), comatose(MAX(debug
,9)));
5329 *(int *)(sparms
->proc
.data
.p
) = debug_num
;
5330 q_status_message1(SM_ORDER
, 0, 3,
5331 "Show debug <= level %s",
5332 comatose(debug_num
));
5350 * Returns the message number closest to target that isn't hidden.
5351 * Make warning at least 100 chars.
5352 * A return of 0 means there is no message to jump to.
5355 closest_jump_target(long int target
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int no_target
, CmdWhere in_index
, char *warning
, size_t warninglen
)
5357 long i
, start
, closest
= 0L;
5362 maxnum
= (in_index
== ThrdIndx
) ? msgmap
->max_thrdno
: mn_get_total(msgmap
);
5367 snprintf(warning
, warninglen
, "No %s number entered, jump to end? ",
5368 (in_index
== ThrdIndx
) ? "thread" : "message");
5369 warning
[warninglen
-1] = '\0';
5371 else if(target
< 1L)
5372 start
= 1L - target
;
5373 else if(target
> maxnum
)
5374 start
= target
- maxnum
;
5378 if(target
> 0L && target
<= maxnum
)
5379 if(in_index
== ThrdIndx
5380 || !msgline_hidden(stream
, msgmap
, target
, 0))
5383 for(i
= start
; target
+i
<= maxnum
|| target
-i
> 0L; i
++){
5385 if(target
+i
> 0L && target
+i
<= maxnum
&&
5386 (in_index
== ThrdIndx
5387 || !msgline_hidden(stream
, msgmap
, target
+i
, 0))){
5392 if(target
-i
> 0L && target
-i
<= maxnum
&&
5393 (in_index
== ThrdIndx
5394 || !msgline_hidden(stream
, msgmap
, target
-i
, 0))){
5400 strncpy(buf
, long2string(closest
), sizeof(buf
));
5401 buf
[sizeof(buf
)-1] = '\0';
5404 strncpy(warning
, "Nothing to jump to", warninglen
);
5405 else if(target
< 1L)
5406 snprintf(warning
, warninglen
, "%s number (%s) must be at least %s",
5407 (in_index
== ThrdIndx
) ? "Thread" : "Message",
5408 long2string(target
), buf
);
5409 else if(target
> maxnum
)
5410 snprintf(warning
, warninglen
, "%s number (%s) may be no more than %s",
5411 (in_index
== ThrdIndx
) ? "Thread" : "Message",
5412 long2string(target
), buf
);
5414 snprintf(warning
, warninglen
,
5415 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5416 long2string(target
), buf
);
5418 warning
[warninglen
-1] = '\0';
5424 /*----------------------------------------------------------------------
5425 Prompt for folder name to open, expand the name and return it
5427 Args: qline -- Screen line to prompt on
5428 allow_list -- if 1, allow ^T to bring up collection lister
5430 Result: returns the folder name or NULL
5431 pine structure mangled_footer flag is set
5432 may call the collection lister in which case mangled screen will be set
5434 This prompts the user for the folder to open, possibly calling up
5435 the collection lister if the user types ^T.
5436 ----------------------------------------------------------------------*/
5438 broach_folder(int qline
, int allow_list
, int *notrealinbox
, CONTEXT_S
**context
)
5441 static char newfolder
[MAILTMPLEN
];
5442 char expanded
[MAXPATH
+1],
5443 prompt
[MAX_SCREEN_COLS
+1],
5445 unsigned char *f1
, *f2
, *f3
;
5446 static HISTORY_S
*history
= NULL
;
5447 CONTEXT_S
*tc
, *tc2
;
5449 int rc
, r
, ku
= -1, n
, flags
, last_rc
= 0, inbox
, done
= 0;
5452 * the idea is to provide a clue for the context the file name
5453 * will be saved in (if a non-imap names is typed), and to
5454 * only show the previous if it was also in the same context
5461 (*notrealinbox
) = 1;
5463 init_hist(&history
, HISTSIZE
);
5465 tc
= broach_get_folder(context
? *context
: NULL
, &inbox
, NULL
);
5467 /* set up extra command option keys */
5469 ekey
[rc
].ch
= (allow_list
) ? ctrl('T') : 0 ;
5470 ekey
[rc
].rval
= (allow_list
) ? 2 : 0;
5471 ekey
[rc
].name
= (allow_list
) ? "^T" : "";
5472 ekey
[rc
++].label
= (allow_list
) ? N_("ToFldrs") : "";
5474 if(ps_global
->context_list
->next
){
5475 ekey
[rc
].ch
= ctrl('P');
5477 ekey
[rc
].name
= "^P";
5478 ekey
[rc
++].label
= N_("Prev Collection");
5480 ekey
[rc
].ch
= ctrl('N');
5482 ekey
[rc
].name
= "^N";
5483 ekey
[rc
++].label
= N_("Next Collection");
5486 ekey
[rc
].ch
= ctrl('W');
5488 ekey
[rc
].name
= "^W";
5489 ekey
[rc
++].label
= N_("INBOX");
5491 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps_global
)){
5494 ekey
[rc
].name
= "TAB";
5495 ekey
[rc
++].label
= N_("Complete");
5498 if(F_ON(F_ENABLE_SUB_LISTS
, ps_global
)){
5499 ekey
[rc
].ch
= ctrl('X');
5501 ekey
[rc
].name
= "^X";
5502 ekey
[rc
++].label
= N_("ListMatches");
5505 if(ps_global
->context_list
->next
&& F_ON(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
5506 ekey
[rc
].ch
= KEY_UP
;
5509 ekey
[rc
++].label
= "";
5511 ekey
[rc
].ch
= KEY_DOWN
;
5514 ekey
[rc
++].label
= "";
5516 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
5517 ekey
[rc
].ch
= KEY_UP
;
5521 ekey
[rc
++].label
= "";
5523 ekey
[rc
].ch
= KEY_DOWN
;
5526 ekey
[rc
++].label
= "";
5533 * Figure out next default value for this context. The idea
5534 * is that in each context the last folder opened is cached.
5535 * It's up to pick it out and display it. This is fine
5536 * and dandy if we've currently got the inbox open, BUT
5537 * if not, make the inbox the default the first time thru.
5540 last_folder
= ps_global
->inbox_name
;
5541 inbox
= 1; /* pretend we're in inbox from here on out */
5544 last_folder
= (ps_global
->last_unambig_folder
[0])
5545 ? ps_global
->last_unambig_folder
5546 : ((tc
->last_folder
[0]) ? tc
->last_folder
: NULL
);
5549 unsigned char *fname
= folder_name_decoded((unsigned char *)last_folder
);
5550 snprintf(expanded
, sizeof(expanded
), " [%.*s]", sizeof(expanded
)-5,
5551 fname
? (char *) fname
: last_folder
);
5552 if(fname
) fs_give((void **)&fname
);
5557 expanded
[sizeof(expanded
)-1] = '\0';
5559 /* only show collection number if more than one available */
5560 if(ps_global
->context_list
->next
)
5561 snprintf(prompt
, sizeof(prompt
), "GOTO %s in <%s> %.*s%s: ",
5562 NEWS_TEST(tc
) ? "news group" : "folder",
5563 tc
->nickname
, sizeof(prompt
)-50, expanded
,
5564 *expanded
? " " : "");
5566 snprintf(prompt
, sizeof(prompt
), "GOTO folder %.*s%s: ", sizeof(prompt
)-20, expanded
,
5567 *expanded
? " " : "");
5569 prompt
[sizeof(prompt
)-1] = '\0';
5571 if(utf8_width(prompt
) > MAXPROMPT
){
5572 if(ps_global
->context_list
->next
)
5573 snprintf(prompt
, sizeof(prompt
), "GOTO <%s> %.*s%s: ",
5574 tc
->nickname
, sizeof(prompt
)-50, expanded
,
5575 *expanded
? " " : "");
5577 snprintf(prompt
, sizeof(prompt
), "GOTO %.*s%s: ", sizeof(prompt
)-20, expanded
,
5578 *expanded
? " " : "");
5580 prompt
[sizeof(prompt
)-1] = '\0';
5582 if(utf8_width(prompt
) > MAXPROMPT
){
5583 if(ps_global
->context_list
->next
)
5584 snprintf(prompt
, sizeof(prompt
), "<%s> %.*s%s: ",
5585 tc
->nickname
, sizeof(prompt
)-50, expanded
,
5586 *expanded
? " " : "");
5588 snprintf(prompt
, sizeof(prompt
), "%.*s%s: ", sizeof(prompt
)-20, expanded
,
5589 *expanded
? " " : "");
5591 prompt
[sizeof(prompt
)-1] = '\0';
5596 if(items_in_hist(history
) > 1){
5597 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
5598 ekey
[ku
].label
= HISTORY_KEYLABEL
;
5599 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
5600 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
5604 ekey
[ku
].label
= "";
5605 ekey
[ku
+1].name
= "";
5606 ekey
[ku
+1].label
= "";
5610 /* is there any other way to do this? The point is that we
5611 * are trying to hide mutf7 from the user, and use the utf8
5612 * equivalent. So we create a variable f to take place of
5613 * newfolder, including content and size. f2 is copy of f1
5614 * that has to freed. Sigh!
5616 f3
= (unsigned char *) cpystr(newfolder
);
5617 f1
= fs_get(sizeof(newfolder
));
5618 f2
= folder_name_decoded(f3
);
5619 if(f3
) fs_give((void **)&f3
);
5620 strncpy((char *)f1
, (char *)f2
, sizeof(newfolder
));
5621 f1
[sizeof(newfolder
)-1] = '\0';
5622 if(f2
) fs_give((void **)&f2
);
5624 flags
= OE_APPEND_CURRENT
;
5625 rc
= optionally_enter(f1
, qline
, 0, sizeof(newfolder
),
5626 (char *) prompt
, ekey
, help
, &flags
);
5628 f2
= folder_name_encoded(f1
);
5629 strncpy(newfolder
, (char *)f2
, sizeof(newfolder
));
5630 if(f1
) fs_give((void **)&f1
);
5631 if(f2
) fs_give((void **)&f2
);
5633 ps_global
->mangled_footer
= 1;
5636 case -1 : /* o_e says error! */
5637 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
5638 _("Error reading folder name"));
5641 case 0 : /* o_e says normal entry */
5642 removing_trailing_white_space(newfolder
);
5643 removing_leading_white_space(newfolder
);
5646 char *name
, *fullname
= NULL
;
5647 int exists
, breakout
= 0;
5649 save_hist(history
, newfolder
, 0, tc
);
5651 if(!(name
= folder_is_nick(newfolder
, FOLDERS(tc
),
5655 if(update_folder_spec(expanded
, sizeof(expanded
), name
)){
5656 strncpy(name
= newfolder
, expanded
, sizeof(newfolder
));
5657 newfolder
[sizeof(newfolder
)-1] = '\0';
5660 exists
= folder_name_exists(tc
, name
, &fullname
);
5663 strncpy(name
= newfolder
, fullname
, sizeof(newfolder
));
5664 newfolder
[sizeof(newfolder
)-1] = '\0';
5665 fs_give((void **) &fullname
);
5670 * if we know the things a folder, open it.
5671 * else if we know its a directory, visit it.
5672 * else we're not sure (it either doesn't really
5673 * exist or its unLISTable) so try opening it anyway
5675 if(exists
& FEX_ISFILE
){
5679 else if((exists
& FEX_ISDIR
)){
5681 CONTEXT_S
*fake_context
;
5682 char tmp
[MAILTMPLEN
];
5685 strncpy(tmp
, name
, sizeof(tmp
));
5686 tmp
[sizeof(tmp
)-2-1] = '\0';
5687 if(tmp
[(l
= strlen(tmp
)) - 1] != tc
->dir
->delim
){
5688 if(l
< sizeof(tmp
)){
5689 tmp
[l
] = tc
->dir
->delim
;
5690 strncpy(&tmp
[l
+1], "[]", sizeof(tmp
)-(l
+1));
5694 strncat(tmp
, "[]", sizeof(tmp
)-strlen(tmp
)-1);
5696 tmp
[sizeof(tmp
)-1] = '\0';
5698 fake_context
= new_context(tmp
, 0);
5699 newfolder
[0] = '\0';
5700 done
= display_folder_list(&fake_context
, newfolder
,
5701 1, folders_for_goto
);
5702 free_context(&fake_context
);
5705 else if(!(tc
->use
& CNTXT_INCMNG
)){
5706 done
= display_folder_list(&tc
, newfolder
,
5707 1, folders_for_goto
);
5711 else if((exists
& FEX_ERROR
)){
5712 q_status_message1(SM_ORDER
, 0, 3,
5713 _("Problem accessing folder \"%s\""),
5722 if(exists
== FEX_ERROR
)
5723 q_status_message1(SM_ORDER
, 0, 3,
5724 _("Problem accessing folder \"%s\""),
5726 else if(tc
->use
& CNTXT_INCMNG
)
5727 q_status_message1(SM_ORDER
, 0, 3,
5728 _("Can't find Incoming Folder: %s"),
5730 else if(context_isambig(newfolder
))
5731 q_status_message2(SM_ORDER
, 0, 3,
5732 _("Can't find folder \"%s\" in %s"),
5733 newfolder
, (void *) tc
->nickname
);
5735 q_status_message1(SM_ORDER
, 0, 3,
5736 _("Can't find folder \"%s\""),
5741 else if(last_folder
){
5742 if(ps_global
->goto_default_rule
== GOTO_FIRST_CLCTN_DEF_INBOX
5743 && !strucmp(last_folder
, ps_global
->inbox_name
)
5744 && tc
== ((ps_global
->context_list
->use
& CNTXT_INCMNG
)
5745 ? ps_global
->context_list
->next
: ps_global
->context_list
)){
5747 (*notrealinbox
) = 0;
5749 tc
= ps_global
->context_list
;
5752 strncpy(newfolder
, last_folder
, sizeof(newfolder
));
5753 newfolder
[sizeof(newfolder
)-1] = '\0';
5754 save_hist(history
, newfolder
, 0, tc
);
5758 /* fall thru like they cancelled */
5760 case 1 : /* o_e says user cancel */
5761 cmd_cancelled("Open folder");
5764 case 2 : /* o_e says user wants list */
5765 r
= display_folder_list(&tc
, newfolder
, 0, folders_for_goto
);
5771 case 3 : /* o_e says user wants help */
5772 help
= help
== NO_HELP
? h_oe_broach
: NO_HELP
;
5775 case 4 : /* redraw */
5778 case 10 : /* Previous collection */
5779 tc2
= ps_global
->context_list
;
5780 while(tc2
->next
&& tc2
->next
!= tc
)
5786 case 11 : /* Next collection */
5787 tc
= (tc
->next
) ? tc
->next
: ps_global
->context_list
;
5790 case 12 : /* file name completion */
5791 if(!folder_complete(tc
, newfolder
, sizeof(newfolder
), &n
)){
5792 if(n
&& last_rc
== 12 && !(flags
& OE_USER_MODIFIED
)){
5793 r
= display_folder_list(&tc
, newfolder
, 1,folders_for_goto
);
5795 done
++; /* bingo! */
5797 rc
= 0; /* burn last_rc */
5805 case 14 : /* file name completion */
5806 r
= display_folder_list(&tc
, newfolder
, 2, folders_for_goto
);
5808 done
++; /* bingo! */
5810 rc
= 0; /* burn last_rc */
5814 case 17 : /* GoTo INBOX */
5816 strncpy(newfolder
, ps_global
->inbox_name
, sizeof(newfolder
)-1);
5817 newfolder
[sizeof(newfolder
)-1] = '\0';
5819 (*notrealinbox
) = 0;
5821 tc
= ps_global
->context_list
;
5822 save_hist(history
, newfolder
, 0, tc
);
5827 if((p
= get_prev_hist(history
, newfolder
, 0, tc
)) != NULL
){
5828 strncpy(newfolder
, p
, sizeof(newfolder
));
5829 newfolder
[sizeof(newfolder
)-1] = '\0';
5830 if(history
->hist
[history
->curindex
])
5831 tc
= history
->hist
[history
->curindex
]->cntxt
;
5839 if((p
= get_next_hist(history
, newfolder
, 0, tc
)) != NULL
){
5840 strncpy(newfolder
, p
, sizeof(newfolder
));
5841 newfolder
[sizeof(newfolder
)-1] = '\0';
5842 if(history
->hist
[history
->curindex
])
5843 tc
= history
->hist
[history
->curindex
]->cntxt
;
5851 alpine_panic("Unhandled case");
5858 dprint((2, "broach folder, name entered \"%s\"\n",
5859 newfolder
? newfolder
: "?"));
5861 /*-- Just check that we can expand this. It gets done for real later --*/
5862 strncpy(expanded
, newfolder
, sizeof(expanded
));
5863 expanded
[sizeof(expanded
)-1] = '\0';
5865 if(!expand_foldername(expanded
, sizeof(expanded
))) {
5866 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
5867 expanded
? expanded
: "?"));
5876 /*----------------------------------------------------------------------
5877 Check to see if user wants to reopen dead stream.
5882 Result: 1 if the folder was successfully updatedn
5887 ask_mailbox_reopen(struct pine
*ps
, int *reopenp
)
5889 if(((ps
->mail_stream
->dtb
5890 && ((ps
->mail_stream
->dtb
->flags
& DR_NONEWMAIL
)
5891 || (ps
->mail_stream
->rdonly
5892 && ps
->mail_stream
->dtb
->flags
& DR_NONEWMAILRONLY
)))
5893 && (ps
->reopen_rule
== REOPEN_ASK_ASK_Y
5894 || ps
->reopen_rule
== REOPEN_ASK_ASK_N
5895 || ps
->reopen_rule
== REOPEN_ASK_NO_Y
5896 || ps
->reopen_rule
== REOPEN_ASK_NO_N
))
5897 || ((ps
->mail_stream
->dtb
5898 && ps
->mail_stream
->rdonly
5899 && !(ps
->mail_stream
->dtb
->flags
& DR_LOCAL
))
5900 && (ps
->reopen_rule
== REOPEN_YES_ASK_Y
5901 || ps
->reopen_rule
== REOPEN_YES_ASK_N
5902 || ps
->reopen_rule
== REOPEN_ASK_ASK_Y
5903 || ps
->reopen_rule
== REOPEN_ASK_ASK_N
))){
5906 switch(ps
->reopen_rule
){
5907 case REOPEN_YES_ASK_Y
:
5908 case REOPEN_ASK_ASK_Y
:
5909 case REOPEN_ASK_NO_Y
:
5918 switch(want_to("Re-open folder to check for new messages", deefault
,
5919 'x', h_reopen_folder
, WT_NORM
)){
5934 /*----------------------------------------------------------------------
5935 Check to see if user input is in form of old c-client mailbox speck
5940 Result: 1 if the folder was successfully updatedn
5945 update_folder_spec(char *new, size_t newlen
, char *old
)
5951 if(*(p
= old
) == '*') /* old form? */
5954 if(*old
== '{') /* copy host spec */
5956 switch(*new = *old
++){
5961 if(!struncmp(old
, "nntp", 4))
5969 while(*new++ != '}' && (new-orignew
) < newlen
-1);
5971 if((*p
== '*' && *old
) || ((*old
== '*') ? *++old
: 0)){
5973 * OK, some heuristics here. If it looks like a newsgroup
5974 * then we plunk it into the #news namespace else we
5975 * assume that they're trying to get at a #public folder...
5978 *p
&& (isalnum((unsigned char) *p
) || strindex(".-", *p
));
5982 sstrncpy(&new, (*p
&& !nntp
) ? "#public/" : "#news.", newlen
-(new-orignew
));
5983 strncpy(new, old
, newlen
-(new-orignew
));
5987 orignew
[newlen
-1] = '\0';
5993 /*----------------------------------------------------------------------
5994 Open the requested folder in the requested context
5996 Args: state -- usual pine state struct
5997 newfolder -- folder to open
5998 new_context -- folder context might live in
5999 stream -- candidate for recycling
6001 Result: New folder open or not (if error), and we're set to
6002 enter the index screen.
6005 visit_folder(struct pine
*state
, char *newfolder
, CONTEXT_S
*new_context
,
6006 MAILSTREAM
*stream
, long unsigned int flags
)
6008 dprint((9, "visit_folder(%s, %s)\n",
6009 newfolder
? newfolder
: "?",
6010 (new_context
&& new_context
->context
)
6011 ? new_context
->context
: "(NULL)"));
6013 if(ps_global
&& ps_global
->ttyo
){
6014 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
6015 ps_global
->mangled_footer
= 1;
6018 if(do_broach_folder(newfolder
, new_context
, stream
? &stream
: NULL
,
6020 || !sp_flagged(state
->mail_stream
, SP_LOCKED
))
6021 state
->next_screen
= mail_index_screen
;
6023 state
->next_screen
= folder_screen
;
6027 /*----------------------------------------------------------------------
6028 Move read messages from folder if listed in archive
6034 read_msg_prompt(long int n
, char *f
)
6036 char buf
[MAX_SCREEN_COLS
+1];
6038 snprintf(buf
, sizeof(buf
), "Save the %ld read message%s in \"%s\"", n
, plural(n
), f
);
6039 buf
[sizeof(buf
)-1] = '\0';
6040 return(want_to(buf
, 'y', 0, NO_HELP
, WT_NORM
) == 'y');
6044 /*----------------------------------------------------------------------
6045 Print current message[s] or folder index
6047 Args: state -- pointer to struct holding a bunch of pine state
6048 msgmap -- table mapping msg nums to c-client sequence nums
6049 aopt -- aggregate options
6050 in_index -- boolean indicating we're called from Index Screen
6052 Filters the original header and sends stuff to printer
6055 cmd_print(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, CmdWhere in_index
)
6058 long i
, msgs
, rawno
;
6059 int next
= 0, do_index
= 0, rv
= 0;
6064 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
6067 msgs
= mn_total_cur(msgmap
);
6069 if((in_index
!= View
) && F_ON(F_PRINT_INDEX
, state
)){
6072 static ESCKEY_S prt_opts
[] = {
6073 {'i', 'i', "I", N_("Index")},
6074 {'m', 'm', "M", NULL
},
6075 {-1, 0, NULL
, NULL
}};
6077 if(in_index
== ThrdIndx
){
6078 /* TRANSLATORS: This is a question, Print Index ? */
6079 if(want_to(_("Print Index"), 'y', 'x', NO_HELP
, WT_NORM
) == 'y')
6085 snprintf(m
, sizeof(m
), "Message%s", (msgs
>1L) ? "s" : "");
6086 m
[sizeof(m
)-1] = '\0';
6087 prt_opts
[1].label
= m
;
6088 snprintf(prompt
, sizeof(prompt
), "Print %sFolder Index or %s %s? ",
6089 (aopt
& MCMD_AGG_2
) ? "thread " : MCMD_ISAGG(aopt
) ? "selected " : "",
6090 (aopt
& MCMD_AGG_2
) ? "thread" : MCMD_ISAGG(aopt
) ? "selected" : "current", m
);
6091 prompt
[sizeof(prompt
)-1] = '\0';
6093 ans
= radio_buttons(prompt
, -FOOTER_ROWS(state
), prt_opts
, 'm', 'x',
6094 NO_HELP
, RB_NORM
|RB_SEQ_SENSITIVE
);
6099 cmd_cancelled("Print");
6100 if(MCMD_ISAGG(aopt
))
6101 restore_selected(msgmap
);
6116 snprintf(prompt
, sizeof(prompt
), "%sFolder Index",
6117 (aopt
& MCMD_AGG_2
) ? "Thread " : MCMD_ISAGG(aopt
) ? "Selected " : "");
6119 snprintf(prompt
, sizeof(prompt
), "%s messages", long2string(msgs
));
6121 snprintf(prompt
, sizeof(prompt
), "Message %s", long2string(mn_get_cur(msgmap
)));
6123 prompt
[sizeof(prompt
)-1] = '\0';
6125 if(open_printer(prompt
) < 0){
6126 if(MCMD_ISAGG(aopt
))
6127 restore_selected(msgmap
);
6135 tc
= format_titlebar();
6137 /* Print titlebar... */
6138 print_text1("%s\n\n", tc
? tc
->titlebar_line
: "");
6139 /* then all the index members... */
6140 if(!print_index(state
, msgmap
, MCMD_ISAGG(aopt
)))
6141 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6142 _("Error printing folder index"));
6148 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
), next
++){
6149 if(next
&& F_ON(F_AGG_PRINT_FF
, state
))
6150 if(!print_char(FORMFEED
)){
6155 if(!(state
->mail_stream
6156 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
6157 && rawno
<= state
->mail_stream
->nmsgs
6158 && (mc
= mail_elt(state
->mail_stream
, rawno
))
6162 if(!(e
=pine_mail_fetchstructure(state
->mail_stream
,
6165 || (F_ON(F_FROM_DELIM_IN_PRINT
, ps_global
)
6166 && !bezerk_delimiter(e
, mc
, print_char
, next
))
6167 || !format_message(mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
6168 e
, b
, NULL
, FM_NEW_MESS
| FM_NOINDENT
,
6170 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6171 _("Error printing message"));
6180 if(MCMD_ISAGG(aopt
))
6181 restore_selected(msgmap
);
6187 /*----------------------------------------------------------------------
6190 Args: state -- various pine state bits
6191 msgmap -- Message number mapping table
6192 aopt -- option flags
6194 Filters the original header and sends stuff to specified command
6197 cmd_pipe(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
6203 char *resultfilename
= NULL
, prompt
[80], *p
;
6204 int done
= 0, rv
= 0;
6206 int fourlabel
= -1, j
= 0, next
= 0, ku
;
6207 int pipe_rv
; /* rv of proc to separate from close_system_pipe rv */
6209 unsigned flagsforhist
= 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6210 static HISTORY_S
*history
= NULL
;
6211 int capture
= 1, raw
= 0, delimit
= 0, newpipe
= 0;
6212 char pipe_command
[MAXPATH
];
6213 ESCKEY_S pipe_opt
[8];
6215 if(ps_global
->restricted
){
6216 q_status_message(SM_ORDER
| SM_DING
, 0, 4,
6217 "Alpine demo can't pipe messages");
6220 else if(!any_messages(msgmap
, NULL
, "to Pipe"))
6223 pipe_command
[0] = '\0';
6224 init_hist(&history
, HISTSIZE
);
6225 flagsforhist
= (raw
? 0x8 : 0) +
6226 (delimit
? 0x4 : 0) +
6227 (newpipe
? 0x2 : 0) +
6228 (capture
? 0x1 : 0);
6229 if((p
= get_prev_hist(history
, "", flagsforhist
, NULL
)) != NULL
){
6230 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6231 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6232 if(history
->hist
[history
->curindex
]){
6233 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6234 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6235 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6236 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6237 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6242 pipe_opt
[j
].rval
= 0;
6243 pipe_opt
[j
].name
= "";
6244 pipe_opt
[j
++].label
= "";
6246 pipe_opt
[j
].ch
= ctrl('W');
6247 pipe_opt
[j
].rval
= 10;
6248 pipe_opt
[j
].name
= "^W";
6249 pipe_opt
[j
++].label
= NULL
;
6251 pipe_opt
[j
].ch
= ctrl('Y');
6252 pipe_opt
[j
].rval
= 11;
6253 pipe_opt
[j
].name
= "^Y";
6254 pipe_opt
[j
++].label
= NULL
;
6256 pipe_opt
[j
].ch
= ctrl('R');
6257 pipe_opt
[j
].rval
= 12;
6258 pipe_opt
[j
].name
= "^R";
6259 pipe_opt
[j
++].label
= NULL
;
6261 if(MCMD_ISAGG(aopt
)){
6262 if(!pseudo_selected(state
->mail_stream
, msgmap
))
6266 pipe_opt
[j
].ch
= ctrl('T');
6267 pipe_opt
[j
].rval
= 13;
6268 pipe_opt
[j
].name
= "^T";
6269 pipe_opt
[j
++].label
= NULL
;
6273 pipe_opt
[j
].ch
= KEY_UP
;
6274 pipe_opt
[j
].rval
= 30;
6275 pipe_opt
[j
].name
= "";
6277 pipe_opt
[j
++].label
= "";
6279 pipe_opt
[j
].ch
= KEY_DOWN
;
6280 pipe_opt
[j
].rval
= 31;
6281 pipe_opt
[j
].name
= "";
6282 pipe_opt
[j
++].label
= "";
6284 pipe_opt
[j
].ch
= -1;
6289 snprintf(prompt
, sizeof(prompt
), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6291 MCMD_ISAGG(aopt
) ? "s" : " ",
6292 MCMD_ISAGG(aopt
) ? "" : comatose(mn_get_cur(msgmap
)),
6293 (!capture
|| delimit
|| (newpipe
&& MCMD_ISAGG(aopt
))) ? "(" : "",
6294 capture
? "" : "uncaptured",
6295 (!capture
&& delimit
) ? "," : "",
6296 delimit
? "delimited" : "",
6297 ((!capture
|| delimit
) && newpipe
&& MCMD_ISAGG(aopt
)) ? "," : "",
6298 (newpipe
&& MCMD_ISAGG(aopt
)) ? "new pipe" : "",
6299 (!capture
|| delimit
|| (newpipe
&& MCMD_ISAGG(aopt
))) ? ") " : "");
6300 prompt
[sizeof(prompt
)-1] = '\0';
6301 pipe_opt
[1].label
= raw
? N_("Shown Text") : N_("Raw Text");
6302 pipe_opt
[2].label
= capture
? N_("Free Output") : N_("Capture Output");
6303 pipe_opt
[3].label
= delimit
? N_("No Delimiter") : N_("With Delimiter");
6305 pipe_opt
[fourlabel
].label
= newpipe
? N_("To Same Pipe") : N_("To Individual Pipes");
6309 * 2 is really 1 because there will be one real entry and
6310 * one entry of "" because of the get_prev_hist above.
6312 if(items_in_hist(history
) > 2){
6313 pipe_opt
[ku
].name
= HISTORY_UP_KEYNAME
;
6314 pipe_opt
[ku
].label
= HISTORY_KEYLABEL
;
6315 pipe_opt
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
6316 pipe_opt
[ku
+1].label
= HISTORY_KEYLABEL
;
6319 pipe_opt
[ku
].name
= "";
6320 pipe_opt
[ku
].label
= "";
6321 pipe_opt
[ku
+1].name
= "";
6322 pipe_opt
[ku
+1].label
= "";
6325 flags
= OE_APPEND_CURRENT
| OE_SEQ_SENSITIVE
;
6326 switch(optionally_enter(pipe_command
, -FOOTER_ROWS(state
), 0,
6327 sizeof(pipe_command
), prompt
,
6328 pipe_opt
, NO_HELP
, &flags
)){
6330 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
6331 _("Internal problem encountered"));
6335 case 10 : /* flip raw bit */
6339 case 11 : /* flip capture bit */
6343 case 12 : /* flip delimit bit */
6347 case 13 : /* flip newpipe bit */
6352 flagsforhist
= (raw
? 0x8 : 0) +
6353 (delimit
? 0x4 : 0) +
6354 (newpipe
? 0x2 : 0) +
6355 (capture
? 0x1 : 0);
6356 if((p
= get_prev_hist(history
, pipe_command
, flagsforhist
, NULL
)) != NULL
){
6357 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6358 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6359 if(history
->hist
[history
->curindex
]){
6360 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6361 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6362 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6363 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6364 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6373 flagsforhist
= (raw
? 0x8 : 0) +
6374 (delimit
? 0x4 : 0) +
6375 (newpipe
? 0x2 : 0) +
6376 (capture
? 0x1 : 0);
6377 if((p
= get_next_hist(history
, pipe_command
, flagsforhist
, NULL
)) != NULL
){
6378 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6379 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6380 if(history
->hist
[history
->curindex
]){
6381 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6382 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6383 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6384 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6385 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6394 if(pipe_command
[0]){
6396 flagsforhist
= (raw
? 0x8 : 0) +
6397 (delimit
? 0x4 : 0) +
6398 (newpipe
? 0x2 : 0) +
6399 (capture
? 0x1 : 0);
6400 save_hist(history
, pipe_command
, flagsforhist
, NULL
);
6402 flags
= PIPE_USER
| PIPE_WRITE
| PIPE_STDERR
;
6403 flags
|= (raw
? PIPE_RAW
: 0);
6409 ps_global
->mangled_screen
= 1;
6410 ps_global
->in_init_seq
= 1;
6412 flags
|= PIPE_RESET
;
6415 if(!newpipe
&& !(syspipe
= cmd_pipe_open(pipe_command
,
6416 (flags
& PIPE_RESET
)
6422 for(i
= mn_first_cur(msgmap
);
6424 i
= mn_next_cur(msgmap
)){
6425 e
= pine_mail_fetchstructure(ps_global
->mail_stream
,
6426 mn_m2raw(msgmap
, i
), &b
);
6427 if(!(state
->mail_stream
6428 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
6429 && rawno
<= state
->mail_stream
->nmsgs
6430 && (mc
= mail_elt(state
->mail_stream
, rawno
))
6435 && !(syspipe
= cmd_pipe_open(pipe_command
,
6436 (flags
& PIPE_RESET
)
6440 || (delimit
&& !bezerk_delimiter(e
, mc
, pc
, next
++)))
6447 prime_raw_pipe_getc(ps_global
->mail_stream
,
6448 mn_m2raw(msgmap
, i
), -1L, 0L);
6450 gf_link_filter(gf_nvtnl_local
, NULL
);
6451 if((pipe_err
= gf_pipe(raw_pipe_getc
, pc
)) != NULL
){
6452 q_status_message1(SM_ORDER
|SM_DING
,
6454 _("Internal Error: %s"),
6459 else if(!format_message(mn_m2raw(msgmap
, i
), e
, b
,
6460 NULL
, FM_NEW_MESS
| FM_NOWRAP
, pc
))
6465 if(close_system_pipe(&syspipe
, &pipe_rv
, pipe_callback
) == -1)
6470 ps_global
->in_init_seq
= 0;
6473 if(close_system_pipe(&syspipe
, &pipe_rv
, pipe_callback
) == -1)
6475 if(done
) /* say we had a problem */
6476 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6477 _("Error piping message"));
6478 else if(resultfilename
){
6480 /* only display if no error */
6481 display_output_file(resultfilename
, "PIPE MESSAGE",
6483 fs_give((void **)&resultfilename
);
6487 q_status_message(SM_ORDER
, 0, 2, _("Pipe command completed"));
6493 /* else fall thru as if cancelled */
6496 cmd_cancelled("Pipe command");
6501 helper(h_common_pipe
, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE
);
6502 ps_global
->mangled_screen
= 1;
6505 case 2 : /* no place to escape to */
6506 case 4 : /* can't suspend */
6512 ps_global
->mangled_footer
= 1;
6513 if(MCMD_ISAGG(aopt
))
6514 restore_selected(msgmap
);
6520 /*----------------------------------------------------------------------
6521 Screen to offer list management commands contained in message
6523 Args: state -- pointer to struct holding a bunch of pine state
6524 msgmap -- table mapping msg nums to c-client sequence nums
6525 aopt -- aggregate options
6529 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6532 rfc2369_display(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
)
6535 char *h
, *hdrs
[MLCMD_COUNT
+ 1];
6536 long index_no
= mn_raw2m(msgmap
, msgno
);
6537 RFC2369_S data
[MLCMD_COUNT
];
6539 /* for each header field */
6540 if((h
= pine_fetchheader_lines(stream
, msgno
, NULL
, rfc2369_hdrs(hdrs
))) != NULL
){
6541 memset(&data
[0], 0, sizeof(RFC2369_S
) * MLCMD_COUNT
);
6542 if(rfc2369_parse_fields(h
, &data
[0])){
6545 if((explain
= list_mgmt_text(data
, index_no
)) != NULL
){
6546 list_mgmt_screen(explain
);
6547 ps_global
->mangled_screen
= 1;
6553 fs_give((void **) &h
);
6557 q_status_message1(SM_ORDER
, 0, 3,
6558 "Message %s contains no list management information",
6559 comatose(index_no
));
6564 list_mgmt_text(RFC2369_S
*data
, long int msgno
)
6567 int i
, j
, n
, fields
= 0;
6568 static char *rfc2369_intro1
=
6569 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6570 static char *rfc2369_intro2
[] = {
6571 N_(" has information associated with it "),
6572 N_("that explains how to participate in an email list. An "),
6573 N_("email list is represented by a single email address that "),
6574 N_("users sharing a common interest can send messages to (known "),
6575 N_("as posting) which are then redistributed to all members "),
6576 N_("of the list (sometimes after review by a moderator)."),
6577 N_("<P>List participation commands in this message include:"),
6581 if((store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) != NULL
){
6583 /* Insert introductory text */
6584 so_puts(store
, rfc2369_intro1
);
6586 so_puts(store
, comatose(msgno
));
6588 for(i
= 0; rfc2369_intro2
[i
]; i
++)
6589 so_puts(store
, _(rfc2369_intro2
[i
]));
6591 so_puts(store
, "<P>");
6592 for(i
= 0; i
< MLCMD_COUNT
; i
++)
6593 if(data
[i
].data
[0].value
6594 || data
[i
].data
[0].comment
6595 || data
[i
].data
[0].error
){
6597 so_puts(store
, "<UL>");
6599 so_puts(store
, "<LI>");
6601 (n
= (data
[i
].data
[1].value
|| data
[i
].data
[1].comment
))
6605 so_puts(store
, data
[i
].field
.description
);
6606 so_puts(store
, ". ");
6609 so_puts(store
, "<OL>");
6613 && (data
[i
].data
[j
].comment
6614 || data
[i
].data
[j
].value
6615 || data
[i
].data
[j
].error
);
6618 so_puts(store
, n
? "<P><LI>" : "<P>");
6620 if(data
[i
].data
[j
].comment
){
6622 _("With the provided comment:<P><BLOCKQUOTE>"));
6623 so_puts(store
, data
[i
].data
[j
].comment
);
6624 so_puts(store
, "</BLOCKQUOTE><P>");
6627 if(data
[i
].data
[j
].value
){
6629 && !strucmp(data
[i
].data
[j
].value
, "NO")){
6631 _("Posting is <EM>not</EM> allowed on this list"));
6634 so_puts(store
, "Select <A HREF=\"");
6635 so_puts(store
, data
[i
].data
[j
].value
);
6636 so_puts(store
, "\">HERE</A> to ");
6637 so_puts(store
, (data
[i
].field
.action
)
6638 ? data
[i
].field
.action
6642 so_puts(store
, ".");
6645 if(data
[i
].data
[j
].error
){
6646 so_puts(store
, "<P>Unfortunately, Alpine can not offer");
6647 so_puts(store
, " to take direct action based upon it");
6648 so_puts(store
, " because it was improperly formatted.");
6649 so_puts(store
, " The unrecognized data associated with");
6650 so_puts(store
, " the \"");
6651 so_puts(store
, data
[i
].field
.name
);
6652 so_puts(store
, "\" header field was:<P><BLOCKQUOTE>");
6653 so_puts(store
, data
[i
].data
[j
].error
);
6654 so_puts(store
, "</BLOCKQUOTE>");
6657 so_puts(store
, "<P>");
6661 so_puts(store
, "</OL>");
6665 so_puts(store
, "</UL>");
6667 so_puts(store
, "</BODY></HTML>");
6675 list_mgmt_screen(STORE_S
*html
)
6681 HANDLE_S
*handles
= NULL
;
6685 so_seek(html
, 0L, 0);
6686 gf_set_so_readc(&gc
, html
);
6688 init_handles(&handles
);
6690 if((store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) != NULL
){
6691 gf_set_so_writec(&pc
, store
);
6694 gf_link_filter(gf_html2plain
,
6695 gf_html2plain_opt(NULL
, ps_global
->ttyo
->screen_cols
,
6696 non_messageview_margin(), &handles
, NULL
, 0));
6698 error
= gf_pipe(gc
, pc
);
6700 gf_clear_so_writec(store
);
6705 memset(&sargs
, 0, sizeof(SCROLL_S
));
6706 sargs
.text
.text
= so_text(store
);
6707 sargs
.text
.src
= CharStar
;
6708 sargs
.text
.desc
= "list commands";
6709 sargs
.text
.handles
= handles
;
6711 sargs
.start
.on
= Offset
;
6712 sargs
.start
.loc
.offset
= offset
;
6715 sargs
.bar
.title
= _("MAIL LIST COMMANDS");
6716 sargs
.bar
.style
= MessageNumber
;
6717 sargs
.resize_exit
= 1;
6718 sargs
.help
.text
= h_special_list_commands
;
6719 sargs
.help
.title
= _("HELP FOR LIST COMMANDS");
6720 sargs
.keys
.menu
= &listmgr_keymenu
;
6721 setbitmap(sargs
.keys
.bitmap
);
6723 clrbitn(LM_TRY_KEY
, sargs
.keys
.bitmap
);
6724 clrbitn(LM_PREV_KEY
, sargs
.keys
.bitmap
);
6725 clrbitn(LM_NEXT_KEY
, sargs
.keys
.bitmap
);
6728 cmd
= scrolltool(&sargs
);
6729 offset
= sargs
.start
.loc
.offset
;
6735 free_handles(&handles
);
6736 gf_clear_so_readc(html
);
6738 while(cmd
== MC_RESIZE
);
6742 /*----------------------------------------------------------------------
6743 Prompt the user for the type of select desired
6745 NOTE: any and all functions that successfully exit the second
6746 switch() statement below (currently "select_*() functions"),
6747 *MUST* update the folder's MESSAGECACHE element's "searched"
6748 bits to reflect the search result. Functions using
6749 mail_search() get this for free, the others must update 'em
6752 Returns -1 if canceled without changing selection
6753 0 if selection may have changed
6756 aggregate_select(struct pine
*state
, MSGNO_S
*msgmap
, int q_line
, CmdWhere in_index
)
6758 long i
, diff
, old_tot
, msgno
, raw
;
6759 int q
= 0, rv
= 0, narrow
= 0, hidden
, ret
= -1;
6762 SEARCHSET
*limitsrch
= NULL
;
6764 extern MAILSTREAM
*mm_search_stream
;
6765 extern long mm_search_count
;
6767 hidden
= any_lflagged(msgmap
, MN_HIDE
) > 0L;
6768 mm_search_stream
= state
->mail_stream
;
6769 mm_search_count
= 0L;
6771 sel_opts
= THRD_INDX() ? sel_opts4
: sel_opts2
;
6773 sel_opts
[SEL_OPTS_THREAD
].ch
= SEL_OPTS_THREAD_CH
;
6776 sel_opts
[SEL_OPTS_THREAD
].ch
= -1;
6779 if((old_tot
= any_lflagged(msgmap
, MN_SLCT
)) != 0){
6782 thrd
= fetch_thread(state
->mail_stream
,
6783 mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
6784 /* check if whole thread is selected or not */
6786 count_lflags_in_thread(state
->mail_stream
,thrd
,msgmap
,MN_SLCT
)
6788 count_lflags_in_thread(state
->mail_stream
,thrd
,msgmap
,MN_NONE
))
6791 sel_opts1
[1].label
= i
? N_("unselect Curthrd") : N_("select Curthrd");
6794 i
= get_lflag(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
6796 sel_opts1
[1].label
= i
? N_("unselect Cur") : N_("select Cur");
6799 sel_opts
+= 2; /* disable extra options */
6800 switch(q
= radio_buttons(_(sel_pmt1
), q_line
, sel_opts1
, 'c', 'x', NO_HELP
,
6802 case 'f' : /* flip selection */
6804 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++){
6806 q
= !get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
);
6807 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, q
);
6809 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, !q
);
6811 mn_reset_cur(msgmap
, msgno
= i
);
6817 case 'n' : /* narrow selection */
6819 case 'b' : /* broaden selection */
6820 q
= 0; /* offer criteria prompt */
6823 case 'c' : /* Un/Select Current */
6824 case 'a' : /* Unselect All */
6825 case 'x' : /* cancel */
6829 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6830 "Unsupported Select option");
6837 q
= radio_buttons(sel_pmt2
, q_line
, sel_opts
, 'c', 'x',
6838 NO_HELP
, RB_NORM
|RB_RET_HELP
);
6841 helper(h_index_cmd_select
, _("HELP FOR SELECT"), HLPD_SIMPLE
);
6842 ps_global
->mangled_screen
= 1;
6850 * The purpose of this is to add the appropriate searchset to the
6851 * search so that the search can be limited to only looking at what
6852 * it needs to look at. That is, if we are narrowing then we only need
6853 * to look at messages which are already selected, and if we are
6854 * broadening, then we only need to look at messages which are not
6855 * yet selected. This routine will work whether or not
6856 * limiting_searchset properly limits the search set. In particular,
6857 * the searchset returned by limiting_searchset may include messages
6858 * which really shouldn't be included. We do that because a too-large
6859 * searchset will break some IMAP servers. It is even possible that it
6860 * becomes inefficient to send the whole set. If the select function
6861 * frees limitsrch, it should be sure to set it to NULL so we won't
6862 * try freeing it again here.
6864 limitsrch
= limiting_searchset(state
->mail_stream
, narrow
);
6867 * NOTE: See note about MESSAGECACHE "searched" bits above!
6870 case 'x': /* cancel */
6871 cmd_cancelled("Select command");
6874 case 'c' : /* select/unselect current */
6875 (void) select_by_current(state
, msgmap
, in_index
);
6879 case 'a' : /* select/unselect all */
6880 msgno
= any_lflagged(msgmap
, MN_SLCT
);
6881 diff
= (!msgno
) ? mn_get_total(msgmap
) : 0L;
6883 agg_select_all(state
->mail_stream
, msgmap
, &diff
,
6884 any_lflagged(msgmap
, MN_SLCT
) <= 0L);
6885 q_status_message4(SM_ORDER
,0,2,
6886 "%s%s message%s %sselected",
6887 msgno
? "" : "All ", comatose(diff
),
6888 plural(diff
), msgno
? "UN" : "");
6891 case 'n' : /* Select by Number */
6894 rv
= select_by_thrd_number(state
->mail_stream
, msgmap
, &limitsrch
);
6896 rv
= select_by_number(state
->mail_stream
, msgmap
, &limitsrch
);
6900 case 'd' : /* Select by Date */
6902 rv
= select_by_date(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
6906 case 't' : /* Text */
6908 rv
= select_by_text(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
6912 case 'z' : /* Size */
6914 rv
= select_by_size(state
->mail_stream
, &limitsrch
);
6917 case 's' : /* Status */
6919 rv
= select_by_status(state
->mail_stream
, &limitsrch
);
6922 case 'k' : /* Keyword */
6924 rv
= select_by_keyword(state
->mail_stream
, &limitsrch
);
6927 case 'r' : /* Rule */
6929 rv
= select_by_rule(state
->mail_stream
, &limitsrch
);
6932 case 'h' : /* Thread */
6934 rv
= select_by_thread(state
->mail_stream
, msgmap
, &limitsrch
);
6938 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6939 "Unsupported Select option");
6944 mail_free_searchset(&limitsrch
);
6946 if(rv
) /* bad return value.. */
6947 return(ret
); /* error already displayed */
6949 if(narrow
) /* make sure something was selected */
6950 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++)
6951 if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
6952 && raw
<= state
->mail_stream
->nmsgs
6953 && (mc
= mail_elt(state
->mail_stream
, raw
)) && mc
->searched
){
6954 if(get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
))
6961 if(mm_search_count
){
6963 * loop thru all the messages, adjusting local flag bits
6964 * based on their "searched" bit...
6966 for(i
= 1L, msgno
= 0L; i
<= mn_get_total(msgmap
); i
++)
6968 /* turning OFF selectedness if the "searched" bit isn't lit. */
6969 if(get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
)){
6970 if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
6971 && raw
<= state
->mail_stream
->nmsgs
6972 && (mc
= mail_elt(state
->mail_stream
, raw
))
6975 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, 0);
6977 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, 1);
6979 /* adjust current message in case we unselect and hide it */
6980 else if(msgno
< mn_get_cur(msgmap
)
6982 || !get_lflag(state
->mail_stream
, msgmap
,
6987 else if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
6988 && raw
<= state
->mail_stream
->nmsgs
6989 && (mc
= mail_elt(state
->mail_stream
, raw
)) && mc
->searched
){
6990 /* turn ON selectedness if "searched" bit is lit. */
6991 if(!get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
)){
6993 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, 1);
6995 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, 0);
6999 /* if we're zoomed and the current message was unselected */
7001 && get_lflag(state
->mail_stream
,msgmap
,mn_get_cur(msgmap
),MN_HIDE
))
7002 mn_reset_cur(msgmap
, msgno
);
7007 q_status_message4(SM_ORDER
, 3, 3,
7008 "%s. %s message%s remain%s selected.",
7010 ? "No change resulted"
7011 : "No messages in intersection",
7012 comatose(old_tot
), plural(old_tot
),
7013 (old_tot
== 1L) ? "s" : "");
7015 q_status_message(SM_ORDER
, 3, 3,
7016 _("No change resulted. Matching messages already selected."));
7018 q_status_message1(SM_ORDER
| SM_DING
, 3, 3,
7019 _("Select failed. No %smessages selected."),
7020 old_tot
? _("additional ") : "");
7023 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
7024 "Select matched %ld message%s. %s %smessage%s %sselected.",
7025 (diff
> 0) ? diff
: old_tot
+ diff
,
7026 plural((diff
> 0) ? diff
: old_tot
+ diff
),
7027 comatose((diff
> 0) ? any_lflagged(msgmap
, MN_SLCT
) : -diff
),
7028 (diff
> 0) ? "total " : "",
7029 plural((diff
> 0) ? any_lflagged(msgmap
, MN_SLCT
) : -diff
),
7030 (diff
> 0) ? "" : "UN");
7031 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
7032 q_status_message(SM_ORDER
, 3, 3, tmp_20k_buf
);
7035 q_status_message2(SM_ORDER
, 3, 3, _("Select matched %s message%s!"),
7036 comatose(diff
), plural(diff
));
7042 /*----------------------------------------------------------------------
7043 Toggle the state of the current message
7045 Args: state -- pointer pine's state variables
7046 msgmap -- message collection to operate on
7047 in_index -- in the message index view
7048 Returns: TRUE if current marked selected, FALSE otw
7051 select_by_current(struct pine
*state
, MSGNO_S
*msgmap
, CmdWhere in_index
)
7054 int all_selected
= 0;
7055 unsigned long was
, tot
, rawno
;
7058 cur
= mn_get_cur(msgmap
);
7061 thrd
= fetch_thread(state
->mail_stream
, mn_m2raw(msgmap
, cur
));
7065 was
= count_lflags_in_thread(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
);
7066 tot
= count_lflags_in_thread(state
->mail_stream
, thrd
, msgmap
, MN_NONE
);
7071 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
, 0);
7072 if(any_lflagged(msgmap
, MN_HIDE
) > 0L){
7073 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_HIDE
, 1);
7075 * See if there's anything left to zoom on. If so,
7076 * pick an adjacent one for highlighting, else make
7077 * sure nothing is left hidden...
7079 if(any_lflagged(msgmap
, MN_SLCT
)){
7080 mn_inc_cur(state
->mail_stream
, msgmap
,
7081 (in_index
== View
&& THREADING()
7082 && sp_viewing_a_thread(state
->mail_stream
))
7084 : (in_index
== View
)
7085 ? MH_ANYTHD
: MH_NONE
);
7086 if(mn_get_cur(msgmap
) == cur
)
7087 mn_dec_cur(state
->mail_stream
, msgmap
,
7088 (in_index
== View
&& THREADING()
7089 && sp_viewing_a_thread(state
->mail_stream
))
7091 : (in_index
== View
)
7092 ? MH_ANYTHD
: MH_NONE
);
7094 else /* clear all hidden flags */
7095 (void) unzoom_index(state
, state
->mail_stream
, msgmap
);
7099 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
, 1);
7101 q_status_message3(SM_ORDER
, 0, 2, "%s message%s %sselected",
7102 comatose(all_selected
? was
: tot
-was
),
7103 plural(all_selected
? was
: tot
-was
),
7104 all_selected
? "UN" : "");
7106 /* collapsed thread */
7108 && ((rawno
= mn_m2raw(msgmap
, cur
)) != 0L)
7109 && ((thrd
= fetch_thread(state
->mail_stream
, rawno
)) != NULL
)
7110 && (thrd
&& thrd
->next
&& get_lflag(state
->mail_stream
, NULL
, rawno
, MN_COLL
))){
7112 * This doesn't work quite the same as the colon command works, but
7113 * it is arguably doing the correct thing. The difference is
7114 * that aggregate_select will zoom after selecting back where it
7115 * was called from, but selecting a thread with colon won't zoom.
7116 * Maybe it makes sense to zoom after a select but not after a colon
7117 * command even though they are very similar.
7119 thread_command(state
, state
->mail_stream
, msgmap
, ':', -FOOTER_ROWS(state
));
7123 get_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
)) != 0){ /* set? */
7124 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
, 0);
7125 if(any_lflagged(msgmap
, MN_HIDE
) > 0L){
7126 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_HIDE
, 1);
7128 * See if there's anything left to zoom on. If so,
7129 * pick an adjacent one for highlighting, else make
7130 * sure nothing is left hidden...
7132 if(any_lflagged(msgmap
, MN_SLCT
)){
7133 mn_inc_cur(state
->mail_stream
, msgmap
,
7134 (in_index
== View
&& THREADING()
7135 && sp_viewing_a_thread(state
->mail_stream
))
7137 : (in_index
== View
)
7138 ? MH_ANYTHD
: MH_NONE
);
7139 if(mn_get_cur(msgmap
) == cur
)
7140 mn_dec_cur(state
->mail_stream
, msgmap
,
7141 (in_index
== View
&& THREADING()
7142 && sp_viewing_a_thread(state
->mail_stream
))
7144 : (in_index
== View
)
7145 ? MH_ANYTHD
: MH_NONE
);
7147 else /* clear all hidden flags */
7148 (void) unzoom_index(state
, state
->mail_stream
, msgmap
);
7152 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
, 1);
7154 q_status_message2(SM_ORDER
, 0, 2, "Message %s %sselected",
7155 long2string(cur
), all_selected
? "UN" : "");
7159 return(!all_selected
);
7163 /*----------------------------------------------------------------------
7164 Prompt the user for the command to perform on selected messages
7166 Args: state -- pointer pine's state variables
7167 msgmap -- message collection to operate on
7168 q_line -- line on display to write prompts
7169 Returns: 1 if the selected messages are suitably commanded,
7170 0 if the choice to pick the command was declined
7174 apply_command(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
,
7175 UCS preloadkeystroke
, int flags
, int q_line
)
7177 int i
= 8, /* number of static entries in sel_opts3 */
7181 agg
= (flags
& AC_FROM_THREAD
) ? MCMD_AGG_2
: MCMD_AGG
;
7185 * To do this "right", we really ought to have access to the keymenu
7186 * here and change the typed command into a real command by running
7187 * it through menu_command. Then the switch below would be against
7188 * results from menu_command. If we did that we'd also pass the
7189 * results of menu_command in as preloadkeystroke instead of passing
7190 * the keystroke itself. But we don't have the keymenu handy,
7191 * so we have to fake it. The only complication that we run into
7192 * is that KEY_DEL is an escape sequence so we change a typed
7193 * KEY_DEL esc seq into the letter D.
7196 if(!preloadkeystroke
){
7197 if(F_ON(F_ENABLE_FLAG
,state
)){ /* flag? */
7198 sel_opts3
[i
].ch
= '*';
7199 sel_opts3
[i
].rval
= '*';
7200 sel_opts3
[i
].name
= "*";
7201 sel_opts3
[i
++].label
= N_("Flag");
7204 if(F_ON(F_ENABLE_PIPE
,state
)){ /* pipe? */
7205 sel_opts3
[i
].ch
= '|';
7206 sel_opts3
[i
].rval
= '|';
7207 sel_opts3
[i
].name
= "|";
7208 sel_opts3
[i
++].label
= N_("Pipe");
7211 if(F_ON(F_ENABLE_BOUNCE
,state
)){ /* bounce? */
7212 sel_opts3
[i
].ch
= 'b';
7213 sel_opts3
[i
].rval
= 'b';
7214 sel_opts3
[i
].name
= "B";
7215 sel_opts3
[i
++].label
= N_("Bounce");
7218 if(flags
& AC_FROM_THREAD
){
7219 if(flags
& (AC_COLL
| AC_EXPN
)){
7220 sel_opts3
[i
].ch
= '/';
7221 sel_opts3
[i
].rval
= '/';
7222 sel_opts3
[i
].name
= "/";
7223 sel_opts3
[i
++].label
= (flags
& AC_COLL
) ? N_("Collapse")
7227 sel_opts3
[i
].ch
= ';';
7228 sel_opts3
[i
].rval
= ';';
7229 sel_opts3
[i
].name
= ";";
7230 if(flags
& AC_UNSEL
)
7231 sel_opts3
[i
++].label
= N_("UnSelect");
7233 sel_opts3
[i
++].label
= N_("Select");
7236 if(F_ON(F_ENABLE_PRYNT
, state
)){ /* this one is invisible */
7237 sel_opts3
[i
].ch
= 'y';
7238 sel_opts3
[i
].rval
= '%';
7239 sel_opts3
[i
].name
= "";
7240 sel_opts3
[i
++].label
= "";
7243 if(!is_imap_stream(stream
) || LEVELUIDPLUS(stream
)){ /* expunge selected messages */
7244 sel_opts3
[i
].ch
= 'x';
7245 sel_opts3
[i
].rval
= 'x';
7246 sel_opts3
[i
].name
= "X";
7247 sel_opts3
[i
++].label
= N_("Expunge");
7250 sel_opts3
[i
].ch
= KEY_DEL
; /* also invisible */
7251 sel_opts3
[i
].rval
= 'd';
7252 sel_opts3
[i
].name
= "";
7253 sel_opts3
[i
++].label
= "";
7255 sel_opts3
[i
].ch
= -1;
7257 snprintf(prompt
, sizeof(prompt
), "%s command : ",
7258 (flags
& AC_FROM_THREAD
) ? "THREAD" : "APPLY");
7259 prompt
[sizeof(prompt
)-1] = '\0';
7260 cmd
= double_radio_buttons(prompt
, q_line
, sel_opts3
, 'z', 'c', NO_HELP
,
7266 if(preloadkeystroke
== KEY_DEL
)
7269 if(preloadkeystroke
< 0x80 && isupper((int) preloadkeystroke
))
7270 cmd
= tolower((int) preloadkeystroke
);
7272 cmd
= (int) preloadkeystroke
; /* shouldn't happen */
7277 case 'd' : /* delete */
7278 we_cancel
= busy_cue(NULL
, NULL
, 1);
7279 rv
= cmd_delete(state
, msgmap
, agg
, NULL
); /* don't advance or offer "TAB" */
7284 case 'u' : /* undelete */
7285 we_cancel
= busy_cue(NULL
, NULL
, 1);
7286 rv
= cmd_undelete(state
, msgmap
, agg
);
7291 case 'r' : /* reply */
7292 rv
= cmd_reply(state
, msgmap
, agg
);
7295 case 'f' : /* Forward */
7296 rv
= cmd_forward(state
, msgmap
, agg
);
7299 case '%' : /* print */
7300 rv
= cmd_print(state
, msgmap
, agg
, MsgIndx
);
7303 case 't' : /* take address */
7304 rv
= cmd_take_addr(state
, msgmap
, agg
);
7307 case 's' : /* save */
7308 rv
= cmd_save(state
, stream
, msgmap
, agg
, MsgIndx
);
7311 case 'e' : /* export */
7312 rv
= cmd_export(state
, msgmap
, q_line
, agg
);
7315 case '|' : /* pipe */
7316 rv
= cmd_pipe(state
, msgmap
, agg
);
7319 case '*' : /* flag */
7320 we_cancel
= busy_cue(NULL
, NULL
, 1);
7321 rv
= cmd_flag(state
, msgmap
, agg
);
7326 case 'b' : /* bounce */
7327 rv
= cmd_bounce(state
, msgmap
, agg
);
7331 collapse_or_expand(state
, stream
, msgmap
,
7332 F_ON(F_SLASH_COLL_ENTIRE
, ps_global
)
7334 : mn_get_cur(msgmap
));
7338 select_thread_stmp(state
, stream
, msgmap
);
7341 case 'x' : /* Expunge */
7342 rv
= cmd_expunge(state
, stream
, msgmap
, agg
);
7345 case 'c' : /* cancel */
7346 cmd_cancelled((flags
& AC_FROM_THREAD
) ? "Thread command"
7350 case 'z' : /* default */
7351 q_status_message(SM_INFO
, 0, 2,
7352 "Cancelled, there is no default command");
7364 * Select by message number ranges.
7365 * Sets searched bits in mail_elts
7367 * Args limitsrch -- limit search to this searchset
7369 * Returns 0 on success.
7372 select_by_number(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**limitsrch
)
7376 char number1
[16], number2
[16], numbers
[80], *p
, *t
;
7381 ps_global
->mangled_footer
= 1;
7384 int flags
= OE_APPEND_CURRENT
;
7386 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
7387 sizeof(numbers
), _(select_num
), NULL
, help
, &flags
);
7392 help
= (help
== NO_HELP
) ? h_select_by_num
: NO_HELP
;
7396 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
7397 if(!isspace((unsigned char)*p
))
7402 if(r
== 1 || numbers
[0] == '\0'){
7403 cmd_cancelled("Selection by number");
7410 for(n1
= 1; n1
<= stream
->nmsgs
; n1
++)
7411 if((mc
= mail_elt(stream
, n1
)) != NULL
)
7412 mc
->searched
= 0; /* clear searched bits */
7414 for(p
= numbers
; *p
; p
++){
7416 while(*p
&& isdigit((unsigned char)*p
))
7422 if(number1
[0] == '\0'){
7424 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7425 _("Invalid number range, missing number before \"-\": %s"),
7429 else if(!strucmp("end", p
)){
7434 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7435 _("Invalid message number: %s"), numbers
);
7441 n1
= mn_get_total(msgmap
);
7442 else if((n1
= atol(number1
)) < 1L || n1
> mn_get_total(msgmap
)){
7443 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7444 _("\"%s\" out of message number range"),
7451 while(*++p
&& isdigit((unsigned char)*p
))
7457 if(number2
[0] == '\0'){
7458 if(!strucmp("end", p
)){
7463 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7464 _("Invalid number range, missing number after \"-\": %s"),
7471 n2
= mn_get_total(msgmap
);
7472 else if((n2
= atol(number2
)) < 1L || n2
> mn_get_total(msgmap
)){
7473 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7474 _("\"%s\" out of message number range"),
7482 strncpy(t
, long2string(n1
), sizeof(t
));
7483 t
[sizeof(t
)-1] = '\0';
7484 q_status_message2(SM_ORDER
| SM_DING
, 0, 2,
7485 _("Invalid reverse message number range: %s-%s"),
7486 t
, long2string(n2
));
7490 for(;n1
<= n2
; n1
++){
7491 raw
= mn_m2raw(msgmap
, n1
);
7493 && (!(limitsrch
&& *limitsrch
)
7494 || in_searchset(*limitsrch
, (unsigned long) raw
)))
7495 mm_searched(stream
, raw
);
7499 raw
= mn_m2raw(msgmap
, n1
);
7501 && (!(limitsrch
&& *limitsrch
)
7502 || in_searchset(*limitsrch
, (unsigned long) raw
)))
7503 mm_searched(stream
, raw
);
7515 * Select by thread number ranges.
7516 * Sets searched bits in mail_elts
7518 * Args limitsrch -- limit search to this searchset
7520 * Returns 0 on success.
7523 select_by_thrd_number(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**msgset
)
7527 char number1
[16], number2
[16], numbers
[80], *p
, *t
;
7529 PINETHRD_S
*thrd
= NULL
;
7533 ps_global
->mangled_footer
= 1;
7536 int flags
= OE_APPEND_CURRENT
;
7538 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
7539 sizeof(numbers
), _(select_num
), NULL
, help
, &flags
);
7544 help
= (help
== NO_HELP
) ? h_select_by_thrdnum
: NO_HELP
;
7548 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
7549 if(!isspace((unsigned char)*p
))
7554 if(r
== 1 || numbers
[0] == '\0'){
7555 cmd_cancelled("Selection by number");
7562 for(n1
= 1; n1
<= stream
->nmsgs
; n1
++)
7563 if((mc
= mail_elt(stream
, n1
)) != NULL
)
7564 mc
->searched
= 0; /* clear searched bits */
7566 for(p
= numbers
; *p
; p
++){
7568 while(*p
&& isdigit((unsigned char)*p
))
7574 if(number1
[0] == '\0'){
7576 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7577 _("Invalid number range, missing number before \"-\": %s"),
7581 else if(!strucmp("end", p
)){
7586 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7587 _("Invalid thread number: %s"), numbers
);
7593 n1
= msgmap
->max_thrdno
;
7594 else if((n1
= atol(number1
)) < 1L || n1
> msgmap
->max_thrdno
){
7595 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7596 _("\"%s\" out of thread number range"),
7604 while(*++p
&& isdigit((unsigned char)*p
))
7610 if(number2
[0] == '\0'){
7611 if(!strucmp("end", p
)){
7616 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7617 _("Invalid number range, missing number after \"-\": %s"),
7624 n2
= msgmap
->max_thrdno
;
7625 else if((n2
= atol(number2
)) < 1L || n2
> msgmap
->max_thrdno
){
7626 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7627 _("\"%s\" out of thread number range"),
7635 strncpy(t
, long2string(n1
), sizeof(t
));
7636 t
[sizeof(t
)-1] = '\0';
7637 q_status_message2(SM_ORDER
| SM_DING
, 0, 2,
7638 _("Invalid reverse message number range: %s-%s"),
7639 t
, long2string(n2
));
7643 for(;n1
<= n2
; n1
++){
7644 thrd
= find_thread_by_number(stream
, msgmap
, n1
, thrd
);
7647 set_search_bit_for_thread(stream
, thrd
, msgset
);
7651 thrd
= find_thread_by_number(stream
, msgmap
, n1
, NULL
);
7654 set_search_bit_for_thread(stream
, thrd
, msgset
);
7666 * Select by message dates.
7667 * Sets searched bits in mail_elts
7669 * Args limitsrch -- limit search to this searchset
7671 * Returns 0 on success.
7674 select_by_date(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
, SEARCHSET
**limitsrch
)
7676 int r
, we_cancel
= 0, when
= 0;
7677 char date
[100], defdate
[100], prompt
[128];
7678 time_t seldate
= time(0);
7679 struct tm
*seldate_tm
;
7682 static struct _tense
{
7687 {"were ", "SENT SINCE", " (inclusive)"},
7688 {"were ", "SENT BEFORE", " (exclusive)"},
7689 {"were ", "SENT ON", "" },
7690 {"", "ARRIVED SINCE", " (inclusive)"},
7691 {"", "ARRIVED BEFORE", " (exclusive)"},
7692 {"", "ARRIVED ON", "" }
7696 ps_global
->mangled_footer
= 1;
7700 * If talking to an old server, default to SINCE instead of
7701 * SENTSINCE, which was added later.
7703 if(is_imap_stream(stream
) && !modern_imap_stream(stream
))
7707 int flags
= OE_APPEND_CURRENT
;
7709 seldate_tm
= localtime(&seldate
);
7710 snprintf(defdate
, sizeof(defdate
), "%.2d-%.4s-%.4d", seldate_tm
->tm_mday
,
7711 month_abbrev(seldate_tm
->tm_mon
+ 1),
7712 seldate_tm
->tm_year
+ 1900);
7713 defdate
[sizeof(defdate
)-1] = '\0';
7714 snprintf(prompt
,sizeof(prompt
),"Select messages which %s%s%s [%s]: ",
7715 tense
[when
].preamble
, tense
[when
].range
,
7716 tense
[when
].scope
, defdate
);
7717 prompt
[sizeof(prompt
)-1] = '\0';
7718 r
= optionally_enter(date
,-FOOTER_ROWS(ps_global
), 0, sizeof(date
),
7719 prompt
, sel_date_opt
, help
, &flags
);
7722 cmd_cancelled("Selection by date");
7726 help
= (help
== NO_HELP
) ? h_select_date
: NO_HELP
;
7737 if(stream
&& (rawno
= mn_m2raw(msgmap
, msgno
)) > 0L
7738 && rawno
<= stream
->nmsgs
7739 && (mc
= mail_elt(stream
, rawno
))){
7741 /* cache not filled in yet? */
7745 if(stream
->dtb
&& stream
->dtb
->flags
& DR_NEWS
){
7747 ulong2string(mail_uid(stream
, rawno
)),
7749 seq
[sizeof(seq
)-1] = '\0';
7750 mail_fetch_overview(stream
, seq
, NULL
);
7753 strncpy(seq
, long2string(rawno
),
7755 seq
[sizeof(seq
)-1] = '\0';
7756 mail_fetch_fast(stream
, seq
, 0L);
7760 /* mail_date returns fixed field width date */
7761 mail_date(date
, mc
);
7768 case 12 : /* set default to PREVIOUS day */
7772 case 13 : /* set default to NEXT day */
7777 when
= (when
+1) % (sizeof(tense
) / sizeof(struct _tense
));
7784 removing_leading_white_space(date
);
7785 removing_trailing_white_space(date
);
7787 strncpy(date
, defdate
, sizeof(date
));
7788 date
[sizeof(date
)-1] = '\0';
7794 if((pgm
= mail_newsearchpgm()) != NULL
){
7796 short converted_date
;
7798 if(mail_parse_date(&elt
, (unsigned char *) date
)){
7799 converted_date
= mail_shortdate(elt
.year
, elt
.month
, elt
.day
);
7803 pgm
->sentsince
= converted_date
;
7806 pgm
->sentbefore
= converted_date
;
7809 pgm
->senton
= converted_date
;
7812 pgm
->since
= converted_date
;
7815 pgm
->before
= converted_date
;
7818 pgm
->on
= converted_date
;
7822 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
7824 if(ps_global
&& ps_global
->ttyo
){
7825 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
7826 ps_global
->mangled_footer
= 1;
7829 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
7831 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
7836 /* we know this was freed in mail_search, let caller know */
7841 mail_free_searchpgm(&pgm
);
7842 q_status_message1(SM_ORDER
, 3, 3,
7843 _("Invalid date entered: %s"), date
);
7853 * Select by searching in message headers or body.
7854 * Sets searched bits in mail_elts
7856 * Args limitsrch -- limit search to this searchset
7858 * Returns 0 on success.
7861 select_by_text(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
, SEARCHSET
**limitsrch
)
7863 int r
, ku
, type
, we_cancel
= 0, flags
, rv
, ekeyi
= 0;
7864 int not = 0, me
= 0;
7865 char sstring
[80], savedsstring
[80], tmp
[128];
7866 char *p
, *sval
= NULL
;
7867 char buftmp
[MAILTMPLEN
], namehdr
[80];
7869 ENVELOPE
*env
= NULL
;
7871 unsigned flagsforhist
= 0;
7872 static HISTORY_S
*history
= NULL
;
7873 static char *recip
= "RECIPIENTS";
7874 static char *partic
= "PARTICIPANTS";
7875 static char *match_me
= N_("[Match_My_Addresses]");
7876 static char *dont_match_me
= N_("[Don't_Match_My_Addresses]");
7878 ps_global
->mangled_footer
= 1;
7879 savedsstring
[0] = '\0';
7880 ekey
[0].ch
= ekey
[1].ch
= ekey
[2].ch
= ekey
[3].ch
= -1;
7883 type
= radio_buttons(not ? _(sel_text_not
) : _(sel_text
),
7884 -FOOTER_ROWS(ps_global
), sel_text_opt
,
7885 's', 'x', NO_HELP
, RB_NORM
|RB_RET_HELP
);
7890 helper(h_select_text
, "HELP FOR SELECT BASED ON CONTENTS",
7892 ps_global
->mangled_screen
= 1;
7899 * prepare some friendly defaults...
7902 case 't' : /* address fields, offer To or From */
7907 sval
= (type
== 't') ? "TO" :
7908 (type
== 'f') ? "FROM" :
7909 (type
== 'c') ? "CC" :
7910 (type
== 'r') ? recip
: partic
;
7911 ekey
[ekeyi
].ch
= ctrl('T');
7912 ekey
[ekeyi
].name
= "^T";
7913 ekey
[ekeyi
].rval
= 10;
7914 /* TRANSLATORS: use Current To Address */
7915 ekey
[ekeyi
++].label
= N_("Cur To");
7916 ekey
[ekeyi
].ch
= ctrl('R');
7917 ekey
[ekeyi
].name
= "^R";
7918 ekey
[ekeyi
].rval
= 11;
7919 /* TRANSLATORS: use Current From Address */
7920 ekey
[ekeyi
++].label
= N_("Cur From");
7921 ekey
[ekeyi
].ch
= ctrl('W');
7922 ekey
[ekeyi
].name
= "^W";
7923 ekey
[ekeyi
].rval
= 12;
7924 /* TRANSLATORS: use Current Cc Address */
7925 ekey
[ekeyi
++].label
= N_("Cur Cc");
7926 ekey
[ekeyi
].ch
= ctrl('Y');
7927 ekey
[ekeyi
].name
= "^Y";
7928 ekey
[ekeyi
].rval
= 13;
7929 /* TRANSLATORS: Match Me means match my address */
7930 ekey
[ekeyi
++].label
= N_("Match Me");
7932 ekey
[ekeyi
].name
= "";
7933 ekey
[ekeyi
].rval
= 0;
7934 ekey
[ekeyi
++].label
= "";
7939 ekey
[ekeyi
].ch
= ctrl('X');
7940 ekey
[ekeyi
].name
= "^X";
7941 ekey
[ekeyi
].rval
= 14;
7942 /* TRANSLATORS: use Current Subject */
7943 ekey
[ekeyi
++].label
= N_("Cur Subject");
7955 strncpy(tmp
, "Name of HEADER to match : ", sizeof(tmp
)-1);
7956 tmp
[sizeof(tmp
)-1] = '\0';
7957 flags
= OE_APPEND_CURRENT
;
7963 r
= optionally_enter(namehdr
, -FOOTER_ROWS(ps_global
), 0,
7964 sizeof(namehdr
), tmp
, ekey
, NO_HELP
, &flags
);
7966 cmd_cancelled("Selection by text");
7969 removing_leading_white_space(namehdr
);
7971 while ((namehdr
[0] != '\0') && /* remove trailing ":" */
7972 (namehdr
[strlen(namehdr
) - 1] == ':'))
7973 namehdr
[strlen(namehdr
) - 1] = '\0';
7974 if ((namehdr
[0] != '\0')
7975 && isspace((unsigned char) namehdr
[strlen(namehdr
) - 1]))
7976 removing_trailing_white_space(namehdr
);
7980 if (strchr(namehdr
,' ') || strchr(namehdr
,'\t') ||
7981 strchr(namehdr
,':'))
7983 if (namehdr
[0] == '\0')
7993 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
7997 ekey
[ekeyi
].ch
= KEY_UP
;
7998 ekey
[ekeyi
].rval
= 30;
7999 ekey
[ekeyi
].name
= "";
8001 ekey
[ekeyi
++].label
= "";
8003 ekey
[ekeyi
].ch
= KEY_DOWN
;
8004 ekey
[ekeyi
].rval
= 31;
8005 ekey
[ekeyi
].name
= "";
8006 ekey
[ekeyi
++].label
= "";
8008 ekey
[ekeyi
].ch
= -1;
8012 init_hist(&history
, HISTSIZE
);
8014 if(ekey
[0].ch
> -1 && msgno
> 0L
8015 && !(env
=pine_mail_fetchstructure(stream
,mn_m2raw(msgmap
,msgno
),
8024 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8025 snprintf(tmp
, sizeof(tmp
), "String in message %s to NOT match : ", sval
);
8027 snprintf(tmp
, sizeof(tmp
), "String in message %s to match : ", sval
);
8029 if(items_in_hist(history
) > 0){
8030 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
8031 ekey
[ku
].label
= HISTORY_KEYLABEL
;
8032 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
8033 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
8037 ekey
[ku
].label
= "";
8038 ekey
[ku
+1].name
= "";
8039 ekey
[ku
+1].label
= "";
8042 flags
= OE_APPEND_CURRENT
| OE_KEEP_TRAILING_SPACE
;
8043 r
= optionally_enter(sstring
, -FOOTER_ROWS(ps_global
), 0,
8044 79, tmp
, ekey
, help
, &flags
);
8046 if(me
&& r
== 0 && ((!not && strcmp(sstring
, _(match_me
))) || (not && strcmp(sstring
, _(dont_match_me
)))))
8051 help
= (help
== NO_HELP
)
8053 ? ((type
== 'f') ? h_select_txt_not_from
8054 : (type
== 't') ? h_select_txt_not_to
8055 : (type
== 'c') ? h_select_txt_not_cc
8056 : (type
== 's') ? h_select_txt_not_subj
8057 : (type
== 'a') ? h_select_txt_not_all
8058 : (type
== 'r') ? h_select_txt_not_recip
8059 : (type
== 'p') ? h_select_txt_not_partic
8060 : (type
== 'b') ? h_select_txt_not_body
8062 : ((type
== 'f') ? h_select_txt_from
8063 : (type
== 't') ? h_select_txt_to
8064 : (type
== 'c') ? h_select_txt_cc
8065 : (type
== 's') ? h_select_txt_subj
8066 : (type
== 'a') ? h_select_txt_all
8067 : (type
== 'r') ? h_select_txt_recip
8068 : (type
== 'p') ? h_select_txt_partic
8069 : (type
== 'b') ? h_select_txt_body
8076 case 10 : /* To: default */
8077 if(env
&& env
->to
&& env
->to
->mailbox
){
8078 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->to
->mailbox
,
8079 env
->to
->host
? "@" : "",
8080 env
->to
->host
? env
->to
->host
: "");
8081 sstring
[sizeof(sstring
)-1] = '\0';
8085 case 11 : /* From: default */
8086 if(env
&& env
->from
&& env
->from
->mailbox
){
8087 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->from
->mailbox
,
8088 env
->from
->host
? "@" : "",
8089 env
->from
->host
? env
->from
->host
: "");
8090 sstring
[sizeof(sstring
)-1] = '\0';
8094 case 12 : /* Cc: default */
8095 if(env
&& env
->cc
&& env
->cc
->mailbox
){
8096 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->cc
->mailbox
,
8097 env
->cc
->host
? "@" : "",
8098 env
->cc
->host
? env
->cc
->host
: "");
8099 sstring
[sizeof(sstring
)-1] = '\0';
8103 case 13 : /* Match my addresses */
8105 snprintf(sstring
, sizeof(sstring
), "%s", not ? _(dont_match_me
) : _(match_me
));
8108 case 14 : /* Subject: default */
8109 if(env
&& env
->subject
&& env
->subject
[0]){
8112 snprintf(buftmp
, sizeof(buftmp
), "%.75s", env
->subject
);
8113 buftmp
[sizeof(buftmp
)-1] = '\0';
8114 q
= (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf
,
8115 SIZEOF_20KBUF
, buftmp
);
8116 if(q
!= env
->subject
){
8117 snprintf(savedsstring
, sizeof(savedsstring
), "%.70s", q
);
8118 savedsstring
[sizeof(savedsstring
)-1] = '\0';
8121 snprintf(sstring
, sizeof(sstring
), "%s", q
);
8122 sstring
[sizeof(sstring
)-1] = '\0';
8128 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8129 if((p
= get_prev_hist(history
, sstring
, flagsforhist
, NULL
)) != NULL
){
8130 strncpy(sstring
, p
, sizeof(sstring
));
8131 sstring
[sizeof(sstring
)-1] = '\0';
8132 if(history
->hist
[history
->curindex
]){
8133 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
8134 not = (flagsforhist
& 0x1) ? 1 : 0;
8135 me
= (flagsforhist
& 0x2) ? 1 : 0;
8144 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8145 if((p
= get_next_hist(history
, sstring
, flagsforhist
, NULL
)) != NULL
){
8146 strncpy(sstring
, p
, sizeof(sstring
));
8147 sstring
[sizeof(sstring
)-1] = '\0';
8148 if(history
->hist
[history
->curindex
]){
8149 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
8150 not = (flagsforhist
& 0x1) ? 1 : 0;
8151 me
= (flagsforhist
& 0x2) ? 1 : 0;
8163 if(r
== 1 || sstring
[0] == '\0')
8170 if(type
== 'x' || r
== 'x'){
8171 cmd_cancelled("Selection by text");
8175 if(ps_global
&& ps_global
->ttyo
){
8176 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8177 ps_global
->mangled_footer
= 1;
8180 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8182 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8183 save_hist(history
, sstring
, flagsforhist
, NULL
);
8185 rv
= agg_text_select(stream
, msgmap
, type
, namehdr
, not, me
, sstring
, "utf-8", limitsrch
);
8194 * Select by message size.
8195 * Sets searched bits in mail_elts
8197 * Args limitsrch -- limit search to this searchset
8199 * Returns 0 on success.
8202 select_by_size(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8204 int r
, large
= 1, we_cancel
= 0;
8205 unsigned long n
, mult
= 1L, numerator
= 0L, divisor
= 1L;
8206 char size
[16], numbers
[80], *p
, *t
;
8209 long flags
= (SE_NOPREFETCH
| SE_FREE
);
8212 ps_global
->mangled_footer
= 1;
8216 int flgs
= OE_APPEND_CURRENT
;
8218 sel_size_opt
[1].label
= large
? sel_size_smaller
: sel_size_larger
;
8220 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
8221 sizeof(numbers
), large
? _(select_size_larger_msg
)
8222 : _(select_size_smaller_msg
),
8223 sel_size_opt
, help
, &flgs
);
8233 help
= (help
== NO_HELP
) ? (large
? h_select_by_larger_size
8234 : h_select_by_smaller_size
)
8239 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
8240 if(!isspace((unsigned char)*p
))
8245 if(r
== 1 || numbers
[0] == '\0'){
8246 cmd_cancelled("Selection by size");
8253 if(numbers
[0] == '-'){
8254 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
8255 _("Invalid size entered: %s"), numbers
);
8262 while(*p
&& isdigit((unsigned char)*p
))
8267 if(size
[0] == '\0' && *p
== '.' && isdigit(*(p
+1))){
8272 if(size
[0] == '\0'){
8273 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
8274 _("Invalid size entered: %s"), numbers
);
8278 n
= strtoul(size
, (char **)NULL
, 10);
8283 * We probably ought to just use atof() to convert 1.1 into a
8284 * double, but since we haven't used atof() anywhere else I'm
8285 * reluctant to use it because of portability concerns.
8289 while(*p
&& isdigit((unsigned char)*p
)){
8297 numerator
= strtoul(size
, (char **)NULL
, 10);
8317 n
= n
* mult
+ (numerator
* mult
) / divisor
;
8319 pgm
= mail_newsearchpgm();
8325 if(is_imap_stream(stream
) && !modern_imap_stream(stream
))
8326 flags
|= SE_NOSERVER
;
8328 if(ps_global
&& ps_global
->ttyo
){
8329 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8330 ps_global
->mangled_footer
= 1;
8333 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8335 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
8336 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
8337 /* we know this was freed in mail_search, let caller know */
8349 * visible_searchset -- return c-client search set unEXLDed
8353 visible_searchset(MAILSTREAM
*stream
, MSGNO_S
*msgmap
)
8356 SEARCHSET
*full_set
= NULL
, **set
;
8359 * If we're talking to anything other than a server older than
8360 * imap 4rev1, build a searchset otherwise it'll choke.
8362 if(!(is_imap_stream(stream
) && !modern_imap_stream(stream
))){
8363 if(any_lflagged(msgmap
, MN_EXLD
)){
8364 for(n
= 1L, set
= &full_set
, run
= 0L; n
<= stream
->nmsgs
; n
++)
8365 if(get_lflag(stream
, NULL
, n
, MN_EXLD
)){
8366 if(run
){ /* previous NOT excluded? */
8368 (*set
)->last
= n
- 1L;
8370 set
= &(*set
)->next
;
8374 else if(run
++){ /* next in run */
8377 else{ /* start of run */
8378 *set
= mail_newsearchset();
8383 full_set
= mail_newsearchset();
8384 full_set
->first
= 1L;
8385 full_set
->last
= stream
->nmsgs
;
8394 * Select by message status bits.
8395 * Sets searched bits in mail_elts
8397 * Args limitsrch -- limit search to this searchset
8399 * Returns 0 on success.
8402 select_by_status(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8404 int s
, not = 0, we_cancel
= 0, rv
;
8407 s
= radio_buttons((not) ? _(sel_flag_not
) : _(sel_flag
),
8408 -FOOTER_ROWS(ps_global
), sel_flag_opt
, '*', 'x',
8409 NO_HELP
, RB_NORM
|RB_RET_HELP
);
8412 cmd_cancelled("Selection by status");
8416 helper(h_select_status
, _("HELP FOR SELECT BASED ON STATUS"),
8418 ps_global
->mangled_screen
= 1;
8426 if(ps_global
&& ps_global
->ttyo
){
8427 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8428 ps_global
->mangled_footer
= 1;
8431 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8432 rv
= agg_flag_select(stream
, not, s
, limitsrch
);
8441 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8442 * Sets searched bits in mail_elts
8444 * Args limitsrch -- limit search to this searchset
8446 * Returns 0 on success.
8449 select_by_rule(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8451 char rulenick
[1000], *nick
;
8453 int r
, not = 0, we_cancel
= 0, rflags
= ROLE_DO_SRCH
8461 ps_global
->mangled_footer
= 1;
8466 oe_flags
= OE_APPEND_CURRENT
;
8467 r
= optionally_enter(rulenick
, -FOOTER_ROWS(ps_global
), 0,
8469 not ? _("Rule to NOT match: ")
8470 : _("Rule to match: "),
8471 sel_key_opt
, NO_HELP
, &oe_flags
);
8474 /* select rulenick from a list */
8475 if((nick
=choose_a_rule(rflags
)) != NULL
){
8476 strncpy(rulenick
, nick
, sizeof(rulenick
)-1);
8477 rulenick
[sizeof(rulenick
)-1] = '\0';
8478 fs_give((void **) &nick
);
8487 helper(h_select_rule
, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE
);
8488 ps_global
->mangled_screen
= 1;
8491 cmd_cancelled("Selection by Rule");
8495 removing_leading_and_trailing_white_space(rulenick
);
8497 }while(r
== 3 || r
== 4 || r
== '!');
8501 * The approach of requiring a nickname instead of just allowing the
8502 * user to select from the list of rules has the drawback that a rule
8503 * may not have a nickname, or there may be more than one rule with
8504 * the same nickname. However, it has the benefit of allowing the user
8505 * to type in the nickname and, most importantly, allows us to set
8506 * up the ! (not). We could incorporate the ! into the selection
8507 * screen, but this is easier and also allows the typing of nicks.
8508 * User can just set up nicknames if they want to use this feature.
8510 patgrp
= nick_to_patgrp(rulenick
, rflags
);
8513 if(ps_global
&& ps_global
->ttyo
){
8514 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8515 ps_global
->mangled_footer
= 1;
8518 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8519 match_pattern(patgrp
, stream
, limitsrch
? *limitsrch
: 0, NULL
,
8521 (not ? MP_NOT
: 0) | SE_NOPREFETCH
);
8522 free_patgrp(&patgrp
);
8527 if(limitsrch
&& *limitsrch
){
8528 mail_free_searchset(limitsrch
);
8537 * Allow user to choose a rule from their list of rules.
8539 * Returns an allocated rule nickname on success, NULL otherwise.
8542 choose_a_rule(int rflags
)
8544 char *choice
= NULL
;
8545 char **rule_list
, **lp
;
8550 if(!(nonempty_patterns(rflags
, &pstate
) && first_pattern(&pstate
))){
8551 q_status_message(SM_ORDER
, 3, 3,
8552 _("No rules available. Use Setup/Rules to add some."));
8557 * Build a list of rules to choose from.
8560 for(pat
= first_pattern(&pstate
); pat
; pat
= next_pattern(&pstate
))
8564 q_status_message(SM_ORDER
, 3, 4, _("No rules defined, use Setup/Rules"));
8568 lp
= rule_list
= (char **) fs_get((cnt
+ 1) * sizeof(*rule_list
));
8569 memset(rule_list
, 0, (cnt
+1) * sizeof(*rule_list
));
8571 for(pat
= first_pattern(&pstate
); pat
; pat
= next_pattern(&pstate
))
8572 *lp
++ = cpystr((pat
->patgrp
&& pat
->patgrp
->nick
)
8573 ? pat
->patgrp
->nick
: "?");
8575 /* TRANSLATORS: SELECT A RULE is a screen title
8576 TRANSLATORS: Print something1 using something2.
8577 "rules" is something1 */
8578 choice
= choose_item_from_list(rule_list
, NULL
, _("SELECT A RULE"),
8579 _("rules"), h_select_rule_screen
,
8580 _("HELP FOR SELECTING A RULE NICKNAME"), NULL
);
8583 q_status_message(SM_ORDER
, 1, 4, "No choice");
8585 free_list_array(&rule_list
);
8592 * Select by current thread.
8593 * Sets searched bits in mail_elts for this entire thread
8595 * Args limitsrch -- limit search to this searchset
8597 * Returns 0 on success.
8600 select_by_thread(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**limitsrch
)
8603 PINETHRD_S
*thrd
= NULL
;
8610 for(n
= 1L; n
<= stream
->nmsgs
; n
++)
8611 if((mc
= mail_elt(stream
, n
)) != NULL
)
8612 mc
->searched
= 0; /* clear searched bits */
8614 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
8615 if(thrd
&& thrd
->top
&& thrd
->top
!= thrd
->rawno
)
8616 thrd
= fetch_thread(stream
, thrd
->top
);
8619 * This doesn't unselect if the thread is already selected
8620 * (like select current does), it always selects.
8621 * There is no way to select ! this thread.
8624 set_search_bit_for_thread(stream
, thrd
, limitsrch
);
8633 * Select by message keywords.
8634 * Sets searched bits in mail_elts
8636 * Args limitsrch -- limit search to this searchset
8638 * Returns 0 on success.
8641 select_by_keyword(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8643 int r
, not = 0, we_cancel
= 0;
8644 char keyword
[MAXUSERFLAG
+1], *kword
;
8645 char *error
= NULL
, *p
, *prompt
;
8650 ps_global
->mangled_footer
= 1;
8657 q_status_message(SM_ORDER
, 3, 4, error
);
8658 fs_give((void **) &error
);
8661 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
) && ps_global
->keywords
){
8663 prompt
= _("Keyword (or keyword initial) to NOT match: ");
8665 prompt
= _("Keyword (or keyword initial) to match: ");
8669 prompt
= _("Keyword to NOT match: ");
8671 prompt
= _("Keyword to match: ");
8674 oe_flags
= OE_APPEND_CURRENT
;
8675 r
= optionally_enter(keyword
, -FOOTER_ROWS(ps_global
), 0,
8677 prompt
, sel_key_opt
, help
, &oe_flags
);
8680 /* select keyword from a list */
8681 if((kword
=choose_a_keyword()) != NULL
){
8682 strncpy(keyword
, kword
, sizeof(keyword
)-1);
8683 keyword
[sizeof(keyword
)-1] = '\0';
8684 fs_give((void **) &kword
);
8693 help
= help
== NO_HELP
? h_select_keyword
: NO_HELP
;
8695 cmd_cancelled("Selection by keyword");
8699 removing_leading_and_trailing_white_space(keyword
);
8701 }while(r
== 3 || r
== 4 || r
== '!' || keyword_check(keyword
, &error
));
8704 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
) && ps_global
->keywords
){
8705 p
= initial_to_keyword(keyword
);
8707 strncpy(keyword
, p
, sizeof(keyword
)-1);
8708 keyword
[sizeof(keyword
)-1] = '\0';
8713 * We want to check the keyword, not the nickname of the keyword,
8714 * so convert it to the keyword if necessary.
8716 p
= nick_to_keyword(keyword
);
8718 strncpy(keyword
, p
, sizeof(keyword
)-1);
8719 keyword
[sizeof(keyword
)-1] = '\0';
8722 pgm
= mail_newsearchpgm();
8724 pgm
->unkeyword
= mail_newstringlist();
8725 pgm
->unkeyword
->text
.data
= (unsigned char *) cpystr(keyword
);
8726 pgm
->unkeyword
->text
.size
= strlen(keyword
);
8729 pgm
->keyword
= mail_newstringlist();
8730 pgm
->keyword
->text
.data
= (unsigned char *) cpystr(keyword
);
8731 pgm
->keyword
->text
.size
= strlen(keyword
);
8734 if(ps_global
&& ps_global
->ttyo
){
8735 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8736 ps_global
->mangled_footer
= 1;
8739 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8741 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
8742 pine_mail_search_full(stream
, "UTF-8", pgm
, SE_NOPREFETCH
| SE_FREE
);
8743 /* we know this was freed in mail_search, let caller know */
8755 * Allow user to choose a keyword from their list of keywords.
8757 * Returns an allocated keyword on success, NULL otherwise.
8760 choose_a_keyword(void)
8762 char *choice
= NULL
;
8763 char **keyword_list
, **lp
;
8768 * Build a list of keywords to choose from.
8771 for(cnt
= 0, kw
= ps_global
->keywords
; kw
; kw
= kw
->next
)
8775 q_status_message(SM_ORDER
, 3, 4,
8776 _("No keywords defined, use \"keywords\" option in Setup/Config"));
8780 lp
= keyword_list
= (char **) fs_get((cnt
+ 1) * sizeof(*keyword_list
));
8781 memset(keyword_list
, 0, (cnt
+1) * sizeof(*keyword_list
));
8783 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
)
8784 *lp
++ = cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
8786 /* TRANSLATORS: SELECT A KEYWORD is a screen title
8787 TRANSLATORS: Print something1 using something2.
8788 "keywords" is something1 */
8789 choice
= choose_item_from_list(keyword_list
, NULL
, _("SELECT A KEYWORD"),
8790 _("keywords"), h_select_keyword_screen
,
8791 _("HELP FOR SELECTING A KEYWORD"), NULL
);
8794 q_status_message(SM_ORDER
, 1, 4, "No choice");
8796 free_list_array(&keyword_list
);
8803 * Allow user to choose a list of keywords from their list of keywords.
8805 * Returns allocated list.
8808 choose_list_of_keywords(void)
8810 LIST_SEL_S
*listhead
, *ls
, *p
;
8816 * Build a list of keywords to choose from.
8819 p
= listhead
= NULL
;
8820 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
8822 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
8823 memset(ls
, 0, sizeof(*ls
));
8824 ls
->item
= cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
8837 /* TRANSLATORS: SELECT KEYWORDS is a screen title
8838 Print something1 using something2.
8839 "keywords" is something1 */
8840 if(!select_from_list_screen(listhead
, SFL_ALLOW_LISTMODE
,
8841 _("SELECT KEYWORDS"), _("keywords"),
8842 h_select_multkeyword_screen
,
8843 _("HELP FOR SELECTING KEYWORDS"), NULL
)){
8844 for(cnt
= 0, p
= listhead
; p
; p
= p
->next
)
8848 ret
= (char **) fs_get((cnt
+1) * sizeof(*ret
));
8849 memset(ret
, 0, (cnt
+1) * sizeof(*ret
));
8850 for(i
= 0, p
= listhead
; p
; p
= p
->next
)
8852 ret
[i
++] = cpystr(p
->item
? p
->item
: "");
8855 free_list_sel(&listhead
);
8862 * Allow user to choose a charset
8864 * Returns an allocated charset on success, NULL otherwise.
8867 choose_a_charset(int which_charsets
)
8869 char *choice
= NULL
;
8870 char **charset_list
, **lp
;
8875 * Build a list of charsets to choose from.
8878 for(cnt
= 0, cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
8879 if(!(cs
->flags
& (CF_UNSUPRT
|CF_NOEMAIL
))
8880 && ((which_charsets
& CAC_ALL
)
8881 || (which_charsets
& CAC_POSTING
8882 && cs
->flags
& CF_POSTING
)
8883 || (which_charsets
& CAC_DISPLAY
8884 && cs
->type
!= CT_2022
8885 && (cs
->flags
& (CF_PRIMARY
|CF_DISPLAY
)) == (CF_PRIMARY
|CF_DISPLAY
))))
8890 q_status_message(SM_ORDER
, 3, 4,
8891 _("No charsets found? Enter charset manually."));
8895 lp
= charset_list
= (char **) fs_get((cnt
+ 1) * sizeof(*charset_list
));
8896 memset(charset_list
, 0, (cnt
+1) * sizeof(*charset_list
));
8898 for(cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
8899 if(!(cs
->flags
& (CF_UNSUPRT
|CF_NOEMAIL
))
8900 && ((which_charsets
& CAC_ALL
)
8901 || (which_charsets
& CAC_POSTING
8902 && cs
->flags
& CF_POSTING
)
8903 || (which_charsets
& CAC_DISPLAY
8904 && cs
->type
!= CT_2022
8905 && (cs
->flags
& (CF_PRIMARY
|CF_DISPLAY
)) == (CF_PRIMARY
|CF_DISPLAY
))))
8906 *lp
++ = cpystr(cs
->name
);
8909 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
8910 TRANSLATORS: Print something1 using something2.
8911 "character sets" is something1 */
8912 choice
= choose_item_from_list(charset_list
, NULL
, _("SELECT A CHARACTER SET"),
8913 _("character sets"), h_select_charset_screen
,
8914 _("HELP FOR SELECTING A CHARACTER SET"), NULL
);
8917 q_status_message(SM_ORDER
, 1, 4, "No choice");
8919 free_list_array(&charset_list
);
8926 * Allow user to choose a list of character sets and/or scripts
8928 * Returns allocated list.
8931 choose_list_of_charsets(void)
8933 LIST_SEL_S
*listhead
, *ls
, *p
;
8935 int cnt
, i
, got_one
;
8940 char buf
[1024], *folded
;
8943 * Build a list of charsets to choose from.
8946 p
= listhead
= NULL
;
8948 /* this width is determined by select_from_list_screen() */
8949 width
= ps_global
->ttyo
->screen_cols
- 4;
8951 /* first comes a list of scripts (sets of character sets) */
8952 for(s
= utf8_script(NIL
); s
&& s
->name
; s
++){
8954 limit
= sizeof(buf
)-1;
8956 memset(q
, 0, limit
+1);
8959 sstrncpy(&q
, s
->name
, limit
);
8962 sstrncpy(&q
, " (", limit
-(q
-buf
));
8963 sstrncpy(&q
, s
->description
, limit
-(q
-buf
));
8964 sstrncpy(&q
, ")", limit
-(q
-buf
));
8967 /* add the list of charsets that are in this script */
8969 for(cs
= utf8_charset(NIL
);
8970 cs
&& cs
->name
&& (q
-buf
) < limit
; cs
++){
8971 if(cs
->script
& s
->script
){
8973 * Filter out some un-useful members of the list.
8974 * UTF-7 and UTF-8 weren't actually in the list at the
8975 * time this was written. Just making sure.
8977 if(!strucmp(cs
->name
, "ISO-2022-JP-2")
8978 || !strucmp(cs
->name
, "UTF-7")
8979 || !strucmp(cs
->name
, "UTF-8"))
8983 sstrncpy(&q
, " ", limit
-(q
-buf
));
8986 sstrncpy(&q
, " {", limit
-(q
-buf
));
8989 sstrncpy(&q
, cs
->name
, limit
-(q
-buf
));
8994 sstrncpy(&q
, "}", limit
-(q
-buf
));
8996 /* fold this line so that it can all be seen on the screen */
8997 folded
= fold(buf
, width
, width
, "", " ", FLD_NONE
);
9000 while(t
&& *t
&& (q
= strindex(t
, '\n')) != NULL
){
9003 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9004 memset(ls
, 0, sizeof(*ls
));
9006 ls
->item
= cpystr(s
->name
);
9008 ls
->flags
= SFL_NOSELECT
;
9010 ls
->display_item
= cpystr(t
);
9020 listhead
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9021 memset(listhead
, 0, sizeof(*listhead
));
9022 listhead
->flags
= SFL_NOSELECT
;
9023 listhead
->display_item
=
9024 cpystr(_("Scripts representing groups of related character sets"));
9025 listhead
->next
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9026 memset(listhead
->next
, 0, sizeof(*listhead
));
9027 listhead
->next
->flags
= SFL_NOSELECT
;
9028 listhead
->next
->display_item
=
9029 cpystr(repeat_char(width
, '-'));
9031 listhead
->next
->next
= ls
;
9036 fs_give((void **) &folded
);
9040 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9041 memset(ls
, 0, sizeof(*ls
));
9042 ls
->flags
= SFL_NOSELECT
;
9050 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9051 memset(ls
, 0, sizeof(*ls
));
9052 ls
->flags
= SFL_NOSELECT
;
9054 cpystr(_("Individual character sets, may be mixed with scripts"));
9058 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9059 memset(ls
, 0, sizeof(*ls
));
9060 ls
->flags
= SFL_NOSELECT
;
9062 cpystr(repeat_char(width
, '-'));
9066 /* then comes a list of individual character sets */
9067 for(cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
9068 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9069 memset(ls
, 0, sizeof(*ls
));
9070 ls
->item
= cpystr(cs
->name
);
9083 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9084 Print something1 using something2.
9085 "character sets" is something1 */
9086 if(!select_from_list_screen(listhead
, SFL_ALLOW_LISTMODE
,
9087 _("SELECT CHARACTER SETS"), _("character sets"),
9088 h_select_multcharsets_screen
,
9089 _("HELP FOR SELECTING CHARACTER SETS"), NULL
)){
9090 for(cnt
= 0, p
= listhead
; p
; p
= p
->next
)
9094 ret
= (char **) fs_get((cnt
+1) * sizeof(*ret
));
9095 memset(ret
, 0, (cnt
+1) * sizeof(*ret
));
9096 for(i
= 0, p
= listhead
; p
; p
= p
->next
)
9098 ret
[i
++] = cpystr(p
->item
? p
->item
: "");
9101 free_list_sel(&listhead
);
9106 /* Report quota summary resources in an IMAP server */
9108 void cmd_quota (struct pine
*state
)
9110 QUOTALIST
*imapquota
;
9115 if(!state
->mail_stream
|| !is_imap_stream(state
->mail_stream
)){
9116 q_status_message(SM_ORDER
, 1, 5, "Quota only available for IMAP folders");
9120 if (state
->mail_stream
9121 && !sp_dead_stream(state
->mail_stream
)
9122 && state
->mail_stream
->mailbox
9123 && *state
->mail_stream
->mailbox
9124 && mail_valid_net_parse(state
->mail_stream
->mailbox
, &mb
))
9125 imap_getquotaroot(state
->mail_stream
, mb
.mailbox
);
9127 if(!state
->quota
) /* failed ? */
9128 return; /* go back... */
9130 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
9131 q_status_message(SM_ORDER
| SM_DING
, 3, 3, "Error allocating space.");
9135 so_puts(store
, "Quota Report for ");
9136 so_puts(store
, state
->mail_stream
->original_mailbox
);
9137 so_puts(store
, "\n\n");
9139 for (imapquota
= state
->quota
; imapquota
; imapquota
= imapquota
->next
){
9141 so_puts(store
, _("Resource : "));
9142 so_puts(store
, imapquota
->name
);
9143 so_writec('\n', store
);
9145 so_puts(store
, _("Usage : "));
9146 so_puts(store
, long2string(imapquota
->usage
));
9147 if(!strucmp(imapquota
->name
,"STORAGE"))
9148 so_puts(store
, " KiB ");
9149 if(!strucmp(imapquota
->name
,"MESSAGE")){
9150 so_puts(store
, _(" message"));
9151 if(imapquota
->usage
!= 1)
9152 so_puts(store
, _("s ")); /* plural */
9154 so_puts(store
, _(" "));
9156 so_writec('(', store
);
9157 so_puts(store
, long2string(100*imapquota
->usage
/imapquota
->limit
));
9158 so_puts(store
, "%)\n");
9160 so_puts(store
, _("Limit : "));
9161 so_puts(store
, long2string(imapquota
->limit
));
9162 if(!strucmp(imapquota
->name
,"STORAGE"))
9163 so_puts(store
, " KiB\n\n");
9164 if(!strucmp(imapquota
->name
,"MESSAGE")){
9165 so_puts(store
, _(" message"));
9166 if(imapquota
->usage
!= 1)
9167 so_puts(store
, _("s\n\n")); /* plural */
9169 so_puts(store
, _("\n\n"));
9173 memset(&sargs
, 0, sizeof(SCROLL_S
));
9174 sargs
.text
.text
= so_text(store
);
9175 sargs
.text
.src
= CharStar
;
9176 sargs
.text
.desc
= _("Quota Resources Summary");
9177 sargs
.bar
.title
= _("QUOTA SUMMARY");
9178 sargs
.proc
.tool
= NULL
;
9179 sargs
.help
.text
= h_quota_command
;
9180 sargs
.help
.title
= NULL
;
9181 sargs
.keys
.menu
= NULL
;
9182 setbitmap(sargs
.keys
.bitmap
);
9188 mail_free_quotalist(&(state
->quota
));
9191 /*----------------------------------------------------------------------
9192 Prompt the user for the type of sort he desires
9194 Args: state -- pine state pointer
9195 q1 -- Line to prompt on
9197 Returns 0 if it was cancelled, 1 otherwise.
9200 select_sort(struct pine
*state
, int ql
, SortOrder
*sort
, int *rev
)
9202 char prompt
[200], tmp
[3], *p
;
9204 int deefault
= 'a', retval
= 1;
9209 DLG_SORTPARAM sortsel
;
9211 if (mswin_usedialog ()) {
9213 sortsel
.reverse
= mn_get_revsort (state
->msgmap
);
9214 sortsel
.cursort
= mn_get_sort (state
->msgmap
);
9215 /* assumption here that HelpType is char ** */
9216 sortsel
.helptext
= h_select_sort
;
9219 if ((retval
= os_sortdialog (&sortsel
))) {
9220 *sort
= sortsel
.cursort
;
9221 *rev
= sortsel
.reverse
;
9228 /*----- String together the prompt ------*/
9230 if(F_ON(F_USE_FK
,ps_global
))
9231 strncpy(prompt
, _("Choose type of sort : "), sizeof(prompt
));
9233 strncpy(prompt
, _("Choose type of sort, or 'R' to reverse current sort : "),
9236 for(i
= 0; state
->sort_types
[i
] != EndofList
; i
++) {
9238 p
= sorts
[i
].label
= sort_name(state
->sort_types
[i
]);
9239 while(*(p
+1) && islower((unsigned char)*p
))
9242 sorts
[i
].ch
= tolower((unsigned char)(tmp
[0] = *p
));
9243 sorts
[i
].name
= cpystr(tmp
);
9245 if(mn_get_sort(state
->msgmap
) == state
->sort_types
[i
])
9246 deefault
= sorts
[i
].rval
;
9250 sorts
[i
].rval
= 'r';
9251 sorts
[i
].name
= cpystr("R");
9252 if(F_ON(F_USE_FK
,ps_global
))
9253 sorts
[i
].label
= N_("Reverse");
9255 sorts
[i
].label
= "";
9258 help
= h_select_sort
;
9260 if((F_ON(F_USE_FK
,ps_global
)
9261 && ((s
= double_radio_buttons(prompt
,ql
,sorts
,deefault
,'x',
9262 help
,RB_NORM
)) != 'x'))
9264 (F_OFF(F_USE_FK
,ps_global
)
9265 && ((s
= radio_buttons(prompt
,ql
,sorts
,deefault
,'x',
9266 help
,RB_NORM
)) != 'x'))){
9267 state
->mangled_body
= 1; /* signal screen's changed */
9269 *rev
= !mn_get_revsort(state
->msgmap
);
9271 *sort
= state
->sort_types
[s
];
9273 if(F_ON(F_SHOW_SORT
, ps_global
))
9274 ps_global
->mangled_header
= 1;
9278 cmd_cancelled("Sort");
9282 fs_give((void **)&sorts
[i
].name
);
9284 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
9289 /*---------------------------------------------------------------------
9290 Build list of folders in the given context for user selection
9292 Args: c -- pointer to pointer to folder's context context
9293 f -- folder prefix to display
9294 sublist -- whether or not to use 'f's contents as prefix
9295 lister -- function used to do the actual display
9297 Returns: malloc'd string containing sequence, else NULL if
9298 no messages in msgmap with local "selected" flag.
9301 display_folder_list(CONTEXT_S
**c
, char *f
, int sublist
, int (*lister
) (struct pine
*, CONTEXT_S
**, char *, int))
9305 void (*redraw
)(void) = ps_global
->redrawer
;
9307 push_titlebar_state();
9309 if((rc
= (*lister
)(ps_global
, &tc
, f
, sublist
)) != 0)
9313 pop_titlebar_state();
9315 if((ps_global
->redrawer
= redraw
) != NULL
) /* reset old value, and test */
9316 (*ps_global
->redrawer
)();
9318 if(rc
== 1 && F_ON(F_SELECT_WO_CONFIRM
, ps_global
))
9326 * Allow user to choose a single item from a list of strings.
9328 * Args list -- Array of strings to choose from, NULL terminated.
9329 * displist -- Array of strings to display instead of displaying list.
9330 * Indices correspond to the list array. Display the displist
9331 * but return the item from list if displist non-NULL.
9332 * title -- For conf_scroll_screen
9333 * pdesc -- For conf_scroll_screen
9334 * help -- For conf_scroll_screen
9335 * htitle -- For conf_scroll_screen
9337 * Returns an allocated copy of the chosen item or NULL.
9340 choose_item_from_list(char **list
, char **displist
, char *title
, char *pdesc
, HelpType help
,
9341 char *htitle
, char *cursor_location
)
9343 LIST_SEL_S
*listhead
, *ls
, *p
, *starting_val
= NULL
;
9345 char *ret
= NULL
, *choice
= NULL
;
9347 /* build the LIST_SEL_S list */
9348 p
= listhead
= NULL
;
9349 for(t
= list
, dl
= displist
; *t
; t
++, dl
++){
9350 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9351 memset(ls
, 0, sizeof(*ls
));
9352 ls
->item
= cpystr(*t
);
9354 ls
->display_item
= cpystr(*dl
);
9356 if(cursor_location
&& (cursor_location
== (*t
)))
9370 if(!select_from_list_screen(listhead
, SFL_NONE
, title
, pdesc
,
9371 help
, htitle
, starting_val
))
9372 for(p
= listhead
; !choice
&& p
; p
= p
->next
)
9377 ret
= cpystr(choice
);
9379 free_list_sel(&listhead
);
9386 free_list_sel(LIST_SEL_S
**lsel
)
9389 free_list_sel(&(*lsel
)->next
);
9391 fs_give((void **) &(*lsel
)->item
);
9393 if((*lsel
)->display_item
)
9394 fs_give((void **) &(*lsel
)->display_item
);
9396 fs_give((void **) lsel
);
9402 * file_lister - call pico library's file lister
9405 file_lister(char *title
, char *path
, size_t pathlen
, char *file
, size_t filelen
, int newmail
, int flags
)
9409 void (*redraw
)(void) = ps_global
->redrawer
;
9411 standard_picobuf_setup(&pbf
);
9412 push_titlebar_state();
9416 /* BUG: what about help command and text? */
9417 pbf
.pine_anchor
= title
;
9419 rv
= pico_file_browse(&pbf
, path
, pathlen
, file
, filelen
, NULL
, 0, flags
);
9420 standard_picobuf_teardown(&pbf
);
9421 fix_windsize(ps_global
);
9422 init_signals(); /* has it's own signal stuff */
9424 /* Restore display's titlebar and body */
9425 pop_titlebar_state();
9427 if((ps_global
->redrawer
= redraw
) != NULL
)
9428 (*ps_global
->redrawer
)();
9434 /*----------------------------------------------------------------------
9435 Print current folder index
9439 print_index(struct pine
*state
, MSGNO_S
*msgmap
, int agg
)
9443 char buf
[MAX_SCREEN_COLS
+1];
9445 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++){
9446 if(agg
&& !get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
))
9449 if(!agg
&& msgline_hidden(state
->mail_stream
, msgmap
, i
, 0))
9452 ice
= build_header_line(state
, state
->mail_stream
, msgmap
, i
, NULL
);
9456 * I don't understand why we'd want to mark the current message
9457 * instead of printing out the first character of the status
9458 * so I'm taking it out and including the first character of the
9459 * line instead. Hubert 2006-02-09
9461 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
9465 if(!gf_puts(simple_index_line(buf
,sizeof(buf
),ice
,i
),
9467 || !gf_puts(NEWLINE
, print_char
))
9479 * windows callback to get/set header mode state
9482 header_mode_callback(set
, args
)
9486 return(ps_global
->full_header
);
9491 * windows callback to get/set zoom mode state
9494 zoom_mode_callback(set
, args
)
9498 return(any_lflagged(ps_global
->msgmap
, MN_HIDE
) != 0);
9503 * windows callback to get/set zoom mode state
9506 any_selected_callback(set
, args
)
9510 return(any_lflagged(ps_global
->msgmap
, MN_SLCT
) != 0);
9518 flag_callback(set
, flags
)
9528 case 1: /* Important */
9529 permflag
= ps_global
->mail_stream
->perm_flagged
;
9533 permflag
= ps_global
->mail_stream
->perm_seen
;
9536 case 3: /* Answered */
9537 permflag
= ps_global
->mail_stream
->perm_answered
;
9540 case 4: /* Deleted */
9541 permflag
= ps_global
->mail_stream
->perm_deleted
;
9546 if(!(any_messages(ps_global
->msgmap
, NULL
, "to Flag")
9547 && can_set_flag(ps_global
, "flag", permflag
)))
9550 if(sp_io_error_on_stream(ps_global
->mail_stream
)){
9551 sp_set_io_error_on_stream(ps_global
->mail_stream
, 0);
9552 pine_mail_check(ps_global
->mail_stream
); /* forces write */
9556 msgno
= mn_m2raw(ps_global
->msgmap
, mn_get_cur(ps_global
->msgmap
));
9557 if(msgno
> 0L && ps_global
->mail_stream
9558 && msgno
<= ps_global
->mail_stream
->nmsgs
9559 && (mc
= mail_elt(ps_global
->mail_stream
, msgno
))
9562 * NOTE: code below is *VERY* sensitive to the order of
9563 * the messages defined in resource.h for flag handling.
9564 * Don't change it unless you know what you're doing.
9571 case 1 : /* Important */
9572 flagstr
= "\\FLAGGED";
9573 mflag
= (mc
->flagged
) ? 0L : ST_SET
;
9578 mflag
= (mc
->seen
) ? 0L : ST_SET
;
9581 case 3 : /* Answered */
9582 flagstr
= "\\ANSWERED";
9583 mflag
= (mc
->answered
) ? 0L : ST_SET
;
9586 case 4 : /* Deleted */
9587 flagstr
= "\\DELETED";
9588 mflag
= (mc
->deleted
) ? 0L : ST_SET
;
9591 default : /* bogus */
9595 mail_flag(ps_global
->mail_stream
, long2string(msgno
),
9598 if(ps_global
->redrawer
)
9599 (*ps_global
->redrawer
)();
9626 * BUG: Should teach this about keywords
9632 static MPopup flag_submenu
[] = {
9633 {tMessage
, {N_("Important"), lNormal
}, {IDM_MI_FLAGIMPORTANT
}},
9634 {tMessage
, {N_("New"), lNormal
}, {IDM_MI_FLAGNEW
}},
9635 {tMessage
, {N_("Answered"), lNormal
}, {IDM_MI_FLAGANSWERED
}},
9636 {tMessage
, {N_("Deleted"), lNormal
}, {IDM_MI_FLAGDELETED
}},
9641 flag_submenu
[0].label
.style
= (mc
&& mc
->flagged
) ? lChecked
: lNormal
;
9644 flag_submenu
[1].label
.style
= (mc
&& mc
->seen
) ? lNormal
: lChecked
;
9647 flag_submenu
[2].label
.style
= (mc
&& mc
->answered
) ? lChecked
: lNormal
;
9650 flag_submenu
[3].label
.style
= (mc
&& mc
->deleted
) ? lChecked
: lNormal
;
9652 return(flag_submenu
);
9655 #endif /* _WINDOWS */