2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2009 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 /*======================================================================
17 The meat and pototoes of mail processing here:
18 - initial command processing and dispatch
20 - capture address off incoming mail
21 - jump to specific numbered message
22 - open (broach) a new folder
23 - search message headers (where is) command
31 #include "flagmaint.h"
48 #include "../pith/state.h"
49 #include "../pith/msgno.h"
50 #include "../pith/store.h"
51 #include "../pith/thread.h"
52 #include "../pith/flag.h"
53 #include "../pith/sort.h"
54 #include "../pith/maillist.h"
55 #include "../pith/save.h"
56 #include "../pith/pipe.h"
57 #include "../pith/news.h"
58 #include "../pith/util.h"
59 #include "../pith/sequence.h"
60 #include "../pith/keyword.h"
61 #include "../pith/stream.h"
62 #include "../pith/mailcmd.h"
63 #include "../pith/hist.h"
64 #include "../pith/list.h"
65 #include "../pith/icache.h"
66 #include "../pith/busy.h"
67 #include "../pith/mimedesc.h"
68 #include "../pith/pattern.h"
69 #include "../pith/tempfile.h"
70 #include "../pith/search.h"
71 #include "../pith/margin.h"
73 #include "../pico/osdep/mswin.h"
79 int cmd_flag(struct pine
*, MSGNO_S
*, int);
80 int cmd_flag_prompt(struct pine
*, struct flag_screen
*, int);
81 void free_flag_table(struct flag_table
**);
82 int cmd_reply(struct pine
*, MSGNO_S
*, int, ACTION_S
*);
83 int cmd_forward(struct pine
*, MSGNO_S
*, int, ACTION_S
*);
84 int cmd_bounce(struct pine
*, MSGNO_S
*, int, ACTION_S
*);
85 int cmd_save(struct pine
*, MAILSTREAM
*, MSGNO_S
*, int, CmdWhere
);
86 void role_compose(struct pine
*);
87 int cmd_expunge(struct pine
*, MAILSTREAM
*, MSGNO_S
*, int);
88 int cmd_export(struct pine
*, MSGNO_S
*, int, int);
89 char *cmd_delete_action(struct pine
*, MSGNO_S
*, CmdWhere
);
90 char *cmd_delete_view(struct pine
*, MSGNO_S
*);
91 char *cmd_delete_index(struct pine
*, MSGNO_S
*);
92 long get_level(int, UCS
, SCROLL_S
*);
93 long closest_jump_target(long, MAILSTREAM
*, MSGNO_S
*, int, CmdWhere
, char *, size_t);
94 int update_folder_spec(char *, size_t, char *);
95 int cmd_print(struct pine
*, MSGNO_S
*, int, CmdWhere
);
96 int cmd_pipe(struct pine
*, MSGNO_S
*, int);
97 STORE_S
*list_mgmt_text(RFC2369_S
*, long);
98 void list_mgmt_screen(STORE_S
*);
99 int aggregate_select(struct pine
*, MSGNO_S
*, int, CmdWhere
);
100 int select_by_number(MAILSTREAM
*, MSGNO_S
*, SEARCHSET
**);
101 int select_by_thrd_number(MAILSTREAM
*, MSGNO_S
*, SEARCHSET
**);
102 int select_by_date(MAILSTREAM
*, MSGNO_S
*, long, SEARCHSET
**);
103 int select_by_text(MAILSTREAM
*, MSGNO_S
*, long, SEARCHSET
**);
104 int select_by_gm_content(MAILSTREAM
*, MSGNO_S
*, long, SEARCHSET
**);
105 int select_by_size(MAILSTREAM
*, SEARCHSET
**);
106 SEARCHSET
*visible_searchset(MAILSTREAM
*, MSGNO_S
*);
107 int select_by_status(MAILSTREAM
*, SEARCHSET
**);
108 int select_by_rule(MAILSTREAM
*, SEARCHSET
**);
109 int select_by_thread(MAILSTREAM
*, MSGNO_S
*, SEARCHSET
**);
110 char *choose_a_rule(int);
111 int select_by_keyword(MAILSTREAM
*, SEARCHSET
**);
112 char *choose_a_keyword(void);
113 int select_sort(struct pine
*, int, SortOrder
*, int *);
114 int print_index(struct pine
*, MSGNO_S
*, int);
117 * List of Select options used by apply_* functions...
119 static char *sel_pmt1
= N_("ALTER message selection : ");
120 ESCKEY_S sel_opts1
[] = {
121 /* TRANSLATORS: these are keymenu names for selecting. Broaden selection means
122 we will add more messages to the selection, Narrow selection means we will
123 remove some selections (like a logical AND instead of logical OR), and Flip
124 Selected means that all the messages that are currently selected become unselected,
125 and all the unselected messages become selected. */
126 {'a', 'a', "A", N_("unselect All")},
127 {'c', 'c', "C", NULL
},
128 {'b', 'b', "B", N_("Broaden selctn")},
129 {'n', 'n', "N", N_("Narrow selctn")},
130 {'f', 'f', "F", N_("Flip selected")},
131 {'r', 'r', "R", N_("Replace selctn")},
136 #define SEL_OPTS_THREAD 9 /* index number of "tHread" */
137 #define SEL_OPTS_THREAD_CH 'h'
138 #define SEL_OPTS_XGMEXT 10 /* index number of "boX" */
139 #define SEL_OPTS_XGMEXT_CH 'g'
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 currently 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")},
202 static ESCKEY_S sel_opts5
[] = {
203 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
204 means select the currently highlighted message; select by Number is by message
205 number; Status is by status of the message, for example the message might be
206 New or it might be Unseen or marked Important; Size has the Z upper case because
207 it is a Z command; Keyword is an alpine keyword that has been set by the user;
208 and Rule is an alpine rule */
209 {'a', 'a', "A", N_("select All")},
210 {'c', 'c', "C", N_("select Cur")},
211 {'n', 'n', "N", N_("Number")},
212 {'d', 'd', "D", N_("Date")},
213 {'t', 't', "T", N_("Text")},
214 {'s', 's', "S", N_("Status")},
215 {'z', 'z', "Z", N_("siZe")},
216 {'k', 'k', "K", N_("Keyword")},
217 {'r', 'r', "R", N_("Rule")},
218 {SEL_OPTS_THREAD_CH
, 'h', "H", N_("tHread")},
219 {SEL_OPTS_XGMEXT_CH
, 'g', "G", N_("GmSearch")},
227 static ESCKEY_S sel_opts6
[] = {
228 {'a', 'a', "A", N_("select All")},
229 /* TRANSLATORS: select currently highlighted message Thread */
230 {'c', 'c', "C", N_("select Curthrd")},
231 {'n', 'n', "N", N_("Number")},
232 {'d', 'd', "D", N_("Date")},
233 {'t', 't', "T", N_("Text")},
234 {'s', 's', "S", N_("Status")},
235 {'z', 'z', "Z", N_("siZe")},
236 {'k', 'k', "K", N_("Keyword")},
237 {'r', 'r', "R", N_("Rule")},
238 {SEL_OPTS_THREAD_CH
, 'h', "H", N_("tHread")},
239 {SEL_OPTS_XGMEXT_CH
, 'g', "G", N_("Gmail")},
248 static char *sel_flag
=
249 N_("Select New, Deleted, Answered, Forwarded, or Important messages ? ");
250 static char *sel_flag_not
=
251 N_("Select NOT New, NOT Deleted, NOT Answered, NOT Forwarded or NOT Important msgs ? ");
252 static ESCKEY_S sel_flag_opt
[] = {
253 /* TRANSLATORS: When selecting messages by message Status these are the
254 different types of Status you can select on. Is the message New, Recent,
255 and so on. Not means flip the meaning of the selection to the opposite
256 thing, so message is not New or not Important. */
257 {'n', 'n', "N", N_("New")},
258 {'*', '*', "*", N_("Important")},
259 {'d', 'd', "D", N_("Deleted")},
260 {'a', 'a', "A", N_("Answered")},
261 {'f', 'f', "F", N_("Forwarded")},
263 {'!', '!', "!", N_("Not")},
265 {'r', 'r', "R", N_("Recent")},
266 {'u', 'u', "U", N_("Unseen")},
271 static ESCKEY_S sel_date_opt
[] = {
273 /* TRANSLATORS: options when selecting messages by Date */
274 {ctrl('P'), 12, "^P", N_("Prev Day")},
275 {ctrl('N'), 13, "^N", N_("Next Day")},
276 {ctrl('X'), 11, "^X", N_("Cur Msg")},
277 {ctrl('W'), 14, "^W", N_("Toggle When")},
278 {KEY_UP
, 12, "", ""},
279 {KEY_DOWN
, 13, "", ""},
284 static char *sel_x_gm_ext
=
286 static char *sel_text
=
287 N_("Select based on To, From, Cc, Recip, Partic, Subject fields or All msg text ? ");
288 static char *sel_text_not
=
289 N_("Select based on NOT To, From, Cc, Recip, Partic, Subject or All msg text ? ");
290 static ESCKEY_S sel_text_opt
[] = {
291 /* TRANSLATORS: Select messages based on the text contained in the From line, or
292 the Subject line, and so on. */
293 {'f', 'f', "F", N_("From")},
294 {'s', 's', "S", N_("Subject")},
295 {'t', 't', "T", N_("To")},
296 {'a', 'a', "A", N_("All Text")},
297 {'c', 'c', "C", N_("Cc")},
298 {'!', '!', "!", N_("Not")},
299 {'r', 'r', "R", N_("Recipient")},
300 {'p', 'p', "P", N_("Participant")},
301 {'b', 'b', "B", N_("Body")},
302 {'h', 'h', "H", N_("Header")},
306 static ESCKEY_S choose_action
[] = {
307 {'c', 'c', "C", N_("Compose")},
308 {'r', 'r', "R", N_("Reply")},
309 {'f', 'f', "F", N_("Forward")},
310 {'b', 'b', "B", N_("Bounce")},
314 static char *select_num
=
315 N_("Enter comma-delimited list of numbers (dash between ranges): ");
317 static char *select_size_larger_msg
=
318 N_("Select messages with size larger than: ");
320 static char *select_size_smaller_msg
=
321 N_("Select messages with size smaller than: ");
323 static char *sel_size_larger
= N_("Larger");
324 static char *sel_size_smaller
= N_("Smaller");
325 static ESCKEY_S sel_size_opt
[] = {
327 {ctrl('W'), 14, "^W", NULL
},
331 static ESCKEY_S sel_key_opt
[] = {
333 {ctrl('T'), 14, "^T", N_("To List")},
335 {'!', '!', "!", N_("Not")},
339 static ESCKEY_S flag_text_opt
[] = {
340 /* TRANSLATORS: these are types of flags (markers) that the user can
341 set. For example, they can flag the message as an important message. */
342 {'n', 'n', "N", N_("New")},
343 {'*', '*', "*", N_("Important")},
344 {'d', 'd', "D", N_("Deleted")},
345 {'a', 'a', "A", N_("Answered")},
346 {'f', 'f', "F", N_("Forwarded")},
347 {'!', '!', "!", N_("Not")},
348 {ctrl('T'), 10, "^T", N_("To Flag Details")},
353 alpine_smime_confirm_save(char *email
)
357 snprintf(prompt
, sizeof(prompt
), _("Save certificate for <%s>"),
358 email
? email
: _("missing address"));
359 return want_to(prompt
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y';
363 alpine_get_password(char *prompt
, char *pass
, size_t len
)
365 int flags
= F_ON(F_QUELL_ASTERISKS
, ps_global
) ? OE_PASSWD_NOAST
: OE_PASSWD
;
367 flags
|= OE_DISALLOW_HELP
;
369 rv
= optionally_enter(pass
,
370 -(ps_global
->ttyo
? FOOTER_ROWS(ps_global
) : 3),
371 0, len
, prompt
, NULL
, NO_HELP
, &flags
);
372 if(rv
== 1) ps_global
->user_says_cancel
= 1;
377 smime_import_certificate(char *filename
, char *full_filename
, char *what
, size_t len
)
380 static HISTORY_S
*history
= NULL
;
381 static ESCKEY_S eopts
[] = {
382 {ctrl('T'), 10, "^T", N_("To Files")},
384 {-1, 0, NULL
, NULL
}};
386 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps_global
)){
387 eopts
[r
].ch
= ctrl('I');
389 eopts
[r
].name
= "TAB";
390 eopts
[r
].label
= N_("Complete");
396 full_filename
[0] = '\0';
398 r
= get_export_filename(ps_global
, filename
, NULL
, full_filename
,
399 len
, what
, "IMPORT", eopts
, NULL
,
400 -FOOTER_ROWS(ps_global
), GE_IS_IMPORT
, &history
);
406 /*----------------------------------------------------------------------
407 The giant switch on the commands for index and viewing
409 Input: command -- The command char/code
410 in_index -- flag indicating command is from index
411 orig_command -- The original command typed before pre-processing
412 Output: force_mailchk -- Set to tell caller to force call to new_mail().
416 Returns 1 if the message number or attachment to show changed
419 process_cmd(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
,
420 int command
, CmdWhere in_index
, int *force_mailchk
)
422 int question_line
, a_changed
, flags
= 0, ret
, j
;
424 long new_msgno
, del_count
, old_msgno
, i
;
426 char *newfolder
, prompt
[MAX_SCREEN_COLS
+1];
429 #if defined(DOS) && !defined(_WINDOWS)
430 extern long coreleft();
433 dprint((4, "\n - process_cmd(cmd=%d) -\n", command
));
435 question_line
= -FOOTER_ROWS(state
);
436 state
->mangled_screen
= 0;
437 state
->mangled_footer
= 0;
438 state
->mangled_header
= 0;
439 state
->next_screen
= SCREEN_FUN_NULL
;
440 old_msgno
= mn_get_cur(msgmap
);
445 /*------------- Help --------*/
448 * We're not using the h_mail_view portion of this right now because
449 * that call is being handled in scrolltool() before it gets
450 * here. Leave it in case we change how it works.
452 helper((in_index
== MsgIndx
)
456 : h_mail_thread_index
,
457 (in_index
== MsgIndx
)
458 ? _("HELP FOR MESSAGE INDEX")
460 ? _("HELP FOR MESSAGE TEXT")
461 : _("HELP FOR THREAD INDEX"),
463 dprint((4,"MAIL_CMD: did help command\n"));
464 state
->mangled_screen
= 1;
468 /*--------- Return to main menu ------------*/
470 state
->next_screen
= main_menu_screen
;
471 dprint((2,"MAIL_CMD: going back to main menu\n"));
475 /*------- View message text --------*/
478 if(any_messages(msgmap
, NULL
, "to View")){
479 state
->next_screen
= mail_view_screen
;
485 /*------- View attachment --------*/
487 state
->next_screen
= attachment_screen
;
488 dprint((2,"MAIL_CMD: going to attachment screen\n"));
492 /*---------- Previous message ----------*/
494 if(any_messages(msgmap
, NULL
, NULL
)){
495 if((i
= mn_get_cur(msgmap
)) > 1L){
496 mn_dec_cur(stream
, msgmap
,
497 (in_index
== View
&& THREADING()
498 && sp_viewing_a_thread(stream
))
501 ? MH_ANYTHD
: MH_NONE
);
502 if(i
== mn_get_cur(msgmap
)){
503 PINETHRD_S
*thrd
= NULL
, *topthrd
= NULL
;
505 if(THRD_INDX_ENABLED()){
506 mn_dec_cur(stream
, msgmap
, MH_ANYTHD
);
507 if(i
== mn_get_cur(msgmap
))
508 q_status_message1(SM_ORDER
, 0, 2,
509 _("Already on first %s in Zoomed Index"),
510 THRD_INDX() ? _("thread") : _("message"));
513 || F_ON(F_NEXT_THRD_WO_CONFIRM
, state
))
516 ret
= want_to(_("View previous thread"), 'y', 'x',
520 q_status_message(SM_ORDER
, 0, 2,
521 _("Viewing previous thread"));
522 new_msgno
= mn_get_cur(msgmap
);
523 mn_set_cur(msgmap
, i
);
524 if(unview_thread(state
, stream
, msgmap
)){
525 state
->next_screen
= mail_index_screen
;
526 state
->view_skipped_index
= 0;
527 state
->mangled_screen
= 1;
530 mn_set_cur(msgmap
, new_msgno
);
531 if(THRD_AUTO_VIEW() && in_index
== View
){
533 thrd
= fetch_thread(stream
,
536 if(count_lflags_in_thread(stream
, thrd
,
539 if(view_thread(state
, stream
, msgmap
, 1)){
540 if(current_index_state
)
541 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
543 state
->view_skipped_index
= 1;
544 command
= MC_VIEW_TEXT
;
551 if(THRD_AUTO_VIEW() && in_index
!= View
){
552 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, new_msgno
));
553 if(thrd
&& thrd
->top
)
554 topthrd
= fetch_thread(stream
, thrd
->top
);
557 j
= count_lflags_in_thread(stream
, topthrd
, msgmap
, MN_NONE
);
560 if(!THRD_AUTO_VIEW() || in_index
== View
|| j
!= 1){
561 if(view_thread(state
, stream
, msgmap
, 1)
562 && current_index_state
)
563 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
567 state
->next_screen
= SCREEN_FUN_NULL
;
570 mn_set_cur(msgmap
, i
); /* put it back */
574 q_status_message1(SM_ORDER
, 0, 2,
575 _("Already on first %s in Zoomed Index"),
576 THRD_INDX() ? _("thread") : _("message"));
583 && ((now
= time(0)) > state
->last_nextitem_forcechk
)){
585 /* check at most once a second */
586 state
->last_nextitem_forcechk
= now
;
589 q_status_message1(SM_ORDER
, 0, 1, _("Already on first %s"),
590 THRD_INDX() ? _("thread") : _("message"));
597 /*---------- Next Message ----------*/
599 if(mn_get_total(msgmap
) > 0L
600 && ((i
= mn_get_cur(msgmap
)) < mn_get_total(msgmap
))){
601 mn_inc_cur(stream
, msgmap
,
602 (in_index
== View
&& THREADING()
603 && sp_viewing_a_thread(stream
))
606 ? MH_ANYTHD
: MH_NONE
);
607 if(i
== mn_get_cur(msgmap
)){
608 PINETHRD_S
*thrd
, *topthrd
;
610 if(THRD_INDX_ENABLED()){
612 mn_inc_cur(stream
, msgmap
, MH_ANYTHD
);
614 if(i
== mn_get_cur(msgmap
)){
615 if(any_lflagged(msgmap
, MN_HIDE
))
616 any_messages(NULL
, "more", "in Zoomed Index");
622 || F_ON(F_NEXT_THRD_WO_CONFIRM
, state
))
625 ret
= want_to(_("View next thread"), 'y', 'x',
629 q_status_message(SM_ORDER
, 0, 2,
630 _("Viewing next thread"));
631 new_msgno
= mn_get_cur(msgmap
);
632 mn_set_cur(msgmap
, i
);
633 if(unview_thread(state
, stream
, msgmap
)){
634 state
->next_screen
= mail_index_screen
;
635 state
->view_skipped_index
= 0;
636 state
->mangled_screen
= 1;
639 mn_set_cur(msgmap
, new_msgno
);
640 if(THRD_AUTO_VIEW() && in_index
== View
){
642 thrd
= fetch_thread(stream
,
645 if(count_lflags_in_thread(stream
, thrd
,
648 if(view_thread(state
, stream
, msgmap
, 1)){
649 if(current_index_state
)
650 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
652 state
->view_skipped_index
= 1;
653 command
= MC_VIEW_TEXT
;
660 if(THRD_AUTO_VIEW() && in_index
!= View
){
661 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, new_msgno
));
662 if(thrd
&& thrd
->top
)
663 topthrd
= fetch_thread(stream
, thrd
->top
);
668 j
= count_lflags_in_thread(stream
, topthrd
, msgmap
, MN_NONE
);
671 if(!THRD_AUTO_VIEW() || in_index
== View
|| j
!= 1){
672 if(view_thread(state
, stream
, msgmap
, 1)
673 && current_index_state
)
674 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
678 state
->next_screen
= SCREEN_FUN_NULL
;
681 mn_set_cur(msgmap
, i
); /* put it back */
685 && (thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, i
)))
687 && get_lflag(stream
, NULL
, thrd
->rawno
, MN_COLL
)){
688 q_status_message(SM_ORDER
, 0, 2,
689 _("Expand collapsed thread to see more messages"));
692 any_messages(NULL
, "more", "in Zoomed Index");
700 || (state
->context_current
->use
& CNTXT_INCMNG
)){
701 char nextfolder
[MAXPATH
];
703 strncpy(nextfolder
, state
->cur_folder
, sizeof(nextfolder
));
704 nextfolder
[sizeof(nextfolder
)-1] = '\0';
705 if(next_folder(NULL
, nextfolder
, sizeof(nextfolder
), nextfolder
,
706 state
->context_current
, NULL
, NULL
))
707 strncpy(prompt
, _(". Press TAB for next folder."),
710 strncpy(prompt
, _(". No more folders to TAB to."),
713 prompt
[sizeof(prompt
)-1] = '\0';
716 any_messages(NULL
, (mn_get_total(msgmap
) > 0L) ? "more" : NULL
,
717 prompt
[0] ? prompt
: NULL
);
720 && ((now
= time(0)) > state
->last_nextitem_forcechk
)){
722 /* check at most once a second */
723 state
->last_nextitem_forcechk
= now
;
730 /*---------- Delete message ----------*/
732 (void) cmd_delete(state
, msgmap
, MCMD_NONE
,
733 (in_index
== View
) ? cmd_delete_view
: cmd_delete_index
);
737 /*---------- Undelete message ----------*/
739 (void) cmd_undelete(state
, msgmap
, MCMD_NONE
);
740 update_titlebar_status();
744 /*---------- Reply to message ----------*/
746 (void) cmd_reply(state
, msgmap
, MCMD_NONE
, NULL
);
750 /*---------- Forward message ----------*/
752 (void) cmd_forward(state
, msgmap
, MCMD_NONE
, NULL
);
756 /*---------- Quit pine ------------*/
758 state
->next_screen
= quit_screen
;
759 dprint((1,"MAIL_CMD: quit\n"));
763 /*---------- Compose message ----------*/
765 state
->prev_screen
= (in_index
== View
) ? mail_view_screen
767 compose_screen(state
);
768 state
->mangled_screen
= 1;
769 if (state
->next_screen
)
774 /*---------- Alt Compose message ----------*/
776 state
->prev_screen
= (in_index
== View
) ? mail_view_screen
779 if(state
->next_screen
)
785 /*--------- Folders menu ------------*/
787 state
->start_in_context
= 1;
789 /*--------- Top of Folders list menu ------------*/
790 case MC_COLLECTIONS
:
791 state
->next_screen
= folder_screen
;
792 dprint((2,"MAIL_CMD: going to folder/collection menu\n"));
796 /*---------- Open specific new folder ----------*/
798 tc
= (state
->context_last
&& !NEWS_TEST(state
->context_current
))
799 ? state
->context_last
: state
->context_current
;
801 newfolder
= broach_folder(question_line
, 1, ¬realinbox
, &tc
);
803 visit_folder(state
, newfolder
, tc
, NULL
, notrealinbox
? 0L : DB_INBOXWOCNTXT
);
810 /*------- Go to Index Screen ----------*/
812 state
->next_screen
= mail_index_screen
;
815 /*------- Skip to next interesting message -----------*/
821 * If we're in the thread index, start looking after this
822 * thread. We don't want to match something in the current
825 start
= mn_get_cur(msgmap
);
826 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
827 if(mn_get_revsort(msgmap
)){
828 /* if reversed, top of thread is last one before next thread */
829 if(thrd
&& thrd
->top
)
830 start
= mn_raw2m(msgmap
, thrd
->top
);
833 /* last msg of thread is at the ends of the branches/nexts */
835 start
= mn_raw2m(msgmap
, thrd
->rawno
);
837 thrd
= fetch_thread(stream
, thrd
->branch
);
839 thrd
= fetch_thread(stream
, thrd
->next
);
846 * Flags is 0 in this case because we want to not skip
847 * messages inside of threads so that we can find threads
848 * which have some unseen messages even though the top-level
849 * of the thread is already seen.
850 * If new_msgno ends up being a message which is not visible
851 * because it isn't at the top-level, the current message #
852 * will be adjusted below in adjust_cur.
855 new_msgno
= next_sorted_flagged((F_UNDEL
857 | ((F_ON(F_TAB_TO_NEW
,state
))
859 stream
, start
, &flags
);
861 else if(THREADING() && sp_viewing_a_thread(stream
)){
862 PINETHRD_S
*thrd
, *topthrd
= NULL
;
864 start
= mn_get_cur(msgmap
);
867 * Things are especially complicated when we're viewing_a_thread
868 * from the thread index. First we have to check within the
869 * current thread for a new message. If none is found, then
870 * we search in the next threads and offer to continue in
871 * them. Then we offer to go to the next folder.
873 flags
= NSF_SKIP_CHID
;
874 new_msgno
= next_sorted_flagged((F_UNDEL
876 | ((F_ON(F_TAB_TO_NEW
,state
))
878 stream
, start
, &flags
);
880 * If we found a match then we are done, that is another message
881 * in the current thread index. Otherwise, we have to look
884 if(!(flags
& NSF_FLAG_MATCH
)){
889 new_msgno
= next_sorted_flagged((F_UNDEL
891 | ((F_ON(F_TAB_TO_NEW
,
894 stream
, start
, &flags
);
896 * If we got a match, new_msgno is a message in
897 * a different thread from the one we are viewing.
899 if(flags
& NSF_FLAG_MATCH
){
900 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
,new_msgno
));
901 if(thrd
&& thrd
->top
)
902 topthrd
= fetch_thread(stream
, thrd
->top
);
904 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD
, state
)){
905 static ESCKEY_S next_opt
[] = {
906 {'y', 'y', "Y", N_("Yes")},
907 {'n', 'n', "N", N_("No")},
908 {TAB
, 'n', "Tab", N_("NextNew")},
913 snprintf(prompt
, sizeof(prompt
), _("View thread number %s? "),
914 topthrd
? comatose(topthrd
->thrdno
) : "?");
916 snprintf(prompt
, sizeof(prompt
),
917 _("View message in thread number %s? "),
918 topthrd
? comatose(topthrd
->thrdno
) : "?");
920 prompt
[sizeof(prompt
)-1] = '\0';
922 ret
= radio_buttons(prompt
, -FOOTER_ROWS(state
),
923 next_opt
, 'y', 'x', NO_HELP
,
934 if(unview_thread(state
, stream
, msgmap
)){
935 state
->next_screen
= mail_index_screen
;
936 state
->view_skipped_index
= 0;
937 state
->mangled_screen
= 1;
940 mn_set_cur(msgmap
, new_msgno
);
941 if(THRD_AUTO_VIEW()){
943 if(count_lflags_in_thread(stream
, topthrd
,
944 msgmap
, MN_NONE
) == 1){
945 if(view_thread(state
, stream
, msgmap
, 1)){
946 if(current_index_state
)
947 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
949 state
->view_skipped_index
= 1;
950 command
= MC_VIEW_TEXT
;
956 if(view_thread(state
, stream
, msgmap
, 1) && current_index_state
)
957 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
959 state
->next_screen
= SCREEN_FUN_NULL
;
962 else if(ret
== 'n' && topthrd
){
964 * skip to end of this thread and look starting
965 * in the next thread.
967 if(mn_get_revsort(msgmap
)){
969 * if reversed, top of thread is last one
972 start
= mn_raw2m(msgmap
, topthrd
->rawno
);
976 * last msg of thread is at the ends of
981 start
= mn_raw2m(msgmap
, thrd
->rawno
);
983 thrd
= fetch_thread(stream
, thrd
->branch
);
985 thrd
= fetch_thread(stream
, thrd
->next
);
1001 start
= mn_get_cur(msgmap
);
1004 * If we are on a collapsed thread, start looking after the
1005 * collapsed part, unless we are viewing the message.
1007 if(THREADING() && in_index
!= View
){
1012 rawno
= mn_m2raw(msgmap
, start
);
1013 thrd
= fetch_thread(stream
, rawno
);
1014 collapsed
= thrd
&& thrd
->next
1015 && get_lflag(stream
, NULL
, rawno
, MN_COLL
);
1018 if(mn_get_revsort(msgmap
)){
1019 if(thrd
&& thrd
->top
)
1020 start
= mn_raw2m(msgmap
, thrd
->top
);
1024 start
= mn_raw2m(msgmap
, thrd
->rawno
);
1026 thrd
= fetch_thread(stream
, thrd
->branch
);
1028 thrd
= fetch_thread(stream
, thrd
->next
);
1037 new_msgno
= next_sorted_flagged((F_UNDEL
1039 | ((F_ON(F_TAB_TO_NEW
,state
))
1041 stream
, start
, &flags
);
1045 * If there weren't any unread messages left, OR there
1046 * aren't any messages at all, we may want to offer to
1047 * go on to the next folder...
1049 if(flags
& NSF_FLAG_MATCH
){
1050 mn_set_cur(msgmap
, new_msgno
);
1051 if(in_index
!= View
)
1052 adjust_cur_to_visible(stream
, msgmap
);
1055 int in_inbox
= sp_flagged(stream
, SP_INBOX
);
1057 if(state
->context_current
1058 && ((NEWS_TEST(state
->context_current
)
1059 && context_isambig(state
->cur_folder
))
1060 || ((state
->context_current
->use
& CNTXT_INCMNG
)
1062 || folder_index(state
->cur_folder
,
1063 state
->context_current
,
1064 FI_FOLDER
) >= 0)))){
1065 char nextfolder
[MAXPATH
];
1066 MAILSTREAM
*nextstream
= NULL
;
1070 strncpy(nextfolder
, state
->cur_folder
, sizeof(nextfolder
));
1071 nextfolder
[sizeof(nextfolder
)-1] = '\0';
1073 if(!(next_folder(&nextstream
, nextfolder
, sizeof(nextfolder
), nextfolder
,
1074 state
->context_current
, &recent_cnt
,
1075 F_ON(F_TAB_NO_CONFIRM
,state
)
1076 ? NULL
: &did_cancel
))){
1078 static ESCKEY_S inbox_opt
[] = {
1079 {'y', 'y', "Y", N_("Yes")},
1080 {'n', 'n', "N", N_("No")},
1081 {TAB
, 'z', "Tab", N_("To Inbox")},
1085 if(F_ON(F_RET_INBOX_NO_CONFIRM
,state
))
1088 /* TRANSLATORS: this is a question, with some information followed
1089 by Return to INBOX? */
1090 if(state
->context_current
->use
&CNTXT_INCMNG
)
1091 snprintf(prompt
, sizeof(prompt
), _("No more incoming folders. Return to \"%s\"? "), state
->inbox_name
);
1093 snprintf(prompt
, sizeof(prompt
), _("No more news groups. Return to \"%s\"? "), state
->inbox_name
);
1095 ret
= radio_buttons(prompt
, -FOOTER_ROWS(state
),
1096 inbox_opt
, 'y', 'x',
1101 * 'z' is a synonym for 'y'. It is not 'y'
1102 * so that it isn't displayed as a default
1103 * action with square-brackets around it
1106 if(ret
== 'y' || ret
== 'z'){
1107 visit_folder(state
, state
->inbox_name
,
1108 state
->context_current
,
1109 NULL
, DB_INBOXWOCNTXT
);
1113 else if (did_cancel
)
1114 cmd_cancelled(NULL
);
1116 if(state
->context_current
->use
&CNTXT_INCMNG
)
1117 q_status_message(SM_ORDER
, 0, 2, _("No more incoming folders"));
1119 q_status_message(SM_ORDER
, 0, 2, _("No more news groups"));
1127 char *front
, type
[80], cnt
[CNTLEN
], fbuf
[MAX_SCREEN_COLS
/2+1];
1128 int rbspace
, avail
, need
, take_back
;
1132 * Incoming_folder_ or news_group_ or folder_ or group_
1134 * _(13 recent) or _(some recent) or nothing
1137 front
= "View next";
1139 (state
->context_current
->use
& CNTXT_INCMNG
)
1140 ? "Incoming folder" : "news group",
1142 type
[sizeof(type
)-1] = '\0';
1143 snprintf(cnt
, sizeof(cnt
), " (%.*s %s)", CNTLEN
-20,
1144 recent_cnt
? long2string(recent_cnt
) : "some",
1145 F_ON(F_TAB_USES_UNSEEN
, ps_global
)
1146 ? "unseen" : "recent");
1147 cnt
[sizeof(cnt
)-1] = '\0';
1150 * Space reserved for radio_buttons call.
1151 * If we make this 3 then radio_buttons won't mess
1152 * with the prompt. If we make it 2, then we get
1153 * one more character to use but radio_buttons will
1154 * cut off the last character of our prompt, which is
1155 * ok because it is a space.
1158 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
1160 need
= strlen(front
)+1 + strlen(type
)+1 +
1161 + strlen(nextfolder
)+2 + strlen(cnt
) +
1164 take_back
= strlen(type
);
1166 (state
->context_current
->use
& CNTXT_INCMNG
)
1167 ? "folder" : "group", sizeof(type
));
1168 take_back
-= strlen(type
);
1171 need
-= strlen(cnt
);
1175 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1176 snprintf(prompt
, sizeof(prompt
), "%.*s %.*s \"%.*s\"%.*s? ",
1177 (MAX_SCREEN_COLS
+1)/8, front
,
1178 (MAX_SCREEN_COLS
+1)/8, type
,
1179 (MAX_SCREEN_COLS
+1)/2,
1180 short_str(nextfolder
, fbuf
, sizeof(fbuf
),
1181 strlen(nextfolder
) -
1182 ((need
>avail
) ? (need
-avail
) : 0),
1184 (MAX_SCREEN_COLS
+1)/8, cnt
);
1185 prompt
[sizeof(prompt
)-1] = '\0';
1189 * When help gets added, this'll have to become
1190 * a loop like the rest...
1192 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD
, state
)){
1193 static ESCKEY_S next_opt
[] = {
1194 {'y', 'y', "Y", N_("Yes")},
1195 {'n', 'n', "N", N_("No")},
1196 {TAB
, 'n', "Tab", N_("NextNew")},
1200 ret
= radio_buttons(prompt
, -FOOTER_ROWS(state
),
1201 next_opt
, 'y', 'x', NO_HELP
,
1204 cmd_cancelled(NULL
);
1212 if(nextstream
&& sp_dead_stream(nextstream
))
1215 visit_folder(state
, nextfolder
,
1216 state
->context_current
, nextstream
,
1218 /* visit_folder takes care of nextstream */
1226 pine_mail_close(nextstream
);
1230 (mn_get_total(msgmap
) > 0L)
1231 ? IS_NEWS(stream
) ? "more undeleted" : "more new"
1241 /*------- Zoom -----------*/
1244 * Right now the way zoom is implemented is sort of silly.
1245 * There are two per-message flags where just one and a
1246 * global "zoom mode" flag to suppress messages from the index
1249 if(any_messages(msgmap
, NULL
, "to Zoom on")){
1250 if(unzoom_index(state
, stream
, msgmap
)){
1251 dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
1252 q_status_message(SM_ORDER
,0,2, _("Index Zoom Mode is now off"));
1254 else if((i
= zoom_index(state
, stream
, msgmap
, MN_SLCT
)) != 0){
1255 if(any_lflagged(msgmap
, MN_HIDE
)){
1256 dprint((4,"\n\n ---- Entering ZOOM mode ----\n"));
1257 q_status_message4(SM_ORDER
, 0, 2,
1258 _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"),
1259 THRD_INDX() ? "" : comatose(i
),
1260 THRD_INDX() ? "" : " ",
1261 THRD_INDX() ? _("threads") : _("message"),
1262 THRD_INDX() ? "" : plural(i
));
1265 q_status_message(SM_ORDER
, 0, 2,
1266 _("All messages selected, so not entering Index Zoom Mode"));
1269 any_messages(NULL
, "selected", "to Zoom on");
1275 /*---------- print message on paper ----------*/
1277 if(any_messages(msgmap
, NULL
, "to print"))
1278 (void) cmd_print(state
, msgmap
, MCMD_NONE
, in_index
);
1283 /*---------- Take Address ----------*/
1285 if(F_ON(F_ENABLE_ROLE_TAKE
, state
) ||
1286 any_messages(msgmap
, NULL
, "to Take address from"))
1287 (void) cmd_take_addr(state
, msgmap
, MCMD_NONE
);
1292 /*---------- Save Message ----------*/
1294 if(any_messages(msgmap
, NULL
, "to Save"))
1295 (void) cmd_save(state
, stream
, msgmap
, MCMD_NONE
, in_index
);
1300 /*---------- Export message ----------*/
1302 if(any_messages(msgmap
, NULL
, "to Export")){
1303 (void) cmd_export(state
, msgmap
, question_line
, MCMD_NONE
);
1304 state
->mangled_footer
= 1;
1310 /*---------- Expunge ----------*/
1312 (void) cmd_expunge(state
, stream
, msgmap
, MCMD_NONE
);
1316 /*------- Unexclude -----------*/
1318 if(!(IS_NEWS(stream
) && stream
->rdonly
)){
1319 q_status_message(SM_ORDER
, 0, 3,
1320 _("Unexclude not available for mail folders"));
1322 else if(any_lflagged(msgmap
, MN_EXLD
)){
1328 * Since excluded means "hidden deleted" and "killed",
1329 * the count should reflect the former.
1331 pgm
= mail_newsearchpgm();
1333 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
1334 for(i
= 1L, del_count
= 0L; i
<= stream
->nmsgs
; i
++)
1335 if((mc
= mail_elt(stream
, i
)) && mc
->searched
1336 && get_lflag(stream
, NULL
, i
, MN_EXLD
)
1337 && !(msgno_exceptions(stream
, i
, "0", &exbits
, FALSE
)
1338 && (exbits
& MSG_EX_FILTERED
)))
1342 state
->mangled_footer
= 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1343 snprintf(prompt
, sizeof(prompt
), "UNexclude %ld message%s in %.*s", del_count
,
1344 plural(del_count
), MAX_SCREEN_COLS
+1-45,
1345 pretty_fn(state
->cur_folder
));
1346 prompt
[sizeof(prompt
)-1] = '\0';
1347 if(F_ON(F_FULL_AUTO_EXPUNGE
, state
)
1348 || (F_ON(F_AUTO_EXPUNGE
, state
)
1349 && (state
->context_current
1350 && (state
->context_current
->use
& CNTXT_INCMNG
))
1351 && context_isambig(state
->cur_folder
))
1352 || want_to(prompt
, 'y', 0, NO_HELP
, WT_NORM
) == 'y'){
1353 long save_cur_rawno
;
1354 int were_viewing_a_thread
;
1356 save_cur_rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
1357 were_viewing_a_thread
= (THREADING()
1358 && sp_viewing_a_thread(stream
));
1360 if(msgno_include(stream
, msgmap
, MI_NONE
)){
1361 clear_index_cache(stream
, 0);
1363 if(stream
&& stream
->spare
)
1364 erase_threading_info(stream
, msgmap
);
1366 refresh_sort(stream
, msgmap
, SRT_NON
);
1369 if(were_viewing_a_thread
){
1370 if(save_cur_rawno
> 0L)
1371 mn_set_cur(msgmap
, mn_raw2m(msgmap
,save_cur_rawno
));
1373 if(view_thread(state
, stream
, msgmap
, 1) && current_index_state
)
1374 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
1377 if(save_cur_rawno
> 0L)
1378 mn_set_cur(msgmap
, mn_raw2m(msgmap
,save_cur_rawno
));
1380 state
->mangled_screen
= 1;
1381 q_status_message2(SM_ORDER
, 0, 4,
1382 "%s message%s UNexcluded",
1383 long2string(del_count
),
1386 if(in_index
!= View
)
1387 adjust_cur_to_visible(stream
, msgmap
);
1390 any_messages(NULL
, NULL
, "UNexcluded");
1393 any_messages(NULL
, "excluded", "to UNexclude");
1396 any_messages(NULL
, "excluded", "to UNexclude");
1401 /*------- Make Selection -----------*/
1403 if(any_messages(msgmap
, NULL
, "to Select")){
1404 if(aggregate_select(state
, msgmap
, question_line
, in_index
) == 0
1405 && (in_index
== MsgIndx
|| in_index
== ThrdIndx
)
1406 && F_ON(F_AUTO_ZOOM
, state
)
1407 && any_lflagged(msgmap
, MN_SLCT
) > 0L
1408 && !any_lflagged(msgmap
, MN_HIDE
))
1409 (void) zoom_index(state
, stream
, msgmap
, MN_SLCT
);
1415 /*------- Toggle Current Message Selection State -----------*/
1417 if(any_messages(msgmap
, NULL
, NULL
)){
1418 if((select_by_current(state
, msgmap
, in_index
)
1419 || (F_OFF(F_UNSELECT_WONT_ADVANCE
, state
)
1420 && !any_lflagged(msgmap
, MN_HIDE
)))
1421 && (i
= mn_get_cur(msgmap
)) < mn_get_total(msgmap
)){
1422 /* advance current */
1423 mn_inc_cur(stream
, msgmap
,
1424 (in_index
== View
&& THREADING()
1425 && sp_viewing_a_thread(stream
))
1427 : (in_index
== View
)
1428 ? MH_ANYTHD
: MH_NONE
);
1435 /*------- Apply command -----------*/
1437 if(any_messages(msgmap
, NULL
, NULL
)){
1438 if(any_lflagged(msgmap
, MN_SLCT
) > 0L){
1439 if(apply_command(state
, stream
, msgmap
, 0,
1440 AC_NONE
, question_line
)){
1441 if(F_ON(F_AUTO_UNSELECT
, state
)){
1442 agg_select_all(stream
, msgmap
, NULL
, 0);
1443 unzoom_index(state
, stream
, msgmap
);
1445 else if(F_ON(F_AUTO_UNZOOM
, state
))
1446 unzoom_index(state
, stream
, msgmap
);
1450 any_messages(NULL
, NULL
, "to Apply command to. Try \"Select\"");
1456 /*-------- Sort command -------*/
1459 int were_threading
= THREADING();
1460 SortOrder sort
= mn_get_sort(msgmap
);
1461 int rev
= mn_get_revsort(msgmap
);
1463 dprint((1,"MAIL_CMD: sort\n"));
1464 if(select_sort(state
, question_line
, &sort
, &rev
)){
1465 /* $ command reinitializes threading collapsed/expanded info */
1466 if(SORT_IS_THREADED(msgmap
) && !SEP_THRDINDX())
1467 erase_threading_info(stream
, msgmap
);
1469 if(ps_global
&& ps_global
->ttyo
){
1470 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
1471 ps_global
->mangled_footer
= 1;
1474 sort_folder(stream
, msgmap
, sort
, rev
, SRT_VRB
|SRT_MAN
);
1477 state
->mangled_footer
= 1;
1480 * We've changed whether we are threading or not so we need to
1481 * exit the index and come back in so that we switch between the
1482 * thread index and the regular index. Sort_folder will have
1483 * reset viewing_a_thread if necessary.
1486 && ((!were_threading
&& THREADING())
1487 || (were_threading
&& !THREADING()))){
1488 state
->next_screen
= mail_index_screen
;
1489 state
->mangled_screen
= 1;
1496 /*------- Toggle Full Headers -----------*/
1498 state
->full_header
++;
1499 if(state
->full_header
== 1){
1500 if(!(state
->quote_suppression_threshold
1501 && (state
->some_quoting_was_suppressed
|| in_index
!= View
)))
1502 state
->full_header
++;
1504 else if(state
->full_header
> 2)
1505 state
->full_header
= 0;
1507 switch(state
->full_header
){
1509 q_status_message(SM_ORDER
, 0, 3,
1510 _("Display of full headers is now off."));
1514 q_status_message1(SM_ORDER
, 0, 3,
1515 _("Quotes displayed, use %s to see full headers"),
1516 F_ON(F_USE_FK
, state
) ? "F9" : "H");
1520 q_status_message(SM_ORDER
, 0, 3,
1521 _("Display of full headers is now on."));
1536 /*------- Try to decrypt message -----------*/
1538 if(state
->smime
&& state
->smime
->need_passphrase
)
1539 smime_get_passphrase();
1545 smime_info_screen(state
);
1550 /*------- Bounce -----------*/
1552 (void) cmd_bounce(state
, msgmap
, MCMD_NONE
, NULL
);
1556 /*------- Flag -----------*/
1558 dprint((4, "\n - flag message -\n"));
1559 (void) cmd_flag(state
, msgmap
, MCMD_NONE
);
1563 /*------- Pipe message -----------*/
1565 (void) cmd_pipe(state
, msgmap
, MCMD_NONE
);
1569 /*--------- Default, unknown command ----------*/
1571 alpine_panic("Unexpected command case");
1575 return((a_changed
|| mn_get_cur(msgmap
) != old_msgno
) ? 1 : 0);
1580 /*----------------------------------------------------------------------
1581 Map some of the special characters into sensible strings for human
1583 c is a UCS-4 character!
1586 pretty_command(UCS c
)
1588 static char buf
[10];
1595 case ' ' : s
= "SPACE"; break;
1596 case '\033' : s
= "ESC"; break;
1597 case '\177' : s
= "DEL"; break;
1598 case ctrl('I') : s
= "TAB"; break;
1599 case ctrl('J') : s
= "LINEFEED"; break;
1600 case ctrl('M') : s
= "RETURN"; break;
1601 case ctrl('Q') : s
= "XON"; break;
1602 case ctrl('S') : s
= "XOFF"; break;
1603 case KEY_UP
: s
= "Up Arrow"; break;
1604 case KEY_DOWN
: s
= "Down Arrow"; break;
1605 case KEY_RIGHT
: s
= "Right Arrow"; break;
1606 case KEY_LEFT
: s
= "Left Arrow"; break;
1607 case KEY_PGUP
: s
= "Prev Page"; break;
1608 case KEY_PGDN
: s
= "Next Page"; break;
1609 case KEY_HOME
: s
= "Home"; break;
1610 case KEY_END
: s
= "End"; break;
1611 case KEY_DEL
: s
= "Delete"; break; /* Not necessary DEL! */
1612 case KEY_JUNK
: s
= "Junk!"; break;
1613 case BADESC
: s
= "Bad Esc"; break;
1614 case NO_OP_IDLE
: s
= "NO_OP_IDLE"; break;
1615 case NO_OP_COMMAND
: s
= "NO_OP_COMMAND"; break;
1616 case KEY_RESIZE
: s
= "KEY_RESIZE"; break;
1617 case KEY_UTF8
: s
= "KEY_UTF8"; break;
1618 case KEY_MOUSE
: s
= "KEY_MOUSE"; break;
1619 case KEY_SCRLUPL
: s
= "KEY_SCRLUPL"; break;
1620 case KEY_SCRLDNL
: s
= "KEY_SCRLDNL"; break;
1621 case KEY_SCRLTO
: s
= "KEY_SCRLTO"; break;
1622 case KEY_XTERM_MOUSE
: s
= "KEY_XTERM_MOUSE"; break;
1623 case KEY_DOUBLE_ESC
: s
= "KEY_DOUBLE_ESC"; break;
1624 case CTRL_KEY_UP
: s
= "Ctrl Up Arrow"; break;
1625 case CTRL_KEY_DOWN
: s
= "Ctrl Down Arrow"; break;
1626 case CTRL_KEY_RIGHT
: s
= "Ctrl Right Arrow"; break;
1627 case CTRL_KEY_LEFT
: s
= "Ctrl Left Arrow"; break;
1640 snprintf(s
= buf
, sizeof(buf
), "F%ld", (long) (c
- PF1
+ 1));
1644 if(c
< ' ' || (c
>= 0x80 && c
< 0xA0)){
1649 d
= (c
& 0x1f) + 'A' - 1;
1650 snprintf(s
= buf
, sizeof(buf
), "%c%c", c1
? '~' : '^', d
);
1653 memset(buf
, 0, sizeof(buf
));
1654 utf8_put((unsigned char *) buf
, (unsigned long) c
);
1664 /*----------------------------------------------------------------------
1665 Complain about bogus input
1667 Args: ch -- input command to complain about
1668 help -- string indicating where to get help
1672 bogus_command(UCS cmd
, char *help
)
1674 if(cmd
== ctrl('Q') || cmd
== ctrl('S'))
1675 q_status_message1(SM_ASYNC
, 0, 2,
1676 "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.",
1677 pretty_command(cmd
));
1678 else if(cmd
== KEY_JUNK
)
1679 q_status_message3(SM_ORDER
, 0, 2,
1680 "Invalid key pressed.%s%s%s",
1681 (help
) ? " Use " : "",
1683 (help
) ? " for help" : "");
1685 q_status_message4(SM_ORDER
, 0, 2,
1686 "Command \"%s\" not defined for this screen.%s%s%s",
1687 pretty_command(cmd
),
1688 (help
) ? " Use " : "",
1690 (help
) ? " for help" : "");
1695 bogus_utf8_command(char *cmd
, char *help
)
1697 q_status_message4(SM_ORDER
, 0, 2,
1698 "Command \"%s\" not defined for this screen.%s%s%s",
1700 (help
) ? " Use " : "",
1702 (help
) ? " for help" : "");
1706 /*----------------------------------------------------------------------
1707 Execute FLAG message command
1709 Args: state -- Various satate info
1710 msgmap -- map of c-client to local message numbers
1712 Result: with side effect of "current" message FLAG flag set or UNset
1716 cmd_flag(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
1718 char *flagit
, *seq
, *screen_text
[20], **exp
, **p
, *answer
= NULL
;
1719 char *keyword_array
[2];
1720 int user_defined_flags
= 0, mailbox_flags
= 0;
1721 int directly_to_maint_screen
= 0;
1722 int use_maint_screen
= F_ON(F_FLAG_SCREEN_DFLT
, ps_global
);
1723 long unflagged
, flagged
, flags
, rawno
;
1724 MESSAGECACHE
*mc
= NULL
;
1726 int i
, cnt
, is_set
, trouble
= 0, rv
= 0;
1728 struct flag_table
*fp
, *ftbl
= NULL
;
1729 struct flag_screen flag_screen
;
1730 static char *flag_screen_text1
[] = {
1731 N_(" Set desired flags for current message below. An 'X' means set"),
1732 N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."),
1736 static char *flag_screen_text2
[] = {
1737 N_(" Set desired flags below for selected messages. A '?' means to"),
1738 N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"),
1739 N_(" to unset it. Use the \"Return\" key to toggle, and choose"),
1740 N_(" \"Exit\" when finished."),
1744 static struct flag_table default_ftbl
[] = {
1745 {N_("Important"), h_flag_important
, F_FLAG
, 0, 0, NULL
, NULL
},
1746 {N_("New"), h_flag_new
, F_SEEN
, 0, 0, NULL
, NULL
},
1747 {N_("Answered"), h_flag_answered
, F_ANS
, 0, 0, NULL
, NULL
},
1748 {N_("Forwarded"), h_flag_forwarded
, F_FWD
, 0, 0, NULL
, NULL
},
1749 {N_("Deleted"), h_flag_deleted
, F_DEL
, 0, 0, NULL
, NULL
},
1750 {NULL
, NO_HELP
, 0, 0, 0, NULL
, NULL
}
1753 /* Only check for dead stream for now. Should check permanent flags
1756 if(!(any_messages(msgmap
, NULL
, "to Flag") && can_set_flag(state
, "flag", 1)))
1759 if(sp_io_error_on_stream(state
->mail_stream
)){
1760 sp_set_io_error_on_stream(state
->mail_stream
, 0);
1761 pine_mail_check(state
->mail_stream
); /* forces write */
1767 user_defined_flags
= 0;
1773 /* count how large ftbl will be */
1774 for(cnt
= 0; default_ftbl
[cnt
].name
; cnt
++)
1777 /* add user flags */
1778 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1779 if(!((kw
->nick
&& !strucmp(FORWARDED_FLAG
, kw
->nick
)) || (kw
->kw
&& !strucmp(FORWARDED_FLAG
, kw
->kw
)))){
1780 user_defined_flags
++;
1786 * Add mailbox flags that aren't user-defined flags.
1787 * Don't consider it if it matches either one of our defined
1788 * keywords or one of our defined nicknames for a keyword.
1790 for(i
= 0; stream_to_user_flag_name(state
->mail_stream
, i
); i
++){
1793 q
= stream_to_user_flag_name(state
->mail_stream
, i
);
1795 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1796 if((kw
->nick
&& !strucmp(kw
->nick
, q
)) || (kw
->kw
&& !strucmp(kw
->kw
, q
)))
1801 if(!kw
&& !(q
&& !strucmp(FORWARDED_FLAG
, q
))){
1807 cnt
+= (user_defined_flags
? 2 : 0) + (mailbox_flags
? 2 : 0);
1809 /* set up ftbl, first the system flags */
1810 ftbl
= (struct flag_table
*) fs_get((cnt
+1) * sizeof(*ftbl
));
1811 memset(ftbl
, 0, (cnt
+1) * sizeof(*ftbl
));
1812 for(i
= 0, fp
= ftbl
; default_ftbl
[i
].name
; i
++, fp
++){
1813 fp
->name
= cpystr(default_ftbl
[i
].name
);
1814 fp
->help
= default_ftbl
[i
].help
;
1815 fp
->flag
= default_ftbl
[i
].flag
;
1816 fp
->set
= default_ftbl
[i
].set
;
1817 fp
->ukn
= default_ftbl
[i
].ukn
;
1820 if(user_defined_flags
){
1821 fp
->flag
= F_COMMENT
;
1822 fp
->name
= cpystr("");
1824 fp
->flag
= F_COMMENT
;
1825 len
= strlen(_("User-defined Keywords from Setup/Config"));
1826 fp
->name
= (char *) fs_get((len
+6+6+1) * sizeof(char));
1827 snprintf(fp
->name
, len
+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config"));
1831 /* then the user-defined keywords */
1832 if(user_defined_flags
)
1833 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1834 if(!((kw
->nick
&& !strucmp(FORWARDED_FLAG
, kw
->nick
))
1835 || (kw
->kw
&& !strucmp(FORWARDED_FLAG
, kw
->kw
)))){
1836 fp
->name
= cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
1837 fp
->keyword
= cpystr(kw
->kw
? kw
->kw
: "");
1838 if(kw
->nick
&& kw
->kw
){
1841 l
= strlen(kw
->kw
)+2;
1842 fp
->comment
= (char *) fs_get((l
+1) * sizeof(char));
1843 snprintf(fp
->comment
, l
+1, "(%.*s)", (int) strlen(kw
->kw
), kw
->kw
);
1844 fp
->comment
[l
] = '\0';
1847 fp
->help
= h_flag_user_flag
;
1848 fp
->flag
= F_KEYWORD
;
1856 fp
->flag
= F_COMMENT
;
1857 fp
->name
= cpystr("");
1859 fp
->flag
= F_COMMENT
;
1860 len
= strlen(_("Other keywords in the mailbox that are not user-defined"));
1861 fp
->name
= (char *) fs_get((len
+6+6+1) * sizeof(char));
1862 snprintf(fp
->name
, len
+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined"));
1866 /* then the extra mailbox-defined keywords */
1868 for(i
= 0; stream_to_user_flag_name(state
->mail_stream
, i
); i
++){
1871 q
= stream_to_user_flag_name(state
->mail_stream
, i
);
1873 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1874 if((kw
->nick
&& !strucmp(kw
->nick
, q
)) || (kw
->kw
&& !strucmp(kw
->kw
, q
)))
1879 if(!kw
&& !(q
&& !strucmp(FORWARDED_FLAG
, q
))){
1880 fp
->name
= cpystr(q
);
1881 fp
->keyword
= cpystr(q
);
1882 fp
->help
= h_flag_user_flag
;
1883 fp
->flag
= F_KEYWORD
;
1890 flag_screen
.flag_table
= &ftbl
;
1891 flag_screen
.explanation
= screen_text
;
1893 if(MCMD_ISAGG(aopt
)){
1894 if(!pseudo_selected(ps_global
->mail_stream
, msgmap
)){
1895 free_flag_table(&ftbl
);
1899 exp
= flag_screen_text2
;
1900 for(fp
= ftbl
; fp
->name
; fp
++){
1901 fp
->set
= CMD_FLAG_UNKN
; /* set to unknown */
1905 else if(state
->mail_stream
1906 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
1907 && rawno
<= state
->mail_stream
->nmsgs
1908 && (mc
= mail_elt(state
->mail_stream
, rawno
))){
1909 exp
= flag_screen_text1
;
1910 for(fp
= &ftbl
[0]; fp
->name
; fp
++){
1912 if(fp
->flag
== F_KEYWORD
){
1913 /* see if this keyword is defined for this message */
1914 fp
->set
= CMD_FLAG_CLEAR
;
1915 if(user_flag_is_set(state
->mail_stream
,
1916 rawno
, fp
->keyword
))
1917 fp
->set
= CMD_FLAG_SET
;
1919 else if(fp
->flag
== F_FWD
){
1920 /* see if forwarded keyword is defined for this message */
1921 fp
->set
= CMD_FLAG_CLEAR
;
1922 if(user_flag_is_set(state
->mail_stream
,
1923 rawno
, FORWARDED_FLAG
))
1924 fp
->set
= CMD_FLAG_SET
;
1926 else if(fp
->flag
!= F_COMMENT
)
1927 fp
->set
= ((fp
->flag
== F_SEEN
&& !mc
->seen
)
1928 || (fp
->flag
== F_DEL
&& mc
->deleted
)
1929 || (fp
->flag
== F_FLAG
&& mc
->flagged
)
1930 || (fp
->flag
== F_ANS
&& mc
->answered
))
1931 ? CMD_FLAG_SET
: CMD_FLAG_CLEAR
;
1935 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
1936 _("Error accessing message data"));
1937 free_flag_table(&ftbl
);
1941 if(directly_to_maint_screen
)
1942 goto the_maint_screen
;
1945 if (mswin_usedialog ()) {
1946 if (!os_flagmsgdialog (&ftbl
[0])){
1947 free_flag_table(&ftbl
);
1954 int keyword_shortcut
= 0;
1956 if(!use_maint_screen
){
1958 * We're going to call cmd_flag_prompt(). We need
1959 * to decide whether or not to offer the keyword setting
1960 * shortcut. We'll offer it if the user has the feature
1961 * enabled AND there are some possible keywords that could
1964 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
)){
1965 for(fp
= &ftbl
[0]; fp
->name
&& !keyword_shortcut
; fp
++){
1966 if(fp
->flag
== F_KEYWORD
){
1970 first_char
= (fp
->name
&& fp
->name
[0])
1972 if(isascii(first_char
) && isupper(first_char
))
1973 first_char
= tolower((unsigned char) first_char
);
1975 for(tp
=flag_text_opt
; tp
->ch
!= -1; tp
++)
1976 if(tp
->ch
== first_char
)
1985 use_maint_screen
= !cmd_flag_prompt(state
, &flag_screen
,
1990 if(use_maint_screen
){
1991 for(p
= &screen_text
[0]; *exp
; p
++, exp
++)
1996 directly_to_maint_screen
= flag_maintenance_screen(state
, &flag_screen
);
2000 /* reacquire the elt pointer */
2001 mc
= (state
->mail_stream
2002 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
2003 && rawno
<= state
->mail_stream
->nmsgs
)
2004 ? mail_elt(state
->mail_stream
, rawno
) : NULL
;
2006 for(fp
= ftbl
; mc
&& fp
->name
; fp
++){
2010 if((!MCMD_ISAGG(aopt
) && fp
->set
!= !mc
->seen
)
2011 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2019 unflagged
= F_UNSEEN
;
2026 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->answered
)
2027 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2028 flagit
= "\\ANSWERED";
2031 unflagged
= F_UNANS
;
2042 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->deleted
)
2043 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2044 flagit
= "\\DELETED";
2047 unflagged
= F_UNDEL
;
2058 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->flagged
)
2059 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2060 flagit
= "\\FLAGGED";
2063 unflagged
= F_UNFLAG
;
2074 if(!MCMD_ISAGG(aopt
)){
2075 /* see if forwarded is defined for this message */
2076 is_set
= CMD_FLAG_CLEAR
;
2077 if(user_flag_is_set(state
->mail_stream
,
2078 mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
2080 is_set
= CMD_FLAG_SET
;
2083 if((!MCMD_ISAGG(aopt
) && fp
->set
!= is_set
)
2084 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2085 flagit
= FORWARDED_FLAG
;
2088 unflagged
= F_UNFWD
;
2099 if(!MCMD_ISAGG(aopt
)){
2100 /* see if this keyword is defined for this message */
2101 is_set
= CMD_FLAG_CLEAR
;
2102 if(user_flag_is_set(state
->mail_stream
,
2103 mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
2105 is_set
= CMD_FLAG_SET
;
2108 if((!MCMD_ISAGG(aopt
) && fp
->set
!= is_set
)
2109 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2110 flagit
= fp
->keyword
;
2111 keyword_array
[0] = fp
->keyword
;
2112 keyword_array
[1] = NULL
;
2115 unflagged
= F_UNKEYWORD
;
2119 unflagged
= F_KEYWORD
;
2131 && (seq
= currentf_sequence(state
->mail_stream
, msgmap
,
2132 unflagged
, &flagged
, unflagged
& F_DEL
,
2133 (fp
->flag
== F_KEYWORD
2134 && unflagged
== F_KEYWORD
)
2135 ? keyword_array
: NULL
,
2136 (fp
->flag
== F_KEYWORD
2137 && unflagged
== F_UNKEYWORD
)
2138 ? keyword_array
: NULL
))){
2140 * For user keywords, we may have to create the flag in
2141 * the folder if it doesn't already exist and we are setting
2142 * it (as opposed to clearing it). Mail_flag will
2143 * do that for us, but it's failure isn't very friendly
2144 * error-wise. So we try to make it a little smoother.
2146 if(!(fp
->flag
== F_KEYWORD
|| fp
->flag
== F_FWD
) || !fp
->set
2147 || ((i
=user_flag_index(state
->mail_stream
, flagit
)) >= 0
2149 mail_flag(state
->mail_stream
, seq
, flagit
, flags
);
2151 /* trouble, see if we can add the user flag */
2152 if(state
->mail_stream
->kwd_create
)
2153 mail_flag(state
->mail_stream
, seq
, flagit
, flags
);
2157 if(some_user_flags_defined(state
->mail_stream
))
2158 q_status_message(SM_ORDER
, 3, 4,
2159 _("No more keywords allowed in this folder!"));
2160 else if(fp
->flag
== F_FWD
)
2161 q_status_message(SM_ORDER
, 3, 4,
2162 _("Cannot add keywords for this folder so cannot set Forwarded flag"));
2164 q_status_message(SM_ORDER
, 3, 4,
2165 _("Cannot add keywords for this folder"));
2169 fs_give((void **) &seq
);
2170 if(flagged
&& !trouble
){
2171 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%slagged%s%s%s%s%s message%s%s \"%s\"",
2172 (fp
->set
) ? "F" : "Unf",
2173 MCMD_ISAGG(aopt
) ? " " : "",
2174 MCMD_ISAGG(aopt
) ? long2string(flagged
) : "",
2175 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2177 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2178 ? comatose(mn_total_cur(msgmap
)) : "",
2179 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2181 MCMD_ISAGG(aopt
) ? plural(flagged
) : " ",
2182 MCMD_ISAGG(aopt
) ? "" : long2string(mn_get_cur(msgmap
)),
2184 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2185 q_status_message(SM_ORDER
, 0, 2, answer
= tmp_20k_buf
);
2191 free_flag_table(&ftbl
);
2193 if(directly_to_maint_screen
)
2196 if(MCMD_ISAGG(aopt
))
2197 restore_selected(msgmap
);
2200 q_status_message(SM_ORDER
, 0, 2, _("No flags changed."));
2206 /*----------------------------------------------------------------------
2207 Offer concise status line flag prompt
2209 Args: state -- Various satate info
2210 flags -- flags to offer setting
2212 Result: TRUE if flag to set specified in flags struct or FALSE otw
2216 cmd_flag_prompt(struct pine
*state
, struct flag_screen
*flags
, int allow_keyword_shortcuts
)
2218 int r
, setflag
= 1, first_char
;
2219 struct flag_table
*fp
;
2221 char *ftext
, *ftext_not
;
2222 static char *flag_text
=
2223 N_("Flag New, Deleted, Answered, Forwarded or Important ? ");
2224 static char *flag_text_ak
=
2225 N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? ");
2226 static char *flag_text_not
=
2227 N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? ");
2228 static char *flag_text_ak_not
=
2229 N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? ");
2231 if(allow_keyword_shortcuts
){
2233 ESCKEY_S
*dp
, *sp
, *tp
;
2235 for(sp
=flag_text_opt
; sp
->ch
!= -1; sp
++)
2238 for(fp
=(flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++)
2239 if(fp
->flag
== F_KEYWORD
)
2242 /* set up an ESCKEY_S list which includes invisible keys for keywords */
2243 ek
= (ESCKEY_S
*) fs_get((cnt
+ 1) * sizeof(*ek
));
2244 memset(ek
, 0, (cnt
+1) * sizeof(*ek
));
2245 for(dp
=ek
, sp
=flag_text_opt
; sp
->ch
!= -1; sp
++, dp
++)
2248 for(fp
=(flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++){
2249 if(fp
->flag
== F_KEYWORD
){
2250 first_char
= (fp
->name
&& fp
->name
[0]) ? fp
->name
[0] : -2;
2251 if(isascii(first_char
) && isupper(first_char
))
2252 first_char
= tolower((unsigned char) first_char
);
2255 * Check to see if an earlier keyword in the list, or one of
2256 * the builtin system letters already uses this character.
2257 * If so, the first one wins.
2259 for(tp
=ek
; tp
->ch
!= 0; tp
++)
2260 if(tp
->ch
== first_char
)
2264 continue; /* skip it, already used that char */
2266 dp
->ch
= first_char
;
2267 dp
->rval
= first_char
;
2275 ftext
= _(flag_text_ak
);
2276 ftext_not
= _(flag_text_ak_not
);
2280 ftext
= _(flag_text
);
2281 ftext_not
= _(flag_text_not
);
2285 r
= radio_buttons(setflag
? ftext
: ftext_not
,
2286 -FOOTER_ROWS(state
), ek
, '*', SEQ_EXCEPTION
-1,
2287 NO_HELP
, RB_NORM
| RB_SEQ_SENSITIVE
);
2289 * It is SEQ_EXCEPTION-1 just so that it is some value that isn't
2290 * being used otherwise. The keywords use up all the possible
2291 * letters, so a negative number is good, but it has to be different
2292 * from other negative return values.
2294 if(r
== SEQ_EXCEPTION
-1) /* ol'cancelrooney */
2296 else if(r
== 10) /* return and goto flag screen */
2298 else if(r
== '!') /* flip intention */
2304 for(fp
= (flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++){
2305 if(r
== 'n' || r
== '*' || r
== 'd' || r
== 'a' || r
== 'f'){
2306 if((r
== 'n' && fp
->flag
== F_SEEN
)
2307 || (r
== '*' && fp
->flag
== F_FLAG
)
2308 || (r
== 'd' && fp
->flag
== F_DEL
)
2309 || (r
== 'f' && fp
->flag
== F_FWD
)
2310 || (r
== 'a' && fp
->flag
== F_ANS
)){
2311 fp
->set
= setflag
? CMD_FLAG_SET
: CMD_FLAG_CLEAR
;
2315 else if(allow_keyword_shortcuts
&& fp
->flag
== F_KEYWORD
){
2316 first_char
= (fp
->name
&& fp
->name
[0]) ? fp
->name
[0] : -2;
2317 if(isascii(first_char
) && isupper(first_char
))
2318 first_char
= tolower((unsigned char) first_char
);
2320 if(r
== first_char
){
2321 fp
->set
= setflag
? CMD_FLAG_SET
: CMD_FLAG_CLEAR
;
2327 if(ek
!= flag_text_opt
)
2328 fs_give((void **) &ek
);
2335 * (*ft) is an array of flag_table entries.
2338 free_flag_table(struct flag_table
**ft
)
2340 struct flag_table
*fp
;
2343 for(fp
= (*ft
); fp
->name
; fp
++){
2345 fs_give((void **) &fp
->name
);
2348 fs_give((void **) &fp
->keyword
);
2351 fs_give((void **) &fp
->comment
);
2354 fs_give((void **) ft
);
2359 /*----------------------------------------------------------------------
2360 Execute REPLY message command
2362 Args: state -- Various satate info
2363 msgmap -- map of c-client to local message numbers
2365 Result: reply sent or not
2369 cmd_reply(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, ACTION_S
*role
)
2373 if(any_messages(msgmap
, NULL
, "to Reply to")){
2374 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
2377 rv
= reply(state
, role
);
2379 if(MCMD_ISAGG(aopt
))
2380 restore_selected(msgmap
);
2382 state
->mangled_screen
= 1;
2389 /*----------------------------------------------------------------------
2390 Execute FORWARD message command
2392 Args: state -- Various satate info
2393 msgmap -- map of c-client to local message numbers
2395 Result: selected message[s] forwarded or not
2399 cmd_forward(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, ACTION_S
*role
)
2403 if(any_messages(msgmap
, NULL
, "to Forward")){
2404 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
2407 rv
= forward(state
, role
);
2409 if(MCMD_ISAGG(aopt
))
2410 restore_selected(msgmap
);
2412 state
->mangled_screen
= 1;
2419 /*----------------------------------------------------------------------
2420 Execute BOUNCE message command
2422 Args: state -- Various satate info
2423 msgmap -- map of c-client to local message numbers
2424 aopt -- aggregate options
2426 Result: selected message[s] bounced or not
2430 cmd_bounce(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, ACTION_S
*role
)
2434 if(any_messages(msgmap
, NULL
, "to Bounce")){
2436 if(MCMD_ISAGG(aopt
)){
2437 if(!pseudo_selected(state
->mail_stream
, msgmap
))
2440 else if((i
= any_lflagged(msgmap
, MN_SLCT
)) > 0
2441 && get_lflag(state
->mail_stream
, msgmap
,
2442 mn_m2raw(msgmap
, mn_get_cur(msgmap
)), MN_SLCT
) == 0)
2443 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
2444 _("WARNING: non-selected message is being bounced!"));
2446 && get_lflag(state
->mail_stream
, msgmap
,
2447 mn_m2raw(msgmap
, mn_get_cur(msgmap
)), MN_SLCT
))
2448 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
2449 _("WARNING: not bouncing all selected messages!"));
2451 rv
= bounce(state
, role
);
2453 if(MCMD_ISAGG(aopt
))
2454 restore_selected(msgmap
);
2456 state
->mangled_footer
= 1;
2463 /*----------------------------------------------------------------------
2464 Execute save message command: prompt for folder and call function to save
2466 Args: screen_line -- Line on the screen to prompt on
2467 message -- The MESSAGECACHE entry of message to save
2469 Result: The folder lister can be called to make selection; mangled screen set
2471 This does the prompting for the folder name to save to, possibly calling
2472 up the folder display for selection of folder by user.
2475 cmd_save(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int aopt
, CmdWhere in_index
)
2477 char newfolder
[MAILTMPLEN
], nmsgs
[32], *nick
;
2478 int we_cancel
= 0, rv
= 0, save_flags
;
2480 CONTEXT_S
*cntxt
= NULL
;
2482 SaveDel del
= DontAsk
;
2483 SavePreserveOrder pre
= DontAskPreserve
;
2485 dprint((4, "\n - saving message -\n"));
2487 if(MCMD_ISAGG(aopt
) && !pseudo_selected(stream
, msgmap
))
2490 state
->ugly_consider_advancing_bit
= 0;
2491 if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM
, state
)
2492 && msgno_any_deletedparts(stream
, msgmap
)
2493 && want_to(_("Saved copy will NOT include entire message! Continue"),
2494 'y', 'n', NO_HELP
, WT_FLUSH_IN
| WT_SEQ_SENSITIVE
) != 'y'){
2495 restore_selected(msgmap
);
2496 cmd_cancelled("Save message");
2500 raw
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
2502 if(mn_total_cur(msgmap
) <= 1L){
2503 snprintf(nmsgs
, sizeof(nmsgs
), "Msg #%ld ", mn_get_cur(msgmap
));
2504 nmsgs
[sizeof(nmsgs
)-1] = '\0';
2505 e
= pine_mail_fetchstructure(stream
, raw
, NULL
);
2507 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
2508 _("Can't save message. Error accessing folder"));
2509 restore_selected(msgmap
);
2514 snprintf(nmsgs
, sizeof(nmsgs
), "%s msgs ", comatose(mn_total_cur(msgmap
)));
2515 nmsgs
[sizeof(nmsgs
)-1] = '\0';
2517 /* e is just used to get a default save folder from the first msg */
2518 e
= pine_mail_fetchstructure(stream
,
2519 mn_m2raw(msgmap
, mn_first_cur(msgmap
)),
2523 del
= (!READONLY_FOLDER(stream
) && F_OFF(F_SAVE_WONT_DELETE
, ps_global
))
2525 if(mn_total_cur(msgmap
) > 1L)
2526 pre
= F_OFF(F_AGG_SEQ_COPY
, ps_global
) ? Preserve
: NoPreserve
;
2528 pre
= DontAskPreserve
;
2530 if(save_prompt(state
, &cntxt
, newfolder
, sizeof(newfolder
), nmsgs
, e
,
2531 raw
, NULL
, &del
, &pre
)){
2533 if(ps_global
&& ps_global
->ttyo
){
2534 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
2535 ps_global
->mangled_footer
= 1;
2538 save_flags
= SV_FIX_DELS
;
2539 if(pre
== RetPreserve
)
2540 save_flags
|= SV_PRESERVE
;
2542 save_flags
|= SV_DELETE
;
2543 if(ps_global
->context_list
== cntxt
&& !strucmp(newfolder
, ps_global
->inbox_name
))
2544 save_flags
|= SV_INBOXWOCNTXT
;
2546 we_cancel
= busy_cue(_("Saving"), NULL
, 1);
2547 i
= save(state
, stream
, cntxt
, newfolder
, msgmap
, save_flags
);
2551 if(i
== mn_total_cur(msgmap
)){
2553 if(mn_total_cur(msgmap
) <= 1L){
2554 int need
, avail
= ps_global
->ttyo
->screen_cols
- 2;
2555 int lennick
, lenfldr
;
2558 && ps_global
->context_list
->next
2559 && context_isambig(newfolder
)){
2560 lennick
= MIN(strlen(cntxt
->nickname
), 500);
2561 lenfldr
= MIN(strlen(newfolder
), 500);
2562 need
= 27 + strlen(long2string(mn_get_cur(msgmap
))) +
2566 need
-= MIN(lennick
-10, need
-avail
);
2567 lennick
-= MIN(lennick
-10, need
-avail
);
2570 if(need
> avail
&& lenfldr
> 10)
2571 lenfldr
-= MIN(lenfldr
-10, need
-avail
);
2574 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
2575 "Message %s copied to \"%s\" in <%s>",
2576 long2string(mn_get_cur(msgmap
)),
2577 short_str(newfolder
, (char *)(tmp_20k_buf
+1000), 1000,
2579 short_str(cntxt
->nickname
,
2580 (char *)(tmp_20k_buf
+2000), 1000,
2582 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2584 else if((nick
=folder_is_target_of_nick(newfolder
, cntxt
)) != NULL
){
2585 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
2586 "Message %s copied to \"%s\"",
2587 long2string(mn_get_cur(msgmap
)),
2589 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2592 char *f
= " folder";
2594 lenfldr
= MIN(strlen(newfolder
), 500);
2595 need
= 28 + strlen(long2string(mn_get_cur(msgmap
))) +
2600 if(need
> avail
&& lenfldr
> 10)
2601 lenfldr
-= MIN(lenfldr
-10, need
-avail
);
2604 snprintf(tmp_20k_buf
,SIZEOF_20KBUF
,
2605 "Message %s copied to%s \"%s\"",
2606 long2string(mn_get_cur(msgmap
)), f
,
2607 short_str(newfolder
, (char *)(tmp_20k_buf
+1000), 1000,
2609 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2613 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%s messages saved",
2614 comatose(mn_total_cur(msgmap
)));
2615 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2619 strncat(tmp_20k_buf
, " and deleted", SIZEOF_20KBUF
-strlen(tmp_20k_buf
)-1);
2620 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2623 q_status_message(SM_ORDER
, 0, 3, tmp_20k_buf
);
2625 if(!MCMD_ISAGG(aopt
) && F_ON(F_SAVE_ADVANCES
, state
)){
2626 if(sp_new_mail_count(stream
))
2627 process_filter_patterns(stream
, msgmap
,
2628 sp_new_mail_count(stream
));
2630 mn_inc_cur(stream
, msgmap
,
2631 (in_index
== View
&& THREADING()
2632 && sp_viewing_a_thread(stream
))
2634 : (in_index
== View
)
2635 ? MH_ANYTHD
: MH_NONE
);
2638 state
->ugly_consider_advancing_bit
= 1;
2642 if(MCMD_ISAGG(aopt
)) /* straighten out fakes */
2643 restore_selected(msgmap
);
2646 update_titlebar_status(); /* make sure they see change */
2653 role_compose(struct pine
*state
)
2657 if(F_ON(F_ALT_ROLE_MENU
, state
) && mn_get_total(state
->msgmap
) > 0L){
2660 if(nonempty_patterns(ROLE_DO_ROLES
, &pstate
) && first_pattern(&pstate
)){
2661 action
= radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
2662 -FOOTER_ROWS(state
), choose_action
,
2663 'c', 'x', h_role_compose
, RB_NORM
);
2666 q_status_message(SM_ORDER
, 0, 3,
2667 _("No roles available. Use Setup/Rules to add roles."));
2674 if(action
== 'c' || action
== 'r' || action
== 'f' || action
== 'b'){
2675 ACTION_S
*role
= NULL
;
2676 void (*prev_screen
)(struct pine
*) = NULL
, (*redraw
)(void) = NULL
;
2678 redraw
= state
->redrawer
;
2679 state
->redrawer
= NULL
;
2680 prev_screen
= state
->prev_screen
;
2682 state
->next_screen
= SCREEN_FUN_NULL
;
2685 if(role_select_screen(state
, &role
,
2686 action
== 'f' ? MC_FORWARD
:
2687 action
== 'r' ? MC_REPLY
:
2688 action
== 'b' ? MC_BOUNCE
:
2689 action
== 'c' ? MC_COMPOSE
: 0) < 0){
2690 cmd_cancelled(action
== 'f' ? _("Forward") :
2691 action
== 'r' ? _("Reply") :
2692 action
== 'c' ? _("Composition") : _("Bounce"));
2693 state
->next_screen
= prev_screen
;
2694 state
->redrawer
= redraw
;
2695 state
->mangled_screen
= 1;
2699 * If default role was selected (NULL) we need to make
2700 * up a role which won't do anything, but will cause
2701 * compose_mail to think there's already a role so that
2702 * it won't try to confirm the default.
2705 role
= combine_inherited_role(role
);
2707 role
= (ACTION_S
*) fs_get(sizeof(*role
));
2708 memset((void *) role
, 0, sizeof(*role
));
2709 role
->nick
= cpystr("Default Role");
2712 state
->redrawer
= NULL
;
2715 compose_mail(NULL
, NULL
, role
, NULL
, NULL
);
2719 (void) reply(state
, role
);
2723 (void) forward(state
, role
);
2727 (void) bounce(state
, role
);
2734 state
->next_screen
= prev_screen
;
2735 state
->redrawer
= redraw
;
2736 state
->mangled_screen
= 1;
2742 /*----------------------------------------------------------------------
2743 Do the dirty work of prompting the user for a folder name
2746 nfldr should be a buffer at least MAILTMPLEN long
2747 dela -- a pointer to a SaveDel. If it is
2748 DontAsk on input, don't offer Delete prompt
2749 Del on input, offer Delete command with default of Delete
2751 RetDel and RetNoDel are return values
2758 save_prompt(struct pine
*state
, CONTEXT_S
**cntxt
, char *nfldr
, size_t len_nfldr
,
2759 char *nmsgs
, ENVELOPE
*env
, long int rawmsgno
, char *section
,
2760 SaveDel
*dela
, SavePreserveOrder
*prea
)
2762 int rc
, ku
= -1, n
, flags
, last_rc
= 0, saveable_count
= 0, done
= 0;
2763 int delindex
= 0, preindex
= 0, r
;
2764 char prompt
[6*MAX_SCREEN_COLS
+1], *p
, expanded
[MAILTMPLEN
];
2765 char *buf
= tmp_20k_buf
;
2769 SaveDel del
= DontAsk
;
2770 SavePreserveOrder pre
= DontAskPreserve
;
2771 char *deltext
= NULL
;
2772 static HISTORY_S
*history
= NULL
;
2777 alpine_panic("no context ptr in save_prompt");
2779 init_hist(&history
, HISTSIZE
);
2781 if(!(folder
= save_get_default(state
, env
, rawmsgno
, section
, cntxt
)))
2782 return(0); /* message expunged! */
2784 /* how many context's can be saved to... */
2785 for(tc
= state
->context_list
; tc
; tc
= tc
->next
)
2789 /* set up extra command option keys */
2791 ekey
[rc
].ch
= ctrl('T');
2793 ekey
[rc
].name
= "^T";
2794 /* TRANSLATORS: command means go to Folders list */
2795 ekey
[rc
++].label
= N_("To Fldrs");
2797 if(saveable_count
> 1){
2798 ekey
[rc
].ch
= ctrl('P');
2800 ekey
[rc
].name
= "^P";
2801 ekey
[rc
++].label
= N_("Prev Collection");
2803 ekey
[rc
].ch
= ctrl('N');
2805 ekey
[rc
].name
= "^N";
2806 ekey
[rc
++].label
= N_("Next Collection");
2809 if(F_ON(F_ENABLE_TAB_COMPLETE
, ps_global
)){
2812 ekey
[rc
].name
= "TAB";
2813 /* TRANSLATORS: command asks alpine to complete the name when tab is typed */
2814 ekey
[rc
++].label
= N_("Complete");
2817 if(F_ON(F_ENABLE_SUB_LISTS
, ps_global
)){
2818 ekey
[rc
].ch
= ctrl('X');
2820 ekey
[rc
].name
= "^X";
2821 /* TRANSLATORS: list all the matches */
2822 ekey
[rc
++].label
= N_("ListMatches");
2825 if(dela
&& (*dela
== NoDel
|| *dela
== Del
)){
2826 ekey
[rc
].ch
= ctrl('R');
2828 ekey
[rc
].name
= "^R";
2833 if(prea
&& (*prea
== NoPreserve
|| *prea
== Preserve
)){
2834 ekey
[rc
].ch
= ctrl('W');
2836 ekey
[rc
].name
= "^W";
2841 if(saveable_count
> 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
2842 ekey
[rc
].ch
= KEY_UP
;
2845 ekey
[rc
++].label
= "";
2847 ekey
[rc
].ch
= KEY_DOWN
;
2850 ekey
[rc
++].label
= "";
2852 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
2853 ekey
[rc
].ch
= KEY_UP
;
2857 ekey
[rc
++].label
= "";
2859 ekey
[rc
].ch
= KEY_DOWN
;
2862 ekey
[rc
++].label
= "";
2870 /* only show collection number if more than one available */
2871 if(ps_global
->context_list
->next
)
2872 snprintf(prompt
, sizeof(prompt
), "SAVE%s %sto folder in <%s> [%s] : ",
2873 deltext
? deltext
: "",
2875 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2876 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2878 snprintf(prompt
, sizeof(prompt
), "SAVE%s %sto folder [%s] : ",
2879 deltext
? deltext
: "",
2880 nmsgs
, strsquish(buf
, SIZEOF_20KBUF
, folder
, 40));
2882 prompt
[sizeof(prompt
)-1] = '\0';
2885 * If the prompt won't fit, try removing deltext.
2887 if(state
->ttyo
->screen_cols
< strlen(prompt
) + MIN_OPT_ENT_WIDTH
&& deltext
){
2888 if(ps_global
->context_list
->next
)
2889 snprintf(prompt
, sizeof(prompt
), "SAVE %sto folder in <%s> [%s] : ",
2891 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2892 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2894 snprintf(prompt
, sizeof(prompt
), "SAVE %sto folder [%s] : ",
2895 nmsgs
, strsquish(buf
, SIZEOF_20KBUF
, folder
, 40));
2897 prompt
[sizeof(prompt
)-1] = '\0';
2901 * If the prompt still won't fit, remove the extra info contained
2904 if(state
->ttyo
->screen_cols
< strlen(prompt
) + MIN_OPT_ENT_WIDTH
&& *nmsgs
){
2905 if(ps_global
->context_list
->next
)
2906 snprintf(prompt
, sizeof(prompt
), "SAVE to folder in <%s> [%s] : ",
2907 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2908 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2910 snprintf(prompt
, sizeof(prompt
), "SAVE to folder [%s] : ",
2911 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2913 prompt
[sizeof(prompt
)-1] = '\0';
2917 ekey
[delindex
].label
= (del
== NoDel
) ? "Delete" : "No Delete";
2919 if(pre
!= DontAskPreserve
)
2920 ekey
[preindex
].label
= (pre
== NoPreserve
) ? "Preserve Order" : "Any Order";
2923 if(items_in_hist(history
) > 1){
2924 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
2925 ekey
[ku
].label
= HISTORY_KEYLABEL
;
2926 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
2927 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
2931 ekey
[ku
].label
= "";
2932 ekey
[ku
+1].name
= "";
2933 ekey
[ku
+1].label
= "";
2937 flags
= OE_APPEND_CURRENT
| OE_SEQ_SENSITIVE
;
2938 rc
= optionally_enter(nfldr
, -FOOTER_ROWS(state
), 0, len_nfldr
,
2939 prompt
, ekey
, help
, &flags
);
2943 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
2944 _("Error reading folder name"));
2949 removing_trailing_white_space(nfldr
);
2950 removing_leading_white_space(nfldr
);
2952 if(*nfldr
|| *folder
){
2953 char *p
, *name
, *fullname
= NULL
;
2954 int exists
, breakout
= FALSE
;
2957 strncpy(nfldr
, folder
, len_nfldr
-1);
2958 nfldr
[len_nfldr
-1] = '\0';
2961 save_hist(history
, nfldr
, 0, (void *) *cntxt
);
2963 if(!(name
= folder_is_nick(nfldr
, FOLDERS(*cntxt
), 0)))
2966 if(update_folder_spec(expanded
, sizeof(expanded
), name
)){
2967 strncpy(name
= nfldr
, expanded
, len_nfldr
-1);
2968 nfldr
[len_nfldr
-1] = '\0';
2971 exists
= folder_name_exists(*cntxt
, name
, &fullname
);
2973 if(exists
== FEX_ERROR
){
2974 q_status_message1(SM_ORDER
, 0, 3,
2975 _("Problem accessing folder \"%s\""),
2981 strncpy(name
= nfldr
, fullname
, len_nfldr
-1);
2982 nfldr
[len_nfldr
-1] = '\0';
2983 fs_give((void **) &fullname
);
2987 if(exists
& FEX_ISFILE
){
2990 else if((exists
& FEX_ISDIR
)){
2991 char tmp
[MAILTMPLEN
];
2995 CONTEXT_S
*fake_context
;
2998 strncpy(tmp
, name
, sizeof(tmp
));
2999 tmp
[sizeof(tmp
)-2-1] = '\0';
3000 if(tmp
[(l
= strlen(tmp
)) - 1] != tc
->dir
->delim
){
3001 if(l
< sizeof(tmp
)){
3002 tmp
[l
] = tc
->dir
->delim
;
3003 strncpy(&tmp
[l
+1], "[]", sizeof(tmp
)-(l
+1));
3007 strncat(tmp
, "[]", sizeof(tmp
)-strlen(tmp
)-1);
3009 tmp
[sizeof(tmp
)-1] = '\0';
3011 fake_context
= new_context(tmp
, 0);
3013 done
= display_folder_list(&fake_context
, nfldr
,
3014 1, folders_for_save
);
3015 free_context(&fake_context
);
3017 else if(tc
->dir
->delim
3018 && (p
= strrindex(name
, tc
->dir
->delim
))
3020 done
= display_folder_list(cntxt
, nfldr
,
3021 1, folders_for_save
);
3023 q_status_message1(SM_ORDER
, 3, 3,
3024 _("\"%s\" is a directory"), name
);
3026 && !((p
=strrindex(name
, tc
->dir
->delim
)) && *(p
+1) == '\0')){
3027 strncpy(tmp
, name
, sizeof(tmp
));
3028 tmp
[sizeof(tmp
)-1] = '\0';
3029 snprintf(nfldr
, len_nfldr
, "%s%c", tmp
, tc
->dir
->delim
);
3033 else{ /* Doesn't exist, create! */
3034 if((fullname
= folder_as_breakout(*cntxt
, name
)) != NULL
){
3035 strncpy(name
= nfldr
, fullname
, len_nfldr
-1);
3036 nfldr
[len_nfldr
-1] = '\0';
3037 fs_give((void **) &fullname
);
3040 switch(create_for_save(*cntxt
, name
)){
3041 case 1 : /* success */
3044 case 0 : /* error */
3045 case -1 : /* declined */
3054 /* else fall thru like they cancelled */
3057 cmd_cancelled("Save message");
3062 r
= display_folder_list(cntxt
, nfldr
, 0, folders_for_save
);
3070 helper(h_save
, _("HELP FOR SAVE"), HLPD_SIMPLE
);
3071 ps_global
->mangled_screen
= 1;
3074 case 4 : /* redraw */
3077 case 10 : /* previous collection */
3078 for(tc
= (*cntxt
)->prev
; tc
; tc
= tc
->prev
)
3085 for(tc2
= (tc
= (*cntxt
))->next
; tc2
; tc2
= tc2
->next
)
3093 case 11 : /* next collection */
3097 if(((*cntxt
) = (*cntxt
)->next
) == NULL
)
3098 (*cntxt
) = ps_global
->context_list
;
3099 while(NEWS_TEST(*cntxt
) && (*cntxt
) != tc
);
3102 case 12 : /* file name completion */
3103 if(!folder_complete(*cntxt
, nfldr
, len_nfldr
, &n
)){
3104 if(n
&& last_rc
== 12 && !(flags
& OE_USER_MODIFIED
)){
3105 r
= display_folder_list(cntxt
, nfldr
, 1, folders_for_save
);
3107 done
++; /* bingo! */
3109 rc
= 0; /* burn last_rc */
3117 case 14 : /* file name completion */
3118 r
= display_folder_list(cntxt
, nfldr
, 2, folders_for_save
);
3120 done
++; /* bingo! */
3122 rc
= 0; /* burn last_rc */
3126 case 15 : /* Delete / No Delete */
3127 del
= (del
== NoDel
) ? Del
: NoDel
;
3128 deltext
= (del
== NoDel
) ? " (no delete)" : " (and delete)";
3131 case 16 : /* Preserve Order or not */
3132 pre
= (pre
== NoPreserve
) ? Preserve
: NoPreserve
;
3136 if((p
= get_prev_hist(history
, nfldr
, 0, (void *) *cntxt
)) != NULL
){
3137 strncpy(nfldr
, p
, len_nfldr
);
3138 nfldr
[len_nfldr
-1] = '\0';
3139 if(history
->hist
[history
->curindex
])
3140 *cntxt
= (CONTEXT_S
*) history
->hist
[history
->curindex
]->cntxt
;
3148 if((p
= get_next_hist(history
, nfldr
, 0, (void *) *cntxt
)) != NULL
){
3149 strncpy(nfldr
, p
, len_nfldr
);
3150 nfldr
[len_nfldr
-1] = '\0';
3151 if(history
->hist
[history
->curindex
])
3152 *cntxt
= (CONTEXT_S
*) history
->hist
[history
->curindex
]->cntxt
;
3160 alpine_panic("Unhandled case");
3167 ps_global
->mangled_footer
= 1;
3173 strncpy(ps_global
->last_save_folder
, nfldr
, sizeof(ps_global
->last_save_folder
)-1);
3174 ps_global
->last_save_folder
[sizeof(ps_global
->last_save_folder
)-1] = '\0';
3176 ps_global
->last_save_context
= *cntxt
;
3179 strncpy(nfldr
, folder
, len_nfldr
-1);
3180 nfldr
[len_nfldr
-1] = '\0';
3183 /* nickname? Copy real name to nfldr */
3185 && context_isambig(nfldr
)
3186 && (p
= folder_is_nick(nfldr
, FOLDERS(*cntxt
), 0))){
3187 strncpy(nfldr
, p
, len_nfldr
-1);
3188 nfldr
[len_nfldr
-1] = '\0';
3191 if(dela
&& (*dela
== NoDel
|| *dela
== Del
))
3192 *dela
= (del
== NoDel
) ? RetNoDel
: RetDel
;
3194 if(prea
&& (*prea
== NoPreserve
|| *prea
== Preserve
))
3195 *prea
= (pre
== NoPreserve
) ? RetNoPreserve
: RetPreserve
;
3201 /*----------------------------------------------------------------------
3202 Prompt user before implicitly creating a folder for saving
3204 Args: context - context to create folder in
3205 folder - folder name to create
3207 Result: 1 on proceed, -1 on decline, 0 on error
3211 create_for_save_prompt(CONTEXT_S
*context
, char *folder
, int sequence_sensitive
)
3213 if(context
&& ps_global
->context_list
->next
&& context_isambig(folder
)){
3214 if(context
->use
& CNTXT_INCMNG
){
3215 snprintf(tmp_20k_buf
,SIZEOF_20KBUF
,
3216 _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
3217 folder
, (strlen(folder
) > 15) ? "..." : "");
3218 q_status_message(SM_ORDER
, 3, 3, tmp_20k_buf
);
3219 return(0); /* error */
3222 snprintf(tmp_20k_buf
,SIZEOF_20KBUF
,
3223 _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
3224 folder
, (strlen(folder
) > 15) ? "..." : "",
3226 (strlen(context
->nickname
) > 15) ? "..." : "");
3229 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
3230 _("Folder \"%.40s%s\" doesn't exist. Create"),
3231 folder
, strlen(folder
) > 40 ? "..." : "");
3233 if(want_to(tmp_20k_buf
, 'y', 'n',
3234 NO_HELP
, (sequence_sensitive
) ? WT_SEQ_SENSITIVE
: WT_NORM
) != 'y'){
3235 cmd_cancelled("Save message");
3244 /*----------------------------------------------------------------------
3245 Expunge messages from current folder
3247 Args: state -- pointer to struct holding a bunch of pine state
3248 msgmap -- table mapping msg nums to c-client sequence nums
3249 qline -- screen line to ask questions on
3250 agg -- boolean indicating we're to operate on aggregate set
3255 cmd_expunge(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int agg
)
3257 long del_count
, prefilter_del_count
= 0;
3258 int we_cancel
= 0, rv
= 0;
3259 char prompt
[MAX_SCREEN_COLS
+1];
3261 COLOR_PAIR
*lastc
= NULL
;
3263 dprint((2, "\n - expunge -\n"));
3267 sequence
= MCMD_ISAGG(agg
) ? selected_sequence(stream
, msgmap
, NULL
, 0) : NULL
;
3269 if(MCMD_ISAGG(agg
)){
3272 for(i
= 1L; i
<= stream
->nmsgs
; i
++){
3273 if((mc
= mail_elt(stream
, i
)) != NULL
3274 && mc
->sequence
&& mc
->deleted
)
3278 q_status_message(SM_ORDER
, 0, 4,
3279 _("No selected messages are deleted"));
3283 if(!any_messages(msgmap
, NULL
, "to Expunge"))
3287 if(IS_NEWS(stream
) && stream
->rdonly
){
3288 if(!MCMD_ISAGG(agg
))
3289 del_count
= count_flagged(stream
, F_DEL
);
3291 state
->mangled_footer
= 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3292 snprintf(prompt
, sizeof(prompt
), "Exclude %ld message%s from %.*s", del_count
,
3293 plural(del_count
), MAX_SCREEN_COLS
+1-40,
3294 pretty_fn(state
->cur_folder
));
3295 prompt
[sizeof(prompt
)-1] = '\0';
3296 if(F_ON(F_FULL_AUTO_EXPUNGE
, state
)
3297 || (F_ON(F_AUTO_EXPUNGE
, state
)
3298 && (state
->context_current
3299 && (state
->context_current
->use
& CNTXT_INCMNG
))
3300 && context_isambig(state
->cur_folder
))
3301 || want_to(prompt
, 'y', 0, NO_HELP
, WT_NORM
) == 'y'){
3303 if(F_ON(F_NEWS_CROSS_DELETE
, state
))
3304 cross_delete_crossposts(stream
);
3306 msgno_exclude_deleted(stream
, msgmap
, sequence
);
3307 clear_index_cache(stream
, 0);
3310 * This is kind of surprising at first. For most sort
3311 * orders, if the whole set is sorted, then any subset
3312 * is also sorted. Not so for threaded sorts.
3314 if(SORT_IS_THREADED(msgmap
))
3315 refresh_sort(stream
, msgmap
, SRT_NON
);
3317 state
->mangled_body
= 1;
3318 state
->mangled_header
= 1;
3319 q_status_message2(SM_ORDER
, 0, 4,
3320 "%s message%s excluded",
3321 long2string(del_count
),
3325 any_messages(NULL
, NULL
, "Excluded");
3328 any_messages(NULL
, "deleted", "to Exclude");
3332 else if(READONLY_FOLDER(stream
)){
3333 q_status_message(SM_ORDER
, 0, 4,
3334 _("Can't expunge. Folder is read-only"));
3338 if(!MCMD_ISAGG(agg
)){
3339 prefilter_del_count
= count_flagged(stream
, F_DEL
|F_NOFILT
);
3340 mail_expunge_prefilter(stream
, MI_NONE
);
3341 del_count
= count_flagged(stream
, F_DEL
|F_NOFILT
);
3346 unsigned char *fname
= folder_name_decoded((unsigned char *)state
->cur_folder
);
3347 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3348 snprintf(prompt
, sizeof(prompt
), "Expunge %ld message%s from %.*s", del_count
,
3349 plural(del_count
), MAX_SCREEN_COLS
+1-40,
3350 pretty_fn((char *) fname
));
3351 if(fname
) fs_give((void **)&fname
);
3352 prompt
[sizeof(prompt
)-1] = '\0';
3353 state
->mangled_footer
= 1;
3355 if(F_ON(F_FULL_AUTO_EXPUNGE
, state
)
3356 || (F_ON(F_AUTO_EXPUNGE
, state
)
3357 && ((!strucmp(state
->cur_folder
,state
->inbox_name
))
3358 || (state
->context_current
->use
& CNTXT_INCMNG
))
3359 && context_isambig(state
->cur_folder
))
3360 || (ret
=want_to(prompt
, 'y', 0, NO_HELP
, WT_NORM
)) == 'y')
3364 cmd_cancelled("Expunge");
3370 dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
3371 mn_get_total(msgmap
), mn_get_cur(msgmap
), del_count
));
3373 lastc
= pico_set_colors(state
->VAR_TITLE_FORE_COLOR
,
3374 state
->VAR_TITLE_BACK_COLOR
,
3377 PutLine0(0, 0, "**"); /* indicate delay */
3380 (void)pico_set_colorp(lastc
, PSC_NONE
);
3381 free_color_pair(&lastc
);
3384 MoveCursor(state
->ttyo
->screen_rows
-FOOTER_ROWS(state
), 0);
3387 we_cancel
= busy_cue(_("Expunging"), NULL
, 1);
3389 if(cmd_expunge_work(stream
, msgmap
, sequence
))
3390 state
->mangled_body
= 1;
3393 fs_give((void **)&sequence
);
3396 cancel_busy_cue((sp_expunge_count(stream
) > 0) ? 0 : -1);
3398 lastc
= pico_set_colors(state
->VAR_TITLE_FORE_COLOR
,
3399 state
->VAR_TITLE_BACK_COLOR
,
3401 PutLine0(0, 0, " "); /* indicate delay's over */
3404 (void)pico_set_colorp(lastc
, PSC_NONE
);
3405 free_color_pair(&lastc
);
3410 if(sp_expunge_count(stream
) > 0){
3412 * This is kind of surprising at first. For most sort
3413 * orders, if the whole set is sorted, then any subset
3414 * is also sorted. Not so for threaded sorts.
3416 if(SORT_IS_THREADED(msgmap
))
3417 refresh_sort(stream
, msgmap
, SRT_NON
);
3421 unsigned char *fname
= folder_name_decoded((unsigned char *)state
->cur_folder
);
3422 q_status_message1(SM_ORDER
, 0, 3,
3423 _("No messages expunged from folder \"%s\""),
3424 pretty_fn((char *) fname
));
3425 if(fname
) fs_give((void **)&fname
);
3427 else if(!prefilter_del_count
)
3428 q_status_message(SM_ORDER
, 0, 3,
3429 _("No messages marked deleted. No messages expunged."));
3435 /*----------------------------------------------------------------------
3436 Expunge_and_close callback to prompt user for confirmation
3438 Args: stream -- folder's stream
3439 folder -- name of folder containing folders
3440 deleted -- number of del'd msgs
3442 Result: 'y' to continue with expunge
3445 expunge_prompt(MAILSTREAM
*stream
, char *folder
, long int deleted
)
3449 char prompt_b
[MAX_SCREEN_COLS
+1], temp
[MAILTMPLEN
+1], buff
[MAX_SCREEN_COLS
+1];
3450 char *short_folder_name
;
3455 snprintf(temp
, sizeof(temp
), "%ld", deleted
);
3456 charcnt
= strlen(temp
)+1;
3459 max_folder
= MAX(1,MAXPROMPT
- (36+charcnt
));
3460 strncpy(temp
, folder
, sizeof(temp
));
3461 temp
[sizeof(temp
)-1] = '\0';
3462 short_folder_name
= short_str(temp
,buff
,sizeof(buff
),max_folder
,FrontDots
);
3465 snprintf(prompt_b
, sizeof(prompt_b
),
3466 "Delete %s%ld message%s from \"%s\"",
3467 (deleted
> 1L) ? "all " : "", deleted
,
3468 plural(deleted
), short_folder_name
);
3470 snprintf(prompt_b
, sizeof(prompt_b
),
3471 "Expunge the %ld deleted message%s from \"%s\"",
3472 deleted
, deleted
== 1 ? "" : "s",
3475 return(want_to(prompt_b
, 'y', 0, NO_HELP
, WT_NORM
));
3480 * This is used with multiple append saves. Call it once before
3481 * the series of appends with SSCP_INIT and once after all are
3482 * done with SSCP_END. In between, it is called automatically
3483 * from save_fetch_append or save_fetch_append_cb when we need
3484 * to ask the user if he or she wants to continue even though
3485 * announced message size doesn't match the actual message size.
3486 * As of 2008-02-29 the gmail IMAP server has these size mismatches
3487 * on a regular basis even though the data is ok.
3490 save_size_changed_prompt(long msgno
, int flags
)
3494 static int remember_the_yes
= 0;
3495 static int possible_corruption
= 0;
3496 static ESCKEY_S save_size_opts
[] = {
3497 {'y', 'y', "Y", "Yes"},
3498 {'n', 'n', "N", "No"},
3499 {'a', 'a', "A", "yes to All"},
3503 if(F_ON(F_IGNORE_SIZE
, ps_global
))
3506 if(flags
& SSCP_INIT
|| flags
& SSCP_END
){
3507 if(flags
& SSCP_END
&& possible_corruption
)
3508 q_status_message(SM_ORDER
, 3, 3, "There is possible data corruption, check the results");
3510 remember_the_yes
= 0;
3511 possible_corruption
= 0;
3512 ps_global
->noshow_error
= 0;
3513 ps_global
->noshow_warn
= 0;
3517 if(remember_the_yes
){
3518 snprintf(prompt
, sizeof(prompt
),
3519 "Message to save shrank! (msg # %ld): Continuing", msgno
);
3520 q_status_message(SM_ORDER
, 0, 3, prompt
);
3521 display_message('x');
3522 return(remember_the_yes
);
3525 snprintf(prompt
, sizeof(prompt
),
3526 "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno
);
3527 ret
= radio_buttons(prompt
, -FOOTER_ROWS(ps_global
), save_size_opts
,
3528 'n', 0, h_save_size_changed
, RB_NORM
|RB_NO_NEWMAIL
);
3532 remember_the_yes
= 'y';
3533 possible_corruption
++;
3534 return(remember_the_yes
);
3537 possible_corruption
++;
3541 possible_corruption
= 0;
3542 ps_global
->noshow_error
= 1;
3543 ps_global
->noshow_warn
= 1;
3551 /*----------------------------------------------------------------------
3552 Expunge_and_close callback that happens once the decision to expunge
3553 and close has been made and before expunging and closing begins
3556 Args: stream -- folder's stream
3557 folder -- name of folder containing folders
3558 deleted -- number of del'd msgs
3560 Result: 'y' to continue with expunge
3563 expunge_and_close_begins(int flags
, char *folder
)
3565 if(!(flags
& EC_NO_CLOSE
)){
3566 unsigned char *fname
= folder_name_decoded((unsigned char *)folder
);
3567 q_status_message1(SM_INFO
, 0, 1, "Closing \"%.200s\"...", (char *) fname
);
3568 flush_status_messages(1);
3569 if(fname
) fs_give((void **)&fname
);
3574 /*----------------------------------------------------------------------
3575 Export a message to a plain file in users home directory
3577 Args: state -- pointer to struct holding a bunch of pine state
3578 msgmap -- table mapping msg nums to c-client sequence nums
3579 qline -- screen line to ask questions on
3580 agg -- boolean indicating we're to operate on aggregate set
3585 cmd_export(struct pine
*state
, MSGNO_S
*msgmap
, int qline
, int aopt
)
3587 char filename
[MAXPATH
+1], full_filename
[MAXPATH
+1], *err
;
3589 int r
, leading_nl
, failure
= 0, orig_errno
= 0, rflags
= GER_NONE
;
3590 int flags
= GE_IS_EXPORT
| GE_SEQ_SENSITIVE
, rv
= 0;
3594 long i
, count
= 0L, start_of_append
= 0, rawno
;
3597 struct variable
*vars
= state
? ps_global
->vars
: NULL
;
3598 ESCKEY_S export_opts
[5];
3599 static HISTORY_S
*history
= NULL
;
3601 if(ps_global
->restricted
){
3602 q_status_message(SM_ORDER
, 0, 3,
3603 "Alpine demo can't export messages to files");
3607 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
3610 export_opts
[i
= 0].ch
= ctrl('T');
3611 export_opts
[i
].rval
= 10;
3612 export_opts
[i
].name
= "^T";
3613 export_opts
[i
++].label
= N_("To Files");
3615 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3616 if(ps_global
->VAR_DOWNLOAD_CMD
&& ps_global
->VAR_DOWNLOAD_CMD
[0]){
3617 export_opts
[i
].ch
= ctrl('V');
3618 export_opts
[i
].rval
= 12;
3619 export_opts
[i
].name
= "^V";
3620 /* TRANSLATORS: this is an abbreviation for Download Messages */
3621 export_opts
[i
++].label
= N_("Downld Msg");
3623 #endif /* !(DOS || MAC) */
3625 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps_global
)){
3626 export_opts
[i
].ch
= ctrl('I');
3627 export_opts
[i
].rval
= 11;
3628 export_opts
[i
].name
= "TAB";
3629 export_opts
[i
++].label
= N_("Complete");
3633 /* Commented out since it's not yet support! */
3634 if(F_ON(F_ENABLE_SUB_LISTS
,ps_global
)){
3635 export_opts
[i
].ch
= ctrl('X');
3636 export_opts
[i
].rval
= 14;
3637 export_opts
[i
].name
= "^X";
3638 export_opts
[i
++].label
= N_("ListMatches");
3643 * If message has attachments, add a toggle that will allow the user
3644 * to save all of the attachments to a single directory, using the
3645 * names provided with the attachments or part names. What we'll do is
3646 * export the message as usual, and then export the attachments into
3647 * a subdirectory that did not exist before. The subdir will be named
3648 * something based on the name of the file being saved to, but a
3651 if(!MCMD_ISAGG(aopt
)
3652 && state
->mail_stream
3653 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
3654 && rawno
<= state
->mail_stream
->nmsgs
3655 && (env
= pine_mail_fetchstructure(state
->mail_stream
, rawno
, &b
))
3657 && b
->type
== TYPEMULTIPART
3659 && strucmp(b
->subtype
, "ALTERNATIVE") != 0){
3662 part
= b
->nested
.part
; /* 1st part */
3663 if(part
&& part
->next
)
3664 flags
|= GE_ALLPARTS
;
3667 export_opts
[i
].ch
= -1;
3670 if(mn_total_cur(msgmap
) <= 1L){
3671 snprintf(nmsgs
, sizeof(nmsgs
), "Msg #%ld", mn_get_cur(msgmap
));
3672 nmsgs
[sizeof(nmsgs
)-1] = '\0';
3675 snprintf(nmsgs
, sizeof(nmsgs
), "%s messages", comatose(mn_total_cur(msgmap
)));
3676 nmsgs
[sizeof(nmsgs
)-1] = '\0';
3679 r
= get_export_filename(state
, filename
, NULL
, full_filename
,
3680 sizeof(filename
), nmsgs
, "EXPORT",
3681 export_opts
, &rflags
, qline
, flags
, &history
);
3686 cmd_cancelled("Export message");
3690 q_status_message1(SM_ORDER
, 0, 2,
3691 _("Can't export to file outside of %s"),
3698 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3699 else if(r
== 12){ /* Download */
3700 char cmd
[MAXPATH
], *tfp
= NULL
;
3706 if(ps_global
->restricted
){
3707 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3708 "Download disallowed in restricted mode");
3713 tfp
= temp_nam(NULL
, "pd");
3714 build_updown_cmd(cmd
, sizeof(cmd
), ps_global
->VAR_DOWNLOAD_CMD_PREFIX
,
3715 ps_global
->VAR_DOWNLOAD_CMD
, tfp
);
3716 dprint((1, "Download cmd called: \"%s\"\n", cmd
));
3717 if((so
= so_get(FileStar
, tfp
, WRITE_ACCESS
|OWNER_ONLY
|WRITE_TO_LOCALE
)) != NULL
){
3718 gf_set_so_writec(&pc
, so
);
3720 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
)){
3721 if(!(state
->mail_stream
3722 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
3723 && rawno
<= state
->mail_stream
->nmsgs
3724 && (mc
= mail_elt(state
->mail_stream
, rawno
))
3728 if(!(env
= pine_mail_fetchstructure(state
->mail_stream
,
3729 mn_m2raw(msgmap
, i
), &b
))
3730 || !bezerk_delimiter(env
, mc
, pc
, next
++)
3731 || !format_message(mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
3732 env
, b
, NULL
, FM_NEW_MESS
| FM_NOWRAP
, pc
)){
3733 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3734 err
= "Error writing tempfile for download");
3739 gf_clear_so_writec(so
);
3740 if(so_give(&so
)){ /* close file */
3742 err
= "Error writing tempfile for download";
3746 if((syspipe
= open_system_pipe(cmd
, NULL
, NULL
,
3747 PIPE_USER
| PIPE_RESET
,
3748 0, pipe_callback
, pipe_report_error
)) != NULL
)
3749 (void) close_system_pipe(&syspipe
, NULL
, pipe_callback
);
3751 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3752 err
= _("Error running download command"));
3756 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3757 err
= "Error building temp file for download");
3761 fs_give((void **)&tfp
);
3765 q_status_message(SM_ORDER
, 0, 3, _("Download Command Completed"));
3769 #endif /* !(DOS || MAC) */
3772 if(rflags
& GER_APPEND
)
3777 dprint((5, "Opening file \"%s\" for export\n",
3778 full_filename
? full_filename
: "?"));
3780 if(!(store
= so_get(FileStar
, full_filename
, WRITE_ACCESS
|WRITE_TO_LOCALE
))){
3781 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
3782 /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
3783 _("Error opening file \"%s\" to export message: %s"),
3784 full_filename
, error_description(errno
));
3788 gf_set_so_writec(&pc
, store
);
3791 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
), count
++){
3792 env
= pine_mail_fetchstructure(state
->mail_stream
, mn_m2raw(msgmap
, i
),
3795 err
= _("Can't export message. Error accessing mail folder");
3800 if(!(state
->mail_stream
3801 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
3802 && rawno
<= state
->mail_stream
->nmsgs
3803 && (mc
= mail_elt(state
->mail_stream
, rawno
))
3807 start_of_append
= so_tell(store
);
3808 if(!bezerk_delimiter(env
, mc
, pc
, leading_nl
)
3809 || !format_message(mn_m2raw(msgmap
, i
), env
, b
, NULL
,
3810 FM_NEW_MESS
| FM_NOWRAP
, pc
)){
3811 orig_errno
= errno
; /* save in case things are really bad */
3812 failure
= 1; /* pop out of here */
3819 gf_clear_so_writec(store
);
3820 if(so_give(&store
)) /* release storage */
3824 our_truncate(full_filename
, (off_t
)start_of_append
);
3826 dprint((1, "FAILED Export: fetch(%ld): %s\n",
3827 i
, err
? err
: "?"));
3828 q_status_message(SM_ORDER
| SM_DING
, 3, 4, err
);
3831 dprint((1, "FAILED Export: file \"%s\" : %s\n",
3832 full_filename
? full_filename
: "?",
3833 error_description(orig_errno
)));
3834 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
3835 /* TRANSLATORS: Error exporting to <filename>: <error text> */
3836 _("Error exporting to \"%s\" : %s"),
3837 filename
, error_description(orig_errno
));
3841 if(rflags
& GER_ALLPARTS
&& full_filename
[0]){
3842 char dir
[MAXPATH
+1];
3843 char lfile
[MAXPATH
+1];
3844 int ok
= 0, tries
= 0, saved
= 0, errs
= 0, counter
= 2;
3848 * Now we want to save all of the attachments to a subdirectory.
3849 * To make it easier for us and probably easier for the user, and
3850 * to prevent the user from shooting himself in the foot, we
3851 * make a new subdirectory so that we can't possibly step on
3852 * any existing files, and we don't need any interaction with the
3853 * user while saving.
3855 * We'll just use the directory name full_filename.d or if that
3856 * already exists and isn't empty, we'll try adding a suffix to
3857 * that until we get something to use.
3860 if(strlen(full_filename
) + strlen(".d") + 1 > sizeof(dir
)){
3861 q_status_message1(SM_ORDER
| SM_DING
, 3, 4,
3862 _("Can't save attachments, filename too long: %s"),
3868 snprintf(dir
, sizeof(dir
), "%.*s.d", MAXPATH
-2, full_filename
);
3869 dir
[sizeof(dir
)-1] = '\0';
3873 switch(r
= is_writable_dir(dir
)){
3874 case 0: /* exists and is a writable dir */
3876 * We could figure out if it is empty and use it in
3877 * that case, but that sounds like a lot of work, so
3878 * just fall through to default.
3882 if(strlen(full_filename
) + strlen(".d") + 1 +
3883 1 + strlen(long2string((long) tries
)) > sizeof(dir
)){
3884 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3885 "Problem saving attachments");
3889 snprintf(dir
, sizeof(dir
), "%.*s.d_%s", MAXPATH
- (int) strlen(long2string((long) tries
))-3, full_filename
,
3890 long2string((long) tries
));
3891 dir
[sizeof(dir
)-1] = '\0';
3894 case 3: /* doesn't exist, that's good! */
3895 /* make new directory */
3899 } while(!ok
&& tries
< 1000);
3902 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3903 _("Problem saving attachments"));
3907 /* create the new directory */
3908 if(our_mkdir(dir
, 0700)){
3909 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
3910 _("Problem saving attachments: %s: %s"), dir
,
3911 error_description(errno
));
3915 if(!(state
->mail_stream
3916 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
3917 && rawno
<= state
->mail_stream
->nmsgs
3918 && (env
=pine_mail_fetchstructure(state
->mail_stream
,rawno
,&b
))
3920 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3921 _("Problem reading message"));
3925 zero_atmts(state
->atmts
);
3926 describe_mime(b
, "", 1, 1, 0, 0);
3929 if(a
&& a
->description
) /* skip main body part */
3932 for(; a
->description
!= NULL
; a
++){
3933 /* skip over these parts of the message */
3934 if(MIME_MSG_A(a
) || MIME_DGST_A(a
) || MIME_VCARD_A(a
))
3938 (void) get_filename_parameter(lfile
, sizeof(lfile
), a
->body
, NULL
);
3940 if(lfile
[0] == '\0'){ /* MAXPATH + 1 = sizeof(lfile) */
3941 snprintf(lfile
, sizeof(lfile
), "part_%.*s", MAXPATH
+1-6,
3942 a
->number
? a
->number
: "?");
3943 lfile
[sizeof(lfile
)-1] = '\0';
3946 if(strlen(dir
) + strlen(S_FILESEP
) + strlen(lfile
) + 1
3947 > sizeof(filename
)){
3949 "FAILED Att Export: name too long: %s\n",
3950 dir
, S_FILESEP
, lfile
));
3955 /* although files are being saved in a unique directory, there is
3956 * no guarantee that attachment names have unique names, so we have
3957 * to make sure that we are not constantly rewriting the same file name
3958 * over and over. In order to avoid this we test if the file already exists,
3959 * and if so, we write a counter name in the file name, just before the
3960 * extension of the file, and separate it with an underscore.
3962 snprintf(filename
, sizeof(filename
), "%.*s%.*s%.*s", (int) strlen(dir
), dir
,
3963 (int) strlen(S_FILESEP
), S_FILESEP
,
3964 MAXPATH
- (int) strlen(dir
) - (int) strlen(S_FILESEP
), lfile
);
3965 filename
[sizeof(filename
)-1] = '\0';
3966 while((ok
= can_access(filename
, ACCESS_EXISTS
)) == 0 && errs
== 0){
3967 char *ext
, count
[MAXPATH
+1];
3968 unsigned long total
;
3969 snprintf(count
, sizeof(count
), "%d", counter
);
3970 if((ext
= strrchr(lfile
, '.')) != NULL
)
3972 total
= strlen(dir
) + strlen(S_FILESEP
) + strlen(lfile
) + strlen(count
) + 3
3973 + (ext
? strlen(ext
+1) : 0);
3974 if(total
> sizeof(filename
)){
3976 "FAILED Att Export: name too long: %s\n",
3977 dir
, S_FILESEP
, lfile
));
3981 snprintf(filename
, sizeof(filename
), "%.*s%.*s%.*s%.*s%.*d%.*s%.*s",
3982 (int) strlen(dir
), dir
, (int) strlen(S_FILESEP
), S_FILESEP
,
3983 (int) strlen(lfile
), lfile
,
3984 ext
? 1 : 0, ext
? "_" : "",
3985 (int) strlen(count
), counter
++,
3986 ext
? 1 : 0, ext
? "." : "",
3987 ext
? (int) (sizeof(filename
) - total
) : 0,
3989 filename
[sizeof(filename
)-1] = '\0';
3992 if(write_attachment_to_file(state
->mail_stream
, rawno
,
3993 a
, GER_NONE
, filename
) == 1)
4001 q_status_message1(SM_ORDER
, 3, 3,
4002 "Errors saving some attachments, %s attachments saved",
4003 long2string((long) saved
));
4005 q_status_message(SM_ORDER
, 3, 3,
4006 _("Problems saving attachments"));
4010 q_status_message2(SM_ORDER
, 0, 3,
4011 /* TRANSLATORS: Saved <how many> attachments to <directory name> */
4012 _("Saved %s attachments to %s"),
4013 long2string((long) saved
), dir
);
4015 q_status_message(SM_ORDER
, 3, 3, _("No attachments to save"));
4018 else if(mn_total_cur(msgmap
) > 1L)
4019 q_status_message4(SM_ORDER
,0,3,
4020 "%s message%s %s to file \"%s\"",
4021 long2string(count
), plural(count
),
4024 : rflags
& GER_APPEND
? "appended" : "exported",
4027 q_status_message3(SM_ORDER
,0,3,
4028 "Message %s %s to file \"%s\"",
4029 long2string(mn_get_cur(msgmap
)),
4032 : rflags
& GER_APPEND
? "appended" : "exported",
4038 if(MCMD_ISAGG(aopt
))
4039 restore_selected(msgmap
);
4046 * Ask user what file to export to. Export from srcstore to that file.
4048 * Args ps -- pine struct
4049 * srctext -- pointer to source text
4050 * srctype -- type of that source text
4051 * prompt_msg -- see get_export_filename
4054 * Returns: != 0 : error
4058 simple_export(struct pine
*ps
, void *srctext
, SourceType srctype
, char *prompt_msg
, char *lister_msg
)
4060 int r
= 1, rflags
= GER_NONE
;
4061 char filename
[MAXPATH
+1], full_filename
[MAXPATH
+1];
4062 STORE_S
*store
= NULL
;
4063 struct variable
*vars
= ps
? ps
->vars
: NULL
;
4064 static HISTORY_S
*history
= NULL
;
4065 static ESCKEY_S simple_export_opts
[] = {
4066 {ctrl('T'), 10, "^T", N_("To Files")},
4067 {-1, 0, NULL
, NULL
},
4068 {-1, 0, NULL
, NULL
}};
4070 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps
)){
4071 simple_export_opts
[r
].ch
= ctrl('I');
4072 simple_export_opts
[r
].rval
= 11;
4073 simple_export_opts
[r
].name
= "TAB";
4074 simple_export_opts
[r
].label
= N_("Complete");
4078 q_status_message(SM_ORDER
, 0, 2, _("Error allocating space"));
4083 simple_export_opts
[++r
].ch
= -1;
4085 full_filename
[0] = '\0';
4087 r
= get_export_filename(ps
, filename
, NULL
, full_filename
, sizeof(filename
),
4088 prompt_msg
, lister_msg
, simple_export_opts
, &rflags
,
4089 -FOOTER_ROWS(ps
), GE_IS_EXPORT
, &history
);
4093 else if(!full_filename
[0]){
4098 dprint((5, "Opening file \"%s\" for export\n",
4099 full_filename
? full_filename
: "?"));
4101 if((store
= so_get(FileStar
, full_filename
, WRITE_ACCESS
|WRITE_TO_LOCALE
)) != NULL
){
4105 gf_set_so_writec(&pc
, store
);
4106 gf_set_readc(&gc
, srctext
, (srctype
== CharStar
)
4107 ? strlen((char *)srctext
)
4111 if((pipe_err
= gf_pipe(gc
, pc
)) != NULL
){
4112 q_status_message2(SM_ORDER
| SM_DING
, 3, 3,
4113 /* TRANSLATORS: Problem saving to <filename>: <error text> */
4114 _("Problem saving to \"%s\": %s"),
4115 filename
, pipe_err
);
4121 gf_clear_so_writec(store
);
4122 if(so_give(&store
)){
4123 q_status_message2(SM_ORDER
| SM_DING
, 3, 3,
4124 _("Problem saving to \"%s\": %s"),
4125 filename
, error_description(errno
));
4130 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
4131 _("Error opening file \"%s\" for export: %s"),
4132 full_filename
, error_description(errno
));
4139 /* overloading full_filename */
4140 snprintf(full_filename
, sizeof(full_filename
), "%c%s",
4141 (prompt_msg
&& prompt_msg
[0])
4142 ? (islower((unsigned char)prompt_msg
[0])
4143 ? toupper((unsigned char)prompt_msg
[0]) : prompt_msg
[0])
4145 (prompt_msg
&& prompt_msg
[0]) ? prompt_msg
+1 : "ext");
4146 full_filename
[sizeof(full_filename
)-1] = '\0';
4147 q_status_message3(SM_ORDER
,0,2,"%s %s to \"%s\"",
4151 : rflags
& GER_APPEND
? "appended" : "exported",
4156 cmd_cancelled("Export");
4160 q_status_message1(SM_ORDER
, 0, 2,
4161 _("Can't export to file outside of %s"), VAR_OPER_DIR
);
4165 ps
->mangled_footer
= 1;
4171 * Ask user what file to export to.
4173 * filename -- On input, this is the filename to start with. On exit,
4174 * this is the filename chosen. (but this isn't used)
4175 * deefault -- This is the default value if user hits return. The
4176 * prompt will have [deefault] added to it automatically.
4177 * full_filename -- This is the full filename on exit.
4178 * len -- Minimum length of _both_ filename and full_filename.
4179 * prompt_msg -- Message to insert in prompt.
4180 * lister_msg -- Message to insert in file_lister.
4181 * opts -- Key options.
4182 * There is a tangled relationship between the callers
4183 * and this routine as far as opts are concerned. Some
4184 * of the opts are handled here. In particular, r == 3,
4185 * r == 10, r == 11, and r == 13 are all handled here.
4186 * Don't use those values unless you want what happens
4187 * here. r == 12 and others are handled by the caller.
4188 * rflags -- Return flags
4189 * GER_OVER - overwrite of existing file
4190 * GER_APPEND - append of existing file
4191 * else file did not exist before
4193 * GER_ALLPARTS - AllParts toggle was turned on
4195 * qline -- Command line to prompt on.
4196 * flags -- Logically OR'd flags
4197 * GE_IS_EXPORT - The command was an Export command
4198 * so the prompt should include
4200 * GE_SEQ_SENSITIVE - The command that got us here is
4201 * sensitive to sequence number changes
4202 * caused by unsolicited expunges.
4203 * GE_NO_APPEND - We will not allow append to an
4204 * existing file, only removal of the
4205 * file if it exists.
4206 * GE_IS_IMPORT - We are selecting for reading.
4207 * No overwriting or checking for
4208 * existence at all. Don't use this
4209 * together with GE_NO_APPEND.
4210 * GE_ALLPARTS - Turn on AllParts toggle.
4211 * GE_BINARY - Turn on Binary toggle.
4213 * Returns: -1 cancelled
4214 * -2 prohibited by VAR_OPER_DIR
4215 * -3 other error, already reported here
4217 * 12 user chose 12 command from opts
4220 get_export_filename(struct pine
*ps
, char *filename
, char *deefault
,
4221 char *full_filename
, size_t len
, char *prompt_msg
,
4222 char *lister_msg
, ESCKEY_S
*optsarg
, int *rflags
,
4223 int qline
, int flags
, HISTORY_S
**history
)
4225 char dir
[MAXPATH
+1], dir2
[MAXPATH
+1], orig_dir
[MAXPATH
+1];
4226 char precolon
[MAXPATH
+1], postcolon
[MAXPATH
+1];
4227 char filename2
[MAXPATH
+1], tmp
[MAXPATH
+1], *fn
, *ill
;
4228 int l
, i
, ku
= -1, kp
= -1, r
, fatal
, homedir
= 0, was_abs_path
=0, avail
, ret
= 0;
4229 int allparts
= 0, binary
= 0;
4230 char prompt_buf
[400];
4232 ESCKEY_S
*opts
= NULL
;
4233 struct variable
*vars
= ps
->vars
;
4234 static HISTORY_S
*dir_hist
= NULL
;
4236 int pos
, hist_len
= 0;
4239 /* we will fake a history with the ps_global->VAR_HISTORY variable
4240 * We fake that we combine this variable into a history variable
4241 * by stacking VAR_HISTORY on top of dir_hist. We keep track of this
4242 * by looking at the variable pos.
4244 if(ps_global
->VAR_HISTORY
!= NULL
)
4245 for(hist_len
= 0; ps_global
->VAR_HISTORY
[hist_len
]
4246 && ps_global
->VAR_HISTORY
[hist_len
][0]; hist_len
++)
4249 pos
= hist_len
+ items_in_hist(dir_hist
);
4251 if(flags
& GE_ALLPARTS
|| history
|| dir_hist
){
4253 * Copy the opts and add one to the end of the list.
4255 for(i
= 0; optsarg
[i
].ch
!= -1; i
++)
4258 if(dir_hist
|| hist_len
> 0)
4262 i
+= dir_hist
|| hist_len
> 0 ? 2 : 4;
4264 if(flags
& GE_ALLPARTS
)
4267 if(flags
& GE_BINARY
)
4270 opts
= (ESCKEY_S
*) fs_get((i
+1) * sizeof(*opts
));
4271 memset(opts
, 0, (i
+1) * sizeof(*opts
));
4273 for(i
= 0; optsarg
[i
].ch
!= -1; i
++){
4274 opts
[i
].ch
= optsarg
[i
].ch
;
4275 opts
[i
].rval
= optsarg
[i
].rval
;
4276 opts
[i
].name
= optsarg
[i
].name
; /* no need to make a copy */
4277 opts
[i
].label
= optsarg
[i
].label
; /* " */
4280 if(flags
& GE_ALLPARTS
){
4282 opts
[i
].ch
= ctrl('P');
4284 opts
[i
].name
= "^P";
4285 /* TRANSLATORS: Export all attachment parts */
4286 opts
[i
++].label
= N_("AllParts");
4289 if(flags
& GE_BINARY
){
4291 opts
[i
].ch
= ctrl('R');
4293 opts
[i
].name
= "^R";
4294 opts
[i
++].label
= N_("Binary");
4297 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf
,
4298 SIZEOF_20KBUF
, filename
);
4300 /* In the Windows operating system we always return the UTF8 encoded name */
4301 if(strcmp(tmp_20k_buf
, filename
)){
4302 opts
[i
].ch
= ctrl('N');
4304 opts
[i
].name
= "^N";
4305 opts
[i
++].label
= "Name UTF8";
4308 strncpy(filename
, tmp_20k_buf
, len
);
4309 filename
[len
-1] = '\0';
4310 #endif /* _WINDOWS */
4312 if(dir_hist
|| hist_len
> 0){
4313 opts
[i
].ch
= ctrl('Y');
4317 opts
[i
++].label
= "";
4319 opts
[i
].ch
= ctrl('V');
4322 opts
[i
++].label
= "";
4326 opts
[i
].ch
= KEY_UP
;
4330 opts
[i
++].label
= "";
4332 opts
[i
].ch
= KEY_DOWN
;
4335 opts
[i
++].label
= "";
4341 init_hist(history
, HISTSIZE
);
4342 init_hist(&dir_hist
, HISTSIZE
); /* reset history to the end */
4350 if(F_ON(F_USE_CURRENT_DIR
, ps
))
4352 else if(VAR_OPER_DIR
){
4353 strncpy(dir
, VAR_OPER_DIR
, sizeof(dir
));
4354 dir
[sizeof(dir
)-1] = '\0';
4356 #if defined(DOS) || defined(OS2)
4357 else if(VAR_FILE_DIR
){
4358 strncpy(dir
, VAR_FILE_DIR
, sizeof(dir
));
4359 dir
[sizeof(dir
)-1] = '\0';
4367 strncpy(orig_dir
, dir
, sizeof(orig_dir
));
4368 orig_dir
[sizeof(orig_dir
)-1] = '\0';
4370 postcolon
[0] = '\0';
4371 strncpy(precolon
, dir
, sizeof(precolon
));
4372 precolon
[sizeof(precolon
)-1] = '\0';
4374 strncpy(def
, deefault
, sizeof(def
)-1);
4375 def
[sizeof(def
)-1] = '\0';
4376 removing_leading_and_trailing_white_space(def
);
4381 avail
= MAX(20, ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80) - MIN_OPT_ENT_WIDTH
;
4383 /*---------- Prompt the user for the file name -------------*/
4386 char dirb
[50], fileb
[50];
4387 int l1
, l2
, l3
, l4
, l5
, needed
;
4388 char *p
, p1
[100], p2
[100], *p3
, p4
[100], p5
[100];
4390 snprintf(p1
, sizeof(p1
), "%sCopy ",
4391 (flags
& GE_IS_EXPORT
) ? "EXPORT: " :
4392 (flags
& GE_IS_IMPORT
) ? "IMPORT: " : "SAVE: ");
4393 p1
[sizeof(p1
)-1] = '\0';
4396 strncpy(p2
, prompt_msg
? prompt_msg
: "", sizeof(p2
)-1);
4397 p2
[sizeof(p2
)-1] = '\0';
4400 if(rflags
&& *rflags
& GER_ALLPARTS
)
4407 snprintf(p4
, sizeof(p4
), " %s file%s%s",
4408 (flags
& GE_IS_IMPORT
) ? "from" : "to",
4409 is_absolute_path(filename
) ? "" : " in ",
4410 is_absolute_path(filename
) ? "" :
4411 (!dir
[0] ? "current directory"
4412 : (dir
[0] == '~' && !dir
[1]) ? "home directory"
4413 : short_str(dir
,dirb
,sizeof(dirb
),30,FrontDots
)));
4414 p4
[sizeof(p4
)-1] = '\0';
4417 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4419 *def
? short_str(def
,fileb
,sizeof(fileb
),40,EndDots
) : "",
4421 p5
[sizeof(p5
)-1] = '\0';
4424 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0){
4425 snprintf(p4
, sizeof(p4
), " %s file%s%s",
4426 (flags
& GE_IS_IMPORT
) ? "from" : "to",
4427 is_absolute_path(filename
) ? "" : " in ",
4428 is_absolute_path(filename
) ? "" :
4429 (!dir
[0] ? "current dir"
4430 : (dir
[0] == '~' && !dir
[1]) ? "home dir"
4431 : short_str(dir
,dirb
,sizeof(dirb
),10,FrontDots
)));
4432 p4
[sizeof(p4
)-1] = '\0';
4436 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l5
> 0){
4437 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4439 *def
? short_str(def
,fileb
,sizeof(fileb
),
4440 MAX(15,l5
-5-needed
),EndDots
) : "",
4442 p5
[sizeof(p5
)-1] = '\0';
4446 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l2
> 0){
4449 * 14 is about the shortest we can make this, because there are
4450 * fixed length strings of length 14 coming in here.
4452 p
= short_str(prompt_msg
, p2
, sizeof(p2
), MAX(14,l2
-needed
), FrontDots
);
4454 strncpy(p2
, p
, sizeof(p2
)-1);
4455 p2
[sizeof(p2
)-1] = '\0';
4461 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0){
4462 strncpy(p1
, "Copy ", sizeof(p1
)-1);
4463 p1
[sizeof(p1
)-1] = '\0';
4467 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l5
> 0){
4468 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4470 *def
? short_str(def
,fileb
, sizeof(fileb
),
4471 MAX(10,l5
-5-needed
),EndDots
) : "",
4473 p5
[sizeof(p5
)-1] = '\0';
4477 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l3
> 0){
4478 if(needed
<= l3
- strlen(" (+ atts)"))
4480 else if(needed
<= l3
- strlen(" (atts)"))
4482 else if(needed
<= l3
- strlen(" (+)"))
4484 else if(needed
<= l3
- strlen("+"))
4492 snprintf(prompt_buf
, sizeof(prompt_buf
), "%s%s%s%s%s", p1
, p2
, p3
, p4
, p5
);
4493 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4496 if(items_in_hist(dir_hist
) > 0 || hist_len
> 0){ /* any directories */
4497 opts
[kp
].name
= "^Y";
4498 opts
[kp
].label
= "Prev Dir";
4499 opts
[kp
+1].name
= "^V";
4500 opts
[kp
+1].label
= "Next Dir";
4504 opts
[kp
].label
= "";
4505 opts
[kp
+1].name
= "";
4506 opts
[kp
+1].label
= "";
4511 if(items_in_hist(*history
) > 0){
4512 opts
[ku
].name
= HISTORY_UP_KEYNAME
;
4513 opts
[ku
].label
= HISTORY_KEYLABEL
;
4514 opts
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
4515 opts
[ku
+1].label
= HISTORY_KEYLABEL
;
4519 opts
[ku
].label
= "";
4520 opts
[ku
+1].name
= "";
4521 opts
[ku
+1].label
= "";
4525 oeflags
= OE_APPEND_CURRENT
|
4526 ((flags
& GE_SEQ_SENSITIVE
) ? OE_SEQ_SENSITIVE
: 0);
4527 r
= optionally_enter(filename
, qline
, 0, len
, prompt_buf
,
4528 opts
, NO_HELP
, &oeflags
);
4530 dprint((2, "\n - export_filename = \"%s\", r = %d -\n", filename
, r
));
4534 * Helps may not be right if you add another caller or change
4535 * things. Check it out.
4537 if(flags
& GE_IS_IMPORT
)
4538 helper(h_ge_import
, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE
);
4539 else if(flags
& GE_ALLPARTS
)
4540 helper(h_ge_allparts
, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE
);
4542 helper(h_ge_export
, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE
);
4544 ps
->mangled_screen
= 1;
4548 else if(r
== 10 || r
== 11){ /* Browser or File Completion */
4549 if(filename
[0]=='~'){
4550 if(filename
[1] == C_FILESEP
&& filename
[2]!='\0'){
4553 for(i
=0; filename
[i
+2] != '\0' && i
+2 < len
-1; i
++)
4554 filename
[i
] = filename
[i
+2];
4556 strncpy(dir
, precolon
, sizeof(dir
)-1);
4557 dir
[sizeof(dir
)-1] = '\0';
4559 else if(filename
[1]=='\0' ||
4560 (filename
[1] == C_FILESEP
&& filename
[2] == '\0')){
4564 strncpy(dir
, precolon
, sizeof(dir
)-1);
4565 dir
[sizeof(dir
)-1] = '\0';
4568 else if(!dir
[0] && !is_absolute_path(filename
) && was_abs_path
){
4572 strncpy(dir
, precolon
, sizeof(dir
)-1);
4573 dir
[sizeof(dir
)-1] = '\0';
4582 strncpy(tmp
, filename
, sizeof(tmp
)-1);
4583 tmp
[sizeof(tmp
)-1] = '\0';
4584 if(*tmp
&& is_absolute_path(tmp
))
4585 fnexpand(tmp
, sizeof(tmp
));
4586 if(strncmp(tmp
,postcolon
, strlen(postcolon
)))
4587 postcolon
[0] = '\0';
4589 if(*tmp
&& (fn
= last_cmpnt(tmp
))){
4591 strncpy(filename2
, fn
, sizeof(filename2
)-1);
4592 filename2
[sizeof(filename2
)-1] = '\0';
4593 if(is_absolute_path(tmp
)){
4594 strncpy(dir2
, tmp
, MIN(fn
- tmp
, sizeof(dir2
)-1));
4595 dir2
[MIN(fn
- tmp
, sizeof(dir2
)-1)] = '\0';
4597 if(tmp
[1]==':' && tmp
[2]=='\\' && dir2
[2]=='\0'){
4602 strncpy(postcolon
, dir2
, sizeof(postcolon
)-1);
4603 postcolon
[sizeof(postcolon
)-1] = '\0';
4609 * Just building the directory name in dir2,
4610 * full_filename is overloaded.
4612 snprintf(full_filename
, len
, "%.*s", (int) MIN(fn
-tmp
,len
-1), tmp
);
4613 full_filename
[len
-1] = '\0';
4614 strncpy(postcolon
, full_filename
, sizeof(postcolon
)-1);
4615 postcolon
[sizeof(postcolon
)-1] = '\0';
4616 build_path(dir2
, !dir
[0] ? p
= (char *)getcwd(NULL
,MAXPATH
)
4617 : (dir
[0] == '~' && !dir
[1])
4620 full_filename
, sizeof(dir2
));
4626 if(is_absolute_path(tmp
)){
4627 strncpy(dir2
, tmp
, sizeof(dir2
)-1);
4628 dir2
[sizeof(dir2
)-1] = '\0';
4630 if(dir2
[2]=='\0' && dir2
[1]==':'){
4633 strncpy(postcolon
,dir2
,sizeof(postcolon
)-1);
4634 postcolon
[sizeof(postcolon
)-1] = '\0';
4637 filename2
[0] = '\0';
4641 strncpy(filename2
, tmp
, sizeof(filename2
)-1);
4642 filename2
[sizeof(filename2
)-1] = '\0';
4644 if(getcwd(dir2
, sizeof(dir2
)) == NULL
)
4645 alpine_panic(_("getcwd() call failed at get_export_filename"));
4647 else if(dir
[0] == '~' && !dir
[1]){
4648 strncpy(dir2
, ps
->home_dir
, sizeof(dir2
)-1);
4649 dir2
[sizeof(dir2
)-1] = '\0';
4652 strncpy(dir2
, dir
, sizeof(dir2
)-1);
4653 dir2
[sizeof(dir2
)-1] = '\0';
4656 postcolon
[0] = '\0';
4660 build_path(full_filename
, dir2
, filename2
, len
);
4661 if(!strcmp(full_filename
, dir2
))
4662 filename2
[0] = '\0';
4663 if(full_filename
[strlen(full_filename
)-1] == C_FILESEP
4664 && isdir(full_filename
,NULL
,NULL
)){
4665 if(strlen(full_filename
) == 1)
4666 strncpy(postcolon
, full_filename
, sizeof(postcolon
)-1);
4667 else if(filename2
[0])
4668 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4669 postcolon
[sizeof(postcolon
)-1] = '\0';
4670 strncpy(dir2
, full_filename
, sizeof(dir2
)-1);
4671 dir2
[sizeof(dir2
)-1] = '\0';
4672 filename2
[0] = '\0';
4674 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4675 else if(full_filename
[strlen(full_filename
)-1] == C_FILESEP
){
4676 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4677 postcolon
[sizeof(postcolon
)-1] = '\0';
4678 strncpy(dir2
, full_filename
, sizeof(dir2
)-1);
4679 dir2
[sizeof(dir2
)-1] = '\0';
4680 filename2
[0] = '\0';
4683 if(dir2
[strlen(dir2
)-1] == C_FILESEP
&& strlen(dir2
)!=1
4684 && strcmp(dir2
+1, ":\\"))
4685 /* last condition to prevent stripping of '\\'
4686 in windows partition */
4687 dir2
[strlen(dir2
)-1] = '\0';
4689 if(r
== 10){ /* File Browser */
4690 r
= file_lister(lister_msg
? lister_msg
: "EXPORT",
4691 dir2
, sizeof(dir2
), filename2
, sizeof(filename2
),
4693 (flags
& GE_IS_IMPORT
) ? FB_READ
: FB_SAVE
);
4695 /* Windows has a special "feature" in which entering the file browser will
4696 change the working directory if the directory is changed at all (even
4697 clicking "Cancel" will change the working directory).
4699 if(F_ON(F_USE_CURRENT_DIR
, ps
))
4700 (void)getcwd(dir2
,sizeof(dir2
));
4702 if(isdir(dir2
,NULL
,NULL
)){
4703 strncpy(precolon
, dir2
, sizeof(precolon
)-1);
4704 precolon
[sizeof(precolon
)-1] = '\0';
4706 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4707 postcolon
[sizeof(postcolon
)-1] = '\0';
4709 build_path(full_filename
, dir2
, filename2
, len
);
4710 if(isdir(full_filename
, NULL
, NULL
)){
4711 strncpy(dir
, full_filename
, sizeof(dir
)-1);
4712 dir
[sizeof(dir
)-1] = '\0';
4716 fn
= last_cmpnt(full_filename
);
4717 strncpy(dir
, full_filename
,
4718 MIN(fn
- full_filename
, sizeof(dir
)-1));
4719 dir
[MIN(fn
- full_filename
, sizeof(dir
)-1)] = '\0';
4720 if(fn
- full_filename
> 1)
4721 dir
[fn
- full_filename
- 1] = '\0';
4724 if(!strcmp(dir
, ps
->home_dir
)){
4729 strncpy(filename
, fn
, len
-1);
4730 filename
[len
-1] = '\0';
4733 else{ /* File Completion */
4734 if(!pico_fncomplete(dir2
, filename2
, sizeof(filename2
)))
4736 strncat(postcolon
, filename2
,
4737 sizeof(postcolon
)-1-strlen(postcolon
));
4738 postcolon
[sizeof(postcolon
)-1] = '\0';
4740 was_abs_path
= is_absolute_path(filename
);
4742 if(!strcmp(dir
, ps
->home_dir
)){
4747 strncpy(filename
, postcolon
, len
-1);
4748 filename
[len
-1] = '\0';
4749 strncpy(dir
, precolon
, sizeof(dir
)-1);
4750 dir
[sizeof(dir
)-1] = '\0';
4752 if(filename
[0] == '~' && !filename
[1]){
4760 else if(r
== 12){ /* Download, caller handles it */
4764 else if(r
== 13){ /* toggle AllParts bit */
4766 if(*rflags
& GER_ALLPARTS
){
4767 *rflags
&= ~GER_ALLPARTS
;
4768 opts
[allparts
].label
= N_("AllParts");
4771 *rflags
|= GER_ALLPARTS
;
4772 /* opposite of All Parts, No All Parts */
4773 opts
[allparts
].label
= N_("NoAllParts");
4780 else if(r
== 14){ /* List file names matching partial? */
4784 else if(r
== 15){ /* toggle Binary bit */
4786 if(*rflags
& GER_BINARY
){
4787 *rflags
&= ~GER_BINARY
;
4788 opts
[binary
].label
= N_("Binary");
4791 *rflags
|= GER_BINARY
;
4792 opts
[binary
].label
= N_("No Binary");
4798 else if(r
== 1){ /* Cancel */
4805 else if(r
>= 30 && r
<= 33){
4808 if(r
== 30 || r
== 31){
4811 p
= get_prev_hist(*history
, filename
, 0, NULL
);
4813 p
= get_next_hist(*history
, filename
, 0, NULL
);
4817 if(r
== 32 || r
== 33){
4818 int nitems
= items_in_hist(dir_hist
);
4819 if(dir_hist
|| hist_len
> 0){
4822 p
= hist_in_pos(--pos
, ps_global
->VAR_HISTORY
, hist_len
, dir_hist
, nitems
);
4826 if(pos
< hist_len
+ nitems
)
4827 p
= hist_in_pos(++pos
, ps_global
->VAR_HISTORY
, hist_len
, dir_hist
, nitems
);
4829 if(p
== NULL
|| *p
== '\0')
4833 last
= p
; /* save it! */
4835 if(p
!= NULL
&& *p
!= '\0'){
4836 if(r
== 30 || r
== 31){
4837 if((fn
= last_cmpnt(p
)) != NULL
){
4838 strncpy(dir
, p
, MIN(fn
- p
, sizeof(dir
)-1));
4839 dir
[MIN(fn
- p
, sizeof(dir
)-1)] = '\0';
4841 dir
[fn
- p
- 1] = '\0';
4842 strncpy(filename
, fn
, len
-1);
4843 filename
[len
-1] = '\0';
4845 } else { /* r == 32 || r == 33 */
4846 strncpy(dir
, p
, sizeof(dir
)-1);
4847 dir
[sizeof(dir
)-1] = '\0';
4850 if(!strcmp(dir
, ps
->home_dir
)){
4861 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf
,
4862 SIZEOF_20KBUF
, filename
);
4863 strncpy(filename
, tmp_20k_buf
, len
);
4864 filename
[len
-1] = '\0';
4867 #endif /* _WINDOWS */
4873 removing_leading_and_trailing_white_space(filename
);
4876 if(!*def
){ /* Cancel */
4881 strncpy(filename
, def
, len
-1);
4882 filename
[len
-1] = '\0';
4885 #if defined(DOS) || defined(OS2)
4886 if(is_absolute_path(filename
)){
4887 fixpath(filename
, len
);
4890 if(filename
[0] == '~'){
4891 if(fnexpand(filename
, len
) == NULL
){
4892 char *p
= strindex(filename
, '/');
4895 q_status_message1(SM_ORDER
| SM_DING
, 3, 3,
4896 _("Error expanding file name: \"%s\" unknown user"),
4903 if(is_absolute_path(filename
)){
4904 strncpy(full_filename
, filename
, len
-1);
4905 full_filename
[len
-1] = '\0';
4909 build_path(full_filename
, (char *)getcwd(dir
,sizeof(dir
)),
4911 else if(dir
[0] == '~' && !dir
[1])
4912 build_path(full_filename
, ps
->home_dir
, filename
, len
);
4914 build_path(full_filename
, dir
, filename
, len
);
4917 if((ill
= filter_filename(full_filename
, &fatal
,
4918 ps_global
->restricted
|| ps_global
->VAR_OPER_DIR
)) != NULL
){
4920 q_status_message1(SM_ORDER
| SM_DING
, 3, 3, "%s", ill
);
4924 /* BUG: we should beep when the key's pressed rather than bitch later */
4925 /* Warn and ask for confirmation. */
4926 snprintf(prompt_buf
, sizeof(prompt_buf
), "File name contains a '%s'. %s anyway",
4927 ill
, (flags
& GE_IS_EXPORT
) ? "Export" : "Save");
4928 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4929 if(want_to(prompt_buf
, 'n', 0, NO_HELP
,
4930 ((flags
& GE_SEQ_SENSITIVE
) ? RB_SEQ_SENSITIVE
: 0)) != 'y')
4935 break; /* Must have got an OK file name */
4938 if(VAR_OPER_DIR
&& !in_dir(VAR_OPER_DIR
, full_filename
)){
4943 if(!can_access(full_filename
, ACCESS_EXISTS
)){
4945 static ESCKEY_S access_opts
[] = {
4946 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4947 a file or append to the end of the file */
4948 {'o', 'o', "O", N_("Overwrite")},
4949 {'a', 'a', "A", N_("Append")},
4950 {-1, 0, NULL
, NULL
}};
4952 rbflags
= RB_NORM
| ((flags
& GE_SEQ_SENSITIVE
) ? RB_SEQ_SENSITIVE
: 0);
4954 if(flags
& GE_NO_APPEND
){
4955 r
= strlen(filename
);
4956 snprintf(prompt_buf
, sizeof(prompt_buf
),
4957 /* TRANSLATORS: asking user whether to overwrite a file or not,
4958 File <filename> already exists. Overwrite it ? */
4959 _("File \"%s%s\" already exists. Overwrite it "),
4960 (r
> 20) ? "..." : "",
4961 filename
+ ((r
> 20) ? r
- 20 : 0));
4962 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4963 if(want_to(prompt_buf
, 'n', 'x', NO_HELP
, rbflags
) == 'y'){
4965 *rflags
|= GER_OVER
;
4967 if(our_unlink(full_filename
) < 0){
4968 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
4969 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4970 _("Cannot remove old %s: %s"),
4971 full_filename
, error_description(errno
));
4979 else if(!(flags
& GE_IS_IMPORT
)){
4980 r
= strlen(filename
);
4981 snprintf(prompt_buf
, sizeof(prompt_buf
),
4982 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4983 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4984 (r
> 20) ? "..." : "",
4985 filename
+ ((r
> 20) ? r
- 20 : 0));
4986 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4987 switch(radio_buttons(prompt_buf
, -FOOTER_ROWS(ps_global
),
4988 access_opts
, 'a', 'x', NO_HELP
, rbflags
)){
4991 *rflags
|= GER_OVER
;
4993 if(our_truncate(full_filename
, (off_t
)0) < 0)
4994 /* trouble truncating, but we'll give it a try anyway */
4995 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
4996 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
4997 _("Warning: Cannot truncate old %s: %s"),
4998 full_filename
, error_description(errno
));
5003 *rflags
|= GER_APPEND
;
5016 if(history
&& ret
== 0){
5017 save_hist(*history
, full_filename
, 0, NULL
);
5018 strncpy(tmp
, full_filename
, MAXPATH
);
5019 tmp
[MAXPATH
] = '\0';
5020 if((fn
= strrchr(tmp
, C_FILESEP
)) != NULL
)
5025 save_hist(dir_hist
, tmp
, 0, NULL
);
5028 if(opts
&& opts
!= optsarg
)
5029 fs_give((void **) &opts
);
5035 /*----------------------------------------------------------------------
5036 parse the config'd upload/download command
5038 Args: cmd -- buffer to return command fit for shellin'
5041 fname -- file name to build into the command
5043 Returns: pointer to cmd_str buffer or NULL on real bad error
5045 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
5046 cfg_str is written to standard out right before a successful
5047 return of this function. The call immediately following this
5048 function darn well better be the shell exec...
5051 build_updown_cmd(char *cmd
, size_t cmdlen
, char *prefix
, char *cfg_str
, char *fname
)
5054 int fname_found
= 0;
5056 if(prefix
&& *prefix
){
5057 /* loop thru replacing all occurrences of _FILE_ */
5058 p
= strncpy(cmd
, prefix
, cmdlen
);
5059 cmd
[cmdlen
-1] = '\0';
5060 while((p
= strstr(p
, "_FILE_")))
5061 rplstr(p
, cmdlen
-(p
-cmd
), 6, fname
);
5066 /* loop thru replacing all occurrences of _FILE_ */
5067 p
= strncpy(cmd
, cfg_str
, cmdlen
);
5068 cmd
[cmdlen
-1] = '\0';
5069 while((p
= strstr(p
, "_FILE_"))){
5070 rplstr(p
, cmdlen
-(p
-cmd
), 6, fname
);
5075 snprintf(cmd
+strlen(cmd
), cmdlen
-strlen(cmd
), " %s", fname
);
5077 cmd
[cmdlen
-1] = '\0';
5079 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
5085 /*----------------------------------------------------------------------
5086 Write a berzerk format message delimiter using the given putc function
5088 Args: e -- envelope of message to write
5089 pc -- function to use
5091 Returns: TRUE if we could write it, FALSE if there was a problem
5093 NOTE: follows delimiter with OS-dependent newline
5096 bezerk_delimiter(ENVELOPE
*env
, MESSAGECACHE
*mc
, gf_io_t pc
, int leading_newline
)
5102 /* write "[\n]From mailbox[@host] " */
5103 if(!((leading_newline
? gf_puts(NEWLINE
, pc
) : 1)
5104 && gf_puts("From ", pc
)
5105 && gf_puts((env
&& env
->from
) ? env
->from
->mailbox
5106 : "the-concourse-on-high", pc
)
5107 && gf_puts((env
&& env
->from
&& env
->from
->host
) ? "@" : "", pc
)
5108 && gf_puts((env
&& env
->from
&& env
->from
->host
) ? env
->from
->host
5114 when
= mail_longdate(mc
);
5115 else if(env
&& env
->date
&& env
->date
[0]
5116 && mail_parse_date(&telt
,env
->date
))
5117 when
= mail_longdate(&telt
);
5123 while(p
&& *p
&& *p
!= '\n') /* write date */
5127 if(!gf_puts(NEWLINE
, pc
)) /* write terminating newline */
5134 /*----------------------------------------------------------------------
5135 Execute command to jump to a given message number
5137 Args: qline -- Line to ask question on
5139 Result: returns true if the use selected a new message, false otherwise
5143 jump_to(MSGNO_S
*msgmap
, int qline
, UCS first_num
, SCROLL_S
*sparms
, CmdWhere in_index
)
5145 char jump_num_string
[80], *j
, prompt
[70];
5148 static ESCKEY_S jump_to_key
[] = { {0, 0, NULL
, NULL
},
5149 /* TRANSLATORS: go to First Message */
5150 {ctrl('Y'), 10, "^Y", N_("First Msg")},
5151 {ctrl('V'), 11, "^V", N_("Last Msg")},
5152 {-1, 0, NULL
, NULL
} };
5154 dprint((4, "\n - jump_to -\n"));
5157 if(sparms
&& sparms
->jump_is_debug
)
5158 return(get_level(qline
, first_num
, sparms
));
5161 if(!any_messages(msgmap
, NULL
, "to Jump to"))
5164 if(first_num
&& first_num
< 0x80 && isdigit((unsigned char) first_num
)){
5165 jump_num_string
[0] = first_num
;
5166 jump_num_string
[1] = '\0';
5169 jump_num_string
[0] = '\0';
5171 if(mn_total_cur(msgmap
) > 1L){
5172 snprintf(prompt
, sizeof(prompt
), "Unselect %s msgs in favor of number to be entered",
5173 comatose(mn_total_cur(msgmap
)));
5174 prompt
[sizeof(prompt
)-1] = '\0';
5175 if((rc
= want_to(prompt
, 'n', 0, NO_HELP
, WT_NORM
)) == 'n')
5179 snprintf(prompt
, sizeof(prompt
), "%s number to jump to : ", in_index
== ThrdIndx
5182 prompt
[sizeof(prompt
)-1] = '\0';
5186 int flags
= OE_APPEND_CURRENT
;
5188 rc
= optionally_enter(jump_num_string
, qline
, 0,
5189 sizeof(jump_num_string
), prompt
,
5190 jump_to_key
, help
, &flags
);
5192 help
= help
== NO_HELP
5193 ? (in_index
== ThrdIndx
? h_oe_jump_thd
: h_oe_jump
)
5197 else if(rc
== 10 || rc
== 11){
5201 closest
= closest_jump_target(rc
== 10 ? 1L
5202 : ((in_index
== ThrdIndx
)
5203 ? msgmap
->max_thrdno
5204 : mn_get_total(msgmap
)),
5205 ps_global
->mail_stream
,
5207 in_index
, warning
, sizeof(warning
));
5208 /* ignore warning */
5213 * If we take out the *jump_num_string nonempty test in this if
5214 * then the closest_jump_target routine will offer a jump to the
5215 * last message. However, it is slow because you have to wait for
5216 * the status message and it is annoying for people who hit J command
5217 * by mistake and just want to hit return to do nothing, like has
5218 * always worked. So the test is there for now. Hubert 2002-08-19
5220 * Jumping to first/last message is now possible through ^Y/^V
5221 * commands above. jpf 2002-08-21
5222 * (and through "end" hubert 2006-07-07)
5224 if(rc
== 0 && *jump_num_string
!= '\0'){
5225 removing_leading_and_trailing_white_space(jump_num_string
);
5226 for(j
=jump_num_string
; isdigit((unsigned char)*j
) || *j
=='-'; j
++)
5230 if(!strucmp("end", j
))
5231 return((in_index
== ThrdIndx
) ? msgmap
->max_thrdno
: mn_get_total(msgmap
));
5233 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5234 _("Invalid number entered. Use only digits 0-9"));
5235 jump_num_string
[0] = '\0';
5239 long closest
, jump_num
;
5241 if(*jump_num_string
)
5242 jump_num
= atol(jump_num_string
);
5247 closest
= closest_jump_target(jump_num
, ps_global
->mail_stream
,
5249 *jump_num_string
? 0 : 1,
5250 in_index
, warning
, sizeof(warning
));
5252 q_status_message(SM_ORDER
| SM_DING
, 2, 2, warning
);
5254 if(closest
== jump_num
)
5258 jump_num_string
[0] = '\0';
5260 strncpy(jump_num_string
, long2string(closest
),
5261 sizeof(jump_num_string
));
5276 * cmd_delete_action - handle msgno advance and such after single message deletion
5279 cmd_delete_action(struct pine
*state
, MSGNO_S
*msgmap
, CmdWhere in_index
)
5285 msgno
= mn_get_cur(msgmap
);
5286 advance_cur_after_delete(state
, state
->mail_stream
, msgmap
, in_index
);
5288 if(IS_NEWS(state
->mail_stream
)
5289 || ((state
->context_current
->use
& CNTXT_INCMNG
)
5290 && context_isambig(state
->cur_folder
))){
5292 opts
= (NSF_TRUST_FLAGS
| NSF_SKIP_CHID
);
5293 if(in_index
== View
)
5294 opts
&= ~NSF_SKIP_CHID
;
5296 (void)next_sorted_flagged(F_UNDEL
|F_UNSEEN
, state
->mail_stream
, msgno
, &opts
);
5297 if(!(opts
& NSF_FLAG_MATCH
)){
5298 char nextfolder
[MAXPATH
];
5300 strncpy(nextfolder
, state
->cur_folder
, sizeof(nextfolder
));
5301 nextfolder
[sizeof(nextfolder
)-1] = '\0';
5302 rv
= next_folder(NULL
, nextfolder
, sizeof(nextfolder
), nextfolder
,
5303 state
->context_current
, NULL
, NULL
)
5304 ? ". Press TAB for next folder."
5305 : ". No more folders to TAB to.";
5314 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5317 cmd_delete_index(struct pine
*state
, MSGNO_S
*msgmap
)
5319 return(cmd_delete_action(state
, msgmap
,MsgIndx
));
5323 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5326 cmd_delete_view(struct pine
*state
, MSGNO_S
*msgmap
)
5328 return(cmd_delete_action(state
, msgmap
, View
));
5333 advance_cur_after_delete(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, CmdWhere in_index
)
5335 long new_msgno
, msgno
;
5338 new_msgno
= msgno
= mn_get_cur(msgmap
);
5339 opts
= NSF_TRUST_FLAGS
;
5341 if(F_ON(F_DEL_SKIPS_DEL
, state
)){
5343 if(THREADING() && sp_viewing_a_thread(stream
))
5344 opts
|= NSF_SKIP_CHID
;
5346 new_msgno
= next_sorted_flagged(F_UNDEL
, stream
, msgno
, &opts
);
5349 mn_inc_cur(stream
, msgmap
,
5350 (in_index
== View
&& THREADING()
5351 && sp_viewing_a_thread(stream
))
5353 : (in_index
== View
)
5354 ? MH_ANYTHD
: MH_NONE
);
5355 new_msgno
= mn_get_cur(msgmap
);
5356 if(new_msgno
!= msgno
)
5357 opts
|= NSF_FLAG_MATCH
;
5361 * Viewing_a_thread is the complicated case because we want to ignore
5362 * other threads at first and then look in other threads if we have to.
5363 * By ignoring other threads we also ignore collapsed partial threads
5364 * in our own thread.
5366 if(THREADING() && sp_viewing_a_thread(stream
) && !(opts
& NSF_FLAG_MATCH
)){
5367 long rawno
, orig_thrdno
;
5368 PINETHRD_S
*thrd
, *topthrd
= NULL
;
5370 rawno
= mn_m2raw(msgmap
, msgno
);
5371 thrd
= fetch_thread(stream
, rawno
);
5372 if(thrd
&& thrd
->top
)
5373 topthrd
= fetch_thread(stream
, thrd
->top
);
5375 orig_thrdno
= topthrd
? topthrd
->thrdno
: -1L;
5377 opts
= NSF_TRUST_FLAGS
;
5378 new_msgno
= next_sorted_flagged(F_UNDEL
, stream
, msgno
, &opts
);
5381 * If we got a match, new_msgno may be a message in
5382 * a different thread from the one we are viewing, or it could be
5383 * in a collapsed part of this thread.
5385 if(opts
& NSF_FLAG_MATCH
){
5390 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
,new_msgno
));
5391 if(thrd
&& thrd
->top
)
5392 topthrd
= fetch_thread(stream
, thrd
->top
);
5395 * If this match is in the same thread we're already in
5396 * then we're done, else we have to ask the user and maybe
5399 if(!(orig_thrdno
> 0L && topthrd
5400 && topthrd
->thrdno
== orig_thrdno
)){
5402 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD
, state
)){
5403 if(in_index
== View
)
5404 snprintf(pmt
, sizeof(pmt
),
5405 "View message in thread number %.10s",
5406 topthrd
? comatose(topthrd
->thrdno
) : "?");
5408 snprintf(pmt
, sizeof(pmt
), "View thread number %.10s",
5409 topthrd
? comatose(topthrd
->thrdno
) : "?");
5411 ret
= want_to(pmt
, 'y', 'x', NO_HELP
, WT_NORM
);
5417 unview_thread(state
, stream
, msgmap
);
5418 mn_set_cur(msgmap
, new_msgno
);
5420 && (count_lflags_in_thread(stream
, topthrd
, msgmap
,
5422 && view_thread(state
, stream
, msgmap
, 1)){
5423 if(current_index_state
)
5424 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
5426 state
->view_skipped_index
= 1;
5427 state
->next_screen
= mail_view_screen
;
5430 view_thread(state
, stream
, msgmap
, 1);
5431 if(current_index_state
)
5432 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
5434 state
->next_screen
= SCREEN_FUN_NULL
;
5438 new_msgno
= msgno
; /* stick with original */
5443 mn_set_cur(msgmap
, new_msgno
);
5444 if(in_index
!= View
)
5445 adjust_cur_to_visible(stream
, msgmap
);
5451 get_level(int qline
, UCS first_num
, SCROLL_S
*sparms
)
5453 char debug_num_string
[80], *j
, prompt
[70];
5458 if(first_num
&& first_num
< 0x80 && isdigit((unsigned char)first_num
)){
5459 debug_num_string
[0] = first_num
;
5460 debug_num_string
[1] = '\0';
5461 debug_num
= atol(debug_num_string
);
5462 *(int *)(sparms
->proc
.data
.p
) = debug_num
;
5463 q_status_message1(SM_ORDER
, 0, 3, "Show debug <= level %s",
5464 comatose(debug_num
));
5468 debug_num_string
[0] = '\0';
5470 snprintf(prompt
, sizeof(prompt
), "Show debug <= this level (0-%d) : ", MAX(debug
, 9));
5471 prompt
[sizeof(prompt
)-1] = '\0';
5475 int flags
= OE_APPEND_CURRENT
;
5477 rc
= optionally_enter(debug_num_string
, qline
, 0,
5478 sizeof(debug_num_string
), prompt
,
5479 NULL
, help
, &flags
);
5481 help
= help
== NO_HELP
? h_oe_debuglevel
: NO_HELP
;
5486 removing_leading_and_trailing_white_space(debug_num_string
);
5487 for(j
=debug_num_string
; isdigit((unsigned char)*j
); j
++)
5491 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5492 _("Invalid number entered. Use only digits 0-9"));
5493 debug_num_string
[0] = '\0';
5496 debug_num
= atol(debug_num_string
);
5498 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5499 _("Number should be >= 0"));
5500 else if(debug_num
> MAX(debug
,9))
5501 q_status_message1(SM_ORDER
| SM_DING
, 2, 2,
5502 _("Maximum is %s"), comatose(MAX(debug
,9)));
5504 *(int *)(sparms
->proc
.data
.p
) = debug_num
;
5505 q_status_message1(SM_ORDER
, 0, 3,
5506 "Show debug <= level %s",
5507 comatose(debug_num
));
5525 * Returns the message number closest to target that isn't hidden.
5526 * Make warning at least 100 chars.
5527 * A return of 0 means there is no message to jump to.
5530 closest_jump_target(long int target
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int no_target
, CmdWhere in_index
, char *warning
, size_t warninglen
)
5532 long i
, start
, closest
= 0L;
5537 maxnum
= (in_index
== ThrdIndx
) ? msgmap
->max_thrdno
: mn_get_total(msgmap
);
5542 snprintf(warning
, warninglen
, "No %s number entered, jump to end? ",
5543 (in_index
== ThrdIndx
) ? "thread" : "message");
5544 warning
[warninglen
-1] = '\0';
5546 else if(target
< 1L)
5547 start
= 1L - target
;
5548 else if(target
> maxnum
)
5549 start
= target
- maxnum
;
5553 if(target
> 0L && target
<= maxnum
)
5554 if(in_index
== ThrdIndx
5555 || !msgline_hidden(stream
, msgmap
, target
, 0))
5558 for(i
= start
; target
+i
<= maxnum
|| target
-i
> 0L; i
++){
5560 if(target
+i
> 0L && target
+i
<= maxnum
&&
5561 (in_index
== ThrdIndx
5562 || !msgline_hidden(stream
, msgmap
, target
+i
, 0))){
5567 if(target
-i
> 0L && target
-i
<= maxnum
&&
5568 (in_index
== ThrdIndx
5569 || !msgline_hidden(stream
, msgmap
, target
-i
, 0))){
5575 strncpy(buf
, long2string(closest
), sizeof(buf
));
5576 buf
[sizeof(buf
)-1] = '\0';
5579 strncpy(warning
, "Nothing to jump to", warninglen
);
5580 else if(target
< 1L)
5581 snprintf(warning
, warninglen
, "%s number (%s) must be at least %s",
5582 (in_index
== ThrdIndx
) ? "Thread" : "Message",
5583 long2string(target
), buf
);
5584 else if(target
> maxnum
)
5585 snprintf(warning
, warninglen
, "%s number (%s) may be no more than %s",
5586 (in_index
== ThrdIndx
) ? "Thread" : "Message",
5587 long2string(target
), buf
);
5589 snprintf(warning
, warninglen
,
5590 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5591 long2string(target
), buf
);
5593 warning
[warninglen
-1] = '\0';
5599 /*----------------------------------------------------------------------
5600 Prompt for folder name to open, expand the name and return it
5602 Args: qline -- Screen line to prompt on
5603 allow_list -- if 1, allow ^T to bring up collection lister
5605 Result: returns the folder name or NULL
5606 pine structure mangled_footer flag is set
5607 may call the collection lister in which case mangled screen will be set
5609 This prompts the user for the folder to open, possibly calling up
5610 the collection lister if the user types ^T.
5611 ----------------------------------------------------------------------*/
5613 broach_folder(int qline
, int allow_list
, int *notrealinbox
, CONTEXT_S
**context
)
5616 static char newfolder
[MAILTMPLEN
];
5617 char expanded
[MAXPATH
+1],
5618 prompt
[MAX_SCREEN_COLS
+1],
5620 unsigned char *f1
, *f2
, *f3
;
5621 static HISTORY_S
*history
= NULL
;
5622 CONTEXT_S
*tc
, *tc2
;
5624 int rc
, r
, ku
= -1, n
, flags
, last_rc
= 0, inbox
, done
= 0;
5627 * the idea is to provide a clue for the context the file name
5628 * will be saved in (if a non-imap names is typed), and to
5629 * only show the previous if it was also in the same context
5636 (*notrealinbox
) = 1;
5638 init_hist(&history
, HISTSIZE
);
5640 tc
= broach_get_folder(context
? *context
: NULL
, &inbox
, NULL
);
5642 /* set up extra command option keys */
5644 ekey
[rc
].ch
= (allow_list
) ? ctrl('T') : 0 ;
5645 ekey
[rc
].rval
= (allow_list
) ? 2 : 0;
5646 ekey
[rc
].name
= (allow_list
) ? "^T" : "";
5647 ekey
[rc
++].label
= (allow_list
) ? N_("ToFldrs") : "";
5649 if(ps_global
->context_list
->next
){
5650 ekey
[rc
].ch
= ctrl('P');
5652 ekey
[rc
].name
= "^P";
5653 ekey
[rc
++].label
= N_("Prev Collection");
5655 ekey
[rc
].ch
= ctrl('N');
5657 ekey
[rc
].name
= "^N";
5658 ekey
[rc
++].label
= N_("Next Collection");
5661 ekey
[rc
].ch
= ctrl('W');
5663 ekey
[rc
].name
= "^W";
5664 ekey
[rc
++].label
= N_("INBOX");
5666 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps_global
)){
5669 ekey
[rc
].name
= "TAB";
5670 ekey
[rc
++].label
= N_("Complete");
5673 if(F_ON(F_ENABLE_SUB_LISTS
, ps_global
)){
5674 ekey
[rc
].ch
= ctrl('X');
5676 ekey
[rc
].name
= "^X";
5677 ekey
[rc
++].label
= N_("ListMatches");
5680 if(ps_global
->context_list
->next
&& F_ON(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
5681 ekey
[rc
].ch
= KEY_UP
;
5684 ekey
[rc
++].label
= "";
5686 ekey
[rc
].ch
= KEY_DOWN
;
5689 ekey
[rc
++].label
= "";
5691 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
5692 ekey
[rc
].ch
= KEY_UP
;
5696 ekey
[rc
++].label
= "";
5698 ekey
[rc
].ch
= KEY_DOWN
;
5701 ekey
[rc
++].label
= "";
5708 * Figure out next default value for this context. The idea
5709 * is that in each context the last folder opened is cached.
5710 * It's up to pick it out and display it. This is fine
5711 * and dandy if we've currently got the inbox open, BUT
5712 * if not, make the inbox the default the first time thru.
5715 last_folder
= ps_global
->inbox_name
;
5716 inbox
= 1; /* pretend we're in inbox from here on out */
5719 last_folder
= (ps_global
->last_unambig_folder
[0])
5720 ? ps_global
->last_unambig_folder
5721 : ((tc
->last_folder
[0]) ? tc
->last_folder
: NULL
);
5723 if(last_folder
){ /* MAXPATH + 1 = sizeof(expanded) */
5724 unsigned char *fname
= folder_name_decoded((unsigned char *)last_folder
);
5725 snprintf(expanded
, sizeof(expanded
), " [%.*s]", MAXPATH
+1-5,
5726 fname
? (char *) fname
: last_folder
);
5727 if(fname
) fs_give((void **)&fname
);
5732 expanded
[sizeof(expanded
)-1] = '\0';
5734 /* only show collection number if more than one available */
5735 if(ps_global
->context_list
->next
) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5736 snprintf(prompt
, sizeof(prompt
), "GOTO %s in <%s> %.*s%s: ",
5737 NEWS_TEST(tc
) ? "news group" : "folder",
5738 tc
->nickname
, MAX_SCREEN_COLS
+1-50, expanded
,
5739 *expanded
? " " : "");
5740 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5741 snprintf(prompt
, sizeof(prompt
), "GOTO folder %.*s%s: ", MAX_SCREEN_COLS
+1-20, expanded
,
5742 *expanded
? " " : "");
5744 prompt
[sizeof(prompt
)-1] = '\0';
5746 if(utf8_width(prompt
) > MAXPROMPT
){
5747 if(ps_global
->context_list
->next
) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5748 snprintf(prompt
, sizeof(prompt
), "GOTO <%s> %.*s%s: ",
5749 tc
->nickname
, MAX_SCREEN_COLS
+1-50, expanded
,
5750 *expanded
? " " : "");
5751 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5752 snprintf(prompt
, sizeof(prompt
), "GOTO %.*s%s: ", MAX_SCREEN_COLS
+1-20, expanded
,
5753 *expanded
? " " : "");
5755 prompt
[sizeof(prompt
)-1] = '\0';
5757 if(utf8_width(prompt
) > MAXPROMPT
){
5758 if(ps_global
->context_list
->next
) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5759 snprintf(prompt
, sizeof(prompt
), "<%s> %.*s%s: ",
5760 tc
->nickname
, MAX_SCREEN_COLS
+1-50, expanded
,
5761 *expanded
? " " : "");
5762 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5763 snprintf(prompt
, sizeof(prompt
), "%.*s%s: ", MAX_SCREEN_COLS
+1-20, expanded
,
5764 *expanded
? " " : "");
5766 prompt
[sizeof(prompt
)-1] = '\0';
5771 if(items_in_hist(history
) > 1){
5772 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
5773 ekey
[ku
].label
= HISTORY_KEYLABEL
;
5774 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
5775 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
5779 ekey
[ku
].label
= "";
5780 ekey
[ku
+1].name
= "";
5781 ekey
[ku
+1].label
= "";
5785 /* is there any other way to do this? The point is that we
5786 * are trying to hide mutf7 from the user, and use the utf8
5787 * equivalent. So we create a variable f to take place of
5788 * newfolder, including content and size. f2 is copy of f1
5789 * that has to freed. Sigh!
5791 f3
= (unsigned char *) cpystr(newfolder
);
5792 f1
= fs_get(sizeof(newfolder
));
5793 f2
= folder_name_decoded(f3
);
5794 if(f3
) fs_give((void **)&f3
);
5795 strncpy((char *)f1
, (char *)f2
, sizeof(newfolder
));
5796 f1
[sizeof(newfolder
)-1] = '\0';
5797 if(f2
) fs_give((void **)&f2
);
5799 flags
= OE_APPEND_CURRENT
;
5800 rc
= optionally_enter((char *) f1
, qline
, 0, sizeof(newfolder
),
5801 (char *) prompt
, ekey
, help
, &flags
);
5803 f2
= folder_name_encoded(f1
);
5804 strncpy(newfolder
, (char *)f2
, sizeof(newfolder
));
5805 if(f1
) fs_give((void **)&f1
);
5806 if(f2
) fs_give((void **)&f2
);
5808 ps_global
->mangled_footer
= 1;
5811 case -1 : /* o_e says error! */
5812 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
5813 _("Error reading folder name"));
5816 case 0 : /* o_e says normal entry */
5817 removing_trailing_white_space(newfolder
);
5818 removing_leading_white_space(newfolder
);
5821 char *name
, *fullname
= NULL
;
5822 int exists
, breakout
= 0;
5824 save_hist(history
, newfolder
, 0, tc
);
5826 if(!(name
= folder_is_nick(newfolder
, FOLDERS(tc
),
5830 if(update_folder_spec(expanded
, sizeof(expanded
), name
)){
5831 strncpy(name
= newfolder
, expanded
, sizeof(newfolder
));
5832 newfolder
[sizeof(newfolder
)-1] = '\0';
5835 exists
= folder_name_exists(tc
, name
, &fullname
);
5838 strncpy(name
= newfolder
, fullname
, sizeof(newfolder
));
5839 newfolder
[sizeof(newfolder
)-1] = '\0';
5840 fs_give((void **) &fullname
);
5845 * if we know the things a folder, open it.
5846 * else if we know its a directory, visit it.
5847 * else we're not sure (it either doesn't really
5848 * exist or its unLISTable) so try opening it anyway
5850 if(exists
& FEX_ISFILE
){
5854 else if((exists
& FEX_ISDIR
)){
5856 CONTEXT_S
*fake_context
;
5857 char tmp
[MAILTMPLEN
];
5860 strncpy(tmp
, name
, sizeof(tmp
));
5861 tmp
[sizeof(tmp
)-2-1] = '\0';
5862 if(tmp
[(l
= strlen(tmp
)) - 1] != tc
->dir
->delim
){
5863 if(l
< sizeof(tmp
)){
5864 tmp
[l
] = tc
->dir
->delim
;
5865 strncpy(&tmp
[l
+1], "[]", sizeof(tmp
)-(l
+1));
5869 strncat(tmp
, "[]", sizeof(tmp
)-strlen(tmp
)-1);
5871 tmp
[sizeof(tmp
)-1] = '\0';
5873 fake_context
= new_context(tmp
, 0);
5874 newfolder
[0] = '\0';
5875 done
= display_folder_list(&fake_context
, newfolder
,
5876 1, folders_for_goto
);
5877 free_context(&fake_context
);
5880 else if(!(tc
->use
& CNTXT_INCMNG
)){
5881 done
= display_folder_list(&tc
, newfolder
,
5882 1, folders_for_goto
);
5886 else if((exists
& FEX_ERROR
)){
5887 q_status_message1(SM_ORDER
, 0, 3,
5888 _("Problem accessing folder \"%s\""),
5897 if(exists
== FEX_ERROR
)
5898 q_status_message1(SM_ORDER
, 0, 3,
5899 _("Problem accessing folder \"%s\""),
5901 else if(tc
->use
& CNTXT_INCMNG
)
5902 q_status_message1(SM_ORDER
, 0, 3,
5903 _("Can't find Incoming Folder: %s"),
5905 else if(context_isambig(newfolder
))
5906 q_status_message2(SM_ORDER
, 0, 3,
5907 _("Can't find folder \"%s\" in %s"),
5908 newfolder
, (void *) tc
->nickname
);
5910 q_status_message1(SM_ORDER
, 0, 3,
5911 _("Can't find folder \"%s\""),
5916 else if(last_folder
){
5917 if(ps_global
->goto_default_rule
== GOTO_FIRST_CLCTN_DEF_INBOX
5918 && !strucmp(last_folder
, ps_global
->inbox_name
)
5919 && tc
== ((ps_global
->context_list
->use
& CNTXT_INCMNG
)
5920 ? ps_global
->context_list
->next
: ps_global
->context_list
)){
5922 (*notrealinbox
) = 0;
5924 tc
= ps_global
->context_list
;
5927 strncpy(newfolder
, last_folder
, sizeof(newfolder
));
5928 newfolder
[sizeof(newfolder
)-1] = '\0';
5929 save_hist(history
, newfolder
, 0, tc
);
5933 /* fall thru like they cancelled */
5935 case 1 : /* o_e says user cancel */
5936 cmd_cancelled("Open folder");
5939 case 2 : /* o_e says user wants list */
5940 r
= display_folder_list(&tc
, newfolder
, 0, folders_for_goto
);
5946 case 3 : /* o_e says user wants help */
5947 help
= help
== NO_HELP
? h_oe_broach
: NO_HELP
;
5950 case 4 : /* redraw */
5953 case 10 : /* Previous collection */
5954 tc2
= ps_global
->context_list
;
5955 while(tc2
->next
&& tc2
->next
!= tc
)
5961 case 11 : /* Next collection */
5962 tc
= (tc
->next
) ? tc
->next
: ps_global
->context_list
;
5965 case 12 : /* file name completion */
5966 if(!folder_complete(tc
, newfolder
, sizeof(newfolder
), &n
)){
5967 if(n
&& last_rc
== 12 && !(flags
& OE_USER_MODIFIED
)){
5968 r
= display_folder_list(&tc
, newfolder
, 1,folders_for_goto
);
5970 done
++; /* bingo! */
5972 rc
= 0; /* burn last_rc */
5980 case 14 : /* file name completion */
5981 r
= display_folder_list(&tc
, newfolder
, 2, folders_for_goto
);
5983 done
++; /* bingo! */
5985 rc
= 0; /* burn last_rc */
5989 case 17 : /* GoTo INBOX */
5991 strncpy(newfolder
, ps_global
->inbox_name
, sizeof(newfolder
)-1);
5992 newfolder
[sizeof(newfolder
)-1] = '\0';
5994 (*notrealinbox
) = 0;
5996 tc
= ps_global
->context_list
;
5997 save_hist(history
, newfolder
, 0, tc
);
6002 if((p
= get_prev_hist(history
, newfolder
, 0, tc
)) != NULL
){
6003 strncpy(newfolder
, p
, sizeof(newfolder
));
6004 newfolder
[sizeof(newfolder
)-1] = '\0';
6005 if(history
->hist
[history
->curindex
])
6006 tc
= history
->hist
[history
->curindex
]->cntxt
;
6014 if((p
= get_next_hist(history
, newfolder
, 0, tc
)) != NULL
){
6015 strncpy(newfolder
, p
, sizeof(newfolder
));
6016 newfolder
[sizeof(newfolder
)-1] = '\0';
6017 if(history
->hist
[history
->curindex
])
6018 tc
= history
->hist
[history
->curindex
]->cntxt
;
6026 alpine_panic("Unhandled case");
6033 dprint((2, "broach folder, name entered \"%s\"\n",
6034 newfolder
? newfolder
: "?"));
6036 /*-- Just check that we can expand this. It gets done for real later --*/
6037 strncpy(expanded
, newfolder
, sizeof(expanded
));
6038 expanded
[sizeof(expanded
)-1] = '\0';
6040 if(!expand_foldername(expanded
, sizeof(expanded
))) {
6041 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
6042 expanded
? expanded
: "?"));
6051 /*----------------------------------------------------------------------
6052 Check to see if user wants to reopen dead stream.
6057 Result: 1 if the folder was successfully updatedn
6062 ask_mailbox_reopen(struct pine
*ps
, int *reopenp
)
6064 if(((ps
->mail_stream
->dtb
6065 && ((ps
->mail_stream
->dtb
->flags
& DR_NONEWMAIL
)
6066 || (ps
->mail_stream
->rdonly
6067 && ps
->mail_stream
->dtb
->flags
& DR_NONEWMAILRONLY
)))
6068 && (ps
->reopen_rule
== REOPEN_ASK_ASK_Y
6069 || ps
->reopen_rule
== REOPEN_ASK_ASK_N
6070 || ps
->reopen_rule
== REOPEN_ASK_NO_Y
6071 || ps
->reopen_rule
== REOPEN_ASK_NO_N
))
6072 || ((ps
->mail_stream
->dtb
6073 && ps
->mail_stream
->rdonly
6074 && !(ps
->mail_stream
->dtb
->flags
& DR_LOCAL
))
6075 && (ps
->reopen_rule
== REOPEN_YES_ASK_Y
6076 || ps
->reopen_rule
== REOPEN_YES_ASK_N
6077 || ps
->reopen_rule
== REOPEN_ASK_ASK_Y
6078 || ps
->reopen_rule
== REOPEN_ASK_ASK_N
))){
6081 switch(ps
->reopen_rule
){
6082 case REOPEN_YES_ASK_Y
:
6083 case REOPEN_ASK_ASK_Y
:
6084 case REOPEN_ASK_NO_Y
:
6093 switch(want_to("Re-open folder to check for new messages", deefault
,
6094 'x', h_reopen_folder
, WT_NORM
)){
6109 /*----------------------------------------------------------------------
6110 Check to see if user input is in form of old c-client mailbox speck
6115 Result: 1 if the folder was successfully updatedn
6120 update_folder_spec(char *new, size_t newlen
, char *old
)
6126 if(*(p
= old
) == '*') /* old form? */
6129 if(*old
== '{') /* copy host spec */
6131 switch(*new = *old
++){
6136 if(!struncmp(old
, "nntp", 4))
6144 while(*new++ != '}' && (new-orignew
) < newlen
-1);
6146 if((*p
== '*' && *old
) || ((*old
== '*') ? *++old
: 0)){
6148 * OK, some heuristics here. If it looks like a newsgroup
6149 * then we plunk it into the #news namespace else we
6150 * assume that they're trying to get at a #public folder...
6153 *p
&& (isalnum((unsigned char) *p
) || strindex(".-", *p
));
6157 sstrncpy(&new, (*p
&& !nntp
) ? "#public/" : "#news.", newlen
-(new-orignew
));
6158 strncpy(new, old
, newlen
-(new-orignew
));
6162 orignew
[newlen
-1] = '\0';
6168 /*----------------------------------------------------------------------
6169 Open the requested folder in the requested context
6171 Args: state -- usual pine state struct
6172 newfolder -- folder to open
6173 new_context -- folder context might live in
6174 stream -- candidate for recycling
6176 Result: New folder open or not (if error), and we're set to
6177 enter the index screen.
6180 visit_folder(struct pine
*state
, char *newfolder
, CONTEXT_S
*new_context
,
6181 MAILSTREAM
*stream
, long unsigned int flags
)
6183 dprint((9, "visit_folder(%s, %s)\n",
6184 newfolder
? newfolder
: "?",
6185 (new_context
&& new_context
->context
)
6186 ? new_context
->context
: "(NULL)"));
6188 if(ps_global
&& ps_global
->ttyo
){
6189 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
6190 ps_global
->mangled_footer
= 1;
6193 if(do_broach_folder(newfolder
, new_context
, stream
? &stream
: NULL
,
6195 || !sp_flagged(state
->mail_stream
, SP_LOCKED
))
6196 state
->next_screen
= mail_index_screen
;
6198 state
->next_screen
= folder_screen
;
6202 /*----------------------------------------------------------------------
6203 Move read messages from folder if listed in archive
6209 read_msg_prompt(long int n
, char *f
)
6211 char buf
[MAX_SCREEN_COLS
+1];
6213 snprintf(buf
, sizeof(buf
), "Save the %ld read message%s in \"%s\"", n
, plural(n
), f
);
6214 buf
[sizeof(buf
)-1] = '\0';
6215 return(want_to(buf
, 'y', 0, NO_HELP
, WT_NORM
) == 'y');
6219 /*----------------------------------------------------------------------
6220 Print current message[s] or folder index
6222 Args: state -- pointer to struct holding a bunch of pine state
6223 msgmap -- table mapping msg nums to c-client sequence nums
6224 aopt -- aggregate options
6225 in_index -- boolean indicating we're called from Index Screen
6227 Filters the original header and sends stuff to printer
6230 cmd_print(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, CmdWhere in_index
)
6233 long i
, msgs
, rawno
;
6234 int next
= 0, do_index
= 0, rv
= 0;
6239 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
6242 msgs
= mn_total_cur(msgmap
);
6244 if((in_index
!= View
) && F_ON(F_PRINT_INDEX
, state
)){
6247 static ESCKEY_S prt_opts
[] = {
6248 {'i', 'i', "I", N_("Index")},
6249 {'m', 'm', "M", NULL
},
6250 {-1, 0, NULL
, NULL
}};
6252 if(in_index
== ThrdIndx
){
6253 /* TRANSLATORS: This is a question, Print Index ? */
6254 if(want_to(_("Print Index"), 'y', 'x', NO_HELP
, WT_NORM
) == 'y')
6260 snprintf(m
, sizeof(m
), "Message%s", (msgs
>1L) ? "s" : "");
6261 m
[sizeof(m
)-1] = '\0';
6262 prt_opts
[1].label
= m
;
6263 snprintf(prompt
, sizeof(prompt
), "Print %sFolder Index or %s %s? ",
6264 (aopt
& MCMD_AGG_2
) ? "thread " : MCMD_ISAGG(aopt
) ? "selected " : "",
6265 (aopt
& MCMD_AGG_2
) ? "thread" : MCMD_ISAGG(aopt
) ? "selected" : "current", m
);
6266 prompt
[sizeof(prompt
)-1] = '\0';
6268 ans
= radio_buttons(prompt
, -FOOTER_ROWS(state
), prt_opts
, 'm', 'x',
6269 NO_HELP
, RB_NORM
|RB_SEQ_SENSITIVE
);
6274 cmd_cancelled("Print");
6275 if(MCMD_ISAGG(aopt
))
6276 restore_selected(msgmap
);
6291 snprintf(prompt
, sizeof(prompt
), "%sFolder Index",
6292 (aopt
& MCMD_AGG_2
) ? "Thread " : MCMD_ISAGG(aopt
) ? "Selected " : "");
6294 snprintf(prompt
, sizeof(prompt
), "%s messages", long2string(msgs
));
6296 snprintf(prompt
, sizeof(prompt
), "Message %s", long2string(mn_get_cur(msgmap
)));
6298 prompt
[sizeof(prompt
)-1] = '\0';
6300 if(open_printer(prompt
) < 0){
6301 if(MCMD_ISAGG(aopt
))
6302 restore_selected(msgmap
);
6310 tc
= format_titlebar();
6312 /* Print titlebar... */
6313 print_text1("%s\n\n", tc
? tc
->titlebar_line
: "");
6314 /* then all the index members... */
6315 if(!print_index(state
, msgmap
, MCMD_ISAGG(aopt
)))
6316 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6317 _("Error printing folder index"));
6323 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
), next
++){
6324 if(next
&& F_ON(F_AGG_PRINT_FF
, state
))
6325 if(!print_char(FORMFEED
)){
6330 if(!(state
->mail_stream
6331 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
6332 && rawno
<= state
->mail_stream
->nmsgs
6333 && (mc
= mail_elt(state
->mail_stream
, rawno
))
6337 if(!(e
=pine_mail_fetchstructure(state
->mail_stream
,
6340 || (F_ON(F_FROM_DELIM_IN_PRINT
, ps_global
)
6341 && !bezerk_delimiter(e
, mc
, print_char
, next
))
6342 || !format_message(mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
6343 e
, b
, NULL
, FM_NEW_MESS
| FM_NOINDENT
,
6345 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6346 _("Error printing message"));
6355 if(MCMD_ISAGG(aopt
))
6356 restore_selected(msgmap
);
6362 /*----------------------------------------------------------------------
6365 Args: state -- various pine state bits
6366 msgmap -- Message number mapping table
6367 aopt -- option flags
6369 Filters the original header and sends stuff to specified command
6372 cmd_pipe(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
6378 char *resultfilename
= NULL
, prompt
[80], *p
;
6379 int done
= 0, rv
= 0;
6381 int fourlabel
= -1, j
= 0, next
= 0, ku
;
6382 int pipe_rv
; /* rv of proc to separate from close_system_pipe rv */
6384 unsigned flagsforhist
= 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6385 static HISTORY_S
*history
= NULL
;
6386 int capture
= 1, raw
= 0, delimit
= 0, newpipe
= 0;
6387 char pipe_command
[MAXPATH
];
6388 ESCKEY_S pipe_opt
[8];
6390 if(ps_global
->restricted
){
6391 q_status_message(SM_ORDER
| SM_DING
, 0, 4,
6392 "Alpine demo can't pipe messages");
6395 else if(!any_messages(msgmap
, NULL
, "to Pipe"))
6398 pipe_command
[0] = '\0';
6399 init_hist(&history
, HISTSIZE
);
6400 flagsforhist
= (raw
? 0x8 : 0) +
6401 (delimit
? 0x4 : 0) +
6402 (newpipe
? 0x2 : 0) +
6403 (capture
? 0x1 : 0);
6404 if((p
= get_prev_hist(history
, "", flagsforhist
, NULL
)) != NULL
){
6405 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6406 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6407 if(history
->hist
[history
->curindex
]){
6408 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6409 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6410 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6411 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6412 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6417 pipe_opt
[j
].rval
= 0;
6418 pipe_opt
[j
].name
= "";
6419 pipe_opt
[j
++].label
= "";
6421 pipe_opt
[j
].ch
= ctrl('W');
6422 pipe_opt
[j
].rval
= 10;
6423 pipe_opt
[j
].name
= "^W";
6424 pipe_opt
[j
++].label
= NULL
;
6426 pipe_opt
[j
].ch
= ctrl('Y');
6427 pipe_opt
[j
].rval
= 11;
6428 pipe_opt
[j
].name
= "^Y";
6429 pipe_opt
[j
++].label
= NULL
;
6431 pipe_opt
[j
].ch
= ctrl('R');
6432 pipe_opt
[j
].rval
= 12;
6433 pipe_opt
[j
].name
= "^R";
6434 pipe_opt
[j
++].label
= NULL
;
6436 if(MCMD_ISAGG(aopt
)){
6437 if(!pseudo_selected(state
->mail_stream
, msgmap
))
6441 pipe_opt
[j
].ch
= ctrl('T');
6442 pipe_opt
[j
].rval
= 13;
6443 pipe_opt
[j
].name
= "^T";
6444 pipe_opt
[j
++].label
= NULL
;
6448 pipe_opt
[j
].ch
= KEY_UP
;
6449 pipe_opt
[j
].rval
= 30;
6450 pipe_opt
[j
].name
= "";
6452 pipe_opt
[j
++].label
= "";
6454 pipe_opt
[j
].ch
= KEY_DOWN
;
6455 pipe_opt
[j
].rval
= 31;
6456 pipe_opt
[j
].name
= "";
6457 pipe_opt
[j
++].label
= "";
6459 pipe_opt
[j
].ch
= -1;
6464 snprintf(prompt
, sizeof(prompt
), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6466 MCMD_ISAGG(aopt
) ? "s" : " ",
6467 MCMD_ISAGG(aopt
) ? "" : comatose(mn_get_cur(msgmap
)),
6468 (!capture
|| delimit
|| (newpipe
&& MCMD_ISAGG(aopt
))) ? "(" : "",
6469 capture
? "" : "uncaptured",
6470 (!capture
&& delimit
) ? "," : "",
6471 delimit
? "delimited" : "",
6472 ((!capture
|| delimit
) && newpipe
&& MCMD_ISAGG(aopt
)) ? "," : "",
6473 (newpipe
&& MCMD_ISAGG(aopt
)) ? "new pipe" : "",
6474 (!capture
|| delimit
|| (newpipe
&& MCMD_ISAGG(aopt
))) ? ") " : "");
6475 prompt
[sizeof(prompt
)-1] = '\0';
6476 pipe_opt
[1].label
= raw
? N_("Shown Text") : N_("Raw Text");
6477 pipe_opt
[2].label
= capture
? N_("Free Output") : N_("Capture Output");
6478 pipe_opt
[3].label
= delimit
? N_("No Delimiter") : N_("With Delimiter");
6480 pipe_opt
[fourlabel
].label
= newpipe
? N_("To Same Pipe") : N_("To Individual Pipes");
6484 * 2 is really 1 because there will be one real entry and
6485 * one entry of "" because of the get_prev_hist above.
6487 if(items_in_hist(history
) > 2){
6488 pipe_opt
[ku
].name
= HISTORY_UP_KEYNAME
;
6489 pipe_opt
[ku
].label
= HISTORY_KEYLABEL
;
6490 pipe_opt
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
6491 pipe_opt
[ku
+1].label
= HISTORY_KEYLABEL
;
6494 pipe_opt
[ku
].name
= "";
6495 pipe_opt
[ku
].label
= "";
6496 pipe_opt
[ku
+1].name
= "";
6497 pipe_opt
[ku
+1].label
= "";
6500 flags
= OE_APPEND_CURRENT
| OE_SEQ_SENSITIVE
;
6501 switch(optionally_enter(pipe_command
, -FOOTER_ROWS(state
), 0,
6502 sizeof(pipe_command
), prompt
,
6503 pipe_opt
, NO_HELP
, &flags
)){
6505 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
6506 _("Internal problem encountered"));
6510 case 10 : /* flip raw bit */
6514 case 11 : /* flip capture bit */
6518 case 12 : /* flip delimit bit */
6522 case 13 : /* flip newpipe bit */
6527 flagsforhist
= (raw
? 0x8 : 0) +
6528 (delimit
? 0x4 : 0) +
6529 (newpipe
? 0x2 : 0) +
6530 (capture
? 0x1 : 0);
6531 if((p
= get_prev_hist(history
, pipe_command
, flagsforhist
, NULL
)) != NULL
){
6532 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6533 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6534 if(history
->hist
[history
->curindex
]){
6535 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6536 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6537 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6538 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6539 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6548 flagsforhist
= (raw
? 0x8 : 0) +
6549 (delimit
? 0x4 : 0) +
6550 (newpipe
? 0x2 : 0) +
6551 (capture
? 0x1 : 0);
6552 if((p
= get_next_hist(history
, pipe_command
, flagsforhist
, NULL
)) != NULL
){
6553 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6554 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6555 if(history
->hist
[history
->curindex
]){
6556 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6557 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6558 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6559 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6560 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6569 if(pipe_command
[0]){
6571 flagsforhist
= (raw
? 0x8 : 0) +
6572 (delimit
? 0x4 : 0) +
6573 (newpipe
? 0x2 : 0) +
6574 (capture
? 0x1 : 0);
6575 save_hist(history
, pipe_command
, flagsforhist
, NULL
);
6577 flags
= PIPE_USER
| PIPE_WRITE
| PIPE_STDERR
;
6578 flags
|= (raw
? PIPE_RAW
: 0);
6584 ps_global
->mangled_screen
= 1;
6585 ps_global
->in_init_seq
= 1;
6587 flags
|= PIPE_RESET
;
6590 if(!newpipe
&& !(syspipe
= cmd_pipe_open(pipe_command
,
6591 (flags
& PIPE_RESET
)
6597 for(i
= mn_first_cur(msgmap
);
6599 i
= mn_next_cur(msgmap
)){
6600 e
= pine_mail_fetchstructure(ps_global
->mail_stream
,
6601 mn_m2raw(msgmap
, i
), &b
);
6602 if(!(state
->mail_stream
6603 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
6604 && rawno
<= state
->mail_stream
->nmsgs
6605 && (mc
= mail_elt(state
->mail_stream
, rawno
))
6610 && !(syspipe
= cmd_pipe_open(pipe_command
,
6611 (flags
& PIPE_RESET
)
6615 || (delimit
&& !bezerk_delimiter(e
, mc
, pc
, next
++)))
6622 prime_raw_pipe_getc(ps_global
->mail_stream
,
6623 mn_m2raw(msgmap
, i
), -1L, 0L);
6625 gf_link_filter(gf_nvtnl_local
, NULL
);
6626 if((pipe_err
= gf_pipe(raw_pipe_getc
, pc
)) != NULL
){
6627 q_status_message1(SM_ORDER
|SM_DING
,
6629 _("Internal Error: %s"),
6634 else if(!format_message(mn_m2raw(msgmap
, i
), e
, b
,
6635 NULL
, FM_NEW_MESS
| FM_NOWRAP
, pc
))
6640 if(close_system_pipe(&syspipe
, &pipe_rv
, pipe_callback
) == -1)
6645 ps_global
->in_init_seq
= 0;
6648 if(close_system_pipe(&syspipe
, &pipe_rv
, pipe_callback
) == -1)
6650 if(done
) /* say we had a problem */
6651 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6652 _("Error piping message"));
6653 else if(resultfilename
){
6655 /* only display if no error */
6656 display_output_file(resultfilename
, "PIPE MESSAGE",
6658 fs_give((void **)&resultfilename
);
6662 q_status_message(SM_ORDER
, 0, 2, _("Pipe command completed"));
6668 /* else fall thru as if cancelled */
6671 cmd_cancelled("Pipe command");
6676 helper(h_common_pipe
, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE
);
6677 ps_global
->mangled_screen
= 1;
6680 case 2 : /* no place to escape to */
6681 case 4 : /* can't suspend */
6687 ps_global
->mangled_footer
= 1;
6688 if(MCMD_ISAGG(aopt
))
6689 restore_selected(msgmap
);
6695 /*----------------------------------------------------------------------
6696 Screen to offer list management commands contained in message
6698 Args: state -- pointer to struct holding a bunch of pine state
6699 msgmap -- table mapping msg nums to c-client sequence nums
6700 aopt -- aggregate options
6704 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6707 rfc2369_display(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
)
6710 char *h
, *hdrs
[MLCMD_COUNT
+ 1];
6711 long index_no
= mn_raw2m(msgmap
, msgno
);
6712 RFC2369_S data
[MLCMD_COUNT
];
6714 /* for each header field */
6715 if((h
= pine_fetchheader_lines(stream
, msgno
, NULL
, rfc2369_hdrs(hdrs
))) != NULL
){
6716 memset(&data
[0], 0, sizeof(RFC2369_S
) * MLCMD_COUNT
);
6717 if(rfc2369_parse_fields(h
, &data
[0])){
6720 if((explain
= list_mgmt_text(data
, index_no
)) != NULL
){
6721 list_mgmt_screen(explain
);
6722 ps_global
->mangled_screen
= 1;
6728 fs_give((void **) &h
);
6732 q_status_message1(SM_ORDER
, 0, 3,
6733 "Message %s contains no list management information",
6734 comatose(index_no
));
6739 list_mgmt_text(RFC2369_S
*data
, long int msgno
)
6742 int i
, j
, n
, fields
= 0;
6743 static char *rfc2369_intro1
=
6744 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6745 static char *rfc2369_intro2
[] = {
6746 N_(" has information associated with it "),
6747 N_("that explains how to participate in an email list. An "),
6748 N_("email list is represented by a single email address that "),
6749 N_("users sharing a common interest can send messages to (known "),
6750 N_("as posting) which are then redistributed to all members "),
6751 N_("of the list (sometimes after review by a moderator)."),
6752 N_("<P>List participation commands in this message include:"),
6756 if((store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) != NULL
){
6758 /* Insert introductory text */
6759 so_puts(store
, rfc2369_intro1
);
6761 so_puts(store
, comatose(msgno
));
6763 for(i
= 0; rfc2369_intro2
[i
]; i
++)
6764 so_puts(store
, _(rfc2369_intro2
[i
]));
6766 so_puts(store
, "<P>");
6767 for(i
= 0; i
< MLCMD_COUNT
; i
++)
6768 if(data
[i
].data
[0].value
6769 || data
[i
].data
[0].comment
6770 || data
[i
].data
[0].error
){
6772 so_puts(store
, "<UL>");
6774 so_puts(store
, "<LI>");
6776 (n
= (data
[i
].data
[1].value
|| data
[i
].data
[1].comment
))
6780 so_puts(store
, data
[i
].field
.description
);
6781 so_puts(store
, ". ");
6784 so_puts(store
, "<OL>");
6788 && (data
[i
].data
[j
].comment
6789 || data
[i
].data
[j
].value
6790 || data
[i
].data
[j
].error
);
6793 so_puts(store
, n
? "<P><LI>" : "<P>");
6795 if(data
[i
].data
[j
].comment
){
6797 _("With the provided comment:<P><BLOCKQUOTE>"));
6798 so_puts(store
, data
[i
].data
[j
].comment
);
6799 so_puts(store
, "</BLOCKQUOTE><P>");
6802 if(data
[i
].data
[j
].value
){
6804 && !strucmp(data
[i
].data
[j
].value
, "NO")){
6806 _("Posting is <EM>not</EM> allowed on this list"));
6809 so_puts(store
, "Select <A HREF=\"");
6810 so_puts(store
, data
[i
].data
[j
].value
);
6811 so_puts(store
, "\">HERE</A> to ");
6812 so_puts(store
, (data
[i
].field
.action
)
6813 ? data
[i
].field
.action
6817 so_puts(store
, ".");
6820 if(data
[i
].data
[j
].error
){
6821 so_puts(store
, "<P>Unfortunately, Alpine can not offer");
6822 so_puts(store
, " to take direct action based upon it");
6823 so_puts(store
, " because it was improperly formatted.");
6824 so_puts(store
, " The unrecognized data associated with");
6825 so_puts(store
, " the \"");
6826 so_puts(store
, data
[i
].field
.name
);
6827 so_puts(store
, "\" header field was:<P><BLOCKQUOTE>");
6828 so_puts(store
, data
[i
].data
[j
].error
);
6829 so_puts(store
, "</BLOCKQUOTE>");
6832 so_puts(store
, "<P>");
6836 so_puts(store
, "</OL>");
6840 so_puts(store
, "</UL>");
6842 so_puts(store
, "</BODY></HTML>");
6850 list_mgmt_screen(STORE_S
*html
)
6856 HANDLE_S
*handles
= NULL
;
6860 so_seek(html
, 0L, 0);
6861 gf_set_so_readc(&gc
, html
);
6863 init_handles(&handles
);
6865 if((store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) != NULL
){
6866 gf_set_so_writec(&pc
, store
);
6869 gf_link_filter(gf_html2plain
,
6870 gf_html2plain_opt(NULL
, ps_global
->ttyo
->screen_cols
,
6871 non_messageview_margin(), &handles
, NULL
, 0));
6873 error
= gf_pipe(gc
, pc
);
6875 gf_clear_so_writec(store
);
6880 memset(&sargs
, 0, sizeof(SCROLL_S
));
6881 sargs
.text
.text
= so_text(store
);
6882 sargs
.text
.src
= CharStar
;
6883 sargs
.text
.desc
= "list commands";
6884 sargs
.text
.handles
= handles
;
6886 sargs
.start
.on
= Offset
;
6887 sargs
.start
.loc
.offset
= offset
;
6890 sargs
.bar
.title
= _("MAIL LIST COMMANDS");
6891 sargs
.bar
.style
= MessageNumber
;
6892 sargs
.resize_exit
= 1;
6893 sargs
.help
.text
= h_special_list_commands
;
6894 sargs
.help
.title
= _("HELP FOR LIST COMMANDS");
6895 sargs
.keys
.menu
= &listmgr_keymenu
;
6896 setbitmap(sargs
.keys
.bitmap
);
6898 clrbitn(LM_TRY_KEY
, sargs
.keys
.bitmap
);
6899 clrbitn(LM_PREV_KEY
, sargs
.keys
.bitmap
);
6900 clrbitn(LM_NEXT_KEY
, sargs
.keys
.bitmap
);
6903 cmd
= scrolltool(&sargs
);
6904 offset
= sargs
.start
.loc
.offset
;
6910 free_handles(&handles
);
6911 gf_clear_so_readc(html
);
6913 while(cmd
== MC_RESIZE
);
6917 /*----------------------------------------------------------------------
6918 Prompt the user for the type of select desired
6920 NOTE: any and all functions that successfully exit the second
6921 switch() statement below (currently "select_*() functions"),
6922 *MUST* update the folder's MESSAGECACHE element's "searched"
6923 bits to reflect the search result. Functions using
6924 mail_search() get this for free, the others must update 'em
6927 Returns -1 if canceled without changing selection
6928 0 if selection may have changed
6931 aggregate_select(struct pine
*state
, MSGNO_S
*msgmap
, int q_line
, CmdWhere in_index
)
6933 long i
, diff
, old_tot
, msgno
, raw
;
6934 int p
= 0, q
= 0, rv
= 0, narrow
= 0, hidden
, ret
= -1;
6937 SEARCHSET
*limitsrch
= NULL
;
6939 extern MAILSTREAM
*mm_search_stream
;
6940 extern long mm_search_count
;
6942 hidden
= any_lflagged(msgmap
, MN_HIDE
) > 0L;
6943 mm_search_stream
= state
->mail_stream
;
6944 mm_search_count
= 0L;
6946 if(is_imap_stream(state
->mail_stream
) && XGMEXT1(state
->mail_stream
))
6947 sel_opts
= THRD_INDX() ? sel_opts6
: sel_opts5
;
6949 sel_opts
= THRD_INDX() ? sel_opts4
: sel_opts2
;
6952 sel_opts
[SEL_OPTS_THREAD
].ch
= SEL_OPTS_THREAD_CH
;
6955 if(is_imap_stream(state
->mail_stream
) && XGMEXT1(state
->mail_stream
)){
6956 sel_opts
[SEL_OPTS_THREAD
].ch
= SEL_OPTS_XGMEXT_CH
;
6957 sel_opts
[SEL_OPTS_THREAD
].rval
= sel_opts
[SEL_OPTS_XGMEXT
].rval
;
6958 sel_opts
[SEL_OPTS_THREAD
].name
= sel_opts
[SEL_OPTS_XGMEXT
].name
;
6959 sel_opts
[SEL_OPTS_THREAD
].label
= sel_opts
[SEL_OPTS_XGMEXT
].label
;
6960 sel_opts
[SEL_OPTS_XGMEXT
].ch
= -1;
6963 sel_opts
[SEL_OPTS_THREAD
].ch
= -1;
6966 if((old_tot
= any_lflagged(msgmap
, MN_SLCT
)) != 0){
6969 thrd
= fetch_thread(state
->mail_stream
,
6970 mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
6971 /* check if whole thread is selected or not */
6973 count_lflags_in_thread(state
->mail_stream
,thrd
,msgmap
,MN_SLCT
)
6975 count_lflags_in_thread(state
->mail_stream
,thrd
,msgmap
,MN_NONE
))
6978 sel_opts1
[1].label
= i
? N_("unselect Curthrd") : N_("select Curthrd");
6981 i
= get_lflag(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
6983 sel_opts1
[1].label
= i
? N_("unselect Cur") : N_("select Cur");
6986 sel_opts
+= 2; /* disable extra options */
6987 if(is_imap_stream(state
->mail_stream
) && XGMEXT1(state
->mail_stream
))
6988 q
= double_radio_buttons(_(sel_pmt1
), q_line
, sel_opts1
, 'c', 'x', NO_HELP
,
6991 q
= radio_buttons(_(sel_pmt1
), q_line
, sel_opts1
, 'c', 'x', NO_HELP
,
6994 case 'f' : /* flip selection */
6996 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++){
6998 q
= !get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
);
6999 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, q
);
7001 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, !q
);
7003 mn_reset_cur(msgmap
, msgno
= i
);
7009 case 'n' : /* narrow selection */
7013 case 'r' : /* replace selection */
7014 p
= 1; /* set flag we want to replace */
7015 sel_opts
-= 2; /* re-enable first two options */
7016 case 'b' : /* broaden selection */
7017 q
= 0; /* offer criteria prompt */
7020 case 'c' : /* Un/Select Current */
7021 case 'a' : /* Unselect All */
7022 case 'x' : /* cancel */
7026 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
7027 "Unsupported Select option");
7034 if(is_imap_stream(state
->mail_stream
) && XGMEXT1(state
->mail_stream
))
7035 q
= double_radio_buttons(sel_pmt2
, q_line
, sel_opts
, 'c', 'x', NO_HELP
,
7038 q
= radio_buttons(sel_pmt2
, q_line
, sel_opts
, 'c', 'x', NO_HELP
,
7039 RB_NORM
|RB_RET_HELP
);
7042 helper(h_index_cmd_select
, _("HELP FOR SELECT"), HLPD_SIMPLE
);
7043 ps_global
->mangled_screen
= 1;
7050 /* When we are replacing the search, unselect all messages first, unless
7051 * we are cancelling, in whose case, leave the screen as is, because we
7054 if(p
== 1 && q
!= 'x'){
7055 msgno
= any_lflagged(msgmap
, MN_SLCT
);
7056 diff
= (!msgno
) ? mn_get_total(msgmap
) : 0L;
7057 agg_select_all(state
->mail_stream
, msgmap
, &diff
,
7058 any_lflagged(msgmap
, MN_SLCT
) <= 0L);
7062 * The purpose of this is to add the appropriate searchset to the
7063 * search so that the search can be limited to only looking at what
7064 * it needs to look at. That is, if we are narrowing then we only need
7065 * to look at messages which are already selected, and if we are
7066 * broadening, then we only need to look at messages which are not
7067 * yet selected. This routine will work whether or not
7068 * limiting_searchset properly limits the search set. In particular,
7069 * the searchset returned by limiting_searchset may include messages
7070 * which really shouldn't be included. We do that because a too-large
7071 * searchset will break some IMAP servers. It is even possible that it
7072 * becomes inefficient to send the whole set. If the select function
7073 * frees limitsrch, it should be sure to set it to NULL so we won't
7074 * try freeing it again here.
7076 limitsrch
= limiting_searchset(state
->mail_stream
, narrow
);
7079 * NOTE: See note about MESSAGECACHE "searched" bits above!
7082 case 'x': /* cancel */
7083 cmd_cancelled("Select command");
7086 case 'c' : /* select/unselect current */
7087 (void) select_by_current(state
, msgmap
, in_index
);
7091 case 'a' : /* select/unselect all */
7092 msgno
= any_lflagged(msgmap
, MN_SLCT
);
7093 diff
= (!msgno
) ? mn_get_total(msgmap
) : 0L;
7095 agg_select_all(state
->mail_stream
, msgmap
, &diff
,
7096 any_lflagged(msgmap
, MN_SLCT
) <= 0L);
7097 q_status_message4(SM_ORDER
,0,2,
7098 "%s%s message%s %sselected",
7099 msgno
? "" : "All ", comatose(diff
),
7100 plural(diff
), msgno
? "UN" : "");
7103 case 'n' : /* Select by Number */
7106 rv
= select_by_thrd_number(state
->mail_stream
, msgmap
, &limitsrch
);
7108 rv
= select_by_number(state
->mail_stream
, msgmap
, &limitsrch
);
7112 case 'd' : /* Select by Date */
7114 rv
= select_by_date(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
7118 case 't' : /* Text */
7120 rv
= select_by_text(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
7124 case 'z' : /* Size */
7126 rv
= select_by_size(state
->mail_stream
, &limitsrch
);
7129 case 's' : /* Status */
7131 rv
= select_by_status(state
->mail_stream
, &limitsrch
);
7134 case 'k' : /* Keyword */
7136 rv
= select_by_keyword(state
->mail_stream
, &limitsrch
);
7139 case 'r' : /* Rule */
7141 rv
= select_by_rule(state
->mail_stream
, &limitsrch
);
7144 case 'h' : /* Thread */
7146 rv
= select_by_thread(state
->mail_stream
, msgmap
, &limitsrch
);
7149 case 'g' : /* X-GM-EXT-1 */
7151 rv
= select_by_gm_content(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
), &limitsrch
);
7155 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
7156 "Unsupported Select option");
7161 mail_free_searchset(&limitsrch
);
7163 if(rv
) /* bad return value.. */
7164 return(ret
); /* error already displayed */
7166 if(narrow
) /* make sure something was selected */
7167 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++)
7168 if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
7169 && raw
<= state
->mail_stream
->nmsgs
7170 && (mc
= mail_elt(state
->mail_stream
, raw
)) && mc
->searched
){
7171 if(get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
))
7178 if(mm_search_count
){
7180 * loop thru all the messages, adjusting local flag bits
7181 * based on their "searched" bit...
7183 for(i
= 1L, msgno
= 0L; i
<= mn_get_total(msgmap
); i
++)
7185 /* turning OFF selectedness if the "searched" bit isn't lit. */
7186 if(get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
)){
7187 if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
7188 && raw
<= state
->mail_stream
->nmsgs
7189 && (mc
= mail_elt(state
->mail_stream
, raw
))
7192 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, 0);
7194 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, 1);
7196 /* adjust current message in case we unselect and hide it */
7197 else if(msgno
< mn_get_cur(msgmap
)
7199 || !get_lflag(state
->mail_stream
, msgmap
,
7204 else if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
7205 && raw
<= state
->mail_stream
->nmsgs
7206 && (mc
= mail_elt(state
->mail_stream
, raw
)) && mc
->searched
){
7207 /* turn ON selectedness if "searched" bit is lit. */
7208 if(!get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
)){
7210 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, 1);
7212 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, 0);
7216 /* if we're zoomed and the current message was unselected */
7218 && get_lflag(state
->mail_stream
,msgmap
,mn_get_cur(msgmap
),MN_HIDE
))
7219 mn_reset_cur(msgmap
, msgno
);
7224 q_status_message4(SM_ORDER
, 3, 3,
7225 "%s. %s message%s remain%s selected.",
7227 ? "No change resulted"
7228 : "No messages in intersection",
7229 comatose(old_tot
), plural(old_tot
),
7230 (old_tot
== 1L) ? "s" : "");
7232 q_status_message(SM_ORDER
, 3, 3,
7233 _("No change resulted. Matching messages already selected."));
7235 q_status_message1(SM_ORDER
| SM_DING
, 3, 3,
7236 _("Select failed. No %smessages selected."),
7237 old_tot
? _("additional ") : "");
7240 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
7241 "Select matched %ld message%s. %s %smessage%s %sselected.",
7242 (diff
> 0) ? diff
: old_tot
+ diff
,
7243 plural((diff
> 0) ? diff
: old_tot
+ diff
),
7244 comatose((diff
> 0) ? any_lflagged(msgmap
, MN_SLCT
) : -diff
),
7245 (diff
> 0) ? "total " : "",
7246 plural((diff
> 0) ? any_lflagged(msgmap
, MN_SLCT
) : -diff
),
7247 (diff
> 0) ? "" : "UN");
7248 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
7249 q_status_message(SM_ORDER
, 3, 3, tmp_20k_buf
);
7252 q_status_message2(SM_ORDER
, 3, 3, _("Select matched %s message%s!"),
7253 comatose(diff
), plural(diff
));
7259 /*----------------------------------------------------------------------
7260 Toggle the state of the current message
7262 Args: state -- pointer pine's state variables
7263 msgmap -- message collection to operate on
7264 in_index -- in the message index view
7265 Returns: TRUE if current marked selected, FALSE otw
7268 select_by_current(struct pine
*state
, MSGNO_S
*msgmap
, CmdWhere in_index
)
7271 int all_selected
= 0;
7272 unsigned long was
, tot
, rawno
;
7275 cur
= mn_get_cur(msgmap
);
7278 thrd
= fetch_thread(state
->mail_stream
, mn_m2raw(msgmap
, cur
));
7282 was
= count_lflags_in_thread(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
);
7283 tot
= count_lflags_in_thread(state
->mail_stream
, thrd
, msgmap
, MN_NONE
);
7288 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
, 0);
7289 if(any_lflagged(msgmap
, MN_HIDE
) > 0L){
7290 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_HIDE
, 1);
7292 * See if there's anything left to zoom on. If so,
7293 * pick an adjacent one for highlighting, else make
7294 * sure nothing is left hidden...
7296 if(any_lflagged(msgmap
, MN_SLCT
)){
7297 mn_inc_cur(state
->mail_stream
, msgmap
,
7298 (in_index
== View
&& THREADING()
7299 && sp_viewing_a_thread(state
->mail_stream
))
7301 : (in_index
== View
)
7302 ? MH_ANYTHD
: MH_NONE
);
7303 if(mn_get_cur(msgmap
) == cur
)
7304 mn_dec_cur(state
->mail_stream
, msgmap
,
7305 (in_index
== View
&& THREADING()
7306 && sp_viewing_a_thread(state
->mail_stream
))
7308 : (in_index
== View
)
7309 ? MH_ANYTHD
: MH_NONE
);
7311 else /* clear all hidden flags */
7312 (void) unzoom_index(state
, state
->mail_stream
, msgmap
);
7316 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
, 1);
7318 q_status_message3(SM_ORDER
, 0, 2, "%s message%s %sselected",
7319 comatose(all_selected
? was
: tot
-was
),
7320 plural(all_selected
? was
: tot
-was
),
7321 all_selected
? "UN" : "");
7323 /* collapsed thread */
7325 && ((rawno
= mn_m2raw(msgmap
, cur
)) != 0L)
7326 && ((thrd
= fetch_thread(state
->mail_stream
, rawno
)) != NULL
)
7327 && (thrd
&& thrd
->next
&& get_lflag(state
->mail_stream
, NULL
, rawno
, MN_COLL
))){
7329 * This doesn't work quite the same as the colon command works, but
7330 * it is arguably doing the correct thing. The difference is
7331 * that aggregate_select will zoom after selecting back where it
7332 * was called from, but selecting a thread with colon won't zoom.
7333 * Maybe it makes sense to zoom after a select but not after a colon
7334 * command even though they are very similar.
7336 thread_command(state
, state
->mail_stream
, msgmap
, ':', -FOOTER_ROWS(state
));
7340 get_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
)) != 0){ /* set? */
7341 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
, 0);
7342 if(any_lflagged(msgmap
, MN_HIDE
) > 0L){
7343 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_HIDE
, 1);
7345 * See if there's anything left to zoom on. If so,
7346 * pick an adjacent one for highlighting, else make
7347 * sure nothing is left hidden...
7349 if(any_lflagged(msgmap
, MN_SLCT
)){
7350 mn_inc_cur(state
->mail_stream
, msgmap
,
7351 (in_index
== View
&& THREADING()
7352 && sp_viewing_a_thread(state
->mail_stream
))
7354 : (in_index
== View
)
7355 ? MH_ANYTHD
: MH_NONE
);
7356 if(mn_get_cur(msgmap
) == cur
)
7357 mn_dec_cur(state
->mail_stream
, msgmap
,
7358 (in_index
== View
&& THREADING()
7359 && sp_viewing_a_thread(state
->mail_stream
))
7361 : (in_index
== View
)
7362 ? MH_ANYTHD
: MH_NONE
);
7364 else /* clear all hidden flags */
7365 (void) unzoom_index(state
, state
->mail_stream
, msgmap
);
7369 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
, 1);
7371 q_status_message2(SM_ORDER
, 0, 2, "Message %s %sselected",
7372 long2string(cur
), all_selected
? "UN" : "");
7376 return(!all_selected
);
7380 /*----------------------------------------------------------------------
7381 Prompt the user for the command to perform on selected messages
7383 Args: state -- pointer pine's state variables
7384 msgmap -- message collection to operate on
7385 q_line -- line on display to write prompts
7386 Returns: 1 if the selected messages are suitably commanded,
7387 0 if the choice to pick the command was declined
7391 apply_command(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
,
7392 UCS preloadkeystroke
, int flags
, int q_line
)
7394 int i
= 8, /* number of static entries in sel_opts3 */
7398 agg
= (flags
& AC_FROM_THREAD
) ? MCMD_AGG_2
: MCMD_AGG
;
7403 * To do this "right", we really ought to have access to the keymenu
7404 * here and change the typed command into a real command by running
7405 * it through menu_command. Then the switch below would be against
7406 * results from menu_command. If we did that we'd also pass the
7407 * results of menu_command in as preloadkeystroke instead of passing
7408 * the keystroke itself. But we don't have the keymenu handy,
7409 * so we have to fake it. The only complication that we run into
7410 * is that KEY_DEL is an escape sequence so we change a typed
7411 * KEY_DEL esc seq into the letter D.
7414 if(!preloadkeystroke
){
7415 if(F_ON(F_ENABLE_FLAG
,state
)){ /* flag? */
7416 sel_opts3
[i
].ch
= '*';
7417 sel_opts3
[i
].rval
= '*';
7418 sel_opts3
[i
].name
= "*";
7419 sel_opts3
[i
++].label
= N_("Flag");
7422 if(F_ON(F_ENABLE_PIPE
,state
)){ /* pipe? */
7423 sel_opts3
[i
].ch
= '|';
7424 sel_opts3
[i
].rval
= '|';
7425 sel_opts3
[i
].name
= "|";
7426 sel_opts3
[i
++].label
= N_("Pipe");
7429 if(F_ON(F_ENABLE_BOUNCE
,state
)){ /* bounce? */
7430 sel_opts3
[i
].ch
= 'b';
7431 sel_opts3
[i
].rval
= 'b';
7432 sel_opts3
[i
].name
= "B";
7433 sel_opts3
[i
++].label
= N_("Bounce");
7436 if(flags
& AC_FROM_THREAD
){
7437 if(flags
& (AC_COLL
| AC_EXPN
)){
7438 sel_opts3
[i
].ch
= '/';
7439 sel_opts3
[i
].rval
= '/';
7440 sel_opts3
[i
].name
= "/";
7441 sel_opts3
[i
++].label
= (flags
& AC_COLL
) ? N_("Collapse")
7445 sel_opts3
[i
].ch
= ';';
7446 sel_opts3
[i
].rval
= ';';
7447 sel_opts3
[i
].name
= ";";
7448 if(flags
& AC_UNSEL
)
7449 sel_opts3
[i
++].label
= N_("UnSelect");
7451 sel_opts3
[i
++].label
= N_("Select");
7454 if(F_ON(F_ENABLE_PRYNT
, state
)){ /* this one is invisible */
7455 sel_opts3
[i
].ch
= 'y';
7456 sel_opts3
[i
].rval
= '%';
7457 sel_opts3
[i
].name
= "";
7458 sel_opts3
[i
++].label
= "";
7461 if(!is_imap_stream(stream
) || LEVELUIDPLUS(stream
)){ /* expunge selected messages */
7462 sel_opts3
[i
].ch
= 'x';
7463 sel_opts3
[i
].rval
= 'x';
7464 sel_opts3
[i
].name
= "X";
7465 sel_opts3
[i
++].label
= N_("Expunge");
7468 if(nonempty_patterns(ROLE_DO_ROLES
, &pstate
) && first_pattern(&pstate
)){
7469 sel_opts3
[i
].ch
= '#';
7470 sel_opts3
[i
].rval
= '#';
7471 sel_opts3
[i
].name
= "#";
7472 sel_opts3
[i
++].label
= N_("Set Role");
7475 sel_opts3
[i
].ch
= KEY_DEL
; /* also invisible */
7476 sel_opts3
[i
].rval
= 'd';
7477 sel_opts3
[i
].name
= "";
7478 sel_opts3
[i
++].label
= "";
7480 sel_opts3
[i
].ch
= -1;
7482 snprintf(prompt
, sizeof(prompt
), "%s command : ",
7483 (flags
& AC_FROM_THREAD
) ? "THREAD" : "APPLY");
7484 prompt
[sizeof(prompt
)-1] = '\0';
7485 cmd
= double_radio_buttons(prompt
, q_line
, sel_opts3
, 'z', 'c', NO_HELP
,
7491 if(preloadkeystroke
== KEY_DEL
)
7494 if(preloadkeystroke
< 0x80 && isupper((int) preloadkeystroke
))
7495 cmd
= tolower((int) preloadkeystroke
);
7497 cmd
= (int) preloadkeystroke
; /* shouldn't happen */
7502 case 'd' : /* delete */
7503 we_cancel
= busy_cue(NULL
, NULL
, 1);
7504 rv
= cmd_delete(state
, msgmap
, agg
, NULL
); /* don't advance or offer "TAB" */
7509 case 'u' : /* undelete */
7510 we_cancel
= busy_cue(NULL
, NULL
, 1);
7511 rv
= cmd_undelete(state
, msgmap
, agg
);
7516 case 'r' : /* reply */
7517 rv
= cmd_reply(state
, msgmap
, agg
, NULL
);
7520 case 'f' : /* Forward */
7521 rv
= cmd_forward(state
, msgmap
, agg
, NULL
);
7524 case '%' : /* print */
7525 rv
= cmd_print(state
, msgmap
, agg
, MsgIndx
);
7528 case 't' : /* take address */
7529 rv
= cmd_take_addr(state
, msgmap
, agg
);
7532 case 's' : /* save */
7533 rv
= cmd_save(state
, stream
, msgmap
, agg
, MsgIndx
);
7536 case 'e' : /* export */
7537 rv
= cmd_export(state
, msgmap
, q_line
, agg
);
7540 case '|' : /* pipe */
7541 rv
= cmd_pipe(state
, msgmap
, agg
);
7544 case '*' : /* flag */
7545 we_cancel
= busy_cue(NULL
, NULL
, 1);
7546 rv
= cmd_flag(state
, msgmap
, agg
);
7551 case 'b' : /* bounce */
7552 rv
= cmd_bounce(state
, msgmap
, agg
, NULL
);
7556 collapse_or_expand(state
, stream
, msgmap
,
7557 F_ON(F_SLASH_COLL_ENTIRE
, ps_global
)
7559 : mn_get_cur(msgmap
));
7563 select_thread_stmp(state
, stream
, msgmap
);
7566 case 'x' : /* Expunge */
7567 rv
= cmd_expunge(state
, stream
, msgmap
, agg
);
7570 case 'c' : /* cancel */
7571 cmd_cancelled((flags
& AC_FROM_THREAD
) ? "Thread command"
7576 if(nonempty_patterns(ROLE_DO_ROLES
, &pstate
) && first_pattern(&pstate
)){
7577 static ESCKEY_S choose_role
[] = {
7578 {'r', 'r', "R", N_("Reply")},
7579 {'f', 'f', "F", N_("Forward")},
7580 {'b', 'b', "B", N_("Bounce")},
7584 ACTION_S
*role
= NULL
;
7586 action
= radio_buttons(_("Reply, Forward or Bounce using a role? "),
7587 -FOOTER_ROWS(state
), choose_role
,
7588 'r', 'x', h_role_aggregate
, RB_NORM
);
7589 if(action
== 'r' || action
== 'f' || action
== 'b'){
7590 void (*prev_screen
)(struct pine
*) = NULL
, (*redraw
)(void) = NULL
;
7592 redraw
= state
->redrawer
;
7593 state
->redrawer
= NULL
;
7594 prev_screen
= state
->prev_screen
;
7596 state
->next_screen
= SCREEN_FUN_NULL
;
7598 if(role_select_screen(state
, &role
,
7599 action
== 'f' ? MC_FORWARD
:
7600 action
== 'r' ? MC_REPLY
:
7601 action
== 'b' ? MC_BOUNCE
: 0) < 0){
7602 cmd_cancelled(action
== 'f' ? _("Forward") :
7603 action
== 'r' ? _("Reply") : _("Bounce"));
7604 state
->next_screen
= prev_screen
;
7605 state
->redrawer
= redraw
;
7606 state
->mangled_screen
= 1;
7610 role
= combine_inherited_role(role
);
7612 role
= (ACTION_S
*) fs_get(sizeof(*role
));
7613 memset((void *) role
, 0, sizeof(*role
));
7614 role
->nick
= cpystr("Default Role");
7617 state
->redrawer
= NULL
;
7620 (void) cmd_reply(state
, msgmap
, agg
, role
);
7624 (void) cmd_forward(state
, msgmap
, agg
, role
);
7628 (void) cmd_bounce(state
, msgmap
, agg
, role
);
7638 state
->next_screen
= prev_screen
;
7639 state
->redrawer
= redraw
;
7640 state
->mangled_screen
= 1;
7646 case 'z' : /* default */
7647 q_status_message(SM_INFO
, 0, 2,
7648 "Cancelled, there is no default command");
7660 * Select by message number ranges.
7661 * Sets searched bits in mail_elts
7663 * Args limitsrch -- limit search to this searchset
7665 * Returns 0 on success.
7668 select_by_number(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**limitsrch
)
7672 char number1
[16], number2
[16], numbers
[80], *p
, *t
;
7677 ps_global
->mangled_footer
= 1;
7680 int flags
= OE_APPEND_CURRENT
;
7682 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
7683 sizeof(numbers
), _(select_num
), NULL
, help
, &flags
);
7688 help
= (help
== NO_HELP
) ? h_select_by_num
: NO_HELP
;
7692 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
7693 if(!isspace((unsigned char)*p
))
7698 if(r
== 1 || numbers
[0] == '\0'){
7699 cmd_cancelled("Selection by number");
7706 for(n1
= 1; n1
<= stream
->nmsgs
; n1
++)
7707 if((mc
= mail_elt(stream
, n1
)) != NULL
)
7708 mc
->searched
= 0; /* clear searched bits */
7710 for(p
= numbers
; *p
; p
++){
7712 while(*p
&& isdigit((unsigned char)*p
))
7718 if(number1
[0] == '\0'){
7720 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7721 _("Invalid number range, missing number before \"-\": %s"),
7725 else if(!strucmp("end", p
)){
7729 else if(!strucmp("$", p
)){
7738 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7739 _("Invalid message number: %s"), numbers
);
7745 n1
= mn_get_total(msgmap
);
7747 n1
= mn_get_cur(msgmap
);
7748 else if((n1
= atol(number1
)) < 1L || n1
> mn_get_total(msgmap
)){
7749 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7750 _("\"%s\" out of message number range"),
7757 while(*++p
&& isdigit((unsigned char)*p
))
7763 if(number2
[0] == '\0'){
7764 if(!strucmp("end", p
)){
7768 else if(!strucmp(p
, "$")){
7777 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7778 _("Invalid number range, missing number after \"-\": %s"),
7785 n2
= mn_get_total(msgmap
);
7787 n2
= mn_get_cur(msgmap
);
7788 else if((n2
= atol(number2
)) < 1L || n2
> mn_get_total(msgmap
)){
7789 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7790 _("\"%s\" out of message number range"),
7798 strncpy(t
, long2string(n1
), sizeof(t
));
7799 t
[sizeof(t
)-1] = '\0';
7800 q_status_message2(SM_ORDER
| SM_DING
, 0, 2,
7801 _("Invalid reverse message number range: %s-%s"),
7802 t
, long2string(n2
));
7806 for(;n1
<= n2
; n1
++){
7807 raw
= mn_m2raw(msgmap
, n1
);
7809 && (!(limitsrch
&& *limitsrch
)
7810 || in_searchset(*limitsrch
, (unsigned long) raw
)))
7811 mm_searched(stream
, raw
);
7815 raw
= mn_m2raw(msgmap
, n1
);
7817 && (!(limitsrch
&& *limitsrch
)
7818 || in_searchset(*limitsrch
, (unsigned long) raw
)))
7819 mm_searched(stream
, raw
);
7831 * Select by thread number ranges.
7832 * Sets searched bits in mail_elts
7834 * Args limitsrch -- limit search to this searchset
7836 * Returns 0 on success.
7839 select_by_thrd_number(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**msgset
)
7843 char number1
[16], number2
[16], numbers
[80], *p
, *t
;
7845 PINETHRD_S
*thrd
= NULL
, *th
;
7849 ps_global
->mangled_footer
= 1;
7852 int flags
= OE_APPEND_CURRENT
;
7854 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
7855 sizeof(numbers
), _(select_num
), NULL
, help
, &flags
);
7860 help
= (help
== NO_HELP
) ? h_select_by_thrdnum
: NO_HELP
;
7864 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
7865 if(!isspace((unsigned char)*p
))
7870 if(r
== 1 || numbers
[0] == '\0'){
7871 cmd_cancelled("Selection by number");
7878 for(n1
= 1; n1
<= stream
->nmsgs
; n1
++)
7879 if((mc
= mail_elt(stream
, n1
)) != NULL
)
7880 mc
->searched
= 0; /* clear searched bits */
7882 for(p
= numbers
; *p
; p
++){
7884 while(*p
&& isdigit((unsigned char)*p
))
7890 if(number1
[0] == '\0'){
7892 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7893 _("Invalid number range, missing number before \"-\": %s"),
7897 else if(!strucmp("end", p
)){
7901 else if(!strucmp(p
, "$")){
7910 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7911 _("Invalid thread number: %s"), numbers
);
7917 n1
= msgmap
->max_thrdno
;
7919 th
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
7922 else if((n1
= atol(number1
)) < 1L || n1
> msgmap
->max_thrdno
){
7923 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7924 _("\"%s\" out of thread number range"),
7932 while(*++p
&& isdigit((unsigned char)*p
))
7938 if(number2
[0] == '\0'){
7939 if(!strucmp("end", p
)){
7943 else if(!strucmp("$", p
)){
7952 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7953 _("Invalid number range, missing number after \"-\": %s"),
7960 n2
= msgmap
->max_thrdno
;
7962 th
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
7965 else if((n2
= atol(number2
)) < 1L || n2
> msgmap
->max_thrdno
){
7966 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7967 _("\"%s\" out of thread number range"),
7975 strncpy(t
, long2string(n1
), sizeof(t
));
7976 t
[sizeof(t
)-1] = '\0';
7977 q_status_message2(SM_ORDER
| SM_DING
, 0, 2,
7978 _("Invalid reverse message number range: %s-%s"),
7979 t
, long2string(n2
));
7983 for(;n1
<= n2
; n1
++){
7984 thrd
= find_thread_by_number(stream
, msgmap
, n1
, thrd
);
7987 set_search_bit_for_thread(stream
, thrd
, msgset
);
7991 thrd
= find_thread_by_number(stream
, msgmap
, n1
, NULL
);
7994 set_search_bit_for_thread(stream
, thrd
, msgset
);
8006 * Select by message dates.
8007 * Sets searched bits in mail_elts
8009 * Args limitsrch -- limit search to this searchset
8011 * Returns 0 on success.
8014 select_by_date(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
, SEARCHSET
**limitsrch
)
8016 int r
, we_cancel
= 0, when
= 0;
8017 char date
[100], defdate
[100], prompt
[128];
8018 time_t seldate
= time(0);
8019 struct tm
*seldate_tm
;
8022 static struct _tense
{
8027 {"were ", "SENT SINCE", " (inclusive)"},
8028 {"were ", "SENT BEFORE", " (exclusive)"},
8029 {"were ", "SENT ON", "" },
8030 {"", "ARRIVED SINCE", " (inclusive)"},
8031 {"", "ARRIVED BEFORE", " (exclusive)"},
8032 {"", "ARRIVED ON", "" }
8036 ps_global
->mangled_footer
= 1;
8040 * If talking to an old server, default to SINCE instead of
8041 * SENTSINCE, which was added later.
8043 if(is_imap_stream(stream
) && !modern_imap_stream(stream
))
8047 int flags
= OE_APPEND_CURRENT
;
8049 seldate_tm
= localtime(&seldate
);
8050 snprintf(defdate
, sizeof(defdate
), "%.2d-%.4s-%.4d", seldate_tm
->tm_mday
,
8051 month_abbrev(seldate_tm
->tm_mon
+ 1),
8052 seldate_tm
->tm_year
+ 1900);
8053 defdate
[sizeof(defdate
)-1] = '\0';
8054 snprintf(prompt
,sizeof(prompt
),"Select messages which %s%s%s [%s]: ",
8055 tense
[when
].preamble
, tense
[when
].range
,
8056 tense
[when
].scope
, defdate
);
8057 prompt
[sizeof(prompt
)-1] = '\0';
8058 r
= optionally_enter(date
,-FOOTER_ROWS(ps_global
), 0, sizeof(date
),
8059 prompt
, sel_date_opt
, help
, &flags
);
8062 cmd_cancelled("Selection by date");
8066 help
= (help
== NO_HELP
) ? h_select_date
: NO_HELP
;
8077 if(stream
&& (rawno
= mn_m2raw(msgmap
, msgno
)) > 0L
8078 && rawno
<= stream
->nmsgs
8079 && (mc
= mail_elt(stream
, rawno
))){
8081 /* cache not filled in yet? */
8085 if(stream
->dtb
&& stream
->dtb
->flags
& DR_NEWS
){
8087 ulong2string(mail_uid(stream
, rawno
)),
8089 seq
[sizeof(seq
)-1] = '\0';
8090 mail_fetch_overview(stream
, seq
, NULL
);
8093 strncpy(seq
, long2string(rawno
),
8095 seq
[sizeof(seq
)-1] = '\0';
8096 mail_fetch_fast(stream
, seq
, 0L);
8100 /* mail_date returns fixed field width date */
8101 mail_date(date
, mc
);
8108 case 12 : /* set default to PREVIOUS day */
8112 case 13 : /* set default to NEXT day */
8117 when
= (when
+1) % (sizeof(tense
) / sizeof(struct _tense
));
8124 removing_leading_white_space(date
);
8125 removing_trailing_white_space(date
);
8127 strncpy(date
, defdate
, sizeof(date
));
8128 date
[sizeof(date
)-1] = '\0';
8134 if((pgm
= mail_newsearchpgm()) != NULL
){
8136 short converted_date
;
8138 if(mail_parse_date(&elt
, (unsigned char *) date
)){
8139 converted_date
= mail_shortdate(elt
.year
, elt
.month
, elt
.day
);
8143 pgm
->sentsince
= converted_date
;
8146 pgm
->sentbefore
= converted_date
;
8149 pgm
->senton
= converted_date
;
8152 pgm
->since
= converted_date
;
8155 pgm
->before
= converted_date
;
8158 pgm
->on
= converted_date
;
8162 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
8164 if(ps_global
&& ps_global
->ttyo
){
8165 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8166 ps_global
->mangled_footer
= 1;
8169 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8171 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
8176 /* we know this was freed in mail_search, let caller know */
8181 mail_free_searchpgm(&pgm
);
8182 q_status_message1(SM_ORDER
, 3, 3,
8183 _("Invalid date entered: %s"), date
);
8192 * Select by searching in message headers or body
8193 * using the x-gm-ext-1 capaility. This function
8194 * reads the input from the user and passes it to
8195 * the server directly. We need a x-gm-ext-1 variable
8196 * in the search pgm to carry this information to the
8199 * Sets searched bits in mail_elts
8201 * Args limitsrch -- limit search to this searchset
8203 * Returns 0 on success.
8206 select_by_gm_content(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
, SEARCHSET
**limitsrch
)
8208 int r
, we_cancel
= 0, rv
;
8210 char namehdr
[MAILTMPLEN
];
8214 ps_global
->mangled_footer
= 1;
8215 ekey
[0].ch
= ekey
[1].ch
= ekey
[2].ch
= ekey
[3].ch
= -1;
8217 strncpy(tmp
, sel_x_gm_ext
, sizeof(tmp
)-1);
8218 tmp
[sizeof(tmp
)-1] = '\0';
8224 int flags
= OE_APPEND_CURRENT
;
8226 r
= optionally_enter(namehdr
, -FOOTER_ROWS(ps_global
), 0,
8227 sizeof(namehdr
), tmp
, ekey
, help
, &flags
);
8233 help
= (help
== NO_HELP
) ? h_select_by_gm_content
: NO_HELP
;
8238 cmd_cancelled("Selection by content");
8242 removing_leading_white_space(namehdr
);
8244 if ((namehdr
[0] != '\0')
8245 && isspace((unsigned char) namehdr
[strlen(namehdr
) - 1]))
8246 removing_trailing_white_space(namehdr
);
8248 if (namehdr
[0] != '\0')
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 rv
= agg_text_select(stream
, msgmap
, 'g', namehdr
, 0, 0, NULL
, "utf-8", limitsrch
);
8267 * Select by searching in message headers or body.
8268 * Sets searched bits in mail_elts
8270 * Args limitsrch -- limit search to this searchset
8272 * Returns 0 on success.
8275 select_by_text(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
, SEARCHSET
**limitsrch
)
8277 int r
= '\0', ku
, type
, we_cancel
= 0, flags
, rv
, ekeyi
= 0;
8278 int not = 0, me
= 0;
8279 char sstring
[512], tmp
[128];
8280 char *p
, *sval
= NULL
;
8283 ENVELOPE
*env
= NULL
;
8285 unsigned flagsforhist
= 0;
8286 static HISTORY_S
*history
= NULL
;
8287 static char *recip
= "RECIPIENTS";
8288 static char *partic
= "PARTICIPANTS";
8289 static char *match_me
= N_("[Match_My_Addresses]");
8290 static char *dont_match_me
= N_("[Don't_Match_My_Addresses]");
8292 ps_global
->mangled_footer
= 1;
8293 ekey
[0].ch
= ekey
[1].ch
= ekey
[2].ch
= ekey
[3].ch
= -1;
8296 type
= radio_buttons(not ? _(sel_text_not
) : _(sel_text
),
8297 -FOOTER_ROWS(ps_global
), sel_text_opt
,
8298 's', 'x', NO_HELP
, RB_NORM
|RB_RET_HELP
);
8303 helper(h_select_text
, "HELP FOR SELECT BASED ON CONTENTS",
8305 ps_global
->mangled_screen
= 1;
8312 * prepare some friendly defaults...
8315 case 't' : /* address fields, offer To or From */
8320 sval
= (type
== 't') ? "TO" :
8321 (type
== 'f') ? "FROM" :
8322 (type
== 'c') ? "CC" :
8323 (type
== 'r') ? recip
: partic
;
8324 ekey
[ekeyi
].ch
= ctrl('T');
8325 ekey
[ekeyi
].name
= "^T";
8326 ekey
[ekeyi
].rval
= 10;
8327 /* TRANSLATORS: use Current To Address */
8328 ekey
[ekeyi
++].label
= N_("Cur To");
8329 ekey
[ekeyi
].ch
= ctrl('R');
8330 ekey
[ekeyi
].name
= "^R";
8331 ekey
[ekeyi
].rval
= 11;
8332 /* TRANSLATORS: use Current From Address */
8333 ekey
[ekeyi
++].label
= N_("Cur From");
8334 ekey
[ekeyi
].ch
= ctrl('W');
8335 ekey
[ekeyi
].name
= "^W";
8336 ekey
[ekeyi
].rval
= 12;
8337 /* TRANSLATORS: use Current Cc Address */
8338 ekey
[ekeyi
++].label
= N_("Cur Cc");
8339 ekey
[ekeyi
].ch
= ctrl('Y');
8340 ekey
[ekeyi
].name
= "^Y";
8341 ekey
[ekeyi
].rval
= 13;
8342 /* TRANSLATORS: Match Me means match my address */
8343 ekey
[ekeyi
++].label
= N_("Match Me");
8345 ekey
[ekeyi
].name
= "";
8346 ekey
[ekeyi
].rval
= 0;
8347 ekey
[ekeyi
++].label
= "";
8352 ekey
[ekeyi
].ch
= ctrl('X');
8353 ekey
[ekeyi
].name
= "^X";
8354 ekey
[ekeyi
].rval
= 14;
8355 /* TRANSLATORS: use Current Subject */
8356 ekey
[ekeyi
++].label
= N_("Cur Subject");
8368 strncpy(tmp
, "Name of HEADER to match : ", sizeof(tmp
)-1);
8369 tmp
[sizeof(tmp
)-1] = '\0';
8370 flags
= OE_APPEND_CURRENT
;
8376 r
= optionally_enter(namehdr
, -FOOTER_ROWS(ps_global
), 0,
8377 sizeof(namehdr
), tmp
, ekey
, NO_HELP
, &flags
);
8379 cmd_cancelled("Selection by text");
8382 removing_leading_white_space(namehdr
);
8384 while ((namehdr
[0] != '\0') && /* remove trailing ":" */
8385 (namehdr
[strlen(namehdr
) - 1] == ':'))
8386 namehdr
[strlen(namehdr
) - 1] = '\0';
8387 if ((namehdr
[0] != '\0')
8388 && isspace((unsigned char) namehdr
[strlen(namehdr
) - 1]))
8389 removing_trailing_white_space(namehdr
);
8393 if (strchr(namehdr
,' ') || strchr(namehdr
,'\t') ||
8394 strchr(namehdr
,':'))
8396 if (namehdr
[0] == '\0')
8406 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
8410 ekey
[ekeyi
].ch
= KEY_UP
;
8411 ekey
[ekeyi
].rval
= 30;
8412 ekey
[ekeyi
].name
= "";
8414 ekey
[ekeyi
++].label
= "";
8416 ekey
[ekeyi
].ch
= KEY_DOWN
;
8417 ekey
[ekeyi
].rval
= 31;
8418 ekey
[ekeyi
].name
= "";
8419 ekey
[ekeyi
++].label
= "";
8421 ekey
[ekeyi
].ch
= -1;
8425 init_hist(&history
, HISTSIZE
);
8427 if(ekey
[0].ch
> -1 && msgno
> 0L
8428 && !(env
=pine_mail_fetchstructure(stream
,mn_m2raw(msgmap
,msgno
),
8437 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8438 snprintf(tmp
, sizeof(tmp
), "String in message %s to NOT match : ", sval
);
8440 snprintf(tmp
, sizeof(tmp
), "String in message %s to match : ", sval
);
8442 if(items_in_hist(history
) > 0){
8443 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
8444 ekey
[ku
].label
= HISTORY_KEYLABEL
;
8445 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
8446 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
8450 ekey
[ku
].label
= "";
8451 ekey
[ku
+1].name
= "";
8452 ekey
[ku
+1].label
= "";
8455 flags
= OE_APPEND_CURRENT
| OE_KEEP_TRAILING_SPACE
;
8456 r
= optionally_enter(sstring
, -FOOTER_ROWS(ps_global
), 0,
8457 sizeof(sstring
), tmp
, ekey
, help
, &flags
);
8459 if(me
&& r
== 0 && ((!not && strcmp(sstring
, _(match_me
))) || (not && strcmp(sstring
, _(dont_match_me
)))))
8464 help
= (help
== NO_HELP
)
8466 ? ((type
== 'f') ? h_select_txt_not_from
8467 : (type
== 't') ? h_select_txt_not_to
8468 : (type
== 'c') ? h_select_txt_not_cc
8469 : (type
== 's') ? h_select_txt_not_subj
8470 : (type
== 'a') ? h_select_txt_not_all
8471 : (type
== 'r') ? h_select_txt_not_recip
8472 : (type
== 'p') ? h_select_txt_not_partic
8473 : (type
== 'b') ? h_select_txt_not_body
8475 : ((type
== 'f') ? h_select_txt_from
8476 : (type
== 't') ? h_select_txt_to
8477 : (type
== 'c') ? h_select_txt_cc
8478 : (type
== 's') ? h_select_txt_subj
8479 : (type
== 'a') ? h_select_txt_all
8480 : (type
== 'r') ? h_select_txt_recip
8481 : (type
== 'p') ? h_select_txt_partic
8482 : (type
== 'b') ? h_select_txt_body
8489 case 10 : /* To: default */
8490 if(env
&& env
->to
&& env
->to
->mailbox
){
8491 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->to
->mailbox
,
8492 env
->to
->host
? "@" : "",
8493 env
->to
->host
? env
->to
->host
: "");
8494 sstring
[sizeof(sstring
)-1] = '\0';
8498 case 11 : /* From: default */
8499 if(env
&& env
->from
&& env
->from
->mailbox
){
8500 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->from
->mailbox
,
8501 env
->from
->host
? "@" : "",
8502 env
->from
->host
? env
->from
->host
: "");
8503 sstring
[sizeof(sstring
)-1] = '\0';
8507 case 12 : /* Cc: default */
8508 if(env
&& env
->cc
&& env
->cc
->mailbox
){
8509 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->cc
->mailbox
,
8510 env
->cc
->host
? "@" : "",
8511 env
->cc
->host
? env
->cc
->host
: "");
8512 sstring
[sizeof(sstring
)-1] = '\0';
8516 case 13 : /* Match my addresses */
8518 snprintf(sstring
, sizeof(sstring
), "%s", not ? _(dont_match_me
) : _(match_me
));
8521 case 14 : /* Subject: default */
8522 if(env
&& env
->subject
&& env
->subject
[0]){
8525 q
= (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf
,
8526 SIZEOF_20KBUF
, env
->subject
);
8527 snprintf(sstring
, sizeof(sstring
), "%s", q
);
8528 sstring
[sizeof(sstring
)-1] = '\0';
8534 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8535 if((p
= get_prev_hist(history
, sstring
, flagsforhist
, NULL
)) != NULL
){
8536 strncpy(sstring
, p
, sizeof(sstring
));
8537 sstring
[sizeof(sstring
)-1] = '\0';
8538 if(history
->hist
[history
->curindex
]){
8539 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
8540 not = (flagsforhist
& 0x1) ? 1 : 0;
8541 me
= (flagsforhist
& 0x2) ? 1 : 0;
8550 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8551 if((p
= get_next_hist(history
, sstring
, flagsforhist
, NULL
)) != NULL
){
8552 strncpy(sstring
, p
, sizeof(sstring
));
8553 sstring
[sizeof(sstring
)-1] = '\0';
8554 if(history
->hist
[history
->curindex
]){
8555 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
8556 not = (flagsforhist
& 0x1) ? 1 : 0;
8557 me
= (flagsforhist
& 0x2) ? 1 : 0;
8569 if(r
== 1 || sstring
[0] == '\0')
8576 if(type
== 'x' || r
== 'x'){
8577 cmd_cancelled("Selection by text");
8581 if(ps_global
&& ps_global
->ttyo
){
8582 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8583 ps_global
->mangled_footer
= 1;
8586 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8588 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8589 save_hist(history
, sstring
, flagsforhist
, NULL
);
8591 rv
= agg_text_select(stream
, msgmap
, type
, namehdr
, not, me
, sstring
, "utf-8", limitsrch
);
8600 * Select by message size.
8601 * Sets searched bits in mail_elts
8603 * Args limitsrch -- limit search to this searchset
8605 * Returns 0 on success.
8608 select_by_size(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8610 int r
, large
= 1, we_cancel
= 0;
8611 unsigned long n
, mult
= 1L, numerator
= 0L, divisor
= 1L;
8612 char size
[16], numbers
[80], *p
, *t
;
8615 long flags
= (SE_NOPREFETCH
| SE_FREE
);
8618 ps_global
->mangled_footer
= 1;
8622 int flgs
= OE_APPEND_CURRENT
;
8624 sel_size_opt
[1].label
= large
? sel_size_smaller
: sel_size_larger
;
8626 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
8627 sizeof(numbers
), large
? _(select_size_larger_msg
)
8628 : _(select_size_smaller_msg
),
8629 sel_size_opt
, help
, &flgs
);
8639 help
= (help
== NO_HELP
) ? (large
? h_select_by_larger_size
8640 : h_select_by_smaller_size
)
8645 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
8646 if(!isspace((unsigned char)*p
))
8651 if(r
== 1 || numbers
[0] == '\0'){
8652 cmd_cancelled("Selection by size");
8659 if(numbers
[0] == '-'){
8660 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
8661 _("Invalid size entered: %s"), numbers
);
8668 while(*p
&& isdigit((unsigned char)*p
))
8673 if(size
[0] == '\0' && *p
== '.' && isdigit(*(p
+1))){
8678 if(size
[0] == '\0'){
8679 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
8680 _("Invalid size entered: %s"), numbers
);
8684 n
= strtoul(size
, (char **)NULL
, 10);
8689 * We probably ought to just use atof() to convert 1.1 into a
8690 * double, but since we haven't used atof() anywhere else I'm
8691 * reluctant to use it because of portability concerns.
8695 while(*p
&& isdigit((unsigned char)*p
)){
8703 numerator
= strtoul(size
, (char **)NULL
, 10);
8723 n
= n
* mult
+ (numerator
* mult
) / divisor
;
8725 pgm
= mail_newsearchpgm();
8731 if(is_imap_stream(stream
) && !modern_imap_stream(stream
))
8732 flags
|= SE_NOSERVER
;
8734 if(ps_global
&& ps_global
->ttyo
){
8735 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8736 ps_global
->mangled_footer
= 1;
8739 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8741 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
8742 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
8743 /* we know this was freed in mail_search, let caller know */
8755 * visible_searchset -- return c-client search set unEXLDed
8759 visible_searchset(MAILSTREAM
*stream
, MSGNO_S
*msgmap
)
8762 SEARCHSET
*full_set
= NULL
, **set
;
8765 * If we're talking to anything other than a server older than
8766 * imap 4rev1, build a searchset otherwise it'll choke.
8768 if(!(is_imap_stream(stream
) && !modern_imap_stream(stream
))){
8769 if(any_lflagged(msgmap
, MN_EXLD
)){
8770 for(n
= 1L, set
= &full_set
, run
= 0L; n
<= stream
->nmsgs
; n
++)
8771 if(get_lflag(stream
, NULL
, n
, MN_EXLD
)){
8772 if(run
){ /* previous NOT excluded? */
8774 (*set
)->last
= n
- 1L;
8776 set
= &(*set
)->next
;
8780 else if(run
++){ /* next in run */
8783 else{ /* start of run */
8784 *set
= mail_newsearchset();
8789 full_set
= mail_newsearchset();
8790 full_set
->first
= 1L;
8791 full_set
->last
= stream
->nmsgs
;
8800 * Select by message status bits.
8801 * Sets searched bits in mail_elts
8803 * Args limitsrch -- limit search to this searchset
8805 * Returns 0 on success.
8808 select_by_status(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8810 int s
, not = 0, we_cancel
= 0, rv
;
8813 s
= radio_buttons((not) ? _(sel_flag_not
) : _(sel_flag
),
8814 -FOOTER_ROWS(ps_global
), sel_flag_opt
, '*', 'x',
8815 NO_HELP
, RB_NORM
|RB_RET_HELP
);
8818 cmd_cancelled("Selection by status");
8822 helper(h_select_status
, _("HELP FOR SELECT BASED ON STATUS"),
8824 ps_global
->mangled_screen
= 1;
8832 if(ps_global
&& ps_global
->ttyo
){
8833 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8834 ps_global
->mangled_footer
= 1;
8837 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8838 rv
= agg_flag_select(stream
, not, s
, limitsrch
);
8847 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8848 * Sets searched bits in mail_elts
8850 * Args limitsrch -- limit search to this searchset
8852 * Returns 0 on success.
8855 select_by_rule(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8857 char rulenick
[1000], *nick
;
8859 int r
, not = 0, we_cancel
= 0, rflags
= ROLE_DO_SRCH
8867 ps_global
->mangled_footer
= 1;
8872 oe_flags
= OE_APPEND_CURRENT
;
8873 r
= optionally_enter(rulenick
, -FOOTER_ROWS(ps_global
), 0,
8875 not ? _("Rule to NOT match: ")
8876 : _("Rule to match: "),
8877 sel_key_opt
, NO_HELP
, &oe_flags
);
8880 /* select rulenick from a list */
8881 if((nick
=choose_a_rule(rflags
)) != NULL
){
8882 strncpy(rulenick
, nick
, sizeof(rulenick
)-1);
8883 rulenick
[sizeof(rulenick
)-1] = '\0';
8884 fs_give((void **) &nick
);
8893 helper(h_select_rule
, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE
);
8894 ps_global
->mangled_screen
= 1;
8897 cmd_cancelled("Selection by Rule");
8901 removing_leading_and_trailing_white_space(rulenick
);
8903 }while(r
== 3 || r
== 4 || r
== '!');
8907 * The approach of requiring a nickname instead of just allowing the
8908 * user to select from the list of rules has the drawback that a rule
8909 * may not have a nickname, or there may be more than one rule with
8910 * the same nickname. However, it has the benefit of allowing the user
8911 * to type in the nickname and, most importantly, allows us to set
8912 * up the ! (not). We could incorporate the ! into the selection
8913 * screen, but this is easier and also allows the typing of nicks.
8914 * User can just set up nicknames if they want to use this feature.
8916 patgrp
= nick_to_patgrp(rulenick
, rflags
);
8919 if(ps_global
&& ps_global
->ttyo
){
8920 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8921 ps_global
->mangled_footer
= 1;
8924 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8925 match_pattern(patgrp
, stream
, limitsrch
? *limitsrch
: 0, NULL
,
8927 (not ? MP_NOT
: 0) | SE_NOPREFETCH
);
8928 free_patgrp(&patgrp
);
8933 if(limitsrch
&& *limitsrch
){
8934 mail_free_searchset(limitsrch
);
8943 * Allow user to choose a rule from their list of rules.
8945 * Returns an allocated rule nickname on success, NULL otherwise.
8948 choose_a_rule(int rflags
)
8950 char *choice
= NULL
;
8951 char **rule_list
, **lp
;
8956 if(!(nonempty_patterns(rflags
, &pstate
) && first_pattern(&pstate
))){
8957 q_status_message(SM_ORDER
, 3, 3,
8958 _("No rules available. Use Setup/Rules to add some."));
8963 * Build a list of rules to choose from.
8966 for(pat
= first_pattern(&pstate
); pat
; pat
= next_pattern(&pstate
))
8970 q_status_message(SM_ORDER
, 3, 4, _("No rules defined, use Setup/Rules"));
8974 lp
= rule_list
= (char **) fs_get((cnt
+ 1) * sizeof(*rule_list
));
8975 memset(rule_list
, 0, (cnt
+1) * sizeof(*rule_list
));
8977 for(pat
= first_pattern(&pstate
); pat
; pat
= next_pattern(&pstate
))
8978 *lp
++ = cpystr((pat
->patgrp
&& pat
->patgrp
->nick
)
8979 ? pat
->patgrp
->nick
: "?");
8981 /* TRANSLATORS: SELECT A RULE is a screen title
8982 TRANSLATORS: Print something1 using something2.
8983 "rules" is something1 */
8984 choice
= choose_item_from_list(rule_list
, NULL
, _("SELECT A RULE"),
8985 _("rules"), h_select_rule_screen
,
8986 _("HELP FOR SELECTING A RULE NICKNAME"), NULL
);
8989 q_status_message(SM_ORDER
, 1, 4, "No choice");
8991 free_list_array(&rule_list
);
8998 * Select by current thread.
8999 * Sets searched bits in mail_elts for this entire thread
9001 * Args limitsrch -- limit search to this searchset
9003 * Returns 0 on success.
9006 select_by_thread(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**limitsrch
)
9009 PINETHRD_S
*thrd
= NULL
;
9016 for(n
= 1L; n
<= stream
->nmsgs
; n
++)
9017 if((mc
= mail_elt(stream
, n
)) != NULL
)
9018 mc
->searched
= 0; /* clear searched bits */
9020 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
9021 if(thrd
&& thrd
->top
&& thrd
->top
!= thrd
->rawno
)
9022 thrd
= fetch_thread(stream
, thrd
->top
);
9025 * This doesn't unselect if the thread is already selected
9026 * (like select current does), it always selects.
9027 * There is no way to select ! this thread.
9030 set_search_bit_for_thread(stream
, thrd
, limitsrch
);
9039 * Select by message keywords.
9040 * Sets searched bits in mail_elts
9042 * Args limitsrch -- limit search to this searchset
9044 * Returns 0 on success.
9047 select_by_keyword(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
9049 int r
, not = 0, we_cancel
= 0;
9050 char keyword
[MAXUSERFLAG
+1], *kword
;
9051 char *error
= NULL
, *p
, *prompt
;
9056 ps_global
->mangled_footer
= 1;
9063 q_status_message(SM_ORDER
, 3, 4, error
);
9064 fs_give((void **) &error
);
9067 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
) && ps_global
->keywords
){
9069 prompt
= _("Keyword (or keyword initial) to NOT match: ");
9071 prompt
= _("Keyword (or keyword initial) to match: ");
9075 prompt
= _("Keyword to NOT match: ");
9077 prompt
= _("Keyword to match: ");
9080 oe_flags
= OE_APPEND_CURRENT
;
9081 r
= optionally_enter(keyword
, -FOOTER_ROWS(ps_global
), 0,
9083 prompt
, sel_key_opt
, help
, &oe_flags
);
9086 /* select keyword from a list */
9087 if((kword
=choose_a_keyword()) != NULL
){
9088 strncpy(keyword
, kword
, sizeof(keyword
)-1);
9089 keyword
[sizeof(keyword
)-1] = '\0';
9090 fs_give((void **) &kword
);
9099 help
= help
== NO_HELP
? h_select_keyword
: NO_HELP
;
9101 cmd_cancelled("Selection by keyword");
9105 removing_leading_and_trailing_white_space(keyword
);
9107 }while(r
== 3 || r
== 4 || r
== '!' || keyword_check(keyword
, &error
));
9110 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
) && ps_global
->keywords
){
9111 p
= initial_to_keyword(keyword
);
9113 strncpy(keyword
, p
, sizeof(keyword
)-1);
9114 keyword
[sizeof(keyword
)-1] = '\0';
9119 * We want to check the keyword, not the nickname of the keyword,
9120 * so convert it to the keyword if necessary.
9122 p
= nick_to_keyword(keyword
);
9124 strncpy(keyword
, p
, sizeof(keyword
)-1);
9125 keyword
[sizeof(keyword
)-1] = '\0';
9128 pgm
= mail_newsearchpgm();
9130 pgm
->unkeyword
= mail_newstringlist();
9131 pgm
->unkeyword
->text
.data
= (unsigned char *) cpystr(keyword
);
9132 pgm
->unkeyword
->text
.size
= strlen(keyword
);
9135 pgm
->keyword
= mail_newstringlist();
9136 pgm
->keyword
->text
.data
= (unsigned char *) cpystr(keyword
);
9137 pgm
->keyword
->text
.size
= strlen(keyword
);
9140 if(ps_global
&& ps_global
->ttyo
){
9141 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
9142 ps_global
->mangled_footer
= 1;
9145 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
9147 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
9148 pine_mail_search_full(stream
, "UTF-8", pgm
, SE_NOPREFETCH
| SE_FREE
);
9149 /* we know this was freed in mail_search, let caller know */
9161 * Allow user to choose a keyword from their list of keywords.
9163 * Returns an allocated keyword on success, NULL otherwise.
9166 choose_a_keyword(void)
9168 char *choice
= NULL
;
9169 char **keyword_list
, **lp
;
9174 * Build a list of keywords to choose from.
9177 for(cnt
= 0, kw
= ps_global
->keywords
; kw
; kw
= kw
->next
)
9181 q_status_message(SM_ORDER
, 3, 4,
9182 _("No keywords defined, use \"keywords\" option in Setup/Config"));
9186 lp
= keyword_list
= (char **) fs_get((cnt
+ 1) * sizeof(*keyword_list
));
9187 memset(keyword_list
, 0, (cnt
+1) * sizeof(*keyword_list
));
9189 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
)
9190 *lp
++ = cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
9192 /* TRANSLATORS: SELECT A KEYWORD is a screen title
9193 TRANSLATORS: Print something1 using something2.
9194 "keywords" is something1 */
9195 choice
= choose_item_from_list(keyword_list
, NULL
, _("SELECT A KEYWORD"),
9196 _("keywords"), h_select_keyword_screen
,
9197 _("HELP FOR SELECTING A KEYWORD"), NULL
);
9200 q_status_message(SM_ORDER
, 1, 4, "No choice");
9202 free_list_array(&keyword_list
);
9209 * Allow user to choose a list of keywords from their list of keywords.
9211 * Returns allocated list.
9214 choose_list_of_keywords(void)
9216 LIST_SEL_S
*listhead
, *ls
, *p
;
9222 * Build a list of keywords to choose from.
9225 p
= listhead
= NULL
;
9226 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
9228 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9229 memset(ls
, 0, sizeof(*ls
));
9230 ls
->item
= cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
9243 /* TRANSLATORS: SELECT KEYWORDS is a screen title
9244 Print something1 using something2.
9245 "keywords" is something1 */
9246 if(!select_from_list_screen(listhead
, SFL_ALLOW_LISTMODE
,
9247 _("SELECT KEYWORDS"), _("keywords"),
9248 h_select_multkeyword_screen
,
9249 _("HELP FOR SELECTING KEYWORDS"), NULL
)){
9250 for(cnt
= 0, p
= listhead
; p
; p
= p
->next
)
9254 ret
= (char **) fs_get((cnt
+1) * sizeof(*ret
));
9255 memset(ret
, 0, (cnt
+1) * sizeof(*ret
));
9256 for(i
= 0, p
= listhead
; p
; p
= p
->next
)
9258 ret
[i
++] = cpystr(p
->item
? p
->item
: "");
9261 free_list_sel(&listhead
);
9268 * Allow user to choose a charset
9270 * Returns an allocated charset on success, NULL otherwise.
9273 choose_a_charset(int which_charsets
)
9275 char *choice
= NULL
;
9276 char **charset_list
, **lp
;
9281 * Build a list of charsets to choose from.
9284 for(cnt
= 0, cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
9285 if(!(cs
->flags
& (CF_UNSUPRT
|CF_NOEMAIL
))
9286 && ((which_charsets
& CAC_ALL
)
9287 || (which_charsets
& CAC_POSTING
9288 && cs
->flags
& CF_POSTING
)
9289 || (which_charsets
& CAC_DISPLAY
9290 && cs
->type
!= CT_2022
9291 && (cs
->flags
& (CF_PRIMARY
|CF_DISPLAY
)) == (CF_PRIMARY
|CF_DISPLAY
))))
9296 q_status_message(SM_ORDER
, 3, 4,
9297 _("No charsets found? Enter charset manually."));
9301 lp
= charset_list
= (char **) fs_get((cnt
+ 1) * sizeof(*charset_list
));
9302 memset(charset_list
, 0, (cnt
+1) * sizeof(*charset_list
));
9304 for(cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
9305 if(!(cs
->flags
& (CF_UNSUPRT
|CF_NOEMAIL
))
9306 && ((which_charsets
& CAC_ALL
)
9307 || (which_charsets
& CAC_POSTING
9308 && cs
->flags
& CF_POSTING
)
9309 || (which_charsets
& CAC_DISPLAY
9310 && cs
->type
!= CT_2022
9311 && (cs
->flags
& (CF_PRIMARY
|CF_DISPLAY
)) == (CF_PRIMARY
|CF_DISPLAY
))))
9312 *lp
++ = cpystr(cs
->name
);
9315 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
9316 TRANSLATORS: Print something1 using something2.
9317 "character sets" is something1 */
9318 choice
= choose_item_from_list(charset_list
, NULL
, _("SELECT A CHARACTER SET"),
9319 _("character sets"), h_select_charset_screen
,
9320 _("HELP FOR SELECTING A CHARACTER SET"), NULL
);
9323 q_status_message(SM_ORDER
, 1, 4, "No choice");
9325 free_list_array(&charset_list
);
9332 * Allow user to choose a list of character sets and/or scripts
9334 * Returns allocated list.
9337 choose_list_of_charsets(void)
9339 LIST_SEL_S
*listhead
, *ls
, *p
;
9341 int cnt
, i
, got_one
;
9346 char buf
[1024], *folded
;
9349 * Build a list of charsets to choose from.
9352 p
= listhead
= NULL
;
9354 /* this width is determined by select_from_list_screen() */
9355 width
= ps_global
->ttyo
->screen_cols
- 4;
9357 /* first comes a list of scripts (sets of character sets) */
9358 for(s
= utf8_script(NIL
); s
&& s
->name
; s
++){
9360 limit
= sizeof(buf
)-1;
9362 memset(q
, 0, limit
+1);
9365 sstrncpy(&q
, s
->name
, limit
);
9368 sstrncpy(&q
, " (", limit
-(q
-buf
));
9369 sstrncpy(&q
, s
->description
, limit
-(q
-buf
));
9370 sstrncpy(&q
, ")", limit
-(q
-buf
));
9373 /* add the list of charsets that are in this script */
9375 for(cs
= utf8_charset(NIL
);
9376 cs
&& cs
->name
&& (q
-buf
) < limit
; cs
++){
9377 if(cs
->script
& s
->script
){
9379 * Filter out some un-useful members of the list.
9380 * UTF-7 and UTF-8 weren't actually in the list at the
9381 * time this was written. Just making sure.
9383 if(!strucmp(cs
->name
, "ISO-2022-JP-2")
9384 || !strucmp(cs
->name
, "UTF-7")
9385 || !strucmp(cs
->name
, "UTF-8"))
9389 sstrncpy(&q
, " ", limit
-(q
-buf
));
9392 sstrncpy(&q
, " {", limit
-(q
-buf
));
9395 sstrncpy(&q
, cs
->name
, limit
-(q
-buf
));
9400 sstrncpy(&q
, "}", limit
-(q
-buf
));
9402 /* fold this line so that it can all be seen on the screen */
9403 folded
= fold(buf
, width
, width
, "", " ", FLD_NONE
);
9406 while(t
&& *t
&& (q
= strindex(t
, '\n')) != NULL
){
9409 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9410 memset(ls
, 0, sizeof(*ls
));
9412 ls
->item
= cpystr(s
->name
);
9414 ls
->flags
= SFL_NOSELECT
;
9416 ls
->display_item
= cpystr(t
);
9426 listhead
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9427 memset(listhead
, 0, sizeof(*listhead
));
9428 listhead
->flags
= SFL_NOSELECT
;
9429 listhead
->display_item
=
9430 cpystr(_("Scripts representing groups of related character sets"));
9431 listhead
->next
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9432 memset(listhead
->next
, 0, sizeof(*listhead
));
9433 listhead
->next
->flags
= SFL_NOSELECT
;
9434 listhead
->next
->display_item
=
9435 cpystr(repeat_char(width
, '-'));
9437 listhead
->next
->next
= ls
;
9442 fs_give((void **) &folded
);
9446 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9447 memset(ls
, 0, sizeof(*ls
));
9448 ls
->flags
= SFL_NOSELECT
;
9456 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9457 memset(ls
, 0, sizeof(*ls
));
9458 ls
->flags
= SFL_NOSELECT
;
9460 cpystr(_("Individual character sets, may be mixed with scripts"));
9464 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9465 memset(ls
, 0, sizeof(*ls
));
9466 ls
->flags
= SFL_NOSELECT
;
9468 cpystr(repeat_char(width
, '-'));
9472 /* then comes a list of individual character sets */
9473 for(cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
9474 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9475 memset(ls
, 0, sizeof(*ls
));
9476 ls
->item
= cpystr(cs
->name
);
9489 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9490 Print something1 using something2.
9491 "character sets" is something1 */
9492 if(!select_from_list_screen(listhead
, SFL_ALLOW_LISTMODE
,
9493 _("SELECT CHARACTER SETS"), _("character sets"),
9494 h_select_multcharsets_screen
,
9495 _("HELP FOR SELECTING CHARACTER SETS"), NULL
)){
9496 for(cnt
= 0, p
= listhead
; p
; p
= p
->next
)
9500 ret
= (char **) fs_get((cnt
+1) * sizeof(*ret
));
9501 memset(ret
, 0, (cnt
+1) * sizeof(*ret
));
9502 for(i
= 0, p
= listhead
; p
; p
= p
->next
)
9504 ret
[i
++] = cpystr(p
->item
? p
->item
: "");
9507 free_list_sel(&listhead
);
9512 /* Report quota summary resources in an IMAP server */
9515 cmd_quota (struct pine
*state
)
9517 QUOTALIST
*imapquota
;
9522 if(!state
->mail_stream
|| !is_imap_stream(state
->mail_stream
)){
9523 q_status_message(SM_ORDER
, 1, 5, "Quota only available for IMAP folders");
9527 if (state
->mail_stream
9528 && !sp_dead_stream(state
->mail_stream
)
9529 && state
->mail_stream
->mailbox
9530 && *state
->mail_stream
->mailbox
9531 && mail_valid_net_parse(state
->mail_stream
->mailbox
, &mb
))
9532 imap_getquotaroot(state
->mail_stream
, mb
.mailbox
);
9534 if(!state
->quota
) /* failed ? */
9535 return; /* go back... */
9537 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
9538 q_status_message(SM_ORDER
| SM_DING
, 3, 3, "Error allocating space.");
9542 so_puts(store
, "Quota Report for ");
9543 so_puts(store
, state
->mail_stream
->original_mailbox
);
9544 so_puts(store
, "\n\n");
9546 for (imapquota
= state
->quota
; imapquota
; imapquota
= imapquota
->next
){
9548 so_puts(store
, _("Resource : "));
9549 so_puts(store
, imapquota
->name
);
9550 so_writec('\n', store
);
9552 so_puts(store
, _("Usage : "));
9553 so_puts(store
, long2string(imapquota
->usage
));
9554 if(!strucmp(imapquota
->name
,"STORAGE"))
9555 so_puts(store
, " KiB ");
9556 if(!strucmp(imapquota
->name
,"MESSAGE")){
9557 so_puts(store
, _(" message"));
9558 if(imapquota
->usage
!= 1)
9559 so_puts(store
, _("s ")); /* plural */
9561 so_puts(store
, _(" "));
9563 so_writec('(', store
);
9564 so_puts(store
, long2string(100*imapquota
->usage
/imapquota
->limit
));
9565 so_puts(store
, "%)\n");
9567 so_puts(store
, _("Limit : "));
9568 so_puts(store
, long2string(imapquota
->limit
));
9569 if(!strucmp(imapquota
->name
,"STORAGE"))
9570 so_puts(store
, " KiB\n\n");
9571 if(!strucmp(imapquota
->name
,"MESSAGE")){
9572 so_puts(store
, _(" message"));
9573 if(imapquota
->usage
!= 1)
9574 so_puts(store
, _("s\n\n")); /* plural */
9576 so_puts(store
, _("\n\n"));
9580 memset(&sargs
, 0, sizeof(SCROLL_S
));
9581 sargs
.text
.text
= so_text(store
);
9582 sargs
.text
.src
= CharStar
;
9583 sargs
.text
.desc
= _("Quota Resources Summary");
9584 sargs
.bar
.title
= _("QUOTA SUMMARY");
9585 sargs
.proc
.tool
= NULL
;
9586 sargs
.help
.text
= h_quota_command
;
9587 sargs
.help
.title
= NULL
;
9588 sargs
.keys
.menu
= NULL
;
9589 setbitmap(sargs
.keys
.bitmap
);
9595 mail_free_quotalist(&(state
->quota
));
9598 /*----------------------------------------------------------------------
9599 Prompt the user for the type of sort he desires
9601 Args: state -- pine state pointer
9602 q1 -- Line to prompt on
9604 Returns 0 if it was cancelled, 1 otherwise.
9607 select_sort(struct pine
*state
, int ql
, SortOrder
*sort
, int *rev
)
9609 char prompt
[200], tmp
[3], *p
;
9611 int deefault
= 'a', retval
= 1;
9616 DLG_SORTPARAM sortsel
;
9618 if (mswin_usedialog ()) {
9620 sortsel
.reverse
= mn_get_revsort (state
->msgmap
);
9621 sortsel
.cursort
= mn_get_sort (state
->msgmap
);
9622 /* assumption here that HelpType is char ** */
9623 sortsel
.helptext
= h_select_sort
;
9626 if ((retval
= os_sortdialog (&sortsel
))) {
9627 *sort
= sortsel
.cursort
;
9628 *rev
= sortsel
.reverse
;
9635 /*----- String together the prompt ------*/
9637 if(F_ON(F_USE_FK
,ps_global
))
9638 strncpy(prompt
, _("Choose type of sort : "), sizeof(prompt
));
9640 strncpy(prompt
, _("Choose type of sort, or 'R' to reverse current sort : "),
9643 for(i
= 0; state
->sort_types
[i
] != EndofList
; i
++) {
9645 p
= sorts
[i
].label
= sort_name(state
->sort_types
[i
]);
9646 while(*(p
+1) && islower((unsigned char)*p
))
9649 sorts
[i
].ch
= tolower((unsigned char)(tmp
[0] = *p
));
9650 sorts
[i
].name
= cpystr(tmp
);
9652 if(mn_get_sort(state
->msgmap
) == state
->sort_types
[i
])
9653 deefault
= sorts
[i
].rval
;
9657 sorts
[i
].rval
= 'r';
9658 sorts
[i
].name
= cpystr("R");
9659 if(F_ON(F_USE_FK
,ps_global
))
9660 sorts
[i
].label
= N_("Reverse");
9662 sorts
[i
].label
= "";
9665 help
= h_select_sort
;
9667 if((F_ON(F_USE_FK
,ps_global
)
9668 && ((s
= double_radio_buttons(prompt
,ql
,sorts
,deefault
,'x',
9669 help
,RB_NORM
)) != 'x'))
9671 (F_OFF(F_USE_FK
,ps_global
)
9672 && ((s
= radio_buttons(prompt
,ql
,sorts
,deefault
,'x',
9673 help
,RB_NORM
)) != 'x'))){
9674 state
->mangled_body
= 1; /* signal screen's changed */
9676 *rev
= !mn_get_revsort(state
->msgmap
);
9678 *sort
= state
->sort_types
[s
];
9680 if(F_ON(F_SHOW_SORT
, ps_global
))
9681 ps_global
->mangled_header
= 1;
9685 cmd_cancelled("Sort");
9689 fs_give((void **)&sorts
[i
].name
);
9691 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
9696 /*---------------------------------------------------------------------
9697 Build list of folders in the given context for user selection
9699 Args: c -- pointer to pointer to folder's context context
9700 f -- folder prefix to display
9701 sublist -- whether or not to use 'f's contents as prefix
9702 lister -- function used to do the actual display
9704 Returns: malloc'd string containing sequence, else NULL if
9705 no messages in msgmap with local "selected" flag.
9708 display_folder_list(CONTEXT_S
**c
, char *f
, int sublist
, int (*lister
) (struct pine
*, CONTEXT_S
**, char *, int))
9712 void (*redraw
)(void) = ps_global
->redrawer
;
9714 push_titlebar_state();
9716 if((rc
= (*lister
)(ps_global
, &tc
, f
, sublist
)) != 0)
9720 pop_titlebar_state();
9722 if((ps_global
->redrawer
= redraw
) != NULL
) /* reset old value, and test */
9723 (*ps_global
->redrawer
)();
9725 if(rc
== 1 && F_ON(F_SELECT_WO_CONFIRM
, ps_global
))
9733 * Allow user to choose a single item from a list of strings.
9735 * Args list -- Array of strings to choose from, NULL terminated.
9736 * displist -- Array of strings to display instead of displaying list.
9737 * Indices correspond to the list array. Display the displist
9738 * but return the item from list if displist non-NULL.
9739 * title -- For conf_scroll_screen
9740 * pdesc -- For conf_scroll_screen
9741 * help -- For conf_scroll_screen
9742 * htitle -- For conf_scroll_screen
9744 * Returns an allocated copy of the chosen item or NULL.
9747 choose_item_from_list(char **list
, char **displist
, char *title
, char *pdesc
, HelpType help
,
9748 char *htitle
, char *cursor_location
)
9750 LIST_SEL_S
*listhead
, *ls
, *p
, *starting_val
= NULL
;
9752 char *ret
= NULL
, *choice
= NULL
;
9754 /* build the LIST_SEL_S list */
9755 p
= listhead
= NULL
;
9756 for(t
= list
, dl
= displist
; *t
; t
++, dl
++){
9757 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9758 memset(ls
, 0, sizeof(*ls
));
9759 ls
->item
= cpystr(*t
);
9761 ls
->display_item
= cpystr(*dl
);
9763 if(cursor_location
&& (cursor_location
== (*t
)))
9777 if(!select_from_list_screen(listhead
, SFL_NONE
, title
, pdesc
,
9778 help
, htitle
, starting_val
))
9779 for(p
= listhead
; !choice
&& p
; p
= p
->next
)
9784 ret
= cpystr(choice
);
9786 free_list_sel(&listhead
);
9793 free_list_sel(LIST_SEL_S
**lsel
)
9796 free_list_sel(&(*lsel
)->next
);
9798 fs_give((void **) &(*lsel
)->item
);
9800 if((*lsel
)->display_item
)
9801 fs_give((void **) &(*lsel
)->display_item
);
9803 fs_give((void **) lsel
);
9809 * file_lister - call pico library's file lister
9812 file_lister(char *title
, char *path
, size_t pathlen
, char *file
, size_t filelen
, int newmail
, int flags
)
9816 void (*redraw
)(void) = ps_global
->redrawer
;
9818 standard_picobuf_setup(&pbf
);
9819 push_titlebar_state();
9823 /* BUG: what about help command and text? */
9824 pbf
.pine_anchor
= title
;
9826 rv
= pico_file_browse(&pbf
, path
, pathlen
, file
, filelen
, NULL
, 0, flags
);
9827 standard_picobuf_teardown(&pbf
);
9828 fix_windsize(ps_global
);
9829 init_signals(); /* has it's own signal stuff */
9831 /* Restore display's titlebar and body */
9832 pop_titlebar_state();
9834 if((ps_global
->redrawer
= redraw
) != NULL
)
9835 (*ps_global
->redrawer
)();
9841 /*----------------------------------------------------------------------
9842 Print current folder index
9846 print_index(struct pine
*state
, MSGNO_S
*msgmap
, int agg
)
9850 char buf
[MAX_SCREEN_COLS
+1];
9852 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++){
9853 if(agg
&& !get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
))
9856 if(!agg
&& msgline_hidden(state
->mail_stream
, msgmap
, i
, 0))
9859 ice
= build_header_line(state
, state
->mail_stream
, msgmap
, i
, NULL
);
9863 * I don't understand why we'd want to mark the current message
9864 * instead of printing out the first character of the status
9865 * so I'm taking it out and including the first character of the
9866 * line instead. Hubert 2006-02-09
9868 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
9872 if(!gf_puts(simple_index_line(buf
,sizeof(buf
),ice
,i
),
9874 || !gf_puts(NEWLINE
, print_char
))
9885 * windows callback to get/set header mode state
9888 header_mode_callback(set
, args
)
9892 return(ps_global
->full_header
);
9897 * windows callback to get/set zoom mode state
9900 zoom_mode_callback(set
, args
)
9904 return(any_lflagged(ps_global
->msgmap
, MN_HIDE
) != 0);
9909 * windows callback to get/set zoom mode state
9912 any_selected_callback(set
, args
)
9916 return(any_lflagged(ps_global
->msgmap
, MN_SLCT
) != 0);
9924 flag_callback(set
, flags
)
9934 case 1: /* Important */
9935 permflag
= ps_global
->mail_stream
->perm_flagged
;
9939 permflag
= ps_global
->mail_stream
->perm_seen
;
9942 case 3: /* Answered */
9943 permflag
= ps_global
->mail_stream
->perm_answered
;
9946 case 4: /* Deleted */
9947 permflag
= ps_global
->mail_stream
->perm_deleted
;
9952 if(!(any_messages(ps_global
->msgmap
, NULL
, "to Flag")
9953 && can_set_flag(ps_global
, "flag", permflag
)))
9956 if(sp_io_error_on_stream(ps_global
->mail_stream
)){
9957 sp_set_io_error_on_stream(ps_global
->mail_stream
, 0);
9958 pine_mail_check(ps_global
->mail_stream
); /* forces write */
9962 msgno
= mn_m2raw(ps_global
->msgmap
, mn_get_cur(ps_global
->msgmap
));
9963 if(msgno
> 0L && ps_global
->mail_stream
9964 && msgno
<= ps_global
->mail_stream
->nmsgs
9965 && (mc
= mail_elt(ps_global
->mail_stream
, msgno
))
9968 * NOTE: code below is *VERY* sensitive to the order of
9969 * the messages defined in resource.h for flag handling.
9970 * Don't change it unless you know what you're doing.
9977 case 1 : /* Important */
9978 flagstr
= "\\FLAGGED";
9979 mflag
= (mc
->flagged
) ? 0L : ST_SET
;
9984 mflag
= (mc
->seen
) ? 0L : ST_SET
;
9987 case 3 : /* Answered */
9988 flagstr
= "\\ANSWERED";
9989 mflag
= (mc
->answered
) ? 0L : ST_SET
;
9992 case 4 : /* Deleted */
9993 flagstr
= "\\DELETED";
9994 mflag
= (mc
->deleted
) ? 0L : ST_SET
;
9997 default : /* bogus */
10001 mail_flag(ps_global
->mail_stream
, long2string(msgno
),
10004 if(ps_global
->redrawer
)
10005 (*ps_global
->redrawer
)();
10010 newflags
|= 0x0001;
10014 newflags
|= 0x0002;
10018 newflags
|= 0x0004;
10022 newflags
|= 0x0008;
10032 * BUG: Should teach this about keywords
10038 static MPopup flag_submenu
[] = {
10039 {tMessage
, {N_("Important"), lNormal
}, {IDM_MI_FLAGIMPORTANT
}},
10040 {tMessage
, {N_("New"), lNormal
}, {IDM_MI_FLAGNEW
}},
10041 {tMessage
, {N_("Answered"), lNormal
}, {IDM_MI_FLAGANSWERED
}},
10042 {tMessage
, {N_("Deleted"), lNormal
}, {IDM_MI_FLAGDELETED
}},
10047 flag_submenu
[0].label
.style
= (mc
&& mc
->flagged
) ? lChecked
: lNormal
;
10050 flag_submenu
[1].label
.style
= (mc
&& mc
->seen
) ? lNormal
: lChecked
;
10053 flag_submenu
[2].label
.style
= (mc
&& mc
->answered
) ? lChecked
: lNormal
;
10056 flag_submenu
[3].label
.style
= (mc
&& mc
->deleted
) ? lChecked
: lNormal
;
10058 return(flag_submenu
);
10061 #endif /* _WINDOWS */