2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006-2009 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 /*======================================================================
17 The meat and pototoes of mail processing here:
18 - initial command processing and dispatch
20 - capture address off incoming mail
21 - jump to specific numbered message
22 - open (broach) a new folder
23 - search message headers (where is) command
31 #include "flagmaint.h"
48 #include "../pith/state.h"
49 #include "../pith/msgno.h"
50 #include "../pith/store.h"
51 #include "../pith/thread.h"
52 #include "../pith/flag.h"
53 #include "../pith/sort.h"
54 #include "../pith/maillist.h"
55 #include "../pith/save.h"
56 #include "../pith/pipe.h"
57 #include "../pith/news.h"
58 #include "../pith/util.h"
59 #include "../pith/sequence.h"
60 #include "../pith/keyword.h"
61 #include "../pith/stream.h"
62 #include "../pith/mailcmd.h"
63 #include "../pith/hist.h"
64 #include "../pith/list.h"
65 #include "../pith/icache.h"
66 #include "../pith/busy.h"
67 #include "../pith/mimedesc.h"
68 #include "../pith/pattern.h"
69 #include "../pith/tempfile.h"
70 #include "../pith/search.h"
71 #include "../pith/margin.h"
73 #include "../pico/osdep/mswin.h"
79 int cmd_flag(struct pine
*, MSGNO_S
*, int);
80 int cmd_flag_prompt(struct pine
*, struct flag_screen
*, int);
81 void free_flag_table(struct flag_table
**);
82 int cmd_reply(struct pine
*, MSGNO_S
*, int, ACTION_S
*);
83 int cmd_forward(struct pine
*, MSGNO_S
*, int, ACTION_S
*);
84 int cmd_bounce(struct pine
*, MSGNO_S
*, int, ACTION_S
*);
85 int cmd_save(struct pine
*, MAILSTREAM
*, MSGNO_S
*, int, CmdWhere
);
86 void role_compose(struct pine
*);
87 int cmd_expunge(struct pine
*, MAILSTREAM
*, MSGNO_S
*, int);
88 int cmd_export(struct pine
*, MSGNO_S
*, int, int);
89 char *cmd_delete_action(struct pine
*, MSGNO_S
*, CmdWhere
);
90 char *cmd_delete_view(struct pine
*, MSGNO_S
*);
91 char *cmd_delete_index(struct pine
*, MSGNO_S
*);
92 long get_level(int, UCS
, SCROLL_S
*);
93 long closest_jump_target(long, MAILSTREAM
*, MSGNO_S
*, int, CmdWhere
, char *, size_t);
94 int update_folder_spec(char *, size_t, char *);
95 int cmd_print(struct pine
*, MSGNO_S
*, int, CmdWhere
);
96 int cmd_pipe(struct pine
*, MSGNO_S
*, int);
97 STORE_S
*list_mgmt_text(RFC2369_S
*, long);
98 void list_mgmt_screen(STORE_S
*);
99 int aggregate_select(struct pine
*, MSGNO_S
*, int, CmdWhere
);
100 int select_by_number(MAILSTREAM
*, MSGNO_S
*, SEARCHSET
**);
101 int select_by_thrd_number(MAILSTREAM
*, MSGNO_S
*, SEARCHSET
**);
102 int select_by_date(MAILSTREAM
*, MSGNO_S
*, long, SEARCHSET
**);
103 int select_by_text(MAILSTREAM
*, MSGNO_S
*, long, SEARCHSET
**);
104 int select_by_gm_content(MAILSTREAM
*, MSGNO_S
*, long, SEARCHSET
**);
105 int select_by_size(MAILSTREAM
*, SEARCHSET
**);
106 SEARCHSET
*visible_searchset(MAILSTREAM
*, MSGNO_S
*);
107 int select_by_status(MAILSTREAM
*, SEARCHSET
**);
108 int select_by_rule(MAILSTREAM
*, SEARCHSET
**);
109 int select_by_thread(MAILSTREAM
*, MSGNO_S
*, SEARCHSET
**);
110 char *choose_a_rule(int, char *);
111 int rulenick_complete(int, char *, int);
112 int select_by_keyword(MAILSTREAM
*, SEARCHSET
**);
113 char *choose_a_keyword(char *);
114 int keyword_complete(char *, int);
115 int select_sort(struct pine
*, int, SortOrder
*, int *);
116 int print_index(struct pine
*, MSGNO_S
*, int);
119 * List of Select options used by apply_* functions...
121 static char *sel_pmt1
= N_("ALTER message selection : ");
122 ESCKEY_S sel_opts1
[] = {
123 /* TRANSLATORS: these are keymenu names for selecting. Broaden selection means
124 we will add more messages to the selection, Narrow selection means we will
125 remove some selections (like a logical AND instead of logical OR), and Flip
126 Selected means that all the messages that are currently selected become unselected,
127 and all the unselected messages become selected. */
128 {'a', 'a', "A", N_("unselect All")},
129 {'c', 'c', "C", NULL
},
130 {'b', 'b', "B", N_("Broaden selctn")},
131 {'n', 'n', "N", N_("Narrow selctn")},
132 {'f', 'f', "F", N_("Flip selected")},
133 {'r', 'r', "R", N_("Replace selctn")},
138 #define SEL_OPTS_THREAD 9 /* index number of "tHread" */
139 #define SEL_OPTS_THREAD_CH 'h'
140 #define SEL_OPTS_XGMEXT 10 /* index number of "boX" */
141 #define SEL_OPTS_XGMEXT_CH 'g'
143 char *sel_pmt2
= "SELECT criteria : ";
144 static ESCKEY_S sel_opts2
[] = {
145 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
146 means select the currently highlighted message; select by Number is by message
147 number; Status is by status of the message, for example the message might be
148 New or it might be Unseen or marked Important; Size has the Z upper case because
149 it is a Z command; Keyword is an alpine keyword that has been set by the user;
150 and Rule is an alpine rule */
151 {'a', 'a', "A", N_("select All")},
152 {'c', 'c', "C", N_("select Cur")},
153 {'n', 'n', "N", N_("Number")},
154 {'d', 'd', "D", N_("Date")},
155 {'t', 't', "T", N_("Text")},
156 {'s', 's', "S", N_("Status")},
157 {'z', 'z', "Z", N_("siZe")},
158 {'k', 'k', "K", N_("Keyword")},
159 {'r', 'r', "R", N_("Rule")},
160 {SEL_OPTS_THREAD_CH
, 'h', "H", N_("tHread")},
165 static ESCKEY_S sel_opts3
[] = {
166 /* TRANSLATORS: these are operations we can do on a set of selected messages.
167 Del is Delete; Undel is Undelete; TakeAddr means to Take some Addresses into
168 the address book; Save means to save the messages into another alpine folder;
169 Export means to copy the messages to a file outside of alpine, external to
171 {'d', 'd', "D", N_("Del")},
172 {'u', 'u', "U", N_("Undel")},
173 {'r', 'r', "R", N_("Reply")},
174 {'f', 'f', "F", N_("Forward")},
175 {'%', '%', "%", N_("Print")},
176 {'t', 't', "T", N_("TakeAddr")},
177 {'s', 's', "S", N_("Save")},
178 {'e', 'e', "E", N_("Export")},
179 { -1, 0, NULL
, NULL
},
180 { -1, 0, NULL
, NULL
},
181 { -1, 0, NULL
, NULL
},
182 { -1, 0, NULL
, NULL
},
183 { -1, 0, NULL
, NULL
},
184 { -1, 0, NULL
, NULL
},
185 { -1, 0, NULL
, NULL
},
189 static ESCKEY_S sel_opts4
[] = {
190 {'a', 'a', "A", N_("select All")},
191 /* TRANSLATORS: select currently highlighted message Thread */
192 {'c', 'c', "C", N_("select Curthrd")},
193 {'n', 'n', "N", N_("Number")},
194 {'d', 'd', "D", N_("Date")},
195 {'t', 't', "T", N_("Text")},
196 {'s', 's', "S", N_("Status")},
197 {'z', 'z', "Z", N_("siZe")},
198 {'k', 'k', "K", N_("Keyword")},
199 {'r', 'r', "R", N_("Rule")},
200 {SEL_OPTS_THREAD_CH
, 'h', "H", N_("tHread")},
204 static ESCKEY_S sel_opts5
[] = {
205 /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur
206 means select the currently highlighted message; select by Number is by message
207 number; Status is by status of the message, for example the message might be
208 New or it might be Unseen or marked Important; Size has the Z upper case because
209 it is a Z command; Keyword is an alpine keyword that has been set by the user;
210 and Rule is an alpine rule */
211 {'a', 'a', "A", N_("select All")},
212 {'c', 'c', "C", N_("select Cur")},
213 {'n', 'n', "N", N_("Number")},
214 {'d', 'd', "D", N_("Date")},
215 {'t', 't', "T", N_("Text")},
216 {'s', 's', "S", N_("Status")},
217 {'z', 'z', "Z", N_("siZe")},
218 {'k', 'k', "K", N_("Keyword")},
219 {'r', 'r', "R", N_("Rule")},
220 {SEL_OPTS_THREAD_CH
, 'h', "H", N_("tHread")},
221 {SEL_OPTS_XGMEXT_CH
, 'g', "G", N_("GmSearch")},
229 static ESCKEY_S sel_opts6
[] = {
230 {'a', 'a', "A", N_("select All")},
231 /* TRANSLATORS: select currently highlighted message Thread */
232 {'c', 'c', "C", N_("select Curthrd")},
233 {'n', 'n', "N", N_("Number")},
234 {'d', 'd', "D", N_("Date")},
235 {'t', 't', "T", N_("Text")},
236 {'s', 's', "S", N_("Status")},
237 {'z', 'z', "Z", N_("siZe")},
238 {'k', 'k', "K", N_("Keyword")},
239 {'r', 'r', "R", N_("Rule")},
240 {SEL_OPTS_THREAD_CH
, 'h', "H", N_("tHread")},
241 {SEL_OPTS_XGMEXT_CH
, 'g', "G", N_("Gmail")},
250 static char *sel_flag
=
251 N_("Select New, Deleted, Answered, Forwarded, or Important messages ? ");
252 static char *sel_flag_not
=
253 N_("Select NOT New, NOT Deleted, NOT Answered, NOT Forwarded or NOT Important msgs ? ");
254 static ESCKEY_S sel_flag_opt
[] = {
255 /* TRANSLATORS: When selecting messages by message Status these are the
256 different types of Status you can select on. Is the message New, Recent,
257 and so on. Not means flip the meaning of the selection to the opposite
258 thing, so message is not New or not Important. */
259 {'n', 'n', "N", N_("New")},
260 {'*', '*', "*", N_("Important")},
261 {'d', 'd', "D", N_("Deleted")},
262 {'a', 'a', "A", N_("Answered")},
263 {'f', 'f', "F", N_("Forwarded")},
265 {'!', '!', "!", N_("Not")},
267 {'r', 'r', "R", N_("Recent")},
268 {'u', 'u', "U", N_("Unseen")},
273 static ESCKEY_S sel_date_opt
[] = {
275 /* TRANSLATORS: options when selecting messages by Date */
276 {ctrl('P'), 12, "^P", N_("Prev Day")},
277 {ctrl('N'), 13, "^N", N_("Next Day")},
278 {ctrl('X'), 11, "^X", N_("Cur Msg")},
279 {ctrl('W'), 14, "^W", N_("Toggle When")},
280 {KEY_UP
, 12, "", ""},
281 {KEY_DOWN
, 13, "", ""},
286 static char *sel_x_gm_ext
=
288 static char *sel_text
=
289 N_("Select based on To, From, Cc, Recip, Partic, Subject fields or All msg text ? ");
290 static char *sel_text_not
=
291 N_("Select based on NOT To, From, Cc, Recip, Partic, Subject or All msg text ? ");
292 static ESCKEY_S sel_text_opt
[] = {
293 /* TRANSLATORS: Select messages based on the text contained in the From line, or
294 the Subject line, and so on. */
295 {'f', 'f', "F", N_("From")},
296 {'s', 's', "S", N_("Subject")},
297 {'t', 't', "T", N_("To")},
298 {'a', 'a', "A", N_("All Text")},
299 {'c', 'c', "C", N_("Cc")},
300 {'!', '!', "!", N_("Not")},
301 {'r', 'r', "R", N_("Recipient")},
302 {'p', 'p', "P", N_("Participant")},
303 {'b', 'b', "B", N_("Body")},
304 {'h', 'h', "H", N_("Header")},
308 static ESCKEY_S choose_action
[] = {
309 {'c', 'c', "C", N_("Compose")},
310 {'r', 'r', "R", N_("Reply")},
311 {'f', 'f', "F", N_("Forward")},
312 {'b', 'b', "B", N_("Bounce")},
316 static char *select_num
=
317 N_("Enter comma-delimited list of numbers (dash between ranges): ");
319 static char *select_size_larger_msg
=
320 N_("Select messages with size larger than: ");
322 static char *select_size_smaller_msg
=
323 N_("Select messages with size smaller than: ");
325 static char *sel_size_larger
= N_("Larger");
326 static char *sel_size_smaller
= N_("Smaller");
327 static ESCKEY_S sel_size_opt
[] = {
329 {ctrl('W'), 14, "^W", NULL
},
333 static ESCKEY_S sel_rule_opt
[] = {
335 {ctrl('T'), 14, "^T", N_("To List")},
336 {0, 0, NULL
, NULL
}, /* Reserved for TAB completion */
337 {'!', '!', "!", N_("Not")},
341 static ESCKEY_S sel_key_opt
[] = {
343 {ctrl('T'), 14, "^T", N_("To List")},
344 {0, 0, NULL
, NULL
}, /* Reserved for TAB completion */
345 {'!', '!', "!", N_("Not")},
349 static ESCKEY_S flag_text_opt
[] = {
350 /* TRANSLATORS: these are types of flags (markers) that the user can
351 set. For example, they can flag the message as an important message. */
352 {'n', 'n', "N", N_("New")},
353 {'*', '*', "*", N_("Important")},
354 {'d', 'd', "D", N_("Deleted")},
355 {'a', 'a', "A", N_("Answered")},
356 {'f', 'f', "F", N_("Forwarded")},
357 {'!', '!', "!", N_("Not")},
358 {ctrl('T'), 10, "^T", N_("To Flag Details")},
363 alpine_smime_confirm_save(char *email
)
367 snprintf(prompt
, sizeof(prompt
), _("Save certificate for <%s>"),
368 email
? email
: _("missing address"));
369 return want_to(prompt
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y';
373 alpine_get_password(char *prompt
, char *pass
, size_t len
)
375 int flags
= F_ON(F_QUELL_ASTERISKS
, ps_global
) ? OE_PASSWD_NOAST
: OE_PASSWD
;
377 flags
|= OE_DISALLOW_HELP
;
379 rv
= optionally_enter(pass
,
380 -(ps_global
->ttyo
? FOOTER_ROWS(ps_global
) : 3),
381 0, len
, prompt
, NULL
, NO_HELP
, &flags
);
382 if(rv
== 1) ps_global
->user_says_cancel
= 1;
387 smime_import_certificate(char *filename
, char *full_filename
, char *what
, size_t len
)
390 static HISTORY_S
*history
= NULL
;
391 static ESCKEY_S eopts
[] = {
392 {ctrl('T'), 10, "^T", N_("To Files")},
394 {-1, 0, NULL
, NULL
}};
396 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps_global
)){
397 eopts
[r
].ch
= ctrl('I');
399 eopts
[r
].name
= "TAB";
400 eopts
[r
].label
= N_("Complete");
406 full_filename
[0] = '\0';
408 r
= get_export_filename(ps_global
, filename
, NULL
, full_filename
,
409 len
, what
, "IMPORT", eopts
, NULL
,
410 -FOOTER_ROWS(ps_global
), GE_IS_IMPORT
, &history
);
416 /*----------------------------------------------------------------------
417 The giant switch on the commands for index and viewing
419 Input: command -- The command char/code
420 in_index -- flag indicating command is from index
421 orig_command -- The original command typed before pre-processing
422 Output: force_mailchk -- Set to tell caller to force call to new_mail().
426 Returns 1 if the message number or attachment to show changed
429 process_cmd(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
,
430 int command
, CmdWhere in_index
, int *force_mailchk
)
432 int question_line
, a_changed
, flags
= 0, ret
, j
;
434 long new_msgno
, del_count
, old_msgno
, i
;
436 char *newfolder
, prompt
[MAX_SCREEN_COLS
+1];
439 #if defined(DOS) && !defined(_WINDOWS)
440 extern long coreleft();
443 dprint((4, "\n - process_cmd(cmd=%d) -\n", command
));
445 question_line
= -FOOTER_ROWS(state
);
446 state
->mangled_screen
= 0;
447 state
->mangled_footer
= 0;
448 state
->mangled_header
= 0;
449 state
->next_screen
= SCREEN_FUN_NULL
;
450 old_msgno
= mn_get_cur(msgmap
);
455 /*------------- Help --------*/
458 * We're not using the h_mail_view portion of this right now because
459 * that call is being handled in scrolltool() before it gets
460 * here. Leave it in case we change how it works.
462 helper((in_index
== MsgIndx
)
466 : h_mail_thread_index
,
467 (in_index
== MsgIndx
)
468 ? _("HELP FOR MESSAGE INDEX")
470 ? _("HELP FOR MESSAGE TEXT")
471 : _("HELP FOR THREAD INDEX"),
473 dprint((4,"MAIL_CMD: did help command\n"));
474 state
->mangled_screen
= 1;
478 /*--------- Return to main menu ------------*/
480 state
->next_screen
= main_menu_screen
;
481 dprint((2,"MAIL_CMD: going back to main menu\n"));
485 /*------- View message text --------*/
488 if(any_messages(msgmap
, NULL
, "to View")){
489 state
->next_screen
= mail_view_screen
;
495 /*------- View attachment --------*/
497 state
->next_screen
= attachment_screen
;
498 dprint((2,"MAIL_CMD: going to attachment screen\n"));
502 /*---------- Previous message ----------*/
504 if(any_messages(msgmap
, NULL
, NULL
)){
505 if((i
= mn_get_cur(msgmap
)) > 1L){
506 mn_dec_cur(stream
, msgmap
,
507 (in_index
== View
&& THREADING()
508 && sp_viewing_a_thread(stream
))
511 ? MH_ANYTHD
: MH_NONE
);
512 if(i
== mn_get_cur(msgmap
)){
513 PINETHRD_S
*thrd
= NULL
, *topthrd
= NULL
;
515 if(THRD_INDX_ENABLED()){
516 mn_dec_cur(stream
, msgmap
, MH_ANYTHD
);
517 if(i
== mn_get_cur(msgmap
))
518 q_status_message1(SM_ORDER
, 0, 2,
519 _("Already on first %s in Zoomed Index"),
520 THRD_INDX() ? _("thread") : _("message"));
523 || F_ON(F_NEXT_THRD_WO_CONFIRM
, state
))
526 ret
= want_to(_("View previous thread"), 'y', 'x',
530 q_status_message(SM_ORDER
, 0, 2,
531 _("Viewing previous thread"));
532 new_msgno
= mn_get_cur(msgmap
);
533 mn_set_cur(msgmap
, i
);
534 if(unview_thread(state
, stream
, msgmap
)){
535 state
->next_screen
= mail_index_screen
;
536 state
->view_skipped_index
= 0;
537 state
->mangled_screen
= 1;
540 mn_set_cur(msgmap
, new_msgno
);
541 if(THRD_AUTO_VIEW() && in_index
== View
){
543 thrd
= fetch_thread(stream
,
546 if(count_lflags_in_thread(stream
, thrd
,
549 if(view_thread(state
, stream
, msgmap
, 1)){
550 if(current_index_state
)
551 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
553 state
->view_skipped_index
= 1;
554 command
= MC_VIEW_TEXT
;
561 if(THRD_AUTO_VIEW() && in_index
!= View
){
562 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, new_msgno
));
563 if(thrd
&& thrd
->top
)
564 topthrd
= fetch_thread(stream
, thrd
->top
);
567 j
= count_lflags_in_thread(stream
, topthrd
, msgmap
, MN_NONE
);
570 if(!THRD_AUTO_VIEW() || in_index
== View
|| j
!= 1){
571 if(view_thread(state
, stream
, msgmap
, 1)
572 && current_index_state
)
573 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
577 state
->next_screen
= SCREEN_FUN_NULL
;
580 mn_set_cur(msgmap
, i
); /* put it back */
584 q_status_message1(SM_ORDER
, 0, 2,
585 _("Already on first %s in Zoomed Index"),
586 THRD_INDX() ? _("thread") : _("message"));
593 && ((now
= time(0)) > state
->last_nextitem_forcechk
)){
595 /* check at most once a second */
596 state
->last_nextitem_forcechk
= now
;
599 q_status_message1(SM_ORDER
, 0, 1, _("Already on first %s"),
600 THRD_INDX() ? _("thread") : _("message"));
607 /*---------- Next Message ----------*/
609 if(mn_get_total(msgmap
) > 0L
610 && ((i
= mn_get_cur(msgmap
)) < mn_get_total(msgmap
))){
611 mn_inc_cur(stream
, msgmap
,
612 (in_index
== View
&& THREADING()
613 && sp_viewing_a_thread(stream
))
616 ? MH_ANYTHD
: MH_NONE
);
617 if(i
== mn_get_cur(msgmap
)){
618 PINETHRD_S
*thrd
, *topthrd
;
620 if(THRD_INDX_ENABLED()){
622 mn_inc_cur(stream
, msgmap
, MH_ANYTHD
);
624 if(i
== mn_get_cur(msgmap
)){
625 if(any_lflagged(msgmap
, MN_HIDE
))
626 any_messages(NULL
, "more", "in Zoomed Index");
632 || F_ON(F_NEXT_THRD_WO_CONFIRM
, state
))
635 ret
= want_to(_("View next thread"), 'y', 'x',
639 q_status_message(SM_ORDER
, 0, 2,
640 _("Viewing next thread"));
641 new_msgno
= mn_get_cur(msgmap
);
642 mn_set_cur(msgmap
, i
);
643 if(unview_thread(state
, stream
, msgmap
)){
644 state
->next_screen
= mail_index_screen
;
645 state
->view_skipped_index
= 0;
646 state
->mangled_screen
= 1;
649 mn_set_cur(msgmap
, new_msgno
);
650 if(THRD_AUTO_VIEW() && in_index
== View
){
652 thrd
= fetch_thread(stream
,
655 if(count_lflags_in_thread(stream
, thrd
,
658 if(view_thread(state
, stream
, msgmap
, 1)){
659 if(current_index_state
)
660 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
662 state
->view_skipped_index
= 1;
663 command
= MC_VIEW_TEXT
;
670 if(THRD_AUTO_VIEW() && in_index
!= View
){
671 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, new_msgno
));
672 if(thrd
&& thrd
->top
)
673 topthrd
= fetch_thread(stream
, thrd
->top
);
678 j
= count_lflags_in_thread(stream
, topthrd
, msgmap
, MN_NONE
);
681 if(!THRD_AUTO_VIEW() || in_index
== View
|| j
!= 1){
682 if(view_thread(state
, stream
, msgmap
, 1)
683 && current_index_state
)
684 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
688 state
->next_screen
= SCREEN_FUN_NULL
;
691 mn_set_cur(msgmap
, i
); /* put it back */
695 && (thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, i
)))
697 && get_lflag(stream
, NULL
, thrd
->rawno
, MN_COLL
)){
698 q_status_message(SM_ORDER
, 0, 2,
699 _("Expand collapsed thread to see more messages"));
702 any_messages(NULL
, "more", "in Zoomed Index");
710 || (state
->context_current
->use
& CNTXT_INCMNG
)){
711 char nextfolder
[MAXPATH
];
713 strncpy(nextfolder
, state
->cur_folder
, sizeof(nextfolder
));
714 nextfolder
[sizeof(nextfolder
)-1] = '\0';
715 if(next_folder(NULL
, nextfolder
, sizeof(nextfolder
), nextfolder
,
716 state
->context_current
, NULL
, NULL
))
717 strncpy(prompt
, _(". Press TAB for next folder."),
720 strncpy(prompt
, _(". No more folders to TAB to."),
723 prompt
[sizeof(prompt
)-1] = '\0';
726 any_messages(NULL
, (mn_get_total(msgmap
) > 0L) ? "more" : NULL
,
727 prompt
[0] ? prompt
: NULL
);
730 && ((now
= time(0)) > state
->last_nextitem_forcechk
)){
732 /* check at most once a second */
733 state
->last_nextitem_forcechk
= now
;
740 /*---------- Delete message ----------*/
742 (void) cmd_delete(state
, msgmap
, MCMD_NONE
,
743 (in_index
== View
) ? cmd_delete_view
: cmd_delete_index
);
747 /*---------- Undelete message ----------*/
749 (void) cmd_undelete(state
, msgmap
, MCMD_NONE
);
750 update_titlebar_status();
754 /*---------- Reply to message ----------*/
756 (void) cmd_reply(state
, msgmap
, MCMD_NONE
, NULL
);
760 /*---------- Forward message ----------*/
762 (void) cmd_forward(state
, msgmap
, MCMD_NONE
, NULL
);
766 /*---------- Quit pine ------------*/
768 state
->next_screen
= quit_screen
;
769 dprint((1,"MAIL_CMD: quit\n"));
773 /*---------- Compose message ----------*/
775 state
->prev_screen
= (in_index
== View
) ? mail_view_screen
777 compose_screen(state
);
778 state
->mangled_screen
= 1;
779 if (state
->next_screen
)
784 /*---------- Alt Compose message ----------*/
786 state
->prev_screen
= (in_index
== View
) ? mail_view_screen
789 if(state
->next_screen
)
795 /*--------- Folders menu ------------*/
797 state
->start_in_context
= 1;
799 /*--------- Top of Folders list menu ------------*/
800 case MC_COLLECTIONS
:
801 state
->next_screen
= folder_screen
;
802 dprint((2,"MAIL_CMD: going to folder/collection menu\n"));
806 /*---------- Open specific new folder ----------*/
808 tc
= (state
->context_last
&& !NEWS_TEST(state
->context_current
))
809 ? state
->context_last
: state
->context_current
;
811 newfolder
= broach_folder(question_line
, 1, ¬realinbox
, &tc
);
813 visit_folder(state
, newfolder
, tc
, NULL
, notrealinbox
? 0L : DB_INBOXWOCNTXT
);
820 /*------- Go to Index Screen ----------*/
822 state
->next_screen
= mail_index_screen
;
825 /*------- Skip to next interesting message -----------*/
831 * If we're in the thread index, start looking after this
832 * thread. We don't want to match something in the current
835 start
= mn_get_cur(msgmap
);
836 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
837 if(mn_get_revsort(msgmap
)){
838 /* if reversed, top of thread is last one before next thread */
839 if(thrd
&& thrd
->top
)
840 start
= mn_raw2m(msgmap
, thrd
->top
);
843 /* last msg of thread is at the ends of the branches/nexts */
845 start
= mn_raw2m(msgmap
, thrd
->rawno
);
847 thrd
= fetch_thread(stream
, thrd
->branch
);
849 thrd
= fetch_thread(stream
, thrd
->next
);
856 * Flags is 0 in this case because we want to not skip
857 * messages inside of threads so that we can find threads
858 * which have some unseen messages even though the top-level
859 * of the thread is already seen.
860 * If new_msgno ends up being a message which is not visible
861 * because it isn't at the top-level, the current message #
862 * will be adjusted below in adjust_cur.
865 new_msgno
= next_sorted_flagged((F_UNDEL
867 | ((F_ON(F_TAB_TO_NEW
,state
))
869 stream
, start
, &flags
);
871 else if(THREADING() && sp_viewing_a_thread(stream
)){
872 PINETHRD_S
*thrd
, *topthrd
= NULL
;
874 start
= mn_get_cur(msgmap
);
877 * Things are especially complicated when we're viewing_a_thread
878 * from the thread index. First we have to check within the
879 * current thread for a new message. If none is found, then
880 * we search in the next threads and offer to continue in
881 * them. Then we offer to go to the next folder.
883 flags
= NSF_SKIP_CHID
;
884 new_msgno
= next_sorted_flagged((F_UNDEL
886 | ((F_ON(F_TAB_TO_NEW
,state
))
888 stream
, start
, &flags
);
890 * If we found a match then we are done, that is another message
891 * in the current thread index. Otherwise, we have to look
894 if(!(flags
& NSF_FLAG_MATCH
)){
899 new_msgno
= next_sorted_flagged((F_UNDEL
901 | ((F_ON(F_TAB_TO_NEW
,
904 stream
, start
, &flags
);
906 * If we got a match, new_msgno is a message in
907 * a different thread from the one we are viewing.
909 if(flags
& NSF_FLAG_MATCH
){
910 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
,new_msgno
));
911 if(thrd
&& thrd
->top
)
912 topthrd
= fetch_thread(stream
, thrd
->top
);
914 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD
, state
)){
915 static ESCKEY_S next_opt
[] = {
916 {'y', 'y', "Y", N_("Yes")},
917 {'n', 'n', "N", N_("No")},
918 {TAB
, 'n', "Tab", N_("NextNew")},
923 snprintf(prompt
, sizeof(prompt
), _("View thread number %s? "),
924 topthrd
? comatose(topthrd
->thrdno
) : "?");
926 snprintf(prompt
, sizeof(prompt
),
927 _("View message in thread number %s? "),
928 topthrd
? comatose(topthrd
->thrdno
) : "?");
930 prompt
[sizeof(prompt
)-1] = '\0';
932 ret
= radio_buttons(prompt
, -FOOTER_ROWS(state
),
933 next_opt
, 'y', 'x', NO_HELP
,
944 if(unview_thread(state
, stream
, msgmap
)){
945 state
->next_screen
= mail_index_screen
;
946 state
->view_skipped_index
= 0;
947 state
->mangled_screen
= 1;
950 mn_set_cur(msgmap
, new_msgno
);
951 if(THRD_AUTO_VIEW()){
953 if(count_lflags_in_thread(stream
, topthrd
,
954 msgmap
, MN_NONE
) == 1){
955 if(view_thread(state
, stream
, msgmap
, 1)){
956 if(current_index_state
)
957 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
959 state
->view_skipped_index
= 1;
960 command
= MC_VIEW_TEXT
;
966 if(view_thread(state
, stream
, msgmap
, 1) && current_index_state
)
967 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
969 state
->next_screen
= SCREEN_FUN_NULL
;
972 else if(ret
== 'n' && topthrd
){
974 * skip to end of this thread and look starting
975 * in the next thread.
977 if(mn_get_revsort(msgmap
)){
979 * if reversed, top of thread is last one
982 start
= mn_raw2m(msgmap
, topthrd
->rawno
);
986 * last msg of thread is at the ends of
991 start
= mn_raw2m(msgmap
, thrd
->rawno
);
993 thrd
= fetch_thread(stream
, thrd
->branch
);
995 thrd
= fetch_thread(stream
, thrd
->next
);
1011 start
= mn_get_cur(msgmap
);
1014 * If we are on a collapsed thread, start looking after the
1015 * collapsed part, unless we are viewing the message.
1017 if(THREADING() && in_index
!= View
){
1022 rawno
= mn_m2raw(msgmap
, start
);
1023 thrd
= fetch_thread(stream
, rawno
);
1024 collapsed
= thrd
&& thrd
->next
1025 && get_lflag(stream
, NULL
, rawno
, MN_COLL
);
1028 if(mn_get_revsort(msgmap
)){
1029 if(thrd
&& thrd
->top
)
1030 start
= mn_raw2m(msgmap
, thrd
->top
);
1034 start
= mn_raw2m(msgmap
, thrd
->rawno
);
1036 thrd
= fetch_thread(stream
, thrd
->branch
);
1038 thrd
= fetch_thread(stream
, thrd
->next
);
1047 new_msgno
= next_sorted_flagged((F_UNDEL
1049 | ((F_ON(F_TAB_TO_NEW
,state
))
1051 stream
, start
, &flags
);
1055 * If there weren't any unread messages left, OR there
1056 * aren't any messages at all, we may want to offer to
1057 * go on to the next folder...
1059 if(flags
& NSF_FLAG_MATCH
){
1060 mn_set_cur(msgmap
, new_msgno
);
1061 if(in_index
!= View
)
1062 adjust_cur_to_visible(stream
, msgmap
);
1065 int in_inbox
= sp_flagged(stream
, SP_INBOX
);
1067 if(state
->context_current
1068 && ((NEWS_TEST(state
->context_current
)
1069 && context_isambig(state
->cur_folder
))
1070 || ((state
->context_current
->use
& CNTXT_INCMNG
)
1072 || folder_index(state
->cur_folder
,
1073 state
->context_current
,
1074 FI_FOLDER
) >= 0)))){
1075 char nextfolder
[MAXPATH
];
1076 MAILSTREAM
*nextstream
= NULL
;
1080 strncpy(nextfolder
, state
->cur_folder
, sizeof(nextfolder
));
1081 nextfolder
[sizeof(nextfolder
)-1] = '\0';
1083 if(!(next_folder(&nextstream
, nextfolder
, sizeof(nextfolder
), nextfolder
,
1084 state
->context_current
, &recent_cnt
,
1085 F_ON(F_TAB_NO_CONFIRM
,state
)
1086 ? NULL
: &did_cancel
))){
1088 static ESCKEY_S inbox_opt
[] = {
1089 {'y', 'y', "Y", N_("Yes")},
1090 {'n', 'n', "N", N_("No")},
1091 {TAB
, 'z', "Tab", N_("To Inbox")},
1095 if(F_ON(F_RET_INBOX_NO_CONFIRM
,state
))
1098 /* TRANSLATORS: this is a question, with some information followed
1099 by Return to INBOX? */
1100 if(state
->context_current
->use
&CNTXT_INCMNG
)
1101 snprintf(prompt
, sizeof(prompt
), _("No more incoming folders. Return to \"%s\"? "), state
->inbox_name
);
1103 snprintf(prompt
, sizeof(prompt
), _("No more news groups. Return to \"%s\"? "), state
->inbox_name
);
1105 ret
= radio_buttons(prompt
, -FOOTER_ROWS(state
),
1106 inbox_opt
, 'y', 'x',
1111 * 'z' is a synonym for 'y'. It is not 'y'
1112 * so that it isn't displayed as a default
1113 * action with square-brackets around it
1116 if(ret
== 'y' || ret
== 'z'){
1117 visit_folder(state
, state
->inbox_name
,
1118 state
->context_current
,
1119 NULL
, DB_INBOXWOCNTXT
);
1123 else if (did_cancel
)
1124 cmd_cancelled(NULL
);
1126 if(state
->context_current
->use
&CNTXT_INCMNG
)
1127 q_status_message(SM_ORDER
, 0, 2, _("No more incoming folders"));
1129 q_status_message(SM_ORDER
, 0, 2, _("No more news groups"));
1137 char *front
, type
[80], cnt
[CNTLEN
], fbuf
[MAX_SCREEN_COLS
/2+1];
1138 int rbspace
, avail
, need
, take_back
;
1142 * Incoming_folder_ or news_group_ or folder_ or group_
1144 * _(13 recent) or _(some recent) or nothing
1147 front
= "View next";
1149 (state
->context_current
->use
& CNTXT_INCMNG
)
1150 ? "Incoming folder" : "news group",
1152 type
[sizeof(type
)-1] = '\0';
1153 snprintf(cnt
, sizeof(cnt
), " (%.*s %s)", CNTLEN
-20,
1154 recent_cnt
? long2string(recent_cnt
) : "some",
1155 F_ON(F_TAB_USES_UNSEEN
, ps_global
)
1156 ? "unseen" : "recent");
1157 cnt
[sizeof(cnt
)-1] = '\0';
1160 * Space reserved for radio_buttons call.
1161 * If we make this 3 then radio_buttons won't mess
1162 * with the prompt. If we make it 2, then we get
1163 * one more character to use but radio_buttons will
1164 * cut off the last character of our prompt, which is
1165 * ok because it is a space.
1168 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
1170 need
= strlen(front
)+1 + strlen(type
)+1 +
1171 + strlen(nextfolder
)+2 + strlen(cnt
) +
1174 take_back
= strlen(type
);
1176 (state
->context_current
->use
& CNTXT_INCMNG
)
1177 ? "folder" : "group", sizeof(type
));
1178 take_back
-= strlen(type
);
1181 need
-= strlen(cnt
);
1185 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1186 snprintf(prompt
, sizeof(prompt
), "%.*s %.*s \"%.*s\"%.*s? ",
1187 (MAX_SCREEN_COLS
+1)/8, front
,
1188 (MAX_SCREEN_COLS
+1)/8, type
,
1189 (MAX_SCREEN_COLS
+1)/2,
1190 short_str(nextfolder
, fbuf
, sizeof(fbuf
),
1191 strlen(nextfolder
) -
1192 ((need
>avail
) ? (need
-avail
) : 0),
1194 (MAX_SCREEN_COLS
+1)/8, cnt
);
1195 prompt
[sizeof(prompt
)-1] = '\0';
1199 * When help gets added, this'll have to become
1200 * a loop like the rest...
1202 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD
, state
)){
1203 static ESCKEY_S next_opt
[] = {
1204 {'y', 'y', "Y", N_("Yes")},
1205 {'n', 'n', "N", N_("No")},
1206 {TAB
, 'n', "Tab", N_("NextNew")},
1210 ret
= radio_buttons(prompt
, -FOOTER_ROWS(state
),
1211 next_opt
, 'y', 'x', NO_HELP
,
1214 cmd_cancelled(NULL
);
1222 if(nextstream
&& sp_dead_stream(nextstream
))
1225 visit_folder(state
, nextfolder
,
1226 state
->context_current
, nextstream
,
1228 /* visit_folder takes care of nextstream */
1236 pine_mail_close(nextstream
);
1240 (mn_get_total(msgmap
) > 0L)
1241 ? IS_NEWS(stream
) ? "more undeleted" : "more new"
1251 /*------- Zoom -----------*/
1254 * Right now the way zoom is implemented is sort of silly.
1255 * There are two per-message flags where just one and a
1256 * global "zoom mode" flag to suppress messages from the index
1259 if(any_messages(msgmap
, NULL
, "to Zoom on")){
1260 if(unzoom_index(state
, stream
, msgmap
)){
1261 dprint((4, "\n\n ---- Exiting ZOOM mode ----\n"));
1262 q_status_message(SM_ORDER
,0,2, _("Index Zoom Mode is now off"));
1264 else if((i
= zoom_index(state
, stream
, msgmap
, MN_SLCT
)) != 0){
1265 if(any_lflagged(msgmap
, MN_HIDE
)){
1266 dprint((4,"\n\n ---- Entering ZOOM mode ----\n"));
1267 q_status_message4(SM_ORDER
, 0, 2,
1268 _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"),
1269 THRD_INDX() ? "" : comatose(i
),
1270 THRD_INDX() ? "" : " ",
1271 THRD_INDX() ? _("threads") : _("message"),
1272 THRD_INDX() ? "" : plural(i
));
1275 q_status_message(SM_ORDER
, 0, 2,
1276 _("All messages selected, so not entering Index Zoom Mode"));
1279 any_messages(NULL
, "selected", "to Zoom on");
1285 /*---------- print message on paper ----------*/
1287 if(any_messages(msgmap
, NULL
, "to print"))
1288 (void) cmd_print(state
, msgmap
, MCMD_NONE
, in_index
);
1293 /*---------- Take Address ----------*/
1295 if(F_ON(F_ENABLE_ROLE_TAKE
, state
) ||
1296 any_messages(msgmap
, NULL
, "to Take address from"))
1297 (void) cmd_take_addr(state
, msgmap
, MCMD_NONE
);
1302 /*---------- Save Message ----------*/
1304 if(any_messages(msgmap
, NULL
, "to Save"))
1305 (void) cmd_save(state
, stream
, msgmap
, MCMD_NONE
, in_index
);
1310 /*---------- Export message ----------*/
1312 if(any_messages(msgmap
, NULL
, "to Export")){
1313 (void) cmd_export(state
, msgmap
, question_line
, MCMD_NONE
);
1314 state
->mangled_footer
= 1;
1320 /*---------- Expunge ----------*/
1322 (void) cmd_expunge(state
, stream
, msgmap
, MCMD_NONE
);
1326 /*------- Unexclude -----------*/
1328 if(!(IS_NEWS(stream
) && stream
->rdonly
)){
1329 q_status_message(SM_ORDER
, 0, 3,
1330 _("Unexclude not available for mail folders"));
1332 else if(any_lflagged(msgmap
, MN_EXLD
)){
1338 * Since excluded means "hidden deleted" and "killed",
1339 * the count should reflect the former.
1341 pgm
= mail_newsearchpgm();
1343 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
1344 for(i
= 1L, del_count
= 0L; i
<= stream
->nmsgs
; i
++)
1345 if((mc
= mail_elt(stream
, i
)) && mc
->searched
1346 && get_lflag(stream
, NULL
, i
, MN_EXLD
)
1347 && !(msgno_exceptions(stream
, i
, "0", &exbits
, FALSE
)
1348 && (exbits
& MSG_EX_FILTERED
)))
1352 state
->mangled_footer
= 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
1353 snprintf(prompt
, sizeof(prompt
), "UNexclude %ld message%s in %.*s", del_count
,
1354 plural(del_count
), MAX_SCREEN_COLS
+1-45,
1355 pretty_fn(state
->cur_folder
));
1356 prompt
[sizeof(prompt
)-1] = '\0';
1357 if(F_ON(F_FULL_AUTO_EXPUNGE
, state
)
1358 || (F_ON(F_AUTO_EXPUNGE
, state
)
1359 && (state
->context_current
1360 && (state
->context_current
->use
& CNTXT_INCMNG
))
1361 && context_isambig(state
->cur_folder
))
1362 || want_to(prompt
, 'y', 0, NO_HELP
, WT_NORM
) == 'y'){
1363 long save_cur_rawno
;
1364 int were_viewing_a_thread
;
1366 save_cur_rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
1367 were_viewing_a_thread
= (THREADING()
1368 && sp_viewing_a_thread(stream
));
1370 if(msgno_include(stream
, msgmap
, MI_NONE
)){
1371 clear_index_cache(stream
, 0);
1373 if(stream
&& stream
->spare
)
1374 erase_threading_info(stream
, msgmap
);
1376 refresh_sort(stream
, msgmap
, SRT_NON
);
1379 if(were_viewing_a_thread
){
1380 if(save_cur_rawno
> 0L)
1381 mn_set_cur(msgmap
, mn_raw2m(msgmap
,save_cur_rawno
));
1383 if(view_thread(state
, stream
, msgmap
, 1) && current_index_state
)
1384 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
1387 if(save_cur_rawno
> 0L)
1388 mn_set_cur(msgmap
, mn_raw2m(msgmap
,save_cur_rawno
));
1390 state
->mangled_screen
= 1;
1391 q_status_message2(SM_ORDER
, 0, 4,
1392 "%s message%s UNexcluded",
1393 long2string(del_count
),
1396 if(in_index
!= View
)
1397 adjust_cur_to_visible(stream
, msgmap
);
1400 any_messages(NULL
, NULL
, "UNexcluded");
1403 any_messages(NULL
, "excluded", "to UNexclude");
1406 any_messages(NULL
, "excluded", "to UNexclude");
1411 /*------- Make Selection -----------*/
1413 if(any_messages(msgmap
, NULL
, "to Select")){
1414 if(aggregate_select(state
, msgmap
, question_line
, in_index
) == 0
1415 && (in_index
== MsgIndx
|| in_index
== ThrdIndx
)
1416 && F_ON(F_AUTO_ZOOM
, state
)
1417 && any_lflagged(msgmap
, MN_SLCT
) > 0L
1418 && !any_lflagged(msgmap
, MN_HIDE
))
1419 (void) zoom_index(state
, stream
, msgmap
, MN_SLCT
);
1425 /*------- Toggle Current Message Selection State -----------*/
1427 if(any_messages(msgmap
, NULL
, NULL
)){
1428 if((select_by_current(state
, msgmap
, in_index
)
1429 || (F_OFF(F_UNSELECT_WONT_ADVANCE
, state
)
1430 && !any_lflagged(msgmap
, MN_HIDE
)))
1431 && (i
= mn_get_cur(msgmap
)) < mn_get_total(msgmap
)){
1432 /* advance current */
1433 mn_inc_cur(stream
, msgmap
,
1434 (in_index
== View
&& THREADING()
1435 && sp_viewing_a_thread(stream
))
1437 : (in_index
== View
)
1438 ? MH_ANYTHD
: MH_NONE
);
1445 /*------- Apply command -----------*/
1447 if(any_messages(msgmap
, NULL
, NULL
)){
1448 if(any_lflagged(msgmap
, MN_SLCT
) > 0L){
1449 if(apply_command(state
, stream
, msgmap
, 0,
1450 AC_NONE
, question_line
)){
1451 if(F_ON(F_AUTO_UNSELECT
, state
)){
1452 agg_select_all(stream
, msgmap
, NULL
, 0);
1453 unzoom_index(state
, stream
, msgmap
);
1455 else if(F_ON(F_AUTO_UNZOOM
, state
))
1456 unzoom_index(state
, stream
, msgmap
);
1460 any_messages(NULL
, NULL
, "to Apply command to. Try \"Select\"");
1466 /*-------- Sort command -------*/
1469 int were_threading
= THREADING();
1470 SortOrder sort
= mn_get_sort(msgmap
);
1471 int rev
= mn_get_revsort(msgmap
);
1473 dprint((1,"MAIL_CMD: sort\n"));
1474 if(select_sort(state
, question_line
, &sort
, &rev
)){
1475 /* $ command reinitializes threading collapsed/expanded info */
1476 if(SORT_IS_THREADED(msgmap
) && !SEP_THRDINDX())
1477 erase_threading_info(stream
, msgmap
);
1479 if(ps_global
&& ps_global
->ttyo
){
1480 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
1481 ps_global
->mangled_footer
= 1;
1484 sort_folder(stream
, msgmap
, sort
, rev
, SRT_VRB
|SRT_MAN
);
1487 state
->mangled_footer
= 1;
1490 * We've changed whether we are threading or not so we need to
1491 * exit the index and come back in so that we switch between the
1492 * thread index and the regular index. Sort_folder will have
1493 * reset viewing_a_thread if necessary.
1496 && ((!were_threading
&& THREADING())
1497 || (were_threading
&& !THREADING()))){
1498 state
->next_screen
= mail_index_screen
;
1499 state
->mangled_screen
= 1;
1506 /*------- Toggle Full Headers -----------*/
1508 state
->full_header
++;
1509 if(state
->full_header
== 1){
1510 if(!(state
->quote_suppression_threshold
1511 && (state
->some_quoting_was_suppressed
|| in_index
!= View
)))
1512 state
->full_header
++;
1514 else if(state
->full_header
> 2)
1515 state
->full_header
= 0;
1517 switch(state
->full_header
){
1519 q_status_message(SM_ORDER
, 0, 3,
1520 _("Display of full headers is now off."));
1524 q_status_message1(SM_ORDER
, 0, 3,
1525 _("Quotes displayed, use %s to see full headers"),
1526 F_ON(F_USE_FK
, state
) ? "F9" : "H");
1530 q_status_message(SM_ORDER
, 0, 3,
1531 _("Display of full headers is now on."));
1546 /*------- Try to decrypt message -----------*/
1548 if(state
->smime
&& state
->smime
->need_passphrase
)
1549 smime_get_passphrase();
1555 smime_info_screen(state
);
1560 /*------- Bounce -----------*/
1562 (void) cmd_bounce(state
, msgmap
, MCMD_NONE
, NULL
);
1566 /*------- Flag -----------*/
1568 dprint((4, "\n - flag message -\n"));
1569 (void) cmd_flag(state
, msgmap
, MCMD_NONE
);
1573 /*------- Pipe message -----------*/
1575 (void) cmd_pipe(state
, msgmap
, MCMD_NONE
);
1579 /*--------- Default, unknown command ----------*/
1581 alpine_panic("Unexpected command case");
1585 return((a_changed
|| mn_get_cur(msgmap
) != old_msgno
) ? 1 : 0);
1590 /*----------------------------------------------------------------------
1591 Map some of the special characters into sensible strings for human
1593 c is a UCS-4 character!
1596 pretty_command(UCS c
)
1598 static char buf
[10];
1605 case ' ' : s
= "SPACE"; break;
1606 case '\033' : s
= "ESC"; break;
1607 case '\177' : s
= "DEL"; break;
1608 case ctrl('I') : s
= "TAB"; break;
1609 case ctrl('J') : s
= "LINEFEED"; break;
1610 case ctrl('M') : s
= "RETURN"; break;
1611 case ctrl('Q') : s
= "XON"; break;
1612 case ctrl('S') : s
= "XOFF"; break;
1613 case KEY_UP
: s
= "Up Arrow"; break;
1614 case KEY_DOWN
: s
= "Down Arrow"; break;
1615 case KEY_RIGHT
: s
= "Right Arrow"; break;
1616 case KEY_LEFT
: s
= "Left Arrow"; break;
1617 case KEY_PGUP
: s
= "Prev Page"; break;
1618 case KEY_PGDN
: s
= "Next Page"; break;
1619 case KEY_HOME
: s
= "Home"; break;
1620 case KEY_END
: s
= "End"; break;
1621 case KEY_DEL
: s
= "Delete"; break; /* Not necessary DEL! */
1622 case KEY_JUNK
: s
= "Junk!"; break;
1623 case BADESC
: s
= "Bad Esc"; break;
1624 case NO_OP_IDLE
: s
= "NO_OP_IDLE"; break;
1625 case NO_OP_COMMAND
: s
= "NO_OP_COMMAND"; break;
1626 case KEY_RESIZE
: s
= "KEY_RESIZE"; break;
1627 case KEY_UTF8
: s
= "KEY_UTF8"; break;
1628 case KEY_MOUSE
: s
= "KEY_MOUSE"; break;
1629 case KEY_SCRLUPL
: s
= "KEY_SCRLUPL"; break;
1630 case KEY_SCRLDNL
: s
= "KEY_SCRLDNL"; break;
1631 case KEY_SCRLTO
: s
= "KEY_SCRLTO"; break;
1632 case KEY_XTERM_MOUSE
: s
= "KEY_XTERM_MOUSE"; break;
1633 case KEY_DOUBLE_ESC
: s
= "KEY_DOUBLE_ESC"; break;
1634 case CTRL_KEY_UP
: s
= "Ctrl Up Arrow"; break;
1635 case CTRL_KEY_DOWN
: s
= "Ctrl Down Arrow"; break;
1636 case CTRL_KEY_RIGHT
: s
= "Ctrl Right Arrow"; break;
1637 case CTRL_KEY_LEFT
: s
= "Ctrl Left Arrow"; break;
1650 snprintf(s
= buf
, sizeof(buf
), "F%ld", (long) (c
- PF1
+ 1));
1654 if(c
< ' ' || (c
>= 0x80 && c
< 0xA0)){
1659 d
= (c
& 0x1f) + 'A' - 1;
1660 snprintf(s
= buf
, sizeof(buf
), "%c%c", c1
? '~' : '^', d
);
1663 memset(buf
, 0, sizeof(buf
));
1664 utf8_put((unsigned char *) buf
, (unsigned long) c
);
1674 /*----------------------------------------------------------------------
1675 Complain about bogus input
1677 Args: ch -- input command to complain about
1678 help -- string indicating where to get help
1682 bogus_command(UCS cmd
, char *help
)
1684 if(cmd
== ctrl('Q') || cmd
== ctrl('S'))
1685 q_status_message1(SM_ASYNC
, 0, 2,
1686 "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.",
1687 pretty_command(cmd
));
1688 else if(cmd
== KEY_JUNK
)
1689 q_status_message3(SM_ORDER
, 0, 2,
1690 "Invalid key pressed.%s%s%s",
1691 (help
) ? " Use " : "",
1693 (help
) ? " for help" : "");
1695 q_status_message4(SM_ORDER
, 0, 2,
1696 "Command \"%s\" not defined for this screen.%s%s%s",
1697 pretty_command(cmd
),
1698 (help
) ? " Use " : "",
1700 (help
) ? " for help" : "");
1705 bogus_utf8_command(char *cmd
, char *help
)
1707 q_status_message4(SM_ORDER
, 0, 2,
1708 "Command \"%s\" not defined for this screen.%s%s%s",
1710 (help
) ? " Use " : "",
1712 (help
) ? " for help" : "");
1716 /*----------------------------------------------------------------------
1717 Execute FLAG message command
1719 Args: state -- Various satate info
1720 msgmap -- map of c-client to local message numbers
1722 Result: with side effect of "current" message FLAG flag set or UNset
1726 cmd_flag(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
1728 char *flagit
, *seq
, *screen_text
[20], **exp
, **p
, *answer
= NULL
;
1729 char *keyword_array
[2];
1730 int user_defined_flags
= 0, mailbox_flags
= 0;
1731 int directly_to_maint_screen
= 0;
1732 int use_maint_screen
= F_ON(F_FLAG_SCREEN_DFLT
, ps_global
);
1733 long unflagged
, flagged
, flags
, rawno
;
1734 MESSAGECACHE
*mc
= NULL
;
1736 int i
, cnt
, is_set
, trouble
= 0, rv
= 0;
1738 struct flag_table
*fp
, *ftbl
= NULL
;
1739 struct flag_screen flag_screen
;
1740 static char *flag_screen_text1
[] = {
1741 N_(" Set desired flags for current message below. An 'X' means set"),
1742 N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."),
1746 static char *flag_screen_text2
[] = {
1747 N_(" Set desired flags below for selected messages. A '?' means to"),
1748 N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"),
1749 N_(" to unset it. Use the \"Return\" key to toggle, and choose"),
1750 N_(" \"Exit\" when finished."),
1754 static struct flag_table default_ftbl
[] = {
1755 {N_("Important"), h_flag_important
, F_FLAG
, 0, 0, NULL
, NULL
},
1756 {N_("New"), h_flag_new
, F_SEEN
, 0, 0, NULL
, NULL
},
1757 {N_("Answered"), h_flag_answered
, F_ANS
, 0, 0, NULL
, NULL
},
1758 {N_("Forwarded"), h_flag_forwarded
, F_FWD
, 0, 0, NULL
, NULL
},
1759 {N_("Deleted"), h_flag_deleted
, F_DEL
, 0, 0, NULL
, NULL
},
1760 {NULL
, NO_HELP
, 0, 0, 0, NULL
, NULL
}
1763 /* Only check for dead stream for now. Should check permanent flags
1766 if(!(any_messages(msgmap
, NULL
, "to Flag") && can_set_flag(state
, "flag", 1)))
1769 if(sp_io_error_on_stream(state
->mail_stream
)){
1770 sp_set_io_error_on_stream(state
->mail_stream
, 0);
1771 pine_mail_check(state
->mail_stream
); /* forces write */
1777 user_defined_flags
= 0;
1783 /* count how large ftbl will be */
1784 for(cnt
= 0; default_ftbl
[cnt
].name
; cnt
++)
1787 /* add user flags */
1788 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1789 if(!((kw
->nick
&& !strucmp(FORWARDED_FLAG
, kw
->nick
)) || (kw
->kw
&& !strucmp(FORWARDED_FLAG
, kw
->kw
)))){
1790 user_defined_flags
++;
1796 * Add mailbox flags that aren't user-defined flags.
1797 * Don't consider it if it matches either one of our defined
1798 * keywords or one of our defined nicknames for a keyword.
1800 for(i
= 0; stream_to_user_flag_name(state
->mail_stream
, i
); i
++){
1803 q
= stream_to_user_flag_name(state
->mail_stream
, i
);
1805 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1806 if((kw
->nick
&& !strucmp(kw
->nick
, q
)) || (kw
->kw
&& !strucmp(kw
->kw
, q
)))
1811 if(!kw
&& !(q
&& !strucmp(FORWARDED_FLAG
, q
))){
1817 cnt
+= (user_defined_flags
? 2 : 0) + (mailbox_flags
? 2 : 0);
1819 /* set up ftbl, first the system flags */
1820 ftbl
= (struct flag_table
*) fs_get((cnt
+1) * sizeof(*ftbl
));
1821 memset(ftbl
, 0, (cnt
+1) * sizeof(*ftbl
));
1822 for(i
= 0, fp
= ftbl
; default_ftbl
[i
].name
; i
++, fp
++){
1823 fp
->name
= cpystr(default_ftbl
[i
].name
);
1824 fp
->help
= default_ftbl
[i
].help
;
1825 fp
->flag
= default_ftbl
[i
].flag
;
1826 fp
->set
= default_ftbl
[i
].set
;
1827 fp
->ukn
= default_ftbl
[i
].ukn
;
1830 if(user_defined_flags
){
1831 fp
->flag
= F_COMMENT
;
1832 fp
->name
= cpystr("");
1834 fp
->flag
= F_COMMENT
;
1835 len
= strlen(_("User-defined Keywords from Setup/Config"));
1836 fp
->name
= (char *) fs_get((len
+6+6+1) * sizeof(char));
1837 snprintf(fp
->name
, len
+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config"));
1841 /* then the user-defined keywords */
1842 if(user_defined_flags
)
1843 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1844 if(!((kw
->nick
&& !strucmp(FORWARDED_FLAG
, kw
->nick
))
1845 || (kw
->kw
&& !strucmp(FORWARDED_FLAG
, kw
->kw
)))){
1846 fp
->name
= cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
1847 fp
->keyword
= cpystr(kw
->kw
? kw
->kw
: "");
1848 if(kw
->nick
&& kw
->kw
){
1851 l
= strlen(kw
->kw
)+2;
1852 fp
->comment
= (char *) fs_get((l
+1) * sizeof(char));
1853 snprintf(fp
->comment
, l
+1, "(%.*s)", (int) strlen(kw
->kw
), kw
->kw
);
1854 fp
->comment
[l
] = '\0';
1857 fp
->help
= h_flag_user_flag
;
1858 fp
->flag
= F_KEYWORD
;
1866 fp
->flag
= F_COMMENT
;
1867 fp
->name
= cpystr("");
1869 fp
->flag
= F_COMMENT
;
1870 len
= strlen(_("Other keywords in the mailbox that are not user-defined"));
1871 fp
->name
= (char *) fs_get((len
+6+6+1) * sizeof(char));
1872 snprintf(fp
->name
, len
+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined"));
1876 /* then the extra mailbox-defined keywords */
1878 for(i
= 0; stream_to_user_flag_name(state
->mail_stream
, i
); i
++){
1881 q
= stream_to_user_flag_name(state
->mail_stream
, i
);
1883 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
1884 if((kw
->nick
&& !strucmp(kw
->nick
, q
)) || (kw
->kw
&& !strucmp(kw
->kw
, q
)))
1889 if(!kw
&& !(q
&& !strucmp(FORWARDED_FLAG
, q
))){
1890 fp
->name
= cpystr(q
);
1891 fp
->keyword
= cpystr(q
);
1892 fp
->help
= h_flag_user_flag
;
1893 fp
->flag
= F_KEYWORD
;
1900 flag_screen
.flag_table
= &ftbl
;
1901 flag_screen
.explanation
= screen_text
;
1903 if(MCMD_ISAGG(aopt
)){
1904 if(!pseudo_selected(ps_global
->mail_stream
, msgmap
)){
1905 free_flag_table(&ftbl
);
1909 exp
= flag_screen_text2
;
1910 for(fp
= ftbl
; fp
->name
; fp
++){
1911 fp
->set
= CMD_FLAG_UNKN
; /* set to unknown */
1915 else if(state
->mail_stream
1916 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
1917 && rawno
<= state
->mail_stream
->nmsgs
1918 && (mc
= mail_elt(state
->mail_stream
, rawno
))){
1919 exp
= flag_screen_text1
;
1920 for(fp
= &ftbl
[0]; fp
->name
; fp
++){
1922 if(fp
->flag
== F_KEYWORD
){
1923 /* see if this keyword is defined for this message */
1924 fp
->set
= CMD_FLAG_CLEAR
;
1925 if(user_flag_is_set(state
->mail_stream
,
1926 rawno
, fp
->keyword
))
1927 fp
->set
= CMD_FLAG_SET
;
1929 else if(fp
->flag
== F_FWD
){
1930 /* see if forwarded keyword is defined for this message */
1931 fp
->set
= CMD_FLAG_CLEAR
;
1932 if(user_flag_is_set(state
->mail_stream
,
1933 rawno
, FORWARDED_FLAG
))
1934 fp
->set
= CMD_FLAG_SET
;
1936 else if(fp
->flag
!= F_COMMENT
)
1937 fp
->set
= ((fp
->flag
== F_SEEN
&& !mc
->seen
)
1938 || (fp
->flag
== F_DEL
&& mc
->deleted
)
1939 || (fp
->flag
== F_FLAG
&& mc
->flagged
)
1940 || (fp
->flag
== F_ANS
&& mc
->answered
))
1941 ? CMD_FLAG_SET
: CMD_FLAG_CLEAR
;
1945 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
1946 _("Error accessing message data"));
1947 free_flag_table(&ftbl
);
1951 if(directly_to_maint_screen
)
1952 goto the_maint_screen
;
1955 if (mswin_usedialog ()) {
1956 if (!os_flagmsgdialog (&ftbl
[0])){
1957 free_flag_table(&ftbl
);
1964 int keyword_shortcut
= 0;
1966 if(!use_maint_screen
){
1968 * We're going to call cmd_flag_prompt(). We need
1969 * to decide whether or not to offer the keyword setting
1970 * shortcut. We'll offer it if the user has the feature
1971 * enabled AND there are some possible keywords that could
1974 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
)){
1975 for(fp
= &ftbl
[0]; fp
->name
&& !keyword_shortcut
; fp
++){
1976 if(fp
->flag
== F_KEYWORD
){
1980 first_char
= (fp
->name
&& fp
->name
[0])
1982 if(isascii(first_char
) && isupper(first_char
))
1983 first_char
= tolower((unsigned char) first_char
);
1985 for(tp
=flag_text_opt
; tp
->ch
!= -1; tp
++)
1986 if(tp
->ch
== first_char
)
1995 use_maint_screen
= !cmd_flag_prompt(state
, &flag_screen
,
2000 if(use_maint_screen
){
2001 for(p
= &screen_text
[0]; *exp
; p
++, exp
++)
2006 directly_to_maint_screen
= flag_maintenance_screen(state
, &flag_screen
);
2010 /* reacquire the elt pointer */
2011 mc
= (state
->mail_stream
2012 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
2013 && rawno
<= state
->mail_stream
->nmsgs
)
2014 ? mail_elt(state
->mail_stream
, rawno
) : NULL
;
2016 for(fp
= ftbl
; mc
&& fp
->name
; fp
++){
2020 if((!MCMD_ISAGG(aopt
) && fp
->set
!= !mc
->seen
)
2021 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2029 unflagged
= F_UNSEEN
;
2036 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->answered
)
2037 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2038 flagit
= "\\ANSWERED";
2041 unflagged
= F_UNANS
;
2052 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->deleted
)
2053 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2054 flagit
= "\\DELETED";
2057 unflagged
= F_UNDEL
;
2068 if((!MCMD_ISAGG(aopt
) && fp
->set
!= mc
->flagged
)
2069 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2070 flagit
= "\\FLAGGED";
2073 unflagged
= F_UNFLAG
;
2084 if(!MCMD_ISAGG(aopt
)){
2085 /* see if forwarded is defined for this message */
2086 is_set
= CMD_FLAG_CLEAR
;
2087 if(user_flag_is_set(state
->mail_stream
,
2088 mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
2090 is_set
= CMD_FLAG_SET
;
2093 if((!MCMD_ISAGG(aopt
) && fp
->set
!= is_set
)
2094 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2095 flagit
= FORWARDED_FLAG
;
2098 unflagged
= F_UNFWD
;
2109 if(!MCMD_ISAGG(aopt
)){
2110 /* see if this keyword is defined for this message */
2111 is_set
= CMD_FLAG_CLEAR
;
2112 if(user_flag_is_set(state
->mail_stream
,
2113 mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
2115 is_set
= CMD_FLAG_SET
;
2118 if((!MCMD_ISAGG(aopt
) && fp
->set
!= is_set
)
2119 || (MCMD_ISAGG(aopt
) && fp
->set
!= CMD_FLAG_UNKN
)){
2120 flagit
= fp
->keyword
;
2121 keyword_array
[0] = fp
->keyword
;
2122 keyword_array
[1] = NULL
;
2125 unflagged
= F_UNKEYWORD
;
2129 unflagged
= F_KEYWORD
;
2141 && (seq
= currentf_sequence(state
->mail_stream
, msgmap
,
2142 unflagged
, &flagged
, unflagged
& F_DEL
,
2143 (fp
->flag
== F_KEYWORD
2144 && unflagged
== F_KEYWORD
)
2145 ? keyword_array
: NULL
,
2146 (fp
->flag
== F_KEYWORD
2147 && unflagged
== F_UNKEYWORD
)
2148 ? keyword_array
: NULL
))){
2150 * For user keywords, we may have to create the flag in
2151 * the folder if it doesn't already exist and we are setting
2152 * it (as opposed to clearing it). Mail_flag will
2153 * do that for us, but it's failure isn't very friendly
2154 * error-wise. So we try to make it a little smoother.
2156 if(!(fp
->flag
== F_KEYWORD
|| fp
->flag
== F_FWD
) || !fp
->set
2157 || ((i
=user_flag_index(state
->mail_stream
, flagit
)) >= 0
2159 mail_flag(state
->mail_stream
, seq
, flagit
, flags
);
2161 /* trouble, see if we can add the user flag */
2162 if(state
->mail_stream
->kwd_create
)
2163 mail_flag(state
->mail_stream
, seq
, flagit
, flags
);
2167 if(some_user_flags_defined(state
->mail_stream
))
2168 q_status_message(SM_ORDER
, 3, 4,
2169 _("No more keywords allowed in this folder!"));
2170 else if(fp
->flag
== F_FWD
)
2171 q_status_message(SM_ORDER
, 3, 4,
2172 _("Cannot add keywords for this folder so cannot set Forwarded flag"));
2174 q_status_message(SM_ORDER
, 3, 4,
2175 _("Cannot add keywords for this folder"));
2179 fs_give((void **) &seq
);
2180 if(flagged
&& !trouble
){
2181 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%slagged%s%s%s%s%s message%s%s \"%s\"",
2182 (fp
->set
) ? "F" : "Unf",
2183 MCMD_ISAGG(aopt
) ? " " : "",
2184 MCMD_ISAGG(aopt
) ? long2string(flagged
) : "",
2185 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2187 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2188 ? comatose(mn_total_cur(msgmap
)) : "",
2189 (MCMD_ISAGG(aopt
) && flagged
!= mn_total_cur(msgmap
))
2191 MCMD_ISAGG(aopt
) ? plural(flagged
) : " ",
2192 MCMD_ISAGG(aopt
) ? "" : long2string(mn_get_cur(msgmap
)),
2194 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2195 q_status_message(SM_ORDER
, 0, 2, answer
= tmp_20k_buf
);
2201 free_flag_table(&ftbl
);
2203 if(directly_to_maint_screen
)
2206 if(MCMD_ISAGG(aopt
))
2207 restore_selected(msgmap
);
2210 q_status_message(SM_ORDER
, 0, 2, _("No flags changed."));
2216 /*----------------------------------------------------------------------
2217 Offer concise status line flag prompt
2219 Args: state -- Various satate info
2220 flags -- flags to offer setting
2222 Result: TRUE if flag to set specified in flags struct or FALSE otw
2226 cmd_flag_prompt(struct pine
*state
, struct flag_screen
*flags
, int allow_keyword_shortcuts
)
2228 int r
, setflag
= 1, first_char
;
2229 struct flag_table
*fp
;
2231 char *ftext
, *ftext_not
;
2232 static char *flag_text
=
2233 N_("Flag New, Deleted, Answered, Forwarded or Important ? ");
2234 static char *flag_text_ak
=
2235 N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? ");
2236 static char *flag_text_not
=
2237 N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? ");
2238 static char *flag_text_ak_not
=
2239 N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? ");
2241 if(allow_keyword_shortcuts
){
2243 ESCKEY_S
*dp
, *sp
, *tp
;
2245 for(sp
=flag_text_opt
; sp
->ch
!= -1; sp
++)
2248 for(fp
=(flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++)
2249 if(fp
->flag
== F_KEYWORD
)
2252 /* set up an ESCKEY_S list which includes invisible keys for keywords */
2253 ek
= (ESCKEY_S
*) fs_get((cnt
+ 1) * sizeof(*ek
));
2254 memset(ek
, 0, (cnt
+1) * sizeof(*ek
));
2255 for(dp
=ek
, sp
=flag_text_opt
; sp
->ch
!= -1; sp
++, dp
++)
2258 for(fp
=(flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++){
2259 if(fp
->flag
== F_KEYWORD
){
2260 first_char
= (fp
->name
&& fp
->name
[0]) ? fp
->name
[0] : -2;
2261 if(isascii(first_char
) && isupper(first_char
))
2262 first_char
= tolower((unsigned char) first_char
);
2265 * Check to see if an earlier keyword in the list, or one of
2266 * the builtin system letters already uses this character.
2267 * If so, the first one wins.
2269 for(tp
=ek
; tp
->ch
!= 0; tp
++)
2270 if(tp
->ch
== first_char
)
2274 continue; /* skip it, already used that char */
2276 dp
->ch
= first_char
;
2277 dp
->rval
= first_char
;
2285 ftext
= _(flag_text_ak
);
2286 ftext_not
= _(flag_text_ak_not
);
2290 ftext
= _(flag_text
);
2291 ftext_not
= _(flag_text_not
);
2295 r
= radio_buttons(setflag
? ftext
: ftext_not
,
2296 -FOOTER_ROWS(state
), ek
, '*', SEQ_EXCEPTION
-1,
2297 NO_HELP
, RB_NORM
| RB_SEQ_SENSITIVE
);
2299 * It is SEQ_EXCEPTION-1 just so that it is some value that isn't
2300 * being used otherwise. The keywords use up all the possible
2301 * letters, so a negative number is good, but it has to be different
2302 * from other negative return values.
2304 if(r
== SEQ_EXCEPTION
-1) /* ol'cancelrooney */
2306 else if(r
== 10) /* return and goto flag screen */
2308 else if(r
== '!') /* flip intention */
2314 for(fp
= (flags
->flag_table
? *flags
->flag_table
: NULL
); fp
->name
; fp
++){
2315 if(r
== 'n' || r
== '*' || r
== 'd' || r
== 'a' || r
== 'f'){
2316 if((r
== 'n' && fp
->flag
== F_SEEN
)
2317 || (r
== '*' && fp
->flag
== F_FLAG
)
2318 || (r
== 'd' && fp
->flag
== F_DEL
)
2319 || (r
== 'f' && fp
->flag
== F_FWD
)
2320 || (r
== 'a' && fp
->flag
== F_ANS
)){
2321 fp
->set
= setflag
? CMD_FLAG_SET
: CMD_FLAG_CLEAR
;
2325 else if(allow_keyword_shortcuts
&& fp
->flag
== F_KEYWORD
){
2326 first_char
= (fp
->name
&& fp
->name
[0]) ? fp
->name
[0] : -2;
2327 if(isascii(first_char
) && isupper(first_char
))
2328 first_char
= tolower((unsigned char) first_char
);
2330 if(r
== first_char
){
2331 fp
->set
= setflag
? CMD_FLAG_SET
: CMD_FLAG_CLEAR
;
2337 if(ek
!= flag_text_opt
)
2338 fs_give((void **) &ek
);
2345 * (*ft) is an array of flag_table entries.
2348 free_flag_table(struct flag_table
**ft
)
2350 struct flag_table
*fp
;
2353 for(fp
= (*ft
); fp
->name
; fp
++){
2355 fs_give((void **) &fp
->name
);
2358 fs_give((void **) &fp
->keyword
);
2361 fs_give((void **) &fp
->comment
);
2364 fs_give((void **) ft
);
2369 /*----------------------------------------------------------------------
2370 Execute REPLY message command
2372 Args: state -- Various satate info
2373 msgmap -- map of c-client to local message numbers
2375 Result: reply sent or not
2379 cmd_reply(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, ACTION_S
*role
)
2383 if(any_messages(msgmap
, NULL
, "to Reply to")){
2384 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
2387 rv
= reply(state
, role
);
2389 if(MCMD_ISAGG(aopt
))
2390 restore_selected(msgmap
);
2392 state
->mangled_screen
= 1;
2399 /*----------------------------------------------------------------------
2400 Execute FORWARD message command
2402 Args: state -- Various satate info
2403 msgmap -- map of c-client to local message numbers
2405 Result: selected message[s] forwarded or not
2409 cmd_forward(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, ACTION_S
*role
)
2413 if(any_messages(msgmap
, NULL
, "to Forward")){
2414 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
2417 rv
= forward(state
, role
);
2419 if(MCMD_ISAGG(aopt
))
2420 restore_selected(msgmap
);
2422 state
->mangled_screen
= 1;
2429 /*----------------------------------------------------------------------
2430 Execute BOUNCE message command
2432 Args: state -- Various satate info
2433 msgmap -- map of c-client to local message numbers
2434 aopt -- aggregate options
2436 Result: selected message[s] bounced or not
2440 cmd_bounce(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, ACTION_S
*role
)
2444 if(any_messages(msgmap
, NULL
, "to Bounce")){
2446 if(MCMD_ISAGG(aopt
)){
2447 if(!pseudo_selected(state
->mail_stream
, msgmap
))
2450 else if((i
= any_lflagged(msgmap
, MN_SLCT
)) > 0
2451 && get_lflag(state
->mail_stream
, msgmap
,
2452 mn_m2raw(msgmap
, mn_get_cur(msgmap
)), MN_SLCT
) == 0)
2453 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
2454 _("WARNING: non-selected message is being bounced!"));
2456 && get_lflag(state
->mail_stream
, msgmap
,
2457 mn_m2raw(msgmap
, mn_get_cur(msgmap
)), MN_SLCT
))
2458 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
2459 _("WARNING: not bouncing all selected messages!"));
2461 rv
= bounce(state
, role
);
2463 if(MCMD_ISAGG(aopt
))
2464 restore_selected(msgmap
);
2466 state
->mangled_footer
= 1;
2473 /*----------------------------------------------------------------------
2474 Execute save message command: prompt for folder and call function to save
2476 Args: screen_line -- Line on the screen to prompt on
2477 message -- The MESSAGECACHE entry of message to save
2479 Result: The folder lister can be called to make selection; mangled screen set
2481 This does the prompting for the folder name to save to, possibly calling
2482 up the folder display for selection of folder by user.
2485 cmd_save(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int aopt
, CmdWhere in_index
)
2487 char newfolder
[MAILTMPLEN
], nmsgs
[32], *nick
;
2488 int we_cancel
= 0, rv
= 0, save_flags
;
2490 CONTEXT_S
*cntxt
= NULL
;
2492 SaveDel del
= DontAsk
;
2493 SavePreserveOrder pre
= DontAskPreserve
;
2495 dprint((4, "\n - saving message -\n"));
2497 if(MCMD_ISAGG(aopt
) && !pseudo_selected(stream
, msgmap
))
2500 state
->ugly_consider_advancing_bit
= 0;
2501 if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM
, state
)
2502 && msgno_any_deletedparts(stream
, msgmap
)
2503 && want_to(_("Saved copy will NOT include entire message! Continue"),
2504 'y', 'n', NO_HELP
, WT_FLUSH_IN
| WT_SEQ_SENSITIVE
) != 'y'){
2505 restore_selected(msgmap
);
2506 cmd_cancelled("Save message");
2510 raw
= mn_m2raw(msgmap
, mn_get_cur(msgmap
));
2512 if(mn_total_cur(msgmap
) <= 1L){
2513 snprintf(nmsgs
, sizeof(nmsgs
), "Msg #%ld ", mn_get_cur(msgmap
));
2514 nmsgs
[sizeof(nmsgs
)-1] = '\0';
2515 e
= pine_mail_fetchstructure(stream
, raw
, NULL
);
2517 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
2518 _("Can't save message. Error accessing folder"));
2519 restore_selected(msgmap
);
2524 snprintf(nmsgs
, sizeof(nmsgs
), "%s msgs ", comatose(mn_total_cur(msgmap
)));
2525 nmsgs
[sizeof(nmsgs
)-1] = '\0';
2527 /* e is just used to get a default save folder from the first msg */
2528 e
= pine_mail_fetchstructure(stream
,
2529 mn_m2raw(msgmap
, mn_first_cur(msgmap
)),
2533 del
= (!READONLY_FOLDER(stream
) && F_OFF(F_SAVE_WONT_DELETE
, ps_global
))
2535 if(mn_total_cur(msgmap
) > 1L)
2536 pre
= F_OFF(F_AGG_SEQ_COPY
, ps_global
) ? Preserve
: NoPreserve
;
2538 pre
= DontAskPreserve
;
2540 if(save_prompt(state
, &cntxt
, newfolder
, sizeof(newfolder
), nmsgs
, e
,
2541 raw
, NULL
, &del
, &pre
)){
2543 if(ps_global
&& ps_global
->ttyo
){
2544 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
2545 ps_global
->mangled_footer
= 1;
2548 save_flags
= SV_FIX_DELS
;
2549 if(pre
== RetPreserve
)
2550 save_flags
|= SV_PRESERVE
;
2552 save_flags
|= SV_DELETE
;
2553 if(ps_global
->context_list
== cntxt
&& !strucmp(newfolder
, ps_global
->inbox_name
))
2554 save_flags
|= SV_INBOXWOCNTXT
;
2556 we_cancel
= busy_cue(_("Saving"), NULL
, 1);
2557 i
= save(state
, stream
, cntxt
, newfolder
, msgmap
, save_flags
);
2561 if(i
== mn_total_cur(msgmap
)){
2563 if(mn_total_cur(msgmap
) <= 1L){
2564 int need
, avail
= ps_global
->ttyo
->screen_cols
- 2;
2565 int lennick
, lenfldr
;
2568 && ps_global
->context_list
->next
2569 && context_isambig(newfolder
)){
2570 lennick
= MIN(strlen(cntxt
->nickname
), 500);
2571 lenfldr
= MIN(strlen(newfolder
), 500);
2572 need
= 27 + strlen(long2string(mn_get_cur(msgmap
))) +
2576 need
-= MIN(lennick
-10, need
-avail
);
2577 lennick
-= MIN(lennick
-10, need
-avail
);
2580 if(need
> avail
&& lenfldr
> 10)
2581 lenfldr
-= MIN(lenfldr
-10, need
-avail
);
2584 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
2585 "Message %s copied to \"%s\" in <%s>",
2586 long2string(mn_get_cur(msgmap
)),
2587 short_str(newfolder
, (char *)(tmp_20k_buf
+1000), 1000,
2589 short_str(cntxt
->nickname
,
2590 (char *)(tmp_20k_buf
+2000), 1000,
2592 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2594 else if((nick
=folder_is_target_of_nick(newfolder
, cntxt
)) != NULL
){
2595 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
2596 "Message %s copied to \"%s\"",
2597 long2string(mn_get_cur(msgmap
)),
2599 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2602 char *f
= " folder";
2604 lenfldr
= MIN(strlen(newfolder
), 500);
2605 need
= 28 + strlen(long2string(mn_get_cur(msgmap
))) +
2610 if(need
> avail
&& lenfldr
> 10)
2611 lenfldr
-= MIN(lenfldr
-10, need
-avail
);
2614 snprintf(tmp_20k_buf
,SIZEOF_20KBUF
,
2615 "Message %s copied to%s \"%s\"",
2616 long2string(mn_get_cur(msgmap
)), f
,
2617 short_str(newfolder
, (char *)(tmp_20k_buf
+1000), 1000,
2619 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2623 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%s messages saved",
2624 comatose(mn_total_cur(msgmap
)));
2625 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2629 strncat(tmp_20k_buf
, " and deleted", SIZEOF_20KBUF
-strlen(tmp_20k_buf
)-1);
2630 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
2633 q_status_message(SM_ORDER
, 0, 3, tmp_20k_buf
);
2635 if(!MCMD_ISAGG(aopt
) && F_ON(F_SAVE_ADVANCES
, state
)){
2636 if(sp_new_mail_count(stream
))
2637 process_filter_patterns(stream
, msgmap
,
2638 sp_new_mail_count(stream
));
2640 mn_inc_cur(stream
, msgmap
,
2641 (in_index
== View
&& THREADING()
2642 && sp_viewing_a_thread(stream
))
2644 : (in_index
== View
)
2645 ? MH_ANYTHD
: MH_NONE
);
2648 state
->ugly_consider_advancing_bit
= 1;
2652 if(MCMD_ISAGG(aopt
)) /* straighten out fakes */
2653 restore_selected(msgmap
);
2656 update_titlebar_status(); /* make sure they see change */
2663 role_compose(struct pine
*state
)
2667 if(F_ON(F_ALT_ROLE_MENU
, state
) && mn_get_total(state
->msgmap
) > 0L){
2670 if(nonempty_patterns(ROLE_DO_ROLES
, &pstate
) && first_pattern(&pstate
)){
2671 action
= radio_buttons(_("Compose, Forward, Reply, or Bounce? "),
2672 -FOOTER_ROWS(state
), choose_action
,
2673 'c', 'x', h_role_compose
, RB_NORM
);
2676 q_status_message(SM_ORDER
, 0, 3,
2677 _("No roles available. Use Setup/Rules to add roles."));
2684 if(action
== 'c' || action
== 'r' || action
== 'f' || action
== 'b'){
2685 ACTION_S
*role
= NULL
;
2686 void (*prev_screen
)(struct pine
*) = NULL
, (*redraw
)(void) = NULL
;
2688 redraw
= state
->redrawer
;
2689 state
->redrawer
= NULL
;
2690 prev_screen
= state
->prev_screen
;
2692 state
->next_screen
= SCREEN_FUN_NULL
;
2695 if(role_select_screen(state
, &role
,
2696 action
== 'f' ? MC_FORWARD
:
2697 action
== 'r' ? MC_REPLY
:
2698 action
== 'b' ? MC_BOUNCE
:
2699 action
== 'c' ? MC_COMPOSE
: 0) < 0){
2700 cmd_cancelled(action
== 'f' ? _("Forward") :
2701 action
== 'r' ? _("Reply") :
2702 action
== 'c' ? _("Composition") : _("Bounce"));
2703 state
->next_screen
= prev_screen
;
2704 state
->redrawer
= redraw
;
2705 state
->mangled_screen
= 1;
2709 * If default role was selected (NULL) we need to make
2710 * up a role which won't do anything, but will cause
2711 * compose_mail to think there's already a role so that
2712 * it won't try to confirm the default.
2715 role
= combine_inherited_role(role
);
2717 role
= (ACTION_S
*) fs_get(sizeof(*role
));
2718 memset((void *) role
, 0, sizeof(*role
));
2719 role
->nick
= cpystr("Default Role");
2722 state
->redrawer
= NULL
;
2725 compose_mail(NULL
, NULL
, role
, NULL
, NULL
);
2729 (void) reply(state
, role
);
2733 (void) forward(state
, role
);
2737 (void) bounce(state
, role
);
2744 state
->next_screen
= prev_screen
;
2745 state
->redrawer
= redraw
;
2746 state
->mangled_screen
= 1;
2752 /*----------------------------------------------------------------------
2753 Do the dirty work of prompting the user for a folder name
2756 nfldr should be a buffer at least MAILTMPLEN long
2757 dela -- a pointer to a SaveDel. If it is
2758 DontAsk on input, don't offer Delete prompt
2759 Del on input, offer Delete command with default of Delete
2761 RetDel and RetNoDel are return values
2768 save_prompt(struct pine
*state
, CONTEXT_S
**cntxt
, char *nfldr
, size_t len_nfldr
,
2769 char *nmsgs
, ENVELOPE
*env
, long int rawmsgno
, char *section
,
2770 SaveDel
*dela
, SavePreserveOrder
*prea
)
2772 int rc
, ku
= -1, n
, flags
, last_rc
= 0, saveable_count
= 0, done
= 0;
2773 int delindex
= 0, preindex
= 0, r
;
2774 char prompt
[6*MAX_SCREEN_COLS
+1], *p
, expanded
[MAILTMPLEN
];
2775 char *buf
= tmp_20k_buf
;
2779 SaveDel del
= DontAsk
;
2780 SavePreserveOrder pre
= DontAskPreserve
;
2781 char *deltext
= NULL
;
2782 static HISTORY_S
*history
= NULL
;
2787 alpine_panic("no context ptr in save_prompt");
2789 init_hist(&history
, HISTSIZE
);
2791 if(!(folder
= save_get_default(state
, env
, rawmsgno
, section
, cntxt
)))
2792 return(0); /* message expunged! */
2794 /* how many context's can be saved to... */
2795 for(tc
= state
->context_list
; tc
; tc
= tc
->next
)
2799 /* set up extra command option keys */
2801 ekey
[rc
].ch
= ctrl('T');
2803 ekey
[rc
].name
= "^T";
2804 /* TRANSLATORS: command means go to Folders list */
2805 ekey
[rc
++].label
= N_("To Fldrs");
2807 if(saveable_count
> 1){
2808 ekey
[rc
].ch
= ctrl('P');
2810 ekey
[rc
].name
= "^P";
2811 ekey
[rc
++].label
= N_("Prev Collection");
2813 ekey
[rc
].ch
= ctrl('N');
2815 ekey
[rc
].name
= "^N";
2816 ekey
[rc
++].label
= N_("Next Collection");
2819 if(F_ON(F_ENABLE_TAB_COMPLETE
, ps_global
)){
2822 ekey
[rc
].name
= "TAB";
2823 /* TRANSLATORS: command asks alpine to complete the name when tab is typed */
2824 ekey
[rc
++].label
= N_("Complete");
2827 if(F_ON(F_ENABLE_SUB_LISTS
, ps_global
)){
2828 ekey
[rc
].ch
= ctrl('X');
2830 ekey
[rc
].name
= "^X";
2831 /* TRANSLATORS: list all the matches */
2832 ekey
[rc
++].label
= N_("ListMatches");
2835 if(dela
&& (*dela
== NoDel
|| *dela
== Del
)){
2836 ekey
[rc
].ch
= ctrl('R');
2838 ekey
[rc
].name
= "^R";
2843 if(prea
&& (*prea
== NoPreserve
|| *prea
== Preserve
)){
2844 ekey
[rc
].ch
= ctrl('W');
2846 ekey
[rc
].name
= "^W";
2851 if(saveable_count
> 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
2852 ekey
[rc
].ch
= KEY_UP
;
2855 ekey
[rc
++].label
= "";
2857 ekey
[rc
].ch
= KEY_DOWN
;
2860 ekey
[rc
++].label
= "";
2862 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
2863 ekey
[rc
].ch
= KEY_UP
;
2867 ekey
[rc
++].label
= "";
2869 ekey
[rc
].ch
= KEY_DOWN
;
2872 ekey
[rc
++].label
= "";
2880 /* only show collection number if more than one available */
2881 if(ps_global
->context_list
->next
)
2882 snprintf(prompt
, sizeof(prompt
), "SAVE%s %sto folder in <%s> [%s] : ",
2883 deltext
? deltext
: "",
2885 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2886 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2888 snprintf(prompt
, sizeof(prompt
), "SAVE%s %sto folder [%s] : ",
2889 deltext
? deltext
: "",
2890 nmsgs
, strsquish(buf
, SIZEOF_20KBUF
, folder
, 40));
2892 prompt
[sizeof(prompt
)-1] = '\0';
2895 * If the prompt won't fit, try removing deltext.
2897 if(state
->ttyo
->screen_cols
< strlen(prompt
) + MIN_OPT_ENT_WIDTH
&& deltext
){
2898 if(ps_global
->context_list
->next
)
2899 snprintf(prompt
, sizeof(prompt
), "SAVE %sto folder in <%s> [%s] : ",
2901 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2902 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2904 snprintf(prompt
, sizeof(prompt
), "SAVE %sto folder [%s] : ",
2905 nmsgs
, strsquish(buf
, SIZEOF_20KBUF
, folder
, 40));
2907 prompt
[sizeof(prompt
)-1] = '\0';
2911 * If the prompt still won't fit, remove the extra info contained
2914 if(state
->ttyo
->screen_cols
< strlen(prompt
) + MIN_OPT_ENT_WIDTH
&& *nmsgs
){
2915 if(ps_global
->context_list
->next
)
2916 snprintf(prompt
, sizeof(prompt
), "SAVE to folder in <%s> [%s] : ",
2917 short_str((*cntxt
)->nickname
, shortbuf
, sizeof(shortbuf
), 16, EndDots
),
2918 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2920 snprintf(prompt
, sizeof(prompt
), "SAVE to folder [%s] : ",
2921 strsquish(buf
, SIZEOF_20KBUF
, folder
, 25));
2923 prompt
[sizeof(prompt
)-1] = '\0';
2927 ekey
[delindex
].label
= (del
== NoDel
) ? "Delete" : "No Delete";
2929 if(pre
!= DontAskPreserve
)
2930 ekey
[preindex
].label
= (pre
== NoPreserve
) ? "Preserve Order" : "Any Order";
2933 if(items_in_hist(history
) > 1){
2934 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
2935 ekey
[ku
].label
= HISTORY_KEYLABEL
;
2936 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
2937 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
2941 ekey
[ku
].label
= "";
2942 ekey
[ku
+1].name
= "";
2943 ekey
[ku
+1].label
= "";
2947 flags
= OE_APPEND_CURRENT
| OE_SEQ_SENSITIVE
;
2948 rc
= optionally_enter(nfldr
, -FOOTER_ROWS(state
), 0, len_nfldr
,
2949 prompt
, ekey
, help
, &flags
);
2953 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
2954 _("Error reading folder name"));
2959 removing_trailing_white_space(nfldr
);
2960 removing_leading_white_space(nfldr
);
2962 if(*nfldr
|| *folder
){
2963 char *p
, *name
, *fullname
= NULL
;
2964 int exists
, breakout
= FALSE
;
2967 strncpy(nfldr
, folder
, len_nfldr
-1);
2968 nfldr
[len_nfldr
-1] = '\0';
2971 save_hist(history
, nfldr
, 0, (void *) *cntxt
);
2973 if(!(name
= folder_is_nick(nfldr
, FOLDERS(*cntxt
), 0)))
2976 if(update_folder_spec(expanded
, sizeof(expanded
), name
)){
2977 strncpy(name
= nfldr
, expanded
, len_nfldr
-1);
2978 nfldr
[len_nfldr
-1] = '\0';
2981 exists
= folder_name_exists(*cntxt
, name
, &fullname
);
2983 if(exists
== FEX_ERROR
){
2984 q_status_message1(SM_ORDER
, 0, 3,
2985 _("Problem accessing folder \"%s\""),
2991 strncpy(name
= nfldr
, fullname
, len_nfldr
-1);
2992 nfldr
[len_nfldr
-1] = '\0';
2993 fs_give((void **) &fullname
);
2997 if(exists
& FEX_ISFILE
){
3000 else if((exists
& FEX_ISDIR
)){
3001 char tmp
[MAILTMPLEN
];
3005 CONTEXT_S
*fake_context
;
3008 strncpy(tmp
, name
, sizeof(tmp
));
3009 tmp
[sizeof(tmp
)-2-1] = '\0';
3010 if(tmp
[(l
= strlen(tmp
)) - 1] != tc
->dir
->delim
){
3011 if(l
< sizeof(tmp
)){
3012 tmp
[l
] = tc
->dir
->delim
;
3013 strncpy(&tmp
[l
+1], "[]", sizeof(tmp
)-(l
+1));
3017 strncat(tmp
, "[]", sizeof(tmp
)-strlen(tmp
)-1);
3019 tmp
[sizeof(tmp
)-1] = '\0';
3021 fake_context
= new_context(tmp
, 0);
3023 done
= display_folder_list(&fake_context
, nfldr
,
3024 1, folders_for_save
);
3025 free_context(&fake_context
);
3027 else if(tc
->dir
->delim
3028 && (p
= strrindex(name
, tc
->dir
->delim
))
3030 done
= display_folder_list(cntxt
, nfldr
,
3031 1, folders_for_save
);
3033 q_status_message1(SM_ORDER
, 3, 3,
3034 _("\"%s\" is a directory"), name
);
3036 && !((p
=strrindex(name
, tc
->dir
->delim
)) && *(p
+1) == '\0')){
3037 strncpy(tmp
, name
, sizeof(tmp
));
3038 tmp
[sizeof(tmp
)-1] = '\0';
3039 snprintf(nfldr
, len_nfldr
, "%s%c", tmp
, tc
->dir
->delim
);
3043 else{ /* Doesn't exist, create! */
3044 if((fullname
= folder_as_breakout(*cntxt
, name
)) != NULL
){
3045 strncpy(name
= nfldr
, fullname
, len_nfldr
-1);
3046 nfldr
[len_nfldr
-1] = '\0';
3047 fs_give((void **) &fullname
);
3050 switch(create_for_save(*cntxt
, name
)){
3051 case 1 : /* success */
3054 case 0 : /* error */
3055 case -1 : /* declined */
3064 /* else fall thru like they cancelled */
3067 cmd_cancelled("Save message");
3072 r
= display_folder_list(cntxt
, nfldr
, 0, folders_for_save
);
3080 helper(h_save
, _("HELP FOR SAVE"), HLPD_SIMPLE
);
3081 ps_global
->mangled_screen
= 1;
3084 case 4 : /* redraw */
3087 case 10 : /* previous collection */
3088 for(tc
= (*cntxt
)->prev
; tc
; tc
= tc
->prev
)
3095 for(tc2
= (tc
= (*cntxt
))->next
; tc2
; tc2
= tc2
->next
)
3103 case 11 : /* next collection */
3107 if(((*cntxt
) = (*cntxt
)->next
) == NULL
)
3108 (*cntxt
) = ps_global
->context_list
;
3109 while(NEWS_TEST(*cntxt
) && (*cntxt
) != tc
);
3112 case 12 : /* file name completion */
3113 if(!folder_complete(*cntxt
, nfldr
, len_nfldr
, &n
)){
3114 if(n
&& last_rc
== 12 && !(flags
& OE_USER_MODIFIED
)){
3115 r
= display_folder_list(cntxt
, nfldr
, 1, folders_for_save
);
3117 done
++; /* bingo! */
3119 rc
= 0; /* burn last_rc */
3127 case 14 : /* file name completion */
3128 r
= display_folder_list(cntxt
, nfldr
, 2, folders_for_save
);
3130 done
++; /* bingo! */
3132 rc
= 0; /* burn last_rc */
3136 case 15 : /* Delete / No Delete */
3137 del
= (del
== NoDel
) ? Del
: NoDel
;
3138 deltext
= (del
== NoDel
) ? " (no delete)" : " (and delete)";
3141 case 16 : /* Preserve Order or not */
3142 pre
= (pre
== NoPreserve
) ? Preserve
: NoPreserve
;
3146 if((p
= get_prev_hist(history
, nfldr
, 0, (void *) *cntxt
)) != NULL
){
3147 strncpy(nfldr
, p
, len_nfldr
);
3148 nfldr
[len_nfldr
-1] = '\0';
3149 if(history
->hist
[history
->curindex
])
3150 *cntxt
= (CONTEXT_S
*) history
->hist
[history
->curindex
]->cntxt
;
3158 if((p
= get_next_hist(history
, nfldr
, 0, (void *) *cntxt
)) != NULL
){
3159 strncpy(nfldr
, p
, len_nfldr
);
3160 nfldr
[len_nfldr
-1] = '\0';
3161 if(history
->hist
[history
->curindex
])
3162 *cntxt
= (CONTEXT_S
*) history
->hist
[history
->curindex
]->cntxt
;
3170 alpine_panic("Unhandled case");
3177 ps_global
->mangled_footer
= 1;
3183 strncpy(ps_global
->last_save_folder
, nfldr
, sizeof(ps_global
->last_save_folder
)-1);
3184 ps_global
->last_save_folder
[sizeof(ps_global
->last_save_folder
)-1] = '\0';
3186 ps_global
->last_save_context
= *cntxt
;
3189 strncpy(nfldr
, folder
, len_nfldr
-1);
3190 nfldr
[len_nfldr
-1] = '\0';
3193 /* nickname? Copy real name to nfldr */
3195 && context_isambig(nfldr
)
3196 && (p
= folder_is_nick(nfldr
, FOLDERS(*cntxt
), 0))){
3197 strncpy(nfldr
, p
, len_nfldr
-1);
3198 nfldr
[len_nfldr
-1] = '\0';
3201 if(dela
&& (*dela
== NoDel
|| *dela
== Del
))
3202 *dela
= (del
== NoDel
) ? RetNoDel
: RetDel
;
3204 if(prea
&& (*prea
== NoPreserve
|| *prea
== Preserve
))
3205 *prea
= (pre
== NoPreserve
) ? RetNoPreserve
: RetPreserve
;
3211 /*----------------------------------------------------------------------
3212 Prompt user before implicitly creating a folder for saving
3214 Args: context - context to create folder in
3215 folder - folder name to create
3217 Result: 1 on proceed, -1 on decline, 0 on error
3221 create_for_save_prompt(CONTEXT_S
*context
, char *folder
, int sequence_sensitive
)
3223 if(context
&& ps_global
->context_list
->next
&& context_isambig(folder
)){
3224 if(context
->use
& CNTXT_INCMNG
){
3225 snprintf(tmp_20k_buf
,SIZEOF_20KBUF
,
3226 _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"),
3227 folder
, (strlen(folder
) > 15) ? "..." : "");
3228 q_status_message(SM_ORDER
, 3, 3, tmp_20k_buf
);
3229 return(0); /* error */
3232 snprintf(tmp_20k_buf
,SIZEOF_20KBUF
,
3233 _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"),
3234 folder
, (strlen(folder
) > 15) ? "..." : "",
3236 (strlen(context
->nickname
) > 15) ? "..." : "");
3239 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
3240 _("Folder \"%.40s%s\" doesn't exist. Create"),
3241 folder
, strlen(folder
) > 40 ? "..." : "");
3243 if(want_to(tmp_20k_buf
, 'y', 'n',
3244 NO_HELP
, (sequence_sensitive
) ? WT_SEQ_SENSITIVE
: WT_NORM
) != 'y'){
3245 cmd_cancelled("Save message");
3254 /*----------------------------------------------------------------------
3255 Expunge messages from current folder
3257 Args: state -- pointer to struct holding a bunch of pine state
3258 msgmap -- table mapping msg nums to c-client sequence nums
3259 qline -- screen line to ask questions on
3260 agg -- boolean indicating we're to operate on aggregate set
3265 cmd_expunge(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int agg
)
3267 long del_count
, prefilter_del_count
= 0;
3268 int we_cancel
= 0, rv
= 0;
3269 char prompt
[MAX_SCREEN_COLS
+1];
3271 COLOR_PAIR
*lastc
= NULL
;
3273 dprint((2, "\n - expunge -\n"));
3277 sequence
= MCMD_ISAGG(agg
) ? selected_sequence(stream
, msgmap
, NULL
, 0) : NULL
;
3279 if(MCMD_ISAGG(agg
)){
3282 for(i
= 1L; i
<= stream
->nmsgs
; i
++){
3283 if((mc
= mail_elt(stream
, i
)) != NULL
3284 && mc
->sequence
&& mc
->deleted
)
3288 q_status_message(SM_ORDER
, 0, 4,
3289 _("No selected messages are deleted"));
3293 if(!any_messages(msgmap
, NULL
, "to Expunge"))
3297 if(IS_NEWS(stream
) && stream
->rdonly
){
3298 if(!MCMD_ISAGG(agg
))
3299 del_count
= count_flagged(stream
, F_DEL
);
3301 state
->mangled_footer
= 1; /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3302 snprintf(prompt
, sizeof(prompt
), "Exclude %ld message%s from %.*s", del_count
,
3303 plural(del_count
), MAX_SCREEN_COLS
+1-40,
3304 pretty_fn(state
->cur_folder
));
3305 prompt
[sizeof(prompt
)-1] = '\0';
3306 if(F_ON(F_FULL_AUTO_EXPUNGE
, state
)
3307 || (F_ON(F_AUTO_EXPUNGE
, state
)
3308 && (state
->context_current
3309 && (state
->context_current
->use
& CNTXT_INCMNG
))
3310 && context_isambig(state
->cur_folder
))
3311 || want_to(prompt
, 'y', 0, NO_HELP
, WT_NORM
) == 'y'){
3313 if(F_ON(F_NEWS_CROSS_DELETE
, state
))
3314 cross_delete_crossposts(stream
);
3316 msgno_exclude_deleted(stream
, msgmap
, sequence
);
3317 clear_index_cache(stream
, 0);
3320 * This is kind of surprising at first. For most sort
3321 * orders, if the whole set is sorted, then any subset
3322 * is also sorted. Not so for threaded sorts.
3324 if(SORT_IS_THREADED(msgmap
))
3325 refresh_sort(stream
, msgmap
, SRT_NON
);
3327 state
->mangled_body
= 1;
3328 state
->mangled_header
= 1;
3329 q_status_message2(SM_ORDER
, 0, 4,
3330 "%s message%s excluded",
3331 long2string(del_count
),
3335 any_messages(NULL
, NULL
, "Excluded");
3338 any_messages(NULL
, "deleted", "to Exclude");
3342 else if(READONLY_FOLDER(stream
)){
3343 q_status_message(SM_ORDER
, 0, 4,
3344 _("Can't expunge. Folder is read-only"));
3348 if(!MCMD_ISAGG(agg
)){
3349 prefilter_del_count
= count_flagged(stream
, F_DEL
|F_NOFILT
);
3350 mail_expunge_prefilter(stream
, MI_NONE
);
3351 del_count
= count_flagged(stream
, F_DEL
|F_NOFILT
);
3356 unsigned char *fname
= folder_name_decoded((unsigned char *)state
->cur_folder
);
3357 /* MAX_SCREEN_COLS+1 = sizeof(prompt) */
3358 snprintf(prompt
, sizeof(prompt
), "Expunge %ld message%s from %.*s", del_count
,
3359 plural(del_count
), MAX_SCREEN_COLS
+1-40,
3360 pretty_fn((char *) fname
));
3361 if(fname
) fs_give((void **)&fname
);
3362 prompt
[sizeof(prompt
)-1] = '\0';
3363 state
->mangled_footer
= 1;
3365 if(F_ON(F_FULL_AUTO_EXPUNGE
, state
)
3366 || (F_ON(F_AUTO_EXPUNGE
, state
)
3367 && ((!strucmp(state
->cur_folder
,state
->inbox_name
))
3368 || (state
->context_current
->use
& CNTXT_INCMNG
))
3369 && context_isambig(state
->cur_folder
))
3370 || (ret
=want_to(prompt
, 'y', 0, NO_HELP
, WT_NORM
)) == 'y')
3374 cmd_cancelled("Expunge");
3380 dprint((8, "Expunge max:%ld cur:%ld kill:%d\n",
3381 mn_get_total(msgmap
), mn_get_cur(msgmap
), del_count
));
3383 lastc
= pico_set_colors(state
->VAR_TITLE_FORE_COLOR
,
3384 state
->VAR_TITLE_BACK_COLOR
,
3387 PutLine0(0, 0, "**"); /* indicate delay */
3390 (void)pico_set_colorp(lastc
, PSC_NONE
);
3391 free_color_pair(&lastc
);
3394 MoveCursor(state
->ttyo
->screen_rows
-FOOTER_ROWS(state
), 0);
3397 we_cancel
= busy_cue(_("Expunging"), NULL
, 1);
3399 if(cmd_expunge_work(stream
, msgmap
, sequence
))
3400 state
->mangled_body
= 1;
3403 fs_give((void **)&sequence
);
3406 cancel_busy_cue((sp_expunge_count(stream
) > 0) ? 0 : -1);
3408 lastc
= pico_set_colors(state
->VAR_TITLE_FORE_COLOR
,
3409 state
->VAR_TITLE_BACK_COLOR
,
3411 PutLine0(0, 0, " "); /* indicate delay's over */
3414 (void)pico_set_colorp(lastc
, PSC_NONE
);
3415 free_color_pair(&lastc
);
3420 if(sp_expunge_count(stream
) > 0){
3422 * This is kind of surprising at first. For most sort
3423 * orders, if the whole set is sorted, then any subset
3424 * is also sorted. Not so for threaded sorts.
3426 if(SORT_IS_THREADED(msgmap
))
3427 refresh_sort(stream
, msgmap
, SRT_NON
);
3431 unsigned char *fname
= folder_name_decoded((unsigned char *)state
->cur_folder
);
3432 q_status_message1(SM_ORDER
, 0, 3,
3433 _("No messages expunged from folder \"%s\""),
3434 pretty_fn((char *) fname
));
3435 if(fname
) fs_give((void **)&fname
);
3437 else if(!prefilter_del_count
)
3438 q_status_message(SM_ORDER
, 0, 3,
3439 _("No messages marked deleted. No messages expunged."));
3445 /*----------------------------------------------------------------------
3446 Expunge_and_close callback to prompt user for confirmation
3448 Args: stream -- folder's stream
3449 folder -- name of folder containing folders
3450 deleted -- number of del'd msgs
3452 Result: 'y' to continue with expunge
3455 expunge_prompt(MAILSTREAM
*stream
, char *folder
, long int deleted
)
3459 char prompt_b
[MAX_SCREEN_COLS
+1], temp
[MAILTMPLEN
+1], buff
[MAX_SCREEN_COLS
+1];
3460 char *short_folder_name
;
3465 snprintf(temp
, sizeof(temp
), "%ld", deleted
);
3466 charcnt
= strlen(temp
)+1;
3469 max_folder
= MAX(1,MAXPROMPT
- (36+charcnt
));
3470 strncpy(temp
, folder
, sizeof(temp
));
3471 temp
[sizeof(temp
)-1] = '\0';
3472 short_folder_name
= short_str(temp
,buff
,sizeof(buff
),max_folder
,FrontDots
);
3475 snprintf(prompt_b
, sizeof(prompt_b
),
3476 "Delete %s%ld message%s from \"%s\"",
3477 (deleted
> 1L) ? "all " : "", deleted
,
3478 plural(deleted
), short_folder_name
);
3480 snprintf(prompt_b
, sizeof(prompt_b
),
3481 "Expunge the %ld deleted message%s from \"%s\"",
3482 deleted
, deleted
== 1 ? "" : "s",
3485 return(want_to(prompt_b
, 'y', 0, NO_HELP
, WT_NORM
));
3490 * This is used with multiple append saves. Call it once before
3491 * the series of appends with SSCP_INIT and once after all are
3492 * done with SSCP_END. In between, it is called automatically
3493 * from save_fetch_append or save_fetch_append_cb when we need
3494 * to ask the user if he or she wants to continue even though
3495 * announced message size doesn't match the actual message size.
3496 * As of 2008-02-29 the gmail IMAP server has these size mismatches
3497 * on a regular basis even though the data is ok.
3500 save_size_changed_prompt(long msgno
, int flags
)
3504 static int remember_the_yes
= 0;
3505 static int possible_corruption
= 0;
3506 static ESCKEY_S save_size_opts
[] = {
3507 {'y', 'y', "Y", "Yes"},
3508 {'n', 'n', "N", "No"},
3509 {'a', 'a', "A", "yes to All"},
3513 if(F_ON(F_IGNORE_SIZE
, ps_global
))
3516 if(flags
& SSCP_INIT
|| flags
& SSCP_END
){
3517 if(flags
& SSCP_END
&& possible_corruption
)
3518 q_status_message(SM_ORDER
, 3, 3, "There is possible data corruption, check the results");
3520 remember_the_yes
= 0;
3521 possible_corruption
= 0;
3522 ps_global
->noshow_error
= 0;
3523 ps_global
->noshow_warn
= 0;
3527 if(remember_the_yes
){
3528 snprintf(prompt
, sizeof(prompt
),
3529 "Message to save shrank! (msg # %ld): Continuing", msgno
);
3530 q_status_message(SM_ORDER
, 0, 3, prompt
);
3531 display_message('x');
3532 return(remember_the_yes
);
3535 snprintf(prompt
, sizeof(prompt
),
3536 "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno
);
3537 ret
= radio_buttons(prompt
, -FOOTER_ROWS(ps_global
), save_size_opts
,
3538 'n', 0, h_save_size_changed
, RB_NORM
|RB_NO_NEWMAIL
);
3542 remember_the_yes
= 'y';
3543 possible_corruption
++;
3544 return(remember_the_yes
);
3547 possible_corruption
++;
3551 possible_corruption
= 0;
3552 ps_global
->noshow_error
= 1;
3553 ps_global
->noshow_warn
= 1;
3561 /*----------------------------------------------------------------------
3562 Expunge_and_close callback that happens once the decision to expunge
3563 and close has been made and before expunging and closing begins
3566 Args: stream -- folder's stream
3567 folder -- name of folder containing folders
3568 deleted -- number of del'd msgs
3570 Result: 'y' to continue with expunge
3573 expunge_and_close_begins(int flags
, char *folder
)
3575 if(!(flags
& EC_NO_CLOSE
)){
3576 unsigned char *fname
= folder_name_decoded((unsigned char *)folder
);
3577 q_status_message1(SM_INFO
, 0, 1, "Closing \"%.200s\"...", (char *) fname
);
3578 flush_status_messages(1);
3579 if(fname
) fs_give((void **)&fname
);
3584 /*----------------------------------------------------------------------
3585 Export a message to a plain file in users home directory
3587 Args: state -- pointer to struct holding a bunch of pine state
3588 msgmap -- table mapping msg nums to c-client sequence nums
3589 qline -- screen line to ask questions on
3590 agg -- boolean indicating we're to operate on aggregate set
3595 cmd_export(struct pine
*state
, MSGNO_S
*msgmap
, int qline
, int aopt
)
3597 char filename
[MAXPATH
+1], full_filename
[MAXPATH
+1], *err
;
3599 int r
, leading_nl
, failure
= 0, orig_errno
= 0, rflags
= GER_NONE
;
3600 int flags
= GE_IS_EXPORT
| GE_SEQ_SENSITIVE
, rv
= 0;
3604 long i
, count
= 0L, start_of_append
= 0, rawno
;
3607 struct variable
*vars
= state
? ps_global
->vars
: NULL
;
3608 ESCKEY_S export_opts
[5];
3609 static HISTORY_S
*history
= NULL
;
3611 if(ps_global
->restricted
){
3612 q_status_message(SM_ORDER
, 0, 3,
3613 "Alpine demo can't export messages to files");
3617 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
3620 export_opts
[i
= 0].ch
= ctrl('T');
3621 export_opts
[i
].rval
= 10;
3622 export_opts
[i
].name
= "^T";
3623 export_opts
[i
++].label
= N_("To Files");
3625 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3626 if(ps_global
->VAR_DOWNLOAD_CMD
&& ps_global
->VAR_DOWNLOAD_CMD
[0]){
3627 export_opts
[i
].ch
= ctrl('V');
3628 export_opts
[i
].rval
= 12;
3629 export_opts
[i
].name
= "^V";
3630 /* TRANSLATORS: this is an abbreviation for Download Messages */
3631 export_opts
[i
++].label
= N_("Downld Msg");
3633 #endif /* !(DOS || MAC) */
3635 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps_global
)){
3636 export_opts
[i
].ch
= ctrl('I');
3637 export_opts
[i
].rval
= 11;
3638 export_opts
[i
].name
= "TAB";
3639 export_opts
[i
++].label
= N_("Complete");
3643 /* Commented out since it's not yet support! */
3644 if(F_ON(F_ENABLE_SUB_LISTS
,ps_global
)){
3645 export_opts
[i
].ch
= ctrl('X');
3646 export_opts
[i
].rval
= 14;
3647 export_opts
[i
].name
= "^X";
3648 export_opts
[i
++].label
= N_("ListMatches");
3653 * If message has attachments, add a toggle that will allow the user
3654 * to save all of the attachments to a single directory, using the
3655 * names provided with the attachments or part names. What we'll do is
3656 * export the message as usual, and then export the attachments into
3657 * a subdirectory that did not exist before. The subdir will be named
3658 * something based on the name of the file being saved to, but a
3661 if(!MCMD_ISAGG(aopt
)
3662 && state
->mail_stream
3663 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
3664 && rawno
<= state
->mail_stream
->nmsgs
3665 && (env
= pine_mail_fetchstructure(state
->mail_stream
, rawno
, &b
))
3667 && b
->type
== TYPEMULTIPART
3669 && strucmp(b
->subtype
, "ALTERNATIVE") != 0){
3672 part
= b
->nested
.part
; /* 1st part */
3673 if(part
&& part
->next
)
3674 flags
|= GE_ALLPARTS
;
3677 export_opts
[i
].ch
= -1;
3680 if(mn_total_cur(msgmap
) <= 1L){
3681 snprintf(nmsgs
, sizeof(nmsgs
), "Msg #%ld", mn_get_cur(msgmap
));
3682 nmsgs
[sizeof(nmsgs
)-1] = '\0';
3685 snprintf(nmsgs
, sizeof(nmsgs
), "%s messages", comatose(mn_total_cur(msgmap
)));
3686 nmsgs
[sizeof(nmsgs
)-1] = '\0';
3689 r
= get_export_filename(state
, filename
, NULL
, full_filename
,
3690 sizeof(filename
), nmsgs
, "EXPORT",
3691 export_opts
, &rflags
, qline
, flags
, &history
);
3696 cmd_cancelled("Export message");
3700 q_status_message1(SM_ORDER
, 0, 2,
3701 _("Can't export to file outside of %s"),
3708 #if !defined(DOS) && !defined(MAC) && !defined(OS2)
3709 else if(r
== 12){ /* Download */
3710 char cmd
[MAXPATH
], *tfp
= NULL
;
3716 if(ps_global
->restricted
){
3717 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3718 "Download disallowed in restricted mode");
3723 tfp
= temp_nam(NULL
, "pd");
3724 build_updown_cmd(cmd
, sizeof(cmd
), ps_global
->VAR_DOWNLOAD_CMD_PREFIX
,
3725 ps_global
->VAR_DOWNLOAD_CMD
, tfp
);
3726 dprint((1, "Download cmd called: \"%s\"\n", cmd
));
3727 if((so
= so_get(FileStar
, tfp
, WRITE_ACCESS
|OWNER_ONLY
|WRITE_TO_LOCALE
)) != NULL
){
3728 gf_set_so_writec(&pc
, so
);
3730 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
)){
3731 if(!(state
->mail_stream
3732 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
3733 && rawno
<= state
->mail_stream
->nmsgs
3734 && (mc
= mail_elt(state
->mail_stream
, rawno
))
3738 if(!(env
= pine_mail_fetchstructure(state
->mail_stream
,
3739 mn_m2raw(msgmap
, i
), &b
))
3740 || !bezerk_delimiter(env
, mc
, pc
, next
++)
3741 || !format_message(mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
3742 env
, b
, NULL
, FM_NEW_MESS
| FM_NOWRAP
, pc
)){
3743 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3744 err
= "Error writing tempfile for download");
3749 gf_clear_so_writec(so
);
3750 if(so_give(&so
)){ /* close file */
3752 err
= "Error writing tempfile for download";
3756 if((syspipe
= open_system_pipe(cmd
, NULL
, NULL
,
3757 PIPE_USER
| PIPE_RESET
,
3758 0, pipe_callback
, pipe_report_error
)) != NULL
)
3759 (void) close_system_pipe(&syspipe
, NULL
, pipe_callback
);
3761 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3762 err
= _("Error running download command"));
3766 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3767 err
= "Error building temp file for download");
3771 fs_give((void **)&tfp
);
3775 q_status_message(SM_ORDER
, 0, 3, _("Download Command Completed"));
3779 #endif /* !(DOS || MAC) */
3782 if(rflags
& GER_APPEND
)
3787 dprint((5, "Opening file \"%s\" for export\n",
3788 full_filename
? full_filename
: "?"));
3790 if(!(store
= so_get(FileStar
, full_filename
, WRITE_ACCESS
|WRITE_TO_LOCALE
))){
3791 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
3792 /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */
3793 _("Error opening file \"%s\" to export message: %s"),
3794 full_filename
, error_description(errno
));
3798 gf_set_so_writec(&pc
, store
);
3801 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
), count
++){
3802 env
= pine_mail_fetchstructure(state
->mail_stream
, mn_m2raw(msgmap
, i
),
3805 err
= _("Can't export message. Error accessing mail folder");
3810 if(!(state
->mail_stream
3811 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
3812 && rawno
<= state
->mail_stream
->nmsgs
3813 && (mc
= mail_elt(state
->mail_stream
, rawno
))
3817 start_of_append
= so_tell(store
);
3818 if(!bezerk_delimiter(env
, mc
, pc
, leading_nl
)
3819 || !format_message(mn_m2raw(msgmap
, i
), env
, b
, NULL
,
3820 FM_NEW_MESS
| FM_NOWRAP
, pc
)){
3821 orig_errno
= errno
; /* save in case things are really bad */
3822 failure
= 1; /* pop out of here */
3829 gf_clear_so_writec(store
);
3830 if(so_give(&store
)) /* release storage */
3834 our_truncate(full_filename
, (off_t
)start_of_append
);
3836 dprint((1, "FAILED Export: fetch(%ld): %s\n",
3837 i
, err
? err
: "?"));
3838 q_status_message(SM_ORDER
| SM_DING
, 3, 4, err
);
3841 dprint((1, "FAILED Export: file \"%s\" : %s\n",
3842 full_filename
? full_filename
: "?",
3843 error_description(orig_errno
)));
3844 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
3845 /* TRANSLATORS: Error exporting to <filename>: <error text> */
3846 _("Error exporting to \"%s\" : %s"),
3847 filename
, error_description(orig_errno
));
3851 if(rflags
& GER_ALLPARTS
&& full_filename
[0]){
3852 char dir
[MAXPATH
+1];
3853 char lfile
[MAXPATH
+1];
3854 int ok
= 0, tries
= 0, saved
= 0, errs
= 0, counter
= 2;
3858 * Now we want to save all of the attachments to a subdirectory.
3859 * To make it easier for us and probably easier for the user, and
3860 * to prevent the user from shooting himself in the foot, we
3861 * make a new subdirectory so that we can't possibly step on
3862 * any existing files, and we don't need any interaction with the
3863 * user while saving.
3865 * We'll just use the directory name full_filename.d or if that
3866 * already exists and isn't empty, we'll try adding a suffix to
3867 * that until we get something to use.
3870 if(strlen(full_filename
) + strlen(".d") + 1 > sizeof(dir
)){
3871 q_status_message1(SM_ORDER
| SM_DING
, 3, 4,
3872 _("Can't save attachments, filename too long: %s"),
3878 snprintf(dir
, sizeof(dir
), "%.*s.d", MAXPATH
-2, full_filename
);
3879 dir
[sizeof(dir
)-1] = '\0';
3883 switch(r
= is_writable_dir(dir
)){
3884 case 0: /* exists and is a writable dir */
3886 * We could figure out if it is empty and use it in
3887 * that case, but that sounds like a lot of work, so
3888 * just fall through to default.
3892 if(strlen(full_filename
) + strlen(".d") + 1 +
3893 1 + strlen(long2string((long) tries
)) > sizeof(dir
)){
3894 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3895 "Problem saving attachments");
3899 snprintf(dir
, sizeof(dir
), "%.*s.d_%s", MAXPATH
- (int) strlen(long2string((long) tries
))-3, full_filename
,
3900 long2string((long) tries
));
3901 dir
[sizeof(dir
)-1] = '\0';
3904 case 3: /* doesn't exist, that's good! */
3905 /* make new directory */
3909 } while(!ok
&& tries
< 1000);
3912 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3913 _("Problem saving attachments"));
3917 /* create the new directory */
3918 if(our_mkdir(dir
, 0700)){
3919 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
3920 _("Problem saving attachments: %s: %s"), dir
,
3921 error_description(errno
));
3925 if(!(state
->mail_stream
3926 && (rawno
= mn_m2raw(msgmap
, mn_get_cur(msgmap
))) > 0L
3927 && rawno
<= state
->mail_stream
->nmsgs
3928 && (env
=pine_mail_fetchstructure(state
->mail_stream
,rawno
,&b
))
3930 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
3931 _("Problem reading message"));
3935 zero_atmts(state
->atmts
);
3936 describe_mime(b
, "", 1, 1, 0, 0);
3939 if(a
&& a
->description
) /* skip main body part */
3942 for(; a
->description
!= NULL
; a
++){
3943 /* skip over these parts of the message */
3944 if(MIME_MSG_A(a
) || MIME_DGST_A(a
) || MIME_VCARD_A(a
))
3948 (void) get_filename_parameter(lfile
, sizeof(lfile
), a
->body
, NULL
);
3950 if(lfile
[0] == '\0'){ /* MAXPATH + 1 = sizeof(lfile) */
3951 snprintf(lfile
, sizeof(lfile
), "part_%.*s", MAXPATH
+1-6,
3952 a
->number
? a
->number
: "?");
3953 lfile
[sizeof(lfile
)-1] = '\0';
3956 if(strlen(dir
) + strlen(S_FILESEP
) + strlen(lfile
) + 1
3957 > sizeof(filename
)){
3959 "FAILED Att Export: name too long: %s\n",
3960 dir
, S_FILESEP
, lfile
));
3965 /* although files are being saved in a unique directory, there is
3966 * no guarantee that attachment names have unique names, so we have
3967 * to make sure that we are not constantly rewriting the same file name
3968 * over and over. In order to avoid this we test if the file already exists,
3969 * and if so, we write a counter name in the file name, just before the
3970 * extension of the file, and separate it with an underscore.
3972 snprintf(filename
, sizeof(filename
), "%.*s%.*s%.*s", (int) strlen(dir
), dir
,
3973 (int) strlen(S_FILESEP
), S_FILESEP
,
3974 MAXPATH
- (int) strlen(dir
) - (int) strlen(S_FILESEP
), lfile
);
3975 filename
[sizeof(filename
)-1] = '\0';
3976 while((ok
= can_access(filename
, ACCESS_EXISTS
)) == 0 && errs
== 0){
3977 char *ext
, count
[MAXPATH
+1];
3978 unsigned long total
;
3979 snprintf(count
, sizeof(count
), "%d", counter
);
3980 if((ext
= strrchr(lfile
, '.')) != NULL
)
3982 total
= strlen(dir
) + strlen(S_FILESEP
) + strlen(lfile
) + strlen(count
) + 3
3983 + (ext
? strlen(ext
+1) : 0);
3984 if(total
> sizeof(filename
)){
3986 "FAILED Att Export: name too long: %s\n",
3987 dir
, S_FILESEP
, lfile
));
3991 snprintf(filename
, sizeof(filename
), "%.*s%.*s%.*s%.*s%.*d%.*s%.*s",
3992 (int) strlen(dir
), dir
, (int) strlen(S_FILESEP
), S_FILESEP
,
3993 (int) strlen(lfile
), lfile
,
3994 ext
? 1 : 0, ext
? "_" : "",
3995 (int) strlen(count
), counter
++,
3996 ext
? 1 : 0, ext
? "." : "",
3997 ext
? (int) (sizeof(filename
) - total
) : 0,
3999 filename
[sizeof(filename
)-1] = '\0';
4002 if(write_attachment_to_file(state
->mail_stream
, rawno
,
4003 a
, GER_NONE
, filename
) == 1)
4011 q_status_message1(SM_ORDER
, 3, 3,
4012 "Errors saving some attachments, %s attachments saved",
4013 long2string((long) saved
));
4015 q_status_message(SM_ORDER
, 3, 3,
4016 _("Problems saving attachments"));
4020 q_status_message2(SM_ORDER
, 0, 3,
4021 /* TRANSLATORS: Saved <how many> attachments to <directory name> */
4022 _("Saved %s attachments to %s"),
4023 long2string((long) saved
), dir
);
4025 q_status_message(SM_ORDER
, 3, 3, _("No attachments to save"));
4028 else if(mn_total_cur(msgmap
) > 1L)
4029 q_status_message4(SM_ORDER
,0,3,
4030 "%s message%s %s to file \"%s\"",
4031 long2string(count
), plural(count
),
4034 : rflags
& GER_APPEND
? "appended" : "exported",
4037 q_status_message3(SM_ORDER
,0,3,
4038 "Message %s %s to file \"%s\"",
4039 long2string(mn_get_cur(msgmap
)),
4042 : rflags
& GER_APPEND
? "appended" : "exported",
4048 if(MCMD_ISAGG(aopt
))
4049 restore_selected(msgmap
);
4056 * Ask user what file to export to. Export from srcstore to that file.
4058 * Args ps -- pine struct
4059 * srctext -- pointer to source text
4060 * srctype -- type of that source text
4061 * prompt_msg -- see get_export_filename
4064 * Returns: != 0 : error
4068 simple_export(struct pine
*ps
, void *srctext
, SourceType srctype
, char *prompt_msg
, char *lister_msg
)
4070 int r
= 1, rflags
= GER_NONE
;
4071 char filename
[MAXPATH
+1], full_filename
[MAXPATH
+1];
4072 STORE_S
*store
= NULL
;
4073 struct variable
*vars
= ps
? ps
->vars
: NULL
;
4074 static HISTORY_S
*history
= NULL
;
4075 static ESCKEY_S simple_export_opts
[] = {
4076 {ctrl('T'), 10, "^T", N_("To Files")},
4077 {-1, 0, NULL
, NULL
},
4078 {-1, 0, NULL
, NULL
}};
4080 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps
)){
4081 simple_export_opts
[r
].ch
= ctrl('I');
4082 simple_export_opts
[r
].rval
= 11;
4083 simple_export_opts
[r
].name
= "TAB";
4084 simple_export_opts
[r
].label
= N_("Complete");
4088 q_status_message(SM_ORDER
, 0, 2, _("Error allocating space"));
4093 simple_export_opts
[++r
].ch
= -1;
4095 full_filename
[0] = '\0';
4097 r
= get_export_filename(ps
, filename
, NULL
, full_filename
, sizeof(filename
),
4098 prompt_msg
, lister_msg
, simple_export_opts
, &rflags
,
4099 -FOOTER_ROWS(ps
), GE_IS_EXPORT
, &history
);
4103 else if(!full_filename
[0]){
4108 dprint((5, "Opening file \"%s\" for export\n",
4109 full_filename
? full_filename
: "?"));
4111 if((store
= so_get(FileStar
, full_filename
, WRITE_ACCESS
|WRITE_TO_LOCALE
)) != NULL
){
4115 gf_set_so_writec(&pc
, store
);
4116 gf_set_readc(&gc
, srctext
, (srctype
== CharStar
)
4117 ? strlen((char *)srctext
)
4121 if((pipe_err
= gf_pipe(gc
, pc
)) != NULL
){
4122 q_status_message2(SM_ORDER
| SM_DING
, 3, 3,
4123 /* TRANSLATORS: Problem saving to <filename>: <error text> */
4124 _("Problem saving to \"%s\": %s"),
4125 filename
, pipe_err
);
4131 gf_clear_so_writec(store
);
4132 if(so_give(&store
)){
4133 q_status_message2(SM_ORDER
| SM_DING
, 3, 3,
4134 _("Problem saving to \"%s\": %s"),
4135 filename
, error_description(errno
));
4140 q_status_message2(SM_ORDER
| SM_DING
, 3, 4,
4141 _("Error opening file \"%s\" for export: %s"),
4142 full_filename
, error_description(errno
));
4149 /* overloading full_filename */
4150 snprintf(full_filename
, sizeof(full_filename
), "%c%s",
4151 (prompt_msg
&& prompt_msg
[0])
4152 ? (islower((unsigned char)prompt_msg
[0])
4153 ? toupper((unsigned char)prompt_msg
[0]) : prompt_msg
[0])
4155 (prompt_msg
&& prompt_msg
[0]) ? prompt_msg
+1 : "ext");
4156 full_filename
[sizeof(full_filename
)-1] = '\0';
4157 q_status_message3(SM_ORDER
,0,2,"%s %s to \"%s\"",
4161 : rflags
& GER_APPEND
? "appended" : "exported",
4166 cmd_cancelled("Export");
4170 q_status_message1(SM_ORDER
, 0, 2,
4171 _("Can't export to file outside of %s"), VAR_OPER_DIR
);
4175 ps
->mangled_footer
= 1;
4181 * Ask user what file to export to.
4183 * filename -- On input, this is the filename to start with. On exit,
4184 * this is the filename chosen. (but this isn't used)
4185 * deefault -- This is the default value if user hits return. The
4186 * prompt will have [deefault] added to it automatically.
4187 * full_filename -- This is the full filename on exit.
4188 * len -- Minimum length of _both_ filename and full_filename.
4189 * prompt_msg -- Message to insert in prompt.
4190 * lister_msg -- Message to insert in file_lister.
4191 * opts -- Key options.
4192 * There is a tangled relationship between the callers
4193 * and this routine as far as opts are concerned. Some
4194 * of the opts are handled here. In particular, r == 3,
4195 * r == 10, r == 11, and r == 13 are all handled here.
4196 * Don't use those values unless you want what happens
4197 * here. r == 12 and others are handled by the caller.
4198 * rflags -- Return flags
4199 * GER_OVER - overwrite of existing file
4200 * GER_APPEND - append of existing file
4201 * else file did not exist before
4203 * GER_ALLPARTS - AllParts toggle was turned on
4205 * qline -- Command line to prompt on.
4206 * flags -- Logically OR'd flags
4207 * GE_IS_EXPORT - The command was an Export command
4208 * so the prompt should include
4210 * GE_SEQ_SENSITIVE - The command that got us here is
4211 * sensitive to sequence number changes
4212 * caused by unsolicited expunges.
4213 * GE_NO_APPEND - We will not allow append to an
4214 * existing file, only removal of the
4215 * file if it exists.
4216 * GE_IS_IMPORT - We are selecting for reading.
4217 * No overwriting or checking for
4218 * existence at all. Don't use this
4219 * together with GE_NO_APPEND.
4220 * GE_ALLPARTS - Turn on AllParts toggle.
4221 * GE_BINARY - Turn on Binary toggle.
4223 * Returns: -1 cancelled
4224 * -2 prohibited by VAR_OPER_DIR
4225 * -3 other error, already reported here
4227 * 12 user chose 12 command from opts
4230 get_export_filename(struct pine
*ps
, char *filename
, char *deefault
,
4231 char *full_filename
, size_t len
, char *prompt_msg
,
4232 char *lister_msg
, ESCKEY_S
*optsarg
, int *rflags
,
4233 int qline
, int flags
, HISTORY_S
**history
)
4235 char dir
[MAXPATH
+1], dir2
[MAXPATH
+1], orig_dir
[MAXPATH
+1];
4236 char precolon
[MAXPATH
+1], postcolon
[MAXPATH
+1];
4237 char filename2
[MAXPATH
+1], tmp
[MAXPATH
+1], *fn
, *ill
;
4238 int l
, i
, ku
= -1, kp
= -1, r
, fatal
, homedir
= 0, was_abs_path
=0, avail
, ret
= 0;
4239 int allparts
= 0, binary
= 0;
4240 char prompt_buf
[400];
4242 ESCKEY_S
*opts
= NULL
;
4243 struct variable
*vars
= ps
->vars
;
4244 static HISTORY_S
*dir_hist
= NULL
;
4246 int pos
, hist_len
= 0;
4249 /* we will fake a history with the ps_global->VAR_HISTORY variable
4250 * We fake that we combine this variable into a history variable
4251 * by stacking VAR_HISTORY on top of dir_hist. We keep track of this
4252 * by looking at the variable pos.
4254 if(ps_global
->VAR_HISTORY
!= NULL
)
4255 for(hist_len
= 0; ps_global
->VAR_HISTORY
[hist_len
]
4256 && ps_global
->VAR_HISTORY
[hist_len
][0]; hist_len
++)
4259 pos
= hist_len
+ items_in_hist(dir_hist
);
4261 if(flags
& GE_ALLPARTS
|| history
|| dir_hist
){
4263 * Copy the opts and add one to the end of the list.
4265 for(i
= 0; optsarg
[i
].ch
!= -1; i
++)
4268 if(dir_hist
|| hist_len
> 0)
4272 i
+= dir_hist
|| hist_len
> 0 ? 2 : 4;
4274 if(flags
& GE_ALLPARTS
)
4277 if(flags
& GE_BINARY
)
4280 opts
= (ESCKEY_S
*) fs_get((i
+1) * sizeof(*opts
));
4281 memset(opts
, 0, (i
+1) * sizeof(*opts
));
4283 for(i
= 0; optsarg
[i
].ch
!= -1; i
++){
4284 opts
[i
].ch
= optsarg
[i
].ch
;
4285 opts
[i
].rval
= optsarg
[i
].rval
;
4286 opts
[i
].name
= optsarg
[i
].name
; /* no need to make a copy */
4287 opts
[i
].label
= optsarg
[i
].label
; /* " */
4290 if(flags
& GE_ALLPARTS
){
4292 opts
[i
].ch
= ctrl('P');
4294 opts
[i
].name
= "^P";
4295 /* TRANSLATORS: Export all attachment parts */
4296 opts
[i
++].label
= N_("AllParts");
4299 if(flags
& GE_BINARY
){
4301 opts
[i
].ch
= ctrl('R');
4303 opts
[i
].name
= "^R";
4304 opts
[i
++].label
= N_("Binary");
4307 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf
,
4308 SIZEOF_20KBUF
, filename
);
4310 /* In the Windows operating system we always return the UTF8 encoded name */
4311 if(strcmp(tmp_20k_buf
, filename
)){
4312 opts
[i
].ch
= ctrl('N');
4314 opts
[i
].name
= "^N";
4315 opts
[i
++].label
= "Name UTF8";
4318 strncpy(filename
, tmp_20k_buf
, len
);
4319 filename
[len
-1] = '\0';
4320 #endif /* _WINDOWS */
4322 if(dir_hist
|| hist_len
> 0){
4323 opts
[i
].ch
= ctrl('Y');
4327 opts
[i
++].label
= "";
4329 opts
[i
].ch
= ctrl('V');
4332 opts
[i
++].label
= "";
4336 opts
[i
].ch
= KEY_UP
;
4340 opts
[i
++].label
= "";
4342 opts
[i
].ch
= KEY_DOWN
;
4345 opts
[i
++].label
= "";
4351 init_hist(history
, HISTSIZE
);
4352 init_hist(&dir_hist
, HISTSIZE
); /* reset history to the end */
4360 if(F_ON(F_USE_CURRENT_DIR
, ps
))
4362 else if(VAR_OPER_DIR
){
4363 strncpy(dir
, VAR_OPER_DIR
, sizeof(dir
));
4364 dir
[sizeof(dir
)-1] = '\0';
4366 #if defined(DOS) || defined(OS2)
4367 else if(VAR_FILE_DIR
){
4368 strncpy(dir
, VAR_FILE_DIR
, sizeof(dir
));
4369 dir
[sizeof(dir
)-1] = '\0';
4377 strncpy(orig_dir
, dir
, sizeof(orig_dir
));
4378 orig_dir
[sizeof(orig_dir
)-1] = '\0';
4380 postcolon
[0] = '\0';
4381 strncpy(precolon
, dir
, sizeof(precolon
));
4382 precolon
[sizeof(precolon
)-1] = '\0';
4384 strncpy(def
, deefault
, sizeof(def
)-1);
4385 def
[sizeof(def
)-1] = '\0';
4386 removing_leading_and_trailing_white_space(def
);
4391 avail
= MAX(20, ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80) - MIN_OPT_ENT_WIDTH
;
4393 /*---------- Prompt the user for the file name -------------*/
4396 char dirb
[50], fileb
[50];
4397 int l1
, l2
, l3
, l4
, l5
, needed
;
4398 char *p
, p1
[100], p2
[100], *p3
, p4
[100], p5
[100];
4400 snprintf(p1
, sizeof(p1
), "%sCopy ",
4401 (flags
& GE_IS_EXPORT
) ? "EXPORT: " :
4402 (flags
& GE_IS_IMPORT
) ? "IMPORT: " : "SAVE: ");
4403 p1
[sizeof(p1
)-1] = '\0';
4406 strncpy(p2
, prompt_msg
? prompt_msg
: "", sizeof(p2
)-1);
4407 p2
[sizeof(p2
)-1] = '\0';
4410 if(rflags
&& *rflags
& GER_ALLPARTS
)
4417 snprintf(p4
, sizeof(p4
), " %s file%s%s",
4418 (flags
& GE_IS_IMPORT
) ? "from" : "to",
4419 is_absolute_path(filename
) ? "" : " in ",
4420 is_absolute_path(filename
) ? "" :
4421 (!dir
[0] ? "current directory"
4422 : (dir
[0] == '~' && !dir
[1]) ? "home directory"
4423 : short_str(dir
,dirb
,sizeof(dirb
),30,FrontDots
)));
4424 p4
[sizeof(p4
)-1] = '\0';
4427 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4429 *def
? short_str(def
,fileb
,sizeof(fileb
),40,EndDots
) : "",
4431 p5
[sizeof(p5
)-1] = '\0';
4434 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0){
4435 snprintf(p4
, sizeof(p4
), " %s file%s%s",
4436 (flags
& GE_IS_IMPORT
) ? "from" : "to",
4437 is_absolute_path(filename
) ? "" : " in ",
4438 is_absolute_path(filename
) ? "" :
4439 (!dir
[0] ? "current dir"
4440 : (dir
[0] == '~' && !dir
[1]) ? "home dir"
4441 : short_str(dir
,dirb
,sizeof(dirb
),10,FrontDots
)));
4442 p4
[sizeof(p4
)-1] = '\0';
4446 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l5
> 0){
4447 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4449 *def
? short_str(def
,fileb
,sizeof(fileb
),
4450 MAX(15,l5
-5-needed
),EndDots
) : "",
4452 p5
[sizeof(p5
)-1] = '\0';
4456 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l2
> 0){
4459 * 14 is about the shortest we can make this, because there are
4460 * fixed length strings of length 14 coming in here.
4462 p
= short_str(prompt_msg
, p2
, sizeof(p2
), MAX(14,l2
-needed
), FrontDots
);
4464 strncpy(p2
, p
, sizeof(p2
)-1);
4465 p2
[sizeof(p2
)-1] = '\0';
4471 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0){
4472 strncpy(p1
, "Copy ", sizeof(p1
)-1);
4473 p1
[sizeof(p1
)-1] = '\0';
4477 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l5
> 0){
4478 snprintf(p5
, sizeof(p5
), "%s%s%s: ",
4480 *def
? short_str(def
,fileb
, sizeof(fileb
),
4481 MAX(10,l5
-5-needed
),EndDots
) : "",
4483 p5
[sizeof(p5
)-1] = '\0';
4487 if((needed
= l1
+l2
+l3
+l4
+l5
-avail
) > 0 && l3
> 0){
4488 if(needed
<= l3
- strlen(" (+ atts)"))
4490 else if(needed
<= l3
- strlen(" (atts)"))
4492 else if(needed
<= l3
- strlen(" (+)"))
4494 else if(needed
<= l3
- strlen("+"))
4502 snprintf(prompt_buf
, sizeof(prompt_buf
), "%s%s%s%s%s", p1
, p2
, p3
, p4
, p5
);
4503 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4506 if(items_in_hist(dir_hist
) > 0 || hist_len
> 0){ /* any directories */
4507 opts
[kp
].name
= "^Y";
4508 opts
[kp
].label
= "Prev Dir";
4509 opts
[kp
+1].name
= "^V";
4510 opts
[kp
+1].label
= "Next Dir";
4514 opts
[kp
].label
= "";
4515 opts
[kp
+1].name
= "";
4516 opts
[kp
+1].label
= "";
4521 if(items_in_hist(*history
) > 0){
4522 opts
[ku
].name
= HISTORY_UP_KEYNAME
;
4523 opts
[ku
].label
= HISTORY_KEYLABEL
;
4524 opts
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
4525 opts
[ku
+1].label
= HISTORY_KEYLABEL
;
4529 opts
[ku
].label
= "";
4530 opts
[ku
+1].name
= "";
4531 opts
[ku
+1].label
= "";
4535 oeflags
= OE_APPEND_CURRENT
|
4536 ((flags
& GE_SEQ_SENSITIVE
) ? OE_SEQ_SENSITIVE
: 0);
4537 r
= optionally_enter(filename
, qline
, 0, len
, prompt_buf
,
4538 opts
, NO_HELP
, &oeflags
);
4540 dprint((2, "\n - export_filename = \"%s\", r = %d -\n", filename
, r
));
4544 * Helps may not be right if you add another caller or change
4545 * things. Check it out.
4547 if(flags
& GE_IS_IMPORT
)
4548 helper(h_ge_import
, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE
);
4549 else if(flags
& GE_ALLPARTS
)
4550 helper(h_ge_allparts
, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE
);
4552 helper(h_ge_export
, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE
);
4554 ps
->mangled_screen
= 1;
4558 else if(r
== 10 || r
== 11){ /* Browser or File Completion */
4559 if(filename
[0]=='~'){
4560 if(filename
[1] == C_FILESEP
&& filename
[2]!='\0'){
4563 for(i
=0; filename
[i
+2] != '\0' && i
+2 < len
-1; i
++)
4564 filename
[i
] = filename
[i
+2];
4566 strncpy(dir
, precolon
, sizeof(dir
)-1);
4567 dir
[sizeof(dir
)-1] = '\0';
4569 else if(filename
[1]=='\0' ||
4570 (filename
[1] == C_FILESEP
&& filename
[2] == '\0')){
4574 strncpy(dir
, precolon
, sizeof(dir
)-1);
4575 dir
[sizeof(dir
)-1] = '\0';
4578 else if(!dir
[0] && !is_absolute_path(filename
) && was_abs_path
){
4582 strncpy(dir
, precolon
, sizeof(dir
)-1);
4583 dir
[sizeof(dir
)-1] = '\0';
4592 strncpy(tmp
, filename
, sizeof(tmp
)-1);
4593 tmp
[sizeof(tmp
)-1] = '\0';
4594 if(*tmp
&& is_absolute_path(tmp
))
4595 fnexpand(tmp
, sizeof(tmp
));
4596 if(strncmp(tmp
,postcolon
, strlen(postcolon
)))
4597 postcolon
[0] = '\0';
4599 if(*tmp
&& (fn
= last_cmpnt(tmp
))){
4601 strncpy(filename2
, fn
, sizeof(filename2
)-1);
4602 filename2
[sizeof(filename2
)-1] = '\0';
4603 if(is_absolute_path(tmp
)){
4604 strncpy(dir2
, tmp
, MIN(fn
- tmp
, sizeof(dir2
)-1));
4605 dir2
[MIN(fn
- tmp
, sizeof(dir2
)-1)] = '\0';
4607 if(tmp
[1]==':' && tmp
[2]=='\\' && dir2
[2]=='\0'){
4612 strncpy(postcolon
, dir2
, sizeof(postcolon
)-1);
4613 postcolon
[sizeof(postcolon
)-1] = '\0';
4619 * Just building the directory name in dir2,
4620 * full_filename is overloaded.
4622 snprintf(full_filename
, len
, "%.*s", (int) MIN(fn
-tmp
,len
-1), tmp
);
4623 full_filename
[len
-1] = '\0';
4624 strncpy(postcolon
, full_filename
, sizeof(postcolon
)-1);
4625 postcolon
[sizeof(postcolon
)-1] = '\0';
4626 build_path(dir2
, !dir
[0] ? p
= (char *)getcwd(NULL
,MAXPATH
)
4627 : (dir
[0] == '~' && !dir
[1])
4630 full_filename
, sizeof(dir2
));
4636 if(is_absolute_path(tmp
)){
4637 strncpy(dir2
, tmp
, sizeof(dir2
)-1);
4638 dir2
[sizeof(dir2
)-1] = '\0';
4640 if(dir2
[2]=='\0' && dir2
[1]==':'){
4643 strncpy(postcolon
,dir2
,sizeof(postcolon
)-1);
4644 postcolon
[sizeof(postcolon
)-1] = '\0';
4647 filename2
[0] = '\0';
4651 strncpy(filename2
, tmp
, sizeof(filename2
)-1);
4652 filename2
[sizeof(filename2
)-1] = '\0';
4654 if(getcwd(dir2
, sizeof(dir2
)) == NULL
)
4655 alpine_panic(_("getcwd() call failed at get_export_filename"));
4657 else if(dir
[0] == '~' && !dir
[1]){
4658 strncpy(dir2
, ps
->home_dir
, sizeof(dir2
)-1);
4659 dir2
[sizeof(dir2
)-1] = '\0';
4662 strncpy(dir2
, dir
, sizeof(dir2
)-1);
4663 dir2
[sizeof(dir2
)-1] = '\0';
4666 postcolon
[0] = '\0';
4670 build_path(full_filename
, dir2
, filename2
, len
);
4671 if(!strcmp(full_filename
, dir2
))
4672 filename2
[0] = '\0';
4673 if(full_filename
[strlen(full_filename
)-1] == C_FILESEP
4674 && isdir(full_filename
,NULL
,NULL
)){
4675 if(strlen(full_filename
) == 1)
4676 strncpy(postcolon
, full_filename
, sizeof(postcolon
)-1);
4677 else if(filename2
[0])
4678 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4679 postcolon
[sizeof(postcolon
)-1] = '\0';
4680 strncpy(dir2
, full_filename
, sizeof(dir2
)-1);
4681 dir2
[sizeof(dir2
)-1] = '\0';
4682 filename2
[0] = '\0';
4684 #ifdef _WINDOWS /* use full_filename even if not a valid directory */
4685 else if(full_filename
[strlen(full_filename
)-1] == C_FILESEP
){
4686 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4687 postcolon
[sizeof(postcolon
)-1] = '\0';
4688 strncpy(dir2
, full_filename
, sizeof(dir2
)-1);
4689 dir2
[sizeof(dir2
)-1] = '\0';
4690 filename2
[0] = '\0';
4693 if(dir2
[strlen(dir2
)-1] == C_FILESEP
&& strlen(dir2
)!=1
4694 && strcmp(dir2
+1, ":\\"))
4695 /* last condition to prevent stripping of '\\'
4696 in windows partition */
4697 dir2
[strlen(dir2
)-1] = '\0';
4699 if(r
== 10){ /* File Browser */
4700 r
= file_lister(lister_msg
? lister_msg
: "EXPORT",
4701 dir2
, sizeof(dir2
), filename2
, sizeof(filename2
),
4703 (flags
& GE_IS_IMPORT
) ? FB_READ
: FB_SAVE
);
4705 /* Windows has a special "feature" in which entering the file browser will
4706 change the working directory if the directory is changed at all (even
4707 clicking "Cancel" will change the working directory).
4709 if(F_ON(F_USE_CURRENT_DIR
, ps
))
4710 (void)getcwd(dir2
,sizeof(dir2
));
4712 if(isdir(dir2
,NULL
,NULL
)){
4713 strncpy(precolon
, dir2
, sizeof(precolon
)-1);
4714 precolon
[sizeof(precolon
)-1] = '\0';
4716 strncpy(postcolon
, filename2
, sizeof(postcolon
)-1);
4717 postcolon
[sizeof(postcolon
)-1] = '\0';
4719 build_path(full_filename
, dir2
, filename2
, len
);
4720 if(isdir(full_filename
, NULL
, NULL
)){
4721 strncpy(dir
, full_filename
, sizeof(dir
)-1);
4722 dir
[sizeof(dir
)-1] = '\0';
4726 fn
= last_cmpnt(full_filename
);
4727 strncpy(dir
, full_filename
,
4728 MIN(fn
- full_filename
, sizeof(dir
)-1));
4729 dir
[MIN(fn
- full_filename
, sizeof(dir
)-1)] = '\0';
4730 if(fn
- full_filename
> 1)
4731 dir
[fn
- full_filename
- 1] = '\0';
4734 if(!strcmp(dir
, ps
->home_dir
)){
4739 strncpy(filename
, fn
, len
-1);
4740 filename
[len
-1] = '\0';
4743 else{ /* File Completion */
4744 if(!pico_fncomplete(dir2
, filename2
, sizeof(filename2
)))
4746 strncat(postcolon
, filename2
,
4747 sizeof(postcolon
)-1-strlen(postcolon
));
4748 postcolon
[sizeof(postcolon
)-1] = '\0';
4750 was_abs_path
= is_absolute_path(filename
);
4752 if(!strcmp(dir
, ps
->home_dir
)){
4757 strncpy(filename
, postcolon
, len
-1);
4758 filename
[len
-1] = '\0';
4759 strncpy(dir
, precolon
, sizeof(dir
)-1);
4760 dir
[sizeof(dir
)-1] = '\0';
4762 if(filename
[0] == '~' && !filename
[1]){
4770 else if(r
== 12){ /* Download, caller handles it */
4774 else if(r
== 13){ /* toggle AllParts bit */
4776 if(*rflags
& GER_ALLPARTS
){
4777 *rflags
&= ~GER_ALLPARTS
;
4778 opts
[allparts
].label
= N_("AllParts");
4781 *rflags
|= GER_ALLPARTS
;
4782 /* opposite of All Parts, No All Parts */
4783 opts
[allparts
].label
= N_("NoAllParts");
4790 else if(r
== 14){ /* List file names matching partial? */
4794 else if(r
== 15){ /* toggle Binary bit */
4796 if(*rflags
& GER_BINARY
){
4797 *rflags
&= ~GER_BINARY
;
4798 opts
[binary
].label
= N_("Binary");
4801 *rflags
|= GER_BINARY
;
4802 opts
[binary
].label
= N_("No Binary");
4808 else if(r
== 1){ /* Cancel */
4815 else if(r
>= 30 && r
<= 33){
4818 if(r
== 30 || r
== 31){
4821 p
= get_prev_hist(*history
, filename
, 0, NULL
);
4823 p
= get_next_hist(*history
, filename
, 0, NULL
);
4827 if(r
== 32 || r
== 33){
4828 int nitems
= items_in_hist(dir_hist
);
4829 if(dir_hist
|| hist_len
> 0){
4832 p
= hist_in_pos(--pos
, ps_global
->VAR_HISTORY
, hist_len
, dir_hist
, nitems
);
4836 if(pos
< hist_len
+ nitems
)
4837 p
= hist_in_pos(++pos
, ps_global
->VAR_HISTORY
, hist_len
, dir_hist
, nitems
);
4839 if(p
== NULL
|| *p
== '\0')
4843 last
= p
; /* save it! */
4845 if(p
!= NULL
&& *p
!= '\0'){
4846 if(r
== 30 || r
== 31){
4847 if((fn
= last_cmpnt(p
)) != NULL
){
4848 strncpy(dir
, p
, MIN(fn
- p
, sizeof(dir
)-1));
4849 dir
[MIN(fn
- p
, sizeof(dir
)-1)] = '\0';
4851 dir
[fn
- p
- 1] = '\0';
4852 strncpy(filename
, fn
, len
-1);
4853 filename
[len
-1] = '\0';
4855 } else { /* r == 32 || r == 33 */
4856 strncpy(dir
, p
, sizeof(dir
)-1);
4857 dir
[sizeof(dir
)-1] = '\0';
4860 if(!strcmp(dir
, ps
->home_dir
)){
4871 rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf
,
4872 SIZEOF_20KBUF
, filename
);
4873 strncpy(filename
, tmp_20k_buf
, len
);
4874 filename
[len
-1] = '\0';
4877 #endif /* _WINDOWS */
4883 removing_leading_and_trailing_white_space(filename
);
4886 if(!*def
){ /* Cancel */
4891 strncpy(filename
, def
, len
-1);
4892 filename
[len
-1] = '\0';
4895 #if defined(DOS) || defined(OS2)
4896 if(is_absolute_path(filename
)){
4897 fixpath(filename
, len
);
4900 if(filename
[0] == '~'){
4901 if(fnexpand(filename
, len
) == NULL
){
4902 char *p
= strindex(filename
, '/');
4905 q_status_message1(SM_ORDER
| SM_DING
, 3, 3,
4906 _("Error expanding file name: \"%s\" unknown user"),
4913 if(is_absolute_path(filename
)){
4914 strncpy(full_filename
, filename
, len
-1);
4915 full_filename
[len
-1] = '\0';
4919 build_path(full_filename
, (char *)getcwd(dir
,sizeof(dir
)),
4921 else if(dir
[0] == '~' && !dir
[1])
4922 build_path(full_filename
, ps
->home_dir
, filename
, len
);
4924 build_path(full_filename
, dir
, filename
, len
);
4927 if((ill
= filter_filename(full_filename
, &fatal
,
4928 ps_global
->restricted
|| ps_global
->VAR_OPER_DIR
)) != NULL
){
4930 q_status_message1(SM_ORDER
| SM_DING
, 3, 3, "%s", ill
);
4934 /* BUG: we should beep when the key's pressed rather than bitch later */
4935 /* Warn and ask for confirmation. */
4936 snprintf(prompt_buf
, sizeof(prompt_buf
), "File name contains a '%s'. %s anyway",
4937 ill
, (flags
& GE_IS_EXPORT
) ? "Export" : "Save");
4938 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4939 if(want_to(prompt_buf
, 'n', 0, NO_HELP
,
4940 ((flags
& GE_SEQ_SENSITIVE
) ? RB_SEQ_SENSITIVE
: 0)) != 'y')
4945 break; /* Must have got an OK file name */
4948 if(VAR_OPER_DIR
&& !in_dir(VAR_OPER_DIR
, full_filename
)){
4953 if(!can_access(full_filename
, ACCESS_EXISTS
)){
4955 static ESCKEY_S access_opts
[] = {
4956 /* TRANSLATORS: asking user if they want to overwrite (replace contents of)
4957 a file or append to the end of the file */
4958 {'o', 'o', "O", N_("Overwrite")},
4959 {'a', 'a', "A", N_("Append")},
4960 {-1, 0, NULL
, NULL
}};
4962 rbflags
= RB_NORM
| ((flags
& GE_SEQ_SENSITIVE
) ? RB_SEQ_SENSITIVE
: 0);
4964 if(flags
& GE_NO_APPEND
){
4965 r
= strlen(filename
);
4966 snprintf(prompt_buf
, sizeof(prompt_buf
),
4967 /* TRANSLATORS: asking user whether to overwrite a file or not,
4968 File <filename> already exists. Overwrite it ? */
4969 _("File \"%s%s\" already exists. Overwrite it "),
4970 (r
> 20) ? "..." : "",
4971 filename
+ ((r
> 20) ? r
- 20 : 0));
4972 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4973 if(want_to(prompt_buf
, 'n', 'x', NO_HELP
, rbflags
) == 'y'){
4975 *rflags
|= GER_OVER
;
4977 if(our_unlink(full_filename
) < 0){
4978 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
4979 /* TRANSLATORS: Cannot remove old <filename>: <error text> */
4980 _("Cannot remove old %s: %s"),
4981 full_filename
, error_description(errno
));
4989 else if(!(flags
& GE_IS_IMPORT
)){
4990 r
= strlen(filename
);
4991 snprintf(prompt_buf
, sizeof(prompt_buf
),
4992 /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */
4993 _("File \"%s%s\" already exists. Overwrite or append to it ? "),
4994 (r
> 20) ? "..." : "",
4995 filename
+ ((r
> 20) ? r
- 20 : 0));
4996 prompt_buf
[sizeof(prompt_buf
)-1] = '\0';
4997 switch(radio_buttons(prompt_buf
, -FOOTER_ROWS(ps_global
),
4998 access_opts
, 'a', 'x', NO_HELP
, rbflags
)){
5001 *rflags
|= GER_OVER
;
5003 if(our_truncate(full_filename
, (off_t
)0) < 0)
5004 /* trouble truncating, but we'll give it a try anyway */
5005 q_status_message2(SM_ORDER
| SM_DING
, 3, 5,
5006 /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */
5007 _("Warning: Cannot truncate old %s: %s"),
5008 full_filename
, error_description(errno
));
5013 *rflags
|= GER_APPEND
;
5026 if(history
&& ret
== 0){
5027 save_hist(*history
, full_filename
, 0, NULL
);
5028 strncpy(tmp
, full_filename
, MAXPATH
);
5029 tmp
[MAXPATH
] = '\0';
5030 if((fn
= strrchr(tmp
, C_FILESEP
)) != NULL
)
5035 save_hist(dir_hist
, tmp
, 0, NULL
);
5038 if(opts
&& opts
!= optsarg
)
5039 fs_give((void **) &opts
);
5045 /*----------------------------------------------------------------------
5046 parse the config'd upload/download command
5048 Args: cmd -- buffer to return command fit for shellin'
5051 fname -- file name to build into the command
5053 Returns: pointer to cmd_str buffer or NULL on real bad error
5055 NOTE: One SIDE EFFECT is that any defined "prefix" string in the
5056 cfg_str is written to standard out right before a successful
5057 return of this function. The call immediately following this
5058 function darn well better be the shell exec...
5061 build_updown_cmd(char *cmd
, size_t cmdlen
, char *prefix
, char *cfg_str
, char *fname
)
5064 int fname_found
= 0;
5066 if(prefix
&& *prefix
){
5067 /* loop thru replacing all occurrences of _FILE_ */
5068 p
= strncpy(cmd
, prefix
, cmdlen
);
5069 cmd
[cmdlen
-1] = '\0';
5070 while((p
= strstr(p
, "_FILE_")))
5071 rplstr(p
, cmdlen
-(p
-cmd
), 6, fname
);
5076 /* loop thru replacing all occurrences of _FILE_ */
5077 p
= strncpy(cmd
, cfg_str
, cmdlen
);
5078 cmd
[cmdlen
-1] = '\0';
5079 while((p
= strstr(p
, "_FILE_"))){
5080 rplstr(p
, cmdlen
-(p
-cmd
), 6, fname
);
5085 snprintf(cmd
+strlen(cmd
), cmdlen
-strlen(cmd
), " %s", fname
);
5087 cmd
[cmdlen
-1] = '\0';
5089 dprint((4, "\n - build_updown_cmd = \"%s\" -\n",
5095 /*----------------------------------------------------------------------
5096 Write a berzerk format message delimiter using the given putc function
5098 Args: e -- envelope of message to write
5099 pc -- function to use
5101 Returns: TRUE if we could write it, FALSE if there was a problem
5103 NOTE: follows delimiter with OS-dependent newline
5106 bezerk_delimiter(ENVELOPE
*env
, MESSAGECACHE
*mc
, gf_io_t pc
, int leading_newline
)
5112 /* write "[\n]From mailbox[@host] " */
5113 if(!((leading_newline
? gf_puts(NEWLINE
, pc
) : 1)
5114 && gf_puts("From ", pc
)
5115 && gf_puts((env
&& env
->from
) ? env
->from
->mailbox
5116 : "the-concourse-on-high", pc
)
5117 && gf_puts((env
&& env
->from
&& env
->from
->host
) ? "@" : "", pc
)
5118 && gf_puts((env
&& env
->from
&& env
->from
->host
) ? env
->from
->host
5124 when
= mail_longdate(mc
);
5125 else if(env
&& env
->date
&& env
->date
[0]
5126 && mail_parse_date(&telt
,env
->date
))
5127 when
= mail_longdate(&telt
);
5133 while(p
&& *p
&& *p
!= '\n') /* write date */
5137 if(!gf_puts(NEWLINE
, pc
)) /* write terminating newline */
5144 /*----------------------------------------------------------------------
5145 Execute command to jump to a given message number
5147 Args: qline -- Line to ask question on
5149 Result: returns true if the use selected a new message, false otherwise
5153 jump_to(MSGNO_S
*msgmap
, int qline
, UCS first_num
, SCROLL_S
*sparms
, CmdWhere in_index
)
5155 char jump_num_string
[80], *j
, prompt
[70];
5158 static ESCKEY_S jump_to_key
[] = { {0, 0, NULL
, NULL
},
5159 /* TRANSLATORS: go to First Message */
5160 {ctrl('Y'), 10, "^Y", N_("First Msg")},
5161 {ctrl('V'), 11, "^V", N_("Last Msg")},
5162 {-1, 0, NULL
, NULL
} };
5164 dprint((4, "\n - jump_to -\n"));
5167 if(sparms
&& sparms
->jump_is_debug
)
5168 return(get_level(qline
, first_num
, sparms
));
5171 if(!any_messages(msgmap
, NULL
, "to Jump to"))
5174 if(first_num
&& first_num
< 0x80 && isdigit((unsigned char) first_num
)){
5175 jump_num_string
[0] = first_num
;
5176 jump_num_string
[1] = '\0';
5179 jump_num_string
[0] = '\0';
5181 if(mn_total_cur(msgmap
) > 1L){
5182 snprintf(prompt
, sizeof(prompt
), "Unselect %s msgs in favor of number to be entered",
5183 comatose(mn_total_cur(msgmap
)));
5184 prompt
[sizeof(prompt
)-1] = '\0';
5185 if((rc
= want_to(prompt
, 'n', 0, NO_HELP
, WT_NORM
)) == 'n')
5189 snprintf(prompt
, sizeof(prompt
), "%s number to jump to : ", in_index
== ThrdIndx
5192 prompt
[sizeof(prompt
)-1] = '\0';
5196 int flags
= OE_APPEND_CURRENT
;
5198 rc
= optionally_enter(jump_num_string
, qline
, 0,
5199 sizeof(jump_num_string
), prompt
,
5200 jump_to_key
, help
, &flags
);
5202 help
= help
== NO_HELP
5203 ? (in_index
== ThrdIndx
? h_oe_jump_thd
: h_oe_jump
)
5207 else if(rc
== 10 || rc
== 11){
5211 closest
= closest_jump_target(rc
== 10 ? 1L
5212 : ((in_index
== ThrdIndx
)
5213 ? msgmap
->max_thrdno
5214 : mn_get_total(msgmap
)),
5215 ps_global
->mail_stream
,
5217 in_index
, warning
, sizeof(warning
));
5218 /* ignore warning */
5223 * If we take out the *jump_num_string nonempty test in this if
5224 * then the closest_jump_target routine will offer a jump to the
5225 * last message. However, it is slow because you have to wait for
5226 * the status message and it is annoying for people who hit J command
5227 * by mistake and just want to hit return to do nothing, like has
5228 * always worked. So the test is there for now. Hubert 2002-08-19
5230 * Jumping to first/last message is now possible through ^Y/^V
5231 * commands above. jpf 2002-08-21
5232 * (and through "end" hubert 2006-07-07)
5234 if(rc
== 0 && *jump_num_string
!= '\0'){
5235 removing_leading_and_trailing_white_space(jump_num_string
);
5236 for(j
=jump_num_string
; isdigit((unsigned char)*j
) || *j
=='-'; j
++)
5240 if(!strucmp("end", j
))
5241 return((in_index
== ThrdIndx
) ? msgmap
->max_thrdno
: mn_get_total(msgmap
));
5243 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5244 _("Invalid number entered. Use only digits 0-9"));
5245 jump_num_string
[0] = '\0';
5249 long closest
, jump_num
;
5251 if(*jump_num_string
)
5252 jump_num
= atol(jump_num_string
);
5257 closest
= closest_jump_target(jump_num
, ps_global
->mail_stream
,
5259 *jump_num_string
? 0 : 1,
5260 in_index
, warning
, sizeof(warning
));
5262 q_status_message(SM_ORDER
| SM_DING
, 2, 2, warning
);
5264 if(closest
== jump_num
)
5268 jump_num_string
[0] = '\0';
5270 strncpy(jump_num_string
, long2string(closest
),
5271 sizeof(jump_num_string
));
5286 * cmd_delete_action - handle msgno advance and such after single message deletion
5289 cmd_delete_action(struct pine
*state
, MSGNO_S
*msgmap
, CmdWhere in_index
)
5295 msgno
= mn_get_cur(msgmap
);
5296 advance_cur_after_delete(state
, state
->mail_stream
, msgmap
, in_index
);
5298 if(IS_NEWS(state
->mail_stream
)
5299 || ((state
->context_current
->use
& CNTXT_INCMNG
)
5300 && context_isambig(state
->cur_folder
))){
5302 opts
= (NSF_TRUST_FLAGS
| NSF_SKIP_CHID
);
5303 if(in_index
== View
)
5304 opts
&= ~NSF_SKIP_CHID
;
5306 (void)next_sorted_flagged(F_UNDEL
|F_UNSEEN
, state
->mail_stream
, msgno
, &opts
);
5307 if(!(opts
& NSF_FLAG_MATCH
)){
5308 char nextfolder
[MAXPATH
];
5310 strncpy(nextfolder
, state
->cur_folder
, sizeof(nextfolder
));
5311 nextfolder
[sizeof(nextfolder
)-1] = '\0';
5312 rv
= next_folder(NULL
, nextfolder
, sizeof(nextfolder
), nextfolder
,
5313 state
->context_current
, NULL
, NULL
)
5314 ? ". Press TAB for next folder."
5315 : ". No more folders to TAB to.";
5324 * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing
5327 cmd_delete_index(struct pine
*state
, MSGNO_S
*msgmap
)
5329 return(cmd_delete_action(state
, msgmap
,MsgIndx
));
5333 * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing
5336 cmd_delete_view(struct pine
*state
, MSGNO_S
*msgmap
)
5338 return(cmd_delete_action(state
, msgmap
, View
));
5343 advance_cur_after_delete(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, CmdWhere in_index
)
5345 long new_msgno
, msgno
;
5348 new_msgno
= msgno
= mn_get_cur(msgmap
);
5349 opts
= NSF_TRUST_FLAGS
;
5351 if(F_ON(F_DEL_SKIPS_DEL
, state
)){
5353 if(THREADING() && sp_viewing_a_thread(stream
))
5354 opts
|= NSF_SKIP_CHID
;
5356 new_msgno
= next_sorted_flagged(F_UNDEL
, stream
, msgno
, &opts
);
5359 mn_inc_cur(stream
, msgmap
,
5360 (in_index
== View
&& THREADING()
5361 && sp_viewing_a_thread(stream
))
5363 : (in_index
== View
)
5364 ? MH_ANYTHD
: MH_NONE
);
5365 new_msgno
= mn_get_cur(msgmap
);
5366 if(new_msgno
!= msgno
)
5367 opts
|= NSF_FLAG_MATCH
;
5371 * Viewing_a_thread is the complicated case because we want to ignore
5372 * other threads at first and then look in other threads if we have to.
5373 * By ignoring other threads we also ignore collapsed partial threads
5374 * in our own thread.
5376 if(THREADING() && sp_viewing_a_thread(stream
) && !(opts
& NSF_FLAG_MATCH
)){
5377 long rawno
, orig_thrdno
;
5378 PINETHRD_S
*thrd
, *topthrd
= NULL
;
5380 rawno
= mn_m2raw(msgmap
, msgno
);
5381 thrd
= fetch_thread(stream
, rawno
);
5382 if(thrd
&& thrd
->top
)
5383 topthrd
= fetch_thread(stream
, thrd
->top
);
5385 orig_thrdno
= topthrd
? topthrd
->thrdno
: -1L;
5387 opts
= NSF_TRUST_FLAGS
;
5388 new_msgno
= next_sorted_flagged(F_UNDEL
, stream
, msgno
, &opts
);
5391 * If we got a match, new_msgno may be a message in
5392 * a different thread from the one we are viewing, or it could be
5393 * in a collapsed part of this thread.
5395 if(opts
& NSF_FLAG_MATCH
){
5400 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
,new_msgno
));
5401 if(thrd
&& thrd
->top
)
5402 topthrd
= fetch_thread(stream
, thrd
->top
);
5405 * If this match is in the same thread we're already in
5406 * then we're done, else we have to ask the user and maybe
5409 if(!(orig_thrdno
> 0L && topthrd
5410 && topthrd
->thrdno
== orig_thrdno
)){
5412 if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD
, state
)){
5413 if(in_index
== View
)
5414 snprintf(pmt
, sizeof(pmt
),
5415 "View message in thread number %.10s",
5416 topthrd
? comatose(topthrd
->thrdno
) : "?");
5418 snprintf(pmt
, sizeof(pmt
), "View thread number %.10s",
5419 topthrd
? comatose(topthrd
->thrdno
) : "?");
5421 ret
= want_to(pmt
, 'y', 'x', NO_HELP
, WT_NORM
);
5427 unview_thread(state
, stream
, msgmap
);
5428 mn_set_cur(msgmap
, new_msgno
);
5430 && (count_lflags_in_thread(stream
, topthrd
, msgmap
,
5432 && view_thread(state
, stream
, msgmap
, 1)){
5433 if(current_index_state
)
5434 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
5436 state
->view_skipped_index
= 1;
5437 state
->next_screen
= mail_view_screen
;
5440 view_thread(state
, stream
, msgmap
, 1);
5441 if(current_index_state
)
5442 msgmap
->top_after_thrd
= current_index_state
->msg_at_top
;
5444 state
->next_screen
= SCREEN_FUN_NULL
;
5448 new_msgno
= msgno
; /* stick with original */
5453 mn_set_cur(msgmap
, new_msgno
);
5454 if(in_index
!= View
)
5455 adjust_cur_to_visible(stream
, msgmap
);
5461 get_level(int qline
, UCS first_num
, SCROLL_S
*sparms
)
5463 char debug_num_string
[80], *j
, prompt
[70];
5468 if(first_num
&& first_num
< 0x80 && isdigit((unsigned char)first_num
)){
5469 debug_num_string
[0] = first_num
;
5470 debug_num_string
[1] = '\0';
5471 debug_num
= atol(debug_num_string
);
5472 *(int *)(sparms
->proc
.data
.p
) = debug_num
;
5473 q_status_message1(SM_ORDER
, 0, 3, "Show debug <= level %s",
5474 comatose(debug_num
));
5478 debug_num_string
[0] = '\0';
5480 snprintf(prompt
, sizeof(prompt
), "Show debug <= this level (0-%d) : ", MAX(debug
, 9));
5481 prompt
[sizeof(prompt
)-1] = '\0';
5485 int flags
= OE_APPEND_CURRENT
;
5487 rc
= optionally_enter(debug_num_string
, qline
, 0,
5488 sizeof(debug_num_string
), prompt
,
5489 NULL
, help
, &flags
);
5491 help
= help
== NO_HELP
? h_oe_debuglevel
: NO_HELP
;
5496 removing_leading_and_trailing_white_space(debug_num_string
);
5497 for(j
=debug_num_string
; isdigit((unsigned char)*j
); j
++)
5501 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5502 _("Invalid number entered. Use only digits 0-9"));
5503 debug_num_string
[0] = '\0';
5506 debug_num
= atol(debug_num_string
);
5508 q_status_message(SM_ORDER
| SM_DING
, 2, 2,
5509 _("Number should be >= 0"));
5510 else if(debug_num
> MAX(debug
,9))
5511 q_status_message1(SM_ORDER
| SM_DING
, 2, 2,
5512 _("Maximum is %s"), comatose(MAX(debug
,9)));
5514 *(int *)(sparms
->proc
.data
.p
) = debug_num
;
5515 q_status_message1(SM_ORDER
, 0, 3,
5516 "Show debug <= level %s",
5517 comatose(debug_num
));
5535 * Returns the message number closest to target that isn't hidden.
5536 * Make warning at least 100 chars.
5537 * A return of 0 means there is no message to jump to.
5540 closest_jump_target(long int target
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
, int no_target
, CmdWhere in_index
, char *warning
, size_t warninglen
)
5542 long i
, start
, closest
= 0L;
5547 maxnum
= (in_index
== ThrdIndx
) ? msgmap
->max_thrdno
: mn_get_total(msgmap
);
5552 snprintf(warning
, warninglen
, "No %s number entered, jump to end? ",
5553 (in_index
== ThrdIndx
) ? "thread" : "message");
5554 warning
[warninglen
-1] = '\0';
5556 else if(target
< 1L)
5557 start
= 1L - target
;
5558 else if(target
> maxnum
)
5559 start
= target
- maxnum
;
5563 if(target
> 0L && target
<= maxnum
)
5564 if(in_index
== ThrdIndx
5565 || !msgline_hidden(stream
, msgmap
, target
, 0))
5568 for(i
= start
; target
+i
<= maxnum
|| target
-i
> 0L; i
++){
5570 if(target
+i
> 0L && target
+i
<= maxnum
&&
5571 (in_index
== ThrdIndx
5572 || !msgline_hidden(stream
, msgmap
, target
+i
, 0))){
5577 if(target
-i
> 0L && target
-i
<= maxnum
&&
5578 (in_index
== ThrdIndx
5579 || !msgline_hidden(stream
, msgmap
, target
-i
, 0))){
5585 strncpy(buf
, long2string(closest
), sizeof(buf
));
5586 buf
[sizeof(buf
)-1] = '\0';
5589 strncpy(warning
, "Nothing to jump to", warninglen
);
5590 else if(target
< 1L)
5591 snprintf(warning
, warninglen
, "%s number (%s) must be at least %s",
5592 (in_index
== ThrdIndx
) ? "Thread" : "Message",
5593 long2string(target
), buf
);
5594 else if(target
> maxnum
)
5595 snprintf(warning
, warninglen
, "%s number (%s) may be no more than %s",
5596 (in_index
== ThrdIndx
) ? "Thread" : "Message",
5597 long2string(target
), buf
);
5599 snprintf(warning
, warninglen
,
5600 "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)",
5601 long2string(target
), buf
);
5603 warning
[warninglen
-1] = '\0';
5609 /*----------------------------------------------------------------------
5610 Prompt for folder name to open, expand the name and return it
5612 Args: qline -- Screen line to prompt on
5613 allow_list -- if 1, allow ^T to bring up collection lister
5615 Result: returns the folder name or NULL
5616 pine structure mangled_footer flag is set
5617 may call the collection lister in which case mangled screen will be set
5619 This prompts the user for the folder to open, possibly calling up
5620 the collection lister if the user types ^T.
5621 ----------------------------------------------------------------------*/
5623 broach_folder(int qline
, int allow_list
, int *notrealinbox
, CONTEXT_S
**context
)
5626 static char newfolder
[MAILTMPLEN
];
5627 char expanded
[MAXPATH
+1],
5628 prompt
[MAX_SCREEN_COLS
+1],
5630 unsigned char *f1
, *f2
, *f3
;
5631 static HISTORY_S
*history
= NULL
;
5632 CONTEXT_S
*tc
, *tc2
;
5634 int rc
, r
, ku
= -1, n
, flags
, last_rc
= 0, inbox
, done
= 0;
5637 * the idea is to provide a clue for the context the file name
5638 * will be saved in (if a non-imap names is typed), and to
5639 * only show the previous if it was also in the same context
5646 (*notrealinbox
) = 1;
5648 init_hist(&history
, HISTSIZE
);
5650 tc
= broach_get_folder(context
? *context
: NULL
, &inbox
, NULL
);
5652 /* set up extra command option keys */
5654 ekey
[rc
].ch
= (allow_list
) ? ctrl('T') : 0 ;
5655 ekey
[rc
].rval
= (allow_list
) ? 2 : 0;
5656 ekey
[rc
].name
= (allow_list
) ? "^T" : "";
5657 ekey
[rc
++].label
= (allow_list
) ? N_("ToFldrs") : "";
5659 if(ps_global
->context_list
->next
){
5660 ekey
[rc
].ch
= ctrl('P');
5662 ekey
[rc
].name
= "^P";
5663 ekey
[rc
++].label
= N_("Prev Collection");
5665 ekey
[rc
].ch
= ctrl('N');
5667 ekey
[rc
].name
= "^N";
5668 ekey
[rc
++].label
= N_("Next Collection");
5671 ekey
[rc
].ch
= ctrl('W');
5673 ekey
[rc
].name
= "^W";
5674 ekey
[rc
++].label
= N_("INBOX");
5676 if(F_ON(F_ENABLE_TAB_COMPLETE
,ps_global
)){
5679 ekey
[rc
].name
= "TAB";
5680 ekey
[rc
++].label
= N_("Complete");
5683 if(F_ON(F_ENABLE_SUB_LISTS
, ps_global
)){
5684 ekey
[rc
].ch
= ctrl('X');
5686 ekey
[rc
].name
= "^X";
5687 ekey
[rc
++].label
= N_("ListMatches");
5690 if(ps_global
->context_list
->next
&& F_ON(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
5691 ekey
[rc
].ch
= KEY_UP
;
5694 ekey
[rc
++].label
= "";
5696 ekey
[rc
].ch
= KEY_DOWN
;
5699 ekey
[rc
++].label
= "";
5701 else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY
, ps_global
)){
5702 ekey
[rc
].ch
= KEY_UP
;
5706 ekey
[rc
++].label
= "";
5708 ekey
[rc
].ch
= KEY_DOWN
;
5711 ekey
[rc
++].label
= "";
5718 * Figure out next default value for this context. The idea
5719 * is that in each context the last folder opened is cached.
5720 * It's up to pick it out and display it. This is fine
5721 * and dandy if we've currently got the inbox open, BUT
5722 * if not, make the inbox the default the first time thru.
5725 last_folder
= ps_global
->inbox_name
;
5726 inbox
= 1; /* pretend we're in inbox from here on out */
5729 last_folder
= (ps_global
->last_unambig_folder
[0])
5730 ? ps_global
->last_unambig_folder
5731 : ((tc
->last_folder
[0]) ? tc
->last_folder
: NULL
);
5733 if(last_folder
){ /* MAXPATH + 1 = sizeof(expanded) */
5734 unsigned char *fname
= folder_name_decoded((unsigned char *)last_folder
);
5735 snprintf(expanded
, sizeof(expanded
), " [%.*s]", MAXPATH
+1-5,
5736 fname
? (char *) fname
: last_folder
);
5737 if(fname
) fs_give((void **)&fname
);
5742 expanded
[sizeof(expanded
)-1] = '\0';
5744 /* only show collection number if more than one available */
5745 if(ps_global
->context_list
->next
) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5746 snprintf(prompt
, sizeof(prompt
), "GOTO %s in <%s> %.*s%s: ",
5747 NEWS_TEST(tc
) ? "news group" : "folder",
5748 tc
->nickname
, MAX_SCREEN_COLS
+1-50, expanded
,
5749 *expanded
? " " : "");
5750 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5751 snprintf(prompt
, sizeof(prompt
), "GOTO folder %.*s%s: ", MAX_SCREEN_COLS
+1-20, expanded
,
5752 *expanded
? " " : "");
5754 prompt
[sizeof(prompt
)-1] = '\0';
5756 if(utf8_width(prompt
) > MAXPROMPT
){
5757 if(ps_global
->context_list
->next
) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5758 snprintf(prompt
, sizeof(prompt
), "GOTO <%s> %.*s%s: ",
5759 tc
->nickname
, MAX_SCREEN_COLS
+1-50, expanded
,
5760 *expanded
? " " : "");
5761 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5762 snprintf(prompt
, sizeof(prompt
), "GOTO %.*s%s: ", MAX_SCREEN_COLS
+1-20, expanded
,
5763 *expanded
? " " : "");
5765 prompt
[sizeof(prompt
)-1] = '\0';
5767 if(utf8_width(prompt
) > MAXPROMPT
){
5768 if(ps_global
->context_list
->next
) /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5769 snprintf(prompt
, sizeof(prompt
), "<%s> %.*s%s: ",
5770 tc
->nickname
, MAX_SCREEN_COLS
+1-50, expanded
,
5771 *expanded
? " " : "");
5772 else /* MAX_SCREEN_COLS+1 == sizeof(prompt) */
5773 snprintf(prompt
, sizeof(prompt
), "%.*s%s: ", MAX_SCREEN_COLS
+1-20, expanded
,
5774 *expanded
? " " : "");
5776 prompt
[sizeof(prompt
)-1] = '\0';
5781 if(items_in_hist(history
) > 1){
5782 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
5783 ekey
[ku
].label
= HISTORY_KEYLABEL
;
5784 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
5785 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
5789 ekey
[ku
].label
= "";
5790 ekey
[ku
+1].name
= "";
5791 ekey
[ku
+1].label
= "";
5795 /* is there any other way to do this? The point is that we
5796 * are trying to hide mutf7 from the user, and use the utf8
5797 * equivalent. So we create a variable f to take place of
5798 * newfolder, including content and size. f2 is copy of f1
5799 * that has to freed. Sigh!
5801 f3
= (unsigned char *) cpystr(newfolder
);
5802 f1
= fs_get(sizeof(newfolder
));
5803 f2
= folder_name_decoded(f3
);
5804 if(f3
) fs_give((void **)&f3
);
5805 strncpy((char *)f1
, (char *)f2
, sizeof(newfolder
));
5806 f1
[sizeof(newfolder
)-1] = '\0';
5807 if(f2
) fs_give((void **)&f2
);
5809 flags
= OE_APPEND_CURRENT
;
5810 rc
= optionally_enter((char *) f1
, qline
, 0, sizeof(newfolder
),
5811 (char *) prompt
, ekey
, help
, &flags
);
5813 f2
= folder_name_encoded(f1
);
5814 strncpy(newfolder
, (char *)f2
, sizeof(newfolder
));
5815 if(f1
) fs_give((void **)&f1
);
5816 if(f2
) fs_give((void **)&f2
);
5818 ps_global
->mangled_footer
= 1;
5821 case -1 : /* o_e says error! */
5822 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
5823 _("Error reading folder name"));
5826 case 0 : /* o_e says normal entry */
5827 removing_trailing_white_space(newfolder
);
5828 removing_leading_white_space(newfolder
);
5831 char *name
, *fullname
= NULL
;
5832 int exists
, breakout
= 0;
5834 save_hist(history
, newfolder
, 0, tc
);
5836 if(!(name
= folder_is_nick(newfolder
, FOLDERS(tc
),
5840 if(update_folder_spec(expanded
, sizeof(expanded
), name
)){
5841 strncpy(name
= newfolder
, expanded
, sizeof(newfolder
));
5842 newfolder
[sizeof(newfolder
)-1] = '\0';
5845 exists
= folder_name_exists(tc
, name
, &fullname
);
5848 strncpy(name
= newfolder
, fullname
, sizeof(newfolder
));
5849 newfolder
[sizeof(newfolder
)-1] = '\0';
5850 fs_give((void **) &fullname
);
5855 * if we know the things a folder, open it.
5856 * else if we know its a directory, visit it.
5857 * else we're not sure (it either doesn't really
5858 * exist or its unLISTable) so try opening it anyway
5860 if(exists
& FEX_ISFILE
){
5864 else if((exists
& FEX_ISDIR
)){
5866 CONTEXT_S
*fake_context
;
5867 char tmp
[MAILTMPLEN
];
5870 strncpy(tmp
, name
, sizeof(tmp
));
5871 tmp
[sizeof(tmp
)-2-1] = '\0';
5872 if(tmp
[(l
= strlen(tmp
)) - 1] != tc
->dir
->delim
){
5873 if(l
< sizeof(tmp
)){
5874 tmp
[l
] = tc
->dir
->delim
;
5875 strncpy(&tmp
[l
+1], "[]", sizeof(tmp
)-(l
+1));
5879 strncat(tmp
, "[]", sizeof(tmp
)-strlen(tmp
)-1);
5881 tmp
[sizeof(tmp
)-1] = '\0';
5883 fake_context
= new_context(tmp
, 0);
5884 newfolder
[0] = '\0';
5885 done
= display_folder_list(&fake_context
, newfolder
,
5886 1, folders_for_goto
);
5887 free_context(&fake_context
);
5890 else if(!(tc
->use
& CNTXT_INCMNG
)){
5891 done
= display_folder_list(&tc
, newfolder
,
5892 1, folders_for_goto
);
5896 else if((exists
& FEX_ERROR
)){
5897 q_status_message1(SM_ORDER
, 0, 3,
5898 _("Problem accessing folder \"%s\""),
5907 if(exists
== FEX_ERROR
)
5908 q_status_message1(SM_ORDER
, 0, 3,
5909 _("Problem accessing folder \"%s\""),
5911 else if(tc
->use
& CNTXT_INCMNG
)
5912 q_status_message1(SM_ORDER
, 0, 3,
5913 _("Can't find Incoming Folder: %s"),
5915 else if(context_isambig(newfolder
))
5916 q_status_message2(SM_ORDER
, 0, 3,
5917 _("Can't find folder \"%s\" in %s"),
5918 newfolder
, (void *) tc
->nickname
);
5920 q_status_message1(SM_ORDER
, 0, 3,
5921 _("Can't find folder \"%s\""),
5926 else if(last_folder
){
5927 if(ps_global
->goto_default_rule
== GOTO_FIRST_CLCTN_DEF_INBOX
5928 && !strucmp(last_folder
, ps_global
->inbox_name
)
5929 && tc
== ((ps_global
->context_list
->use
& CNTXT_INCMNG
)
5930 ? ps_global
->context_list
->next
: ps_global
->context_list
)){
5932 (*notrealinbox
) = 0;
5934 tc
= ps_global
->context_list
;
5937 strncpy(newfolder
, last_folder
, sizeof(newfolder
));
5938 newfolder
[sizeof(newfolder
)-1] = '\0';
5939 save_hist(history
, newfolder
, 0, tc
);
5943 /* fall thru like they cancelled */
5945 case 1 : /* o_e says user cancel */
5946 cmd_cancelled("Open folder");
5949 case 2 : /* o_e says user wants list */
5950 r
= display_folder_list(&tc
, newfolder
, 0, folders_for_goto
);
5956 case 3 : /* o_e says user wants help */
5957 help
= help
== NO_HELP
? h_oe_broach
: NO_HELP
;
5960 case 4 : /* redraw */
5963 case 10 : /* Previous collection */
5964 tc2
= ps_global
->context_list
;
5965 while(tc2
->next
&& tc2
->next
!= tc
)
5971 case 11 : /* Next collection */
5972 tc
= (tc
->next
) ? tc
->next
: ps_global
->context_list
;
5975 case 12 : /* file name completion */
5976 if(!folder_complete(tc
, newfolder
, sizeof(newfolder
), &n
)){
5977 if(n
&& last_rc
== 12 && !(flags
& OE_USER_MODIFIED
)){
5978 r
= display_folder_list(&tc
, newfolder
, 1,folders_for_goto
);
5980 done
++; /* bingo! */
5982 rc
= 0; /* burn last_rc */
5990 case 14 : /* file name completion */
5991 r
= display_folder_list(&tc
, newfolder
, 2, folders_for_goto
);
5993 done
++; /* bingo! */
5995 rc
= 0; /* burn last_rc */
5999 case 17 : /* GoTo INBOX */
6001 strncpy(newfolder
, ps_global
->inbox_name
, sizeof(newfolder
)-1);
6002 newfolder
[sizeof(newfolder
)-1] = '\0';
6004 (*notrealinbox
) = 0;
6006 tc
= ps_global
->context_list
;
6007 save_hist(history
, newfolder
, 0, tc
);
6012 if((p
= get_prev_hist(history
, newfolder
, 0, tc
)) != NULL
){
6013 strncpy(newfolder
, p
, sizeof(newfolder
));
6014 newfolder
[sizeof(newfolder
)-1] = '\0';
6015 if(history
->hist
[history
->curindex
])
6016 tc
= history
->hist
[history
->curindex
]->cntxt
;
6024 if((p
= get_next_hist(history
, newfolder
, 0, tc
)) != NULL
){
6025 strncpy(newfolder
, p
, sizeof(newfolder
));
6026 newfolder
[sizeof(newfolder
)-1] = '\0';
6027 if(history
->hist
[history
->curindex
])
6028 tc
= history
->hist
[history
->curindex
]->cntxt
;
6036 alpine_panic("Unhandled case");
6043 dprint((2, "broach folder, name entered \"%s\"\n",
6044 newfolder
? newfolder
: "?"));
6046 /*-- Just check that we can expand this. It gets done for real later --*/
6047 strncpy(expanded
, newfolder
, sizeof(expanded
));
6048 expanded
[sizeof(expanded
)-1] = '\0';
6050 if(!expand_foldername(expanded
, sizeof(expanded
))) {
6051 dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n",
6052 expanded
? expanded
: "?"));
6061 /*----------------------------------------------------------------------
6062 Check to see if user wants to reopen dead stream.
6067 Result: 1 if the folder was successfully updatedn
6072 ask_mailbox_reopen(struct pine
*ps
, int *reopenp
)
6074 if(((ps
->mail_stream
->dtb
6075 && ((ps
->mail_stream
->dtb
->flags
& DR_NONEWMAIL
)
6076 || (ps
->mail_stream
->rdonly
6077 && ps
->mail_stream
->dtb
->flags
& DR_NONEWMAILRONLY
)))
6078 && (ps
->reopen_rule
== REOPEN_ASK_ASK_Y
6079 || ps
->reopen_rule
== REOPEN_ASK_ASK_N
6080 || ps
->reopen_rule
== REOPEN_ASK_NO_Y
6081 || ps
->reopen_rule
== REOPEN_ASK_NO_N
))
6082 || ((ps
->mail_stream
->dtb
6083 && ps
->mail_stream
->rdonly
6084 && !(ps
->mail_stream
->dtb
->flags
& DR_LOCAL
))
6085 && (ps
->reopen_rule
== REOPEN_YES_ASK_Y
6086 || ps
->reopen_rule
== REOPEN_YES_ASK_N
6087 || ps
->reopen_rule
== REOPEN_ASK_ASK_Y
6088 || ps
->reopen_rule
== REOPEN_ASK_ASK_N
))){
6091 switch(ps
->reopen_rule
){
6092 case REOPEN_YES_ASK_Y
:
6093 case REOPEN_ASK_ASK_Y
:
6094 case REOPEN_ASK_NO_Y
:
6103 switch(want_to("Re-open folder to check for new messages", deefault
,
6104 'x', h_reopen_folder
, WT_NORM
)){
6119 /*----------------------------------------------------------------------
6120 Check to see if user input is in form of old c-client mailbox speck
6125 Result: 1 if the folder was successfully updatedn
6130 update_folder_spec(char *new, size_t newlen
, char *old
)
6136 if(*(p
= old
) == '*') /* old form? */
6139 if(*old
== '{') /* copy host spec */
6141 switch(*new = *old
++){
6146 if(!struncmp(old
, "nntp", 4))
6154 while(*new++ != '}' && (new-orignew
) < newlen
-1);
6156 if((*p
== '*' && *old
) || ((*old
== '*') ? *++old
: 0)){
6158 * OK, some heuristics here. If it looks like a newsgroup
6159 * then we plunk it into the #news namespace else we
6160 * assume that they're trying to get at a #public folder...
6163 *p
&& (isalnum((unsigned char) *p
) || strindex(".-", *p
));
6167 sstrncpy(&new, (*p
&& !nntp
) ? "#public/" : "#news.", newlen
-(new-orignew
));
6168 strncpy(new, old
, newlen
-(new-orignew
));
6172 orignew
[newlen
-1] = '\0';
6178 /*----------------------------------------------------------------------
6179 Open the requested folder in the requested context
6181 Args: state -- usual pine state struct
6182 newfolder -- folder to open
6183 new_context -- folder context might live in
6184 stream -- candidate for recycling
6186 Result: New folder open or not (if error), and we're set to
6187 enter the index screen.
6190 visit_folder(struct pine
*state
, char *newfolder
, CONTEXT_S
*new_context
,
6191 MAILSTREAM
*stream
, long unsigned int flags
)
6193 dprint((9, "visit_folder(%s, %s)\n",
6194 newfolder
? newfolder
: "?",
6195 (new_context
&& new_context
->context
)
6196 ? new_context
->context
: "(NULL)"));
6198 if(ps_global
&& ps_global
->ttyo
){
6199 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
6200 ps_global
->mangled_footer
= 1;
6203 if(do_broach_folder(newfolder
, new_context
, stream
? &stream
: NULL
,
6205 || !sp_flagged(state
->mail_stream
, SP_LOCKED
))
6206 state
->next_screen
= mail_index_screen
;
6208 state
->next_screen
= folder_screen
;
6212 /*----------------------------------------------------------------------
6213 Move read messages from folder if listed in archive
6219 read_msg_prompt(long int n
, char *f
)
6221 char buf
[MAX_SCREEN_COLS
+1];
6223 snprintf(buf
, sizeof(buf
), "Save the %ld read message%s in \"%s\"", n
, plural(n
), f
);
6224 buf
[sizeof(buf
)-1] = '\0';
6225 return(want_to(buf
, 'y', 0, NO_HELP
, WT_NORM
) == 'y');
6229 /*----------------------------------------------------------------------
6230 Print current message[s] or folder index
6232 Args: state -- pointer to struct holding a bunch of pine state
6233 msgmap -- table mapping msg nums to c-client sequence nums
6234 aopt -- aggregate options
6235 in_index -- boolean indicating we're called from Index Screen
6237 Filters the original header and sends stuff to printer
6240 cmd_print(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
, CmdWhere in_index
)
6243 long i
, msgs
, rawno
;
6244 int next
= 0, do_index
= 0, rv
= 0;
6249 if(MCMD_ISAGG(aopt
) && !pseudo_selected(state
->mail_stream
, msgmap
))
6252 msgs
= mn_total_cur(msgmap
);
6254 if((in_index
!= View
) && F_ON(F_PRINT_INDEX
, state
)){
6257 static ESCKEY_S prt_opts
[] = {
6258 {'i', 'i', "I", N_("Index")},
6259 {'m', 'm', "M", NULL
},
6260 {-1, 0, NULL
, NULL
}};
6262 if(in_index
== ThrdIndx
){
6263 /* TRANSLATORS: This is a question, Print Index ? */
6264 if(want_to(_("Print Index"), 'y', 'x', NO_HELP
, WT_NORM
) == 'y')
6270 snprintf(m
, sizeof(m
), "Message%s", (msgs
>1L) ? "s" : "");
6271 m
[sizeof(m
)-1] = '\0';
6272 prt_opts
[1].label
= m
;
6273 snprintf(prompt
, sizeof(prompt
), "Print %sFolder Index or %s %s? ",
6274 (aopt
& MCMD_AGG_2
) ? "thread " : MCMD_ISAGG(aopt
) ? "selected " : "",
6275 (aopt
& MCMD_AGG_2
) ? "thread" : MCMD_ISAGG(aopt
) ? "selected" : "current", m
);
6276 prompt
[sizeof(prompt
)-1] = '\0';
6278 ans
= radio_buttons(prompt
, -FOOTER_ROWS(state
), prt_opts
, 'm', 'x',
6279 NO_HELP
, RB_NORM
|RB_SEQ_SENSITIVE
);
6284 cmd_cancelled("Print");
6285 if(MCMD_ISAGG(aopt
))
6286 restore_selected(msgmap
);
6301 snprintf(prompt
, sizeof(prompt
), "%sFolder Index",
6302 (aopt
& MCMD_AGG_2
) ? "Thread " : MCMD_ISAGG(aopt
) ? "Selected " : "");
6304 snprintf(prompt
, sizeof(prompt
), "%s messages", long2string(msgs
));
6306 snprintf(prompt
, sizeof(prompt
), "Message %s", long2string(mn_get_cur(msgmap
)));
6308 prompt
[sizeof(prompt
)-1] = '\0';
6310 if(open_printer(prompt
) < 0){
6311 if(MCMD_ISAGG(aopt
))
6312 restore_selected(msgmap
);
6320 tc
= format_titlebar();
6322 /* Print titlebar... */
6323 print_text1("%s\n\n", tc
? tc
->titlebar_line
: "");
6324 /* then all the index members... */
6325 if(!print_index(state
, msgmap
, MCMD_ISAGG(aopt
)))
6326 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6327 _("Error printing folder index"));
6333 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
), next
++){
6334 if(next
&& F_ON(F_AGG_PRINT_FF
, state
))
6335 if(!print_char(FORMFEED
)){
6340 if(!(state
->mail_stream
6341 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
6342 && rawno
<= state
->mail_stream
->nmsgs
6343 && (mc
= mail_elt(state
->mail_stream
, rawno
))
6347 if(!(e
=pine_mail_fetchstructure(state
->mail_stream
,
6350 || (F_ON(F_FROM_DELIM_IN_PRINT
, ps_global
)
6351 && !bezerk_delimiter(e
, mc
, print_char
, next
))
6352 || !format_message(mn_m2raw(msgmap
, mn_get_cur(msgmap
)),
6353 e
, b
, NULL
, FM_NEW_MESS
| FM_NOINDENT
,
6355 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6356 _("Error printing message"));
6365 if(MCMD_ISAGG(aopt
))
6366 restore_selected(msgmap
);
6372 /*----------------------------------------------------------------------
6375 Args: state -- various pine state bits
6376 msgmap -- Message number mapping table
6377 aopt -- option flags
6379 Filters the original header and sends stuff to specified command
6382 cmd_pipe(struct pine
*state
, MSGNO_S
*msgmap
, int aopt
)
6388 char *resultfilename
= NULL
, prompt
[80], *p
;
6389 int done
= 0, rv
= 0;
6391 int fourlabel
= -1, j
= 0, next
= 0, ku
;
6392 int pipe_rv
; /* rv of proc to separate from close_system_pipe rv */
6394 unsigned flagsforhist
= 1; /* raw=8/delimit=4/newpipe=2/capture=1 */
6395 static HISTORY_S
*history
= NULL
;
6396 int capture
= 1, raw
= 0, delimit
= 0, newpipe
= 0;
6397 char pipe_command
[MAXPATH
];
6398 ESCKEY_S pipe_opt
[8];
6400 if(ps_global
->restricted
){
6401 q_status_message(SM_ORDER
| SM_DING
, 0, 4,
6402 "Alpine demo can't pipe messages");
6405 else if(!any_messages(msgmap
, NULL
, "to Pipe"))
6408 pipe_command
[0] = '\0';
6409 init_hist(&history
, HISTSIZE
);
6410 flagsforhist
= (raw
? 0x8 : 0) +
6411 (delimit
? 0x4 : 0) +
6412 (newpipe
? 0x2 : 0) +
6413 (capture
? 0x1 : 0);
6414 if((p
= get_prev_hist(history
, "", flagsforhist
, NULL
)) != NULL
){
6415 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6416 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6417 if(history
->hist
[history
->curindex
]){
6418 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6419 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6420 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6421 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6422 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6427 pipe_opt
[j
].rval
= 0;
6428 pipe_opt
[j
].name
= "";
6429 pipe_opt
[j
++].label
= "";
6431 pipe_opt
[j
].ch
= ctrl('W');
6432 pipe_opt
[j
].rval
= 10;
6433 pipe_opt
[j
].name
= "^W";
6434 pipe_opt
[j
++].label
= NULL
;
6436 pipe_opt
[j
].ch
= ctrl('Y');
6437 pipe_opt
[j
].rval
= 11;
6438 pipe_opt
[j
].name
= "^Y";
6439 pipe_opt
[j
++].label
= NULL
;
6441 pipe_opt
[j
].ch
= ctrl('R');
6442 pipe_opt
[j
].rval
= 12;
6443 pipe_opt
[j
].name
= "^R";
6444 pipe_opt
[j
++].label
= NULL
;
6446 if(MCMD_ISAGG(aopt
)){
6447 if(!pseudo_selected(state
->mail_stream
, msgmap
))
6451 pipe_opt
[j
].ch
= ctrl('T');
6452 pipe_opt
[j
].rval
= 13;
6453 pipe_opt
[j
].name
= "^T";
6454 pipe_opt
[j
++].label
= NULL
;
6458 pipe_opt
[j
].ch
= KEY_UP
;
6459 pipe_opt
[j
].rval
= 30;
6460 pipe_opt
[j
].name
= "";
6462 pipe_opt
[j
++].label
= "";
6464 pipe_opt
[j
].ch
= KEY_DOWN
;
6465 pipe_opt
[j
].rval
= 31;
6466 pipe_opt
[j
].name
= "";
6467 pipe_opt
[j
++].label
= "";
6469 pipe_opt
[j
].ch
= -1;
6474 snprintf(prompt
, sizeof(prompt
), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ",
6476 MCMD_ISAGG(aopt
) ? "s" : " ",
6477 MCMD_ISAGG(aopt
) ? "" : comatose(mn_get_cur(msgmap
)),
6478 (!capture
|| delimit
|| (newpipe
&& MCMD_ISAGG(aopt
))) ? "(" : "",
6479 capture
? "" : "uncaptured",
6480 (!capture
&& delimit
) ? "," : "",
6481 delimit
? "delimited" : "",
6482 ((!capture
|| delimit
) && newpipe
&& MCMD_ISAGG(aopt
)) ? "," : "",
6483 (newpipe
&& MCMD_ISAGG(aopt
)) ? "new pipe" : "",
6484 (!capture
|| delimit
|| (newpipe
&& MCMD_ISAGG(aopt
))) ? ") " : "");
6485 prompt
[sizeof(prompt
)-1] = '\0';
6486 pipe_opt
[1].label
= raw
? N_("Shown Text") : N_("Raw Text");
6487 pipe_opt
[2].label
= capture
? N_("Free Output") : N_("Capture Output");
6488 pipe_opt
[3].label
= delimit
? N_("No Delimiter") : N_("With Delimiter");
6490 pipe_opt
[fourlabel
].label
= newpipe
? N_("To Same Pipe") : N_("To Individual Pipes");
6494 * 2 is really 1 because there will be one real entry and
6495 * one entry of "" because of the get_prev_hist above.
6497 if(items_in_hist(history
) > 2){
6498 pipe_opt
[ku
].name
= HISTORY_UP_KEYNAME
;
6499 pipe_opt
[ku
].label
= HISTORY_KEYLABEL
;
6500 pipe_opt
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
6501 pipe_opt
[ku
+1].label
= HISTORY_KEYLABEL
;
6504 pipe_opt
[ku
].name
= "";
6505 pipe_opt
[ku
].label
= "";
6506 pipe_opt
[ku
+1].name
= "";
6507 pipe_opt
[ku
+1].label
= "";
6510 flags
= OE_APPEND_CURRENT
| OE_SEQ_SENSITIVE
;
6511 switch(optionally_enter(pipe_command
, -FOOTER_ROWS(state
), 0,
6512 sizeof(pipe_command
), prompt
,
6513 pipe_opt
, NO_HELP
, &flags
)){
6515 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
6516 _("Internal problem encountered"));
6520 case 10 : /* flip raw bit */
6524 case 11 : /* flip capture bit */
6528 case 12 : /* flip delimit bit */
6532 case 13 : /* flip newpipe bit */
6537 flagsforhist
= (raw
? 0x8 : 0) +
6538 (delimit
? 0x4 : 0) +
6539 (newpipe
? 0x2 : 0) +
6540 (capture
? 0x1 : 0);
6541 if((p
= get_prev_hist(history
, pipe_command
, flagsforhist
, NULL
)) != NULL
){
6542 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6543 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6544 if(history
->hist
[history
->curindex
]){
6545 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6546 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6547 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6548 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6549 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6558 flagsforhist
= (raw
? 0x8 : 0) +
6559 (delimit
? 0x4 : 0) +
6560 (newpipe
? 0x2 : 0) +
6561 (capture
? 0x1 : 0);
6562 if((p
= get_next_hist(history
, pipe_command
, flagsforhist
, NULL
)) != NULL
){
6563 strncpy(pipe_command
, p
, sizeof(pipe_command
));
6564 pipe_command
[sizeof(pipe_command
)-1] = '\0';
6565 if(history
->hist
[history
->curindex
]){
6566 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
6567 raw
= (flagsforhist
& 0x8) ? 1 : 0;
6568 delimit
= (flagsforhist
& 0x4) ? 1 : 0;
6569 newpipe
= (flagsforhist
& 0x2) ? 1 : 0;
6570 capture
= (flagsforhist
& 0x1) ? 1 : 0;
6579 if(pipe_command
[0]){
6581 flagsforhist
= (raw
? 0x8 : 0) +
6582 (delimit
? 0x4 : 0) +
6583 (newpipe
? 0x2 : 0) +
6584 (capture
? 0x1 : 0);
6585 save_hist(history
, pipe_command
, flagsforhist
, NULL
);
6587 flags
= PIPE_USER
| PIPE_WRITE
| PIPE_STDERR
;
6588 flags
|= (raw
? PIPE_RAW
: 0);
6594 ps_global
->mangled_screen
= 1;
6595 ps_global
->in_init_seq
= 1;
6597 flags
|= PIPE_RESET
;
6600 if(!newpipe
&& !(syspipe
= cmd_pipe_open(pipe_command
,
6601 (flags
& PIPE_RESET
)
6607 for(i
= mn_first_cur(msgmap
);
6609 i
= mn_next_cur(msgmap
)){
6610 e
= pine_mail_fetchstructure(ps_global
->mail_stream
,
6611 mn_m2raw(msgmap
, i
), &b
);
6612 if(!(state
->mail_stream
6613 && (rawno
= mn_m2raw(msgmap
, i
)) > 0L
6614 && rawno
<= state
->mail_stream
->nmsgs
6615 && (mc
= mail_elt(state
->mail_stream
, rawno
))
6620 && !(syspipe
= cmd_pipe_open(pipe_command
,
6621 (flags
& PIPE_RESET
)
6625 || (delimit
&& !bezerk_delimiter(e
, mc
, pc
, next
++)))
6632 prime_raw_pipe_getc(ps_global
->mail_stream
,
6633 mn_m2raw(msgmap
, i
), -1L, 0L);
6635 gf_link_filter(gf_nvtnl_local
, NULL
);
6636 if((pipe_err
= gf_pipe(raw_pipe_getc
, pc
)) != NULL
){
6637 q_status_message1(SM_ORDER
|SM_DING
,
6639 _("Internal Error: %s"),
6644 else if(!format_message(mn_m2raw(msgmap
, i
), e
, b
,
6645 NULL
, FM_NEW_MESS
| FM_NOWRAP
, pc
))
6650 if(close_system_pipe(&syspipe
, &pipe_rv
, pipe_callback
) == -1)
6655 ps_global
->in_init_seq
= 0;
6658 if(close_system_pipe(&syspipe
, &pipe_rv
, pipe_callback
) == -1)
6660 if(done
) /* say we had a problem */
6661 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
6662 _("Error piping message"));
6663 else if(resultfilename
){
6665 /* only display if no error */
6666 display_output_file(resultfilename
, "PIPE MESSAGE",
6668 fs_give((void **)&resultfilename
);
6672 q_status_message(SM_ORDER
, 0, 2, _("Pipe command completed"));
6678 /* else fall thru as if cancelled */
6681 cmd_cancelled("Pipe command");
6686 helper(h_common_pipe
, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE
);
6687 ps_global
->mangled_screen
= 1;
6690 case 2 : /* no place to escape to */
6691 case 4 : /* can't suspend */
6697 ps_global
->mangled_footer
= 1;
6698 if(MCMD_ISAGG(aopt
))
6699 restore_selected(msgmap
);
6705 /*----------------------------------------------------------------------
6706 Screen to offer list management commands contained in message
6708 Args: state -- pointer to struct holding a bunch of pine state
6709 msgmap -- table mapping msg nums to c-client sequence nums
6710 aopt -- aggregate options
6714 NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net>
6717 rfc2369_display(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
)
6720 char *h
, *hdrs
[MLCMD_COUNT
+ 1];
6721 long index_no
= mn_raw2m(msgmap
, msgno
);
6722 RFC2369_S data
[MLCMD_COUNT
];
6724 /* for each header field */
6725 if((h
= pine_fetchheader_lines(stream
, msgno
, NULL
, rfc2369_hdrs(hdrs
))) != NULL
){
6726 memset(&data
[0], 0, sizeof(RFC2369_S
) * MLCMD_COUNT
);
6727 if(rfc2369_parse_fields(h
, &data
[0])){
6730 if((explain
= list_mgmt_text(data
, index_no
)) != NULL
){
6731 list_mgmt_screen(explain
);
6732 ps_global
->mangled_screen
= 1;
6738 fs_give((void **) &h
);
6742 q_status_message1(SM_ORDER
, 0, 3,
6743 "Message %s contains no list management information",
6744 comatose(index_no
));
6749 list_mgmt_text(RFC2369_S
*data
, long int msgno
)
6752 int i
, j
, n
, fields
= 0;
6753 static char *rfc2369_intro1
=
6754 "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message ";
6755 static char *rfc2369_intro2
[] = {
6756 N_(" has information associated with it "),
6757 N_("that explains how to participate in an email list. An "),
6758 N_("email list is represented by a single email address that "),
6759 N_("users sharing a common interest can send messages to (known "),
6760 N_("as posting) which are then redistributed to all members "),
6761 N_("of the list (sometimes after review by a moderator)."),
6762 N_("<P>List participation commands in this message include:"),
6766 if((store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) != NULL
){
6768 /* Insert introductory text */
6769 so_puts(store
, rfc2369_intro1
);
6771 so_puts(store
, comatose(msgno
));
6773 for(i
= 0; rfc2369_intro2
[i
]; i
++)
6774 so_puts(store
, _(rfc2369_intro2
[i
]));
6776 so_puts(store
, "<P>");
6777 for(i
= 0; i
< MLCMD_COUNT
; i
++)
6778 if(data
[i
].data
[0].value
6779 || data
[i
].data
[0].comment
6780 || data
[i
].data
[0].error
){
6782 so_puts(store
, "<UL>");
6784 so_puts(store
, "<LI>");
6786 (n
= (data
[i
].data
[1].value
|| data
[i
].data
[1].comment
))
6790 so_puts(store
, data
[i
].field
.description
);
6791 so_puts(store
, ". ");
6794 so_puts(store
, "<OL>");
6798 && (data
[i
].data
[j
].comment
6799 || data
[i
].data
[j
].value
6800 || data
[i
].data
[j
].error
);
6803 so_puts(store
, n
? "<P><LI>" : "<P>");
6805 if(data
[i
].data
[j
].comment
){
6807 _("With the provided comment:<P><BLOCKQUOTE>"));
6808 so_puts(store
, data
[i
].data
[j
].comment
);
6809 so_puts(store
, "</BLOCKQUOTE><P>");
6812 if(data
[i
].data
[j
].value
){
6814 && !strucmp(data
[i
].data
[j
].value
, "NO")){
6816 _("Posting is <EM>not</EM> allowed on this list"));
6819 so_puts(store
, "Select <A HREF=\"");
6820 so_puts(store
, data
[i
].data
[j
].value
);
6821 so_puts(store
, "\">HERE</A> ");
6822 if(!struncmp(data
[i
].data
[j
].value
, "http", 4)) {
6823 so_puts(store
, "(web link) ");
6825 else if(!struncmp(data
[i
].data
[j
].value
, "mailto", 5)) {
6826 so_puts(store
, " (email link) ");
6827 } so_puts(store
, "to ");
6828 so_puts(store
, (data
[i
].field
.action
)
6829 ? data
[i
].field
.action
6833 so_puts(store
, ".");
6836 if(data
[i
].data
[j
].error
){
6837 so_puts(store
, "<P>Unfortunately, Alpine can not offer");
6838 so_puts(store
, " to take direct action based upon it");
6839 so_puts(store
, " because it was improperly formatted.");
6840 so_puts(store
, " The unrecognized data associated with");
6841 so_puts(store
, " the \"");
6842 so_puts(store
, data
[i
].field
.name
);
6843 so_puts(store
, "\" header field was:<P><BLOCKQUOTE>");
6844 so_puts(store
, data
[i
].data
[j
].error
);
6845 so_puts(store
, "</BLOCKQUOTE>");
6848 so_puts(store
, "<P>");
6852 so_puts(store
, "</OL>");
6856 so_puts(store
, "</UL>");
6858 so_puts(store
, "</BODY></HTML>");
6866 list_mgmt_screen(STORE_S
*html
)
6872 HANDLE_S
*handles
= NULL
;
6876 so_seek(html
, 0L, 0);
6877 gf_set_so_readc(&gc
, html
);
6879 init_handles(&handles
);
6881 if((store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) != NULL
){
6882 gf_set_so_writec(&pc
, store
);
6885 gf_link_filter(gf_html2plain
,
6886 gf_html2plain_opt(NULL
, ps_global
->ttyo
->screen_cols
,
6887 non_messageview_margin(), &handles
, NULL
, 0));
6889 error
= gf_pipe(gc
, pc
);
6891 gf_clear_so_writec(store
);
6896 memset(&sargs
, 0, sizeof(SCROLL_S
));
6897 sargs
.text
.text
= so_text(store
);
6898 sargs
.text
.src
= CharStar
;
6899 sargs
.text
.desc
= "list commands";
6900 sargs
.text
.handles
= handles
;
6902 sargs
.start
.on
= Offset
;
6903 sargs
.start
.loc
.offset
= offset
;
6906 sargs
.bar
.title
= _("MAIL LIST COMMANDS");
6907 sargs
.bar
.style
= MessageNumber
;
6908 sargs
.resize_exit
= 1;
6909 sargs
.help
.text
= h_special_list_commands
;
6910 sargs
.help
.title
= _("HELP FOR LIST COMMANDS");
6911 sargs
.keys
.menu
= &listmgr_keymenu
;
6912 setbitmap(sargs
.keys
.bitmap
);
6914 clrbitn(LM_TRY_KEY
, sargs
.keys
.bitmap
);
6915 clrbitn(LM_PREV_KEY
, sargs
.keys
.bitmap
);
6916 clrbitn(LM_NEXT_KEY
, sargs
.keys
.bitmap
);
6919 cmd
= scrolltool(&sargs
);
6920 offset
= sargs
.start
.loc
.offset
;
6926 free_handles(&handles
);
6927 gf_clear_so_readc(html
);
6929 while(cmd
== MC_RESIZE
);
6933 /*----------------------------------------------------------------------
6934 Prompt the user for the type of select desired
6936 NOTE: any and all functions that successfully exit the second
6937 switch() statement below (currently "select_*() functions"),
6938 *MUST* update the folder's MESSAGECACHE element's "searched"
6939 bits to reflect the search result. Functions using
6940 mail_search() get this for free, the others must update 'em
6943 Returns -1 if canceled without changing selection
6944 0 if selection may have changed
6947 aggregate_select(struct pine
*state
, MSGNO_S
*msgmap
, int q_line
, CmdWhere in_index
)
6949 long i
, diff
, old_tot
, msgno
, raw
;
6950 int p
= 0, q
= 0, rv
= 0, narrow
= 0, hidden
, ret
= -1;
6953 SEARCHSET
*limitsrch
= NULL
;
6955 extern MAILSTREAM
*mm_search_stream
;
6956 extern long mm_search_count
;
6958 hidden
= any_lflagged(msgmap
, MN_HIDE
) > 0L;
6959 mm_search_stream
= state
->mail_stream
;
6960 mm_search_count
= 0L;
6962 if(is_imap_stream(state
->mail_stream
) && XGMEXT1(state
->mail_stream
))
6963 sel_opts
= THRD_INDX() ? sel_opts6
: sel_opts5
;
6965 sel_opts
= THRD_INDX() ? sel_opts4
: sel_opts2
;
6968 sel_opts
[SEL_OPTS_THREAD
].ch
= SEL_OPTS_THREAD_CH
;
6971 if(is_imap_stream(state
->mail_stream
) && XGMEXT1(state
->mail_stream
)){
6972 sel_opts
[SEL_OPTS_THREAD
].ch
= SEL_OPTS_XGMEXT_CH
;
6973 sel_opts
[SEL_OPTS_THREAD
].rval
= sel_opts
[SEL_OPTS_XGMEXT
].rval
;
6974 sel_opts
[SEL_OPTS_THREAD
].name
= sel_opts
[SEL_OPTS_XGMEXT
].name
;
6975 sel_opts
[SEL_OPTS_THREAD
].label
= sel_opts
[SEL_OPTS_XGMEXT
].label
;
6976 sel_opts
[SEL_OPTS_XGMEXT
].ch
= -1;
6979 sel_opts
[SEL_OPTS_THREAD
].ch
= -1;
6982 if((old_tot
= any_lflagged(msgmap
, MN_SLCT
)) != 0){
6985 thrd
= fetch_thread(state
->mail_stream
,
6986 mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
6987 /* check if whole thread is selected or not */
6989 count_lflags_in_thread(state
->mail_stream
,thrd
,msgmap
,MN_SLCT
)
6991 count_lflags_in_thread(state
->mail_stream
,thrd
,msgmap
,MN_NONE
))
6994 sel_opts1
[1].label
= i
? N_("unselect Curthrd") : N_("select Curthrd");
6997 i
= get_lflag(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
6999 sel_opts1
[1].label
= i
? N_("unselect Cur") : N_("select Cur");
7002 sel_opts
+= 2; /* disable extra options */
7003 if(is_imap_stream(state
->mail_stream
) && XGMEXT1(state
->mail_stream
))
7004 q
= double_radio_buttons(_(sel_pmt1
), q_line
, sel_opts1
, 'c', 'x', NO_HELP
,
7007 q
= radio_buttons(_(sel_pmt1
), q_line
, sel_opts1
, 'c', 'x', NO_HELP
,
7010 case 'f' : /* flip selection */
7012 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++){
7014 q
= !get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
);
7015 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, q
);
7017 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, !q
);
7019 mn_reset_cur(msgmap
, msgno
= i
);
7025 case 'n' : /* narrow selection */
7029 case 'r' : /* replace selection */
7030 p
= 1; /* set flag we want to replace */
7031 sel_opts
-= 2; /* re-enable first two options */
7032 case 'b' : /* broaden selection */
7033 q
= 0; /* offer criteria prompt */
7036 case 'c' : /* Un/Select Current */
7037 case 'a' : /* Unselect All */
7038 case 'x' : /* cancel */
7042 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
7043 "Unsupported Select option");
7050 if(is_imap_stream(state
->mail_stream
) && XGMEXT1(state
->mail_stream
))
7051 q
= double_radio_buttons(sel_pmt2
, q_line
, sel_opts
, 'c', 'x', NO_HELP
,
7054 q
= radio_buttons(sel_pmt2
, q_line
, sel_opts
, 'c', 'x', NO_HELP
,
7055 RB_NORM
|RB_RET_HELP
);
7058 helper(h_index_cmd_select
, _("HELP FOR SELECT"), HLPD_SIMPLE
);
7059 ps_global
->mangled_screen
= 1;
7066 /* When we are replacing the search, unselect all messages first, unless
7067 * we are cancelling, in whose case, leave the screen as is, because we
7070 if(p
== 1 && q
!= 'x'){
7071 msgno
= any_lflagged(msgmap
, MN_SLCT
);
7072 diff
= (!msgno
) ? mn_get_total(msgmap
) : 0L;
7073 agg_select_all(state
->mail_stream
, msgmap
, &diff
,
7074 any_lflagged(msgmap
, MN_SLCT
) <= 0L);
7078 * The purpose of this is to add the appropriate searchset to the
7079 * search so that the search can be limited to only looking at what
7080 * it needs to look at. That is, if we are narrowing then we only need
7081 * to look at messages which are already selected, and if we are
7082 * broadening, then we only need to look at messages which are not
7083 * yet selected. This routine will work whether or not
7084 * limiting_searchset properly limits the search set. In particular,
7085 * the searchset returned by limiting_searchset may include messages
7086 * which really shouldn't be included. We do that because a too-large
7087 * searchset will break some IMAP servers. It is even possible that it
7088 * becomes inefficient to send the whole set. If the select function
7089 * frees limitsrch, it should be sure to set it to NULL so we won't
7090 * try freeing it again here.
7092 limitsrch
= limiting_searchset(state
->mail_stream
, narrow
);
7095 * NOTE: See note about MESSAGECACHE "searched" bits above!
7098 case 'x': /* cancel */
7099 cmd_cancelled("Select command");
7102 case 'c' : /* select/unselect current */
7103 (void) select_by_current(state
, msgmap
, in_index
);
7107 case 'a' : /* select/unselect all */
7108 msgno
= any_lflagged(msgmap
, MN_SLCT
);
7109 diff
= (!msgno
) ? mn_get_total(msgmap
) : 0L;
7111 agg_select_all(state
->mail_stream
, msgmap
, &diff
,
7112 any_lflagged(msgmap
, MN_SLCT
) <= 0L);
7113 q_status_message4(SM_ORDER
,0,2,
7114 "%s%s message%s %sselected",
7115 msgno
? "" : "All ", comatose(diff
),
7116 plural(diff
), msgno
? "UN" : "");
7119 case 'n' : /* Select by Number */
7122 rv
= select_by_thrd_number(state
->mail_stream
, msgmap
, &limitsrch
);
7124 rv
= select_by_number(state
->mail_stream
, msgmap
, &limitsrch
);
7128 case 'd' : /* Select by Date */
7130 rv
= select_by_date(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
7134 case 't' : /* Text */
7136 rv
= select_by_text(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
),
7140 case 'z' : /* Size */
7142 rv
= select_by_size(state
->mail_stream
, &limitsrch
);
7145 case 's' : /* Status */
7147 rv
= select_by_status(state
->mail_stream
, &limitsrch
);
7150 case 'k' : /* Keyword */
7152 rv
= select_by_keyword(state
->mail_stream
, &limitsrch
);
7155 case 'r' : /* Rule */
7157 rv
= select_by_rule(state
->mail_stream
, &limitsrch
);
7160 case 'h' : /* Thread */
7162 rv
= select_by_thread(state
->mail_stream
, msgmap
, &limitsrch
);
7165 case 'g' : /* X-GM-EXT-1 */
7167 rv
= select_by_gm_content(state
->mail_stream
, msgmap
, mn_get_cur(msgmap
), &limitsrch
);
7171 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
7172 "Unsupported Select option");
7177 mail_free_searchset(&limitsrch
);
7179 if(rv
) /* bad return value.. */
7180 return(ret
); /* error already displayed */
7182 if(narrow
) /* make sure something was selected */
7183 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++)
7184 if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
7185 && raw
<= state
->mail_stream
->nmsgs
7186 && (mc
= mail_elt(state
->mail_stream
, raw
)) && mc
->searched
){
7187 if(get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
))
7194 if(mm_search_count
){
7196 * loop thru all the messages, adjusting local flag bits
7197 * based on their "searched" bit...
7199 for(i
= 1L, msgno
= 0L; i
<= mn_get_total(msgmap
); i
++)
7201 /* turning OFF selectedness if the "searched" bit isn't lit. */
7202 if(get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
)){
7203 if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
7204 && raw
<= state
->mail_stream
->nmsgs
7205 && (mc
= mail_elt(state
->mail_stream
, raw
))
7208 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, 0);
7210 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, 1);
7212 /* adjust current message in case we unselect and hide it */
7213 else if(msgno
< mn_get_cur(msgmap
)
7215 || !get_lflag(state
->mail_stream
, msgmap
,
7220 else if((raw
= mn_m2raw(msgmap
, i
)) > 0L && state
->mail_stream
7221 && raw
<= state
->mail_stream
->nmsgs
7222 && (mc
= mail_elt(state
->mail_stream
, raw
)) && mc
->searched
){
7223 /* turn ON selectedness if "searched" bit is lit. */
7224 if(!get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
)){
7226 set_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
, 1);
7228 set_lflag(state
->mail_stream
, msgmap
, i
, MN_HIDE
, 0);
7232 /* if we're zoomed and the current message was unselected */
7234 && get_lflag(state
->mail_stream
,msgmap
,mn_get_cur(msgmap
),MN_HIDE
))
7235 mn_reset_cur(msgmap
, msgno
);
7240 q_status_message4(SM_ORDER
, 3, 3,
7241 "%s. %s message%s remain%s selected.",
7243 ? "No change resulted"
7244 : "No messages in intersection",
7245 comatose(old_tot
), plural(old_tot
),
7246 (old_tot
== 1L) ? "s" : "");
7248 q_status_message(SM_ORDER
, 3, 3,
7249 _("No change resulted. Matching messages already selected."));
7251 q_status_message1(SM_ORDER
| SM_DING
, 3, 3,
7252 _("Select failed. No %smessages selected."),
7253 old_tot
? _("additional ") : "");
7256 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
7257 "Select matched %ld message%s. %s %smessage%s %sselected.",
7258 (diff
> 0) ? diff
: old_tot
+ diff
,
7259 plural((diff
> 0) ? diff
: old_tot
+ diff
),
7260 comatose((diff
> 0) ? any_lflagged(msgmap
, MN_SLCT
) : -diff
),
7261 (diff
> 0) ? "total " : "",
7262 plural((diff
> 0) ? any_lflagged(msgmap
, MN_SLCT
) : -diff
),
7263 (diff
> 0) ? "" : "UN");
7264 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
7265 q_status_message(SM_ORDER
, 3, 3, tmp_20k_buf
);
7268 q_status_message2(SM_ORDER
, 3, 3, _("Select matched %s message%s!"),
7269 comatose(diff
), plural(diff
));
7275 /*----------------------------------------------------------------------
7276 Toggle the state of the current message
7278 Args: state -- pointer pine's state variables
7279 msgmap -- message collection to operate on
7280 in_index -- in the message index view
7281 Returns: TRUE if current marked selected, FALSE otw
7284 select_by_current(struct pine
*state
, MSGNO_S
*msgmap
, CmdWhere in_index
)
7287 int all_selected
= 0;
7288 unsigned long was
, tot
, rawno
;
7291 cur
= mn_get_cur(msgmap
);
7294 thrd
= fetch_thread(state
->mail_stream
, mn_m2raw(msgmap
, cur
));
7298 was
= count_lflags_in_thread(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
);
7299 tot
= count_lflags_in_thread(state
->mail_stream
, thrd
, msgmap
, MN_NONE
);
7304 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
, 0);
7305 if(any_lflagged(msgmap
, MN_HIDE
) > 0L){
7306 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_HIDE
, 1);
7308 * See if there's anything left to zoom on. If so,
7309 * pick an adjacent one for highlighting, else make
7310 * sure nothing is left hidden...
7312 if(any_lflagged(msgmap
, MN_SLCT
)){
7313 mn_inc_cur(state
->mail_stream
, msgmap
,
7314 (in_index
== View
&& THREADING()
7315 && sp_viewing_a_thread(state
->mail_stream
))
7317 : (in_index
== View
)
7318 ? MH_ANYTHD
: MH_NONE
);
7319 if(mn_get_cur(msgmap
) == cur
)
7320 mn_dec_cur(state
->mail_stream
, msgmap
,
7321 (in_index
== View
&& THREADING()
7322 && sp_viewing_a_thread(state
->mail_stream
))
7324 : (in_index
== View
)
7325 ? MH_ANYTHD
: MH_NONE
);
7327 else /* clear all hidden flags */
7328 (void) unzoom_index(state
, state
->mail_stream
, msgmap
);
7332 set_thread_lflags(state
->mail_stream
, thrd
, msgmap
, MN_SLCT
, 1);
7334 q_status_message3(SM_ORDER
, 0, 2, "%s message%s %sselected",
7335 comatose(all_selected
? was
: tot
-was
),
7336 plural(all_selected
? was
: tot
-was
),
7337 all_selected
? "UN" : "");
7339 /* collapsed thread */
7341 && ((rawno
= mn_m2raw(msgmap
, cur
)) != 0L)
7342 && ((thrd
= fetch_thread(state
->mail_stream
, rawno
)) != NULL
)
7343 && (thrd
&& thrd
->next
&& get_lflag(state
->mail_stream
, NULL
, rawno
, MN_COLL
))){
7345 * This doesn't work quite the same as the colon command works, but
7346 * it is arguably doing the correct thing. The difference is
7347 * that aggregate_select will zoom after selecting back where it
7348 * was called from, but selecting a thread with colon won't zoom.
7349 * Maybe it makes sense to zoom after a select but not after a colon
7350 * command even though they are very similar.
7352 thread_command(state
, state
->mail_stream
, msgmap
, ':', -FOOTER_ROWS(state
));
7356 get_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
)) != 0){ /* set? */
7357 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
, 0);
7358 if(any_lflagged(msgmap
, MN_HIDE
) > 0L){
7359 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_HIDE
, 1);
7361 * See if there's anything left to zoom on. If so,
7362 * pick an adjacent one for highlighting, else make
7363 * sure nothing is left hidden...
7365 if(any_lflagged(msgmap
, MN_SLCT
)){
7366 mn_inc_cur(state
->mail_stream
, msgmap
,
7367 (in_index
== View
&& THREADING()
7368 && sp_viewing_a_thread(state
->mail_stream
))
7370 : (in_index
== View
)
7371 ? MH_ANYTHD
: MH_NONE
);
7372 if(mn_get_cur(msgmap
) == cur
)
7373 mn_dec_cur(state
->mail_stream
, msgmap
,
7374 (in_index
== View
&& THREADING()
7375 && sp_viewing_a_thread(state
->mail_stream
))
7377 : (in_index
== View
)
7378 ? MH_ANYTHD
: MH_NONE
);
7380 else /* clear all hidden flags */
7381 (void) unzoom_index(state
, state
->mail_stream
, msgmap
);
7385 set_lflag(state
->mail_stream
, msgmap
, cur
, MN_SLCT
, 1);
7387 q_status_message2(SM_ORDER
, 0, 2, "Message %s %sselected",
7388 long2string(cur
), all_selected
? "UN" : "");
7392 return(!all_selected
);
7396 /*----------------------------------------------------------------------
7397 Prompt the user for the command to perform on selected messages
7399 Args: state -- pointer pine's state variables
7400 msgmap -- message collection to operate on
7401 q_line -- line on display to write prompts
7402 Returns: 1 if the selected messages are suitably commanded,
7403 0 if the choice to pick the command was declined
7407 apply_command(struct pine
*state
, MAILSTREAM
*stream
, MSGNO_S
*msgmap
,
7408 UCS preloadkeystroke
, int flags
, int q_line
)
7410 int i
= 8, /* number of static entries in sel_opts3 */
7414 agg
= (flags
& AC_FROM_THREAD
) ? MCMD_AGG_2
: MCMD_AGG
;
7419 * To do this "right", we really ought to have access to the keymenu
7420 * here and change the typed command into a real command by running
7421 * it through menu_command. Then the switch below would be against
7422 * results from menu_command. If we did that we'd also pass the
7423 * results of menu_command in as preloadkeystroke instead of passing
7424 * the keystroke itself. But we don't have the keymenu handy,
7425 * so we have to fake it. The only complication that we run into
7426 * is that KEY_DEL is an escape sequence so we change a typed
7427 * KEY_DEL esc seq into the letter D.
7430 if(!preloadkeystroke
){
7431 if(F_ON(F_ENABLE_FLAG
,state
)){ /* flag? */
7432 sel_opts3
[i
].ch
= '*';
7433 sel_opts3
[i
].rval
= '*';
7434 sel_opts3
[i
].name
= "*";
7435 sel_opts3
[i
++].label
= N_("Flag");
7438 if(F_ON(F_ENABLE_PIPE
,state
)){ /* pipe? */
7439 sel_opts3
[i
].ch
= '|';
7440 sel_opts3
[i
].rval
= '|';
7441 sel_opts3
[i
].name
= "|";
7442 sel_opts3
[i
++].label
= N_("Pipe");
7445 if(F_ON(F_ENABLE_BOUNCE
,state
)){ /* bounce? */
7446 sel_opts3
[i
].ch
= 'b';
7447 sel_opts3
[i
].rval
= 'b';
7448 sel_opts3
[i
].name
= "B";
7449 sel_opts3
[i
++].label
= N_("Bounce");
7452 if(flags
& AC_FROM_THREAD
){
7453 if(flags
& (AC_COLL
| AC_EXPN
)){
7454 sel_opts3
[i
].ch
= '/';
7455 sel_opts3
[i
].rval
= '/';
7456 sel_opts3
[i
].name
= "/";
7457 sel_opts3
[i
++].label
= (flags
& AC_COLL
) ? N_("Collapse")
7461 sel_opts3
[i
].ch
= ';';
7462 sel_opts3
[i
].rval
= ';';
7463 sel_opts3
[i
].name
= ";";
7464 if(flags
& AC_UNSEL
)
7465 sel_opts3
[i
++].label
= N_("UnSelect");
7467 sel_opts3
[i
++].label
= N_("Select");
7470 if(F_ON(F_ENABLE_PRYNT
, state
)){ /* this one is invisible */
7471 sel_opts3
[i
].ch
= 'y';
7472 sel_opts3
[i
].rval
= '%';
7473 sel_opts3
[i
].name
= "";
7474 sel_opts3
[i
++].label
= "";
7477 if(!is_imap_stream(stream
) || LEVELUIDPLUS(stream
)){ /* expunge selected messages */
7478 sel_opts3
[i
].ch
= 'x';
7479 sel_opts3
[i
].rval
= 'x';
7480 sel_opts3
[i
].name
= "X";
7481 sel_opts3
[i
++].label
= N_("Expunge");
7484 if(nonempty_patterns(ROLE_DO_ROLES
, &pstate
) && first_pattern(&pstate
)){
7485 sel_opts3
[i
].ch
= '#';
7486 sel_opts3
[i
].rval
= '#';
7487 sel_opts3
[i
].name
= "#";
7488 sel_opts3
[i
++].label
= N_("Set Role");
7491 sel_opts3
[i
].ch
= KEY_DEL
; /* also invisible */
7492 sel_opts3
[i
].rval
= 'd';
7493 sel_opts3
[i
].name
= "";
7494 sel_opts3
[i
++].label
= "";
7496 sel_opts3
[i
].ch
= -1;
7498 snprintf(prompt
, sizeof(prompt
), "%s command : ",
7499 (flags
& AC_FROM_THREAD
) ? "THREAD" : "APPLY");
7500 prompt
[sizeof(prompt
)-1] = '\0';
7501 cmd
= double_radio_buttons(prompt
, q_line
, sel_opts3
, 'z', 'c', NO_HELP
,
7507 if(preloadkeystroke
== KEY_DEL
)
7510 if(preloadkeystroke
< 0x80 && isupper((int) preloadkeystroke
))
7511 cmd
= tolower((int) preloadkeystroke
);
7513 cmd
= (int) preloadkeystroke
; /* shouldn't happen */
7518 case 'd' : /* delete */
7519 we_cancel
= busy_cue(NULL
, NULL
, 1);
7520 rv
= cmd_delete(state
, msgmap
, agg
, NULL
); /* don't advance or offer "TAB" */
7525 case 'u' : /* undelete */
7526 we_cancel
= busy_cue(NULL
, NULL
, 1);
7527 rv
= cmd_undelete(state
, msgmap
, agg
);
7532 case 'r' : /* reply */
7533 rv
= cmd_reply(state
, msgmap
, agg
, NULL
);
7536 case 'f' : /* Forward */
7537 rv
= cmd_forward(state
, msgmap
, agg
, NULL
);
7540 case '%' : /* print */
7541 rv
= cmd_print(state
, msgmap
, agg
, MsgIndx
);
7544 case 't' : /* take address */
7545 rv
= cmd_take_addr(state
, msgmap
, agg
);
7548 case 's' : /* save */
7549 rv
= cmd_save(state
, stream
, msgmap
, agg
, MsgIndx
);
7552 case 'e' : /* export */
7553 rv
= cmd_export(state
, msgmap
, q_line
, agg
);
7556 case '|' : /* pipe */
7557 rv
= cmd_pipe(state
, msgmap
, agg
);
7560 case '*' : /* flag */
7561 we_cancel
= busy_cue(NULL
, NULL
, 1);
7562 rv
= cmd_flag(state
, msgmap
, agg
);
7567 case 'b' : /* bounce */
7568 rv
= cmd_bounce(state
, msgmap
, agg
, NULL
);
7572 collapse_or_expand(state
, stream
, msgmap
,
7573 F_ON(F_SLASH_COLL_ENTIRE
, ps_global
)
7575 : mn_get_cur(msgmap
));
7579 select_thread_stmp(state
, stream
, msgmap
);
7582 case 'x' : /* Expunge */
7583 rv
= cmd_expunge(state
, stream
, msgmap
, agg
);
7586 case 'c' : /* cancel */
7587 cmd_cancelled((flags
& AC_FROM_THREAD
) ? "Thread command"
7592 if(nonempty_patterns(ROLE_DO_ROLES
, &pstate
) && first_pattern(&pstate
)){
7593 static ESCKEY_S choose_role
[] = {
7594 {'r', 'r', "R", N_("Reply")},
7595 {'f', 'f', "F", N_("Forward")},
7596 {'b', 'b', "B", N_("Bounce")},
7600 ACTION_S
*role
= NULL
;
7602 action
= radio_buttons(_("Reply, Forward or Bounce using a role? "),
7603 -FOOTER_ROWS(state
), choose_role
,
7604 'r', 'x', h_role_aggregate
, RB_NORM
);
7605 if(action
== 'r' || action
== 'f' || action
== 'b'){
7606 void (*prev_screen
)(struct pine
*) = NULL
, (*redraw
)(void) = NULL
;
7608 redraw
= state
->redrawer
;
7609 state
->redrawer
= NULL
;
7610 prev_screen
= state
->prev_screen
;
7612 state
->next_screen
= SCREEN_FUN_NULL
;
7614 if(role_select_screen(state
, &role
,
7615 action
== 'f' ? MC_FORWARD
:
7616 action
== 'r' ? MC_REPLY
:
7617 action
== 'b' ? MC_BOUNCE
: 0) < 0){
7618 cmd_cancelled(action
== 'f' ? _("Forward") :
7619 action
== 'r' ? _("Reply") : _("Bounce"));
7620 state
->next_screen
= prev_screen
;
7621 state
->redrawer
= redraw
;
7622 state
->mangled_screen
= 1;
7626 role
= combine_inherited_role(role
);
7628 role
= (ACTION_S
*) fs_get(sizeof(*role
));
7629 memset((void *) role
, 0, sizeof(*role
));
7630 role
->nick
= cpystr("Default Role");
7633 state
->redrawer
= NULL
;
7636 (void) cmd_reply(state
, msgmap
, agg
, role
);
7640 (void) cmd_forward(state
, msgmap
, agg
, role
);
7644 (void) cmd_bounce(state
, msgmap
, agg
, role
);
7654 state
->next_screen
= prev_screen
;
7655 state
->redrawer
= redraw
;
7656 state
->mangled_screen
= 1;
7662 case 'z' : /* default */
7663 q_status_message(SM_INFO
, 0, 2,
7664 "Cancelled, there is no default command");
7676 * Select by message number ranges.
7677 * Sets searched bits in mail_elts
7679 * Args limitsrch -- limit search to this searchset
7681 * Returns 0 on success.
7684 select_by_number(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**limitsrch
)
7688 char number1
[16], number2
[16], numbers
[80], *p
, *t
;
7693 ps_global
->mangled_footer
= 1;
7696 int flags
= OE_APPEND_CURRENT
;
7698 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
7699 sizeof(numbers
), _(select_num
), NULL
, help
, &flags
);
7704 help
= (help
== NO_HELP
) ? h_select_by_num
: NO_HELP
;
7708 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
7709 if(!isspace((unsigned char)*p
))
7714 if(r
== 1 || numbers
[0] == '\0'){
7715 cmd_cancelled("Selection by number");
7722 for(n1
= 1; n1
<= stream
->nmsgs
; n1
++)
7723 if((mc
= mail_elt(stream
, n1
)) != NULL
)
7724 mc
->searched
= 0; /* clear searched bits */
7726 for(p
= numbers
; *p
; p
++){
7728 while(*p
&& isdigit((unsigned char)*p
))
7734 if(number1
[0] == '\0'){
7736 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7737 _("Invalid number range, missing number before \"-\": %s"),
7741 else if(!strucmp("end", p
)){
7745 else if(!strucmp("$", p
)){
7754 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7755 _("Invalid message number: %s"), numbers
);
7761 n1
= mn_get_total(msgmap
);
7763 n1
= mn_get_cur(msgmap
);
7764 else if((n1
= atol(number1
)) < 1L || n1
> mn_get_total(msgmap
)){
7765 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7766 _("\"%s\" out of message number range"),
7773 while(*++p
&& isdigit((unsigned char)*p
))
7779 if(number2
[0] == '\0'){
7780 if(!strucmp("end", p
)){
7784 else if(!strucmp(p
, "$")){
7793 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7794 _("Invalid number range, missing number after \"-\": %s"),
7801 n2
= mn_get_total(msgmap
);
7803 n2
= mn_get_cur(msgmap
);
7804 else if((n2
= atol(number2
)) < 1L || n2
> mn_get_total(msgmap
)){
7805 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7806 _("\"%s\" out of message number range"),
7814 strncpy(t
, long2string(n1
), sizeof(t
));
7815 t
[sizeof(t
)-1] = '\0';
7816 q_status_message2(SM_ORDER
| SM_DING
, 0, 2,
7817 _("Invalid reverse message number range: %s-%s"),
7818 t
, long2string(n2
));
7822 for(;n1
<= n2
; n1
++){
7823 raw
= mn_m2raw(msgmap
, n1
);
7825 && (!(limitsrch
&& *limitsrch
)
7826 || in_searchset(*limitsrch
, (unsigned long) raw
)))
7827 mm_searched(stream
, raw
);
7831 raw
= mn_m2raw(msgmap
, n1
);
7833 && (!(limitsrch
&& *limitsrch
)
7834 || in_searchset(*limitsrch
, (unsigned long) raw
)))
7835 mm_searched(stream
, raw
);
7847 * Select by thread number ranges.
7848 * Sets searched bits in mail_elts
7850 * Args limitsrch -- limit search to this searchset
7852 * Returns 0 on success.
7855 select_by_thrd_number(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**msgset
)
7859 char number1
[16], number2
[16], numbers
[80], *p
, *t
;
7861 PINETHRD_S
*thrd
= NULL
, *th
;
7865 ps_global
->mangled_footer
= 1;
7868 int flags
= OE_APPEND_CURRENT
;
7870 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
7871 sizeof(numbers
), _(select_num
), NULL
, help
, &flags
);
7876 help
= (help
== NO_HELP
) ? h_select_by_thrdnum
: NO_HELP
;
7880 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
7881 if(!isspace((unsigned char)*p
))
7886 if(r
== 1 || numbers
[0] == '\0'){
7887 cmd_cancelled("Selection by number");
7894 for(n1
= 1; n1
<= stream
->nmsgs
; n1
++)
7895 if((mc
= mail_elt(stream
, n1
)) != NULL
)
7896 mc
->searched
= 0; /* clear searched bits */
7898 for(p
= numbers
; *p
; p
++){
7900 while(*p
&& isdigit((unsigned char)*p
))
7906 if(number1
[0] == '\0'){
7908 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7909 _("Invalid number range, missing number before \"-\": %s"),
7913 else if(!strucmp("end", p
)){
7917 else if(!strucmp(p
, "$")){
7926 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7927 _("Invalid thread number: %s"), numbers
);
7933 n1
= msgmap
->max_thrdno
;
7935 th
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
7938 else if((n1
= atol(number1
)) < 1L || n1
> msgmap
->max_thrdno
){
7939 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7940 _("\"%s\" out of thread number range"),
7948 while(*++p
&& isdigit((unsigned char)*p
))
7954 if(number2
[0] == '\0'){
7955 if(!strucmp("end", p
)){
7959 else if(!strucmp("$", p
)){
7968 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7969 _("Invalid number range, missing number after \"-\": %s"),
7976 n2
= msgmap
->max_thrdno
;
7978 th
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
7981 else if((n2
= atol(number2
)) < 1L || n2
> msgmap
->max_thrdno
){
7982 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
7983 _("\"%s\" out of thread number range"),
7991 strncpy(t
, long2string(n1
), sizeof(t
));
7992 t
[sizeof(t
)-1] = '\0';
7993 q_status_message2(SM_ORDER
| SM_DING
, 0, 2,
7994 _("Invalid reverse message number range: %s-%s"),
7995 t
, long2string(n2
));
7999 for(;n1
<= n2
; n1
++){
8000 thrd
= find_thread_by_number(stream
, msgmap
, n1
, thrd
);
8003 set_search_bit_for_thread(stream
, thrd
, msgset
);
8007 thrd
= find_thread_by_number(stream
, msgmap
, n1
, NULL
);
8010 set_search_bit_for_thread(stream
, thrd
, msgset
);
8022 * Select by message dates.
8023 * Sets searched bits in mail_elts
8025 * Args limitsrch -- limit search to this searchset
8027 * Returns 0 on success.
8030 select_by_date(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
, SEARCHSET
**limitsrch
)
8032 int r
, we_cancel
= 0, when
= 0;
8033 char date
[100], defdate
[100], prompt
[128];
8034 time_t seldate
= time(0);
8035 struct tm
*seldate_tm
;
8038 static struct _tense
{
8043 {"were ", "SENT SINCE", " (inclusive)"},
8044 {"were ", "SENT BEFORE", " (exclusive)"},
8045 {"were ", "SENT ON", "" },
8046 {"", "ARRIVED SINCE", " (inclusive)"},
8047 {"", "ARRIVED BEFORE", " (exclusive)"},
8048 {"", "ARRIVED ON", "" }
8052 ps_global
->mangled_footer
= 1;
8056 * If talking to an old server, default to SINCE instead of
8057 * SENTSINCE, which was added later.
8059 if(is_imap_stream(stream
) && !modern_imap_stream(stream
))
8063 int flags
= OE_APPEND_CURRENT
;
8065 seldate_tm
= localtime(&seldate
);
8066 snprintf(defdate
, sizeof(defdate
), "%.2d-%.4s-%.4d", seldate_tm
->tm_mday
,
8067 month_abbrev(seldate_tm
->tm_mon
+ 1),
8068 seldate_tm
->tm_year
+ 1900);
8069 defdate
[sizeof(defdate
)-1] = '\0';
8070 snprintf(prompt
,sizeof(prompt
),"Select messages which %s%s%s [%s]: ",
8071 tense
[when
].preamble
, tense
[when
].range
,
8072 tense
[when
].scope
, defdate
);
8073 prompt
[sizeof(prompt
)-1] = '\0';
8074 r
= optionally_enter(date
,-FOOTER_ROWS(ps_global
), 0, sizeof(date
),
8075 prompt
, sel_date_opt
, help
, &flags
);
8078 cmd_cancelled("Selection by date");
8082 help
= (help
== NO_HELP
) ? h_select_date
: NO_HELP
;
8093 if(stream
&& (rawno
= mn_m2raw(msgmap
, msgno
)) > 0L
8094 && rawno
<= stream
->nmsgs
8095 && (mc
= mail_elt(stream
, rawno
))){
8097 /* cache not filled in yet? */
8101 if(stream
->dtb
&& stream
->dtb
->flags
& DR_NEWS
){
8103 ulong2string(mail_uid(stream
, rawno
)),
8105 seq
[sizeof(seq
)-1] = '\0';
8106 mail_fetch_overview(stream
, seq
, NULL
);
8109 strncpy(seq
, long2string(rawno
),
8111 seq
[sizeof(seq
)-1] = '\0';
8112 mail_fetch_fast(stream
, seq
, 0L);
8116 /* mail_date returns fixed field width date */
8117 mail_date(date
, mc
);
8124 case 12 : /* set default to PREVIOUS day */
8128 case 13 : /* set default to NEXT day */
8133 when
= (when
+1) % (sizeof(tense
) / sizeof(struct _tense
));
8140 removing_leading_white_space(date
);
8141 removing_trailing_white_space(date
);
8143 strncpy(date
, defdate
, sizeof(date
));
8144 date
[sizeof(date
)-1] = '\0';
8150 if((pgm
= mail_newsearchpgm()) != NULL
){
8152 short converted_date
;
8154 if(mail_parse_date(&elt
, (unsigned char *) date
)){
8155 converted_date
= mail_shortdate(elt
.year
, elt
.month
, elt
.day
);
8159 pgm
->sentsince
= converted_date
;
8162 pgm
->sentbefore
= converted_date
;
8165 pgm
->senton
= converted_date
;
8168 pgm
->since
= converted_date
;
8171 pgm
->before
= converted_date
;
8174 pgm
->on
= converted_date
;
8178 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
8180 if(ps_global
&& ps_global
->ttyo
){
8181 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8182 ps_global
->mangled_footer
= 1;
8185 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8187 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
8192 /* we know this was freed in mail_search, let caller know */
8197 mail_free_searchpgm(&pgm
);
8198 q_status_message1(SM_ORDER
, 3, 3,
8199 _("Invalid date entered: %s"), date
);
8208 * Select by searching in message headers or body
8209 * using the x-gm-ext-1 capaility. This function
8210 * reads the input from the user and passes it to
8211 * the server directly. We need a x-gm-ext-1 variable
8212 * in the search pgm to carry this information to the
8215 * Sets searched bits in mail_elts
8217 * Args limitsrch -- limit search to this searchset
8219 * Returns 0 on success.
8222 select_by_gm_content(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
, SEARCHSET
**limitsrch
)
8224 int r
, we_cancel
= 0, rv
;
8226 char namehdr
[MAILTMPLEN
];
8230 ps_global
->mangled_footer
= 1;
8231 ekey
[0].ch
= ekey
[1].ch
= ekey
[2].ch
= ekey
[3].ch
= -1;
8233 strncpy(tmp
, sel_x_gm_ext
, sizeof(tmp
)-1);
8234 tmp
[sizeof(tmp
)-1] = '\0';
8240 int flags
= OE_APPEND_CURRENT
;
8242 r
= optionally_enter(namehdr
, -FOOTER_ROWS(ps_global
), 0,
8243 sizeof(namehdr
), tmp
, ekey
, help
, &flags
);
8249 help
= (help
== NO_HELP
) ? h_select_by_gm_content
: NO_HELP
;
8254 cmd_cancelled("Selection by content");
8258 removing_leading_white_space(namehdr
);
8260 if ((namehdr
[0] != '\0')
8261 && isspace((unsigned char) namehdr
[strlen(namehdr
) - 1]))
8262 removing_trailing_white_space(namehdr
);
8264 if (namehdr
[0] != '\0')
8268 if(ps_global
&& ps_global
->ttyo
){
8269 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8270 ps_global
->mangled_footer
= 1;
8273 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8275 rv
= agg_text_select(stream
, msgmap
, 'g', namehdr
, 0, 0, NULL
, "utf-8", limitsrch
);
8283 * Select by searching in message headers or body.
8284 * Sets searched bits in mail_elts
8286 * Args limitsrch -- limit search to this searchset
8288 * Returns 0 on success.
8291 select_by_text(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, long int msgno
, SEARCHSET
**limitsrch
)
8293 int r
= '\0', ku
, type
, we_cancel
= 0, flags
, rv
, ekeyi
= 0;
8294 int not = 0, me
= 0;
8295 char sstring
[512], tmp
[128];
8296 char *p
, *sval
= NULL
;
8299 ENVELOPE
*env
= NULL
;
8301 unsigned flagsforhist
= 0;
8302 static HISTORY_S
*history
= NULL
;
8303 static char *recip
= "RECIPIENTS";
8304 static char *partic
= "PARTICIPANTS";
8305 static char *match_me
= N_("[Match_My_Addresses]");
8306 static char *dont_match_me
= N_("[Don't_Match_My_Addresses]");
8308 ps_global
->mangled_footer
= 1;
8309 ekey
[0].ch
= ekey
[1].ch
= ekey
[2].ch
= ekey
[3].ch
= -1;
8312 type
= radio_buttons(not ? _(sel_text_not
) : _(sel_text
),
8313 -FOOTER_ROWS(ps_global
), sel_text_opt
,
8314 's', 'x', NO_HELP
, RB_NORM
|RB_RET_HELP
);
8319 helper(h_select_text
, "HELP FOR SELECT BASED ON CONTENTS",
8321 ps_global
->mangled_screen
= 1;
8328 * prepare some friendly defaults...
8331 case 't' : /* address fields, offer To or From */
8336 sval
= (type
== 't') ? "TO" :
8337 (type
== 'f') ? "FROM" :
8338 (type
== 'c') ? "CC" :
8339 (type
== 'r') ? recip
: partic
;
8340 ekey
[ekeyi
].ch
= ctrl('T');
8341 ekey
[ekeyi
].name
= "^T";
8342 ekey
[ekeyi
].rval
= 10;
8343 /* TRANSLATORS: use Current To Address */
8344 ekey
[ekeyi
++].label
= N_("Cur To");
8345 ekey
[ekeyi
].ch
= ctrl('R');
8346 ekey
[ekeyi
].name
= "^R";
8347 ekey
[ekeyi
].rval
= 11;
8348 /* TRANSLATORS: use Current From Address */
8349 ekey
[ekeyi
++].label
= N_("Cur From");
8350 ekey
[ekeyi
].ch
= ctrl('W');
8351 ekey
[ekeyi
].name
= "^W";
8352 ekey
[ekeyi
].rval
= 12;
8353 /* TRANSLATORS: use Current Cc Address */
8354 ekey
[ekeyi
++].label
= N_("Cur Cc");
8355 ekey
[ekeyi
].ch
= ctrl('Y');
8356 ekey
[ekeyi
].name
= "^Y";
8357 ekey
[ekeyi
].rval
= 13;
8358 /* TRANSLATORS: Match Me means match my address */
8359 ekey
[ekeyi
++].label
= N_("Match Me");
8361 ekey
[ekeyi
].name
= "";
8362 ekey
[ekeyi
].rval
= 0;
8363 ekey
[ekeyi
++].label
= "";
8368 ekey
[ekeyi
].ch
= ctrl('X');
8369 ekey
[ekeyi
].name
= "^X";
8370 ekey
[ekeyi
].rval
= 14;
8371 /* TRANSLATORS: use Current Subject */
8372 ekey
[ekeyi
++].label
= N_("Cur Subject");
8384 strncpy(tmp
, "Name of HEADER to match : ", sizeof(tmp
)-1);
8385 tmp
[sizeof(tmp
)-1] = '\0';
8386 flags
= OE_APPEND_CURRENT
;
8392 r
= optionally_enter(namehdr
, -FOOTER_ROWS(ps_global
), 0,
8393 sizeof(namehdr
), tmp
, ekey
, NO_HELP
, &flags
);
8395 cmd_cancelled("Selection by text");
8398 removing_leading_white_space(namehdr
);
8400 while ((namehdr
[0] != '\0') && /* remove trailing ":" */
8401 (namehdr
[strlen(namehdr
) - 1] == ':'))
8402 namehdr
[strlen(namehdr
) - 1] = '\0';
8403 if ((namehdr
[0] != '\0')
8404 && isspace((unsigned char) namehdr
[strlen(namehdr
) - 1]))
8405 removing_trailing_white_space(namehdr
);
8409 if (strchr(namehdr
,' ') || strchr(namehdr
,'\t') ||
8410 strchr(namehdr
,':'))
8412 if (namehdr
[0] == '\0')
8422 dprint((1,"\n - BOTCH: select_text unrecognized option\n"));
8426 ekey
[ekeyi
].ch
= KEY_UP
;
8427 ekey
[ekeyi
].rval
= 30;
8428 ekey
[ekeyi
].name
= "";
8430 ekey
[ekeyi
++].label
= "";
8432 ekey
[ekeyi
].ch
= KEY_DOWN
;
8433 ekey
[ekeyi
].rval
= 31;
8434 ekey
[ekeyi
].name
= "";
8435 ekey
[ekeyi
++].label
= "";
8437 ekey
[ekeyi
].ch
= -1;
8441 init_hist(&history
, HISTSIZE
);
8443 if(ekey
[0].ch
> -1 && msgno
> 0L
8444 && !(env
=pine_mail_fetchstructure(stream
,mn_m2raw(msgmap
,msgno
),
8453 /* TRANSLATORS: character String in message <message number> to NOT match : " */
8454 snprintf(tmp
, sizeof(tmp
), "String in message %s to NOT match : ", sval
);
8456 snprintf(tmp
, sizeof(tmp
), "String in message %s to match : ", sval
);
8458 if(items_in_hist(history
) > 0){
8459 ekey
[ku
].name
= HISTORY_UP_KEYNAME
;
8460 ekey
[ku
].label
= HISTORY_KEYLABEL
;
8461 ekey
[ku
+1].name
= HISTORY_DOWN_KEYNAME
;
8462 ekey
[ku
+1].label
= HISTORY_KEYLABEL
;
8466 ekey
[ku
].label
= "";
8467 ekey
[ku
+1].name
= "";
8468 ekey
[ku
+1].label
= "";
8471 flags
= OE_APPEND_CURRENT
| OE_KEEP_TRAILING_SPACE
;
8472 r
= optionally_enter(sstring
, -FOOTER_ROWS(ps_global
), 0,
8473 sizeof(sstring
), tmp
, ekey
, help
, &flags
);
8475 if(me
&& r
== 0 && ((!not && strcmp(sstring
, _(match_me
))) || (not && strcmp(sstring
, _(dont_match_me
)))))
8480 help
= (help
== NO_HELP
)
8482 ? ((type
== 'f') ? h_select_txt_not_from
8483 : (type
== 't') ? h_select_txt_not_to
8484 : (type
== 'c') ? h_select_txt_not_cc
8485 : (type
== 's') ? h_select_txt_not_subj
8486 : (type
== 'a') ? h_select_txt_not_all
8487 : (type
== 'r') ? h_select_txt_not_recip
8488 : (type
== 'p') ? h_select_txt_not_partic
8489 : (type
== 'b') ? h_select_txt_not_body
8491 : ((type
== 'f') ? h_select_txt_from
8492 : (type
== 't') ? h_select_txt_to
8493 : (type
== 'c') ? h_select_txt_cc
8494 : (type
== 's') ? h_select_txt_subj
8495 : (type
== 'a') ? h_select_txt_all
8496 : (type
== 'r') ? h_select_txt_recip
8497 : (type
== 'p') ? h_select_txt_partic
8498 : (type
== 'b') ? h_select_txt_body
8505 case 10 : /* To: default */
8506 if(env
&& env
->to
&& env
->to
->mailbox
){
8507 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->to
->mailbox
,
8508 env
->to
->host
? "@" : "",
8509 env
->to
->host
? env
->to
->host
: "");
8510 sstring
[sizeof(sstring
)-1] = '\0';
8514 case 11 : /* From: default */
8515 if(env
&& env
->from
&& env
->from
->mailbox
){
8516 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->from
->mailbox
,
8517 env
->from
->host
? "@" : "",
8518 env
->from
->host
? env
->from
->host
: "");
8519 sstring
[sizeof(sstring
)-1] = '\0';
8523 case 12 : /* Cc: default */
8524 if(env
&& env
->cc
&& env
->cc
->mailbox
){
8525 snprintf(sstring
, sizeof(sstring
), "%s%s%s", env
->cc
->mailbox
,
8526 env
->cc
->host
? "@" : "",
8527 env
->cc
->host
? env
->cc
->host
: "");
8528 sstring
[sizeof(sstring
)-1] = '\0';
8532 case 13 : /* Match my addresses */
8534 snprintf(sstring
, sizeof(sstring
), "%s", not ? _(dont_match_me
) : _(match_me
));
8537 case 14 : /* Subject: default */
8538 if(env
&& env
->subject
&& env
->subject
[0]){
8541 q
= (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf
,
8542 SIZEOF_20KBUF
, env
->subject
);
8543 snprintf(sstring
, sizeof(sstring
), "%s", q
);
8544 sstring
[sizeof(sstring
)-1] = '\0';
8550 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8551 if((p
= get_prev_hist(history
, sstring
, flagsforhist
, NULL
)) != NULL
){
8552 strncpy(sstring
, p
, sizeof(sstring
));
8553 sstring
[sizeof(sstring
)-1] = '\0';
8554 if(history
->hist
[history
->curindex
]){
8555 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
8556 not = (flagsforhist
& 0x1) ? 1 : 0;
8557 me
= (flagsforhist
& 0x2) ? 1 : 0;
8566 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8567 if((p
= get_next_hist(history
, sstring
, flagsforhist
, NULL
)) != NULL
){
8568 strncpy(sstring
, p
, sizeof(sstring
));
8569 sstring
[sizeof(sstring
)-1] = '\0';
8570 if(history
->hist
[history
->curindex
]){
8571 flagsforhist
= history
->hist
[history
->curindex
]->flags
;
8572 not = (flagsforhist
& 0x1) ? 1 : 0;
8573 me
= (flagsforhist
& 0x2) ? 1 : 0;
8585 if(r
== 1 || sstring
[0] == '\0')
8592 if(type
== 'x' || r
== 'x'){
8593 cmd_cancelled("Selection by text");
8597 if(ps_global
&& ps_global
->ttyo
){
8598 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8599 ps_global
->mangled_footer
= 1;
8602 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8604 flagsforhist
= (not ? 0x1 : 0) + (me
? 0x2 : 0);
8605 save_hist(history
, sstring
, flagsforhist
, NULL
);
8607 rv
= agg_text_select(stream
, msgmap
, type
, namehdr
, not, me
, sstring
, "utf-8", limitsrch
);
8616 * Select by message size.
8617 * Sets searched bits in mail_elts
8619 * Args limitsrch -- limit search to this searchset
8621 * Returns 0 on success.
8624 select_by_size(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8626 int r
, large
= 1, we_cancel
= 0;
8627 unsigned long n
, mult
= 1L, numerator
= 0L, divisor
= 1L;
8628 char size
[16], numbers
[80], *p
, *t
;
8631 long flags
= (SE_NOPREFETCH
| SE_FREE
);
8634 ps_global
->mangled_footer
= 1;
8638 int flgs
= OE_APPEND_CURRENT
;
8640 sel_size_opt
[1].label
= large
? sel_size_smaller
: sel_size_larger
;
8642 r
= optionally_enter(numbers
, -FOOTER_ROWS(ps_global
), 0,
8643 sizeof(numbers
), large
? _(select_size_larger_msg
)
8644 : _(select_size_smaller_msg
),
8645 sel_size_opt
, help
, &flgs
);
8655 help
= (help
== NO_HELP
) ? (large
? h_select_by_larger_size
8656 : h_select_by_smaller_size
)
8661 for(t
= p
= numbers
; *p
; p
++) /* strip whitespace */
8662 if(!isspace((unsigned char)*p
))
8667 if(r
== 1 || numbers
[0] == '\0'){
8668 cmd_cancelled("Selection by size");
8675 if(numbers
[0] == '-'){
8676 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
8677 _("Invalid size entered: %s"), numbers
);
8684 while(*p
&& isdigit((unsigned char)*p
))
8689 if(size
[0] == '\0' && *p
== '.' && isdigit(*(p
+1))){
8694 if(size
[0] == '\0'){
8695 q_status_message1(SM_ORDER
| SM_DING
, 0, 2,
8696 _("Invalid size entered: %s"), numbers
);
8700 n
= strtoul(size
, (char **)NULL
, 10);
8705 * We probably ought to just use atof() to convert 1.1 into a
8706 * double, but since we haven't used atof() anywhere else I'm
8707 * reluctant to use it because of portability concerns.
8711 while(*p
&& isdigit((unsigned char)*p
)){
8719 numerator
= strtoul(size
, (char **)NULL
, 10);
8739 n
= n
* mult
+ (numerator
* mult
) / divisor
;
8741 pgm
= mail_newsearchpgm();
8747 if(is_imap_stream(stream
) && !modern_imap_stream(stream
))
8748 flags
|= SE_NOSERVER
;
8750 if(ps_global
&& ps_global
->ttyo
){
8751 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8752 ps_global
->mangled_footer
= 1;
8755 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8757 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
8758 pine_mail_search_full(stream
, NULL
, pgm
, SE_NOPREFETCH
| SE_FREE
);
8759 /* we know this was freed in mail_search, let caller know */
8771 * visible_searchset -- return c-client search set unEXLDed
8775 visible_searchset(MAILSTREAM
*stream
, MSGNO_S
*msgmap
)
8778 SEARCHSET
*full_set
= NULL
, **set
;
8781 * If we're talking to anything other than a server older than
8782 * imap 4rev1, build a searchset otherwise it'll choke.
8784 if(!(is_imap_stream(stream
) && !modern_imap_stream(stream
))){
8785 if(any_lflagged(msgmap
, MN_EXLD
)){
8786 for(n
= 1L, set
= &full_set
, run
= 0L; n
<= stream
->nmsgs
; n
++)
8787 if(get_lflag(stream
, NULL
, n
, MN_EXLD
)){
8788 if(run
){ /* previous NOT excluded? */
8790 (*set
)->last
= n
- 1L;
8792 set
= &(*set
)->next
;
8796 else if(run
++){ /* next in run */
8799 else{ /* start of run */
8800 *set
= mail_newsearchset();
8805 full_set
= mail_newsearchset();
8806 full_set
->first
= 1L;
8807 full_set
->last
= stream
->nmsgs
;
8816 * Select by message status bits.
8817 * Sets searched bits in mail_elts
8819 * Args limitsrch -- limit search to this searchset
8821 * Returns 0 on success.
8824 select_by_status(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8826 int s
, not = 0, we_cancel
= 0, rv
;
8829 s
= radio_buttons((not) ? _(sel_flag_not
) : _(sel_flag
),
8830 -FOOTER_ROWS(ps_global
), sel_flag_opt
, '*', 'x',
8831 NO_HELP
, RB_NORM
|RB_RET_HELP
);
8834 cmd_cancelled("Selection by status");
8838 helper(h_select_status
, _("HELP FOR SELECT BASED ON STATUS"),
8840 ps_global
->mangled_screen
= 1;
8848 if(ps_global
&& ps_global
->ttyo
){
8849 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8850 ps_global
->mangled_footer
= 1;
8853 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8854 rv
= agg_flag_select(stream
, not, s
, limitsrch
);
8863 * Select by rule. Usually srch, indexcolor, and roles would be most useful.
8864 * Sets searched bits in mail_elts
8866 * Args limitsrch -- limit search to this searchset
8868 * Returns 0 on success.
8871 select_by_rule(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
8873 char rulenick
[1000], *nick
;
8875 int r
, last_r
= 0, not = 0, we_cancel
= 0, rflags
= ROLE_DO_SRCH
8883 ps_global
->mangled_footer
= 1;
8885 if(F_ON(F_ENABLE_TAB_COMPLETE
, ps_global
)){
8886 sel_rule_opt
[2].ch
= TAB
;
8887 sel_rule_opt
[2].rval
= 15;
8888 sel_rule_opt
[2].name
= "TAB";
8889 sel_rule_opt
[2].label
= N_("Complete");
8892 memset(&sel_rule_opt
[2], 0, sizeof(sel_rule_opt
[2]));
8898 oe_flags
= OE_APPEND_CURRENT
;
8899 r
= optionally_enter(rulenick
, -FOOTER_ROWS(ps_global
), 0,
8901 not ? _("Rule to NOT match: ")
8902 : _("Rule to match: "),
8903 sel_rule_opt
, NO_HELP
, &oe_flags
);
8906 /* select rulenick from a list */
8907 if((nick
=choose_a_rule(rflags
, NULL
)) != NULL
){
8908 strncpy(rulenick
, nick
, sizeof(rulenick
)-1);
8909 rulenick
[sizeof(rulenick
)-1] = '\0';
8910 fs_give((void **) &nick
);
8916 int n
= rulenick_complete(rflags
, rulenick
, sizeof(rulenick
));
8918 if(n
> 1 && last_r
== 15 && !(oe_flags
& OE_USER_MODIFIED
)){
8919 /* double tab with multiple completions: select from list */
8920 if((nick
=choose_a_rule(rflags
, rulenick
)) != NULL
){
8921 strncpy(rulenick
, nick
, sizeof(rulenick
)-1);
8922 rulenick
[sizeof(rulenick
)-1] = '\0';
8923 fs_give((void **) &nick
);
8936 helper(h_select_rule
, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE
);
8937 ps_global
->mangled_screen
= 1;
8940 cmd_cancelled("Selection by Rule");
8944 removing_leading_and_trailing_white_space(rulenick
);
8947 }while(r
== 3 || r
== 4 || r
== 15 || r
== '!');
8951 * The approach of requiring a nickname instead of just allowing the
8952 * user to select from the list of rules has the drawback that a rule
8953 * may not have a nickname, or there may be more than one rule with
8954 * the same nickname. However, it has the benefit of allowing the user
8955 * to type in the nickname and, most importantly, allows us to set
8956 * up the ! (not). We could incorporate the ! into the selection
8957 * screen, but this is easier and also allows the typing of nicks.
8958 * User can just set up nicknames if they want to use this feature.
8960 patgrp
= nick_to_patgrp(rulenick
, rflags
);
8963 if(ps_global
&& ps_global
->ttyo
){
8964 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
8965 ps_global
->mangled_footer
= 1;
8968 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
8969 match_pattern(patgrp
, stream
, limitsrch
? *limitsrch
: 0, NULL
,
8971 (not ? MP_NOT
: 0) | SE_NOPREFETCH
);
8972 free_patgrp(&patgrp
);
8977 if(limitsrch
&& *limitsrch
){
8978 mail_free_searchset(limitsrch
);
8987 * Allow user to choose a rule from their list of rules.
8989 * Args rflags -- Pattern types to choose from
8990 * prefix -- Only list rules matching the given prefix
8992 * Returns an allocated rule nickname on success, NULL otherwise.
8995 choose_a_rule(int rflags
, char *prefix
)
8997 char *choice
= NULL
;
8998 char **rule_list
, **lp
;
9000 int prefix_length
= 0;
9003 void (*redraw
)(void) = ps_global
->redrawer
;
9005 if(!(nonempty_patterns(rflags
, &pstate
) && first_pattern(&pstate
))){
9006 q_status_message(SM_ORDER
, 3, 3,
9007 _("No rules available. Use Setup/Rules to add some."));
9012 * Build a list of rules to choose from.
9016 prefix_length
= strlen(prefix
);
9018 for(pat
= first_pattern(&pstate
); pat
; pat
= next_pattern(&pstate
)) {
9019 if(!pat
->patgrp
|| !pat
->patgrp
->nick
)
9021 if(!prefix
|| strncmp(pat
->patgrp
->nick
, prefix
, prefix_length
) == 0)
9026 q_status_message(SM_ORDER
, 3, 4, _("No rules defined, use Setup/Rules"));
9030 lp
= rule_list
= (char **) fs_get((cnt
+ 1) * sizeof(*rule_list
));
9031 memset(rule_list
, 0, (cnt
+1) * sizeof(*rule_list
));
9033 for(pat
= first_pattern(&pstate
); pat
; pat
= next_pattern(&pstate
)) {
9034 if(!pat
->patgrp
|| !pat
->patgrp
->nick
)
9036 if(!prefix
|| strncmp(pat
->patgrp
->nick
, prefix
, prefix_length
) == 0)
9037 *lp
++ = cpystr(pat
->patgrp
->nick
);
9040 /* TRANSLATORS: SELECT A RULE is a screen title
9041 TRANSLATORS: Print something1 using something2.
9042 "rules" is something1 */
9043 choice
= choose_item_from_list(rule_list
, NULL
, _("SELECT A RULE"),
9044 _("rules"), h_select_rule_screen
,
9045 _("HELP FOR SELECTING A RULE NICKNAME"), NULL
);
9048 q_status_message(SM_ORDER
, 1, 4, "No choice");
9049 ps_global
->redrawer
= redraw
;
9052 free_list_array(&rule_list
);
9059 * Complete a partial rule name against the user's list of rules.
9061 * Args rflags -- Pattern types to choose from
9062 * nick -- Current rule nickname to complete
9063 * nick_size -- Maximum length of the nick array to store completion in
9065 * Returns the number of valid completions, i.e.:
9067 * 0 if there are no valid completions. The value of the nick argument has not
9069 * 1 if there is only a single valid completion. The value of the nick argument
9070 * has been replaced with the full text of that completion.
9071 * >1 if there is more than one valid completion. The value of the nick argument
9072 * has been replaced with the longest substring common to all the appropriate
9076 rulenick_complete(int rflags
, char *nick
, int nick_size
)
9078 char *candidate
= NULL
;
9080 int common_prefix_length
= 0;
9085 if(!(nonempty_patterns(rflags
, &pstate
) && first_pattern(&pstate
)))
9088 nick_length
= strlen(nick
);
9090 for(pat
= first_pattern(&pstate
); pat
; pat
= next_pattern(&pstate
)){
9091 if(!pat
->patgrp
|| !pat
->patgrp
->nick
)
9093 if(strncmp(pat
->patgrp
->nick
, nick
, nick_length
) == 0){
9094 /* This is a candidate for completion. */
9097 /* This is the first candidate. Keep it as a future reference to
9098 * compare against to find the longest common prefix length of
9099 * all the matches. */
9100 candidate
= pat
->patgrp
->nick
;
9101 common_prefix_length
= strlen(pat
->patgrp
->nick
);
9104 /* Find the common prefix length between the first candidate and
9107 for(i
= 0; i
< common_prefix_length
; i
++){
9108 if(pat
->patgrp
->nick
[i
] != candidate
[i
]){
9109 /* In the event that we ended up in the middle of a
9110 * UTF-8 code point, backtrack to the byte before the
9111 * start of this code point. */
9112 while(i
> 0 && (candidate
[i
] & 0xC0) == 0x80)
9114 common_prefix_length
= i
;
9123 int length
= MIN(nick_size
, common_prefix_length
);
9124 strncpy(nick
+ nick_length
, candidate
+ nick_length
, length
- nick_length
);
9125 nick
[length
] = '\0';
9133 * Select by current thread.
9134 * Sets searched bits in mail_elts for this entire thread
9136 * Args limitsrch -- limit search to this searchset
9138 * Returns 0 on success.
9141 select_by_thread(MAILSTREAM
*stream
, MSGNO_S
*msgmap
, SEARCHSET
**limitsrch
)
9144 PINETHRD_S
*thrd
= NULL
;
9151 for(n
= 1L; n
<= stream
->nmsgs
; n
++)
9152 if((mc
= mail_elt(stream
, n
)) != NULL
)
9153 mc
->searched
= 0; /* clear searched bits */
9155 thrd
= fetch_thread(stream
, mn_m2raw(msgmap
, mn_get_cur(msgmap
)));
9156 if(thrd
&& thrd
->top
&& thrd
->top
!= thrd
->rawno
)
9157 thrd
= fetch_thread(stream
, thrd
->top
);
9160 * This doesn't unselect if the thread is already selected
9161 * (like select current does), it always selects.
9162 * There is no way to select ! this thread.
9165 set_search_bit_for_thread(stream
, thrd
, limitsrch
);
9174 * Select by message keywords.
9175 * Sets searched bits in mail_elts
9177 * Args limitsrch -- limit search to this searchset
9179 * Returns 0 on success.
9182 select_by_keyword(MAILSTREAM
*stream
, SEARCHSET
**limitsrch
)
9184 int r
, last_r
= 0, not = 0, we_cancel
= 0;
9185 char keyword
[MAXUSERFLAG
+1], *kword
;
9186 char *error
= NULL
, *p
, *prompt
;
9191 ps_global
->mangled_footer
= 1;
9193 if(F_ON(F_ENABLE_TAB_COMPLETE
, ps_global
)){
9194 sel_key_opt
[2].ch
= TAB
;
9195 sel_key_opt
[2].rval
= 15;
9196 sel_key_opt
[2].name
= "TAB";
9197 sel_key_opt
[2].label
= N_("Complete");
9200 memset(&sel_key_opt
[2], 0, sizeof(sel_key_opt
[2]));
9208 q_status_message(SM_ORDER
, 3, 4, error
);
9209 fs_give((void **) &error
);
9212 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
) && ps_global
->keywords
){
9214 prompt
= _("Keyword (or keyword initial) to NOT match: ");
9216 prompt
= _("Keyword (or keyword initial) to match: ");
9220 prompt
= _("Keyword to NOT match: ");
9222 prompt
= _("Keyword to match: ");
9225 oe_flags
= OE_APPEND_CURRENT
;
9226 r
= optionally_enter(keyword
, -FOOTER_ROWS(ps_global
), 0,
9228 prompt
, sel_key_opt
, help
, &oe_flags
);
9231 /* select keyword from a list */
9232 if((kword
=choose_a_keyword(NULL
)) != NULL
){
9233 strncpy(keyword
, kword
, sizeof(keyword
)-1);
9234 keyword
[sizeof(keyword
)-1] = '\0';
9235 fs_give((void **) &kword
);
9241 int n
= keyword_complete(keyword
, sizeof(keyword
));
9243 if(n
> 1 && last_r
== 15 && !(oe_flags
& OE_USER_MODIFIED
)){
9244 /* double tab with multiple completions: select from list */
9245 if((kword
=choose_a_keyword(keyword
)) != NULL
){
9246 strncpy(keyword
, kword
, sizeof(keyword
)-1);
9247 keyword
[sizeof(keyword
)-1] = '\0';
9248 fs_give((void **) &kword
);
9261 help
= help
== NO_HELP
? h_select_keyword
: NO_HELP
;
9263 cmd_cancelled("Selection by keyword");
9267 removing_leading_and_trailing_white_space(keyword
);
9270 }while(r
== 3 || r
== 4 || r
== 15 || r
== '!' || keyword_check(keyword
, &error
));
9273 if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT
, ps_global
) && ps_global
->keywords
){
9274 p
= initial_to_keyword(keyword
);
9276 strncpy(keyword
, p
, sizeof(keyword
)-1);
9277 keyword
[sizeof(keyword
)-1] = '\0';
9282 * We want to check the keyword, not the nickname of the keyword,
9283 * so convert it to the keyword if necessary.
9285 p
= nick_to_keyword(keyword
);
9287 strncpy(keyword
, p
, sizeof(keyword
)-1);
9288 keyword
[sizeof(keyword
)-1] = '\0';
9291 pgm
= mail_newsearchpgm();
9293 pgm
->unkeyword
= mail_newstringlist();
9294 pgm
->unkeyword
->text
.data
= (unsigned char *) cpystr(keyword
);
9295 pgm
->unkeyword
->text
.size
= strlen(keyword
);
9298 pgm
->keyword
= mail_newstringlist();
9299 pgm
->keyword
->text
.data
= (unsigned char *) cpystr(keyword
);
9300 pgm
->keyword
->text
.size
= strlen(keyword
);
9303 if(ps_global
&& ps_global
->ttyo
){
9304 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
9305 ps_global
->mangled_footer
= 1;
9308 we_cancel
= busy_cue(_("Selecting"), NULL
, 1);
9310 pgm
->msgno
= (limitsrch
? *limitsrch
: NULL
);
9311 pine_mail_search_full(stream
, "UTF-8", pgm
, SE_NOPREFETCH
| SE_FREE
);
9312 /* we know this was freed in mail_search, let caller know */
9324 * Allow user to choose a keyword from their list of keywords.
9326 * Args prefix -- Only list keywords matching the given prefix
9328 * Returns an allocated keyword on success, NULL otherwise.
9331 choose_a_keyword(char *prefix
)
9333 char *choice
= NULL
;
9334 char **keyword_list
, **lp
;
9336 int prefix_length
= 0;
9338 void (*redraw
)(void) = ps_global
->redrawer
;
9341 * Build a list of keywords to choose from.
9345 prefix_length
= strlen(prefix
);
9347 for(cnt
= 0, kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
9348 char *kw_name
= kw
->nick
? kw
->nick
: kw
->kw
;
9349 if(!prefix
|| kw_name
&& strncmp(kw_name
, prefix
, prefix_length
) == 0)
9354 q_status_message(SM_ORDER
, 3, 4,
9355 _("No keywords defined, use \"keywords\" option in Setup/Config"));
9359 lp
= keyword_list
= (char **) fs_get((cnt
+ 1) * sizeof(*keyword_list
));
9360 memset(keyword_list
, 0, (cnt
+1) * sizeof(*keyword_list
));
9362 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
9363 char *kw_name
= kw
->nick
? kw
->nick
: kw
->kw
;
9366 if(!prefix
|| kw_name
&& strncmp(kw_name
, prefix
, prefix_length
) == 0)
9367 *lp
++ = cpystr(kw_name
);
9370 /* TRANSLATORS: SELECT A KEYWORD is a screen title
9371 TRANSLATORS: Print something1 using something2.
9372 "keywords" is something1 */
9373 choice
= choose_item_from_list(keyword_list
, NULL
, _("SELECT A KEYWORD"),
9374 _("keywords"), h_select_keyword_screen
,
9375 _("HELP FOR SELECTING A KEYWORD"), NULL
);
9378 q_status_message(SM_ORDER
, 1, 4, "No choice");
9379 ps_global
->redrawer
= redraw
;
9382 free_list_array(&keyword_list
);
9389 * Complete a partial keyword name against the user's list of keywords.
9391 * Args keyword -- Current keyword to complete
9392 * keyword_size -- Maximum length of the keyword array to store
9395 * Returns the number of valid completions, i.e.:
9397 * 0 if there are no valid completions. The value of the keyword argument has
9399 * 1 if there is only a single valid completion. The value of the keyword
9400 * argument has been replaced with the full text of that completion.
9401 * >1 if there is more than one valid completion. The value of the keyword
9402 * argument has been replaced with the longest substring common to all the
9403 * appropriate completions.
9406 keyword_complete(char *keyword
, int keyword_size
)
9408 char *candidate
= NULL
;
9410 int common_prefix_length
= 0;
9414 keyword_length
= strlen(keyword
);
9416 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
9417 char *kw_name
= kw
->nick
? kw
->nick
: kw
->kw
;
9420 if(strncmp(kw_name
, keyword
, keyword_length
) == 0){
9421 /* This is a candidate for completion. */
9424 /* This is the first candidate. Keep it as a future reference to
9425 * compare against to find the longest common prefix length of
9426 * all the matches. */
9427 candidate
= kw_name
;
9428 common_prefix_length
= strlen(candidate
);
9431 /* Find the common prefix length between the first candidate and
9434 for(i
= 0; i
< common_prefix_length
; i
++){
9435 if(kw_name
[i
] != candidate
[i
]){
9436 /* In the event that we ended up in the middle of a
9437 * UTF-8 code point, backtrack to the byte before the
9438 * start of this code point. */
9439 while(i
> 0 && (candidate
[i
] & 0xC0) == 0x80)
9441 common_prefix_length
= i
;
9450 int length
= MIN(keyword_size
, common_prefix_length
);
9451 strncpy(keyword
+ keyword_length
, candidate
+ keyword_length
, length
- keyword_length
);
9452 keyword
[length
] = '\0';
9460 * Allow user to choose a list of keywords from their list of keywords.
9462 * Returns allocated list.
9465 choose_list_of_keywords(void)
9467 LIST_SEL_S
*listhead
, *ls
, *p
;
9473 * Build a list of keywords to choose from.
9476 p
= listhead
= NULL
;
9477 for(kw
= ps_global
->keywords
; kw
; kw
= kw
->next
){
9479 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9480 memset(ls
, 0, sizeof(*ls
));
9481 ls
->item
= cpystr(kw
->nick
? kw
->nick
: kw
->kw
? kw
->kw
: "");
9494 /* TRANSLATORS: SELECT KEYWORDS is a screen title
9495 Print something1 using something2.
9496 "keywords" is something1 */
9497 if(!select_from_list_screen(listhead
, SFL_ALLOW_LISTMODE
,
9498 _("SELECT KEYWORDS"), _("keywords"),
9499 h_select_multkeyword_screen
,
9500 _("HELP FOR SELECTING KEYWORDS"), NULL
)){
9501 for(cnt
= 0, p
= listhead
; p
; p
= p
->next
)
9505 ret
= (char **) fs_get((cnt
+1) * sizeof(*ret
));
9506 memset(ret
, 0, (cnt
+1) * sizeof(*ret
));
9507 for(i
= 0, p
= listhead
; p
; p
= p
->next
)
9509 ret
[i
++] = cpystr(p
->item
? p
->item
: "");
9512 free_list_sel(&listhead
);
9519 * Allow user to choose a charset
9521 * Returns an allocated charset on success, NULL otherwise.
9524 choose_a_charset(int which_charsets
)
9526 char *choice
= NULL
;
9527 char **charset_list
, **lp
;
9530 void (*redraw
)(void) = ps_global
->redrawer
;
9533 * Build a list of charsets to choose from.
9536 for(cnt
= 0, cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
9537 if(!(cs
->flags
& (CF_UNSUPRT
|CF_NOEMAIL
))
9538 && ((which_charsets
& CAC_ALL
)
9539 || (which_charsets
& CAC_POSTING
9540 && cs
->flags
& CF_POSTING
)
9541 || (which_charsets
& CAC_DISPLAY
9542 && cs
->type
!= CT_2022
9543 && (cs
->flags
& (CF_PRIMARY
|CF_DISPLAY
)) == (CF_PRIMARY
|CF_DISPLAY
))))
9548 q_status_message(SM_ORDER
, 3, 4,
9549 _("No charsets found? Enter charset manually."));
9553 lp
= charset_list
= (char **) fs_get((cnt
+ 1) * sizeof(*charset_list
));
9554 memset(charset_list
, 0, (cnt
+1) * sizeof(*charset_list
));
9556 for(cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
9557 if(!(cs
->flags
& (CF_UNSUPRT
|CF_NOEMAIL
))
9558 && ((which_charsets
& CAC_ALL
)
9559 || (which_charsets
& CAC_POSTING
9560 && cs
->flags
& CF_POSTING
)
9561 || (which_charsets
& CAC_DISPLAY
9562 && cs
->type
!= CT_2022
9563 && (cs
->flags
& (CF_PRIMARY
|CF_DISPLAY
)) == (CF_PRIMARY
|CF_DISPLAY
))))
9564 *lp
++ = cpystr(cs
->name
);
9567 /* TRANSLATORS: SELECT A CHARACTER SET is a screen title
9568 TRANSLATORS: Print something1 using something2.
9569 "character sets" is something1 */
9570 choice
= choose_item_from_list(charset_list
, NULL
, _("SELECT A CHARACTER SET"),
9571 _("character sets"), h_select_charset_screen
,
9572 _("HELP FOR SELECTING A CHARACTER SET"), NULL
);
9575 q_status_message(SM_ORDER
, 1, 4, "No choice");
9576 ps_global
->redrawer
= redraw
;
9579 free_list_array(&charset_list
);
9586 * Allow user to choose a list of character sets and/or scripts
9588 * Returns allocated list.
9591 choose_list_of_charsets(void)
9593 LIST_SEL_S
*listhead
, *ls
, *p
;
9595 int cnt
, i
, got_one
;
9600 char buf
[1024], *folded
;
9603 * Build a list of charsets to choose from.
9606 p
= listhead
= NULL
;
9608 /* this width is determined by select_from_list_screen() */
9609 width
= ps_global
->ttyo
->screen_cols
- 4;
9611 /* first comes a list of scripts (sets of character sets) */
9612 for(s
= utf8_script(NIL
); s
&& s
->name
; s
++){
9614 limit
= sizeof(buf
)-1;
9616 memset(q
, 0, limit
+1);
9619 sstrncpy(&q
, s
->name
, limit
);
9622 sstrncpy(&q
, " (", limit
-(q
-buf
));
9623 sstrncpy(&q
, s
->description
, limit
-(q
-buf
));
9624 sstrncpy(&q
, ")", limit
-(q
-buf
));
9627 /* add the list of charsets that are in this script */
9629 for(cs
= utf8_charset(NIL
);
9630 cs
&& cs
->name
&& (q
-buf
) < limit
; cs
++){
9631 if(cs
->script
& s
->script
){
9633 * Filter out some un-useful members of the list.
9634 * UTF-7 and UTF-8 weren't actually in the list at the
9635 * time this was written. Just making sure.
9637 if(!strucmp(cs
->name
, "ISO-2022-JP-2")
9638 || !strucmp(cs
->name
, "UTF-7")
9639 || !strucmp(cs
->name
, "UTF-8"))
9643 sstrncpy(&q
, " ", limit
-(q
-buf
));
9646 sstrncpy(&q
, " {", limit
-(q
-buf
));
9649 sstrncpy(&q
, cs
->name
, limit
-(q
-buf
));
9654 sstrncpy(&q
, "}", limit
-(q
-buf
));
9656 /* fold this line so that it can all be seen on the screen */
9657 folded
= fold(buf
, width
, width
, "", " ", FLD_NONE
);
9660 while(t
&& *t
&& (q
= strindex(t
, '\n')) != NULL
){
9663 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9664 memset(ls
, 0, sizeof(*ls
));
9666 ls
->item
= cpystr(s
->name
);
9668 ls
->flags
= SFL_NOSELECT
;
9670 ls
->display_item
= cpystr(t
);
9680 listhead
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9681 memset(listhead
, 0, sizeof(*listhead
));
9682 listhead
->flags
= SFL_NOSELECT
;
9683 listhead
->display_item
=
9684 cpystr(_("Scripts representing groups of related character sets"));
9685 listhead
->next
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9686 memset(listhead
->next
, 0, sizeof(*listhead
));
9687 listhead
->next
->flags
= SFL_NOSELECT
;
9688 listhead
->next
->display_item
=
9689 cpystr(repeat_char(width
, '-'));
9691 listhead
->next
->next
= ls
;
9696 fs_give((void **) &folded
);
9700 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9701 memset(ls
, 0, sizeof(*ls
));
9702 ls
->flags
= SFL_NOSELECT
;
9710 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9711 memset(ls
, 0, sizeof(*ls
));
9712 ls
->flags
= SFL_NOSELECT
;
9714 cpystr(_("Individual character sets, may be mixed with scripts"));
9718 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9719 memset(ls
, 0, sizeof(*ls
));
9720 ls
->flags
= SFL_NOSELECT
;
9722 cpystr(repeat_char(width
, '-'));
9726 /* then comes a list of individual character sets */
9727 for(cs
= utf8_charset(NIL
); cs
&& cs
->name
; cs
++){
9728 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
9729 memset(ls
, 0, sizeof(*ls
));
9730 ls
->item
= cpystr(cs
->name
);
9743 /* TRANSLATORS: SELECT CHARACTER SETS is a screen title
9744 Print something1 using something2.
9745 "character sets" is something1 */
9746 if(!select_from_list_screen(listhead
, SFL_ALLOW_LISTMODE
,
9747 _("SELECT CHARACTER SETS"), _("character sets"),
9748 h_select_multcharsets_screen
,
9749 _("HELP FOR SELECTING CHARACTER SETS"), NULL
)){
9750 for(cnt
= 0, p
= listhead
; p
; p
= p
->next
)
9754 ret
= (char **) fs_get((cnt
+1) * sizeof(*ret
));
9755 memset(ret
, 0, (cnt
+1) * sizeof(*ret
));
9756 for(i
= 0, p
= listhead
; p
; p
= p
->next
)
9758 ret
[i
++] = cpystr(p
->item
? p
->item
: "");
9761 free_list_sel(&listhead
);
9766 /* Report quota summary resources in an IMAP server */
9769 cmd_quota (struct pine
*state
)
9771 QUOTALIST
*imapquota
;
9776 if(!state
->mail_stream
|| !is_imap_stream(state
->mail_stream
)){
9777 q_status_message(SM_ORDER
, 1, 5, "Quota only available for IMAP folders");
9781 if (state
->mail_stream
9782 && !sp_dead_stream(state
->mail_stream
)
9783 && state
->mail_stream
->mailbox
9784 && *state
->mail_stream
->mailbox
9785 && mail_valid_net_parse(state
->mail_stream
->mailbox
, &mb
))
9786 imap_getquotaroot(state
->mail_stream
, mb
.mailbox
);
9788 if(!state
->quota
) /* failed ? */
9789 return; /* go back... */
9791 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
9792 q_status_message(SM_ORDER
| SM_DING
, 3, 3, "Error allocating space.");
9796 so_puts(store
, "Quota Report for ");
9797 so_puts(store
, state
->mail_stream
->original_mailbox
);
9798 so_puts(store
, "\n\n");
9800 for (imapquota
= state
->quota
; imapquota
; imapquota
= imapquota
->next
){
9802 so_puts(store
, _("Resource : "));
9803 so_puts(store
, imapquota
->name
);
9804 so_writec('\n', store
);
9806 so_puts(store
, _("Usage : "));
9807 so_puts(store
, long2string(imapquota
->usage
));
9808 if(!strucmp(imapquota
->name
,"STORAGE"))
9809 so_puts(store
, " KiB ");
9810 if(!strucmp(imapquota
->name
,"MESSAGE")){
9811 so_puts(store
, _(" message"));
9812 if(imapquota
->usage
!= 1)
9813 so_puts(store
, _("s ")); /* plural */
9815 so_puts(store
, _(" "));
9817 so_writec('(', store
);
9818 so_puts(store
, long2string(100*imapquota
->usage
/imapquota
->limit
));
9819 so_puts(store
, "%)\n");
9821 so_puts(store
, _("Limit : "));
9822 so_puts(store
, long2string(imapquota
->limit
));
9823 if(!strucmp(imapquota
->name
,"STORAGE"))
9824 so_puts(store
, " KiB\n\n");
9825 if(!strucmp(imapquota
->name
,"MESSAGE")){
9826 so_puts(store
, _(" message"));
9827 if(imapquota
->usage
!= 1)
9828 so_puts(store
, _("s\n\n")); /* plural */
9830 so_puts(store
, _("\n\n"));
9834 memset(&sargs
, 0, sizeof(SCROLL_S
));
9835 sargs
.text
.text
= so_text(store
);
9836 sargs
.text
.src
= CharStar
;
9837 sargs
.text
.desc
= _("Quota Resources Summary");
9838 sargs
.bar
.title
= _("QUOTA SUMMARY");
9839 sargs
.proc
.tool
= NULL
;
9840 sargs
.help
.text
= h_quota_command
;
9841 sargs
.help
.title
= NULL
;
9842 sargs
.keys
.menu
= NULL
;
9843 setbitmap(sargs
.keys
.bitmap
);
9849 mail_free_quotalist(&(state
->quota
));
9852 /*----------------------------------------------------------------------
9853 Prompt the user for the type of sort he desires
9855 Args: state -- pine state pointer
9856 q1 -- Line to prompt on
9858 Returns 0 if it was cancelled, 1 otherwise.
9861 select_sort(struct pine
*state
, int ql
, SortOrder
*sort
, int *rev
)
9863 char prompt
[200], tmp
[3], *p
;
9865 int deefault
= 'a', retval
= 1;
9870 DLG_SORTPARAM sortsel
;
9872 if (mswin_usedialog ()) {
9874 sortsel
.reverse
= mn_get_revsort (state
->msgmap
);
9875 sortsel
.cursort
= mn_get_sort (state
->msgmap
);
9876 /* assumption here that HelpType is char ** */
9877 sortsel
.helptext
= h_select_sort
;
9880 if ((retval
= os_sortdialog (&sortsel
))) {
9881 *sort
= sortsel
.cursort
;
9882 *rev
= sortsel
.reverse
;
9889 /*----- String together the prompt ------*/
9891 if(F_ON(F_USE_FK
,ps_global
))
9892 strncpy(prompt
, _("Choose type of sort : "), sizeof(prompt
));
9894 strncpy(prompt
, _("Choose type of sort, or 'R' to reverse current sort : "),
9897 for(i
= 0; state
->sort_types
[i
] != EndofList
; i
++) {
9899 p
= sorts
[i
].label
= sort_name(state
->sort_types
[i
]);
9900 while(*(p
+1) && islower((unsigned char)*p
))
9903 sorts
[i
].ch
= tolower((unsigned char)(tmp
[0] = *p
));
9904 sorts
[i
].name
= cpystr(tmp
);
9906 if(mn_get_sort(state
->msgmap
) == state
->sort_types
[i
])
9907 deefault
= sorts
[i
].rval
;
9911 sorts
[i
].rval
= 'r';
9912 sorts
[i
].name
= cpystr("R");
9913 if(F_ON(F_USE_FK
,ps_global
))
9914 sorts
[i
].label
= N_("Reverse");
9916 sorts
[i
].label
= "";
9919 help
= h_select_sort
;
9921 if((F_ON(F_USE_FK
,ps_global
)
9922 && ((s
= double_radio_buttons(prompt
,ql
,sorts
,deefault
,'x',
9923 help
,RB_NORM
)) != 'x'))
9925 (F_OFF(F_USE_FK
,ps_global
)
9926 && ((s
= radio_buttons(prompt
,ql
,sorts
,deefault
,'x',
9927 help
,RB_NORM
)) != 'x'))){
9928 state
->mangled_body
= 1; /* signal screen's changed */
9930 *rev
= !mn_get_revsort(state
->msgmap
);
9932 *sort
= state
->sort_types
[s
];
9934 if(F_ON(F_SHOW_SORT
, ps_global
))
9935 ps_global
->mangled_header
= 1;
9939 cmd_cancelled("Sort");
9943 fs_give((void **)&sorts
[i
].name
);
9945 blank_keymenu(ps_global
->ttyo
->screen_rows
- 2, 0);
9950 /*---------------------------------------------------------------------
9951 Build list of folders in the given context for user selection
9953 Args: c -- pointer to pointer to folder's context context
9954 f -- folder prefix to display
9955 sublist -- whether or not to use 'f's contents as prefix
9956 lister -- function used to do the actual display
9958 Returns: malloc'd string containing sequence, else NULL if
9959 no messages in msgmap with local "selected" flag.
9962 display_folder_list(CONTEXT_S
**c
, char *f
, int sublist
, int (*lister
) (struct pine
*, CONTEXT_S
**, char *, int))
9966 void (*redraw
)(void) = ps_global
->redrawer
;
9968 push_titlebar_state();
9970 if((rc
= (*lister
)(ps_global
, &tc
, f
, sublist
)) != 0)
9974 pop_titlebar_state();
9976 if((ps_global
->redrawer
= redraw
) != NULL
) /* reset old value, and test */
9977 (*ps_global
->redrawer
)();
9979 if(rc
== 1 && F_ON(F_SELECT_WO_CONFIRM
, ps_global
))
9987 * Allow user to choose a single item from a list of strings.
9989 * Args list -- Array of strings to choose from, NULL terminated.
9990 * displist -- Array of strings to display instead of displaying list.
9991 * Indices correspond to the list array. Display the displist
9992 * but return the item from list if displist non-NULL.
9993 * title -- For conf_scroll_screen
9994 * pdesc -- For conf_scroll_screen
9995 * help -- For conf_scroll_screen
9996 * htitle -- For conf_scroll_screen
9998 * Returns an allocated copy of the chosen item or NULL.
10001 choose_item_from_list(char **list
, char **displist
, char *title
, char *pdesc
, HelpType help
,
10002 char *htitle
, char *cursor_location
)
10004 LIST_SEL_S
*listhead
, *ls
, *p
, *starting_val
= NULL
;
10006 char *ret
= NULL
, *choice
= NULL
;
10008 /* build the LIST_SEL_S list */
10009 p
= listhead
= NULL
;
10010 for(t
= list
, dl
= displist
; *t
; t
++, dl
++){
10011 ls
= (LIST_SEL_S
*) fs_get(sizeof(*ls
));
10012 memset(ls
, 0, sizeof(*ls
));
10013 ls
->item
= cpystr(*t
);
10015 ls
->display_item
= cpystr(*dl
);
10017 if(cursor_location
&& (cursor_location
== (*t
)))
10031 if(!select_from_list_screen(listhead
, SFL_NONE
, title
, pdesc
,
10032 help
, htitle
, starting_val
))
10033 for(p
= listhead
; !choice
&& p
; p
= p
->next
)
10038 ret
= cpystr(choice
);
10040 free_list_sel(&listhead
);
10047 free_list_sel(LIST_SEL_S
**lsel
)
10050 free_list_sel(&(*lsel
)->next
);
10052 fs_give((void **) &(*lsel
)->item
);
10054 if((*lsel
)->display_item
)
10055 fs_give((void **) &(*lsel
)->display_item
);
10057 fs_give((void **) lsel
);
10063 * file_lister - call pico library's file lister
10066 file_lister(char *title
, char *path
, size_t pathlen
, char *file
, size_t filelen
, int newmail
, int flags
)
10070 void (*redraw
)(void) = ps_global
->redrawer
;
10072 standard_picobuf_setup(&pbf
);
10073 push_titlebar_state();
10075 pbf
.newmail
= NULL
;
10077 /* BUG: what about help command and text? */
10078 pbf
.pine_anchor
= title
;
10080 rv
= pico_file_browse(&pbf
, path
, pathlen
, file
, filelen
, NULL
, 0, flags
);
10081 standard_picobuf_teardown(&pbf
);
10082 fix_windsize(ps_global
);
10083 init_signals(); /* has it's own signal stuff */
10085 /* Restore display's titlebar and body */
10086 pop_titlebar_state();
10088 if((ps_global
->redrawer
= redraw
) != NULL
)
10089 (*ps_global
->redrawer
)();
10095 /*----------------------------------------------------------------------
10096 Print current folder index
10100 print_index(struct pine
*state
, MSGNO_S
*msgmap
, int agg
)
10104 char buf
[MAX_SCREEN_COLS
+1];
10106 for(i
= 1L; i
<= mn_get_total(msgmap
); i
++){
10107 if(agg
&& !get_lflag(state
->mail_stream
, msgmap
, i
, MN_SLCT
))
10110 if(!agg
&& msgline_hidden(state
->mail_stream
, msgmap
, i
, 0))
10113 ice
= build_header_line(state
, state
->mail_stream
, msgmap
, i
, NULL
);
10117 * I don't understand why we'd want to mark the current message
10118 * instead of printing out the first character of the status
10119 * so I'm taking it out and including the first character of the
10120 * line instead. Hubert 2006-02-09
10122 if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' '))
10126 if(!gf_puts(simple_index_line(buf
,sizeof(buf
),ice
,i
),
10128 || !gf_puts(NEWLINE
, print_char
))
10139 * windows callback to get/set header mode state
10142 header_mode_callback(set
, args
)
10146 return(ps_global
->full_header
);
10151 * windows callback to get/set zoom mode state
10154 zoom_mode_callback(set
, args
)
10158 return(any_lflagged(ps_global
->msgmap
, MN_HIDE
) != 0);
10163 * windows callback to get/set zoom mode state
10166 any_selected_callback(set
, args
)
10170 return(any_lflagged(ps_global
->msgmap
, MN_SLCT
) != 0);
10178 flag_callback(set
, flags
)
10188 case 1: /* Important */
10189 permflag
= ps_global
->mail_stream
->perm_flagged
;
10193 permflag
= ps_global
->mail_stream
->perm_seen
;
10196 case 3: /* Answered */
10197 permflag
= ps_global
->mail_stream
->perm_answered
;
10200 case 4: /* Deleted */
10201 permflag
= ps_global
->mail_stream
->perm_deleted
;
10206 if(!(any_messages(ps_global
->msgmap
, NULL
, "to Flag")
10207 && can_set_flag(ps_global
, "flag", permflag
)))
10210 if(sp_io_error_on_stream(ps_global
->mail_stream
)){
10211 sp_set_io_error_on_stream(ps_global
->mail_stream
, 0);
10212 pine_mail_check(ps_global
->mail_stream
); /* forces write */
10216 msgno
= mn_m2raw(ps_global
->msgmap
, mn_get_cur(ps_global
->msgmap
));
10217 if(msgno
> 0L && ps_global
->mail_stream
10218 && msgno
<= ps_global
->mail_stream
->nmsgs
10219 && (mc
= mail_elt(ps_global
->mail_stream
, msgno
))
10222 * NOTE: code below is *VERY* sensitive to the order of
10223 * the messages defined in resource.h for flag handling.
10224 * Don't change it unless you know what you're doing.
10231 case 1 : /* Important */
10232 flagstr
= "\\FLAGGED";
10233 mflag
= (mc
->flagged
) ? 0L : ST_SET
;
10237 flagstr
= "\\SEEN";
10238 mflag
= (mc
->seen
) ? 0L : ST_SET
;
10241 case 3 : /* Answered */
10242 flagstr
= "\\ANSWERED";
10243 mflag
= (mc
->answered
) ? 0L : ST_SET
;
10246 case 4 : /* Deleted */
10247 flagstr
= "\\DELETED";
10248 mflag
= (mc
->deleted
) ? 0L : ST_SET
;
10251 default : /* bogus */
10255 mail_flag(ps_global
->mail_stream
, long2string(msgno
),
10258 if(ps_global
->redrawer
)
10259 (*ps_global
->redrawer
)();
10264 newflags
|= 0x0001;
10268 newflags
|= 0x0002;
10272 newflags
|= 0x0004;
10276 newflags
|= 0x0008;
10286 * BUG: Should teach this about keywords
10292 static MPopup flag_submenu
[] = {
10293 {tMessage
, {N_("Important"), lNormal
}, {IDM_MI_FLAGIMPORTANT
}},
10294 {tMessage
, {N_("New"), lNormal
}, {IDM_MI_FLAGNEW
}},
10295 {tMessage
, {N_("Answered"), lNormal
}, {IDM_MI_FLAGANSWERED
}},
10296 {tMessage
, {N_("Deleted"), lNormal
}, {IDM_MI_FLAGDELETED
}},
10301 flag_submenu
[0].label
.style
= (mc
&& mc
->flagged
) ? lChecked
: lNormal
;
10304 flag_submenu
[1].label
.style
= (mc
&& mc
->seen
) ? lNormal
: lChecked
;
10307 flag_submenu
[2].label
.style
= (mc
&& mc
->answered
) ? lChecked
: lNormal
;
10310 flag_submenu
[3].label
.style
= (mc
&& mc
->deleted
) ? lChecked
: lNormal
;
10312 return(flag_submenu
);
10315 #endif /* _WINDOWS */