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-2014 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
, *topthrd
;
443 if(THRD_INDX_ENABLED()){
444 mn_dec_cur(stream
, msgmap
, MH_ANYTHD
);
445 if(i
== mn_get_cur(msgmap
))
446 q_status_message1(SM_ORDER
, 0, 2,
447 _("Already on first %s in Zoomed Index"),
448 THRD_INDX() ? _("thread") : _("message"));
451 || F_ON(F_NEXT_THRD_WO_CONFIRM
, state
))
454 ret
= want_to(_("View previous thread"), 'y', 'x',
458 q_status_message(SM_ORDER
, 0, 2,
459 _("Viewing previous thread"));
460 new_msgno
= mn_get_cur(msgmap
);
461 mn_set_cur(msgmap
, i
);
462 if(unview_thread(state
, stream
, msgmap
)){
463 state
->next_screen
= mail_index_screen
;
464 state
->view_skipped_index
= 0;
465 state
->mangled_screen
= 1;
468 mn_set_cur(msgmap
, new_msgno
);
469 if(THRD_AUTO_VIEW() && in_index
== View
){
471 thrd
= fetch_thread(stream
,
474 if(count_lflags_in_thread(stream
, thrd
,
477 if(view_thread(state
, stream
, msgmap
, 1)){
478 if(current_index_state
)
479 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
481 state
->view_skipped_index
= 1;
482 command
= MC_VIEW_TEXT
;
489 if(THRD_AUTO_VIEW() && in_index
!= View
){
490 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, new_msgno
));
491 if(thrd
&& thrd
->top
)
492 topthrd
= fetch_thread(stream
, thrd
->top
);
495 j
= count_lflags_in_thread(stream
, topthrd
, msgmap
, MN_NONE
);
498 if(!THRD_AUTO_VIEW() || in_index
== View
|| j
!= 1){
499 if(view_thread(state
, stream
, msgmap
, 1)
500 && current_index_state
)
501 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
505 state
->next_screen
= SCREEN_FUN_NULL
;
508 mn_set_cur(msgmap
, i
); /* put it back */
512 q_status_message1(SM_ORDER
, 0, 2,
513 _("Already on first %s in Zoomed Index"),
514 THRD_INDX() ? _("thread") : _("message"));
521 && ((now
= time(0)) > state
->last_nextitem_forcechk
)){
523 /* check at most once a second */
524 state
->last_nextitem_forcechk
= now
;
527 q_status_message1(SM_ORDER
, 0, 1, _("Already on first %s"),
528 THRD_INDX() ? _("thread") : _("message"));
535 /*---------- Next Message ----------*/
537 if(mn_get_total(msgmap
) > 0L
538 && ((i
= mn_get_cur(msgmap
)) < mn_get_total(msgmap
))){
539 mn_inc_cur(stream
, msgmap
,
540 (in_index
== View
&& THREADING()
541 && sp_viewing_a_thread(stream
))
544 ? MH_ANYTHD
: MH_NONE
);
545 if(i
== mn_get_cur(msgmap
)){
546 PINETHRD_S
*thrd
, *topthrd
;
548 if(THRD_INDX_ENABLED()){
550 mn_inc_cur(stream
, msgmap
, MH_ANYTHD
);
552 if(i
== mn_get_cur(msgmap
)){
553 if(any_lflagged(msgmap
, MN_HIDE
))
554 any_messages(NULL
, "more", "in Zoomed Index");
560 || F_ON(F_NEXT_THRD_WO_CONFIRM
, state
))
563 ret
= want_to(_("View next thread"), 'y', 'x',
567 q_status_message(SM_ORDER
, 0, 2,
568 _("Viewing next thread"));
569 new_msgno
= mn_get_cur(msgmap
);
570 mn_set_cur(msgmap
, i
);
571 if(unview_thread(state
, stream
, msgmap
)){
572 state
->next_screen
= mail_index_screen
;
573 state
->view_skipped_index
= 0;
574 state
->mangled_screen
= 1;
577 mn_set_cur(msgmap
, new_msgno
);
578 if(THRD_AUTO_VIEW() && in_index
== View
){
580 thrd
= fetch_thread(stream
,
583 if(count_lflags_in_thread(stream
, thrd
,
586 if(view_thread(state
, stream
, msgmap
, 1)){
587 if(current_index_state
)
588 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
590 state
->view_skipped_index
= 1;
591 command
= MC_VIEW_TEXT
;
598 if(THRD_AUTO_VIEW() && in_index
!= View
){
599 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, new_msgno
));
600 if(thrd
&& thrd
->top
)
601 topthrd
= fetch_thread(stream
, thrd
->top
);
604 j
= count_lflags_in_thread(stream
, topthrd
, msgmap
, MN_NONE
);
607 if(!THRD_AUTO_VIEW() || in_index
== View
|| j
!= 1){
608 if(view_thread(state
, stream
, msgmap
, 1)
609 && current_index_state
)
610 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
614 state
->next_screen
= SCREEN_FUN_NULL
;
617 mn_set_cur(msgmap
, i
); /* put it back */
621 && (thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, i
)))
623 && get_lflag(stream
, NULL
, thrd
->rawno
, MN_COLL
)){
624 q_status_message(SM_ORDER
, 0, 2,
625 _("Expand collapsed thread to see more messages"));
628 any_messages(NULL
, "more", "in Zoomed Index");
636 || (state
->context_current
->use
& CNTXT_INCMNG
)){
637 char nextfolder
[MAXPATH
];
639 strncpy(nextfolder
, state
->cur_folder
, sizeof(nextfolder
));
640 nextfolder
[sizeof(nextfolder
)-1] = '\0';
641 if(next_folder(NULL
, nextfolder
, sizeof(nextfolder
), nextfolder
,
642 state
->context_current
, NULL
, NULL
))
643 strncpy(prompt
, _(". Press TAB for next folder."),
646 strncpy(prompt
, _(". No more folders to TAB to."),
649 prompt
[sizeof(prompt
)-1] = '\0';
652 any_messages(NULL
, (mn_get_total(msgmap
) > 0L) ? "more" : NULL
,
653 prompt
[0] ? prompt
: NULL
);
656 && ((now
= time(0)) > state
->last_nextitem_forcechk
)){
658 /* check at most once a second */
659 state
->last_nextitem_forcechk
= now
;
666 /*---------- Delete message ----------*/
668 (void) cmd_delete(state
, msgmap
, MCMD_NONE
,
669 (in_index
== View
) ? cmd_delete_view
: cmd_delete_index
);
673 /*---------- Undelete message ----------*/
675 (void) cmd_undelete(state
, msgmap
, MCMD_NONE
);
676 update_titlebar_status();
680 /*---------- Reply to message ----------*/
682 (void) cmd_reply(state
, msgmap
, MCMD_NONE
);
686 /*---------- Forward message ----------*/
688 (void) cmd_forward(state
, msgmap
, MCMD_NONE
);
692 /*---------- Quit pine ------------*/
694 state
->next_screen
= quit_screen
;
695 dprint((1,"MAIL_CMD: quit\n"));
699 /*---------- Compose message ----------*/
701 state
->prev_screen
= (in_index
== View
) ? mail_view_screen
703 compose_screen(state
);
704 state
->mangled_screen
= 1;
705 if (state
->next_screen
)
710 /*---------- Alt Compose message ----------*/
712 state
->prev_screen
= (in_index
== View
) ? mail_view_screen
715 if(state
->next_screen
)
721 /*--------- Folders menu ------------*/
723 state
->start_in_context
= 1;
725 /*--------- Top of Folders list menu ------------*/
726 case MC_COLLECTIONS
:
727 state
->next_screen
= folder_screen
;
728 dprint((2,"MAIL_CMD: going to folder/collection menu\n"));
732 /*---------- Open specific new folder ----------*/
734 tc
= (state
->context_last
&& !NEWS_TEST(state
->context_current
))
735 ? state
->context_last
: state
->context_current
;
737 newfolder
= broach_folder(question_line
, 1, ¬realinbox
, &tc
);
739 visit_folder(state
, newfolder
, tc
, NULL
, notrealinbox
? 0L : DB_INBOXWOCNTXT
);
746 /*------- Go to Index Screen ----------*/
748 state
->next_screen
= mail_index_screen
;
751 /*------- Skip to next interesting message -----------*/
757 * If we're in the thread index, start looking after this
758 * thread. We don't want to match something in the current
761 start
= mn_get_cur(msgmap
);
762 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
763 if(mn_get_revsort(msgmap
)){
764 /* if reversed, top of thread is last one before next thread */
765 if(thrd
&& thrd
->top
)
766 start
= mn_raw2m(msgmap
, thrd
->top
);
769 /* last msg of thread is at the ends of the branches/nexts */
771 start
= mn_raw2m(msgmap
, thrd
->rawno
);
773 thrd
= fetch_thread(stream
, thrd
->branch
);
775 thrd
= fetch_thread(stream
, thrd
->next
);
782 * Flags is 0 in this case because we want to not skip
783 * messages inside of threads so that we can find threads
784 * which have some unseen messages even though the top-level
785 * of the thread is already seen.
786 * If new_msgno ends up being a message which is not visible
787 * because it isn't at the top-level, the current message #
788 * will be adjusted below in adjust_cur.
791 new_msgno
= next_sorted_flagged((F_UNDEL
793 | ((F_ON(F_TAB_TO_NEW
,state
))
795 stream
, start
, &flags
);
797 else if(THREADING() && sp_viewing_a_thread(stream
)){
798 PINETHRD_S
*thrd
, *topthrd
= NULL
;
800 start
= mn_get_cur(msgmap
);
803 * Things are especially complicated when we're viewing_a_thread
804 * from the thread index. First we have to check within the
805 * current thread for a new message. If none is found, then
806 * we search in the next threads and offer to continue in
807 * them. Then we offer to go to the next folder.
809 flags
= NSF_SKIP_CHID
;
810 new_msgno
= next_sorted_flagged((F_UNDEL
812 | ((F_ON(F_TAB_TO_NEW
,state
))
814 stream
, start
, &flags
);
816 * If we found a match then we are done, that is another message
817 * in the current thread index. Otherwise, we have to look
820 if(!(flags
& NSF_FLAG_MATCH
)){
825 new_msgno
= next_sorted_flagged((F_UNDEL
827 | ((F_ON(F_TAB_TO_NEW
,
830 stream
, start
, &flags
);
832 * If we got a match, new_msgno is a message in
833 * a different thread from the one we are viewing.
835 if(flags
& NSF_FLAG_MATCH
){
836 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
,new_msgno
));
837 if(thrd
&& thrd
->top
)
838 topthrd
= fetch_thread(stream
, thrd
->top
);
840 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD
, state
)){
841 static ESCKEY_S next_opt
[] = {
842 {'y', 'y', "Y", N_("Yes")},
843 {'n', 'n', "N", N_("No")},
844 {TAB
, 'n', "Tab", N_("NextNew")},
849 snprintf(prompt
, sizeof(prompt
), _("View thread number %s? "),
850 topthrd
? comatose(topthrd
->thrdno
) : "?");
852 snprintf(prompt
, sizeof(prompt
),
853 _("View message in thread number %s? "),
854 topthrd
? comatose(topthrd
->thrdno
) : "?");
856 prompt
[sizeof(prompt
)-1] = '\0';
858 ret
= radio_buttons(prompt
, -FOOTER_ROWS(state
),
859 next_opt
, 'y', 'x', NO_HELP
,
870 if(unview_thread(state
, stream
, msgmap
)){
871 state
->next_screen
= mail_index_screen
;
872 state
->view_skipped_index
= 0;
873 state
->mangled_screen
= 1;
876 mn_set_cur(msgmap
, new_msgno
);
877 if(THRD_AUTO_VIEW()){
879 if(count_lflags_in_thread(stream
, topthrd
,
880 msgmap
, MN_NONE
) == 1){
881 if(view_thread(state
, stream
, msgmap
, 1)){
882 if(current_index_state
)
883 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
885 state
->view_skipped_index
= 1;
886 command
= MC_VIEW_TEXT
;
892 if(view_thread(state
, stream
, msgmap
, 1) && current_index_state
)
893 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
895 state
->next_screen
= SCREEN_FUN_NULL
;
898 else if(ret
== 'n' && topthrd
){
900 * skip to end of this thread and look starting
901 * in the next thread.
903 if(mn_get_revsort(msgmap
)){
905 * if reversed, top of thread is last one
908 start
= mn_raw2m(msgmap
, topthrd
->rawno
);
912 * last msg of thread is at the ends of
917 start
= mn_raw2m(msgmap
, thrd
->rawno
);
919 thrd
= fetch_thread(stream
, thrd
->branch
);
921 thrd
= fetch_thread(stream
, thrd
->next
);
937 start
= mn_get_cur(msgmap
);
940 * If we are on a collapsed thread, start looking after the
941 * collapsed part, unless we are viewing the message.
943 if(THREADING() && in_index
!= View
){
948 rawno
= mn_m2raw(msgmap
, start
);
949 thrd
= fetch_thread(stream
, rawno
);
950 collapsed
= thrd
&& thrd
->next
951 && get_lflag(stream
, NULL
, rawno
, MN_COLL
);
954 if(mn_get_revsort(msgmap
)){
955 if(thrd
&& thrd
->top
)
956 start
= mn_raw2m(msgmap
, thrd
->top
);
960 start
= mn_raw2m(msgmap
, thrd
->rawno
);
962 thrd
= fetch_thread(stream
, thrd
->branch
);
964 thrd
= fetch_thread(stream
, thrd
->next
);
973 new_msgno
= next_sorted_flagged((F_UNDEL
975 | ((F_ON(F_TAB_TO_NEW
,state
))
977 stream
, start
, &flags
);
981 * If there weren't any unread messages left, OR there
982 * aren't any messages at all, we may want to offer to
983 * go on to the next folder...
985 if(flags
& NSF_FLAG_MATCH
){
986 mn_set_cur(msgmap
, new_msgno
);
988 adjust_cur_to_visible(stream
, msgmap
);
991 int in_inbox
= sp_flagged(stream
, SP_INBOX
);
993 if(state
->context_current
994 && ((NEWS_TEST(state
->context_current
)
995 && context_isambig(state
->cur_folder
))
996 || ((state
->context_current
->use
& CNTXT_INCMNG
)
998 || folder_index(state
->cur_folder
,
999 state
->context_current
,
1000 FI_FOLDER
) >= 0)))){
1001 char nextfolder
[MAXPATH
];
1002 MAILSTREAM
*nextstream
= NULL
;
1006 strncpy(nextfolder
, state
->cur_folder
, sizeof(nextfolder
));
1007 nextfolder
[sizeof(nextfolder
)-1] = '\0';
1009 if(!(next_folder(&nextstream
, nextfolder
, sizeof(nextfolder
), nextfolder
,
1010 state
->context_current
, &recent_cnt
,
1011 F_ON(F_TAB_NO_CONFIRM
,state
)
1012 ? NULL
: &did_cancel
))){
1014 static ESCKEY_S inbox_opt
[] = {
1015 {'y', 'y', "Y", N_("Yes")},
1016 {'n', 'n', "N", N_("No")},
1017 {TAB
, 'z', "Tab", N_("To Inbox")},
1021 if(F_ON(F_RET_INBOX_NO_CONFIRM
,state
))
1024 /* TRANSLATORS: this is a question, with some information followed
1025 by Return to INBOX? */
1026 if(state
->context_current
->use
&CNTXT_INCMNG
)
1027 snprintf(prompt
, sizeof(prompt
), _("No more incoming folders. Return to \"%s\"? "), state
->inbox_name
);
1029 snprintf(prompt
, sizeof(prompt
), _("No more news groups. Return to \"%s\"? "), state
->inbox_name
);
1031 ret
= radio_buttons(prompt
, -FOOTER_ROWS(state
),
1032 inbox_opt
, 'y', 'x',
1037 * 'z' is a synonym for 'y'. It is not 'y'
1038 * so that it isn't displayed as a default
1039 * action with square-brackets around it
1042 if(ret
== 'y' || ret
== 'z'){
1043 visit_folder(state
, state
->inbox_name
,
1044 state
->context_current
,
1045 NULL
, DB_INBOXWOCNTXT
);
1049 else if (did_cancel
)
1050 cmd_cancelled(NULL
);
1052 if(state
->context_current
->use
&CNTXT_INCMNG
)
1053 q_status_message(SM_ORDER
, 0, 2, _("No more incoming folders"));
1055 q_status_message(SM_ORDER
, 0, 2, _("No more news groups"));
1061 {char *front
, type
[80], cnt
[80], fbuf
[MAX_SCREEN_COLS
/2+1];
1062 int rbspace
, avail
, need
, take_back
;
1066 * Incoming_folder_ or news_group_ or folder_ or group_
1068 * _(13 recent) or _(some recent) or nothing
1071 front
= "View next";
1073 (state
->context_current
->use
& CNTXT_INCMNG
)
1074 ? "Incoming folder" : "news group",
1076 type
[sizeof(type
)-1] = '\0';
1077 snprintf(cnt
, sizeof(cnt
), " (%.*s %s)", sizeof(cnt
)-20,
1078 recent_cnt
? long2string(recent_cnt
) : "some",
1079 F_ON(F_TAB_USES_UNSEEN
, ps_global
)
1080 ? "unseen" : "recent");
1081 cnt
[sizeof(cnt
)-1] = '\0';
1084 * Space reserved for radio_buttons call.
1085 * If we make this 3 then radio_buttons won't mess
1086 * with the prompt. If we make it 2, then we get
1087 * one more character to use but radio_buttons will
1088 * cut off the last character of our prompt, which is
1089 * ok because it is a space.
1092 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
1094 need
= strlen(front
)+1 + strlen(type
)+1 +
1095 + strlen(nextfolder
)+2 + strlen(cnt
) +
1098 take_back
= strlen(type
);
1100 (state
->context_current
->use
& CNTXT_INCMNG
)
1101 ? "folder" : "group", sizeof(type
));
1102 take_back
-= strlen(type
);
1105 need
-= strlen(cnt
);
1110 snprintf(prompt
, sizeof(prompt
), "%.*s %.*s \"%.*s\"%.*s? ",
1111 sizeof(prompt
)/8, front
,
1112 sizeof(prompt
)/8, type
,
1114 short_str(nextfolder
, fbuf
, sizeof(fbuf
),
1115 strlen(nextfolder
) -
1116 ((need
>avail
) ? (need
-avail
) : 0),
1118 sizeof(prompt
)/8, cnt
);
1119 prompt
[sizeof(prompt
)-1] = '\0';
1123 * When help gets added, this'll have to become
1124 * a loop like the rest...
1126 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD
, state
)){
1127 static ESCKEY_S next_opt
[] = {
1128 {'y', 'y', "Y", N_("Yes")},
1129 {'n', 'n', "N", N_("No")},
1130 {TAB
, 'n', "Tab", N_("NextNew")},
1134 ret
= radio_buttons(prompt
, -FOOTER_ROWS(state
),
1135 next_opt
, 'y', 'x', NO_HELP
,
1138 cmd_cancelled(NULL
);
1146 if(nextstream
&& sp_dead_stream(nextstream
))
1149 visit_folder(state
, nextfolder
,
1150 state
->context_current
, nextstream
,
1152 /* visit_folder takes care of nextstream */
1160 pine_mail_close(nextstream
);
1164 (mn_get_total(msgmap
) > 0L)
1165 ? IS_NEWS(stream
) ? "more undeleted" : "more new"
1175 /*------- Zoom -----------*/
1178 * Right now the way zoom is implemented is sort of silly.
1179 * There are two per-message flags where just one and a
1180 * global "zoom mode" flag to suppress messags from the index
1183 if(any_messages(msgmap
, NULL
, "to Zoom on")){
1184 if(unzoom_index(state
, stream
, msgmap
)){
1185 dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
1186 q_status_message(SM_ORDER
,0,2, _("Index Zoom Mode is now off"));
1188 else if((i
= zoom_index(state
, stream
, msgmap
, MN_SLCT
)) != 0){
1189 if(any_lflagged(msgmap
, MN_HIDE
)){
1190 dprint((4,"\n\n ---- Entering ZOOM mode ----\n"));
1191 q_status_message4(SM_ORDER
, 0, 2,
1192 _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"),
1193 THRD_INDX() ? "" : comatose(i
),
1194 THRD_INDX() ? "" : " ",
1195 THRD_INDX() ? _("threads") : _("message"),
1196 THRD_INDX() ? "" : plural(i
));
1199 q_status_message(SM_ORDER
, 0, 2,
1200 _("All messages selected, so not entering Index Zoom Mode"));
1203 any_messages(NULL
, "selected", "to Zoom on");
1209 /*---------- print message on paper ----------*/
1211 if(any_messages(msgmap
, NULL
, "to print"))
1212 (void) cmd_print(state
, msgmap
, MCMD_NONE
, in_index
);
1217 /*---------- Take Address ----------*/
1219 if(F_ON(F_ENABLE_ROLE_TAKE
, state
) ||
1220 any_messages(msgmap
, NULL
, "to Take address from"))
1221 (void) cmd_take_addr(state
, msgmap
, MCMD_NONE
);
1226 /*---------- Save Message ----------*/
1228 if(any_messages(msgmap
, NULL
, "to Save"))
1229 (void) cmd_save(state
, stream
, msgmap
, MCMD_NONE
, in_index
);
1234 /*---------- Export message ----------*/
1236 if(any_messages(msgmap
, NULL
, "to Export")){
1237 (void) cmd_export(state
, msgmap
, question_line
, MCMD_NONE
);
1238 state
->mangled_footer
= 1;
1244 /*---------- Expunge ----------*/
1246 (void) cmd_expunge(state
, stream
, msgmap
, MCMD_NONE
);
1250 /*------- Unexclude -----------*/
1252 if(!(IS_NEWS(stream
) && stream
->rdonly
)){
1253 q_status_message(SM_ORDER
, 0, 3,
1254 _("Unexclude not available for mail folders"));
1256 else if(any_lflagged(msgmap
, MN_EXLD
)){
1262 * Since excluded means "hidden deleted" and "killed",
1263 * the count should reflect the former.
1265 pgm
= mail_newsearchpgm();
1267 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
1268 for(i
= 1L, del_count
= 0L; i
<= stream
->nmsgs
; i
++)
1269 if((mc
= mail_elt(stream
, i
)) && mc
->searched
1270 && get_lflag(stream
, NULL
, i
, MN_EXLD
)
1271 && !(msgno_exceptions(stream
, i
, "0", &exbits
, FALSE
)
1272 && (exbits
& MSG_EX_FILTERED
)))
1276 state
->mangled_footer
= 1;
1277 snprintf(prompt
, sizeof(prompt
), "UNexclude %ld message%s in %.*s", del_count
,
1278 plural(del_count
), sizeof(prompt
)-40,
1279 pretty_fn(state
->cur_folder
));
1280 prompt
[sizeof(prompt
)-1] = '\0';
1281 if(F_ON(F_FULL_AUTO_EXPUNGE
, state
)
1282 || (F_ON(F_AUTO_EXPUNGE
, state
)
1283 && (state
->context_current
1284 && (state
->context_current
->use
& CNTXT_INCMNG
))
1285 && context_isambig(state
->cur_folder
))
1286 || want_to(prompt
, 'y', 0, NO_HELP
, WT_NORM
) == 'y'){
1287 long save_cur_rawno
;
1288 int were_viewing_a_thread
;
1290 save_cur_rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
1291 were_viewing_a_thread
= (THREADING()
1292 && sp_viewing_a_thread(stream
));
1294 if(msgno_include(stream
, msgmap
, MI_NONE
)){
1295 clear_index_cache(stream
, 0);
1297 if(stream
&& stream
->spare
)
1298 erase_threading_info(stream
, msgmap
);
1300 refresh_sort(stream
, msgmap
, SRT_NON
);
1303 if(were_viewing_a_thread
){
1304 if(save_cur_rawno
> 0L)
1305 mn_set_cur(msgmap
, mn_raw2m(msgmap
,save_cur_rawno
));
1307 if(view_thread(state
, stream
, msgmap
, 1) && current_index_state
)
1308 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
1311 if(save_cur_rawno
> 0L)
1312 mn_set_cur(msgmap
, mn_raw2m(msgmap
,save_cur_rawno
));
1314 state
->mangled_screen
= 1;
1315 q_status_message2(SM_ORDER
, 0, 4,
1316 "%s message%s UNexcluded",
1317 long2string(del_count
),
1320 if(in_index
!= View
)
1321 adjust_cur_to_visible(stream
, msgmap
);
1324 any_messages(NULL
, NULL
, "UNexcluded");
1327 any_messages(NULL
, "excluded", "to UNexclude");
1330 any_messages(NULL
, "excluded", "to UNexclude");
1335 /*------- Make Selection -----------*/
1337 if(any_messages(msgmap
, NULL
, "to Select")){
1338 if(aggregate_select(state
, msgmap
, question_line
, in_index
) == 0
1339 && (in_index
== MsgIndx
|| in_index
== ThrdIndx
)
1340 && F_ON(F_AUTO_ZOOM
, state
)
1341 && any_lflagged(msgmap
, MN_SLCT
) > 0L
1342 && !any_lflagged(msgmap
, MN_HIDE
))
1343 (void) zoom_index(state
, stream
, msgmap
, MN_SLCT
);
1349 /*------- Toggle Current Message Selection State -----------*/
1351 if(any_messages(msgmap
, NULL
, NULL
)){
1352 if((select_by_current(state
, msgmap
, in_index
)
1353 || (F_OFF(F_UNSELECT_WONT_ADVANCE
, state
)
1354 && !any_lflagged(msgmap
, MN_HIDE
)))
1355 && (i
= mn_get_cur(msgmap
)) < mn_get_total(msgmap
)){
1356 /* advance current */
1357 mn_inc_cur(stream
, msgmap
,
1358 (in_index
== View
&& THREADING()
1359 && sp_viewing_a_thread(stream
))
1361 : (in_index
== View
)
1362 ? MH_ANYTHD
: MH_NONE
);
1369 /*------- Apply command -----------*/
1371 if(any_messages(msgmap
, NULL
, NULL
)){
1372 if(any_lflagged(msgmap
, MN_SLCT
) > 0L){
1373 if(apply_command(state
, stream
, msgmap
, 0,
1374 AC_NONE
, question_line
)){
1375 if(F_ON(F_AUTO_UNSELECT
, state
)){
1376 agg_select_all(stream
, msgmap
, NULL
, 0);
1377 unzoom_index(state
, stream
, msgmap
);
1379 else if(F_ON(F_AUTO_UNZOOM
, state
))
1380 unzoom_index(state
, stream
, msgmap
);
1384 any_messages(NULL
, NULL
, "to Apply command to. Try \"Select\"");
1390 /*-------- Sort command -------*/
1393 int were_threading
= THREADING();
1394 SortOrder sort
= mn_get_sort(msgmap
);
1395 int rev
= mn_get_revsort(msgmap
);
1397 dprint((1,"MAIL_CMD: sort\n"));
1398 if(select_sort(state
, question_line
, &sort
, &rev
)){
1399 /* $ command reinitializes threading collapsed/expanded info */
1400 if(SORT_IS_THREADED(msgmap
) && !SEP_THRDINDX())
1401 erase_threading_info(stream
, msgmap
);
1403 if(ps_global
&& ps_global
->ttyo
){
1404 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
1405 ps_global
->mangled_footer
= 1;
1408 sort_folder(stream
, msgmap
, sort
, rev
, SRT_VRB
|SRT_MAN
);
1411 state
->mangled_footer
= 1;
1414 * We've changed whether we are threading or not so we need to
1415 * exit the index and come back in so that we switch between the
1416 * thread index and the regular index. Sort_folder will have
1417 * reset viewing_a_thread if necessary.
1420 && ((!were_threading
&& THREADING())
1421 || (were_threading
&& !THREADING()))){
1422 state
->next_screen
= mail_index_screen
;
1423 state
->mangled_screen
= 1;
1430 /*------- Toggle Full Headers -----------*/
1432 state
->full_header
++;
1433 if(state
->full_header
== 1){
1434 if(!(state
->quote_suppression_threshold
1435 && (state
->some_quoting_was_suppressed
|| in_index
!= View
)))
1436 state
->full_header
++;
1438 else if(state
->full_header
> 2)
1439 state
->full_header
= 0;
1441 switch(state
->full_header
){
1443 q_status_message(SM_ORDER
, 0, 3,
1444 _("Display of full headers is now off."));
1448 q_status_message1(SM_ORDER
, 0, 3,
1449 _("Quotes displayed, use %s to see full headers"),
1450 F_ON(F_USE_FK
, state
) ? "F9" : "H");
1454 q_status_message(SM_ORDER
, 0, 3,
1455 _("Display of full headers is now on."));
1470 /*------- Try to decrypt message -----------*/
1472 if(state
->smime
&& state
->smime
->need_passphrase
)
1473 smime_get_passphrase();
1479 state
->next_screen
= smime_info_screen
;
1484 /*------- Bounce -----------*/
1486 (void) cmd_bounce(state
, msgmap
, MCMD_NONE
);
1490 /*------- Flag -----------*/
1492 dprint((4, "\n - flag message -\n"));
1493 (void) cmd_flag(state
, msgmap
, MCMD_NONE
);
1497 /*------- Pipe message -----------*/
1499 (void) cmd_pipe(state
, msgmap
, MCMD_NONE
);
1503 /*--------- Default, unknown command ----------*/
1505 panic("Unexpected command case");
1509 return((a_changed
|| mn_get_cur(msgmap
) != old_msgno
) ? 1 : 0);
1514 /*----------------------------------------------------------------------
1515 Map some of the special characters into sensible strings for human
1517 c is a UCS-4 character!
1520 pretty_command(UCS c
)
1522 static char buf
[10];
1529 case ' ' : s
= "SPACE"; break;
1530 case '\033' : s
= "ESC"; break;
1531 case '\177' : s
= "DEL"; break;
1532 case ctrl('I') : s
= "TAB"; break;
1533 case ctrl('J') : s
= "LINEFEED"; break;
1534 case ctrl('M') : s
= "RETURN"; break;
1535 case ctrl('Q') : s
= "XON"; break;
1536 case ctrl('S') : s
= "XOFF"; break;
1537 case KEY_UP
: s
= "Up Arrow"; break;
1538 case KEY_DOWN
: s
= "Down Arrow"; break;
1539 case KEY_RIGHT
: s
= "Right Arrow"; break;
1540 case KEY_LEFT
: s
= "Left Arrow"; break;
1541 case KEY_PGUP
: s
= "Prev Page"; break;
1542 case KEY_PGDN
: s
= "Next Page"; break;
1543 case KEY_HOME
: s
= "Home"; break;
1544 case KEY_END
: s
= "End"; break;
1545 case KEY_DEL
: s
= "Delete"; break; /* Not necessary DEL! */
1546 case KEY_JUNK
: s
= "Junk!"; break;
1547 case BADESC
: s
= "Bad Esc"; break;
1548 case NO_OP_IDLE
: s
= "NO_OP_IDLE"; break;
1549 case NO_OP_COMMAND
: s
= "NO_OP_COMMAND"; break;
1550 case KEY_RESIZE
: s
= "KEY_RESIZE"; break;
1551 case KEY_UTF8
: s
= "KEY_UTF8"; break;
1552 case KEY_MOUSE
: s
= "KEY_MOUSE"; break;
1553 case KEY_SCRLUPL
: s
= "KEY_SCRLUPL"; break;
1554 case KEY_SCRLDNL
: s
= "KEY_SCRLDNL"; break;
1555 case KEY_SCRLTO
: s
= "KEY_SCRLTO"; break;
1556 case KEY_XTERM_MOUSE
: s
= "KEY_XTERM_MOUSE"; break;
1557 case KEY_DOUBLE_ESC
: s
= "KEY_DOUBLE_ESC"; break;
1558 case CTRL_KEY_UP
: s
= "Ctrl Up Arrow"; break;
1559 case CTRL_KEY_DOWN
: s
= "Ctrl Down Arrow"; break;
1560 case CTRL_KEY_RIGHT
: s
= "Ctrl Right Arrow"; break;
1561 case CTRL_KEY_LEFT
: s
= "Ctrl Left Arrow"; break;
1574 snprintf(s
= buf
, sizeof(buf
), "F%ld", (long) (c
- PF1
+ 1));
1578 if(c
< ' ' || (c
>= 0x80 && c
< 0xA0)){
1583 d
= (c
& 0x1f) + 'A' - 1;
1584 snprintf(s
= buf
, sizeof(buf
), "%c%c", c1
? '~' : '^', d
);
1587 memset(buf
, 0, sizeof(buf
));
1588 utf8_put((unsigned char *) buf
, (unsigned long) c
);
1598 /*----------------------------------------------------------------------
1599 Complain about bogus input
1601 Args: ch -- input command to complain about
1602 help -- string indicating where to get help
1606 bogus_command(UCS cmd
, char *help
)
1608 if(cmd
== ctrl('Q') || cmd
== ctrl('S'))
1609 q_status_message1(SM_ASYNC
, 0, 2,
1610 "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.",
1611 pretty_command(cmd
));
1612 else if(cmd
== KEY_JUNK
)
1613 q_status_message3(SM_ORDER
, 0, 2,
1614 "Invalid key pressed.%s%s%s",
1615 (help
) ? " Use " : "",
1617 (help
) ? " for help" : "");
1619 q_status_message4(SM_ORDER
, 0, 2,
1620 "Command \"%s\" not defined for this screen.%s%s%s",
1621 pretty_command(cmd
),
1622 (help
) ? " Use " : "",
1624 (help
) ? " for help" : "");
1629 bogus_utf8_command(char *cmd
, char *help
)
1631 q_status_message4(SM_ORDER
, 0, 2,
1632 "Command \"%s\" not defined for this screen.%s%s%s",
1634 (help
) ? " Use " : "",
1636 (help
) ? " for help" : "");
1640 /*----------------------------------------------------------------------
1641 Execute FLAG message command
1643 Args: state -- Various satate info
1644 msgmap -- map of c-client to local message numbers
1646 Result: with side effect of "current" message FLAG flag set or UNset
1650 cmd_flag(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
1652 char *flagit
, *seq
, *screen_text
[20], **exp
, **p
, *answer
= NULL
;
1653 char *keyword_array
[2];
1654 int user_defined_flags
= 0, mailbox_flags
= 0;
1655 int directly_to_maint_screen
= 0;
1656 long unflagged
, flagged
, flags
, rawno
;
1657 MESSAGECACHE
*mc
= NULL
;
1659 int i
, cnt
, is_set
, trouble
= 0, rv
= 0;
1661 struct flag_table
*fp
, *ftbl
= NULL
;
1662 struct flag_screen flag_screen
;
1663 static char *flag_screen_text1
[] = {
1664 N_(" Set desired flags for current message below. An 'X' means set"),
1665 N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."),
1669 static char *flag_screen_text2
[] = {
1670 N_(" Set desired flags below for selected messages. A '?' means to"),
1671 N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"),
1672 N_(" to unset it. Use the \"Return\" key to toggle, and choose"),
1673 N_(" \"Exit\" when finished."),
1677 static struct flag_table default_ftbl
[] = {
1678 {N_("Important"), h_flag_important
, F_FLAG
, 0, 0, NULL
, NULL
},
1679 {N_("New"), h_flag_new
, F_SEEN
, 0, 0, NULL
, NULL
},
1680 {N_("Answered"), h_flag_answered
, F_ANS
, 0, 0, NULL
, NULL
},
1681 {N_("Forwarded"), h_flag_forwarded
, F_FWD
, 0, 0, NULL
, NULL
},
1682 {N_("Deleted"), h_flag_deleted
, F_DEL
, 0, 0, NULL
, NULL
},
1683 {NULL
, NO_HELP
, 0, 0, 0, NULL
, NULL
}
1686 /* Only check for dead stream for now. Should check permanent flags
1689 if(!(any_messages(msgmap
, NULL
, "to Flag") && can_set_flag(state
, "flag", 1)))
1692 if(sp_io_error_on_stream(state
->mail_stream
)){
1693 sp_set_io_error_on_stream(state
->mail_stream
, 0);
1694 pine_mail_check(state
->mail_stream
); /* forces write */
1700 user_defined_flags
= 0;
1706 /* count how large ftbl will be */
1707 for(cnt
= 0; default_ftbl
[cnt
].name
; cnt
++)
1710 /* add user flags */
1711 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1712 if(!((kw
->nick
&& !strucmp(FORWARDED_FLAG
, kw
->nick
)) || (kw
->kw
&& !strucmp(FORWARDED_FLAG
, kw
->kw
)))){
1713 user_defined_flags
++;
1719 * Add mailbox flags that aren't user-defined flags.
1720 * Don't consider it if it matches either one of our defined
1721 * keywords or one of our defined nicknames for a keyword.
1723 for(i
= 0; stream_to_user_flag_name(state
->mail_stream
, i
); i
++){
1726 q
= stream_to_user_flag_name(state
->mail_stream
, i
);
1728 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1729 if((kw
->nick
&& !strucmp(kw
->nick
, q
)) || (kw
->kw
&& !strucmp(kw
->kw
, q
)))
1734 if(!kw
&& !(q
&& !strucmp(FORWARDED_FLAG
, q
))){
1740 cnt
+= (user_defined_flags
? 2 : 0) + (mailbox_flags
? 2 : 0);
1742 /* set up ftbl, first the system flags */
1743 ftbl
= (struct flag_table
*) fs_get((cnt
+1) * sizeof(*ftbl
));
1744 memset(ftbl
, 0, (cnt
+1) * sizeof(*ftbl
));
1745 for(i
= 0, fp
= ftbl
; default_ftbl
[i
].name
; i
++, fp
++){
1746 fp
->name
= cpystr(default_ftbl
[i
].name
);
1747 fp
->help
= default_ftbl
[i
].help
;
1748 fp
->flag
= default_ftbl
[i
].flag
;
1749 fp
->set
= default_ftbl
[i
].set
;
1750 fp
->ukn
= default_ftbl
[i
].ukn
;
1753 if(user_defined_flags
){
1754 fp
->flag
= F_COMMENT
;
1755 fp
->name
= cpystr("");
1757 fp
->flag
= F_COMMENT
;
1758 len
= strlen(_("User-defined Keywords from Setup/Config"));
1759 fp
->name
= (char *) fs_get((len
+6+6+1) * sizeof(char));
1760 snprintf(fp
->name
, len
+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config"));
1764 /* then the user-defined keywords */
1765 if(user_defined_flags
)
1766 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1767 if(!((kw
->nick
&& !strucmp(FORWARDED_FLAG
, kw
->nick
))
1768 || (kw
->kw
&& !strucmp(FORWARDED_FLAG
, kw
->kw
)))){
1769 fp
->name
= cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
1770 fp
->keyword
= cpystr(kw
->kw
? kw
->kw
: "");
1771 if(kw
->nick
&& kw
->kw
){
1774 l
= strlen(kw
->kw
)+2;
1775 fp
->comment
= (char *) fs_get((l
+1) * sizeof(char));
1776 snprintf(fp
->comment
, l
+1, "(%.*s)", strlen(kw
->kw
), kw
->kw
);
1777 fp
->comment
[l
] = '\0';
1780 fp
->help
= h_flag_user_flag
;
1781 fp
->flag
= F_KEYWORD
;
1789 fp
->flag
= F_COMMENT
;
1790 fp
->name
= cpystr("");
1792 fp
->flag
= F_COMMENT
;
1793 len
= strlen(_("Other keywords in the mailbox that are not user-defined"));
1794 fp
->name
= (char *) fs_get((len
+6+6+1) * sizeof(char));
1795 snprintf(fp
->name
, len
+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined"));
1799 /* then the extra mailbox-defined keywords */
1801 for(i
= 0; stream_to_user_flag_name(state
->mail_stream
, i
); i
++){
1804 q
= stream_to_user_flag_name(state
->mail_stream
, i
);
1806 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1807 if((kw
->nick
&& !strucmp(kw
->nick
, q
)) || (kw
->kw
&& !strucmp(kw
->kw
, q
)))
1812 if(!kw
&& !(q
&& !strucmp(FORWARDED_FLAG
, q
))){
1813 fp
->name
= cpystr(q
);
1814 fp
->keyword
= cpystr(q
);
1815 fp
->help
= h_flag_user_flag
;
1816 fp
->flag
= F_KEYWORD
;
1823 flag_screen
.flag_table
= &ftbl
;
1824 flag_screen
.explanation
= screen_text
;
1826 if(MCMD_ISAGG(aopt
)){
1827 if(!pseudo_selected(ps_global
->mail_stream
, msgmap
)){
1828 free_flag_table(&ftbl
);
1832 exp
= flag_screen_text2
;
1833 for(fp
= ftbl
; fp
->name
; fp
++){
1834 fp
->set
= CMD_FLAG_UNKN
; /* set to unknown */
1838 else if(state
->mail_stream
1839 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
1840 && rawno
<= state
->mail_stream
->nmsgs
1841 && (mc
= mail_elt(state
->mail_stream
, rawno
))){
1842 exp
= flag_screen_text1
;
1843 for(fp
= &ftbl
[0]; fp
->name
; fp
++){
1845 if(fp
->flag
== F_KEYWORD
){
1846 /* see if this keyword is defined for this message */
1847 fp
->set
= CMD_FLAG_CLEAR
;
1848 if(user_flag_is_set(state
->mail_stream
,
1849 rawno
, fp
->keyword
))
1850 fp
->set
= CMD_FLAG_SET
;
1852 else if(fp
->flag
== F_FWD
){
1853 /* see if forwarded keyword is defined for this message */
1854 fp
->set
= CMD_FLAG_CLEAR
;
1855 if(user_flag_is_set(state
->mail_stream
,
1856 rawno
, FORWARDED_FLAG
))
1857 fp
->set
= CMD_FLAG_SET
;
1859 else if(fp
->flag
!= F_COMMENT
)
1860 fp
->set
= ((fp
->flag
== F_SEEN
&& !mc
->seen
)
1861 || (fp
->flag
== F_DEL
&& mc
->deleted
)
1862 || (fp
->flag
== F_FLAG
&& mc
->flagged
)
1863 || (fp
->flag
== F_ANS
&& mc
->answered
))
1864 ? CMD_FLAG_SET
: CMD_FLAG_CLEAR
;
1868 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
1869 _("Error accessing message data"));
1870 free_flag_table(&ftbl
);
1874 if(directly_to_maint_screen
)
1875 goto the_maint_screen
;
1878 if (mswin_usedialog ()) {
1879 if (!os_flagmsgdialog (&ftbl
[0])){
1880 free_flag_table(&ftbl
);
1887 int use_maint_screen
;
1888 int keyword_shortcut
= 0;
1890 use_maint_screen
= F_ON(F_FLAG_SCREEN_DFLT
, ps_global
);
1892 if(!use_maint_screen
){
1894 * We're going to call cmd_flag_prompt(). We need
1895 * to decide whether or not to offer the keyword setting
1896 * shortcut. We'll offer it if the user has the feature
1897 * enabled AND there are some possible keywords that could
1900 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
)){
1901 for(fp
= &ftbl
[0]; fp
->name
&& !keyword_shortcut
; fp
++){
1902 if(fp
->flag
== F_KEYWORD
){
1906 first_char
= (fp
->name
&& fp
->name
[0])
1908 if(isascii(first_char
) && isupper(first_char
))
1909 first_char
= tolower((unsigned char) first_char
);
1911 for(tp
=flag_text_opt
; tp
->ch
!= -1; tp
++)
1912 if(tp
->ch
== first_char
)
1921 use_maint_screen
= !cmd_flag_prompt(state
, &flag_screen
,
1926 if(use_maint_screen
){
1927 for(p
= &screen_text
[0]; *exp
; p
++, exp
++)
1932 directly_to_maint_screen
= flag_maintenance_screen(state
, &flag_screen
);
1936 /* reaquire the elt pointer */
1937 mc
= (state
->mail_stream
1938 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
1939 && rawno
<= state
->mail_stream
->nmsgs
)
1940 ? mail_elt(state
->mail_stream
, rawno
) : NULL
;
1942 for(fp
= ftbl
; mc
&& fp
->name
; fp
++){
1946 if((!MCMD_ISAGG(aopt
) && fp
->set
!= !mc
->seen
)
1947 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
1955 unflagged
= F_UNSEEN
;
1962 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->answered
)
1963 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
1964 flagit
= "\\ANSWERED";
1967 unflagged
= F_UNANS
;
1978 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->deleted
)
1979 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
1980 flagit
= "\\DELETED";
1983 unflagged
= F_UNDEL
;
1994 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->flagged
)
1995 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
1996 flagit
= "\\FLAGGED";
1999 unflagged
= F_UNFLAG
;
2010 if(!MCMD_ISAGG(aopt
)){
2011 /* see if forwarded is defined for this message */
2012 is_set
= CMD_FLAG_CLEAR
;
2013 if(user_flag_is_set(state
->mail_stream
,
2014 mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
2016 is_set
= CMD_FLAG_SET
;
2019 if((!MCMD_ISAGG(aopt
) && fp
->set
!= is_set
)
2020 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2021 flagit
= FORWARDED_FLAG
;
2024 unflagged
= F_UNFWD
;
2035 if(!MCMD_ISAGG(aopt
)){
2036 /* see if this keyword is defined for this message */
2037 is_set
= CMD_FLAG_CLEAR
;
2038 if(user_flag_is_set(state
->mail_stream
,
2039 mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
2041 is_set
= CMD_FLAG_SET
;
2044 if((!MCMD_ISAGG(aopt
) && fp
->set
!= is_set
)
2045 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2046 flagit
= fp
->keyword
;
2047 keyword_array
[0] = fp
->keyword
;
2048 keyword_array
[1] = NULL
;
2051 unflagged
= F_UNKEYWORD
;
2055 unflagged
= F_KEYWORD
;
2067 && (seq
= currentf_sequence(state
->mail_stream
, msgmap
,
2068 unflagged
, &flagged
, unflagged
& F_DEL
,
2069 (fp
->flag
== F_KEYWORD
2070 && unflagged
== F_KEYWORD
)
2071 ? keyword_array
: NULL
,
2072 (fp
->flag
== F_KEYWORD
2073 && unflagged
== F_UNKEYWORD
)
2074 ? keyword_array
: NULL
))){
2076 * For user keywords, we may have to create the flag in
2077 * the folder if it doesn't already exist and we are setting
2078 * it (as opposed to clearing it). Mail_flag will
2079 * do that for us, but it's failure isn't very friendly
2080 * error-wise. So we try to make it a little smoother.
2082 if(!(fp
->flag
== F_KEYWORD
|| fp
->flag
== F_FWD
) || !fp
->set
2083 || ((i
=user_flag_index(state
->mail_stream
, flagit
)) >= 0
2085 mail_flag(state
->mail_stream
, seq
, flagit
, flags
);
2087 /* trouble, see if we can add the user flag */
2088 if(state
->mail_stream
->kwd_create
)
2089 mail_flag(state
->mail_stream
, seq
, flagit
, flags
);
2093 if(some_user_flags_defined(state
->mail_stream
))
2094 q_status_message(SM_ORDER
, 3, 4,
2095 _("No more keywords allowed in this folder!"));
2096 else if(fp
->flag
== F_FWD
)
2097 q_status_message(SM_ORDER
, 3, 4,
2098 _("Cannot add keywords for this folder so cannot set Forwarded flag"));
2100 q_status_message(SM_ORDER
, 3, 4,
2101 _("Cannot add keywords for this folder"));
2105 fs_give((void **) &seq
);
2106 if(flagged
&& !trouble
){
2107 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%slagged%s%s%s%s%s message%s%s \"%s\"",
2108 (fp
->set
) ? "F" : "Unf",
2109 MCMD_ISAGG(aopt
) ? " " : "",
2110 MCMD_ISAGG(aopt
) ? long2string(flagged
) : "",
2111 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2113 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2114 ? comatose(mn_total_cur(msgmap
)) : "",
2115 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2117 MCMD_ISAGG(aopt
) ? plural(flagged
) : " ",
2118 MCMD_ISAGG(aopt
) ? "" : long2string(mn_get_cur(msgmap
)),
2120 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2121 q_status_message(SM_ORDER
, 0, 2, answer
= tmp_20k_buf
);
2127 free_flag_table(&ftbl
);
2129 if(directly_to_maint_screen
)
2132 if(MCMD_ISAGG(aopt
))
2133 restore_selected(msgmap
);
2136 q_status_message(SM_ORDER
, 0, 2, _("No flags changed."));
2142 /*----------------------------------------------------------------------
2143 Offer concise status line flag prompt
2145 Args: state -- Various satate info
2146 flags -- flags to offer setting
2148 Result: TRUE if flag to set specified in flags struct or FALSE otw
2152 cmd_flag_prompt(struct pine
*state
, struct flag_screen
*flags
, int allow_keyword_shortcuts
)
2154 int r
, setflag
= 1, first_char
;
2155 struct flag_table
*fp
;
2157 char *ftext
, *ftext_not
;
2158 static char *flag_text
=
2159 N_("Flag New, Deleted, Answered, Forwarded or Important ? ");
2160 static char *flag_text_ak
=
2161 N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? ");
2162 static char *flag_text_not
=
2163 N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? ");
2164 static char *flag_text_ak_not
=
2165 N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? ");
2167 if(allow_keyword_shortcuts
){
2169 ESCKEY_S
*dp
, *sp
, *tp
;
2171 for(sp
=flag_text_opt
; sp
->ch
!= -1; sp
++)
2174 for(fp
=(flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++)
2175 if(fp
->flag
== F_KEYWORD
)
2178 /* set up an ESCKEY_S list which includes invisible keys for keywords */
2179 ek
= (ESCKEY_S
*) fs_get((cnt
+ 1) * sizeof(*ek
));
2180 memset(ek
, 0, (cnt
+1) * sizeof(*ek
));
2181 for(dp
=ek
, sp
=flag_text_opt
; sp
->ch
!= -1; sp
++, dp
++)
2184 for(fp
=(flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++){
2185 if(fp
->flag
== F_KEYWORD
){
2186 first_char
= (fp
->name
&& fp
->name
[0]) ? fp
->name
[0] : -2;
2187 if(isascii(first_char
) && isupper(first_char
))
2188 first_char
= tolower((unsigned char) first_char
);
2191 * Check to see if an earlier keyword in the list, or one of
2192 * the builtin system letters already uses this character.
2193 * If so, the first one wins.
2195 for(tp
=ek
; tp
->ch
!= 0; tp
++)
2196 if(tp
->ch
== first_char
)
2200 continue; /* skip it, already used that char */
2202 dp
->ch
= first_char
;
2203 dp
->rval
= first_char
;
2211 ftext
= _(flag_text_ak
);
2212 ftext_not
= _(flag_text_ak_not
);
2216 ftext
= _(flag_text
);
2217 ftext_not
= _(flag_text_not
);
2221 r
= radio_buttons(setflag
? ftext
: ftext_not
,
2222 -FOOTER_ROWS(state
), ek
, '*', SEQ_EXCEPTION
-1,
2223 NO_HELP
, RB_NORM
| RB_SEQ_SENSITIVE
);
2225 * It is SEQ_EXCEPTION-1 just so that it is some value that isn't
2226 * being used otherwise. The keywords use up all the possible
2227 * letters, so a negative number is good, but it has to be different
2228 * from other negative return values.
2230 if(r
== SEQ_EXCEPTION
-1) /* ol'cancelrooney */
2232 else if(r
== 10) /* return and goto flag screen */
2234 else if(r
== '!') /* flip intention */
2240 for(fp
= (flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++){
2241 if(r
== 'n' || r
== '*' || r
== 'd' || r
== 'a' || r
== 'f'){
2242 if((r
== 'n' && fp
->flag
== F_SEEN
)
2243 || (r
== '*' && fp
->flag
== F_FLAG
)
2244 || (r
== 'd' && fp
->flag
== F_DEL
)
2245 || (r
== 'f' && fp
->flag
== F_FWD
)
2246 || (r
== 'a' && fp
->flag
== F_ANS
)){
2247 fp
->set
= setflag
? CMD_FLAG_SET
: CMD_FLAG_CLEAR
;
2251 else if(allow_keyword_shortcuts
&& fp
->flag
== F_KEYWORD
){
2252 first_char
= (fp
->name
&& fp
->name
[0]) ? fp
->name
[0] : -2;
2253 if(isascii(first_char
) && isupper(first_char
))
2254 first_char
= tolower((unsigned char) first_char
);
2256 if(r
== first_char
){
2257 fp
->set
= setflag
? CMD_FLAG_SET
: CMD_FLAG_CLEAR
;
2263 if(ek
!= flag_text_opt
)
2264 fs_give((void **) &ek
);
2271 * (*ft) is an array of flag_table entries.
2274 free_flag_table(struct flag_table
**ft
)
2276 struct flag_table
*fp
;
2279 for(fp
= (*ft
); fp
->name
; fp
++){
2281 fs_give((void **) &fp
->name
);
2284 fs_give((void **) &fp
->keyword
);
2287 fs_give((void **) &fp
->comment
);
2290 fs_give((void **) ft
);
2295 /*----------------------------------------------------------------------
2296 Execute REPLY message command
2298 Args: state -- Various satate info
2299 msgmap -- map of c-client to local message numbers
2301 Result: reply sent or not
2305 cmd_reply(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
2309 if(any_messages(msgmap
, NULL
, "to Reply to")){
2310 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
2313 rv
= reply(state
, NULL
);
2315 if(MCMD_ISAGG(aopt
))
2316 restore_selected(msgmap
);
2318 state
->mangled_screen
= 1;
2325 /*----------------------------------------------------------------------
2326 Execute FORWARD message command
2328 Args: state -- Various satate info
2329 msgmap -- map of c-client to local message numbers
2331 Result: selected message[s] forwarded or not
2335 cmd_forward(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
2339 if(any_messages(msgmap
, NULL
, "to Forward")){
2340 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
2343 rv
= forward(state
, NULL
);
2345 if(MCMD_ISAGG(aopt
))
2346 restore_selected(msgmap
);
2348 state
->mangled_screen
= 1;
2355 /*----------------------------------------------------------------------
2356 Execute BOUNCE message command
2358 Args: state -- Various satate info
2359 msgmap -- map of c-client to local message numbers
2360 aopt -- aggregate options
2362 Result: selected message[s] bounced or not
2366 cmd_bounce(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
2370 if(any_messages(msgmap
, NULL
, "to Bounce")){
2371 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
2374 rv
= bounce(state
, NULL
);
2375 if(MCMD_ISAGG(aopt
))
2376 restore_selected(msgmap
);
2378 state
->mangled_footer
= 1;
2385 /*----------------------------------------------------------------------
2386 Execute save message command: prompt for folder and call function to save
2388 Args: screen_line -- Line on the screen to prompt on
2389 message -- The MESSAGECACHE entry of message to save
2391 Result: The folder lister can be called to make selection; mangled screen set
2393 This does the prompting for the folder name to save to, possibly calling
2394 up the folder display for selection of folder by user.
2397 cmd_save(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int aopt
, CmdWhere in_index
)
2399 char newfolder
[MAILTMPLEN
], nmsgs
[32], *nick
;
2400 int we_cancel
= 0, rv
= 0, save_flags
;
2402 CONTEXT_S
*cntxt
= NULL
;
2404 SaveDel del
= DontAsk
;
2405 SavePreserveOrder pre
= DontAskPreserve
;
2407 dprint((4, "\n - saving message -\n"));
2409 if(MCMD_ISAGG(aopt
) && !pseudo_selected(stream
, msgmap
))
2412 state
->ugly_consider_advancing_bit
= 0;
2413 if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM
, state
)
2414 && msgno_any_deletedparts(stream
, msgmap
)
2415 && want_to(_("Saved copy will NOT include entire message! Continue"),
2416 'y', 'n', NO_HELP
, WT_FLUSH_IN
| WT_SEQ_SENSITIVE
) != 'y'){
2417 restore_selected(msgmap
);
2418 cmd_cancelled("Save message");
2422 raw
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
2424 if(mn_total_cur(msgmap
) <= 1L){
2425 snprintf(nmsgs
, sizeof(nmsgs
), "Msg #%ld ", mn_get_cur(msgmap
));
2426 nmsgs
[sizeof(nmsgs
)-1] = '\0';
2427 e
= pine_mail_fetchstructure(stream
, raw
, NULL
);
2429 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
2430 _("Can't save message. Error accessing folder"));
2431 restore_selected(msgmap
);
2436 snprintf(nmsgs
, sizeof(nmsgs
), "%s msgs ", comatose(mn_total_cur(msgmap
)));
2437 nmsgs
[sizeof(nmsgs
)-1] = '\0';
2439 /* e is just used to get a default save folder from the first msg */
2440 e
= pine_mail_fetchstructure(stream
,
2441 mn_m2raw(msgmap
, mn_first_cur(msgmap
)),
2445 del
= (!READONLY_FOLDER(stream
) && F_OFF(F_SAVE_WONT_DELETE
, ps_global
))
2447 if(mn_total_cur(msgmap
) > 1L)
2448 pre
= F_OFF(F_AGG_SEQ_COPY
, ps_global
) ? Preserve
: NoPreserve
;
2450 pre
= DontAskPreserve
;
2452 if(save_prompt(state
, &cntxt
, newfolder
, sizeof(newfolder
), nmsgs
, e
,
2453 raw
, NULL
, &del
, &pre
)){
2455 if(ps_global
&& ps_global
->ttyo
){
2456 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
2457 ps_global
->mangled_footer
= 1;
2460 save_flags
= SV_FIX_DELS
;
2461 if(pre
== RetPreserve
)
2462 save_flags
|= SV_PRESERVE
;
2464 save_flags
|= SV_DELETE
;
2465 if(ps_global
->context_list
== cntxt
&& !strucmp(newfolder
, ps_global
->inbox_name
))
2466 save_flags
|= SV_INBOXWOCNTXT
;
2468 we_cancel
= busy_cue(_("Saving"), NULL
, 1);
2469 i
= save(state
, stream
, cntxt
, newfolder
, msgmap
, save_flags
);
2473 if(i
== mn_total_cur(msgmap
)){
2475 if(mn_total_cur(msgmap
) <= 1L){
2476 int need
, avail
= ps_global
->ttyo
->screen_cols
- 2;
2477 int lennick
, lenfldr
;
2480 && ps_global
->context_list
->next
2481 && context_isambig(newfolder
)){
2482 lennick
= MIN(strlen(cntxt
->nickname
), 500);
2483 lenfldr
= MIN(strlen(newfolder
), 500);
2484 need
= 27 + strlen(long2string(mn_get_cur(msgmap
))) +
2488 need
-= MIN(lennick
-10, need
-avail
);
2489 lennick
-= MIN(lennick
-10, need
-avail
);
2492 if(need
> avail
&& lenfldr
> 10)
2493 lenfldr
-= MIN(lenfldr
-10, need
-avail
);
2496 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
2497 "Message %s copied to \"%s\" in <%s>",
2498 long2string(mn_get_cur(msgmap
)),
2499 short_str(newfolder
, (char *)(tmp_20k_buf
+1000), 1000,
2501 short_str(cntxt
->nickname
,
2502 (char *)(tmp_20k_buf
+2000), 1000,
2504 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2506 else if((nick
=folder_is_target_of_nick(newfolder
, cntxt
)) != NULL
){
2507 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
2508 "Message %s copied to \"%s\"",
2509 long2string(mn_get_cur(msgmap
)),
2511 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2514 char *f
= " folder";
2516 lenfldr
= MIN(strlen(newfolder
), 500);
2517 need
= 28 + strlen(long2string(mn_get_cur(msgmap
))) +
2522 if(need
> avail
&& lenfldr
> 10)
2523 lenfldr
-= MIN(lenfldr
-10, need
-avail
);
2526 snprintf(tmp_20k_buf
,SIZEOF_20KBUF
,
2527 "Message %s copied to%s \"%s\"",
2528 long2string(mn_get_cur(msgmap
)), f
,
2529 short_str(newfolder
, (char *)(tmp_20k_buf
+1000), 1000,
2531 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2535 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%s messages saved",
2536 comatose(mn_total_cur(msgmap
)));
2537 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2541 strncat(tmp_20k_buf
, " and deleted", SIZEOF_20KBUF
-strlen(tmp_20k_buf
)-1);
2542 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2545 q_status_message(SM_ORDER
, 0, 3, tmp_20k_buf
);
2547 if(!MCMD_ISAGG(aopt
) && F_ON(F_SAVE_ADVANCES
, state
)){
2548 if(sp_new_mail_count(stream
))
2549 process_filter_patterns(stream
, msgmap
,
2550 sp_new_mail_count(stream
));
2552 mn_inc_cur(stream
, msgmap
,
2553 (in_index
== View
&& THREADING()
2554 && sp_viewing_a_thread(stream
))
2556 : (in_index
== View
)
2557 ? MH_ANYTHD
: MH_NONE
);
2560 state
->ugly_consider_advancing_bit
= 1;
2564 if(MCMD_ISAGG(aopt
)) /* straighten out fakes */
2565 restore_selected(msgmap
);
2568 update_titlebar_status(); /* make sure they see change */
2575 role_compose(struct pine
*state
)
2579 if(F_ON(F_ALT_ROLE_MENU
, state
) && mn_get_total(state
->msgmap
) > 0L){
2582 if(nonempty_patterns(ROLE_DO_ROLES
, &pstate
) && first_pattern(&pstate
)){
2583 action
= radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
2584 -FOOTER_ROWS(state
), choose_action
,
2585 'c', 'x', h_role_compose
, RB_NORM
);
2588 q_status_message(SM_ORDER
, 0, 3,
2589 _("No roles available. Use Setup/Rules to add roles."));
2596 if(action
== 'c' || action
== 'r' || action
== 'f' || action
== 'b'){
2597 ACTION_S
*role
= NULL
;
2598 void (*prev_screen
)(struct pine
*) = NULL
, (*redraw
)(void) = NULL
;
2600 redraw
= state
->redrawer
;
2601 state
->redrawer
= NULL
;
2602 prev_screen
= state
->prev_screen
;
2604 state
->next_screen
= SCREEN_FUN_NULL
;
2607 if(role_select_screen(state
, &role
,
2608 action
== 'f' ? MC_FORWARD
:
2609 action
== 'r' ? MC_REPLY
:
2610 action
== 'b' ? MC_BOUNCE
:
2611 action
== 'c' ? MC_COMPOSE
: 0) < 0){
2612 cmd_cancelled(action
== 'f' ? _("Forward") :
2613 action
== 'r' ? _("Reply") :
2614 action
== 'c' ? _("Composition") : _("Bounce"));
2615 state
->next_screen
= prev_screen
;
2616 state
->redrawer
= redraw
;
2617 state
->mangled_screen
= 1;
2621 * If default role was selected (NULL) we need to make
2622 * up a role which won't do anything, but will cause
2623 * compose_mail to think there's already a role so that
2624 * it won't try to confirm the default.
2627 role
= combine_inherited_role(role
);
2629 role
= (ACTION_S
*) fs_get(sizeof(*role
));
2630 memset((void *) role
, 0, sizeof(*role
));
2631 role
->nick
= cpystr("Default Role");
2634 state
->redrawer
= NULL
;
2637 compose_mail(NULL
, NULL
, role
, NULL
, NULL
);
2641 (void) reply(state
, role
);
2645 (void) forward(state
, role
);
2649 (void) bounce(state
, role
);
2656 state
->next_screen
= prev_screen
;
2657 state
->redrawer
= redraw
;
2658 state
->mangled_screen
= 1;
2664 /*----------------------------------------------------------------------
2665 Do the dirty work of prompting the user for a folder name
2668 nfldr should be a buffer at least MAILTMPLEN long
2669 dela -- a pointer to a SaveDel. If it is
2670 DontAsk on input, don't offer Delete prompt
2671 Del on input, offer Delete command with default of Delete
2673 RetDel and RetNoDel are return values
2680 save_prompt(struct pine
*state
, CONTEXT_S
**cntxt
, char *nfldr
, size_t len_nfldr
,
2681 char *nmsgs
, ENVELOPE
*env
, long int rawmsgno
, char *section
,
2682 SaveDel
*dela
, SavePreserveOrder
*prea
)
2684 int rc
, ku
= -1, n
, flags
, last_rc
= 0, saveable_count
= 0, done
= 0;
2685 int delindex
, preindex
, r
;
2686 char prompt
[6*MAX_SCREEN_COLS
+1], *p
, expanded
[MAILTMPLEN
];
2687 char *buf
= tmp_20k_buf
;
2691 SaveDel del
= DontAsk
;
2692 SavePreserveOrder pre
= DontAskPreserve
;
2693 char *deltext
= NULL
;
2694 static HISTORY_S
*history
= NULL
;
2699 panic("no context ptr in save_prompt");
2701 init_hist(&history
, HISTSIZE
);
2703 if(!(folder
= save_get_default(state
, env
, rawmsgno
, section
, cntxt
)))
2704 return(0); /* message expunged! */
2706 /* how many context's can be saved to... */
2707 for(tc
= state
->context_list
; tc
; tc
= tc
->next
)
2711 /* set up extra command option keys */
2713 ekey
[rc
].ch
= ctrl('T');
2715 ekey
[rc
].name
= "^T";
2716 /* TRANSLATORS: command means go to Folders list */
2717 ekey
[rc
++].label
= N_("To Fldrs");
2719 if(saveable_count
> 1){
2720 ekey
[rc
].ch
= ctrl('P');
2722 ekey
[rc
].name
= "^P";
2723 ekey
[rc
++].label
= N_("Prev Collection");
2725 ekey
[rc
].ch
= ctrl('N');
2727 ekey
[rc
].name
= "^N";
2728 ekey
[rc
++].label
= N_("Next Collection");
2731 if(F_ON(F_ENABLE_TAB_COMPLETE
, ps_global
)){
2734 ekey
[rc
].name
= "TAB";
2735 /* TRANSLATORS: command asks alpine to complete the name when tab is typed */
2736 ekey
[rc
++].label
= N_("Complete");
2739 if(F_ON(F_ENABLE_SUB_LISTS
, ps_global
)){
2740 ekey
[rc
].ch
= ctrl('X');
2742 ekey
[rc
].name
= "^X";
2743 /* TRANSLATORS: list all the matches */
2744 ekey
[rc
++].label
= N_("ListMatches");
2747 if(dela
&& (*dela
== NoDel
|| *dela
== Del
)){
2748 ekey
[rc
].ch
= ctrl('R');
2750 ekey
[rc
].name
= "^R";
2755 if(prea
&& (*prea
== NoPreserve
|| *prea
== Preserve
)){
2756 ekey
[rc
].ch
= ctrl('W');
2758 ekey
[rc
].name
= "^W";
2763 if(saveable_count
> 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
2764 ekey
[rc
].ch
= KEY_UP
;
2767 ekey
[rc
++].label
= "";
2769 ekey
[rc
].ch
= KEY_DOWN
;
2772 ekey
[rc
++].label
= "";
2774 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
2775 ekey
[rc
].ch
= KEY_UP
;
2779 ekey
[rc
++].label
= "";
2781 ekey
[rc
].ch
= KEY_DOWN
;
2784 ekey
[rc
++].label
= "";
2792 /* only show collection number if more than one available */
2793 if(ps_global
->context_list
->next
)
2794 snprintf(prompt
, sizeof(prompt
), "SAVE%s %sto folder in <%s> [%s] : ",
2795 deltext
? deltext
: "",
2797 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2798 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2800 snprintf(prompt
, sizeof(prompt
), "SAVE%s %sto folder [%s] : ",
2801 deltext
? deltext
: "",
2802 nmsgs
, strsquish(buf
, SIZEOF_20KBUF
, folder
, 40));
2804 prompt
[sizeof(prompt
)-1] = '\0';
2807 * If the prompt won't fit, try removing deltext.
2809 if(state
->ttyo
->screen_cols
< strlen(prompt
) + MIN_OPT_ENT_WIDTH
&& deltext
){
2810 if(ps_global
->context_list
->next
)
2811 snprintf(prompt
, sizeof(prompt
), "SAVE %sto folder in <%s> [%s] : ",
2813 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2814 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2816 snprintf(prompt
, sizeof(prompt
), "SAVE %sto folder [%s] : ",
2817 nmsgs
, strsquish(buf
, SIZEOF_20KBUF
, folder
, 40));
2819 prompt
[sizeof(prompt
)-1] = '\0';
2823 * If the prompt still won't fit, remove the extra info contained
2826 if(state
->ttyo
->screen_cols
< strlen(prompt
) + MIN_OPT_ENT_WIDTH
&& *nmsgs
){
2827 if(ps_global
->context_list
->next
)
2828 snprintf(prompt
, sizeof(prompt
), "SAVE to folder in <%s> [%s] : ",
2829 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2830 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2832 snprintf(prompt
, sizeof(prompt
), "SAVE to folder [%s] : ",
2833 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2835 prompt
[sizeof(prompt
)-1] = '\0';
2839 ekey
[delindex
].label
= (del
== NoDel
) ? "Delete" : "No Delete";
2841 if(pre
!= DontAskPreserve
)
2842 ekey
[preindex
].label
= (pre
== NoPreserve
) ? "Preserve Order" : "Any Order";
2845 if(items_in_hist(history
) > 1){
2846 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
2847 ekey
[ku
].label
= HISTORY_KEYLABEL
;
2848 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
2849 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
2853 ekey
[ku
].label
= "";
2854 ekey
[ku
+1].name
= "";
2855 ekey
[ku
+1].label
= "";
2859 flags
= OE_APPEND_CURRENT
| OE_SEQ_SENSITIVE
;
2860 rc
= optionally_enter(nfldr
, -FOOTER_ROWS(state
), 0, len_nfldr
,
2861 prompt
, ekey
, help
, &flags
);
2865 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
2866 _("Error reading folder name"));
2871 removing_trailing_white_space(nfldr
);
2872 removing_leading_white_space(nfldr
);
2874 if(*nfldr
|| *folder
){
2875 char *p
, *name
, *fullname
= NULL
;
2876 int exists
, breakout
= FALSE
;
2879 strncpy(nfldr
, folder
, len_nfldr
-1);
2880 nfldr
[len_nfldr
-1] = '\0';
2883 save_hist(history
, nfldr
, 0, (void *) *cntxt
);
2885 if(!(name
= folder_is_nick(nfldr
, FOLDERS(*cntxt
), 0)))
2888 if(update_folder_spec(expanded
, sizeof(expanded
), name
)){
2889 strncpy(name
= nfldr
, expanded
, len_nfldr
-1);
2890 nfldr
[len_nfldr
-1] = '\0';
2893 exists
= folder_name_exists(*cntxt
, name
, &fullname
);
2895 if(exists
== FEX_ERROR
){
2896 q_status_message1(SM_ORDER
, 0, 3,
2897 _("Problem accessing folder \"%s\""),
2903 strncpy(name
= nfldr
, fullname
, len_nfldr
-1);
2904 nfldr
[len_nfldr
-1] = '\0';
2905 fs_give((void **) &fullname
);
2909 if(exists
& FEX_ISFILE
){
2912 else if((exists
& FEX_ISDIR
)){
2913 char tmp
[MAILTMPLEN
];
2917 CONTEXT_S
*fake_context
;
2920 strncpy(tmp
, name
, sizeof(tmp
));
2921 tmp
[sizeof(tmp
)-2-1] = '\0';
2922 if(tmp
[(l
= strlen(tmp
)) - 1] != tc
->dir
->delim
){
2923 if(l
< sizeof(tmp
)){
2924 tmp
[l
] = tc
->dir
->delim
;
2925 strncpy(&tmp
[l
+1], "[]", sizeof(tmp
)-(l
+1));
2929 strncat(tmp
, "[]", sizeof(tmp
)-strlen(tmp
)-1);
2931 tmp
[sizeof(tmp
)-1] = '\0';
2933 fake_context
= new_context(tmp
, 0);
2935 done
= display_folder_list(&fake_context
, nfldr
,
2936 1, folders_for_save
);
2937 free_context(&fake_context
);
2939 else if(tc
->dir
->delim
2940 && (p
= strrindex(name
, tc
->dir
->delim
))
2942 done
= display_folder_list(cntxt
, nfldr
,
2943 1, folders_for_save
);
2945 q_status_message1(SM_ORDER
, 3, 3,
2946 _("\"%s\" is a directory"), name
);
2948 && !((p
=strrindex(name
, tc
->dir
->delim
)) && *(p
+1) == '\0')){
2949 strncpy(tmp
, name
, sizeof(tmp
));
2950 tmp
[sizeof(tmp
)-1] = '\0';
2951 snprintf(nfldr
, len_nfldr
, "%s%c", tmp
, tc
->dir
->delim
);
2955 else{ /* Doesn't exist, create! */
2956 if((fullname
= folder_as_breakout(*cntxt
, name
)) != NULL
){
2957 strncpy(name
= nfldr
, fullname
, len_nfldr
-1);
2958 nfldr
[len_nfldr
-1] = '\0';
2959 fs_give((void **) &fullname
);
2962 switch(create_for_save(*cntxt
, name
)){
2963 case 1 : /* success */
2966 case 0 : /* error */
2967 case -1 : /* declined */
2976 /* else fall thru like they cancelled */
2979 cmd_cancelled("Save message");
2984 r
= display_folder_list(cntxt
, nfldr
, 0, folders_for_save
);
2992 helper(h_save
, _("HELP FOR SAVE"), HLPD_SIMPLE
);
2993 ps_global
->mangled_screen
= 1;
2996 case 4 : /* redraw */
2999 case 10 : /* previous collection */
3000 for(tc
= (*cntxt
)->prev
; tc
; tc
= tc
->prev
)
3007 for(tc2
= (tc
= (*cntxt
))->next
; tc2
; tc2
= tc2
->next
)
3015 case 11 : /* next collection */
3019 if(((*cntxt
) = (*cntxt
)->next
) == NULL
)
3020 (*cntxt
) = ps_global
->context_list
;
3021 while(NEWS_TEST(*cntxt
) && (*cntxt
) != tc
);
3024 case 12 : /* file name completion */
3025 if(!folder_complete(*cntxt
, nfldr
, len_nfldr
, &n
)){
3026 if(n
&& last_rc
== 12 && !(flags
& OE_USER_MODIFIED
)){
3027 r
= display_folder_list(cntxt
, nfldr
, 1, folders_for_save
);
3029 done
++; /* bingo! */
3031 rc
= 0; /* burn last_rc */
3039 case 14 : /* file name completion */
3040 r
= display_folder_list(cntxt
, nfldr
, 2, folders_for_save
);
3042 done
++; /* bingo! */
3044 rc
= 0; /* burn last_rc */
3048 case 15 : /* Delete / No Delete */
3049 del
= (del
== NoDel
) ? Del
: NoDel
;
3050 deltext
= (del
== NoDel
) ? " (no delete)" : " (and delete)";
3053 case 16 : /* Preserve Order or not */
3054 pre
= (pre
== NoPreserve
) ? Preserve
: NoPreserve
;
3058 if((p
= get_prev_hist(history
, nfldr
, 0, (void *) *cntxt
)) != NULL
){
3059 strncpy(nfldr
, p
, len_nfldr
);
3060 nfldr
[len_nfldr
-1] = '\0';
3061 if(history
->hist
[history
->curindex
])
3062 *cntxt
= (CONTEXT_S
*) history
->hist
[history
->curindex
]->cntxt
;
3070 if((p
= get_next_hist(history
, nfldr
, 0, (void *) *cntxt
)) != NULL
){
3071 strncpy(nfldr
, p
, len_nfldr
);
3072 nfldr
[len_nfldr
-1] = '\0';
3073 if(history
->hist
[history
->curindex
])
3074 *cntxt
= (CONTEXT_S
*) history
->hist
[history
->curindex
]->cntxt
;
3082 panic("Unhandled case");
3089 ps_global
->mangled_footer
= 1;
3095 strncpy(ps_global
->last_save_folder
, nfldr
, sizeof(ps_global
->last_save_folder
)-1);
3096 ps_global
->last_save_folder
[sizeof(ps_global
->last_save_folder
)-1] = '\0';
3098 ps_global
->last_save_context
= *cntxt
;
3101 strncpy(nfldr
, folder
, len_nfldr
-1);
3102 nfldr
[len_nfldr
-1] = '\0';
3105 /* nickname? Copy real name to nfldr */
3107 && context_isambig(nfldr
)
3108 && (p
= folder_is_nick(nfldr
, FOLDERS(*cntxt
), 0))){
3109 strncpy(nfldr
, p
, len_nfldr
-1);
3110 nfldr
[len_nfldr
-1] = '\0';
3113 if(dela
&& (*dela
== NoDel
|| *dela
== Del
))
3114 *dela
= (del
== NoDel
) ? RetNoDel
: RetDel
;
3116 if(prea
&& (*prea
== NoPreserve
|| *prea
== Preserve
))
3117 *prea
= (pre
== NoPreserve
) ? RetNoPreserve
: RetPreserve
;
3123 /*----------------------------------------------------------------------
3124 Prompt user before implicitly creating a folder for saving
3126 Args: context - context to create folder in
3127 folder - folder name to create
3129 Result: 1 on proceed, -1 on decline, 0 on error
3133 create_for_save_prompt(CONTEXT_S
*context
, char *folder
, int sequence_sensitive
)
3135 if(context
&& ps_global
->context_list
->next
&& context_isambig(folder
)){
3136 if(context
->use
& CNTXT_INCMNG
){
3137 snprintf(tmp_20k_buf
,SIZEOF_20KBUF
,
3138 _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
3139 folder
, (strlen(folder
) > 15) ? "..." : "");
3140 q_status_message(SM_ORDER
, 3, 3, tmp_20k_buf
);
3141 return(0); /* error */
3144 snprintf(tmp_20k_buf
,SIZEOF_20KBUF
,
3145 _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
3146 folder
, (strlen(folder
) > 15) ? "..." : "",
3148 (strlen(context
->nickname
) > 15) ? "..." : "");
3151 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
3152 _("Folder \"%.40s%s\" doesn't exist. Create"),
3153 folder
, strlen(folder
) > 40 ? "..." : "");
3155 if(want_to(tmp_20k_buf
, 'y', 'n',
3156 NO_HELP
, (sequence_sensitive
) ? WT_SEQ_SENSITIVE
: WT_NORM
) != 'y'){
3157 cmd_cancelled("Save message");
3166 /*----------------------------------------------------------------------
3167 Expunge messages from current folder
3169 Args: state -- pointer to struct holding a bunch of pine state
3170 msgmap -- table mapping msg nums to c-client sequence nums
3171 qline -- screen line to ask questions on
3172 agg -- boolean indicating we're to operate on aggregate set
3177 cmd_expunge(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int agg
)
3179 long del_count
, prefilter_del_count
;
3180 int we_cancel
= 0, rv
= 0;
3181 char prompt
[MAX_SCREEN_COLS
+1];
3183 COLOR_PAIR
*lastc
= NULL
;
3185 dprint((2, "\n - expunge -\n"));
3189 sequence
= MCMD_ISAGG(agg
) ? selected_sequence(stream
, msgmap
, NULL
, 0) : NULL
;
3191 if(MCMD_ISAGG(agg
)){
3194 for(i
= 1L; i
<= stream
->nmsgs
; i
++){
3195 if((mc
= mail_elt(stream
, i
)) != NULL
3196 && mc
->sequence
&& mc
->deleted
)
3200 q_status_message(SM_ORDER
, 0, 4,
3201 _("No selected messages are deleted"));
3205 if(!any_messages(msgmap
, NULL
, "to Expunge"))
3209 if(IS_NEWS(stream
) && stream
->rdonly
){
3210 if(!MCMD_ISAGG(agg
))
3211 del_count
= count_flagged(stream
, F_DEL
);
3213 state
->mangled_footer
= 1;
3214 snprintf(prompt
, sizeof(prompt
), "Exclude %ld message%s from %.*s", del_count
,
3215 plural(del_count
), sizeof(prompt
)-40,
3216 pretty_fn(state
->cur_folder
));
3217 prompt
[sizeof(prompt
)-1] = '\0';
3218 if(F_ON(F_FULL_AUTO_EXPUNGE
, state
)
3219 || (F_ON(F_AUTO_EXPUNGE
, state
)
3220 && (state
->context_current
3221 && (state
->context_current
->use
& CNTXT_INCMNG
))
3222 && context_isambig(state
->cur_folder
))
3223 || want_to(prompt
, 'y', 0, NO_HELP
, WT_NORM
) == 'y'){
3225 if(F_ON(F_NEWS_CROSS_DELETE
, state
))
3226 cross_delete_crossposts(stream
);
3228 msgno_exclude_deleted(stream
, msgmap
, sequence
);
3229 clear_index_cache(stream
, 0);
3232 * This is kind of surprising at first. For most sort
3233 * orders, if the whole set is sorted, then any subset
3234 * is also sorted. Not so for threaded sorts.
3236 if(SORT_IS_THREADED(msgmap
))
3237 refresh_sort(stream
, msgmap
, SRT_NON
);
3239 state
->mangled_body
= 1;
3240 state
->mangled_header
= 1;
3241 q_status_message2(SM_ORDER
, 0, 4,
3242 "%s message%s excluded",
3243 long2string(del_count
),
3247 any_messages(NULL
, NULL
, "Excluded");
3250 any_messages(NULL
, "deleted", "to Exclude");
3254 else if(READONLY_FOLDER(stream
)){
3255 q_status_message(SM_ORDER
, 0, 4,
3256 _("Can't expunge. Folder is read-only"));
3260 if(!MCMD_ISAGG(agg
)){
3261 prefilter_del_count
= count_flagged(stream
, F_DEL
|F_NOFILT
);
3262 mail_expunge_prefilter(stream
, MI_NONE
);
3263 del_count
= count_flagged(stream
, F_DEL
|F_NOFILT
);
3268 unsigned char *fname
= folder_name_decoded((unsigned char *)state
->cur_folder
);
3270 snprintf(prompt
, sizeof(prompt
), "Expunge %ld message%s from %.*s", del_count
,
3271 plural(del_count
), sizeof(prompt
)-40,
3272 pretty_fn((char *) fname
));
3273 if(fname
) fs_give((void **)&fname
);
3274 prompt
[sizeof(prompt
)-1] = '\0';
3275 state
->mangled_footer
= 1;
3277 if(F_ON(F_FULL_AUTO_EXPUNGE
, state
)
3278 || (F_ON(F_AUTO_EXPUNGE
, state
)
3279 && ((!strucmp(state
->cur_folder
,state
->inbox_name
))
3280 || (state
->context_current
->use
& CNTXT_INCMNG
))
3281 && context_isambig(state
->cur_folder
))
3282 || (ret
=want_to(prompt
, 'y', 0, NO_HELP
, WT_NORM
)) == 'y')
3286 cmd_cancelled("Expunge");
3292 dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
3293 mn_get_total(msgmap
), mn_get_cur(msgmap
), del_count
));
3295 lastc
= pico_set_colors(state
->VAR_TITLE_FORE_COLOR
,
3296 state
->VAR_TITLE_BACK_COLOR
,
3299 PutLine0(0, 0, "**"); /* indicate delay */
3302 (void)pico_set_colorp(lastc
, PSC_NONE
);
3303 free_color_pair(&lastc
);
3306 MoveCursor(state
->ttyo
->screen_rows
-FOOTER_ROWS(state
), 0);
3309 we_cancel
= busy_cue(_("Expunging"), NULL
, 1);
3311 if(cmd_expunge_work(stream
, msgmap
, sequence
))
3312 state
->mangled_body
= 1;
3315 fs_give((void **)&sequence
);
3318 cancel_busy_cue((sp_expunge_count(stream
) > 0) ? 0 : -1);
3320 lastc
= pico_set_colors(state
->VAR_TITLE_FORE_COLOR
,
3321 state
->VAR_TITLE_BACK_COLOR
,
3323 PutLine0(0, 0, " "); /* indicate delay's over */
3326 (void)pico_set_colorp(lastc
, PSC_NONE
);
3327 free_color_pair(&lastc
);
3332 if(sp_expunge_count(stream
) > 0){
3334 * This is kind of surprising at first. For most sort
3335 * orders, if the whole set is sorted, then any subset
3336 * is also sorted. Not so for threaded sorts.
3338 if(SORT_IS_THREADED(msgmap
))
3339 refresh_sort(stream
, msgmap
, SRT_NON
);
3343 unsigned char *fname
= folder_name_decoded((unsigned char *)state
->cur_folder
);
3344 q_status_message1(SM_ORDER
, 0, 3,
3345 _("No messages expunged from folder \"%s\""),
3346 pretty_fn((char *) fname
));
3347 if(fname
) fs_give((void **)&fname
);
3349 else if(!prefilter_del_count
)
3350 q_status_message(SM_ORDER
, 0, 3,
3351 _("No messages marked deleted. No messages expunged."));
3357 /*----------------------------------------------------------------------
3358 Expunge_and_close callback to prompt user for confirmation
3360 Args: stream -- folder's stream
3361 folder -- name of folder containing folders
3362 deleted -- number of del'd msgs
3364 Result: 'y' to continue with expunge
3367 expunge_prompt(MAILSTREAM
*stream
, char *folder
, long int deleted
)
3371 char prompt_b
[MAX_SCREEN_COLS
+1], temp
[MAILTMPLEN
+1], buff
[MAX_SCREEN_COLS
+1];
3372 char *short_folder_name
;
3377 snprintf(temp
, sizeof(temp
), "%ld", deleted
);
3378 charcnt
= strlen(temp
)+1;
3381 max_folder
= MAX(1,MAXPROMPT
- (36+charcnt
));
3382 strncpy(temp
, folder
, sizeof(temp
));
3383 temp
[sizeof(temp
)-1] = '\0';
3384 short_folder_name
= short_str(temp
,buff
,sizeof(buff
),max_folder
,FrontDots
);
3387 snprintf(prompt_b
, sizeof(prompt_b
),
3388 "Delete %s%ld message%s from \"%s\"",
3389 (deleted
> 1L) ? "all " : "", deleted
,
3390 plural(deleted
), short_folder_name
);
3392 snprintf(prompt_b
, sizeof(prompt_b
),
3393 "Expunge the %ld deleted message%s from \"%s\"",
3394 deleted
, deleted
== 1 ? "" : "s",
3397 return(want_to(prompt_b
, 'y', 0, NO_HELP
, WT_NORM
));
3402 * This is used with multiple append saves. Call it once before
3403 * the series of appends with SSCP_INIT and once after all are
3404 * done with SSCP_END. In between, it is called automatically
3405 * from save_fetch_append or save_fetch_append_cb when we need
3406 * to ask the user if he or she wants to continue even though
3407 * announced message size doesn't match the actual message size.
3408 * As of 2008-02-29 the gmail IMAP server has these size mismatches
3409 * on a regular basis even though the data is ok.
3412 save_size_changed_prompt(long msgno
, int flags
)
3416 static int remember_the_yes
= 0;
3417 static int possible_corruption
= 0;
3418 static ESCKEY_S save_size_opts
[] = {
3419 {'y', 'y', "Y", "Yes"},
3420 {'n', 'n', "N", "No"},
3421 {'a', 'a', "A", "yes to All"},
3425 if(flags
& SSCP_INIT
|| flags
& SSCP_END
){
3426 if(flags
& SSCP_END
&& possible_corruption
)
3427 q_status_message(SM_ORDER
, 3, 3, "There is possible data corruption, check the results");
3429 remember_the_yes
= 0;
3430 possible_corruption
= 0;
3431 ps_global
->noshow_error
= 0;
3432 ps_global
->noshow_warn
= 0;
3436 if(remember_the_yes
){
3437 snprintf(prompt
, sizeof(prompt
),
3438 "Message to save shrank! (msg # %ld): Continuing", msgno
);
3439 q_status_message(SM_ORDER
, 0, 3, prompt
);
3440 display_message('x');
3441 return(remember_the_yes
);
3444 snprintf(prompt
, sizeof(prompt
),
3445 "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno
);
3446 ret
= radio_buttons(prompt
, -FOOTER_ROWS(ps_global
), save_size_opts
,
3447 'n', 0, h_save_size_changed
, RB_NORM
);
3451 remember_the_yes
= 'y';
3452 possible_corruption
++;
3453 return(remember_the_yes
);
3456 possible_corruption
++;
3460 possible_corruption
= 0;
3461 ps_global
->noshow_error
= 1;
3462 ps_global
->noshow_warn
= 1;
3470 /*----------------------------------------------------------------------
3471 Expunge_and_close callback that happens once the decision to expunge
3472 and close has been made and before expunging and closing begins
3475 Args: stream -- folder's stream
3476 folder -- name of folder containing folders
3477 deleted -- number of del'd msgs
3479 Result: 'y' to continue with expunge
3482 expunge_and_close_begins(int flags
, char *folder
)
3484 if(!(flags
& EC_NO_CLOSE
)){
3485 unsigned char *fname
= folder_name_decoded((unsigned char *)folder
);
3486 q_status_message1(SM_INFO
, 0, 1, "Closing \"%.200s\"...", (char *) fname
);
3487 flush_status_messages(1);
3488 if(fname
) fs_give((void **)&fname
);
3493 /*----------------------------------------------------------------------
3494 Export a message to a plain file in users home directory
3496 Args: state -- pointer to struct holding a bunch of pine state
3497 msgmap -- table mapping msg nums to c-client sequence nums
3498 qline -- screen line to ask questions on
3499 agg -- boolean indicating we're to operate on aggregate set
3504 cmd_export(struct pine
*state
, MSGNO_S
*msgmap
, int qline
, int aopt
)
3506 char filename
[MAXPATH
+1], full_filename
[MAXPATH
+1], *err
;
3508 int r
, leading_nl
, failure
= 0, orig_errno
, rflags
= GER_NONE
;
3509 int flags
= GE_IS_EXPORT
| GE_SEQ_SENSITIVE
, rv
= 0;
3513 long i
, count
= 0L, start_of_append
, rawno
;
3516 struct variable
*vars
= ps_global
->vars
;
3517 ESCKEY_S export_opts
[5];
3518 static HISTORY_S
*history
= NULL
;
3520 if(ps_global
->restricted
){
3521 q_status_message(SM_ORDER
, 0, 3,
3522 "Alpine demo can't export messages to files");
3526 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
3529 export_opts
[i
= 0].ch
= ctrl('T');
3530 export_opts
[i
].rval
= 10;
3531 export_opts
[i
].name
= "^T";
3532 export_opts
[i
++].label
= N_("To Files");
3534 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3535 if(ps_global
->VAR_DOWNLOAD_CMD
&& ps_global
->VAR_DOWNLOAD_CMD
[0]){
3536 export_opts
[i
].ch
= ctrl('V');
3537 export_opts
[i
].rval
= 12;
3538 export_opts
[i
].name
= "^V";
3539 /* TRANSLATORS: this is an abbreviation for Download Messages */
3540 export_opts
[i
++].label
= N_("Downld Msg");
3542 #endif /* !(DOS || MAC) */
3544 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps_global
)){
3545 export_opts
[i
].ch
= ctrl('I');
3546 export_opts
[i
].rval
= 11;
3547 export_opts
[i
].name
= "TAB";
3548 export_opts
[i
++].label
= N_("Complete");
3552 /* Commented out since it's not yet support! */
3553 if(F_ON(F_ENABLE_SUB_LISTS
,ps_global
)){
3554 export_opts
[i
].ch
= ctrl('X');
3555 export_opts
[i
].rval
= 14;
3556 export_opts
[i
].name
= "^X";
3557 export_opts
[i
++].label
= N_("ListMatches");
3562 * If message has attachments, add a toggle that will allow the user
3563 * to save all of the attachments to a single directory, using the
3564 * names provided with the attachments or part names. What we'll do is
3565 * export the message as usual, and then export the attachments into
3566 * a subdirectory that did not exist before. The subdir will be named
3567 * something based on the name of the file being saved to, but a
3570 if(!MCMD_ISAGG(aopt
)
3571 && state
->mail_stream
3572 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
3573 && rawno
<= state
->mail_stream
->nmsgs
3574 && (env
= pine_mail_fetchstructure(state
->mail_stream
, rawno
, &b
))
3576 && b
->type
== TYPEMULTIPART
3578 && strucmp(b
->subtype
, "ALTERNATIVE") != 0){
3581 part
= b
->nested
.part
; /* 1st part */
3582 if(part
&& part
->next
)
3583 flags
|= GE_ALLPARTS
;
3586 export_opts
[i
].ch
= -1;
3589 if(mn_total_cur(msgmap
) <= 1L){
3590 snprintf(nmsgs
, sizeof(nmsgs
), "Msg #%ld", mn_get_cur(msgmap
));
3591 nmsgs
[sizeof(nmsgs
)-1] = '\0';
3594 snprintf(nmsgs
, sizeof(nmsgs
), "%s messages", comatose(mn_total_cur(msgmap
)));
3595 nmsgs
[sizeof(nmsgs
)-1] = '\0';
3598 r
= get_export_filename(state
, filename
, NULL
, full_filename
,
3599 sizeof(filename
), nmsgs
, "EXPORT",
3600 export_opts
, &rflags
, qline
, flags
, &history
);
3605 cmd_cancelled("Export message");
3609 q_status_message1(SM_ORDER
, 0, 2,
3610 _("Can't export to file outside of %s"),
3617 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3618 else if(r
== 12){ /* Download */
3619 char cmd
[MAXPATH
], *tfp
= NULL
;
3625 if(ps_global
->restricted
){
3626 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3627 "Download disallowed in restricted mode");
3632 tfp
= temp_nam(NULL
, "pd");
3633 build_updown_cmd(cmd
, sizeof(cmd
), ps_global
->VAR_DOWNLOAD_CMD_PREFIX
,
3634 ps_global
->VAR_DOWNLOAD_CMD
, tfp
);
3635 dprint((1, "Download cmd called: \"%s\"\n", cmd
));
3636 if((so
= so_get(FileStar
, tfp
, WRITE_ACCESS
|OWNER_ONLY
|WRITE_TO_LOCALE
)) != NULL
){
3637 gf_set_so_writec(&pc
, so
);
3639 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
)){
3640 if(!(state
->mail_stream
3641 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
3642 && rawno
<= state
->mail_stream
->nmsgs
3643 && (mc
= mail_elt(state
->mail_stream
, rawno
))
3647 if(!(env
= pine_mail_fetchstructure(state
->mail_stream
,
3648 mn_m2raw(msgmap
, i
), &b
))
3649 || !bezerk_delimiter(env
, mc
, pc
, next
++)
3650 || !format_message(mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
3651 env
, b
, NULL
, FM_NEW_MESS
| FM_NOWRAP
, pc
)){
3652 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3653 err
= "Error writing tempfile for download");
3658 gf_clear_so_writec(so
);
3659 if(so_give(&so
)){ /* close file */
3661 err
= "Error writing tempfile for download";
3665 if((syspipe
= open_system_pipe(cmd
, NULL
, NULL
,
3666 PIPE_USER
| PIPE_RESET
,
3667 0, pipe_callback
, pipe_report_error
)) != NULL
)
3668 (void) close_system_pipe(&syspipe
, NULL
, pipe_callback
);
3670 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3671 err
= _("Error running download command"));
3675 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3676 err
= "Error building temp file for download");
3680 fs_give((void **)&tfp
);
3684 q_status_message(SM_ORDER
, 0, 3, _("Download Command Completed"));
3688 #endif /* !(DOS || MAC) */
3691 if(rflags
& GER_APPEND
)
3696 dprint((5, "Opening file \"%s\" for export\n",
3697 full_filename
? full_filename
: "?"));
3699 if(!(store
= so_get(FileStar
, full_filename
, WRITE_ACCESS
|WRITE_TO_LOCALE
))){
3700 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
3701 /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
3702 _("Error opening file \"%s\" to export message: %s"),
3703 full_filename
, error_description(errno
));
3707 gf_set_so_writec(&pc
, store
);
3710 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
), count
++){
3711 env
= pine_mail_fetchstructure(state
->mail_stream
, mn_m2raw(msgmap
, i
),
3714 err
= _("Can't export message. Error accessing mail folder");
3719 if(!(state
->mail_stream
3720 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
3721 && rawno
<= state
->mail_stream
->nmsgs
3722 && (mc
= mail_elt(state
->mail_stream
, rawno
))
3726 start_of_append
= so_tell(store
);
3727 if(!bezerk_delimiter(env
, mc
, pc
, leading_nl
)
3728 || !format_message(mn_m2raw(msgmap
, i
), env
, b
, NULL
,
3729 FM_NEW_MESS
| FM_NOWRAP
, pc
)){
3730 orig_errno
= errno
; /* save incase things are really bad */
3731 failure
= 1; /* pop out of here */
3738 gf_clear_so_writec(store
);
3739 if(so_give(&store
)) /* release storage */
3743 our_truncate(full_filename
, (off_t
)start_of_append
);
3745 dprint((1, "FAILED Export: fetch(%ld): %s\n",
3746 i
, err
? err
: "?"));
3747 q_status_message(SM_ORDER
| SM_DING
, 3, 4, err
);
3750 dprint((1, "FAILED Export: file \"%s\" : %s\n",
3751 full_filename
? full_filename
: "?",
3752 error_description(orig_errno
)));
3753 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
3754 /* TRANSLATORS: Error exporting to <filename>: <error text> */
3755 _("Error exporting to \"%s\" : %s"),
3756 filename
, error_description(orig_errno
));
3760 if(rflags
& GER_ALLPARTS
&& full_filename
[0]){
3761 char dir
[MAXPATH
+1];
3762 char lfile
[MAXPATH
+1];
3763 int ok
= 0, tries
= 0, saved
= 0, errs
= 0;
3767 * Now we want to save all of the attachments to a subdirectory.
3768 * To make it easier for us and probably easier for the user, and
3769 * to prevent the user from shooting himself in the foot, we
3770 * make a new subdirectory so that we can't possibly step on
3771 * any existing files, and we don't need any interaction with the
3772 * user while saving.
3774 * We'll just use the directory name full_filename.d or if that
3775 * already exists and isn't empty, we'll try adding a suffix to
3776 * that until we get something to use.
3779 if(strlen(full_filename
) + strlen(".d") + 1 > sizeof(dir
)){
3780 q_status_message1(SM_ORDER
| SM_DING
, 3, 4,
3781 _("Can't save attachments, filename too long: %s"),
3787 snprintf(dir
, sizeof(dir
), "%s.d", full_filename
);
3788 dir
[sizeof(dir
)-1] = '\0';
3792 switch(r
= is_writable_dir(dir
)){
3793 case 0: /* exists and is a writable dir */
3795 * We could figure out if it is empty and use it in
3796 * that case, but that sounds like a lot of work, so
3797 * just fall through to default.
3801 if(strlen(full_filename
) + strlen(".d") + 1 +
3802 1 + strlen(long2string((long) tries
)) > sizeof(dir
)){
3803 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3804 "Problem saving attachments");
3808 snprintf(dir
, sizeof(dir
), "%s.d_%s", full_filename
,
3809 long2string((long) tries
));
3810 dir
[sizeof(dir
)-1] = '\0';
3813 case 3: /* doesn't exist, that's good! */
3814 /* make new directory */
3818 } while(!ok
&& tries
< 1000);
3821 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3822 _("Problem saving attachments"));
3826 /* create the new directory */
3827 if(our_mkdir(dir
, 0700)){
3828 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
3829 _("Problem saving attachments: %s: %s"), dir
,
3830 error_description(errno
));
3834 if(!(state
->mail_stream
3835 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
3836 && rawno
<= state
->mail_stream
->nmsgs
3837 && (env
=pine_mail_fetchstructure(state
->mail_stream
,rawno
,&b
))
3839 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3840 _("Problem reading message"));
3844 zero_atmts(state
->atmts
);
3845 describe_mime(b
, "", 1, 1, 0, 0);
3848 if(a
&& a
->description
) /* skip main body part */
3851 for(; a
->description
!= NULL
; a
++){
3852 /* skip over these parts of the message */
3853 if(MIME_MSG_A(a
) || MIME_DGST_A(a
) || MIME_VCARD_A(a
))
3857 (void) get_filename_parameter(lfile
, sizeof(lfile
), a
->body
, NULL
);
3859 if(lfile
[0] == '\0'){
3860 snprintf(lfile
, sizeof(lfile
), "part_%.*s", sizeof(lfile
)-6,
3861 a
->number
? a
->number
: "?");
3862 lfile
[sizeof(lfile
)-1] = '\0';
3865 if(strlen(dir
) + strlen(S_FILESEP
) + strlen(lfile
) + 1
3866 > sizeof(filename
)){
3868 "FAILED Att Export: name too long: %s\n",
3869 dir
, S_FILESEP
, lfile
));
3874 snprintf(filename
, sizeof(filename
), "%s%s%s", dir
, S_FILESEP
, lfile
);
3875 filename
[sizeof(filename
)-1] = '\0';
3877 if(write_attachment_to_file(state
->mail_stream
, rawno
,
3878 a
, GER_NONE
, filename
) == 1)
3886 q_status_message1(SM_ORDER
, 3, 3,
3887 "Errors saving some attachments, %s attachments saved",
3888 long2string((long) saved
));
3890 q_status_message(SM_ORDER
, 3, 3,
3891 _("Problems saving attachments"));
3895 q_status_message2(SM_ORDER
, 0, 3,
3896 /* TRANSLATORS: Saved <how many> attachements to <directory name> */
3897 _("Saved %s attachments to %s"),
3898 long2string((long) saved
), dir
);
3900 q_status_message(SM_ORDER
, 3, 3, _("No attachments to save"));
3903 else if(mn_total_cur(msgmap
) > 1L)
3904 q_status_message4(SM_ORDER
,0,3,
3905 "%s message%s %s to file \"%s\"",
3906 long2string(count
), plural(count
),
3909 : rflags
& GER_APPEND
? "appended" : "exported",
3912 q_status_message3(SM_ORDER
,0,3,
3913 "Message %s %s to file \"%s\"",
3914 long2string(mn_get_cur(msgmap
)),
3917 : rflags
& GER_APPEND
? "appended" : "exported",
3923 if(MCMD_ISAGG(aopt
))
3924 restore_selected(msgmap
);
3931 * Ask user what file to export to. Export from srcstore to that file.
3933 * Args ps -- pine struct
3934 * srctext -- pointer to source text
3935 * srctype -- type of that source text
3936 * prompt_msg -- see get_export_filename
3939 * Returns: != 0 : error
3943 simple_export(struct pine
*ps
, void *srctext
, SourceType srctype
, char *prompt_msg
, char *lister_msg
)
3945 int r
= 1, rflags
= GER_NONE
;
3946 char filename
[MAXPATH
+1], full_filename
[MAXPATH
+1];
3947 STORE_S
*store
= NULL
;
3948 struct variable
*vars
= ps
->vars
;
3949 static HISTORY_S
*history
= NULL
;
3950 static ESCKEY_S simple_export_opts
[] = {
3951 {ctrl('T'), 10, "^T", N_("To Files")},
3952 {-1, 0, NULL
, NULL
},
3953 {-1, 0, NULL
, NULL
}};
3955 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps
)){
3956 simple_export_opts
[r
].ch
= ctrl('I');
3957 simple_export_opts
[r
].rval
= 11;
3958 simple_export_opts
[r
].name
= "TAB";
3959 simple_export_opts
[r
].label
= N_("Complete");
3963 q_status_message(SM_ORDER
, 0, 2, _("Error allocating space"));
3968 simple_export_opts
[++r
].ch
= -1;
3970 full_filename
[0] = '\0';
3972 r
= get_export_filename(ps
, filename
, NULL
, full_filename
, sizeof(filename
),
3973 prompt_msg
, lister_msg
, simple_export_opts
, &rflags
,
3974 -FOOTER_ROWS(ps
), GE_IS_EXPORT
, &history
);
3978 else if(!full_filename
[0]){
3983 dprint((5, "Opening file \"%s\" for export\n",
3984 full_filename
? full_filename
: "?"));
3986 if((store
= so_get(FileStar
, full_filename
, WRITE_ACCESS
|WRITE_TO_LOCALE
)) != NULL
){
3990 gf_set_so_writec(&pc
, store
);
3991 gf_set_readc(&gc
, srctext
, (srctype
== CharStar
)
3992 ? strlen((char *)srctext
)
3996 if((pipe_err
= gf_pipe(gc
, pc
)) != NULL
){
3997 q_status_message2(SM_ORDER
| SM_DING
, 3, 3,
3998 /* TRANSLATORS: Problem saving to <filename>: <error text> */
3999 _("Problem saving to \"%s\": %s"),
4000 filename
, pipe_err
);
4006 gf_clear_so_writec(store
);
4007 if(so_give(&store
)){
4008 q_status_message2(SM_ORDER
| SM_DING
, 3, 3,
4009 _("Problem saving to \"%s\": %s"),
4010 filename
, error_description(errno
));
4015 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
4016 _("Error opening file \"%s\" for export: %s"),
4017 full_filename
, error_description(errno
));
4024 /* overloading full_filename */
4025 snprintf(full_filename
, sizeof(full_filename
), "%c%s",
4026 (prompt_msg
&& prompt_msg
[0])
4027 ? (islower((unsigned char)prompt_msg
[0])
4028 ? toupper((unsigned char)prompt_msg
[0]) : prompt_msg
[0])
4030 (prompt_msg
&& prompt_msg
[0]) ? prompt_msg
+1 : "ext");
4031 full_filename
[sizeof(full_filename
)-1] = '\0';
4032 q_status_message3(SM_ORDER
,0,2,"%s %s to \"%s\"",
4036 : rflags
& GER_APPEND
? "appended" : "exported",
4041 cmd_cancelled("Export");
4045 q_status_message1(SM_ORDER
, 0, 2,
4046 _("Can't export to file outside of %s"), VAR_OPER_DIR
);
4050 ps
->mangled_footer
= 1;
4056 * Ask user what file to export to.
4058 * filename -- On input, this is the filename to start with. On exit,
4059 * this is the filename chosen. (but this isn't used)
4060 * deefault -- This is the default value if user hits return. The
4061 * prompt will have [deefault] added to it automatically.
4062 * full_filename -- This is the full filename on exit.
4063 * len -- Minimum length of _both_ filename and full_filename.
4064 * prompt_msg -- Message to insert in prompt.
4065 * lister_msg -- Message to insert in file_lister.
4066 * opts -- Key options.
4067 * There is a tangled relationship between the callers
4068 * and this routine as far as opts are concerned. Some
4069 * of the opts are handled here. In particular, r == 3,
4070 * r == 10, r == 11, and r == 13 are all handled here.
4071 * Don't use those values unless you want what happens
4072 * here. r == 12 and others are handled by the caller.
4073 * rflags -- Return flags
4074 * GER_OVER - overwrite of existing file
4075 * GER_APPEND - append of existing file
4076 * else file did not exist before
4078 * GER_ALLPARTS - AllParts toggle was turned on
4080 * qline -- Command line to prompt on.
4081 * flags -- Logically OR'd flags
4082 * GE_IS_EXPORT - The command was an Export command
4083 * so the prompt should include
4085 * GE_SEQ_SENSITIVE - The command that got us here is
4086 * sensitive to sequence number changes
4087 * caused by unsolicited expunges.
4088 * GE_NO_APPEND - We will not allow append to an
4089 * existing file, only removal of the
4090 * file if it exists.
4091 * GE_IS_IMPORT - We are selecting for reading.
4092 * No overwriting or checking for
4093 * existence at all. Don't use this
4094 * together with GE_NO_APPEND.
4095 * GE_ALLPARTS - Turn on AllParts toggle.
4097 * Returns: -1 cancelled
4098 * -2 prohibited by VAR_OPER_DIR
4099 * -3 other error, already reported here
4101 * 12 user chose 12 command from opts
4104 get_export_filename(struct pine
*ps
, char *filename
, char *deefault
,
4105 char *full_filename
, size_t len
, char *prompt_msg
,
4106 char *lister_msg
, ESCKEY_S
*optsarg
, int *rflags
,
4107 int qline
, int flags
, HISTORY_S
**history
)
4109 char dir
[MAXPATH
+1], dir2
[MAXPATH
+1];
4110 char precolon
[MAXPATH
+1], postcolon
[MAXPATH
+1];
4111 char filename2
[MAXPATH
+1], tmp
[MAXPATH
+1], *fn
, *ill
;
4112 int l
, i
, ku
= -1, r
, fatal
, homedir
= 0, was_abs_path
=0, avail
, ret
= 0;
4114 char prompt_buf
[400];
4116 ESCKEY_S
*opts
= NULL
;
4117 struct variable
*vars
= ps
->vars
;
4119 if(flags
& GE_ALLPARTS
|| history
){
4121 * Copy the opts and add one to the end of the list.
4123 for(i
= 0; optsarg
[i
].ch
!= -1; i
++)
4129 if(flags
& GE_ALLPARTS
)
4132 opts
= (ESCKEY_S
*) fs_get((i
+1) * sizeof(*opts
));
4133 memset(opts
, 0, (i
+1) * sizeof(*opts
));
4135 for(i
= 0; optsarg
[i
].ch
!= -1; i
++){
4136 opts
[i
].ch
= optsarg
[i
].ch
;
4137 opts
[i
].rval
= optsarg
[i
].rval
;
4138 opts
[i
].name
= optsarg
[i
].name
; /* no need to make a copy */
4139 opts
[i
].label
= optsarg
[i
].label
; /* " */
4142 if(flags
& GE_ALLPARTS
){
4144 opts
[i
].ch
= ctrl('P');
4146 opts
[i
].name
= "^P";
4147 /* TRANSLATORS: Export all attachment parts */
4148 opts
[i
++].label
= N_("AllParts");
4152 opts
[i
].ch
= KEY_UP
;
4156 opts
[i
++].label
= "";
4158 opts
[i
].ch
= KEY_DOWN
;
4161 opts
[i
++].label
= "";
4167 init_hist(history
, HISTSIZE
);
4175 if(F_ON(F_USE_CURRENT_DIR
, ps
))
4177 else if(VAR_OPER_DIR
){
4178 strncpy(dir
, VAR_OPER_DIR
, sizeof(dir
));
4179 dir
[sizeof(dir
)-1] = '\0';
4181 #if defined(DOS) || defined(OS2)
4182 else if(VAR_FILE_DIR
){
4183 strncpy(dir
, VAR_FILE_DIR
, sizeof(dir
));
4184 dir
[sizeof(dir
)-1] = '\0';
4193 postcolon
[0] = '\0';
4194 strncpy(precolon
, dir
, sizeof(precolon
));
4195 precolon
[sizeof(precolon
)-1] = '\0';
4197 strncpy(def
, deefault
, sizeof(def
)-1);
4198 def
[sizeof(def
)-1] = '\0';
4199 removing_leading_and_trailing_white_space(def
);
4204 avail
= MAX(20, ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80) - MIN_OPT_ENT_WIDTH
;
4206 /*---------- Prompt the user for the file name -------------*/
4209 char dirb
[50], fileb
[50];
4210 int l1
, l2
, l3
, l4
, l5
, needed
;
4211 char *p
, p1
[100], p2
[100], *p3
, p4
[100], p5
[100];
4213 snprintf(p1
, sizeof(p1
), "%sCopy ",
4214 (flags
& GE_IS_EXPORT
) ? "EXPORT: " :
4215 (flags
& GE_IS_IMPORT
) ? "IMPORT: " : "SAVE: ");
4216 p1
[sizeof(p1
)-1] = '\0';
4219 strncpy(p2
, prompt_msg
? prompt_msg
: "", sizeof(p2
)-1);
4220 p2
[sizeof(p2
)-1] = '\0';
4223 if(rflags
&& *rflags
& GER_ALLPARTS
)
4230 snprintf(p4
, sizeof(p4
), " %s file%s%s",
4231 (flags
& GE_IS_IMPORT
) ? "from" : "to",
4232 is_absolute_path(filename
) ? "" : " in ",
4233 is_absolute_path(filename
) ? "" :
4234 (!dir
[0] ? "current directory"
4235 : (dir
[0] == '~' && !dir
[1]) ? "home directory"
4236 : short_str(dir
,dirb
,sizeof(dirb
),30,FrontDots
)));
4237 p4
[sizeof(p4
)-1] = '\0';
4240 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4242 *def
? short_str(def
,fileb
,sizeof(fileb
),40,EndDots
) : "",
4244 p5
[sizeof(p5
)-1] = '\0';
4247 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0){
4248 snprintf(p4
, sizeof(p4
), " %s file%s%s",
4249 (flags
& GE_IS_IMPORT
) ? "from" : "to",
4250 is_absolute_path(filename
) ? "" : " in ",
4251 is_absolute_path(filename
) ? "" :
4252 (!dir
[0] ? "current dir"
4253 : (dir
[0] == '~' && !dir
[1]) ? "home dir"
4254 : short_str(dir
,dirb
,sizeof(dirb
),10,FrontDots
)));
4255 p4
[sizeof(p4
)-1] = '\0';
4259 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l5
> 0){
4260 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4262 *def
? short_str(def
,fileb
,sizeof(fileb
),
4263 MAX(15,l5
-5-needed
),EndDots
) : "",
4265 p5
[sizeof(p5
)-1] = '\0';
4269 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l2
> 0){
4272 * 14 is about the shortest we can make this, because there are
4273 * fixed length strings of length 14 coming in here.
4275 p
= short_str(prompt_msg
, p2
, sizeof(p2
), MAX(14,l2
-needed
), FrontDots
);
4277 strncpy(p2
, p
, sizeof(p2
)-1);
4278 p2
[sizeof(p2
)-1] = '\0';
4284 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0){
4285 strncpy(p1
, "Copy ", sizeof(p1
)-1);
4286 p1
[sizeof(p1
)-1] = '\0';
4290 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l5
> 0){
4291 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4293 *def
? short_str(def
,fileb
, sizeof(fileb
),
4294 MAX(10,l5
-5-needed
),EndDots
) : "",
4296 p5
[sizeof(p5
)-1] = '\0';
4300 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l3
> 0){
4301 if(needed
<= l3
- strlen(" (+ atts)"))
4303 else if(needed
<= l3
- strlen(" (atts)"))
4305 else if(needed
<= l3
- strlen(" (+)"))
4307 else if(needed
<= l3
- strlen("+"))
4315 snprintf(prompt_buf
, sizeof(prompt_buf
), "%s%s%s%s%s", p1
, p2
, p3
, p4
, p5
);
4316 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4319 if(items_in_hist(*history
) > 0){
4320 opts
[ku
].name
= HISTORY_UP_KEYNAME
;
4321 opts
[ku
].label
= HISTORY_KEYLABEL
;
4322 opts
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
4323 opts
[ku
+1].label
= HISTORY_KEYLABEL
;
4327 opts
[ku
].label
= "";
4328 opts
[ku
+1].name
= "";
4329 opts
[ku
+1].label
= "";
4333 oeflags
= OE_APPEND_CURRENT
|
4334 ((flags
& GE_SEQ_SENSITIVE
) ? OE_SEQ_SENSITIVE
: 0);
4335 r
= optionally_enter(filename
, qline
, 0, len
, prompt_buf
,
4336 opts
, NO_HELP
, &oeflags
);
4341 * Helps may not be right if you add another caller or change
4342 * things. Check it out.
4344 if(flags
& GE_IS_IMPORT
)
4345 helper(h_ge_import
, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE
);
4346 else if(flags
& GE_ALLPARTS
)
4347 helper(h_ge_allparts
, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE
);
4349 helper(h_ge_export
, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE
);
4351 ps
->mangled_screen
= 1;
4355 else if(r
== 10 || r
== 11){ /* Browser or File Completion */
4356 if(filename
[0]=='~'){
4357 if(filename
[1] == C_FILESEP
&& filename
[2]!='\0'){
4360 for(i
=0; filename
[i
+2] != '\0' && i
+2 < len
-1; i
++)
4361 filename
[i
] = filename
[i
+2];
4363 strncpy(dir
, precolon
, sizeof(dir
)-1);
4364 dir
[sizeof(dir
)-1] = '\0';
4366 else if(filename
[1]=='\0' ||
4367 (filename
[1] == C_FILESEP
&& filename
[2] == '\0')){
4371 strncpy(dir
, precolon
, sizeof(dir
)-1);
4372 dir
[sizeof(dir
)-1] = '\0';
4375 else if(!dir
[0] && !is_absolute_path(filename
) && was_abs_path
){
4379 strncpy(dir
, precolon
, sizeof(dir
)-1);
4380 dir
[sizeof(dir
)-1] = '\0';
4389 strncpy(tmp
, filename
, sizeof(tmp
)-1);
4390 tmp
[sizeof(tmp
)-1] = '\0';
4391 if(*tmp
&& is_absolute_path(tmp
))
4392 fnexpand(tmp
, sizeof(tmp
));
4393 if(strncmp(tmp
,postcolon
, strlen(postcolon
)))
4394 postcolon
[0] = '\0';
4396 if(*tmp
&& (fn
= last_cmpnt(tmp
))){
4398 strncpy(filename2
, fn
, sizeof(filename2
)-1);
4399 filename2
[sizeof(filename2
)-1] = '\0';
4400 if(is_absolute_path(tmp
)){
4401 strncpy(dir2
, tmp
, MIN(fn
- tmp
, sizeof(dir2
)-1));
4402 dir2
[MIN(fn
- tmp
, sizeof(dir2
)-1)] = '\0';
4404 if(tmp
[1]==':' && tmp
[2]=='\\' && dir2
[2]=='\0'){
4409 strncpy(postcolon
, dir2
, sizeof(postcolon
)-1);
4410 postcolon
[sizeof(postcolon
)-1] = '\0';
4416 * Just building the directory name in dir2,
4417 * full_filename is overloaded.
4419 snprintf(full_filename
, len
, "%.*s", MIN(fn
-tmp
,len
-1), tmp
);
4420 full_filename
[len
-1] = '\0';
4421 strncpy(postcolon
, full_filename
, sizeof(postcolon
)-1);
4422 postcolon
[sizeof(postcolon
)-1] = '\0';
4423 build_path(dir2
, !dir
[0] ? p
= (char *)getcwd(NULL
,MAXPATH
)
4424 : (dir
[0] == '~' && !dir
[1])
4427 full_filename
, sizeof(dir2
));
4433 if(is_absolute_path(tmp
)){
4434 strncpy(dir2
, tmp
, sizeof(dir2
)-1);
4435 dir2
[sizeof(dir2
)-1] = '\0';
4437 if(dir2
[2]=='\0' && dir2
[1]==':'){
4440 strncpy(postcolon
,dir2
,sizeof(postcolon
)-1);
4441 postcolon
[sizeof(postcolon
)-1] = '\0';
4444 filename2
[0] = '\0';
4448 strncpy(filename2
, tmp
, sizeof(filename2
)-1);
4449 filename2
[sizeof(filename2
)-1] = '\0';
4451 (void)getcwd(dir2
, sizeof(dir2
));
4452 else if(dir
[0] == '~' && !dir
[1]){
4453 strncpy(dir2
, ps
->home_dir
, sizeof(dir2
)-1);
4454 dir2
[sizeof(dir2
)-1] = '\0';
4457 strncpy(dir2
, dir
, sizeof(dir2
)-1);
4458 dir2
[sizeof(dir2
)-1] = '\0';
4461 postcolon
[0] = '\0';
4465 build_path(full_filename
, dir2
, filename2
, len
);
4466 if(!strcmp(full_filename
, dir2
))
4467 filename2
[0] = '\0';
4468 if(full_filename
[strlen(full_filename
)-1] == C_FILESEP
4469 && isdir(full_filename
,NULL
,NULL
)){
4470 if(strlen(full_filename
) == 1)
4471 strncpy(postcolon
, full_filename
, sizeof(postcolon
)-1);
4472 else if(filename2
[0])
4473 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4474 postcolon
[sizeof(postcolon
)-1] = '\0';
4475 strncpy(dir2
, full_filename
, sizeof(dir2
)-1);
4476 dir2
[sizeof(dir2
)-1] = '\0';
4477 filename2
[0] = '\0';
4479 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4480 else if(full_filename
[strlen(full_filename
)-1] == C_FILESEP
){
4481 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4482 postcolon
[sizeof(postcolon
)-1] = '\0';
4483 strncpy(dir2
, full_filename
, sizeof(dir2
)-1);
4484 dir2
[sizeof(dir2
)-1] = '\0';
4485 filename2
[0] = '\0';
4488 if(dir2
[strlen(dir2
)-1] == C_FILESEP
&& strlen(dir2
)!=1
4489 && strcmp(dir2
+1, ":\\"))
4490 /* last condition to prevent stripping of '\\'
4491 in windows partition */
4492 dir2
[strlen(dir2
)-1] = '\0';
4494 if(r
== 10){ /* File Browser */
4495 r
= file_lister(lister_msg
? lister_msg
: "EXPORT",
4496 dir2
, sizeof(dir2
), filename2
, sizeof(filename2
),
4498 (flags
& GE_IS_IMPORT
) ? FB_READ
: FB_SAVE
);
4500 /* Windows has a special "feature" in which entering the file browser will
4501 change the working directory if the directory is changed at all (even
4502 clicking "Cancel" will change the working directory).
4504 if(F_ON(F_USE_CURRENT_DIR
, ps
))
4505 (void)getcwd(dir2
,sizeof(dir2
));
4507 if(isdir(dir2
,NULL
,NULL
)){
4508 strncpy(precolon
, dir2
, sizeof(precolon
)-1);
4509 precolon
[sizeof(precolon
)-1] = '\0';
4511 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4512 postcolon
[sizeof(postcolon
)-1] = '\0';
4514 build_path(full_filename
, dir2
, filename2
, len
);
4515 if(isdir(full_filename
, NULL
, NULL
)){
4516 strncpy(dir
, full_filename
, sizeof(dir
)-1);
4517 dir
[sizeof(dir
)-1] = '\0';
4521 fn
= last_cmpnt(full_filename
);
4522 strncpy(dir
, full_filename
,
4523 MIN(fn
- full_filename
, sizeof(dir
)-1));
4524 dir
[MIN(fn
- full_filename
, sizeof(dir
)-1)] = '\0';
4525 if(fn
- full_filename
> 1)
4526 dir
[fn
- full_filename
- 1] = '\0';
4529 if(!strcmp(dir
, ps
->home_dir
)){
4534 strncpy(filename
, fn
, len
-1);
4535 filename
[len
-1] = '\0';
4538 else{ /* File Completion */
4539 if(!pico_fncomplete(dir2
, filename2
, sizeof(filename2
)))
4541 strncat(postcolon
, filename2
,
4542 sizeof(postcolon
)-1-strlen(postcolon
));
4543 postcolon
[sizeof(postcolon
)-1] = '\0';
4545 was_abs_path
= is_absolute_path(filename
);
4547 if(!strcmp(dir
, ps
->home_dir
)){
4552 strncpy(filename
, postcolon
, len
-1);
4553 filename
[len
-1] = '\0';
4554 strncpy(dir
, precolon
, sizeof(dir
)-1);
4555 dir
[sizeof(dir
)-1] = '\0';
4557 if(filename
[0] == '~' && !filename
[1]){
4565 else if(r
== 12){ /* Download, caller handles it */
4569 else if(r
== 13){ /* toggle AllParts bit */
4571 if(*rflags
& GER_ALLPARTS
){
4572 *rflags
&= ~GER_ALLPARTS
;
4573 opts
[allparts
].label
= N_("AllParts");
4576 *rflags
|= GER_ALLPARTS
;
4577 /* opposite of All Parts, No All Parts */
4578 opts
[allparts
].label
= N_("NoAllParts");
4585 else if(r
== 14){ /* List file names matching partial? */
4589 else if(r
== 1){ /* Cancel */
4596 else if(r
== 30 || r
== 31){
4601 p
= get_prev_hist(*history
, filename
, 0, NULL
);
4603 p
= get_next_hist(*history
, filename
, 0, NULL
);
4608 strncpy(dir
, p
, MIN(fn
- p
, sizeof(dir
)-1));
4609 dir
[MIN(fn
- p
, sizeof(dir
)-1)] = '\0';
4611 dir
[fn
- p
- 1] = '\0';
4613 if(!strcmp(dir
, ps
->home_dir
)){
4618 strncpy(filename
, fn
, len
-1);
4619 filename
[len
-1] = '\0';
4631 removing_leading_and_trailing_white_space(filename
);
4634 if(!*def
){ /* Cancel */
4639 strncpy(filename
, def
, len
-1);
4640 filename
[len
-1] = '\0';
4643 #if defined(DOS) || defined(OS2)
4644 if(is_absolute_path(filename
)){
4645 fixpath(filename
, len
);
4648 if(filename
[0] == '~'){
4649 if(fnexpand(filename
, len
) == NULL
){
4650 char *p
= strindex(filename
, '/');
4653 q_status_message1(SM_ORDER
| SM_DING
, 3, 3,
4654 _("Error expanding file name: \"%s\" unknown user"),
4661 if(is_absolute_path(filename
)){
4662 strncpy(full_filename
, filename
, len
-1);
4663 full_filename
[len
-1] = '\0';
4667 build_path(full_filename
, (char *)getcwd(dir
,sizeof(dir
)),
4669 else if(dir
[0] == '~' && !dir
[1])
4670 build_path(full_filename
, ps
->home_dir
, filename
, len
);
4672 build_path(full_filename
, dir
, filename
, len
);
4675 if((ill
= filter_filename(full_filename
, &fatal
,
4676 ps_global
->restricted
|| ps_global
->VAR_OPER_DIR
)) != NULL
){
4678 q_status_message1(SM_ORDER
| SM_DING
, 3, 3, "%s", ill
);
4682 /* BUG: we should beep when the key's pressed rather than bitch later */
4683 /* Warn and ask for confirmation. */
4684 snprintf(prompt_buf
, sizeof(prompt_buf
), "File name contains a '%s'. %s anyway",
4685 ill
, (flags
& GE_IS_EXPORT
) ? "Export" : "Save");
4686 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4687 if(want_to(prompt_buf
, 'n', 0, NO_HELP
,
4688 ((flags
& GE_SEQ_SENSITIVE
) ? RB_SEQ_SENSITIVE
: 0)) != 'y')
4693 break; /* Must have got an OK file name */
4696 if(VAR_OPER_DIR
&& !in_dir(VAR_OPER_DIR
, full_filename
)){
4701 if(!can_access(full_filename
, ACCESS_EXISTS
)){
4703 static ESCKEY_S access_opts
[] = {
4704 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4705 a file or append to the end of the file */
4706 {'o', 'o', "O", N_("Overwrite")},
4707 {'a', 'a', "A", N_("Append")},
4708 {-1, 0, NULL
, NULL
}};
4710 rbflags
= RB_NORM
| ((flags
& GE_SEQ_SENSITIVE
) ? RB_SEQ_SENSITIVE
: 0);
4712 if(flags
& GE_NO_APPEND
){
4713 r
= strlen(filename
);
4714 snprintf(prompt_buf
, sizeof(prompt_buf
),
4715 /* TRANSLATORS: asking user whether to overwrite a file or not,
4716 File <filename> already exists. Overwrite it ? */
4717 _("File \"%s%s\" already exists. Overwrite it "),
4718 (r
> 20) ? "..." : "",
4719 filename
+ ((r
> 20) ? r
- 20 : 0));
4720 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4721 if(want_to(prompt_buf
, 'n', 'x', NO_HELP
, rbflags
) == 'y'){
4723 *rflags
|= GER_OVER
;
4725 if(our_unlink(full_filename
) < 0){
4726 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
4727 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4728 _("Cannot remove old %s: %s"),
4729 full_filename
, error_description(errno
));
4737 else if(!(flags
& GE_IS_IMPORT
)){
4738 r
= strlen(filename
);
4739 snprintf(prompt_buf
, sizeof(prompt_buf
),
4740 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4741 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4742 (r
> 20) ? "..." : "",
4743 filename
+ ((r
> 20) ? r
- 20 : 0));
4744 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4745 switch(radio_buttons(prompt_buf
, -FOOTER_ROWS(ps_global
),
4746 access_opts
, 'a', 'x', NO_HELP
, rbflags
)){
4749 *rflags
|= GER_OVER
;
4751 if(our_truncate(full_filename
, (off_t
)0) < 0)
4752 /* trouble truncating, but we'll give it a try anyway */
4753 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
4754 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
4755 _("Warning: Cannot truncate old %s: %s"),
4756 full_filename
, error_description(errno
));
4761 *rflags
|= GER_APPEND
;
4774 if(history
&& ret
== 0)
4775 save_hist(*history
, full_filename
, 0, NULL
);
4777 if(opts
&& opts
!= optsarg
)
4778 fs_give((void **) &opts
);
4784 /*----------------------------------------------------------------------
4785 parse the config'd upload/download command
4787 Args: cmd -- buffer to return command fit for shellin'
4790 fname -- file name to build into the command
4792 Returns: pointer to cmd_str buffer or NULL on real bad error
4794 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
4795 cfg_str is written to standard out right before a successful
4796 return of this function. The call immediately following this
4797 function darn well better be the shell exec...
4800 build_updown_cmd(char *cmd
, size_t cmdlen
, char *prefix
, char *cfg_str
, char *fname
)
4803 int fname_found
= 0;
4805 if(prefix
&& *prefix
){
4806 /* loop thru replacing all occurances of _FILE_ */
4807 p
= strncpy(cmd
, prefix
, cmdlen
);
4808 cmd
[cmdlen
-1] = '\0';
4809 while((p
= strstr(p
, "_FILE_")))
4810 rplstr(p
, cmdlen
-(p
-cmd
), 6, fname
);
4815 /* loop thru replacing all occurances of _FILE_ */
4816 p
= strncpy(cmd
, cfg_str
, cmdlen
);
4817 cmd
[cmdlen
-1] = '\0';
4818 while((p
= strstr(p
, "_FILE_"))){
4819 rplstr(p
, cmdlen
-(p
-cmd
), 6, fname
);
4824 snprintf(cmd
+strlen(cmd
), cmdlen
-strlen(cmd
), " %s", fname
);
4826 cmd
[cmdlen
-1] = '\0';
4828 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
4834 /*----------------------------------------------------------------------
4835 Write a berzerk format message delimiter using the given putc function
4837 Args: e -- envelope of message to write
4838 pc -- function to use
4840 Returns: TRUE if we could write it, FALSE if there was a problem
4842 NOTE: follows delimiter with OS-dependent newline
4845 bezerk_delimiter(ENVELOPE
*env
, MESSAGECACHE
*mc
, gf_io_t pc
, int leading_newline
)
4851 /* write "[\n]From mailbox[@host] " */
4852 if(!((leading_newline
? gf_puts(NEWLINE
, pc
) : 1)
4853 && gf_puts("From ", pc
)
4854 && gf_puts((env
&& env
->from
) ? env
->from
->mailbox
4855 : "the-concourse-on-high", pc
)
4856 && gf_puts((env
&& env
->from
&& env
->from
->host
) ? "@" : "", pc
)
4857 && gf_puts((env
&& env
->from
&& env
->from
->host
) ? env
->from
->host
4863 when
= mail_longdate(mc
);
4864 else if(env
&& env
->date
&& env
->date
[0]
4865 && mail_parse_date(&telt
,env
->date
))
4866 when
= mail_longdate(&telt
);
4872 while(p
&& *p
&& *p
!= '\n') /* write date */
4876 if(!gf_puts(NEWLINE
, pc
)) /* write terminating newline */
4883 /*----------------------------------------------------------------------
4884 Execute command to jump to a given message number
4886 Args: qline -- Line to ask question on
4888 Result: returns true if the use selected a new message, false otherwise
4892 jump_to(MSGNO_S
*msgmap
, int qline
, UCS first_num
, SCROLL_S
*sparms
, CmdWhere in_index
)
4894 char jump_num_string
[80], *j
, prompt
[70];
4897 static ESCKEY_S jump_to_key
[] = { {0, 0, NULL
, NULL
},
4898 /* TRANSLATORS: go to First Message */
4899 {ctrl('Y'), 10, "^Y", N_("First Msg")},
4900 {ctrl('V'), 11, "^V", N_("Last Msg")},
4901 {-1, 0, NULL
, NULL
} };
4903 dprint((4, "\n - jump_to -\n"));
4906 if(sparms
&& sparms
->jump_is_debug
)
4907 return(get_level(qline
, first_num
, sparms
));
4910 if(!any_messages(msgmap
, NULL
, "to Jump to"))
4913 if(first_num
&& first_num
< 0x80 && isdigit((unsigned char) first_num
)){
4914 jump_num_string
[0] = first_num
;
4915 jump_num_string
[1] = '\0';
4918 jump_num_string
[0] = '\0';
4920 if(mn_total_cur(msgmap
) > 1L){
4921 snprintf(prompt
, sizeof(prompt
), "Unselect %s msgs in favor of number to be entered",
4922 comatose(mn_total_cur(msgmap
)));
4923 prompt
[sizeof(prompt
)-1] = '\0';
4924 if((rc
= want_to(prompt
, 'n', 0, NO_HELP
, WT_NORM
)) == 'n')
4928 snprintf(prompt
, sizeof(prompt
), "%s number to jump to : ", in_index
== ThrdIndx
4931 prompt
[sizeof(prompt
)-1] = '\0';
4935 int flags
= OE_APPEND_CURRENT
;
4937 rc
= optionally_enter(jump_num_string
, qline
, 0,
4938 sizeof(jump_num_string
), prompt
,
4939 jump_to_key
, help
, &flags
);
4941 help
= help
== NO_HELP
4942 ? (in_index
== ThrdIndx
? h_oe_jump_thd
: h_oe_jump
)
4946 else if(rc
== 10 || rc
== 11){
4950 closest
= closest_jump_target(rc
== 10 ? 1L
4951 : ((in_index
== ThrdIndx
)
4952 ? msgmap
->max_thrdno
4953 : mn_get_total(msgmap
)),
4954 ps_global
->mail_stream
,
4956 in_index
, warning
, sizeof(warning
));
4957 /* ignore warning */
4962 * If we take out the *jump_num_string nonempty test in this if
4963 * then the closest_jump_target routine will offer a jump to the
4964 * last message. However, it is slow because you have to wait for
4965 * the status message and it is annoying for people who hit J command
4966 * by mistake and just want to hit return to do nothing, like has
4967 * always worked. So the test is there for now. Hubert 2002-08-19
4969 * Jumping to first/last message is now possible through ^Y/^V
4970 * commands above. jpf 2002-08-21
4971 * (and through "end" hubert 2006-07-07)
4973 if(rc
== 0 && *jump_num_string
!= '\0'){
4974 removing_leading_and_trailing_white_space(jump_num_string
);
4975 for(j
=jump_num_string
; isdigit((unsigned char)*j
) || *j
=='-'; j
++)
4979 if(!strucmp("end", j
))
4980 return((in_index
== ThrdIndx
) ? msgmap
->max_thrdno
: mn_get_total(msgmap
));
4982 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
4983 _("Invalid number entered. Use only digits 0-9"));
4984 jump_num_string
[0] = '\0';
4988 long closest
, jump_num
;
4990 if(*jump_num_string
)
4991 jump_num
= atol(jump_num_string
);
4996 closest
= closest_jump_target(jump_num
, ps_global
->mail_stream
,
4998 *jump_num_string
? 0 : 1,
4999 in_index
, warning
, sizeof(warning
));
5001 q_status_message(SM_ORDER
| SM_DING
, 2, 2, warning
);
5003 if(closest
== jump_num
)
5007 jump_num_string
[0] = '\0';
5009 strncpy(jump_num_string
, long2string(closest
),
5010 sizeof(jump_num_string
));
5025 * cmd_delete_action - handle msgno advance and such after single message deletion
5028 cmd_delete_action(struct pine
*state
, MSGNO_S
*msgmap
, CmdWhere in_index
)
5034 msgno
= mn_get_cur(msgmap
);
5035 advance_cur_after_delete(state
, state
->mail_stream
, msgmap
, in_index
);
5037 if(IS_NEWS(state
->mail_stream
)
5038 || ((state
->context_current
->use
& CNTXT_INCMNG
)
5039 && context_isambig(state
->cur_folder
))){
5041 opts
= (NSF_TRUST_FLAGS
| NSF_SKIP_CHID
);
5042 if(in_index
== View
)
5043 opts
&= ~NSF_SKIP_CHID
;
5045 (void)next_sorted_flagged(F_UNDEL
|F_UNSEEN
, state
->mail_stream
, msgno
, &opts
);
5046 if(!(opts
& NSF_FLAG_MATCH
)){
5047 char nextfolder
[MAXPATH
];
5049 strncpy(nextfolder
, state
->cur_folder
, sizeof(nextfolder
));
5050 nextfolder
[sizeof(nextfolder
)-1] = '\0';
5051 rv
= next_folder(NULL
, nextfolder
, sizeof(nextfolder
), nextfolder
,
5052 state
->context_current
, NULL
, NULL
)
5053 ? ". Press TAB for next folder."
5054 : ". No more folders to TAB to.";
5063 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5066 cmd_delete_index(struct pine
*state
, MSGNO_S
*msgmap
)
5068 return(cmd_delete_action(state
, msgmap
,MsgIndx
));
5072 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5075 cmd_delete_view(struct pine
*state
, MSGNO_S
*msgmap
)
5077 return(cmd_delete_action(state
, msgmap
, View
));
5082 advance_cur_after_delete(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, CmdWhere in_index
)
5084 long new_msgno
, msgno
;
5087 new_msgno
= msgno
= mn_get_cur(msgmap
);
5088 opts
= NSF_TRUST_FLAGS
;
5090 if(F_ON(F_DEL_SKIPS_DEL
, state
)){
5092 if(THREADING() && sp_viewing_a_thread(stream
))
5093 opts
|= NSF_SKIP_CHID
;
5095 new_msgno
= next_sorted_flagged(F_UNDEL
, stream
, msgno
, &opts
);
5098 mn_inc_cur(stream
, msgmap
,
5099 (in_index
== View
&& THREADING()
5100 && sp_viewing_a_thread(stream
))
5102 : (in_index
== View
)
5103 ? MH_ANYTHD
: MH_NONE
);
5104 new_msgno
= mn_get_cur(msgmap
);
5105 if(new_msgno
!= msgno
)
5106 opts
|= NSF_FLAG_MATCH
;
5110 * Viewing_a_thread is the complicated case because we want to ignore
5111 * other threads at first and then look in other threads if we have to.
5112 * By ignoring other threads we also ignore collapsed partial threads
5113 * in our own thread.
5115 if(THREADING() && sp_viewing_a_thread(stream
) && !(opts
& NSF_FLAG_MATCH
)){
5116 long rawno
, orig_thrdno
;
5117 PINETHRD_S
*thrd
, *topthrd
= NULL
;
5119 rawno
= mn_m2raw(msgmap
, msgno
);
5120 thrd
= fetch_thread(stream
, rawno
);
5121 if(thrd
&& thrd
->top
)
5122 topthrd
= fetch_thread(stream
, thrd
->top
);
5124 orig_thrdno
= topthrd
? topthrd
->thrdno
: -1L;
5126 opts
= NSF_TRUST_FLAGS
;
5127 new_msgno
= next_sorted_flagged(F_UNDEL
, stream
, msgno
, &opts
);
5130 * If we got a match, new_msgno may be a message in
5131 * a different thread from the one we are viewing, or it could be
5132 * in a collapsed part of this thread.
5134 if(opts
& NSF_FLAG_MATCH
){
5139 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
,new_msgno
));
5140 if(thrd
&& thrd
->top
)
5141 topthrd
= fetch_thread(stream
, thrd
->top
);
5144 * If this match is in the same thread we're already in
5145 * then we're done, else we have to ask the user and maybe
5148 if(!(orig_thrdno
> 0L && topthrd
5149 && topthrd
->thrdno
== orig_thrdno
)){
5151 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD
, state
)){
5152 if(in_index
== View
)
5153 snprintf(pmt
, sizeof(pmt
),
5154 "View message in thread number %.10s",
5155 topthrd
? comatose(topthrd
->thrdno
) : "?");
5157 snprintf(pmt
, sizeof(pmt
), "View thread number %.10s",
5158 topthrd
? comatose(topthrd
->thrdno
) : "?");
5160 ret
= want_to(pmt
, 'y', 'x', NO_HELP
, WT_NORM
);
5166 unview_thread(state
, stream
, msgmap
);
5167 mn_set_cur(msgmap
, new_msgno
);
5169 && (count_lflags_in_thread(stream
, topthrd
, msgmap
,
5171 && view_thread(state
, stream
, msgmap
, 1)){
5172 if(current_index_state
)
5173 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
5175 state
->view_skipped_index
= 1;
5176 state
->next_screen
= mail_view_screen
;
5179 view_thread(state
, stream
, msgmap
, 1);
5180 if(current_index_state
)
5181 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
5183 state
->next_screen
= SCREEN_FUN_NULL
;
5187 new_msgno
= msgno
; /* stick with original */
5192 mn_set_cur(msgmap
, new_msgno
);
5193 if(in_index
!= View
)
5194 adjust_cur_to_visible(stream
, msgmap
);
5200 get_level(int qline
, UCS first_num
, SCROLL_S
*sparms
)
5202 char debug_num_string
[80], *j
, prompt
[70];
5207 if(first_num
&& first_num
< 0x80 && isdigit((unsigned char)first_num
)){
5208 debug_num_string
[0] = first_num
;
5209 debug_num_string
[1] = '\0';
5210 debug_num
= atol(debug_num_string
);
5211 *(int *)(sparms
->proc
.data
.p
) = debug_num
;
5212 q_status_message1(SM_ORDER
, 0, 3, "Show debug <= level %s",
5213 comatose(debug_num
));
5217 debug_num_string
[0] = '\0';
5219 snprintf(prompt
, sizeof(prompt
), "Show debug <= this level (0-%d) : ", MAX(debug
, 9));
5220 prompt
[sizeof(prompt
)-1] = '\0';
5224 int flags
= OE_APPEND_CURRENT
;
5226 rc
= optionally_enter(debug_num_string
, qline
, 0,
5227 sizeof(debug_num_string
), prompt
,
5228 NULL
, help
, &flags
);
5230 help
= help
== NO_HELP
? h_oe_debuglevel
: NO_HELP
;
5235 removing_leading_and_trailing_white_space(debug_num_string
);
5236 for(j
=debug_num_string
; isdigit((unsigned char)*j
); j
++)
5240 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5241 _("Invalid number entered. Use only digits 0-9"));
5242 debug_num_string
[0] = '\0';
5245 debug_num
= atol(debug_num_string
);
5247 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5248 _("Number should be >= 0"));
5249 else if(debug_num
> MAX(debug
,9))
5250 q_status_message1(SM_ORDER
| SM_DING
, 2, 2,
5251 _("Maximum is %s"), comatose(MAX(debug
,9)));
5253 *(int *)(sparms
->proc
.data
.p
) = debug_num
;
5254 q_status_message1(SM_ORDER
, 0, 3,
5255 "Show debug <= level %s",
5256 comatose(debug_num
));
5274 * Returns the message number closest to target that isn't hidden.
5275 * Make warning at least 100 chars.
5276 * A return of 0 means there is no message to jump to.
5279 closest_jump_target(long int target
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int no_target
, CmdWhere in_index
, char *warning
, size_t warninglen
)
5281 long i
, start
, closest
= 0L;
5286 maxnum
= (in_index
== ThrdIndx
) ? msgmap
->max_thrdno
: mn_get_total(msgmap
);
5291 snprintf(warning
, warninglen
, "No %s number entered, jump to end? ",
5292 (in_index
== ThrdIndx
) ? "thread" : "message");
5293 warning
[warninglen
-1] = '\0';
5295 else if(target
< 1L)
5296 start
= 1L - target
;
5297 else if(target
> maxnum
)
5298 start
= target
- maxnum
;
5302 if(target
> 0L && target
<= maxnum
)
5303 if(in_index
== ThrdIndx
5304 || !msgline_hidden(stream
, msgmap
, target
, 0))
5307 for(i
= start
; target
+i
<= maxnum
|| target
-i
> 0L; i
++){
5309 if(target
+i
> 0L && target
+i
<= maxnum
&&
5310 (in_index
== ThrdIndx
5311 || !msgline_hidden(stream
, msgmap
, target
+i
, 0))){
5316 if(target
-i
> 0L && target
-i
<= maxnum
&&
5317 (in_index
== ThrdIndx
5318 || !msgline_hidden(stream
, msgmap
, target
-i
, 0))){
5324 strncpy(buf
, long2string(closest
), sizeof(buf
));
5325 buf
[sizeof(buf
)-1] = '\0';
5328 strncpy(warning
, "Nothing to jump to", warninglen
);
5329 else if(target
< 1L)
5330 snprintf(warning
, warninglen
, "%s number (%s) must be at least %s",
5331 (in_index
== ThrdIndx
) ? "Thread" : "Message",
5332 long2string(target
), buf
);
5333 else if(target
> maxnum
)
5334 snprintf(warning
, warninglen
, "%s number (%s) may be no more than %s",
5335 (in_index
== ThrdIndx
) ? "Thread" : "Message",
5336 long2string(target
), buf
);
5338 snprintf(warning
, warninglen
,
5339 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5340 long2string(target
), buf
);
5342 warning
[warninglen
-1] = '\0';
5348 /*----------------------------------------------------------------------
5349 Prompt for folder name to open, expand the name and return it
5351 Args: qline -- Screen line to prompt on
5352 allow_list -- if 1, allow ^T to bring up collection lister
5354 Result: returns the folder name or NULL
5355 pine structure mangled_footer flag is set
5356 may call the collection lister in which case mangled screen will be set
5358 This prompts the user for the folder to open, possibly calling up
5359 the collection lister if the user types ^T.
5360 ----------------------------------------------------------------------*/
5362 broach_folder(int qline
, int allow_list
, int *notrealinbox
, CONTEXT_S
**context
)
5365 static char newfolder
[MAILTMPLEN
];
5366 char expanded
[MAXPATH
+1],
5367 prompt
[MAX_SCREEN_COLS
+1],
5369 unsigned char *f1
, *f2
, *f3
;
5370 static HISTORY_S
*history
= NULL
;
5371 CONTEXT_S
*tc
, *tc2
;
5373 int rc
, r
, ku
= -1, n
, flags
, last_rc
= 0, inbox
, done
= 0;
5376 * the idea is to provide a clue for the context the file name
5377 * will be saved in (if a non-imap names is typed), and to
5378 * only show the previous if it was also in the same context
5385 (*notrealinbox
) = 1;
5387 init_hist(&history
, HISTSIZE
);
5389 tc
= broach_get_folder(context
? *context
: NULL
, &inbox
, NULL
);
5391 /* set up extra command option keys */
5393 ekey
[rc
].ch
= (allow_list
) ? ctrl('T') : 0 ;
5394 ekey
[rc
].rval
= (allow_list
) ? 2 : 0;
5395 ekey
[rc
].name
= (allow_list
) ? "^T" : "";
5396 ekey
[rc
++].label
= (allow_list
) ? N_("ToFldrs") : "";
5398 if(ps_global
->context_list
->next
){
5399 ekey
[rc
].ch
= ctrl('P');
5401 ekey
[rc
].name
= "^P";
5402 ekey
[rc
++].label
= N_("Prev Collection");
5404 ekey
[rc
].ch
= ctrl('N');
5406 ekey
[rc
].name
= "^N";
5407 ekey
[rc
++].label
= N_("Next Collection");
5410 ekey
[rc
].ch
= ctrl('W');
5412 ekey
[rc
].name
= "^W";
5413 ekey
[rc
++].label
= N_("INBOX");
5415 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps_global
)){
5418 ekey
[rc
].name
= "TAB";
5419 ekey
[rc
++].label
= N_("Complete");
5422 if(F_ON(F_ENABLE_SUB_LISTS
, ps_global
)){
5423 ekey
[rc
].ch
= ctrl('X');
5425 ekey
[rc
].name
= "^X";
5426 ekey
[rc
++].label
= N_("ListMatches");
5429 if(ps_global
->context_list
->next
&& F_ON(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
5430 ekey
[rc
].ch
= KEY_UP
;
5433 ekey
[rc
++].label
= "";
5435 ekey
[rc
].ch
= KEY_DOWN
;
5438 ekey
[rc
++].label
= "";
5440 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
5441 ekey
[rc
].ch
= KEY_UP
;
5445 ekey
[rc
++].label
= "";
5447 ekey
[rc
].ch
= KEY_DOWN
;
5450 ekey
[rc
++].label
= "";
5457 * Figure out next default value for this context. The idea
5458 * is that in each context the last folder opened is cached.
5459 * It's up to pick it out and display it. This is fine
5460 * and dandy if we've currently got the inbox open, BUT
5461 * if not, make the inbox the default the first time thru.
5464 last_folder
= ps_global
->inbox_name
;
5465 inbox
= 1; /* pretend we're in inbox from here on out */
5468 last_folder
= (ps_global
->last_unambig_folder
[0])
5469 ? ps_global
->last_unambig_folder
5470 : ((tc
->last_folder
[0]) ? tc
->last_folder
: NULL
);
5473 unsigned char *fname
= folder_name_decoded((unsigned char *)last_folder
);
5474 snprintf(expanded
, sizeof(expanded
), " [%.*s]", sizeof(expanded
)-5,
5475 fname
? (char *) fname
: last_folder
);
5476 if(fname
) fs_give((void **)&fname
);
5481 expanded
[sizeof(expanded
)-1] = '\0';
5483 /* only show collection number if more than one available */
5484 if(ps_global
->context_list
->next
)
5485 snprintf(prompt
, sizeof(prompt
), "GOTO %s in <%s> %.*s%s: ",
5486 NEWS_TEST(tc
) ? "news group" : "folder",
5487 tc
->nickname
, sizeof(prompt
)-50, expanded
,
5488 *expanded
? " " : "");
5490 snprintf(prompt
, sizeof(prompt
), "GOTO folder %.*s%s: ", sizeof(prompt
)-20, expanded
,
5491 *expanded
? " " : "");
5493 prompt
[sizeof(prompt
)-1] = '\0';
5495 if(utf8_width(prompt
) > MAXPROMPT
){
5496 if(ps_global
->context_list
->next
)
5497 snprintf(prompt
, sizeof(prompt
), "GOTO <%s> %.*s%s: ",
5498 tc
->nickname
, sizeof(prompt
)-50, expanded
,
5499 *expanded
? " " : "");
5501 snprintf(prompt
, sizeof(prompt
), "GOTO %.*s%s: ", sizeof(prompt
)-20, expanded
,
5502 *expanded
? " " : "");
5504 prompt
[sizeof(prompt
)-1] = '\0';
5506 if(utf8_width(prompt
) > MAXPROMPT
){
5507 if(ps_global
->context_list
->next
)
5508 snprintf(prompt
, sizeof(prompt
), "<%s> %.*s%s: ",
5509 tc
->nickname
, sizeof(prompt
)-50, expanded
,
5510 *expanded
? " " : "");
5512 snprintf(prompt
, sizeof(prompt
), "%.*s%s: ", sizeof(prompt
)-20, expanded
,
5513 *expanded
? " " : "");
5515 prompt
[sizeof(prompt
)-1] = '\0';
5520 if(items_in_hist(history
) > 1){
5521 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
5522 ekey
[ku
].label
= HISTORY_KEYLABEL
;
5523 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
5524 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
5528 ekey
[ku
].label
= "";
5529 ekey
[ku
+1].name
= "";
5530 ekey
[ku
+1].label
= "";
5534 /* is there any other way to do this? The point is that we
5535 * are trying to hide mutf7 from the user, and use the utf8
5536 * equivalent. So we create a variable f to take place of
5537 * newfolder, including content and size. f2 is copy of f1
5538 * that has to freed. Sigh!
5540 f3
= cpystr(newfolder
);
5541 f1
= fs_get(sizeof(newfolder
));
5542 f2
= folder_name_decoded(f3
);
5543 if(f3
) fs_give((void **)&f3
);
5544 strncpy(f1
, f2
, sizeof(newfolder
));
5545 f1
[sizeof(newfolder
)-1] = '\0';
5546 if(f2
) fs_give((void **)&f2
);
5548 flags
= OE_APPEND_CURRENT
;
5549 rc
= optionally_enter(f1
, qline
, 0, sizeof(newfolder
),
5550 prompt
, ekey
, help
, &flags
);
5552 f2
= folder_name_encoded(f1
);
5553 strncpy(newfolder
, f2
, sizeof(newfolder
));
5554 if(f1
) fs_give((void **)&f1
);
5555 if(f2
) fs_give((void **)&f2
);
5557 ps_global
->mangled_footer
= 1;
5560 case -1 : /* o_e says error! */
5561 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
5562 _("Error reading folder name"));
5565 case 0 : /* o_e says normal entry */
5566 removing_trailing_white_space(newfolder
);
5567 removing_leading_white_space(newfolder
);
5570 char *name
, *fullname
= NULL
;
5571 int exists
, breakout
= 0;
5573 save_hist(history
, newfolder
, 0, tc
);
5575 if(!(name
= folder_is_nick(newfolder
, FOLDERS(tc
),
5579 if(update_folder_spec(expanded
, sizeof(expanded
), name
)){
5580 strncpy(name
= newfolder
, expanded
, sizeof(newfolder
));
5581 newfolder
[sizeof(newfolder
)-1] = '\0';
5584 exists
= folder_name_exists(tc
, name
, &fullname
);
5587 strncpy(name
= newfolder
, fullname
, sizeof(newfolder
));
5588 newfolder
[sizeof(newfolder
)-1] = '\0';
5589 fs_give((void **) &fullname
);
5594 * if we know the things a folder, open it.
5595 * else if we know its a directory, visit it.
5596 * else we're not sure (it either doesn't really
5597 * exist or its unLISTable) so try opening it anyway
5599 if(exists
& FEX_ISFILE
){
5603 else if((exists
& FEX_ISDIR
)){
5605 CONTEXT_S
*fake_context
;
5606 char tmp
[MAILTMPLEN
];
5609 strncpy(tmp
, name
, sizeof(tmp
));
5610 tmp
[sizeof(tmp
)-2-1] = '\0';
5611 if(tmp
[(l
= strlen(tmp
)) - 1] != tc
->dir
->delim
){
5612 if(l
< sizeof(tmp
)){
5613 tmp
[l
] = tc
->dir
->delim
;
5614 strncpy(&tmp
[l
+1], "[]", sizeof(tmp
)-(l
+1));
5618 strncat(tmp
, "[]", sizeof(tmp
)-strlen(tmp
)-1);
5620 tmp
[sizeof(tmp
)-1] = '\0';
5622 fake_context
= new_context(tmp
, 0);
5623 newfolder
[0] = '\0';
5624 done
= display_folder_list(&fake_context
, newfolder
,
5625 1, folders_for_goto
);
5626 free_context(&fake_context
);
5629 else if(!(tc
->use
& CNTXT_INCMNG
)){
5630 done
= display_folder_list(&tc
, newfolder
,
5631 1, folders_for_goto
);
5635 else if((exists
& FEX_ERROR
)){
5636 q_status_message1(SM_ORDER
, 0, 3,
5637 _("Problem accessing folder \"%s\""),
5646 if(exists
== FEX_ERROR
)
5647 q_status_message1(SM_ORDER
, 0, 3,
5648 _("Problem accessing folder \"%s\""),
5650 else if(tc
->use
& CNTXT_INCMNG
)
5651 q_status_message1(SM_ORDER
, 0, 3,
5652 _("Can't find Incoming Folder: %s"),
5654 else if(context_isambig(newfolder
))
5655 q_status_message2(SM_ORDER
, 0, 3,
5656 _("Can't find folder \"%s\" in %s"),
5657 newfolder
, (void *) tc
->nickname
);
5659 q_status_message1(SM_ORDER
, 0, 3,
5660 _("Can't find folder \"%s\""),
5665 else if(last_folder
){
5666 if(ps_global
->goto_default_rule
== GOTO_FIRST_CLCTN_DEF_INBOX
5667 && !strucmp(last_folder
, ps_global
->inbox_name
)
5668 && tc
== ((ps_global
->context_list
->use
& CNTXT_INCMNG
)
5669 ? ps_global
->context_list
->next
: ps_global
->context_list
)){
5671 (*notrealinbox
) = 0;
5673 tc
= ps_global
->context_list
;
5676 strncpy(newfolder
, last_folder
, sizeof(newfolder
));
5677 newfolder
[sizeof(newfolder
)-1] = '\0';
5678 save_hist(history
, newfolder
, 0, tc
);
5682 /* fall thru like they cancelled */
5684 case 1 : /* o_e says user cancel */
5685 cmd_cancelled("Open folder");
5688 case 2 : /* o_e says user wants list */
5689 r
= display_folder_list(&tc
, newfolder
, 0, folders_for_goto
);
5695 case 3 : /* o_e says user wants help */
5696 help
= help
== NO_HELP
? h_oe_broach
: NO_HELP
;
5699 case 4 : /* redraw */
5702 case 10 : /* Previous collection */
5703 tc2
= ps_global
->context_list
;
5704 while(tc2
->next
&& tc2
->next
!= tc
)
5710 case 11 : /* Next collection */
5711 tc
= (tc
->next
) ? tc
->next
: ps_global
->context_list
;
5714 case 12 : /* file name completion */
5715 if(!folder_complete(tc
, newfolder
, sizeof(newfolder
), &n
)){
5716 if(n
&& last_rc
== 12 && !(flags
& OE_USER_MODIFIED
)){
5717 r
= display_folder_list(&tc
, newfolder
, 1,folders_for_goto
);
5719 done
++; /* bingo! */
5721 rc
= 0; /* burn last_rc */
5729 case 14 : /* file name completion */
5730 r
= display_folder_list(&tc
, newfolder
, 2, folders_for_goto
);
5732 done
++; /* bingo! */
5734 rc
= 0; /* burn last_rc */
5738 case 17 : /* GoTo INBOX */
5740 strncpy(newfolder
, ps_global
->inbox_name
, sizeof(newfolder
)-1);
5741 newfolder
[sizeof(newfolder
)-1] = '\0';
5743 (*notrealinbox
) = 0;
5745 tc
= ps_global
->context_list
;
5746 save_hist(history
, newfolder
, 0, tc
);
5751 if((p
= get_prev_hist(history
, newfolder
, 0, tc
)) != NULL
){
5752 strncpy(newfolder
, p
, sizeof(newfolder
));
5753 newfolder
[sizeof(newfolder
)-1] = '\0';
5754 if(history
->hist
[history
->curindex
])
5755 tc
= history
->hist
[history
->curindex
]->cntxt
;
5763 if((p
= get_next_hist(history
, newfolder
, 0, tc
)) != NULL
){
5764 strncpy(newfolder
, p
, sizeof(newfolder
));
5765 newfolder
[sizeof(newfolder
)-1] = '\0';
5766 if(history
->hist
[history
->curindex
])
5767 tc
= history
->hist
[history
->curindex
]->cntxt
;
5775 panic("Unhandled case");
5782 dprint((2, "broach folder, name entered \"%s\"\n",
5783 newfolder
? newfolder
: "?"));
5785 /*-- Just check that we can expand this. It gets done for real later --*/
5786 strncpy(expanded
, newfolder
, sizeof(expanded
));
5787 expanded
[sizeof(expanded
)-1] = '\0';
5789 if(!expand_foldername(expanded
, sizeof(expanded
))) {
5790 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
5791 expanded
? expanded
: "?"));
5800 /*----------------------------------------------------------------------
5801 Check to see if user wants to reopen dead stream.
5806 Result: 1 if the folder was successfully updatedn
5811 ask_mailbox_reopen(struct pine
*ps
, int *reopenp
)
5813 if(((ps
->mail_stream
->dtb
5814 && ((ps
->mail_stream
->dtb
->flags
& DR_NONEWMAIL
)
5815 || (ps
->mail_stream
->rdonly
5816 && ps
->mail_stream
->dtb
->flags
& DR_NONEWMAILRONLY
)))
5817 && (ps
->reopen_rule
== REOPEN_ASK_ASK_Y
5818 || ps
->reopen_rule
== REOPEN_ASK_ASK_N
5819 || ps
->reopen_rule
== REOPEN_ASK_NO_Y
5820 || ps
->reopen_rule
== REOPEN_ASK_NO_N
))
5821 || ((ps
->mail_stream
->dtb
5822 && ps
->mail_stream
->rdonly
5823 && !(ps
->mail_stream
->dtb
->flags
& DR_LOCAL
))
5824 && (ps
->reopen_rule
== REOPEN_YES_ASK_Y
5825 || ps
->reopen_rule
== REOPEN_YES_ASK_N
5826 || ps
->reopen_rule
== REOPEN_ASK_ASK_Y
5827 || ps
->reopen_rule
== REOPEN_ASK_ASK_N
))){
5830 switch(ps
->reopen_rule
){
5831 case REOPEN_YES_ASK_Y
:
5832 case REOPEN_ASK_ASK_Y
:
5833 case REOPEN_ASK_NO_Y
:
5842 switch(want_to("Re-open folder to check for new messages", deefault
,
5843 'x', h_reopen_folder
, WT_NORM
)){
5858 /*----------------------------------------------------------------------
5859 Check to see if user input is in form of old c-client mailbox speck
5864 Result: 1 if the folder was successfully updatedn
5869 update_folder_spec(char *new, size_t newlen
, char *old
)
5875 if(*(p
= old
) == '*') /* old form? */
5878 if(*old
== '{') /* copy host spec */
5880 switch(*new = *old
++){
5885 if(!struncmp(old
, "nntp", 4))
5893 while(*new++ != '}' && (new-orignew
) < newlen
-1);
5895 if((*p
== '*' && *old
) || ((*old
== '*') ? *++old
: 0)){
5897 * OK, some heuristics here. If it looks like a newsgroup
5898 * then we plunk it into the #news namespace else we
5899 * assume that they're trying to get at a #public folder...
5902 *p
&& (isalnum((unsigned char) *p
) || strindex(".-", *p
));
5906 sstrncpy(&new, (*p
&& !nntp
) ? "#public/" : "#news.", newlen
-(new-orignew
));
5907 strncpy(new, old
, newlen
-(new-orignew
));
5911 orignew
[newlen
-1] = '\0';
5917 /*----------------------------------------------------------------------
5918 Open the requested folder in the requested context
5920 Args: state -- usual pine state struct
5921 newfolder -- folder to open
5922 new_context -- folder context might live in
5923 stream -- candidate for recycling
5925 Result: New folder open or not (if error), and we're set to
5926 enter the index screen.
5929 visit_folder(struct pine
*state
, char *newfolder
, CONTEXT_S
*new_context
,
5930 MAILSTREAM
*stream
, long unsigned int flags
)
5932 dprint((9, "visit_folder(%s, %s)\n",
5933 newfolder
? newfolder
: "?",
5934 (new_context
&& new_context
->context
)
5935 ? new_context
->context
: "(NULL)"));
5937 if(ps_global
&& ps_global
->ttyo
){
5938 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
5939 ps_global
->mangled_footer
= 1;
5942 if(do_broach_folder(newfolder
, new_context
, stream
? &stream
: NULL
,
5944 || !sp_flagged(state
->mail_stream
, SP_LOCKED
))
5945 state
->next_screen
= mail_index_screen
;
5947 state
->next_screen
= folder_screen
;
5951 /*----------------------------------------------------------------------
5952 Move read messages from folder if listed in archive
5958 read_msg_prompt(long int n
, char *f
)
5960 char buf
[MAX_SCREEN_COLS
+1];
5962 snprintf(buf
, sizeof(buf
), "Save the %ld read message%s in \"%s\"", n
, plural(n
), f
);
5963 buf
[sizeof(buf
)-1] = '\0';
5964 return(want_to(buf
, 'y', 0, NO_HELP
, WT_NORM
) == 'y');
5968 /*----------------------------------------------------------------------
5969 Print current message[s] or folder index
5971 Args: state -- pointer to struct holding a bunch of pine state
5972 msgmap -- table mapping msg nums to c-client sequence nums
5973 aopt -- aggregate options
5974 in_index -- boolean indicating we're called from Index Screen
5976 Filters the original header and sends stuff to printer
5979 cmd_print(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, CmdWhere in_index
)
5982 long i
, msgs
, rawno
;
5983 int next
= 0, do_index
= 0, rv
= 0;
5988 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
5991 msgs
= mn_total_cur(msgmap
);
5993 if((in_index
!= View
) && F_ON(F_PRINT_INDEX
, state
)){
5996 static ESCKEY_S prt_opts
[] = {
5997 {'i', 'i', "I", N_("Index")},
5998 {'m', 'm', "M", NULL
},
5999 {-1, 0, NULL
, NULL
}};
6001 if(in_index
== ThrdIndx
){
6002 /* TRANSLATORS: This is a question, Print Index ? */
6003 if(want_to(_("Print Index"), 'y', 'x', NO_HELP
, WT_NORM
) == 'y')
6009 snprintf(m
, sizeof(m
), "Message%s", (msgs
>1L) ? "s" : "");
6010 m
[sizeof(m
)-1] = '\0';
6011 prt_opts
[1].label
= m
;
6012 snprintf(prompt
, sizeof(prompt
), "Print %sFolder Index or %s %s? ",
6013 (aopt
& MCMD_AGG_2
) ? "thread " : MCMD_ISAGG(aopt
) ? "selected " : "",
6014 (aopt
& MCMD_AGG_2
) ? "thread" : MCMD_ISAGG(aopt
) ? "selected" : "current", m
);
6015 prompt
[sizeof(prompt
)-1] = '\0';
6017 ans
= radio_buttons(prompt
, -FOOTER_ROWS(state
), prt_opts
, 'm', 'x',
6018 NO_HELP
, RB_NORM
|RB_SEQ_SENSITIVE
);
6023 cmd_cancelled("Print");
6024 if(MCMD_ISAGG(aopt
))
6025 restore_selected(msgmap
);
6040 snprintf(prompt
, sizeof(prompt
), "%sFolder Index",
6041 (aopt
& MCMD_AGG_2
) ? "Thread " : MCMD_ISAGG(aopt
) ? "Selected " : "");
6043 snprintf(prompt
, sizeof(prompt
), "%s messages", long2string(msgs
));
6045 snprintf(prompt
, sizeof(prompt
), "Message %s", long2string(mn_get_cur(msgmap
)));
6047 prompt
[sizeof(prompt
)-1] = '\0';
6049 if(open_printer(prompt
) < 0){
6050 if(MCMD_ISAGG(aopt
))
6051 restore_selected(msgmap
);
6059 tc
= format_titlebar();
6061 /* Print titlebar... */
6062 print_text1("%s\n\n", tc
? tc
->titlebar_line
: "");
6063 /* then all the index members... */
6064 if(!print_index(state
, msgmap
, MCMD_ISAGG(aopt
)))
6065 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6066 _("Error printing folder index"));
6072 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
), next
++){
6073 if(next
&& F_ON(F_AGG_PRINT_FF
, state
))
6074 if(!print_char(FORMFEED
)){
6079 if(!(state
->mail_stream
6080 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
6081 && rawno
<= state
->mail_stream
->nmsgs
6082 && (mc
= mail_elt(state
->mail_stream
, rawno
))
6086 if(!(e
=pine_mail_fetchstructure(state
->mail_stream
,
6089 || (F_ON(F_FROM_DELIM_IN_PRINT
, ps_global
)
6090 && !bezerk_delimiter(e
, mc
, print_char
, next
))
6091 || !format_message(mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
6092 e
, b
, NULL
, FM_NEW_MESS
| FM_NOINDENT
,
6094 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6095 _("Error printing message"));
6104 if(MCMD_ISAGG(aopt
))
6105 restore_selected(msgmap
);
6111 /*----------------------------------------------------------------------
6114 Args: state -- various pine state bits
6115 msgmap -- Message number mapping table
6116 aopt -- option flags
6118 Filters the original header and sends stuff to specified command
6121 cmd_pipe(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
6127 char *resultfilename
= NULL
, prompt
[80], *p
;
6128 int done
= 0, rv
= 0;
6130 int fourlabel
= -1, j
= 0, next
= 0, ku
;
6131 int pipe_rv
; /* rv of proc to separate from close_system_pipe rv */
6133 unsigned flagsforhist
= 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6134 static HISTORY_S
*history
= NULL
;
6135 int capture
= 1, raw
= 0, delimit
= 0, newpipe
= 0;
6136 char pipe_command
[MAXPATH
];
6137 ESCKEY_S pipe_opt
[8];
6139 if(ps_global
->restricted
){
6140 q_status_message(SM_ORDER
| SM_DING
, 0, 4,
6141 "Alpine demo can't pipe messages");
6144 else if(!any_messages(msgmap
, NULL
, "to Pipe"))
6147 pipe_command
[0] = '\0';
6148 init_hist(&history
, HISTSIZE
);
6149 flagsforhist
= (raw
? 0x8 : 0) +
6150 (delimit
? 0x4 : 0) +
6151 (newpipe
? 0x2 : 0) +
6152 (capture
? 0x1 : 0);
6153 if((p
= get_prev_hist(history
, "", flagsforhist
, NULL
)) != NULL
){
6154 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6155 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6156 if(history
->hist
[history
->curindex
]){
6157 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6158 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6159 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6160 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6161 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6166 pipe_opt
[j
].rval
= 0;
6167 pipe_opt
[j
].name
= "";
6168 pipe_opt
[j
++].label
= "";
6170 pipe_opt
[j
].ch
= ctrl('W');
6171 pipe_opt
[j
].rval
= 10;
6172 pipe_opt
[j
].name
= "^W";
6173 pipe_opt
[j
++].label
= NULL
;
6175 pipe_opt
[j
].ch
= ctrl('Y');
6176 pipe_opt
[j
].rval
= 11;
6177 pipe_opt
[j
].name
= "^Y";
6178 pipe_opt
[j
++].label
= NULL
;
6180 pipe_opt
[j
].ch
= ctrl('R');
6181 pipe_opt
[j
].rval
= 12;
6182 pipe_opt
[j
].name
= "^R";
6183 pipe_opt
[j
++].label
= NULL
;
6185 if(MCMD_ISAGG(aopt
)){
6186 if(!pseudo_selected(state
->mail_stream
, msgmap
))
6190 pipe_opt
[j
].ch
= ctrl('T');
6191 pipe_opt
[j
].rval
= 13;
6192 pipe_opt
[j
].name
= "^T";
6193 pipe_opt
[j
++].label
= NULL
;
6197 pipe_opt
[j
].ch
= KEY_UP
;
6198 pipe_opt
[j
].rval
= 30;
6199 pipe_opt
[j
].name
= "";
6201 pipe_opt
[j
++].label
= "";
6203 pipe_opt
[j
].ch
= KEY_DOWN
;
6204 pipe_opt
[j
].rval
= 31;
6205 pipe_opt
[j
].name
= "";
6206 pipe_opt
[j
++].label
= "";
6208 pipe_opt
[j
].ch
= -1;
6213 snprintf(prompt
, sizeof(prompt
), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6215 MCMD_ISAGG(aopt
) ? "s" : " ",
6216 MCMD_ISAGG(aopt
) ? "" : comatose(mn_get_cur(msgmap
)),
6217 (!capture
|| delimit
|| (newpipe
&& MCMD_ISAGG(aopt
))) ? "(" : "",
6218 capture
? "" : "uncaptured",
6219 (!capture
&& delimit
) ? "," : "",
6220 delimit
? "delimited" : "",
6221 ((!capture
|| delimit
) && newpipe
&& MCMD_ISAGG(aopt
)) ? "," : "",
6222 (newpipe
&& MCMD_ISAGG(aopt
)) ? "new pipe" : "",
6223 (!capture
|| delimit
|| (newpipe
&& MCMD_ISAGG(aopt
))) ? ") " : "");
6224 prompt
[sizeof(prompt
)-1] = '\0';
6225 pipe_opt
[1].label
= raw
? N_("Shown Text") : N_("Raw Text");
6226 pipe_opt
[2].label
= capture
? N_("Free Output") : N_("Capture Output");
6227 pipe_opt
[3].label
= delimit
? N_("No Delimiter") : N_("With Delimiter");
6229 pipe_opt
[fourlabel
].label
= newpipe
? N_("To Same Pipe") : N_("To Individual Pipes");
6233 * 2 is really 1 because there will be one real entry and
6234 * one entry of "" because of the get_prev_hist above.
6236 if(items_in_hist(history
) > 2){
6237 pipe_opt
[ku
].name
= HISTORY_UP_KEYNAME
;
6238 pipe_opt
[ku
].label
= HISTORY_KEYLABEL
;
6239 pipe_opt
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
6240 pipe_opt
[ku
+1].label
= HISTORY_KEYLABEL
;
6243 pipe_opt
[ku
].name
= "";
6244 pipe_opt
[ku
].label
= "";
6245 pipe_opt
[ku
+1].name
= "";
6246 pipe_opt
[ku
+1].label
= "";
6249 flags
= OE_APPEND_CURRENT
| OE_SEQ_SENSITIVE
;
6250 switch(optionally_enter(pipe_command
, -FOOTER_ROWS(state
), 0,
6251 sizeof(pipe_command
), prompt
,
6252 pipe_opt
, NO_HELP
, &flags
)){
6254 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
6255 _("Internal problem encountered"));
6259 case 10 : /* flip raw bit */
6263 case 11 : /* flip capture bit */
6267 case 12 : /* flip delimit bit */
6271 case 13 : /* flip newpipe bit */
6276 flagsforhist
= (raw
? 0x8 : 0) +
6277 (delimit
? 0x4 : 0) +
6278 (newpipe
? 0x2 : 0) +
6279 (capture
? 0x1 : 0);
6280 if((p
= get_prev_hist(history
, pipe_command
, flagsforhist
, NULL
)) != NULL
){
6281 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6282 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6283 if(history
->hist
[history
->curindex
]){
6284 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6285 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6286 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6287 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6288 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6297 flagsforhist
= (raw
? 0x8 : 0) +
6298 (delimit
? 0x4 : 0) +
6299 (newpipe
? 0x2 : 0) +
6300 (capture
? 0x1 : 0);
6301 if((p
= get_next_hist(history
, pipe_command
, flagsforhist
, NULL
)) != NULL
){
6302 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6303 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6304 if(history
->hist
[history
->curindex
]){
6305 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6306 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6307 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6308 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6309 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6318 if(pipe_command
[0]){
6320 flagsforhist
= (raw
? 0x8 : 0) +
6321 (delimit
? 0x4 : 0) +
6322 (newpipe
? 0x2 : 0) +
6323 (capture
? 0x1 : 0);
6324 save_hist(history
, pipe_command
, flagsforhist
, NULL
);
6326 flags
= PIPE_USER
| PIPE_WRITE
| PIPE_STDERR
;
6327 flags
|= (raw
? PIPE_RAW
: 0);
6333 ps_global
->mangled_screen
= 1;
6334 ps_global
->in_init_seq
= 1;
6336 flags
|= PIPE_RESET
;
6339 if(!newpipe
&& !(syspipe
= cmd_pipe_open(pipe_command
,
6340 (flags
& PIPE_RESET
)
6346 for(i
= mn_first_cur(msgmap
);
6348 i
= mn_next_cur(msgmap
)){
6349 e
= pine_mail_fetchstructure(ps_global
->mail_stream
,
6350 mn_m2raw(msgmap
, i
), &b
);
6351 if(!(state
->mail_stream
6352 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
6353 && rawno
<= state
->mail_stream
->nmsgs
6354 && (mc
= mail_elt(state
->mail_stream
, rawno
))
6359 && !(syspipe
= cmd_pipe_open(pipe_command
,
6360 (flags
& PIPE_RESET
)
6364 || (delimit
&& !bezerk_delimiter(e
, mc
, pc
, next
++)))
6371 prime_raw_pipe_getc(ps_global
->mail_stream
,
6372 mn_m2raw(msgmap
, i
), -1L, 0L);
6374 gf_link_filter(gf_nvtnl_local
, NULL
);
6375 if((pipe_err
= gf_pipe(raw_pipe_getc
, pc
)) != NULL
){
6376 q_status_message1(SM_ORDER
|SM_DING
,
6378 _("Internal Error: %s"),
6383 else if(!format_message(mn_m2raw(msgmap
, i
), e
, b
,
6384 NULL
, FM_NEW_MESS
| FM_NOWRAP
, pc
))
6389 if(close_system_pipe(&syspipe
, &pipe_rv
, pipe_callback
) == -1)
6394 ps_global
->in_init_seq
= 0;
6397 if(close_system_pipe(&syspipe
, &pipe_rv
, pipe_callback
) == -1)
6399 if(done
) /* say we had a problem */
6400 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6401 _("Error piping message"));
6402 else if(resultfilename
){
6404 /* only display if no error */
6405 display_output_file(resultfilename
, "PIPE MESSAGE",
6407 fs_give((void **)&resultfilename
);
6411 q_status_message(SM_ORDER
, 0, 2, _("Pipe command completed"));
6417 /* else fall thru as if cancelled */
6420 cmd_cancelled("Pipe command");
6425 helper(h_common_pipe
, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE
);
6426 ps_global
->mangled_screen
= 1;
6429 case 2 : /* no place to escape to */
6430 case 4 : /* can't suspend */
6436 ps_global
->mangled_footer
= 1;
6437 if(MCMD_ISAGG(aopt
))
6438 restore_selected(msgmap
);
6444 /*----------------------------------------------------------------------
6445 Screen to offer list management commands contained in message
6447 Args: state -- pointer to struct holding a bunch of pine state
6448 msgmap -- table mapping msg nums to c-client sequence nums
6449 aopt -- aggregate options
6453 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6456 rfc2369_display(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
)
6459 char *h
, *hdrs
[MLCMD_COUNT
+ 1];
6460 long index_no
= mn_raw2m(msgmap
, msgno
);
6461 RFC2369_S data
[MLCMD_COUNT
];
6463 /* for each header field */
6464 if((h
= pine_fetchheader_lines(stream
, msgno
, NULL
, rfc2369_hdrs(hdrs
))) != NULL
){
6465 memset(&data
[0], 0, sizeof(RFC2369_S
) * MLCMD_COUNT
);
6466 if(rfc2369_parse_fields(h
, &data
[0])){
6469 if((explain
= list_mgmt_text(data
, index_no
)) != NULL
){
6470 list_mgmt_screen(explain
);
6471 ps_global
->mangled_screen
= 1;
6477 fs_give((void **) &h
);
6481 q_status_message1(SM_ORDER
, 0, 3,
6482 "Message %s contains no list management information",
6483 comatose(index_no
));
6488 list_mgmt_text(RFC2369_S
*data
, long int msgno
)
6491 int i
, j
, n
, fields
= 0;
6492 static char *rfc2369_intro1
=
6493 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6494 static char *rfc2369_intro2
[] = {
6495 N_(" has information associated with it "),
6496 N_("that explains how to participate in an email list. An "),
6497 N_("email list is represented by a single email address that "),
6498 N_("users sharing a common interest can send messages to (known "),
6499 N_("as posting) which are then redistributed to all members "),
6500 N_("of the list (sometimes after review by a moderator)."),
6501 N_("<P>List participation commands in this message include:"),
6505 if((store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) != NULL
){
6507 /* Insert introductory text */
6508 so_puts(store
, rfc2369_intro1
);
6510 so_puts(store
, comatose(msgno
));
6512 for(i
= 0; rfc2369_intro2
[i
]; i
++)
6513 so_puts(store
, _(rfc2369_intro2
[i
]));
6515 so_puts(store
, "<P>");
6516 for(i
= 0; i
< MLCMD_COUNT
; i
++)
6517 if(data
[i
].data
[0].value
6518 || data
[i
].data
[0].comment
6519 || data
[i
].data
[0].error
){
6521 so_puts(store
, "<UL>");
6523 so_puts(store
, "<LI>");
6525 (n
= (data
[i
].data
[1].value
|| data
[i
].data
[1].comment
))
6529 so_puts(store
, data
[i
].field
.description
);
6530 so_puts(store
, ". ");
6533 so_puts(store
, "<OL>");
6537 && (data
[i
].data
[j
].comment
6538 || data
[i
].data
[j
].value
6539 || data
[i
].data
[j
].error
);
6542 so_puts(store
, n
? "<P><LI>" : "<P>");
6544 if(data
[i
].data
[j
].comment
){
6546 _("With the provided comment:<P><BLOCKQUOTE>"));
6547 so_puts(store
, data
[i
].data
[j
].comment
);
6548 so_puts(store
, "</BLOCKQUOTE><P>");
6551 if(data
[i
].data
[j
].value
){
6553 && !strucmp(data
[i
].data
[j
].value
, "NO")){
6555 _("Posting is <EM>not</EM> allowed on this list"));
6558 so_puts(store
, "Select <A HREF=\"");
6559 so_puts(store
, data
[i
].data
[j
].value
);
6560 so_puts(store
, "\">HERE</A> to ");
6561 so_puts(store
, (data
[i
].field
.action
)
6562 ? data
[i
].field
.action
6566 so_puts(store
, ".");
6569 if(data
[i
].data
[j
].error
){
6570 so_puts(store
, "<P>Unfortunately, Alpine can not offer");
6571 so_puts(store
, " to take direct action based upon it");
6572 so_puts(store
, " because it was improperly formatted.");
6573 so_puts(store
, " The unrecognized data associated with");
6574 so_puts(store
, " the \"");
6575 so_puts(store
, data
[i
].field
.name
);
6576 so_puts(store
, "\" header field was:<P><BLOCKQUOTE>");
6577 so_puts(store
, data
[i
].data
[j
].error
);
6578 so_puts(store
, "</BLOCKQUOTE>");
6581 so_puts(store
, "<P>");
6585 so_puts(store
, "</OL>");
6589 so_puts(store
, "</UL>");
6591 so_puts(store
, "</BODY></HTML>");
6599 list_mgmt_screen(STORE_S
*html
)
6605 HANDLE_S
*handles
= NULL
;
6609 so_seek(html
, 0L, 0);
6610 gf_set_so_readc(&gc
, html
);
6612 init_handles(&handles
);
6614 if((store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) != NULL
){
6615 gf_set_so_writec(&pc
, store
);
6618 gf_link_filter(gf_html2plain
,
6619 gf_html2plain_opt(NULL
, ps_global
->ttyo
->screen_cols
,
6620 non_messageview_margin(), &handles
, NULL
, 0));
6622 error
= gf_pipe(gc
, pc
);
6624 gf_clear_so_writec(store
);
6629 memset(&sargs
, 0, sizeof(SCROLL_S
));
6630 sargs
.text
.text
= so_text(store
);
6631 sargs
.text
.src
= CharStar
;
6632 sargs
.text
.desc
= "list commands";
6633 sargs
.text
.handles
= handles
;
6635 sargs
.start
.on
= Offset
;
6636 sargs
.start
.loc
.offset
= offset
;
6639 sargs
.bar
.title
= _("MAIL LIST COMMANDS");
6640 sargs
.bar
.style
= MessageNumber
;
6641 sargs
.resize_exit
= 1;
6642 sargs
.help
.text
= h_special_list_commands
;
6643 sargs
.help
.title
= _("HELP FOR LIST COMMANDS");
6644 sargs
.keys
.menu
= &listmgr_keymenu
;
6645 setbitmap(sargs
.keys
.bitmap
);
6647 clrbitn(LM_TRY_KEY
, sargs
.keys
.bitmap
);
6648 clrbitn(LM_PREV_KEY
, sargs
.keys
.bitmap
);
6649 clrbitn(LM_NEXT_KEY
, sargs
.keys
.bitmap
);
6652 cmd
= scrolltool(&sargs
);
6653 offset
= sargs
.start
.loc
.offset
;
6659 free_handles(&handles
);
6660 gf_clear_so_readc(html
);
6662 while(cmd
== MC_RESIZE
);
6666 /*----------------------------------------------------------------------
6667 Prompt the user for the type of select desired
6669 NOTE: any and all functions that successfully exit the second
6670 switch() statement below (currently "select_*() functions"),
6671 *MUST* update the folder's MESSAGECACHE element's "searched"
6672 bits to reflect the search result. Functions using
6673 mail_search() get this for free, the others must update 'em
6676 Returns -1 if canceled without changing selection
6677 0 if selection may have changed
6680 aggregate_select(struct pine
*state
, MSGNO_S
*msgmap
, int q_line
, CmdWhere in_index
)
6682 long i
, diff
, old_tot
, msgno
, raw
;
6683 int q
= 0, rv
= 0, narrow
= 0, hidden
, ret
= -1;
6686 SEARCHSET
*limitsrch
= NULL
;
6688 extern MAILSTREAM
*mm_search_stream
;
6689 extern long mm_search_count
;
6691 hidden
= any_lflagged(msgmap
, MN_HIDE
) > 0L;
6692 mm_search_stream
= state
->mail_stream
;
6693 mm_search_count
= 0L;
6695 sel_opts
= THRD_INDX() ? sel_opts4
: sel_opts2
;
6697 sel_opts
[SEL_OPTS_THREAD
].ch
= SEL_OPTS_THREAD_CH
;
6700 sel_opts
[SEL_OPTS_THREAD
].ch
= -1;
6703 if((old_tot
= any_lflagged(msgmap
, MN_SLCT
)) != 0){
6706 thrd
= fetch_thread(state
->mail_stream
,
6707 mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
6708 /* check if whole thread is selected or not */
6710 count_lflags_in_thread(state
->mail_stream
,thrd
,msgmap
,MN_SLCT
)
6712 count_lflags_in_thread(state
->mail_stream
,thrd
,msgmap
,MN_NONE
))
6715 sel_opts1
[1].label
= i
? N_("unselect Curthrd") : N_("select Curthrd");
6718 i
= get_lflag(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
6720 sel_opts1
[1].label
= i
? N_("unselect Cur") : N_("select Cur");
6723 sel_opts
+= 2; /* disable extra options */
6724 switch(q
= radio_buttons(_(sel_pmt1
), q_line
, sel_opts1
, 'c', 'x', NO_HELP
,
6726 case 'f' : /* flip selection */
6728 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++){
6730 q
= !get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
);
6731 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, q
);
6733 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, !q
);
6735 mn_reset_cur(msgmap
, msgno
= i
);
6741 case 'n' : /* narrow selection */
6743 case 'b' : /* broaden selection */
6744 q
= 0; /* offer criteria prompt */
6747 case 'c' : /* Un/Select Current */
6748 case 'a' : /* Unselect All */
6749 case 'x' : /* cancel */
6753 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6754 "Unsupported Select option");
6761 q
= radio_buttons(sel_pmt2
, q_line
, sel_opts
, 'c', 'x',
6762 NO_HELP
, RB_NORM
|RB_RET_HELP
);
6765 helper(h_index_cmd_select
, _("HELP FOR SELECT"), HLPD_SIMPLE
);
6766 ps_global
->mangled_screen
= 1;
6774 * The purpose of this is to add the appropriate searchset to the
6775 * search so that the search can be limited to only looking at what
6776 * it needs to look at. That is, if we are narrowing then we only need
6777 * to look at messages which are already selected, and if we are
6778 * broadening, then we only need to look at messages which are not
6779 * yet selected. This routine will work whether or not
6780 * limiting_searchset properly limits the search set. In particular,
6781 * the searchset returned by limiting_searchset may include messages
6782 * which really shouldn't be included. We do that because a too-large
6783 * searchset will break some IMAP servers. It is even possible that it
6784 * becomes inefficient to send the whole set. If the select function
6785 * frees limitsrch, it should be sure to set it to NULL so we won't
6786 * try freeing it again here.
6788 limitsrch
= limiting_searchset(state
->mail_stream
, narrow
);
6791 * NOTE: See note about MESSAGECACHE "searched" bits above!
6794 case 'x': /* cancel */
6795 cmd_cancelled("Select command");
6798 case 'c' : /* select/unselect current */
6799 (void) select_by_current(state
, msgmap
, in_index
);
6803 case 'a' : /* select/unselect all */
6804 msgno
= any_lflagged(msgmap
, MN_SLCT
);
6805 diff
= (!msgno
) ? mn_get_total(msgmap
) : 0L;
6807 agg_select_all(state
->mail_stream
, msgmap
, &diff
,
6808 any_lflagged(msgmap
, MN_SLCT
) <= 0L);
6809 q_status_message4(SM_ORDER
,0,2,
6810 "%s%s message%s %sselected",
6811 msgno
? "" : "All ", comatose(diff
),
6812 plural(diff
), msgno
? "UN" : "");
6815 case 'n' : /* Select by Number */
6818 rv
= select_by_thrd_number(state
->mail_stream
, msgmap
, &limitsrch
);
6820 rv
= select_by_number(state
->mail_stream
, msgmap
, &limitsrch
);
6824 case 'd' : /* Select by Date */
6826 rv
= select_by_date(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
6830 case 't' : /* Text */
6832 rv
= select_by_text(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
6836 case 'z' : /* Size */
6838 rv
= select_by_size(state
->mail_stream
, &limitsrch
);
6841 case 's' : /* Status */
6843 rv
= select_by_status(state
->mail_stream
, &limitsrch
);
6846 case 'k' : /* Keyword */
6848 rv
= select_by_keyword(state
->mail_stream
, &limitsrch
);
6851 case 'r' : /* Rule */
6853 rv
= select_by_rule(state
->mail_stream
, &limitsrch
);
6856 case 'h' : /* Thread */
6858 rv
= select_by_thread(state
->mail_stream
, msgmap
, &limitsrch
);
6862 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6863 "Unsupported Select option");
6868 mail_free_searchset(&limitsrch
);
6870 if(rv
) /* bad return value.. */
6871 return(ret
); /* error already displayed */
6873 if(narrow
) /* make sure something was selected */
6874 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++)
6875 if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
6876 && raw
<= state
->mail_stream
->nmsgs
6877 && (mc
= mail_elt(state
->mail_stream
, raw
)) && mc
->searched
){
6878 if(get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
))
6885 if(mm_search_count
){
6887 * loop thru all the messages, adjusting local flag bits
6888 * based on their "searched" bit...
6890 for(i
= 1L, msgno
= 0L; i
<= mn_get_total(msgmap
); i
++)
6892 /* turning OFF selectedness if the "searched" bit isn't lit. */
6893 if(get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
)){
6894 if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
6895 && raw
<= state
->mail_stream
->nmsgs
6896 && (mc
= mail_elt(state
->mail_stream
, raw
))
6899 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, 0);
6901 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, 1);
6903 /* adjust current message in case we unselect and hide it */
6904 else if(msgno
< mn_get_cur(msgmap
)
6906 || !get_lflag(state
->mail_stream
, msgmap
,
6911 else if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
6912 && raw
<= state
->mail_stream
->nmsgs
6913 && (mc
= mail_elt(state
->mail_stream
, raw
)) && mc
->searched
){
6914 /* turn ON selectedness if "searched" bit is lit. */
6915 if(!get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
)){
6917 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, 1);
6919 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, 0);
6923 /* if we're zoomed and the current message was unselected */
6925 && get_lflag(state
->mail_stream
,msgmap
,mn_get_cur(msgmap
),MN_HIDE
))
6926 mn_reset_cur(msgmap
, msgno
);
6931 q_status_message4(SM_ORDER
, 3, 3,
6932 "%s. %s message%s remain%s selected.",
6934 ? "No change resulted"
6935 : "No messages in intersection",
6936 comatose(old_tot
), plural(old_tot
),
6937 (old_tot
== 1L) ? "s" : "");
6939 q_status_message(SM_ORDER
, 3, 3,
6940 _("No change resulted. Matching messages already selected."));
6942 q_status_message1(SM_ORDER
| SM_DING
, 3, 3,
6943 _("Select failed. No %smessages selected."),
6944 old_tot
? _("additional ") : "");
6947 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
6948 "Select matched %ld message%s. %s %smessage%s %sselected.",
6949 (diff
> 0) ? diff
: old_tot
+ diff
,
6950 plural((diff
> 0) ? diff
: old_tot
+ diff
),
6951 comatose((diff
> 0) ? any_lflagged(msgmap
, MN_SLCT
) : -diff
),
6952 (diff
> 0) ? "total " : "",
6953 plural((diff
> 0) ? any_lflagged(msgmap
, MN_SLCT
) : -diff
),
6954 (diff
> 0) ? "" : "UN");
6955 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
6956 q_status_message(SM_ORDER
, 3, 3, tmp_20k_buf
);
6959 q_status_message2(SM_ORDER
, 3, 3, _("Select matched %s message%s!"),
6960 comatose(diff
), plural(diff
));
6966 /*----------------------------------------------------------------------
6967 Toggle the state of the current message
6969 Args: state -- pointer pine's state variables
6970 msgmap -- message collection to operate on
6971 in_index -- in the message index view
6972 Returns: TRUE if current marked selected, FALSE otw
6975 select_by_current(struct pine
*state
, MSGNO_S
*msgmap
, CmdWhere in_index
)
6978 int all_selected
= 0;
6979 unsigned long was
, tot
, rawno
;
6982 cur
= mn_get_cur(msgmap
);
6985 thrd
= fetch_thread(state
->mail_stream
, mn_m2raw(msgmap
, cur
));
6989 was
= count_lflags_in_thread(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
);
6990 tot
= count_lflags_in_thread(state
->mail_stream
, thrd
, msgmap
, MN_NONE
);
6995 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
, 0);
6996 if(any_lflagged(msgmap
, MN_HIDE
) > 0L){
6997 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_HIDE
, 1);
6999 * See if there's anything left to zoom on. If so,
7000 * pick an adjacent one for highlighting, else make
7001 * sure nothing is left hidden...
7003 if(any_lflagged(msgmap
, MN_SLCT
)){
7004 mn_inc_cur(state
->mail_stream
, msgmap
,
7005 (in_index
== View
&& THREADING()
7006 && sp_viewing_a_thread(state
->mail_stream
))
7008 : (in_index
== View
)
7009 ? MH_ANYTHD
: MH_NONE
);
7010 if(mn_get_cur(msgmap
) == cur
)
7011 mn_dec_cur(state
->mail_stream
, msgmap
,
7012 (in_index
== View
&& THREADING()
7013 && sp_viewing_a_thread(state
->mail_stream
))
7015 : (in_index
== View
)
7016 ? MH_ANYTHD
: MH_NONE
);
7018 else /* clear all hidden flags */
7019 (void) unzoom_index(state
, state
->mail_stream
, msgmap
);
7023 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
, 1);
7025 q_status_message3(SM_ORDER
, 0, 2, "%s message%s %sselected",
7026 comatose(all_selected
? was
: tot
-was
),
7027 plural(all_selected
? was
: tot
-was
),
7028 all_selected
? "UN" : "");
7030 /* collapsed thread */
7032 && ((rawno
= mn_m2raw(msgmap
, cur
)) != 0L)
7033 && ((thrd
= fetch_thread(state
->mail_stream
, rawno
)) != NULL
)
7034 && (thrd
&& thrd
->next
&& get_lflag(state
->mail_stream
, NULL
, rawno
, MN_COLL
))){
7036 * This doesn't work quite the same as the colon command works, but
7037 * it is arguably doing the correct thing. The difference is
7038 * that aggregate_select will zoom after selecting back where it
7039 * was called from, but selecting a thread with colon won't zoom.
7040 * Maybe it makes sense to zoom after a select but not after a colon
7041 * command even though they are very similar.
7043 thread_command(state
, state
->mail_stream
, msgmap
, ':', -FOOTER_ROWS(state
));
7047 get_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
)) != 0){ /* set? */
7048 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
, 0);
7049 if(any_lflagged(msgmap
, MN_HIDE
) > 0L){
7050 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_HIDE
, 1);
7052 * See if there's anything left to zoom on. If so,
7053 * pick an adjacent one for highlighting, else make
7054 * sure nothing is left hidden...
7056 if(any_lflagged(msgmap
, MN_SLCT
)){
7057 mn_inc_cur(state
->mail_stream
, msgmap
,
7058 (in_index
== View
&& THREADING()
7059 && sp_viewing_a_thread(state
->mail_stream
))
7061 : (in_index
== View
)
7062 ? MH_ANYTHD
: MH_NONE
);
7063 if(mn_get_cur(msgmap
) == cur
)
7064 mn_dec_cur(state
->mail_stream
, msgmap
,
7065 (in_index
== View
&& THREADING()
7066 && sp_viewing_a_thread(state
->mail_stream
))
7068 : (in_index
== View
)
7069 ? MH_ANYTHD
: MH_NONE
);
7071 else /* clear all hidden flags */
7072 (void) unzoom_index(state
, state
->mail_stream
, msgmap
);
7076 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
, 1);
7078 q_status_message2(SM_ORDER
, 0, 2, "Message %s %sselected",
7079 long2string(cur
), all_selected
? "UN" : "");
7083 return(!all_selected
);
7087 /*----------------------------------------------------------------------
7088 Prompt the user for the command to perform on selected messages
7090 Args: state -- pointer pine's state variables
7091 msgmap -- message collection to operate on
7092 q_line -- line on display to write prompts
7093 Returns: 1 if the selected messages are suitably commanded,
7094 0 if the choice to pick the command was declined
7098 apply_command(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
,
7099 UCS preloadkeystroke
, int flags
, int q_line
)
7101 int i
= 8, /* number of static entries in sel_opts3 */
7105 agg
= (flags
& AC_FROM_THREAD
) ? MCMD_AGG_2
: MCMD_AGG
;
7109 * To do this "right", we really ought to have access to the keymenu
7110 * here and change the typed command into a real command by running
7111 * it through menu_command. Then the switch below would be against
7112 * results from menu_command. If we did that we'd also pass the
7113 * results of menu_command in as preloadkeystroke instead of passing
7114 * the keystroke itself. But we don't have the keymenu handy,
7115 * so we have to fake it. The only complication that we run into
7116 * is that KEY_DEL is an escape sequence so we change a typed
7117 * KEY_DEL esc seq into the letter D.
7120 if(!preloadkeystroke
){
7121 if(F_ON(F_ENABLE_FLAG
,state
)){ /* flag? */
7122 sel_opts3
[i
].ch
= '*';
7123 sel_opts3
[i
].rval
= '*';
7124 sel_opts3
[i
].name
= "*";
7125 sel_opts3
[i
++].label
= N_("Flag");
7128 if(F_ON(F_ENABLE_PIPE
,state
)){ /* pipe? */
7129 sel_opts3
[i
].ch
= '|';
7130 sel_opts3
[i
].rval
= '|';
7131 sel_opts3
[i
].name
= "|";
7132 sel_opts3
[i
++].label
= N_("Pipe");
7135 if(F_ON(F_ENABLE_BOUNCE
,state
)){ /* bounce? */
7136 sel_opts3
[i
].ch
= 'b';
7137 sel_opts3
[i
].rval
= 'b';
7138 sel_opts3
[i
].name
= "B";
7139 sel_opts3
[i
++].label
= N_("Bounce");
7142 if(flags
& AC_FROM_THREAD
){
7143 if(flags
& (AC_COLL
| AC_EXPN
)){
7144 sel_opts3
[i
].ch
= '/';
7145 sel_opts3
[i
].rval
= '/';
7146 sel_opts3
[i
].name
= "/";
7147 sel_opts3
[i
++].label
= (flags
& AC_COLL
) ? N_("Collapse")
7151 sel_opts3
[i
].ch
= ';';
7152 sel_opts3
[i
].rval
= ';';
7153 sel_opts3
[i
].name
= ";";
7154 if(flags
& AC_UNSEL
)
7155 sel_opts3
[i
++].label
= N_("UnSelect");
7157 sel_opts3
[i
++].label
= N_("Select");
7160 if(F_ON(F_ENABLE_PRYNT
, state
)){ /* this one is invisible */
7161 sel_opts3
[i
].ch
= 'y';
7162 sel_opts3
[i
].rval
= '%';
7163 sel_opts3
[i
].name
= "";
7164 sel_opts3
[i
++].label
= "";
7167 if(!is_imap_stream(stream
) || LEVELUIDPLUS(stream
)){ /* expunge selected messages */
7168 sel_opts3
[i
].ch
= 'x';
7169 sel_opts3
[i
].rval
= 'x';
7170 sel_opts3
[i
].name
= "X";
7171 sel_opts3
[i
++].label
= N_("Expunge");
7174 sel_opts3
[i
].ch
= KEY_DEL
; /* also invisible */
7175 sel_opts3
[i
].rval
= 'd';
7176 sel_opts3
[i
].name
= "";
7177 sel_opts3
[i
++].label
= "";
7179 sel_opts3
[i
].ch
= -1;
7181 snprintf(prompt
, sizeof(prompt
), "%s command : ",
7182 (flags
& AC_FROM_THREAD
) ? "THREAD" : "APPLY");
7183 prompt
[sizeof(prompt
)-1] = '\0';
7184 cmd
= double_radio_buttons(prompt
, q_line
, sel_opts3
, 'z', 'c', NO_HELP
,
7190 if(preloadkeystroke
== KEY_DEL
)
7193 if(preloadkeystroke
< 0x80 && isupper((int) preloadkeystroke
))
7194 cmd
= tolower((int) preloadkeystroke
);
7196 cmd
= (int) preloadkeystroke
; /* shouldn't happen */
7201 case 'd' : /* delete */
7202 we_cancel
= busy_cue(NULL
, NULL
, 1);
7203 rv
= cmd_delete(state
, msgmap
, agg
, NULL
); /* don't advance or offer "TAB" */
7208 case 'u' : /* undelete */
7209 we_cancel
= busy_cue(NULL
, NULL
, 1);
7210 rv
= cmd_undelete(state
, msgmap
, agg
);
7215 case 'r' : /* reply */
7216 rv
= cmd_reply(state
, msgmap
, agg
);
7219 case 'f' : /* Forward */
7220 rv
= cmd_forward(state
, msgmap
, agg
);
7223 case '%' : /* print */
7224 rv
= cmd_print(state
, msgmap
, agg
, MsgIndx
);
7227 case 't' : /* take address */
7228 rv
= cmd_take_addr(state
, msgmap
, agg
);
7231 case 's' : /* save */
7232 rv
= cmd_save(state
, stream
, msgmap
, agg
, MsgIndx
);
7235 case 'e' : /* export */
7236 rv
= cmd_export(state
, msgmap
, q_line
, agg
);
7239 case '|' : /* pipe */
7240 rv
= cmd_pipe(state
, msgmap
, agg
);
7243 case '*' : /* flag */
7244 we_cancel
= busy_cue(NULL
, NULL
, 1);
7245 rv
= cmd_flag(state
, msgmap
, agg
);
7250 case 'b' : /* bounce */
7251 rv
= cmd_bounce(state
, msgmap
, agg
);
7255 collapse_or_expand(state
, stream
, msgmap
,
7256 F_ON(F_SLASH_COLL_ENTIRE
, ps_global
)
7258 : mn_get_cur(msgmap
));
7262 select_thread_stmp(state
, stream
, msgmap
);
7265 case 'x' : /* Expunge */
7266 rv
= cmd_expunge(state
, stream
, msgmap
, agg
);
7269 case 'c' : /* cancel */
7270 cmd_cancelled((flags
& AC_FROM_THREAD
) ? "Thread command"
7274 case 'z' : /* default */
7275 q_status_message(SM_INFO
, 0, 2,
7276 "Cancelled, there is no default command");
7288 * Select by message number ranges.
7289 * Sets searched bits in mail_elts
7291 * Args limitsrch -- limit search to this searchset
7293 * Returns 0 on success.
7296 select_by_number(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**limitsrch
)
7300 char number1
[16], number2
[16], numbers
[80], *p
, *t
;
7305 ps_global
->mangled_footer
= 1;
7308 int flags
= OE_APPEND_CURRENT
;
7310 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
7311 sizeof(numbers
), _(select_num
), NULL
, help
, &flags
);
7316 help
= (help
== NO_HELP
) ? h_select_by_num
: NO_HELP
;
7320 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
7321 if(!isspace((unsigned char)*p
))
7326 if(r
== 1 || numbers
[0] == '\0'){
7327 cmd_cancelled("Selection by number");
7334 for(n1
= 1; n1
<= stream
->nmsgs
; n1
++)
7335 if((mc
= mail_elt(stream
, n1
)) != NULL
)
7336 mc
->searched
= 0; /* clear searched bits */
7338 for(p
= numbers
; *p
; p
++){
7340 while(*p
&& isdigit((unsigned char)*p
))
7346 if(number1
[0] == '\0'){
7348 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7349 _("Invalid number range, missing number before \"-\": %s"),
7353 else if(!strucmp("end", p
)){
7358 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7359 _("Invalid message number: %s"), numbers
);
7365 n1
= mn_get_total(msgmap
);
7366 else if((n1
= atol(number1
)) < 1L || n1
> mn_get_total(msgmap
)){
7367 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7368 _("\"%s\" out of message number range"),
7375 while(*++p
&& isdigit((unsigned char)*p
))
7381 if(number2
[0] == '\0'){
7382 if(!strucmp("end", p
)){
7387 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7388 _("Invalid number range, missing number after \"-\": %s"),
7395 n2
= mn_get_total(msgmap
);
7396 else if((n2
= atol(number2
)) < 1L || n2
> mn_get_total(msgmap
)){
7397 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7398 _("\"%s\" out of message number range"),
7406 strncpy(t
, long2string(n1
), sizeof(t
));
7407 t
[sizeof(t
)-1] = '\0';
7408 q_status_message2(SM_ORDER
| SM_DING
, 0, 2,
7409 _("Invalid reverse message number range: %s-%s"),
7410 t
, long2string(n2
));
7414 for(;n1
<= n2
; n1
++){
7415 raw
= mn_m2raw(msgmap
, n1
);
7417 && (!(limitsrch
&& *limitsrch
)
7418 || in_searchset(*limitsrch
, (unsigned long) raw
)))
7419 mm_searched(stream
, raw
);
7423 raw
= mn_m2raw(msgmap
, n1
);
7425 && (!(limitsrch
&& *limitsrch
)
7426 || in_searchset(*limitsrch
, (unsigned long) raw
)))
7427 mm_searched(stream
, raw
);
7439 * Select by thread number ranges.
7440 * Sets searched bits in mail_elts
7442 * Args limitsrch -- limit search to this searchset
7444 * Returns 0 on success.
7447 select_by_thrd_number(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**msgset
)
7451 char number1
[16], number2
[16], numbers
[80], *p
, *t
;
7453 PINETHRD_S
*thrd
= NULL
;
7457 ps_global
->mangled_footer
= 1;
7460 int flags
= OE_APPEND_CURRENT
;
7462 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
7463 sizeof(numbers
), _(select_num
), NULL
, help
, &flags
);
7468 help
= (help
== NO_HELP
) ? h_select_by_thrdnum
: NO_HELP
;
7472 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
7473 if(!isspace((unsigned char)*p
))
7478 if(r
== 1 || numbers
[0] == '\0'){
7479 cmd_cancelled("Selection by number");
7486 for(n1
= 1; n1
<= stream
->nmsgs
; n1
++)
7487 if((mc
= mail_elt(stream
, n1
)) != NULL
)
7488 mc
->searched
= 0; /* clear searched bits */
7490 for(p
= numbers
; *p
; p
++){
7492 while(*p
&& isdigit((unsigned char)*p
))
7498 if(number1
[0] == '\0'){
7500 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7501 _("Invalid number range, missing number before \"-\": %s"),
7505 else if(!strucmp("end", p
)){
7510 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7511 _("Invalid thread number: %s"), numbers
);
7517 n1
= msgmap
->max_thrdno
;
7518 else if((n1
= atol(number1
)) < 1L || n1
> msgmap
->max_thrdno
){
7519 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7520 _("\"%s\" out of thread number range"),
7528 while(*++p
&& isdigit((unsigned char)*p
))
7534 if(number2
[0] == '\0'){
7535 if(!strucmp("end", p
)){
7540 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7541 _("Invalid number range, missing number after \"-\": %s"),
7548 n2
= msgmap
->max_thrdno
;
7549 else if((n2
= atol(number2
)) < 1L || n2
> msgmap
->max_thrdno
){
7550 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7551 _("\"%s\" out of thread number range"),
7559 strncpy(t
, long2string(n1
), sizeof(t
));
7560 t
[sizeof(t
)-1] = '\0';
7561 q_status_message2(SM_ORDER
| SM_DING
, 0, 2,
7562 _("Invalid reverse message number range: %s-%s"),
7563 t
, long2string(n2
));
7567 for(;n1
<= n2
; n1
++){
7568 thrd
= find_thread_by_number(stream
, msgmap
, n1
, thrd
);
7571 set_search_bit_for_thread(stream
, thrd
, msgset
);
7575 thrd
= find_thread_by_number(stream
, msgmap
, n1
, NULL
);
7578 set_search_bit_for_thread(stream
, thrd
, msgset
);
7590 * Select by message dates.
7591 * Sets searched bits in mail_elts
7593 * Args limitsrch -- limit search to this searchset
7595 * Returns 0 on success.
7598 select_by_date(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
, SEARCHSET
**limitsrch
)
7600 int r
, we_cancel
= 0, when
= 0;
7601 char date
[100], defdate
[100], prompt
[128];
7602 time_t seldate
= time(0);
7603 struct tm
*seldate_tm
;
7606 static struct _tense
{
7611 {"were ", "SENT SINCE", " (inclusive)"},
7612 {"were ", "SENT BEFORE", " (exclusive)"},
7613 {"were ", "SENT ON", "" },
7614 {"", "ARRIVED SINCE", " (inclusive)"},
7615 {"", "ARRIVED BEFORE", " (exclusive)"},
7616 {"", "ARRIVED ON", "" }
7620 ps_global
->mangled_footer
= 1;
7624 * If talking to an old server, default to SINCE instead of
7625 * SENTSINCE, which was added later.
7627 if(is_imap_stream(stream
) && !modern_imap_stream(stream
))
7631 int flags
= OE_APPEND_CURRENT
;
7633 seldate_tm
= localtime(&seldate
);
7634 snprintf(defdate
, sizeof(defdate
), "%.2d-%.4s-%.4d", seldate_tm
->tm_mday
,
7635 month_abbrev(seldate_tm
->tm_mon
+ 1),
7636 seldate_tm
->tm_year
+ 1900);
7637 defdate
[sizeof(defdate
)-1] = '\0';
7638 snprintf(prompt
,sizeof(prompt
),"Select messages which %s%s%s [%s]: ",
7639 tense
[when
].preamble
, tense
[when
].range
,
7640 tense
[when
].scope
, defdate
);
7641 prompt
[sizeof(prompt
)-1] = '\0';
7642 r
= optionally_enter(date
,-FOOTER_ROWS(ps_global
), 0, sizeof(date
),
7643 prompt
, sel_date_opt
, help
, &flags
);
7646 cmd_cancelled("Selection by date");
7650 help
= (help
== NO_HELP
) ? h_select_date
: NO_HELP
;
7661 if(stream
&& (rawno
= mn_m2raw(msgmap
, msgno
)) > 0L
7662 && rawno
<= stream
->nmsgs
7663 && (mc
= mail_elt(stream
, rawno
))){
7665 /* cache not filled in yet? */
7669 if(stream
->dtb
&& stream
->dtb
->flags
& DR_NEWS
){
7671 ulong2string(mail_uid(stream
, rawno
)),
7673 seq
[sizeof(seq
)-1] = '\0';
7674 mail_fetch_overview(stream
, seq
, NULL
);
7677 strncpy(seq
, long2string(rawno
),
7679 seq
[sizeof(seq
)-1] = '\0';
7680 mail_fetch_fast(stream
, seq
, 0L);
7684 /* mail_date returns fixed field width date */
7685 mail_date(date
, mc
);
7692 case 12 : /* set default to PREVIOUS day */
7696 case 13 : /* set default to NEXT day */
7701 when
= (when
+1) % (sizeof(tense
) / sizeof(struct _tense
));
7708 removing_leading_white_space(date
);
7709 removing_trailing_white_space(date
);
7711 strncpy(date
, defdate
, sizeof(date
));
7712 date
[sizeof(date
)-1] = '\0';
7718 if((pgm
= mail_newsearchpgm()) != NULL
){
7720 short converted_date
;
7722 if(mail_parse_date(&elt
, (unsigned char *) date
)){
7723 converted_date
= mail_shortdate(elt
.year
, elt
.month
, elt
.day
);
7727 pgm
->sentsince
= converted_date
;
7730 pgm
->sentbefore
= converted_date
;
7733 pgm
->senton
= converted_date
;
7736 pgm
->since
= converted_date
;
7739 pgm
->before
= converted_date
;
7742 pgm
->on
= converted_date
;
7746 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
7748 if(ps_global
&& ps_global
->ttyo
){
7749 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
7750 ps_global
->mangled_footer
= 1;
7753 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
7755 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
7760 /* we know this was freed in mail_search, let caller know */
7765 mail_free_searchpgm(&pgm
);
7766 q_status_message1(SM_ORDER
, 3, 3,
7767 _("Invalid date entered: %s"), date
);
7777 * Select by searching in message headers or body.
7778 * Sets searched bits in mail_elts
7780 * Args limitsrch -- limit search to this searchset
7782 * Returns 0 on success.
7785 select_by_text(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
, SEARCHSET
**limitsrch
)
7787 int r
, ku
, type
, we_cancel
= 0, flags
, rv
, ekeyi
= 0;
7788 int not = 0, me
= 0;
7789 char sstring
[80], savedsstring
[80], tmp
[128];
7790 char *p
, *sval
= NULL
;
7791 char buftmp
[MAILTMPLEN
], namehdr
[80];
7793 ENVELOPE
*env
= NULL
;
7795 unsigned flagsforhist
= 0;
7796 static HISTORY_S
*history
= NULL
;
7797 static char *recip
= "RECIPIENTS";
7798 static char *partic
= "PARTICIPANTS";
7799 static char *match_me
= N_("[Match_My_Addresses]");
7800 static char *dont_match_me
= N_("[Don't_Match_My_Addresses]");
7802 ps_global
->mangled_footer
= 1;
7803 savedsstring
[0] = '\0';
7804 ekey
[0].ch
= ekey
[1].ch
= ekey
[2].ch
= ekey
[3].ch
= -1;
7807 type
= radio_buttons(not ? _(sel_text_not
) : _(sel_text
),
7808 -FOOTER_ROWS(ps_global
), sel_text_opt
,
7809 's', 'x', NO_HELP
, RB_NORM
|RB_RET_HELP
);
7814 helper(h_select_text
, "HELP FOR SELECT BASED ON CONTENTS",
7816 ps_global
->mangled_screen
= 1;
7823 * prepare some friendly defaults...
7826 case 't' : /* address fields, offer To or From */
7831 sval
= (type
== 't') ? "TO" :
7832 (type
== 'f') ? "FROM" :
7833 (type
== 'c') ? "CC" :
7834 (type
== 'r') ? recip
: partic
;
7835 ekey
[ekeyi
].ch
= ctrl('T');
7836 ekey
[ekeyi
].name
= "^T";
7837 ekey
[ekeyi
].rval
= 10;
7838 /* TRANSLATORS: use Current To Address */
7839 ekey
[ekeyi
++].label
= N_("Cur To");
7840 ekey
[ekeyi
].ch
= ctrl('R');
7841 ekey
[ekeyi
].name
= "^R";
7842 ekey
[ekeyi
].rval
= 11;
7843 /* TRANSLATORS: use Current From Address */
7844 ekey
[ekeyi
++].label
= N_("Cur From");
7845 ekey
[ekeyi
].ch
= ctrl('W');
7846 ekey
[ekeyi
].name
= "^W";
7847 ekey
[ekeyi
].rval
= 12;
7848 /* TRANSLATORS: use Current Cc Address */
7849 ekey
[ekeyi
++].label
= N_("Cur Cc");
7850 ekey
[ekeyi
].ch
= ctrl('Y');
7851 ekey
[ekeyi
].name
= "^Y";
7852 ekey
[ekeyi
].rval
= 13;
7853 /* TRANSLATORS: Match Me means match my address */
7854 ekey
[ekeyi
++].label
= N_("Match Me");
7856 ekey
[ekeyi
].name
= "";
7857 ekey
[ekeyi
].rval
= 0;
7858 ekey
[ekeyi
++].label
= "";
7863 ekey
[ekeyi
].ch
= ctrl('X');
7864 ekey
[ekeyi
].name
= "^X";
7865 ekey
[ekeyi
].rval
= 14;
7866 /* TRANSLATORS: use Current Subject */
7867 ekey
[ekeyi
++].label
= N_("Cur Subject");
7879 strncpy(tmp
, "Name of HEADER to match : ", sizeof(tmp
)-1);
7880 tmp
[sizeof(tmp
)-1] = '\0';
7881 flags
= OE_APPEND_CURRENT
;
7887 r
= optionally_enter(namehdr
, -FOOTER_ROWS(ps_global
), 0,
7888 sizeof(namehdr
), tmp
, ekey
, NO_HELP
, &flags
);
7890 cmd_cancelled("Selection by text");
7893 removing_leading_white_space(namehdr
);
7895 while ((namehdr
[0] != '\0') && /* remove trailing ":" */
7896 (namehdr
[strlen(namehdr
) - 1] == ':'))
7897 namehdr
[strlen(namehdr
) - 1] = '\0';
7898 if ((namehdr
[0] != '\0')
7899 && isspace((unsigned char) namehdr
[strlen(namehdr
) - 1]))
7900 removing_trailing_white_space(namehdr
);
7904 if (strchr(namehdr
,' ') || strchr(namehdr
,'\t') ||
7905 strchr(namehdr
,':'))
7907 if (namehdr
[0] == '\0')
7917 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
7921 ekey
[ekeyi
].ch
= KEY_UP
;
7922 ekey
[ekeyi
].rval
= 30;
7923 ekey
[ekeyi
].name
= "";
7925 ekey
[ekeyi
++].label
= "";
7927 ekey
[ekeyi
].ch
= KEY_DOWN
;
7928 ekey
[ekeyi
].rval
= 31;
7929 ekey
[ekeyi
].name
= "";
7930 ekey
[ekeyi
++].label
= "";
7932 ekey
[ekeyi
].ch
= -1;
7936 init_hist(&history
, HISTSIZE
);
7938 if(ekey
[0].ch
> -1 && msgno
> 0L
7939 && !(env
=pine_mail_fetchstructure(stream
,mn_m2raw(msgmap
,msgno
),
7948 /* TRANSLATORS: character String in message <message number> to NOT match : " */
7949 snprintf(tmp
, sizeof(tmp
), "String in message %s to NOT match : ", sval
);
7951 snprintf(tmp
, sizeof(tmp
), "String in message %s to match : ", sval
);
7953 if(items_in_hist(history
) > 0){
7954 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
7955 ekey
[ku
].label
= HISTORY_KEYLABEL
;
7956 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
7957 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
7961 ekey
[ku
].label
= "";
7962 ekey
[ku
+1].name
= "";
7963 ekey
[ku
+1].label
= "";
7966 flags
= OE_APPEND_CURRENT
| OE_KEEP_TRAILING_SPACE
;
7967 r
= optionally_enter(sstring
, -FOOTER_ROWS(ps_global
), 0,
7968 79, tmp
, ekey
, help
, &flags
);
7970 if(me
&& r
== 0 && ((!not && strcmp(sstring
, _(match_me
))) || (not && strcmp(sstring
, _(dont_match_me
)))))
7975 help
= (help
== NO_HELP
)
7977 ? ((type
== 'f') ? h_select_txt_not_from
7978 : (type
== 't') ? h_select_txt_not_to
7979 : (type
== 'c') ? h_select_txt_not_cc
7980 : (type
== 's') ? h_select_txt_not_subj
7981 : (type
== 'a') ? h_select_txt_not_all
7982 : (type
== 'r') ? h_select_txt_not_recip
7983 : (type
== 'p') ? h_select_txt_not_partic
7984 : (type
== 'b') ? h_select_txt_not_body
7986 : ((type
== 'f') ? h_select_txt_from
7987 : (type
== 't') ? h_select_txt_to
7988 : (type
== 'c') ? h_select_txt_cc
7989 : (type
== 's') ? h_select_txt_subj
7990 : (type
== 'a') ? h_select_txt_all
7991 : (type
== 'r') ? h_select_txt_recip
7992 : (type
== 'p') ? h_select_txt_partic
7993 : (type
== 'b') ? h_select_txt_body
8000 case 10 : /* To: default */
8001 if(env
&& env
->to
&& env
->to
->mailbox
){
8002 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->to
->mailbox
,
8003 env
->to
->host
? "@" : "",
8004 env
->to
->host
? env
->to
->host
: "");
8005 sstring
[sizeof(sstring
)-1] = '\0';
8009 case 11 : /* From: default */
8010 if(env
&& env
->from
&& env
->from
->mailbox
){
8011 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->from
->mailbox
,
8012 env
->from
->host
? "@" : "",
8013 env
->from
->host
? env
->from
->host
: "");
8014 sstring
[sizeof(sstring
)-1] = '\0';
8018 case 12 : /* Cc: default */
8019 if(env
&& env
->cc
&& env
->cc
->mailbox
){
8020 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->cc
->mailbox
,
8021 env
->cc
->host
? "@" : "",
8022 env
->cc
->host
? env
->cc
->host
: "");
8023 sstring
[sizeof(sstring
)-1] = '\0';
8027 case 13 : /* Match my addresses */
8029 snprintf(sstring
, sizeof(sstring
), "%s", not ? _(dont_match_me
) : _(match_me
));
8032 case 14 : /* Subject: default */
8033 if(env
&& env
->subject
&& env
->subject
[0]){
8036 snprintf(buftmp
, sizeof(buftmp
), "%.75s", env
->subject
);
8037 buftmp
[sizeof(buftmp
)-1] = '\0';
8038 q
= (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf
,
8039 SIZEOF_20KBUF
, buftmp
);
8040 if(q
!= env
->subject
){
8041 snprintf(savedsstring
, sizeof(savedsstring
), "%.70s", q
);
8042 savedsstring
[sizeof(savedsstring
)-1] = '\0';
8045 snprintf(sstring
, sizeof(sstring
), "%s", q
);
8046 sstring
[sizeof(sstring
)-1] = '\0';
8052 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8053 if((p
= get_prev_hist(history
, sstring
, flagsforhist
, NULL
)) != NULL
){
8054 strncpy(sstring
, p
, sizeof(sstring
));
8055 sstring
[sizeof(sstring
)-1] = '\0';
8056 if(history
->hist
[history
->curindex
]){
8057 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
8058 not = (flagsforhist
& 0x1) ? 1 : 0;
8059 me
= (flagsforhist
& 0x2) ? 1 : 0;
8068 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8069 if((p
= get_next_hist(history
, sstring
, flagsforhist
, NULL
)) != NULL
){
8070 strncpy(sstring
, p
, sizeof(sstring
));
8071 sstring
[sizeof(sstring
)-1] = '\0';
8072 if(history
->hist
[history
->curindex
]){
8073 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
8074 not = (flagsforhist
& 0x1) ? 1 : 0;
8075 me
= (flagsforhist
& 0x2) ? 1 : 0;
8087 if(r
== 1 || sstring
[0] == '\0')
8094 if(type
== 'x' || r
== 'x'){
8095 cmd_cancelled("Selection by text");
8099 if(ps_global
&& ps_global
->ttyo
){
8100 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8101 ps_global
->mangled_footer
= 1;
8104 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8106 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8107 save_hist(history
, sstring
, flagsforhist
, NULL
);
8109 rv
= agg_text_select(stream
, msgmap
, type
, namehdr
, not, me
, sstring
, "utf-8", limitsrch
);
8118 * Select by message size.
8119 * Sets searched bits in mail_elts
8121 * Args limitsrch -- limit search to this searchset
8123 * Returns 0 on success.
8126 select_by_size(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8128 int r
, large
= 1, we_cancel
= 0;
8129 unsigned long n
, mult
= 1L, numerator
= 0L, divisor
= 1L;
8130 char size
[16], numbers
[80], *p
, *t
;
8133 long flags
= (SE_NOPREFETCH
| SE_FREE
);
8136 ps_global
->mangled_footer
= 1;
8140 int flgs
= OE_APPEND_CURRENT
;
8142 sel_size_opt
[1].label
= large
? sel_size_smaller
: sel_size_larger
;
8144 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
8145 sizeof(numbers
), large
? _(select_size_larger_msg
)
8146 : _(select_size_smaller_msg
),
8147 sel_size_opt
, help
, &flgs
);
8157 help
= (help
== NO_HELP
) ? (large
? h_select_by_larger_size
8158 : h_select_by_smaller_size
)
8163 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
8164 if(!isspace((unsigned char)*p
))
8169 if(r
== 1 || numbers
[0] == '\0'){
8170 cmd_cancelled("Selection by size");
8177 if(numbers
[0] == '-'){
8178 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
8179 _("Invalid size entered: %s"), numbers
);
8186 while(*p
&& isdigit((unsigned char)*p
))
8191 if(size
[0] == '\0' && *p
== '.' && isdigit(*(p
+1))){
8196 if(size
[0] == '\0'){
8197 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
8198 _("Invalid size entered: %s"), numbers
);
8202 n
= strtoul(size
, (char **)NULL
, 10);
8207 * We probably ought to just use atof() to convert 1.1 into a
8208 * double, but since we haven't used atof() anywhere else I'm
8209 * reluctant to use it because of portability concerns.
8213 while(*p
&& isdigit((unsigned char)*p
)){
8221 numerator
= strtoul(size
, (char **)NULL
, 10);
8241 n
= n
* mult
+ (numerator
* mult
) / divisor
;
8243 pgm
= mail_newsearchpgm();
8249 if(is_imap_stream(stream
) && !modern_imap_stream(stream
))
8250 flags
|= SE_NOSERVER
;
8252 if(ps_global
&& ps_global
->ttyo
){
8253 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8254 ps_global
->mangled_footer
= 1;
8257 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8259 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
8260 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
8261 /* we know this was freed in mail_search, let caller know */
8273 * visible_searchset -- return c-client search set unEXLDed
8277 visible_searchset(MAILSTREAM
*stream
, MSGNO_S
*msgmap
)
8280 SEARCHSET
*full_set
= NULL
, **set
;
8283 * If we're talking to anything other than a server older than
8284 * imap 4rev1, build a searchset otherwise it'll choke.
8286 if(!(is_imap_stream(stream
) && !modern_imap_stream(stream
))){
8287 if(any_lflagged(msgmap
, MN_EXLD
)){
8288 for(n
= 1L, set
= &full_set
, run
= 0L; n
<= stream
->nmsgs
; n
++)
8289 if(get_lflag(stream
, NULL
, n
, MN_EXLD
)){
8290 if(run
){ /* previous NOT excluded? */
8292 (*set
)->last
= n
- 1L;
8294 set
= &(*set
)->next
;
8298 else if(run
++){ /* next in run */
8301 else{ /* start of run */
8302 *set
= mail_newsearchset();
8307 full_set
= mail_newsearchset();
8308 full_set
->first
= 1L;
8309 full_set
->last
= stream
->nmsgs
;
8318 * Select by message status bits.
8319 * Sets searched bits in mail_elts
8321 * Args limitsrch -- limit search to this searchset
8323 * Returns 0 on success.
8326 select_by_status(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8328 int s
, not = 0, we_cancel
= 0, rv
;
8331 s
= radio_buttons((not) ? _(sel_flag_not
) : _(sel_flag
),
8332 -FOOTER_ROWS(ps_global
), sel_flag_opt
, '*', 'x',
8333 NO_HELP
, RB_NORM
|RB_RET_HELP
);
8336 cmd_cancelled("Selection by status");
8340 helper(h_select_status
, _("HELP FOR SELECT BASED ON STATUS"),
8342 ps_global
->mangled_screen
= 1;
8350 if(ps_global
&& ps_global
->ttyo
){
8351 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8352 ps_global
->mangled_footer
= 1;
8355 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8356 rv
= agg_flag_select(stream
, not, s
, limitsrch
);
8365 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8366 * Sets searched bits in mail_elts
8368 * Args limitsrch -- limit search to this searchset
8370 * Returns 0 on success.
8373 select_by_rule(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8375 char rulenick
[1000], *nick
;
8377 int r
, not = 0, we_cancel
= 0, rflags
= ROLE_DO_SRCH
8385 ps_global
->mangled_footer
= 1;
8390 oe_flags
= OE_APPEND_CURRENT
;
8391 r
= optionally_enter(rulenick
, -FOOTER_ROWS(ps_global
), 0,
8393 not ? _("Rule to NOT match: ")
8394 : _("Rule to match: "),
8395 sel_key_opt
, NO_HELP
, &oe_flags
);
8398 /* select rulenick from a list */
8399 if((nick
=choose_a_rule(rflags
)) != NULL
){
8400 strncpy(rulenick
, nick
, sizeof(rulenick
)-1);
8401 rulenick
[sizeof(rulenick
)-1] = '\0';
8402 fs_give((void **) &nick
);
8411 helper(h_select_rule
, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE
);
8412 ps_global
->mangled_screen
= 1;
8415 cmd_cancelled("Selection by Rule");
8419 removing_leading_and_trailing_white_space(rulenick
);
8421 }while(r
== 3 || r
== 4 || r
== '!');
8425 * The approach of requiring a nickname instead of just allowing the
8426 * user to select from the list of rules has the drawback that a rule
8427 * may not have a nickname, or there may be more than one rule with
8428 * the same nickname. However, it has the benefit of allowing the user
8429 * to type in the nickname and, most importantly, allows us to set
8430 * up the ! (not). We could incorporate the ! into the selection
8431 * screen, but this is easier and also allows the typing of nicks.
8432 * User can just set up nicknames if they want to use this feature.
8434 patgrp
= nick_to_patgrp(rulenick
, rflags
);
8437 if(ps_global
&& ps_global
->ttyo
){
8438 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8439 ps_global
->mangled_footer
= 1;
8442 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8443 match_pattern(patgrp
, stream
, limitsrch
? *limitsrch
: 0, NULL
,
8445 (not ? MP_NOT
: 0) | SE_NOPREFETCH
);
8446 free_patgrp(&patgrp
);
8451 if(limitsrch
&& *limitsrch
){
8452 mail_free_searchset(limitsrch
);
8461 * Allow user to choose a rule from their list of rules.
8463 * Returns an allocated rule nickname on success, NULL otherwise.
8466 choose_a_rule(int rflags
)
8468 char *choice
= NULL
;
8469 char **rule_list
, **lp
;
8474 if(!(nonempty_patterns(rflags
, &pstate
) && first_pattern(&pstate
))){
8475 q_status_message(SM_ORDER
, 3, 3,
8476 _("No rules available. Use Setup/Rules to add some."));
8481 * Build a list of rules to choose from.
8484 for(pat
= first_pattern(&pstate
); pat
; pat
= next_pattern(&pstate
))
8488 q_status_message(SM_ORDER
, 3, 4, _("No rules defined, use Setup/Rules"));
8492 lp
= rule_list
= (char **) fs_get((cnt
+ 1) * sizeof(*rule_list
));
8493 memset(rule_list
, 0, (cnt
+1) * sizeof(*rule_list
));
8495 for(pat
= first_pattern(&pstate
); pat
; pat
= next_pattern(&pstate
))
8496 *lp
++ = cpystr((pat
->patgrp
&& pat
->patgrp
->nick
)
8497 ? pat
->patgrp
->nick
: "?");
8499 /* TRANSLATORS: SELECT A RULE is a screen title
8500 TRANSLATORS: Print something1 using something2.
8501 "rules" is something1 */
8502 choice
= choose_item_from_list(rule_list
, NULL
, _("SELECT A RULE"),
8503 _("rules"), h_select_rule_screen
,
8504 _("HELP FOR SELECTING A RULE NICKNAME"), NULL
);
8507 q_status_message(SM_ORDER
, 1, 4, "No choice");
8509 free_list_array(&rule_list
);
8516 * Select by current thread.
8517 * Sets searched bits in mail_elts for this entire thread
8519 * Args limitsrch -- limit search to this searchset
8521 * Returns 0 on success.
8524 select_by_thread(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**limitsrch
)
8527 PINETHRD_S
*thrd
= NULL
;
8534 for(n
= 1L; n
<= stream
->nmsgs
; n
++)
8535 if((mc
= mail_elt(stream
, n
)) != NULL
)
8536 mc
->searched
= 0; /* clear searched bits */
8538 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
8539 if(thrd
&& thrd
->top
&& thrd
->top
!= thrd
->rawno
)
8540 thrd
= fetch_thread(stream
, thrd
->top
);
8543 * This doesn't unselect if the thread is already selected
8544 * (like select current does), it always selects.
8545 * There is no way to select ! this thread.
8548 set_search_bit_for_thread(stream
, thrd
, limitsrch
);
8557 * Select by message keywords.
8558 * Sets searched bits in mail_elts
8560 * Args limitsrch -- limit search to this searchset
8562 * Returns 0 on success.
8565 select_by_keyword(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8567 int r
, not = 0, we_cancel
= 0;
8568 char keyword
[MAXUSERFLAG
+1], *kword
;
8569 char *error
= NULL
, *p
, *prompt
;
8574 ps_global
->mangled_footer
= 1;
8581 q_status_message(SM_ORDER
, 3, 4, error
);
8582 fs_give((void **) &error
);
8585 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
) && ps_global
->keywords
){
8587 prompt
= _("Keyword (or keyword initial) to NOT match: ");
8589 prompt
= _("Keyword (or keyword initial) to match: ");
8593 prompt
= _("Keyword to NOT match: ");
8595 prompt
= _("Keyword to match: ");
8598 oe_flags
= OE_APPEND_CURRENT
;
8599 r
= optionally_enter(keyword
, -FOOTER_ROWS(ps_global
), 0,
8601 prompt
, sel_key_opt
, help
, &oe_flags
);
8604 /* select keyword from a list */
8605 if((kword
=choose_a_keyword()) != NULL
){
8606 strncpy(keyword
, kword
, sizeof(keyword
)-1);
8607 keyword
[sizeof(keyword
)-1] = '\0';
8608 fs_give((void **) &kword
);
8617 help
= help
== NO_HELP
? h_select_keyword
: NO_HELP
;
8619 cmd_cancelled("Selection by keyword");
8623 removing_leading_and_trailing_white_space(keyword
);
8625 }while(r
== 3 || r
== 4 || r
== '!' || keyword_check(keyword
, &error
));
8628 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
) && ps_global
->keywords
){
8629 p
= initial_to_keyword(keyword
);
8631 strncpy(keyword
, p
, sizeof(keyword
)-1);
8632 keyword
[sizeof(keyword
)-1] = '\0';
8637 * We want to check the keyword, not the nickname of the keyword,
8638 * so convert it to the keyword if necessary.
8640 p
= nick_to_keyword(keyword
);
8642 strncpy(keyword
, p
, sizeof(keyword
)-1);
8643 keyword
[sizeof(keyword
)-1] = '\0';
8646 pgm
= mail_newsearchpgm();
8648 pgm
->unkeyword
= mail_newstringlist();
8649 pgm
->unkeyword
->text
.data
= (unsigned char *) cpystr(keyword
);
8650 pgm
->unkeyword
->text
.size
= strlen(keyword
);
8653 pgm
->keyword
= mail_newstringlist();
8654 pgm
->keyword
->text
.data
= (unsigned char *) cpystr(keyword
);
8655 pgm
->keyword
->text
.size
= strlen(keyword
);
8658 if(ps_global
&& ps_global
->ttyo
){
8659 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8660 ps_global
->mangled_footer
= 1;
8663 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8665 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
8666 pine_mail_search_full(stream
, "UTF-8", pgm
, SE_NOPREFETCH
| SE_FREE
);
8667 /* we know this was freed in mail_search, let caller know */
8679 * Allow user to choose a keyword from their list of keywords.
8681 * Returns an allocated keyword on success, NULL otherwise.
8684 choose_a_keyword(void)
8686 char *choice
= NULL
;
8687 char **keyword_list
, **lp
;
8692 * Build a list of keywords to choose from.
8695 for(cnt
= 0, kw
= ps_global
->keywords
; kw
; kw
= kw
->next
)
8699 q_status_message(SM_ORDER
, 3, 4,
8700 _("No keywords defined, use \"keywords\" option in Setup/Config"));
8704 lp
= keyword_list
= (char **) fs_get((cnt
+ 1) * sizeof(*keyword_list
));
8705 memset(keyword_list
, 0, (cnt
+1) * sizeof(*keyword_list
));
8707 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
)
8708 *lp
++ = cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
8710 /* TRANSLATORS: SELECT A KEYWORD is a screen title
8711 TRANSLATORS: Print something1 using something2.
8712 "keywords" is something1 */
8713 choice
= choose_item_from_list(keyword_list
, NULL
, _("SELECT A KEYWORD"),
8714 _("keywords"), h_select_keyword_screen
,
8715 _("HELP FOR SELECTING A KEYWORD"), NULL
);
8718 q_status_message(SM_ORDER
, 1, 4, "No choice");
8720 free_list_array(&keyword_list
);
8727 * Allow user to choose a list of keywords from their list of keywords.
8729 * Returns allocated list.
8732 choose_list_of_keywords(void)
8734 LIST_SEL_S
*listhead
, *ls
, *p
;
8740 * Build a list of keywords to choose from.
8743 p
= listhead
= NULL
;
8744 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
8746 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
8747 memset(ls
, 0, sizeof(*ls
));
8748 ls
->item
= cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
8761 /* TRANSLATORS: SELECT KEYWORDS is a screen title
8762 Print something1 using something2.
8763 "keywords" is something1 */
8764 if(!select_from_list_screen(listhead
, SFL_ALLOW_LISTMODE
,
8765 _("SELECT KEYWORDS"), _("keywords"),
8766 h_select_multkeyword_screen
,
8767 _("HELP FOR SELECTING KEYWORDS"), NULL
)){
8768 for(cnt
= 0, p
= listhead
; p
; p
= p
->next
)
8772 ret
= (char **) fs_get((cnt
+1) * sizeof(*ret
));
8773 memset(ret
, 0, (cnt
+1) * sizeof(*ret
));
8774 for(i
= 0, p
= listhead
; p
; p
= p
->next
)
8776 ret
[i
++] = cpystr(p
->item
? p
->item
: "");
8779 free_list_sel(&listhead
);
8786 * Allow user to choose a charset
8788 * Returns an allocated charset on success, NULL otherwise.
8791 choose_a_charset(int which_charsets
)
8793 char *choice
= NULL
;
8794 char **charset_list
, **lp
;
8799 * Build a list of charsets to choose from.
8802 for(cnt
= 0, cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
8803 if(!(cs
->flags
& (CF_UNSUPRT
|CF_NOEMAIL
))
8804 && ((which_charsets
& CAC_ALL
)
8805 || (which_charsets
& CAC_POSTING
8806 && cs
->flags
& CF_POSTING
)
8807 || (which_charsets
& CAC_DISPLAY
8808 && cs
->type
!= CT_2022
8809 && (cs
->flags
& (CF_PRIMARY
|CF_DISPLAY
)) == (CF_PRIMARY
|CF_DISPLAY
))))
8814 q_status_message(SM_ORDER
, 3, 4,
8815 _("No charsets found? Enter charset manually."));
8819 lp
= charset_list
= (char **) fs_get((cnt
+ 1) * sizeof(*charset_list
));
8820 memset(charset_list
, 0, (cnt
+1) * sizeof(*charset_list
));
8822 for(cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
8823 if(!(cs
->flags
& (CF_UNSUPRT
|CF_NOEMAIL
))
8824 && ((which_charsets
& CAC_ALL
)
8825 || (which_charsets
& CAC_POSTING
8826 && cs
->flags
& CF_POSTING
)
8827 || (which_charsets
& CAC_DISPLAY
8828 && cs
->type
!= CT_2022
8829 && (cs
->flags
& (CF_PRIMARY
|CF_DISPLAY
)) == (CF_PRIMARY
|CF_DISPLAY
))))
8830 *lp
++ = cpystr(cs
->name
);
8833 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
8834 TRANSLATORS: Print something1 using something2.
8835 "character sets" is something1 */
8836 choice
= choose_item_from_list(charset_list
, NULL
, _("SELECT A CHARACTER SET"),
8837 _("character sets"), h_select_charset_screen
,
8838 _("HELP FOR SELECTING A CHARACTER SET"), NULL
);
8841 q_status_message(SM_ORDER
, 1, 4, "No choice");
8843 free_list_array(&charset_list
);
8850 * Allow user to choose a list of character sets and/or scripts
8852 * Returns allocated list.
8855 choose_list_of_charsets(void)
8857 LIST_SEL_S
*listhead
, *ls
, *p
;
8859 int cnt
, i
, got_one
;
8864 char buf
[1024], *folded
;
8867 * Build a list of charsets to choose from.
8870 p
= listhead
= NULL
;
8872 /* this width is determined by select_from_list_screen() */
8873 width
= ps_global
->ttyo
->screen_cols
- 4;
8875 /* first comes a list of scripts (sets of character sets) */
8876 for(s
= utf8_script(NIL
); s
&& s
->name
; s
++){
8878 limit
= sizeof(buf
)-1;
8880 memset(q
, 0, limit
+1);
8883 sstrncpy(&q
, s
->name
, limit
);
8886 sstrncpy(&q
, " (", limit
-(q
-buf
));
8887 sstrncpy(&q
, s
->description
, limit
-(q
-buf
));
8888 sstrncpy(&q
, ")", limit
-(q
-buf
));
8891 /* add the list of charsets that are in this script */
8893 for(cs
= utf8_charset(NIL
);
8894 cs
&& cs
->name
&& (q
-buf
) < limit
; cs
++){
8895 if(cs
->script
& s
->script
){
8897 * Filter out some un-useful members of the list.
8898 * UTF-7 and UTF-8 weren't actually in the list at the
8899 * time this was written. Just making sure.
8901 if(!strucmp(cs
->name
, "ISO-2022-JP-2")
8902 || !strucmp(cs
->name
, "UTF-7")
8903 || !strucmp(cs
->name
, "UTF-8"))
8907 sstrncpy(&q
, " ", limit
-(q
-buf
));
8910 sstrncpy(&q
, " {", limit
-(q
-buf
));
8913 sstrncpy(&q
, cs
->name
, limit
-(q
-buf
));
8918 sstrncpy(&q
, "}", limit
-(q
-buf
));
8920 /* fold this line so that it can all be seen on the screen */
8921 folded
= fold(buf
, width
, width
, "", " ", FLD_NONE
);
8924 while(t
&& *t
&& (q
= strindex(t
, '\n')) != NULL
){
8927 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
8928 memset(ls
, 0, sizeof(*ls
));
8930 ls
->item
= cpystr(s
->name
);
8932 ls
->flags
= SFL_NOSELECT
;
8934 ls
->display_item
= cpystr(t
);
8944 listhead
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
8945 memset(listhead
, 0, sizeof(*listhead
));
8946 listhead
->flags
= SFL_NOSELECT
;
8947 listhead
->display_item
=
8948 cpystr(_("Scripts representing groups of related character sets"));
8949 listhead
->next
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
8950 memset(listhead
->next
, 0, sizeof(*listhead
));
8951 listhead
->next
->flags
= SFL_NOSELECT
;
8952 listhead
->next
->display_item
=
8953 cpystr(repeat_char(width
, '-'));
8955 listhead
->next
->next
= ls
;
8960 fs_give((void **) &folded
);
8964 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
8965 memset(ls
, 0, sizeof(*ls
));
8966 ls
->flags
= SFL_NOSELECT
;
8974 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
8975 memset(ls
, 0, sizeof(*ls
));
8976 ls
->flags
= SFL_NOSELECT
;
8978 cpystr(_("Individual character sets, may be mixed with scripts"));
8982 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
8983 memset(ls
, 0, sizeof(*ls
));
8984 ls
->flags
= SFL_NOSELECT
;
8986 cpystr(repeat_char(width
, '-'));
8990 /* then comes a list of individual character sets */
8991 for(cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
8992 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
8993 memset(ls
, 0, sizeof(*ls
));
8994 ls
->item
= cpystr(cs
->name
);
9007 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9008 Print something1 using something2.
9009 "character sets" is something1 */
9010 if(!select_from_list_screen(listhead
, SFL_ALLOW_LISTMODE
,
9011 _("SELECT CHARACTER SETS"), _("character sets"),
9012 h_select_multcharsets_screen
,
9013 _("HELP FOR SELECTING CHARACTER SETS"), NULL
)){
9014 for(cnt
= 0, p
= listhead
; p
; p
= p
->next
)
9018 ret
= (char **) fs_get((cnt
+1) * sizeof(*ret
));
9019 memset(ret
, 0, (cnt
+1) * sizeof(*ret
));
9020 for(i
= 0, p
= listhead
; p
; p
= p
->next
)
9022 ret
[i
++] = cpystr(p
->item
? p
->item
: "");
9025 free_list_sel(&listhead
);
9030 /* Report quota summary resources in an IMAP server */
9032 void cmd_quota (struct pine
*state
)
9034 QUOTALIST
*imapquota
;
9039 if(!state
->mail_stream
|| !is_imap_stream(state
->mail_stream
)){
9040 q_status_message(SM_ORDER
, 1, 5, "Quota only available for IMAP folders");
9044 if (state
->mail_stream
9045 && !sp_dead_stream(state
->mail_stream
)
9046 && state
->mail_stream
->mailbox
9047 && *state
->mail_stream
->mailbox
9048 && mail_valid_net_parse(state
->mail_stream
->mailbox
, &mb
))
9049 imap_getquotaroot(state
->mail_stream
, mb
.mailbox
);
9051 if(!state
->quota
) /* failed ? */
9052 return; /* go back... */
9054 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
9055 q_status_message(SM_ORDER
| SM_DING
, 3, 3, "Error allocating space.");
9059 so_puts(store
, "Quota Report for ");
9060 so_puts(store
, state
->mail_stream
->original_mailbox
);
9061 so_puts(store
, "\n\n");
9063 for (imapquota
= state
->quota
; imapquota
; imapquota
= imapquota
->next
){
9065 so_puts(store
, _("Resource : "));
9066 so_puts(store
, imapquota
->name
);
9067 so_writec('\n', store
);
9069 so_puts(store
, _("Usage : "));
9070 so_puts(store
, long2string(imapquota
->usage
));
9071 if(!strucmp(imapquota
->name
,"STORAGE"))
9072 so_puts(store
, " KiB ");
9073 if(!strucmp(imapquota
->name
,"MESSAGE")){
9074 so_puts(store
, _(" message"));
9075 if(imapquota
->usage
!= 1)
9076 so_puts(store
, _("s ")); /* plural */
9078 so_puts(store
, _(" "));
9080 so_writec('(', store
);
9081 so_puts(store
, long2string(100*imapquota
->usage
/imapquota
->limit
));
9082 so_puts(store
, "%)\n");
9084 so_puts(store
, _("Limit : "));
9085 so_puts(store
, long2string(imapquota
->limit
));
9086 if(!strucmp(imapquota
->name
,"STORAGE"))
9087 so_puts(store
, " KiB\n\n");
9088 if(!strucmp(imapquota
->name
,"MESSAGE")){
9089 so_puts(store
, _(" message"));
9090 if(imapquota
->usage
!= 1)
9091 so_puts(store
, _("s\n\n")); /* plural */
9093 so_puts(store
, _("\n\n"));
9097 memset(&sargs
, 0, sizeof(SCROLL_S
));
9098 sargs
.text
.text
= so_text(store
);
9099 sargs
.text
.src
= CharStar
;
9100 sargs
.text
.desc
= _("Quota Resources Summary");
9101 sargs
.bar
.title
= _("QUOTA SUMMARY");
9102 sargs
.proc
.tool
= NULL
;
9103 sargs
.help
.text
= h_quota_command
;
9104 sargs
.help
.title
= NULL
;
9105 sargs
.keys
.menu
= NULL
;
9106 setbitmap(sargs
.keys
.bitmap
);
9112 mail_free_quotalist(&(state
->quota
));
9115 /*----------------------------------------------------------------------
9116 Prompt the user for the type of sort he desires
9118 Args: state -- pine state pointer
9119 q1 -- Line to prompt on
9121 Returns 0 if it was cancelled, 1 otherwise.
9124 select_sort(struct pine
*state
, int ql
, SortOrder
*sort
, int *rev
)
9126 char prompt
[200], tmp
[3], *p
;
9128 int deefault
= 'a', retval
= 1;
9133 DLG_SORTPARAM sortsel
;
9135 if (mswin_usedialog ()) {
9137 sortsel
.reverse
= mn_get_revsort (state
->msgmap
);
9138 sortsel
.cursort
= mn_get_sort (state
->msgmap
);
9139 /* assumption here that HelpType is char ** */
9140 sortsel
.helptext
= h_select_sort
;
9143 if ((retval
= os_sortdialog (&sortsel
))) {
9144 *sort
= sortsel
.cursort
;
9145 *rev
= sortsel
.reverse
;
9152 /*----- String together the prompt ------*/
9154 if(F_ON(F_USE_FK
,ps_global
))
9155 strncpy(prompt
, _("Choose type of sort : "), sizeof(prompt
));
9157 strncpy(prompt
, _("Choose type of sort, or 'R' to reverse current sort : "),
9160 for(i
= 0; state
->sort_types
[i
] != EndofList
; i
++) {
9162 p
= sorts
[i
].label
= sort_name(state
->sort_types
[i
]);
9163 while(*(p
+1) && islower((unsigned char)*p
))
9166 sorts
[i
].ch
= tolower((unsigned char)(tmp
[0] = *p
));
9167 sorts
[i
].name
= cpystr(tmp
);
9169 if(mn_get_sort(state
->msgmap
) == state
->sort_types
[i
])
9170 deefault
= sorts
[i
].rval
;
9174 sorts
[i
].rval
= 'r';
9175 sorts
[i
].name
= cpystr("R");
9176 if(F_ON(F_USE_FK
,ps_global
))
9177 sorts
[i
].label
= N_("Reverse");
9179 sorts
[i
].label
= "";
9182 help
= h_select_sort
;
9184 if((F_ON(F_USE_FK
,ps_global
)
9185 && ((s
= double_radio_buttons(prompt
,ql
,sorts
,deefault
,'x',
9186 help
,RB_NORM
)) != 'x'))
9188 (F_OFF(F_USE_FK
,ps_global
)
9189 && ((s
= radio_buttons(prompt
,ql
,sorts
,deefault
,'x',
9190 help
,RB_NORM
)) != 'x'))){
9191 state
->mangled_body
= 1; /* signal screen's changed */
9193 *rev
= !mn_get_revsort(state
->msgmap
);
9195 *sort
= state
->sort_types
[s
];
9197 if(F_ON(F_SHOW_SORT
, ps_global
))
9198 ps_global
->mangled_header
= 1;
9202 cmd_cancelled("Sort");
9206 fs_give((void **)&sorts
[i
].name
);
9208 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
9213 /*---------------------------------------------------------------------
9214 Build list of folders in the given context for user selection
9216 Args: c -- pointer to pointer to folder's context context
9217 f -- folder prefix to display
9218 sublist -- whether or not to use 'f's contents as prefix
9219 lister -- function used to do the actual display
9221 Returns: malloc'd string containing sequence, else NULL if
9222 no messages in msgmap with local "selected" flag.
9225 display_folder_list(CONTEXT_S
**c
, char *f
, int sublist
, int (*lister
) (struct pine
*, CONTEXT_S
**, char *, int))
9229 void (*redraw
)(void) = ps_global
->redrawer
;
9231 push_titlebar_state();
9233 if((rc
= (*lister
)(ps_global
, &tc
, f
, sublist
)) != 0)
9237 pop_titlebar_state();
9239 if((ps_global
->redrawer
= redraw
) != NULL
) /* reset old value, and test */
9240 (*ps_global
->redrawer
)();
9242 if(rc
== 1 && F_ON(F_SELECT_WO_CONFIRM
, ps_global
))
9250 * Allow user to choose a single item from a list of strings.
9252 * Args list -- Array of strings to choose from, NULL terminated.
9253 * displist -- Array of strings to display instead of displaying list.
9254 * Indices correspond to the list array. Display the displist
9255 * but return the item from list if displist non-NULL.
9256 * title -- For conf_scroll_screen
9257 * pdesc -- For conf_scroll_screen
9258 * help -- For conf_scroll_screen
9259 * htitle -- For conf_scroll_screen
9261 * Returns an allocated copy of the chosen item or NULL.
9264 choose_item_from_list(char **list
, char **displist
, char *title
, char *pdesc
, HelpType help
,
9265 char *htitle
, char *cursor_location
)
9267 LIST_SEL_S
*listhead
, *ls
, *p
, *starting_val
= NULL
;
9269 char *ret
= NULL
, *choice
= NULL
;
9271 /* build the LIST_SEL_S list */
9272 p
= listhead
= NULL
;
9273 for(t
= list
, dl
= displist
; *t
; t
++, dl
++){
9274 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9275 memset(ls
, 0, sizeof(*ls
));
9276 ls
->item
= cpystr(*t
);
9278 ls
->display_item
= cpystr(*dl
);
9280 if(cursor_location
&& (cursor_location
== (*t
)))
9294 if(!select_from_list_screen(listhead
, SFL_NONE
, title
, pdesc
,
9295 help
, htitle
, starting_val
))
9296 for(p
= listhead
; !choice
&& p
; p
= p
->next
)
9301 ret
= cpystr(choice
);
9303 free_list_sel(&listhead
);
9310 free_list_sel(LIST_SEL_S
**lsel
)
9313 free_list_sel(&(*lsel
)->next
);
9315 fs_give((void **) &(*lsel
)->item
);
9317 if((*lsel
)->display_item
)
9318 fs_give((void **) &(*lsel
)->display_item
);
9320 fs_give((void **) lsel
);
9326 * file_lister - call pico library's file lister
9329 file_lister(char *title
, char *path
, size_t pathlen
, char *file
, size_t filelen
, int newmail
, int flags
)
9333 void (*redraw
)(void) = ps_global
->redrawer
;
9335 standard_picobuf_setup(&pbf
);
9336 push_titlebar_state();
9340 /* BUG: what about help command and text? */
9341 pbf
.pine_anchor
= title
;
9343 rv
= pico_file_browse(&pbf
, path
, pathlen
, file
, filelen
, NULL
, 0, flags
);
9344 standard_picobuf_teardown(&pbf
);
9345 fix_windsize(ps_global
);
9346 init_signals(); /* has it's own signal stuff */
9348 /* Restore display's titlebar and body */
9349 pop_titlebar_state();
9351 if((ps_global
->redrawer
= redraw
) != NULL
)
9352 (*ps_global
->redrawer
)();
9358 /*----------------------------------------------------------------------
9359 Print current folder index
9363 print_index(struct pine
*state
, MSGNO_S
*msgmap
, int agg
)
9367 char buf
[MAX_SCREEN_COLS
+1];
9369 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++){
9370 if(agg
&& !get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
))
9373 if(!agg
&& msgline_hidden(state
->mail_stream
, msgmap
, i
, 0))
9376 ice
= build_header_line(state
, state
->mail_stream
, msgmap
, i
, NULL
);
9380 * I don't understand why we'd want to mark the current message
9381 * instead of printing out the first character of the status
9382 * so I'm taking it out and including the first character of the
9383 * line instead. Hubert 2006-02-09
9385 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
9389 if(!gf_puts(simple_index_line(buf
,sizeof(buf
),ice
,i
),
9391 || !gf_puts(NEWLINE
, print_char
))
9403 * windows callback to get/set header mode state
9406 header_mode_callback(set
, args
)
9410 return(ps_global
->full_header
);
9415 * windows callback to get/set zoom mode state
9418 zoom_mode_callback(set
, args
)
9422 return(any_lflagged(ps_global
->msgmap
, MN_HIDE
) != 0);
9427 * windows callback to get/set zoom mode state
9430 any_selected_callback(set
, args
)
9434 return(any_lflagged(ps_global
->msgmap
, MN_SLCT
) != 0);
9442 flag_callback(set
, flags
)
9452 case 1: /* Important */
9453 permflag
= ps_global
->mail_stream
->perm_flagged
;
9457 permflag
= ps_global
->mail_stream
->perm_seen
;
9460 case 3: /* Answered */
9461 permflag
= ps_global
->mail_stream
->perm_answered
;
9464 case 4: /* Deleted */
9465 permflag
= ps_global
->mail_stream
->perm_deleted
;
9470 if(!(any_messages(ps_global
->msgmap
, NULL
, "to Flag")
9471 && can_set_flag(ps_global
, "flag", permflag
)))
9474 if(sp_io_error_on_stream(ps_global
->mail_stream
)){
9475 sp_set_io_error_on_stream(ps_global
->mail_stream
, 0);
9476 pine_mail_check(ps_global
->mail_stream
); /* forces write */
9480 msgno
= mn_m2raw(ps_global
->msgmap
, mn_get_cur(ps_global
->msgmap
));
9481 if(msgno
> 0L && ps_global
->mail_stream
9482 && msgno
<= ps_global
->mail_stream
->nmsgs
9483 && (mc
= mail_elt(ps_global
->mail_stream
, msgno
))
9486 * NOTE: code below is *VERY* sensitive to the order of
9487 * the messages defined in resource.h for flag handling.
9488 * Don't change it unless you know what you're doing.
9495 case 1 : /* Important */
9496 flagstr
= "\\FLAGGED";
9497 mflag
= (mc
->flagged
) ? 0L : ST_SET
;
9502 mflag
= (mc
->seen
) ? 0L : ST_SET
;
9505 case 3 : /* Answered */
9506 flagstr
= "\\ANSWERED";
9507 mflag
= (mc
->answered
) ? 0L : ST_SET
;
9510 case 4 : /* Deleted */
9511 flagstr
= "\\DELETED";
9512 mflag
= (mc
->deleted
) ? 0L : ST_SET
;
9515 default : /* bogus */
9519 mail_flag(ps_global
->mail_stream
, long2string(msgno
),
9522 if(ps_global
->redrawer
)
9523 (*ps_global
->redrawer
)();
9550 * BUG: Should teach this about keywords
9556 static MPopup flag_submenu
[] = {
9557 {tMessage
, {N_("Important"), lNormal
}, {IDM_MI_FLAGIMPORTANT
}},
9558 {tMessage
, {N_("New"), lNormal
}, {IDM_MI_FLAGNEW
}},
9559 {tMessage
, {N_("Answered"), lNormal
}, {IDM_MI_FLAGANSWERED
}},
9560 {tMessage
, {N_("Deleted"), lNormal
}, {IDM_MI_FLAGDELETED
}},
9565 flag_submenu
[0].label
.style
= (mc
&& mc
->flagged
) ? lChecked
: lNormal
;
9568 flag_submenu
[1].label
.style
= (mc
&& mc
->seen
) ? lNormal
: lChecked
;
9571 flag_submenu
[2].label
.style
= (mc
&& mc
->answered
) ? lChecked
: lNormal
;
9574 flag_submenu
[3].label
.style
= (mc
&& mc
->deleted
) ? lChecked
: lNormal
;
9576 return(flag_submenu
);
9579 #endif /* _WINDOWS */