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"));
1063 char *front
, type
[80], cnt
[CNTLEN
], fbuf
[MAX_SCREEN_COLS
/2+1];
1064 int rbspace
, avail
, need
, take_back
;
1068 * Incoming_folder_ or news_group_ or folder_ or group_
1070 * _(13 recent) or _(some recent) or nothing
1073 front
= "View next";
1075 (state
->context_current
->use
& CNTXT_INCMNG
)
1076 ? "Incoming folder" : "news group",
1078 type
[sizeof(type
)-1] = '\0';
1079 snprintf(cnt
, sizeof(cnt
), " (%.*s %s)", CNTLEN
-20,
1080 recent_cnt
? long2string(recent_cnt
) : "some",
1081 F_ON(F_TAB_USES_UNSEEN
, ps_global
)
1082 ? "unseen" : "recent");
1083 cnt
[sizeof(cnt
)-1] = '\0';
1086 * Space reserved for radio_buttons call.
1087 * If we make this 3 then radio_buttons won't mess
1088 * with the prompt. If we make it 2, then we get
1089 * one more character to use but radio_buttons will
1090 * cut off the last character of our prompt, which is
1091 * ok because it is a space.
1094 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
1096 need
= strlen(front
)+1 + strlen(type
)+1 +
1097 + strlen(nextfolder
)+2 + strlen(cnt
) +
1100 take_back
= strlen(type
);
1102 (state
->context_current
->use
& CNTXT_INCMNG
)
1103 ? "folder" : "group", sizeof(type
));
1104 take_back
-= strlen(type
);
1107 need
-= strlen(cnt
);
1111 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1112 snprintf(prompt
, sizeof(prompt
), "%.*s %.*s \"%.*s\"%.*s? ",
1113 (MAX_SCREEN_COLS
+1)/8, front
,
1114 (MAX_SCREEN_COLS
+1)/8, type
,
1115 (MAX_SCREEN_COLS
+1)/2,
1116 short_str(nextfolder
, fbuf
, sizeof(fbuf
),
1117 strlen(nextfolder
) -
1118 ((need
>avail
) ? (need
-avail
) : 0),
1120 (MAX_SCREEN_COLS
+1)/8, cnt
);
1121 prompt
[sizeof(prompt
)-1] = '\0';
1125 * When help gets added, this'll have to become
1126 * a loop like the rest...
1128 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD
, state
)){
1129 static ESCKEY_S next_opt
[] = {
1130 {'y', 'y', "Y", N_("Yes")},
1131 {'n', 'n', "N", N_("No")},
1132 {TAB
, 'n', "Tab", N_("NextNew")},
1136 ret
= radio_buttons(prompt
, -FOOTER_ROWS(state
),
1137 next_opt
, 'y', 'x', NO_HELP
,
1140 cmd_cancelled(NULL
);
1148 if(nextstream
&& sp_dead_stream(nextstream
))
1151 visit_folder(state
, nextfolder
,
1152 state
->context_current
, nextstream
,
1154 /* visit_folder takes care of nextstream */
1162 pine_mail_close(nextstream
);
1166 (mn_get_total(msgmap
) > 0L)
1167 ? IS_NEWS(stream
) ? "more undeleted" : "more new"
1177 /*------- Zoom -----------*/
1180 * Right now the way zoom is implemented is sort of silly.
1181 * There are two per-message flags where just one and a
1182 * global "zoom mode" flag to suppress messags from the index
1185 if(any_messages(msgmap
, NULL
, "to Zoom on")){
1186 if(unzoom_index(state
, stream
, msgmap
)){
1187 dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
1188 q_status_message(SM_ORDER
,0,2, _("Index Zoom Mode is now off"));
1190 else if((i
= zoom_index(state
, stream
, msgmap
, MN_SLCT
)) != 0){
1191 if(any_lflagged(msgmap
, MN_HIDE
)){
1192 dprint((4,"\n\n ---- Entering ZOOM mode ----\n"));
1193 q_status_message4(SM_ORDER
, 0, 2,
1194 _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"),
1195 THRD_INDX() ? "" : comatose(i
),
1196 THRD_INDX() ? "" : " ",
1197 THRD_INDX() ? _("threads") : _("message"),
1198 THRD_INDX() ? "" : plural(i
));
1201 q_status_message(SM_ORDER
, 0, 2,
1202 _("All messages selected, so not entering Index Zoom Mode"));
1205 any_messages(NULL
, "selected", "to Zoom on");
1211 /*---------- print message on paper ----------*/
1213 if(any_messages(msgmap
, NULL
, "to print"))
1214 (void) cmd_print(state
, msgmap
, MCMD_NONE
, in_index
);
1219 /*---------- Take Address ----------*/
1221 if(F_ON(F_ENABLE_ROLE_TAKE
, state
) ||
1222 any_messages(msgmap
, NULL
, "to Take address from"))
1223 (void) cmd_take_addr(state
, msgmap
, MCMD_NONE
);
1228 /*---------- Save Message ----------*/
1230 if(any_messages(msgmap
, NULL
, "to Save"))
1231 (void) cmd_save(state
, stream
, msgmap
, MCMD_NONE
, in_index
);
1236 /*---------- Export message ----------*/
1238 if(any_messages(msgmap
, NULL
, "to Export")){
1239 (void) cmd_export(state
, msgmap
, question_line
, MCMD_NONE
);
1240 state
->mangled_footer
= 1;
1246 /*---------- Expunge ----------*/
1248 (void) cmd_expunge(state
, stream
, msgmap
, MCMD_NONE
);
1252 /*------- Unexclude -----------*/
1254 if(!(IS_NEWS(stream
) && stream
->rdonly
)){
1255 q_status_message(SM_ORDER
, 0, 3,
1256 _("Unexclude not available for mail folders"));
1258 else if(any_lflagged(msgmap
, MN_EXLD
)){
1264 * Since excluded means "hidden deleted" and "killed",
1265 * the count should reflect the former.
1267 pgm
= mail_newsearchpgm();
1269 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
1270 for(i
= 1L, del_count
= 0L; i
<= stream
->nmsgs
; i
++)
1271 if((mc
= mail_elt(stream
, i
)) && mc
->searched
1272 && get_lflag(stream
, NULL
, i
, MN_EXLD
)
1273 && !(msgno_exceptions(stream
, i
, "0", &exbits
, FALSE
)
1274 && (exbits
& MSG_EX_FILTERED
)))
1278 state
->mangled_footer
= 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1279 snprintf(prompt
, sizeof(prompt
), "UNexclude %ld message%s in %.*s", del_count
,
1280 plural(del_count
), MAX_SCREEN_COLS
+1-40,
1281 pretty_fn(state
->cur_folder
));
1282 prompt
[sizeof(prompt
)-1] = '\0';
1283 if(F_ON(F_FULL_AUTO_EXPUNGE
, state
)
1284 || (F_ON(F_AUTO_EXPUNGE
, state
)
1285 && (state
->context_current
1286 && (state
->context_current
->use
& CNTXT_INCMNG
))
1287 && context_isambig(state
->cur_folder
))
1288 || want_to(prompt
, 'y', 0, NO_HELP
, WT_NORM
) == 'y'){
1289 long save_cur_rawno
;
1290 int were_viewing_a_thread
;
1292 save_cur_rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
1293 were_viewing_a_thread
= (THREADING()
1294 && sp_viewing_a_thread(stream
));
1296 if(msgno_include(stream
, msgmap
, MI_NONE
)){
1297 clear_index_cache(stream
, 0);
1299 if(stream
&& stream
->spare
)
1300 erase_threading_info(stream
, msgmap
);
1302 refresh_sort(stream
, msgmap
, SRT_NON
);
1305 if(were_viewing_a_thread
){
1306 if(save_cur_rawno
> 0L)
1307 mn_set_cur(msgmap
, mn_raw2m(msgmap
,save_cur_rawno
));
1309 if(view_thread(state
, stream
, msgmap
, 1) && current_index_state
)
1310 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
1313 if(save_cur_rawno
> 0L)
1314 mn_set_cur(msgmap
, mn_raw2m(msgmap
,save_cur_rawno
));
1316 state
->mangled_screen
= 1;
1317 q_status_message2(SM_ORDER
, 0, 4,
1318 "%s message%s UNexcluded",
1319 long2string(del_count
),
1322 if(in_index
!= View
)
1323 adjust_cur_to_visible(stream
, msgmap
);
1326 any_messages(NULL
, NULL
, "UNexcluded");
1329 any_messages(NULL
, "excluded", "to UNexclude");
1332 any_messages(NULL
, "excluded", "to UNexclude");
1337 /*------- Make Selection -----------*/
1339 if(any_messages(msgmap
, NULL
, "to Select")){
1340 if(aggregate_select(state
, msgmap
, question_line
, in_index
) == 0
1341 && (in_index
== MsgIndx
|| in_index
== ThrdIndx
)
1342 && F_ON(F_AUTO_ZOOM
, state
)
1343 && any_lflagged(msgmap
, MN_SLCT
) > 0L
1344 && !any_lflagged(msgmap
, MN_HIDE
))
1345 (void) zoom_index(state
, stream
, msgmap
, MN_SLCT
);
1351 /*------- Toggle Current Message Selection State -----------*/
1353 if(any_messages(msgmap
, NULL
, NULL
)){
1354 if((select_by_current(state
, msgmap
, in_index
)
1355 || (F_OFF(F_UNSELECT_WONT_ADVANCE
, state
)
1356 && !any_lflagged(msgmap
, MN_HIDE
)))
1357 && (i
= mn_get_cur(msgmap
)) < mn_get_total(msgmap
)){
1358 /* advance current */
1359 mn_inc_cur(stream
, msgmap
,
1360 (in_index
== View
&& THREADING()
1361 && sp_viewing_a_thread(stream
))
1363 : (in_index
== View
)
1364 ? MH_ANYTHD
: MH_NONE
);
1371 /*------- Apply command -----------*/
1373 if(any_messages(msgmap
, NULL
, NULL
)){
1374 if(any_lflagged(msgmap
, MN_SLCT
) > 0L){
1375 if(apply_command(state
, stream
, msgmap
, 0,
1376 AC_NONE
, question_line
)){
1377 if(F_ON(F_AUTO_UNSELECT
, state
)){
1378 agg_select_all(stream
, msgmap
, NULL
, 0);
1379 unzoom_index(state
, stream
, msgmap
);
1381 else if(F_ON(F_AUTO_UNZOOM
, state
))
1382 unzoom_index(state
, stream
, msgmap
);
1386 any_messages(NULL
, NULL
, "to Apply command to. Try \"Select\"");
1392 /*-------- Sort command -------*/
1395 int were_threading
= THREADING();
1396 SortOrder sort
= mn_get_sort(msgmap
);
1397 int rev
= mn_get_revsort(msgmap
);
1399 dprint((1,"MAIL_CMD: sort\n"));
1400 if(select_sort(state
, question_line
, &sort
, &rev
)){
1401 /* $ command reinitializes threading collapsed/expanded info */
1402 if(SORT_IS_THREADED(msgmap
) && !SEP_THRDINDX())
1403 erase_threading_info(stream
, msgmap
);
1405 if(ps_global
&& ps_global
->ttyo
){
1406 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
1407 ps_global
->mangled_footer
= 1;
1410 sort_folder(stream
, msgmap
, sort
, rev
, SRT_VRB
|SRT_MAN
);
1413 state
->mangled_footer
= 1;
1416 * We've changed whether we are threading or not so we need to
1417 * exit the index and come back in so that we switch between the
1418 * thread index and the regular index. Sort_folder will have
1419 * reset viewing_a_thread if necessary.
1422 && ((!were_threading
&& THREADING())
1423 || (were_threading
&& !THREADING()))){
1424 state
->next_screen
= mail_index_screen
;
1425 state
->mangled_screen
= 1;
1432 /*------- Toggle Full Headers -----------*/
1434 state
->full_header
++;
1435 if(state
->full_header
== 1){
1436 if(!(state
->quote_suppression_threshold
1437 && (state
->some_quoting_was_suppressed
|| in_index
!= View
)))
1438 state
->full_header
++;
1440 else if(state
->full_header
> 2)
1441 state
->full_header
= 0;
1443 switch(state
->full_header
){
1445 q_status_message(SM_ORDER
, 0, 3,
1446 _("Display of full headers is now off."));
1450 q_status_message1(SM_ORDER
, 0, 3,
1451 _("Quotes displayed, use %s to see full headers"),
1452 F_ON(F_USE_FK
, state
) ? "F9" : "H");
1456 q_status_message(SM_ORDER
, 0, 3,
1457 _("Display of full headers is now on."));
1472 /*------- Try to decrypt message -----------*/
1474 if(state
->smime
&& state
->smime
->need_passphrase
)
1475 smime_get_passphrase();
1481 smime_info_screen(state
);
1486 /*------- Bounce -----------*/
1488 (void) cmd_bounce(state
, msgmap
, MCMD_NONE
);
1492 /*------- Flag -----------*/
1494 dprint((4, "\n - flag message -\n"));
1495 (void) cmd_flag(state
, msgmap
, MCMD_NONE
);
1499 /*------- Pipe message -----------*/
1501 (void) cmd_pipe(state
, msgmap
, MCMD_NONE
);
1505 /*--------- Default, unknown command ----------*/
1507 alpine_panic("Unexpected command case");
1511 return((a_changed
|| mn_get_cur(msgmap
) != old_msgno
) ? 1 : 0);
1516 /*----------------------------------------------------------------------
1517 Map some of the special characters into sensible strings for human
1519 c is a UCS-4 character!
1522 pretty_command(UCS c
)
1524 static char buf
[10];
1531 case ' ' : s
= "SPACE"; break;
1532 case '\033' : s
= "ESC"; break;
1533 case '\177' : s
= "DEL"; break;
1534 case ctrl('I') : s
= "TAB"; break;
1535 case ctrl('J') : s
= "LINEFEED"; break;
1536 case ctrl('M') : s
= "RETURN"; break;
1537 case ctrl('Q') : s
= "XON"; break;
1538 case ctrl('S') : s
= "XOFF"; break;
1539 case KEY_UP
: s
= "Up Arrow"; break;
1540 case KEY_DOWN
: s
= "Down Arrow"; break;
1541 case KEY_RIGHT
: s
= "Right Arrow"; break;
1542 case KEY_LEFT
: s
= "Left Arrow"; break;
1543 case KEY_PGUP
: s
= "Prev Page"; break;
1544 case KEY_PGDN
: s
= "Next Page"; break;
1545 case KEY_HOME
: s
= "Home"; break;
1546 case KEY_END
: s
= "End"; break;
1547 case KEY_DEL
: s
= "Delete"; break; /* Not necessary DEL! */
1548 case KEY_JUNK
: s
= "Junk!"; break;
1549 case BADESC
: s
= "Bad Esc"; break;
1550 case NO_OP_IDLE
: s
= "NO_OP_IDLE"; break;
1551 case NO_OP_COMMAND
: s
= "NO_OP_COMMAND"; break;
1552 case KEY_RESIZE
: s
= "KEY_RESIZE"; break;
1553 case KEY_UTF8
: s
= "KEY_UTF8"; break;
1554 case KEY_MOUSE
: s
= "KEY_MOUSE"; break;
1555 case KEY_SCRLUPL
: s
= "KEY_SCRLUPL"; break;
1556 case KEY_SCRLDNL
: s
= "KEY_SCRLDNL"; break;
1557 case KEY_SCRLTO
: s
= "KEY_SCRLTO"; break;
1558 case KEY_XTERM_MOUSE
: s
= "KEY_XTERM_MOUSE"; break;
1559 case KEY_DOUBLE_ESC
: s
= "KEY_DOUBLE_ESC"; break;
1560 case CTRL_KEY_UP
: s
= "Ctrl Up Arrow"; break;
1561 case CTRL_KEY_DOWN
: s
= "Ctrl Down Arrow"; break;
1562 case CTRL_KEY_RIGHT
: s
= "Ctrl Right Arrow"; break;
1563 case CTRL_KEY_LEFT
: s
= "Ctrl Left Arrow"; break;
1576 snprintf(s
= buf
, sizeof(buf
), "F%ld", (long) (c
- PF1
+ 1));
1580 if(c
< ' ' || (c
>= 0x80 && c
< 0xA0)){
1585 d
= (c
& 0x1f) + 'A' - 1;
1586 snprintf(s
= buf
, sizeof(buf
), "%c%c", c1
? '~' : '^', d
);
1589 memset(buf
, 0, sizeof(buf
));
1590 utf8_put((unsigned char *) buf
, (unsigned long) c
);
1600 /*----------------------------------------------------------------------
1601 Complain about bogus input
1603 Args: ch -- input command to complain about
1604 help -- string indicating where to get help
1608 bogus_command(UCS cmd
, char *help
)
1610 if(cmd
== ctrl('Q') || cmd
== ctrl('S'))
1611 q_status_message1(SM_ASYNC
, 0, 2,
1612 "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.",
1613 pretty_command(cmd
));
1614 else if(cmd
== KEY_JUNK
)
1615 q_status_message3(SM_ORDER
, 0, 2,
1616 "Invalid key pressed.%s%s%s",
1617 (help
) ? " Use " : "",
1619 (help
) ? " for help" : "");
1621 q_status_message4(SM_ORDER
, 0, 2,
1622 "Command \"%s\" not defined for this screen.%s%s%s",
1623 pretty_command(cmd
),
1624 (help
) ? " Use " : "",
1626 (help
) ? " for help" : "");
1631 bogus_utf8_command(char *cmd
, char *help
)
1633 q_status_message4(SM_ORDER
, 0, 2,
1634 "Command \"%s\" not defined for this screen.%s%s%s",
1636 (help
) ? " Use " : "",
1638 (help
) ? " for help" : "");
1642 /*----------------------------------------------------------------------
1643 Execute FLAG message command
1645 Args: state -- Various satate info
1646 msgmap -- map of c-client to local message numbers
1648 Result: with side effect of "current" message FLAG flag set or UNset
1652 cmd_flag(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
1654 char *flagit
, *seq
, *screen_text
[20], **exp
, **p
, *answer
= NULL
;
1655 char *keyword_array
[2];
1656 int user_defined_flags
= 0, mailbox_flags
= 0;
1657 int directly_to_maint_screen
= 0;
1658 long unflagged
, flagged
, flags
, rawno
;
1659 MESSAGECACHE
*mc
= NULL
;
1661 int i
, cnt
, is_set
, trouble
= 0, rv
= 0;
1663 struct flag_table
*fp
, *ftbl
= NULL
;
1664 struct flag_screen flag_screen
;
1665 static char *flag_screen_text1
[] = {
1666 N_(" Set desired flags for current message below. An 'X' means set"),
1667 N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."),
1671 static char *flag_screen_text2
[] = {
1672 N_(" Set desired flags below for selected messages. A '?' means to"),
1673 N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"),
1674 N_(" to unset it. Use the \"Return\" key to toggle, and choose"),
1675 N_(" \"Exit\" when finished."),
1679 static struct flag_table default_ftbl
[] = {
1680 {N_("Important"), h_flag_important
, F_FLAG
, 0, 0, NULL
, NULL
},
1681 {N_("New"), h_flag_new
, F_SEEN
, 0, 0, NULL
, NULL
},
1682 {N_("Answered"), h_flag_answered
, F_ANS
, 0, 0, NULL
, NULL
},
1683 {N_("Forwarded"), h_flag_forwarded
, F_FWD
, 0, 0, NULL
, NULL
},
1684 {N_("Deleted"), h_flag_deleted
, F_DEL
, 0, 0, NULL
, NULL
},
1685 {NULL
, NO_HELP
, 0, 0, 0, NULL
, NULL
}
1688 /* Only check for dead stream for now. Should check permanent flags
1691 if(!(any_messages(msgmap
, NULL
, "to Flag") && can_set_flag(state
, "flag", 1)))
1694 if(sp_io_error_on_stream(state
->mail_stream
)){
1695 sp_set_io_error_on_stream(state
->mail_stream
, 0);
1696 pine_mail_check(state
->mail_stream
); /* forces write */
1702 user_defined_flags
= 0;
1708 /* count how large ftbl will be */
1709 for(cnt
= 0; default_ftbl
[cnt
].name
; cnt
++)
1712 /* add user flags */
1713 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1714 if(!((kw
->nick
&& !strucmp(FORWARDED_FLAG
, kw
->nick
)) || (kw
->kw
&& !strucmp(FORWARDED_FLAG
, kw
->kw
)))){
1715 user_defined_flags
++;
1721 * Add mailbox flags that aren't user-defined flags.
1722 * Don't consider it if it matches either one of our defined
1723 * keywords or one of our defined nicknames for a keyword.
1725 for(i
= 0; stream_to_user_flag_name(state
->mail_stream
, i
); i
++){
1728 q
= stream_to_user_flag_name(state
->mail_stream
, i
);
1730 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1731 if((kw
->nick
&& !strucmp(kw
->nick
, q
)) || (kw
->kw
&& !strucmp(kw
->kw
, q
)))
1736 if(!kw
&& !(q
&& !strucmp(FORWARDED_FLAG
, q
))){
1742 cnt
+= (user_defined_flags
? 2 : 0) + (mailbox_flags
? 2 : 0);
1744 /* set up ftbl, first the system flags */
1745 ftbl
= (struct flag_table
*) fs_get((cnt
+1) * sizeof(*ftbl
));
1746 memset(ftbl
, 0, (cnt
+1) * sizeof(*ftbl
));
1747 for(i
= 0, fp
= ftbl
; default_ftbl
[i
].name
; i
++, fp
++){
1748 fp
->name
= cpystr(default_ftbl
[i
].name
);
1749 fp
->help
= default_ftbl
[i
].help
;
1750 fp
->flag
= default_ftbl
[i
].flag
;
1751 fp
->set
= default_ftbl
[i
].set
;
1752 fp
->ukn
= default_ftbl
[i
].ukn
;
1755 if(user_defined_flags
){
1756 fp
->flag
= F_COMMENT
;
1757 fp
->name
= cpystr("");
1759 fp
->flag
= F_COMMENT
;
1760 len
= strlen(_("User-defined Keywords from Setup/Config"));
1761 fp
->name
= (char *) fs_get((len
+6+6+1) * sizeof(char));
1762 snprintf(fp
->name
, len
+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config"));
1766 /* then the user-defined keywords */
1767 if(user_defined_flags
)
1768 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1769 if(!((kw
->nick
&& !strucmp(FORWARDED_FLAG
, kw
->nick
))
1770 || (kw
->kw
&& !strucmp(FORWARDED_FLAG
, kw
->kw
)))){
1771 fp
->name
= cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
1772 fp
->keyword
= cpystr(kw
->kw
? kw
->kw
: "");
1773 if(kw
->nick
&& kw
->kw
){
1776 l
= strlen(kw
->kw
)+2;
1777 fp
->comment
= (char *) fs_get((l
+1) * sizeof(char));
1778 snprintf(fp
->comment
, l
+1, "(%.*s)", (int) strlen(kw
->kw
), kw
->kw
);
1779 fp
->comment
[l
] = '\0';
1782 fp
->help
= h_flag_user_flag
;
1783 fp
->flag
= F_KEYWORD
;
1791 fp
->flag
= F_COMMENT
;
1792 fp
->name
= cpystr("");
1794 fp
->flag
= F_COMMENT
;
1795 len
= strlen(_("Other keywords in the mailbox that are not user-defined"));
1796 fp
->name
= (char *) fs_get((len
+6+6+1) * sizeof(char));
1797 snprintf(fp
->name
, len
+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined"));
1801 /* then the extra mailbox-defined keywords */
1803 for(i
= 0; stream_to_user_flag_name(state
->mail_stream
, i
); i
++){
1806 q
= stream_to_user_flag_name(state
->mail_stream
, i
);
1808 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1809 if((kw
->nick
&& !strucmp(kw
->nick
, q
)) || (kw
->kw
&& !strucmp(kw
->kw
, q
)))
1814 if(!kw
&& !(q
&& !strucmp(FORWARDED_FLAG
, q
))){
1815 fp
->name
= cpystr(q
);
1816 fp
->keyword
= cpystr(q
);
1817 fp
->help
= h_flag_user_flag
;
1818 fp
->flag
= F_KEYWORD
;
1825 flag_screen
.flag_table
= &ftbl
;
1826 flag_screen
.explanation
= screen_text
;
1828 if(MCMD_ISAGG(aopt
)){
1829 if(!pseudo_selected(ps_global
->mail_stream
, msgmap
)){
1830 free_flag_table(&ftbl
);
1834 exp
= flag_screen_text2
;
1835 for(fp
= ftbl
; fp
->name
; fp
++){
1836 fp
->set
= CMD_FLAG_UNKN
; /* set to unknown */
1840 else if(state
->mail_stream
1841 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
1842 && rawno
<= state
->mail_stream
->nmsgs
1843 && (mc
= mail_elt(state
->mail_stream
, rawno
))){
1844 exp
= flag_screen_text1
;
1845 for(fp
= &ftbl
[0]; fp
->name
; fp
++){
1847 if(fp
->flag
== F_KEYWORD
){
1848 /* see if this keyword is defined for this message */
1849 fp
->set
= CMD_FLAG_CLEAR
;
1850 if(user_flag_is_set(state
->mail_stream
,
1851 rawno
, fp
->keyword
))
1852 fp
->set
= CMD_FLAG_SET
;
1854 else if(fp
->flag
== F_FWD
){
1855 /* see if forwarded keyword is defined for this message */
1856 fp
->set
= CMD_FLAG_CLEAR
;
1857 if(user_flag_is_set(state
->mail_stream
,
1858 rawno
, FORWARDED_FLAG
))
1859 fp
->set
= CMD_FLAG_SET
;
1861 else if(fp
->flag
!= F_COMMENT
)
1862 fp
->set
= ((fp
->flag
== F_SEEN
&& !mc
->seen
)
1863 || (fp
->flag
== F_DEL
&& mc
->deleted
)
1864 || (fp
->flag
== F_FLAG
&& mc
->flagged
)
1865 || (fp
->flag
== F_ANS
&& mc
->answered
))
1866 ? CMD_FLAG_SET
: CMD_FLAG_CLEAR
;
1870 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
1871 _("Error accessing message data"));
1872 free_flag_table(&ftbl
);
1876 if(directly_to_maint_screen
)
1877 goto the_maint_screen
;
1880 if (mswin_usedialog ()) {
1881 if (!os_flagmsgdialog (&ftbl
[0])){
1882 free_flag_table(&ftbl
);
1889 int use_maint_screen
;
1890 int keyword_shortcut
= 0;
1892 use_maint_screen
= F_ON(F_FLAG_SCREEN_DFLT
, ps_global
);
1894 if(!use_maint_screen
){
1896 * We're going to call cmd_flag_prompt(). We need
1897 * to decide whether or not to offer the keyword setting
1898 * shortcut. We'll offer it if the user has the feature
1899 * enabled AND there are some possible keywords that could
1902 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
)){
1903 for(fp
= &ftbl
[0]; fp
->name
&& !keyword_shortcut
; fp
++){
1904 if(fp
->flag
== F_KEYWORD
){
1908 first_char
= (fp
->name
&& fp
->name
[0])
1910 if(isascii(first_char
) && isupper(first_char
))
1911 first_char
= tolower((unsigned char) first_char
);
1913 for(tp
=flag_text_opt
; tp
->ch
!= -1; tp
++)
1914 if(tp
->ch
== first_char
)
1923 use_maint_screen
= !cmd_flag_prompt(state
, &flag_screen
,
1928 if(use_maint_screen
){
1929 for(p
= &screen_text
[0]; *exp
; p
++, exp
++)
1934 directly_to_maint_screen
= flag_maintenance_screen(state
, &flag_screen
);
1938 /* reaquire the elt pointer */
1939 mc
= (state
->mail_stream
1940 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
1941 && rawno
<= state
->mail_stream
->nmsgs
)
1942 ? mail_elt(state
->mail_stream
, rawno
) : NULL
;
1944 for(fp
= ftbl
; mc
&& fp
->name
; fp
++){
1948 if((!MCMD_ISAGG(aopt
) && fp
->set
!= !mc
->seen
)
1949 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
1957 unflagged
= F_UNSEEN
;
1964 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->answered
)
1965 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
1966 flagit
= "\\ANSWERED";
1969 unflagged
= F_UNANS
;
1980 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->deleted
)
1981 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
1982 flagit
= "\\DELETED";
1985 unflagged
= F_UNDEL
;
1996 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->flagged
)
1997 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
1998 flagit
= "\\FLAGGED";
2001 unflagged
= F_UNFLAG
;
2012 if(!MCMD_ISAGG(aopt
)){
2013 /* see if forwarded is defined for this message */
2014 is_set
= CMD_FLAG_CLEAR
;
2015 if(user_flag_is_set(state
->mail_stream
,
2016 mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
2018 is_set
= CMD_FLAG_SET
;
2021 if((!MCMD_ISAGG(aopt
) && fp
->set
!= is_set
)
2022 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2023 flagit
= FORWARDED_FLAG
;
2026 unflagged
= F_UNFWD
;
2037 if(!MCMD_ISAGG(aopt
)){
2038 /* see if this keyword is defined for this message */
2039 is_set
= CMD_FLAG_CLEAR
;
2040 if(user_flag_is_set(state
->mail_stream
,
2041 mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
2043 is_set
= CMD_FLAG_SET
;
2046 if((!MCMD_ISAGG(aopt
) && fp
->set
!= is_set
)
2047 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2048 flagit
= fp
->keyword
;
2049 keyword_array
[0] = fp
->keyword
;
2050 keyword_array
[1] = NULL
;
2053 unflagged
= F_UNKEYWORD
;
2057 unflagged
= F_KEYWORD
;
2069 && (seq
= currentf_sequence(state
->mail_stream
, msgmap
,
2070 unflagged
, &flagged
, unflagged
& F_DEL
,
2071 (fp
->flag
== F_KEYWORD
2072 && unflagged
== F_KEYWORD
)
2073 ? keyword_array
: NULL
,
2074 (fp
->flag
== F_KEYWORD
2075 && unflagged
== F_UNKEYWORD
)
2076 ? keyword_array
: NULL
))){
2078 * For user keywords, we may have to create the flag in
2079 * the folder if it doesn't already exist and we are setting
2080 * it (as opposed to clearing it). Mail_flag will
2081 * do that for us, but it's failure isn't very friendly
2082 * error-wise. So we try to make it a little smoother.
2084 if(!(fp
->flag
== F_KEYWORD
|| fp
->flag
== F_FWD
) || !fp
->set
2085 || ((i
=user_flag_index(state
->mail_stream
, flagit
)) >= 0
2087 mail_flag(state
->mail_stream
, seq
, flagit
, flags
);
2089 /* trouble, see if we can add the user flag */
2090 if(state
->mail_stream
->kwd_create
)
2091 mail_flag(state
->mail_stream
, seq
, flagit
, flags
);
2095 if(some_user_flags_defined(state
->mail_stream
))
2096 q_status_message(SM_ORDER
, 3, 4,
2097 _("No more keywords allowed in this folder!"));
2098 else if(fp
->flag
== F_FWD
)
2099 q_status_message(SM_ORDER
, 3, 4,
2100 _("Cannot add keywords for this folder so cannot set Forwarded flag"));
2102 q_status_message(SM_ORDER
, 3, 4,
2103 _("Cannot add keywords for this folder"));
2107 fs_give((void **) &seq
);
2108 if(flagged
&& !trouble
){
2109 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%slagged%s%s%s%s%s message%s%s \"%s\"",
2110 (fp
->set
) ? "F" : "Unf",
2111 MCMD_ISAGG(aopt
) ? " " : "",
2112 MCMD_ISAGG(aopt
) ? long2string(flagged
) : "",
2113 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2115 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2116 ? comatose(mn_total_cur(msgmap
)) : "",
2117 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2119 MCMD_ISAGG(aopt
) ? plural(flagged
) : " ",
2120 MCMD_ISAGG(aopt
) ? "" : long2string(mn_get_cur(msgmap
)),
2122 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2123 q_status_message(SM_ORDER
, 0, 2, answer
= tmp_20k_buf
);
2129 free_flag_table(&ftbl
);
2131 if(directly_to_maint_screen
)
2134 if(MCMD_ISAGG(aopt
))
2135 restore_selected(msgmap
);
2138 q_status_message(SM_ORDER
, 0, 2, _("No flags changed."));
2144 /*----------------------------------------------------------------------
2145 Offer concise status line flag prompt
2147 Args: state -- Various satate info
2148 flags -- flags to offer setting
2150 Result: TRUE if flag to set specified in flags struct or FALSE otw
2154 cmd_flag_prompt(struct pine
*state
, struct flag_screen
*flags
, int allow_keyword_shortcuts
)
2156 int r
, setflag
= 1, first_char
;
2157 struct flag_table
*fp
;
2159 char *ftext
, *ftext_not
;
2160 static char *flag_text
=
2161 N_("Flag New, Deleted, Answered, Forwarded or Important ? ");
2162 static char *flag_text_ak
=
2163 N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? ");
2164 static char *flag_text_not
=
2165 N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? ");
2166 static char *flag_text_ak_not
=
2167 N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? ");
2169 if(allow_keyword_shortcuts
){
2171 ESCKEY_S
*dp
, *sp
, *tp
;
2173 for(sp
=flag_text_opt
; sp
->ch
!= -1; sp
++)
2176 for(fp
=(flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++)
2177 if(fp
->flag
== F_KEYWORD
)
2180 /* set up an ESCKEY_S list which includes invisible keys for keywords */
2181 ek
= (ESCKEY_S
*) fs_get((cnt
+ 1) * sizeof(*ek
));
2182 memset(ek
, 0, (cnt
+1) * sizeof(*ek
));
2183 for(dp
=ek
, sp
=flag_text_opt
; sp
->ch
!= -1; sp
++, dp
++)
2186 for(fp
=(flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++){
2187 if(fp
->flag
== F_KEYWORD
){
2188 first_char
= (fp
->name
&& fp
->name
[0]) ? fp
->name
[0] : -2;
2189 if(isascii(first_char
) && isupper(first_char
))
2190 first_char
= tolower((unsigned char) first_char
);
2193 * Check to see if an earlier keyword in the list, or one of
2194 * the builtin system letters already uses this character.
2195 * If so, the first one wins.
2197 for(tp
=ek
; tp
->ch
!= 0; tp
++)
2198 if(tp
->ch
== first_char
)
2202 continue; /* skip it, already used that char */
2204 dp
->ch
= first_char
;
2205 dp
->rval
= first_char
;
2213 ftext
= _(flag_text_ak
);
2214 ftext_not
= _(flag_text_ak_not
);
2218 ftext
= _(flag_text
);
2219 ftext_not
= _(flag_text_not
);
2223 r
= radio_buttons(setflag
? ftext
: ftext_not
,
2224 -FOOTER_ROWS(state
), ek
, '*', SEQ_EXCEPTION
-1,
2225 NO_HELP
, RB_NORM
| RB_SEQ_SENSITIVE
);
2227 * It is SEQ_EXCEPTION-1 just so that it is some value that isn't
2228 * being used otherwise. The keywords use up all the possible
2229 * letters, so a negative number is good, but it has to be different
2230 * from other negative return values.
2232 if(r
== SEQ_EXCEPTION
-1) /* ol'cancelrooney */
2234 else if(r
== 10) /* return and goto flag screen */
2236 else if(r
== '!') /* flip intention */
2242 for(fp
= (flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++){
2243 if(r
== 'n' || r
== '*' || r
== 'd' || r
== 'a' || r
== 'f'){
2244 if((r
== 'n' && fp
->flag
== F_SEEN
)
2245 || (r
== '*' && fp
->flag
== F_FLAG
)
2246 || (r
== 'd' && fp
->flag
== F_DEL
)
2247 || (r
== 'f' && fp
->flag
== F_FWD
)
2248 || (r
== 'a' && fp
->flag
== F_ANS
)){
2249 fp
->set
= setflag
? CMD_FLAG_SET
: CMD_FLAG_CLEAR
;
2253 else if(allow_keyword_shortcuts
&& fp
->flag
== F_KEYWORD
){
2254 first_char
= (fp
->name
&& fp
->name
[0]) ? fp
->name
[0] : -2;
2255 if(isascii(first_char
) && isupper(first_char
))
2256 first_char
= tolower((unsigned char) first_char
);
2258 if(r
== first_char
){
2259 fp
->set
= setflag
? CMD_FLAG_SET
: CMD_FLAG_CLEAR
;
2265 if(ek
!= flag_text_opt
)
2266 fs_give((void **) &ek
);
2273 * (*ft) is an array of flag_table entries.
2276 free_flag_table(struct flag_table
**ft
)
2278 struct flag_table
*fp
;
2281 for(fp
= (*ft
); fp
->name
; fp
++){
2283 fs_give((void **) &fp
->name
);
2286 fs_give((void **) &fp
->keyword
);
2289 fs_give((void **) &fp
->comment
);
2292 fs_give((void **) ft
);
2297 /*----------------------------------------------------------------------
2298 Execute REPLY message command
2300 Args: state -- Various satate info
2301 msgmap -- map of c-client to local message numbers
2303 Result: reply sent or not
2307 cmd_reply(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
2311 if(any_messages(msgmap
, NULL
, "to Reply to")){
2312 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
2315 rv
= reply(state
, NULL
);
2317 if(MCMD_ISAGG(aopt
))
2318 restore_selected(msgmap
);
2320 state
->mangled_screen
= 1;
2327 /*----------------------------------------------------------------------
2328 Execute FORWARD message command
2330 Args: state -- Various satate info
2331 msgmap -- map of c-client to local message numbers
2333 Result: selected message[s] forwarded or not
2337 cmd_forward(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
2341 if(any_messages(msgmap
, NULL
, "to Forward")){
2342 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
2345 rv
= forward(state
, NULL
);
2347 if(MCMD_ISAGG(aopt
))
2348 restore_selected(msgmap
);
2350 state
->mangled_screen
= 1;
2357 /*----------------------------------------------------------------------
2358 Execute BOUNCE message command
2360 Args: state -- Various satate info
2361 msgmap -- map of c-client to local message numbers
2362 aopt -- aggregate options
2364 Result: selected message[s] bounced or not
2368 cmd_bounce(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
2371 ACTION_S
*role
= NULL
;
2373 if(any_messages(msgmap
, NULL
, "to Bounce")){
2374 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
2377 if(MCMD_ISAGG(aopt
)){ /* check for possible role */
2381 if(nonempty_patterns(ROLE_DO_ROLES
, &pstate
) && first_pattern(&pstate
)){
2382 static ESCKEY_S yesno_opts
[] = {
2383 {'y', 'y', "Y", N_("Yes")},
2384 {'n', 'n', "N", N_("No")},
2388 action
= radio_buttons(_("Bounce messages using a role? "),
2389 -FOOTER_ROWS(state
), yesno_opts
,
2390 'y', 'x', h_role_compose
, RB_NORM
);
2392 void (*prev_screen
)(struct pine
*) = NULL
, (*redraw
)(void) = NULL
;
2394 redraw
= state
->redrawer
;
2395 state
->redrawer
= NULL
;
2396 prev_screen
= state
->prev_screen
;
2398 state
->next_screen
= SCREEN_FUN_NULL
;
2400 if(role_select_screen(state
, &role
, MC_BOUNCE
) < 0)
2401 cmd_cancelled(_("Bounce"));
2404 role
= combine_inherited_role(role
);
2406 role
= (ACTION_S
*) fs_get(sizeof(*role
));
2407 memset((void *) role
, 0, sizeof(*role
));
2408 role
->nick
= cpystr("Default Role");
2415 state
->next_screen
= prev_screen
;
2416 state
->redrawer
= redraw
;
2417 state
->mangled_screen
= 1;
2422 rv
= bounce(state
, role
);
2427 if(MCMD_ISAGG(aopt
))
2428 restore_selected(msgmap
);
2430 state
->mangled_footer
= 1;
2437 /*----------------------------------------------------------------------
2438 Execute save message command: prompt for folder and call function to save
2440 Args: screen_line -- Line on the screen to prompt on
2441 message -- The MESSAGECACHE entry of message to save
2443 Result: The folder lister can be called to make selection; mangled screen set
2445 This does the prompting for the folder name to save to, possibly calling
2446 up the folder display for selection of folder by user.
2449 cmd_save(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int aopt
, CmdWhere in_index
)
2451 char newfolder
[MAILTMPLEN
], nmsgs
[32], *nick
;
2452 int we_cancel
= 0, rv
= 0, save_flags
;
2454 CONTEXT_S
*cntxt
= NULL
;
2456 SaveDel del
= DontAsk
;
2457 SavePreserveOrder pre
= DontAskPreserve
;
2459 dprint((4, "\n - saving message -\n"));
2461 if(MCMD_ISAGG(aopt
) && !pseudo_selected(stream
, msgmap
))
2464 state
->ugly_consider_advancing_bit
= 0;
2465 if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM
, state
)
2466 && msgno_any_deletedparts(stream
, msgmap
)
2467 && want_to(_("Saved copy will NOT include entire message! Continue"),
2468 'y', 'n', NO_HELP
, WT_FLUSH_IN
| WT_SEQ_SENSITIVE
) != 'y'){
2469 restore_selected(msgmap
);
2470 cmd_cancelled("Save message");
2474 raw
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
2476 if(mn_total_cur(msgmap
) <= 1L){
2477 snprintf(nmsgs
, sizeof(nmsgs
), "Msg #%ld ", mn_get_cur(msgmap
));
2478 nmsgs
[sizeof(nmsgs
)-1] = '\0';
2479 e
= pine_mail_fetchstructure(stream
, raw
, NULL
);
2481 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
2482 _("Can't save message. Error accessing folder"));
2483 restore_selected(msgmap
);
2488 snprintf(nmsgs
, sizeof(nmsgs
), "%s msgs ", comatose(mn_total_cur(msgmap
)));
2489 nmsgs
[sizeof(nmsgs
)-1] = '\0';
2491 /* e is just used to get a default save folder from the first msg */
2492 e
= pine_mail_fetchstructure(stream
,
2493 mn_m2raw(msgmap
, mn_first_cur(msgmap
)),
2497 del
= (!READONLY_FOLDER(stream
) && F_OFF(F_SAVE_WONT_DELETE
, ps_global
))
2499 if(mn_total_cur(msgmap
) > 1L)
2500 pre
= F_OFF(F_AGG_SEQ_COPY
, ps_global
) ? Preserve
: NoPreserve
;
2502 pre
= DontAskPreserve
;
2504 if(save_prompt(state
, &cntxt
, newfolder
, sizeof(newfolder
), nmsgs
, e
,
2505 raw
, NULL
, &del
, &pre
)){
2507 if(ps_global
&& ps_global
->ttyo
){
2508 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
2509 ps_global
->mangled_footer
= 1;
2512 save_flags
= SV_FIX_DELS
;
2513 if(pre
== RetPreserve
)
2514 save_flags
|= SV_PRESERVE
;
2516 save_flags
|= SV_DELETE
;
2517 if(ps_global
->context_list
== cntxt
&& !strucmp(newfolder
, ps_global
->inbox_name
))
2518 save_flags
|= SV_INBOXWOCNTXT
;
2520 we_cancel
= busy_cue(_("Saving"), NULL
, 1);
2521 i
= save(state
, stream
, cntxt
, newfolder
, msgmap
, save_flags
);
2525 if(i
== mn_total_cur(msgmap
)){
2527 if(mn_total_cur(msgmap
) <= 1L){
2528 int need
, avail
= ps_global
->ttyo
->screen_cols
- 2;
2529 int lennick
, lenfldr
;
2532 && ps_global
->context_list
->next
2533 && context_isambig(newfolder
)){
2534 lennick
= MIN(strlen(cntxt
->nickname
), 500);
2535 lenfldr
= MIN(strlen(newfolder
), 500);
2536 need
= 27 + strlen(long2string(mn_get_cur(msgmap
))) +
2540 need
-= MIN(lennick
-10, need
-avail
);
2541 lennick
-= MIN(lennick
-10, need
-avail
);
2544 if(need
> avail
&& lenfldr
> 10)
2545 lenfldr
-= MIN(lenfldr
-10, need
-avail
);
2548 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
2549 "Message %s copied to \"%s\" in <%s>",
2550 long2string(mn_get_cur(msgmap
)),
2551 short_str(newfolder
, (char *)(tmp_20k_buf
+1000), 1000,
2553 short_str(cntxt
->nickname
,
2554 (char *)(tmp_20k_buf
+2000), 1000,
2556 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2558 else if((nick
=folder_is_target_of_nick(newfolder
, cntxt
)) != NULL
){
2559 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
2560 "Message %s copied to \"%s\"",
2561 long2string(mn_get_cur(msgmap
)),
2563 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2566 char *f
= " folder";
2568 lenfldr
= MIN(strlen(newfolder
), 500);
2569 need
= 28 + strlen(long2string(mn_get_cur(msgmap
))) +
2574 if(need
> avail
&& lenfldr
> 10)
2575 lenfldr
-= MIN(lenfldr
-10, need
-avail
);
2578 snprintf(tmp_20k_buf
,SIZEOF_20KBUF
,
2579 "Message %s copied to%s \"%s\"",
2580 long2string(mn_get_cur(msgmap
)), f
,
2581 short_str(newfolder
, (char *)(tmp_20k_buf
+1000), 1000,
2583 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2587 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%s messages saved",
2588 comatose(mn_total_cur(msgmap
)));
2589 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2593 strncat(tmp_20k_buf
, " and deleted", SIZEOF_20KBUF
-strlen(tmp_20k_buf
)-1);
2594 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2597 q_status_message(SM_ORDER
, 0, 3, tmp_20k_buf
);
2599 if(!MCMD_ISAGG(aopt
) && F_ON(F_SAVE_ADVANCES
, state
)){
2600 if(sp_new_mail_count(stream
))
2601 process_filter_patterns(stream
, msgmap
,
2602 sp_new_mail_count(stream
));
2604 mn_inc_cur(stream
, msgmap
,
2605 (in_index
== View
&& THREADING()
2606 && sp_viewing_a_thread(stream
))
2608 : (in_index
== View
)
2609 ? MH_ANYTHD
: MH_NONE
);
2612 state
->ugly_consider_advancing_bit
= 1;
2616 if(MCMD_ISAGG(aopt
)) /* straighten out fakes */
2617 restore_selected(msgmap
);
2620 update_titlebar_status(); /* make sure they see change */
2627 role_compose(struct pine
*state
)
2631 if(F_ON(F_ALT_ROLE_MENU
, state
) && mn_get_total(state
->msgmap
) > 0L){
2634 if(nonempty_patterns(ROLE_DO_ROLES
, &pstate
) && first_pattern(&pstate
)){
2635 action
= radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
2636 -FOOTER_ROWS(state
), choose_action
,
2637 'c', 'x', h_role_compose
, RB_NORM
);
2640 q_status_message(SM_ORDER
, 0, 3,
2641 _("No roles available. Use Setup/Rules to add roles."));
2648 if(action
== 'c' || action
== 'r' || action
== 'f' || action
== 'b'){
2649 ACTION_S
*role
= NULL
;
2650 void (*prev_screen
)(struct pine
*) = NULL
, (*redraw
)(void) = NULL
;
2652 redraw
= state
->redrawer
;
2653 state
->redrawer
= NULL
;
2654 prev_screen
= state
->prev_screen
;
2656 state
->next_screen
= SCREEN_FUN_NULL
;
2659 if(role_select_screen(state
, &role
,
2660 action
== 'f' ? MC_FORWARD
:
2661 action
== 'r' ? MC_REPLY
:
2662 action
== 'b' ? MC_BOUNCE
:
2663 action
== 'c' ? MC_COMPOSE
: 0) < 0){
2664 cmd_cancelled(action
== 'f' ? _("Forward") :
2665 action
== 'r' ? _("Reply") :
2666 action
== 'c' ? _("Composition") : _("Bounce"));
2667 state
->next_screen
= prev_screen
;
2668 state
->redrawer
= redraw
;
2669 state
->mangled_screen
= 1;
2673 * If default role was selected (NULL) we need to make
2674 * up a role which won't do anything, but will cause
2675 * compose_mail to think there's already a role so that
2676 * it won't try to confirm the default.
2679 role
= combine_inherited_role(role
);
2681 role
= (ACTION_S
*) fs_get(sizeof(*role
));
2682 memset((void *) role
, 0, sizeof(*role
));
2683 role
->nick
= cpystr("Default Role");
2686 state
->redrawer
= NULL
;
2689 compose_mail(NULL
, NULL
, role
, NULL
, NULL
);
2693 (void) reply(state
, role
);
2697 (void) forward(state
, role
);
2701 (void) bounce(state
, role
);
2708 state
->next_screen
= prev_screen
;
2709 state
->redrawer
= redraw
;
2710 state
->mangled_screen
= 1;
2716 /*----------------------------------------------------------------------
2717 Do the dirty work of prompting the user for a folder name
2720 nfldr should be a buffer at least MAILTMPLEN long
2721 dela -- a pointer to a SaveDel. If it is
2722 DontAsk on input, don't offer Delete prompt
2723 Del on input, offer Delete command with default of Delete
2725 RetDel and RetNoDel are return values
2732 save_prompt(struct pine
*state
, CONTEXT_S
**cntxt
, char *nfldr
, size_t len_nfldr
,
2733 char *nmsgs
, ENVELOPE
*env
, long int rawmsgno
, char *section
,
2734 SaveDel
*dela
, SavePreserveOrder
*prea
)
2736 int rc
, ku
= -1, n
, flags
, last_rc
= 0, saveable_count
= 0, done
= 0;
2737 int delindex
, preindex
, r
;
2738 char prompt
[6*MAX_SCREEN_COLS
+1], *p
, expanded
[MAILTMPLEN
];
2739 char *buf
= tmp_20k_buf
;
2743 SaveDel del
= DontAsk
;
2744 SavePreserveOrder pre
= DontAskPreserve
;
2745 char *deltext
= NULL
;
2746 static HISTORY_S
*history
= NULL
;
2751 alpine_panic("no context ptr in save_prompt");
2753 init_hist(&history
, HISTSIZE
);
2755 if(!(folder
= save_get_default(state
, env
, rawmsgno
, section
, cntxt
)))
2756 return(0); /* message expunged! */
2758 /* how many context's can be saved to... */
2759 for(tc
= state
->context_list
; tc
; tc
= tc
->next
)
2763 /* set up extra command option keys */
2765 ekey
[rc
].ch
= ctrl('T');
2767 ekey
[rc
].name
= "^T";
2768 /* TRANSLATORS: command means go to Folders list */
2769 ekey
[rc
++].label
= N_("To Fldrs");
2771 if(saveable_count
> 1){
2772 ekey
[rc
].ch
= ctrl('P');
2774 ekey
[rc
].name
= "^P";
2775 ekey
[rc
++].label
= N_("Prev Collection");
2777 ekey
[rc
].ch
= ctrl('N');
2779 ekey
[rc
].name
= "^N";
2780 ekey
[rc
++].label
= N_("Next Collection");
2783 if(F_ON(F_ENABLE_TAB_COMPLETE
, ps_global
)){
2786 ekey
[rc
].name
= "TAB";
2787 /* TRANSLATORS: command asks alpine to complete the name when tab is typed */
2788 ekey
[rc
++].label
= N_("Complete");
2791 if(F_ON(F_ENABLE_SUB_LISTS
, ps_global
)){
2792 ekey
[rc
].ch
= ctrl('X');
2794 ekey
[rc
].name
= "^X";
2795 /* TRANSLATORS: list all the matches */
2796 ekey
[rc
++].label
= N_("ListMatches");
2799 if(dela
&& (*dela
== NoDel
|| *dela
== Del
)){
2800 ekey
[rc
].ch
= ctrl('R');
2802 ekey
[rc
].name
= "^R";
2807 if(prea
&& (*prea
== NoPreserve
|| *prea
== Preserve
)){
2808 ekey
[rc
].ch
= ctrl('W');
2810 ekey
[rc
].name
= "^W";
2815 if(saveable_count
> 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
2816 ekey
[rc
].ch
= KEY_UP
;
2819 ekey
[rc
++].label
= "";
2821 ekey
[rc
].ch
= KEY_DOWN
;
2824 ekey
[rc
++].label
= "";
2826 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
2827 ekey
[rc
].ch
= KEY_UP
;
2831 ekey
[rc
++].label
= "";
2833 ekey
[rc
].ch
= KEY_DOWN
;
2836 ekey
[rc
++].label
= "";
2844 /* only show collection number if more than one available */
2845 if(ps_global
->context_list
->next
)
2846 snprintf(prompt
, sizeof(prompt
), "SAVE%s %sto folder in <%s> [%s] : ",
2847 deltext
? deltext
: "",
2849 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2850 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2852 snprintf(prompt
, sizeof(prompt
), "SAVE%s %sto folder [%s] : ",
2853 deltext
? deltext
: "",
2854 nmsgs
, strsquish(buf
, SIZEOF_20KBUF
, folder
, 40));
2856 prompt
[sizeof(prompt
)-1] = '\0';
2859 * If the prompt won't fit, try removing deltext.
2861 if(state
->ttyo
->screen_cols
< strlen(prompt
) + MIN_OPT_ENT_WIDTH
&& deltext
){
2862 if(ps_global
->context_list
->next
)
2863 snprintf(prompt
, sizeof(prompt
), "SAVE %sto folder in <%s> [%s] : ",
2865 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2866 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2868 snprintf(prompt
, sizeof(prompt
), "SAVE %sto folder [%s] : ",
2869 nmsgs
, strsquish(buf
, SIZEOF_20KBUF
, folder
, 40));
2871 prompt
[sizeof(prompt
)-1] = '\0';
2875 * If the prompt still won't fit, remove the extra info contained
2878 if(state
->ttyo
->screen_cols
< strlen(prompt
) + MIN_OPT_ENT_WIDTH
&& *nmsgs
){
2879 if(ps_global
->context_list
->next
)
2880 snprintf(prompt
, sizeof(prompt
), "SAVE to folder in <%s> [%s] : ",
2881 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2882 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2884 snprintf(prompt
, sizeof(prompt
), "SAVE to folder [%s] : ",
2885 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2887 prompt
[sizeof(prompt
)-1] = '\0';
2891 ekey
[delindex
].label
= (del
== NoDel
) ? "Delete" : "No Delete";
2893 if(pre
!= DontAskPreserve
)
2894 ekey
[preindex
].label
= (pre
== NoPreserve
) ? "Preserve Order" : "Any Order";
2897 if(items_in_hist(history
) > 1){
2898 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
2899 ekey
[ku
].label
= HISTORY_KEYLABEL
;
2900 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
2901 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
2905 ekey
[ku
].label
= "";
2906 ekey
[ku
+1].name
= "";
2907 ekey
[ku
+1].label
= "";
2911 flags
= OE_APPEND_CURRENT
| OE_SEQ_SENSITIVE
;
2912 rc
= optionally_enter(nfldr
, -FOOTER_ROWS(state
), 0, len_nfldr
,
2913 prompt
, ekey
, help
, &flags
);
2917 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
2918 _("Error reading folder name"));
2923 removing_trailing_white_space(nfldr
);
2924 removing_leading_white_space(nfldr
);
2926 if(*nfldr
|| *folder
){
2927 char *p
, *name
, *fullname
= NULL
;
2928 int exists
, breakout
= FALSE
;
2931 strncpy(nfldr
, folder
, len_nfldr
-1);
2932 nfldr
[len_nfldr
-1] = '\0';
2935 save_hist(history
, nfldr
, 0, (void *) *cntxt
);
2937 if(!(name
= folder_is_nick(nfldr
, FOLDERS(*cntxt
), 0)))
2940 if(update_folder_spec(expanded
, sizeof(expanded
), name
)){
2941 strncpy(name
= nfldr
, expanded
, len_nfldr
-1);
2942 nfldr
[len_nfldr
-1] = '\0';
2945 exists
= folder_name_exists(*cntxt
, name
, &fullname
);
2947 if(exists
== FEX_ERROR
){
2948 q_status_message1(SM_ORDER
, 0, 3,
2949 _("Problem accessing folder \"%s\""),
2955 strncpy(name
= nfldr
, fullname
, len_nfldr
-1);
2956 nfldr
[len_nfldr
-1] = '\0';
2957 fs_give((void **) &fullname
);
2961 if(exists
& FEX_ISFILE
){
2964 else if((exists
& FEX_ISDIR
)){
2965 char tmp
[MAILTMPLEN
];
2969 CONTEXT_S
*fake_context
;
2972 strncpy(tmp
, name
, sizeof(tmp
));
2973 tmp
[sizeof(tmp
)-2-1] = '\0';
2974 if(tmp
[(l
= strlen(tmp
)) - 1] != tc
->dir
->delim
){
2975 if(l
< sizeof(tmp
)){
2976 tmp
[l
] = tc
->dir
->delim
;
2977 strncpy(&tmp
[l
+1], "[]", sizeof(tmp
)-(l
+1));
2981 strncat(tmp
, "[]", sizeof(tmp
)-strlen(tmp
)-1);
2983 tmp
[sizeof(tmp
)-1] = '\0';
2985 fake_context
= new_context(tmp
, 0);
2987 done
= display_folder_list(&fake_context
, nfldr
,
2988 1, folders_for_save
);
2989 free_context(&fake_context
);
2991 else if(tc
->dir
->delim
2992 && (p
= strrindex(name
, tc
->dir
->delim
))
2994 done
= display_folder_list(cntxt
, nfldr
,
2995 1, folders_for_save
);
2997 q_status_message1(SM_ORDER
, 3, 3,
2998 _("\"%s\" is a directory"), name
);
3000 && !((p
=strrindex(name
, tc
->dir
->delim
)) && *(p
+1) == '\0')){
3001 strncpy(tmp
, name
, sizeof(tmp
));
3002 tmp
[sizeof(tmp
)-1] = '\0';
3003 snprintf(nfldr
, len_nfldr
, "%s%c", tmp
, tc
->dir
->delim
);
3007 else{ /* Doesn't exist, create! */
3008 if((fullname
= folder_as_breakout(*cntxt
, name
)) != NULL
){
3009 strncpy(name
= nfldr
, fullname
, len_nfldr
-1);
3010 nfldr
[len_nfldr
-1] = '\0';
3011 fs_give((void **) &fullname
);
3014 switch(create_for_save(*cntxt
, name
)){
3015 case 1 : /* success */
3018 case 0 : /* error */
3019 case -1 : /* declined */
3028 /* else fall thru like they cancelled */
3031 cmd_cancelled("Save message");
3036 r
= display_folder_list(cntxt
, nfldr
, 0, folders_for_save
);
3044 helper(h_save
, _("HELP FOR SAVE"), HLPD_SIMPLE
);
3045 ps_global
->mangled_screen
= 1;
3048 case 4 : /* redraw */
3051 case 10 : /* previous collection */
3052 for(tc
= (*cntxt
)->prev
; tc
; tc
= tc
->prev
)
3059 for(tc2
= (tc
= (*cntxt
))->next
; tc2
; tc2
= tc2
->next
)
3067 case 11 : /* next collection */
3071 if(((*cntxt
) = (*cntxt
)->next
) == NULL
)
3072 (*cntxt
) = ps_global
->context_list
;
3073 while(NEWS_TEST(*cntxt
) && (*cntxt
) != tc
);
3076 case 12 : /* file name completion */
3077 if(!folder_complete(*cntxt
, nfldr
, len_nfldr
, &n
)){
3078 if(n
&& last_rc
== 12 && !(flags
& OE_USER_MODIFIED
)){
3079 r
= display_folder_list(cntxt
, nfldr
, 1, folders_for_save
);
3081 done
++; /* bingo! */
3083 rc
= 0; /* burn last_rc */
3091 case 14 : /* file name completion */
3092 r
= display_folder_list(cntxt
, nfldr
, 2, folders_for_save
);
3094 done
++; /* bingo! */
3096 rc
= 0; /* burn last_rc */
3100 case 15 : /* Delete / No Delete */
3101 del
= (del
== NoDel
) ? Del
: NoDel
;
3102 deltext
= (del
== NoDel
) ? " (no delete)" : " (and delete)";
3105 case 16 : /* Preserve Order or not */
3106 pre
= (pre
== NoPreserve
) ? Preserve
: NoPreserve
;
3110 if((p
= get_prev_hist(history
, nfldr
, 0, (void *) *cntxt
)) != NULL
){
3111 strncpy(nfldr
, p
, len_nfldr
);
3112 nfldr
[len_nfldr
-1] = '\0';
3113 if(history
->hist
[history
->curindex
])
3114 *cntxt
= (CONTEXT_S
*) history
->hist
[history
->curindex
]->cntxt
;
3122 if((p
= get_next_hist(history
, nfldr
, 0, (void *) *cntxt
)) != NULL
){
3123 strncpy(nfldr
, p
, len_nfldr
);
3124 nfldr
[len_nfldr
-1] = '\0';
3125 if(history
->hist
[history
->curindex
])
3126 *cntxt
= (CONTEXT_S
*) history
->hist
[history
->curindex
]->cntxt
;
3134 alpine_panic("Unhandled case");
3141 ps_global
->mangled_footer
= 1;
3147 strncpy(ps_global
->last_save_folder
, nfldr
, sizeof(ps_global
->last_save_folder
)-1);
3148 ps_global
->last_save_folder
[sizeof(ps_global
->last_save_folder
)-1] = '\0';
3150 ps_global
->last_save_context
= *cntxt
;
3153 strncpy(nfldr
, folder
, len_nfldr
-1);
3154 nfldr
[len_nfldr
-1] = '\0';
3157 /* nickname? Copy real name to nfldr */
3159 && context_isambig(nfldr
)
3160 && (p
= folder_is_nick(nfldr
, FOLDERS(*cntxt
), 0))){
3161 strncpy(nfldr
, p
, len_nfldr
-1);
3162 nfldr
[len_nfldr
-1] = '\0';
3165 if(dela
&& (*dela
== NoDel
|| *dela
== Del
))
3166 *dela
= (del
== NoDel
) ? RetNoDel
: RetDel
;
3168 if(prea
&& (*prea
== NoPreserve
|| *prea
== Preserve
))
3169 *prea
= (pre
== NoPreserve
) ? RetNoPreserve
: RetPreserve
;
3175 /*----------------------------------------------------------------------
3176 Prompt user before implicitly creating a folder for saving
3178 Args: context - context to create folder in
3179 folder - folder name to create
3181 Result: 1 on proceed, -1 on decline, 0 on error
3185 create_for_save_prompt(CONTEXT_S
*context
, char *folder
, int sequence_sensitive
)
3187 if(context
&& ps_global
->context_list
->next
&& context_isambig(folder
)){
3188 if(context
->use
& CNTXT_INCMNG
){
3189 snprintf(tmp_20k_buf
,SIZEOF_20KBUF
,
3190 _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
3191 folder
, (strlen(folder
) > 15) ? "..." : "");
3192 q_status_message(SM_ORDER
, 3, 3, tmp_20k_buf
);
3193 return(0); /* error */
3196 snprintf(tmp_20k_buf
,SIZEOF_20KBUF
,
3197 _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
3198 folder
, (strlen(folder
) > 15) ? "..." : "",
3200 (strlen(context
->nickname
) > 15) ? "..." : "");
3203 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
3204 _("Folder \"%.40s%s\" doesn't exist. Create"),
3205 folder
, strlen(folder
) > 40 ? "..." : "");
3207 if(want_to(tmp_20k_buf
, 'y', 'n',
3208 NO_HELP
, (sequence_sensitive
) ? WT_SEQ_SENSITIVE
: WT_NORM
) != 'y'){
3209 cmd_cancelled("Save message");
3218 /*----------------------------------------------------------------------
3219 Expunge messages from current folder
3221 Args: state -- pointer to struct holding a bunch of pine state
3222 msgmap -- table mapping msg nums to c-client sequence nums
3223 qline -- screen line to ask questions on
3224 agg -- boolean indicating we're to operate on aggregate set
3229 cmd_expunge(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int agg
)
3231 long del_count
, prefilter_del_count
;
3232 int we_cancel
= 0, rv
= 0;
3233 char prompt
[MAX_SCREEN_COLS
+1];
3235 COLOR_PAIR
*lastc
= NULL
;
3237 dprint((2, "\n - expunge -\n"));
3241 sequence
= MCMD_ISAGG(agg
) ? selected_sequence(stream
, msgmap
, NULL
, 0) : NULL
;
3243 if(MCMD_ISAGG(agg
)){
3246 for(i
= 1L; i
<= stream
->nmsgs
; i
++){
3247 if((mc
= mail_elt(stream
, i
)) != NULL
3248 && mc
->sequence
&& mc
->deleted
)
3252 q_status_message(SM_ORDER
, 0, 4,
3253 _("No selected messages are deleted"));
3257 if(!any_messages(msgmap
, NULL
, "to Expunge"))
3261 if(IS_NEWS(stream
) && stream
->rdonly
){
3262 if(!MCMD_ISAGG(agg
))
3263 del_count
= count_flagged(stream
, F_DEL
);
3265 state
->mangled_footer
= 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3266 snprintf(prompt
, sizeof(prompt
), "Exclude %ld message%s from %.*s", del_count
,
3267 plural(del_count
), MAX_SCREEN_COLS
+1-40,
3268 pretty_fn(state
->cur_folder
));
3269 prompt
[sizeof(prompt
)-1] = '\0';
3270 if(F_ON(F_FULL_AUTO_EXPUNGE
, state
)
3271 || (F_ON(F_AUTO_EXPUNGE
, state
)
3272 && (state
->context_current
3273 && (state
->context_current
->use
& CNTXT_INCMNG
))
3274 && context_isambig(state
->cur_folder
))
3275 || want_to(prompt
, 'y', 0, NO_HELP
, WT_NORM
) == 'y'){
3277 if(F_ON(F_NEWS_CROSS_DELETE
, state
))
3278 cross_delete_crossposts(stream
);
3280 msgno_exclude_deleted(stream
, msgmap
, sequence
);
3281 clear_index_cache(stream
, 0);
3284 * This is kind of surprising at first. For most sort
3285 * orders, if the whole set is sorted, then any subset
3286 * is also sorted. Not so for threaded sorts.
3288 if(SORT_IS_THREADED(msgmap
))
3289 refresh_sort(stream
, msgmap
, SRT_NON
);
3291 state
->mangled_body
= 1;
3292 state
->mangled_header
= 1;
3293 q_status_message2(SM_ORDER
, 0, 4,
3294 "%s message%s excluded",
3295 long2string(del_count
),
3299 any_messages(NULL
, NULL
, "Excluded");
3302 any_messages(NULL
, "deleted", "to Exclude");
3306 else if(READONLY_FOLDER(stream
)){
3307 q_status_message(SM_ORDER
, 0, 4,
3308 _("Can't expunge. Folder is read-only"));
3312 if(!MCMD_ISAGG(agg
)){
3313 prefilter_del_count
= count_flagged(stream
, F_DEL
|F_NOFILT
);
3314 mail_expunge_prefilter(stream
, MI_NONE
);
3315 del_count
= count_flagged(stream
, F_DEL
|F_NOFILT
);
3320 unsigned char *fname
= folder_name_decoded((unsigned char *)state
->cur_folder
);
3321 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3322 snprintf(prompt
, sizeof(prompt
), "Expunge %ld message%s from %.*s", del_count
,
3323 plural(del_count
), MAX_SCREEN_COLS
+1-40,
3324 pretty_fn((char *) fname
));
3325 if(fname
) fs_give((void **)&fname
);
3326 prompt
[sizeof(prompt
)-1] = '\0';
3327 state
->mangled_footer
= 1;
3329 if(F_ON(F_FULL_AUTO_EXPUNGE
, state
)
3330 || (F_ON(F_AUTO_EXPUNGE
, state
)
3331 && ((!strucmp(state
->cur_folder
,state
->inbox_name
))
3332 || (state
->context_current
->use
& CNTXT_INCMNG
))
3333 && context_isambig(state
->cur_folder
))
3334 || (ret
=want_to(prompt
, 'y', 0, NO_HELP
, WT_NORM
)) == 'y')
3338 cmd_cancelled("Expunge");
3344 dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
3345 mn_get_total(msgmap
), mn_get_cur(msgmap
), del_count
));
3347 lastc
= pico_set_colors(state
->VAR_TITLE_FORE_COLOR
,
3348 state
->VAR_TITLE_BACK_COLOR
,
3351 PutLine0(0, 0, "**"); /* indicate delay */
3354 (void)pico_set_colorp(lastc
, PSC_NONE
);
3355 free_color_pair(&lastc
);
3358 MoveCursor(state
->ttyo
->screen_rows
-FOOTER_ROWS(state
), 0);
3361 we_cancel
= busy_cue(_("Expunging"), NULL
, 1);
3363 if(cmd_expunge_work(stream
, msgmap
, sequence
))
3364 state
->mangled_body
= 1;
3367 fs_give((void **)&sequence
);
3370 cancel_busy_cue((sp_expunge_count(stream
) > 0) ? 0 : -1);
3372 lastc
= pico_set_colors(state
->VAR_TITLE_FORE_COLOR
,
3373 state
->VAR_TITLE_BACK_COLOR
,
3375 PutLine0(0, 0, " "); /* indicate delay's over */
3378 (void)pico_set_colorp(lastc
, PSC_NONE
);
3379 free_color_pair(&lastc
);
3384 if(sp_expunge_count(stream
) > 0){
3386 * This is kind of surprising at first. For most sort
3387 * orders, if the whole set is sorted, then any subset
3388 * is also sorted. Not so for threaded sorts.
3390 if(SORT_IS_THREADED(msgmap
))
3391 refresh_sort(stream
, msgmap
, SRT_NON
);
3395 unsigned char *fname
= folder_name_decoded((unsigned char *)state
->cur_folder
);
3396 q_status_message1(SM_ORDER
, 0, 3,
3397 _("No messages expunged from folder \"%s\""),
3398 pretty_fn((char *) fname
));
3399 if(fname
) fs_give((void **)&fname
);
3401 else if(!prefilter_del_count
)
3402 q_status_message(SM_ORDER
, 0, 3,
3403 _("No messages marked deleted. No messages expunged."));
3409 /*----------------------------------------------------------------------
3410 Expunge_and_close callback to prompt user for confirmation
3412 Args: stream -- folder's stream
3413 folder -- name of folder containing folders
3414 deleted -- number of del'd msgs
3416 Result: 'y' to continue with expunge
3419 expunge_prompt(MAILSTREAM
*stream
, char *folder
, long int deleted
)
3423 char prompt_b
[MAX_SCREEN_COLS
+1], temp
[MAILTMPLEN
+1], buff
[MAX_SCREEN_COLS
+1];
3424 char *short_folder_name
;
3429 snprintf(temp
, sizeof(temp
), "%ld", deleted
);
3430 charcnt
= strlen(temp
)+1;
3433 max_folder
= MAX(1,MAXPROMPT
- (36+charcnt
));
3434 strncpy(temp
, folder
, sizeof(temp
));
3435 temp
[sizeof(temp
)-1] = '\0';
3436 short_folder_name
= short_str(temp
,buff
,sizeof(buff
),max_folder
,FrontDots
);
3439 snprintf(prompt_b
, sizeof(prompt_b
),
3440 "Delete %s%ld message%s from \"%s\"",
3441 (deleted
> 1L) ? "all " : "", deleted
,
3442 plural(deleted
), short_folder_name
);
3444 snprintf(prompt_b
, sizeof(prompt_b
),
3445 "Expunge the %ld deleted message%s from \"%s\"",
3446 deleted
, deleted
== 1 ? "" : "s",
3449 return(want_to(prompt_b
, 'y', 0, NO_HELP
, WT_NORM
));
3454 * This is used with multiple append saves. Call it once before
3455 * the series of appends with SSCP_INIT and once after all are
3456 * done with SSCP_END. In between, it is called automatically
3457 * from save_fetch_append or save_fetch_append_cb when we need
3458 * to ask the user if he or she wants to continue even though
3459 * announced message size doesn't match the actual message size.
3460 * As of 2008-02-29 the gmail IMAP server has these size mismatches
3461 * on a regular basis even though the data is ok.
3464 save_size_changed_prompt(long msgno
, int flags
)
3468 static int remember_the_yes
= 0;
3469 static int possible_corruption
= 0;
3470 static ESCKEY_S save_size_opts
[] = {
3471 {'y', 'y', "Y", "Yes"},
3472 {'n', 'n', "N", "No"},
3473 {'a', 'a', "A", "yes to All"},
3477 if(flags
& SSCP_INIT
|| flags
& SSCP_END
){
3478 if(flags
& SSCP_END
&& possible_corruption
)
3479 q_status_message(SM_ORDER
, 3, 3, "There is possible data corruption, check the results");
3481 remember_the_yes
= 0;
3482 possible_corruption
= 0;
3483 ps_global
->noshow_error
= 0;
3484 ps_global
->noshow_warn
= 0;
3488 if(remember_the_yes
){
3489 snprintf(prompt
, sizeof(prompt
),
3490 "Message to save shrank! (msg # %ld): Continuing", msgno
);
3491 q_status_message(SM_ORDER
, 0, 3, prompt
);
3492 display_message('x');
3493 return(remember_the_yes
);
3496 snprintf(prompt
, sizeof(prompt
),
3497 "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno
);
3498 ret
= radio_buttons(prompt
, -FOOTER_ROWS(ps_global
), save_size_opts
,
3499 'n', 0, h_save_size_changed
, RB_NORM
);
3503 remember_the_yes
= 'y';
3504 possible_corruption
++;
3505 return(remember_the_yes
);
3508 possible_corruption
++;
3512 possible_corruption
= 0;
3513 ps_global
->noshow_error
= 1;
3514 ps_global
->noshow_warn
= 1;
3522 /*----------------------------------------------------------------------
3523 Expunge_and_close callback that happens once the decision to expunge
3524 and close has been made and before expunging and closing begins
3527 Args: stream -- folder's stream
3528 folder -- name of folder containing folders
3529 deleted -- number of del'd msgs
3531 Result: 'y' to continue with expunge
3534 expunge_and_close_begins(int flags
, char *folder
)
3536 if(!(flags
& EC_NO_CLOSE
)){
3537 unsigned char *fname
= folder_name_decoded((unsigned char *)folder
);
3538 q_status_message1(SM_INFO
, 0, 1, "Closing \"%.200s\"...", (char *) fname
);
3539 flush_status_messages(1);
3540 if(fname
) fs_give((void **)&fname
);
3545 /*----------------------------------------------------------------------
3546 Export a message to a plain file in users home directory
3548 Args: state -- pointer to struct holding a bunch of pine state
3549 msgmap -- table mapping msg nums to c-client sequence nums
3550 qline -- screen line to ask questions on
3551 agg -- boolean indicating we're to operate on aggregate set
3556 cmd_export(struct pine
*state
, MSGNO_S
*msgmap
, int qline
, int aopt
)
3558 char filename
[MAXPATH
+1], full_filename
[MAXPATH
+1], *err
;
3560 int r
, leading_nl
, failure
= 0, orig_errno
, rflags
= GER_NONE
;
3561 int flags
= GE_IS_EXPORT
| GE_SEQ_SENSITIVE
, rv
= 0;
3565 long i
, count
= 0L, start_of_append
, rawno
;
3568 struct variable
*vars
= ps_global
->vars
;
3569 ESCKEY_S export_opts
[5];
3570 static HISTORY_S
*history
= NULL
;
3572 if(ps_global
->restricted
){
3573 q_status_message(SM_ORDER
, 0, 3,
3574 "Alpine demo can't export messages to files");
3578 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
3581 export_opts
[i
= 0].ch
= ctrl('T');
3582 export_opts
[i
].rval
= 10;
3583 export_opts
[i
].name
= "^T";
3584 export_opts
[i
++].label
= N_("To Files");
3586 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3587 if(ps_global
->VAR_DOWNLOAD_CMD
&& ps_global
->VAR_DOWNLOAD_CMD
[0]){
3588 export_opts
[i
].ch
= ctrl('V');
3589 export_opts
[i
].rval
= 12;
3590 export_opts
[i
].name
= "^V";
3591 /* TRANSLATORS: this is an abbreviation for Download Messages */
3592 export_opts
[i
++].label
= N_("Downld Msg");
3594 #endif /* !(DOS || MAC) */
3596 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps_global
)){
3597 export_opts
[i
].ch
= ctrl('I');
3598 export_opts
[i
].rval
= 11;
3599 export_opts
[i
].name
= "TAB";
3600 export_opts
[i
++].label
= N_("Complete");
3604 /* Commented out since it's not yet support! */
3605 if(F_ON(F_ENABLE_SUB_LISTS
,ps_global
)){
3606 export_opts
[i
].ch
= ctrl('X');
3607 export_opts
[i
].rval
= 14;
3608 export_opts
[i
].name
= "^X";
3609 export_opts
[i
++].label
= N_("ListMatches");
3614 * If message has attachments, add a toggle that will allow the user
3615 * to save all of the attachments to a single directory, using the
3616 * names provided with the attachments or part names. What we'll do is
3617 * export the message as usual, and then export the attachments into
3618 * a subdirectory that did not exist before. The subdir will be named
3619 * something based on the name of the file being saved to, but a
3622 if(!MCMD_ISAGG(aopt
)
3623 && state
->mail_stream
3624 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
3625 && rawno
<= state
->mail_stream
->nmsgs
3626 && (env
= pine_mail_fetchstructure(state
->mail_stream
, rawno
, &b
))
3628 && b
->type
== TYPEMULTIPART
3630 && strucmp(b
->subtype
, "ALTERNATIVE") != 0){
3633 part
= b
->nested
.part
; /* 1st part */
3634 if(part
&& part
->next
)
3635 flags
|= GE_ALLPARTS
;
3638 export_opts
[i
].ch
= -1;
3641 if(mn_total_cur(msgmap
) <= 1L){
3642 snprintf(nmsgs
, sizeof(nmsgs
), "Msg #%ld", mn_get_cur(msgmap
));
3643 nmsgs
[sizeof(nmsgs
)-1] = '\0';
3646 snprintf(nmsgs
, sizeof(nmsgs
), "%s messages", comatose(mn_total_cur(msgmap
)));
3647 nmsgs
[sizeof(nmsgs
)-1] = '\0';
3650 r
= get_export_filename(state
, filename
, NULL
, full_filename
,
3651 sizeof(filename
), nmsgs
, "EXPORT",
3652 export_opts
, &rflags
, qline
, flags
, &history
);
3657 cmd_cancelled("Export message");
3661 q_status_message1(SM_ORDER
, 0, 2,
3662 _("Can't export to file outside of %s"),
3669 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3670 else if(r
== 12){ /* Download */
3671 char cmd
[MAXPATH
], *tfp
= NULL
;
3677 if(ps_global
->restricted
){
3678 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3679 "Download disallowed in restricted mode");
3684 tfp
= temp_nam(NULL
, "pd");
3685 build_updown_cmd(cmd
, sizeof(cmd
), ps_global
->VAR_DOWNLOAD_CMD_PREFIX
,
3686 ps_global
->VAR_DOWNLOAD_CMD
, tfp
);
3687 dprint((1, "Download cmd called: \"%s\"\n", cmd
));
3688 if((so
= so_get(FileStar
, tfp
, WRITE_ACCESS
|OWNER_ONLY
|WRITE_TO_LOCALE
)) != NULL
){
3689 gf_set_so_writec(&pc
, so
);
3691 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
)){
3692 if(!(state
->mail_stream
3693 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
3694 && rawno
<= state
->mail_stream
->nmsgs
3695 && (mc
= mail_elt(state
->mail_stream
, rawno
))
3699 if(!(env
= pine_mail_fetchstructure(state
->mail_stream
,
3700 mn_m2raw(msgmap
, i
), &b
))
3701 || !bezerk_delimiter(env
, mc
, pc
, next
++)
3702 || !format_message(mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
3703 env
, b
, NULL
, FM_NEW_MESS
| FM_NOWRAP
, pc
)){
3704 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3705 err
= "Error writing tempfile for download");
3710 gf_clear_so_writec(so
);
3711 if(so_give(&so
)){ /* close file */
3713 err
= "Error writing tempfile for download";
3717 if((syspipe
= open_system_pipe(cmd
, NULL
, NULL
,
3718 PIPE_USER
| PIPE_RESET
,
3719 0, pipe_callback
, pipe_report_error
)) != NULL
)
3720 (void) close_system_pipe(&syspipe
, NULL
, pipe_callback
);
3722 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3723 err
= _("Error running download command"));
3727 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3728 err
= "Error building temp file for download");
3732 fs_give((void **)&tfp
);
3736 q_status_message(SM_ORDER
, 0, 3, _("Download Command Completed"));
3740 #endif /* !(DOS || MAC) */
3743 if(rflags
& GER_APPEND
)
3748 dprint((5, "Opening file \"%s\" for export\n",
3749 full_filename
? full_filename
: "?"));
3751 if(!(store
= so_get(FileStar
, full_filename
, WRITE_ACCESS
|WRITE_TO_LOCALE
))){
3752 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
3753 /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
3754 _("Error opening file \"%s\" to export message: %s"),
3755 full_filename
, error_description(errno
));
3759 gf_set_so_writec(&pc
, store
);
3762 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
), count
++){
3763 env
= pine_mail_fetchstructure(state
->mail_stream
, mn_m2raw(msgmap
, i
),
3766 err
= _("Can't export message. Error accessing mail folder");
3771 if(!(state
->mail_stream
3772 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
3773 && rawno
<= state
->mail_stream
->nmsgs
3774 && (mc
= mail_elt(state
->mail_stream
, rawno
))
3778 start_of_append
= so_tell(store
);
3779 if(!bezerk_delimiter(env
, mc
, pc
, leading_nl
)
3780 || !format_message(mn_m2raw(msgmap
, i
), env
, b
, NULL
,
3781 FM_NEW_MESS
| FM_NOWRAP
, pc
)){
3782 orig_errno
= errno
; /* save incase things are really bad */
3783 failure
= 1; /* pop out of here */
3790 gf_clear_so_writec(store
);
3791 if(so_give(&store
)) /* release storage */
3795 our_truncate(full_filename
, (off_t
)start_of_append
);
3797 dprint((1, "FAILED Export: fetch(%ld): %s\n",
3798 i
, err
? err
: "?"));
3799 q_status_message(SM_ORDER
| SM_DING
, 3, 4, err
);
3802 dprint((1, "FAILED Export: file \"%s\" : %s\n",
3803 full_filename
? full_filename
: "?",
3804 error_description(orig_errno
)));
3805 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
3806 /* TRANSLATORS: Error exporting to <filename>: <error text> */
3807 _("Error exporting to \"%s\" : %s"),
3808 filename
, error_description(orig_errno
));
3812 if(rflags
& GER_ALLPARTS
&& full_filename
[0]){
3813 char dir
[MAXPATH
+1];
3814 char lfile
[MAXPATH
+1];
3815 int ok
= 0, tries
= 0, saved
= 0, errs
= 0;
3819 * Now we want to save all of the attachments to a subdirectory.
3820 * To make it easier for us and probably easier for the user, and
3821 * to prevent the user from shooting himself in the foot, we
3822 * make a new subdirectory so that we can't possibly step on
3823 * any existing files, and we don't need any interaction with the
3824 * user while saving.
3826 * We'll just use the directory name full_filename.d or if that
3827 * already exists and isn't empty, we'll try adding a suffix to
3828 * that until we get something to use.
3831 if(strlen(full_filename
) + strlen(".d") + 1 > sizeof(dir
)){
3832 q_status_message1(SM_ORDER
| SM_DING
, 3, 4,
3833 _("Can't save attachments, filename too long: %s"),
3839 snprintf(dir
, sizeof(dir
), "%s.d", full_filename
);
3840 dir
[sizeof(dir
)-1] = '\0';
3844 switch(r
= is_writable_dir(dir
)){
3845 case 0: /* exists and is a writable dir */
3847 * We could figure out if it is empty and use it in
3848 * that case, but that sounds like a lot of work, so
3849 * just fall through to default.
3853 if(strlen(full_filename
) + strlen(".d") + 1 +
3854 1 + strlen(long2string((long) tries
)) > sizeof(dir
)){
3855 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3856 "Problem saving attachments");
3860 snprintf(dir
, sizeof(dir
), "%s.d_%s", full_filename
,
3861 long2string((long) tries
));
3862 dir
[sizeof(dir
)-1] = '\0';
3865 case 3: /* doesn't exist, that's good! */
3866 /* make new directory */
3870 } while(!ok
&& tries
< 1000);
3873 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3874 _("Problem saving attachments"));
3878 /* create the new directory */
3879 if(our_mkdir(dir
, 0700)){
3880 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
3881 _("Problem saving attachments: %s: %s"), dir
,
3882 error_description(errno
));
3886 if(!(state
->mail_stream
3887 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
3888 && rawno
<= state
->mail_stream
->nmsgs
3889 && (env
=pine_mail_fetchstructure(state
->mail_stream
,rawno
,&b
))
3891 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3892 _("Problem reading message"));
3896 zero_atmts(state
->atmts
);
3897 describe_mime(b
, "", 1, 1, 0, 0);
3900 if(a
&& a
->description
) /* skip main body part */
3903 for(; a
->description
!= NULL
; a
++){
3904 /* skip over these parts of the message */
3905 if(MIME_MSG_A(a
) || MIME_DGST_A(a
) || MIME_VCARD_A(a
))
3909 (void) get_filename_parameter(lfile
, sizeof(lfile
), a
->body
, NULL
);
3911 if(lfile
[0] == '\0'){ /* MAXPATH + 1 = sizeof(lfile) */
3912 snprintf(lfile
, sizeof(lfile
), "part_%.*s", MAXPATH
+1-6,
3913 a
->number
? a
->number
: "?");
3914 lfile
[sizeof(lfile
)-1] = '\0';
3917 if(strlen(dir
) + strlen(S_FILESEP
) + strlen(lfile
) + 1
3918 > sizeof(filename
)){
3920 "FAILED Att Export: name too long: %s\n",
3921 dir
, S_FILESEP
, lfile
));
3926 snprintf(filename
, sizeof(filename
), "%s%s%s", dir
, S_FILESEP
, lfile
);
3927 filename
[sizeof(filename
)-1] = '\0';
3929 if(write_attachment_to_file(state
->mail_stream
, rawno
,
3930 a
, GER_NONE
, filename
) == 1)
3938 q_status_message1(SM_ORDER
, 3, 3,
3939 "Errors saving some attachments, %s attachments saved",
3940 long2string((long) saved
));
3942 q_status_message(SM_ORDER
, 3, 3,
3943 _("Problems saving attachments"));
3947 q_status_message2(SM_ORDER
, 0, 3,
3948 /* TRANSLATORS: Saved <how many> attachements to <directory name> */
3949 _("Saved %s attachments to %s"),
3950 long2string((long) saved
), dir
);
3952 q_status_message(SM_ORDER
, 3, 3, _("No attachments to save"));
3955 else if(mn_total_cur(msgmap
) > 1L)
3956 q_status_message4(SM_ORDER
,0,3,
3957 "%s message%s %s to file \"%s\"",
3958 long2string(count
), plural(count
),
3961 : rflags
& GER_APPEND
? "appended" : "exported",
3964 q_status_message3(SM_ORDER
,0,3,
3965 "Message %s %s to file \"%s\"",
3966 long2string(mn_get_cur(msgmap
)),
3969 : rflags
& GER_APPEND
? "appended" : "exported",
3975 if(MCMD_ISAGG(aopt
))
3976 restore_selected(msgmap
);
3983 * Ask user what file to export to. Export from srcstore to that file.
3985 * Args ps -- pine struct
3986 * srctext -- pointer to source text
3987 * srctype -- type of that source text
3988 * prompt_msg -- see get_export_filename
3991 * Returns: != 0 : error
3995 simple_export(struct pine
*ps
, void *srctext
, SourceType srctype
, char *prompt_msg
, char *lister_msg
)
3997 int r
= 1, rflags
= GER_NONE
;
3998 char filename
[MAXPATH
+1], full_filename
[MAXPATH
+1];
3999 STORE_S
*store
= NULL
;
4000 struct variable
*vars
= ps
->vars
;
4001 static HISTORY_S
*history
= NULL
;
4002 static ESCKEY_S simple_export_opts
[] = {
4003 {ctrl('T'), 10, "^T", N_("To Files")},
4004 {-1, 0, NULL
, NULL
},
4005 {-1, 0, NULL
, NULL
}};
4007 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps
)){
4008 simple_export_opts
[r
].ch
= ctrl('I');
4009 simple_export_opts
[r
].rval
= 11;
4010 simple_export_opts
[r
].name
= "TAB";
4011 simple_export_opts
[r
].label
= N_("Complete");
4015 q_status_message(SM_ORDER
, 0, 2, _("Error allocating space"));
4020 simple_export_opts
[++r
].ch
= -1;
4022 full_filename
[0] = '\0';
4024 r
= get_export_filename(ps
, filename
, NULL
, full_filename
, sizeof(filename
),
4025 prompt_msg
, lister_msg
, simple_export_opts
, &rflags
,
4026 -FOOTER_ROWS(ps
), GE_IS_EXPORT
, &history
);
4030 else if(!full_filename
[0]){
4035 dprint((5, "Opening file \"%s\" for export\n",
4036 full_filename
? full_filename
: "?"));
4038 if((store
= so_get(FileStar
, full_filename
, WRITE_ACCESS
|WRITE_TO_LOCALE
)) != NULL
){
4042 gf_set_so_writec(&pc
, store
);
4043 gf_set_readc(&gc
, srctext
, (srctype
== CharStar
)
4044 ? strlen((char *)srctext
)
4048 if((pipe_err
= gf_pipe(gc
, pc
)) != NULL
){
4049 q_status_message2(SM_ORDER
| SM_DING
, 3, 3,
4050 /* TRANSLATORS: Problem saving to <filename>: <error text> */
4051 _("Problem saving to \"%s\": %s"),
4052 filename
, pipe_err
);
4058 gf_clear_so_writec(store
);
4059 if(so_give(&store
)){
4060 q_status_message2(SM_ORDER
| SM_DING
, 3, 3,
4061 _("Problem saving to \"%s\": %s"),
4062 filename
, error_description(errno
));
4067 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
4068 _("Error opening file \"%s\" for export: %s"),
4069 full_filename
, error_description(errno
));
4076 /* overloading full_filename */
4077 snprintf(full_filename
, sizeof(full_filename
), "%c%s",
4078 (prompt_msg
&& prompt_msg
[0])
4079 ? (islower((unsigned char)prompt_msg
[0])
4080 ? toupper((unsigned char)prompt_msg
[0]) : prompt_msg
[0])
4082 (prompt_msg
&& prompt_msg
[0]) ? prompt_msg
+1 : "ext");
4083 full_filename
[sizeof(full_filename
)-1] = '\0';
4084 q_status_message3(SM_ORDER
,0,2,"%s %s to \"%s\"",
4088 : rflags
& GER_APPEND
? "appended" : "exported",
4093 cmd_cancelled("Export");
4097 q_status_message1(SM_ORDER
, 0, 2,
4098 _("Can't export to file outside of %s"), VAR_OPER_DIR
);
4102 ps
->mangled_footer
= 1;
4108 * Ask user what file to export to.
4110 * filename -- On input, this is the filename to start with. On exit,
4111 * this is the filename chosen. (but this isn't used)
4112 * deefault -- This is the default value if user hits return. The
4113 * prompt will have [deefault] added to it automatically.
4114 * full_filename -- This is the full filename on exit.
4115 * len -- Minimum length of _both_ filename and full_filename.
4116 * prompt_msg -- Message to insert in prompt.
4117 * lister_msg -- Message to insert in file_lister.
4118 * opts -- Key options.
4119 * There is a tangled relationship between the callers
4120 * and this routine as far as opts are concerned. Some
4121 * of the opts are handled here. In particular, r == 3,
4122 * r == 10, r == 11, and r == 13 are all handled here.
4123 * Don't use those values unless you want what happens
4124 * here. r == 12 and others are handled by the caller.
4125 * rflags -- Return flags
4126 * GER_OVER - overwrite of existing file
4127 * GER_APPEND - append of existing file
4128 * else file did not exist before
4130 * GER_ALLPARTS - AllParts toggle was turned on
4132 * qline -- Command line to prompt on.
4133 * flags -- Logically OR'd flags
4134 * GE_IS_EXPORT - The command was an Export command
4135 * so the prompt should include
4137 * GE_SEQ_SENSITIVE - The command that got us here is
4138 * sensitive to sequence number changes
4139 * caused by unsolicited expunges.
4140 * GE_NO_APPEND - We will not allow append to an
4141 * existing file, only removal of the
4142 * file if it exists.
4143 * GE_IS_IMPORT - We are selecting for reading.
4144 * No overwriting or checking for
4145 * existence at all. Don't use this
4146 * together with GE_NO_APPEND.
4147 * GE_ALLPARTS - Turn on AllParts toggle.
4148 * GE_BINARY - Turn on Binary toggle.
4150 * Returns: -1 cancelled
4151 * -2 prohibited by VAR_OPER_DIR
4152 * -3 other error, already reported here
4154 * 12 user chose 12 command from opts
4157 get_export_filename(struct pine
*ps
, char *filename
, char *deefault
,
4158 char *full_filename
, size_t len
, char *prompt_msg
,
4159 char *lister_msg
, ESCKEY_S
*optsarg
, int *rflags
,
4160 int qline
, int flags
, HISTORY_S
**history
)
4162 char dir
[MAXPATH
+1], dir2
[MAXPATH
+1];
4163 char precolon
[MAXPATH
+1], postcolon
[MAXPATH
+1];
4164 char filename2
[MAXPATH
+1], tmp
[MAXPATH
+1], *fn
, *ill
;
4165 int l
, i
, ku
= -1, r
, fatal
, homedir
= 0, was_abs_path
=0, avail
, ret
= 0;
4166 int allparts
= 0, binary
= 0;
4167 char prompt_buf
[400];
4169 ESCKEY_S
*opts
= NULL
;
4170 struct variable
*vars
= ps
->vars
;
4171 static HISTORY_S
*dir_hist
= NULL
;
4173 if(flags
& GE_ALLPARTS
|| history
){
4175 * Copy the opts and add one to the end of the list.
4177 for(i
= 0; optsarg
[i
].ch
!= -1; i
++)
4183 if(flags
& GE_ALLPARTS
)
4186 if(flags
& GE_BINARY
)
4189 opts
= (ESCKEY_S
*) fs_get((i
+1) * sizeof(*opts
));
4190 memset(opts
, 0, (i
+1) * sizeof(*opts
));
4192 for(i
= 0; optsarg
[i
].ch
!= -1; i
++){
4193 opts
[i
].ch
= optsarg
[i
].ch
;
4194 opts
[i
].rval
= optsarg
[i
].rval
;
4195 opts
[i
].name
= optsarg
[i
].name
; /* no need to make a copy */
4196 opts
[i
].label
= optsarg
[i
].label
; /* " */
4199 if(flags
& GE_ALLPARTS
){
4201 opts
[i
].ch
= ctrl('P');
4203 opts
[i
].name
= "^P";
4204 /* TRANSLATORS: Export all attachment parts */
4205 opts
[i
++].label
= N_("AllParts");
4208 if(flags
& GE_BINARY
){
4210 opts
[i
].ch
= ctrl('R');
4212 opts
[i
].name
= "^R";
4213 opts
[i
++].label
= N_("Binary");
4217 opts
[i
].ch
= KEY_UP
;
4221 opts
[i
++].label
= "";
4223 opts
[i
].ch
= KEY_DOWN
;
4226 opts
[i
++].label
= "";
4228 opts
[i
].ch
= ctrl('Y');
4231 opts
[i
++].label
= "";
4233 opts
[i
].ch
= ctrl('V');
4236 opts
[i
++].label
= "";
4242 init_hist(history
, HISTSIZE
);
4243 init_hist(&dir_hist
, HISTSIZE
);
4252 if(F_ON(F_USE_CURRENT_DIR
, ps
))
4254 else if(VAR_OPER_DIR
){
4255 strncpy(dir
, VAR_OPER_DIR
, sizeof(dir
));
4256 dir
[sizeof(dir
)-1] = '\0';
4258 #if defined(DOS) || defined(OS2)
4259 else if(VAR_FILE_DIR
){
4260 strncpy(dir
, VAR_FILE_DIR
, sizeof(dir
));
4261 dir
[sizeof(dir
)-1] = '\0';
4270 postcolon
[0] = '\0';
4271 strncpy(precolon
, dir
, sizeof(precolon
));
4272 precolon
[sizeof(precolon
)-1] = '\0';
4274 strncpy(def
, deefault
, sizeof(def
)-1);
4275 def
[sizeof(def
)-1] = '\0';
4276 removing_leading_and_trailing_white_space(def
);
4281 avail
= MAX(20, ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80) - MIN_OPT_ENT_WIDTH
;
4283 /*---------- Prompt the user for the file name -------------*/
4286 char dirb
[50], fileb
[50];
4287 int l1
, l2
, l3
, l4
, l5
, needed
;
4288 char *p
, p1
[100], p2
[100], *p3
, p4
[100], p5
[100];
4290 snprintf(p1
, sizeof(p1
), "%sCopy ",
4291 (flags
& GE_IS_EXPORT
) ? "EXPORT: " :
4292 (flags
& GE_IS_IMPORT
) ? "IMPORT: " : "SAVE: ");
4293 p1
[sizeof(p1
)-1] = '\0';
4296 strncpy(p2
, prompt_msg
? prompt_msg
: "", sizeof(p2
)-1);
4297 p2
[sizeof(p2
)-1] = '\0';
4300 if(rflags
&& *rflags
& GER_ALLPARTS
)
4307 snprintf(p4
, sizeof(p4
), " %s file%s%s",
4308 (flags
& GE_IS_IMPORT
) ? "from" : "to",
4309 is_absolute_path(filename
) ? "" : " in ",
4310 is_absolute_path(filename
) ? "" :
4311 (!dir
[0] ? "current directory"
4312 : (dir
[0] == '~' && !dir
[1]) ? "home directory"
4313 : short_str(dir
,dirb
,sizeof(dirb
),30,FrontDots
)));
4314 p4
[sizeof(p4
)-1] = '\0';
4317 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4319 *def
? short_str(def
,fileb
,sizeof(fileb
),40,EndDots
) : "",
4321 p5
[sizeof(p5
)-1] = '\0';
4324 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0){
4325 snprintf(p4
, sizeof(p4
), " %s file%s%s",
4326 (flags
& GE_IS_IMPORT
) ? "from" : "to",
4327 is_absolute_path(filename
) ? "" : " in ",
4328 is_absolute_path(filename
) ? "" :
4329 (!dir
[0] ? "current dir"
4330 : (dir
[0] == '~' && !dir
[1]) ? "home dir"
4331 : short_str(dir
,dirb
,sizeof(dirb
),10,FrontDots
)));
4332 p4
[sizeof(p4
)-1] = '\0';
4336 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l5
> 0){
4337 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4339 *def
? short_str(def
,fileb
,sizeof(fileb
),
4340 MAX(15,l5
-5-needed
),EndDots
) : "",
4342 p5
[sizeof(p5
)-1] = '\0';
4346 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l2
> 0){
4349 * 14 is about the shortest we can make this, because there are
4350 * fixed length strings of length 14 coming in here.
4352 p
= short_str(prompt_msg
, p2
, sizeof(p2
), MAX(14,l2
-needed
), FrontDots
);
4354 strncpy(p2
, p
, sizeof(p2
)-1);
4355 p2
[sizeof(p2
)-1] = '\0';
4361 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0){
4362 strncpy(p1
, "Copy ", sizeof(p1
)-1);
4363 p1
[sizeof(p1
)-1] = '\0';
4367 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l5
> 0){
4368 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4370 *def
? short_str(def
,fileb
, sizeof(fileb
),
4371 MAX(10,l5
-5-needed
),EndDots
) : "",
4373 p5
[sizeof(p5
)-1] = '\0';
4377 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l3
> 0){
4378 if(needed
<= l3
- strlen(" (+ atts)"))
4380 else if(needed
<= l3
- strlen(" (atts)"))
4382 else if(needed
<= l3
- strlen(" (+)"))
4384 else if(needed
<= l3
- strlen("+"))
4392 snprintf(prompt_buf
, sizeof(prompt_buf
), "%s%s%s%s%s", p1
, p2
, p3
, p4
, p5
);
4393 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4396 if(items_in_hist(*history
) > 0){
4397 opts
[ku
].name
= HISTORY_UP_KEYNAME
;
4398 opts
[ku
].label
= HISTORY_KEYLABEL
;
4399 opts
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
4400 opts
[ku
+1].label
= HISTORY_KEYLABEL
;
4401 if(items_in_hist(dir_hist
) > 0){ /* any directories */
4402 opts
[ku
+2].name
= "^Y";
4403 opts
[ku
+2].label
= "Prev Dir";
4404 opts
[ku
+3].name
= "^V";
4405 opts
[ku
+3].label
= "Next Dir";
4408 opts
[ku
+2].name
= "";
4409 opts
[ku
+2].label
= "";
4410 opts
[ku
+3].name
= "";
4411 opts
[ku
+3].label
= "";
4416 opts
[ku
].label
= "";
4417 opts
[ku
+1].name
= "";
4418 opts
[ku
+1].label
= "";
4422 oeflags
= OE_APPEND_CURRENT
|
4423 ((flags
& GE_SEQ_SENSITIVE
) ? OE_SEQ_SENSITIVE
: 0);
4424 r
= optionally_enter(filename
, qline
, 0, len
, prompt_buf
,
4425 opts
, NO_HELP
, &oeflags
);
4427 dprint((2, "\n - export_filename = \"%s\", r = %d -\n", filename
, r
));
4431 * Helps may not be right if you add another caller or change
4432 * things. Check it out.
4434 if(flags
& GE_IS_IMPORT
)
4435 helper(h_ge_import
, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE
);
4436 else if(flags
& GE_ALLPARTS
)
4437 helper(h_ge_allparts
, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE
);
4439 helper(h_ge_export
, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE
);
4441 ps
->mangled_screen
= 1;
4445 else if(r
== 10 || r
== 11){ /* Browser or File Completion */
4446 if(filename
[0]=='~'){
4447 if(filename
[1] == C_FILESEP
&& filename
[2]!='\0'){
4450 for(i
=0; filename
[i
+2] != '\0' && i
+2 < len
-1; i
++)
4451 filename
[i
] = filename
[i
+2];
4453 strncpy(dir
, precolon
, sizeof(dir
)-1);
4454 dir
[sizeof(dir
)-1] = '\0';
4456 else if(filename
[1]=='\0' ||
4457 (filename
[1] == C_FILESEP
&& filename
[2] == '\0')){
4461 strncpy(dir
, precolon
, sizeof(dir
)-1);
4462 dir
[sizeof(dir
)-1] = '\0';
4465 else if(!dir
[0] && !is_absolute_path(filename
) && was_abs_path
){
4469 strncpy(dir
, precolon
, sizeof(dir
)-1);
4470 dir
[sizeof(dir
)-1] = '\0';
4479 strncpy(tmp
, filename
, sizeof(tmp
)-1);
4480 tmp
[sizeof(tmp
)-1] = '\0';
4481 if(*tmp
&& is_absolute_path(tmp
))
4482 fnexpand(tmp
, sizeof(tmp
));
4483 if(strncmp(tmp
,postcolon
, strlen(postcolon
)))
4484 postcolon
[0] = '\0';
4486 if(*tmp
&& (fn
= last_cmpnt(tmp
))){
4488 strncpy(filename2
, fn
, sizeof(filename2
)-1);
4489 filename2
[sizeof(filename2
)-1] = '\0';
4490 if(is_absolute_path(tmp
)){
4491 strncpy(dir2
, tmp
, MIN(fn
- tmp
, sizeof(dir2
)-1));
4492 dir2
[MIN(fn
- tmp
, sizeof(dir2
)-1)] = '\0';
4494 if(tmp
[1]==':' && tmp
[2]=='\\' && dir2
[2]=='\0'){
4499 strncpy(postcolon
, dir2
, sizeof(postcolon
)-1);
4500 postcolon
[sizeof(postcolon
)-1] = '\0';
4506 * Just building the directory name in dir2,
4507 * full_filename is overloaded.
4509 snprintf(full_filename
, len
, "%.*s", (int) MIN(fn
-tmp
,len
-1), tmp
);
4510 full_filename
[len
-1] = '\0';
4511 strncpy(postcolon
, full_filename
, sizeof(postcolon
)-1);
4512 postcolon
[sizeof(postcolon
)-1] = '\0';
4513 build_path(dir2
, !dir
[0] ? p
= (char *)getcwd(NULL
,MAXPATH
)
4514 : (dir
[0] == '~' && !dir
[1])
4517 full_filename
, sizeof(dir2
));
4523 if(is_absolute_path(tmp
)){
4524 strncpy(dir2
, tmp
, sizeof(dir2
)-1);
4525 dir2
[sizeof(dir2
)-1] = '\0';
4527 if(dir2
[2]=='\0' && dir2
[1]==':'){
4530 strncpy(postcolon
,dir2
,sizeof(postcolon
)-1);
4531 postcolon
[sizeof(postcolon
)-1] = '\0';
4534 filename2
[0] = '\0';
4538 strncpy(filename2
, tmp
, sizeof(filename2
)-1);
4539 filename2
[sizeof(filename2
)-1] = '\0';
4541 (void)getcwd(dir2
, sizeof(dir2
));
4542 else if(dir
[0] == '~' && !dir
[1]){
4543 strncpy(dir2
, ps
->home_dir
, sizeof(dir2
)-1);
4544 dir2
[sizeof(dir2
)-1] = '\0';
4547 strncpy(dir2
, dir
, sizeof(dir2
)-1);
4548 dir2
[sizeof(dir2
)-1] = '\0';
4551 postcolon
[0] = '\0';
4555 build_path(full_filename
, dir2
, filename2
, len
);
4556 if(!strcmp(full_filename
, dir2
))
4557 filename2
[0] = '\0';
4558 if(full_filename
[strlen(full_filename
)-1] == C_FILESEP
4559 && isdir(full_filename
,NULL
,NULL
)){
4560 if(strlen(full_filename
) == 1)
4561 strncpy(postcolon
, full_filename
, sizeof(postcolon
)-1);
4562 else if(filename2
[0])
4563 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4564 postcolon
[sizeof(postcolon
)-1] = '\0';
4565 strncpy(dir2
, full_filename
, sizeof(dir2
)-1);
4566 dir2
[sizeof(dir2
)-1] = '\0';
4567 filename2
[0] = '\0';
4569 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4570 else if(full_filename
[strlen(full_filename
)-1] == C_FILESEP
){
4571 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4572 postcolon
[sizeof(postcolon
)-1] = '\0';
4573 strncpy(dir2
, full_filename
, sizeof(dir2
)-1);
4574 dir2
[sizeof(dir2
)-1] = '\0';
4575 filename2
[0] = '\0';
4578 if(dir2
[strlen(dir2
)-1] == C_FILESEP
&& strlen(dir2
)!=1
4579 && strcmp(dir2
+1, ":\\"))
4580 /* last condition to prevent stripping of '\\'
4581 in windows partition */
4582 dir2
[strlen(dir2
)-1] = '\0';
4584 if(r
== 10){ /* File Browser */
4585 r
= file_lister(lister_msg
? lister_msg
: "EXPORT",
4586 dir2
, sizeof(dir2
), filename2
, sizeof(filename2
),
4588 (flags
& GE_IS_IMPORT
) ? FB_READ
: FB_SAVE
);
4590 /* Windows has a special "feature" in which entering the file browser will
4591 change the working directory if the directory is changed at all (even
4592 clicking "Cancel" will change the working directory).
4594 if(F_ON(F_USE_CURRENT_DIR
, ps
))
4595 (void)getcwd(dir2
,sizeof(dir2
));
4597 if(isdir(dir2
,NULL
,NULL
)){
4598 strncpy(precolon
, dir2
, sizeof(precolon
)-1);
4599 precolon
[sizeof(precolon
)-1] = '\0';
4601 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4602 postcolon
[sizeof(postcolon
)-1] = '\0';
4604 build_path(full_filename
, dir2
, filename2
, len
);
4605 if(isdir(full_filename
, NULL
, NULL
)){
4606 strncpy(dir
, full_filename
, sizeof(dir
)-1);
4607 dir
[sizeof(dir
)-1] = '\0';
4611 fn
= last_cmpnt(full_filename
);
4612 strncpy(dir
, full_filename
,
4613 MIN(fn
- full_filename
, sizeof(dir
)-1));
4614 dir
[MIN(fn
- full_filename
, sizeof(dir
)-1)] = '\0';
4615 if(fn
- full_filename
> 1)
4616 dir
[fn
- full_filename
- 1] = '\0';
4619 if(!strcmp(dir
, ps
->home_dir
)){
4624 strncpy(filename
, fn
, len
-1);
4625 filename
[len
-1] = '\0';
4628 else{ /* File Completion */
4629 if(!pico_fncomplete(dir2
, filename2
, sizeof(filename2
)))
4631 strncat(postcolon
, filename2
,
4632 sizeof(postcolon
)-1-strlen(postcolon
));
4633 postcolon
[sizeof(postcolon
)-1] = '\0';
4635 was_abs_path
= is_absolute_path(filename
);
4637 if(!strcmp(dir
, ps
->home_dir
)){
4642 strncpy(filename
, postcolon
, len
-1);
4643 filename
[len
-1] = '\0';
4644 strncpy(dir
, precolon
, sizeof(dir
)-1);
4645 dir
[sizeof(dir
)-1] = '\0';
4647 if(filename
[0] == '~' && !filename
[1]){
4655 else if(r
== 12){ /* Download, caller handles it */
4659 else if(r
== 13){ /* toggle AllParts bit */
4661 if(*rflags
& GER_ALLPARTS
){
4662 *rflags
&= ~GER_ALLPARTS
;
4663 opts
[allparts
].label
= N_("AllParts");
4666 *rflags
|= GER_ALLPARTS
;
4667 /* opposite of All Parts, No All Parts */
4668 opts
[allparts
].label
= N_("NoAllParts");
4675 else if(r
== 14){ /* List file names matching partial? */
4679 else if(r
== 15){ /* toggle Binary bit */
4681 if(*rflags
& GER_BINARY
){
4682 *rflags
&= ~GER_BINARY
;
4683 opts
[binary
].label
= N_("Binary");
4686 *rflags
|= GER_BINARY
;
4687 opts
[binary
].label
= N_("No Binary");
4693 else if(r
== 1){ /* Cancel */
4700 else if(r
>= 30 && r
<= 33){
4705 case 30: p
= get_prev_hist(*history
, filename
, 0, NULL
); break;
4706 case 31: p
= get_next_hist(*history
, filename
, 0, NULL
); break;
4707 case 32: p
= get_prev_hist(dir_hist
, NULL
, 0, NULL
); break;
4708 case 33: p
= get_next_hist(dir_hist
, NULL
, 0, NULL
); break;
4709 default: alpine_panic("Impossible case in save attachment"); break;
4713 if(p
!= NULL
&& *p
!= '\0'){
4714 if(r
== 30 || r
== 31){
4715 if((fn
= last_cmpnt(p
)) != NULL
){
4716 strncpy(dir
, p
, MIN(fn
- p
, sizeof(dir
)-1));
4717 dir
[MIN(fn
- p
, sizeof(dir
)-1)] = '\0';
4719 dir
[fn
- p
- 1] = '\0';
4720 strncpy(filename
, fn
, len
-1);
4721 filename
[len
-1] = '\0';
4723 } else { /* r == 32 || r == 33 */
4724 strncpy(dir
, p
, sizeof(dir
)-1);
4725 dir
[sizeof(dir
)-1] = '\0';
4728 if(!strcmp(dir
, ps
->home_dir
)){
4742 removing_leading_and_trailing_white_space(filename
);
4745 if(!*def
){ /* Cancel */
4750 strncpy(filename
, def
, len
-1);
4751 filename
[len
-1] = '\0';
4754 #if defined(DOS) || defined(OS2)
4755 if(is_absolute_path(filename
)){
4756 fixpath(filename
, len
);
4759 if(filename
[0] == '~'){
4760 if(fnexpand(filename
, len
) == NULL
){
4761 char *p
= strindex(filename
, '/');
4764 q_status_message1(SM_ORDER
| SM_DING
, 3, 3,
4765 _("Error expanding file name: \"%s\" unknown user"),
4772 if(is_absolute_path(filename
)){
4773 strncpy(full_filename
, filename
, len
-1);
4774 full_filename
[len
-1] = '\0';
4778 build_path(full_filename
, (char *)getcwd(dir
,sizeof(dir
)),
4780 else if(dir
[0] == '~' && !dir
[1])
4781 build_path(full_filename
, ps
->home_dir
, filename
, len
);
4783 build_path(full_filename
, dir
, filename
, len
);
4786 if((ill
= filter_filename(full_filename
, &fatal
,
4787 ps_global
->restricted
|| ps_global
->VAR_OPER_DIR
)) != NULL
){
4789 q_status_message1(SM_ORDER
| SM_DING
, 3, 3, "%s", ill
);
4793 /* BUG: we should beep when the key's pressed rather than bitch later */
4794 /* Warn and ask for confirmation. */
4795 snprintf(prompt_buf
, sizeof(prompt_buf
), "File name contains a '%s'. %s anyway",
4796 ill
, (flags
& GE_IS_EXPORT
) ? "Export" : "Save");
4797 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4798 if(want_to(prompt_buf
, 'n', 0, NO_HELP
,
4799 ((flags
& GE_SEQ_SENSITIVE
) ? RB_SEQ_SENSITIVE
: 0)) != 'y')
4804 break; /* Must have got an OK file name */
4807 if(VAR_OPER_DIR
&& !in_dir(VAR_OPER_DIR
, full_filename
)){
4812 if(!can_access(full_filename
, ACCESS_EXISTS
)){
4814 static ESCKEY_S access_opts
[] = {
4815 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4816 a file or append to the end of the file */
4817 {'o', 'o', "O", N_("Overwrite")},
4818 {'a', 'a', "A", N_("Append")},
4819 {-1, 0, NULL
, NULL
}};
4821 rbflags
= RB_NORM
| ((flags
& GE_SEQ_SENSITIVE
) ? RB_SEQ_SENSITIVE
: 0);
4823 if(flags
& GE_NO_APPEND
){
4824 r
= strlen(filename
);
4825 snprintf(prompt_buf
, sizeof(prompt_buf
),
4826 /* TRANSLATORS: asking user whether to overwrite a file or not,
4827 File <filename> already exists. Overwrite it ? */
4828 _("File \"%s%s\" already exists. Overwrite it "),
4829 (r
> 20) ? "..." : "",
4830 filename
+ ((r
> 20) ? r
- 20 : 0));
4831 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4832 if(want_to(prompt_buf
, 'n', 'x', NO_HELP
, rbflags
) == 'y'){
4834 *rflags
|= GER_OVER
;
4836 if(our_unlink(full_filename
) < 0){
4837 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
4838 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4839 _("Cannot remove old %s: %s"),
4840 full_filename
, error_description(errno
));
4848 else if(!(flags
& GE_IS_IMPORT
)){
4849 r
= strlen(filename
);
4850 snprintf(prompt_buf
, sizeof(prompt_buf
),
4851 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4852 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4853 (r
> 20) ? "..." : "",
4854 filename
+ ((r
> 20) ? r
- 20 : 0));
4855 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4856 switch(radio_buttons(prompt_buf
, -FOOTER_ROWS(ps_global
),
4857 access_opts
, 'a', 'x', NO_HELP
, rbflags
)){
4860 *rflags
|= GER_OVER
;
4862 if(our_truncate(full_filename
, (off_t
)0) < 0)
4863 /* trouble truncating, but we'll give it a try anyway */
4864 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
4865 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
4866 _("Warning: Cannot truncate old %s: %s"),
4867 full_filename
, error_description(errno
));
4872 *rflags
|= GER_APPEND
;
4885 if(history
&& ret
== 0){
4886 save_hist(*history
, full_filename
, 0, NULL
);
4887 strncpy(tmp
, full_filename
, MAXPATH
);
4888 tmp
[MAXPATH
] = '\0';
4889 if((fn
= strrchr(tmp
, C_FILESEP
)) != NULL
)
4891 save_hist(dir_hist
, tmp
, 0, NULL
);
4894 if(opts
&& opts
!= optsarg
)
4895 fs_give((void **) &opts
);
4901 /*----------------------------------------------------------------------
4902 parse the config'd upload/download command
4904 Args: cmd -- buffer to return command fit for shellin'
4907 fname -- file name to build into the command
4909 Returns: pointer to cmd_str buffer or NULL on real bad error
4911 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
4912 cfg_str is written to standard out right before a successful
4913 return of this function. The call immediately following this
4914 function darn well better be the shell exec...
4917 build_updown_cmd(char *cmd
, size_t cmdlen
, char *prefix
, char *cfg_str
, char *fname
)
4920 int fname_found
= 0;
4922 if(prefix
&& *prefix
){
4923 /* loop thru replacing all occurances of _FILE_ */
4924 p
= strncpy(cmd
, prefix
, cmdlen
);
4925 cmd
[cmdlen
-1] = '\0';
4926 while((p
= strstr(p
, "_FILE_")))
4927 rplstr(p
, cmdlen
-(p
-cmd
), 6, fname
);
4932 /* loop thru replacing all occurances of _FILE_ */
4933 p
= strncpy(cmd
, cfg_str
, cmdlen
);
4934 cmd
[cmdlen
-1] = '\0';
4935 while((p
= strstr(p
, "_FILE_"))){
4936 rplstr(p
, cmdlen
-(p
-cmd
), 6, fname
);
4941 snprintf(cmd
+strlen(cmd
), cmdlen
-strlen(cmd
), " %s", fname
);
4943 cmd
[cmdlen
-1] = '\0';
4945 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
4951 /*----------------------------------------------------------------------
4952 Write a berzerk format message delimiter using the given putc function
4954 Args: e -- envelope of message to write
4955 pc -- function to use
4957 Returns: TRUE if we could write it, FALSE if there was a problem
4959 NOTE: follows delimiter with OS-dependent newline
4962 bezerk_delimiter(ENVELOPE
*env
, MESSAGECACHE
*mc
, gf_io_t pc
, int leading_newline
)
4968 /* write "[\n]From mailbox[@host] " */
4969 if(!((leading_newline
? gf_puts(NEWLINE
, pc
) : 1)
4970 && gf_puts("From ", pc
)
4971 && gf_puts((env
&& env
->from
) ? env
->from
->mailbox
4972 : "the-concourse-on-high", pc
)
4973 && gf_puts((env
&& env
->from
&& env
->from
->host
) ? "@" : "", pc
)
4974 && gf_puts((env
&& env
->from
&& env
->from
->host
) ? env
->from
->host
4980 when
= mail_longdate(mc
);
4981 else if(env
&& env
->date
&& env
->date
[0]
4982 && mail_parse_date(&telt
,env
->date
))
4983 when
= mail_longdate(&telt
);
4989 while(p
&& *p
&& *p
!= '\n') /* write date */
4993 if(!gf_puts(NEWLINE
, pc
)) /* write terminating newline */
5000 /*----------------------------------------------------------------------
5001 Execute command to jump to a given message number
5003 Args: qline -- Line to ask question on
5005 Result: returns true if the use selected a new message, false otherwise
5009 jump_to(MSGNO_S
*msgmap
, int qline
, UCS first_num
, SCROLL_S
*sparms
, CmdWhere in_index
)
5011 char jump_num_string
[80], *j
, prompt
[70];
5014 static ESCKEY_S jump_to_key
[] = { {0, 0, NULL
, NULL
},
5015 /* TRANSLATORS: go to First Message */
5016 {ctrl('Y'), 10, "^Y", N_("First Msg")},
5017 {ctrl('V'), 11, "^V", N_("Last Msg")},
5018 {-1, 0, NULL
, NULL
} };
5020 dprint((4, "\n - jump_to -\n"));
5023 if(sparms
&& sparms
->jump_is_debug
)
5024 return(get_level(qline
, first_num
, sparms
));
5027 if(!any_messages(msgmap
, NULL
, "to Jump to"))
5030 if(first_num
&& first_num
< 0x80 && isdigit((unsigned char) first_num
)){
5031 jump_num_string
[0] = first_num
;
5032 jump_num_string
[1] = '\0';
5035 jump_num_string
[0] = '\0';
5037 if(mn_total_cur(msgmap
) > 1L){
5038 snprintf(prompt
, sizeof(prompt
), "Unselect %s msgs in favor of number to be entered",
5039 comatose(mn_total_cur(msgmap
)));
5040 prompt
[sizeof(prompt
)-1] = '\0';
5041 if((rc
= want_to(prompt
, 'n', 0, NO_HELP
, WT_NORM
)) == 'n')
5045 snprintf(prompt
, sizeof(prompt
), "%s number to jump to : ", in_index
== ThrdIndx
5048 prompt
[sizeof(prompt
)-1] = '\0';
5052 int flags
= OE_APPEND_CURRENT
;
5054 rc
= optionally_enter(jump_num_string
, qline
, 0,
5055 sizeof(jump_num_string
), prompt
,
5056 jump_to_key
, help
, &flags
);
5058 help
= help
== NO_HELP
5059 ? (in_index
== ThrdIndx
? h_oe_jump_thd
: h_oe_jump
)
5063 else if(rc
== 10 || rc
== 11){
5067 closest
= closest_jump_target(rc
== 10 ? 1L
5068 : ((in_index
== ThrdIndx
)
5069 ? msgmap
->max_thrdno
5070 : mn_get_total(msgmap
)),
5071 ps_global
->mail_stream
,
5073 in_index
, warning
, sizeof(warning
));
5074 /* ignore warning */
5079 * If we take out the *jump_num_string nonempty test in this if
5080 * then the closest_jump_target routine will offer a jump to the
5081 * last message. However, it is slow because you have to wait for
5082 * the status message and it is annoying for people who hit J command
5083 * by mistake and just want to hit return to do nothing, like has
5084 * always worked. So the test is there for now. Hubert 2002-08-19
5086 * Jumping to first/last message is now possible through ^Y/^V
5087 * commands above. jpf 2002-08-21
5088 * (and through "end" hubert 2006-07-07)
5090 if(rc
== 0 && *jump_num_string
!= '\0'){
5091 removing_leading_and_trailing_white_space(jump_num_string
);
5092 for(j
=jump_num_string
; isdigit((unsigned char)*j
) || *j
=='-'; j
++)
5096 if(!strucmp("end", j
))
5097 return((in_index
== ThrdIndx
) ? msgmap
->max_thrdno
: mn_get_total(msgmap
));
5099 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5100 _("Invalid number entered. Use only digits 0-9"));
5101 jump_num_string
[0] = '\0';
5105 long closest
, jump_num
;
5107 if(*jump_num_string
)
5108 jump_num
= atol(jump_num_string
);
5113 closest
= closest_jump_target(jump_num
, ps_global
->mail_stream
,
5115 *jump_num_string
? 0 : 1,
5116 in_index
, warning
, sizeof(warning
));
5118 q_status_message(SM_ORDER
| SM_DING
, 2, 2, warning
);
5120 if(closest
== jump_num
)
5124 jump_num_string
[0] = '\0';
5126 strncpy(jump_num_string
, long2string(closest
),
5127 sizeof(jump_num_string
));
5142 * cmd_delete_action - handle msgno advance and such after single message deletion
5145 cmd_delete_action(struct pine
*state
, MSGNO_S
*msgmap
, CmdWhere in_index
)
5151 msgno
= mn_get_cur(msgmap
);
5152 advance_cur_after_delete(state
, state
->mail_stream
, msgmap
, in_index
);
5154 if(IS_NEWS(state
->mail_stream
)
5155 || ((state
->context_current
->use
& CNTXT_INCMNG
)
5156 && context_isambig(state
->cur_folder
))){
5158 opts
= (NSF_TRUST_FLAGS
| NSF_SKIP_CHID
);
5159 if(in_index
== View
)
5160 opts
&= ~NSF_SKIP_CHID
;
5162 (void)next_sorted_flagged(F_UNDEL
|F_UNSEEN
, state
->mail_stream
, msgno
, &opts
);
5163 if(!(opts
& NSF_FLAG_MATCH
)){
5164 char nextfolder
[MAXPATH
];
5166 strncpy(nextfolder
, state
->cur_folder
, sizeof(nextfolder
));
5167 nextfolder
[sizeof(nextfolder
)-1] = '\0';
5168 rv
= next_folder(NULL
, nextfolder
, sizeof(nextfolder
), nextfolder
,
5169 state
->context_current
, NULL
, NULL
)
5170 ? ". Press TAB for next folder."
5171 : ". No more folders to TAB to.";
5180 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5183 cmd_delete_index(struct pine
*state
, MSGNO_S
*msgmap
)
5185 return(cmd_delete_action(state
, msgmap
,MsgIndx
));
5189 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5192 cmd_delete_view(struct pine
*state
, MSGNO_S
*msgmap
)
5194 return(cmd_delete_action(state
, msgmap
, View
));
5199 advance_cur_after_delete(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, CmdWhere in_index
)
5201 long new_msgno
, msgno
;
5204 new_msgno
= msgno
= mn_get_cur(msgmap
);
5205 opts
= NSF_TRUST_FLAGS
;
5207 if(F_ON(F_DEL_SKIPS_DEL
, state
)){
5209 if(THREADING() && sp_viewing_a_thread(stream
))
5210 opts
|= NSF_SKIP_CHID
;
5212 new_msgno
= next_sorted_flagged(F_UNDEL
, stream
, msgno
, &opts
);
5215 mn_inc_cur(stream
, msgmap
,
5216 (in_index
== View
&& THREADING()
5217 && sp_viewing_a_thread(stream
))
5219 : (in_index
== View
)
5220 ? MH_ANYTHD
: MH_NONE
);
5221 new_msgno
= mn_get_cur(msgmap
);
5222 if(new_msgno
!= msgno
)
5223 opts
|= NSF_FLAG_MATCH
;
5227 * Viewing_a_thread is the complicated case because we want to ignore
5228 * other threads at first and then look in other threads if we have to.
5229 * By ignoring other threads we also ignore collapsed partial threads
5230 * in our own thread.
5232 if(THREADING() && sp_viewing_a_thread(stream
) && !(opts
& NSF_FLAG_MATCH
)){
5233 long rawno
, orig_thrdno
;
5234 PINETHRD_S
*thrd
, *topthrd
= NULL
;
5236 rawno
= mn_m2raw(msgmap
, msgno
);
5237 thrd
= fetch_thread(stream
, rawno
);
5238 if(thrd
&& thrd
->top
)
5239 topthrd
= fetch_thread(stream
, thrd
->top
);
5241 orig_thrdno
= topthrd
? topthrd
->thrdno
: -1L;
5243 opts
= NSF_TRUST_FLAGS
;
5244 new_msgno
= next_sorted_flagged(F_UNDEL
, stream
, msgno
, &opts
);
5247 * If we got a match, new_msgno may be a message in
5248 * a different thread from the one we are viewing, or it could be
5249 * in a collapsed part of this thread.
5251 if(opts
& NSF_FLAG_MATCH
){
5256 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
,new_msgno
));
5257 if(thrd
&& thrd
->top
)
5258 topthrd
= fetch_thread(stream
, thrd
->top
);
5261 * If this match is in the same thread we're already in
5262 * then we're done, else we have to ask the user and maybe
5265 if(!(orig_thrdno
> 0L && topthrd
5266 && topthrd
->thrdno
== orig_thrdno
)){
5268 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD
, state
)){
5269 if(in_index
== View
)
5270 snprintf(pmt
, sizeof(pmt
),
5271 "View message in thread number %.10s",
5272 topthrd
? comatose(topthrd
->thrdno
) : "?");
5274 snprintf(pmt
, sizeof(pmt
), "View thread number %.10s",
5275 topthrd
? comatose(topthrd
->thrdno
) : "?");
5277 ret
= want_to(pmt
, 'y', 'x', NO_HELP
, WT_NORM
);
5283 unview_thread(state
, stream
, msgmap
);
5284 mn_set_cur(msgmap
, new_msgno
);
5286 && (count_lflags_in_thread(stream
, topthrd
, msgmap
,
5288 && view_thread(state
, stream
, msgmap
, 1)){
5289 if(current_index_state
)
5290 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
5292 state
->view_skipped_index
= 1;
5293 state
->next_screen
= mail_view_screen
;
5296 view_thread(state
, stream
, msgmap
, 1);
5297 if(current_index_state
)
5298 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
5300 state
->next_screen
= SCREEN_FUN_NULL
;
5304 new_msgno
= msgno
; /* stick with original */
5309 mn_set_cur(msgmap
, new_msgno
);
5310 if(in_index
!= View
)
5311 adjust_cur_to_visible(stream
, msgmap
);
5317 get_level(int qline
, UCS first_num
, SCROLL_S
*sparms
)
5319 char debug_num_string
[80], *j
, prompt
[70];
5324 if(first_num
&& first_num
< 0x80 && isdigit((unsigned char)first_num
)){
5325 debug_num_string
[0] = first_num
;
5326 debug_num_string
[1] = '\0';
5327 debug_num
= atol(debug_num_string
);
5328 *(int *)(sparms
->proc
.data
.p
) = debug_num
;
5329 q_status_message1(SM_ORDER
, 0, 3, "Show debug <= level %s",
5330 comatose(debug_num
));
5334 debug_num_string
[0] = '\0';
5336 snprintf(prompt
, sizeof(prompt
), "Show debug <= this level (0-%d) : ", MAX(debug
, 9));
5337 prompt
[sizeof(prompt
)-1] = '\0';
5341 int flags
= OE_APPEND_CURRENT
;
5343 rc
= optionally_enter(debug_num_string
, qline
, 0,
5344 sizeof(debug_num_string
), prompt
,
5345 NULL
, help
, &flags
);
5347 help
= help
== NO_HELP
? h_oe_debuglevel
: NO_HELP
;
5352 removing_leading_and_trailing_white_space(debug_num_string
);
5353 for(j
=debug_num_string
; isdigit((unsigned char)*j
); j
++)
5357 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5358 _("Invalid number entered. Use only digits 0-9"));
5359 debug_num_string
[0] = '\0';
5362 debug_num
= atol(debug_num_string
);
5364 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5365 _("Number should be >= 0"));
5366 else if(debug_num
> MAX(debug
,9))
5367 q_status_message1(SM_ORDER
| SM_DING
, 2, 2,
5368 _("Maximum is %s"), comatose(MAX(debug
,9)));
5370 *(int *)(sparms
->proc
.data
.p
) = debug_num
;
5371 q_status_message1(SM_ORDER
, 0, 3,
5372 "Show debug <= level %s",
5373 comatose(debug_num
));
5391 * Returns the message number closest to target that isn't hidden.
5392 * Make warning at least 100 chars.
5393 * A return of 0 means there is no message to jump to.
5396 closest_jump_target(long int target
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int no_target
, CmdWhere in_index
, char *warning
, size_t warninglen
)
5398 long i
, start
, closest
= 0L;
5403 maxnum
= (in_index
== ThrdIndx
) ? msgmap
->max_thrdno
: mn_get_total(msgmap
);
5408 snprintf(warning
, warninglen
, "No %s number entered, jump to end? ",
5409 (in_index
== ThrdIndx
) ? "thread" : "message");
5410 warning
[warninglen
-1] = '\0';
5412 else if(target
< 1L)
5413 start
= 1L - target
;
5414 else if(target
> maxnum
)
5415 start
= target
- maxnum
;
5419 if(target
> 0L && target
<= maxnum
)
5420 if(in_index
== ThrdIndx
5421 || !msgline_hidden(stream
, msgmap
, target
, 0))
5424 for(i
= start
; target
+i
<= maxnum
|| target
-i
> 0L; i
++){
5426 if(target
+i
> 0L && target
+i
<= maxnum
&&
5427 (in_index
== ThrdIndx
5428 || !msgline_hidden(stream
, msgmap
, target
+i
, 0))){
5433 if(target
-i
> 0L && target
-i
<= maxnum
&&
5434 (in_index
== ThrdIndx
5435 || !msgline_hidden(stream
, msgmap
, target
-i
, 0))){
5441 strncpy(buf
, long2string(closest
), sizeof(buf
));
5442 buf
[sizeof(buf
)-1] = '\0';
5445 strncpy(warning
, "Nothing to jump to", warninglen
);
5446 else if(target
< 1L)
5447 snprintf(warning
, warninglen
, "%s number (%s) must be at least %s",
5448 (in_index
== ThrdIndx
) ? "Thread" : "Message",
5449 long2string(target
), buf
);
5450 else if(target
> maxnum
)
5451 snprintf(warning
, warninglen
, "%s number (%s) may be no more than %s",
5452 (in_index
== ThrdIndx
) ? "Thread" : "Message",
5453 long2string(target
), buf
);
5455 snprintf(warning
, warninglen
,
5456 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5457 long2string(target
), buf
);
5459 warning
[warninglen
-1] = '\0';
5465 /*----------------------------------------------------------------------
5466 Prompt for folder name to open, expand the name and return it
5468 Args: qline -- Screen line to prompt on
5469 allow_list -- if 1, allow ^T to bring up collection lister
5471 Result: returns the folder name or NULL
5472 pine structure mangled_footer flag is set
5473 may call the collection lister in which case mangled screen will be set
5475 This prompts the user for the folder to open, possibly calling up
5476 the collection lister if the user types ^T.
5477 ----------------------------------------------------------------------*/
5479 broach_folder(int qline
, int allow_list
, int *notrealinbox
, CONTEXT_S
**context
)
5482 static char newfolder
[MAILTMPLEN
];
5483 char expanded
[MAXPATH
+1],
5484 prompt
[MAX_SCREEN_COLS
+1],
5486 unsigned char *f1
, *f2
, *f3
;
5487 static HISTORY_S
*history
= NULL
;
5488 CONTEXT_S
*tc
, *tc2
;
5490 int rc
, r
, ku
= -1, n
, flags
, last_rc
= 0, inbox
, done
= 0;
5493 * the idea is to provide a clue for the context the file name
5494 * will be saved in (if a non-imap names is typed), and to
5495 * only show the previous if it was also in the same context
5502 (*notrealinbox
) = 1;
5504 init_hist(&history
, HISTSIZE
);
5506 tc
= broach_get_folder(context
? *context
: NULL
, &inbox
, NULL
);
5508 /* set up extra command option keys */
5510 ekey
[rc
].ch
= (allow_list
) ? ctrl('T') : 0 ;
5511 ekey
[rc
].rval
= (allow_list
) ? 2 : 0;
5512 ekey
[rc
].name
= (allow_list
) ? "^T" : "";
5513 ekey
[rc
++].label
= (allow_list
) ? N_("ToFldrs") : "";
5515 if(ps_global
->context_list
->next
){
5516 ekey
[rc
].ch
= ctrl('P');
5518 ekey
[rc
].name
= "^P";
5519 ekey
[rc
++].label
= N_("Prev Collection");
5521 ekey
[rc
].ch
= ctrl('N');
5523 ekey
[rc
].name
= "^N";
5524 ekey
[rc
++].label
= N_("Next Collection");
5527 ekey
[rc
].ch
= ctrl('W');
5529 ekey
[rc
].name
= "^W";
5530 ekey
[rc
++].label
= N_("INBOX");
5532 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps_global
)){
5535 ekey
[rc
].name
= "TAB";
5536 ekey
[rc
++].label
= N_("Complete");
5539 if(F_ON(F_ENABLE_SUB_LISTS
, ps_global
)){
5540 ekey
[rc
].ch
= ctrl('X');
5542 ekey
[rc
].name
= "^X";
5543 ekey
[rc
++].label
= N_("ListMatches");
5546 if(ps_global
->context_list
->next
&& F_ON(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
5547 ekey
[rc
].ch
= KEY_UP
;
5550 ekey
[rc
++].label
= "";
5552 ekey
[rc
].ch
= KEY_DOWN
;
5555 ekey
[rc
++].label
= "";
5557 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
5558 ekey
[rc
].ch
= KEY_UP
;
5562 ekey
[rc
++].label
= "";
5564 ekey
[rc
].ch
= KEY_DOWN
;
5567 ekey
[rc
++].label
= "";
5574 * Figure out next default value for this context. The idea
5575 * is that in each context the last folder opened is cached.
5576 * It's up to pick it out and display it. This is fine
5577 * and dandy if we've currently got the inbox open, BUT
5578 * if not, make the inbox the default the first time thru.
5581 last_folder
= ps_global
->inbox_name
;
5582 inbox
= 1; /* pretend we're in inbox from here on out */
5585 last_folder
= (ps_global
->last_unambig_folder
[0])
5586 ? ps_global
->last_unambig_folder
5587 : ((tc
->last_folder
[0]) ? tc
->last_folder
: NULL
);
5589 if(last_folder
){ /* MAXPATH + 1 = sizeof(expanded) */
5590 unsigned char *fname
= folder_name_decoded((unsigned char *)last_folder
);
5591 snprintf(expanded
, sizeof(expanded
), " [%.*s]", MAXPATH
+1-5,
5592 fname
? (char *) fname
: last_folder
);
5593 if(fname
) fs_give((void **)&fname
);
5598 expanded
[sizeof(expanded
)-1] = '\0';
5600 /* only show collection number if more than one available */
5601 if(ps_global
->context_list
->next
) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5602 snprintf(prompt
, sizeof(prompt
), "GOTO %s in <%s> %.*s%s: ",
5603 NEWS_TEST(tc
) ? "news group" : "folder",
5604 tc
->nickname
, MAX_SCREEN_COLS
+1-50, expanded
,
5605 *expanded
? " " : "");
5606 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5607 snprintf(prompt
, sizeof(prompt
), "GOTO folder %.*s%s: ", MAX_SCREEN_COLS
+1-20, expanded
,
5608 *expanded
? " " : "");
5610 prompt
[sizeof(prompt
)-1] = '\0';
5612 if(utf8_width(prompt
) > MAXPROMPT
){
5613 if(ps_global
->context_list
->next
) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5614 snprintf(prompt
, sizeof(prompt
), "GOTO <%s> %.*s%s: ",
5615 tc
->nickname
, MAX_SCREEN_COLS
+1-50, expanded
,
5616 *expanded
? " " : "");
5617 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5618 snprintf(prompt
, sizeof(prompt
), "GOTO %.*s%s: ", MAX_SCREEN_COLS
+1-20, expanded
,
5619 *expanded
? " " : "");
5621 prompt
[sizeof(prompt
)-1] = '\0';
5623 if(utf8_width(prompt
) > MAXPROMPT
){
5624 if(ps_global
->context_list
->next
) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5625 snprintf(prompt
, sizeof(prompt
), "<%s> %.*s%s: ",
5626 tc
->nickname
, MAX_SCREEN_COLS
+1-50, expanded
,
5627 *expanded
? " " : "");
5628 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5629 snprintf(prompt
, sizeof(prompt
), "%.*s%s: ", MAX_SCREEN_COLS
+1-20, expanded
,
5630 *expanded
? " " : "");
5632 prompt
[sizeof(prompt
)-1] = '\0';
5637 if(items_in_hist(history
) > 1){
5638 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
5639 ekey
[ku
].label
= HISTORY_KEYLABEL
;
5640 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
5641 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
5645 ekey
[ku
].label
= "";
5646 ekey
[ku
+1].name
= "";
5647 ekey
[ku
+1].label
= "";
5651 /* is there any other way to do this? The point is that we
5652 * are trying to hide mutf7 from the user, and use the utf8
5653 * equivalent. So we create a variable f to take place of
5654 * newfolder, including content and size. f2 is copy of f1
5655 * that has to freed. Sigh!
5657 f3
= (unsigned char *) cpystr(newfolder
);
5658 f1
= fs_get(sizeof(newfolder
));
5659 f2
= folder_name_decoded(f3
);
5660 if(f3
) fs_give((void **)&f3
);
5661 strncpy((char *)f1
, (char *)f2
, sizeof(newfolder
));
5662 f1
[sizeof(newfolder
)-1] = '\0';
5663 if(f2
) fs_give((void **)&f2
);
5665 flags
= OE_APPEND_CURRENT
;
5666 rc
= optionally_enter((char *) f1
, qline
, 0, sizeof(newfolder
),
5667 (char *) prompt
, ekey
, help
, &flags
);
5669 f2
= folder_name_encoded(f1
);
5670 strncpy(newfolder
, (char *)f2
, sizeof(newfolder
));
5671 if(f1
) fs_give((void **)&f1
);
5672 if(f2
) fs_give((void **)&f2
);
5674 ps_global
->mangled_footer
= 1;
5677 case -1 : /* o_e says error! */
5678 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
5679 _("Error reading folder name"));
5682 case 0 : /* o_e says normal entry */
5683 removing_trailing_white_space(newfolder
);
5684 removing_leading_white_space(newfolder
);
5687 char *name
, *fullname
= NULL
;
5688 int exists
, breakout
= 0;
5690 save_hist(history
, newfolder
, 0, tc
);
5692 if(!(name
= folder_is_nick(newfolder
, FOLDERS(tc
),
5696 if(update_folder_spec(expanded
, sizeof(expanded
), name
)){
5697 strncpy(name
= newfolder
, expanded
, sizeof(newfolder
));
5698 newfolder
[sizeof(newfolder
)-1] = '\0';
5701 exists
= folder_name_exists(tc
, name
, &fullname
);
5704 strncpy(name
= newfolder
, fullname
, sizeof(newfolder
));
5705 newfolder
[sizeof(newfolder
)-1] = '\0';
5706 fs_give((void **) &fullname
);
5711 * if we know the things a folder, open it.
5712 * else if we know its a directory, visit it.
5713 * else we're not sure (it either doesn't really
5714 * exist or its unLISTable) so try opening it anyway
5716 if(exists
& FEX_ISFILE
){
5720 else if((exists
& FEX_ISDIR
)){
5722 CONTEXT_S
*fake_context
;
5723 char tmp
[MAILTMPLEN
];
5726 strncpy(tmp
, name
, sizeof(tmp
));
5727 tmp
[sizeof(tmp
)-2-1] = '\0';
5728 if(tmp
[(l
= strlen(tmp
)) - 1] != tc
->dir
->delim
){
5729 if(l
< sizeof(tmp
)){
5730 tmp
[l
] = tc
->dir
->delim
;
5731 strncpy(&tmp
[l
+1], "[]", sizeof(tmp
)-(l
+1));
5735 strncat(tmp
, "[]", sizeof(tmp
)-strlen(tmp
)-1);
5737 tmp
[sizeof(tmp
)-1] = '\0';
5739 fake_context
= new_context(tmp
, 0);
5740 newfolder
[0] = '\0';
5741 done
= display_folder_list(&fake_context
, newfolder
,
5742 1, folders_for_goto
);
5743 free_context(&fake_context
);
5746 else if(!(tc
->use
& CNTXT_INCMNG
)){
5747 done
= display_folder_list(&tc
, newfolder
,
5748 1, folders_for_goto
);
5752 else if((exists
& FEX_ERROR
)){
5753 q_status_message1(SM_ORDER
, 0, 3,
5754 _("Problem accessing folder \"%s\""),
5763 if(exists
== FEX_ERROR
)
5764 q_status_message1(SM_ORDER
, 0, 3,
5765 _("Problem accessing folder \"%s\""),
5767 else if(tc
->use
& CNTXT_INCMNG
)
5768 q_status_message1(SM_ORDER
, 0, 3,
5769 _("Can't find Incoming Folder: %s"),
5771 else if(context_isambig(newfolder
))
5772 q_status_message2(SM_ORDER
, 0, 3,
5773 _("Can't find folder \"%s\" in %s"),
5774 newfolder
, (void *) tc
->nickname
);
5776 q_status_message1(SM_ORDER
, 0, 3,
5777 _("Can't find folder \"%s\""),
5782 else if(last_folder
){
5783 if(ps_global
->goto_default_rule
== GOTO_FIRST_CLCTN_DEF_INBOX
5784 && !strucmp(last_folder
, ps_global
->inbox_name
)
5785 && tc
== ((ps_global
->context_list
->use
& CNTXT_INCMNG
)
5786 ? ps_global
->context_list
->next
: ps_global
->context_list
)){
5788 (*notrealinbox
) = 0;
5790 tc
= ps_global
->context_list
;
5793 strncpy(newfolder
, last_folder
, sizeof(newfolder
));
5794 newfolder
[sizeof(newfolder
)-1] = '\0';
5795 save_hist(history
, newfolder
, 0, tc
);
5799 /* fall thru like they cancelled */
5801 case 1 : /* o_e says user cancel */
5802 cmd_cancelled("Open folder");
5805 case 2 : /* o_e says user wants list */
5806 r
= display_folder_list(&tc
, newfolder
, 0, folders_for_goto
);
5812 case 3 : /* o_e says user wants help */
5813 help
= help
== NO_HELP
? h_oe_broach
: NO_HELP
;
5816 case 4 : /* redraw */
5819 case 10 : /* Previous collection */
5820 tc2
= ps_global
->context_list
;
5821 while(tc2
->next
&& tc2
->next
!= tc
)
5827 case 11 : /* Next collection */
5828 tc
= (tc
->next
) ? tc
->next
: ps_global
->context_list
;
5831 case 12 : /* file name completion */
5832 if(!folder_complete(tc
, newfolder
, sizeof(newfolder
), &n
)){
5833 if(n
&& last_rc
== 12 && !(flags
& OE_USER_MODIFIED
)){
5834 r
= display_folder_list(&tc
, newfolder
, 1,folders_for_goto
);
5836 done
++; /* bingo! */
5838 rc
= 0; /* burn last_rc */
5846 case 14 : /* file name completion */
5847 r
= display_folder_list(&tc
, newfolder
, 2, folders_for_goto
);
5849 done
++; /* bingo! */
5851 rc
= 0; /* burn last_rc */
5855 case 17 : /* GoTo INBOX */
5857 strncpy(newfolder
, ps_global
->inbox_name
, sizeof(newfolder
)-1);
5858 newfolder
[sizeof(newfolder
)-1] = '\0';
5860 (*notrealinbox
) = 0;
5862 tc
= ps_global
->context_list
;
5863 save_hist(history
, newfolder
, 0, tc
);
5868 if((p
= get_prev_hist(history
, newfolder
, 0, tc
)) != NULL
){
5869 strncpy(newfolder
, p
, sizeof(newfolder
));
5870 newfolder
[sizeof(newfolder
)-1] = '\0';
5871 if(history
->hist
[history
->curindex
])
5872 tc
= history
->hist
[history
->curindex
]->cntxt
;
5880 if((p
= get_next_hist(history
, newfolder
, 0, tc
)) != NULL
){
5881 strncpy(newfolder
, p
, sizeof(newfolder
));
5882 newfolder
[sizeof(newfolder
)-1] = '\0';
5883 if(history
->hist
[history
->curindex
])
5884 tc
= history
->hist
[history
->curindex
]->cntxt
;
5892 alpine_panic("Unhandled case");
5899 dprint((2, "broach folder, name entered \"%s\"\n",
5900 newfolder
? newfolder
: "?"));
5902 /*-- Just check that we can expand this. It gets done for real later --*/
5903 strncpy(expanded
, newfolder
, sizeof(expanded
));
5904 expanded
[sizeof(expanded
)-1] = '\0';
5906 if(!expand_foldername(expanded
, sizeof(expanded
))) {
5907 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
5908 expanded
? expanded
: "?"));
5917 /*----------------------------------------------------------------------
5918 Check to see if user wants to reopen dead stream.
5923 Result: 1 if the folder was successfully updatedn
5928 ask_mailbox_reopen(struct pine
*ps
, int *reopenp
)
5930 if(((ps
->mail_stream
->dtb
5931 && ((ps
->mail_stream
->dtb
->flags
& DR_NONEWMAIL
)
5932 || (ps
->mail_stream
->rdonly
5933 && ps
->mail_stream
->dtb
->flags
& DR_NONEWMAILRONLY
)))
5934 && (ps
->reopen_rule
== REOPEN_ASK_ASK_Y
5935 || ps
->reopen_rule
== REOPEN_ASK_ASK_N
5936 || ps
->reopen_rule
== REOPEN_ASK_NO_Y
5937 || ps
->reopen_rule
== REOPEN_ASK_NO_N
))
5938 || ((ps
->mail_stream
->dtb
5939 && ps
->mail_stream
->rdonly
5940 && !(ps
->mail_stream
->dtb
->flags
& DR_LOCAL
))
5941 && (ps
->reopen_rule
== REOPEN_YES_ASK_Y
5942 || ps
->reopen_rule
== REOPEN_YES_ASK_N
5943 || ps
->reopen_rule
== REOPEN_ASK_ASK_Y
5944 || ps
->reopen_rule
== REOPEN_ASK_ASK_N
))){
5947 switch(ps
->reopen_rule
){
5948 case REOPEN_YES_ASK_Y
:
5949 case REOPEN_ASK_ASK_Y
:
5950 case REOPEN_ASK_NO_Y
:
5959 switch(want_to("Re-open folder to check for new messages", deefault
,
5960 'x', h_reopen_folder
, WT_NORM
)){
5975 /*----------------------------------------------------------------------
5976 Check to see if user input is in form of old c-client mailbox speck
5981 Result: 1 if the folder was successfully updatedn
5986 update_folder_spec(char *new, size_t newlen
, char *old
)
5992 if(*(p
= old
) == '*') /* old form? */
5995 if(*old
== '{') /* copy host spec */
5997 switch(*new = *old
++){
6002 if(!struncmp(old
, "nntp", 4))
6010 while(*new++ != '}' && (new-orignew
) < newlen
-1);
6012 if((*p
== '*' && *old
) || ((*old
== '*') ? *++old
: 0)){
6014 * OK, some heuristics here. If it looks like a newsgroup
6015 * then we plunk it into the #news namespace else we
6016 * assume that they're trying to get at a #public folder...
6019 *p
&& (isalnum((unsigned char) *p
) || strindex(".-", *p
));
6023 sstrncpy(&new, (*p
&& !nntp
) ? "#public/" : "#news.", newlen
-(new-orignew
));
6024 strncpy(new, old
, newlen
-(new-orignew
));
6028 orignew
[newlen
-1] = '\0';
6034 /*----------------------------------------------------------------------
6035 Open the requested folder in the requested context
6037 Args: state -- usual pine state struct
6038 newfolder -- folder to open
6039 new_context -- folder context might live in
6040 stream -- candidate for recycling
6042 Result: New folder open or not (if error), and we're set to
6043 enter the index screen.
6046 visit_folder(struct pine
*state
, char *newfolder
, CONTEXT_S
*new_context
,
6047 MAILSTREAM
*stream
, long unsigned int flags
)
6049 dprint((9, "visit_folder(%s, %s)\n",
6050 newfolder
? newfolder
: "?",
6051 (new_context
&& new_context
->context
)
6052 ? new_context
->context
: "(NULL)"));
6054 if(ps_global
&& ps_global
->ttyo
){
6055 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
6056 ps_global
->mangled_footer
= 1;
6059 if(do_broach_folder(newfolder
, new_context
, stream
? &stream
: NULL
,
6061 || !sp_flagged(state
->mail_stream
, SP_LOCKED
))
6062 state
->next_screen
= mail_index_screen
;
6064 state
->next_screen
= folder_screen
;
6068 /*----------------------------------------------------------------------
6069 Move read messages from folder if listed in archive
6075 read_msg_prompt(long int n
, char *f
)
6077 char buf
[MAX_SCREEN_COLS
+1];
6079 snprintf(buf
, sizeof(buf
), "Save the %ld read message%s in \"%s\"", n
, plural(n
), f
);
6080 buf
[sizeof(buf
)-1] = '\0';
6081 return(want_to(buf
, 'y', 0, NO_HELP
, WT_NORM
) == 'y');
6085 /*----------------------------------------------------------------------
6086 Print current message[s] or folder index
6088 Args: state -- pointer to struct holding a bunch of pine state
6089 msgmap -- table mapping msg nums to c-client sequence nums
6090 aopt -- aggregate options
6091 in_index -- boolean indicating we're called from Index Screen
6093 Filters the original header and sends stuff to printer
6096 cmd_print(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, CmdWhere in_index
)
6099 long i
, msgs
, rawno
;
6100 int next
= 0, do_index
= 0, rv
= 0;
6105 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
6108 msgs
= mn_total_cur(msgmap
);
6110 if((in_index
!= View
) && F_ON(F_PRINT_INDEX
, state
)){
6113 static ESCKEY_S prt_opts
[] = {
6114 {'i', 'i', "I", N_("Index")},
6115 {'m', 'm', "M", NULL
},
6116 {-1, 0, NULL
, NULL
}};
6118 if(in_index
== ThrdIndx
){
6119 /* TRANSLATORS: This is a question, Print Index ? */
6120 if(want_to(_("Print Index"), 'y', 'x', NO_HELP
, WT_NORM
) == 'y')
6126 snprintf(m
, sizeof(m
), "Message%s", (msgs
>1L) ? "s" : "");
6127 m
[sizeof(m
)-1] = '\0';
6128 prt_opts
[1].label
= m
;
6129 snprintf(prompt
, sizeof(prompt
), "Print %sFolder Index or %s %s? ",
6130 (aopt
& MCMD_AGG_2
) ? "thread " : MCMD_ISAGG(aopt
) ? "selected " : "",
6131 (aopt
& MCMD_AGG_2
) ? "thread" : MCMD_ISAGG(aopt
) ? "selected" : "current", m
);
6132 prompt
[sizeof(prompt
)-1] = '\0';
6134 ans
= radio_buttons(prompt
, -FOOTER_ROWS(state
), prt_opts
, 'm', 'x',
6135 NO_HELP
, RB_NORM
|RB_SEQ_SENSITIVE
);
6140 cmd_cancelled("Print");
6141 if(MCMD_ISAGG(aopt
))
6142 restore_selected(msgmap
);
6157 snprintf(prompt
, sizeof(prompt
), "%sFolder Index",
6158 (aopt
& MCMD_AGG_2
) ? "Thread " : MCMD_ISAGG(aopt
) ? "Selected " : "");
6160 snprintf(prompt
, sizeof(prompt
), "%s messages", long2string(msgs
));
6162 snprintf(prompt
, sizeof(prompt
), "Message %s", long2string(mn_get_cur(msgmap
)));
6164 prompt
[sizeof(prompt
)-1] = '\0';
6166 if(open_printer(prompt
) < 0){
6167 if(MCMD_ISAGG(aopt
))
6168 restore_selected(msgmap
);
6176 tc
= format_titlebar();
6178 /* Print titlebar... */
6179 print_text1("%s\n\n", tc
? tc
->titlebar_line
: "");
6180 /* then all the index members... */
6181 if(!print_index(state
, msgmap
, MCMD_ISAGG(aopt
)))
6182 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6183 _("Error printing folder index"));
6189 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
), next
++){
6190 if(next
&& F_ON(F_AGG_PRINT_FF
, state
))
6191 if(!print_char(FORMFEED
)){
6196 if(!(state
->mail_stream
6197 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
6198 && rawno
<= state
->mail_stream
->nmsgs
6199 && (mc
= mail_elt(state
->mail_stream
, rawno
))
6203 if(!(e
=pine_mail_fetchstructure(state
->mail_stream
,
6206 || (F_ON(F_FROM_DELIM_IN_PRINT
, ps_global
)
6207 && !bezerk_delimiter(e
, mc
, print_char
, next
))
6208 || !format_message(mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
6209 e
, b
, NULL
, FM_NEW_MESS
| FM_NOINDENT
,
6211 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6212 _("Error printing message"));
6221 if(MCMD_ISAGG(aopt
))
6222 restore_selected(msgmap
);
6228 /*----------------------------------------------------------------------
6231 Args: state -- various pine state bits
6232 msgmap -- Message number mapping table
6233 aopt -- option flags
6235 Filters the original header and sends stuff to specified command
6238 cmd_pipe(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
6244 char *resultfilename
= NULL
, prompt
[80], *p
;
6245 int done
= 0, rv
= 0;
6247 int fourlabel
= -1, j
= 0, next
= 0, ku
;
6248 int pipe_rv
; /* rv of proc to separate from close_system_pipe rv */
6250 unsigned flagsforhist
= 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6251 static HISTORY_S
*history
= NULL
;
6252 int capture
= 1, raw
= 0, delimit
= 0, newpipe
= 0;
6253 char pipe_command
[MAXPATH
];
6254 ESCKEY_S pipe_opt
[8];
6256 if(ps_global
->restricted
){
6257 q_status_message(SM_ORDER
| SM_DING
, 0, 4,
6258 "Alpine demo can't pipe messages");
6261 else if(!any_messages(msgmap
, NULL
, "to Pipe"))
6264 pipe_command
[0] = '\0';
6265 init_hist(&history
, HISTSIZE
);
6266 flagsforhist
= (raw
? 0x8 : 0) +
6267 (delimit
? 0x4 : 0) +
6268 (newpipe
? 0x2 : 0) +
6269 (capture
? 0x1 : 0);
6270 if((p
= get_prev_hist(history
, "", flagsforhist
, NULL
)) != NULL
){
6271 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6272 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6273 if(history
->hist
[history
->curindex
]){
6274 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6275 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6276 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6277 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6278 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6283 pipe_opt
[j
].rval
= 0;
6284 pipe_opt
[j
].name
= "";
6285 pipe_opt
[j
++].label
= "";
6287 pipe_opt
[j
].ch
= ctrl('W');
6288 pipe_opt
[j
].rval
= 10;
6289 pipe_opt
[j
].name
= "^W";
6290 pipe_opt
[j
++].label
= NULL
;
6292 pipe_opt
[j
].ch
= ctrl('Y');
6293 pipe_opt
[j
].rval
= 11;
6294 pipe_opt
[j
].name
= "^Y";
6295 pipe_opt
[j
++].label
= NULL
;
6297 pipe_opt
[j
].ch
= ctrl('R');
6298 pipe_opt
[j
].rval
= 12;
6299 pipe_opt
[j
].name
= "^R";
6300 pipe_opt
[j
++].label
= NULL
;
6302 if(MCMD_ISAGG(aopt
)){
6303 if(!pseudo_selected(state
->mail_stream
, msgmap
))
6307 pipe_opt
[j
].ch
= ctrl('T');
6308 pipe_opt
[j
].rval
= 13;
6309 pipe_opt
[j
].name
= "^T";
6310 pipe_opt
[j
++].label
= NULL
;
6314 pipe_opt
[j
].ch
= KEY_UP
;
6315 pipe_opt
[j
].rval
= 30;
6316 pipe_opt
[j
].name
= "";
6318 pipe_opt
[j
++].label
= "";
6320 pipe_opt
[j
].ch
= KEY_DOWN
;
6321 pipe_opt
[j
].rval
= 31;
6322 pipe_opt
[j
].name
= "";
6323 pipe_opt
[j
++].label
= "";
6325 pipe_opt
[j
].ch
= -1;
6330 snprintf(prompt
, sizeof(prompt
), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6332 MCMD_ISAGG(aopt
) ? "s" : " ",
6333 MCMD_ISAGG(aopt
) ? "" : comatose(mn_get_cur(msgmap
)),
6334 (!capture
|| delimit
|| (newpipe
&& MCMD_ISAGG(aopt
))) ? "(" : "",
6335 capture
? "" : "uncaptured",
6336 (!capture
&& delimit
) ? "," : "",
6337 delimit
? "delimited" : "",
6338 ((!capture
|| delimit
) && newpipe
&& MCMD_ISAGG(aopt
)) ? "," : "",
6339 (newpipe
&& MCMD_ISAGG(aopt
)) ? "new pipe" : "",
6340 (!capture
|| delimit
|| (newpipe
&& MCMD_ISAGG(aopt
))) ? ") " : "");
6341 prompt
[sizeof(prompt
)-1] = '\0';
6342 pipe_opt
[1].label
= raw
? N_("Shown Text") : N_("Raw Text");
6343 pipe_opt
[2].label
= capture
? N_("Free Output") : N_("Capture Output");
6344 pipe_opt
[3].label
= delimit
? N_("No Delimiter") : N_("With Delimiter");
6346 pipe_opt
[fourlabel
].label
= newpipe
? N_("To Same Pipe") : N_("To Individual Pipes");
6350 * 2 is really 1 because there will be one real entry and
6351 * one entry of "" because of the get_prev_hist above.
6353 if(items_in_hist(history
) > 2){
6354 pipe_opt
[ku
].name
= HISTORY_UP_KEYNAME
;
6355 pipe_opt
[ku
].label
= HISTORY_KEYLABEL
;
6356 pipe_opt
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
6357 pipe_opt
[ku
+1].label
= HISTORY_KEYLABEL
;
6360 pipe_opt
[ku
].name
= "";
6361 pipe_opt
[ku
].label
= "";
6362 pipe_opt
[ku
+1].name
= "";
6363 pipe_opt
[ku
+1].label
= "";
6366 flags
= OE_APPEND_CURRENT
| OE_SEQ_SENSITIVE
;
6367 switch(optionally_enter(pipe_command
, -FOOTER_ROWS(state
), 0,
6368 sizeof(pipe_command
), prompt
,
6369 pipe_opt
, NO_HELP
, &flags
)){
6371 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
6372 _("Internal problem encountered"));
6376 case 10 : /* flip raw bit */
6380 case 11 : /* flip capture bit */
6384 case 12 : /* flip delimit bit */
6388 case 13 : /* flip newpipe bit */
6393 flagsforhist
= (raw
? 0x8 : 0) +
6394 (delimit
? 0x4 : 0) +
6395 (newpipe
? 0x2 : 0) +
6396 (capture
? 0x1 : 0);
6397 if((p
= get_prev_hist(history
, pipe_command
, flagsforhist
, NULL
)) != NULL
){
6398 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6399 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6400 if(history
->hist
[history
->curindex
]){
6401 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6402 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6403 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6404 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6405 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6414 flagsforhist
= (raw
? 0x8 : 0) +
6415 (delimit
? 0x4 : 0) +
6416 (newpipe
? 0x2 : 0) +
6417 (capture
? 0x1 : 0);
6418 if((p
= get_next_hist(history
, pipe_command
, flagsforhist
, NULL
)) != NULL
){
6419 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6420 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6421 if(history
->hist
[history
->curindex
]){
6422 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6423 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6424 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6425 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6426 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6435 if(pipe_command
[0]){
6437 flagsforhist
= (raw
? 0x8 : 0) +
6438 (delimit
? 0x4 : 0) +
6439 (newpipe
? 0x2 : 0) +
6440 (capture
? 0x1 : 0);
6441 save_hist(history
, pipe_command
, flagsforhist
, NULL
);
6443 flags
= PIPE_USER
| PIPE_WRITE
| PIPE_STDERR
;
6444 flags
|= (raw
? PIPE_RAW
: 0);
6450 ps_global
->mangled_screen
= 1;
6451 ps_global
->in_init_seq
= 1;
6453 flags
|= PIPE_RESET
;
6456 if(!newpipe
&& !(syspipe
= cmd_pipe_open(pipe_command
,
6457 (flags
& PIPE_RESET
)
6463 for(i
= mn_first_cur(msgmap
);
6465 i
= mn_next_cur(msgmap
)){
6466 e
= pine_mail_fetchstructure(ps_global
->mail_stream
,
6467 mn_m2raw(msgmap
, i
), &b
);
6468 if(!(state
->mail_stream
6469 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
6470 && rawno
<= state
->mail_stream
->nmsgs
6471 && (mc
= mail_elt(state
->mail_stream
, rawno
))
6476 && !(syspipe
= cmd_pipe_open(pipe_command
,
6477 (flags
& PIPE_RESET
)
6481 || (delimit
&& !bezerk_delimiter(e
, mc
, pc
, next
++)))
6488 prime_raw_pipe_getc(ps_global
->mail_stream
,
6489 mn_m2raw(msgmap
, i
), -1L, 0L);
6491 gf_link_filter(gf_nvtnl_local
, NULL
);
6492 if((pipe_err
= gf_pipe(raw_pipe_getc
, pc
)) != NULL
){
6493 q_status_message1(SM_ORDER
|SM_DING
,
6495 _("Internal Error: %s"),
6500 else if(!format_message(mn_m2raw(msgmap
, i
), e
, b
,
6501 NULL
, FM_NEW_MESS
| FM_NOWRAP
, pc
))
6506 if(close_system_pipe(&syspipe
, &pipe_rv
, pipe_callback
) == -1)
6511 ps_global
->in_init_seq
= 0;
6514 if(close_system_pipe(&syspipe
, &pipe_rv
, pipe_callback
) == -1)
6516 if(done
) /* say we had a problem */
6517 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6518 _("Error piping message"));
6519 else if(resultfilename
){
6521 /* only display if no error */
6522 display_output_file(resultfilename
, "PIPE MESSAGE",
6524 fs_give((void **)&resultfilename
);
6528 q_status_message(SM_ORDER
, 0, 2, _("Pipe command completed"));
6534 /* else fall thru as if cancelled */
6537 cmd_cancelled("Pipe command");
6542 helper(h_common_pipe
, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE
);
6543 ps_global
->mangled_screen
= 1;
6546 case 2 : /* no place to escape to */
6547 case 4 : /* can't suspend */
6553 ps_global
->mangled_footer
= 1;
6554 if(MCMD_ISAGG(aopt
))
6555 restore_selected(msgmap
);
6561 /*----------------------------------------------------------------------
6562 Screen to offer list management commands contained in message
6564 Args: state -- pointer to struct holding a bunch of pine state
6565 msgmap -- table mapping msg nums to c-client sequence nums
6566 aopt -- aggregate options
6570 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6573 rfc2369_display(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
)
6576 char *h
, *hdrs
[MLCMD_COUNT
+ 1];
6577 long index_no
= mn_raw2m(msgmap
, msgno
);
6578 RFC2369_S data
[MLCMD_COUNT
];
6580 /* for each header field */
6581 if((h
= pine_fetchheader_lines(stream
, msgno
, NULL
, rfc2369_hdrs(hdrs
))) != NULL
){
6582 memset(&data
[0], 0, sizeof(RFC2369_S
) * MLCMD_COUNT
);
6583 if(rfc2369_parse_fields(h
, &data
[0])){
6586 if((explain
= list_mgmt_text(data
, index_no
)) != NULL
){
6587 list_mgmt_screen(explain
);
6588 ps_global
->mangled_screen
= 1;
6594 fs_give((void **) &h
);
6598 q_status_message1(SM_ORDER
, 0, 3,
6599 "Message %s contains no list management information",
6600 comatose(index_no
));
6605 list_mgmt_text(RFC2369_S
*data
, long int msgno
)
6608 int i
, j
, n
, fields
= 0;
6609 static char *rfc2369_intro1
=
6610 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6611 static char *rfc2369_intro2
[] = {
6612 N_(" has information associated with it "),
6613 N_("that explains how to participate in an email list. An "),
6614 N_("email list is represented by a single email address that "),
6615 N_("users sharing a common interest can send messages to (known "),
6616 N_("as posting) which are then redistributed to all members "),
6617 N_("of the list (sometimes after review by a moderator)."),
6618 N_("<P>List participation commands in this message include:"),
6622 if((store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) != NULL
){
6624 /* Insert introductory text */
6625 so_puts(store
, rfc2369_intro1
);
6627 so_puts(store
, comatose(msgno
));
6629 for(i
= 0; rfc2369_intro2
[i
]; i
++)
6630 so_puts(store
, _(rfc2369_intro2
[i
]));
6632 so_puts(store
, "<P>");
6633 for(i
= 0; i
< MLCMD_COUNT
; i
++)
6634 if(data
[i
].data
[0].value
6635 || data
[i
].data
[0].comment
6636 || data
[i
].data
[0].error
){
6638 so_puts(store
, "<UL>");
6640 so_puts(store
, "<LI>");
6642 (n
= (data
[i
].data
[1].value
|| data
[i
].data
[1].comment
))
6646 so_puts(store
, data
[i
].field
.description
);
6647 so_puts(store
, ". ");
6650 so_puts(store
, "<OL>");
6654 && (data
[i
].data
[j
].comment
6655 || data
[i
].data
[j
].value
6656 || data
[i
].data
[j
].error
);
6659 so_puts(store
, n
? "<P><LI>" : "<P>");
6661 if(data
[i
].data
[j
].comment
){
6663 _("With the provided comment:<P><BLOCKQUOTE>"));
6664 so_puts(store
, data
[i
].data
[j
].comment
);
6665 so_puts(store
, "</BLOCKQUOTE><P>");
6668 if(data
[i
].data
[j
].value
){
6670 && !strucmp(data
[i
].data
[j
].value
, "NO")){
6672 _("Posting is <EM>not</EM> allowed on this list"));
6675 so_puts(store
, "Select <A HREF=\"");
6676 so_puts(store
, data
[i
].data
[j
].value
);
6677 so_puts(store
, "\">HERE</A> to ");
6678 so_puts(store
, (data
[i
].field
.action
)
6679 ? data
[i
].field
.action
6683 so_puts(store
, ".");
6686 if(data
[i
].data
[j
].error
){
6687 so_puts(store
, "<P>Unfortunately, Alpine can not offer");
6688 so_puts(store
, " to take direct action based upon it");
6689 so_puts(store
, " because it was improperly formatted.");
6690 so_puts(store
, " The unrecognized data associated with");
6691 so_puts(store
, " the \"");
6692 so_puts(store
, data
[i
].field
.name
);
6693 so_puts(store
, "\" header field was:<P><BLOCKQUOTE>");
6694 so_puts(store
, data
[i
].data
[j
].error
);
6695 so_puts(store
, "</BLOCKQUOTE>");
6698 so_puts(store
, "<P>");
6702 so_puts(store
, "</OL>");
6706 so_puts(store
, "</UL>");
6708 so_puts(store
, "</BODY></HTML>");
6716 list_mgmt_screen(STORE_S
*html
)
6722 HANDLE_S
*handles
= NULL
;
6726 so_seek(html
, 0L, 0);
6727 gf_set_so_readc(&gc
, html
);
6729 init_handles(&handles
);
6731 if((store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) != NULL
){
6732 gf_set_so_writec(&pc
, store
);
6735 gf_link_filter(gf_html2plain
,
6736 gf_html2plain_opt(NULL
, ps_global
->ttyo
->screen_cols
,
6737 non_messageview_margin(), &handles
, NULL
, 0));
6739 error
= gf_pipe(gc
, pc
);
6741 gf_clear_so_writec(store
);
6746 memset(&sargs
, 0, sizeof(SCROLL_S
));
6747 sargs
.text
.text
= so_text(store
);
6748 sargs
.text
.src
= CharStar
;
6749 sargs
.text
.desc
= "list commands";
6750 sargs
.text
.handles
= handles
;
6752 sargs
.start
.on
= Offset
;
6753 sargs
.start
.loc
.offset
= offset
;
6756 sargs
.bar
.title
= _("MAIL LIST COMMANDS");
6757 sargs
.bar
.style
= MessageNumber
;
6758 sargs
.resize_exit
= 1;
6759 sargs
.help
.text
= h_special_list_commands
;
6760 sargs
.help
.title
= _("HELP FOR LIST COMMANDS");
6761 sargs
.keys
.menu
= &listmgr_keymenu
;
6762 setbitmap(sargs
.keys
.bitmap
);
6764 clrbitn(LM_TRY_KEY
, sargs
.keys
.bitmap
);
6765 clrbitn(LM_PREV_KEY
, sargs
.keys
.bitmap
);
6766 clrbitn(LM_NEXT_KEY
, sargs
.keys
.bitmap
);
6769 cmd
= scrolltool(&sargs
);
6770 offset
= sargs
.start
.loc
.offset
;
6776 free_handles(&handles
);
6777 gf_clear_so_readc(html
);
6779 while(cmd
== MC_RESIZE
);
6783 /*----------------------------------------------------------------------
6784 Prompt the user for the type of select desired
6786 NOTE: any and all functions that successfully exit the second
6787 switch() statement below (currently "select_*() functions"),
6788 *MUST* update the folder's MESSAGECACHE element's "searched"
6789 bits to reflect the search result. Functions using
6790 mail_search() get this for free, the others must update 'em
6793 Returns -1 if canceled without changing selection
6794 0 if selection may have changed
6797 aggregate_select(struct pine
*state
, MSGNO_S
*msgmap
, int q_line
, CmdWhere in_index
)
6799 long i
, diff
, old_tot
, msgno
, raw
;
6800 int q
= 0, rv
= 0, narrow
= 0, hidden
, ret
= -1;
6803 SEARCHSET
*limitsrch
= NULL
;
6805 extern MAILSTREAM
*mm_search_stream
;
6806 extern long mm_search_count
;
6808 hidden
= any_lflagged(msgmap
, MN_HIDE
) > 0L;
6809 mm_search_stream
= state
->mail_stream
;
6810 mm_search_count
= 0L;
6812 sel_opts
= THRD_INDX() ? sel_opts4
: sel_opts2
;
6814 sel_opts
[SEL_OPTS_THREAD
].ch
= SEL_OPTS_THREAD_CH
;
6817 sel_opts
[SEL_OPTS_THREAD
].ch
= -1;
6820 if((old_tot
= any_lflagged(msgmap
, MN_SLCT
)) != 0){
6823 thrd
= fetch_thread(state
->mail_stream
,
6824 mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
6825 /* check if whole thread is selected or not */
6827 count_lflags_in_thread(state
->mail_stream
,thrd
,msgmap
,MN_SLCT
)
6829 count_lflags_in_thread(state
->mail_stream
,thrd
,msgmap
,MN_NONE
))
6832 sel_opts1
[1].label
= i
? N_("unselect Curthrd") : N_("select Curthrd");
6835 i
= get_lflag(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
6837 sel_opts1
[1].label
= i
? N_("unselect Cur") : N_("select Cur");
6840 sel_opts
+= 2; /* disable extra options */
6841 switch(q
= radio_buttons(_(sel_pmt1
), q_line
, sel_opts1
, 'c', 'x', NO_HELP
,
6843 case 'f' : /* flip selection */
6845 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++){
6847 q
= !get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
);
6848 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, q
);
6850 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, !q
);
6852 mn_reset_cur(msgmap
, msgno
= i
);
6858 case 'n' : /* narrow selection */
6860 case 'b' : /* broaden selection */
6861 q
= 0; /* offer criteria prompt */
6864 case 'c' : /* Un/Select Current */
6865 case 'a' : /* Unselect All */
6866 case 'x' : /* cancel */
6870 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6871 "Unsupported Select option");
6878 q
= radio_buttons(sel_pmt2
, q_line
, sel_opts
, 'c', 'x',
6879 NO_HELP
, RB_NORM
|RB_RET_HELP
);
6882 helper(h_index_cmd_select
, _("HELP FOR SELECT"), HLPD_SIMPLE
);
6883 ps_global
->mangled_screen
= 1;
6891 * The purpose of this is to add the appropriate searchset to the
6892 * search so that the search can be limited to only looking at what
6893 * it needs to look at. That is, if we are narrowing then we only need
6894 * to look at messages which are already selected, and if we are
6895 * broadening, then we only need to look at messages which are not
6896 * yet selected. This routine will work whether or not
6897 * limiting_searchset properly limits the search set. In particular,
6898 * the searchset returned by limiting_searchset may include messages
6899 * which really shouldn't be included. We do that because a too-large
6900 * searchset will break some IMAP servers. It is even possible that it
6901 * becomes inefficient to send the whole set. If the select function
6902 * frees limitsrch, it should be sure to set it to NULL so we won't
6903 * try freeing it again here.
6905 limitsrch
= limiting_searchset(state
->mail_stream
, narrow
);
6908 * NOTE: See note about MESSAGECACHE "searched" bits above!
6911 case 'x': /* cancel */
6912 cmd_cancelled("Select command");
6915 case 'c' : /* select/unselect current */
6916 (void) select_by_current(state
, msgmap
, in_index
);
6920 case 'a' : /* select/unselect all */
6921 msgno
= any_lflagged(msgmap
, MN_SLCT
);
6922 diff
= (!msgno
) ? mn_get_total(msgmap
) : 0L;
6924 agg_select_all(state
->mail_stream
, msgmap
, &diff
,
6925 any_lflagged(msgmap
, MN_SLCT
) <= 0L);
6926 q_status_message4(SM_ORDER
,0,2,
6927 "%s%s message%s %sselected",
6928 msgno
? "" : "All ", comatose(diff
),
6929 plural(diff
), msgno
? "UN" : "");
6932 case 'n' : /* Select by Number */
6935 rv
= select_by_thrd_number(state
->mail_stream
, msgmap
, &limitsrch
);
6937 rv
= select_by_number(state
->mail_stream
, msgmap
, &limitsrch
);
6941 case 'd' : /* Select by Date */
6943 rv
= select_by_date(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
6947 case 't' : /* Text */
6949 rv
= select_by_text(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
6953 case 'z' : /* Size */
6955 rv
= select_by_size(state
->mail_stream
, &limitsrch
);
6958 case 's' : /* Status */
6960 rv
= select_by_status(state
->mail_stream
, &limitsrch
);
6963 case 'k' : /* Keyword */
6965 rv
= select_by_keyword(state
->mail_stream
, &limitsrch
);
6968 case 'r' : /* Rule */
6970 rv
= select_by_rule(state
->mail_stream
, &limitsrch
);
6973 case 'h' : /* Thread */
6975 rv
= select_by_thread(state
->mail_stream
, msgmap
, &limitsrch
);
6979 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6980 "Unsupported Select option");
6985 mail_free_searchset(&limitsrch
);
6987 if(rv
) /* bad return value.. */
6988 return(ret
); /* error already displayed */
6990 if(narrow
) /* make sure something was selected */
6991 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++)
6992 if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
6993 && raw
<= state
->mail_stream
->nmsgs
6994 && (mc
= mail_elt(state
->mail_stream
, raw
)) && mc
->searched
){
6995 if(get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
))
7002 if(mm_search_count
){
7004 * loop thru all the messages, adjusting local flag bits
7005 * based on their "searched" bit...
7007 for(i
= 1L, msgno
= 0L; i
<= mn_get_total(msgmap
); i
++)
7009 /* turning OFF selectedness if the "searched" bit isn't lit. */
7010 if(get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
)){
7011 if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
7012 && raw
<= state
->mail_stream
->nmsgs
7013 && (mc
= mail_elt(state
->mail_stream
, raw
))
7016 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, 0);
7018 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, 1);
7020 /* adjust current message in case we unselect and hide it */
7021 else if(msgno
< mn_get_cur(msgmap
)
7023 || !get_lflag(state
->mail_stream
, msgmap
,
7028 else if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
7029 && raw
<= state
->mail_stream
->nmsgs
7030 && (mc
= mail_elt(state
->mail_stream
, raw
)) && mc
->searched
){
7031 /* turn ON selectedness if "searched" bit is lit. */
7032 if(!get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
)){
7034 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, 1);
7036 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, 0);
7040 /* if we're zoomed and the current message was unselected */
7042 && get_lflag(state
->mail_stream
,msgmap
,mn_get_cur(msgmap
),MN_HIDE
))
7043 mn_reset_cur(msgmap
, msgno
);
7048 q_status_message4(SM_ORDER
, 3, 3,
7049 "%s. %s message%s remain%s selected.",
7051 ? "No change resulted"
7052 : "No messages in intersection",
7053 comatose(old_tot
), plural(old_tot
),
7054 (old_tot
== 1L) ? "s" : "");
7056 q_status_message(SM_ORDER
, 3, 3,
7057 _("No change resulted. Matching messages already selected."));
7059 q_status_message1(SM_ORDER
| SM_DING
, 3, 3,
7060 _("Select failed. No %smessages selected."),
7061 old_tot
? _("additional ") : "");
7064 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
7065 "Select matched %ld message%s. %s %smessage%s %sselected.",
7066 (diff
> 0) ? diff
: old_tot
+ diff
,
7067 plural((diff
> 0) ? diff
: old_tot
+ diff
),
7068 comatose((diff
> 0) ? any_lflagged(msgmap
, MN_SLCT
) : -diff
),
7069 (diff
> 0) ? "total " : "",
7070 plural((diff
> 0) ? any_lflagged(msgmap
, MN_SLCT
) : -diff
),
7071 (diff
> 0) ? "" : "UN");
7072 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
7073 q_status_message(SM_ORDER
, 3, 3, tmp_20k_buf
);
7076 q_status_message2(SM_ORDER
, 3, 3, _("Select matched %s message%s!"),
7077 comatose(diff
), plural(diff
));
7083 /*----------------------------------------------------------------------
7084 Toggle the state of the current message
7086 Args: state -- pointer pine's state variables
7087 msgmap -- message collection to operate on
7088 in_index -- in the message index view
7089 Returns: TRUE if current marked selected, FALSE otw
7092 select_by_current(struct pine
*state
, MSGNO_S
*msgmap
, CmdWhere in_index
)
7095 int all_selected
= 0;
7096 unsigned long was
, tot
, rawno
;
7099 cur
= mn_get_cur(msgmap
);
7102 thrd
= fetch_thread(state
->mail_stream
, mn_m2raw(msgmap
, cur
));
7106 was
= count_lflags_in_thread(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
);
7107 tot
= count_lflags_in_thread(state
->mail_stream
, thrd
, msgmap
, MN_NONE
);
7112 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
, 0);
7113 if(any_lflagged(msgmap
, MN_HIDE
) > 0L){
7114 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_HIDE
, 1);
7116 * See if there's anything left to zoom on. If so,
7117 * pick an adjacent one for highlighting, else make
7118 * sure nothing is left hidden...
7120 if(any_lflagged(msgmap
, MN_SLCT
)){
7121 mn_inc_cur(state
->mail_stream
, msgmap
,
7122 (in_index
== View
&& THREADING()
7123 && sp_viewing_a_thread(state
->mail_stream
))
7125 : (in_index
== View
)
7126 ? MH_ANYTHD
: MH_NONE
);
7127 if(mn_get_cur(msgmap
) == cur
)
7128 mn_dec_cur(state
->mail_stream
, msgmap
,
7129 (in_index
== View
&& THREADING()
7130 && sp_viewing_a_thread(state
->mail_stream
))
7132 : (in_index
== View
)
7133 ? MH_ANYTHD
: MH_NONE
);
7135 else /* clear all hidden flags */
7136 (void) unzoom_index(state
, state
->mail_stream
, msgmap
);
7140 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
, 1);
7142 q_status_message3(SM_ORDER
, 0, 2, "%s message%s %sselected",
7143 comatose(all_selected
? was
: tot
-was
),
7144 plural(all_selected
? was
: tot
-was
),
7145 all_selected
? "UN" : "");
7147 /* collapsed thread */
7149 && ((rawno
= mn_m2raw(msgmap
, cur
)) != 0L)
7150 && ((thrd
= fetch_thread(state
->mail_stream
, rawno
)) != NULL
)
7151 && (thrd
&& thrd
->next
&& get_lflag(state
->mail_stream
, NULL
, rawno
, MN_COLL
))){
7153 * This doesn't work quite the same as the colon command works, but
7154 * it is arguably doing the correct thing. The difference is
7155 * that aggregate_select will zoom after selecting back where it
7156 * was called from, but selecting a thread with colon won't zoom.
7157 * Maybe it makes sense to zoom after a select but not after a colon
7158 * command even though they are very similar.
7160 thread_command(state
, state
->mail_stream
, msgmap
, ':', -FOOTER_ROWS(state
));
7164 get_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
)) != 0){ /* set? */
7165 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
, 0);
7166 if(any_lflagged(msgmap
, MN_HIDE
) > 0L){
7167 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_HIDE
, 1);
7169 * See if there's anything left to zoom on. If so,
7170 * pick an adjacent one for highlighting, else make
7171 * sure nothing is left hidden...
7173 if(any_lflagged(msgmap
, MN_SLCT
)){
7174 mn_inc_cur(state
->mail_stream
, msgmap
,
7175 (in_index
== View
&& THREADING()
7176 && sp_viewing_a_thread(state
->mail_stream
))
7178 : (in_index
== View
)
7179 ? MH_ANYTHD
: MH_NONE
);
7180 if(mn_get_cur(msgmap
) == cur
)
7181 mn_dec_cur(state
->mail_stream
, msgmap
,
7182 (in_index
== View
&& THREADING()
7183 && sp_viewing_a_thread(state
->mail_stream
))
7185 : (in_index
== View
)
7186 ? MH_ANYTHD
: MH_NONE
);
7188 else /* clear all hidden flags */
7189 (void) unzoom_index(state
, state
->mail_stream
, msgmap
);
7193 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
, 1);
7195 q_status_message2(SM_ORDER
, 0, 2, "Message %s %sselected",
7196 long2string(cur
), all_selected
? "UN" : "");
7200 return(!all_selected
);
7204 /*----------------------------------------------------------------------
7205 Prompt the user for the command to perform on selected messages
7207 Args: state -- pointer pine's state variables
7208 msgmap -- message collection to operate on
7209 q_line -- line on display to write prompts
7210 Returns: 1 if the selected messages are suitably commanded,
7211 0 if the choice to pick the command was declined
7215 apply_command(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
,
7216 UCS preloadkeystroke
, int flags
, int q_line
)
7218 int i
= 8, /* number of static entries in sel_opts3 */
7222 agg
= (flags
& AC_FROM_THREAD
) ? MCMD_AGG_2
: MCMD_AGG
;
7226 * To do this "right", we really ought to have access to the keymenu
7227 * here and change the typed command into a real command by running
7228 * it through menu_command. Then the switch below would be against
7229 * results from menu_command. If we did that we'd also pass the
7230 * results of menu_command in as preloadkeystroke instead of passing
7231 * the keystroke itself. But we don't have the keymenu handy,
7232 * so we have to fake it. The only complication that we run into
7233 * is that KEY_DEL is an escape sequence so we change a typed
7234 * KEY_DEL esc seq into the letter D.
7237 if(!preloadkeystroke
){
7238 if(F_ON(F_ENABLE_FLAG
,state
)){ /* flag? */
7239 sel_opts3
[i
].ch
= '*';
7240 sel_opts3
[i
].rval
= '*';
7241 sel_opts3
[i
].name
= "*";
7242 sel_opts3
[i
++].label
= N_("Flag");
7245 if(F_ON(F_ENABLE_PIPE
,state
)){ /* pipe? */
7246 sel_opts3
[i
].ch
= '|';
7247 sel_opts3
[i
].rval
= '|';
7248 sel_opts3
[i
].name
= "|";
7249 sel_opts3
[i
++].label
= N_("Pipe");
7252 if(F_ON(F_ENABLE_BOUNCE
,state
)){ /* bounce? */
7253 sel_opts3
[i
].ch
= 'b';
7254 sel_opts3
[i
].rval
= 'b';
7255 sel_opts3
[i
].name
= "B";
7256 sel_opts3
[i
++].label
= N_("Bounce");
7259 if(flags
& AC_FROM_THREAD
){
7260 if(flags
& (AC_COLL
| AC_EXPN
)){
7261 sel_opts3
[i
].ch
= '/';
7262 sel_opts3
[i
].rval
= '/';
7263 sel_opts3
[i
].name
= "/";
7264 sel_opts3
[i
++].label
= (flags
& AC_COLL
) ? N_("Collapse")
7268 sel_opts3
[i
].ch
= ';';
7269 sel_opts3
[i
].rval
= ';';
7270 sel_opts3
[i
].name
= ";";
7271 if(flags
& AC_UNSEL
)
7272 sel_opts3
[i
++].label
= N_("UnSelect");
7274 sel_opts3
[i
++].label
= N_("Select");
7277 if(F_ON(F_ENABLE_PRYNT
, state
)){ /* this one is invisible */
7278 sel_opts3
[i
].ch
= 'y';
7279 sel_opts3
[i
].rval
= '%';
7280 sel_opts3
[i
].name
= "";
7281 sel_opts3
[i
++].label
= "";
7284 if(!is_imap_stream(stream
) || LEVELUIDPLUS(stream
)){ /* expunge selected messages */
7285 sel_opts3
[i
].ch
= 'x';
7286 sel_opts3
[i
].rval
= 'x';
7287 sel_opts3
[i
].name
= "X";
7288 sel_opts3
[i
++].label
= N_("Expunge");
7291 sel_opts3
[i
].ch
= KEY_DEL
; /* also invisible */
7292 sel_opts3
[i
].rval
= 'd';
7293 sel_opts3
[i
].name
= "";
7294 sel_opts3
[i
++].label
= "";
7296 sel_opts3
[i
].ch
= -1;
7298 snprintf(prompt
, sizeof(prompt
), "%s command : ",
7299 (flags
& AC_FROM_THREAD
) ? "THREAD" : "APPLY");
7300 prompt
[sizeof(prompt
)-1] = '\0';
7301 cmd
= double_radio_buttons(prompt
, q_line
, sel_opts3
, 'z', 'c', NO_HELP
,
7307 if(preloadkeystroke
== KEY_DEL
)
7310 if(preloadkeystroke
< 0x80 && isupper((int) preloadkeystroke
))
7311 cmd
= tolower((int) preloadkeystroke
);
7313 cmd
= (int) preloadkeystroke
; /* shouldn't happen */
7318 case 'd' : /* delete */
7319 we_cancel
= busy_cue(NULL
, NULL
, 1);
7320 rv
= cmd_delete(state
, msgmap
, agg
, NULL
); /* don't advance or offer "TAB" */
7325 case 'u' : /* undelete */
7326 we_cancel
= busy_cue(NULL
, NULL
, 1);
7327 rv
= cmd_undelete(state
, msgmap
, agg
);
7332 case 'r' : /* reply */
7333 rv
= cmd_reply(state
, msgmap
, agg
);
7336 case 'f' : /* Forward */
7337 rv
= cmd_forward(state
, msgmap
, agg
);
7340 case '%' : /* print */
7341 rv
= cmd_print(state
, msgmap
, agg
, MsgIndx
);
7344 case 't' : /* take address */
7345 rv
= cmd_take_addr(state
, msgmap
, agg
);
7348 case 's' : /* save */
7349 rv
= cmd_save(state
, stream
, msgmap
, agg
, MsgIndx
);
7352 case 'e' : /* export */
7353 rv
= cmd_export(state
, msgmap
, q_line
, agg
);
7356 case '|' : /* pipe */
7357 rv
= cmd_pipe(state
, msgmap
, agg
);
7360 case '*' : /* flag */
7361 we_cancel
= busy_cue(NULL
, NULL
, 1);
7362 rv
= cmd_flag(state
, msgmap
, agg
);
7367 case 'b' : /* bounce */
7368 rv
= cmd_bounce(state
, msgmap
, agg
);
7372 collapse_or_expand(state
, stream
, msgmap
,
7373 F_ON(F_SLASH_COLL_ENTIRE
, ps_global
)
7375 : mn_get_cur(msgmap
));
7379 select_thread_stmp(state
, stream
, msgmap
);
7382 case 'x' : /* Expunge */
7383 rv
= cmd_expunge(state
, stream
, msgmap
, agg
);
7386 case 'c' : /* cancel */
7387 cmd_cancelled((flags
& AC_FROM_THREAD
) ? "Thread command"
7391 case 'z' : /* default */
7392 q_status_message(SM_INFO
, 0, 2,
7393 "Cancelled, there is no default command");
7405 * Select by message number ranges.
7406 * Sets searched bits in mail_elts
7408 * Args limitsrch -- limit search to this searchset
7410 * Returns 0 on success.
7413 select_by_number(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**limitsrch
)
7417 char number1
[16], number2
[16], numbers
[80], *p
, *t
;
7422 ps_global
->mangled_footer
= 1;
7425 int flags
= OE_APPEND_CURRENT
;
7427 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
7428 sizeof(numbers
), _(select_num
), NULL
, help
, &flags
);
7433 help
= (help
== NO_HELP
) ? h_select_by_num
: NO_HELP
;
7437 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
7438 if(!isspace((unsigned char)*p
))
7443 if(r
== 1 || numbers
[0] == '\0'){
7444 cmd_cancelled("Selection by number");
7451 for(n1
= 1; n1
<= stream
->nmsgs
; n1
++)
7452 if((mc
= mail_elt(stream
, n1
)) != NULL
)
7453 mc
->searched
= 0; /* clear searched bits */
7455 for(p
= numbers
; *p
; p
++){
7457 while(*p
&& isdigit((unsigned char)*p
))
7463 if(number1
[0] == '\0'){
7465 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7466 _("Invalid number range, missing number before \"-\": %s"),
7470 else if(!strucmp("end", p
)){
7475 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7476 _("Invalid message number: %s"), numbers
);
7482 n1
= mn_get_total(msgmap
);
7483 else if((n1
= atol(number1
)) < 1L || n1
> mn_get_total(msgmap
)){
7484 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7485 _("\"%s\" out of message number range"),
7492 while(*++p
&& isdigit((unsigned char)*p
))
7498 if(number2
[0] == '\0'){
7499 if(!strucmp("end", p
)){
7504 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7505 _("Invalid number range, missing number after \"-\": %s"),
7512 n2
= mn_get_total(msgmap
);
7513 else if((n2
= atol(number2
)) < 1L || n2
> mn_get_total(msgmap
)){
7514 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7515 _("\"%s\" out of message number range"),
7523 strncpy(t
, long2string(n1
), sizeof(t
));
7524 t
[sizeof(t
)-1] = '\0';
7525 q_status_message2(SM_ORDER
| SM_DING
, 0, 2,
7526 _("Invalid reverse message number range: %s-%s"),
7527 t
, long2string(n2
));
7531 for(;n1
<= n2
; n1
++){
7532 raw
= mn_m2raw(msgmap
, n1
);
7534 && (!(limitsrch
&& *limitsrch
)
7535 || in_searchset(*limitsrch
, (unsigned long) raw
)))
7536 mm_searched(stream
, raw
);
7540 raw
= mn_m2raw(msgmap
, n1
);
7542 && (!(limitsrch
&& *limitsrch
)
7543 || in_searchset(*limitsrch
, (unsigned long) raw
)))
7544 mm_searched(stream
, raw
);
7556 * Select by thread number ranges.
7557 * Sets searched bits in mail_elts
7559 * Args limitsrch -- limit search to this searchset
7561 * Returns 0 on success.
7564 select_by_thrd_number(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**msgset
)
7568 char number1
[16], number2
[16], numbers
[80], *p
, *t
;
7570 PINETHRD_S
*thrd
= NULL
;
7574 ps_global
->mangled_footer
= 1;
7577 int flags
= OE_APPEND_CURRENT
;
7579 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
7580 sizeof(numbers
), _(select_num
), NULL
, help
, &flags
);
7585 help
= (help
== NO_HELP
) ? h_select_by_thrdnum
: NO_HELP
;
7589 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
7590 if(!isspace((unsigned char)*p
))
7595 if(r
== 1 || numbers
[0] == '\0'){
7596 cmd_cancelled("Selection by number");
7603 for(n1
= 1; n1
<= stream
->nmsgs
; n1
++)
7604 if((mc
= mail_elt(stream
, n1
)) != NULL
)
7605 mc
->searched
= 0; /* clear searched bits */
7607 for(p
= numbers
; *p
; p
++){
7609 while(*p
&& isdigit((unsigned char)*p
))
7615 if(number1
[0] == '\0'){
7617 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7618 _("Invalid number range, missing number before \"-\": %s"),
7622 else if(!strucmp("end", p
)){
7627 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7628 _("Invalid thread number: %s"), numbers
);
7634 n1
= msgmap
->max_thrdno
;
7635 else if((n1
= atol(number1
)) < 1L || n1
> msgmap
->max_thrdno
){
7636 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7637 _("\"%s\" out of thread number range"),
7645 while(*++p
&& isdigit((unsigned char)*p
))
7651 if(number2
[0] == '\0'){
7652 if(!strucmp("end", p
)){
7657 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7658 _("Invalid number range, missing number after \"-\": %s"),
7665 n2
= msgmap
->max_thrdno
;
7666 else if((n2
= atol(number2
)) < 1L || n2
> msgmap
->max_thrdno
){
7667 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7668 _("\"%s\" out of thread number range"),
7676 strncpy(t
, long2string(n1
), sizeof(t
));
7677 t
[sizeof(t
)-1] = '\0';
7678 q_status_message2(SM_ORDER
| SM_DING
, 0, 2,
7679 _("Invalid reverse message number range: %s-%s"),
7680 t
, long2string(n2
));
7684 for(;n1
<= n2
; n1
++){
7685 thrd
= find_thread_by_number(stream
, msgmap
, n1
, thrd
);
7688 set_search_bit_for_thread(stream
, thrd
, msgset
);
7692 thrd
= find_thread_by_number(stream
, msgmap
, n1
, NULL
);
7695 set_search_bit_for_thread(stream
, thrd
, msgset
);
7707 * Select by message dates.
7708 * Sets searched bits in mail_elts
7710 * Args limitsrch -- limit search to this searchset
7712 * Returns 0 on success.
7715 select_by_date(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
, SEARCHSET
**limitsrch
)
7717 int r
, we_cancel
= 0, when
= 0;
7718 char date
[100], defdate
[100], prompt
[128];
7719 time_t seldate
= time(0);
7720 struct tm
*seldate_tm
;
7723 static struct _tense
{
7728 {"were ", "SENT SINCE", " (inclusive)"},
7729 {"were ", "SENT BEFORE", " (exclusive)"},
7730 {"were ", "SENT ON", "" },
7731 {"", "ARRIVED SINCE", " (inclusive)"},
7732 {"", "ARRIVED BEFORE", " (exclusive)"},
7733 {"", "ARRIVED ON", "" }
7737 ps_global
->mangled_footer
= 1;
7741 * If talking to an old server, default to SINCE instead of
7742 * SENTSINCE, which was added later.
7744 if(is_imap_stream(stream
) && !modern_imap_stream(stream
))
7748 int flags
= OE_APPEND_CURRENT
;
7750 seldate_tm
= localtime(&seldate
);
7751 snprintf(defdate
, sizeof(defdate
), "%.2d-%.4s-%.4d", seldate_tm
->tm_mday
,
7752 month_abbrev(seldate_tm
->tm_mon
+ 1),
7753 seldate_tm
->tm_year
+ 1900);
7754 defdate
[sizeof(defdate
)-1] = '\0';
7755 snprintf(prompt
,sizeof(prompt
),"Select messages which %s%s%s [%s]: ",
7756 tense
[when
].preamble
, tense
[when
].range
,
7757 tense
[when
].scope
, defdate
);
7758 prompt
[sizeof(prompt
)-1] = '\0';
7759 r
= optionally_enter(date
,-FOOTER_ROWS(ps_global
), 0, sizeof(date
),
7760 prompt
, sel_date_opt
, help
, &flags
);
7763 cmd_cancelled("Selection by date");
7767 help
= (help
== NO_HELP
) ? h_select_date
: NO_HELP
;
7778 if(stream
&& (rawno
= mn_m2raw(msgmap
, msgno
)) > 0L
7779 && rawno
<= stream
->nmsgs
7780 && (mc
= mail_elt(stream
, rawno
))){
7782 /* cache not filled in yet? */
7786 if(stream
->dtb
&& stream
->dtb
->flags
& DR_NEWS
){
7788 ulong2string(mail_uid(stream
, rawno
)),
7790 seq
[sizeof(seq
)-1] = '\0';
7791 mail_fetch_overview(stream
, seq
, NULL
);
7794 strncpy(seq
, long2string(rawno
),
7796 seq
[sizeof(seq
)-1] = '\0';
7797 mail_fetch_fast(stream
, seq
, 0L);
7801 /* mail_date returns fixed field width date */
7802 mail_date(date
, mc
);
7809 case 12 : /* set default to PREVIOUS day */
7813 case 13 : /* set default to NEXT day */
7818 when
= (when
+1) % (sizeof(tense
) / sizeof(struct _tense
));
7825 removing_leading_white_space(date
);
7826 removing_trailing_white_space(date
);
7828 strncpy(date
, defdate
, sizeof(date
));
7829 date
[sizeof(date
)-1] = '\0';
7835 if((pgm
= mail_newsearchpgm()) != NULL
){
7837 short converted_date
;
7839 if(mail_parse_date(&elt
, (unsigned char *) date
)){
7840 converted_date
= mail_shortdate(elt
.year
, elt
.month
, elt
.day
);
7844 pgm
->sentsince
= converted_date
;
7847 pgm
->sentbefore
= converted_date
;
7850 pgm
->senton
= converted_date
;
7853 pgm
->since
= converted_date
;
7856 pgm
->before
= converted_date
;
7859 pgm
->on
= converted_date
;
7863 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
7865 if(ps_global
&& ps_global
->ttyo
){
7866 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
7867 ps_global
->mangled_footer
= 1;
7870 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
7872 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
7877 /* we know this was freed in mail_search, let caller know */
7882 mail_free_searchpgm(&pgm
);
7883 q_status_message1(SM_ORDER
, 3, 3,
7884 _("Invalid date entered: %s"), date
);
7894 * Select by searching in message headers or body.
7895 * Sets searched bits in mail_elts
7897 * Args limitsrch -- limit search to this searchset
7899 * Returns 0 on success.
7902 select_by_text(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
, SEARCHSET
**limitsrch
)
7904 int r
, ku
, type
, we_cancel
= 0, flags
, rv
, ekeyi
= 0;
7905 int not = 0, me
= 0;
7906 char sstring
[80], savedsstring
[80], tmp
[128];
7907 char *p
, *sval
= NULL
;
7908 char buftmp
[MAILTMPLEN
], namehdr
[80];
7910 ENVELOPE
*env
= NULL
;
7912 unsigned flagsforhist
= 0;
7913 static HISTORY_S
*history
= NULL
;
7914 static char *recip
= "RECIPIENTS";
7915 static char *partic
= "PARTICIPANTS";
7916 static char *match_me
= N_("[Match_My_Addresses]");
7917 static char *dont_match_me
= N_("[Don't_Match_My_Addresses]");
7919 ps_global
->mangled_footer
= 1;
7920 savedsstring
[0] = '\0';
7921 ekey
[0].ch
= ekey
[1].ch
= ekey
[2].ch
= ekey
[3].ch
= -1;
7924 type
= radio_buttons(not ? _(sel_text_not
) : _(sel_text
),
7925 -FOOTER_ROWS(ps_global
), sel_text_opt
,
7926 's', 'x', NO_HELP
, RB_NORM
|RB_RET_HELP
);
7931 helper(h_select_text
, "HELP FOR SELECT BASED ON CONTENTS",
7933 ps_global
->mangled_screen
= 1;
7940 * prepare some friendly defaults...
7943 case 't' : /* address fields, offer To or From */
7948 sval
= (type
== 't') ? "TO" :
7949 (type
== 'f') ? "FROM" :
7950 (type
== 'c') ? "CC" :
7951 (type
== 'r') ? recip
: partic
;
7952 ekey
[ekeyi
].ch
= ctrl('T');
7953 ekey
[ekeyi
].name
= "^T";
7954 ekey
[ekeyi
].rval
= 10;
7955 /* TRANSLATORS: use Current To Address */
7956 ekey
[ekeyi
++].label
= N_("Cur To");
7957 ekey
[ekeyi
].ch
= ctrl('R');
7958 ekey
[ekeyi
].name
= "^R";
7959 ekey
[ekeyi
].rval
= 11;
7960 /* TRANSLATORS: use Current From Address */
7961 ekey
[ekeyi
++].label
= N_("Cur From");
7962 ekey
[ekeyi
].ch
= ctrl('W');
7963 ekey
[ekeyi
].name
= "^W";
7964 ekey
[ekeyi
].rval
= 12;
7965 /* TRANSLATORS: use Current Cc Address */
7966 ekey
[ekeyi
++].label
= N_("Cur Cc");
7967 ekey
[ekeyi
].ch
= ctrl('Y');
7968 ekey
[ekeyi
].name
= "^Y";
7969 ekey
[ekeyi
].rval
= 13;
7970 /* TRANSLATORS: Match Me means match my address */
7971 ekey
[ekeyi
++].label
= N_("Match Me");
7973 ekey
[ekeyi
].name
= "";
7974 ekey
[ekeyi
].rval
= 0;
7975 ekey
[ekeyi
++].label
= "";
7980 ekey
[ekeyi
].ch
= ctrl('X');
7981 ekey
[ekeyi
].name
= "^X";
7982 ekey
[ekeyi
].rval
= 14;
7983 /* TRANSLATORS: use Current Subject */
7984 ekey
[ekeyi
++].label
= N_("Cur Subject");
7996 strncpy(tmp
, "Name of HEADER to match : ", sizeof(tmp
)-1);
7997 tmp
[sizeof(tmp
)-1] = '\0';
7998 flags
= OE_APPEND_CURRENT
;
8004 r
= optionally_enter(namehdr
, -FOOTER_ROWS(ps_global
), 0,
8005 sizeof(namehdr
), tmp
, ekey
, NO_HELP
, &flags
);
8007 cmd_cancelled("Selection by text");
8010 removing_leading_white_space(namehdr
);
8012 while ((namehdr
[0] != '\0') && /* remove trailing ":" */
8013 (namehdr
[strlen(namehdr
) - 1] == ':'))
8014 namehdr
[strlen(namehdr
) - 1] = '\0';
8015 if ((namehdr
[0] != '\0')
8016 && isspace((unsigned char) namehdr
[strlen(namehdr
) - 1]))
8017 removing_trailing_white_space(namehdr
);
8021 if (strchr(namehdr
,' ') || strchr(namehdr
,'\t') ||
8022 strchr(namehdr
,':'))
8024 if (namehdr
[0] == '\0')
8034 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
8038 ekey
[ekeyi
].ch
= KEY_UP
;
8039 ekey
[ekeyi
].rval
= 30;
8040 ekey
[ekeyi
].name
= "";
8042 ekey
[ekeyi
++].label
= "";
8044 ekey
[ekeyi
].ch
= KEY_DOWN
;
8045 ekey
[ekeyi
].rval
= 31;
8046 ekey
[ekeyi
].name
= "";
8047 ekey
[ekeyi
++].label
= "";
8049 ekey
[ekeyi
].ch
= -1;
8053 init_hist(&history
, HISTSIZE
);
8055 if(ekey
[0].ch
> -1 && msgno
> 0L
8056 && !(env
=pine_mail_fetchstructure(stream
,mn_m2raw(msgmap
,msgno
),
8065 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8066 snprintf(tmp
, sizeof(tmp
), "String in message %s to NOT match : ", sval
);
8068 snprintf(tmp
, sizeof(tmp
), "String in message %s to match : ", sval
);
8070 if(items_in_hist(history
) > 0){
8071 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
8072 ekey
[ku
].label
= HISTORY_KEYLABEL
;
8073 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
8074 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
8078 ekey
[ku
].label
= "";
8079 ekey
[ku
+1].name
= "";
8080 ekey
[ku
+1].label
= "";
8083 flags
= OE_APPEND_CURRENT
| OE_KEEP_TRAILING_SPACE
;
8084 r
= optionally_enter(sstring
, -FOOTER_ROWS(ps_global
), 0,
8085 79, tmp
, ekey
, help
, &flags
);
8087 if(me
&& r
== 0 && ((!not && strcmp(sstring
, _(match_me
))) || (not && strcmp(sstring
, _(dont_match_me
)))))
8092 help
= (help
== NO_HELP
)
8094 ? ((type
== 'f') ? h_select_txt_not_from
8095 : (type
== 't') ? h_select_txt_not_to
8096 : (type
== 'c') ? h_select_txt_not_cc
8097 : (type
== 's') ? h_select_txt_not_subj
8098 : (type
== 'a') ? h_select_txt_not_all
8099 : (type
== 'r') ? h_select_txt_not_recip
8100 : (type
== 'p') ? h_select_txt_not_partic
8101 : (type
== 'b') ? h_select_txt_not_body
8103 : ((type
== 'f') ? h_select_txt_from
8104 : (type
== 't') ? h_select_txt_to
8105 : (type
== 'c') ? h_select_txt_cc
8106 : (type
== 's') ? h_select_txt_subj
8107 : (type
== 'a') ? h_select_txt_all
8108 : (type
== 'r') ? h_select_txt_recip
8109 : (type
== 'p') ? h_select_txt_partic
8110 : (type
== 'b') ? h_select_txt_body
8117 case 10 : /* To: default */
8118 if(env
&& env
->to
&& env
->to
->mailbox
){
8119 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->to
->mailbox
,
8120 env
->to
->host
? "@" : "",
8121 env
->to
->host
? env
->to
->host
: "");
8122 sstring
[sizeof(sstring
)-1] = '\0';
8126 case 11 : /* From: default */
8127 if(env
&& env
->from
&& env
->from
->mailbox
){
8128 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->from
->mailbox
,
8129 env
->from
->host
? "@" : "",
8130 env
->from
->host
? env
->from
->host
: "");
8131 sstring
[sizeof(sstring
)-1] = '\0';
8135 case 12 : /* Cc: default */
8136 if(env
&& env
->cc
&& env
->cc
->mailbox
){
8137 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->cc
->mailbox
,
8138 env
->cc
->host
? "@" : "",
8139 env
->cc
->host
? env
->cc
->host
: "");
8140 sstring
[sizeof(sstring
)-1] = '\0';
8144 case 13 : /* Match my addresses */
8146 snprintf(sstring
, sizeof(sstring
), "%s", not ? _(dont_match_me
) : _(match_me
));
8149 case 14 : /* Subject: default */
8150 if(env
&& env
->subject
&& env
->subject
[0]){
8153 snprintf(buftmp
, sizeof(buftmp
), "%.75s", env
->subject
);
8154 buftmp
[sizeof(buftmp
)-1] = '\0';
8155 q
= (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf
,
8156 SIZEOF_20KBUF
, buftmp
);
8157 if(q
!= env
->subject
){
8158 snprintf(savedsstring
, sizeof(savedsstring
), "%.70s", q
);
8159 savedsstring
[sizeof(savedsstring
)-1] = '\0';
8162 snprintf(sstring
, sizeof(sstring
), "%s", q
);
8163 sstring
[sizeof(sstring
)-1] = '\0';
8169 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8170 if((p
= get_prev_hist(history
, sstring
, flagsforhist
, NULL
)) != NULL
){
8171 strncpy(sstring
, p
, sizeof(sstring
));
8172 sstring
[sizeof(sstring
)-1] = '\0';
8173 if(history
->hist
[history
->curindex
]){
8174 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
8175 not = (flagsforhist
& 0x1) ? 1 : 0;
8176 me
= (flagsforhist
& 0x2) ? 1 : 0;
8185 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8186 if((p
= get_next_hist(history
, sstring
, flagsforhist
, NULL
)) != NULL
){
8187 strncpy(sstring
, p
, sizeof(sstring
));
8188 sstring
[sizeof(sstring
)-1] = '\0';
8189 if(history
->hist
[history
->curindex
]){
8190 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
8191 not = (flagsforhist
& 0x1) ? 1 : 0;
8192 me
= (flagsforhist
& 0x2) ? 1 : 0;
8204 if(r
== 1 || sstring
[0] == '\0')
8211 if(type
== 'x' || r
== 'x'){
8212 cmd_cancelled("Selection by text");
8216 if(ps_global
&& ps_global
->ttyo
){
8217 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8218 ps_global
->mangled_footer
= 1;
8221 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8223 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8224 save_hist(history
, sstring
, flagsforhist
, NULL
);
8226 rv
= agg_text_select(stream
, msgmap
, type
, namehdr
, not, me
, sstring
, "utf-8", limitsrch
);
8235 * Select by message size.
8236 * Sets searched bits in mail_elts
8238 * Args limitsrch -- limit search to this searchset
8240 * Returns 0 on success.
8243 select_by_size(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8245 int r
, large
= 1, we_cancel
= 0;
8246 unsigned long n
, mult
= 1L, numerator
= 0L, divisor
= 1L;
8247 char size
[16], numbers
[80], *p
, *t
;
8250 long flags
= (SE_NOPREFETCH
| SE_FREE
);
8253 ps_global
->mangled_footer
= 1;
8257 int flgs
= OE_APPEND_CURRENT
;
8259 sel_size_opt
[1].label
= large
? sel_size_smaller
: sel_size_larger
;
8261 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
8262 sizeof(numbers
), large
? _(select_size_larger_msg
)
8263 : _(select_size_smaller_msg
),
8264 sel_size_opt
, help
, &flgs
);
8274 help
= (help
== NO_HELP
) ? (large
? h_select_by_larger_size
8275 : h_select_by_smaller_size
)
8280 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
8281 if(!isspace((unsigned char)*p
))
8286 if(r
== 1 || numbers
[0] == '\0'){
8287 cmd_cancelled("Selection by size");
8294 if(numbers
[0] == '-'){
8295 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
8296 _("Invalid size entered: %s"), numbers
);
8303 while(*p
&& isdigit((unsigned char)*p
))
8308 if(size
[0] == '\0' && *p
== '.' && isdigit(*(p
+1))){
8313 if(size
[0] == '\0'){
8314 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
8315 _("Invalid size entered: %s"), numbers
);
8319 n
= strtoul(size
, (char **)NULL
, 10);
8324 * We probably ought to just use atof() to convert 1.1 into a
8325 * double, but since we haven't used atof() anywhere else I'm
8326 * reluctant to use it because of portability concerns.
8330 while(*p
&& isdigit((unsigned char)*p
)){
8338 numerator
= strtoul(size
, (char **)NULL
, 10);
8358 n
= n
* mult
+ (numerator
* mult
) / divisor
;
8360 pgm
= mail_newsearchpgm();
8366 if(is_imap_stream(stream
) && !modern_imap_stream(stream
))
8367 flags
|= SE_NOSERVER
;
8369 if(ps_global
&& ps_global
->ttyo
){
8370 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8371 ps_global
->mangled_footer
= 1;
8374 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8376 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
8377 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
8378 /* we know this was freed in mail_search, let caller know */
8390 * visible_searchset -- return c-client search set unEXLDed
8394 visible_searchset(MAILSTREAM
*stream
, MSGNO_S
*msgmap
)
8397 SEARCHSET
*full_set
= NULL
, **set
;
8400 * If we're talking to anything other than a server older than
8401 * imap 4rev1, build a searchset otherwise it'll choke.
8403 if(!(is_imap_stream(stream
) && !modern_imap_stream(stream
))){
8404 if(any_lflagged(msgmap
, MN_EXLD
)){
8405 for(n
= 1L, set
= &full_set
, run
= 0L; n
<= stream
->nmsgs
; n
++)
8406 if(get_lflag(stream
, NULL
, n
, MN_EXLD
)){
8407 if(run
){ /* previous NOT excluded? */
8409 (*set
)->last
= n
- 1L;
8411 set
= &(*set
)->next
;
8415 else if(run
++){ /* next in run */
8418 else{ /* start of run */
8419 *set
= mail_newsearchset();
8424 full_set
= mail_newsearchset();
8425 full_set
->first
= 1L;
8426 full_set
->last
= stream
->nmsgs
;
8435 * Select by message status bits.
8436 * Sets searched bits in mail_elts
8438 * Args limitsrch -- limit search to this searchset
8440 * Returns 0 on success.
8443 select_by_status(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8445 int s
, not = 0, we_cancel
= 0, rv
;
8448 s
= radio_buttons((not) ? _(sel_flag_not
) : _(sel_flag
),
8449 -FOOTER_ROWS(ps_global
), sel_flag_opt
, '*', 'x',
8450 NO_HELP
, RB_NORM
|RB_RET_HELP
);
8453 cmd_cancelled("Selection by status");
8457 helper(h_select_status
, _("HELP FOR SELECT BASED ON STATUS"),
8459 ps_global
->mangled_screen
= 1;
8467 if(ps_global
&& ps_global
->ttyo
){
8468 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8469 ps_global
->mangled_footer
= 1;
8472 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8473 rv
= agg_flag_select(stream
, not, s
, limitsrch
);
8482 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8483 * Sets searched bits in mail_elts
8485 * Args limitsrch -- limit search to this searchset
8487 * Returns 0 on success.
8490 select_by_rule(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8492 char rulenick
[1000], *nick
;
8494 int r
, not = 0, we_cancel
= 0, rflags
= ROLE_DO_SRCH
8502 ps_global
->mangled_footer
= 1;
8507 oe_flags
= OE_APPEND_CURRENT
;
8508 r
= optionally_enter(rulenick
, -FOOTER_ROWS(ps_global
), 0,
8510 not ? _("Rule to NOT match: ")
8511 : _("Rule to match: "),
8512 sel_key_opt
, NO_HELP
, &oe_flags
);
8515 /* select rulenick from a list */
8516 if((nick
=choose_a_rule(rflags
)) != NULL
){
8517 strncpy(rulenick
, nick
, sizeof(rulenick
)-1);
8518 rulenick
[sizeof(rulenick
)-1] = '\0';
8519 fs_give((void **) &nick
);
8528 helper(h_select_rule
, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE
);
8529 ps_global
->mangled_screen
= 1;
8532 cmd_cancelled("Selection by Rule");
8536 removing_leading_and_trailing_white_space(rulenick
);
8538 }while(r
== 3 || r
== 4 || r
== '!');
8542 * The approach of requiring a nickname instead of just allowing the
8543 * user to select from the list of rules has the drawback that a rule
8544 * may not have a nickname, or there may be more than one rule with
8545 * the same nickname. However, it has the benefit of allowing the user
8546 * to type in the nickname and, most importantly, allows us to set
8547 * up the ! (not). We could incorporate the ! into the selection
8548 * screen, but this is easier and also allows the typing of nicks.
8549 * User can just set up nicknames if they want to use this feature.
8551 patgrp
= nick_to_patgrp(rulenick
, rflags
);
8554 if(ps_global
&& ps_global
->ttyo
){
8555 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8556 ps_global
->mangled_footer
= 1;
8559 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8560 match_pattern(patgrp
, stream
, limitsrch
? *limitsrch
: 0, NULL
,
8562 (not ? MP_NOT
: 0) | SE_NOPREFETCH
);
8563 free_patgrp(&patgrp
);
8568 if(limitsrch
&& *limitsrch
){
8569 mail_free_searchset(limitsrch
);
8578 * Allow user to choose a rule from their list of rules.
8580 * Returns an allocated rule nickname on success, NULL otherwise.
8583 choose_a_rule(int rflags
)
8585 char *choice
= NULL
;
8586 char **rule_list
, **lp
;
8591 if(!(nonempty_patterns(rflags
, &pstate
) && first_pattern(&pstate
))){
8592 q_status_message(SM_ORDER
, 3, 3,
8593 _("No rules available. Use Setup/Rules to add some."));
8598 * Build a list of rules to choose from.
8601 for(pat
= first_pattern(&pstate
); pat
; pat
= next_pattern(&pstate
))
8605 q_status_message(SM_ORDER
, 3, 4, _("No rules defined, use Setup/Rules"));
8609 lp
= rule_list
= (char **) fs_get((cnt
+ 1) * sizeof(*rule_list
));
8610 memset(rule_list
, 0, (cnt
+1) * sizeof(*rule_list
));
8612 for(pat
= first_pattern(&pstate
); pat
; pat
= next_pattern(&pstate
))
8613 *lp
++ = cpystr((pat
->patgrp
&& pat
->patgrp
->nick
)
8614 ? pat
->patgrp
->nick
: "?");
8616 /* TRANSLATORS: SELECT A RULE is a screen title
8617 TRANSLATORS: Print something1 using something2.
8618 "rules" is something1 */
8619 choice
= choose_item_from_list(rule_list
, NULL
, _("SELECT A RULE"),
8620 _("rules"), h_select_rule_screen
,
8621 _("HELP FOR SELECTING A RULE NICKNAME"), NULL
);
8624 q_status_message(SM_ORDER
, 1, 4, "No choice");
8626 free_list_array(&rule_list
);
8633 * Select by current thread.
8634 * Sets searched bits in mail_elts for this entire thread
8636 * Args limitsrch -- limit search to this searchset
8638 * Returns 0 on success.
8641 select_by_thread(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**limitsrch
)
8644 PINETHRD_S
*thrd
= NULL
;
8651 for(n
= 1L; n
<= stream
->nmsgs
; n
++)
8652 if((mc
= mail_elt(stream
, n
)) != NULL
)
8653 mc
->searched
= 0; /* clear searched bits */
8655 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
8656 if(thrd
&& thrd
->top
&& thrd
->top
!= thrd
->rawno
)
8657 thrd
= fetch_thread(stream
, thrd
->top
);
8660 * This doesn't unselect if the thread is already selected
8661 * (like select current does), it always selects.
8662 * There is no way to select ! this thread.
8665 set_search_bit_for_thread(stream
, thrd
, limitsrch
);
8674 * Select by message keywords.
8675 * Sets searched bits in mail_elts
8677 * Args limitsrch -- limit search to this searchset
8679 * Returns 0 on success.
8682 select_by_keyword(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8684 int r
, not = 0, we_cancel
= 0;
8685 char keyword
[MAXUSERFLAG
+1], *kword
;
8686 char *error
= NULL
, *p
, *prompt
;
8691 ps_global
->mangled_footer
= 1;
8698 q_status_message(SM_ORDER
, 3, 4, error
);
8699 fs_give((void **) &error
);
8702 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
) && ps_global
->keywords
){
8704 prompt
= _("Keyword (or keyword initial) to NOT match: ");
8706 prompt
= _("Keyword (or keyword initial) to match: ");
8710 prompt
= _("Keyword to NOT match: ");
8712 prompt
= _("Keyword to match: ");
8715 oe_flags
= OE_APPEND_CURRENT
;
8716 r
= optionally_enter(keyword
, -FOOTER_ROWS(ps_global
), 0,
8718 prompt
, sel_key_opt
, help
, &oe_flags
);
8721 /* select keyword from a list */
8722 if((kword
=choose_a_keyword()) != NULL
){
8723 strncpy(keyword
, kword
, sizeof(keyword
)-1);
8724 keyword
[sizeof(keyword
)-1] = '\0';
8725 fs_give((void **) &kword
);
8734 help
= help
== NO_HELP
? h_select_keyword
: NO_HELP
;
8736 cmd_cancelled("Selection by keyword");
8740 removing_leading_and_trailing_white_space(keyword
);
8742 }while(r
== 3 || r
== 4 || r
== '!' || keyword_check(keyword
, &error
));
8745 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
) && ps_global
->keywords
){
8746 p
= initial_to_keyword(keyword
);
8748 strncpy(keyword
, p
, sizeof(keyword
)-1);
8749 keyword
[sizeof(keyword
)-1] = '\0';
8754 * We want to check the keyword, not the nickname of the keyword,
8755 * so convert it to the keyword if necessary.
8757 p
= nick_to_keyword(keyword
);
8759 strncpy(keyword
, p
, sizeof(keyword
)-1);
8760 keyword
[sizeof(keyword
)-1] = '\0';
8763 pgm
= mail_newsearchpgm();
8765 pgm
->unkeyword
= mail_newstringlist();
8766 pgm
->unkeyword
->text
.data
= (unsigned char *) cpystr(keyword
);
8767 pgm
->unkeyword
->text
.size
= strlen(keyword
);
8770 pgm
->keyword
= mail_newstringlist();
8771 pgm
->keyword
->text
.data
= (unsigned char *) cpystr(keyword
);
8772 pgm
->keyword
->text
.size
= strlen(keyword
);
8775 if(ps_global
&& ps_global
->ttyo
){
8776 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8777 ps_global
->mangled_footer
= 1;
8780 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8782 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
8783 pine_mail_search_full(stream
, "UTF-8", pgm
, SE_NOPREFETCH
| SE_FREE
);
8784 /* we know this was freed in mail_search, let caller know */
8796 * Allow user to choose a keyword from their list of keywords.
8798 * Returns an allocated keyword on success, NULL otherwise.
8801 choose_a_keyword(void)
8803 char *choice
= NULL
;
8804 char **keyword_list
, **lp
;
8809 * Build a list of keywords to choose from.
8812 for(cnt
= 0, kw
= ps_global
->keywords
; kw
; kw
= kw
->next
)
8816 q_status_message(SM_ORDER
, 3, 4,
8817 _("No keywords defined, use \"keywords\" option in Setup/Config"));
8821 lp
= keyword_list
= (char **) fs_get((cnt
+ 1) * sizeof(*keyword_list
));
8822 memset(keyword_list
, 0, (cnt
+1) * sizeof(*keyword_list
));
8824 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
)
8825 *lp
++ = cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
8827 /* TRANSLATORS: SELECT A KEYWORD is a screen title
8828 TRANSLATORS: Print something1 using something2.
8829 "keywords" is something1 */
8830 choice
= choose_item_from_list(keyword_list
, NULL
, _("SELECT A KEYWORD"),
8831 _("keywords"), h_select_keyword_screen
,
8832 _("HELP FOR SELECTING A KEYWORD"), NULL
);
8835 q_status_message(SM_ORDER
, 1, 4, "No choice");
8837 free_list_array(&keyword_list
);
8844 * Allow user to choose a list of keywords from their list of keywords.
8846 * Returns allocated list.
8849 choose_list_of_keywords(void)
8851 LIST_SEL_S
*listhead
, *ls
, *p
;
8857 * Build a list of keywords to choose from.
8860 p
= listhead
= NULL
;
8861 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
8863 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
8864 memset(ls
, 0, sizeof(*ls
));
8865 ls
->item
= cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
8878 /* TRANSLATORS: SELECT KEYWORDS is a screen title
8879 Print something1 using something2.
8880 "keywords" is something1 */
8881 if(!select_from_list_screen(listhead
, SFL_ALLOW_LISTMODE
,
8882 _("SELECT KEYWORDS"), _("keywords"),
8883 h_select_multkeyword_screen
,
8884 _("HELP FOR SELECTING KEYWORDS"), NULL
)){
8885 for(cnt
= 0, p
= listhead
; p
; p
= p
->next
)
8889 ret
= (char **) fs_get((cnt
+1) * sizeof(*ret
));
8890 memset(ret
, 0, (cnt
+1) * sizeof(*ret
));
8891 for(i
= 0, p
= listhead
; p
; p
= p
->next
)
8893 ret
[i
++] = cpystr(p
->item
? p
->item
: "");
8896 free_list_sel(&listhead
);
8903 * Allow user to choose a charset
8905 * Returns an allocated charset on success, NULL otherwise.
8908 choose_a_charset(int which_charsets
)
8910 char *choice
= NULL
;
8911 char **charset_list
, **lp
;
8916 * Build a list of charsets to choose from.
8919 for(cnt
= 0, cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
8920 if(!(cs
->flags
& (CF_UNSUPRT
|CF_NOEMAIL
))
8921 && ((which_charsets
& CAC_ALL
)
8922 || (which_charsets
& CAC_POSTING
8923 && cs
->flags
& CF_POSTING
)
8924 || (which_charsets
& CAC_DISPLAY
8925 && cs
->type
!= CT_2022
8926 && (cs
->flags
& (CF_PRIMARY
|CF_DISPLAY
)) == (CF_PRIMARY
|CF_DISPLAY
))))
8931 q_status_message(SM_ORDER
, 3, 4,
8932 _("No charsets found? Enter charset manually."));
8936 lp
= charset_list
= (char **) fs_get((cnt
+ 1) * sizeof(*charset_list
));
8937 memset(charset_list
, 0, (cnt
+1) * sizeof(*charset_list
));
8939 for(cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
8940 if(!(cs
->flags
& (CF_UNSUPRT
|CF_NOEMAIL
))
8941 && ((which_charsets
& CAC_ALL
)
8942 || (which_charsets
& CAC_POSTING
8943 && cs
->flags
& CF_POSTING
)
8944 || (which_charsets
& CAC_DISPLAY
8945 && cs
->type
!= CT_2022
8946 && (cs
->flags
& (CF_PRIMARY
|CF_DISPLAY
)) == (CF_PRIMARY
|CF_DISPLAY
))))
8947 *lp
++ = cpystr(cs
->name
);
8950 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
8951 TRANSLATORS: Print something1 using something2.
8952 "character sets" is something1 */
8953 choice
= choose_item_from_list(charset_list
, NULL
, _("SELECT A CHARACTER SET"),
8954 _("character sets"), h_select_charset_screen
,
8955 _("HELP FOR SELECTING A CHARACTER SET"), NULL
);
8958 q_status_message(SM_ORDER
, 1, 4, "No choice");
8960 free_list_array(&charset_list
);
8967 * Allow user to choose a list of character sets and/or scripts
8969 * Returns allocated list.
8972 choose_list_of_charsets(void)
8974 LIST_SEL_S
*listhead
, *ls
, *p
;
8976 int cnt
, i
, got_one
;
8981 char buf
[1024], *folded
;
8984 * Build a list of charsets to choose from.
8987 p
= listhead
= NULL
;
8989 /* this width is determined by select_from_list_screen() */
8990 width
= ps_global
->ttyo
->screen_cols
- 4;
8992 /* first comes a list of scripts (sets of character sets) */
8993 for(s
= utf8_script(NIL
); s
&& s
->name
; s
++){
8995 limit
= sizeof(buf
)-1;
8997 memset(q
, 0, limit
+1);
9000 sstrncpy(&q
, s
->name
, limit
);
9003 sstrncpy(&q
, " (", limit
-(q
-buf
));
9004 sstrncpy(&q
, s
->description
, limit
-(q
-buf
));
9005 sstrncpy(&q
, ")", limit
-(q
-buf
));
9008 /* add the list of charsets that are in this script */
9010 for(cs
= utf8_charset(NIL
);
9011 cs
&& cs
->name
&& (q
-buf
) < limit
; cs
++){
9012 if(cs
->script
& s
->script
){
9014 * Filter out some un-useful members of the list.
9015 * UTF-7 and UTF-8 weren't actually in the list at the
9016 * time this was written. Just making sure.
9018 if(!strucmp(cs
->name
, "ISO-2022-JP-2")
9019 || !strucmp(cs
->name
, "UTF-7")
9020 || !strucmp(cs
->name
, "UTF-8"))
9024 sstrncpy(&q
, " ", limit
-(q
-buf
));
9027 sstrncpy(&q
, " {", limit
-(q
-buf
));
9030 sstrncpy(&q
, cs
->name
, limit
-(q
-buf
));
9035 sstrncpy(&q
, "}", limit
-(q
-buf
));
9037 /* fold this line so that it can all be seen on the screen */
9038 folded
= fold(buf
, width
, width
, "", " ", FLD_NONE
);
9041 while(t
&& *t
&& (q
= strindex(t
, '\n')) != NULL
){
9044 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9045 memset(ls
, 0, sizeof(*ls
));
9047 ls
->item
= cpystr(s
->name
);
9049 ls
->flags
= SFL_NOSELECT
;
9051 ls
->display_item
= cpystr(t
);
9061 listhead
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9062 memset(listhead
, 0, sizeof(*listhead
));
9063 listhead
->flags
= SFL_NOSELECT
;
9064 listhead
->display_item
=
9065 cpystr(_("Scripts representing groups of related character sets"));
9066 listhead
->next
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9067 memset(listhead
->next
, 0, sizeof(*listhead
));
9068 listhead
->next
->flags
= SFL_NOSELECT
;
9069 listhead
->next
->display_item
=
9070 cpystr(repeat_char(width
, '-'));
9072 listhead
->next
->next
= ls
;
9077 fs_give((void **) &folded
);
9081 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9082 memset(ls
, 0, sizeof(*ls
));
9083 ls
->flags
= SFL_NOSELECT
;
9091 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9092 memset(ls
, 0, sizeof(*ls
));
9093 ls
->flags
= SFL_NOSELECT
;
9095 cpystr(_("Individual character sets, may be mixed with scripts"));
9099 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9100 memset(ls
, 0, sizeof(*ls
));
9101 ls
->flags
= SFL_NOSELECT
;
9103 cpystr(repeat_char(width
, '-'));
9107 /* then comes a list of individual character sets */
9108 for(cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
9109 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9110 memset(ls
, 0, sizeof(*ls
));
9111 ls
->item
= cpystr(cs
->name
);
9124 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9125 Print something1 using something2.
9126 "character sets" is something1 */
9127 if(!select_from_list_screen(listhead
, SFL_ALLOW_LISTMODE
,
9128 _("SELECT CHARACTER SETS"), _("character sets"),
9129 h_select_multcharsets_screen
,
9130 _("HELP FOR SELECTING CHARACTER SETS"), NULL
)){
9131 for(cnt
= 0, p
= listhead
; p
; p
= p
->next
)
9135 ret
= (char **) fs_get((cnt
+1) * sizeof(*ret
));
9136 memset(ret
, 0, (cnt
+1) * sizeof(*ret
));
9137 for(i
= 0, p
= listhead
; p
; p
= p
->next
)
9139 ret
[i
++] = cpystr(p
->item
? p
->item
: "");
9142 free_list_sel(&listhead
);
9147 /* Report quota summary resources in an IMAP server */
9149 void cmd_quota (struct pine
*state
)
9151 QUOTALIST
*imapquota
;
9156 if(!state
->mail_stream
|| !is_imap_stream(state
->mail_stream
)){
9157 q_status_message(SM_ORDER
, 1, 5, "Quota only available for IMAP folders");
9161 if (state
->mail_stream
9162 && !sp_dead_stream(state
->mail_stream
)
9163 && state
->mail_stream
->mailbox
9164 && *state
->mail_stream
->mailbox
9165 && mail_valid_net_parse(state
->mail_stream
->mailbox
, &mb
))
9166 imap_getquotaroot(state
->mail_stream
, mb
.mailbox
);
9168 if(!state
->quota
) /* failed ? */
9169 return; /* go back... */
9171 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
9172 q_status_message(SM_ORDER
| SM_DING
, 3, 3, "Error allocating space.");
9176 so_puts(store
, "Quota Report for ");
9177 so_puts(store
, state
->mail_stream
->original_mailbox
);
9178 so_puts(store
, "\n\n");
9180 for (imapquota
= state
->quota
; imapquota
; imapquota
= imapquota
->next
){
9182 so_puts(store
, _("Resource : "));
9183 so_puts(store
, imapquota
->name
);
9184 so_writec('\n', store
);
9186 so_puts(store
, _("Usage : "));
9187 so_puts(store
, long2string(imapquota
->usage
));
9188 if(!strucmp(imapquota
->name
,"STORAGE"))
9189 so_puts(store
, " KiB ");
9190 if(!strucmp(imapquota
->name
,"MESSAGE")){
9191 so_puts(store
, _(" message"));
9192 if(imapquota
->usage
!= 1)
9193 so_puts(store
, _("s ")); /* plural */
9195 so_puts(store
, _(" "));
9197 so_writec('(', store
);
9198 so_puts(store
, long2string(100*imapquota
->usage
/imapquota
->limit
));
9199 so_puts(store
, "%)\n");
9201 so_puts(store
, _("Limit : "));
9202 so_puts(store
, long2string(imapquota
->limit
));
9203 if(!strucmp(imapquota
->name
,"STORAGE"))
9204 so_puts(store
, " KiB\n\n");
9205 if(!strucmp(imapquota
->name
,"MESSAGE")){
9206 so_puts(store
, _(" message"));
9207 if(imapquota
->usage
!= 1)
9208 so_puts(store
, _("s\n\n")); /* plural */
9210 so_puts(store
, _("\n\n"));
9214 memset(&sargs
, 0, sizeof(SCROLL_S
));
9215 sargs
.text
.text
= so_text(store
);
9216 sargs
.text
.src
= CharStar
;
9217 sargs
.text
.desc
= _("Quota Resources Summary");
9218 sargs
.bar
.title
= _("QUOTA SUMMARY");
9219 sargs
.proc
.tool
= NULL
;
9220 sargs
.help
.text
= h_quota_command
;
9221 sargs
.help
.title
= NULL
;
9222 sargs
.keys
.menu
= NULL
;
9223 setbitmap(sargs
.keys
.bitmap
);
9229 mail_free_quotalist(&(state
->quota
));
9232 /*----------------------------------------------------------------------
9233 Prompt the user for the type of sort he desires
9235 Args: state -- pine state pointer
9236 q1 -- Line to prompt on
9238 Returns 0 if it was cancelled, 1 otherwise.
9241 select_sort(struct pine
*state
, int ql
, SortOrder
*sort
, int *rev
)
9243 char prompt
[200], tmp
[3], *p
;
9245 int deefault
= 'a', retval
= 1;
9250 DLG_SORTPARAM sortsel
;
9252 if (mswin_usedialog ()) {
9254 sortsel
.reverse
= mn_get_revsort (state
->msgmap
);
9255 sortsel
.cursort
= mn_get_sort (state
->msgmap
);
9256 /* assumption here that HelpType is char ** */
9257 sortsel
.helptext
= h_select_sort
;
9260 if ((retval
= os_sortdialog (&sortsel
))) {
9261 *sort
= sortsel
.cursort
;
9262 *rev
= sortsel
.reverse
;
9269 /*----- String together the prompt ------*/
9271 if(F_ON(F_USE_FK
,ps_global
))
9272 strncpy(prompt
, _("Choose type of sort : "), sizeof(prompt
));
9274 strncpy(prompt
, _("Choose type of sort, or 'R' to reverse current sort : "),
9277 for(i
= 0; state
->sort_types
[i
] != EndofList
; i
++) {
9279 p
= sorts
[i
].label
= sort_name(state
->sort_types
[i
]);
9280 while(*(p
+1) && islower((unsigned char)*p
))
9283 sorts
[i
].ch
= tolower((unsigned char)(tmp
[0] = *p
));
9284 sorts
[i
].name
= cpystr(tmp
);
9286 if(mn_get_sort(state
->msgmap
) == state
->sort_types
[i
])
9287 deefault
= sorts
[i
].rval
;
9291 sorts
[i
].rval
= 'r';
9292 sorts
[i
].name
= cpystr("R");
9293 if(F_ON(F_USE_FK
,ps_global
))
9294 sorts
[i
].label
= N_("Reverse");
9296 sorts
[i
].label
= "";
9299 help
= h_select_sort
;
9301 if((F_ON(F_USE_FK
,ps_global
)
9302 && ((s
= double_radio_buttons(prompt
,ql
,sorts
,deefault
,'x',
9303 help
,RB_NORM
)) != 'x'))
9305 (F_OFF(F_USE_FK
,ps_global
)
9306 && ((s
= radio_buttons(prompt
,ql
,sorts
,deefault
,'x',
9307 help
,RB_NORM
)) != 'x'))){
9308 state
->mangled_body
= 1; /* signal screen's changed */
9310 *rev
= !mn_get_revsort(state
->msgmap
);
9312 *sort
= state
->sort_types
[s
];
9314 if(F_ON(F_SHOW_SORT
, ps_global
))
9315 ps_global
->mangled_header
= 1;
9319 cmd_cancelled("Sort");
9323 fs_give((void **)&sorts
[i
].name
);
9325 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
9330 /*---------------------------------------------------------------------
9331 Build list of folders in the given context for user selection
9333 Args: c -- pointer to pointer to folder's context context
9334 f -- folder prefix to display
9335 sublist -- whether or not to use 'f's contents as prefix
9336 lister -- function used to do the actual display
9338 Returns: malloc'd string containing sequence, else NULL if
9339 no messages in msgmap with local "selected" flag.
9342 display_folder_list(CONTEXT_S
**c
, char *f
, int sublist
, int (*lister
) (struct pine
*, CONTEXT_S
**, char *, int))
9346 void (*redraw
)(void) = ps_global
->redrawer
;
9348 push_titlebar_state();
9350 if((rc
= (*lister
)(ps_global
, &tc
, f
, sublist
)) != 0)
9354 pop_titlebar_state();
9356 if((ps_global
->redrawer
= redraw
) != NULL
) /* reset old value, and test */
9357 (*ps_global
->redrawer
)();
9359 if(rc
== 1 && F_ON(F_SELECT_WO_CONFIRM
, ps_global
))
9367 * Allow user to choose a single item from a list of strings.
9369 * Args list -- Array of strings to choose from, NULL terminated.
9370 * displist -- Array of strings to display instead of displaying list.
9371 * Indices correspond to the list array. Display the displist
9372 * but return the item from list if displist non-NULL.
9373 * title -- For conf_scroll_screen
9374 * pdesc -- For conf_scroll_screen
9375 * help -- For conf_scroll_screen
9376 * htitle -- For conf_scroll_screen
9378 * Returns an allocated copy of the chosen item or NULL.
9381 choose_item_from_list(char **list
, char **displist
, char *title
, char *pdesc
, HelpType help
,
9382 char *htitle
, char *cursor_location
)
9384 LIST_SEL_S
*listhead
, *ls
, *p
, *starting_val
= NULL
;
9386 char *ret
= NULL
, *choice
= NULL
;
9388 /* build the LIST_SEL_S list */
9389 p
= listhead
= NULL
;
9390 for(t
= list
, dl
= displist
; *t
; t
++, dl
++){
9391 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9392 memset(ls
, 0, sizeof(*ls
));
9393 ls
->item
= cpystr(*t
);
9395 ls
->display_item
= cpystr(*dl
);
9397 if(cursor_location
&& (cursor_location
== (*t
)))
9411 if(!select_from_list_screen(listhead
, SFL_NONE
, title
, pdesc
,
9412 help
, htitle
, starting_val
))
9413 for(p
= listhead
; !choice
&& p
; p
= p
->next
)
9418 ret
= cpystr(choice
);
9420 free_list_sel(&listhead
);
9427 free_list_sel(LIST_SEL_S
**lsel
)
9430 free_list_sel(&(*lsel
)->next
);
9432 fs_give((void **) &(*lsel
)->item
);
9434 if((*lsel
)->display_item
)
9435 fs_give((void **) &(*lsel
)->display_item
);
9437 fs_give((void **) lsel
);
9443 * file_lister - call pico library's file lister
9446 file_lister(char *title
, char *path
, size_t pathlen
, char *file
, size_t filelen
, int newmail
, int flags
)
9450 void (*redraw
)(void) = ps_global
->redrawer
;
9452 standard_picobuf_setup(&pbf
);
9453 push_titlebar_state();
9457 /* BUG: what about help command and text? */
9458 pbf
.pine_anchor
= title
;
9460 rv
= pico_file_browse(&pbf
, path
, pathlen
, file
, filelen
, NULL
, 0, flags
);
9461 standard_picobuf_teardown(&pbf
);
9462 fix_windsize(ps_global
);
9463 init_signals(); /* has it's own signal stuff */
9465 /* Restore display's titlebar and body */
9466 pop_titlebar_state();
9468 if((ps_global
->redrawer
= redraw
) != NULL
)
9469 (*ps_global
->redrawer
)();
9475 /*----------------------------------------------------------------------
9476 Print current folder index
9480 print_index(struct pine
*state
, MSGNO_S
*msgmap
, int agg
)
9484 char buf
[MAX_SCREEN_COLS
+1];
9486 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++){
9487 if(agg
&& !get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
))
9490 if(!agg
&& msgline_hidden(state
->mail_stream
, msgmap
, i
, 0))
9493 ice
= build_header_line(state
, state
->mail_stream
, msgmap
, i
, NULL
);
9497 * I don't understand why we'd want to mark the current message
9498 * instead of printing out the first character of the status
9499 * so I'm taking it out and including the first character of the
9500 * line instead. Hubert 2006-02-09
9502 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
9506 if(!gf_puts(simple_index_line(buf
,sizeof(buf
),ice
,i
),
9508 || !gf_puts(NEWLINE
, print_char
))
9520 * windows callback to get/set header mode state
9523 header_mode_callback(set
, args
)
9527 return(ps_global
->full_header
);
9532 * windows callback to get/set zoom mode state
9535 zoom_mode_callback(set
, args
)
9539 return(any_lflagged(ps_global
->msgmap
, MN_HIDE
) != 0);
9544 * windows callback to get/set zoom mode state
9547 any_selected_callback(set
, args
)
9551 return(any_lflagged(ps_global
->msgmap
, MN_SLCT
) != 0);
9559 flag_callback(set
, flags
)
9569 case 1: /* Important */
9570 permflag
= ps_global
->mail_stream
->perm_flagged
;
9574 permflag
= ps_global
->mail_stream
->perm_seen
;
9577 case 3: /* Answered */
9578 permflag
= ps_global
->mail_stream
->perm_answered
;
9581 case 4: /* Deleted */
9582 permflag
= ps_global
->mail_stream
->perm_deleted
;
9587 if(!(any_messages(ps_global
->msgmap
, NULL
, "to Flag")
9588 && can_set_flag(ps_global
, "flag", permflag
)))
9591 if(sp_io_error_on_stream(ps_global
->mail_stream
)){
9592 sp_set_io_error_on_stream(ps_global
->mail_stream
, 0);
9593 pine_mail_check(ps_global
->mail_stream
); /* forces write */
9597 msgno
= mn_m2raw(ps_global
->msgmap
, mn_get_cur(ps_global
->msgmap
));
9598 if(msgno
> 0L && ps_global
->mail_stream
9599 && msgno
<= ps_global
->mail_stream
->nmsgs
9600 && (mc
= mail_elt(ps_global
->mail_stream
, msgno
))
9603 * NOTE: code below is *VERY* sensitive to the order of
9604 * the messages defined in resource.h for flag handling.
9605 * Don't change it unless you know what you're doing.
9612 case 1 : /* Important */
9613 flagstr
= "\\FLAGGED";
9614 mflag
= (mc
->flagged
) ? 0L : ST_SET
;
9619 mflag
= (mc
->seen
) ? 0L : ST_SET
;
9622 case 3 : /* Answered */
9623 flagstr
= "\\ANSWERED";
9624 mflag
= (mc
->answered
) ? 0L : ST_SET
;
9627 case 4 : /* Deleted */
9628 flagstr
= "\\DELETED";
9629 mflag
= (mc
->deleted
) ? 0L : ST_SET
;
9632 default : /* bogus */
9636 mail_flag(ps_global
->mail_stream
, long2string(msgno
),
9639 if(ps_global
->redrawer
)
9640 (*ps_global
->redrawer
)();
9667 * BUG: Should teach this about keywords
9673 static MPopup flag_submenu
[] = {
9674 {tMessage
, {N_("Important"), lNormal
}, {IDM_MI_FLAGIMPORTANT
}},
9675 {tMessage
, {N_("New"), lNormal
}, {IDM_MI_FLAGNEW
}},
9676 {tMessage
, {N_("Answered"), lNormal
}, {IDM_MI_FLAGANSWERED
}},
9677 {tMessage
, {N_("Deleted"), lNormal
}, {IDM_MI_FLAGDELETED
}},
9682 flag_submenu
[0].label
.style
= (mc
&& mc
->flagged
) ? lChecked
: lNormal
;
9685 flag_submenu
[1].label
.style
= (mc
&& mc
->seen
) ? lNormal
: lChecked
;
9688 flag_submenu
[2].label
.style
= (mc
&& mc
->answered
) ? lChecked
: lNormal
;
9691 flag_submenu
[3].label
.style
= (mc
&& mc
->deleted
) ? lChecked
: lNormal
;
9693 return(flag_submenu
);
9696 #endif /* _WINDOWS */