1 #if !defined(lint) && !defined(DOS)
2 static char rcsid
[] = "$Id: mailcmd.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
6 * ========================================================================
7 * Copyright 2013-2018 Eduardo Chappa
8 * Copyright 2006-2009 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
21 The meat and pototoes of mail processing here:
22 - initial command processing and dispatch
24 - capture address off incoming mail
25 - jump to specific numbered message
26 - open (broach) a new folder
27 - search message headers (where is) command
35 #include "flagmaint.h"
52 #include "../pith/state.h"
53 #include "../pith/msgno.h"
54 #include "../pith/store.h"
55 #include "../pith/thread.h"
56 #include "../pith/flag.h"
57 #include "../pith/sort.h"
58 #include "../pith/maillist.h"
59 #include "../pith/save.h"
60 #include "../pith/pipe.h"
61 #include "../pith/news.h"
62 #include "../pith/util.h"
63 #include "../pith/sequence.h"
64 #include "../pith/keyword.h"
65 #include "../pith/stream.h"
66 #include "../pith/mailcmd.h"
67 #include "../pith/hist.h"
68 #include "../pith/list.h"
69 #include "../pith/icache.h"
70 #include "../pith/busy.h"
71 #include "../pith/mimedesc.h"
72 #include "../pith/pattern.h"
73 #include "../pith/tempfile.h"
74 #include "../pith/search.h"
75 #include "../pith/margin.h"
77 #include "../pico/osdep/mswin.h"
83 int cmd_flag(struct pine
*, MSGNO_S
*, int);
84 int cmd_flag_prompt(struct pine
*, struct flag_screen
*, int);
85 void free_flag_table(struct flag_table
**);
86 int cmd_reply(struct pine
*, MSGNO_S
*, int, ACTION_S
*);
87 int cmd_forward(struct pine
*, MSGNO_S
*, int, ACTION_S
*);
88 int cmd_bounce(struct pine
*, MSGNO_S
*, int, ACTION_S
*);
89 int cmd_save(struct pine
*, MAILSTREAM
*, MSGNO_S
*, int, CmdWhere
);
90 void role_compose(struct pine
*);
91 int cmd_expunge(struct pine
*, MAILSTREAM
*, MSGNO_S
*, int);
92 int cmd_export(struct pine
*, MSGNO_S
*, int, int);
93 char *cmd_delete_action(struct pine
*, MSGNO_S
*, CmdWhere
);
94 char *cmd_delete_view(struct pine
*, MSGNO_S
*);
95 char *cmd_delete_index(struct pine
*, MSGNO_S
*);
96 long get_level(int, UCS
, SCROLL_S
*);
97 long closest_jump_target(long, MAILSTREAM
*, MSGNO_S
*, int, CmdWhere
, char *, size_t);
98 int update_folder_spec(char *, size_t, char *);
99 int cmd_print(struct pine
*, MSGNO_S
*, int, CmdWhere
);
100 int cmd_pipe(struct pine
*, MSGNO_S
*, int);
101 STORE_S
*list_mgmt_text(RFC2369_S
*, long);
102 void list_mgmt_screen(STORE_S
*);
103 int aggregate_select(struct pine
*, MSGNO_S
*, int, CmdWhere
);
104 int select_by_number(MAILSTREAM
*, MSGNO_S
*, SEARCHSET
**);
105 int select_by_thrd_number(MAILSTREAM
*, MSGNO_S
*, SEARCHSET
**);
106 int select_by_date(MAILSTREAM
*, MSGNO_S
*, long, SEARCHSET
**);
107 int select_by_text(MAILSTREAM
*, MSGNO_S
*, long, SEARCHSET
**);
108 int select_by_gm_content(MAILSTREAM
*, MSGNO_S
*, long, SEARCHSET
**);
109 int select_by_size(MAILSTREAM
*, SEARCHSET
**);
110 SEARCHSET
*visible_searchset(MAILSTREAM
*, MSGNO_S
*);
111 int select_by_status(MAILSTREAM
*, SEARCHSET
**);
112 int select_by_rule(MAILSTREAM
*, SEARCHSET
**);
113 int select_by_thread(MAILSTREAM
*, MSGNO_S
*, SEARCHSET
**);
114 char *choose_a_rule(int);
115 int select_by_keyword(MAILSTREAM
*, SEARCHSET
**);
116 char *choose_a_keyword(void);
117 int select_sort(struct pine
*, int, SortOrder
*, int *);
118 int print_index(struct pine
*, MSGNO_S
*, int);
121 * List of Select options used by apply_* functions...
123 static char *sel_pmt1
= N_("ALTER message selection : ");
124 ESCKEY_S sel_opts1
[] = {
125 /* TRANSLATORS: these are keymenu names for selecting. Broaden selection means
126 we will add more messages to the selection, Narrow selection means we will
127 remove some selections (like a logical AND instead of logical OR), and Flip
128 Selected means that all the messages that are currently selected become unselected,
129 and all the unselected messages become selected. */
130 {'a', 'a', "A", N_("unselect All")},
131 {'c', 'c', "C", NULL
},
132 {'b', 'b', "B", N_("Broaden selctn")},
133 {'n', 'n', "N", N_("Narrow selctn")},
134 {'f', 'f', "F", N_("Flip selected")},
139 #define SEL_OPTS_THREAD 9 /* index number of "tHread" */
140 #define SEL_OPTS_THREAD_CH 'h'
141 #define SEL_OPTS_XGMEXT 10 /* index number of "boX" */
142 #define SEL_OPTS_XGMEXT_CH 'g'
144 char *sel_pmt2
= "SELECT criteria : ";
145 static ESCKEY_S sel_opts2
[] = {
146 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
147 means select the currently highlighted message; select by Number is by message
148 number; Status is by status of the message, for example the message might be
149 New or it might be Unseen or marked Important; Size has the Z upper case because
150 it is a Z command; Keyword is an alpine keyword that has been set by the user;
151 and Rule is an alpine rule */
152 {'a', 'a', "A", N_("select All")},
153 {'c', 'c', "C", N_("select Cur")},
154 {'n', 'n', "N", N_("Number")},
155 {'d', 'd', "D", N_("Date")},
156 {'t', 't', "T", N_("Text")},
157 {'s', 's', "S", N_("Status")},
158 {'z', 'z', "Z", N_("siZe")},
159 {'k', 'k', "K", N_("Keyword")},
160 {'r', 'r', "R", N_("Rule")},
161 {SEL_OPTS_THREAD_CH
, 'h', "H", N_("tHread")},
166 static ESCKEY_S sel_opts3
[] = {
167 /* TRANSLATORS: these are operations we can do on a set of selected messages.
168 Del is Delete; Undel is Undelete; TakeAddr means to Take some Addresses into
169 the address book; Save means to save the messages into another alpine folder;
170 Export means to copy the messages to a file outside of alpine, external to
172 {'d', 'd', "D", N_("Del")},
173 {'u', 'u', "U", N_("Undel")},
174 {'r', 'r', "R", N_("Reply")},
175 {'f', 'f', "F", N_("Forward")},
176 {'%', '%', "%", N_("Print")},
177 {'t', 't', "T", N_("TakeAddr")},
178 {'s', 's', "S", N_("Save")},
179 {'e', 'e', "E", N_("Export")},
180 { -1, 0, NULL
, NULL
},
181 { -1, 0, NULL
, NULL
},
182 { -1, 0, NULL
, NULL
},
183 { -1, 0, NULL
, NULL
},
184 { -1, 0, NULL
, NULL
},
185 { -1, 0, NULL
, NULL
},
186 { -1, 0, NULL
, NULL
},
190 static ESCKEY_S sel_opts4
[] = {
191 {'a', 'a', "A", N_("select All")},
192 /* TRANSLATORS: select currrently highlighted message Thread */
193 {'c', 'c', "C", N_("select Curthrd")},
194 {'n', 'n', "N", N_("Number")},
195 {'d', 'd', "D", N_("Date")},
196 {'t', 't', "T", N_("Text")},
197 {'s', 's', "S", N_("Status")},
198 {'z', 'z', "Z", N_("siZe")},
199 {'k', 'k', "K", N_("Keyword")},
200 {'r', 'r', "R", N_("Rule")},
201 {SEL_OPTS_THREAD_CH
, 'h', "H", N_("tHread")},
205 static ESCKEY_S sel_opts5
[] = {
206 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
207 means select the currently highlighted message; select by Number is by message
208 number; Status is by status of the message, for example the message might be
209 New or it might be Unseen or marked Important; Size has the Z upper case because
210 it is a Z command; Keyword is an alpine keyword that has been set by the user;
211 and Rule is an alpine rule */
212 {'a', 'a', "A", N_("select All")},
213 {'c', 'c', "C", N_("select Cur")},
214 {'n', 'n', "N", N_("Number")},
215 {'d', 'd', "D", N_("Date")},
216 {'t', 't', "T", N_("Text")},
217 {'s', 's', "S", N_("Status")},
218 {'z', 'z', "Z", N_("siZe")},
219 {'k', 'k', "K", N_("Keyword")},
220 {'r', 'r', "R", N_("Rule")},
221 {SEL_OPTS_THREAD_CH
, 'h', "H", N_("tHread")},
222 {SEL_OPTS_XGMEXT_CH
, 'g', "G", N_("GmSearch")},
230 static ESCKEY_S sel_opts6
[] = {
231 {'a', 'a', "A", N_("select All")},
232 /* TRANSLATORS: select currrently highlighted message Thread */
233 {'c', 'c', "C", N_("select Curthrd")},
234 {'n', 'n', "N", N_("Number")},
235 {'d', 'd', "D", N_("Date")},
236 {'t', 't', "T", N_("Text")},
237 {'s', 's', "S", N_("Status")},
238 {'z', 'z', "Z", N_("siZe")},
239 {'k', 'k', "K", N_("Keyword")},
240 {'r', 'r', "R", N_("Rule")},
241 {SEL_OPTS_THREAD_CH
, 'h', "H", N_("tHread")},
242 {SEL_OPTS_XGMEXT_CH
, 'g', "G", N_("Gmail")},
251 static char *sel_flag
=
252 N_("Select New, Deleted, Answered, Forwarded, or Important messages ? ");
253 static char *sel_flag_not
=
254 N_("Select NOT New, NOT Deleted, NOT Answered, NOT Forwarded or NOT Important msgs ? ");
255 static ESCKEY_S sel_flag_opt
[] = {
256 /* TRANSLATORS: When selecting messages by message Status these are the
257 different types of Status you can select on. Is the message New, Recent,
258 and so on. Not means flip the meaning of the selection to the opposite
259 thing, so message is not New or not Important. */
260 {'n', 'n', "N", N_("New")},
261 {'*', '*', "*", N_("Important")},
262 {'d', 'd', "D", N_("Deleted")},
263 {'a', 'a', "A", N_("Answered")},
264 {'f', 'f', "F", N_("Forwarded")},
266 {'!', '!', "!", N_("Not")},
268 {'r', 'r', "R", N_("Recent")},
269 {'u', 'u', "U", N_("Unseen")},
274 static ESCKEY_S sel_date_opt
[] = {
276 /* TRANSLATORS: options when selecting messages by Date */
277 {ctrl('P'), 12, "^P", N_("Prev Day")},
278 {ctrl('N'), 13, "^N", N_("Next Day")},
279 {ctrl('X'), 11, "^X", N_("Cur Msg")},
280 {ctrl('W'), 14, "^W", N_("Toggle When")},
281 {KEY_UP
, 12, "", ""},
282 {KEY_DOWN
, 13, "", ""},
287 static char *sel_x_gm_ext
=
289 static char *sel_text
=
290 N_("Select based on To, From, Cc, Recip, Partic, Subject fields or All msg text ? ");
291 static char *sel_text_not
=
292 N_("Select based on NOT To, From, Cc, Recip, Partic, Subject or All msg text ? ");
293 static ESCKEY_S sel_text_opt
[] = {
294 /* TRANSLATORS: Select messages based on the text contained in the From line, or
295 the Subject line, and so on. */
296 {'f', 'f', "F", N_("From")},
297 {'s', 's', "S", N_("Subject")},
298 {'t', 't', "T", N_("To")},
299 {'a', 'a', "A", N_("All Text")},
300 {'c', 'c', "C", N_("Cc")},
301 {'!', '!', "!", N_("Not")},
302 {'r', 'r', "R", N_("Recipient")},
303 {'p', 'p', "P", N_("Participant")},
304 {'b', 'b', "B", N_("Body")},
305 {'h', 'h', "H", N_("Header")},
309 static ESCKEY_S choose_action
[] = {
310 {'c', 'c', "C", N_("Compose")},
311 {'r', 'r', "R", N_("Reply")},
312 {'f', 'f', "F", N_("Forward")},
313 {'b', 'b', "B", N_("Bounce")},
317 static char *select_num
=
318 N_("Enter comma-delimited list of numbers (dash between ranges): ");
320 static char *select_size_larger_msg
=
321 N_("Select messages with size larger than: ");
323 static char *select_size_smaller_msg
=
324 N_("Select messages with size smaller than: ");
326 static char *sel_size_larger
= N_("Larger");
327 static char *sel_size_smaller
= N_("Smaller");
328 static ESCKEY_S sel_size_opt
[] = {
330 {ctrl('W'), 14, "^W", NULL
},
334 static ESCKEY_S sel_key_opt
[] = {
336 {ctrl('T'), 14, "^T", N_("To List")},
338 {'!', '!', "!", N_("Not")},
342 static ESCKEY_S flag_text_opt
[] = {
343 /* TRANSLATORS: these are types of flags (markers) that the user can
344 set. For example, they can flag the message as an important message. */
345 {'n', 'n', "N", N_("New")},
346 {'*', '*', "*", N_("Important")},
347 {'d', 'd', "D", N_("Deleted")},
348 {'a', 'a', "A", N_("Answered")},
349 {'f', 'f', "F", N_("Forwarded")},
350 {'!', '!', "!", N_("Not")},
351 {ctrl('T'), 10, "^T", N_("To Flag Details")},
356 alpine_smime_confirm_save(char *email
)
360 snprintf(prompt
, sizeof(prompt
), _("Save certificate for <%s>"),
361 email
? email
: _("missing address"));
362 return want_to(prompt
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y';
366 alpine_get_password(char *prompt
, char *pass
, size_t len
)
368 int flags
= OE_PASSWD
| OE_DISALLOW_HELP
;
370 return optionally_enter(pass
,
371 -(ps_global
->ttyo
? FOOTER_ROWS(ps_global
) : 3),
372 0, len
, prompt
, NULL
, NO_HELP
, &flags
);
376 smime_import_certificate(char *filename
, char *full_filename
, char *what
, size_t len
)
379 static HISTORY_S
*history
= NULL
;
380 static ESCKEY_S eopts
[] = {
381 {ctrl('T'), 10, "^T", N_("To Files")},
383 {-1, 0, NULL
, NULL
}};
385 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps_global
)){
386 eopts
[r
].ch
= ctrl('I');
388 eopts
[r
].name
= "TAB";
389 eopts
[r
].label
= N_("Complete");
395 full_filename
[0] = '\0';
397 r
= get_export_filename(ps_global
, filename
, NULL
, full_filename
,
398 len
, what
, "IMPORT", eopts
, NULL
,
399 -FOOTER_ROWS(ps_global
), GE_IS_IMPORT
, &history
);
405 /*----------------------------------------------------------------------
406 The giant switch on the commands for index and viewing
408 Input: command -- The command char/code
409 in_index -- flag indicating command is from index
410 orig_command -- The original command typed before pre-processing
411 Output: force_mailchk -- Set to tell caller to force call to new_mail().
415 Returns 1 if the message number or attachment to show changed
418 process_cmd(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
,
419 int command
, CmdWhere in_index
, int *force_mailchk
)
421 int question_line
, a_changed
, flags
= 0, ret
, j
;
423 long new_msgno
, del_count
, old_msgno
, i
;
425 char *newfolder
, prompt
[MAX_SCREEN_COLS
+1];
428 #if defined(DOS) && !defined(_WINDOWS)
429 extern long coreleft();
432 dprint((4, "\n - process_cmd(cmd=%d) -\n", command
));
434 question_line
= -FOOTER_ROWS(state
);
435 state
->mangled_screen
= 0;
436 state
->mangled_footer
= 0;
437 state
->mangled_header
= 0;
438 state
->next_screen
= SCREEN_FUN_NULL
;
439 old_msgno
= mn_get_cur(msgmap
);
444 /*------------- Help --------*/
447 * We're not using the h_mail_view portion of this right now because
448 * that call is being handled in scrolltool() before it gets
449 * here. Leave it in case we change how it works.
451 helper((in_index
== MsgIndx
)
455 : h_mail_thread_index
,
456 (in_index
== MsgIndx
)
457 ? _("HELP FOR MESSAGE INDEX")
459 ? _("HELP FOR MESSAGE TEXT")
460 : _("HELP FOR THREAD INDEX"),
462 dprint((4,"MAIL_CMD: did help command\n"));
463 state
->mangled_screen
= 1;
467 /*--------- Return to main menu ------------*/
469 state
->next_screen
= main_menu_screen
;
470 dprint((2,"MAIL_CMD: going back to main menu\n"));
474 /*------- View message text --------*/
477 if(any_messages(msgmap
, NULL
, "to View")){
478 state
->next_screen
= mail_view_screen
;
484 /*------- View attachment --------*/
486 state
->next_screen
= attachment_screen
;
487 dprint((2,"MAIL_CMD: going to attachment screen\n"));
491 /*---------- Previous message ----------*/
493 if(any_messages(msgmap
, NULL
, NULL
)){
494 if((i
= mn_get_cur(msgmap
)) > 1L){
495 mn_dec_cur(stream
, msgmap
,
496 (in_index
== View
&& THREADING()
497 && sp_viewing_a_thread(stream
))
500 ? MH_ANYTHD
: MH_NONE
);
501 if(i
== mn_get_cur(msgmap
)){
502 PINETHRD_S
*thrd
= NULL
, *topthrd
= NULL
;
504 if(THRD_INDX_ENABLED()){
505 mn_dec_cur(stream
, msgmap
, MH_ANYTHD
);
506 if(i
== mn_get_cur(msgmap
))
507 q_status_message1(SM_ORDER
, 0, 2,
508 _("Already on first %s in Zoomed Index"),
509 THRD_INDX() ? _("thread") : _("message"));
512 || F_ON(F_NEXT_THRD_WO_CONFIRM
, state
))
515 ret
= want_to(_("View previous thread"), 'y', 'x',
519 q_status_message(SM_ORDER
, 0, 2,
520 _("Viewing previous thread"));
521 new_msgno
= mn_get_cur(msgmap
);
522 mn_set_cur(msgmap
, i
);
523 if(unview_thread(state
, stream
, msgmap
)){
524 state
->next_screen
= mail_index_screen
;
525 state
->view_skipped_index
= 0;
526 state
->mangled_screen
= 1;
529 mn_set_cur(msgmap
, new_msgno
);
530 if(THRD_AUTO_VIEW() && in_index
== View
){
532 thrd
= fetch_thread(stream
,
535 if(count_lflags_in_thread(stream
, thrd
,
538 if(view_thread(state
, stream
, msgmap
, 1)){
539 if(current_index_state
)
540 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
542 state
->view_skipped_index
= 1;
543 command
= MC_VIEW_TEXT
;
550 if(THRD_AUTO_VIEW() && in_index
!= View
){
551 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, new_msgno
));
552 if(thrd
&& thrd
->top
)
553 topthrd
= fetch_thread(stream
, thrd
->top
);
556 j
= count_lflags_in_thread(stream
, topthrd
, msgmap
, MN_NONE
);
559 if(!THRD_AUTO_VIEW() || in_index
== View
|| j
!= 1){
560 if(view_thread(state
, stream
, msgmap
, 1)
561 && current_index_state
)
562 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
566 state
->next_screen
= SCREEN_FUN_NULL
;
569 mn_set_cur(msgmap
, i
); /* put it back */
573 q_status_message1(SM_ORDER
, 0, 2,
574 _("Already on first %s in Zoomed Index"),
575 THRD_INDX() ? _("thread") : _("message"));
582 && ((now
= time(0)) > state
->last_nextitem_forcechk
)){
584 /* check at most once a second */
585 state
->last_nextitem_forcechk
= now
;
588 q_status_message1(SM_ORDER
, 0, 1, _("Already on first %s"),
589 THRD_INDX() ? _("thread") : _("message"));
596 /*---------- Next Message ----------*/
598 if(mn_get_total(msgmap
) > 0L
599 && ((i
= mn_get_cur(msgmap
)) < mn_get_total(msgmap
))){
600 mn_inc_cur(stream
, msgmap
,
601 (in_index
== View
&& THREADING()
602 && sp_viewing_a_thread(stream
))
605 ? MH_ANYTHD
: MH_NONE
);
606 if(i
== mn_get_cur(msgmap
)){
607 PINETHRD_S
*thrd
, *topthrd
;
609 if(THRD_INDX_ENABLED()){
611 mn_inc_cur(stream
, msgmap
, MH_ANYTHD
);
613 if(i
== mn_get_cur(msgmap
)){
614 if(any_lflagged(msgmap
, MN_HIDE
))
615 any_messages(NULL
, "more", "in Zoomed Index");
621 || F_ON(F_NEXT_THRD_WO_CONFIRM
, state
))
624 ret
= want_to(_("View next thread"), 'y', 'x',
628 q_status_message(SM_ORDER
, 0, 2,
629 _("Viewing next thread"));
630 new_msgno
= mn_get_cur(msgmap
);
631 mn_set_cur(msgmap
, i
);
632 if(unview_thread(state
, stream
, msgmap
)){
633 state
->next_screen
= mail_index_screen
;
634 state
->view_skipped_index
= 0;
635 state
->mangled_screen
= 1;
638 mn_set_cur(msgmap
, new_msgno
);
639 if(THRD_AUTO_VIEW() && in_index
== View
){
641 thrd
= fetch_thread(stream
,
644 if(count_lflags_in_thread(stream
, thrd
,
647 if(view_thread(state
, stream
, msgmap
, 1)){
648 if(current_index_state
)
649 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
651 state
->view_skipped_index
= 1;
652 command
= MC_VIEW_TEXT
;
659 if(THRD_AUTO_VIEW() && in_index
!= View
){
660 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, new_msgno
));
661 if(thrd
&& thrd
->top
)
662 topthrd
= fetch_thread(stream
, thrd
->top
);
665 j
= count_lflags_in_thread(stream
, topthrd
, msgmap
, MN_NONE
);
668 if(!THRD_AUTO_VIEW() || in_index
== View
|| j
!= 1){
669 if(view_thread(state
, stream
, msgmap
, 1)
670 && current_index_state
)
671 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
675 state
->next_screen
= SCREEN_FUN_NULL
;
678 mn_set_cur(msgmap
, i
); /* put it back */
682 && (thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, i
)))
684 && get_lflag(stream
, NULL
, thrd
->rawno
, MN_COLL
)){
685 q_status_message(SM_ORDER
, 0, 2,
686 _("Expand collapsed thread to see more messages"));
689 any_messages(NULL
, "more", "in Zoomed Index");
697 || (state
->context_current
->use
& CNTXT_INCMNG
)){
698 char nextfolder
[MAXPATH
];
700 strncpy(nextfolder
, state
->cur_folder
, sizeof(nextfolder
));
701 nextfolder
[sizeof(nextfolder
)-1] = '\0';
702 if(next_folder(NULL
, nextfolder
, sizeof(nextfolder
), nextfolder
,
703 state
->context_current
, NULL
, NULL
))
704 strncpy(prompt
, _(". Press TAB for next folder."),
707 strncpy(prompt
, _(". No more folders to TAB to."),
710 prompt
[sizeof(prompt
)-1] = '\0';
713 any_messages(NULL
, (mn_get_total(msgmap
) > 0L) ? "more" : NULL
,
714 prompt
[0] ? prompt
: NULL
);
717 && ((now
= time(0)) > state
->last_nextitem_forcechk
)){
719 /* check at most once a second */
720 state
->last_nextitem_forcechk
= now
;
727 /*---------- Delete message ----------*/
729 (void) cmd_delete(state
, msgmap
, MCMD_NONE
,
730 (in_index
== View
) ? cmd_delete_view
: cmd_delete_index
);
734 /*---------- Undelete message ----------*/
736 (void) cmd_undelete(state
, msgmap
, MCMD_NONE
);
737 update_titlebar_status();
741 /*---------- Reply to message ----------*/
743 (void) cmd_reply(state
, msgmap
, MCMD_NONE
, NULL
);
747 /*---------- Forward message ----------*/
749 (void) cmd_forward(state
, msgmap
, MCMD_NONE
, NULL
);
753 /*---------- Quit pine ------------*/
755 state
->next_screen
= quit_screen
;
756 dprint((1,"MAIL_CMD: quit\n"));
760 /*---------- Compose message ----------*/
762 state
->prev_screen
= (in_index
== View
) ? mail_view_screen
764 compose_screen(state
);
765 state
->mangled_screen
= 1;
766 if (state
->next_screen
)
771 /*---------- Alt Compose message ----------*/
773 state
->prev_screen
= (in_index
== View
) ? mail_view_screen
776 if(state
->next_screen
)
782 /*--------- Folders menu ------------*/
784 state
->start_in_context
= 1;
786 /*--------- Top of Folders list menu ------------*/
787 case MC_COLLECTIONS
:
788 state
->next_screen
= folder_screen
;
789 dprint((2,"MAIL_CMD: going to folder/collection menu\n"));
793 /*---------- Open specific new folder ----------*/
795 tc
= (state
->context_last
&& !NEWS_TEST(state
->context_current
))
796 ? state
->context_last
: state
->context_current
;
798 newfolder
= broach_folder(question_line
, 1, ¬realinbox
, &tc
);
800 visit_folder(state
, newfolder
, tc
, NULL
, notrealinbox
? 0L : DB_INBOXWOCNTXT
);
807 /*------- Go to Index Screen ----------*/
809 state
->next_screen
= mail_index_screen
;
812 /*------- Skip to next interesting message -----------*/
818 * If we're in the thread index, start looking after this
819 * thread. We don't want to match something in the current
822 start
= mn_get_cur(msgmap
);
823 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
824 if(mn_get_revsort(msgmap
)){
825 /* if reversed, top of thread is last one before next thread */
826 if(thrd
&& thrd
->top
)
827 start
= mn_raw2m(msgmap
, thrd
->top
);
830 /* last msg of thread is at the ends of the branches/nexts */
832 start
= mn_raw2m(msgmap
, thrd
->rawno
);
834 thrd
= fetch_thread(stream
, thrd
->branch
);
836 thrd
= fetch_thread(stream
, thrd
->next
);
843 * Flags is 0 in this case because we want to not skip
844 * messages inside of threads so that we can find threads
845 * which have some unseen messages even though the top-level
846 * of the thread is already seen.
847 * If new_msgno ends up being a message which is not visible
848 * because it isn't at the top-level, the current message #
849 * will be adjusted below in adjust_cur.
852 new_msgno
= next_sorted_flagged((F_UNDEL
854 | ((F_ON(F_TAB_TO_NEW
,state
))
856 stream
, start
, &flags
);
858 else if(THREADING() && sp_viewing_a_thread(stream
)){
859 PINETHRD_S
*thrd
, *topthrd
= NULL
;
861 start
= mn_get_cur(msgmap
);
864 * Things are especially complicated when we're viewing_a_thread
865 * from the thread index. First we have to check within the
866 * current thread for a new message. If none is found, then
867 * we search in the next threads and offer to continue in
868 * them. Then we offer to go to the next folder.
870 flags
= NSF_SKIP_CHID
;
871 new_msgno
= next_sorted_flagged((F_UNDEL
873 | ((F_ON(F_TAB_TO_NEW
,state
))
875 stream
, start
, &flags
);
877 * If we found a match then we are done, that is another message
878 * in the current thread index. Otherwise, we have to look
881 if(!(flags
& NSF_FLAG_MATCH
)){
886 new_msgno
= next_sorted_flagged((F_UNDEL
888 | ((F_ON(F_TAB_TO_NEW
,
891 stream
, start
, &flags
);
893 * If we got a match, new_msgno is a message in
894 * a different thread from the one we are viewing.
896 if(flags
& NSF_FLAG_MATCH
){
897 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
,new_msgno
));
898 if(thrd
&& thrd
->top
)
899 topthrd
= fetch_thread(stream
, thrd
->top
);
901 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD
, state
)){
902 static ESCKEY_S next_opt
[] = {
903 {'y', 'y', "Y", N_("Yes")},
904 {'n', 'n', "N", N_("No")},
905 {TAB
, 'n', "Tab", N_("NextNew")},
910 snprintf(prompt
, sizeof(prompt
), _("View thread number %s? "),
911 topthrd
? comatose(topthrd
->thrdno
) : "?");
913 snprintf(prompt
, sizeof(prompt
),
914 _("View message in thread number %s? "),
915 topthrd
? comatose(topthrd
->thrdno
) : "?");
917 prompt
[sizeof(prompt
)-1] = '\0';
919 ret
= radio_buttons(prompt
, -FOOTER_ROWS(state
),
920 next_opt
, 'y', 'x', NO_HELP
,
931 if(unview_thread(state
, stream
, msgmap
)){
932 state
->next_screen
= mail_index_screen
;
933 state
->view_skipped_index
= 0;
934 state
->mangled_screen
= 1;
937 mn_set_cur(msgmap
, new_msgno
);
938 if(THRD_AUTO_VIEW()){
940 if(count_lflags_in_thread(stream
, topthrd
,
941 msgmap
, MN_NONE
) == 1){
942 if(view_thread(state
, stream
, msgmap
, 1)){
943 if(current_index_state
)
944 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
946 state
->view_skipped_index
= 1;
947 command
= MC_VIEW_TEXT
;
953 if(view_thread(state
, stream
, msgmap
, 1) && current_index_state
)
954 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
956 state
->next_screen
= SCREEN_FUN_NULL
;
959 else if(ret
== 'n' && topthrd
){
961 * skip to end of this thread and look starting
962 * in the next thread.
964 if(mn_get_revsort(msgmap
)){
966 * if reversed, top of thread is last one
969 start
= mn_raw2m(msgmap
, topthrd
->rawno
);
973 * last msg of thread is at the ends of
978 start
= mn_raw2m(msgmap
, thrd
->rawno
);
980 thrd
= fetch_thread(stream
, thrd
->branch
);
982 thrd
= fetch_thread(stream
, thrd
->next
);
998 start
= mn_get_cur(msgmap
);
1001 * If we are on a collapsed thread, start looking after the
1002 * collapsed part, unless we are viewing the message.
1004 if(THREADING() && in_index
!= View
){
1009 rawno
= mn_m2raw(msgmap
, start
);
1010 thrd
= fetch_thread(stream
, rawno
);
1011 collapsed
= thrd
&& thrd
->next
1012 && get_lflag(stream
, NULL
, rawno
, MN_COLL
);
1015 if(mn_get_revsort(msgmap
)){
1016 if(thrd
&& thrd
->top
)
1017 start
= mn_raw2m(msgmap
, thrd
->top
);
1021 start
= mn_raw2m(msgmap
, thrd
->rawno
);
1023 thrd
= fetch_thread(stream
, thrd
->branch
);
1025 thrd
= fetch_thread(stream
, thrd
->next
);
1034 new_msgno
= next_sorted_flagged((F_UNDEL
1036 | ((F_ON(F_TAB_TO_NEW
,state
))
1038 stream
, start
, &flags
);
1042 * If there weren't any unread messages left, OR there
1043 * aren't any messages at all, we may want to offer to
1044 * go on to the next folder...
1046 if(flags
& NSF_FLAG_MATCH
){
1047 mn_set_cur(msgmap
, new_msgno
);
1048 if(in_index
!= View
)
1049 adjust_cur_to_visible(stream
, msgmap
);
1052 int in_inbox
= sp_flagged(stream
, SP_INBOX
);
1054 if(state
->context_current
1055 && ((NEWS_TEST(state
->context_current
)
1056 && context_isambig(state
->cur_folder
))
1057 || ((state
->context_current
->use
& CNTXT_INCMNG
)
1059 || folder_index(state
->cur_folder
,
1060 state
->context_current
,
1061 FI_FOLDER
) >= 0)))){
1062 char nextfolder
[MAXPATH
];
1063 MAILSTREAM
*nextstream
= NULL
;
1067 strncpy(nextfolder
, state
->cur_folder
, sizeof(nextfolder
));
1068 nextfolder
[sizeof(nextfolder
)-1] = '\0';
1070 if(!(next_folder(&nextstream
, nextfolder
, sizeof(nextfolder
), nextfolder
,
1071 state
->context_current
, &recent_cnt
,
1072 F_ON(F_TAB_NO_CONFIRM
,state
)
1073 ? NULL
: &did_cancel
))){
1075 static ESCKEY_S inbox_opt
[] = {
1076 {'y', 'y', "Y", N_("Yes")},
1077 {'n', 'n', "N", N_("No")},
1078 {TAB
, 'z', "Tab", N_("To Inbox")},
1082 if(F_ON(F_RET_INBOX_NO_CONFIRM
,state
))
1085 /* TRANSLATORS: this is a question, with some information followed
1086 by Return to INBOX? */
1087 if(state
->context_current
->use
&CNTXT_INCMNG
)
1088 snprintf(prompt
, sizeof(prompt
), _("No more incoming folders. Return to \"%s\"? "), state
->inbox_name
);
1090 snprintf(prompt
, sizeof(prompt
), _("No more news groups. Return to \"%s\"? "), state
->inbox_name
);
1092 ret
= radio_buttons(prompt
, -FOOTER_ROWS(state
),
1093 inbox_opt
, 'y', 'x',
1098 * 'z' is a synonym for 'y'. It is not 'y'
1099 * so that it isn't displayed as a default
1100 * action with square-brackets around it
1103 if(ret
== 'y' || ret
== 'z'){
1104 visit_folder(state
, state
->inbox_name
,
1105 state
->context_current
,
1106 NULL
, DB_INBOXWOCNTXT
);
1110 else if (did_cancel
)
1111 cmd_cancelled(NULL
);
1113 if(state
->context_current
->use
&CNTXT_INCMNG
)
1114 q_status_message(SM_ORDER
, 0, 2, _("No more incoming folders"));
1116 q_status_message(SM_ORDER
, 0, 2, _("No more news groups"));
1124 char *front
, type
[80], cnt
[CNTLEN
], fbuf
[MAX_SCREEN_COLS
/2+1];
1125 int rbspace
, avail
, need
, take_back
;
1129 * Incoming_folder_ or news_group_ or folder_ or group_
1131 * _(13 recent) or _(some recent) or nothing
1134 front
= "View next";
1136 (state
->context_current
->use
& CNTXT_INCMNG
)
1137 ? "Incoming folder" : "news group",
1139 type
[sizeof(type
)-1] = '\0';
1140 snprintf(cnt
, sizeof(cnt
), " (%.*s %s)", CNTLEN
-20,
1141 recent_cnt
? long2string(recent_cnt
) : "some",
1142 F_ON(F_TAB_USES_UNSEEN
, ps_global
)
1143 ? "unseen" : "recent");
1144 cnt
[sizeof(cnt
)-1] = '\0';
1147 * Space reserved for radio_buttons call.
1148 * If we make this 3 then radio_buttons won't mess
1149 * with the prompt. If we make it 2, then we get
1150 * one more character to use but radio_buttons will
1151 * cut off the last character of our prompt, which is
1152 * ok because it is a space.
1155 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
1157 need
= strlen(front
)+1 + strlen(type
)+1 +
1158 + strlen(nextfolder
)+2 + strlen(cnt
) +
1161 take_back
= strlen(type
);
1163 (state
->context_current
->use
& CNTXT_INCMNG
)
1164 ? "folder" : "group", sizeof(type
));
1165 take_back
-= strlen(type
);
1168 need
-= strlen(cnt
);
1172 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1173 snprintf(prompt
, sizeof(prompt
), "%.*s %.*s \"%.*s\"%.*s? ",
1174 (MAX_SCREEN_COLS
+1)/8, front
,
1175 (MAX_SCREEN_COLS
+1)/8, type
,
1176 (MAX_SCREEN_COLS
+1)/2,
1177 short_str(nextfolder
, fbuf
, sizeof(fbuf
),
1178 strlen(nextfolder
) -
1179 ((need
>avail
) ? (need
-avail
) : 0),
1181 (MAX_SCREEN_COLS
+1)/8, cnt
);
1182 prompt
[sizeof(prompt
)-1] = '\0';
1186 * When help gets added, this'll have to become
1187 * a loop like the rest...
1189 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD
, state
)){
1190 static ESCKEY_S next_opt
[] = {
1191 {'y', 'y', "Y", N_("Yes")},
1192 {'n', 'n', "N", N_("No")},
1193 {TAB
, 'n', "Tab", N_("NextNew")},
1197 ret
= radio_buttons(prompt
, -FOOTER_ROWS(state
),
1198 next_opt
, 'y', 'x', NO_HELP
,
1201 cmd_cancelled(NULL
);
1209 if(nextstream
&& sp_dead_stream(nextstream
))
1212 visit_folder(state
, nextfolder
,
1213 state
->context_current
, nextstream
,
1215 /* visit_folder takes care of nextstream */
1223 pine_mail_close(nextstream
);
1227 (mn_get_total(msgmap
) > 0L)
1228 ? IS_NEWS(stream
) ? "more undeleted" : "more new"
1238 /*------- Zoom -----------*/
1241 * Right now the way zoom is implemented is sort of silly.
1242 * There are two per-message flags where just one and a
1243 * global "zoom mode" flag to suppress messags from the index
1246 if(any_messages(msgmap
, NULL
, "to Zoom on")){
1247 if(unzoom_index(state
, stream
, msgmap
)){
1248 dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
1249 q_status_message(SM_ORDER
,0,2, _("Index Zoom Mode is now off"));
1251 else if((i
= zoom_index(state
, stream
, msgmap
, MN_SLCT
)) != 0){
1252 if(any_lflagged(msgmap
, MN_HIDE
)){
1253 dprint((4,"\n\n ---- Entering ZOOM mode ----\n"));
1254 q_status_message4(SM_ORDER
, 0, 2,
1255 _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"),
1256 THRD_INDX() ? "" : comatose(i
),
1257 THRD_INDX() ? "" : " ",
1258 THRD_INDX() ? _("threads") : _("message"),
1259 THRD_INDX() ? "" : plural(i
));
1262 q_status_message(SM_ORDER
, 0, 2,
1263 _("All messages selected, so not entering Index Zoom Mode"));
1266 any_messages(NULL
, "selected", "to Zoom on");
1272 /*---------- print message on paper ----------*/
1274 if(any_messages(msgmap
, NULL
, "to print"))
1275 (void) cmd_print(state
, msgmap
, MCMD_NONE
, in_index
);
1280 /*---------- Take Address ----------*/
1282 if(F_ON(F_ENABLE_ROLE_TAKE
, state
) ||
1283 any_messages(msgmap
, NULL
, "to Take address from"))
1284 (void) cmd_take_addr(state
, msgmap
, MCMD_NONE
);
1289 /*---------- Save Message ----------*/
1291 if(any_messages(msgmap
, NULL
, "to Save"))
1292 (void) cmd_save(state
, stream
, msgmap
, MCMD_NONE
, in_index
);
1297 /*---------- Export message ----------*/
1299 if(any_messages(msgmap
, NULL
, "to Export")){
1300 (void) cmd_export(state
, msgmap
, question_line
, MCMD_NONE
);
1301 state
->mangled_footer
= 1;
1307 /*---------- Expunge ----------*/
1309 (void) cmd_expunge(state
, stream
, msgmap
, MCMD_NONE
);
1313 /*------- Unexclude -----------*/
1315 if(!(IS_NEWS(stream
) && stream
->rdonly
)){
1316 q_status_message(SM_ORDER
, 0, 3,
1317 _("Unexclude not available for mail folders"));
1319 else if(any_lflagged(msgmap
, MN_EXLD
)){
1325 * Since excluded means "hidden deleted" and "killed",
1326 * the count should reflect the former.
1328 pgm
= mail_newsearchpgm();
1330 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
1331 for(i
= 1L, del_count
= 0L; i
<= stream
->nmsgs
; i
++)
1332 if((mc
= mail_elt(stream
, i
)) && mc
->searched
1333 && get_lflag(stream
, NULL
, i
, MN_EXLD
)
1334 && !(msgno_exceptions(stream
, i
, "0", &exbits
, FALSE
)
1335 && (exbits
& MSG_EX_FILTERED
)))
1339 state
->mangled_footer
= 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1340 snprintf(prompt
, sizeof(prompt
), "UNexclude %ld message%s in %.*s", del_count
,
1341 plural(del_count
), MAX_SCREEN_COLS
+1-40,
1342 pretty_fn(state
->cur_folder
));
1343 prompt
[sizeof(prompt
)-1] = '\0';
1344 if(F_ON(F_FULL_AUTO_EXPUNGE
, state
)
1345 || (F_ON(F_AUTO_EXPUNGE
, state
)
1346 && (state
->context_current
1347 && (state
->context_current
->use
& CNTXT_INCMNG
))
1348 && context_isambig(state
->cur_folder
))
1349 || want_to(prompt
, 'y', 0, NO_HELP
, WT_NORM
) == 'y'){
1350 long save_cur_rawno
;
1351 int were_viewing_a_thread
;
1353 save_cur_rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
1354 were_viewing_a_thread
= (THREADING()
1355 && sp_viewing_a_thread(stream
));
1357 if(msgno_include(stream
, msgmap
, MI_NONE
)){
1358 clear_index_cache(stream
, 0);
1360 if(stream
&& stream
->spare
)
1361 erase_threading_info(stream
, msgmap
);
1363 refresh_sort(stream
, msgmap
, SRT_NON
);
1366 if(were_viewing_a_thread
){
1367 if(save_cur_rawno
> 0L)
1368 mn_set_cur(msgmap
, mn_raw2m(msgmap
,save_cur_rawno
));
1370 if(view_thread(state
, stream
, msgmap
, 1) && current_index_state
)
1371 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
1374 if(save_cur_rawno
> 0L)
1375 mn_set_cur(msgmap
, mn_raw2m(msgmap
,save_cur_rawno
));
1377 state
->mangled_screen
= 1;
1378 q_status_message2(SM_ORDER
, 0, 4,
1379 "%s message%s UNexcluded",
1380 long2string(del_count
),
1383 if(in_index
!= View
)
1384 adjust_cur_to_visible(stream
, msgmap
);
1387 any_messages(NULL
, NULL
, "UNexcluded");
1390 any_messages(NULL
, "excluded", "to UNexclude");
1393 any_messages(NULL
, "excluded", "to UNexclude");
1398 /*------- Make Selection -----------*/
1400 if(any_messages(msgmap
, NULL
, "to Select")){
1401 if(aggregate_select(state
, msgmap
, question_line
, in_index
) == 0
1402 && (in_index
== MsgIndx
|| in_index
== ThrdIndx
)
1403 && F_ON(F_AUTO_ZOOM
, state
)
1404 && any_lflagged(msgmap
, MN_SLCT
) > 0L
1405 && !any_lflagged(msgmap
, MN_HIDE
))
1406 (void) zoom_index(state
, stream
, msgmap
, MN_SLCT
);
1412 /*------- Toggle Current Message Selection State -----------*/
1414 if(any_messages(msgmap
, NULL
, NULL
)){
1415 if((select_by_current(state
, msgmap
, in_index
)
1416 || (F_OFF(F_UNSELECT_WONT_ADVANCE
, state
)
1417 && !any_lflagged(msgmap
, MN_HIDE
)))
1418 && (i
= mn_get_cur(msgmap
)) < mn_get_total(msgmap
)){
1419 /* advance current */
1420 mn_inc_cur(stream
, msgmap
,
1421 (in_index
== View
&& THREADING()
1422 && sp_viewing_a_thread(stream
))
1424 : (in_index
== View
)
1425 ? MH_ANYTHD
: MH_NONE
);
1432 /*------- Apply command -----------*/
1434 if(any_messages(msgmap
, NULL
, NULL
)){
1435 if(any_lflagged(msgmap
, MN_SLCT
) > 0L){
1436 if(apply_command(state
, stream
, msgmap
, 0,
1437 AC_NONE
, question_line
)){
1438 if(F_ON(F_AUTO_UNSELECT
, state
)){
1439 agg_select_all(stream
, msgmap
, NULL
, 0);
1440 unzoom_index(state
, stream
, msgmap
);
1442 else if(F_ON(F_AUTO_UNZOOM
, state
))
1443 unzoom_index(state
, stream
, msgmap
);
1447 any_messages(NULL
, NULL
, "to Apply command to. Try \"Select\"");
1453 /*-------- Sort command -------*/
1456 int were_threading
= THREADING();
1457 SortOrder sort
= mn_get_sort(msgmap
);
1458 int rev
= mn_get_revsort(msgmap
);
1460 dprint((1,"MAIL_CMD: sort\n"));
1461 if(select_sort(state
, question_line
, &sort
, &rev
)){
1462 /* $ command reinitializes threading collapsed/expanded info */
1463 if(SORT_IS_THREADED(msgmap
) && !SEP_THRDINDX())
1464 erase_threading_info(stream
, msgmap
);
1466 if(ps_global
&& ps_global
->ttyo
){
1467 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
1468 ps_global
->mangled_footer
= 1;
1471 sort_folder(stream
, msgmap
, sort
, rev
, SRT_VRB
|SRT_MAN
);
1474 state
->mangled_footer
= 1;
1477 * We've changed whether we are threading or not so we need to
1478 * exit the index and come back in so that we switch between the
1479 * thread index and the regular index. Sort_folder will have
1480 * reset viewing_a_thread if necessary.
1483 && ((!were_threading
&& THREADING())
1484 || (were_threading
&& !THREADING()))){
1485 state
->next_screen
= mail_index_screen
;
1486 state
->mangled_screen
= 1;
1493 /*------- Toggle Full Headers -----------*/
1495 state
->full_header
++;
1496 if(state
->full_header
== 1){
1497 if(!(state
->quote_suppression_threshold
1498 && (state
->some_quoting_was_suppressed
|| in_index
!= View
)))
1499 state
->full_header
++;
1501 else if(state
->full_header
> 2)
1502 state
->full_header
= 0;
1504 switch(state
->full_header
){
1506 q_status_message(SM_ORDER
, 0, 3,
1507 _("Display of full headers is now off."));
1511 q_status_message1(SM_ORDER
, 0, 3,
1512 _("Quotes displayed, use %s to see full headers"),
1513 F_ON(F_USE_FK
, state
) ? "F9" : "H");
1517 q_status_message(SM_ORDER
, 0, 3,
1518 _("Display of full headers is now on."));
1533 /*------- Try to decrypt message -----------*/
1535 if(state
->smime
&& state
->smime
->need_passphrase
)
1536 smime_get_passphrase();
1542 smime_info_screen(state
);
1547 /*------- Bounce -----------*/
1549 (void) cmd_bounce(state
, msgmap
, MCMD_NONE
, NULL
);
1553 /*------- Flag -----------*/
1555 dprint((4, "\n - flag message -\n"));
1556 (void) cmd_flag(state
, msgmap
, MCMD_NONE
);
1560 /*------- Pipe message -----------*/
1562 (void) cmd_pipe(state
, msgmap
, MCMD_NONE
);
1566 /*--------- Default, unknown command ----------*/
1568 alpine_panic("Unexpected command case");
1572 return((a_changed
|| mn_get_cur(msgmap
) != old_msgno
) ? 1 : 0);
1577 /*----------------------------------------------------------------------
1578 Map some of the special characters into sensible strings for human
1580 c is a UCS-4 character!
1583 pretty_command(UCS c
)
1585 static char buf
[10];
1592 case ' ' : s
= "SPACE"; break;
1593 case '\033' : s
= "ESC"; break;
1594 case '\177' : s
= "DEL"; break;
1595 case ctrl('I') : s
= "TAB"; break;
1596 case ctrl('J') : s
= "LINEFEED"; break;
1597 case ctrl('M') : s
= "RETURN"; break;
1598 case ctrl('Q') : s
= "XON"; break;
1599 case ctrl('S') : s
= "XOFF"; break;
1600 case KEY_UP
: s
= "Up Arrow"; break;
1601 case KEY_DOWN
: s
= "Down Arrow"; break;
1602 case KEY_RIGHT
: s
= "Right Arrow"; break;
1603 case KEY_LEFT
: s
= "Left Arrow"; break;
1604 case KEY_PGUP
: s
= "Prev Page"; break;
1605 case KEY_PGDN
: s
= "Next Page"; break;
1606 case KEY_HOME
: s
= "Home"; break;
1607 case KEY_END
: s
= "End"; break;
1608 case KEY_DEL
: s
= "Delete"; break; /* Not necessary DEL! */
1609 case KEY_JUNK
: s
= "Junk!"; break;
1610 case BADESC
: s
= "Bad Esc"; break;
1611 case NO_OP_IDLE
: s
= "NO_OP_IDLE"; break;
1612 case NO_OP_COMMAND
: s
= "NO_OP_COMMAND"; break;
1613 case KEY_RESIZE
: s
= "KEY_RESIZE"; break;
1614 case KEY_UTF8
: s
= "KEY_UTF8"; break;
1615 case KEY_MOUSE
: s
= "KEY_MOUSE"; break;
1616 case KEY_SCRLUPL
: s
= "KEY_SCRLUPL"; break;
1617 case KEY_SCRLDNL
: s
= "KEY_SCRLDNL"; break;
1618 case KEY_SCRLTO
: s
= "KEY_SCRLTO"; break;
1619 case KEY_XTERM_MOUSE
: s
= "KEY_XTERM_MOUSE"; break;
1620 case KEY_DOUBLE_ESC
: s
= "KEY_DOUBLE_ESC"; break;
1621 case CTRL_KEY_UP
: s
= "Ctrl Up Arrow"; break;
1622 case CTRL_KEY_DOWN
: s
= "Ctrl Down Arrow"; break;
1623 case CTRL_KEY_RIGHT
: s
= "Ctrl Right Arrow"; break;
1624 case CTRL_KEY_LEFT
: s
= "Ctrl Left Arrow"; break;
1637 snprintf(s
= buf
, sizeof(buf
), "F%ld", (long) (c
- PF1
+ 1));
1641 if(c
< ' ' || (c
>= 0x80 && c
< 0xA0)){
1646 d
= (c
& 0x1f) + 'A' - 1;
1647 snprintf(s
= buf
, sizeof(buf
), "%c%c", c1
? '~' : '^', d
);
1650 memset(buf
, 0, sizeof(buf
));
1651 utf8_put((unsigned char *) buf
, (unsigned long) c
);
1661 /*----------------------------------------------------------------------
1662 Complain about bogus input
1664 Args: ch -- input command to complain about
1665 help -- string indicating where to get help
1669 bogus_command(UCS cmd
, char *help
)
1671 if(cmd
== ctrl('Q') || cmd
== ctrl('S'))
1672 q_status_message1(SM_ASYNC
, 0, 2,
1673 "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.",
1674 pretty_command(cmd
));
1675 else if(cmd
== KEY_JUNK
)
1676 q_status_message3(SM_ORDER
, 0, 2,
1677 "Invalid key pressed.%s%s%s",
1678 (help
) ? " Use " : "",
1680 (help
) ? " for help" : "");
1682 q_status_message4(SM_ORDER
, 0, 2,
1683 "Command \"%s\" not defined for this screen.%s%s%s",
1684 pretty_command(cmd
),
1685 (help
) ? " Use " : "",
1687 (help
) ? " for help" : "");
1692 bogus_utf8_command(char *cmd
, char *help
)
1694 q_status_message4(SM_ORDER
, 0, 2,
1695 "Command \"%s\" not defined for this screen.%s%s%s",
1697 (help
) ? " Use " : "",
1699 (help
) ? " for help" : "");
1703 /*----------------------------------------------------------------------
1704 Execute FLAG message command
1706 Args: state -- Various satate info
1707 msgmap -- map of c-client to local message numbers
1709 Result: with side effect of "current" message FLAG flag set or UNset
1713 cmd_flag(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
1715 char *flagit
, *seq
, *screen_text
[20], **exp
, **p
, *answer
= NULL
;
1716 char *keyword_array
[2];
1717 int user_defined_flags
= 0, mailbox_flags
= 0;
1718 int directly_to_maint_screen
= 0;
1719 long unflagged
, flagged
, flags
, rawno
;
1720 MESSAGECACHE
*mc
= NULL
;
1722 int i
, cnt
, is_set
, trouble
= 0, rv
= 0;
1724 struct flag_table
*fp
, *ftbl
= NULL
;
1725 struct flag_screen flag_screen
;
1726 static char *flag_screen_text1
[] = {
1727 N_(" Set desired flags for current message below. An 'X' means set"),
1728 N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."),
1732 static char *flag_screen_text2
[] = {
1733 N_(" Set desired flags below for selected messages. A '?' means to"),
1734 N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"),
1735 N_(" to unset it. Use the \"Return\" key to toggle, and choose"),
1736 N_(" \"Exit\" when finished."),
1740 static struct flag_table default_ftbl
[] = {
1741 {N_("Important"), h_flag_important
, F_FLAG
, 0, 0, NULL
, NULL
},
1742 {N_("New"), h_flag_new
, F_SEEN
, 0, 0, NULL
, NULL
},
1743 {N_("Answered"), h_flag_answered
, F_ANS
, 0, 0, NULL
, NULL
},
1744 {N_("Forwarded"), h_flag_forwarded
, F_FWD
, 0, 0, NULL
, NULL
},
1745 {N_("Deleted"), h_flag_deleted
, F_DEL
, 0, 0, NULL
, NULL
},
1746 {NULL
, NO_HELP
, 0, 0, 0, NULL
, NULL
}
1749 /* Only check for dead stream for now. Should check permanent flags
1752 if(!(any_messages(msgmap
, NULL
, "to Flag") && can_set_flag(state
, "flag", 1)))
1755 if(sp_io_error_on_stream(state
->mail_stream
)){
1756 sp_set_io_error_on_stream(state
->mail_stream
, 0);
1757 pine_mail_check(state
->mail_stream
); /* forces write */
1763 user_defined_flags
= 0;
1769 /* count how large ftbl will be */
1770 for(cnt
= 0; default_ftbl
[cnt
].name
; cnt
++)
1773 /* add user flags */
1774 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1775 if(!((kw
->nick
&& !strucmp(FORWARDED_FLAG
, kw
->nick
)) || (kw
->kw
&& !strucmp(FORWARDED_FLAG
, kw
->kw
)))){
1776 user_defined_flags
++;
1782 * Add mailbox flags that aren't user-defined flags.
1783 * Don't consider it if it matches either one of our defined
1784 * keywords or one of our defined nicknames for a keyword.
1786 for(i
= 0; stream_to_user_flag_name(state
->mail_stream
, i
); i
++){
1789 q
= stream_to_user_flag_name(state
->mail_stream
, i
);
1791 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1792 if((kw
->nick
&& !strucmp(kw
->nick
, q
)) || (kw
->kw
&& !strucmp(kw
->kw
, q
)))
1797 if(!kw
&& !(q
&& !strucmp(FORWARDED_FLAG
, q
))){
1803 cnt
+= (user_defined_flags
? 2 : 0) + (mailbox_flags
? 2 : 0);
1805 /* set up ftbl, first the system flags */
1806 ftbl
= (struct flag_table
*) fs_get((cnt
+1) * sizeof(*ftbl
));
1807 memset(ftbl
, 0, (cnt
+1) * sizeof(*ftbl
));
1808 for(i
= 0, fp
= ftbl
; default_ftbl
[i
].name
; i
++, fp
++){
1809 fp
->name
= cpystr(default_ftbl
[i
].name
);
1810 fp
->help
= default_ftbl
[i
].help
;
1811 fp
->flag
= default_ftbl
[i
].flag
;
1812 fp
->set
= default_ftbl
[i
].set
;
1813 fp
->ukn
= default_ftbl
[i
].ukn
;
1816 if(user_defined_flags
){
1817 fp
->flag
= F_COMMENT
;
1818 fp
->name
= cpystr("");
1820 fp
->flag
= F_COMMENT
;
1821 len
= strlen(_("User-defined Keywords from Setup/Config"));
1822 fp
->name
= (char *) fs_get((len
+6+6+1) * sizeof(char));
1823 snprintf(fp
->name
, len
+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config"));
1827 /* then the user-defined keywords */
1828 if(user_defined_flags
)
1829 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1830 if(!((kw
->nick
&& !strucmp(FORWARDED_FLAG
, kw
->nick
))
1831 || (kw
->kw
&& !strucmp(FORWARDED_FLAG
, kw
->kw
)))){
1832 fp
->name
= cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
1833 fp
->keyword
= cpystr(kw
->kw
? kw
->kw
: "");
1834 if(kw
->nick
&& kw
->kw
){
1837 l
= strlen(kw
->kw
)+2;
1838 fp
->comment
= (char *) fs_get((l
+1) * sizeof(char));
1839 snprintf(fp
->comment
, l
+1, "(%.*s)", (int) strlen(kw
->kw
), kw
->kw
);
1840 fp
->comment
[l
] = '\0';
1843 fp
->help
= h_flag_user_flag
;
1844 fp
->flag
= F_KEYWORD
;
1852 fp
->flag
= F_COMMENT
;
1853 fp
->name
= cpystr("");
1855 fp
->flag
= F_COMMENT
;
1856 len
= strlen(_("Other keywords in the mailbox that are not user-defined"));
1857 fp
->name
= (char *) fs_get((len
+6+6+1) * sizeof(char));
1858 snprintf(fp
->name
, len
+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined"));
1862 /* then the extra mailbox-defined keywords */
1864 for(i
= 0; stream_to_user_flag_name(state
->mail_stream
, i
); i
++){
1867 q
= stream_to_user_flag_name(state
->mail_stream
, i
);
1869 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1870 if((kw
->nick
&& !strucmp(kw
->nick
, q
)) || (kw
->kw
&& !strucmp(kw
->kw
, q
)))
1875 if(!kw
&& !(q
&& !strucmp(FORWARDED_FLAG
, q
))){
1876 fp
->name
= cpystr(q
);
1877 fp
->keyword
= cpystr(q
);
1878 fp
->help
= h_flag_user_flag
;
1879 fp
->flag
= F_KEYWORD
;
1886 flag_screen
.flag_table
= &ftbl
;
1887 flag_screen
.explanation
= screen_text
;
1889 if(MCMD_ISAGG(aopt
)){
1890 if(!pseudo_selected(ps_global
->mail_stream
, msgmap
)){
1891 free_flag_table(&ftbl
);
1895 exp
= flag_screen_text2
;
1896 for(fp
= ftbl
; fp
->name
; fp
++){
1897 fp
->set
= CMD_FLAG_UNKN
; /* set to unknown */
1901 else if(state
->mail_stream
1902 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
1903 && rawno
<= state
->mail_stream
->nmsgs
1904 && (mc
= mail_elt(state
->mail_stream
, rawno
))){
1905 exp
= flag_screen_text1
;
1906 for(fp
= &ftbl
[0]; fp
->name
; fp
++){
1908 if(fp
->flag
== F_KEYWORD
){
1909 /* see if this keyword is defined for this message */
1910 fp
->set
= CMD_FLAG_CLEAR
;
1911 if(user_flag_is_set(state
->mail_stream
,
1912 rawno
, fp
->keyword
))
1913 fp
->set
= CMD_FLAG_SET
;
1915 else if(fp
->flag
== F_FWD
){
1916 /* see if forwarded keyword is defined for this message */
1917 fp
->set
= CMD_FLAG_CLEAR
;
1918 if(user_flag_is_set(state
->mail_stream
,
1919 rawno
, FORWARDED_FLAG
))
1920 fp
->set
= CMD_FLAG_SET
;
1922 else if(fp
->flag
!= F_COMMENT
)
1923 fp
->set
= ((fp
->flag
== F_SEEN
&& !mc
->seen
)
1924 || (fp
->flag
== F_DEL
&& mc
->deleted
)
1925 || (fp
->flag
== F_FLAG
&& mc
->flagged
)
1926 || (fp
->flag
== F_ANS
&& mc
->answered
))
1927 ? CMD_FLAG_SET
: CMD_FLAG_CLEAR
;
1931 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
1932 _("Error accessing message data"));
1933 free_flag_table(&ftbl
);
1937 if(directly_to_maint_screen
)
1938 goto the_maint_screen
;
1941 if (mswin_usedialog ()) {
1942 if (!os_flagmsgdialog (&ftbl
[0])){
1943 free_flag_table(&ftbl
);
1950 int use_maint_screen
;
1951 int keyword_shortcut
= 0;
1953 use_maint_screen
= F_ON(F_FLAG_SCREEN_DFLT
, ps_global
);
1955 if(!use_maint_screen
){
1957 * We're going to call cmd_flag_prompt(). We need
1958 * to decide whether or not to offer the keyword setting
1959 * shortcut. We'll offer it if the user has the feature
1960 * enabled AND there are some possible keywords that could
1963 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
)){
1964 for(fp
= &ftbl
[0]; fp
->name
&& !keyword_shortcut
; fp
++){
1965 if(fp
->flag
== F_KEYWORD
){
1969 first_char
= (fp
->name
&& fp
->name
[0])
1971 if(isascii(first_char
) && isupper(first_char
))
1972 first_char
= tolower((unsigned char) first_char
);
1974 for(tp
=flag_text_opt
; tp
->ch
!= -1; tp
++)
1975 if(tp
->ch
== first_char
)
1984 use_maint_screen
= !cmd_flag_prompt(state
, &flag_screen
,
1989 if(use_maint_screen
){
1990 for(p
= &screen_text
[0]; *exp
; p
++, exp
++)
1995 directly_to_maint_screen
= flag_maintenance_screen(state
, &flag_screen
);
1999 /* reaquire the elt pointer */
2000 mc
= (state
->mail_stream
2001 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
2002 && rawno
<= state
->mail_stream
->nmsgs
)
2003 ? mail_elt(state
->mail_stream
, rawno
) : NULL
;
2005 for(fp
= ftbl
; mc
&& fp
->name
; fp
++){
2009 if((!MCMD_ISAGG(aopt
) && fp
->set
!= !mc
->seen
)
2010 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2018 unflagged
= F_UNSEEN
;
2025 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->answered
)
2026 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2027 flagit
= "\\ANSWERED";
2030 unflagged
= F_UNANS
;
2041 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->deleted
)
2042 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2043 flagit
= "\\DELETED";
2046 unflagged
= F_UNDEL
;
2057 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->flagged
)
2058 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2059 flagit
= "\\FLAGGED";
2062 unflagged
= F_UNFLAG
;
2073 if(!MCMD_ISAGG(aopt
)){
2074 /* see if forwarded is defined for this message */
2075 is_set
= CMD_FLAG_CLEAR
;
2076 if(user_flag_is_set(state
->mail_stream
,
2077 mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
2079 is_set
= CMD_FLAG_SET
;
2082 if((!MCMD_ISAGG(aopt
) && fp
->set
!= is_set
)
2083 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2084 flagit
= FORWARDED_FLAG
;
2087 unflagged
= F_UNFWD
;
2098 if(!MCMD_ISAGG(aopt
)){
2099 /* see if this keyword is defined for this message */
2100 is_set
= CMD_FLAG_CLEAR
;
2101 if(user_flag_is_set(state
->mail_stream
,
2102 mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
2104 is_set
= CMD_FLAG_SET
;
2107 if((!MCMD_ISAGG(aopt
) && fp
->set
!= is_set
)
2108 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2109 flagit
= fp
->keyword
;
2110 keyword_array
[0] = fp
->keyword
;
2111 keyword_array
[1] = NULL
;
2114 unflagged
= F_UNKEYWORD
;
2118 unflagged
= F_KEYWORD
;
2130 && (seq
= currentf_sequence(state
->mail_stream
, msgmap
,
2131 unflagged
, &flagged
, unflagged
& F_DEL
,
2132 (fp
->flag
== F_KEYWORD
2133 && unflagged
== F_KEYWORD
)
2134 ? keyword_array
: NULL
,
2135 (fp
->flag
== F_KEYWORD
2136 && unflagged
== F_UNKEYWORD
)
2137 ? keyword_array
: NULL
))){
2139 * For user keywords, we may have to create the flag in
2140 * the folder if it doesn't already exist and we are setting
2141 * it (as opposed to clearing it). Mail_flag will
2142 * do that for us, but it's failure isn't very friendly
2143 * error-wise. So we try to make it a little smoother.
2145 if(!(fp
->flag
== F_KEYWORD
|| fp
->flag
== F_FWD
) || !fp
->set
2146 || ((i
=user_flag_index(state
->mail_stream
, flagit
)) >= 0
2148 mail_flag(state
->mail_stream
, seq
, flagit
, flags
);
2150 /* trouble, see if we can add the user flag */
2151 if(state
->mail_stream
->kwd_create
)
2152 mail_flag(state
->mail_stream
, seq
, flagit
, flags
);
2156 if(some_user_flags_defined(state
->mail_stream
))
2157 q_status_message(SM_ORDER
, 3, 4,
2158 _("No more keywords allowed in this folder!"));
2159 else if(fp
->flag
== F_FWD
)
2160 q_status_message(SM_ORDER
, 3, 4,
2161 _("Cannot add keywords for this folder so cannot set Forwarded flag"));
2163 q_status_message(SM_ORDER
, 3, 4,
2164 _("Cannot add keywords for this folder"));
2168 fs_give((void **) &seq
);
2169 if(flagged
&& !trouble
){
2170 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%slagged%s%s%s%s%s message%s%s \"%s\"",
2171 (fp
->set
) ? "F" : "Unf",
2172 MCMD_ISAGG(aopt
) ? " " : "",
2173 MCMD_ISAGG(aopt
) ? long2string(flagged
) : "",
2174 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2176 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2177 ? comatose(mn_total_cur(msgmap
)) : "",
2178 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2180 MCMD_ISAGG(aopt
) ? plural(flagged
) : " ",
2181 MCMD_ISAGG(aopt
) ? "" : long2string(mn_get_cur(msgmap
)),
2183 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2184 q_status_message(SM_ORDER
, 0, 2, answer
= tmp_20k_buf
);
2190 free_flag_table(&ftbl
);
2192 if(directly_to_maint_screen
)
2195 if(MCMD_ISAGG(aopt
))
2196 restore_selected(msgmap
);
2199 q_status_message(SM_ORDER
, 0, 2, _("No flags changed."));
2205 /*----------------------------------------------------------------------
2206 Offer concise status line flag prompt
2208 Args: state -- Various satate info
2209 flags -- flags to offer setting
2211 Result: TRUE if flag to set specified in flags struct or FALSE otw
2215 cmd_flag_prompt(struct pine
*state
, struct flag_screen
*flags
, int allow_keyword_shortcuts
)
2217 int r
, setflag
= 1, first_char
;
2218 struct flag_table
*fp
;
2220 char *ftext
, *ftext_not
;
2221 static char *flag_text
=
2222 N_("Flag New, Deleted, Answered, Forwarded or Important ? ");
2223 static char *flag_text_ak
=
2224 N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? ");
2225 static char *flag_text_not
=
2226 N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? ");
2227 static char *flag_text_ak_not
=
2228 N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? ");
2230 if(allow_keyword_shortcuts
){
2232 ESCKEY_S
*dp
, *sp
, *tp
;
2234 for(sp
=flag_text_opt
; sp
->ch
!= -1; sp
++)
2237 for(fp
=(flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++)
2238 if(fp
->flag
== F_KEYWORD
)
2241 /* set up an ESCKEY_S list which includes invisible keys for keywords */
2242 ek
= (ESCKEY_S
*) fs_get((cnt
+ 1) * sizeof(*ek
));
2243 memset(ek
, 0, (cnt
+1) * sizeof(*ek
));
2244 for(dp
=ek
, sp
=flag_text_opt
; sp
->ch
!= -1; sp
++, dp
++)
2247 for(fp
=(flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++){
2248 if(fp
->flag
== F_KEYWORD
){
2249 first_char
= (fp
->name
&& fp
->name
[0]) ? fp
->name
[0] : -2;
2250 if(isascii(first_char
) && isupper(first_char
))
2251 first_char
= tolower((unsigned char) first_char
);
2254 * Check to see if an earlier keyword in the list, or one of
2255 * the builtin system letters already uses this character.
2256 * If so, the first one wins.
2258 for(tp
=ek
; tp
->ch
!= 0; tp
++)
2259 if(tp
->ch
== first_char
)
2263 continue; /* skip it, already used that char */
2265 dp
->ch
= first_char
;
2266 dp
->rval
= first_char
;
2274 ftext
= _(flag_text_ak
);
2275 ftext_not
= _(flag_text_ak_not
);
2279 ftext
= _(flag_text
);
2280 ftext_not
= _(flag_text_not
);
2284 r
= radio_buttons(setflag
? ftext
: ftext_not
,
2285 -FOOTER_ROWS(state
), ek
, '*', SEQ_EXCEPTION
-1,
2286 NO_HELP
, RB_NORM
| RB_SEQ_SENSITIVE
);
2288 * It is SEQ_EXCEPTION-1 just so that it is some value that isn't
2289 * being used otherwise. The keywords use up all the possible
2290 * letters, so a negative number is good, but it has to be different
2291 * from other negative return values.
2293 if(r
== SEQ_EXCEPTION
-1) /* ol'cancelrooney */
2295 else if(r
== 10) /* return and goto flag screen */
2297 else if(r
== '!') /* flip intention */
2303 for(fp
= (flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++){
2304 if(r
== 'n' || r
== '*' || r
== 'd' || r
== 'a' || r
== 'f'){
2305 if((r
== 'n' && fp
->flag
== F_SEEN
)
2306 || (r
== '*' && fp
->flag
== F_FLAG
)
2307 || (r
== 'd' && fp
->flag
== F_DEL
)
2308 || (r
== 'f' && fp
->flag
== F_FWD
)
2309 || (r
== 'a' && fp
->flag
== F_ANS
)){
2310 fp
->set
= setflag
? CMD_FLAG_SET
: CMD_FLAG_CLEAR
;
2314 else if(allow_keyword_shortcuts
&& fp
->flag
== F_KEYWORD
){
2315 first_char
= (fp
->name
&& fp
->name
[0]) ? fp
->name
[0] : -2;
2316 if(isascii(first_char
) && isupper(first_char
))
2317 first_char
= tolower((unsigned char) first_char
);
2319 if(r
== first_char
){
2320 fp
->set
= setflag
? CMD_FLAG_SET
: CMD_FLAG_CLEAR
;
2326 if(ek
!= flag_text_opt
)
2327 fs_give((void **) &ek
);
2334 * (*ft) is an array of flag_table entries.
2337 free_flag_table(struct flag_table
**ft
)
2339 struct flag_table
*fp
;
2342 for(fp
= (*ft
); fp
->name
; fp
++){
2344 fs_give((void **) &fp
->name
);
2347 fs_give((void **) &fp
->keyword
);
2350 fs_give((void **) &fp
->comment
);
2353 fs_give((void **) ft
);
2358 /*----------------------------------------------------------------------
2359 Execute REPLY message command
2361 Args: state -- Various satate info
2362 msgmap -- map of c-client to local message numbers
2364 Result: reply sent or not
2368 cmd_reply(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, ACTION_S
*role
)
2372 if(any_messages(msgmap
, NULL
, "to Reply to")){
2373 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
2376 rv
= reply(state
, role
);
2378 if(MCMD_ISAGG(aopt
))
2379 restore_selected(msgmap
);
2381 state
->mangled_screen
= 1;
2388 /*----------------------------------------------------------------------
2389 Execute FORWARD message command
2391 Args: state -- Various satate info
2392 msgmap -- map of c-client to local message numbers
2394 Result: selected message[s] forwarded or not
2398 cmd_forward(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, ACTION_S
*role
)
2402 if(any_messages(msgmap
, NULL
, "to Forward")){
2403 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
2406 rv
= forward(state
, role
);
2408 if(MCMD_ISAGG(aopt
))
2409 restore_selected(msgmap
);
2411 state
->mangled_screen
= 1;
2418 /*----------------------------------------------------------------------
2419 Execute BOUNCE message command
2421 Args: state -- Various satate info
2422 msgmap -- map of c-client to local message numbers
2423 aopt -- aggregate options
2425 Result: selected message[s] bounced or not
2429 cmd_bounce(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, ACTION_S
*role
)
2433 if(any_messages(msgmap
, NULL
, "to Bounce")){
2435 if(MCMD_ISAGG(aopt
)){
2436 if(!pseudo_selected(state
->mail_stream
, msgmap
))
2439 else if((i
= any_lflagged(msgmap
, MN_SLCT
)) > 0
2440 && get_lflag(state
->mail_stream
, msgmap
,
2441 mn_m2raw(msgmap
, mn_get_cur(msgmap
)), MN_SLCT
) == 0)
2442 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
2443 _("WARNING: non-selected message is being bounced!"));
2445 && get_lflag(state
->mail_stream
, msgmap
,
2446 mn_m2raw(msgmap
, mn_get_cur(msgmap
)), MN_SLCT
))
2447 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
2448 _("WARNING: not bouncing all selected messages!"));
2450 rv
= bounce(state
, role
);
2452 if(MCMD_ISAGG(aopt
))
2453 restore_selected(msgmap
);
2455 state
->mangled_footer
= 1;
2462 /*----------------------------------------------------------------------
2463 Execute save message command: prompt for folder and call function to save
2465 Args: screen_line -- Line on the screen to prompt on
2466 message -- The MESSAGECACHE entry of message to save
2468 Result: The folder lister can be called to make selection; mangled screen set
2470 This does the prompting for the folder name to save to, possibly calling
2471 up the folder display for selection of folder by user.
2474 cmd_save(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int aopt
, CmdWhere in_index
)
2476 char newfolder
[MAILTMPLEN
], nmsgs
[32], *nick
;
2477 int we_cancel
= 0, rv
= 0, save_flags
;
2479 CONTEXT_S
*cntxt
= NULL
;
2481 SaveDel del
= DontAsk
;
2482 SavePreserveOrder pre
= DontAskPreserve
;
2484 dprint((4, "\n - saving message -\n"));
2486 if(MCMD_ISAGG(aopt
) && !pseudo_selected(stream
, msgmap
))
2489 state
->ugly_consider_advancing_bit
= 0;
2490 if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM
, state
)
2491 && msgno_any_deletedparts(stream
, msgmap
)
2492 && want_to(_("Saved copy will NOT include entire message! Continue"),
2493 'y', 'n', NO_HELP
, WT_FLUSH_IN
| WT_SEQ_SENSITIVE
) != 'y'){
2494 restore_selected(msgmap
);
2495 cmd_cancelled("Save message");
2499 raw
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
2501 if(mn_total_cur(msgmap
) <= 1L){
2502 snprintf(nmsgs
, sizeof(nmsgs
), "Msg #%ld ", mn_get_cur(msgmap
));
2503 nmsgs
[sizeof(nmsgs
)-1] = '\0';
2504 e
= pine_mail_fetchstructure(stream
, raw
, NULL
);
2506 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
2507 _("Can't save message. Error accessing folder"));
2508 restore_selected(msgmap
);
2513 snprintf(nmsgs
, sizeof(nmsgs
), "%s msgs ", comatose(mn_total_cur(msgmap
)));
2514 nmsgs
[sizeof(nmsgs
)-1] = '\0';
2516 /* e is just used to get a default save folder from the first msg */
2517 e
= pine_mail_fetchstructure(stream
,
2518 mn_m2raw(msgmap
, mn_first_cur(msgmap
)),
2522 del
= (!READONLY_FOLDER(stream
) && F_OFF(F_SAVE_WONT_DELETE
, ps_global
))
2524 if(mn_total_cur(msgmap
) > 1L)
2525 pre
= F_OFF(F_AGG_SEQ_COPY
, ps_global
) ? Preserve
: NoPreserve
;
2527 pre
= DontAskPreserve
;
2529 if(save_prompt(state
, &cntxt
, newfolder
, sizeof(newfolder
), nmsgs
, e
,
2530 raw
, NULL
, &del
, &pre
)){
2532 if(ps_global
&& ps_global
->ttyo
){
2533 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
2534 ps_global
->mangled_footer
= 1;
2537 save_flags
= SV_FIX_DELS
;
2538 if(pre
== RetPreserve
)
2539 save_flags
|= SV_PRESERVE
;
2541 save_flags
|= SV_DELETE
;
2542 if(ps_global
->context_list
== cntxt
&& !strucmp(newfolder
, ps_global
->inbox_name
))
2543 save_flags
|= SV_INBOXWOCNTXT
;
2545 we_cancel
= busy_cue(_("Saving"), NULL
, 1);
2546 i
= save(state
, stream
, cntxt
, newfolder
, msgmap
, save_flags
);
2550 if(i
== mn_total_cur(msgmap
)){
2552 if(mn_total_cur(msgmap
) <= 1L){
2553 int need
, avail
= ps_global
->ttyo
->screen_cols
- 2;
2554 int lennick
, lenfldr
;
2557 && ps_global
->context_list
->next
2558 && context_isambig(newfolder
)){
2559 lennick
= MIN(strlen(cntxt
->nickname
), 500);
2560 lenfldr
= MIN(strlen(newfolder
), 500);
2561 need
= 27 + strlen(long2string(mn_get_cur(msgmap
))) +
2565 need
-= MIN(lennick
-10, need
-avail
);
2566 lennick
-= MIN(lennick
-10, need
-avail
);
2569 if(need
> avail
&& lenfldr
> 10)
2570 lenfldr
-= MIN(lenfldr
-10, need
-avail
);
2573 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
2574 "Message %s copied to \"%s\" in <%s>",
2575 long2string(mn_get_cur(msgmap
)),
2576 short_str(newfolder
, (char *)(tmp_20k_buf
+1000), 1000,
2578 short_str(cntxt
->nickname
,
2579 (char *)(tmp_20k_buf
+2000), 1000,
2581 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2583 else if((nick
=folder_is_target_of_nick(newfolder
, cntxt
)) != NULL
){
2584 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
2585 "Message %s copied to \"%s\"",
2586 long2string(mn_get_cur(msgmap
)),
2588 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2591 char *f
= " folder";
2593 lenfldr
= MIN(strlen(newfolder
), 500);
2594 need
= 28 + strlen(long2string(mn_get_cur(msgmap
))) +
2599 if(need
> avail
&& lenfldr
> 10)
2600 lenfldr
-= MIN(lenfldr
-10, need
-avail
);
2603 snprintf(tmp_20k_buf
,SIZEOF_20KBUF
,
2604 "Message %s copied to%s \"%s\"",
2605 long2string(mn_get_cur(msgmap
)), f
,
2606 short_str(newfolder
, (char *)(tmp_20k_buf
+1000), 1000,
2608 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2612 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%s messages saved",
2613 comatose(mn_total_cur(msgmap
)));
2614 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2618 strncat(tmp_20k_buf
, " and deleted", SIZEOF_20KBUF
-strlen(tmp_20k_buf
)-1);
2619 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2622 q_status_message(SM_ORDER
, 0, 3, tmp_20k_buf
);
2624 if(!MCMD_ISAGG(aopt
) && F_ON(F_SAVE_ADVANCES
, state
)){
2625 if(sp_new_mail_count(stream
))
2626 process_filter_patterns(stream
, msgmap
,
2627 sp_new_mail_count(stream
));
2629 mn_inc_cur(stream
, msgmap
,
2630 (in_index
== View
&& THREADING()
2631 && sp_viewing_a_thread(stream
))
2633 : (in_index
== View
)
2634 ? MH_ANYTHD
: MH_NONE
);
2637 state
->ugly_consider_advancing_bit
= 1;
2641 if(MCMD_ISAGG(aopt
)) /* straighten out fakes */
2642 restore_selected(msgmap
);
2645 update_titlebar_status(); /* make sure they see change */
2652 role_compose(struct pine
*state
)
2656 if(F_ON(F_ALT_ROLE_MENU
, state
) && mn_get_total(state
->msgmap
) > 0L){
2659 if(nonempty_patterns(ROLE_DO_ROLES
, &pstate
) && first_pattern(&pstate
)){
2660 action
= radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
2661 -FOOTER_ROWS(state
), choose_action
,
2662 'c', 'x', h_role_compose
, RB_NORM
);
2665 q_status_message(SM_ORDER
, 0, 3,
2666 _("No roles available. Use Setup/Rules to add roles."));
2673 if(action
== 'c' || action
== 'r' || action
== 'f' || action
== 'b'){
2674 ACTION_S
*role
= NULL
;
2675 void (*prev_screen
)(struct pine
*) = NULL
, (*redraw
)(void) = NULL
;
2677 redraw
= state
->redrawer
;
2678 state
->redrawer
= NULL
;
2679 prev_screen
= state
->prev_screen
;
2681 state
->next_screen
= SCREEN_FUN_NULL
;
2684 if(role_select_screen(state
, &role
,
2685 action
== 'f' ? MC_FORWARD
:
2686 action
== 'r' ? MC_REPLY
:
2687 action
== 'b' ? MC_BOUNCE
:
2688 action
== 'c' ? MC_COMPOSE
: 0) < 0){
2689 cmd_cancelled(action
== 'f' ? _("Forward") :
2690 action
== 'r' ? _("Reply") :
2691 action
== 'c' ? _("Composition") : _("Bounce"));
2692 state
->next_screen
= prev_screen
;
2693 state
->redrawer
= redraw
;
2694 state
->mangled_screen
= 1;
2698 * If default role was selected (NULL) we need to make
2699 * up a role which won't do anything, but will cause
2700 * compose_mail to think there's already a role so that
2701 * it won't try to confirm the default.
2704 role
= combine_inherited_role(role
);
2706 role
= (ACTION_S
*) fs_get(sizeof(*role
));
2707 memset((void *) role
, 0, sizeof(*role
));
2708 role
->nick
= cpystr("Default Role");
2711 state
->redrawer
= NULL
;
2714 compose_mail(NULL
, NULL
, role
, NULL
, NULL
);
2718 (void) reply(state
, role
);
2722 (void) forward(state
, role
);
2726 (void) bounce(state
, role
);
2733 state
->next_screen
= prev_screen
;
2734 state
->redrawer
= redraw
;
2735 state
->mangled_screen
= 1;
2741 /*----------------------------------------------------------------------
2742 Do the dirty work of prompting the user for a folder name
2745 nfldr should be a buffer at least MAILTMPLEN long
2746 dela -- a pointer to a SaveDel. If it is
2747 DontAsk on input, don't offer Delete prompt
2748 Del on input, offer Delete command with default of Delete
2750 RetDel and RetNoDel are return values
2757 save_prompt(struct pine
*state
, CONTEXT_S
**cntxt
, char *nfldr
, size_t len_nfldr
,
2758 char *nmsgs
, ENVELOPE
*env
, long int rawmsgno
, char *section
,
2759 SaveDel
*dela
, SavePreserveOrder
*prea
)
2761 int rc
, ku
= -1, n
, flags
, last_rc
= 0, saveable_count
= 0, done
= 0;
2762 int delindex
, preindex
, r
;
2763 char prompt
[6*MAX_SCREEN_COLS
+1], *p
, expanded
[MAILTMPLEN
];
2764 char *buf
= tmp_20k_buf
;
2768 SaveDel del
= DontAsk
;
2769 SavePreserveOrder pre
= DontAskPreserve
;
2770 char *deltext
= NULL
;
2771 static HISTORY_S
*history
= NULL
;
2776 alpine_panic("no context ptr in save_prompt");
2778 init_hist(&history
, HISTSIZE
);
2780 if(!(folder
= save_get_default(state
, env
, rawmsgno
, section
, cntxt
)))
2781 return(0); /* message expunged! */
2783 /* how many context's can be saved to... */
2784 for(tc
= state
->context_list
; tc
; tc
= tc
->next
)
2788 /* set up extra command option keys */
2790 ekey
[rc
].ch
= ctrl('T');
2792 ekey
[rc
].name
= "^T";
2793 /* TRANSLATORS: command means go to Folders list */
2794 ekey
[rc
++].label
= N_("To Fldrs");
2796 if(saveable_count
> 1){
2797 ekey
[rc
].ch
= ctrl('P');
2799 ekey
[rc
].name
= "^P";
2800 ekey
[rc
++].label
= N_("Prev Collection");
2802 ekey
[rc
].ch
= ctrl('N');
2804 ekey
[rc
].name
= "^N";
2805 ekey
[rc
++].label
= N_("Next Collection");
2808 if(F_ON(F_ENABLE_TAB_COMPLETE
, ps_global
)){
2811 ekey
[rc
].name
= "TAB";
2812 /* TRANSLATORS: command asks alpine to complete the name when tab is typed */
2813 ekey
[rc
++].label
= N_("Complete");
2816 if(F_ON(F_ENABLE_SUB_LISTS
, ps_global
)){
2817 ekey
[rc
].ch
= ctrl('X');
2819 ekey
[rc
].name
= "^X";
2820 /* TRANSLATORS: list all the matches */
2821 ekey
[rc
++].label
= N_("ListMatches");
2824 if(dela
&& (*dela
== NoDel
|| *dela
== Del
)){
2825 ekey
[rc
].ch
= ctrl('R');
2827 ekey
[rc
].name
= "^R";
2832 if(prea
&& (*prea
== NoPreserve
|| *prea
== Preserve
)){
2833 ekey
[rc
].ch
= ctrl('W');
2835 ekey
[rc
].name
= "^W";
2840 if(saveable_count
> 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
2841 ekey
[rc
].ch
= KEY_UP
;
2844 ekey
[rc
++].label
= "";
2846 ekey
[rc
].ch
= KEY_DOWN
;
2849 ekey
[rc
++].label
= "";
2851 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
2852 ekey
[rc
].ch
= KEY_UP
;
2856 ekey
[rc
++].label
= "";
2858 ekey
[rc
].ch
= KEY_DOWN
;
2861 ekey
[rc
++].label
= "";
2869 /* only show collection number if more than one available */
2870 if(ps_global
->context_list
->next
)
2871 snprintf(prompt
, sizeof(prompt
), "SAVE%s %sto folder in <%s> [%s] : ",
2872 deltext
? deltext
: "",
2874 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2875 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2877 snprintf(prompt
, sizeof(prompt
), "SAVE%s %sto folder [%s] : ",
2878 deltext
? deltext
: "",
2879 nmsgs
, strsquish(buf
, SIZEOF_20KBUF
, folder
, 40));
2881 prompt
[sizeof(prompt
)-1] = '\0';
2884 * If the prompt won't fit, try removing deltext.
2886 if(state
->ttyo
->screen_cols
< strlen(prompt
) + MIN_OPT_ENT_WIDTH
&& deltext
){
2887 if(ps_global
->context_list
->next
)
2888 snprintf(prompt
, sizeof(prompt
), "SAVE %sto folder in <%s> [%s] : ",
2890 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2891 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2893 snprintf(prompt
, sizeof(prompt
), "SAVE %sto folder [%s] : ",
2894 nmsgs
, strsquish(buf
, SIZEOF_20KBUF
, folder
, 40));
2896 prompt
[sizeof(prompt
)-1] = '\0';
2900 * If the prompt still won't fit, remove the extra info contained
2903 if(state
->ttyo
->screen_cols
< strlen(prompt
) + MIN_OPT_ENT_WIDTH
&& *nmsgs
){
2904 if(ps_global
->context_list
->next
)
2905 snprintf(prompt
, sizeof(prompt
), "SAVE to folder in <%s> [%s] : ",
2906 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2907 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2909 snprintf(prompt
, sizeof(prompt
), "SAVE to folder [%s] : ",
2910 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2912 prompt
[sizeof(prompt
)-1] = '\0';
2916 ekey
[delindex
].label
= (del
== NoDel
) ? "Delete" : "No Delete";
2918 if(pre
!= DontAskPreserve
)
2919 ekey
[preindex
].label
= (pre
== NoPreserve
) ? "Preserve Order" : "Any Order";
2922 if(items_in_hist(history
) > 1){
2923 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
2924 ekey
[ku
].label
= HISTORY_KEYLABEL
;
2925 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
2926 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
2930 ekey
[ku
].label
= "";
2931 ekey
[ku
+1].name
= "";
2932 ekey
[ku
+1].label
= "";
2936 flags
= OE_APPEND_CURRENT
| OE_SEQ_SENSITIVE
;
2937 rc
= optionally_enter(nfldr
, -FOOTER_ROWS(state
), 0, len_nfldr
,
2938 prompt
, ekey
, help
, &flags
);
2942 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
2943 _("Error reading folder name"));
2948 removing_trailing_white_space(nfldr
);
2949 removing_leading_white_space(nfldr
);
2951 if(*nfldr
|| *folder
){
2952 char *p
, *name
, *fullname
= NULL
;
2953 int exists
, breakout
= FALSE
;
2956 strncpy(nfldr
, folder
, len_nfldr
-1);
2957 nfldr
[len_nfldr
-1] = '\0';
2960 save_hist(history
, nfldr
, 0, (void *) *cntxt
);
2962 if(!(name
= folder_is_nick(nfldr
, FOLDERS(*cntxt
), 0)))
2965 if(update_folder_spec(expanded
, sizeof(expanded
), name
)){
2966 strncpy(name
= nfldr
, expanded
, len_nfldr
-1);
2967 nfldr
[len_nfldr
-1] = '\0';
2970 exists
= folder_name_exists(*cntxt
, name
, &fullname
);
2972 if(exists
== FEX_ERROR
){
2973 q_status_message1(SM_ORDER
, 0, 3,
2974 _("Problem accessing folder \"%s\""),
2980 strncpy(name
= nfldr
, fullname
, len_nfldr
-1);
2981 nfldr
[len_nfldr
-1] = '\0';
2982 fs_give((void **) &fullname
);
2986 if(exists
& FEX_ISFILE
){
2989 else if((exists
& FEX_ISDIR
)){
2990 char tmp
[MAILTMPLEN
];
2994 CONTEXT_S
*fake_context
;
2997 strncpy(tmp
, name
, sizeof(tmp
));
2998 tmp
[sizeof(tmp
)-2-1] = '\0';
2999 if(tmp
[(l
= strlen(tmp
)) - 1] != tc
->dir
->delim
){
3000 if(l
< sizeof(tmp
)){
3001 tmp
[l
] = tc
->dir
->delim
;
3002 strncpy(&tmp
[l
+1], "[]", sizeof(tmp
)-(l
+1));
3006 strncat(tmp
, "[]", sizeof(tmp
)-strlen(tmp
)-1);
3008 tmp
[sizeof(tmp
)-1] = '\0';
3010 fake_context
= new_context(tmp
, 0);
3012 done
= display_folder_list(&fake_context
, nfldr
,
3013 1, folders_for_save
);
3014 free_context(&fake_context
);
3016 else if(tc
->dir
->delim
3017 && (p
= strrindex(name
, tc
->dir
->delim
))
3019 done
= display_folder_list(cntxt
, nfldr
,
3020 1, folders_for_save
);
3022 q_status_message1(SM_ORDER
, 3, 3,
3023 _("\"%s\" is a directory"), name
);
3025 && !((p
=strrindex(name
, tc
->dir
->delim
)) && *(p
+1) == '\0')){
3026 strncpy(tmp
, name
, sizeof(tmp
));
3027 tmp
[sizeof(tmp
)-1] = '\0';
3028 snprintf(nfldr
, len_nfldr
, "%s%c", tmp
, tc
->dir
->delim
);
3032 else{ /* Doesn't exist, create! */
3033 if((fullname
= folder_as_breakout(*cntxt
, name
)) != NULL
){
3034 strncpy(name
= nfldr
, fullname
, len_nfldr
-1);
3035 nfldr
[len_nfldr
-1] = '\0';
3036 fs_give((void **) &fullname
);
3039 switch(create_for_save(*cntxt
, name
)){
3040 case 1 : /* success */
3043 case 0 : /* error */
3044 case -1 : /* declined */
3053 /* else fall thru like they cancelled */
3056 cmd_cancelled("Save message");
3061 r
= display_folder_list(cntxt
, nfldr
, 0, folders_for_save
);
3069 helper(h_save
, _("HELP FOR SAVE"), HLPD_SIMPLE
);
3070 ps_global
->mangled_screen
= 1;
3073 case 4 : /* redraw */
3076 case 10 : /* previous collection */
3077 for(tc
= (*cntxt
)->prev
; tc
; tc
= tc
->prev
)
3084 for(tc2
= (tc
= (*cntxt
))->next
; tc2
; tc2
= tc2
->next
)
3092 case 11 : /* next collection */
3096 if(((*cntxt
) = (*cntxt
)->next
) == NULL
)
3097 (*cntxt
) = ps_global
->context_list
;
3098 while(NEWS_TEST(*cntxt
) && (*cntxt
) != tc
);
3101 case 12 : /* file name completion */
3102 if(!folder_complete(*cntxt
, nfldr
, len_nfldr
, &n
)){
3103 if(n
&& last_rc
== 12 && !(flags
& OE_USER_MODIFIED
)){
3104 r
= display_folder_list(cntxt
, nfldr
, 1, folders_for_save
);
3106 done
++; /* bingo! */
3108 rc
= 0; /* burn last_rc */
3116 case 14 : /* file name completion */
3117 r
= display_folder_list(cntxt
, nfldr
, 2, folders_for_save
);
3119 done
++; /* bingo! */
3121 rc
= 0; /* burn last_rc */
3125 case 15 : /* Delete / No Delete */
3126 del
= (del
== NoDel
) ? Del
: NoDel
;
3127 deltext
= (del
== NoDel
) ? " (no delete)" : " (and delete)";
3130 case 16 : /* Preserve Order or not */
3131 pre
= (pre
== NoPreserve
) ? Preserve
: NoPreserve
;
3135 if((p
= get_prev_hist(history
, nfldr
, 0, (void *) *cntxt
)) != NULL
){
3136 strncpy(nfldr
, p
, len_nfldr
);
3137 nfldr
[len_nfldr
-1] = '\0';
3138 if(history
->hist
[history
->curindex
])
3139 *cntxt
= (CONTEXT_S
*) history
->hist
[history
->curindex
]->cntxt
;
3147 if((p
= get_next_hist(history
, nfldr
, 0, (void *) *cntxt
)) != NULL
){
3148 strncpy(nfldr
, p
, len_nfldr
);
3149 nfldr
[len_nfldr
-1] = '\0';
3150 if(history
->hist
[history
->curindex
])
3151 *cntxt
= (CONTEXT_S
*) history
->hist
[history
->curindex
]->cntxt
;
3159 alpine_panic("Unhandled case");
3166 ps_global
->mangled_footer
= 1;
3172 strncpy(ps_global
->last_save_folder
, nfldr
, sizeof(ps_global
->last_save_folder
)-1);
3173 ps_global
->last_save_folder
[sizeof(ps_global
->last_save_folder
)-1] = '\0';
3175 ps_global
->last_save_context
= *cntxt
;
3178 strncpy(nfldr
, folder
, len_nfldr
-1);
3179 nfldr
[len_nfldr
-1] = '\0';
3182 /* nickname? Copy real name to nfldr */
3184 && context_isambig(nfldr
)
3185 && (p
= folder_is_nick(nfldr
, FOLDERS(*cntxt
), 0))){
3186 strncpy(nfldr
, p
, len_nfldr
-1);
3187 nfldr
[len_nfldr
-1] = '\0';
3190 if(dela
&& (*dela
== NoDel
|| *dela
== Del
))
3191 *dela
= (del
== NoDel
) ? RetNoDel
: RetDel
;
3193 if(prea
&& (*prea
== NoPreserve
|| *prea
== Preserve
))
3194 *prea
= (pre
== NoPreserve
) ? RetNoPreserve
: RetPreserve
;
3200 /*----------------------------------------------------------------------
3201 Prompt user before implicitly creating a folder for saving
3203 Args: context - context to create folder in
3204 folder - folder name to create
3206 Result: 1 on proceed, -1 on decline, 0 on error
3210 create_for_save_prompt(CONTEXT_S
*context
, char *folder
, int sequence_sensitive
)
3212 if(context
&& ps_global
->context_list
->next
&& context_isambig(folder
)){
3213 if(context
->use
& CNTXT_INCMNG
){
3214 snprintf(tmp_20k_buf
,SIZEOF_20KBUF
,
3215 _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
3216 folder
, (strlen(folder
) > 15) ? "..." : "");
3217 q_status_message(SM_ORDER
, 3, 3, tmp_20k_buf
);
3218 return(0); /* error */
3221 snprintf(tmp_20k_buf
,SIZEOF_20KBUF
,
3222 _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
3223 folder
, (strlen(folder
) > 15) ? "..." : "",
3225 (strlen(context
->nickname
) > 15) ? "..." : "");
3228 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
3229 _("Folder \"%.40s%s\" doesn't exist. Create"),
3230 folder
, strlen(folder
) > 40 ? "..." : "");
3232 if(want_to(tmp_20k_buf
, 'y', 'n',
3233 NO_HELP
, (sequence_sensitive
) ? WT_SEQ_SENSITIVE
: WT_NORM
) != 'y'){
3234 cmd_cancelled("Save message");
3243 /*----------------------------------------------------------------------
3244 Expunge messages from current folder
3246 Args: state -- pointer to struct holding a bunch of pine state
3247 msgmap -- table mapping msg nums to c-client sequence nums
3248 qline -- screen line to ask questions on
3249 agg -- boolean indicating we're to operate on aggregate set
3254 cmd_expunge(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int agg
)
3256 long del_count
, prefilter_del_count
;
3257 int we_cancel
= 0, rv
= 0;
3258 char prompt
[MAX_SCREEN_COLS
+1];
3260 COLOR_PAIR
*lastc
= NULL
;
3262 dprint((2, "\n - expunge -\n"));
3266 sequence
= MCMD_ISAGG(agg
) ? selected_sequence(stream
, msgmap
, NULL
, 0) : NULL
;
3268 if(MCMD_ISAGG(agg
)){
3271 for(i
= 1L; i
<= stream
->nmsgs
; i
++){
3272 if((mc
= mail_elt(stream
, i
)) != NULL
3273 && mc
->sequence
&& mc
->deleted
)
3277 q_status_message(SM_ORDER
, 0, 4,
3278 _("No selected messages are deleted"));
3282 if(!any_messages(msgmap
, NULL
, "to Expunge"))
3286 if(IS_NEWS(stream
) && stream
->rdonly
){
3287 if(!MCMD_ISAGG(agg
))
3288 del_count
= count_flagged(stream
, F_DEL
);
3290 state
->mangled_footer
= 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3291 snprintf(prompt
, sizeof(prompt
), "Exclude %ld message%s from %.*s", del_count
,
3292 plural(del_count
), MAX_SCREEN_COLS
+1-40,
3293 pretty_fn(state
->cur_folder
));
3294 prompt
[sizeof(prompt
)-1] = '\0';
3295 if(F_ON(F_FULL_AUTO_EXPUNGE
, state
)
3296 || (F_ON(F_AUTO_EXPUNGE
, state
)
3297 && (state
->context_current
3298 && (state
->context_current
->use
& CNTXT_INCMNG
))
3299 && context_isambig(state
->cur_folder
))
3300 || want_to(prompt
, 'y', 0, NO_HELP
, WT_NORM
) == 'y'){
3302 if(F_ON(F_NEWS_CROSS_DELETE
, state
))
3303 cross_delete_crossposts(stream
);
3305 msgno_exclude_deleted(stream
, msgmap
, sequence
);
3306 clear_index_cache(stream
, 0);
3309 * This is kind of surprising at first. For most sort
3310 * orders, if the whole set is sorted, then any subset
3311 * is also sorted. Not so for threaded sorts.
3313 if(SORT_IS_THREADED(msgmap
))
3314 refresh_sort(stream
, msgmap
, SRT_NON
);
3316 state
->mangled_body
= 1;
3317 state
->mangled_header
= 1;
3318 q_status_message2(SM_ORDER
, 0, 4,
3319 "%s message%s excluded",
3320 long2string(del_count
),
3324 any_messages(NULL
, NULL
, "Excluded");
3327 any_messages(NULL
, "deleted", "to Exclude");
3331 else if(READONLY_FOLDER(stream
)){
3332 q_status_message(SM_ORDER
, 0, 4,
3333 _("Can't expunge. Folder is read-only"));
3337 if(!MCMD_ISAGG(agg
)){
3338 prefilter_del_count
= count_flagged(stream
, F_DEL
|F_NOFILT
);
3339 mail_expunge_prefilter(stream
, MI_NONE
);
3340 del_count
= count_flagged(stream
, F_DEL
|F_NOFILT
);
3345 unsigned char *fname
= folder_name_decoded((unsigned char *)state
->cur_folder
);
3346 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3347 snprintf(prompt
, sizeof(prompt
), "Expunge %ld message%s from %.*s", del_count
,
3348 plural(del_count
), MAX_SCREEN_COLS
+1-40,
3349 pretty_fn((char *) fname
));
3350 if(fname
) fs_give((void **)&fname
);
3351 prompt
[sizeof(prompt
)-1] = '\0';
3352 state
->mangled_footer
= 1;
3354 if(F_ON(F_FULL_AUTO_EXPUNGE
, state
)
3355 || (F_ON(F_AUTO_EXPUNGE
, state
)
3356 && ((!strucmp(state
->cur_folder
,state
->inbox_name
))
3357 || (state
->context_current
->use
& CNTXT_INCMNG
))
3358 && context_isambig(state
->cur_folder
))
3359 || (ret
=want_to(prompt
, 'y', 0, NO_HELP
, WT_NORM
)) == 'y')
3363 cmd_cancelled("Expunge");
3369 dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
3370 mn_get_total(msgmap
), mn_get_cur(msgmap
), del_count
));
3372 lastc
= pico_set_colors(state
->VAR_TITLE_FORE_COLOR
,
3373 state
->VAR_TITLE_BACK_COLOR
,
3376 PutLine0(0, 0, "**"); /* indicate delay */
3379 (void)pico_set_colorp(lastc
, PSC_NONE
);
3380 free_color_pair(&lastc
);
3383 MoveCursor(state
->ttyo
->screen_rows
-FOOTER_ROWS(state
), 0);
3386 we_cancel
= busy_cue(_("Expunging"), NULL
, 1);
3388 if(cmd_expunge_work(stream
, msgmap
, sequence
))
3389 state
->mangled_body
= 1;
3392 fs_give((void **)&sequence
);
3395 cancel_busy_cue((sp_expunge_count(stream
) > 0) ? 0 : -1);
3397 lastc
= pico_set_colors(state
->VAR_TITLE_FORE_COLOR
,
3398 state
->VAR_TITLE_BACK_COLOR
,
3400 PutLine0(0, 0, " "); /* indicate delay's over */
3403 (void)pico_set_colorp(lastc
, PSC_NONE
);
3404 free_color_pair(&lastc
);
3409 if(sp_expunge_count(stream
) > 0){
3411 * This is kind of surprising at first. For most sort
3412 * orders, if the whole set is sorted, then any subset
3413 * is also sorted. Not so for threaded sorts.
3415 if(SORT_IS_THREADED(msgmap
))
3416 refresh_sort(stream
, msgmap
, SRT_NON
);
3420 unsigned char *fname
= folder_name_decoded((unsigned char *)state
->cur_folder
);
3421 q_status_message1(SM_ORDER
, 0, 3,
3422 _("No messages expunged from folder \"%s\""),
3423 pretty_fn((char *) fname
));
3424 if(fname
) fs_give((void **)&fname
);
3426 else if(!prefilter_del_count
)
3427 q_status_message(SM_ORDER
, 0, 3,
3428 _("No messages marked deleted. No messages expunged."));
3434 /*----------------------------------------------------------------------
3435 Expunge_and_close callback to prompt user for confirmation
3437 Args: stream -- folder's stream
3438 folder -- name of folder containing folders
3439 deleted -- number of del'd msgs
3441 Result: 'y' to continue with expunge
3444 expunge_prompt(MAILSTREAM
*stream
, char *folder
, long int deleted
)
3448 char prompt_b
[MAX_SCREEN_COLS
+1], temp
[MAILTMPLEN
+1], buff
[MAX_SCREEN_COLS
+1];
3449 char *short_folder_name
;
3454 snprintf(temp
, sizeof(temp
), "%ld", deleted
);
3455 charcnt
= strlen(temp
)+1;
3458 max_folder
= MAX(1,MAXPROMPT
- (36+charcnt
));
3459 strncpy(temp
, folder
, sizeof(temp
));
3460 temp
[sizeof(temp
)-1] = '\0';
3461 short_folder_name
= short_str(temp
,buff
,sizeof(buff
),max_folder
,FrontDots
);
3464 snprintf(prompt_b
, sizeof(prompt_b
),
3465 "Delete %s%ld message%s from \"%s\"",
3466 (deleted
> 1L) ? "all " : "", deleted
,
3467 plural(deleted
), short_folder_name
);
3469 snprintf(prompt_b
, sizeof(prompt_b
),
3470 "Expunge the %ld deleted message%s from \"%s\"",
3471 deleted
, deleted
== 1 ? "" : "s",
3474 return(want_to(prompt_b
, 'y', 0, NO_HELP
, WT_NORM
));
3479 * This is used with multiple append saves. Call it once before
3480 * the series of appends with SSCP_INIT and once after all are
3481 * done with SSCP_END. In between, it is called automatically
3482 * from save_fetch_append or save_fetch_append_cb when we need
3483 * to ask the user if he or she wants to continue even though
3484 * announced message size doesn't match the actual message size.
3485 * As of 2008-02-29 the gmail IMAP server has these size mismatches
3486 * on a regular basis even though the data is ok.
3489 save_size_changed_prompt(long msgno
, int flags
)
3493 static int remember_the_yes
= 0;
3494 static int possible_corruption
= 0;
3495 static ESCKEY_S save_size_opts
[] = {
3496 {'y', 'y', "Y", "Yes"},
3497 {'n', 'n', "N", "No"},
3498 {'a', 'a', "A", "yes to All"},
3502 if(F_ON(F_IGNORE_SIZE
, ps_global
))
3505 if(flags
& SSCP_INIT
|| flags
& SSCP_END
){
3506 if(flags
& SSCP_END
&& possible_corruption
)
3507 q_status_message(SM_ORDER
, 3, 3, "There is possible data corruption, check the results");
3509 remember_the_yes
= 0;
3510 possible_corruption
= 0;
3511 ps_global
->noshow_error
= 0;
3512 ps_global
->noshow_warn
= 0;
3516 if(remember_the_yes
){
3517 snprintf(prompt
, sizeof(prompt
),
3518 "Message to save shrank! (msg # %ld): Continuing", msgno
);
3519 q_status_message(SM_ORDER
, 0, 3, prompt
);
3520 display_message('x');
3521 return(remember_the_yes
);
3524 snprintf(prompt
, sizeof(prompt
),
3525 "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno
);
3526 ret
= radio_buttons(prompt
, -FOOTER_ROWS(ps_global
), save_size_opts
,
3527 'n', 0, h_save_size_changed
, RB_NORM
);
3531 remember_the_yes
= 'y';
3532 possible_corruption
++;
3533 return(remember_the_yes
);
3536 possible_corruption
++;
3540 possible_corruption
= 0;
3541 ps_global
->noshow_error
= 1;
3542 ps_global
->noshow_warn
= 1;
3550 /*----------------------------------------------------------------------
3551 Expunge_and_close callback that happens once the decision to expunge
3552 and close has been made and before expunging and closing begins
3555 Args: stream -- folder's stream
3556 folder -- name of folder containing folders
3557 deleted -- number of del'd msgs
3559 Result: 'y' to continue with expunge
3562 expunge_and_close_begins(int flags
, char *folder
)
3564 if(!(flags
& EC_NO_CLOSE
)){
3565 unsigned char *fname
= folder_name_decoded((unsigned char *)folder
);
3566 q_status_message1(SM_INFO
, 0, 1, "Closing \"%.200s\"...", (char *) fname
);
3567 flush_status_messages(1);
3568 if(fname
) fs_give((void **)&fname
);
3573 /*----------------------------------------------------------------------
3574 Export a message to a plain file in users home directory
3576 Args: state -- pointer to struct holding a bunch of pine state
3577 msgmap -- table mapping msg nums to c-client sequence nums
3578 qline -- screen line to ask questions on
3579 agg -- boolean indicating we're to operate on aggregate set
3584 cmd_export(struct pine
*state
, MSGNO_S
*msgmap
, int qline
, int aopt
)
3586 char filename
[MAXPATH
+1], full_filename
[MAXPATH
+1], *err
;
3588 int r
, leading_nl
, failure
= 0, orig_errno
, rflags
= GER_NONE
;
3589 int flags
= GE_IS_EXPORT
| GE_SEQ_SENSITIVE
, rv
= 0;
3593 long i
, count
= 0L, start_of_append
, rawno
;
3596 struct variable
*vars
= state
? ps_global
->vars
: NULL
;
3597 ESCKEY_S export_opts
[5];
3598 static HISTORY_S
*history
= NULL
;
3600 if(ps_global
->restricted
){
3601 q_status_message(SM_ORDER
, 0, 3,
3602 "Alpine demo can't export messages to files");
3606 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
3609 export_opts
[i
= 0].ch
= ctrl('T');
3610 export_opts
[i
].rval
= 10;
3611 export_opts
[i
].name
= "^T";
3612 export_opts
[i
++].label
= N_("To Files");
3614 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3615 if(ps_global
->VAR_DOWNLOAD_CMD
&& ps_global
->VAR_DOWNLOAD_CMD
[0]){
3616 export_opts
[i
].ch
= ctrl('V');
3617 export_opts
[i
].rval
= 12;
3618 export_opts
[i
].name
= "^V";
3619 /* TRANSLATORS: this is an abbreviation for Download Messages */
3620 export_opts
[i
++].label
= N_("Downld Msg");
3622 #endif /* !(DOS || MAC) */
3624 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps_global
)){
3625 export_opts
[i
].ch
= ctrl('I');
3626 export_opts
[i
].rval
= 11;
3627 export_opts
[i
].name
= "TAB";
3628 export_opts
[i
++].label
= N_("Complete");
3632 /* Commented out since it's not yet support! */
3633 if(F_ON(F_ENABLE_SUB_LISTS
,ps_global
)){
3634 export_opts
[i
].ch
= ctrl('X');
3635 export_opts
[i
].rval
= 14;
3636 export_opts
[i
].name
= "^X";
3637 export_opts
[i
++].label
= N_("ListMatches");
3642 * If message has attachments, add a toggle that will allow the user
3643 * to save all of the attachments to a single directory, using the
3644 * names provided with the attachments or part names. What we'll do is
3645 * export the message as usual, and then export the attachments into
3646 * a subdirectory that did not exist before. The subdir will be named
3647 * something based on the name of the file being saved to, but a
3650 if(!MCMD_ISAGG(aopt
)
3651 && state
->mail_stream
3652 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
3653 && rawno
<= state
->mail_stream
->nmsgs
3654 && (env
= pine_mail_fetchstructure(state
->mail_stream
, rawno
, &b
))
3656 && b
->type
== TYPEMULTIPART
3658 && strucmp(b
->subtype
, "ALTERNATIVE") != 0){
3661 part
= b
->nested
.part
; /* 1st part */
3662 if(part
&& part
->next
)
3663 flags
|= GE_ALLPARTS
;
3666 export_opts
[i
].ch
= -1;
3669 if(mn_total_cur(msgmap
) <= 1L){
3670 snprintf(nmsgs
, sizeof(nmsgs
), "Msg #%ld", mn_get_cur(msgmap
));
3671 nmsgs
[sizeof(nmsgs
)-1] = '\0';
3674 snprintf(nmsgs
, sizeof(nmsgs
), "%s messages", comatose(mn_total_cur(msgmap
)));
3675 nmsgs
[sizeof(nmsgs
)-1] = '\0';
3678 r
= get_export_filename(state
, filename
, NULL
, full_filename
,
3679 sizeof(filename
), nmsgs
, "EXPORT",
3680 export_opts
, &rflags
, qline
, flags
, &history
);
3685 cmd_cancelled("Export message");
3689 q_status_message1(SM_ORDER
, 0, 2,
3690 _("Can't export to file outside of %s"),
3697 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3698 else if(r
== 12){ /* Download */
3699 char cmd
[MAXPATH
], *tfp
= NULL
;
3705 if(ps_global
->restricted
){
3706 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3707 "Download disallowed in restricted mode");
3712 tfp
= temp_nam(NULL
, "pd");
3713 build_updown_cmd(cmd
, sizeof(cmd
), ps_global
->VAR_DOWNLOAD_CMD_PREFIX
,
3714 ps_global
->VAR_DOWNLOAD_CMD
, tfp
);
3715 dprint((1, "Download cmd called: \"%s\"\n", cmd
));
3716 if((so
= so_get(FileStar
, tfp
, WRITE_ACCESS
|OWNER_ONLY
|WRITE_TO_LOCALE
)) != NULL
){
3717 gf_set_so_writec(&pc
, so
);
3719 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
)){
3720 if(!(state
->mail_stream
3721 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
3722 && rawno
<= state
->mail_stream
->nmsgs
3723 && (mc
= mail_elt(state
->mail_stream
, rawno
))
3727 if(!(env
= pine_mail_fetchstructure(state
->mail_stream
,
3728 mn_m2raw(msgmap
, i
), &b
))
3729 || !bezerk_delimiter(env
, mc
, pc
, next
++)
3730 || !format_message(mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
3731 env
, b
, NULL
, FM_NEW_MESS
| FM_NOWRAP
, pc
)){
3732 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3733 err
= "Error writing tempfile for download");
3738 gf_clear_so_writec(so
);
3739 if(so_give(&so
)){ /* close file */
3741 err
= "Error writing tempfile for download";
3745 if((syspipe
= open_system_pipe(cmd
, NULL
, NULL
,
3746 PIPE_USER
| PIPE_RESET
,
3747 0, pipe_callback
, pipe_report_error
)) != NULL
)
3748 (void) close_system_pipe(&syspipe
, NULL
, pipe_callback
);
3750 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3751 err
= _("Error running download command"));
3755 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3756 err
= "Error building temp file for download");
3760 fs_give((void **)&tfp
);
3764 q_status_message(SM_ORDER
, 0, 3, _("Download Command Completed"));
3768 #endif /* !(DOS || MAC) */
3771 if(rflags
& GER_APPEND
)
3776 dprint((5, "Opening file \"%s\" for export\n",
3777 full_filename
? full_filename
: "?"));
3779 if(!(store
= so_get(FileStar
, full_filename
, WRITE_ACCESS
|WRITE_TO_LOCALE
))){
3780 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
3781 /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
3782 _("Error opening file \"%s\" to export message: %s"),
3783 full_filename
, error_description(errno
));
3787 gf_set_so_writec(&pc
, store
);
3790 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
), count
++){
3791 env
= pine_mail_fetchstructure(state
->mail_stream
, mn_m2raw(msgmap
, i
),
3794 err
= _("Can't export message. Error accessing mail folder");
3799 if(!(state
->mail_stream
3800 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
3801 && rawno
<= state
->mail_stream
->nmsgs
3802 && (mc
= mail_elt(state
->mail_stream
, rawno
))
3806 start_of_append
= so_tell(store
);
3807 if(!bezerk_delimiter(env
, mc
, pc
, leading_nl
)
3808 || !format_message(mn_m2raw(msgmap
, i
), env
, b
, NULL
,
3809 FM_NEW_MESS
| FM_NOWRAP
, pc
)){
3810 orig_errno
= errno
; /* save incase things are really bad */
3811 failure
= 1; /* pop out of here */
3818 gf_clear_so_writec(store
);
3819 if(so_give(&store
)) /* release storage */
3823 our_truncate(full_filename
, (off_t
)start_of_append
);
3825 dprint((1, "FAILED Export: fetch(%ld): %s\n",
3826 i
, err
? err
: "?"));
3827 q_status_message(SM_ORDER
| SM_DING
, 3, 4, err
);
3830 dprint((1, "FAILED Export: file \"%s\" : %s\n",
3831 full_filename
? full_filename
: "?",
3832 error_description(orig_errno
)));
3833 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
3834 /* TRANSLATORS: Error exporting to <filename>: <error text> */
3835 _("Error exporting to \"%s\" : %s"),
3836 filename
, error_description(orig_errno
));
3840 if(rflags
& GER_ALLPARTS
&& full_filename
[0]){
3841 char dir
[MAXPATH
+1];
3842 char lfile
[MAXPATH
+1];
3843 int ok
= 0, tries
= 0, saved
= 0, errs
= 0, counter
= 2;
3847 * Now we want to save all of the attachments to a subdirectory.
3848 * To make it easier for us and probably easier for the user, and
3849 * to prevent the user from shooting himself in the foot, we
3850 * make a new subdirectory so that we can't possibly step on
3851 * any existing files, and we don't need any interaction with the
3852 * user while saving.
3854 * We'll just use the directory name full_filename.d or if that
3855 * already exists and isn't empty, we'll try adding a suffix to
3856 * that until we get something to use.
3859 if(strlen(full_filename
) + strlen(".d") + 1 > sizeof(dir
)){
3860 q_status_message1(SM_ORDER
| SM_DING
, 3, 4,
3861 _("Can't save attachments, filename too long: %s"),
3867 snprintf(dir
, sizeof(dir
), "%s.d", full_filename
);
3868 dir
[sizeof(dir
)-1] = '\0';
3872 switch(r
= is_writable_dir(dir
)){
3873 case 0: /* exists and is a writable dir */
3875 * We could figure out if it is empty and use it in
3876 * that case, but that sounds like a lot of work, so
3877 * just fall through to default.
3881 if(strlen(full_filename
) + strlen(".d") + 1 +
3882 1 + strlen(long2string((long) tries
)) > sizeof(dir
)){
3883 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3884 "Problem saving attachments");
3888 snprintf(dir
, sizeof(dir
), "%s.d_%s", full_filename
,
3889 long2string((long) tries
));
3890 dir
[sizeof(dir
)-1] = '\0';
3893 case 3: /* doesn't exist, that's good! */
3894 /* make new directory */
3898 } while(!ok
&& tries
< 1000);
3901 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3902 _("Problem saving attachments"));
3906 /* create the new directory */
3907 if(our_mkdir(dir
, 0700)){
3908 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
3909 _("Problem saving attachments: %s: %s"), dir
,
3910 error_description(errno
));
3914 if(!(state
->mail_stream
3915 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
3916 && rawno
<= state
->mail_stream
->nmsgs
3917 && (env
=pine_mail_fetchstructure(state
->mail_stream
,rawno
,&b
))
3919 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3920 _("Problem reading message"));
3924 zero_atmts(state
->atmts
);
3925 describe_mime(b
, "", 1, 1, 0, 0);
3928 if(a
&& a
->description
) /* skip main body part */
3931 for(; a
->description
!= NULL
; a
++){
3932 /* skip over these parts of the message */
3933 if(MIME_MSG_A(a
) || MIME_DGST_A(a
) || MIME_VCARD_A(a
))
3937 (void) get_filename_parameter(lfile
, sizeof(lfile
), a
->body
, NULL
);
3939 if(lfile
[0] == '\0'){ /* MAXPATH + 1 = sizeof(lfile) */
3940 snprintf(lfile
, sizeof(lfile
), "part_%.*s", MAXPATH
+1-6,
3941 a
->number
? a
->number
: "?");
3942 lfile
[sizeof(lfile
)-1] = '\0';
3945 if(strlen(dir
) + strlen(S_FILESEP
) + strlen(lfile
) + 1
3946 > sizeof(filename
)){
3948 "FAILED Att Export: name too long: %s\n",
3949 dir
, S_FILESEP
, lfile
));
3954 /* although files are being saved in a unique directory, there is
3955 * no guarantee that attachment names have unique names, so we have
3956 * to make sure that we are not constantly rewriting the same file name
3957 * over and over. In order to avoid this we test if the file already exists,
3958 * and if so, we write a counter name in the file name, just before the
3959 * extension of the file, and separate it with an underscore.
3961 snprintf(filename
, sizeof(filename
), "%s%s%s", dir
, S_FILESEP
, lfile
);
3962 filename
[sizeof(filename
)-1] = '\0';
3963 while((ok
= can_access(filename
, ACCESS_EXISTS
)) == 0 && errs
== 0){
3965 snprintf(filename
, sizeof(filename
), "%d", counter
);
3966 if(strlen(dir
) + strlen(S_FILESEP
) + strlen(lfile
) + strlen(filename
) + 2
3967 > sizeof(filename
)){
3969 "FAILED Att Export: name too long: %s\n",
3970 dir
, S_FILESEP
, lfile
));
3974 if((ext
= strrchr(lfile
, '.')) != NULL
)
3976 snprintf(filename
, sizeof(filename
), "%s%s%s%s%d%s%s",
3977 dir
, S_FILESEP
, lfile
,
3978 ext
? "_" : "", counter
++, ext
? "." : "", ext
? ext
+1 : "");
3979 filename
[sizeof(filename
)-1] = '\0';
3982 if(write_attachment_to_file(state
->mail_stream
, rawno
,
3983 a
, GER_NONE
, filename
) == 1)
3991 q_status_message1(SM_ORDER
, 3, 3,
3992 "Errors saving some attachments, %s attachments saved",
3993 long2string((long) saved
));
3995 q_status_message(SM_ORDER
, 3, 3,
3996 _("Problems saving attachments"));
4000 q_status_message2(SM_ORDER
, 0, 3,
4001 /* TRANSLATORS: Saved <how many> attachements to <directory name> */
4002 _("Saved %s attachments to %s"),
4003 long2string((long) saved
), dir
);
4005 q_status_message(SM_ORDER
, 3, 3, _("No attachments to save"));
4008 else if(mn_total_cur(msgmap
) > 1L)
4009 q_status_message4(SM_ORDER
,0,3,
4010 "%s message%s %s to file \"%s\"",
4011 long2string(count
), plural(count
),
4014 : rflags
& GER_APPEND
? "appended" : "exported",
4017 q_status_message3(SM_ORDER
,0,3,
4018 "Message %s %s to file \"%s\"",
4019 long2string(mn_get_cur(msgmap
)),
4022 : rflags
& GER_APPEND
? "appended" : "exported",
4028 if(MCMD_ISAGG(aopt
))
4029 restore_selected(msgmap
);
4036 * Ask user what file to export to. Export from srcstore to that file.
4038 * Args ps -- pine struct
4039 * srctext -- pointer to source text
4040 * srctype -- type of that source text
4041 * prompt_msg -- see get_export_filename
4044 * Returns: != 0 : error
4048 simple_export(struct pine
*ps
, void *srctext
, SourceType srctype
, char *prompt_msg
, char *lister_msg
)
4050 int r
= 1, rflags
= GER_NONE
;
4051 char filename
[MAXPATH
+1], full_filename
[MAXPATH
+1];
4052 STORE_S
*store
= NULL
;
4053 struct variable
*vars
= ps
? ps
->vars
: NULL
;
4054 static HISTORY_S
*history
= NULL
;
4055 static ESCKEY_S simple_export_opts
[] = {
4056 {ctrl('T'), 10, "^T", N_("To Files")},
4057 {-1, 0, NULL
, NULL
},
4058 {-1, 0, NULL
, NULL
}};
4060 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps
)){
4061 simple_export_opts
[r
].ch
= ctrl('I');
4062 simple_export_opts
[r
].rval
= 11;
4063 simple_export_opts
[r
].name
= "TAB";
4064 simple_export_opts
[r
].label
= N_("Complete");
4068 q_status_message(SM_ORDER
, 0, 2, _("Error allocating space"));
4073 simple_export_opts
[++r
].ch
= -1;
4075 full_filename
[0] = '\0';
4077 r
= get_export_filename(ps
, filename
, NULL
, full_filename
, sizeof(filename
),
4078 prompt_msg
, lister_msg
, simple_export_opts
, &rflags
,
4079 -FOOTER_ROWS(ps
), GE_IS_EXPORT
, &history
);
4083 else if(!full_filename
[0]){
4088 dprint((5, "Opening file \"%s\" for export\n",
4089 full_filename
? full_filename
: "?"));
4091 if((store
= so_get(FileStar
, full_filename
, WRITE_ACCESS
|WRITE_TO_LOCALE
)) != NULL
){
4095 gf_set_so_writec(&pc
, store
);
4096 gf_set_readc(&gc
, srctext
, (srctype
== CharStar
)
4097 ? strlen((char *)srctext
)
4101 if((pipe_err
= gf_pipe(gc
, pc
)) != NULL
){
4102 q_status_message2(SM_ORDER
| SM_DING
, 3, 3,
4103 /* TRANSLATORS: Problem saving to <filename>: <error text> */
4104 _("Problem saving to \"%s\": %s"),
4105 filename
, pipe_err
);
4111 gf_clear_so_writec(store
);
4112 if(so_give(&store
)){
4113 q_status_message2(SM_ORDER
| SM_DING
, 3, 3,
4114 _("Problem saving to \"%s\": %s"),
4115 filename
, error_description(errno
));
4120 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
4121 _("Error opening file \"%s\" for export: %s"),
4122 full_filename
, error_description(errno
));
4129 /* overloading full_filename */
4130 snprintf(full_filename
, sizeof(full_filename
), "%c%s",
4131 (prompt_msg
&& prompt_msg
[0])
4132 ? (islower((unsigned char)prompt_msg
[0])
4133 ? toupper((unsigned char)prompt_msg
[0]) : prompt_msg
[0])
4135 (prompt_msg
&& prompt_msg
[0]) ? prompt_msg
+1 : "ext");
4136 full_filename
[sizeof(full_filename
)-1] = '\0';
4137 q_status_message3(SM_ORDER
,0,2,"%s %s to \"%s\"",
4141 : rflags
& GER_APPEND
? "appended" : "exported",
4146 cmd_cancelled("Export");
4150 q_status_message1(SM_ORDER
, 0, 2,
4151 _("Can't export to file outside of %s"), VAR_OPER_DIR
);
4155 ps
->mangled_footer
= 1;
4161 * Ask user what file to export to.
4163 * filename -- On input, this is the filename to start with. On exit,
4164 * this is the filename chosen. (but this isn't used)
4165 * deefault -- This is the default value if user hits return. The
4166 * prompt will have [deefault] added to it automatically.
4167 * full_filename -- This is the full filename on exit.
4168 * len -- Minimum length of _both_ filename and full_filename.
4169 * prompt_msg -- Message to insert in prompt.
4170 * lister_msg -- Message to insert in file_lister.
4171 * opts -- Key options.
4172 * There is a tangled relationship between the callers
4173 * and this routine as far as opts are concerned. Some
4174 * of the opts are handled here. In particular, r == 3,
4175 * r == 10, r == 11, and r == 13 are all handled here.
4176 * Don't use those values unless you want what happens
4177 * here. r == 12 and others are handled by the caller.
4178 * rflags -- Return flags
4179 * GER_OVER - overwrite of existing file
4180 * GER_APPEND - append of existing file
4181 * else file did not exist before
4183 * GER_ALLPARTS - AllParts toggle was turned on
4185 * qline -- Command line to prompt on.
4186 * flags -- Logically OR'd flags
4187 * GE_IS_EXPORT - The command was an Export command
4188 * so the prompt should include
4190 * GE_SEQ_SENSITIVE - The command that got us here is
4191 * sensitive to sequence number changes
4192 * caused by unsolicited expunges.
4193 * GE_NO_APPEND - We will not allow append to an
4194 * existing file, only removal of the
4195 * file if it exists.
4196 * GE_IS_IMPORT - We are selecting for reading.
4197 * No overwriting or checking for
4198 * existence at all. Don't use this
4199 * together with GE_NO_APPEND.
4200 * GE_ALLPARTS - Turn on AllParts toggle.
4201 * GE_BINARY - Turn on Binary toggle.
4203 * Returns: -1 cancelled
4204 * -2 prohibited by VAR_OPER_DIR
4205 * -3 other error, already reported here
4207 * 12 user chose 12 command from opts
4210 get_export_filename(struct pine
*ps
, char *filename
, char *deefault
,
4211 char *full_filename
, size_t len
, char *prompt_msg
,
4212 char *lister_msg
, ESCKEY_S
*optsarg
, int *rflags
,
4213 int qline
, int flags
, HISTORY_S
**history
)
4215 char dir
[MAXPATH
+1], dir2
[MAXPATH
+1], orig_dir
[MAXPATH
+1];
4216 char precolon
[MAXPATH
+1], postcolon
[MAXPATH
+1];
4217 char filename2
[MAXPATH
+1], tmp
[MAXPATH
+1], *fn
, *ill
;
4218 int l
, i
, ku
= -1, kp
= -1, r
, fatal
, homedir
= 0, was_abs_path
=0, avail
, ret
= 0;
4219 int allparts
= 0, binary
= 0;
4220 char prompt_buf
[400];
4222 ESCKEY_S
*opts
= NULL
;
4223 struct variable
*vars
= ps
->vars
;
4224 static HISTORY_S
*dir_hist
= NULL
;
4226 int pos
, hist_len
= 0;
4229 /* we will fake a history with the ps_global->VAR_HISTORY variable
4230 * We fake that we combine this variable into a history variable
4231 * by stacking VAR_HISTORY on top of dir_hist. We keep track of this
4232 * by looking at the variable pos.
4234 if(ps_global
->VAR_HISTORY
!= NULL
)
4235 for(hist_len
= 0; ps_global
->VAR_HISTORY
[hist_len
]
4236 && ps_global
->VAR_HISTORY
[hist_len
][0]; hist_len
++)
4239 pos
= hist_len
+ items_in_hist(dir_hist
);
4241 if(flags
& GE_ALLPARTS
|| history
|| dir_hist
){
4243 * Copy the opts and add one to the end of the list.
4245 for(i
= 0; optsarg
[i
].ch
!= -1; i
++)
4248 if(dir_hist
|| hist_len
> 0)
4252 i
+= dir_hist
|| hist_len
> 0 ? 2 : 4;
4254 if(flags
& GE_ALLPARTS
)
4257 if(flags
& GE_BINARY
)
4260 opts
= (ESCKEY_S
*) fs_get((i
+1) * sizeof(*opts
));
4261 memset(opts
, 0, (i
+1) * sizeof(*opts
));
4263 for(i
= 0; optsarg
[i
].ch
!= -1; i
++){
4264 opts
[i
].ch
= optsarg
[i
].ch
;
4265 opts
[i
].rval
= optsarg
[i
].rval
;
4266 opts
[i
].name
= optsarg
[i
].name
; /* no need to make a copy */
4267 opts
[i
].label
= optsarg
[i
].label
; /* " */
4270 if(flags
& GE_ALLPARTS
){
4272 opts
[i
].ch
= ctrl('P');
4274 opts
[i
].name
= "^P";
4275 /* TRANSLATORS: Export all attachment parts */
4276 opts
[i
++].label
= N_("AllParts");
4279 if(flags
& GE_BINARY
){
4281 opts
[i
].ch
= ctrl('R');
4283 opts
[i
].name
= "^R";
4284 opts
[i
++].label
= N_("Binary");
4287 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf
,
4288 SIZEOF_20KBUF
, filename
);
4289 if(strcmp(tmp_20k_buf
, filename
)){
4290 opts
[i
].ch
= ctrl('N');
4292 opts
[i
].name
= "^N";
4293 opts
[i
++].label
= "Name UTF8";
4296 if(dir_hist
|| hist_len
> 0){
4297 opts
[i
].ch
= ctrl('Y');
4301 opts
[i
++].label
= "";
4303 opts
[i
].ch
= ctrl('V');
4306 opts
[i
++].label
= "";
4310 opts
[i
].ch
= KEY_UP
;
4314 opts
[i
++].label
= "";
4316 opts
[i
].ch
= KEY_DOWN
;
4319 opts
[i
++].label
= "";
4325 init_hist(history
, HISTSIZE
);
4326 init_hist(&dir_hist
, HISTSIZE
); /* reset history to the end */
4334 if(F_ON(F_USE_CURRENT_DIR
, ps
))
4336 else if(VAR_OPER_DIR
){
4337 strncpy(dir
, VAR_OPER_DIR
, sizeof(dir
));
4338 dir
[sizeof(dir
)-1] = '\0';
4340 #if defined(DOS) || defined(OS2)
4341 else if(VAR_FILE_DIR
){
4342 strncpy(dir
, VAR_FILE_DIR
, sizeof(dir
));
4343 dir
[sizeof(dir
)-1] = '\0';
4351 strncpy(orig_dir
, dir
, sizeof(orig_dir
));
4352 orig_dir
[sizeof(orig_dir
)-1] = '\0';
4354 postcolon
[0] = '\0';
4355 strncpy(precolon
, dir
, sizeof(precolon
));
4356 precolon
[sizeof(precolon
)-1] = '\0';
4358 strncpy(def
, deefault
, sizeof(def
)-1);
4359 def
[sizeof(def
)-1] = '\0';
4360 removing_leading_and_trailing_white_space(def
);
4365 avail
= MAX(20, ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80) - MIN_OPT_ENT_WIDTH
;
4367 /*---------- Prompt the user for the file name -------------*/
4370 char dirb
[50], fileb
[50];
4371 int l1
, l2
, l3
, l4
, l5
, needed
;
4372 char *p
, p1
[100], p2
[100], *p3
, p4
[100], p5
[100];
4374 snprintf(p1
, sizeof(p1
), "%sCopy ",
4375 (flags
& GE_IS_EXPORT
) ? "EXPORT: " :
4376 (flags
& GE_IS_IMPORT
) ? "IMPORT: " : "SAVE: ");
4377 p1
[sizeof(p1
)-1] = '\0';
4380 strncpy(p2
, prompt_msg
? prompt_msg
: "", sizeof(p2
)-1);
4381 p2
[sizeof(p2
)-1] = '\0';
4384 if(rflags
&& *rflags
& GER_ALLPARTS
)
4391 snprintf(p4
, sizeof(p4
), " %s file%s%s",
4392 (flags
& GE_IS_IMPORT
) ? "from" : "to",
4393 is_absolute_path(filename
) ? "" : " in ",
4394 is_absolute_path(filename
) ? "" :
4395 (!dir
[0] ? "current directory"
4396 : (dir
[0] == '~' && !dir
[1]) ? "home directory"
4397 : short_str(dir
,dirb
,sizeof(dirb
),30,FrontDots
)));
4398 p4
[sizeof(p4
)-1] = '\0';
4401 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4403 *def
? short_str(def
,fileb
,sizeof(fileb
),40,EndDots
) : "",
4405 p5
[sizeof(p5
)-1] = '\0';
4408 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0){
4409 snprintf(p4
, sizeof(p4
), " %s file%s%s",
4410 (flags
& GE_IS_IMPORT
) ? "from" : "to",
4411 is_absolute_path(filename
) ? "" : " in ",
4412 is_absolute_path(filename
) ? "" :
4413 (!dir
[0] ? "current dir"
4414 : (dir
[0] == '~' && !dir
[1]) ? "home dir"
4415 : short_str(dir
,dirb
,sizeof(dirb
),10,FrontDots
)));
4416 p4
[sizeof(p4
)-1] = '\0';
4420 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l5
> 0){
4421 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4423 *def
? short_str(def
,fileb
,sizeof(fileb
),
4424 MAX(15,l5
-5-needed
),EndDots
) : "",
4426 p5
[sizeof(p5
)-1] = '\0';
4430 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l2
> 0){
4433 * 14 is about the shortest we can make this, because there are
4434 * fixed length strings of length 14 coming in here.
4436 p
= short_str(prompt_msg
, p2
, sizeof(p2
), MAX(14,l2
-needed
), FrontDots
);
4438 strncpy(p2
, p
, sizeof(p2
)-1);
4439 p2
[sizeof(p2
)-1] = '\0';
4445 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0){
4446 strncpy(p1
, "Copy ", sizeof(p1
)-1);
4447 p1
[sizeof(p1
)-1] = '\0';
4451 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l5
> 0){
4452 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4454 *def
? short_str(def
,fileb
, sizeof(fileb
),
4455 MAX(10,l5
-5-needed
),EndDots
) : "",
4457 p5
[sizeof(p5
)-1] = '\0';
4461 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l3
> 0){
4462 if(needed
<= l3
- strlen(" (+ atts)"))
4464 else if(needed
<= l3
- strlen(" (atts)"))
4466 else if(needed
<= l3
- strlen(" (+)"))
4468 else if(needed
<= l3
- strlen("+"))
4476 snprintf(prompt_buf
, sizeof(prompt_buf
), "%s%s%s%s%s", p1
, p2
, p3
, p4
, p5
);
4477 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4480 if(items_in_hist(dir_hist
) > 0 || hist_len
> 0){ /* any directories */
4481 opts
[kp
].name
= "^Y";
4482 opts
[kp
].label
= "Prev Dir";
4483 opts
[kp
+1].name
= "^V";
4484 opts
[kp
+1].label
= "Next Dir";
4488 opts
[kp
].label
= "";
4489 opts
[kp
+1].name
= "";
4490 opts
[kp
+1].label
= "";
4495 if(items_in_hist(*history
) > 0){
4496 opts
[ku
].name
= HISTORY_UP_KEYNAME
;
4497 opts
[ku
].label
= HISTORY_KEYLABEL
;
4498 opts
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
4499 opts
[ku
+1].label
= HISTORY_KEYLABEL
;
4503 opts
[ku
].label
= "";
4504 opts
[ku
+1].name
= "";
4505 opts
[ku
+1].label
= "";
4509 oeflags
= OE_APPEND_CURRENT
|
4510 ((flags
& GE_SEQ_SENSITIVE
) ? OE_SEQ_SENSITIVE
: 0);
4511 r
= optionally_enter(filename
, qline
, 0, len
, prompt_buf
,
4512 opts
, NO_HELP
, &oeflags
);
4514 dprint((2, "\n - export_filename = \"%s\", r = %d -\n", filename
, r
));
4518 * Helps may not be right if you add another caller or change
4519 * things. Check it out.
4521 if(flags
& GE_IS_IMPORT
)
4522 helper(h_ge_import
, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE
);
4523 else if(flags
& GE_ALLPARTS
)
4524 helper(h_ge_allparts
, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE
);
4526 helper(h_ge_export
, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE
);
4528 ps
->mangled_screen
= 1;
4532 else if(r
== 10 || r
== 11){ /* Browser or File Completion */
4533 if(filename
[0]=='~'){
4534 if(filename
[1] == C_FILESEP
&& filename
[2]!='\0'){
4537 for(i
=0; filename
[i
+2] != '\0' && i
+2 < len
-1; i
++)
4538 filename
[i
] = filename
[i
+2];
4540 strncpy(dir
, precolon
, sizeof(dir
)-1);
4541 dir
[sizeof(dir
)-1] = '\0';
4543 else if(filename
[1]=='\0' ||
4544 (filename
[1] == C_FILESEP
&& filename
[2] == '\0')){
4548 strncpy(dir
, precolon
, sizeof(dir
)-1);
4549 dir
[sizeof(dir
)-1] = '\0';
4552 else if(!dir
[0] && !is_absolute_path(filename
) && was_abs_path
){
4556 strncpy(dir
, precolon
, sizeof(dir
)-1);
4557 dir
[sizeof(dir
)-1] = '\0';
4566 strncpy(tmp
, filename
, sizeof(tmp
)-1);
4567 tmp
[sizeof(tmp
)-1] = '\0';
4568 if(*tmp
&& is_absolute_path(tmp
))
4569 fnexpand(tmp
, sizeof(tmp
));
4570 if(strncmp(tmp
,postcolon
, strlen(postcolon
)))
4571 postcolon
[0] = '\0';
4573 if(*tmp
&& (fn
= last_cmpnt(tmp
))){
4575 strncpy(filename2
, fn
, sizeof(filename2
)-1);
4576 filename2
[sizeof(filename2
)-1] = '\0';
4577 if(is_absolute_path(tmp
)){
4578 strncpy(dir2
, tmp
, MIN(fn
- tmp
, sizeof(dir2
)-1));
4579 dir2
[MIN(fn
- tmp
, sizeof(dir2
)-1)] = '\0';
4581 if(tmp
[1]==':' && tmp
[2]=='\\' && dir2
[2]=='\0'){
4586 strncpy(postcolon
, dir2
, sizeof(postcolon
)-1);
4587 postcolon
[sizeof(postcolon
)-1] = '\0';
4593 * Just building the directory name in dir2,
4594 * full_filename is overloaded.
4596 snprintf(full_filename
, len
, "%.*s", (int) MIN(fn
-tmp
,len
-1), tmp
);
4597 full_filename
[len
-1] = '\0';
4598 strncpy(postcolon
, full_filename
, sizeof(postcolon
)-1);
4599 postcolon
[sizeof(postcolon
)-1] = '\0';
4600 build_path(dir2
, !dir
[0] ? p
= (char *)getcwd(NULL
,MAXPATH
)
4601 : (dir
[0] == '~' && !dir
[1])
4604 full_filename
, sizeof(dir2
));
4610 if(is_absolute_path(tmp
)){
4611 strncpy(dir2
, tmp
, sizeof(dir2
)-1);
4612 dir2
[sizeof(dir2
)-1] = '\0';
4614 if(dir2
[2]=='\0' && dir2
[1]==':'){
4617 strncpy(postcolon
,dir2
,sizeof(postcolon
)-1);
4618 postcolon
[sizeof(postcolon
)-1] = '\0';
4621 filename2
[0] = '\0';
4625 strncpy(filename2
, tmp
, sizeof(filename2
)-1);
4626 filename2
[sizeof(filename2
)-1] = '\0';
4628 if(getcwd(dir2
, sizeof(dir2
)) == NULL
)
4629 alpine_panic(_("getcwd() call failed at get_export_filename"));
4631 else if(dir
[0] == '~' && !dir
[1]){
4632 strncpy(dir2
, ps
->home_dir
, sizeof(dir2
)-1);
4633 dir2
[sizeof(dir2
)-1] = '\0';
4636 strncpy(dir2
, dir
, sizeof(dir2
)-1);
4637 dir2
[sizeof(dir2
)-1] = '\0';
4640 postcolon
[0] = '\0';
4644 build_path(full_filename
, dir2
, filename2
, len
);
4645 if(!strcmp(full_filename
, dir2
))
4646 filename2
[0] = '\0';
4647 if(full_filename
[strlen(full_filename
)-1] == C_FILESEP
4648 && isdir(full_filename
,NULL
,NULL
)){
4649 if(strlen(full_filename
) == 1)
4650 strncpy(postcolon
, full_filename
, sizeof(postcolon
)-1);
4651 else if(filename2
[0])
4652 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4653 postcolon
[sizeof(postcolon
)-1] = '\0';
4654 strncpy(dir2
, full_filename
, sizeof(dir2
)-1);
4655 dir2
[sizeof(dir2
)-1] = '\0';
4656 filename2
[0] = '\0';
4658 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4659 else if(full_filename
[strlen(full_filename
)-1] == C_FILESEP
){
4660 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4661 postcolon
[sizeof(postcolon
)-1] = '\0';
4662 strncpy(dir2
, full_filename
, sizeof(dir2
)-1);
4663 dir2
[sizeof(dir2
)-1] = '\0';
4664 filename2
[0] = '\0';
4667 if(dir2
[strlen(dir2
)-1] == C_FILESEP
&& strlen(dir2
)!=1
4668 && strcmp(dir2
+1, ":\\"))
4669 /* last condition to prevent stripping of '\\'
4670 in windows partition */
4671 dir2
[strlen(dir2
)-1] = '\0';
4673 if(r
== 10){ /* File Browser */
4674 r
= file_lister(lister_msg
? lister_msg
: "EXPORT",
4675 dir2
, sizeof(dir2
), filename2
, sizeof(filename2
),
4677 (flags
& GE_IS_IMPORT
) ? FB_READ
: FB_SAVE
);
4679 /* Windows has a special "feature" in which entering the file browser will
4680 change the working directory if the directory is changed at all (even
4681 clicking "Cancel" will change the working directory).
4683 if(F_ON(F_USE_CURRENT_DIR
, ps
))
4684 (void)getcwd(dir2
,sizeof(dir2
));
4686 if(isdir(dir2
,NULL
,NULL
)){
4687 strncpy(precolon
, dir2
, sizeof(precolon
)-1);
4688 precolon
[sizeof(precolon
)-1] = '\0';
4690 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4691 postcolon
[sizeof(postcolon
)-1] = '\0';
4693 build_path(full_filename
, dir2
, filename2
, len
);
4694 if(isdir(full_filename
, NULL
, NULL
)){
4695 strncpy(dir
, full_filename
, sizeof(dir
)-1);
4696 dir
[sizeof(dir
)-1] = '\0';
4700 fn
= last_cmpnt(full_filename
);
4701 strncpy(dir
, full_filename
,
4702 MIN(fn
- full_filename
, sizeof(dir
)-1));
4703 dir
[MIN(fn
- full_filename
, sizeof(dir
)-1)] = '\0';
4704 if(fn
- full_filename
> 1)
4705 dir
[fn
- full_filename
- 1] = '\0';
4708 if(!strcmp(dir
, ps
->home_dir
)){
4713 strncpy(filename
, fn
, len
-1);
4714 filename
[len
-1] = '\0';
4717 else{ /* File Completion */
4718 if(!pico_fncomplete(dir2
, filename2
, sizeof(filename2
)))
4720 strncat(postcolon
, filename2
,
4721 sizeof(postcolon
)-1-strlen(postcolon
));
4722 postcolon
[sizeof(postcolon
)-1] = '\0';
4724 was_abs_path
= is_absolute_path(filename
);
4726 if(!strcmp(dir
, ps
->home_dir
)){
4731 strncpy(filename
, postcolon
, len
-1);
4732 filename
[len
-1] = '\0';
4733 strncpy(dir
, precolon
, sizeof(dir
)-1);
4734 dir
[sizeof(dir
)-1] = '\0';
4736 if(filename
[0] == '~' && !filename
[1]){
4744 else if(r
== 12){ /* Download, caller handles it */
4748 else if(r
== 13){ /* toggle AllParts bit */
4750 if(*rflags
& GER_ALLPARTS
){
4751 *rflags
&= ~GER_ALLPARTS
;
4752 opts
[allparts
].label
= N_("AllParts");
4755 *rflags
|= GER_ALLPARTS
;
4756 /* opposite of All Parts, No All Parts */
4757 opts
[allparts
].label
= N_("NoAllParts");
4764 else if(r
== 14){ /* List file names matching partial? */
4768 else if(r
== 15){ /* toggle Binary bit */
4770 if(*rflags
& GER_BINARY
){
4771 *rflags
&= ~GER_BINARY
;
4772 opts
[binary
].label
= N_("Binary");
4775 *rflags
|= GER_BINARY
;
4776 opts
[binary
].label
= N_("No Binary");
4782 else if(r
== 1){ /* Cancel */
4789 else if(r
>= 30 && r
<= 33){
4792 if(r
== 30 || r
== 31){
4795 p
= get_prev_hist(*history
, filename
, 0, NULL
);
4797 p
= get_next_hist(*history
, filename
, 0, NULL
);
4801 if(r
== 32 || r
== 33){
4802 int nitems
= items_in_hist(dir_hist
);
4803 if(dir_hist
|| hist_len
> 0){
4806 p
= hist_in_pos(--pos
, ps_global
->VAR_HISTORY
, hist_len
, dir_hist
, nitems
);
4810 if(pos
< hist_len
+ nitems
)
4811 p
= hist_in_pos(++pos
, ps_global
->VAR_HISTORY
, hist_len
, dir_hist
, nitems
);
4813 if(p
== NULL
|| *p
== '\0')
4817 last
= p
; /* save it! */
4819 if(p
!= NULL
&& *p
!= '\0'){
4820 if(r
== 30 || r
== 31){
4821 if((fn
= last_cmpnt(p
)) != NULL
){
4822 strncpy(dir
, p
, MIN(fn
- p
, sizeof(dir
)-1));
4823 dir
[MIN(fn
- p
, sizeof(dir
)-1)] = '\0';
4825 dir
[fn
- p
- 1] = '\0';
4826 strncpy(filename
, fn
, len
-1);
4827 filename
[len
-1] = '\0';
4829 } else { /* r == 32 || r == 33 */
4830 strncpy(dir
, p
, sizeof(dir
)-1);
4831 dir
[sizeof(dir
)-1] = '\0';
4834 if(!strcmp(dir
, ps
->home_dir
)){
4844 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf
,
4845 SIZEOF_20KBUF
, filename
);
4846 strncpy(filename
, tmp_20k_buf
, len
);
4847 filename
[len
-1] = '\0';
4855 removing_leading_and_trailing_white_space(filename
);
4858 if(!*def
){ /* Cancel */
4863 strncpy(filename
, def
, len
-1);
4864 filename
[len
-1] = '\0';
4867 #if defined(DOS) || defined(OS2)
4868 if(is_absolute_path(filename
)){
4869 fixpath(filename
, len
);
4872 if(filename
[0] == '~'){
4873 if(fnexpand(filename
, len
) == NULL
){
4874 char *p
= strindex(filename
, '/');
4877 q_status_message1(SM_ORDER
| SM_DING
, 3, 3,
4878 _("Error expanding file name: \"%s\" unknown user"),
4885 if(is_absolute_path(filename
)){
4886 strncpy(full_filename
, filename
, len
-1);
4887 full_filename
[len
-1] = '\0';
4891 build_path(full_filename
, (char *)getcwd(dir
,sizeof(dir
)),
4893 else if(dir
[0] == '~' && !dir
[1])
4894 build_path(full_filename
, ps
->home_dir
, filename
, len
);
4896 build_path(full_filename
, dir
, filename
, len
);
4899 if((ill
= filter_filename(full_filename
, &fatal
,
4900 ps_global
->restricted
|| ps_global
->VAR_OPER_DIR
)) != NULL
){
4902 q_status_message1(SM_ORDER
| SM_DING
, 3, 3, "%s", ill
);
4906 /* BUG: we should beep when the key's pressed rather than bitch later */
4907 /* Warn and ask for confirmation. */
4908 snprintf(prompt_buf
, sizeof(prompt_buf
), "File name contains a '%s'. %s anyway",
4909 ill
, (flags
& GE_IS_EXPORT
) ? "Export" : "Save");
4910 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4911 if(want_to(prompt_buf
, 'n', 0, NO_HELP
,
4912 ((flags
& GE_SEQ_SENSITIVE
) ? RB_SEQ_SENSITIVE
: 0)) != 'y')
4917 break; /* Must have got an OK file name */
4920 if(VAR_OPER_DIR
&& !in_dir(VAR_OPER_DIR
, full_filename
)){
4925 if(!can_access(full_filename
, ACCESS_EXISTS
)){
4927 static ESCKEY_S access_opts
[] = {
4928 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4929 a file or append to the end of the file */
4930 {'o', 'o', "O", N_("Overwrite")},
4931 {'a', 'a', "A", N_("Append")},
4932 {-1, 0, NULL
, NULL
}};
4934 rbflags
= RB_NORM
| ((flags
& GE_SEQ_SENSITIVE
) ? RB_SEQ_SENSITIVE
: 0);
4936 if(flags
& GE_NO_APPEND
){
4937 r
= strlen(filename
);
4938 snprintf(prompt_buf
, sizeof(prompt_buf
),
4939 /* TRANSLATORS: asking user whether to overwrite a file or not,
4940 File <filename> already exists. Overwrite it ? */
4941 _("File \"%s%s\" already exists. Overwrite it "),
4942 (r
> 20) ? "..." : "",
4943 filename
+ ((r
> 20) ? r
- 20 : 0));
4944 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4945 if(want_to(prompt_buf
, 'n', 'x', NO_HELP
, rbflags
) == 'y'){
4947 *rflags
|= GER_OVER
;
4949 if(our_unlink(full_filename
) < 0){
4950 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
4951 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4952 _("Cannot remove old %s: %s"),
4953 full_filename
, error_description(errno
));
4961 else if(!(flags
& GE_IS_IMPORT
)){
4962 r
= strlen(filename
);
4963 snprintf(prompt_buf
, sizeof(prompt_buf
),
4964 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4965 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4966 (r
> 20) ? "..." : "",
4967 filename
+ ((r
> 20) ? r
- 20 : 0));
4968 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4969 switch(radio_buttons(prompt_buf
, -FOOTER_ROWS(ps_global
),
4970 access_opts
, 'a', 'x', NO_HELP
, rbflags
)){
4973 *rflags
|= GER_OVER
;
4975 if(our_truncate(full_filename
, (off_t
)0) < 0)
4976 /* trouble truncating, but we'll give it a try anyway */
4977 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
4978 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
4979 _("Warning: Cannot truncate old %s: %s"),
4980 full_filename
, error_description(errno
));
4985 *rflags
|= GER_APPEND
;
4998 if(history
&& ret
== 0){
4999 save_hist(*history
, full_filename
, 0, NULL
);
5000 strncpy(tmp
, full_filename
, MAXPATH
);
5001 tmp
[MAXPATH
] = '\0';
5002 if((fn
= strrchr(tmp
, C_FILESEP
)) != NULL
)
5007 save_hist(dir_hist
, tmp
, 0, NULL
);
5010 if(opts
&& opts
!= optsarg
)
5011 fs_give((void **) &opts
);
5017 /*----------------------------------------------------------------------
5018 parse the config'd upload/download command
5020 Args: cmd -- buffer to return command fit for shellin'
5023 fname -- file name to build into the command
5025 Returns: pointer to cmd_str buffer or NULL on real bad error
5027 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
5028 cfg_str is written to standard out right before a successful
5029 return of this function. The call immediately following this
5030 function darn well better be the shell exec...
5033 build_updown_cmd(char *cmd
, size_t cmdlen
, char *prefix
, char *cfg_str
, char *fname
)
5036 int fname_found
= 0;
5038 if(prefix
&& *prefix
){
5039 /* loop thru replacing all occurances of _FILE_ */
5040 p
= strncpy(cmd
, prefix
, cmdlen
);
5041 cmd
[cmdlen
-1] = '\0';
5042 while((p
= strstr(p
, "_FILE_")))
5043 rplstr(p
, cmdlen
-(p
-cmd
), 6, fname
);
5048 /* loop thru replacing all occurances of _FILE_ */
5049 p
= strncpy(cmd
, cfg_str
, cmdlen
);
5050 cmd
[cmdlen
-1] = '\0';
5051 while((p
= strstr(p
, "_FILE_"))){
5052 rplstr(p
, cmdlen
-(p
-cmd
), 6, fname
);
5057 snprintf(cmd
+strlen(cmd
), cmdlen
-strlen(cmd
), " %s", fname
);
5059 cmd
[cmdlen
-1] = '\0';
5061 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
5067 /*----------------------------------------------------------------------
5068 Write a berzerk format message delimiter using the given putc function
5070 Args: e -- envelope of message to write
5071 pc -- function to use
5073 Returns: TRUE if we could write it, FALSE if there was a problem
5075 NOTE: follows delimiter with OS-dependent newline
5078 bezerk_delimiter(ENVELOPE
*env
, MESSAGECACHE
*mc
, gf_io_t pc
, int leading_newline
)
5084 /* write "[\n]From mailbox[@host] " */
5085 if(!((leading_newline
? gf_puts(NEWLINE
, pc
) : 1)
5086 && gf_puts("From ", pc
)
5087 && gf_puts((env
&& env
->from
) ? env
->from
->mailbox
5088 : "the-concourse-on-high", pc
)
5089 && gf_puts((env
&& env
->from
&& env
->from
->host
) ? "@" : "", pc
)
5090 && gf_puts((env
&& env
->from
&& env
->from
->host
) ? env
->from
->host
5096 when
= mail_longdate(mc
);
5097 else if(env
&& env
->date
&& env
->date
[0]
5098 && mail_parse_date(&telt
,env
->date
))
5099 when
= mail_longdate(&telt
);
5105 while(p
&& *p
&& *p
!= '\n') /* write date */
5109 if(!gf_puts(NEWLINE
, pc
)) /* write terminating newline */
5116 /*----------------------------------------------------------------------
5117 Execute command to jump to a given message number
5119 Args: qline -- Line to ask question on
5121 Result: returns true if the use selected a new message, false otherwise
5125 jump_to(MSGNO_S
*msgmap
, int qline
, UCS first_num
, SCROLL_S
*sparms
, CmdWhere in_index
)
5127 char jump_num_string
[80], *j
, prompt
[70];
5130 static ESCKEY_S jump_to_key
[] = { {0, 0, NULL
, NULL
},
5131 /* TRANSLATORS: go to First Message */
5132 {ctrl('Y'), 10, "^Y", N_("First Msg")},
5133 {ctrl('V'), 11, "^V", N_("Last Msg")},
5134 {-1, 0, NULL
, NULL
} };
5136 dprint((4, "\n - jump_to -\n"));
5139 if(sparms
&& sparms
->jump_is_debug
)
5140 return(get_level(qline
, first_num
, sparms
));
5143 if(!any_messages(msgmap
, NULL
, "to Jump to"))
5146 if(first_num
&& first_num
< 0x80 && isdigit((unsigned char) first_num
)){
5147 jump_num_string
[0] = first_num
;
5148 jump_num_string
[1] = '\0';
5151 jump_num_string
[0] = '\0';
5153 if(mn_total_cur(msgmap
) > 1L){
5154 snprintf(prompt
, sizeof(prompt
), "Unselect %s msgs in favor of number to be entered",
5155 comatose(mn_total_cur(msgmap
)));
5156 prompt
[sizeof(prompt
)-1] = '\0';
5157 if((rc
= want_to(prompt
, 'n', 0, NO_HELP
, WT_NORM
)) == 'n')
5161 snprintf(prompt
, sizeof(prompt
), "%s number to jump to : ", in_index
== ThrdIndx
5164 prompt
[sizeof(prompt
)-1] = '\0';
5168 int flags
= OE_APPEND_CURRENT
;
5170 rc
= optionally_enter(jump_num_string
, qline
, 0,
5171 sizeof(jump_num_string
), prompt
,
5172 jump_to_key
, help
, &flags
);
5174 help
= help
== NO_HELP
5175 ? (in_index
== ThrdIndx
? h_oe_jump_thd
: h_oe_jump
)
5179 else if(rc
== 10 || rc
== 11){
5183 closest
= closest_jump_target(rc
== 10 ? 1L
5184 : ((in_index
== ThrdIndx
)
5185 ? msgmap
->max_thrdno
5186 : mn_get_total(msgmap
)),
5187 ps_global
->mail_stream
,
5189 in_index
, warning
, sizeof(warning
));
5190 /* ignore warning */
5195 * If we take out the *jump_num_string nonempty test in this if
5196 * then the closest_jump_target routine will offer a jump to the
5197 * last message. However, it is slow because you have to wait for
5198 * the status message and it is annoying for people who hit J command
5199 * by mistake and just want to hit return to do nothing, like has
5200 * always worked. So the test is there for now. Hubert 2002-08-19
5202 * Jumping to first/last message is now possible through ^Y/^V
5203 * commands above. jpf 2002-08-21
5204 * (and through "end" hubert 2006-07-07)
5206 if(rc
== 0 && *jump_num_string
!= '\0'){
5207 removing_leading_and_trailing_white_space(jump_num_string
);
5208 for(j
=jump_num_string
; isdigit((unsigned char)*j
) || *j
=='-'; j
++)
5212 if(!strucmp("end", j
))
5213 return((in_index
== ThrdIndx
) ? msgmap
->max_thrdno
: mn_get_total(msgmap
));
5215 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5216 _("Invalid number entered. Use only digits 0-9"));
5217 jump_num_string
[0] = '\0';
5221 long closest
, jump_num
;
5223 if(*jump_num_string
)
5224 jump_num
= atol(jump_num_string
);
5229 closest
= closest_jump_target(jump_num
, ps_global
->mail_stream
,
5231 *jump_num_string
? 0 : 1,
5232 in_index
, warning
, sizeof(warning
));
5234 q_status_message(SM_ORDER
| SM_DING
, 2, 2, warning
);
5236 if(closest
== jump_num
)
5240 jump_num_string
[0] = '\0';
5242 strncpy(jump_num_string
, long2string(closest
),
5243 sizeof(jump_num_string
));
5258 * cmd_delete_action - handle msgno advance and such after single message deletion
5261 cmd_delete_action(struct pine
*state
, MSGNO_S
*msgmap
, CmdWhere in_index
)
5267 msgno
= mn_get_cur(msgmap
);
5268 advance_cur_after_delete(state
, state
->mail_stream
, msgmap
, in_index
);
5270 if(IS_NEWS(state
->mail_stream
)
5271 || ((state
->context_current
->use
& CNTXT_INCMNG
)
5272 && context_isambig(state
->cur_folder
))){
5274 opts
= (NSF_TRUST_FLAGS
| NSF_SKIP_CHID
);
5275 if(in_index
== View
)
5276 opts
&= ~NSF_SKIP_CHID
;
5278 (void)next_sorted_flagged(F_UNDEL
|F_UNSEEN
, state
->mail_stream
, msgno
, &opts
);
5279 if(!(opts
& NSF_FLAG_MATCH
)){
5280 char nextfolder
[MAXPATH
];
5282 strncpy(nextfolder
, state
->cur_folder
, sizeof(nextfolder
));
5283 nextfolder
[sizeof(nextfolder
)-1] = '\0';
5284 rv
= next_folder(NULL
, nextfolder
, sizeof(nextfolder
), nextfolder
,
5285 state
->context_current
, NULL
, NULL
)
5286 ? ". Press TAB for next folder."
5287 : ". No more folders to TAB to.";
5296 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5299 cmd_delete_index(struct pine
*state
, MSGNO_S
*msgmap
)
5301 return(cmd_delete_action(state
, msgmap
,MsgIndx
));
5305 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5308 cmd_delete_view(struct pine
*state
, MSGNO_S
*msgmap
)
5310 return(cmd_delete_action(state
, msgmap
, View
));
5315 advance_cur_after_delete(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, CmdWhere in_index
)
5317 long new_msgno
, msgno
;
5320 new_msgno
= msgno
= mn_get_cur(msgmap
);
5321 opts
= NSF_TRUST_FLAGS
;
5323 if(F_ON(F_DEL_SKIPS_DEL
, state
)){
5325 if(THREADING() && sp_viewing_a_thread(stream
))
5326 opts
|= NSF_SKIP_CHID
;
5328 new_msgno
= next_sorted_flagged(F_UNDEL
, stream
, msgno
, &opts
);
5331 mn_inc_cur(stream
, msgmap
,
5332 (in_index
== View
&& THREADING()
5333 && sp_viewing_a_thread(stream
))
5335 : (in_index
== View
)
5336 ? MH_ANYTHD
: MH_NONE
);
5337 new_msgno
= mn_get_cur(msgmap
);
5338 if(new_msgno
!= msgno
)
5339 opts
|= NSF_FLAG_MATCH
;
5343 * Viewing_a_thread is the complicated case because we want to ignore
5344 * other threads at first and then look in other threads if we have to.
5345 * By ignoring other threads we also ignore collapsed partial threads
5346 * in our own thread.
5348 if(THREADING() && sp_viewing_a_thread(stream
) && !(opts
& NSF_FLAG_MATCH
)){
5349 long rawno
, orig_thrdno
;
5350 PINETHRD_S
*thrd
, *topthrd
= NULL
;
5352 rawno
= mn_m2raw(msgmap
, msgno
);
5353 thrd
= fetch_thread(stream
, rawno
);
5354 if(thrd
&& thrd
->top
)
5355 topthrd
= fetch_thread(stream
, thrd
->top
);
5357 orig_thrdno
= topthrd
? topthrd
->thrdno
: -1L;
5359 opts
= NSF_TRUST_FLAGS
;
5360 new_msgno
= next_sorted_flagged(F_UNDEL
, stream
, msgno
, &opts
);
5363 * If we got a match, new_msgno may be a message in
5364 * a different thread from the one we are viewing, or it could be
5365 * in a collapsed part of this thread.
5367 if(opts
& NSF_FLAG_MATCH
){
5372 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
,new_msgno
));
5373 if(thrd
&& thrd
->top
)
5374 topthrd
= fetch_thread(stream
, thrd
->top
);
5377 * If this match is in the same thread we're already in
5378 * then we're done, else we have to ask the user and maybe
5381 if(!(orig_thrdno
> 0L && topthrd
5382 && topthrd
->thrdno
== orig_thrdno
)){
5384 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD
, state
)){
5385 if(in_index
== View
)
5386 snprintf(pmt
, sizeof(pmt
),
5387 "View message in thread number %.10s",
5388 topthrd
? comatose(topthrd
->thrdno
) : "?");
5390 snprintf(pmt
, sizeof(pmt
), "View thread number %.10s",
5391 topthrd
? comatose(topthrd
->thrdno
) : "?");
5393 ret
= want_to(pmt
, 'y', 'x', NO_HELP
, WT_NORM
);
5399 unview_thread(state
, stream
, msgmap
);
5400 mn_set_cur(msgmap
, new_msgno
);
5402 && (count_lflags_in_thread(stream
, topthrd
, msgmap
,
5404 && view_thread(state
, stream
, msgmap
, 1)){
5405 if(current_index_state
)
5406 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
5408 state
->view_skipped_index
= 1;
5409 state
->next_screen
= mail_view_screen
;
5412 view_thread(state
, stream
, msgmap
, 1);
5413 if(current_index_state
)
5414 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
5416 state
->next_screen
= SCREEN_FUN_NULL
;
5420 new_msgno
= msgno
; /* stick with original */
5425 mn_set_cur(msgmap
, new_msgno
);
5426 if(in_index
!= View
)
5427 adjust_cur_to_visible(stream
, msgmap
);
5433 get_level(int qline
, UCS first_num
, SCROLL_S
*sparms
)
5435 char debug_num_string
[80], *j
, prompt
[70];
5440 if(first_num
&& first_num
< 0x80 && isdigit((unsigned char)first_num
)){
5441 debug_num_string
[0] = first_num
;
5442 debug_num_string
[1] = '\0';
5443 debug_num
= atol(debug_num_string
);
5444 *(int *)(sparms
->proc
.data
.p
) = debug_num
;
5445 q_status_message1(SM_ORDER
, 0, 3, "Show debug <= level %s",
5446 comatose(debug_num
));
5450 debug_num_string
[0] = '\0';
5452 snprintf(prompt
, sizeof(prompt
), "Show debug <= this level (0-%d) : ", MAX(debug
, 9));
5453 prompt
[sizeof(prompt
)-1] = '\0';
5457 int flags
= OE_APPEND_CURRENT
;
5459 rc
= optionally_enter(debug_num_string
, qline
, 0,
5460 sizeof(debug_num_string
), prompt
,
5461 NULL
, help
, &flags
);
5463 help
= help
== NO_HELP
? h_oe_debuglevel
: NO_HELP
;
5468 removing_leading_and_trailing_white_space(debug_num_string
);
5469 for(j
=debug_num_string
; isdigit((unsigned char)*j
); j
++)
5473 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5474 _("Invalid number entered. Use only digits 0-9"));
5475 debug_num_string
[0] = '\0';
5478 debug_num
= atol(debug_num_string
);
5480 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5481 _("Number should be >= 0"));
5482 else if(debug_num
> MAX(debug
,9))
5483 q_status_message1(SM_ORDER
| SM_DING
, 2, 2,
5484 _("Maximum is %s"), comatose(MAX(debug
,9)));
5486 *(int *)(sparms
->proc
.data
.p
) = debug_num
;
5487 q_status_message1(SM_ORDER
, 0, 3,
5488 "Show debug <= level %s",
5489 comatose(debug_num
));
5507 * Returns the message number closest to target that isn't hidden.
5508 * Make warning at least 100 chars.
5509 * A return of 0 means there is no message to jump to.
5512 closest_jump_target(long int target
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int no_target
, CmdWhere in_index
, char *warning
, size_t warninglen
)
5514 long i
, start
, closest
= 0L;
5519 maxnum
= (in_index
== ThrdIndx
) ? msgmap
->max_thrdno
: mn_get_total(msgmap
);
5524 snprintf(warning
, warninglen
, "No %s number entered, jump to end? ",
5525 (in_index
== ThrdIndx
) ? "thread" : "message");
5526 warning
[warninglen
-1] = '\0';
5528 else if(target
< 1L)
5529 start
= 1L - target
;
5530 else if(target
> maxnum
)
5531 start
= target
- maxnum
;
5535 if(target
> 0L && target
<= maxnum
)
5536 if(in_index
== ThrdIndx
5537 || !msgline_hidden(stream
, msgmap
, target
, 0))
5540 for(i
= start
; target
+i
<= maxnum
|| target
-i
> 0L; i
++){
5542 if(target
+i
> 0L && target
+i
<= maxnum
&&
5543 (in_index
== ThrdIndx
5544 || !msgline_hidden(stream
, msgmap
, target
+i
, 0))){
5549 if(target
-i
> 0L && target
-i
<= maxnum
&&
5550 (in_index
== ThrdIndx
5551 || !msgline_hidden(stream
, msgmap
, target
-i
, 0))){
5557 strncpy(buf
, long2string(closest
), sizeof(buf
));
5558 buf
[sizeof(buf
)-1] = '\0';
5561 strncpy(warning
, "Nothing to jump to", warninglen
);
5562 else if(target
< 1L)
5563 snprintf(warning
, warninglen
, "%s number (%s) must be at least %s",
5564 (in_index
== ThrdIndx
) ? "Thread" : "Message",
5565 long2string(target
), buf
);
5566 else if(target
> maxnum
)
5567 snprintf(warning
, warninglen
, "%s number (%s) may be no more than %s",
5568 (in_index
== ThrdIndx
) ? "Thread" : "Message",
5569 long2string(target
), buf
);
5571 snprintf(warning
, warninglen
,
5572 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5573 long2string(target
), buf
);
5575 warning
[warninglen
-1] = '\0';
5581 /*----------------------------------------------------------------------
5582 Prompt for folder name to open, expand the name and return it
5584 Args: qline -- Screen line to prompt on
5585 allow_list -- if 1, allow ^T to bring up collection lister
5587 Result: returns the folder name or NULL
5588 pine structure mangled_footer flag is set
5589 may call the collection lister in which case mangled screen will be set
5591 This prompts the user for the folder to open, possibly calling up
5592 the collection lister if the user types ^T.
5593 ----------------------------------------------------------------------*/
5595 broach_folder(int qline
, int allow_list
, int *notrealinbox
, CONTEXT_S
**context
)
5598 static char newfolder
[MAILTMPLEN
];
5599 char expanded
[MAXPATH
+1],
5600 prompt
[MAX_SCREEN_COLS
+1],
5602 unsigned char *f1
, *f2
, *f3
;
5603 static HISTORY_S
*history
= NULL
;
5604 CONTEXT_S
*tc
, *tc2
;
5606 int rc
, r
, ku
= -1, n
, flags
, last_rc
= 0, inbox
, done
= 0;
5609 * the idea is to provide a clue for the context the file name
5610 * will be saved in (if a non-imap names is typed), and to
5611 * only show the previous if it was also in the same context
5618 (*notrealinbox
) = 1;
5620 init_hist(&history
, HISTSIZE
);
5622 tc
= broach_get_folder(context
? *context
: NULL
, &inbox
, NULL
);
5624 /* set up extra command option keys */
5626 ekey
[rc
].ch
= (allow_list
) ? ctrl('T') : 0 ;
5627 ekey
[rc
].rval
= (allow_list
) ? 2 : 0;
5628 ekey
[rc
].name
= (allow_list
) ? "^T" : "";
5629 ekey
[rc
++].label
= (allow_list
) ? N_("ToFldrs") : "";
5631 if(ps_global
->context_list
->next
){
5632 ekey
[rc
].ch
= ctrl('P');
5634 ekey
[rc
].name
= "^P";
5635 ekey
[rc
++].label
= N_("Prev Collection");
5637 ekey
[rc
].ch
= ctrl('N');
5639 ekey
[rc
].name
= "^N";
5640 ekey
[rc
++].label
= N_("Next Collection");
5643 ekey
[rc
].ch
= ctrl('W');
5645 ekey
[rc
].name
= "^W";
5646 ekey
[rc
++].label
= N_("INBOX");
5648 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps_global
)){
5651 ekey
[rc
].name
= "TAB";
5652 ekey
[rc
++].label
= N_("Complete");
5655 if(F_ON(F_ENABLE_SUB_LISTS
, ps_global
)){
5656 ekey
[rc
].ch
= ctrl('X');
5658 ekey
[rc
].name
= "^X";
5659 ekey
[rc
++].label
= N_("ListMatches");
5662 if(ps_global
->context_list
->next
&& F_ON(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
5663 ekey
[rc
].ch
= KEY_UP
;
5666 ekey
[rc
++].label
= "";
5668 ekey
[rc
].ch
= KEY_DOWN
;
5671 ekey
[rc
++].label
= "";
5673 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
5674 ekey
[rc
].ch
= KEY_UP
;
5678 ekey
[rc
++].label
= "";
5680 ekey
[rc
].ch
= KEY_DOWN
;
5683 ekey
[rc
++].label
= "";
5690 * Figure out next default value for this context. The idea
5691 * is that in each context the last folder opened is cached.
5692 * It's up to pick it out and display it. This is fine
5693 * and dandy if we've currently got the inbox open, BUT
5694 * if not, make the inbox the default the first time thru.
5697 last_folder
= ps_global
->inbox_name
;
5698 inbox
= 1; /* pretend we're in inbox from here on out */
5701 last_folder
= (ps_global
->last_unambig_folder
[0])
5702 ? ps_global
->last_unambig_folder
5703 : ((tc
->last_folder
[0]) ? tc
->last_folder
: NULL
);
5705 if(last_folder
){ /* MAXPATH + 1 = sizeof(expanded) */
5706 unsigned char *fname
= folder_name_decoded((unsigned char *)last_folder
);
5707 snprintf(expanded
, sizeof(expanded
), " [%.*s]", MAXPATH
+1-5,
5708 fname
? (char *) fname
: last_folder
);
5709 if(fname
) fs_give((void **)&fname
);
5714 expanded
[sizeof(expanded
)-1] = '\0';
5716 /* only show collection number if more than one available */
5717 if(ps_global
->context_list
->next
) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5718 snprintf(prompt
, sizeof(prompt
), "GOTO %s in <%s> %.*s%s: ",
5719 NEWS_TEST(tc
) ? "news group" : "folder",
5720 tc
->nickname
, MAX_SCREEN_COLS
+1-50, expanded
,
5721 *expanded
? " " : "");
5722 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5723 snprintf(prompt
, sizeof(prompt
), "GOTO folder %.*s%s: ", MAX_SCREEN_COLS
+1-20, expanded
,
5724 *expanded
? " " : "");
5726 prompt
[sizeof(prompt
)-1] = '\0';
5728 if(utf8_width(prompt
) > MAXPROMPT
){
5729 if(ps_global
->context_list
->next
) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5730 snprintf(prompt
, sizeof(prompt
), "GOTO <%s> %.*s%s: ",
5731 tc
->nickname
, MAX_SCREEN_COLS
+1-50, expanded
,
5732 *expanded
? " " : "");
5733 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5734 snprintf(prompt
, sizeof(prompt
), "GOTO %.*s%s: ", MAX_SCREEN_COLS
+1-20, expanded
,
5735 *expanded
? " " : "");
5737 prompt
[sizeof(prompt
)-1] = '\0';
5739 if(utf8_width(prompt
) > MAXPROMPT
){
5740 if(ps_global
->context_list
->next
) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5741 snprintf(prompt
, sizeof(prompt
), "<%s> %.*s%s: ",
5742 tc
->nickname
, MAX_SCREEN_COLS
+1-50, expanded
,
5743 *expanded
? " " : "");
5744 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5745 snprintf(prompt
, sizeof(prompt
), "%.*s%s: ", MAX_SCREEN_COLS
+1-20, expanded
,
5746 *expanded
? " " : "");
5748 prompt
[sizeof(prompt
)-1] = '\0';
5753 if(items_in_hist(history
) > 1){
5754 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
5755 ekey
[ku
].label
= HISTORY_KEYLABEL
;
5756 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
5757 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
5761 ekey
[ku
].label
= "";
5762 ekey
[ku
+1].name
= "";
5763 ekey
[ku
+1].label
= "";
5767 /* is there any other way to do this? The point is that we
5768 * are trying to hide mutf7 from the user, and use the utf8
5769 * equivalent. So we create a variable f to take place of
5770 * newfolder, including content and size. f2 is copy of f1
5771 * that has to freed. Sigh!
5773 f3
= (unsigned char *) cpystr(newfolder
);
5774 f1
= fs_get(sizeof(newfolder
));
5775 f2
= folder_name_decoded(f3
);
5776 if(f3
) fs_give((void **)&f3
);
5777 strncpy((char *)f1
, (char *)f2
, sizeof(newfolder
));
5778 f1
[sizeof(newfolder
)-1] = '\0';
5779 if(f2
) fs_give((void **)&f2
);
5781 flags
= OE_APPEND_CURRENT
;
5782 rc
= optionally_enter((char *) f1
, qline
, 0, sizeof(newfolder
),
5783 (char *) prompt
, ekey
, help
, &flags
);
5785 f2
= folder_name_encoded(f1
);
5786 strncpy(newfolder
, (char *)f2
, sizeof(newfolder
));
5787 if(f1
) fs_give((void **)&f1
);
5788 if(f2
) fs_give((void **)&f2
);
5790 ps_global
->mangled_footer
= 1;
5793 case -1 : /* o_e says error! */
5794 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
5795 _("Error reading folder name"));
5798 case 0 : /* o_e says normal entry */
5799 removing_trailing_white_space(newfolder
);
5800 removing_leading_white_space(newfolder
);
5803 char *name
, *fullname
= NULL
;
5804 int exists
, breakout
= 0;
5806 save_hist(history
, newfolder
, 0, tc
);
5808 if(!(name
= folder_is_nick(newfolder
, FOLDERS(tc
),
5812 if(update_folder_spec(expanded
, sizeof(expanded
), name
)){
5813 strncpy(name
= newfolder
, expanded
, sizeof(newfolder
));
5814 newfolder
[sizeof(newfolder
)-1] = '\0';
5817 exists
= folder_name_exists(tc
, name
, &fullname
);
5820 strncpy(name
= newfolder
, fullname
, sizeof(newfolder
));
5821 newfolder
[sizeof(newfolder
)-1] = '\0';
5822 fs_give((void **) &fullname
);
5827 * if we know the things a folder, open it.
5828 * else if we know its a directory, visit it.
5829 * else we're not sure (it either doesn't really
5830 * exist or its unLISTable) so try opening it anyway
5832 if(exists
& FEX_ISFILE
){
5836 else if((exists
& FEX_ISDIR
)){
5838 CONTEXT_S
*fake_context
;
5839 char tmp
[MAILTMPLEN
];
5842 strncpy(tmp
, name
, sizeof(tmp
));
5843 tmp
[sizeof(tmp
)-2-1] = '\0';
5844 if(tmp
[(l
= strlen(tmp
)) - 1] != tc
->dir
->delim
){
5845 if(l
< sizeof(tmp
)){
5846 tmp
[l
] = tc
->dir
->delim
;
5847 strncpy(&tmp
[l
+1], "[]", sizeof(tmp
)-(l
+1));
5851 strncat(tmp
, "[]", sizeof(tmp
)-strlen(tmp
)-1);
5853 tmp
[sizeof(tmp
)-1] = '\0';
5855 fake_context
= new_context(tmp
, 0);
5856 newfolder
[0] = '\0';
5857 done
= display_folder_list(&fake_context
, newfolder
,
5858 1, folders_for_goto
);
5859 free_context(&fake_context
);
5862 else if(!(tc
->use
& CNTXT_INCMNG
)){
5863 done
= display_folder_list(&tc
, newfolder
,
5864 1, folders_for_goto
);
5868 else if((exists
& FEX_ERROR
)){
5869 q_status_message1(SM_ORDER
, 0, 3,
5870 _("Problem accessing folder \"%s\""),
5879 if(exists
== FEX_ERROR
)
5880 q_status_message1(SM_ORDER
, 0, 3,
5881 _("Problem accessing folder \"%s\""),
5883 else if(tc
->use
& CNTXT_INCMNG
)
5884 q_status_message1(SM_ORDER
, 0, 3,
5885 _("Can't find Incoming Folder: %s"),
5887 else if(context_isambig(newfolder
))
5888 q_status_message2(SM_ORDER
, 0, 3,
5889 _("Can't find folder \"%s\" in %s"),
5890 newfolder
, (void *) tc
->nickname
);
5892 q_status_message1(SM_ORDER
, 0, 3,
5893 _("Can't find folder \"%s\""),
5898 else if(last_folder
){
5899 if(ps_global
->goto_default_rule
== GOTO_FIRST_CLCTN_DEF_INBOX
5900 && !strucmp(last_folder
, ps_global
->inbox_name
)
5901 && tc
== ((ps_global
->context_list
->use
& CNTXT_INCMNG
)
5902 ? ps_global
->context_list
->next
: ps_global
->context_list
)){
5904 (*notrealinbox
) = 0;
5906 tc
= ps_global
->context_list
;
5909 strncpy(newfolder
, last_folder
, sizeof(newfolder
));
5910 newfolder
[sizeof(newfolder
)-1] = '\0';
5911 save_hist(history
, newfolder
, 0, tc
);
5915 /* fall thru like they cancelled */
5917 case 1 : /* o_e says user cancel */
5918 cmd_cancelled("Open folder");
5921 case 2 : /* o_e says user wants list */
5922 r
= display_folder_list(&tc
, newfolder
, 0, folders_for_goto
);
5928 case 3 : /* o_e says user wants help */
5929 help
= help
== NO_HELP
? h_oe_broach
: NO_HELP
;
5932 case 4 : /* redraw */
5935 case 10 : /* Previous collection */
5936 tc2
= ps_global
->context_list
;
5937 while(tc2
->next
&& tc2
->next
!= tc
)
5943 case 11 : /* Next collection */
5944 tc
= (tc
->next
) ? tc
->next
: ps_global
->context_list
;
5947 case 12 : /* file name completion */
5948 if(!folder_complete(tc
, newfolder
, sizeof(newfolder
), &n
)){
5949 if(n
&& last_rc
== 12 && !(flags
& OE_USER_MODIFIED
)){
5950 r
= display_folder_list(&tc
, newfolder
, 1,folders_for_goto
);
5952 done
++; /* bingo! */
5954 rc
= 0; /* burn last_rc */
5962 case 14 : /* file name completion */
5963 r
= display_folder_list(&tc
, newfolder
, 2, folders_for_goto
);
5965 done
++; /* bingo! */
5967 rc
= 0; /* burn last_rc */
5971 case 17 : /* GoTo INBOX */
5973 strncpy(newfolder
, ps_global
->inbox_name
, sizeof(newfolder
)-1);
5974 newfolder
[sizeof(newfolder
)-1] = '\0';
5976 (*notrealinbox
) = 0;
5978 tc
= ps_global
->context_list
;
5979 save_hist(history
, newfolder
, 0, tc
);
5984 if((p
= get_prev_hist(history
, newfolder
, 0, tc
)) != NULL
){
5985 strncpy(newfolder
, p
, sizeof(newfolder
));
5986 newfolder
[sizeof(newfolder
)-1] = '\0';
5987 if(history
->hist
[history
->curindex
])
5988 tc
= history
->hist
[history
->curindex
]->cntxt
;
5996 if((p
= get_next_hist(history
, newfolder
, 0, tc
)) != NULL
){
5997 strncpy(newfolder
, p
, sizeof(newfolder
));
5998 newfolder
[sizeof(newfolder
)-1] = '\0';
5999 if(history
->hist
[history
->curindex
])
6000 tc
= history
->hist
[history
->curindex
]->cntxt
;
6008 alpine_panic("Unhandled case");
6015 dprint((2, "broach folder, name entered \"%s\"\n",
6016 newfolder
? newfolder
: "?"));
6018 /*-- Just check that we can expand this. It gets done for real later --*/
6019 strncpy(expanded
, newfolder
, sizeof(expanded
));
6020 expanded
[sizeof(expanded
)-1] = '\0';
6022 if(!expand_foldername(expanded
, sizeof(expanded
))) {
6023 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
6024 expanded
? expanded
: "?"));
6033 /*----------------------------------------------------------------------
6034 Check to see if user wants to reopen dead stream.
6039 Result: 1 if the folder was successfully updatedn
6044 ask_mailbox_reopen(struct pine
*ps
, int *reopenp
)
6046 if(((ps
->mail_stream
->dtb
6047 && ((ps
->mail_stream
->dtb
->flags
& DR_NONEWMAIL
)
6048 || (ps
->mail_stream
->rdonly
6049 && ps
->mail_stream
->dtb
->flags
& DR_NONEWMAILRONLY
)))
6050 && (ps
->reopen_rule
== REOPEN_ASK_ASK_Y
6051 || ps
->reopen_rule
== REOPEN_ASK_ASK_N
6052 || ps
->reopen_rule
== REOPEN_ASK_NO_Y
6053 || ps
->reopen_rule
== REOPEN_ASK_NO_N
))
6054 || ((ps
->mail_stream
->dtb
6055 && ps
->mail_stream
->rdonly
6056 && !(ps
->mail_stream
->dtb
->flags
& DR_LOCAL
))
6057 && (ps
->reopen_rule
== REOPEN_YES_ASK_Y
6058 || ps
->reopen_rule
== REOPEN_YES_ASK_N
6059 || ps
->reopen_rule
== REOPEN_ASK_ASK_Y
6060 || ps
->reopen_rule
== REOPEN_ASK_ASK_N
))){
6063 switch(ps
->reopen_rule
){
6064 case REOPEN_YES_ASK_Y
:
6065 case REOPEN_ASK_ASK_Y
:
6066 case REOPEN_ASK_NO_Y
:
6075 switch(want_to("Re-open folder to check for new messages", deefault
,
6076 'x', h_reopen_folder
, WT_NORM
)){
6091 /*----------------------------------------------------------------------
6092 Check to see if user input is in form of old c-client mailbox speck
6097 Result: 1 if the folder was successfully updatedn
6102 update_folder_spec(char *new, size_t newlen
, char *old
)
6108 if(*(p
= old
) == '*') /* old form? */
6111 if(*old
== '{') /* copy host spec */
6113 switch(*new = *old
++){
6118 if(!struncmp(old
, "nntp", 4))
6126 while(*new++ != '}' && (new-orignew
) < newlen
-1);
6128 if((*p
== '*' && *old
) || ((*old
== '*') ? *++old
: 0)){
6130 * OK, some heuristics here. If it looks like a newsgroup
6131 * then we plunk it into the #news namespace else we
6132 * assume that they're trying to get at a #public folder...
6135 *p
&& (isalnum((unsigned char) *p
) || strindex(".-", *p
));
6139 sstrncpy(&new, (*p
&& !nntp
) ? "#public/" : "#news.", newlen
-(new-orignew
));
6140 strncpy(new, old
, newlen
-(new-orignew
));
6144 orignew
[newlen
-1] = '\0';
6150 /*----------------------------------------------------------------------
6151 Open the requested folder in the requested context
6153 Args: state -- usual pine state struct
6154 newfolder -- folder to open
6155 new_context -- folder context might live in
6156 stream -- candidate for recycling
6158 Result: New folder open or not (if error), and we're set to
6159 enter the index screen.
6162 visit_folder(struct pine
*state
, char *newfolder
, CONTEXT_S
*new_context
,
6163 MAILSTREAM
*stream
, long unsigned int flags
)
6165 dprint((9, "visit_folder(%s, %s)\n",
6166 newfolder
? newfolder
: "?",
6167 (new_context
&& new_context
->context
)
6168 ? new_context
->context
: "(NULL)"));
6170 if(ps_global
&& ps_global
->ttyo
){
6171 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
6172 ps_global
->mangled_footer
= 1;
6175 if(do_broach_folder(newfolder
, new_context
, stream
? &stream
: NULL
,
6177 || !sp_flagged(state
->mail_stream
, SP_LOCKED
))
6178 state
->next_screen
= mail_index_screen
;
6180 state
->next_screen
= folder_screen
;
6184 /*----------------------------------------------------------------------
6185 Move read messages from folder if listed in archive
6191 read_msg_prompt(long int n
, char *f
)
6193 char buf
[MAX_SCREEN_COLS
+1];
6195 snprintf(buf
, sizeof(buf
), "Save the %ld read message%s in \"%s\"", n
, plural(n
), f
);
6196 buf
[sizeof(buf
)-1] = '\0';
6197 return(want_to(buf
, 'y', 0, NO_HELP
, WT_NORM
) == 'y');
6201 /*----------------------------------------------------------------------
6202 Print current message[s] or folder index
6204 Args: state -- pointer to struct holding a bunch of pine state
6205 msgmap -- table mapping msg nums to c-client sequence nums
6206 aopt -- aggregate options
6207 in_index -- boolean indicating we're called from Index Screen
6209 Filters the original header and sends stuff to printer
6212 cmd_print(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, CmdWhere in_index
)
6215 long i
, msgs
, rawno
;
6216 int next
= 0, do_index
= 0, rv
= 0;
6221 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
6224 msgs
= mn_total_cur(msgmap
);
6226 if((in_index
!= View
) && F_ON(F_PRINT_INDEX
, state
)){
6229 static ESCKEY_S prt_opts
[] = {
6230 {'i', 'i', "I", N_("Index")},
6231 {'m', 'm', "M", NULL
},
6232 {-1, 0, NULL
, NULL
}};
6234 if(in_index
== ThrdIndx
){
6235 /* TRANSLATORS: This is a question, Print Index ? */
6236 if(want_to(_("Print Index"), 'y', 'x', NO_HELP
, WT_NORM
) == 'y')
6242 snprintf(m
, sizeof(m
), "Message%s", (msgs
>1L) ? "s" : "");
6243 m
[sizeof(m
)-1] = '\0';
6244 prt_opts
[1].label
= m
;
6245 snprintf(prompt
, sizeof(prompt
), "Print %sFolder Index or %s %s? ",
6246 (aopt
& MCMD_AGG_2
) ? "thread " : MCMD_ISAGG(aopt
) ? "selected " : "",
6247 (aopt
& MCMD_AGG_2
) ? "thread" : MCMD_ISAGG(aopt
) ? "selected" : "current", m
);
6248 prompt
[sizeof(prompt
)-1] = '\0';
6250 ans
= radio_buttons(prompt
, -FOOTER_ROWS(state
), prt_opts
, 'm', 'x',
6251 NO_HELP
, RB_NORM
|RB_SEQ_SENSITIVE
);
6256 cmd_cancelled("Print");
6257 if(MCMD_ISAGG(aopt
))
6258 restore_selected(msgmap
);
6273 snprintf(prompt
, sizeof(prompt
), "%sFolder Index",
6274 (aopt
& MCMD_AGG_2
) ? "Thread " : MCMD_ISAGG(aopt
) ? "Selected " : "");
6276 snprintf(prompt
, sizeof(prompt
), "%s messages", long2string(msgs
));
6278 snprintf(prompt
, sizeof(prompt
), "Message %s", long2string(mn_get_cur(msgmap
)));
6280 prompt
[sizeof(prompt
)-1] = '\0';
6282 if(open_printer(prompt
) < 0){
6283 if(MCMD_ISAGG(aopt
))
6284 restore_selected(msgmap
);
6292 tc
= format_titlebar();
6294 /* Print titlebar... */
6295 print_text1("%s\n\n", tc
? tc
->titlebar_line
: "");
6296 /* then all the index members... */
6297 if(!print_index(state
, msgmap
, MCMD_ISAGG(aopt
)))
6298 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6299 _("Error printing folder index"));
6305 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
), next
++){
6306 if(next
&& F_ON(F_AGG_PRINT_FF
, state
))
6307 if(!print_char(FORMFEED
)){
6312 if(!(state
->mail_stream
6313 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
6314 && rawno
<= state
->mail_stream
->nmsgs
6315 && (mc
= mail_elt(state
->mail_stream
, rawno
))
6319 if(!(e
=pine_mail_fetchstructure(state
->mail_stream
,
6322 || (F_ON(F_FROM_DELIM_IN_PRINT
, ps_global
)
6323 && !bezerk_delimiter(e
, mc
, print_char
, next
))
6324 || !format_message(mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
6325 e
, b
, NULL
, FM_NEW_MESS
| FM_NOINDENT
,
6327 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6328 _("Error printing message"));
6337 if(MCMD_ISAGG(aopt
))
6338 restore_selected(msgmap
);
6344 /*----------------------------------------------------------------------
6347 Args: state -- various pine state bits
6348 msgmap -- Message number mapping table
6349 aopt -- option flags
6351 Filters the original header and sends stuff to specified command
6354 cmd_pipe(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
6360 char *resultfilename
= NULL
, prompt
[80], *p
;
6361 int done
= 0, rv
= 0;
6363 int fourlabel
= -1, j
= 0, next
= 0, ku
;
6364 int pipe_rv
; /* rv of proc to separate from close_system_pipe rv */
6366 unsigned flagsforhist
= 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6367 static HISTORY_S
*history
= NULL
;
6368 int capture
= 1, raw
= 0, delimit
= 0, newpipe
= 0;
6369 char pipe_command
[MAXPATH
];
6370 ESCKEY_S pipe_opt
[8];
6372 if(ps_global
->restricted
){
6373 q_status_message(SM_ORDER
| SM_DING
, 0, 4,
6374 "Alpine demo can't pipe messages");
6377 else if(!any_messages(msgmap
, NULL
, "to Pipe"))
6380 pipe_command
[0] = '\0';
6381 init_hist(&history
, HISTSIZE
);
6382 flagsforhist
= (raw
? 0x8 : 0) +
6383 (delimit
? 0x4 : 0) +
6384 (newpipe
? 0x2 : 0) +
6385 (capture
? 0x1 : 0);
6386 if((p
= get_prev_hist(history
, "", flagsforhist
, NULL
)) != NULL
){
6387 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6388 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6389 if(history
->hist
[history
->curindex
]){
6390 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6391 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6392 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6393 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6394 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6399 pipe_opt
[j
].rval
= 0;
6400 pipe_opt
[j
].name
= "";
6401 pipe_opt
[j
++].label
= "";
6403 pipe_opt
[j
].ch
= ctrl('W');
6404 pipe_opt
[j
].rval
= 10;
6405 pipe_opt
[j
].name
= "^W";
6406 pipe_opt
[j
++].label
= NULL
;
6408 pipe_opt
[j
].ch
= ctrl('Y');
6409 pipe_opt
[j
].rval
= 11;
6410 pipe_opt
[j
].name
= "^Y";
6411 pipe_opt
[j
++].label
= NULL
;
6413 pipe_opt
[j
].ch
= ctrl('R');
6414 pipe_opt
[j
].rval
= 12;
6415 pipe_opt
[j
].name
= "^R";
6416 pipe_opt
[j
++].label
= NULL
;
6418 if(MCMD_ISAGG(aopt
)){
6419 if(!pseudo_selected(state
->mail_stream
, msgmap
))
6423 pipe_opt
[j
].ch
= ctrl('T');
6424 pipe_opt
[j
].rval
= 13;
6425 pipe_opt
[j
].name
= "^T";
6426 pipe_opt
[j
++].label
= NULL
;
6430 pipe_opt
[j
].ch
= KEY_UP
;
6431 pipe_opt
[j
].rval
= 30;
6432 pipe_opt
[j
].name
= "";
6434 pipe_opt
[j
++].label
= "";
6436 pipe_opt
[j
].ch
= KEY_DOWN
;
6437 pipe_opt
[j
].rval
= 31;
6438 pipe_opt
[j
].name
= "";
6439 pipe_opt
[j
++].label
= "";
6441 pipe_opt
[j
].ch
= -1;
6446 snprintf(prompt
, sizeof(prompt
), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6448 MCMD_ISAGG(aopt
) ? "s" : " ",
6449 MCMD_ISAGG(aopt
) ? "" : comatose(mn_get_cur(msgmap
)),
6450 (!capture
|| delimit
|| (newpipe
&& MCMD_ISAGG(aopt
))) ? "(" : "",
6451 capture
? "" : "uncaptured",
6452 (!capture
&& delimit
) ? "," : "",
6453 delimit
? "delimited" : "",
6454 ((!capture
|| delimit
) && newpipe
&& MCMD_ISAGG(aopt
)) ? "," : "",
6455 (newpipe
&& MCMD_ISAGG(aopt
)) ? "new pipe" : "",
6456 (!capture
|| delimit
|| (newpipe
&& MCMD_ISAGG(aopt
))) ? ") " : "");
6457 prompt
[sizeof(prompt
)-1] = '\0';
6458 pipe_opt
[1].label
= raw
? N_("Shown Text") : N_("Raw Text");
6459 pipe_opt
[2].label
= capture
? N_("Free Output") : N_("Capture Output");
6460 pipe_opt
[3].label
= delimit
? N_("No Delimiter") : N_("With Delimiter");
6462 pipe_opt
[fourlabel
].label
= newpipe
? N_("To Same Pipe") : N_("To Individual Pipes");
6466 * 2 is really 1 because there will be one real entry and
6467 * one entry of "" because of the get_prev_hist above.
6469 if(items_in_hist(history
) > 2){
6470 pipe_opt
[ku
].name
= HISTORY_UP_KEYNAME
;
6471 pipe_opt
[ku
].label
= HISTORY_KEYLABEL
;
6472 pipe_opt
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
6473 pipe_opt
[ku
+1].label
= HISTORY_KEYLABEL
;
6476 pipe_opt
[ku
].name
= "";
6477 pipe_opt
[ku
].label
= "";
6478 pipe_opt
[ku
+1].name
= "";
6479 pipe_opt
[ku
+1].label
= "";
6482 flags
= OE_APPEND_CURRENT
| OE_SEQ_SENSITIVE
;
6483 switch(optionally_enter(pipe_command
, -FOOTER_ROWS(state
), 0,
6484 sizeof(pipe_command
), prompt
,
6485 pipe_opt
, NO_HELP
, &flags
)){
6487 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
6488 _("Internal problem encountered"));
6492 case 10 : /* flip raw bit */
6496 case 11 : /* flip capture bit */
6500 case 12 : /* flip delimit bit */
6504 case 13 : /* flip newpipe bit */
6509 flagsforhist
= (raw
? 0x8 : 0) +
6510 (delimit
? 0x4 : 0) +
6511 (newpipe
? 0x2 : 0) +
6512 (capture
? 0x1 : 0);
6513 if((p
= get_prev_hist(history
, pipe_command
, flagsforhist
, NULL
)) != NULL
){
6514 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6515 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6516 if(history
->hist
[history
->curindex
]){
6517 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6518 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6519 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6520 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6521 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6530 flagsforhist
= (raw
? 0x8 : 0) +
6531 (delimit
? 0x4 : 0) +
6532 (newpipe
? 0x2 : 0) +
6533 (capture
? 0x1 : 0);
6534 if((p
= get_next_hist(history
, pipe_command
, flagsforhist
, NULL
)) != NULL
){
6535 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6536 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6537 if(history
->hist
[history
->curindex
]){
6538 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6539 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6540 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6541 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6542 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6551 if(pipe_command
[0]){
6553 flagsforhist
= (raw
? 0x8 : 0) +
6554 (delimit
? 0x4 : 0) +
6555 (newpipe
? 0x2 : 0) +
6556 (capture
? 0x1 : 0);
6557 save_hist(history
, pipe_command
, flagsforhist
, NULL
);
6559 flags
= PIPE_USER
| PIPE_WRITE
| PIPE_STDERR
;
6560 flags
|= (raw
? PIPE_RAW
: 0);
6566 ps_global
->mangled_screen
= 1;
6567 ps_global
->in_init_seq
= 1;
6569 flags
|= PIPE_RESET
;
6572 if(!newpipe
&& !(syspipe
= cmd_pipe_open(pipe_command
,
6573 (flags
& PIPE_RESET
)
6579 for(i
= mn_first_cur(msgmap
);
6581 i
= mn_next_cur(msgmap
)){
6582 e
= pine_mail_fetchstructure(ps_global
->mail_stream
,
6583 mn_m2raw(msgmap
, i
), &b
);
6584 if(!(state
->mail_stream
6585 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
6586 && rawno
<= state
->mail_stream
->nmsgs
6587 && (mc
= mail_elt(state
->mail_stream
, rawno
))
6592 && !(syspipe
= cmd_pipe_open(pipe_command
,
6593 (flags
& PIPE_RESET
)
6597 || (delimit
&& !bezerk_delimiter(e
, mc
, pc
, next
++)))
6604 prime_raw_pipe_getc(ps_global
->mail_stream
,
6605 mn_m2raw(msgmap
, i
), -1L, 0L);
6607 gf_link_filter(gf_nvtnl_local
, NULL
);
6608 if((pipe_err
= gf_pipe(raw_pipe_getc
, pc
)) != NULL
){
6609 q_status_message1(SM_ORDER
|SM_DING
,
6611 _("Internal Error: %s"),
6616 else if(!format_message(mn_m2raw(msgmap
, i
), e
, b
,
6617 NULL
, FM_NEW_MESS
| FM_NOWRAP
, pc
))
6622 if(close_system_pipe(&syspipe
, &pipe_rv
, pipe_callback
) == -1)
6627 ps_global
->in_init_seq
= 0;
6630 if(close_system_pipe(&syspipe
, &pipe_rv
, pipe_callback
) == -1)
6632 if(done
) /* say we had a problem */
6633 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6634 _("Error piping message"));
6635 else if(resultfilename
){
6637 /* only display if no error */
6638 display_output_file(resultfilename
, "PIPE MESSAGE",
6640 fs_give((void **)&resultfilename
);
6644 q_status_message(SM_ORDER
, 0, 2, _("Pipe command completed"));
6650 /* else fall thru as if cancelled */
6653 cmd_cancelled("Pipe command");
6658 helper(h_common_pipe
, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE
);
6659 ps_global
->mangled_screen
= 1;
6662 case 2 : /* no place to escape to */
6663 case 4 : /* can't suspend */
6669 ps_global
->mangled_footer
= 1;
6670 if(MCMD_ISAGG(aopt
))
6671 restore_selected(msgmap
);
6677 /*----------------------------------------------------------------------
6678 Screen to offer list management commands contained in message
6680 Args: state -- pointer to struct holding a bunch of pine state
6681 msgmap -- table mapping msg nums to c-client sequence nums
6682 aopt -- aggregate options
6686 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6689 rfc2369_display(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
)
6692 char *h
, *hdrs
[MLCMD_COUNT
+ 1];
6693 long index_no
= mn_raw2m(msgmap
, msgno
);
6694 RFC2369_S data
[MLCMD_COUNT
];
6696 /* for each header field */
6697 if((h
= pine_fetchheader_lines(stream
, msgno
, NULL
, rfc2369_hdrs(hdrs
))) != NULL
){
6698 memset(&data
[0], 0, sizeof(RFC2369_S
) * MLCMD_COUNT
);
6699 if(rfc2369_parse_fields(h
, &data
[0])){
6702 if((explain
= list_mgmt_text(data
, index_no
)) != NULL
){
6703 list_mgmt_screen(explain
);
6704 ps_global
->mangled_screen
= 1;
6710 fs_give((void **) &h
);
6714 q_status_message1(SM_ORDER
, 0, 3,
6715 "Message %s contains no list management information",
6716 comatose(index_no
));
6721 list_mgmt_text(RFC2369_S
*data
, long int msgno
)
6724 int i
, j
, n
, fields
= 0;
6725 static char *rfc2369_intro1
=
6726 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6727 static char *rfc2369_intro2
[] = {
6728 N_(" has information associated with it "),
6729 N_("that explains how to participate in an email list. An "),
6730 N_("email list is represented by a single email address that "),
6731 N_("users sharing a common interest can send messages to (known "),
6732 N_("as posting) which are then redistributed to all members "),
6733 N_("of the list (sometimes after review by a moderator)."),
6734 N_("<P>List participation commands in this message include:"),
6738 if((store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) != NULL
){
6740 /* Insert introductory text */
6741 so_puts(store
, rfc2369_intro1
);
6743 so_puts(store
, comatose(msgno
));
6745 for(i
= 0; rfc2369_intro2
[i
]; i
++)
6746 so_puts(store
, _(rfc2369_intro2
[i
]));
6748 so_puts(store
, "<P>");
6749 for(i
= 0; i
< MLCMD_COUNT
; i
++)
6750 if(data
[i
].data
[0].value
6751 || data
[i
].data
[0].comment
6752 || data
[i
].data
[0].error
){
6754 so_puts(store
, "<UL>");
6756 so_puts(store
, "<LI>");
6758 (n
= (data
[i
].data
[1].value
|| data
[i
].data
[1].comment
))
6762 so_puts(store
, data
[i
].field
.description
);
6763 so_puts(store
, ". ");
6766 so_puts(store
, "<OL>");
6770 && (data
[i
].data
[j
].comment
6771 || data
[i
].data
[j
].value
6772 || data
[i
].data
[j
].error
);
6775 so_puts(store
, n
? "<P><LI>" : "<P>");
6777 if(data
[i
].data
[j
].comment
){
6779 _("With the provided comment:<P><BLOCKQUOTE>"));
6780 so_puts(store
, data
[i
].data
[j
].comment
);
6781 so_puts(store
, "</BLOCKQUOTE><P>");
6784 if(data
[i
].data
[j
].value
){
6786 && !strucmp(data
[i
].data
[j
].value
, "NO")){
6788 _("Posting is <EM>not</EM> allowed on this list"));
6791 so_puts(store
, "Select <A HREF=\"");
6792 so_puts(store
, data
[i
].data
[j
].value
);
6793 so_puts(store
, "\">HERE</A> to ");
6794 so_puts(store
, (data
[i
].field
.action
)
6795 ? data
[i
].field
.action
6799 so_puts(store
, ".");
6802 if(data
[i
].data
[j
].error
){
6803 so_puts(store
, "<P>Unfortunately, Alpine can not offer");
6804 so_puts(store
, " to take direct action based upon it");
6805 so_puts(store
, " because it was improperly formatted.");
6806 so_puts(store
, " The unrecognized data associated with");
6807 so_puts(store
, " the \"");
6808 so_puts(store
, data
[i
].field
.name
);
6809 so_puts(store
, "\" header field was:<P><BLOCKQUOTE>");
6810 so_puts(store
, data
[i
].data
[j
].error
);
6811 so_puts(store
, "</BLOCKQUOTE>");
6814 so_puts(store
, "<P>");
6818 so_puts(store
, "</OL>");
6822 so_puts(store
, "</UL>");
6824 so_puts(store
, "</BODY></HTML>");
6832 list_mgmt_screen(STORE_S
*html
)
6838 HANDLE_S
*handles
= NULL
;
6842 so_seek(html
, 0L, 0);
6843 gf_set_so_readc(&gc
, html
);
6845 init_handles(&handles
);
6847 if((store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) != NULL
){
6848 gf_set_so_writec(&pc
, store
);
6851 gf_link_filter(gf_html2plain
,
6852 gf_html2plain_opt(NULL
, ps_global
->ttyo
->screen_cols
,
6853 non_messageview_margin(), &handles
, NULL
, 0));
6855 error
= gf_pipe(gc
, pc
);
6857 gf_clear_so_writec(store
);
6862 memset(&sargs
, 0, sizeof(SCROLL_S
));
6863 sargs
.text
.text
= so_text(store
);
6864 sargs
.text
.src
= CharStar
;
6865 sargs
.text
.desc
= "list commands";
6866 sargs
.text
.handles
= handles
;
6868 sargs
.start
.on
= Offset
;
6869 sargs
.start
.loc
.offset
= offset
;
6872 sargs
.bar
.title
= _("MAIL LIST COMMANDS");
6873 sargs
.bar
.style
= MessageNumber
;
6874 sargs
.resize_exit
= 1;
6875 sargs
.help
.text
= h_special_list_commands
;
6876 sargs
.help
.title
= _("HELP FOR LIST COMMANDS");
6877 sargs
.keys
.menu
= &listmgr_keymenu
;
6878 setbitmap(sargs
.keys
.bitmap
);
6880 clrbitn(LM_TRY_KEY
, sargs
.keys
.bitmap
);
6881 clrbitn(LM_PREV_KEY
, sargs
.keys
.bitmap
);
6882 clrbitn(LM_NEXT_KEY
, sargs
.keys
.bitmap
);
6885 cmd
= scrolltool(&sargs
);
6886 offset
= sargs
.start
.loc
.offset
;
6892 free_handles(&handles
);
6893 gf_clear_so_readc(html
);
6895 while(cmd
== MC_RESIZE
);
6899 /*----------------------------------------------------------------------
6900 Prompt the user for the type of select desired
6902 NOTE: any and all functions that successfully exit the second
6903 switch() statement below (currently "select_*() functions"),
6904 *MUST* update the folder's MESSAGECACHE element's "searched"
6905 bits to reflect the search result. Functions using
6906 mail_search() get this for free, the others must update 'em
6909 Returns -1 if canceled without changing selection
6910 0 if selection may have changed
6913 aggregate_select(struct pine
*state
, MSGNO_S
*msgmap
, int q_line
, CmdWhere in_index
)
6915 long i
, diff
, old_tot
, msgno
, raw
;
6916 int q
= 0, rv
= 0, narrow
= 0, hidden
, ret
= -1;
6919 SEARCHSET
*limitsrch
= NULL
;
6921 extern MAILSTREAM
*mm_search_stream
;
6922 extern long mm_search_count
;
6924 hidden
= any_lflagged(msgmap
, MN_HIDE
) > 0L;
6925 mm_search_stream
= state
->mail_stream
;
6926 mm_search_count
= 0L;
6928 if(is_imap_stream(state
->mail_stream
) && XGMEXT1(state
->mail_stream
))
6929 sel_opts
= THRD_INDX() ? sel_opts6
: sel_opts5
;
6931 sel_opts
= THRD_INDX() ? sel_opts4
: sel_opts2
;
6934 sel_opts
[SEL_OPTS_THREAD
].ch
= SEL_OPTS_THREAD_CH
;
6937 if(is_imap_stream(state
->mail_stream
) && XGMEXT1(state
->mail_stream
)){
6938 sel_opts
[SEL_OPTS_THREAD
].ch
= SEL_OPTS_XGMEXT_CH
;
6939 sel_opts
[SEL_OPTS_THREAD
].rval
= sel_opts
[SEL_OPTS_XGMEXT
].rval
;
6940 sel_opts
[SEL_OPTS_THREAD
].name
= sel_opts
[SEL_OPTS_XGMEXT
].name
;
6941 sel_opts
[SEL_OPTS_THREAD
].label
= sel_opts
[SEL_OPTS_XGMEXT
].label
;
6942 sel_opts
[SEL_OPTS_XGMEXT
].ch
= -1;
6945 sel_opts
[SEL_OPTS_THREAD
].ch
= -1;
6948 if((old_tot
= any_lflagged(msgmap
, MN_SLCT
)) != 0){
6951 thrd
= fetch_thread(state
->mail_stream
,
6952 mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
6953 /* check if whole thread is selected or not */
6955 count_lflags_in_thread(state
->mail_stream
,thrd
,msgmap
,MN_SLCT
)
6957 count_lflags_in_thread(state
->mail_stream
,thrd
,msgmap
,MN_NONE
))
6960 sel_opts1
[1].label
= i
? N_("unselect Curthrd") : N_("select Curthrd");
6963 i
= get_lflag(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
6965 sel_opts1
[1].label
= i
? N_("unselect Cur") : N_("select Cur");
6968 sel_opts
+= 2; /* disable extra options */
6969 if(is_imap_stream(state
->mail_stream
) && XGMEXT1(state
->mail_stream
))
6970 q
= double_radio_buttons(_(sel_pmt1
), q_line
, sel_opts1
, 'c', 'x', NO_HELP
,
6973 q
= radio_buttons(_(sel_pmt1
), q_line
, sel_opts1
, 'c', 'x', NO_HELP
,
6976 case 'f' : /* flip selection */
6978 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++){
6980 q
= !get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
);
6981 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, q
);
6983 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, !q
);
6985 mn_reset_cur(msgmap
, msgno
= i
);
6991 case 'n' : /* narrow selection */
6993 case 'b' : /* broaden selection */
6994 q
= 0; /* offer criteria prompt */
6997 case 'c' : /* Un/Select Current */
6998 case 'a' : /* Unselect All */
6999 case 'x' : /* cancel */
7003 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
7004 "Unsupported Select option");
7011 if(is_imap_stream(state
->mail_stream
) && XGMEXT1(state
->mail_stream
))
7012 q
= double_radio_buttons(sel_pmt2
, q_line
, sel_opts
, 'c', 'x', NO_HELP
,
7015 q
= radio_buttons(sel_pmt2
, q_line
, sel_opts
, 'c', 'x', NO_HELP
,
7016 RB_NORM
|RB_RET_HELP
);
7019 helper(h_index_cmd_select
, _("HELP FOR SELECT"), HLPD_SIMPLE
);
7020 ps_global
->mangled_screen
= 1;
7028 * The purpose of this is to add the appropriate searchset to the
7029 * search so that the search can be limited to only looking at what
7030 * it needs to look at. That is, if we are narrowing then we only need
7031 * to look at messages which are already selected, and if we are
7032 * broadening, then we only need to look at messages which are not
7033 * yet selected. This routine will work whether or not
7034 * limiting_searchset properly limits the search set. In particular,
7035 * the searchset returned by limiting_searchset may include messages
7036 * which really shouldn't be included. We do that because a too-large
7037 * searchset will break some IMAP servers. It is even possible that it
7038 * becomes inefficient to send the whole set. If the select function
7039 * frees limitsrch, it should be sure to set it to NULL so we won't
7040 * try freeing it again here.
7042 limitsrch
= limiting_searchset(state
->mail_stream
, narrow
);
7045 * NOTE: See note about MESSAGECACHE "searched" bits above!
7048 case 'x': /* cancel */
7049 cmd_cancelled("Select command");
7052 case 'c' : /* select/unselect current */
7053 (void) select_by_current(state
, msgmap
, in_index
);
7057 case 'a' : /* select/unselect all */
7058 msgno
= any_lflagged(msgmap
, MN_SLCT
);
7059 diff
= (!msgno
) ? mn_get_total(msgmap
) : 0L;
7061 agg_select_all(state
->mail_stream
, msgmap
, &diff
,
7062 any_lflagged(msgmap
, MN_SLCT
) <= 0L);
7063 q_status_message4(SM_ORDER
,0,2,
7064 "%s%s message%s %sselected",
7065 msgno
? "" : "All ", comatose(diff
),
7066 plural(diff
), msgno
? "UN" : "");
7069 case 'n' : /* Select by Number */
7072 rv
= select_by_thrd_number(state
->mail_stream
, msgmap
, &limitsrch
);
7074 rv
= select_by_number(state
->mail_stream
, msgmap
, &limitsrch
);
7078 case 'd' : /* Select by Date */
7080 rv
= select_by_date(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
7084 case 't' : /* Text */
7086 rv
= select_by_text(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
7090 case 'z' : /* Size */
7092 rv
= select_by_size(state
->mail_stream
, &limitsrch
);
7095 case 's' : /* Status */
7097 rv
= select_by_status(state
->mail_stream
, &limitsrch
);
7100 case 'k' : /* Keyword */
7102 rv
= select_by_keyword(state
->mail_stream
, &limitsrch
);
7105 case 'r' : /* Rule */
7107 rv
= select_by_rule(state
->mail_stream
, &limitsrch
);
7110 case 'h' : /* Thread */
7112 rv
= select_by_thread(state
->mail_stream
, msgmap
, &limitsrch
);
7115 case 'g' : /* X-GM-EXT-1 */
7117 rv
= select_by_gm_content(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
), &limitsrch
);
7121 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
7122 "Unsupported Select option");
7127 mail_free_searchset(&limitsrch
);
7129 if(rv
) /* bad return value.. */
7130 return(ret
); /* error already displayed */
7132 if(narrow
) /* make sure something was selected */
7133 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++)
7134 if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
7135 && raw
<= state
->mail_stream
->nmsgs
7136 && (mc
= mail_elt(state
->mail_stream
, raw
)) && mc
->searched
){
7137 if(get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
))
7144 if(mm_search_count
){
7146 * loop thru all the messages, adjusting local flag bits
7147 * based on their "searched" bit...
7149 for(i
= 1L, msgno
= 0L; i
<= mn_get_total(msgmap
); i
++)
7151 /* turning OFF selectedness if the "searched" bit isn't lit. */
7152 if(get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
)){
7153 if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
7154 && raw
<= state
->mail_stream
->nmsgs
7155 && (mc
= mail_elt(state
->mail_stream
, raw
))
7158 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, 0);
7160 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, 1);
7162 /* adjust current message in case we unselect and hide it */
7163 else if(msgno
< mn_get_cur(msgmap
)
7165 || !get_lflag(state
->mail_stream
, msgmap
,
7170 else if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
7171 && raw
<= state
->mail_stream
->nmsgs
7172 && (mc
= mail_elt(state
->mail_stream
, raw
)) && mc
->searched
){
7173 /* turn ON selectedness if "searched" bit is lit. */
7174 if(!get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
)){
7176 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, 1);
7178 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, 0);
7182 /* if we're zoomed and the current message was unselected */
7184 && get_lflag(state
->mail_stream
,msgmap
,mn_get_cur(msgmap
),MN_HIDE
))
7185 mn_reset_cur(msgmap
, msgno
);
7190 q_status_message4(SM_ORDER
, 3, 3,
7191 "%s. %s message%s remain%s selected.",
7193 ? "No change resulted"
7194 : "No messages in intersection",
7195 comatose(old_tot
), plural(old_tot
),
7196 (old_tot
== 1L) ? "s" : "");
7198 q_status_message(SM_ORDER
, 3, 3,
7199 _("No change resulted. Matching messages already selected."));
7201 q_status_message1(SM_ORDER
| SM_DING
, 3, 3,
7202 _("Select failed. No %smessages selected."),
7203 old_tot
? _("additional ") : "");
7206 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
7207 "Select matched %ld message%s. %s %smessage%s %sselected.",
7208 (diff
> 0) ? diff
: old_tot
+ diff
,
7209 plural((diff
> 0) ? diff
: old_tot
+ diff
),
7210 comatose((diff
> 0) ? any_lflagged(msgmap
, MN_SLCT
) : -diff
),
7211 (diff
> 0) ? "total " : "",
7212 plural((diff
> 0) ? any_lflagged(msgmap
, MN_SLCT
) : -diff
),
7213 (diff
> 0) ? "" : "UN");
7214 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
7215 q_status_message(SM_ORDER
, 3, 3, tmp_20k_buf
);
7218 q_status_message2(SM_ORDER
, 3, 3, _("Select matched %s message%s!"),
7219 comatose(diff
), plural(diff
));
7225 /*----------------------------------------------------------------------
7226 Toggle the state of the current message
7228 Args: state -- pointer pine's state variables
7229 msgmap -- message collection to operate on
7230 in_index -- in the message index view
7231 Returns: TRUE if current marked selected, FALSE otw
7234 select_by_current(struct pine
*state
, MSGNO_S
*msgmap
, CmdWhere in_index
)
7237 int all_selected
= 0;
7238 unsigned long was
, tot
, rawno
;
7241 cur
= mn_get_cur(msgmap
);
7244 thrd
= fetch_thread(state
->mail_stream
, mn_m2raw(msgmap
, cur
));
7248 was
= count_lflags_in_thread(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
);
7249 tot
= count_lflags_in_thread(state
->mail_stream
, thrd
, msgmap
, MN_NONE
);
7254 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
, 0);
7255 if(any_lflagged(msgmap
, MN_HIDE
) > 0L){
7256 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_HIDE
, 1);
7258 * See if there's anything left to zoom on. If so,
7259 * pick an adjacent one for highlighting, else make
7260 * sure nothing is left hidden...
7262 if(any_lflagged(msgmap
, MN_SLCT
)){
7263 mn_inc_cur(state
->mail_stream
, msgmap
,
7264 (in_index
== View
&& THREADING()
7265 && sp_viewing_a_thread(state
->mail_stream
))
7267 : (in_index
== View
)
7268 ? MH_ANYTHD
: MH_NONE
);
7269 if(mn_get_cur(msgmap
) == cur
)
7270 mn_dec_cur(state
->mail_stream
, msgmap
,
7271 (in_index
== View
&& THREADING()
7272 && sp_viewing_a_thread(state
->mail_stream
))
7274 : (in_index
== View
)
7275 ? MH_ANYTHD
: MH_NONE
);
7277 else /* clear all hidden flags */
7278 (void) unzoom_index(state
, state
->mail_stream
, msgmap
);
7282 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
, 1);
7284 q_status_message3(SM_ORDER
, 0, 2, "%s message%s %sselected",
7285 comatose(all_selected
? was
: tot
-was
),
7286 plural(all_selected
? was
: tot
-was
),
7287 all_selected
? "UN" : "");
7289 /* collapsed thread */
7291 && ((rawno
= mn_m2raw(msgmap
, cur
)) != 0L)
7292 && ((thrd
= fetch_thread(state
->mail_stream
, rawno
)) != NULL
)
7293 && (thrd
&& thrd
->next
&& get_lflag(state
->mail_stream
, NULL
, rawno
, MN_COLL
))){
7295 * This doesn't work quite the same as the colon command works, but
7296 * it is arguably doing the correct thing. The difference is
7297 * that aggregate_select will zoom after selecting back where it
7298 * was called from, but selecting a thread with colon won't zoom.
7299 * Maybe it makes sense to zoom after a select but not after a colon
7300 * command even though they are very similar.
7302 thread_command(state
, state
->mail_stream
, msgmap
, ':', -FOOTER_ROWS(state
));
7306 get_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
)) != 0){ /* set? */
7307 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
, 0);
7308 if(any_lflagged(msgmap
, MN_HIDE
) > 0L){
7309 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_HIDE
, 1);
7311 * See if there's anything left to zoom on. If so,
7312 * pick an adjacent one for highlighting, else make
7313 * sure nothing is left hidden...
7315 if(any_lflagged(msgmap
, MN_SLCT
)){
7316 mn_inc_cur(state
->mail_stream
, msgmap
,
7317 (in_index
== View
&& THREADING()
7318 && sp_viewing_a_thread(state
->mail_stream
))
7320 : (in_index
== View
)
7321 ? MH_ANYTHD
: MH_NONE
);
7322 if(mn_get_cur(msgmap
) == cur
)
7323 mn_dec_cur(state
->mail_stream
, msgmap
,
7324 (in_index
== View
&& THREADING()
7325 && sp_viewing_a_thread(state
->mail_stream
))
7327 : (in_index
== View
)
7328 ? MH_ANYTHD
: MH_NONE
);
7330 else /* clear all hidden flags */
7331 (void) unzoom_index(state
, state
->mail_stream
, msgmap
);
7335 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
, 1);
7337 q_status_message2(SM_ORDER
, 0, 2, "Message %s %sselected",
7338 long2string(cur
), all_selected
? "UN" : "");
7342 return(!all_selected
);
7346 /*----------------------------------------------------------------------
7347 Prompt the user for the command to perform on selected messages
7349 Args: state -- pointer pine's state variables
7350 msgmap -- message collection to operate on
7351 q_line -- line on display to write prompts
7352 Returns: 1 if the selected messages are suitably commanded,
7353 0 if the choice to pick the command was declined
7357 apply_command(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
,
7358 UCS preloadkeystroke
, int flags
, int q_line
)
7360 int i
= 8, /* number of static entries in sel_opts3 */
7364 agg
= (flags
& AC_FROM_THREAD
) ? MCMD_AGG_2
: MCMD_AGG
;
7369 * To do this "right", we really ought to have access to the keymenu
7370 * here and change the typed command into a real command by running
7371 * it through menu_command. Then the switch below would be against
7372 * results from menu_command. If we did that we'd also pass the
7373 * results of menu_command in as preloadkeystroke instead of passing
7374 * the keystroke itself. But we don't have the keymenu handy,
7375 * so we have to fake it. The only complication that we run into
7376 * is that KEY_DEL is an escape sequence so we change a typed
7377 * KEY_DEL esc seq into the letter D.
7380 if(!preloadkeystroke
){
7381 if(F_ON(F_ENABLE_FLAG
,state
)){ /* flag? */
7382 sel_opts3
[i
].ch
= '*';
7383 sel_opts3
[i
].rval
= '*';
7384 sel_opts3
[i
].name
= "*";
7385 sel_opts3
[i
++].label
= N_("Flag");
7388 if(F_ON(F_ENABLE_PIPE
,state
)){ /* pipe? */
7389 sel_opts3
[i
].ch
= '|';
7390 sel_opts3
[i
].rval
= '|';
7391 sel_opts3
[i
].name
= "|";
7392 sel_opts3
[i
++].label
= N_("Pipe");
7395 if(F_ON(F_ENABLE_BOUNCE
,state
)){ /* bounce? */
7396 sel_opts3
[i
].ch
= 'b';
7397 sel_opts3
[i
].rval
= 'b';
7398 sel_opts3
[i
].name
= "B";
7399 sel_opts3
[i
++].label
= N_("Bounce");
7402 if(flags
& AC_FROM_THREAD
){
7403 if(flags
& (AC_COLL
| AC_EXPN
)){
7404 sel_opts3
[i
].ch
= '/';
7405 sel_opts3
[i
].rval
= '/';
7406 sel_opts3
[i
].name
= "/";
7407 sel_opts3
[i
++].label
= (flags
& AC_COLL
) ? N_("Collapse")
7411 sel_opts3
[i
].ch
= ';';
7412 sel_opts3
[i
].rval
= ';';
7413 sel_opts3
[i
].name
= ";";
7414 if(flags
& AC_UNSEL
)
7415 sel_opts3
[i
++].label
= N_("UnSelect");
7417 sel_opts3
[i
++].label
= N_("Select");
7420 if(F_ON(F_ENABLE_PRYNT
, state
)){ /* this one is invisible */
7421 sel_opts3
[i
].ch
= 'y';
7422 sel_opts3
[i
].rval
= '%';
7423 sel_opts3
[i
].name
= "";
7424 sel_opts3
[i
++].label
= "";
7427 if(!is_imap_stream(stream
) || LEVELUIDPLUS(stream
)){ /* expunge selected messages */
7428 sel_opts3
[i
].ch
= 'x';
7429 sel_opts3
[i
].rval
= 'x';
7430 sel_opts3
[i
].name
= "X";
7431 sel_opts3
[i
++].label
= N_("Expunge");
7434 if(nonempty_patterns(ROLE_DO_ROLES
, &pstate
) && first_pattern(&pstate
)){
7435 sel_opts3
[i
].ch
= '#';
7436 sel_opts3
[i
].rval
= '#';
7437 sel_opts3
[i
].name
= "#";
7438 sel_opts3
[i
++].label
= N_("Set Role");
7441 sel_opts3
[i
].ch
= KEY_DEL
; /* also invisible */
7442 sel_opts3
[i
].rval
= 'd';
7443 sel_opts3
[i
].name
= "";
7444 sel_opts3
[i
++].label
= "";
7446 sel_opts3
[i
].ch
= -1;
7448 snprintf(prompt
, sizeof(prompt
), "%s command : ",
7449 (flags
& AC_FROM_THREAD
) ? "THREAD" : "APPLY");
7450 prompt
[sizeof(prompt
)-1] = '\0';
7451 cmd
= double_radio_buttons(prompt
, q_line
, sel_opts3
, 'z', 'c', NO_HELP
,
7457 if(preloadkeystroke
== KEY_DEL
)
7460 if(preloadkeystroke
< 0x80 && isupper((int) preloadkeystroke
))
7461 cmd
= tolower((int) preloadkeystroke
);
7463 cmd
= (int) preloadkeystroke
; /* shouldn't happen */
7468 case 'd' : /* delete */
7469 we_cancel
= busy_cue(NULL
, NULL
, 1);
7470 rv
= cmd_delete(state
, msgmap
, agg
, NULL
); /* don't advance or offer "TAB" */
7475 case 'u' : /* undelete */
7476 we_cancel
= busy_cue(NULL
, NULL
, 1);
7477 rv
= cmd_undelete(state
, msgmap
, agg
);
7482 case 'r' : /* reply */
7483 rv
= cmd_reply(state
, msgmap
, agg
, NULL
);
7486 case 'f' : /* Forward */
7487 rv
= cmd_forward(state
, msgmap
, agg
, NULL
);
7490 case '%' : /* print */
7491 rv
= cmd_print(state
, msgmap
, agg
, MsgIndx
);
7494 case 't' : /* take address */
7495 rv
= cmd_take_addr(state
, msgmap
, agg
);
7498 case 's' : /* save */
7499 rv
= cmd_save(state
, stream
, msgmap
, agg
, MsgIndx
);
7502 case 'e' : /* export */
7503 rv
= cmd_export(state
, msgmap
, q_line
, agg
);
7506 case '|' : /* pipe */
7507 rv
= cmd_pipe(state
, msgmap
, agg
);
7510 case '*' : /* flag */
7511 we_cancel
= busy_cue(NULL
, NULL
, 1);
7512 rv
= cmd_flag(state
, msgmap
, agg
);
7517 case 'b' : /* bounce */
7518 rv
= cmd_bounce(state
, msgmap
, agg
, NULL
);
7522 collapse_or_expand(state
, stream
, msgmap
,
7523 F_ON(F_SLASH_COLL_ENTIRE
, ps_global
)
7525 : mn_get_cur(msgmap
));
7529 select_thread_stmp(state
, stream
, msgmap
);
7532 case 'x' : /* Expunge */
7533 rv
= cmd_expunge(state
, stream
, msgmap
, agg
);
7536 case 'c' : /* cancel */
7537 cmd_cancelled((flags
& AC_FROM_THREAD
) ? "Thread command"
7542 if(nonempty_patterns(ROLE_DO_ROLES
, &pstate
) && first_pattern(&pstate
)){
7543 static ESCKEY_S choose_role
[] = {
7544 {'r', 'r', "R", N_("Reply")},
7545 {'f', 'f', "F", N_("Forward")},
7546 {'b', 'b', "B", N_("Bounce")},
7550 ACTION_S
*role
= NULL
;
7552 action
= radio_buttons(_("Reply, Forward or Bounce using a role? "),
7553 -FOOTER_ROWS(state
), choose_role
,
7554 'r', 'x', h_role_aggregate
, RB_NORM
);
7555 if(action
== 'r' || action
== 'f' || action
== 'b'){
7556 void (*prev_screen
)(struct pine
*) = NULL
, (*redraw
)(void) = NULL
;
7558 redraw
= state
->redrawer
;
7559 state
->redrawer
= NULL
;
7560 prev_screen
= state
->prev_screen
;
7562 state
->next_screen
= SCREEN_FUN_NULL
;
7564 if(role_select_screen(state
, &role
,
7565 action
== 'f' ? MC_FORWARD
:
7566 action
== 'r' ? MC_REPLY
:
7567 action
== 'b' ? MC_BOUNCE
: 0) < 0){
7568 cmd_cancelled(action
== 'f' ? _("Forward") :
7569 action
== 'r' ? _("Reply") : _("Bounce"));
7570 state
->next_screen
= prev_screen
;
7571 state
->redrawer
= redraw
;
7572 state
->mangled_screen
= 1;
7576 role
= combine_inherited_role(role
);
7578 role
= (ACTION_S
*) fs_get(sizeof(*role
));
7579 memset((void *) role
, 0, sizeof(*role
));
7580 role
->nick
= cpystr("Default Role");
7583 state
->redrawer
= NULL
;
7586 (void) cmd_reply(state
, msgmap
, agg
, role
);
7590 (void) cmd_forward(state
, msgmap
, agg
, role
);
7594 (void) cmd_bounce(state
, msgmap
, agg
, role
);
7604 state
->next_screen
= prev_screen
;
7605 state
->redrawer
= redraw
;
7606 state
->mangled_screen
= 1;
7612 case 'z' : /* default */
7613 q_status_message(SM_INFO
, 0, 2,
7614 "Cancelled, there is no default command");
7626 * Select by message number ranges.
7627 * Sets searched bits in mail_elts
7629 * Args limitsrch -- limit search to this searchset
7631 * Returns 0 on success.
7634 select_by_number(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**limitsrch
)
7638 char number1
[16], number2
[16], numbers
[80], *p
, *t
;
7643 ps_global
->mangled_footer
= 1;
7646 int flags
= OE_APPEND_CURRENT
;
7648 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
7649 sizeof(numbers
), _(select_num
), NULL
, help
, &flags
);
7654 help
= (help
== NO_HELP
) ? h_select_by_num
: NO_HELP
;
7658 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
7659 if(!isspace((unsigned char)*p
))
7664 if(r
== 1 || numbers
[0] == '\0'){
7665 cmd_cancelled("Selection by number");
7672 for(n1
= 1; n1
<= stream
->nmsgs
; n1
++)
7673 if((mc
= mail_elt(stream
, n1
)) != NULL
)
7674 mc
->searched
= 0; /* clear searched bits */
7676 for(p
= numbers
; *p
; p
++){
7678 while(*p
&& isdigit((unsigned char)*p
))
7684 if(number1
[0] == '\0'){
7686 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7687 _("Invalid number range, missing number before \"-\": %s"),
7691 else if(!strucmp("end", p
)){
7695 else if(!strucmp("$", p
)){
7704 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7705 _("Invalid message number: %s"), numbers
);
7711 n1
= mn_get_total(msgmap
);
7713 n1
= mn_get_cur(msgmap
);
7714 else if((n1
= atol(number1
)) < 1L || n1
> mn_get_total(msgmap
)){
7715 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7716 _("\"%s\" out of message number range"),
7723 while(*++p
&& isdigit((unsigned char)*p
))
7729 if(number2
[0] == '\0'){
7730 if(!strucmp("end", p
)){
7734 else if(!strucmp(p
, "$")){
7743 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7744 _("Invalid number range, missing number after \"-\": %s"),
7751 n2
= mn_get_total(msgmap
);
7753 n2
= mn_get_cur(msgmap
);
7754 else if((n2
= atol(number2
)) < 1L || n2
> mn_get_total(msgmap
)){
7755 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7756 _("\"%s\" out of message number range"),
7764 strncpy(t
, long2string(n1
), sizeof(t
));
7765 t
[sizeof(t
)-1] = '\0';
7766 q_status_message2(SM_ORDER
| SM_DING
, 0, 2,
7767 _("Invalid reverse message number range: %s-%s"),
7768 t
, long2string(n2
));
7772 for(;n1
<= n2
; n1
++){
7773 raw
= mn_m2raw(msgmap
, n1
);
7775 && (!(limitsrch
&& *limitsrch
)
7776 || in_searchset(*limitsrch
, (unsigned long) raw
)))
7777 mm_searched(stream
, raw
);
7781 raw
= mn_m2raw(msgmap
, n1
);
7783 && (!(limitsrch
&& *limitsrch
)
7784 || in_searchset(*limitsrch
, (unsigned long) raw
)))
7785 mm_searched(stream
, raw
);
7797 * Select by thread number ranges.
7798 * Sets searched bits in mail_elts
7800 * Args limitsrch -- limit search to this searchset
7802 * Returns 0 on success.
7805 select_by_thrd_number(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**msgset
)
7809 char number1
[16], number2
[16], numbers
[80], *p
, *t
;
7811 PINETHRD_S
*thrd
= NULL
, *th
;
7815 ps_global
->mangled_footer
= 1;
7818 int flags
= OE_APPEND_CURRENT
;
7820 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
7821 sizeof(numbers
), _(select_num
), NULL
, help
, &flags
);
7826 help
= (help
== NO_HELP
) ? h_select_by_thrdnum
: NO_HELP
;
7830 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
7831 if(!isspace((unsigned char)*p
))
7836 if(r
== 1 || numbers
[0] == '\0'){
7837 cmd_cancelled("Selection by number");
7844 for(n1
= 1; n1
<= stream
->nmsgs
; n1
++)
7845 if((mc
= mail_elt(stream
, n1
)) != NULL
)
7846 mc
->searched
= 0; /* clear searched bits */
7848 for(p
= numbers
; *p
; p
++){
7850 while(*p
&& isdigit((unsigned char)*p
))
7856 if(number1
[0] == '\0'){
7858 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7859 _("Invalid number range, missing number before \"-\": %s"),
7863 else if(!strucmp("end", p
)){
7867 else if(!strucmp(p
, "$")){
7876 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7877 _("Invalid thread number: %s"), numbers
);
7883 n1
= msgmap
->max_thrdno
;
7885 th
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
7888 else if((n1
= atol(number1
)) < 1L || n1
> msgmap
->max_thrdno
){
7889 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7890 _("\"%s\" out of thread number range"),
7898 while(*++p
&& isdigit((unsigned char)*p
))
7904 if(number2
[0] == '\0'){
7905 if(!strucmp("end", p
)){
7909 else if(!strucmp("$", p
)){
7918 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7919 _("Invalid number range, missing number after \"-\": %s"),
7926 n2
= msgmap
->max_thrdno
;
7928 th
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
7931 else if((n2
= atol(number2
)) < 1L || n2
> msgmap
->max_thrdno
){
7932 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7933 _("\"%s\" out of thread number range"),
7941 strncpy(t
, long2string(n1
), sizeof(t
));
7942 t
[sizeof(t
)-1] = '\0';
7943 q_status_message2(SM_ORDER
| SM_DING
, 0, 2,
7944 _("Invalid reverse message number range: %s-%s"),
7945 t
, long2string(n2
));
7949 for(;n1
<= n2
; n1
++){
7950 thrd
= find_thread_by_number(stream
, msgmap
, n1
, thrd
);
7953 set_search_bit_for_thread(stream
, thrd
, msgset
);
7957 thrd
= find_thread_by_number(stream
, msgmap
, n1
, NULL
);
7960 set_search_bit_for_thread(stream
, thrd
, msgset
);
7972 * Select by message dates.
7973 * Sets searched bits in mail_elts
7975 * Args limitsrch -- limit search to this searchset
7977 * Returns 0 on success.
7980 select_by_date(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
, SEARCHSET
**limitsrch
)
7982 int r
, we_cancel
= 0, when
= 0;
7983 char date
[100], defdate
[100], prompt
[128];
7984 time_t seldate
= time(0);
7985 struct tm
*seldate_tm
;
7988 static struct _tense
{
7993 {"were ", "SENT SINCE", " (inclusive)"},
7994 {"were ", "SENT BEFORE", " (exclusive)"},
7995 {"were ", "SENT ON", "" },
7996 {"", "ARRIVED SINCE", " (inclusive)"},
7997 {"", "ARRIVED BEFORE", " (exclusive)"},
7998 {"", "ARRIVED ON", "" }
8002 ps_global
->mangled_footer
= 1;
8006 * If talking to an old server, default to SINCE instead of
8007 * SENTSINCE, which was added later.
8009 if(is_imap_stream(stream
) && !modern_imap_stream(stream
))
8013 int flags
= OE_APPEND_CURRENT
;
8015 seldate_tm
= localtime(&seldate
);
8016 snprintf(defdate
, sizeof(defdate
), "%.2d-%.4s-%.4d", seldate_tm
->tm_mday
,
8017 month_abbrev(seldate_tm
->tm_mon
+ 1),
8018 seldate_tm
->tm_year
+ 1900);
8019 defdate
[sizeof(defdate
)-1] = '\0';
8020 snprintf(prompt
,sizeof(prompt
),"Select messages which %s%s%s [%s]: ",
8021 tense
[when
].preamble
, tense
[when
].range
,
8022 tense
[when
].scope
, defdate
);
8023 prompt
[sizeof(prompt
)-1] = '\0';
8024 r
= optionally_enter(date
,-FOOTER_ROWS(ps_global
), 0, sizeof(date
),
8025 prompt
, sel_date_opt
, help
, &flags
);
8028 cmd_cancelled("Selection by date");
8032 help
= (help
== NO_HELP
) ? h_select_date
: NO_HELP
;
8043 if(stream
&& (rawno
= mn_m2raw(msgmap
, msgno
)) > 0L
8044 && rawno
<= stream
->nmsgs
8045 && (mc
= mail_elt(stream
, rawno
))){
8047 /* cache not filled in yet? */
8051 if(stream
->dtb
&& stream
->dtb
->flags
& DR_NEWS
){
8053 ulong2string(mail_uid(stream
, rawno
)),
8055 seq
[sizeof(seq
)-1] = '\0';
8056 mail_fetch_overview(stream
, seq
, NULL
);
8059 strncpy(seq
, long2string(rawno
),
8061 seq
[sizeof(seq
)-1] = '\0';
8062 mail_fetch_fast(stream
, seq
, 0L);
8066 /* mail_date returns fixed field width date */
8067 mail_date(date
, mc
);
8074 case 12 : /* set default to PREVIOUS day */
8078 case 13 : /* set default to NEXT day */
8083 when
= (when
+1) % (sizeof(tense
) / sizeof(struct _tense
));
8090 removing_leading_white_space(date
);
8091 removing_trailing_white_space(date
);
8093 strncpy(date
, defdate
, sizeof(date
));
8094 date
[sizeof(date
)-1] = '\0';
8100 if((pgm
= mail_newsearchpgm()) != NULL
){
8102 short converted_date
;
8104 if(mail_parse_date(&elt
, (unsigned char *) date
)){
8105 converted_date
= mail_shortdate(elt
.year
, elt
.month
, elt
.day
);
8109 pgm
->sentsince
= converted_date
;
8112 pgm
->sentbefore
= converted_date
;
8115 pgm
->senton
= converted_date
;
8118 pgm
->since
= converted_date
;
8121 pgm
->before
= converted_date
;
8124 pgm
->on
= converted_date
;
8128 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
8130 if(ps_global
&& ps_global
->ttyo
){
8131 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8132 ps_global
->mangled_footer
= 1;
8135 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8137 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
8142 /* we know this was freed in mail_search, let caller know */
8147 mail_free_searchpgm(&pgm
);
8148 q_status_message1(SM_ORDER
, 3, 3,
8149 _("Invalid date entered: %s"), date
);
8158 * Select by searching in message headers or body
8159 * using the x-gm-ext-1 capaility. This function
8160 * reads the input from the user and passes it to
8161 * the server directly. We need a x-gm-ext-1 variable
8162 * in the search pgm to carry this information to the
8165 * Sets searched bits in mail_elts
8167 * Args limitsrch -- limit search to this searchset
8169 * Returns 0 on success.
8172 select_by_gm_content(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
, SEARCHSET
**limitsrch
)
8174 int r
, we_cancel
= 0, rv
;
8176 char namehdr
[MAILTMPLEN
];
8180 ps_global
->mangled_footer
= 1;
8181 ekey
[0].ch
= ekey
[1].ch
= ekey
[2].ch
= ekey
[3].ch
= -1;
8183 strncpy(tmp
, sel_x_gm_ext
, sizeof(tmp
)-1);
8184 tmp
[sizeof(tmp
)-1] = '\0';
8190 int flags
= OE_APPEND_CURRENT
;
8192 r
= optionally_enter(namehdr
, -FOOTER_ROWS(ps_global
), 0,
8193 sizeof(namehdr
), tmp
, ekey
, help
, &flags
);
8199 help
= (help
== NO_HELP
) ? h_select_by_gm_content
: NO_HELP
;
8204 cmd_cancelled("Selection by content");
8208 removing_leading_white_space(namehdr
);
8210 if ((namehdr
[0] != '\0')
8211 && isspace((unsigned char) namehdr
[strlen(namehdr
) - 1]))
8212 removing_trailing_white_space(namehdr
);
8214 if (namehdr
[0] != '\0')
8218 if(ps_global
&& ps_global
->ttyo
){
8219 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8220 ps_global
->mangled_footer
= 1;
8223 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8225 rv
= agg_text_select(stream
, msgmap
, 'g', namehdr
, 0, 0, NULL
, "utf-8", limitsrch
);
8233 * Select by searching in message headers or body.
8234 * Sets searched bits in mail_elts
8236 * Args limitsrch -- limit search to this searchset
8238 * Returns 0 on success.
8241 select_by_text(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
, SEARCHSET
**limitsrch
)
8243 int r
, ku
, type
, we_cancel
= 0, flags
, rv
, ekeyi
= 0;
8244 int not = 0, me
= 0;
8245 char sstring
[80], savedsstring
[80], tmp
[128];
8246 char *p
, *sval
= NULL
;
8247 char buftmp
[MAILTMPLEN
], namehdr
[80];
8249 ENVELOPE
*env
= NULL
;
8251 unsigned flagsforhist
= 0;
8252 static HISTORY_S
*history
= NULL
;
8253 static char *recip
= "RECIPIENTS";
8254 static char *partic
= "PARTICIPANTS";
8255 static char *match_me
= N_("[Match_My_Addresses]");
8256 static char *dont_match_me
= N_("[Don't_Match_My_Addresses]");
8258 ps_global
->mangled_footer
= 1;
8259 savedsstring
[0] = '\0';
8260 ekey
[0].ch
= ekey
[1].ch
= ekey
[2].ch
= ekey
[3].ch
= -1;
8263 type
= radio_buttons(not ? _(sel_text_not
) : _(sel_text
),
8264 -FOOTER_ROWS(ps_global
), sel_text_opt
,
8265 's', 'x', NO_HELP
, RB_NORM
|RB_RET_HELP
);
8270 helper(h_select_text
, "HELP FOR SELECT BASED ON CONTENTS",
8272 ps_global
->mangled_screen
= 1;
8279 * prepare some friendly defaults...
8282 case 't' : /* address fields, offer To or From */
8287 sval
= (type
== 't') ? "TO" :
8288 (type
== 'f') ? "FROM" :
8289 (type
== 'c') ? "CC" :
8290 (type
== 'r') ? recip
: partic
;
8291 ekey
[ekeyi
].ch
= ctrl('T');
8292 ekey
[ekeyi
].name
= "^T";
8293 ekey
[ekeyi
].rval
= 10;
8294 /* TRANSLATORS: use Current To Address */
8295 ekey
[ekeyi
++].label
= N_("Cur To");
8296 ekey
[ekeyi
].ch
= ctrl('R');
8297 ekey
[ekeyi
].name
= "^R";
8298 ekey
[ekeyi
].rval
= 11;
8299 /* TRANSLATORS: use Current From Address */
8300 ekey
[ekeyi
++].label
= N_("Cur From");
8301 ekey
[ekeyi
].ch
= ctrl('W');
8302 ekey
[ekeyi
].name
= "^W";
8303 ekey
[ekeyi
].rval
= 12;
8304 /* TRANSLATORS: use Current Cc Address */
8305 ekey
[ekeyi
++].label
= N_("Cur Cc");
8306 ekey
[ekeyi
].ch
= ctrl('Y');
8307 ekey
[ekeyi
].name
= "^Y";
8308 ekey
[ekeyi
].rval
= 13;
8309 /* TRANSLATORS: Match Me means match my address */
8310 ekey
[ekeyi
++].label
= N_("Match Me");
8312 ekey
[ekeyi
].name
= "";
8313 ekey
[ekeyi
].rval
= 0;
8314 ekey
[ekeyi
++].label
= "";
8319 ekey
[ekeyi
].ch
= ctrl('X');
8320 ekey
[ekeyi
].name
= "^X";
8321 ekey
[ekeyi
].rval
= 14;
8322 /* TRANSLATORS: use Current Subject */
8323 ekey
[ekeyi
++].label
= N_("Cur Subject");
8335 strncpy(tmp
, "Name of HEADER to match : ", sizeof(tmp
)-1);
8336 tmp
[sizeof(tmp
)-1] = '\0';
8337 flags
= OE_APPEND_CURRENT
;
8343 r
= optionally_enter(namehdr
, -FOOTER_ROWS(ps_global
), 0,
8344 sizeof(namehdr
), tmp
, ekey
, NO_HELP
, &flags
);
8346 cmd_cancelled("Selection by text");
8349 removing_leading_white_space(namehdr
);
8351 while ((namehdr
[0] != '\0') && /* remove trailing ":" */
8352 (namehdr
[strlen(namehdr
) - 1] == ':'))
8353 namehdr
[strlen(namehdr
) - 1] = '\0';
8354 if ((namehdr
[0] != '\0')
8355 && isspace((unsigned char) namehdr
[strlen(namehdr
) - 1]))
8356 removing_trailing_white_space(namehdr
);
8360 if (strchr(namehdr
,' ') || strchr(namehdr
,'\t') ||
8361 strchr(namehdr
,':'))
8363 if (namehdr
[0] == '\0')
8373 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
8377 ekey
[ekeyi
].ch
= KEY_UP
;
8378 ekey
[ekeyi
].rval
= 30;
8379 ekey
[ekeyi
].name
= "";
8381 ekey
[ekeyi
++].label
= "";
8383 ekey
[ekeyi
].ch
= KEY_DOWN
;
8384 ekey
[ekeyi
].rval
= 31;
8385 ekey
[ekeyi
].name
= "";
8386 ekey
[ekeyi
++].label
= "";
8388 ekey
[ekeyi
].ch
= -1;
8392 init_hist(&history
, HISTSIZE
);
8394 if(ekey
[0].ch
> -1 && msgno
> 0L
8395 && !(env
=pine_mail_fetchstructure(stream
,mn_m2raw(msgmap
,msgno
),
8404 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8405 snprintf(tmp
, sizeof(tmp
), "String in message %s to NOT match : ", sval
);
8407 snprintf(tmp
, sizeof(tmp
), "String in message %s to match : ", sval
);
8409 if(items_in_hist(history
) > 0){
8410 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
8411 ekey
[ku
].label
= HISTORY_KEYLABEL
;
8412 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
8413 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
8417 ekey
[ku
].label
= "";
8418 ekey
[ku
+1].name
= "";
8419 ekey
[ku
+1].label
= "";
8422 flags
= OE_APPEND_CURRENT
| OE_KEEP_TRAILING_SPACE
;
8423 r
= optionally_enter(sstring
, -FOOTER_ROWS(ps_global
), 0,
8424 79, tmp
, ekey
, help
, &flags
);
8426 if(me
&& r
== 0 && ((!not && strcmp(sstring
, _(match_me
))) || (not && strcmp(sstring
, _(dont_match_me
)))))
8431 help
= (help
== NO_HELP
)
8433 ? ((type
== 'f') ? h_select_txt_not_from
8434 : (type
== 't') ? h_select_txt_not_to
8435 : (type
== 'c') ? h_select_txt_not_cc
8436 : (type
== 's') ? h_select_txt_not_subj
8437 : (type
== 'a') ? h_select_txt_not_all
8438 : (type
== 'r') ? h_select_txt_not_recip
8439 : (type
== 'p') ? h_select_txt_not_partic
8440 : (type
== 'b') ? h_select_txt_not_body
8442 : ((type
== 'f') ? h_select_txt_from
8443 : (type
== 't') ? h_select_txt_to
8444 : (type
== 'c') ? h_select_txt_cc
8445 : (type
== 's') ? h_select_txt_subj
8446 : (type
== 'a') ? h_select_txt_all
8447 : (type
== 'r') ? h_select_txt_recip
8448 : (type
== 'p') ? h_select_txt_partic
8449 : (type
== 'b') ? h_select_txt_body
8456 case 10 : /* To: default */
8457 if(env
&& env
->to
&& env
->to
->mailbox
){
8458 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->to
->mailbox
,
8459 env
->to
->host
? "@" : "",
8460 env
->to
->host
? env
->to
->host
: "");
8461 sstring
[sizeof(sstring
)-1] = '\0';
8465 case 11 : /* From: default */
8466 if(env
&& env
->from
&& env
->from
->mailbox
){
8467 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->from
->mailbox
,
8468 env
->from
->host
? "@" : "",
8469 env
->from
->host
? env
->from
->host
: "");
8470 sstring
[sizeof(sstring
)-1] = '\0';
8474 case 12 : /* Cc: default */
8475 if(env
&& env
->cc
&& env
->cc
->mailbox
){
8476 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->cc
->mailbox
,
8477 env
->cc
->host
? "@" : "",
8478 env
->cc
->host
? env
->cc
->host
: "");
8479 sstring
[sizeof(sstring
)-1] = '\0';
8483 case 13 : /* Match my addresses */
8485 snprintf(sstring
, sizeof(sstring
), "%s", not ? _(dont_match_me
) : _(match_me
));
8488 case 14 : /* Subject: default */
8489 if(env
&& env
->subject
&& env
->subject
[0]){
8492 snprintf(buftmp
, sizeof(buftmp
), "%.75s", env
->subject
);
8493 buftmp
[sizeof(buftmp
)-1] = '\0';
8494 q
= (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf
,
8495 SIZEOF_20KBUF
, buftmp
);
8496 if(q
!= env
->subject
){
8497 snprintf(savedsstring
, sizeof(savedsstring
), "%.70s", q
);
8498 savedsstring
[sizeof(savedsstring
)-1] = '\0';
8501 snprintf(sstring
, sizeof(sstring
), "%s", q
);
8502 sstring
[sizeof(sstring
)-1] = '\0';
8508 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8509 if((p
= get_prev_hist(history
, sstring
, flagsforhist
, NULL
)) != NULL
){
8510 strncpy(sstring
, p
, sizeof(sstring
));
8511 sstring
[sizeof(sstring
)-1] = '\0';
8512 if(history
->hist
[history
->curindex
]){
8513 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
8514 not = (flagsforhist
& 0x1) ? 1 : 0;
8515 me
= (flagsforhist
& 0x2) ? 1 : 0;
8524 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8525 if((p
= get_next_hist(history
, sstring
, flagsforhist
, NULL
)) != NULL
){
8526 strncpy(sstring
, p
, sizeof(sstring
));
8527 sstring
[sizeof(sstring
)-1] = '\0';
8528 if(history
->hist
[history
->curindex
]){
8529 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
8530 not = (flagsforhist
& 0x1) ? 1 : 0;
8531 me
= (flagsforhist
& 0x2) ? 1 : 0;
8543 if(r
== 1 || sstring
[0] == '\0')
8550 if(type
== 'x' || r
== 'x'){
8551 cmd_cancelled("Selection by text");
8555 if(ps_global
&& ps_global
->ttyo
){
8556 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8557 ps_global
->mangled_footer
= 1;
8560 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8562 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8563 save_hist(history
, sstring
, flagsforhist
, NULL
);
8565 rv
= agg_text_select(stream
, msgmap
, type
, namehdr
, not, me
, sstring
, "utf-8", limitsrch
);
8574 * Select by message size.
8575 * Sets searched bits in mail_elts
8577 * Args limitsrch -- limit search to this searchset
8579 * Returns 0 on success.
8582 select_by_size(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8584 int r
, large
= 1, we_cancel
= 0;
8585 unsigned long n
, mult
= 1L, numerator
= 0L, divisor
= 1L;
8586 char size
[16], numbers
[80], *p
, *t
;
8589 long flags
= (SE_NOPREFETCH
| SE_FREE
);
8592 ps_global
->mangled_footer
= 1;
8596 int flgs
= OE_APPEND_CURRENT
;
8598 sel_size_opt
[1].label
= large
? sel_size_smaller
: sel_size_larger
;
8600 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
8601 sizeof(numbers
), large
? _(select_size_larger_msg
)
8602 : _(select_size_smaller_msg
),
8603 sel_size_opt
, help
, &flgs
);
8613 help
= (help
== NO_HELP
) ? (large
? h_select_by_larger_size
8614 : h_select_by_smaller_size
)
8619 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
8620 if(!isspace((unsigned char)*p
))
8625 if(r
== 1 || numbers
[0] == '\0'){
8626 cmd_cancelled("Selection by size");
8633 if(numbers
[0] == '-'){
8634 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
8635 _("Invalid size entered: %s"), numbers
);
8642 while(*p
&& isdigit((unsigned char)*p
))
8647 if(size
[0] == '\0' && *p
== '.' && isdigit(*(p
+1))){
8652 if(size
[0] == '\0'){
8653 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
8654 _("Invalid size entered: %s"), numbers
);
8658 n
= strtoul(size
, (char **)NULL
, 10);
8663 * We probably ought to just use atof() to convert 1.1 into a
8664 * double, but since we haven't used atof() anywhere else I'm
8665 * reluctant to use it because of portability concerns.
8669 while(*p
&& isdigit((unsigned char)*p
)){
8677 numerator
= strtoul(size
, (char **)NULL
, 10);
8697 n
= n
* mult
+ (numerator
* mult
) / divisor
;
8699 pgm
= mail_newsearchpgm();
8705 if(is_imap_stream(stream
) && !modern_imap_stream(stream
))
8706 flags
|= SE_NOSERVER
;
8708 if(ps_global
&& ps_global
->ttyo
){
8709 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8710 ps_global
->mangled_footer
= 1;
8713 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8715 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
8716 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
8717 /* we know this was freed in mail_search, let caller know */
8729 * visible_searchset -- return c-client search set unEXLDed
8733 visible_searchset(MAILSTREAM
*stream
, MSGNO_S
*msgmap
)
8736 SEARCHSET
*full_set
= NULL
, **set
;
8739 * If we're talking to anything other than a server older than
8740 * imap 4rev1, build a searchset otherwise it'll choke.
8742 if(!(is_imap_stream(stream
) && !modern_imap_stream(stream
))){
8743 if(any_lflagged(msgmap
, MN_EXLD
)){
8744 for(n
= 1L, set
= &full_set
, run
= 0L; n
<= stream
->nmsgs
; n
++)
8745 if(get_lflag(stream
, NULL
, n
, MN_EXLD
)){
8746 if(run
){ /* previous NOT excluded? */
8748 (*set
)->last
= n
- 1L;
8750 set
= &(*set
)->next
;
8754 else if(run
++){ /* next in run */
8757 else{ /* start of run */
8758 *set
= mail_newsearchset();
8763 full_set
= mail_newsearchset();
8764 full_set
->first
= 1L;
8765 full_set
->last
= stream
->nmsgs
;
8774 * Select by message status bits.
8775 * Sets searched bits in mail_elts
8777 * Args limitsrch -- limit search to this searchset
8779 * Returns 0 on success.
8782 select_by_status(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8784 int s
, not = 0, we_cancel
= 0, rv
;
8787 s
= radio_buttons((not) ? _(sel_flag_not
) : _(sel_flag
),
8788 -FOOTER_ROWS(ps_global
), sel_flag_opt
, '*', 'x',
8789 NO_HELP
, RB_NORM
|RB_RET_HELP
);
8792 cmd_cancelled("Selection by status");
8796 helper(h_select_status
, _("HELP FOR SELECT BASED ON STATUS"),
8798 ps_global
->mangled_screen
= 1;
8806 if(ps_global
&& ps_global
->ttyo
){
8807 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8808 ps_global
->mangled_footer
= 1;
8811 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8812 rv
= agg_flag_select(stream
, not, s
, limitsrch
);
8821 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8822 * Sets searched bits in mail_elts
8824 * Args limitsrch -- limit search to this searchset
8826 * Returns 0 on success.
8829 select_by_rule(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8831 char rulenick
[1000], *nick
;
8833 int r
, not = 0, we_cancel
= 0, rflags
= ROLE_DO_SRCH
8841 ps_global
->mangled_footer
= 1;
8846 oe_flags
= OE_APPEND_CURRENT
;
8847 r
= optionally_enter(rulenick
, -FOOTER_ROWS(ps_global
), 0,
8849 not ? _("Rule to NOT match: ")
8850 : _("Rule to match: "),
8851 sel_key_opt
, NO_HELP
, &oe_flags
);
8854 /* select rulenick from a list */
8855 if((nick
=choose_a_rule(rflags
)) != NULL
){
8856 strncpy(rulenick
, nick
, sizeof(rulenick
)-1);
8857 rulenick
[sizeof(rulenick
)-1] = '\0';
8858 fs_give((void **) &nick
);
8867 helper(h_select_rule
, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE
);
8868 ps_global
->mangled_screen
= 1;
8871 cmd_cancelled("Selection by Rule");
8875 removing_leading_and_trailing_white_space(rulenick
);
8877 }while(r
== 3 || r
== 4 || r
== '!');
8881 * The approach of requiring a nickname instead of just allowing the
8882 * user to select from the list of rules has the drawback that a rule
8883 * may not have a nickname, or there may be more than one rule with
8884 * the same nickname. However, it has the benefit of allowing the user
8885 * to type in the nickname and, most importantly, allows us to set
8886 * up the ! (not). We could incorporate the ! into the selection
8887 * screen, but this is easier and also allows the typing of nicks.
8888 * User can just set up nicknames if they want to use this feature.
8890 patgrp
= nick_to_patgrp(rulenick
, rflags
);
8893 if(ps_global
&& ps_global
->ttyo
){
8894 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8895 ps_global
->mangled_footer
= 1;
8898 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8899 match_pattern(patgrp
, stream
, limitsrch
? *limitsrch
: 0, NULL
,
8901 (not ? MP_NOT
: 0) | SE_NOPREFETCH
);
8902 free_patgrp(&patgrp
);
8907 if(limitsrch
&& *limitsrch
){
8908 mail_free_searchset(limitsrch
);
8917 * Allow user to choose a rule from their list of rules.
8919 * Returns an allocated rule nickname on success, NULL otherwise.
8922 choose_a_rule(int rflags
)
8924 char *choice
= NULL
;
8925 char **rule_list
, **lp
;
8930 if(!(nonempty_patterns(rflags
, &pstate
) && first_pattern(&pstate
))){
8931 q_status_message(SM_ORDER
, 3, 3,
8932 _("No rules available. Use Setup/Rules to add some."));
8937 * Build a list of rules to choose from.
8940 for(pat
= first_pattern(&pstate
); pat
; pat
= next_pattern(&pstate
))
8944 q_status_message(SM_ORDER
, 3, 4, _("No rules defined, use Setup/Rules"));
8948 lp
= rule_list
= (char **) fs_get((cnt
+ 1) * sizeof(*rule_list
));
8949 memset(rule_list
, 0, (cnt
+1) * sizeof(*rule_list
));
8951 for(pat
= first_pattern(&pstate
); pat
; pat
= next_pattern(&pstate
))
8952 *lp
++ = cpystr((pat
->patgrp
&& pat
->patgrp
->nick
)
8953 ? pat
->patgrp
->nick
: "?");
8955 /* TRANSLATORS: SELECT A RULE is a screen title
8956 TRANSLATORS: Print something1 using something2.
8957 "rules" is something1 */
8958 choice
= choose_item_from_list(rule_list
, NULL
, _("SELECT A RULE"),
8959 _("rules"), h_select_rule_screen
,
8960 _("HELP FOR SELECTING A RULE NICKNAME"), NULL
);
8963 q_status_message(SM_ORDER
, 1, 4, "No choice");
8965 free_list_array(&rule_list
);
8972 * Select by current thread.
8973 * Sets searched bits in mail_elts for this entire thread
8975 * Args limitsrch -- limit search to this searchset
8977 * Returns 0 on success.
8980 select_by_thread(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**limitsrch
)
8983 PINETHRD_S
*thrd
= NULL
;
8990 for(n
= 1L; n
<= stream
->nmsgs
; n
++)
8991 if((mc
= mail_elt(stream
, n
)) != NULL
)
8992 mc
->searched
= 0; /* clear searched bits */
8994 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
8995 if(thrd
&& thrd
->top
&& thrd
->top
!= thrd
->rawno
)
8996 thrd
= fetch_thread(stream
, thrd
->top
);
8999 * This doesn't unselect if the thread is already selected
9000 * (like select current does), it always selects.
9001 * There is no way to select ! this thread.
9004 set_search_bit_for_thread(stream
, thrd
, limitsrch
);
9013 * Select by message keywords.
9014 * Sets searched bits in mail_elts
9016 * Args limitsrch -- limit search to this searchset
9018 * Returns 0 on success.
9021 select_by_keyword(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
9023 int r
, not = 0, we_cancel
= 0;
9024 char keyword
[MAXUSERFLAG
+1], *kword
;
9025 char *error
= NULL
, *p
, *prompt
;
9030 ps_global
->mangled_footer
= 1;
9037 q_status_message(SM_ORDER
, 3, 4, error
);
9038 fs_give((void **) &error
);
9041 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
) && ps_global
->keywords
){
9043 prompt
= _("Keyword (or keyword initial) to NOT match: ");
9045 prompt
= _("Keyword (or keyword initial) to match: ");
9049 prompt
= _("Keyword to NOT match: ");
9051 prompt
= _("Keyword to match: ");
9054 oe_flags
= OE_APPEND_CURRENT
;
9055 r
= optionally_enter(keyword
, -FOOTER_ROWS(ps_global
), 0,
9057 prompt
, sel_key_opt
, help
, &oe_flags
);
9060 /* select keyword from a list */
9061 if((kword
=choose_a_keyword()) != NULL
){
9062 strncpy(keyword
, kword
, sizeof(keyword
)-1);
9063 keyword
[sizeof(keyword
)-1] = '\0';
9064 fs_give((void **) &kword
);
9073 help
= help
== NO_HELP
? h_select_keyword
: NO_HELP
;
9075 cmd_cancelled("Selection by keyword");
9079 removing_leading_and_trailing_white_space(keyword
);
9081 }while(r
== 3 || r
== 4 || r
== '!' || keyword_check(keyword
, &error
));
9084 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
) && ps_global
->keywords
){
9085 p
= initial_to_keyword(keyword
);
9087 strncpy(keyword
, p
, sizeof(keyword
)-1);
9088 keyword
[sizeof(keyword
)-1] = '\0';
9093 * We want to check the keyword, not the nickname of the keyword,
9094 * so convert it to the keyword if necessary.
9096 p
= nick_to_keyword(keyword
);
9098 strncpy(keyword
, p
, sizeof(keyword
)-1);
9099 keyword
[sizeof(keyword
)-1] = '\0';
9102 pgm
= mail_newsearchpgm();
9104 pgm
->unkeyword
= mail_newstringlist();
9105 pgm
->unkeyword
->text
.data
= (unsigned char *) cpystr(keyword
);
9106 pgm
->unkeyword
->text
.size
= strlen(keyword
);
9109 pgm
->keyword
= mail_newstringlist();
9110 pgm
->keyword
->text
.data
= (unsigned char *) cpystr(keyword
);
9111 pgm
->keyword
->text
.size
= strlen(keyword
);
9114 if(ps_global
&& ps_global
->ttyo
){
9115 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
9116 ps_global
->mangled_footer
= 1;
9119 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
9121 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
9122 pine_mail_search_full(stream
, "UTF-8", pgm
, SE_NOPREFETCH
| SE_FREE
);
9123 /* we know this was freed in mail_search, let caller know */
9135 * Allow user to choose a keyword from their list of keywords.
9137 * Returns an allocated keyword on success, NULL otherwise.
9140 choose_a_keyword(void)
9142 char *choice
= NULL
;
9143 char **keyword_list
, **lp
;
9148 * Build a list of keywords to choose from.
9151 for(cnt
= 0, kw
= ps_global
->keywords
; kw
; kw
= kw
->next
)
9155 q_status_message(SM_ORDER
, 3, 4,
9156 _("No keywords defined, use \"keywords\" option in Setup/Config"));
9160 lp
= keyword_list
= (char **) fs_get((cnt
+ 1) * sizeof(*keyword_list
));
9161 memset(keyword_list
, 0, (cnt
+1) * sizeof(*keyword_list
));
9163 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
)
9164 *lp
++ = cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
9166 /* TRANSLATORS: SELECT A KEYWORD is a screen title
9167 TRANSLATORS: Print something1 using something2.
9168 "keywords" is something1 */
9169 choice
= choose_item_from_list(keyword_list
, NULL
, _("SELECT A KEYWORD"),
9170 _("keywords"), h_select_keyword_screen
,
9171 _("HELP FOR SELECTING A KEYWORD"), NULL
);
9174 q_status_message(SM_ORDER
, 1, 4, "No choice");
9176 free_list_array(&keyword_list
);
9183 * Allow user to choose a list of keywords from their list of keywords.
9185 * Returns allocated list.
9188 choose_list_of_keywords(void)
9190 LIST_SEL_S
*listhead
, *ls
, *p
;
9196 * Build a list of keywords to choose from.
9199 p
= listhead
= NULL
;
9200 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
9202 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9203 memset(ls
, 0, sizeof(*ls
));
9204 ls
->item
= cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
9217 /* TRANSLATORS: SELECT KEYWORDS is a screen title
9218 Print something1 using something2.
9219 "keywords" is something1 */
9220 if(!select_from_list_screen(listhead
, SFL_ALLOW_LISTMODE
,
9221 _("SELECT KEYWORDS"), _("keywords"),
9222 h_select_multkeyword_screen
,
9223 _("HELP FOR SELECTING KEYWORDS"), NULL
)){
9224 for(cnt
= 0, p
= listhead
; p
; p
= p
->next
)
9228 ret
= (char **) fs_get((cnt
+1) * sizeof(*ret
));
9229 memset(ret
, 0, (cnt
+1) * sizeof(*ret
));
9230 for(i
= 0, p
= listhead
; p
; p
= p
->next
)
9232 ret
[i
++] = cpystr(p
->item
? p
->item
: "");
9235 free_list_sel(&listhead
);
9242 * Allow user to choose a charset
9244 * Returns an allocated charset on success, NULL otherwise.
9247 choose_a_charset(int which_charsets
)
9249 char *choice
= NULL
;
9250 char **charset_list
, **lp
;
9255 * Build a list of charsets to choose from.
9258 for(cnt
= 0, cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
9259 if(!(cs
->flags
& (CF_UNSUPRT
|CF_NOEMAIL
))
9260 && ((which_charsets
& CAC_ALL
)
9261 || (which_charsets
& CAC_POSTING
9262 && cs
->flags
& CF_POSTING
)
9263 || (which_charsets
& CAC_DISPLAY
9264 && cs
->type
!= CT_2022
9265 && (cs
->flags
& (CF_PRIMARY
|CF_DISPLAY
)) == (CF_PRIMARY
|CF_DISPLAY
))))
9270 q_status_message(SM_ORDER
, 3, 4,
9271 _("No charsets found? Enter charset manually."));
9275 lp
= charset_list
= (char **) fs_get((cnt
+ 1) * sizeof(*charset_list
));
9276 memset(charset_list
, 0, (cnt
+1) * sizeof(*charset_list
));
9278 for(cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
9279 if(!(cs
->flags
& (CF_UNSUPRT
|CF_NOEMAIL
))
9280 && ((which_charsets
& CAC_ALL
)
9281 || (which_charsets
& CAC_POSTING
9282 && cs
->flags
& CF_POSTING
)
9283 || (which_charsets
& CAC_DISPLAY
9284 && cs
->type
!= CT_2022
9285 && (cs
->flags
& (CF_PRIMARY
|CF_DISPLAY
)) == (CF_PRIMARY
|CF_DISPLAY
))))
9286 *lp
++ = cpystr(cs
->name
);
9289 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
9290 TRANSLATORS: Print something1 using something2.
9291 "character sets" is something1 */
9292 choice
= choose_item_from_list(charset_list
, NULL
, _("SELECT A CHARACTER SET"),
9293 _("character sets"), h_select_charset_screen
,
9294 _("HELP FOR SELECTING A CHARACTER SET"), NULL
);
9297 q_status_message(SM_ORDER
, 1, 4, "No choice");
9299 free_list_array(&charset_list
);
9306 * Allow user to choose a list of character sets and/or scripts
9308 * Returns allocated list.
9311 choose_list_of_charsets(void)
9313 LIST_SEL_S
*listhead
, *ls
, *p
;
9315 int cnt
, i
, got_one
;
9320 char buf
[1024], *folded
;
9323 * Build a list of charsets to choose from.
9326 p
= listhead
= NULL
;
9328 /* this width is determined by select_from_list_screen() */
9329 width
= ps_global
->ttyo
->screen_cols
- 4;
9331 /* first comes a list of scripts (sets of character sets) */
9332 for(s
= utf8_script(NIL
); s
&& s
->name
; s
++){
9334 limit
= sizeof(buf
)-1;
9336 memset(q
, 0, limit
+1);
9339 sstrncpy(&q
, s
->name
, limit
);
9342 sstrncpy(&q
, " (", limit
-(q
-buf
));
9343 sstrncpy(&q
, s
->description
, limit
-(q
-buf
));
9344 sstrncpy(&q
, ")", limit
-(q
-buf
));
9347 /* add the list of charsets that are in this script */
9349 for(cs
= utf8_charset(NIL
);
9350 cs
&& cs
->name
&& (q
-buf
) < limit
; cs
++){
9351 if(cs
->script
& s
->script
){
9353 * Filter out some un-useful members of the list.
9354 * UTF-7 and UTF-8 weren't actually in the list at the
9355 * time this was written. Just making sure.
9357 if(!strucmp(cs
->name
, "ISO-2022-JP-2")
9358 || !strucmp(cs
->name
, "UTF-7")
9359 || !strucmp(cs
->name
, "UTF-8"))
9363 sstrncpy(&q
, " ", limit
-(q
-buf
));
9366 sstrncpy(&q
, " {", limit
-(q
-buf
));
9369 sstrncpy(&q
, cs
->name
, limit
-(q
-buf
));
9374 sstrncpy(&q
, "}", limit
-(q
-buf
));
9376 /* fold this line so that it can all be seen on the screen */
9377 folded
= fold(buf
, width
, width
, "", " ", FLD_NONE
);
9380 while(t
&& *t
&& (q
= strindex(t
, '\n')) != NULL
){
9383 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9384 memset(ls
, 0, sizeof(*ls
));
9386 ls
->item
= cpystr(s
->name
);
9388 ls
->flags
= SFL_NOSELECT
;
9390 ls
->display_item
= cpystr(t
);
9400 listhead
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9401 memset(listhead
, 0, sizeof(*listhead
));
9402 listhead
->flags
= SFL_NOSELECT
;
9403 listhead
->display_item
=
9404 cpystr(_("Scripts representing groups of related character sets"));
9405 listhead
->next
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9406 memset(listhead
->next
, 0, sizeof(*listhead
));
9407 listhead
->next
->flags
= SFL_NOSELECT
;
9408 listhead
->next
->display_item
=
9409 cpystr(repeat_char(width
, '-'));
9411 listhead
->next
->next
= ls
;
9416 fs_give((void **) &folded
);
9420 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9421 memset(ls
, 0, sizeof(*ls
));
9422 ls
->flags
= SFL_NOSELECT
;
9430 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9431 memset(ls
, 0, sizeof(*ls
));
9432 ls
->flags
= SFL_NOSELECT
;
9434 cpystr(_("Individual character sets, may be mixed with scripts"));
9438 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9439 memset(ls
, 0, sizeof(*ls
));
9440 ls
->flags
= SFL_NOSELECT
;
9442 cpystr(repeat_char(width
, '-'));
9446 /* then comes a list of individual character sets */
9447 for(cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
9448 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9449 memset(ls
, 0, sizeof(*ls
));
9450 ls
->item
= cpystr(cs
->name
);
9463 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9464 Print something1 using something2.
9465 "character sets" is something1 */
9466 if(!select_from_list_screen(listhead
, SFL_ALLOW_LISTMODE
,
9467 _("SELECT CHARACTER SETS"), _("character sets"),
9468 h_select_multcharsets_screen
,
9469 _("HELP FOR SELECTING CHARACTER SETS"), NULL
)){
9470 for(cnt
= 0, p
= listhead
; p
; p
= p
->next
)
9474 ret
= (char **) fs_get((cnt
+1) * sizeof(*ret
));
9475 memset(ret
, 0, (cnt
+1) * sizeof(*ret
));
9476 for(i
= 0, p
= listhead
; p
; p
= p
->next
)
9478 ret
[i
++] = cpystr(p
->item
? p
->item
: "");
9481 free_list_sel(&listhead
);
9486 /* Report quota summary resources in an IMAP server */
9489 cmd_quota (struct pine
*state
)
9491 QUOTALIST
*imapquota
;
9496 if(!state
->mail_stream
|| !is_imap_stream(state
->mail_stream
)){
9497 q_status_message(SM_ORDER
, 1, 5, "Quota only available for IMAP folders");
9501 if (state
->mail_stream
9502 && !sp_dead_stream(state
->mail_stream
)
9503 && state
->mail_stream
->mailbox
9504 && *state
->mail_stream
->mailbox
9505 && mail_valid_net_parse(state
->mail_stream
->mailbox
, &mb
))
9506 imap_getquotaroot(state
->mail_stream
, mb
.mailbox
);
9508 if(!state
->quota
) /* failed ? */
9509 return; /* go back... */
9511 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
9512 q_status_message(SM_ORDER
| SM_DING
, 3, 3, "Error allocating space.");
9516 so_puts(store
, "Quota Report for ");
9517 so_puts(store
, state
->mail_stream
->original_mailbox
);
9518 so_puts(store
, "\n\n");
9520 for (imapquota
= state
->quota
; imapquota
; imapquota
= imapquota
->next
){
9522 so_puts(store
, _("Resource : "));
9523 so_puts(store
, imapquota
->name
);
9524 so_writec('\n', store
);
9526 so_puts(store
, _("Usage : "));
9527 so_puts(store
, long2string(imapquota
->usage
));
9528 if(!strucmp(imapquota
->name
,"STORAGE"))
9529 so_puts(store
, " KiB ");
9530 if(!strucmp(imapquota
->name
,"MESSAGE")){
9531 so_puts(store
, _(" message"));
9532 if(imapquota
->usage
!= 1)
9533 so_puts(store
, _("s ")); /* plural */
9535 so_puts(store
, _(" "));
9537 so_writec('(', store
);
9538 so_puts(store
, long2string(100*imapquota
->usage
/imapquota
->limit
));
9539 so_puts(store
, "%)\n");
9541 so_puts(store
, _("Limit : "));
9542 so_puts(store
, long2string(imapquota
->limit
));
9543 if(!strucmp(imapquota
->name
,"STORAGE"))
9544 so_puts(store
, " KiB\n\n");
9545 if(!strucmp(imapquota
->name
,"MESSAGE")){
9546 so_puts(store
, _(" message"));
9547 if(imapquota
->usage
!= 1)
9548 so_puts(store
, _("s\n\n")); /* plural */
9550 so_puts(store
, _("\n\n"));
9554 memset(&sargs
, 0, sizeof(SCROLL_S
));
9555 sargs
.text
.text
= so_text(store
);
9556 sargs
.text
.src
= CharStar
;
9557 sargs
.text
.desc
= _("Quota Resources Summary");
9558 sargs
.bar
.title
= _("QUOTA SUMMARY");
9559 sargs
.proc
.tool
= NULL
;
9560 sargs
.help
.text
= h_quota_command
;
9561 sargs
.help
.title
= NULL
;
9562 sargs
.keys
.menu
= NULL
;
9563 setbitmap(sargs
.keys
.bitmap
);
9569 mail_free_quotalist(&(state
->quota
));
9572 /*----------------------------------------------------------------------
9573 Prompt the user for the type of sort he desires
9575 Args: state -- pine state pointer
9576 q1 -- Line to prompt on
9578 Returns 0 if it was cancelled, 1 otherwise.
9581 select_sort(struct pine
*state
, int ql
, SortOrder
*sort
, int *rev
)
9583 char prompt
[200], tmp
[3], *p
;
9585 int deefault
= 'a', retval
= 1;
9590 DLG_SORTPARAM sortsel
;
9592 if (mswin_usedialog ()) {
9594 sortsel
.reverse
= mn_get_revsort (state
->msgmap
);
9595 sortsel
.cursort
= mn_get_sort (state
->msgmap
);
9596 /* assumption here that HelpType is char ** */
9597 sortsel
.helptext
= h_select_sort
;
9600 if ((retval
= os_sortdialog (&sortsel
))) {
9601 *sort
= sortsel
.cursort
;
9602 *rev
= sortsel
.reverse
;
9609 /*----- String together the prompt ------*/
9611 if(F_ON(F_USE_FK
,ps_global
))
9612 strncpy(prompt
, _("Choose type of sort : "), sizeof(prompt
));
9614 strncpy(prompt
, _("Choose type of sort, or 'R' to reverse current sort : "),
9617 for(i
= 0; state
->sort_types
[i
] != EndofList
; i
++) {
9619 p
= sorts
[i
].label
= sort_name(state
->sort_types
[i
]);
9620 while(*(p
+1) && islower((unsigned char)*p
))
9623 sorts
[i
].ch
= tolower((unsigned char)(tmp
[0] = *p
));
9624 sorts
[i
].name
= cpystr(tmp
);
9626 if(mn_get_sort(state
->msgmap
) == state
->sort_types
[i
])
9627 deefault
= sorts
[i
].rval
;
9631 sorts
[i
].rval
= 'r';
9632 sorts
[i
].name
= cpystr("R");
9633 if(F_ON(F_USE_FK
,ps_global
))
9634 sorts
[i
].label
= N_("Reverse");
9636 sorts
[i
].label
= "";
9639 help
= h_select_sort
;
9641 if((F_ON(F_USE_FK
,ps_global
)
9642 && ((s
= double_radio_buttons(prompt
,ql
,sorts
,deefault
,'x',
9643 help
,RB_NORM
)) != 'x'))
9645 (F_OFF(F_USE_FK
,ps_global
)
9646 && ((s
= radio_buttons(prompt
,ql
,sorts
,deefault
,'x',
9647 help
,RB_NORM
)) != 'x'))){
9648 state
->mangled_body
= 1; /* signal screen's changed */
9650 *rev
= !mn_get_revsort(state
->msgmap
);
9652 *sort
= state
->sort_types
[s
];
9654 if(F_ON(F_SHOW_SORT
, ps_global
))
9655 ps_global
->mangled_header
= 1;
9659 cmd_cancelled("Sort");
9663 fs_give((void **)&sorts
[i
].name
);
9665 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
9670 /*---------------------------------------------------------------------
9671 Build list of folders in the given context for user selection
9673 Args: c -- pointer to pointer to folder's context context
9674 f -- folder prefix to display
9675 sublist -- whether or not to use 'f's contents as prefix
9676 lister -- function used to do the actual display
9678 Returns: malloc'd string containing sequence, else NULL if
9679 no messages in msgmap with local "selected" flag.
9682 display_folder_list(CONTEXT_S
**c
, char *f
, int sublist
, int (*lister
) (struct pine
*, CONTEXT_S
**, char *, int))
9686 void (*redraw
)(void) = ps_global
->redrawer
;
9688 push_titlebar_state();
9690 if((rc
= (*lister
)(ps_global
, &tc
, f
, sublist
)) != 0)
9694 pop_titlebar_state();
9696 if((ps_global
->redrawer
= redraw
) != NULL
) /* reset old value, and test */
9697 (*ps_global
->redrawer
)();
9699 if(rc
== 1 && F_ON(F_SELECT_WO_CONFIRM
, ps_global
))
9707 * Allow user to choose a single item from a list of strings.
9709 * Args list -- Array of strings to choose from, NULL terminated.
9710 * displist -- Array of strings to display instead of displaying list.
9711 * Indices correspond to the list array. Display the displist
9712 * but return the item from list if displist non-NULL.
9713 * title -- For conf_scroll_screen
9714 * pdesc -- For conf_scroll_screen
9715 * help -- For conf_scroll_screen
9716 * htitle -- For conf_scroll_screen
9718 * Returns an allocated copy of the chosen item or NULL.
9721 choose_item_from_list(char **list
, char **displist
, char *title
, char *pdesc
, HelpType help
,
9722 char *htitle
, char *cursor_location
)
9724 LIST_SEL_S
*listhead
, *ls
, *p
, *starting_val
= NULL
;
9726 char *ret
= NULL
, *choice
= NULL
;
9728 /* build the LIST_SEL_S list */
9729 p
= listhead
= NULL
;
9730 for(t
= list
, dl
= displist
; *t
; t
++, dl
++){
9731 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9732 memset(ls
, 0, sizeof(*ls
));
9733 ls
->item
= cpystr(*t
);
9735 ls
->display_item
= cpystr(*dl
);
9737 if(cursor_location
&& (cursor_location
== (*t
)))
9751 if(!select_from_list_screen(listhead
, SFL_NONE
, title
, pdesc
,
9752 help
, htitle
, starting_val
))
9753 for(p
= listhead
; !choice
&& p
; p
= p
->next
)
9758 ret
= cpystr(choice
);
9760 free_list_sel(&listhead
);
9767 free_list_sel(LIST_SEL_S
**lsel
)
9770 free_list_sel(&(*lsel
)->next
);
9772 fs_give((void **) &(*lsel
)->item
);
9774 if((*lsel
)->display_item
)
9775 fs_give((void **) &(*lsel
)->display_item
);
9777 fs_give((void **) lsel
);
9783 * file_lister - call pico library's file lister
9786 file_lister(char *title
, char *path
, size_t pathlen
, char *file
, size_t filelen
, int newmail
, int flags
)
9790 void (*redraw
)(void) = ps_global
->redrawer
;
9792 standard_picobuf_setup(&pbf
);
9793 push_titlebar_state();
9797 /* BUG: what about help command and text? */
9798 pbf
.pine_anchor
= title
;
9800 rv
= pico_file_browse(&pbf
, path
, pathlen
, file
, filelen
, NULL
, 0, flags
);
9801 standard_picobuf_teardown(&pbf
);
9802 fix_windsize(ps_global
);
9803 init_signals(); /* has it's own signal stuff */
9805 /* Restore display's titlebar and body */
9806 pop_titlebar_state();
9808 if((ps_global
->redrawer
= redraw
) != NULL
)
9809 (*ps_global
->redrawer
)();
9815 /*----------------------------------------------------------------------
9816 Print current folder index
9820 print_index(struct pine
*state
, MSGNO_S
*msgmap
, int agg
)
9824 char buf
[MAX_SCREEN_COLS
+1];
9826 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++){
9827 if(agg
&& !get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
))
9830 if(!agg
&& msgline_hidden(state
->mail_stream
, msgmap
, i
, 0))
9833 ice
= build_header_line(state
, state
->mail_stream
, msgmap
, i
, NULL
);
9837 * I don't understand why we'd want to mark the current message
9838 * instead of printing out the first character of the status
9839 * so I'm taking it out and including the first character of the
9840 * line instead. Hubert 2006-02-09
9842 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
9846 if(!gf_puts(simple_index_line(buf
,sizeof(buf
),ice
,i
),
9848 || !gf_puts(NEWLINE
, print_char
))
9859 * windows callback to get/set header mode state
9862 header_mode_callback(set
, args
)
9866 return(ps_global
->full_header
);
9871 * windows callback to get/set zoom mode state
9874 zoom_mode_callback(set
, args
)
9878 return(any_lflagged(ps_global
->msgmap
, MN_HIDE
) != 0);
9883 * windows callback to get/set zoom mode state
9886 any_selected_callback(set
, args
)
9890 return(any_lflagged(ps_global
->msgmap
, MN_SLCT
) != 0);
9898 flag_callback(set
, flags
)
9908 case 1: /* Important */
9909 permflag
= ps_global
->mail_stream
->perm_flagged
;
9913 permflag
= ps_global
->mail_stream
->perm_seen
;
9916 case 3: /* Answered */
9917 permflag
= ps_global
->mail_stream
->perm_answered
;
9920 case 4: /* Deleted */
9921 permflag
= ps_global
->mail_stream
->perm_deleted
;
9926 if(!(any_messages(ps_global
->msgmap
, NULL
, "to Flag")
9927 && can_set_flag(ps_global
, "flag", permflag
)))
9930 if(sp_io_error_on_stream(ps_global
->mail_stream
)){
9931 sp_set_io_error_on_stream(ps_global
->mail_stream
, 0);
9932 pine_mail_check(ps_global
->mail_stream
); /* forces write */
9936 msgno
= mn_m2raw(ps_global
->msgmap
, mn_get_cur(ps_global
->msgmap
));
9937 if(msgno
> 0L && ps_global
->mail_stream
9938 && msgno
<= ps_global
->mail_stream
->nmsgs
9939 && (mc
= mail_elt(ps_global
->mail_stream
, msgno
))
9942 * NOTE: code below is *VERY* sensitive to the order of
9943 * the messages defined in resource.h for flag handling.
9944 * Don't change it unless you know what you're doing.
9951 case 1 : /* Important */
9952 flagstr
= "\\FLAGGED";
9953 mflag
= (mc
->flagged
) ? 0L : ST_SET
;
9958 mflag
= (mc
->seen
) ? 0L : ST_SET
;
9961 case 3 : /* Answered */
9962 flagstr
= "\\ANSWERED";
9963 mflag
= (mc
->answered
) ? 0L : ST_SET
;
9966 case 4 : /* Deleted */
9967 flagstr
= "\\DELETED";
9968 mflag
= (mc
->deleted
) ? 0L : ST_SET
;
9971 default : /* bogus */
9975 mail_flag(ps_global
->mail_stream
, long2string(msgno
),
9978 if(ps_global
->redrawer
)
9979 (*ps_global
->redrawer
)();
10006 * BUG: Should teach this about keywords
10012 static MPopup flag_submenu
[] = {
10013 {tMessage
, {N_("Important"), lNormal
}, {IDM_MI_FLAGIMPORTANT
}},
10014 {tMessage
, {N_("New"), lNormal
}, {IDM_MI_FLAGNEW
}},
10015 {tMessage
, {N_("Answered"), lNormal
}, {IDM_MI_FLAGANSWERED
}},
10016 {tMessage
, {N_("Deleted"), lNormal
}, {IDM_MI_FLAGDELETED
}},
10021 flag_submenu
[0].label
.style
= (mc
&& mc
->flagged
) ? lChecked
: lNormal
;
10024 flag_submenu
[1].label
.style
= (mc
&& mc
->seen
) ? lNormal
: lChecked
;
10027 flag_submenu
[2].label
.style
= (mc
&& mc
->answered
) ? lChecked
: lNormal
;
10030 flag_submenu
[3].label
.style
= (mc
&& mc
->deleted
) ? lChecked
: lNormal
;
10032 return(flag_submenu
);
10035 #endif /* _WINDOWS */